package pl.net.bluesoft.rnd.processtool.ui.basewidgets.editor;
import com.vaadin.data.Item;
import com.vaadin.data.util.HierarchicalContainer;
import com.vaadin.event.*;
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.gwt.client.ui.dd.VerticalDropLocation;
import com.vaadin.ui.*;
import org.apache.commons.beanutils.PropertyUtils;
import org.vaadin.dialogs.ConfirmDialog;
import pl.net.bluesoft.rnd.processtool.ui.basewidgets.xml.WidgetDefinitionLoader;
import pl.net.bluesoft.rnd.processtool.ui.basewidgets.xml.XmlConstants;
import pl.net.bluesoft.rnd.processtool.ui.basewidgets.xml.jaxb.*;
import pl.net.bluesoft.rnd.processtool.ui.basewidgets.xml.validation.XmlValidationError;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import static pl.net.bluesoft.rnd.processtool.ui.basewidgets.editor.EditorHelper.*;
/**
* @author tlipski@bluesoft.net.pl
*/
public class ProcessDataHierarchyEditor extends VerticalLayout {
public static final Logger LOGGER = Logger.getLogger(ProcessDataWidgetsDefinitionEditor.class.getName());
private WidgetDefinitionLoader definitionLoader = WidgetDefinitionLoader.getInstance();
private HierarchicalContainer hierarchicalContainer;
private WidgetsDefinitionElement rootWidget;
private Tree widgetTree = new Tree();
private ProcessDataWidgetsDefinitionEditor editor;
public ProcessDataHierarchyEditor(ProcessDataWidgetsDefinitionEditor editor) {
this.editor = editor;
initGUI();
}
private void initGUI() {
setSizeUndefined();
setWidth("100%");
hierarchicalContainer = new HierarchicalContainer();
hierarchicalContainer.addContainerProperty("name", String.class, null);
hierarchicalContainer.addContainerProperty("widget", Object.class, null);
HorizontalLayout treeAndForm = new HorizontalLayout();
final VerticalLayout formLayout = getFormLayout();
treeAndForm.addComponent(getInitedWidgetTreePanel());
treeAndForm.addComponent(formLayout);
treeAndForm.setExpandRatio(formLayout, 1.0f);
treeAndForm.setWidth("100%");
treeAndForm.setSpacing(true);
addComponent(getAvailableWidgetsComponent());
addComponent(new Label(getLocalizedMessage("info")));
addComponent(treeAndForm);
setExpandRatio(treeAndForm, 1.0f);
}
private VerticalLayout getFormLayout() {
final VerticalLayout formLayout = new VerticalLayout();
formLayout.setWidth("100%");
final WidgetPropertiesEditorFormComponent[] prevFormHandler = new WidgetPropertiesEditorFormComponent[1];
widgetTree.addListener(new ItemClickEvent.ItemClickListener() {
@Override
public void itemClick(ItemClickEvent event) {
final Object itemId = event.getItemId();
final WidgetPropertiesEditorFormComponent formComponent = prevFormHandler[0];
if (formComponent != null && formComponent.getForm().isModified()) {
if (formComponent.getForm().isValid()) {
formComponent.commit();
renderForm(itemId);
} else {
ConfirmDialog.show(
getApplication().getMainWindow(),
getLocalizedMessage("unsaved-data-warning"),
new ConfirmDialog.Listener() {
@Override
public void onClose(ConfirmDialog confirmDialog) {
if (confirmDialog.isConfirmed()) {
renderForm(itemId);
} else {
widgetTree.select(formComponent.getItemId());
}
}
});
}
} else {
renderForm(itemId);
}
}
private void renderForm(Object itemId) {
prevFormHandler[0] = null;
formLayout.removeAllComponents();
if (itemId != null) {
WidgetPropertiesEditorFormComponent editorFormComponent = new WidgetPropertiesEditorFormComponent(itemId, ProcessDataHierarchyEditor.this);
formLayout.addComponent(editorFormComponent);
prevFormHandler[0] = editorFormComponent;
}
}
});
return formLayout;
}
private Panel getInitedWidgetTreePanel() {
widgetTree.setContainerDataSource(hierarchicalContainer);
widgetTree.setDragMode(Tree.TreeDragMode.NODE);
widgetTree.setItemCaptionPropertyId("name");
widgetTree.setDropHandler(new TreeSortDropHandler(widgetTree, hierarchicalContainer));
widgetTree.setWidth("100%");
widgetTree.addShortcutListener(getDeleteShortcutListener());
Panel panel = new Panel(getLocalizedMessage("widget-hierarchy"));
panel.setHeight("340px");
panel.setWidth("250px");
panel.addComponent(widgetTree);
return panel;
}
private ShortcutListener getDeleteShortcutListener() {
return new ShortcutListener("Delete", null, ShortcutAction.KeyCode.DELETE) {
@Override
public void handleAction(Object sender, Object target) {
if (target instanceof Tree) {
Tree target1 = (Tree) target;
final Object itemId = target1.getValue();
if (itemId != null) {
ConfirmDialog.show(
getApplication().getMainWindow(),
getLocalizedMessage("remove-item-confirm"),
new ConfirmDialog.Listener() {
@Override
public void onClose(ConfirmDialog confirmDialog) {
if (confirmDialog.isConfirmed()) {
removeItemFromTreeRecursively(itemId);
}
refreshRawXmlAndPreview();
}
});
}
}
}
};
}
private void removeItemFromTreeRecursively(Object itemId) {
hierarchicalContainer.removeItemRecursively(itemId);
}
private void updateXml(Object itemId) {
//fix relations between widgets
Collection<?> children = hierarchicalContainer.getChildren(itemId);
if (itemId instanceof HasWidgetsElement) {
HasWidgetsElement hasWidgetsElement = (HasWidgetsElement) itemId;
ArrayList<WidgetElement> widgets = new ArrayList<WidgetElement>();
if (children != null) for (Object subItemId : children) {
updateXml(subItemId);
if (subItemId instanceof WidgetElement)
widgets.add((WidgetElement) subItemId);
}
hasWidgetsElement.setWidgets(widgets);
} else if (itemId instanceof AbstractSelectWidgetElement) { //special case, but it should be supported in more generic manner
AbstractSelectWidgetElement selectWidgetElement = (AbstractSelectWidgetElement) itemId;
ArrayList<ItemElement> values = new ArrayList<ItemElement>();
if (children != null) for (Object subItemId : children) {
if (subItemId instanceof ItemElement) {
values.add((ItemElement) subItemId);
}
}
selectWidgetElement.setValues(values);
}
}
public void refreshRawXmlAndPreview() {
updateXml(rootWidget);
List<XmlValidationError> xmlValidationErrors = rootWidget.validate();
Collection<?> all = hierarchicalContainer.getItemIds();
Set<String> ids=new HashSet<String>();
boolean duplicated=false;
for (Object obj : all) {
if (obj instanceof WidgetElement){
WidgetElement we = (WidgetElement) obj;
if (!ids.add(we.getId()) && we.getId()!=null){
duplicated=true;
}
}
}
if (duplicated)xmlValidationErrors.add(new XmlValidationError("select", "id", XmlConstants.XML_TAG_DUPLICATED));
if (xmlValidationErrors != null && !xmlValidationErrors.isEmpty()) {
String msg = joinValidationErrors(xmlValidationErrors);
Window.Notification n = new Window.Notification(getLocalizedMessage("validation-errors"),
msg, Window.Notification.TYPE_TRAY_NOTIFICATION);
n.setDelayMsec(4000);
getApplication().getMainWindow().showNotification(n);
} else {
editor.updateFromWidgetsDefinitionElement(rootWidget);
Window.Notification n = new Window.Notification(getLocalizedMessage("update-success"),
getLocalizedMessage("xml-definition-generate-success"), Window.Notification.TYPE_TRAY_NOTIFICATION);
n.setDelayMsec(2000);
getApplication().getMainWindow().showNotification(n);
}
}
public WidgetsDefinitionElement processXml(String value) {
hierarchicalContainer.removeAllItems();
if (value != null) {
try {
rootWidget = (WidgetsDefinitionElement) definitionLoader.unmarshall(String.valueOf(value));
} catch (Exception e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
rootWidget = new WidgetsDefinitionElement();
}
}
else {
rootWidget = new WidgetsDefinitionElement();
}
Item item = hierarchicalContainer.addItem(rootWidget);
item.getItemProperty("name").setValue(rootWidget.getClass().getSimpleName());
item.getItemProperty("widget").setValue(rootWidget);
hierarchicalContainer.setChildrenAllowed(rootWidget, true);
processWidgetsTree(rootWidget);
widgetTree.expandItemsRecursively(rootWidget);
editor.refreshPreview(rootWidget);
return rootWidget;
}
private Component getAvailableWidgetsComponent() {
CssLayout cssLayout = new CssLayout() {
@Override
protected String getCss(Component c) {
if (c instanceof WidgetDragAndDropWrapper) {
WidgetDragAndDropWrapper widgetDragAndDropWrapper = (WidgetDragAndDropWrapper) c;
Class cls = widgetDragAndDropWrapper.getCls();
String basicCss = "float: left; margin: 3px; padding: 3px; display: inline; font-weight: bold; border: 2px solid ";
if (getFieldAnnotation(cls, XmlElements.class) != null)
return basicCss + "#287ece;";
else if (WidgetElement.class.isAssignableFrom(cls))
return basicCss + "#60b30e;";
else
return basicCss + "#c6c6c6";
}
return super.getCss(c);
}
};
cssLayout.setWidth("100%");
Class[] supportedClasses = definitionLoader.getSupportedClasses();
for (Class cls : supportedClasses) {
if (getAnnotation(cls, XmlRootElement.class) != null) continue; //ignore root elements
if (Modifier.isAbstract(cls.getModifiers())) continue;
Label lbl = new Label(cls.getSimpleName());
lbl.setSizeUndefined();
DragAndDropWrapper c = new WidgetDragAndDropWrapper(lbl, cls);
c.setData(cls);
c.setSizeUndefined();
cssLayout.addComponent(c);
c.setDragStartMode(DragAndDropWrapper.DragStartMode.WRAPPER);
}
DragAndDropWrapper wr = new DragAndDropWrapper(cssLayout);
wr.setDropHandler(new DropHandler() {
@Override
public void drop(DragAndDropEvent event) {
Transferable t = event.getTransferable();
Component src = t.getSourceComponent();
if (src != widgetTree || !(t instanceof DataBoundTransferable)) {
return;
}
Object sourceItemId = ((DataBoundTransferable) t).getItemId();
removeItemFromTreeRecursively(sourceItemId);
refreshRawXmlAndPreview();
}
@Override
public AcceptCriterion getAcceptCriterion() {
return AcceptAll.get();
}
});
return wr;
}
private void processWidgetsTree(Object el) {
java.lang.reflect.Field field = getFieldWithAnnotation(el.getClass(), XmlElements.class);
Collection coll = null;
if (field != null) {
try {
Object property = PropertyUtils.getProperty(el, field.getName());
if (property instanceof Collection) {
coll = (Collection) property;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (coll != null) for (Object widget : coll) {
addTreeItem(widget);
hierarchicalContainer.setParent(widget, el);
if (getFieldAnnotation(widget.getClass(), XmlElements.class) != null) {
processWidgetsTree(widget);
}
}
}
private Item addTreeItem(Object widgetElement) {
Class cls = widgetElement.getClass();
Item subItem;
subItem = hierarchicalContainer.addItem(widgetElement);
subItem.getItemProperty("name").setValue(widgetElement.getClass().getSimpleName());
subItem.getItemProperty("widget").setValue(widgetElement);
if (getFieldAnnotation(cls, XmlElements.class) != null) {
hierarchicalContainer.setChildrenAllowed(widgetElement, true);
widgetTree.expandItem(widgetElement);
} else {
hierarchicalContainer.setChildrenAllowed(widgetElement, false);
}
return subItem;
}
private class TreeSortDropHandler implements DropHandler {
private final Tree tree;
public TreeSortDropHandler(Tree tree, HierarchicalContainer container) {
this.tree = tree;
}
public AcceptCriterion getAcceptCriterion() {
return AcceptAll.get();
}
public void drop(DragAndDropEvent dropEvent) {
Transferable t = dropEvent.getTransferable();
Component src = t.getSourceComponent();
Object sourceItemId;
Object newItemId = null;
// Item subItem = null;
HierarchicalContainer container = (HierarchicalContainer) tree.getContainerDataSource();
if (src instanceof WidgetDragAndDropWrapper) {
WidgetDragAndDropWrapper dragAndDropWrapper = (WidgetDragAndDropWrapper) src;
Class cls = dragAndDropWrapper.getCls();
Object widgetElement = null;
try {
widgetElement = cls.newInstance();
/*subItem = */addTreeItem(widgetElement);
newItemId = widgetElement;
sourceItemId = widgetElement;
} catch (Throwable e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
src.getApplication().getMainWindow().showNotification(getLocalizedMessage("widget-creation-failed"),
e.getClass().getName() + ", " + e.getMessage(),
Window.Notification.TYPE_ERROR_MESSAGE);
// if (subItem != null && widgetElement != null) {
// container.removeItem(widgetElement);
// }
return;
}
} else {
if (src != tree || !(t instanceof DataBoundTransferable)) {
return;
}
sourceItemId = ((DataBoundTransferable) t).getItemId();
}
Tree.TreeTargetDetails dropData = ((Tree.TreeTargetDetails) dropEvent
.getTargetDetails());
Object targetItemId = dropData.getItemIdOver();
VerticalDropLocation location = dropData.getDropLocation();
if (targetItemId instanceof WidgetsDefinitionElement) { //the can be only one! ... root element2
location = VerticalDropLocation.MIDDLE;
}
if (!moveNode(sourceItemId, targetItemId, location)) {
if (newItemId != null) {
container.removeItem(newItemId);
}
} else {
refreshRawXmlAndPreview();
}
}
private boolean moveNode(Object sourceItemId, Object targetItemId,
VerticalDropLocation location) {
HierarchicalContainer container = (HierarchicalContainer) tree.getContainerDataSource();
if (sourceItemId == null || targetItemId == null) return false;
Class srcClass = sourceItemId.getClass();
Class targetClass = targetItemId.getClass();
if (location == VerticalDropLocation.MIDDLE) {
if (!checkIfParentChildRelationIsPossible(srcClass, targetClass)) return false;
if (container.setParent(sourceItemId, targetItemId)
&& container.hasChildren(targetItemId)) {
container.moveAfterSibling(sourceItemId, null);
}
} else if (location == VerticalDropLocation.TOP) {
Object parentId = container.getParent(targetItemId);
if (!checkIfParentChildRelationIsPossible(srcClass, parentId.getClass())) return false;
if (container.setParent(sourceItemId, parentId)) {
container.moveAfterSibling(sourceItemId, targetItemId);
container.moveAfterSibling(targetItemId, sourceItemId);
}
} else if (location == VerticalDropLocation.BOTTOM) {
Object parentId = container.getParent(targetItemId);
if (!checkIfParentChildRelationIsPossible(srcClass, parentId.getClass())) return false;
if (container.setParent(sourceItemId, parentId)) {
container.moveAfterSibling(sourceItemId, targetItemId);
}
}
return true;
}
private boolean checkIfParentChildRelationIsPossible(Class srcClass, Class targetClass) {
XmlElements targetClassAnnotation = (XmlElements) getFieldAnnotation(targetClass, XmlElements.class);
if (targetClassAnnotation == null) {
return false;
}
for (XmlElement xe : targetClassAnnotation.value()) {
if (srcClass.isAssignableFrom(xe.type())) {
return true;
}
}
return false;
}
}
private static class WidgetDragAndDropWrapper extends DragAndDropWrapper {
private Class cls;
private WidgetDragAndDropWrapper(Component root, Class cls) {
super(root);
this.cls = cls;
}
public Class getCls() {
return cls;
}
}
}