]> git.decadent.org.uk Git - ion3.git/blob - de/draw.c
40a2f8ffe02d257af6847cbbe97152ba8947f306
[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 static void draw_borderline(Window win, GC gc, WRectangle *geom,
142                             uint tl, uint br, DEColour tlc, DEColour brc, 
143                             GrBorderLine line)
144 {
145     if(line==GR_BORDERLINE_LEFT && geom->h>0 && tl>0){
146         XSetForeground(ioncore_g.dpy, gc, tlc);
147         XSetBackground(ioncore_g.dpy, gc, tlc);
148         XFillRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y, tl, geom->h);
149         geom->x+=tl;
150     }else if(line==GR_BORDERLINE_TOP && geom->w>0 && tl>0){
151         XSetForeground(ioncore_g.dpy, gc, tlc);
152         XSetBackground(ioncore_g.dpy, gc, tlc);
153         XFillRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y, geom->w, tl);
154         geom->y+=tl;
155     }else if(line==GR_BORDERLINE_RIGHT && geom->h>0 && br>0){
156         XSetForeground(ioncore_g.dpy, gc, brc);
157         XSetBackground(ioncore_g.dpy, gc, brc);
158         XFillRectangle(ioncore_g.dpy, win, gc, geom->x+geom->w-br, geom->y, br, geom->h);
159         geom->w-=br;
160     }else if(line==GR_BORDERLINE_BOTTOM && geom->w>0 && br>0){
161         XSetForeground(ioncore_g.dpy, gc, brc);
162         XSetBackground(ioncore_g.dpy, gc, brc);
163         XFillRectangle(ioncore_g.dpy, win, gc, geom->x, geom->y+geom->h-br, geom->w, br);
164         geom->h-=br;
165     }
166 }
167
168
169 void debrush_do_draw_borderline(DEBrush *brush, WRectangle geom,
170                                 DEColourGroup *cg, GrBorderLine line)
171 {
172     DEBorder *bd=&(brush->d->border);
173     GC gc=brush->d->normal_gc;
174     Window win=brush->win;
175     
176     switch(bd->style){
177     case DEBORDER_RIDGE:
178         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
179     case DEBORDER_INLAID:
180         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
181         draw_borderline(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl, line);
182         break;
183     case DEBORDER_GROOVE:
184         draw_borderline(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl, line);
185         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
186         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
187         break;
188     case DEBORDER_ELEVATED:
189     default:
190         draw_borderline(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh, line);
191         draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
192         break;
193     }
194 }
195
196
197 void debrush_do_draw_padline(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     draw_borderline(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad, line);
205 }
206
207
208 void debrush_draw_borderline(DEBrush *brush, const WRectangle *geom,
209                              GrBorderLine line)
210 {
211     DEColourGroup *cg=debrush_get_current_colour_group(brush);
212     if(cg!=NULL)
213         debrush_do_draw_borderline(brush, *geom, cg, line);
214 }
215
216
217 static void debrush_do_do_draw_border(DEBrush *brush, WRectangle geom, 
218                                       DEColourGroup *cg)
219 {
220     DEBorder *bd=&(brush->d->border);
221     GC gc=brush->d->normal_gc;
222     Window win=brush->win;
223     
224     switch(bd->style){
225     case DEBORDER_RIDGE:
226         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
227     case DEBORDER_INLAID:
228         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
229         draw_border(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl);
230         break;
231     case DEBORDER_GROOVE:
232         draw_border(win, gc, &geom, bd->sh, bd->hl, cg->sh, cg->hl);
233         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
234         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
235         break;
236     case DEBORDER_ELEVATED:
237     default:
238         draw_border(win, gc, &geom, bd->hl, bd->sh, cg->hl, cg->sh);
239         draw_border(win, gc, &geom, bd->pad, bd->pad, cg->pad, cg->pad);
240         break;
241     }
242 }
243
244
245 void debrush_do_draw_border(DEBrush *brush, WRectangle geom, 
246                             DEColourGroup *cg)
247 {
248     DEBorder *bd=&(brush->d->border);
249
250     switch(bd->sides){
251     case DEBORDER_ALL:
252         debrush_do_do_draw_border(brush, geom, cg);
253         break;
254     case DEBORDER_TB:
255         debrush_do_draw_padline(brush, geom, cg, GR_BORDERLINE_LEFT);
256         debrush_do_draw_padline(brush, geom, cg, GR_BORDERLINE_RIGHT);
257         debrush_do_draw_borderline(brush, geom, cg, GR_BORDERLINE_TOP);
258         debrush_do_draw_borderline(brush, geom, cg, GR_BORDERLINE_BOTTOM);
259         break;
260     case DEBORDER_LR:
261         debrush_do_draw_padline(brush, geom, cg, GR_BORDERLINE_TOP);
262         debrush_do_draw_padline(brush, geom, cg, GR_BORDERLINE_BOTTOM);
263         debrush_do_draw_borderline(brush, geom, cg, GR_BORDERLINE_LEFT);
264         debrush_do_draw_borderline(brush, geom, cg, GR_BORDERLINE_RIGHT);
265         break;
266     }
267 }
268
269     
270 void debrush_draw_border(DEBrush *brush, 
271                          const WRectangle *geom)
272 {
273     DEColourGroup *cg=debrush_get_current_colour_group(brush);
274     if(cg!=NULL)
275         debrush_do_draw_border(brush, *geom, cg);
276 }
277
278
279 /*}}}*/
280
281
282 /*{{{ Boxes */
283
284
285 static void copy_masked(DEBrush *brush, Drawable src, Drawable dst,
286                         int src_x, int src_y, int w, int h,
287                         int dst_x, int dst_y)
288 {
289     
290     GC copy_gc=brush->d->copy_gc;
291     
292     XSetClipMask(ioncore_g.dpy, copy_gc, src);
293     XSetClipOrigin(ioncore_g.dpy, copy_gc, dst_x, dst_y);
294     XCopyPlane(ioncore_g.dpy, src, dst, copy_gc, src_x, src_y, w, h,
295                dst_x, dst_y, 1);
296 }
297
298
299 static GrStyleSpec dragged_spec=GR_STYLESPEC_INIT;
300 static GrStyleSpec tagged_spec=GR_STYLESPEC_INIT;
301 static GrStyleSpec submenu_spec=GR_STYLESPEC_INIT;
302
303
304 void debrush_tab_extras(DEBrush *brush, const WRectangle *g, 
305                         DEColourGroup *cg, GrBorderWidths *bdw,
306                         GrFontExtents *fnte,
307                         const GrStyleSpec *a1, 
308                         const GrStyleSpec *a2,
309                         bool pre)
310 {
311     DEStyle *d=brush->d;
312     GC tmp;
313     /* Not thread-safe, but neither is the rest of the drawing code
314      * with shared GC:s.
315      */
316     static bool swapped=FALSE;
317
318     ENSURE_INITSPEC(dragged_spec, "dragged");
319     ENSURE_INITSPEC(tagged_spec, "tagged");
320     
321     if(pre){
322         if(!MATCHES2(dragged_spec, a1, a2))
323             return;
324         
325         tmp=d->normal_gc;
326         d->normal_gc=d->stipple_gc;
327         d->stipple_gc=tmp;
328         swapped=TRUE;
329         XClearArea(ioncore_g.dpy, brush->win, g->x, g->y, g->w, g->h, False);
330         return;
331     }
332     
333     if(MATCHES2(tagged_spec, a1, a2)){
334         XSetForeground(ioncore_g.dpy, d->copy_gc, cg->fg);
335             
336         copy_masked(brush, d->tag_pixmap, brush->win, 0, 0,
337                     d->tag_pixmap_w, d->tag_pixmap_h,
338                     g->x+g->w-bdw->right-d->tag_pixmap_w, 
339                     g->y+bdw->top);
340     }
341
342     if(swapped){
343         tmp=d->normal_gc;
344         d->normal_gc=d->stipple_gc;
345         d->stipple_gc=tmp;
346         swapped=FALSE;
347     }
348     /*if(MATCHES2("*-*-*-dragged", a1, a2)){
349         XFillRectangle(ioncore_g.dpy, win, d->stipple_gc, 
350                        g->x, g->y, g->w, g->h);
351     }*/
352 }
353
354
355 void debrush_menuentry_extras(DEBrush *brush, 
356                               const WRectangle *g, 
357                               DEColourGroup *cg, 
358                               GrBorderWidths *bdw,
359                               GrFontExtents *fnte,
360                               const GrStyleSpec *a1, 
361                               const GrStyleSpec *a2, 
362                               bool pre)
363 {
364     int tx, ty;
365
366     if(pre)
367         return;
368     
369     ENSURE_INITSPEC(submenu_spec, "submenu");
370     
371     if(!MATCHES2(submenu_spec, a1, a2))
372         return;
373         
374     ty=(g->y+bdw->top+fnte->baseline
375         +(g->h-bdw->top-bdw->bottom-fnte->max_height)/2);
376     tx=g->x+g->w-bdw->right;
377
378     debrush_do_draw_string(brush, tx, ty, DE_SUB_IND, DE_SUB_IND_LEN, 
379                            FALSE, cg);
380 }
381
382
383 void debrush_do_draw_box(DEBrush *brush, const WRectangle *geom, 
384                          DEColourGroup *cg, bool needfill)
385 {
386     GC gc=brush->d->normal_gc;
387     
388     if(TRUE/*needfill*/){
389         XSetForeground(ioncore_g.dpy, gc, cg->bg);
390         XFillRectangle(ioncore_g.dpy, brush->win, gc, geom->x, geom->y, 
391                        geom->w, geom->h);
392     }
393     
394     debrush_do_draw_border(brush, *geom, cg);
395 }
396
397
398 static void debrush_do_draw_textbox(DEBrush *brush, 
399                                     const WRectangle *geom, 
400                                     const char *text, 
401                                     DEColourGroup *cg, 
402                                     bool needfill,
403                                     const GrStyleSpec *a1, 
404                                     const GrStyleSpec *a2)
405 {
406     uint len;
407     GrBorderWidths bdw;
408     GrFontExtents fnte;
409     uint tx, ty, tw;
410
411     grbrush_get_border_widths(&(brush->grbrush), &bdw);
412     grbrush_get_font_extents(&(brush->grbrush), &fnte);
413     
414     if(brush->extras_fn!=NULL)
415         brush->extras_fn(brush, geom, cg, &bdw, &fnte, a1, a2, TRUE);
416     
417     debrush_do_draw_box(brush, geom, cg, needfill);
418     
419     do{ /*...while(0)*/
420         if(text==NULL)
421             break;
422         
423         len=strlen(text);
424     
425         if(len==0)
426             break;
427     
428         if(brush->d->textalign!=DEALIGN_LEFT){
429             tw=grbrush_get_text_width((GrBrush*)brush, text, len);
430             
431             if(brush->d->textalign==DEALIGN_CENTER)
432                 tx=geom->x+bdw.left+(geom->w-bdw.left-bdw.right-tw)/2;
433             else
434                 tx=geom->x+geom->w-bdw.right-tw;
435         }else{
436             tx=geom->x+bdw.left;
437         }
438         
439         ty=(geom->y+bdw.top+fnte.baseline
440             +(geom->h-bdw.top-bdw.bottom-fnte.max_height)/2);
441         
442         debrush_do_draw_string(brush, tx, ty, text, len, FALSE, cg);
443     }while(0);
444     
445     if(brush->extras_fn!=NULL)
446         brush->extras_fn(brush, geom, cg, &bdw, &fnte, a1, a2, FALSE);
447 }
448
449
450 void debrush_draw_textbox(DEBrush *brush, const WRectangle *geom, 
451                           const char *text, bool needfill)
452 {
453     GrStyleSpec *attr=debrush_get_current_attr(brush);
454     DEColourGroup *cg=debrush_get_colour_group(brush, attr);
455     
456     if(cg!=NULL){
457         debrush_do_draw_textbox(brush, geom, text, cg, needfill, 
458                                 attr, NULL);
459     }
460 }
461
462
463 void debrush_draw_textboxes(DEBrush *brush, const WRectangle *geom,
464                             int n, const GrTextElem *elem, 
465                             bool needfill)
466 {
467     GrStyleSpec *common_attrib;
468     WRectangle g=*geom;
469     DEColourGroup *cg;
470     GrBorderWidths bdw;
471     int i;
472     
473     common_attrib=debrush_get_current_attr(brush);
474     
475     grbrush_get_border_widths(&(brush->grbrush), &bdw);
476     
477     for(i=0; ; i++){
478         g.w=bdw.left+elem[i].iw+bdw.right;
479         cg=debrush_get_colour_group2(brush, common_attrib, &elem[i].attr);
480         
481         if(cg!=NULL){
482             debrush_do_draw_textbox(brush, &g, elem[i].text, cg, needfill,
483                                     common_attrib, &elem[i].attr);
484         }
485         
486         if(i==n-1)
487             break;
488         
489         g.x+=g.w;
490         if(bdw.spacing>0 && needfill){
491             XClearArea(ioncore_g.dpy, brush->win, g.x, g.y,
492                        brush->d->spacing, g.h, False);
493         }
494         g.x+=bdw.spacing;
495     }
496 }
497
498
499 /*}}}*/
500
501
502 /*{{{ Misc. */
503
504 #define MAXSHAPE 16
505
506 void debrush_set_window_shape(DEBrush *brush, bool rough,
507                               int n, const WRectangle *rects)
508 {
509     XRectangle r[MAXSHAPE];
510     int i;
511     
512     if(n>MAXSHAPE)
513         n=MAXSHAPE;
514     
515     if(n==0){
516         /* n==0 should clear the shape. As there's absolutely no
517          * documentation for XShape (as is typical of all sucky X
518          * extensions), I don't know how the shape should properly 
519          * be cleared. Thus we just use a huge rectangle.
520          */
521         n=1;
522         r[0].x=0;
523         r[0].y=0;
524         r[0].width=USHRT_MAX;
525         r[0].height=USHRT_MAX;
526     }else{
527         for(i=0; i<n; i++){
528             r[i].x=rects[i].x;
529             r[i].y=rects[i].y;
530             r[i].width=rects[i].w;
531             r[i].height=rects[i].h;
532         }
533     }
534     
535     XShapeCombineRectangles(ioncore_g.dpy, brush->win,
536                             ShapeBounding, 0, 0, r, n,
537                             ShapeSet, Unsorted);
538 }
539
540
541 void debrush_enable_transparency(DEBrush *brush, GrTransparency mode)
542 {
543     XSetWindowAttributes attr;
544     ulong attrflags=0;
545
546     if(mode==GR_TRANSPARENCY_DEFAULT)
547         mode=brush->d->transparency_mode;
548     
549     if(mode==GR_TRANSPARENCY_YES){
550         attrflags=CWBackPixmap;
551         attr.background_pixmap=ParentRelative;
552     }else{
553         attrflags=CWBackPixel;
554         attr.background_pixel=brush->d->cgrp.bg;
555     }
556     
557     XChangeWindowAttributes(ioncore_g.dpy, brush->win, attrflags, &attr);
558     XClearWindow(ioncore_g.dpy, brush->win);
559 }
560
561
562 void debrush_fill_area(DEBrush *brush, const WRectangle *geom)
563 {
564     DEColourGroup *cg=debrush_get_current_colour_group(brush);
565     GC gc=brush->d->normal_gc;
566
567     if(cg==NULL)
568         return;
569     
570     XSetForeground(ioncore_g.dpy, gc, cg->bg);
571     XFillRectangle(ioncore_g.dpy, brush->win, gc, 
572                    geom->x, geom->y, geom->w, geom->h);
573 }
574
575
576 void debrush_clear_area(DEBrush *brush, const WRectangle *geom)
577 {
578     XClearArea(ioncore_g.dpy, brush->win,
579                geom->x, geom->y, geom->w, geom->h, False);
580 }
581
582
583 /*}}}*/
584
585
586 /*{{{ Clipping rectangles */
587
588 /* Should actually set the clipping rectangle for all GC:s and use 
589  * window-specific GC:s to do this correctly...
590  */
591
592 static void debrush_set_clipping_rectangle(DEBrush *brush, 
593                                            const WRectangle *geom)
594 {
595     XRectangle rect;
596     
597     assert(!brush->clip_set);
598     
599     rect.x=geom->x;
600     rect.y=geom->y;
601     rect.width=geom->w;
602     rect.height=geom->h;
603     
604     XSetClipRectangles(ioncore_g.dpy, brush->d->normal_gc,
605                        0, 0, &rect, 1, Unsorted);
606     brush->clip_set=TRUE;
607 }
608
609
610 static void debrush_clear_clipping_rectangle(DEBrush *brush)
611 {
612     if(brush->clip_set){
613         XSetClipMask(ioncore_g.dpy, brush->d->normal_gc, None);
614         brush->clip_set=FALSE;
615     }
616 }
617
618
619 /*}}}*/
620
621
622 /*{{{ debrush_begin/end */
623
624
625 void debrush_begin(DEBrush *brush, const WRectangle *geom, int flags)
626 {
627     
628     if(flags&GRBRUSH_AMEND)
629         flags|=GRBRUSH_NO_CLEAR_OK;
630     
631     if(!(flags&GRBRUSH_KEEP_ATTR))
632         debrush_init_attr(brush, NULL);
633     
634     if(!(flags&GRBRUSH_NO_CLEAR_OK))
635         debrush_clear_area(brush, geom);
636     
637     if(flags&GRBRUSH_NEED_CLIP)
638         debrush_set_clipping_rectangle(brush, geom);
639 }
640
641
642 void debrush_end(DEBrush *brush)
643 {
644     debrush_clear_clipping_rectangle(brush);
645 }
646
647
648 /*}}}*/
649