001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.registry;
011:
012: import java.io.BufferedReader;
013: import java.io.File;
014: import java.io.FileInputStream;
015: import java.io.IOException;
016: import java.io.InputStream;
017: import java.io.InputStreamReader;
018: import java.io.Reader;
019: import java.io.StringReader;
020: import java.io.StringWriter;
021: import java.io.Writer;
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.util.List;
025:
026: import org.eclipse.core.runtime.IConfigurationElement;
027: import org.eclipse.core.runtime.IExtension;
028: import org.eclipse.core.runtime.IPath;
029: import org.eclipse.core.runtime.IStatus;
030: import org.eclipse.core.runtime.Platform;
031: import org.eclipse.core.runtime.Status;
032: import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
033: import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
034: import org.eclipse.jface.dialogs.IDialogSettings;
035: import org.eclipse.jface.preference.IPreferenceStore;
036: import org.eclipse.jface.resource.StringConverter;
037: import org.eclipse.jface.util.IPropertyChangeListener;
038: import org.eclipse.jface.util.PropertyChangeEvent;
039: import org.eclipse.ui.IMemento;
040: import org.eclipse.ui.IPerspectiveDescriptor;
041: import org.eclipse.ui.IPerspectiveRegistry;
042: import org.eclipse.ui.IWorkbenchPage;
043: import org.eclipse.ui.IWorkbenchPreferenceConstants;
044: import org.eclipse.ui.IWorkbenchWindow;
045: import org.eclipse.ui.PlatformUI;
046: import org.eclipse.ui.WorkbenchException;
047: import org.eclipse.ui.XMLMemento;
048: import org.eclipse.ui.internal.ClosePerspectiveAction;
049: import org.eclipse.ui.internal.IPreferenceConstants;
050: import org.eclipse.ui.internal.Workbench;
051: import org.eclipse.ui.internal.WorkbenchMessages;
052: import org.eclipse.ui.internal.WorkbenchPage;
053: import org.eclipse.ui.internal.WorkbenchPlugin;
054: import org.eclipse.ui.internal.misc.StatusUtil;
055: import org.eclipse.ui.internal.util.PrefUtil;
056: import org.eclipse.ui.statushandlers.StatusManager;
057:
058: /**
059: * Perspective registry.
060: */
061: public class PerspectiveRegistry implements IPerspectiveRegistry,
062: IExtensionChangeHandler {
063: private String defaultPerspID;
064:
065: private static final String EXT = "_persp.xml"; //$NON-NLS-1$
066:
067: private static final String ID_DEF_PERSP = "PerspectiveRegistry.DEFAULT_PERSP"; //$NON-NLS-1$
068:
069: private static final String PERSP = "_persp"; //$NON-NLS-1$
070:
071: private static final char SPACE_DELIMITER = ' ';
072:
073: private List perspectives = new ArrayList(10);
074:
075: // keep track of the perspectives the user has selected to remove or revert
076: private ArrayList perspToRemove = new ArrayList(5);
077:
078: private IPropertyChangeListener preferenceListener;
079:
080: /**
081: * Construct a new registry.
082: */
083: public PerspectiveRegistry() {
084: IExtensionTracker tracker = PlatformUI.getWorkbench()
085: .getExtensionTracker();
086: tracker.registerHandler(this , null);
087:
088: initializePreferenceChangeListener();
089: WorkbenchPlugin.getDefault().getPreferenceStore()
090: .addPropertyChangeListener(preferenceListener);
091:
092: }
093:
094: /**
095: * Initialize the preference change listener.
096: */
097: private void initializePreferenceChangeListener() {
098: preferenceListener = new IPropertyChangeListener() {
099: public void propertyChange(PropertyChangeEvent event) {
100: /*
101: * To ensure the that no custom perspective definitions are
102: * deleted when preferences are imported, merge old and new
103: * values
104: */
105: if (event.getProperty().endsWith(PERSP)) {
106: /* A Perspective is being changed, merge */
107: mergePerspectives(event);
108: } else if (event.getProperty().equals(
109: IPreferenceConstants.PERSPECTIVES)) {
110: /* The list of perpsectives is being changed, merge */
111: updatePreferenceList((IPreferenceStore) event
112: .getSource());
113: }
114: }
115:
116: private void mergePerspectives(PropertyChangeEvent event) {
117: IPreferenceStore store = (IPreferenceStore) event
118: .getSource();
119: if (event.getNewValue() == null
120: || event.getNewValue().equals("")) { //$NON-NLS-1$
121: /*
122: * Perpsective is being removed; if the user has deleted or
123: * reverted a custom perspective, let the change pass
124: * through. Otherwise, restore the custom perspective entry
125: */
126:
127: // Find the matching descriptor in the registry
128: IPerspectiveDescriptor[] perspectiveList = getPerspectives();
129: for (int i = 0; i < perspectiveList.length; i++) {
130: String id = perspectiveList[i].getId();
131: if (event.getProperty().equals(id + PERSP)) { // found
132: // descriptor
133: // see if the perspective has been flagged for
134: // reverting or deleting
135: if (!perspToRemove.contains(id)) { // restore
136: store.setValue(id + PERSP,
137: (String) event.getOldValue());
138: } else { // remove element from the list
139: perspToRemove.remove(id);
140: }
141: }
142: }
143: } else if ((event.getOldValue() == null || event
144: .getOldValue().equals(""))) { //$NON-NLS-1$
145:
146: /*
147: * New perspective is being added, update the
148: * perspectiveRegistry to contain the new custom perspective
149: */
150:
151: String id = event.getProperty().substring(0,
152: event.getProperty().lastIndexOf(PERSP));
153: if (findPerspectiveWithId(id) == null) {
154: // perspective does not already exist in registry, add
155: // it
156: PerspectiveDescriptor desc = new PerspectiveDescriptor(
157: null, null, null);
158: StringReader reader = new StringReader(
159: (String) event.getNewValue());
160: try {
161: XMLMemento memento = XMLMemento
162: .createReadRoot(reader);
163: desc.restoreState(memento);
164: addPerspective(desc);
165: } catch (WorkbenchException e) {
166: unableToLoadPerspective(e.getStatus());
167: }
168: }
169: }
170: /* If necessary, add to the list of perspectives */
171: updatePreferenceList(store);
172: }
173:
174: /*
175: * Update the list of perspectives from the registry. This will be
176: * called for each perspective during an import preferences, but is
177: * necessary to ensure the perspectives list stays consistent with
178: * the registry
179: */
180: private void updatePreferenceList(IPreferenceStore store) {
181: IPerspectiveDescriptor[] perspectiveList = getPerspectives();
182: StringBuffer perspBuffer = new StringBuffer();
183: for (int i = 0; i < perspectiveList.length; i++) {
184: PerspectiveDescriptor desc = (PerspectiveDescriptor) perspectiveList[i];
185: if (hasCustomDefinition(desc)) {
186: perspBuffer.append(desc.getId()).append(
187: SPACE_DELIMITER);
188: }
189: }
190: String newList = perspBuffer.toString().trim();
191: store.setValue(IPreferenceConstants.PERSPECTIVES,
192: newList);
193: }
194: };
195: }
196:
197: /**
198: * Adds a perspective. This is typically used by the reader.
199: *
200: * @param desc
201: */
202: public void addPerspective(PerspectiveDescriptor desc) {
203: if (desc == null) {
204: return;
205: }
206: add(desc);
207: }
208:
209: /**
210: * @param desc
211: */
212: private void add(PerspectiveDescriptor desc) {
213: perspectives.add(desc);
214: IConfigurationElement element = desc.getConfigElement();
215: if (element != null) {
216: PlatformUI.getWorkbench().getExtensionTracker()
217: .registerObject(element.getDeclaringExtension(),
218: desc, IExtensionTracker.REF_WEAK);
219: }
220: }
221:
222: /**
223: * Create a new perspective.
224: *
225: * @param label
226: * the name of the new descriptor
227: * @param originalDescriptor
228: * the descriptor on which to base the new descriptor
229: * @return a new perspective descriptor or <code>null</code> if the
230: * creation failed.
231: */
232: public PerspectiveDescriptor createPerspective(String label,
233: PerspectiveDescriptor originalDescriptor) {
234: // Sanity check to avoid invalid or duplicate labels.
235: if (!validateLabel(label)) {
236: return null;
237: }
238: if (findPerspectiveWithLabel(label) != null) {
239: return null;
240: }
241:
242: // Calculate ID.
243: String id = label.replace(' ', '_');
244: id = id.trim();
245:
246: // Create descriptor.
247: PerspectiveDescriptor desc = new PerspectiveDescriptor(id,
248: label, originalDescriptor);
249: add(desc);
250: return desc;
251: }
252:
253: /**
254: * Reverts a list of perspectives back to the plugin definition
255: *
256: * @param perspToRevert
257: */
258: public void revertPerspectives(ArrayList perspToRevert) {
259: // indicate that the user is removing these perspectives
260: for (int i = 0; i < perspToRevert.size(); i++) {
261: PerspectiveDescriptor desc = (PerspectiveDescriptor) perspToRevert
262: .get(i);
263: perspToRemove.add(desc.getId());
264: desc.revertToPredefined();
265: }
266: }
267:
268: /**
269: * Deletes a list of perspectives
270: *
271: * @param perspToDelete
272: */
273: public void deletePerspectives(ArrayList perspToDelete) {
274: for (int i = 0; i < perspToDelete.size(); i++) {
275: deletePerspective((IPerspectiveDescriptor) perspToDelete
276: .get(i));
277: }
278: }
279:
280: /**
281: * Delete a perspective. Has no effect if the perspective is defined in an
282: * extension.
283: *
284: * @param in
285: */
286: public void deletePerspective(IPerspectiveDescriptor in) {
287: PerspectiveDescriptor desc = (PerspectiveDescriptor) in;
288: // Don't delete predefined perspectives
289: if (!desc.isPredefined()) {
290: perspToRemove.add(desc.getId());
291: perspectives.remove(desc);
292: desc.deleteCustomDefinition();
293: verifyDefaultPerspective();
294: }
295: }
296:
297: /**
298: * Delete a perspective. This will remove perspectives defined in
299: * extensions.
300: *
301: * @param desc
302: * the perspective to delete
303: * @since 3.1
304: */
305: private void internalDeletePerspective(PerspectiveDescriptor desc) {
306: perspToRemove.add(desc.getId());
307: perspectives.remove(desc);
308: desc.deleteCustomDefinition();
309: verifyDefaultPerspective();
310: }
311:
312: /**
313: * Removes the custom definition of a perspective from the preference store
314: *
315: * @param desc
316: */
317: /* package */
318: void deleteCustomDefinition(PerspectiveDescriptor desc) {
319: // remove the entry from the preference store.
320: IPreferenceStore store = WorkbenchPlugin.getDefault()
321: .getPreferenceStore();
322:
323: /*
324: * To delete the perspective definition from the preference store, use
325: * the setToDefault method. Since no default is defined, this will
326: * remove the entry
327: */
328: store.setToDefault(desc.getId() + PERSP);
329:
330: }
331:
332: /**
333: * Method hasCustomDefinition.
334: *
335: * @param desc
336: */
337: /* package */
338: boolean hasCustomDefinition(PerspectiveDescriptor desc) {
339: IPreferenceStore store = WorkbenchPlugin.getDefault()
340: .getPreferenceStore();
341: return store.contains(desc.getId() + PERSP);
342: }
343:
344: /*
345: * (non-Javadoc)
346: *
347: * @see org.eclipse.ui.IPerspectiveRegistry#findPerspectiveWithId(java.lang.String)
348: */
349: public IPerspectiveDescriptor findPerspectiveWithId(String id) {
350: for (Iterator i = perspectives.iterator(); i.hasNext();) {
351: PerspectiveDescriptor desc = (PerspectiveDescriptor) i
352: .next();
353: if (desc.getId().equals(id)) {
354: return desc;
355: }
356: }
357:
358: return null;
359: }
360:
361: /*
362: * (non-Javadoc)
363: *
364: * @see org.eclipse.ui.IPerspectiveRegistry#findPerspectiveWithLabel(java.lang.String)
365: */
366: public IPerspectiveDescriptor findPerspectiveWithLabel(String label) {
367: for (Iterator i = perspectives.iterator(); i.hasNext();) {
368: PerspectiveDescriptor desc = (PerspectiveDescriptor) i
369: .next();
370: if (desc.getLabel().equals(label)) {
371: return desc;
372: }
373: }
374: return null;
375: }
376:
377: /**
378: * @see IPerspectiveRegistry#getDefaultPerspective()
379: */
380: public String getDefaultPerspective() {
381: return defaultPerspID;
382: }
383:
384: /*
385: * (non-Javadoc)
386: *
387: * @see org.eclipse.ui.IPerspectiveRegistry#getPerspectives()
388: */
389: public IPerspectiveDescriptor[] getPerspectives() {
390: return (IPerspectiveDescriptor[]) perspectives
391: .toArray(new IPerspectiveDescriptor[perspectives.size()]);
392: }
393:
394: /**
395: * Loads the registry.
396: */
397: public void load() {
398: // Load the registries.
399: loadPredefined();
400: loadCustom();
401:
402: // Get default perspective.
403: // Get it from the R1.0 dialog settings first. Fixes bug 17039
404: IDialogSettings dialogSettings = WorkbenchPlugin.getDefault()
405: .getDialogSettings();
406: String str = dialogSettings.get(ID_DEF_PERSP);
407: if (str != null && str.length() > 0) {
408: setDefaultPerspective(str);
409: dialogSettings.put(ID_DEF_PERSP, ""); //$NON-NLS-1$
410: }
411: verifyDefaultPerspective();
412: }
413:
414: /**
415: * Read children from the file system.
416: */
417: private void loadCustom() {
418: Reader reader = null;
419:
420: /* Get the entries from the Preference store */
421: IPreferenceStore store = WorkbenchPlugin.getDefault()
422: .getPreferenceStore();
423:
424: /* Get the space-delimited list of custom perspective ids */
425: String customPerspectives = store
426: .getString(IPreferenceConstants.PERSPECTIVES);
427: String[] perspectivesList = StringConverter
428: .asArray(customPerspectives);
429:
430: for (int i = 0; i < perspectivesList.length; i++) {
431: try {
432: String xmlString = store.getString(perspectivesList[i]
433: + PERSP);
434: if (xmlString != null && xmlString.length() != 0) {
435: reader = new StringReader(xmlString);
436: }
437:
438: // Restore the layout state.
439: XMLMemento memento = XMLMemento.createReadRoot(reader);
440: PerspectiveDescriptor newPersp = new PerspectiveDescriptor(
441: null, null, null);
442: newPersp.restoreState(memento);
443: String id = newPersp.getId();
444: IPerspectiveDescriptor oldPersp = findPerspectiveWithId(id);
445: if (oldPersp == null) {
446: add(newPersp);
447: }
448: reader.close();
449: } catch (IOException e) {
450: unableToLoadPerspective(null);
451: } catch (WorkbenchException e) {
452: unableToLoadPerspective(e.getStatus());
453: }
454: }
455:
456: // Get the entries from files, if any
457: // if -data @noDefault specified the state location may not be
458: // initialized
459: IPath path = WorkbenchPlugin.getDefault().getDataLocation();
460: if (path == null) {
461: return;
462: }
463:
464: File folder = path.toFile();
465:
466: if (folder.isDirectory()) {
467: File[] fileList = folder.listFiles();
468: int nSize = fileList.length;
469: for (int nX = 0; nX < nSize; nX++) {
470: File file = fileList[nX];
471: if (file.getName().endsWith(EXT)) {
472: // get the memento
473: InputStream stream = null;
474: try {
475: stream = new FileInputStream(file);
476: reader = new BufferedReader(
477: new InputStreamReader(stream, "utf-8")); //$NON-NLS-1$
478:
479: // Restore the layout state.
480: XMLMemento memento = XMLMemento
481: .createReadRoot(reader);
482: PerspectiveDescriptor newPersp = new PerspectiveDescriptor(
483: null, null, null);
484: newPersp.restoreState(memento);
485: IPerspectiveDescriptor oldPersp = findPerspectiveWithId(newPersp
486: .getId());
487: if (oldPersp == null) {
488: add(newPersp);
489: }
490:
491: // save to the preference store
492: saveCustomPersp(newPersp, memento);
493:
494: // delete the file
495: file.delete();
496:
497: reader.close();
498: stream.close();
499: } catch (IOException e) {
500: unableToLoadPerspective(null);
501: } catch (WorkbenchException e) {
502: unableToLoadPerspective(e.getStatus());
503: }
504: }
505: }
506: }
507: }
508:
509: /**
510: * @param status
511: */
512: private void unableToLoadPerspective(IStatus status) {
513: String msg = WorkbenchMessages.Perspective_errorLoadingState;
514: if (status == null) {
515: IStatus errStatus = new Status(IStatus.ERROR,
516: WorkbenchPlugin.PI_WORKBENCH, msg);
517: StatusManager.getManager().handle(errStatus,
518: StatusManager.SHOW);
519: } else {
520: IStatus errStatus = StatusUtil.newStatus(status, msg);
521: StatusManager.getManager().handle(errStatus,
522: StatusManager.SHOW);
523: }
524: }
525:
526: /**
527: * Saves a custom perspective definition to the preference store.
528: *
529: * @param desc
530: * the perspective
531: * @param memento
532: * the memento to save to
533: * @throws IOException
534: */
535: public void saveCustomPersp(PerspectiveDescriptor desc,
536: XMLMemento memento) throws IOException {
537:
538: IPreferenceStore store = WorkbenchPlugin.getDefault()
539: .getPreferenceStore();
540:
541: // Save it to the preference store.
542: Writer writer = new StringWriter();
543:
544: memento.save(writer);
545: writer.close();
546: store.setValue(desc.getId() + PERSP, writer.toString());
547:
548: }
549:
550: /**
551: * Gets the Custom perspective definition from the preference store.
552: *
553: * @param id
554: * the id of the perspective to find
555: * @return IMemento a memento containing the perspective description
556: *
557: * @throws WorkbenchException
558: * @throws IOException
559: */
560: public IMemento getCustomPersp(String id)
561: throws WorkbenchException, IOException {
562: Reader reader = null;
563:
564: IPreferenceStore store = WorkbenchPlugin.getDefault()
565: .getPreferenceStore();
566: String xmlString = store.getString(id + PERSP);
567: if (xmlString != null && xmlString.length() != 0) { // defined in store
568: reader = new StringReader(xmlString);
569: }
570: XMLMemento memento = XMLMemento.createReadRoot(reader);
571: reader.close();
572: return memento;
573: }
574:
575: /**
576: * Read children from the plugin registry.
577: */
578: private void loadPredefined() {
579: PerspectiveRegistryReader reader = new PerspectiveRegistryReader(
580: this );
581: reader.readPerspectives(Platform.getExtensionRegistry());
582: }
583:
584: /**
585: * @see IPerspectiveRegistry#setDefaultPerspective(String)
586: */
587: public void setDefaultPerspective(String id) {
588: IPerspectiveDescriptor desc = findPerspectiveWithId(id);
589: if (desc != null) {
590: defaultPerspID = id;
591: PrefUtil
592: .getAPIPreferenceStore()
593: .setValue(
594: IWorkbenchPreferenceConstants.DEFAULT_PERSPECTIVE_ID,
595: id);
596: }
597: }
598:
599: /**
600: * Return <code>true</code> if a label is valid. This checks only the
601: * given label in isolation. It does not check whether the given label is
602: * used by any existing perspectives.
603: *
604: * @param label
605: * the label to test
606: * @return whether the label is valid
607: */
608: public boolean validateLabel(String label) {
609: label = label.trim();
610: if (label.length() <= 0) {
611: return false;
612: }
613: return true;
614: }
615:
616: /**
617: * Verifies the id of the default perspective. If the default perspective is
618: * invalid use the workbench default.
619: */
620: private void verifyDefaultPerspective() {
621: // Step 1: Try current defPerspId value.
622: IPerspectiveDescriptor desc = null;
623: if (defaultPerspID != null) {
624: desc = findPerspectiveWithId(defaultPerspID);
625: }
626: if (desc != null) {
627: return;
628: }
629:
630: // Step 2. Read default value.
631: String str = PrefUtil.getAPIPreferenceStore().getString(
632: IWorkbenchPreferenceConstants.DEFAULT_PERSPECTIVE_ID);
633: if (str != null && str.length() > 0) {
634: desc = findPerspectiveWithId(str);
635: }
636: if (desc != null) {
637: defaultPerspID = str;
638: return;
639: }
640:
641: // Step 3. Use application-specific default
642: defaultPerspID = Workbench.getInstance()
643: .getDefaultPerspectiveId();
644: }
645:
646: /*
647: * (non-Javadoc)
648: *
649: * @see org.eclipse.ui.IPerspectiveRegistry#clonePerspective(java.lang.String,
650: * java.lang.String, org.eclipse.ui.IPerspectiveDescriptor)
651: */
652: public IPerspectiveDescriptor clonePerspective(String id,
653: String label, IPerspectiveDescriptor originalDescriptor) {
654:
655: // Check for invalid labels
656: if (label == null || !(label.trim().length() > 0)) {
657: throw new IllegalArgumentException();
658: }
659:
660: // Check for duplicates
661: IPerspectiveDescriptor desc = findPerspectiveWithId(id);
662: if (desc != null) {
663: throw new IllegalArgumentException();
664: }
665:
666: // Create descriptor.
667: desc = new PerspectiveDescriptor(id, label,
668: (PerspectiveDescriptor) originalDescriptor);
669: add((PerspectiveDescriptor) desc);
670: return desc;
671: }
672:
673: /*
674: * (non-Javadoc)
675: *
676: * @see org.eclipse.ui.IPerspectiveRegistry#revertPerspective(org.eclipse.ui.IPerspectiveDescriptor)
677: */
678: public void revertPerspective(IPerspectiveDescriptor perspToRevert) {
679: PerspectiveDescriptor desc = (PerspectiveDescriptor) perspToRevert;
680: perspToRemove.add(desc.getId());
681: desc.revertToPredefined();
682: }
683:
684: /**
685: * Dispose the receiver.
686: */
687: public void dispose() {
688: PlatformUI.getWorkbench().getExtensionTracker()
689: .unregisterHandler(this );
690: WorkbenchPlugin.getDefault().getPreferenceStore()
691: .removePropertyChangeListener(preferenceListener);
692: }
693:
694: /*
695: * (non-Javadoc)
696: *
697: * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#removeExtension(org.eclipse.core.runtime.IExtension,
698: * java.lang.Object[])
699: */
700: public void removeExtension(IExtension source, Object[] objects) {
701: for (int i = 0; i < objects.length; i++) {
702: if (objects[i] instanceof PerspectiveDescriptor) {
703: // close the perspective in all windows
704: IWorkbenchWindow[] windows = PlatformUI.getWorkbench()
705: .getWorkbenchWindows();
706: PerspectiveDescriptor desc = (PerspectiveDescriptor) objects[i];
707: for (int w = 0; w < windows.length; ++w) {
708: IWorkbenchWindow window = windows[w];
709: IWorkbenchPage[] pages = window.getPages();
710: for (int p = 0; p < pages.length; ++p) {
711: WorkbenchPage page = (WorkbenchPage) pages[p];
712: ClosePerspectiveAction.closePerspective(page,
713: page.findPerspective(desc));
714: }
715: }
716:
717: // ((Workbench)PlatformUI.getWorkbench()).getPerspectiveHistory().removeItem(desc);
718:
719: internalDeletePerspective(desc);
720: }
721:
722: }
723: }
724:
725: /*
726: * (non-Javadoc)
727: *
728: * @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#addExtension(org.eclipse.core.runtime.dynamicHelpers.IExtensionTracker,
729: * org.eclipse.core.runtime.IExtension)
730: */
731: public void addExtension(IExtensionTracker tracker,
732: IExtension addedExtension) {
733: IConfigurationElement[] addedElements = addedExtension
734: .getConfigurationElements();
735: for (int i = 0; i < addedElements.length; i++) {
736: PerspectiveRegistryReader reader = new PerspectiveRegistryReader(
737: this);
738: reader.readElement(addedElements[i]);
739: }
740: }
741: }
|