001: /*
002: * Copyright 2002-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.awt.X11;
027:
028: import java.awt.*;
029: import java.awt.event.*;
030: import java.awt.peer.*;
031: import java.lang.reflect.*;
032: import sun.awt.SunToolkit;
033:
034: class XScrollPanePeer extends XComponentPeer implements ScrollPanePeer,
035: XScrollbarClient {
036:
037: public final static int MARGIN = 1;
038: public final static int SCROLLBAR;
039: public final static int SPACE = 2;
040: public final static int SCROLLBAR_INSET = 2;
041:
042: public final static int VERTICAL = 1 << 0;
043: public final static int HORIZONTAL = 1 << 1;
044:
045: private static Method m_setValue;
046: static {
047: m_setValue = SunToolkit.getMethod(ScrollPaneAdjustable.class,
048: "setTypedValue", new Class[] { Integer.TYPE,
049: Integer.TYPE });
050: SCROLLBAR = XToolkit.getUIDefaults().getInt(
051: "ScrollBar.defaultWidth");
052: }
053:
054: XVerticalScrollbar vsb;
055: XHorizontalScrollbar hsb;
056: XWindow clip;
057:
058: int active = VERTICAL;
059: int hsbSpace;
060: int vsbSpace;
061:
062: static class XScrollPaneContentWindow extends XWindow {
063: XScrollPaneContentWindow(ScrollPane target, long parentWindow) {
064: super (target, parentWindow);
065: }
066:
067: public String getWMName() {
068: return "ScrollPane content";
069: }
070: }
071:
072: XScrollPanePeer(ScrollPane target) {
073: super (target);
074:
075: // Create the clip window. The field "clip" must be null when
076: // we call winCreate, or the parent of clip will be set to itself!
077: clip = null;
078:
079: XWindow c = new XScrollPaneContentWindow(target, window);
080: clip = c;
081:
082: vsb = new XVerticalScrollbar(this );
083:
084: hsb = new XHorizontalScrollbar(this );
085:
086: if (target.getScrollbarDisplayPolicy() == ScrollPane.SCROLLBARS_ALWAYS) {
087: vsbSpace = hsbSpace = SCROLLBAR;
088: } else {
089: vsbSpace = hsbSpace = 0;
090: }
091:
092: int unitIncrement = 1;
093: Adjustable vAdjustable = target.getVAdjustable();
094: if (vAdjustable != null) {
095: unitIncrement = vAdjustable.getUnitIncrement();
096: }
097: int h = height - hsbSpace;
098: vsb.setValues(0, h, 0, h, unitIncrement, Math.max(1,
099: (int) (h * 0.90)));
100: vsb.setSize(vsbSpace - SCROLLBAR_INSET, h);
101:
102: unitIncrement = 1;
103: Adjustable hAdjustable = target.getHAdjustable();
104: if (hAdjustable != null) {
105: unitIncrement = hAdjustable.getUnitIncrement();
106: }
107: int w = width - vsbSpace;
108: hsb.setValues(0, w, 0, w, unitIncrement, Math.max(1,
109: (int) (w * 0.90)));
110: hsb.setSize(w, hsbSpace - SCROLLBAR_INSET);
111:
112: setViewportSize();
113: clip.xSetVisible(true);
114:
115: }
116:
117: public long getContentWindow() {
118: return (clip == null) ? window : clip.getWindow();
119: }
120:
121: public void setBounds(int x, int y, int w, int h, int op) {
122: super .setBounds(x, y, w, h, op);
123:
124: if (clip == null)
125: return;
126: setScrollbarSpace();
127: setViewportSize();
128: repaint();
129: }
130:
131: public Insets getInsets() {
132: return new Insets(MARGIN, MARGIN, MARGIN + hsbSpace, MARGIN
133: + vsbSpace);
134: }
135:
136: public int getHScrollbarHeight() {
137: return SCROLLBAR;
138: }
139:
140: public int getVScrollbarWidth() {
141: return SCROLLBAR;
142: }
143:
144: public void childResized(int w, int h) {
145: if (setScrollbarSpace()) {
146: setViewportSize();
147: }
148: repaint();
149: }
150:
151: Dimension getChildSize() {
152: ScrollPane sp = (ScrollPane) target;
153: if (sp.countComponents() > 0) {
154: Component c = sp.getComponent(0);
155: return c.size();
156: } else {
157: return new Dimension(0, 0);
158: }
159: }
160:
161: boolean setScrollbarSpace() {
162: ScrollPane sp = (ScrollPane) target;
163: boolean changed = false;
164: int sbDisplayPolicy = sp.getScrollbarDisplayPolicy();
165:
166: if (sbDisplayPolicy == ScrollPane.SCROLLBARS_NEVER) {
167: return changed;
168: }
169: Dimension cSize = getChildSize();
170:
171: if (sbDisplayPolicy == ScrollPane.SCROLLBARS_AS_NEEDED) {
172: int oldHsbSpace = hsbSpace;
173: int oldVsbSpace = vsbSpace;
174: hsbSpace = (cSize.width <= (width - 2 * MARGIN) ? 0
175: : SCROLLBAR);
176: vsbSpace = (cSize.height <= (height - 2 * MARGIN) ? 0
177: : SCROLLBAR);
178:
179: if (hsbSpace == 0 && vsbSpace != 0) {
180: hsbSpace = (cSize.width <= (width - SCROLLBAR - 2 * MARGIN) ? 0
181: : SCROLLBAR);
182: }
183: if (vsbSpace == 0 && hsbSpace != 0) {
184: vsbSpace = (cSize.height <= (height - SCROLLBAR - 2 * MARGIN) ? 0
185: : SCROLLBAR);
186: }
187: if (oldHsbSpace != hsbSpace || oldVsbSpace != vsbSpace) {
188: changed = true;
189: }
190: }
191: if (vsbSpace > 0) {
192: int vis = height - (2 * MARGIN) - hsbSpace;
193: int max = Math.max(cSize.height, vis);
194: vsb.setValues(vsb.getValue(), vis, 0, max);
195: vsb.setBlockIncrement((int) (vsb.getVisibleAmount() * .90));
196: vsb.setSize(vsbSpace - SCROLLBAR_INSET, height - hsbSpace);
197: // Adjustable vadj = sp.getVAdjustable();
198: // vadj.setVisibleAmount(vsb.vis);
199: // vadj.setMaximum(vsb.max);
200: // vadj.setBlockIncrement(vsb.page);
201: }
202: if (hsbSpace > 0) {
203: int vis = width - (2 * MARGIN) - vsbSpace;
204: int max = Math.max(cSize.width, vis);
205: hsb.setValues(hsb.getValue(), vis, 0, max);
206: hsb.setBlockIncrement((int) (hsb.getVisibleAmount() * .90));
207: hsb.setSize(width - vsbSpace, hsbSpace - SCROLLBAR_INSET);
208: // Adjustable hadj = sp.getHAdjustable();
209: // hadj.setVisibleAmount(hsb.vis);
210: // hadj.setMaximum(hsb.max);
211: // hadj.setBlockIncrement(hsb.page);
212: }
213:
214: // Check to see if we hid either of the scrollbars but left
215: // ourselves scrolled off of the top and/or right of the pane.
216: // If we did, we need to scroll to the top and/or right of of
217: // the pane to make it visible.
218: //
219: // Reminder: see if there is a better place to put this code.
220: boolean must_scroll = false;
221:
222: // Get the point at which the ScrollPane is currently located
223: // if number of components > 0
224: Point p = new Point(0, 0);
225:
226: if (((ScrollPane) target).getComponentCount() > 0) {
227:
228: p = ((ScrollPane) target).getComponent(0).location();
229:
230: if ((vsbSpace == 0) && (p.y < 0)) {
231: p.y = 0;
232: must_scroll = true;
233: }
234:
235: if ((hsbSpace == 0) && (p.x < 0)) {
236: p.x = 0;
237: must_scroll = true;
238: }
239: }
240:
241: if (must_scroll)
242: scroll(x, y, VERTICAL | HORIZONTAL);
243:
244: return changed;
245: }
246:
247: void setViewportSize() {
248: clip.xSetBounds(MARGIN, MARGIN,
249: width - (2 * MARGIN) - vsbSpace, height - (2 * MARGIN)
250: - hsbSpace);
251: }
252:
253: public void setUnitIncrement(Adjustable adj, int u) {
254: if (adj.getOrientation() == Adjustable.VERTICAL) {
255: vsb.setUnitIncrement(u);
256: } else {
257: // HORIZONTAL
258: hsb.setUnitIncrement(u);
259: }
260: }
261:
262: public void setValue(Adjustable adj, int v) {
263: if (adj.getOrientation() == Adjustable.VERTICAL) {
264: scroll(-1, v, VERTICAL);
265: } else {
266: // HORIZONTAL
267: scroll(v, -1, HORIZONTAL);
268: }
269: }
270:
271: public void setScrollPosition(int x, int y) {
272: scroll(x, y, VERTICAL | HORIZONTAL);
273: }
274:
275: void scroll(int x, int y, int flag) {
276: scroll(x, y, flag, AdjustmentEvent.TRACK);
277: }
278:
279: /**
280: * Scroll the contents to position x, y
281: */
282: void scroll(int x, int y, int flag, int type) {
283: checkSecurity();
284: ScrollPane sp = (ScrollPane) target;
285: Component c = getScrollChild();
286: if (c == null) {
287: return;
288: }
289: int sx, sy;
290: Color colors[] = getGUIcolors();
291:
292: if (sp.getScrollbarDisplayPolicy() == ScrollPane.SCROLLBARS_NEVER) {
293: sx = -x;
294: sy = -y;
295: } else {
296: Point p = c.location();
297: sx = p.x;
298: sy = p.y;
299:
300: if ((flag & HORIZONTAL) != 0) {
301: hsb.setValue(Math.min(x, hsb.getMaximum()
302: - hsb.getVisibleAmount()));
303: ScrollPaneAdjustable hadj = (ScrollPaneAdjustable) sp
304: .getHAdjustable();
305: setAdjustableValue(hadj, hsb.getValue(), type);
306: sx = -(hsb.getValue());
307: Graphics g = getGraphics();
308: try {
309: paintHorScrollbar(g, colors, true);
310: } finally {
311: g.dispose();
312: }
313: }
314: if ((flag & VERTICAL) != 0) {
315: vsb.setValue(Math.min(y, vsb.getMaximum()
316: - vsb.getVisibleAmount()));
317: ScrollPaneAdjustable vadj = (ScrollPaneAdjustable) sp
318: .getVAdjustable();
319: setAdjustableValue(vadj, vsb.getValue(), type);
320: sy = -(vsb.getValue());
321: Graphics g = getGraphics();
322: try {
323: paintVerScrollbar(g, colors, true);
324: } finally {
325: g.dispose();
326: }
327: }
328: }
329: c.move(sx, sy);
330: }
331:
332: void setAdjustableValue(ScrollPaneAdjustable adj, int value,
333: int type) {
334: try {
335: m_setValue.invoke(adj, new Object[] {
336: Integer.valueOf(value), Integer.valueOf(type) });
337: } catch (IllegalAccessException iae) {
338: adj.setValue(value);
339: } catch (IllegalArgumentException iae2) {
340: adj.setValue(value);
341: } catch (InvocationTargetException ite) {
342: adj.setValue(value);
343: ite.getCause().printStackTrace();
344: }
345: }
346:
347: public void paint(Graphics g) {
348: paintComponent(g);
349: }
350:
351: void paintScrollBars(Graphics g, Color[] colors) {
352: if (vsbSpace > 0) {
353: paintVerScrollbar(g, colors, true);
354: // paint the whole scrollbar
355: }
356:
357: if (hsbSpace > 0) {
358: paintHorScrollbar(g, colors, true);
359: // paint the whole scrollbar
360: }
361: }
362:
363: void repaintScrollBars() {
364: Graphics g = getGraphics();
365: Color colors[] = getGUIcolors();
366: if (g != null) {
367: paintScrollBars(g, colors);
368: }
369: g.dispose();
370: }
371:
372: public void repaintScrollbarRequest(XScrollbar sb) {
373: Graphics g = getGraphics();
374: Color colors[] = getGUIcolors();
375: if (g != null) {
376: if (sb == vsb) {
377: paintVerScrollbar(g, colors, true);
378: } else if (sb == hsb) {
379: paintHorScrollbar(g, colors, true);
380: }
381: }
382: }
383:
384: /**
385: * Paint the scrollpane.
386: */
387: public void paintComponent(Graphics g) {
388:
389: Color colors[] = getGUIcolors();
390: g.setColor(colors[BACKGROUND_COLOR]);
391: int h = height - hsbSpace;
392: int w = width - vsbSpace;
393:
394: g.fillRect(0, 0, w, h);
395:
396: // paint rectangular region between scrollbars
397: g.fillRect(w, h, vsbSpace, hsbSpace);
398:
399: if (MARGIN > 0) {
400: draw3DRect(g, colors, 0, 0, w - 1, h - 1, false);
401: }
402:
403: paintScrollBars(g, colors);
404: }
405:
406: public void handleEvent(java.awt.AWTEvent e) {
407: super .handleEvent(e);
408:
409: int id = e.getID();
410: switch (id) {
411: case PaintEvent.PAINT:
412: case PaintEvent.UPDATE:
413: repaintScrollBars();
414: break;
415: }
416: }
417:
418: /**
419: * Paint the horizontal scrollbar to the screen
420: *
421: * @param g the graphics context to draw into
422: * @param colors the colors used to draw the scrollbar
423: * @param paintAll paint the whole scrollbar if true, just the thumb if false
424: */
425: void paintHorScrollbar(Graphics g, Color colors[], boolean paintAll) {
426: if (hsbSpace <= 0) {
427: return;
428: }
429: Graphics ng = g.create();
430: g.setColor(colors[BACKGROUND_COLOR]);
431:
432: // SCROLLBAR is the height of scrollbar area
433: // but the actual scrollbar is SCROLLBAR-SPACE high;
434: // the rest must be filled with background color
435: int w = width - vsbSpace - (2 * MARGIN);
436: g.fillRect(MARGIN, height - SCROLLBAR, w, SPACE);
437: g.fillRect(0, height - SCROLLBAR, MARGIN, SCROLLBAR);
438: g.fillRect(MARGIN + w, height - SCROLLBAR, MARGIN, SCROLLBAR);
439:
440: try {
441: ng.translate(MARGIN, height - (SCROLLBAR - SPACE));
442: hsb.paint(ng, colors, paintAll);
443: } finally {
444: ng.dispose();
445: }
446:
447: }
448:
449: /**
450: * Paint the vertical scrollbar to the screen
451: *
452: * @param g the graphics context to draw into
453: * @param colors the colors used to draw the scrollbar
454: * @param paintAll paint the whole scrollbar if true, just the thumb if false
455: */
456: void paintVerScrollbar(Graphics g, Color colors[], boolean paintAll) {
457: if (vsbSpace <= 0) {
458: return;
459: }
460: Graphics ng = g.create();
461: g.setColor(colors[BACKGROUND_COLOR]);
462:
463: // SCROLLBAR is the width of scrollbar area
464: // but the actual scrollbar is SCROLLBAR-SPACE wide;
465: // the rest must be filled with background color
466: int h = height - hsbSpace - (2 * MARGIN);
467: g.fillRect(width - SCROLLBAR, MARGIN, SPACE, h);
468: g.fillRect(width - SCROLLBAR, 0, SCROLLBAR, MARGIN);
469: g.fillRect(width - SCROLLBAR, MARGIN + h, SCROLLBAR, MARGIN);
470:
471: try {
472: ng.translate(width - (SCROLLBAR - SPACE), MARGIN);
473: vsb.paint(ng, colors, paintAll);
474: } finally {
475: ng.dispose();
476: }
477: }
478:
479: /**
480: *
481: * @see java.awt.event.MouseEvent
482: * MouseEvent.MOUSE_CLICKED
483: * MouseEvent.MOUSE_PRESSED
484: * MouseEvent.MOUSE_RELEASED
485: * MouseEvent.MOUSE_MOVED
486: * MouseEvent.MOUSE_ENTERED
487: * MouseEvent.MOUSE_EXITED
488: * MouseEvent.MOUSE_DRAGGED
489: */
490: public void handleJavaMouseEvent(MouseEvent mouseEvent) {
491: super .handleJavaMouseEvent(mouseEvent);
492: int modifiers = mouseEvent.getModifiers();
493: int id = mouseEvent.getID();
494: int x = mouseEvent.getX();
495: int y = mouseEvent.getY();
496:
497: // super.handleMouseEvent(mouseEvent);
498:
499: if ((modifiers & InputEvent.BUTTON1_MASK) == 0) {
500: return;
501: }
502:
503: switch (id) {
504: case MouseEvent.MOUSE_PRESSED:
505: if (inVerticalScrollbar(x, y)) {
506: active = VERTICAL;
507: int h = height - hsbSpace - (2 * MARGIN);
508: vsb.handleMouseEvent(id, modifiers, x
509: - (width - SCROLLBAR + SPACE), y - MARGIN);
510: } else if (inHorizontalScrollbar(x, y)) {
511: active = HORIZONTAL;
512: int w = width - 2 * MARGIN - vsbSpace;
513: hsb.handleMouseEvent(id, modifiers, x - MARGIN, y
514: - (height - SCROLLBAR + SPACE));
515: }
516: break;
517:
518: // On mouse up, pass the event through to the scrollbar to stop
519: // scrolling. The x & y passed do not matter.
520: case MouseEvent.MOUSE_RELEASED:
521: // winReleaseCursorFocus();
522: if (active == VERTICAL) {
523: vsb.handleMouseEvent(id, modifiers, x, y);
524: } else if (active == HORIZONTAL) {
525: hsb.handleMouseEvent(id, modifiers, x, y);
526: }
527: break;
528:
529: case MouseEvent.MOUSE_DRAGGED:
530: if ((active == VERTICAL)) {
531: int h = height - 2 * MARGIN - hsbSpace;
532: vsb.handleMouseEvent(id, modifiers, x
533: - (width - SCROLLBAR + SPACE), y - MARGIN);
534: } else if ((active == HORIZONTAL)) {
535: int w = width - 2 * MARGIN - vsbSpace;
536: hsb.handleMouseEvent(id, modifiers, x - MARGIN, y
537: - (height - SCROLLBAR + SPACE));
538: }
539: break;
540: }
541: }
542:
543: /**
544: * return value from the scrollbar
545: */
546: public void notifyValue(XScrollbar obj, int type, int v,
547: boolean isAdjusting) {
548: if (obj == vsb) {
549: scroll(-1, v, VERTICAL, type);
550: } else if ((XHorizontalScrollbar) obj == hsb) {
551: scroll(v, -1, HORIZONTAL, type);
552: }
553: }
554:
555: /**
556: * return true if the x and y position is in the verticalscrollbar
557: */
558: boolean inVerticalScrollbar(int x, int y) {
559: if (vsbSpace <= 0) {
560: return false;
561: }
562: int h = height - MARGIN - hsbSpace;
563: return (x >= width - (SCROLLBAR - SPACE)) && (x < width)
564: && (y >= MARGIN) && (y < h);
565: }
566:
567: /**
568: * return true if the x and y position is in the horizontal scrollbar
569: */
570: boolean inHorizontalScrollbar(int x, int y) {
571: if (hsbSpace <= 0) {
572: return false;
573: }
574: int w = width - MARGIN - vsbSpace;
575: return (x >= MARGIN) && (x < w)
576: && (y >= height - (SCROLLBAR - SPACE)) && (y < height);
577: }
578:
579: private Component getScrollChild() {
580: ScrollPane sp = (ScrollPane) target;
581: Component child = null;
582: try {
583: child = sp.getComponent(0);
584: } catch (ArrayIndexOutOfBoundsException e) {
585: // do nothing. in this case we return null
586: }
587: return child;
588: }
589:
590: int vval;
591: int hval;
592: int vmax;
593: int hmax;
594:
595: /*
596: * Print the native component by rendering the Motif look ourselves.
597: * ToDo(aim): needs to query native motif for more accurate size and
598: * color information.
599: */
600: public void print(Graphics g) {
601: ScrollPane sp = (ScrollPane) target;
602: Dimension d = sp.size();
603: Color bg = sp.getBackground();
604: Color fg = sp.getForeground();
605: Point p = sp.getScrollPosition();
606: Component c = getScrollChild();
607: Dimension cd;
608: if (c != null) {
609: cd = c.size();
610: } else {
611: cd = new Dimension(0, 0);
612: }
613: int sbDisplay = sp.getScrollbarDisplayPolicy();
614: int vvis, hvis, vmin, hmin, vmax, hmax, vval, hval;
615:
616: switch (sbDisplay) {
617: case ScrollPane.SCROLLBARS_NEVER:
618: hsbSpace = vsbSpace = 0;
619: break;
620: case ScrollPane.SCROLLBARS_ALWAYS:
621: hsbSpace = vsbSpace = SCROLLBAR;
622: break;
623: case ScrollPane.SCROLLBARS_AS_NEEDED:
624: hsbSpace = (cd.width <= (d.width - 2 * MARGIN) ? 0
625: : SCROLLBAR);
626: vsbSpace = (cd.height <= (d.height - 2 * MARGIN) ? 0
627: : SCROLLBAR);
628:
629: if (hsbSpace == 0 && vsbSpace != 0) {
630: hsbSpace = (cd.width <= (d.width - SCROLLBAR - 2 * MARGIN) ? 0
631: : SCROLLBAR);
632: }
633: if (vsbSpace == 0 && hsbSpace != 0) {
634: vsbSpace = (cd.height <= (d.height - SCROLLBAR - 2 * MARGIN) ? 0
635: : SCROLLBAR);
636: }
637: }
638:
639: vvis = hvis = vmin = hmin = vmax = hmax = vval = hval = 0;
640:
641: if (vsbSpace > 0) {
642: vmin = 0;
643: vvis = d.height - (2 * MARGIN) - hsbSpace;
644: vmax = Math.max(cd.height - vvis, 0);
645: vval = p.y;
646: }
647: if (hsbSpace > 0) {
648: hmin = 0;
649: hvis = d.width - (2 * MARGIN) - vsbSpace;
650: hmax = Math.max(cd.width - hvis, 0);
651: hval = p.x;
652: }
653:
654: // need to be careful to add the margins back in here because
655: // we're drawing the margin border, after all!
656: int w = d.width - vsbSpace;
657: int h = d.height - hsbSpace;
658:
659: g.setColor(bg);
660: g.fillRect(0, 0, d.width, d.height);
661:
662: if (hsbSpace > 0) {
663: int sbw = d.width - vsbSpace;
664: g.fillRect(1, d.height - SCROLLBAR - 3, sbw - 1,
665: SCROLLBAR - 3);
666: Graphics ng = g.create();
667: try {
668: ng.translate(0, d.height - (SCROLLBAR - 2));
669: drawScrollbar(ng, bg, SCROLLBAR - 2, sbw, hmin, hmax,
670: hval, hvis, true);
671: } finally {
672: ng.dispose();
673: }
674: }
675: if (vsbSpace > 0) {
676: int sbh = d.height - hsbSpace;
677: g.fillRect(d.width - SCROLLBAR - 3, 1, SCROLLBAR - 3,
678: sbh - 1);
679: Graphics ng = g.create();
680: try {
681: ng.translate(d.width - (SCROLLBAR - 2), 0);
682: drawScrollbar(ng, bg, SCROLLBAR - 2, sbh, vmin, vmax,
683: vval, vvis, false);
684: } finally {
685: ng.dispose();
686: }
687: }
688:
689: draw3DRect(g, bg, 0, 0, w - 1, h - 1, false);
690:
691: target.print(g);
692: sp.printComponents(g);
693: }
694:
695: }
|