I'm working with Cairo and GTK3.0 and I got problems that I have no clue on how to solve.
Currently I have 2 oddities that I do not have the knowledge of to solve.
The draw event is called whenever the terminal floats over the drawing window.
The program does not pas the gtk_main(); function.
I will provide my code below, which is very basic, it is based on this: http://zetcode.com/gfx/cairo/cairobackends/ , the GTK window part.
I eventually just need a window that I can call the draw event on whenever I want in my code.
#include <cairo.h>
#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int xStart;
int yStart;
int xEnd;
int yEnd;
} lineData;
int i = 0;
int gonnaDraw = 0;
lineData *lines;
int lineSize = 0;
/**
* The drawing with the cairo elements is done here.
* The new line is saved to an array.
* Eventually there should be a loop that draws all lines in the array.
*/
static void do_drawing(cairo_t *cr) {
printf("I'm endless, somehow.\n");
if(lineSize != 0) { //We only want to draw in the infinite for loop, not before it.
cairo_set_source_rgb(cr, 255, 0, 0);
cairo_set_line_width(cr, 1.0);
lineData newLine = { 10.0 + i, 50.0 + i, 100.0 + i, 50.0 + i };
//lines[lineSize - 1] = newLine;
cairo_move_to(cr, newLine.xStart, newLine.xEnd);
cairo_line_to(cr, newLine.yStart, newLine.yEnd);
cairo_stroke(cr);
i = i + 10;
printf("i: %d\n", i);
gonnaDraw = 1;
}
}
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data) {
do_drawing(cr);
return FALSE;
}
int main (int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *darea;
printf("1\n");
gtk_init(&argc, &argv);
printf("2\n");
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
printf("3\n");
darea = gtk_drawing_area_new();
printf("4\n");
gtk_container_add(GTK_CONTAINER(window), darea);
printf("5\n");
g_signal_connect(G_OBJECT(darea), "draw", G_CALLBACK(on_draw_event), NULL);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
printf("6\n");
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 800, 800);
gtk_window_set_title(GTK_WINDOW(window), "GTK window");
printf("7\n");
gtk_widget_show_all(window);
printf("8\n");
gtk_main(); // If I'm removed I got no drawing window.
printf("9\n"); // I do not show up.
lines = (lineData *) malloc(lineSize * sizeof(lineData));
for(;;) {
if(gonnaDraw == 1) {
lineSize++;
printf("lineSize: %d\n", lineSize);
lines = (lineData *) realloc(lines, lineSize * sizeof(lineData));
gtk_widget_queue_draw(window);
gonnaDraw = 0;
}
}
return 0;
}
Compiled with standard method.
You almost understand the GTK+ drawing model now. Question 1 is a consequence of it: when another window goes over yours, the window system tells GTK+ that that area needs to be redrawn. This is what i meant when I talked about clipping.
Now the other part is understanding the GTK+ event model. All window systems operate in such a way that all programs run on a loop that consists of while the program is alive, get a message from the window system and act on it. This is what gtk_main() does. gtk_main() not returning is normal; it doesn't return until a call to gtk_main_quit(). Apart from using signals to perform actions when a widget is interacted with, there are two ways you can "hook into" the main loop: g_timeout_add(), which runs a function on schedule, and g_idle_add(), which runs a function when next possible. The former is useful if you want something to happen every so often; the latter is useful if you want a worker thread to signal the UI to update. The GLib documentation on main loops will tell you more. I'm still not sure what your end goal is, so I suppose you can try playing with both to see what happens.
– andlabs
Related
I'm fairly new to GTK and Cairo, and I need to write code that will allow it to draw my data in a while(1) loop each time gtk_widget_queue_draw is called. Here's my attempt at it:
#include <cairo.h>
#include <gtk/gtk.h>
#include <unistd.h>
int scrH = 892, // Window dimensions.
scrW = 1427,
type = 0; // What kind of lines to draw.
int on_draw_event(GtkWidget *widget,
cairo_t *cr,
gpointer user_data) {
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_paint(cr);
cairo_set_line_width(cr, 0.5);
if (type == 0) {
// Plot a few horizontal lines.
for (int i = 0; i < scrH; i += 10) {
cairo_set_source_rgb(cr, 0.2, 0.99, 0.2);
cairo_move_to(cr, (double) 10, i);
cairo_line_to(cr, (double) (scrW - 10.0), i);
cairo_stroke(cr);
}
} else {
// Plot a few vertical lines.
for (int i = 0; i < scrW; i += 10) {
cairo_set_source_rgb(cr, 0.99, 0.2, 0.99);
cairo_move_to(cr, (double) i, 10.0);
cairo_line_to(cr, (double) i, (scrW - 10.0));
cairo_stroke(cr);
}
}
return 0;
}
void clicked(GtkWidget *widget,
GdkEventButton *event,
gpointer user_data) {
/* This one works, but is useless for my application.
if (event->button == 1) {
type = 0;
gtk_widget_queue_draw(widget);
} else if (event->button == 3) {
gtk_widget_queue_draw(widget);
type = 1;
} //TEST */
/* This one only plots once, not many times. How can I fix it?
while (1) {
if (++type == 2) { type = 0; }
// This was a failed attempt to fix it. It too only plotted a single time.
// gdk_threads_add_idle((GSourceFunc)gtk_widget_queue_draw, (void*) widget);
gtk_widget_queue_draw(widget);
sleep(1);
} //TEST*/
}
int main(int argc, char *argv[]) {
GtkWidget *window;
GtkWidget *darea;
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), scrW, scrH);
gtk_window_set_title(GTK_WINDOW(window), "Lines");
gtk_widget_show_all(window);
gtk_main();
}
Sorry about the code length. Graphical environments tend to be sesquipedalian!
The code was compiled with:
gcc -g -o fj testGTK.c `pkg-config --cflags gtk+-3.0 --libs gtk+-3.0`
on a Debian Linux machine. Details? OK: uname -a:
Linux Sirius 4.19.0-14-amd64 #1 SMP Debian 4.19.171-2 (2021-01-30) x86_64 GNU/Linux
GTK uses an event loop. When you call gtk_main(), it starts this loop, which handles click events, drawing your window, etc. The idea is that the loop calls your functions whenever an event happens. Your function handles it, then returns control back to the main loop so it can handle more events.
Your while(1) loop never returns control to the main loop, so no more events will ever be handled--including the draw event you queue inside the loop. (gtk_widget_queue_draw() doesn't redraw the widget immediately; it schedules a redraw for the next loop).
To fix this, instead of a while(1) loop with a sleep() call, try using g_timeout_add(). This will call a function every interval milliseconds as part of that main loop.
My problem is the operation of the for loop with GUI application. I am writing a program that opens other programs by clicking on the run button (for testing), during the run my UI starts to hang. How to make my UI work simultaneously with another application without freezing? And so that all elements are clickable during the for loop
main.c - gtk code
#include <gtk/gtk.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
GtkWidget *window;
GtkWidget *run_button;
GtkWidget *stop_button;
GtkWidget *button_box;
int main(int argc, char **argv)
{
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS);
gtk_window_set_title(GTK_WINDOW(window), "Application - Launch GUI Tests");
gtk_window_set_default_size(GTK_WINDOW(window), 600, 480);
/* create button box - start / stop buttons */
button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_set_homogeneous(GTK_BOX(button_box), TRUE);
stop_button = gtk_button_new_with_label("Stop");
run_button = gtk_button_new_with_label("Run");
/* button box pack */
gtk_box_pack_start(GTK_BOX(button_box), stop_button, FALSE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(button_box), run_button, FALSE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(window), button_box);
/* signals */
g_signal_connect(G_OBJECT(window), "destroy", gtk_main_quit, NULL);
g_signal_connect(G_OBJECT(run_button), "clicked", G_CALLBACK(on_run_button_clicked), NULL);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
when I click on the run button this code starts working (below)
void on_run_button_clicked(GtkWidget *run_button)
{
char *list[] = {"xfce4-terminal", "gnome-application", "test-app", "firefox"};
pid_t child;
int status;
for (int i = 0; *(list + i); i++) {
switch(child = fork()) {
case -1:
perror("child - fork error");
exit(EXIT_FAILURE);
case 0: /* child process exec another program */
execlp(list[i], list[i], NULL);
exit(127);
default: /* the parent waits until the program is closed */
wait(&status);
//check_status(status);
}
}
}
And the applications are started in turn until the user closes them, but the problem is that the main application starts to hang and the stop button is unavailable. How to fix it? Helps me, please
Looks like gtk_main() in main() isn't able to do its thing because on_run_button_clicked() is stuck in wait().
You need to make sure on_run_button_clicked() actually returns.
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'm using gtk+2.0 and cairo. I wrote a simple program that open a window and
move a point. A simple biliard, only horizontal motion. It's just a test.
The problem is that it seems to be not so smooth, and I would ask if there
are some gtk or cairo expert here that could check for errors in the code.
Thanks.
#include <gtk/gtk.h>
#include <math.h>
gboolean timeout(gpointer data)
{
GtkWidget *widget = GTK_WIDGET(data);
if (!widget->window) return TRUE;
gtk_widget_queue_draw(widget);
}
gboolean configure(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
{
return TRUE;
}
double px = 10;
double vx = **2**;
gboolean expose(GtkWidget *widget, GdkEventExpose *event, gpointer data)
{
cairo_t *cr = gdk_cairo_create(widget->window);
cairo_rectangle(cr, event->area.x, event->area.y, event->area.width, event->area.height);
cairo_clip(cr);
cairo_set_source_rgb(cr,1,0,0);
cairo_arc(cr, px, 100, 6, 0, 2*M_PI);
cairo_fill(cr);
cairo_set_source_rgb(cr,0,0,0);
cairo_destroy(cr);
if (px <= 3 || px >= 200-3) vx = -vx;
px += vx;
return FALSE;
}
int main(int argc, char *argv[])
{
char *title = "Test";
int sx = 200;
int sy = 200;
gtk_init(NULL,NULL);
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window),title);
gtk_container_set_border_width(GTK_CONTAINER (window), 2);
g_signal_connect(window, "destroy",G_CALLBACK(gtk_main_quit),&window);
GtkWidget *drawing_area = gtk_drawing_area_new();
//g_signal_connect(window,"key-press-event",G_CALLBACK(on_key_press),NULL);
const GdkColor bianco = { 0, 0xffff, 0xffff, 0xffff };
const GdkColor nero = { 0, 0x0, 0x0, 0x0 };
gtk_widget_modify_bg(drawing_area, GTK_STATE_NORMAL, &bianco);
gtk_widget_set_size_request(drawing_area, sx, sy);
g_signal_connect(drawing_area,"configure_event",G_CALLBACK(configure), NULL);
g_signal_connect(drawing_area,"expose_event",G_CALLBACK(expose),NULL);
gtk_container_add(GTK_CONTAINER(window), drawing_area);
gtk_widget_show(drawing_area);
g_timeout_add(**10**, timeout, window);
if (!GTK_WIDGET_VISIBLE (window))
gtk_widget_show_all(window);
else {
gtk_widget_destroy (window);
window = NULL;
}
gtk_main();
return 0;
}
Not so smooth ? Well, with a period of 100ms, you're drawing at best 10 frames per second, no wonder it's not smooth... You should aim for 60 fps. Furthermore, you're invalidating the whole widget by calling gtk_widget_queue_draw, so your call to cairo_clip is mostly useless, as the clipping region is the whole widget. You should call gtk_widget_queue_draw_area instead so your clipping region is useful, and determining the area by keeping a record of the animation at frame n and n-1, so you redraw both areas to avoid the previous frame not being deleted.
There are lots of interesting stuff on animation perception on Owen Tailor's blog, starting with this post and more recent:
http://blog.fishsoup.net/2009/05/28/frames-not-idles/
Give a look at all the posts with figures, it's a gold mine.
Here is my code:
#include <gtk/gtk.h>
static int counter = 0;
static PangoLayout* layout;
static GdkGC* gc1;
static GdkGC* gc2;
//**/static GMutex* mu;
static gboolean on_expose_event(GtkWidget* widget, GdkEventExpose* event)
{
gchar the_string[20];
//**/g_mutex_lock(mu);
gdk_draw_rectangle(GDK_DRAWABLE(widget->window), gc1, TRUE, 0, 0, widget->allocation.width, widget->allocation.height);
snprintf(the_string, 20, "%d", counter);
pango_layout_set_text(layout, the_string, -1);
gdk_draw_layout(GDK_DRAWABLE(widget->window), gc2, 180, 120, layout);
//**/g_mutex_unlock(mu);
g_print (".");
return FALSE;
}
gpointer func(gpointer data)
{
//**/g_usleep(10000);
GdkWindow* window = GDK_WINDOW(data);
while(TRUE)
{
gdk_window_invalidate_rect(window, NULL, FALSE);
//**/gdk_window_process_updates(window, FALSE);
if(counter % 100 == 0) g_print("X");
g_usleep(10);
++counter;
}
return FALSE;
}
int main(int argc, char** argv)
{
GtkWidget* window;
GtkWidget* drawer;
GdkColormap* colormap;
GdkColor color;
g_thread_init(NULL);
gdk_threads_init();
gtk_init(&argc, &argv);
//:Create widgets and
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
drawer = gtk_drawing_area_new();
gtk_widget_set_size_request(drawer, 400, 300);
gtk_container_add(GTK_CONTAINER(window), drawer);
//.
gtk_widget_show_all(window);
//:Initializing Graphic Contexts
colormap = gtk_widget_get_colormap(GTK_WIDGET(drawer));
layout = gtk_widget_create_pango_layout(GTK_WIDGET(drawer), NULL);
gc1 = gdk_gc_new(GDK_DRAWABLE(GTK_WIDGET(drawer)->window));
gc2 = gdk_gc_new(GDK_DRAWABLE(GTK_WIDGET(drawer)->window));
gdk_color_parse("#000", &color);
gdk_colormap_alloc_color(colormap, &color, FALSE, TRUE);
gdk_gc_set_background(gc1, &color);
gdk_color_parse("#fff", &color);
gdk_colormap_alloc_color(colormap, &color, FALSE, TRUE);
gdk_gc_set_foreground(gc2, &color);
//.
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(G_OBJECT(drawer), "expose_event", G_CALLBACK(on_expose_event), NULL);
//**/mu = g_mutex_new();
//Run the problematic thread!
g_thread_create(func, GTK_WIDGET(drawer)->window, TRUE, NULL);
gdk_threads_enter();
gtk_main();
gdk_threads_leave();
//**/g_mutex_free(mu);
return 0;
}
It's a multithreaded program, and did nothing so especial! It just force a GtkDrawingArea to redraw itself from the main thread (GTK+ main loop).
My problem (or GTK+ problem!) is that after a long time running (maybe 0.5, 1 or 4 minutes!), the expose event will not work (does not response to my requests until I'm resizing the whole window!)! Please, compile and run the code to check this situation. I added some print commands which may help! but i can't detect the problem, because of my little experience.
I hoped this program can redraws its context for almost always, but it didn't (or couldn't). What can i do to solve this problem? Maybe i should report a bug to GTK+ developers?
I'm using Debian + GCC + GTK2.20
Most likely, you need to surround your gdk_window_invalidate_rect() call with gdk_threads_enter()/gdk_threads_leave() pair. Read the gdk threads for more information about gtk and threads. Look at the example there.
You should use g_timeout_add with your callback and NOT use a second thread that is messing with the UI.