file structure dialog speed search match highlighting
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / commander / CommanderPanel.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.ide.commander;
18
19 import com.intellij.history.LocalHistory;
20 import com.intellij.history.LocalHistoryAction;
21 import com.intellij.ide.CopyPasteDelegator;
22 import com.intellij.ide.DeleteProvider;
23 import com.intellij.ide.IdeBundle;
24 import com.intellij.ide.IdeView;
25 import com.intellij.ide.projectView.ProjectViewNode;
26 import com.intellij.ide.projectView.impl.ModuleGroup;
27 import com.intellij.ide.projectView.impl.ProjectAbstractTreeStructureBase;
28 import com.intellij.ide.projectView.impl.nodes.LibraryGroupElement;
29 import com.intellij.ide.projectView.impl.nodes.NamedLibraryElement;
30 import com.intellij.ide.structureView.StructureViewTreeElement;
31 import com.intellij.ide.ui.customization.CustomActionsSchema;
32 import com.intellij.ide.util.DeleteHandler;
33 import com.intellij.ide.util.DirectoryChooserUtil;
34 import com.intellij.ide.util.EditSourceUtil;
35 import com.intellij.ide.util.EditorHelper;
36 import com.intellij.ide.util.treeView.AbstractTreeNode;
37 import com.intellij.openapi.actionSystem.*;
38 import com.intellij.openapi.application.ApplicationManager;
39 import com.intellij.openapi.application.ModalityState;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.module.Module;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.util.SystemInfo;
44 import com.intellij.openapi.wm.ToolWindowManager;
45 import com.intellij.pom.Navigatable;
46 import com.intellij.psi.PsiDirectory;
47 import com.intellij.psi.PsiElement;
48 import com.intellij.psi.util.PsiUtilBase;
49 import com.intellij.ui.*;
50 import com.intellij.ui.components.JBList;
51 import com.intellij.util.ArrayUtil;
52 import com.intellij.util.containers.ContainerUtil;
53 import com.intellij.util.ui.UIUtil;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import javax.swing.border.BevelBorder;
60 import java.awt.*;
61 import java.awt.event.*;
62 import java.util.ArrayList;
63 import java.util.List;
64
65 /**
66  * @author Eugene Belyaev
67  */
68 public class CommanderPanel extends JPanel {
69   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.commander.CommanderPanel");
70
71   private static final Color DARK_BLUE = new Color(55, 85, 134);
72   private static final Color DARK_BLUE_BRIGHTER = new Color(58, 92, 149);
73   private static final Color DARK_BLUE_DARKER = new Color(38, 64, 106);
74
75   private Project myProject;
76   private AbstractListBuilder myBuilder;
77   private JPanel myTitlePanel;
78   private JLabel myParentTitle;
79   protected final JBList myList;
80   private final MyModel myModel;
81
82   private CopyPasteDelegator myCopyPasteDelegator;
83   protected final ListSpeedSearch myListSpeedSearch;
84   private final IdeView myIdeView = new MyIdeView();
85   private final MyDeleteElementProvider myDeleteElementProvider = new MyDeleteElementProvider();
86   @NonNls
87   private static final String ACTION_DRILL_DOWN = "DrillDown";
88   @NonNls
89   private static final String ACTION_GO_UP = "GoUp";
90   private ProjectAbstractTreeStructureBase myProjectTreeStructure;
91   private boolean myActive = true;
92   private final List<CommanderHistoryListener> myHistoryListeners = ContainerUtil.createEmptyCOWList();
93   private boolean myMoveFocus = false;
94   private boolean myEnableSearchHighlighting;
95
96   public CommanderPanel(final Project project, final boolean enablePopupMenu, final boolean enableSearchHighlighting) {
97     super(new BorderLayout());
98     myProject = project;
99     myEnableSearchHighlighting = enableSearchHighlighting;
100     myModel = new MyModel();
101     myList = new JBList(myModel);
102     myList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
103
104     if (enablePopupMenu) {
105       myCopyPasteDelegator = new CopyPasteDelegator(myProject, myList) {
106         @NotNull
107         protected PsiElement[] getSelectedElements() {
108           return CommanderPanel.this.getSelectedElements();
109         }
110       };
111     }
112
113     myListSpeedSearch = new ListSpeedSearch(myList);
114
115     ListScrollingUtil.installActions(myList);
116
117     myList.registerKeyboardAction(new ActionListener() {
118       public void actionPerformed(final ActionEvent e) {
119         if (myBuilder == null) return;
120         myBuilder.buildRoot();
121       }
122     }, KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SLASH, SystemInfo.isMac ? KeyEvent.META_MASK : KeyEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
123
124     myList.getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), ACTION_DRILL_DOWN);
125     myList.getInputMap(WHEN_FOCUSED)
126       .put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, SystemInfo.isMac ? KeyEvent.META_MASK : KeyEvent.CTRL_MASK), ACTION_DRILL_DOWN);
127     myList.getActionMap().put(ACTION_DRILL_DOWN, new AbstractAction() {
128       public void actionPerformed(final ActionEvent e) {
129         drillDown();
130       }
131     });
132     myList.addMouseListener(new MouseAdapter() {
133       public void mouseClicked(final MouseEvent e) {
134         if (e.getClickCount() == 2) {
135           drillDown();
136         }
137       }
138     });
139     myList.getInputMap(WHEN_FOCUSED)
140       .put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, SystemInfo.isMac ? KeyEvent.META_MASK : KeyEvent.CTRL_MASK), ACTION_GO_UP);
141     myList.getInputMap(WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), ACTION_GO_UP);
142     myList.getActionMap().put(ACTION_GO_UP, new AbstractAction() {
143       public void actionPerformed(final ActionEvent e) {
144         goUp();
145       }
146     });
147
148     myList.getActionMap().put("selectAll", new AbstractAction() {
149       public void actionPerformed(final ActionEvent e) {
150       }
151     });
152
153
154     if (enablePopupMenu) {
155       myList.addMouseListener(new PopupHandler() {
156         public void invokePopup(final Component comp, final int x, final int y) {
157           CommanderPanel.this.invokePopup(comp, x, y);
158         }
159       });
160     }
161
162     myList.addFocusListener(new FocusAdapter() {
163       public void focusGained(final FocusEvent e) {
164         setActive(true);
165       }
166
167       public void focusLost(final FocusEvent e) {
168         setActive(false);
169       }
170     });
171
172   }
173
174   public boolean isEnableSearchHighlighting() {
175     return myEnableSearchHighlighting;
176   }
177
178   public ListSpeedSearch getListSpeedSearch() {
179     return myListSpeedSearch;
180   }
181
182   public void addHistoryListener(CommanderHistoryListener listener) {
183     myHistoryListeners.add(listener);
184   }
185
186   public void removeHistoryListener(CommanderHistoryListener listener) {
187     myHistoryListeners.remove(listener);
188   }
189
190   private void updateHistory(boolean elementExpanded) {
191     for(CommanderHistoryListener listener: myHistoryListeners) {
192       listener.historyChanged(getSelectedElement(), elementExpanded);
193     }
194   }
195
196   public final JList getList() {
197     return myList;
198   }
199
200   public final AbstractListBuilder.Model getModel() {
201     return myModel;
202   }
203
204   public void setMoveFocus(final boolean moveFocus) {
205     myMoveFocus = moveFocus;
206   }
207
208   public void goUp() {
209     if (myBuilder == null) {
210       return;
211     }
212     updateHistory(true);
213     myBuilder.goUp();
214     updateHistory(false);
215   }
216
217   public void drillDown() {
218     if (topElementIsSelected()) {
219       goUp();
220       return;
221     }
222
223     if (getSelectedValue() == null) {
224       return;
225     }
226
227     final AbstractTreeNode element = getSelectedNode();
228     if (element.getChildren().size() == 0) {
229       if (!shouldDrillDownOnEmptyElement(element)) {
230         navigateSelectedElement();
231         return;
232       }
233     }
234
235     if (myBuilder == null) {
236       return;
237     }
238     updateHistory(false);
239     myBuilder.drillDown();
240     updateHistory(true);
241   }
242
243   public boolean navigateSelectedElement() {
244     final AbstractTreeNode selectedNode = getSelectedNode();
245     if (selectedNode != null) {
246       if (selectedNode.canNavigateToSource()) {
247         selectedNode.navigate(true);
248         return true;
249       }
250     }
251     return false;
252   }
253
254   protected boolean shouldDrillDownOnEmptyElement(final AbstractTreeNode node) {
255     return node instanceof ProjectViewNode && ((ProjectViewNode)node).shouldDrillDownOnEmptyElement();
256   }
257
258   private boolean topElementIsSelected() {
259     int[] selectedIndices = myList.getSelectedIndices();
260     return selectedIndices.length == 1 && selectedIndices[0] == 0 && myModel.getElementAt(selectedIndices[0]) instanceof TopLevelNode;
261   }
262
263   public final void setBuilder(final AbstractListBuilder builder) {
264     myBuilder = builder;
265     removeAll();
266
267     myTitlePanel = new JPanel(new BorderLayout());
268     myTitlePanel.setBackground(UIUtil.getControlColor());
269     myTitlePanel.setOpaque(true);
270
271     myParentTitle = new MyTitleLabel(myTitlePanel);
272     myParentTitle.setText(" ");
273     myParentTitle.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD));
274     myParentTitle.setForeground(Color.black);
275     myParentTitle.setUI(new RightAlignedLabelUI());
276     final JPanel panel1 = new JPanel(new BorderLayout());
277     panel1.setOpaque(false);
278     panel1.add(Box.createHorizontalStrut(10), BorderLayout.WEST);
279     panel1.add(myParentTitle, BorderLayout.CENTER);
280     myTitlePanel.add(panel1, BorderLayout.CENTER);
281
282     add(myTitlePanel, BorderLayout.NORTH);
283     final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myList);
284     scrollPane.setBorder(null);
285     scrollPane.getVerticalScrollBar().setFocusable(false); // otherwise the scrollbar steals focus and panel switching with tab is broken 
286     scrollPane.getHorizontalScrollBar().setFocusable(false);
287     add(scrollPane, BorderLayout.CENTER);
288
289     myBuilder.setParentTitle(myParentTitle);
290
291     // TODO[vova,anton] it seems that the code below performs double focus request. Is it OK?
292     myTitlePanel.addMouseListener(new MouseAdapter() {
293       public void mouseClicked(final MouseEvent e) {
294         myList.requestFocus();
295       }
296
297       public void mousePressed(final MouseEvent e) {
298         myList.requestFocus();
299       }
300     });
301   }
302
303   public final AbstractListBuilder getBuilder() {
304     return myBuilder;
305   }
306
307   public final PsiElement getSelectedElement() {
308     Object value = getValueAtIndex(getSelectedNode());
309     return (PsiElement)(value instanceof PsiElement ? value : null);
310   }
311
312   public final PsiElement getSelectedElement(int index) {
313     Object elementAtIndex = myModel.getElementAt(index);
314     Object value = getValueAtIndex(elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode)elementAtIndex : null);
315     return (PsiElement)(value instanceof PsiElement ? value : null);
316   }
317
318   public AbstractTreeNode getSelectedNode() {
319     if (myBuilder == null) return null;
320     final int[] indices = myList.getSelectedIndices();
321     if (indices.length != 1) return null;
322     int index = indices[0];
323     if (index >= myModel.getSize()) return null;
324     Object elementAtIndex = myModel.getElementAt(index);
325     return elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode)elementAtIndex : null;
326   }
327
328   private ArrayList<AbstractTreeNode> getSelectedNodes() {
329     if (myBuilder == null) return null;
330     final int[] indices = myList.getSelectedIndices();
331     ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>();
332     for (int index : indices) {
333       if (index >= myModel.getSize()) continue;
334       Object elementAtIndex = myModel.getElementAt(index);
335       AbstractTreeNode node = elementAtIndex instanceof AbstractTreeNode ? (AbstractTreeNode)elementAtIndex : null;
336       if (node != null) {
337         result.add(node);
338       }
339     }
340     return result;
341   }
342
343
344   public Object getSelectedValue() {
345     return getValueAtIndex(getSelectedNode());
346   }
347
348   private PsiElement[] getSelectedElements() {
349     if (myBuilder == null) return PsiElement.EMPTY_ARRAY;
350     final int[] indices = myList.getSelectedIndices();
351
352     final ArrayList<PsiElement> elements = new ArrayList<PsiElement>();
353     for (int index : indices) {
354       final PsiElement element = getSelectedElement(index);
355       if (element != null) {
356         elements.add(element);
357       }
358     }
359
360     return elements.toArray(new PsiElement[elements.size()]);
361   }
362
363   private static Object getValueAtIndex(AbstractTreeNode node) {
364     if (node == null) return null;
365     Object value = node.getValue();
366     if (value instanceof StructureViewTreeElement) {
367       return ((StructureViewTreeElement)value).getValue();
368     }
369     return value;
370   }
371
372   public final void setActive(final boolean active) {
373     myActive = active;
374     if (active) {
375       myTitlePanel.setBackground(DARK_BLUE);
376       myTitlePanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED, DARK_BLUE_BRIGHTER, DARK_BLUE_DARKER));
377       myParentTitle.setForeground(Color.white);
378     }
379     else {
380       final Color color = UIUtil.getPanelBackground();
381       LOG.assertTrue(color != null);
382       myTitlePanel.setBackground(color);
383       myTitlePanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED, color.brighter(), color.darker()));
384       myParentTitle.setForeground(Color.black);
385     }
386     final int[] selectedIndices = myList.getSelectedIndices();
387     if (selectedIndices.length == 0 && myList.getModel().getSize() > 0) {
388       myList.setSelectedIndex(0);
389       if (!myList.hasFocus()) {
390         myList.requestFocus();
391       }
392     }
393     else if (myList.getModel().getSize() > 0) {
394       // need this to generate SelectionChanged events so that listeners, added by Commander, will be notified
395       myList.setSelectedIndices(selectedIndices);
396     }
397   }
398
399   public boolean isActive() {
400     return myActive;
401   }
402
403   private void invokePopup(final Component c, final int x, final int y) {
404     if (myBuilder == null) return;
405
406     if (myList.getSelectedIndices().length <= 1) {
407       final int popupIndex = myList.locationToIndex(new Point(x, y));
408       if (popupIndex >= 0) {
409         myList.setSelectedIndex(popupIndex);
410         myList.requestFocus();
411       }
412     }
413
414     final ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_COMMANDER_POPUP);
415     final ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.COMMANDER_POPUP, group);
416     popupMenu.getComponent().show(c, x, y);
417   }
418
419   public final void dispose() {
420     if (myBuilder != null) {
421       myBuilder.dispose();
422       myBuilder = null;
423     }
424     myProject = null;
425   }
426
427   public final void setTitlePanelVisible(final boolean flag) {
428     myTitlePanel.setVisible(flag);
429   }
430
431   public final Object getDataImpl(final String dataId) {
432     if (myBuilder == null) return null;
433     final Object selectedValue = getSelectedValue();
434     if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
435       final PsiElement selectedElement = getSelectedElement();
436       return selectedElement != null && selectedElement.isValid() ? selectedElement : null;
437     }
438     if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
439       return filterInvalidElements(getSelectedElements());
440     }
441     else if (LangDataKeys.PASTE_TARGET_PSI_ELEMENT.is(dataId)) {
442       final AbstractTreeNode parentNode = myBuilder.getParentNode();
443       final Object element = parentNode != null ? parentNode.getValue() : null;
444       return element instanceof PsiElement && ((PsiElement)element).isValid() ? element : null;
445     }
446     else if (PlatformDataKeys.NAVIGATABLE_ARRAY.is(dataId)) {
447       return getNavigatables();
448     }
449     else if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
450       return myCopyPasteDelegator != null ? myCopyPasteDelegator.getCopyProvider() : null;
451     }
452     else if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
453       return myCopyPasteDelegator != null ? myCopyPasteDelegator.getCutProvider() : null;
454     }
455     else if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
456       return myCopyPasteDelegator != null ? myCopyPasteDelegator.getPasteProvider() : null;
457     }
458     else if (LangDataKeys.IDE_VIEW.is(dataId)) {
459       return myIdeView;
460     }
461     else if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
462       return myDeleteElementProvider;
463     }
464     else if (LangDataKeys.MODULE.is(dataId)) {
465       return selectedValue instanceof Module ? selectedValue : null;
466     }
467     else if (ModuleGroup.ARRAY_DATA_KEY.is(dataId)) {
468       return selectedValue instanceof ModuleGroup ? new ModuleGroup[]{(ModuleGroup)selectedValue} : null;
469     }
470     else if (LibraryGroupElement.ARRAY_DATA_KEY.is(dataId)) {
471       return selectedValue instanceof LibraryGroupElement ? new LibraryGroupElement[]{(LibraryGroupElement)selectedValue} : null;
472     }
473     else if (NamedLibraryElement.ARRAY_DATA_KEY.is(dataId)) {
474       return selectedValue instanceof NamedLibraryElement ? new NamedLibraryElement[]{(NamedLibraryElement)selectedValue} : null;
475     }
476
477     if (myProjectTreeStructure != null) {
478       return myProjectTreeStructure.getDataFromProviders(getSelectedNodes(), dataId);
479     }
480
481     return null;
482   }
483
484   private Navigatable[] getNavigatables() {
485     if (myBuilder == null) return null;
486     final int[] indices = myList.getSelectedIndices();
487     if (indices == null || indices.length == 0) return null;
488
489     final ArrayList<Navigatable> elements = new ArrayList<Navigatable>();
490     for (int index : indices) {
491       final Object element = myModel.getElementAt(index);
492       if (element instanceof AbstractTreeNode) {
493         elements.add((Navigatable)element);
494       }
495     }
496
497     return elements.toArray(new Navigatable[elements.size()]);
498
499   }
500
501   @Nullable
502   private static PsiElement[] filterInvalidElements(final PsiElement[] elements) {
503     if (elements == null || elements.length == 0) {
504       return null;
505     }
506     final List<PsiElement> validElements = new ArrayList<PsiElement>(elements.length);
507     for (final PsiElement element : elements) {
508       if (element.isValid()) {
509         validElements.add(element);
510       }
511     }
512     return validElements.size() == elements.length ? elements : validElements.toArray(new PsiElement[validElements.size()]);
513   }
514
515   protected final Navigatable createEditSourceDescriptor() {
516     return EditSourceUtil.getDescriptor(getSelectedElement());
517   }
518
519   public void setProjectTreeStructure(final ProjectAbstractTreeStructureBase projectTreeStructure) {
520     myProjectTreeStructure = projectTreeStructure;
521   }
522
523   private static final class MyTitleLabel extends JLabel {
524     private final JPanel myPanel;
525
526     public MyTitleLabel(final JPanel panel) {
527       myPanel = panel;
528     }
529
530     public void setText(String text) {
531       if (text == null || text.length() == 0) {
532         text = " ";
533       }
534       super.setText(text);
535       if (myPanel != null) {
536         myPanel.setToolTipText(text.trim().length() == 0 ? null : text);
537       }
538     }
539   }
540
541   private final class MyDeleteElementProvider implements DeleteProvider {
542     public void deleteElement(final DataContext dataContext) {
543       LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
544       try {
545         final PsiElement[] elements = getSelectedElements();
546         DeleteHandler.deletePsiElement(elements, myProject);
547       }
548       finally {
549         a.finish();
550       }
551     }
552
553     public boolean canDeleteElement(final DataContext dataContext) {
554       final PsiElement[] elements = getSelectedElements();
555       return DeleteHandler.shouldEnableDeleteAction(elements);
556     }
557   }
558
559   private final class MyIdeView implements IdeView {
560     public void selectElement(final PsiElement element) {
561       final boolean isDirectory = element instanceof PsiDirectory;
562       if (!isDirectory) {
563         EditorHelper.openInEditor(element);
564       }
565       ApplicationManager.getApplication().invokeLater(new Runnable() {
566         public void run() {
567           myBuilder.selectElement(element, PsiUtilBase.getVirtualFile(element));
568           if (!isDirectory) {
569             ApplicationManager.getApplication().invokeLater(new Runnable() {
570               public void run() {
571                 if (myMoveFocus) {
572                   ToolWindowManager.getInstance(myProject).activateEditorComponent();
573                 }
574               }
575             });
576           }
577         }
578       }, ModalityState.NON_MODAL);
579     }
580
581     private PsiDirectory getDirectory() {
582       if (myBuilder == null) return null;
583       final Object parentElement = myBuilder.getParentNode();
584       if (parentElement instanceof AbstractTreeNode) {
585         final AbstractTreeNode parentNode = (AbstractTreeNode)parentElement;
586         if (!(parentNode.getValue() instanceof PsiDirectory)) return null;
587         return (PsiDirectory)parentNode.getValue();
588       }
589       else {
590         return null;
591       }
592     }
593
594     public PsiDirectory[] getDirectories() {
595       PsiDirectory directory = getDirectory();
596       return directory == null ? PsiDirectory.EMPTY_ARRAY : new PsiDirectory[]{directory};
597     }
598
599     public PsiDirectory getOrChooseDirectory() {
600       return DirectoryChooserUtil.getOrChooseDirectory(this);
601     }
602   }
603   
604   private static final class MyModel extends AbstractListBuilder.Model {
605     final List myElements = new ArrayList();
606     public void removeAllElements() {
607       int index1 = myElements.size()-1;
608       myElements.clear();
609       if (index1 >= 0) {
610           fireIntervalRemoved(this, 0, index1);
611       }
612     }
613
614     public void addElement(final Object obj) {
615       int index = myElements.size();
616       myElements.add(obj);
617       fireIntervalAdded(this, index, index);
618     }
619
620     public void replaceElements(final List newElements) {
621       removeAllElements();
622       myElements.addAll(newElements);
623       fireIntervalAdded(this, 0, newElements.size());
624     }
625
626     public Object[] toArray() {
627       return ArrayUtil.toObjectArray(myElements);
628     }
629
630     public int indexOf(final Object o) {
631       return myElements.indexOf(o);
632     }
633
634     public int getSize() {
635       return myElements.size();
636     }
637
638     public Object getElementAt(final int index) {
639       return myElements.get(index);
640     }
641   }
642 }