Java tutorial
/******************************************************************************* * Copyright (c) 2010 Tasktop Technologies and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Tasktop Technologies - initial API and implementation * Itema AS - bug 325079 added support for build service messages * Itema AS - bug 331008 automatic resize of view columns *******************************************************************************/ package org.eclipse.mylyn.internal.builds.ui.view; import java.io.File; import java.text.DateFormat; import java.util.Collections; import java.util.Date; import java.util.Set; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.GroupMarker; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.DecoratingStyledCellLabelProvider; import org.eclipse.jface.viewers.IOpenListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.OpenEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.mylyn.builds.core.BuildStatus; import org.eclipse.mylyn.builds.core.IBuildElement; import org.eclipse.mylyn.builds.core.IBuildPlan; import org.eclipse.mylyn.builds.core.IBuildServer; import org.eclipse.mylyn.builds.internal.core.BuildModel; import org.eclipse.mylyn.builds.internal.core.operations.RefreshOperation; import org.eclipse.mylyn.builds.internal.core.util.BuildsConstants; import org.eclipse.mylyn.builds.ui.BuildsUiConstants; import org.eclipse.mylyn.commons.core.CoreUtil; import org.eclipse.mylyn.commons.core.operations.IOperationMonitor.OperationFlag; import org.eclipse.mylyn.commons.ui.AbstractColumnViewerSupport; import org.eclipse.mylyn.commons.ui.CommonImages; import org.eclipse.mylyn.commons.ui.TreeSorter; import org.eclipse.mylyn.commons.ui.TreeViewerSupport; import org.eclipse.mylyn.commons.ui.actions.CollapseAllAction; import org.eclipse.mylyn.commons.ui.actions.ExpandAllAction; import org.eclipse.mylyn.internal.builds.ui.BuildImages; import org.eclipse.mylyn.internal.builds.ui.BuildToolTip; import org.eclipse.mylyn.internal.builds.ui.BuildsUiInternal; import org.eclipse.mylyn.internal.builds.ui.BuildsUiPlugin; import org.eclipse.mylyn.internal.builds.ui.actions.RunBuildAction; import org.eclipse.mylyn.internal.builds.ui.actions.ShowBuildOutputAction; import org.eclipse.mylyn.internal.builds.ui.actions.ShowTestResultsAction; import org.eclipse.mylyn.internal.builds.ui.commands.OpenHandler; import org.eclipse.mylyn.internal.builds.ui.notifications.BuildsServiceMessageControl; import org.eclipse.mylyn.internal.builds.ui.view.BuildContentProvider.Presentation; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IMemento; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.IShowInSource; import org.eclipse.ui.part.IShowInTarget; import org.eclipse.ui.part.IShowInTargetList; import org.eclipse.ui.part.ShowInContext; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.progress.IWorkbenchSiteProgressService; /** * @author Steffen Pingel * @author Torkild U. Resheim * @author Lucas Panjer */ public class BuildsView extends ViewPart implements IShowInTarget { public class BuildsSummary { int numSuccess; int numUnstable; int numFailed; public boolean isSuccess() { return numSuccess > 0 && numUnstable == 0 && numFailed == 0; } public boolean isUnstable() { return numUnstable > 0 && numFailed == 0; } public boolean isFailed() { return numFailed > 0; } public boolean isEmpty() { return numSuccess == 0 && numSuccess == 0 && numFailed == 0; } @Override public String toString() { return NLS.bind("{0} Succeeded, {1} Unstable, {2} Failed", new Object[] { numSuccess, numUnstable, numFailed }); } } private class BuildTreeSorter extends TreeSorter { @Override public int compare(TreeViewer viewer, Object e1, Object e2, int columnIndex) { if (e1 instanceof IBuildServer && e2 instanceof IBuildServer) { // keep server sort order stable for now @SuppressWarnings("unchecked") int res = getComparator().compare(((IBuildElement) e1).getLabel(), ((IBuildElement) e2).getLabel()); if (getSortDirection(viewer) == SWT.UP) { return -res; } return res; } if (e1 instanceof IBuildPlan && e2 instanceof IBuildPlan) { IBuildPlan p1 = (IBuildPlan) e1; IBuildPlan p2 = (IBuildPlan) e2; switch (columnIndex) { case -1: // default return CoreUtil.compare(p1.getLabel(), p2.getLabel()); case 0: // build return CoreUtil.compare(p1.getStatus(), p2.getStatus()); case 1: // summary return p1.getHealth() - p2.getHealth(); case 2: // last build Long t1 = (p1.getLastBuild() != null) ? p1.getLastBuild().getTimestamp() : null; Long t2 = (p2.getLastBuild() != null) ? p2.getLastBuild().getTimestamp() : null; return CoreUtil.compare(t1, t2); } } return super.compare(viewer, e1, e2, columnIndex); } @Override protected int compareDefault(TreeViewer viewer, Object e1, Object e2) { return compare(viewer, e1, e2, -1); } } public static BuildsView openInActivePerspective() { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (window != null) { IWorkbenchPage page = window.getActivePage(); if (page != null) { try { return (BuildsView) page.showView(BuildsUiConstants.ID_VIEW_BUILDS); } catch (PartInitException e) { // ignore } } } return null; } private BuildStatusFilter buildStatusFilter; private Action collapseAllAction; private BuildContentProvider contentProvider; private Action expandAllAction; private FilterByStatusAction filterDisabledAction; private Date lastRefresh; private Composite messageComposite; private BuildModel model; private AdapterImpl modelListener; private BuildElementPropertiesAction propertiesAction; private final IPropertyChangeListener propertyChangeListener = new IPropertyChangeListener() { public void propertyChange(PropertyChangeEvent event) { if (org.eclipse.mylyn.internal.builds.ui.BuildsUiInternal.PREF_AUTO_REFRESH_ENABLED .equals(event.getProperty())) { Display.getDefault().asyncExec(new Runnable() { public void run() { if (refreshAutomaticallyAction != null) { refreshAutomaticallyAction.updateState(); } } }); } } }; private RefreshAutomaticallyAction refreshAutomaticallyAction; private StackLayout stackLayout; private IMemento stateMemento; private TreeViewer viewer; private PresentationMenuAction presentationsMenuAction; private BuildToolTip toolTip; private BuildsServiceMessageControl serviceMessageControl; public BuildsView() { BuildsUiPlugin.getDefault().initializeRefresh(); } private void contributeToActionBars() { IActionBars bars = getViewSite().getActionBars(); fillLocalPullDown(bars.getMenuManager()); fillLocalToolBar(bars.getToolBarManager()); } private void createMessage(Composite parent) { messageComposite = new Composite(parent, SWT.NONE); FillLayout layout = new FillLayout(); layout.marginHeight = 5; layout.marginWidth = 5; messageComposite.setLayout(layout); //GridLayoutFactory.swtDefaults().applyTo(messageComposite); messageComposite.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); Link link = new Link(messageComposite, SWT.WRAP); link.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); link.setText("No build servers available. Create a <a href=\"create\">build server</a>..."); link.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { if ("create".equals(event.text)) { new NewBuildServerAction().run(); } } }); } @Override public void createPartControl(Composite parent) { Composite body = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(1, false); layout.marginHeight = 0; layout.marginWidth = 0; layout.horizontalSpacing = 0; layout.verticalSpacing = 0; layout.numColumns = 1; body.setLayout(layout); Composite composite = new Composite(body, SWT.NONE); composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); stackLayout = new StackLayout(); composite.setLayout(stackLayout); composite.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); createMessage(composite); createViewer(composite); initActions(); createPopupMenu(body); contributeToActionBars(); toolTip = new BuildToolTip(getViewer().getControl()); toolTip.setViewer(viewer); // bug#160897: set to empty string to disable native tooltips (windows only?) viewer.getTree().setToolTipText(""); //$NON-NLS-1$ IWorkbenchSiteProgressService progress = (IWorkbenchSiteProgressService) getSite() .getAdapter(IWorkbenchSiteProgressService.class); if (progress != null) { // show indicator for all running refreshes progress.showBusyForFamily(BuildsConstants.JOB_FAMILY); } model = BuildsUiInternal.getModel(); modelListener = new BuildModelContentAdapter() { @Override public void doNotifyChanged(Notification msg) { if (!viewer.getControl().isDisposed()) { lastRefresh = new Date(); updateContents(Status.OK_STATUS); } } }; model.eAdapters().add(modelListener); if (stateMemento != null) { restoreState(stateMemento); stateMemento = null; } serviceMessageControl = new BuildsServiceMessageControl(body); viewer.setInput(model); viewer.setSorter(new BuildTreeSorter()); viewer.expandAll(); installAutomaticResize(viewer.getTree()); getSite().setSelectionProvider(viewer); getSite().getSelectionProvider().addSelectionChangedListener(propertiesAction); propertiesAction.selectionChanged((IStructuredSelection) getSite().getSelectionProvider().getSelection()); updateContents(Status.OK_STATUS); new TreeViewerSupport(viewer, getStateFile()); // Make sure we get notifications NotificationSinkProxy.setControl(serviceMessageControl); } /** * Initializes automatic resize of the tree control columns. The size of these will be adjusted when a node is * expanded or collapsed and when the tree changes size. * * @param tree * the tree to resize */ private void installAutomaticResize(final Tree tree) { Listener listener = new Listener() { public void handleEvent(Event e) { packColumnsAsync(tree); } }; // Automatically resize columns when we expand tree nodes. tree.addListener(SWT.Collapse, listener); tree.addListener(SWT.Expand, listener); tree.getParent().addListener(SWT.Resize, listener); } void packColumnsAsync(final Tree tree) { tree.getDisplay().asyncExec(new Runnable() { public void run() { if (!tree.isDisposed()) { try { tree.setRedraw(false); packColumns(tree); } finally { tree.setRedraw(true); } } } }); } private File getStateFile() { IPath stateLocation = Platform.getStateLocation(BuildsUiPlugin.getDefault().getBundle()); return stateLocation.append("BuildView.xml").toFile(); //$NON-NLS-1$ } protected void createPopupMenu(Composite parent) { MenuManager menuManager = new MenuManager(); menuManager.add(new GroupMarker("group.open")); menuManager.add(new Separator("group.edit")); menuManager.add(new Separator("group.file")); menuManager.add(new Separator("group.run")); menuManager.add(new Separator("group.refresh")); menuManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); menuManager.add(new Separator(BuildsUiConstants.GROUP_PROPERTIES)); Menu contextMenu = menuManager.createContextMenu(parent); viewer.getTree().setMenu(contextMenu); getSite().registerContextMenu(menuManager, viewer); } protected void createViewer(Composite parent) { // Composite composite = new Composite(parent, SWT.NONE); // TreeColumnLayout treeColumnLayout = new TreeColumnLayout(); // composite.setLayout(treeColumnLayout); viewer = new TreeViewer(parent, SWT.FULL_SELECTION); Tree tree = viewer.getTree(); tree.setHeaderVisible(true); TreeViewerColumn buildViewerColumn = new TreeViewerColumn(viewer, SWT.LEFT); buildViewerColumn.setLabelProvider(new DecoratingStyledCellLabelProvider(new BuildLabelProvider(true), PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator(), null)); TreeColumn buildColumn = buildViewerColumn.getColumn(); buildColumn.setText("Build"); buildColumn.setWidth(220); buildColumn.setData(AbstractColumnViewerSupport.KEY_COLUMN_CAN_HIDE, false); //treeColumnLayout.setColumnData(buildColumn, new ColumnWeightData(20, 50)); TreeViewerColumn summaryViewerColumn = new TreeViewerColumn(viewer, SWT.LEFT); summaryViewerColumn.setLabelProvider(new BuildSummaryLabelProvider()); TreeColumn summaryColumn = summaryViewerColumn.getColumn(); summaryColumn.setText("Summary"); summaryColumn.setWidth(220); //treeColumnLayout.setColumnData(summaryColumn, new ColumnWeightData(60, 200)); TreeViewerColumn lastBuiltViewerColumn = new TreeViewerColumn(viewer, SWT.RIGHT); lastBuiltViewerColumn.setLabelProvider(new RelativeBuildTimeLabelProvider()); TreeColumn lastBuiltColumn = lastBuiltViewerColumn.getColumn(); lastBuiltColumn.setText("Last Built"); lastBuiltColumn.setWidth(50); //treeColumnLayout.setColumnData(lastBuiltColumn, new ColumnWeightData(20, 50)); contentProvider = new BuildContentProvider(); contentProvider.setSelectedOnly(true); viewer.setContentProvider(contentProvider); viewer.addOpenListener(new IOpenListener() { public void open(OpenEvent event) { Object element = ((IStructuredSelection) event.getSelection()).getFirstElement(); if (element instanceof IBuildElement) { OpenHandler.open(getSite().getPage(), Collections.singletonList((IBuildElement) element)); } } }); } @Override public void dispose() { super.dispose(); getModel().eAdapters().remove(modelListener); BuildsUiPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(propertyChangeListener); } private void fillLocalPullDown(IMenuManager manager) { manager.add(presentationsMenuAction); manager.add(new Separator()); manager.add(collapseAllAction); manager.add(expandAllAction); manager.add(new Separator(BuildsUiConstants.GROUP_FILTER)); manager.add(filterDisabledAction); manager.add(new Separator(BuildsUiConstants.GROUP_NAVIGATE)); manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); manager.add(new Separator(BuildsUiConstants.GROUP_PROPERTIES)); manager.add(refreshAutomaticallyAction); manager.add(new Separator(BuildsUiConstants.GROUP_PREFERENCES)); manager.add(new OpenBuildsPreferencesAction()); } private void fillLocalToolBar(IToolBarManager manager) { manager.add(new NewBuildServerMenuAction()); manager.add(new Separator(BuildsUiConstants.GROUP_REFRESH)); RefreshAction refresh = new RefreshAction(); manager.add(refresh); manager.add(new Separator(BuildsUiConstants.GROUP_OPEN)); OpenWithBrowserAction openInBrowserAction = new OpenWithBrowserAction(); openInBrowserAction.selectionChanged(StructuredSelection.EMPTY); viewer.addSelectionChangedListener(openInBrowserAction); manager.add(openInBrowserAction); RunBuildAction runBuildAction = new RunBuildAction(); runBuildAction.selectionChanged(StructuredSelection.EMPTY); viewer.addSelectionChangedListener(runBuildAction); manager.add(runBuildAction); manager.add(new Separator(BuildsUiConstants.GROUP_FILE)); ShowBuildOutputAction openConsoleAction = new ShowBuildOutputAction(); openConsoleAction.selectionChanged(StructuredSelection.EMPTY); viewer.addSelectionChangedListener(openConsoleAction); manager.add(openConsoleAction); ShowTestResultsAction showTestResultsAction = new ShowTestResultsAction(); showTestResultsAction.selectionChanged(StructuredSelection.EMPTY); viewer.addSelectionChangedListener(showTestResultsAction); manager.add(showTestResultsAction); } public BuildStatusFilter getBuildStatusFilter() { if (buildStatusFilter == null) { buildStatusFilter = new BuildStatusFilter(); getViewer().addFilter(buildStatusFilter); } return buildStatusFilter; } public BuildContentProvider getContentProvider() { return contentProvider; } protected BuildsSummary getBuildsSummary() { BuildsSummary buildsSummary = new BuildsSummary(); if (getContentProvider() != null) { for (IBuildPlan plan : getModel().getPlans()) { if (plan.getStatus() != null) { switch (plan.getStatus()) { case SUCCESS: buildsSummary.numSuccess++; break; case UNSTABLE: buildsSummary.numUnstable++; break; case FAILED: buildsSummary.numFailed++; break; } } } } return buildsSummary; } TreeViewer getViewer() { return viewer; } @Override public void init(IViewSite site, IMemento memento) throws PartInitException { super.init(site, memento); BuildsUiPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(propertyChangeListener); // defer restore until view is created this.stateMemento = memento; } private void initActions() { collapseAllAction = new CollapseAllAction(viewer); expandAllAction = new ExpandAllAction(viewer); propertiesAction = new BuildElementPropertiesAction(); refreshAutomaticallyAction = new RefreshAutomaticallyAction(); filterDisabledAction = new FilterByStatusAction(this, BuildStatus.DISABLED); presentationsMenuAction = new PresentationMenuAction(this); } private void restoreState(IMemento memento) { IMemento child = memento.getChild("statusFilter"); if (child != null) { boolean changed = false; for (BuildStatus status : BuildStatus.values()) { Boolean value = child.getBoolean(status.name()); if (value != null && value.booleanValue()) { getBuildStatusFilter().addFiltered(status); changed = true; } } if (changed) { filterDisabledAction.update(); } } child = memento.getChild("presentation"); if (child != null) { String id = child.getString("id"); if (id != null) { try { getContentProvider().setPresentation(Presentation.valueOf(id)); } catch (IllegalArgumentException e) { // use default } } } } @Override public void saveState(IMemento memento) { super.saveState(memento); if (buildStatusFilter != null) { Set<BuildStatus> statuses = buildStatusFilter.getFiltered(); if (statuses.size() > 0) { IMemento child = memento.createChild("statusFilter"); for (BuildStatus status : statuses) { child.putBoolean(status.name(), true); } } } IMemento child = memento.createChild("presentation"); child.putString("id", getContentProvider().getPresentation().name()); } @Override public void setFocus() { getViewer().getControl().setFocus(); if (BuildsUiPlugin.getDefault().getPreferenceStore().getBoolean(BuildsUiInternal.PREF_REFRESH_ON_FOCUS)) { RefreshOperation operation = BuildsUiInternal.getFactory().getRefreshOperation(); operation.addFlag(OperationFlag.BACKGROUND); operation.execute(); } } private void setTopControl(Control control) { if (stackLayout.topControl != control) { stackLayout.topControl = control; control.getParent().layout(); } } public boolean show(ShowInContext context) { if (context.getSelection() != null) { getViewer().setSelection(context.getSelection()); return true; } return false; } private void updateContents(IStatus status) { boolean isShowingViewer = (stackLayout.topControl == viewer.getControl()); boolean hasContents = false; if (contentProvider != null) { if (getModel().getPlans().size() > 0 || getModel().getServers().size() > 0) { hasContents = true; } } if (hasContents) { setTopControl(viewer.getControl()); if (!isShowingViewer) { // initial flip packColumnsAsync(viewer.getTree()); } } else { setTopControl(messageComposite); } updateDecoration(status); } public void updateDecoration(IStatus status) { String statusMessage = ""; if (status.isOK()) { BuildsSummary buildsSummary = getBuildsSummary(); if (buildsSummary.isSuccess()) { setTitleImage(CommonImages.getImageWithOverlay(BuildImages.VIEW_BUILDS, CommonImages.OVERLAY_SUCCESS, false, false)); } else if (buildsSummary.isUnstable()) { setTitleImage(CommonImages.getImageWithOverlay(BuildImages.VIEW_BUILDS, CommonImages.OVERLAY_FAILED, false, false)); } else if (buildsSummary.isFailed()) { setTitleImage(CommonImages.getImageWithOverlay(BuildImages.VIEW_BUILDS, CommonImages.OVERLAY_ERROR, false, false)); } else { setTitleImage(CommonImages.getImage(BuildImages.VIEW_BUILDS)); } if (!buildsSummary.isEmpty()) { setContentDescription(buildsSummary.toString()); } else { setContentDescription(""); //$NON-NLS-1$ } if (lastRefresh != null) { statusMessage = NLS.bind("Last update: {0}", DateFormat.getDateTimeInstance().format(lastRefresh)); } } else { setTitleImage(CommonImages.getImageWithOverlay(BuildImages.VIEW_BUILDS, CommonImages.OVERLAY_WARNING, false, false)); statusMessage = "Last update failed"; } if (statusMessage != null) { final IViewSite viewSite = getViewSite(); if (viewSite != null) { final IStatusLineManager statusLine = viewSite.getActionBars().getStatusLineManager(); if (statusLine != null) { statusLine.setMessage(statusMessage); } } } } @Override public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) { if (adapter == IShowInTargetList.class) { return new IShowInTargetList() { public String[] getShowInTargetIds() { return new String[] { "org.eclipse.team.ui.GenericHistoryView" }; //$NON-NLS-1$ } }; } else if (adapter == IShowInSource.class) { return new IShowInSource() { public ShowInContext getShowInContext() { return new ShowInContext(getViewer().getInput(), getViewer().getSelection()); } }; } return super.getAdapter(adapter); } /** * Packs all columns so that they are able to display all content. If there is any space left, the second column * (summary) will be resized so that the entire width of the control is used. * * @param tree * the tree to resize */ private void packColumns(final Tree tree) { int totalColumnWidth = 0; for (TreeColumn tc : tree.getColumns()) { if (tc.getResizable()) { tc.pack(); totalColumnWidth += tc.getWidth() + 1; } else { totalColumnWidth += 1; // TODO: Determine the exact width of the column separator } } // Adjust the width of the "Summary" column unless it's not resizeable // which case we adjust the width of the "Build" column instead. TreeColumn column = (tree.getColumn(1).getResizable()) ? tree.getColumn(1) : tree.getColumn(0); int nw = column.getWidth() + tree.getClientArea().width - totalColumnWidth; column.setWidth(nw); } protected BuildModel getModel() { return model; } }