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