koto/src/koto-button.c
Joshua Strobl fa19fd23b1 Implement TOML-based configuration support leveraging tomlc99 and custom file writer.
Fix code related to changing from temporary playlists to permanent ones.

Implement new centralized Koto Paths for defining our variables and revdns name for consistency.

Updated Album View to leverage KotoCoverArtButton component.

Started refactoring our theming to support multiple variants. It isn't where I want it yet but we'll get there.
2021-05-27 12:56:36 +03:00

576 lines
15 KiB
C

/* 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 <gtk-4.0/gtk/gtk.h>
#include "koto-button.h"
#include "config/config.h"
#include "koto-utils.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_BTN_0,
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
};
static GParamSpec * btn_props[N_BTN_PROPERTIES] = {
NULL,
};
struct _KotoButton {
GtkBox parent_instance;
guint pix_size;
GtkWidget * button_pic;
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;
};
struct _KotoButtonClass {
GtkBoxClass parent_class;
};
G_DEFINE_TYPE(KotoButton, koto_button, GTK_TYPE_BOX);
static void koto_button_constructed(GObject * obj);
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->constructed = koto_button_constructed;
gobject_class->set_property = koto_button_set_property;
gobject_class->get_property = koto_button_get_property;
btn_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
);
btn_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
);
btn_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
);
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",
"Name of Icon",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
btn_props[PROP_ALT_ICON_NAME] = g_param_spec_string(
"alt-icon-name",
"Name of an Alternate Icon",
"Name of an Alternate Icon",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_BTN_PROPERTIES, btn_props);
}
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) {
KotoButton * self = KOTO_BUTTON(obj);
GtkStyleContext * style = gtk_widget_get_style_context(GTK_WIDGET(self));
gtk_style_context_add_class(style, "koto-button");
G_OBJECT_CLASS(koto_button_parent_class)->constructed(obj);
}
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_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;
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_ALT_ICON_NAME:
g_value_set_string(val, self->alt_icon_name);
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:
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
koto_button_show_image(self, FALSE);
}
break;
case PROP_ALT_ICON_NAME:
koto_button_set_icon_name(self, g_strdup(g_value_get_string(val)), TRUE);
if (self->currently_showing_alt) { // Currently showing the alt image
koto_button_show_image(self, TRUE);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
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("");
} 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_append(GTK_BOX(self), self->badge_label);
}
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), 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 (!koto_utils_is_string_valid(file_path)) { // file path is invalid
return;
}
if (koto_utils_is_string_valid(self->image_file_path)) { // image file path is valid
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);
if (for_alt) { // Is for the alternate icon
if ((self->alt_icon_name != NULL) && strcmp(icon_name, self->alt_icon_name) != 0) { // If the icons are different
g_free(self->alt_icon_name);
}
self->alt_icon_name = copied_icon_name;
} else {
if ((self->icon_name != NULL) && strcmp(icon_name, self->icon_name) != 0) {
g_free(self->icon_name);
}
self->icon_name = copied_icon_name;
}
gboolean hide_image = FALSE;
if (for_alt && self->currently_showing_alt && ((self->alt_icon_name == NULL) || strcmp(self->alt_icon_name, "") == 0)) { // For alt, alt is currently showing, and no longer have alt
hide_image = TRUE;
} else if (!for_alt && ((self->icon_name == NULL) || (strcmp(self->icon_name, "") == 0))) { // Not for alt, no icon
hide_image = TRUE;
}
if (hide_image) { // Should hide the image
if (GTK_IS_PICTURE(self->button_pic)) { // If we already have a button image
gtk_widget_hide(self->button_pic); // Hide
}
return;
}
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
) {
if (size == self->pix_size) {
return;
}
self->pix_size = size;
gtk_widget_set_size_request(GTK_WIDGET(self), self->pix_size, self->pix_size);
g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_PIX_SIZE]);
}
void koto_button_set_text(
KotoButton * self,
gchar * text
) {
if (text == NULL) {
return;
}
if (self->text != NULL) { // Text defined
g_free(self->text); // Free existing text
}
self->text = g_strdup(text);
if (GTK_IS_LABEL(self->button_label)) { // If we have a button label
if (koto_utils_is_string_valid(self->text)) { // 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_box_remove(GTK_BOX(self), self->button_label);
g_free(self->button_label);
}
} else { // If we do not have a button label
if (koto_utils_is_string_valid(self->text)) { // 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);
if (GTK_IS_IMAGE(self->button_pic)) { // If we have an image
gtk_box_insert_child_after(GTK_BOX(self), self->button_label, self->button_pic);
} else {
gtk_box_prepend(GTK_BOX(self), GTK_WIDGET(self->button_label));
}
}
}
g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_TEXT]);
}
void koto_button_show_image(
KotoButton * self,
gboolean use_alt
) {
if (!KOTO_IS_BUTTON(self)) {
return;
}
if (self->use_from_file) { // Use from a file instead of icon name
if (!koto_utils_is_string_valid(self->image_file_path)) { // 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;
}
self->currently_showing_alt = use_alt;
gchar * name = use_alt ? self->alt_icon_name : self->icon_name;
if (GTK_IS_IMAGE(self->button_pic)) {
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_box_prepend(GTK_BOX(self), self->button_pic); // Prepend to the box
}
}
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) {
return g_object_new(
KOTO_TYPE_BUTTON,
"button-text",
label,
NULL
);
}
KotoButton * koto_button_new_with_icon(
gchar * label,
gchar * icon_name,
gchar * alt_icon_name,
KotoButtonPixbufSize size
) {
return g_object_new(
KOTO_TYPE_BUTTON,
"button-text",
label,
"icon-name",
icon_name,
"alt-icon-name",
alt_icon_name,
"pixbuf-size",
koto_get_pixbuf_size(size),
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
);
}