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