001    /*
002     * file FolderView.java
003     * 
004     * Licensed Materials - Property of IBM
005     * Restricted Materials of IBM - you are allowed to copy, modify and 
006     * redistribute this file as part of any program that interfaces with 
007     * IBM Rational CM API.
008     *
009     * com.ibm.rational.teamapi.scout.FolderView
010     *
011     * © Copyright IBM Corporation 2004, 2008.  All Rights Reserved.
012     * Note to U.S. Government Users Restricted Rights:  Use, duplication or 
013     * disclosure restricted by GSA ADP  Schedule Contract with IBM Corp.
014     */
015    package com.ibm.rational.teamapi.scout;
016    
017    import java.util.Iterator;
018    import java.util.List;
019    
020    import javax.wvcm.PropertyRequestItem.PropertyRequest;
021    
022    import org.eclipse.jface.action.Action;
023    import org.eclipse.jface.action.IMenuListener;
024    import org.eclipse.jface.action.IMenuManager;
025    import org.eclipse.jface.action.IToolBarManager;
026    import org.eclipse.jface.action.MenuManager;
027    import org.eclipse.jface.dialogs.InputDialog;
028    import org.eclipse.jface.dialogs.MessageDialog;
029    import org.eclipse.jface.viewers.ISelectionChangedListener;
030    import org.eclipse.jface.viewers.IStructuredSelection;
031    import org.eclipse.jface.viewers.SelectionChangedEvent;
032    import org.eclipse.jface.viewers.TreeViewer;
033    import org.eclipse.jface.viewers.ViewerFilter;
034    import org.eclipse.jface.viewers.ViewerSorter;
035    import org.eclipse.swt.SWT;
036    import org.eclipse.swt.layout.GridData;
037    import org.eclipse.swt.layout.GridLayout;
038    import org.eclipse.swt.widgets.Composite;
039    import org.eclipse.swt.widgets.Shell;
040    import org.eclipse.swt.widgets.Text;
041    import org.eclipse.ui.IWorkbenchPart;
042    import org.eclipse.ui.part.ViewPart;
043    
044    import com.ibm.rational.wvcm.stp.StpProvider;
045    import com.ibm.rational.wvcm.stp.cc.CcRegistryRegion;
046    
047    
048    /**
049     * A CM API Folder hierarchy viewer based on the Eclipse TreeViewer control. The
050     * hierarchy is determined by the CHILD_BINDING_LIST property of each folder in
051     * the view. Each element of the tree view is a ProxyElement, which wraps a CM
052     * API proxy and represents the resource referenced by that proxy. Each element
053     * implements IPropertySource, so the properties of the CM API resource can be
054     * viewed in the Eclipse property viewer simply by selecting the element in the
055     * tree view.
056     */
057    public class FolderView
058        extends ViewPart
059    {
060        /**
061         * The constructor.
062         */
063        public FolderView() {}
064    
065        /**
066         * @see  IWorkbenchPart#createPartControl(Composite)
067         *
068         * @param  parent  The Composite that is the parent of this View.
069         */
070        public void createPartControl(Composite parent)
071        {
072            /*
073             * Create a grid layout object so the text and tree viewer layout is
074             * under our control.
075             */
076            GridLayout layout = new GridLayout();
077    
078            layout.numColumns = 1;
079            layout.verticalSpacing = 2;
080            layout.marginWidth = 0;
081            layout.marginHeight = 2;
082            parent.setLayout(layout);
083    
084            /*
085             * Create a "label" in which to display information about the selected
086             * resource. It's a text field instead of a label so user can copy-paste
087             * out of it.
088             */
089            m_text = new Text(parent, SWT.READ_ONLY | SWT.SINGLE | SWT.BORDER);
090    
091            // layout the text field above the tree viewer
092            GridData layoutData = new GridData();
093    
094            layoutData.grabExcessHorizontalSpace = true;
095            layoutData.horizontalAlignment = GridData.FILL;
096            m_text.setLayoutData(layoutData);
097    
098            // Create the tree viewer as a child of the composite parent
099            m_treeViewer = new TreeViewer(parent);
100            m_treeViewer.setContentProvider(new FolderContentProvider());
101            m_treeViewer.setLabelProvider(new FolderLabelProvider());
102            m_treeViewer.setUseHashlookup(true);
103    
104            // layout the tree viewer below the text field
105            layoutData = new GridData();
106            layoutData.grabExcessHorizontalSpace = true;
107            layoutData.grabExcessVerticalSpace = true;
108            layoutData.horizontalAlignment = GridData.FILL;
109            layoutData.verticalAlignment = GridData.FILL;
110            m_treeViewer.getControl().setLayoutData(layoutData);
111    
112            // Create menu, tool bars, filters, sorters.
113            createFiltersAndSorters();
114            createActions();
115            createMenus();
116            createToolbar();
117            hookListeners();
118    
119            m_treeViewer.setInput(getInitalInput(parent.getShell()));
120            getSite().setSelectionProvider(m_treeViewer);
121        }
122    
123        /**
124         * Creates the filtering and sort objects used to filter and sort the
125         * display
126         */
127        protected void createFiltersAndSorters()
128        {
129            m_atLeastThreeFilter = new NonEmptyFolderFilter();
130            m_onlyFoldersFilter = new FolderOnlyFilter();
131            m_foldersFirstSorter = new FoldersFirstSorter();
132            m_noTypeSorter = new NoTypeSorter();
133        }
134    
135        /**
136         * Creates selection-changed listener, which keeps the label field
137         * up-to-date with the selected elements of the tree view.
138         */
139        protected void hookListeners()
140        {
141            m_treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
142                    public void selectionChanged(SelectionChangedEvent event)
143                    {
144                        // if the selection is empty clear the label
145                        if (event.getSelection().isEmpty()) {
146                            m_text.setText("");
147    
148                            return;
149                        }
150    
151                        if (event.getSelection() instanceof IStructuredSelection) {
152                            IStructuredSelection selection =
153                                (IStructuredSelection)event.getSelection();
154                            StringBuffer         toShow = new StringBuffer();
155    
156                            for (Iterator iterator = selection.iterator();
157                                     iterator.hasNext();) {
158                                if (toShow.length() > 0)
159                                    toShow.append(", ");
160    
161                                toShow.append(((ProxyElement)iterator.next())
162                                                  .getSelector());
163                            }
164    
165                            m_text.setText(toShow.toString());
166                        }
167                    }
168                });
169        }
170    
171        /**
172         * Creates the actions used by this view
173         */
174        protected void createActions()
175        {
176            m_onlyFoldersAction = new Action("Only Folders") {
177                    public void run()
178                    {
179                        updateFilter(m_onlyFoldersAction);
180                    }
181                };
182            m_onlyFoldersAction.setChecked(false);
183    
184            m_nonEmptyFolders = new Action("No Empty Folders") {
185                    public void run()
186                    {
187                        updateFilter(m_nonEmptyFolders);
188                    }
189                };
190            m_nonEmptyFolders.setChecked(false);
191    
192            m_foldersFirstAction = new Action("Folders First") {
193                    public void run()
194                    {
195                        updateSorter(m_foldersFirstAction);
196                    }
197                };
198            m_foldersFirstAction.setChecked(false);
199    
200            m_noTypeAction = new Action("Ignore Types") {
201                    public void run()
202                    {
203                        updateSorter(m_noTypeAction);
204                    }
205                };
206            m_noTypeAction.setChecked(false);
207    
208            m_addRootAction = new Action("Add Root Folder") {
209                    public void run()
210                    {
211                        addNewRoot();
212                    }
213                };
214            m_addRootAction.setToolTipText("Add a new root folder to this view");
215            m_addRootAction.setImageDescriptor(ScoutPlugin.getImageDescriptor("addRoot.gif"));
216    
217            m_removeAction = new Action("Elide") {
218                    public void run()
219                    {
220                        removeRoot();
221                    }
222                };
223            m_removeAction.setToolTipText("Remove selected root folder(s) from this view");
224            m_removeAction.setImageDescriptor(ScoutPlugin.getImageDescriptor("remove.gif"));
225    
226            m_refreshAction = new Action("Refresh") {
227                    public void run()
228                    {
229                        refresh();
230                    }
231                };
232            m_refreshAction.setToolTipText("Recompute bound member list of selected folder(s)");
233            m_refreshAction.setImageDescriptor(ScoutPlugin.getImageDescriptor("refresh.gif"));
234        }
235    
236        /**
237         * Raises a dialog requesting the selector for a folder resource to be added
238         * to the tree view. Unless canceled, by the user, the new folder is added
239         * as a root of the display.
240         */
241        protected void addNewRoot()
242        {
243            InputDialog dialog =
244                new InputDialog(null, "Add Root Folder",
245                                "Enter the Location for a New Root folder."
246                                + " Use 'views', 'vobs', or 'dbsets' "
247                                + " to add repositories known to server",
248                                "<domain>.<namespace>:<name>@<repository>", null);
249    
250            if (dialog.open() == InputDialog.OK) {
251                String root = dialog.getValue(); 
252                StpProvider provider = m_root.getProvider();
253                
254                try {
255                    if (m_askForServerUrl) {
256                        dialog =
257                            new InputDialog(null,
258                                            "CM Server URL",
259                                            "Enter the URL for your CM Server.",
260                                            "http://qwin115:9082/TeamWeb/services/Team",
261                                            null);
262    
263                        if (dialog.open() == InputDialog.OK)
264                            provider.setServerUrl(dialog.getValue());
265                        
266                        m_askForServerUrl = false;
267                    }
268                    
269                    if (root.equals("dbsets")) {
270                        if (addRoots(provider.cqProvider().doGetDbSetList(null)))
271                            m_treeViewer.refresh(m_root);
272                    } else if (root.equals("vobs")) {
273                        CcRegistryRegion rr =
274                            provider.ccProvider().doGetDefaultCcRegistryRegion
275                                (new PropertyRequest(CcRegistryRegion.VOB_TAG_LIST));
276    
277                        if (addRoots(rr.getVobTagList()))
278                            m_treeViewer.refresh(m_root);
279                    } else if (root.equals("views")) {
280                        CcRegistryRegion rr =
281                            provider.ccProvider().doGetDefaultCcRegistryRegion
282                                (new PropertyRequest(CcRegistryRegion.VIEW_TAG_LIST));
283    
284                        if (addRoots(rr.getViewTagList()))
285                            m_treeViewer.refresh(m_root);
286                    } else if (addRoot(root))
287                        m_treeViewer.refresh(m_root);
288                } catch (Throwable e) {
289                    MessageDialog.openError(null, 
290                                            "Can't Add " + root,
291                                            e.getClass().getName() + ": "
292                                            + e.getLocalizedMessage());          
293                    e.printStackTrace();
294                }
295            }
296        }
297    
298        /**
299         * Removes the selected object(s) from the tree view. Only root folders at
300         * the top level may be removed since all other tree view entries are
301         * derived objects.
302         */
303        protected void removeRoot()
304        {
305            boolean removed = false;
306    
307            try {
308                IStructuredSelection selection =
309                    (IStructuredSelection)m_treeViewer.getSelection();
310    
311                for (Iterator iterator = selection.iterator(); iterator.hasNext();)
312                    removed |= m_root.removeChild(iterator.next());
313            } catch (Throwable ex) {
314                MessageDialog.openError(null, "Remove Root Failed", ex.getClass()
315                                            .getName() + ": "
316                                        + ex.getLocalizedMessage());
317            }
318    
319            if (removed)
320                m_treeViewer.refresh(m_root);
321        }
322    
323        /**
324         * Refreshes the child set of each selected resource. The bound member list
325         * of each selected child is reread from the repository.
326         */
327        protected void refresh()
328        {
329            try {
330                IStructuredSelection selection =
331                    (IStructuredSelection)m_treeViewer.getSelection();
332    
333                for (Iterator iterator = selection.iterator();
334                        iterator.hasNext();) {
335                    ProxyElement elem = (ProxyElement)iterator.next();
336    
337                    m_treeViewer.collapseToLevel(elem, TreeViewer.ALL_LEVELS);
338                    elem.refresh();
339                    m_treeViewer.expandToLevel(elem, 1);
340                }
341            } catch (Throwable ex) {
342                MessageDialog.openError(null, "Remove Root Failed", ex.getClass()
343                                            .getName() + ": "
344                                        + ex.getLocalizedMessage());
345            }
346        }
347    
348        /**
349         * Creates the menu structures needed for this view.
350         */
351        protected void createMenus()
352        {
353            IMenuManager rootMenuManager =
354                getViewSite().getActionBars().getMenuManager();
355    
356            rootMenuManager.setRemoveAllWhenShown(true);
357            rootMenuManager.addMenuListener(new IMenuListener() {
358                    public void menuAboutToShow(IMenuManager mgr)
359                    {
360                        fillMenu(mgr);
361                    }
362                });
363    
364            fillMenu(rootMenuManager);
365        }
366    
367        /**
368         * Builds the drop-down menu used for this tree view.
369         *
370         * @param  rootMenuManager  The menu manager that will contain the menu
371         *                          items.
372         */
373        protected void fillMenu(IMenuManager rootMenuManager)
374        {
375            rootMenuManager.add(m_addRootAction);
376            rootMenuManager.add(m_removeAction);
377            rootMenuManager.add(m_refreshAction);
378    
379            IMenuManager filterSubmenu = new MenuManager("Filters");
380            rootMenuManager.add(filterSubmenu);
381            filterSubmenu.add(m_onlyFoldersAction);
382            filterSubmenu.add(m_nonEmptyFolders);
383    
384            IMenuManager sortSubmenu = new MenuManager("Sort By");
385            rootMenuManager.add(sortSubmenu);
386            sortSubmenu.add(m_foldersFirstAction);
387            sortSubmenu.add(m_noTypeAction);
388        }
389    
390        /**
391         * Updates the TreeViewer's sorter to match the options selected by the user
392         * in the sort sub-menu.
393         *
394         * @param  action  The Action selected by the user.
395         */
396        protected void updateSorter(Action action)
397        {
398            if (action == m_foldersFirstAction) {
399                m_noTypeAction.setChecked(!m_foldersFirstAction.isChecked());
400    
401                if (action.isChecked()) {
402                    m_treeViewer.setSorter(m_foldersFirstSorter);
403                } else {
404                    m_treeViewer.setSorter(null);
405                }
406            } else if (action == m_noTypeAction) {
407                m_foldersFirstAction.setChecked(!m_noTypeAction.isChecked());
408    
409                if (action.isChecked()) {
410                    m_treeViewer.setSorter(m_noTypeSorter);
411                } else {
412                    m_treeViewer.setSorter(null);
413                }
414            }
415        }
416    
417        /**
418         * Updates the TreeViewer filters to reflect the filtering options selected
419         * by the user.
420         *
421         * @param  action  An Action identifying the filter option that has changed.
422         */
423        protected void updateFilter(Action action)
424        {
425            if (action == m_nonEmptyFolders) {
426                if (action.isChecked()) {
427                    m_treeViewer.addFilter(m_atLeastThreeFilter);
428                } else {
429                    m_treeViewer.removeFilter(m_atLeastThreeFilter);
430                }
431            } else if (action == m_onlyFoldersAction) {
432                if (action.isChecked()) {
433                    m_treeViewer.addFilter(m_onlyFoldersFilter);
434                } else {
435                    m_treeViewer.removeFilter(m_onlyFoldersFilter);
436                }
437            }
438        }
439    
440        /**
441         * Adds buttons to the tool bar for the actions defined for this view.
442         */
443        protected void createToolbar()
444        {
445            IToolBarManager toolbarManager =
446                getViewSite().getActionBars().getToolBarManager();
447            toolbarManager.add(m_addRootAction);
448            toolbarManager.add(m_removeAction);
449            toolbarManager.add(m_refreshAction);
450        }
451    
452        /**
453         * Constructs a proxy for the resource named by a given selector and adds it
454         * to the tree view as a new top-level folder. The resource referenced by
455         * the selector is not accessed until the new entry is highlighted or
456         * expanded.
457         *
458         * @param  selectorString  A String containing a CM API object selector
459         *                         naming a resource.
460         *
461         * @return  true if the resource was successfully added to the tree view.
462         */
463        protected boolean addRoot(String selectorString)
464        {
465            try {
466                m_root.addChild(selectorString);
467    
468                return true;
469            } catch (Throwable ex) {
470                MessageDialog.openError(null, "Can't Add " + selectorString,
471                                        ex.getClass().getName() + ": "
472                                        + ex.getLocalizedMessage());
473    
474                return false;
475            }
476        }
477    
478        /**
479         * Adds the objects in a given list to the display.
480         *
481         * @param  roots  An array of object whose toString() value is a valid
482         *                object selector
483         *
484         * @return  true if at least one root was added to the display.
485         */
486        protected boolean addRoots(List roots)
487        {
488            Iterator iter = roots.iterator();
489            boolean  added = false;
490    
491            while (iter.hasNext())
492                added |= addRoot(iter.next().toString());
493    
494            return added;
495        }
496    
497        /**
498         * Makes initial entries into the tree viewer for the sample databases and
499         * projects usually installed with ClearQuest. Using a known server (which
500         * will have to be changed for your location), a ClearCase server is
501         * contacted and client workspaces known to the server are added as roots.
502         * 
503         * @param shell The Shell used for display context when requesting
504         *            credentials from the user.
505         * 
506         * @return A ProxyElement object representing the (invisible) root of the
507         *         tree to be viewed.
508         */
509        public Object getInitalInput(Shell shell)
510        {
511            try {
512                m_root = new ProxyElement(shell);
513                StpProvider provider = m_root.getProvider();
514    
515                String url = System.getProperty("scout.cc.server");
516                
517                if (url != null) {
518                    provider.setServerUrl(url);
519                    m_askForServerUrl = false;
520                }
521    
522                return m_root;
523            } catch (Throwable ex) {
524                ex.printStackTrace();
525    
526                return m_root = null;
527            }
528        }
529    
530        /**
531         * @see  IWorkbenchPart#setFocus()
532         */
533        public void setFocus() {}
534    
535        /** The TreeViewer control on which this view is based */
536        protected TreeViewer m_treeViewer;
537    
538        /** The Text for this view */
539        protected Text m_text;
540    
541        /** The Actions for implementing display options */
542        protected Action m_onlyFoldersAction, m_nonEmptyFolders;
543    
544        /** The Actions for implementing sort options */
545        protected Action m_foldersFirstAction, m_noTypeAction;
546    
547        /** The Actions for implementing list management operations */
548        protected Action m_addRootAction, m_removeAction, m_refreshAction;
549    
550        /** The ViewFilter for implementing display options */
551        protected ViewerFilter m_onlyFoldersFilter, m_atLeastThreeFilter;
552    
553        /** The ViewSorter for implementing sort options */
554        protected ViewerSorter m_foldersFirstSorter, m_noTypeSorter;
555    
556        /** The root of the tree view. */
557        protected ProxyElement m_root;
558        
559        private boolean m_askForServerUrl = true;
560    }