Search code examples
cssgtkgtk3

Modify GTK button style / CSS and update/refresh instantly


Let me preface this by saying I'm a GTK Noob and find myself parachuted into modifying someone else's code - that someone else having fled the country to greener pa$ture$.

So, we have a GTK grid of buttons which are just a coloured square, each of which can be modified on-the-fly to change colour.

At present, the program flow goes something like this;

Generate CSS containing a list of 25 button colour styles like this:

.btn_colour_id_XX{background: #336699}
.btn_colour_id_XX:active{ background: shade(#336699, 0.5) }

Then attach a style to each button based on which colour we want it to be:

GtkStyleContext *context = gtk_widget_get_style_context(button);
snprintf(value, 20, "btn_colour_id_%02d", colour_id); // Apply colour_id to button
gtk_style_context_add_class(context, value);

And display our window full of buttons.

When we get a call to modify a button colour, the code just switches the CSS style of that button to the requested one:

snprintf(desired_class, 10, "btn_colour_id_%02d", color_id); // Style we want for button
for (GList *l = classes; l != NULL; l = l->next)
{
    char *classname = (char *)l->data;
    if (strstr(classname, "btn_colour_id_") == NULL)
    {
        continue;
    }
    if (strlen(classname) != strlen(desired_class) || strstr(classname, desired_class) == NULL)
    {
        g_message("Swapping [%s] colour from %s > %s", name, classname, desired_class);
        gtk_style_context_remove_class(context, (const gchar *)l->data);
        gtk_style_context_add_class(context, desired_class);
    }
}

However, what NEEDS to happen is to be able to set any button to any RGB colour on-the-fly, and I'm not convincted that creating 2^24 CSS styles is the optimum route for that ;)

My idea is that we instead give each button its own CSS id selector with its own RGB colour and simply modify the colour values in the CSS as required... however, I have googled myself half to death looking at GTK documentation and I can't see any obvious method by which I could modify the existing CSS and have that change reflected in the displayed buttons.

Can anyone point me in the right direction on this?


EDIT: So far I've found a couple of potential examples...

This one...

static GtkCssProvider* provider = NULL;

static void set_label_color(GtkWidget* label, const char* color)
{
    const char* format = "label { color: %s; }";
    size_t length = strlen(format) - 2 + 1;
    char style[length];
    sprintf(style, format, color);

    if (provider == NULL) {
        // only create and add the provider the first time
        provider = gtk_css_provider_new();
        gtk_style_context_add_provider(
            gtk_widget_get_style_context(label),
            GTK_STYLE_PROVIDER(provider),
            GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
        g_object_unref(provider);
    }

    gtk_css_provider_load_from_data(provider, style, -1, NULL);
} 

And this one...

void set_background_color(GtkWidget *w, gchar *color)
{
    GtkCssProvider *gcp;
    GtkStyleContext *gsc;
    gsc = gtk_widget_get_style_context(w);
    const gchar *type = g_type_name (G_TYPE_FROM_INSTANCE (w));
    gchar *str = g_strdup_printf ("%s {background-color: %s;}", type,
color);
    gcp= gtk_css_provider_new();
    gtk_css_provider_load_from_data(gcp, str, -1, 0);
    g_free (str);
    gtk_style_context_add_provider(gsc, GTK_STYLE_PROVIDER(gcp),
    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}

// I don't know if there is a memory leak here though...

I also don't know for sure if there's a memory leak in either of these and whether providers are static or are destroyed / can be free()d after being added to a widget's context... the docs/examples seem vague on this, anyone got a decent tutorial on how to create/curate multiple proviers in an app?

The 1st example looks most promising to me, although it seems a bit rough to just re-write the provier over and over?


Solution

  • OK, I've figured it out myself - this may be totally the wrong way to do it but it works.

    It involves keeping the CSS provider (rather than g_object_deref() after use) in a global context that contains info about all the buttons. Then simply over-writing the CSS provider each time, which TFM says is perfectly fine.

    We have to keep the CSS provider and the ref to it as GTK seems to be missing a lot of functions to get this sort information back from an object once you've created it - you can add new, replace, and destroy/deref but you can't read the existing one back & modify it. I guess most UI's you write it once and that's it.

    Context:

    struct dev_button
    {
        GtkWidget *btn;
        GtkCssProvider *bp;
    };
    

    Creating buttons:

    dbp = &_context->buttons[i]; // Pointer to a dev_button struct
    
    GtkStyleContext *context = gtk_widget_get_style_context(dbp->btn);
    dbp->bp = gtk_css_provider_new();
        
    // Create a CSS for this button
    snprintf(css, LONG_STR, ".btnid_%02d{background: #%06X}", btnID, colour);
            
    // Convert CSS to provider
    gtk_css_provider_load_from_data(dbp->bp, tstr, -1, NULL);
            
    // Add provider to button
    gtk_style_context_add_provider (context, GTK_STYLE_PROVIDER(dbp->bp), GTK_STYLE_PROVIDER_PRIORITY_USER);
            
    // Give the button the CSS style/class corresponding to the one we just created for it
    snprintf(value, SHORT_STR, "btnid_%02d", buttonId); // Sets default colour from table
    gtk_style_context_add_class(context, value);
    

    Dynamically change the button's colour:

    // Create new CSS
    snprintf(temp_css, LONG_STR, ".btnid_%02d{background: #%06X}", btnID, new_colour);
    // Re-load into provider
    gtk_css_provider_load_from_data(dbp->bp, tstr, -1, NULL);
    

    That's it, it works, and it doesn't leak memory.