Box.c revision 994689c1
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 width <<= 1; 396 if (width > constraint->width) 397 width = constraint->width; 398 DoLayout(w, width, 0, &preferred_width, &preferred_height, False); 399 } while (preferred_height > constraint->height 400 && width < constraint->width); 401 if (width != constraint->width) { 402 do { /* find minimum width */ 403 width = preferred_width; 404 DoLayout(w, (unsigned)(preferred_width - 1), 0, 405 &preferred_width, &preferred_height, False); 406 } while (preferred_height < constraint->height); 407 /* one last time */ 408 DoLayout(w, width, 0, &preferred_width, &preferred_height, False); 409 } 410 } 411 } 412 413 preferred->request_mode = CWWidth | CWHeight; 414 preferred->width = w->box.preferred_width = preferred_width; 415 preferred->height = w->box.preferred_height = preferred_height; 416 417 if (constraint->request_mode == (CWWidth|CWHeight) 418 && constraint->width == preferred_width 419 && constraint->height == preferred_height) 420 return (XtGeometryYes); 421 422 return (XtGeometryAlmost); 423} 424 425/* 426 * Actually layout the box 427 */ 428static void 429XawBoxResize(Widget w) 430{ 431 Dimension tmp; 432 433 DoLayout((BoxWidget)w, XtWidth(w), XtHeight(w), &tmp, &tmp, True); 434} 435 436/* 437 * Try to do a new layout within the current width and height; 438 * if that fails try to resize and do it within the box returne 439 * by XawBoxQueryGeometry 440 * 441 * TryNewLayout just says if it's possible, and doesn't actually move the kids 442 */ 443static Bool 444TryNewLayout(BoxWidget bbw) 445{ 446 Dimension preferred_width, preferred_height; 447 Dimension proposed_width, proposed_height; 448 int iterations; 449 450 DoLayout(bbw, bbw->core.width, bbw->core.height, 451 &preferred_width, &preferred_height, False); 452 453 /* at this point, preferred_width is guaranteed to not be greater 454 than bbw->core.width unless some child is larger, so there's no 455 point in re-computing another layout */ 456 457 if (XtWidth(bbw) == preferred_width && XtHeight(bbw) == preferred_height) 458 return (True); 459 460 /* let's see if our parent will go for a new size */ 461 iterations = 0; 462 proposed_width = preferred_width; 463 proposed_height = preferred_height; 464 do { 465 switch (XtMakeResizeRequest((Widget)bbw,proposed_width,proposed_height, 466 &proposed_width, &proposed_height)) { 467 case XtGeometryYes: 468 return (True); 469 case XtGeometryNo: 470 if (iterations > 0) 471 /* protect from malicious parents who change their minds */ 472 DoLayout(bbw, bbw->core.width, bbw->core.height, 473 &preferred_width, &preferred_height, False); 474 if (preferred_width <= XtWidth(bbw) 475 && preferred_height <= XtHeight(bbw)) 476 return (True); 477 else 478 return (False); 479 case XtGeometryAlmost: 480 if (proposed_height >= preferred_height && 481 proposed_width >= preferred_width) { 482 /* 483 * Take it, and assume the parent knows what it is doing. 484 * 485 * The parent must accept this since it was returned in 486 * almost. 487 */ 488 (void)XtMakeResizeRequest((Widget)bbw, 489 proposed_width, proposed_height, 490 &proposed_width, &proposed_height); 491 return (True); 492 } 493 else if (proposed_width != preferred_width) { 494 /* recalc bounding box; height might change */ 495 DoLayout(bbw, proposed_width, 0, 496 &preferred_width, &preferred_height, False); 497 proposed_height = preferred_height; 498 } 499 else { /* proposed_height != preferred_height */ 500 XtWidgetGeometry constraints, reply; 501 502 constraints.request_mode = CWHeight; 503 constraints.height = proposed_height; 504 (void)XawBoxQueryGeometry((Widget)bbw, &constraints, &reply); 505 proposed_width = preferred_width; 506 } 507 /*FALLTHROUGH*/ 508 default: 509 break; 510 } 511 iterations++; 512 } while (iterations < 10); 513 514 return (False); 515} 516 517/* 518 * Geometry Manager 519 * 520 * 'reply' is unused; we say only yeay or nay, never almost. 521 */ 522/*ARGSUSED*/ 523static XtGeometryResult 524XawBoxGeometryManager(Widget w, XtWidgetGeometry *request, 525 XtWidgetGeometry *reply) 526{ 527 Dimension width, height, borderWidth; 528 BoxWidget bbw; 529 530 /* Position request always denied */ 531 if (((request->request_mode & CWX) && request->x != XtX(w)) 532 || ((request->request_mode & CWY) && request->y != XtY(w))) 533 return (XtGeometryNo); 534 535 /* Size changes must see if the new size can be accomodated */ 536 if (request->request_mode & (CWWidth | CWHeight | CWBorderWidth)) { 537 /* Make all three fields in the request valid */ 538 if ((request->request_mode & CWWidth) == 0) 539 request->width = XtWidth(w); 540 if ((request->request_mode & CWHeight) == 0) 541 request->height = XtHeight(w); 542 if ((request->request_mode & CWBorderWidth) == 0) 543 request->border_width = XtBorderWidth(w); 544 545 /* Save current size and set to new size */ 546 width = XtWidth(w); 547 height = XtHeight(w); 548 borderWidth = XtBorderWidth(w); 549 XtWidth(w) = request->width; 550 XtHeight(w) = request->height; 551 XtBorderWidth(w) = request->border_width; 552 553 /* Decide if new layout works: 554 (1) new widget is smaller, 555 (2) new widget fits in existing Box, 556 (3) Box can be expanded to allow new widget to fit 557 */ 558 559 bbw = (BoxWidget) w->core.parent; 560 561 if (TryNewLayout(bbw)) { 562 /* Fits in existing or new space, relayout */ 563 (*XtClass((Widget)bbw)->core_class.resize)((Widget)bbw); 564 return (XtGeometryYes); 565 } 566 else { 567 /* Cannot satisfy request, change back to original geometry */ 568 XtWidth(w) = width; 569 XtHeight(w) = height; 570 XtBorderWidth(w) = borderWidth; 571 return (XtGeometryNo); 572 } 573 } 574 575 /* Any stacking changes don't make a difference, so allow if that's all */ 576 return (XtGeometryYes); 577} 578 579static void 580XawBoxChangeManaged(Widget w) 581{ 582 /* Reconfigure the box */ 583 (void)TryNewLayout((BoxWidget)w); 584 XawBoxResize(w); 585} 586 587static void 588XawBoxClassInitialize(void) 589{ 590 XawInitializeWidgetSet(); 591 XtAddConverter(XtRString, XtROrientation, XmuCvtStringToOrientation, 592 NULL, 0); 593 XtSetTypeConverter(XtROrientation, XtRString, XmuCvtOrientationToString, 594 NULL, 0, XtCacheNone, NULL); 595} 596 597/*ARGSUSED*/ 598static void 599XawBoxInitialize(Widget request, Widget cnew, 600 ArgList args, Cardinal *num_args) 601{ 602 BoxWidget newbbw = (BoxWidget)cnew; 603 604 newbbw->box.last_query_mode = CWWidth | CWHeight; 605 newbbw->box.last_query_width = newbbw->box.last_query_height = 0; 606 newbbw->box.preferred_width = Max(newbbw->box.h_space, 1); 607 newbbw->box.preferred_height = Max(newbbw->box.v_space, 1); 608 609 if (XtWidth(newbbw) == 0) 610 XtWidth(newbbw) = newbbw->box.preferred_width; 611 612 if (XtHeight(newbbw) == 0) 613 XtHeight(newbbw) = newbbw->box.preferred_height; 614} 615 616static void 617XawBoxRealize(Widget w, Mask *valueMask, XSetWindowAttributes *attributes) 618{ 619#ifndef OLDXAW 620 XawPixmap *pixmap; 621#endif 622 623 XtCreateWindow(w, InputOutput, (Visual *)CopyFromParent, 624 *valueMask, attributes); 625 626#ifndef OLDXAW 627 if (w->core.background_pixmap > XtUnspecifiedPixmap) { 628 pixmap = XawPixmapFromXPixmap(w->core.background_pixmap, XtScreen(w), 629 w->core.colormap, w->core.depth); 630 if (pixmap && pixmap->mask) 631 XawReshapeWidget(w, pixmap); 632 } 633#endif 634} 635 636/*ARGSUSED*/ 637static Boolean 638XawBoxSetValues(Widget current, Widget request, Widget cnew, 639 ArgList args, Cardinal *num_args) 640{ 641 /* need to relayout if h_space or v_space change */ 642#ifndef OLDXAW 643 BoxWidget b_old = (BoxWidget)current; 644 BoxWidget b_new = (BoxWidget)cnew; 645 646 if (b_old->core.background_pixmap != b_new->core.background_pixmap) { 647 XawPixmap *opix, *npix; 648 649 opix = XawPixmapFromXPixmap(b_old->core.background_pixmap, 650 XtScreen(b_old), b_old->core.colormap, 651 b_old->core.depth); 652 npix = XawPixmapFromXPixmap(b_new->core.background_pixmap, 653 XtScreen(b_new), b_new->core.colormap, 654 b_new->core.depth); 655 if ((npix && npix->mask) || (opix && opix->mask)) 656 XawReshapeWidget(cnew, npix); 657 } 658#endif /* OLDXAW */ 659 660 return (False); 661} 662 663#ifndef OLDXAW 664static void 665XawBoxExpose(Widget w, XEvent *event, Region region) 666{ 667 BoxWidget xaw = (BoxWidget)w; 668 669 if (xaw->box.display_list) 670 XawRunDisplayList(w, xaw->box.display_list, event, region); 671} 672#endif /* OLDXAW */ 673