1 /*
2 * Window decoration routines
3 */
4
5
6 #include "ctwm.h"
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 #include <X11/extensions/shape.h>
13
14 #include "gram.tab.h"
15 #include "image.h"
16 #include "iconmgr.h"
17 #include "screen.h"
18 #include "drawing.h"
19 #include "occupation.h"
20 #include "win_utils.h"
21 #include "workspace_manager.h"
22
23 #include "win_decorations.h"
24
25
26 /* Internal bits */
27 static void ComputeWindowTitleOffsets(TwmWindow *tmp_win, unsigned int width,
28 bool squeeze);
29 static void CreateHighlightWindows(TwmWindow *tmp_win);
30 static void CreateLowlightWindows(TwmWindow *tmp_win);
31
32 typedef enum { TopLeft, TopRight, BottomRight, BottomLeft } CornerType;
33 static void Draw3DCorner(Window w, int x, int y, int width, int height,
34 int thick, int bw, ColorPair cp, CornerType type);
35
36
37
38 /*
39 * First, the bits for setting up the frame window
40 */
41
42 /***********************************************************************
43 *
44 * Procedure:
45 * SetupWindow - set window sizes, this was called from either
46 * AddWindow, EndResize, or HandleConfigureNotify.
47 *
48 * Inputs:
49 * tmp_win - the TwmWindow pointer
50 * x - the x coordinate of the upper-left outer corner of the frame
51 * y - the y coordinate of the upper-left outer corner of the frame
52 * w - the width of the frame window w/o border
53 * h - the height of the frame window w/o border
54 * bw - the border width of the frame window or -1 not to change
55 *
56 * Special Considerations:
57 * This routine will check to make sure the window is not completely
58 * off the display, if it is, it'll bring some of it back on.
59 *
60 * The tmp_win->frame_XXX variables should NOT be updated with the
61 * values of x,y,w,h prior to calling this routine, since the new
62 * values are compared against the old to see whether a synthetic
63 * ConfigureNotify event should be sent. (It should be sent if the
64 * window was moved but not resized.)
65 *
66 ***********************************************************************
67 */
68 void
SetupWindow(TwmWindow * tmp_win,int x,int y,int w,int h,int bw)69 SetupWindow(TwmWindow *tmp_win, int x, int y, int w, int h, int bw)
70 {
71 SetupFrame(tmp_win, x, y, w, h, bw, false);
72 }
73
74 void
SetupFrame(TwmWindow * tmp_win,int x,int y,int w,int h,int bw,bool sendEvent)75 SetupFrame(TwmWindow *tmp_win, int x, int y, int w, int h, int bw,
76 bool sendEvent) /* whether or not to force a send */
77 {
78 bool reShape;
79
80 #ifdef DEBUG
81 fprintf(stderr, "SetupFrame: x=%d, y=%d, w=%d, h=%d, bw=%d\n",
82 x, y, w, h, bw);
83 #endif
84
85 /* Negative border width is a magic value for "use current frame's" */
86 if(bw < 0) {
87 bw = tmp_win->frame_bw;
88 }
89
90
91 /*
92 * Set some bounds on the window location, to be sure part of it is
93 * visible.
94 */
95 #define MARGIN 16 /* one "average" cursor width */
96
97 /*
98 * (x,y) is the top left of the window. Make sure it's not off the
99 * right or bottom of the screen
100 */
101 if(x >= Scr->rootw) {
102 x = Scr->rootw - MARGIN;
103 }
104 if(y >= Scr->rooth) {
105 y = Scr->rooth - MARGIN;
106 }
107
108 /*
109 * Make sure the bottom right isn't off the left or top of the
110 * screen.
111 *
112 * XXX Should this be 2*bw?
113 */
114 if((x + w + bw <= 0)) {
115 x = -w + MARGIN;
116 }
117 if((y + h + bw <= 0)) {
118 y = -h + MARGIN;
119 }
120
121 #undef MARGIN
122
123
124 /*
125 * Do some magic if the window being Setup'd is an icon manager. The
126 * width of an icon manager is variable, so something changing the
127 * width of the window needs to pass that info down to the control
128 * struct for the iconmgr. The height is solely determined by its
129 * contents though, so the h we're passed actually needs to be
130 * overridden based on how tall the iconmgr itself thinks it should
131 * be.
132 */
133 if(tmp_win->isiconmgr) {
134 tmp_win->iconmgrp->width = w - (2 * tmp_win->frame_bw3D);
135 h = tmp_win->iconmgrp->height + tmp_win->title_height +
136 (2 * tmp_win->frame_bw3D);
137 }
138
139 /*
140 * If the window is an Occupy window, we have to tell it about its
141 * new size too.
142 */
143 if(tmp_win->isoccupy) {
144 /* XXX maybe add something like ->iconmgrp above? */
145 OccupyWindow *occwin = Scr->workSpaceMgr.occupyWindow;
146
147 /* occwin not yet set during startup */
148 if(occwin != NULL && occwin->twm_win != NULL) {
149 if(tmp_win != occwin->twm_win) {
150 fprintf(stderr, "%s(): %p not the expected Occupy window %p.\n",
151 __func__, tmp_win, occwin->twm_win);
152 }
153 else {
154 ResizeOccupyWindow(tmp_win);
155 }
156 }
157 }
158
159 /*
160 * According to the July 27, 1988 ICCCM draft, we should send a
161 * "synthetic" ConfigureNotify event to the client if the window
162 * was moved but not resized.
163 *
164 * In section "4.2.3 Window Move" in ICCCM 2.0. x-ref
165 * <https://tronche.com/gui/x/icccm/sec-4.html#s-4.2.3>
166 */
167 if(((x != tmp_win->frame_x || y != tmp_win->frame_y) &&
168 (w == tmp_win->frame_width && h == tmp_win->frame_height)) ||
169 (bw != tmp_win->frame_bw)) {
170 sendEvent = true;
171 }
172
173
174 /*
175 * Do the necessary sizing on the title window
176 */
177 {
178 XWindowChanges xwc;
179 unsigned int xwcm;
180 int title_width;
181
182 /* We're gonna be setting the width, even if it's unchanged */
183 xwcm = CWWidth;
184
185 /* Init: it's as wide as the window, minus borders */
186 title_width = xwc.width = w - (2 * tmp_win->frame_bw3D);
187
188 /*
189 * We really want to compute the offsets later, after the below
190 * block potentially changes title_width to deal with squeezing.
191 * However, adjusting and setting w->rightx based on the final
192 * 'squeeze' argument to CWTO() is what determines how far things
193 * get squeezed, so we need to call that first so the block can
194 * figure out the proper width to squeeze to.
195 *
196 * In the non-squeezing case, that arg does nothing, and we get
197 * all our values set. In the squeezing, though, all the values
198 * _but_ w->rightx get bogus values, so we'll have to call it
199 * again after we re-figure the width.
200 */
201 ComputeWindowTitleOffsets(tmp_win, title_width, true);
202
203 reShape = tmp_win->wShaped;
204
205 /*
206 * If the window has SqueezeTitle, the width of the titlebar may
207 * not be the width of the window (the w we're passed), so figure
208 * what it should be.
209 */
210 if(tmp_win->squeeze_info) {
211 title_width = tmp_win->rightx + Scr->TBInfo.rightoff;
212 if(title_width < xwc.width) {
213 xwc.width = title_width;
214 /*
215 * x-ref above comment. We set squeezed=false here so
216 * w->rightx gets figured right, because we're now
217 * passing the squeezed width. The remaining values are
218 * calculated the same, but will now be set right for the
219 * smaller size.
220 *
221 * See CWTO() comment for possible future cleanup.
222 */
223 ComputeWindowTitleOffsets(tmp_win, title_width, false);
224 if(tmp_win->frame_height != h ||
225 tmp_win->frame_width != w ||
226 tmp_win->frame_bw != bw ||
227 title_width != tmp_win->title_width) {
228 reShape = true;
229 }
230 }
231 else {
232 if(!tmp_win->wShaped) {
233 reShape = true;
234 }
235 title_width = xwc.width;
236 }
237 }
238
239 /* Write back whatever width we figured */
240 tmp_win->title_width = title_width;
241
242 /*
243 * If there is a titlebar, set the height.
244 *
245 * title_height=0 is a slightly stupid and nonintuitive way of
246 * flagging "we don't show a titlebar here", but what the heck...
247 */
248 if(tmp_win->title_height != 0) {
249 tmp_win->title_height = Scr->TitleHeight + bw;
250 }
251
252 /*
253 * If we've got a title window, XConfigure it.
254 *
255 * XXX Hang on, if we don't have a title window, all that work we
256 * just did was bogus, right? And in fact, doesn't accomplish
257 * much of anything anyway. Should this if() be around this
258 * whole block??
259 */
260 if(tmp_win->title_w) {
261 /* If border width is changing, update it and the X/Y too */
262 if(bw != tmp_win->frame_bw) {
263 xwc.border_width = bw;
264 tmp_win->title_x = xwc.x = tmp_win->frame_bw3D - bw;
265 tmp_win->title_y = xwc.y = tmp_win->frame_bw3D - bw;
266 xwcm |= (CWX | CWY | CWBorderWidth);
267 }
268
269 XConfigureWindow(dpy, tmp_win->title_w, xwcm, &xwc);
270 }
271 }
272
273
274 /*
275 * Set a few flags and values for the window as a whole
276 */
277 /* width/height changed? */
278 if(tmp_win->attr.width != w) {
279 tmp_win->widthEverChangedByUser = true;
280 }
281 if(tmp_win->attr.height != (h - tmp_win->title_height)) {
282 tmp_win->heightEverChangedByUser = true;
283 }
284
285 /* Write in new values, if the window isn't squeezed away */
286 if(!tmp_win->squeezed) {
287 tmp_win->attr.width = w - (2 * tmp_win->frame_bw3D);
288 tmp_win->attr.height = h - tmp_win->title_height - (2 * tmp_win->frame_bw3D);
289 }
290
291 /* If it is squeezed, stash values for when we unsqueeze */
292 if(tmp_win->squeezed) {
293 if(x != tmp_win->frame_x) {
294 tmp_win->actual_frame_x += x - tmp_win->frame_x;
295 }
296 if(y != tmp_win->frame_y) {
297 tmp_win->actual_frame_y += y - tmp_win->frame_y;
298 }
299 }
300
301
302 /*
303 * fix up frame and assign size/location values in tmp_win
304 */
305 {
306 XWindowChanges frame_wc;
307 unsigned int frame_mask;
308
309 frame_mask = 0;
310 if(bw != tmp_win->frame_bw) {
311 frame_wc.border_width = tmp_win->frame_bw = bw;
312 if(bw == 0) {
313 tmp_win->frame_bw3D = 0;
314 }
315 frame_mask |= CWBorderWidth;
316 }
317 tmp_win->frame_x = x;
318 tmp_win->frame_y = y;
319 if(tmp_win->UnmapByMovingFarAway && !visible(tmp_win)) {
320 frame_wc.x = Scr->rootw + 1;
321 frame_wc.y = Scr->rooth + 1;
322 }
323 else {
324 frame_wc.x = tmp_win->frame_x;
325 frame_wc.y = tmp_win->frame_y;
326 }
327 frame_wc.width = tmp_win->frame_width = w;
328 frame_wc.height = tmp_win->frame_height = h;
329
330 /* Move/resize the frame */
331 frame_mask |= (CWX | CWY | CWWidth | CWHeight);
332 XConfigureWindow(dpy, tmp_win->frame, frame_mask, &frame_wc);
333
334 /*
335 * Move/resize the "real" window inside the frame. Is it
336 * actually meaningful to "move", since it's always the same
337 * place inside the frame? I'm not sure; this may be necessary
338 * for the client to re-learn its new position in the screen as a
339 * whole?
340 */
341 XMoveResizeWindow(dpy, tmp_win->w, tmp_win->frame_bw3D,
342 tmp_win->title_height + tmp_win->frame_bw3D,
343 tmp_win->attr.width, tmp_win->attr.height);
344 }
345
346
347 /*
348 * If there's a titlebar, we may have hilight/lolight windows in it
349 * to fix up.
350 *
351 * The sizing/positioning is all wonked up. In particular, the
352 * left-side hi/lolite windows don't work out right because they
353 * extend from the left side (after buttons) until name_x, which is
354 * the start of the title, which means they jam right up against the
355 * text. The math happens to mostly work out OK for UseThreeDTitles,
356 * but it doesn't do well in the opposing case.
357 *
358 * The right side never jam right up against the text, because their
359 * inside edge is highlightxr, figured in ComputeWindowTitleOffsets()
360 * to be name_x + name_width. Their placement is asymmetric with the
361 * above especially in the 2d case, but that may be a case of the R
362 * being wrong, not the L; x-ref discussion in CWTO() about it.
363 *
364 * It's probably necessary to fix both at once to get things coming
365 * out right. Of course, all the issues are invisible unless you're
366 * using TitleJustification center or right, which may be rare
367 * enough that nobody who cares enough has noticed...
368 */
369 if(tmp_win->title_height != 0) {
370 XWindowChanges xwc;
371 unsigned int xwcm;
372
373 /*
374 * Left-side window bits
375 */
376 /* Starts from highlightxl, goes to name_x */
377 xwc.width = (tmp_win->name_x - tmp_win->highlightxl);
378
379 /* Pad for 3d pop-in/out */
380 if(Scr->use3Dtitles) {
381 xwc.width -= Scr->TitleButtonShadowDepth;
382 }
383
384 /* Move offscreen if it's got no width to display, else place */
385 if(xwc.width <= 0) {
386 xwc.x = Scr->rootw; /* move offscreen */
387 xwc.width = 1;
388 }
389 else {
390 xwc.x = tmp_win->highlightxl;
391 }
392
393 /* We're setting the X placement and width */
394 xwcm = CWX | CWWidth;
395
396 /* Move it/them */
397 if(tmp_win->hilite_wl) {
398 XConfigureWindow(dpy, tmp_win->hilite_wl, xwcm, &xwc);
399 }
400 if(tmp_win->lolite_wl) {
401 XConfigureWindow(dpy, tmp_win->lolite_wl, xwcm, &xwc);
402 }
403
404
405 /*
406 * Right-side window bits
407 */
408 /* Full width is from the *lite window start to buttons start */
409 xwc.width = (tmp_win->rightx - tmp_win->highlightxr);
410
411 /* If there are buttons to our right, cut down for the padding */
412 if(Scr->TBInfo.nright > 0) {
413 xwc.width -= 2 * Scr->TitlePadding;
414 }
415
416 /* Rest is similar to above for left-side */
417 if(Scr->use3Dtitles) {
418 xwc.width -= Scr->TitleButtonShadowDepth;
419 }
420
421 /* xwc.width/x different from left, so can't just reuse the values */
422 if(xwc.width <= 0) {
423 xwc.x = Scr->rootw;
424 xwc.width = 1;
425 }
426 else {
427 xwc.x = tmp_win->highlightxr;
428 }
429
430 xwcm = CWX | CWWidth; // Not strictly necessary, same as above
431 if(tmp_win->hilite_wr) {
432 XConfigureWindow(dpy, tmp_win->hilite_wr, xwcm, &xwc);
433 }
434 if(tmp_win->lolite_wr) {
435 XConfigureWindow(dpy, tmp_win->lolite_wr, xwcm, &xwc);
436 }
437 }
438
439
440 /* Set X Shape stuff if we need to */
441 if(HasShape && reShape) {
442 SetFrameShape(tmp_win);
443 }
444
445 /* Possible change how it looks in the WorkspaceManager */
446 WMapSetupWindow(tmp_win, x, y, w, h);
447
448 /*
449 * And send Configure notification to the (real) window if we decided
450 * we have to, telling it about what all has happened.
451 */
452 if(sendEvent) {
453 XEvent client_event;
454
455 memset(&client_event, 0, sizeof(client_event)); // JIC
456
457 client_event.type = ConfigureNotify;
458 client_event.xconfigure.display = dpy;
459 client_event.xconfigure.event = tmp_win->w;
460 client_event.xconfigure.window = tmp_win->w;
461 client_event.xconfigure.x = (x + tmp_win->frame_bw - tmp_win->old_bw
462 + tmp_win->frame_bw3D);
463 client_event.xconfigure.y = (y + tmp_win->frame_bw +
464 tmp_win->title_height - tmp_win->old_bw
465 + tmp_win->frame_bw3D);
466 client_event.xconfigure.width = tmp_win->attr.width;
467 client_event.xconfigure.height = tmp_win->attr.height;
468 client_event.xconfigure.border_width = tmp_win->old_bw;
469 /* Real ConfigureNotify events say we're above title window, so ... */
470 /* what if we don't have a title ????? */
471 client_event.xconfigure.above = tmp_win->frame;
472 client_event.xconfigure.override_redirect = False;
473 XSendEvent(dpy, tmp_win->w, False, StructureNotifyMask, &client_event);
474 }
475 }
476
477
478 /*
479 * Set X Shape extension bits for the window. This only gets called if
480 * we already know the server supports Shape, and if there's shaping to
481 * do. There's shaping to do if either the real window itself wants
482 * Shape'ing, or if we're SqueezeTitle'ing it.
483 */
484 void
SetFrameShape(TwmWindow * tmp)485 SetFrameShape(TwmWindow *tmp)
486 {
487 /*
488 * See if the titlebar needs to move (relative to the frame). A
489 * common reason for this is using SqueezeTitle and squeezing the
490 * window as well; when the window is squeezed away, the titlebar is
491 * the only thing displayed, so the frame is coincident with it, and
492 * it starts at (0,0). But when the window is opened, and the
493 * titlebar is narrower than it, it starts at some x offset, so
494 * opening/closing the window squeeze needs to move the position
495 * relative to the frame.
496 */
497 if(tmp->title_w) {
498 int oldx = tmp->title_x, oldy = tmp->title_y;
499 ComputeTitleLocation(tmp);
500 if(oldx != tmp->title_x || oldy != tmp->title_y) {
501 XMoveWindow(dpy, tmp->title_w, tmp->title_x, tmp->title_y);
502 }
503 }
504
505 /*
506 * The frame consists of the shape of the contents window offset by
507 * title_height or'ed with the shape of title_w (which is always
508 * rectangular).
509 */
510 if(tmp->wShaped) {
511 /*
512 * need to do general case
513 */
514 XShapeCombineShape(dpy, tmp->frame, ShapeBounding,
515 tmp->frame_bw3D, tmp->title_height + tmp->frame_bw3D, tmp->w,
516 ShapeBounding, ShapeSet);
517 if(tmp->title_w) {
518 XShapeCombineShape(dpy, tmp->frame, ShapeBounding,
519 tmp->title_x + tmp->frame_bw,
520 tmp->title_y + tmp->frame_bw,
521 tmp->title_w, ShapeBounding,
522 ShapeUnion);
523 }
524 }
525 else {
526 /*
527 * The window itself isn't shaped, so we only need to handle
528 * shaping for what we're doing.
529 */
530 if(tmp->squeeze_info && !tmp->squeezed) {
531 /*
532 * Titlebar is squeezed and window is shown, so we need to
533 * shape out the missing bits on the side
534 * */
535 XRectangle newBounding[2];
536 XRectangle newClip[2];
537 int fbw2 = 2 * tmp->frame_bw;
538
539 /*
540 * Build the border clipping rectangles; one around title, one
541 * around window. The title_[xy] field already have had frame_bw
542 * subtracted off them so that they line up properly in the frame.
543 *
544 * The frame_width and frame_height do *not* include borders.
545 */
546 /* border */
547 newBounding[0].x = tmp->title_x - tmp->frame_bw3D;
548 newBounding[0].y = tmp->title_y - tmp->frame_bw3D;
549 newBounding[0].width = tmp->title_width + fbw2 + 2 * tmp->frame_bw3D;
550 newBounding[0].height = tmp->title_height;
551 newBounding[1].x = -tmp->frame_bw;
552 newBounding[1].y = Scr->TitleHeight;
553 newBounding[1].width = tmp->attr.width + fbw2 + 2 * tmp->frame_bw3D;
554 newBounding[1].height = tmp->attr.height + fbw2 + 2 * tmp->frame_bw3D;
555 XShapeCombineRectangles(dpy, tmp->frame, ShapeBounding, 0, 0,
556 newBounding, 2, ShapeSet, YXBanded);
557 /* insides */
558 newClip[0].x = tmp->title_x + tmp->frame_bw - tmp->frame_bw3D;
559 newClip[0].y = 0;
560 newClip[0].width = tmp->title_width + 2 * tmp->frame_bw3D;
561 newClip[0].height = Scr->TitleHeight + tmp->frame_bw3D;
562 newClip[1].x = 0;
563 newClip[1].y = tmp->title_height;
564 newClip[1].width = tmp->attr.width + 2 * tmp->frame_bw3D;
565 newClip[1].height = tmp->attr.height + 2 * tmp->frame_bw3D;
566 XShapeCombineRectangles(dpy, tmp->frame, ShapeClip, 0, 0,
567 newClip, 2, ShapeSet, YXBanded);
568 }
569 else {
570 /*
571 * Full width title (or it's squeezed, but the window is also
572 * squeezed away, so it's the full width of what we're
573 * showing anyway), so our simple rectangle covers
574 * everything.
575 */
576 XShapeCombineMask(dpy, tmp->frame, ShapeBounding, 0, 0,
577 None, ShapeSet);
578 XShapeCombineMask(dpy, tmp->frame, ShapeClip, 0, 0,
579 None, ShapeSet);
580 }
581 }
582 }
583
584
585
586 /*
587 * Bits related to setting up titlebars. Their subwindows, icons,
588 * highlights, etc.
589 */
590
591 /*
592 * ComputeTitleLocation - calculate the position of the title window; we need
593 * to take the frame_bw into account since we want (0,0) of the title window
594 * to line up with (0,0) of the frame window.
595 *
596 * This sets ->title_[xy], which are the (x,y) of the ->title_w relative
597 * to the frame window.
598 */
599 void
ComputeTitleLocation(TwmWindow * tmp)600 ComputeTitleLocation(TwmWindow *tmp)
601 {
602 /* y position is always the same */
603 tmp->title_y = tmp->frame_bw3D - tmp->frame_bw;
604
605 /* x can vary depending on squeezing */
606 if(tmp->squeeze_info && !tmp->squeezed) {
607 SqueezeInfo *si = tmp->squeeze_info;
608 int basex;
609 int maxwidth = tmp->frame_width;
610 int tw = tmp->title_width + 2 * tmp->frame_bw3D;
611
612 /* figure label base from squeeze info (justification fraction) */
613 if(si->denom == 0) { /* num is pixel based */
614 basex = si->num;
615 }
616 else { /* num/denom is fraction */
617 basex = ((si->num * maxwidth) / si->denom);
618 }
619 if(si->num < 0) {
620 basex += maxwidth;
621 }
622
623 /* adjust for left (nop), center, right justify */
624 switch(si->justify) {
625 case SIJ_LEFT:
626 break; // nop
627 case SIJ_CENTER:
628 basex -= tw / 2;
629 break;
630 case SIJ_RIGHT:
631 basex -= tw - 1;
632 break;
633 }
634
635 /* Clip */
636 if(basex > maxwidth - tw) {
637 basex = maxwidth - tw;
638 }
639 if(basex < 0) {
640 basex = 0;
641 }
642
643 tmp->title_x = basex - tmp->frame_bw + tmp->frame_bw3D;
644 }
645 else {
646 tmp->title_x = tmp->frame_bw3D - tmp->frame_bw;
647 }
648 }
649
650
651 /*
652 * Despite being called "TitlebarButtons", this actually sets up most of
653 * the subwindows inside the titlebar. There are windows for each
654 * button, but also windows for the shifting-color regions on un/focus.
655 */
656 void
CreateWindowTitlebarButtons(TwmWindow * tmp_win)657 CreateWindowTitlebarButtons(TwmWindow *tmp_win)
658 {
659 unsigned long valuemask; /* mask for create windows */
660 XSetWindowAttributes attributes; /* attributes for create windows */
661 int leftx, rightx, y;
662 TitleButton *tb;
663 int nb;
664
665 /*
666 * If there's no titlebar, we don't need any subwindows or anything,
667 * so just make sure it's all empty and return.
668 */
669 if(tmp_win->title_height == 0) {
670 tmp_win->hilite_wl = (Window) 0;
671 tmp_win->hilite_wr = (Window) 0;
672 tmp_win->lolite_wl = (Window) 0;
673 tmp_win->lolite_wr = (Window) 0;
674 return;
675 }
676
677
678 /* Figure where things go */
679 ComputeWindowTitleOffsets(tmp_win, tmp_win->attr.width, false);
680
681 leftx = y = Scr->TBInfo.leftx;
682 rightx = tmp_win->rightx;
683
684 /*
685 * Setup default attributes for creating the subwindows for each
686 * button.
687 */
688 attributes.win_gravity = NorthWestGravity;
689 attributes.background_pixel = tmp_win->title.back;
690 attributes.border_pixel = tmp_win->title.fore;
691 attributes.event_mask = (ButtonPressMask | ButtonReleaseMask |
692 ExposureMask);
693 attributes.cursor = Scr->ButtonCursor;
694 valuemask = (CWWinGravity | CWBackPixel | CWBorderPixel | CWEventMask |
695 CWCursor);
696
697 /*
698 * Initialize the button images/subwindows for the left/right.
699 */
700 tmp_win->titlebuttons = NULL;
701 nb = Scr->TBInfo.nleft + Scr->TBInfo.nright;
702 if(nb > 0) {
703 /*
704 * XXX Rework this into a proper array, either NULL-terminated or
705 * with a stored size, instead of manually implementing a re-calc
706 * of the size and incrementing pointers every time we want to
707 * walk this.
708 */
709 tmp_win->titlebuttons = calloc(nb, sizeof(TBWindow));
710 if(!tmp_win->titlebuttons) {
711 fprintf(stderr, "%s: unable to allocate %d titlebuttons\n",
712 ProgramName, nb);
713 }
714 else {
715 TBWindow *tbw;
716 int boxwidth = (Scr->TBInfo.width + Scr->TBInfo.pad);
717 unsigned int h = (Scr->TBInfo.width - Scr->TBInfo.border * 2);
718
719 for(tb = Scr->TBInfo.head, tbw = tmp_win->titlebuttons; tb;
720 tb = tb->next, tbw++) {
721 int x;
722 if(tb->rightside) {
723 x = rightx;
724 rightx += boxwidth;
725 attributes.win_gravity = NorthEastGravity;
726 }
727 else {
728 x = leftx;
729 leftx += boxwidth;
730 attributes.win_gravity = NorthWestGravity;
731 }
732 tbw->window = XCreateWindow(dpy, tmp_win->title_w, x, y, h, h,
733 Scr->TBInfo.border,
734 0, CopyFromParent,
735 CopyFromParent,
736 valuemask, &attributes);
737 if(Scr->NameDecorations) {
738 XStoreName(dpy, tbw->window, "TB button");
739 }
740
741 /*
742 * XXX Can we just use tb->image for this instead? I
743 * think we can. The TBInfo.head list is assembled in
744 * calls to CreateTitleButton(), which happen during
745 * config file parsing, and then during
746 * InitTitlebarButtons(), which then goes through and
747 * tb->image = GetImage()'s each of the entries. I don't
748 * believe anything ever gets added to TBInfo.head after
749 * that. And the setting in ITB() could only fail in
750 * cases that would presumably also fail for us here. So
751 * this whole block is redundant?
752 */
753 tbw->image = GetImage(tb->name, tmp_win->title);
754 if(! tbw->image) {
755 tbw->image = GetImage(TBPM_QUESTION, tmp_win->title);
756 if(! tbw->image) { /* cannot happen (see util.c) */
757 fprintf(stderr, "%s: unable to add titlebar button \"%s\"\n",
758 ProgramName, tb->name);
759 }
760 }
761 tbw->info = tb;
762 }
763 }
764 }
765
766 /* Windows in the titlebar that show focus */
767 CreateHighlightWindows(tmp_win);
768 CreateLowlightWindows(tmp_win);
769
770 /* Map all those windows we just created... */
771 XMapSubwindows(dpy, tmp_win->title_w);
772
773 /*
774 * ...but hide away the hilite's, since they'll only show up when we
775 * give the window focus. And when we do (even if that when is
776 * "right now"), the focus handler will handle mapping them for us.
777 */
778 if(tmp_win->hilite_wl) {
779 XUnmapWindow(dpy, tmp_win->hilite_wl);
780 }
781 if(tmp_win->hilite_wr) {
782 XUnmapWindow(dpy, tmp_win->hilite_wr);
783 }
784
785 /*
786 * ... but DO show the lolite's, because... XXX this shouldn't be
787 * necessary at all, because they would already have been mapped
788 * during the XMapSubwindows() call above?
789 */
790 if(tmp_win->lolite_wl) {
791 XMapWindow(dpy, tmp_win->lolite_wl);
792 }
793 if(tmp_win->lolite_wr) {
794 XMapWindow(dpy, tmp_win->lolite_wr);
795 }
796
797 return;
798 }
799
800
801 /*
802 * Figure out where the window title and the hi/lolite windows go within
803 * the titlebar as a whole.
804 *
805 * For a particular window, called during the AddWindow() process, and
806 * also via Setup{Window,Frame}().
807 *
808 * This sets w->name_x (x offset for writing the name), w->highlightx[lr]
809 * (x offset for left/right hilite windows), and w->rightx (x offset for
810 * the right buttons), all relative to the title window.
811 *
812 *
813 * The 'squeeze' argument controls whether rightoff should be corrected
814 * for squeezing; when true, it means the passed width doesn't take into
815 * account squeezing. In fact, this adjustment of rightx is what winds
816 * up determining how small the bar gets squeezed to. This relates to
817 * why it's called twice in SetupFrame() to set things up right.
818 *
819 * XXX Should probably either rework how the squeezed width is figured,
820 * or use squeeze to correct everything in here to reduce the scary magic
821 * double-calling.
822 */
823 static void
ComputeWindowTitleOffsets(TwmWindow * tmp_win,unsigned int width,bool squeeze)824 ComputeWindowTitleOffsets(TwmWindow *tmp_win, unsigned int width, bool squeeze)
825 {
826 /*
827 * Space available for the window title for calculating name_x.
828 * (window width) - (space reserved l and r for buttons)
829 */
830 int titlew = width - Scr->TBInfo.titlex - Scr->TBInfo.rightoff;
831
832 /*
833 * First figure where the window name goes, depending on
834 * TitleJustification. If it's on the left/right, and we're using 3d
835 * titles, we have to move it past the TitleShadowDepth, plus a
836 * little extra for visual padding.
837 *
838 * If it's in the middle, we just center on the middle of the
839 * name, without taking into account what that will do if the name is
840 * "too long" for our space, which causes really bad side effects.
841 * The fixing below at least theoretically fixes that, though other
842 * parts of the drawing will still cause Bad Side Effects.
843 */
844 switch(Scr->TitleJustification) {
845 case TJ_UNDEF:
846 /* Can't happen; fallthru to TJ_LEFT */
847 fprintf(stderr, "%s(): Unexpected Scr->TitleJustification %d, "
848 "treating as left\n", __func__, Scr->TitleJustification);
849 case TJ_LEFT:
850 tmp_win->name_x = Scr->TBInfo.titlex;
851 if(Scr->use3Dtitles) {
852 tmp_win->name_x += Scr->TitleShadowDepth + 2;
853 }
854 break;
855 case TJ_CENTER:
856 tmp_win->name_x = Scr->TBInfo.titlex + (titlew - tmp_win->name_width) / 2;
857 break;
858 case TJ_RIGHT:
859 /*
860 * XXX Since this pushes the end of the name way over to the
861 * right, there's no room for the right highlight window.
862 * But shrinking down the size of that is how the titlebar
863 * gets squeezed for SqueezeTitle. So if TJ_RIGHT, the
864 * titlebar will never get squeezed.
865 */
866 tmp_win->name_x = Scr->TBInfo.titlex + titlew - tmp_win->name_width;
867 if(Scr->use3Dtitles) {
868 tmp_win->name_x -= Scr->TitleShadowDepth - 2;
869 }
870 break;
871 }
872
873 /*
874 * Adjust for sanity. Make sure it's always no earlier than the
875 * start of the titlebar (possible especially in the TJ_CENTER case,
876 * but also theoretically if you set a negative ShadowDepth, which
877 * would be stupid and might break other stuff). In the 3d case,
878 * allow twice the ShadowDepth (once for the shadow itself, the
879 * second time for visual padding).
880 */
881 if(Scr->use3Dtitles) {
882 if(tmp_win->name_x < (Scr->TBInfo.titlex + 2 * Scr->TitleShadowDepth)) {
883 tmp_win->name_x = Scr->TBInfo.titlex + 2 * Scr->TitleShadowDepth;
884 }
885 }
886 else if(tmp_win->name_x < Scr->TBInfo.titlex) {
887 tmp_win->name_x = Scr->TBInfo.titlex;
888 }
889
890
891 /*
892 * Left hilite window starts at the left side, plus some space for a
893 * shadow for 3d effects. That's easy.
894 */
895 tmp_win->highlightxl = Scr->TBInfo.titlex;
896 if(Scr->use3Dtitles) {
897 tmp_win->highlightxl += Scr->TitleShadowDepth;
898 }
899
900 /*
901 * Right hilite window starts after the window name.
902 *
903 * With ThreeDTitles, add +2 to match the spacing added onto the left
904 * size of name_x above.
905 *
906 * If there's a window to show for the hilite, and there are buttons
907 * for the right side, we move it over even further. This
908 * particularly causes extra blank space between the name and hilite
909 * bar in the !(UseThreeDTitles) case (because TitlePadding is >0 by
910 * default there). I'm not sure why this is here. I seem to get
911 * better results in both 3D/!3D cases by unconditionally doing the
912 * +=2, and never adding the TitlePadding. Perhaps it should be
913 * changed?
914 */
915 tmp_win->highlightxr = tmp_win->name_x + tmp_win->name_width;
916 if(Scr->use3Dtitles) {
917 tmp_win->highlightxr += 2;
918 }
919 if(tmp_win->hilite_wr || Scr->TBInfo.nright > 0) {
920 tmp_win->highlightxr += Scr->TitlePadding;
921 }
922
923
924 /*
925 * rightoff tells us how much space we need on the right for the
926 * buttons, a little math with the width tells us how far in from the
927 * left to start for that.
928 *
929 * However, if the title bar is squeezed and the window's up, the
930 * titlebar width will be smaller than our 'width' var (which
931 * describes the window as a whole), so we have to make sure it can't
932 * be too far. So start where the right hilite window goes, with a
933 * little space for it to show up, plus misc padding. x-ref comment
934 * at top of function about the weird ways this gets used.
935 */
936 tmp_win->rightx = width - Scr->TBInfo.rightoff;
937 if(squeeze && tmp_win->squeeze_info && !tmp_win->squeezed) {
938 int rx = (tmp_win->highlightxr
939 + (tmp_win->hilite_wr ? Scr->TBInfo.width * 2 : 0)
940 + (Scr->TBInfo.nright > 0 ? Scr->TitlePadding : 0)
941 + Scr->FramePadding);
942 if(rx < tmp_win->rightx) {
943 tmp_win->rightx = rx;
944 }
945 }
946 return;
947 }
948
949
950 /*
951 * Creation/destruction of "hi/lolite windows". These are the
952 * portion[s] of the title bar which change color/form to indicate focus.
953 */
954 static void
CreateHighlightWindows(TwmWindow * tmp_win)955 CreateHighlightWindows(TwmWindow *tmp_win)
956 {
957 XSetWindowAttributes attributes; /* attributes for create windows */
958 unsigned long valuemask;
959 int h = (Scr->TitleHeight - 2 * Scr->FramePadding);
960 int y = Scr->FramePadding;
961
962 /* Init */
963 tmp_win->hilite_wl = (Window) 0;
964 tmp_win->hilite_wr = (Window) 0;
965
966 /* If this window has NoTitleHighlight, don't do nuthin' */
967 if(! tmp_win->titlehighlight) {
968 return;
969 }
970
971 /*
972 * If a special highlight pixmap was given, use that. Otherwise,
973 * use a nice, even gray pattern. The old horizontal lines look really
974 * awful on interlaced monitors (as well as resembling other looks a
975 * little bit too closely), but can be used by putting
976 *
977 * Pixmaps { TitleHighlight "hline2" }
978 *
979 * (or whatever the horizontal line bitmap is named) in the startup
980 * file. If all else fails, use the foreground color to look like a
981 * solid line.
982 */
983 if(! tmp_win->HiliteImage) {
984 if(Scr->HighlightPixmapName) {
985 tmp_win->HiliteImage = GetImage(Scr->HighlightPixmapName, tmp_win->title);
986 }
987 }
988 if(! tmp_win->HiliteImage) {
989 /* No defined image, create shaded bars */
990 Pixmap pm;
991 char *which;
992
993 if(Scr->use3Dtitles && (Scr->Monochrome != COLOR)) {
994 which = "black";
995 }
996 else {
997 which = "gray";
998 }
999
1000 pm = mk_blackgray_pixmap(which, tmp_win->title_w,
1001 tmp_win->title.fore, tmp_win->title.back);
1002
1003 tmp_win->HiliteImage = AllocImage();
1004 tmp_win->HiliteImage->pixmap = pm;
1005 get_blackgray_size(&(tmp_win->HiliteImage->width),
1006 &(tmp_win->HiliteImage->height));
1007 }
1008
1009 /* Use what we came up with, or fall back to solid pixels */
1010 if(tmp_win->HiliteImage) {
1011 valuemask = CWBackPixmap;
1012 attributes.background_pixmap = tmp_win->HiliteImage->pixmap;
1013 }
1014 else {
1015 valuemask = CWBackPixel;
1016 attributes.background_pixel = tmp_win->title.fore;
1017 }
1018
1019 /*
1020 * Adjust y-positioning and height for 3d extras. Both are fixed
1021 * from the time the titlebar is created. The X position gets
1022 * changed on any sort of resize etc, and SetupFrame() handles that.
1023 * We just left 'em at X position 0 here, they'll get moved by SF()
1024 * before being displayed anyway.
1025 */
1026 if(Scr->use3Dtitles) {
1027 y += Scr->TitleShadowDepth;
1028 h -= 2 * Scr->TitleShadowDepth;
1029 }
1030
1031 /*
1032 * There's a left hilite window unless the title is flush left, and
1033 * similarly for the right.
1034 */
1035 #define MKWIN() XCreateWindow(dpy, tmp_win->title_w, 0, y, \
1036 Scr->TBInfo.width, h, \
1037 0, Scr->d_depth, CopyFromParent, \
1038 Scr->d_visual, valuemask, &attributes)
1039 if(Scr->TitleJustification != TJ_LEFT) {
1040 tmp_win->hilite_wl = MKWIN();
1041 if(Scr->NameDecorations) {
1042 XStoreName(dpy, tmp_win->hilite_wl, "hilite_wl");
1043 }
1044 }
1045 if(Scr->TitleJustification != TJ_RIGHT) {
1046 tmp_win->hilite_wr = MKWIN();
1047 if(Scr->NameDecorations) {
1048 XStoreName(dpy, tmp_win->hilite_wr, "hilite_wr");
1049 }
1050 }
1051 #undef MKWIN
1052 }
1053
1054
1055 /*
1056 * Used in events.c in HandleDestroyNotify(), not here. Called during
1057 * window destruction. Technically, this isn't actually deleting the
1058 * windows; the XDestroyWindow() call it makes will destroy all the
1059 * sub-windows. This is actually just for freeing the image we put in
1060 * the window, if there is one.
1061 */
1062 void
DeleteHighlightWindows(TwmWindow * tmp_win)1063 DeleteHighlightWindows(TwmWindow *tmp_win)
1064 {
1065 if(tmp_win->HiliteImage) {
1066 if(Scr->HighlightPixmapName) {
1067 /*
1068 * Image obtained from GetImage(): it is in a cache
1069 * so we don't need to free it. There will not be multiple
1070 * copies if the same xpm:foo image is requested again.
1071 */
1072 }
1073 else {
1074 XFreePixmap(dpy, tmp_win->HiliteImage->pixmap);
1075 free(tmp_win->HiliteImage);
1076 }
1077 tmp_win->HiliteImage = NULL;
1078 }
1079 }
1080
1081
1082 static void
CreateLowlightWindows(TwmWindow * tmp_win)1083 CreateLowlightWindows(TwmWindow *tmp_win)
1084 {
1085 XSetWindowAttributes attributes; /* attributes for create windows */
1086 unsigned long valuemask;
1087 int h = (Scr->TitleHeight - 2 * Scr->FramePadding);
1088 int y = Scr->FramePadding;
1089 ColorPair cp;
1090
1091 /* Init */
1092 tmp_win->lolite_wl = (Window) 0;
1093 tmp_win->lolite_wr = (Window) 0;
1094
1095 /*
1096 * We don't even make lolite windows unless UseSunkTitlePixmap is
1097 * set.
1098 */
1099 if(!Scr->UseSunkTitlePixmap || ! tmp_win->titlehighlight) {
1100 return;
1101 }
1102
1103 /*
1104 * If there's a defined pixmap for highlights, use that with some
1105 * flipped colors.
1106 * */
1107 if(! tmp_win->LoliteImage) {
1108 if(Scr->HighlightPixmapName) {
1109 cp = tmp_win->title;
1110 cp.shadc = tmp_win->title.shadd;
1111 cp.shadd = tmp_win->title.shadc;
1112 tmp_win->LoliteImage = GetImage(Scr->HighlightPixmapName, cp);
1113 }
1114 }
1115
1116 /* Use our image, or fall back to solid colored bar */
1117 if(tmp_win->LoliteImage) {
1118 valuemask = CWBackPixmap;
1119 attributes.background_pixmap = tmp_win->LoliteImage->pixmap;
1120 }
1121 else {
1122 valuemask = CWBackPixel;
1123 attributes.background_pixel = tmp_win->title.fore;
1124 }
1125
1126 /* Extra padding for 3d decorations */
1127 if(Scr->use3Dtitles) {
1128 y += Scr->TitleShadowDepth;
1129 h -= 2 * Scr->TitleShadowDepth;
1130 }
1131
1132 /*
1133 * Bar on the left, unless the title is flush left, and ditto right.
1134 * Same invocation as above for hilites.
1135 */
1136 #define MKWIN() XCreateWindow(dpy, tmp_win->title_w, 0, y, \
1137 Scr->TBInfo.width, h, \
1138 0, Scr->d_depth, CopyFromParent, \
1139 Scr->d_visual, valuemask, &attributes)
1140 if(Scr->TitleJustification != TJ_LEFT) {
1141 tmp_win->lolite_wl = MKWIN();
1142 if(Scr->NameDecorations) {
1143 XStoreName(dpy, tmp_win->lolite_wl, "lolite_wl");
1144 }
1145 }
1146 if(Scr->TitleJustification != TJ_RIGHT) {
1147 tmp_win->lolite_wr = MKWIN();
1148 if(Scr->NameDecorations) {
1149 XStoreName(dpy, tmp_win->lolite_wr, "lolite_wr");
1150 }
1151 }
1152 #undef MKWIN
1153 }
1154
1155 /*
1156 * There is no DeleteLowlightWindows() as a counterpart to the
1157 * HighlightWindows variant. That func doesn't delete the [sub-]window;
1158 * that happens semi-automatically when the frame window is destroyed.
1159 * It only cleans up the Pixmap if there is one. And the only way the
1160 * Lowlight window can wind up with a pixmap is as a copy of the
1161 * highlight window one, in which case when THAT delete gets called all
1162 * the cleanup is done.
1163 */
1164
1165
1166
1167
1168 /*
1169 * Painting the titlebars. The actual displaying of the stuff that's
1170 * figured or stored above.
1171 */
1172
1173 /*
1174 * Write in the window title
1175 */
1176 void
PaintTitle(TwmWindow * tmp_win)1177 PaintTitle(TwmWindow *tmp_win)
1178 {
1179 /* Draw 3d border around title bits */
1180 if(Scr->use3Dtitles) {
1181 /*
1182 * From the start of the title bits (after left button), to the
1183 * start of the right buttons, minus padding.
1184 */
1185 int wid = tmp_win->title_width - Scr->TBInfo.titlex
1186 - Scr->TBInfo.rightoff - Scr->TitlePadding;
1187 ButtonState state = off;
1188
1189 /*
1190 * If SunkFocusWindowTitle, then we "sink in" the whole title
1191 * window when it's focused. Otherwise (!SunkFocus || !focused)
1192 * it's popped up.
1193 */
1194 if(Scr->SunkFocusWindowTitle && (Scr->Focus == tmp_win) &&
1195 (tmp_win->title_height != 0)) {
1196 state = on;
1197 }
1198
1199 Draw3DBorder(tmp_win->title_w, Scr->TBInfo.titlex, 0, wid,
1200 Scr->TitleHeight, Scr->TitleShadowDepth,
1201 tmp_win->title, state, true, false);
1202 }
1203
1204 /* Setup the X graphics context for the drawing */
1205 FB(tmp_win->title.fore, tmp_win->title.back);
1206
1207 /* And write in the name */
1208 if(Scr->use3Dtitles) {
1209 int width, mwidth, len;
1210 XRectangle ink_rect;
1211 XRectangle logical_rect;
1212
1213 /*
1214 * Do a bunch of trying to chop the length down until it will fit
1215 * into the space. This doesn't seem to actually accomplish
1216 * anything at the moment, as somehow we wind up with nothing
1217 * visible in the case of a long enough title.
1218 */
1219 len = strlen(tmp_win->name);
1220 XmbTextExtents(Scr->TitleBarFont.font_set,
1221 tmp_win->name, len,
1222 &ink_rect, &logical_rect);
1223 width = logical_rect.width;
1224 mwidth = tmp_win->title_width - Scr->TBInfo.titlex -
1225 Scr->TBInfo.rightoff - Scr->TitlePadding -
1226 Scr->TitleShadowDepth - 4;
1227 while((len > 0) && (width > mwidth)) {
1228 len--;
1229 XmbTextExtents(Scr->TitleBarFont.font_set,
1230 tmp_win->name, len,
1231 &ink_rect, &logical_rect);
1232 width = logical_rect.width;
1233 }
1234
1235 /*
1236 * Write it in. The Y position is subtly different from the
1237 * !3Dtitles case due to the potential bordering around it. It's
1238 * not quite clear whether it should be.
1239 */
1240 ((Scr->Monochrome != COLOR) ? XmbDrawImageString : XmbDrawString)
1241 (dpy, tmp_win->title_w, Scr->TitleBarFont.font_set,
1242 Scr->NormalGC,
1243 tmp_win->name_x,
1244 (Scr->TitleHeight - logical_rect.height) / 2 + (- logical_rect.y),
1245 tmp_win->name, len);
1246 }
1247 else {
1248 /*
1249 * XXX The 3Dtitle case above has attempted correction for a lot of
1250 * stuff. It's not entirely clear that it's either needed there,
1251 * or not needed here. It's also not obvious that the magic
1252 * it does to support monochrome isn't applicable here, thought
1253 * it may be a side effect of differences in how the backing
1254 * titlebar is painted. This requires investigation, and either
1255 * fixing the wrong or documentation of why it's right.
1256 */
1257 XmbDrawString(dpy, tmp_win->title_w, Scr->TitleBarFont.font_set,
1258 Scr->NormalGC,
1259 tmp_win->name_x, Scr->TitleBarFont.y,
1260 tmp_win->name, strlen(tmp_win->name));
1261 }
1262 }
1263
1264
1265 /*
1266 * Painting in the buttons on the titlebar
1267 */
1268 /* Iterate and show them all */
1269 void
PaintTitleButtons(TwmWindow * tmp_win)1270 PaintTitleButtons(TwmWindow *tmp_win)
1271 {
1272 int i;
1273 TBWindow *tbw;
1274 int nb = Scr->TBInfo.nleft + Scr->TBInfo.nright;
1275
1276 for(i = 0, tbw = tmp_win->titlebuttons; i < nb; i++, tbw++) {
1277 if(tbw) {
1278 PaintTitleButton(tmp_win, tbw);
1279 }
1280 }
1281 }
1282
1283 /* Blit the pixmap into the right place */
1284 void
PaintTitleButton(TwmWindow * tmp_win,TBWindow * tbw)1285 PaintTitleButton(TwmWindow *tmp_win, TBWindow *tbw)
1286 {
1287 TitleButton *tb = tbw->info;
1288
1289 XCopyArea(dpy, tbw->image->pixmap, tbw->window, Scr->NormalGC,
1290 tb->srcx, tb->srcy, tb->width, tb->height,
1291 tb->dstx, tb->dsty);
1292 return;
1293 }
1294
1295
1296
1297
1298 /*
1299 * Stuff for window borders
1300 */
1301
1302
1303 /*
1304 * Currently only used in drawing window decoration borders. Contrast
1305 * with Draw3DBorder() which is used for all sorts of generalized
1306 * drawing.
1307 */
1308 static void
Draw3DCorner(Window w,int x,int y,int width,int height,int thick,int bw,ColorPair cp,CornerType type)1309 Draw3DCorner(Window w, int x, int y, int width, int height,
1310 int thick, int bw, ColorPair cp, CornerType type)
1311 {
1312 XRectangle rects [2];
1313
1314 switch(type) {
1315 case TopLeft:
1316 Draw3DBorder(w, x, y, width, height, bw, cp, off, true, false);
1317 Draw3DBorder(w, x + thick - bw, y + thick - bw,
1318 width - thick + 2 * bw, height - thick + 2 * bw,
1319 bw, cp, on, true, false);
1320 break;
1321 case TopRight:
1322 Draw3DBorder(w, x, y, width, height, bw, cp, off, true, false);
1323 Draw3DBorder(w, x, y + thick - bw,
1324 width - thick + bw, height - thick,
1325 bw, cp, on, true, false);
1326 break;
1327 case BottomRight:
1328 rects [0].x = x + width - thick;
1329 rects [0].y = y;
1330 rects [0].width = thick;
1331 rects [0].height = height;
1332 rects [1].x = x;
1333 rects [1].y = y + width - thick;
1334 rects [1].width = width - thick;
1335 rects [1].height = thick;
1336 XSetClipRectangles(dpy, Scr->BorderGC, 0, 0, rects, 2, Unsorted);
1337 Draw3DBorder(w, x, y, width, height, bw, cp, off, true, false);
1338 Draw3DBorder(w, x, y,
1339 width - thick + bw, height - thick + bw,
1340 bw, cp, on, true, false);
1341 XSetClipMask(dpy, Scr->BorderGC, None);
1342 break;
1343 case BottomLeft:
1344 rects [0].x = x;
1345 rects [0].y = y;
1346 rects [0].width = thick;
1347 rects [0].height = height;
1348 rects [1].x = x + thick;
1349 rects [1].y = y + height - thick;
1350 rects [1].width = width - thick;
1351 rects [1].height = thick;
1352 XSetClipRectangles(dpy, Scr->BorderGC, 0, 0, rects, 2, Unsorted);
1353 Draw3DBorder(w, x, y, width, height, bw, cp, off, true, false);
1354 Draw3DBorder(w, x + thick - bw, y,
1355 width - thick, height - thick + bw,
1356 bw, cp, on, true, false);
1357 XSetClipMask(dpy, Scr->BorderGC, None);
1358 break;
1359 default:
1360 /* Bad code */
1361 fprintf(stderr, "Internal error: Invalid Draw3DCorner type %d\n",
1362 type);
1363 break;
1364 }
1365 return;
1366 }
1367
1368
1369 /*
1370 * Draw the borders onto the frame for a window
1371 */
1372 void
PaintBorders(TwmWindow * tmp_win,bool focus)1373 PaintBorders(TwmWindow *tmp_win, bool focus)
1374 {
1375 ColorPair cp;
1376
1377 /* Set coloring based on focus/highlight state */
1378 cp = (focus && tmp_win->highlight) ? tmp_win->borderC : tmp_win->border_tile;
1379
1380 /*
1381 * If there's no height to the title bar (e.g., on NoTitle windows),
1382 * there's nothing much to corner around, so we can just border up
1383 * the whole thing. Since the bordering on the frame is "below" the
1384 * real window, we can just draw one giant square, and then one
1385 * slightly smaller (but still larger than the real-window itself)
1386 * square on top of it, and voila; border!
1387 */
1388 if(tmp_win->title_height == 0) {
1389 Draw3DBorder(tmp_win->frame, 0, 0,
1390 tmp_win->frame_width, tmp_win->frame_height,
1391 Scr->BorderShadowDepth, cp, off, true, false);
1392 Draw3DBorder(tmp_win->frame,
1393 tmp_win->frame_bw3D - Scr->BorderShadowDepth,
1394 tmp_win->frame_bw3D - Scr->BorderShadowDepth,
1395 tmp_win->frame_width - 2 * tmp_win->frame_bw3D + 2 * Scr->BorderShadowDepth,
1396 tmp_win->frame_height - 2 * tmp_win->frame_bw3D + 2 * Scr->BorderShadowDepth,
1397 Scr->BorderShadowDepth, cp, on, true, false);
1398 return;
1399 }
1400
1401 /*
1402 * Otherwise, we have to draw corners, which means we have to
1403 * individually draw the 4 side borders between them as well.
1404 *
1405 * So start by laying out the 4 corners.
1406 */
1407
1408 /* How far the corners extend along the sides */
1409 #define CORNERLEN (Scr->TitleHeight + tmp_win->frame_bw3D)
1410
1411 Draw3DCorner(tmp_win->frame,
1412 tmp_win->title_x - tmp_win->frame_bw3D,
1413 0,
1414 CORNERLEN, CORNERLEN,
1415 tmp_win->frame_bw3D, Scr->BorderShadowDepth, cp, TopLeft);
1416 Draw3DCorner(tmp_win->frame,
1417 tmp_win->title_x + tmp_win->title_width - Scr->TitleHeight,
1418 0,
1419 CORNERLEN, CORNERLEN,
1420 tmp_win->frame_bw3D, Scr->BorderShadowDepth, cp, TopRight);
1421 Draw3DCorner(tmp_win->frame,
1422 tmp_win->frame_width - CORNERLEN,
1423 tmp_win->frame_height - CORNERLEN,
1424 CORNERLEN, CORNERLEN,
1425 tmp_win->frame_bw3D, Scr->BorderShadowDepth, cp, BottomRight);
1426 Draw3DCorner(tmp_win->frame,
1427 0,
1428 tmp_win->frame_height - CORNERLEN,
1429 CORNERLEN, CORNERLEN,
1430 tmp_win->frame_bw3D, Scr->BorderShadowDepth, cp, BottomLeft);
1431
1432
1433 /*
1434 * And draw the borders on the 4 sides between the corners
1435 */
1436 /* Top */
1437 Draw3DBorder(tmp_win->frame,
1438 tmp_win->title_x + Scr->TitleHeight,
1439 0,
1440 tmp_win->title_width - 2 * Scr->TitleHeight,
1441 tmp_win->frame_bw3D,
1442 Scr->BorderShadowDepth, cp, off, true, false);
1443 /* Bottom */
1444 Draw3DBorder(tmp_win->frame,
1445 tmp_win->frame_bw3D + Scr->TitleHeight,
1446 tmp_win->frame_height - tmp_win->frame_bw3D,
1447 tmp_win->frame_width - 2 * CORNERLEN,
1448 tmp_win->frame_bw3D,
1449 Scr->BorderShadowDepth, cp, off, true, false);
1450 /* Left */
1451 Draw3DBorder(tmp_win->frame,
1452 0,
1453 Scr->TitleHeight + tmp_win->frame_bw3D,
1454 tmp_win->frame_bw3D,
1455 tmp_win->frame_height - 2 * CORNERLEN,
1456 Scr->BorderShadowDepth, cp, off, true, false);
1457 /* Right */
1458 Draw3DBorder(tmp_win->frame,
1459 tmp_win->frame_width - tmp_win->frame_bw3D,
1460 Scr->TitleHeight + tmp_win->frame_bw3D,
1461 tmp_win->frame_bw3D,
1462 tmp_win->frame_height - 2 * CORNERLEN,
1463 Scr->BorderShadowDepth, cp, off, true, false);
1464
1465 #undef CORNERLEN
1466
1467
1468 /*
1469 * If SqueezeTitle is set for the window, and the window isn't
1470 * squeezed away (whether because it's focused, or it's just not
1471 * squeezed at all), then we need to draw a "top" border onto the
1472 * bare bits of the window to the left/right of where the titlebar
1473 * is.
1474 */
1475 if(tmp_win->squeeze_info && !tmp_win->squeezed) {
1476 /* To the left */
1477 Draw3DBorder(tmp_win->frame,
1478 0,
1479 Scr->TitleHeight,
1480 tmp_win->title_x,
1481 tmp_win->frame_bw3D,
1482 Scr->BorderShadowDepth, cp, off, true, false);
1483 /* And the right */
1484 Draw3DBorder(tmp_win->frame,
1485 tmp_win->title_x + tmp_win->title_width,
1486 Scr->TitleHeight,
1487 tmp_win->frame_width - tmp_win->title_x - tmp_win->title_width,
1488 tmp_win->frame_bw3D,
1489 Scr->BorderShadowDepth, cp, off, true, false);
1490 }
1491 }
1492
1493
1494 /*
1495 * Setup the mouse cursor for various locations on the border of a
1496 * window.
1497 *
1498 * Formerly in util.c
1499 */
1500 void
SetBorderCursor(TwmWindow * tmp_win,int x,int y)1501 SetBorderCursor(TwmWindow *tmp_win, int x, int y)
1502 {
1503 Cursor cursor;
1504 XSetWindowAttributes attr;
1505 int h, fw, fh, wd;
1506
1507 if(!tmp_win) {
1508 return;
1509 }
1510
1511 /* Use the max of these, but since one is always 0 we can add them. */
1512 wd = tmp_win->frame_bw + tmp_win->frame_bw3D;
1513 h = Scr->TitleHeight + wd;
1514 fw = tmp_win->frame_width;
1515 fh = tmp_win->frame_height;
1516
1517 #if defined DEBUG && DEBUG
1518 fprintf(stderr, "wd=%d h=%d, fw=%d fh=%d x=%d y=%d\n",
1519 wd, h, fw, fh, x, y);
1520 #endif
1521
1522 /*
1523 * If not using 3D borders:
1524 *
1525 * The left border has negative x coordinates,
1526 * The top border (above the title) has negative y coordinates.
1527 * The title is TitleHeight high, the next wd pixels are border.
1528 * The bottom border has coordinates >= the frame height.
1529 * The right border has coordinates >= the frame width.
1530 *
1531 * If using 3D borders: all coordinates are >= 0, and all coordinates
1532 * are higher by the border width.
1533 *
1534 * Since we only get events when we're actually in the border, we simply
1535 * allow for both cases at the same time.
1536 */
1537
1538 if((x < -wd) || (y < -wd)) {
1539 cursor = Scr->FrameCursor;
1540 }
1541 else if(x < h) {
1542 if(y < h) {
1543 cursor = TopLeftCursor;
1544 }
1545 else if(y >= fh - h) {
1546 cursor = BottomLeftCursor;
1547 }
1548 else {
1549 cursor = LeftCursor;
1550 }
1551 }
1552 else if(x >= fw - h) {
1553 if(y < h) {
1554 cursor = TopRightCursor;
1555 }
1556 else if(y >= fh - h) {
1557 cursor = BottomRightCursor;
1558 }
1559 else {
1560 cursor = RightCursor;
1561 }
1562 }
1563 else if(y < h) { /* also include title bar in top border area */
1564 cursor = TopCursor;
1565 }
1566 else if(y >= fh - h) {
1567 cursor = BottomCursor;
1568 }
1569 else {
1570 cursor = Scr->FrameCursor;
1571 }
1572 attr.cursor = cursor;
1573 XChangeWindowAttributes(dpy, tmp_win->frame, CWCursor, &attr);
1574 tmp_win->curcurs = cursor;
1575 }
1576
1577
1578
1579 /*
1580 * End of code. Random doc/notes follow.
1581 */
1582
1583
1584 /*
1585 * n.b.: Old doc about squeezed title. Not recently vetted. I'm pretty
1586 * sure it's definitely wrong for the 3D-borders case at the least.
1587 * Should be updated and migrated into developer docs at some point.
1588 *
1589 * Squeezed Title:
1590 *
1591 * tmp->title_x
1592 * 0 |
1593 * tmp->title_y ........+--------------+......... -+,- tmp->frame_bw
1594 * 0 : ......| +----------+ |....... : -++
1595 * : : | | | | : : ||-Scr->TitleHeight
1596 * : : | | | | : : ||
1597 * +-------+ +----------+ +--------+ -+|-tmp->title_height
1598 * | +---------------------------+ | --+
1599 * | | | |
1600 * | | | |
1601 * | | | |
1602 * | | | |
1603 * | | | |
1604 * | +---------------------------+ |
1605 * +-------------------------------+
1606 *
1607 *
1608 * Unsqueezed Title:
1609 *
1610 * tmp->title_x
1611 * | 0
1612 * tmp->title_y +-------------------------------+ -+,tmp->frame_bw
1613 * 0 | +---------------------------+ | -+'
1614 * | | | | |-Scr->TitleHeight
1615 * | | | | |
1616 * + +---------------------------+ + -+
1617 * |-+---------------------------+-|
1618 * | | | |
1619 * | | | |
1620 * | | | |
1621 * | | | |
1622 * | | | |
1623 * | +---------------------------+ |
1624 * +-------------------------------+
1625 *
1626 *
1627 *
1628 * Dimensions and Positions:
1629 *
1630 * frame orgin (0, 0)
1631 * frame upper left border (-tmp->frame_bw, -tmp->frame_bw)
1632 * frame size w/o border tmp->frame_width , tmp->frame_height
1633 * frame/title border width tmp->frame_bw
1634 * extra title height w/o bdr tmp->title_height = TitleHeight + frame_bw
1635 * title window height Scr->TitleHeight
1636 * title origin w/o border (tmp->title_x, tmp->title_y)
1637 * client origin (0, Scr->TitleHeight + tmp->frame_bw)
1638 * client size tmp->attr.width , tmp->attr.height
1639 *
1640 * When shaping, need to remember that the width and height of rectangles
1641 * are really deltax and deltay to lower right handle corner, so they need
1642 * to have -1 subtracted from would normally be the actual extents.
1643 */
1644