How to draw in a subclassed DrawingArea widget? - gtk

I want to implement custom widgets by subclassing DrawingArea Widget, for this I need to draw using cairo. It seems like in gtk3 there is a new signal called 'draw' introduced. How do I draw inside the widget? Should the map and realize signals also be overrided?
A simple example code would be very helpful. Thanks.

To put it simply, you'll need to override the draw signal which will supply a Cairo context:
gboolean
user_function (GtkWidget *widget,
CairoContext *cr,
gpointer user_data)
Then you can use the CairoContext crto draw the actual contents of the widget.
From the C API:
The GtkDrawingArea widget is used for creating custom user interface
elements. It’s essentially a blank widget; you can draw on it. After
creating a drawing area, the application may want to connect to:
Mouse and button press signals to respond to input from the user. (Use
gtk_widget_add_events() to enable events you wish to receive.)
The “realize” signal to take any necessary actions when the widget is instantiated on a particular display. (Create GDK resources in
response to this signal.)
The “size-allocate” signal to take any necessary actions when the widget changes size.
The “draw” signal to handle redrawing the contents of the widget.
The widget should queue some draws when the widget changes, for example, on size allocate you should use gtk_widget_queue_draw to force the widget to draw itsef again.
Example - Using a drawing area not as sub classing it but the concept remains:
(taken from Gnome C API)
gboolean
draw_callback (GtkWidget *widget, cairo_t *cr, gpointer data)
{
guint width, height;
GdkRGBA color;
GtkStyleContext *context;
context = gtk_widget_get_style_context (widget);
width = gtk_widget_get_allocated_width (widget);
height = gtk_widget_get_allocated_height (widget);
gtk_render_background (context, cr, 0, 0, width, height);
cairo_arc (cr,
width / 2.0, height / 2.0,
MIN (width, height) / 2.0,
0, 2 * G_PI);
gtk_style_context_get_color (context,
gtk_style_context_get_state (context),
&color);
gdk_cairo_set_source_rgba (cr, &color);
cairo_fill (cr);
return FALSE;
}
[...]
GtkWidget *drawing_area = gtk_drawing_area_new ();
gtk_widget_set_size_request (drawing_area, 100, 100);
g_signal_connect (G_OBJECT (drawing_area), "draw",
G_CALLBACK (draw_callback), NULL);
You should also read about Height-for-width Geometry Management in GtkWidget
I've used C because there was no reference to programming language on your question and at the same time it's the original API from which all other are written.
There are some examples about creating Gtk+ custom Widgets on the internet.

Related

GTK ignores any type of window positioning

I've been trying to position a splash screen at the center of the screen. In Windows, such a request is easy using SetWindowPos and a bit of geometric arithmetic. I found out that all requests to move a window are ignored by the windowing manager. So my question is, how come I see so many applications with pretty splash screens properly centered? I started with GTK_WINDOW_TOPLEVEL BTW and just switch to popup trying a few things. Setting the gravity and position do not fail, they are just ignored. Even defined in a .glade file, the window is ignored.
{
GtkWidget *pNewWindow = gtk_window_new(GTK_WINDOW_POPUP);
gtk_window_move(GTK_WINDOW(pNewWindow), 0, 0);
gtk_widget_show_all(pNewWindow);
while (gtk_events_pending())
gtk_main_iteration();
gtk_widget_set_size_request(pNewWindow, width, height);
gtk_window_set_decorated(GTK_WINDOW(pNewWindow), FALSE);
// gtk_window_set_position(GTK_WINDOW(pNewWindow), GTK_WIN_POS_CENTER_ALWAYS);
gtk_window_set_resizable(GTK_WINDOW(pNewWindow), FALSE);
// gtk_window_set_gravity(GTK_WINDOW(pNewWindow), GDK_GRAVITY_CENTER);
gtk_window_move(GTK_WINDOW(pNewWindow), 0, 0);
while (gtk_events_pending())
gtk_main_iteration();
return pNewWindow;
}

Gtk - draw event fired for wrong widget, and widget not redrawn

I'm trying to create a custom scrollable text area. I created a DrawingArea and a ScrollBar inside a Grid. I have attached the draw event of DrawingArea to this.on_draw method which simply looks at ScrollBar's value and moves the Cairo.Context appropriately before drawing the Pango.Layout.
The first problem is that this.on_draw is getting invoked whenever the ScrollBar is touched even though I have not registered any events with ScrollBar. How do I prevent this, or check this?
The second problem is that even though this.on_draw is invoked, the changes made to the Context is not displayed unless the ScrollBar value is near 0 or 100 (100 is the upper value of Adjustment). Why is this happening?
I did find out that if I connect the value_changed event of ScrollBar to a method that calls queue_redraw of DrawingArea, it would invoke this.on_draw and display it properly after it. But due to the second problem, I think this.on_draw is getting invoked too many times unnecessarily. So, what is the "proper" way of accomplishing this?
using Cairo;
using Gdk;
using Gtk;
using Pango;
public class Texter : Gtk.Window {
private Gtk.DrawingArea darea;
private Gtk.Scrollbar scroll;
private string text = "Hello\nWorld!";
public Texter () {
GLib.Object (type: Gtk.WindowType.TOPLEVEL);
Gtk.Grid grid = new Gtk.Grid();
this.add (grid);
var drawing_area = new Gtk.DrawingArea ();
drawing_area.set_size_request (200, 200);
drawing_area.expand = true;
drawing_area.draw.connect (this.on_draw);
grid.attach (drawing_area, 0, 0);
var scrollbar = new Gtk.Scrollbar (Gtk.Orientation.VERTICAL,
new Gtk.Adjustment(0, 0, 100, 0, 0, 1));
grid.attach (scrollbar, 1, 0);
this.darea = drawing_area;
this.scroll = scrollbar;
this.destroy.connect (Gtk.main_quit);
}
private bool on_draw (Gtk.Widget sender, Cairo.Context ctx) {
ctx.set_source_rgb (0.9, 0.9, 0.9);
ctx.paint ();
var y_offset = this.scroll.get_value();
stdout.printf("%f\n", y_offset);
ctx.set_source_rgb (0.25, 0.25, 0.25);
ctx.move_to(0, 100 - y_offset);
var layout = Pango.cairo_create_layout(ctx);
layout.set_font_description(Pango.FontDescription.from_string("Sans 12"));
layout.set_auto_dir(false);
layout.set_text(this.text, this.text.length);
Pango.cairo_show_layout(ctx, layout);
return false;
}
static int main (string[] args) {
Gtk.init (ref args);
var window = new Texter ();
window.show_all ();
Gtk.main ();
return 0;
}
}
Also, please point out any (possibly unrelated) mistake if you find one in the above code.
The part that you are missing is that a draw signal does not mean "redraw everything". Instead, GTK+ sets the clip region of the cairo context to the part that needs to be redrawn, so everything else you do doesn't have any effect. The cairo function cairo_clip_extents() will tell you what that region is. The queue_draw_area() method on GtkWidget will allow you to explicitly mark a certain area for drawing, instead of the entire widget.
But your approach to scrollbars is wrong anyway: you're trying to build the entire infrastructure from scratch! Consider using a GtkScrolledWindow instead. This automatically takes care of all the details of scrolling for you, and will give you the overlay scrollbars I mentioned. All you need to do is set the size of the GtkDrawingArea to the size you want it to be, and GtkScrolledWindow will do the rest. The best way to do this is to subclass GtkDrawingArea and override the get_preferred_height() and/or get_preferred_width() virtual functions (being sure to set both minimum and natural sizes to the sizes you want for that particular dimension). If you ever need to change this size later, call the queue_resize() method of GtkWidget. (You probably could get away with just using set_size_request(), but what I described is the preferred way of doing this.) Doing this also gives you the advantage of not having to worry about transforming your cairo coordinates; GtkScrolledWindow does this for you.

How to change selection shape marker in "gdk_pixbuf_composite" with gtk c?

I use this code for crop selection:
gboolean mouse_press_callback(GtkWidget *event_box,
GdkEventButton *event,
gpointer data)
{
if (img1buffer == NULL)
return TRUE;
static gint press_x = 0, press_y = 0, rel_x = 0, rel_y = 0;
GtkAllocation ebox;
gint img1_x_offset = 0, img1_y_offset = 0;
gtk_widget_get_allocation(event_box, &ebox);
img1_x_offset = (ebox.width - width) / 2;
img1_y_offset = (ebox.height - height) / 2;
if (event->type == GDK_BUTTON_PRESS)
{
press_x = event->x - img1_x_offset;
press_y = event->y - img1_y_offset;
//g_print ("Event box clicked at coordinates %f,%f\n",
//event->x - img1_x_offset, event->y - img1_y_offset);
}
else if (event->type == GDK_BUTTON_RELEASE)
{
rel_x = event->x - img1_x_offset;
rel_y = event->y - img1_y_offset;
//g_print ("Event box released at coordinates %f,%f\n",
//event->x - img1_x_offset, event->y - img1_y_offset);
dest_x = rel_x < press_x ? rel_x : press_x;
dest_y = rel_y < press_y ? rel_y : press_y;
dest_width = abs(rel_x - press_x);
dest_height = abs(rel_y - press_y);
// mark user selection in image
GdkPixbuf *img1buffer_resized = gdk_pixbuf_scale_simple(img1buffer, width, height, GDK_INTERP_TILES);
gdk_pixbuf_composite(croppic, img1buffer_resized, dest_x, dest_y, dest_width, dest_height, 0, 0, 1, 1, GDK_INTERP_TILES, 170);
gtk_image_set_from_pixbuf(GTK_IMAGE(img1), img1buffer_resized);
}
return TRUE;
}
that in main function:
croppic = gdk_pixbuf_new_from_file("E:/Works for Gov Project/DOC/GUI/logogui1/crop_bg.png", NULL);
img1 = gtk_image_new();
event_box = gtk_event_box_new();
gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), FALSE);
gtk_container_add(GTK_CONTAINER(event_box), img1);
gtk_container_add(GTK_CONTAINER(frame1), event_box);
g_signal_connect(G_OBJECT(event_box), "button_press_event", G_CALLBACK(mouse_press_callback), NULL);
g_signal_connect(G_OBJECT(event_box), "button-release-event", G_CALLBACK(mouse_press_callback), NULL);
and "crop_bg.png" is:
But I want to a selection shape similar to in paint software:
What ideas on how to solve this task would you suggest? Or on what resource on the internet can I find help?
You are trying to draw on top of a GtkImage. This isn't the best way to do custom drawing, and as you've noticed, gdk_pixbuf_composite() is rather limited.
Instead, you'll want to do drawing the proper way, using the ::draw signal and cairo. cairo is the vector graphics library that GTK+ 3 uses to draw its own widgets, and the ::draw signal gives you a cairo context to draw with:
gboolean draw(GtkWidget *widget, cairo_t *cr, void *data);
cairo itself is easy to use; it's well documented and has a whole bunch of samples. What you want to do for your purposes is make a dashed stroked rectangle.
In addition, instead of using a GtkImage, you'll want to use GtkDrawingArea. GtkDrawingArea is specifically designed to be drawn on, and with a little extra work can be made to handle your events.
The last piece of the puzzle, then, is how do you start drawing when the mouse events come in? You don't get a cairo_t in button-press-event or button-release-event, so you can't draw there. Indeed, GTK+ is optimized so that it only draws when necessary. To indicate that it's necessary to draw, you can use the gtk_widget_queue_draw() method. There are variations on this method that mark only a subset of the widget to be redrawn (you can get this subset back with cairo_clip_extents() from within the ::draw handler).
Remember that widget coordinates are floating-point; so are cairo coordinates.
Let's demonstrate. I'm going to use global variables for this; you probably don't want to.
gdouble x0, y0;
gdouble x1, y1;
These will store the endpoints of the rectangle. When we press the mouse button, we want to start the drawing:
gboolean button_press_event(GtkWidget *widget, GdkEventButton *e, gpointer data)
{
// ...
x0 = e->x;
y0 = e->y;
x1 = e->x; // start with a zero-sized rectangle
y1 = e->y;
// ...
}
When we move the mouse, we want to change x1 and y1 to the new mouse coordinates, and then update the rectangle. To optimize things, we'll only update the area that changed:
gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *e, gpointer data)
{
// ...
// first queue both old and new rectangles for drawing
gtk_widget_queue_draw_area(widget,
x0, y0,
MAX(x1, e->x), MAX(y1, e->y));
// then set the new rectangle
x1 = e->x;
y1 = e->y;
// ...
}
You can probably figure out what to do on button-release-event from the above. draw would look something like
gboolean draw(GtkWidget *widget, cairo_t *cr, gpointer data)
{
// ...
cairo_rectangle(cr, x0, y0, x1 - x0, y1 - y0);
// set up a dashed solid-color stroke
cairo_stroke(cr);
// ...
}
Good luck!

Gtk3 loading PixbufAnimation inside DrawingArea?

For the purpose of mine gstreamer application I tought about simple loader before I give a handle of DrawingArea widget to sink element.The basic idea was to load an animated .gif inside Gtk.DrawingArea but I run on the problem with documentation.I found out about PixbufAnimation and I used it with Gtk.Image widget but the same logic doesn't work for Gtk.DrawingArea and since it doesn't have add method I don't know what to do so as my last resort I came here to get a help.
This is what I did with Gtk.Image:
from gi.repository import Gdk,Gtk,GdkPixbuf
class animatedWin(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self,width_request=640,height_request=480)
self.canvas=Gtk.Image()
self.add(self.canvas)
self.load_file()
self.connect("delete-event",self.Gtk.main_quit)
def load_file(self):
self.loader=GdkPixbuf.PixbufAnimation.new_from_file("loader.gif")
self.canvas.set_from_animation(self.loader)
app=animatedWin()
app.show_all()
Gtk.main()
is it possible to achieve the same thing with DrawingArea ?
DrawingArea like most widgets in gtk3 uses cairo for drawing on them. Cairo draws on surfaces using context. You can convert pixbuf into surface by
public Surface Gdk.cairo_surface_create_from_pixbuf (Pixbuf pixbuf, int scale, Window? for_window)
And back by
public Pixbuf? Gdk.pixbuf_get_from_surface (Surface surface, int src_x, int src_y, int width, int height)
(taken from valadoc.org)
Example code snippet from my drawing app (I'm learning Vala while I writing it, so it may not be best implementation):
private void on_scale (Gtk.Button button) { // on button press
var my_pixbuf = Gdk.pixbuf_get_from_surface (this.buf_surface, 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
var tmp_surface = Gdk.cairo_surface_create_from_pixbuf (my_pixbuf, 2, null);
var ctx = this.ccc; //this.ccc is context of drawing surface
ctx.set_source_surface (tmp_surface, 0, 0);
ctx.paint();
drawing_area.queue_draw(); // ask drawing_area to redraw, on redraw I have function/method that will paint drawing_area widget surface with drawing surface
}
PS. see http://valadoc.org/#!api=cairo/Cairo for more info on cairo. As I see it, cairo used for vector graphics and pixbuf for raster.

How do I change the colors of an arbitrary widget in GTK+?

If I'm writing an application that wants to communicate some information through the use of color, how can I change the background and foreground colors of a given widget? I would like to know how to do this in glade if it's possible, as well as programmatically (to a computed color).
I want to know how to do this to a complex widget as well, for example, an HBox that contains a VBox that contains some Labels.
Ideally this would also include a solution solution that allows me to tint the widget's existing colors, and identify the average colors of any images in use by the theme, so that I can programmatically compensate for any color choices which might make text unreadable or otherwise clashing - but I would be happy if I could just turn a button red.
Example program:
#include <gtk/gtk.h>
static void on_destroy(GtkWidget* widget, gpointer data)
{
gtk_main_quit ();
}
int main (int argc, char* argv[])
{
GtkWidget* window;
GtkWidget* button;
gtk_init(&argc, &argv);
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(G_OBJECT (window), "destroy",
G_CALLBACK (on_destroy), NULL);
button = gtk_button_new_with_label("Hello world!");
GdkColor red = {0, 0xffff, 0x0000, 0x0000};
GdkColor green = {0, 0x0000, 0xffff, 0x0000};
GdkColor blue = {0, 0x0000, 0x0000, 0xffff};
gtk_widget_modify_bg(button, GTK_STATE_NORMAL, &red);
gtk_widget_modify_bg(button, GTK_STATE_PRELIGHT, &green);
gtk_widget_modify_bg(button, GTK_STATE_ACTIVE, &blue);
gtk_container_add(GTK_CONTAINER(window), button);
gtk_widget_show_all(window);
gtk_main();
return 0;
}
The best documentation that I know of is the one available here: http://ometer.com/gtk-colors.html
You can always use gtk_widget_override_color () and gtk_widget_override_background_color (). These two functions allow you to change the color of a widget. But it is better to use CSS classes and regions in your widget/container implementation through gtk_style_context_add_class() and gtk_style_context_add_region().
To modify the color of a widget you can initialize a color and use it to modify the color of the widget:
GdkColor color;
gdk_color_parse("#00FF7F", &color);
gtk_widget_modify_bg(widget, GTK_STATE_NORMAL, &color);
To use an image instead of color:
GdkPixbuf *image = NULL;
GdkPixmap *background = NULL;
GtkStyle *style = NULL;
image = gdk_pixbuf_new_from_file ("background.jpg", NULL);
gdk_pixbuf_render_pixmap_and_mask (image, &background, NULL, 0);
style = gtk_style_new ();
style->bg_pixmap [0] = background;
gtk_widget_set_style (GTK_WIDGET(widget), GTK_STYLE (style));