1 /**
2 * @file border.c
3 * @author Joe Wingbermuehle
4 * @date 2004-2015
5 *
6 * @brief Functions for handling window borders.
7 *
8 */
9
10 #include "jwm.h"
11 #include "border.h"
12 #include "client.h"
13 #include "clientlist.h"
14 #include "color.h"
15 #include "icon.h"
16 #include "font.h"
17 #include "misc.h"
18 #include "settings.h"
19 #include "grab.h"
20
21 static char *buttonNames[BI_COUNT];
22 static IconNode *buttonIcons[BI_COUNT];
23
24 static void DrawBorderHelper(const ClientNode *np);
25 static void DrawBorderHandles(const ClientNode *np,
26 Pixmap canvas, GC gc);
27 static void DrawBorderButtons(const ClientNode *np,
28 Pixmap canvas, GC gc);
29 static char DrawBorderIcon(BorderIconType t,
30 unsigned xoffset, unsigned yoffset,
31 Pixmap canvas, long fg);
32 static void DrawCloseButton(unsigned xoffset, unsigned yoffset,
33 Pixmap canvas, GC gc, long fg);
34 static void DrawMaxIButton(unsigned xoffset, unsigned yoffset,
35 Pixmap canvas, GC gc, long fg);
36 static void DrawMaxAButton(unsigned xoffset, unsigned yoffset,
37 Pixmap canvas, GC gc, long fg);
38 static void DrawMinButton(unsigned xoffset, unsigned yoffset,
39 Pixmap canvas, GC gc, long fg);
40 static unsigned GetButtonCount(const ClientNode *np);
41
42 #ifdef USE_SHAPE
43 static void FillRoundedRectangle(Drawable d, GC gc, int x, int y,
44 int width, int height, int radius);
45 #endif
46
47 /** Initialize structures. */
InitializeBorders(void)48 void InitializeBorders(void)
49 {
50 memset(buttonNames, 0, sizeof(buttonNames));
51 }
52
53 /** Initialize server resources. */
StartupBorders(void)54 void StartupBorders(void)
55 {
56 unsigned int i;
57
58 for(i = 0; i < BI_COUNT; i++) {
59 if(buttonNames[i]) {
60 buttonIcons[i] = LoadNamedIcon(buttonNames[i], 1, 1);
61 Release(buttonNames[i]);
62 buttonNames[i] = NULL;
63 } else {
64 buttonIcons[i] = NULL;
65 }
66 }
67
68 /* Always load a menu icon for windows without one. */
69 if(buttonIcons[BI_MENU] == NULL) {
70 buttonIcons[BI_MENU] = GetDefaultIcon();
71 }
72 }
73
74 /** Destroy structures. */
DestroyBorders(void)75 void DestroyBorders(void)
76 {
77 unsigned i;
78 for(i = 0; i < BI_COUNT; i++)
79 {
80 if(buttonNames[i]) {
81 Release(buttonNames[i]);
82 buttonNames[i] = NULL;
83 }
84 }
85 }
86
87 /** Get the size of the icon to display on a window. */
GetBorderIconSize(void)88 int GetBorderIconSize(void)
89 {
90 const unsigned height = GetTitleHeight();
91 if(settings.windowDecorations == DECO_MOTIF) {
92 return Max((int)height - 4, 0);
93 } else {
94 return Max((int)height - 6, 0);
95 }
96 }
97
98 /** Determine the border action to take given coordinates. */
GetBorderActionType(const ClientNode * np,int x,int y)99 BorderActionType GetBorderActionType(const ClientNode *np, int x, int y)
100 {
101
102 int north, south, east, west;
103 unsigned int resizeMask;
104 const unsigned int titleHeight = GetTitleHeight();
105
106 GetBorderSize(&np->state, &north, &south, &east, &west);
107
108 /* Check title bar actions. */
109 if((np->state.border & BORDER_TITLE) &&
110 titleHeight > settings.borderWidth) {
111
112 /* Check buttons on the title bar. */
113 int offset = np->width + west;
114 if(y >= south && y <= titleHeight + south) {
115
116 /* Menu button. */
117 if(np->width >= titleHeight) {
118 if(x > west && x <= titleHeight + west) {
119 return BA_MENU;
120 }
121 }
122
123 /* Close button. */
124 if((np->state.border & BORDER_CLOSE) && offset > 2 * titleHeight) {
125 if(x > offset - titleHeight && x < offset) {
126 return BA_CLOSE;
127 }
128 offset -= titleHeight + 1;
129 }
130
131 /* Maximize button. */
132 if((np->state.border & BORDER_MAX) && offset > 2 * titleHeight) {
133 if(x > offset - titleHeight && x < offset) {
134 return BA_MAXIMIZE;
135 }
136 offset -= titleHeight + 1;
137 }
138
139 /* Minimize button. */
140 if((np->state.border & BORDER_MIN) && offset > 2 * titleHeight) {
141 if(x > offset - titleHeight && x < offset) {
142 return BA_MINIMIZE;
143 }
144 }
145
146 }
147
148 /* Check for move. */
149 if(y >= south && y <= titleHeight + south) {
150 if(x > west && x < offset) {
151 if(np->state.border & BORDER_MOVE) {
152 return BA_MOVE;
153 } else {
154 return BA_NONE;
155 }
156 }
157 }
158
159 }
160
161 /* Now we check resize actions.
162 * There is no need to go further if resizing isn't allowed. */
163 if(!(np->state.border & BORDER_RESIZE)) {
164 return BA_NONE;
165 }
166
167 /* We don't allow resizing maximized windows. */
168 resizeMask = BA_RESIZE_S | BA_RESIZE_N
169 | BA_RESIZE_E | BA_RESIZE_W
170 | BA_RESIZE;
171 if(np->state.maxFlags & MAX_HORIZ) {
172 resizeMask &= ~(BA_RESIZE_E | BA_RESIZE_W);
173 }
174 if(np->state.maxFlags & MAX_VERT) {
175 resizeMask &= ~(BA_RESIZE_N | BA_RESIZE_S);
176 }
177 if(np->state.status & STAT_SHADED) {
178 resizeMask &= ~(BA_RESIZE_N | BA_RESIZE_S);
179 }
180
181 /* Check south east/west and north east/west resizing. */
182 if(y > np->height + north - titleHeight) {
183 if(x < titleHeight) {
184 return (BA_RESIZE_S | BA_RESIZE_W | BA_RESIZE) & resizeMask;
185 } else if(x > np->width + west - titleHeight) {
186 return (BA_RESIZE_S | BA_RESIZE_E | BA_RESIZE) & resizeMask;
187 }
188 } else if(y < titleHeight) {
189 if(x < titleHeight) {
190 return (BA_RESIZE_N | BA_RESIZE_W | BA_RESIZE) & resizeMask;
191 } else if(x > np->width + west - titleHeight) {
192 return (BA_RESIZE_N | BA_RESIZE_E | BA_RESIZE) & resizeMask;
193 }
194 }
195
196 /* Check east, west, north, and south resizing. */
197 if(x <= west) {
198 return (BA_RESIZE_W | BA_RESIZE) & resizeMask;
199 } else if(x >= np->width + west) {
200 return (BA_RESIZE_E | BA_RESIZE) & resizeMask;
201 } else if(y >= np->height + north) {
202 return (BA_RESIZE_S | BA_RESIZE) & resizeMask;
203 } else if(y <= south) {
204 return (BA_RESIZE_N | BA_RESIZE) & resizeMask;
205 } else {
206 return BA_NONE;
207 }
208
209 }
210
211 /** Reset the shape of a window border. */
ResetBorder(const ClientNode * np)212 void ResetBorder(const ClientNode *np)
213 {
214 #ifdef USE_SHAPE
215 Pixmap shapePixmap;
216 GC shapeGC;
217 #endif
218
219 int north, south, east, west;
220 int width, height;
221
222 if(np->parent == None) {
223 JXMoveResizeWindow(display, np->window, np->x, np->y,
224 np->width, np->height);
225 return;
226 }
227
228 GrabServer();
229
230 /* Determine the size of the window. */
231 GetBorderSize(&np->state, &north, &south, &east, &west);
232 width = np->width + east + west;
233 if(np->state.status & STAT_SHADED) {
234 height = north + south;
235 } else {
236 height = np->height + north + south;
237 }
238
239 /** Set the window size. */
240 if(!(np->state.status & STAT_SHADED)) {
241 JXMoveResizeWindow(display, np->window, west, north,
242 np->width, np->height);
243 }
244 JXMoveResizeWindow(display, np->parent, np->x - west, np->y - north,
245 width, height);
246
247 #ifdef USE_SHAPE
248 if(settings.cornerRadius > 0 || (np->state.status & STAT_SHAPED)) {
249
250 /* First set the shape to the window border. */
251 shapePixmap = JXCreatePixmap(display, np->parent, width, height, 1);
252 shapeGC = JXCreateGC(display, shapePixmap, 0, NULL);
253
254 /* Make the whole area transparent. */
255 JXSetForeground(display, shapeGC, 0);
256 JXFillRectangle(display, shapePixmap, shapeGC, 0, 0, width, height);
257
258 /* Draw the window area without the corners. */
259 /* Corner bound radius -1 to allow slightly better outline drawing */
260 JXSetForeground(display, shapeGC, 1);
261 if(((np->state.status & STAT_FULLSCREEN) || np->state.maxFlags) &&
262 !(np->state.status & (STAT_SHADED))) {
263 JXFillRectangle(display, shapePixmap, shapeGC, 0, 0, width, height);
264 } else {
265 FillRoundedRectangle(shapePixmap, shapeGC, 0, 0, width, height,
266 settings.cornerRadius - 1);
267 }
268
269 /* Apply the client window. */
270 if(!(np->state.status & STAT_SHADED) &&
271 (np->state.status & STAT_SHAPED)) {
272
273 XRectangle *rects;
274 int count;
275 int ordering;
276
277 /* Cut out an area for the client window. */
278 JXSetForeground(display, shapeGC, 0);
279 JXFillRectangle(display, shapePixmap, shapeGC, west, north,
280 np->width, np->height);
281
282 /* Fill in the visible area. */
283 rects = JXShapeGetRectangles(display, np->window, ShapeBounding,
284 &count, &ordering);
285 if(JLIKELY(rects)) {
286 int i;
287 for(i = 0; i < count; i++) {
288 rects[i].x += east;
289 rects[i].y += north;
290 }
291 JXSetForeground(display, shapeGC, 1);
292 JXFillRectangles(display, shapePixmap, shapeGC, rects, count);
293 JXFree(rects);
294 }
295
296 }
297
298 /* Set the shape. */
299 JXShapeCombineMask(display, np->parent, ShapeBounding, 0, 0,
300 shapePixmap, ShapeSet);
301
302 JXFreeGC(display, shapeGC);
303 JXFreePixmap(display, shapePixmap);
304 }
305 #endif
306
307 UngrabServer();
308
309 }
310
311 /** Draw a client border. */
DrawBorder(ClientNode * np)312 void DrawBorder(ClientNode *np)
313 {
314
315 Assert(np);
316
317 /* Don't draw any more if we are shutting down. */
318 if(JUNLIKELY(shouldExit)) {
319 return;
320 }
321
322 /* Must be either mapped or shaded to have a border. */
323 if(!(np->state.status & (STAT_MAPPED | STAT_SHADED))) {
324 return;
325 }
326
327 /* Hidden and fullscreen windows don't get borders. */
328 if(np->state.status & (STAT_HIDDEN | STAT_FULLSCREEN)) {
329 return;
330 }
331
332 /* Create the frame if needed. */
333 ReparentClient(np);
334
335 /* Return if there is no border. */
336 if(np->parent == None) {
337 return;
338 }
339
340 /* Do the actual drawing. */
341 DrawBorderHelper(np);
342
343 }
344
345 /** Helper method for drawing borders. */
DrawBorderHelper(const ClientNode * np)346 void DrawBorderHelper(const ClientNode *np)
347 {
348
349 ColorType borderTextColor;
350
351 long titleColor1, titleColor2;
352 long outlineColor;
353
354 int north, south, east, west;
355 unsigned int width, height;
356
357 unsigned int buttonCount;
358 int titleWidth, titleHeight;
359 Pixmap canvas;
360 GC gc;
361
362 Assert(np);
363
364 GetBorderSize(&np->state, &north, &south, &east, &west);
365 width = np->width + east + west;
366 height = np->height + north + south;
367
368 /* Determine the colors and gradients to use. */
369 if(np->state.status & (STAT_ACTIVE | STAT_FLASH)) {
370
371 borderTextColor = COLOR_TITLE_ACTIVE_FG;
372 titleColor1 = colors[COLOR_TITLE_ACTIVE_BG1];
373 titleColor2 = colors[COLOR_TITLE_ACTIVE_BG2];
374 outlineColor = colors[COLOR_TITLE_ACTIVE_DOWN];
375
376 } else {
377
378 borderTextColor = COLOR_TITLE_FG;
379 titleColor1 = colors[COLOR_TITLE_BG1];
380 titleColor2 = colors[COLOR_TITLE_BG2];
381 outlineColor = colors[COLOR_TITLE_DOWN];
382
383 }
384
385 /* Set parent background to reduce flicker. */
386 JXSetWindowBackground(display, np->parent, titleColor2);
387
388 canvas = JXCreatePixmap(display, np->parent, width, north, rootDepth);
389 gc = JXCreateGC(display, canvas, 0, NULL);
390
391 /* Clear the window with the right color. */
392 JXSetForeground(display, gc, titleColor2);
393 JXFillRectangle(display, canvas, gc, 0, 0, width, north);
394
395 /* Determine how many pixels may be used for the title. */
396 buttonCount = GetButtonCount(np);
397 titleHeight = GetTitleHeight();
398 titleWidth = width - east - west - 5;
399 titleWidth -= titleHeight * (buttonCount + 1);
400 titleWidth -= settings.windowDecorations == DECO_MOTIF
401 ? (buttonCount + 1) : 0;
402
403 /* Draw the top part (either a title or north border). */
404 if((np->state.border & BORDER_TITLE) &&
405 titleHeight > settings.borderWidth) {
406
407 const unsigned startx = west + 1;
408 unsigned starty = 0;
409 if(settings.windowDecorations == DECO_MOTIF) {
410 if(!(np->state.maxFlags & (MAX_VERT | MAX_TOP))) {
411 starty += settings.borderWidth - 1;
412 }
413 }
414
415 /* Draw a title bar. */
416 DrawHorizontalGradient(canvas, gc, titleColor1, titleColor2,
417 0, 1, width, titleHeight - 2);
418
419 /* Draw the icon. */
420 #ifdef USE_ICONS
421 if(np->width >= titleHeight) {
422 const int iconSize = GetBorderIconSize();
423 IconNode *icon = np->icon ? np->icon : buttonIcons[BI_MENU];
424 PutIcon(icon, canvas, colors[borderTextColor],
425 startx, starty + (titleHeight - iconSize) / 2,
426 iconSize, iconSize);
427 }
428 #endif
429
430 if(np->name && np->name[0] && titleWidth > 0) {
431 const int sheight = GetStringHeight(FONT_BORDER);
432 const int textWidth = GetStringWidth(FONT_BORDER, np->name);
433 unsigned titlex, titley;
434 int xoffset = 0;
435 switch (settings.titleTextAlignment) {
436 case ALIGN_CENTER:
437 xoffset = (titleWidth - textWidth) / 2;
438 break;
439 case ALIGN_RIGHT:
440 xoffset = (titleWidth - textWidth);
441 break;
442 }
443 xoffset = Max(xoffset, 0);
444 titlex = startx + titleHeight + xoffset
445 + (settings.windowDecorations == DECO_MOTIF ? 4 : 0);
446 titley = starty + (titleHeight - sheight) / 2;
447 RenderString(canvas, FONT_BORDER, borderTextColor,
448 titlex, titley, titleWidth, np->name);
449 }
450
451 DrawBorderButtons(np, canvas, gc);
452 }
453
454 /* Copy the pixmap (for the title bar) to the window. */
455
456 /* Copy the pixmap for the title bar and clear the part of
457 * the window to be drawn directly. */
458 if(settings.windowDecorations == DECO_MOTIF) {
459 const int off = np->state.maxFlags ? 0 : 2;
460 JXCopyArea(display, canvas, np->parent, gc, off, off,
461 width - 2 * off, north - off, off, off);
462 JXClearArea(display, np->parent,
463 off, north, width - 2 * off, height - north - off, False);
464 } else {
465 JXCopyArea(display, canvas, np->parent, gc, 1, 1,
466 width - 2, north - 1, 1, 1);
467 JXClearArea(display, np->parent,
468 1, north, width - 2, height - north - 1, False);
469 }
470
471 /* Window outline. */
472 if(settings.windowDecorations == DECO_MOTIF) {
473 DrawBorderHandles(np, np->parent, gc);
474 } else {
475 JXSetForeground(display, gc, outlineColor);
476 if(np->state.status & STAT_SHADED) {
477 DrawRoundedRectangle(np->parent, gc, 0, 0, width - 1, north - 1,
478 settings.cornerRadius);
479 } else if(np->state.maxFlags & MAX_HORIZ) {
480 if(!(np->state.maxFlags & (MAX_TOP | MAX_VERT))) {
481 /* Top */
482 JXDrawLine(display, np->parent, gc, 0, 0, width, 0);
483 }
484 if(!(np->state.maxFlags & (MAX_BOTTOM | MAX_VERT))) {
485 /* Bottom */
486 JXDrawLine(display, np->parent, gc,
487 0, height - 1, width, height - 1);
488 }
489 } else if(np->state.maxFlags & MAX_VERT) {
490 if(!(np->state.maxFlags & (MAX_LEFT | MAX_HORIZ))) {
491 /* Left */
492 JXDrawLine(display, np->parent, gc, 0, 0, 0, height);
493 }
494 if(!(np->state.maxFlags & (MAX_RIGHT | MAX_HORIZ))) {
495 /* Right */
496 JXDrawLine(display, np->parent, gc, width - 1, 0,
497 width - 1, height);
498 }
499 } else {
500 DrawRoundedRectangle(np->parent, gc, 0, 0, width - 1, height - 1,
501 settings.cornerRadius);
502 }
503 }
504
505 JXFreePixmap(display, canvas);
506 JXFreeGC(display, gc);
507
508 }
509
510 /** Draw window handles. */
DrawBorderHandles(const ClientNode * np,Pixmap canvas,GC gc)511 void DrawBorderHandles(const ClientNode *np, Pixmap canvas, GC gc)
512 {
513 XSegment segments[9];
514 long pixelUp, pixelDown;
515 int width, height;
516 int north, south, east, west;
517 unsigned offset = 0;
518 unsigned starty = 0;
519 unsigned titleHeight;
520
521 /* Determine the window size. */
522 GetBorderSize(&np->state, &north, &south, &east, &west);
523 titleHeight = GetTitleHeight();
524 width = np->width + east + west;
525 if(np->state.status & STAT_SHADED) {
526 height = north + south;
527 } else {
528 height = np->height + north + south;
529 }
530
531 /* Determine the y-offset to start drawing. */
532 if(!(np->state.maxFlags & (MAX_VERT | MAX_TOP))) {
533 starty = settings.borderWidth;
534 }
535
536 /* Determine the colors to use. */
537 if(np->state.status & (STAT_ACTIVE | STAT_FLASH)) {
538 pixelUp = colors[COLOR_TITLE_ACTIVE_UP];
539 pixelDown = colors[COLOR_TITLE_ACTIVE_DOWN];
540 } else {
541 pixelUp = colors[COLOR_TITLE_UP];
542 pixelDown = colors[COLOR_TITLE_DOWN];
543 }
544
545 if(!(np->state.maxFlags & (MAX_VERT | MAX_TOP))) {
546 /* Top title border. */
547 segments[offset].x1 = west;
548 segments[offset].y1 = settings.borderWidth;
549 segments[offset].x2 = width - east - 1;
550 segments[offset].y2 = settings.borderWidth;
551 offset += 1;
552 }
553
554 if(!(np->state.maxFlags & (MAX_HORIZ | MAX_RIGHT))) {
555 /* Right title border. */
556 segments[offset].x1 = west;
557 segments[offset].y1 = starty + 1;
558 segments[offset].x2 = east;
559 segments[offset].y2 = titleHeight + south - 1;
560 offset += 1;
561
562 /* Inside right border. */
563 segments[offset].x1 = width - east;
564 segments[offset].y1 = starty;
565 segments[offset].x2 = width - east;
566 segments[offset].y2 = height - south;
567 offset += 1;
568 }
569
570 if(!(np->state.maxFlags & (MAX_HORIZ | MAX_LEFT))) {
571 /* Inside left border. */
572 segments[offset].x1 = west;
573 segments[offset].y1 = starty;
574 segments[offset].x2 = west;
575 segments[offset].y2 = starty + titleHeight;
576 offset += 1;
577 }
578
579 /* Inside bottom border. */
580 segments[offset].x1 = west;
581 segments[offset].y1 = height - south;
582 segments[offset].x2 = width - east;
583 segments[offset].y2 = height - south;
584 offset += 1;
585
586 if(!(np->state.maxFlags & (MAX_HORIZ | MAX_LEFT))) {
587 /* Left border. */
588 segments[offset].x1 = 0;
589 segments[offset].y1 = 0;
590 segments[offset].x2 = 0;
591 segments[offset].y2 = height - 1;
592 offset += 1;
593 segments[offset].x1 = 1;
594 segments[offset].y1 = 1;
595 segments[offset].x2 = 1;
596 segments[offset].y2 = height - 2;
597 offset += 1;
598 }
599
600 if(!(np->state.maxFlags & (MAX_VERT | MAX_TOP))) {
601 /* Top border. */
602 segments[offset].x1 = 1;
603 segments[offset].y1 = 0;
604 segments[offset].x2 = width - 1;
605 segments[offset].y2 = 0;
606 offset += 1;
607 segments[offset].x1 = 1;
608 segments[offset].y1 = 1;
609 segments[offset].x2 = width - 2;
610 segments[offset].y2 = 1;
611 offset += 1;
612 }
613
614 /* Draw pixel-up segments. */
615 JXSetForeground(display, gc, pixelUp);
616 JXDrawSegments(display, canvas, gc, segments, offset);
617 offset = 0;
618
619 /* Bottom title border. */
620 segments[offset].x1 = west + 1;
621 segments[offset].y1 = north - 1;
622 segments[offset].x2 = width - east - 1;
623 segments[offset].y2 = north - 1;
624 offset += 1;
625
626 if(!(np->state.maxFlags & (MAX_HORIZ | MAX_RIGHT))) {
627 /* Right title border. */
628 segments[offset].x1 = width - east - 1;
629 segments[offset].y1 = starty + 1;
630 segments[offset].x2 = width - east - 1;
631 segments[offset].y2 = north - 1;
632 offset += 1;
633 }
634
635 if(!(np->state.maxFlags & (MAX_VERT | MAX_TOP))) {
636 /* Inside top border. */
637 segments[offset].x1 = west - 1;
638 segments[offset].y1 = settings.borderWidth - 1;
639 segments[offset].x2 = width - east;
640 segments[offset].y2 = settings.borderWidth - 1;
641 offset += 1;
642 }
643
644 if(!(np->state.maxFlags & (MAX_HORIZ | MAX_LEFT))) {
645 /* Inside left border. */
646 segments[offset].x1 = west - 1;
647 segments[offset].y1 = starty;
648 segments[offset].x2 = west - 1;
649 segments[offset].y2 = height - starty;
650 offset += 1;
651 }
652
653 if(!(np->state.maxFlags & (MAX_HORIZ | MAX_RIGHT))) {
654 /* Right border. */
655 segments[offset].x1 = width - 1;
656 segments[offset].y1 = 0;
657 segments[offset].x2 = width - 1;
658 segments[offset].y2 = height - 1;
659 offset += 1;
660 segments[offset].x1 = width - 2;
661 segments[offset].y1 = 1;
662 segments[offset].x2 = width - 2;
663 segments[offset].y2 = height - 2;
664 offset += 1;
665 }
666
667 if(!(np->state.maxFlags & (MAX_VERT | MAX_BOTTOM))) {
668 /* Bottom border. */
669 segments[offset].x1 = 0;
670 segments[offset].y1 = height - 1;
671 segments[offset].x2 = width;
672 segments[offset].y2 = height - 1;
673 offset += 1;
674 segments[offset].x1 = 1;
675 segments[offset].y1 = height - 2;
676 segments[offset].x2 = width - 1;
677 segments[offset].y2 = height - 2;
678 offset += 1;
679 }
680
681 /* Draw pixel-down segments. */
682 JXSetForeground(display, gc, pixelDown);
683 JXDrawSegments(display, canvas, gc, segments, offset);
684 offset = 0;
685
686 /* Draw marks */
687 if((np->state.border & BORDER_RESIZE)
688 && !(np->state.status & STAT_SHADED)
689 && !(np->state.maxFlags)) {
690
691 /* Upper left */
692 segments[0].x1 = titleHeight + settings.borderWidth - 1;
693 segments[0].y1 = 0;
694 segments[0].x2 = titleHeight + settings.borderWidth - 1;
695 segments[0].y2 = settings.borderWidth;
696 segments[1].x1 = 0;
697 segments[1].y1 = titleHeight + settings.borderWidth - 1;
698 segments[1].x2 = settings.borderWidth;
699 segments[1].y2 = titleHeight + settings.borderWidth - 1;
700
701 /* Upper right. */
702 segments[2].x1 = width - settings.borderWidth;
703 segments[2].y1 = titleHeight + settings.borderWidth - 1;
704 segments[2].x2 = width;
705 segments[2].y2 = titleHeight + settings.borderWidth - 1;
706 segments[3].x1 = width - titleHeight - settings.borderWidth - 1;
707 segments[3].y1 = 0;
708 segments[3].x2 = width - titleHeight - settings.borderWidth - 1;
709 segments[3].y2 = settings.borderWidth;
710
711 /* Lower left */
712 segments[4].x1 = 0;
713 segments[4].y1 = height - titleHeight - settings.borderWidth - 1;
714 segments[4].x2 = settings.borderWidth;
715 segments[4].y2 = height - titleHeight - settings.borderWidth - 1;
716 segments[5].x1 = titleHeight + settings.borderWidth - 1;
717 segments[5].y1 = height - settings.borderWidth;
718 segments[5].x2 = titleHeight + settings.borderWidth - 1;
719 segments[5].y2 = height;
720
721 /* Lower right */
722 segments[6].x1 = width - settings.borderWidth;
723 segments[6].y1 = height - titleHeight - settings.borderWidth - 1;
724 segments[6].x2 = width;
725 segments[6].y2 = height - titleHeight - settings.borderWidth - 1;
726 segments[7].x1 = width - titleHeight - settings.borderWidth - 1;
727 segments[7].y1 = height - settings.borderWidth;
728 segments[7].x2 = width - titleHeight - settings.borderWidth - 1;
729 segments[7].y2 = height;
730
731 /* Draw pixel-down segments. */
732 JXSetForeground(display, gc, pixelDown);
733 JXDrawSegments(display, canvas, gc, segments, 8);
734
735 /* Upper left */
736 segments[0].x1 = titleHeight + settings.borderWidth;
737 segments[0].y1 = 0;
738 segments[0].x2 = titleHeight + settings.borderWidth;
739 segments[0].y2 = settings.borderWidth;
740 segments[1].x1 = 0;
741 segments[1].y1 = titleHeight + settings.borderWidth;
742 segments[1].x2 = settings.borderWidth;
743 segments[1].y2 = titleHeight + settings.borderWidth;
744
745 /* Upper right */
746 segments[2].x1 = width - titleHeight - settings.borderWidth;
747 segments[2].y1 = 0;
748 segments[2].x2 = width - titleHeight - settings.borderWidth;
749 segments[2].y2 = settings.borderWidth;
750 segments[3].x1 = width - settings.borderWidth;
751 segments[3].y1 = titleHeight + settings.borderWidth;
752 segments[3].x2 = width;
753 segments[3].y2 = titleHeight + settings.borderWidth;
754
755 /* Lower left */
756 segments[4].x1 = 0;
757 segments[4].y1 = height - titleHeight - settings.borderWidth;
758 segments[4].x2 = settings.borderWidth;
759 segments[4].y2 = height - titleHeight - settings.borderWidth;
760 segments[5].x1 = titleHeight + settings.borderWidth;
761 segments[5].y1 = height - settings.borderWidth;
762 segments[5].x2 = titleHeight + settings.borderWidth;
763 segments[5].y2 = height;
764
765 /* Lower right */
766 segments[6].x1 = width - settings.borderWidth;
767 segments[6].y1 = height - titleHeight - settings.borderWidth;
768 segments[6].x2 = width;
769 segments[6].y2 = height - titleHeight - settings.borderWidth;
770 segments[7].x1 = width - titleHeight - settings.borderWidth;
771 segments[7].y1 = height - settings.borderWidth;
772 segments[7].x2 = width - titleHeight - settings.borderWidth;
773 segments[7].y2 = height;
774
775 /* Draw pixel-up segments. */
776 JXSetForeground(display, gc, pixelUp);
777 JXDrawSegments(display, canvas, gc, segments, 8);
778 }
779 }
780
781 /** Determine the number of buttons to be displayed for a client. */
GetButtonCount(const ClientNode * np)782 unsigned GetButtonCount(const ClientNode *np)
783 {
784
785 int north, south, east, west;
786 unsigned count;
787 unsigned buttonWidth;
788 int available;
789 const unsigned titleHeight = GetTitleHeight();
790
791 if(!(np->state.border & BORDER_TITLE)) {
792 return 0;
793 }
794 if(titleHeight <= settings.borderWidth) {
795 return 0;
796 }
797
798 buttonWidth = titleHeight;
799 buttonWidth += settings.windowDecorations == DECO_MOTIF ? 1 : 0;
800
801 GetBorderSize(&np->state, &north, &south, &east, &west);
802
803 count = 0;
804 available = np->width - buttonWidth;
805 if(available < buttonWidth) {
806 return count;
807 }
808
809 if(np->state.border & BORDER_CLOSE) {
810 count += 1;
811 available -= buttonWidth;
812 if(available < buttonWidth) {
813 return count;
814 }
815 }
816
817 if(np->state.border & BORDER_MAX) {
818 count += 1;
819 available -= buttonWidth;
820 if(available < buttonWidth) {
821 return count;
822 }
823 }
824
825 if(np->state.border & BORDER_MIN) {
826 count += 1;
827 }
828
829 return count;
830 }
831
832 /** Draw the buttons on a client frame. */
DrawBorderButtons(const ClientNode * np,Pixmap canvas,GC gc)833 void DrawBorderButtons(const ClientNode *np, Pixmap canvas, GC gc)
834 {
835 long color;
836 long pixelUp, pixelDown;
837 const unsigned titleHeight = GetTitleHeight();
838 int xoffset, yoffset;
839 int north, south, east, west;
840 int minx;
841
842 GetBorderSize(&np->state, &north, &south, &east, &west);
843 xoffset = np->width + Min(east, west) - titleHeight;
844 minx = titleHeight + east;
845 if(xoffset <= minx) {
846 return;
847 }
848
849 /* Determine the colors to use. */
850 if(np->state.status & (STAT_ACTIVE | STAT_FLASH)) {
851 color = colors[COLOR_TITLE_ACTIVE_FG];
852 pixelUp = colors[COLOR_TITLE_ACTIVE_UP];
853 pixelDown = colors[COLOR_TITLE_ACTIVE_DOWN];
854 } else {
855 color = colors[COLOR_TITLE_FG];
856 pixelUp = colors[COLOR_TITLE_UP];
857 pixelDown = colors[COLOR_TITLE_DOWN];
858 }
859
860 yoffset = 0;
861 if(settings.windowDecorations == DECO_MOTIF) {
862 if(!(np->state.maxFlags & (MAX_TOP | MAX_VERT))) {
863 yoffset += settings.borderWidth - 1;
864 }
865 }
866
867 if(settings.windowDecorations == DECO_MOTIF) {
868 JXSetForeground(display, gc, pixelDown);
869 JXDrawLine(display, canvas, gc,
870 west + titleHeight - 1,
871 yoffset,
872 west + titleHeight - 1,
873 yoffset + titleHeight);
874 JXSetForeground(display, gc, pixelUp);
875 JXDrawLine(display, canvas, gc,
876 west + titleHeight,
877 yoffset,
878 west + titleHeight,
879 yoffset + titleHeight);
880 }
881
882 /* Close button. */
883 if(np->state.border & BORDER_CLOSE) {
884
885 JXSetForeground(display, gc, color);
886 DrawCloseButton(xoffset, yoffset, canvas, gc, color);
887
888 if(settings.windowDecorations == DECO_MOTIF) {
889 JXSetForeground(display, gc, pixelDown);
890 JXDrawLine(display, canvas, gc, xoffset - 1,
891 yoffset, xoffset - 1,
892 yoffset + titleHeight);
893 JXSetForeground(display, gc, pixelUp);
894 JXDrawLine(display, canvas, gc, xoffset,
895 yoffset, xoffset, yoffset + titleHeight);
896 xoffset -= 1;
897 }
898
899 xoffset -= titleHeight;
900 if(xoffset <= minx) {
901 return;
902 }
903 }
904
905 /* Maximize button. */
906 if(np->state.border & BORDER_MAX) {
907
908 JXSetForeground(display, gc, color);
909 if(np->state.maxFlags) {
910 DrawMaxAButton(xoffset, yoffset, canvas, gc, color);
911 } else {
912 DrawMaxIButton(xoffset, yoffset, canvas, gc, color);
913 }
914
915 if(settings.windowDecorations == DECO_MOTIF) {
916 JXSetForeground(display, gc, pixelDown);
917 JXDrawLine(display, canvas, gc, xoffset - 1,
918 yoffset, xoffset - 1,
919 yoffset + titleHeight);
920 JXSetForeground(display, gc, pixelUp);
921 JXDrawLine(display, canvas, gc, xoffset,
922 yoffset, xoffset, yoffset + titleHeight);
923 xoffset -= 1;
924 }
925
926 xoffset -= titleHeight;
927 if(xoffset <= minx) {
928 return;
929 }
930 }
931
932 /* Minimize button. */
933 if(np->state.border & BORDER_MIN) {
934
935 JXSetForeground(display, gc, color);
936 DrawMinButton(xoffset, yoffset, canvas, gc, color);
937
938 if(settings.windowDecorations == DECO_MOTIF) {
939 JXSetForeground(display, gc, pixelDown);
940 JXDrawLine(display, canvas, gc, xoffset - 1,
941 yoffset, xoffset - 1,
942 yoffset + titleHeight);
943 JXSetForeground(display, gc, pixelUp);
944 JXDrawLine(display, canvas, gc, xoffset,
945 yoffset, xoffset, yoffset + titleHeight);
946 xoffset -= 1;
947 }
948 }
949 }
950
951 /** Attempt to draw a border icon. */
DrawBorderIcon(BorderIconType t,unsigned xoffset,unsigned yoffset,Pixmap canvas,long fg)952 char DrawBorderIcon(BorderIconType t,
953 unsigned xoffset, unsigned yoffset,
954 Pixmap canvas, long fg)
955 {
956 if(buttonIcons[t]) {
957 #ifdef USE_ICONS
958 const unsigned titleHeight = GetTitleHeight();
959 PutIcon(buttonIcons[t], canvas, fg, xoffset + 2, yoffset + 2,
960 titleHeight - 4, titleHeight - 4);
961 #endif
962 return 1;
963 } else {
964 return 0;
965 }
966 }
967
968 /** Draw a close button. */
DrawCloseButton(unsigned xoffset,unsigned yoffset,Pixmap canvas,GC gc,long fg)969 void DrawCloseButton(unsigned xoffset, unsigned yoffset,
970 Pixmap canvas, GC gc, long fg)
971 {
972 XSegment segments[2];
973 const unsigned titleHeight = GetTitleHeight();
974 unsigned size;
975 unsigned x1, y1;
976 unsigned x2, y2;
977
978 if(DrawBorderIcon(BI_CLOSE, xoffset, yoffset, canvas, fg)) {
979 return;
980 }
981
982 size = (titleHeight + 2) / 3;
983 x1 = xoffset + titleHeight / 2 - size / 2;
984 y1 = yoffset + titleHeight / 2 - size / 2;
985 x2 = x1 + size;
986 y2 = y1 + size;
987
988 segments[0].x1 = x1;
989 segments[0].y1 = y1;
990 segments[0].x2 = x2;
991 segments[0].y2 = y2;
992
993 segments[1].x1 = x2;
994 segments[1].y1 = y1;
995 segments[1].x2 = x1;
996 segments[1].y2 = y2;
997
998 JXSetLineAttributes(display, gc, 2, LineSolid,
999 CapProjecting, JoinBevel);
1000 JXDrawSegments(display, canvas, gc, segments, 2);
1001 JXSetLineAttributes(display, gc, 1, LineSolid,
1002 CapNotLast, JoinMiter);
1003
1004 }
1005
1006 /** Draw an inactive maximize button. */
DrawMaxIButton(unsigned xoffset,unsigned yoffset,Pixmap canvas,GC gc,long fg)1007 void DrawMaxIButton(unsigned xoffset, unsigned yoffset,
1008 Pixmap canvas, GC gc, long fg)
1009 {
1010
1011 XSegment segments[5];
1012 const unsigned titleHeight = GetTitleHeight();
1013 unsigned int size;
1014 unsigned int x1, y1;
1015 unsigned int x2, y2;
1016
1017 if(DrawBorderIcon(BI_MAX, xoffset, yoffset, canvas, fg)) {
1018 return;
1019 }
1020
1021 size = 2 + (titleHeight + 2) / 3;
1022 x1 = xoffset + titleHeight / 2 - size / 2;
1023 y1 = yoffset + titleHeight / 2 - size / 2;
1024 x2 = x1 + size;
1025 y2 = y1 + size;
1026
1027 segments[0].x1 = x1;
1028 segments[0].y1 = y1;
1029 segments[0].x2 = x1 + size;
1030 segments[0].y2 = y1;
1031
1032 segments[1].x1 = x1;
1033 segments[1].y1 = y1 + 1;
1034 segments[1].x2 = x1 + size;
1035 segments[1].y2 = y1 + 1;
1036
1037 segments[2].x1 = x1;
1038 segments[2].y1 = y1;
1039 segments[2].x2 = x1;
1040 segments[2].y2 = y2;
1041
1042 segments[3].x1 = x2;
1043 segments[3].y1 = y1;
1044 segments[3].x2 = x2;
1045 segments[3].y2 = y2;
1046
1047 segments[4].x1 = x1;
1048 segments[4].y1 = y2;
1049 segments[4].x2 = x2;
1050 segments[4].y2 = y2;
1051
1052 JXSetLineAttributes(display, gc, 1, LineSolid,
1053 CapProjecting, JoinMiter);
1054 JXDrawSegments(display, canvas, gc, segments, 5);
1055 JXSetLineAttributes(display, gc, 1, LineSolid,
1056 CapButt, JoinMiter);
1057
1058 }
1059
1060 /** Draw an active maximize button. */
DrawMaxAButton(unsigned xoffset,unsigned yoffset,Pixmap canvas,GC gc,long fg)1061 void DrawMaxAButton(unsigned xoffset, unsigned yoffset,
1062 Pixmap canvas, GC gc, long fg)
1063 {
1064 XSegment segments[8];
1065 unsigned titleHeight;
1066 unsigned size;
1067 unsigned x1, y1;
1068 unsigned x2, y2;
1069 unsigned x3, y3;
1070
1071 if(DrawBorderIcon(BI_MAX_ACTIVE, xoffset, yoffset, canvas, fg)) {
1072 return;
1073 }
1074
1075 titleHeight = GetTitleHeight();
1076 size = 2 + (titleHeight + 2) / 3;
1077 x1 = xoffset + titleHeight / 2 - size / 2;
1078 y1 = yoffset + titleHeight / 2 - size / 2;
1079 x2 = x1 + size;
1080 y2 = y1 + size;
1081 x3 = x1 + size / 2;
1082 y3 = y1 + size / 2;
1083
1084 segments[0].x1 = x1;
1085 segments[0].y1 = y1;
1086 segments[0].x2 = x2;
1087 segments[0].y2 = y1;
1088
1089 segments[1].x1 = x1;
1090 segments[1].y1 = y1 + 1;
1091 segments[1].x2 = x2;
1092 segments[1].y2 = y1 + 1;
1093
1094 segments[2].x1 = x1;
1095 segments[2].y1 = y1;
1096 segments[2].x2 = x1;
1097 segments[2].y2 = y2;
1098
1099 segments[3].x1 = x2;
1100 segments[3].y1 = y1;
1101 segments[3].x2 = x2;
1102 segments[3].y2 = y2;
1103
1104 segments[4].x1 = x1;
1105 segments[4].y1 = y2;
1106 segments[4].x2 = x2;
1107 segments[4].y2 = y2;
1108
1109 segments[5].x1 = x1;
1110 segments[5].y1 = y3;
1111 segments[5].x2 = x3;
1112 segments[5].y2 = y3;
1113
1114 segments[6].x1 = x1;
1115 segments[6].y1 = y3 + 1;
1116 segments[6].x2 = x3;
1117 segments[6].y2 = y3 + 1;
1118
1119 segments[7].x1 = x3;
1120 segments[7].y1 = y3;
1121 segments[7].x2 = x3;
1122 segments[7].y2 = y2;
1123
1124 JXSetLineAttributes(display, gc, 1, LineSolid,
1125 CapProjecting, JoinMiter);
1126 JXDrawSegments(display, canvas, gc, segments, 8);
1127 JXSetLineAttributes(display, gc, 1, LineSolid,
1128 CapButt, JoinMiter);
1129 }
1130
1131 /** Draw a minimize button. */
DrawMinButton(unsigned xoffset,unsigned yoffset,Pixmap canvas,GC gc,long fg)1132 void DrawMinButton(unsigned xoffset, unsigned yoffset,
1133 Pixmap canvas, GC gc, long fg)
1134 {
1135 unsigned titleHeight;
1136 unsigned size;
1137 unsigned x1, y1;
1138 unsigned x2, y2;
1139
1140 if(DrawBorderIcon(BI_MIN, xoffset, yoffset, canvas, fg)) {
1141 return;
1142 }
1143
1144 titleHeight = GetTitleHeight();
1145 size = (titleHeight + 2) / 3;
1146 x1 = xoffset + titleHeight / 2 - size / 2;
1147 y1 = yoffset + titleHeight / 2 - size / 2;
1148 x2 = x1 + size;
1149 y2 = y1 + size;
1150 JXSetLineAttributes(display, gc, 2, LineSolid,
1151 CapProjecting, JoinMiter);
1152 JXDrawLine(display, canvas, gc, x1, y2, x2, y2);
1153 JXSetLineAttributes(display, gc, 1, LineSolid, CapButt, JoinMiter);
1154
1155 }
1156
1157 /** Redraw the borders on the current desktop.
1158 * This should be done after loading clients since the stacking order
1159 * may cause borders on the current desktop to become visible after moving
1160 * clients to their assigned desktops.
1161 */
ExposeCurrentDesktop(void)1162 void ExposeCurrentDesktop(void)
1163 {
1164 ClientNode *np;
1165 int layer;
1166
1167 for(layer = 0; layer < LAYER_COUNT; layer++) {
1168 for(np = nodes[layer]; np; np = np->next) {
1169 if(!(np->state.status & (STAT_HIDDEN | STAT_MINIMIZED))) {
1170 DrawBorder(np);
1171 }
1172 }
1173 }
1174 }
1175
1176 /** Get the height of a window title bar. */
GetTitleHeight(void)1177 unsigned GetTitleHeight(void)
1178 {
1179 if(JUNLIKELY(settings.titleHeight == 0)) {
1180 settings.titleHeight = GetStringHeight(FONT_BORDER) + 4;
1181 }
1182 return settings.titleHeight;
1183 }
1184
1185 /** Get the size of the borders for a client. */
GetBorderSize(const ClientState * state,int * north,int * south,int * east,int * west)1186 void GetBorderSize(const ClientState *state,
1187 int *north, int *south, int *east, int *west)
1188 {
1189 Assert(state);
1190 Assert(north);
1191 Assert(south);
1192 Assert(east);
1193 Assert(west);
1194
1195 /* Full screen is a special case. */
1196 if(state->status & STAT_FULLSCREEN) {
1197 *north = 0;
1198 *south = 0;
1199 *east = 0;
1200 *west = 0;
1201 return;
1202 }
1203
1204 if(state->border & BORDER_OUTLINE) {
1205
1206 if(state->border & BORDER_TITLE) {
1207 *north = GetTitleHeight();
1208 } else if(settings.windowDecorations == DECO_MOTIF) {
1209 *north = 0;
1210 } else {
1211 *north = settings.borderWidth;
1212 if(state->maxFlags & (MAX_VERT | MAX_TOP)) {
1213 *north = Max(0, *north - 1);
1214 }
1215 }
1216 if(state->maxFlags & MAX_VERT) {
1217 *south = 0;
1218 } else {
1219 if(settings.windowDecorations == DECO_MOTIF) {
1220 if(!(state->maxFlags & MAX_TOP)) {
1221 *north += settings.borderWidth;
1222 }
1223 if(!(state->maxFlags & MAX_BOTTOM)) {
1224 *south = settings.borderWidth;
1225 } else {
1226 *south = 0;
1227 }
1228 } else {
1229 if(state->status & STAT_SHADED) {
1230 *south = 0;
1231 } else {
1232 *south = settings.borderWidth;
1233 }
1234 }
1235 }
1236
1237 if(state->maxFlags & (MAX_HORIZ | MAX_LEFT)) {
1238 *west = 0;
1239 } else {
1240 *west = settings.borderWidth;
1241 }
1242 if(state->maxFlags & (MAX_HORIZ | MAX_RIGHT)) {
1243 *east = 0;
1244 } else {
1245 *east = settings.borderWidth;
1246 }
1247
1248 } else {
1249
1250 *north = 0;
1251 *south = 0;
1252 *east = 0;
1253 *west = 0;
1254
1255 }
1256 }
1257
1258 /** Draw a rounded rectangle. */
DrawRoundedRectangle(Drawable d,GC gc,int x,int y,int width,int height,int radius)1259 void DrawRoundedRectangle(Drawable d, GC gc, int x, int y,
1260 int width, int height, int radius)
1261 {
1262 #ifdef USE_SHAPE
1263 #ifdef USE_XMU
1264
1265 if(radius > 0) {
1266 XmuDrawRoundedRectangle(display, d, gc, x, y, width, height,
1267 radius, radius);
1268 } else {
1269 JXDrawRectangle(display, d, gc, x, y, width, height);
1270 }
1271
1272 #else
1273
1274 if(radius > 0) {
1275 XSegment segments[4];
1276 XArc arcs[4];
1277
1278 segments[0].x1 = x + radius; segments[0].y1 = y;
1279 segments[0].x2 = x + width - radius; segments[0].y2 = y;
1280 segments[1].x1 = x + radius; segments[1].y1 = y + height;
1281 segments[1].x2 = x + width - radius; segments[1].y2 = y + height;
1282 segments[2].x1 = x; segments[2].y1 = y + radius;
1283 segments[2].x2 = x; segments[2].y2 = y + height - radius;
1284 segments[3].x1 = x + width; segments[3].y1 = y + radius;
1285 segments[3].x2 = x + width; segments[3].y2 = y + height - radius;
1286 JXDrawSegments(display, d, gc, segments, 4);
1287
1288 arcs[0].x = x;
1289 arcs[0].y = y;
1290 arcs[0].width = radius * 2;
1291 arcs[0].height = radius * 2;
1292 arcs[0].angle1 = 90 * 64;
1293 arcs[0].angle2 = 90 * 64;
1294 arcs[1].x = x + width - radius * 2;
1295 arcs[1].y = y;
1296 arcs[1].width = radius * 2;
1297 arcs[1].height = radius * 2;
1298 arcs[1].angle1 = 0 * 64;
1299 arcs[1].angle2 = 90 * 64;
1300 arcs[2].x = x;
1301 arcs[2].y = y + height - radius * 2;
1302 arcs[2].width = radius * 2;
1303 arcs[2].height = radius * 2;
1304 arcs[2].angle1 = 180 * 64;
1305 arcs[2].angle2 = 90 * 64;
1306 arcs[3].x = x + width - radius * 2;
1307 arcs[3].y = y + height - radius * 2;
1308 arcs[3].width = radius * 2;
1309 arcs[3].height = radius * 2;
1310 arcs[3].angle1 = 270 * 64;
1311 arcs[3].angle2 = 90 * 64;
1312 JXDrawArcs(display, d, gc, arcs, 4);
1313 } else {
1314 JXDrawRectangle(display, d, gc, x, y, width, height);
1315 }
1316
1317 #endif
1318 #else
1319
1320 JXDrawRectangle(display, d, gc, x, y, width, height);
1321
1322 #endif
1323 }
1324
1325 /** Fill a rounded rectangle. */
1326 #ifdef USE_SHAPE
FillRoundedRectangle(Drawable d,GC gc,int x,int y,int width,int height,int radius)1327 void FillRoundedRectangle(Drawable d, GC gc, int x, int y,
1328 int width, int height, int radius)
1329 {
1330
1331 #ifdef USE_XMU
1332
1333 XmuFillRoundedRectangle(display, d, gc, x, y, width, height,
1334 radius, radius);
1335
1336 #else
1337
1338 XRectangle rects[3];
1339 XArc arcs[4];
1340
1341 rects[0].x = x + radius;
1342 rects[0].y = y;
1343 rects[0].width = width - radius * 2;
1344 rects[0].height = radius;
1345 rects[1].x = x;
1346 rects[1].y = radius;
1347 rects[1].width = width;
1348 rects[1].height = height - radius * 2;
1349 rects[2].x = x + radius;
1350 rects[2].y = y + height - radius;
1351 rects[2].width = width - radius * 2;
1352 rects[2].height = radius;
1353 JXFillRectangles(display, d, gc, rects, 3);
1354
1355 arcs[0].x = x;
1356 arcs[0].y = y;
1357 arcs[0].width = radius * 2;
1358 arcs[0].height = radius * 2;
1359 arcs[0].angle1 = 90 * 64;
1360 arcs[0].angle2 = 90 * 64;
1361 arcs[1].x = x + width - radius * 2 - 1;
1362 arcs[1].y = y;
1363 arcs[1].width = radius * 2;
1364 arcs[1].height = radius * 2;
1365 arcs[1].angle1 = 0 * 64;
1366 arcs[1].angle2 = 90 * 64;
1367 arcs[2].x = x;
1368 arcs[2].y = y + height - radius * 2 - 1;
1369 arcs[2].width = radius * 2;
1370 arcs[2].height = radius * 2;
1371 arcs[2].angle1 = 180 * 64;
1372 arcs[2].angle2 = 90 * 64;
1373 arcs[3].x = x + width - radius * 2 - 1;
1374 arcs[3].y = y + height - radius * 2 -1;
1375 arcs[3].width = radius * 2;
1376 arcs[3].height = radius * 2;
1377 arcs[3].angle1 = 270 * 64;
1378 arcs[3].angle2 = 90 * 64;
1379 JXFillArcs(display, d, gc, arcs, 4);
1380
1381 #endif
1382
1383 }
1384 #endif
1385
1386 /** Set the icon to use for a button. */
SetBorderIcon(BorderIconType t,const char * name)1387 void SetBorderIcon(BorderIconType t, const char *name)
1388 {
1389 if(buttonNames[t]) {
1390 Release(buttonNames[t]);
1391 }
1392 buttonNames[t] = CopyString(name);
1393 }
1394
1395