I have a GTK+ application written in C that loads a matrix of animated GIF files. These GtkImages automatically run the animation when they are loaded and then stop when the animation is completed. How would I restart the animation of each GtkImage containing the GIF and are signals generated when the animation is complete?
Thank you.
EDIT :
Would the use of gdk_pixbuf_animation_get_iter() described here make this possible?
Full code is provided below.
/*
* Compile me with:
* gcc -o reels reels.c $(pkg-config --cflags --libs gtk+-2.0 gmodule-2.0)
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
/* GTK */
#include <gdk/gdkx.h>
#include <gtk/gtk.h>
/**** prototypes ****/
static void destroy (GtkWidget*, gpointer);
GdkPixbuf *create_pixbuf(const gchar * filename);
GtkWidget *SetupWindow(gchar *data, const gchar *filename);
static void destroy (GtkWidget *window, gpointer data);
void btnSpin_clicked(GtkWidget *button, gpointer data);
void btnExit_clicked(GtkWidget *button, gpointer data);
/********************/
GtkWidget *images[3][5];
static void destroy (GtkWidget *window, gpointer data)
{
gtk_main_quit ();
}
void btnSpin_clicked(GtkWidget *button, gpointer data)
{
printf("Spin Button pressed.\n");
return;
}
void btnExit_clicked(GtkWidget *button, gpointer data)
{
gtk_main_quit();
return;
}
GtkWidget *SetupWindow(gchar *data, const gchar *filename)
{
GdkPixmap *background;
GdkPixbuf *pixbuf;
GdkScreen *ourscreen;
GdkColormap *colormap;
GtkStyle *style;
GdkColor fg;
GdkColor bg;
GError *error = NULL;
GdkRectangle *rect;
GtkWidget *window;
pixbuf = gdk_pixbuf_new_from_file (filename,&error);
if (error != NULL) {
if (error->domain == GDK_PIXBUF_ERROR) {
g_print ("Pixbuf Related Error:\n");
}
if (error->domain == G_FILE_ERROR) {
g_print ("File Error: Check file permissions and state:\n");
}
g_printerr ("%s\n", error[0].message);
}
gdk_pixbuf_render_pixmap_and_mask (pixbuf, &background, NULL, 0);
style = gtk_style_new ();
style->bg_pixmap[0] = background;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), data);
// gtk_window_maximize(GTK_WINDOW(window));
gtk_window_set_modal (GTK_WINDOW (window),TRUE);
gtk_window_set_default_size(GTK_WINDOW(window),628,530);
gtk_widget_set_style (GTK_WIDGET(window), GTK_STYLE(style));
gtk_window_set_position(GTK_WINDOW(window),GTK_WIN_POS_CENTER_ALWAYS);
gtk_container_set_border_width(GTK_CONTAINER(window), 0);
//gtk_window_set_resizable(GTK_WINDOW(window), (gboolean) FALSE);
gtk_window_set_decorated( GTK_WINDOW(window), FALSE );
return(window);
}
int main (int argc, char *argv[])
{
GdkPixbufAnimation *animation;
GtkWidget *image;
int x,y;
GdkPixbuf *pixBuf;
GdkPixmap *pixMap;
gchar filename[20];
GtkWidget *btnSpin, *btnExit;
GtkWidget *frame; /* for absolute positionining of widgets */
GtkWidget *window;
int posx, posy;
gtk_init (&argc, &argv);
window = SetupWindow("Demo", "background.gif");
g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);
frame = gtk_fixed_new();
gtk_container_add(GTK_CONTAINER(window), frame);
btnSpin = gtk_button_new_with_label("Spin");
gtk_widget_set_size_request(btnSpin, 80, 35);
gtk_fixed_put(GTK_FIXED(frame), btnSpin, 229, 485);
g_signal_connect(G_OBJECT( btnSpin ), "clicked", G_CALLBACK(btnSpin_clicked), NULL );
btnExit = gtk_button_new_with_label("Exit");
gtk_widget_set_size_request(btnExit, 80, 35);
gtk_fixed_put(GTK_FIXED(frame), btnExit, 320, 485);
g_signal_connect(G_OBJECT( btnExit ), "clicked", G_CALLBACK(btnExit_clicked), NULL );
/* setup animated gifs */
for( y = 0; y < 3; y++ )
{
posy = (y*120) + 20;
for( x = 0; x < 5; x++ )
{
posx = (x*120) + 20;
/* set each Image widget to spin GIF */
sprintf( filename,"%d-%d.gif", y+1,x+1 );
images[y][x] = gtk_image_new_from_file(filename);
gtk_fixed_put(GTK_FIXED(frame), images[y][x], posx, posy);
}
}
gtk_widget_show_all(window);
gtk_main ();
return 0;
}
After taking a look at the GtkImage source code, I am afraid there is no signal that is generated when the animation is completed. However, you should be able to restart the animation by calling gtk_image_set_from_file() or gtk_image_set_from_animation().
To completely solve your initial issue, I propose you create a subclass of GtkImage. It should behave exactly like GtkImage, except that animation_timeout should send a signal if delay < 0 around line 1315.
Information about creating a subclass of a GObject (note that GtkImage is a GObject) can be found here.
Based on user1202136's answer the following code snippets show the solution. I post this in case others find it useful.
The basic idea is to use GdkPixbufAnimation, gdk_pixbuf_animation_new_from_file and gtk_image_set_from_animation
The following code snippet(s) show how to do it.
GtkWidget *images[3][5];
GdkPixbufAnimation *animations[3][5];
/* Initial setup of animated gifs */
for( y = 0; y < 3; y++ )
{
for( x = 0; x < 5; x++ )
{
/* set each Image widget to spin GIF */
sprintf( filename,"%d-%d.gif", y+1,x+1 );
images[y][x] = gtk_image_new();
animations[y][x] = gdk_pixbuf_animation_new_from_file ( filename , &error);
gtk_image_set_from_animation (GTK_IMAGE(images[y][x]), animations[y][x]);
gtk_fixed_put(GTK_FIXED(frame), images[y][x], (x*120) + 20, (y*120) + 20);
}
}
/* now to restart the animations use the images and animations array already stored in memory,
no need to re-read the animations from disk so this happens quickly
*/
/* restart animated gifs */
for( y = 0; y < 3; y++ )
{
for( x = 0; x < 5; x++ )
{
gtk_image_set_from_animation(GTK_IMAGE(images[y][x]), animations[y][x]);
}
}
Related
I'm trying to use cairo to draw some arcs but gcc warns me that gdk_cairo_create() is deprecated. Use gdk_window_begin_draw_frame() and gdk_drawing_context_get_cairo_context() instead.
To get around this I did some research and found out that for gdk_window_begin_draw_frame() I need "GdkWindow".I've always been using GtkWidget for my windows so I need to convert "GtkWidget" to "GdkWindow", but gtk_widget_get_window() returns NULL and causes segfault.
#include <gtk/gtk.h>
#include <cairo.h>
void main(int argc , char **argv){
gtk_init(&argc , &argv);
GtkWidget *win;
GdkWindow *gdkwin;
GdkDrawingContext *dc;
cairo_region_t *region;
cairo_t *cr;
win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
region = cairo_region_create();
gdkwin = gtk_widget_get_window(GTK_WIDGET(win));
//Here gdkwin should contain a GdkWindow but it's NULL.
gc = gdk_window_begin_draw_frame(gdkwin , (const cairo_region_t*)®ion);
...
...
Here's the runtime errors:
(a.out:6852): Gdk-CRITICAL **: 23:53:06.042: gdk_window_begin_draw_frame: assertion 'GDK_IS_WINDOW (window)' failed
(a.out:6852): Gdk-CRITICAL **: 23:53:06.042: gdk_drawing_context_get_cairo_context: assertion 'GDK_IS_DRAWING_CONTEXT (context)' failed
Segmentation fault
I want to get a cairo object and use it for cairo_arc().
Thanks.Best regards.
The below is the complete source code to get Cairo working under GTK 3. It should be compilable as is.
As the others already pointed out, you have to use the draw signal to make things work.
#include <gtk/gtk.h>
#include <cairo.h>
// ------------------------------------------------------------
gboolean on_draw (GtkWidget *widget,
GdkEventExpose *event,
gpointer data)
{
// "convert" the G*t*kWidget to G*d*kWindow (no, it's not a GtkWindow!)
GdkWindow* window = gtk_widget_get_window(widget);
cairo_region_t * cairoRegion = cairo_region_create();
GdkDrawingContext * drawingContext;
drawingContext = gdk_window_begin_draw_frame (window,cairoRegion);
{
// say: "I want to start drawing"
cairo_t * cr = gdk_drawing_context_get_cairo_context (drawingContext);
{ // do your drawing
cairo_move_to(cr, 30, 30);
cairo_set_font_size(cr,15);
cairo_show_text(cr, "hello world");
}
// say: "I'm finished drawing
gdk_window_end_draw_frame(window,drawingContext);
}
// cleanup
cairo_region_destroy(cairoRegion);
return FALSE;
}
// ------------------------------------------------------------
int main (int argc, char * argv[]) {
gtk_init(&argc, &argv);
GtkWindow * window;
{ // window setup
window = (GtkWindow*)gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (window, 200, 200);
gtk_window_set_position (window, GTK_WIN_POS_CENTER);
gtk_window_set_title (window, "Drawing");
g_signal_connect(window, "destroy", gtk_main_quit, NULL);
}
// create the are we can draw in
GtkDrawingArea* drawingArea;
{
drawingArea = (GtkDrawingArea*) gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER(window), (GtkWidget*)drawingArea);
g_signal_connect((GtkWidget*)drawingArea, "draw", G_CALLBACK(on_draw), NULL);
}
gtk_widget_show_all ((GtkWidget*)window);
gtk_main();
return 0;
}
// ------------------------------------------------------------
In GTK+ 3, you're supposed to do your drawing in response to the draw signal. Doing it in the main makes no sense (the widgets have just been created, but initializing them further in done when running the main event loop).
Please read: https://developer.gnome.org/gtk3/stable/chap-drawing-model.html
The Dark Trick's program is complete.
He uses the functions as follows,
GdkWindow* window = gtk_widget_get_window (widget);
cairo_region_t *cairoRegion = cairo_region_create();
GdkDrawingContext *drawingContext;
drawingContext = gdk_window_begin_draw_frame (window, cairoRegion);
cairo_t *cr = gdk_drawing_context_get_cairo_context (drawingContext);
But I am using the the functions as follows,
GdkWindow *window = gtk_widget_get_window(widget);
cairo_rectangle_int_t cairoRectangle = {0, 0, 200, 200};
cairo_region_t *cairoRegion = cairo_region_create_rectangle (&cairoRectangle);
GdkDrawingContext *drawingContext;
drawingContext = gdk_window_begin_draw_frame (window,cairoRegion);
cairo_t *cr = gdk_drawing_context_get_cairo_context (drawingContext);
This worked, but I can not understand the differencies, for I am an OldUrologist.
I'm not positive, but I think you're trying to get the GdkWindow before it is ready. I think you need to connect to the window's "realize" signal, and only when that signal has been emitted should you try to access the underlying GdkWindow.
#include <gtk/gtk.h>
#include <cairo.h>
void OnWindowRealize(GtkWidget *pWidget, gpointer data)
{
GdkWindow *pUnderlyingWindow = gtk_widget_get_window(pWidget);
cairo_region_t *region = cairo_region_create();
GdkDrawingContext *gc = gdk_window_begin_draw_frame(pUnderlyingWindow, (const cairo_region_t*)®ion);
//etc...
}
void main(int argc , char **argv)
{
gtk_init(&argc , &argv);
GtkWidget *win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(G_OBJECT(win), "realize", OnWindowRealize, NULL);
//etc...
}
I am trying to modify the cairo graphics rendering I found in http://zetcode.com/gfx/cairo/basicdrawing/ so that it updates the graphics when I click the drawing area with the left mouse button. Currently the code in the website uses the right mouse button click to update the graphics.
I have tried adding gtk_widget_queue_draw(widget); to gboolean clicked(), when event->button == 1:
#include <cairo.h>
#include <gtk/gtk.h>
static void do_drawing(cairo_t *);
struct {
int count;
double coordx[100];
double coordy[100];
} glob;
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
gpointer user_data)
{
do_drawing(cr);
return FALSE;
}
static void do_drawing(cairo_t *cr)
{
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 0.5);
int i, j;
for (i = 0; i <= glob.count - 1; i++ ) {
for (j = 0; j <= glob.count - 1; j++ ) {
cairo_move_to(cr, glob.coordx[i], glob.coordy[i]);
cairo_line_to(cr, glob.coordx[j], glob.coordy[j]);
}
}
glob.count = 0;
cairo_stroke(cr);
}
static gboolean clicked(GtkWidget *widget, GdkEventButton *event,
gpointer user_data)
{
if (event->button == 1) {
glob.coordx[glob.count] = event->x;
glob.coordy[glob.count++] = event->y;
gtk_widget_queue_draw(widget);
}
if (event->button == 3) {
gtk_widget_queue_draw(widget);
}
return TRUE;
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
glob.count = 0;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER(window), darea);
gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
g_signal_connect(G_OBJECT(darea), "draw",
G_CALLBACK(on_draw_event), NULL);
g_signal_connect(window, "destroy",
G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(window, "button-press-event",
G_CALLBACK(clicked), NULL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
gtk_window_set_title(GTK_WINDOW(window), "Lines");
gtk_widget_show_all(window);
gtk_main();
return 0;
}
However, this does not work since now the graphics aren't updated at all.
Is there a way of updating the graphics by pressing the left mouse button (button 1) and simultaneously carry out inserting the coordinates to struct glob as before?
Thanks!
You are setting glob.count = 0 after drawing the lines, however glob.count is only updated by one in the clicked() callback, so it will never draw more than a single line. Also the first line is from point (glob.coordx[0], glob.coordy[0]) to (glob.coordx[0], glob.coordy[0]) (that is: it starts and ends at the same point) so it has no length, and will not be visible.
Solution: remove the line glob.count = 0; from do_drawing().
g_signal_connect(window, "button-press-event", G_CALLBACK(clicked), NULL);
When your callback is called, the window (which emits "button-press-event") is passed as first parameter. The queue_draw call queues redraw of window. However, redraw of DrawingArea is not requested and will not happen.
You can:
Queue redraw of DrawingArea
Connect to "button-press-event" of DrawingArea
Without any changes of code: press a mouse button and resize the window. Gtk will see, that DrawingArea has changed (it's resized too) and queue redraw of this widget.
Where, in the following zetcode, is the cairo context cr declared?
#include <cairo.h>
#include <gtk/gtk.h>
static void do_drawing(cairo_t *);
struct {
int count;
double coordx[100];
double coordy[100];
} glob;
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
gpointer user_data)
{
do_drawing(cr);
return FALSE;
}
static void do_drawing(cairo_t *cr)
{
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 0.5);
int i, j;
for (i = 0; i <= glob.count - 1; i++ ) {
for (j = 0; j <= glob.count - 1; j++ ) {
cairo_move_to(cr, glob.coordx[i], glob.coordy[i]);
cairo_line_to(cr, glob.coordx[j], glob.coordy[j]);
}
}
glob.count = 0;
cairo_stroke(cr);
}
static gboolean clicked(GtkWidget *widget, GdkEventButton *event,
gpointer user_data)
{
if (event->button == 1) {
glob.coordx[glob.count] = event->x;
glob.coordy[glob.count++] = event->y;
}
if (event->button == 3) {
gtk_widget_queue_draw(widget);
}
return TRUE;
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
glob.count = 0;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER(window), darea);
gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);
g_signal_connect(G_OBJECT(darea), "draw",
G_CALLBACK(on_draw_event), NULL);
g_signal_connect(window, "destroy",
G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(window, "button-press-event",
G_CALLBACK(clicked), NULL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
gtk_window_set_title(GTK_WINDOW(window), "Lines");
gtk_widget_show_all(window);
gtk_main();
return 0;
}
Is cairo context cr automatically declared in the code and associated with the darea (unfortunate name for drawing area) when we call the function
g_signal_connect(G_OBJECT(darea), "draw",
G_CALLBACK(on_draw_event), NULL);
?
The widget will emit the signal and pass it's internal cairo context. When you connect a callback to handle the signal, cairo context is sent by the widget, you receive it and work on it.
Draw signal belongs to Gtk Widget class:
gboolean user_function (GtkWidget *widget, CairoContext *cr, gpointer user_data)
From the draw documentation:
This signal is emitted when a widget is supposed to render itself. The
widget 's top left corner must be painted at the origin of the passed
in context and be sized to the values returned by
gtk_widget_get_allocated_width() and
gtk_widget_get_allocated_height().
Signal handlers connected to this signal can modify the cairo context
passed as cr in any way they like and don't need to restore it. The
signal emission takes care of calling cairo_save() before and
cairo_restore() after invoking the handler.
The signal handler will get a cr with a clip region already set to the
widget's dirty region, i.e. to the area that needs repainting.
Complicated widgets that want to avoid redrawing themselves completely
can get the full extents of the clip region with
gdk_cairo_get_clip_rectangle(), or they can get a finer-grained
representation of the dirty region with
cairo_copy_clip_rectangle_list().
I also hated the CairoContext *cr is
not defined but is simply cairo_t *cr
Currently I am writing a program in C, on a linux system (Raspberry Pi to be exact) which should draw to a GTK window using Cairo. I've been following the tutorial at: http://zetcode.com/gfx/cairo/ . But it is way to vague with it's explanations at certain points.
It does not explain two points that I really need:
I can't figure out a way to draw to the window with a proper function call.
It removes what is already drawn.
I need a piece of code that does some simple things, in a very Object-Oriented manner:
Draw lines to a GTK window with a function call, given X and Y for both starting and end point;
Do not remove what is previously drawn;
All initializations of variables and the window should be outside the main function.
So basically something similar to this:
#include <cairo.h>
#include <gtk/gtk.h>
void drawLine(int xStart, int yStart, int yEnd, int xEnd) {
//Drawing code here.
}
void initializeCairo() {
//Insert cairo initialization.
}
void initializeGTK() {
//Insert GTK initialization.
}
/*If needed a general initializer for both cairo and GTK*/
void initialize() {
//Insert general initialization.
}
int main (int argc, char *archv[]) {
intializeGTK();
initializeCairo();
if(doSomething) {
drawLine(10, 10, 20, 20);
}
}
If it could be explained what a method does (in proper English please, not a reference to the documentation), that'd be absolutely great.
Also please include the gcc build command used.
Thanks in advance!
The answers from andlabs are fine. Here is in addition a short (although not entirely elegant) example. It will "kind of remember" the last NUM lines - creation/resize/activation/deactivation of the window will trigger a "draw" of the content. A Next button click will add a new line to the output. Check also the command-line output for an update of
the array values that are drawn.
#include <gtk/gtk.h>
#include <glib/gprintf.h>
#include <cairo.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#define NUM 3
typedef struct {
GtkApplication *app;
GtkWidget *window;
GtkWidget *button;
GtkWidget *da;
cairo_t* cr;
gboolean redraw;
gint xsize;
gint ysize;
} appWidgets;
gboolean drawEvent (GSimpleAction *action, GVariant *parameter, gpointer data);
void nextCallback (GtkWidget *widget, gpointer data);
void nextCallback (GtkWidget *widget, gpointer data)
{
appWidgets *w = (appWidgets*) data;
static gint cnt = 0;
static gdouble x[NUM], y[NUM], u[NUM], v[NUM];
// determine the next coordinates for a line
if (w->redraw == FALSE) {
x[cnt] = g_random_double();
y[cnt] = g_random_double();
u[cnt] = g_random_double();
v[cnt] = g_random_double();
}
w->cr = gdk_cairo_create (gtk_widget_get_window (w->da));
// map (0,0)...(xsize,ysize) to (0,0)...(1,1)
cairo_translate (w->cr, 0, 0);
cairo_scale (w->cr, w->xsize, w->ysize);
// set linewidth
cairo_set_line_width (w->cr, 0.005);
// draw the lines
for (int k = 0; k < NUM; k++) {
cairo_move_to (w->cr, x[k], y[k]);
cairo_line_to (w->cr, u[k], v[k]);
cairo_stroke (w->cr);
g_print("k=%d:(%1.2lf,%1.2lf).(%1.2lf,%1.2lf) ",
k, x[k], y[k], u[k], v[k]);
}
g_print("\n");
cairo_destroy (w->cr);
if (w->redraw == FALSE) {
cnt++;
if (cnt == NUM)
cnt = 0;
}
}
gboolean drawEvent (GSimpleAction *action, GVariant *parameter, gpointer data)
{
appWidgets *w = (appWidgets*) data;
w->xsize = gtk_widget_get_allocated_width (w->da);
w->ysize = gtk_widget_get_allocated_height (w->da);
w->redraw = TRUE;
nextCallback (NULL, w);
w->redraw = FALSE;
return TRUE;
}
void activate (GtkApplication *app, gpointer data)
{
GtkWidget *box;
appWidgets *w = (appWidgets*) data;
w->window = gtk_application_window_new (w->app);
gtk_window_set_application (GTK_WINDOW (w->window), GTK_APPLICATION (w->app));
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add (GTK_CONTAINER (w->window), box);
w->da = gtk_drawing_area_new();
gtk_widget_set_size_request (w->da, 400, 400);
gtk_box_pack_start (GTK_BOX (box), w->da, TRUE, TRUE, 0);
g_signal_connect (w->da, "draw", G_CALLBACK (drawEvent), (gpointer) w);
w->button = gtk_button_new_with_label ("Next");
g_signal_connect (G_OBJECT (w->button), "clicked", G_CALLBACK (nextCallback),
(gpointer) w);
gtk_box_pack_start (GTK_BOX (box), w->button, FALSE, TRUE, 0);
gtk_widget_show_all (GTK_WIDGET (w->window));
w->redraw = FALSE;
}
int main (int argc, char *argv[])
{
gint status;
appWidgets *w = g_malloc (sizeof (appWidgets));
w->app = gtk_application_new ("org.gtk.example", G_APPLICATION_FLAGS_NONE);
g_signal_connect (w->app, "activate", G_CALLBACK (activate), (gpointer) w);
status = g_application_run (G_APPLICATION (w->app), argc, argv);
g_object_unref (w->app);
g_free (w);
w = NULL;
return status;
}
Build the program as usual:
gcc example.c -o example `pkg-config --cflags --libs gtk+-3.0`
Here is my code.
#include <gtk/gtk.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#define MAX_LENGTH 8
#define WIDGET_NUM 3
typedef struct Widget
{
GtkWidget *window;
GtkWidget *button[WIDGET_NUM];
GtkWidget *entry[WIDGET_NUM];
GtkWidget *label[WIDGET_NUM];
GtkWidget *grid;
pthread_t pid[WIDGET_NUM];
int button_num;
}Widget;
void num_2_time(int num, char *buf)
{
int h = num / 3600;
int m = num % 3600 / 60;
int s = num % 60;
sprintf(buf, "%d:%d:%d", h, m, s);
}
void *wait_4_waking(void *arg)
{
Widget *window = (Widget*)arg;
int input_num, window_num = window->button_num;
const char *text;
char buf[MAX_LENGTH * 2];
text = gtk_entry_get_text(GTK_ENTRY(window->entry[window_num]));
input_num = atoi(text);
gtk_entry_set_text(GTK_ENTRY(window->entry[window_num]), "");
while (input_num >= 0)
{
num_2_time(input_num, buf);
//Segmentation fault
gtk_label_set_text(GTK_LABEL(window->label[window_num]), buf);
sleep(1);
input_num--;
}
return NULL;
}
void button_clicked_0(GtkWidget *widget, gpointer data)
{
Widget *window = (Widget*)data;
window->button_num = 0;
printf("%u\n", window->pid[0]);
if (window->pid[0] > 0)
{
pthread_cancel(window->pid[0]);
}
pthread_create(window->pid, NULL, wait_4_waking, data);
}
void button_clicked_1(GtkWidget *widget, gpointer data)
{
Widget *window = (Widget*)data;
window->button_num = 1;
if (window->pid[1] > 0)
{
pthread_cancel(window->pid[1]);
}
pthread_create(window->pid+1, NULL, wait_4_waking, data);
}
void button_clicked_2(GtkWidget *widget, gpointer data)
{
Widget *window = (Widget*)data;
window->button_num = 2;
if (window->pid[2] > 0)
{
pthread_cancel(window->pid[2]);
}
pthread_create(window->pid+2, NULL, wait_4_waking, data);
}
int main (int argc, char **argv)
{
Widget window;
int i;
void (*button_clicked[WIDGET_NUM])(GtkWidget*, gpointer) = {
button_clicked_0, button_clicked_1, button_clicked_2
};
gtk_init(&argc, &argv);
window.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window.window), "Window");
gtk_window_set_default_size (GTK_WINDOW (window.window), 400, 200);
window.grid = gtk_grid_new();
for (i = 0; i < WIDGET_NUM; i++)
{
window.entry[i] = gtk_entry_new();
window.label[i] = gtk_label_new("0:0:0");
window.button[i] = gtk_button_new_with_label("Go!");
window.pid[i] = 0;
gtk_entry_set_max_length(GTK_ENTRY(window.entry[i]), MAX_LENGTH);
gtk_grid_attach(GTK_GRID(window.grid), window.entry[i], 0, i, 1, 1);
gtk_grid_attach(GTK_GRID(window.grid), window.button[i], 1, i, 1, 1);
gtk_grid_attach(GTK_GRID(window.grid), window.label[i], 2, i, 1, 1);
}
gtk_container_add (GTK_CONTAINER (window.window), window.grid);
for (i = 0; i < WIDGET_NUM; i++)
{
g_signal_connect (window.button[i], "clicked", G_CALLBACK (button_clicked[i]), (gpointer)&window);
}
g_signal_connect (window.window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
gtk_widget_show_all (window.window);
gtk_main();
return 0;
}
Building command:
gcc -o 1 test2.c `pkg-config --libs --cflags gtk+-3.0` -pthread -g
This error occurred when I clicked The button, but it didn't occur all the time. Before it occurred, I get an information below.
(1:18144): Pango-CRITICAL **: pango_layout_get_pixel_extents: assertion 'PANGO_IS_LAYOUT (layout)' failed
(1:18144): Pango-CRITICAL **: pango_layout_get_iter: assertion 'PANGO_IS_LAYOUT (layout)' failed
And then segmentation fault.
I checked its information by dmesg command. And I got this.
[ 703.437988] 1[6358]: segfault at 10 ip 00007f9b07ac3f91 sp 00007ffe3bd53790 error 4 in libpango-1.0.so.0.3800.1[7f9b07aa0000+48000]
How can I do for this error?
You are calling a Gtk+ widget function from another thread. Gtk+ is not thread safe so don't do that.
Your best option is to avoid threads: design your code so that the main loop is never blocked for long periods. Usually when I see threading problems, the use of threads is unnecessary and the whole mess could have been avoided by a cleaner design.
If the code can't be designed in a better way, then you'll need to use e.g. g_main_context_invoke() in your other thread to invoke a function in the main thread: that function can then modify the Gtk+ widget state safely. Be careful not to make mistakes with lifetimes of pointers that you share between threads.