]> git.decadent.org.uk Git - ion3.git/blob - de/draw.c
[svn-upgrade] Integrating new upstream version, ion3 (20070203)
[ion3.git] / de / draw.c
1 /*
2  * ion/de/draw.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2007. 
5  *
6  * Ion is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2.1 of the License, or
9  * (at your option) any later version.
10  */
11
12 #include <string.h>
13 #include <limits.h>
14
15 #include <ioncore/global.h>
16 #include <ioncore/common.h>
17 #include <ioncore/gr.h>
18 #include "brush.h"
19 #include "font.h"
20 #include "private.h"
21
22 #include <X11/extensions/shape.h>
23
24
25 /*{{{ Colour group lookup */
26
27
28 static DEColourGroup *destyle_get_colour_group2(DEStyle *style,
29                                                 const GrStyleSpec *a1,
30                                                 const GrStyleSpec *a2)
31 {
32     int i, score, maxscore=0;
33     DEColourGroup *maxg=&(style->cgrp);
34     
35     while(style!=NULL){
36         for(i=0; i<style->n_extra_cgrps; i++){
37             score=gr_stylespec_score2(&style->extra_cgrps[i].spec, a1, a2);
38             
39             if(score>maxscore){
40                 maxg=&(style->extra_cgrps[i]);
41                 maxscore=score;
42             }
43         }
44         style=style->based_on;
45     }
46     
47     return maxg;
48 }
49
50
51 DEColourGroup *debrush_get_colour_group2(DEBrush *brush, 
52                                          const GrStyleSpec *a1,
53                                          const GrStyleSpec *a2)
54 {
55     return destyle_get_colour_group2(brush->d, a1, a2);
56 }
57
58
59 DEColourGroup *debrush_get_colour_group(DEBrush *brush, const GrStyleSpec *attr)
60 {
61     return destyle_get_colour_group2(brush->d, attr, NULL);
62 }
63
64
65 DEColourGroup *debrush_get_current_colour_group(DEBrush *brush)
66 {
67     return debrush_get_colour_group(brush, debrush_get_current_attr(brush));
68 }
69
70
71 /*}}}*/
72
73
74 /*{{{ Borders */
75
76
77 /* Draw a border at x, y with outer width w x h. Top and left 'tl' pixels
78  * wide with color 'tlc' and bottom and right 'br' pixels with colors 'brc'.
79  */
80 static void do_draw_border(Window win, GC gc, int x, int y, int w, int h,
81                            uint tl, uint br, DEColour tlc, DEColour brc)
82 {
83     XPoint points[3];
84     uint i=0, a=0, b=0;
85     
86     w--;
87     h--;
88
89     XSetForeground(ioncore_g.dpy, gc, tlc);
90
91     
92     a=(br!=0);
93     b=0;
94     
95     for(i=0; i<tl; i++){
96         points[0].x=x+i;        points[0].y=y+h+1-b;
97         points[1].x=x+i;        points[1].y=y+i;
98         points[2].x=x+w+1-a;    points[2].y=y+i;
99
100         if(a<br)
101             a++;
102         if(b<br)
103             b++;
104     
105         XDrawLines(ioncore_g.dpy, win, gc, points, 3, CoordModeOrigin);
106     }
107
108     
109     XSetForeground(ioncore_g.dpy, gc, brc);
110
111     a=(tl!=0);
112     b=0;
113     
114     for(i=0; i<br; i++){
115         points[0].x=x+w-i;        points[0].y=y+b;
116         points[1].x=x+w-i;        points[1].y=y+h-i;
117         points[2].x=x+a;        points[2].y=y+h-i;
118     
119         if(a<tl)
120             a++;
121         if(b<tl)
122             b++;
123         
124         XDrawLines(ioncore_g.dpy, win, gc, points, 3, CoordModeOrigin);
125     }
126 }
127
128
129 static void draw_border(Window win, GC gc, WRectangle *geom,
130                         uint tl, uint br, DEColour tlc, DEColour brc)
131 {
132     do_draw_border(win, gc, geom->x, geom->y, geom->w, geom->h,
133                    tl, br, tlc, brc);
134     geom->x+=tl;
135     geom->y+=tl;
136     geom->w-=tl+br;
137     geom->h-=tl+br;
138 }
139
140
141 void debrush_do_draw_border(DEBrush *brush, WRectangle geom, 
142                             DEColourGroup *cg)
143 {
144     DEBorder *bd=&(brush->d->border);
145     GC gc=brush->d->normal_gc;
146     Window win=brush->win;
147     
148     switch(bd->style){
149     case DEBORDER_RIDGE:
150         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
151     case DEBORDER_INLAID:
152         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
153         draw_border(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl);
154         break;
155     case DEBORDER_GROOVE:
156         draw_border(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl);
157         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
158         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
159         break;
160     case DEBORDER_ELEVATED:
161     default:
162         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
163         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
164         break;
165     }
166 }
167
168
169
170     
171 void debrush_draw_border(DEBrush *brush, 
172                          const WRectangle *geom)
173 {
174     DEColourGroup *cg=debrush_get_current_colour_group(brush);
175     if(cg!=NULL)
176         debrush_do_draw_border(brush, *geom, cg);
177 }
178
179
180 static void draw_borderline(Window win, GC gc, WRectangle *geom,
181                             uint tl, uint br, DEColour tlc, DEColour brc, 
182                             GrBorderLine line)
183 {
184     if(line==GR_BORDERLINE_LEFT && geom->h>0){
185         XSetForeground(ioncore_g.dpy, gc, tlc);
186         XDrawRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y, tl, geom->h);
187         geom->x+=tl;
188     }else if(line==GR_BORDERLINE_TOP && geom->w>0){
189         XSetForeground(ioncore_g.dpy, gc, tlc);
190         XDrawRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y, geom->w, tl);
191         geom->y+=tl;
192     }else if(line==GR_BORDERLINE_RIGHT && geom->h>0){
193         XSetForeground(ioncore_g.dpy, gc, brc);
194         XDrawRectangle(ioncore_g.dpy, win, gc, geom->x+geom->w-1-br, geom->y, br, geom->h);
195         geom->w-=br;
196     }else if(line==GR_BORDERLINE_BOTTOM && geom->w>0){
197         XSetForeground(ioncore_g.dpy, gc, brc);
198         XDrawRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y+geom->h-1-br, geom->w, br);
199         geom->h-=br;
200     }
201 }
202
203
204 void debrush_do_draw_borderline(DEBrush *brush, WRectangle geom,
205                                 DEColourGroup *cg, GrBorderLine line)
206 {
207     DEBorder *bd=&(brush->d->border);
208     GC gc=brush->d->normal_gc;
209     Window win=brush->win;
210     
211     switch(bd->style){
212     case DEBORDER_RIDGE:
213         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
214     case DEBORDER_INLAID:
215         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
216         draw_borderline(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl, line);
217         break;
218     case DEBORDER_GROOVE:
219         draw_borderline(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl, line);
220         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
221         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
222         break;
223     case DEBORDER_ELEVATED:
224     default:
225         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
226         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
227         break;
228     }
229 }
230
231
232 void debrush_draw_borderline(DEBrush *brush, const WRectangle *geom,
233                              GrBorderLine line)
234 {
235     DEColourGroup *cg=debrush_get_current_colour_group(brush);
236     if(cg!=NULL)
237         debrush_do_draw_borderline(brush, *geom, cg, line);
238 }
239
240
241 /*}}}*/
242
243
244 /*{{{ Boxes */
245
246
247 static void copy_masked(DEBrush *brush, Drawable src, Drawable dst,
248                         int src_x, int src_y, int w, int h,
249                         int dst_x, int dst_y)
250 {
251     
252     GC copy_gc=brush->d->copy_gc;
253     
254     XSetClipMask(ioncore_g.dpy, copy_gc, src);
255     XSetClipOrigin(ioncore_g.dpy, copy_gc, dst_x, dst_y);
256     XCopyPlane(ioncore_g.dpy, src, dst, copy_gc, src_x, src_y, w, h,
257                dst_x, dst_y, 1);
258 }
259
260
261 static GrStyleSpec dragged_spec=GR_STYLESPEC_INIT;
262 static GrStyleSpec tagged_spec=GR_STYLESPEC_INIT;
263 static GrStyleSpec submenu_spec=GR_STYLESPEC_INIT;
264
265
266 void debrush_tab_extras(DEBrush *brush, const WRectangle *g, 
267                         DEColourGroup *cg, GrBorderWidths *bdw,
268                         GrFontExtents *fnte,
269                         const GrStyleSpec *a1, 
270                         const GrStyleSpec *a2,
271                         bool pre)
272 {
273     DEStyle *d=brush->d;
274     GC tmp;
275     /* Not thread-safe, but neither is the rest of the drawing code
276      * with shared GC:s.
277      */
278     static bool swapped=FALSE;
279
280     ENSURE_INITSPEC(dragged_spec, "dragged");
281     ENSURE_INITSPEC(tagged_spec, "tagged");
282     
283     if(pre){
284         if(!MATCHES2(dragged_spec, a1, a2))
285             return;
286         
287         tmp=d->normal_gc;
288         d->normal_gc=d->stipple_gc;
289         d->stipple_gc=tmp;
290         swapped=TRUE;
291         XClearArea(ioncore_g.dpy, brush->win, g->x, g->y, g->w, g->h, False);
292         return;
293     }
294     
295     if(MATCHES2(tagged_spec, a1, a2)){
296         XSetForeground(ioncore_g.dpy, d->copy_gc, cg->fg);
297             
298         copy_masked(brush, d->tag_pixmap, brush->win, 0, 0,
299                     d->tag_pixmap_w, d->tag_pixmap_h,
300                     g->x+g->w-bdw->right-d->tag_pixmap_w, 
301                     g->y+bdw->top);
302     }
303
304     if(swapped){
305         tmp=d->normal_gc;
306         d->normal_gc=d->stipple_gc;
307         d->stipple_gc=tmp;
308         swapped=FALSE;
309     }
310     /*if(MATCHES2("*-*-*-dragged", a1, a2)){
311         XFillRectangle(ioncore_g.dpy, win, d->stipple_gc, 
312                        g->x, g->y, g->w, g->h);
313     }*/
314 }
315
316
317 void debrush_menuentry_extras(DEBrush *brush, 
318                               const WRectangle *g, 
319                               DEColourGroup *cg, 
320                               GrBorderWidths *bdw,
321                               GrFontExtents *fnte,
322                               const GrStyleSpec *a1, 
323                               const GrStyleSpec *a2, 
324                               bool pre)
325 {
326     int tx, ty;
327
328     if(pre)
329         return;
330     
331     ENSURE_INITSPEC(submenu_spec, "submenu");
332     
333     if(!MATCHES2(submenu_spec, a1, a2))
334         return;
335         
336     ty=(g->y+bdw->top+fnte->baseline
337         +(g->h-bdw->top-bdw->bottom-fnte->max_height)/2);
338     tx=g->x+g->w-bdw->right;
339
340     debrush_do_draw_string(brush, tx, ty, DE_SUB_IND, DE_SUB_IND_LEN, 
341                            FALSE, cg);
342 }
343
344
345 void debrush_do_draw_box(DEBrush *brush, const WRectangle *geom, 
346                          DEColourGroup *cg, bool needfill)
347 {
348     GC gc=brush->d->normal_gc;
349     
350     if(TRUE/*needfill*/){
351         XSetForeground(ioncore_g.dpy, gc, cg->bg);
352         XFillRectangle(ioncore_g.dpy, brush->win, gc, geom->x, geom->y, 
353                        geom->w, geom->h);
354     }
355     
356     debrush_do_draw_border(brush, *geom, cg);
357 }
358
359
360 static void debrush_do_draw_textbox(DEBrush *brush, 
361                                     const WRectangle *geom, 
362                                     const char *text, 
363                                     DEColourGroup *cg, 
364                                     bool needfill,
365                                     const GrStyleSpec *a1, 
366                                     const GrStyleSpec *a2)
367 {
368     uint len;
369     GrBorderWidths bdw;
370     GrFontExtents fnte;
371     uint tx, ty, tw;
372
373     grbrush_get_border_widths(&(brush->grbrush), &bdw);
374     grbrush_get_font_extents(&(brush->grbrush), &fnte);
375     
376     if(brush->extras_fn!=NULL)
377         brush->extras_fn(brush, geom, cg, &bdw, &fnte, a1, a2, TRUE);
378     
379     debrush_do_draw_box(brush, geom, cg, needfill);
380     
381     do{ /*...while(0)*/
382         if(text==NULL)
383             break;
384         
385         len=strlen(text);
386     
387         if(len==0)
388             break;
389     
390         if(brush->d->textalign!=DEALIGN_LEFT){
391             tw=grbrush_get_text_width((GrBrush*)brush, text, len);
392             
393             if(brush->d->textalign==DEALIGN_CENTER)
394                 tx=geom->x+bdw.left+(geom->w-bdw.left-bdw.right-tw)/2;
395             else
396                 tx=geom->x+geom->w-bdw.right-tw;
397         }else{
398             tx=geom->x+bdw.left;
399         }
400         
401         ty=(geom->y+bdw.top+fnte.baseline
402             +(geom->h-bdw.top-bdw.bottom-fnte.max_height)/2);
403         
404         debrush_do_draw_string(brush, tx, ty, text, len, FALSE, cg);
405     }while(0);
406     
407     if(brush->extras_fn!=NULL)
408         brush->extras_fn(brush, geom, cg, &bdw, &fnte, a1, a2, FALSE);
409 }
410
411
412 void debrush_draw_textbox(DEBrush *brush, const WRectangle *geom, 
413                           const char *text, bool needfill)
414 {
415     GrStyleSpec *attr=debrush_get_current_attr(brush);
416     DEColourGroup *cg=debrush_get_colour_group(brush, attr);
417     
418     if(cg!=NULL){
419         debrush_do_draw_textbox(brush, geom, text, cg, needfill, 
420                                 attr, NULL);
421     }
422 }
423
424
425 void debrush_draw_textboxes(DEBrush *brush, const WRectangle *geom,
426                             int n, const GrTextElem *elem, 
427                             bool needfill)
428 {
429     GrStyleSpec *common_attrib;
430     WRectangle g=*geom;
431     DEColourGroup *cg;
432     GrBorderWidths bdw;
433     int i;
434     
435     common_attrib=debrush_get_current_attr(brush);
436     
437     grbrush_get_border_widths(&(brush->grbrush), &bdw);
438     
439     for(i=0; ; i++){
440         g.w=bdw.left+elem[i].iw+bdw.right;
441         cg=debrush_get_colour_group2(brush, common_attrib, &elem[i].attr);
442         
443         if(cg!=NULL){
444             debrush_do_draw_textbox(brush, &g, elem[i].text, cg, needfill,
445                                     common_attrib, &elem[i].attr);
446         }
447         
448         if(i==n-1)
449             break;
450         
451         g.x+=g.w;
452         if(bdw.spacing>0 && needfill){
453             XClearArea(ioncore_g.dpy, brush->win, g.x, g.y,
454                        brush->d->spacing, g.h, False);
455         }
456         g.x+=bdw.spacing;
457     }
458 }
459
460
461 /*}}}*/
462
463
464 /*{{{ Misc. */
465
466 #define MAXSHAPE 16
467
468 void debrush_set_window_shape(DEBrush *brush, bool rough,
469                               int n, const WRectangle *rects)
470 {
471     XRectangle r[MAXSHAPE];
472     int i;
473     
474     if(n>MAXSHAPE)
475         n=MAXSHAPE;
476     
477     if(n==0){
478         /* n==0 should clear the shape. As there's absolutely no
479          * documentation for XShape (as is typical of all sucky X
480          * extensions), I don't know how the shape should properly 
481          * be cleared. Thus we just use a huge rectangle.
482          */
483         n=1;
484         r[0].x=0;
485         r[0].y=0;
486         r[0].width=USHRT_MAX;
487         r[0].height=USHRT_MAX;
488     }else{
489         for(i=0; i<n; i++){
490             r[i].x=rects[i].x;
491             r[i].y=rects[i].y;
492             r[i].width=rects[i].w;
493             r[i].height=rects[i].h;
494         }
495     }
496     
497     XShapeCombineRectangles(ioncore_g.dpy, brush->win,
498                             ShapeBounding, 0, 0, r, n,
499                             ShapeSet, Unsorted);
500 }
501
502
503 void debrush_enable_transparency(DEBrush *brush, GrTransparency mode)
504 {
505     XSetWindowAttributes attr;
506     ulong attrflags=0;
507
508     if(mode==GR_TRANSPARENCY_DEFAULT)
509         mode=brush->d->transparency_mode;
510     
511     if(mode==GR_TRANSPARENCY_YES){
512         attrflags=CWBackPixmap;
513         attr.background_pixmap=ParentRelative;
514     }else{
515         attrflags=CWBackPixel;
516         attr.background_pixel=brush->d->cgrp.bg;
517     }
518     
519     XChangeWindowAttributes(ioncore_g.dpy, brush->win, attrflags, &attr);
520     XClearWindow(ioncore_g.dpy, brush->win);
521 }
522
523
524 void debrush_fill_area(DEBrush *brush, const WRectangle *geom)
525 {
526     DEColourGroup *cg=debrush_get_current_colour_group(brush);
527     GC gc=brush->d->normal_gc;
528
529     if(cg==NULL)
530         return;
531     
532     XSetForeground(ioncore_g.dpy, gc, cg->bg);
533     XFillRectangle(ioncore_g.dpy, brush->win, gc, 
534                    geom->x, geom->y, geom->w, geom->h);
535 }
536
537
538 void debrush_clear_area(DEBrush *brush, const WRectangle *geom)
539 {
540     XClearArea(ioncore_g.dpy, brush->win,
541                geom->x, geom->y, geom->w, geom->h, False);
542 }
543
544
545 /*}}}*/
546
547
548 /*{{{ Clipping rectangles */
549
550 /* Should actually set the clipping rectangle for all GC:s and use 
551  * window-specific GC:s to do this correctly...
552  */
553
554 static void debrush_set_clipping_rectangle(DEBrush *brush, 
555                                            const WRectangle *geom)
556 {
557     XRectangle rect;
558     
559     assert(!brush->clip_set);
560     
561     rect.x=geom->x;
562     rect.y=geom->y;
563     rect.width=geom->w;
564     rect.height=geom->h;
565     
566     XSetClipRectangles(ioncore_g.dpy, brush->d->normal_gc,
567                        0, 0, &rect, 1, Unsorted);
568     brush->clip_set=TRUE;
569 }
570
571
572 static void debrush_clear_clipping_rectangle(DEBrush *brush)
573 {
574     if(brush->clip_set){
575         XSetClipMask(ioncore_g.dpy, brush->d->normal_gc, None);
576         brush->clip_set=FALSE;
577     }
578 }
579
580
581 /*}}}*/
582
583
584 /*{{{ debrush_begin/end */
585
586
587 void debrush_begin(DEBrush *brush, const WRectangle *geom, int flags)
588 {
589     
590     if(flags&GRBRUSH_AMEND)
591         flags|=GRBRUSH_NO_CLEAR_OK;
592     
593     if(!(flags&GRBRUSH_KEEP_ATTR))
594         debrush_init_attr(brush, NULL);
595     
596     if(!(flags&GRBRUSH_NO_CLEAR_OK))
597         debrush_clear_area(brush, geom);
598     
599     if(flags&GRBRUSH_NEED_CLIP)
600         debrush_set_clipping_rectangle(brush, geom);
601 }
602
603
604 void debrush_end(DEBrush *brush)
605 {
606     debrush_clear_clipping_rectangle(brush);
607 }
608
609
610 /*}}}*/
611