diff --git a/build-aux/meson/postinstall.py b/build-aux/meson/postinstall.py new file mode 100755 index 0000000..6a3ea97 --- /dev/null +++ b/build-aux/meson/postinstall.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +from os import environ, path +from subprocess import call + +prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') +datadir = path.join(prefix, 'share') +destdir = environ.get('DESTDIR', '') + +# Package managers set this so we don't need to run +if not destdir: + print('Updating icon cache...') + call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')]) + + print('Updating desktop database...') + call(['update-desktop-database', '-q', path.join(datadir, 'applications')]) + + print('Compiling GSettings schemas...') + call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')]) + + diff --git a/com.github.joshstrobl.koto.json b/com.github.joshstrobl.koto.json new file mode 100644 index 0000000..587014a --- /dev/null +++ b/com.github.joshstrobl.koto.json @@ -0,0 +1,37 @@ +{ + "app-id" : "com.github.joshstrobl.koto", + "runtime" : "org.gnome.Platform", + "runtime-version" : "3.38", + "sdk" : "org.gnome.Sdk", + "command" : "com.github.joshstrobl.koto", + "finish-args" : [ + "--share=network", + "--share=ipc", + "--socket=fallback-x11", + "--socket=wayland" + ], + "cleanup" : [ + "/include", + "/lib/pkgconfig", + "/man", + "/share/doc", + "/share/gtk-doc", + "/share/man", + "/share/pkgconfig", + "*.la", + "*.a" + ], + "modules" : [ + { + "name" : "koto", + "builddir" : true, + "buildsystem" : "meson", + "sources" : [ + { + "type" : "git", + "url" : "file:///home/joshua/Code/Personal/Koto" + } + ] + } + ] +} diff --git a/data/com.github.joshstrobl.koto.appdata.xml.in b/data/com.github.joshstrobl.koto.appdata.xml.in new file mode 100644 index 0000000..ce3a8a4 --- /dev/null +++ b/data/com.github.joshstrobl.koto.appdata.xml.in @@ -0,0 +1,8 @@ + + + com.github.joshstrobl.koto.desktop + CC0-1.0 + + + + diff --git a/data/com.github.joshstrobl.koto.desktop.in b/data/com.github.joshstrobl.koto.desktop.in new file mode 100644 index 0000000..fb87fd6 --- /dev/null +++ b/data/com.github.joshstrobl.koto.desktop.in @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=koto +Exec=koto +Terminal=false +Type=Application +Categories=GTK; +StartupNotify=true diff --git a/data/com.github.joshstrobl.koto.gschema.xml b/data/com.github.joshstrobl.koto.gschema.xml new file mode 100644 index 0000000..7ff368a --- /dev/null +++ b/data/com.github.joshstrobl.koto.gschema.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..3336028 --- /dev/null +++ b/data/meson.build @@ -0,0 +1,41 @@ +desktop_file = i18n.merge_file( + input: 'com.github.joshstrobl.koto.desktop.in', + output: 'com.github.joshstrobl.koto.desktop', + type: 'desktop', + po_dir: '../po', + install: true, + install_dir: join_paths(get_option('datadir'), 'applications') +) + +desktop_utils = find_program('desktop-file-validate', required: false) +if desktop_utils.found() + test('Validate desktop file', desktop_utils, + args: [desktop_file] + ) +endif + +appstream_file = i18n.merge_file( + input: 'com.github.joshstrobl.koto.appdata.xml.in', + output: 'com.github.joshstrobl.koto.appdata.xml', + po_dir: '../po', + install: true, + install_dir: join_paths(get_option('datadir'), 'appdata') +) + +appstream_util = find_program('appstream-util', required: false) +if appstream_util.found() + test('Validate appstream file', appstream_util, + args: ['validate', appstream_file] + ) +endif + +install_data('com.github.joshstrobl.koto.gschema.xml', + install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') +) + +compile_schemas = find_program('glib-compile-schemas', required: false) +if compile_schemas.found() + test('Validate schema file', compile_schemas, + args: ['--strict', '--dry-run', meson.current_source_dir()] + ) +endif diff --git a/data/style.css b/data/style.css new file mode 100644 index 0000000..5329f46 --- /dev/null +++ b/data/style.css @@ -0,0 +1,20 @@ +.primary-nav > .frame > box >.koto-button { + padding: 10px 0; +} + +.primary-nav .expander .expander-header { + padding-bottom: 10px; +} + +.expander > revealer > box > .koto-button { + min-height: 40px; +} + +.koto-button > image { + margin-right: 10px; +} + +.primary-nav label, +.expander-header label { + font-size: large; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..5623ddf --- /dev/null +++ b/meson.build @@ -0,0 +1,27 @@ +project('koto', 'c', + version: '0.1.0', + meson_version: '>= 0.50.0', + default_options: [ 'warning_level=2', + 'c_std=gnu11', + ], +) + +i18n = import('i18n') + +config_h = configuration_data() +config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) +config_h.set_quoted('GETTEXT_PACKAGE', 'koto') +config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) +configure_file( + output: 'koto-config.h', + configuration: config_h, +) +add_project_arguments([ + '-I' + meson.build_root(), +], language: 'c') + +subdir('data') +subdir('src') +subdir('po') + +meson.add_install_script('build-aux/meson/postinstall.py') diff --git a/po/LINGUAS b/po/LINGUAS new file mode 100644 index 0000000..e69de29 diff --git a/po/POTFILES b/po/POTFILES new file mode 100644 index 0000000..2839995 --- /dev/null +++ b/po/POTFILES @@ -0,0 +1,7 @@ +data/com.github.joshstrobl.koto.desktop.in +data/com.github.joshstrobl.koto.appdata.xml.in +data/com.github.joshstrobl.koto.gschema.xml +src/koto-window.ui +src/main.c +src/koto-window.c + diff --git a/po/meson.build b/po/meson.build new file mode 100644 index 0000000..fe55b6a --- /dev/null +++ b/po/meson.build @@ -0,0 +1 @@ +i18n.gettext('koto', preset: 'glib') diff --git a/src/indexer/album.h b/src/indexer/album.h new file mode 100644 index 0000000..f9cc23b --- /dev/null +++ b/src/indexer/album.h @@ -0,0 +1,33 @@ +/* album.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 + +G_BEGIN_DECLS + +#define KOTO_INDEXED_ALBUM_TYPE koto_indexed_album_get_type() +G_DECLARE_FINAL_TYPE (KotoIndexedAlbum, koto_indexed_album, KOTO, INDEXED_ALBUM, GObject); + +KotoIndexedAlbum* koto_indexed_album_new(const gchar *path); + +void add_song(KotoIndexedAlbum *self, KotoIndexedFile *file); +KotoIndexedFile** get_songs(KotoIndexedAlbum *self); +void remove_song(KotoIndexedAlbum *self, KotoIndexedFile *file); +void remove_song_by_name(KotoIndexedAlbum *self, gchar *file_name); + +G_END_DECLS diff --git a/src/indexer/artist.h b/src/indexer/artist.h new file mode 100644 index 0000000..e87fa2f --- /dev/null +++ b/src/indexer/artist.h @@ -0,0 +1,33 @@ +/* artist.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 + +G_BEGIN_DECLS + +#define KOTO_INDEXED_ARTIST_TYPE koto_indexed_artist_get_type() +G_DECLARE_FINAL_TYPE (KotoIndexedArtist, koto_indexed_artist, KOTO, INDEXED_ARTIST, GObject); + +KotoIndexedArtist* koto_indexed_artist_new(const gchar *path); + +void add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); +KotoIndexedAlbum** get_albums(KotoIndexedArtist *self); +void remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); +void remove_album_by_name(KotoIndexedArtist *self, gchar *album_name); + +G_END_DECLS diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c new file mode 100644 index 0000000..a6588ab --- /dev/null +++ b/src/indexer/file-indexer.c @@ -0,0 +1,193 @@ +/* file-indexer.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "file-indexer.h" + +struct KotoIndexedFile { + GObject parent_instance; + gchar *file_name; + gchar *path; +}; + +struct KotoIndexedAlbum { + GObject parent_instance; + gboolean has_album_art; + gchar *album_name; + gchar *art_path; + gchar *path; + GHashTable *songs; +}; + +struct _KotoIndexedArtist { + GObject parent_instance; + gboolean has_artist_art; + gchar *artist_name; + GHashTable *albums; + gchar *path; +} + +struct _KotoIndexedLibrary { + GObject parent_instance; + gchar *path; + magic_t magic_cookie; + GHashTable *music_artists; +}; + +G_DEFINE_TYPE(KotoIndexedLibrary, koto_indexed_library, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_PATH, + N_PROPERTIES +}; + +static GParamSpec *props[N_PROPERTIES] = { NULL, }; + +static void koto_indexed_library_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec); +static void koto_indexed_library_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec); + +static void koto_indexed_library_class_init(KotoIndexedLibraryClass *c) { + GObjectClass *gobject_class; + gobject_class = G_OBJECT_CLASS(c); + gobject_class->set_property = koto_indexed_library_set_property; + gobject_class->get_property = koto_indexed_library_get_property; + + props[PROP_PATH] = g_param_spec_string( + "path", + "Path", + "Path to Music", + 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_indexed_library_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { + KotoIndexedLibrary *self = KOTO_INDEXED_LIBRARY(obj); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string(val, self->path); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +static void koto_indexed_library_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){ + KotoIndexedLibrary *self = KOTO_INDEXED_LIBRARY(obj); + + switch (prop_id) { + case PROP_PATH: + self->path = g_strdup(g_value_get_string(val)); + g_message("Set to %s", self->path); + start_indexing(self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +void start_indexing(KotoIndexedLibrary *self) { + struct stat library_stat; + int success = stat(self->path, &library_stat); + + if (success != 0) { // Failed to read the library path + return; + } + + if (!S_ISDIR(library_stat.st_mode)) { // Is not a directory + g_warning("%s is not a directory", self->path); + return; + } + + self->magic_cookie = magic_open(MAGIC_MIME); + + if (self->magic_cookie == NULL) { + return; + } + + if (magic_load(self->magic_cookie, NULL) != 0) { + magic_close(self->magic_cookie); + return; + } + + index_folder(self, self->path); + magic_close(self->magic_cookie); +} + +void index_folder(KotoIndexedLibrary *self, gchar *path) { + DIR *dir = opendir(path); // Attempt to open our directory + + if (dir == NULL) { + return; + } + + struct dirent *entry; + + while ((entry = readdir(dir))) { + if (!g_str_has_prefix(entry->d_name, ".")) { // Not a reference to parent dir, self, or a hidden item + gchar *full_path = g_strdup_printf("%s%s%s", path, G_DIR_SEPARATOR_S, entry->d_name); + + if (entry->d_type == DT_DIR) { // Directory + index_folder(self, full_path); // Index this directory + } else if (entry->d_type == DT_REG) { // Regular file + index_file(self, full_path); // Index the file + } + + g_free(full_path); + } + } + + closedir(dir); // Close the directory +} + +void index_file(KotoIndexedLibrary *self, gchar *path) { + const char *mime_type = magic_file(self->magic_cookie, path); + + if (mime_type == NULL) { // Failed to get the mimetype + return; + } + + gchar** mime_info = g_strsplit(mime_type, ";", 2); // Only care about our first item + + if ( + g_str_has_prefix(mime_info[0], "audio/") || // Is audio + g_str_has_prefix(mime_info[0], "image/") // Is image + ) { + g_message("File Name: %s", path); + } + + g_strfreev(mime_info); // Free our mimeinfo +} + +static void koto_indexed_library_init(KotoIndexedLibrary *self) { + self->files = g_hash_table_new(g_str_hash, g_str_equal); +} + +KotoIndexedLibrary* koto_indexed_library_new(const gchar *path) { + return g_object_new(KOTO_INDEXED_TYPE_LIBRARY, + "path", path, + NULL + ); +} diff --git a/src/indexer/file-indexer.h b/src/indexer/file-indexer.h new file mode 100644 index 0000000..f5f61f2 --- /dev/null +++ b/src/indexer/file-indexer.h @@ -0,0 +1,36 @@ +/* file-indexer.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 + +G_BEGIN_DECLS + +#define KOTO_INDEXED_TYPE_FILE koto_indexed_file_get_type() +G_DECLARE_FINAL_TYPE(KotoIndexedFile, koto_indexed_file, KOTO, INDEXED_FILE, GObject); + +#define KOTO_INDEXED_TYPE_LIBRARY koto_indexed_library_get_type() +G_DECLARE_FINAL_TYPE(KotoIndexedLibrary, koto_indexed_library, KOTO, INDEXED_LIBRARY, GObject); + +KotoIndexedLibrary* koto_indexed_library_new(const gchar *path); +KotoIndexedFile* koto_indexed_file_new(gchar *path); + +void start_indexing(KotoIndexedLibrary *self); +void index_folder(KotoIndexedLibrary *self, gchar *path); +void index_file(KotoIndexedLibrary *self, gchar *path); + +G_END_DECLS diff --git a/src/indexer/meson.build b/src/indexer/meson.build new file mode 100644 index 0000000..df7c618 --- /dev/null +++ b/src/indexer/meson.build @@ -0,0 +1,8 @@ +indexer_sources = [ + 'file-indexer.c', +] + +indexer_deps = [ + dependency('glib-2.0', version: '>= 2.66'), + dependency('gio-2.0', version: '>= 2.66'), +] diff --git a/src/koto-button.c b/src/koto-button.c new file mode 100644 index 0000000..7857fd6 --- /dev/null +++ b/src/koto-button.c @@ -0,0 +1,340 @@ +/* koto-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 +#include "koto-button.h" +#include "koto-config.h" + +struct _PixbufSize { + guint size; +}; + +typedef struct _PixbufSize PixbufSize; + +static PixbufSize *pixbuf_sizes = NULL; +static guint pixbuf_sizes_allocated = 0; + +static void init_pixbuf_sizes() { + if (pixbuf_sizes == NULL) { + pixbuf_sizes = g_new(PixbufSize, NUM_BUILTIN_SIZES); + + pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_INVALID].size = 0; + pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_TINY].size = 16; + pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_SMALL].size = 24; + pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_NORMAL].size = 32; + pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_LARGE].size = 64; + pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_MASSIVE].size = 96; + pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_GODLIKE].size = 128; + pixbuf_sizes_allocated = NUM_BUILTIN_SIZES; + } +} + +guint koto_get_pixbuf_size(KotoButtonPixbufSize s) { + init_pixbuf_sizes(); + + return pixbuf_sizes[s].size; +} + +enum { + PROP_0, + PROP_PIX_SIZE, + PROP_TEXT, + PROP_BADGE_TEXT, + PROP_PIX, + PROP_ICON_NAME, + N_PROPERTIES +}; + +static GParamSpec *props[N_PROPERTIES] = { NULL, }; + +struct _KotoButton { + GtkBox parent_instance; + guint pix_size; + + GtkWidget *button_image; + GtkWidget *badge_label; + GtkWidget *button_label; + + gchar *badge_text; + gchar *icon_name; + gchar *text; + + GdkPixbuf *pix; +}; + +struct _KotoButtonClass { + GtkBoxClass parent_class; +}; + +G_DEFINE_TYPE(KotoButton, koto_button, GTK_TYPE_BOX); + +static void koto_button_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec); +static void koto_button_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec); + +static void koto_button_class_init(KotoButtonClass *c) { + GObjectClass *gobject_class; + gobject_class = G_OBJECT_CLASS(c); + gobject_class->set_property = koto_button_set_property; + gobject_class->get_property = koto_button_get_property; + + props[PROP_PIX_SIZE] = g_param_spec_uint( + "pixbuf-size", + "Pixbuf Size", + "Size of the pixbuf", + koto_get_pixbuf_size(KOTO_BUTTON_PIXBUF_SIZE_TINY), + koto_get_pixbuf_size(KOTO_BUTTON_PIXBUF_SIZE_GODLIKE), + koto_get_pixbuf_size(KOTO_BUTTON_PIXBUF_SIZE_SMALL), + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_TEXT] = g_param_spec_string( + "button-text", + "Button Text", + "Text of Button", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_BADGE_TEXT] = g_param_spec_string( + "badge-text", + "Badge Text", + "Text of Badge", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_PIX] = g_param_spec_object( + "pixbuf", + "Pixbuf", + "Pixbuf", + GDK_TYPE_PIXBUF, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_ICON_NAME] = g_param_spec_string( + "icon-name", + "Icon Name", + "Name of Icon", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + g_object_class_install_properties(gobject_class, N_PROPERTIES, props); +} + +static void koto_button_init(KotoButton *self) { + GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(self)); + gtk_style_context_add_class(style, "koto-button"); +} + +static void koto_button_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { + KotoButton *self = KOTO_BUTTON(obj); + + switch (prop_id) { + case PROP_PIX_SIZE: + g_value_set_uint(val, self->pix_size); + break; + case PROP_TEXT: + g_value_set_string(val, self->text); + break; + case PROP_BADGE_TEXT: + g_value_set_string(val, self->badge_text); + break; + case PROP_ICON_NAME: + g_value_set_string(val, self->icon_name); + break; + case PROP_PIX: + g_value_set_object(val, self->pix); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +static void koto_button_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) { + KotoButton *self = KOTO_BUTTON(obj); + + switch (prop_id) { + case PROP_PIX_SIZE: + koto_button_set_pixbuf_size(self, g_value_get_uint(val)); + break; + case PROP_TEXT: + koto_button_set_text(self, g_strdup(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_PIX: + koto_button_set_pixbuf(self, (GdkPixbuf*) g_value_get_object(val)); + break; + case PROP_ICON_NAME: + koto_button_set_icon_name(self, g_strdup(g_value_get_string(val))); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +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(""); + } else { + g_free(self->badge_text); + self->badge_text = g_strdup(text); + } + + if (GTK_IS_LABEL(self->badge_label)) { // If badge label already exists + gtk_label_set_text(GTK_LABEL(self->badge_label), self->badge_text); + } else { + self->badge_label = gtk_label_new(self->badge_text); // Create our label + gtk_box_pack_end(GTK_BOX(self), self->badge_label, FALSE, FALSE, 0); // Add to the end of the box + } + + if (strcmp(self->badge_text, "") != 0) { // Empty badge + gtk_widget_hide(self->badge_label); // Hide our badge + } else { // Have some text + gtk_widget_show(self->badge_label); // Show our badge + } + + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_BADGE_TEXT]); +} + +void koto_button_set_icon_name(KotoButton *self, gchar *icon_name) { + gchar *copied_icon_name = g_strdup(icon_name); + g_message("icon name set in koto button: %s", icon_name); + g_free(self->icon_name); + self->icon_name = copied_icon_name; + + if ((self->icon_name == NULL) || (strcmp(self->icon_name,"") == 0)) { // Have no icon name now + g_message("Have no icon name now?"); + if (GTK_IS_IMAGE(self->button_image)) { // If we already have a button image + gtk_widget_hide(self->button_image); // Hide + } + + return; + } + + GtkIconTheme *theme = gtk_icon_theme_get_default(); // Get the default icon theme + GdkPixbuf* icon_pix = gtk_icon_theme_load_icon_for_scale(theme, self->icon_name, (gint) self->pix_size, 1, GTK_ICON_LOOKUP_USE_BUILTIN & GTK_ICON_LOOKUP_GENERIC_FALLBACK, NULL); + g_return_if_fail(GDK_IS_PIXBUF(icon_pix)); // Return if not a pixbuf + koto_button_set_pixbuf(self, icon_pix); + + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ICON_NAME]); +} + +void koto_button_set_pixbuf(KotoButton *self, GdkPixbuf *pix) { + g_return_if_fail(self->pix_size != 0); // Return if don't have a pixbuf size to indicate what to scale to + + if (!GDK_IS_PIXBUF(pix)) { + return; + } + + self->pix = pix; + + GdkPixbuf *use_pix = pix; // Default to using our provided pixbuf + if (gdk_pixbuf_get_height(pix) > (int) self->pix_size) { // If our height is greater than our desired pixel size + use_pix = gdk_pixbuf_scale_simple(pix, -1, self->pix_size, GDK_INTERP_BILINEAR); // Scale using bilnear + g_return_if_fail(use_pix != NULL); // Return if we could not allocate memory for the scaling + } + + if (GTK_IS_IMAGE(self->button_image)) { // If we already have a button image + gtk_image_set_from_pixbuf(GTK_IMAGE(self->button_image), use_pix); // Update our pixbuf + gtk_widget_show(self->button_image); // Ensure we show the image + } else { // No image set + GtkWidget *new_image = gtk_image_new_from_pixbuf(use_pix); // Create our new image + g_return_if_fail(GTK_IS_IMAGE(new_image)); // Return if we failed to create our image + self->button_image = new_image; + gtk_box_pack_start(GTK_BOX(self), self->button_image, FALSE, FALSE, 0); // Prepend the image + gtk_box_reorder_child(GTK_BOX(self), self->button_image, 0); + } + + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PIX]); +} + +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 + + self->pix_size = size; + + if (GDK_PIXBUF(self->pix)) { // If we already have a pixbuf set and we're changing the size + koto_button_set_pixbuf(self, self->pix); + } + + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PIX_SIZE]); +} + +void koto_button_set_text(KotoButton *self, gchar *text) { + gchar *copied_text = g_strdup(text); // Copy our text + + if (strcmp(copied_text, "") == 0) { // Clearing our text + g_free(self->text); // Free existing text + } + + self->text = copied_text; + + if (GTK_IS_LABEL(self->button_label)) { // If we have a button label + if (strcmp(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 + gtk_container_remove(GTK_CONTAINER(self), self->button_label); // Remove the label + g_free(self->button_label); + } + } else { // If we do not have a button label + if (strcmp(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); + gtk_box_pack_start(GTK_BOX(self), self->button_label, FALSE, FALSE, 0); // Add to the beginning of the box + + if (GTK_IS_IMAGE(self->button_image)) { // If we have an image + gtk_box_reorder_child(GTK_BOX(self), self->button_image, 0); // Move to the beginning + } + } + } + + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_TEXT]); +} + +KotoButton* koto_button_new_plain(gchar *label) { + return g_object_new(KOTO_TYPE_BUTTON, + "button-text", label, + "orientation", GTK_ORIENTATION_HORIZONTAL, + NULL + ); +} + +KotoButton* koto_button_new_with_icon(gchar *label, gchar *icon_name, KotoButtonPixbufSize size) { + return g_object_new(KOTO_TYPE_BUTTON, + "button-text", label, + "icon-name", icon_name, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "pixbuf-size", koto_get_pixbuf_size(size), + NULL + ); +} + +KotoButton* koto_button_new_with_pixbuf(gchar *label, GdkPixbuf *pix, KotoButtonPixbufSize size) { + return g_object_new(KOTO_TYPE_BUTTON, + "button-text", label, + "pixbuf", pix, + "orientation", GTK_ORIENTATION_HORIZONTAL, + "pixbuf-size", koto_get_pixbuf_size(size), + NULL + ); +} diff --git a/src/koto-button.h b/src/koto-button.h new file mode 100644 index 0000000..4f99950 --- /dev/null +++ b/src/koto-button.h @@ -0,0 +1,57 @@ +/* koto-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. + */ + +#pragma once +#ifndef __GTK_ENUMS_H__ +#define __GTK_ENUMS_H__ +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +typedef enum { + KOTO_BUTTON_PIXBUF_SIZE_INVALID, + KOTO_BUTTON_PIXBUF_SIZE_TINY, + KOTO_BUTTON_PIXBUF_SIZE_SMALL, + KOTO_BUTTON_PIXBUF_SIZE_NORMAL, + KOTO_BUTTON_PIXBUF_SIZE_LARGE, + KOTO_BUTTON_PIXBUF_SIZE_MASSIVE, + KOTO_BUTTON_PIXBUF_SIZE_GODLIKE +} KotoButtonPixbufSize; + +#define NUM_BUILTIN_SIZES 7 + +#define KOTO_TYPE_BUTTON (koto_button_get_type()) + +G_DECLARE_FINAL_TYPE (KotoButton, koto_button, KOTO, BUTTON, GtkBox) + +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, KotoButtonPixbufSize size); +KotoButton* koto_button_new_with_pixbuf(gchar *label, GdkPixbuf *pix, KotoButtonPixbufSize size); + +void koto_button_set_badge_text(KotoButton *self, gchar *text); +void koto_button_set_icon_name(KotoButton *self, gchar *icon_name); +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); + +G_END_DECLS diff --git a/src/koto-expander.c b/src/koto-expander.c new file mode 100644 index 0000000..3ecae3d --- /dev/null +++ b/src/koto-expander.c @@ -0,0 +1,245 @@ +/* koto-expander.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include "koto-config.h" +#include "koto-button.h" +#include "koto-flipper-button.h" +#include "koto-expander.h" +#include "koto-utils.h" + +enum { + PROP_0, + PROP_HEADER_ICON_NAME, + PROP_HEADER_LABEL, + PROP_HEADER_SECONDARY_BUTTON, + PROP_CONTENT, + N_PROPERTIES +}; + +static GParamSpec *props[N_PROPERTIES] = { NULL, }; + +struct _KotoExpander { + GtkBox parent_instance; + gboolean constructed; + GtkWidget *header; + KotoButton *header_button; + + gchar *icon_name; + gchar *label; + + GtkWidget *header_secondary_button; + KotoFlipperButton *header_expand_button; + + GtkWidget *revealer; + GtkWidget *content; +}; + +struct _KotoExpanderClass { + GtkBoxClass parent_class; +}; + +G_DEFINE_TYPE(KotoExpander, koto_expander, GTK_TYPE_BOX); + +static void koto_expander_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec); +static void koto_expander_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec); + +static void koto_expander_class_init(KotoExpanderClass *c) { + GObjectClass *gobject_class; + gobject_class = G_OBJECT_CLASS(c); + gobject_class->set_property = koto_expander_set_property; + gobject_class->get_property = koto_expander_get_property; + + props[PROP_HEADER_ICON_NAME] = g_param_spec_string( + "icon-name", + "Icon Name", + "Name of the icon to use in the Expander", + "emblem-favorite-symbolic", + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_HEADER_LABEL] = g_param_spec_string( + "label", + "Label", + "Label for the Expander", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_HEADER_SECONDARY_BUTTON] = g_param_spec_object( + "secondary-button", + "Secondary Button", + "Secondary Button to be placed next to Expander button", + GTK_TYPE_WIDGET, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + props[PROP_CONTENT] = g_param_spec_object( + "content", + "Content", + "Content inside the Expander", + GTK_TYPE_WIDGET, + G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE + ); + + g_object_class_install_properties(gobject_class, N_PROPERTIES, props); +} + +static void koto_expander_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { + KotoExpander *self = KOTO_EXPANDER(obj); + + switch (prop_id) { + case PROP_HEADER_ICON_NAME: + g_value_set_string(val, self->icon_name); + break; + case PROP_HEADER_LABEL: + g_value_set_string(val, self->label); + break; + case PROP_HEADER_SECONDARY_BUTTON: + g_value_set_object(val, (GObject *) self->header_secondary_button); + break; + case PROP_CONTENT: + g_value_set_object(val, (GObject *) self->content); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +static void koto_expander_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) { + 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", KOTO_BUTTON_PIXBUF_SIZE_SMALL); + + if (GTK_IS_WIDGET(new_button)) { // Created our widget successfully + self->header_button = new_button; + gtk_box_pack_start(GTK_BOX(self->header), GTK_WIDGET(self->header_button), TRUE, TRUE, 0); // Add it + } + } + + switch (prop_id) { + case PROP_HEADER_ICON_NAME: + g_return_if_fail(GTK_IS_WIDGET(self->header_button)); + koto_button_set_icon_name(self->header_button, g_strdup(g_value_get_string(val))); + break; + case PROP_HEADER_LABEL: + g_return_if_fail(GTK_IS_WIDGET(self->header_button)); + koto_button_set_text(self->header_button, g_strdup(g_value_get_string(val))); + break; + case PROP_HEADER_SECONDARY_BUTTON: + koto_expander_set_secondary_button(self, (GtkWidget*) g_value_get_object(val)); + break; + case PROP_CONTENT: + koto_expander_set_content(self, (GtkWidget*) g_value_get_object(val)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + break; + } +} + +static void koto_expander_init(KotoExpander *self) { + GtkSettings *settings = gtk_settings_get_default(); + g_object_set(settings, "gtk_application_prefer_dark_theme", TRUE, NULL); + + GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(self)); + gtk_style_context_add_class(style, "expander"); + + self->header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); + + GtkStyleContext *header_style = gtk_widget_get_style_context(self->header); + gtk_style_context_add_class(header_style, "expander-header"); + + self->revealer = gtk_revealer_new(); + gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), TRUE); // Set to be revealed by default + gtk_revealer_set_transition_type(GTK_REVEALER(self->revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN); + + self->header_expand_button = koto_flipper_button_new("pan-down-symbolic", "pan-up-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_box_pack_end(GTK_BOX(self->header), GTK_WIDGET(self->header_expand_button), FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(self), self->header, FALSE, FALSE, 0); + gtk_box_pack_end(GTK_BOX(self), self->revealer, TRUE, TRUE, 0); + + self->constructed = TRUE; + + g_signal_connect(self->header_expand_button, "clicked", G_CALLBACK(koto_expander_toggle_content), self); +} + +void koto_expander_set_secondary_button(KotoExpander *self, GtkWidget *new_button) { + if (!self->constructed) { + return; + } + + if (!GTK_IS_BUTTON(new_button)) { + return; + } + + if (GTK_IS_BUTTON(self->header_secondary_button)) { // Already have a button + gtk_container_remove(GTK_CONTAINER(self->header), self->header_secondary_button); + //g_free(self->header_secondary_button); + } + + self->header_secondary_button = new_button; + gtk_box_pack_end(GTK_BOX(self->header), self->header_secondary_button, FALSE, FALSE, 0); + + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_HEADER_SECONDARY_BUTTON]); +} + +void koto_expander_set_content(KotoExpander *self, GtkWidget *new_content) { + if (!self->constructed) { + return; + } + + if (self->content != NULL) { // Already have content + gtk_container_remove(GTK_CONTAINER(self->revealer), self->content); + g_free(self->content); + } + + self->content = new_content; + gtk_container_add(GTK_CONTAINER(self->revealer), self->content); + + g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CONTENT]); +} + +void koto_expander_toggle_content(GtkWidget *button, gpointer data) { + KotoExpander* self = data; + koto_flipper_button_flip(KOTO_FLIPPER_BUTTON(button)); + GtkRevealer* rev = GTK_REVEALER(self->revealer); + gtk_revealer_set_reveal_child(rev, !gtk_revealer_get_reveal_child(rev)); // Invert our values +} + +KotoExpander* koto_expander_new(gchar *primary_icon_name, gchar *primary_label_text) { + return g_object_new(KOTO_TYPE_EXPANDER, + "orientation", GTK_ORIENTATION_VERTICAL, + "icon-name", primary_icon_name, + "label", primary_label_text, + NULL + ); +} + +KotoExpander* koto_expander_new_with_button(gchar *primary_icon_name, gchar *primary_label_text, GtkWidget *secondary_button) { + return g_object_new(KOTO_TYPE_EXPANDER, + "orientation", GTK_ORIENTATION_VERTICAL, + "icon-name", primary_icon_name, + "label", primary_label_text, + "secondary-button", secondary_button, + NULL + ); +} diff --git a/src/koto-expander.h b/src/koto-expander.h new file mode 100644 index 0000000..c047ab7 --- /dev/null +++ b/src/koto-expander.h @@ -0,0 +1,36 @@ +/* koto-expander.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 + +G_BEGIN_DECLS + +#define KOTO_TYPE_EXPANDER (koto_expander_get_type()) + +G_DECLARE_FINAL_TYPE (KotoExpander, koto_expander, KOTO, EXPANDER, GtkBox) + +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, GtkWidget *secondary_button); +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, GtkWidget *new_button); +void koto_expander_set_content(KotoExpander *self, GtkWidget *new_content); +void koto_expander_toggle_content(GtkWidget *button, gpointer data); + +G_END_DECLS diff --git a/src/koto-flipper-button.c b/src/koto-flipper-button.c new file mode 100644 index 0000000..510bf0d --- /dev/null +++ b/src/koto-flipper-button.c @@ -0,0 +1,129 @@ +/* koto-flipper-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 +#include "koto-config.h" +#include "koto-flipper-button.h" + +enum { + PROP_0, + PROP_SIZE, + PROP_INITIAL_IMAGE, + PROP_FLIPPED_IMAGE, + N_PROPERTIES +}; + +static GParamSpec *props[N_PROPERTIES] = { NULL, }; + +struct _KotoFlipperButton { + GtkButton parent_instance; + GtkIconSize size; + gboolean flipped; + + gchar *initial_image; + gchar *flipped_image; +}; + +struct _KotoFlipperButtonClass { + GtkButtonClass parent_class; +}; + +G_DEFINE_TYPE(KotoFlipperButton, koto_flipper_button, GTK_TYPE_BUTTON); +static void koto_flipper_button_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec); + +static void koto_flipper_button_class_init(KotoFlipperButtonClass *c) { + GObjectClass *gobject_class; + gobject_class = G_OBJECT_CLASS(c); + gobject_class->set_property = koto_flipper_button_set_property; + + props[PROP_SIZE] = g_param_spec_enum( + "size", + "Icon Size", + "Size of the icon", + GTK_TYPE_ICON_SIZE, + GTK_ICON_SIZE_MENU, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_WRITABLE + ); + + props[PROP_INITIAL_IMAGE] = g_param_spec_string( + "initial-image", + "Initial Image", + "Image to use initially for the button", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_WRITABLE + ); + + props[PROP_FLIPPED_IMAGE] = g_param_spec_string( + "flipped-image", + "Flipped Image", + "Image to use when the button is flipped", + NULL, + G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_WRITABLE + ); + + g_object_class_install_properties(gobject_class, N_PROPERTIES, props); +} + +static void koto_flipper_button_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) { + KotoFlipperButton *self = KOTO_FLIPPER_BUTTON(obj); + + if (prop_id == PROP_INITIAL_IMAGE) { + g_free(self->initial_image); + self->initial_image = g_strdup(g_value_get_string(val)); + koto_flipper_button_set_icon(self, self->initial_image); + } else if (prop_id == PROP_FLIPPED_IMAGE) { + g_free(self->flipped_image); + self->flipped_image = g_strdup(g_value_get_string(val)); + } else if (prop_id == PROP_SIZE) { + self->size = (GtkIconSize) g_value_get_enum(val); + + if (self->initial_image != NULL && !self->flipped) { + koto_flipper_button_set_icon(self, self->flipped ? self->initial_image : self->flipped_image); + } + } else { + G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); + return; + } + + g_object_notify_by_pspec(obj, props[prop_id]); +} + +static void koto_flipper_button_init(KotoFlipperButton *self) { + self->flipped = FALSE; + + GtkStyleContext *button_style = gtk_widget_get_style_context(GTK_WIDGET(self)); // Get the styling for the flipper button + gtk_style_context_add_class(button_style, "flat"); // Set it to flat +} + +void koto_flipper_button_flip(KotoFlipperButton *self) { + koto_flipper_button_set_icon(self, self->flipped ? self->initial_image : self->flipped_image); + self->flipped = !self->flipped; +} + +void koto_flipper_button_set_icon(KotoFlipperButton *self, gchar *name) { + GtkWidget *new_image = gtk_image_new_from_icon_name(name, self->size); + gtk_button_set_image(GTK_BUTTON(self), new_image); +} + +KotoFlipperButton* koto_flipper_button_new(gchar *initial_icon, gchar *flipped_icon, GtkIconSize size) { + return g_object_new(KOTO_TYPE_FLIPPER_BUTTON, + "initial-image", initial_icon, + "flipped-image", flipped_icon, + "size", size, + NULL + ); +} diff --git a/src/koto-flipper-button.h b/src/koto-flipper-button.h new file mode 100644 index 0000000..711e607 --- /dev/null +++ b/src/koto-flipper-button.h @@ -0,0 +1,32 @@ +/* koto-flipper-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. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define KOTO_TYPE_FLIPPER_BUTTON (koto_flipper_button_get_type()) + +G_DECLARE_FINAL_TYPE (KotoFlipperButton, koto_flipper_button, KOTO, FLIPPER_BUTTON, GtkButton) + +KotoFlipperButton* koto_flipper_button_new(gchar *initial_icon, gchar *flipped_icon, GtkIconSize size); +void koto_flipper_button_flip(KotoFlipperButton *self); +void koto_flipper_button_set_icon(KotoFlipperButton *self, gchar *name); + +G_END_DECLS diff --git a/src/koto-headerbar.c b/src/koto-headerbar.c new file mode 100644 index 0000000..e408891 --- /dev/null +++ b/src/koto-headerbar.c @@ -0,0 +1,47 @@ +/* koto-headerbar.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 "koto-config.h" +#include "koto-headerbar.h" + +struct _KotoHeaderBar { + GtkHeaderBar parent_instance; + GtkButton *menu_button; + GtkWidget *search; +}; + +struct _KotoHeaderBarClass { + GtkHeaderBarClass parent_class; +}; + +G_DEFINE_TYPE(KotoHeaderBar, koto_headerbar, GTK_TYPE_HEADER_BAR) + +static void koto_headerbar_class_init(KotoHeaderBarClass *c) { + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(c); + gtk_widget_class_set_template_from_resource(widget_class, "/com/github/joshstrobl/koto/koto-headerbar.ui"); + //gtk_widget_class_bind_template_child(widget_class, KotoHeaderBar, menu_button); + //gtk_widget_class_bind_template_child(widget_class, KotoHeaderBar, search); +} + +static void koto_headerbar_init(KotoHeaderBar *self) { + gtk_widget_init_template(GTK_WIDGET(self)); + gtk_widget_show_all(GTK_WIDGET(self)); +} + +KotoHeaderBar* koto_headerbar_new(void) { + return g_object_new(KOTO_TYPE_HEADERBAR, NULL); +} diff --git a/src/koto-headerbar.h b/src/koto-headerbar.h new file mode 100644 index 0000000..43c4ffc --- /dev/null +++ b/src/koto-headerbar.h @@ -0,0 +1,30 @@ +/* koto-headerbar.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 + +G_BEGIN_DECLS + +#define KOTO_TYPE_HEADERBAR (koto_headerbar_get_type()) + +G_DECLARE_FINAL_TYPE (KotoHeaderBar, koto_headerbar, KOTO, HEADERBAR, GtkHeaderBar) + +KotoHeaderBar* koto_headerbar_new (void); + +G_END_DECLS diff --git a/src/koto-headerbar.ui b/src/koto-headerbar.ui new file mode 100644 index 0000000..03384fe --- /dev/null +++ b/src/koto-headerbar.ui @@ -0,0 +1,40 @@ + + + + + + False + audio-headphones + 3 + + + diff --git a/src/koto-nav.c b/src/koto-nav.c new file mode 100644 index 0000000..15ec21f --- /dev/null +++ b/src/koto-nav.c @@ -0,0 +1,155 @@ +/* koto-nav.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "koto-config.h" +#include "koto-button.h" +#include "koto-expander.h" +#include "koto-nav.h" +#include "koto-utils.h" + +struct _KotoNav { + GtkScrolledWindow parent_instance; + GtkWidget *content; + + KotoButton *home_button; + KotoExpander *audiobook_expander; + KotoExpander *music_expander; + KotoExpander *podcast_expander; + KotoExpander *playlists_expander; + + // Audiobooks + + KotoButton *audiobooks_local; + KotoButton *audiobooks_audible; + KotoButton *audiobooks_librivox; + + // Music + + KotoButton *music_local; + KotoButton *music_radio; + + // Podcasts + + KotoButton *podcasts_local; + KotoButton *podcasts_discover; +}; + +struct _KotoNavClass { + GtkScrolledWindowClass parent_class; +}; + +G_DEFINE_TYPE(KotoNav, koto_nav, GTK_TYPE_SCROLLED_WINDOW); + +static void koto_nav_class_init(KotoNavClass *c) { + +} + +static void koto_nav_init(KotoNav *self) { + gtk_widget_set_size_request(GTK_WIDGET(self), 300, -1); // Take up 300px width + self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); + + GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(self)); + gtk_style_context_add_class(style, "primary-nav"); + + gtk_container_add(GTK_CONTAINER(self), self->content); + + KotoButton *h_button = koto_button_new_with_icon("Home", "user-home-symbolic", KOTO_BUTTON_PIXBUF_SIZE_SMALL); + + if (h_button != NULL) { + self->home_button = h_button; + gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->home_button), FALSE, FALSE, 0); + } + + koto_nav_create_audiobooks_section(self); + koto_nav_create_music_section(self); + koto_nav_create_podcasts_section(self); + + + + GtkWidget *playlist_add_button = koto_create_flat_icon_button("list-add-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR); + 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_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->playlists_expander), FALSE, FALSE, 0); + } + + gtk_widget_show_all(GTK_WIDGET(self)); +} + +void koto_nav_create_audiobooks_section(KotoNav *self) { + KotoExpander *a_expander = koto_expander_new("ephy-bookmarks-symbolic", "Audiobooks"); + + self->audiobook_expander = a_expander; + gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->audiobook_expander), FALSE, FALSE, 0); + + GtkWidget *new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + + self->audiobooks_local = koto_button_new_plain("Local Library"); + self->audiobooks_audible = koto_button_new_plain("Audible"); + self->audiobooks_librivox = koto_button_new_plain("LibriVox"); + + gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_local), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_audible), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_librivox), FALSE, FALSE, 0); + + koto_expander_set_content(a_expander, new_content); +} + +void koto_nav_create_music_section(KotoNav *self) { + KotoExpander *m_expander = koto_expander_new("emblem-music-symbolic", "Music"); + self->music_expander = m_expander; + gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->music_expander), FALSE, FALSE, 0); + + GtkWidget *new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + + self->music_local = koto_button_new_plain("Local Library"); + self->music_radio = koto_button_new_plain("Radio"); + + gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->music_local), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->music_radio), FALSE, FALSE, 0); + + koto_expander_set_content(m_expander, new_content); +} + +void koto_nav_create_podcasts_section(KotoNav *self) { + KotoExpander *p_expander = koto_expander_new("microphone-sensitivity-high-symbolic", "Podcasts"); + self->podcast_expander = p_expander; + gtk_box_pack_start(GTK_BOX(self->content), GTK_WIDGET(self->podcast_expander), FALSE, FALSE, 0); + + GtkWidget *new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + + self->podcasts_local = koto_button_new_plain("Library"); + self->podcasts_discover = koto_button_new_plain("Find New Podcasts"); + + gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->podcasts_local), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(new_content), GTK_WIDGET(self->podcasts_discover), FALSE, FALSE, 0); + + koto_expander_set_content(p_expander, new_content); +} + +KotoNav* koto_nav_new(void) { + return g_object_new(KOTO_TYPE_NAV, + "hscrollbar-policy", GTK_POLICY_NEVER, + "min-content-width", 300, + "max-content-width", 300, + "propagate-natural-height", TRUE, + "propagate-natural-width", TRUE, + "shadow-type", GTK_SHADOW_NONE + ,NULL); +} diff --git a/src/koto-nav.h b/src/koto-nav.h new file mode 100644 index 0000000..58c395f --- /dev/null +++ b/src/koto-nav.h @@ -0,0 +1,32 @@ +/* koto-nav.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. + */ + +#pragma once +#include + +G_BEGIN_DECLS + +#define KOTO_TYPE_NAV (koto_nav_get_type()) + +G_DECLARE_FINAL_TYPE (KotoNav, koto_nav, KOTO, NAV, GtkScrolledWindow) + +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); + +G_END_DECLS diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c new file mode 100644 index 0000000..307f0d7 --- /dev/null +++ b/src/koto-playerbar.c @@ -0,0 +1,172 @@ +/* koto-playerbar.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "koto-config.h" +#include "koto-playerbar.h" +#include "koto-utils.h" + +struct _KotoPlayerBar { + GtkBox parent_instance; + + /* Sections */ + GtkWidget *playback_section; + GtkWidget *primary_controls_section; + GtkWidget *secondary_controls_section; + + /* Primary Buttons */ + GtkWidget *back_button; + GtkWidget *play_pause_button; + GtkWidget *forward_button; + GtkWidget *repeat_button; + GtkWidget *shuffle_button; + GtkWidget *playlist_button; + GtkWidget *eq_button; + GtkWidget *volume_button; + + /* Selected Playback Section */ + + GtkWidget *playback_details_section; + GtkWidget *artwork; + GtkWidget *playback_title; + GtkWidget *playback_album; + GtkWidget *playback_artist; +}; + +struct _KotoPlayerBarClass { + GtkBoxClass parent_class; +}; + +G_DEFINE_TYPE(KotoPlayerBar, koto_playerbar, GTK_TYPE_BOX); + +static void koto_playerbar_constructed(GObject *obj); + +static void koto_playerbar_class_init(KotoPlayerBarClass *c) { + GObjectClass *gobject_class; + gobject_class = G_OBJECT_CLASS(c); + + gobject_class->constructed = koto_playerbar_constructed; +} + +static void koto_playerbar_constructed(GObject *obj) { + KotoPlayerBar *self = KOTO_PLAYERBAR(obj); + self->playback_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + self->primary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + self->secondary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + + gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(self->primary_controls_section), FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(self->playback_section), TRUE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(self), GTK_WIDGET(self->secondary_controls_section), FALSE, FALSE, 0); + + koto_playerbar_create_playback_details(self); + koto_playerbar_create_primary_controls(self); + koto_playerbar_create_secondary_controls(self); + + gtk_widget_set_margin_top(GTK_WIDGET(self), 10); + gtk_widget_set_margin_bottom(GTK_WIDGET(self), 10); + gtk_widget_set_margin_start(GTK_WIDGET(self), 10); + gtk_widget_set_margin_end(GTK_WIDGET(self), 10); +} + +static void koto_playerbar_init(KotoPlayerBar *self) { + gtk_widget_show_all(GTK_WIDGET(self)); +} + +KotoPlayerBar* koto_playerbar_new(void) { + return g_object_new(KOTO_TYPE_PLAYERBAR, "orientation", GTK_ORIENTATION_HORIZONTAL, NULL); +} + +void koto_playerbar_create_playback_details(KotoPlayerBar* bar) { + bar->playback_details_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + + GtkIconTheme* default_icon_theme = gtk_icon_theme_get_default(); + + if (default_icon_theme != NULL) { + gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(bar)); + GdkPixbuf* audio_pixbuf = gtk_icon_theme_load_icon_for_scale(default_icon_theme, "audio-x-generic-symbolic", 96, scale_factor, GTK_ICON_LOOKUP_USE_BUILTIN & GTK_ICON_LOOKUP_FORCE_SIZE, NULL); + + if (audio_pixbuf != NULL) { + bar->artwork = gtk_image_new_from_pixbuf(audio_pixbuf); + } + } + + if (bar->artwork == NULL) { + bar->artwork = gtk_image_new_from_icon_name("audio-x-generic-symbolic", GTK_ICON_SIZE_DIALOG); + } + + if (bar->artwork != NULL) { + gtk_widget_set_margin_end(bar->artwork, 10); + gtk_box_pack_start(GTK_BOX(bar->playback_section), bar->artwork, FALSE, FALSE, 0); + } + + bar->playback_title = gtk_label_new(_("Title")); + bar->playback_album = gtk_label_new(_("Album")); + bar->playback_artist = gtk_label_new(_("Artist")); + + gtk_box_pack_start(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_title), TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_album), TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(bar->playback_details_section), GTK_WIDGET(bar->playback_artist), TRUE, TRUE, 0); + + gtk_box_pack_end(GTK_BOX(bar->playback_section), GTK_WIDGET(bar->playback_details_section), FALSE, FALSE, 0); +} + +void koto_playerbar_create_primary_controls(KotoPlayerBar* bar) { + bar->back_button = koto_create_flat_icon_button("media-skip-backward-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + bar->play_pause_button = koto_create_flat_icon_button("media-playback-start-symbolic", GTK_ICON_SIZE_DND); // TODO: Have this take in a state and switch to a different icon if necessary + bar->forward_button = koto_create_flat_icon_button("media-skip-forward-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + + if (bar->back_button != NULL) { + gtk_box_pack_start(GTK_BOX(bar->primary_controls_section), bar->back_button, FALSE, FALSE, 0); + } + + if (bar->play_pause_button != NULL) { + gtk_box_pack_start(GTK_BOX(bar->primary_controls_section), bar->play_pause_button, FALSE, FALSE, 0); + } + + if (bar->forward_button != NULL) { + gtk_box_pack_start(GTK_BOX(bar->primary_controls_section), bar->forward_button, FALSE, FALSE, 0); + } +} + +void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) { + bar->repeat_button = koto_create_flat_icon_button("media-playlist-repeat-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + bar->shuffle_button = koto_create_flat_icon_button("media-playlist-shuffle-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + bar->playlist_button = koto_create_flat_icon_button("playlist-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + bar->eq_button = koto_create_flat_icon_button("multimedia-equalizer-symbolic", GTK_ICON_SIZE_LARGE_TOOLBAR); + bar->volume_button = gtk_volume_button_new(); // Have this take in a state and switch to a different icon if necessary + gtk_scale_button_set_value(GTK_SCALE_BUTTON(bar->volume_button), 0.5); + + if (bar->repeat_button != NULL) { + gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), bar->repeat_button, FALSE, FALSE, 0); + } + + if (bar->shuffle_button != NULL) { + gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), bar->shuffle_button, FALSE, FALSE, 0); + } + + if (bar->playlist_button != NULL) { + gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), bar->playlist_button, FALSE, FALSE, 0); + } + + if (bar->eq_button != NULL) { + gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), bar->eq_button, FALSE, FALSE, 0); + } + + if (bar->volume_button != NULL) { + gtk_box_pack_start(GTK_BOX(bar->secondary_controls_section), bar->volume_button, FALSE, FALSE, 0); + } +} diff --git a/src/koto-playerbar.h b/src/koto-playerbar.h new file mode 100644 index 0000000..1b204f2 --- /dev/null +++ b/src/koto-playerbar.h @@ -0,0 +1,32 @@ +/* koto-playerbar.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 + +G_BEGIN_DECLS + +#define KOTO_TYPE_PLAYERBAR (koto_playerbar_get_type()) + +G_DECLARE_FINAL_TYPE (KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GtkBox) + +KotoPlayerBar* koto_playerbar_new (void); +void koto_playerbar_create_playback_details(KotoPlayerBar* bar); +void koto_playerbar_create_primary_controls(KotoPlayerBar* bar); +void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar); + +G_END_DECLS diff --git a/src/koto-utils.c b/src/koto-utils.c new file mode 100644 index 0000000..985c787 --- /dev/null +++ b/src/koto-utils.c @@ -0,0 +1,28 @@ +/* koto-utils.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 "koto-utils.h" + +GtkWidget* koto_create_flat_icon_button(gchar *icon_name, GtkIconSize size) { + GtkWidget* button; + button = gtk_button_new_from_icon_name(icon_name, size); + + GtkStyleContext *button_style = gtk_widget_get_style_context(button); + gtk_style_context_add_class(button_style, "flat"); + + return button; +} diff --git a/src/koto-utils.h b/src/koto-utils.h new file mode 100644 index 0000000..0a13d1c --- /dev/null +++ b/src/koto-utils.h @@ -0,0 +1,24 @@ +/* koto-utils.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 + +G_BEGIN_DECLS + +GtkWidget* koto_create_flat_icon_button(gchar *icon_name, GtkIconSize size); + +G_END_DECLS diff --git a/src/koto-window.c b/src/koto-window.c new file mode 100644 index 0000000..b86d565 --- /dev/null +++ b/src/koto-window.c @@ -0,0 +1,89 @@ +/* koto-window.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 "indexer/file-indexer.h" +#include "koto-config.h" +#include "koto-headerbar.h" +#include "koto-nav.h" +#include "koto-playerbar.h" +#include "koto-window.h" + +struct _KotoWindow { + GtkApplicationWindow parent_instance; + KotoIndexedLibrary *library; + KotoHeaderBar *header_bar; + GtkWidget *primary_layout; + GtkWidget *content_layout; + + KotoNav *nav; + KotoPlayerBar *player_bar; +}; + +G_DEFINE_TYPE (KotoWindow, koto_window, GTK_TYPE_APPLICATION_WINDOW) + +static void koto_window_class_init (KotoWindowClass *klass) { + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/com/github/joshstrobl/koto/koto-window.ui"); +} + +static void koto_window_init (KotoWindow *self) { + gtk_widget_init_template (GTK_WIDGET (self)); + GtkCssProvider* provider = gtk_css_provider_new(); + gtk_css_provider_load_from_resource(provider, "/com/github/joshstrobl/koto/style.css"); + gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + //KotoHeaderBar *header = koto_headerbar_new(); + self->header_bar = koto_headerbar_new(); + + if (self->header_bar != NULL) { + gtk_window_set_titlebar(GTK_WINDOW(self), GTK_WIDGET(self->header_bar)); + } + + self->primary_layout = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + self->content_layout = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + + self->nav = koto_nav_new(); + + if (self->nav != NULL) { + gtk_box_pack_start(GTK_BOX(self->content_layout), GTK_WIDGET(self->nav), FALSE, TRUE, 10); + + GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL); + gtk_box_pack_end(GTK_BOX(self->content_layout), sep, FALSE, TRUE, 0); + } + + gtk_box_pack_start(GTK_BOX(self->primary_layout), self->content_layout, TRUE, TRUE, 0); + + self->player_bar = koto_playerbar_new(); + + if (self->player_bar != NULL) { + gtk_box_pack_start(GTK_BOX(self->primary_layout), GTK_WIDGET(self->player_bar), FALSE, FALSE, 0); + } + + gtk_container_add(GTK_CONTAINER(self), self->primary_layout); + gtk_widget_show_all(GTK_WIDGET(self)); + + g_thread_new("load-library", load_library, self); +} + +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; + } +} diff --git a/src/koto-window.h b/src/koto-window.h new file mode 100644 index 0000000..bab11e8 --- /dev/null +++ b/src/koto-window.h @@ -0,0 +1,30 @@ +/* koto-window.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 + +G_BEGIN_DECLS + +#define KOTO_TYPE_WINDOW (koto_window_get_type()) + +G_DECLARE_FINAL_TYPE (KotoWindow, koto_window, KOTO, WINDOW, GtkApplicationWindow) + +G_END_DECLS + +void load_library(KotoWindow *self); diff --git a/src/koto-window.ui b/src/koto-window.ui new file mode 100644 index 0000000..a1da769 --- /dev/null +++ b/src/koto-window.ui @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/koto.gresource.xml b/src/koto.gresource.xml new file mode 100644 index 0000000..5e969dc --- /dev/null +++ b/src/koto.gresource.xml @@ -0,0 +1,8 @@ + + + + koto-headerbar.ui + koto-window.ui + ../data/style.css + + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..4bf6957 --- /dev/null +++ b/src/main.c @@ -0,0 +1,50 @@ +/* main.c + * + * Copyright 2021 Joshua Strobl + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "koto-config.h" +#include "koto-window.h" + +static void on_activate (GtkApplication *app) { + g_assert(GTK_IS_APPLICATION (app)); + + GtkWindow *window; + + window = gtk_application_get_active_window (app); + if (window == NULL) { + window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 600, "default-height", 300, NULL); + } + + gtk_window_present(window); +} + +int main (int argc, char *argv[]) { + g_autoptr(GtkApplication) app = NULL; + int ret; + + /* Set up gettext translations */ + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + app = gtk_application_new ("com.github.joshstrobl.koto", G_APPLICATION_FLAGS_NONE); + g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); + ret = g_application_run (G_APPLICATION (app), argc, argv); + + return ret; +} diff --git a/src/meson.build b/src/meson.build new file mode 100644 index 0000000..ff11a8d --- /dev/null +++ b/src/meson.build @@ -0,0 +1,31 @@ +koto_sources = [ + 'indexer/file-indexer.c', + 'main.c', + 'koto-button.c', + 'koto-expander.c', + 'koto-flipper-button.c', + 'koto-headerbar.c', + 'koto-nav.c', + 'koto-playerbar.c', + 'koto-utils.c', + 'koto-window.c', +] + +koto_deps = [ + dependency('glib-2.0', version: '>= 2.66'), + dependency('gio-2.0', version: '>= 2.66'), + dependency('gtk+-3.0', version: '>= 3.24'), + dependency('libmagic', version: '>=5.39'), +] + +gnome = import('gnome') + +koto_sources += gnome.compile_resources('koto-resources', + 'koto.gresource.xml', + c_name: 'koto' +) + +executable('com.github.joshstrobl.koto', koto_sources, + dependencies: koto_deps, + install: true, +)