I'm coding with C, GTK+ and cairo. I'm trying to use a slider to change the value of a variable. This is what I have so far:
adj = (GtkAdjustment *) gtk_adjustment_new (300.0, -50.0, 500.0, 1.0, 1.0, 1.0);
scale = gtk_hscale_new (GTK_ADJUSTMENT (adj));
gtk_box_pack_start (GTK_BOX (vbox1), scale, FALSE, TRUE, 5);
gtk_signal_connect(GTK_OBJECT(adj), "value_changed", GTK_SIGNAL_FUNC(value_changed), NULL);
This is where I create the adjustment and send the signal whenever the user moves the slider.
double
value_changed (GtkAdjustment *adj)
{
pos1x = gtk_adjustment_get_value(adj);
printf ("\n%lf", pos1x);
return pos1x;
}
Here I change the pos1x value to the value the slider has.
gboolean
on_expose_event (GtkWidget *widget,
GdkEventExpose *event,
gpointer data)
{
cairo_t *cr;
double posy;
static gdouble pos2x = 450., pos2y = 290.; //Coordenadas Espelho
static gdouble pos3x = 450., pos3y = 250.;
cr = gdk_cairo_create(widget->window);
pos1x = gtk_adjustment_get_value(adj);
posy = 250.;
cairo_set_source_rgb (cr, 0, 0, 0);
cairo_set_line_width (cr, 1.0);
cairo_rectangle (cr, (double) pos1x, (double) posy, 20, 80);
cairo_stroke_preserve (cr);
cairo_set_source_rgb (cr, 1, 1, 1);
cairo_fill (cr);
Now I want to use the pos1x variable to change the coordinates of the rectangle I created with cairo as the user moves the slider. However, I can only get the initial value of the adjustment, not the changes. I'm having trouble thinking of a way to do this, and I'd appreciate it if you could lend me a hand.
Thanks in advance.
You need to call gdk_window_invalidate_rect() or something similar from your value_changed() function to have GTK+ re-emit expose-event.
Related
I worked with cairo and X11 before, and had a piece of code working perfectly, and now I am developping a new project (supposed to be a karaoke), and I took a piece of the code that was used to display something on the screen, which worked on the old project, but which doesn't work anymore.
I have been looking for a mistake all day and I must be missing something, because nothing works.
The code is the following.
A first function used to display some text :
void display_line(cairo_surface_t *surface, lyrics_line l)
{
cairo_t *cr;
cr=cairo_create(surface);
cairo_set_source_rgb(cr, 0, 0, 0); // Should paint the window black
cairo_paint(cr);
cairo_set_source_rgb(cr, 1., 1., 1.);
cairo_select_font_face(cr, "Hacker", CAIRO_FONT_SLANT_NORMAL,CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 13);
cairo_move_to(cr, 620, 30);
char text[255];
strcpy(text, l.text);
cairo_show_text(cr, text); // Should print the text in white
cairo_destroy(cr);
printf("%s\n", text);
}
And the main function, that calls the previous one,
void display(song s)
{
// X11 display
Display *dpy;
Window rootwin;
Window win;
int scr;
// init the display
if(!(dpy=XOpenDisplay(NULL))) {
fprintf(stderr, "ERROR: Could not open display\n");
exit(1);
}
scr=DefaultScreen(dpy);
rootwin=RootWindow(dpy, scr);
win=XCreateSimpleWindow(dpy, rootwin, 1, 1, WINSIZEX, WINSIZEY, 0, BlackPixel(dpy, scr), BlackPixel(dpy, scr));
XStoreName(dpy, win, "Karaoke");
KeyPressMask|ButtonPressMask|ExposureMask);
XMapWindow(dpy, win);
// create cairo surface
cairo_surface_t *cs;
cs=cairo_xlib_surface_create(dpy, win, DefaultVisual(dpy, 0), WINSIZEX, WINSIZEY);
cairo_t *cr;
cr=cairo_create(cs);
cairo_set_source_rgb (cr, 0.0, 0.0, 1.0);
cairo_paint(cr); // Should fill the window in blue
cairo_destroy(cr);
int i;
printf("\n");
lyrics_line l = {"That's a test", 200};
display_line(cs, l);
usleep(s.text[0].length*10000);
for(i=0; i<s.length; i++)
{
display_line(cs, s.text[i]);
usleep((s.text[i+1].length - s.text[i].length)*10000);
}
cairo_surface_destroy(cs); // destroy cairo surface
XCloseDisplay(dpy); // close the display
}
I don't know if display_line causes a problem, because when I simply try to color the window in blue with displayit doesn't even work.
However, the lyrics do display in the console, so the algorithm is not the problem.
What could I be missing ?
Thanks in advance.
When (re)drawing CAIRO rectangles after an expose_event, I see that the double buffering prevents the screen from flickering. Unfortunately when resizing the window frame the double buffer seems to "hang" on the screen as a shadow. Setting double buffering to FALSE, removes the shadow but makes a complex drawing flicker.
If I would "invalidate" the complete newly resized window area (in the expose event), the problem is solved, but the expose event fires constantly new request to redraw.
How can I resize the GDK clip region in a double buffered context ?
thanks a lot
The program code I used detect the cause of problems :
#include <stdio.h>
#include <cairo.h>
#include <gtk/gtk.h>
static gboolean on_expose_event (GtkWidget *widget,
GdkEventExpose *event ,
gpointer data)
{
cairo_t *cr;
int width, height, kwadrant;
cr = gdk_cairo_create (widget->window);
gtk_window_get_size(GTK_WINDOW(widget), &width, &height);
cairo_rectangle(cr, 0.0, 0.0, width, height);
cairo_set_source_rgb(cr, 0.0, 0.0, 0.5);
cairo_fill(cr);
if (width < height)
kwadrant = 0.8 * width;
else
kwadrant = 0.8 * height;
cairo_set_line_width(cr, 1);
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_rectangle(cr, (width - kwadrant)/2, (height - kwadrant)/2, kwadrant, kwadrant);
cairo_stroke(cr);
cairo_destroy(cr);
return TRUE;
}
void frame_callback(GtkWindow *window,
GdkEvent *event,
gpointer data)
{
char buff [50];
sprintf (buff, "%i X %i", event->configure.width,
event->configure.height);
gtk_window_set_title(window, buff);
return;
}
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_set_app_paintable(window, TRUE);
gtk_widget_set_double_buffered(window, TRUE);
g_signal_connect(G_OBJECT(window), "expose-event",
G_CALLBACK(on_expose_event), NULL);
g_signal_connect(G_OBJECT(window), "configure-event",
G_CALLBACK(frame_callback), NULL);
g_signal_connect_swapped(G_OBJECT(window), "destroy",
G_CALLBACK(gtk_main_quit), G_OBJECT(window));
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);
gtk_widget_show (window);
gtk_main();
return 0;
}
========= added later :
As it often happens :
After searching for days for a valid solution, I posted this message. A few moments afterwards I tried to isolate the "firing the clipping" outside the EXPOSE event and replace all
gtk_widget_queue_draw (widget);
calls with
void request_redraw (GtkWidget *widget)
{
GdkRectangle rect;
int width, height;
gtk_window_get_size (GTK_WINDOW(window), &width, &height);
rect.x = 0;
rect.y = 0;
rect.width = width;
rect.height = height;
gdk_window_invalidate_rect (gtk_widget_get_window(widget), &rect, TRUE);
gtk_widget_queue_draw (widget);
return;
}
This allows me to keep the double buffering, avoid the flickering of the screen and to redraw the full area that corresponds with the resized frame/window.
Is this a correct solution or is there a better way ?
Sorry for having asked an unnecessary question
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.
I have a problem.
I need to draw on the widget type GtkDrawingArea using functions Xlib (XDrawLine etc).
Why?
I use the library, which draws with Xlib. And I need to pass any arguments (Display, Window, GC) in the rendering function drawSome (...). All is well. I obtain these arguments (via gdk_x11_... (), GdkDrawable, GdkGC) and call drawSome (...) with obtained parameters.
But there are problems - drawing is not always done. The image is not displayed when maximizing windows, dragging, resizing DrawingArea etc.. The image is displayed only under unusual manipulation of the top window .
Then I tested the function XDrawPoint/Line/Rectangle - the same problem. If we use gdk_draw_rectangle (...) - all is normal.
Here's the code below:
...
GtkDrawingArea* area;
...
int main (int argc, char *argv[])
{
...
area=GTK_DRAWING_AREA(gtk_builder_get_object(builder,"area"));
gtk_widget_realize (GTK_WIDGET(area));
...
g_signal_connect (G_OBJECT(area), "expose_event", G_CALLBACK(expose_event_callback), NULL);
...
}
...
gboolean expose_event_callback (GtkWidget *widget, GdkEventExpose *event, gpointer data)
{
Display *dpy = gdk_x11_drawable_get_xdisplay(widget->window);
Window win =gdk_x11_drawable_get_xid(widget->window);
GC gc = DefaultGC(dpy, DefaultScreen(dpy));
//draw image on (0,0) in widget DrawingArea and a small black rectangle over image
drawSome(dpy, win, gc, ...);
XFillRectangle(dpy, win, gc, 0, 0, 10, 10);
return FALSE;
}
...
Image and a small black rectangle displayed only in one case: if the window move beyond the desktop and return back to the desktop - the image appears. In other cases, it is not displayed.
The impression is that another function erases DrawingArea.
Who can tell me what's the problem?
I would be grateful!
And... sorry to so bad English!
I think all you need to do is add XFlush(dpy); after the XFillRectangle command. I wrote a short routine and it seems to work.
#include <X11/Xlib.h> //-lX11
#include <gtk/gtk.h> //$$(pkg-config --cflags --libs gtk+-3.0)
#include <gdk/gdkx.h>
void DrawOnWidget(GtkWidget *widget)
{
GdkDisplay *gdk_dis = gdk_display_get_default();
Display *dis = gdk_x11_display_get_xdisplay (gdk_dis);
GC gc = DefaultGC(dis, DefaultScreen(dis));
GdkWindow *gdk_window = gtk_widget_get_window(widget);
Window win = gdk_x11_window_get_xid(gdk_window);
unsigned long valuemask = GCForeground;
XGCValues vColor;
vColor.foreground = 0x000000FF;
XChangeGC(dis, gc, valuemask, &vColor);
XFillRectangle(dis, win, gc, 0, 0, 100, 100);
XFlush(dis);
}
You'll need to use these functions X Window System Interaction but be warned but there may be other pitfalls. I think you'll also need to disable double-buffering for your GtkDrawingArea using gtk_widget_set_double_buffered
This is my second answer to this question... My first answer probably addresses the original question so I left it as unchanged. Although, the more general solution which is what I was looking for when I first found this thread was not addressed therefore, I decided to post a more general solution as well.
In short, I created a gtk image widget which can be displayed independently or attached to other widgets like buttons. Then I sent the image widget to the drawing function. In the drawing function all of the xlib parameters are queried in order to create a Pixmap as the xlib drawable which is where all xlib drawing is rendered. Then the GdkPixbuf is created and the Pixmap pixels are copied to the GdkPixbuf which is then set to the image widget...
There are a few commented lines which can be used to change the behavior the explanation will be left to the reader to determine.
It should be noted that while using xlib is possible; cairo appears to be a bit less cumbersome to implement.
#include <gtk/gtk.h> //$$(pkg-config --cflags --libs gtk+-3.0)
#include <gdk/gdkx.h>
#include <X11/Xlib.h> //-lX11
void DrawOnWidget(GtkWidget *widget, int width, int height)
{
GdkDisplay *gdk_dis = gdk_display_get_default();
Display *dis = gdk_x11_display_get_xdisplay (gdk_dis);
GC gc = DefaultGC(dis, DefaultScreen(dis));
GdkWindow *gdk_window = gtk_widget_get_window(widget);
Window win = gdk_x11_window_get_xid(gdk_window);
GdkPixbuf *pb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, width, height);
//GdkPixbuf *pb = gtk_image_get_pixbuf((GtkImage *) widget);
char *data = (char *) gdk_pixbuf_read_pixels((const GdkPixbuf *) pb); //RGB(A)
//int width = gdk_pixbuf_get_width(pb);
//int height = gdk_pixbuf_get_height(pb);
int pb_depth = gdk_pixbuf_get_n_channels(pb);
int depth = DefaultDepth(dis, DefaultScreen(dis)) / 8;
Pixmap pm = XCreatePixmap(dis, win, width, height, depth * 8);
unsigned long valuemask = GCForeground;
XGCValues vColor;
vColor.foreground = 0x00FF0000;
XChangeGC(dis, gc, valuemask, &vColor);
XFillRectangle(dis, pm, gc, 0, 0, width, height);
XFlush(dis);
XImage *ximage = XGetImage(dis, pm, 0, 0, width, height, AllPlanes, ZPixmap); //BGRX
for(int i=0, j=0; i<width*height*pb_depth; i+=pb_depth, j+=4)
{
data[i+0] = ximage->data[j+2];
data[i+1] = ximage->data[j+1];
data[i+2] = ximage->data[j+0];
if(pb_depth == 4) data[i+3] = 255;
}
gtk_image_set_from_pixbuf((GtkImage *) widget, pb);
XFreePixmap(dis, pm);
g_object_unref(pb);
return;
}
static void destroy( GtkWidget *widget, gpointer data )
{
gtk_main_quit();
}
static void test( GtkWidget *widget, gpointer data )
{
DrawOnWidget((GtkWidget *) data, 200, 200);
}
void GTK_Win()
{
GtkWidget *window, *grid;
GtkWidget *button_Exit, *button_Test;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
g_signal_connect (window, "destroy", G_CALLBACK (destroy), NULL);
grid = gtk_grid_new ();
gtk_container_add (GTK_CONTAINER (window), grid);
button_Exit = gtk_button_new_with_label ("x");
g_signal_connect (button_Exit, "clicked", G_CALLBACK (destroy), NULL);
//GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 200, 200);
//GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
GtkWidget *image = gtk_image_new();
button_Test = gtk_button_new_with_label ("Test");
g_signal_connect (button_Test, "clicked", G_CALLBACK (test), image);
//gtk_button_set_image((GtkButton *) button_Test, image);
gtk_grid_attach (GTK_GRID (grid), button_Exit, 0, 0, 1, 1);
gtk_grid_attach (GTK_GRID (grid), button_Test, 0, 1, 1, 1);
gtk_grid_attach (GTK_GRID (grid), image, 0, 2, 1, 1);
gtk_widget_show_all (window);
gtk_main();
}
int main(int argc, char** argv)
{
gtk_init (&argc, &argv);
GTK_Win();
return 0;
}
I've got a simple application that is supposed to rotate a decorated wheel so many degrees every x number of milliseconds using GTK+ and Cairo. I've got some code below that calls cairo_rotate() from a timer. However, the image doesn't change. Do I have to invalidate the image to cause the expose-event to fire? I'm so new to Cairo that a simple example demonstrating how to rotate an image using Cairo in GTK+ would be highly appreciated.
#include <cairo.h>
#include <gtk/gtk.h>
cairo_surface_t *image;
cairo_t *cr;
gboolean rotate_cb( void )
{
cairo_rotate (cr, 1);
//cairo_paint(cr);
printf("rotating\n");
return( TRUE );
}
static gboolean
on_expose_event(GtkWidget *widget,
GdkEventExpose *event,
gpointer data)
{
cr = gdk_cairo_create (widget->window);
cairo_set_source_surface(cr, image, 0, 0);
cairo_paint(cr);
printf("Paint\n");
//cairo_destroy(cr);
return FALSE;
}
int main(int argc, char *argv[])
{
GtkWidget *window;
image = cairo_image_surface_create_from_png("wheel.png");
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "expose-event",
G_CALLBACK (on_expose_event), NULL);
g_signal_connect(window, "destroy",
G_CALLBACK (gtk_main_quit), NULL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), 500, 500);
gtk_widget_set_app_paintable(window, TRUE);
gtk_widget_show_all(window);
g_timeout_add(500, (GSourceFunc) rotate_cb, NULL);
gtk_main();
cairo_destroy(cr);
cairo_surface_destroy(image);
return 0;
}
You need to store the rotation in a variable and put the cairo_rotate(cr, rotation_amt); call into the on_expose_event method, before paint.
Also translate to the center of the window, rotate, and translate back, to make the wheel rotate around it's center, if the image is centered.
cairo_translate(cr, width / 2.0, height / 2.0);
cairo_rotate(cr, rotation_amt);
cairo_translate(cr, - image_w / 2.0, - image_h / 2.0);
cairo_set_source_surface(cr, image, 0, 0);
cairo_paint(cr);
I hope that's right.
And as ptomato said, you need to invalidate your drawing surface by calling gtk_widget_queue_draw from rotate_cb. And keeping a global variable for the Cairo context is redundant. The image doesn't rotate because a newly created context is loaded with an identity matrix and all your previous transformations are reset.
With the help of the cairo mailing list, this is a working example. I'm sharing this for those who might find this useful.
#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>
#include <stdlib.h>
cairo_surface_t *image;
cairo_t *cr;
gdouble rotation = 0;
GtkWidget *window;
gint image_w, image_h;
double DegreesToRadians( int degrees );
double DegreesToRadians( int degrees )
{
return((double)((double)degrees * ( M_PI/180 )));
}
gboolean rotate_cb( void *degrees )
{
// Any rotation applied to cr here will be lost, as we create
// a new cairo context on every expose event
//cairo_rotate (cr, 4);
rotation += DegreesToRadians((*(int*)(degrees)));
//cairo_paint(cr);
// printf("rotating\n");
// Tell our window that it should repaint itself (ie. emit an expose event)
gtk_widget_queue_draw(window);
return( TRUE );
}
static gboolean on_expose_event(GtkWidget *widget, GdkEventExpose *event,gpointer data)
{
// Make sure our window wasn't destroyed yet
// (to silence a warning)
g_return_if_fail(GTK_IS_WIDGET(widget));
cr = gdk_cairo_create (widget->window);
// We need to apply transformation before setting the source surface
// We translate (0, 0) to the center of the screen,
// so we can rotate the image around its center point,
// not its upper left corner
cairo_translate(cr, image_w/2, image_h/2);
cairo_rotate(cr, rotation);
cairo_set_source_surface(cr, image, -image_w/2, -image_h/2);
// We need to clip around the image, or cairo will paint garbage data
//cairo_rectangle(cr, -image_w/2, -image_h/2, image_w, image_h);
//cairo_clip(cr);
cairo_paint(cr);
//printf("Paint\n");
cairo_destroy(cr);
return FALSE;
}
int main(int argc, char *argv[])
{
int degrees = 10, speed = 125;
image = cairo_image_surface_create_from_png("wheel.png");
image_w = cairo_image_surface_get_width(image);
image_h = cairo_image_surface_get_height(image);
gtk_init(&argc, &argv);
if( argc == 3 )
{
degrees = atoi(argv[1]);
speed = atoi(argv[2]);
}
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "expose-event",
G_CALLBACK (on_expose_event), NULL);
g_signal_connect(window, "destroy",
G_CALLBACK (gtk_main_quit), NULL);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
gtk_window_set_default_size(GTK_WINDOW(window), image_w, image_h);
gtk_widget_set_app_paintable(window, TRUE);
gtk_widget_show_all(window);
g_timeout_add(speed, (GSourceFunc) rotate_cb, (void *)°rees);
gtk_main();
cairo_surface_destroy(image);
return 0;
}