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