package org.aperteworkflow.editor.stepeditor.user; import com.vaadin.data.Container; import com.vaadin.data.Item; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.data.util.HierarchicalContainer; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; import com.vaadin.event.DataBoundTransferable; import com.vaadin.event.Transferable; import com.vaadin.event.dd.DragAndDropEvent; import com.vaadin.event.dd.DropHandler; import com.vaadin.event.dd.acceptcriteria.AcceptAll; import com.vaadin.event.dd.acceptcriteria.AcceptCriterion; import com.vaadin.terminal.Resource; import com.vaadin.terminal.StreamResource; import com.vaadin.ui.*; import com.vaadin.ui.Tree.TreeDragMode; import com.vaadin.ui.Window.Notification; import com.vaadin.ui.themes.Reindeer; import org.aperteworkflow.editor.domain.Permission; import org.aperteworkflow.editor.stepeditor.AbstractStepEditorWindow; import org.aperteworkflow.editor.stepeditor.StepEditorApplication; import org.aperteworkflow.editor.stepeditor.user.JSONHandler.ParsingFailedException; import org.aperteworkflow.editor.stepeditor.user.JSONHandler.WidgetNotFoundException; import org.aperteworkflow.editor.ui.permission.PermissionDefinition; import org.aperteworkflow.editor.ui.permission.PermissionEditor; import org.aperteworkflow.editor.ui.permission.PermissionProvider; import org.aperteworkflow.editor.ui.property.PropertiesForm; import org.aperteworkflow.editor.ui.property.PropertiesPanel; import org.aperteworkflow.util.vaadin.VaadinUtility; import org.vaadin.dialogs.ConfirmDialog; import pl.net.bluesoft.rnd.util.i18n.I18NSource; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import static org.aperteworkflow.util.vaadin.VaadinUtility.htmlLabel; import static org.aperteworkflow.util.vaadin.VaadinUtility.styled; import static pl.net.bluesoft.util.lang.FormatUtil.nvl; public class UserStepEditorWindow extends AbstractStepEditorWindow implements Handler, ValueChangeListener { private static final Logger logger = Logger.getLogger(UserStepEditorWindow.class.getName()); private static final Action[] COMMON_ACTIONS = new Action[] {}; private HierarchicalContainer stepTreeContainer; private Tree stepTree; private Component availableWidgetsPane; private WidgetItemInStep rootItem; private TextField assigneeField; private TextField candidateGroupsField; private TextField swimlaneField; private TextField descriptionField; private RichTextArea commentaryTextArea; private WidgetFormWindow paramPanel; private PermissionEditor permissionEditor; private Action actionDelete; private Action[] actions; private Collection<Permission> permissions; public UserStepEditorWindow(StepEditorApplication application, String jsonConfig, String url, String stepName, String stepType) { super(application, jsonConfig, url, stepName, stepType); permissions = new LinkedHashSet<Permission>(); actionDelete = new Action(I18NSource.ThreadUtil.getThreadI18nSource().getMessage("stepTree.action.delete")); actions = new Action[] { actionDelete }; } @Override public ComponentContainer init() { ComponentContainer comp = buildLayout(); if (jsonConfig != null && jsonConfig.trim().length() > 0) { if (stepTreeContainer != null && rootItem != null) { loadJSONConfig(); } } permissionEditor.loadData(); return comp; } private ComponentContainer buildLayout() { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); prepareAvailableWidgetsComponent(); stepTree = new Tree(messages.getMessage("stepTree.title"), getCurrentStep()); stepTree.setItemCaptionMode(Tree.ITEM_CAPTION_MODE_PROPERTY); stepTree.setItemCaptionPropertyId("name"); stepTree.setDragMode(TreeDragMode.NODE); stepTree.addActionHandler(this); stepTree.addListener(this); stepTree.setSelectable(true); stepTree.setImmediate(true); stepTree.setItemDescriptionGenerator(new PropertiesDescriptionGenerator()); stepTree.expandItemsRecursively(rootItem); stepTree.setDropHandler(new TreeDropHandler(stepTree, stepTreeContainer)); paramPanel = new WidgetFormWindow(); permissionEditor = new PermissionEditor(); permissionEditor.setMargin(true); permissionEditor.setProvider(new PermissionProvider() { @Override public Collection<Permission> getPermissions() { return permissions; } @Override public Collection<PermissionDefinition> getPermissionDefinitions() { PermissionDefinition perm1 = new PermissionDefinition(); perm1.setKey("SEARCH"); perm1.setDescription("editor.permissions.description.step.SEARCH"); return Arrays.asList(perm1); } @Override public boolean isNewPermissionDefinitionAllowed() { return false; } @Override public void addPermission(Permission permission) { permissions.add(permission); } @Override public void removePermission(Permission permission) { permissions.remove(permission); } }); VerticalLayout assignmentLayout = prepareAssignmentLayout(); VerticalLayout stepDefinitionLayout = buildStateDefinitionLayout(); VerticalLayout stepLayout = buildWidgetEditorTabContent(); VerticalLayout vl = new VerticalLayout(); vl.setSizeFull(); vl.setSpacing(true); vl.addComponent(new Label(messages.getMessage("userstep.editor.instructions"), Label.CONTENT_XHTML)); TabSheet ts = new TabSheet(); ts.setSizeFull(); ts.addTab(stepLayout, messages.getMessage("userstep.editor.widgets.tabcaption")); ts.addTab(stepDefinitionLayout, messages.getMessage("userstep.state.tabcaption")); ts.addTab(assignmentLayout, messages.getMessage("userstep.editor.assignment.tabcaption")); ts.addTab(permissionEditor, messages.getMessage("userstep.editor.permissions.tabcaption")); ts.setSelectedTab(stepLayout); vl.addComponent(ts); vl.setExpandRatio(ts, 1.0f); return vl; } private VerticalLayout prepareAssignmentLayout() { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); assigneeField = new TextField();//Messages.getString("field.assignee")); assigneeField.setWidth("100%"); assigneeField.setNullRepresentation(""); candidateGroupsField = new TextField();//Messages.getString("field.candidateGroups")); candidateGroupsField.setWidth("100%"); candidateGroupsField .setNullRepresentation(""); swimlaneField = new TextField();//Messages.getString("field.swimlane")); swimlaneField.setWidth("100%"); swimlaneField.setNullRepresentation(""); VerticalLayout assignmentLayout = new VerticalLayout(); assignmentLayout.setWidth("100%"); assignmentLayout.setSpacing(true); assignmentLayout.setMargin(true); assignmentLayout.addComponent(styled(new Label(messages.getMessage("field.assignee")), "h1")); assignmentLayout.addComponent(htmlLabel(messages.getMessage("field.assignee.info"))); assignmentLayout.addComponent(assigneeField); assignmentLayout.addComponent(styled(new Label(messages.getMessage("field.candidateGroups")), "h1")); assignmentLayout.addComponent(htmlLabel(messages.getMessage("field.candidateGroups.info"))); assignmentLayout.addComponent(candidateGroupsField); assignmentLayout.addComponent(styled(new Label(messages.getMessage("field.swimlane")), "h1")); assignmentLayout.addComponent(htmlLabel(messages.getMessage("field.swimlane.info"))); assignmentLayout.addComponent(swimlaneField); return assignmentLayout; } private void prepareAvailableWidgetsComponent() { List<WidgetItem> availableWidgetItems = getAvailableWidgetItems(); Collections.sort(availableWidgetItems, new Comparator<WidgetItem>(){ @Override public int compare(WidgetItem o1, WidgetItem o2) { return o1.getName().compareTo(o2.getName()); } }); CssLayout pane = new CssLayout() { @Override protected String getCss(Component component) { if (component instanceof WidgetInfoDnDWrapper) { WidgetInfoDnDWrapper wrapper = (WidgetInfoDnDWrapper) component; String basicCss = "float: left; margin: 3px; padding: 3px; display: inline; font-weight: bold; border: 2px solid "; return basicCss + (nvl(wrapper.widgetItem.getChildrenAllowed(), false) ? "#287ece;" : "#60b30e;"); } return super.getCss(component); } }; pane.setWidth("100%"); for (WidgetItem wi : availableWidgetItems) { Label lbl = new Label(wi.getName()); lbl.setDescription(wi.getDescription()); lbl.setSizeUndefined(); DragAndDropWrapper c = new WidgetInfoDnDWrapper(lbl, wi); c.setSizeUndefined(); pane.addComponent(c); c.setDragStartMode(DragAndDropWrapper.DragStartMode.WRAPPER); } DragAndDropWrapper wr = new DragAndDropWrapper(pane); wr.setDropHandler(new DropHandler() { @Override public void drop(DragAndDropEvent event) { Transferable t = event.getTransferable(); Component src = t.getSourceComponent(); if (src != stepTree || !(t instanceof DataBoundTransferable)) { return; } Object sourceItemId = ((DataBoundTransferable) t).getItemId(); stepTreeContainer.removeItemRecursively(sourceItemId); } @Override public AcceptCriterion getAcceptCriterion() { return AcceptAll.get(); } }); this.availableWidgetsPane = wr; } private List<WidgetItem> getAvailableWidgetItems() { List<WidgetItem> widgetItems = new ArrayList<WidgetItem>(); Map<BundleItem, Collection<WidgetItem>> availableWidgets = new HashMap<BundleItem, Collection<WidgetItem>>(); try { availableWidgets = WidgetInfoLoader.loadAvailableWidgets(application); } catch (ClassNotFoundException e) { logger.log(Level.SEVERE, "Error loading available widgets", e); } for (Entry<BundleItem, Collection<WidgetItem>> entry : availableWidgets.entrySet()) { final Collection<WidgetItem> widgets = entry.getValue(); for (WidgetItem widgetItem : widgets) { widgetItems.add(widgetItem); } } return widgetItems; } private VerticalLayout buildWidgetEditorTabContent() { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); VerticalLayout availableWidgetsLayout = new VerticalLayout(); availableWidgetsLayout.setSpacing(true); availableWidgetsLayout.setWidth("100%"); availableWidgetsLayout.addComponent(availableWidgetsPane); VerticalLayout stepLayout = new VerticalLayout(); stepLayout.setWidth("100%"); stepLayout.setSpacing(true); stepLayout.setMargin(true); stepLayout.addComponent(new Label(messages.getMessage("userstep.editor.widgets.instructions"), Label.CONTENT_XHTML)); stepLayout.addComponent(availableWidgetsLayout); Panel treePanel = new Panel(); treePanel.setStyleName(Reindeer.PANEL_LIGHT); treePanel.addComponent(stepTree); treePanel.setWidth("245px"); HorizontalLayout treeAndParamLayout = new HorizontalLayout(); treeAndParamLayout.setWidth("100%"); treeAndParamLayout.setSpacing(true); treeAndParamLayout.addComponent(treePanel); treeAndParamLayout.addComponent(paramPanel); treeAndParamLayout.setExpandRatio(paramPanel, 1.0f); stepLayout.addComponent(treeAndParamLayout); stepLayout.setExpandRatio(treeAndParamLayout, 1.0f); return stepLayout; } private VerticalLayout buildStateDefinitionLayout() { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); descriptionField = new TextField(); descriptionField.setNullRepresentation(""); descriptionField.setWidth("100%"); commentaryTextArea = new RichTextArea(); commentaryTextArea.setNullRepresentation(""); commentaryTextArea.setWidth("100%"); VerticalLayout stateLayout = new VerticalLayout(); stateLayout.setWidth("100%"); stateLayout.setSpacing(true); stateLayout.setMargin(true); stateLayout.addComponent(styled(new Label(messages.getMessage("field.description")), "h1")); stateLayout.addComponent(htmlLabel(messages.getMessage("field.description.info"))); stateLayout.addComponent(descriptionField); stateLayout.addComponent(styled(new Label(messages.getMessage("field.commentary")), "h1")); stateLayout.addComponent(htmlLabel(messages.getMessage("field.commentary.info"))); stateLayout.addComponent(commentaryTextArea); return stateLayout; } public void deleteTreeItem(final Object widget) { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); ConfirmDialog.show(application.getMainWindow(), messages.getMessage("dialog.delete.title"), messages.getMessage("dialog.delete.question"), messages.getMessage("dialog.delete.confirm"), messages.getMessage("dialog.delete.cancel"), new ConfirmDialog.Listener() { public void onClose(ConfirmDialog dialog) { if (dialog.isConfirmed()) { HierarchicalContainer hc = (HierarchicalContainer) stepTree.getContainerDataSource(); hc.removeItemRecursively(widget); showParams(null); } } } ); } @Override public Action[] getActions(Object target, Object sender) { if (target != rootItem) { return actions; } else { return COMMON_ACTIONS; } } private Resource getWidgetIcon(WidgetItem widgetItem) { try { final InputStream stream = widgetItem.getBundle().getIconStream(widgetItem.getIcon()); if (stream != null) return new StreamResource(new StreamResource.StreamSource() { @Override public InputStream getStream() { return stream; } }, widgetItem.getIcon(), application); } catch (IOException e) { logger.log(Level.WARNING, "Icon \"" + widgetItem.getIcon() + "\" for widget \"" + widgetItem.getName() + "\" not found!"); } return getResource("icon.widget.default"); } private Resource getResource(String pathKey) { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); final String path = messages.getMessage(pathKey); final InputStream stream = getClass().getClassLoader().getResourceAsStream(path); if (stream != null) { String[] pathParts = path.split("/"); return new StreamResource(new StreamResource.StreamSource() { @Override public InputStream getStream() { return stream; } }, pathParts[pathParts.length - 1], application); } return null; } private Container getCurrentStep() { prepareTreeContainer(); return stepTreeContainer; } @Override public void handleAction(Action action, Object sender, Object target) { if (action == actionDelete) { deleteTreeItem(target); } } private void loadJSONConfig() { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); try { Map<String, String> map = JSONHandler.loadConfig(stepTreeContainer, rootItem, jsonConfig, permissions); if (stepTree != null) { stepTree.expandItemsRecursively(rootItem); } assigneeField.setValue(map.get(JSONHandler.ASSIGNEE)); swimlaneField.setValue(map.get(JSONHandler.SWIMLANE)); candidateGroupsField.setValue(map.get(JSONHandler.CANDIDATE_GROUPS)); commentaryTextArea.setValue(map.get(JSONHandler.COMMENTARY)); descriptionField.setValue(map.get(JSONHandler.DESCRIPTION)); for (Object widget : stepTreeContainer.getItemIds()) { if (widget != rootItem) { stepTree.getItem(widget).getItemProperty("icon").setValue(getWidgetIcon(((WidgetItemInStep) widget).getWidgetItem())); } } } catch (WidgetNotFoundException e) { logger.log(Level.SEVERE, "Widget not found", e); application.getMainWindow().showNotification(messages.getMessage("error.config_not_loaded.title"), messages.getMessage("error.config_not_loaded.widget_not_found.body", e.getWidgetItemName()), Notification.TYPE_ERROR_MESSAGE); } catch (ParsingFailedException e) { logger.log(Level.SEVERE, "Parsing failed found", e); application.getMainWindow().showNotification( messages.getMessage("error.config_not_loaded.title"), messages.getMessage("error.config_not_loaded.unexpected_error.body", e.getLocalizedMessage()), Notification.TYPE_ERROR_MESSAGE); } } private HierarchicalContainer prepareTreeContainer() { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); stepTreeContainer = new HierarchicalContainer(); stepTreeContainer.addContainerProperty("name", String.class, messages.getMessage("stepTree.name.default")); stepTreeContainer.addContainerProperty("icon", Resource.class, getResource("icon.widget.default")); final WidgetItem widgetItem = new WidgetItem( "ROOT", messages.getMessage("stepTree.root.name"), messages.getMessage("stepTree.root.description"), null, null, new ArrayList<PermissionDefinition>(), true, null ); rootItem = new WidgetItemInStep(widgetItem, null, null, null); Item item = stepTreeContainer.addItem(rootItem); item.getItemProperty("name").setValue(widgetItem.getName()); item.getItemProperty("icon").setValue(getResource("icon.root.default")); stepTreeContainer.setChildrenAllowed(item, widgetItem.getChildrenAllowed()); return stepTreeContainer; } private void showParams(WidgetItemInStep widget) { paramPanel.loadWidget(widget, true); } @Override public void save() { // perform validation of all the widget definitions Collection<?> itemIds = stepTreeContainer.getItemIds(); for (Object itemId : itemIds) { WidgetItemInStep widgetInStep = (WidgetItemInStep) itemId; PropertiesPanel propertiesPanel = widgetInStep.getWidgetPropertiesPanel(); if (propertiesPanel != null) { widgetInStep.getWidgetPropertiesPanel().ensureForm(); PropertiesForm propertiesForm = propertiesPanel.getPropertiesForm(); if (!propertiesForm.isValid()) { I18NSource messages = I18NSource.ThreadUtil.getThreadI18nSource(); VaadinUtility.validationNotification( application, messages, messages.getMessage("stepTree.contains.invalid") ); // switch to problematic widget paramPanel.loadWidget(widgetInStep, false); stepTree.setValue(widgetInStep); return; } } } String json = JSONHandler.dumpTreeToJSON(stepTree, rootItem, assigneeField.getValue(), candidateGroupsField.getValue(), swimlaneField.getValue(), stepType, descriptionField.getValue(), commentaryTextArea.getValue(), permissions ); application.getJsHelper().postAndRedirectStep(url, json); } @Override public void valueChange(ValueChangeEvent event) { if (event.getProperty() == stepTree) { if (stepTree.getValue() == null || stepTree.getValue() == rootItem) { showParams(null); } else { WidgetItemInStep widget = (WidgetItemInStep) stepTree.getValue(); stepTree.setValue(widget); showParams(widget); } } } }