reset upload status when starting upload; load track in reverse order and once we...
[lantea.git] / js / map.js
index b5fe9e2a088607063160a1be0d54cbd252c3b09d..3cc00c5384c6fd6b97bbf00496a5d91404819f6b 100644 (file)
--- a/js/map.js
+++ b/js/map.js
@@ -89,74 +89,101 @@ function initMap() {
     }
   }
 
     }
   }
 
-  var loopCnt = 0;
-  var getPersistentPrefs = function() {
-    if (mainDB) {
-      gWaitCounter++;
-      gPrefs.get("position", function(aValue) {
-        if (aValue) {
-          gPos = aValue;
-          gWaitCounter--;
-        }
-      });
-      gWaitCounter++;
-      gPrefs.get("center_map", function(aValue) {
-        if (aValue === undefined)
-          document.getElementById("centerCheckbox").checked = true;
-        else
-          document.getElementById("centerCheckbox").checked = aValue;
-        setCentering(document.getElementById("centerCheckbox"));
-        gWaitCounter--;
-      });
-      gWaitCounter++;
-      gPrefs.get("tracking_enabled", function(aValue) {
-        if (aValue === undefined)
-          document.getElementById("trackCheckbox").checked = true;
-        else
-          document.getElementById("trackCheckbox").checked = aValue;
-        gWaitCounter--;
-      });
-      gWaitCounter++;
-      gTrackStore.getList(function(aTPoints) {
-        if (gDebug)
-          console.log(aTPoints.length + " points loaded.");
-        if (aTPoints.length) {
-          gTrack = aTPoints;
-        }
-        gWaitCounter--;
-      });
-    }
-    else
-      setTimeout(getPersistentPrefs, 100);
-    loopCnt++;
-    if (loopCnt > 50) {
-      console.log("Loading prefs failed.");
+  console.log("map vars set, loading prefs...");
+  loadPrefs();
+}
+
+function loadPrefs(aEvent) {
+  if (aEvent && aEvent.type == "prefs-step") {
+    console.log("wait: " + gWaitCounter);
+    if (gWaitCounter == 0) {
+      gAction.removeEventListener(aEvent.type, loadPrefs, false);
+      gMapPrefsLoaded = true;
+      console.log("prefs loaded.");
+
+      gTrackCanvas.addEventListener("mouseup", mapEvHandler, false);
+      gTrackCanvas.addEventListener("mousemove", mapEvHandler, false);
+      gTrackCanvas.addEventListener("mousedown", mapEvHandler, false);
+      gTrackCanvas.addEventListener("mouseout", mapEvHandler, false);
+
+      gTrackCanvas.addEventListener("touchstart", mapEvHandler, false);
+      gTrackCanvas.addEventListener("touchmove", mapEvHandler, false);
+      gTrackCanvas.addEventListener("touchend", mapEvHandler, false);
+      gTrackCanvas.addEventListener("touchcancel", mapEvHandler, false);
+      gTrackCanvas.addEventListener("touchleave", mapEvHandler, false);
+
+      gTrackCanvas.addEventListener("wheel", mapEvHandler, false);
+
+      document.getElementById("body").addEventListener("keydown", mapEvHandler, false);
+
+      document.getElementById("copyright").innerHTML =
+          gMapStyles[gActiveMap].copyright;
+
+      gLoadingTile = new Image();
+      gLoadingTile.src = "style/loading.png";
+      gLoadingTile.onload = function() {
+        var throwEv = new CustomEvent("mapinit-done");
+        gAction.dispatchEvent(throwEv);
+      };
     }
     }
-  };
-  getPersistentPrefs();
-
-  gTrackCanvas.addEventListener("mouseup", mapEvHandler, false);
-  gTrackCanvas.addEventListener("mousemove", mapEvHandler, false);
-  gTrackCanvas.addEventListener("mousedown", mapEvHandler, false);
-  gTrackCanvas.addEventListener("mouseout", mapEvHandler, false);
-
-  gTrackCanvas.addEventListener("touchstart", mapEvHandler, false);
-  gTrackCanvas.addEventListener("touchmove", mapEvHandler, false);
-  gTrackCanvas.addEventListener("touchend", mapEvHandler, false);
-  gTrackCanvas.addEventListener("touchcancel", mapEvHandler, false);
-  gTrackCanvas.addEventListener("touchleave", mapEvHandler, false);
-
-  // XXX deprecated? see https://groups.google.com/forum/?fromgroups#!topic/mozilla.dev.planning/kuhrORubaRY[1-25]
-  gTrackCanvas.addEventListener("DOMMouseScroll", mapEvHandler, false);
-  gTrackCanvas.addEventListener("mousewheel", mapEvHandler, false);
-
-  document.getElementById("copyright").innerHTML =
-      gMapStyles[gActiveMap].copyright;
-
-  gLoadingTile = new Image();
-  gLoadingTile.src = "style/loading.png";
-  gWaitCounter++;
-  gLoadingTile.onload = function() { gWaitCounter--; };
+  }
+  else {
+    if (aEvent)
+      gAction.removeEventListener(aEvent.type, loadPrefs, false);
+    gAction.addEventListener("prefs-step", loadPrefs, false);
+    gWaitCounter++;
+    gPrefs.get("position", function(aValue) {
+      if (aValue) {
+        gPos = aValue;
+      }
+      gWaitCounter--;
+      var throwEv = new CustomEvent("prefs-step");
+      gAction.dispatchEvent(throwEv);
+    });
+    gWaitCounter++;
+    gPrefs.get("center_map", function(aValue) {
+      if (aValue === undefined)
+        document.getElementById("centerCheckbox").checked = true;
+      else
+        document.getElementById("centerCheckbox").checked = aValue;
+      setCentering(document.getElementById("centerCheckbox"));
+      gWaitCounter--;
+      var throwEv = new CustomEvent("prefs-step");
+      gAction.dispatchEvent(throwEv);
+    });
+    gWaitCounter++;
+    gPrefs.get("tracking_enabled", function(aValue) {
+      if (aValue === undefined)
+        document.getElementById("trackCheckbox").checked = true;
+      else
+        document.getElementById("trackCheckbox").checked = aValue;
+      gWaitCounter--;
+      var throwEv = new CustomEvent("prefs-step");
+      gAction.dispatchEvent(throwEv);
+    });
+    gWaitCounter++;
+    var trackLoadStarted = false;
+    gTrackStore.getListStepped(function(aTPoint) {
+      if (aTPoint) {
+        // Add in front and return new length.
+        var tracklen = gTrack.unshift(aTPoint);
+        // Redraw track every 100 values (initial paint will do first anyhow).
+        if (tracklen % 100 == 0)
+          drawTrack();
+      }
+      else {
+        // Last point received.
+        drawTrack();
+      }
+      if (!trackLoadStarted) {
+        // We have the most recent point, if present, rest will load async.
+        trackLoadStarted = true;
+        gWaitCounter--;
+        var throwEv = new CustomEvent("prefs-step");
+        gAction.dispatchEvent(throwEv);
+      }
+    });
+  }
 }
 
 function resizeAndDraw() {
 }
 
 function resizeAndDraw() {
@@ -189,6 +216,14 @@ function zoomOut() {
   }
 }
 
   }
 }
 
+function zoomTo(aTargetLevel) {
+  aTargetLevel = parseInt(aTargetLevel);
+  if (aTargetLevel >= 0 && aTargetLevel <= gMaxZoom) {
+    gPos.z = aTargetLevel;
+    drawMap();
+  }
+}
+
 function gps2xy(aLatitude, aLongitude) {
   var maxZoomFactor = Math.pow(2, gMaxZoom) * gTileSize;
   var convLat = aLatitude * Math.PI / 180;
 function gps2xy(aLatitude, aLongitude) {
   var maxZoomFactor = Math.pow(2, gMaxZoom) * gTileSize;
   var convLat = aLatitude * Math.PI / 180;
@@ -331,6 +366,10 @@ function drawMap(aPixels, aOverdraw) {
       }
     }
   }
       }
     }
   }
+  drawTrack();
+}
+
+function drawTrack() {
   gLastDrawnPoint = null;
   gCurPosMapCache = undefined;
   gTrackContext.clearRect(0, 0, gTrackCanvas.width, gTrackCanvas.height);
   gLastDrawnPoint = null;
   gCurPosMapCache = undefined;
   gTrackContext.clearRect(0, 0, gTrackCanvas.width, gTrackCanvas.height);
@@ -419,8 +458,12 @@ var mapEvHandler = {
   handleEvent: function(aEvent) {
     var touchEvent = aEvent.type.indexOf('touch') != -1;
 
   handleEvent: function(aEvent) {
     var touchEvent = aEvent.type.indexOf('touch') != -1;
 
-    // Bail out on unwanted map moves, but not zoom-changing events.
-    if (aEvent.type != "DOMMouseScroll" && aEvent.type != "mousewheel") {
+    // Bail out if the event is happening on an input.
+    if (aEvent.target.tagName.toLowerCase() == "input")
+      return;
+
+    // Bail out on unwanted map moves, but not zoom or keyboard events.
+    if (aEvent.type.indexOf("mouse") === 0 || aEvent.type.indexOf("touch") === 0) {
       // Bail out if this is neither a touch nor left-click.
       if (!touchEvent && aEvent.button != 0)
         return;
       // Bail out if this is neither a touch nor left-click.
       if (!touchEvent && aEvent.button != 0)
         return;
@@ -461,13 +504,20 @@ var mapEvHandler = {
           var dY = y - gLastMouseY;
           gPos.x -= dX * gZoomFactor;
           gPos.y -= dY * gZoomFactor;
           var dY = y - gLastMouseY;
           gPos.x -= dX * gZoomFactor;
           gPos.y -= dY * gZoomFactor;
-          var mapData = gMapContext.getImageData(0, 0, gMapCanvas.width, gMapCanvas.height);
-          gMapContext.clearRect(0, 0, gMapCanvas.width, gMapCanvas.height);
-          gMapContext.putImageData(mapData, dX, dY);
-          drawMap({left: (dX > 0) ? dX : 0,
-                   right: (dX < 0) ? -dX : 0,
-                   top: (dY > 0) ? dY : 0,
-                   bottom: (dY < 0) ? -dY : 0});
+          if (true) { // use optimized path
+            var mapData = gMapContext.getImageData(0, 0,
+                                                   gMapCanvas.width,
+                                                   gMapCanvas.height);
+            gMapContext.clearRect(0, 0, gMapCanvas.width, gMapCanvas.height);
+            gMapContext.putImageData(mapData, dX, dY);
+            drawMap({left: (dX > 0) ? dX : 0,
+                     right: (dX < 0) ? -dX : 0,
+                     top: (dY > 0) ? dY : 0,
+                     bottom: (dY < 0) ? -dY : 0});
+          }
+          else {
+            drawMap(false, true);
+          }
           showUI();
         }
         gLastMouseX = x;
           showUI();
         }
         gLastMouseX = x;
@@ -483,17 +533,13 @@ var mapEvHandler = {
       case "touchleave":
         //gDragging = false;
         break;
       case "touchleave":
         //gDragging = false;
         break;
-      case "DOMMouseScroll":
-      case "mousewheel":
-        var delta = 0;
-        if (aEvent.wheelDelta) {
-          delta = aEvent.wheelDelta / 120;
-          if (window.opera)
-            delta = -delta;
-        }
-        else if (aEvent.detail) {
-          delta = -aEvent.detail / 3;
-        }
+      case "wheel":
+        // If we'd want pixels, we'd need to calc up using aEvent.deltaMode.
+        // See https://developer.mozilla.org/en-US/docs/Mozilla_event_reference/wheel
+
+        // Only accept (non-null) deltaY values
+        if (!aEvent.deltaY)
+          break;
 
         // Debug output: "coordinates" of the point the mouse was over.
         /*
 
         // Debug output: "coordinates" of the point the mouse was over.
         /*
@@ -506,7 +552,7 @@ var mapEvHandler = {
                     pt2Coord.x + "/" + pt2Coord.y);
         */
 
                     pt2Coord.x + "/" + pt2Coord.y);
         */
 
-        var newZoomLevel = gPos.z + (delta > 0 ? 1 : -1);
+        var newZoomLevel = gPos.z + (aEvent.deltaY < 0 ? 1 : -1);
         if ((newZoomLevel >= 0) && (newZoomLevel <= gMaxZoom)) {
           // Calculate new center of the map - same point stays under the mouse.
           // This means that the pixel distance between the old center and point
         if ((newZoomLevel >= 0) && (newZoomLevel <= gMaxZoom)) {
           // Calculate new center of the map - same point stays under the mouse.
           // This means that the pixel distance between the old center and point
@@ -519,12 +565,94 @@ var mapEvHandler = {
           gPos.x -= (x - gMapCanvas.width / 2) * (newZoomFactor - gZoomFactor);
           gPos.y -= (y - gMapCanvas.height / 2) * (newZoomFactor - gZoomFactor);
 
           gPos.x -= (x - gMapCanvas.width / 2) * (newZoomFactor - gZoomFactor);
           gPos.y -= (y - gMapCanvas.height / 2) * (newZoomFactor - gZoomFactor);
 
-          if (delta > 0)
+          if (aEvent.deltaY < 0)
             zoomIn();
             zoomIn();
-          else if (delta < 0)
+          else
             zoomOut();
         }
         break;
             zoomOut();
         }
         break;
+      case "keydown":
+        // Allow keyboard control to move and zoom the map.
+        // Should use aEvent.key instead of aEvent.which but needs bug 680830.
+        // See https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/keydown
+        var dX = 0;
+        var dY = 0;
+        switch (aEvent.which) {
+          case 39: // right
+            dX = -gTileSize / 2;
+          break;
+          case 37: // left
+            dX = gTileSize / 2;
+          break;
+          case 38: // up
+            dY = gTileSize / 2;
+          break;
+          case 40: // down
+            dY = -gTileSize / 2;
+          break;
+          case 87: // w
+          case 107: // + (numpad)
+          case 171: // + (normal key)
+            zoomIn();
+          break;
+          case 83: // s
+          case 109: // - (numpad)
+          case 173: // - (normal key)
+            zoomOut();
+          break;
+          case 48: // 0
+          case 49: // 1
+          case 50: // 2
+          case 51: // 3
+          case 52: // 4
+          case 53: // 5
+          case 54: // 6
+          case 55: // 7
+          case 56: // 8
+            zoomTo(aEvent.which - 38);
+          break;
+          case 57: // 9
+            zoomTo(9);
+          break;
+          case 96: // 0 (numpad)
+          case 97: // 1 (numpad)
+          case 98: // 2 (numpad)
+          case 99: // 3 (numpad)
+          case 100: // 4 (numpad)
+          case 101: // 5 (numpad)
+          case 102: // 6 (numpad)
+          case 103: // 7 (numpad)
+          case 104: // 8 (numpad)
+            zoomTo(aEvent.which - 86);
+          break;
+          case 105: // 9 (numpad)
+            zoomTo(9);
+          break;
+          default: // not supported
+            console.log("key not supported: " + aEvent.which);
+          break;
+        }
+
+        // Move if needed.
+        if (dX || dY) {
+          gPos.x -= dX * gZoomFactor;
+          gPos.y -= dY * gZoomFactor;
+          if (true) { // use optimized path
+            var mapData = gMapContext.getImageData(0, 0,
+                                                   gMapCanvas.width,
+                                                   gMapCanvas.height);
+            gMapContext.clearRect(0, 0, gMapCanvas.width, gMapCanvas.height);
+            gMapContext.putImageData(mapData, dX, dY);
+            drawMap({left: (dX > 0) ? dX : 0,
+                     right: (dX < 0) ? -dX : 0,
+                     top: (dY > 0) ? dY : 0,
+                     bottom: (dY < 0) ? -dY : 0});
+          }
+          else {
+            drawMap(false, true);
+          }
+        }
+        break;
     }
   }
 };
     }
   }
 };
@@ -643,42 +771,47 @@ function endTracking() {
 function clearTrack() {
   gTrack = [];
   gTrackStore.clear();
 function clearTrack() {
   gTrack = [];
   gTrackStore.clear();
-  drawMap();
+  drawTrack();
 }
 
 var gTileService = {
   objStore: "tilecache",
 
 }
 
 var gTileService = {
   objStore: "tilecache",
 
+  ageLimit: 14 * 86400 * 1000, // 2 weeks (in ms)
+
   get: function(aStyle, aCoords, aCallback) {
     var norm = normalizeCoords(aCoords);
     var dbkey = aStyle + "::" + norm.x + "," + norm.y + "," + norm.z;
     this.getDBCache(dbkey, function(aResult, aEvent) {
       if (aResult) {
         // We did get a cached object.
   get: function(aStyle, aCoords, aCallback) {
     var norm = normalizeCoords(aCoords);
     var dbkey = aStyle + "::" + norm.x + "," + norm.y + "," + norm.z;
     this.getDBCache(dbkey, function(aResult, aEvent) {
       if (aResult) {
         // We did get a cached object.
-        // TODO: Look at the timestamp and trigger a reload when it's too old.
         aCallback(aResult.image, aStyle, aCoords);
         aCallback(aResult.image, aStyle, aCoords);
+        // Look at the timestamp and return if it's not too old.
+        if (aResult.timestamp + gTileService.ageLimit > Date.now())
+          return;
+        // Reload cached tile otherwise.
+        var oldDate = new Date(aResult.timestamp);
+        console.log("reload cached tile: " + dbkey + " - " + oldDate.toUTCString());
       }
       }
-      else {
-        // Retrieve image from the web and store it in the cache.
-        var XHR = new XMLHttpRequest();
-        XHR.open("GET",
-                 gMapStyles[aStyle].url
-                   .replace("{x}", norm.x)
-                   .replace("{y}", norm.y)
-                   .replace("{z}", norm.z)
-                   .replace("[a-c]", String.fromCharCode(97 + Math.floor(Math.random() * 2)))
-                   .replace("[1-4]", 1 + Math.floor(Math.random() * 3)),
-                 true);
-        XHR.responseType = "blob";
-        XHR.addEventListener("load", function () {
-          if (XHR.status === 200) {
-            var blob = XHR.response;
-            gTileService.setDBCache(dbkey, {image: blob, timestamp: Date.now()});
-            aCallback(blob, aStyle, aCoords);
-          }
-        }, false);
-        XHR.send();
-      }
+      // Retrieve image from the web and store it in the cache.
+      var XHR = new XMLHttpRequest();
+      XHR.open("GET",
+                gMapStyles[aStyle].url
+                  .replace("{x}", norm.x)
+                  .replace("{y}", norm.y)
+                  .replace("{z}", norm.z)
+                  .replace("[a-c]", String.fromCharCode(97 + Math.floor(Math.random() * 2)))
+                  .replace("[1-4]", 1 + Math.floor(Math.random() * 3)),
+                true);
+      XHR.responseType = "blob";
+      XHR.addEventListener("load", function () {
+        if (XHR.status === 200) {
+          var blob = XHR.response;
+          aCallback(blob, aStyle, aCoords);
+          gTileService.setDBCache(dbkey, {image: blob, timestamp: Date.now()});
+        }
+      }, false);
+      XHR.send();
     });
   },
 
     });
   },
 
@@ -731,5 +864,23 @@ var gTileService = {
       if (aCallback)
         aCallback(success, event);
     }
       if (aCallback)
         aCallback(success, event);
     }
+  },
+
+  clearDB: function(aCallback) {
+    if (!mainDB)
+      return;
+    var success = false;
+    var transaction = mainDB.transaction([this.objStore], "readwrite");
+    var request = transaction.objectStore(this.objStore).clear();
+    request.onsuccess = function(event) {
+      success = true;
+      if (aCallback)
+        aCallback(success, event);
+    };
+    request.onerror = function(event) {
+      // Errors can be handled here.
+      if (aCallback)
+        aCallback(success, event);
+    }
   }
 };
   }
 };