This function to show the text inside the progress bar deprecated in Gtk3+.
But we will not display text inside the progress bar.
We will take it the next level.
Not only we'll put image inside the progress bar but we will make it move away from it as as the "progress progresses".
No pun intended there.
For this experiment we will use Vladimir's avatar,
because it's a cool image and Vladimir is a cool fellow with a great sence of humor and administrator of foss.rs (Free and Open Source Serbia).
And because if I did that with xenopeek's avatar he would kick my a... cough, cough... lover back.
He probably wouldn't, but it's better safe than sorry people.
We will need an animated gif and since we will use this only for fun, we need to find a quick animation.
I found this website that creates a simple animation from a static image and exports it to a video or animated gif.
https://www.kapwing.com/
Doesn't even requires to upload an image only a url of that image.
And so, after choosing the vibration animation and the adjusting the size of animation it's time to export.
But the image was not as I expected it to be.
It had a white background behind the animation.
After some trial and error, the setting for transparent background appears only after going back to edit the exported animation.
Here's the final result 36x36 pixel jumping avatar.
The appropriate file name for this animated image is vlada.gif
Why, because Vlada is usually a nickname for people that their parents name Vladimir.
The complex Slavic name Vladimir with the meaning "to rule in peace".
Now we need one A.C.M.E. Gtk3 window, one GtkProgressBar, one GtkLabel, one GtkImage and a couple of other widgets.
First we need a file to write the code.
Folks let's name it vlada-bar.c because we want to have a uniform naming and because we will literaly put vlada in the bar.
GtkWindow will be our main container widget. It will a parent of all the other widgets.
Code: Select all
/*Create a window with a title, and a default size*/
window = gtk_application_window_new (app);
gtk_window_set_title (GTK_WINDOW (window),
"Vladimir Fries the Guitar");
gtk_window_set_default_size (GTK_WINDOW (window),
420, 60);
Great-great-great-great-great-great-great-great-great-great-great-great-great-great-grandfather, 16th male ancestor.
We call him "beli orao" (the white eagle). And yes, there is a word for every ancestor in Serbian. https://familypedia.wikia.org/wiki/Serbian_kinship
Next, we need another container, GtkGrid.
Grid will host all the others and adjust their placement.
Code: Select all
/*Create a grid container to store the widgets*/
grid = gtk_grid_new ();
gtk_grid_set_row_spacing (GTK_GRID (grid),
10);
gtk_grid_set_column_spacing (GTK_GRID (grid),
10);
gtk_grid_set_column_homogeneous (GTK_GRID (grid),
TRUE);
gtk_grid_set_row_homogeneous (GTK_GRID (grid),
FALSE);
------------------------------------window---------------------------
|-----------------------progress bar--------------------------------|
|---progress label----------action button---cancel button--|
-------------------------------------------------------------------------
Progress will be in the first row of the window.
In the second row there will be a GtkLabel to show the actual progress and two GtkButtons.
Now progress bar is going to need another container because we want our image on top of it.
We will use GtkOverlay for this and store progress as it's child widget.
window---grid---overlay---progress
Now about an image, the dancing guitar player avatar of our rock star admin.
It will have to have another container.
I know, "not another container please!".
Sorry pals, we need one pixel which will separate the progress from the image and not resize the window to infinity later on.
The image widget must be smaller than the progress bar.
Code: Select all
gtk_box_pack_start (GTK_BOX(box), progress_animation, FALSE, FALSE, 1);
// this one pixel is essential
Now let's attach them.
Overlay and it's children progress bar and image in the first row.
While the label and two buttons go to the lower row.
Code: Select all
gtk_grid_attach (GTK_GRID (grid), progress_overlay, 0, 0, 4, 1);
gtk_grid_attach (GTK_GRID (grid), progress_label, 0, 1, 2, 1);
gtk_grid_attach (GTK_GRID (grid), button1, 2, 1, 1, 1);
gtk_grid_attach_next_to (GTK_GRID (grid), button2, button1, GTK_POS_RIGHT, 1, 1);
/*And attach grid to the window.*/
gtk_container_add (GTK_CONTAINER (window), grid);
We need a size group to ensure the progress bar does not cut the image.
The image dictates the size of of the progress bar, not the way around.
We have to connect to the size-allocate signal so that whenever the image is resized, the progress bar would increase in height.
We also have to connect to the size-allocate signal of the progress bar so that whenever the window is resized, the image would be positioned correctly.
Code: Select all
g_signal_connect (progress_animation,
"size-allocate",
G_CALLBACK (progress_animation_size_allocate),
widgets);
g_signal_connect (progress_bar,
"size-allocate",
G_CALLBACK (progress_bar_size_allocate),
widgets);
The first button starts and stops a new thread at regular intervals of 100 miliseconds.
g_timeout_add_full calls function "fill" which does a few things:
- increments the progress
- sets the porogress bar
- sets the text of the label
There is also a callback function which is called when the fill function returns FALSE or when we click the button1 again.
Code: Select all
guint threadID = 0;
void button1_clicked_cb
(GtkButton *button, app_widgets *widgets)
{
if (threadID == 0)
{
threadID = g_timeout_add_full (0, 100, fill,
widgets, stop_progress_cb);
gtk_button_set_label (button,
"\xe2\x8f\xb8");
}
else
{
GSource *source = g_main_context_find_source_by_id(NULL,
threadID);
if (source)
{
g_source_destroy (source);
}
threadID = 0;
gtk_button_set_label (button,
"\xe2\x8f\xaf");
}
}
I hope you don't mind there are unicode characters in the button label.
We want to keep those small because GtkButton size will increase if the text label size is increased.
And maybe the window size will increase then.
One event leads to another and next thing we wake up having a big hangover in Mexico with nothing but the shirt.
Oh wait, that's only in the movies.
Or is it?! Right, carry on, nothing to see here.
Now we have vlada.gif and vlada-bar.c
Full code here:
Code: Select all
#include <stdio.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <glib.h>
#define ANIMATION "vlada.gif"
/* Vladin bar animated progressbar
gcc -Wall -Wextra -o vlada-bar vlada-bar.c `pkg-config --cflags --libs gtk+-3.0`
depends: gcc libgtk-3-dev (3.24)
*/
guint threadID = 0;
typedef struct {
GtkWidget *progress_bar;
GtkWidget *button1;
GtkWidget *progress_animation;
GtkWidget *progress_label;
GtkStyleProvider *progressbar_style_provider;
} app_widgets;
void destroy_handler (GtkApplication* app, gpointer data)
{
(void) app;
g_application_quit(G_APPLICATION (data));
}
void
stop_progress_cb (gpointer user_data)
{
app_widgets *widgets = (app_widgets *) user_data;
gdouble fraction;
fraction = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR(widgets->progress_bar));
g_print("Action: %.0f %%\n", fraction*100);
}
static gboolean
fill (gpointer user_data)
{
app_widgets *widgets = (app_widgets *) user_data;
GtkAllocation *alloc = g_new(GtkAllocation, 1);
gdouble fraction;
/*Get the current progress*/
fraction = gtk_progress_bar_get_fraction (GTK_PROGRESS_BAR(widgets->progress_bar));
if (fraction > 0.999) {
fraction = 0;
}
/*Increase the bar by 1% each time this function is called*/
if (fraction < 0.999)
fraction += 0.01;
gtk_widget_get_allocation (widgets->progress_bar, alloc);
gtk_widget_set_margin_start(widgets->progress_animation,
alloc->width*fraction);
/*Fill in the bar with the new fraction*/
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(widgets->progress_bar),
fraction);
gchar temp[256];
memset(&temp, 0x0, 256);
if (fraction > 0.999) {
snprintf(temp, 255, "Over and Out: %.0f %%", fraction*100);
gtk_button_set_label (GTK_BUTTON(widgets->button1),
"Repeat");
threadID = 0;
}
else {
snprintf(temp, 255, "Action: %.0f %%", fraction*100);
}
gtk_label_set_text (GTK_LABEL(widgets->progress_label), temp);
g_free(alloc);
/*Ensures that the fraction stays below 1.0*/
if (fraction < 0.999)
return TRUE;
return FALSE;
}
void
button1_clicked_cb (GtkButton *button,
app_widgets *widgets)
{
if (threadID == 0)
{
threadID = g_timeout_add_full (0, 100, fill,
widgets, stop_progress_cb);
gtk_button_set_label (button,
"\xe2\x8f\xb8");
}
else
{
GSource *source = g_main_context_find_source_by_id(NULL,
threadID);
if (source)
{
g_source_destroy (source);
}
threadID = 0;
gtk_button_set_label (button,
"\xe2\x8f\xaf");
}
}
static void
progress_bar_size_allocate (GtkWidget *progress_bar,
GdkRectangle *allocation,
gpointer user_data)
{
(void) progress_bar;
app_widgets *widgets = (app_widgets *) user_data;
gdouble fraction;
/*Get the current progress*/
fraction = gtk_progress_bar_get_fraction
(GTK_PROGRESS_BAR(widgets->progress_bar));
if (fraction == 0)
{
fraction = 0.01;
}
/*Set the margin of animation when the window width changes*/
gtk_widget_set_margin_start(widgets->progress_animation,
allocation->width*fraction);
if (fraction > 0.999)
gtk_widget_set_margin_start(widgets->progress_animation,
1);
}
static void
progress_animation_size_allocate (GtkWidget *animation,
GdkRectangle *allocation,
gpointer user_data)
{
(void) animation;
app_widgets *widgets = (app_widgets *) user_data;
GtkAllocation *progress_bar_allocation = g_new(GtkAllocation, 1);
char *css_text;
/*Get progress bar allocation*/
gtk_widget_get_allocation (widgets->progress_bar,
progress_bar_allocation);
/*weird but working*/
if ((progress_bar_allocation->height = allocation->height) ||
(progress_bar_allocation->height < allocation->height))
{
css_text = g_strdup_printf ("progressbar trough,\n"
"progressbar progress\n"
"{\n"
" min-height: %dpx;\n"
"}\n",
allocation->height);
gtk_css_provider_load_from_data (GTK_CSS_PROVIDER
(widgets->progressbar_style_provider),
css_text, -1, NULL);
}
g_free(progress_bar_allocation);
g_free (css_text);
}
static void
activate (GtkApplication *app,
gpointer user_data)
{
(void) user_data;
GtkSizeGroup *size_group;
GtkWidget *window;
GtkWidget *grid;
GtkWidget *button2;
GtkWidget *progress_overlay;
GdkPixbufAnimation *animation;
GError *error = NULL;
GtkWidget *box;
app_widgets *widgets = g_slice_new(app_widgets);
gdouble fraction = 0.0;
/*Create a window with a title, and a default size*/
window = gtk_application_window_new (app);
gtk_window_set_title (GTK_WINDOW (window),
"Vladimir Fries the Guitar");
gtk_window_set_default_size (GTK_WINDOW (window),
420, 60);
/*Create a grid container to store the widgets*/
grid = gtk_grid_new ();
gtk_grid_set_row_spacing (GTK_GRID (grid),
10);
gtk_grid_set_column_spacing (GTK_GRID (grid),
10);
gtk_grid_set_column_homogeneous (GTK_GRID (grid),
TRUE);
gtk_grid_set_row_homogeneous (GTK_GRID (grid),
FALSE);
/*Create a progressbar*/
widgets->progress_bar = gtk_progress_bar_new();
gtk_progress_bar_set_inverted (GTK_PROGRESS_BAR(widgets->progress_bar),
FALSE);
gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR(widgets->progress_bar),
FALSE);
/*Fill in the given fraction of the bar.
It has to be between 0.0-1.0 inclusive*/
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (widgets->progress_bar), fraction);
widgets->progress_label = gtk_label_new ("Ready to Rock");
/*Create buttons*/
widgets->button1 = gtk_button_new_with_label ("\xe2\x96\xb6");
button2 = gtk_button_new_with_label ("Cancel");
/*Create an overlay*/
progress_overlay = gtk_overlay_new ();
gtk_widget_set_hexpand (progress_overlay, TRUE);
gtk_widget_set_vexpand (progress_overlay, FALSE);
gtk_container_add (GTK_CONTAINER (progress_overlay),
widgets->progress_bar);
/*Create an animation pixbuf*/
animation = gdk_pixbuf_animation_new_from_file(ANIMATION, &error);
if (error) {
g_warning("No image found\n*ERROR %s\n", error->message);
destroy_handler(NULL, app);
}
widgets->progress_animation = gtk_image_new_from_animation (animation);
gtk_widget_set_vexpand(widgets->progress_bar, FALSE);
gtk_widget_set_name (widgets->progress_animation,
"progress-animation");
/*Image has to be in a (box) container with a one pixel padding*/
/*create a box container for the image*/
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start (GTK_BOX(box), widgets->progress_animation, FALSE, FALSE, 1); // this one pixel is essential
gtk_widget_set_halign (widgets->progress_animation,
GTK_ALIGN_START);
gtk_overlay_add_overlay (GTK_OVERLAY (progress_overlay),
box);
gtk_overlay_set_overlay_pass_through (GTK_OVERLAY (progress_overlay),
box, TRUE);
/*Size group and add widgets*/
size_group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);
gtk_size_group_add_widget (size_group, widgets->progress_bar);
gtk_size_group_add_widget (size_group, box);
/*Create a style provider for the progessbar*/
widgets->progressbar_style_provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ());
gtk_style_context_add_provider (gtk_widget_get_style_context (widgets->progress_bar),
widgets->progressbar_style_provider,
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_signal_connect (widgets->progress_animation,
"size-allocate",
G_CALLBACK (progress_animation_size_allocate),
widgets);
g_signal_connect (widgets->progress_bar,
"size-allocate",
G_CALLBACK (progress_bar_size_allocate),
widgets);
gtk_grid_attach (GTK_GRID (grid), progress_overlay, 0, 0, 4, 1);
gtk_grid_attach (GTK_GRID (grid), widgets->progress_label, 0, 1, 2, 1);
gtk_grid_attach (GTK_GRID (grid), widgets->button1, 2, 1, 1, 1);
gtk_grid_attach_next_to (GTK_GRID (grid), button2, widgets->button1, GTK_POS_RIGHT, 1, 1);
gtk_container_add (GTK_CONTAINER (window), grid);
gtk_container_set_border_width(GTK_CONTAINER(grid),12);
gtk_container_set_border_width(GTK_CONTAINER(window),5);
g_signal_connect (G_OBJECT(window), "destroy",
G_CALLBACK (destroy_handler), app);
g_signal_connect (widgets->button1, "clicked",
G_CALLBACK (button1_clicked_cb), widgets);
g_signal_connect (button2, "clicked",
G_CALLBACK (destroy_handler), app);
gtk_widget_show_all (window);
}
int
main (int argc, char **argv)
{
GtkApplication *app;
int status;
app = gtk_application_new ("org.gtk.vlada_bar", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
status = g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return status;
}
Install dependancies:
Code: Select all
apt-install gcc libgtk-3-dev
Make sure it's at least version 3.24, or 3.22 maybe I didn't test it much...
Anyway, cd to the path where you keep the files and run:
Code: Select all
gcc -Wall -Wextra -o vlada-bar vlada-bar.c `pkg-config --cflags --libs gtk+-3.0`
Code: Select all
./vlada-bar
P.S.
Playing "Twisted Sister - I Wanna Rock" is obligatory with this.