JWM Source Documentation
border.c
Go to the documentation of this file.
1 
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];
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 
49 {
50  memset(buttonNames, 0, sizeof(buttonNames));
51 }
52 
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 
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 
89 {
90  const unsigned height = GetTitleHeight();
92  return Max((int)height - 4, 0);
93  } else {
94  return Max((int)height - 6, 0);
95  }
96 }
97 
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
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 
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 
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 
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 
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  const unsigned starty = settings.windowDecorations == DECO_MOTIF
409  ? (south - 1) : 0;
410 
411  /* Draw a title bar. */
412  DrawHorizontalGradient(canvas, gc, titleColor1, titleColor2,
413  0, 1, width, titleHeight - 2);
414 
415  /* Draw the icon. */
416 #ifdef USE_ICONS
417  if(np->width >= titleHeight) {
418  const int iconSize = GetBorderIconSize();
419  IconNode *icon = np->icon ? np->icon : buttonIcons[BI_MENU];
420  PutIcon(icon, canvas, colors[borderTextColor],
421  startx, starty + (titleHeight - iconSize) / 2,
422  iconSize, iconSize);
423  }
424 #endif
425 
426  if(np->name && np->name[0] && titleWidth > 0) {
427  const int sheight = GetStringHeight(FONT_BORDER);
428  const int textWidth = GetStringWidth(FONT_BORDER, np->name);
429  unsigned titlex, titley;
430  int xoffset = 0;
431  switch (settings.titleTextAlignment) {
432  case ALIGN_CENTER:
433  xoffset = (titleWidth - textWidth) / 2;
434  break;
435  case ALIGN_RIGHT:
436  xoffset = (titleWidth - textWidth);
437  break;
438  }
439  xoffset = Max(xoffset, 0);
440  titlex = startx + titleHeight + xoffset
441  + (settings.windowDecorations == DECO_MOTIF ? 4 : 0);
442  titley = starty + (titleHeight - sheight) / 2;
443  RenderString(canvas, FONT_BORDER, borderTextColor,
444  titlex, titley, titleWidth, np->name);
445  }
446 
447  DrawBorderButtons(np, canvas, gc);
448 
449  }
450 
451  /* Copy the pixmap (for the title bar) to the window. */
452 
453  /* Copy the pixmap for the title bar and clear the part of
454  * the window to be drawn directly. */
456  const int off = np->state.maxFlags ? 0 : 2;
457  JXCopyArea(display, canvas, np->parent, gc, off, off,
458  width - 2 * off, north - off, off, off);
460  off, north, width - 2 * off, height - north - off, False);
461  } else {
462  JXCopyArea(display, canvas, np->parent, gc, 1, 1,
463  width - 2, north - 1, 1, 1);
465  1, north, width - 2, height - north - 1, False);
466  }
467 
468  /* Window outline. */
470  DrawBorderHandles(np, np->parent, gc);
471  } else {
472  JXSetForeground(display, gc, outlineColor);
473  if(np->state.status & STAT_SHADED) {
474  DrawRoundedRectangle(np->parent, gc, 0, 0, width - 1, north - 1,
476  } else if(np->state.maxFlags & MAX_HORIZ) {
477  if(!(np->state.maxFlags & (MAX_TOP | MAX_VERT))) {
478  /* Top */
479  JXDrawLine(display, np->parent, gc, 0, 0, width, 0);
480  }
481  if(!(np->state.maxFlags & (MAX_BOTTOM | MAX_VERT))) {
482  /* Bottom */
483  JXDrawLine(display, np->parent, gc,
484  0, height - 1, width, height - 1);
485  }
486  } else if(np->state.maxFlags & MAX_VERT) {
487  if(!(np->state.maxFlags & (MAX_LEFT | MAX_HORIZ))) {
488  /* Left */
489  JXDrawLine(display, np->parent, gc, 0, 0, 0, height);
490  }
491  if(!(np->state.maxFlags & (MAX_RIGHT | MAX_HORIZ))) {
492  /* Right */
493  JXDrawLine(display, np->parent, gc, width - 1, 0,
494  width - 1, height);
495  }
496  } else {
497  DrawRoundedRectangle(np->parent, gc, 0, 0, width - 1, height - 1,
499  }
500  }
501 
502  JXFreePixmap(display, canvas);
503  JXFreeGC(display, gc);
504 
505 }
506 
508 void DrawBorderHandles(const ClientNode *np, Pixmap canvas, GC gc)
509 {
510  XSegment segments[8];
511  long pixelUp, pixelDown;
512  int width, height;
513  int north, south, east, west;
514  unsigned offset = 0;
515  unsigned titleHeight;
516 
517  /* Determine the window size. */
518  GetBorderSize(&np->state, &north, &south, &east, &west);
519  titleHeight = GetTitleHeight();
520  width = np->width + east + west;
521  if(np->state.status & STAT_SHADED) {
522  height = north + south;
523  } else {
524  height = np->height + north + south;
525  }
526 
527  /* Determine the colors to use. */
528  if(np->state.status & (STAT_ACTIVE | STAT_FLASH)) {
529  pixelUp = colors[COLOR_TITLE_ACTIVE_UP];
530  pixelDown = colors[COLOR_TITLE_ACTIVE_DOWN];
531  } else {
532  pixelUp = colors[COLOR_TITLE_UP];
533  pixelDown = colors[COLOR_TITLE_DOWN];
534  }
535 
536  if(!(np->state.maxFlags & (MAX_VERT | MAX_TOP))) {
537  /* Top title border. */
538  segments[offset].x1 = west;
539  segments[offset].y1 = settings.borderWidth;
540  segments[offset].x2 = width - east - 1;
541  segments[offset].y2 = settings.borderWidth;
542  offset += 1;
543  }
544 
545  if(!(np->state.maxFlags & (MAX_HORIZ | MAX_RIGHT))) {
546  /* Right title border. */
547  segments[offset].x1 = west;
548  segments[offset].y1 = south + 1;
549  segments[offset].x2 = east;
550  segments[offset].y2 = titleHeight + south - 1;
551  offset += 1;
552 
553  /* Inside right border. */
554  segments[offset].x1 = width - east;
555  segments[offset].y1 = south;
556  segments[offset].x2 = width - east;
557  segments[offset].y2 = height - south;
558  offset += 1;
559  }
560 
561  /* Inside bottom border. */
562  segments[offset].x1 = west;
563  segments[offset].y1 = height - south;
564  segments[offset].x2 = width - east;
565  segments[offset].y2 = height - south;
566  offset += 1;
567 
568  if(!(np->state.maxFlags & (MAX_HORIZ | MAX_LEFT))) {
569  /* Left border. */
570  segments[offset].x1 = 0;
571  segments[offset].y1 = 0;
572  segments[offset].x2 = 0;
573  segments[offset].y2 = height - 1;
574  offset += 1;
575  segments[offset].x1 = 1;
576  segments[offset].y1 = 1;
577  segments[offset].x2 = 1;
578  segments[offset].y2 = height - 2;
579  offset += 1;
580  }
581 
582  if(!(np->state.maxFlags & (MAX_VERT | MAX_TOP))) {
583  /* Top border. */
584  segments[offset].x1 = 1;
585  segments[offset].y1 = 0;
586  segments[offset].x2 = width - 1;
587  segments[offset].y2 = 0;
588  offset += 1;
589  segments[offset].x1 = 1;
590  segments[offset].y1 = 1;
591  segments[offset].x2 = width - 2;
592  segments[offset].y2 = 1;
593  offset += 1;
594  }
595 
596  /* Draw pixel-up segments. */
597  JXSetForeground(display, gc, pixelUp);
598  JXDrawSegments(display, canvas, gc, segments, offset);
599  offset = 0;
600 
601  /* Bottom title border. */
602  segments[offset].x1 = west + 1;
603  segments[offset].y1 = north - 1;
604  segments[offset].x2 = width - east - 1;
605  segments[offset].y2 = north - 1;
606  offset += 1;
607 
608  if(!(np->state.maxFlags & (MAX_HORIZ | MAX_RIGHT))) {
609  /* Right title border. */
610  segments[offset].x1 = width - east - 1;
611  segments[offset].y1 = south + 1;
612  segments[offset].x2 = width - east - 1;
613  segments[offset].y2 = north - 1;
614  offset += 1;
615  }
616 
617  if(!(np->state.maxFlags & (MAX_VERT | MAX_TOP))) {
618  /* Inside top border. */
619  segments[offset].x1 = west - 1;
620  segments[offset].y1 = settings.borderWidth - 1;
621  segments[offset].x2 = width - east;
622  segments[offset].y2 = settings.borderWidth - 1;
623  offset += 1;
624  }
625 
626  if(!(np->state.maxFlags & (MAX_HORIZ | MAX_LEFT))) {
627  /* Inside left border. */
628  segments[offset].x1 = west - 1;
629  segments[offset].y1 = south;
630  segments[offset].x2 = west - 1;
631  segments[offset].y2 = height - south;
632  offset += 1;
633  }
634 
635  if(!(np->state.maxFlags & (MAX_HORIZ | MAX_RIGHT))) {
636  /* Right border. */
637  segments[offset].x1 = width - 1;
638  segments[offset].y1 = 0;
639  segments[offset].x2 = width - 1;
640  segments[offset].y2 = height - 1;
641  offset += 1;
642  segments[offset].x1 = width - 2;
643  segments[offset].y1 = 1;
644  segments[offset].x2 = width - 2;
645  segments[offset].y2 = height - 2;
646  offset += 1;
647  }
648 
649  if(!(np->state.maxFlags & (MAX_VERT | MAX_BOTTOM))) {
650  /* Bottom border. */
651  segments[offset].x1 = 0;
652  segments[offset].y1 = height - 1;
653  segments[offset].x2 = width;
654  segments[offset].y2 = height - 1;
655  offset += 1;
656  segments[offset].x1 = 1;
657  segments[offset].y1 = height - 2;
658  segments[offset].x2 = width - 1;
659  segments[offset].y2 = height - 2;
660  offset += 1;
661  }
662 
663  /* Draw pixel-down segments. */
664  JXSetForeground(display, gc, pixelDown);
665  JXDrawSegments(display, canvas, gc, segments, offset);
666  offset = 0;
667 
668  /* Draw marks */
669  if((np->state.border & BORDER_RESIZE)
670  && !(np->state.status & STAT_SHADED)
671  && !(np->state.maxFlags)) {
672 
673  /* Upper left */
674  segments[0].x1 = titleHeight + settings.borderWidth - 1;
675  segments[0].y1 = 0;
676  segments[0].x2 = titleHeight + settings.borderWidth - 1;
677  segments[0].y2 = settings.borderWidth;
678  segments[1].x1 = 0;
679  segments[1].y1 = titleHeight + settings.borderWidth - 1;
680  segments[1].x2 = settings.borderWidth;
681  segments[1].y2 = titleHeight + settings.borderWidth - 1;
682 
683  /* Upper right. */
684  segments[2].x1 = width - settings.borderWidth;
685  segments[2].y1 = titleHeight + settings.borderWidth - 1;
686  segments[2].x2 = width;
687  segments[2].y2 = titleHeight + settings.borderWidth - 1;
688  segments[3].x1 = width - titleHeight - settings.borderWidth - 1;
689  segments[3].y1 = 0;
690  segments[3].x2 = width - titleHeight - settings.borderWidth - 1;
691  segments[3].y2 = settings.borderWidth;
692 
693  /* Lower left */
694  segments[4].x1 = 0;
695  segments[4].y1 = height - titleHeight - settings.borderWidth - 1;
696  segments[4].x2 = settings.borderWidth;
697  segments[4].y2 = height - titleHeight - settings.borderWidth - 1;
698  segments[5].x1 = titleHeight + settings.borderWidth - 1;
699  segments[5].y1 = height - settings.borderWidth;
700  segments[5].x2 = titleHeight + settings.borderWidth - 1;
701  segments[5].y2 = height;
702 
703  /* Lower right */
704  segments[6].x1 = width - settings.borderWidth;
705  segments[6].y1 = height - titleHeight - settings.borderWidth - 1;
706  segments[6].x2 = width;
707  segments[6].y2 = height - titleHeight - settings.borderWidth - 1;
708  segments[7].x1 = width - titleHeight - settings.borderWidth - 1;
709  segments[7].y1 = height - settings.borderWidth;
710  segments[7].x2 = width - titleHeight - settings.borderWidth - 1;
711  segments[7].y2 = height;
712 
713  /* Draw pixel-down segments. */
714  JXSetForeground(display, gc, pixelDown);
715  JXDrawSegments(display, canvas, gc, segments, 8);
716 
717  /* Upper left */
718  segments[0].x1 = titleHeight + settings.borderWidth;
719  segments[0].y1 = 0;
720  segments[0].x2 = titleHeight + settings.borderWidth;
721  segments[0].y2 = settings.borderWidth;
722  segments[1].x1 = 0;
723  segments[1].y1 = titleHeight + settings.borderWidth;
724  segments[1].x2 = settings.borderWidth;
725  segments[1].y2 = titleHeight + settings.borderWidth;
726 
727  /* Upper right */
728  segments[2].x1 = width - titleHeight - settings.borderWidth;
729  segments[2].y1 = 0;
730  segments[2].x2 = width - titleHeight - settings.borderWidth;
731  segments[2].y2 = settings.borderWidth;
732  segments[3].x1 = width - settings.borderWidth;
733  segments[3].y1 = titleHeight + settings.borderWidth;
734  segments[3].x2 = width;
735  segments[3].y2 = titleHeight + settings.borderWidth;
736 
737  /* Lower left */
738  segments[4].x1 = 0;
739  segments[4].y1 = height - titleHeight - settings.borderWidth;
740  segments[4].x2 = settings.borderWidth;
741  segments[4].y2 = height - titleHeight - settings.borderWidth;
742  segments[5].x1 = titleHeight + settings.borderWidth;
743  segments[5].y1 = height - settings.borderWidth;
744  segments[5].x2 = titleHeight + settings.borderWidth;
745  segments[5].y2 = height;
746 
747  /* Lower right */
748  segments[6].x1 = width - settings.borderWidth;
749  segments[6].y1 = height - titleHeight - settings.borderWidth;
750  segments[6].x2 = width;
751  segments[6].y2 = height - titleHeight - settings.borderWidth;
752  segments[7].x1 = width - titleHeight - settings.borderWidth;
753  segments[7].y1 = height - settings.borderWidth;
754  segments[7].x2 = width - titleHeight - settings.borderWidth;
755  segments[7].y2 = height;
756 
757  /* Draw pixel-up segments. */
758  JXSetForeground(display, gc, pixelUp);
759  JXDrawSegments(display, canvas, gc, segments, 8);
760 
761  }
762 }
763 
765 unsigned GetButtonCount(const ClientNode *np)
766 {
767 
768  int north, south, east, west;
769  unsigned count;
770  unsigned buttonWidth;
771  int available;
772  const unsigned titleHeight = GetTitleHeight();
773 
774  if(!(np->state.border & BORDER_TITLE)) {
775  return 0;
776  }
777  if(titleHeight <= settings.borderWidth) {
778  return 0;
779  }
780 
781  buttonWidth = titleHeight;
782  buttonWidth += settings.windowDecorations == DECO_MOTIF ? 1 : 0;
783 
784  GetBorderSize(&np->state, &north, &south, &east, &west);
785 
786  count = 0;
787  available = np->width - buttonWidth;
788  if(available < buttonWidth) {
789  return count;
790  }
791 
792  if(np->state.border & BORDER_CLOSE) {
793  count += 1;
794  available -= buttonWidth;
795  if(available < buttonWidth) {
796  return count;
797  }
798  }
799 
800  if(np->state.border & BORDER_MAX) {
801  count += 1;
802  available -= buttonWidth;
803  if(available < buttonWidth) {
804  return count;
805  }
806  }
807 
808  if(np->state.border & BORDER_MIN) {
809  count += 1;
810  }
811 
812  return count;
813 }
814 
816 void DrawBorderButtons(const ClientNode *np, Pixmap canvas, GC gc)
817 {
818  long color;
819  long pixelUp, pixelDown;
820  const unsigned titleHeight = GetTitleHeight();
821  int xoffset, yoffset;
822  int north, south, east, west;
823  int minx;
824 
825  GetBorderSize(&np->state, &north, &south, &east, &west);
826  xoffset = np->width + west - titleHeight;
827  minx = titleHeight + east;
828  if(xoffset <= minx) {
829  return;
830  }
831 
832  /* Determine the colors to use. */
833  if(np->state.status & (STAT_ACTIVE | STAT_FLASH)) {
834  color = colors[COLOR_TITLE_ACTIVE_FG];
835  pixelUp = colors[COLOR_TITLE_ACTIVE_UP];
836  pixelDown = colors[COLOR_TITLE_ACTIVE_DOWN];
837  } else {
838  color = colors[COLOR_TITLE_FG];
839  pixelUp = colors[COLOR_TITLE_UP];
840  pixelDown = colors[COLOR_TITLE_DOWN];
841  }
842 
844  JXSetForeground(display, gc, pixelDown);
845  JXDrawLine(display, canvas, gc,
846  west + titleHeight - 1,
847  south,
848  west + titleHeight - 1,
849  south + titleHeight);
850  JXSetForeground(display, gc, pixelUp);
851  JXDrawLine(display, canvas, gc,
852  west + titleHeight,
853  south,
854  west + titleHeight,
855  south + titleHeight);
856  }
857 
858  /* Close button. */
859  yoffset = settings.windowDecorations == DECO_MOTIF ? (south - 1) : 0;
860  if(np->state.border & BORDER_CLOSE) {
861 
862  JXSetForeground(display, gc, color);
863  DrawCloseButton(xoffset, yoffset, canvas, gc, color);
864 
866  JXSetForeground(display, gc, pixelDown);
867  JXDrawLine(display, canvas, gc, xoffset - 1,
868  south, xoffset - 1,
869  south + titleHeight);
870  JXSetForeground(display, gc, pixelUp);
871  JXDrawLine(display, canvas, gc, xoffset,
872  south, xoffset, south + titleHeight);
873  xoffset -= 1;
874  }
875 
876  xoffset -= titleHeight;
877  if(xoffset <= minx) {
878  return;
879  }
880  }
881 
882  /* Maximize button. */
883  if(np->state.border & BORDER_MAX) {
884 
885  JXSetForeground(display, gc, color);
886  if(np->state.maxFlags) {
887  DrawMaxAButton(xoffset, yoffset, canvas, gc, color);
888  } else {
889  DrawMaxIButton(xoffset, yoffset, canvas, gc, color);
890  }
891 
893  JXSetForeground(display, gc, pixelDown);
894  JXDrawLine(display, canvas, gc, xoffset - 1,
895  south, xoffset - 1,
896  south + titleHeight);
897  JXSetForeground(display, gc, pixelUp);
898  JXDrawLine(display, canvas, gc, xoffset,
899  south, xoffset, south + titleHeight);
900  xoffset -= 1;
901  }
902 
903  xoffset -= titleHeight;
904  if(xoffset <= minx) {
905  return;
906  }
907  }
908 
909  /* Minimize button. */
910  if(np->state.border & BORDER_MIN) {
911 
912  JXSetForeground(display, gc, color);
913  DrawMinButton(xoffset, yoffset, canvas, gc, color);
914 
916  JXSetForeground(display, gc, pixelDown);
917  JXDrawLine(display, canvas, gc, xoffset - 1,
918  south, xoffset - 1,
919  south + titleHeight);
920  JXSetForeground(display, gc, pixelUp);
921  JXDrawLine(display, canvas, gc, xoffset,
922  south, xoffset, south + titleHeight);
923  xoffset -= 1;
924  }
925  }
926 }
927 
930  unsigned xoffset, unsigned yoffset,
931  Pixmap canvas, long fg)
932 {
933  if(buttonIcons[t]) {
934 #ifdef USE_ICONS
935  const unsigned titleHeight = GetTitleHeight();
936  PutIcon(buttonIcons[t], canvas, fg, xoffset + 2, yoffset + 2,
937  titleHeight - 4, titleHeight - 4);
938 #endif
939  return 1;
940  } else {
941  return 0;
942  }
943 }
944 
946 void DrawCloseButton(unsigned xoffset, unsigned yoffset,
947  Pixmap canvas, GC gc, long fg)
948 {
949  XSegment segments[2];
950  const unsigned titleHeight = GetTitleHeight();
951  unsigned size;
952  unsigned x1, y1;
953  unsigned x2, y2;
954 
955  if(DrawBorderIcon(BI_CLOSE, xoffset, yoffset, canvas, fg)) {
956  return;
957  }
958 
959  size = (titleHeight + 2) / 3;
960  x1 = xoffset + titleHeight / 2 - size / 2;
961  y1 = yoffset + titleHeight / 2 - size / 2;
962  x2 = x1 + size;
963  y2 = y1 + size;
964 
965  segments[0].x1 = x1;
966  segments[0].y1 = y1;
967  segments[0].x2 = x2;
968  segments[0].y2 = y2;
969 
970  segments[1].x1 = x2;
971  segments[1].y1 = y1;
972  segments[1].x2 = x1;
973  segments[1].y2 = y2;
974 
975  JXSetLineAttributes(display, gc, 2, LineSolid,
976  CapProjecting, JoinBevel);
977  JXDrawSegments(display, canvas, gc, segments, 2);
978  JXSetLineAttributes(display, gc, 1, LineSolid,
979  CapNotLast, JoinMiter);
980 
981 }
982 
984 void DrawMaxIButton(unsigned xoffset, unsigned yoffset,
985  Pixmap canvas, GC gc, long fg)
986 {
987 
988  XSegment segments[5];
989  const unsigned titleHeight = GetTitleHeight();
990  unsigned int size;
991  unsigned int x1, y1;
992  unsigned int x2, y2;
993 
994  if(DrawBorderIcon(BI_MAX, xoffset, yoffset, canvas, fg)) {
995  return;
996  }
997 
998  size = 2 + (titleHeight + 2) / 3;
999  x1 = xoffset + titleHeight / 2 - size / 2;
1000  y1 = yoffset + titleHeight / 2 - size / 2;
1001  x2 = x1 + size;
1002  y2 = y1 + size;
1003 
1004  segments[0].x1 = x1;
1005  segments[0].y1 = y1;
1006  segments[0].x2 = x1 + size;
1007  segments[0].y2 = y1;
1008 
1009  segments[1].x1 = x1;
1010  segments[1].y1 = y1 + 1;
1011  segments[1].x2 = x1 + size;
1012  segments[1].y2 = y1 + 1;
1013 
1014  segments[2].x1 = x1;
1015  segments[2].y1 = y1;
1016  segments[2].x2 = x1;
1017  segments[2].y2 = y2;
1018 
1019  segments[3].x1 = x2;
1020  segments[3].y1 = y1;
1021  segments[3].x2 = x2;
1022  segments[3].y2 = y2;
1023 
1024  segments[4].x1 = x1;
1025  segments[4].y1 = y2;
1026  segments[4].x2 = x2;
1027  segments[4].y2 = y2;
1028 
1029  JXSetLineAttributes(display, gc, 1, LineSolid,
1030  CapProjecting, JoinMiter);
1031  JXDrawSegments(display, canvas, gc, segments, 5);
1032  JXSetLineAttributes(display, gc, 1, LineSolid,
1033  CapButt, JoinMiter);
1034 
1035 }
1036 
1038 void DrawMaxAButton(unsigned xoffset, unsigned yoffset,
1039  Pixmap canvas, GC gc, long fg)
1040 {
1041  XSegment segments[8];
1042  unsigned titleHeight;
1043  unsigned size;
1044  unsigned x1, y1;
1045  unsigned x2, y2;
1046  unsigned x3, y3;
1047 
1048  if(DrawBorderIcon(BI_MAX_ACTIVE, xoffset, yoffset, canvas, fg)) {
1049  return;
1050  }
1051 
1052  titleHeight = GetTitleHeight();
1053  size = 2 + (titleHeight + 2) / 3;
1054  x1 = xoffset + titleHeight / 2 - size / 2;
1055  y1 = yoffset + titleHeight / 2 - size / 2;
1056  x2 = x1 + size;
1057  y2 = y1 + size;
1058  x3 = x1 + size / 2;
1059  y3 = y1 + size / 2;
1060 
1061  segments[0].x1 = x1;
1062  segments[0].y1 = y1;
1063  segments[0].x2 = x2;
1064  segments[0].y2 = y1;
1065 
1066  segments[1].x1 = x1;
1067  segments[1].y1 = y1 + 1;
1068  segments[1].x2 = x2;
1069  segments[1].y2 = y1 + 1;
1070 
1071  segments[2].x1 = x1;
1072  segments[2].y1 = y1;
1073  segments[2].x2 = x1;
1074  segments[2].y2 = y2;
1075 
1076  segments[3].x1 = x2;
1077  segments[3].y1 = y1;
1078  segments[3].x2 = x2;
1079  segments[3].y2 = y2;
1080 
1081  segments[4].x1 = x1;
1082  segments[4].y1 = y2;
1083  segments[4].x2 = x2;
1084  segments[4].y2 = y2;
1085 
1086  segments[5].x1 = x1;
1087  segments[5].y1 = y3;
1088  segments[5].x2 = x3;
1089  segments[5].y2 = y3;
1090 
1091  segments[6].x1 = x1;
1092  segments[6].y1 = y3 + 1;
1093  segments[6].x2 = x3;
1094  segments[6].y2 = y3 + 1;
1095 
1096  segments[7].x1 = x3;
1097  segments[7].y1 = y3;
1098  segments[7].x2 = x3;
1099  segments[7].y2 = y2;
1100 
1101  JXSetLineAttributes(display, gc, 1, LineSolid,
1102  CapProjecting, JoinMiter);
1103  JXDrawSegments(display, canvas, gc, segments, 8);
1104  JXSetLineAttributes(display, gc, 1, LineSolid,
1105  CapButt, JoinMiter);
1106 }
1107 
1109 void DrawMinButton(unsigned xoffset, unsigned yoffset,
1110  Pixmap canvas, GC gc, long fg)
1111 {
1112  unsigned titleHeight;
1113  unsigned size;
1114  unsigned x1, y1;
1115  unsigned x2, y2;
1116 
1117  if(DrawBorderIcon(BI_MIN, xoffset, yoffset, canvas, fg)) {
1118  return;
1119  }
1120 
1121  titleHeight = GetTitleHeight();
1122  size = (titleHeight + 2) / 3;
1123  x1 = xoffset + titleHeight / 2 - size / 2;
1124  y1 = yoffset + titleHeight / 2 - size / 2;
1125  x2 = x1 + size;
1126  y2 = y1 + size;
1127  JXSetLineAttributes(display, gc, 2, LineSolid,
1128  CapProjecting, JoinMiter);
1129  JXDrawLine(display, canvas, gc, x1, y2, x2, y2);
1130  JXSetLineAttributes(display, gc, 1, LineSolid, CapButt, JoinMiter);
1131 
1132 }
1133 
1140 {
1141  ClientNode *np;
1142  int layer;
1143 
1144  for(layer = 0; layer < LAYER_COUNT; layer++) {
1145  for(np = nodes[layer]; np; np = np->next) {
1146  if(!(np->state.status & (STAT_HIDDEN | STAT_MINIMIZED))) {
1147  DrawBorder(np);
1148  }
1149  }
1150  }
1151 }
1152 
1154 unsigned GetTitleHeight(void)
1155 {
1156  if(JUNLIKELY(settings.titleHeight == 0)) {
1158  }
1159  return settings.titleHeight;
1160 }
1161 
1163 void GetBorderSize(const ClientState *state,
1164  int *north, int *south, int *east, int *west)
1165 {
1166  Assert(state);
1167  Assert(north);
1168  Assert(south);
1169  Assert(east);
1170  Assert(west);
1171 
1172  /* Full screen is a special case. */
1173  if(state->status & STAT_FULLSCREEN) {
1174  *north = 0;
1175  *south = 0;
1176  *east = 0;
1177  *west = 0;
1178  return;
1179  }
1180 
1181  if(state->border & BORDER_OUTLINE) {
1182 
1183  if(state->border & BORDER_TITLE) {
1184  *north = GetTitleHeight();
1185  } else if(settings.windowDecorations == DECO_MOTIF) {
1186  *north = 0;
1187  } else {
1188  *north = settings.borderWidth;
1189  if(state->maxFlags & (MAX_VERT | MAX_TOP)) {
1190  *north = Max(0, *north - 1);
1191  }
1192  }
1193  if(state->maxFlags & MAX_VERT) {
1194  *south = 0;
1195  } else {
1197  *north += settings.borderWidth;
1198  *south = settings.borderWidth;
1199  } else {
1200  if(state->status & STAT_SHADED) {
1201  *south = 0;
1202  } else {
1203  *south = settings.borderWidth;
1204  }
1205  }
1206  }
1207 
1208  if(state->maxFlags & (MAX_HORIZ | MAX_LEFT)) {
1209  *west = 0;
1210  } else {
1211  *west = settings.borderWidth;
1212  }
1213  if(state->maxFlags & (MAX_HORIZ | MAX_RIGHT)) {
1214  *east = 0;
1215  } else {
1216  *east = settings.borderWidth;
1217  }
1218 
1219  } else {
1220 
1221  *north = 0;
1222  *south = 0;
1223  *east = 0;
1224  *west = 0;
1225 
1226  }
1227 }
1228 
1230 void DrawRoundedRectangle(Drawable d, GC gc, int x, int y,
1231  int width, int height, int radius)
1232 {
1233 #ifdef USE_SHAPE
1234 #ifdef USE_XMU
1235 
1236  if(radius > 0) {
1237  XmuDrawRoundedRectangle(display, d, gc, x, y, width, height,
1238  radius, radius);
1239  } else {
1240  JXDrawRectangle(display, d, gc, x, y, width, height);
1241  }
1242 
1243 #else
1244 
1245  if(radius > 0) {
1246  XSegment segments[4];
1247  XArc arcs[4];
1248 
1249  segments[0].x1 = x + radius; segments[0].y1 = y;
1250  segments[0].x2 = x + width - radius; segments[0].y2 = y;
1251  segments[1].x1 = x + radius; segments[1].y1 = y + height;
1252  segments[1].x2 = x + width - radius; segments[1].y2 = y + height;
1253  segments[2].x1 = x; segments[2].y1 = y + radius;
1254  segments[2].x2 = x; segments[2].y2 = y + height - radius;
1255  segments[3].x1 = x + width; segments[3].y1 = y + radius;
1256  segments[3].x2 = x + width; segments[3].y2 = y + height - radius;
1257  JXDrawSegments(display, d, gc, segments, 4);
1258 
1259  arcs[0].x = x;
1260  arcs[0].y = y;
1261  arcs[0].width = radius * 2;
1262  arcs[0].height = radius * 2;
1263  arcs[0].angle1 = 90 * 64;
1264  arcs[0].angle2 = 90 * 64;
1265  arcs[1].x = x + width - radius * 2;
1266  arcs[1].y = y;
1267  arcs[1].width = radius * 2;
1268  arcs[1].height = radius * 2;
1269  arcs[1].angle1 = 0 * 64;
1270  arcs[1].angle2 = 90 * 64;
1271  arcs[2].x = x;
1272  arcs[2].y = y + height - radius * 2;
1273  arcs[2].width = radius * 2;
1274  arcs[2].height = radius * 2;
1275  arcs[2].angle1 = 180 * 64;
1276  arcs[2].angle2 = 90 * 64;
1277  arcs[3].x = x + width - radius * 2;
1278  arcs[3].y = y + height - radius * 2;
1279  arcs[3].width = radius * 2;
1280  arcs[3].height = radius * 2;
1281  arcs[3].angle1 = 270 * 64;
1282  arcs[3].angle2 = 90 * 64;
1283  JXDrawArcs(display, d, gc, arcs, 4);
1284  } else {
1285  JXDrawRectangle(display, d, gc, x, y, width, height);
1286  }
1287 
1288 #endif
1289 #else
1290 
1291  JXDrawRectangle(display, d, gc, x, y, width, height);
1292 
1293 #endif
1294 }
1295 
1297 #ifdef USE_SHAPE
1298 void FillRoundedRectangle(Drawable d, GC gc, int x, int y,
1299  int width, int height, int radius)
1300 {
1301 
1302 #ifdef USE_XMU
1303 
1304  XmuFillRoundedRectangle(display, d, gc, x, y, width, height,
1305  radius, radius);
1306 
1307 #else
1308 
1309  XRectangle rects[3];
1310  XArc arcs[4];
1311 
1312  rects[0].x = x + radius;
1313  rects[0].y = y;
1314  rects[0].width = width - radius * 2;
1315  rects[0].height = radius;
1316  rects[1].x = x;
1317  rects[1].y = radius;
1318  rects[1].width = width;
1319  rects[1].height = height - radius * 2;
1320  rects[2].x = x + radius;
1321  rects[2].y = y + height - radius;
1322  rects[2].width = width - radius * 2;
1323  rects[2].height = radius;
1324  JXFillRectangles(display, d, gc, rects, 3);
1325 
1326  arcs[0].x = x;
1327  arcs[0].y = y;
1328  arcs[0].width = radius * 2;
1329  arcs[0].height = radius * 2;
1330  arcs[0].angle1 = 90 * 64;
1331  arcs[0].angle2 = 90 * 64;
1332  arcs[1].x = x + width - radius * 2 - 1;
1333  arcs[1].y = y;
1334  arcs[1].width = radius * 2;
1335  arcs[1].height = radius * 2;
1336  arcs[1].angle1 = 0 * 64;
1337  arcs[1].angle2 = 90 * 64;
1338  arcs[2].x = x;
1339  arcs[2].y = y + height - radius * 2 - 1;
1340  arcs[2].width = radius * 2;
1341  arcs[2].height = radius * 2;
1342  arcs[2].angle1 = 180 * 64;
1343  arcs[2].angle2 = 90 * 64;
1344  arcs[3].x = x + width - radius * 2 - 1;
1345  arcs[3].y = y + height - radius * 2 -1;
1346  arcs[3].width = radius * 2;
1347  arcs[3].height = radius * 2;
1348  arcs[3].angle1 = 270 * 64;
1349  arcs[3].angle2 = 90 * 64;
1350  JXFillArcs(display, d, gc, arcs, 4);
1351 
1352 #endif
1353 
1354 }
1355 #endif
1356 
1358 void SetBorderIcon(BorderIconType t, const char *name)
1359 {
1360  if(buttonNames[t]) {
1361  Release(buttonNames[t]);
1362  }
1363  buttonNames[t] = CopyString(name);
1364 }
1365 

joewing.net / Projects / JWM