]> git.argeo.org Git - gpl/argeo-suite.git/blob - swt/org.argeo.app.geo.swt/src/org/argeo/app/geo/swt/SwtJSMapPart.java
09848f9757f7069008f01f578a586b8f0518aadd
[gpl/argeo-suite.git] / swt / org.argeo.app.geo.swt / src / org / argeo / app / geo / swt / SwtJSMapPart.java
1 package org.argeo.app.geo.swt;
2
3 import java.util.Locale;
4 import java.util.concurrent.CompletableFuture;
5 import java.util.concurrent.CompletionStage;
6 import java.util.function.Consumer;
7
8 import org.argeo.api.cms.CmsLog;
9 import org.argeo.app.geo.ux.JsImplementation;
10 import org.argeo.app.geo.ux.MapPart;
11 import org.argeo.cms.swt.CmsSwtUtils;
12 import org.eclipse.swt.SWT;
13 import org.eclipse.swt.browser.Browser;
14 import org.eclipse.swt.browser.BrowserFunction;
15 import org.eclipse.swt.browser.ProgressEvent;
16 import org.eclipse.swt.browser.ProgressListener;
17 import org.eclipse.swt.layout.GridData;
18 import org.eclipse.swt.widgets.Composite;
19
20 /**
21 * An SWT implementation of {@link MapPart} based on JavaScript execute in a
22 * {@link Browser} control.
23 */
24 public class SwtJSMapPart extends Composite implements MapPart {
25 static final long serialVersionUID = 2713128477504858552L;
26
27 private final static CmsLog log = CmsLog.getLog(SwtJSMapPart.class);
28
29 private final static String GLOBAL_THIS_ = "globalThis.";
30
31 private final Browser browser;
32
33 private final CompletableFuture<Boolean> pageLoaded = new CompletableFuture<>();
34
35 private String jsImplementation = JsImplementation.OPENLAYERS_MAP_PART.getJsClass();
36 private String mapVar = "argeoMap";
37
38 public SwtJSMapPart(Composite parent, int style) {
39 super(parent, style);
40 parent.setLayout(CmsSwtUtils.noSpaceGridLayout());
41 setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
42 setLayout(CmsSwtUtils.noSpaceGridLayout());
43
44 browser = new Browser(this, SWT.BORDER);
45 browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
46
47 browser.setUrl("/pkg/org.argeo.app.geo.js/index.html");
48 browser.addProgressListener(new ProgressListener() {
49 static final long serialVersionUID = 1L;
50
51 @Override
52 public void completed(ProgressEvent event) {
53 try {
54 // create map
55 browser.execute(getJsMapVar() + " = new " + jsImplementation + "();");
56 pageLoaded.complete(true);
57 } catch (Exception e) {
58 log.error("Cannot create map in browser", e);
59 pageLoaded.complete(false);
60 }
61 }
62
63 @Override
64 public void changed(ProgressEvent event) {
65 }
66 });
67 }
68
69 /*
70 * MapPart.js METHODS
71 */
72
73 @Override
74 public void addPoint(double lng, double lat, String style) {
75 callMapMethod("addPoint(%f, %f, %s)", lng, lat, style == null ? "'default'" : style);
76 }
77
78 @Override
79 public void addUrlLayer(String url, GeoFormat format) {
80 callMapMethod("addUrlLayer('%s', '%s')", url, format.name());
81 }
82
83 @Override
84 public void setZoom(int zoom) {
85 callMapMethod("setZoom(%d)", zoom);
86 }
87
88 @Override
89 public void setCenter(double lng, double lat) {
90 callMapMethod("setCenter(%f, %f)", lng, lat);
91 }
92
93 protected CompletionStage<Object> callMapMethod(String methodCall, Object... args) {
94 return callMethod(getJsMapVar(), methodCall, args);
95 }
96
97 protected CompletionStage<Object> callMethod(String jsObject, String methodCall, Object... args) {
98 return evaluate(jsObject + '.' + methodCall, args);
99 }
100
101 private String getJsMapVar() {
102 return GLOBAL_THIS_ + mapVar;
103 }
104
105 /**
106 * Execute this JavaScript on the client side after making sure that the page
107 * has been loaded and the map object has been created.
108 *
109 * @param js the JavaScript code, possibly formatted according to
110 * {@link String#format}, with {@link Locale#ROOT} as locale (for
111 * stability of decimal separator, as expected by JavaScript.
112 * @param args the optional arguments of
113 * {@link String#format(String, Object...)}
114 */
115 protected CompletionStage<Object> evaluate(String js, Object... args) {
116 CompletableFuture<Object> res = pageLoaded.thenApply((ready) -> {
117 if (!ready)
118 throw new IllegalStateException("Map " + mapVar + " is not initialised.");
119 Object result = browser.evaluate(String.format(Locale.ROOT, js, args));
120 return result;
121 });
122 return res.minimalCompletionStage();
123 }
124
125 /*
126 * CALLBACKS
127 */
128 public void onFeatureSelected(Consumer<FeatureSelectedEvent> toDo) {
129 addCallback("FeatureSelected", (arr) -> toDo.accept(new FeatureSelectedEvent((String) arr[0])));
130 }
131
132 public void onFeatureSingleClick(Consumer<FeatureSingleClickEvent> toDo) {
133 addCallback("FeatureSingleClick", (arr) -> toDo.accept(new FeatureSingleClickEvent((String) arr[0])));
134 }
135
136 protected void addCallback(String suffix, Consumer<Object[]> toDo) {
137 pageLoaded.thenAccept((ready) -> {
138 // browser functions must be directly on window (RAP specific)
139 new BrowserFunction(browser, mapVar + "__on" + suffix) {
140
141 @Override
142 public Object function(Object[] arguments) {
143 toDo.accept(arguments);
144 return null;
145 }
146
147 };
148 browser.execute(getJsMapVar() + ".callbacks['on" + suffix + "']=window." + mapVar + "__on" + suffix + ";");
149 callMethod(mapVar, "enable" + suffix + "()");
150 });
151 }
152 }