diff --git a/.travis.yml b/.travis.yml index e673cfab4379..01e00c40b921 100644 --- a/.travis.yml +++ b/.travis.yml @@ -109,6 +109,7 @@ matrix: - ant $OPTS build script: - travis_retry hide-logs.sh ant $OPTS -f platform/api.htmlui test + - travis_retry hide-logs.sh ant $OPTS -f platform/htmlui test - hide-logs.sh ant $OPTS -f platform/api.intent test - hide-logs.sh ant $OPTS -f platform/api.io test - hide-logs.sh ant $OPTS -f platform/api.progress test diff --git a/harness/nbjunit/src/org/netbeans/junit/MockServices.java b/harness/nbjunit/src/org/netbeans/junit/MockServices.java index c450ae552032..08f9cf053d92 100644 --- a/harness/nbjunit/src/org/netbeans/junit/MockServices.java +++ b/harness/nbjunit/src/org/netbeans/junit/MockServices.java @@ -37,8 +37,6 @@ import java.util.NoSuchElementException; import java.util.logging.Level; import java.util.logging.Logger; -import junit.framework.Assert; -import junit.framework.AssertionFailedError; /** * Lets you register mock implementations of global services. @@ -161,7 +159,10 @@ public ServiceClassLoader(Class[] services, ClassLoader l, boolean test) { for (Class c : services) { try { if (test) { - Assert.assertEquals(c, getParent().loadClass(c.getName())); + final Class real = getParent().loadClass(c.getName()); + if (!c.equals(real)) { + throw new AssertionError("Service " + c + " isn't " + real); + } } int mods = c.getModifiers(); if (!Modifier.isPublic(mods) || Modifier.isAbstract(mods)) { @@ -173,7 +174,7 @@ public ServiceClassLoader(Class[] services, ClassLoader l, boolean test) { } catch (NoSuchMethodException x) { throw (IllegalArgumentException) new IllegalArgumentException("Class " + c.getName() + " has no public no-arg constructor").initCause(x); } catch (Exception x) { - throw (AssertionFailedError) new AssertionFailedError(x.toString()).initCause(x); + throw new AssertionError(x.toString(), x); } } this.services = services; diff --git a/nbbuild/cluster.properties b/nbbuild/cluster.properties index cde5a720b97c..0120da02fca4 100644 --- a/nbbuild/cluster.properties +++ b/nbbuild/cluster.properties @@ -226,6 +226,7 @@ nb.cluster.platform=\ editor.mimelookup,\ editor.mimelookup.impl,\ favorites,\ + htmlui,\ janitor,\ javahelp,\ junitlib,\ @@ -263,6 +264,7 @@ nb.cluster.platform=\ o.apache.commons.logging,\ o.n.core,\ o.n.html.ko4j,\ + o.n.html.presenters.spi,\ o.n.html.xhr4j,\ o.n.swing.laf.dark,\ o.n.swing.laf.flatlaf,\ diff --git a/nbbuild/javadoctools/links.xml b/nbbuild/javadoctools/links.xml index e6e8f213c22c..d163be9d7e64 100644 --- a/nbbuild/javadoctools/links.xml +++ b/nbbuild/javadoctools/links.xml @@ -117,7 +117,7 @@ - + diff --git a/platform/api.htmlui/apichanges.xml b/platform/api.htmlui/apichanges.xml index 9e2cbe52b4f1..e2ff680b4a1e 100644 --- a/platform/api.htmlui/apichanges.xml +++ b/platform/api.htmlui/apichanges.xml @@ -25,6 +25,25 @@ HTML UI API + + + OnSubmit & Service Provider Interface + + + + + +

+ Validate your dialogs with + OnSubmit + interface. Write own + HTMLViewerSpi + to provide own "chrome" for displaying HTML UI components. +

+
+ + +
FreeGeoIp service no longer works diff --git a/platform/api.htmlui/manifest.mf b/platform/api.htmlui/manifest.mf index 1efee18ceff7..6e5cfe9a5774 100644 --- a/platform/api.htmlui/manifest.mf +++ b/platform/api.htmlui/manifest.mf @@ -1,5 +1,4 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.api.htmlui OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/htmlui/Bundle.properties -OpenIDE-Module-Specification-Version: 1.22 - +OpenIDE-Module-Specification-Version: 1.23 diff --git a/platform/api.htmlui/nbproject/project.xml b/platform/api.htmlui/nbproject/project.xml index 276307bdc18d..d2ab119ef323 100644 --- a/platform/api.htmlui/nbproject/project.xml +++ b/platform/api.htmlui/nbproject/project.xml @@ -37,12 +37,6 @@ - - net.java.html.boot.fx - - - - net.java.html.json @@ -57,14 +51,6 @@ org.netbeans.html.xhr4j - - org.netbeans.libs.javafx - - - - 2.4 - - org.netbeans.libs.asm @@ -77,14 +63,6 @@ 7.62 - - org.openide.dialogs - - - - 7.30 - - org.openide.filesystems @@ -93,14 +71,6 @@ 9.0 - - org.openide.util.ui - - - - 9.3 - - org.openide.util @@ -118,11 +88,11 @@ - org.openide.windows + org.openide.util.ui - 6.71 + 9.3 @@ -142,10 +112,15 @@ org.netbeans.libs.testng + + org.netbeans.modules.nbjunit + + org.netbeans.api.htmlui + org.netbeans.spi.htmlui diff --git a/platform/api.htmlui/src/org/netbeans/api/htmlui/HTMLDialog.java b/platform/api.htmlui/src/org/netbeans/api/htmlui/HTMLDialog.java index ebc1fd1749ca..b45dacf4ee6e 100644 --- a/platform/api.htmlui/src/org/netbeans/api/htmlui/HTMLDialog.java +++ b/platform/api.htmlui/src/org/netbeans/api/htmlui/HTMLDialog.java @@ -23,76 +23,57 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Locale; -import java.awt.event.ActionEvent; -import net.java.html.json.Model; -import net.java.html.json.Property; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.netbeans.html.context.spi.Contexts.Id; -import org.netbeans.modules.htmlui.HTMLDialogImpl; +import org.netbeans.modules.htmlui.HTMLDialogBase; -/** Generates method that opens an HTML based modal dialog. Sample of a typical +/** Generates method that opens an HTML based modal dialog. Sample of a typical * usage follows. - * HTML Page dialog.html - *
-<html>
-    <head> <!-- btw. use data-netbeans-css attribute to control the CSS -->
-        <title>Base question</title>
-        <meta charset="UTF-8">
-    </head>
-    <body>
-        <div>Hello World! How are you?</div>
-        <-- you need to check the checkbox to enabled the OK button -->
-        <input type="checkbox" data-bind="checked: ok">OK?<br>
-        <-- enabled with checkbox is checked -->
-        <button id='ok' hidden data-bind="enable: ok">Good</button>
-        <button id='bad' hidden>Bad</button>
-    </body>
-</html>
- * 
- * Java Source AskQuestion.java - *
-{@link Model @Model}(className = "AskCtrl", properties = {
-    {@link Property @Property}(name = "ok", type = boolean.class)
-})
-public final class AskQuestion implements ActionListener {
-    {@link HTMLDialog @HTMLDialog}(url = "dialog.html") static void showHelloWorld(boolean checked) {
-        new AskCtrl(checked).applyBindings();
-    }
-
-    {@link Override @Override} public void actionPerformed({@link ActionEvent} e) {
-        // shows dialog with a question, checkbox is checked by default
-        // {@link #className() Pages} is automatically generated class 
-        String ret = Pages.showHelloWorld(true);
-        
-        System.out.println("User selected: " + ret);
-    }
-}
- * 
+ *
HTML Page dialog.html
+ *

+ * {@codesnippet org.netbeans.api.htmlui.dialog.html} + * The dialog.html page defines two buttons as hidden - + * they are re-rendered by the embedding "chrome" (for example as Swing buttons), + * but they can be enabled/disabled. For example the ok property + * (defined in the Java model below) connects the state of the checkbox and + * the Good button. + * + *

Java Source AskQuestion.java
+ * {@codesnippet org.netbeans.api.htmlui.AskQuestion} + *

+ * The method is generated into AskPages class (specified in the + * {@code className} attribute) + * in the same package and has the same name, + * and parameters as the method annotated by the {@code HTMLDialog} annotation. *

- * The method is generated into Pages class in the same package - * (unless one changes the name via {@link #className()}) and has the same name, - * and parameters as the method annotated by this annotation. When the method - * is invoked, it opens a dialog, loads an HTML page into it. When the page is - * loaded, it calls back the method annotated by this annotation and passes it - * its own arguments. The method is supposed to make the page live, preferrably - * by using {@link net.java.html.json.Model} generated class and calling - * applyBindings() on it. + * When the method {@code AskPages.showHelloWorld(true)} + * is invoked, it opens a dialog, loads an HTML page {@code dialog.html} + * into it. When the page is + * loaded, it calls back the method {@code AskQuestion.showHelloWorld} + * and passes it + * its own arguments. The method is supposed to make the page live, preferrably + * by using {@link net.java.html.json.Model} generated class and calling + * applyBindings() on it. The method is suggested to return + * an instance of {@link OnSubmit} callback to be notified about user pressing + * one of the dialog buttons. *

- * The HTML page may contain hidden <button> elements. If it does so, + * The HTML page may contain hidden <button> elements. If it does so, * those buttons are copied to the dialog frame and displayed underneath the page. * Their enabled/disabled state reflects the state of the buttons in the page. - * When one of the buttons is selected, the dialog closes and the generated - * method returns with 'id' of the selected button (or null if - * the dialog was closed). + * When one of the buttons is selected a callback to {@link OnSubmit} instance + * is made. If it returns {@code true}, the dialog closes otherwise its closing + * is prevented. A {@code null} 'id' signals user closing or cancelling the dialog. *

* By default, if the HTML defines no hidden * <button> elements, two buttons are added. One representing * the OK choice (with id="OK") and one representing * the cancel choice (with null id). Both buttons are always - * enabled. One can check the - * return value from the dialog showing method - * to be "OK" to know whether the - * user approved the dialog. + * enabled. One can check the callback 'id' + * to be "OK" to know whether the user approved the dialog. * + * * @author Jaroslav Tulach */ @Retention(RetentionPolicy.SOURCE) @@ -102,17 +83,17 @@ * Will be resolved by the annotation processor and converted into * nbresloc protocol - as such the HTML page can be L10Ned * later by adding classical L10N suffixes. E.g. index_cs.html - * will take preceedence over index.html if the user is + * will take preceedence over index.html if the user is * running in Czech {@link Locale}. - * + * * @return relative path the HTML page */ String url(); - + /** Name of the file to generate the method that opens the dialog * into. Class of such name will be generated into the same - * package. - * + * package. + * * @return name of class to generate */ String className() default "Pages"; @@ -120,30 +101,54 @@ /** Selects some of provided technologies. The HTML/Java API @ version 1.1 * supports {@link Id technology ids}. One can specify the preferred ones * to use in this NetBeans component by using this attribute. - * + * * @return list of preferred technology ids * @since 1.3 */ String[] techIds() default {}; - - /** Rather than using this class directly, consider - * {@link HTMLDialog}. The {@link HTMLDialog} annotation + + /** Callback to be notified when user closes a dialog. Return an + * implementation of this interface from a method annotated by + * {@link HTMLDialog} annotation: + *

+ * {@codesnippet org.netbeans.api.htmlui.AskQuestion} + * + * The example returns a lambda function which gets automatically + * converted into {@code OnSubmit} instance. + * + * @since 1.23 + */ + @FunctionalInterface + public interface OnSubmit { + /** Callback when a button is pressed. + * + * @param button the ID of the pressed button or {@code null} on cancel + * @return {@code true} to close the dialog, {@code false} to ignore + * the button press and leave the dialog open + * @since 1.23 + */ + boolean onSubmit(String button); + } + + /** Rather than using this class directly, consider + * {@link HTMLDialog}. The {@link HTMLDialog} annotation * generates boilderplate code for you - * and can do some compile times checks helping you to warnings + * and can do some compile times checks helping you to get warnings * as soon as possible. */ public static final class Builder { - private final HTMLDialogImpl impl; - + private final String url; + private List techIds = new ArrayList<>(); + private Runnable onPageLoad; + private Builder(String u) { - impl = new HTMLDialogImpl(); - impl.setUrl(u); + this.url = u; } /** Starts creation of a new HTML dialog. The page * can contain hidden buttons as described at * {@link HTMLDialog}. - * + * * @param url URL (usually using nbresloc protocol) * of the page to display in the dialog. * @return instance of the builder @@ -151,51 +156,64 @@ private Builder(String u) { public static Builder newDialog(String url) { return new Builder(url); } - + /** Registers a runnable to be executed when the page * becomes ready. - * + * * @param run runnable to run * @return this builder */ public Builder loadFinished(Runnable run) { - impl.setOnPageLoad(run); + this.onPageLoad = run; return this; } - + /** Requests some of provided technologies. The HTML/Java API @ version 1.1 * supports {@link Id technology ids}. One can specify the preferred ones * to use in this NetBeans component by using calling this method. - * + * * @param ids list of preferred technology ids to add to the builder * @return instance of the builder * @since 1.3 */ public Builder addTechIds(String... ids) { - impl.addTechIds(ids); + techIds.addAll(Arrays.asList(ids)); return this; } - /** Displays the dialog. This method blocks waiting for the - * dialog to be shown and closed by the user. - * + /** Displays the dialog and waits. This method blocks waiting for the + * dialog to be shown and closed by the user. + * * @return 'id' of a selected button element or null * if the dialog was closed without selecting a button */ public String showAndWait() { + HTMLDialogBase impl = HTMLDialogBase.create(url, onPageLoad, null, techIds.toArray(new String[0]), null); return impl.showAndWait(); } - + + /** Displays the dialog and returns immediately. + * + * @param s callback to call when a button is clicked and dialog + * is about to be closed + * @since 1.23 + */ + public void show(OnSubmit s) { + HTMLDialogBase impl = HTMLDialogBase.create(url, onPageLoad, s, techIds.toArray(new String[0]), null); + impl.show(s); + } + /** Obtains the component from the builder. The parameter * can either be {@link javafx.embed.swing.JFXPanel}.class or * {@link javafx.scene.web.WebView}.class. After calling this * method the builder becomes useless. - * + * * @param requested component type * @param type either {@link javafx.embed.swing.JFXPanel} or {@link javafx.scene.web.WebView} class * @return instance of the requested component */ public C component(Class type) { + HTMLDialogBase impl = HTMLDialogBase.create(url, onPageLoad, null, techIds.toArray(new String[0]), type); return impl.component(type); } } diff --git a/platform/api.htmlui/src/org/netbeans/api/htmlui/OpenHTMLRegistration.java b/platform/api.htmlui/src/org/netbeans/api/htmlui/OpenHTMLRegistration.java index 850894d37385..8a28f7f28210 100644 --- a/platform/api.htmlui/src/org/netbeans/api/htmlui/OpenHTMLRegistration.java +++ b/platform/api.htmlui/src/org/netbeans/api/htmlui/OpenHTMLRegistration.java @@ -34,36 +34,20 @@ * In such case the associated static method (which is annotated by this annotation) will be * called once the HTML page is loaded. One is expected to instantiate class generated * by the {@link net.java.html.json.Model} annotation and call applyBindings() - * on it. Here is an example:

- *{@link net.java.html.json.Model @Model}(className="UI", properties={
- *  {@link net.java.html.json.Property @Property}(name = "text", type = {@link String}.class)
- *})
- *public final class UICntrl {
- *  {@link org.openide.awt.ActionID @ActionID}(
- *     category = "Tools",
- *     id = "my.sample.HtmlHelloWorld"
- *  )
- *  {@link org.openide.awt.ActionReferences @ActionReferences}({
- *    {@link org.openide.awt.ActionReference @ActionReference}(path = "Menu/Tools"),
- *    {@link org.openide.awt.ActionReference @ActionReference}(path = "Toolbars/File"),
- *  })
- *  {@link org.openide.util.NbBundle.Messages @NbBundle.Messages}("CTL_OpenHtmlHelloWorld=Open HTML Hello World!")
- *  {@link OpenHTMLRegistration @OpenHTMLRegistration}(
- *    url = "ui.html",
- *    displayName = "#CTL_OpenHtmlHelloWorld"
- *  )
- *  public static UI onPageLoad() {
- *    return new UI("Hello World!").applyBindings();
- *  }
- *}
- * 
- * The above would display a new action in Toolbar and in Menu that would, upon invocation, + * on it. Here is an example: + *

+ * {@codesnippet org.netbeans.api.htmlui.UICntrl} + +* The above would display a new action in Toolbar and in Menu that would, upon invocation, * open up a new component displaying the * ui.html page. The page can use * Knockout.js bindings like - * <input data-bind="value: text"></input> to reference + * <input data-bind="textInput: text"></input> to reference * properties defined by the {@link net.java.html.json.Model} annotation in the generated class - * UI. + * UI: + *

+ * {@codesnippet org.netbeans.api.htmlui.ui.html} + * *

* In addition to the above, there is a special support for influencing {@link org.openide.util.Utilities#actionsGlobalContext() * action context} and thus turning on and off various actions shown in menu and toolbar. Just diff --git a/platform/api.htmlui/src/org/netbeans/modules/htmlui/Buttons.java b/platform/api.htmlui/src/org/netbeans/modules/htmlui/Buttons.java index 21c86bb8233a..50a7408ba2c4 100644 --- a/platform/api.htmlui/src/org/netbeans/modules/htmlui/Buttons.java +++ b/platform/api.htmlui/src/org/netbeans/modules/htmlui/Buttons.java @@ -18,21 +18,41 @@ */ package org.netbeans.modules.htmlui; -import java.awt.EventQueue; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import javax.swing.JButton; +import java.util.Objects; import net.java.html.js.JavaScriptBody; +import org.netbeans.api.htmlui.HTMLDialog; +import org.netbeans.spi.htmlui.HTMLViewerSpi; import org.openide.util.NbBundle; /** * * @author Jaroslav Tulach */ -final class Buttons { - private final List arr = new ArrayList<>(); - - @JavaScriptBody(args = {}, javacall = true, body = +class Buttons { + private static final String PREFIX = "dialog-buttons-"; + + private final HTMLDialog.OnSubmit onSubmit; + private final HTMLViewerSpi viewer; + private final View view; + + private final List