I have been wondering what the best way to extend a widget class in gtk4 would look like. I read through Custom widgets in GTK 4 – Introduction – GTK Development Blog and looked at some discussions in forums and I am still a bit unsure of what the best course of action would be.
So lets take the example of where you need to add custom properties to a button. You would do something like
struct _CustomBtn {
GtkWidget parent_instance;
GtkButton *btn;
// custom properties
};
static void custom_btn_class_init(PkLauncherBtnClass *klass) {
self->btn = gtk_button_new();
gtk_widget_class_set_layout_manager_type(
GTK_WIDGET_CLASS(klass),
GTK_TYPE_BIN_LAYOUT
);
}
the only problem with this is if your writing a library of extendable widgets to use then you would still want the be able to use your extended widgets in xml
<object class="CustomBtn">
<property name="label">Test me</property>
<property name="custom-prop">My custom property</property>
</object>
So the custom button would still be able to use the label and all other properties of the original button. The only way I can see this being possible is to basically write the property getters and setters for the entire button. Initialize those properties in init and so on. This would be a pain to do every time.
So I then I figured you could do something like just create a custom button that would extend all the functionality of the gtk button and then I could just extend that button when needed something like:
#include "btn.h"
static void buildable_init(GtkBuildableIface *iface);
G_DEFINE_TYPE_WITH_CODE(
CustomBtn,
custom_btn,
GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE(GTK_TYPE_BUILDABLE, buildable_init)
);
// default gtk button attributes
enum {
PROP_0,
PROP_LABEL,
PROP_ICON_NAME,
PROP_HAS_FRAME,
PROP_USE_UNDERLINE,
PROP_CAN_SHRINK,
N_PROPS
};
static GParamSpec *props[N_PROPS];
static void set_property(
GObject *obj,
guint prop_id,
const GValue *value,
GParamSpec *pspec
) {
CustomBtn *self = CUSTOM_BTN(obj);
switch(prop_id) {
case PROP_LABEL:
gtk_button_set_label(self->btn, g_value_get_string(value));
break;
case PROP_ICON_NAME:
gtk_button_set_icon_name(self->btn, g_value_get_string(value));
break;
case PROP_HAS_FRAME:
gtk_button_set_has_frame(self->btn, g_value_get_boolean(value));
break;
case PROP_CAN_SHRINK:
gtk_button_set_can_shrink(self->btn, g_value_get_boolean(value));
break;
case PROP_USE_UNDERLINE:
gtk_button_set_use_underline(self->btn, g_value_get_boolean(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
}
}
static void get_property(
GObject *obj,
guint prop_id,
GValue *value,
GParamSpec *pspec
) {
CustomBtn *self = CUSTOM_BTN(obj);
switch(prop_id) {
case PROP_LABEL:
g_value_set_string(value, gtk_button_get_label(self->btn));
break;
case PROP_ICON_NAME:
g_value_set_string(value, gtk_button_get_icon_name(self->btn));
break;
case PROP_HAS_FRAME:
g_value_set_boolean(value, gtk_button_get_has_frame(self->btn));
break;
case PROP_USE_UNDERLINE:
g_value_set_boolean(value, gtk_button_get_use_underline(self->btn));
break;
case PROP_CAN_SHRINK:
g_value_set_boolean(value, gtk_button_get_can_shrink(self->btn));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
}
}
static void add_child(
GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *type
) {
CustomBtn *self = CUSTOM_BTN(buildable);
gtk_button_set_child(self->btn, GTK_WIDGET(child));
}
static void buildable_init(GtkBuildableIface *iface) {
iface->add_child = add_child;
}
static void dispose(GObject *obj) {
CustomBtn *self = CUSTOM_BTN(obj);
g_clear_pointer((GtkWidget **)&self->btn, gtk_widget_unparent);
G_OBJECT_CLASS(custom_btn_parent_class)->dispose(obj);
}
static void custom_btn_init(CustomBtn *self) {
self->btn = GTK_BUTTON(gtk_button_new());
gtk_widget_set_layout_manager(GTK_WIDGET(self), gtk_bin_layout_new());
gtk_widget_set_parent(GTK_WIDGET(self->btn), GTK_WIDGET(self));
}
static void custom_btn_class_init(CustomBtnClass *klass) {
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
obj_class->dispose = dispose;
obj_class->set_property = set_property;
obj_class->get_property = get_property;
props[PROP_LABEL] = g_param_spec_string(
"label",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
);
props[PROP_ICON_NAME] = g_param_spec_string(
"icon-name",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
);
props[PROP_HAS_FRAME] = g_param_spec_boolean(
"has-frame",
NULL,
NULL,
TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS
);
props[PROP_CAN_SHRINK] = g_param_spec_boolean(
"can-shrink",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY
);
props[PROP_USE_UNDERLINE] = g_param_spec_boolean(
"use-underline",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY
);
g_object_class_install_properties(obj_class, N_PROPS, props);
}
This seems completely unnecessary for something that used to be as simple as
struct _PkBtn {
GtkButton parent_instance;
// custom properties
};
You would basically be rewriting the button with the exact same code as the original button just to extend it. And you would have to do this for all widgets that you want to extend functionality to.
So is this right? For every widget that I want to extend is it preferred to just create a wrapper GtkWidget and hold a reference to the widget you want to extend, then forward all properties to the reference or is there a better way to go about this?