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