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