| /******************************************************************************* |
| * Copyright (c) 2007, 2011 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.equinox.p2.ui; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.equinox.internal.p2.ui.*; |
| import org.eclipse.equinox.internal.p2.ui.dialogs.CopyUtils; |
| import org.eclipse.equinox.internal.p2.ui.dialogs.InstalledIUGroup; |
| import org.eclipse.equinox.internal.p2.ui.model.ProfileSnapshots; |
| import org.eclipse.equinox.internal.p2.ui.model.RollbackProfileElement; |
| import org.eclipse.equinox.internal.p2.ui.viewers.*; |
| import org.eclipse.equinox.p2.core.ProvisionException; |
| import org.eclipse.equinox.p2.engine.*; |
| import org.eclipse.equinox.p2.operations.*; |
| import org.eclipse.equinox.p2.planner.IPlanner; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.dialogs.*; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| import org.eclipse.jface.viewers.*; |
| import org.eclipse.jface.window.Window; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.SashForm; |
| import org.eclipse.swt.dnd.*; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.*; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.about.InstallationPage; |
| import org.eclipse.ui.menus.AbstractContributionFactory; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| /** |
| * RevertProfilePage displays a profile's configuration history in |
| * an Installation Page. Clients can use this class as the implementation |
| * class for an installationPages extension. |
| * |
| * @see InstallationPage |
| * |
| * @noextend This class is not intended to be subclassed by clients. |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| * @since 2.0 |
| * |
| */ |
| public class RevertProfilePage extends InstallationPage implements ICopyable { |
| |
| private static final int REVERT_ID = IDialogConstants.CLIENT_ID; |
| private static final int DELETE_ID = IDialogConstants.CLIENT_ID + 1; |
| TableViewer configsViewer; |
| TreeViewer configContentsViewer; |
| IUDetailsLabelProvider labelProvider; |
| IAction revertAction; |
| Button revertButton, deleteButton; |
| String profileId; |
| AbstractContributionFactory factory; |
| Text detailsArea; |
| InstalledIUGroup installedIUGroup; |
| ProvisioningUI ui; |
| |
| private static class TagEditingSuport extends EditingSupport { |
| |
| private ProvisioningUI ui; |
| |
| public TagEditingSuport(TableViewer viewer, ProvisioningUI ui) { |
| super(viewer); |
| this.ui = ui; |
| } |
| |
| @Override |
| protected CellEditor getCellEditor(Object element) { |
| return new TextCellEditor(((TableViewer) getViewer()).getTable()); |
| } |
| |
| @Override |
| protected boolean canEdit(Object element) { |
| return element instanceof RollbackProfileElement; |
| } |
| |
| @Override |
| protected Object getValue(Object element) { |
| if (element instanceof RollbackProfileElement) { |
| return ((RollbackProfileElement) element).getProfileTag() != null ? ((RollbackProfileElement) element).getProfileTag() : ""; //$NON-NLS-1$ |
| } |
| return null; |
| } |
| |
| @Override |
| protected void setValue(Object element, Object value) { |
| if (element instanceof RollbackProfileElement && value instanceof String) { |
| RollbackProfileElement ele = ((RollbackProfileElement) element); |
| ele.setProfileTag((String) value); |
| // save |
| IProfileRegistry registry = ProvUI.getProfileRegistry(ui.getSession()); |
| if (registry != null) { |
| IStatus status; |
| if (((String) value).length() > 0) { |
| status = registry.setProfileStateProperty(ele.getProfileId(), ele.getTimestamp(), IProfile.STATE_PROP_TAG, (String) value); |
| } else { |
| status = registry.removeProfileStateProperties(ele.getProfileId(), ele.getTimestamp(), Collections.singleton(IProfile.STATE_PROP_TAG)); |
| } |
| if (!status.isOK()) { |
| StatusManager.getManager().handle(status); |
| } |
| } |
| getViewer().update(element, null); |
| } |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.ui.about.InstallationPage#createPageButtons(org.eclipse.swt.widgets.Composite) |
| */ |
| @Override |
| public void createPageButtons(Composite parent) { |
| if (profileId == null) |
| return; |
| deleteButton = createButton(parent, DELETE_ID, ProvUIMessages.RevertProfilePage_Delete); |
| deleteButton.setToolTipText(ProvUIMessages.RevertProfilePage_DeleteTooltip); |
| deleteButton.setEnabled(computeDeleteEnablement()); |
| revertButton = createButton(parent, REVERT_ID, revertAction.getText()); |
| revertButton.setToolTipText(revertAction.getToolTipText()); |
| revertButton.setEnabled(revertAction.isEnabled()); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) |
| */ |
| @Override |
| public void createControl(Composite parent) { |
| profileId = getProvisioningUI().getProfileId(); |
| if (profileId == null) { |
| IStatus status = getProvisioningUI().getPolicy().getNoProfileChosenStatus(); |
| if (status != null) |
| ProvUI.reportStatus(status, StatusManager.LOG); |
| Text text = new Text(parent, SWT.WRAP | SWT.READ_ONLY); |
| text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| text.setText(ProvUIMessages.RevertProfilePage_NoProfile); |
| setControl(text); |
| return; |
| } |
| |
| initializeDialogUnits(parent); |
| PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, IProvHelpContextIds.REVERT_CONFIGURATION_WIZARD); |
| |
| SashForm sashForm = new SashForm(parent, SWT.VERTICAL); |
| sashForm.setLayout(new GridLayout()); |
| GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); |
| sashForm.setLayoutData(gd); |
| |
| createConfigurationsSection(sashForm); |
| createContentsSection(sashForm); |
| setControl(sashForm); |
| |
| // prime the selection. The selection accesses the |
| // revert action, so create it also. |
| createRevertAction(); |
| } |
| |
| private void createConfigurationsSection(Composite parent) { |
| Composite composite = new Composite(parent, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.marginWidth = 0; |
| layout.marginHeight = 0; |
| composite.setLayout(layout); |
| GridData gd = new GridData(GridData.FILL_BOTH); |
| composite.setLayoutData(gd); |
| |
| Label label = new Label(composite, SWT.NONE); |
| label.setText(ProvUIMessages.RevertDialog_ConfigsLabel); |
| configsViewer = new TableViewer(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.FULL_SELECTION); |
| ProvElementContentProvider provider = new ProvElementContentProvider() { |
| @Override |
| protected void finishedFetchingElements(Object o) { |
| Object element = configsViewer.getElementAt(0); |
| if (element != null) |
| configsViewer.setSelection(new StructuredSelection(element)); |
| } |
| }; |
| setConfigsColumns(configsViewer); |
| |
| // Use deferred fetch because getting snapshots is expensive. |
| provider.setFetchInBackground(true); |
| configsViewer.setContentProvider(provider); |
| configsViewer.setLabelProvider(new ProvElementLabelProvider()); |
| configsViewer.setComparator(new ViewerComparator() { |
| // We override the ViewerComparator so that we don't get the labels of the elements |
| // for comparison, but rather get the timestamps and compare them. |
| // Reverse sorting is used so that newest is first. |
| @Override |
| public int compare(Viewer viewer, Object o1, Object o2) { |
| if (o1 instanceof RollbackProfileElement && o2 instanceof RollbackProfileElement) { |
| long timestamp1 = ((RollbackProfileElement) o1).getTimestamp(); |
| long timestamp2 = ((RollbackProfileElement) o2).getTimestamp(); |
| if (timestamp1 > timestamp2) |
| return -1; |
| return 1; |
| } |
| // this is naive (doesn't consult the label provider), but shouldn't happen |
| return o2.toString().compareTo(o1.toString()); |
| } |
| }); |
| configsViewer.setInput(getInput()); |
| |
| configsViewer.addSelectionChangedListener(new ISelectionChangedListener() { |
| @Override |
| public void selectionChanged(SelectionChangedEvent event) { |
| handleSelectionChanged((IStructuredSelection) event.getSelection()); |
| } |
| |
| }); |
| CopyUtils.activateCopy(this, configsViewer.getControl()); |
| gd = new GridData(SWT.FILL, SWT.FILL, true, true); |
| configsViewer.getControl().setLayoutData(gd); |
| } |
| |
| private void setConfigsColumns(TableViewer tableViewer) { |
| tableViewer.getTable().setHeaderVisible(true); |
| TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE); |
| TableColumn tc = column.getColumn(); |
| tc.setResizable(true); |
| tc.setText(ProvUIMessages.RevertProfilePage_ProfileTimestampColumn); |
| tc.setWidth(175); |
| |
| column = new TableViewerColumn(tableViewer, SWT.NONE); |
| tc = column.getColumn(); |
| tc.setResizable(true); |
| tc.setText(ProvUIMessages.RevertProfilePage_ProfileTagColumn); |
| tc.setWidth(200); |
| |
| column.setEditingSupport(new TagEditingSuport(tableViewer, ui)); |
| } |
| |
| private void createContentsSection(Composite parent) { |
| Composite composite = new Composite(parent, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.marginWidth = 0; |
| layout.marginHeight = 0; |
| composite.setLayout(layout); |
| GridData gd = new GridData(GridData.FILL_BOTH); |
| composite.setLayoutData(gd); |
| |
| Label label = new Label(composite, SWT.NONE); |
| label.setText(ProvUIMessages.RevertDialog_ConfigContentsLabel); |
| configContentsViewer = new TreeViewer(composite, SWT.MULTI | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); |
| IUComparator comparator = new IUComparator(IUComparator.IU_NAME); |
| comparator.useColumnConfig(ProvUI.getIUColumnConfig()); |
| configContentsViewer.setComparator(comparator); |
| configContentsViewer.setComparer(new ProvElementComparer()); |
| configContentsViewer.setContentProvider(new DeferredQueryContentProvider()); |
| |
| // columns before labels or you get a blank table |
| setTreeColumns(configContentsViewer.getTree()); |
| labelProvider = new IUDetailsLabelProvider(); |
| configContentsViewer.setLabelProvider(labelProvider); |
| |
| gd = new GridData(GridData.FILL_BOTH); |
| configContentsViewer.getControl().setLayoutData(gd); |
| CopyUtils.activateCopy(this, configContentsViewer.getControl()); |
| } |
| |
| private void createRevertAction() { |
| revertAction = new Action() { |
| @Override |
| public void run() { |
| int result = MessageDialog.open(MessageDialog.QUESTION, getShell(), ProvUIMessages.RevertDialog_Title, ProvUIMessages.RevertDialog_ConfirmRestartMessage, SWT.NONE, ProvUIMessages.RevertProfilePage_RevertLabel, ProvUIMessages.RevertDialog_CancelButtonLabel); |
| if (result != Window.OK) |
| return; |
| boolean finish = revert(); |
| if (finish) { |
| getPageContainer().closeModalContainers(); |
| } |
| } |
| }; |
| revertAction.setText(ProvUIMessages.RevertProfilePage_RevertLabel); |
| revertAction.setToolTipText(ProvUIMessages.RevertProfilePage_RevertTooltip); |
| } |
| |
| private Object getInput() { |
| ProfileSnapshots element = new ProfileSnapshots(profileId, getProvisioningUI().getSession()); |
| return element; |
| } |
| |
| @Override |
| protected void buttonPressed(int buttonId) { |
| switch (buttonId) { |
| case REVERT_ID : |
| revertAction.run(); |
| break; |
| case DELETE_ID : |
| deleteSelectedSnapshots(); |
| break; |
| } |
| } |
| |
| protected void handleSelectionChanged(IStructuredSelection selection) { |
| if (!selection.isEmpty()) { |
| if (selection.size() == 1) { |
| final Object selected = selection.getFirstElement(); |
| if (selected instanceof RollbackProfileElement) { |
| Object[] elements = configContentsViewer.getExpandedElements(); |
| configContentsViewer.getTree().setRedraw(false); |
| configContentsViewer.setInput(selected); |
| configContentsViewer.setExpandedElements(elements); |
| configContentsViewer.getTree().setRedraw(true); |
| boolean isNotCurrentProfile = !((RollbackProfileElement) selected).isCurrentProfile(); |
| revertAction.setEnabled(isNotCurrentProfile); |
| if (revertButton != null) |
| revertButton.setEnabled(isNotCurrentProfile); |
| if (deleteButton != null) |
| deleteButton.setEnabled(isNotCurrentProfile); |
| return; |
| } |
| } else { |
| // multiple selections, can't revert or look at details |
| revertAction.setEnabled(false); |
| if (revertButton != null) { |
| revertButton.setEnabled(false); |
| } |
| configContentsViewer.setInput(null); |
| deleteButton.setEnabled(computeDeleteEnablement()); |
| return; |
| } |
| } |
| // Nothing is selected |
| configContentsViewer.setInput(null); |
| revertAction.setEnabled(false); |
| if (revertButton != null) |
| revertButton.setEnabled(false); |
| if (deleteButton != null) |
| deleteButton.setEnabled(computeDeleteEnablement()); |
| } |
| |
| boolean computeDeleteEnablement() { |
| // delete is permitted if none of the selected elements are the current profile |
| boolean okToDelete = true; |
| Iterator<?> iter = ((IStructuredSelection) configsViewer.getSelection()).iterator(); |
| while (iter.hasNext()) { |
| Object selected = iter.next(); |
| // If it's not a recognized element or if it is the current profile, we can't delete. Stop iterating. |
| if (!(selected instanceof RollbackProfileElement) || ((RollbackProfileElement) selected).isCurrentProfile()) { |
| okToDelete = false; |
| break; |
| } |
| } |
| return okToDelete; |
| } |
| |
| private void setTreeColumns(Tree tree) { |
| IUColumnConfig[] columns = ProvUI.getIUColumnConfig(); |
| tree.setHeaderVisible(true); |
| |
| for (int i = 0; i < columns.length; i++) { |
| TreeColumn tc = new TreeColumn(tree, SWT.NONE, i); |
| tc.setResizable(true); |
| tc.setText(columns[i].getColumnTitle()); |
| tc.setWidth(columns[i].getWidthInPixels(tree)); |
| } |
| } |
| |
| private IProfile getSelectedSnapshot() { |
| Object selected = ((IStructuredSelection) configsViewer.getSelection()).getFirstElement(); |
| if (selected != null && selected instanceof RollbackProfileElement) |
| return ((RollbackProfileElement) selected).getProfileSnapshot(new NullProgressMonitor()); |
| return null; |
| } |
| |
| boolean revert() { |
| final IProfile snapshot = getSelectedSnapshot(); |
| if (snapshot == null) |
| return false; |
| final IProvisioningPlan[] plan = new IProvisioningPlan[1]; |
| IRunnableWithProgress runnable = new IRunnableWithProgress() { |
| @Override |
| public void run(IProgressMonitor monitor) { |
| IProfile currentProfile; |
| IProfileRegistry registry = ProvUI.getProfileRegistry(getSession()); |
| IPlanner planner = (IPlanner) getSession().getProvisioningAgent().getService(IPlanner.SERVICE_NAME); |
| currentProfile = registry.getProfile(profileId); |
| plan[0] = planner.getDiffPlan(currentProfile, snapshot, monitor); |
| } |
| }; |
| ProgressMonitorDialog dialog = new ProgressMonitorDialog(getShell()); |
| try { |
| dialog.run(true, true, runnable); |
| } catch (InvocationTargetException e) { |
| ProvUI.handleException(e.getCause(), null, StatusManager.SHOW | StatusManager.LOG); |
| } catch (InterruptedException e) { |
| // nothing to report |
| } |
| // the dialog does not throw OperationCanceledException so we have to |
| // check the monitor |
| if (dialog.getProgressMonitor().isCanceled()) |
| return false; |
| |
| boolean reverted = false; |
| if (plan[0] != null) { |
| if (plan[0].getStatus().isOK()) { |
| // We use a default provisioning context (all repos) because we have no other |
| // way currently to figure out which sites the user wants to contact |
| ProfileModificationJob op = new ProfileModificationJob(ProvUIMessages.RevertDialog_RevertOperationLabel, getSession(), profileId, plan[0], new ProvisioningContext(getSession().getProvisioningAgent())); |
| // we want to force a restart (not allow apply changes) |
| op.setRestartPolicy(ProvisioningJob.RESTART_ONLY); |
| getProvisioningUI().schedule(op, StatusManager.SHOW | StatusManager.LOG); |
| reverted = true; |
| } else if (plan[0].getStatus().getSeverity() != IStatus.CANCEL) { |
| ProvUI.reportStatus(plan[0].getStatus(), StatusManager.LOG | StatusManager.SHOW); |
| // This message has no effect in an installation dialog |
| // setMessage(ProvUIMessages.ProfileModificationWizardPage_UnexpectedError, IMessageProvider.ERROR); |
| } |
| } |
| return reverted; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.equinox.p2.ui.ICopyable#copyToClipboard(org.eclipse.swt.widgets.Control) |
| */ |
| @Override |
| public void copyToClipboard(Control activeControl) { |
| String text = ""; //$NON-NLS-1$ |
| if (activeControl == configContentsViewer.getControl()) { |
| text = CopyUtils.getIndentedClipboardText(((IStructuredSelection) configContentsViewer.getSelection()).toArray(), labelProvider); |
| } else if (activeControl == configsViewer.getControl()) { |
| Object[] elements = ((IStructuredSelection) configsViewer.getSelection()).toArray(); |
| StringBuffer buffer = new StringBuffer(); |
| for (int i = 0; i < elements.length; i++) { |
| if (elements[i] instanceof RollbackProfileElement) { |
| if (i > 0) |
| buffer.append(CopyUtils.NEWLINE); |
| buffer.append(((RollbackProfileElement) elements[i]).getLabel(elements[i])); |
| } |
| } |
| text = buffer.toString(); |
| } else |
| return; |
| if (text.length() == 0) |
| return; |
| Clipboard clipboard = new Clipboard(PlatformUI.getWorkbench().getDisplay()); |
| clipboard.setContents(new Object[] {text}, new Transfer[] {TextTransfer.getInstance()}); |
| clipboard.dispose(); |
| } |
| |
| void deleteSelectedSnapshots() { |
| IStructuredSelection selection = (IStructuredSelection) configsViewer.getSelection(); |
| if (selection.isEmpty()) |
| return; |
| String title = selection.size() == 1 ? ProvUIMessages.RevertProfilePage_DeleteSingleConfigurationTitle : ProvUIMessages.RevertProfilePage_DeleteMultipleConfigurationsTitle; |
| String confirmMessage = selection.size() == 1 ? ProvUIMessages.RevertProfilePage_ConfirmDeleteSingleConfig : ProvUIMessages.RevertProfilePage_ConfirmDeleteMultipleConfigs; |
| if (MessageDialog.openConfirm(configsViewer.getControl().getShell(), title, confirmMessage)) { |
| Iterator<?> iter = selection.iterator(); |
| while (iter.hasNext()) { |
| Object selected = iter.next(); |
| // If it is a recognized element and it is not the current profile, then it can be deleted. |
| if (selected instanceof RollbackProfileElement && !((RollbackProfileElement) selected).isCurrentProfile()) { |
| RollbackProfileElement snapshot = (RollbackProfileElement) selected; |
| IProfileRegistry registry = ProvUI.getProfileRegistry(getSession()); |
| if (registry != null) { |
| try { |
| registry.removeProfile(profileId, snapshot.getTimestamp()); |
| } catch (ProvisionException e) { |
| ProvUI.handleException(e, null, StatusManager.SHOW | StatusManager.LOG); |
| } |
| } |
| } |
| } |
| configsViewer.refresh(); |
| } |
| } |
| |
| ProvisioningSession getSession() { |
| return getProvisioningUI().getSession(); |
| } |
| |
| ProvisioningUI getProvisioningUI() { |
| // if a UI has not been set then assume that the current default UI is the right thing |
| if (ui == null) |
| return ui = ProvisioningUI.getDefaultUI(); |
| return ui; |
| } |
| |
| protected IStructuredSelection getSelection() { |
| return (IStructuredSelection) configsViewer.getSelection(); |
| } |
| |
| /** |
| * Set the provisioning UI to use with this page |
| * |
| * @param value the provisioning ui to use |
| * @since 2.1 |
| */ |
| public void setProvisioningUI(ProvisioningUI value) { |
| ui = value; |
| } |
| } |