package org.activiti.designer.property; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.activiti.bpmn.model.CallActivity; import org.activiti.designer.Activator; import org.activiti.designer.PluginImage; import org.activiti.designer.eclipse.common.ActivitiPlugin; import org.activiti.designer.util.ActivitiConstants; import org.activiti.designer.util.dialog.ActivitiResourceSelectionDialog; import org.activiti.designer.util.eclipse.ActivitiUiUtil; import org.activiti.designer.util.property.ActivitiPropertySection; import org.activiti.designer.util.workspace.ActivitiWorkspaceUtil; import org.apache.commons.lang.StringUtils; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.TwoPaneElementSelector; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.model.WorkbenchLabelProvider; import org.eclipse.ui.views.properties.tabbed.ITabbedPropertyConstants; import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage; import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory; /** * Adds the called element of the call activity to the "Main Config" properties sheet. Provides a * possibility to lookup a process with the given element ID in the currently open workspace * projects as well as a choice of all available projects. * * @since 5.12 */ public class PropertyCallActivitySection extends ActivitiPropertySection implements ITabbedPropertyConstants { /** The text field of the process ID to call */ private Text calledElementText; /** * A button that becomes active in case a process is found in the current workspace with this * the ID */ private Button openCalledElementButton; /** * A button that allows to choose a called element among all currently found processes. */ private Button chooseCalledElementButton; /** * Checks, whether the given process ID refers to a process this call activity might lead to. * * @param calledElement the process ID to check * @return <code>true</code> in case such a process ID exists, <code>false</code> otherwise. */ private boolean isCalledElementExisting(final String calledElement) { final Set<IFile> resources = ActivitiWorkspaceUtil.getDiagramDataFilesByProcessId(calledElement); return !resources.isEmpty(); } /** * Evaluates the current state of the button to open the corresponding process diagram of the * given process ID. In case the called element is empty or a not found, the button will be * disabled, otherwise enabled. */ private void evaluateOpenCalledElementButtonEnabledStatus() { final String calledElement = calledElementText.getText(); if (StringUtils.isBlank(calledElement) || !isCalledElementExisting(calledElement)) { openCalledElementButton.setEnabled(false); openCalledElementButton.setToolTipText(null); } else { openCalledElementButton.setEnabled(true); openCalledElementButton.setToolTipText("Click to open the called element's process diagram"); } } /** * This modification listener will call {@link #evaluateOpenCalledElementButtonEnabledStatus()} * in case the text in the field {@link #calledElementText} has changed. */ private ModifyListener calledElementTextModified = new ModifyListener() { @Override public void modifyText(ModifyEvent event) { evaluateOpenCalledElementButtonEnabledStatus(); } }; /** * This will be triggered as soon as the focus of the field {@link #calledElementText} changed. It * reacts on focus loss and checks, whether the new value of the text is no longer equal to the * call activity. In that case it runs a model change to reflect the change in the diagram. */ private FocusListener calledElementTextFocusChanged = new FocusListener() { @Override public void focusLost(FocusEvent event) { final CallActivity callActivity = getDefaultBusinessObject(CallActivity.class); final String calledElement = calledElementText.getText(); if (callActivity != null && !StringUtils.equals(calledElement, callActivity.getCalledElement())) { final TransactionalEditingDomain ted = getTransactionalEditingDomain(); ActivitiUiUtil.runModelChange(new Runnable() { @Override public void run() { callActivity.setCalledElement(calledElement); } }, ted, ActivitiConstants.DEFAULT_MODEL_CHANGE_TEXT); } } @Override public void focusGained(FocusEvent event) { // intentionally left blank } }; /** * A private process ID label provider that simply returns the second element of the pair built * from process ID and data file. This is used in the selection list for an available process. */ private static class ProcessIdLabelProvider extends LabelProvider { @Override public String getText(Object element) { return (String) ((Object[]) element)[1]; } } /** * This label provider is used in the selection list for a diagram in the lower list. It utilizes * a workbench label provider to retrieve the appropriate image for the diagram. */ private static class DiagramLabelProvider extends LabelProvider { private WorkbenchLabelProvider labelProvider = new WorkbenchLabelProvider(); @Override public Image getImage(Object element) { final IResource resource = (IResource) ((Object[]) element)[0]; return labelProvider.getImage(resource); } @Override public String getText(Object element) { final IResource resource = (IResource) ((Object[]) element)[0]; return resource.getFullPath().makeRelative().toString(); } } /** * This listener is called in case the user presses the button to choose a process from one of * the existing processes in any open project. */ private SelectionListener chooseCalledElementSelected = new SelectionListener() { @Override public void widgetSelected(SelectionEvent event) { final Map<IFile, Set<String>> processIdsByDataFiles = ActivitiWorkspaceUtil.getAllProcessIdsByDiagramDataFile(); // we now need to make a list out of this, as the TwoPaneElementSelector wants it this way final List<Object[]> selectorInput = new ArrayList<Object[]>(); for (final Entry<IFile, Set<String>> entry : processIdsByDataFiles.entrySet()) { for (final String processId : entry.getValue()) { entry.getKey().getFullPath(); selectorInput.add(new Object[] { entry.getKey(), processId }); } } final TwoPaneElementSelector dialog = new TwoPaneElementSelector(Display.getCurrent().getActiveShell() , new DiagramLabelProvider() , new ProcessIdLabelProvider()); dialog.setTitle("Choose Called Element for Call Activity"); dialog.setMessage("Choose a diagram and a process"); dialog.setBlockOnOpen(true); dialog.setElements(selectorInput.toArray()); dialog.setUpperListLabel("Diagrams"); dialog.setLowerListLabel("Processes"); if (dialog.open() == Window.OK) { final Object[] data = (Object[]) dialog.getFirstResult(); calledElementText.setText((String) data[1]); } } @Override public void widgetDefaultSelected(SelectionEvent event) { // intentionally left blank } }; /** * This listener is called in case the user clicks the button to open the selected process * model. */ private SelectionListener openCalledElementSelected = new SelectionListener() { @Override public void widgetSelected(SelectionEvent event) { final String calledElement = calledElementText.getText(); final Set<IFile> resources = ActivitiWorkspaceUtil.getDiagramDataFilesByProcessId(calledElement); if (resources.size() == 1) { // open diagram openDiagramForBpmnFile(resources.iterator().next()); } else if (resources.size() > 1) { final ActivitiResourceSelectionDialog dialog = new ActivitiResourceSelectionDialog( Display.getCurrent().getActiveShell(), resources.toArray(new IResource[] {})); dialog.setTitle("Multiple Processes Found"); dialog.setMessage("Select a Process Model to use (* = any string, ? = any char):"); dialog.setBlockOnOpen(true); dialog.setInitialPattern("*"); if (dialog.open() == Window.OK) { final Object[] result = dialog.getResult(); openDiagramForBpmnFile((IFile) result[0]); } } } /** * Opens the given diagram specified by the given data file in a new editor. In case an error * occurs while doing so, opens an error dialog. * * @param dataFile the data file to use for the new editor to open */ private void openDiagramForBpmnFile(IFile dataFile) { if (dataFile.exists()) { final IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); try { IDE.openEditor(activePage, dataFile, ActivitiConstants.DIAGRAM_EDITOR_ID, true); } catch (PartInitException exception) { final IStatus status = new Status(IStatus.ERROR, ActivitiPlugin.getID() , "Error while opening new editor.", exception); ErrorDialog.openError(Display.getCurrent().getActiveShell() , "Error Opening Activiti Diagram", null, status); } } } @Override public void widgetDefaultSelected(SelectionEvent event) { widgetSelected(event); } }; @Override public void createControls(Composite parent, TabbedPropertySheetPage tabbedPropertySheetPage) { super.createControls(parent, tabbedPropertySheetPage); final TabbedPropertySheetWidgetFactory factory = getWidgetFactory(); final Composite composite = factory.createFlatFormComposite(parent); FormData data = null; openCalledElementButton = factory.createButton(composite, StringUtils.EMPTY, SWT.PUSH); openCalledElementButton.setImage(Activator.getImage(PluginImage.ACTION_GO)); data = new FormData(); data.right = new FormAttachment(100, -HSPACE); openCalledElementButton.setLayoutData(data); openCalledElementButton.addSelectionListener(openCalledElementSelected); chooseCalledElementButton = factory.createButton(composite, "\u2026", SWT.PUSH); chooseCalledElementButton.setToolTipText( "Click to open a dialog to choose from all found processes."); data = new FormData(); data.right = new FormAttachment(openCalledElementButton, -HSPACE); chooseCalledElementButton.setLayoutData(data); chooseCalledElementButton.addSelectionListener(chooseCalledElementSelected); // by default, we set the text field to an empty text. The update() method will deal with this calledElementText = factory.createText(composite, ActivitiConstants.EMPTY_STRING); data = new FormData(); data.left = new FormAttachment(0, 150); data.right = new FormAttachment(chooseCalledElementButton, -HSPACE); data.top = new FormAttachment(0, VSPACE); calledElementText.setLayoutData(data); calledElementText.addModifyListener(calledElementTextModified); calledElementText.addFocusListener(calledElementTextFocusChanged); CLabel elementLabel = factory.createCLabel(composite, "Called Element:"); data = new FormData(); data.left = new FormAttachment(0, 0); data.right = new FormAttachment(calledElementText, -HSPACE); data.top = new FormAttachment(calledElementText, 0, SWT.TOP); elementLabel.setLayoutData(data); } @Override public void refresh() { calledElementText.removeFocusListener(calledElementTextFocusChanged); calledElementText.removeModifyListener(calledElementTextModified); final CallActivity callActivity = getDefaultBusinessObject(CallActivity.class); if (callActivity != null) { final String calledElement = callActivity.getCalledElement(); if (calledElement == null) { calledElementText.setText(ActivitiConstants.EMPTY_STRING); } else { calledElementText.setText(calledElement); } } calledElementText.addModifyListener(calledElementTextModified); calledElementText.addFocusListener(calledElementTextFocusChanged); } }