Implement Playlist functionality. My god...
Too many changes to summarize. - Fixes #2. - Fixes #3. - Fixes #5. - Fixes #7. Start work on uncrustify config.
This commit is contained in:
parent
ddf1987b50
commit
0e2244ba90
62 changed files with 6280 additions and 374 deletions
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -14,7 +14,7 @@
|
|||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
{"name" : "G_MESSAGES_DEBUG", "value": "all" },
|
||||
{ "name": "GTK_THEME", "value": "Default:dark" }
|
||||
{ "name": "GTK_THEME", "value": "Adwaita:dark" }
|
||||
],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
|
@ -37,7 +37,7 @@
|
|||
"cwd": "${workspaceFolder}",
|
||||
"environment": [
|
||||
{"name" : "G_MESSAGES_DEBUG", "value": "all" },
|
||||
{ "name": "GTK_THEME", "value": "Default:dark" }
|
||||
{ "name": "GTK_THEME", "value": "Adwaita:dark" }
|
||||
],
|
||||
"externalConsole": false,
|
||||
"linux": {
|
||||
|
|
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
|
@ -1,5 +1,10 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"glib.h": "c"
|
||||
"glib.h": "c",
|
||||
"ios": "c",
|
||||
"__node_handle": "c",
|
||||
"gtk.h": "c",
|
||||
"gtktreeview.h": "c",
|
||||
"cartographer.h": "c"
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ Koto is an in-development audiobook, music, and podcast manager that is designed
|
|||
|
||||
## Blog
|
||||
|
||||
- [Dev Diary 6: Koto April Progress Report (A-side)](https://joshuastrobl.com/2021/04/26/dev-diary-6-koto-april-progress-report-a-side/)
|
||||
- [Dev Diary 5: Koto March Progress Report (B-side)](https://joshuastrobl.com/2021/04/08/dev-diary-5-koto-march-progress-report-b-side/)
|
||||
- [Dev Diary 4: Koto March Progress Report (A-side)](https://joshuastrobl.com/2021/03/26/dev-diary-4-koto-march-progress-report-a-side/)
|
||||
- [Dev Diary 3: Koto February Progress Report (B-side)](https://joshuastrobl.com/2021/03/05/dev-diary-3-koto-february-progress-report-b-side/)
|
||||
- [Dev Diary 2: Koto February Progress Report (A-side)](https://joshuastrobl.com/2021/02/17/dev-diary-2-koto-february-progress-report-a-side/)
|
||||
|
|
|
@ -3,6 +3,7 @@ project('koto', 'c',
|
|||
meson_version: '>= 0.57.0',
|
||||
default_options: [ 'warning_level=2',
|
||||
'c_std=gnu11',
|
||||
'werror=true',
|
||||
],
|
||||
)
|
||||
|
||||
|
|
355
src/components/koto-action-bar.c
Normal file
355
src/components/koto-action-bar.c
Normal file
|
@ -0,0 +1,355 @@
|
|||
/* koto-action-bar.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 "koto-action-bar.h"
|
||||
#include "../db/cartographer.h"
|
||||
#include "../pages/music/music-local.h"
|
||||
#include "../playlist/add-remove-track-popover.h"
|
||||
#include "../playback/engine.h"
|
||||
#include "../koto-button.h"
|
||||
#include "../koto-window.h"
|
||||
|
||||
extern KotoAddRemoveTrackPopover *koto_add_remove_track_popup;
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoPageMusicLocal *music_local_page;
|
||||
extern KotoPlaybackEngine *playback_engine;
|
||||
extern KotoWindow *main_window;
|
||||
|
||||
enum {
|
||||
SIGNAL_CLOSED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint actionbar_signals[N_SIGNALS] = { 0 };
|
||||
|
||||
struct _KotoActionBar {
|
||||
GObject parent_instance;
|
||||
|
||||
GtkActionBar *main;
|
||||
|
||||
GtkWidget *center_box_content;
|
||||
GtkWidget *start_box_content;
|
||||
GtkWidget *stop_box_content;
|
||||
|
||||
KotoButton *close_button;
|
||||
GtkWidget *go_to_artist;
|
||||
GtkWidget *playlists;
|
||||
GtkWidget *play_track;
|
||||
GtkWidget *remove_from_playlist;
|
||||
|
||||
GList *current_list;
|
||||
gchar *current_album_uuid;
|
||||
gchar *current_playlist_uuid;
|
||||
KotoActionBarRelative relative;
|
||||
};
|
||||
|
||||
struct _KotoActionBarClass {
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (* closed) (KotoActionBar *self);
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoActionBar, koto_action_bar, G_TYPE_OBJECT);
|
||||
|
||||
KotoActionBar* action_bar;
|
||||
|
||||
static void koto_action_bar_class_init(KotoActionBarClass *c) {
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS(c);
|
||||
|
||||
actionbar_signals[SIGNAL_CLOSED] = g_signal_new(
|
||||
"closed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoActionBarClass, closed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
static void koto_action_bar_init(KotoActionBar *self) {
|
||||
self->main = GTK_ACTION_BAR(gtk_action_bar_new()); // Create a new action bar
|
||||
self->current_list = NULL;
|
||||
|
||||
self->close_button = koto_button_new_with_icon(NULL, "window-close-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
self->go_to_artist = gtk_button_new_with_label("Go to Artist");
|
||||
self->playlists = gtk_button_new_with_label("Playlists");
|
||||
self->play_track = gtk_button_new_with_label("Play");
|
||||
self->remove_from_playlist = gtk_button_new_with_label("Remove from Playlist");
|
||||
|
||||
gtk_widget_add_css_class(self->playlists, "suggested-action");
|
||||
gtk_widget_add_css_class(self->play_track, "suggested-action");
|
||||
gtk_widget_add_css_class(self->remove_from_playlist, "destructive-action");
|
||||
|
||||
gtk_widget_set_size_request(self->play_track, 160, -1);
|
||||
|
||||
self->center_box_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
self->start_box_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
self->stop_box_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 20);
|
||||
|
||||
gtk_box_prepend(GTK_BOX(self->start_box_content), GTK_WIDGET(self->go_to_artist));
|
||||
|
||||
gtk_box_prepend(GTK_BOX(self->center_box_content), GTK_WIDGET(self->play_track));
|
||||
|
||||
gtk_box_prepend(GTK_BOX(self->stop_box_content), GTK_WIDGET(self->playlists));
|
||||
gtk_box_append(GTK_BOX(self->stop_box_content), GTK_WIDGET(self->remove_from_playlist));
|
||||
gtk_box_append(GTK_BOX(self->stop_box_content), GTK_WIDGET(self->close_button));
|
||||
|
||||
gtk_action_bar_pack_start(self->main, self->start_box_content);
|
||||
gtk_action_bar_pack_end(self->main, self->stop_box_content);
|
||||
gtk_action_bar_set_center_widget(self->main, self->center_box_content);
|
||||
|
||||
// Hide all the widgets by default
|
||||
gtk_widget_hide(GTK_WIDGET(self->go_to_artist));
|
||||
gtk_widget_hide(GTK_WIDGET(self->playlists));
|
||||
gtk_widget_hide(GTK_WIDGET(self->play_track));
|
||||
gtk_widget_hide(GTK_WIDGET(self->remove_from_playlist));
|
||||
|
||||
// Set up bindings
|
||||
|
||||
koto_button_add_click_handler(self->close_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_action_bar_handle_close_button_clicked), self);
|
||||
g_signal_connect(self->go_to_artist, "clicked", G_CALLBACK(koto_action_bar_handle_go_to_artist_button_clicked), self);
|
||||
g_signal_connect(self->playlists, "clicked", G_CALLBACK(koto_action_bar_handle_playlists_button_clicked), self);
|
||||
g_signal_connect(self->play_track, "clicked", G_CALLBACK(koto_action_bar_handle_play_track_button_clicked), self);
|
||||
g_signal_connect(self->remove_from_playlist, "clicked", G_CALLBACK(koto_action_bar_handle_remove_from_playlist_button_clicked), self);
|
||||
|
||||
koto_action_bar_toggle_reveal(self, FALSE); // Hide by default
|
||||
}
|
||||
|
||||
void koto_action_bar_close(KotoActionBar *self) {
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_widget_hide(GTK_WIDGET(koto_add_remove_track_popup)); // Hide the Add / Remove Track Playlists popover
|
||||
koto_action_bar_toggle_reveal(self, FALSE); // Hide the action bar
|
||||
|
||||
g_signal_emit(
|
||||
self,
|
||||
actionbar_signals[SIGNAL_CLOSED],
|
||||
0,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
GtkActionBar* koto_action_bar_get_main(KotoActionBar *self) {
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return self->main;
|
||||
}
|
||||
|
||||
void koto_action_bar_handle_close_button_clicked(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
koto_action_bar_close(data);
|
||||
}
|
||||
|
||||
void koto_action_bar_handle_go_to_artist_button_clicked(GtkButton *button, gpointer data) {
|
||||
(void) button;
|
||||
KotoActionBar *self = data;
|
||||
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->current_list == NULL || (g_list_length(self->current_list) != 1)) { // Not a list or not exactly one item
|
||||
return;
|
||||
}
|
||||
|
||||
KotoIndexedTrack *selected_track = g_list_nth_data(self->current_list, 0); // Get the first item
|
||||
|
||||
if (!KOTO_IS_INDEXED_TRACK(selected_track)) { // Not a track
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *artist_uuid = NULL;
|
||||
g_object_get(
|
||||
selected_track,
|
||||
"artist-uuid",
|
||||
&artist_uuid,
|
||||
NULL
|
||||
);
|
||||
|
||||
koto_page_music_local_go_to_artist_by_uuid(music_local_page, artist_uuid); // Go to the artist
|
||||
koto_window_go_to_page(main_window, "music.local"); // Navigate to the local music stack so we can see the substack page
|
||||
koto_action_bar_close(self); // Close the action bar
|
||||
}
|
||||
void koto_action_bar_handle_playlists_button_clicked(GtkButton *button, gpointer data) {
|
||||
(void) button;
|
||||
KotoActionBar *self = data;
|
||||
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->current_list == NULL || (g_list_length(self->current_list) == 0)) { // Not a list or no content
|
||||
return;
|
||||
}
|
||||
|
||||
koto_add_remove_track_popover_set_tracks(koto_add_remove_track_popup, self->current_list); // Set the popover tracks
|
||||
koto_add_remove_track_popover_set_pointing_to_widget(koto_add_remove_track_popup, GTK_WIDGET(self->playlists), GTK_POS_TOP); // Show the popover above the button
|
||||
gtk_widget_show(GTK_WIDGET(koto_add_remove_track_popup));
|
||||
}
|
||||
|
||||
void koto_action_bar_handle_play_track_button_clicked(GtkButton *button, gpointer data) {
|
||||
(void) button;
|
||||
KotoActionBar *self = data;
|
||||
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->current_list == NULL || (g_list_length(self->current_list) != 1)) { // Not a list or not exactly 1 item selected
|
||||
goto doclose;
|
||||
}
|
||||
|
||||
KotoIndexedTrack *track = g_list_nth_data(self->current_list, 0); // Get the first track
|
||||
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) { // Not a track
|
||||
goto doclose;
|
||||
}
|
||||
|
||||
koto_playback_engine_set_track_by_uuid(playback_engine, koto_indexed_track_get_uuid(track)); // Set the track to play
|
||||
|
||||
doclose:
|
||||
koto_action_bar_close(self);
|
||||
}
|
||||
|
||||
void koto_action_bar_handle_remove_from_playlist_button_clicked(GtkButton *button, gpointer data) {
|
||||
(void) button;
|
||||
KotoActionBar *self = data;
|
||||
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->current_list == NULL || (g_list_length(self->current_list) == 0)) { // Not a list or no content
|
||||
goto doclose;
|
||||
}
|
||||
|
||||
if (self->current_playlist_uuid == NULL || (g_strcmp0(self->current_playlist_uuid, "") == 0)) { // Not valid UUID
|
||||
goto doclose;
|
||||
}
|
||||
|
||||
KotoPlaylist *playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->current_playlist_uuid);
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist
|
||||
goto doclose;
|
||||
}
|
||||
|
||||
GList *cur_list;
|
||||
for (cur_list = self->current_list; cur_list != NULL; cur_list = cur_list->next) { // For each KotoIndexedTrack
|
||||
KotoIndexedTrack *track = cur_list->data;
|
||||
koto_playlist_remove_track_by_uuid(playlist, koto_indexed_track_get_uuid(track)); // Remove this track
|
||||
}
|
||||
|
||||
doclose:
|
||||
koto_action_bar_close(self);
|
||||
}
|
||||
|
||||
void koto_action_bar_set_tracks_in_album_selection(KotoActionBar *self, gchar *album_uuid, GList *tracks) {
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->current_album_uuid != NULL && (g_strcmp0(self->current_album_uuid, "") != 0)) { // Album UUID currently set
|
||||
g_free(self->current_album_uuid);
|
||||
}
|
||||
|
||||
if (self->current_playlist_uuid != NULL && (g_strcmp0(self->current_playlist_uuid, "") != 0)) { // Playlist UUID currently set
|
||||
g_free(self->current_playlist_uuid);
|
||||
}
|
||||
|
||||
self->current_playlist_uuid = NULL;
|
||||
self->current_album_uuid = g_strdup(album_uuid);
|
||||
self->relative = KOTO_ACTION_BAR_IS_ALBUM_RELATIVE;
|
||||
|
||||
g_list_free(self->current_list);
|
||||
self->current_list = g_list_copy(tracks);
|
||||
|
||||
koto_add_remove_track_popover_clear_tracks(koto_add_remove_track_popup); // Clear the current popover contents
|
||||
koto_add_remove_track_popover_set_tracks(koto_add_remove_track_popup, self->current_list); // Set the associated tracks to remove
|
||||
|
||||
koto_action_bar_toggle_go_to_artist_visibility(self, FALSE);
|
||||
koto_action_bar_toggle_play_button_visibility(self, g_list_length(self->current_list) == 1);
|
||||
|
||||
gtk_widget_show(GTK_WIDGET(self->playlists));
|
||||
gtk_widget_hide(GTK_WIDGET(self->remove_from_playlist));
|
||||
}
|
||||
|
||||
void koto_action_bar_set_tracks_in_playlist_selection(KotoActionBar *self, gchar *playlist_uuid, GList *tracks) {
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->current_album_uuid != NULL && (g_strcmp0(self->current_album_uuid, "") != 0)) { // Album UUID currently set
|
||||
g_free(self->current_album_uuid);
|
||||
}
|
||||
|
||||
if (self->current_playlist_uuid != NULL && (g_strcmp0(self->current_playlist_uuid, "") != 0)) { // Playlist UUID currently set
|
||||
g_free(self->current_playlist_uuid);
|
||||
}
|
||||
|
||||
self->current_album_uuid = NULL;
|
||||
self->current_playlist_uuid = g_strdup(playlist_uuid);
|
||||
self->relative = KOTO_ACTION_BAR_IS_PLAYLIST_RELATIVE;
|
||||
|
||||
g_list_free(self->current_list);
|
||||
self->current_list = g_list_copy(tracks);
|
||||
|
||||
gboolean single_selected = g_list_length(tracks) == 1;
|
||||
koto_action_bar_toggle_go_to_artist_visibility(self, single_selected);
|
||||
koto_action_bar_toggle_play_button_visibility(self, single_selected);
|
||||
gtk_widget_hide(GTK_WIDGET(self->playlists));
|
||||
gtk_widget_show(GTK_WIDGET(self->remove_from_playlist));
|
||||
}
|
||||
|
||||
void koto_action_bar_toggle_go_to_artist_visibility(KotoActionBar *self, gboolean visible) {
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
(visible) ? gtk_widget_show(GTK_WIDGET(self->go_to_artist)) : gtk_widget_hide(GTK_WIDGET(self->go_to_artist));
|
||||
}
|
||||
|
||||
void koto_action_bar_toggle_play_button_visibility(KotoActionBar *self, gboolean visible) {
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
(visible) ? gtk_widget_show(GTK_WIDGET(self->play_track)) : gtk_widget_hide(GTK_WIDGET(self->play_track));
|
||||
}
|
||||
|
||||
void koto_action_bar_toggle_reveal(KotoActionBar *self, gboolean state) {
|
||||
if (!KOTO_IS_ACTION_BAR(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_action_bar_set_revealed(self->main, state);
|
||||
}
|
||||
|
||||
KotoActionBar* koto_action_bar_new() {
|
||||
return g_object_new(
|
||||
KOTO_TYPE_ACTION_BAR,
|
||||
NULL
|
||||
);
|
||||
}
|
58
src/components/koto-action-bar.h
Normal file
58
src/components/koto-action-bar.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/* koto-action-bar.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.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../indexer/structs.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
KOTO_ACTION_BAR_IS_ALBUM_RELATIVE = 1,
|
||||
KOTO_ACTION_BAR_IS_PLAYLIST_RELATIVE = 2
|
||||
} KotoActionBarRelative;
|
||||
|
||||
#define KOTO_TYPE_ACTION_BAR (koto_action_bar_get_type())
|
||||
#define KOTO_IS_ACTION_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ACTION_BAR))
|
||||
|
||||
typedef struct _KotoActionBar KotoActionBar;
|
||||
typedef struct _KotoActionBarClass KotoActionBarClass;
|
||||
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GType koto_action_bar_get_type(void) G_GNUC_CONST;
|
||||
|
||||
/**
|
||||
* Action Bar Functions
|
||||
**/
|
||||
|
||||
KotoActionBar* koto_action_bar_new(void);
|
||||
void koto_action_bar_close(KotoActionBar *self);
|
||||
GtkActionBar* koto_action_bar_get_main(KotoActionBar *self);
|
||||
void koto_action_bar_handle_close_button_clicked(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_action_bar_handle_go_to_artist_button_clicked(GtkButton *button, gpointer data);
|
||||
void koto_action_bar_handle_playlists_button_clicked(GtkButton *button, gpointer data);
|
||||
void koto_action_bar_handle_play_track_button_clicked(GtkButton *button, gpointer data);
|
||||
void koto_action_bar_handle_remove_from_playlist_button_clicked(GtkButton *button, gpointer data);
|
||||
void koto_action_bar_set_tracks_in_album_selection(KotoActionBar *self, gchar *album_uuid, GList *tracks);
|
||||
void koto_action_bar_set_tracks_in_playlist_selection(KotoActionBar *self, gchar *playlist_uuid, GList *tracks);
|
||||
void koto_action_bar_toggle_go_to_artist_visibility(KotoActionBar *self, gboolean visible);
|
||||
void koto_action_bar_toggle_play_button_visibility(KotoActionBar *self, gboolean visible);
|
||||
void koto_action_bar_toggle_reveal(KotoActionBar *self, gboolean state);
|
||||
|
||||
G_END_DECLS
|
217
src/components/koto-cover-art-button.c
Normal file
217
src/components/koto-cover-art-button.c
Normal file
|
@ -0,0 +1,217 @@
|
|||
/* koto-cover-art-button.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 "koto-cover-art-button.h"
|
||||
#include "../koto-button.h"
|
||||
#include "../koto-utils.h"
|
||||
|
||||
struct _KotoCoverArtButton {
|
||||
GObject parent_instance;
|
||||
|
||||
GtkWidget *art;
|
||||
GtkWidget *main;
|
||||
GtkWidget *revealer;
|
||||
KotoButton *play_pause_button;
|
||||
|
||||
guint height;
|
||||
guint width;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoCoverArtButton, koto_cover_art_button, G_TYPE_OBJECT);
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_DESIRED_HEIGHT,
|
||||
PROP_DESIRED_WIDTH,
|
||||
PROP_ART_PATH,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *props[N_PROPERTIES] = { NULL, };
|
||||
static void koto_cover_art_button_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_cover_art_button_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_cover_art_button_class_init(KotoCoverArtButtonClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->get_property = koto_cover_art_button_get_property;
|
||||
gobject_class->set_property = koto_cover_art_button_set_property;
|
||||
|
||||
props[PROP_DESIRED_HEIGHT] = g_param_spec_uint(
|
||||
"desired-height",
|
||||
"Desired height",
|
||||
"Desired height",
|
||||
0,
|
||||
G_MAXUINT,
|
||||
0,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_WRITABLE
|
||||
);
|
||||
|
||||
props[PROP_DESIRED_WIDTH] = g_param_spec_uint(
|
||||
"desired-width",
|
||||
"Desired width",
|
||||
"Desired width",
|
||||
0,
|
||||
G_MAXUINT,
|
||||
0,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_WRITABLE
|
||||
);
|
||||
|
||||
props[PROP_ART_PATH] = g_param_spec_string(
|
||||
"art-path",
|
||||
"Path to art",
|
||||
"Path to art",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_WRITABLE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
static void koto_cover_art_button_init(KotoCoverArtButton *self) {
|
||||
self->main = gtk_overlay_new(); // Create our overlay container
|
||||
gtk_widget_add_css_class(self->main, "cover-art-button");
|
||||
self->revealer = gtk_revealer_new(); // Create a new revealer
|
||||
gtk_revealer_set_transition_type(GTK_REVEALER(self->revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE);
|
||||
gtk_revealer_set_transition_duration(GTK_REVEALER(self->revealer), 400);
|
||||
|
||||
GtkWidget *controls = gtk_center_box_new(); // Create a center box for the controls
|
||||
self->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
gtk_center_box_set_center_widget(GTK_CENTER_BOX(controls), GTK_WIDGET(self->play_pause_button));
|
||||
|
||||
gtk_revealer_set_child(GTK_REVEALER(self->revealer), controls);
|
||||
koto_cover_art_button_hide_overlay_controls(NULL, self); // Hide by default
|
||||
gtk_overlay_add_overlay(GTK_OVERLAY(self->main), self->revealer); // Add our revealer as the overlay
|
||||
|
||||
GtkEventController *motion_controller = gtk_event_controller_motion_new(); // Create our new motion event controller to track mouse leave and enter
|
||||
g_signal_connect(motion_controller, "enter", G_CALLBACK(koto_cover_art_button_show_overlay_controls), self);
|
||||
g_signal_connect(motion_controller, "leave", G_CALLBACK(koto_cover_art_button_hide_overlay_controls), self);
|
||||
gtk_widget_add_controller(self->main, motion_controller);
|
||||
}
|
||||
|
||||
static void koto_cover_art_button_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
(void) val;
|
||||
|
||||
switch (prop_id) {
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_cover_art_button_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoCoverArtButton *self = KOTO_COVER_ART_BUTTON(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ART_PATH:
|
||||
koto_cover_art_button_set_art_path(self, g_value_get_string(val)); // Get the value and call our set_art_path with it
|
||||
break;
|
||||
case PROP_DESIRED_HEIGHT:
|
||||
koto_cover_art_button_set_dimensions(self, g_value_get_uint(val), 0);
|
||||
break;
|
||||
case PROP_DESIRED_WIDTH:
|
||||
koto_cover_art_button_set_dimensions(self, 0, g_value_get_uint(val));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void koto_cover_art_button_hide_overlay_controls(GtkEventControllerFocus *controller, gpointer data) {
|
||||
(void) controller;
|
||||
KotoCoverArtButton* self = data;
|
||||
gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), FALSE);
|
||||
}
|
||||
|
||||
KotoButton* koto_cover_art_button_get_button(KotoCoverArtButton *self) {
|
||||
if (!KOTO_IS_COVER_ART_BUTTON(self)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return self->play_pause_button;
|
||||
}
|
||||
|
||||
GtkWidget* koto_cover_art_button_get_main(KotoCoverArtButton *self) {
|
||||
if (!KOTO_IS_COVER_ART_BUTTON(self)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return self->main;
|
||||
}
|
||||
|
||||
void koto_cover_art_button_set_art_path(KotoCoverArtButton *self, const gchar *art_path) {
|
||||
if (!KOTO_IS_COVER_ART_BUTTON(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gboolean defined_artwork = (art_path != NULL || (g_strcmp0(art_path, "") != 0));
|
||||
|
||||
if (GTK_IS_IMAGE(self->art)) { // Already have an image
|
||||
if (!defined_artwork) { // No art path or empty string
|
||||
gtk_image_set_from_icon_name(GTK_IMAGE(self->art), "audio-x-generic-symbolic");
|
||||
} else { // Have an art path
|
||||
gtk_image_set_from_file(GTK_IMAGE(self->art), g_strdup(art_path)); // Set from the file
|
||||
}
|
||||
} else { // If we don't have an image
|
||||
self->art = koto_utils_create_image_from_filepath(defined_artwork ? g_strdup(art_path) : NULL, "audio-x-generic-symbolic", self->width, self->height);
|
||||
gtk_overlay_set_child(GTK_OVERLAY(self->main), self->art); // Set the child
|
||||
}
|
||||
}
|
||||
|
||||
void koto_cover_art_button_set_dimensions(KotoCoverArtButton *self, guint height, guint width) {
|
||||
if (!KOTO_IS_COVER_ART_BUTTON(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (height != 0) {
|
||||
self->height = height;
|
||||
}
|
||||
|
||||
if (width != 0) {
|
||||
self->width = width;
|
||||
}
|
||||
|
||||
if ((self->height != 0) && (self->width != 0)) { // Both height and width set
|
||||
gtk_widget_set_size_request(self->main, self->width, self->height); // Update our widget
|
||||
|
||||
if (GTK_IS_IMAGE(self->art)) { // Art is defined
|
||||
gtk_widget_set_size_request(self->art, self->width, self->height); // Update our image as well
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void koto_cover_art_button_show_overlay_controls(GtkEventControllerFocus *controller, gpointer data) {
|
||||
(void) controller;
|
||||
KotoCoverArtButton* self = data;
|
||||
|
||||
gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), TRUE);
|
||||
}
|
||||
|
||||
KotoCoverArtButton* koto_cover_art_button_new(guint height, guint width, const gchar *art_path) {
|
||||
return g_object_new(KOTO_TYPE_COVER_ART_BUTTON,
|
||||
"desired-height",
|
||||
height,
|
||||
"desired-width",
|
||||
width,
|
||||
"art-path",
|
||||
art_path,
|
||||
NULL
|
||||
);
|
||||
}
|
40
src/components/koto-cover-art-button.h
Normal file
40
src/components/koto-cover-art-button.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* koto-cover-art-button.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 <glib-2.0/glib.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../koto-button.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_COVER_ART_BUTTON (koto_cover_art_button_get_type())
|
||||
G_DECLARE_FINAL_TYPE(KotoCoverArtButton, koto_cover_art_button, KOTO, COVER_ART_BUTTON, GObject);
|
||||
#define KOTO_IS_COVER_ART_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_COVER_ART_BUTTON))
|
||||
|
||||
/**
|
||||
* Cover Art Functions
|
||||
**/
|
||||
|
||||
KotoCoverArtButton* koto_cover_art_button_new(guint height, guint width, const gchar *art_path);
|
||||
KotoButton* koto_cover_art_button_get_button(KotoCoverArtButton *self);
|
||||
GtkWidget* koto_cover_art_button_get_main(KotoCoverArtButton *self);
|
||||
void koto_cover_art_button_hide_overlay_controls(GtkEventControllerFocus *controller, gpointer data);
|
||||
void koto_cover_art_button_set_art_path(KotoCoverArtButton *self, const gchar *art_path);
|
||||
void koto_cover_art_button_set_dimensions(KotoCoverArtButton *self, guint height, guint width);
|
||||
void koto_cover_art_button_show_overlay_controls(GtkEventControllerFocus *controller, gpointer data);
|
||||
|
||||
G_END_DECLS
|
|
@ -18,6 +18,20 @@
|
|||
#include <glib-2.0/glib.h>
|
||||
#include "cartographer.h"
|
||||
|
||||
enum {
|
||||
SIGNAL_ALBUM_ADDED,
|
||||
SIGNAL_ALBUM_REMOVED,
|
||||
SIGNAL_ARTIST_ADDED,
|
||||
SIGNAL_ARTIST_REMOVED,
|
||||
SIGNAL_PLAYLIST_ADDED,
|
||||
SIGNAL_PLAYLIST_REMOVED,
|
||||
SIGNAL_TRACK_ADDED,
|
||||
SIGNAL_TRACK_REMOVED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
static guint cartographer_signals[N_SIGNALS] = { 0 };
|
||||
|
||||
struct _KotoCartographer {
|
||||
GObject parent_instance;
|
||||
|
||||
|
@ -27,12 +41,130 @@ struct _KotoCartographer {
|
|||
GHashTable *tracks;
|
||||
};
|
||||
|
||||
struct _KotoCartographerClass {
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (* album_added) (KotoCartographer *cartographer, KotoIndexedAlbum *album);
|
||||
void (* album_removed) (KotoCartographer *cartographer, KotoIndexedAlbum *album);
|
||||
void (* artist_added) (KotoCartographer *cartographer, KotoIndexedArtist *artist);
|
||||
void (* artist_removed) (KotoCartographer *cartographer, KotoIndexedArtist *artist);
|
||||
void (* playlist_added) (KotoCartographer *cartographer, KotoPlaylist *playlist);
|
||||
void (* playlist_removed) (KotoCartographer *cartographer, KotoPlaylist *playlist);
|
||||
void (* track_added) (KotoCartographer *cartographer, KotoIndexedTrack *track);
|
||||
void (* track_removed) (KotoCartographer *cartographer, KotoIndexedTrack *track);
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoCartographer, koto_cartographer, G_TYPE_OBJECT);
|
||||
|
||||
KotoCartographer *koto_maps = NULL;
|
||||
|
||||
static void koto_cartographer_class_init(KotoCartographerClass *c) {
|
||||
(void) c;
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
|
||||
cartographer_signals[SIGNAL_ALBUM_ADDED] = g_signal_new(
|
||||
"album-added",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoCartographerClass, album_added),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
KOTO_TYPE_INDEXED_ALBUM
|
||||
);
|
||||
|
||||
cartographer_signals[SIGNAL_ALBUM_REMOVED] = g_signal_new(
|
||||
"album-removed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoCartographerClass, album_removed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_CHAR
|
||||
);
|
||||
|
||||
cartographer_signals[SIGNAL_ARTIST_ADDED] = g_signal_new(
|
||||
"artist-added",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoCartographerClass, artist_added),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
KOTO_TYPE_INDEXED_ARTIST
|
||||
);
|
||||
|
||||
cartographer_signals[SIGNAL_ARTIST_REMOVED] = g_signal_new(
|
||||
"artist-removed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoCartographerClass, artist_removed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_CHAR
|
||||
);
|
||||
|
||||
cartographer_signals[SIGNAL_PLAYLIST_ADDED] = g_signal_new(
|
||||
"playlist-added",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoCartographerClass, playlist_added),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
KOTO_TYPE_PLAYLIST
|
||||
);
|
||||
|
||||
cartographer_signals[SIGNAL_PLAYLIST_REMOVED] = g_signal_new(
|
||||
"playlist-removed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoCartographerClass, playlist_removed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_CHAR
|
||||
);
|
||||
|
||||
cartographer_signals[SIGNAL_TRACK_ADDED] = g_signal_new(
|
||||
"track-added",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoCartographerClass, track_added),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
KOTO_TYPE_INDEXED_TRACK
|
||||
);
|
||||
|
||||
cartographer_signals[SIGNAL_TRACK_REMOVED] = g_signal_new(
|
||||
"track-removed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoCartographerClass, track_removed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_CHAR
|
||||
);
|
||||
}
|
||||
|
||||
static void koto_cartographer_init(KotoCartographer *self) {
|
||||
|
@ -46,36 +178,80 @@ void koto_cartographer_add_album(KotoCartographer *self, KotoIndexedAlbum *album
|
|||
gchar *album_uuid = NULL;
|
||||
g_object_get(album, "uuid", &album_uuid, NULL);
|
||||
|
||||
if ((album_uuid != NULL) && (!koto_cartographer_has_album_by_uuid(self, album_uuid))) { // Don't have this album
|
||||
g_hash_table_replace(self->albums, album_uuid, album);
|
||||
if ((album_uuid == NULL) || koto_cartographer_has_album_by_uuid(self, album_uuid)) { // Have the album or invalid UUID
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_replace(self->albums, album_uuid, album);
|
||||
|
||||
g_signal_emit(
|
||||
self,
|
||||
cartographer_signals[SIGNAL_ALBUM_ADDED],
|
||||
0,
|
||||
album
|
||||
);
|
||||
}
|
||||
|
||||
void koto_cartographer_add_artist(KotoCartographer *self, KotoIndexedArtist *artist) {
|
||||
gchar *artist_uuid = NULL;
|
||||
g_object_get(artist, "uuid", &artist_uuid, NULL);
|
||||
|
||||
if ((artist_uuid != NULL) && (!koto_cartographer_has_artist_by_uuid(self, artist_uuid))) { // Don't have this album
|
||||
g_hash_table_replace(self->artists, artist_uuid, artist);
|
||||
if ((artist_uuid == NULL) || koto_cartographer_has_artist_by_uuid(self, artist_uuid)) { // Have the artist or invalid UUID
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_replace(self->artists, artist_uuid, artist);
|
||||
|
||||
g_signal_emit(
|
||||
self,
|
||||
cartographer_signals[SIGNAL_ARTIST_ADDED],
|
||||
0,
|
||||
artist
|
||||
);
|
||||
}
|
||||
|
||||
void koto_cartographer_add_playlist(KotoCartographer *self, KotoPlaylist *playlist) {
|
||||
gchar *playlist_uuid = NULL;
|
||||
g_object_get(playlist, "uuid", &playlist_uuid, NULL);
|
||||
|
||||
if ((playlist_uuid != NULL) && (!koto_cartographer_has_playlist_by_uuid(self, playlist_uuid))) { // Don't have this album
|
||||
g_hash_table_replace(self->playlists, playlist_uuid, playlist);
|
||||
if ((playlist_uuid == NULL) || koto_cartographer_has_playlist_by_uuid(self, playlist_uuid)) { // Have the playlist or invalid UUID
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_replace(self->playlists, playlist_uuid, playlist);
|
||||
|
||||
if (koto_playlist_get_is_finalized(playlist)) { // Already finalized
|
||||
koto_cartographer_emit_playlist_added(playlist, self); // Emit playlist-added immediately
|
||||
} else { // Not finalized
|
||||
g_signal_connect(playlist, "track-load-finalized", G_CALLBACK(koto_cartographer_emit_playlist_added), self);
|
||||
}
|
||||
}
|
||||
|
||||
void koto_cartographer_emit_playlist_added(KotoPlaylist *playlist, KotoCartographer *self) {
|
||||
g_signal_emit(
|
||||
self,
|
||||
cartographer_signals[SIGNAL_PLAYLIST_ADDED],
|
||||
0,
|
||||
playlist
|
||||
);
|
||||
}
|
||||
|
||||
void koto_cartographer_add_track(KotoCartographer *self, KotoIndexedTrack *track) {
|
||||
gchar *track_uuid = NULL;
|
||||
g_object_get(track, "uuid", &track_uuid, NULL);
|
||||
|
||||
if ((track_uuid != NULL) && (!koto_cartographer_has_playlist_by_uuid(self, track_uuid))) { // Don't have this album
|
||||
g_hash_table_replace(self->tracks, track_uuid, track);
|
||||
if ((track_uuid == NULL) || koto_cartographer_has_track_by_uuid(self, track_uuid)) { // Have the track or invalid UUID
|
||||
return;
|
||||
}
|
||||
|
||||
g_hash_table_replace(self->tracks, track_uuid, track);
|
||||
|
||||
g_signal_emit(
|
||||
self,
|
||||
cartographer_signals[SIGNAL_TRACK_ADDED],
|
||||
0,
|
||||
track
|
||||
);
|
||||
}
|
||||
|
||||
KotoIndexedAlbum* koto_cartographer_get_album_by_uuid(KotoCartographer *self, gchar* album_uuid) {
|
||||
|
@ -86,6 +262,10 @@ KotoIndexedArtist* koto_cartographer_get_artist_by_uuid(KotoCartographer *self,
|
|||
return g_hash_table_lookup(self->artists, artist_uuid);
|
||||
}
|
||||
|
||||
GHashTable* koto_cartographer_get_playlists(KotoCartographer *self) {
|
||||
return self->playlists;
|
||||
}
|
||||
|
||||
KotoPlaylist* koto_cartographer_get_playlist_by_uuid(KotoCartographer *self, gchar* playlist_uuid) {
|
||||
return g_hash_table_lookup(self->playlists, playlist_uuid);
|
||||
}
|
||||
|
@ -153,57 +333,77 @@ gboolean koto_cartographer_has_track_by_uuid(KotoCartographer *self, gchar* trac
|
|||
void koto_cartographer_remove_album(KotoCartographer *self, KotoIndexedAlbum *album) {
|
||||
gchar *album_uuid = NULL;
|
||||
g_object_get(album, "uuid", &album_uuid, NULL);
|
||||
return koto_cartographer_remove_album_by_uuid(self, album_uuid);
|
||||
koto_cartographer_remove_album_by_uuid(self, album_uuid);
|
||||
}
|
||||
|
||||
void koto_cartographer_remove_album_by_uuid(KotoCartographer *self, gchar* album_uuid) {
|
||||
if (album_uuid != NULL) {
|
||||
g_hash_table_remove(self->albums, album_uuid);
|
||||
}
|
||||
|
||||
return;
|
||||
g_signal_emit(
|
||||
self,
|
||||
cartographer_signals[SIGNAL_ALBUM_REMOVED],
|
||||
0,
|
||||
album_uuid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void koto_cartographer_remove_artist(KotoCartographer *self, KotoIndexedArtist *artist) {
|
||||
gchar *artist_uuid = NULL;
|
||||
g_object_get(artist, "uuid", &artist_uuid, NULL);
|
||||
return koto_cartographer_remove_artist_by_uuid(self, artist_uuid);
|
||||
koto_cartographer_remove_artist_by_uuid(self, artist_uuid);
|
||||
}
|
||||
|
||||
void koto_cartographer_remove_artist_by_uuid(KotoCartographer *self, gchar* artist_uuid) {
|
||||
if (artist_uuid == NULL) {
|
||||
g_hash_table_remove(self->artists, artist_uuid);
|
||||
}
|
||||
|
||||
return;
|
||||
g_signal_emit(
|
||||
self,
|
||||
cartographer_signals[SIGNAL_ARTIST_REMOVED],
|
||||
0,
|
||||
artist_uuid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void koto_cartographer_remove_playlist(KotoCartographer *self, KotoPlaylist *playlist) {
|
||||
gchar *playlist_uuid = NULL;
|
||||
g_object_get(playlist, "uuid", &playlist_uuid, NULL);
|
||||
return koto_cartographer_remove_playlist_by_uuid(self, playlist_uuid);
|
||||
koto_cartographer_remove_playlist_by_uuid(self, playlist_uuid);
|
||||
}
|
||||
|
||||
void koto_cartographer_remove_playlist_by_uuid(KotoCartographer *self, gchar* playlist_uuid) {
|
||||
if (playlist_uuid != NULL) {
|
||||
g_hash_table_remove(self->playlists, playlist_uuid);
|
||||
}
|
||||
|
||||
return;
|
||||
g_signal_emit(
|
||||
self,
|
||||
cartographer_signals[SIGNAL_PLAYLIST_REMOVED],
|
||||
0,
|
||||
playlist_uuid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void koto_cartographer_remove_track(KotoCartographer *self, KotoIndexedTrack *track) {
|
||||
gchar *track_uuid = NULL;
|
||||
g_object_get(track, "uuid", &track_uuid, NULL);
|
||||
return koto_cartographer_remove_track_by_uuid(self, track_uuid);
|
||||
koto_cartographer_remove_track_by_uuid(self, track_uuid);
|
||||
}
|
||||
|
||||
void koto_cartographer_remove_track_by_uuid(KotoCartographer *self, gchar* track_uuid) {
|
||||
if (track_uuid != NULL) {
|
||||
g_hash_table_remove(self->tracks, track_uuid);
|
||||
}
|
||||
|
||||
return;
|
||||
g_signal_emit(
|
||||
self,
|
||||
cartographer_signals[SIGNAL_TRACK_REMOVED],
|
||||
0,
|
||||
track_uuid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
KotoCartographer* koto_cartographer_new() {
|
||||
|
|
|
@ -27,7 +27,12 @@ G_BEGIN_DECLS
|
|||
**/
|
||||
|
||||
#define KOTO_TYPE_CARTOGRAPHER koto_cartographer_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoCartographer, koto_cartographer, KOTO, CARTOGRAPHER, GObject);
|
||||
|
||||
typedef struct _KotoCartographer KotoCartographer;
|
||||
typedef struct _KotoCartographerClass KotoCartographerClass;
|
||||
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GType koto_cartographer_get_type(void) G_GNUC_CONST;
|
||||
|
||||
/**
|
||||
* Cartographer Functions
|
||||
|
@ -40,9 +45,12 @@ void koto_cartographer_add_artist(KotoCartographer *self, KotoIndexedArtist *art
|
|||
void koto_cartographer_add_playlist(KotoCartographer *self, KotoPlaylist *playlist);
|
||||
void koto_cartographer_add_track(KotoCartographer *self, KotoIndexedTrack *track);
|
||||
|
||||
void koto_cartographer_emit_playlist_added(KotoPlaylist *playlist, KotoCartographer *self);
|
||||
|
||||
KotoIndexedAlbum* koto_cartographer_get_album_by_uuid(KotoCartographer *self, gchar* album_uuid);
|
||||
KotoIndexedArtist* koto_cartographer_get_artist_by_uuid(KotoCartographer *self, gchar* artist_uuid);
|
||||
KotoPlaylist* koto_cartographer_get_playlist_by_uuid(KotoCartographer *self, gchar* playlist_uuid);
|
||||
GHashTable* koto_cartographer_get_playlists(KotoCartographer *self);
|
||||
KotoIndexedTrack* koto_cartographer_get_track_by_uuid(KotoCartographer *self, gchar* track_uuid);
|
||||
|
||||
gboolean koto_cartographer_has_album(KotoCartographer *self, KotoIndexedAlbum *album);
|
||||
|
|
|
@ -38,8 +38,8 @@ int create_db_tables() {
|
|||
char *tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, path string, type int, name string, art_path string);"
|
||||
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, path string, artist_id string, name string, art_path string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
|
||||
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, path string, type int, artist_id string, album_id string, file_name string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
|
||||
"CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string);"
|
||||
"CREATE TABLE IF NOT EXISTS playlist_tracks(playlist_id string PRIMARY KEY, track_id string, position int, current int, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
|
||||
"CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int);"
|
||||
"CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, current int, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
|
||||
|
||||
gchar *create_tables_errmsg = NULL;
|
||||
int rc = sqlite3_exec(koto_db, tables_creation_queries, 0,0, &create_tables_errmsg);
|
||||
|
|
|
@ -373,14 +373,46 @@ static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const G
|
|||
}
|
||||
|
||||
gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self) {
|
||||
return g_strdup((self->has_album_art && (self->art_path != NULL) && (strcmp(self->art_path, "") != 0)) ? self->art_path : "");
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return g_strdup("");
|
||||
}
|
||||
|
||||
return g_strdup((self->has_album_art && (self->art_path != NULL) && (g_strcmp0(self->art_path, "") != 0)) ? self->art_path : "");
|
||||
}
|
||||
|
||||
gchar *koto_indexed_album_get_album_name(KotoIndexedAlbum *self) {
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((self->name == NULL) || g_strcmp0(self->name, "") == 0) { // Not set
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_strdup(self->name); // Return duplicate of the name
|
||||
}
|
||||
|
||||
gchar* koto_indexed_album_get_album_uuid(KotoIndexedAlbum *self) {
|
||||
if ((self->uuid == NULL) || g_strcmp0(self->uuid, "") == 0) { // Not set
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_strdup(self->uuid); // Return a duplicate of the UUID
|
||||
}
|
||||
|
||||
GList* koto_indexed_album_get_tracks(KotoIndexedAlbum *self) {
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return self->tracks; // Return
|
||||
}
|
||||
|
||||
void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art) {
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return;
|
||||
}
|
||||
|
||||
if (album_art == NULL) { // Not valid album art
|
||||
return;
|
||||
}
|
||||
|
@ -395,6 +427,10 @@ void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album
|
|||
}
|
||||
|
||||
void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedTrack *track) {
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return;
|
||||
}
|
||||
|
||||
if (track == NULL) { // Not a file
|
||||
return;
|
||||
}
|
||||
|
@ -405,6 +441,10 @@ void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedTrack *tr
|
|||
}
|
||||
|
||||
void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name) {
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return;
|
||||
}
|
||||
|
||||
if (album_name == NULL) { // Not valid album name
|
||||
return;
|
||||
}
|
||||
|
@ -417,6 +457,10 @@ void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *albu
|
|||
}
|
||||
|
||||
void koto_indexed_album_set_artist_uuid(KotoIndexedAlbum *self, const gchar *artist_uuid) {
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return;
|
||||
}
|
||||
|
||||
if (artist_uuid == NULL) {
|
||||
return;
|
||||
}
|
||||
|
@ -429,6 +473,10 @@ void koto_indexed_album_set_artist_uuid(KotoIndexedAlbum *self, const gchar *art
|
|||
}
|
||||
|
||||
void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self) {
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->tracks == NULL) { // No files to add to the playlist
|
||||
return;
|
||||
}
|
||||
|
@ -436,11 +484,23 @@ void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self) {
|
|||
KotoPlaylist *new_album_playlist = koto_playlist_new(); // Create a new playlist
|
||||
g_object_set(new_album_playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary
|
||||
|
||||
// The following section effectively reverses our tracks, so the first is now last.
|
||||
// It then adds them in reverse order, since our playlist add function will prepend to our queue
|
||||
// This enables the preservation and defaulting of "newest" first everywhere else, while in albums preserving the rightful order of the album
|
||||
// e.g. first track (0) being added last is actually first in the playlist's tracks
|
||||
GList *reversed_tracks = g_list_copy(self->tracks); // Copy our tracks so we can reverse the order
|
||||
reversed_tracks = g_list_reverse(reversed_tracks); // Actually reverse it
|
||||
|
||||
GList *t;
|
||||
for (t = self->tracks; t != NULL; t = t->next) { // For each of the tracks
|
||||
koto_playlist_add_track_by_uuid(new_album_playlist, (gchar*) t->data); // Add the UUID
|
||||
for (t = reversed_tracks; t != NULL; t = t->next) { // For each of the tracks
|
||||
gchar* track_uuid = t->data;
|
||||
koto_playlist_add_track_by_uuid(new_album_playlist, track_uuid, FALSE, FALSE); // Add the UUID, skip commit to table since it is temporary
|
||||
}
|
||||
|
||||
g_list_free(t);
|
||||
g_list_free(reversed_tracks);
|
||||
|
||||
koto_playlist_apply_model(new_album_playlist, KOTO_PREFERRED_MODEL_TYPE_DEFAULT); // Ensure we are using our default model
|
||||
koto_current_playlist_set_playlist(current_playlist, new_album_playlist); // Set our new current playlist
|
||||
}
|
||||
|
||||
|
@ -491,16 +551,19 @@ gint koto_indexed_album_sort_tracks(gconstpointer track1_uuid, gconstpointer tra
|
|||
}
|
||||
|
||||
void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_path) {
|
||||
if (new_path == NULL) {
|
||||
if (!KOTO_IS_INDEXED_ALBUM(self)) { // Not an album
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->path != NULL) {
|
||||
if ((new_path == NULL) || g_strcmp0(new_path, "") == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((self->path != NULL) && g_strcmp0(self->path, "") != 0) {
|
||||
g_free(self->path);
|
||||
}
|
||||
|
||||
self->path = g_strdup(new_path);
|
||||
|
||||
koto_indexed_album_set_album_name(self, g_path_get_basename(self->path)); // Update our album name based on the base name
|
||||
|
||||
if (!self->do_initial_index) { // Not doing our initial index
|
||||
|
|
|
@ -151,7 +151,11 @@ static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const
|
|||
}
|
||||
|
||||
void koto_indexed_artist_add_album(KotoIndexedArtist *self, gchar *album_uuid) {
|
||||
if ((album_uuid == NULL) || (strcmp(album_uuid, "") == 0)) { // No album UUID really defined
|
||||
if (!KOTO_IS_INDEXED_ARTIST(self)) { // Not an artist
|
||||
return;
|
||||
}
|
||||
|
||||
if ((album_uuid == NULL) || g_strcmp0(album_uuid, "") == 0) { // No album UUID really defined
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -163,11 +167,27 @@ void koto_indexed_artist_add_album(KotoIndexedArtist *self, gchar *album_uuid) {
|
|||
}
|
||||
|
||||
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) {
|
||||
if (!KOTO_IS_INDEXED_ARTIST(self)) { // Not an artist
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return g_list_copy(self->albums);
|
||||
}
|
||||
|
||||
gchar* koto_indexed_artist_get_name(KotoIndexedArtist *self) {
|
||||
if (!KOTO_IS_INDEXED_ARTIST(self)) { // Not an artist
|
||||
return g_strdup("");
|
||||
}
|
||||
|
||||
return g_strdup(g_strcmp0(self->artist_name, "") == 0 ? "" : self->artist_name); // Return artist name if set
|
||||
}
|
||||
|
||||
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) {
|
||||
if (album == NULL) { // No album defined
|
||||
if (!KOTO_IS_INDEXED_ARTIST(self)) { // Not an artist
|
||||
return;
|
||||
}
|
||||
|
||||
if (!KOTO_INDEXED_ALBUM(album)) { // No album defined
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -177,11 +197,15 @@ void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum
|
|||
}
|
||||
|
||||
void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path) {
|
||||
if (new_path == NULL) { // No path really
|
||||
if (!KOTO_IS_INDEXED_ARTIST(self)) { // Not an artist
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->path != NULL) { // Already have a path set
|
||||
if ((new_path == NULL) || g_strcmp0(new_path, "") == 0) { // No path really
|
||||
return;
|
||||
}
|
||||
|
||||
if ((self->path != NULL) && g_strcmp0(self->path, "") != 0) { // Already have a path set
|
||||
g_free(self->path); // Free
|
||||
}
|
||||
|
||||
|
@ -190,11 +214,15 @@ void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_p
|
|||
}
|
||||
|
||||
void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name) {
|
||||
if (artist_name == NULL) { // No artist name
|
||||
if (!KOTO_IS_INDEXED_ARTIST(self)) { // Not an artist
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->artist_name != NULL) { // Has artist name
|
||||
if ((artist_name == NULL) || g_strcmp0(artist_name, "") == 0) { // No artist name
|
||||
return;
|
||||
}
|
||||
|
||||
if ((self->artist_name != NULL) && g_strcmp0(self->artist_name, "") != 0) { // Has artist name
|
||||
g_free(self->artist_name);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <taglib/tag_c.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "../db/db.h"
|
||||
#include "../playlist/playlist.h"
|
||||
#include "../koto-utils.h"
|
||||
#include "structs.h"
|
||||
|
||||
|
@ -242,6 +243,57 @@ int process_albums(void *data, int num_columns, char **fields, char **column_nam
|
|||
return 0;
|
||||
}
|
||||
|
||||
int process_playlists(void *data, int num_columns, char **fields, char **column_names) {
|
||||
(void) data; (void) num_columns; (void) column_names; // Don't need any of the params
|
||||
|
||||
gchar *playlist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
|
||||
gchar *playlist_name = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is playlist name
|
||||
gchar *playlist_art_path = g_strdup(koto_utils_unquote_string(fields[2])); // Third column is any art path
|
||||
|
||||
KotoPlaylist *playlist = koto_playlist_new_with_uuid(playlist_uuid); // Create a playlist using the existing UUID
|
||||
koto_playlist_set_name(playlist, playlist_name); // Add the playlist name
|
||||
koto_playlist_set_artwork(playlist, playlist_art_path); // Add the playlist art path
|
||||
|
||||
koto_cartographer_add_playlist(koto_maps, playlist); // Add to cartographer
|
||||
|
||||
int playlist_tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM playlist_tracks WHERE playlist_id=\"%s\" ORDER BY position ASC", playlist_uuid), process_playlists_tracks, playlist, NULL); // Process our playlist tracks
|
||||
if (playlist_tracks_rc != SQLITE_OK) { // Failed to get our playlist tracks
|
||||
g_critical("Failed to read our playlist tracks: %s", sqlite3_errmsg(koto_db));
|
||||
return 1;
|
||||
}
|
||||
|
||||
koto_playlist_mark_as_finalized(playlist); // Mark as finalized since loading should be complete
|
||||
|
||||
g_free(playlist_uuid);
|
||||
g_free(playlist_name);
|
||||
g_free(playlist_art_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_playlists_tracks(void *data, int num_columns, char **fields, char **column_names) {
|
||||
(void) data; (void) num_columns; (void) column_names; // Don't need these
|
||||
|
||||
gchar *playlist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
|
||||
gchar *track_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
|
||||
gboolean current = g_strcmp0(koto_utils_unquote_string(fields[3]), "0");
|
||||
|
||||
KotoPlaylist *playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, playlist_uuid); // Get the playlist
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) {
|
||||
goto freeforret;
|
||||
}
|
||||
|
||||
koto_playlist_add_track(playlist, track, current, FALSE); // Add the track to the playlist but don't re-commit to the table
|
||||
|
||||
freeforret:
|
||||
g_free(playlist_uuid);
|
||||
g_free(track_uuid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_tracks(void *data, int num_columns, char **fields, char **column_names) {
|
||||
(void) num_columns; (void) column_names; // Don't need these
|
||||
|
||||
|
@ -287,6 +339,12 @@ void read_from_db(KotoIndexedLibrary *self) {
|
|||
}
|
||||
|
||||
g_hash_table_foreach(self->music_artists, output_artists, NULL);
|
||||
|
||||
int playlist_rc = sqlite3_exec(koto_db, "SELECT * FROM playlist_meta", process_playlists, self, NULL); // Process our playlists
|
||||
if (playlist_rc != SQLITE_OK) { // Failed to get our playlists
|
||||
g_critical("Failed to read our playlists: %s", sqlite3_errmsg(koto_db));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void start_indexing(KotoIndexedLibrary *self) {
|
||||
|
@ -362,7 +420,6 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
|
|||
KotoIndexedArtist *artist = koto_indexed_library_get_artist(self, artist_name); // Get the artist
|
||||
|
||||
if (artist == NULL) {
|
||||
g_message("Failed to get artist by name of: %s", artist_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -422,7 +479,6 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
|
|||
void output_track(gpointer data, gpointer user_data) {
|
||||
(void) user_data;
|
||||
|
||||
g_message("Track UUID: %s", g_strdup(data));
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) data);
|
||||
|
||||
if (track == NULL) {
|
||||
|
|
|
@ -27,12 +27,15 @@ G_BEGIN_DECLS
|
|||
|
||||
#define KOTO_TYPE_INDEXED_LIBRARY koto_indexed_library_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoIndexedLibrary, koto_indexed_library, KOTO, INDEXED_LIBRARY, GObject);
|
||||
#define KOTO_IS_INDEXED_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_INDEXED_LIBRARY))
|
||||
|
||||
#define KOTO_TYPE_INDEXED_ARTIST koto_indexed_artist_get_type()
|
||||
G_DECLARE_FINAL_TYPE (KotoIndexedArtist, koto_indexed_artist, KOTO, INDEXED_ARTIST, GObject);
|
||||
#define KOTO_IS_INDEXED_ARTIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_INDEXED_ARTIST))
|
||||
|
||||
#define KOTO_TYPE_INDEXED_ALBUM koto_indexed_album_get_type()
|
||||
G_DECLARE_FINAL_TYPE (KotoIndexedAlbum, koto_indexed_album, KOTO, INDEXED_ALBUM, GObject);
|
||||
#define KOTO_IS_INDEXED_ALBUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_INDEXED_ALBUM))
|
||||
|
||||
#define KOTO_TYPE_INDEXED_TRACK koto_indexed_track_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoIndexedTrack, koto_indexed_track, KOTO, INDEXED_TRACK, GObject);
|
||||
|
@ -51,6 +54,8 @@ void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArt
|
|||
void koto_indexed_library_set_path(KotoIndexedLibrary *self, gchar *path);
|
||||
int process_artists(void *data, int num_columns, char **fields, char **column_names);
|
||||
int process_albums(void *data, int num_columns, char **fields, char **column_names);
|
||||
int process_playlists(void *data, int num_columns, char **fields, char **column_names);
|
||||
int process_playlists_tracks(void *data, int num_columns, char **fields, char **column_names);
|
||||
int process_tracks(void *data, int num_columns, char **fields, char **column_names);
|
||||
void read_from_db(KotoIndexedLibrary *self);
|
||||
void start_indexing(KotoIndexedLibrary *self);
|
||||
|
@ -68,6 +73,7 @@ void koto_indexed_artist_add_album(KotoIndexedArtist *self, gchar *album_uuid);
|
|||
void koto_indexed_artist_commit(KotoIndexedArtist *self);
|
||||
guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data);
|
||||
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self);
|
||||
gchar* koto_indexed_artist_get_name(KotoIndexedArtist *self);
|
||||
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album);
|
||||
void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name);
|
||||
void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name);
|
||||
|
@ -86,6 +92,8 @@ void koto_indexed_album_commit(KotoIndexedAlbum *self);
|
|||
void koto_indexed_album_find_album_art(KotoIndexedAlbum *self);
|
||||
void koto_indexed_album_find_tracks(KotoIndexedAlbum *self, magic_t magic_cookie, const gchar *path);
|
||||
gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self);
|
||||
gchar* koto_indexed_album_get_album_name(KotoIndexedAlbum *self);
|
||||
gchar* koto_indexed_album_get_album_uuid(KotoIndexedAlbum *self);
|
||||
GList* koto_indexed_album_get_tracks(KotoIndexedAlbum *self);
|
||||
void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedTrack *track);
|
||||
void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art);
|
||||
|
@ -103,8 +111,10 @@ KotoIndexedTrack* koto_indexed_track_new(KotoIndexedAlbum *album, const gchar *p
|
|||
KotoIndexedTrack* koto_indexed_track_new_with_uuid(const gchar *uuid);
|
||||
|
||||
void koto_indexed_track_commit(KotoIndexedTrack *self);
|
||||
gchar* koto_indexed_track_get_uuid(KotoIndexedTrack *self);
|
||||
void koto_indexed_track_parse_name(KotoIndexedTrack *self);
|
||||
void koto_indexed_track_save_to_playlist(KotoIndexedTrack *self, gchar *playlist_uuid, guint position, gint current);
|
||||
void koto_indexed_track_remove_from_playlist(KotoIndexedTrack *self, gchar *playlist_uuid);
|
||||
void koto_indexed_track_save_to_playlist(KotoIndexedTrack *self, gchar *playlist_uuid, gint current);
|
||||
void koto_indexed_track_set_file_name(KotoIndexedTrack *self, gchar *new_file_name);
|
||||
void koto_indexed_track_set_cd(KotoIndexedTrack *self, guint cd);
|
||||
void koto_indexed_track_set_parsed_name(KotoIndexedTrack *self, gchar *new_parsed_name);
|
||||
|
|
|
@ -278,6 +278,14 @@ void koto_indexed_track_commit(KotoIndexedTrack *self) {
|
|||
g_free(commit_op_errmsg);
|
||||
}
|
||||
|
||||
gchar* koto_indexed_track_get_uuid(KotoIndexedTrack *self) {
|
||||
if (!KOTO_IS_INDEXED_TRACK(self)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return self->uuid; // Do not return a duplicate since otherwise comparison refs fail due to pointer positions being different
|
||||
}
|
||||
|
||||
void koto_indexed_track_parse_name(KotoIndexedTrack *self) {
|
||||
gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters
|
||||
|
||||
|
@ -335,14 +343,37 @@ void koto_indexed_track_parse_name(KotoIndexedTrack *self) {
|
|||
g_free(file_without_ext);
|
||||
}
|
||||
|
||||
void koto_indexed_track_save_to_playlist(KotoIndexedTrack *self, gchar *playlist_uuid, guint position, gint current) {
|
||||
void koto_indexed_track_remove_from_playlist(KotoIndexedTrack *self, gchar *playlist_uuid) {
|
||||
if (!KOTO_IS_INDEXED_TRACK(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *commit_op = g_strdup_printf(
|
||||
"INSERT INTO playlist_tracks(playlist_id, track_id, position, current)"
|
||||
"VALUES('%s', '%s', quote(\"%d\"), quote(\"%d\")"
|
||||
"ON CONFLICT(playlist_id, track_id) DO UPDATE SET position=excluded.position, current=excluded.current;",
|
||||
"DELETE FROM playlist_tracks WHERE track_id='%s' AND playlist_id='%s'",
|
||||
self->uuid,
|
||||
playlist_uuid
|
||||
);
|
||||
|
||||
gchar *commit_op_errmsg = NULL;
|
||||
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
|
||||
if (rc != SQLITE_OK) {
|
||||
g_warning("Failed to remove track from playlist: %s", commit_op_errmsg);
|
||||
}
|
||||
|
||||
g_free(commit_op);
|
||||
g_free(commit_op_errmsg);
|
||||
}
|
||||
|
||||
void koto_indexed_track_save_to_playlist(KotoIndexedTrack *self, gchar *playlist_uuid, gint current) {
|
||||
if (!KOTO_IS_INDEXED_TRACK(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *commit_op = g_strdup_printf(
|
||||
"INSERT INTO playlist_tracks(playlist_id, track_id, current)"
|
||||
"VALUES('%s', '%s', quote(\"%d\"))",
|
||||
playlist_uuid,
|
||||
self->uuid,
|
||||
position,
|
||||
current
|
||||
);
|
||||
|
||||
|
|
|
@ -51,10 +51,11 @@ guint koto_get_pixbuf_size(KotoButtonPixbufSize s) {
|
|||
|
||||
enum {
|
||||
PROP_BTN_0,
|
||||
PROP_USE_FROM_FILE,
|
||||
PROP_PIX_SIZE,
|
||||
PROP_TEXT,
|
||||
PROP_BADGE_TEXT,
|
||||
PROP_USE_FROM_FILE,
|
||||
PROP_IMAGE_FILE_PATH,
|
||||
PROP_ICON_NAME,
|
||||
PROP_ALT_ICON_NAME,
|
||||
N_BTN_PROPERTIES
|
||||
|
@ -70,11 +71,16 @@ struct _KotoButton {
|
|||
GtkWidget *badge_label;
|
||||
GtkWidget *button_label;
|
||||
|
||||
GtkGesture *left_click_gesture;
|
||||
GtkGesture *right_click_gesture;
|
||||
|
||||
gchar *image_file_path;
|
||||
gchar *badge_text;
|
||||
gchar *icon_name;
|
||||
gchar *alt_icon_name;
|
||||
gchar *text;
|
||||
|
||||
KotoButtonImagePosition image_position;
|
||||
gboolean use_from_file;
|
||||
gboolean currently_showing_alt;
|
||||
};
|
||||
|
@ -96,14 +102,6 @@ static void koto_button_class_init(KotoButtonClass *c) {
|
|||
gobject_class->set_property = koto_button_set_property;
|
||||
gobject_class->get_property = koto_button_get_property;
|
||||
|
||||
btn_props[PROP_USE_FROM_FILE] = g_param_spec_boolean(
|
||||
"use-from-file",
|
||||
"Use from a file / file name",
|
||||
"Use from a file / file name",
|
||||
FALSE,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
btn_props[PROP_PIX_SIZE] = g_param_spec_uint(
|
||||
"pixbuf-size",
|
||||
"Pixbuf Size",
|
||||
|
@ -130,6 +128,22 @@ static void koto_button_class_init(KotoButtonClass *c) {
|
|||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
btn_props[PROP_USE_FROM_FILE] = g_param_spec_boolean(
|
||||
"use-from-file",
|
||||
"Use from a file / file name",
|
||||
"Use from a file / file name",
|
||||
FALSE,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
btn_props[PROP_IMAGE_FILE_PATH] = g_param_spec_string(
|
||||
"image-file-path",
|
||||
"File path to image",
|
||||
"File path to image",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
btn_props[PROP_ICON_NAME] = g_param_spec_string(
|
||||
"icon-name",
|
||||
"Icon Name",
|
||||
|
@ -151,6 +165,16 @@ static void koto_button_class_init(KotoButtonClass *c) {
|
|||
|
||||
static void koto_button_init(KotoButton *self) {
|
||||
self->currently_showing_alt = FALSE;
|
||||
self->image_position = KOTO_BUTTON_IMAGE_POS_LEFT;
|
||||
|
||||
self->left_click_gesture = gtk_gesture_click_new(); // Set up our left click gesture
|
||||
self->right_click_gesture = gtk_gesture_click_new(); // Set up our right click gesture
|
||||
|
||||
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(self->left_click_gesture), (int) KOTO_BUTTON_CLICK_TYPE_PRIMARY); // Only allow left clicks on left click gesture
|
||||
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(self->right_click_gesture), (int) KOTO_BUTTON_CLICK_TYPE_SECONDARY); // Only allow right clicks on right click gesture
|
||||
|
||||
gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(self->left_click_gesture)); // Add our left click gesture
|
||||
gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(self->right_click_gesture)); // Add our right click gesture
|
||||
}
|
||||
|
||||
static void koto_button_constructed(GObject *obj) {
|
||||
|
@ -165,6 +189,9 @@ static void koto_button_get_property(GObject *obj, guint prop_id, GValue *val, G
|
|||
KotoButton *self = KOTO_BUTTON(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_IMAGE_FILE_PATH:
|
||||
g_value_set_string(val, self->image_file_path);
|
||||
break;
|
||||
case PROP_USE_FROM_FILE:
|
||||
g_value_set_boolean(val, self->use_from_file);
|
||||
break;
|
||||
|
@ -193,23 +220,24 @@ static void koto_button_set_property(GObject *obj, guint prop_id, const GValue *
|
|||
KotoButton *self = KOTO_BUTTON(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_USE_FROM_FILE:
|
||||
self->use_from_file = g_value_get_boolean(val);
|
||||
break;
|
||||
case PROP_PIX_SIZE:
|
||||
koto_button_set_pixbuf_size(self, g_value_get_uint(val));
|
||||
break;
|
||||
case PROP_TEXT:
|
||||
if (val == NULL) {
|
||||
koto_button_set_text(self, NULL);
|
||||
} else {
|
||||
koto_button_set_text(self, g_strdup(g_value_get_string(val)));
|
||||
if (val != NULL) {
|
||||
koto_button_set_text(self, (gchar*) g_value_get_string(val));
|
||||
}
|
||||
|
||||
break;
|
||||
case PROP_BADGE_TEXT:
|
||||
koto_button_set_badge_text(self, g_strdup(g_value_get_string(val)));
|
||||
break;
|
||||
case PROP_USE_FROM_FILE:
|
||||
self->use_from_file = g_value_get_boolean(val);
|
||||
break;
|
||||
case PROP_IMAGE_FILE_PATH:
|
||||
koto_button_set_file_path(self, (gchar*) g_value_get_string(val));
|
||||
break;
|
||||
case PROP_ICON_NAME:
|
||||
koto_button_set_icon_name(self, g_strdup(g_value_get_string(val)), FALSE);
|
||||
if (!self->currently_showing_alt) { // Not showing alt
|
||||
|
@ -228,10 +256,32 @@ static void koto_button_set_property(GObject *obj, guint prop_id, const GValue *
|
|||
}
|
||||
}
|
||||
|
||||
void koto_button_add_click_handler(KotoButton *self, KotoButtonClickType button, GCallback handler, gpointer user_data) {
|
||||
if (!KOTO_IS_BUTTON(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((button != KOTO_BUTTON_CLICK_TYPE_PRIMARY) && (button != KOTO_BUTTON_CLICK_TYPE_SECONDARY)) { // Not valid type
|
||||
return;
|
||||
}
|
||||
|
||||
g_signal_connect((button == KOTO_BUTTON_CLICK_TYPE_PRIMARY) ? self->left_click_gesture : self->right_click_gesture, "pressed", handler, user_data);
|
||||
}
|
||||
|
||||
void koto_button_flip(KotoButton *self) {
|
||||
if (!KOTO_IS_BUTTON(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
koto_button_show_image(self, !self->currently_showing_alt);
|
||||
}
|
||||
|
||||
void koto_button_hide_image(KotoButton *self) {
|
||||
if (GTK_IS_WIDGET(self->button_pic)) { // Is a widget
|
||||
gtk_widget_hide(self->button_pic);
|
||||
}
|
||||
}
|
||||
|
||||
void koto_button_set_badge_text(KotoButton *self, gchar *text) {
|
||||
if ((text == NULL) || (strcmp(text, "") == 0)) { // If the text is empty
|
||||
self->badge_text = g_strdup("");
|
||||
|
@ -256,6 +306,23 @@ void koto_button_set_badge_text(KotoButton *self, gchar *text) {
|
|||
g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_BADGE_TEXT]);
|
||||
}
|
||||
|
||||
void koto_button_set_file_path(KotoButton *self, gchar *file_path) {
|
||||
if (!KOTO_IS_BUTTON(self)) { // Not a button
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_path == NULL || (g_strcmp0(file_path, "") == 0)) { // Empty string or null
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->image_file_path != NULL && (g_strcmp0(self->image_file_path, "") != 0)) { // Not null and not empty
|
||||
g_free(self->image_file_path);
|
||||
}
|
||||
|
||||
self->image_file_path = g_strdup(file_path);
|
||||
koto_button_show_image(self, FALSE);
|
||||
}
|
||||
|
||||
void koto_button_set_icon_name(KotoButton *self, gchar *icon_name, gboolean for_alt) {
|
||||
gchar *copied_icon_name = g_strdup(icon_name);
|
||||
|
||||
|
@ -291,6 +358,22 @@ void koto_button_set_icon_name(KotoButton *self, gchar *icon_name, gboolean for_
|
|||
g_object_notify_by_pspec(G_OBJECT(self), for_alt ? btn_props[PROP_ALT_ICON_NAME] : btn_props[PROP_ICON_NAME]);
|
||||
}
|
||||
|
||||
void koto_button_set_image_position(KotoButton *self, KotoButtonImagePosition pos) {
|
||||
if (self->image_position == pos) { // Is a different position that currently
|
||||
return;
|
||||
}
|
||||
|
||||
if (GTK_IS_WIDGET(self->button_pic)) { // Button is already defined
|
||||
if (pos == KOTO_BUTTON_IMAGE_POS_RIGHT) { // If we want to move the image to the right
|
||||
gtk_box_reorder_child_after(GTK_BOX(self), self->button_pic, self->button_label); // Move image to after label
|
||||
} else { // Moving image to left
|
||||
gtk_box_reorder_child_after(GTK_BOX(self), self->button_label, self->button_pic); // Move label to after image
|
||||
}
|
||||
}
|
||||
|
||||
self->image_position = pos;
|
||||
}
|
||||
|
||||
void koto_button_set_pixbuf_size(KotoButton *self, guint size) {
|
||||
g_return_if_fail(size != self->pix_size); // If the sizes aren't different, return
|
||||
|
||||
|
@ -305,16 +388,14 @@ void koto_button_set_text(KotoButton *self, gchar *text) {
|
|||
return;
|
||||
}
|
||||
|
||||
gchar *copied_text = g_strdup(text); // Copy our text
|
||||
|
||||
if (strcmp(copied_text, "") == 0) { // Clearing our text
|
||||
if (self->text != NULL) { // Text defined
|
||||
g_free(self->text); // Free existing text
|
||||
}
|
||||
|
||||
self->text = copied_text;
|
||||
self->text = g_strdup(text);
|
||||
|
||||
if (GTK_IS_LABEL(self->button_label)) { // If we have a button label
|
||||
if (strcmp(self->text, "") != 0) { // Have text set
|
||||
if (g_strcmp0(self->text, "") != 0) { // Have text set
|
||||
gtk_label_set_text(GTK_LABEL(self->button_label), self->text);
|
||||
gtk_widget_show(self->button_label); // Show the label
|
||||
} else { // Have a label but no longer text
|
||||
|
@ -322,7 +403,7 @@ void koto_button_set_text(KotoButton *self, gchar *text) {
|
|||
g_free(self->button_label);
|
||||
}
|
||||
} else { // If we do not have a button label
|
||||
if (strcmp(self->text, "") != 0) { // If we have text
|
||||
if ((self->text != NULL) && (g_strcmp0(self->text, "") != 0)) { // If we have text
|
||||
self->button_label = gtk_label_new(self->text); // Create our label
|
||||
gtk_label_set_xalign(GTK_LABEL(self->button_label), 0);
|
||||
|
||||
|
@ -342,15 +423,25 @@ void koto_button_show_image(KotoButton *self, gboolean use_alt) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (self->use_from_file) { // Use from a file instead of icon name
|
||||
if ((self->image_file_path == NULL) || g_strcmp0(self->image_file_path, "") == 0) { // Not set
|
||||
return;
|
||||
}
|
||||
|
||||
if (GTK_IS_IMAGE(self->button_pic)) { // Already have an icon
|
||||
gtk_image_set_from_file(GTK_IMAGE(self->button_pic), self->image_file_path);
|
||||
} else { // Don't have an image yet
|
||||
self->button_pic = gtk_image_new_from_file(self->image_file_path); // Create a new image from the file
|
||||
gtk_box_prepend(GTK_BOX(self), self->button_pic); // Prepend to the box
|
||||
}
|
||||
} else { // From icon name
|
||||
|
||||
if (use_alt && ((self->alt_icon_name == NULL) || (strcmp(self->alt_icon_name, "") == 0))) { // Don't have an alt icon set
|
||||
return;
|
||||
} else if (!use_alt && ((self->icon_name == NULL) || (strcmp(self->icon_name, "") == 0))) { // Don't have icon set
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->use_from_file) { // Use from a file instead of icon name
|
||||
// TODO: Add
|
||||
} else { // From icon name
|
||||
self->currently_showing_alt = use_alt;
|
||||
gchar *name = use_alt ? self->alt_icon_name : self->icon_name;
|
||||
|
||||
|
@ -358,12 +449,21 @@ void koto_button_show_image(KotoButton *self, gboolean use_alt) {
|
|||
gtk_image_set_from_icon_name(GTK_IMAGE(self->button_pic), name); // Just update the existing iamge
|
||||
} else { // Not an image
|
||||
self->button_pic = gtk_image_new_from_icon_name(name); // Get our new image
|
||||
gtk_image_set_pixel_size(GTK_IMAGE(self->button_pic), self->pix_size);
|
||||
gtk_box_prepend(GTK_BOX(self), self->button_pic); // Prepend to the box
|
||||
}
|
||||
|
||||
gtk_image_set_icon_size(GTK_IMAGE(self->button_pic), GTK_ICON_SIZE_INHERIT); // Inherit height of parent widget
|
||||
}
|
||||
|
||||
gtk_image_set_pixel_size(GTK_IMAGE(self->button_pic), self->pix_size);
|
||||
gtk_image_set_icon_size(GTK_IMAGE(self->button_pic), GTK_ICON_SIZE_INHERIT); // Inherit height of parent widget
|
||||
gtk_widget_show(self->button_pic); // Ensure we actually are showing the image
|
||||
}
|
||||
|
||||
void koto_button_unflatten(KotoButton *self) {
|
||||
if (!KOTO_IS_BUTTON(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_widget_remove_css_class(GTK_WIDGET(self), "flat");
|
||||
}
|
||||
|
||||
KotoButton* koto_button_new_plain(gchar *label) {
|
||||
|
@ -382,3 +482,16 @@ KotoButton* koto_button_new_with_icon(gchar *label, gchar *icon_name, gchar *alt
|
|||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
KotoButton *koto_button_new_with_file(gchar *label, gchar *file_path, KotoButtonPixbufSize size) {
|
||||
return g_object_new(KOTO_TYPE_BUTTON,
|
||||
"button-text", label,
|
||||
"use-from-file",
|
||||
TRUE,
|
||||
"image-file-path",
|
||||
file_path,
|
||||
"pixbuf-size",
|
||||
koto_get_pixbuf_size(size),
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
KOTO_BUTTON_CLICK_TYPE_PRIMARY = 1,
|
||||
KOTO_BUTTON_CLICK_TYPE_SECONDARY = 3
|
||||
} KotoButtonClickType;
|
||||
|
||||
typedef enum {
|
||||
KOTO_BUTTON_PIXBUF_SIZE_INVALID,
|
||||
KOTO_BUTTON_PIXBUF_SIZE_TINY,
|
||||
|
@ -33,6 +38,11 @@ typedef enum {
|
|||
KOTO_BUTTON_PIXBUF_SIZE_GODLIKE
|
||||
} KotoButtonPixbufSize;
|
||||
|
||||
typedef enum {
|
||||
KOTO_BUTTON_IMAGE_POS_LEFT,
|
||||
KOTO_BUTTON_IMAGE_POS_RIGHT
|
||||
} KotoButtonImagePosition;
|
||||
|
||||
#define NUM_BUILTIN_SIZES 7
|
||||
|
||||
#define KOTO_TYPE_BUTTON (koto_button_get_type())
|
||||
|
@ -43,14 +53,19 @@ guint koto_get_pixbuf_size(KotoButtonPixbufSize size);
|
|||
|
||||
KotoButton* koto_button_new_plain(gchar *label);
|
||||
KotoButton* koto_button_new_with_icon(gchar *label, gchar *icon_name, gchar *alt_icon_name, KotoButtonPixbufSize size);
|
||||
KotoButton* koto_button_new_with_pixbuf(gchar *label, GdkPixbuf *pix, KotoButtonPixbufSize size);
|
||||
KotoButton *koto_button_new_with_file(gchar *label, gchar *file_path, KotoButtonPixbufSize size);
|
||||
|
||||
void koto_button_add_click_handler(KotoButton *self, KotoButtonClickType button, GCallback handler, gpointer user_data);
|
||||
void koto_button_flip(KotoButton *self);
|
||||
void koto_button_hide_image(KotoButton *self);
|
||||
void koto_button_set_badge_text(KotoButton *self, gchar *text);
|
||||
void koto_button_set_file_path(KotoButton *self, gchar *file_path);
|
||||
void koto_button_set_icon_name(KotoButton *self, gchar *icon_name, gboolean for_alt);
|
||||
void koto_button_set_image_position(KotoButton *self, KotoButtonImagePosition pos);
|
||||
void koto_button_set_pixbuf(KotoButton *self, GdkPixbuf *pix);
|
||||
void koto_button_set_pixbuf_size(KotoButton *self, guint size);
|
||||
void koto_button_set_text(KotoButton *self, gchar *text);
|
||||
void koto_button_show_image(KotoButton *self, gboolean use_alt);
|
||||
void koto_button_unflatten(KotoButton *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
99
src/koto-dialog-container.c
Normal file
99
src/koto-dialog-container.c
Normal file
|
@ -0,0 +1,99 @@
|
|||
/* koto-dialog-container.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-object.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "koto-button.h"
|
||||
#include "koto-dialog-container.h"
|
||||
|
||||
struct _KotoDialogContainer {
|
||||
GtkBox parent_instance;
|
||||
KotoButton *close_button;
|
||||
GtkWidget *dialogs;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoDialogContainer, koto_dialog_container, GTK_TYPE_BOX);
|
||||
|
||||
static void koto_dialog_container_class_init(KotoDialogContainerClass *c) {
|
||||
(void) c;
|
||||
}
|
||||
|
||||
static void koto_dialog_container_init(KotoDialogContainer *self) {
|
||||
gtk_widget_add_css_class(GTK_WIDGET(self), "koto-dialog-container");
|
||||
|
||||
g_object_set(GTK_WIDGET(self),
|
||||
"hexpand",
|
||||
TRUE,
|
||||
"vexpand",
|
||||
TRUE,
|
||||
NULL);
|
||||
|
||||
self->close_button = koto_button_new_with_icon(NULL, "window-close-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_LARGE);
|
||||
gtk_widget_set_halign(GTK_WIDGET(self->close_button), GTK_ALIGN_END);
|
||||
gtk_box_prepend(GTK_BOX(self), GTK_WIDGET(self->close_button)); // Add our close button
|
||||
|
||||
self->dialogs = gtk_stack_new();
|
||||
gtk_stack_set_transition_duration(GTK_STACK(self->dialogs), 0); // No transition timing
|
||||
gtk_stack_set_transition_type(GTK_STACK(self->dialogs), GTK_STACK_TRANSITION_TYPE_NONE); // No transition
|
||||
gtk_widget_set_halign(self->dialogs, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_hexpand(self->dialogs, TRUE);
|
||||
gtk_widget_set_valign(self->dialogs, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_vexpand(self->dialogs, TRUE);
|
||||
|
||||
gtk_box_append(GTK_BOX(self), self->dialogs); // Add the dialogs stack
|
||||
|
||||
koto_button_add_click_handler(self->close_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_dialog_container_handle_close_click), self);
|
||||
gtk_widget_hide(GTK_WIDGET(self)); // Hide by default
|
||||
}
|
||||
|
||||
void koto_dialog_container_add_dialog(KotoDialogContainer *self, gchar *dialog_name, GtkWidget *dialog) {
|
||||
if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_stack_add_named(GTK_STACK(self->dialogs), dialog, dialog_name); // Add the dialog to the stack
|
||||
}
|
||||
|
||||
void koto_dialog_container_handle_close_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
koto_dialog_container_hide((KotoDialogContainer*) user_data);
|
||||
}
|
||||
|
||||
void koto_dialog_container_hide(KotoDialogContainer *self) {
|
||||
if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_widget_hide(GTK_WIDGET(self));
|
||||
}
|
||||
|
||||
void koto_dialog_container_show_dialog(KotoDialogContainer *self, gchar *dialog_name) {
|
||||
if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->dialogs), dialog_name); // Set to the dialog name
|
||||
gtk_widget_show(GTK_WIDGET(self)); // Ensure we show self
|
||||
}
|
||||
|
||||
KotoDialogContainer* koto_dialog_container_new() {
|
||||
return g_object_new(KOTO_TYPE_DIALOG_CONTAINER,
|
||||
"orientation",
|
||||
GTK_ORIENTATION_VERTICAL,
|
||||
NULL
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
/* create-dialog.h
|
||||
/* koto-dialog-container.h
|
||||
*
|
||||
* Copyright 2021 Joshua Strobl
|
||||
*
|
||||
|
@ -25,17 +25,18 @@ G_BEGIN_DECLS
|
|||
* Type Definition
|
||||
**/
|
||||
|
||||
#define KOTO_TYPE_CREATE_PLAYLIST_DIALOG koto_create_playlist_dialog_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoCreatePlaylistDialog, koto_create_playlist_dialog, KOTO, CREATE_PLAYLIST_DIALOG, GObject);
|
||||
#define KOTO_TYPE_DIALOG_CONTAINER koto_dialog_container_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoDialogContainer, koto_dialog_container, KOTO, DIALOG_CONTAINER, GtkBox);
|
||||
#define KOTO_IS_DIALOG_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_DIALOG_CONTAINER))
|
||||
|
||||
/**
|
||||
* Create Dialog Functions
|
||||
* Functions
|
||||
**/
|
||||
|
||||
KotoCreatePlaylistDialog* koto_create_playlist_dialog_new();
|
||||
GtkWidget* koto_create_playlist_dialog_get_content(KotoCreatePlaylistDialog *self);
|
||||
void koto_create_playlist_dialog_handle_close(KotoCreatePlaylistDialog *self);
|
||||
void koto_create_playlist_dialog_handle_create(KotoCreatePlaylistDialog *self);
|
||||
void koto_create_playlist_dialog_handle_image_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data);
|
||||
KotoDialogContainer* koto_dialog_container_new();
|
||||
void koto_dialog_container_add_dialog(KotoDialogContainer *self, gchar *dialog_name, GtkWidget *dialog);
|
||||
void koto_dialog_container_handle_close_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data);
|
||||
void koto_dialog_container_hide(KotoDialogContainer *self);
|
||||
void koto_dialog_container_show_dialog(KotoDialogContainer *self, gchar *dialog_name);
|
||||
|
||||
G_END_DECLS
|
|
@ -123,7 +123,7 @@ static void koto_expander_set_property(GObject *obj, guint prop_id, const GValue
|
|||
KotoExpander *self = KOTO_EXPANDER(obj);
|
||||
|
||||
if (!GTK_IS_WIDGET(self->header_button)) { // Header Button is not a widget
|
||||
KotoButton *new_button = koto_button_new_with_icon("Temporary Text", "emblem-favorite-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
KotoButton *new_button = koto_button_new_with_icon(NULL, "emblem-favorite-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
|
||||
if (GTK_IS_WIDGET(new_button)) { // Created our widget successfully
|
||||
self->header_button = new_button;
|
||||
|
@ -175,9 +175,7 @@ static void koto_expander_init(KotoExpander *self) {
|
|||
|
||||
self->constructed = TRUE;
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_expander_toggle_content), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->header_expand_button), GTK_EVENT_CONTROLLER(controller));
|
||||
koto_button_add_click_handler(self->header_expand_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_expander_toggle_content), self);
|
||||
}
|
||||
|
||||
void koto_expander_set_secondary_button(KotoExpander *self, KotoButton *new_button) {
|
||||
|
@ -215,6 +213,10 @@ void koto_expander_set_content(KotoExpander *self, GtkWidget *new_content) {
|
|||
g_object_notify_by_pspec(G_OBJECT(self), expander_props[PROP_CONTENT]);
|
||||
}
|
||||
|
||||
GtkWidget* koto_expander_get_content(KotoExpander *self) {
|
||||
return self->content;
|
||||
}
|
||||
|
||||
void koto_expander_toggle_content(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
KotoExpander* self = data;
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_EXPANDER (koto_expander_get_type())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (KotoExpander, koto_expander, KOTO, EXPANDER, GtkBox)
|
||||
#define KOTO_IS_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_EXPANDER))
|
||||
|
||||
KotoExpander* koto_expander_new(gchar *primary_icon_name, gchar *primary_label_text);
|
||||
KotoExpander* koto_expander_new_with_button(gchar *primary_icon_name, gchar *primary_label_text, KotoButton *secondary_button);
|
||||
GtkWidget* koto_expander_get_content(KotoExpander *self);
|
||||
void koto_expander_set_icon_name(KotoExpander *self, const gchar *in);
|
||||
void koto_expander_set_label(KotoExpander *self, const gchar *label);
|
||||
void koto_expander_set_secondary_button(KotoExpander *self, KotoButton *new_button);
|
||||
|
|
113
src/koto-nav.c
113
src/koto-nav.c
|
@ -16,12 +16,16 @@
|
|||
*/
|
||||
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "db/cartographer.h"
|
||||
#include "indexer/structs.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-expander.h"
|
||||
#include "koto-nav.h"
|
||||
#include "koto-window.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoWindow *main_window;
|
||||
|
||||
struct _KotoNav {
|
||||
|
@ -46,6 +50,10 @@ struct _KotoNav {
|
|||
KotoButton *music_local;
|
||||
KotoButton *music_radio;
|
||||
|
||||
// Playlists
|
||||
|
||||
GHashTable *playlist_buttons;
|
||||
|
||||
// Podcasts
|
||||
|
||||
KotoButton *podcasts_local;
|
||||
|
@ -63,6 +71,7 @@ static void koto_nav_class_init(KotoNavClass *c) {
|
|||
}
|
||||
|
||||
static void koto_nav_init(KotoNav *self) {
|
||||
self->playlist_buttons = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
self->win = gtk_scrolled_window_new();
|
||||
gtk_widget_set_hexpand_set(self->win, TRUE); // using hexpand-set works, hexpand seems to break it by causing it to take up way too much space
|
||||
gtk_widget_set_size_request(self->win, 300, -1);
|
||||
|
@ -88,19 +97,7 @@ static void koto_nav_init(KotoNav *self) {
|
|||
koto_nav_create_audiobooks_section(self);
|
||||
koto_nav_create_music_section(self);
|
||||
koto_nav_create_podcasts_section(self);
|
||||
|
||||
KotoButton *playlist_add_button = koto_button_new_with_icon("", "list-add-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
KotoExpander *pl_expander = koto_expander_new_with_button("playlist-symbolic", "Playlists", playlist_add_button);
|
||||
|
||||
if (pl_expander != NULL) {
|
||||
self->playlists_expander = pl_expander;
|
||||
gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->playlists_expander));
|
||||
}
|
||||
|
||||
GtkGesture *playlist_add_gesture = gtk_gesture_click_new(); // Create a gesture for clicking on the playlist add
|
||||
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(playlist_add_gesture), 1); // Only allow left click
|
||||
g_signal_connect(playlist_add_gesture, "pressed", G_CALLBACK(koto_nav_handle_playlist_add_click), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(playlist_add_button), GTK_EVENT_CONTROLLER(playlist_add_gesture));
|
||||
koto_nav_create_playlist_section(self);
|
||||
}
|
||||
|
||||
void koto_nav_create_audiobooks_section(KotoNav *self) {
|
||||
|
@ -135,6 +132,24 @@ void koto_nav_create_music_section(KotoNav *self) {
|
|||
gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_radio));
|
||||
|
||||
koto_expander_set_content(m_expander, new_content);
|
||||
koto_button_add_click_handler(self->music_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_nav_handle_local_music_click), NULL);
|
||||
}
|
||||
|
||||
void koto_nav_create_playlist_section(KotoNav *self) {
|
||||
KotoButton *playlist_add_button = koto_button_new_with_icon("", "list-add-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL);
|
||||
KotoExpander *pl_expander = koto_expander_new_with_button("playlist-symbolic", "Playlists", playlist_add_button);
|
||||
|
||||
self->playlists_expander = pl_expander;
|
||||
gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->playlists_expander));
|
||||
|
||||
// TODO: Turn into ListBox to sort playlists
|
||||
GtkWidget *playlist_list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
|
||||
koto_expander_set_content(self->playlists_expander, playlist_list);
|
||||
koto_button_add_click_handler(playlist_add_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_nav_handle_playlist_add_click), NULL);
|
||||
|
||||
g_signal_connect(koto_maps, "playlist-added", G_CALLBACK(koto_nav_handle_playlist_added), self);
|
||||
g_signal_connect(koto_maps, "playlist-removed", G_CALLBACK(koto_nav_handle_playlist_removed), self);
|
||||
}
|
||||
|
||||
void koto_nav_create_podcasts_section(KotoNav *self) {
|
||||
|
@ -155,8 +170,76 @@ void koto_nav_create_podcasts_section(KotoNav *self) {
|
|||
|
||||
void koto_nav_handle_playlist_add_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y; (void) user_data;
|
||||
g_message("plz");
|
||||
koto_window_show_create_playlist_dialog(main_window);
|
||||
koto_window_show_dialog(main_window, "create-modify-playlist");
|
||||
}
|
||||
|
||||
void koto_nav_handle_local_music_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y; (void) user_data;
|
||||
koto_window_go_to_page(main_window, "music.local"); // Go to the playlist page
|
||||
}
|
||||
|
||||
void koto_nav_handle_playlist_button_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
gchar *playlist_uuid = user_data;
|
||||
koto_window_go_to_page(main_window, playlist_uuid); // Go to the playlist page
|
||||
}
|
||||
|
||||
void koto_nav_handle_playlist_added(KotoCartographer *carto, KotoPlaylist *playlist, gpointer user_data) {
|
||||
(void) carto;
|
||||
g_return_if_fail(KOTO_IS_PLAYLIST(playlist));
|
||||
|
||||
KotoNav *self = user_data;
|
||||
g_return_if_fail(KOTO_IS_NAV(self));
|
||||
|
||||
gchar *playlist_uuid = koto_playlist_get_uuid(playlist); // Get the UUID for a playlist
|
||||
|
||||
if (g_hash_table_contains(self->playlist_buttons, playlist_uuid)) { // Already added button
|
||||
g_free(playlist_uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *playlist_name = koto_playlist_get_name(playlist);
|
||||
gchar *playlist_art_path = koto_playlist_get_artwork(playlist); // Get any file path for it
|
||||
KotoButton *playlist_button = NULL;
|
||||
|
||||
if ((playlist_art_path != NULL) && g_strcmp0(playlist_art_path, "") != 0) { // Have a file associated
|
||||
playlist_button = koto_button_new_with_file(playlist_name, playlist_art_path, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
} else { // No file associated
|
||||
playlist_button = koto_button_new_with_icon(playlist_name, "audio-x-generic-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
}
|
||||
|
||||
if (KOTO_IS_BUTTON(playlist_button)) {
|
||||
g_hash_table_insert(self->playlist_buttons, playlist_uuid, playlist_button); // Add the button
|
||||
|
||||
// TODO: Make this a ListBox and sort the playlists alphabetically
|
||||
GtkBox *playlist_expander_content = GTK_BOX(koto_expander_get_content(self->playlists_expander));
|
||||
|
||||
if (GTK_IS_BOX(playlist_expander_content)) {
|
||||
gtk_box_append(playlist_expander_content, GTK_WIDGET(playlist_button));
|
||||
|
||||
koto_button_add_click_handler(playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_nav_handle_playlist_button_click), playlist_uuid);
|
||||
koto_window_handle_playlist_added(koto_maps, playlist, main_window); // TODO: MOVE THIS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void koto_nav_handle_playlist_removed(KotoCartographer *carto, gchar *playlist_uuid, gpointer user_data) {
|
||||
(void) carto;
|
||||
KotoNav *self = user_data;
|
||||
|
||||
if (!g_hash_table_contains(self->playlist_buttons, playlist_uuid)) { // Does not contain this
|
||||
return;
|
||||
}
|
||||
|
||||
KotoButton *playlist_btn = g_hash_table_lookup(self->playlist_buttons, playlist_uuid); // Get the playlist button
|
||||
|
||||
if (!KOTO_IS_BUTTON(playlist_btn)) { // Not a playlist button
|
||||
return;
|
||||
}
|
||||
|
||||
GtkBox *playlist_expander_content = GTK_BOX(koto_expander_get_content(self->playlists_expander));
|
||||
gtk_box_remove(playlist_expander_content, GTK_WIDGET(playlist_btn)); // Remove the button
|
||||
g_hash_table_remove(self->playlist_buttons, playlist_uuid); // Remove from the playlist buttons hash table
|
||||
}
|
||||
|
||||
GtkWidget* koto_nav_get_nav(KotoNav *self) {
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#pragma once
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "db/cartographer.h"
|
||||
#include "indexer/structs.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
@ -27,8 +29,12 @@ G_DECLARE_FINAL_TYPE (KotoNav, koto_nav, KOTO, NAV, GObject)
|
|||
KotoNav* koto_nav_new (void);
|
||||
void koto_nav_create_audiobooks_section(KotoNav *self);
|
||||
void koto_nav_create_music_section(KotoNav *self);
|
||||
void koto_nav_create_playlist_section(KotoNav *self);
|
||||
void koto_nav_create_podcasts_section(KotoNav *self);
|
||||
void koto_nav_handle_playlist_add_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data);
|
||||
void koto_nav_handle_playlist_added(KotoCartographer *carto, KotoPlaylist *playlist, gpointer user_data);
|
||||
void koto_nav_handle_playlist_removed(KotoCartographer *carto, gchar *playlist_uuid, gpointer user_data);
|
||||
void koto_nav_handle_local_music_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data);
|
||||
|
||||
GtkWidget* koto_nav_get_nav(KotoNav *self);
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <gstreamer-1.0/gst/gst.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "db/cartographer.h"
|
||||
#include "playlist/add-remove-track-popover.h"
|
||||
#include "playlist/current.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playback/engine.h"
|
||||
|
@ -25,6 +26,7 @@
|
|||
#include "koto-config.h"
|
||||
#include "koto-playerbar.h"
|
||||
|
||||
extern KotoAddRemoveTrackPopover *koto_add_remove_track_popup;
|
||||
extern KotoCurrentPlaylist *current_playlist;
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoPlaybackEngine *playback_engine;
|
||||
|
@ -82,7 +84,10 @@ static void koto_playerbar_class_init(KotoPlayerBarClass *c) {
|
|||
static void koto_playerbar_constructed(GObject *obj) {
|
||||
KotoPlayerBar *self = KOTO_PLAYERBAR(obj);
|
||||
self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_add_css_class(self->main, "player-bar");
|
||||
|
||||
self->progress_bar = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 120, 1); // Default to 120 as random max
|
||||
|
||||
gtk_scale_set_draw_value(GTK_SCALE(self->progress_bar), FALSE);
|
||||
gtk_scale_set_digits(GTK_SCALE(self->progress_bar), 0);
|
||||
gtk_range_set_increments(GTK_RANGE(self->progress_bar), 1, 1);
|
||||
|
@ -94,7 +99,6 @@ static void koto_playerbar_constructed(GObject *obj) {
|
|||
g_signal_connect(press_controller, "begin", G_CALLBACK(koto_playerbar_handle_progressbar_gesture_begin), self);
|
||||
g_signal_connect(press_controller, "end", G_CALLBACK(koto_playerbar_handle_progressbar_gesture_end), self);
|
||||
g_signal_connect(press_controller, "pressed", G_CALLBACK(koto_playerbar_handle_progressbar_pressed), self);
|
||||
//g_signal_connect(press_controller, "unpaired-release", G_CALLBACK(koto_playerbar_handle_progressbar_unpaired_release), self);
|
||||
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->progress_bar), GTK_EVENT_CONTROLLER(press_controller));
|
||||
|
||||
|
@ -102,11 +106,15 @@ static void koto_playerbar_constructed(GObject *obj) {
|
|||
|
||||
self->controls = gtk_center_box_new();
|
||||
gtk_center_box_set_baseline_position(GTK_CENTER_BOX(self->controls), GTK_BASELINE_POSITION_CENTER);
|
||||
gtk_widget_add_css_class(self->main, "player-bar");
|
||||
|
||||
self->primary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_add_css_class(self->primary_controls_section, "playerbar-primary-controls");
|
||||
|
||||
self->playback_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
self->primary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_add_css_class(self->playback_section, "playerbar-info");
|
||||
|
||||
self->secondary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_add_css_class(self->secondary_controls_section, "playerbar-secondary-controls");
|
||||
|
||||
gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->primary_controls_section));
|
||||
gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->playback_section));
|
||||
|
@ -155,6 +163,7 @@ void koto_playerbar_create_playback_details(KotoPlayerBar* bar) {
|
|||
gtk_image_set_from_paintable(GTK_IMAGE(bar->artwork), GDK_PAINTABLE(audio_paintable));
|
||||
} else { // Not an image
|
||||
bar->artwork = gtk_image_new_from_paintable(GDK_PAINTABLE(audio_paintable));
|
||||
gtk_widget_add_css_class(bar->artwork, "circular");
|
||||
gtk_widget_set_size_request(bar->artwork, 96, 96);
|
||||
gtk_box_append(GTK_BOX(bar->playback_section), bar->artwork);
|
||||
}
|
||||
|
@ -162,8 +171,13 @@ void koto_playerbar_create_playback_details(KotoPlayerBar* bar) {
|
|||
}
|
||||
|
||||
bar->playback_title = gtk_label_new("Title");
|
||||
gtk_label_set_xalign(GTK_LABEL(bar->playback_title), 0);
|
||||
|
||||
bar->playback_album = gtk_label_new("Album");
|
||||
gtk_label_set_xalign(GTK_LABEL(bar->playback_album), 0);
|
||||
|
||||
bar->playback_artist = gtk_label_new("Artist");
|
||||
gtk_label_set_xalign(GTK_LABEL(bar->playback_artist), 0);
|
||||
|
||||
gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_title));
|
||||
gtk_box_append(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_album));
|
||||
|
@ -179,26 +193,17 @@ void koto_playerbar_create_primary_controls(KotoPlayerBar* bar) {
|
|||
|
||||
if (KOTO_IS_BUTTON(bar->back_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->back_button));
|
||||
|
||||
GtkGesture *back_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(back_controller, "pressed", G_CALLBACK(koto_playerbar_go_backwards), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->back_button), GTK_EVENT_CONTROLLER(back_controller));
|
||||
koto_button_add_click_handler(bar->back_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_backwards), bar);
|
||||
}
|
||||
|
||||
if (KOTO_IS_BUTTON(bar->play_pause_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->play_pause_button));
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_play_pause), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->play_pause_button), GTK_EVENT_CONTROLLER(controller));
|
||||
koto_button_add_click_handler(bar->play_pause_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_play_pause), bar);
|
||||
}
|
||||
|
||||
if (KOTO_IS_BUTTON(bar->forward_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button));
|
||||
|
||||
GtkGesture *forwards_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(forwards_controller, "pressed", G_CALLBACK(koto_playerbar_go_forwards), NULL);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->forward_button), GTK_EVENT_CONTROLLER(forwards_controller));
|
||||
koto_button_add_click_handler(bar->forward_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_forwards), bar);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,22 +217,17 @@ void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) {
|
|||
|
||||
if (KOTO_IS_BUTTON(bar->repeat_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->repeat_button));
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_track_repeat), bar);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->repeat_button), GTK_EVENT_CONTROLLER(controller));
|
||||
koto_button_add_click_handler(bar->repeat_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_track_repeat), bar);
|
||||
}
|
||||
|
||||
if (KOTO_IS_BUTTON(bar->shuffle_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->shuffle_button));
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_playlist_shuffle), bar);
|
||||
gtk_widget_add_controller(GTK_WIDGET(bar->shuffle_button), GTK_EVENT_CONTROLLER(controller));
|
||||
koto_button_add_click_handler(bar->shuffle_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_playlist_shuffle), bar);
|
||||
}
|
||||
|
||||
if (KOTO_IS_BUTTON(bar->playlist_button)) {
|
||||
gtk_box_append(GTK_BOX(bar->secondary_controls_section), GTK_WIDGET(bar->playlist_button));
|
||||
koto_button_add_click_handler(bar->playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_handle_playlist_button_clicked), bar);
|
||||
}
|
||||
|
||||
if (KOTO_IS_BUTTON(bar->eq_button)) {
|
||||
|
@ -285,6 +285,18 @@ void koto_playerbar_handle_is_paused(KotoPlaybackEngine *engine, gpointer user_d
|
|||
koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_playlist_button_clicked(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
KotoPlayerBar *self = data;
|
||||
|
||||
if (!KOTO_IS_PLAYERBAR(self)) { // Not a playerbar
|
||||
return;
|
||||
}
|
||||
|
||||
koto_add_remove_track_popover_set_pointing_to_widget(koto_add_remove_track_popup, GTK_WIDGET(self->playlist_button), GTK_POS_TOP); // Position above the playlist button
|
||||
gtk_widget_show(GTK_WIDGET(koto_add_remove_track_popup));
|
||||
}
|
||||
|
||||
void koto_playerbar_handle_progressbar_gesture_begin(GtkGesture *gesture, GdkEventSequence *seq, gpointer data) {
|
||||
(void) gesture; (void) seq;
|
||||
KotoPlayerBar *bar = data;
|
||||
|
@ -421,7 +433,7 @@ void koto_playerbar_set_progressbar_duration(KotoPlayerBar* bar, gint64 duration
|
|||
}
|
||||
}
|
||||
|
||||
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gint64 progress) {
|
||||
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, double progress) {
|
||||
gtk_range_set_value(GTK_RANGE(bar->progress_bar), progress);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ void koto_playerbar_go_backwards(GtkGestureClick *gesture, int n_press, double x
|
|||
void koto_playerbar_go_forwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_handle_is_playing(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
void koto_playerbar_handle_is_paused(KotoPlaybackEngine *engine, gpointer user_data);
|
||||
void koto_playerbar_handle_playlist_button_clicked(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_handle_progressbar_scroll_begin(GtkEventControllerScroll *controller, gpointer data);
|
||||
void koto_playerbar_handle_progressbar_gesture_begin(GtkGesture *gesture, GdkEventSequence *seq, gpointer data);
|
||||
void koto_playerbar_handle_progressbar_gesture_end(GtkGesture *gesture, GdkEventSequence *seq, gpointer data);
|
||||
|
@ -47,7 +48,7 @@ void koto_playerbar_handle_track_shuffle(KotoPlaybackEngine *engine, gpointer us
|
|||
void koto_playerbar_handle_volume_button_change(GtkScaleButton *button, double value, gpointer user_data);
|
||||
void koto_playerbar_reset_progressbar(KotoPlayerBar* bar);
|
||||
void koto_playerbar_set_progressbar_duration(KotoPlayerBar* bar, gint64 duration);
|
||||
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gint64 progress);
|
||||
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gdouble progress);
|
||||
void koto_playerbar_toggle_play_pause(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_toggle_playlist_shuffle(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
void koto_playerbar_toggle_track_repeat(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
|
||||
|
|
|
@ -16,15 +16,18 @@
|
|||
*/
|
||||
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "indexer/structs.h"
|
||||
#include "playlist/add-remove-track-popover.h"
|
||||
#include "koto-button.h"
|
||||
#include "koto-track-item.h"
|
||||
|
||||
extern KotoAddRemoveTrackPopover *koto_add_remove_track_popup;
|
||||
|
||||
struct _KotoTrackItem {
|
||||
GtkBox parent_instance;
|
||||
KotoIndexedTrack *track;
|
||||
|
||||
GtkWidget *track_label;
|
||||
KotoButton *add_to_playlist_button;
|
||||
};
|
||||
|
||||
struct _KotoTrackItemClass {
|
||||
|
@ -91,14 +94,15 @@ static void koto_track_item_init(KotoTrackItem *self) {
|
|||
self->track_label = gtk_label_new(NULL); // Create with no track name
|
||||
gtk_label_set_xalign(GTK_LABEL(self->track_label), 0.0);
|
||||
|
||||
self->add_to_playlist_button = koto_button_new_with_icon(NULL, "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_TINY);
|
||||
|
||||
gtk_widget_add_css_class(GTK_WIDGET(self), "track-item");
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE);
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(self->track_label), TRUE);
|
||||
|
||||
gtk_box_prepend(GTK_BOX(self), self->track_label);
|
||||
gtk_box_append(GTK_BOX(self), GTK_WIDGET(self->add_to_playlist_button));
|
||||
}
|
||||
|
||||
KotoIndexedTrack* koto_track_item_get_track(KotoTrackItem *self) {
|
||||
return self->track;
|
||||
}
|
||||
|
||||
void koto_track_item_set_track(KotoTrackItem *self, KotoIndexedTrack *track) {
|
||||
|
|
|
@ -28,6 +28,8 @@ G_BEGIN_DECLS
|
|||
G_DECLARE_FINAL_TYPE(KotoTrackItem, koto_track_item, KOTO, TRACK_ITEM, GtkBox)
|
||||
|
||||
KotoTrackItem* koto_track_item_new(KotoIndexedTrack *track);
|
||||
void koto_track_item_handle_add_to_playlist_button_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data);
|
||||
KotoIndexedTrack* koto_track_item_get_track(KotoTrackItem *self);
|
||||
void koto_track_item_set_track(KotoTrackItem *self, KotoIndexedTrack *track);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -18,6 +18,25 @@
|
|||
#include <glib-2.0/glib.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
|
||||
extern GtkWindow *main_window;
|
||||
|
||||
GtkFileChooserNative* koto_utils_create_image_file_chooser(gchar *file_chooser_label) {
|
||||
GtkFileChooserNative* chooser = gtk_file_chooser_native_new(
|
||||
file_chooser_label,
|
||||
main_window,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"Choose",
|
||||
"Cancel"
|
||||
);
|
||||
|
||||
GtkFileFilter *image_filter = gtk_file_filter_new(); // Create our file filter
|
||||
gtk_file_filter_add_mime_type(image_filter, "image/*"); // Only allow for images
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(chooser), image_filter); // Only allow picking images
|
||||
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), FALSE);
|
||||
|
||||
return chooser;
|
||||
}
|
||||
|
||||
GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height) {
|
||||
GtkWidget* image = NULL;
|
||||
|
||||
|
@ -68,6 +87,10 @@ gchar* koto_utils_get_filename_without_extension(gchar *filename) {
|
|||
return stripped_file_name;
|
||||
}
|
||||
|
||||
void koto_utils_push_queue_element_to_store(gpointer data, gpointer user_data) {
|
||||
g_list_store_append(G_LIST_STORE(user_data), data);
|
||||
}
|
||||
|
||||
gchar* koto_utils_replace_string_all(gchar *str, gchar *find, gchar *repl) {
|
||||
gchar *cleaned_string = "";
|
||||
gchar **split = g_strsplit(str, find, -1); // Split on find
|
||||
|
|
|
@ -21,8 +21,10 @@
|
|||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GtkFileChooserNative* koto_utils_create_image_file_chooser(gchar *file_chooser_label);
|
||||
GtkWidget* koto_utils_create_image_from_filepath(gchar *filepath, gchar *fallback_icon, guint width, guint height);
|
||||
gchar* koto_utils_get_filename_without_extension(gchar *filename);
|
||||
void koto_utils_push_queue_element_to_store(gpointer data, gpointer user_data);
|
||||
gchar *koto_utils_replace_string_all(gchar *str, gchar *find, gchar *repl);
|
||||
gchar* koto_utils_unquote_string(gchar *s);
|
||||
|
||||
|
|
|
@ -16,17 +16,27 @@
|
|||
*/
|
||||
|
||||
#include <gtk-4.0/gdk/x11/gdkx.h>
|
||||
#include "components/koto-action-bar.h"
|
||||
#include "db/cartographer.h"
|
||||
#include "indexer/structs.h"
|
||||
#include "pages/music/music-local.h"
|
||||
#include "pages/playlist/list.h"
|
||||
#include "playback/engine.h"
|
||||
#include "playlist/add-remove-track-popover.h"
|
||||
#include "playlist/current.h"
|
||||
#include "playlist/create-dialog.h"
|
||||
#include "playlist/create-modify-dialog.h"
|
||||
#include "koto-config.h"
|
||||
#include "koto-dialog-container.h"
|
||||
#include "koto-nav.h"
|
||||
#include "koto-playerbar.h"
|
||||
#include "koto-window.h"
|
||||
|
||||
extern KotoActionBar *action_bar;
|
||||
extern KotoAddRemoveTrackPopover *koto_add_remove_track_popup;
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoCreateModifyPlaylistDialog *playlist_create_modify_dialog;
|
||||
extern KotoCurrentPlaylist *current_playlist;
|
||||
extern KotoPageMusicLocal *music_local_page;
|
||||
extern KotoPlaybackEngine *playback_engine;
|
||||
|
||||
struct _KotoWindow {
|
||||
|
@ -34,7 +44,7 @@ struct _KotoWindow {
|
|||
KotoIndexedLibrary *library;
|
||||
KotoCurrentPlaylist *current_playlist;
|
||||
|
||||
KotoCreatePlaylistDialog *playlist_create_dialog;
|
||||
KotoDialogContainer *dialogs;
|
||||
|
||||
GtkWidget *overlay;
|
||||
GtkWidget *header_bar;
|
||||
|
@ -66,14 +76,18 @@ static void koto_window_init (KotoWindow *self) {
|
|||
create_new_headerbar(self); // Create our headerbar
|
||||
|
||||
self->overlay = gtk_overlay_new(); // Create our overlay
|
||||
self->playlist_create_dialog = koto_create_playlist_dialog_new(); // Create our Create Playlist dialog
|
||||
self->dialogs = koto_dialog_container_new(); // Create our dialog container
|
||||
|
||||
self->primary_layout = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_add_css_class(self->primary_layout, "primary-layout");
|
||||
gtk_widget_set_hexpand(self->primary_layout, TRUE);
|
||||
gtk_widget_set_vexpand(self->primary_layout, TRUE);
|
||||
|
||||
playlist_create_modify_dialog = koto_create_modify_playlist_dialog_new(); // Create our Create Playlist dialog
|
||||
koto_dialog_container_add_dialog(self->dialogs, "create-modify-playlist", GTK_WIDGET(playlist_create_modify_dialog));
|
||||
|
||||
gtk_overlay_set_child(GTK_OVERLAY(self->overlay), self->primary_layout); // Add our primary layout to the overlay
|
||||
gtk_overlay_add_overlay(GTK_OVERLAY(self->overlay), GTK_WIDGET(self->dialogs)); // Add the stack as our overlay
|
||||
|
||||
self->content_layout = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_add_css_class(self->content_layout, "content-layout");
|
||||
|
@ -97,9 +111,20 @@ static void koto_window_init (KotoWindow *self) {
|
|||
|
||||
gtk_box_prepend(GTK_BOX(self->primary_layout), self->content_layout);
|
||||
|
||||
koto_add_remove_track_popup = koto_add_remove_track_popover_new(); // Create our popover for adding and removing tracks
|
||||
action_bar = koto_action_bar_new(); // Create our Koto Action Bar
|
||||
|
||||
if (KOTO_IS_ACTION_BAR(action_bar)) { // Is an action bar
|
||||
GtkActionBar *bar = koto_action_bar_get_main(action_bar);
|
||||
|
||||
if (GTK_IS_ACTION_BAR(bar)) {
|
||||
gtk_box_append(GTK_BOX(self->primary_layout), GTK_WIDGET(bar)); // Add the action
|
||||
}
|
||||
}
|
||||
|
||||
self->player_bar = koto_playerbar_new();
|
||||
|
||||
if (self->player_bar != NULL) {
|
||||
if (KOTO_IS_PLAYERBAR(self->player_bar)) { // Is a playerbar
|
||||
GtkWidget *playerbar_main = koto_playerbar_get_main(self->player_bar);
|
||||
gtk_box_append(GTK_BOX(self->primary_layout), playerbar_main);
|
||||
}
|
||||
|
@ -116,6 +141,42 @@ static void koto_window_init (KotoWindow *self) {
|
|||
g_thread_new("load-library", (void*) load_library, self);
|
||||
}
|
||||
|
||||
void koto_window_add_page(KotoWindow *self, gchar *page_name, GtkWidget *page) {
|
||||
gtk_stack_add_named(GTK_STACK(self->pages), page, page_name);
|
||||
}
|
||||
|
||||
void koto_window_go_to_page(KotoWindow *self, gchar *page_name) {
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->pages), page_name);
|
||||
}
|
||||
|
||||
void koto_window_handle_playlist_added(KotoCartographer *carto, KotoPlaylist *playlist, gpointer user_data) {
|
||||
(void) carto;
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) {
|
||||
return;
|
||||
}
|
||||
|
||||
KotoWindow *self = user_data;
|
||||
|
||||
gchar *playlist_uuid = koto_playlist_get_uuid(playlist);
|
||||
KotoPlaylistPage *playlist_page = koto_playlist_page_new(playlist_uuid); // Create our new Playlist Page
|
||||
koto_window_add_page(self, playlist_uuid, koto_playlist_page_get_main(playlist_page)); // Get the GtkScrolledWindow "main" content of the playlist page and add that as a page to our stack by the playlist UUID
|
||||
}
|
||||
|
||||
void koto_window_hide_dialogs(KotoWindow *self) {
|
||||
koto_dialog_container_hide(self->dialogs); // Hide the dialog container
|
||||
}
|
||||
|
||||
void koto_window_remove_page(KotoWindow *self, gchar *page_name) {
|
||||
GtkWidget *page = gtk_stack_get_child_by_name(GTK_STACK(self->pages), page_name);
|
||||
g_return_if_fail(page != NULL);
|
||||
gtk_stack_remove(GTK_STACK(self->pages), page);
|
||||
}
|
||||
|
||||
void koto_window_show_dialog(KotoWindow *self, gchar *dialog_name) {
|
||||
koto_dialog_container_show_dialog(self->dialogs, dialog_name);
|
||||
}
|
||||
|
||||
void create_new_headerbar(KotoWindow *self) {
|
||||
self->header_bar = gtk_header_bar_new();
|
||||
gtk_widget_add_css_class(self->header_bar, "hdr");
|
||||
|
@ -136,26 +197,18 @@ void create_new_headerbar(KotoWindow *self) {
|
|||
gtk_window_set_titlebar(GTK_WINDOW(self), self->header_bar);
|
||||
}
|
||||
|
||||
void koto_window_hide_create_playlist_dialog(KotoWindow *self) {
|
||||
gtk_overlay_remove_overlay(GTK_OVERLAY(self->overlay), koto_create_playlist_dialog_get_content(self->playlist_create_dialog));
|
||||
}
|
||||
|
||||
void koto_window_show_create_playlist_dialog(KotoWindow *self) {
|
||||
gtk_overlay_add_overlay(GTK_OVERLAY(self->overlay), koto_create_playlist_dialog_get_content(self->playlist_create_dialog));
|
||||
}
|
||||
|
||||
void load_library(KotoWindow *self) {
|
||||
KotoIndexedLibrary *lib = koto_indexed_library_new(g_get_user_special_dir(G_USER_DIRECTORY_MUSIC));
|
||||
|
||||
if (lib != NULL) {
|
||||
self->library = lib;
|
||||
KotoPageMusicLocal* l = koto_page_music_local_new();
|
||||
music_local_page = koto_page_music_local_new();
|
||||
|
||||
// TODO: Remove and do some fancy state loading
|
||||
gtk_stack_add_named(GTK_STACK(self->pages), GTK_WIDGET(l), "music.local");
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->pages), "music.local");
|
||||
koto_window_add_page(self, "music.local", GTK_WIDGET(music_local_page));
|
||||
koto_window_go_to_page(self, "music.local");
|
||||
gtk_widget_show(self->pages); // Do not remove this. Will cause sporadic hiding of the local page content otherwise.
|
||||
koto_page_music_local_set_library(l, self->library);
|
||||
koto_page_music_local_set_library(music_local_page, self->library);
|
||||
}
|
||||
|
||||
g_thread_exit(0);
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "db/cartographer.h"
|
||||
#include "playlist/playlist.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
@ -25,10 +27,15 @@ G_BEGIN_DECLS
|
|||
|
||||
G_DECLARE_FINAL_TYPE (KotoWindow, koto_window, KOTO, WINDOW, GtkApplicationWindow)
|
||||
|
||||
void koto_window_show_create_playlist_dialog(KotoWindow *self);
|
||||
void koto_window_hide_create_playlist_dialog(KotoWindow *self);
|
||||
void koto_window_add_page(KotoWindow *self, gchar *page_name, GtkWidget *page);
|
||||
void koto_window_go_to_page(KotoWindow *self, gchar *page_name);
|
||||
void koto_window_handle_playlist_added(KotoCartographer *carto, KotoPlaylist *playlist, gpointer user_data);
|
||||
void koto_window_hide_dialogs(KotoWindow *self);
|
||||
void koto_window_remove_page(KotoWindow *self, gchar *page_name);
|
||||
void koto_window_show_dialog(KotoWindow *self, gchar *dialog_name);
|
||||
|
||||
void create_new_headerbar(KotoWindow *self);
|
||||
void handle_album_added();
|
||||
void load_library(KotoWindow *self);
|
||||
void set_optimal_default_window_size(KotoWindow *self);
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
add_project_arguments('-Db_sanitize=address', language: 'c')
|
||||
|
||||
koto_sources = [
|
||||
'components/koto-action-bar.c',
|
||||
'components/koto-cover-art-button.c',
|
||||
'db/cartographer.c',
|
||||
'db/db.c',
|
||||
'indexer/album.c',
|
||||
|
@ -11,15 +13,18 @@ koto_sources = [
|
|||
'pages/music/artist-view.c',
|
||||
'pages/music/disc-view.c',
|
||||
'pages/music/music-local.c',
|
||||
'pages/playlist/list.c',
|
||||
'playback/engine.c',
|
||||
'playback/media-keys.c',
|
||||
'playback/mimes.c',
|
||||
'playback/mpris.c',
|
||||
'playlist/create-dialog.c',
|
||||
'playlist/add-remove-track-popover.c',
|
||||
'playlist/create-modify-dialog.c',
|
||||
'playlist/current.c',
|
||||
'playlist/playlist.c',
|
||||
'main.c',
|
||||
'koto-button.c',
|
||||
'koto-dialog-container.c',
|
||||
'koto-expander.c',
|
||||
'koto-nav.c',
|
||||
'koto-playerbar.c',
|
||||
|
|
|
@ -82,9 +82,11 @@ static void koto_album_view_init(KotoAlbumView *self) {
|
|||
|
||||
self->discs = gtk_list_box_new(); // Create our list of our tracks
|
||||
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->discs), GTK_SELECTION_NONE);
|
||||
gtk_list_box_set_show_separators(GTK_LIST_BOX(self->discs), FALSE);
|
||||
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_add_css_class(self->discs, "discs-list");
|
||||
gtk_widget_set_can_focus(self->discs, FALSE);
|
||||
gtk_widget_set_focusable(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
|
||||
|
@ -116,9 +118,7 @@ static void koto_album_view_init(KotoAlbumView *self) {
|
|||
g_signal_connect(motion_controller, "leave", G_CALLBACK(koto_album_view_hide_overlay_controls), self);
|
||||
gtk_widget_add_controller(self->album_overlay_container, motion_controller);
|
||||
|
||||
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
|
||||
g_signal_connect(controller, "pressed", G_CALLBACK(koto_album_view_toggle_album_playback), self);
|
||||
gtk_widget_add_controller(GTK_WIDGET(self->play_pause_button), GTK_EVENT_CONTROLLER(controller));
|
||||
koto_button_add_click_handler(self->play_pause_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_album_view_toggle_album_playback), self);
|
||||
}
|
||||
|
||||
GtkWidget* koto_album_view_get_main(KotoAlbumView *self) {
|
||||
|
|
|
@ -122,7 +122,7 @@ static void koto_artist_view_constructed(GObject *obj) {
|
|||
gtk_widget_set_halign(self->favorites_list, GTK_ALIGN_START);
|
||||
|
||||
self->album_list = gtk_flow_box_new(); // Create our list of our albums
|
||||
gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(self->album_list), FALSE);
|
||||
//gtk_flow_box_set_activate_on_single_click(GTK_FLOW_BOX(self->album_list), FALSE);
|
||||
gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->album_list), GTK_SELECTION_NONE);
|
||||
gtk_widget_add_css_class(self->album_list, "album-list");
|
||||
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
*/
|
||||
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../../components/koto-action-bar.h"
|
||||
#include "../../db/cartographer.h"
|
||||
#include "../../indexer/structs.h"
|
||||
#include "../../koto-track-item.h"
|
||||
#include "disc-view.h"
|
||||
|
||||
extern KotoActionBar *action_bar;
|
||||
extern KotoCartographer *koto_maps;
|
||||
|
||||
struct _KotoDiscView {
|
||||
|
@ -121,31 +123,17 @@ static void koto_disc_view_init(KotoDiscView *self) {
|
|||
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_activate_on_single_click(GTK_LIST_BOX(self->list), FALSE);
|
||||
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list), GTK_SELECTION_MULTIPLE);
|
||||
gtk_widget_add_css_class(self->list, "track-list");
|
||||
gtk_widget_set_can_focus(self->list, FALSE);
|
||||
gtk_widget_set_focusable(self->list, FALSE);
|
||||
gtk_widget_set_size_request(self->list, 600, -1);
|
||||
gtk_box_append(GTK_BOX(self), self->list);
|
||||
|
||||
g_list_foreach(koto_indexed_album_get_tracks(self->album), koto_disc_view_list_tracks, self);
|
||||
g_signal_connect(self->list, "selected-rows-changed", G_CALLBACK(koto_disc_view_handle_selected_rows_changed), self);
|
||||
}
|
||||
|
||||
void koto_disc_view_list_tracks(gpointer data, gpointer selfptr) {
|
||||
|
@ -163,6 +151,49 @@ void koto_disc_view_list_tracks(gpointer data, gpointer selfptr) {
|
|||
gtk_list_box_append(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Add to our tracks list box
|
||||
}
|
||||
|
||||
void koto_disc_view_handle_selected_rows_changed(GtkListBox *box, gpointer user_data) {
|
||||
KotoDiscView *self = user_data;
|
||||
|
||||
gchar *album_uuid = koto_indexed_album_get_album_uuid(self->album); // Get the UUID
|
||||
|
||||
if ((album_uuid == NULL) || g_strcmp0(album_uuid, "") == 0) { // Not set
|
||||
return;
|
||||
}
|
||||
|
||||
GList *selected_rows = gtk_list_box_get_selected_rows(box); // Get the selected rows
|
||||
|
||||
if (g_list_length(selected_rows) == 0) { // No rows selected
|
||||
koto_action_bar_toggle_reveal(action_bar, FALSE); // Close the action bar
|
||||
return;
|
||||
}
|
||||
|
||||
GList *selected_tracks = NULL; // Create our list of KotoIndexedTracks
|
||||
GList *cur_selected_rows;
|
||||
for (cur_selected_rows = selected_rows; cur_selected_rows != NULL; cur_selected_rows = cur_selected_rows->next) { // Iterate over the rows
|
||||
KotoTrackItem *track_item = (KotoTrackItem*) gtk_list_box_row_get_child(cur_selected_rows->data);
|
||||
selected_tracks = g_list_append(selected_tracks, koto_track_item_get_track(track_item)); // Add the KotoIndexedTrack to our list
|
||||
}
|
||||
|
||||
g_list_free(cur_selected_rows);
|
||||
|
||||
koto_action_bar_set_tracks_in_album_selection(action_bar, album_uuid, selected_tracks); // Set our album selection
|
||||
koto_action_bar_toggle_reveal(action_bar, TRUE); // Show the action bar
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
g_list_foreach(koto_indexed_album_get_tracks(self->album), koto_disc_view_list_tracks, self);
|
||||
}
|
||||
|
||||
void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number) {
|
||||
if (disc_number == 0) {
|
||||
return;
|
||||
|
|
|
@ -28,6 +28,7 @@ G_BEGIN_DECLS
|
|||
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_handle_selected_rows_changed(GtkListBox *box, gpointer user_data);
|
||||
void koto_disc_view_list_tracks(gpointer data, gpointer selfptr);
|
||||
void koto_disc_view_set_album(KotoDiscView *self, KotoIndexedAlbum *album);
|
||||
void koto_disc_view_set_disc_label_visible(KotoDiscView *self, gboolean visible);
|
||||
|
|
|
@ -49,6 +49,8 @@ struct _KotoPageMusicLocalClass {
|
|||
|
||||
G_DEFINE_TYPE(KotoPageMusicLocal, koto_page_music_local, GTK_TYPE_BOX);
|
||||
|
||||
KotoPageMusicLocal *music_local_page;
|
||||
|
||||
static void koto_page_music_local_constructed(GObject *obj);
|
||||
static void koto_page_music_local_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_page_music_local_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
@ -145,6 +147,32 @@ void koto_page_music_local_add_artist(KotoPageMusicLocal *self, KotoIndexedArtis
|
|||
gtk_stack_add_named(GTK_STACK(self->stack), koto_artist_view_get_main(artist_view), artist_name);
|
||||
}
|
||||
|
||||
void koto_page_music_local_go_to_artist_by_name(KotoPageMusicLocal *self, gchar *artist_name) {
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->stack), artist_name);
|
||||
}
|
||||
|
||||
void koto_page_music_local_go_to_artist_by_uuid(KotoPageMusicLocal *self, gchar *artist_uuid) {
|
||||
KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); // Get the artist
|
||||
|
||||
if (!KOTO_IS_INDEXED_ARTIST(artist)) { // No artist for this UUID
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *artist_name = NULL;
|
||||
g_object_get(
|
||||
artist,
|
||||
"name",
|
||||
&artist_name,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (artist_name == NULL || (g_strcmp0(artist_name, "") == 0)) { // Failed to get the artist name
|
||||
return;
|
||||
}
|
||||
|
||||
koto_page_music_local_go_to_artist_by_name(self, artist_name);
|
||||
}
|
||||
|
||||
void koto_page_music_local_handle_artist_click(GtkListBox *box, GtkListBoxRow *row, gpointer data) {
|
||||
(void) box;
|
||||
KotoPageMusicLocal *self = (KotoPageMusicLocal*) data;
|
||||
|
@ -152,7 +180,7 @@ void koto_page_music_local_handle_artist_click(GtkListBox *box, GtkListBoxRow *r
|
|||
|
||||
gchar *artist_name;
|
||||
g_object_get(btn, "button-text", &artist_name, NULL);
|
||||
gtk_stack_set_visible_child_name(GTK_STACK(self->stack), artist_name);
|
||||
koto_page_music_local_go_to_artist_by_name(self, artist_name);
|
||||
}
|
||||
|
||||
void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibrary *lib) {
|
||||
|
|
|
@ -31,6 +31,8 @@ G_DECLARE_FINAL_TYPE (KotoPageMusicLocal, koto_page_music_local, KOTO, PAGE_MUSI
|
|||
KotoPageMusicLocal* koto_page_music_local_new();
|
||||
void koto_page_music_local_add_artist(KotoPageMusicLocal *self, KotoIndexedArtist *artist);
|
||||
void koto_page_music_local_handle_artist_click(GtkListBox *box, GtkListBoxRow *row, gpointer data);
|
||||
void koto_page_music_local_go_to_artist_by_name(KotoPageMusicLocal *self, gchar *artist_name);
|
||||
void koto_page_music_local_go_to_artist_by_uuid(KotoPageMusicLocal *self, gchar *artist_uuid);
|
||||
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);
|
||||
|
||||
|
|
510
src/pages/playlist/list.c
Normal file
510
src/pages/playlist/list.c
Normal file
|
@ -0,0 +1,510 @@
|
|||
/* list.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 "../../components/koto-action-bar.h"
|
||||
#include "../../components/koto-cover-art-button.h"
|
||||
#include "../../db/cartographer.h"
|
||||
#include "../../playlist/current.h"
|
||||
#include "../../playlist/playlist.h"
|
||||
#include "../../koto-button.h"
|
||||
#include "../../koto-window.h"
|
||||
#include "../../playlist/create-modify-dialog.h"
|
||||
#include "list.h"
|
||||
|
||||
extern KotoActionBar *action_bar;
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoCreateModifyPlaylistDialog *playlist_create_modify_dialog;
|
||||
extern KotoCurrentPlaylist *current_playlist;
|
||||
extern KotoWindow *main_window;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PLAYLIST_UUID,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
static GParamSpec *props[N_PROPERTIES] = { NULL, };
|
||||
|
||||
struct _KotoPlaylistPage {
|
||||
GObject parent_instance;
|
||||
KotoPlaylist *playlist;
|
||||
gchar *uuid;
|
||||
|
||||
GtkWidget *main; // Our Scrolled Window
|
||||
GtkWidget *content; // Content inside scrolled window
|
||||
GtkWidget *header;
|
||||
|
||||
KotoCoverArtButton *playlist_image;
|
||||
GtkWidget *name_label;
|
||||
GtkWidget *tracks_count_label;
|
||||
GtkWidget *type_label;
|
||||
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;
|
||||
};
|
||||
|
||||
struct _KotoPlaylistPageClass {
|
||||
GObjectClass parent_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoPlaylistPage, koto_playlist_page, G_TYPE_OBJECT);
|
||||
|
||||
static void koto_playlist_page_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_playlist_page_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_playlist_page_class_init(KotoPlaylistPageClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->get_property = koto_playlist_page_get_property;
|
||||
gobject_class->set_property = koto_playlist_page_set_property;
|
||||
|
||||
props[PROP_PLAYLIST_UUID] = g_param_spec_string(
|
||||
"uuid",
|
||||
"UUID of associated Playlist",
|
||||
"UUID of associated Playlist",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->main), self->content);
|
||||
|
||||
self->header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create a horizontal box
|
||||
gtk_widget_add_css_class(self->header, "playlist-page-header");
|
||||
gtk_box_prepend(GTK_BOX(self->content), self->header);
|
||||
|
||||
self->playlist_image = koto_cover_art_button_new(220, 220, NULL); // Create our Cover Art Button with no art by default
|
||||
KotoButton *cover_art_button = koto_cover_art_button_get_button(self->playlist_image); // Get the button for the cover art
|
||||
koto_button_add_click_handler(cover_art_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_cover_art_clicked), self);
|
||||
|
||||
GtkWidget *info_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_size_request(info_box, -1, 220);
|
||||
gtk_widget_add_css_class(info_box, "playlist-page-header-info");
|
||||
gtk_widget_set_hexpand(info_box, TRUE);
|
||||
|
||||
self->type_label = gtk_label_new(NULL); // Create our type label
|
||||
gtk_widget_set_halign(self->type_label, GTK_ALIGN_START);
|
||||
|
||||
self->name_label = gtk_label_new(NULL);
|
||||
gtk_widget_set_halign(self->name_label, GTK_ALIGN_START);
|
||||
|
||||
self->tracks_count_label = gtk_label_new(NULL);
|
||||
gtk_widget_set_halign(self->tracks_count_label, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign(self->tracks_count_label, GTK_ALIGN_END);
|
||||
|
||||
gtk_box_append(GTK_BOX(info_box), self->type_label);
|
||||
gtk_box_append(GTK_BOX(info_box), self->name_label);
|
||||
gtk_box_append(GTK_BOX(info_box), self->tracks_count_label);
|
||||
|
||||
self->favorite_button = koto_button_new_with_icon(NULL, "emblem-favorite-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
self->edit_button = koto_button_new_with_icon(NULL, "emblem-system-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL);
|
||||
koto_button_add_click_handler(self->edit_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_edit_button_clicked), self); // Set up our binding
|
||||
|
||||
gtk_box_append(GTK_BOX(self->header), koto_cover_art_button_get_main(self->playlist_image)); // Add the Cover Art Button main overlay
|
||||
gtk_box_append(GTK_BOX(self->header), info_box); // Add our info box
|
||||
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
|
||||
}
|
||||
|
||||
static void koto_playlist_page_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
KotoPlaylistPage *self = KOTO_PLAYLIST_PAGE(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_PLAYLIST_UUID:
|
||||
g_value_set_string(val, self->uuid);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_playlist_page_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoPlaylistPage *self = KOTO_PLAYLIST_PAGE(obj);
|
||||
switch (prop_id) {
|
||||
case PROP_PLAYLIST_UUID:
|
||||
koto_playlist_page_set_playlist_uuid(self, g_strdup(g_value_get_string(val))); // Call to our playlist UUID set function
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
KotoIndexedTrack *track = gtk_list_item_get_item(item); // Get the track UUID from our model
|
||||
|
||||
g_return_if_fail(KOTO_IS_INDEXED_TRACK(track));
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
|
||||
KotoIndexedAlbum *album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
|
||||
|
||||
if (KOTO_IS_INDEXED_ALBUM(album)) {
|
||||
gtk_label_set_label(GTK_LABEL(track_album_label), koto_indexed_album_get_album_name(album)); // Get the name of the album and set it to the label
|
||||
}
|
||||
|
||||
KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid);
|
||||
|
||||
if (KOTO_IS_INDEXED_ARTIST(artist)) {
|
||||
gtk_label_set_label(GTK_LABEL(track_artist_label), koto_indexed_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, double x, double y, gpointer user_data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
KotoPlaylistPage *self = user_data;
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(self->playlist)) { // No playlist set
|
||||
return;
|
||||
}
|
||||
|
||||
koto_current_playlist_set_playlist(current_playlist, self->playlist);
|
||||
}
|
||||
|
||||
void koto_playlist_page_handle_edit_button_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;
|
||||
koto_create_modify_playlist_dialog_set_playlist_uuid(playlist_create_modify_dialog, koto_playlist_get_uuid(self->playlist));
|
||||
koto_window_show_dialog(main_window, "create-modify-playlist");
|
||||
}
|
||||
|
||||
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
|
||||
KotoIndexedTrack *selected_track = g_list_model_get_item(self->model, GPOINTER_TO_UINT(cur_pos_list->data)); // Get the KotoIndexedTrack 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) {
|
||||
g_return_if_fail(KOTO_IS_PLAYLIST_PAGE(self));
|
||||
g_return_if_fail(g_strcmp0(playlist_uuid, "") != 0); // Return if empty string
|
||||
g_return_if_fail(koto_cartographer_has_playlist_by_uuid(koto_maps, playlist_uuid)); // Don't have a playlist with this UUID
|
||||
|
||||
self->uuid = g_strdup(playlist_uuid); // Duplicate the 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
|
||||
}
|
||||
|
||||
void koto_playlist_page_set_playlist_model(KotoPlaylistPage *self, KotoPreferredModelType model) {
|
||||
g_return_if_fail(KOTO_IS_PLAYLIST_PAGE(self));
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void koto_playlist_page_update_header(KotoPlaylistPage *self) {
|
||||
g_return_if_fail(KOTO_IS_PLAYLIST_PAGE(self));
|
||||
g_return_if_fail(KOTO_IS_PLAYLIST(self->playlist)); // Not a valid playlist
|
||||
|
||||
gboolean ephemeral = TRUE;
|
||||
g_object_get(
|
||||
self->playlist,
|
||||
"ephemeral",
|
||||
&ephemeral,
|
||||
NULL
|
||||
);
|
||||
|
||||
gtk_label_set_text(GTK_LABEL(self->type_label), ephemeral ? "Generated playlist" : "Curated playlist");
|
||||
|
||||
gtk_label_set_text(GTK_LABEL(self->name_label), koto_playlist_get_name(self->playlist)); // Set the name label to our playlist name
|
||||
guint track_count = koto_playlist_get_length(self->playlist); // Get the number of tracks
|
||||
gtk_label_set_text(GTK_LABEL(self->tracks_count_label), g_strdup_printf(track_count != 1 ? "%u tracks" : "%u track", track_count)); // Set the text to "N tracks" where N is the number
|
||||
|
||||
gchar *artwork = koto_playlist_get_artwork(self->playlist);
|
||||
|
||||
if ((artwork != NULL) && g_strcmp0(artwork, "") != 0) { // 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) {
|
||||
return g_object_new(KOTO_TYPE_PLAYLIST_PAGE,
|
||||
"uuid",
|
||||
playlist_uuid,
|
||||
NULL
|
||||
);
|
||||
}
|
55
src/pages/playlist/list.h
Normal file
55
src/pages/playlist/list.h
Normal file
|
@ -0,0 +1,55 @@
|
|||
/* list.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 "../../components/koto-action-bar.h"
|
||||
#include "../../playlist/playlist.h"
|
||||
#include "../../koto-utils.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define KOTO_TYPE_PLAYLIST_PAGE koto_playlist_page_get_type()
|
||||
G_DECLARE_FINAL_TYPE (KotoPlaylistPage, koto_playlist_page, KOTO, PLAYLIST_PAGE, GObject);
|
||||
#define KOTO_IS_PLAYLIST_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYLIST_PAGE))
|
||||
|
||||
KotoPlaylistPage* koto_playlist_page_new(gchar *playlist_uuid);
|
||||
void koto_playlist_page_add_track(KotoPlaylistPage* self, const gchar *track_uuid);
|
||||
void koto_playlist_page_create_tracks_header(KotoPlaylistPage *self);
|
||||
void koto_playlist_page_bind_track_item(GtkListItemFactory *factory, GtkListItem *item, KotoPlaylistPage *self);
|
||||
void koto_playlist_page_remove_track(KotoPlaylistPage *self, const gchar *track_uuid);
|
||||
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, int n_press, double x, double y, gpointer user_data);
|
||||
void koto_playlist_page_handle_edit_button_clicked(GtkGestureClick *gesture, int n_press, double x, double y, 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
|
|
@ -36,7 +36,6 @@ enum {
|
|||
N_SIGNALS
|
||||
};
|
||||
|
||||
static glong NS = 1000000000;
|
||||
static guint playback_engine_signals[N_SIGNALS] = { 0 };
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
|
@ -51,12 +50,16 @@ struct _KotoPlaybackEngine {
|
|||
GstElement *suppress_video;
|
||||
GstBus *monitor;
|
||||
|
||||
GstQuery *duration_query;
|
||||
GstQuery *position_query;
|
||||
|
||||
KotoIndexedTrack *current_track;
|
||||
|
||||
gboolean is_muted;
|
||||
gboolean is_repeat_enabled;
|
||||
|
||||
gboolean is_playing;
|
||||
gboolean is_playing_specific_track;
|
||||
|
||||
gboolean tick_duration_timer_running;
|
||||
gboolean tick_track_timer_running;
|
||||
|
@ -197,12 +200,17 @@ static void koto_playback_engine_init(KotoPlaybackEngine *self) {
|
|||
gst_bin_add(GST_BIN(self->player), self->playbin);
|
||||
self->monitor = gst_bus_new(); // Get the bus for the playbin
|
||||
|
||||
self->duration_query = gst_query_new_duration(GST_FORMAT_TIME); // Create our re-usable query for querying the duration
|
||||
self->position_query = gst_query_new_position(GST_FORMAT_TIME); // Create our re-usable query for querying the position
|
||||
|
||||
if (GST_IS_BUS(self->monitor)) {
|
||||
gst_bus_add_watch(self->monitor, koto_playback_engine_monitor_changed, self);
|
||||
gst_element_set_bus(self->player, self->monitor); // Set our bus to monitor changes
|
||||
}
|
||||
|
||||
self->is_muted = FALSE;
|
||||
self->is_playing = FALSE;
|
||||
self->is_playing_specific_track = FALSE;
|
||||
self->is_repeat_enabled = FALSE;
|
||||
self->tick_duration_timer_running = FALSE;
|
||||
self->tick_track_timer_running = FALSE;
|
||||
|
@ -219,6 +227,10 @@ void koto_playback_engine_backwards(KotoPlaybackEngine *self) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (self->is_repeat_enabled || self->is_playing_specific_track) { // Repeat enabled or playing a specific track
|
||||
return;
|
||||
}
|
||||
|
||||
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_previous(playlist));
|
||||
}
|
||||
|
||||
|
@ -245,7 +257,7 @@ void koto_playback_engine_forwards(KotoPlaybackEngine *self) {
|
|||
|
||||
if (self->is_repeat_enabled) { // Is repeat enabled
|
||||
koto_playback_engine_set_position(self, 0); // Set position back to 0 to repeat the track
|
||||
} else { // Repeat not enabled
|
||||
} else if (!self->is_repeat_enabled && !self->is_playing_specific_track) { // Repeat not enabled and we are not playing a specific track
|
||||
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist));
|
||||
}
|
||||
}
|
||||
|
@ -256,27 +268,32 @@ KotoIndexedTrack* koto_playback_engine_get_current_track(KotoPlaybackEngine *sel
|
|||
|
||||
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self) {
|
||||
gint64 duration = 0;
|
||||
if (gst_element_query_duration(self->player, GST_FORMAT_TIME, &duration)) {
|
||||
duration = duration / NS; // Divide by NS to get seconds
|
||||
if (gst_element_query(self->player, self->duration_query)) { // Able to query our duration
|
||||
gst_query_parse_duration(self->duration_query, NULL, &duration); // Get the duration
|
||||
duration = duration / GST_SECOND; // Divide by NS to get seconds
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self) {
|
||||
gint64 progress = 0;
|
||||
if (gst_element_query_position(self->player, GST_FORMAT_TIME, &progress)) {
|
||||
progress = progress / NS; // Divide by NS to get seconds
|
||||
gdouble koto_playback_engine_get_progress(KotoPlaybackEngine *self) {
|
||||
gdouble progress = 0.0;
|
||||
gint64 gstprog = 0;
|
||||
if (gst_element_query(self->playbin, self->position_query)) { // Able to get our position
|
||||
gst_query_parse_position(self->position_query, NULL, &gstprog); // Get the progress
|
||||
|
||||
if (gstprog < 1) { // Less than a second
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
progress = gstprog / GST_SECOND; // Divide by GST_SECOND then again by 100.
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self) {
|
||||
GstState current_state;
|
||||
gst_element_get_state(self->player, ¤t_state, NULL, GST_SECOND); // Get the current state, allowing up to a second to get it
|
||||
|
||||
return current_state;
|
||||
return GST_STATE(self->player);
|
||||
}
|
||||
|
||||
gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine *self) {
|
||||
|
@ -361,7 +378,7 @@ void koto_playback_engine_pause(KotoPlaybackEngine *self) {
|
|||
}
|
||||
|
||||
void koto_playback_engine_set_position(KotoPlaybackEngine *self, int position) {
|
||||
gst_element_seek_simple(self->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, position * NS);
|
||||
gst_element_seek_simple(self->playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, position * GST_SECOND);
|
||||
}
|
||||
|
||||
void koto_playback_engine_set_track_repeat(KotoPlaybackEngine *self, gboolean enable_repeat) {
|
||||
|
|
|
@ -48,7 +48,7 @@ void koto_playback_engine_forwards(KotoPlaybackEngine *self);
|
|||
KotoIndexedTrack* koto_playback_engine_get_current_track(KotoPlaybackEngine *self);
|
||||
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self);
|
||||
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self);
|
||||
gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self);
|
||||
gdouble koto_playback_engine_get_progress(KotoPlaybackEngine *self);
|
||||
gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine *self);
|
||||
gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine *self);
|
||||
void koto_playback_engine_mute(KotoPlaybackEngine *self);
|
||||
|
|
249
src/playlist/add-remove-track-popover.c
Normal file
249
src/playlist/add-remove-track-popover.c
Normal file
|
@ -0,0 +1,249 @@
|
|||
/* add-remove-track-popover.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 "../playback/engine.h"
|
||||
#include "../playlist/playlist.h"
|
||||
#include "add-remove-track-popover.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
|
||||
struct _KotoAddRemoveTrackPopover {
|
||||
GtkPopover parent_instance;
|
||||
GtkWidget *list_box;
|
||||
GHashTable *checkbox_to_playlist_uuid;
|
||||
GHashTable *playlist_uuid_to_checkbox;
|
||||
GList *tracks;
|
||||
|
||||
GHashTable *checkbox_to_signal_ids;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoAddRemoveTrackPopover, koto_add_remove_track_popover, GTK_TYPE_POPOVER);
|
||||
|
||||
KotoAddRemoveTrackPopover *koto_add_remove_track_popup = NULL;
|
||||
|
||||
static void koto_add_remove_track_popover_class_init(KotoAddRemoveTrackPopoverClass *c) {
|
||||
(void) c;
|
||||
}
|
||||
|
||||
static void koto_add_remove_track_popover_init(KotoAddRemoveTrackPopover *self) {
|
||||
self->list_box = gtk_list_box_new(); // Create our new GtkListBox
|
||||
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list_box), GTK_SELECTION_NONE);
|
||||
|
||||
self->checkbox_to_playlist_uuid = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
self->playlist_uuid_to_checkbox = g_hash_table_new(g_str_hash, g_str_equal);
|
||||
self->checkbox_to_signal_ids = g_hash_table_new(g_str_hash, g_str_equal),
|
||||
self->tracks = NULL; // Initialize our tracks
|
||||
|
||||
gtk_popover_set_autohide(GTK_POPOVER(self), TRUE); // Ensure autohide is enabled
|
||||
gtk_popover_set_child(GTK_POPOVER(self), self->list_box);
|
||||
|
||||
g_signal_connect(koto_maps, "playlist-added", G_CALLBACK(koto_add_remove_track_popover_handle_playlist_added), self);
|
||||
g_signal_connect(koto_maps, "playlist-removed", G_CALLBACK(koto_add_remove_track_popover_handle_playlist_removed), self);
|
||||
}
|
||||
|
||||
void koto_add_remove_track_popover_add_playlist(KotoAddRemoveTrackPopover *self, KotoPlaylist *playlist) {
|
||||
if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist
|
||||
return;
|
||||
}
|
||||
|
||||
gchar *playlist_uuid = koto_playlist_get_uuid(playlist); // Get the UUID of the playlist
|
||||
|
||||
if (GTK_IS_CHECK_BUTTON(g_hash_table_lookup(self->playlist_uuid_to_checkbox, playlist_uuid))) { // Already have a check button for this
|
||||
g_free(playlist_uuid);
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget *playlist_button = gtk_check_button_new_with_label(koto_playlist_get_name(playlist)); // Create our GtkCheckButton
|
||||
g_hash_table_insert(self->checkbox_to_playlist_uuid, playlist_button, playlist_uuid);
|
||||
g_hash_table_insert(self->playlist_uuid_to_checkbox, playlist_uuid, playlist_button);
|
||||
|
||||
gulong playlist_sig_id = g_signal_connect(playlist_button, "toggled", G_CALLBACK(koto_add_remove_track_popover_handle_checkbutton_toggle), self);
|
||||
g_hash_table_insert(self->checkbox_to_signal_ids, playlist_button, GUINT_TO_POINTER(playlist_sig_id)); // Add our GSignal handler ID
|
||||
|
||||
gtk_list_box_append(GTK_LIST_BOX(self->list_box), playlist_button); // Add the playlist to the list box
|
||||
}
|
||||
|
||||
void koto_add_remove_track_popover_clear_tracks(KotoAddRemoveTrackPopover *self) {
|
||||
if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->tracks != NULL) { // Is a list
|
||||
g_list_free(self->tracks);
|
||||
self->tracks = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void koto_add_remove_track_popover_remove_playlist(KotoAddRemoveTrackPopover *self, gchar *playlist_uuid) {
|
||||
if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_hash_table_contains(self->playlist_uuid_to_checkbox, playlist_uuid)) { // Playlist not added
|
||||
return;
|
||||
}
|
||||
|
||||
GtkCheckButton *btn = GTK_CHECK_BUTTON(g_hash_table_lookup(self->playlist_uuid_to_checkbox, playlist_uuid)); // Get the check button
|
||||
|
||||
if (GTK_IS_CHECK_BUTTON(btn)) { // Is a check button
|
||||
g_hash_table_remove(self->checkbox_to_playlist_uuid, btn); // Remove uuid based on btn
|
||||
gtk_list_box_remove(GTK_LIST_BOX(self->list_box), GTK_WIDGET(btn)); // Remove the button from the list box
|
||||
}
|
||||
|
||||
g_hash_table_remove(self->playlist_uuid_to_checkbox, playlist_uuid);
|
||||
}
|
||||
|
||||
void koto_add_remove_track_popover_handle_checkbutton_toggle(GtkCheckButton *btn, gpointer user_data) {
|
||||
KotoAddRemoveTrackPopover *self = user_data;
|
||||
|
||||
if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gboolean should_add = gtk_check_button_get_active(btn); // Get the now active state
|
||||
gchar *playlist_uuid = g_hash_table_lookup(self->checkbox_to_playlist_uuid, btn); // Get the playlist UUID for this button
|
||||
|
||||
KotoPlaylist *playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, playlist_uuid); // Get the playlist
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // Failed to get the playlist
|
||||
return;
|
||||
}
|
||||
|
||||
GList *pos;
|
||||
for (pos = self->tracks; pos != NULL; pos = pos->next) { // Iterate over our KotoIndexedTracks
|
||||
KotoIndexedTrack *track = pos->data;
|
||||
|
||||
if (!KOTO_INDEXED_TRACK(track)) { // Not a track
|
||||
continue; // Skip this
|
||||
}
|
||||
|
||||
gchar *track_uuid = koto_indexed_track_get_uuid(track); // Get the track
|
||||
|
||||
if (should_add) { // Should be adding
|
||||
koto_playlist_add_track_by_uuid(playlist, track_uuid, FALSE, TRUE); // Add the track to the playlist
|
||||
} else { // Should be removing
|
||||
koto_playlist_remove_track_by_uuid(playlist, track_uuid); // Remove the track from the playlist
|
||||
}
|
||||
}
|
||||
|
||||
gtk_popover_popdown(GTK_POPOVER(self)); // Temporary to hopefully prevent a bork
|
||||
}
|
||||
|
||||
void koto_add_remove_track_popover_handle_playlist_added(KotoCartographer *carto, KotoPlaylist *playlist, gpointer user_data) {
|
||||
(void) carto;
|
||||
KotoAddRemoveTrackPopover *self = user_data;
|
||||
|
||||
if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
koto_add_remove_track_popover_add_playlist(self, playlist);
|
||||
}
|
||||
|
||||
void koto_add_remove_track_popover_handle_playlist_removed(KotoCartographer *carto, gchar *playlist_uuid, gpointer user_data) {
|
||||
(void) carto;
|
||||
KotoAddRemoveTrackPopover *self = user_data;
|
||||
|
||||
if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
koto_add_remove_track_popover_remove_playlist(self, playlist_uuid);
|
||||
}
|
||||
|
||||
void koto_add_remove_track_popover_set_pointing_to_widget(KotoAddRemoveTrackPopover *self, GtkWidget *widget, GtkPositionType pos) {
|
||||
if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GTK_IS_WIDGET(widget)) { // Not a widget
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget* existing_parent = gtk_widget_get_parent(GTK_WIDGET(self));
|
||||
|
||||
if (existing_parent != NULL) {
|
||||
g_object_ref(GTK_WIDGET(self)); // Increment widget ref since unparent will do an unref
|
||||
gtk_widget_unparent(GTK_WIDGET(self)); // Unparent the popup
|
||||
}
|
||||
|
||||
gtk_widget_insert_after(GTK_WIDGET(self), widget, gtk_widget_get_last_child(widget));
|
||||
gtk_popover_set_position(GTK_POPOVER(self), pos);
|
||||
}
|
||||
|
||||
void koto_add_remove_track_popover_set_tracks(KotoAddRemoveTrackPopover *self, GList *tracks) {
|
||||
if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gint tracks_len = g_list_length(tracks);
|
||||
|
||||
if (tracks_len == 0) { // No tracks
|
||||
return;
|
||||
}
|
||||
|
||||
self->tracks = g_list_copy(tracks);
|
||||
GHashTable *playlists = koto_cartographer_get_playlists(koto_maps); // Get our playlists
|
||||
GHashTableIter playlists_iter;
|
||||
gpointer uuid, playlist_ptr;
|
||||
|
||||
g_hash_table_iter_init(&playlists_iter, playlists); // Init our HashTable iterator
|
||||
|
||||
while (g_hash_table_iter_next(&playlists_iter, &uuid, &playlist_ptr)) { // While we are iterating through our playlists
|
||||
KotoPlaylist *playlist = playlist_ptr;
|
||||
gboolean should_be_checked = FALSE;
|
||||
|
||||
if (tracks_len > 1) { // More than one track
|
||||
GList *pos;
|
||||
for (pos = self->tracks; pos != NULL; pos = pos->next) { // Iterate over our tracks
|
||||
should_be_checked = (koto_playlist_get_position_of_track(playlist, pos->data) != -1);
|
||||
|
||||
if (!should_be_checked) { // Should not be checked
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
KotoIndexedTrack *track = g_list_nth_data(self->tracks, 0); // Get the first track
|
||||
|
||||
if (KOTO_IS_INDEXED_TRACK(track)) {
|
||||
gint pos = koto_playlist_get_position_of_track(playlist, track);
|
||||
should_be_checked = (pos != -1);
|
||||
}
|
||||
}
|
||||
|
||||
GtkCheckButton *playlist_check = g_hash_table_lookup(self->playlist_uuid_to_checkbox, uuid); // Get the GtkCheckButton for this playlist
|
||||
|
||||
if (GTK_IS_CHECK_BUTTON(playlist_check)) { // Is a checkbox
|
||||
gpointer sig_id_ptr = g_hash_table_lookup(self->checkbox_to_signal_ids, playlist_check);
|
||||
gulong check_button_sig_id = GPOINTER_TO_UINT(sig_id_ptr);
|
||||
g_signal_handler_block(playlist_check, check_button_sig_id); // Temporary ignore toggled signal, since set_active calls toggled
|
||||
gtk_check_button_set_active(playlist_check, should_be_checked); // Set active to our should_be_checked bool
|
||||
g_signal_handler_unblock(playlist_check, check_button_sig_id); // Unblock the signal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KotoAddRemoveTrackPopover* koto_add_remove_track_popover_new() {
|
||||
return g_object_new(KOTO_TYPE_ADD_REMOVE_TRACK_POPOVER, NULL);
|
||||
}
|
48
src/playlist/add-remove-track-popover.h
Normal file
48
src/playlist/add-remove-track-popover.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/* add-remove-track-popover.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 "../db/cartographer.h"
|
||||
#include "playlist.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Type Definition
|
||||
**/
|
||||
|
||||
#define KOTO_TYPE_ADD_REMOVE_TRACK_POPOVER koto_add_remove_track_popover_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoAddRemoveTrackPopover, koto_add_remove_track_popover, KOTO, ADD_REMOVE_TRACK_POPOVER, GtkPopover);
|
||||
#define KOTO_JS_ADD_REMOVE_TRACK_POPOVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ADD_REMOVE_TRACK_POPOVER))
|
||||
|
||||
/**
|
||||
* Functions
|
||||
**/
|
||||
|
||||
KotoAddRemoveTrackPopover* koto_add_remove_track_popover_new();
|
||||
void koto_add_remove_track_popover_add_playlist(KotoAddRemoveTrackPopover *self, KotoPlaylist *playlist);
|
||||
void koto_add_remove_track_popover_clear_tracks(KotoAddRemoveTrackPopover *self);
|
||||
void koto_add_remove_track_popover_remove_playlist(KotoAddRemoveTrackPopover *self, gchar *playlist_uuid);
|
||||
void koto_add_remove_track_popover_handle_checkbutton_toggle(GtkCheckButton *btn, gpointer user_data);
|
||||
void koto_add_remove_track_popover_handle_playlist_added(KotoCartographer *carto, KotoPlaylist *playlist, gpointer user_data);
|
||||
void koto_add_remove_track_popover_handle_playlist_removed(KotoCartographer *carto, gchar *playlist_uuid, gpointer user_data);
|
||||
void koto_add_remove_track_popover_set_pointing_to_widget(KotoAddRemoveTrackPopover *self, GtkWidget *widget, GtkPositionType pos);
|
||||
void koto_add_remove_track_popover_set_tracks(KotoAddRemoveTrackPopover *self, GList *tracks);
|
||||
|
||||
G_END_DECLS
|
|
@ -1,99 +0,0 @@
|
|||
/* create-dialog.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 <glib-2.0/glib-object.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "../db/db.h"
|
||||
#include "create-dialog.h"
|
||||
|
||||
extern GtkWindow *main_window;
|
||||
|
||||
struct _KotoCreatePlaylistDialog {
|
||||
GObject parent_instance;
|
||||
GtkWidget *content;
|
||||
GtkWidget *playlist_image;
|
||||
GtkFileChooserNative *playlist_file_chooser;
|
||||
GtkWidget *name_entry;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoCreatePlaylistDialog, koto_create_playlist_dialog, G_TYPE_OBJECT);
|
||||
|
||||
static void koto_create_playlist_dialog_class_init(KotoCreatePlaylistDialogClass *c) {
|
||||
(void) c;
|
||||
}
|
||||
|
||||
static void koto_create_playlist_dialog_init(KotoCreatePlaylistDialog *self) {
|
||||
self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_halign(self->content, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_valign(self->content, GTK_ALIGN_CENTER);
|
||||
|
||||
GtkIconTheme *default_icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); // Get the icon theme for this display
|
||||
|
||||
if (default_icon_theme != NULL) {
|
||||
gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self->content));
|
||||
GtkIconPaintable* audio_paintable = gtk_icon_theme_lookup_icon(default_icon_theme, "insert-image-symbolic", NULL, 96, scale_factor, GTK_TEXT_DIR_NONE, GTK_ICON_LOOKUP_PRELOAD);
|
||||
|
||||
if (GTK_IS_ICON_PAINTABLE(audio_paintable)) {
|
||||
self->playlist_image = gtk_image_new_from_paintable(GDK_PAINTABLE(audio_paintable));
|
||||
gtk_widget_set_size_request(self->playlist_image, 96, 96);
|
||||
gtk_box_append(GTK_BOX(self->content), self->playlist_image); // Add our image to our content
|
||||
|
||||
GtkGesture *image_click_controller = gtk_gesture_click_new(); // Create a click gesture for the image clicking
|
||||
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(image_click_controller), 1); // Only allow left click
|
||||
g_signal_connect(GTK_EVENT_CONTROLLER(image_click_controller), "pressed", G_CALLBACK(koto_create_playlist_dialog_handle_image_click), self);
|
||||
|
||||
gtk_widget_add_controller(self->playlist_image, GTK_EVENT_CONTROLLER(image_click_controller));
|
||||
}
|
||||
}
|
||||
|
||||
self->playlist_file_chooser = gtk_file_chooser_native_new(
|
||||
"Choose playlist image",
|
||||
main_window,
|
||||
GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
"Choose",
|
||||
"Cancel"
|
||||
);
|
||||
|
||||
GtkFileFilter *image_filter = gtk_file_filter_new(); // Create our file filter
|
||||
gtk_file_filter_add_pattern(image_filter, "image/*"); // Only allow for images
|
||||
gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(self->playlist_file_chooser), image_filter); // Only allow picking images
|
||||
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(self->playlist_file_chooser), FALSE);
|
||||
|
||||
self->name_entry = gtk_entry_new(); // Create our text entry for the name of the playlist
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), "Name of playlist");
|
||||
gtk_entry_set_input_purpose(GTK_ENTRY(self->name_entry), GTK_INPUT_PURPOSE_NAME);
|
||||
gtk_entry_set_input_hints(GTK_ENTRY(self->name_entry), GTK_INPUT_HINT_SPELLCHECK & GTK_INPUT_HINT_NO_EMOJI & GTK_INPUT_HINT_PRIVATE);
|
||||
|
||||
gtk_box_append(GTK_BOX(self->content), self->name_entry);
|
||||
}
|
||||
|
||||
GtkWidget* koto_create_playlist_dialog_get_content(KotoCreatePlaylistDialog *self) {
|
||||
return self->content;
|
||||
}
|
||||
|
||||
void koto_create_playlist_dialog_handle_image_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
|
||||
KotoCreatePlaylistDialog *self = user_data;
|
||||
|
||||
gtk_native_dialog_show(GTK_NATIVE_DIALOG(self->playlist_file_chooser)); // Show our file chooser
|
||||
}
|
||||
|
||||
KotoCreatePlaylistDialog* koto_create_playlist_dialog_new() {
|
||||
return g_object_new(KOTO_TYPE_CREATE_PLAYLIST_DIALOG, NULL);
|
||||
}
|
301
src/playlist/create-modify-dialog.c
Normal file
301
src/playlist/create-modify-dialog.c
Normal file
|
@ -0,0 +1,301 @@
|
|||
/* create-modify-dialog.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-object.h>
|
||||
#include <gtk-4.0/gtk/gtk.h>
|
||||
#include <magic.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "../playlist/playlist.h"
|
||||
#include "../koto-utils.h"
|
||||
#include "../koto-window.h"
|
||||
#include "create-modify-dialog.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern KotoWindow *main_window;
|
||||
|
||||
enum {
|
||||
PROP_DIALOG_0,
|
||||
PROP_PLAYLIST_UUID,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
static GParamSpec *dialog_props[N_PROPS] = { NULL, };
|
||||
|
||||
|
||||
struct _KotoCreateModifyPlaylistDialog {
|
||||
GtkBox parent_instance;
|
||||
GtkWidget *playlist_image;
|
||||
GtkWidget *name_entry;
|
||||
|
||||
GtkWidget *create_button;
|
||||
|
||||
gchar *playlist_image_path;
|
||||
gchar *playlist_uuid;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoCreateModifyPlaylistDialog, koto_create_modify_playlist_dialog, GTK_TYPE_BOX);
|
||||
|
||||
KotoCreateModifyPlaylistDialog *playlist_create_modify_dialog;
|
||||
|
||||
static void koto_create_modify_playlist_dialog_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
|
||||
static void koto_create_modify_playlist_dialog_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
static void koto_create_modify_playlist_dialog_class_init(KotoCreateModifyPlaylistDialogClass *c) {
|
||||
GObjectClass *gobject_class;
|
||||
gobject_class = G_OBJECT_CLASS(c);
|
||||
gobject_class->set_property = koto_create_modify_playlist_dialog_set_property;
|
||||
gobject_class->get_property = koto_create_modify_playlist_dialog_get_property;
|
||||
|
||||
dialog_props[PROP_PLAYLIST_UUID] = g_param_spec_string(
|
||||
"playlist-uuid",
|
||||
"Playlist UUID",
|
||||
"Playlist UUID",
|
||||
NULL,
|
||||
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
|
||||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPS, dialog_props);
|
||||
}
|
||||
|
||||
static void koto_create_modify_playlist_dialog_init(KotoCreateModifyPlaylistDialog *self) {
|
||||
self->playlist_image_path = NULL;
|
||||
|
||||
gtk_widget_set_halign(GTK_WIDGET(self), GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_valign(GTK_WIDGET(self), GTK_ALIGN_CENTER);
|
||||
|
||||
self->playlist_image = gtk_image_new_from_icon_name("insert-image-symbolic");
|
||||
gtk_image_set_pixel_size(GTK_IMAGE(self->playlist_image), 220);
|
||||
gtk_widget_set_size_request(self->playlist_image, 220, 220);
|
||||
gtk_box_append(GTK_BOX(self), self->playlist_image); // Add our image
|
||||
|
||||
GtkDropTarget *target = gtk_drop_target_new(G_TYPE_FILE, GDK_ACTION_COPY);
|
||||
g_signal_connect(GTK_EVENT_CONTROLLER(target), "drop", G_CALLBACK(koto_create_modify_playlist_dialog_handle_drop), self);
|
||||
gtk_widget_add_controller(self->playlist_image, GTK_EVENT_CONTROLLER(target));
|
||||
|
||||
GtkGesture *image_click_controller = gtk_gesture_click_new(); // Create a click gesture for the image clicking
|
||||
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(image_click_controller), 1); // Only allow left click
|
||||
g_signal_connect(GTK_EVENT_CONTROLLER(image_click_controller), "pressed", G_CALLBACK(koto_create_modify_playlist_dialog_handle_image_click), self);
|
||||
|
||||
gtk_widget_add_controller(self->playlist_image, GTK_EVENT_CONTROLLER(image_click_controller));
|
||||
|
||||
self->name_entry = gtk_entry_new(); // Create our text entry for the name of the playlist
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), "Name of playlist");
|
||||
gtk_entry_set_input_purpose(GTK_ENTRY(self->name_entry), GTK_INPUT_PURPOSE_NAME);
|
||||
gtk_entry_set_input_hints(GTK_ENTRY(self->name_entry), GTK_INPUT_HINT_SPELLCHECK & GTK_INPUT_HINT_NO_EMOJI & GTK_INPUT_HINT_PRIVATE);
|
||||
|
||||
gtk_box_append(GTK_BOX(self), self->name_entry);
|
||||
|
||||
self->create_button = gtk_button_new_with_label("Create");
|
||||
gtk_widget_add_css_class(self->create_button, "suggested-action");
|
||||
g_signal_connect(self->create_button, "clicked", G_CALLBACK(koto_create_modify_playlist_dialog_handle_create_click), self);
|
||||
gtk_box_append(GTK_BOX(self), self->create_button); // Add the create button
|
||||
}
|
||||
|
||||
static void koto_create_modify_playlist_dialog_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec){
|
||||
KotoCreateModifyPlaylistDialog *self = KOTO_CREATE_MODIFY_PLAYLIST_DIALOG(obj);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_PLAYLIST_UUID:
|
||||
g_value_set_string(val, (self->playlist_uuid != NULL) ? g_strdup(self->playlist_uuid) : NULL);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void koto_create_modify_playlist_dialog_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
|
||||
KotoCreateModifyPlaylistDialog *self = KOTO_CREATE_MODIFY_PLAYLIST_DIALOG(obj);
|
||||
(void) self; (void) val;
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_PLAYLIST_UUID:
|
||||
// TODO: Implement
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void koto_create_modify_playlist_dialog_handle_chooser_response(GtkNativeDialog *native, int response, gpointer user_data) {
|
||||
if (response != GTK_RESPONSE_ACCEPT) { // Not accept
|
||||
g_object_unref(native);
|
||||
return;
|
||||
}
|
||||
|
||||
KotoCreateModifyPlaylistDialog *self = user_data;
|
||||
if (!KOTO_IS_CURRENT_MODIFY_PLAYLIST(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(native));
|
||||
gchar *file_path = g_file_get_path(file); // Get the absolute path
|
||||
|
||||
if (file_path != NULL) {
|
||||
self->playlist_image_path = g_strdup(file_path);
|
||||
gtk_image_set_from_file(GTK_IMAGE(self->playlist_image), self->playlist_image_path); // Set the file path
|
||||
g_free(file_path);
|
||||
}
|
||||
|
||||
g_object_unref(file);
|
||||
g_object_unref(native);
|
||||
}
|
||||
|
||||
void koto_create_modify_playlist_dialog_handle_create_click(GtkButton *button, gpointer user_data) {
|
||||
(void) button;
|
||||
|
||||
KotoCreateModifyPlaylistDialog *self = user_data;
|
||||
|
||||
if (!KOTO_IS_CURRENT_MODIFY_PLAYLIST(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gtk_entry_get_text_length(GTK_ENTRY(self->name_entry)) == 0) { // No text
|
||||
gtk_widget_grab_focus(GTK_WIDGET(self->name_entry)); // Select the name entry
|
||||
return;
|
||||
}
|
||||
|
||||
KotoPlaylist *playlist = NULL;
|
||||
gboolean modify_existing_playlist = ((self->playlist_uuid != NULL) && (g_strcmp0(self->playlist_uuid, "") != 0));
|
||||
|
||||
if (modify_existing_playlist) { // Modifying an existing playlist
|
||||
playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->playlist_uuid);
|
||||
} else { // Creating a new playlist
|
||||
playlist = koto_playlist_new(); // Create a new playlist with a new UUID
|
||||
}
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) { // If this isn't a playlist
|
||||
return;
|
||||
}
|
||||
|
||||
koto_playlist_set_name(playlist, gtk_entry_buffer_get_text(gtk_entry_get_buffer(GTK_ENTRY(self->name_entry)))); // Set the name to the text we get from the entry buffer
|
||||
koto_playlist_set_artwork(playlist, self->playlist_image_path); // Add the playlist path if any
|
||||
koto_playlist_commit(playlist); // Save the playlist to the database
|
||||
|
||||
if (!modify_existing_playlist) { // Not a new playlist
|
||||
koto_cartographer_add_playlist(koto_maps, playlist); // Add to cartographer
|
||||
koto_playlist_mark_as_finalized(playlist); // Ensure our tracks loaded finalized signal is emitted for the new playlist
|
||||
}
|
||||
|
||||
koto_create_modify_playlist_dialog_reset(self);
|
||||
koto_window_hide_dialogs(main_window); // Hide the dialogs
|
||||
}
|
||||
|
||||
gboolean koto_create_modify_playlist_dialog_handle_drop(GtkDropTarget *target, const GValue *val, double x, double y, gpointer user_data) {
|
||||
(void) target; (void) x; (void) y;
|
||||
|
||||
if (!G_VALUE_HOLDS(val, G_TYPE_FILE)) { // Not a file
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
KotoCreateModifyPlaylistDialog *self = user_data;
|
||||
|
||||
if (!KOTO_IS_CURRENT_MODIFY_PLAYLIST(self)) { // No dialog
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
GFile *dropped_file = g_value_get_object(val); // Get the GValue
|
||||
gchar *file_path = g_file_get_path(dropped_file); // Get the absolute path
|
||||
g_object_unref(dropped_file); // Unref the file
|
||||
|
||||
if (file_path == NULL) {
|
||||
return FALSE; // Failed to get the path so immediately return false
|
||||
}
|
||||
|
||||
magic_t magic_cookie = magic_open(MAGIC_MIME);
|
||||
|
||||
if (magic_cookie == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (magic_load(magic_cookie, NULL) != 0) {
|
||||
goto cookie_closure;
|
||||
}
|
||||
|
||||
const char *mime_type = magic_file(magic_cookie, file_path);
|
||||
|
||||
if ((mime_type != NULL) && g_str_has_prefix(mime_type, "image/")) { // Is an image
|
||||
self->playlist_image_path = g_strdup(file_path);
|
||||
gtk_image_set_from_file(GTK_IMAGE(self->playlist_image), self->playlist_image_path); // Set the file path
|
||||
g_free(file_path);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
cookie_closure:
|
||||
magic_close(magic_cookie);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void koto_create_modify_playlist_dialog_handle_image_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data) {
|
||||
(void) gesture; (void) n_press; (void) x; (void) y;
|
||||
|
||||
KotoCreateModifyPlaylistDialog *self = user_data;
|
||||
|
||||
GtkFileChooserNative* chooser = koto_utils_create_image_file_chooser("Choose playlist image");
|
||||
g_signal_connect(chooser, "response", G_CALLBACK(koto_create_modify_playlist_dialog_handle_chooser_response), self);
|
||||
gtk_native_dialog_show(GTK_NATIVE_DIALOG(chooser)); // Show our file chooser
|
||||
}
|
||||
|
||||
void koto_create_modify_playlist_dialog_reset(KotoCreateModifyPlaylistDialog *self) {
|
||||
if (!KOTO_IS_CURRENT_MODIFY_PLAYLIST(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gtk_entry_buffer_set_text(gtk_entry_get_buffer(GTK_ENTRY(self->name_entry)), "", -1); // Reset existing buffer to empty string
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), "Name of playlist"); // Reset placeholder
|
||||
gtk_image_set_from_icon_name(GTK_IMAGE(self->playlist_image), "insert-image-symbolic"); // Reset the image
|
||||
gtk_button_set_label(GTK_BUTTON(self->create_button), "Create");
|
||||
}
|
||||
|
||||
void koto_create_modify_playlist_dialog_set_playlist_uuid(KotoCreateModifyPlaylistDialog *self, gchar *playlist_uuid) {
|
||||
if ((playlist_uuid == NULL) || g_strcmp0(playlist_uuid, "") == 0) { // Is an empty string or not a string at all
|
||||
return;
|
||||
}
|
||||
|
||||
KotoPlaylist *playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, playlist_uuid);
|
||||
|
||||
if (!KOTO_IS_PLAYLIST(playlist)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->playlist_uuid = g_strdup(playlist_uuid);
|
||||
gtk_entry_buffer_set_text(gtk_entry_get_buffer(GTK_ENTRY(self->name_entry)), koto_playlist_get_name(playlist), -1); // Update the input buffer
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), ""); // Clear placeholder
|
||||
|
||||
gchar *art = koto_playlist_get_artwork(playlist);
|
||||
|
||||
if ((art == NULL) || (g_strcmp0(art, "") == 0)) { // Is an empty string or not set
|
||||
gtk_image_set_from_icon_name(GTK_IMAGE(self->playlist_image), "insert-image-symbolic"); // Reset the image
|
||||
} else {
|
||||
gtk_image_set_from_file(GTK_IMAGE(self->playlist_image), art);
|
||||
g_free(art);
|
||||
}
|
||||
|
||||
gtk_button_set_label(GTK_BUTTON(self->create_button), "Save");
|
||||
}
|
||||
|
||||
KotoCreateModifyPlaylistDialog* koto_create_modify_playlist_dialog_new(char *playlist_uuid) {
|
||||
(void) playlist_uuid;
|
||||
|
||||
return g_object_new(KOTO_TYPE_CREATE_MODIFY_PLAYLIST_DIALOG,
|
||||
"orientation",
|
||||
GTK_ORIENTATION_VERTICAL,
|
||||
"spacing",
|
||||
40,
|
||||
NULL);
|
||||
}
|
44
src/playlist/create-modify-dialog.h
Normal file
44
src/playlist/create-modify-dialog.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* create-modify-dialog.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>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Type Definition
|
||||
**/
|
||||
|
||||
#define KOTO_TYPE_CREATE_MODIFY_PLAYLIST_DIALOG koto_create_modify_playlist_dialog_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoCreateModifyPlaylistDialog, koto_create_modify_playlist_dialog, KOTO, CREATE_MODIFY_PLAYLIST_DIALOG, GtkBox);
|
||||
#define KOTO_IS_CURRENT_MODIFY_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_CREATE_MODIFY_PLAYLIST_DIALOG))
|
||||
|
||||
/**
|
||||
* Functions
|
||||
**/
|
||||
|
||||
KotoCreateModifyPlaylistDialog* koto_create_modify_playlist_dialog_new();
|
||||
void koto_create_modify_playlist_dialog_handle_chooser_response(GtkNativeDialog *native, int response, gpointer user_data);
|
||||
void koto_create_modify_playlist_dialog_handle_create_click(GtkButton *button, gpointer user_data);
|
||||
gboolean koto_create_modify_playlist_dialog_handle_drop(GtkDropTarget *target, const GValue *val, double x, double y, gpointer user_data);
|
||||
void koto_create_modify_playlist_dialog_handle_image_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data);
|
||||
void koto_create_modify_playlist_dialog_reset(KotoCreateModifyPlaylistDialog *self);
|
||||
void koto_create_modify_playlist_dialog_set_playlist_uuid(KotoCreateModifyPlaylistDialog *self, gchar *playlist_uuid);
|
||||
|
||||
G_END_DECLS
|
|
@ -112,6 +112,8 @@ void koto_current_playlist_set_playlist(KotoCurrentPlaylist *self, KotoPlaylist
|
|||
}
|
||||
|
||||
self->current_playlist = playlist;
|
||||
// TODO: Saved state
|
||||
koto_playlist_set_position(self->current_playlist, -1); // Reset our position, use -1 since "next" song is then 0
|
||||
g_object_ref(playlist); // Increment the reference
|
||||
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CURRENT_PLAYLIST]);
|
||||
}
|
||||
|
|
|
@ -16,29 +16,16 @@
|
|||
*/
|
||||
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <glib-2.0/gio/gio.h>
|
||||
#include <magic.h>
|
||||
#include <sqlite3.h>
|
||||
#include "../db/cartographer.h"
|
||||
#include "../koto-utils.h"
|
||||
#include "playlist.h"
|
||||
|
||||
extern KotoCartographer *koto_maps;
|
||||
extern sqlite3 *koto_db;
|
||||
|
||||
struct _KotoPlaylist {
|
||||
GObject parent_instance;
|
||||
gchar *uuid;
|
||||
gchar *name;
|
||||
gchar *art_path;
|
||||
gint current_position;
|
||||
gboolean ephemeral;
|
||||
gboolean is_shuffle_enabled;
|
||||
|
||||
GQueue *tracks;
|
||||
GQueue *played_tracks;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoPlaylist, koto_playlist, G_TYPE_OBJECT);
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_UUID,
|
||||
|
@ -49,7 +36,47 @@ enum {
|
|||
N_PROPERTIES,
|
||||
};
|
||||
|
||||
enum {
|
||||
SIGNAL_TRACK_ADDED,
|
||||
SIGNAL_TRACK_LOAD_FINALIZED,
|
||||
SIGNAL_TRACK_REMOVED,
|
||||
N_SIGNALS
|
||||
};
|
||||
|
||||
struct _KotoPlaylist {
|
||||
GObject parent_instance;
|
||||
gchar *uuid;
|
||||
gchar *name;
|
||||
gchar *art_path;
|
||||
gint current_position;
|
||||
gchar *current_uuid;
|
||||
|
||||
KotoPreferredModelType model;
|
||||
|
||||
gboolean ephemeral;
|
||||
gboolean is_shuffle_enabled;
|
||||
gboolean finalized;
|
||||
|
||||
GListStore *store;
|
||||
GQueue *sorted_tracks;
|
||||
|
||||
GQueue *tracks; // This is effectively our vanilla value that should never change
|
||||
GQueue *played_tracks;
|
||||
};
|
||||
|
||||
struct _KotoPlaylistClass {
|
||||
GObjectClass parent_class;
|
||||
|
||||
void (* track_added) (KotoPlaylist *playlist, gchar *track_uuid);
|
||||
void (* track_load_finalized) (KotoPlaylist *playlist);
|
||||
void (* track_removed) (KotoPlaylist *playlist, gchar *track_uuid);
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(KotoPlaylist, koto_playlist, G_TYPE_OBJECT);
|
||||
|
||||
static GParamSpec *props[N_PROPERTIES] = { NULL };
|
||||
static guint playlist_signals[N_SIGNALS] = { 0 };
|
||||
|
||||
static void koto_playlist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);;
|
||||
static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
|
||||
|
||||
|
@ -100,6 +127,44 @@ static void koto_playlist_class_init(KotoPlaylistClass *c) {
|
|||
);
|
||||
|
||||
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
|
||||
|
||||
playlist_signals[SIGNAL_TRACK_ADDED] = g_signal_new(
|
||||
"track-added",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoPlaylistClass, track_added),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_CHAR
|
||||
);
|
||||
|
||||
playlist_signals[SIGNAL_TRACK_LOAD_FINALIZED] = g_signal_new(
|
||||
"track-load-finalized",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoPlaylistClass, track_load_finalized),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0
|
||||
);
|
||||
|
||||
playlist_signals[SIGNAL_TRACK_REMOVED] = g_signal_new(
|
||||
"track-removed",
|
||||
G_TYPE_FROM_CLASS(gobject_class),
|
||||
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
|
||||
G_STRUCT_OFFSET(KotoPlaylistClass, track_removed),
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_CHAR
|
||||
);
|
||||
}
|
||||
|
||||
static void koto_playlist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
|
||||
|
@ -154,9 +219,16 @@ static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue
|
|||
|
||||
static void koto_playlist_init(KotoPlaylist *self) {
|
||||
self->current_position = -1; // Default to -1 so first time incrementing puts it at 0
|
||||
self->current_uuid = NULL;
|
||||
self->model = KOTO_PREFERRED_MODEL_TYPE_DEFAULT; // Default to default model
|
||||
self->is_shuffle_enabled = FALSE;
|
||||
self->played_tracks = g_queue_new(); // Set as an empty GQueue
|
||||
self->ephemeral = FALSE;
|
||||
self->finalized = FALSE;
|
||||
|
||||
self->tracks = g_queue_new(); // Set as an empty GQueue
|
||||
self->played_tracks = g_queue_new(); // Set as an empty GQueue
|
||||
self->sorted_tracks = g_queue_new(); // Set as an empty GQueue
|
||||
self->store = g_list_store_new(KOTO_TYPE_INDEXED_TRACK);
|
||||
}
|
||||
|
||||
void koto_playlist_add_to_played_tracks(KotoPlaylist *self, gchar *uuid) {
|
||||
|
@ -167,17 +239,62 @@ void koto_playlist_add_to_played_tracks(KotoPlaylist *self, gchar *uuid) {
|
|||
g_queue_push_tail(self->played_tracks, uuid); // Add to end
|
||||
}
|
||||
|
||||
void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track) {
|
||||
gchar *uuid = NULL;
|
||||
g_object_get(track, "uuid", &uuid, NULL); // Get the UUID
|
||||
koto_playlist_add_track_by_uuid(self, uuid); // Add by the file's UUID
|
||||
void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track, gboolean current, gboolean commit_to_table) {
|
||||
koto_playlist_add_track_by_uuid(self, koto_indexed_track_get_uuid(track), current, commit_to_table);
|
||||
}
|
||||
|
||||
void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid) {
|
||||
gchar *dup_uuid = g_strdup(uuid);
|
||||
void koto_playlist_add_track_by_uuid(KotoPlaylist *self, gchar *uuid, gboolean current, gboolean commit_to_table) {
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, uuid); // Get the track
|
||||
|
||||
g_queue_push_tail(self->tracks, dup_uuid); // Append the UUID to the tracks
|
||||
// TODO: Add to table
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GList *found_tracks_uuids = g_queue_find_custom(self->tracks, uuid, koto_playlist_compare_track_uuids);
|
||||
if (found_tracks_uuids != NULL) { // Is somewhere in the tracks already
|
||||
g_list_free(found_tracks_uuids);
|
||||
return;
|
||||
}
|
||||
|
||||
g_list_free(found_tracks_uuids);
|
||||
|
||||
g_queue_push_tail(self->tracks, uuid); // Prepend the UUID to the tracks
|
||||
g_queue_push_tail(self->sorted_tracks, uuid); // Also add to our sorted tracks
|
||||
g_list_store_append(self->store, track); // Add to the store
|
||||
|
||||
if (self->finalized) { // Is already finalized
|
||||
koto_playlist_apply_model(self, self->model); // Re-apply our current model to ensure our "sorted tracks" is sorted, as is our GLIstStore used in the playlist page
|
||||
}
|
||||
|
||||
if (commit_to_table) {
|
||||
koto_indexed_track_save_to_playlist(track, self->uuid, (g_strcmp0(self->current_uuid, uuid) == 0) ? 1 : 0); // Call to save the playlist to the track
|
||||
}
|
||||
|
||||
if (current && (g_queue_get_length(self->tracks) > 1)) { // Is current and NOT the first item
|
||||
self->current_uuid = uuid; // Mark this as current UUID
|
||||
}
|
||||
|
||||
g_signal_emit(
|
||||
self,
|
||||
playlist_signals[SIGNAL_TRACK_ADDED],
|
||||
0,
|
||||
uuid
|
||||
);
|
||||
}
|
||||
|
||||
void koto_playlist_apply_model(KotoPlaylist *self, KotoPreferredModelType preferred_model) {
|
||||
GList *sort_user_data = NULL;
|
||||
sort_user_data = g_list_prepend(sort_user_data, GUINT_TO_POINTER(preferred_model)); // Prepend our preferred model first
|
||||
sort_user_data = g_list_prepend(sort_user_data, self); // Prepend ourself
|
||||
|
||||
g_queue_sort(self->sorted_tracks, koto_playlist_model_sort_by_uuid, sort_user_data); // Sort tracks, which is by UUID
|
||||
g_list_store_sort(self->store, koto_playlist_model_sort_by_track, sort_user_data); // Sort tracks by indexed tracks
|
||||
|
||||
self->model = preferred_model; // Update our preferred model
|
||||
|
||||
/*if (self->current_position != -1) { // Have a position set
|
||||
koto_playlist_set_track_as_current(self, self->current_uuid); // Update the position based on the new model just by setting it as current again
|
||||
}*/
|
||||
}
|
||||
|
||||
void koto_playlist_commit(KotoPlaylist *self) {
|
||||
|
@ -186,8 +303,8 @@ void koto_playlist_commit(KotoPlaylist *self) {
|
|||
}
|
||||
|
||||
gchar *commit_op = g_strdup_printf(
|
||||
"INSERT INTO playlist_meta(id, name, art_path)"
|
||||
"VALUES('%s', quote(\"%s\"), quote(\"%s\")"
|
||||
"INSERT INTO playlist_meta(id, name, art_path, preferred_model)"
|
||||
"VALUES('%s', quote(\"%s\"), quote(\"%s\"), 0)"
|
||||
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
|
||||
self->uuid,
|
||||
self->name,
|
||||
|
@ -215,22 +332,30 @@ void koto_playlist_commit_tracks(gpointer data, gpointer user_data) {
|
|||
gchar *playlist_uuid = self->uuid; // Get the playlist UUID
|
||||
|
||||
gchar *current_track = g_queue_peek_nth(self->tracks, self->current_position); // Get the UUID of the current track
|
||||
koto_indexed_track_save_to_playlist(track, playlist_uuid, g_queue_index(self->tracks, data), (data == current_track) ? 1 : 0); // Call to save the playlist to the track
|
||||
//koto_indexed_track_save_to_playlist(track, playlist_uuid, (data == current_track) ? 1 : 0); // Call to save the playlist to the track
|
||||
g_free(playlist_uuid);
|
||||
g_free(current_track);
|
||||
}
|
||||
}
|
||||
|
||||
gint koto_playlist_compare_track_uuids(gconstpointer a, gconstpointer b) {
|
||||
return g_strcmp0(a, b);
|
||||
}
|
||||
|
||||
gchar* koto_playlist_get_artwork(KotoPlaylist *self) {
|
||||
return g_strdup(self->art_path); // Return a duplicate of our art path
|
||||
return (self->art_path == NULL) ? NULL : g_strdup(self->art_path); // Return a duplicate of our art path
|
||||
}
|
||||
|
||||
KotoPreferredModelType koto_playlist_get_current_model(KotoPlaylist *self) {
|
||||
return self->model;
|
||||
}
|
||||
|
||||
guint koto_playlist_get_current_position(KotoPlaylist *self) {
|
||||
return self->current_position;
|
||||
}
|
||||
|
||||
gchar* koto_playlist_get_current_uuid(KotoPlaylist *self) {
|
||||
return g_queue_peek_nth(self->tracks, self->current_position);
|
||||
gboolean koto_playlist_get_is_finalized(KotoPlaylist *self) {
|
||||
return self->finalized;
|
||||
}
|
||||
|
||||
guint koto_playlist_get_length(KotoPlaylist *self) {
|
||||
|
@ -241,12 +366,35 @@ gchar* koto_playlist_get_name(KotoPlaylist *self) {
|
|||
return (self->name == NULL) ? NULL : g_strdup(self->name);
|
||||
}
|
||||
|
||||
gint koto_playlist_get_position_of_track(KotoPlaylist *self, KotoIndexedTrack *track) {
|
||||
if (!KOTO_IS_PLAYLIST(self)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!G_IS_LIST_STORE(self->store)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
gint position = -1;
|
||||
guint found_pos = 0;
|
||||
|
||||
if (g_list_store_find(self->store , track, &found_pos)) { // Found the item
|
||||
position = (gint) found_pos; // Cast our found position from guint to gint
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
gchar* koto_playlist_get_random_track(KotoPlaylist *self) {
|
||||
gchar *track_uuid = NULL;
|
||||
guint tracks_len = g_queue_get_length(self->tracks);
|
||||
guint tracks_len = g_queue_get_length(self->sorted_tracks);
|
||||
|
||||
if (tracks_len == g_queue_get_length(self->played_tracks)) { // Played all tracks
|
||||
track_uuid = g_list_nth_data(self->tracks->head, 0); // Get the first
|
||||
track_uuid = g_list_nth_data(self->sorted_tracks->head, 0); // Get the first
|
||||
g_queue_clear(self->played_tracks); // Clear our played tracks
|
||||
} else { // Have not played all tracks
|
||||
GRand* rando_calrissian = g_rand_new(); // Create a new RNG
|
||||
|
@ -255,7 +403,7 @@ gchar* koto_playlist_get_random_track(KotoPlaylist *self) {
|
|||
while (track_uuid == NULL) { // Haven't selected a track yet
|
||||
attempt++;
|
||||
gint32 selected_item = g_rand_int_range(rando_calrissian, 0, (gint32) tracks_len);
|
||||
gchar *selected_track = g_queue_peek_nth(self->tracks, (guint) selected_item); // Get the UUID of the selected item
|
||||
gchar *selected_track = g_queue_peek_nth(self->sorted_tracks, (guint) selected_item); // Get the UUID of the selected item
|
||||
|
||||
if (g_queue_index(self->played_tracks, selected_track) == -1) { // Haven't played the track
|
||||
self->current_position = (gint) selected_item;
|
||||
|
@ -274,6 +422,10 @@ gchar* koto_playlist_get_random_track(KotoPlaylist *self) {
|
|||
return track_uuid;
|
||||
}
|
||||
|
||||
GListStore* koto_playlist_get_store(KotoPlaylist *self) {
|
||||
return self->store;
|
||||
}
|
||||
|
||||
GQueue* koto_playlist_get_tracks(KotoPlaylist *self) {
|
||||
return self->tracks;
|
||||
}
|
||||
|
@ -283,22 +435,38 @@ gchar* koto_playlist_get_uuid(KotoPlaylist *self) {
|
|||
}
|
||||
|
||||
gchar* koto_playlist_go_to_next(KotoPlaylist *self) {
|
||||
if (!KOTO_IS_PLAYLIST(self)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (self->is_shuffle_enabled) { // Shuffling enabled
|
||||
gchar *random_track_uuid = koto_playlist_get_random_track(self); // Get a random track
|
||||
koto_playlist_add_to_played_tracks(self, random_track_uuid);
|
||||
return random_track_uuid;
|
||||
}
|
||||
|
||||
gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID
|
||||
if (self->current_uuid == NULL || (g_strcmp0(self->current_uuid, "") == 0)) {
|
||||
self->current_position++;
|
||||
} else { // Have a UUID currently
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid);
|
||||
|
||||
if (current_uuid == self->tracks->tail->data) { // Current UUID matches the last item in the playlist
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->current_position++; // Increment our position
|
||||
current_uuid = koto_playlist_get_current_uuid(self); // Return the new UUID
|
||||
koto_playlist_add_to_played_tracks(self, current_uuid);
|
||||
return current_uuid;
|
||||
gint pos_of_song = koto_playlist_get_position_of_track(self, track); // Get the position of the current track based on the current model
|
||||
|
||||
if ((guint) pos_of_song == (g_queue_get_length(self->sorted_tracks) - 1)) { // At end
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->current_position = pos_of_song+1; // Increment our position based on position of song
|
||||
}
|
||||
|
||||
self->current_uuid = g_queue_peek_nth(self->sorted_tracks, self->current_position);
|
||||
koto_playlist_add_to_played_tracks(self, self->current_uuid);
|
||||
|
||||
return self->current_uuid;
|
||||
}
|
||||
|
||||
gchar* koto_playlist_go_to_previous(KotoPlaylist *self) {
|
||||
|
@ -306,45 +474,217 @@ gchar* koto_playlist_go_to_previous(KotoPlaylist *self) {
|
|||
return koto_playlist_get_random_track(self); // Get a random track
|
||||
}
|
||||
|
||||
gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID
|
||||
|
||||
if (current_uuid == self->tracks->head->data) { // Current UUID matches the first item in the playlist
|
||||
if (self->current_uuid == NULL || (g_strcmp0(self->current_uuid, "") == 0)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->current_position--; // Decrement our position
|
||||
return koto_playlist_get_current_uuid(self); // Return the new UUID
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid);
|
||||
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gint pos_of_song = koto_playlist_get_position_of_track(self, track); // Get the position of the current track based on the current model
|
||||
|
||||
if (pos_of_song == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->current_position = pos_of_song - 1; // Decrement our position based on position of song
|
||||
self->current_uuid = g_queue_peek_nth(self->sorted_tracks, self->current_position);
|
||||
|
||||
return self->current_uuid;
|
||||
}
|
||||
|
||||
void koto_playlist_mark_as_finalized(KotoPlaylist *self) {
|
||||
if (self->finalized) { // Already finalized
|
||||
return;
|
||||
}
|
||||
|
||||
self->finalized = TRUE;
|
||||
koto_playlist_apply_model(self, self->model); // Re-apply our model to enforce mass sort
|
||||
|
||||
g_signal_emit(
|
||||
self,
|
||||
playlist_signals[SIGNAL_TRACK_LOAD_FINALIZED],
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
gint koto_playlist_model_sort_by_uuid(gconstpointer first_item, gconstpointer second_item, gpointer data_list) {
|
||||
KotoIndexedTrack *first_track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) first_item);
|
||||
KotoIndexedTrack *second_track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) second_item);
|
||||
|
||||
return koto_playlist_model_sort_by_track(first_track, second_track, data_list);
|
||||
}
|
||||
|
||||
gint koto_playlist_model_sort_by_track(gconstpointer first_item, gconstpointer second_item, gpointer data_list) {
|
||||
KotoIndexedTrack *first_track = (KotoIndexedTrack*) first_item;
|
||||
KotoIndexedTrack *second_track = (KotoIndexedTrack*) second_item;
|
||||
|
||||
GList* ptr_list = data_list;
|
||||
KotoPlaylist *self = g_list_nth_data(ptr_list, 0); // First item in the GPtrArray is a pointer to our playlist
|
||||
KotoPreferredModelType model = GPOINTER_TO_UINT(g_list_nth_data(ptr_list, 1)); // Second item in the GPtrArray is a pointer to our KotoPreferredModelType
|
||||
|
||||
if (
|
||||
(model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) || // Newest first model
|
||||
(model == KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST) // Oldest first
|
||||
) {
|
||||
gint first_track_pos = g_queue_index(self->tracks, koto_indexed_track_get_uuid(first_track));
|
||||
gint second_track_pos = g_queue_index(self->tracks, koto_indexed_track_get_uuid(second_track));
|
||||
|
||||
if (first_track_pos == -1) { // First track isn't in tracks
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (second_track_pos == -1) { // Second track isn't in tracks
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (model == KOTO_PREFERRED_MODEL_TYPE_DEFAULT) { // Newest first
|
||||
return (first_track_pos < second_track_pos) ? 1 : -1; // Display first at end, not beginning
|
||||
} else {
|
||||
return (first_track_pos < second_track_pos) ? -1 : 1; // Display at beginning, not end
|
||||
}
|
||||
}
|
||||
|
||||
if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM) { // Sort by album name
|
||||
gchar *first_album_uuid = NULL;
|
||||
gchar *second_album_uuid = NULL;
|
||||
|
||||
g_object_get(
|
||||
first_track,
|
||||
"album-uuid",
|
||||
&first_album_uuid,
|
||||
NULL
|
||||
);
|
||||
|
||||
g_object_get(
|
||||
second_track,
|
||||
"album-uuid",
|
||||
&second_album_uuid,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (g_strcmp0(first_album_uuid, second_album_uuid) == 0) { // Identical albums
|
||||
g_free(first_album_uuid);
|
||||
g_free(second_album_uuid);
|
||||
return 0; // Don't get too granular, just consider them equal
|
||||
}
|
||||
|
||||
KotoIndexedAlbum *first_album = koto_cartographer_get_album_by_uuid(koto_maps, first_album_uuid);
|
||||
KotoIndexedAlbum *second_album = koto_cartographer_get_album_by_uuid(koto_maps, second_album_uuid);
|
||||
|
||||
g_free(first_album_uuid);
|
||||
g_free(second_album_uuid);
|
||||
|
||||
if (!KOTO_IS_INDEXED_ALBUM(first_album) && !KOTO_IS_INDEXED_ALBUM(second_album)) { // Neither are valid albums
|
||||
return 0; // Just consider them as equal
|
||||
}
|
||||
|
||||
return g_utf8_collate(koto_indexed_album_get_album_name(first_album), koto_indexed_album_get_album_name(second_album));
|
||||
}
|
||||
|
||||
if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST) { // Sort by artist name
|
||||
gchar *first_artist_uuid = NULL;
|
||||
gchar *second_artist_uuid = NULL;
|
||||
|
||||
g_object_get(
|
||||
first_track,
|
||||
"artist-uuid",
|
||||
&first_artist_uuid,
|
||||
NULL
|
||||
);
|
||||
|
||||
g_object_get(
|
||||
second_track,
|
||||
"artist-uuid",
|
||||
&second_artist_uuid,
|
||||
NULL
|
||||
);
|
||||
|
||||
KotoIndexedArtist *first_artist = koto_cartographer_get_artist_by_uuid(koto_maps, first_artist_uuid);
|
||||
KotoIndexedArtist *second_artist = koto_cartographer_get_artist_by_uuid(koto_maps, second_artist_uuid);
|
||||
|
||||
g_free(first_artist_uuid);
|
||||
g_free(second_artist_uuid);
|
||||
|
||||
if (!KOTO_IS_INDEXED_ARTIST(first_artist) && !KOTO_IS_INDEXED_ARTIST(second_artist)) { // Neither are valid artists
|
||||
return 0; // Just consider them as equal
|
||||
}
|
||||
|
||||
return g_utf8_collate(koto_indexed_artist_get_name(first_artist), koto_indexed_artist_get_name(second_artist));
|
||||
}
|
||||
|
||||
if (model == KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME) { // Track name
|
||||
gchar *first_track_name = NULL;
|
||||
gchar *second_track_name = NULL;
|
||||
|
||||
g_object_get(
|
||||
first_track,
|
||||
"parsed-name",
|
||||
&first_track_name,
|
||||
NULL
|
||||
);
|
||||
|
||||
g_object_get(
|
||||
second_track,
|
||||
"parsed-name",
|
||||
&second_track_name,
|
||||
NULL
|
||||
);
|
||||
|
||||
gint ret = g_utf8_collate(first_track_name, second_track_name);
|
||||
g_free(first_track_name);
|
||||
g_free(second_track_name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void koto_playlist_remove_from_played_tracks(KotoPlaylist *self, gchar *uuid) {
|
||||
g_queue_remove(self->played_tracks, uuid);
|
||||
}
|
||||
|
||||
void koto_playlist_remove_track(KotoPlaylist *self, KotoIndexedTrack *track) {
|
||||
gchar *track_uuid = NULL;
|
||||
g_object_get(track, "uuid", &track_uuid, NULL);
|
||||
|
||||
if (track_uuid != NULL) {
|
||||
koto_playlist_remove_track_by_uuid(self, track_uuid);
|
||||
g_free(track_uuid);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void koto_playlist_remove_track_by_uuid(KotoPlaylist *self, gchar *uuid) {
|
||||
if (uuid == NULL) {
|
||||
if (!KOTO_IS_PLAYLIST(self)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gint file_index = g_queue_index(self->tracks, uuid); // Get the position of this uuid
|
||||
|
||||
if (file_index == -1) { // Does not exist in our tracks list
|
||||
if (file_index != -1) { // Have in tracks
|
||||
g_queue_pop_nth(self->tracks, file_index); // Remove nth where it is the file index
|
||||
}
|
||||
|
||||
gint file_index_in_sorted = g_queue_index(self->sorted_tracks, uuid); // Get position in sorted tracks
|
||||
|
||||
if (file_index_in_sorted != -1) { // Have in sorted tracks
|
||||
g_queue_pop_nth(self->sorted_tracks, file_index_in_sorted); // Remove nth where it is the index in sorted tracks
|
||||
}
|
||||
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, uuid); // Get the track
|
||||
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) { // Is not a track
|
||||
return;
|
||||
}
|
||||
|
||||
g_queue_pop_nth(self->tracks, file_index); // Remove nth where it is the file index
|
||||
return;
|
||||
guint position = 0;
|
||||
|
||||
if (g_list_store_find(self->store, track, &position)) { // Got the position
|
||||
g_list_store_remove(self->store, position); // Remove from the store
|
||||
}
|
||||
|
||||
koto_indexed_track_remove_from_playlist(track, self->uuid);
|
||||
|
||||
g_signal_emit(
|
||||
self,
|
||||
playlist_signals[SIGNAL_TRACK_REMOVED],
|
||||
0,
|
||||
uuid
|
||||
);
|
||||
}
|
||||
|
||||
void koto_playlist_set_artwork(KotoPlaylist *self, const gchar *path) {
|
||||
|
@ -376,11 +716,10 @@ void koto_playlist_set_artwork(KotoPlaylist *self, const gchar *path) {
|
|||
|
||||
free_cookie:
|
||||
magic_close(cookie); // Close and free the cookie to the cookie monster
|
||||
return;
|
||||
}
|
||||
|
||||
void koto_playlist_set_name(KotoPlaylist *self, const gchar *name) {
|
||||
if (name == NULL) { // No actual name
|
||||
if (name == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -389,7 +728,16 @@ void koto_playlist_set_name(KotoPlaylist *self, const gchar *name) {
|
|||
}
|
||||
|
||||
self->name = g_strdup(name);
|
||||
return;
|
||||
}
|
||||
|
||||
void koto_playlist_set_position(KotoPlaylist *self, gint position) {
|
||||
self->current_position = position;
|
||||
}
|
||||
|
||||
void koto_playlist_set_track_as_current(KotoPlaylist *self, gchar *track_uuid) {
|
||||
gint position_of_track = g_queue_index(self->sorted_tracks, track_uuid); // Get the position of the UUID in our tracks
|
||||
g_return_if_fail(position_of_track != -1);
|
||||
self->current_position = position_of_track;
|
||||
}
|
||||
|
||||
void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid) {
|
||||
|
@ -402,9 +750,19 @@ void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid) {
|
|||
}
|
||||
|
||||
self->uuid = g_strdup(uuid); // Set the new UUID
|
||||
}
|
||||
|
||||
void koto_playlist_tracks_queue_push_to_store(gpointer data, gpointer user_data) {
|
||||
gchar *track_uuid = (gchar *) data;
|
||||
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid);
|
||||
|
||||
if (!KOTO_IS_INDEXED_TRACK(track)) { // Not a track
|
||||
return;
|
||||
}
|
||||
|
||||
g_list_store_append(G_LIST_STORE(user_data), track);
|
||||
}
|
||||
|
||||
void koto_playlist_unmap(KotoPlaylist *self) {
|
||||
koto_cartographer_remove_playlist_by_uuid(koto_maps, self->uuid); // Remove from our cartographer
|
||||
}
|
||||
|
|
|
@ -16,19 +16,34 @@
|
|||
*/
|
||||
|
||||
#pragma once
|
||||
#include <glib-2.0/glib-object.h>
|
||||
#include <glib-2.0/glib.h>
|
||||
#include <glib-2.0/gio/gio.h>
|
||||
#include "../indexer/structs.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef enum {
|
||||
KOTO_PREFERRED_MODEL_TYPE_DEFAULT, // Considered to be newest first
|
||||
KOTO_PREFERRED_MODEL_TYPE_OLDEST_FIRST,
|
||||
KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ALBUM,
|
||||
KOTO_PREFERRED_MODEL_TYPE_SORT_BY_ARTIST,
|
||||
KOTO_PREFERRED_MODEL_TYPE_SORT_BY_TRACK_NAME
|
||||
} KotoPreferredModelType;
|
||||
|
||||
/**
|
||||
* Type Definition
|
||||
**/
|
||||
|
||||
#define KOTO_TYPE_PLAYLIST koto_playlist_get_type()
|
||||
G_DECLARE_FINAL_TYPE(KotoPlaylist, koto_playlist, KOTO, PLAYLIST, GObject);
|
||||
#define KOTO_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), KOTO_TYPE_PLAYLIST, KotoPlaylist))
|
||||
#define KOTO_IS_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYLIST))
|
||||
|
||||
typedef struct _KotoPlaylist KotoPlaylist;
|
||||
typedef struct _KotoPlaylistClass KotoPlaylistClass;
|
||||
|
||||
GLIB_AVAILABLE_IN_ALL
|
||||
GType koto_playlist_get_type(void) G_GNUC_CONST;
|
||||
|
||||
/**
|
||||
* Playlist Functions
|
||||
**/
|
||||
|
@ -36,27 +51,36 @@ G_DECLARE_FINAL_TYPE(KotoPlaylist, koto_playlist, KOTO, PLAYLIST, GObject);
|
|||
KotoPlaylist* koto_playlist_new();
|
||||
KotoPlaylist* koto_playlist_new_with_uuid(const gchar *uuid);
|
||||
void koto_playlist_add_to_played_tracks(KotoPlaylist *self, gchar *uuid);
|
||||
void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track);
|
||||
void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid);
|
||||
void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track, gboolean current, gboolean commit_to_table);
|
||||
void koto_playlist_add_track_by_uuid(KotoPlaylist *self, gchar *uuid, gboolean current, gboolean commit_to_table);
|
||||
void koto_playlist_apply_model(KotoPlaylist *self, KotoPreferredModelType preferred_model);
|
||||
void koto_playlist_commit(KotoPlaylist *self);
|
||||
void koto_playlist_commit_tracks(gpointer data, gpointer user_data);
|
||||
gint koto_playlist_compare_track_uuids(gconstpointer a, gconstpointer b);
|
||||
gchar* koto_playlist_get_artwork(KotoPlaylist *self);
|
||||
KotoPreferredModelType koto_playlist_get_current_model(KotoPlaylist *self);
|
||||
guint koto_playlist_get_current_position(KotoPlaylist *self);
|
||||
gchar* koto_playlist_get_current_uuid(KotoPlaylist *self);
|
||||
guint koto_playlist_get_length(KotoPlaylist *self);
|
||||
gboolean koto_playlist_get_is_finalized(KotoPlaylist *self);
|
||||
gchar* koto_playlist_get_name(KotoPlaylist *self);
|
||||
gint koto_playlist_get_position_of_track(KotoPlaylist *self, KotoIndexedTrack *track);
|
||||
GListStore* koto_playlist_get_store(KotoPlaylist *self);
|
||||
GQueue* koto_playlist_get_tracks(KotoPlaylist *self);
|
||||
gchar* koto_playlist_get_uuid(KotoPlaylist *self);
|
||||
gchar* koto_playlist_go_to_next(KotoPlaylist *self);
|
||||
gchar* koto_playlist_go_to_previous(KotoPlaylist *self);
|
||||
void koto_playlist_mark_as_finalized(KotoPlaylist *self);
|
||||
gint koto_playlist_model_sort_by_uuid(gconstpointer first_item, gconstpointer second_item, gpointer data_list);
|
||||
gint koto_playlist_model_sort_by_track(gconstpointer first_item, gconstpointer second_item, gpointer model_ptr);
|
||||
void koto_playlist_remove_from_played_tracks(KotoPlaylist *self, gchar *uuid);
|
||||
void koto_playlist_remove_track(KotoPlaylist *self, KotoIndexedTrack *track);
|
||||
void koto_playlist_remove_track_by_uuid(KotoPlaylist *self, gchar *uuid);
|
||||
void koto_playlist_set_artwork(KotoPlaylist *self, const gchar *path);
|
||||
void koto_playlist_save_state(KotoPlaylist *self);
|
||||
void koto_playlist_set_name(KotoPlaylist *self, const gchar *name);
|
||||
void koto_playlist_set_position(KotoPlaylist *self, guint pos);
|
||||
void koto_playlist_set_position(KotoPlaylist *self, gint position);
|
||||
void koto_playlist_set_track_as_current(KotoPlaylist *self, gchar *track_uuid);
|
||||
void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid);
|
||||
void koto_playlist_tracks_queue_push_to_store(gpointer data, gpointer user_data);
|
||||
void koto_playlist_unmap(KotoPlaylist *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "vars";
|
||||
|
||||
.koto-button {
|
||||
& > image {
|
||||
margin-right: 10px;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
& .track-list {
|
||||
& > row {
|
||||
&:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides
|
||||
&:nth-child(odd):not(:hover) {
|
||||
background-color: $midnight;
|
||||
}
|
||||
|
@ -18,3 +19,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,4 +14,32 @@
|
|||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.playerbar-info { // Central info section
|
||||
& > box { // Info labels
|
||||
margin-left: 2ex;
|
||||
|
||||
& > label {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
&:nth-child(1) { // Title
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:not(:nth-child(1)) { // Album and Artist
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
&:nth-child(2) { // Album
|
||||
|
||||
}
|
||||
|
||||
&:nth-child(3) { // Artist
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ $grey: #2e2e2e;
|
|||
$midnight: #1d1d1d;
|
||||
$darkgrey: #666666;
|
||||
$green: #60E078;
|
||||
$palewhite: #cccccc;
|
||||
$red : #FF4652;
|
||||
|
||||
$itempadding: 40px;
|
||||
$halvedpadding: $itempadding / 2;
|
||||
|
|
5
theme/components/_cover-art-button.scss
Normal file
5
theme/components/_cover-art-button.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
.cover-art-button {
|
||||
& > revealer > box { // Inner controls
|
||||
background-color: rgba(0,0,0,0.75);
|
||||
}
|
||||
}
|
63
theme/components/_gtk-overrides.scss
Normal file
63
theme/components/_gtk-overrides.scss
Normal file
|
@ -0,0 +1,63 @@
|
|||
@import '../vars';
|
||||
|
||||
@mixin selected-row-styling {
|
||||
color: $midnight;
|
||||
background-color: $green;
|
||||
border: 0; // Don't have a border
|
||||
border-image: none; // GTK uses an image which is weird
|
||||
border-image-width: 0;
|
||||
outline: none;
|
||||
outline-offset: 0;
|
||||
outline-style: none;
|
||||
}
|
||||
|
||||
button {
|
||||
&.destructive-action {
|
||||
background-color: $red;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
&.suggested-action { // Adwaita makes it blue but we want it green
|
||||
color: $midnight;
|
||||
background-color: $green;
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
listview {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
list:not(.discs-list), listview {
|
||||
&:not(.track-list) > row { // Rows which are now in the track list
|
||||
&:active, &:selected { // Active or selected
|
||||
@include selected-row-styling;
|
||||
}
|
||||
}
|
||||
|
||||
&.track-list > row {
|
||||
&:selected { // Only selected rows
|
||||
@include selected-row-styling;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
range {
|
||||
&.dragging { // Dragging a range
|
||||
& > trough {
|
||||
& > slider {
|
||||
background-color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scale { // Progress bar
|
||||
highlight {
|
||||
background-color: $green;
|
||||
}
|
||||
|
||||
slider { // Slider
|
||||
outline-color: $green;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
@import 'pages/music-local.scss';
|
||||
@import 'components/cover-art-button';
|
||||
@import 'components/gtk-overrides';
|
||||
@import 'pages/music-local';
|
||||
@import 'pages/playlist-page';
|
||||
|
||||
@import 'button';
|
||||
@import 'disc-view';
|
||||
|
@ -15,4 +18,15 @@ window {
|
|||
background-color: $midnight;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.koto-dialog-container {
|
||||
background-color: transparentize($midnight, 0.5);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
// All the classes we want consistent padding applied to for its primary content
|
||||
.artist-view-content, // Has the albums
|
||||
.playlist-page { // Individual playlists
|
||||
padding: $itempadding;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,10 @@ theme = custom_target('Theme generation',
|
|||
'@INPUT@', '@OUTPUT@',
|
||||
],
|
||||
depend_files: files([
|
||||
'components/_cover-art-button.scss',
|
||||
'components/_gtk-overrides.scss',
|
||||
'pages/_music-local.scss',
|
||||
'pages/_playlist-page.scss',
|
||||
'_button.scss',
|
||||
'_disc-view.scss',
|
||||
'_expander.scss',
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
& > stack {
|
||||
& > .artist-view {
|
||||
& > viewport > .artist-view-content {
|
||||
padding: $itempadding;
|
||||
|
||||
& > .album-strip {
|
||||
margin-bottom: $itempadding;
|
||||
& > flowboxchild {
|
||||
|
|
84
theme/pages/_playlist-page.scss
Normal file
84
theme/pages/_playlist-page.scss
Normal file
|
@ -0,0 +1,84 @@
|
|||
@import '../vars';
|
||||
|
||||
.playlist-page {
|
||||
.playlist-page-header { // Our header
|
||||
& > .playlist-page-header-info { // Our info centerbox
|
||||
margin-left: 40px;
|
||||
|
||||
& > label { // All labels
|
||||
color: $palewhite;
|
||||
}
|
||||
|
||||
& > label:nth-child(1) { // First item (type of playlist)
|
||||
font-size: 3ex;
|
||||
font-weight: 700;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
& > label:nth-child(2),
|
||||
& > label:nth-child(3) {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
& > label:nth-child(2) { // Second item (playlist name)
|
||||
font-size: 10ex;
|
||||
}
|
||||
|
||||
& > label:nth-child(3) { // Third item (number of tracks)
|
||||
font-size: 4ex;
|
||||
margin-top: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
& > .koto-button {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.track-list-content { // Our Track List
|
||||
& > .track-list-header,
|
||||
.track-list-columned-item {
|
||||
font-size: x-large;
|
||||
padding: 3ex 2ex;
|
||||
}
|
||||
|
||||
& > .track-list-header { // Headers
|
||||
font-weight: bold;
|
||||
|
||||
.koto-button { // All Koto buttons in our header
|
||||
&.active { // Is active
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .track-list-columned { // Column content
|
||||
& > row {
|
||||
& > .track-list-columned-item { // Track rows
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
&:nth-child(odd):not(:selected) {
|
||||
background-color: $midnight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.track-column-number { // Column section within header and track items
|
||||
|
||||
}
|
||||
|
||||
.track-column-name { // Name section within header and track items
|
||||
|
||||
}
|
||||
|
||||
.track-column-album { // Album section within headers and track items
|
||||
|
||||
}
|
||||
|
||||
.track-column-artist { // Artist section within headers and track items
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue