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