MIDAS
Loading...
Searching...
No Matches
controls.js
Go to the documentation of this file.
1//
2// controls.js
3// Custom Controls
4//
5// Created by Stefan Ritt on 5/8/15.
6//
7
8/*
9
10 Usage
11 =====
12
13 Dialog boxes
14 ------------
15
16 Dialog boxes consists of normal HTML code defined on the current web page. By
17 using the class "dlgFrame" they are automatically hidden until the
18 dlgShow() function is called. The dialog has a title bar and can be moved
19 around by dragging the title bar. The dlgHide() function should be called
20 to close (hide) the dialog.
21
22 Following button shows the dialog dlgXXX:
23
24 <button type="button" onClick="dlgShow('dlgXXX')">XXX</button>
25
26 Following HTML code defines the dialog dlgXXX:
27
28 <div id="dlgXXX" class="dlgFrame">
29 <div class="dlgTitlebar">Title</div>
30 <div class="dlgPanel">
31 <div>Dialog Contents</div>
32 <button class="dlgButton" onClick="dlgHide('dlgXXX')">Close</button>
33 </div>
34 </div>
35
36
37 Standard dialog boxes
38 ---------------------
39
40 dlgAlert(message)
41 Replacement for alert() function showing a standard notification
42
43 dlgConfirm(message, callback, param)
44 Replacement of confirm() dialog. Shows a message dialog with a 'Cancel'
45 and 'Ok' button. The callback is called with the first parameter either
46 true (if Ok has been clicked) or false (if Cancel has been clicked) and
47 the second parameter a copy of 'param' passed to dlgConfirm().
48
49 dlgQuery(message, value, callback, param)
50 Replacement of prompt() dialog. Shows a dialog box ith a 'Cancel', 'Ok'
51 button and a field to enter a value. 'message' is shown before the
52 input filed and can contain a string like 'Please enter value:'. If
53 'cancel' is pressed, the 'callback' function is called with the first
54 parameter equal 'false'. If 'Ok' is pressed, 'callback' is called with
55 the first parameter being the value of the input field. 'param' is just
56 passed to the callback function as an optional second parameter. So a
57 typical callback function can look like
58
59 function cb(value, param) {
60 if (value !== false)
61 alert('Value is '+value+', param is '+param);
62 }
63
64 where 'param' can also be ommitted.
65
66 dlgMessage(title, message, modal, error, callback, param)
67 Similar to dlgAlert, but with the option to set a custom title which
68 gets a red background if error is true. After the 'Ok' button is pressed,
69 the callback function is called with an optional parameter passed to
70 dlgMessage. If modal equals true, the whole screen is greyed out and all
71 mouse events are captured until the 'Ok' button is pressed.
72
73 dlgWait(time, string)
74 Shows a model dialog with a progress bar for 'time' seconds and 'string'
75 in the first line. After 'time' seconds the dialog closes automatically.
76
77 Sliders
78 -------
79
80 <button name="ctrlHSlider" type="button" class="ctrlHSlider" data-update="xxx()" id="yyy"></button>
81
82 <button name="ctrlVSlider" type="button" class="ctrlVSlider" data-update="xxx()" id="yyy"></button>
83
84 On each change of the slider, the function xxx(value, final) is called with
85 value ranging from 0 to 1. Dragging the slider will cause many updates with
86 final = false, and once the mouse button got released, the function is called
87 once with final = true.
88
89 To set the slider programmatically call
90
91 document.getElementById('yyy').set(0.5);
92
93 Valid range is again 0 to 1.
94
95
96 Progress bars
97 -------------
98
99 <div name="ctrlProgress" style="width:xxxpx;color:yyy;" id="zzz"></div>
100
101 document.getElementById('yyy').set(v); // v = 0...1
102
103
104
105 Icon buttons
106 ------------
107
108 <button name="ctrlButton" data-draw="xxx" type="button" id="yyy" onClick="zzz()"></button>
109
110 function xxx(cvs)
111 {
112 // example for an up-arrow with 36x36 pixels
113 cvs.width = 36;
114 cvs.height = 36;
115 let ctx = cvs.getContext("2d");
116 ctx.fillStyle = "#E0E0E0";
117 ctx.fillRect(0, 0, 36, 36);
118 ctx.beginPath();
119 ctx.moveTo(18, 7);
120 ctx.lineTo(31, 27);
121 ctx.lineTo(5, 27);
122 ctx.lineTo(18, 7);
123 ctx.closePath();
124 ctx.fillStyle = "#808080";
125 ctx.fill();
126 }
127
128*/
129
130// default styles for dialog boxes
131let controls_css = `<style>
132 .dlgFrame {
133 font-family: verdana,tahoma,sans-serif;
134 border: 1px solid black;
135 box-shadow: 6px 6px 10px 4px rgba(0,0,0,0.2);
136 border-radius: 6px;
137 position: absolute;
138 top: 0;
139 left: 0;
140 z-index: 10;
141 display: none; /* pre-hidden, to be changed to flex */
142 flex-direction: column; /* needed for dlgPanel to follow our size */
143 min-width: max-content;
144 min-height: max-content;
145 }
146 .dlgTitlebar {
147 user-select: none;
148 text-align: center;
149 background-color: #C0C0C0;
150 border-top-left-radius: 6px;
151 border-top-right-radius: 6px;
152 font-size: 10pt;
153 flex: 0 0 16px;
154 height: 16px;
155 padding: 2px;
156 padding-left: 30px;
157 padding-right: 30px;
158 /*overflow: hidden;
159 white-space: nowrap;*/
160 }
161 .dlgTitlebar:hover {
162 cursor: pointer;
163 }
164 .dlgButton {
165 font-size: 10pt;
166 background-color: #F8F8F8;
167 border: 1px solid #808080;
168 border-radius: 6px;
169 padding: 2px 4px;
170 margin: 3px;
171 }
172 .dlgButtonDefault {
173 font-size: 10pt;
174 background-color: #4187F7;
175 color: #FFFFFF;
176 border: 1px solid #808080;
177 border-radius: 6px;
178 padding: 2px 4px;
179 margin: 3px;
180 }
181 .dlgButton:hover {
182 background-color: #F0F0F0;
183 }
184 .dlgPanel {
185 background-color: #F0F0F0;
186 text-align: center;
187 padding: 4px;
188 border-bottom-left-radius: 6px;
189 border-bottom-right-radius: 6px;
190 flex: 1 1 auto; /* follow dlgFrame in size */
191 min-width: max-content;
192 min-height: max-content;
193 }
194 .dlgBlackout {
195 background: rgba(0,0,0,.5);
196 position: fixed;
197 top: 0;
198 left: 0;
199 bottom: 0;
200 right: 0;
201 z-index: 20;
202 }
203 .ctrlHSlider {
204 width: 200px;
205 height: 30px;
206 border-radius: 5px;
207 padding: 0;
208 }
209 .ctrlVSlider {
210 width: 20px;
211 height: 200px;
212 border-radius: 5px;
213 padding: 0;
214 }
215 .ctrlProgress {
216 border: 1px solid #A0A0A0;
217 border-radius: 5px;
218 width: 500px;
219 height: 5px;
220 background-color: #E0E0E0;
221 margin-left: 6px;
222 margin-top: 5px;
223 }
224 .ctrlProgressInd {
225 border-radius: 5px;
226 width: 0;
227 height: 5px;
228 background-color: #419bf9;
229 margin: 0;
230 }
231 </style>`;
232
233(function (window) { // anonymous global function
234 window.addEventListener("load", ctlInit, false);
235})(window);
236
237function ctlInit() {
238 let CTL = new Controls();
239 CTL.init();
240}
241
242function Controls() // constructor
243{
244}
245
246Controls.prototype.init = function () // scan DOM
247{
248 // add special style
249 document.head.insertAdjacentHTML("beforeend", controls_css);
250
251 // scan DOM for controls
252 this.ctrlButton = document.getElementsByName("ctrlButton");
253 this.ctrlVSlider = document.getElementsByName("ctrlVSlider");
254 this.ctrlHSlider = document.getElementsByName("ctrlHSlider");
255 this.ctrlProgress = document.getElementsByName("ctrlProgress");
256
257 // ctrlButton
258 for (let i = 0; i < this.ctrlButton.length; i++) {
259 let cvs = document.createElement("canvas");
260 this.ctrlButton[i].appendChild(cvs);
261
262 if (this.ctrlButton[i].dataset.draw !== undefined)
263 eval(this.ctrlButton[i].dataset.draw + "(cvs)");
264 }
265
266 // ctrlVSlider
267 for (let i = 0; i < this.ctrlVSlider.length; i++) {
268 let cvs = document.createElement("canvas");
269 let sl = this.ctrlVSlider[i];
270
271 if (sl.style.height === "") {
272 dlgAlert("ctrlVSlider: undefined height");
273 return;
274 }
275 if (sl.dataset.minValue === undefined)
276 sl.minValue = 0;
277 else
278 sl.minValue = Number.parseFloat(sl.dataset.minValue);
279 if (sl.dataset.maxValue === undefined)
280 sl.maxValue = 1;
281 else
282 sl.maxValue = Number.parseFloat(sl.dataset.maxValue);
283
284 cvs.width = 26;
285 cvs.height = Number.parseInt(sl.style.height)-4;
286 sl.appendChild(cvs);
287 sl.canvas = cvs;
288
289 sl.value = (sl.minValue + sl.maxValue) / 2;
290 sl.position = 0.5; // slider position 0...1
291 sl.addEventListener("click", this.ctrlVSliderHandler.bind(this));
292 sl.addEventListener("contextmenu", this.ctrlVSliderHandler.bind(this));
293 sl.addEventListener("mousemove", this.ctrlVSliderHandler.bind(this));
294 sl.addEventListener("touchmove", this.ctrlVSliderHandler.bind(this));
295 sl.draw = this.ctrlVSliderDraw;
296 sl.draw(sl);
297 sl.set = this.ctrlVSliderSet;
298 }
299
300 // ctrlHSlider
301 for (let i = 0; i < this.ctrlHSlider.length; i++) {
302 let cvs = document.createElement("canvas");
303 let sl = this.ctrlHSlider[i];
304
305 if (sl.style.width === "") {
306 dlgAlert("ctrlHSlider: undefined width");
307 return;
308 }
309 if (sl.dataset.minValue === undefined)
310 sl.minValue = 0;
311 else
312 sl.minValue = Number.parseFloat(sl.dataset.minValue);
313 if (sl.dataset.maxValue === undefined)
314 sl.maxValue = 1;
315 else
316 sl.maxValue = Number.parseFloat(sl.dataset.maxValue);
317
318 cvs.width = Number.parseInt(sl.style.width)-4;
319 cvs.height = 26;
320
321 sl.appendChild(cvs);
322 sl.canvas = cvs;
323
324 sl.value = (sl.minValue + sl.maxValue) / 2;
325 sl.position = 0.5; // slider position 0...1
326 sl.addEventListener("click", this.ctrlHSliderHandler.bind(this));
327 sl.addEventListener("contextmenu", this.ctrlHSliderHandler.bind(this));
328 sl.addEventListener("mousemove", this.ctrlHSliderHandler.bind(this));
329 sl.addEventListener("touchmove", this.ctrlHSliderHandler.bind(this));
330 sl.addEventListener("mouseup", this.ctrlHSliderHandler.bind(this));
331 sl.draw = this.ctrlHSliderDraw;
332 sl.draw(sl);
333 sl.set = this.ctrlHSliderSet;
334 }
335
336 // ctrlProgress
337 for (let i = 0; i < this.ctrlProgress.length; i++) {
338 let p = this.ctrlProgress[i];
339 p.className = "ctrlProgress";
340 let ind = document.createElement("div");
341 ind.className = "ctrlProgressInd";
342 ind.style.height = p.style.height;
343 ind.style.backgroundColor = p.style.color;
344 p.appendChild(ind);
345 p.set = this.ctrlProgressSet;
346 }
347
348};
349
350Controls.prototype.ctrlVSliderDraw = function (b) {
351 if (b === undefined)
352 b = this;
353 let w = b.canvas.width;
354 let h = b.canvas.height;
355 b.sliderOfs = 20;
356
357 let ctx = b.canvas.getContext("2d");
358 ctx.fillStyle = "#E0E0E0";
359 ctx.fillRect(0, 0, b.canvas.width, b.canvas.height);
360
361 let knob = b.sliderOfs + (1 - b.position) * (h - 2 * b.sliderOfs);
362
363 ctx.strokeStyle = "#A0A0A0";
364 ctx.lineWidth = 3;
365 ctx.beginPath();
366 ctx.moveTo(w / 2, b.sliderOfs);
367 ctx.lineTo(w / 2, knob);
368 ctx.stroke();
369
370 ctx.strokeStyle = "#00A0FF";
371 ctx.beginPath();
372 ctx.moveTo(w / 2, knob);
373 ctx.lineTo(w / 2, h - b.sliderOfs);
374 ctx.stroke();
375
376 ctx.lineWidth = 2;
377 ctx.strokeStyle = "#A0A0A0";
378 for (let i=1 ; i<8 ; i++) {
379 ctx.beginPath();
380 let x = b.sliderOfs + i/8 * (h - 2 * b.sliderOfs);
381 ctx.moveTo(w / 2 + 5, x);
382 ctx.lineTo(w / 2 + 7, x);
383 ctx.stroke();
384 }
385
386 ctx.fillStyle = "#E0E0E0";
387 ctx.strokeStyle = "#C0C0C0";
388 ctx.beginPath();
389 ctx.arc(w / 2, knob, 10, 0, 2 * Math.PI);
390 ctx.stroke();
391 ctx.fill();
392
393 if (b.dataset?.scale === "1") {
394 ctx.font = "14px sans-serif";
395 ctx.strokeStyle = "#808080";
396 ctx.fillStyle = "#808080";
397 ctx.textAlign = "center";
398 ctx.textBaseline = "middle";
399 ctx.fillText(b.minValue, w / 2, h - 10);
400 ctx.fillText(b.maxValue, w / 2, 10);
401 }
402
403};
404
405Controls.prototype.ctrlVSliderSet = function (value) {
406 if (value < this.minValue)
407 value = this.minValue;
408 if (value > this.maxValue)
409 value = this.maxValue;
410 this.value = value;
411 this.position = (value - this.minValue) / (this.maxValue - this.minValue);
412 this.draw();
413};
414
415Controls.prototype.ctrlVSliderHandler = function (e) {
416 e.preventDefault();
417 let y = undefined;
418 let b = e.currentTarget;
419
420 if (b.canvas === undefined) // we can get events from parent node
421 return;
422
423 if ((e.buttons === 1 && e.type === "mousemove") || e.type === "click")
424 y = e.offsetY;
425 if (e.type === "touchmove")
426 y = e.changedTouches[e.changedTouches.length - 1].clientY - b.getBoundingClientRect().top;
427
428 if (e.type === "contextmenu") {
429 b.position = 0.5;
430 b.value = (b.minValue + b.maxValue) / 2;
431 b.contextMenu = true;
432 this.ctrlVSliderDraw(b);
433 let f = b.dataset.update;
434 if (f.indexOf("("))
435 f = f.substr(0, f.indexOf("("));
436 window[f](b.value, true);
437 }
438
439 if (y !== undefined) {
440 b.contextMenu = false;
441 b.position = 1 - (y - b.sliderOfs) / (b.clientHeight - 2 * b.sliderOfs);
442 if (b.position < 0)
443 b.position = 0;
444 if (b.position > 1)
445 b.position = 1;
446 b.value = b.minValue + b.position * (b.maxValue - b.minValue);
447 this.ctrlVSliderDraw(b);
448 let f = b.dataset.update;
449 if (f.indexOf("("))
450 f = f.substr(0, f.indexOf("("));
451 window[f](b.value, false);
452 }
453
454 if (e.type === "mouseup" && !b.contextMenu) {
455 let f = b.dataset.update;
456 if (f.indexOf("("))
457 f = f.substr(0, f.indexOf("("));
458 window[f](b.value, true);
459 }
460};
461
462Controls.prototype.ctrlHSliderDraw = function (b) {
463 if (b === undefined)
464 b = this;
465 let w = b.canvas.width;
466 let h = b.canvas.height;
467
468 let ctx = b.canvas.getContext("2d");
469 ctx.fillStyle = "#E0E0E0";
470 ctx.fillRect(0, 0, b.canvas.width, b.canvas.height);
471
472 b.sliderOfs = 20;
473 if (b.dataset?.scale === "1") {
474 ctx.font = "14px sans-serif";
475 let o = ctx.measureText(String(b.minValue)).width + 10;
476 if (o > b.sliderOfs)
477 b.sliderOfs = o;
478 o = ctx.measureText(String(b.maxValue)).width + 10;
479 if (o > b.sliderOfs)
480 b.sliderOfs = o;
481 }
482
483 let knob = b.sliderOfs + (b.position) * (w - 2 * b.sliderOfs);
484
485 ctx.strokeStyle = "#A0A0A0";
486 ctx.lineWidth = 3;
487 ctx.beginPath();
488 ctx.moveTo(w - b.sliderOfs, h / 2);
489 ctx.lineTo(knob, h / 2);
490 ctx.stroke();
491
492 ctx.strokeStyle = "#00A0FF";
493 ctx.beginPath();
494 ctx.moveTo(knob, h / 2);
495 ctx.lineTo(b.sliderOfs, h / 2);
496 ctx.stroke();
497
498 ctx.lineWidth = 2;
499 ctx.strokeStyle = "#A0A0A0";
500 for (let i=1 ; i<8 ; i++) {
501 ctx.beginPath();
502 let x = b.sliderOfs + i/8 * (w - 2 * b.sliderOfs);
503 ctx.moveTo(x, h / 2 + 5);
504 ctx.lineTo(x, h / 2 + 7);
505 ctx.stroke();
506 }
507
508 ctx.fillStyle = "#E0E0E0";
509 ctx.strokeStyle = "#C0C0C0";
510 ctx.beginPath();
511 ctx.arc(knob, h / 2, 10, 0, 2 * Math.PI);
512 ctx.stroke();
513 ctx.fill();
514
515 if (b.dataset?.scale === "1") {
516 ctx.font = "14px sans-serif";
517 ctx.strokeStyle = "#808080";
518 ctx.fillStyle = "#808080";
519 ctx.textAlign = "center";
520 ctx.textBaseline = "middle";
521 ctx.fillText(b.minValue, b.sliderOfs / 2, h / 2);
522 ctx.fillText(b.maxValue, w - b.sliderOfs / 2, h / 2);
523 }
524};
525
526Controls.prototype.ctrlHSliderSet = function (value) {
527 if (value < this.minValue)
528 value = this.minValue;
529 if (value > this.maxValue)
530 value = this.maxValue;
531 this.value = value;
532 this.position = (value - this.minValue) / (this.maxValue - this.minValue);
533 this.draw();
534};
535
536Controls.prototype.ctrlHSliderHandler = function (e) {
537 e.preventDefault();
538 let x = undefined;
539 let b = e.currentTarget;
540
541 if (b.canvas === undefined) // we can get events from parent node
542 return;
543
544 if ((e.buttons === 1 && e.type === "mousemove") || e.type === "click")
545 x = e.offsetX;
546 if (e.type === "touchmove")
547 x = e.changedTouches[e.changedTouches.length - 1].clientX - b.getBoundingClientRect().left;
548
549 if (e.type === "contextmenu") {
550 b.position = 0.5;
551 b.value = (b.minValue + b.maxValue) / 2;
552 b.contextMenu = true;
553 this.ctrlHSliderDraw(b);
554 let f = b.dataset.update;
555 if (f.indexOf("("))
556 f = f.substr(0, f.indexOf("("));
557 window[f](b.value, true);
558 }
559
560 if (x !== undefined) {
561 b.contextMenu = false;
562 b.position = (x - b.sliderOfs) / (b.clientWidth - 2 * b.sliderOfs);
563 if (b.position < 0)
564 b.position = 0;
565 if (b.position > 1)
566 b.position = 1;
567 b.value = b.minValue + b.position * (b.maxValue - b.minValue);
568 this.ctrlHSliderDraw(b);
569 let f = b.dataset.update;
570 if (f.indexOf("("))
571 f = f.substr(0, f.indexOf("("));
572 window[f](b.value, false);
573 }
574
575 if (e.type === "mouseup" && !b.contextMenu) {
576 let f = b.dataset.update;
577 if (f.indexOf("("))
578 f = f.substr(0, f.indexOf("("));
579 window[f](Number(b.value), true);
580 }
581};
582
583Controls.prototype.ctrlProgressSet = function (value) {
584 if (value < 0)
585 value = 0;
586 if (value > 1)
587 value = 1;
588 this.firstChild.style.width = Math.round(parseInt(this.style.width) * value) + "px";
589};
590
591//-------------------------------------------------------------------------------------------------
592var dlgLoadedDialogs = [];
593
594function dlgLoad(url) {
595 // check if dialog already laoded
596 if (dlgLoadedDialogs.includes(url))
597 return;
598
599 dlgLoadedDialogs.push(url);
600
601 // load dialog via AJAX
602 return new Promise(function (resolve, reject) {
603 let xhr = new XMLHttpRequest();
604 xhr.onreadystatechange = function () {
605 if (xhr.readyState === 4) {
606 if (xhr.status === 200) {
607 let d = document.createElement("div");
608 d.innerHTML = xhr.responseText;
609 document.body.appendChild(d);
610 resolve(xhr.responseText);
611 } else {
612 dlgAlert("network error: see javascript console: dlgLoad() cannot load " + url + ", HTTP status: " + xhr.status);
613 reject(xhr.responseURL);
614 }
615 }
616 };
617
618 xhr.open("GET", url, true);
619 xhr.setRequestHeader('Content-type', 'text/html');
620 xhr.send();
621 });
622}
623
624function drawCloseButton(c, mark) {
625 if (!c.getContext)
626 return;
627 let ctx = c.getContext("2d");
628 ctx.clearRect(0, 0, c.width, c.height);
629 ctx.lineWidth = 0.5;
630 ctx.beginPath();
631 ctx.arc(c.width/2, c.height/2, c.width/2-1, 0, 2*Math.PI);
632 ctx.fillStyle = '#FD5E59';
633 ctx.fill();
634 ctx.strokeStyle = '#DF2020';
635 ctx.stroke();
636 if (mark) {
637 ctx.strokeStyle = '#000000';
638 ctx.beginPath();
639 ctx.lineWidth = 1;
640 ctx.moveTo(c.width/2-3, c.height/2-3);
641 ctx.lineTo(c.width/2+3, c.height/2+3);
642 ctx.moveTo(c.width/2+3, c.height/2-3);
643 ctx.lineTo(c.width/2-3, c.height/2+3);
644 ctx.stroke();
645 }
646}
647
648function dlgCenter(dlg) {
649 let d;
650 if (typeof dlg === "string")
651 d = document.getElementById(dlg);
652 else
653 d = dlg;
654
655 if (d === null) {
656 dlgAlert("Dialog '" + dlg + "' does not exist");
657 return;
658 }
659
660 let doc = document.documentElement;
661 d.style.left = Math.round(doc.clientWidth / 2 + doc.scrollLeft - d.offsetWidth / 2) + "px";
662 if (doc.clientHeight / 2 - d.offsetHeight / 2 < 0)
663 d.style.top = "0px";
664 else
665 d.style.top = Math.round(doc.clientHeight / 2 + doc.scrollTop - d.offsetHeight / 2) + "px";
666
667 // allow for scrolling of very high dialog boxes
668 if (d.offsetHeight > document.documentElement.clientHeight)
669 d.style.position = "absolute";
670}
671
672function checkResize(dlg, e) {
673 const rect = dlg.getBoundingClientRect();
674 const margin = 10;
675
676 let c = '';
677 dlg.resizeLeft = false;
678 dlg.resizeRight = false;
679 dlg.resizeTop = false;
680 dlg.resizeBottom = false;
681
682 let mx = e.clientX;
683 let my = e.clientY;
684
685 if (mx >= rect.left && mx <= rect.right && my >= rect.top && my <= rect.bottom) {
686 if (mx < rect.left + margin) { // left side
687 dlg.resizeLeft = true;
688 if (my < rect.top + margin) {
689 dlg.resizeTop = true;
690 c = 'nw-resize';
691 } else if (my > rect.bottom - margin) {
692 dlg.resizeBottom = true;
693 c = 'sw-resize';
694 } else
695 c = 'w-resize';
696 } else if (mx > rect.right - margin) { // right side
697 dlg.resizeRight = true;
698 if (my < rect.top + margin) {
699 dlg.resizeTop = true;
700 c = 'ne-resize';
701 } else if (my > rect.bottom - margin) {
702 dlg.resizeBottom = true;
703 c = 'se-resize';
704 } else
705 c = 'e-resize';
706 } else if (my > rect.bottom - margin) { // bottom side
707 dlg.resizeBottom = true;
708 c = 's-resize';
709 }
710 }
711
712 // safari has limited set of cursors
713 if (navigator.vendor === 'Apple Computer, Inc.' && c !== '')
714 c = 'pointer';
715
716 if (e.target.className === 'dlgTitlebar') {
717 if (c === '') {
718 if (dlg.dragActive)
719 e.target.style.cursor = 'move';
720 else
721 e.target.style.cursor = 'pointer';
722 } else
723 e.target.style.cursor = c;
724 } else
725 dlg.style.cursor = c;
726
727 if (c !== '' && c !== 'pointer') {
728 e.stopPropagation();
729 }
730}
731
732function dlgShow(dlg, modal, param) {
733 let d;
734 if (typeof dlg === "string")
735 d = document.getElementById(dlg);
736 else
737 d = dlg;
738
739 if (d === null) {
740 dlgAlert("Dialog '" + dlg + "' does not exist");
741 return;
742 }
743
744 if (d.childNodes === undefined) {
745 dlgAlert("Dialog '" + dlg + "' has no title bar");
746 return;
747 }
748
749 // add optional parameter to dialog
750 d.param = param;
751
752 // put "close" icon into title bar
753 let t;
754 if (d.childNodes[0].className === "dlgTitlebar")
755 t = d.childNodes[0];
756 if (d.childNodes[1] && d.childNodes[1].className === "dlgTitlebar")
757 t = d.childNodes[1];
758 if (t !== undefined) {
759 let ttext = t.innerHTML;
760 if (ttext.search('dlgHide') === -1) {
761 t.innerHTML = "<div style=\"position: absolute;left: 6px;top: 3px;\" " +
762 "onclick=\"dlgClose(this);\">" +
763 "<canvas id=\"cvsClose\" width=\"14px\" height=\"14px\"></canvas>" +
764 "</div>" + ttext;
765 }
766 d.canvas = t.childNodes[0].childNodes[0];
767 drawCloseButton(d.canvas, false);
768 }
769
770 d.dragActive = false;
771 d.resizeActive = false;
772 d.dragMouseX = undefined;
773 d.dragMouseY = undefined;
774 d.dragStartLeft = undefined;
775 d.dragStartTop = undefined;
776 d.dragStartRight = undefined;
777 d.dragStartBottom = undefined;
778 d.modal = (modal === true);
779
780 d.style.display = "flex";
781 dlgCenter(d);
782
783 // put dialog on top of all other dialogs
784 let dlgs = document.getElementsByClassName("dlgFrame");
785 for (let i = 0; i < dlgs.length; i++)
786 dlgs[i].style.zIndex = "30"; // on top of blackout (20)
787 d.style.zIndex = "31";
788
789 // move dialog right-down if on top of previous one
790 if (dlgs.length > 1) {
791 d.style.left = (parseInt(dlgs[dlgs.length - 2].style.left) + 30).toString() + "px";
792 d.style.top = (parseInt(dlgs[dlgs.length - 2].style.top) + 30).toString() + "px";
793 }
794
795 // enable scrolling if dialog box goes beyond screen
796 d.oldScroll = window.getComputedStyle(document.body).overflow;
797 document.body.style.overflow = "scroll";
798
799 if (d.modal) {
800 let b = document.getElementById("dlgBlackout");
801 if (b === undefined || b === null) {
802 b = document.createElement("div");
803 b.id = "dlgBlackout";
804 b.className = "dlgBlackout";
805 document.body.appendChild(b);
806 }
807
808 b.style.display = "flex";
809 d.dlgBlackout = b;
810 }
811
812 d.dlgMouseDown = function (e) {
813 if (!d.modal && !d.contains(e.target) && !this.dragActive && !this.resizeActive)
814 return;
815 if (d.style.display === "none")
816 return;
817 if (e.target.tagName === "INPUT" || e.target.tagName === "SELECT")
818 return;
819
820 // ignore right mouse clicks
821 if (e.button !== 0)
822 return;
823
824 // start dragging
825 e.preventDefault();
826
827 checkResize(d, e);
828 if (d.resizeLeft || d.resizeRight ||
829 d.resizeTop || d.resizeBottom)
830 d.resizeActive = true;
831 else if (e.target.className === "dlgTitlebar")
832 this.dragActive = true;
833
834 const rect = d.getBoundingClientRect();
835
836 this.dragMouseX = e.clientX;
837 this.dragMouseY = e.clientY;
838 this.dragStartLeft = rect.left;
839 this.dragStartTop = rect.top;
840 this.dragStartRight = rect.right;
841 this.dragStartBottom = rect.bottom;
842
843 if (d.modal && e.target !== d && !d.contains(e.target)) {
844 // catch all mouse events outside the dialog
845 e.preventDefault();
846 } else {
847 if (e.target === this || d.contains(e.target)) {
848 let dlgs = document.getElementsByClassName("dlgFrame");
849 for (let i = 0; i < dlgs.length; i++)
850 dlgs[i].style.zIndex = "30";
851 d.style.zIndex = "31";
852 }
853 }
854 };
855
856 d.dlgMouseMove = function (e) {
857 if (!d.contains(e.target) && !this.dragActive && !this.resizeActive)
858 return;
859 if (d.style.display === "none")
860 return;
861
862 // draw close button with "x" if mouse cursor is inside
863 drawCloseButton(d.canvas, e.target === d.canvas);
864
865 // show resize cursors
866 if (!this.resizeActive)
867 checkResize(d, e);
868
869 // handle dragging
870 if (this.dragActive) {
871 e.preventDefault();
872 e.stopPropagation();
873 let x = e.clientX;
874 let y = e.clientY;
875 // stop dragging if leaving window
876 if (x < 0 || y < 0 ||
877 x > document.documentElement.clientWidth ||
878 y > document.documentElement.clientHeight ||
879 (this.dragStartTop + (y - this.dragMouseY)) < 0) {
880 this.dragActive = false;
881 } else {
882 this.style.left = (this.dragStartLeft + (x - this.dragMouseX)) + "px";
883 this.style.top = (this.dragStartTop + (y - this.dragMouseY)) + "px";
884 }
885 }
886
887 // handle resizing
888 if (this.resizeActive) {
889 e.preventDefault();
890 e.stopPropagation();
891 let x = e.clientX;
892 let y = e.clientY;
893 if (x < 0 || y < 0 ||
894 x > document.documentElement.clientWidth ||
895 y > document.documentElement.clientHeight) {
896 this.resizeActive = false;
897 } else {
898 if (this.resizeLeft) {
899 this.style.left = (this.dragStartLeft + (x - this.dragMouseX)) + "px";
900 this.style.width = (this.dragStartRight - this.dragStartLeft - (x - this.dragMouseX)) + "px";
901 }
902 if (this.resizeTop) {
903 this.style.top = (this.dragStartTop + (y - this.dragMouseY)) + "px";
904 this.style.height = (this.dragStartBottom - this.dragStartTop - (y - this.dragMouseY)) + "px";
905 }
906 if (this.resizeRight) {
907 this.style.width = (this.dragStartRight - this.dragStartLeft + (x - this.dragMouseX)) + "px";
908 }
909 if (this.resizeBottom) {
910 this.style.height = (this.dragStartBottom - this.dragStartTop + (y - this.dragMouseY)) + "px";
911 }
912 }
913 }
914 };
915
916 d.dlgMouseUp = function (e) {
917 if (!d.contains(e.target) && !this.dragActive && !this.resizeActive)
918 return;
919 this.dragActive = false;
920 this.resizeActive = false;
921 };
922
923 d.dlgTouchStart = function (e) {
924 if (d.style.display === "none")
925 return;
926
927 if ((e.target === this || e.target.parentNode === this) &&
928 e.target.className === "dlgTitlebar") {
929 e.preventDefault();
930 this.dragMouseX = e.targetTouches[0].clientX;
931 this.dragMouseY = e.targetTouches[0].clientY;
932 this.dragStartLeft = parseInt(this.style.left);
933 this.dragStartTop = parseInt(this.style.top);
934 }
935
936 if (d.modal && e.target !== d && !d.contains(e.target)) {
937 // catch all mouse events
938 e.preventDefault();
939 } else {
940 if (e.target === this || d.contains(e.target)) {
941 let dlgs = document.getElementsByClassName("dlgFrame");
942 for (let i = 0; i < dlgs.length; i++)
943 dlgs[i].style.zIndex = "30";
944 d.style.zIndex = "31";
945 }
946 }
947 };
948
949 d.dlgTouchMove = function (e) {
950 if (!d.contains(e.target) && !this.dragActive && !this.resizeActive)
951 return;
952 if (d.style.display === "none")
953 return;
954
955 if (this.dragMouseX > 0 && this.dragMouseY > 0) {
956 e.preventDefault();
957 let x = e.changedTouches[e.changedTouches.length - 1].clientX;
958 let y = e.changedTouches[e.changedTouches.length - 1].clientY;
959 this.style.left = (this.dragStartLeft + (x - this.dragMouseX)) + "px";
960 this.style.top = (this.dragStartTop + (y - this.dragMouseY)) + "px";
961 }
962 };
963
964 d.dlgTouchEnd = function (e) {
965 if (!d.contains(e.target) && !this.dragActive && !this.resizeActive)
966 return;
967 if (d.style.display === "none")
968 return;
969
970 if (this.dragMouseX > 0 && this.dragMouseY > 0) {
971 e.preventDefault();
972 this.dragMouseX = 0;
973 this.dragMouseY = 0;
974 }
975 };
976
977 d.dlgTouchCancel = function (e) {
978 if (d.style.display === "none")
979 return;
980
981 if (this.dragMouseX > 0 && this.dragMouseY > 0) {
982 e.preventDefault();
983 this.dragMouseX = 0;
984 this.dragMouseY = 0;
985 }
986 };
987
988 d.dlgKeyDown = function (e) {
989 if (d.style.display === "none")
990 return;
991
992 if (e.key === 'Escape') {
993 e.preventDefault();
994 dlgClose(d.childNodes[1].childNodes[0]);
995 }
996 }
997 window.addEventListener("keydown", d.dlgKeyDown.bind(d), true);
998
999 window.addEventListener("mousedown", d.dlgMouseDown.bind(d), true);
1000 window.addEventListener("mousemove", d.dlgMouseMove.bind(d), true);
1001 window.addEventListener("mouseup", d.dlgMouseUp.bind(d), true);
1002 window.addEventListener("touchstart", d.dlgTouchStart.bind(d), true);
1003 window.addEventListener("touchmove", d.dlgTouchMove.bind(d), true);
1004 window.addEventListener("touchend", d.dlgTouchEnd.bind(d), true);
1005 window.addEventListener("touchcancel", d.dlgTouchCancel.bind(d), true);
1006}
1007
1008function dlgMove(d, x, y) {
1009 d.style.left = x + "px";
1010 d.style.top = y + "px";
1011}
1012
1013function dlgClose(div) {
1014 let dlg = div;
1015 while (!dlg.className.includes("dlgFrame"))
1016 dlg = dlg.parentElement;
1017 if (dlg.shouldDestroy)
1018 dlgMessageDestroy(dlg);
1019 else if (dlg.id !== undefined)
1020 dlgHide(dlg.id);
1021}
1022""
1023function dlgHide(dlg) {
1024 if (typeof dlg === "string")
1025 dlg = document.getElementById(dlg);
1026 else if (dlg.type === "button") {
1027 do {
1028 dlg = dlg.parentElement;
1029 } while (dlg.className !== 'dlgFrame');
1030 }
1031
1032 if (dlg.modal) {
1033 // only remove blackout if we are the only modal dialog left
1034 let dlgs = document.getElementsByClassName("dlgFrame");
1035 let n=0;
1036 for (let i = 0; i < dlgs.length; i++)
1037 if (dlgs[i].style.display !== "none" && dlgs[i].modal)
1038 n++;
1039 if (n === 1) {
1040 let d = document.getElementById("dlgBlackout");
1041 if (d !== undefined && d !== null)
1042 d.style.display = "none";
1043 }
1044 }
1045 dlg.style.display = "none";
1046 if (dlg.oldScroll !== "")
1047 document.body.style.overflow = dlg.oldScroll;
1048}
1049
1050function dlgMessageDestroy(b) {
1051 let dlg = b;
1052 while (!dlg.className.includes("dlgFrame"))
1053 dlg = dlg.parentElement;
1054 if (dlg.modal) {
1055 // only remove blackout if we are the only modal dialog left
1056 let dlgs = document.getElementsByClassName("dlgFrame");
1057 let n=0;
1058 for (let i = 0; i < dlgs.length; i++)
1059 if (dlgs[i].style.display !== "none" && dlgs[i].modal)
1060 n++;
1061 if (n === 1) {
1062 let d = document.getElementById("dlgBlackout");
1063 if (d !== undefined && d !== null)
1064 d.style.display = "none";
1065 }
1066 }
1067 // dialog is not really removed from memory, event listerner is still active and
1068 // grabs mousdown events, so mark its display "none" to prevent eating mouse events
1069 // above in dlgMouseDown routine
1070 dlg.style.display = "none";
1071 document.body.removeChild(dlg);
1072}
1073
1074function dlgMessage(title, string, modal, error, callback, param) {
1075 let d = document.createElement("div");
1076 d.className = "dlgFrame";
1077 d.style.zIndex = modal ? "31" : "30";
1078 d.callback = callback;
1079 d.callbackParam = param;
1080 d.shouldDestroy = true;
1081
1082 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">" + title + "</div>" +
1083 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
1084 "<div id=\"dlgMessageString\">" + string + "</div>" +
1085 "<br /><br /><button class=\"dlgButton\" id=\"dlgMessageButton\" type=\"button\" " +
1086 " onClick=\"let d=this.parentElement.parentElement;if(d.callback!==undefined)d.callback(d.callbackParam);dlgMessageDestroy(this)\">Close</button>" +
1087 "</div>";
1088 document.body.appendChild(d);
1089
1090 if (error === true) {
1091 let t = document.getElementById("dlgMessageTitle");
1092 t.style.backgroundColor = "#9E2A2A";
1093 t.style.color = "white";
1094 }
1095
1096 dlgShow(d, modal);
1097 return d;
1098}
1099
1100function dlgAlert(s, callback) {
1101 dlgMessage('Message', s, true, false, callback);
1102}
1103
1104function dlgConfirm(string, confirmCallback, param) {
1105 let d = document.createElement("div");
1106 d.className = "dlgFrame";
1107 d.style.zIndex = "31";
1108 d.callback = confirmCallback;
1109 d.callbackParam = param;
1110 d.shouldDestroy = true;
1111
1112 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Please confirm</div>" +
1113 "<div class=\"dlgPanel\" style=\"padding: 30px;\">" +
1114 "<div id=\"dlgMessageString\">" + string + "</div>" +
1115 "<br /><br />" +
1116 "<button class=\"dlgButtonDefault\" id=\"dlgMessageButtonOk\" type=\"button\" " +
1117 " onClick=\"let d=this.parentElement.parentElement;d.callback(true,d.callbackParam);dlgMessageDestroy(this);\">OK</button>" +
1118 "<button class=\"dlgButton\" id=\"dlgMessageButtonCancel\" type=\"button\" " +
1119 " onClick=\"let d=this.parentElement.parentElement;d.callback(false,d.callbackParam);dlgMessageDestroy(this);\">Cancel</button>" +
1120 "</div>";
1121
1122 document.body.appendChild(d);
1123
1124 dlgShow(d, true);
1125 document.getElementById('dlgMessageButtonOk').focus();
1126 return d;
1127}
1128
1129// Populate a modal with a general html
1130function dlgGeneral(p) {
1131 /* general dialog containing p.html code and other optional parameters
1132 p.iddiv - the name of the dialog div (optional)
1133 p.width/p.height - initial width/height of dialog (optional)
1134 p.minWidth/p.minHeight - minimal width/height of dialog (optional)
1135 p.x/p.y - initial position of dialog (optional)
1136 p.title - title of the dialog (optional)
1137 */
1138
1139 // First make sure you removed existing iddiv
1140 if (document.getElementById(p.iddiv)) document.getElementById(p.iddiv).remove();
1141 let d = document.createElement("div");
1142 d.className = "dlgFrame";
1143 if (p.iddiv) d.id = p.iddiv;
1144 d.style.zIndex = "30";
1145 d.style.overflow = "hidden";
1146 d.style.width = p.width ? p.width + "px" : "400px";
1147 d.style.height = p.height ? p.height + "px" : "200px";
1148 d.style.minWidth = p.minWidth ? p.minWidth + "px" : d.style.width;
1149 d.style.minHeight = p.minHeight ? p.minHeight + "px" : d.style.height;
1150 d.style.maxHeight = "90vh";
1151 d.style.maxWidth = "60vw";
1152 d.shouldDestroy = true;
1153
1154 const dlgTitle = document.createElement("div");
1155 dlgTitle.className = "dlgTitlebar";
1156 //dlgTitle.id = "dlgMessageTitle";
1157 dlgTitle.innerText = p.title || (p.iddiv ? p.iddiv + " dialog" : "General dialog");
1158 d.appendChild(dlgTitle);
1159
1160 const dlgPanel = document.createElement("div");
1161 dlgPanel.className = "dlgPanel";
1162 //dlgPanel.id = "dlgPanel";
1163 d.appendChild(dlgPanel);
1164
1165 const content = document.createElement("div");
1166 //content.id = "dlgHTML";
1167 content.style.overflow = "auto";
1168 content.innerHTML = p.html;
1169 dlgPanel.appendChild(content);
1170
1171 document.body.appendChild(d);
1172 dlgShow(d);
1173
1174 if (p.x !== undefined && p.y !== undefined)
1175 dlgMove(d, p.x, p.y);
1176
1177 // Initial size based on content
1178 d.style.height = (p.height !== undefined) ? p.height : (content.scrollHeight + dlgTitle.offsetHeight + 5 ) + "px";
1179 d.style.width = (p.width !== undefined) ? p.width : (content.scrollWidth + 5 ) + "px";
1180
1181 // Function to handle resize events
1182 function handleResize() {
1183 content.style.height = (d.offsetHeight - dlgTitle.offsetHeight - 5 ) + "px";
1184 }
1185
1186 // Resize observer to watch for dialog resizing
1187 const resizeObs = new ResizeObserver(handleResize);
1188 resizeObs.observe(d);
1189
1190 return d;
1191}
1192
1193function dlgQueryKeyDown(event, inp) {
1194 let keyCode = ('which' in event) ? event.which : event.keyCode;
1195
1196 if (keyCode === 27) {
1197 // cancel editing
1198 let d = inp.parentElement.parentElement.parentElement;
1199 d.callback(false, d.callbackParam);
1200 dlgMessageDestroy(inp.parentElement);
1201 return false;
1202 }
1203
1204 if (keyCode === 13) {
1205 // finish editing
1206 let d = inp.parentElement.parentElement.parentElement;
1207 d.callback(inp.value, d.callbackParam);
1208 dlgMessageDestroy(inp.parentElement);
1209 return false;
1210 }
1211
1212 return true;
1213}
1214
1215function dlgQuery(string, value, queryCallback, param, size) {
1216 let d = document.createElement("div");
1217 d.className = "dlgFrame";
1218 d.style.zIndex = "31";
1219 d.callback = queryCallback;
1220 d.callbackParam = param;
1221 d.shouldDestroy = true;
1222
1223 if (size === undefined)
1224 size = 30;
1225
1226 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">&nbsp;</div>" +
1227 "<div class=\"dlgPanel\" style=\"padding: 20px;\">" +
1228 "<div id=\"dlgMessageString\">" + string + "&nbsp;&nbsp;<input type='text' size='"+size+"' id='dlgQueryInput' onkeydown='return dlgQueryKeyDown(event, this);' value='" + value + "'></input></div>" +
1229 "<br /><br />" +
1230 "<button class=\"dlgButtonDefault\" id=\"dlgMessageButton\" type=\"button\" " +
1231 " onClick=\"let d=this.parentElement.parentElement;d.callback(document.getElementById('dlgQueryInput').value,d.callbackParam);dlgMessageDestroy(this);\">OK</button>" +
1232 "<button class=\"dlgButton\" id=\"dlgMessageButton\" type=\"button\" " +
1233 " onClick=\"let d=this.parentElement.parentElement;d.callback(false,d.callbackParam);dlgMessageDestroy(this);\">Cancel</button>" +
1234 "</div>";
1235
1236 document.body.appendChild(d);
1237
1238 dlgShow(d, true);
1239 document.getElementById('dlgQueryInput').focus();
1240 document.getElementById('dlgQueryInput').select();
1241
1242 return d;
1243}
1244
1245let dlgWaitDialog;
1246let dlgWaitProgress;
1247let dlgWaitTime;
1248let dlgWaitFunc;
1249let dlgWaitFuncParam;
1250
1251function dlgWait(time, string, func, param) {
1252
1253 let d = document.createElement("div");
1254 d.className = "dlgFrame";
1255 d.style.zIndex = "31";
1256 d.shouldDestroy = true;
1257
1258 <!-- wait dialog -->
1259 d.innerHTML = "<div class=\"dlgTitlebar\" id=\"dlgMessageTitle\">Please wait...</div>" +
1260 "<div class=\"dlgPanel\" style=\"padding: 20px;\">" +
1261 "<div id=\"dlgMessageString\">" + string + "</div>" +
1262 "<br />" +
1263 "<div name=\"ctrlProgress\" style=\"margin:auto;width:250px;\" id=\"dlgWaitProgress\"></div>" +
1264 "</div>";
1265
1266 document.body.appendChild(d);
1267
1268 // init progress bar
1269 let p = document.getElementById('dlgWaitProgress');
1270 p.className = "ctrlProgress";
1271 let ind = document.createElement("div");
1272 ind.className = "ctrlProgressInd";
1273 ind.style.height = p.style.height;
1274 ind.style.backgroundColor = p.style.color;
1275 p.appendChild(ind);
1276 p.set = function (value) {
1277 this.firstChild.style.width = Math.round(parseInt(this.style.width) * value) + "px";
1278 };
1279
1280 dlgShow(d, true);
1281 dlgWaitDialog = d;
1282
1283 dlgWaitProgress = 0;
1284 dlgWaitTime = time;
1285 dlgWaitFunc = func;
1286 dlgWaitFuncParam = param;
1287 window.setTimeout(updateDlgWaitProgress, 100);
1288 return d;
1289}
1290
1291function updateDlgWaitProgress() {
1292 dlgWaitProgress += 0.1;
1293 let d = document.getElementById("dlgWaitProgress");
1294 d.set(dlgWaitProgress / dlgWaitTime);
1295 if (dlgWaitProgress >= dlgWaitTime) {
1296 dlgWaitDialog.style.display = "none";
1297 document.body.removeChild(dlgWaitDialog);
1298 let d = document.getElementById("dlgBlackout");
1299 if (d !== undefined && d !== null)
1300 d.style.display = "none";
1301 if (dlgWaitFunc !== undefined)
1302 dlgWaitFunc(dlgWaitFuncParam);
1303 } else
1304 window.setTimeout(updateDlgWaitProgress, 100);
1305}