Implemented no albums view and improved indexing of file content directly within an artist.

Every KotoArtist now has a dedicated KotoPlaylist that is dynamically generated during KotoArtist initialization and contains references to all KotoTracks.

Fixed weird parsing error with some track names that had single quote (ew) and made changes so various parts of the UX would not freak out when we didn't have an album associated with a track.
This commit is contained in:
Joshua Strobl 2021-07-06 13:06:20 +03:00
parent 2bf5aa9388
commit 34ca201121
19 changed files with 948 additions and 530 deletions

View file

@ -0,0 +1,486 @@
/* track-table.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 "../db/cartographer.h"
#include "../playlist/playlist.h"
#include "../koto-button.h"
#include "../koto-utils.h"
#include "../koto-window.h"
#include "koto-action-bar.h"
#include "track-table.h"
extern KotoActionBar * action_bar;
extern KotoCartographer * koto_maps;
extern KotoWindow * main_window;
struct _KotoTrackTable {
GObject parent_instance;
gchar * uuid;
KotoPlaylist * playlist;
GtkWidget * main;
GtkListItemFactory * item_factory;
GListModel * model;
GtkSelectionModel * selection_model;
GtkWidget * track_list_content;
GtkWidget * track_list_header;
GtkWidget * track_list_view;
KotoButton * track_album_button;
KotoButton * track_artist_button;
KotoButton * track_num_button;
KotoButton * track_title_button;
GtkSizeGroup * track_pos_size_group;
GtkSizeGroup * track_name_size_group;
GtkSizeGroup * track_album_size_group;
GtkSizeGroup * track_artist_size_group;
};
struct _KotoTrackTableClass {
GObjectClass parent_class;
};
G_DEFINE_TYPE(KotoTrackTable, koto_track_table, G_TYPE_OBJECT);
static void koto_track_table_class_init(KotoTrackTableClass * c) {
(void) c;
}
static void koto_track_table_init(KotoTrackTable * self) {
self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
self->track_name_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
self->track_pos_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
self->track_album_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
self->track_artist_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
self->item_factory = gtk_signal_list_item_factory_new(); // Create a new signal list item factory
g_signal_connect(self->item_factory, "setup", G_CALLBACK(koto_track_table_setup_track_item), self);
g_signal_connect(self->item_factory, "bind", G_CALLBACK(koto_track_table_bind_track_item), self);
self->track_list_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class((self->track_list_content), "track-list-content");
gtk_widget_set_hexpand(self->track_list_content, TRUE); // Expand horizontally
gtk_widget_set_vexpand(self->track_list_content, TRUE); // Expand vertically
self->track_list_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_add_css_class(self->track_list_header, "track-list-header");
koto_track_table_create_tracks_header(self); // Create our tracks header content
self->track_list_view = gtk_list_view_new(NULL, self->item_factory); // Create our list view with no model yet
gtk_widget_add_css_class(self->track_list_view, "track-list-columned");
gtk_widget_set_hexpand(self->track_list_view, TRUE); // Expand horizontally
gtk_widget_set_vexpand(self->track_list_view, TRUE); // Expand vertically
gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_header);
gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_view);
gtk_box_append(GTK_BOX(self->main), self->track_list_content);
g_signal_connect(action_bar, "closed", G_CALLBACK(koto_track_table_handle_action_bar_closed), self); // Handle closed action bar
}
void koto_track_table_bind_track_item(
GtkListItemFactory * factory,
GtkListItem * item,
KotoTrackTable * self
) {
(void) factory;
GtkWidget * track_position_label = gtk_widget_get_first_child(gtk_list_item_get_child(item));
GtkWidget * track_name_label = gtk_widget_get_next_sibling(track_position_label);
GtkWidget * track_album_label = gtk_widget_get_next_sibling(track_name_label);
GtkWidget * track_artist_label = gtk_widget_get_next_sibling(track_album_label);
KotoTrack * track = gtk_list_item_get_item(item); // Get the track UUID from our model
if (!KOTO_IS_TRACK(track)) {
return;
}
gchar * track_name = NULL;
gchar * album_uuid = NULL;
gchar * artist_uuid = NULL;
g_object_get(
track,
"parsed-name",
&track_name,
"album-uuid",
&album_uuid,
"artist-uuid",
&artist_uuid,
NULL
);
guint track_position = koto_playlist_get_position_of_track(self->playlist, track) + 1;
gtk_label_set_label(GTK_LABEL(track_position_label), g_strdup_printf("%u", track_position)); // Set the track position
gtk_label_set_label(GTK_LABEL(track_name_label), track_name); // Set our track name
if (koto_utils_is_string_valid(album_uuid)) { // Is associated with an album
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
if (KOTO_IS_ALBUM(album)) {
gtk_label_set_label(GTK_LABEL(track_album_label), koto_album_get_name(album)); // Get the name of the album and set it to the label
}
}
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
if (KOTO_IS_ARTIST(artist)) {
gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label
}
}
void koto_track_table_create_tracks_header(KotoTrackTable * self) {
self->track_num_button = koto_button_new_with_icon("#", "pan-down-symbolic", "pan-up-symbolic", KOTO_BUTTON_PIXBUF_SIZE_SMALL);
koto_button_add_click_handler(self->track_num_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_num_clicked), self);
koto_button_set_image_position(self->track_num_button, KOTO_BUTTON_IMAGE_POS_RIGHT); // Move the image to the right
gtk_size_group_add_widget(self->track_pos_size_group, GTK_WIDGET(self->track_num_button));
self->track_title_button = koto_button_new_plain("Title");
koto_button_add_click_handler(self->track_title_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_name_clicked), self);
gtk_size_group_add_widget(self->track_name_size_group, GTK_WIDGET(self->track_title_button));
self->track_album_button = koto_button_new_plain("Album");
gtk_widget_set_margin_start(GTK_WIDGET(self->track_album_button), 50);
koto_button_add_click_handler(self->track_album_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_album_clicked), self);
gtk_size_group_add_widget(self->track_album_size_group, GTK_WIDGET(self->track_album_button));
self->track_artist_button = koto_button_new_plain("Artist");
gtk_widget_set_margin_start(GTK_WIDGET(self->track_artist_button), 50);
koto_button_add_click_handler(self->track_artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_artist_clicked), self);
gtk_size_group_add_widget(self->track_artist_size_group, GTK_WIDGET(self->track_artist_button));
gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_num_button));
gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_title_button));
gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_album_button));
gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_artist_button));
}
GtkWidget * koto_track_table_get_main(KotoTrackTable * self) {
return self->main;
}
void koto_track_table_handle_action_bar_closed (
KotoActionBar * bar,
gpointer data
) {
(void) bar;
KotoTrackTable * self = data;
if (!KOTO_IS_TRACK_TABLE(self)) { // Self is not a track table
return;
}
if (!KOTO_IS_PLAYLIST(self->playlist)) { // No playlist set
return;
}
gtk_selection_model_unselect_all(self->selection_model);
gtk_widget_grab_focus(GTK_WIDGET(main_window)); // Focus on the window
}
void koto_track_table_handle_track_album_clicked (
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoTrackTable * self = user_data;
gtk_widget_add_css_class(GTK_WIDGET(self->track_album_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM);
}
void koto_track_table_handle_track_artist_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoTrackTable * self = user_data;
gtk_widget_add_css_class(GTK_WIDGET(self->track_artist_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST);
}
void koto_track_table_handle_track_name_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoTrackTable * self = user_data;
gtk_widget_add_css_class(GTK_WIDGET(self->track_title_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME);
}
void koto_track_table_handle_track_num_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoTrackTable * self = user_data;
KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist);
if (current_model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Set to newest currently
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST); // Sort reversed (oldest)
koto_button_show_image(self->track_num_button, TRUE); // Use inverted value (pan-up-symbolic)
} else {
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Sort newest
koto_button_show_image(self->track_num_button, FALSE); // Use pan down default
}
}
void koto_track_table_handle_tracks_selected(
GtkSelectionModel * model,
guint position,
guint n_items,
gpointer user_data
) {
(void) position;
KotoTrackTable * self = user_data;
if (!KOTO_IS_TRACK_TABLE(self)) { // Not a track table
return;
}
if (n_items == 0) { // No items selected
koto_action_bar_toggle_reveal(action_bar, FALSE); // Hide the action bar
return;
}
GtkBitset * selected_items_bitset = gtk_selection_model_get_selection(model); // Get the selected items as a GtkBitSet
GtkBitsetIter iter;
GList * selected_tracks = NULL;
GList * selected_tracks_pos = NULL;
guint first_track_pos;
if (!gtk_bitset_iter_init_first(&iter, selected_items_bitset, &first_track_pos)) { // Failed to get the first item
return;
}
selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(first_track_pos));
gboolean have_more_items = TRUE;
while (have_more_items) { // While we are able to get selected items
guint track_pos;
have_more_items = gtk_bitset_iter_next(&iter, &track_pos);
if (have_more_items) { // Got the next track
selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(track_pos));
}
}
GList * cur_pos_list;
for (cur_pos_list = selected_tracks_pos; cur_pos_list != NULL; cur_pos_list = cur_pos_list->next) { // Iterate over every position that we accumulated
KotoTrack * selected_track = g_list_model_get_item(self->model, GPOINTER_TO_UINT(cur_pos_list->data)); // Get the KotoTrack in the GListModel for this current position
selected_tracks = g_list_append(selected_tracks, selected_track); // Add to selected tracks
}
koto_action_bar_set_tracks_in_playlist_selection(action_bar, koto_playlist_get_uuid(self->playlist), selected_tracks); // Set the tracks for the playlist selection
koto_action_bar_toggle_reveal(action_bar, TRUE); // Show the items
}
void koto_track_table_set_model(
KotoTrackTable * self,
KotoPreferredModelType model
) {
if (!KOTO_IS_TRACK_TABLE(self)) {
return;
}
koto_playlist_apply_model(self->playlist, model); // Apply our new model
self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active");
}
self->selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(self->model));
g_signal_connect(self->selection_model, "selection-changed", G_CALLBACK(koto_track_table_handle_tracks_selected), self); // Bind to our selection changed
gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model
}
void koto_track_table_set_playlist_model (
KotoTrackTable * self,
KotoPreferredModelType model
) {
if (!KOTO_IS_TRACK_TABLE(self)) { // Not a track table
return;
}
if (!KOTO_IS_PLAYLIST(self->playlist)) { // Don't have a playlist yet
return;
}
koto_playlist_apply_model(self->playlist, model); // Apply our new model
self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active");
}
self->selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(self->model));
g_signal_connect(self->selection_model, "selection-changed", G_CALLBACK(koto_track_table_handle_tracks_selected), self); // Bind to our selection changed
gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model
KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model
if (current_model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) {
koto_button_show_image(self->track_num_button, TRUE); // Immediately use pan-up-symbolic
}
}
void koto_track_table_set_playlist(
KotoTrackTable * self,
KotoPlaylist * playlist
) {
if (!KOTO_IS_TRACK_TABLE(self)) {
return;
}
if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist
return;
}
self->playlist = playlist;
koto_track_table_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // TODO: Enable this to be changed
}
void koto_track_table_setup_track_item(
GtkListItemFactory * factory,
GtkListItem * item,
gpointer user_data
) {
(void) factory;
KotoTrackTable * self = user_data;
if (!KOTO_IS_TRACK_TABLE(self)) {
return;
}
GtkWidget * item_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Have a horizontal box for our content
gtk_widget_add_css_class(item_content, "track-list-columned-item");
GtkWidget * track_number = gtk_label_new(NULL); // Our track number
gtk_label_set_xalign(GTK_LABEL(track_number), 0);
gtk_widget_add_css_class(track_number, "track-column-number");
gtk_widget_set_halign(track_number, GTK_ALIGN_END);
gtk_widget_set_hexpand(track_number, FALSE);
gtk_widget_set_size_request(track_number, 150, -1);
gtk_box_append(GTK_BOX(item_content), track_number);
gtk_size_group_add_widget(self->track_pos_size_group, track_number);
GtkWidget * track_name = gtk_label_new(NULL); // Our track name
gtk_label_set_xalign(GTK_LABEL(track_name), 0);
gtk_widget_add_css_class(track_name, "track-column-name");
gtk_widget_set_halign(track_name, GTK_ALIGN_START);
gtk_widget_set_hexpand(track_name, FALSE);
gtk_widget_set_size_request(track_name, 350, -1);
gtk_box_append(GTK_BOX(item_content), track_name);
gtk_size_group_add_widget(self->track_name_size_group, track_name);
GtkWidget * track_album = gtk_label_new(NULL); // Our track album
gtk_label_set_xalign(GTK_LABEL(track_album), 0);
gtk_widget_add_css_class(track_album, "track-column-album");
gtk_widget_set_halign(track_album, GTK_ALIGN_START);
gtk_widget_set_hexpand(track_album, FALSE);
gtk_widget_set_margin_start(track_album, 50);
gtk_widget_set_size_request(track_album, 350, -1);
gtk_box_append(GTK_BOX(item_content), track_album);
gtk_size_group_add_widget(self->track_album_size_group, track_album);
GtkWidget * track_artist = gtk_label_new(NULL); // Our track artist
gtk_label_set_xalign(GTK_LABEL(track_artist), 0);
gtk_widget_add_css_class(track_artist, "track-column-artist");
gtk_widget_set_halign(track_artist, GTK_ALIGN_START);
gtk_widget_set_hexpand(track_artist, TRUE);
gtk_widget_set_margin_start(track_artist, 50);
gtk_widget_set_size_request(track_artist, 350, -1);
gtk_box_append(GTK_BOX(item_content), track_artist);
gtk_size_group_add_widget(self->track_artist_size_group, track_artist);
gtk_list_item_set_child(item, item_content);
}
KotoTrackTable * koto_track_table_new() {
return g_object_new(
KOTO_TYPE_TRACK_TABLE,
NULL
);
}

View file

@ -0,0 +1,114 @@
/* track-table.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 "../playlist/playlist.h"
#include "koto-action-bar.h"
G_BEGIN_DECLS
#define KOTO_TYPE_TRACK_TABLE koto_track_table_get_type()
#define KOTO_TRACK_TABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_TRACK_TABLE, KotoTrackTable))
typedef struct _KotoTrackTable KotoTrackTable;
typedef struct _KotoTrackTableClass KotoTrackTableClass;
GLIB_AVAILABLE_IN_ALL
GType koto_track_type_get_type(void) G_GNUC_CONST;
#define KOTO_IS_TRACK_TABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_TRACK_TABLE))
void koto_track_table_bind_track_item(
GtkListItemFactory * factory,
GtkListItem * item,
KotoTrackTable * self
);
void koto_track_table_create_tracks_header(KotoTrackTable * self);
GtkWidget * koto_track_table_get_main(KotoTrackTable * self);
void koto_track_table_handle_action_bar_closed(
KotoActionBar * bar,
gpointer data
);
void koto_track_table_handle_track_album_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_track_table_handle_track_artist_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_track_table_handle_track_name_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_track_table_handle_track_num_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_track_table_handle_tracks_selected(
GtkSelectionModel * model,
guint position,
guint n_items,
gpointer user_data
);
void koto_track_table_set_model(
KotoTrackTable * self,
KotoPreferredModelType model
);
void koto_track_table_set_playlist_model(
KotoTrackTable * self,
KotoPreferredModelType model
);
void koto_track_table_set_playlist(
KotoTrackTable * self,
KotoPlaylist * playlist
);
void koto_track_table_setup_track_item(
GtkListItemFactory * factory,
GtkListItem * item,
gpointer user_data
);
KotoTrackTable * koto_track_table_new();
G_END_DECLS

View file

@ -61,6 +61,8 @@ int process_artists(
return 1;
}
koto_artist_set_as_finalized(artist); // Indicate it is finalized
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE artist_id=\"%s\"", artist_uuid), process_tracks, NULL, NULL); // Process our tracks by artist uuid
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks

View file

@ -0,0 +1,25 @@
/* artist-playlist-funcs.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 "../playlist/playlist.h"
#include "structs.h"
G_BEGIN_DECLS
KotoPlaylist * koto_artist_get_playlist(KotoArtist * self);
G_END_DECLS

View file

@ -19,7 +19,9 @@
#include <sqlite3.h>
#include "../db/db.h"
#include "../db/cartographer.h"
#include "../playlist/playlist.h"
#include "../koto-utils.h"
#include "artist-playlist-funcs.h"
#include "structs.h"
#include "track-helpers.h"
@ -39,6 +41,7 @@ static GParamSpec * props[N_PROPERTIES] = {
enum {
SIGNAL_ALBUM_ADDED,
SIGNAL_ALBUM_REMOVED,
SIGNAL_HAS_NO_ALBUMS,
SIGNAL_TRACK_ADDED,
SIGNAL_TRACK_REMOVED,
N_SIGNALS
@ -52,6 +55,9 @@ struct _KotoArtist {
GObject parent_instance;
gchar * uuid;
KotoPlaylist * content_playlist;
gboolean finalized;
gboolean has_artist_art;
gchar * artist_name;
GList * albums;
@ -71,6 +77,7 @@ struct _KotoArtistClass {
KotoArtist * artist,
KotoAlbum * album
);
void (* has_no_albums) (KotoArtist * artist);
void (* track_added) (
KotoArtist * artist,
KotoTrack * track
@ -130,6 +137,18 @@ static void koto_artist_class_init(KotoArtistClass * c) {
KOTO_TYPE_ALBUM
);
artist_signals[SIGNAL_HAS_NO_ALBUMS] = g_signal_new(
"has-no-albums",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoArtistClass, has_no_albums),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0
);
artist_signals[SIGNAL_TRACK_ADDED] = g_signal_new(
"track-added",
G_TYPE_FROM_CLASS(gobject_class),
@ -175,6 +194,24 @@ static void koto_artist_class_init(KotoArtistClass * c) {
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
}
static void koto_artist_init(KotoArtist * self) {
self->albums = NULL; // Create a new GList
self->content_playlist = koto_playlist_new(); // Create our playlist
g_object_set(
self->content_playlist,
"ephemeral", // Indicate that it is temporary
TRUE,
NULL
);
self->finalized = FALSE; // Indicate we not finalized
self->has_artist_art = FALSE;
self->paths = g_hash_table_new(g_str_hash, g_str_equal);
self->tracks = NULL;
self->type = KOTO_LIBRARY_TYPE_UNKNOWN;
}
void koto_artist_commit(KotoArtist * self) {
if ((self->uuid == NULL) || strcmp(self->uuid, "")) { // UUID not set
self->uuid = g_strdup(g_uuid_string_random());
@ -211,12 +248,12 @@ void koto_artist_commit(KotoArtist * self) {
}
}
static void koto_artist_init(KotoArtist * self) {
self->albums = NULL; // Create a new GList
self->has_artist_art = FALSE;
self->paths = g_hash_table_new(g_str_hash, g_str_equal);
self->tracks = NULL;
self->type = KOTO_LIBRARY_TYPE_UNKNOWN;
KotoPlaylist * koto_artist_get_playlist(KotoArtist * self) {
if (!KOTO_IS_ARTIST(self)) {
return NULL;
}
return self->content_playlist;
}
static void koto_artist_get_property(
@ -311,6 +348,8 @@ void koto_artist_add_track(
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL);
koto_playlist_add_track(self->content_playlist, track, FALSE, FALSE); // Add this new track for the artist to its playlist
g_signal_emit(
self,
artist_signals[SIGNAL_TRACK_ADDED],
@ -408,8 +447,11 @@ void koto_artist_remove_track(
return;
}
gchar * track_uuid = koto_track_get_uuid(track);
self->tracks = g_list_remove(self->tracks, koto_track_get_uuid(track));
koto_playlist_remove_track_by_uuid(self->content_playlist, track_uuid); // Remove the track from our playlist
g_signal_emit(
self,
artist_signals[SIGNAL_TRACK_ADDED],
@ -438,6 +480,18 @@ void koto_artist_set_artist_name(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_NAME]);
}
void koto_artist_set_as_finalized(KotoArtist * self) {
if (!KOTO_IS_ARTIST(self)) { // Not an artist
return;
}
self->finalized = TRUE;
if (g_list_length(self->albums) == 0) { // Have no albums
g_signal_emit_by_name(self, "has-no-albums");
}
}
void koto_artist_set_path(
KotoArtist * self,
KotoLibrary * lib,

View file

@ -57,6 +57,7 @@ void index_folder(
koto_artist_set_path(artist, self, full_path, TRUE); // Add the path for this library on this Artist and commit immediately
koto_cartographer_add_artist(koto_maps, artist); // Add the artist to cartographer
index_folder(self, full_path, depth); // Index this directory
koto_artist_set_as_finalized(artist); // Indicate it is finalized
}
} else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album
gchar * artist_name = g_path_get_basename(path); // Get the last entry from our path which is probably the artist
@ -197,10 +198,6 @@ void index_file(
album = KOTO_IS_ALBUM(possible_album) ? possible_album : NULL;
}
if (!KOTO_IS_ALBUM(album)) {
return;
}
gchar * album_uuid = KOTO_IS_ALBUM(album) ? koto_album_get_uuid(album) : NULL;
track = koto_track_new(koto_artist_get_uuid(artist), album_uuid, file_name, cd);

View file

@ -180,6 +180,8 @@ void koto_artist_set_artist_name(
gchar * artist_name
);
void koto_artist_set_as_finalized(KotoArtist * self);
void koto_artist_set_path(
KotoArtist * self,
KotoLibrary * lib,
@ -282,6 +284,11 @@ void koto_track_remove_from_playlist(
gchar * playlist_uuid
);
void koto_track_set_album_uuid(
KotoTrack * self,
const gchar * album_uuid
);
void koto_track_save_to_playlist(
KotoTrack * self,
gchar * playlist_uuid,

View file

@ -210,8 +210,7 @@ static void koto_track_set_property(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_UUID]);
break;
case PROP_ALBUM_UUID:
self->album_uuid = g_strdup(g_value_get_string(val));
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ALBUM_UUID]);
koto_track_set_album_uuid(self, g_value_get_string(val));
break;
case PROP_UUID:
self->uuid = g_strdup(g_value_get_string(val));
@ -260,12 +259,14 @@ void koto_track_commit(KotoTrack * self) {
self->uuid,
self->artist_uuid,
self->album_uuid,
self->parsed_name,
g_strescape(self->parsed_name, NULL),
(int) self->cd,
(int) self->position
);
new_transaction(commit_op, "Failed to write our file to the database", FALSE);
if (new_transaction(commit_op, "Failed to write our file to the database", FALSE) != SQLITE_OK) {
g_message("Failed with song: %s", self->parsed_name);
}
GHashTableIter paths_iter;
g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths
@ -315,7 +316,7 @@ GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new_string(album_name));
}
} else {
} // TODO: Implement artist artwork fetching here
} // TODO: Implement artist artwork fetching here
g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(self->uuid));
@ -362,10 +363,10 @@ gchar * koto_track_get_path(KotoTrack * self) {
gchar * path = NULL;
while (g_hash_table_iter_next(&iter, &uuidptr, &relpathptr)) { // Iterate over all the paths for this file
KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, (gchar *) uuidptr);
KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, (gchar*) uuidptr);
if (KOTO_IS_LIBRARY(library)) {
path = g_strdup(g_build_path(G_DIR_SEPARATOR_S, koto_library_get_path(library), koto_library_get_relative_path_to_file(library, (gchar *) relpathptr), NULL)); // Build our full library path using library's path and our file relative path
path = g_strdup(g_build_path(G_DIR_SEPARATOR_S, koto_library_get_path(library), koto_library_get_relative_path_to_file(library, (gchar*) relpathptr), NULL)); // Build our full library path using library's path and our file relative path
break;
}
}
@ -426,6 +427,28 @@ void koto_track_remove_from_playlist(
new_transaction(commit_op, "Failed to remove track from playlist", FALSE);
}
void koto_track_set_album_uuid(
KotoTrack * self,
const gchar * album_uuid
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (album_uuid == NULL) {
return;
}
gchar * uuid = g_strdup(album_uuid);
if (!koto_utils_is_string_valid(uuid)) { // If this is not a valid string
return;
}
self->album_uuid = uuid;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ALBUM_UUID]);
}
void koto_track_save_to_playlist(
KotoTrack * self,
gchar * playlist_uuid,

View file

@ -26,6 +26,7 @@
#include "koto-button.h"
#include "config/config.h"
#include "koto-playerbar.h"
#include "koto-utils.h"
extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup;
extern KotoCartographer * koto_maps;
@ -616,10 +617,8 @@ void koto_playerbar_update_track_info(
g_object_get(current_track, "parsed-name", &track_name, "artist-uuid", &artist_uuid, "album-uuid", &album_uuid, NULL);
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
g_free(artist_uuid);
g_free(album_uuid);
if ((track_name != NULL) && (strcmp(track_name, "") != 0)) { // Have a track name
gtk_label_set_text(GTK_LABEL(bar->playback_title), track_name); // Set the label
@ -637,6 +636,13 @@ void koto_playerbar_update_track_info(
}
}
if (!koto_utils_is_string_valid(album_uuid)) { // Do not have a valid album UUID
return;
}
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
g_free(album_uuid);
if (KOTO_IS_ALBUM(album)) {
gchar * album_name = NULL;
gchar * art_path = NULL;

View file

@ -46,6 +46,9 @@ extern GList * supported_mimes;
extern gchar * koto_path_to_conf;
extern gchar * koto_rev_dns;
extern gboolean created_new_db;
GVolumeMonitor * volume_monitor = NULL;
GtkApplication * app = NULL;
GtkWindow * main_window;
@ -59,7 +62,10 @@ static void on_activate (GtkApplication * app) {
main_window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL);
setup_mpris_interfaces(); // Set up our MPRIS interfaces
setup_mediakeys_interface(); // Set up our media key support
read_from_db(); // Read the database, allowing us to propagate the UI with various data such as artists and playlist navigation elements
if (!created_new_db) {
read_from_db(); // Read the database, allowing us to propagate the UI with various data such as artists and playlist navigation elements
}
}
gtk_window_present(main_window);

View file

@ -7,6 +7,7 @@ add_global_arguments([
koto_sources = [
'components/koto-action-bar.c',
'components/koto-cover-art-button.c',
'components/track-table.c',
'config/config.c',
'db/cartographer.c',
'db/db.c',

View file

@ -17,13 +17,18 @@
#include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/koto-cover-art-button.h"
#include "../../components/track-table.h"
#include "../../db/cartographer.h"
#include "../../indexer/artist-playlist-funcs.h"
#include "../../indexer/structs.h"
#include "../../playlist/current.h"
#include "album-view.h"
#include "artist-view.h"
#include "config/config.h"
#include "koto-utils.h"
extern KotoCurrentPlaylist * current_playlist;
extern KotoCartographer * koto_maps;
struct _KotoArtistView {
@ -33,6 +38,12 @@ struct _KotoArtistView {
GtkWidget * content;
GtkWidget * album_list;
GtkWidget * no_albums_view;
GtkWidget * no_albums_artist_header;
KotoCoverArtButton * no_albums_artist_button;
GtkWidget * no_albums_artist_label;
KotoTrackTable * table;
GHashTable * albums_to_component;
};
@ -47,7 +58,6 @@ enum {
static GParamSpec * props[N_PROPERTIES] = {
NULL,
};
static void koto_artist_view_constructed(GObject * obj);
static void koto_artist_view_get_property(
GObject * obj,
@ -67,7 +77,6 @@ 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;
@ -82,6 +91,50 @@ static void koto_artist_view_class_init(KotoArtistViewClass * c) {
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
}
static void koto_artist_view_init(KotoArtistView * self) {
self->artist = NULL;
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
gtk_widget_add_css_class(GTK_WIDGET(self->scrolled_window), "artist-view");
gtk_widget_add_css_class(GTK_WIDGET(self->content), "artist-view-content");
self->album_list = gtk_flow_box_new(); // Create our list of our albums
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->album_list), GTK_SELECTION_NONE);
gtk_widget_add_css_class(self->album_list, "album-list");
self->no_albums_view = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class(self->no_albums_view, "no-albums-view");
self->no_albums_artist_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_add_css_class(self->no_albums_artist_header, "no-albums-view-header"),
self->no_albums_artist_button = koto_cover_art_button_new(220, 220, NULL);
KotoButton * cover_art_button = koto_cover_art_button_get_button(self->no_albums_artist_button); // Get the button for the KotoCoverArt
koto_button_add_click_handler(cover_art_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_artist_view_toggle_playback), self); // Handle clicking the button
self->no_albums_artist_label = gtk_label_new(NULL); // Create a label without any artist name
gtk_box_append(GTK_BOX(self->no_albums_artist_header), koto_cover_art_button_get_main(self->no_albums_artist_button)); // Add our button to the header
gtk_box_append(GTK_BOX(self->no_albums_artist_header), self->no_albums_artist_label);
gtk_box_append(GTK_BOX(self->no_albums_view), self->no_albums_artist_header); // Add the header to the no albums view
self->table = koto_track_table_new(); //Create our track table
gtk_box_append(GTK_BOX(self->no_albums_view), koto_track_table_get_main(self->table)); // Add the table to the no albums view
gtk_box_append(GTK_BOX(self->content), self->album_list); // Add the album flowbox
gtk_box_append(GTK_BOX(self->content), self->no_albums_view); // Add the no albums view just in case we do not have any albums
gtk_widget_set_hexpand(GTK_WIDGET(self->album_list), TRUE);
gtk_widget_set_hexpand(GTK_WIDGET(self->no_albums_view), TRUE);
}
static void koto_artist_view_get_property(
GObject * obj,
guint prop_id,
@ -118,34 +171,6 @@ static void koto_artist_view_set_property(
}
}
static void koto_artist_view_init(KotoArtistView * self) {
self->artist = NULL;
self->albums_to_component = g_hash_table_new(g_str_hash, g_str_equal);
}
static void koto_artist_view_constructed(GObject * obj) {
KotoArtistView * self = KOTO_ARTIST_VIEW(obj);
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
gtk_widget_add_css_class(GTK_WIDGET(self->scrolled_window), "artist-view");
gtk_widget_add_css_class(GTK_WIDGET(self->content), "artist-view-content");
self->album_list = gtk_flow_box_new(); // Create our list of our albums
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->album_list), GTK_SELECTION_NONE);
gtk_widget_add_css_class(self->album_list, "album-list");
gtk_box_append(GTK_BOX(self->content), self->album_list); // Add the list
gtk_widget_set_hexpand(GTK_WIDGET(self->album_list), TRUE);
G_OBJECT_CLASS(koto_artist_view_parent_class)->constructed(obj);
}
void koto_artist_view_add_album(
KotoArtistView * self,
KotoAlbum * album
@ -169,6 +194,13 @@ void koto_artist_view_add_album(
gtk_flow_box_insert(GTK_FLOW_BOX(self->album_list), album_view_main, -1); // Append the album view to the album list
g_hash_table_replace(self->albums_to_component, album_uuid, album_view_main);
gtk_widget_hide(self->no_albums_view); // Hide the no album view
gtk_widget_show(self->album_list); // Show the album list now that it has albums
}
GtkWidget * koto_artist_view_get_main(KotoArtistView * self) {
return self->scrolled_window;
}
void koto_artist_view_handle_album_added(
@ -222,6 +254,30 @@ void koto_artist_view_handle_album_removed(
g_hash_table_remove(self->albums_to_component, album_uuid); // Remove the album from our hash table
}
void koto_artist_view_handle_artist_name_changed(
KotoArtist * artist,
guint prop_id,
KotoArtistView * self
) {
(void) prop_id;
gtk_label_set_text(GTK_LABEL(self->no_albums_artist_label), koto_artist_get_name(artist)); // Update the label for the artist now that it has changed
}
void koto_artist_view_handle_has_no_albums(
KotoArtist * artist,
gpointer user_data
) {
(void) artist;
KotoArtistView * self = user_data;
if (!KOTO_IS_ARTIST_VIEW(self)) {
return;
}
gtk_widget_hide(self->album_list); // Hide our album list flowbox
gtk_widget_show(self->no_albums_view); // Show the no albums view
}
void koto_artist_view_set_artist(
KotoArtistView * self,
KotoArtist * artist
@ -235,12 +291,39 @@ void koto_artist_view_set_artist(
}
self->artist = artist;
koto_track_table_set_playlist(self->table, koto_artist_get_playlist(self->artist)); // Set our track table to the artist's playlist
gtk_label_set_text(GTK_LABEL(self->no_albums_artist_label), koto_artist_get_name(self->artist)); // Update our label with the name of the artist
g_signal_connect(artist, "album-added", G_CALLBACK(koto_artist_view_handle_album_added), self);
g_signal_connect(artist, "album-removed", G_CALLBACK(koto_artist_view_handle_album_removed), self);
g_signal_connect(artist, "has-no-albums", G_CALLBACK(koto_artist_view_handle_has_no_albums), self);
g_signal_connect(artist, "notify::name", G_CALLBACK(koto_artist_view_handle_artist_name_changed), self);
}
GtkWidget * koto_artist_view_get_main(KotoArtistView * self) {
return self->scrolled_window;
void koto_artist_view_toggle_playback(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoArtistView * self = data;
if (!KOTO_IS_ARTIST_VIEW(self)) {
return;
}
KotoPlaylist * artist_playlist = koto_artist_get_playlist(self->artist);
if (!KOTO_IS_PLAYLIST(artist_playlist)) { // Failed our is playlist check for the artist playlist
return;
}
koto_current_playlist_set_playlist(current_playlist, artist_playlist); // Set our playlist to the one associated with the Artist
}
KotoArtistView * koto_artist_view_new(KotoArtist * artist) {

View file

@ -34,6 +34,8 @@ void koto_artist_view_add_album(
KotoAlbum * album
);
GtkWidget * koto_artist_view_get_main(KotoArtistView * self);
void koto_artist_view_handle_album_added(
KotoArtist * artist,
KotoAlbum * album,
@ -46,11 +48,28 @@ void koto_artist_view_handle_album_removed(
gpointer user_data
);
void koto_artist_view_handle_artist_name_changed(
KotoArtist * artist,
guint prop_id,
KotoArtistView * self
);
void koto_artist_view_handle_has_no_albums(
KotoArtist * artist,
gpointer user_data
);
void koto_artist_view_set_artist(
KotoArtistView * self,
KotoArtist * artist
);
GtkWidget * koto_artist_view_get_main(KotoArtistView * self);
void koto_artist_view_toggle_playback(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer data
);
G_END_DECLS

View file

@ -19,7 +19,9 @@
#include <gtk-4.0/gtk/gtk.h>
#include "../../components/koto-action-bar.h"
#include "../../components/koto-cover-art-button.h"
#include "../../components/track-table.h"
#include "../../db/cartographer.h"
#include "../../indexer/structs.h"
#include "../../playlist/current.h"
#include "../../playlist/playlist.h"
#include "../../koto-button.h"
@ -59,23 +61,7 @@ struct _KotoPlaylistPage {
KotoButton * favorite_button;
KotoButton * edit_button;
GtkListItemFactory * item_factory;
GListModel * model;
GtkSelectionModel * selection_model;
GtkWidget * track_list_content;
GtkWidget * track_list_header;
GtkWidget * track_list_view;
KotoButton * track_num_button;
KotoButton * track_title_button;
KotoButton * track_album_button;
KotoButton * track_artist_button;
GtkSizeGroup * track_pos_size_group;
GtkSizeGroup * track_name_size_group;
GtkSizeGroup * track_album_size_group;
GtkSizeGroup * track_artist_size_group;
KotoTrackTable * table;
};
struct _KotoPlaylistPageClass {
@ -117,11 +103,6 @@ static void koto_playlist_page_class_init(KotoPlaylistPageClass * c) {
}
static void koto_playlist_page_init(KotoPlaylistPage * self) {
self->track_name_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
self->track_pos_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
self->track_album_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
self->track_artist_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
self->main = gtk_scrolled_window_new(); // Create our scrolled window
self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class(self->content, "playlist-page");
@ -166,30 +147,9 @@ static void koto_playlist_page_init(KotoPlaylistPage * self) {
gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->favorite_button)); // Add the favorite button
gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->edit_button)); // Add the edit button
self->item_factory = gtk_signal_list_item_factory_new(); // Create a new signal list item factory
g_signal_connect(self->item_factory, "setup", G_CALLBACK(koto_playlist_page_setup_track_item), self);
g_signal_connect(self->item_factory, "bind", G_CALLBACK(koto_playlist_page_bind_track_item), self);
self->track_list_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_add_css_class((self->track_list_content), "track-list-content");
gtk_widget_set_hexpand(self->track_list_content, TRUE); // Expand horizontally
gtk_widget_set_vexpand(self->track_list_content, TRUE); // Expand vertically
self->track_list_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_add_css_class(self->track_list_header, "track-list-header");
koto_playlist_page_create_tracks_header(self); // Create our tracks header content
self->track_list_view = gtk_list_view_new(NULL, self->item_factory); // Create our list view with no model yet
gtk_widget_add_css_class(self->track_list_view, "track-list-columned");
gtk_widget_set_hexpand(self->track_list_view, TRUE); // Expand horizontally
gtk_widget_set_vexpand(self->track_list_view, TRUE); // Expand vertically
gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_header);
gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_view);
gtk_box_append(GTK_BOX(self->content), self->track_list_content);
g_signal_connect(action_bar, "closed", G_CALLBACK(koto_playlist_page_handle_action_bar_closed), self); // Handle closed action bar
self->table = koto_track_table_new(); // Create our new KotoTrackTable
GtkWidget * track_main = koto_track_table_get_main(self->table);
gtk_box_append(GTK_BOX(self->content), track_main);
}
static void koto_playlist_page_get_property(
@ -228,104 +188,10 @@ static void koto_playlist_page_set_property(
}
}
void koto_playlist_page_bind_track_item(
GtkListItemFactory * factory,
GtkListItem * item,
KotoPlaylistPage * self
) {
(void) factory;
GtkWidget * track_position_label = gtk_widget_get_first_child(gtk_list_item_get_child(item));
GtkWidget * track_name_label = gtk_widget_get_next_sibling(track_position_label);
GtkWidget * track_album_label = gtk_widget_get_next_sibling(track_name_label);
GtkWidget * track_artist_label = gtk_widget_get_next_sibling(track_album_label);
KotoTrack * track = gtk_list_item_get_item(item); // Get the track UUID from our model
if (!KOTO_IS_TRACK(track)) {
return;
}
gchar * track_name = NULL;
gchar * album_uuid = NULL;
gchar * artist_uuid = NULL;
g_object_get(
track,
"parsed-name",
&track_name,
"album-uuid",
&album_uuid,
"artist-uuid",
&artist_uuid,
NULL
);
guint track_position = koto_playlist_get_position_of_track(self->playlist, track) + 1;
gtk_label_set_label(GTK_LABEL(track_position_label), g_strdup_printf("%u", track_position)); // Set the track position
gtk_label_set_label(GTK_LABEL(track_name_label), track_name); // Set our track name
KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
if (KOTO_IS_ALBUM(album)) {
gtk_label_set_label(GTK_LABEL(track_album_label), koto_album_get_name(album)); // Get the name of the album and set it to the label
}
KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
if (KOTO_IS_ARTIST(artist)) {
gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label
}
}
void koto_playlist_page_create_tracks_header(KotoPlaylistPage * self) {
self->track_num_button = koto_button_new_with_icon("#", "pan-down-symbolic", "pan-up-symbolic", KOTO_BUTTON_PIXBUF_SIZE_SMALL);
koto_button_add_click_handler(self->track_num_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_track_num_clicked), self);
koto_button_set_image_position(self->track_num_button, KOTO_BUTTON_IMAGE_POS_RIGHT); // Move the image to the right
gtk_size_group_add_widget(self->track_pos_size_group, GTK_WIDGET(self->track_num_button));
self->track_title_button = koto_button_new_plain("Title");
koto_button_add_click_handler(self->track_title_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_track_name_clicked), self);
gtk_size_group_add_widget(self->track_name_size_group, GTK_WIDGET(self->track_title_button));
self->track_album_button = koto_button_new_plain("Album");
gtk_widget_set_margin_start(GTK_WIDGET(self->track_album_button), 50);
koto_button_add_click_handler(self->track_album_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_track_album_clicked), self);
gtk_size_group_add_widget(self->track_album_size_group, GTK_WIDGET(self->track_album_button));
self->track_artist_button = koto_button_new_plain("Artist");
gtk_widget_set_margin_start(GTK_WIDGET(self->track_artist_button), 50);
koto_button_add_click_handler(self->track_artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_track_artist_clicked), self);
gtk_size_group_add_widget(self->track_artist_size_group, GTK_WIDGET(self->track_artist_button));
gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_num_button));
gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_title_button));
gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_album_button));
gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_artist_button));
}
GtkWidget * koto_playlist_page_get_main(KotoPlaylistPage * self) {
return self->main;
}
void koto_playlist_page_handle_action_bar_closed(
KotoActionBar * bar,
gpointer data
) {
(void) bar;
KotoPlaylistPage * self = data;
if (!KOTO_IS_PLAYLIST(self->playlist)) { // No playlist set
return;
}
gtk_selection_model_unselect_all(self->selection_model);
gtk_widget_grab_focus(GTK_WIDGET(main_window)); // Focus on the window
}
void koto_playlist_page_handle_cover_art_clicked(
GtkGestureClick * gesture,
int n_press,
@ -390,132 +256,6 @@ void koto_playlist_page_handle_playlist_modified(
}
}
void koto_playlist_page_handle_track_album_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoPlaylistPage * self = user_data;
gtk_widget_add_css_class(GTK_WIDGET(self->track_album_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM);
}
void koto_playlist_page_handle_track_artist_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoPlaylistPage * self = user_data;
gtk_widget_add_css_class(GTK_WIDGET(self->track_artist_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST);
}
void koto_playlist_page_handle_track_name_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoPlaylistPage * self = user_data;
gtk_widget_add_css_class(GTK_WIDGET(self->track_title_button), "active");
koto_button_hide_image(self->track_num_button); // Go back to hiding the image
koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME);
}
void koto_playlist_page_handle_track_num_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
) {
(void) gesture;
(void) n_press;
(void) x;
(void) y;
KotoPlaylistPage * self = user_data;
KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist);
if (current_model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Set to newest currently
koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST); // Sort reversed (oldest)
koto_button_show_image(self->track_num_button, TRUE); // Use inverted value (pan-up-symbolic)
} else {
koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Sort newest
koto_button_show_image(self->track_num_button, FALSE); // Use pan down default
}
}
void koto_playlist_page_handle_tracks_selected(
GtkSelectionModel * model,
guint position,
guint n_items,
gpointer user_data
) {
(void) position;
KotoPlaylistPage * self = user_data;
if (n_items == 0) { // No items selected
koto_action_bar_toggle_reveal(action_bar, FALSE); // Hide the action bar
return;
}
GtkBitset * selected_items_bitset = gtk_selection_model_get_selection(model); // Get the selected items as a GtkBitSet
GtkBitsetIter iter;
GList * selected_tracks = NULL;
GList * selected_tracks_pos = NULL;
guint first_track_pos;
if (!gtk_bitset_iter_init_first(&iter, selected_items_bitset, &first_track_pos)) { // Failed to get the first item
return;
}
selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(first_track_pos));
gboolean have_more_items = TRUE;
while (have_more_items) { // While we are able to get selected items
guint track_pos;
have_more_items = gtk_bitset_iter_next(&iter, &track_pos);
if (have_more_items) { // Got the next track
selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(track_pos));
}
}
GList * cur_pos_list;
for (cur_pos_list = selected_tracks_pos; cur_pos_list != NULL; cur_pos_list = cur_pos_list->next) { // Iterate over every position that we accumulated
KotoTrack * selected_track = g_list_model_get_item(self->model, GPOINTER_TO_UINT(cur_pos_list->data)); // Get the KotoTrack in the GListModel for this current position
selected_tracks = g_list_append(selected_tracks, selected_track); // Add to selected tracks
}
koto_action_bar_set_tracks_in_playlist_selection(action_bar, self->uuid, selected_tracks); // Set the tracks for the playlist selection
koto_action_bar_toggle_reveal(action_bar, TRUE); // Show the items
}
void koto_playlist_page_set_playlist_uuid(
KotoPlaylistPage * self,
gchar * playlist_uuid
@ -524,10 +264,6 @@ void koto_playlist_page_set_playlist_uuid(
return;
}
if (!KOTO_IS_PLAYLIST_PAGE(self)) {
return;
}
if (!koto_utils_is_string_valid(playlist_uuid)) { // Provided UUID string is not valid
return;
}
@ -540,100 +276,10 @@ void koto_playlist_page_set_playlist_uuid(
KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->uuid);
self->playlist = playlist;
koto_playlist_page_set_playlist_model(self, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // TODO: Enable this to be changed
koto_playlist_page_update_header(self); // Update our header
g_signal_connect(playlist, "modified", G_CALLBACK(koto_playlist_page_handle_playlist_modified), self); // Handle playlist modification
}
void koto_playlist_page_set_playlist_model(
KotoPlaylistPage * self,
KotoPreferredModelType model
) {
if (!KOTO_IS_PLAYLIST_PAGE(self)) {
return;
}
koto_playlist_apply_model(self->playlist, model); // Apply our new model
self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently
gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active");
}
if (model != KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name
gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active");
}
self->selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(self->model));
g_signal_connect(self->selection_model, "selection-changed", G_CALLBACK(koto_playlist_page_handle_tracks_selected), self); // Bind to our selection changed
gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model
}
void koto_playlist_page_setup_track_item(
GtkListItemFactory * factory,
GtkListItem * item,
gpointer user_data
) {
(void) factory;
KotoPlaylistPage * self = user_data;
if (!KOTO_IS_PLAYLIST_PAGE(self)) {
return;
}
GtkWidget * item_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Have a horizontal box for our content
gtk_widget_add_css_class(item_content, "track-list-columned-item");
GtkWidget * track_number = gtk_label_new(NULL); // Our track number
gtk_label_set_xalign(GTK_LABEL(track_number), 0);
gtk_widget_add_css_class(track_number, "track-column-number");
gtk_widget_set_halign(track_number, GTK_ALIGN_END);
gtk_widget_set_hexpand(track_number, FALSE);
gtk_widget_set_size_request(track_number, 150, -1);
gtk_box_append(GTK_BOX(item_content), track_number);
gtk_size_group_add_widget(self->track_pos_size_group, track_number);
GtkWidget * track_name = gtk_label_new(NULL); // Our track name
gtk_label_set_xalign(GTK_LABEL(track_name), 0);
gtk_widget_add_css_class(track_name, "track-column-name");
gtk_widget_set_halign(track_name, GTK_ALIGN_START);
gtk_widget_set_hexpand(track_name, FALSE);
gtk_widget_set_size_request(track_name, 350, -1);
gtk_box_append(GTK_BOX(item_content), track_name);
gtk_size_group_add_widget(self->track_name_size_group, track_name);
GtkWidget * track_album = gtk_label_new(NULL); // Our track album
gtk_label_set_xalign(GTK_LABEL(track_album), 0);
gtk_widget_add_css_class(track_album, "track-column-album");
gtk_widget_set_halign(track_album, GTK_ALIGN_START);
gtk_widget_set_hexpand(track_album, FALSE);
gtk_widget_set_margin_start(track_album, 50);
gtk_widget_set_size_request(track_album, 350, -1);
gtk_box_append(GTK_BOX(item_content), track_album);
gtk_size_group_add_widget(self->track_album_size_group, track_album);
GtkWidget * track_artist = gtk_label_new(NULL); // Our track artist
gtk_label_set_xalign(GTK_LABEL(track_artist), 0);
gtk_widget_add_css_class(track_artist, "track-column-artist");
gtk_widget_set_halign(track_artist, GTK_ALIGN_START);
gtk_widget_set_hexpand(track_artist, TRUE);
gtk_widget_set_margin_start(track_artist, 50);
gtk_widget_set_size_request(track_artist, 350, -1);
gtk_box_append(GTK_BOX(item_content), track_artist);
gtk_size_group_add_widget(self->track_artist_size_group, track_artist);
gtk_list_item_set_child(item, item_content);
koto_track_table_set_playlist(self->table, playlist); // Set our track table
}
void koto_playlist_page_update_header(KotoPlaylistPage * self) {
@ -666,12 +312,6 @@ void koto_playlist_page_update_header(KotoPlaylistPage * self) {
if (koto_utils_is_string_valid(artwork)) { // Have artwork
koto_cover_art_button_set_art_path(self->playlist_image, artwork); // Update our artwork
}
KotoPreferredModelType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model
if (current_model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) {
koto_button_show_image(self->track_num_button, TRUE); // Immediately use pan-up-symbolic
}
}
KotoPlaylistPage * koto_playlist_page_new(gchar * playlist_uuid) {

View file

@ -51,10 +51,6 @@ void koto_playlist_page_remove_track(
GtkWidget * koto_playlist_page_get_main(KotoPlaylistPage * self);
void koto_playlist_page_handle_action_bar_closed(
KotoActionBar * bar,
gpointer data
);
void koto_playlist_page_handle_cover_art_clicked(
GtkGestureClick * gesture,
@ -77,71 +73,15 @@ void koto_playlist_page_handle_playlist_modified(
gpointer user_data
);
void koto_playlist_page_handle_track_album_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_playlist_page_handle_track_artist_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_playlist_page_handle_track_name_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_playlist_page_handle_track_num_clicked(
GtkGestureClick * gesture,
int n_press,
double x,
double y,
gpointer user_data
);
void koto_playlist_page_handle_tracks_selected(
GtkSelectionModel * model,
guint position,
guint n_items,
gpointer user_data
);
void koto_playlist_page_hide_popover(KotoPlaylistPage * self);
void koto_playlist_page_setup_track_item(
GtkListItemFactory * factory,
GtkListItem * item,
gpointer user_data
);
void koto_playlist_page_set_playlist_uuid(
KotoPlaylistPage * self,
gchar * playlist_uuid
);
void koto_playlist_page_set_playlist_model(
KotoPlaylistPage * self,
KotoPreferredModelType model
);
void koto_playlist_page_show_popover(KotoPlaylistPage * self);
void koto_playlist_page_update_header(KotoPlaylistPage * self);
void koto_playlist_page_handle_listitem_activate(
GtkListView * list,
guint position,
gpointer user_data
);
G_END_DECLS