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