comparison dwtx/jface/preference/PreferenceDialog.d @ 9:6c14e54dfc11

completed /jface/resource/
author Frank Benoit <benoit@tionex.de>
date Sat, 29 Mar 2008 02:25:12 +0100
parents
children b3c8e32d406f
comparison
equal deleted inserted replaced
8:a3ff22a98bef 9:6c14e54dfc11
1 /*******************************************************************************
2 * Copyright (c) 2000, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Teddy Walker <teddy.walker@googlemail.com>
11 * - Bug 188056 [Preferences] PreferencePages have to less indent in PreferenceDialog
12 * Port to the D programming language:
13 * Frank Benoit <benoit@tionex.de>
14 *******************************************************************************/
15 module dwtx.jface.preference.PreferenceDialog;
16
17 import dwt.dwthelper.utils;
18
19 pragma( msg, "FIXME dwtx.jface.preference.PreferenceDialog" );
20 class PreferenceDialog{
21 public static const String PREF_DLG_TITLE_IMG = "preference_dialog_title_image"; //$NON-NLS-1$
22 }
23
24 /++
25 import java.io.IOException;
26 import java.util.Iterator;
27 import java.util.List;
28
29 import dwt.DWT;
30 import dwt.custom.BusyIndicator;
31 import dwt.custom.ScrolledComposite;
32 import dwt.events.ControlAdapter;
33 import dwt.events.ControlEvent;
34 import dwt.events.DisposeEvent;
35 import dwt.events.DisposeListener;
36 import dwt.events.HelpEvent;
37 import dwt.events.HelpListener;
38 import dwt.events.SelectionAdapter;
39 import dwt.events.SelectionEvent;
40 import dwt.events.ShellAdapter;
41 import dwt.events.ShellEvent;
42 import dwt.graphics.Font;
43 import dwt.graphics.Point;
44 import dwt.graphics.Rectangle;
45 import dwt.layout.FormAttachment;
46 import dwt.layout.FormData;
47 import dwt.layout.FormLayout;
48 import dwt.layout.GridData;
49 import dwt.layout.GridLayout;
50 import dwt.widgets.Button;
51 import dwt.widgets.Composite;
52 import dwt.widgets.Control;
53 import dwt.widgets.Event;
54 import dwt.widgets.Label;
55 import dwt.widgets.Layout;
56 import dwt.widgets.Listener;
57 import dwt.widgets.Sash;
58 import dwt.widgets.Shell;
59 import dwt.widgets.Tree;
60 import dwtx.core.runtime.Assert;
61 import dwtx.core.runtime.ISafeRunnable;
62 import dwtx.core.runtime.IStatus;
63 import dwtx.core.runtime.ListenerList;
64 import dwtx.core.runtime.SafeRunner;
65 import dwtx.core.runtime.Status;
66 import dwtx.jface.dialogs.DialogMessageArea;
67 import dwtx.jface.dialogs.IDialogConstants;
68 import dwtx.jface.dialogs.IMessageProvider;
69 import dwtx.jface.dialogs.IPageChangeProvider;
70 import dwtx.jface.dialogs.IPageChangedListener;
71 import dwtx.jface.dialogs.MessageDialog;
72 import dwtx.jface.dialogs.PageChangedEvent;
73 import dwtx.jface.dialogs.TrayDialog;
74 import dwtx.jface.resource.JFaceResources;
75 import dwtx.jface.util.IPropertyChangeListener;
76 import dwtx.jface.util.Policy;
77 import dwtx.jface.util.PropertyChangeEvent;
78 import dwtx.jface.util.SafeRunnable;
79 import dwtx.jface.viewers.ISelection;
80 import dwtx.jface.viewers.ISelectionChangedListener;
81 import dwtx.jface.viewers.IStructuredSelection;
82 import dwtx.jface.viewers.SelectionChangedEvent;
83 import dwtx.jface.viewers.StructuredSelection;
84 import dwtx.jface.viewers.TreeViewer;
85 import dwtx.jface.viewers.ViewerFilter;
86
87 /**
88 * A preference dialog is a hierarchical presentation of preference pages. Each
89 * page is represented by a node in the tree shown on the left hand side of the
90 * dialog; when a node is selected, the corresponding page is shown on the right
91 * hand side.
92 */
93 public class PreferenceDialog extends TrayDialog implements IPreferencePageContainer, IPageChangeProvider {
94 /**
95 * Layout for the page container.
96 *
97 */
98 private class PageLayout extends Layout {
99 public Point computeSize(Composite composite, int wHint, int hHint, bool force) {
100 if (wHint !is DWT.DEFAULT && hHint !is DWT.DEFAULT) {
101 return new Point(wHint, hHint);
102 }
103 int x = minimumPageSize.x;
104 int y = minimumPageSize.y;
105 Control[] children = composite.getChildren();
106 for (int i = 0; i < children.length; i++) {
107 Point size = children[i].computeSize(DWT.DEFAULT, DWT.DEFAULT, force);
108 x = Math.max(x, size.x);
109 y = Math.max(y, size.y);
110 }
111
112 //As pages can implement thier own computeSize
113 //take it into account
114 if(currentPage !is null){
115 Point size = currentPage.computeSize();
116 x = Math.max(x, size.x);
117 y = Math.max(y, size.y);
118 }
119
120 if (wHint !is DWT.DEFAULT) {
121 x = wHint;
122 }
123 if (hHint !is DWT.DEFAULT) {
124 y = hHint;
125 }
126 return new Point(x, y);
127 }
128
129 public void layout(Composite composite, bool force) {
130 Rectangle rect = composite.getClientArea();
131 Control[] children = composite.getChildren();
132 for (int i = 0; i < children.length; i++) {
133 children[i].setSize(rect.width, rect.height);
134 }
135 }
136 }
137
138 //The id of the last page that was selected
139 private static String lastPreferenceId = null;
140
141 //The last known tree width
142 private static int lastTreeWidth = 180;
143
144 /**
145 * Indentifier for the error image
146 */
147 public static final String PREF_DLG_IMG_TITLE_ERROR = DLG_IMG_MESSAGE_ERROR;
148
149 /**
150 * Title area fields
151 */
152 public static final String PREF_DLG_TITLE_IMG = "preference_dialog_title_image"; //$NON-NLS-1$
153
154 /**
155 * Return code used when dialog failed
156 */
157 protected static final int FAILED = 2;
158
159 /**
160 * The current preference page, or <code>null</code> if there is none.
161 */
162 private IPreferencePage currentPage;
163
164 private DialogMessageArea messageArea;
165
166 private Point lastShellSize;
167
168 private IPreferenceNode lastSuccessfulNode;
169
170 /**
171 * The minimum page size; 400 by 400 by default.
172 *
173 * @see #setMinimumPageSize(Point)
174 */
175 private Point minimumPageSize = new Point(400, 400);
176
177 /**
178 * The OK button.
179 */
180 private Button okButton;
181
182 /**
183 * The Composite in which a page is shown.
184 */
185 private Composite pageContainer;
186
187 /**
188 * The preference manager.
189 */
190 private PreferenceManager preferenceManager;
191
192 /**
193 * Flag for the presence of the error message.
194 */
195 private bool showingError = false;
196
197 /**
198 * Preference store, initially <code>null</code> meaning none.
199 *
200 * @see #setPreferenceStore
201 */
202 private IPreferenceStore preferenceStore;
203
204 private Composite titleArea;
205
206 /**
207 * The tree viewer.
208 */
209 private TreeViewer treeViewer;
210
211 private ListenerList pageChangedListeners = new ListenerList();
212
213 /**
214 * Composite with a FormLayout to contain the title area
215 */
216 Composite formTitleComposite;
217
218 private ScrolledComposite scrolled;
219
220 /**
221 * Creates a new preference dialog under the control of the given preference
222 * manager.
223 *
224 * @param parentShell
225 * the parent shell
226 * @param manager
227 * the preference manager
228 */
229 public PreferenceDialog(Shell parentShell, PreferenceManager manager) {
230 super(parentShell);
231 setShellStyle(getShellStyle() | DWT.RESIZE | DWT.MAX);
232 preferenceManager = manager;
233 }
234
235 /*
236 * (non-Javadoc)
237 *
238 * @see dwtx.jface.dialogs.Dialog#buttonPressed(int)
239 */
240 protected void buttonPressed(int buttonId) {
241 switch (buttonId) {
242 case IDialogConstants.OK_ID: {
243 okPressed();
244 return;
245 }
246 case IDialogConstants.CANCEL_ID: {
247 cancelPressed();
248 return;
249 }
250 case IDialogConstants.HELP_ID: {
251 helpPressed();
252 return;
253 }
254 }
255 }
256
257 /*
258 * (non-Javadoc)
259 *
260 * @see dwtx.jface.dialogs.Dialog#cancelPressed()
261 */
262 protected void cancelPressed() {
263 // Inform all pages that we are cancelling
264 Iterator nodes = preferenceManager.getElements(PreferenceManager.PRE_ORDER).iterator();
265 while (nodes.hasNext()) {
266 final IPreferenceNode node = (IPreferenceNode) nodes.next();
267 if (getPage(node) !is null) {
268 SafeRunnable.run(new SafeRunnable() {
269 public void run() {
270 if (!getPage(node).performCancel()) {
271 return;
272 }
273 }
274 });
275 }
276 }
277 setReturnCode(CANCEL);
278 close();
279 }
280
281 /**
282 * Clear the last selected node. This is so that we not chache the last
283 * selection in case of an error.
284 */
285 void clearSelectedNode() {
286 setSelectedNodePreference(null);
287 }
288
289 /*
290 * (non-Javadoc)
291 *
292 * @see dwtx.jface.window.Window#close()
293 */
294 public bool close() {
295
296 //Do this is in a SafeRunnable as it may run client code
297 SafeRunnable runnable = new SafeRunnable(){
298 /* (non-Javadoc)
299 * @see dwtx.core.runtime.ISafeRunnable#run()
300 */
301 public void run() throws Exception {
302 List nodes = preferenceManager.getElements(PreferenceManager.PRE_ORDER);
303 for (int i = 0; i < nodes.size(); i++) {
304 IPreferenceNode node = (IPreferenceNode) nodes.get(i);
305 node.disposeResources();
306 }
307
308 }
309
310 /* (non-Javadoc)
311 * @see dwtx.jface.util.SafeRunnable#handleException(java.lang.Throwable)
312 */
313 public void handleException(Throwable e) {
314 super.handleException(e);
315 clearSelectedNode();//Do not cache a node with problems
316 }
317 };
318
319 SafeRunner.run(runnable);
320
321 return super.close();
322 }
323
324 /*
325 * (non-Javadoc)
326 *
327 * @see dwtx.jface.window.Window#configureShell(dwt.widgets.Shell)
328 */
329 protected void configureShell(Shell newShell) {
330 super.configureShell(newShell);
331 newShell.setText(JFaceResources.getString("PreferenceDialog.title")); //$NON-NLS-1$
332 newShell.addShellListener(new ShellAdapter() {
333 public void shellActivated(ShellEvent e) {
334 if (lastShellSize is null) {
335 lastShellSize = getShell().getSize();
336 }
337 }
338
339 });
340
341 }
342
343 /*
344 * (non-Javadoc)
345 *
346 * @see dwtx.jface.window.Window#constrainShellSize()
347 */
348 protected void constrainShellSize() {
349 super.constrainShellSize();
350 // record opening shell size
351 if (lastShellSize is null) {
352 lastShellSize = getShell().getSize();
353 }
354 }
355
356 /*
357 * (non-Javadoc)
358 *
359 * @see dwtx.jface.dialogs.Dialog#createButtonsForButtonBar(dwt.widgets.Composite)
360 */
361 protected void createButtonsForButtonBar(Composite parent) {
362 // create OK and Cancel buttons by default
363 okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
364 getShell().setDefaultButton(okButton);
365 createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
366 }
367
368 /*
369 * (non-Javadoc)
370 *
371 * @see dwtx.jface.window.Window#createContents(dwt.widgets.Composite)
372 */
373 protected Control createContents(final Composite parent) {
374 final Control[] control = new Control[1];
375 BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
376 public void run() {
377 control[0] = PreferenceDialog.super.createContents(parent);
378 // Add the first page
379 selectSavedItem();
380 }
381 });
382
383 return control[0];
384 }
385
386 /*
387 * (non-Javadoc)
388 *
389 * @see dwtx.jface.dialogs.Dialog#createDialogArea(dwt.widgets.Composite)
390 */
391 protected Control createDialogArea(Composite parent) {
392 final Composite composite = (Composite) super.createDialogArea(parent);
393 GridLayout parentLayout = ((GridLayout) composite.getLayout());
394 parentLayout.numColumns = 4;
395 parentLayout.marginHeight = 0;
396 parentLayout.marginWidth = 0;
397 parentLayout.verticalSpacing = 0;
398 parentLayout.horizontalSpacing = 0;
399
400 composite.setBackground(parent.getDisplay().getSystemColor(DWT.COLOR_LIST_BACKGROUND));
401
402 Control treeControl = createTreeAreaContents(composite);
403 createSash(composite,treeControl);
404
405 Label versep = new Label(composite, DWT.SEPARATOR | DWT.VERTICAL);
406 GridData verGd = new GridData(GridData.FILL_VERTICAL | GridData.GRAB_VERTICAL);
407
408 versep.setLayoutData(verGd);
409 versep.setLayoutData(new GridData(DWT.LEFT, DWT.FILL, false, true));
410
411 Composite pageAreaComposite = new Composite(composite, DWT.NONE);
412 pageAreaComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
413 GridLayout layout = new GridLayout(1, true);
414 layout.marginHeight = 0;
415 layout.marginWidth = 0;
416 layout.verticalSpacing = 0;
417 pageAreaComposite.setLayout(layout);
418
419 formTitleComposite = new Composite(pageAreaComposite, DWT.NONE);
420 FormLayout titleLayout = new FormLayout();
421 titleLayout.marginWidth = 0;
422 titleLayout.marginHeight = 0;
423 formTitleComposite.setLayout(titleLayout);
424
425 GridData titleGridData = new GridData(GridData.FILL_HORIZONTAL);
426 titleGridData.horizontalIndent = IDialogConstants.HORIZONTAL_MARGIN;
427 formTitleComposite.setLayoutData(titleGridData);
428
429 // Build the title area and separator line
430 Composite titleComposite = new Composite(formTitleComposite, DWT.NONE);
431 layout = new GridLayout();
432 layout.marginBottom = 5;
433 layout.marginHeight = 0;
434 layout.marginWidth = 0;
435 layout.horizontalSpacing = 0;
436 titleComposite.setLayout(layout);
437
438 FormData titleFormData = new FormData();
439 titleFormData.top = new FormAttachment(0,0);
440 titleFormData.left = new FormAttachment(0,0);
441 titleFormData.right = new FormAttachment(100,0);
442 titleFormData.bottom = new FormAttachment(100,0);
443
444 titleComposite.setLayoutData(titleFormData);
445 createTitleArea(titleComposite);
446
447 Label separator = new Label(pageAreaComposite, DWT.HORIZONTAL | DWT.SEPARATOR);
448
449 separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL));
450
451
452 // Build the Page container
453 pageContainer = createPageContainer(pageAreaComposite);
454 GridData pageContainerData = new GridData(GridData.FILL_BOTH);
455 pageContainerData.horizontalIndent = IDialogConstants.HORIZONTAL_MARGIN;
456 pageContainer.setLayoutData(pageContainerData);
457 // Build the separator line
458 Label bottomSeparator = new Label(parent, DWT.HORIZONTAL | DWT.SEPARATOR);
459 bottomSeparator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL));
460 return composite;
461 }
462
463 /**
464 * Create the sash with right control on the right. Note
465 * that this method assumes GridData for the layout data
466 * of the rightControl.
467 * @param composite
468 * @param rightControl
469 * @return Sash
470 *
471 * @since 3.1
472 */
473 protected Sash createSash(final Composite composite, final Control rightControl) {
474 final Sash sash = new Sash(composite, DWT.VERTICAL);
475 sash.setLayoutData(new GridData(GridData.FILL_VERTICAL));
476 sash.setBackground(composite.getDisplay().getSystemColor(DWT.COLOR_LIST_BACKGROUND));
477 // the following listener resizes the tree control based on sash deltas.
478 // If necessary, it will also grow/shrink the dialog.
479 sash.addListener(DWT.Selection, new Listener() {
480 /*
481 * (non-Javadoc)
482 *
483 * @see dwt.widgets.Listener#handleEvent(dwt.widgets.Event)
484 */
485 public void handleEvent(Event event) {
486 if (event.detail is DWT.DRAG) {
487 return;
488 }
489 int shift = event.x - sash.getBounds().x;
490 GridData data = (GridData) rightControl.getLayoutData();
491 int newWidthHint = data.widthHint + shift;
492 if (newWidthHint < 20) {
493 return;
494 }
495 Point computedSize = getShell().computeSize(DWT.DEFAULT, DWT.DEFAULT);
496 Point currentSize = getShell().getSize();
497 // if the dialog wasn't of a custom size we know we can shrink
498 // it if necessary based on sash movement.
499 bool customSize = !computedSize.equals(currentSize);
500 data.widthHint = newWidthHint;
501 setLastTreeWidth(newWidthHint);
502 composite.layout(true);
503 // recompute based on new widget size
504 computedSize = getShell().computeSize(DWT.DEFAULT, DWT.DEFAULT);
505 // if the dialog was of a custom size then increase it only if
506 // necessary.
507 if (customSize) {
508 computedSize.x = Math.max(computedSize.x, currentSize.x);
509 }
510 computedSize.y = Math.max(computedSize.y, currentSize.y);
511 if (computedSize.equals(currentSize)) {
512 return;
513 }
514 setShellSize(computedSize.x, computedSize.y);
515 lastShellSize = getShell().getSize();
516 }
517 });
518 return sash;
519 }
520
521 /**
522 * Creates the inner page container.
523 *
524 * @param parent
525 * @return Composite
526 */
527 protected Composite createPageContainer(Composite parent) {
528
529 Composite outer = new Composite(parent, DWT.NONE);
530
531 GridData outerData = new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL
532 | GridData.GRAB_VERTICAL);
533 outerData.horizontalIndent = IDialogConstants.HORIZONTAL_MARGIN;
534
535 outer.setLayout(new GridLayout());
536 outer.setLayoutData(outerData);
537
538 //Create an outer composite for spacing
539 scrolled = new ScrolledComposite(outer, DWT.V_SCROLL | DWT.H_SCROLL);
540
541 scrolled.setExpandHorizontal(true);
542 scrolled.setExpandVertical(true);
543
544 GridData scrolledData = new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL
545 | GridData.GRAB_VERTICAL);
546
547 scrolled.setLayoutData(scrolledData);
548
549 Composite result = new Composite(scrolled, DWT.NONE);
550
551 GridData resultData = new GridData(GridData.FILL_BOTH | GridData.GRAB_HORIZONTAL
552 | GridData.GRAB_VERTICAL);
553
554 result.setLayout(getPageLayout());
555 result.setLayoutData(resultData);
556
557 scrolled.setContent(result);
558
559 return result;
560 }
561
562 /**
563 * Return the layout for the composite that contains
564 * the pages.
565 * @return PageLayout
566 *
567 * @since 3.1
568 */
569 protected Layout getPageLayout() {
570 return new PageLayout();
571 }
572
573 /**
574 * Creates the wizard's title area.
575 *
576 * @param parent
577 * the DWT parent for the title area composite.
578 * @return the created title area composite.
579 */
580 protected Composite createTitleArea(Composite parent) {
581 // Create the title area which will contain
582 // a title, message, and image.
583 int margins = 2;
584 titleArea = new Composite(parent, DWT.NONE);
585 FormLayout layout = new FormLayout();
586 layout.marginHeight = 0;
587 layout.marginWidth = margins;
588 titleArea.setLayout(layout);
589
590
591 GridData layoutData = new GridData(GridData.FILL_HORIZONTAL);
592 layoutData.verticalAlignment = DWT.TOP;
593 titleArea.setLayoutData(layoutData);
594
595 // Message label
596 messageArea = new DialogMessageArea();
597 messageArea.createContents(titleArea);
598
599 titleArea.addControlListener(new ControlAdapter() {
600 /* (non-Javadoc)
601 * @see dwt.events.ControlAdapter#controlResized(dwt.events.ControlEvent)
602 */
603 public void controlResized(ControlEvent e) {
604 updateMessage();
605 }
606 });
607
608 final IPropertyChangeListener fontListener = new IPropertyChangeListener() {
609 public void propertyChange(PropertyChangeEvent event) {
610 if (JFaceResources.BANNER_FONT.equals(event.getProperty())) {
611 updateMessage();
612 }
613 if (JFaceResources.DIALOG_FONT.equals(event.getProperty())) {
614 updateMessage();
615 Font dialogFont = JFaceResources.getDialogFont();
616 updateTreeFont(dialogFont);
617 Control[] children = ((Composite) buttonBar).getChildren();
618 for (int i = 0; i < children.length; i++) {
619 children[i].setFont(dialogFont);
620 }
621 }
622 }
623 };
624
625 titleArea.addDisposeListener(new DisposeListener() {
626 public void widgetDisposed(DisposeEvent event) {
627 JFaceResources.getFontRegistry().removeListener(fontListener);
628 }
629 });
630 JFaceResources.getFontRegistry().addListener(fontListener);
631 messageArea.setTitleLayoutData(createMessageAreaData());
632 messageArea.setMessageLayoutData(createMessageAreaData());
633 return titleArea;
634 }
635
636 /**
637 * Create the layout data for the message area.
638 *
639 * @return FormData for the message area.
640 */
641 private FormData createMessageAreaData() {
642 FormData messageData = new FormData();
643 messageData.top = new FormAttachment(0);
644 messageData.bottom = new FormAttachment(100);
645 messageData.right = new FormAttachment(100);
646 messageData.left = new FormAttachment(0);
647 return messageData;
648 }
649
650 /**
651 * @param parent
652 * the DWT parent for the tree area controls.
653 * @return the new <code>Control</code>.
654 * @since 3.0
655 */
656 protected Control createTreeAreaContents(Composite parent) {
657 // Build the tree an put it into the composite.
658 treeViewer = createTreeViewer(parent);
659 treeViewer.setInput(getPreferenceManager());
660 updateTreeFont(JFaceResources.getDialogFont());
661 layoutTreeAreaControl(treeViewer.getControl());
662 return treeViewer.getControl();
663 }
664
665 /**
666 * Create a new <code>TreeViewer</code>.
667 *
668 * @param parent
669 * the parent <code>Composite</code>.
670 * @return the <code>TreeViewer</code>.
671 * @since 3.0
672 */
673 protected TreeViewer createTreeViewer(Composite parent) {
674 final TreeViewer viewer = new TreeViewer(parent, DWT.NONE);
675 addListeners(viewer);
676 viewer.setLabelProvider(new PreferenceLabelProvider());
677 viewer.setContentProvider(new PreferenceContentProvider());
678 return viewer;
679 }
680
681 /**
682 * Add the listeners to the tree viewer.
683 * @param viewer
684 *
685 * @since 3.1
686 */
687 protected void addListeners(final TreeViewer viewer) {
688 viewer.addPostSelectionChangedListener(new ISelectionChangedListener() {
689 private void handleError() {
690 try {
691 // remove the listener temporarily so that the events caused
692 // by the error handling dont further cause error handling
693 // to occur.
694 viewer.removePostSelectionChangedListener(this);
695 showPageFlippingAbortDialog();
696 selectCurrentPageAgain();
697 clearSelectedNode();
698 } finally {
699 viewer.addPostSelectionChangedListener(this);
700 }
701 }
702
703 public void selectionChanged(SelectionChangedEvent event) {
704 Object selection = getSingleSelection(event.getSelection());
705 if (selection instanceof IPreferenceNode) {
706 if (!isCurrentPageValid()) {
707 handleError();
708 } else if (!showPage((IPreferenceNode) selection)) {
709 // Page flipping wasn't successful
710 handleError();
711 } else {
712 // Everything went well
713 lastSuccessfulNode = (IPreferenceNode) selection;
714 }
715 }
716 }
717 });
718 ((Tree) viewer.getControl()).addSelectionListener(new SelectionAdapter() {
719 public void widgetDefaultSelected(final SelectionEvent event) {
720 ISelection selection = viewer.getSelection();
721 if (selection.isEmpty()) {
722 return;
723 }
724 IPreferenceNode singleSelection = getSingleSelection(selection);
725 bool expanded = viewer.getExpandedState(singleSelection);
726 viewer.setExpandedState(singleSelection, !expanded);
727 }
728 });
729 //Register help listener on the tree to use context sensitive help
730 viewer.getControl().addHelpListener(new HelpListener() {
731 public void helpRequested(HelpEvent event) {
732 // call perform help on the current page
733 if (currentPage !is null) {
734 currentPage.performHelp();
735 }
736 }
737 });
738 }
739
740 /**
741 * Find the <code>IPreferenceNode</code> that has data the same id as the
742 * supplied value.
743 *
744 * @param nodeId
745 * the id to search for.
746 * @return <code>IPreferenceNode</code> or <code>null</code> if not
747 * found.
748 */
749 protected IPreferenceNode findNodeMatching(String nodeId) {
750 List nodes = preferenceManager.getElements(PreferenceManager.POST_ORDER);
751 for (Iterator i = nodes.iterator(); i.hasNext();) {
752 IPreferenceNode node = (IPreferenceNode) i.next();
753 if (node.getId().equals(nodeId)) {
754 return node;
755 }
756 }
757 return null;
758 }
759
760 /**
761 * Get the last known right side width.
762 *
763 * @return the width.
764 */
765 protected int getLastRightWidth() {
766 return lastTreeWidth;
767 }
768
769 /**
770 * Returns the preference mananger used by this preference dialog.
771 *
772 * @return the preference mananger
773 */
774 public PreferenceManager getPreferenceManager() {
775 return preferenceManager;
776 }
777
778 /*
779 * (non-Javadoc)
780 *
781 * @see dwtx.jface.preference.IPreferencePageContainer#getPreferenceStore()
782 */
783 public IPreferenceStore getPreferenceStore() {
784 return preferenceStore;
785 }
786
787 /**
788 * Get the name of the selected item preference
789 *
790 * @return String
791 */
792 protected String getSelectedNodePreference() {
793 return lastPreferenceId;
794 }
795
796 /**
797 * @param selection
798 * the <code>ISelection</code> to examine.
799 * @return the first element, or null if empty.
800 */
801 protected IPreferenceNode getSingleSelection(ISelection selection) {
802 if (!selection.isEmpty()) {
803 IStructuredSelection structured = (IStructuredSelection) selection;
804 if (structured.getFirstElement() instanceof IPreferenceNode) {
805 return (IPreferenceNode) structured.getFirstElement();
806 }
807 }
808 return null;
809 }
810
811 /**
812 * @return the <code>TreeViewer</code> for this dialog.
813 * @since 3.3
814 */
815 public TreeViewer getTreeViewer() {
816 return treeViewer;
817 }
818
819 /**
820 * Save the values specified in the pages.
821 * <p>
822 * The default implementation of this framework method saves all pages of
823 * type <code>PreferencePage</code> (if their store needs saving and is a
824 * <code>PreferenceStore</code>).
825 * </p>
826 * <p>
827 * Subclasses may override.
828 * </p>
829 */
830 protected void handleSave() {
831 Iterator nodes = preferenceManager.getElements(PreferenceManager.PRE_ORDER).iterator();
832 while (nodes.hasNext()) {
833 IPreferenceNode node = (IPreferenceNode) nodes.next();
834 IPreferencePage page = node.getPage();
835 if (page instanceof PreferencePage) {
836 // Save now in case tbe workbench does not shutdown cleanly
837 IPreferenceStore store = ((PreferencePage) page).getPreferenceStore();
838 if (store !is null && store.needsSaving()
839 && store instanceof IPersistentPreferenceStore) {
840 try {
841 ((IPersistentPreferenceStore) store).save();
842 } catch (IOException e) {
843 MessageDialog
844 .openError(
845 getShell(),
846 JFaceResources.getString("PreferenceDialog.saveErrorTitle"), //$NON-NLS-1$
847 JFaceResources
848 .format(
849 "PreferenceDialog.saveErrorMessage", new Object[] { page.getTitle(), e.getMessage() })); //$NON-NLS-1$
850 }
851 }
852 }
853 }
854 }
855
856 /**
857 * Notifies that the window's close button was pressed, the close menu was
858 * selected, or the ESCAPE key pressed.
859 * <p>
860 * The default implementation of this framework method sets the window's
861 * return code to <code>CANCEL</code> and closes the window using
862 * <code>close</code>. Subclasses may extend or reimplement.
863 * </p>
864 */
865 protected void handleShellCloseEvent() {
866 // handle the same as pressing cancel
867 cancelPressed();
868 }
869
870 /**
871 * Notifies of the pressing of the Help button.
872 * <p>
873 * The default implementation of this framework method calls
874 * <code>performHelp</code> on the currently active page.
875 * </p>
876 */
877 protected void helpPressed() {
878 if (currentPage !is null) {
879 currentPage.performHelp();
880 }
881 }
882
883 /**
884 * Returns whether the current page is valid.
885 *
886 * @return <code>false</code> if the current page is not valid, or or
887 * <code>true</code> if the current page is valid or there is no
888 * current page
889 */
890 protected bool isCurrentPageValid() {
891 if (currentPage is null) {
892 return true;
893 }
894 return currentPage.isValid();
895 }
896
897 /**
898 * @param control
899 * the <code>Control</code> to lay out.
900 * @since 3.0
901 */
902 protected void layoutTreeAreaControl(Control control) {
903 GridData gd = new GridData(GridData.FILL_VERTICAL);
904 gd.widthHint = getLastRightWidth();
905 gd.verticalSpan = 1;
906 control.setLayoutData(gd);
907 }
908
909 /**
910 * The preference dialog implementation of this <code>Dialog</code>
911 * framework method sends <code>performOk</code> to all pages of the
912 * preference dialog, then calls <code>handleSave</code> on this dialog to
913 * save any state, and then calls <code>close</code> to close this dialog.
914 */
915 protected void okPressed() {
916 SafeRunnable.run(new SafeRunnable() {
917 private bool errorOccurred;
918
919 /*
920 * (non-Javadoc)
921 *
922 * @see dwtx.core.runtime.ISafeRunnable#run()
923 */
924 public void run() {
925 getButton(IDialogConstants.OK_ID).setEnabled(false);
926 errorOccurred = false;
927 bool hasFailedOK = false;
928 try {
929 // Notify all the pages and give them a chance to abort
930 Iterator nodes = preferenceManager.getElements(PreferenceManager.PRE_ORDER)
931 .iterator();
932 while (nodes.hasNext()) {
933 IPreferenceNode node = (IPreferenceNode) nodes.next();
934 IPreferencePage page = node.getPage();
935 if (page !is null) {
936 if (!page.performOk()){
937 hasFailedOK = true;
938 return;
939 }
940 }
941 }
942 } catch (Exception e) {
943 handleException(e);
944 } finally {
945 //Don't bother closing if the OK failed
946 if(hasFailedOK){
947 setReturnCode(FAILED);
948 getButton(IDialogConstants.OK_ID).setEnabled(true);
949 return;
950 }
951
952 if (!errorOccurred) {
953 //Give subclasses the choice to save the state of the
954 //preference pages.
955 handleSave();
956 }
957 setReturnCode(OK);
958 close();
959 }
960 }
961
962 /*
963 * (non-Javadoc)
964 *
965 * @see dwtx.core.runtime.ISafeRunnable#handleException(java.lang.Throwable)
966 */
967 public void handleException(Throwable e) {
968 errorOccurred = true;
969
970 Policy.getLog().log(new Status(IStatus.ERROR, Policy.JFACE, 0, e.toString(), e));
971
972 clearSelectedNode();
973 String message = JFaceResources.getString("SafeRunnable.errorMessage"); //$NON-NLS-1$
974 MessageDialog.openError(getShell(), JFaceResources.getString("Error"), message); //$NON-NLS-1$
975
976 }
977 });
978 }
979
980 /**
981 * Selects the page determined by <code>lastSuccessfulNode</code> in the
982 * page hierarchy.
983 */
984 void selectCurrentPageAgain() {
985 if (lastSuccessfulNode is null) {
986 return;
987 }
988 getTreeViewer().setSelection(new StructuredSelection(lastSuccessfulNode));
989 currentPage.setVisible(true);
990 }
991
992 /**
993 * Selects the saved item in the tree of preference pages. If it cannot do
994 * this it saves the first one.
995 */
996 protected void selectSavedItem() {
997 IPreferenceNode node = findNodeMatching(getSelectedNodePreference());
998 if (node is null) {
999 IPreferenceNode[] nodes = preferenceManager.getRootSubNodes();
1000 ViewerFilter[] filters = getTreeViewer().getFilters();
1001 for (int i = 0; i < nodes.length; i++) {
1002 IPreferenceNode selectedNode = nodes[i];
1003 // See if it passes all filters
1004 for (int j = 0; j < filters.length; j++) {
1005 if (!filters[j].select(this.treeViewer, preferenceManager
1006 .getRoot(), selectedNode)) {
1007 selectedNode = null;
1008 break;
1009 }
1010 }
1011 // if it passes all filters select it
1012 if (selectedNode !is null) {
1013 node = selectedNode;
1014 break;
1015 }
1016 }
1017 }
1018 if (node !is null) {
1019 getTreeViewer().setSelection(new StructuredSelection(node), true);
1020 // Keep focus in tree. See bugs 2692, 2621, and 6775.
1021 getTreeViewer().getControl().setFocus();
1022 }
1023 }
1024
1025 /**
1026 * Display the given error message. The currently displayed message is saved
1027 * and will be redisplayed when the error message is set to
1028 * <code>null</code>.
1029 *
1030 * @param newErrorMessage
1031 * the errorMessage to display or <code>null</code>
1032 */
1033 public void setErrorMessage(String newErrorMessage) {
1034 if (newErrorMessage is null) {
1035 messageArea.clearErrorMessage();
1036 } else {
1037 messageArea.updateText(newErrorMessage, IMessageProvider.ERROR);
1038 }
1039 }
1040
1041 /**
1042 * Save the last known tree width.
1043 *
1044 * @param width
1045 * the width.
1046 */
1047 private void setLastTreeWidth(int width) {
1048 lastTreeWidth = width;
1049 }
1050
1051 /**
1052 * Set the message text. If the message line currently displays an error,
1053 * the message is stored and will be shown after a call to clearErrorMessage
1054 * <p>
1055 * Shortcut for <code>setMessage(newMessage, NONE)</code>
1056 * </p>
1057 *
1058 * @param newMessage
1059 * the message, or <code>null</code> to clear the message
1060 */
1061 public void setMessage(String newMessage) {
1062 setMessage(newMessage, IMessageProvider.NONE);
1063 }
1064
1065 /**
1066 * Sets the message for this dialog with an indication of what type of
1067 * message it is.
1068 * <p>
1069 * The valid message types are one of <code>NONE</code>,
1070 * <code>INFORMATION</code>,<code>WARNING</code>, or
1071 * <code>ERROR</code>.
1072 * </p>
1073 * <p>
1074 * Note that for backward compatibility, a message of type
1075 * <code>ERROR</code> is different than an error message (set using
1076 * <code>setErrorMessage</code>). An error message overrides the current
1077 * message until the error message is cleared. This method replaces the
1078 * current message and does not affect the error message.
1079 * </p>
1080 *
1081 * @param newMessage
1082 * the message, or <code>null</code> to clear the message
1083 * @param newType
1084 * the message type
1085 * @since 2.0
1086 */
1087 public void setMessage(String newMessage, int newType) {
1088 messageArea.updateText(newMessage, newType);
1089 }
1090
1091 /**
1092 * Sets the minimum page size.
1093 *
1094 * @param minWidth
1095 * the minimum page width
1096 * @param minHeight
1097 * the minimum page height
1098 * @see #setMinimumPageSize(Point)
1099 */
1100 public void setMinimumPageSize(int minWidth, int minHeight) {
1101 minimumPageSize.x = minWidth;
1102 minimumPageSize.y = minHeight;
1103 }
1104
1105 /**
1106 * Sets the minimum page size.
1107 *
1108 * @param size
1109 * the page size encoded as <code>new Point(width,height)</code>
1110 * @see #setMinimumPageSize(int,int)
1111 */
1112 public void setMinimumPageSize(Point size) {
1113 minimumPageSize.x = size.x;
1114 minimumPageSize.y = size.y;
1115 }
1116
1117 /**
1118 * Sets the preference store for this preference dialog.
1119 *
1120 * @param store
1121 * the preference store
1122 * @see #getPreferenceStore
1123 */
1124 public void setPreferenceStore(IPreferenceStore store) {
1125 Assert.isNotNull(store);
1126 preferenceStore = store;
1127 }
1128
1129 /**
1130 * Save the currently selected node.
1131 */
1132 private void setSelectedNode() {
1133 String storeValue = null;
1134 IStructuredSelection selection = (IStructuredSelection) getTreeViewer().getSelection();
1135 if (selection.size() is 1) {
1136 IPreferenceNode node = (IPreferenceNode) selection.getFirstElement();
1137 storeValue = node.getId();
1138 }
1139 setSelectedNodePreference(storeValue);
1140 }
1141
1142 /**
1143 * Sets the name of the selected item preference. Public equivalent to
1144 * <code>setSelectedNodePreference</code>.
1145 *
1146 * @param pageId
1147 * The identifier for the page
1148 * @since 3.0
1149 */
1150 public void setSelectedNode(String pageId) {
1151 setSelectedNodePreference(pageId);
1152 }
1153
1154 /**
1155 * Sets the name of the selected item preference.
1156 *
1157 * @param pageId
1158 * The identifier for the page
1159 */
1160 protected void setSelectedNodePreference(String pageId) {
1161 lastPreferenceId = pageId;
1162 }
1163
1164 /**
1165 * Changes the shell size to the given size, ensuring that it is no larger
1166 * than the display bounds.
1167 *
1168 * @param width
1169 * the shell width
1170 * @param height
1171 * the shell height
1172 */
1173 private void setShellSize(int width, int height) {
1174 Rectangle preferred = getShell().getBounds();
1175 preferred.width = width;
1176 preferred.height = height;
1177 getShell().setBounds(getConstrainedShellBounds(preferred));
1178 }
1179
1180 /**
1181 * Shows the preference page corresponding to the given preference node.
1182 * Does nothing if that page is already current.
1183 *
1184 * @param node
1185 * the preference node, or <code>null</code> if none
1186 * @return <code>true</code> if the page flip was successful, and
1187 * <code>false</code> is unsuccessful
1188 */
1189 protected bool showPage(IPreferenceNode node) {
1190 if (node is null) {
1191 return false;
1192 }
1193 // Create the page if nessessary
1194 if (node.getPage() is null) {
1195 createPage(node);
1196 }
1197 if (node.getPage() is null) {
1198 return false;
1199 }
1200 IPreferencePage newPage = getPage(node);
1201 if (newPage is currentPage) {
1202 return true;
1203 }
1204 if (currentPage !is null) {
1205 if (!currentPage.okToLeave()) {
1206 return false;
1207 }
1208 }
1209 IPreferencePage oldPage = currentPage;
1210 currentPage = newPage;
1211 // Set the new page's container
1212 currentPage.setContainer(this);
1213 // Ensure that the page control has been created
1214 // (this allows lazy page control creation)
1215 if (currentPage.getControl() is null) {
1216 final bool[] failed = { false };
1217 SafeRunnable.run(new ISafeRunnable() {
1218 public void handleException(Throwable e) {
1219 failed[0] = true;
1220 }
1221
1222 public void run() {
1223 createPageControl(currentPage, pageContainer);
1224 }
1225 });
1226 if (failed[0]) {
1227 return false;
1228 }
1229 // the page is responsible for ensuring the created control is
1230 // accessable
1231 // via getControl.
1232 Assert.isNotNull(currentPage.getControl());
1233 }
1234 // Force calculation of the page's description label because
1235 // label can be wrapped.
1236 final Point[] size = new Point[1];
1237 final Point failed = new Point(-1, -1);
1238 SafeRunnable.run(new ISafeRunnable() {
1239 public void handleException(Throwable e) {
1240 size[0] = failed;
1241 }
1242
1243 public void run() {
1244 size[0] = currentPage.computeSize();
1245 }
1246 });
1247 if (size[0].equals(failed)) {
1248 return false;
1249 }
1250 Point contentSize = size[0];
1251 // Do we need resizing. Computation not needed if the
1252 // first page is inserted since computing the dialog's
1253 // size is done by calling dialog.open().
1254 // Also prevent auto resize if the user has manually resized
1255 Shell shell = getShell();
1256 Point shellSize = shell.getSize();
1257 if (oldPage !is null) {
1258 Rectangle rect = pageContainer.getClientArea();
1259 Point containerSize = new Point(rect.width, rect.height);
1260 int hdiff = contentSize.x - containerSize.x;
1261 int vdiff = contentSize.y - containerSize.y;
1262 if ((hdiff > 0 || vdiff > 0) && shellSize.equals(lastShellSize)) {
1263 hdiff = Math.max(0, hdiff);
1264 vdiff = Math.max(0, vdiff);
1265 setShellSize(shellSize.x + hdiff, shellSize.y + vdiff);
1266 lastShellSize = shell.getSize();
1267 if (currentPage.getControl().getSize().x is 0) {
1268 currentPage.getControl().setSize(containerSize);
1269 }
1270
1271 } else {
1272 currentPage.setSize(containerSize);
1273 }
1274 }
1275
1276 scrolled.setMinSize(contentSize);
1277 // Ensure that all other pages are invisible
1278 // (including ones that triggered an exception during
1279 // their creation).
1280 Control[] children = pageContainer.getChildren();
1281 Control currentControl = currentPage.getControl();
1282 for (int i = 0; i < children.length; i++) {
1283 if (children[i] !is currentControl) {
1284 children[i].setVisible(false);
1285 }
1286 }
1287 // Make the new page visible
1288 currentPage.setVisible(true);
1289 if (oldPage !is null) {
1290 oldPage.setVisible(false);
1291 }
1292 // update the dialog controls
1293 update();
1294 return true;
1295 }
1296
1297 /**
1298 * Create the page for the node.
1299 * @param node
1300 *
1301 * @since 3.1
1302 */
1303 protected void createPage(IPreferenceNode node) {
1304 node.createPage();
1305 }
1306
1307 /**
1308 * Get the page for the node.
1309 * @param node
1310 * @return IPreferencePage
1311 *
1312 * @since 3.1
1313 */
1314 protected IPreferencePage getPage(IPreferenceNode node) {
1315 return node.getPage();
1316 }
1317
1318 /**
1319 * Shows the "Page Flipping abort" dialog.
1320 */
1321 void showPageFlippingAbortDialog() {
1322 MessageDialog.openError(getShell(), JFaceResources
1323 .getString("AbortPageFlippingDialog.title"), //$NON-NLS-1$
1324 JFaceResources.getString("AbortPageFlippingDialog.message")); //$NON-NLS-1$
1325 }
1326
1327 /**
1328 * Updates this dialog's controls to reflect the current page.
1329 */
1330 protected void update() {
1331 // Update the title bar
1332 updateTitle();
1333 // Update the message line
1334 updateMessage();
1335 // Update the buttons
1336 updateButtons();
1337 //Saved the selected node in the preferences
1338 setSelectedNode();
1339 firePageChanged(new PageChangedEvent(this, getCurrentPage()));
1340 }
1341
1342 /*
1343 * (non-Javadoc)
1344 *
1345 * @see dwtx.jface.preference.IPreferencePageContainer#updateButtons()
1346 */
1347 public void updateButtons() {
1348 okButton.setEnabled(isCurrentPageValid());
1349 }
1350
1351 /*
1352 * (non-Javadoc)
1353 *
1354 * @see dwtx.jface.preference.IPreferencePageContainer#updateMessage()
1355 */
1356 public void updateMessage() {
1357 String message = null;
1358 String errorMessage = null;
1359 if(currentPage !is null){
1360 message = currentPage.getMessage();
1361 errorMessage = currentPage.getErrorMessage();
1362 }
1363 int messageType = IMessageProvider.NONE;
1364 if (message !is null && currentPage instanceof IMessageProvider) {
1365 messageType = ((IMessageProvider) currentPage).getMessageType();
1366 }
1367
1368 if (errorMessage is null){
1369 if (showingError) {
1370 // we were previously showing an error
1371 showingError = false;
1372 }
1373 }
1374 else {
1375 message = errorMessage;
1376 messageType = IMessageProvider.ERROR;
1377 if (!showingError) {
1378 // we were not previously showing an error
1379 showingError = true;
1380 }
1381 }
1382 messageArea.updateText(message,messageType);
1383 }
1384
1385 /*
1386 * (non-Javadoc)
1387 *
1388 * @see dwtx.jface.preference.IPreferencePageContainer#updateTitle()
1389 */
1390 public void updateTitle() {
1391 if(currentPage is null) {
1392 return;
1393 }
1394 messageArea.showTitle(currentPage.getTitle(), currentPage.getImage());
1395 }
1396
1397 /**
1398 * Update the tree to use the specified <code>Font</code>.
1399 *
1400 * @param dialogFont
1401 * the <code>Font</code> to use.
1402 * @since 3.0
1403 */
1404 protected void updateTreeFont(Font dialogFont) {
1405 getTreeViewer().getControl().setFont(dialogFont);
1406 }
1407
1408 /**
1409 * Returns the currentPage.
1410 * @return IPreferencePage
1411 * @since 3.1
1412 */
1413 protected IPreferencePage getCurrentPage() {
1414 return currentPage;
1415 }
1416
1417 /**
1418 * Sets the current page.
1419 * @param currentPage
1420 *
1421 * @since 3.1
1422 */
1423 protected void setCurrentPage(IPreferencePage currentPage) {
1424 this.currentPage = currentPage;
1425 }
1426
1427 /**
1428 * Set the treeViewer.
1429 * @param treeViewer
1430 *
1431 * @since 3.1
1432 */
1433 protected void setTreeViewer(TreeViewer treeViewer) {
1434 this.treeViewer = treeViewer;
1435 }
1436
1437 /**
1438 * Get the composite that is showing the page.
1439 *
1440 * @return Composite.
1441 *
1442 * @since 3.1
1443 */
1444 protected Composite getPageContainer() {
1445 return this.pageContainer;
1446 }
1447
1448 /**
1449 * Set the composite that is showing the page.
1450 * @param pageContainer Composite
1451 *
1452 * @since 3.1
1453 */
1454 protected void setPageContainer(Composite pageContainer) {
1455 this.pageContainer = pageContainer;
1456 }
1457 /**
1458 * Create the page control for the supplied page.
1459 *
1460 * @param page - the preference page to be shown
1461 * @param parent - the composite to parent the page
1462 *
1463 * @since 3.1
1464 */
1465 protected void createPageControl(IPreferencePage page, Composite parent) {
1466 page.createControl(parent);
1467 }
1468
1469 /**
1470 * @see dwtx.jface.dialogs.IPageChangeProvider#getSelectedPage()
1471 *
1472 * @since 3.1
1473 */
1474 public Object getSelectedPage() {
1475 return getCurrentPage();
1476 }
1477
1478 /**
1479 * @see dwtx.jface.dialogs.IPageChangeProvider#addPageChangedListener(dwtx.jface.dialogs.IPageChangedListener)
1480 * @since 3.1
1481 */
1482 public void addPageChangedListener(IPageChangedListener listener) {
1483 pageChangedListeners.add(listener);
1484 }
1485
1486 /**
1487 * @see dwtx.jface.dialogs.IPageChangeProvider#removePageChangedListener(dwtx.jface.dialogs.IPageChangedListener)
1488 * @since 3.1
1489 */
1490 public void removePageChangedListener(IPageChangedListener listener) {
1491 pageChangedListeners.remove(listener);
1492
1493 }
1494
1495 /**
1496 * Notifies any selection changed listeners that the selected page
1497 * has changed.
1498 * Only listeners registered at the time this method is called are notified.
1499 *
1500 * @param event a selection changed event
1501 *
1502 * @see IPageChangedListener#pageChanged
1503 *
1504 * @since 3.1
1505 */
1506 protected void firePageChanged(final PageChangedEvent event) {
1507 Object[] listeners = pageChangedListeners.getListeners();
1508 for (int i = 0; i < listeners.length; i++) {
1509 final IPageChangedListener l = (IPageChangedListener) listeners[i];
1510 SafeRunnable.run(new SafeRunnable() {
1511 public void run() {
1512 l.pageChanged(event);
1513 }
1514 });
1515 }
1516 }
1517 }
1518 ++/