Add some comments to window functions
[awesome.git] / common / draw.c
blob3ff0372667f3c1be7fe8d27ff6e642ccbcee9cd4
1 /*
2 * draw.c - draw functions
4 * Copyright © 2007-2008 Julien Danjou <julien@danjou.info>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include <cairo.h>
23 #include <cairo-ft.h>
24 #include <cairo-xlib.h>
26 #include <langinfo.h>
27 #include <iconv.h>
28 #include <errno.h>
30 #include <math.h>
32 #include "draw.h"
33 #include "common/util.h"
35 /** Convert text from any charset to UTF-8 using iconv
36 * \param iso the ISO string to convert
37 * \return NULL if error, otherwise pointer to the new converted string
39 static char *
40 draw_iso2utf8(char *iso)
42 iconv_t iso2utf8;
43 size_t len, utf8len;
44 char *utf8, *utf8p;
46 if(!(len = a_strlen(iso)))
47 return NULL;
49 if(!a_strcmp(nl_langinfo(CODESET), "UTF-8"))
50 return NULL;
52 iso2utf8 = iconv_open("UTF-8", nl_langinfo(CODESET));
53 if(iso2utf8 == (iconv_t) -1)
55 if(errno == EINVAL)
56 warn("unable to convert text from %s to UTF-8, not available",
57 nl_langinfo(CODESET));
58 else
59 perror("awesome: unable to convert text");
61 return NULL;
64 utf8len = (3 * len) / 2 + 1;
65 utf8 = utf8p = p_new(char, utf8len);
67 if(iconv(iso2utf8, &iso, &len, &utf8, &utf8len) == (size_t) -1)
69 perror("awesome: text conversion failed");
70 p_delete(&utf8p);
71 return NULL;
74 if(iconv_close(iso2utf8))
75 warn("error closing iconv");
77 return utf8p;
80 /** Get a draw context
81 * \param disp Display ref
82 * \param phys_screen physical screen id
83 * \param width width
84 * \param height height
85 * \param dw Drawable object to store in DrawCtx
86 * \return draw context ref
88 DrawCtx *
89 draw_context_new(Display *disp, int phys_screen, int width, int height, Drawable dw)
91 DrawCtx *d = p_new(DrawCtx, 1);
93 d->display = disp;
94 d->phys_screen = phys_screen;
95 d->width = width;
96 d->height = height;
97 d->depth = DefaultDepth(disp, phys_screen);
98 d->visual = DefaultVisual(disp, phys_screen);
99 d->drawable = dw;
100 d->surface = cairo_xlib_surface_create(disp, dw, d->visual, width, height);
101 d->cr = cairo_create(d->surface);
103 return d;
106 /** Delete a draw context
107 * \param ctx DrawCtx to delete
109 void
110 draw_context_delete(DrawCtx *ctx)
112 cairo_surface_destroy(ctx->surface);
113 cairo_destroy(ctx->cr);
114 p_delete(&ctx);
117 /** Draw text into a draw context
118 * \param ctx DrawCtx to draw to
119 * \param area area to draw to
120 * \param align alignment
121 * \param padding padding to add before drawing the text
122 * \param font font to use
123 * \param text text to draw
124 * \param fg foreground color
125 * \param bg background color
127 void
128 draw_text(DrawCtx *ctx,
129 Area area,
130 Alignment align,
131 int padding,
132 XftFont *font, char *text,
133 XColor fg, XColor bg)
135 int nw = 0;
136 ssize_t len, olen;
137 char *buf = NULL, *utf8 = NULL;
138 cairo_font_face_t *font_face;
140 draw_rectangle(ctx, area, True, bg);
142 if(!(len = olen = a_strlen(text)))
143 return;
145 /* try to convert it to UTF-8 */
146 if((utf8 = draw_iso2utf8(text)))
148 buf = utf8;
149 len = olen = a_strlen(buf);
151 else
152 buf = a_strdup(text);
154 /* check that the text is not too long */
155 while(len && (nw = (draw_textwidth(ctx->display, font, buf)) + padding * 2) > area.width)
157 len--;
158 /* we can't blindly null the char, we need to check if it's not part of
159 * a multi byte char: if mbtowc return -1, we know that we must go back
160 * in the string to find the beginning of the multi byte char */
161 while(mbtowc(NULL, buf + len, a_strlen(buf + len)) < 0)
162 len--;
163 buf[len] = '\0';
165 if(nw > area.width)
166 return; /* too long */
167 if(len < olen)
169 if(len > 1)
170 buf[len - 1] = '.';
171 if(len > 2)
172 buf[len - 2] = '.';
173 if(len > 3)
174 buf[len - 3] = '.';
177 font_face = cairo_ft_font_face_create_for_pattern(font->pattern);
178 cairo_set_font_face(ctx->cr, font_face);
179 cairo_set_font_size(ctx->cr, font->height);
180 cairo_set_source_rgb(ctx->cr, fg.red / 65535.0, fg.green / 65535.0, fg.blue / 65535.0);
182 switch(align)
184 case AlignLeft:
185 cairo_move_to(ctx->cr, area.x + padding, area.y + font->ascent + (ctx->height - font->height) / 2);
186 break;
187 case AlignRight:
188 cairo_move_to(ctx->cr, area.x + (area.width - nw) + padding,
189 area.y + font->ascent + (ctx->height - font->height) / 2);
190 break;
191 default:
192 cairo_move_to(ctx->cr, area.x + ((area.width - nw) / 2) + padding,
193 area.y + font->ascent + (ctx->height - font->height) / 2);
194 break;
197 cairo_show_text(ctx->cr, buf);
199 cairo_font_face_destroy(font_face);
201 p_delete(&buf);
204 /** Setup color-source for cairo (gradient or mono)
205 * \param ctx Draw context
206 * \param x x-offset of widget
207 * \param y y-offset of widget
208 * \param width width in pixels
209 * \param color color to use from 0%
210 * \param pcolor_center color at 50% of width
211 * \param pcolor_end color at 100% of width
212 * \return pat pattern or NULL; needs to get cairo_pattern_destroy()'ed;
214 static cairo_pattern_t *
215 draw_setup_cairo_color_source(DrawCtx *ctx, int x, int y, int width,
216 XColor color, XColor *pcolor_center, XColor *pcolor_end)
218 cairo_pattern_t *pat = NULL;
220 if(pcolor_center || pcolor_end) /* draw a gradient */
222 pat = cairo_pattern_create_linear(x, y, x + width, y);
224 cairo_pattern_add_color_stop_rgb(pat, 0, color.red / 65535.0,
225 color.green / 65535.0, color.blue / 65535.0);
226 if(pcolor_center)
227 cairo_pattern_add_color_stop_rgb(pat, 0.5, pcolor_center->red / 65535.0,
228 pcolor_center->green / 65535.0, pcolor_center->blue / 65535.0);
229 if(pcolor_end)
230 cairo_pattern_add_color_stop_rgb(pat, 1, pcolor_end->red / 65535.0,
231 pcolor_end->green / 65535.0, pcolor_end->blue / 65535.0);
232 else
233 cairo_pattern_add_color_stop_rgb(pat, 1, color.red / 65535.0,
234 color.green / 65535.0, color.blue / 65535.0);
235 cairo_set_source(ctx->cr, pat);
237 else
238 cairo_set_source_rgb(ctx->cr, color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0);
240 return pat;
243 /** Draw rectangle
244 * \param ctx Draw context
245 * \param geometry geometry
246 * \param filled fill rectangle?
247 * \param color color to use
249 void
250 draw_rectangle(DrawCtx *ctx, Area geometry, Bool filled, XColor color)
252 cairo_set_antialias(ctx->cr, CAIRO_ANTIALIAS_NONE);
253 cairo_set_line_width(ctx->cr, 1.0);
254 cairo_set_source_rgb(ctx->cr, color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0);
255 if(filled)
257 cairo_rectangle(ctx->cr, geometry.x, geometry.y, geometry.width, geometry.height);
258 cairo_fill(ctx->cr);
260 else
262 cairo_rectangle(ctx->cr, geometry.x + 1, geometry.y, geometry.width - 1, geometry.height - 1);
263 cairo_stroke(ctx->cr);
266 /** Draw rectangle with gradient colors
267 * \param ctx Draw context
268 * \param geometry geometry
269 * \param fullwidth width of full bar in pixels
270 * \param filled filled rectangle?
271 * \param color color to use from 0%
272 * \param pcolor_center color at 50%
273 * \param pcolor_end color at 100%
275 void
276 draw_rectangle_gradient(DrawCtx *ctx, Area geometry, int fullwidth, Bool filled,
277 XColor color, XColor *pcolor_center, XColor *pcolor_end)
279 cairo_pattern_t *pat;
281 cairo_set_antialias(ctx->cr, CAIRO_ANTIALIAS_NONE);
282 cairo_set_line_width(ctx->cr, 1.0);
284 pat = draw_setup_cairo_color_source(ctx, geometry.x, geometry.y, fullwidth,
285 color, pcolor_center, pcolor_end);
287 if(filled)
289 cairo_rectangle(ctx->cr, geometry.x, geometry.y, geometry.width, geometry.height);
290 cairo_fill(ctx->cr);
292 else
294 cairo_rectangle(ctx->cr, geometry.x + 1, geometry.y, geometry.width - 1, geometry.height - 1);
295 cairo_stroke(ctx->cr);
298 if(pat)
299 cairo_pattern_destroy(pat);
302 /** Setup some cairo-things for drawing a graph
303 * \param ctx Draw context
305 void
306 draw_graph_setup(DrawCtx *ctx)
308 cairo_set_antialias(ctx->cr, CAIRO_ANTIALIAS_NONE);
309 cairo_set_line_width(ctx->cr, 1.0);
310 /* without it, it can draw over the path on sharp angles (...too long lines) */
311 cairo_set_line_join (ctx->cr, CAIRO_LINE_JOIN_ROUND);
314 /** Draw a graph
315 * \param ctx Draw context
316 * \param x x-offset of widget
317 * \param y y-offset of widget
318 * \param w width in pixels
319 * \param from array of starting-point offsets to draw a graph-lines
320 * \param to array of end-point offsets to draw a graph-lines
321 * \param cur_index current position in data-array (cycles around)
322 * \param color color to use from 0%
323 * \param pcolor_center color at 50%
324 * \param pcolor_end color at 100%
326 void
327 draw_graph(DrawCtx *ctx, int x, int y, int w, int *from, int *to, int cur_index,
328 XColor color, XColor *pcolor_center, XColor *pcolor_end)
330 int i;
331 cairo_pattern_t *pat;
332 pat = draw_setup_cairo_color_source(ctx, x, y, w, color, pcolor_center, pcolor_end);
334 i = -1;
335 while(++i < w)
337 cairo_move_to(ctx->cr, x, y - from[cur_index]);
338 cairo_line_to(ctx->cr, x, y - to[cur_index]);
339 x++;
341 if (--cur_index < 0)
342 cur_index = w - 1;
345 cairo_stroke(ctx->cr);
347 if(pat)
348 cairo_pattern_destroy(pat);
351 /** Draw a line into a graph-widget
352 * \param ctx Draw context
353 * \param x x-offset of widget
354 * \param y y-offset of widget
355 * \param w width in pixels
356 * \param to array of offsets to draw the line through...
357 * \param cur_index current position in data-array (cycles around)
358 * \param color color to use from 0%
359 * \param pcolor_center color at 50%
360 * \param pcolor_end color at 100%
362 void
363 draw_graph_line(DrawCtx *ctx, int x, int y, int w, int *to, int cur_index,
364 XColor color, XColor *pcolor_center, XColor *pcolor_end)
366 int i;
367 int flag = 0; /* used to prevent drawing a line from 0 to 0 values */
368 cairo_pattern_t *pat;
370 pat = draw_setup_cairo_color_source(ctx, x, y, w, color, pcolor_center, pcolor_end);
372 /* x-1 (on the border), paints *from* the last point (... not included itself) */
373 /* makes sense when you assume there is already some line drawn to it. */
374 cairo_move_to(ctx->cr, x - 1, y - to[cur_index]);
376 for (i = 0; i < w; i++)
378 if (to[cur_index] > 0)
380 cairo_line_to(ctx->cr, x, y - to[cur_index]);
381 flag = 1;
383 else
385 if(flag) /* only draw from values > 0 to 0-values */
387 cairo_line_to(ctx->cr, x, y);
388 flag = 0;
390 else
391 cairo_move_to(ctx->cr, x, y);
394 if (--cur_index < 0) /* cycles around the index */
395 cur_index = w - 1;
396 x++;
398 cairo_stroke(ctx->cr);
400 if(pat)
401 cairo_pattern_destroy(pat);
404 /** Draw a circle
405 * \param ctx Draw context to draw to
406 * \param x x coordinate
407 * \param y y coordinate
408 * \param r size of the circle
409 * \param filled fill circle?
410 * \param color color to use
412 void
413 draw_circle(DrawCtx *ctx, int x, int y, int r, Bool filled, XColor color)
415 cairo_set_line_width(ctx->cr, 1.0);
416 cairo_set_source_rgb(ctx->cr, color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0);
418 cairo_new_sub_path(ctx->cr); /* don't draw from the old reference point to.. */
420 if(filled)
422 cairo_arc (ctx->cr, x + r, y + r, r, 0, 2 * M_PI);
423 cairo_fill(ctx->cr);
425 else
426 cairo_arc (ctx->cr, x + r, y + r, r - 1, 0, 2 * M_PI);
428 cairo_stroke(ctx->cr);
431 /** Draw an image from ARGB data to a draw context.
432 * Data should be stored as an array of alpha, red, blue, green for each pixel
433 * and the array size should be w * h elements long.
434 * \param ctx Draw context to draw to
435 * \param x x coordinate
436 * \param y y coordinate
437 * \param w width
438 * \param h height
439 * \param wanted_h wanted height: if > 0, image will be resized
440 * \param data the image pixels array
442 void draw_image_from_argb_data(DrawCtx *ctx, int x, int y, int w, int h,
443 int wanted_h, unsigned char *data)
445 double ratio;
446 cairo_t *cr;
447 cairo_surface_t *source;
449 source = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, w, h, 0);
450 cr = cairo_create(ctx->surface);
451 if(wanted_h > 0 && h > 0)
453 ratio = (double) wanted_h / (double) h;
454 cairo_scale(cr, ratio, ratio);
455 cairo_set_source_surface(cr, source, x / ratio, y / ratio);
457 else
458 cairo_set_source_surface(cr, source, x, y);
460 cairo_paint(cr);
462 cairo_destroy(cr);
463 cairo_surface_destroy(source);
466 /** Draw an image (PNG format only) from a file to a draw context
467 * \param ctx Draw context to draw to
468 * \param x x coordinate
469 * \param y y coordinate
470 * \param wanted_h wanted height: if > 0, image will be resized
471 * \param filename file name to draw
473 void
474 draw_image(DrawCtx *ctx, int x, int y, int wanted_h, const char *filename)
476 double ratio;
477 int h;
478 cairo_surface_t *source;
479 cairo_t *cr;
480 cairo_status_t cairo_st;
482 source = cairo_image_surface_create_from_png(filename);
483 if((cairo_st = cairo_surface_status(source)))
485 warn("failed to draw image %s: %s\n", filename, cairo_status_to_string(cairo_st));
486 cairo_surface_destroy(source);
487 return;
489 cr = cairo_create (ctx->surface);
490 if(wanted_h > 0 && (h = cairo_image_surface_get_height(source)) > 0)
492 ratio = (double) wanted_h / (double) h;
493 cairo_scale(cr, ratio, ratio);
494 cairo_set_source_surface(cr, source, x / ratio, y / ratio);
496 else
497 cairo_set_source_surface(cr, source, x, y);
498 cairo_paint(cr);
500 cairo_destroy(cr);
501 cairo_surface_destroy(source);
504 /** Get an imag size (PNG format only)
505 * \param filename file name
506 * \return Area structure with width and height set to image size
508 Area
509 draw_get_image_size(const char *filename)
511 Area size = { -1, -1, -1, -1, NULL };
512 cairo_surface_t *surface;
513 cairo_status_t cairo_st;
515 surface = cairo_image_surface_create_from_png(filename);
516 if((cairo_st = cairo_surface_status(surface)))
517 warn("failed to get image size %s: %s\n", filename, cairo_status_to_string(cairo_st));
518 else
520 cairo_image_surface_get_width(surface);
521 size.x = 0;
522 size.y = 0;
523 size.width = cairo_image_surface_get_width(surface);
524 size.height = cairo_image_surface_get_height(surface);
527 cairo_surface_destroy(surface);
529 return size;
532 /** Rotate a drawable
533 * \param ctx Draw context to draw to
534 * \param phys_screen physical screen id
535 * \param angle angle to rotate
536 * \param tx translate to this x coordinate
537 * \param ty translate to this y coordinate
538 * \return new rotated drawable
540 Drawable
541 draw_rotate(DrawCtx *ctx, int phys_screen, double angle, int tx, int ty)
543 cairo_surface_t *surface, *source;
544 cairo_t *cr;
545 Drawable newdrawable;
547 newdrawable = XCreatePixmap(ctx->display,
548 RootWindow(ctx->display, phys_screen),
549 ctx->height, ctx->width,
550 ctx->depth);
551 surface = cairo_xlib_surface_create(ctx->display, newdrawable, ctx->visual, ctx->height, ctx->width);
552 source = cairo_xlib_surface_create(ctx->display, ctx->drawable, ctx->visual, ctx->width, ctx->height);
553 cr = cairo_create (surface);
555 cairo_translate(cr, tx, ty);
556 cairo_rotate(cr, angle);
558 cairo_set_source_surface(cr, source, 0.0, 0.0);
559 cairo_paint(cr);
561 cairo_destroy(cr);
562 cairo_surface_destroy(source);
563 cairo_surface_destroy(surface);
565 return newdrawable;
568 /** Return the width of a text in pixel
569 * \param disp Display ref
570 * \param font font to use
571 * \param text the text
572 * \return text width
574 unsigned short
575 draw_textwidth(Display *disp, XftFont *font, char *text)
577 cairo_surface_t *surface;
578 cairo_t *cr;
579 cairo_font_face_t *font_face;
580 cairo_text_extents_t te;
582 if (!a_strlen(text))
583 return 0;
585 surface = cairo_xlib_surface_create(disp, DefaultScreen(disp),
586 DefaultVisual(disp, DefaultScreen(disp)),
587 DisplayWidth(disp, DefaultScreen(disp)),
588 DisplayHeight(disp, DefaultScreen(disp)));
589 cr = cairo_create(surface);
590 font_face = cairo_ft_font_face_create_for_pattern(font->pattern);
591 cairo_set_font_face(cr, font_face);
592 cairo_set_font_size(cr, font->height);
593 cairo_text_extents(cr, text, &te);
594 cairo_destroy(cr);
595 cairo_surface_destroy(surface);
596 cairo_font_face_destroy(font_face);
598 return MAX(te.x_advance, te.width);
601 /** Transform a string to a Alignment type.
602 * Recognized string are left, center or right. Everything else will be
603 * recognized as AlignAuto.
604 * \param align string with align text
605 * \return Alignment type
607 Alignment
608 draw_get_align(const char *align)
610 if(!a_strncmp(align, "left", 4))
611 return AlignLeft;
612 else if(!a_strncmp(align, "center", 6))
613 return AlignCenter;
614 else if(!a_strncmp(align, "right", 5))
615 return AlignRight;
616 else if(!a_strncmp(align, "flex", 4))
617 return AlignFlex;
619 return AlignAuto;
622 /** Initialize an X color
623 * \param disp display ref
624 * \param phys_screen Physical screen number
625 * \param colstr Color specification
626 * \param color XColor struct to store color to
627 * \return true if color allocation was successfull
629 Bool
630 draw_color_new(Display *disp, int phys_screen, const char *colstr, XColor *color)
632 Bool ret;
633 XColor exactColor;
635 if(!(ret = XAllocNamedColor(disp,
636 DefaultColormap(disp, phys_screen),
637 colstr,
638 color,
639 &exactColor)))
640 warn("awesome: error, cannot allocate color '%s'\n", colstr);
642 return ret;
645 /** Remove a area from a list of them,
646 * spliting the space between several area that can overlaps
647 * \param head list head
648 * \param elem area to remove
650 void
651 area_list_remove(Area **head, Area *elem)
653 Area *r, inter, *extra, *rnext;
655 for(r = *head; r; r = rnext)
657 rnext = r->next;
658 if(area_intersect_area(*r, *elem))
660 /* remove it from the list */
661 area_list_detach(head, r);
663 inter = area_get_intersect_area(*r, *elem);
665 if(AREA_LEFT(inter) > AREA_LEFT(*r))
667 extra = p_new(Area, 1);
668 extra->x = r->x;
669 extra->y = r->y;
670 extra->width = AREA_LEFT(inter) - r->x;
671 extra->height = r->height;
672 area_list_append(head, extra);
675 if(AREA_TOP(inter) > AREA_TOP(*r))
677 extra = p_new(Area, 1);
678 extra->x = r->x;
679 extra->y = r->y;
680 extra->width = r->width;
681 extra->height = AREA_TOP(inter) - r->y;
682 area_list_append(head, extra);
685 if(AREA_RIGHT(inter) < AREA_RIGHT(*r))
687 extra = p_new(Area, 1);
688 extra->x = AREA_RIGHT(inter);
689 extra->y = r->y;
690 extra->width = AREA_RIGHT(*r) - AREA_RIGHT(inter);
691 extra->height = r->height;
692 area_list_append(head, extra);
695 if(AREA_BOTTOM(inter) < AREA_BOTTOM(*r))
697 extra = p_new(Area, 1);
698 extra->x = r->x;
699 extra->y = AREA_BOTTOM(inter);
700 extra->width = r->width;
701 extra->height = AREA_BOTTOM(*r) - AREA_BOTTOM(inter);
702 area_list_append(head, extra);
705 /* delete the elem since we removed it from the list */
706 p_delete(&r);
711 // vim: filetype=c:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80