add item to new Firefox button menu, make Firefox and SeaMonkey open Mandelbrot in...
[mandelbrot.git] / xulapp / chrome / mandelbrot / content / mandelbrot.js
index e4c7d9a77de84f7dcae94470317f5f3ac5b86319..c2bfba29e800c03a1ef0c7b9d40df704d944850f 100644 (file)
  *
  * The Initial Developer of the Original Code is
  * Robert Kaiser <kairo@kairo.at>.
- * Portions created by the Initial Developer are Copyright (C) 2008
+ * Portions created by the Initial Developer are Copyright (C) 2008-2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Robert Kaiser <kairo@kairo.at>
+ *   prefiks (patch for some speedups)
+ *   Boris Zbarsky <bzbarsky@mit.edu> (use imageData for canvas interaction)
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
@@ -45,30 +47,105 @@ var gCurrentImageData;
 
 function Startup() {
   updateIterMenu();
+  updateAlgoMenu();
   updatePaletteMenu();
   gMbrotBundle = document.getElementById("mbrotBundle");
   document.getElementById("statusLabel").value = gMbrotBundle.getString("statusEmpty");
 }
 
-function adjustCoordsAndDraw(aC_min, aC_max) {
-  let iWidth = 0;
-  try {
-    iWidth = gPref.getIntPref("mandelbrot.image.width");
-  }
-  catch (e) { }
-  if ((iWidth < 10) || (iWidth > 5000)) {
-    iWidth = 300;
-    gPref.setIntPref("mandelbrot.image.width", iWidth);
-  }
-  let iHeight = 0;
-  try {
-    iHeight = gPref.getIntPref("mandelbrot.image.height");
-  }
-  catch (e) { }
-  if ((iHeight < 10) || (iHeight > 5000)) {
-    iHeight = 300;
-    gPref.setIntPref("mandelbrot.image.height", iHeight);
+function getAdjustPref(prefname) {
+  let value;
+  switch (prefname) {
+    case "image.width":
+    case "image.height":
+      value = 0;
+      try {
+        value = gPref.getIntPref("mandelbrot." + prefname);
+      }
+      catch (e) { }
+      if ((value < 10) || (value > 5000)) {
+        value = 300;
+        gPref.setIntPref("mandelbrot." + prefname, value);
+      }
+      return value;
+    case "last_image.Cr_*":
+      let Cr_min = -2.0;
+      let Cr_max = 1.0;
+      try {
+        Cr_min = parseFloat(gPref.getCharPref("mandelbrot.last_image.Cr_min"));
+        Cr_max = parseFloat(gPref.getCharPref("mandelbrot.last_image.Cr_max"));
+      }
+      catch (e) { }
+      if ((Cr_min < -3) || (Cr_min > 2) ||
+          (Cr_max < -3) || (Cr_max > 2) || (Cr_min >= Cr_max)) {
+        Cr_min = -2.0; Cr_max = 1.0;
+      }
+      gPref.setCharPref("mandelbrot.last_image.Cr_min", Cr_min);
+      gPref.setCharPref("mandelbrot.last_image.Cr_max", Cr_max);
+      return {Cr_min: Cr_min, Cr_max: Cr_max};
+    case "last_image.Ci_*":
+      let Ci_min = -1.5;
+      let Ci_max = 1.5;
+      try {
+        Ci_min = parseFloat(gPref.getCharPref("mandelbrot.last_image.Ci_min"));
+        Ci_max = parseFloat(gPref.getCharPref("mandelbrot.last_image.Ci_max"));
+      }
+      catch (e) { }
+      if ((Ci_min < -2.5) || (Ci_min > 2.5) ||
+          (Ci_max < -2.5) || (Ci_max > 2.5) || (Ci_min >= Ci_max)) {
+        Ci_min = -1.5; Ci_max = 1.5;
+      }
+      gPref.setCharPref("mandelbrot.last_image.Ci_min", Ci_min);
+      gPref.setCharPref("mandelbrot.last_image.Ci_max", Ci_max);
+      return {Ci_min: Ci_min, Ci_max: Ci_max};
+    case "iteration_max":
+      value = 500;
+      try {
+        value = gPref.getIntPref("mandelbrot." + prefname);
+      }
+      catch (e) {
+        setIter(value);
+      }
+      if (value < 10 || value > 10000) {
+        value = 500;
+        setIter(value);
+      }
+      return value;
+    case "use_algorithm":
+      value = "numeric";
+      try {
+        value = gPref.getCharPref("mandelbrot." + prefname);
+      }
+      catch (e) {
+        setAlgorithm(value);
+      }
+      return value;
+   case "color_palette":
+      value = "kairo";
+      try {
+        value = gPref.getCharPref("mandelbrot." + prefname);
+      }
+      catch(e) {
+        setPalette(value);
+      }
+      return value;
+   case "syncProportions":
+      value = true;
+      try {
+        value = gPref.getBoolPref("mandelbrot." + prefname);
+      }
+      catch(e) {
+        gPref.setBoolPref("mandelbrot." + prefname, value);
+      }
+      return value;
+    default:
+      return false;
   }
+}
+
+function adjustCoordsAndDraw(aC_min, aC_max) {
+  let iWidth = getAdjustPref("image.width");
+  let iHeight = getAdjustPref("image.height");
 
   // correct coordinates
   if (aC_min.r < -2)
@@ -108,55 +185,19 @@ function drawImage() {
 
   document.getElementById("statusLabel").value = gMbrotBundle.getString("statusDrawing");
 
-  let Cr_min = -2.0;
-  let Cr_max = 1.0;
-  try {
-    Cr_min = parseFloat(gPref.getCharPref("mandelbrot.last_image.Cr_min"));
-    Cr_max = parseFloat(gPref.getCharPref("mandelbrot.last_image.Cr_max"));
-  }
-  catch (e) { }
-  if ((Cr_min < -3) || (Cr_min > 2) ||
-      (Cr_max < -3) || (Cr_max > 2) || (Cr_min >= Cr_max)) {
-    Cr_min = -2.0; Cr_max = 1.0;
-  }
-  gPref.setCharPref("mandelbrot.last_image.Cr_min", Cr_min);
-  gPref.setCharPref("mandelbrot.last_image.Cr_max", Cr_max);
+  let Cr_vals = getAdjustPref("last_image.Cr_*");
+  let Cr_min = Cr_vals.Cr_min;
+  let Cr_max = Cr_vals.Cr_max;
 
-  let Ci_min = -1.5;
-  let Ci_max = 1.5;
-  try {
-    Ci_min = parseFloat(gPref.getCharPref("mandelbrot.last_image.Ci_min"));
-    Ci_max = parseFloat(gPref.getCharPref("mandelbrot.last_image.Ci_max"));
-  }
-  catch (e) { }
-  if ((Ci_min < -2.5) || (Ci_min > 2.5) ||
-      (Ci_max < -2.5) || (Ci_max > 2.5) || (Ci_min >= Ci_max)) {
-    Ci_min = -1.5; Ci_max = 1.5;
-  }
-  gPref.setCharPref("mandelbrot.last_image.Ci_min", Ci_min);
-  gPref.setCharPref("mandelbrot.last_image.Ci_max", Ci_max);
+  let Ci_vals = getAdjustPref("last_image.Ci_*");
+  let Ci_min = Ci_vals.Ci_min;
+  let Ci_max = Ci_vals.Ci_max;
 
-  let iterMax = gPref.getIntPref("mandelbrot.iteration_max");
-  let algorithm = gPref.getCharPref("mandelbrot.use_algorithm");
+  let iterMax = getAdjustPref("iteration_max");
+  let algorithm = getAdjustPref("use_algorithm");
 
-  let iWidth = 0;
-  try {
-    iWidth = gPref.getIntPref("mandelbrot.image.width");
-  }
-  catch (e) { }
-  if ((iWidth < 10) || (iWidth > 5000)) {
-    iWidth = 300;
-    gPref.setIntPref("mandelbrot.image.width", iWidth);
-  }
-  let iHeight = 0;
-  try {
-    iHeight = gPref.getIntPref("mandelbrot.image.height");
-  }
-  catch (e) { }
-  if ((iHeight < 10) || (iHeight > 5000)) {
-    iHeight = 300;
-    gPref.setIntPref("mandelbrot.image.height", iHeight);
-  }
+  let iWidth = getAdjustPref("image.width");
+  let iHeight = getAdjustPref("image.height");
 
   gCurrentImageData = {
     C_min: new complex(Cr_min, Ci_min),
@@ -187,14 +228,21 @@ function drawLine(line, dimensions, canvas, context, iterMax, algorithm) {
   let Ci_max = dimensions[3];
   let Ci_scale = Ci_max - Ci_min;
 
-  let pixels = [];
+  let lines = Math.min(canvas.height - line, 8);
+  let imageData = context.createImageData(canvas.width, lines);
+  let pixels = imageData.data;
+  let idx = 0;
   for (var img_y = line; img_y < canvas.height && img_y < line+8; img_y++)
     for (let img_x = 0; img_x < canvas.width; img_x++) {
       let C = new complex(Cr_min + (img_x / canvas.width) * Cr_scale,
                           Ci_min + (img_y / canvas.height) * Ci_scale);
-      pixels.push.apply(pixels, drawPoint(context, img_x, img_y, C, iterMax, algorithm));
+      let colors = drawPoint(context, img_x, img_y, C, iterMax, algorithm);
+      pixels[idx++] = colors[0];
+      pixels[idx++] = colors[1];
+      pixels[idx++] = colors[2];
+      pixels[idx++] = colors[3];
     }
-  context.putImageData({width: canvas.width, height: pixels.length/4/canvas.width, data: pixels}, 0, line);
+  context.putImageData(imageData, 0, line);
 
   if (img_y < canvas.height)
     setTimeout(drawLine, 0, img_y, dimensions, canvas, context, iterMax, algorithm);
@@ -432,24 +480,17 @@ function mouseevent(etype, event) {
         if (zoomend.y < zoomstart.y)
           [zoomend.y, zoomstart.y] = [zoomstart.y, zoomend.y];
 
-        let prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
-                                .getService(Components.interfaces.nsIPromptService);
-        let ok = prompts.confirm(null, gMbrotBundle.getString("zoomConfirmTitle"),
-                                 gMbrotBundle.getString("zoomConfirmLabel"));
-        // ok is now true if OK was clicked, and false if cancel was clicked
-        if (ok) {
-          // determine new "coordinates"
-          let CWidth = gCurrentImageData.C_max.r - gCurrentImageData.C_min.r;
-          let CHeight = gCurrentImageData.C_max.i - gCurrentImageData.C_min.i;
-          let newC_min = new complex(
-              gCurrentImageData.C_min.r + zoomstart.x / gCurrentImageData.iWidth * CWidth,
-              gCurrentImageData.C_min.i + zoomstart.y / gCurrentImageData.iHeight * CHeight);
-          let newC_max = new complex(
-              gCurrentImageData.C_min.r + zoomend.x / gCurrentImageData.iWidth * CWidth,
-              gCurrentImageData.C_min.i + zoomend.y / gCurrentImageData.iHeight * CHeight);
-
-          adjustCoordsAndDraw(newC_min, newC_max);
-        }
+        // determine new "coordinates"
+        let CWidth = gCurrentImageData.C_max.r - gCurrentImageData.C_min.r;
+        let CHeight = gCurrentImageData.C_max.i - gCurrentImageData.C_min.i;
+        let newC_min = new complex(
+            gCurrentImageData.C_min.r + zoomstart.x / gCurrentImageData.iWidth * CWidth,
+            gCurrentImageData.C_min.i + zoomstart.y / gCurrentImageData.iHeight * CHeight);
+        let newC_max = new complex(
+            gCurrentImageData.C_min.r + zoomend.x / gCurrentImageData.iWidth * CWidth,
+            gCurrentImageData.C_min.i + zoomend.y / gCurrentImageData.iHeight * CHeight);
+
+        adjustCoordsAndDraw(newC_min, newC_max);
       }
       zoomstart = undefined;
       break;
@@ -484,6 +525,15 @@ function saveImage() {
   }
 }
 
+function exitMandelbrot() {
+  var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
+                          .getService(Components.interfaces.nsIXULAppInfo);
+  if (appInfo.ID == "mandelbrot@kairo.at")
+    quitApp(false);
+  else
+    window.close();
+}
+
 function updateBookmarkMenu(aParent) {
   document.getElementById("bookmarkSave").disabled =
     (!document.getElementById("drawButton").hidden || (gStartTime > 0));
@@ -605,15 +655,7 @@ function saveBookmark() {
 }
 
 function updateIterMenu() {
-  let currentIter = 0;
-  try {
-    currentIter = gPref.getIntPref("mandelbrot.iteration_max");
-  }
-  catch(e) { }
-  if (currentIter < 10) {
-    currentIter = 500;
-    setIter(currentIter);
-  }
+  let currentIter = getAdjustPref("iteration_max");
 
   let popup = document.getElementById("menu_iterPopup");
   let item = popup.firstChild;
@@ -633,15 +675,7 @@ function setIter(aIter) {
 }
 
 function updatePaletteMenu() {
-  let currentPalette = '';
-  try {
-    currentPalette = gPref.getCharPref("mandelbrot.color_palette");
-  }
-  catch(e) { }
-  if (!currentPalette.length) {
-    currentPalette = 'kairo';
-    setPalette(currentPalette);
-  }
+  let currentPalette = getAdjustPref("color_palette");
   if (!gColorPalette || !gColorPalette.length)
     gColorPalette = getColorPalette(currentPalette);
 
@@ -664,30 +698,36 @@ function setPalette(aPaletteID) {
 }
 
 function imgSettings() {
-  window.openDialog("chrome://mandelbrot/content/image-settings.xul");
+  let anchor = null;
+  let position = "before_start";
+  if (document.getElementById("mandelbrotWindow").nodeName == "page") {
+    anchor = document.getElementById("mandelbrotToolbar");
+  }
+  else {
+    anchor = document.getElementById("mandelbrotMenubar");
+    position = "after_start";
+  }
+  document.getElementById("imgSettingsPanel").showPopup(anchor, position);
 }
 
 function updateDebugMenu() {
-  var jitMenuItem = document.getElementById("jitEnabled");
-  jitMenuItem.setAttribute("checked", gPref.getBoolPref("javascript.options.jit.chrome"));
+  let scope = (document.getElementById("mandelbrotWindow").nodeName == "page") ? "content" : "chrome";
+  for each (let type in ["tracejit", "methodjit"]) {
+    let jitMenuItem = document.getElementById(type + "Enabled");
+    jitMenuItem.setAttribute("checked", gPref.getBoolPref("javascript.options." + type + "." + scope));
+  }
 }
 
-function toggleJITState(jitMenuItem) {
-  var jitEnabled = !gPref.getBoolPref("javascript.options.jit.chrome");
-  gPref.setBoolPref("javascript.options.jit.chrome", jitEnabled)
-  jitMenuItem.setAttribute("checked", jitEnabled? "true" : "false");
+function toggleJITState(jitMenuItem, jittype) {
+  let scope = (document.getElementById("mandelbrotWindow").nodeName == "page") ? "content" : "chrome";
+  let jitpref = "javascript.options." + jittype + "jit." + scope;
+  let jitEnabled = !gPref.getBoolPref(jitpref);
+  gPref.setBoolPref(jitpref, jitEnabled)
+  jitMenuItem.setAttribute("checked", jitEnabled ? "true" : "false");
 }
 
 function updateAlgoMenu() {
-  let currentAlgo = '';
-  try {
-    currentAlgo = gPref.getCharPref("mandelbrot.use_algorithm");
-  }
-  catch(e) { }
-  if (!currentAlgo.length) {
-    currentAlgo = 'numeric';
-    setAlgorithm(currentAlgo);
-  }
+  let currentAlgo = getAdjustPref("use_algorithm");
 
   let popup = document.getElementById("menu_algoPopup");
   let item = popup.firstChild;
@@ -729,6 +769,143 @@ function errorConsole() {
   toOpenWindowByType("global:console", "chrome://global/content/console.xul");
 }
 
+function initImgSettings() {
+  // Get values from prefs.
+  for each (let coord in ["Cr", "Ci"]) {
+    let coord_vals = getAdjustPref("last_image." + coord + "_*");
+    document.getElementById("is_" + coord + "_min").value = coord_vals[coord + "_min"];
+    document.getElementById("is_" + coord + "_max").value = coord_vals[coord + "_max"];
+  }
+  for each (let dim in ["width", "height"]) {
+    document.getElementById("is_img_" + dim).value = getAdjustPref("image." + dim);
+  }
+  document.getElementById("is_syncProp").checked = getAdjustPref("syncProportions");
+
+  // Calculate scales.
+  recalcCoord("Cr", "scale");
+  recalcCoord("Ci", "scale");
+
+  // Clear the preview.
+  let canvas = document.getElementById("is_mbrotPreview");
+  let context = canvas.getContext("2d");
+  context.fillStyle = "rgba(255, 255, 255, 127)";
+  context.fillRect(0, 0, canvas.width, canvas.height);
+}
+
+function closeImgSettings() {
+  // Hide popup, which will automatically make a call to save values.
+  document.getElementById("imgSettingsPanel").hidePopup();
+}
+
+function saveImgSettings() {
+  // Get values to prefs.
+  for each (let coord in ["Cr_min", "Cr_max", "Ci_min", "Ci_max"]) {
+    gPref.setCharPref("mandelbrot.last_image." + coord,
+                      document.getElementById("is_" + coord).value);
+  }
+  for each (let dim in ["width", "height"]) {
+    gPref.setIntPref("mandelbrot.image." + dim,
+                     document.getElementById("is_img_" + dim).value);
+  }
+  gPref.setBoolPref("mandelbrot.syncProportions",
+                    document.getElementById("is_syncProp").checked);
+}
+
+function checkISValue(textbox, type) {
+  if (type == "coord") {
+    textbox.value = roundCoord(parseFloat(textbox.value));
+  }
+  else if (type == "dim") {
+    textbox.value = parseInt(textbox.value);
+  }
+}
+
+function drawPreview() {
+  let canvas = document.getElementById("is_mbrotPreview");
+  let context = canvas.getContext("2d");
+
+  if (document.getElementById("is_img_width").value /
+      document.getElementById("is_img_height").value
+        < 80 / 50) {
+    canvas.height = 50;
+    canvas.width = canvas.height *
+      document.getElementById("is_img_width").value /
+      document.getElementById("is_img_height").value;
+  }
+  else {
+    canvas.width = 80;
+    canvas.height = canvas.width *
+      document.getElementById("is_imgHeight").value /
+      document.getElementById("is_imgWidth").value;
+  }
+
+  let Cr_min = parseFloat(document.getElementById("is_Cr_min").value);
+  let Cr_max = parseFloat(document.getElementById("is_Cr_max").value);
+  if ((Cr_min < -2) || (Cr_min > 2) ||
+      (Cr_max < -2) || (Cr_max > 2) || (Cr_min >= Cr_max)) {
+    Cr_min = -2.0; Cr_max = 1.0;
+  }
+
+  let Ci_min = parseFloat(document.getElementById("is_Ci_min").value);
+  let Ci_max = parseFloat(document.getElementById("is_Ci_max").value);
+  if ((Ci_min < -2) || (Ci_min > 2) ||
+      (Ci_max < -2) || (Ci_max > 2) || (Ci_min >= Ci_max)) {
+    Ci_min = -2.0; Ci_max = 1.0;
+  }
+
+  let iterMax = getAdjustPref("iteration_max");
+  let algorithm = getAdjustPref("use_algorithm");
+
+  context.fillStyle = "rgba(255, 255, 255, 127)";
+  context.fillRect(0, 0, canvas.width, canvas.height);
+
+  let currentPalette = getAdjustPref("color_palette");
+  gColorPalette = getColorPalette(currentPalette);
+
+  drawLine(0, [Cr_min, Cr_max, Ci_min, Ci_max],
+              canvas, context, iterMax, algorithm);
+}
+
+function recalcCoord(coord, target) {
+  let othercoord = (coord == "Ci") ? "Cr" : "Ci";
+  let owndim = (coord == "Ci") ? "height" : "width";
+  let otherdim = (coord == "Ci") ? "width" : "height";
+  if (target == "scale") {
+    var myscale =
+      parseFloat(document.getElementById("is_" + coord + "_max").value) -
+      parseFloat(document.getElementById("is_" + coord + "_min").value);
+    document.getElementById("is_" + coord + "_scale").value = roundCoord(myscale);
+  }
+  else if (target == 'max') {
+    let mymax =
+      parseFloat(document.getElementById("is_" + coord + "_min").value) +
+      parseFloat(document.getElementById("is_" + coord + "_scale").value);
+    document.getElementById("is_" + coord + "_max").value = roundCoord(mymax);
+    var myscale = document.getElementById("is_" + coord + "_scale").value;
+  }
+  if (document.getElementById("is_syncProp").checked) {
+    let otherscale = myscale *
+      document.getElementById("is_img_" + otherdim).value /
+      document.getElementById("is_img_" + owndim).value;
+    document.getElementById("is_" + othercoord + "_scale").value = roundCoord(otherscale);
+    let othermax =
+      parseFloat(document.getElementById("is_" + othercoord + "_min").value) +
+      parseFloat(document.getElementById("is_" + othercoord + "_scale").value);
+    document.getElementById("is_" + othercoord + "_max").value = roundCoord(othermax);
+  }
+}
+
+function checkProportions() {
+  if (!document.getElementById("is_syncProp").checked) {
+    recalcCoord("Cr", "scale");
+  }
+}
+
+function roundCoord(floatval) {
+  // We should round to 10 decimals here or so
+  return parseFloat(floatval.toFixed(10));
+}
+
 /***** helper functions from external sources *****/
 
 // function below is based on http://developer.mozilla.org/en/docs/Code_snippets:Canvas