/******************************************************************************* * Copyright (c) 2013, 2016 Red Hat, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Red Hat Inc. - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.thym.ui.config.internal; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.UpdateValueStrategy; import org.eclipse.core.databinding.beans.BeanProperties; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.observable.value.WritableValue; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; import org.eclipse.jface.databinding.swt.WidgetProperties; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.PixelConverter; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.eclipse.thym.core.HybridProject; import org.eclipse.thym.core.config.Author; import org.eclipse.thym.core.config.Content; import org.eclipse.thym.core.config.Engine; import org.eclipse.thym.core.config.Widget; import org.eclipse.thym.core.config.WidgetModel; import org.eclipse.thym.core.engine.HybridMobileEngine; import org.eclipse.thym.core.engine.internal.cordova.CordovaEngineProvider; import org.eclipse.thym.ui.HybridUI; import org.eclipse.thym.ui.internal.engine.AvailableCordovaEnginesSection; import org.eclipse.thym.ui.plugins.internal.CordovaPluginSelectionPage; import org.eclipse.thym.ui.plugins.internal.LaunchCordovaPluginWizardAction; import org.eclipse.thym.ui.wizard.export.NativeArtifactExportAction; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.events.IHyperlinkListener; import org.eclipse.ui.forms.widgets.FormText; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.forms.widgets.TableWrapData; /** * MultiEditor page to collect general information on the Cordova application. * * @author Gorkem Ercan * */ public class EssentialsPage extends AbstactConfigEditorPage implements IHyperlinkListener{ private static final String PLUGINS_SECTION_CONTENT = "<form><p>Add Cordova plug-ins to extend your applications functionality</p>" + "<li style=\"image\" value=\"plugin\" bindent=\"5\">Search and install from a <a href=\"plugin.registry\">registry</a></li>" + "<li style=\"image\" value=\"plugin\" bindent=\"5\">Use a <a href=\"plugin.git\">git</a> URL to pull and install from a repo</li>" + "<li style=\"image\" value=\"plugin\" bindent=\"5\">Install from a <a href=\"plugin.folder\">directory</a></li>" + "</form>"; private static final String EXPORT_SECTION_CONTENT = "<form><p>Options available to export this application to supported platforms:</p>" + "<li style=\"image\" value=\"export\" bindent=\"5\">Export <a href=\"export.app\">Mobile application(s)</a> to distribute</li>" + "</form>"; private DataBindingContext m_bindingContext; private AvailableCordovaEnginesSection engineSection; private FormToolkit formToolkit; private Text txtIdtxt; private Text txtAuthorname; private Text txtDescription; private Text txtName; private Text txtEmail; private Text txtUrl; private Text txtVersion; private Text txtContentsource; private PropertyChangeListener engineListener; private PropertyChangeListener contentListener; private PropertyChangeListener authorListener; //Author look alike class to be able to work the bindings with //initial null values. private class DummyAuthor{ @SuppressWarnings("unused") public void setHref(String href) { Author a = createAuthor(); a.setHref(href); } @SuppressWarnings("unused") public void setEmail(String email) { Author a = createAuthor(); a.setEmail(email); } @SuppressWarnings("unused") public void setName(String name) { Author a =createAuthor(); a.setName(name); } private Author createAuthor(){ Author a = ((ConfigEditor)getEditor()).getWidgetModel().createAuthor(getWidget()); getWidget().setAuthor(a); return a; } @SuppressWarnings("unused") public String getName(){ return null; } @SuppressWarnings("unused") public String getEmail(){ return null; } @SuppressWarnings("unused") public String getHref(){ return null;} @SuppressWarnings("unused") public void addPropertyChangeListener(PropertyChangeListener l){ //no implementation } @SuppressWarnings("unused") public void removePropertyChangeListener(PropertyChangeListener l){ //no implementation } } // Content look alike to cheat binding private class DummyContent{ @SuppressWarnings("unused") public void setSrc(String href ){ Content c = ((ConfigEditor)getEditor()).getWidgetModel().createContent(getWidget()); getWidget().setContent(c); c.setSrc(href); } @SuppressWarnings("unused") public String getSrc(){return null;} @SuppressWarnings("unused") public void addPropertyChangeListener(PropertyChangeListener l){ //no implementation } @SuppressWarnings("unused") public void removePropertyChangeListener(PropertyChangeListener l){ //no implementation } } public EssentialsPage(FormEditor editor) { super(editor, "essentials", "Overview"); formToolkit = editor.getToolkit(); } private WidgetModel getWidgetModel() { return ((ConfigEditor)getEditor()).getWidgetModel(); } private Widget getWidget(){ return ((ConfigEditor)getEditor()).getWidget(); } @Override protected void createFormContent(IManagedForm managedForm) { super.createFormContent(managedForm); final ScrolledForm form = managedForm.getForm(); formToolkit.decorateFormHeading( form.getForm()); managedForm.getForm().setText(getTitle()); Composite body = managedForm.getForm().getBody(); body.setLayout(FormUtils.createFormTableWrapLayout(2)); Composite left = formToolkit.createComposite(body); left.setLayout(FormUtils.createFormPaneTableWrapLayout(1)); left.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); Composite right = formToolkit.createComposite(body); right.setLayout(FormUtils.createFormPaneTableWrapLayout(1)); right.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); createNameDescriptionSection(left); createAuthorSection(left); createExportSection(left); createPluginsSection(right); createEngineSection(right); m_bindingContext = initDataBindings(); bindAuthor(m_bindingContext); // binding separately is necessary to be able to work with WindowBuilder bindContent(m_bindingContext); } private void createPluginsSection(Composite parent){ Section sctnPlugins = createSection(parent, "Plug-ins"); sctnPlugins.setLayout(FormUtils.createClearTableWrapLayout(false, 1)); TableWrapData data = new TableWrapData(TableWrapData.FILL_GRAB); sctnPlugins.setLayoutData(data); FormText text = formToolkit.createFormText(sctnPlugins, true); ImageDescriptor idesc = HybridUI.getImageDescriptor(HybridUI.PLUGIN_ID,"/icons/etool16/cordovaplug_wiz.png"); text.setImage("plugin", idesc.createImage()); text.setText(PLUGINS_SECTION_CONTENT, true, false); sctnPlugins.setClient(text); text.addHyperlinkListener(this); } private void createExportSection(Composite parent) { Section sctnExport = createSection(parent, "Export"); sctnExport.setLayout(FormUtils.createClearTableWrapLayout(false, 1)); TableWrapData data = new TableWrapData(TableWrapData.FILL_GRAB); sctnExport.setLayoutData(data); FormText text = formToolkit.createFormText(sctnExport, true); ImageDescriptor idesc = HybridUI.getImageDescriptor(HybridUI.PLUGIN_ID, "/icons/etool16/export_wiz.png"); text.setImage("export", idesc.createImage()); text.setText(EXPORT_SECTION_CONTENT, true, false); sctnExport.setClient(text); text.addHyperlinkListener(this); } private void createAuthorSection(Composite parent) { Section sctnAuthor = createSection(parent, "Author"); sctnAuthor.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); Composite composite = formToolkit.createComposite(sctnAuthor, SWT.WRAP); formToolkit.paintBordersFor(composite); sctnAuthor.setClient(composite); composite.setLayout(FormUtils.createSectionClientGridLayout(false, 2)); createFormFieldLabel(composite, "Name:"); txtAuthorname = formToolkit.createText(composite, "", SWT.WRAP); txtAuthorname.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); createFormFieldLabel(composite, "Email:"); txtEmail = formToolkit.createText(composite, "", SWT.NONE); txtEmail.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); createFormFieldLabel(composite, "URL:"); txtUrl = formToolkit.createText(composite, "", SWT.NONE); txtUrl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); } private Label createFormFieldLabel(final Composite composite, final String labelText) { Label label = formToolkit.createLabel(composite, labelText, SWT.NONE); PixelConverter converter = new PixelConverter(label); int widthHint = converter.convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH); widthHint = Math.max(widthHint, label.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x); GridDataFactory.swtDefaults().hint(widthHint, SWT.DEFAULT).applyTo(label); return label; } private void createNameDescriptionSection(Composite parent) { Section sctnNameAndDescription = createSection(parent, "Name and Description"); sctnNameAndDescription.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB)); Composite container = formToolkit.createComposite(sctnNameAndDescription, SWT.WRAP); formToolkit.paintBordersFor(container); sctnNameAndDescription.setClient(container); container.setLayout(FormUtils.createSectionClientGridLayout(false, 2)); GridData textGridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1); createFormFieldLabel(container, "ID:"); txtIdtxt = formToolkit.createText(container, "", SWT.NONE); txtIdtxt.setLayoutData(textGridData); createFormFieldLabel(container, "Name:"); txtName = formToolkit.createText(container, "", SWT.NONE); GridDataFactory.createFrom(textGridData).applyTo(txtName); createFormFieldLabel(container, "Version:"); txtVersion = formToolkit.createText(container, "", SWT.NONE); GridDataFactory.createFrom(textGridData).applyTo(txtVersion); createFormFieldLabel(container, "Description:"); txtDescription = formToolkit.createText(container, "", SWT.MULTI | SWT.WRAP | SWT.V_SCROLL); GridDataFactory.createFrom(textGridData).hint(SWT.DEFAULT, 100).applyTo(txtDescription); createFormFieldLabel(container, "Content Source:"); txtContentsource = formToolkit.createText(container, "", SWT.NONE); GridDataFactory.createFrom(textGridData).applyTo(txtContentsource); } private void createEngineSection(Composite parent) { Section sctnEngines = createSection(parent, "Hybrid Mobile Engine"); sctnEngines.setLayout(FormUtils.createClearTableWrapLayout(false, 1)); TableWrapData data = new TableWrapData(TableWrapData.FILL_GRAB); sctnEngines.setLayoutData(data); Composite container = formToolkit.createComposite(sctnEngines, SWT.WRAP); sctnEngines.setClient(container); container.setLayout(FormUtils.createSectionClientGridLayout(false, 1)); engineSection = new AvailableCordovaEnginesSection(); engineSection.createControl(container); engineListener = new PropertyChangeListener() { // We can't use updateActiveEngines() here because config.xml changes before // the active engine in the HybridProject -- the change fires while active // engine the old one. @Override public void propertyChange(PropertyChangeEvent ev) { Display.getDefault().asyncExec(new Runnable () { @Override public void run() { // The PropertyChangeEvent can fire before engines are actually // updated, so we have to rely on the Widget for active engines // rather than call HybridMobileEngineManager.getActiveEngines() getEnginesFromWidget(); } }); } }; getWidget().addPropertyChangeListener("engines", engineListener); updateActiveEngines(); engineSection.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection newSelection = (IStructuredSelection) engineSection.getSelection(); WidgetModel model = getWidgetModel(); Widget w = getWidget(); // Null check is required since w.getEngines() will return null // when there are no engine tags in config.xml. if (w.getEngines() != null) { for (Engine e : w.getEngines()) { w.removeEngine(e); } } if (newSelection != null && !newSelection.isEmpty()) { HybridMobileEngine hybridEngine; Engine engine; for (Iterator<?> iter = newSelection.iterator(); iter.hasNext(); ) { hybridEngine = (HybridMobileEngine) iter.next(); engine = model.createEngine(w); engine.setName(hybridEngine.getId()); if (hybridEngine.isManaged()) { engine.setSpec(hybridEngine.getVersion()); } else { engine.setSpec(hybridEngine.getLocation().toString()); } w.addEngine(engine); } } } }); } private void getEnginesFromWidget() { List<Engine> activeEngines = getWidget().getEngines(); // getEngines() can return null; property change fires when engine is removed if (activeEngines == null || activeEngines.size() == 0) { engineSection.setSelection(new StructuredSelection()); return; } List<HybridMobileEngine> engines = new ArrayList<HybridMobileEngine>(); List<HybridMobileEngine> availableEngines = new CordovaEngineProvider().getAvailableEngines(); for (HybridMobileEngine availEngine : availableEngines) { for (Engine activeEngine : activeEngines) { if (availEngine.isManaged()) { if (availEngine.getId().equals(activeEngine.getName()) && availEngine.getVersion().equals(activeEngine.getSpec())) { engines.add(availEngine); } } else { if (availEngine.getId().equals(activeEngine.getName()) && availEngine.getLocation().equals(activeEngine.getSpec())) { engines.add(availEngine); } } } } if (engines.size() != 0) { engineSection.setSelection(new StructuredSelection(engines.toArray())); } } private void updateActiveEngines() { IFile file = (IFile) getEditor().getEditorInput().getAdapter(IFile.class); HybridProject project = HybridProject.getHybridProject(file.getProject()); HybridMobileEngine[] activeEngines = project.getActiveEngines(); if (activeEngines != null) { engineSection.setSelection(new StructuredSelection(activeEngines)); } } private Section createSection(Composite parent, String text){ Section sctn = formToolkit.createSection(parent, Section.TITLE_BAR); sctn.clientVerticalSpacing = FormUtils.SECTION_HEADER_VERTICAL_SPACING; sctn.setText(text); return sctn; } private void bindAuthor(DataBindingContext bindingContext) { // final WritableValue value = new WritableValue(); if (getWidget() != null) { Author author = getWidget().getAuthor(); if (author == null) { value.setValue(new DummyAuthor()); } else { value.setValue(author); } authorListener = new PropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent evt) { value.getRealm().exec(new Runnable() { @Override public void run() { if (evt.getNewValue() == null) { value.setValue(new DummyAuthor()); } else { value.setValue(evt.getNewValue()); } } }); } }; getWidget().addPropertyChangeListener("author", authorListener); } // IObservableValue observeTextTxtAuthornameObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtAuthorname); IObservableValue authornameGetWidgetObserveValue = BeanProperties.value("name").observeDetail(value); UpdateValueStrategy strategy = new UpdateValueStrategy(); strategy.setConverter(new StringToDisplayableStringConverter()); bindingContext.bindValue(observeTextTxtAuthornameObserveWidget, authornameGetWidgetObserveValue, null,strategy); // IObservableValue observeTextTxtEmailObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtEmail); IObservableValue authoremailGetWidgetObserveValue = BeanProperties.value("email").observeDetail(value); bindingContext.bindValue(observeTextTxtEmailObserveWidget, authoremailGetWidgetObserveValue, null, null); // IObservableValue observeTextTxtUrlObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtUrl); IObservableValue authorhrefGetWidgetObserveValue = BeanProperties.value("href").observeDetail(value); bindingContext.bindValue(observeTextTxtUrlObserveWidget, authorhrefGetWidgetObserveValue, null, null); } private void bindContent(DataBindingContext bindingContext) { final WritableValue value = new WritableValue(); if (getWidget() != null) { Content content= getWidget().getContent(); if (content == null) { value.setValue(new DummyContent()); } else { value.setValue(content); } contentListener = new PropertyChangeListener() { @Override public void propertyChange(final PropertyChangeEvent evt) { value.getRealm().exec(new Runnable() { @Override public void run() { if (evt.getNewValue() == null) { value.setValue(new DummyContent()); } else { value.setValue(evt.getNewValue()); } } }); } }; getWidget().addPropertyChangeListener("content", contentListener); } IObservableValue observeTextTxtContentsourceObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtContentsource); IObservableValue contentsrcGetWidgetObserveValue = BeanProperties.value("src").observeDetail(value); bindingContext.bindValue(observeTextTxtContentsourceObserveWidget, contentsrcGetWidgetObserveValue, new UpdateValueStrategy() { @Override protected IStatus doSet(IObservableValue observableValue, Object value) { if(value == null || value.toString().isEmpty()){ getWidget().setContent(null); return Status.OK_STATUS; // Wste are done } return super.doSet(observableValue, value); } }, null); } protected DataBindingContext initDataBindings() { DataBindingContext bindingContext = new DataBindingContext(); // IObservableValue observeTextTxtIdtxtObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtIdtxt); IObservableValue idGetWidgetObserveValue = BeanProperties.value("id").observe(getWidget()); bindingContext.bindValue(observeTextTxtIdtxtObserveWidget, idGetWidgetObserveValue, null, null); // IObservableValue observeTextTxtNameObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtName); IObservableValue nameGetWidgetObserveValue = BeanProperties.value("name").observe(getWidget()); UpdateValueStrategy strategy_3 = new UpdateValueStrategy(); strategy_3.setConverter(new StringToDisplayableStringConverter()); bindingContext.bindValue(observeTextTxtNameObserveWidget, nameGetWidgetObserveValue,null, strategy_3); // IObservableValue observeTextTxtDescriptionObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtDescription); IObservableValue descriptionGetWidgetObserveValue = BeanProperties.value("description").observe(getWidget()); UpdateValueStrategy strategy_2 = new UpdateValueStrategy(); strategy_2.setConverter(new StringToDisplayableStringConverter()); bindingContext.bindValue(observeTextTxtDescriptionObserveWidget, descriptionGetWidgetObserveValue, null, strategy_2); // IObservableValue observeTextTxtVersionObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtVersion); IObservableValue versionGetWidgetObserveValue = BeanProperties.value("version").observe(getWidget()); bindingContext.bindValue(observeTextTxtVersionObserveWidget, versionGetWidgetObserveValue, null, null); // return bindingContext; } @Override public void linkActivated(HyperlinkEvent e) { String href = (String) e.getHref(); Action action = null; if("export.app".equals(href)){ action = new NativeArtifactExportAction(getConfigEditor()); } else if("plugin.folder".equals(href)){ action = new LaunchCordovaPluginWizardAction(getConfigEditor(), CordovaPluginSelectionPage.PLUGIN_SOURCE_DIRECTORY); } else if("plugin.git".equals(href)){ action = new LaunchCordovaPluginWizardAction(getConfigEditor(), CordovaPluginSelectionPage.PLUGIN_SOURCE_GIT); } else if("plugin.registry".equals(href)){ action = new LaunchCordovaPluginWizardAction(getConfigEditor(), CordovaPluginSelectionPage.PLUGIN_SOURCE_REGISTRY); } Assert.isNotNull(action); action.run(); } @Override public void linkEntered(HyperlinkEvent e) { } @Override public void linkExited(HyperlinkEvent e) { } public void dispose() { Widget w = getWidget(); w.removePropertyChangeListener(engineListener); w.removePropertyChangeListener(authorListener); w.removePropertyChangeListener(contentListener); super.dispose(); } }