win_decorations.c revision 0bbfda8a
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 */
27static void ComputeWindowTitleOffsets(TwmWindow *tmp_win, unsigned int width,
28                                      bool squeeze);
29static void CreateHighlightWindows(TwmWindow *tmp_win);
30static void CreateLowlightWindows(TwmWindow *tmp_win);
31
32typedef enum { TopLeft, TopRight, BottomRight, BottomLeft } CornerType;
33static 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 */
68void
69SetupWindow(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
74void
75SetupFrame(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 */
484void
485SetFrameShape(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 */
599void
600ComputeTitleLocation(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 */
656void
657CreateWindowTitlebarButtons(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 */
823static void
824ComputeWindowTitleOffsets(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 */
954static void
955CreateHighlightWindows(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 */
1062void
1063DeleteHighlightWindows(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
1082static void
1083CreateLowlightWindows(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 */
1176void
1177PaintTitle(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 */
1269void
1270PaintTitleButtons(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 */
1284void
1285PaintTitleButton(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 */
1308static void
1309Draw3DCorner(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 */
1372void
1373PaintBorders(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 */
1500void
1501SetBorderCursor(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