/* audiobook-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 #include #include "../../components/album-info.h" #include "../../components/button.h" #include "../../components/track-item.h" #include "../../db/cartographer.h" #include "../../indexer/album-playlist-funcs.h" #include "../../indexer/structs.h" #include "../../playlist/current.h" #include "../../koto-utils.h" #include "../../koto-window.h" #include "audiobook-view.h" extern KotoCartographer * koto_maps; extern KotoCurrentPlaylist * current_playlist; extern KotoWindow * main_window; struct _KotoAudiobookView { GtkBox parent_instance; KotoAlbum * album; // Album associated with this view GtkWidget * side_info; // Our side info (artwork, playback button, position) GtkWidget * info_contents; // Our info and contents vertical box KotoAlbumInfo * album_info; // Our "Album" (Audiobook) info GtkWidget * audiobook_art; // Our GtkImage for the audiobook art GtkWidget * playback_button; // Our GtkButton for playback GtkWidget * chapter_info; // Our GtkLabel of the position, effectively GtkWidget * playback_position; // Our GtkLabel for the track playback position GtkWidget * tracks_list; // GtkListBox of tracks }; struct _KotoAudiobookViewClass { GtkBoxClass parent_class; }; G_DEFINE_TYPE(KotoAudiobookView, koto_audiobook_view, GTK_TYPE_BOX); static void koto_audiobook_view_class_init(KotoAudiobookViewClass * c) { (void) c; } static void koto_audiobook_view_init(KotoAudiobookView * self) { gtk_widget_add_css_class(GTK_WIDGET(self), "audiobook-view"); self->side_info = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_widget_add_css_class(self->side_info, "side-info"); gtk_widget_set_size_request(self->side_info, 220, -1); // Match audiobook art size self->info_contents = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); self->audiobook_art = gtk_image_new_from_icon_name("audio-x-generic-symbolic"); // Create an image with a symbolic icon gtk_widget_set_size_request(self->audiobook_art, 220, 220); // Set to 220 like our cover art button self->playback_button = gtk_button_new_with_label("Play"); // Default to our button saying Play instead of continue gtk_widget_add_css_class(self->playback_button, "suggested-action"); g_signal_connect(self->playback_button, "clicked", G_CALLBACK(koto_audiobook_view_handle_play_clicked), self); self->chapter_info = gtk_label_new(NULL); self->playback_position = gtk_label_new(NULL); gtk_widget_set_halign(self->chapter_info, GTK_ALIGN_START); gtk_widget_set_halign(self->playback_position, GTK_ALIGN_START); gtk_box_append(GTK_BOX(self->side_info), self->audiobook_art); // Add our audiobook art to the side info gtk_box_append(GTK_BOX(self->side_info), GTK_WIDGET(self->playback_button)); // Add our playback button to the side info gtk_box_append(GTK_BOX(self->side_info), self->chapter_info); gtk_box_append(GTK_BOX(self->side_info), self->playback_position); self->album_info = koto_album_info_new("audiobook"); // Create an "audiobook" type KotoAlbumInfo gtk_box_append(GTK_BOX(self->info_contents), GTK_WIDGET(self->album_info)); // Add the album info the info contents GtkWidget * chapters_label = gtk_label_new("Chapters"); gtk_widget_add_css_class(chapters_label, "chapters-label"); gtk_widget_set_halign(chapters_label, GTK_ALIGN_START); // Align to the start gtk_box_append(GTK_BOX(self->info_contents), chapters_label); self->tracks_list = gtk_list_box_new(); // Create our list of our tracks gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->tracks_list), FALSE); gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->tracks_list), GTK_SELECTION_MULTIPLE); gtk_widget_add_css_class(self->tracks_list, "track-list"); gtk_box_append(GTK_BOX(self->info_contents), self->tracks_list); // Add our listbox to the info contents gtk_box_append(GTK_BOX(self), self->side_info); // Add our side info to the box gtk_box_append(GTK_BOX(self), self->info_contents); // Add our info contents to the box } GtkWidget * koto_audiobook_view_create_track_item( gpointer item, gpointer user_data ) { (void) user_data; if (!KOTO_IS_TRACK(item)) { // Not actually a track return NULL; } KotoTrack * track = KOTO_TRACK(item); // Cast our item as a track KotoTrackItem * track_item = koto_track_item_new(track); // Create our track item if (!KOTO_IS_TRACK_ITEM(track_item)) { // Not a track item return NULL; } return GTK_WIDGET(track_item); // Cast as a widget and return the track item } void koto_audiobook_view_handle_play_clicked( GtkButton * button, gpointer user_data ) { (void) button; KotoAudiobookView * self = user_data; if (!KOTO_IS_AUDIOBOOK_VIEW(self)) { return; } if (!KOTO_IS_ALBUM(self->album)) { // Don't have an album associated with this view return; } koto_playlist_commit(koto_album_get_playlist(self->album)); // Ensure we commit the current playlist to the database. This is needed to ensure we are able to save the playlist state going forward koto_current_playlist_set_playlist(current_playlist, koto_album_get_playlist(self->album), TRUE, TRUE); // Set this playlist to be played immediately, play current track } void koto_audiobook_view_handle_playlist_updated( KotoPlaylist * playlist, gpointer user_data ) { (void) playlist; KotoAudiobookView * self = user_data; if (!KOTO_IS_AUDIOBOOK_VIEW(self)) { return; } koto_audiobook_view_update_side_info(self); // Update the side info based on the playlist modification } void koto_audiobook_view_set_album( KotoAudiobookView * self, KotoAlbum * album ) { if (!KOTO_IS_AUDIOBOOK_VIEW(self)) { return; } if (!KOTO_IS_ALBUM(album)) { // Not an album return; } self->album = album; gchar * album_art_path = koto_album_get_art(album); // Get any artwork if (koto_utils_string_is_valid(album_art_path)) { // Have album art gtk_image_set_from_file(GTK_IMAGE(self->audiobook_art), album_art_path); // Set our album art } koto_album_info_set_album_uuid(self->album_info, koto_album_get_uuid(album)); // Apply our album info gtk_list_box_bind_model( // Apply our binding for the GtkListBox GTK_LIST_BOX(self->tracks_list), G_LIST_MODEL(koto_album_get_store(album)), koto_audiobook_view_create_track_item, NULL, koto_audiobook_view_destroy_associated_user_data ); KotoPlaylist * album_playlist = koto_album_get_playlist(self->album); // Get the album playlist if (!KOTO_IS_PLAYLIST(album_playlist)) { // Not a playlist return; } g_signal_connect(album_playlist, "modified", G_CALLBACK(koto_audiobook_view_handle_playlist_updated), self); // Handle modifications of a playlist g_signal_connect(album_playlist, "track-load-finalized", G_CALLBACK(koto_audiobook_view_handle_playlist_updated), self); // Handle when a playlist is finalized koto_audiobook_view_update_side_info(self); // Update our side info } void koto_audiobook_view_update_side_info(KotoAudiobookView * self) { if (!KOTO_IS_AUDIOBOOK_VIEW(self)) { return; } if (!KOTO_IS_ALBUM(self->album)) { // Not an album return; } KotoPlaylist * album_playlist = koto_album_get_playlist(self->album); // Get the album playlist if (!KOTO_IS_PLAYLIST(album_playlist)) { // Not a playlist return; } gint playlist_position = koto_playlist_get_current_position(album_playlist); // Get the current position in the playlist gint playlist_length = koto_playlist_get_length(album_playlist); // Get the length of the playlist KotoTrack * current_track = koto_playlist_get_current_track(album_playlist); // Get the track for the playlist if (!KOTO_IS_TRACK(current_track)) { // Not a track return; } guint playback_position = koto_track_get_playback_position(current_track); guint duration = koto_track_get_duration(current_track); gboolean continuing = (playlist_position >= 1) || ((playlist_position <= 0) && (playback_position != 0)); gtk_button_set_label(GTK_BUTTON(self->playback_button), continuing ? "Resume" : "Play"); if (continuing) { // Have been playing track gtk_label_set_text(GTK_LABEL(self->chapter_info), g_strdup_printf("Track %i of %i", (playlist_position <= 0) ? 1 : (playlist_position + 1), playlist_length)); gtk_label_set_text(GTK_LABEL(self->playback_position), g_strdup_printf("%s / %s", koto_utils_seconds_to_time_format(playback_position), koto_utils_seconds_to_time_format(duration))); gtk_widget_show(self->chapter_info); gtk_widget_show(self->playback_position); } else { gtk_widget_hide(self->chapter_info); // Hide by default gtk_widget_hide(self->playback_position); // Hide by default } } void koto_audiobook_view_destroy_associated_user_data(gpointer user_data) { (void) user_data; } KotoAudiobookView * koto_audiobook_view_new() { return g_object_new( KOTO_TYPE_AUDIOBOOK_VIEW, "orientation", GTK_ORIENTATION_HORIZONTAL, NULL ); }