/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.web.pubspec; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.core.generator.DartIdentifierUtil; import com.google.dart.tools.core.pub.IModelListener; import com.google.dart.tools.core.pub.PubspecModel; import com.google.dart.tools.core.pub.RunPubJob; import com.google.dart.tools.core.utilities.yaml.PubYamlUtils; import com.google.dart.tools.ui.actions.RunPubAction; import com.google.dart.tools.ui.actions.RunPublishAction; import com.google.dart.tools.ui.internal.util.ExternalBrowserUtil; import com.google.dart.tools.ui.web.DartWebPlugin; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; 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.TraverseEvent; import org.eclipse.swt.events.TraverseListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.List; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.PartInitException; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.SectionPart; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.editor.FormPage; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Hyperlink; import org.eclipse.ui.forms.widgets.ImageHyperlink; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.forms.widgets.TableWrapLayout; /** * The forms page for the Pubspec Editor */ public class OverviewFormPage extends FormPage implements IModelListener { private static String NAME_MESSAGE_KEY = "nameMessage"; private static String VERSION_MESSAGE_KEY = "versionMessage"; private static String SDK_VERSION_MESSAGE_KEY = "sdkVersionMessage"; private Control lastFocusControl; private DependenciesMasterBlock block; private Text nameText; private Text authorText; private Text versionText; private Text homepageText; private Text description; private SectionPart infoSectionPart; private FormToolkit toolkit; private IManagedForm form; private PubspecModel model; private boolean ignoreModify = false; private Text sdkVersionText; private boolean editable; private Text documentationText; public OverviewFormPage(FormEditor editor) { super(editor, "overview", "Overview"); editable = ((PubspecEditor) editor).isEditable(); block = new DependenciesMasterBlock(this, editable); model = ((PubspecEditor) this.getEditor()).getModel(); lastFocusControl = null; } /** * Add focus listeners to the specified composite and its children that track the last control to * have focus before a page change or the editor lost focus */ public void addLastFocusListeners(Composite composite) { Control[] controls = composite.getChildren(); for (int i = 0; i < controls.length; i++) { Control control = controls[i]; // Add a focus listener if the control is any one of the below types if ((control instanceof Text) || (control instanceof Button) || (control instanceof Combo) || (control instanceof Table) || (control instanceof Hyperlink) || (control instanceof List)) { addLastFocusListener(control); } if (control instanceof Composite) { // Recursively add focus listeners to this composites children addLastFocusListeners((Composite) control); } } } @Override public void createPartControl(Composite parent) { super.createPartControl(parent); // Dynamically add focus listeners to all the forms children in order // to track the last focus control IManagedForm managedForm = getManagedForm(); if (managedForm != null) { addLastFocusListeners(managedForm.getForm()); } } public Control getLastFocusControl() { return lastFocusControl; } @Override public void modelChanged(Object[] objects, String type) { if (type.equals(IModelListener.REFRESH)) { ignoreModify = true; } updateInfoSection(); } public void setLastFocusControl(Control control) { lastFocusControl = control; } /** * Set the focus on the last control to have focus before a page change or the editor lost focus. */ public void updateFormSelection() { if ((lastFocusControl != null) && (lastFocusControl.isDisposed() == false)) { Control lastControl = lastFocusControl; // Set focus on the control lastControl.forceFocus(); // If the control is a Text widget, select its contents if (lastControl instanceof Text) { Text text = (Text) lastControl; text.setSelection(0, text.getText().length()); } } else { if (model.getDependecies().length > 0) { block.getViewer().setSelection(new StructuredSelection(model.getDependecies()[0])); block.getViewer().getTable().forceFocus(); } setFocus(); } } @Override protected void createFormContent(final IManagedForm managedForm) { this.form = managedForm; final ScrolledForm scrolledForm = form.getForm(); toolkit = form.getToolkit(); scrolledForm.setText("Pubspec Details"); scrolledForm.setImage(DartWebPlugin.getImage("pubspec.png")); toolkit.decorateFormHeading(scrolledForm.getForm()); scrolledForm.getBody().setLayout(new GridLayout()); Composite top = toolkit.createComposite(scrolledForm.getBody()); GridLayout layout = new GridLayout(); layout.marginWidth = 0; layout.numColumns = 2; top.setLayout(layout); top.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); Composite bottom = toolkit.createComposite(scrolledForm.getBody()); bottom.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); createInfoSection(top, scrolledForm, toolkit); Composite right = toolkit.createComposite(top); GridLayoutFactory.fillDefaults().applyTo(right); GridData griData = new GridData(SWT.FILL, SWT.TOP, false, false); griData.widthHint = 350; right.setLayoutData(griData); if (editable) { createActionsSection(right); } createExploreSection(right); block.createContent(form, bottom); model.addModelListener(this); } /** * Add a focus listener to the specified control that tracks the last control to have focus on * this page. When focus is gained by this control, it registers itself as the last control to * have focus. The last control to have focus is stored in order to be restored after a page * change or editor loses focus. */ private void addLastFocusListener(final Control control) { control.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { // NO-OP } @Override public void focusLost(FocusEvent e) { lastFocusControl = control; } }); } private void createActionsSection(Composite composite) { Section section = toolkit.createSection(composite, Section.TITLE_BAR); GridData sectionLayoutData = new GridData(SWT.FILL, SWT.FILL, true, true); section.setLayoutData(sectionLayoutData); section.setText("Pub Actions"); Composite client = toolkit.createComposite(section); client.setLayout(new TableWrapLayout()); section.setClient(client); Composite links = new Composite(client, SWT.NONE); GridLayoutFactory.fillDefaults().spacing(15, 5).applyTo(links); ImageHyperlink saveActionText = toolkit.createImageHyperlink(links, SWT.NONE); saveActionText.setText("Run pub get"); saveActionText.setImage(DartWebPlugin.getImage("pubspec.png")); saveActionText.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { if (getEditor().isDirty()) { getEditor().doSave(new NullProgressMonitor()); if (!DartCore.getPlugin().isAutoRunPubEnabled()) { runPub(); } } else { runPub(); } } }); ImageHyperlink deployActionText = toolkit.createImageHyperlink(links, SWT.NONE); deployActionText.setText("Run pub build - minified"); deployActionText.setImage(DartWebPlugin.getImage("package_obj.gif")); deployActionText.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { RunPubAction pubAction = RunPublishAction.createPubBuildAction(getSite().getWorkbenchWindow()); pubAction.run(); } }); ImageHyperlink deployDebugActionText = toolkit.createImageHyperlink(links, SWT.NONE); deployDebugActionText.setText("Run pub build - debug"); deployDebugActionText.setImage(DartWebPlugin.getImage("package_obj.gif")); deployDebugActionText.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { RunPubAction pubAction = RunPublishAction.createPubBuildDebugAction(getSite().getWorkbenchWindow()); pubAction.run(); } }); ImageHyperlink publishActionText = toolkit.createImageHyperlink(links, SWT.NONE); publishActionText.setText("Publish on pub.dartlang.org..."); publishActionText.setImage(DartWebPlugin.getImage("export.gif")); publishActionText.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { RunPublishAction pubAction = RunPublishAction.createPubPublishAction(getSite().getWorkbenchWindow()); pubAction.run(); } }); } private void createExploreSection(Composite top) { Section section = toolkit.createSection(top, Section.TITLE_BAR); GridData sectionLayoutData = new GridData(SWT.FILL, SWT.TOP, true, false); section.setLayoutData(sectionLayoutData); section.setText("Explore"); Composite client = toolkit.createComposite(section); client.setLayout(new TableWrapLayout()); section.setClient(client); Composite links = new Composite(client, SWT.NONE); GridLayoutFactory.fillDefaults().spacing(15, 5).applyTo(links); Hyperlink link = toolkit.createHyperlink(client, "Show packages on pub.dartlang.org", SWT.NONE); link.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { try { getSite().getPage().showView("com.google.dart.tools.ui.view.packages"); } catch (PartInitException exception) { DartWebPlugin.logError(exception); } } }); createExternalLink( links, "View Pubspec documentation", "http://pub.dartlang.org/doc/pubspec.html"); createExternalLink(links, "View Semantic versioning documentation", "http://semver.org/"); } private void createExternalLink(Composite client, String text, final String href) { Hyperlink link = toolkit.createHyperlink(client, text, SWT.NONE); link.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { ExternalBrowserUtil.openInExternalBrowser(href); } }); } private void createInfoSection(Composite parent, final ScrolledForm scrolledForm, FormToolkit toolkit) { Section section = toolkit.createSection(parent, Section.TITLE_BAR); GridData sectionLayoutData = new GridData(SWT.FILL, SWT.BOTTOM, true, false); sectionLayoutData.horizontalSpan = 1; section.setLayoutData(sectionLayoutData); section.setRedraw(true); section.setText("General Information"); Composite client = toolkit.createComposite(section); GridLayoutFactory.swtDefaults().spacing(5, 5).numColumns(2).margins(0, 0).applyTo(client); section.setClient(client); infoSectionPart = new SectionPart(section); form.addPart(infoSectionPart); Label nameLabel = toolkit.createLabel(client, "Name:"); nameLabel.setToolTipText("A unique name to identify this package. The name should be a valid Dart identifier."); nameText = toolkit.createText(client, "", SWT.SINGLE | SWT.BORDER); nameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); nameText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { if (validateName(nameText.getText().trim())) { model.setName(nameText.getText().trim()); } setTextDirty(); } }); Label authorLabel = toolkit.createLabel(client, "Author:"); authorLabel.setToolTipText("Name(s) of the author(s) of this package. Email address can also be included."); authorText = toolkit.createText(client, "", SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.SCROLL_LINE); GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP).grab(true, false).hint(200, SWT.DEFAULT).applyTo( authorText); authorText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { model.setAuthor(authorText.getText().trim()); setTextDirty(); } }); authorText.addTraverseListener(new TraverseListener() { @Override public void keyTraversed(TraverseEvent e) { if (e.detail == SWT.TRAVERSE_TAB_NEXT || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) { e.doit = true; } } }); Label versionLabel = toolkit.createLabel(client, "Version:"); versionLabel.setToolTipText("A version number is three numbers separated by dots, like 0.2.43. " + "It can also have a build (+hotfix.oopsie) or pre-release (-alpha.12) suffix."); versionText = toolkit.createText(client, "", SWT.SINGLE | SWT.BORDER); versionText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); versionText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { if (validateVersion(versionText.getText().trim())) { model.setVersion(versionText.getText().trim()); } setTextDirty(); } }); Label homepageLabel = toolkit.createLabel(client, "Homepage: "); homepageLabel.setToolTipText("The homepage is the URL pointing to the website for this package."); homepageText = toolkit.createText(client, "", SWT.SINGLE | SWT.BORDER); homepageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); homepageText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { model.setHomepage(homepageText.getText().trim()); setTextDirty(); } }); Label documentationLabel = toolkit.createLabel(client, "Documentation: "); documentationLabel.setToolTipText("URL for the site that hosts documentation separate from the main homepage for this package."); documentationText = toolkit.createText(client, "", SWT.SINGLE | SWT.BORDER); documentationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); documentationText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { model.setDocumentation(documentationText.getText().trim()); setTextDirty(); } }); Label sdkVersionLabel = toolkit.createLabel(client, "SDK version:"); sdkVersionLabel.setToolTipText("Set SDK version contraints for this package"); sdkVersionText = toolkit.createText(client, "", SWT.SINGLE | SWT.BORDER); sdkVersionText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); sdkVersionText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { if (validateVersionConstriants(sdkVersionText.getText().trim())) { model.setSdkVersion(sdkVersionText.getText().trim()); } setTextDirty(); } }); Label descriptionLabel = toolkit.createLabel(client, "Description:"); descriptionLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); descriptionLabel.setToolTipText("A description about this package"); description = toolkit.createText(client, "", SWT.MULTI | SWT.WRAP | SWT.V_SCROLL | SWT.BORDER); toolkit.adapt(description, true, true); GridData gd = new GridData(SWT.FILL, SWT.CENTER, true, false); gd.widthHint = 200; gd.heightHint = 50; description.setLayoutData(gd); description.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { model.setDescription(description.getText().trim()); setTextDirty(); } }); ignoreModify = true; updateInfoSection(); if (model != null && model.isErrorOnParse()) { MessageDialog.openError( null, "Pubspec Editor", "Looks like the pubspec.yaml has invalid syntax or is corrupted.\nSwitch to the Source tab to fix."); } infoSectionPart.getSection().setEnabled(editable); } private void runPub() { IEditorInput input = getEditorInput(); if (input instanceof IFileEditorInput) { IFile file = ((IFileEditorInput) input).getFile(); if (file != null) { new RunPubJob(file.getParent(), RunPubJob.INSTALL_COMMAND, false).schedule(); } } } private void setTextDirty() { if (!ignoreModify) { model.setDirty(true); infoSectionPart.markDirty(); } } private void updateInfoSection() { if (model != null) { nameText.setText(model.getName()); versionText.setText(model.getVersion()); description.setText(model.getDescription()); homepageText.setText(model.getHomepage()); authorText.setText(model.getAuthor()); sdkVersionText.setText(model.getSdkVersion()); documentationText.setText(model.getDocumentation()); } if (ignoreModify) { ignoreModify = false; } } private boolean validateName(String name) { IStatus status = DartIdentifierUtil.validateIdentifier(name); if (status == Status.OK_STATUS) { form.getMessageManager().removeMessage(NAME_MESSAGE_KEY, nameText); return true; } if (status.getSeverity() == Status.ERROR) { form.getMessageManager().addMessage( NAME_MESSAGE_KEY, "The name must be all lowercase, start with an alphabetic character, '_' or '$' and include only [a-z0-9_].", null, IMessageProvider.ERROR, nameText); } return false; } private boolean validateVersion(String version) { if (version.isEmpty() || version.matches(PubYamlUtils.PACKAGE_VERSION_EXPRESSION)) { form.getMessageManager().removeMessage(VERSION_MESSAGE_KEY, versionText); return true; } form.getMessageManager().addMessage( VERSION_MESSAGE_KEY, "The specified version does not have the correct format (major.minor.patch), or contains invalid characters.", null, IMessageProvider.ERROR, versionText); return false; } private boolean validateVersionConstriants(String version) { boolean isValid = PubYamlUtils.isValidVersionConstraintString(version); if (isValid) { getManagedForm().getMessageManager().removeMessage(SDK_VERSION_MESSAGE_KEY, sdkVersionText); } else { getManagedForm().getMessageManager().addMessage( SDK_VERSION_MESSAGE_KEY, "The SDK version constriant does not have the correct format as in '1.0.0', '<1.5.0', \n'>=2.0.0 <3.0.0', or it contains invalid characters", null, IMessageProvider.ERROR, sdkVersionText); } return isValid; } }