Box.c revision 421c997b
1/*********************************************************** 2 3Copyright 1987, 1988, 1994, 1998 The Open Group 4 5Permission to use, copy, modify, distribute, and sell this software and its 6documentation for any purpose is hereby granted without fee, provided that 7the above copyright notice appear in all copies and that both that 8copyright notice and this permission notice appear in supporting 9documentation. 10 11The above copyright notice and this permission notice shall be included in 12all copies or substantial portions of the Software. 13 14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 18AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 21Except as contained in this notice, the name of The Open Group shall not be 22used in advertising or otherwise to promote the sale, use or other dealings 23in this Software without prior written authorization from The Open Group. 24 25 26Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts. 27 28 All Rights Reserved 29 30Permission to use, copy, modify, and distribute this software and its 31documentation for any purpose and without fee is hereby granted, 32provided that the above copyright notice appear in all copies and that 33both that copyright notice and this permission notice appear in 34supporting documentation, and that the name of Digital not be 35used in advertising or publicity pertaining to distribution of the 36software without specific, written prior permission. 37 38DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 39ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL 40DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR 41ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 42WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 43ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 44SOFTWARE. 45 46******************************************************************/ 47 48#ifdef HAVE_CONFIG_H 49#include <config.h> 50#endif 51#include <X11/IntrinsicP.h> 52#include <X11/StringDefs.h> 53#include <X11/Xmu/Misc.h> 54#include <X11/Xaw/BoxP.h> 55#include <X11/Xaw/XawInit.h> 56#include "Private.h" 57 58/* 59 * Class Methods 60 */ 61static void XawBoxChangeManaged(Widget); 62static void XawBoxClassInitialize(void); 63#ifndef OLDXAW 64static void XawBoxExpose(Widget, XEvent*, Region); 65#endif 66static XtGeometryResult XawBoxGeometryManager(Widget, XtWidgetGeometry*, 67 XtWidgetGeometry*); 68static void XawBoxInitialize(Widget, Widget, ArgList, Cardinal*); 69static XtGeometryResult XawBoxQueryGeometry(Widget, XtWidgetGeometry*, 70 XtWidgetGeometry*); 71static void XawBoxRealize(Widget, Mask*, XSetWindowAttributes*); 72static void XawBoxResize(Widget); 73static Boolean XawBoxSetValues(Widget, Widget, Widget, 74 ArgList, Cardinal*); 75 76/* 77 * Prototypes 78 */ 79static void DoLayout(BoxWidget, unsigned int, unsigned int, 80 Dimension*, Dimension*, Bool); 81static Bool TryNewLayout(BoxWidget); 82 83/* 84 * Initialization 85 */ 86#ifndef OLDXAW 87static XtActionsRec actions[] = { 88 {"set-values", XawSetValuesAction}, 89 {"get-values", XawGetValuesAction}, 90 {"declare", XawDeclareAction}, 91 {"call-proc", XawCallProcAction}, 92}; 93#endif 94 95static XtResource resources[] = { 96 { 97 XtNhSpace, 98 XtCHSpace, 99 XtRDimension, 100 sizeof(Dimension), 101 XtOffsetOf(BoxRec, box.h_space), 102 XtRImmediate, 103 (XtPointer)4 104 }, 105 { 106 XtNvSpace, 107 XtCVSpace, 108 XtRDimension, 109 sizeof(Dimension), 110 XtOffsetOf(BoxRec, box.v_space), 111 XtRImmediate, 112 (XtPointer)4 113 }, 114 { 115 XtNorientation, 116 XtCOrientation, 117 XtROrientation, 118 sizeof(XtOrientation), 119 XtOffsetOf(BoxRec, box.orientation), 120 XtRImmediate, 121 (XtPointer)XtorientVertical 122 }, 123#ifndef OLDXAW 124 { 125 XawNdisplayList, 126 XawCDisplayList, 127 XawRDisplayList, 128 sizeof(XawDisplayList*), 129 XtOffsetOf(BoxRec, box.display_list), 130 XtRImmediate, 131 NULL 132 }, 133#endif 134}; 135 136BoxClassRec boxClassRec = { 137 /* core */ 138 { 139 (WidgetClass)&compositeClassRec, /* superclass */ 140 "Box", /* class_name */ 141 sizeof(BoxRec), /* widget_size */ 142 XawBoxClassInitialize, /* class_initialize */ 143 NULL, /* class_part_init */ 144 False, /* class_inited */ 145 XawBoxInitialize, /* initialize */ 146 NULL, /* initialize_hook */ 147 XawBoxRealize, /* realize */ 148#ifndef OLDXAW 149 actions, /* actions */ 150 XtNumber(actions), /* num_actions */ 151#else 152 NULL, /* actions */ 153 0, /* num_actions */ 154#endif 155 resources, /* resources */ 156 XtNumber(resources), /* num_resources */ 157 NULLQUARK, /* xrm_class */ 158 True, /* compress_motion */ 159 True, /* compress_exposure */ 160 True, /* compress_enterleave */ 161 False, /* visible_interest */ 162 NULL, /* destroy */ 163 XawBoxResize, /* resize */ 164#ifndef OLDXAW 165 XawBoxExpose, /* expose */ 166#else 167 NULL, /* expose */ 168#endif 169 XawBoxSetValues, /* set_values */ 170 NULL, /* set_values_hook */ 171 XtInheritSetValuesAlmost, /* set_values_almost */ 172 NULL, /* get_values_hook */ 173 NULL, /* accept_focus */ 174 XtVersion, /* version */ 175 NULL, /* callback_private */ 176 NULL, /* tm_table */ 177 XawBoxQueryGeometry, /* query_geometry */ 178 XtInheritDisplayAccelerator, /* display_accelerator */ 179 NULL, /* extension */ 180 }, 181 /* composite */ 182 { 183 XawBoxGeometryManager, /* geometry_manager */ 184 XawBoxChangeManaged, /* change_managed */ 185 XtInheritInsertChild, /* insert_child */ 186 XtInheritDeleteChild, /* delete_child */ 187 NULL, /* extension */ 188 }, 189 /* box */ 190 { 191 NULL, /* extension */ 192 }, 193}; 194 195WidgetClass boxWidgetClass = (WidgetClass)&boxClassRec; 196 197/* 198 * Do a layout, either actually assigning positions, or just calculating size. 199 * Returns minimum width and height that will preserve the same layout. 200 */ 201static void 202DoLayout(BoxWidget bbw, unsigned int width, unsigned int height, 203 Dimension *reply_width, Dimension *reply_height, Bool position) 204{ 205 Boolean vbox = (bbw->box.orientation == XtorientVertical); 206 Cardinal i; 207 Dimension w, h; /* Width and height needed for box */ 208 Dimension lw, lh; /* Width and height needed for current line */ 209 Dimension bw, bh; /* Width and height needed for current widget */ 210 Dimension h_space; /* Local copy of bbw->box.h_space */ 211 Widget widget; /* Current widget */ 212 unsigned int num_mapped_children = 0; 213 214 /* Box width and height */ 215 h_space = bbw->box.h_space; 216 217 w = 0; 218 for (i = 0; i < bbw->composite.num_children; i++) { 219 if (XtIsManaged(bbw->composite.children[i]) 220 && bbw->composite.children[i]->core.width > w) 221 w = bbw->composite.children[i]->core.width; 222 } 223 w += h_space; 224 if (w > width) 225 width = w; 226 h = bbw->box.v_space; 227 228 /* Line width and height */ 229 lh = 0; 230 lw = h_space; 231 232 for (i = 0; i < bbw->composite.num_children; i++) { 233 widget = bbw->composite.children[i]; 234 if (widget->core.managed) { 235 if (widget->core.mapped_when_managed) 236 num_mapped_children++; 237 /* Compute widget width */ 238 bw = XtWidth(widget) + (XtBorderWidth(widget)<<1) + h_space; 239 if ((Dimension)(lw + bw) > width) { 240 if (lw > h_space) { 241 /* At least one widget on this line, and 242 * can't fit any more. Start new line if vbox 243 */ 244 AssignMax(w, lw); 245 if (vbox) { 246 h += lh + bbw->box.v_space; 247 lh = 0; 248 lw = h_space; 249 } 250 } 251 else if (!position) { 252 /* too narrow for this widget; we'll assume we can grow */ 253 DoLayout(bbw, (unsigned)(lw + bw), height, reply_width, 254 reply_height, position); 255 return; 256 } 257 } 258 if (position && (lw != XtX(widget) || h != XtY(widget))) { 259 /* It would be nice to use window gravity, but there isn't 260 * sufficient fine-grain control to nicely handle all 261 * situations (e.g. when only the height changes -- 262 * a common case). Explicit unmapping is a cheap hack 263 * to speed things up & avoid the visual jitter as 264 * things slide around. 265 * 266 * %%% perhaps there should be a client resource to 267 * control this. If so, we'll have to optimize to 268 * perform the moves from the correct end so we don't 269 * force extra exposures as children occlude each other. 270 */ 271 if (XtIsRealized(widget) && widget->core.mapped_when_managed) 272 XUnmapWindow( XtDisplay(widget), XtWindow(widget)); 273 XtMoveWidget(widget, (int)lw, (int)h); 274 } 275 lw += bw; 276 bh = XtHeight(widget) + (XtBorderWidth(widget) << 1); 277 AssignMax(lh, bh); 278 } 279 } 280 281 if (!vbox && width && lw > width && lh < height) { 282 /* reduce width if too wide and height not filled */ 283 Dimension sw = lw, sh = lh; 284 Dimension width_needed = width; 285 XtOrientation orientation = bbw->box.orientation; 286 287 bbw->box.orientation = XtorientVertical; 288 while (sh < height && sw > width) { 289 width_needed = sw; 290 DoLayout(bbw, (unsigned)(sw-1), height, &sw, &sh, False); 291 } 292 if (sh < height) 293 width_needed = sw; 294 if (width_needed != lw) { 295 DoLayout(bbw, width_needed, height, 296 reply_width, reply_height, position); 297 bbw->box.orientation = orientation; 298 return; 299 } 300 bbw->box.orientation = orientation; 301 } 302 if (vbox && (width < w || width < lw)) { 303 AssignMax(w, lw); 304 DoLayout(bbw, w, height, reply_width, reply_height, position); 305 return; 306 } 307 if (position && XtIsRealized((Widget)bbw)) { 308 if (bbw->composite.num_children == num_mapped_children) 309 XMapSubwindows(XtDisplay((Widget)bbw), XtWindow((Widget)bbw)); 310 else { 311 int ii = bbw->composite.num_children; 312 Widget *childP = bbw->composite.children; 313 314 for (; ii > 0; childP++, ii--) 315 if (XtIsRealized(*childP) && XtIsManaged(*childP) 316 && (*childP)->core.mapped_when_managed) 317 XtMapWidget(*childP); 318 } 319 } 320 321 /* Finish off last line */ 322 if (lw > h_space) { 323 AssignMax(w, lw); 324 h += lh + bbw->box.v_space; 325 } 326 327 *reply_width = Max(w, 1); 328 *reply_height = Max(h, 1); 329} 330 331/* 332 * Calculate preferred size, given constraining box, caching it in the widget 333 */ 334static XtGeometryResult 335XawBoxQueryGeometry(Widget widget, XtWidgetGeometry *constraint, 336 XtWidgetGeometry *preferred) 337{ 338 BoxWidget w = (BoxWidget)widget; 339 Dimension width; 340 Dimension preferred_width = w->box.preferred_width; 341 Dimension preferred_height = w->box.preferred_height; 342 343 constraint->request_mode &= CWWidth | CWHeight; 344 345 if (constraint->request_mode == 0) 346 /* parent isn't going to change w or h, so nothing to re-compute */ 347 return (XtGeometryYes); 348 349 if (constraint->request_mode == w->box.last_query_mode 350 && (!(constraint->request_mode & CWWidth) 351 || constraint->width == w->box.last_query_width) 352 && (!(constraint->request_mode & CWHeight) 353 || constraint->height == w->box.last_query_height)) { 354 /* same query; current preferences are still valid */ 355 preferred->request_mode = CWWidth | CWHeight; 356 preferred->width = preferred_width; 357 preferred->height = preferred_height; 358 if (constraint->request_mode == (CWWidth | CWHeight) 359 && constraint->width == preferred_width 360 && constraint->height == preferred_height) 361 return (XtGeometryYes); 362 else 363 return (XtGeometryAlmost); 364 } 365 366 /* else gotta do it the long way... 367 I have a preference for tall and narrow, so if my width is 368 constrained, I'll accept it; otherwise, I'll compute the minimum 369 width that will fit me within the height constraint */ 370 371 w->box.last_query_mode = constraint->request_mode; 372 w->box.last_query_width = constraint->width; 373 w->box.last_query_height= constraint->height; 374 375 if (constraint->request_mode & CWWidth) 376 width = constraint->width; 377 else { /* if (constraint->request_mode & CWHeight) */ 378 /* let's see if I can become any narrower */ 379 width = 0; 380 constraint->width = 65535; 381 } 382 383 /* height is currently ignored by DoLayout. 384 height = (constraint->request_mode & CWHeight) ? constraint->height 385 : *preferred_height; 386 */ 387 DoLayout(w, width, 0, &preferred_width, &preferred_height, False); 388 389 if (constraint->request_mode & CWHeight 390 && preferred_height > constraint->height) { 391 /* find minimum width for this height */ 392 if (preferred_width <= constraint->width) { 393 width = preferred_width; 394 do { /* find some width big enough to stay within this height */ 395 if (width > (constraint->width >> 1)) /* avoid short int overflow */ 396 width = constraint->width; 397 else 398 width <<= 1; 399 DoLayout(w, width, 0, &preferred_width, &preferred_height, False); 400 } while (preferred_height > constraint->height 401 && width < constraint->width); 402 if (width != constraint->width) { 403 do { /* find minimum width */ 404 width = preferred_width; 405 DoLayout(w, (unsigned)(preferred_width - 1), 0, 406 &preferred_width, &preferred_height, False); 407 } while (preferred_height < constraint->height); 408 /* one last time */ 409 DoLayout(w, width, 0, &preferred_width, &preferred_height, False); 410 } 411 } 412 } 413 414 preferred->request_mode = CWWidth | CWHeight; 415 preferred->width = w->box.preferred_width = preferred_width; 416 preferred->height = w->box.preferred_height = preferred_height; 417 418 if (constraint->request_mode == (CWWidth|CWHeight) 419 && constraint->width == preferred_width 420 && constraint->height == preferred_height) 421 return (XtGeometryYes); 422 423 return (XtGeometryAlmost); 424} 425 426/* 427 * Actually layout the box 428 */ 429static void 430XawBoxResize(Widget w) 431{ 432 Dimension tmp; 433 434 DoLayout((BoxWidget)w, XtWidth(w), XtHeight(w), &tmp, &tmp, True); 435} 436 437/* 438 * Try to do a new layout within the current width and height; 439 * if that fails try to resize and do it within the box returne 440 * by XawBoxQueryGeometry 441 * 442 * TryNewLayout just says if it's possible, and doesn't actually move the kids 443 */ 444static Bool 445TryNewLayout(BoxWidget bbw) 446{ 447 Dimension preferred_width, preferred_height; 448 Dimension proposed_width, proposed_height; 449 int iterations; 450 451 DoLayout(bbw, bbw->core.width, bbw->core.height, 452 &preferred_width, &preferred_height, False); 453 454 /* at this point, preferred_width is guaranteed to not be greater 455 than bbw->core.width unless some child is larger, so there's no 456 point in re-computing another layout */ 457 458 if (XtWidth(bbw) == preferred_width && XtHeight(bbw) == preferred_height) 459 return (True); 460 461 /* let's see if our parent will go for a new size */ 462 iterations = 0; 463 proposed_width = preferred_width; 464 proposed_height = preferred_height; 465 do { 466 switch (XtMakeResizeRequest((Widget)bbw,proposed_width,proposed_height, 467 &proposed_width, &proposed_height)) { 468 case XtGeometryYes: 469 return (True); 470 case XtGeometryNo: 471 if (iterations > 0) 472 /* protect from malicious parents who change their minds */ 473 DoLayout(bbw, bbw->core.width, bbw->core.height, 474 &preferred_width, &preferred_height, False); 475 if (preferred_width <= XtWidth(bbw) 476 && preferred_height <= XtHeight(bbw)) 477 return (True); 478 else 479 return (False); 480 case XtGeometryAlmost: 481 if (proposed_height >= preferred_height && 482 proposed_width >= preferred_width) { 483 /* 484 * Take it, and assume the parent knows what it is doing. 485 * 486 * The parent must accept this since it was returned in 487 * almost. 488 */ 489 (void)XtMakeResizeRequest((Widget)bbw, 490 proposed_width, proposed_height, 491 &proposed_width, &proposed_height); 492 return (True); 493 } 494 else if (proposed_width != preferred_width) { 495 /* recalc bounding box; height might change */ 496 DoLayout(bbw, proposed_width, 0, 497 &preferred_width, &preferred_height, False); 498 proposed_height = preferred_height; 499 } 500 else { /* proposed_height != preferred_height */ 501 XtWidgetGeometry constraints, reply; 502 503 constraints.request_mode = CWHeight; 504 constraints.height = proposed_height; 505 (void)XawBoxQueryGeometry((Widget)bbw, &constraints, &reply); 506 proposed_width = preferred_width; 507 } 508 /*FALLTHROUGH*/ 509 default: 510 break; 511 } 512 iterations++; 513 } while (iterations < 10); 514 515 return (False); 516} 517 518/* 519 * Geometry Manager 520 * 521 * 'reply' is unused; we say only yeay or nay, never almost. 522 */ 523/*ARGSUSED*/ 524static XtGeometryResult 525XawBoxGeometryManager(Widget w, XtWidgetGeometry *request, 526 XtWidgetGeometry *reply) 527{ 528 Dimension width, height, borderWidth; 529 BoxWidget bbw; 530 531 /* Position request always denied */ 532 if (((request->request_mode & CWX) && request->x != XtX(w)) 533 || ((request->request_mode & CWY) && request->y != XtY(w))) 534 return (XtGeometryNo); 535 536 /* Size changes must see if the new size can be accomodated */ 537 if (request->request_mode & (CWWidth | CWHeight | CWBorderWidth)) { 538 /* Make all three fields in the request valid */ 539 if ((request->request_mode & CWWidth) == 0) 540 request->width = XtWidth(w); 541 if ((request->request_mode & CWHeight) == 0) 542 request->height = XtHeight(w); 543 if ((request->request_mode & CWBorderWidth) == 0) 544 request->border_width = XtBorderWidth(w); 545 546 /* Save current size and set to new size */ 547 width = XtWidth(w); 548 height = XtHeight(w); 549 borderWidth = XtBorderWidth(w); 550 XtWidth(w) = request->width; 551 XtHeight(w) = request->height; 552 XtBorderWidth(w) = request->border_width; 553 554 /* Decide if new layout works: 555 (1) new widget is smaller, 556 (2) new widget fits in existing Box, 557 (3) Box can be expanded to allow new widget to fit 558 */ 559 560 bbw = (BoxWidget) w->core.parent; 561 562 if (TryNewLayout(bbw)) { 563 /* Fits in existing or new space, relayout */ 564 (*XtClass((Widget)bbw)->core_class.resize)((Widget)bbw); 565 return (XtGeometryYes); 566 } 567 else { 568 /* Cannot satisfy request, change back to original geometry */ 569 XtWidth(w) = width; 570 XtHeight(w) = height; 571 XtBorderWidth(w) = borderWidth; 572 return (XtGeometryNo); 573 } 574 } 575 576 /* Any stacking changes don't make a difference, so allow if that's all */ 577 return (XtGeometryYes); 578} 579 580static void 581XawBoxChangeManaged(Widget w) 582{ 583 /* Reconfigure the box */ 584 (void)TryNewLayout((BoxWidget)w); 585 XawBoxResize(w); 586} 587 588static void 589XawBoxClassInitialize(void) 590{ 591 XawInitializeWidgetSet(); 592 XtAddConverter(XtRString, XtROrientation, XmuCvtStringToOrientation, 593 NULL, 0); 594 XtSetTypeConverter(XtROrientation, XtRString, XmuCvtOrientationToString, 595 NULL, 0, XtCacheNone, NULL); 596} 597 598/*ARGSUSED*/ 599static void 600XawBoxInitialize(Widget request, Widget cnew, 601 ArgList args, Cardinal *num_args) 602{ 603 BoxWidget newbbw = (BoxWidget)cnew; 604 605 newbbw->box.last_query_mode = CWWidth | CWHeight; 606 newbbw->box.last_query_width = newbbw->box.last_query_height = 0; 607 newbbw->box.preferred_width = Max(newbbw->box.h_space, 1); 608 newbbw->box.preferred_height = Max(newbbw->box.v_space, 1); 609 610 if (XtWidth(newbbw) == 0) 611 XtWidth(newbbw) = newbbw->box.preferred_width; 612 613 if (XtHeight(newbbw) == 0) 614 XtHeight(newbbw) = newbbw->box.preferred_height; 615} 616 617static void 618XawBoxRealize(Widget w, Mask *valueMask, XSetWindowAttributes *attributes) 619{ 620#ifndef OLDXAW 621 XawPixmap *pixmap; 622#endif 623 624 XtCreateWindow(w, InputOutput, (Visual *)CopyFromParent, 625 *valueMask, attributes); 626 627#ifndef OLDXAW 628 if (w->core.background_pixmap > XtUnspecifiedPixmap) { 629 pixmap = XawPixmapFromXPixmap(w->core.background_pixmap, XtScreen(w), 630 w->core.colormap, w->core.depth); 631 if (pixmap && pixmap->mask) 632 XawReshapeWidget(w, pixmap); 633 } 634#endif 635} 636 637/*ARGSUSED*/ 638static Boolean 639XawBoxSetValues(Widget current, Widget request, Widget cnew, 640 ArgList args, Cardinal *num_args) 641{ 642 /* need to relayout if h_space or v_space change */ 643#ifndef OLDXAW 644 BoxWidget b_old = (BoxWidget)current; 645 BoxWidget b_new = (BoxWidget)cnew; 646 647 if (b_old->core.background_pixmap != b_new->core.background_pixmap) { 648 XawPixmap *opix, *npix; 649 650 opix = XawPixmapFromXPixmap(b_old->core.background_pixmap, 651 XtScreen(b_old), b_old->core.colormap, 652 b_old->core.depth); 653 npix = XawPixmapFromXPixmap(b_new->core.background_pixmap, 654 XtScreen(b_new), b_new->core.colormap, 655 b_new->core.depth); 656 if ((npix && npix->mask) || (opix && opix->mask)) 657 XawReshapeWidget(cnew, npix); 658 } 659#endif /* OLDXAW */ 660 661 return (False); 662} 663 664#ifndef OLDXAW 665static void 666XawBoxExpose(Widget w, XEvent *event, Region region) 667{ 668 BoxWidget xaw = (BoxWidget)w; 669 670 if (xaw->box.display_list) 671 XawRunDisplayList(w, xaw->box.display_list, event, region); 672} 673#endif /* OLDXAW */ 674