I am building a nested GTK application:
There is GtkNotebook - GtkGrid - GtkGrid - GtkFrame - GtkDrawingArea.
In GtkDrawingArea (I fix the size through a size request) I create drawings and I have built the possibility to zoom via the scroll wheel.
Logically, when I 'zoom in' the surface becomes bigger and scroll bars are needed to view the whole drawing.
How can I get these? I tried GtkLayout and GtkSrolledWindow in different combinations but they didn't work. I haven't found any useful example on the Internet.
What's the way to get a scrollable drawing area?
Where should I start to dig in? Any hints are very appreciated!
Thank you.
BTW: I am using Debian, GTK3, C and Cairo
MCVE: I created this example. It draws a rectangle and you can zoom it by using your scroll wheel. This happens within a 300x300 frame. I'd like to have this frame with scrollbars. Any ideas how to do this?
This code can be compiled by using
gcc `pkg-config --cflags gtk+-3.0` -lm -o cf cf.c `pkg-config --libs gtk+-3.0`
cf.c:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include <gtk/gtk.h>
#define ZOOMING_STEP 1.2
typedef struct m_data
{
double scaling_factor;
cairo_surface_t *circle_surface_p, *final_surface_p;
GtkWidget *window_p;
} m_data_struct;
static void activate(GtkApplication *, gpointer);
static gboolean zoom_it(GtkWidget *, GdkEvent *, gpointer);
static gboolean configure_it(GtkWidget *, GdkEventConfigure *, gpointer);
static gboolean draw_it(GtkWidget *, cairo_t *, gpointer);
int main(int argc, char **argv)
{
GtkApplication *app_p;
m_data_struct my_data;
int status;
my_data.scaling_factor = 1.0/11.0;
my_data.final_surface_p = NULL;
app_p = gtk_application_new("calc.foil", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app_p, "activate", G_CALLBACK(activate), &my_data);
status = g_application_run(G_APPLICATION(app_p), 0, NULL);
g_object_unref(app_p);
}
static void activate(GtkApplication *app_p, gpointer g_data_p)
{
GtkWidget *frame_p, *notebook_p, *grid0_p, *grid1_p, *drawing_area_p;
m_data_struct *my_data_p;
cairo_t *cr_p;
my_data_p = (m_data_struct *)g_data_p;
my_data_p->window_p = gtk_application_window_new(app_p);
gtk_window_set_title(GTK_WINDOW(my_data_p->window_p), "Fot Calculation");
gtk_container_set_border_width(GTK_CONTAINER(my_data_p->window_p), 8);
notebook_p = gtk_notebook_new();
gtk_container_add(GTK_CONTAINER(my_data_p->window_p), notebook_p);
grid0_p = gtk_grid_new();
gtk_notebook_append_page(GTK_NOTEBOOK(notebook_p), grid0_p,
gtk_label_new("First Tab"));
grid1_p = gtk_grid_new();
gtk_grid_attach(GTK_GRID(grid0_p), grid1_p, 2, 2, 2, 50);
frame_p = gtk_frame_new("Rectangle");
drawing_area_p = gtk_drawing_area_new();
gtk_widget_set_size_request(frame_p, 300, 300);
g_signal_connect(drawing_area_p, "configure-event",
G_CALLBACK(configure_it), g_data_p);
g_signal_connect(drawing_area_p, "draw", G_CALLBACK(draw_it), g_data_p);
g_signal_connect(drawing_area_p, "scroll-event", G_CALLBACK(zoom_it),
g_data_p);
gtk_widget_set_events(drawing_area_p,
gtk_widget_get_events(drawing_area_p)
| GDK_SCROLL_MASK);
gtk_container_add(GTK_CONTAINER(frame_p), drawing_area_p);
gtk_grid_attach(GTK_GRID(grid1_p), frame_p, 1, 0, 1, 2);
my_data_p->circle_surface_p
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 3000, 3000);
cr_p = cairo_create(my_data_p->circle_surface_p);
cairo_set_line_width(cr_p, 10);
cairo_rectangle(cr_p, 100, 100, 2900, 2900);
cairo_set_source_rgb(cr_p, 0, 0, 0);
cairo_stroke(cr_p);
gtk_widget_show_all(my_data_p->window_p);
}
static gboolean zoom_it(GtkWidget *widget_p,
GdkEvent *event_p,
gpointer g_data_p)
{
if (((GdkEventScroll *)event_p)->direction == GDK_SCROLL_UP)
{
((m_data_struct *)g_data_p)->scaling_factor *= ZOOMING_STEP;
configure_it(widget_p, (GdkEventConfigure *)event_p, g_data_p);
gtk_widget_queue_draw(widget_p);
}
if (((GdkEventScroll *)event_p)->direction == GDK_SCROLL_DOWN)
{
((m_data_struct *)g_data_p)->scaling_factor /= ZOOMING_STEP;
configure_it(widget_p, (GdkEventConfigure *)event_p, g_data_p);
gtk_widget_queue_draw(widget_p);
}
return TRUE;
}
static gboolean configure_it(GtkWidget *widget_p,
GdkEventConfigure *event_p,
gpointer g_data_p)
{
cairo_t *cr_p;
m_data_struct * my_data_p;
my_data_p = (m_data_struct *)g_data_p;
if (my_data_p->final_surface_p)
cairo_surface_destroy(my_data_p->final_surface_p);
my_data_p->final_surface_p
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 300, 300);
cr_p = cairo_create(my_data_p->final_surface_p);
cairo_scale(cr_p, my_data_p->scaling_factor, my_data_p->scaling_factor);
cairo_set_source_surface(cr_p, my_data_p->circle_surface_p, 0, 0);
cairo_paint(cr_p);
cairo_destroy(cr_p);
return TRUE;
}
static gboolean draw_it(GtkWidget *widget_p,
cairo_t *cr_p,
gpointer g_data_p)
{
cairo_set_source_surface(cr_p, ((m_data_struct *)g_data_p)->final_surface_p,
0, 0);
cairo_paint(cr_p);
return FALSE;
}
To produce a scrollable GTK drawing area (GtkDrawingArea) a scrolled window can be taken (GtkScrolledWindow). The drawing area is a child of the scrolled window (gtk_container_add).
However, to scroll the drawing area I need to specify its size every time it changes it (gtk_widget_set_size_request). And, of course, scrolling only makes sense if its size is bigger than the size of the scrolled window.
Finally I wanted to use the scroll wheel to zoom into my Cairo drawing. This only works well if the point under the mouse is fixed. So I needed to adjust the scrolled window (GtkAdjustment) not to have the drawing moved under the mouse. Unless when zooming out to the very low levels.
However I couldn't find out where these adjustments are set when the drawing areas are changed so I had to do it manually when zooming in.
I changed the example in a way that it's working now. The code is complete to run:
cf.c:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include <gtk/gtk.h>
#define ZOOMING_STEP 1.2
#define SCALING_FACTOR_INIT 1.0/10.0
typedef struct m_data
{
double scaling_factor;
cairo_surface_t *rectangle_surface_p, *final_surface_p;
GtkWidget *window_p, *drawing_area_p;
GtkAdjustment *hadjust_p, *vadjust_p;
} m_data_struct;
static void activate(GtkApplication *, gpointer);
static gboolean zoom_it(GtkWidget *, GdkEvent *, gpointer);
static gboolean configure_it(GtkWidget *, GdkEventConfigure *, gpointer);
static gboolean draw_it(GtkWidget *, cairo_t *, gpointer);
int main(int argc, char **argv)
{
GtkApplication *app_p;
m_data_struct my_data;
int status;
my_data.scaling_factor = SCALING_FACTOR_INIT;
my_data.final_surface_p = NULL;
app_p = gtk_application_new("calc.foil", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app_p, "activate", G_CALLBACK(activate), &my_data);
status = g_application_run(G_APPLICATION(app_p), 0, NULL);
g_object_unref(app_p);
}
static void activate(GtkApplication *app_p, gpointer g_data_p)
{
GtkWidget *frame_p, *notebook_p, *grid0_p, *grid1_p, *drawing_area_p;
GtkWidget *scrolled_window_p, *frame0_p;
m_data_struct *my_data_p;
cairo_t *cr_p;
my_data_p = (m_data_struct *)g_data_p;
my_data_p->window_p = gtk_application_window_new(app_p);
gtk_window_set_title(GTK_WINDOW(my_data_p->window_p), "Fot Calculation");
gtk_container_set_border_width(GTK_CONTAINER(my_data_p->window_p), 8);
notebook_p = gtk_notebook_new();
gtk_container_add(GTK_CONTAINER(my_data_p->window_p), notebook_p);
grid0_p = gtk_grid_new();
gtk_notebook_append_page(GTK_NOTEBOOK(notebook_p), grid0_p,
gtk_label_new("First Tab"));
grid1_p = gtk_grid_new();
gtk_grid_attach(GTK_GRID(grid0_p), grid1_p, 2, 2, 2, 50);
frame_p = gtk_frame_new("Rectangle");
gtk_frame_set_shadow_type(GTK_FRAME(frame_p), GTK_SHADOW_NONE);
frame0_p = gtk_frame_new(NULL);
scrolled_window_p = gtk_scrolled_window_new(NULL, NULL);
my_data_p->hadjust_p
= gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW
(scrolled_window_p));
my_data_p->vadjust_p
= gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW
(scrolled_window_p));
drawing_area_p = gtk_drawing_area_new();
gtk_widget_set_size_request(scrolled_window_p, 300, 300);
g_signal_connect(drawing_area_p, "configure-event",
G_CALLBACK(configure_it), g_data_p);
g_signal_connect(drawing_area_p, "draw", G_CALLBACK(draw_it), g_data_p);
g_signal_connect(drawing_area_p, "scroll-event", G_CALLBACK(zoom_it),
g_data_p);
gtk_widget_set_events(drawing_area_p,
gtk_widget_get_events(drawing_area_p)
| GDK_SCROLL_MASK);
my_data_p->drawing_area_p = drawing_area_p;
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window_p),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_container_add(GTK_CONTAINER(scrolled_window_p), drawing_area_p);
gtk_container_add(GTK_CONTAINER(frame_p), frame0_p);
gtk_container_add(GTK_CONTAINER(frame0_p), scrolled_window_p);
gtk_grid_attach(GTK_GRID(grid1_p), frame_p, 1, 0, 1, 2);
my_data_p->rectangle_surface_p
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 3000, 3000);
cr_p = cairo_create(my_data_p->rectangle_surface_p);
cairo_set_line_width(cr_p, 50);
cairo_rectangle(cr_p, 100, 100, 1375, 1375);
cairo_rectangle(cr_p, 1525, 1525, 1375, 1375);
cairo_set_source_rgb(cr_p, 0, 0, 0);
cairo_stroke(cr_p);
gtk_widget_show_all(my_data_p->window_p);
}
static gboolean zoom_it(GtkWidget *widget_p,
GdkEvent *event_p,
gpointer g_data_p)
{
m_data_struct *my_data_p;
GdkEventScroll *this_event_p;
gdouble new_x, new_y;
int do_zoom = 0;
my_data_p = (m_data_struct *)g_data_p;
this_event_p = (GdkEventScroll *)event_p;
if (this_event_p->direction == GDK_SCROLL_UP)
{
my_data_p->scaling_factor *= ZOOMING_STEP;
gtk_adjustment_set_upper(my_data_p->hadjust_p,
gtk_adjustment_get_upper(my_data_p->hadjust_p)
* ZOOMING_STEP
+ 1); /* we need to calc the new upper
to set value, +1 for
inaccuracy */
gtk_adjustment_set_upper(my_data_p->vadjust_p,
gtk_adjustment_get_upper(my_data_p->vadjust_p)
* ZOOMING_STEP
+ 1);
new_x = this_event_p->x * ZOOMING_STEP;
new_y = this_event_p->y * ZOOMING_STEP;
do_zoom = 1;
}
if (this_event_p->direction == GDK_SCROLL_DOWN)
{
double sf;
sf = my_data_p->scaling_factor / ZOOMING_STEP;
if (sf >= SCALING_FACTOR_INIT / (1 + (ZOOMING_STEP - 1) / 2))
/* zoom out max till level 0 but
preventing inability not to zoom
to level 0 due to inaccurancy */
{
my_data_p->scaling_factor = sf;
new_x = this_event_p->x / ZOOMING_STEP;
new_y = this_event_p->y / ZOOMING_STEP;
do_zoom = 1;
}
}
if (do_zoom)
{
gtk_adjustment_set_value
(my_data_p->hadjust_p,
new_x
+ gtk_adjustment_get_value(my_data_p->hadjust_p)
- this_event_p->x);
gtk_adjustment_set_value
(my_data_p->vadjust_p,
new_y
+ gtk_adjustment_get_value(my_data_p->vadjust_p)
- this_event_p->y);
configure_it(widget_p, (GdkEventConfigure *)event_p, g_data_p);
gtk_widget_queue_draw(widget_p);
}
return TRUE;
}
static gboolean configure_it(GtkWidget *widget_p,
GdkEventConfigure *event_p,
gpointer g_data_p)
{
cairo_t *cr_p;
m_data_struct *my_data_p;
my_data_p = (m_data_struct *)g_data_p;
if (my_data_p->final_surface_p)
cairo_surface_destroy(my_data_p->final_surface_p);
my_data_p->final_surface_p
= cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
3000 * my_data_p->scaling_factor,
3000 * my_data_p->scaling_factor);
gtk_widget_set_size_request(my_data_p->drawing_area_p,
3000 * my_data_p->scaling_factor,
3000 * my_data_p->scaling_factor);
cr_p = cairo_create(my_data_p->final_surface_p);
cairo_scale(cr_p, my_data_p->scaling_factor, my_data_p->scaling_factor);
cairo_set_source_surface(cr_p, my_data_p->rectangle_surface_p, 0, 0);
cairo_paint(cr_p);
cairo_destroy(cr_p);
return TRUE;
}
static gboolean draw_it(GtkWidget *widget_p,
cairo_t *cr_p,
gpointer g_data_p)
{
cairo_set_source_surface(cr_p, ((m_data_struct *)g_data_p)->final_surface_p,
0, 0);
cairo_paint(cr_p);
return FALSE;
}
Related
When I'm resizing the GtkWindow, i want the GtkImage to resize as well while keeping the same aspect ratio as before.
I can't find any good examples on how to set this up with GTK3
This is what i've tried so far:
#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
GtkAllocation *allocation = g_new0 (GtkAllocation, 1);
gboolean resize_image(GtkWidget *widget, GdkEvent *event, GtkWidget *window) {
GdkPixbuf *pixbuf = gtk_image_get_pixbuf(GTK_IMAGE(widget));
if (pixbuf == NULL) {
g_printerr("Failed to resize image\n");
return 1;
}
gtk_widget_get_allocation(GTK_WIDGET(widget), allocation);
pixbuf = gdk_pixbuf_scale_simple(pixbuf, widget->allocation.width, widget->allocation.height, GDK_INTERP_BILINEAR);
gtk_image_set_from_pixbuf(GTK_IMAGE(widget), pixbuf);
return FALSE;
}
int main(int argc, char **argv) {
GtkWidget *window = NULL;
GtkWidget *image = NULL;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
image = gtk_image_new_from_file("image.jpg");
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(image, "draw", G_CALLBACK(resize_image), (gpointer)window);
gtk_container_add(GTK_CONTAINER(window), image);
gtk_widget_show_all(GTK_WIDGET(window));
gtk_main();
return 0;
}
This code should just resize the Pixbuf to the size of the parent, but it doesn't work, i get these errors:
GdkPixbuf-CRITICAL **: gdk_pixbuf_get_width: assertion 'GDK_IS_PIXBUF
(pixbuf)' failed
GLib-GObject-CRITICAL **: g_object_unref: assertion 'G_IS_OBJECT
(object)' failed
Even if this code would work, i wouldn't be able to keep the same aspect ratio, how to achieve this?
Thanks in Advance
Well, a trick to do this is using a GtkLayout between the image and the window.
Also, instead of using the draw signal, use the size-allocate signal.
We will load a GdkPixbuf and use it as reference for scaling, otherwise the quality would deteriorate with cumulative 'resizing'.
A simple approach would be:
#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
struct _resize_widgets {
GtkWidget *image;
GdkPixbuf *pixbuf;
};
typedef struct _resize_widgets ResizeWidgets;
gboolean resize_image(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data) {
int x,y,w,h;
GdkPixbuf *pxbscaled;
GtkWidget *image = (GtkWidget *) ((ResizeWidgets *) user_data)->image;
GdkPixbuf *pixbuf= (GdkPixbuf *) ((ResizeWidgets *) user_data)->pixbuf;
x = 0;
y = 0;
h = allocation->height;
w = (gdk_pixbuf_get_width(pixbuf) * h) / gdk_pixbuf_get_height(pixbuf);
pxbscaled = gdk_pixbuf_scale_simple(pixbuf, w, h, GDK_INTERP_BILINEAR);
if (w < allocation->width) {
x = (allocation->width - w) / 2;
gtk_layout_move(GTK_LAYOUT(widget), image, x, y);
}
gtk_image_set_from_pixbuf(GTK_IMAGE(image), pxbscaled);
g_object_unref (pxbscaled);
return FALSE;
}
int main(int argc, char **argv) {
GtkWidget *window = NULL;
GtkWidget *image = NULL;
GtkWidget *container = NULL;
GdkPixbuf *pixbuf = NULL;
ResizeWidgets *widgets;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
container = gtk_layout_new(NULL, NULL);
image = gtk_image_new();
pixbuf = gdk_pixbuf_new_from_file ("image.png", NULL);
if (pixbuf == NULL) {
g_printerr("Failed to resize image\n");
return 1;
}
widgets = g_new0(ResizeWidgets, 1);
widgets->image = image;
widgets->pixbuf = pixbuf;
gtk_container_add(GTK_CONTAINER(window), container);
gtk_layout_put(GTK_LAYOUT(container), image, 0, 0);
gtk_widget_set_size_request (GTK_WIDGET(window), 20, 20);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(container, "size-allocate", G_CALLBACK(resize_image), widgets);
gtk_widget_show_all(GTK_WIDGET(window));
gtk_main();
g_object_unref (pixbuf);
g_free(widgets);
return 0;
}
Compile with gcc -o main main.cpkg-config --cflags --libs gtk+-3.0`
This will keep aspect ratio. If that is not desired, then the resize_image callback handler would be:
gboolean resize_image(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data) {
int x,y,w,h;
GdkPixbuf *pxbscaled;
GtkWidget *image = (GtkWidget *) ((ResizeWidgets *) user_data)->image;
GdkPixbuf *pixbuf= (GdkPixbuf *) ((ResizeWidgets *) user_data)->pixbuf;
pxbscaled = gdk_pixbuf_scale_simple(pixbuf, allocation->width, allocation->height, GDK_INTERP_BILINEAR);
gtk_image_set_from_pixbuf(GTK_IMAGE(image), pxbscaled);
g_object_unref (pxbscaled);
return FALSE;
}
This is a simple hack, there are other options obviously, like cairo for drawing or simply checking Eye of Gnome (eog) implementation which is much more complete.
I found the following GTK+3 code in Zetcode. It creates an animation using the cairo library while displaying an image:
#include <cairo.h>
#include <gtk/gtk.h>
/* compile with
*
* gcc spectrum.c -o spectrum `pkg-config --cflags --libs gtk+-3.0`
*
* */
static void do_drawing(cairo_t *);
struct {
gboolean timer;
cairo_surface_t *image;
cairo_surface_t *surface;
gint img_width;
gint img_height;
} glob;
static void init_vars()
{
glob.image = cairo_image_surface_create_from_png("beckov.png");
glob.img_width = cairo_image_surface_get_width(glob.image);
glob.img_height = cairo_image_surface_get_height(glob.image);
glob.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
glob.img_width, glob.img_height);
glob.timer = TRUE;
}
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_t *ic;
static gint count = 0;
ic = cairo_create(glob.surface);
gint i, j;
for (i = 0; i <= glob.img_height; i+=7) {
for (j = 0 ; j < count; j++) {
cairo_move_to(ic, 0, i+j);
cairo_line_to(ic, glob.img_width, i+j);
}
}
count++;
if (count == 8) glob.timer = FALSE;
cairo_set_source_surface(cr, glob.image, 10, 10);
cairo_mask_surface(cr, glob.surface, 10, 10);
cairo_stroke(ic);
cairo_destroy(ic);
}
static gboolean time_handler(GtkWidget *widget)
{
if (!glob.timer) return FALSE;
gtk_widget_queue_draw(widget);
return TRUE;
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
init_vars();
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER (window), darea);
g_signal_connect(G_OBJECT(darea), "draw",
G_CALLBACK(on_draw_event), NULL);
g_signal_connect(G_OBJECT(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), 325, 250);
gtk_window_set_title(GTK_WINDOW(window), "Spectrum");
g_timeout_add(400, (GSourceFunc) time_handler, (gpointer) window);
gtk_widget_show_all(window);
gtk_main();
cairo_surface_destroy(glob.image);
cairo_surface_destroy(glob.surface);
return 0;
}
I achieve exatly the same result if I remove the do_drawing() function and move its code to the on_draw_event(), like:
#include <cairo.h>
#include <gtk/gtk.h>
/* compile with
*
* gcc spectrum.c -o spectrum `pkg-config --cflags --libs gtk+-3.0`
*
* */
struct {
gboolean timer;
cairo_surface_t *image;
cairo_surface_t *surface;
gint img_width;
gint img_height;
} glob;
static void init_vars()
{
glob.image = cairo_image_surface_create_from_png("beckov.png");
glob.img_width = cairo_image_surface_get_width(glob.image);
glob.img_height = cairo_image_surface_get_height(glob.image);
glob.surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
glob.img_width, glob.img_height);
glob.timer = TRUE;
}
static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr,
gpointer user_data)
{
cairo_t *ic;
static gint count = 0;
ic = cairo_create(glob.surface);
gint i, j;
for (i = 0; i <= glob.img_height; i+=7) {
for (j = 0 ; j < count; j++) {
cairo_move_to(ic, 0, i+j);
cairo_line_to(ic, glob.img_width, i+j);
}
}
count++;
if (count == 8) glob.timer = FALSE;
cairo_set_source_surface(cr, glob.image, 10, 10);
cairo_mask_surface(cr, glob.surface, 10, 10);
cairo_stroke(ic);
cairo_destroy(ic);
return FALSE;
}
static gboolean time_handler(GtkWidget *widget)
{
if (!glob.timer) return FALSE;
gtk_widget_queue_draw(widget);
return TRUE;
}
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *darea;
init_vars();
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
darea = gtk_drawing_area_new();
gtk_container_add(GTK_CONTAINER (window), darea);
g_signal_connect(G_OBJECT(darea), "draw",
G_CALLBACK(on_draw_event), NULL);
g_signal_connect(G_OBJECT(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), 325, 250);
gtk_window_set_title(GTK_WINDOW(window), "Spectrum");
g_timeout_add(400, (GSourceFunc) time_handler, (gpointer) window);
gtk_widget_show_all(window);
gtk_main();
cairo_surface_destroy(glob.image);
cairo_surface_destroy(glob.surface);
return 0;
}
So... I wonder... Am I missing something here (loss of generality)?
Or was the call to do_drawing() in function on_draw_event() of the original code redundant?
Thanks
Yes. It's correct on compiler's side.
But it's a good practice for functions to solve exactly one task. do_drawing is busy with drawing lines and pixels, on_draw_event is busy with processing events. Probably in this code snippet there is no real reason to make a separate function, but usually on_draw_event would be much more complicated.
I am trying to create a smoothly animated window in GTK+
Here is the code I came up with:
#include <gtk/gtk.h>
typedef struct {
GtkWindow *window;
int timePassed;
int fromX;
int fromY;
int toX;
int toY;
int time;
int step;
void (*callback)(void*);
void *data;
} AnimationStructure;
double coolBezier(double t) {
double register one_minus_t_powered;
double register one_minus_t_;
one_minus_t_ = 1 - t;
one_minus_t_powered = one_minus_t_ * one_minus_t_;
return one_minus_t_powered * 0.0 + one_minus_t_ * 2.0 * t * -1.00 + t * t * 1.0;
}
gboolean animate_window_timeout(AnimationStructure *ani) {
double time = coolBezier((double) ani->timePassed / ani->time);
if (time >= 1.0) {
gtk_window_move(ani->window, ani->fromX + ani->toX, ani->fromY + ani->toY);
ani->callback(ani->data);
g_free(ani);
return FALSE;
}
printf("FromX: %d; FromY: %d\n", ani->fromX, ani->fromY);
gtk_window_move(ani->window, ani->fromX + (ani->toX * time), ani->fromY + (ani->toY * time));
ani->timePassed += ani->step;
return TRUE;
}
void animate_window(GtkWindow *window, int x, int y, int time, int step_time, void (*callback)(void*), void* data) {
AnimationStructure *ani = g_new(AnimationStructure, 1);
ani->window = window;
ani->toX = x;
ani->toY = y;
ani->timePassed = 0;
ani->time = time;
ani->step = step_time;
ani->callback = callback;
ani->data = data;
gtk_window_get_position(window, &(ani->fromX), &(ani->fromY));
g_timeout_add(step_time, (GSourceFunc) animate_window_timeout, ani);
}
int main (int argc, char *argv[]) {
GtkWindow *window;
gtk_init(&argc, &argv);
window = (GtkWindow*) gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_position(window, GTK_WIN_POS_CENTER);
gtk_window_set_default_size(window, 300, 300);
g_signal_connect_swapped(window, "destroy", gtk_main_quit, NULL);
animate_window(window, 300, 0, 1000, 15, NULL, NULL);
gtk_widget_show_all(GTK_WIDGET(window));
gtk_main();
return 0;
}
This works and when I applied it to the notification window the effect was like on this gif:
But the window is not smooth as I would like it to be, you can notice that at the end the window almost jumps 50px from left to right.
how do I implement mouse wheel up/down events using C GTK3?
I have adapted this code in order to handle mouse scoll events:
#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;
}
static gboolean mouse_scroll (GtkWidget *widget,
GdkEvent *event,
gpointer user_data)
{
printf("scrolled up! \n");
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);
gtk_widget_add_events(window, GDK_SCROLL_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);
g_signal_connect(window, "scroll-event",
G_CALLBACK(mouse_scroll), 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 only catches a general mouse event, irrespective of whether the mouse is scrolled up or down. I want to know how can I get it to printf("scrolled up! \n") only when the mouse wheel is scrolled up.
Any ideas?
The *event parameter in mouse_scroll should point to GdkEventScroll structure which has direction member:
GdkScrollDirection direction; the direction to scroll to (one of
GDK_SCROLL_UP, GDK_SCROLL_DOWN, GDK_SCROLL_LEFT,
GDK_SCROLL_RIGHT or GDK_SCROLL_SMOOTH).
I had a programm with C and libchamplain which showed a map. worked fine. Now i wanted to integrate it into an gtk application. But at running, i get a segfault:
#include <stdio.h>
#include <gtk/gtk.h>
#include <champlain/champlain.h>
#include <champlain-gtk/champlain-gtk.h>
#include <clutter-gtk/clutter-gtk.h>
#include <math.h>
#include "GPS_stub.h"
#define MARKER_SIZE 10
#define MAX_SIZ 4096
static gboolean draw_center(ClutterCanvas *canvas, cairo_t *cr, int width, int height){
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_arc(cr, MARKER_SIZE / 2.0, MARKER_SIZE / 2.0, MARKER_SIZE / 2.0, 0, 2 * M_PI);
cairo_close_path(cr);
cairo_set_source_rgba(cr, 0.1, 0.1, 0.9, 1.0);
cairo_fill(cr);
return TRUE;
}
static ClutterActor *create_marker(void){
ClutterActor *marker;
ClutterActor *bg;
ClutterContent *canvas;
ClutterTransition *transition;
marker = champlain_custom_marker_new();
canvas = clutter_canvas_new();
clutter_canvas_set_size(CLUTTER_CANVAS(canvas), MARKER_SIZE, MARKER_SIZE);
g_signal_connect(canvas, "draw", G_CALLBACK(draw_center), NULL);
bg = clutter_actor_new();
clutter_actor_set_size(bg, MARKER_SIZE, MARKER_SIZE);
clutter_actor_set_content(bg, canvas);
clutter_content_invalidate(canvas);
g_object_unref(canvas);
clutter_actor_add_child(marker, bg);
clutter_actor_set_pivot_point(bg, 0.5, 0.5);
clutter_actor_set_position(bg, -MARKER_SIZE, -MARKER_SIZE);
transition = clutter_property_transition_new("opacity");
clutter_actor_set_easing_mode(bg, CLUTTER_EASE_OUT_SINE);
clutter_timeline_set_duration(CLUTTER_TIMELINE(transition), 1000);
clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transition), -1);
clutter_transition_set_from(transition, G_TYPE_UINT, 255);
clutter_transition_set_to(transition, G_TYPE_UINT, 0);
clutter_actor_add_transition(bg, "animate-opacity", transition);
transition = clutter_property_transition_new("scale-x");
clutter_actor_set_easing_mode(bg, CLUTTER_EASE_OUT_SINE);
clutter_timeline_set_duration(CLUTTER_TIMELINE(transition), 1000);
clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transition), -1);
clutter_transition_set_from(transition, G_TYPE_FLOAT, 0.5);
clutter_transition_set_to(transition, G_TYPE_FLOAT, 1.0);
clutter_actor_add_transition(bg, "animate-scale-x", transition);
transition = clutter_property_transition_new("scale-y");
clutter_actor_set_easing_mode(bg, CLUTTER_EASE_OUT_SINE);
clutter_timeline_set_duration(CLUTTER_TIMELINE(transition), 1000);
clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transition), -1);
clutter_transition_set_from(transition, G_TYPE_FLOAT, 0.5);
clutter_transition_set_to(transition, G_TYPE_FLOAT, 1.0);
clutter_actor_add_transition(bg, "animate-scale-y", transition);
return marker;
}
static gboolean gps_callback(ChamplainMarker *marker[MAX_SIZ]){
return TRUE;
}
typedef struct{
double lat;
double lng;
} GpsCallbackData;
static void load_map(GpsCallbackData *data){
ClutterActor *actor, *stage;
ChamplainMarkerLayer *layer;
ClutterActor *marker[MAX_SIZ];
stage = clutter_stage_new();
clutter_actor_set_size(stage, 800, 600);
g_signal_connect(stage, "destroy", G_CALLBACK(clutter_main_quit), NULL);
actor = champlain_view_new();
clutter_actor_set_size(CLUTTER_ACTOR(actor), 800, 600);
clutter_actor_add_child(stage, actor);
layer = champlain_marker_layer_new_full(CHAMPLAIN_SELECTION_SINGLE);
clutter_actor_show(CLUTTER_ACTOR(layer));
champlain_view_add_layer(CHAMPLAIN_VIEW(actor), CHAMPLAIN_LAYER(layer));
for(int i = 0; i < MAX_SIZ/40; i++){
marker[i] = create_marker();
champlain_marker_layer_add_marker(layer, CHAMPLAIN_MARKER(marker[i]));
champlain_location_set_location(CHAMPLAIN_LOCATION(marker[i]), data->lat+i/10, data->lng+i/10);
}
g_object_set(G_OBJECT(actor), "zoom-level", 2, "kinetic-mode", TRUE, NULL);
champlain_view_center_on(CHAMPLAIN_VIEW(actor), 40, 0);
g_timeout_add(1000, (GSourceFunc) gps_callback, CHAMPLAIN_MARKER(marker));
clutter_actor_show(stage);
clutter_main();
}
int main(int argc, char *argv[]){
GtkWidget *window, *button;
GpsCallbackData *gps;
double lat, lng;
gtk_clutter_init(&argc, &argv);
hs_init(&argc, &argv);
getGPS("166.82.21.200", &lat, &lng);
printf("GPS: %f %f\n", lat, lng);
gps->lat = lat;
gps->lng = lng;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
gtk_container_set_border_width(GTK_CONTAINER(window), 10);
button = gtk_button_new_with_label("Load Map");
g_signal_connect(button, "clicked", G_CALLBACK(load_map), &gps);
gtk_container_add(GTK_CONTAINER(window), button);
gtk_widget_show(window);
gtk_main();
hs_exit();
return 0;
}
like you can see in main , it prints out coordinates. This works fine. But then after the printing i'm getting an segfault.
Why?
I have a same problem on ARM Linux from here.
stage = clutter_stage_new();
clutter_actor_set_size(stage, 800, 600);
When I removed or blocked "clutter_actor_set", there's no seg fault error.
actor is O.K, but stage is matter.
But on X86 Ubuntu desktop, it's O.K.