summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/emcclib.js
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/emcclib.js')
-rw-r--r--apps/plugins/puzzles/src/emcclib.js752
1 files changed, 752 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/emcclib.js b/apps/plugins/puzzles/src/emcclib.js
new file mode 100644
index 0000000000..cd8876e76d
--- /dev/null
+++ b/apps/plugins/puzzles/src/emcclib.js
@@ -0,0 +1,752 @@
1/*
2 * emcclib.js: one of the Javascript components of an Emscripten-based
3 * web/Javascript front end for Puzzles.
4 *
5 * The other parts of this system live in emcc.c and emccpre.js. It
6 * also depends on being run in the context of a web page containing
7 * an appropriate collection of bits and pieces (a canvas, some
8 * buttons and links etc), which is generated for each puzzle by the
9 * script html/jspage.pl.
10 *
11 * This file contains a set of Javascript functions which we insert
12 * into Emscripten's library object via the --js-library option; this
13 * allows us to provide JS code which can be called from the
14 * Emscripten-compiled C, mostly dealing with UI interaction of
15 * various kinds.
16 */
17
18mergeInto(LibraryManager.library, {
19 /*
20 * void js_debug(const char *message);
21 *
22 * A function to write a diagnostic to the Javascript console.
23 * Unused in production, but handy in development.
24 */
25 js_debug: function(ptr) {
26 console.log(Pointer_stringify(ptr));
27 },
28
29 /*
30 * void js_error_box(const char *message);
31 *
32 * A wrapper around Javascript's alert(), so the C code can print
33 * simple error message boxes (e.g. when invalid data is entered
34 * in a configuration dialog).
35 */
36 js_error_box: function(ptr) {
37 alert(Pointer_stringify(ptr));
38 },
39
40 /*
41 * void js_remove_type_dropdown(void);
42 *
43 * Get rid of the drop-down list on the web page for selecting
44 * game presets. Called at setup time if the game back end
45 * provides neither presets nor configurability.
46 */
47 js_remove_type_dropdown: function() {
48 gametypelist.style.display = "none";
49 },
50
51 /*
52 * void js_remove_solve_button(void);
53 *
54 * Get rid of the Solve button on the web page. Called at setup
55 * time if the game doesn't support an in-game solve function.
56 */
57 js_remove_solve_button: function() {
58 document.getElementById("solve").style.display = "none";
59 },
60
61 /*
62 * void js_add_preset(int menuid, const char *name, int value);
63 *
64 * Add a preset to the drop-down types menu, or to a submenu of
65 * it. 'menuid' specifies an index into our array of submenus
66 * where the item might be placed; 'value' specifies the number
67 * that js_get_selected_preset() will return when this item is
68 * clicked.
69 */
70 js_add_preset: function(menuid, ptr, value) {
71 var name = Pointer_stringify(ptr);
72 var item = document.createElement("li");
73 item.setAttribute("data-index", value);
74 var tick = document.createElement("span");
75 tick.appendChild(document.createTextNode("\u2713"));
76 tick.style.color = "transparent";
77 tick.style.paddingRight = "0.5em";
78 item.appendChild(tick);
79 item.appendChild(document.createTextNode(name));
80 gametypesubmenus[menuid].appendChild(item);
81 gametypeitems.push(item);
82
83 item.onclick = function(event) {
84 if (dlg_dimmer === null) {
85 gametypeselectedindex = value;
86 command(2);
87 }
88 }
89 },
90
91 /*
92 * int js_add_preset_submenu(int menuid, const char *name);
93 *
94 * Add a submenu in the presets menu hierarchy. Returns its index,
95 * for passing as the 'menuid' argument in further calls to
96 * js_add_preset or this function.
97 */
98 js_add_preset_submenu: function(menuid, ptr, value) {
99 var name = Pointer_stringify(ptr);
100 var item = document.createElement("li");
101 // We still create a transparent tick element, even though it
102 // won't ever be selected, to make submenu titles line up
103 // nicely with their neighbours.
104 var tick = document.createElement("span");
105 tick.appendChild(document.createTextNode("\u2713"));
106 tick.style.color = "transparent";
107 tick.style.paddingRight = "0.5em";
108 item.appendChild(tick);
109 item.appendChild(document.createTextNode(name));
110 var submenu = document.createElement("ul");
111 submenu.className = "left";
112 item.appendChild(submenu);
113 gametypesubmenus[menuid].appendChild(item);
114 var toret = gametypesubmenus.length;
115 gametypesubmenus.push(submenu);
116 return toret;
117 },
118
119 /*
120 * int js_get_selected_preset(void);
121 *
122 * Return the index of the currently selected value in the type
123 * dropdown.
124 */
125 js_get_selected_preset: function() {
126 return gametypeselectedindex;
127 },
128
129 /*
130 * void js_select_preset(int n);
131 *
132 * Cause a different value to be selected in the type dropdown
133 * (for when the user selects values from the Custom configurer
134 * which turn out to exactly match a preset).
135 */
136 js_select_preset: function(n) {
137 gametypeselectedindex = n;
138 for (var i in gametypeitems) {
139 var item = gametypeitems[i];
140 var tick = item.firstChild;
141 if (item.getAttribute("data-index") == n) {
142 tick.style.color = "inherit";
143 } else {
144 tick.style.color = "transparent";
145 }
146 }
147 },
148
149 /*
150 * void js_get_date_64(unsigned *p);
151 *
152 * Return the current date, in milliseconds since the epoch
153 * (Javascript's native format), as a 64-bit integer. Used to
154 * invent an initial random seed for puzzle generation.
155 */
156 js_get_date_64: function(ptr) {
157 var d = (new Date()).valueOf();
158 setValue(ptr, d, 'i64');
159 },
160
161 /*
162 * void js_update_permalinks(const char *desc, const char *seed);
163 *
164 * Update the permalinks on the web page for a new game
165 * description and optional random seed. desc can never be NULL,
166 * but seed might be (if the game was generated by entering a
167 * descriptive id by hand), in which case we suppress display of
168 * the random seed permalink.
169 */
170 js_update_permalinks: function(desc, seed) {
171 desc = Pointer_stringify(desc);
172 permalink_desc.href = "#" + desc;
173
174 if (seed == 0) {
175 permalink_seed.style.display = "none";
176 } else {
177 seed = Pointer_stringify(seed);
178 permalink_seed.href = "#" + seed;
179 permalink_seed.style.display = "inline";
180 }
181 },
182
183 /*
184 * void js_enable_undo_redo(int undo, int redo);
185 *
186 * Set the enabled/disabled states of the undo and redo buttons,
187 * after a move.
188 */
189 js_enable_undo_redo: function(undo, redo) {
190 disable_menu_item(undo_button, (undo == 0));
191 disable_menu_item(redo_button, (redo == 0));
192 },
193
194 /*
195 * void js_activate_timer();
196 *
197 * Start calling the C timer_callback() function every 20ms.
198 */
199 js_activate_timer: function() {
200 if (timer === null) {
201 timer_reference_date = (new Date()).valueOf();
202 timer = setInterval(function() {
203 var now = (new Date()).valueOf();
204 timer_callback((now - timer_reference_date) / 1000.0);
205 timer_reference_date = now;
206 return true;
207 }, 20);
208 }
209 },
210
211 /*
212 * void js_deactivate_timer();
213 *
214 * Stop calling the C timer_callback() function every 20ms.
215 */
216 js_deactivate_timer: function() {
217 if (timer !== null) {
218 clearInterval(timer);
219 timer = null;
220 }
221 },
222
223 /*
224 * void js_canvas_start_draw(void);
225 *
226 * Prepare to do some drawing on the canvas.
227 */
228 js_canvas_start_draw: function() {
229 ctx = offscreen_canvas.getContext('2d');
230 update_xmin = update_xmax = update_ymin = update_ymax = undefined;
231 },
232
233 /*
234 * void js_canvas_draw_update(int x, int y, int w, int h);
235 *
236 * Mark a rectangle of the off-screen canvas as needing to be
237 * copied to the on-screen one.
238 */
239 js_canvas_draw_update: function(x, y, w, h) {
240 /*
241 * Currently we do this in a really simple way, just by taking
242 * the smallest rectangle containing all updates so far. We
243 * could instead keep the data in a richer form (e.g. retain
244 * multiple smaller rectangles needing update, and only redraw
245 * the whole thing beyond a certain threshold) but this will
246 * do for now.
247 */
248 if (update_xmin === undefined || update_xmin > x) update_xmin = x;
249 if (update_ymin === undefined || update_ymin > y) update_ymin = y;
250 if (update_xmax === undefined || update_xmax < x+w) update_xmax = x+w;
251 if (update_ymax === undefined || update_ymax < y+h) update_ymax = y+h;
252 },
253
254 /*
255 * void js_canvas_end_draw(void);
256 *
257 * Finish the drawing, by actually copying the newly drawn stuff
258 * to the on-screen canvas.
259 */
260 js_canvas_end_draw: function() {
261 if (update_xmin !== undefined) {
262 var onscreen_ctx = onscreen_canvas.getContext('2d');
263 onscreen_ctx.drawImage(offscreen_canvas,
264 update_xmin, update_ymin,
265 update_xmax - update_xmin,
266 update_ymax - update_ymin,
267 update_xmin, update_ymin,
268 update_xmax - update_xmin,
269 update_ymax - update_ymin);
270 }
271 ctx = null;
272 },
273
274 /*
275 * void js_canvas_draw_rect(int x, int y, int w, int h,
276 * const char *colour);
277 *
278 * Draw a rectangle.
279 */
280 js_canvas_draw_rect: function(x, y, w, h, colptr) {
281 ctx.fillStyle = Pointer_stringify(colptr);
282 ctx.fillRect(x, y, w, h);
283 },
284
285 /*
286 * void js_canvas_clip_rect(int x, int y, int w, int h);
287 *
288 * Set a clipping rectangle.
289 */
290 js_canvas_clip_rect: function(x, y, w, h) {
291 ctx.save();
292 ctx.beginPath();
293 ctx.rect(x, y, w, h);
294 ctx.clip();
295 },
296
297 /*
298 * void js_canvas_unclip(void);
299 *
300 * Reset to no clipping.
301 */
302 js_canvas_unclip: function() {
303 ctx.restore();
304 },
305
306 /*
307 * void js_canvas_draw_line(float x1, float y1, float x2, float y2,
308 * int width, const char *colour);
309 *
310 * Draw a line. We must adjust the coordinates by 0.5 because
311 * Javascript's canvas coordinates appear to be pixel corners,
312 * whereas we want pixel centres. Also, we manually draw the pixel
313 * at each end of the line, which our clients will expect but
314 * Javascript won't reliably do by default (in common with other
315 * Postscriptish drawing frameworks).
316 */
317 js_canvas_draw_line: function(x1, y1, x2, y2, width, colour) {
318 colour = Pointer_stringify(colour);
319
320 ctx.beginPath();
321 ctx.moveTo(x1 + 0.5, y1 + 0.5);
322 ctx.lineTo(x2 + 0.5, y2 + 0.5);
323 ctx.lineWidth = width;
324 ctx.lineCap = 'round';
325 ctx.lineJoin = 'round';
326 ctx.strokeStyle = colour;
327 ctx.stroke();
328 ctx.fillStyle = colour;
329 ctx.fillRect(x1, y1, 1, 1);
330 ctx.fillRect(x2, y2, 1, 1);
331 },
332
333 /*
334 * void js_canvas_draw_poly(int *points, int npoints,
335 * const char *fillcolour,
336 * const char *outlinecolour);
337 *
338 * Draw a polygon.
339 */
340 js_canvas_draw_poly: function(pointptr, npoints, fill, outline) {
341 ctx.beginPath();
342 ctx.moveTo(getValue(pointptr , 'i32') + 0.5,
343 getValue(pointptr+4, 'i32') + 0.5);
344 for (var i = 1; i < npoints; i++)
345 ctx.lineTo(getValue(pointptr+8*i , 'i32') + 0.5,
346 getValue(pointptr+8*i+4, 'i32') + 0.5);
347 ctx.closePath();
348 if (fill != 0) {
349 ctx.fillStyle = Pointer_stringify(fill);
350 ctx.fill();
351 }
352 ctx.lineWidth = '1';
353 ctx.lineCap = 'round';
354 ctx.lineJoin = 'round';
355 ctx.strokeStyle = Pointer_stringify(outline);
356 ctx.stroke();
357 },
358
359 /*
360 * void js_canvas_draw_circle(int x, int y, int r,
361 * const char *fillcolour,
362 * const char *outlinecolour);
363 *
364 * Draw a circle.
365 */
366 js_canvas_draw_circle: function(x, y, r, fill, outline) {
367 ctx.beginPath();
368 ctx.arc(x + 0.5, y + 0.5, r, 0, 2*Math.PI);
369 if (fill != 0) {
370 ctx.fillStyle = Pointer_stringify(fill);
371 ctx.fill();
372 }
373 ctx.lineWidth = '1';
374 ctx.lineCap = 'round';
375 ctx.lineJoin = 'round';
376 ctx.strokeStyle = Pointer_stringify(outline);
377 ctx.stroke();
378 },
379
380 /*
381 * int js_canvas_find_font_midpoint(int height, const char *fontptr);
382 *
383 * Return the adjustment required for text displayed using
384 * ALIGN_VCENTRE. We want to place the midpoint between the
385 * baseline and the cap-height at the specified position; so this
386 * function returns the adjustment which, when added to the
387 * desired centre point, returns the y-coordinate at which you
388 * should put the baseline.
389 *
390 * There is no sensible method of querying this kind of font
391 * metric in Javascript, so instead we render a piece of test text
392 * to a throwaway offscreen canvas and then read the pixel data
393 * back out to find the highest and lowest pixels. That's good
394 * _enough_ (in that we only needed the answer to the nearest
395 * pixel anyway), but rather disgusting!
396 *
397 * Since this is a very expensive operation, we cache the results
398 * per (font,height) pair.
399 */
400 js_canvas_find_font_midpoint: function(height, font) {
401 font = Pointer_stringify(font);
402
403 // Reuse cached value if possible
404 if (midpoint_cache[font] !== undefined)
405 return midpoint_cache[font];
406
407 // Find the width of the string
408 var ctx1 = onscreen_canvas.getContext('2d');
409 ctx1.font = font;
410 var width = (ctx1.measureText(midpoint_test_str).width + 1) | 0;
411
412 // Construct a test canvas of appropriate size, initialise it to
413 // black, and draw the string on it in white
414 var measure_canvas = document.createElement('canvas');
415 var ctx2 = measure_canvas.getContext('2d');
416 ctx2.canvas.width = width;
417 ctx2.canvas.height = 2*height;
418 ctx2.fillStyle = "#000000";
419 ctx2.fillRect(0, 0, width, 2*height);
420 var baseline = (1.5*height) | 0;
421 ctx2.fillStyle = "#ffffff";
422 ctx2.font = font;
423 ctx2.fillText(midpoint_test_str, 0, baseline);
424
425 // Scan the contents of the test canvas to find the top and bottom
426 // set pixels.
427 var pixels = ctx2.getImageData(0, 0, width, 2*height).data;
428 var ymin = 2*height, ymax = -1;
429 for (var y = 0; y < 2*height; y++) {
430 for (var x = 0; x < width; x++) {
431 if (pixels[4*(y*width+x)] != 0) {
432 if (ymin > y) ymin = y;
433 if (ymax < y) ymax = y;
434 break;
435 }
436 }
437 }
438
439 var ret = (baseline - (ymin + ymax) / 2) | 0;
440 midpoint_cache[font] = ret;
441 return ret;
442 },
443
444 /*
445 * void js_canvas_draw_text(int x, int y, int halign,
446 * const char *colptr, const char *fontptr,
447 * const char *text);
448 *
449 * Draw text. Vertical alignment has been taken care of on the C
450 * side, by optionally calling the above function. Horizontal
451 * alignment is handled here, since we can get the canvas draw
452 * function to do it for us with almost no extra effort.
453 */
454 js_canvas_draw_text: function(x, y, halign, colptr, fontptr, text) {
455 ctx.font = Pointer_stringify(fontptr);
456 ctx.fillStyle = Pointer_stringify(colptr);
457 ctx.textAlign = (halign == 0 ? 'left' :
458 halign == 1 ? 'center' : 'right');
459 ctx.textBaseline = 'alphabetic';
460 ctx.fillText(Pointer_stringify(text), x, y);
461 },
462
463 /*
464 * int js_canvas_new_blitter(int w, int h);
465 *
466 * Create a new blitter object, which is just an offscreen canvas
467 * of the specified size.
468 */
469 js_canvas_new_blitter: function(w, h) {
470 var id = blittercount++;
471 blitters[id] = document.createElement("canvas");
472 blitters[id].width = w;
473 blitters[id].height = h;
474 return id;
475 },
476
477 /*
478 * void js_canvas_free_blitter(int id);
479 *
480 * Free a blitter (or rather, destroy our reference to it so JS
481 * can garbage-collect it, and also enforce that we don't
482 * accidentally use it again afterwards).
483 */
484 js_canvas_free_blitter: function(id) {
485 blitters[id] = null;
486 },
487
488 /*
489 * void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
490 *
491 * Copy from the puzzle image to a blitter. The size is passed to
492 * us, partly so we don't have to remember the size of each
493 * blitter, but mostly so that the C side can adjust the copy
494 * rectangle in the case where it partially overlaps the edge of
495 * the screen.
496 */
497 js_canvas_copy_to_blitter: function(id, x, y, w, h) {
498 var blitter_ctx = blitters[id].getContext('2d');
499 blitter_ctx.drawImage(offscreen_canvas,
500 x, y, w, h,
501 0, 0, w, h);
502 },
503
504 /*
505 * void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
506 *
507 * Copy from a blitter back to the puzzle image. As above, the
508 * size of the copied rectangle is passed to us from the C side
509 * and may already have been modified.
510 */
511 js_canvas_copy_from_blitter: function(id, x, y, w, h) {
512 ctx.drawImage(blitters[id],
513 0, 0, w, h,
514 x, y, w, h);
515 },
516
517 /*
518 * void js_canvas_make_statusbar(void);
519 *
520 * Cause a status bar to exist. Called at setup time if the puzzle
521 * back end turns out to want one.
522 */
523 js_canvas_make_statusbar: function() {
524 var statusholder = document.getElementById("statusbarholder");
525 statusbar = document.createElement("div");
526 statusbar.style.overflow = "hidden";
527 statusbar.style.width = (onscreen_canvas.width - 4) + "px";
528 statusholder.style.width = onscreen_canvas.width + "px";
529 statusbar.style.height = "1.2em";
530 statusbar.style.textAlign = "left";
531 statusbar.style.background = "#d8d8d8";
532 statusbar.style.borderLeft = '2px solid #c8c8c8';
533 statusbar.style.borderTop = '2px solid #c8c8c8';
534 statusbar.style.borderRight = '2px solid #e8e8e8';
535 statusbar.style.borderBottom = '2px solid #e8e8e8';
536 statusbar.appendChild(document.createTextNode(" "));
537 statusholder.appendChild(statusbar);
538 },
539
540 /*
541 * void js_canvas_set_statusbar(const char *text);
542 *
543 * Set the text in the status bar.
544 */
545 js_canvas_set_statusbar: function(ptr) {
546 var text = Pointer_stringify(ptr);
547 statusbar.replaceChild(document.createTextNode(text),
548 statusbar.lastChild);
549 },
550
551 /*
552 * void js_canvas_set_size(int w, int h);
553 *
554 * Set the size of the puzzle canvas. Called at setup, and every
555 * time the user picks new puzzle settings requiring a different
556 * size.
557 */
558 js_canvas_set_size: function(w, h) {
559 onscreen_canvas.width = w;
560 offscreen_canvas.width = w;
561 if (statusbar !== null) {
562 statusbar.style.width = (w - 4) + "px";
563 document.getElementById("statusbarholder").style.width = w + "px";
564 }
565 resizable_div.style.width = w + "px";
566
567 onscreen_canvas.height = h;
568 offscreen_canvas.height = h;
569 },
570
571 /*
572 * void js_dialog_init(const char *title);
573 *
574 * Begin constructing a 'dialog box' which will be popped up in an
575 * overlay on top of the rest of the puzzle web page.
576 */
577 js_dialog_init: function(titletext) {
578 // Create an overlay on the page which darkens everything
579 // beneath it.
580 dlg_dimmer = document.createElement("div");
581 dlg_dimmer.style.width = "100%";
582 dlg_dimmer.style.height = "100%";
583 dlg_dimmer.style.background = '#000000';
584 dlg_dimmer.style.position = 'fixed';
585 dlg_dimmer.style.opacity = 0.3;
586 dlg_dimmer.style.top = dlg_dimmer.style.left = 0;
587 dlg_dimmer.style["z-index"] = 99;
588
589 // Now create a form which sits on top of that in turn.
590 dlg_form = document.createElement("form");
591 dlg_form.style.width = (window.innerWidth * 2 / 3) + "px";
592 dlg_form.style.opacity = 1;
593 dlg_form.style.background = '#ffffff';
594 dlg_form.style.color = '#000000';
595 dlg_form.style.position = 'absolute';
596 dlg_form.style.border = "2px solid black";
597 dlg_form.style.padding = "20px";
598 dlg_form.style.top = (window.innerHeight / 10) + "px";
599 dlg_form.style.left = (window.innerWidth / 6) + "px";
600 dlg_form.style["z-index"] = 100;
601
602 var title = document.createElement("p");
603 title.style.marginTop = "0px";
604 title.appendChild(document.createTextNode
605 (Pointer_stringify(titletext)));
606 dlg_form.appendChild(title);
607
608 dlg_return_funcs = [];
609 dlg_next_id = 0;
610 },
611
612 /*
613 * void js_dialog_string(int i, const char *title, const char *initvalue);
614 *
615 * Add a string control (that is, an edit box) to the dialog under
616 * construction.
617 */
618 js_dialog_string: function(index, title, initialtext) {
619 dlg_form.appendChild(document.createTextNode(Pointer_stringify(title)));
620 var editbox = document.createElement("input");
621 editbox.type = "text";
622 editbox.value = Pointer_stringify(initialtext);
623 dlg_form.appendChild(editbox);
624 dlg_form.appendChild(document.createElement("br"));
625
626 dlg_return_funcs.push(function() {
627 dlg_return_sval(index, editbox.value);
628 });
629 },
630
631 /*
632 * void js_dialog_choices(int i, const char *title, const char *choicelist,
633 * int initvalue);
634 *
635 * Add a choices control (i.e. a drop-down list) to the dialog
636 * under construction. The 'choicelist' parameter is unchanged
637 * from the way the puzzle back end will have supplied it: i.e.
638 * it's still encoded as a single string whose first character
639 * gives the separator.
640 */
641 js_dialog_choices: function(index, title, choicelist, initvalue) {
642 dlg_form.appendChild(document.createTextNode(Pointer_stringify(title)));
643 var dropdown = document.createElement("select");
644 var choicestr = Pointer_stringify(choicelist);
645 var items = choicestr.slice(1).split(choicestr[0]);
646 var options = [];
647 for (var i in items) {
648 var option = document.createElement("option");
649 option.value = i;
650 option.appendChild(document.createTextNode(items[i]));
651 if (i == initvalue) option.selected = true;
652 dropdown.appendChild(option);
653 options.push(option);
654 }
655 dlg_form.appendChild(dropdown);
656 dlg_form.appendChild(document.createElement("br"));
657
658 dlg_return_funcs.push(function() {
659 var val = 0;
660 for (var i in options) {
661 if (options[i].selected) {
662 val = options[i].value;
663 break;
664 }
665 }
666 dlg_return_ival(index, val);
667 });
668 },
669
670 /*
671 * void js_dialog_boolean(int i, const char *title, int initvalue);
672 *
673 * Add a boolean control (a checkbox) to the dialog under
674 * construction. Checkboxes are generally expected to be sensitive
675 * on their label text as well as the box itself, so for this
676 * control we create an actual label rather than merely a text
677 * node (and hence we must allocate an id to the checkbox so that
678 * the label can refer to it).
679 */
680 js_dialog_boolean: function(index, title, initvalue) {
681 var checkbox = document.createElement("input");
682 checkbox.type = "checkbox";
683 checkbox.id = "cb" + String(dlg_next_id++);
684 checkbox.checked = (initvalue != 0);
685 dlg_form.appendChild(checkbox);
686 var checkboxlabel = document.createElement("label");
687 checkboxlabel.setAttribute("for", checkbox.id);
688 checkboxlabel.textContent = Pointer_stringify(title);
689 dlg_form.appendChild(checkboxlabel);
690 dlg_form.appendChild(document.createElement("br"));
691
692 dlg_return_funcs.push(function() {
693 dlg_return_ival(index, checkbox.checked ? 1 : 0);
694 });
695 },
696
697 /*
698 * void js_dialog_launch(void);
699 *
700 * Finish constructing a dialog, and actually display it, dimming
701 * everything else on the page.
702 */
703 js_dialog_launch: function() {
704 // Put in the OK and Cancel buttons at the bottom.
705 var button;
706
707 button = document.createElement("input");
708 button.type = "button";
709 button.value = "OK";
710 button.onclick = function(event) {
711 for (var i in dlg_return_funcs)
712 dlg_return_funcs[i]();
713 command(3);
714 }
715 dlg_form.appendChild(button);
716
717 button = document.createElement("input");
718 button.type = "button";
719 button.value = "Cancel";
720 button.onclick = function(event) {
721 command(4);
722 }
723 dlg_form.appendChild(button);
724
725 document.body.appendChild(dlg_dimmer);
726 document.body.appendChild(dlg_form);
727 },
728
729 /*
730 * void js_dialog_cleanup(void);
731 *
732 * Stop displaying a dialog, and clean up the internal state
733 * associated with it.
734 */
735 js_dialog_cleanup: function() {
736 document.body.removeChild(dlg_dimmer);
737 document.body.removeChild(dlg_form);
738 dlg_dimmer = dlg_form = null;
739 onscreen_canvas.focus();
740 },
741
742 /*
743 * void js_focus_canvas(void);
744 *
745 * Return keyboard focus to the puzzle canvas. Called after a
746 * puzzle-control button is pressed, which tends to have the side
747 * effect of taking focus away from the canvas.
748 */
749 js_focus_canvas: function() {
750 onscreen_canvas.focus();
751 }
752});