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