blob: 4442f83de8ae8cadfa24abfe5ced05547e193d25 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2022 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Christoph Läubrich [Bug 567506] - TargetLocationsGroup.handleEdit() should activate bundles if necessary
* [Bug 568865] - add advanced editing capabilities for custom target platforms
*******************************************************************************/
package org.eclipse.pde.internal.ui.shared.target;
import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.IWizard;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.pde.core.target.*;
import org.eclipse.pde.internal.core.PDECore;
import org.eclipse.pde.internal.ui.PDEPlugin;
import org.eclipse.pde.internal.ui.SWTFactory;
import org.eclipse.pde.internal.ui.editor.FormLayoutFactory;
import org.eclipse.pde.internal.ui.editor.targetdefinition.TargetEditor;
import org.eclipse.pde.internal.ui.wizards.target.TargetDefinitionContentPage;
import org.eclipse.pde.ui.target.ITargetLocationHandler;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.progress.UIJob;
/**
* UI part that can be added to a dialog or to a form editor. Contains a table
* displaying the bundle containers of a target definition. Also has buttons to
* add, edit and remove bundle containers of varying types.
*
* @see TargetEditor
* @see TargetDefinitionContentPage
* @see ITargetDefinition
* @see ITargetLocation
*/
public class TargetLocationsGroup {
private static final String BUTTON_STATE = "ButtonState"; //$NON-NLS-1$
private enum DeleteButtonState {
NONE, REMOVE, ENABLE, DISABLE, TOGGLE;
static DeleteButtonState computeState(boolean canRemove, boolean canEnable, boolean canDisable) {
if (canRemove) {
if (canEnable || canDisable) {
// a mixture of actions is currently selected
return NONE;
}
return REMOVE;
}
if (canEnable) {
if (canDisable) {
return TOGGLE;
}
return ENABLE;
} else if (canDisable) {
return DISABLE;
}
return NONE;
}
}
private TreeViewer fTreeViewer;
private Action fCopySelectionAction;
private Button fAddButton;
private Button fEditButton;
private Button fRemoveButton;
private Button fUpdateButton;
private Button fReloadButton;
private Button fExpandCollapseButton;
private Button fShowContentButton;
private ITargetDefinition fTarget;
private ListenerList<ITargetChangedListener> fChangeListeners = new ListenerList<>();
private ListenerList<ITargetChangedListener> fReloadListeners = new ListenerList<>();
private static final TargetLocationHandlerAdapter ADAPTER = new TargetLocationHandlerAdapter();
/**
* Creates this part using the form toolkit and adds it to the given
* composite.
*
* @param parent
* parent composite
* @param toolkit
* toolkit to create the widgets with
* @return generated instance of the table part
*/
public static TargetLocationsGroup createInForm(Composite parent, FormToolkit toolkit) {
TargetLocationsGroup contentTable = new TargetLocationsGroup();
contentTable.createFormContents(parent, toolkit);
return contentTable;
}
/**
* Creates this part using standard dialog widgets and adds it to the given
* composite.
*
* @param parent
* parent composite
* @return generated instance of the table part
*/
public static TargetLocationsGroup createInDialog(Composite parent) {
TargetLocationsGroup contentTable = new TargetLocationsGroup();
contentTable.createDialogContents(parent);
return contentTable;
}
/**
* Private constructor, use one of {@link #createInDialog(Composite)} or
* {@link #createInForm(Composite, FormToolkit)}.
*/
private TargetLocationsGroup() {
}
/**
* Adds a listener to the set of listeners that will be notified when the
* bundle containers are modified. This method has no effect if the listener
* has already been added.
*
* @param listener
* target changed listener to add
*/
public void addTargetChangedListener(ITargetChangedListener listener) {
fChangeListeners.add(listener);
}
/**
* Adds a listener to the set of listeners that will be notified when target
* is reloaded. This method has no effect if the listener has already been
* added.
*
* @param listener
* target changed listener to add
*/
public void addTargetReloadListener(ITargetChangedListener listener) {
fReloadListeners.add(listener);
}
/**
* Creates the part contents from a toolkit
*
* @param parent
* parent composite
* @param toolkit
* form toolkit to create widgets
*/
private void createFormContents(Composite parent, FormToolkit toolkit) {
Composite comp = toolkit.createComposite(parent);
comp.setLayout(FormLayoutFactory.createSectionClientGridLayout(false, 2));
comp.setLayoutData(new GridData(GridData.FILL_BOTH | GridData.GRAB_VERTICAL));
Tree atree = toolkit.createTree(comp, SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI);
atree.setLayout(new GridLayout());
GridData gd = new GridData(GridData.FILL_BOTH);
atree.setLayoutData(gd);
initializeTree(atree);
Composite buttonComp = toolkit.createComposite(comp);
GridLayout layout = new GridLayout();
layout.marginWidth = layout.marginHeight = 0;
buttonComp.setLayout(layout);
buttonComp.setLayoutData(new GridData(GridData.FILL_VERTICAL));
fAddButton = toolkit.createButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Add, SWT.PUSH);
fEditButton = toolkit.createButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Edit, SWT.PUSH);
fRemoveButton = toolkit.createButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Remove, SWT.PUSH);
fUpdateButton = toolkit.createButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Update, SWT.PUSH);
fUpdateButton.setToolTipText(Messages.TargetLocationsGroup_update);
fReloadButton = toolkit.createButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Reload, SWT.PUSH);
fReloadButton.setToolTipText(Messages.TargetLocationsGroup_reload);
fExpandCollapseButton = toolkit.createButton(buttonComp, Messages.BundleContainerTable_Btn_Text_ExpandAll, SWT.PUSH);
fShowContentButton = toolkit.createButton(comp, Messages.TargetLocationsGroup_1, SWT.CHECK);
initializeTreeViewer(atree);
initializeButtons();
toolkit.paintBordersFor(comp);
}
/**
* Creates the part contents using SWTFactory
*
* @param parent
* parent composite
*/
private void createDialogContents(Composite parent) {
Composite comp = SWTFactory.createComposite(parent, 2, 1, GridData.FILL_BOTH, 0, 0);
Tree atree = new Tree(comp, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER | SWT.MULTI);
atree.setFont(comp.getFont());
atree.setLayout(new GridLayout());
GridData gd = new GridData(GridData.FILL_BOTH);
gd.widthHint = 200;
atree.setLayoutData(gd);
initializeTree(atree);
Composite buttonComp = SWTFactory.createComposite(comp, 2, 1, GridData.FILL_BOTH);
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
buttonComp.setLayout(layout);
buttonComp.setLayoutData(new GridData(GridData.FILL_VERTICAL));
fAddButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Add, null);
fEditButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Edit, null);
fRemoveButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Remove, null);
fUpdateButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Update, null);
fReloadButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_Reload, null);
fExpandCollapseButton = SWTFactory.createPushButton(buttonComp, Messages.BundleContainerTable_Btn_Text_ExpandAll, null);
fShowContentButton = SWTFactory.createCheckButton(comp, Messages.TargetLocationsGroup_1, null, false, 2);
initializeTreeViewer(atree);
initializeButtons();
}
private void initializeTree(Tree tree) {
tree.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.DEL && fRemoveButton.getEnabled()) {
handleRemove();
} else if (e.keyCode == 'c' && (e.stateMask & SWT.CTRL) != 0) {
fCopySelectionAction.run();
}
}
});
}
/**
* Sets up the tree viewer using the given tree
*/
private void initializeTreeViewer(Tree tree) {
fTreeViewer = new TreeViewer(tree);
fTreeViewer.setContentProvider(new TargetLocationContentProvider());
fTreeViewer.setLabelProvider(new StyledBundleLabelProvider(true, false));
fTreeViewer.setComparator(new ViewerComparator() {
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
// Status at the end of the list
if (e1 instanceof IStatus && !(e2 instanceof IStatus)) {
return 1;
}
if (e2 instanceof IStatus && !(e1 instanceof IStatus)) {
return -1;
}
return super.compare(viewer, e1, e2);
}
});
fTreeViewer.addSelectionChangedListener(event -> updateButtons());
fTreeViewer.addDoubleClickListener(event -> {
if (!event.getSelection().isEmpty()) {
handleEdit();
}
});
fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
createContextMenu(fTreeViewer.getTree());
fTreeViewer.getTree().addMouseListener(new MouseListener() {
@Override
public void mouseDoubleClick(MouseEvent e) {
setExpandCollapseState();
}
@Override
public void mouseDown(MouseEvent e) {
}
@Override
public void mouseUp(MouseEvent e) {
setExpandCollapseState();
}
});
fTreeViewer.getTree().addKeyListener(new KeyListener() {
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
setExpandCollapseState();
}
});
}
private void setExpandCollapseState() {
if (fTreeViewer == null)
return;
if (fTreeViewer.getVisibleExpandedElements().length == 0) {
fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_ExpandAll);
} else {
fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_CollapseAll);
}
}
private void createContextMenu(Tree tree) {
fCopySelectionAction = new CopyTreeSelectionAction(tree);
MenuManager menuManager = new MenuManager();
menuManager.add(fCopySelectionAction);
Menu menu = menuManager.createContextMenu(tree);
tree.setMenu(menu);
}
/**
* Sets up the buttons, the button fields must already be created before
* calling this method
*/
private void initializeButtons() {
fAddButton.addSelectionListener(widgetSelectedAdapter(e -> handleAdd()));
fAddButton.setLayoutData(new GridData());
SWTFactory.setButtonDimensionHint(fAddButton);
fEditButton.addSelectionListener(widgetSelectedAdapter(e -> handleEdit()));
fEditButton.setLayoutData(new GridData());
fEditButton.setEnabled(false);
SWTFactory.setButtonDimensionHint(fEditButton);
fRemoveButton.addSelectionListener(widgetSelectedAdapter(e -> handleRemove()));
fRemoveButton.setLayoutData(new GridData());
fRemoveButton.setEnabled(false);
SWTFactory.setButtonDimensionHint(fRemoveButton);
fUpdateButton.addSelectionListener(widgetSelectedAdapter(e -> handleUpdate()));
fUpdateButton.setLayoutData(new GridData());
fUpdateButton.setEnabled(false);
SWTFactory.setButtonDimensionHint(fUpdateButton);
fReloadButton.addSelectionListener(widgetSelectedAdapter(e -> handleReload()));
fReloadButton.setLayoutData(new GridData());
fReloadButton.setEnabled(true);
SWTFactory.setButtonDimensionHint(fReloadButton);
fExpandCollapseButton.addSelectionListener(widgetSelectedAdapter(e -> toggleCollapse()));
fExpandCollapseButton.setLayoutData(new GridData());
fExpandCollapseButton.setEnabled(false);
SWTFactory.setButtonDimensionHint(fExpandCollapseButton);
fShowContentButton.addSelectionListener(widgetSelectedAdapter(e -> {
((TargetLocationContentProvider) fTreeViewer.getContentProvider())
.setShowLocationContent(fShowContentButton.getSelection());
fTreeViewer.refresh();
fTreeViewer.expandAll();
fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_CollapseAll);
}));
fShowContentButton.setLayoutData(new GridData());
SWTFactory.setButtonDimensionHint(fShowContentButton);
}
/**
* Sets the target definition model to use as input for the tree, can be
* called with different models to change the tree's input.
*
* @param target
* target model
*/
public void setInput(ITargetDefinition target) {
fTarget = target;
boolean isCollapsed = fTreeViewer.getVisibleExpandedElements().length == 0;
fTreeViewer.setInput(fTarget);
if (isCollapsed)
fTreeViewer.collapseAll();
updateButtons();
}
private void handleAdd() {
AddBundleContainerWizard wizard = new AddBundleContainerWizard(fTarget);
Shell parent = fTreeViewer.getTree().getShell();
WizardDialog dialog = new WizardDialog(parent, wizard);
if (dialog.open() != Window.CANCEL) {
contentsChanged(false);
fTreeViewer.refresh();
updateButtons();
}
}
private void handleEdit() {
ITreeSelection selection = fTreeViewer.getStructuredSelection();
TreePath[] paths = selection.getPaths();
if (paths.length == 1) {
IWizard editWizard = ADAPTER.getEditWizard(fTarget, paths[0]);
if (editWizard != null) {
Shell parent = fTreeViewer.getTree().getShell();
WizardDialog wizard = new WizardDialog(parent, editWizard);
if (wizard.open() == Window.OK) {
updateXML();
contentsChanged(false);
fTreeViewer.refresh();
updateButtons();
}
}
}
}
private void updateXML() {
fTarget.setTargetLocations(fTarget.getTargetLocations());
}
private void handleRemove() {
ITreeSelection selection = fTreeViewer.getStructuredSelection();
DeleteButtonState state = (DeleteButtonState) Objects.requireNonNullElse(fRemoveButton.getData(BUTTON_STATE),
DeleteButtonState.NONE);
if (selection.isEmpty() || state == DeleteButtonState.NONE) {
fRemoveButton.setEnabled(false);
return;
}
IStatus tstatus = fTarget.getStatus();
IStatus status;
if (state == DeleteButtonState.REMOVE) {
status = log(ADAPTER.remove(fTarget, selection.getPaths()));
} else {
status = log(ADAPTER.toggle(fTarget, selection.getPaths()));
}
boolean forceReload = (tstatus != null && !tstatus.isOK())
|| (status != null && status.isOK() && status.getCode() == ITargetLocationHandler.STATUS_FORCE_RELOAD);
updateXML();
contentsChanged(forceReload);
fTreeViewer.refresh();
updateButtons();
}
private void handleUpdate() {
ITreeSelection selection = fTreeViewer.getStructuredSelection();
if (selection.isEmpty()) {
fUpdateButton.setEnabled(false);
return;
}
List<IJobFunction> updateActions = Collections
.singletonList(monitor -> log(ADAPTER.update(fTarget, selection.getPaths(), monitor)));
JobChangeAdapter listener = new JobChangeAdapter() {
@Override
public void done(final IJobChangeEvent event) {
UIJob job = new UIJob(Messages.UpdateTargetJob_UpdateJobName) {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
IStatus result = event.getJob().getResult();
if (!result.isOK()) {
if (!fTreeViewer.getControl().isDisposed()) {
ErrorDialog.openError(fTreeViewer.getTree().getShell(),
Messages.TargetLocationsGroup_TargetUpdateErrorDialog, result.getMessage(),
result);
}
} else if (result.getCode() != ITargetLocationHandler.STATUS_CODE_NO_CHANGE) {
// Update was successful and changed the target, if
// dialog/editor still open, update it
if (!fTreeViewer.getControl().isDisposed()) {
contentsChanged(true);
fTreeViewer.refresh(true);
updateButtons();
}
// If the target is the current platform, run a load
// job for the user
try {
ITargetPlatformService service = PDECore.getDefault()
.acquireService(ITargetPlatformService.class);
if (service != null) {
ITargetHandle currentTarget = service.getWorkspaceTargetHandle();
if (fTarget.getHandle().equals(currentTarget))
LoadTargetDefinitionJob.load(fTarget);
}
} catch (CoreException e) {
// do nothing if we could not set the current
// target.
}
}
return Status.OK_STATUS;
}
};
job.schedule();
}
};
UpdateTargetJob.update(updateActions, listener);
}
private void updateButtons() {
ITreeSelection selection = fTreeViewer.getStructuredSelection();
if (selection.isEmpty()) {
fRemoveButton.setEnabled(false);
fRemoveButton.setText(Messages.BundleContainerTable_Btn_Text_Remove);
fRemoveButton.setData(BUTTON_STATE, DeleteButtonState.NONE);
fUpdateButton.setEnabled(false);
fEditButton.setEnabled(false);
if(fTreeViewer !=null) {
setExpandCollapseState();
}
return;
}
boolean canRemove = false;
boolean canEdit = false;
boolean canUpdate = false;
boolean canEnable = false;
boolean canDisable = false;
TreePath[] paths = selection.getPaths();
for (TreePath path : paths) {
canRemove |= ADAPTER.canRemove(fTarget, path);
canDisable |= ADAPTER.canDisable(fTarget, path);
canEnable |= ADAPTER.canEnable(fTarget, path);
canUpdate |= ADAPTER.canUpdate(fTarget, path);
canEdit = paths.length == 1 && ADAPTER.canEdit(fTarget, path);
}
fEditButton.setEnabled(canEdit);
fUpdateButton.setEnabled(canUpdate);
DeleteButtonState state = DeleteButtonState.computeState(canRemove, canEnable, canDisable);
switch (state)
{
case DISABLE:
fRemoveButton.setText(Messages.BundleContainerTable_Btn_Text_Disable);
break;
case ENABLE:
fRemoveButton.setText(Messages.BundleContainerTable_Btn_Text_Enable);
break;
case TOGGLE:
fRemoveButton.setText(Messages.BundleContainerTable_Btn_Text_Toggle);
break;
default:
fRemoveButton.setText(Messages.BundleContainerTable_Btn_Text_Remove);
break;
}
fRemoveButton.setEnabled(state != DeleteButtonState.NONE);
fRemoveButton.setData(BUTTON_STATE, state);
}
private void handleReload() {
log(ADAPTER.reload(fTarget, fTarget.getTargetLocations(), new NullProgressMonitor()));
Job job = new UIJob("Reloading...") { //$NON-NLS-1$
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
contentsReload();
return Status.OK_STATUS;
}
};
job.schedule();
}
private void toggleCollapse() {
if (fTreeViewer == null)
return;
if (fTreeViewer.getVisibleExpandedElements().length == 0) {
fTreeViewer.expandAll();
fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_CollapseAll);
} else {
fTreeViewer.collapseAll();
fExpandCollapseButton.setText(Messages.BundleContainerTable_Btn_Text_ExpandAll);
}
}
/**
* Informs the reporter for this table that something has changed and is
* dirty.
*/
private void contentsChanged(boolean force) {
for (ITargetChangedListener listener : fChangeListeners) {
listener.contentsChanged(fTarget, this, true, force);
}
}
/**
* Reloads the target
*
*/
private void contentsReload() {
for (ITargetChangedListener listener : fReloadListeners) {
listener.contentsChanged(fTarget, this, true, true);
}
}
private static IStatus log(IStatus status) {
if (status != null && !status.isOK()) {
PDEPlugin.log(status);
}
return status;
}
public void setExpandCollapseState(boolean b) {
if (fExpandCollapseButton != null)
fExpandCollapseButton.setEnabled(b);
}
}