001 /*
002 * Copyright 1997-2006 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 javax.swing.plaf.basic;
027
028 import javax.swing.*;
029 import javax.swing.plaf.*;
030
031 import java.beans.*;
032
033 import java.awt.event.*;
034 import java.awt.Dimension;
035 import java.awt.Insets;
036 import java.awt.Graphics;
037 import java.awt.KeyboardFocusManager;
038 import java.awt.*;
039 import java.util.Vector;
040 import sun.swing.DefaultLookup;
041 import sun.swing.UIAction;
042 import sun.awt.AppContext;
043
044 /**
045 * Basic L&F for a desktop.
046 *
047 * @version 1.67 05/05/07
048 * @author Steve Wilson
049 */
050 public class BasicDesktopPaneUI extends DesktopPaneUI {
051 // Old actions forward to an instance of this.
052 private static final Actions SHARED_ACTION = new Actions();
053 private static Dimension minSize = new Dimension(0, 0);
054 private static Dimension maxSize = new Dimension(Integer.MAX_VALUE,
055 Integer.MAX_VALUE);
056 private Handler handler;
057 private PropertyChangeListener pcl;
058
059 protected JDesktopPane desktop;
060 protected DesktopManager desktopManager;
061
062 /**
063 * As of Java 2 platform v1.3 this previously undocumented field is no
064 * longer used.
065 * Key bindings are now defined by the LookAndFeel, please refer to
066 * the key bindings specification for further details.
067 *
068 * @deprecated As of 1.3.
069 */
070 @Deprecated
071 protected KeyStroke minimizeKey;
072 /**
073 * As of Java 2 platform v1.3 this previously undocumented field is no
074 * longer used.
075 * Key bindings are now defined by the LookAndFeel, please refer to
076 * the key bindings specification for further details.
077 *
078 * @deprecated As of 1.3.
079 */
080 @Deprecated
081 protected KeyStroke maximizeKey;
082 /**
083 * As of Java 2 platform v1.3 this previously undocumented field is no
084 * longer used.
085 * Key bindings are now defined by the LookAndFeel, please refer to
086 * the key bindings specification for further details.
087 *
088 * @deprecated As of 1.3.
089 */
090 @Deprecated
091 protected KeyStroke closeKey;
092 /**
093 * As of Java 2 platform v1.3 this previously undocumented field is no
094 * longer used.
095 * Key bindings are now defined by the LookAndFeel, please refer to
096 * the key bindings specification for further details.
097 *
098 * @deprecated As of 1.3.
099 */
100 @Deprecated
101 protected KeyStroke navigateKey;
102 /**
103 * As of Java 2 platform v1.3 this previously undocumented field is no
104 * longer used.
105 * Key bindings are now defined by the LookAndFeel, please refer to
106 * the key bindings specification for further details.
107 *
108 * @deprecated As of 1.3.
109 */
110 @Deprecated
111 protected KeyStroke navigateKey2;
112
113 public static ComponentUI createUI(JComponent c) {
114 return new BasicDesktopPaneUI();
115 }
116
117 public BasicDesktopPaneUI() {
118 }
119
120 public void installUI(JComponent c) {
121 desktop = (JDesktopPane) c;
122 installDefaults();
123 installDesktopManager();
124 installListeners();
125 installKeyboardActions();
126 }
127
128 public void uninstallUI(JComponent c) {
129 uninstallKeyboardActions();
130 uninstallListeners();
131 uninstallDesktopManager();
132 uninstallDefaults();
133 desktop = null;
134 handler = null;
135 }
136
137 protected void installDefaults() {
138 if (desktop.getBackground() == null
139 || desktop.getBackground() instanceof UIResource) {
140 desktop.setBackground(UIManager
141 .getColor("Desktop.background"));
142 }
143 LookAndFeel.installProperty(desktop, "opaque", Boolean.TRUE);
144 }
145
146 protected void uninstallDefaults() {
147 }
148
149 /**
150 * Installs the <code>PropertyChangeListener</code> returned from
151 * <code>createPropertyChangeListener</code> on the
152 * <code>JDesktopPane</code>.
153 *
154 * @since 1.5
155 * @see #createPropertyChangeListener
156 */
157 protected void installListeners() {
158 pcl = createPropertyChangeListener();
159 desktop.addPropertyChangeListener(pcl);
160 }
161
162 /**
163 * Uninstalls the <code>PropertyChangeListener</code> returned from
164 * <code>createPropertyChangeListener</code> from the
165 * <code>JDesktopPane</code>.
166 *
167 * @since 1.5
168 * @see #createPropertyChangeListener
169 */
170 protected void uninstallListeners() {
171 desktop.removePropertyChangeListener(pcl);
172 pcl = null;
173 }
174
175 protected void installDesktopManager() {
176 desktopManager = desktop.getDesktopManager();
177 if (desktopManager == null) {
178 desktopManager = new BasicDesktopManager();
179 desktop.setDesktopManager(desktopManager);
180 }
181 }
182
183 protected void uninstallDesktopManager() {
184 if (desktop.getDesktopManager() instanceof UIResource) {
185 desktop.setDesktopManager(null);
186 }
187 desktopManager = null;
188 }
189
190 protected void installKeyboardActions() {
191 InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
192 if (inputMap != null) {
193 SwingUtilities.replaceUIInputMap(desktop,
194 JComponent.WHEN_IN_FOCUSED_WINDOW, inputMap);
195 }
196 inputMap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
197 if (inputMap != null) {
198 SwingUtilities.replaceUIInputMap(desktop,
199 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
200 inputMap);
201 }
202
203 LazyActionMap.installLazyActionMap(desktop,
204 BasicDesktopPaneUI.class, "DesktopPane.actionMap");
205 registerKeyboardActions();
206 }
207
208 protected void registerKeyboardActions() {
209 }
210
211 protected void unregisterKeyboardActions() {
212 }
213
214 InputMap getInputMap(int condition) {
215 if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
216 return createInputMap(condition);
217 } else if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
218 return (InputMap) DefaultLookup.get(desktop, this ,
219 "Desktop.ancestorInputMap");
220 }
221 return null;
222 }
223
224 InputMap createInputMap(int condition) {
225 if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
226 Object[] bindings = (Object[]) DefaultLookup.get(desktop,
227 this , "Desktop.windowBindings");
228
229 if (bindings != null) {
230 return LookAndFeel.makeComponentInputMap(desktop,
231 bindings);
232 }
233 }
234 return null;
235 }
236
237 static void loadActionMap(LazyActionMap map) {
238 map.put(new Actions(Actions.RESTORE));
239 map.put(new Actions(Actions.CLOSE));
240 map.put(new Actions(Actions.MOVE));
241 map.put(new Actions(Actions.RESIZE));
242 map.put(new Actions(Actions.LEFT));
243 map.put(new Actions(Actions.SHRINK_LEFT));
244 map.put(new Actions(Actions.RIGHT));
245 map.put(new Actions(Actions.SHRINK_RIGHT));
246 map.put(new Actions(Actions.UP));
247 map.put(new Actions(Actions.SHRINK_UP));
248 map.put(new Actions(Actions.DOWN));
249 map.put(new Actions(Actions.SHRINK_DOWN));
250 map.put(new Actions(Actions.ESCAPE));
251 map.put(new Actions(Actions.MINIMIZE));
252 map.put(new Actions(Actions.MAXIMIZE));
253 map.put(new Actions(Actions.NEXT_FRAME));
254 map.put(new Actions(Actions.PREVIOUS_FRAME));
255 map.put(new Actions(Actions.NAVIGATE_NEXT));
256 map.put(new Actions(Actions.NAVIGATE_PREVIOUS));
257 }
258
259 protected void uninstallKeyboardActions() {
260 unregisterKeyboardActions();
261 SwingUtilities.replaceUIInputMap(desktop,
262 JComponent.WHEN_IN_FOCUSED_WINDOW, null);
263 SwingUtilities.replaceUIInputMap(desktop,
264 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
265 SwingUtilities.replaceUIActionMap(desktop, null);
266 }
267
268 public void paint(Graphics g, JComponent c) {
269 }
270
271 public Dimension getPreferredSize(JComponent c) {
272 return null;
273 }
274
275 public Dimension getMinimumSize(JComponent c) {
276 return minSize;
277 }
278
279 public Dimension getMaximumSize(JComponent c) {
280 return maxSize;
281 }
282
283 /**
284 * Returns the <code>PropertyChangeListener</code> to install on
285 * the <code>JDesktopPane</code>.
286 *
287 * @since 1.5
288 * @return The PropertyChangeListener that will be added to track
289 * changes in the desktop pane.
290 */
291 protected PropertyChangeListener createPropertyChangeListener() {
292 return getHandler();
293 }
294
295 private Handler getHandler() {
296 if (handler == null) {
297 handler = new Handler();
298 }
299 return handler;
300 }
301
302 private class Handler implements PropertyChangeListener {
303 public void propertyChange(PropertyChangeEvent evt) {
304 String propertyName = evt.getPropertyName();
305 if ("desktopManager" == propertyName) {
306 installDesktopManager();
307 }
308 }
309 }
310
311 /**
312 * The default DesktopManager installed by the UI.
313 */
314 private class BasicDesktopManager extends DefaultDesktopManager
315 implements UIResource {
316 }
317
318 private static class Actions extends UIAction {
319 private static String CLOSE = "close";
320 private static String ESCAPE = "escape";
321 private static String MAXIMIZE = "maximize";
322 private static String MINIMIZE = "minimize";
323 private static String MOVE = "move";
324 private static String RESIZE = "resize";
325 private static String RESTORE = "restore";
326 private static String LEFT = "left";
327 private static String RIGHT = "right";
328 private static String UP = "up";
329 private static String DOWN = "down";
330 private static String SHRINK_LEFT = "shrinkLeft";
331 private static String SHRINK_RIGHT = "shrinkRight";
332 private static String SHRINK_UP = "shrinkUp";
333 private static String SHRINK_DOWN = "shrinkDown";
334 private static String NEXT_FRAME = "selectNextFrame";
335 private static String PREVIOUS_FRAME = "selectPreviousFrame";
336 private static String NAVIGATE_NEXT = "navigateNext";
337 private static String NAVIGATE_PREVIOUS = "navigatePrevious";
338 private final int MOVE_RESIZE_INCREMENT = 10;
339 private static boolean moving = false;
340 private static boolean resizing = false;
341 private static JInternalFrame sourceFrame = null;
342 private static Component focusOwner = null;
343
344 Actions() {
345 super (null);
346 }
347
348 Actions(String name) {
349 super (name);
350 }
351
352 public void actionPerformed(ActionEvent e) {
353 JDesktopPane dp = (JDesktopPane) e.getSource();
354 String key = getName();
355
356 if (CLOSE == key || MAXIMIZE == key || MINIMIZE == key
357 || RESTORE == key) {
358 setState(dp, key);
359 } else if (ESCAPE == key) {
360 if (sourceFrame == dp.getSelectedFrame()
361 && focusOwner != null) {
362 focusOwner.requestFocus();
363 }
364 moving = false;
365 resizing = false;
366 sourceFrame = null;
367 focusOwner = null;
368 } else if (MOVE == key || RESIZE == key) {
369 sourceFrame = dp.getSelectedFrame();
370 if (sourceFrame == null) {
371 return;
372 }
373 moving = (key == MOVE) ? true : false;
374 resizing = (key == RESIZE) ? true : false;
375
376 focusOwner = KeyboardFocusManager
377 .getCurrentKeyboardFocusManager()
378 .getFocusOwner();
379 if (!SwingUtilities.isDescendingFrom(focusOwner,
380 sourceFrame)) {
381 focusOwner = null;
382 }
383 sourceFrame.requestFocus();
384 } else if (LEFT == key || RIGHT == key || UP == key
385 || DOWN == key || SHRINK_RIGHT == key
386 || SHRINK_LEFT == key || SHRINK_UP == key
387 || SHRINK_DOWN == key) {
388 JInternalFrame c = dp.getSelectedFrame();
389 if (sourceFrame == null
390 || c != sourceFrame
391 || KeyboardFocusManager
392 .getCurrentKeyboardFocusManager()
393 .getFocusOwner() != sourceFrame) {
394 return;
395 }
396 Insets minOnScreenInsets = UIManager
397 .getInsets("Desktop.minOnScreenInsets");
398 Dimension size = c.getSize();
399 Dimension minSize = c.getMinimumSize();
400 int dpWidth = dp.getWidth();
401 int dpHeight = dp.getHeight();
402 int delta;
403 Point loc = c.getLocation();
404 if (LEFT == key) {
405 if (moving) {
406 c
407 .setLocation(
408 loc.x + size.width
409 - MOVE_RESIZE_INCREMENT < minOnScreenInsets.right ? -size.width
410 + minOnScreenInsets.right
411 : loc.x
412 - MOVE_RESIZE_INCREMENT,
413 loc.y);
414 } else if (resizing) {
415 c.setLocation(loc.x - MOVE_RESIZE_INCREMENT,
416 loc.y);
417 c.setSize(size.width + MOVE_RESIZE_INCREMENT,
418 size.height);
419 }
420 } else if (RIGHT == key) {
421 if (moving) {
422 c
423 .setLocation(
424 loc.x + MOVE_RESIZE_INCREMENT > dpWidth
425 - minOnScreenInsets.left ? dpWidth
426 - minOnScreenInsets.left
427 : loc.x
428 + MOVE_RESIZE_INCREMENT,
429 loc.y);
430 } else if (resizing) {
431 c.setSize(size.width + MOVE_RESIZE_INCREMENT,
432 size.height);
433 }
434 } else if (UP == key) {
435 if (moving) {
436 c
437 .setLocation(
438 loc.x,
439 loc.y + size.height
440 - MOVE_RESIZE_INCREMENT < minOnScreenInsets.bottom ? -size.height
441 + minOnScreenInsets.bottom
442 : loc.y
443 - MOVE_RESIZE_INCREMENT);
444 } else if (resizing) {
445 c.setLocation(loc.x, loc.y
446 - MOVE_RESIZE_INCREMENT);
447 c.setSize(size.width, size.height
448 + MOVE_RESIZE_INCREMENT);
449 }
450 } else if (DOWN == key) {
451 if (moving) {
452 c.setLocation(loc.x, loc.y
453 + MOVE_RESIZE_INCREMENT > dpHeight
454 - minOnScreenInsets.top ? dpHeight
455 - minOnScreenInsets.top : loc.y
456 + MOVE_RESIZE_INCREMENT);
457 } else if (resizing) {
458 c.setSize(size.width, size.height
459 + MOVE_RESIZE_INCREMENT);
460 }
461 } else if (SHRINK_LEFT == key && resizing) {
462 // Make sure we don't resize less than minimum size.
463 if (minSize.width < (size.width - MOVE_RESIZE_INCREMENT)) {
464 delta = MOVE_RESIZE_INCREMENT;
465 } else {
466 delta = size.width - minSize.width;
467 }
468
469 // Ensure that we keep the internal frame on the desktop.
470 if (loc.x + size.width - delta < minOnScreenInsets.left) {
471 delta = loc.x + size.width
472 - minOnScreenInsets.left;
473 }
474 c.setSize(size.width - delta, size.height);
475 } else if (SHRINK_RIGHT == key && resizing) {
476 // Make sure we don't resize less than minimum size.
477 if (minSize.width < (size.width - MOVE_RESIZE_INCREMENT)) {
478 delta = MOVE_RESIZE_INCREMENT;
479 } else {
480 delta = size.width - minSize.width;
481 }
482
483 // Ensure that we keep the internal frame on the desktop.
484 if (loc.x + delta > dpWidth
485 - minOnScreenInsets.right) {
486 delta = (dpWidth - minOnScreenInsets.right)
487 - loc.x;
488 }
489
490 c.setLocation(loc.x + delta, loc.y);
491 c.setSize(size.width - delta, size.height);
492 } else if (SHRINK_UP == key && resizing) {
493 // Make sure we don't resize less than minimum size.
494 if (minSize.height < (size.height - MOVE_RESIZE_INCREMENT)) {
495 delta = MOVE_RESIZE_INCREMENT;
496 } else {
497 delta = size.height - minSize.height;
498 }
499
500 // Ensure that we keep the internal frame on the desktop.
501 if (loc.y + size.height - delta < minOnScreenInsets.bottom) {
502 delta = loc.y + size.height
503 - minOnScreenInsets.bottom;
504 }
505
506 c.setSize(size.width, size.height - delta);
507 } else if (SHRINK_DOWN == key && resizing) {
508 // Make sure we don't resize less than minimum size.
509 if (minSize.height < (size.height - MOVE_RESIZE_INCREMENT)) {
510 delta = MOVE_RESIZE_INCREMENT;
511 } else {
512 delta = size.height - minSize.height;
513 }
514
515 // Ensure that we keep the internal frame on the desktop.
516 if (loc.y + delta > dpHeight
517 - minOnScreenInsets.top) {
518 delta = (dpHeight - minOnScreenInsets.top)
519 - loc.y;
520 }
521
522 c.setLocation(loc.x, loc.y + delta);
523 c.setSize(size.width, size.height - delta);
524 }
525 } else if (NEXT_FRAME == key || PREVIOUS_FRAME == key) {
526 dp.selectFrame((key == NEXT_FRAME) ? true : false);
527 } else if (NAVIGATE_NEXT == key || NAVIGATE_PREVIOUS == key) {
528 boolean moveForward = true;
529 if (NAVIGATE_PREVIOUS == key) {
530 moveForward = false;
531 }
532 Container cycleRoot = dp.getFocusCycleRootAncestor();
533
534 if (cycleRoot != null) {
535 FocusTraversalPolicy policy = cycleRoot
536 .getFocusTraversalPolicy();
537 if (policy != null
538 && policy instanceof SortingFocusTraversalPolicy) {
539 SortingFocusTraversalPolicy sPolicy = (SortingFocusTraversalPolicy) policy;
540 boolean idc = sPolicy
541 .getImplicitDownCycleTraversal();
542 try {
543 sPolicy
544 .setImplicitDownCycleTraversal(false);
545 if (moveForward) {
546 KeyboardFocusManager
547 .getCurrentKeyboardFocusManager()
548 .focusNextComponent(dp);
549 } else {
550 KeyboardFocusManager
551 .getCurrentKeyboardFocusManager()
552 .focusPreviousComponent(dp);
553 }
554 } finally {
555 sPolicy.setImplicitDownCycleTraversal(idc);
556 }
557 }
558 }
559 }
560 }
561
562 private void setState(JDesktopPane dp, String state) {
563 if (state == CLOSE) {
564 JInternalFrame f = dp.getSelectedFrame();
565 if (f == null) {
566 return;
567 }
568 f.doDefaultCloseAction();
569 } else if (state == MAXIMIZE) {
570 // maximize the selected frame
571 JInternalFrame f = dp.getSelectedFrame();
572 if (f == null) {
573 return;
574 }
575 if (!f.isMaximum()) {
576 if (f.isIcon()) {
577 try {
578 f.setIcon(false);
579 f.setMaximum(true);
580 } catch (PropertyVetoException pve) {
581 }
582 } else {
583 try {
584 f.setMaximum(true);
585 } catch (PropertyVetoException pve) {
586 }
587 }
588 }
589 } else if (state == MINIMIZE) {
590 // minimize the selected frame
591 JInternalFrame f = dp.getSelectedFrame();
592 if (f == null) {
593 return;
594 }
595 if (!f.isIcon()) {
596 try {
597 f.setIcon(true);
598 } catch (PropertyVetoException pve) {
599 }
600 }
601 } else if (state == RESTORE) {
602 // restore the selected minimized or maximized frame
603 JInternalFrame f = dp.getSelectedFrame();
604 if (f == null) {
605 return;
606 }
607 try {
608 if (f.isIcon()) {
609 f.setIcon(false);
610 } else if (f.isMaximum()) {
611 f.setMaximum(false);
612 }
613 f.setSelected(true);
614 } catch (PropertyVetoException pve) {
615 }
616 }
617 }
618
619 public boolean isEnabled(Object sender) {
620 if (sender instanceof JDesktopPane) {
621 JDesktopPane dp = (JDesktopPane) sender;
622 String action = getName();
623 if (action == Actions.NEXT_FRAME
624 || action == Actions.PREVIOUS_FRAME) {
625 return true;
626 }
627 JInternalFrame iFrame = dp.getSelectedFrame();
628 if (iFrame == null) {
629 return false;
630 } else if (action == Actions.CLOSE) {
631 return iFrame.isClosable();
632 } else if (action == Actions.MINIMIZE) {
633 return iFrame.isIconifiable();
634 } else if (action == Actions.MAXIMIZE) {
635 return iFrame.isMaximizable();
636 }
637 return true;
638 }
639 return false;
640 }
641 }
642
643 /**
644 * Handles restoring a minimized or maximized internal frame.
645 * @since 1.3
646 */
647 protected class OpenAction extends AbstractAction {
648 public void actionPerformed(ActionEvent evt) {
649 JDesktopPane dp = (JDesktopPane) evt.getSource();
650 SHARED_ACTION.setState(dp, Actions.RESTORE);
651 }
652
653 public boolean isEnabled() {
654 return true;
655 }
656 }
657
658 /**
659 * Handles closing an internal frame.
660 */
661 protected class CloseAction extends AbstractAction {
662 public void actionPerformed(ActionEvent evt) {
663 JDesktopPane dp = (JDesktopPane) evt.getSource();
664 SHARED_ACTION.setState(dp, Actions.CLOSE);
665 }
666
667 public boolean isEnabled() {
668 JInternalFrame iFrame = desktop.getSelectedFrame();
669 if (iFrame != null) {
670 return iFrame.isClosable();
671 }
672 return false;
673 }
674 }
675
676 /**
677 * Handles minimizing an internal frame.
678 */
679 protected class MinimizeAction extends AbstractAction {
680 public void actionPerformed(ActionEvent evt) {
681 JDesktopPane dp = (JDesktopPane) evt.getSource();
682 SHARED_ACTION.setState(dp, Actions.MINIMIZE);
683 }
684
685 public boolean isEnabled() {
686 JInternalFrame iFrame = desktop.getSelectedFrame();
687 if (iFrame != null) {
688 return iFrame.isIconifiable();
689 }
690 return false;
691 }
692 }
693
694 /**
695 * Handles maximizing an internal frame.
696 */
697 protected class MaximizeAction extends AbstractAction {
698 public void actionPerformed(ActionEvent evt) {
699 JDesktopPane dp = (JDesktopPane) evt.getSource();
700 SHARED_ACTION.setState(dp, Actions.MAXIMIZE);
701 }
702
703 public boolean isEnabled() {
704 JInternalFrame iFrame = desktop.getSelectedFrame();
705 if (iFrame != null) {
706 return iFrame.isMaximizable();
707 }
708 return false;
709 }
710 }
711
712 /**
713 * Handles navigating to the next internal frame.
714 */
715 protected class NavigateAction extends AbstractAction {
716 public void actionPerformed(ActionEvent evt) {
717 JDesktopPane dp = (JDesktopPane) evt.getSource();
718 dp.selectFrame(true);
719 }
720
721 public boolean isEnabled() {
722 return true;
723 }
724 }
725 }
|