I have a technical question concerning the creation and destruction of Cairo contexts. My simple code follows. There are multiple creations and destructions of a Cairo context, which seem redundant. I’m confused as to which is primary: is it the cairo_surface or the cairo_context ? The context is created from the surface which certainly suggests the surface is primary. I feel I should be able to create a Cairo context just once (in the configure_event_callback ?) and keep it around for the duration of this simple program.
------------------------------------------------------------------------------*/
static cairo_surface_t *cairo_surface = NULL;
/*------------------------------------------------------------------------------
---------------------------------------------------------------------------*/
static void clear_surface(void)
{
cairo_t *cairo_context;
cairo_context = cairo_create(cairo_surface);
cairo_set_source_rgb(cairo_context, 0.7, 0.7, 0.7);
cairo_paint(cairo_context);
cairo_destroy(cairo_context);
}
/*------------------------------------------------------------------------------
https://developer-old.gnome.org/gtkmm-tutorial/stable/
sec-cairo-drawing-arcs.html.en
---------------------------------------------------------------------------*/
static inline void draw_brush(GtkWidget *widget, gdouble x, gdouble y)
{
cairo_t *cairo_context;
// Paint to the surface, where we store our state
cairo_context = cairo_create(cairo_surface);
//cairo_set_source_rgb(cairo_surface, 0.0, 0.2, 1.0);
// default is black
cairo_arc
(
cairo_context,
x, y,
2.0, // radius
0.0, 2 * M_PI // a full circle
);
cairo_fill(cairo_context);
cairo_destroy(cairo_context);
// the affected region of the drawing area
gtk_widget_queue_draw_area(widget, x - 2, y - 2, 4, 4);
}
/*------------------------------------------------------------------------------
Handle motion events by continuing to draw if button 1 is
still held down. The ::motion-notify signal handler receives
a GdkEventMotion struct which contains this information.
https://stackoverflow.com/questions/48557583/
freehand-drawing-gtk-set-event-compressionfalse-does-not-work
---------------------------------------------------------------------------*/
static gboolean motion_notify_event_callback
(
GtkWidget *widget,
GdkEventMotion *event,
gpointer data
)
{
// paranoia check, in case we haven't gotten a configure event
if(cairo_surface == NULL) return FALSE;
if(event->state & GDK_BUTTON2_MASK)
{
#if 1
static gdouble last_x, last_y;
printf
(
"\t%6.2lf %6.2lf delta %6.2lf\n",
event->x, event->y,
hypot(event->x - last_x, event->y - last_y)
);
last_x = event->x; last_y = event->y;
#endif
draw_brush(widget, event->x, event->y);
}
return TRUE;
}
/*------------------------------------------------------------------------------
Handle button press events by either drawing a rectangle
or clearing the surface, depending on which button was pressed.
The ::button-press signal handler receives a GdkEventButton
struct which contains this information.
---------------------------------------------------------------------------*/
static gboolean button_press_event_callback
(
GtkWidget *widget,
GdkEventButton *event,
gpointer data
)
{
// paranoia check, in case we haven't gotten a configure event
if(cairo_surface == NULL) return FALSE;
if(event->button == GDK_BUTTON_SECONDARY)
{
clear_surface();
gtk_widget_queue_draw(widget);
}
return TRUE;
}
/*------------------------------------------------------------------------------
Create a new surface of the appropriate size to store our scribbles
---------------------------------------------------------------------------*/
static gboolean configure_event_callback
(
GtkWidget *widget,
GdkEventConfigure *event,
gpointer data
)
{
cairo_t *cairo_context;
if(cairo_surface) cairo_surface_destroy(cairo_surface);
cairo_surface = gdk_window_create_similar_surface
(
gtk_widget_get_window(widget),
CAIRO_CONTENT_COLOR,
gtk_widget_get_allocated_width(widget),
gtk_widget_get_allocated_height(widget)
);
cairo_context = cairo_create(cairo_surface);
cairo_set_source_rgb(cairo_context, 0.7, 0.7, 0.7);
cairo_paint(cairo_context);
cairo_destroy(cairo_context);
// this be the trick
gdk_window_set_event_compression(((GdkEventButton *)event)->window, false);
return TRUE;
}
/*------------------------------------------------------------------------------
Redraw the screen from the surface. Note that the ::draw
signal receives a ready-to-be-used cairo_t that is already
clipped to only draw the exposed areas of the widget
---------------------------------------------------------------------------*/
static gboolean draw_callback
(
GtkWidget *widget,
cairo_t *cairo_context,
gpointer data
)
{
cairo_set_source_surface(cairo_context, cairo_surface, 0, 0);
cairo_paint(cairo_context);
cairo_set_source_rgb(cairo_context, 0.0, 0.2, 1.0);
return FALSE;
}
/*------------------------------------------------------------------------------
GtkWidget *event_box;
event_box = gtk_event_box_new();
gtk_container_add(GTK_CONTAINER(event_box), drawing_area);
------------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
GtkWidget *scroll_window;
GtkWidget *capture_button, *email_button;
GtkWidget *frame;
GtkWidget *drawing_area;
printf("Gtk+ version %d.%d\n", gtk_major_version, gtk_minor_version);
image_display_width = 800; image_display_height = 800;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
grid = gtk_grid_new();
frame = gtk_frame_new(NULL);
drawing_area = gtk_drawing_area_new();
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_NONE);
gtk_window_set_title(GTK_WINDOW(window), version);
gtk_container_set_border_width(GTK_CONTAINER(window), 5);
gtk_container_add(GTK_CONTAINER(window), grid);
gtk_grid_set_row_spacing(GTK_GRID(grid), 2);
gtk_grid_set_column_spacing(GTK_GRID(grid), 5);
gtk_container_add(GTK_CONTAINER(frame), drawing_area);
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
scroll_window = gtk_scrolled_window_new(NULL, NULL);
gtk_container_set_border_width(GTK_CONTAINER(scroll_window), 5);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll_window),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_widget_set_size_request
(
scroll_window, image_display_width, image_display_height
);
gtk_container_add(GTK_CONTAINER(scroll_window), frame);
gtk_grid_attach(GTK_GRID(grid), scroll_window, 1, 1, 1, 10);
capture_button = gtk_button_new_with_label("Capture Cairo Surface");
email_button = gtk_button_new_with_label("Email sketch to yyy");
gtk_grid_attach(GTK_GRID(grid), capture_button, 2, 1, 1, 1);
gtk_grid_attach(GTK_GRID(grid), email_button, 2, 2, 1, 1);
g_signal_connect(capture_button, "clicked",
G_CALLBACK(capture_surface), NULL);
g_signal_connect(email_button, "clicked",
G_CALLBACK(email_person), NULL);
// Signals used to handle the backing surface
g_signal_connect(drawing_area, "draw",
G_CALLBACK(draw_callback), NULL);
g_signal_connect(drawing_area,"configure-event",
G_CALLBACK(configure_event_callback), NULL);
g_signal_connect(drawing_area, "motion-notify-event",
G_CALLBACK(motion_notify_event_callback), NULL);
g_signal_connect(drawing_area, "button-press-event",
G_CALLBACK(button_press_event_callback), NULL);
gtk_widget_set_events
(
drawing_area,
gtk_widget_get_events(drawing_area)
| GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK
);
g_signal_connect(window, "destroy",
G_CALLBACK(gtk_main_quit), NULL);
gtk_widget_show_all(window);
gtk_main();
return EXIT_SUCCESS;
}
/*------------------------------------------------------------------------------
---------------------------------------------------------------------------*/