package org.vaadin.mideaas.frontend; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.Set; import org.vaadin.aceeditor.AceEditor; import org.vaadin.aceeditor.AceEditor.SelectionChangeListener; import org.vaadin.aceeditor.AceMode; import org.vaadin.aceeditor.client.AceDoc; import org.vaadin.mideaas.editor.MultiUserDoc; import org.vaadin.mideaas.editor.MultiUserEditor; import org.vaadin.mideaas.editor.UserDoc; import org.vaadin.mideaas.editor.XmlAsyncErrorChecker; import org.vaadin.mideaas.model.User; import org.vaadin.teemu.clara.Clara; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; import com.vaadin.event.FieldEvents.TextChangeEvent; import com.vaadin.event.FieldEvents.TextChangeListener; import com.vaadin.shared.ui.MarginInfo; import com.vaadin.shared.ui.label.ContentMode; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.Component; import com.vaadin.ui.CustomComponent; import com.vaadin.ui.HorizontalSplitPanel; import com.vaadin.ui.Label; import com.vaadin.ui.NativeSelect; import com.vaadin.ui.Panel; import com.vaadin.ui.TextField; import com.vaadin.ui.UI; import com.vaadin.ui.VerticalLayout; @SuppressWarnings("serial") public class ClaraEditor extends CustomComponent implements SelectionChangeListener, TextChangeListener { public interface ClaraEditorListener { public void goToDefinition(String id, String className); public void goToHandler(String id, String cls, String todo); public void setDataSource(String id, String cls, String todo); } private LinkedList<ClaraEditorListener> listeners = new LinkedList<ClaraEditorListener>(); private HorizontalSplitPanel split = new HorizontalSplitPanel(); private MultiUserEditor editor; private final MultiUserDoc mud; private VerticalLayout componentContext = new VerticalLayout(); private ClaraPreviewWindow previewWindow = null; private Component claraComponent; private String rootClassName; private final VisualDesignerConnector visualEditor; private static MideaasServlet servlet; private final String modelId = generateModelId(); private final User user; private static String visualDesignerUrl; public static void setVisualDesignerUrl(String url) { visualDesignerUrl = url; } // TODO: this is probably not the right place for this... private static String generateModelId() { return new BigInteger(130, new Random()).toString(32); } public ClaraEditor(User user, MultiUserDoc modelMud) { super(); this.user = user; mud = modelMud; editor = new MultiUserEditor(user.getEditorUser(), mud); editor.setMode(AceMode.xml); editor.setSizeFull(); editor.setWordWrap(true); editor.setErrorChecker(new XmlAsyncErrorChecker()); storeModel(mud.getBaseText()); Button openPreview = new Button("Preview"); openPreview.addClickListener(new ClickListener() { @Override public void buttonClick(ClickEvent event) { preview(); } }); VerticalLayout right = new VerticalLayout(); right.addComponent(openPreview); right.setMargin(true); right.setSpacing(true); if (visualDesignerUrl != null) { visualEditor = new VisualDesignerConnector(modelId, visualDesignerUrl, this); right.addComponent(visualEditor); } else { visualEditor = null; } right.addComponent(componentContext); componentContext.setMargin(new MarginInfo(true, false, true, false)); split.setSizeFull(); split.setSplitPosition(80f); setCompositionRoot(split); split.setFirstComponent(editor); split.setSecondComponent(right); } private void preview() { if (previewWindow != null) { UI.getCurrent().removeWindow(previewWindow); } previewWindow = new ClaraPreviewWindow(); UI.getCurrent().addWindow(previewWindow); compileClara(); } @Override public void attach() { super.attach(); editor.addSelectionChangeListener(this); editor.addTextChangeListener(this); //mud.addBaseChangedListenerWeak(this); udpateXmlContext(); } private String getXml() { return editor.getDoc().getText(); } public void setXml(String xml) { UserDoc ud = mud.getUserDoc(user.getEditorUser()); ud.getDoc().setDoc(new AceDoc(xml)); } private ClaraXmlHandler parseDocument(InputStream is) { ClaraXmlHandler myHandler = new ClaraXmlHandler(editor.getSelection()); try { XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setContentHandler(myHandler); reader.parse(new InputSource(is)); } catch (SAXException se) { } catch (IOException ie) { } return myHandler; } @Override public void textChange(TextChangeEvent event) { if (previewWindow != null) { compileClara(); } } private void compileClara() { InputStream is1 = new ByteArrayInputStream(getXml().getBytes()); try { claraComponent = Clara.create(is1); // claraComponent.setSizeFull(); // ? previewWindow.setContent(claraComponent); } catch (Exception e) { claraComponent = null; previewWindow.setError(e); } } public String getRootComponentClassName() { return rootClassName; } @Override public void selectionChanged(AceEditor.SelectionChangeEvent e) { udpateXmlContext(); } // @Override // public void baseChanged(AceDoc doc, EditorUser user) { // storeModel(doc.getText()); // } private void storeModel(String xml) { MideaasServlet servlet = getServlet(); if (servlet != null) { servlet.putModelXml(modelId, xml); } else { System.err.println("WARNING: can't store model because servlet is null."); } } public synchronized static void setServlet(MideaasServlet servlet) { ClaraEditor.servlet = servlet; } private synchronized static MideaasServlet getServlet() { return servlet; } private void udpateXmlContext() { componentContext.removeAllComponents(); InputStream is = new ByteArrayInputStream(getXml().getBytes()); ClaraXmlHandler mh = parseDocument(is); this.rootClassName = mh.rootCls; if (mh.currCls == null) { return; } String s; String shortCls = mh.currCls.substring(mh.currCls.lastIndexOf('.') + 1); if (mh.currId == null) { s = shortCls + " (no id)"; } else { s = shortCls + " (id: <strong>" + mh.currId + "</strong>)"; } Label la = new Label(s, ContentMode.HTML); componentContext.addComponent(la); if (mh.currId == null) { return; } final String cls = mh.currCls; final String id = mh.currId; Button b = new Button("Go to " + id + " code"); b.addClickListener(new ClickListener() { @Override public void buttonClick(ClickEvent event) { fireGoToDefinition(id, cls); } }); componentContext.addComponent(b); List<String> types = getHandlerTypesFor(cls); if (!types.isEmpty()) { componentContext.addComponent(new AddHandlerPanel(id, types)); } List<String> datas = getDataSourceTypesFor(cls); if (!datas.isEmpty()) { componentContext.addComponent(new SetDataSourcePanel(id, datas)); } } private class AddHandlerPanel extends Panel { private VerticalLayout layout = new VerticalLayout(); public AddHandlerPanel(final String id, List<String> types) { super("Add Handler"); layout.setMargin(true); setContent(layout); final NativeSelect sel = new NativeSelect(null, types); for (String t : types) { sel.setItemCaption(t, t.substring(t.lastIndexOf(".")+1)); } sel.setValue(types.get(0)); sel.setNullSelectionAllowed(false); layout.addComponent(sel); final TextField tf = new TextField("TODO:"); layout.addComponent(tf); Button sb = new Button("Add Handler"); sb.addClickListener(new ClickListener() { @Override public void buttonClick(ClickEvent event) { fireGoToHandler(id, (String) sel.getValue(), tf.getValue()); } }); layout.addComponent(sb); } } private class SetDataSourcePanel extends Panel { private VerticalLayout layout = new VerticalLayout(); public SetDataSourcePanel(final String id, List<String> types) { super("Add Data Source"); layout.setMargin(true); setContent(layout); final NativeSelect sel = new NativeSelect(null, types); for (String t : types) { sel.setItemCaption(t, t.substring(t.lastIndexOf(".")+1)); } sel.setValue(types.get(0)); sel.setNullSelectionAllowed(false); layout.addComponent(sel); final TextField tf = new TextField("TODO:"); layout.addComponent(tf); Button sb = new Button("SetDataSource"); sb.addClickListener(new ClickListener() { @Override public void buttonClick(ClickEvent event) { fireSetDataSource(id, (String)sel.getValue(), tf.getValue()); } }); layout.addComponent(sb); } } private static final Set<String> VALUE_CHANGE_LISTENER_FOR = new HashSet<String>( Arrays.asList(new String[] { "com.vaadin.addon.touchkit.ui.Switch", "com.vaadin.ui.CheckBox", "com.vaadin.ui.Slider", "com.vaadin.ui.TextField" })); private List<String> getHandlerTypesFor(String cls) { if ("com.vaadin.ui.Button".equals(cls)) { return Collections.singletonList("com.vaadin.ui.Button.ClickEvent"); } final String naviButton = "com.vaadin.addon.touchkit.ui.NavigationButton"; if (naviButton.equals(cls)) { return Collections.singletonList(naviButton + ".NavigationButtonClickEvent"); } if (VALUE_CHANGE_LISTENER_FOR.contains(cls)) { return Collections .singletonList("com.vaadin.data.Property.ValueChangeEvent"); } // TODO: more. automatically? return Collections.emptyList(); } private static final Set<String> CONTAINER_COMPONENTS = new HashSet<String>( Arrays.asList(new String[] { "com.vaadin.ui.Select", "com.vaadin.ui.Table" /* TODO: more or something else? */ })); private List<String> getDataSourceTypesFor(String cls) { if ("com.vaadin.ui.Label".equals(cls)) { return Collections.singletonList("java.lang.String"); } else if (CONTAINER_COMPONENTS.contains(cls)) { return Collections.singletonList("com.vaadin.data.Container"); } // TODO: more. automatically? return Collections.emptyList(); } public void addClaraEditorListener(ClaraEditorListener li) { listeners.add(li); } private void fireGoToDefinition(String id, String cls) { for (ClaraEditorListener li : listeners) { li.goToDefinition(id, cls); } } private void fireGoToHandler(String id, String cls, String todo) { for (ClaraEditorListener li : listeners) { li.goToHandler(id, cls, todo); } } private void fireSetDataSource(String id, String type, String todo) { for (ClaraEditorListener li : listeners) { li.setDataSource(id, type, todo); } } }