Loading...
Searching...
No Matches
MouseXML.h
1/*
2
3MIT License
4
5Copyright (c) 2017 FMI Open Development / Markus Peura, first.last@fmi.fi
6
7Permission is hereby granted, free of charge, to any person obtaining a copy
8of this software and associated documentation files (the "Software"), to deal
9in the Software without restriction, including without limitation the rights
10to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11copies of the Software, and to permit persons to whom the Software is
12furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in all
15copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23SOFTWARE.
24
25*/
26/*
27 * MouseXML.h
28 *
29 * Created on: Jul 12, 2026
30 * Author: mpeura
31 */
32
33#ifndef DRAIN_IMAGE_MOUSEXML_H_
34#define DRAIN_IMAGE_MOUSEXML_H_
35
36#include "drain/Enum.h"
37#include "drain/util/EnumFlagger.h"
38
39#include "drain/StringBuilder.h"
40#include "drain/util/TreeXML.h"
41#include "drain/util/UtilsXML.h"
42#include "drain/util/JavaScriptXML.h"
43#include "drain/js/coords.h"
44
45namespace drain {
46
47namespace image {
48
64class MouseXML {
65
66public:
67
73 enum ElemClass {
74 MOUSE, // A group marked for interaction (mouse event listeners)
75 MOUSE_LISTENER, // Area inside which mouse events will be tracked.
76 MONITOR, // Display of interactive operations
77 MONITOR_MOVE, // Display something when mouse is moving, e.g. cursor coordinates.
78 MONITOR_DOWN, // Display something when mouse is pressed
79 MONITOR_UP, // Display something when mouse is released
80 MONITOR_DRAG, // Display something when mouse is dragged
81 // new:
82 // DATA_ARRAY, // Image used as data array only, not to be displayed
83 //SELECTOR, // Interactive element illustrating a selection by the user
84 };
85
86 // NEW
87 enum EventClass {
88 ENTER,
89 LEAVE,
90 MOVE,
91 DOWN,
92 UP,
93 CLICK,
94 };
95
96 // bool cursorCoord = false;
97
99
102 static
103 void addVisibilitySwitch(XML &dstElem, XML &listenerElem, const std::string & scope = "document",
104 const std::string & mouseEventOn = "onmouseenter",
105 const std::string & mouseEventOff = "onmouseleave");
106
107
108
109 enum CoordinateProcessing {
110 UNDEFINED = 0,
111 //BASIC = 1,
112 IMAGE_COORDS = 1,
113 RELATIVE_COORDS = 3,
114 GEOGRAPHIC_COORDS = 7,
115 };
116
118
119protected:
120
122
128 static
129 std::string getEventFunctionName(MouseXML::EventClass eventKey);
130
131 static
132 std::string getHandlerFunctionName(MouseXML::EventClass eventKey);
133
134 static
135 std::string getInitialiserFunctionName(MouseXML::EventClass eventKey);
136
138 template <class N>
139 static // , XML & elem , const EventClass & eventName
140 DRAIN_XML_TREE(N) & getListenerScope(DRAIN_XML_TREE(N) & root, const std::string & handlerName);
141
142 template <class N>
143 void adjustListenerGeoRef(DRAIN_XML_TREE(N) & handlerScopeJS, CoordFlagger::ivalue_t coords) const ;
144
145
147 template <class N>
148 static inline // const EventClass & mouseEventKey
149 DRAIN_XML_TREE(N) & getListenerInitScope(DRAIN_XML_TREE(N) & root, const std::string & initFnctName);
150
151 // template <class N>
152 //inline
153 //DRAIN_XML_TREE(N) & adjustListenerInitGeoRef(DRAIN_XML_TREE(N) & root, const EventClass & mouseEventKey) const;
154
155protected:
156
158 template <class N>
159 static
160 DRAIN_XML_TREE(N) & ensureListenerUNUSED(DRAIN_XML_TREE(N) & root, XML & elem, const EventClass & eventCls);
161
162 // template <class N>
163 static inline
164 void attachListener(XML & elem, const std::string & eventName, const std::string & handlerName){
165 elem.setAttribute(eventName, handlerName, "(evt)");
166 }
167
168 template <class N>
169 static
170 DRAIN_XML_TREE(N) & ensureListenerHandler(DRAIN_XML_TREE(N) & root, const std::string & handlerName);
171
172
174 template <class N>
175 static
176 DRAIN_XML_TREE(N) & ensureListenerInit(DRAIN_XML_TREE(N) & root, const std::string & initFnctName);
177 //const EventClass & eventName, CoordinateProcessing proc = UNDEFINED); //const std::string & eventName);
178
179 static
180 const std::string COORD_INIT_SCOPE;
181};
182
183
184}
185
186DRAIN_ENUM_DICT(image::MouseXML::ElemClass);
187DRAIN_ENUM_DICT(image::MouseXML::EventClass);
188DRAIN_ENUM_DICT(image::MouseXML::CoordinateProcessing);
189
190DRAIN_ENUM_CLASSXML(image::MouseXML::ElemClass)
191
192
193namespace image {
194
195DRAIN_ENUM_OSTREAM(image::MouseXML::ElemClass)
196DRAIN_ENUM_OSTREAM(image::MouseXML::EventClass)
197
198
199template <class N>
200void MouseXML::adjustListenerGeoRef(DRAIN_XML_TREE(N) & handlerScopeJS, CoordFlagger::ivalue_t coords) const {
201
202 const CoordFlagger flags(coords);
203
204 DRAIN_XML_TREE(N) & coordScopeJS = handlerScopeJS(COORD_INIT_SCOPE);
205
206 if (flags.isSet(IMAGE_COORDS)){
207 coordScopeJS[flags.str()] -> setProgramComment("Coord request: ", flags.str());
208 // TODO: detect if MOVE has already been defined, and drop (duplicated) coordinate processing from here.
209 coordScopeJS["ctx.bbox"] = "ctx.bbox = evt.target.getBoundingClientRect();";
210 coordScopeJS["COORDS"] -> setProgramComment("Image coordinates");
211 coordScopeJS["ctx.x"] = "ctx.x = Math.floor(evt.clientX - ctx.bbox.left);";
212 coordScopeJS["ctx.y"] = "ctx.y = Math.floor(evt.clientY - ctx.bbox.top);";
213 if (flags.isSet(RELATIVE_COORDS)){
214 coordScopeJS["RELATIVE_COORDS"] -> setProgramComment("Relative coordinates");
215 coordScopeJS["ctx.rx"] = "ctx.rx = ctx.x / ctx.bbox.width;";
216 coordScopeJS["ctx.ry"] = "ctx.ry = ctx.y / ctx.bbox.height;";
217 if (flags.isSet(GEOGRAPHIC_COORDS)){
218 coordScopeJS["GEOGRAPHIC_COORDS"] -> setProgramComment("Geographic coordinates");
219 // geo_bbox.left + rx*geo_bbox.width
220 coordScopeJS["ctx.gx"] = "ctx.gx = ctx.georef.bbox.left + ctx.rx*ctx.georef.bbox.width;";
221 coordScopeJS["ctx.gy"] = "ctx.gy = ctx.georef.bbox.top + ctx.ry*ctx.georef.bbox.height;";
222 }
223 }
224 }
225
226}
227
228
229template <class N>
230DRAIN_XML_TREE(N) & MouseXML::ensureListenerUNUSED(DRAIN_XML_TREE(N) & root, XML & elem, const EventClass & eventKey){
231
232 drain::Logger mout(__FILE__, __FUNCTION__);
233
234 std::string handlerName = getHandlerFunctionName(eventKey);
235 ensureListenerHandler(root, handlerName);
236
237 std::string eventName = getEventFunctionName(eventKey);
238 attachListener(elem, eventName, handlerName);
239
240}
241
242/*
243void MouseXML::attachListener(XML & elem, const std::string & eventName, const std::string & handlerName){
244 elem.setAttribute(eventName, handlerName, "(evt)");
245}
246*/
247
248template <class N>
249DRAIN_XML_TREE(N) & MouseXML::ensureListenerHandler(DRAIN_XML_TREE(N) & root, const std::string & handlerName){
250
251 // mout.attention("ensure ",eventName, '/',handlerName);
252
253 // evt is a standard name?
254 DRAIN_XML_TREE(N) & handlerFunctionScope = JavaScriptXML::ensureJavaScriptFunction(root, handlerName, "evt");
255
256 if (!handlerFunctionScope.hasChild(__FUNCTION__)){
257 handlerFunctionScope[__FUNCTION__]->setProgramComment("Std init by ", __FUNCTION__);
258 handlerFunctionScope["const_ctx"] = "const ctx = evt.target.ctx;";
259 DRAIN_XML_TREE(N) & coordInitSubScope = handlerFunctionScope[COORD_INIT_SCOPE];
260 coordInitSubScope->setType(N::SCOPE_CURLY);
261 coordInitSubScope->setText("if (true)");
262 coordInitSubScope.addChild()->setProgramComment("Reserved for coordinates by ", __FUNCTION__);
263 }
264 return handlerFunctionScope;
265
266 /*
267 if (proc == UNDEFINED){
268 if (eventKey == EventClass::MOVE){ // "move"){
269 proc = GEOGRAPHIC_COORDS;
270 }
271 else { // if ( eventKey == "move")
272 proc = BASIC;
273 }
274 }
275
276 if (!scopeJS.hasChild(__FUNCTION__)){
277 scopeJS.addChild(__FUNCTION__)->setProgramComment("Std init by ", __FUNCTION__);
278 scopeJS++ = "const ctx = evt.target.ctx;";
279 // scopeJS++ = "ctx.listerer = evt.target;";
280 int level = static_cast<int>(proc);
281 if (level >= COORDS){
282 // TODO: detect if MOVE has already been defined, and drop (duplicated) coordinate processing from here.
283 scopeJS++ = "ctx.bbox = evt.target.getBoundingClientRect();";
284 scopeJS++ -> setProgramComment("Image coordinates");
285 scopeJS++ = "ctx.x = Math.floor(evt.clientX - ctx.bbox.left);";
286 scopeJS++ = "ctx.y = Math.floor(evt.clientY - ctx.bbox.top);";
287 if (level >= RELATIVE_COORDS){
288 scopeJS++ -> setProgramComment("Relative coordinates");
289 scopeJS++ = "ctx.rx = ctx.x / ctx.bbox.width;";
290 scopeJS++ = "ctx.ry = ctx.y / ctx.bbox.height;";
291 if (level >= GEOGRAPHIC_COORDS){
292 scopeJS++ -> setProgramComment("Geographic coordinates");
293 // geo_bbox.left + rx*geo_bbox.width
294 scopeJS++ = "ctx.gx = ctx.georef.bbox.left + ctx.rx*ctx.georef.bbox.width;";
295 scopeJS++ = "ctx.gy = ctx.georef.bbox.top + ctx.ry*ctx.georef.bbox.height;";
296 }
297 }
298 }
299 // scopeJS++ = "ctx.elem = null;";
300 // scopeJS++ = "ctx.ctx = evt.target.ctx;"; // remove
301 // scopeJS++ = "/ * end init * /" ;
302 }
303 */
304
305}
306
307template <class N>
308DRAIN_XML_TREE(N) & MouseXML::getListenerScope(DRAIN_XML_TREE(N) & root, const std::string & handlerName){ //const EventClass & eventKey){
309 // , CoordinateProcessing proc){ // , const std::string & handlerName){
310
311 drain::Logger mout(__FILE__, __FUNCTION__);
312
313 DRAIN_XML_TREE(N) & scopeJS = ensureListenerHandler(root, handlerName); // ensureListener(root, elem, eventKey);
314
315 DRAIN_XML_TREE(N) & subScopeJS = scopeJS["SUBSCOPE"];
316 subScopeJS->setType(XML::SCOPE_CURLY);
317 // subScopeJS->setId();
318 subScopeJS->setText("with(ctx)");
319 // subScopeJS["COMMENT"]->setProgramComment(" local scope ");
320
321 // TODO: support direct scope?
322
323 return subScopeJS;
324 // return scopeJS;
325}
326
327template <class N>
328DRAIN_XML_TREE(N) & MouseXML::ensureListenerInit(DRAIN_XML_TREE(N) & root,
329 const std::string & initFnctName){ // , const std::string & eventKey){
330
331 drain::Logger mout(__FILE__, __FUNCTION__);
332
333 //std::string initFnctName = getInitialiserFunctionName(eventKey);
334 // mout.attention("init ", eventName, '/', handlerName);
335
336 DRAIN_XML_TREE(N) & scopeJS = JavaScriptXML::ensureJavaScriptFunction(root, initFnctName);
337 scopeJS->setId(initFnctName);
338 if (!scopeJS.hasChildren()){
339 scopeJS.addChild()->setText("/* Added by ", __FUNCTION__, "*/");
340 scopeJS.addChild()->setText("console.info('", __FUNCTION__, ':', initFnctName,"')");
341 /*
342 int level = static_cast<int>(proc);
343 scopeJS++ -> setProgramComment("GeoCoords:");
344 if (level >= GEOGRAPHIC_COORDS){
345 scopeJS++ -> setProgramComment("todo: GeoCoords");
346 }
347 */
348 }
349
350 // Ensure init
351 DRAIN_XML_TREE(N) & onLoadScope = JavaScriptXML::getOnLoadScript(root);
352 onLoadScope[initFnctName]->setText(initFnctName, "();");
353
354 return scopeJS;
355}
356
360template <class N>
361inline //const EventClass & mouseEventKey
362DRAIN_XML_TREE(N) & MouseXML::getListenerInitScope(DRAIN_XML_TREE(N) & root, const std::string & initFnctName) {
363
364 DRAIN_XML_TREE(N) & mouseInit = ensureListenerInit(root, initFnctName);
365 //setListenerInit();
366
367 std::string installerKey = initFnctName+"Installer"; // drain::Enum<MouseXML::EventClass>::getKey(mouseEventKey) + "INSTALLER";
368
369 DRAIN_XML_TREE(N) & installer = mouseInit[installerKey];
370 installer->setText("document.querySelectorAll('.", MouseXML::MOUSE, "').forEach(\n");
371
372 DRAIN_XML_TREE(N) & scope = installer[N::tag_t::SCOPE_CURLY](N::tag_t::SCOPE_CURLY);
373 if (scope.empty()){
374 scope->setText("group =>");
375 scope.addChild()->setProgramComment("MouseXML");
376 scope.addChild()->setText("var listener = group.querySelector('.", MouseXML::MOUSE_LISTENER, "');");
377 }
378 installer["end-foreach"]->setText(")");
379
380 DRAIN_XML_TREE(N) & initScope = scope[N::tag_t::SCOPE_CURLY];
381 if (initScope.empty()){
382 initScope->setType(N::tag_t::SCOPE_CURLY);
383 initScope->setText("if (listener)");
384 initScope["ensure_CTX"] = "if (!('ctx' in listener)) listener.ctx = {};";
385 }
386
387 // if GEOREF
388 if (!initScope.hasChild("georef")){
389 initScope["georef_comment"]->setProgramComment("Georeference added by ", __FUNCTION__, " (unconditional for now)");
390 initScope["georef"]->setText("if (!('georef' in listener.ctx)){ listener.ctx.georef = {}; }");
391 initScope["georef_bbox"]->setText("if (listener.hasAttribute('data-bbox')){ listener.ctx.georef.bbox = new BBox(listener.getAttribute('data-bbox'))};");
392 // consider other element (IMAGE title)
393 initScope["georef_epsg"]->setText("if (listener.hasAttribute('data-epsg')){ listener.ctx.georef.epsg = listener.getAttribute('data-epsg') };");
394 }
395
396 return initScope;
397 //return scope[N::tag_t::SCOPE_CURLY];
398}
399
400/*
401template <class N>
402inline
403DRAIN_XML_TREE(N) & MouseXML::adjustListenerInitGeoRef(DRAIN_XML_TREE(N) & root, const EventClass & mouseEventKey) const {
404
405 DRAIN_XML_TREE(N) & initScope = getListenerInitScope(root, mouseEventKey);
406
407 if (!initScope.hasChild("georef")){
408 initScope["georef"]->setText("if (!('georef' in listener.ctx)){ listener.ctx.georef = {}; }");
409 initScope["georef_bbox"]->setText("if (listener.hasAttribute('data-bbox')){ listener.ctx.georef.bbox = new BBox(listener.getAttribute('data-bbox'))};");
410 // consider other element (IMAGE title)
411 initScope["georef_epsg"]->setText("if (listener.hasAttribute('data-epsg')){ listener.ctx.georef.epsg = listener.getAttribute('data-epsg') };");
412 }
413
414}
415*/
416
417} // image::
418
419// DRAIN_ENUM_DICT(image::MouseXML::ElemClass);
420// DRAIN_ENUM_DICT(image::MouseXML::EventClass);
421
422} // drain::
423
424#endif // DRAIN_IMAGE_MOUSEXML_H_
425
426
427
428 //
429
Flagger accepting values of enum type E.
Definition EnumFlagger.h:56
LogSourc e is the means for a function or any program segment to "connect" to a Log.
Definition Log.h:326
Base class for XML "nodes", to be data elements T for drain::Tree<T>
Definition XML.h:69
virtual void setAttribute(const std::string &key, const std::string &value)
Default implementation. Needed for handling units in strings, like "50%" or "640px".
Definition XML.h:556
Definition MouseXML.h:64
ElemClass
Definition MouseXML.h:73
static DRAIN_XML_TREE(N) &getListenerInitScope(DRAIN_XML_TREE(N) &root
Return ("top-level") initialisation script scope for a mouse event. The routine calls subroutine (tha...
static std::string getEventFunctionName(MouseXML::EventClass eventKey)
Modifies short mouse event name like "click" to "onmouseclick", or prefixed form "handleClick".
Definition MouseXML.cpp:73
static DRAIN_XML_TREE(N) &ensureListenerInit(DRAIN_XML_TREE(N) &root
Create internal ("top-level") initialisation script scope for mouse event, if it does not exist.
static DRAIN_XML_TREE(N) &ensureListenerUNUSED(DRAIN_XML_TREE(N) &root
Create internal ("top-level") routine for mouse event, if it does not exist.
static DRAIN_XML_TREE(N) &getListenerScope(DRAIN_XML_TREE(N) &root
Return internal ("top-level") routine for mouse event. The routine calls subroutine (that has a sub s...
static void addVisibilitySwitch(XML &dstElem, XML &listenerElem, const std::string &scope="document", const std::string &mouseEventOn="onmouseenter", const std::string &mouseEventOff="onmouseleave")
Show an element only if the mouse is on another specified element (listenerElem).
Definition MouseXML.cpp:93
Definition Data.h:1473