Root/
| 1 | /* |
| 2 | * gui_util.c - GUI helper functions |
| 3 | * |
| 4 | * Written 2009, 2010 by Werner Almesberger |
| 5 | * Copyright 2009, 2010 by Werner Almesberger |
| 6 | * |
| 7 | * This program is free software; you can redistribute it and/or modify |
| 8 | * it under the terms of the GNU General Public License as published by |
| 9 | * the Free Software Foundation; either version 2 of the License, or |
| 10 | * (at your option) any later version. |
| 11 | */ |
| 12 | |
| 13 | |
| 14 | #include <stdlib.h> |
| 15 | #include <math.h> |
| 16 | #include <gtk/gtk.h> |
| 17 | |
| 18 | #include "util.h" |
| 19 | #include "gui_style.h" |
| 20 | #include "gui.h" |
| 21 | #include "gui_util.h" |
| 22 | |
| 23 | |
| 24 | struct draw_ctx draw_ctx; |
| 25 | |
| 26 | |
| 27 | /* ----- look up a color --------------------------------------------------- */ |
| 28 | |
| 29 | |
| 30 | GdkColor get_color(const char *spec) |
| 31 | { |
| 32 | GdkColormap *cmap; |
| 33 | GdkColor color; |
| 34 | |
| 35 | cmap = gdk_drawable_get_colormap(root->window); |
| 36 | if (!gdk_color_parse(spec, &color)) |
| 37 | abort(); |
| 38 | if (!gdk_colormap_alloc_color(cmap, &color, FALSE, TRUE)) |
| 39 | abort(); |
| 40 | return color; |
| 41 | } |
| 42 | |
| 43 | |
| 44 | /* ----- lines with a width ------------------------------------------------ */ |
| 45 | |
| 46 | |
| 47 | void set_width(GdkGC *gc, int width) |
| 48 | { |
| 49 | gdk_gc_set_line_attributes(gc, width < 1 ? 1 : width, |
| 50 | GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND); |
| 51 | } |
| 52 | |
| 53 | |
| 54 | /* ----- backing store ----------------------------------------------------- */ |
| 55 | |
| 56 | |
| 57 | void free_pix_buf(struct pix_buf *buf) |
| 58 | { |
| 59 | g_object_unref(G_OBJECT(buf->buf)); |
| 60 | free(buf); |
| 61 | } |
| 62 | |
| 63 | |
| 64 | struct pix_buf *save_pix_buf(GdkDrawable *da, int xa, int ya, int xb, int yb, |
| 65 | int border) |
| 66 | { |
| 67 | struct pix_buf *buf; |
| 68 | int w, h; |
| 69 | |
| 70 | if (xa > xb) |
| 71 | SWAP(xa, xb); |
| 72 | if (ya > yb) |
| 73 | SWAP(ya, yb); |
| 74 | buf = alloc_type(struct pix_buf); |
| 75 | buf->da = da; |
| 76 | buf->x = xa-border; |
| 77 | buf->y = ya-border; |
| 78 | w = xb-xa+1+2*border; |
| 79 | h = yb-ya+1+2*border; |
| 80 | if (buf->x < 0) { |
| 81 | w += buf->x; |
| 82 | buf->x = 0; |
| 83 | } |
| 84 | if (buf->y < 0) { |
| 85 | h += buf->y; |
| 86 | buf->y = 0; |
| 87 | } |
| 88 | buf->buf = gdk_pixbuf_get_from_drawable(NULL, da, NULL, |
| 89 | buf->x, buf->y, 0, 0, w, h); |
| 90 | return buf; |
| 91 | } |
| 92 | |
| 93 | |
| 94 | void restore_pix_buf(struct pix_buf *buf) |
| 95 | { |
| 96 | gdk_draw_pixbuf(buf->da, NULL, buf->buf, 0, 0, buf->x, buf->y, -1, -1, |
| 97 | GDK_RGB_DITHER_NORMAL, 0, 0); |
| 98 | free_pix_buf(buf); |
| 99 | } |
| 100 | |
| 101 | |
| 102 | /* ----- arcs and circles -------------------------------------------------- */ |
| 103 | |
| 104 | |
| 105 | void draw_arc(GdkDrawable *da, GdkGC *gc, int fill, |
| 106 | int x, int y, int r, double a1, double a2) |
| 107 | { |
| 108 | /* |
| 109 | * This adjustment handles two distinct cases: |
| 110 | * - if a1 == a2, we make sure we draw a full circle |
| 111 | * - the end angle a2 must always be greater than the start angle a1 |
| 112 | */ |
| 113 | if (a2 <= a1) |
| 114 | a2 += 360; |
| 115 | gdk_draw_arc(da, gc, fill, x-r, y-r, 2*r, 2*r, a1*64, (a2-a1)*64); |
| 116 | } |
| 117 | |
| 118 | |
| 119 | void draw_circle(GdkDrawable *da, GdkGC *gc, int fill, |
| 120 | int x, int y, int r) |
| 121 | { |
| 122 | draw_arc(da, gc, fill, x, y, r, 0, 360); |
| 123 | } |
| 124 | |
| 125 | |
| 126 | /* ----- labels in a box --------------------------------------------------- */ |
| 127 | |
| 128 | |
| 129 | GtkWidget *label_in_box_new(const char *s, const char *tooltip) |
| 130 | { |
| 131 | GtkWidget *evbox, *label; |
| 132 | |
| 133 | evbox = gtk_event_box_new(); |
| 134 | label = gtk_label_new(s); |
| 135 | gtk_misc_set_padding(GTK_MISC(label), 1, 1); |
| 136 | gtk_container_add(GTK_CONTAINER(evbox), label); |
| 137 | if (tooltip) |
| 138 | gtk_widget_set_tooltip_markup(evbox, tooltip); |
| 139 | return label; |
| 140 | } |
| 141 | |
| 142 | |
| 143 | GtkWidget *box_of_label(GtkWidget *label) |
| 144 | { |
| 145 | return gtk_widget_get_ancestor(label, GTK_TYPE_EVENT_BOX); |
| 146 | } |
| 147 | |
| 148 | |
| 149 | void label_in_box_fg(GtkWidget *label, const char *color) |
| 150 | { |
| 151 | GdkColor col = get_color(color); |
| 152 | |
| 153 | gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &col); |
| 154 | } |
| 155 | |
| 156 | |
| 157 | void label_in_box_bg(GtkWidget *label, const char *color) |
| 158 | { |
| 159 | GtkWidget *box; |
| 160 | GdkColor col = get_color(color); |
| 161 | |
| 162 | box = box_of_label(label); |
| 163 | gtk_widget_modify_bg(box, GTK_STATE_NORMAL, &col); |
| 164 | } |
| 165 | |
| 166 | |
| 167 | /* ----- generate a tool button with an XPM image -------------------------- */ |
| 168 | |
| 169 | |
| 170 | GtkWidget *make_image(GdkDrawable *drawable, char **xpm, const char *tooltip) |
| 171 | { |
| 172 | GdkPixmap *pixmap; |
| 173 | GtkWidget *image; |
| 174 | GdkColor white = get_color("white"); |
| 175 | |
| 176 | pixmap = gdk_pixmap_create_from_xpm_d(drawable, NULL, &white, xpm); |
| 177 | image = gtk_image_new_from_pixmap(pixmap, NULL); |
| 178 | gtk_misc_set_padding(GTK_MISC(image), 1, 1); |
| 179 | if (tooltip) |
| 180 | gtk_widget_set_tooltip_markup(image, tooltip); |
| 181 | return image; |
| 182 | } |
| 183 | |
| 184 | |
| 185 | GtkWidget *make_transparent_image(GdkDrawable *drawable, char **xpm, |
| 186 | const char *tooltip) |
| 187 | { |
| 188 | GdkPixmap *pixmap; |
| 189 | GdkBitmap *mask; |
| 190 | GtkWidget *image; |
| 191 | |
| 192 | pixmap = gdk_pixmap_create_from_xpm_d(drawable, &mask, NULL, xpm); |
| 193 | image = gtk_image_new_from_pixmap(pixmap, mask); |
| 194 | gtk_misc_set_padding(GTK_MISC(image), 1, 1); |
| 195 | if (tooltip) |
| 196 | gtk_widget_set_tooltip_markup(image, tooltip); |
| 197 | return image; |
| 198 | } |
| 199 | |
| 200 | |
| 201 | static void remove_child(GtkWidget *widget, gpointer data) |
| 202 | { |
| 203 | gtk_container_remove(GTK_CONTAINER(data), widget); |
| 204 | } |
| 205 | |
| 206 | |
| 207 | void vacate_widget(GtkWidget *widget) |
| 208 | { |
| 209 | gtk_container_foreach(GTK_CONTAINER(widget), remove_child, widget); |
| 210 | } |
| 211 | |
| 212 | |
| 213 | void set_image(GtkWidget *widget, GtkWidget *image) |
| 214 | { |
| 215 | vacate_widget(widget); |
| 216 | gtk_container_add(GTK_CONTAINER(widget), image); |
| 217 | gtk_widget_show_all(widget); |
| 218 | } |
| 219 | |
| 220 | |
| 221 | GtkWidget *tool_button(GtkWidget *bar, GdkDrawable *drawable, |
| 222 | char **xpm, const char *tooltip, |
| 223 | gboolean (*cb)(GtkWidget *widget, GdkEventButton *event, gpointer data), |
| 224 | gpointer data) |
| 225 | { |
| 226 | GtkWidget *image, *evbox; |
| 227 | GtkToolItem *item; |
| 228 | |
| 229 | /* |
| 230 | * gtk_radio_tool_button_new_from_widget is *huge*. We try to do things |
| 231 | * in a |
| 232 | * more compact way. |
| 233 | */ |
| 234 | |
| 235 | evbox = gtk_event_box_new(); |
| 236 | if (xpm) { |
| 237 | image = make_image(drawable, xpm, tooltip); |
| 238 | gtk_container_add(GTK_CONTAINER(evbox), image); |
| 239 | } |
| 240 | g_signal_connect(G_OBJECT(evbox), "button_press_event", |
| 241 | G_CALLBACK(cb), data); |
| 242 | |
| 243 | item = gtk_tool_item_new(); |
| 244 | gtk_container_add(GTK_CONTAINER(item), evbox); |
| 245 | |
| 246 | gtk_container_set_border_width(GTK_CONTAINER(item), 0); |
| 247 | |
| 248 | gtk_toolbar_insert(GTK_TOOLBAR(bar), item, -1); |
| 249 | |
| 250 | return evbox; |
| 251 | } |
| 252 | |
| 253 | |
| 254 | /* ----- render a text string ---------------------------------------------- */ |
| 255 | |
| 256 | |
| 257 | void render_text(GdkDrawable *da, GdkGC *gc, int x, int y, double angle, |
| 258 | const char *s, const char *font, double xalign, double yalign, |
| 259 | int xmax, int ymax) |
| 260 | { |
| 261 | GdkScreen *screen; |
| 262 | PangoRenderer *renderer; |
| 263 | PangoContext *context; |
| 264 | PangoLayout *layout; |
| 265 | PangoFontDescription *desc; |
| 266 | int width, height; |
| 267 | PangoMatrix m = PANGO_MATRIX_INIT; |
| 268 | double f_min, f; |
| 269 | |
| 270 | /* set up the renderer */ |
| 271 | |
| 272 | screen = gdk_drawable_get_screen(da); |
| 273 | renderer = gdk_pango_renderer_get_default(screen); |
| 274 | gdk_pango_renderer_set_drawable(GDK_PANGO_RENDERER(renderer), da); |
| 275 | gdk_pango_renderer_set_gc(GDK_PANGO_RENDERER(renderer), gc); |
| 276 | |
| 277 | /* start preparing the layout */ |
| 278 | |
| 279 | context = gdk_pango_context_get_for_screen(screen); |
| 280 | layout = pango_layout_new(context); |
| 281 | pango_layout_set_text(layout, s, -1); |
| 282 | |
| 283 | /* apply the font */ |
| 284 | |
| 285 | desc = pango_font_description_from_string(font); |
| 286 | pango_layout_set_font_description(layout, desc); |
| 287 | pango_font_description_free(desc); |
| 288 | |
| 289 | /* align and position the text */ |
| 290 | |
| 291 | pango_layout_get_size(layout, &width, &height); |
| 292 | f_min = 1.0; |
| 293 | if (xmax) { |
| 294 | f = xmax/((double) width/PANGO_SCALE); |
| 295 | if (f < f_min) |
| 296 | f_min = f; |
| 297 | } |
| 298 | if (ymax) { |
| 299 | f = ymax/((double) height/PANGO_SCALE); |
| 300 | if (f < f_min) |
| 301 | f_min = f; |
| 302 | } |
| 303 | if (f_min < MIN_FONT_SCALE) |
| 304 | f_min = MIN_FONT_SCALE; |
| 305 | pango_matrix_translate(&m, x, y); |
| 306 | pango_matrix_rotate(&m, angle); |
| 307 | pango_matrix_translate(&m, |
| 308 | -xalign*f_min*width/PANGO_SCALE, |
| 309 | (yalign-1)*f_min*height/PANGO_SCALE); |
| 310 | pango_matrix_scale(&m, f_min, f_min); |
| 311 | |
| 312 | pango_context_set_matrix(context, &m); |
| 313 | pango_layout_context_changed(layout); |
| 314 | pango_renderer_draw_layout(renderer, layout, 0, 0); |
| 315 | |
| 316 | /* clean up renderer */ |
| 317 | |
| 318 | gdk_pango_renderer_set_drawable(GDK_PANGO_RENDERER(renderer), NULL); |
| 319 | gdk_pango_renderer_set_gc(GDK_PANGO_RENDERER(renderer), NULL); |
| 320 | |
| 321 | /* free objects */ |
| 322 | |
| 323 | g_object_unref(layout); |
| 324 | g_object_unref(context); |
| 325 | } |
| 326 | |
| 327 | |
| 328 | /* ----- Debugging support ------------------------------------------------- */ |
| 329 | |
| 330 | |
| 331 | /* |
| 332 | * View with make montage or something like |
| 333 | * |
| 334 | * montage -label %f -frame 3 __dbg????.png png:- | display - |
| 335 | */ |
| 336 | |
| 337 | void debug_save_pixbuf(GdkPixbuf *buf) |
| 338 | { |
| 339 | static int buf_num = 0; |
| 340 | char name[20]; /* plenty */ |
| 341 | |
| 342 | sprintf(name, "__dbg%04d.png", buf_num++); |
| 343 | gdk_pixbuf_save(buf, name, "png", NULL, NULL); |
| 344 | fprintf(stderr, "saved to %s\n", name); |
| 345 | } |
| 346 | |
| 347 | |
| 348 | /* |
| 349 | * gtk_widget_get_snapshot seems to use an expose event to do the drawing. This |
| 350 | * means that we can't call debug_save_widget from the expose event handler of |
| 351 | * the widget being dumped. |
| 352 | */ |
| 353 | |
| 354 | #if GTK_CHECK_VERSION(2, 14, 0) |
| 355 | |
| 356 | void debug_save_widget(GtkWidget *widget) |
| 357 | { |
| 358 | GdkPixmap *pixmap; |
| 359 | GdkPixbuf *pixbuf; |
| 360 | gint w, h; |
| 361 | |
| 362 | pixmap = gtk_widget_get_snapshot(widget, NULL); |
| 363 | gdk_drawable_get_size(GDK_DRAWABLE(pixmap), &w, &h); |
| 364 | pixbuf = gdk_pixbuf_get_from_drawable(NULL, GDK_DRAWABLE(pixmap), |
| 365 | NULL, 0, 0, 0, 0, w, h); |
| 366 | debug_save_pixbuf(pixbuf); |
| 367 | gdk_pixmap_unref(pixmap); |
| 368 | g_object_unref(pixbuf); |
| 369 | } |
| 370 | |
| 371 | #endif /* GTK_CHECK_VERSION(2, 14, 0) */ |
| 372 | |
| 373 | |
| 374 | /* ----- kill the content of a container ----------------------------------- */ |
| 375 | |
| 376 | |
| 377 | static void destroy_callback(GtkWidget *widget, gpointer data) |
| 378 | { |
| 379 | gtk_widget_destroy(widget); |
| 380 | } |
| 381 | |
| 382 | |
| 383 | void destroy_all_children(GtkContainer *container) |
| 384 | { |
| 385 | gtk_container_foreach(container, destroy_callback, NULL); |
| 386 | } |
| 387 | |
| 388 | |
| 389 | /* ----- get a widget's desired width -------------------------------------- */ |
| 390 | |
| 391 | |
| 392 | int get_widget_width(GtkWidget *widget) |
| 393 | { |
| 394 | GtkRequisition req; |
| 395 | |
| 396 | gtk_widget_show_all(widget); |
| 397 | gtk_widget_size_request(widget, &req); |
| 398 | return req.width; |
| 399 | } |
| 400 |
Branches:
master
