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 unsigned int num_mapped_children = 0; 212 213 /* Box width and height */ 214 h_space = bbw->box.h_space; 215 216 w = 0; 217 for (i = 0; i < bbw->composite.num_children; i++) { 218 if (XtIsManaged(bbw->composite.children[i]) 219 && bbw->composite.children[i]->core.width > w) 220 w = bbw->composite.children[i]->core.width; 221 } 222 w = (Dimension)(w + h_space); 223 if (w > width) 224 width = w; 225 h = bbw->box.v_space; 226 227 /* Line width and height */ 228 lh = 0; 229 lw = h_space; 230 231 for (i = 0; i < bbw->composite.num_children; i++) { 232 Widget widget = bbw->composite.children[i]; /* Current widget */ 233 if (widget->core.managed) { 234 if (widget->core.mapped_when_managed) 235 num_mapped_children++; 236 /* Compute widget width */ 237 bw = (Dimension)(XtWidth(widget) + (XtBorderWidth(widget)<<1) + h_space); 238 if ((Dimension)(lw + bw) > width) { 239 if (lw > h_space) { 240 /* At least one widget on this line, and 241 * can't fit any more. Start new line if vbox 242 */ 243 AssignMax(w, lw); 244 if (vbox) { 245 h = (Dimension)(h + (lh + bbw->box.v_space)); 246 lh = 0; 247 lw = h_space; 248 } 249 } 250 else if (!position) { 251 /* too narrow for this widget; we'll assume we can grow */ 252 DoLayout(bbw, (unsigned)(lw + bw), height, reply_width, 253 reply_height, position); 254 return; 255 } 256 } 257 if (position && (lw != XtX(widget) || h != XtY(widget))) { 258 /* It would be nice to use window gravity, but there isn't 259 * sufficient fine-grain control to nicely handle all 260 * situations (e.g. when only the height changes -- 261 * a common case). Explicit unmapping is a cheap hack 262 * to speed things up & avoid the visual jitter as 263 * things slide around. 264 * 265 * %%% perhaps there should be a client resource to 266 * control this. If so, we'll have to optimize to 267 * perform the moves from the correct end so we don't 268 * force extra exposures as children occlude each other. 269 */ 270 if (XtIsRealized(widget) && widget->core.mapped_when_managed) 271 XUnmapWindow( XtDisplay(widget), XtWindow(widget)); 272 XtMoveWidget(widget, (Position)lw, (Position)h); 273 } 274 lw = (Dimension)(lw + bw); 275 bh = (Dimension)(XtHeight(widget) + (XtBorderWidth(widget) << 1)); 276 AssignMax(lh, bh); 277 } 278 } 279 280 if (!vbox && width && lw > width && lh < height) { 281 /* reduce width if too wide and height not filled */ 282 Dimension sw = lw, sh = lh; 283 Dimension width_needed = (Dimension)width; 284 XtOrientation orientation = bbw->box.orientation; 285 286 bbw->box.orientation = XtorientVertical; 287 while (sh < height && sw > width) { 288 width_needed = sw; 289 DoLayout(bbw, (unsigned)(sw-1), height, &sw, &sh, False); 290 } 291 if (sh < height) 292 width_needed = sw; 293 if (width_needed != lw) { 294 DoLayout(bbw, width_needed, height, 295 reply_width, reply_height, position); 296 bbw->box.orientation = orientation; 297 return; 298 } 299 bbw->box.orientation = orientation; 300 } 301 if (vbox && (width < w || width < lw)) { 302 AssignMax(w, lw); 303 DoLayout(bbw, w, height, reply_width, reply_height, position); 304 return; 305 } 306 if (position && XtIsRealized((Widget)bbw)) { 307 if (bbw->composite.num_children == num_mapped_children) 308 XMapSubwindows(XtDisplay((Widget)bbw), XtWindow((Widget)bbw)); 309 else { 310 int ii = (int)bbw->composite.num_children; 311 Widget *childP = bbw->composite.children; 312 313 for (; ii > 0; childP++, ii--) 314 if (XtIsRealized(*childP) && XtIsManaged(*childP) 315 && (*childP)->core.mapped_when_managed) 316 XtMapWidget(*childP); 317 } 318 } 319 320 /* Finish off last line */ 321 if (lw > h_space) { 322 AssignMax(w, lw); 323 h = (Dimension)(h + (lh + bbw->box.v_space)); 324 } 325 326 *reply_width = Max(w, 1); 327 *reply_height = Max(h, 1); 328} 329 330/* 331 * Calculate preferred size, given constraining box, caching it in the widget 332 */ 333static XtGeometryResult 334XawBoxQueryGeometry(Widget widget, XtWidgetGeometry *constraint, 335 XtWidgetGeometry *preferred) 336{ 337 BoxWidget w = (BoxWidget)widget; 338 Dimension width; 339 Dimension preferred_width = w->box.preferred_width; 340 Dimension preferred_height = w->box.preferred_height; 341 342 constraint->request_mode &= CWWidth | CWHeight; 343 344 if (constraint->request_mode == 0) 345 /* parent isn't going to change w or h, so nothing to re-compute */ 346 return (XtGeometryYes); 347 348 if (constraint->request_mode == w->box.last_query_mode 349 && (!(constraint->request_mode & CWWidth) 350 || constraint->width == w->box.last_query_width) 351 && (!(constraint->request_mode & CWHeight) 352 || constraint->height == w->box.last_query_height)) { 353 /* same query; current preferences are still valid */ 354 preferred->request_mode = CWWidth | CWHeight; 355 preferred->width = preferred_width; 356 preferred->height = preferred_height; 357 if (constraint->request_mode == (CWWidth | CWHeight) 358 && constraint->width == preferred_width 359 && constraint->height == preferred_height) 360 return (XtGeometryYes); 361 else 362 return (XtGeometryAlmost); 363 } 364 365 /* else gotta do it the long way... 366 I have a preference for tall and narrow, so if my width is 367 constrained, I'll accept it; otherwise, I'll compute the minimum 368 width that will fit me within the height constraint */ 369 370 w->box.last_query_mode = constraint->request_mode; 371 w->box.last_query_width = constraint->width; 372 w->box.last_query_height= constraint->height; 373 374 if (constraint->request_mode & CWWidth) 375 width = constraint->width; 376 else { /* if (constraint->request_mode & CWHeight) */ 377 /* let's see if I can become any narrower */ 378 width = 0; 379 constraint->width = 65535; 380 } 381 382 /* height is currently ignored by DoLayout. 383 height = (constraint->request_mode & CWHeight) ? constraint->height 384 : *preferred_height; 385 */ 386 DoLayout(w, width, 0, &preferred_width, &preferred_height, False); 387 388 if (constraint->request_mode & CWHeight 389 && preferred_height > constraint->height) { 390 /* find minimum width for this height */ 391 if (preferred_width <= constraint->width) { 392 width = preferred_width; 393 do { /* find some width big enough to stay within this height */ 394 if (width > (constraint->width >> 1)) /* avoid short int overflow */ 395 width = constraint->width; 396 else 397 width = (Dimension)(width << 1); 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 returned 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 = { 501 .request_mode = CWHeight, 502 .height = proposed_height 503 }; 504 XtWidgetGeometry reply; 505 506 (void)XawBoxQueryGeometry((Widget)bbw, &constraints, &reply); 507 proposed_width = preferred_width; 508 } 509 /*FALLTHROUGH*/ 510 default: 511 break; 512 } 513 iterations++; 514 } while (iterations < 10); 515 516 return (False); 517} 518 519/* 520 * Geometry Manager 521 * 522 * 'reply' is unused; we say only yeay or nay, never almost. 523 */ 524/*ARGSUSED*/ 525static XtGeometryResult 526XawBoxGeometryManager(Widget w, XtWidgetGeometry *request, 527 XtWidgetGeometry *reply _X_UNUSED) 528{ 529 /* Position request always denied */ 530 if (((request->request_mode & CWX) && request->x != XtX(w)) 531 || ((request->request_mode & CWY) && request->y != XtY(w))) 532 return (XtGeometryNo); 533 534 /* Size changes must see if the new size can be accommodated */ 535 if (request->request_mode & (CWWidth | CWHeight | CWBorderWidth)) { 536 Dimension width, height, borderWidth; 537 BoxWidget bbw; 538 539 /* Make all three fields in the request valid */ 540 if ((request->request_mode & CWWidth) == 0) 541 request->width = XtWidth(w); 542 if ((request->request_mode & CWHeight) == 0) 543 request->height = XtHeight(w); 544 if ((request->request_mode & CWBorderWidth) == 0) 545 request->border_width = XtBorderWidth(w); 546 547 /* Save current size and set to new size */ 548 width = XtWidth(w); 549 height = XtHeight(w); 550 borderWidth = XtBorderWidth(w); 551 XtWidth(w) = request->width; 552 XtHeight(w) = request->height; 553 XtBorderWidth(w) = request->border_width; 554 555 /* Decide if new layout works: 556 (1) new widget is smaller, 557 (2) new widget fits in existing Box, 558 (3) Box can be expanded to allow new widget to fit 559 */ 560 561 bbw = (BoxWidget) w->core.parent; 562 563 if (TryNewLayout(bbw)) { 564 /* Fits in existing or new space, relayout */ 565 (*XtClass((Widget)bbw)->core_class.resize)((Widget)bbw); 566 return (XtGeometryYes); 567 } 568 else { 569 /* Cannot satisfy request, change back to original geometry */ 570 XtWidth(w) = width; 571 XtHeight(w) = height; 572 XtBorderWidth(w) = borderWidth; 573 return (XtGeometryNo); 574 } 575 } 576 577 /* Any stacking changes don't make a difference, so allow if that's all */ 578 return (XtGeometryYes); 579} 580 581static void 582XawBoxChangeManaged(Widget w) 583{ 584 /* Reconfigure the box */ 585 (void)TryNewLayout((BoxWidget)w); 586 XawBoxResize(w); 587} 588 589static void 590XawBoxClassInitialize(void) 591{ 592 XawInitializeWidgetSet(); 593 XtAddConverter(XtRString, XtROrientation, XmuCvtStringToOrientation, 594 NULL, 0); 595 XtSetTypeConverter(XtROrientation, XtRString, XmuCvtOrientationToString, 596 NULL, 0, XtCacheNone, NULL); 597} 598 599/*ARGSUSED*/ 600static void 601XawBoxInitialize(Widget request _X_UNUSED, Widget cnew, 602 ArgList args _X_UNUSED, Cardinal *num_args _X_UNUSED) 603{ 604 BoxWidget newbbw = (BoxWidget)cnew; 605 606 newbbw->box.last_query_mode = CWWidth | CWHeight; 607 newbbw->box.last_query_width = newbbw->box.last_query_height = 0; 608 newbbw->box.preferred_width = Max(newbbw->box.h_space, 1); 609 newbbw->box.preferred_height = Max(newbbw->box.v_space, 1); 610 611 if (XtWidth(newbbw) == 0) 612 XtWidth(newbbw) = newbbw->box.preferred_width; 613 614 if (XtHeight(newbbw) == 0) 615 XtHeight(newbbw) = newbbw->box.preferred_height; 616} 617 618static void 619XawBoxRealize(Widget w, Mask *valueMask, XSetWindowAttributes *attributes) 620{ 621#ifndef OLDXAW 622 XawPixmap *pixmap; 623#endif 624 625 XtCreateWindow(w, InputOutput, (Visual *)CopyFromParent, 626 *valueMask, attributes); 627 628#ifndef OLDXAW 629 if (w->core.background_pixmap > XtUnspecifiedPixmap) { 630 pixmap = XawPixmapFromXPixmap(w->core.background_pixmap, XtScreen(w), 631 w->core.colormap, (int)w->core.depth); 632 if (pixmap && pixmap->mask) 633 XawReshapeWidget(w, pixmap); 634 } 635#endif 636} 637 638/*ARGSUSED*/ 639static Boolean 640XawBoxSetValues(Widget current _X_UNUSED, Widget request _X_UNUSED, Widget cnew _X_UNUSED, 641 ArgList args _X_UNUSED, Cardinal *num_args _X_UNUSED) 642{ 643 /* need to relayout if h_space or v_space change */ 644#ifndef OLDXAW 645 BoxWidget b_old = (BoxWidget)current; 646 BoxWidget b_new = (BoxWidget)cnew; 647 648 if (b_old->core.background_pixmap != b_new->core.background_pixmap) { 649 XawPixmap *opix, *npix; 650 651 opix = XawPixmapFromXPixmap(b_old->core.background_pixmap, 652 XtScreen(b_old), b_old->core.colormap, 653 (int)b_old->core.depth); 654 npix = XawPixmapFromXPixmap(b_new->core.background_pixmap, 655 XtScreen(b_new), b_new->core.colormap, 656 (int)b_new->core.depth); 657 if ((npix && npix->mask) || (opix && opix->mask)) 658 XawReshapeWidget(cnew, npix); 659 } 660#endif /* OLDXAW */ 661 662 return (False); 663} 664 665#ifndef OLDXAW 666static void 667XawBoxExpose(Widget w, XEvent *event, Region region) 668{ 669 BoxWidget xaw = (BoxWidget)w; 670 671 if (xaw->box.display_list) 672 XawRunDisplayList(w, xaw->box.display_list, event, region); 673} 674#endif /* OLDXAW */ 675