package org.radrails.rails.internal.ui.railsplugins; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ActionContributionItem; import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.TableEditor; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; import org.radrails.rails.core.railsplugins.IRailsPluginListener; import org.radrails.rails.core.railsplugins.RailsPluginDescriptor; import org.radrails.rails.core.railsplugins.RailsPluginsManager; import org.radrails.rails.core.railsplugins.RailsPluginsManager.RailsPluginException; import org.radrails.rails.internal.core.RailsPlugin; import org.radrails.rails.internal.ui.actions.RailsProjectSelectionAction; import org.radrails.rails.ui.RailsUILog; import org.radrails.rails.ui.RailsUIPlugin; import org.radrails.rails.ui.browser.BrowserUtil; import org.rubypeople.rdt.internal.ui.RubyExplorerTracker; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.RubyExplorerTracker.IRubyProjectListener; import org.rubypeople.rdt.internal.ui.text.RubyColorManager; import org.rubypeople.rdt.internal.ui.util.CollectionContentProvider; import org.rubypeople.rdt.ui.TableViewerSorter; /** * RailsPluginsView */ public class RailsPluginsView extends ViewPart implements ISelectionProvider, ISelectionChangedListener, IRailsPluginListener, IRubyProjectListener { private static final String INSTALL = "Install"; private static final String MANAGE = "Manage"; private static final String PROJECT = "Current Rails Project: "; /** * Remote Plugins / Install tab */ private TableViewer remotePluginTableViewer; private Table pluginsTable; /** * Local Plugins / Manage Tab */ private TableViewer installedPluginsTableViewer; private Composite _composite; private Label projectLabel; private SashForm _outlineSash; /** * Tabs */ private CTabFolder tabs; private CTabItem installTab; private CTabItem manageTab; private RubyColorManager fColorManager; private IProject project = null; private RailsProjectSelectionAction projectSelectionAction; /** * For selections on both tabs */ private ISelection selection; private Set<ISelectionChangedListener> listeners = new HashSet<ISelectionChangedListener>(); private boolean useExternals = false; private boolean checkout = false; private int fLastSortColumn = -1; private boolean sortUp = false; /** * Keeps a list of the TableEditors created, so that when we sort we can properly dispaose of them. Otherwise with * the virtual table we get old table editors "stuck" on rows where the underlying data has changed and the editor * has bad values. */ private List<TableEditor> fTableEditors = new ArrayList<TableEditor>(); private Map<TableItem, Link> fLinks = new HashMap<TableItem, Link>(); private Map<TableItem, Widget> fImages = new HashMap<TableItem, Widget>(); /** * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) */ public void createPartControl(Composite parent) { fColorManager = new RubyColorManager(true); RatingImage.init(parent.getDisplay()); // create main container _composite = createComposite(parent); projectLabel = new Label(_composite, SWT.LEFT); projectLabel.setForeground(fColorManager.getColor(new RGB(128, 128, 128))); projectLabel.setText(PROJECT); GridData plData = new GridData(SWT.FILL, SWT.FILL, true, false); plData.horizontalIndent = 5; plData.verticalIndent = 5; projectLabel.setLayoutData(plData); // create tab sash _outlineSash = this.createSash(_composite); createTabs(_composite); getSite().setSelectionProvider(this); projectSelectionAction = new RailsProjectSelectionAction(); projectSelectionAction.setListener(this); IActionBars bars = getViewSite().getActionBars(); bars.getToolBarManager().add(projectSelectionAction); createPopupMenu(); getViewSite().getActionBars().getMenuManager().add(new ToggleExternals()); getViewSite().getActionBars().getMenuManager().add(new ToggleCheckout()); RailsPluginsManager.addRailsPluginListener(this); } /** * createComposite * * @param parent * @return Composite */ private Composite createComposite(Composite parent) { GridLayout contentAreaLayout = new GridLayout(); contentAreaLayout.numColumns = 1; contentAreaLayout.marginHeight = 0; contentAreaLayout.marginWidth = 0; contentAreaLayout.makeColumnsEqualWidth = false; Composite result = new Composite(parent, SWT.NONE); result.setLayout(contentAreaLayout); result.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); return result; } /** * createSash * * @param parent * @return sash form */ private SashForm createSash(Composite parent) { // create layout data GridData gridData = new GridData(GridData.FILL_BOTH); gridData.exclude = true; // create form SashForm result = new SashForm(parent, SWT.VERTICAL | SWT.BORDER); // set layout data result.setLayoutData(gridData); result.setVisible(false); return result; } private void createTabs(Composite parent) { this.tabs = new CTabFolder(parent, SWT.TOP | SWT.BORDER); this.tabs.setLayoutData(new GridData(GridData.FILL_BOTH)); tabs.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { if (e.item instanceof CTabItem) { remotePluginTableViewer.setSelection(null); installedPluginsTableViewer.setSelection(null); } } public void widgetDefaultSelected(SelectionEvent e) { } }); installTab = createInstallTab(); manageTab = createManageTab(); this.tabs.setSelection(installTab); } private CTabItem createManageTab() { createTabLabel(MANAGE); SashForm manageForm = new SashForm(this.tabs, SWT.NONE); manageForm.setLayoutData(new GridData(GridData.FILL_BOTH)); CTabItem sourceTab = new CTabItem(this.tabs, SWT.NONE); sourceTab.setText(MANAGE); sourceTab.setControl(manageForm); createInstalledPluginsTable(manageForm); return sourceTab; } private CTabItem createInstallTab() { createTabLabel(INSTALL); SashForm preForm = new SashForm(tabs, SWT.NONE); preForm.setLayoutData(new GridData(GridData.FILL_BOTH)); CTabItem tab = new CTabItem(tabs, SWT.NONE); tab.setText(INSTALL); tab.setControl(preForm); createRemotePluginsTable(preForm); return tab; } private void createTabLabel(String label) { Composite previewComp = new Composite(this._outlineSash, SWT.NONE); previewComp.setLayoutData(new GridData(GridData.FILL_BOTH)); Label outlineLabel = new Label(previewComp, SWT.NONE); outlineLabel.setText(label); outlineLabel.setLayoutData(new GridData(GridData.FILL_BOTH)); } private void createRemotePluginsTable(Composite parent) { pluginsTable = new Table(parent, SWT.VIRTUAL | SWT.SINGLE | SWT.FULL_SELECTION); remotePluginTableViewer = new TableViewer(pluginsTable); pluginsTable.setHeaderVisible(true); pluginsTable.setLinesVisible(true); pluginsTable.setLayoutData(new GridData(GridData.FILL_BOTH)); createColumn(pluginsTable, "Name", 150).addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { sort(0); super.widgetSelected(e); } }); ; createColumn(pluginsTable, "Rating", 75).addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { sort(1); super.widgetSelected(e); } }); createColumn(pluginsTable, "License", 100).addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { sort(2); super.widgetSelected(e); } }); ; createColumn(pluginsTable, "Home", 175).addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { sort(3); super.widgetSelected(e); } }); ; remotePluginTableViewer.setLabelProvider(new RailsPluginsLabelProvider()); remotePluginTableViewer.setContentProvider(new CollectionContentProvider()); remotePluginTableViewer.addSelectionChangedListener(this); try { remotePluginTableViewer.setInput(RailsPluginsManager.getInstance().getPlugins()); } catch (RailsPluginException e) { RailsUILog.log(e); } pluginsTable.addListener(SWT.SetData, new Listener() { public void handleEvent(Event event) { TableItem item = (TableItem) event.item; clearExistingForItem(item); createLink(item); setRatingImage(item); } }); createLinksForHomepage(); } protected void clearExistingForItem(TableItem item) { if (item == null) return; Link link = fLinks.remove(item); if (link != null) { link.dispose(); link = null; } // TODO Remove table editor! // fTableEditors. Widget image = fImages.remove(item); if (image != null) { image.dispose(); image = null; } } protected void sort(final int columnIndex) { if (fLastSortColumn == columnIndex) { sortUp = !sortUp; } fLastSortColumn = columnIndex; List<RailsPluginDescriptor> original = new ArrayList<RailsPluginDescriptor>(); try { original = RailsPluginsManager.getInstance().getPlugins(); } catch (RailsPluginException e) { RailsUILog.log(e); } Collections.sort(original, new Comparator<RailsPluginDescriptor>() { public int compare(RailsPluginDescriptor first, RailsPluginDescriptor second) { int value = 0; switch (columnIndex) { case 0: value = first.getRawName().compareTo(second.getRawName()); break; case 1: value = Float.compare(first.getRating(), second.getRating()); break; case 2: value = first.getLicense().compareTo(second.getLicense()); break; case 3: value = first.getHome().compareTo(second.getHome()); break; } if (sortUp) { value = -(value); } return value; } }); pluginsTable.setItemCount(original.size()); pluginsTable.clearAll(); // Dispose custom table editors (or they "stick" to rows even though data has changed for (TableEditor editor : fTableEditors) { editor.getEditor().dispose(); editor.dispose(); } fTableEditors.clear(); pluginsTable.setSortDirection(sortUp ? SWT.UP : SWT.DOWN); remotePluginTableViewer.setInput(original); } /** * Sets the rating image on a table item * * @param item */ protected void setRatingImage(TableItem item) { // Generate a special rating image depending on value RailsPluginDescriptor desc = (RailsPluginDescriptor) item.getData(); float rating = desc.getRating(); if (rating == -1) { return; } Image image = RatingImage.createRatingImage(rating, pluginsTable.getDisplay()); Label bar = new Label(pluginsTable, SWT.NULL); bar.setImage(image); bar.setBackground(pluginsTable.getBackground()); TableEditor editor = new TableEditor(pluginsTable); editor.grabHorizontal = editor.grabVertical = true; editor.setEditor(bar, item, 1); fTableEditors.add(editor); fImages.put(item, bar); } private void createInstalledPluginsTable(Composite parent) { installedPluginsTableViewer = new TableViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION); Table pluginsTable = installedPluginsTableViewer.getTable(); pluginsTable.setHeaderVisible(true); pluginsTable.setLinesVisible(false); pluginsTable.setLayoutData(new GridData(GridData.FILL_BOTH)); createColumn(pluginsTable, "Name", 300); installedPluginsTableViewer.setLabelProvider(new InstalledRailsPluginsLabelProvider()); installedPluginsTableViewer.setContentProvider(new CollectionContentProvider()); TableViewerSorter.bind(installedPluginsTableViewer); installedPluginsTableViewer.addSelectionChangedListener(this); installedPluginsTableViewer.setInput(RailsPluginsManager.getInstalledPlugins(RailsUIPlugin .getSelectedOrOnlyRailsProject())); getProjectTracker().addProjectListener(this); if (this.project == null) { Set<IProject> projects = RailsPlugin.getRailsProjects(); if (projects != null && projects.size() > 0) projectSelected(projects.iterator().next()); } } private RubyExplorerTracker getProjectTracker() { return RubyPlugin.getDefault().getProjectTracker(); } private class ToggleExternals extends Action { /** * ToggleExternals */ public ToggleExternals() { super("svn:externals", Action.AS_CHECK_BOX); setChecked(useExternals); } /** * @see org.eclipse.jface.action.Action#run() */ public void run() { super.run(); useExternals = isChecked(); } } private class ToggleCheckout extends Action { /** * ToggleCheckout */ public ToggleCheckout() { super("svn:checkout", Action.AS_CHECK_BOX); setChecked(checkout); } /** * @see org.eclipse.jface.action.Action#run() */ public void run() { super.run(); checkout = isChecked(); } } /** * Use externals * * @return - true if using externals */ public boolean useExternals() { return useExternals; } /** * Use checkout * * @return - true if using checkout */ public boolean checkout() { return checkout; } private void createLinksForHomepage() { TableItem[] items = pluginsTable.getItems(); for (int i = 0; i < items.length; i++) { createLink(items[i]); } } private void createLink(TableItem item) { TableEditor editor = new TableEditor(pluginsTable); RailsPluginDescriptor desc = (RailsPluginDescriptor) item.getData(); if (desc == null) return; final String url = desc.getHome(); if (url == null || url.trim().length() == 0) return; Link link = new Link(pluginsTable, SWT.NONE); link.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { try { BrowserUtil.openBrowser(url); } catch (PartInitException e1) { RailsUILog.log(e1); } catch (MalformedURLException e1) { RailsUILog.log(e1); } super.widgetSelected(e); } }); link.setBackground(pluginsTable.getBackground()); link.setText("<a href=\"" + url + "\">" + url + "</a>"); editor.grabHorizontal = true; editor.setEditor(link, item, 3); fTableEditors.add(editor); fLinks.put(item, link); } private TableColumn createColumn(Table table, String string, int size) { TableColumn column = new TableColumn(table, SWT.LEFT); column.setText(string); column.setWidth(size); return column; } /** * @see org.eclipse.ui.part.WorkbenchPart#dispose() */ public void dispose() { try { for (Widget w : fLinks.values()) { w.dispose(); } for (Link l : fLinks.values()) { l.dispose(); } RatingImage.dispose(); fColorManager.dispose(); remotePluginTableViewer = null; installedPluginsTableViewer = null; _composite = null; RailsPluginsManager.removeRailsPluginListener(this); getProjectTracker().removeProjectListener(this); } finally { super.dispose(); } } /** * @see org.eclipse.ui.part.WorkbenchPart#setFocus() */ public void setFocus() { remotePluginTableViewer.getTable().setFocus(); } /** * Creates and registers the context menu */ private void createPopupMenu() { MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { IContributionItem[] items = getViewSite().getActionBars().getToolBarManager().getItems(); for (int i = 0; i < items.length; i++) { if (items[i] instanceof ActionContributionItem) { ActionContributionItem aci = (ActionContributionItem) items[i]; if (aci.getAction() != null && aci.getAction() != projectSelectionAction) { manager.add(aci.getAction()); } } } } }); menuMgr.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); Menu menu = menuMgr.createContextMenu(remotePluginTableViewer.getControl()); remotePluginTableViewer.getControl().setMenu(menu); getSite().registerContextMenu(menuMgr, remotePluginTableViewer); } /** * Refreshes the list of plugins. */ public void refreshPlugins() { if (installTabSelected()) { Job j = new Job("Refresh plugins") { protected IStatus run(IProgressMonitor monitor) { try { monitor.beginTask("Loading Rails plugins", 30); SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, 20); RailsPluginsManager.getInstance().updatePlugins(subMonitor); final List plugins = RailsPluginsManager.getInstance().getPlugins(); PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { public void run() { pluginsTable.clearAll(); remotePluginTableViewer.setInput(plugins); } }); monitor.worked(10); } catch (Exception e) { RailsUILog.logError("Error loading Rails plugin list", e); MessageDialog.openError(getSite().getShell(), "Error refreshing plugin list", e.getMessage()); } finally { monitor.done(); } return Status.OK_STATUS; } }; j.schedule(); } else { // refresh locally installed plugins installedPluginsTableViewer.setInput(RailsPluginsManager.getInstalledPlugins(RailsUIPlugin .getSelectedOrOnlyRailsProject())); } } /** * Is install tab selected * * @return - true if selected */ public boolean installTabSelected() { CTabItem selectedTab = tabs.getSelection(); return selectedTab.equals(installTab); } /** * Is manage tab selected * * @return - true if selected */ public boolean manageTabSelected() { return !installTabSelected(); } /** * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) */ public void addSelectionChangedListener(ISelectionChangedListener listener) { listeners.add(listener); } /** * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection() */ public ISelection getSelection() { return selection; } /** * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) */ public void removeSelectionChangedListener(ISelectionChangedListener listener) { listeners.remove(listener); } /** * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection) */ public void setSelection(ISelection selection) { // do nothing this.selection = selection; for (Iterator iter = listeners.iterator(); iter.hasNext();) { ISelectionChangedListener listener = (ISelectionChangedListener) iter.next(); listener.selectionChanged(new SelectionChangedEvent(this, selection)); } } /** * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent) */ public void selectionChanged(SelectionChangedEvent event) { setSelection(event.getSelection()); } /** * Adds an installed plugin to the table * * @param project * @param plugin */ public void pluginInstalled(IProject project, RailsPluginDescriptor plugin) { if (project.equals(RailsUIPlugin.getSelectedOrOnlyRailsProject())) { RailsPluginDescriptor copy = new RailsPluginDescriptor(); copy.setProperty(RailsPluginDescriptor.NAME, plugin.getName()); installedPluginsTableViewer.add(copy); } } /** * Removes an installed plugin from the table * * @param project * @param plugin */ public void pluginRemoved(IProject project, RailsPluginDescriptor plugin) { if (project.equals(RailsUIPlugin.getSelectedOrOnlyRailsProject())) installedPluginsTableViewer.remove(plugin); } /** * @see org.radrails.rails.core.railsplugins.IRailsPluginListener#remotePluginsRefreshed() */ public void remotePluginsRefreshed() { // do nothing(?) } /** * Listen to project selection changes in Ruby Explorer * * @param project */ public void projectSelected(IProject project) { if (installedPluginsTableViewer == null) { return; } if (RailsPlugin.hasRailsNature(project) && project.exists() && project.isOpen()) { projectLabel.setText(PROJECT + project.getName()); this.project = project; } else if (project == null || !project.exists()) { projectLabel.setText(PROJECT + "<Select a Rails project>"); _composite.layout(true, true); this.project = null; } _composite.layout(true, true); installedPluginsTableViewer.setInput(RailsPluginsManager.getInstalledPlugins(project)); } /** * Gets the project the plugins view is currently set to use * * @return - project being used or null */ public IProject getProject() { return this.project; } }