From f2164b2adeedaff727129f4539fb517e230c56e7 Mon Sep 17 00:00:00 2001 From: Joshua Strobl Date: Wed, 7 Apr 2021 13:17:33 +0300 Subject: [PATCH] Start work on playlist creation dialog Started work on the playlist creation dialog. To facilitate my desired UX for dialogs, I changed the immediate child of KotoWindow to being a GtkOverlay, with the "child" being the primary layout GtkBox and the "overlay" being any active dialog. There is no background on the dialogs yet, or really anything meaningful in it besides the GtkImage to show the playlist image as well as a GtkEntry for the label. Upon clicking the image, you will be presented with a GtkFileChooserNative, which will allow us to support various platforms aside from Linux (if we desire) but more importantly support desktop portals and the appropriate picker for each desktop environment. This dialog is currently hooked into the + button in the Playlist nav section. The intent is to also support drag-and-drop when possible, so you can drag an image file onto the GtkImage for it to set it to the playlist image. Fixed various compiler warnings, such as: - Unused variables (cast these as void so compiler knows to ignore them). - Use g_hash_table_add instead of g_hash_table_insert since value does not matter and will complain about not casting TRUE as a pointer. - Fixed some casting. Fixed up Desktop file and fleshed out Appstream data. Use validate-relax in the appstream test or it gets cranky about screenshots missing. I know...the app is not ready yet. Get over it AppStream. Added Visual Studio Code tasks and some C/C++ Extension setting files. --- .vscode/c_cpp_properties.json | 17 ++++ .vscode/settings.json | 5 + .vscode/tasks.json | 73 ++++++++++++++ .../com.github.joshstrobl.koto.appdata.xml.in | 27 ++++- data/com.github.joshstrobl.koto.desktop.in | 7 +- data/meson.build | 4 +- src/koto-nav.c | 14 +++ src/koto-nav.h | 2 + src/koto-playerbar.c | 6 +- src/koto-window.c | 19 +++- src/koto-window.h | 5 +- src/meson.build | 1 + src/playback/media-keys.c | 2 + src/playback/mimes.c | 2 +- src/playback/mpris.c | 3 +- src/playlist/create-dialog.c | 99 +++++++++++++++++++ src/playlist/create-dialog.h | 41 ++++++++ src/playlist/playlist.c | 4 +- 18 files changed, 316 insertions(+), 15 deletions(-) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 src/playlist/create-dialog.c create mode 100644 src/playlist/create-dialog.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..04ba0dd --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "/usr/include/**" + ], + "defines": [], + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu17", + "cppStandard": "c++20", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5d4fac9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "glib.h": "c" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..06c5717 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,73 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Clean builddir", + "type": "shell", + "command": "rm", + "args": [ + "-rf", + "builddir" + ], + "problemMatcher": [] + }, + { + "label": "Meson Configure", + "type": "shell", + "command": "meson", + "args": [ + "--prefix=/usr", + "--libdir=\"libdir\"", + "--sysconfdir=/etc", + "builddir" + ], + "problemMatcher": [] + }, + { + "label": "Meson Compile", + "type": "shell", + "command": "meson", + "args": [ + "compile", + "-C", + "builddir" + ], + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Meson Dist", + "type": "shell", + "command": "meson", + "args": [ + "dist", + "-C", + "builddir", + "--formats", + "xztar", + "--include-subprojects" + ], + "problemMatcher": [] + }, + { + "label": "Meson Install", + "type": "shell", + "command": "sudo", + "args": [ + "meson", + "install", + "-C", + "builddir", + "--destdir", + "/", + "--no-rebuild" + ], + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/data/com.github.joshstrobl.koto.appdata.xml.in b/data/com.github.joshstrobl.koto.appdata.xml.in index ce3a8a4..04164f4 100644 --- a/data/com.github.joshstrobl.koto.appdata.xml.in +++ b/data/com.github.joshstrobl.koto.appdata.xml.in @@ -2,7 +2,32 @@ com.github.joshstrobl.koto.desktop CC0-1.0 - + Apache-2.0 + Koto + Koto is an in-development audiobook, music, and podcast manager. +

Koto is an in-development audiobook, music, and podcast manager that is designed for and caters to a modern desktop Linux experience.

+ com.github.joshstrobl.koto.desktop + https://github.com/JoshStrobl/koto + https://github.com/JoshStrobl/koto/issues + https://patreon.com/joshuastrobl + https://liberapay.com/joshuastrobl + Joshua Strobl + joshua.strobl_AT_outlook.com + + mild + + + com.github.joshstrobl.koto + + + pointing + touch + 1600 + + + keyboard + 1366 +
diff --git a/data/com.github.joshstrobl.koto.desktop.in b/data/com.github.joshstrobl.koto.desktop.in index fb87fd6..eab43a2 100644 --- a/data/com.github.joshstrobl.koto.desktop.in +++ b/data/com.github.joshstrobl.koto.desktop.in @@ -1,7 +1,8 @@ [Desktop Entry] -Name=koto -Exec=koto +Name=Koto +Exec=com.github.joshstrobl.koto +Icon=audio-headphones Terminal=false Type=Application -Categories=GTK; +Categories=AudioVideo;Audio;GTK;Music;Player; StartupNotify=true diff --git a/data/meson.build b/data/meson.build index 3336028..99a118c 100644 --- a/data/meson.build +++ b/data/meson.build @@ -24,8 +24,8 @@ appstream_file = i18n.merge_file( appstream_util = find_program('appstream-util', required: false) if appstream_util.found() - test('Validate appstream file', appstream_util, - args: ['validate', appstream_file] + test('Validate appstream file (relaxed)', appstream_util, + args: ['validate-relax', appstream_file] ) endif diff --git a/src/koto-nav.c b/src/koto-nav.c index 16fc338..f40f2ff 100644 --- a/src/koto-nav.c +++ b/src/koto-nav.c @@ -20,6 +20,9 @@ #include "koto-button.h" #include "koto-expander.h" #include "koto-nav.h" +#include "koto-window.h" + +extern KotoWindow *main_window; struct _KotoNav { GObject parent_instance; @@ -93,6 +96,11 @@ static void koto_nav_init(KotoNav *self) { 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)); } void koto_nav_create_audiobooks_section(KotoNav *self) { @@ -145,6 +153,12 @@ void koto_nav_create_podcasts_section(KotoNav *self) { koto_expander_set_content(p_expander, new_content); } +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); +} + GtkWidget* koto_nav_get_nav(KotoNav *self) { return self->win; } diff --git a/src/koto-nav.h b/src/koto-nav.h index 2d14e51..2961fe5 100644 --- a/src/koto-nav.h +++ b/src/koto-nav.h @@ -28,6 +28,8 @@ 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_podcasts_section(KotoNav *self); +void koto_nav_handle_playlist_add_click(GtkGestureClick *gesture, int n_press, double x, double y, gpointer user_data); + GtkWidget* koto_nav_get_nav(KotoNav *self); G_END_DECLS diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c index 7cca72c..9a5a695 100644 --- a/src/koto-playerbar.c +++ b/src/koto-playerbar.c @@ -26,7 +26,7 @@ #include "koto-playerbar.h" extern KotoCurrentPlaylist *current_playlist; -extern KotoCartographer* koto_maps; +extern KotoCartographer *koto_maps; extern KotoPlaybackEngine *playback_engine; struct _KotoPlayerBar { @@ -91,8 +91,8 @@ static void koto_playerbar_constructed(GObject *obj) { GtkGesture *press_controller = gtk_gesture_click_new(); // Create a new GtkGestureLongPress gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(press_controller), 1); // Set to left click - g_signal_connect(GTK_GESTURE(press_controller), "begin", G_CALLBACK(koto_playerbar_handle_progressbar_gesture_begin), self); - g_signal_connect(GTK_GESTURE(press_controller), "end", G_CALLBACK(koto_playerbar_handle_progressbar_gesture_end), self); + 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); diff --git a/src/koto-window.c b/src/koto-window.c index e94e73c..939535b 100644 --- a/src/koto-window.c +++ b/src/koto-window.c @@ -20,6 +20,7 @@ #include "pages/music/music-local.h" #include "playback/engine.h" #include "playlist/current.h" +#include "playlist/create-dialog.h" #include "koto-config.h" #include "koto-nav.h" #include "koto-playerbar.h" @@ -33,6 +34,9 @@ struct _KotoWindow { KotoIndexedLibrary *library; KotoCurrentPlaylist *current_playlist; + KotoCreatePlaylistDialog *playlist_create_dialog; + + GtkWidget *overlay; GtkWidget *header_bar; GtkWidget *menu_button; GtkWidget *search_entry; @@ -61,11 +65,16 @@ 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->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); + gtk_overlay_set_child(GTK_OVERLAY(self->overlay), self->primary_layout); // Add our primary layout to the overlay + self->content_layout = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_add_css_class(self->content_layout, "content-layout"); gtk_widget_set_hexpand(self->content_layout, TRUE); @@ -95,7 +104,7 @@ static void koto_window_init (KotoWindow *self) { gtk_box_append(GTK_BOX(self->primary_layout), playerbar_main); } - gtk_window_set_child(GTK_WINDOW(self), self->primary_layout); + gtk_window_set_child(GTK_WINDOW(self), self->overlay); #ifdef GDK_WINDOWING_X11 set_optimal_default_window_size(self); #endif @@ -127,6 +136,14 @@ 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)); diff --git a/src/koto-window.h b/src/koto-window.h index 59f9077..3dbca29 100644 --- a/src/koto-window.h +++ b/src/koto-window.h @@ -25,8 +25,11 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (KotoWindow, koto_window, KOTO, WINDOW, GtkApplicationWindow) -G_END_DECLS +void koto_window_show_create_playlist_dialog(KotoWindow *self); +void koto_window_hide_create_playlist_dialog(KotoWindow *self); void create_new_headerbar(KotoWindow *self); void load_library(KotoWindow *self); void set_optimal_default_window_size(KotoWindow *self); + +G_END_DECLS diff --git a/src/meson.build b/src/meson.build index e7374b6..a6c9ac5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,6 +15,7 @@ koto_sources = [ 'playback/media-keys.c', 'playback/mimes.c', 'playback/mpris.c', + 'playlist/create-dialog.c', 'playlist/current.c', 'playlist/playlist.c', 'main.c', diff --git a/src/playback/media-keys.c b/src/playback/media-keys.c index 67fd835..28e099c 100644 --- a/src/playback/media-keys.c +++ b/src/playback/media-keys.c @@ -101,10 +101,12 @@ void handle_media_keys_signal(GDBusProxy *proxy, const gchar *sender_name, const } void handle_window_enter(GtkEventControllerFocus *controller, gpointer user_data) { + (void) controller; (void) user_data; grab_media_keys(); // Grab our media keys } void handle_window_leave(GtkEventControllerFocus *controller, gpointer user_data) { + (void) controller; (void) user_data; release_media_keys(); // Release our media keys } diff --git a/src/playback/mimes.c b/src/playback/mimes.c index 2cf8b32..6f1b44d 100644 --- a/src/playback/mimes.c +++ b/src/playback/mimes.c @@ -35,7 +35,7 @@ gboolean koto_playback_engine_gst_caps_iter(GstCapsFeatures *features, GstStruct return TRUE; } - g_hash_table_insert(supported_mimes_hash, g_strdup(caps_name), TRUE); + g_hash_table_add(supported_mimes_hash, g_strdup(caps_name)); supported_mimes = g_list_prepend(supported_mimes, g_strdup(caps_name)); supported_mimes = g_list_prepend(supported_mimes, g_strdup(koto_utils_replace_string_all(caps_name, "x-", ""))); diff --git a/src/playback/mpris.c b/src/playback/mpris.c index bf388a6..c168414 100644 --- a/src/playback/mpris.c +++ b/src/playback/mpris.c @@ -93,7 +93,7 @@ void handle_method_call( GDBusMethodInvocation *invocation, gpointer user_data ) { - (void) connection; (void) sender; (void) object_path; (void) invocation; (void) user_data; + (void) connection; (void) sender; (void) object_path; (void) parameters; (void) invocation; (void) user_data; if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2") == 0) { // Root mediaplayer2 interface if (g_strcmp0(method_name, "Raise") == 0) { // Raise the window @@ -386,6 +386,7 @@ static const GDBusInterfaceVTable main_mpris_interface_vtable = { handle_method_call, handle_get_property, handle_set_property, + { 0 } }; void on_main_mpris_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { diff --git a/src/playlist/create-dialog.c b/src/playlist/create-dialog.c new file mode 100644 index 0000000..6907a0a --- /dev/null +++ b/src/playlist/create-dialog.c @@ -0,0 +1,99 @@ +/* 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 +#include +#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); +} diff --git a/src/playlist/create-dialog.h b/src/playlist/create-dialog.h new file mode 100644 index 0000000..c210b7b --- /dev/null +++ b/src/playlist/create-dialog.h @@ -0,0 +1,41 @@ +/* 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. + */ + +#pragma once +#include +#include + +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); + +/** + * Create Dialog 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); + +G_END_DECLS diff --git a/src/playlist/playlist.c b/src/playlist/playlist.c index 3d85be0..164238e 100644 --- a/src/playlist/playlist.c +++ b/src/playlist/playlist.c @@ -254,11 +254,11 @@ 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); + 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 if (g_queue_index(self->played_tracks, selected_track) == -1) { // Haven't played the track - self->current_position = (int) selected_item; + self->current_position = (gint) selected_item; track_uuid = selected_track; break; } else { // Failed to get the track