Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNick Doiron <ndoiron@mapmeld.com>2011-05-10 20:18:53 (GMT)
committer Nick Doiron <ndoiron@mapmeld.com>2011-05-10 20:18:53 (GMT)
commit8d981ffc647b76b2b8b6f4d24aba0728d15dfffb (patch)
treed1ff473ddaf0247da904145252d1154de27dc6f6
Uploading OfflineMap with XO-1.5 fix
-rw-r--r--MANIFEST43
-rw-r--r--Makefile35
-rw-r--r--NEWS5
-rw-r--r--_camera.c276
-rw-r--r--_camera.obin0 -> 35172 bytes
-rw-r--r--_camera.sobin0 -> 31804 bytes
-rw-r--r--activity/activity-map.svg20
-rw-r--r--activity/activity.info9
-rw-r--r--button.py79
-rw-r--r--color.py75
-rw-r--r--constants.py127
-rw-r--r--filepicker.py46
-rw-r--r--gplay.py154
-rw-r--r--icons/add-icon.svg26
-rw-r--r--icons/corner-info.svg15
-rw-r--r--icons/delete-icon.svg25
-rw-r--r--icons/map-icon-croseE.svg13
-rw-r--r--icons/map-icon-croseN.svg12
-rw-r--r--icons/map-icon-croseS.svg13
-rw-r--r--icons/map-icon-croseW.svg12
-rw-r--r--icons/map-icon-zoomIn.svg95
-rw-r--r--icons/map-icon-zoomOut.svg12
-rw-r--r--icons/measure-icon.svg12
-rw-r--r--icons/save-search.svg63
-rw-r--r--icons/static-icon.svg16
-rw-r--r--icons/tool-marquee-freeform.svg18
-rw-r--r--icons/tool-polygon.svg59
-rw-r--r--icons/tool-shape-line.svg11
-rw-r--r--icons/topo-icon.svg21
-rw-r--r--icons/web-icon.svg14
-rw-r--r--idlethread.py175
-rw-r--r--instance.py45
-rw-r--r--locale/es/LC_MESSAGES/org.laptop.map.mobin0 -> 1087 bytes
-rw-r--r--locale/es/activity.linfo2
-rw-r--r--locale/mn/LC_MESSAGES/org.laptop.map.mobin0 -> 1200 bytes
-rw-r--r--locale/mn/activity.linfo2
-rw-r--r--logic.py272
-rw-r--r--map.py1675
-rw-r--r--mapviewer/MediaMarker.js21
-rw-r--r--mapviewer/blue_map_dot.pngbin0 -> 308 bytes
-rw-r--r--mapviewer/magenta_map_dot.pngbin0 -> 303 bytes
-rw-r--r--mapviewer/mapCenter.html631
-rw-r--r--mapviewer/orange_map_dot.pngbin0 -> 315 bytes
-rw-r--r--model.py149
-rw-r--r--p5.py86
-rw-r--r--photocanvas.py65
-rw-r--r--po/es.po99
-rw-r--r--po/map.pot113
-rw-r--r--po/mn.po99
-rw-r--r--recorded.py82
-rw-r--r--result.py25
-rw-r--r--savedmap.py41
-rw-r--r--serialize.py206
-rw-r--r--server.py69
-rw-r--r--setup.py25
-rw-r--r--tray.py207
-rw-r--r--utils.py110
-rw-r--r--webviewer.py148
58 files changed, 5653 insertions, 0 deletions
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..82cff9f
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,43 @@
+_camera.c
+logic.py
+server.py
+setup.py
+idlethread.py
+webviewer.py
+NEWS
+instance.py
+constants.py
+tray.py
+_camera.so
+recorded.py
+model.py
+gplay.py
+savedmap.py
+button.py
+utils.py
+p5.py
+filepicker.py
+result.py
+color.py
+serialize.py
+map.py
+_camera.o
+photocanvas.py
+activity/activity.info
+activity/activity-map.svg
+icons/map-icon-zoomIn.svg
+icons/map-icon-croseS.svg
+icons/map-icon-croseE.svg
+icons/map-icon-croseW.svg
+icons/add-icon.svg
+icons/delete-icon.svg
+icons/corner-info.svg
+icons/map-icon-croseN.svg
+icons/topo-icon.svg
+icons/map-icon-zoomOut.svg
+icons/save-search.svg
+icons/web-icon.svg
+icons/measure-icon.svg
+icons/static-icon.svg
+po/map.pot
+Makefile
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3cd0564
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+PYVER=`python -c "import sys; print '%s.%s' % (sys.version_info[0], sys.version_info[1])"`
+PYTHON=python$(PYVER)
+
+GLIB_INCLUDES=`pkg-config --cflags glib-2.0`
+GLIB_LIBS=`pkg-config --libs glib-2.0`
+
+GTK_INCLUDES=`pkg-config --cflags gtk+-2.0`
+GTK_LIBS=`pkg-config --libs gtk+-2.0`
+
+PYGTK_INCLUDES=`pkg-config --cflags pygtk-2.0`
+PYGTK_LIBS=`pkg-config --libs pygtk-2.0`
+
+CAIRO_INCLUDES=`pkg-config --cflags cairo`
+CAIRO_LIBS=`pkg-config --libs cairo`
+
+PYCAIRO_INCLUDES=`pkg-config --cflags pycairo`
+PYCAIRO_LIBS=`pkg-config --libs pycairo`
+
+INCLUDES=-I. -I/usr/include/${PYTHON} ${GLIB_INCLUDES} ${PYGTK_INCLUDES} ${CAIRO_INCLUDES} ${PYCAIRO_INCLUDES} ${GTK_INCLUDES}
+ARCHFLAGS=-m32 -march=i386 -mtune=generic
+OPTFLAGS=-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -fasynchronous-unwind-tables
+CFLAGS=-g -fPIC -DPIC
+LDFLAGS=-shared -nostdlib -Wl,--export-dynamic -pthread
+
+all: build link
+
+build:
+ gcc ${INCLUDES} ${ARCHFLAGS} ${OPTFLAGS} ${CFLAGS} -c _camera.c -o _camera.o
+
+link:
+ g++ ${LDFLAGS} _camera.o ${GLIB_LIBS} ${PYGTK_LIBS} ${CAIRO_LIBS} ${PYCAIRO_LIBS} ${GTK_LIBS} -Wl,-soname -Wl,_camera.so -o _camera.so
+
+clean:
+ @find -name "*.o" -exec rm {} \;
+ @find -name "*.so" -exec rm {} \;
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..8ebe0c3
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,5 @@
+1
+* init (jedierikb)
+
+2
+* 8.2 re-release( jedierikb) \ No newline at end of file
diff --git a/_camera.c b/_camera.c
new file mode 100644
index 0000000..694f0a4
--- /dev/null
+++ b/_camera.c
@@ -0,0 +1,276 @@
+#include <Python.h>
+
+#include "pycairo.h"
+#include <glib.h>
+
+#include <pygobject.h>
+
+static PyTypeObject *_PyGObject_Type;
+#define PyGObject_Type (*_PyGObject_Type)
+Pycairo_CAPI_t *Pycairo_CAPI;
+static PyTypeObject *_PyGdkPixbuf_Type;
+#define PyGdkPixbuf_Type (*_PyGdkPixbuf_Type)
+
+#include <cairo.h>
+#include <gdk/gdkpixbuf.h>
+#include <gdk/gdkpixmap.h>
+#include <cairo-xlib.h>
+#include <gdk/gdkcairo.h>
+
+static cairo_surface_t *
+_cairo_surface_from_pixbuf (GdkPixbuf *pixbuf)
+{
+/* Ripped from GooCanvas */
+ gint width = gdk_pixbuf_get_width (pixbuf);
+ gint height = gdk_pixbuf_get_height (pixbuf);
+ guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf);
+ int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ int n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+ guchar *cairo_pixels;
+ cairo_format_t format;
+ cairo_surface_t *surface;
+ static const cairo_user_data_key_t key;
+ int j;
+
+ if (n_channels == 3)
+ format = CAIRO_FORMAT_RGB24;
+ else
+ format = CAIRO_FORMAT_ARGB32;
+
+ cairo_pixels = g_malloc (4 * width * height);
+ surface = cairo_image_surface_create_for_data ((unsigned char *)cairo_pixels,
+ format,
+ width, height, 4 * width);
+ cairo_surface_set_user_data (surface, &key,
+ cairo_pixels, (cairo_destroy_func_t)g_free);
+
+ for (j = height; j; j--)
+ {
+ guchar *p = gdk_pixels;
+ guchar *q = cairo_pixels;
+
+ if (n_channels == 3)
+ {
+ guchar *end = p + 3 * width;
+
+ while (p < end)
+ {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ q[0] = p[2];
+ q[1] = p[1];
+ q[2] = p[0];
+#else
+ q[1] = p[0];
+ q[2] = p[1];
+ q[3] = p[2];
+#endif
+ p += 3;
+ q += 4;
+ }
+ }
+ else
+ {
+ guchar *end = p + 4 * width;
+ guint t1,t2,t3;
+
+#define MULT(d,c,a,t) G_STMT_START { t = c * a; d = ((t >> 8) + t) >> 8; } G_STMT_END
+
+ while (p < end)
+ {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ MULT(q[0], p[2], p[3], t1);
+ MULT(q[1], p[1], p[3], t2);
+ MULT(q[2], p[0], p[3], t3);
+ q[3] = p[3];
+#else
+ q[0] = p[3];
+ MULT(q[1], p[0], p[3], t1);
+ MULT(q[2], p[1], p[3], t2);
+ MULT(q[3], p[2], p[3], t3);
+#endif
+
+ p += 4;
+ q += 4;
+ }
+
+#undef MULT
+ }
+
+ gdk_pixels += gdk_rowstride;
+ cairo_pixels += 4 * width;
+ }
+
+ return surface;
+}
+
+static PyObject*
+_wrap_camera_cairo_surface_from_gdk_pixbuf(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "pixbuf", NULL };
+ PyGObject *child;
+ cairo_surface_t *surface;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O!:camera.cairo_surface_from_gdk_pixbuf", kwlist, &PyGdkPixbuf_Type, &child))
+ return NULL;
+
+ surface = _cairo_surface_from_pixbuf(GDK_PIXBUF (child->obj));
+ if (surface == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "surface could not be converted");
+ return NULL;
+ }
+
+ return PycairoSurface_FromSurface(surface, NULL);
+}
+
+static GdkPixbuf *
+_pixbuf_from_cairo_surface (cairo_surface_t * sf)
+{
+ GdkPixmap * pixmap;
+ GdkPixbuf * pixbuf = NULL;
+ cairo_surface_type_t type;
+ gint width = 0, height = 0, depth = 0;
+ int format;
+ cairo_t * cr;
+ GdkColormap * cm;
+
+ type = cairo_surface_get_type (sf);
+ switch (type) {
+ case CAIRO_SURFACE_TYPE_IMAGE:
+ width = cairo_image_surface_get_width (sf);
+ height = cairo_image_surface_get_height (sf);
+ format = cairo_image_surface_get_format (sf);
+ if (format == CAIRO_FORMAT_ARGB32)
+ depth = 32;
+ else if (format == CAIRO_FORMAT_RGB24)
+ depth = 24;
+ else if (format == CAIRO_FORMAT_A8)
+ depth = 8;
+ else if (format == CAIRO_FORMAT_A1)
+ depth = 1;
+ else if (format == CAIRO_FORMAT_RGB16_565)
+ depth = 16;
+ break;
+ case CAIRO_SURFACE_TYPE_XLIB:
+ width = cairo_xlib_surface_get_width (sf);
+ height = cairo_xlib_surface_get_height (sf);
+ depth = cairo_xlib_surface_get_depth (sf);
+ break;
+ default:
+ break;
+ }
+ if (!depth)
+ return NULL;
+
+ pixmap = gdk_pixmap_new (NULL, width, height, depth);
+ if (!pixmap)
+ return NULL;
+
+ cr = gdk_cairo_create (pixmap);
+ if (!cr)
+ goto release_pixmap;
+
+ cairo_set_source_surface (cr, sf, 0, 0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+
+ cm = gdk_colormap_get_system ();
+ pixbuf = gdk_pixbuf_get_from_drawable (NULL, pixmap, cm, 0, 0, 0, 0, -1, -1);
+
+release_pixmap:
+ gdk_pixmap_unref (pixmap);
+
+ return pixbuf;
+}
+
+
+static PyObject*
+_wrap_camera_gdk_pixbuf_from_cairo_surface(PyGObject *self, PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "surface", NULL };
+ PyGObject *child;
+ GdkPixbuf * pixbuf;
+ PyTypeObject *type = &PycairoSurface_Type;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,"O!:camera.gdk_pixbuf_from_cairo_surface", kwlist, type, &child))
+ return NULL;
+
+ pixbuf = _pixbuf_from_cairo_surface((cairo_surface_t *)(child->obj));
+ if (pixbuf == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "pixbuf could not be converted");
+ return NULL;
+ }
+
+ return pygobject_new ((GObject *) pixbuf);
+}
+
+const PyMethodDef py_camera_functions[] = {
+ { "cairo_surface_from_gdk_pixbuf", (PyCFunction)_wrap_camera_cairo_surface_from_gdk_pixbuf,
+ METH_VARARGS|METH_KEYWORDS, NULL },
+ { "gdk_pixbuf_from_cairo_surface", (PyCFunction)_wrap_camera_gdk_pixbuf_from_cairo_surface,
+ METH_VARARGS|METH_KEYWORDS, NULL },
+ { NULL, NULL, 0, NULL }
+};
+
+
+/* ----------- enums and flags ----------- */
+
+void
+py_sugar_add_constants(PyObject *module, const gchar *strip_prefix)
+{
+}
+
+/* initialise stuff extension classes */
+void
+py_camera_register_classes(PyObject *d)
+{
+ PyObject *module;
+
+ if ((module = PyImport_ImportModule("gobject")) != NULL) {
+ _PyGObject_Type = (PyTypeObject *)PyObject_GetAttrString(module, "GObject");
+ if (_PyGObject_Type == NULL) {
+ PyErr_SetString(PyExc_ImportError,
+ "cannot import name GObject from gobject");
+ return ;
+ }
+ _PyGObject_Type = (PyTypeObject *)PyObject_GetAttrString(module, "GObject");
+ if (_PyGObject_Type == NULL) {
+ PyErr_SetString(PyExc_ImportError,
+ "cannot import name GObject from gobject");
+ return ;
+ }
+ } else {
+ PyErr_SetString(PyExc_ImportError,
+ "could not import gobject");
+ return ;
+ }
+ if ((module = PyImport_ImportModule("gtk.gdk")) != NULL) {
+ _PyGdkPixbuf_Type = (PyTypeObject *)PyObject_GetAttrString(module, "Pixbuf");
+ if (_PyGdkPixbuf_Type == NULL) {
+ PyErr_SetString(PyExc_ImportError,
+ "cannot import name Pixbuf from gtk.gdk");
+ return ;
+ }
+ } else {
+ PyErr_SetString(PyExc_ImportError,
+ "could not import gtk.gdk");
+ return ;
+ }
+
+ Pycairo_IMPORT;
+}
+
+DL_EXPORT(void)
+init_camera(void)
+{
+ PyObject *m, *d;
+
+ Pycairo_IMPORT;
+
+ m = Py_InitModule ("_camera", py_camera_functions);
+ d = PyModule_GetDict (m);
+
+ py_camera_register_classes (d);
+ if (PyErr_Occurred ()) {
+ Py_FatalError ("can't initialise module _camera");
+ }
+}
diff --git a/_camera.o b/_camera.o
new file mode 100644
index 0000000..e126c76
--- /dev/null
+++ b/_camera.o
Binary files differ
diff --git a/_camera.so b/_camera.so
new file mode 100644
index 0000000..c57a12b
--- /dev/null
+++ b/_camera.so
Binary files differ
diff --git a/activity/activity-map.svg b/activity/activity-map.svg
new file mode 100644
index 0000000..a768956
--- /dev/null
+++ b/activity/activity-map.svg
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"[
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="74.5px" height="74px" viewBox="0 0 74.5 74" enable-background="new 0 0 74.5 74" xml:space="preserve">
+<g>
+ <path fill="&fill_color;" d="M74.563,55.001c0,10.692-8.748,19.44-19.44,19.44H18.67c-10.692,0-19.44-8.748-19.44-19.44V18.882
+ c0-10.692,8.748-19.44,19.44-19.44h36.454c10.692,0,19.44,8.748,19.44,19.44V55.001z"/>
+</g>
+<g>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="37.124" y1="13.641" x2="37.124" y2="23.035"/>
+ <path fill="none" stroke="&stroke_color;" stroke-width="3.5" d="M43.213,29.176c0,3.406-2.762,6.168-6.166,6.168
+ c-3.406,0-6.167-2.762-6.167-6.168c0-3.404,2.761-6.141,6.167-6.141C40.451,23.035,43.213,25.771,43.213,29.176z M21.735,59.275
+ L34.298,34.5"/>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="27.88" y1="47.344" x2="54.881" y2="47.344"/>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="39.949" y1="34.434" x2="52.546" y2="59.275"/>
+</g>
+</svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..b25f509
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,9 @@
+[Activity]
+name = OfflineMaps
+activity_version = 2
+host_version = 1
+license = MIT
+icon = activity-map
+service_name = org.laptop.community.OfflineMaps
+class = map.Map
+show_launcher = 1 \ No newline at end of file
diff --git a/button.py b/button.py
new file mode 100644
index 0000000..d09791d
--- /dev/null
+++ b/button.py
@@ -0,0 +1,79 @@
+import gtk
+import os
+import gobject
+import rsvg
+import gc
+
+from sugar.graphics.palette import Palette
+from sugar.graphics.tray import TrayButton
+from sugar.graphics.icon import Icon
+from sugar.graphics import style
+from constants import Constants
+import utils
+
+class SavedButton(TrayButton, gobject.GObject):
+ def __init__(self, ui, savedmapData):
+ TrayButton.__init__(self)
+ self.ui = ui
+ self.data = savedmapData
+
+ img = self.getImg()
+ self.set_icon_widget( img )
+
+ self.setup_rollover_options()
+
+
+ def getImg( self ):
+ pb = gtk.gdk.pixbuf_new_from_file(self.data.thumbPath)
+
+ img = gtk.Image()
+ img.set_from_pixbuf(pb)
+ img.show()
+
+ return img
+
+
+ def setButtClickedId( self, id ):
+ self.BUTT_CLICKED_ID = id
+
+
+ def getButtClickedId( self ):
+ return self.BUTT_CLICKED_ID
+
+
+ def setup_rollover_options( self ):
+ palette = Palette( Constants.istrSavedMap )
+ self.set_palette(palette)
+
+ self.tag_menu_item = gtk.MenuItem( Constants.istrTagMap )
+ self.ACTIVATE_TAG_ID = self.tag_menu_item.connect('activate', self._tagCb)
+ palette.menu.append(self.tag_menu_item)
+ self.tag_menu_item.show()
+
+ self.rem_menu_item = gtk.MenuItem( Constants.istrRemove )
+ self.ACTIVATE_REMOVE_ID = self.rem_menu_item.connect('activate', self._itemRemoveCb)
+ palette.menu.append(self.rem_menu_item)
+ self.rem_menu_item.show()
+
+ self.copy_menu_item = gtk.MenuItem( Constants.istrCopyToClipboard )
+ self.ACTIVATE_COPY_ID = self.copy_menu_item.connect('activate', self._itemCopyToClipboardCb)
+ self.get_palette().menu.append(self.copy_menu_item)
+ self.copy_menu_item.show()
+
+
+ def cleanUp( self ):
+ self.rem_menu_item.disconnect( self.ACTIVATE_REMOVE_ID )
+ self.copy_menu_item.disconnect( self.ACTIVATE_COPY_ID )
+ self.tag_menu_item.disconnect( self.ACTIVATE_TAG_ID)
+
+
+ def _tagCb(self, widget):
+ self.ui.showSearchResultTags( self.data )
+
+
+ def _itemRemoveCb(self, widget):
+ self.ui.removeThumb( self.data )
+
+
+ def _itemCopyToClipboardCb(self, widget):
+ self.ui.copyToClipboard( self.data )
diff --git a/color.py b/color.py
new file mode 100644
index 0000000..8c8903e
--- /dev/null
+++ b/color.py
@@ -0,0 +1,75 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gtk
+
+class Color:
+
+ def __init__(self):
+ pass
+
+
+ def init_rgba(self, r, g, b, a):
+ self._ro = r
+ self._go = g
+ self._bo = b
+ self._ao = a;
+ self._r = self._ro / 255.0
+ self._g = self._go / 255.0
+ self._b = self._bo / 255.0
+ self._a = self._ao / 255.0
+
+ self._opaque = False
+ if (self._a == 1):
+ self.opaque = True
+
+ rgb_tup = ( self._ro, self._go, self._bo )
+ self.hex = self.rgb_to_hex( rgb_tup )
+ self.gColor = gtk.gdk.color_parse( self.hex )
+
+
+ def init_gdk(self, col):
+ self.init_hex( col.get_html() )
+
+
+ def init_hex(self, hex):
+ cTup = self.hex_to_rgb( hex )
+ self.init_rgba( cTup[0], cTup[1], cTup[2], 255 )
+
+
+ def get_int(self):
+ return int(self._a * 255) + (int(self._b * 255) << 8) + (int(self._g * 255) << 16) + (int(self._r * 255) << 24)
+
+
+ def rgb_to_hex(self, rgb_tup):
+ hexcolor = '#%02x%02x%02x' % rgb_tup
+ return hexcolor
+
+
+ def hex_to_rgb(self, h):
+ c = eval('0x' + h[1:])
+ r = (c >> 16) & 0xFF
+ g = (c >> 8) & 0xFF
+ b = c & 0xFF
+ return (int(r), int(g), int(b))
+
+
+ def getCanvasColor(self):
+ return str(self._ro) + "," + str(self._go) + "," + str(self._bo) \ No newline at end of file
diff --git a/constants.py b/constants.py
new file mode 100644
index 0000000..37e07ba
--- /dev/null
+++ b/constants.py
@@ -0,0 +1,127 @@
+import os
+import gtk
+from gettext import gettext as _
+
+import sugar.graphics.style
+from sugar.activity import activity
+from sugar import profile
+
+from instance import Instance
+from color import Color
+import utils
+
+class Constants:
+
+ VERSION = 2
+
+ SERVICE = "org.laptop.Map"
+ IFACE = SERVICE
+ PATH = "/org/laptop/Map"
+ activityId = None
+
+ gfxPath = os.path.join(activity.get_bundle_path(), "gfx")
+ htmlPath = os.path.join(activity.get_bundle_path(), "html")
+ iconsPath = os.path.join(activity.get_bundle_path(), "icons")
+
+ istrAnnotate = _("Edit")
+ istrSearch = _("Search")
+ istrSearchAddress = _('Find:')
+ istrSearchMedia = _("Tags:")
+ istrSaveSearch = _("Save Search")
+ istrConnecting = _("Connecting to Map Server")
+ istrZoomIn = _("Zoom In")
+ istrZoomOut = _("Zoom Out")
+ istrSaveSearch = _("Save")
+ istrDensity = _("Density")
+ istrSavedMap = _("Saved Map")
+ istrTagMap = _("Describe Map")
+ istrRemove = _("Remove Map")
+ istrCopyToClipboard = _("Copy to Clipboard")
+ istrAddMedia = _("Add Media")
+ istrAddInfo = _("Add Info")
+ istrDeleteMedia = _("Delete Media")
+ istrWebMedia = _("OSM import")
+ istrMeasure = _("Measure")
+ istrStaticMaps = _("Google import")
+ LineButton = _("Add Line")
+ PolyButton = _("Add Shape")
+ istrLatitude = _("Latitude:")
+ istrLongitude = ("Longitude:")
+ istrTags = ("Description:")
+
+ TYPE_PHOTO = 0
+ TYPE_VIDEO = 1
+
+ ui_dim_INSET = 4
+
+ recdAlbum = "map"
+ recdLat = "lat"
+ recdLng = "lng"
+ recdDatastoreId = "datastore"
+ recdInfo = "info"
+ recdMapItem = "mapItem"
+ recdSavedMapItem = "savedMap"
+ recdInfoMarker = "infoMarker"
+ recdIcon = "icon"
+ recdZoom = "zoom"
+ recdNotes = "notes"
+ recdMapImg = "mapImg"
+ recdTags = "tags"
+ recdMapThumbImg = "mapThumbImg"
+ recdRecdId = "recdId"
+ recdRecdLat = "recdLat"
+ recdRecdLng = "recdLng"
+ recdDensity = "density"
+ recdLine = "line"
+ lineID = "lid"
+ lineColor = "lcolor"
+ lineThick = "lthickness"
+ linePts = "lpts"
+ mapLat="lat"
+ mapLng="lng"
+ mapZoom="zoom"
+
+ colorBlack = Color()
+ colorBlack.init_rgba( 0, 0, 0, 255 )
+ colorWhite = Color()
+ colorWhite.init_rgba( 255, 255, 255, 255 )
+ colorRed = Color()
+ colorRed.init_rgba( 255, 0, 0, 255)
+ colorGreen = Color()
+ colorGreen.init_rgba( 0, 255, 0, 255)
+ colorBlue = Color()
+ colorBlue.init_rgba( 0, 0, 255, 255)
+ colorGrey = Color()
+ colorGrey.init_gdk( sugar.graphics.style.COLOR_BUTTON_GREY )
+ colorBg = colorBlack
+
+ def __init__( self, ca ):
+ self.__class__.activityId = ca._activity_id
+ self.__class__.northImgClr, self.__class__.northImgBw = self.loadSvgImg('map-icon-croseN.svg')
+ self.__class__.southImgClr, self.__class__.southImgBw = self.loadSvgImg('map-icon-croseS.svg')
+ self.__class__.eastImgClr, self.__class__.eastImgBw = self.loadSvgImg('map-icon-croseE.svg')
+ self.__class__.westImgClr, self.__class__.westImgBw = self.loadSvgImg('map-icon-croseW.svg')
+
+ infoOnSvgPath = os.path.join(self.__class__.iconsPath, 'corner-info.svg')
+ infoOnSvgFile = open(infoOnSvgPath, 'r')
+ infoOnSvgData = infoOnSvgFile.read()
+ self.__class__.infoOnSvg = utils.loadSvg(infoOnSvgData, None, None )
+ infoOnSvgFile.close()
+
+ def loadSvgImg(self, fileName):
+ SvgPath = os.path.join(self.__class__.iconsPath, fileName)
+ SvgFile = open(SvgPath, 'r')
+ SvgData = SvgFile.read()
+ SvgFile.close()
+
+ ColorSvg = utils.loadSvg(SvgData, Instance.colorStroke.hex, Instance.colorFill.hex)
+ ColorPixBuf = ColorSvg.get_pixbuf()
+ ColorImg = gtk.Image()
+ ColorImg.set_from_pixbuf(ColorPixBuf)
+
+ MonoSvg = utils.loadSvg(SvgData, self.__class__.colorGrey.hex, self.__class__.colorWhite.hex)
+ MonoPixBuf = MonoSvg.get_pixbuf()
+ MonoImg = gtk.Image()
+ MonoImg.set_from_pixbuf(MonoPixBuf)
+
+ return [ColorImg, MonoImg]
diff --git a/filepicker.py b/filepicker.py
new file mode 100644
index 0000000..cabc295
--- /dev/null
+++ b/filepicker.py
@@ -0,0 +1,46 @@
+# Copyright (C) 2007, One Laptop Per Child
+# Copyright (c) 2008, Media Modifications Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+
+from sugar.graphics.objectchooser import ObjectChooser
+
+class FilePicker:
+
+ def __init__(self):
+ pass
+
+
+ def show(self):
+ title = None
+ parent = None
+ file = None
+ job = None
+ chooser = ObjectChooser()
+
+ try:
+ result = chooser.run()
+ if result == gtk.RESPONSE_ACCEPT:
+ jobject = chooser.get_selected_object()
+ if (jobject and jobject.file_path):
+ job = jobject
+
+ finally:
+ chooser.destroy()
+ del chooser
+
+ return job
diff --git a/gplay.py b/gplay.py
new file mode 100644
index 0000000..3ceb353
--- /dev/null
+++ b/gplay.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+#look at jukeboxactivity.py
+
+import gtk
+import pygtk
+pygtk.require('2.0')
+import sys
+import pygst
+pygst.require('0.10')
+import gst
+import gst.interfaces
+import gobject
+import time
+gobject.threads_init()
+
+class Gplay:
+
+ def __init__(self):
+ self.window = None
+ self.players = []
+ self.playing = False
+ self.nextMovie()
+
+ def nextMovie(self):
+ if ( len(self.players) > 0 ):
+ self.playing = False
+ self.getPlayer().set_property("video-sink", None)
+ self.getPlayer().get_bus().disconnect(self.SYNC_ID)
+ self.getPlayer().get_bus().remove_signal_watch()
+ self.getPlayer().get_bus().disable_sync_message_emission()
+
+ player = gst.element_factory_make("playbin", "playbin")
+ xis = gst.element_factory_make("xvimagesink", "xvimagesink")
+ player.set_property("video-sink", xis)
+ bus = player.get_bus()
+ bus.enable_sync_message_emission()
+ bus.add_signal_watch()
+ self.SYNC_ID = bus.connect('sync-message::element', self.onSyncMessage)
+ self.players.append(player)
+
+
+ def getPlayer(self):
+ return self.players[len(self.players)-1]
+
+
+ def onSyncMessage(self, bus, message):
+ if message.structure is None:
+ return True
+ if message.structure.get_name() == 'prepare-xwindow-id':
+ self.window.set_sink(message.src)
+ message.src.set_property('force-aspect-ratio', True)
+ return True
+
+
+ def setLocation(self, location):
+ print("setLocation: ", location )
+ if (self.getPlayer().get_property('uri') == location):
+ self.seek(gst.SECOND*0)
+ return
+
+ self.getPlayer().set_state(gst.STATE_READY)
+ self.getPlayer().set_property('uri', location)
+ ext = location[len(location)-3:]
+ if (ext == "jpg"):
+ self.pause()
+ print("all played?")
+
+
+ def queryPosition(self):
+ "Returns a (position, duration) tuple"
+ try:
+ position, format = self.getPlayer().query_position(gst.FORMAT_TIME)
+ except:
+ position = gst.CLOCK_TIME_NONE
+
+ try:
+ duration, format = self.getPlayer().query_duration(gst.FORMAT_TIME)
+ except:
+ duration = gst.CLOCK_TIME_NONE
+
+ return (position, duration)
+
+
+ def seek(self, location):
+ event = gst.event_new_seek(1.0, gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE, gst.SEEK_TYPE_SET, location, gst.SEEK_TYPE_NONE, 0)
+ res = self.getPlayer().send_event(event)
+ if res:
+ self.getPlayer().set_new_stream_time(0L)
+
+
+ def pause(self):
+ self.playing = False
+ self.getPlayer().set_state(gst.STATE_PAUSED)
+
+
+ def play(self):
+ self.playing = True
+ self.getPlayer().set_state(gst.STATE_PLAYING)
+
+
+ def stop(self):
+ self.playing = False
+ self.getPlayer().set_state(gst.STATE_NULL)
+ self.nextMovie()
+
+
+ def get_state(self, timeout=1):
+ return self.getPlayer().get_state(timeout=timeout)
+
+
+ def is_playing(self):
+ return self.playing
+
+
+
+class PlayVideoWindow(gtk.EventBox):
+ def __init__(self, bgd):
+ gtk.EventBox.__init__(self)
+
+ self.imagesink = None
+
+ self.modify_bg( gtk.STATE_NORMAL, bgd )
+ self.modify_bg( gtk.STATE_INSENSITIVE, bgd )
+ self.unset_flags(gtk.DOUBLE_BUFFERED)
+ self.set_flags(gtk.APP_PAINTABLE)
+
+
+ def set_sink(self, sink):
+ if (self.imagesink != None):
+ assert self.window.xid
+ self.imagesink = None
+ del self.imagesink
+
+ self.imagesink = sink
+ self.imagesink.set_xwindow_id(self.window.xid) \ No newline at end of file
diff --git a/icons/add-icon.svg b/icons/add-icon.svg
new file mode 100644
index 0000000..81e7c87
--- /dev/null
+++ b/icons/add-icon.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="74.5px" height="74px" viewBox="0 0 74.5 74" enable-background="new 0 0 74.5 74" xml:space="preserve">
+<g>
+ <path d="M75.084,54.702c0,10.692-8.748,19.44-19.439,19.44H19.19c-10.692,0-19.44-8.748-19.44-19.44V18.583
+ c0-10.692,8.748-19.44,19.44-19.44h36.454c10.691,0,19.439,8.748,19.439,19.44V54.702z"/>
+</g>
+<g>
+ <g>
+ <g>
+ <path fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" d="M62.587,37.583
+ c0-13.909-11.256-25.165-25.167-25.165S12.254,23.674,12.254,37.583c0,13.911,11.256,25.167,25.167,25.167
+ S62.587,51.494,62.587,37.583z"/>
+ <path fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" d="M37.42,54.825
+ c-9.537,0-17.241-7.705-17.241-17.242c0-9.535,7.704-17.24,17.241-17.24s17.241,7.705,17.241,17.24
+ C54.661,47.12,46.957,54.825,37.42,54.825z"/>
+ </g>
+ </g>
+ <g>
+ <line fill="none" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" x1="26.448" y1="37.638" x2="48.613" y2="37.638"/>
+ <line fill="none" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" x1="37.532" y1="26.555" x2="37.532" y2="48.721"/>
+ </g>
+</g>
+</svg>
diff --git a/icons/corner-info.svg b/icons/corner-info.svg
new file mode 100644
index 0000000..9a22582
--- /dev/null
+++ b/icons/corner-info.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#CDCCCC">
+]><svg enable-background="new 0 0 75 75" height="75px" version="1.1" viewBox="0 0 75 75" width="75px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="corner-info">
+ <g display="inline">
+ <rect height="75" width="75"/>
+ <g>
+ <polygon fill="&fill_color;" points="0,0 75,0 0,75 "/>
+ </g>
+ <g>
+ <path d="M22.34,35.03h2.534l-0.383,2.017h-8.436l3.051-15.704h-2.518l0.384-2.018h8.419L22.34,35.03z M19.923,14.308 c0.177-0.9,0.636-1.659,1.375-2.275c0.739-0.617,1.559-0.925,2.459-0.925c0.877,0,1.567,0.311,2.067,0.934 c0.367,0.456,0.55,0.99,0.55,1.601c0,0.21-0.022,0.433-0.066,0.667c-0.167,0.878-0.62,1.625-1.359,2.243 c-0.74,0.617-1.548,0.925-2.426,0.925c-0.9,0-1.6-0.306-2.101-0.917c-0.377-0.456-0.567-0.99-0.567-1.601 C19.856,14.747,19.879,14.53,19.923,14.308z"/>
+ </g>
+ <polyline fill="none" points="1,75 1,1 75,1 " stroke="#808080" stroke-width="2"/>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/icons/delete-icon.svg b/icons/delete-icon.svg
new file mode 100644
index 0000000..3c33ea9
--- /dev/null
+++ b/icons/delete-icon.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="74.5px" height="74px" viewBox="0 0 74.5 74" enable-background="new 0 0 74.5 74" xml:space="preserve">
+<g>
+ <path d="M75.084,54.702c0,10.692-8.748,19.44-19.439,19.44H19.19c-10.692,0-19.44-8.748-19.44-19.44V18.583
+ c0-10.692,8.748-19.44,19.44-19.44h36.454c10.691,0,19.439,8.748,19.439,19.44V54.702z"/>
+</g>
+<g>
+ <g>
+ <g>
+ <path fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" d="M62.587,37.583
+ c0-13.909-11.256-25.165-25.167-25.165S12.254,23.674,12.254,37.583c0,13.911,11.256,25.167,25.167,25.167
+ S62.587,51.494,62.587,37.583z"/>
+ <path fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" d="M37.42,54.825
+ c-9.537,0-17.241-7.705-17.241-17.242c0-9.535,7.704-17.24,17.241-17.24s17.241,7.705,17.241,17.24
+ C54.661,47.12,46.957,54.825,37.42,54.825z"/>
+ </g>
+ </g>
+ <g>
+ <line fill="none" stroke="#FFFFFF" stroke-width="4" stroke-linecap="round" x1="26.448" y1="37.638" x2="48.613" y2="37.638"/>
+ </g>
+</g>
+</svg>
diff --git a/icons/map-icon-croseE.svg b/icons/map-icon-croseE.svg
new file mode 100644
index 0000000..907247e
--- /dev/null
+++ b/icons/map-icon-croseE.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY stroke_color "#231F20">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="28px" height="84.5px" viewBox="0 0 28 84.5" enable-background="new 0 0 28 84.5" xml:space="preserve">
+
+<g>
+ <polygon fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5" points="25.8,42.5 1.8,78.9 1.8,42.5 1.8,5.9 "/>
+ <polygon fill="&stroke_color;" stroke="&stroke_color;" stroke-width="3.5" points="1.8,44.3 1.8,77.9 21.8,44.3 "/>
+</g>
+</svg>
diff --git a/icons/map-icon-croseN.svg b/icons/map-icon-croseN.svg
new file mode 100644
index 0000000..3b2bbaf
--- /dev/null
+++ b/icons/map-icon-croseN.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="84.5px" height="28px" viewBox="0 0 84.5 28" enable-background="new 0 0 84.5 28" xml:space="preserve">
+
+<g>
+ <polygon fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5" points="42.5,2.1 78.9,26.1 42.5,26.1 5.9,26.1 "/>
+ <polygon fill="&stroke_color;" stroke="&stroke_color;" stroke-width="3.5" points="44.3,26.1 77.9,26.1 44.3,6.1 "/>
+</g>
+</svg>
diff --git a/icons/map-icon-croseS.svg b/icons/map-icon-croseS.svg
new file mode 100644
index 0000000..3d9354d
--- /dev/null
+++ b/icons/map-icon-croseS.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="84.5px" height="28px" viewBox="0 0 84.5 28" enable-background="new 0 0 84.5 28" xml:space="preserve">
+
+<g>
+ <polygon fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5" points="42.2,25.8 5.8,1.8 42.2,1.8 78.8,1.8 "/>
+ <polygon fill="&stroke_color;" stroke="&stroke_color;" stroke-width="3.5" points="40.4,1.8 6.8,1.8 40.4,21.8 "/>
+</g>
+</svg>
diff --git a/icons/map-icon-croseW.svg b/icons/map-icon-croseW.svg
new file mode 100644
index 0000000..714497a
--- /dev/null
+++ b/icons/map-icon-croseW.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="28px" height="84.5px" viewBox="0 0 28 84.5" enable-background="new 0 0 28 84.5" xml:space="preserve">
+
+<g>
+ <polygon fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5" points="2.1,42.2 26.1,5.8 26.1,42.2 26.1,78.8 "/>
+ <polygon fill="&stroke_color;" stroke="&stroke_color;" stroke-width="3.5" points="26.1,40.4 26.1,6.8 6,40.4 "/>
+</g>
+</svg>
diff --git a/icons/map-icon-zoomIn.svg b/icons/map-icon-zoomIn.svg
new file mode 100644
index 0000000..6c139f4
--- /dev/null
+++ b/icons/map-icon-zoomIn.svg
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Layer_5"
+ x="0px"
+ y="0px"
+ width="74.5px"
+ height="74px"
+ viewBox="0 0 74.5 74"
+ enable-background="new 0 0 74.5 74"
+ xml:space="preserve"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="map-icon-zoomIn.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata16"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs14"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 37 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="74.5 : 37 : 1"
+ inkscape:persp3d-origin="37.25 : 24.666667 : 1"
+ id="perspective18" />
+
+
+
+
+
+ </defs><sodipodi:namedview
+ inkscape:window-height="718"
+ inkscape:window-width="1366"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ showgrid="false"
+ inkscape:zoom="6.2432432"
+ inkscape:cx="37.25"
+ inkscape:cy="24.186147"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:current-layer="Layer_5" />
+<circle
+ sodipodi:ry="30"
+ sodipodi:rx="30"
+ sodipodi:cy="37.18"
+ sodipodi:cx="37.026001"
+ id="circle5"
+ r="30"
+ cy="37.18"
+ cx="37.026001" />
+<path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#0000ff;stroke-width:6.53190851;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path2399"
+ sodipodi:cx="42.365803"
+ sodipodi:cy="28.190475"
+ sodipodi:rx="15.937229"
+ sodipodi:ry="17.619047"
+ d="M 58.303032,28.190475 A 15.937229,17.619047 0 1 1 26.428574,28.190475 A 15.937229,17.619047 0 1 1 58.303032,28.190475 z"
+ transform="matrix(1.2877593,0,0,1.1648369,-13.31237,-0.2420693)" /><rect
+ style="fill:#0000ff;fill-opacity:1;stroke:#0000ff;stroke-width:10.37449551;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect3175"
+ width="1.2975725"
+ height="9.29566"
+ x="54.576096"
+ y="29.742977"
+ transform="matrix(0.8590348,0.5119173,-0.6668522,0.74519,0,0)" /><line
+ x1="41.510841"
+ y1="14.695152"
+ x2="41.510841"
+ y2="50.695152"
+ id="line9"
+ style="fill:none;stroke:#ffffff;stroke-width:4" /><line
+ x1="58.645912"
+ y1="33.015495"
+ x2="24.375782"
+ y2="33.015495"
+ id="line11"
+ style="fill:none;stroke:#ffffff;stroke-width:3.90271306" /></svg> \ No newline at end of file
diff --git a/icons/map-icon-zoomOut.svg b/icons/map-icon-zoomOut.svg
new file mode 100644
index 0000000..712f350
--- /dev/null
+++ b/icons/map-icon-zoomOut.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="74.5px" height="74px" viewBox="0 0 74.5 74" enable-background="new 0 0 74.5 74" xml:space="preserve">
+<g>
+ <circle cx="37.026" cy="37.18" r="30"/>
+ <g>
+ <line fill="none" stroke="#FFFFFF" stroke-width="4" x1="55.026" y1="37.18" x2="19.026" y2="37.18"/>
+ </g>
+</g>
+</svg>
diff --git a/icons/measure-icon.svg b/icons/measure-icon.svg
new file mode 100644
index 0000000..cd21013
--- /dev/null
+++ b/icons/measure-icon.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY fill_color "#AAAAAA">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg xmlns="http://www.w3.org/2000/svg" width="45" height="45">
+ <rect x="13" y="3" width="20" height="40" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:3.0"/>
+ <line y1="13" y2="13" x1="25" x2="33" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:3.0"/>
+ <line y1="23" y2="23" x1="25" x2="33" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:3.0"/>
+ <line y1="33" y2="33" x1="25" x2="33" style="fill:&fill_color;;stroke:&stroke_color;;stroke-width:3.0"/>
+</svg>
diff --git a/icons/save-search.svg b/icons/save-search.svg
new file mode 100644
index 0000000..22b18bf
--- /dev/null
+++ b/icons/save-search.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Layer_1"
+ x="0px"
+ y="0px"
+ width="76px"
+ height="76px"
+ viewBox="0 0 76 76"
+ enable-background="new 0 0 76 76"
+ xml:space="preserve"
+ sodipodi:version="0.32"
+ inkscape:version="0.45.1"
+ sodipodi:docname="save-search.svg"
+ sodipodi:docbase="/root/Desktop"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata2230"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs2228" /><sodipodi:namedview
+ inkscape:window-height="622"
+ inkscape:window-width="872"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="5.3552632"
+ inkscape:cx="38"
+ inkscape:cy="38"
+ inkscape:window-x="127"
+ inkscape:window-y="103"
+ inkscape:current-layer="Layer_1" />
+<g
+ id="g2221">
+ <path
+ d="M75.946,56.313c0,10.692-8.748,19.44-19.44,19.44H20.053c-10.692,0-19.44-8.748-19.44-19.44v-36.12 c0-10.692,8.748-19.44,19.44-19.44h36.453c10.692,0,19.44,8.748,19.44,19.44V56.313z"
+ id="path2223" />
+</g>
+
+<g
+ display="block"
+ id="share-link"
+ transform="matrix(1.2,0,0,1.2,5.6048,4.5697942)"
+ style="display:block">
+ <polygon
+ display="inline"
+ points="27.5,7.266 34.074,20.588 48.774,22.723 38.138,33.092 40.647,47.734 27.5,40.82 14.353,47.734 16.862,33.092 6.226,22.723 20.926,20.588 27.5,7.266 "
+ id="polygon5"
+ style="fill:#ffffff;stroke:#010101;stroke-width:3.5;display:inline" />
+</g></svg> \ No newline at end of file
diff --git a/icons/static-icon.svg b/icons/static-icon.svg
new file mode 100644
index 0000000..2bf143b
--- /dev/null
+++ b/icons/static-icon.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"[
+ <!ENTITY stroke_color "#FFFFFF">
+ <!ENTITY fill_color "#010101">
+]>
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="74.5px" height="74px" viewBox="0 0 74.5 74" enable-background="new 0 0 74.5 74" xml:space="preserve">
+<g>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="37.124" y1="13.641" x2="37.124" y2="23.035"/>
+ <path fill="none" stroke="&stroke_color;" stroke-width="3.5" d="M43.213,29.176c0,3.406-2.762,6.168-6.166,6.168
+ c-3.406,0-6.167-2.762-6.167-6.168c0-3.404,2.761-6.141,6.167-6.141C40.451,23.035,43.213,25.771,43.213,29.176z M21.735,59.275
+ L34.298,34.5"/>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="27.88" y1="47.344" x2="54.881" y2="47.344"/>
+ <line fill="none" stroke="&stroke_color;" stroke-width="3.5" x1="39.949" y1="34.434" x2="52.546" y2="59.275"/>
+</g>
+</svg>
diff --git a/icons/tool-marquee-freeform.svg b/icons/tool-marquee-freeform.svg
new file mode 100644
index 0000000..2e02329
--- /dev/null
+++ b/icons/tool-marquee-freeform.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="55px"
+ height="55px" viewBox="0 0 55 55" enable-background="new 0 0 55 55" xml:space="preserve">
+
+<g id="Freeform_Marquee" >
+ <path display="inline" fill="none" stroke="#FFFFFF" stroke-width="2.25" stroke-dasharray="6" d="M4.434,48
+ c0,0,3.453-38.583,19-41.833s28.334,18.5,28.334,18.5L34.434,33"/>
+ <g display="inline">
+ <path fill="#FFFFFF" d="M45.368,43.971l-5.382-5.383h3.642c0.458,0,0.918-0.174,1.267-0.524c0.701-0.7,0.7-1.836,0-2.535
+ c-0.351-0.351-0.811-0.525-1.267-0.526h-9.766l0.001,9.765c0,0.458,0.176,0.916,0.525,1.267c0.7,0.701,1.836,0.701,2.535,0
+ c0.351-0.35,0.524-0.809,0.524-1.267v-3.644l5.385,5.384c0.324,0.323,0.772,0.524,1.268,0.525
+ c0.99-0.001,1.793-0.804,1.793-1.794C45.894,44.742,45.691,44.296,45.368,43.971z"/>
+ </g>
+</g>
+
+</svg>
diff --git a/icons/tool-polygon.svg b/icons/tool-polygon.svg
new file mode 100644
index 0000000..0c8fee6
--- /dev/null
+++ b/icons/tool-polygon.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ x="0px"
+ y="0px"
+ width="55px"
+ height="55px"
+ viewBox="0 0 55 55"
+ enable-background="new 0 0 55 55"
+ xml:space="preserve"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45"
+ sodipodi:docname="tool-shape-polygon.svg"
+ sodipodi:docbase="/home/joy/oficina_sugar/oficina/icons"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="true"><metadata
+ id="metadata10"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs8" /><sodipodi:namedview
+ inkscape:window-height="941"
+ inkscape:window-width="1269"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="6.8909091"
+ inkscape:cx="27.5"
+ inkscape:cy="27.5"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:current-layer="svg2" />
+
+<g
+ id="Polygon"
+ style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1">
+ <polygon
+ display="inline"
+ fill="#FFFFFF"
+ points="16.834,46.58 5.83,27.522 16.834,8.464 38.841,8.464 49.844,27.522 38.841,46.58 "
+ id="polygon5"
+ style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1" />
+</g>
+
+</svg> \ No newline at end of file
diff --git a/icons/tool-shape-line.svg b/icons/tool-shape-line.svg
new file mode 100644
index 0000000..0bf4d8e
--- /dev/null
+++ b/icons/tool-shape-line.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="55px"
+ height="55px" viewBox="0 0 55 55" enable-background="new 0 0 55 55" xml:space="preserve">
+
+<g id="Line" >
+ <line display="inline" fill="none" stroke="#FFFFFF" stroke-width="2.25" x1="8.708" y1="47.167" x2="48.042" y2="7.833"/>
+</g>
+
+</svg>
diff --git a/icons/topo-icon.svg b/icons/topo-icon.svg
new file mode 100644
index 0000000..18a25d6
--- /dev/null
+++ b/icons/topo-icon.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="75.667px" height="75.333px" viewBox="0 0 75.667 75.333" enable-background="new 0 0 75.667 75.333" xml:space="preserve">
+<g>
+ <path d="M75.828,55.925c0,10.692-8.748,19.44-19.439,19.44H19.935c-10.692,0-19.44-8.748-19.44-19.44V19.806
+ c0-10.692,8.748-19.44,19.44-19.44h36.454c10.691,0,19.439,8.748,19.439,19.44V55.925z"/>
+</g>
+<g>
+ <path fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" d="M44.936,43.372
+ c-2.121,4.811-6.641,7.477-10.094,5.953c-3.454-1.522,1.952-3.797,4.073-8.608c2.121-4.811,0.156-10.337,3.609-8.813
+ C45.979,33.427,47.058,38.562,44.936,43.372z"/>
+ <path fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" d="M50.594,45.6
+ c-3.481,7.896-13.633,11.064-22.676,7.077c-9.041-3.987,3.43-6.133,6.911-14.028c3.481-7.896-3.344-18.551,5.696-14.563
+ C49.568,28.072,54.075,37.705,50.594,45.6z"/>
+ <path fill="none" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round" d="M58.834,49.5
+ c-4.625,10.486-21.741,13.091-38.235,5.818c-16.491-7.272,4.856-8.013,9.479-18.498s-9.226-26.746,7.265-19.474
+ C53.837,24.619,63.457,39.015,58.834,49.5z"/>
+</g>
+</svg>
diff --git a/icons/web-icon.svg b/icons/web-icon.svg
new file mode 100644
index 0000000..d5e7e59
--- /dev/null
+++ b/icons/web-icon.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]><svg enable-background="new 0 0 55 55" height="55px" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="activity-browse">
+ <circle cx="27.375" cy="27.5" display="inline" fill="&fill_color;" r="19.903" stroke="&stroke_color;" stroke-width="3.5"/>
+ <g display="inline">
+ <path d="M27.376,7.598c0,0-11.205,8.394-11.205,19.976 c0,11.583,11.205,19.829,11.205,19.829" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+ <path d="M27.376,7.598c0,0,11.066,9.141,11.066,19.976 c0,10.839-11.066,19.829-11.066,19.829" fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5" x1="27.376" x2="27.376" y1="7.598" y2="47.402"/>
+ <line fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5" x1="27.376" x2="27.376" y1="7.598" y2="47.402"/>
+ <line fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5" x1="27.376" x2="27.376" y1="7.598" y2="47.402"/>
+ <line fill="&fill_color;" stroke="&stroke_color;" stroke-width="3.5" x1="7.472" x2="47.278" y1="27.5" y2="27.5"/>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/idlethread.py b/idlethread.py
new file mode 100644
index 0000000..7dc424e
--- /dev/null
+++ b/idlethread.py
@@ -0,0 +1,175 @@
+from __future__ import generators
+
+import gobject
+import time
+import traceback
+
+class GIdleThread(object):
+ """This is a pseudo-"thread" for use with the GTK+ main loop.
+
+ This class does act a bit like a thread, all code is executed in
+ the callers thread though. The provided function should be a generator
+ (or iterator).
+
+ It can be started with start(). While the "thread" is running is_alive()
+ can be called to see if it's alive. wait([timeout]) will wait till the
+ generator is finished, or timeout seconds.
+
+ If an exception is raised from within the generator, it is stored in
+ the error property. Execution of the generator is finished.
+
+ Note that this routine runs in the current thread, so there is no need
+ for nasty locking schemes.
+
+ Example (runs a counter through the GLib main loop routine):
+ >>> def counter(max): for x in xrange(max): yield x
+ >>> t = GIdleThread(counter(123))
+ >>> t.start()
+ >>> while gen.is_alive():
+ ... main.iteration(False)
+ """
+
+ def __init__(self, generator, queue=None):
+ assert hasattr(generator, 'next'), 'The generator should be an iterator'
+ self._generator = generator
+ self._queue = queue
+ self._idle_id = 0
+ self._error = None
+
+ def start(self, priority=gobject.PRIORITY_LOW):
+ """Start the generator. Default priority is low, so screen updates
+ will be allowed to happen.
+ """
+ idle_id = gobject.idle_add(self.__generator_executer,
+ priority=priority)
+ self._idle_id = idle_id
+ return idle_id
+
+ def wait(self, timeout=0):
+ """Wait until the corouine is finished or return after timeout seconds.
+ This is achieved by running the GTK+ main loop.
+ """
+ clock = time.clock
+ start_time = clock()
+ main = gobject.main_context_default()
+ while self.is_alive():
+ main.iteration(False)
+ if timeout and (clock() - start_time >= timeout):
+ return
+
+ def interrupt(self):
+ """Force the generator to stop running.
+ """
+ if self.is_alive():
+ gobject.source_remove(self._idle_id)
+ self._idle_id = 0
+
+ def is_alive(self):
+ """Returns True if the generator is still running.
+ """
+ return self._idle_id != 0
+
+ error = property(lambda self: self._error,
+ doc="Return a possible exception that had occured "\
+ "during execution of the generator")
+
+ def __generator_executer(self):
+ try:
+ result = self._generator.next()
+ if self._queue:
+ try:
+ self._queue.put(result)
+ except QueueFull:
+ self.wait(0.5)
+ # If this doesn't work...
+ self._queue.put(result)
+ return True
+ except StopIteration:
+ self._idle_id = 0
+ return False
+ except Exception, e:
+ self._error = e
+ traceback.print_exc()
+ self._idle_id = 0
+ return False
+
+
+class QueueEmpty(Exception):
+ """Exception raised whenever the queue is empty and someone tries to fetch
+ a value.
+ """
+ pass
+
+
+class QueueFull(Exception):
+ """Exception raised when the queue is full and the oldest item may not be
+ disposed.
+ """
+ pass
+
+
+class Queue(object):
+ """A FIFO queue. If the queue has a max size, the oldest item on the
+ queue is dropped if that size id exceeded.
+ """
+
+ def __init__(self, size=0, dispose_oldest=True):
+ self._queue = []
+ self._size = size
+ self._dispose_oldest = dispose_oldest
+
+ def put(self, item):
+ """Put item on the queue. If the queue size is limited ...
+ """
+ if self._size > 0 and len(self._queue) >= self._size:
+ if self._dispose_oldest:
+ self.get()
+ else:
+ raise QueueFull
+
+ self._queue.insert(0, item)
+
+ def get(self):
+ """Get the oldest item off the queue.
+ QueueEmpty is raised if no items are left on the queue.
+ """
+ try:
+ return self._queue.pop()
+ except IndexError:
+ raise QueueEmpty
+
+
+if __name__ == '__main__':
+ def counter(max):
+ for i in range(max):
+ yield i
+
+ def shower(queue):
+ # Never stop reading the queue:
+ while True:
+ try:
+ cnt = queue.get()
+ print 'cnt =', cnt
+ except QueueEmpty:
+ pass
+ yield None
+
+ print 'Test 1: (should print range 0..22)'
+ queue = Queue()
+ c = GIdleThread(counter(23), queue)
+ s = GIdleThread(shower(queue))
+
+ main = gobject.main_context_default()
+ c.start()
+ s.start()
+ s.wait(2)
+
+ print 'Test 2: (should only print 22)'
+ queue = Queue(size=1)
+ c = GIdleThread(counter(23), queue)
+ s = GIdleThread(shower(queue))
+
+ main = gobject.main_context_default()
+ c.start(priority=gobject.PRIORITY_DEFAULT)
+ s.start()
+ s.wait(3)
diff --git a/instance.py b/instance.py
new file mode 100644
index 0000000..95fb932
--- /dev/null
+++ b/instance.py
@@ -0,0 +1,45 @@
+import os
+
+from sugar import profile
+from sugar import util
+from sugar.activity import activity
+import shutil
+
+from color import Color
+
+class Instance:
+ key = profile.get_pubkey()
+ #joyride ...
+ #keyHash = util.sha_data(key)
+ #8.2...
+ #keyHash = util._sha_data(key)
+ #keyHashPrintable = util.printable_hash(keyHash)
+ nickName = profile.get_nick_name()
+
+ colorFill = Color()
+ colorFill.init_hex( profile.get_color().get_fill_color() )
+ colorStroke = Color()
+ colorStroke.init_hex( profile.get_color().get_stroke_color() )
+
+ instanceId = None
+ instancePath = None
+ dataPath = None
+
+ def __init__(self, ca):
+ self.__class__.instanceId = ca._activity_id
+
+ self.__class__.instancePath = os.path.join(ca.get_activity_root(), "instance")
+ recreateTmp()
+
+ self.__class__.dataPath = os.path.join(ca.get_activity_root(), "data")
+ recreateData()
+
+
+def recreateTmp():
+ if (not os.path.exists(Instance.instancePath)):
+ os.makedirs(Instance.instancePath)
+
+
+def recreateData():
+ if (not os.path.exists(Instance.dataPath)):
+ os.makedirs(Instance.dataPath) \ No newline at end of file
diff --git a/locale/es/LC_MESSAGES/org.laptop.map.mo b/locale/es/LC_MESSAGES/org.laptop.map.mo
new file mode 100644
index 0000000..ea90e33
--- /dev/null
+++ b/locale/es/LC_MESSAGES/org.laptop.map.mo
Binary files differ
diff --git a/locale/es/activity.linfo b/locale/es/activity.linfo
new file mode 100644
index 0000000..b8f1dd2
--- /dev/null
+++ b/locale/es/activity.linfo
@@ -0,0 +1,2 @@
+[Activity]
+name = Mapa
diff --git a/locale/mn/LC_MESSAGES/org.laptop.map.mo b/locale/mn/LC_MESSAGES/org.laptop.map.mo
new file mode 100644
index 0000000..2ef674c
--- /dev/null
+++ b/locale/mn/LC_MESSAGES/org.laptop.map.mo
Binary files differ
diff --git a/locale/mn/activity.linfo b/locale/mn/activity.linfo
new file mode 100644
index 0000000..eadffb2
--- /dev/null
+++ b/locale/mn/activity.linfo
@@ -0,0 +1,2 @@
+[Activity]
+name = Map
diff --git a/logic.py b/logic.py
new file mode 100644
index 0000000..6d135a5
--- /dev/null
+++ b/logic.py
@@ -0,0 +1,272 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+
+from result import ServerResult
+from constants import Constants
+from instance import Instance
+
+from threading import Thread
+import threading
+import os
+import gobject
+import time
+import gtk
+import urllib
+
+class ServerLogic:
+ def __init__(self, ca):
+ self.ca = ca
+ self.proceedTxt = ""
+ self.proceedHeaders = []
+ self.cond = ca.cond
+ self.addKMLSet=0
+
+ def doServerLogic(self, url, path, params):
+ self.ca.remoteServerActive( True )
+ r = ServerResult()
+ fileName = path[len(path)-1]
+
+ if (fileName == "comet.js"):
+
+ #clear...
+ self.proceedHeaders = []
+ self.proceedTxt = ""
+
+ #wait...
+ self.cond.acquire()
+ self.cond.wait()
+ self.cond.release()
+
+ #prep response...
+ for h in range( len(self.proceedHeaders) ):
+ r.headers.append( self.proceedHeaders[h] )
+ r.txt = ""+self.proceedTxt
+
+ else:
+ kickThroughComet = True
+
+ if (fileName =="mediaQuery.js"):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = self.ca.m.getMediaResponse( params[0][1], params[1][1], params[2][1], params[3][1] )
+
+ elif (fileName == "showMedia.js"):
+ id = params[0][1]
+ locX = params[1][1]
+ locY = params[2][1]
+ up = params[3][1]
+ rt = params[4][1]
+ gobject.idle_add(self.ca.showMedia, id, locX, locY, up=='true', rt=='true')
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+
+ elif (fileName == "placeAddMedia.js"):
+ lat = params[0][1]
+ lng = params[1][1]
+ gobject.idle_add(self.ca.placeAddMedia, lat, lng)
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ kickThroughComet = False
+
+ elif (fileName == "hideMedia.js"):
+ gobject.idle_add(self.ca.hideMedia)
+
+ elif (fileName == "getImage.js"):
+ localfile = open(os.path.join(Instance.instancePath, params[0][1]), 'r')
+ localdata = localfile.read()
+ localfile.close()
+
+ #one day we might need to kick you through comet as a base64'd image.
+ r.txt = localdata
+ r.headers.append( ("Content-type", "image/jpeg") )
+ kickThroughComet = False
+
+ elif (fileName == "updateLocation.js"):
+ lat = params[0][1]
+ lng = params[1][1]
+ zoom = params[2][1]
+ x = params[3][1]
+ y = params[4][1]
+ gobject.idle_add(self.ca.updateMapMetaData,lat,lng,zoom,x,y)
+
+ elif (fileName == "addSavedMap.js"):
+ # allow internet to send an array of SavedMaps back to map.py
+ latitudes = params[0][1]
+ longitudes = params[1][1]
+ zooms = params[2][1]
+ notes = params[3][1]
+ gobject.idle_add(self.ca.addSavedMap,latitudes,longitudes,zooms,urllib.unquote(notes),True)
+
+ elif (fileName == "addInfoMarker.js"):
+ lat = params[0][1]
+ lng = params[1][1]
+ info = params[2][1]
+ icon = params[3][1]
+ if(params[4][1] == "True"):
+ isNew = True
+ else:
+ isNew = False
+ gobject.idle_add(self.ca.addInfoMarker,lat,lng,info,icon,isNew)
+
+ elif (fileName == "addLine.js"):
+ id = params[0][1]
+ color = params[1][1]
+ thickness = params[2][1]
+ pts = params[3][1] # send pts separated with | instead of ,
+ gobject.idle_add(self.ca.addLine,id,color,thickness,pts)
+
+ elif (fileName == "joinGroup.js"):
+ groupName = params[0][1]
+ groupFile = urllib.urlopen("http://bluebird-science.appspot.com/group/data?name=" + groupName)
+ self.ca.preComet()
+ self.handleGroupData(groupFile.readline())
+ self.ca.postComet()
+
+ elif (fileName == "promptSearch.js"):
+ address = params[0][1]
+ time.sleep(0.5)
+ self.ca.preComet()
+ self.handleAddressUpdate(address+"+")
+ self.ca.postComet()
+
+ #elif (fileName == "gotoMapV3.js"):
+ # button on static maps links to mapv3
+ #self.ca.loadMapV3()
+
+ if (kickThroughComet):
+ #not sure how & why this goes out, but it does.
+ self.cond.acquire()
+ self.cond.notifyAll()
+ self.cond.release()
+ time.sleep(.1)
+
+ return r
+
+ def handleAddressUpdate( self, address ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "moveTo(" + address + ");"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleGroupData( self, groupData ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "joinThisGroup(" + groupData + ");"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleCompassUpdate( self, dir ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+
+ if (dir == "e"):
+ self.proceedTxt = "dirEast();"
+ elif (dir == "w"):
+ self.proceedTxt = "dirWest();"
+ elif (dir == "n"):
+ self.proceedTxt = "dirNorth();"
+ elif (dir == "s"):
+ self.proceedTxt = "dirSouth();"
+ else:
+ # use this as a print warning window
+ self.proceedTxt = 'showInfo("' + dir + '");'
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleZoomUpdate( self, dir ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ if (dir == "+"):
+ self.proceedTxt = "zoomIn();"
+ elif (dir == "-"):
+ self.proceedTxt = "zoomOut();"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleClear( self ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "clear();"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handlePreAdd( self ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "preAddMedia();"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handlePreAddInfo( self ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "preAddInfo();"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handlePostAdd( self, rec ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "postAddMedia(" + rec.latitude + ", " + rec.longitude + ", '" + rec.getThumbUrl() + "', '" + rec.getThumbBasename() + "', '" + rec.tags + "');"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleDelete( self ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "deleteMedia();"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ # handle a map that was sent to us
+ def handleReceivedMap( self, lat, lng, zoom):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "setMap(" + lat + "," + lng + "," + zoom + ");"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleSavedMap( self, lat, lng, zoom, info ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ if(info.find("Describe the map") != 0):
+ self.proceedTxt = "setMap2(" + lat + "," + lng + "," + zoom + ",'" + urllib.quote(info) + "');"
+ else:
+ self.proceedTxt = "setMap2(" + lat + "," + lng + "," + zoom + ",'');"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ # handle a marker that was sent to us
+ def handleAddMarker( self, lat, lng, pixString, icon ):
+ if(self.addKMLSet == 0):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = ""
+ self.addKMLSet = 1
+ self.proceedTxt = self.proceedTxt + "addPt(" + lat + ", " + lng + ", '" + pixString + "', '" + icon + "',false);"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def mapPaste( self, type ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "mapPaste('" + type + "');"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleEndKML( self ):
+ self.addKMLSet = 0
+
+ def lineMode(self, type):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "lineMode('" + type + "');"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleLine(self,id,color,thickness,pts):
+ if(self.addKMLSet == 0):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = ""
+ self.addKMLSet = 1
+ self.proceedTxt = self.proceedTxt + "addLine('" + id + "','" + color + "','" + thickness + "','" + pts + "');"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ # handle start of measure tool
+ def handleMeasure(self):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "measure();"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);")
+
+ def handleTagSearch( self, tags ):
+ self.proceedHeaders.append( ("Content-type", "text/javascript") )
+ self.proceedTxt = "filterTags('" + tags + "');"
+ self.ca.browser.load_uri("javascript:"+self.proceedTxt+"void(0);") \ No newline at end of file
diff --git a/map.py b/map.py
new file mode 100644
index 0000000..c8159d5
--- /dev/null
+++ b/map.py
@@ -0,0 +1,1675 @@
+#!/usr/bin/env python
+
+# Offline Map Activity : http://wiki.laptop.org/go/User:Ndoiron/OfflineMap
+# All code open-source under the MIT license
+#
+# based on Map Version 3
+# Original Map code copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gtk
+import gobject
+import os
+import threading
+from threading import *
+import time
+import hippo
+import shutil
+import urllib
+import random
+
+from sugar.graphics.toggletoolbutton import ToggleToolButton
+from sugar.graphics.toolbutton import ToolButton
+from sugar.activity import activity
+from sugar.graphics import style
+
+from filepicker import FilePicker
+
+from constants import Constants
+from webviewer import WebViewer
+from server import Server
+from logic import ServerLogic
+from result import ServerResult
+from instance import Instance
+from constants import Constants
+from model import Model
+from tray import HTray
+from photocanvas import PhotoCanvas
+from p5 import P5
+from gplay import Gplay
+from gplay import PlayVideoWindow
+import serialize
+import utils
+from button import SavedButton
+from savedmap import SavedMap
+import _camera
+
+# sharing
+import telepathy
+from dbus.service import method, signal
+from dbus.gobject_service import ExportedGObject
+from sugar.graphics.alert import NotifyAlert
+from sugar.presence import presenceservice
+from sugar.presence.tubeconn import TubeConnection
+
+SERVICE = "org.laptop.map"
+IFACE = SERVICE
+PATH = "/org/laptop/map"
+
+imgWidth = 180
+imgHeight = 135
+webWidth = imgWidth*5
+webHeight = imgHeight*4
+
+class Map(activity.Activity):
+
+ #temp values until they get assigned
+ cometPort = 8889
+ ajaxPort = 8890
+
+ initLat = '42.9'
+ initLng = '-71.0'
+ initZoom = '6'
+
+ popW = 500
+ popH = 375
+ popB = 5
+
+ def __init__(self, handle):
+ activity.Activity.__init__(self, handle)
+ Instance(self)
+ Constants(self)
+ self.modify_bg( gtk.STATE_NORMAL, Constants.colorBg.gColor )
+ gobject.idle_add(self._initme, None)
+
+ def _initme( self, userdata=None ):
+ self.basePath = activity.get_bundle_path()
+ self.htmlPath = os.path.join(self.basePath, "mapviewer")
+ self.libPath = "/home/olpc/Library/MapPack/"
+ self.m = Model(self)
+ self.cond = Condition()
+
+ self.SAVING_SEARCH = False
+ self.shownSave = None
+ self.shownRecd = None
+ #these get updated whenever we hear back from the server
+ self.NOW_MAP_CENTER_LAT = self.__class__.initLat
+ self.NOW_MAP_CENTER_LNG = self.__class__.initLng
+ self.NOW_MAP_ZOOM = self.__class__.initZoom
+ self.NOW_MAP_TAGS = ""
+
+ #calc unique ports
+ h = hash(Instance.instanceId)
+ self.__class__.cometPort = 1024 + (h%32255) * 2
+ self.__class__.ajaxPort = self.__class__.cometPort + 1
+
+ #ui
+ self.windowStack = []
+
+ self.toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(self.toolbox)
+ self.searchToolbar = SearchToolbar(self)
+ self.searchToolbar.connect("address-update", self._addressUpdateCb)
+ self.searchToolbar.connect("zoom-in", self._zoomInCb)
+ self.searchToolbar.connect("zoom-out", self._zoomOutCb)
+ self.searchToolbar.connect("save-search", self._saveSearchCb)
+ self.searchToolbar.connect("search-update", self._searchUpdateCb)
+ self.searchToolbar.set_sensitive(False)
+ self.toolbox.add_toolbar( Constants.istrSearch, self.searchToolbar )
+ self.addToolbar = AddToolbar(self)
+ self.toolbox.add_toolbar( Constants.istrAnnotate, self.addToolbar )
+ self.addToolbar.connect("add-media", self._addMediaCb)
+ self.addToolbar.connect("add-kml", self._addKMLCb)
+ self.addToolbar.connect("add-info", self._addInfoCb)
+ self.addToolbar.connect("delete-media", self._deleteMediaCb)
+ self.addToolbar.connect("measure", self._measureCb)
+ self.addToolbar.set_sensitive(False)
+ self.firstSearch = True
+ self.toolbox.set_current_toolbar(1)
+
+ self.toolbox.remove(self.toolbox._separator)
+ #taken directly from toolbox.py b/c I don't know how to mod the hongry hippo
+ separator = hippo.Canvas()
+ box = hippo.CanvasBox(
+ border_color=Constants.colorBg.get_int(),
+ background_color=Constants.colorBg.get_int(),
+ box_height=style.TOOLBOX_SEPARATOR_HEIGHT,
+ border_bottom=style.LINE_WIDTH)
+ separator.set_root(box)
+ self.toolbox.separator = separator
+ self.toolbox._notebook.set_property("can-focus", False)
+ self.toolbox.show()
+
+ #add components
+ vbox = gtk.VBox()
+
+ self.tray = HTray()
+ self.tray.set_size_request(-1, 130)
+ self.tray.connect("enter-notify-event", self._trayMouseCb)
+ self.tray.viewport.connect("enter-notify-event", self._trayMouseCb)
+ self.tray.viewport.traybar.connect("enter-notify-event", self._trayMouseCb)
+ self.tray.scroll_left.connect("enter-notify-event", self._trayMouseCb)
+ self.tray.scroll_right.connect("enter-notify-event", self._trayMouseCb)
+ self.tray.scroll_left_event.connect("enter-notify-event", self._trayMouseCb)
+ self.tray.scroll_right_event.connect("enter-notify-event", self._trayMouseCb)
+ fakeTray = gtk.VBox()
+ fakeTray.set_size_request(-1, 130)
+ fakeTray.modify_bg( gtk.STATE_NORMAL, Constants.colorBg.gColor )
+ fakeTrayEvent = gtk.EventBox()
+ fakeTrayEvent.modify_bg( gtk.STATE_NORMAL, Constants.colorBg.gColor )
+ fakeTrayEvent.set_size_request(-1, 130)
+ fakeTrayEvent.add( fakeTray )
+ self.trayBox = gtk.Notebook()
+ self.trayBox.set_size_request(-1, 130)
+ self.trayBox.set_show_tabs(False)
+ #vbox.pack_start(self.trayBox, expand=False)
+ self.trayBox.append_page(self.tray)
+ self.trayBox.append_page(fakeTrayEvent)
+ self.realTrayIndex = self.trayBox.page_num(self.tray)
+ self.fakeTrayIndex = self.trayBox.page_num(fakeTrayEvent)
+ self.trayBox.set_current_page(self.realTrayIndex)
+ fakeTrayEvent.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
+ fakeTrayEvent.connect_after("visibility-notify-event", self._fakeTrayVisibleNotifyCb)
+
+ self.browseBox = gtk.VBox()
+ self.browser = WebViewer()
+ self.htmlScale = 1.43
+ self.browser.set_size_request(int(webWidth*self.htmlScale), int(webHeight*self.htmlScale))
+ self.browseBox.pack_start(self.browser, expand=False)
+ self.browseBox.set_size_request(int(webWidth*self.htmlScale), int(webHeight*self.htmlScale))
+ vbox.pack_start(self.browseBox)
+ #vbox.pack_start(self.trayBox, expand=False)
+
+ #important to call these in this order to ensure they show up
+ self.set_canvas(vbox)
+ self.browser.show()
+
+ #fire up the web engine, spiderman!
+ self.cometLogic = ServerLogic(self)
+
+ ajaxServer = ServerThread(self.__class__.ajaxPort, self.cometLogic)
+ ajaxServer.start()
+
+ cometServer = ServerThread(self.__class__.cometPort, self.cometLogic)
+ cometServer.start()
+
+ self.browser.load_uri("file://" + self.htmlPath + "/mapCenter.html?ajaxPort=" + str(self.__class__.ajaxPort) + "&cometPort=" + str(self.__class__.cometPort))
+
+ self.loaded = True
+ self.loadingWindow = gtk.Window()
+ self.addToWindowStack( self.loadingWindow, self )
+ self.loadingWindow.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ loadingVBox = gtk.VBox()
+ self.loadingWindow.add(loadingVBox)
+
+ loadingTopEventBox = gtk.EventBox()
+ loadingTopEventBox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ loadingVBox.pack_start(loadingTopEventBox, expand=True)
+
+ self.loadingCanvas = PhotoCanvas()
+ self.loadingCanvas.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ self.loadingCanvas.set_size_request( 1200, 600 )
+ loadingVBox.pack_start(self.loadingCanvas, expand=True)
+
+ loadingInfo = gtk.Label()
+ loadingInfo.set_alignment( .5, 0 )
+ loadingInfo.set_text( "<b><span foreground='white' size='xx-large'>" + Constants.istrConnecting + "</span></b>")
+ loadingInfo.set_use_markup( True )
+ loadingVBox.pack_start(loadingInfo, expand=False)
+
+ loadingBotEventBox = gtk.EventBox()
+ loadingBotEventBox.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ loadingVBox.pack_start(loadingBotEventBox, expand=True)
+ self.loadingWindow.resize( gtk.gdk.screen_width(), gtk.gdk.screen_height()-(self.toolbox.allocation.height) )
+ self.moveWinOffscreen(self.loadingWindow)
+
+ self.popWindow = gtk.Window()
+ self.addToWindowStack( self.popWindow, self.loadingWindow )
+ self.popWindow.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ self.popWindow.resize( self.popW+(2*self.popB), self.popH+(2*self.popB) )
+ self.popUpBg = PopUpP5()
+ self.popWindow.add( self.popUpBg )
+ self.moveWinOffscreen(self.popWindow)
+ self.popWindow.show_all()
+
+ self.mediaWindow = gtk.Window()
+ self.addToWindowStack(self.mediaWindow, self.popWindow)
+ self.moveWinOffscreen(self.mediaWindow)
+ self.mediaWindow.show_all()
+
+ self.infoWindow = gtk.Window()
+ self._fillInInfoWindow()
+ self.addToWindowStack(self.infoWindow, self.mediaWindow)
+ self.moveWinOffscreen(self.infoWindow)
+ self.infoWindow.show_all()
+
+ # GTK science form window, based on infoWindow
+ #self.scienceWindow = gtk.Window()
+ #self._fillInScienceWindow()
+ #self.moveWinOffscreen(self.scienceWindow)
+ #self.scienceWindow.show_all()
+
+ self.selectWindow = gtk.Window()
+ self.selectWindow.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ self.addToWindowStack(self.selectWindow, self.infoWindow)
+ self.moveWinOffscreen(self.selectWindow)
+ self.selectWindow.show_all()
+
+ self.photoCanvas = PhotoCanvas()
+
+ self.gplayWin = PlayVideoWindow( Constants.colorBlack.gColor )
+ self.gplay = Gplay()
+ self.gplay.window = self.gplayWin
+
+ self.connect("destroy", self.destroy)
+ self.TOOLBOX_SIZE_ALLOCATE_ID = self.toolbox.connect_after("size-allocate", self._sizeAllocateCb)
+ self.show_all()
+
+ # break loading screen
+ self.loaded = True
+ self.enableNavigation()
+
+ # add sharing
+ self.maptube = None # Shared session
+ self.initiating = False
+ self.pservice = presenceservice.get_instance()
+ owner = self.pservice.get_owner()
+ self.owner = owner
+ self.connect('shared', self._shared_cb)
+ self.connect('joined', self._joined_cb)
+ self.mediaType = "photo"
+
+ return False
+
+ def _received_cb(self, text):
+ if(text.find(",") != -1):
+ params = text.split(",")
+ if(params[0] == "loc"):
+ # other XO's map location has moved [lat,lng,zoom]
+ self.preComet()
+ self.cometLogic.handleReceivedMap(params[1],params[2],params[3])
+ self.postComet()
+ elif(params[0] == "mar"):
+ # other XO has added or modded info marker [lat,lng,infoText,iconImg]
+ self.preComet()
+ self.cometLogic.handleAddMarker(params[1], params[2], params[3], params[4])
+ self.cometLogic.handleEndKML()
+ self.postComet()
+ elif(params[0] == "del"):
+ # delete a marker (not working)
+ self.preComet()
+ self.cometLogic.handleDelete()
+ self.postComet()
+ elif(params[0] == "smap"):
+ # add SavedMap [lat,lng,zoom,info] + not-new reminder
+ self.addSavedMap(params[1],params[2],params[3],params[4],False)
+ elif(params[0] == "line"):
+ # add a line
+ self.preComet()
+ self.cometLogic.handleLine(params[1],params[2],params[3],params[4])
+ self.cometLogic.handleEndKML()
+ self.postComet()
+
+ def updateMapMetaData( self, ctrlat, ctrlng, zoom, showx, showy ):
+ self.NOW_MAP_CENTER_LAT = ctrlat
+ self.NOW_MAP_CENTER_LNG = ctrlng
+ self.NOW_MAP_ZOOM = zoom
+ # calculate where to put media overlay
+ showx = int(showx) + 60
+ showy = int(showy) + 425
+ if(showx > 60) and (showx < 700) and (showy > 425) and (showy < 600):
+ # open media overlay
+ if(self.mediaType == "photo"):
+ # mediaWindow contains a photo
+ self.mediaWindow.show()
+ self.smartMove(self.mediaWindow, self.htmlScale * showx, self.htmlScale * showy )
+ else:
+ # popWindow contains a video player
+ self.popWindow.show()
+ self.smartMove(self.popWindow, self.htmlScale * showx, self.htmlScale * showy )
+ self.popWindow.hide()
+ self.mediaWindow.show_all()
+ else:
+ self.mediaWindow.hide()
+ self.popWindow.hide()
+ if(self.maptube is not None):
+ # update XO group location
+ self.maptube.SendText("loc," + ctrlat + "," + ctrlng + "," + zoom)
+
+ def _trayMouseCb( self, events, args):
+ if (self.shownRecd != None):
+ self.hideMedia()
+ self.preComet()
+ self.cometLogic.handleClear( )
+ self.postComet()
+
+ return True
+
+ def _tagsBufferEditedCb(self, widget):
+ if (self.shownSave != None):
+ txt = self.tagsBuffer.get_text( self.tagsBuffer.get_start_iter(), self.tagsBuffer.get_end_iter() )
+ if (txt != self.shownSave.notes):
+ self.shownSave.notes = txt
+
+ def _hideInfoCb( self, butt ):
+ self.hideSearchResult()
+
+ def _hideScienceCb( self, button ):
+ #self.shownSave = None
+ self.scienceWindow.set_property("accept-focus", False)
+ self.moveWinOffscreen( self.scienceWindow )
+ self.enableNavigation()
+
+ def hideSearchResult(self):
+ #self.shownSave = None
+ self.infoWindow.set_property("accept-focus", False)
+ self.moveWinOffscreen( self.infoWindow )
+ self.enableNavigation()
+
+ def showFileLoadBlocker( self, show ):
+ if (show):
+ self.smartMove( self.selectWindow, 0, 0 )
+ self.smartResize( self.selectWindow, gtk.gdk.screen_width(), gtk.gdk.screen_height() )
+ else:
+ self.moveWinOffscreen( self.selectWindow )
+
+
+ def _sizeAllocateCb( self, widget, event ):
+ self.toolbox.disconnect( self.TOOLBOX_SIZE_ALLOCATE_ID)
+
+ self.smartMove(self.loadingWindow, 0, self.toolbox.allocation.height)
+ self.loadingWindow.resize( gtk.gdk.screen_width(), gtk.gdk.screen_height()-(self.toolbox.allocation.height) )
+ return False
+
+
+ def remoteServerActive(self, loaded):
+ if (self.loaded):
+ return
+ else:
+ self.loaded = loaded
+ #add search results from load_file
+ for i in range(0, len(self.m.savedMaps)):
+ smap = self.m.savedMaps[i]
+ self.addThumb( smap, False )
+ self.searchToolbar.set_sensitive(True)
+ self.addToolbar.set_sensitive(True)
+ browserW = self.browseBox.allocation.width
+ browserH = self.browseBox.allocation.height
+ self.smartResize( self.infoWindow, browserW, browserH )
+ self.moveWinOffscreen(self.loadingWindow)
+
+
+ def addToWindowStack( self, win, parent ):
+ self.windowStack.append( win )
+ win.set_transient_for( parent )
+ win.set_type_hint( gtk.gdk.WINDOW_TYPE_HINT_DIALOG )
+ win.set_decorated( False )
+ win.set_focus_on_map( False )
+ win.set_property("accept-focus", False)
+
+
+ def moveWinOffscreen( self, win ):
+ #we move offscreen to resize or else we get flashes on screen, and setting hide() doesn't allow resize & moves
+ offW = (gtk.gdk.screen_width() + 100)
+ offH = (gtk.gdk.screen_height() + 100)
+ self.smartMove(win, offW, offH)
+
+
+ def smartMove( self, win, x, y ):
+ winLoc = win.get_position()
+ if ( (winLoc[0] != x) or (winLoc[1] != y) ):
+ win.move( int(x), int(y) )
+ return True
+ else:
+ return False
+
+
+ def smartResize( self, win, w, h ):
+ winSize = win.get_size()
+ if ( (winSize[0] != w) or (winSize[1] != h) ):
+ win.resize( w, h )
+ return True
+ else:
+ return False
+
+
+ def preComet(self):
+ self.cond.acquire()
+
+
+ def postComet(self):
+ self.cond.notifyAll()
+ self.cond.release()
+ time.sleep(.1)
+
+
+ def read_file(self, file):
+ serialize.fillMediaHash(file, self.m)
+
+
+ def close( self ):
+ self.hide()
+ activity.Activity.close( self )
+
+
+ def write_file(self, file):
+ dom = serialize.saveMediaHash(self.m)
+ xmlFile = open( file, "w" )
+ dom.writexml(xmlFile)
+ xmlFile.close()
+
+ def mapPaste(self):
+ self.preComet()
+ self.cometLogic.mapPaste('osm')
+ self.postComet()
+
+ def googlePaste(self):
+ self.preComet()
+ self.cometLogic.mapPaste('google')
+ self.postComet()
+
+ def _addressUpdateCb( self, otets, address ):
+ #Special to OfflineMaps - decide the latlng of the given address
+ if(address.find(',') != -1):
+ if(len(address.split(',')) == 3):
+ addSplit = address.split(',')
+ if(addSplit[2] == 'm'):
+ # add a marker at this lat,lng
+ self.preComet()
+ self.cometLogic.handleAddMarker(addSplit[0],addSplit[1],addSplit[0]+","+addSplit[1],"magenta")
+ self.cometLogic.handleEndKML()
+ self.postComet()
+ self.addInfoMarker(addSplit[0],addSplit[1],addSplit[0]+","+addSplit[1],"magenta",True)
+ return
+ latlng = address
+ else:
+ address = address.lower().replace(' ','').replace('-','')
+ zoom="16"
+ lat="-190"
+ lng="-190"
+ places = open(self.libPath + "Places.csv", 'r')
+ for place in places:
+ pData = place.split(',')
+ pData[0] = pData[0].lower().replace(' ','').replace('-','')
+ if(pData[0] == address):
+ lat = pData[1]
+ lng = pData[2]
+ if(pData[3] != "-1"):
+ zoom = pData[3]
+ break
+ if(lat=="-190"):
+ return
+ latlng = lat + "," + lng + "," + zoom
+ self.preComet()
+ self.cometLogic.handleAddressUpdate(latlng)
+ self.postComet()
+
+ def _searchUpdateCb( self, otets, tags ):
+ self.NOW_MAP_TAGS = tags
+ self.preComet()
+ self.cometLogic.handleTagSearch(tags)
+ self.postComet()
+
+ def _zoomInCb( self, butt ):
+ self.hideMedia()
+ self.preComet()
+ self.cometLogic.handleZoomUpdate("+")
+ self.postComet()
+
+ def _zoomOutCb( self, butt ):
+ self.hideMedia()
+ self.preComet()
+ self.cometLogic.handleZoomUpdate("-")
+ self.postComet()
+
+ def _saveSearchCb( self, otets ):
+ self._saveSearch()
+
+ def _saveSearch( self ):
+ #1st: cover up the tray when grabbing a screenshot
+ #this will take a moment, so disable all buttons while we wait
+ self.SAVING_SEARCH = False
+ #self.SAVING_SEARCH = True
+ #self.disableNavigation()
+ #self.savingSearchMediaLoc = self.mediaWindow.get_position()
+ #self.moveWinOffscreen( self.mediaWindow )
+ #self.hideTray(True)
+
+
+ def hideTray( self, hide ):
+ if (hide):
+ self.trayBox.set_current_page(self.fakeTrayIndex)
+ else:
+ self.trayBox.set_current_page(self.realTrayIndex)
+
+
+ def _fakeTrayVisibleNotifyCb( self, widget, event ):
+ #if (event.state == gtk.gdk.VISIBILITY_UNOBSCURED): #this is not used, bc a popup might be over it
+ if (self.SAVING_SEARCH):
+ gobject.idle_add( self._saveSearch2 )
+
+ def _saveSearch2( self ):
+ # add SavedMap
+ window = self.canvas.window
+ width, height = window.get_size()
+ screenshot = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=False, bits_per_sample=8, width=width, height=height)
+ screenshot.get_from_drawable(window, window.get_colormap(), 0, 0, 0, 0, width, height)
+
+ popW, popH = [self.popW+(2*self.popB), self.popH+(2*self.popB) ]
+ popScreenshot = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=False, bits_per_sample=8, width=width, height=height)
+ popScreenshot.get_from_drawable(self.popWindow.window, self.popWindow.window.get_colormap(), 0, 0, 0, 0, popW, popH)
+
+ popLoc = self.popWindow.get_position()
+ popLocX = popLoc[0]
+ popLocY = popLoc[1] - self.toolbox.allocation.height
+ popScreenshot.composite(screenshot,
+ popLocX, popLocY, #move the actual mini-canvas (but not its internals (?))
+ popW, popH, #actual size of the mini-canvas
+ popLocX, popLocY, #offx, y of the painting (irrespective of the mini-canvas)
+ 1, 1, #scalex,y
+ gtk.gdk.INTERP_BILINEAR, 255)
+
+ screenshotPath = os.path.join(Instance.instancePath, "savedMap.jpg")
+ screenshotPath = utils.getUniqueFilepath(screenshotPath, 0)
+ screenshot.save( screenshotPath, "jpeg", {} )
+
+ smallHeight = 120
+ smallWidth = (width*smallHeight) / height
+ screenshotSmall = screenshot.scale_simple(smallWidth, smallHeight, gtk.gdk.INTERP_NEAREST)
+ screenshotSmallPath = os.path.join(Instance.instancePath, "savedMapThumb.jpg")
+ screenshotSmallPath = utils.getUniqueFilepath(screenshotSmallPath, 0)
+ screenshotSmall.save( screenshotSmallPath, "jpeg", {} )
+
+ sm = SavedMap( self.NOW_MAP_CENTER_LAT, self.NOW_MAP_CENTER_LNG, self.NOW_MAP_ZOOM, screenshotPath, screenshotSmallPath, "Describe the map", self.NOW_MAP_TAGS )
+ if (self.shownRecd != None):
+ sm.addViewedRecd(self.shownRecd.datastoreId, self.shownRecd.latitude, self.shownRecd.longitude)
+
+ self.m.addSavedMap( sm )
+ self.addThumb( sm, True )
+
+ self.hideTray(False)
+ self.enableNavigation()
+ self.smartMove( self.mediaWindow, self.savingSearchMediaLoc[0], self.savingSearchMediaLoc[1] )
+
+ if(self.maptube is not None):
+ self.maptube.SendText("smap," + str(self.NOW_MAP_CENTER_LAT) + "," + str(self.NOW_MAP_CENTER_LNG) + "," + str(self.NOW_MAP_ZOOM) + ",Describe this map")
+
+ def addLine(self,id,color,thickness,pts):
+ self.m.setLine(id,color,thickness,pts)
+ if(self.maptube is not None):
+ self.maptube.SendText("line," + id + "," + color + "," + thickness + "," + pts)
+
+ def lineMode(self,type):
+ self.preComet()
+ self.cometLogic.lineMode(type)
+ self.postComet()
+
+ def addSavedMap(self,lat,lng,zoom,info,isNew):
+ # add SavedMap received from internet or other XO, use Google Static Maps
+ window = self.canvas.window
+ width, height = window.get_size()
+ screenshotPath = os.path.join(Instance.instancePath, "savedMap.jpg")
+ urllib.urlretrieve("http://maps.google.com/staticmap?key=ABQIAAAAxkKtrWN5q-vPTLRVmO_r6RRFDCLHCbUG3VrjXnZmMRXvQdFL3RS-b-ld9hTrkIgQlYsxPQ1kYq6y9A&size=200x120&format=jpg&center=" + str(lat) + "," + str(lng) + "&zoom=" + str(int(zoom)-1), screenshotPath)
+ smallHeight = 120
+ smallWidth = 200
+ sm = SavedMap( lat, lng, zoom, screenshotPath, screenshotPath, info, "" )
+ self.m.addSavedMap( sm )
+ self.addThumb( sm, True )
+ if(isNew == True):
+ if(self.maptube is not None):
+ # user created SaveMap, send to others
+ self.maptube.SendText("smap," + lat + "," + lng + "," + zoom + "," + info)
+
+ def addInfoMarker(self,lat,lng,info,icon,sendUpdate):
+ # add/update InfoMarker in model
+ self.m.setInfo(lat, lng, info, icon)
+ if(sendUpdate == True):
+ if(self.maptube is not None):
+ # send added/updated info marker
+ self.maptube.SendText("mar," + lat + "," + lng + "," + info + "," + icon)
+ else:
+ # put InfoMarker on own map
+ self.preComet()
+ self.cometLogic.handleAddMarker(lat,lng,info,icon)
+ self.cometLogic.handleEndKML()
+ self.postComet()
+
+ def joinGroup(self,groupName):
+ #groupFile = urllib.urlopen("http://bluebird-science.appspot.com/group/data?name=" + groupName)
+ #readError = 0
+ groupData = '{}'
+ #while readError == 0:
+ # try:
+ # groupData = groupData + groupFile.readline()
+ # except:
+ # readError = 1
+ self.preComet()
+ self.cometLogic.handleGroupData(groupData)
+ self.postComet()
+
+ def removeThumb( self, sm ):
+ kids = self.tray.get_children()
+ for i in range (0, len(kids)):
+ if (kids[i].data == sm):
+ self.tray.remove_item(kids[i])
+ kids[i].cleanUp()
+ kids[i].disconnect( kids[i].getButtClickedId() )
+ kids[i].setButtClickedId(0)
+ if (sm == self.shownSave):
+ self.hideSearchResult()
+ self.m.savedMaps.remove(sm)
+
+ def addThumb( self, sm, forceScroll ):
+ butt = SavedButton( self, sm )
+ BUTT_CLICKED_ID = butt.connect( "clicked", self._thumbClickedCb, sm )
+ butt.setButtClickedId(BUTT_CLICKED_ID)
+ self.tray.add_item( butt, len(self.tray.get_children()) )
+ butt.show()
+ if (forceScroll):
+ self.tray.scroll_to_end()
+
+ #1 way to see a sr
+ def _thumbClickedCb( self, button, smap ):
+ self.showSearchResult(smap)
+ self.preComet()
+ self.cometLogic.handleSavedMap(smap.lat,smap.lng,smap.zoom,smap.notes)
+ self.postComet()
+
+ def showSearchResult( self, smap ):
+ self.shownSave = smap
+ self.updateSearchResultTags(smap)
+ self.searchToolbar._setTagsString( smap.tags )
+
+ def updateSearchResultTags( self, smap ):
+ self.shownSave = smap
+ self.lngValueLabel.set_text( str(smap.lng) )
+ self.latValueLabel.set_text( str(smap.lat) )
+ self.tagsBuffer.set_text( smap.notes )
+
+ def _fillInInfoWindow( self ):
+ hbox = gtk.HBox()
+ self.infoWindow.add(hbox)
+ clr = Constants.colorGrey
+ inset = 10
+ self.infoWindow.modify_bg( gtk.STATE_NORMAL, clr.gColor )
+ ltBox = gtk.VBox()
+ ltBox.set_border_width(inset)
+ rtBox = gtk.VBox()
+ hbox.pack_start(ltBox)
+ hbox.pack_start(rtBox, expand=False)
+ latHBox = gtk.HBox()
+ ltBox.pack_start(latHBox, expand=False)
+ latLabel = gtk.Label("<b>" + Constants.istrLatitude + "</b> ")
+ latLabel.set_use_markup(True)
+ latLabel.set_alignment(0, .5)
+ latHBox.pack_start(latLabel, expand=False)
+ self.latValueLabel = gtk.Label()
+ self.latValueLabel.set_alignment(0, .5)
+ latHBox.pack_start(self.latValueLabel)
+ fillb1 = gtk.HBox()
+ fillb1.set_size_request(inset,inset)
+ ltBox.pack_start(fillb1, expand=False)
+ lngHBox = gtk.HBox()
+ ltBox.pack_start(lngHBox, expand=False)
+ lngLabel = gtk.Label("<b>" + Constants.istrLongitude + "</b> ")
+ lngLabel.set_use_markup(True)
+ lngLabel.set_alignment(0, .5)
+ lngHBox.pack_start(lngLabel, expand=False)
+ self.lngValueLabel = gtk.Label()
+ self.lngValueLabel.set_alignment(0, .5)
+ lngHBox.pack_start(self.lngValueLabel)
+ fillb2 = gtk.HBox()
+ fillb2.set_size_request(inset,inset)
+ ltBox.pack_start(fillb2, expand=False)
+ tagLabelBox = gtk.HBox()
+ ltBox.pack_start(tagLabelBox, expand=False)
+ tagsLabel = gtk.Label("<b>" + Constants.istrTags + "</b>")
+ tagsLabel.set_use_markup(True)
+ tagsLabel.set_alignment(0, .5)
+ tagLabelBox.pack_start(tagsLabel, expand=False)
+ tagBox = gtk.HBox()
+ self.tagsBuffer = gtk.TextBuffer()
+ self.tagsBuffer.set_text("please edit me!")
+ self.tagsBuffer.connect('changed', self._tagsBufferEditedCb)
+ self.tagsField = gtk.TextView(self.tagsBuffer)
+ ltBox.pack_start(self.tagsField, expand=True)
+ rtFill = gtk.VBox()
+ rtFill.set_spacing( 0 )
+ rtFill.set_border_width( 0 )
+ rtBox.pack_start(rtFill, expand=True)
+ buttImg = gtk.Image()
+ buttPixb = Constants.infoOnSvg.get_pixbuf()
+ buttImg.set_from_pixbuf( buttPixb )
+ buttImg.show()
+ rtButt = gtk.Button()
+ rtButt.set_image(buttImg)
+ rtButt.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ rtButt.modify_bg( gtk.STATE_ACTIVE, Constants.colorBlack.gColor )
+ rtButt.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
+ rtButt.set_relief(gtk.RELIEF_NONE)
+ rtButt.set_property('can-default', True)
+ rtButt.set_property('can-focus', False)
+ rtButt.set_property('yalign', 1)
+ rtButt.set_size_request( 75, 75 )
+ rtButt.connect("clicked", self._hideInfoCb)
+ grr = gtk.EventBox()
+ grr.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ grr.add(rtButt)
+ rtBox.pack_start(grr, expand=False)
+ hbox.show_all()
+
+ def _fillInScienceWindow( self ):
+ hbox = gtk.HBox()
+ self.scienceWindow.add(hbox)
+ clr = Constants.colorGrey
+ inset = 10
+ self.scienceWindow.modify_bg( gtk.STATE_NORMAL, clr.gColor )
+ ltBox = gtk.VBox()
+ ltBox.set_border_width(inset)
+ rtBox = gtk.VBox()
+ hbox.pack_start(ltBox)
+ hbox.pack_start(rtBox, expand=False)
+ latHBox = gtk.HBox()
+ ltBox.pack_start(latHBox, expand=False)
+ latLabel = gtk.Label("<b>" + Constants.istrLatitude + "</b> ")
+ latLabel.set_use_markup(True)
+ latLabel.set_alignment(0, .5)
+ latHBox.pack_start(latLabel, expand=False)
+ self.latValueLabel = gtk.Label()
+ self.latValueLabel.set_alignment(0, .5)
+ latHBox.pack_start(self.latValueLabel)
+ fillb1 = gtk.HBox()
+ fillb1.set_size_request(inset,inset)
+ ltBox.pack_start(fillb1, expand=False)
+ lngHBox = gtk.HBox()
+ ltBox.pack_start(lngHBox, expand=False)
+ lngLabel = gtk.Label("<b>" + Constants.istrLongitude + "</b> ")
+ lngLabel.set_use_markup(True)
+ lngLabel.set_alignment(0, .5)
+ lngHBox.pack_start(lngLabel, expand=False)
+ self.lngValueLabel = gtk.Label()
+ self.lngValueLabel.set_alignment(0, .5)
+ lngHBox.pack_start(self.lngValueLabel)
+ fillb2 = gtk.HBox()
+ fillb2.set_size_request(inset,inset)
+ ltBox.pack_start(fillb2, expand=False)
+ tagLabelBox = gtk.HBox()
+ ltBox.pack_start(tagLabelBox, expand=False)
+ tagsLabel = gtk.Label("<b>" + Constants.istrTags + "</b>")
+ tagsLabel.set_use_markup(True)
+ tagsLabel.set_alignment(0, .5)
+ tagLabelBox.pack_start(tagsLabel, expand=False)
+ tagBox = gtk.HBox()
+ self.tagsBuffer = gtk.TextBuffer()
+ self.tagsBuffer.set_text("please edit me!")
+ self.tagsBuffer.connect('changed', self._tagsBufferEditedCb)
+ self.tagsField = gtk.TextView(self.tagsBuffer)
+ ltBox.pack_start(self.tagsField, expand=True)
+ rtFill = gtk.VBox()
+ rtFill.set_spacing( 0 )
+ rtFill.set_border_width( 0 )
+ rtBox.pack_start(rtFill, expand=True)
+ buttImg = gtk.Image()
+ buttPixb = Constants.infoOnSvg.get_pixbuf()
+ buttImg.set_from_pixbuf( buttPixb )
+ buttImg.show()
+ rtButt = gtk.Button()
+ rtButt.set_image(buttImg)
+ rtButt.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ rtButt.modify_bg( gtk.STATE_ACTIVE, Constants.colorBlack.gColor )
+ rtButt.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
+ rtButt.set_relief(gtk.RELIEF_NONE)
+ rtButt.set_property('can-default', True)
+ rtButt.set_property('can-focus', False)
+ rtButt.set_property('yalign', 1)
+ rtButt.set_size_request( 75, 75 )
+ rtButt.connect("clicked", self._hideScienceCb)
+ grr = gtk.EventBox()
+ grr.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ grr.add(rtButt)
+ rtBox.pack_start(grr, expand=False)
+ hbox.show_all()
+
+ def showSearchResultTags( self, smap ):
+ self.updateSearchResultTags( smap )
+
+ #avoid popups lingering
+ self.hideMedia()
+ self.preComet()
+ self.cometLogic.handleClear()
+ self.postComet()
+ self.disableNavigation()
+ browserLoc = self.browseBox.translate_coordinates(self, 0, 0)
+ self.infoWindow.show_all()
+ self.smartMove(self.infoWindow, browserLoc[0], browserLoc[1])
+ browserW = self.browseBox.allocation.width
+ browserH = self.browseBox.allocation.height
+ self.smartResize(self.infoWindow, browserW, browserH)
+ self.infoWindow.set_property("accept-focus", True)
+
+ def copyToClipboard( self, smap ):
+ tmpImgPath = self.doClipboardCopyStart( smap )
+ gtk.Clipboard().set_with_data( [('text/uri-list', 0, 0)], self._clipboardGetFuncCb, self._clipboardClearFuncCb, tmpImgPath )
+ return True
+
+ def doClipboardCopyStart( self, smap ):
+ tmpImgPath = utils.getUniqueFilepath(smap.imgPath, 0)
+ shutil.copyfile( smap.imgPath, tmpImgPath )
+ return tmpImgPath
+
+ def doClipboardCopyCopy( self, tmpImgPath, selection_data ):
+ tmpImgUri = "file://" + tmpImgPath
+ selection_data.set( "text/uri-list", 8, tmpImgUri )
+
+ def doClipboardCopyFinish( self, tmpImgPath ):
+ if (tmpImgPath != None):
+ if (os.path.exists(tmpImgPath)):
+ os.remove( tmpImgPath )
+ tmpImgPath = None
+
+ def _clipboardGetFuncCb( self, clipboard, selection_data, info, data):
+ self.doClipboardCopyCopy( data, selection_data )
+
+ def _clipboardClearFuncCb( self, clipboard, data):
+ self.doClipboardCopyFinish( data )
+
+ def _addMediaCb( self, ot, datastoreOb ):
+ self.addMeToMapObj = datastoreOb
+ self.hideMedia()
+ self.preComet()
+ self.cometLogic.handlePreAdd()
+ self.postComet()
+
+ def _addKMLCb( self, ot, datastoreOb ):
+ kml = open(datastoreOb.file_path, 'r')
+ firstLine = kml.readline()
+ if(firstLine.find('<?xml version="1.0" ?><map')==0):
+ # Offline Map or regular Map file without meta-data
+ self.preComet()
+ xmlarray = firstLine.split('>')
+ for node in xmlarray:
+ #if(node.find('<mapItem') == 0):
+ # picture marker
+ if(node.find('<infoMarker') == 0):
+ # info marker
+ lat = node[node.find('lat="')+5:len(node)]
+ lat = lat[0:lat.find('"')]
+ lng = node[node.find('lng="')+5:len(node)]
+ lng = lng[0:lng.find('"')]
+ description = node[node.find('info="')+6:len(node)]
+ description = description[0:description.find('"')]
+ icon = node[node.find('icon="')+6:len(node)]
+ icon = icon[0:icon.find('"')]
+ self.cometLogic.handleAddMarker(lat,lng,description,icon)
+ self.addInfoMarker(lat,lng,description,icon,True)
+ elif(node.find('<line') == 0):
+ # line
+ lineColor = node[node.find('lcolor="')+8:len(node)]
+ lineColor = lineColor[0:lineColor.find('"')]
+ lineID = node[node.find('lid="')+5:len(node)]
+ lineID = lineID[0:lineID.find('"')]
+ ptStr = node[node.find('lpts="')+6:len(node)]
+ ptStr = ptStr[0:ptStr.find('"')]
+ lineThick = node[node.find('lthickness="')+12:len(node)]
+ lineThick = lineThick[0:lineThick.find('"')]
+ self.cometLogic.handleLine(lineID,lineColor,lineThick,ptStr)
+ elif(node.find('<savedMap') == 0):
+ # saved map
+ lat = node[node.find('lat="')+5:len(node)]
+ lat = lat[0:lat.find('"')]
+ lng = node[node.find('lng="')+5:len(node)]
+ lng = lng[0:lng.find('"')]
+ notes = node[node.find('notes="')+7:len(node)]
+ notes = notes[0:notes.find('"')]
+ zoom = node[node.find('zoom="')+6:len(node)]
+ zoom = zoom[0:zoom.find('"')]
+ self.addSavedMap(lat,lng,zoom,notes,True)
+ self.cometLogic.handleEndKML()
+ self.postComet()
+ return
+ placename = None
+ description = None
+ lat = None
+ lng = None
+ coordList = None
+ placemarkType = None
+ lineType = None
+ stillDescribing = False
+ morePts = False
+ dataTable = None
+ isOSM = False
+ isGeoRSS = False
+ self.preComet()
+ for kmlline in kml:
+ if((kmlline.find('<osm') != -1) and (lat == None) and (lng == None) and (coordList == None)):
+ # jump to osm
+ isOSM = True
+ break
+ elif((kmlline.find('georss') != -1) and (lat == None) and (lng == None) and (coordList == None)):
+ # jump to GeoRSS
+ isGeoRSS = True
+ break
+ if(kmlline.find('<Point') != -1):
+ placemarkType = 'info'
+ elif(kmlline.find('<LineString') != -1):
+ placemarkType = 'line'
+ lineType = 'line'
+ elif(kmlline.find('<Polygon') != -1):
+ placemarkType = 'poly'
+ lineType = 'poly'
+ #elif(kmlline.find('<GroundOverlay') != -1):
+ # placemarkType = 'overlay'
+ if(kmlline.find('<ExtendedData') != -1):
+ dataTable = '<table>'
+ elif(kmlline.find('</ExtendedData>') != -1):
+ dataTable = dataTable + '</table>'
+ if(dataTable is not None):
+ if(kmlline.find('<Data') != -1):
+ dataTable = dataTable + '<tr>'
+ if(kmlline.find('<Data name="') != -1):
+ dataTable = dataTable + "<td><b>" + kmlline[kmlline.find('<Data name="')+12:kmlline.rfind('"')] + "</b></td>"
+ if(kmlline.find('<value>') != -1):
+ dataTable = dataTable + "<td>" + kmlline[kmlline.find('<value>')+7:kmlline.find('</value>')] + "</td>"
+ elif(kmlline.find('</Data>') != -1):
+ dataTable = dataTable + '</tr>'
+
+ if(kmlline.find('<coordinates>') != -1):
+ if(placemarkType == 'info'):
+ lng = kmlline[kmlline.find('<coordinates>')+13:kmlline.find(',')]
+ lat = kmlline[kmlline.find(',')+1:kmlline.rfind(',')]
+ else:
+ if(kmlline.find('</coordinates>') == -1):
+ morePts = True
+ kmlline = kmlline.replace('<coordinates>','').replace('</coordinates>','')
+ coordList = kmlline.split(' ')
+ elif(morePts == True):
+ if(kmlline.find('</coordinates>') != -1):
+ morePts = False
+ kmlline = kmlline.replace('</coordinates>','')
+ coordList.extend(kmlline.split(' '))
+
+ if(kmlline.find('<name>') != -1):
+ placename = kmlline[kmlline.find('<name>')+6:kmlline.find('</name>')]
+ if(kmlline.find('<description>') != -1):
+ description = kmlline[kmlline.find('<description>')+13:len(kmlline)]
+ if(kmlline.find('</description>') == -1):
+ # need to read in more lines
+ stillDescribing = True
+ else:
+ description = description[0:description.find('</description')]
+ if(description.find('<![CDATA[') != -1):
+ description = description.replace('<![CDATA[','').replace(']]>','')
+ elif(stillDescribing == True):
+ description = description + kmlline
+ if(kmlline.find('</description>') != -1):
+ stillDescribing = False
+ description = description[0:description.find('</description')]
+ if(description.find('<![CDATA[') != -1):
+ description = description.replace('<![CDATA[','').replace(']]>','')
+
+ if((kmlline.find('</Placemark>') != -1) or (kmlline.find('<Placemark>') != -1)):
+ # finish this placemark
+ if(lat is not None):
+ if(description is None):
+ description = ""
+ if(dataTable is not None):
+ description = dataTable + description
+ if(placename is not None):
+ description = '<h3>' + placename + '</h3>' + description
+ self.cometLogic.handleAddMarker(lat,lng,description,"magenta")
+ self.addInfoMarker(lat,lng,description,"magenta",True)
+ if(coordList is not None):
+ ptList = []
+ maxInterval = 1
+ if(len(coordList) > 50):
+ maxInterval = int(len(coordList) / 50)
+ interval = 1
+ for pt in coordList:
+ ptloc = pt.split(',')
+ interval = interval - 1
+ if((len(ptloc) > 1) and (interval < 2)):
+ ptList.append(ptloc[1]+"|"+ptloc[0])
+ interval = maxInterval
+ if(lineType == 'line'):
+ self.cometLogic.handleLine(str(random.getrandbits(128)),"B22222","5",'|'.join(ptList))
+ elif(lineType == 'poly'):
+ self.cometLogic.handleLine("poly-" + str(random.getrandbits(128)),"7CFC00","5",'|'.join(ptList))
+ #elif(placemarkType == 'overlay'):
+ lat = None
+ lng = None
+ description = None
+ placename = None
+ placemarkType = None
+ coordList = None
+ dataTable = None
+ if(isOSM == True):
+ # read in an OSM style file
+ osmNodes = {}
+ readingInNodeID = None
+ readingInWayID = None
+ for osm in kml:
+ if(osm.find('<node') != -1):
+ # point, which may be part of a line
+ nodeID = osm[osm.find('id=')+4:len(osm)]
+ nodeID = nodeID[0:nodeID.find('"')]
+ nodeID = nodeID[0:nodeID.find("'")]
+ lat = osm[osm.find('lat=')+5:len(osm)]
+ lat = lat[0:lat.find("'")]
+ lat = lat[0:lat.find('"')]
+ lng = osm[osm.find('lon=')+5:len(osm)]
+ lng = lng[0:lng.find("'")]
+ lng = lng[0:lng.find('"')]
+ osmNodes[nodeID] = {"latitude":lat,"longitude":lng,"inWay":False,"isWay":False}
+ if(osm.find('/>') == -1):
+ readingInNodeID = nodeID
+ elif(readingInNodeID is not None):
+ # reading in tag-values for the point
+ if(osm.find('</node>') != -1):
+ readingInNodeID = None
+ else:
+ if(osm.find('<tag') != -1):
+ tName = osm[osm.find('k=')+3:len(osm)]
+ tName = tName[0:tName.find('"')]
+ tName = tName[0:tName.find("'")]
+ vName = osm[osm.find('v=')+3:len(osm)]
+ vName = vName[0:vName.find('"')]
+ vName = vName[0:vName.find("'")]
+ osmNodes[readingInNodeID][tName] = vName
+ elif(osm.find('<way') != -1):
+ # line, composed of multiple points
+ readingInWayID = osm[osm.find('id=')+4:len(osm)]
+ readingInWayID = readingInWayID[0:readingInWayID.find('"')]
+ readingInWayID = readingInWayID[0:readingInWayID.find("'")]
+ osmNodes[readingInWayID] = {"isWay":True,"pts":[]}
+ elif(readingInWayID is not None):
+ # reading in node ids composing the line
+ if(osm.find('</way>') != -1):
+ readingInWayID = None
+ else:
+ if(osm.find('<nd') != -1):
+ nodeRef = osm[osm.find('ref=')+5:len(osm)]
+ if(nodeRef.find('"') != -1):
+ nodeRef = nodeRef[0:nodeRef.find('"')]
+ else:
+ nodeRef = nodeRef[0:nodeRef.find("'")]
+ osmNodes[readingInWayID]["pts"].append(nodeRef)
+ osmNodes[nodeRef]["inWay"] = True
+ # decide how to publish nodes
+ for node in osmNodes:
+ if(osmNodes[node]["isWay"] == True):
+ # line composed of points
+ ptList=[]
+ for pID in osmNodes[node]["pts"]:
+ ptList.append(osmNodes[pID]["latitude"]+"|"+osmNodes[pID]["longitude"])
+ self.cometLogic.handleLine(str(random.getrandbits(128)),"B22222","5",'|'.join(ptList))
+ elif(osmNodes[node]["inWay"] == False):
+ # independent point
+ description="<table>"
+ for tag in osmNodes[node]:
+ if(tag=="latitude"):
+ continue
+ elif(tag=="longitude"):
+ continue
+ elif(tag=="inWay"):
+ continue
+ elif(tag=="isWay"):
+ continue
+ else:
+ value = osmNodes[node][tag]
+ description=description+"<tr><td>"+tag+"</td><td>"+str(value)+"</td></tr>"
+ description=description+"</table>"
+ self.cometLogic.handleAddMarker(osmNodes[node]["latitude"],osmNodes[node]["longitude"],description,"magenta")
+ self.addInfoMarker(osmNodes[node]["latitude"],osmNodes[node]["longitude"],description,"magenta",True)
+ elif(isGeoRSS == True):
+ # scan in GeoRSS
+ placeLink = None
+ lat = None
+ lng = None
+ description = None
+ placename = None
+ stillDescribing = False
+ #self.preComet()
+ for rss in kml:
+ if(rss.find('<title') != -1):
+ placename = rss[rss.find('>')+1:rss.find('</title>')]
+ if(rss.find('<link') != -1):
+ placeLink = rss[rss.find('>')+1:rss.find('</link>')]
+ if(rss.find('<georss:point') != -1):
+ latlng = rss[rss.find('>')+1:rss.find('</georss:point>')].split(' ')
+ lat = latlng[0]
+ lng = latlng[1]
+ if(rss.find('<description>') != -1):
+ description = rss[rss.find('<description>')+13:len(rss)]
+ if(rss.find('</description>') == -1):
+ # need to read in more lines
+ stillDescribing = True
+ else:
+ description = description[0:description.find('</description')]
+ elif(stillDescribing == True):
+ description = description + rss
+ if(rss.find('</description>') != -1):
+ stillDescribing = False
+ description = description[0:description.find('</description')]
+ if(rss.find('</item>') != -1):
+ # publish the point
+ if(lat is not None):
+ if(description == None):
+ description = ""
+ if(placeLink is not None):
+ description = placeLink + "<br/>" + description
+ if(placename is not None):
+ description = '<h3>' + placename + '</h3>' + description
+ description=description.replace("'","\\'").replace('"','\\"')
+ description=description.replace("\n","<br/>").replace("\r","<br/>")
+ description=description.replace("<![CDATA[","").replace("]]>","")
+ self.cometLogic.handleAddMarker(lat,lng,description,"magenta")
+ self.addInfoMarker(lat,lng,description,"magenta",True)
+ placeLink = None
+ lat = None
+ lng = None
+ placename = None
+ description = None
+ stillDescribing = False
+ self.cometLogic.handleEndKML()
+ self.postComet()
+
+ def _addInfoCb(self,ot):
+ self.preComet()
+ self.cometLogic.handlePreAddInfo()
+ self.postComet()
+
+ def _measureCb(self,ot):
+ self.preComet()
+ self.cometLogic.handleMeasure()
+ self.postComet()
+
+ def disableNavigation( self ):
+ #disable the toolbar buttons
+ self.addToolbar.set_sensitive( False )
+ self.searchToolbar.set_sensitive( False )
+
+ def enableNavigation( self ):
+ #enable the toolbar buttons
+ self.addToolbar.set_sensitive( True )
+ self.searchToolbar.set_sensitive( True )
+
+ def placeAddMedia( self, lat, lng ):
+ self.enableNavigation()
+ #append to the map
+ recd = self.m.addMedia( lat, lng, self.addMeToMapObj )
+ #now show the new addition to the map
+ self.preComet()
+ self.cometLogic.handlePostAdd( recd )
+ self.postComet()
+ # send image to others
+ #if(self.maptube is not None):
+ # self.maptube.SendText("mar," + lat + "," + lng + "," + utils.getStringFromPixbuf(recd.getPixBuf()) )
+
+ def _deleteMediaCb( self, ot ):
+ self.preComet()
+ self.cometLogic.handleDelete()
+ self.postComet()
+
+ def showMedia(self, id, x, y, up, rt):
+ recd = self.m.getMediaByThumbFilename(id)
+ if (recd == None):
+ return
+ self.shownRecd = recd
+ thumbPath = recd.getThumbPath()
+ thumbPixbuf = gtk.gdk.pixbuf_new_from_file(thumbPath)
+ px, py = [self.popW, self.popH]
+ scl = (self.popW+0.0) / (thumbPixbuf.get_width()+0.0)
+ thumbCairo = utils.generateThumbnail( thumbPixbuf, scl, px, py )
+ self.popUpBg.updatePopInfo( recd.colorStroke, recd.colorFill, thumbCairo, up, rt, self.popB, self.popB, self.htmlScale )
+ browserLoc = self.browseBox.translate_coordinates( self, 0, 0 )
+ x = int(x) * self.htmlScale
+ y = int(y) * self.htmlScale
+ x = browserLoc[0]+x
+ y = browserLoc[1]+y
+ if (not rt):
+ x = x - (self.popW + (2*self.popB))
+ if (not up):
+ y = y - (self.popH + (2*self.popB))
+ self.smartMove( self.popWindow, x, y )
+ kid = self.mediaWindow.get_child()
+ if (kid != None):
+ self.gplay.stop()
+ self.mediaWindow.remove( kid )
+ self.mediaWindow.modify_bg(gtk.STATE_NORMAL, recd.colorFill.gColor)
+ if (recd.type == Constants.TYPE_PHOTO):
+ self.mediaType = 'photo'
+ self.popWindow.hide()
+ self.mediaWindow.show()
+ self.smartResize( self.mediaWindow, self.popW, self.popH )
+ self.smartMove( self.mediaWindow, x+self.popB, y+self.popB)
+ pbuf = recd.getPixBuf()
+ img = _camera.cairo_surface_from_gdk_pixbuf(pbuf)
+ self.photoCanvas.modify_bg(gtk.STATE_NORMAL, recd.colorFill.gColor)
+ self.photoCanvas.set_size_request(self.popW, self.popH)
+ self.photoCanvas.setImage(img)
+ self.mediaWindow.add(self.photoCanvas)
+ self.photoCanvas.show_all()
+
+ elif (recd.type == Constants.TYPE_VIDEO):
+ self.mediaType = 'video'
+ self.popWindow.show()
+ self.mediaWindow.show()
+ self.smartResize( self.mediaWindow, self.popW, self.popH )
+ self.smartMove( self.mediaWindow, x+self.popB, y+self.popB)
+ self.smartMove( self.popWindow, x+self.popB, y+self.popB)
+ self.mediaWindow.add(self.gplayWin)
+ self.gplayWin.modify_bg(gtk.STATE_NORMAL, recd.colorFill.gColor)
+ self.popWindow.hide()
+ self.mediaWindow.show()
+ self.gplayWin.show_all()
+ videoUrl = "file://" + str( recd.getFilepath() )
+ self.gplay.setLocation( videoUrl )
+ self.gplay.play()
+
+ def hideMedia( self ):
+ self.shownRecd = None
+ self.moveWinOffscreen( self.popWindow )
+ self.moveWinOffscreen( self.mediaWindow )
+ self.gplay.pause()
+
+ def destroy(self, *args):
+ self.hide_all()
+ os._exit(0) #needed to kill all threads
+ gtk.main_quit()
+
+ def _shared_cb(self, activity):
+ self.initiating = True
+ self._sharing_setup()
+ id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(SERVICE, {})
+
+ def _sharing_setup(self):
+ if self._shared_activity is None:
+ return
+ self.conn = self._shared_activity.telepathy_conn
+ self.tubes_chan = self._shared_activity.telepathy_tubes_chan
+ self.text_chan = self._shared_activity.telepathy_text_chan
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', self._new_tube_cb)
+
+ def _list_tubes_reply_cb(self, tubes):
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ # joining a pre-existing activity
+ def _joined_cb(self, activity):
+ if not self._shared_activity:
+ return
+ self.initiating = False
+ self._sharing_setup()
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(reply_handler=self._list_tubes_reply_cb,error_handler=self.tubes_error)
+
+ # display error raised by XO-XO connection on map
+ def tubes_error(self,e):
+ self.preComet()
+ self.cometLogic.handleCompassUpdate("Error: %s",e)
+ self.postComet()
+
+ def _new_tube_cb(self, id, initiator, type, service, params, state):
+ if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE):
+ if state == telepathy.TUBE_STATE_LOCAL_PENDING:
+ self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
+ tube_conn = TubeConnection(self.conn, self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
+ self.maptube = TextSync(tube_conn, self.initiating, self._received_cb, self.tubes_error, self._get_buddy)
+
+ # _get_buddy may not be necessary. Borrowed from HelloMesh
+ def _get_buddy(self, cs_handle):
+ group = self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]
+ my_csh = group.GetSelfHandle()
+ if my_csh == cs_handle:
+ handle = self.conn.GetSelfHandle()
+ elif group.GetGroupFlags() & telepathy.CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES:
+ handle = group.GetHandleOwners([cs_handle])[0]
+ else:
+ handle = cs_handle
+ return self.pservice.get_buddy_by_telepathy_handle(self.conn.service_name, self.conn.object_path, handle)
+
+class TextSync(ExportedGObject):
+ def __init__(self, tube, is_initiator, text_received_cb, alert, get_buddy):
+ super(TextSync, self).__init__(tube, PATH)
+ self.tube = tube
+ self.is_initiator = is_initiator
+ self.text_received_cb = text_received_cb
+ self._alert = alert
+ self.entered = False # Have we set up the tube?
+ self.text = '' # State that gets sent or received
+ self._get_buddy = get_buddy # Converts handle to Buddy object
+ self.tube.watch_participants(self.participant_change_cb)
+
+ def participant_change_cb(self, added, removed):
+ for handle, bus_name in added:
+ buddy = self._get_buddy(handle)
+ for handle in removed:
+ buddy = self._get_buddy(handle)
+ if not self.entered:
+ if self.is_initiator:
+ self.add_hello_handler()
+ else:
+ self.Hello()
+ self.entered = True
+
+ @signal(dbus_interface=IFACE, signature='')
+ def Hello(self):
+ self.bogus = 1
+
+ @method(dbus_interface=IFACE, in_signature='s', out_signature='')
+ def World(self, text):
+ if not self.text:
+ self.text = text
+ self.text_received_cb(text)
+ self.add_hello_handler()
+
+ def add_hello_handler(self):
+ self.tube.add_signal_receiver(self.hello_cb, 'Hello', IFACE,
+ path=PATH, sender_keyword='sender')
+ self.tube.add_signal_receiver(self.sendtext_cb, 'SendText', IFACE,
+ path=PATH, sender_keyword='sender')
+
+ def hello_cb(self, sender=None):
+ if sender == self.tube.get_unique_name():
+ return
+ self.tube.get_object(sender, PATH).World(self.text,
+ dbus_interface=IFACE)
+
+ def sendtext_cb(self, text, sender=None):
+ if sender == self.tube.get_unique_name():
+ return
+ self.text = text
+ self.text_received_cb(text)
+
+ @signal(dbus_interface=IFACE, signature='s')
+ def SendText(self, text):
+ self.text = text
+
+class SearchToolbar(gtk.Toolbar):
+
+ __gsignals__ = {
+ 'address-update': (gobject.SIGNAL_RUN_FIRST, None, [object]),
+ 'search': (gobject.SIGNAL_RUN_FIRST, None, [object]),
+ 'zoom-in': (gobject.SIGNAL_RUN_FIRST, None, []),
+ 'zoom-out': (gobject.SIGNAL_RUN_FIRST, None, []),
+ 'save-search': (gobject.SIGNAL_RUN_FIRST, None, []),
+ 'search-update': (gobject.SIGNAL_RUN_FIRST, None, [object])
+ }
+
+ def __init__(self, pc):
+ gtk.Toolbar.__init__(self)
+ self.ca = pc
+
+ addressLabel = gtk.Label( Constants.istrSearchAddress )
+ addressLabel.show()
+ toolAddressLabel = gtk.ToolItem()
+ toolAddressLabel.add(addressLabel)
+ self.insert(toolAddressLabel, -1)
+ toolAddressLabel.show()
+
+ self._insertSep()
+
+ self.addressField = gtk.Entry()
+ self.addressField.connect('activate', self._addressActivateCb)
+ addressItem = gtk.ToolItem()
+ addressItem.set_expand(True)
+ addressItem.add(self.addressField)
+ self.addressField.show()
+ self.insert( addressItem, -1 )
+ addressItem.show()
+
+ self._insertSep()
+
+ searchLabel = gtk.Label( Constants.istrSearchMedia )
+ searchLabel.show()
+ toolSearchLabel = gtk.ToolItem()
+ toolSearchLabel.add(searchLabel)
+ self.insert(toolSearchLabel, -1)
+ toolSearchLabel.show()
+
+ self._insertSep()
+
+ self.searchField = gtk.Entry()
+ self.searchField.connect('activate', self._searchActivateCb)
+ searchItem = gtk.ToolItem()
+ searchItem.set_expand(True)
+ searchItem.add(self.searchField)
+ self.searchField.show()
+ self.insert( searchItem, -1 )
+ searchItem.show()
+
+ self._insertSep()
+
+ saveButt = ToolButton("save-search")
+ saveButt.set_tooltip(Constants.istrSaveSearch)
+ saveButt.connect('clicked', self._saveCb)
+ self.insert(saveButt, -1)
+ saveButt.show()
+
+ def _setTagsString( self, tags ):
+ self.searchField.set_text(tags)
+
+ def _insertSep( self ):
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(False)
+ separator.set_expand(False)
+ separator.set_size_request(Constants.ui_dim_INSET, -1)
+ self.insert( separator, -1 )
+
+
+ def _addressActivateCb(self, widget):
+ address = widget.props.text
+ self.emit('address-update', address)
+
+
+ def _saveCb(self, widget):
+ self.emit('save-search')
+
+
+ def _searchActivateCb(self, widget):
+ search = widget.props.text
+ self.emit('search-update', search)
+
+
+ def _zoomInCb(self, button):
+ self.emit('zoom-in')
+
+ def _zoomOutCb(self, button):
+ self.emit('zoom-out')
+
+class PopUpP5(P5):
+ def __init__(self):
+ P5.__init__(self)
+ self.fill = None
+ self.stroke = None
+ self.previewCairoImg = None
+ self.up = None
+ self.rt = None
+ self.scale = None
+ self.insetX = 0
+ self.insetY = 0
+
+ def updatePopInfo(self, stroke, fill, cairoThumb, up, rt, insetX, insetY, scale):
+ self.fill = fill
+ self.stroke = stroke
+ self.previewCairoImg = cairoThumb
+ self.up = up
+ self.rt = rt
+ self.scale = scale
+ self.insetX = insetX
+ self.insetY = insetY
+ self.queue_draw()
+
+
+ def draw(self, ctx, w, h):
+ #for stroking
+ self.fillRect( ctx, self.fill, w, h )
+
+ lw = 5
+ hlw = lw/2
+ ctx.set_line_width( lw*self.scale )
+ ctx.rectangle(0, 0, w, h)
+ self.setColor(ctx, self.stroke)
+ ctx.stroke()
+
+ #for the popup bubble's handle
+ if (not self.up):
+ ctx.translate(0, h-(3*self.scale))
+ if (not self.rt):
+ ctx.translate(w-((hlw+21)*self.scale), 0)
+ else:
+ ctx.translate(hlw*self.scale, 0)
+
+ ctx.rectangle(0, 0, 21*self.scale, 3*self.scale)
+ self.setColor(ctx, self.fill)
+ ctx.fill()
+
+ ctx.identity_matrix()
+ ctx.set_source_surface(self.previewCairoImg, self.insetX, self.insetY)
+ ctx.paint()
+
+
+class AddToolbar(gtk.Toolbar):
+ __gsignals__ = {
+ 'add-media': (gobject.SIGNAL_RUN_FIRST, None, [object]),
+ 'add-kml': (gobject.SIGNAL_RUN_FIRST, None, [object]),
+ 'web-media': (gobject.SIGNAL_RUN_FIRST, None, []),
+ 'delete-media': (gobject.SIGNAL_RUN_FIRST, None, []),
+ 'measure': (gobject.SIGNAL_RUN_FIRST, None, []),
+ 'add-info': (gobject.SIGNAL_RUN_FIRST, None, [])
+ }
+
+ def __init__(self, pc):
+ gtk.Toolbar.__init__(self)
+ self.ca = pc
+
+ self.addButt = ToolButton('add-icon')
+ self.addButt.set_tooltip(Constants.istrAddMedia)
+ self.insert(self.addButt, -1)
+ self.addButt.connect('clicked', self._addCb)
+
+ infoButt = ToolButton('corner-info')
+ infoButt.set_tooltip(Constants.istrAddInfo)
+ self.insert(infoButt, -1)
+ infoButt.connect('clicked', self._addInfoCb)
+
+ self._insertSep()
+
+ delButt = ToolButton('delete-icon')
+ delButt.set_tooltip(Constants.istrDeleteMedia)
+ self.insert(delButt, -1)
+ delButt.connect('clicked', self._delCb)
+
+ self._insertSep()
+
+ # add line tools
+ lineButt = ToolButton('tool-shape-line')
+ lineButt.set_tooltip(Constants.LineButton)
+ self.insert(lineButt, -1)
+ lineButt.connect('clicked', self._lineCb)
+ polyButt = ToolButton('tool-polygon')
+ polyButt.set_tooltip(Constants.PolyButton)
+ self.insert(polyButt,-1)
+ polyButt.connect('clicked', self._polyCb)
+
+ self._insertSep()
+
+
+ #measButt = ToolButton('measure-icon')
+ #measButt.set_tooltip(Constants.istrMeasure)
+ #self.insert(measButt, -1)
+ #measButt.connect('clicked', self._measCb)
+
+ #self._insertSep()
+
+ # OpenStreetMap paste
+ webButt = ToolButton('static-icon')
+ webButt.set_tooltip(Constants.istrWebMedia)
+ self.insert(webButt, -1)
+ webButt.connect('clicked', self._webCb)
+
+ # Google Paste
+ staticButt = ToolButton('web-icon')
+ staticButt.set_tooltip(Constants.istrStaticMaps)
+ self.insert(staticButt, -1)
+ staticButt.connect('clicked', self._toStaticCb)
+
+ def _insertSep( self ):
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(False)
+ separator.set_expand(False)
+ separator.set_size_request(25 + Constants.ui_dim_INSET, -1)
+ self.insert( separator, -1 )
+
+ def _addCb(self, button):
+ self.ca.showFileLoadBlocker(True)
+
+ fp = FilePicker()
+ dOb = fp.show()
+ if (dOb != None):
+ if (dOb.file_path != None):
+ if(dOb.metadata['mime_type']=="video/ogg") or (dOb.metadata['mime_type']=="image/jpeg"):
+ self.emit("add-media", dOb)
+ elif(dOb.metadata['mime_type']=="audio/ogg"):
+ self.emit("add-media", dOb)
+ else:
+ self.emit("add-kml", dOb)
+ #else:
+ # self.emit("add-info")
+
+ self.ca.showFileLoadBlocker(False)
+
+ def _addInfoCb(self, button):
+ self.emit("add-info")
+
+ def _delCb(self, button):
+ self.emit("delete-media")
+
+ def _webCb(self, button):
+ #self.ca.browser.load_uri("http://maptonomy.appspot.com/maps4xo?ajaxPort=" + str(self.ca.__class__.ajaxPort) + "&cometPort=" + str(self.ca.__class__.cometPort) + "&xo=true&lat=" + str(self.ca.NOW_MAP_CENTER_LAT) + "&lng=" + str(self.ca.NOW_MAP_CENTER_LNG) + "&z=" + str(self.ca.NOW_MAP_ZOOM) )
+ self.ca.mapPaste()
+
+ def _measCb(self, button):
+ # start a measure tool (rect area or polyline) - calculation handled in HTML/JavaScript
+ self.emit("measure")
+
+ def _toStaticCb(self, button):
+ #self.ca.browser.load_uri("http://maptonomy.appspot.com/ourmap.html?ajaxPort=" + str(self.ca.__class__.ajaxPort) + "&cometPort=" + str(self.ca.__class__.cometPort) + "&xo=true&lat=" + str(self.ca.NOW_MAP_CENTER_LAT) + "&lng=" + str(self.ca.NOW_MAP_CENTER_LNG) + "&zoom=" + str(self.ca.NOW_MAP_ZOOM) )
+ self.ca.googlePaste()
+
+ def _lineCb(self, button):
+ self.ca.lineMode('line')
+
+ def _polyCb(self, button):
+ self.ca.lineMode('polygon')
+
+class ServerThread(threading.Thread):
+ def __init__(self, port, logic):
+ threading.Thread.__init__(self)
+ self.server = Server( ("127.0.0.1", port), logic)
+
+ def run(self):
+ try:
+ self.server.serve_forever()
+ except:
+ self.run()
+
+ def stop(self):
+ #self.server.shutdown()
+ r = 2
diff --git a/mapviewer/MediaMarker.js b/mapviewer/MediaMarker.js
new file mode 100644
index 0000000..a47b00b
--- /dev/null
+++ b/mapviewer/MediaMarker.js
@@ -0,0 +1,21 @@
+function MediaMarker(){
+return{
+ initialize:function(pt, markerServer, markerId, tags, info, icon)
+ {this.markerServer=markerServer;
+ this.markerId=markerId;
+ this.pt=pt;
+ this.tags=tags;
+ this.info=info;
+ if(icon==null){icon="null";}
+ this.icon=icon;
+ this.iconDiv=null;
+ if((this.tags == undefined)||(this.tags==null))
+ {this.tags = "";}
+ },
+ getLatLng:function(){return this.pt;},
+ get_position:function(){return this.pt;},
+ get_info:function(){return this.info;},
+ getMarkerServer:function(){return this.markerServer;},
+ getMarkerId:function(){return this.markerId;}
+};
+} \ No newline at end of file
diff --git a/mapviewer/blue_map_dot.png b/mapviewer/blue_map_dot.png
new file mode 100644
index 0000000..196ecc2
--- /dev/null
+++ b/mapviewer/blue_map_dot.png
Binary files differ
diff --git a/mapviewer/magenta_map_dot.png b/mapviewer/magenta_map_dot.png
new file mode 100644
index 0000000..4c79ea9
--- /dev/null
+++ b/mapviewer/magenta_map_dot.png
Binary files differ
diff --git a/mapviewer/mapCenter.html b/mapviewer/mapCenter.html
new file mode 100644
index 0000000..41d5a9c
--- /dev/null
+++ b/mapviewer/mapCenter.html
@@ -0,0 +1,631 @@
+<html>
+<head id="head">
+ <title>Offline Map</title>
+<script src="MediaMarker.js" type="text/javascript"></script>
+<script src="file:///home/olpc/Library/MapPack/Maps.js" type="text/javascript"></script>
+<script type="text/javascript">
+var zm=10;
+var realZm=800;
+var xOff=0;
+var yOff=0;
+var factor=0.75;
+var westBound=-74.44;
+var northBound=20.14;
+var portMapOn=false;
+var markerList=[];
+var lineList=[];
+var editTxt="Edit";
+var addTxt="Add";
+var describeTxt="Describe this place";
+var ajaxPort,cometPort,mID,canvas;
+var isMedia=false;
+var addingInfo=false;
+var iMarker = null;
+var addingMedia=false;
+var addingLines=false;
+var currentLine=-1;
+var addingPasteMap=false;
+var pasteType="";
+var mediaWin=null;
+var isOffXO=false;
+function gup(nm){nm=nm.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");var rxS="[\\?&]"+nm+"=([^&#]*)";var rx=new RegExp(rxS);var rs=rx.exec(window.location.href);if(!rs){return null;}else{return rs[1];}}
+function init(){
+ ajaxPort = gup("ajaxPort");
+ cometPort = gup("cometPort");
+ lhCometInit();
+ canvas = $("mapCover").getContext("2d");
+ try{
+ //fix all image links
+ for(var m=0;m<maps.length;m++){
+ if(isOffXO){
+ maps[m].link="../../MapPack/" + maps[m].link;
+ }
+ else{
+ maps[m].link="file:///home/olpc/Library/MapPack/" + maps[m].link;
+ }
+ if(m>0){
+ maps[m].mapdiv=$("overlay-"+maps[m].mapCode);
+ maps[m].isOn=false;
+ }
+ else{
+ // set base map
+ $("mapItself").src=maps[0].link;
+ }
+ }
+ }
+ catch(e){
+ // testing without XO Laptop, assume it's in a neighboring folder
+ isOffXO=true;
+ var newScript = document.createElement('script');
+ newScript.src = "../../MapPack/Maps.js"
+ newScript.type = "text/javascript";
+ newScript.onload = init;
+ $("head").appendChild(newScript);
+ return;
+ }
+ /*
+ var grandG={x:0.5904,y:0.5987,width:0.0168,zoom:18,isOn:false,src:"GrandGoave.jpg"};
+ grandG.mapdiv=$("overlay-5");
+ var sofMira={x:0.4894,y:0.598,width:0.015,zoom:16,isOn:false,src:"SofMiragoane.jpg"};
+ sofMira.mapdiv=$("overlay-3");
+ */
+ var southWest = toLatLng(0,600);
+ var northEast = toLatLng(800,0);
+ var newScript = document.createElement('script');
+ newScript.src = "http://127.0.0.1:" + ajaxPort + "/mediaQuery.js?s=" + southWest[0] + "&w=" + southWest[1] + "&n=" + northEast[0] + "&e=" + northEast[1];
+ newScript.type = "text/javascript";
+ newScript.onload = lhAjax;
+ $("head").appendChild(newScript);
+ //addPt(-1.95575,30.437354,"blue crates thing","blue",false);
+ //addPt(0.021026,32.441184,"blue",false);
+ //moveTo(18.52876,-73.512198,20);
+ //addLine("x00x2","#00f",8,[[19,-73],[18.9,-71]]);
+ //addLine("poly-x00x3","#f00",8,[[19.7,-72.79],[20.1,-73.173],[19.79,-73.31]]);
+}
+function lineMode(type){
+ if(addingLines){
+ addingLines = false;
+ $("mapCover").className = "-moz-grab";
+ var newScript = document.createElement('script');
+ var dLine = lineList[currentLine];
+ var ptOutput = [];
+ for(var p=0;p<dLine.pts.length;p++){
+ ptOutput.push(dLine.pts[p][0] + "|" + dLine.pts[p][1]);
+ }
+ ptOutput = ptOutput.join("|");
+ newScript.src = "http://127.0.0.1:" + ajaxPort + "/addLine.js?id=" + dLine.id + "&col=" + dLine.color.replace("#","") + "&thick=4&pts=" + ptOutput;
+ newScript.type = "text/javascript";
+ newScript.onload = lhAjax;
+ $("head").appendChild(newScript);
+ //showInfo(newScript.src);
+ currentLine = -1;
+ updateLines();
+ }
+ else{
+ addingLines = true;
+ $("mapCover").className = "crosshairMap";
+ var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+ var colorchars = "0123456789abcdef";
+ var string_length = 8;
+ var lineID = '';
+ for(var i=0; i<string_length; i++) {
+ var rnum = Math.floor(Math.random() * chars.length);
+ lineID += chars.substring(rnum,rnum+1);
+ }
+ if(type.indexOf("poly") != -1){
+ lineID = "poly-" + lineID;
+ }
+ var randColor = "";
+ for(var i=0; i<3; i++){
+ var rnum = Math.floor(Math.random() * colorchars.length);
+ randColor += colorchars.substring(rnum,rnum+1);
+ }
+ currentLine = lineList.length;
+ addLine(lineID,randColor,4,[]);
+ }
+}
+function placeVertex(event){
+ var vert = toLatLng(event.clientX,event.clientY);
+ lineList[currentLine].pts.push(vert)
+ updateLines();
+}
+function addLine(line_id,line_color,line_thick,line_pts){
+ try{
+ if(line_pts.indexOf("|") != -1){
+ //in |-separated format from Python
+ line_pts_b = line_pts.split("|");
+ line_pts=[];
+ for(var lpi=0;lpi<line_pts_b.length;lpi+=2){
+ line_pts.push([line_pts_b[lpi],line_pts_b[lpi+1]]);
+ }
+ }
+ }
+ catch(e){}
+ for(var l=0;l<lineList.length;l++){
+ if(lineList[l].id==line_id){
+ lineList[l].color="#"+line_color;
+ lineList[l].thickness=line_thick;
+ lineList[l].pts=line_pts;
+ updateLines();
+ return;
+ }
+ }
+ lineList.push({id:line_id,color:"#"+line_color,thickness:line_thick,pts:line_pts});
+ if(!addingLines){
+ currentLine=lineList.length-1;
+ addingLines=true;
+ lineMode('new');
+ }
+}
+function updateLines(){
+ if(lineList.length == 0){ return }
+ $("mapCover").width=$("mapCover").width;
+ for(var l=0;l<lineList.length;l++){
+ var dLine=lineList[l];
+ if(dLine.pts.length < 2){continue}
+ var lastPt=dLine.pts[0];
+ var drewLast=false;
+ var firstPt=lastPt;
+ if(dLine.id.indexOf("poly")==-1){
+ canvas.strokeStyle=dLine.color;
+ canvas.lineWidth=dLine.thickness;
+ }
+ else{
+ canvas.fillStyle=dLine.color;
+ canvas.lineWidth=0;
+ //canvas.strokeStyle="#000";
+ drewLast=true;
+ }
+ if(inBounds(pixel(lastPt[0],lastPt[1]))||drewLast){
+ drewLast = true;
+ canvas.beginPath();
+ canvas.moveTo(parseInt(pixel(lastPt[0],lastPt[1])[0]),parseInt(pixel(lastPt[0],lastPt[1])[1]));
+ }
+ for(var p=1;p<dLine.pts.length;p++){
+ var thisPt=dLine.pts[p];
+ if(drewLast){
+ canvas.lineTo(parseInt(pixel(thisPt[0],thisPt[1])[0]),parseInt(pixel(thisPt[0],thisPt[1])[1]));
+ }
+ else{
+ if(inBounds(pixel(lastPt[0],lastPt[1]))){
+ drewLast=true;
+ canvas.beginPath();
+ canvas.moveTo(parseInt(pixel(lastPt[0],lastPt[1])[0]),parseInt(pixel(lastPt[0],lastPt[1])[1]));
+ canvas.lineTo(parseInt(pixel(thisPt[0],thisPt[1])[0]),parseInt(pixel(thisPt[0],thisPt[1])[1]));
+ }
+ }
+ lastPt=thisPt;
+ }
+ if(drewLast){
+ if(dLine.id.indexOf("poly")==-1){
+ canvas.stroke();
+ }
+ else{
+ canvas.closePath();
+ canvas.fill();
+ }
+ }
+ }
+ if(currentLine>=0){
+ //draw a square at each point in the line or polygon
+ for(var p=0;p<lineList[currentLine].pts.length;p++){
+ var thisPt=lineList[currentLine].pts[p];
+ canvas.fillStyle="#000";
+ canvas.fillRect(parseInt(pixel(thisPt[0],thisPt[1])[0])-6,parseInt(pixel(thisPt[0],thisPt[1])[1])-6,12,12);
+ }
+ }
+}
+function inBounds(px){
+ if(px[0]<0){return false;}
+ if(px[0]>820){return false;}
+ if(px[1]<0){return false;}
+ if(px[1]>520){return false;}
+ return true;
+}
+function mapPaste(type){
+ addingPasteMap=true;
+ pasteType=type;
+ $("mapCover").className = "crosshairMap";
+}
+function placeOverlay(event){
+ addingPasteMap=false;
+ $("mapCover").className="-moz-grab";
+ var ctr = toLatLng(event.clientX,event.clientY);
+ var standardZoom = 17;
+ if(zm < 13){
+ standardZoom = 10;
+ }
+ else if(zm < 16){
+ standardZoom = 12;
+ }
+ else if(zm < 18){
+ standardZoom = 14;
+ }
+ else{
+ standardZoom = 15;
+ }
+ addPt(toLatLng(304,152)[0],toLatLng(304,152)[1],"","orange",false);
+ var minimap = markerList[markerList.length-1].iconDiv;
+ minimap.onclick="";
+ if(pasteType=='osm'){
+ minimap.src="http://pafciu17.dev.openstreetmap.org/?module=map&width=640&height=404&center="+ctr[1]+","+ctr[0]+"&zoom="+standardZoom;
+ }
+ else{
+ minimap.src="http://maps.google.com/maps/api/staticmap?sensor=false&key=ABQIAAAAxkKtrWN5q-vPTLRVmO_r6RRFDCLHCbUG3VrjXnZmMRXvQdFL3RS-b-ld9hTrkIgQlYsxPQ1kYq6y9A&size=640x404&format=jpg&center="+ctr[0]+","+ctr[1]+"&zoom="+standardZoom+"&maptype=hybrid";
+ }
+ minimap.onclick="";
+ minimap.style.zIndex=-1;
+ var bds = getStandardBounds(ctr,standardZoom); // returns [ sw=[lat,lng] , ne=[lat,lng] ]
+ var osmMap={north:bds[1][0],east:bds[1][1],west:bds[0][1],mapCode:-1,showZoom:zm-1,isOn:true};
+ osmMap.mapdiv=minimap;
+ maps.push(osmMap);
+ markerList.pop();
+ portMapGo();
+}
+function filterTags(sTerms){
+ sTerms = unescape(sTerms).toLowerCase();
+ sTerms = replaceAll(sTerms," ","");
+ for(var m=0;m<markerList.length;m++){
+ var formattedInfo = replaceAll(markerList[m].info.toLowerCase()," ","");
+ if(formattedInfo.indexOf(sTerms) != -1){
+ moveTo(markerList[m].pt[0],markerList[m].pt[1]);
+ closeiWin();
+ isMedia=false;
+ showInfo(markerList[m].info);
+ var bigMarker = markerList[m].iconDiv;
+ bigMarker.width=parseInt(1.8*bigMarker.width);
+ setTimeout(function(){bigMarker.width/=1.8},5000);
+ return;
+ }
+ }
+}
+function replaceAll(src,old,fix){
+ while(src.indexOf(old) != -1){
+ src = src.replace(old,fix);
+ }
+ return src;
+}
+function lhCometInit()
+{ var newScript = document.createElement('script');
+ var d = new Date();
+ newScript.src = "http://127.0.0.1:" + cometPort + "/comet.js?t="+d.getTime()
+ newScript.type = "text/javascript";
+ newScript.onload = lhCometResponse;
+ $("head").appendChild(newScript);
+}
+function lhCometResponse(){ lhCometInit(); }
+function moveTo(lat,lng){moveTo(lat,lng,zm)}
+function moveTo(lat,lng,zoom){
+ while(zoom > zm){zoomIn()}
+ while(zoom < zm){zoomOut()}
+ var currentOffset = pixel(lat,lng);
+ xOff+=(410-1*currentOffset[0]);
+ yOff+=(260-1*currentOffset[1]);
+ $("mapItself").style.left=xOff;
+ $("mapItself").style.top=yOff;
+ portMapGo();
+}
+function preAddInfo(){
+ closeiWin();
+ addingInfo=true;
+ $("mapCover").className = "crosshairMap";
+}
+function placeInfoMarker(event){
+ addingInfo = false;
+ var loc = toLatLng(event.clientX,event.clientY);
+ addPt(loc[0],loc[1],describeTxt,"magenta",true);
+ addedMedia();
+}
+function addPt(lat,lng,text,color,showNow){
+ for(var i=0;i<markerList.length;i++){
+ if(markerList[i].pt[0] == lat){
+ if(markerList[i].pt[1] == lng){
+ markerList[i].info = unescape(text);
+ return;
+ }
+ }
+ }
+ var mk = MediaMarker();
+ mID="info-"+markerList.length;
+ mk.initialize([lat,lng],null,mID,null,unescape(text),color+"_map_dot.png");
+ mk.iconDiv=document.createElement("img");
+ mk.iconDiv.className="marker";
+ mk.iconDiv.src=mk.icon;
+ var px=pixel(mk.pt[0],mk.pt[1]);
+ mk.iconDiv.style.left=parseInt(px[0]-mk.iconDiv.width/2)+"px";
+ mk.iconDiv.style.top=parseInt(px[1]-mk.iconDiv.height/2)+"px";
+ mk.iconDiv.style.position="absolute";
+ mk.iconDiv.style.display="block";
+ mk.iconDiv.onclick=function(event){mID=mk.markerId;isMedia=false;showInfo(mk.get_info());}
+ document.body.appendChild(mk.iconDiv);
+ markerList.push(mk);
+ if(showNow){
+ isMedia = false;
+ mID=mk.getMarkerId();
+ showInfo(text);
+ }
+}
+function pixel(lat,lng){
+ //top left is estimated to be 20.18025 N, -74.62984 W
+ var latdiff = (maps[0].north - 1.00*lat)*latFactor*Math.pow(1.35,zm-10.0);
+ var lngdiff = (1.00*lng - maps[0].west)*lngFactor*Math.pow(1.35,zm-10.0);
+ var x = lngdiff*1.00 + xOff*1.00;
+ var y = latdiff*1.00 + yOff*1.00;
+ return [x,y];
+}
+function zoomIn(){
+ var centerLL = toLatLng(410,260);
+ zm+=1;
+ realZm*=1.35;
+ $("mapItself").style.width=parseInt(realZm)+"px";
+ xOff=1*$("mapItself").style.left.replace("px","").replace("pt","");
+ yOff=1*$("mapItself").style.top.replace("px","").replace("pt","");
+ moveTo(centerLL[0],centerLL[1]);
+}
+function zoomOut(){
+ var centerLL = toLatLng(410,260);
+ zm-=1;
+ realZm/=1.35;
+ $("mapItself").style.width=parseInt(realZm);
+ xOff=1*$("mapItself").style.left.replace("px","").replace("pt","");
+ yOff=1*$("mapItself").style.top.replace("px","").replace("pt","");
+ moveTo(centerLL[0],centerLL[1]);
+}
+function moveLeft(){
+ xOff+=2000/zm;
+ $("mapItself").style.left=parseInt(xOff);
+ portMapGo();
+}
+function moveRight(){
+ xOff-=2000/zm;
+ $("mapItself").style.left=parseInt(xOff);
+ portMapGo();
+}
+function moveUp(){
+ yOff+=2000/zm;
+ $("mapItself").style.top=parseInt(yOff);
+ portMapGo();
+}
+function moveDown(){
+ yOff-=2000/zm;
+ $("mapItself").style.top=parseInt(yOff);
+ portMapGo();
+}
+function mrkWithId(id){
+ for(var i=0;i<markerList.length;i++){
+ if(markerList[i].getMarkerId() == id){
+ return markerList[i];
+ }
+ }
+}
+function editInfoMarker(id){
+ iMarker = mrkWithId(id);
+ mID = id;
+ isMedia = true;
+ showInfo("<textarea id='infoText' style='height:400;width:350;'>" + iMarker.get_info() + "</textarea><br/><input type='button' class='centerButton' value='" + addTxt + "' onclick='updateInfo(\"" + iMarker.getMarkerId() + "\")'/>");
+}
+function updateInfo(id){
+ closeiWin();
+ var iMarker = mrkWithId(id);
+ iMarker.info = $("infoText").value;
+ var loc = iMarker.getLatLng();
+ var newScript = document.createElement('script');
+ newScript.src = "http://127.0.0.1:" + ajaxPort + "/addInfoMarker.js?lat=" + loc[0] + "&lng=" + loc[1] + "&info=" + iMarker.info + "&icon=" + iMarker.icon + "&isNew=True";
+ newScript.type = "text/javascript";
+ newScript.onload = lhAjax;
+ $("head").appendChild(newScript);
+}
+function lhAjax(){}
+function updateMarkers(){
+ for(var m=0;m<markerList.length;m++){
+ var lat=markerList[m].pt[0];
+ var lng=markerList[m].pt[1];
+ var px=pixel(lat,lng);
+ markerList[m].iconDiv.style.left=parseInt(px[0]-markerList[m].iconDiv.width/2);
+ markerList[m].iconDiv.style.top=parseInt(px[1]-markerList[m].iconDiv.height/2);
+ }
+}
+function getStandardBounds(centerLL,zoom){
+ var sw=[1*centerLL[0]-0.55618*Math.pow(2,(9-1*zoom)),centerLL[1]*1 - 0.98877*Math.pow(2,(9-1*zoom))];
+ var ne=[1*centerLL[0]+0.55618*Math.pow(2,(9-1*zoom)),centerLL[1]*1 + 0.98877*Math.pow(2,(9-1*zoom))];
+ return [sw,ne];
+}
+function portMapGo(){
+ //skip m=0, that's the base map
+ for(var m=1;m<maps.length;m++){
+ if(zm>=maps[m].showZoom){
+ if((pixel(maps[m].north,maps[m].east)[0]<0)||(pixel(maps[m].north,maps[m].west)[0]>820)){
+ maps[m].isOn=false;
+ if(maps[m].mapCode > 0){
+ if(maps[m].mapdiv.src.indexOf(maps[m].link.replace("../../",""))!=-1){
+ maps[m].mapdiv.style.display="none";
+ }
+ }
+ else{
+ maps[m].mapdiv.style.display="none";
+ }
+ }
+ else if((pixel(maps[m].north,maps[m].west)[1]+1*maps[m].mapdiv.height<0)||(pixel(maps[m].north,maps[m].west)[1]>620)){
+ maps[m].isOn=false;
+ if(maps[m].mapCode > 0){
+ if(maps[m].mapdiv.src.indexOf(maps[m].link.replace("../../",""))!=-1){
+ maps[m].mapdiv.style.display="none";
+ }
+ }
+ else{
+ maps[m].mapdiv.style.display="none";
+ }
+ }
+ else{
+ maps[m].isOn=true;
+ if(maps[m].mapCode > 0){
+ //shared image-div
+ if(maps[m].mapdiv.src.indexOf(maps[m].link.replace("../../",""))==-1){
+ //swap between major maps
+ maps[m].mapdiv.src=maps[m].link;
+ }
+ }
+ var topleft = pixel(maps[m].north,maps[m].west);
+ var mWidth = pixel(maps[m].north,maps[m].east)[0]-topleft[0];
+ maps[m].mapdiv.style.width = parseInt(mWidth);
+ maps[m].mapdiv.style.left=parseInt(topleft[0]);
+ maps[m].mapdiv.style.top=parseInt(topleft[1]);
+ maps[m].mapdiv.style.display="block";
+ }
+ }
+ else{
+ if(maps[m].mapdiv.src.indexOf(maps[m].src)!=-1){
+ maps[m].mapdiv.style.display="none";
+ }
+ maps[m].isOn=false;
+ }
+ }
+ updateMarkers();
+ updateLines();
+}
+function showInfo(er){
+ //if(er==''){return}
+ if(!isMedia){
+ if(er.indexOf("::") != -1){
+ if(er.indexOf('wiki::') != -1){ er = '<b>' + er.replace('wiki::','</b><iframe height="400" width="350" src="http://simple.m.wikipedia.org/wiki/') + '"></iframe>'; }
+ else if(er.indexOf('img::') != -1){ er = '<b>' + er.replace('img::','</b><br/><img style="max-height:400;max-width:350;" src="') + '"/>'; }
+ else if(er.indexOf('pic::') != -1){ er = '<b>' + er.replace('pic::','</b><br/><img style="max-height:400;max-width:350;" src="') + '"/>'; }
+ else if(er.indexOf('google::') != -1){ er = er.replace('google::','<iframe height="400" width="350" src="http://google.com/search?q=') + '"></iframe>'; }
+ else if(er.indexOf('vid::') != -1){ er = '<b>' + er.replace('vid::','</b><br/><object width="400" height="350" type="application/ogg" data="') + '"></object>'; }
+ else if((er.indexOf('wiki') != -1)&&(er.indexOf('::') > er.indexOf('wiki'))){
+ var erBegin = er.substring(0,er.lastIndexOf('wiki'));
+ er = er.substring(er.lastIndexOf('wiki'));
+ var wikiCode = er.substring(4,er.indexOf('::'));
+ er = er.substring(er.indexOf('::') + 2);
+ er = '<b>' + erBegin + '</b><iframe height="400" width="350" src="http://' + wikiCode + '.m.wikipedia.org/wiki/' + er + '"></iframe>';
+ }
+ }
+ er+="<br/><input type='button' class='centerButton' value='" + editTxt + "' onclick='editInfoMarker(" + '"' + mID + '"' + ")'/>";
+ }
+ $("iWin").innerHTML = er;
+ $("iEnc").style.display = "block";
+ $("iWin").style.display = "block";
+}
+function closeiWin(){
+ $("iWin").style.display = "none";
+ $("iEnc").style.display = "none";
+ mediaWin=null;
+ var newScript = document.createElement('script');
+ var d = new Date();
+ newScript.src = "http://127.0.0.1:" + ajaxPort + "/hideMedia.js?t="+d.getTime()
+ newScript.type = "text/javascript";
+ $("head").appendChild(newScript);
+}
+function toLatLng(x,y){
+ x+=".000000";
+ y+=".000000";
+ x-=xOff;
+ y-=yOff;
+ var latdiff = y / latFactor / Math.pow(1.35,zm-10);
+ var lngdiff = x / lngFactor / Math.pow(1.35,zm-10);
+ var mapLat = maps[0].north - latdiff;
+ var mapLng = maps[0].west + lngdiff;
+ return [mapLat.toFixed(5),mapLng.toFixed(5)];
+}
+function updateLoc(){
+ var ctr = toLatLng(410,260);
+ var newScript = document.createElement('script');
+ if(mediaWin != null){
+ newScript.src = "http://127.0.0.1:" + ajaxPort + "/updateLocation.js?lat=" + ctr[0] + "&lng=" + ctr[1] + "&z=" + zm + "&x=" + ($("iEnc").offsetLeft*1 + 25) + "&y=" + ($("iEnc").offsetTop*1 + 30);
+ }
+ else{
+ newScript.src = "http://127.0.0.1:" + ajaxPort + "/updateLocation.js?lat=" + ctr[0] + "&lng=" + ctr[1] + "&z=" + zm + "&x=-1&y=-1";
+ }
+ newScript.type = "text/javascript";
+ newScript.onload = lhAjax;
+ $("head").appendChild(newScript);
+}
+function infoImg(id){
+ showMedia('<div style="color:#fff;font-size:45pt;width:200;">a<br/>b<br/>a<br/>b<br/><Media>a</div>');
+ var newScript=document.createElement('script');
+ newScript.src="http://127.0.0.1:" + ajaxPort + "/showMedia.js?id=" + id + "&x=" + ($("iEnc").offsetLeft*1 + 25) + "&y=" + ($("iEnc").offsetTop*1 + 10) + "&up=true&rt=true";
+ newScript.type="text/javascript";
+ newScript.onload=lhAjax;
+ $("head").appendChild(newScript);
+}
+function showMedia(er){
+ mediaWin = $("iWin");
+ $("iWin").innerHTML = er;
+ $("iEnc").style.display = "block";
+ $("iWin").style.display = "block";
+}
+function preAddMedia()
+{ if(!mediaWin){closeiWin();}
+ addingMedia = true;
+ updateLoc();
+ $("mapCover").className="crosshairMap";
+}
+function placeAddMedia(event){
+ var pt=toLatLng(event.clientX, event.clientY);
+ var newScript=document.createElement('script');
+ newScript.src="http://127.0.0.1:" + ajaxPort + "/placeAddMedia.js?lat=" + pt[0] + "&lng=" + pt[1];
+ newScript.type="text/javascript";
+ newScript.onload=lhAjax;
+ $("head").appendChild(newScript);
+ addedMedia();
+}
+function postAddMedia( lat, lng, url, basename, tags ){
+ var mk=MediaMarker();
+ mk.initialize([lat,lng],url,basename,tags);
+ mk.iconDiv=document.createElement("img");
+ mk.iconDiv.className="marker";
+ mk.iconDiv.src="orange_map_dot.png";
+ var px=pixel(mk.pt[0],mk.pt[1]);
+ mk.iconDiv.style.left=parseInt(px[0]-mk.iconDiv.width/2)+"px";
+ mk.iconDiv.style.top=parseInt(px[1]-mk.iconDiv.height/2)+"px";
+ mk.iconDiv.style.position="absolute";
+ mk.iconDiv.style.display="block";
+ mk.iconDiv.onclick=function(event){mID=mk.markerId;infoImg(mID);}
+ document.body.appendChild(mk.iconDiv);
+ markerList.push(mk);
+ hidePics();
+ addedMedia();
+ infoImg(mk.markerId);
+ updateLoc();
+}
+function addedMedia()
+{ addingMedia=false;
+ $("mapCover").className="-moz-grab";
+}
+function clickToZoom(event){
+ var ctr = toLatLng(event.clientX,event.clientY);
+ moveTo(ctr[0],ctr[1],zm+1);
+}
+function $(id){return document.getElementById(id)}
+</script>
+<style type="text/css">
+body{font-family:arial}
+#iEnc{position:absolute;left:150;top:30;-moz-border-radius:10;border:2px solid #000;background:#fff;width:390;padding:3;padding-left:10;display:none;z-index:10}
+input.cBtn{float:right}
+input:hover{color:#000;cursor:pointer}
+canvas.crosshairMap{cursor:crosshair}
+img.marker{cursor:pointer}
+</style>
+</head>
+<body onload="init()">
+ <img id="mapItself" src="" style="position:absolute;left:0px;top:0px;width:800px;z-Index:-5"/>
+ <div id="miniMaps">
+ <img id="overlay-1" src="orange_map_dot.png" style="position:absolute;display:none;z-index:-4;"/>
+ <img id="overlay-2" src="orange_map_dot.png" style="position:absolute;display:none;z-index:-4;"/>
+ <img id="overlay-3" src="orange_map_dot.png" style="position:absolute;display:none;z-index:-4;"/>
+ <img id="overlay-4" src="orange_map_dot.png" style="position:absolute;display:none;z-index:-4;"/>
+ <img id="overlay-5" src="orange_map_dot.png" style="position:absolute;display:none;z-index:-4;"/>
+ </div>
+ <canvas id="mapCover" style="position:absolute;left:0px;top:0px;" width="820" height="520" onclick="if(addingMedia){placeAddMedia(event)}if(addingInfo){placeInfoMarker(event)}if(addingLines){placeVertex(event)}if(addingPasteMap){placeOverlay(event)}" ondblclick="clickToZoom(event)"></canvas>
+ <div style="position:absolute;left:20px;top:80px;background-color:#fff;border:1px solid #000;z-index:100;">
+ <input type="button" value="+" style="font-size:12pt;z-index:101;" onclick="zoomIn()"/>
+ <input type="button" value="-" style="font-size:12pt;z-index:101;" onclick="zoomOut()"/>
+ <table>
+ <tr><td></td><td><input type="button" value="&uarr;" style="font-size:12pt;z-index:101;" onclick="moveUp()"/></td><td></td></tr>
+ <tr><td><input type="button" value="&larr;" style="font-size:12pt;z-index-101;" onclick="moveLeft()"/></td><td></td><td><input type="button" value="&rarr;" style="font-size:12pt;" onclick="moveRight()"/></td></tr>
+ <tr><td></td><td><input type="button" value="&darr;" style="font-size:12pt;z-index:101;" onclick="moveDown()"/></td><td></td></tr>
+ </table>
+ </div>
+ <div id="iEnc"><input type="button" class="cBtn" onclick="closeiWin()" value="x"/><div id="iWin"></div></div>
+</body>
+</html>
diff --git a/mapviewer/orange_map_dot.png b/mapviewer/orange_map_dot.png
new file mode 100644
index 0000000..b3fa7d4
--- /dev/null
+++ b/mapviewer/orange_map_dot.png
Binary files differ
diff --git a/model.py b/model.py
new file mode 100644
index 0000000..2a2cf14
--- /dev/null
+++ b/model.py
@@ -0,0 +1,149 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+from constants import Constants
+from recorded import Recorded
+from color import Color
+from instance import Instance
+import utils
+from recorded import Recorded
+
+import gtk
+import os
+import shutil
+import StringIO
+
+class Model:
+ def __init__(self, ca):
+ self.ca = ca
+
+ self.recs = []
+ self.savedMaps = []
+ self.infoMarkers = {}
+ self.lineData = {}
+
+ def getMediaByThumbFilename(self, id):
+ for i in range(len(self.recs)):
+ if (self.recs[i].thumbFilename == id):
+ return self.recs[i]
+
+ return None
+
+
+ def getMediaByDatastoreId(self, id):
+ for i in range(len(self.recs)):
+ if (self.recs[i].datastoreId == id):
+ return self.recs[i]
+
+ return None
+
+
+ def addMedia(self, lat, lon, datastoreOb):
+ rec = Recorded()
+
+ #default photo
+ rec.type = Constants.TYPE_PHOTO
+ if((datastoreOb.metadata['mime_type'] == "video/ogg") or (datastoreOb.metadata['mime_type'] == "audio/ogg")):
+ rec.type = Constants.TYPE_VIDEO
+
+ rec.source = Recorded.SOURCE_DATASTORE
+ rec.datastoreOb = datastoreOb
+ rec.datastoreId = datastoreOb.object_id
+
+ rec.tags = ""
+ if (datastoreOb.metadata.has_key("tags")):
+ rec.tags = datastoreOb.metadata['tags']
+
+ rec.latitude = lat
+ rec.longitude = lon
+
+
+ colors = datastoreOb.metadata['icon-color']
+ colorStroke, colorFill = colors.split(",")
+ rec.colorStroke = Color()
+ rec.colorStroke.init_hex(colorStroke)
+ rec.colorFill = Color()
+ rec.colorFill.init_hex(colorFill)
+
+ thumbPixbuf = None
+ if (datastoreOb.metadata.has_key("preview")):
+ try:
+ thumb = datastoreOb.metadata['preview']
+ if thumb[1:4] == 'PNG':
+ pbl = gtk.gdk.PixbufLoader()
+ pbl.write(thumb)
+ pbl.close()
+ thumbPixbuf = pbl.get_pixbuf()
+ else:
+ thumbPixbuf = utils.getPixbufFromString(thumb)
+ except:
+ pass
+
+ if (thumbPixbuf == None):
+ #try to create the thumbnail yourself...
+ if (rec.type == Constants.TYPE_PHOTO):
+ try:
+ #load in the image, make a thumbnail
+ thumbPixbuf = gtk.gdk.pixbuf_new_from_file_at_size(rec.getFilepath(), 320, 240)
+ except:
+ pass
+
+ if (thumbPixbuf == None):
+ #i give up. load in a blank image from the activity itself.
+ thumbPath = os.path.join(self.ca.htmlPath, "1.jpg")
+ thumbPixbuf = gtk.gdk.pixbuf_new_from_file(thumbPath)
+
+
+ thumbFilepath = os.path.join(Instance.instancePath, "thumb.png")
+ thumbFilepath = utils.getUniqueFilepath(thumbFilepath, 0)
+ thumbPixbuf.save( thumbFilepath, "png", {} )
+ rec.thumbFilename = os.path.basename(thumbFilepath)
+
+ self.recs.append(rec)
+
+ return rec
+
+ def setInfo( self, lat, lng, info, icon ):
+ self.infoMarkers[lat+','+lng] = lat + ";~" + lng + ";~" + info + ";~" + icon
+
+ def setLine( self, id, color, thickness, pts ):
+ self.lineData[id] = id + ";~" + color + ";~" + thickness + ";~" + pts
+
+ def addSavedMap( self, smap ):
+ self.savedMaps.append(smap)
+
+ def getMediaResponse(self, s, w, n, e):
+ r = ""
+
+ for i in range( len(self.recs) ):
+ rec = self.recs[i]
+ #r = r + "var m" + str(i) + " = new MediaMarker( [" + str(rec.latitude) + "," + str(rec.longitude) + "], '" + rec.getThumbUrl() + "', '" + rec.getThumbBasename() + "', null, null, 'blue_map_dot.png');"
+ #r = r + "markerList.push(m" + str(i) + ");"
+ r = r + "postAddMedia(" + str(rec.latitude) + "," + str(rec.longitude) + ",'" + rec.getThumbUrl() + "','" + rec.getThumbBasename() + "','');"
+
+ for k, i in self.infoMarkers.iteritems():
+ iMarker = i.split(";~")
+ r = r + "addPt(" + iMarker[0] + "," + iMarker[1] + ",'" + iMarker[2] + "','blue',false);"
+
+ for k, i in self.lineData.iteritems():
+ iLine = i.split(";~")
+ r = r + "addLine('" + iLine[0] + "','" + iLine[1] + "','" + iLine[2] + "','" + iLine[3] + "');"
+
+ return r
diff --git a/p5.py b/p5.py
new file mode 100644
index 0000000..8a92c15
--- /dev/null
+++ b/p5.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import gtk
+from gtk import gdk
+import gobject
+import cairo
+import math
+
+
+class P5(gtk.DrawingArea):
+ def __init__(self, button=False):
+ super(P5, self).__init__()
+ self.connect("expose_event", self.expose)
+ if (button):
+ self.add_events(gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK | gdk.POINTER_MOTION_MASK)
+ self.connect("button_press_event", self._buttonPress)
+
+
+ def expose(self, widget, event):
+ ctx = widget.window.cairo_create()
+
+ # set a clip region for the expose event
+ ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
+ ctx.clip()
+
+ rect = widget.allocation
+ self.draw( ctx, rect.width, rect.height )
+
+
+ def redraw_canvas(self):
+ #called from update
+ if self.window:
+ alloc = self.get_allocation()
+ self.queue_draw_area(0, 0, alloc.width, alloc.height)
+ self.window.process_updates(True)
+
+
+ def update(self):
+ #paint thread -- call redraw_canvas, which calls expose
+ self.redraw_canvas()
+
+
+ def fillRect( self, ctx, col, w, h ):
+ self.setColor( ctx, col )
+
+ ctx.line_to(0, 0)
+ ctx.line_to(w, 0)
+ ctx.line_to(w, h)
+ ctx.line_to(0, h)
+ ctx.close_path()
+
+ ctx.fill()
+
+
+ def setColor( self, ctx, col ):
+ if (not col._opaque):
+ ctx.set_source_rgba( col._r, col._g, col._b, col._a )
+ else:
+ ctx.set_source_rgb( col._r, col._g, col._b )
+
+
+ def _buttonPress(self, widget, event):
+ self.fireButton()
+
+
+ def fireButton( self ):
+ #for extending
+ pass \ No newline at end of file
diff --git a/photocanvas.py b/photocanvas.py
new file mode 100644
index 0000000..9346066
--- /dev/null
+++ b/photocanvas.py
@@ -0,0 +1,65 @@
+import cairo
+import gtk
+import gobject
+from gtk import gdk
+
+from p5 import P5
+from constants import Constants
+
+class PhotoCanvas(P5):
+
+ def __init__(self):
+ P5.__init__(self)
+ self.img = None
+ self.drawImg = None
+ self.SCALING_IMG_ID = 0
+ self.cacheWid = -1
+ self.modify_bg( gtk.STATE_NORMAL, Constants.colorBlack.gColor )
+ self.modify_bg( gtk.STATE_INSENSITIVE, Constants.colorBlack.gColor )
+
+
+ def draw(self, ctx, w, h):
+ self.fillRect( ctx, Constants.colorBlack, w, h )
+
+ if (self.img != None):
+
+ if (w == self.img.get_width()):
+ self.cacheWid == w
+ self.drawImg = self.img
+
+ #only scale images when you need to, otherwise you're wasting cycles, fool!
+ if (self.cacheWid != w):
+ if (self.SCALING_IMG_ID == 0):
+ self.drawImg = None
+ self.SCALING_IMG_ID = gobject.idle_add( self.resizeImage, w, h )
+
+ if (self.drawImg != None):
+ #center the image based on the image size, and w & h
+ ctx.set_source_surface(self.drawImg, (w/2)-(self.drawImg.get_width()/2), (h/2)-(self.drawImg.get_height()/2))
+ ctx.paint()
+
+ self.cacheWid = w
+
+
+ def setImage(self, img):
+ self.cacheWid = -1
+ self.img = img
+ self.drawImg = None
+ self.queue_draw()
+
+
+ def resizeImage(self, w, h):
+ self.SCALING_IMG_ID = 0
+ if (self.img == None):
+ return
+
+ #use image size in case 640 no more
+ scaleImg = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
+ sCtx = cairo.Context(scaleImg)
+ sScl = (w+0.0)/(self.img.get_width()+0.0)
+ sCtx.scale( sScl, sScl )
+ sCtx.set_source_surface( self.img, 0, 0 )
+ sCtx.paint()
+ self.drawImg = scaleImg
+ self.cacheWid = w
+ self.queue_draw()
diff --git a/po/es.po b/po/es.po
new file mode 100644
index 0000000..f01d56c
--- /dev/null
+++ b/po/es.po
@@ -0,0 +1,99 @@
+#: activity/activity.info:2
+msgid "Map"
+msgstr "Mapa"
+
+#: constants.py:26
+msgid "Edit"
+msgstr "Editar"
+
+#: constants.py:27
+msgid "Search"
+msgstr "Buscar"
+
+#: constants.py:28
+msgid "Find:"
+msgstr "Busca:"
+
+#: constants.py:29
+msgid "Tags:"
+msgstr "Palabras:"
+
+#: constants.py:30
+msgid "Save Search"
+msgstr "Recordar Mapa"
+
+#: constants.py:31
+msgid "Connecting to Map Server"
+msgstr "Está Connectando"
+
+#: constants.py:32
+msgid "Zoom In"
+msgstr "Magnificar"
+
+#: constants.py:33
+msgid "Zoom Out"
+msgstr "Magnificar Menos"
+
+#: constants.py:34
+msgid "Save"
+msgstr "Recordar"
+
+#: constants.py:35
+msgid "Density"
+msgstr "Density"
+
+#: constants.py:36
+msgid "Saved Map"
+msgstr "Mapa recordado"
+
+#: constants.py:37
+msgid "Describe Map"
+msgstr "Escribe sobre esto"
+
+#: constants.py:38
+msgid "Remove Map"
+msgstr "Cancelación"
+
+#: constants.py:39
+msgid "Copy to Clipboard"
+msgstr "Copiar"
+
+#: constants.py:40
+msgid "Add Media"
+msgstr "Foto nuevo"
+
+#: constants.py:41
+msgid "Add Info"
+msgstr "Escribir"
+
+#: constants.py:42
+msgid "Delete"
+msgstr "Cancelar"
+
+#: constants.py:43
+msgid "Library"
+msgstr "Biblioteca"
+
+#: constants.py:44
+msgid "Measure"
+msgstr "Medición"
+
+#: constants.py:45
+msgid "OurMaps"
+msgstr "NuestrosMapas"
+
+#: constants.py:46
+msgid "Latitude:"
+msgstr "Latitud:"
+
+#: constants.py:47
+msgid "Longitude:"
+msgstr "Longitud:"
+
+#: constants.py:48
+msgid "Description:"
+msgstr "Descripción:"
+
+#: constants.py:49
+msgid "lang=en"
+msgstr "lang=es"
diff --git a/po/map.pot b/po/map.pot
new file mode 100644
index 0000000..8b191ef
--- /dev/null
+++ b/po/map.pot
@@ -0,0 +1,113 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-10 11:08-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: activity/activity.info:2
+msgid "Map"
+msgstr ""
+
+#: constants.py:26
+msgid "Edit"
+msgstr ""
+
+#: constants.py:27
+msgid "Search"
+msgstr ""
+
+#: constants.py:28
+msgid "Find:"
+msgstr ""
+
+#: constants.py:29
+msgid "Tags:"
+msgstr ""
+
+#: constants.py:30
+msgid "Save Search"
+msgstr ""
+
+#: constants.py:31
+msgid "Connecting to Map Server"
+msgstr ""
+
+#: constants.py:32
+msgid "Zoom In"
+msgstr ""
+
+#: constants.py:33
+msgid "Zoom Out"
+msgstr ""
+
+#: constants.py:34
+msgid "Save"
+msgstr ""
+
+#: constants.py:35
+msgid "Density"
+msgstr ""
+
+#: constants.py:36
+msgid "Saved Map"
+msgstr ""
+
+#: constants.py:37
+msgid "Describe Map"
+msgstr ""
+
+#: constants.py:38
+msgid "Remove Map"
+msgstr ""
+
+#: constants.py:39
+msgid "Copy to Clipboard"
+msgstr ""
+
+#: constants.py:40
+msgid "Add Media"
+msgstr ""
+
+#: constants.py:41
+msgid "Add Info"
+msgstr ""
+
+#: constants.py:42
+msgid "Delete"
+msgstr ""
+
+#: constants.py:43
+msgid "Library"
+msgstr ""
+
+#: constants.py:44
+msgid "Measure"
+msgstr ""
+
+#: constants.py:45
+msgid "OurMaps"
+msgstr ""
+
+#: constants.py:46
+msgid "Latitude:"
+msgstr ""
+
+#: constants.py:47
+msgid "Longitude:"
+msgstr ""
+
+#: constants.py:48
+msgid "Description:"
+msgstr "" \ No newline at end of file
diff --git a/po/mn.po b/po/mn.po
new file mode 100644
index 0000000..433bdce
--- /dev/null
+++ b/po/mn.po
@@ -0,0 +1,99 @@
+#: activity/activity.info:2
+msgid "Map"
+msgstr "Газрын зураг"
+
+#: constants.py:26
+msgid "Edit"
+msgstr "хийх"
+
+#: constants.py:27
+msgid "Search"
+msgstr "хайлт"
+
+#: constants.py:28
+msgid "Find:"
+msgstr "олох:"
+
+#: constants.py:29
+msgid "Tags:"
+msgstr "--"
+
+#: constants.py:30
+msgid "Save Search"
+msgstr "хадгалах"
+
+#: constants.py:31
+msgid "Connecting to Map Server"
+msgstr "Connecting"
+
+#: constants.py:32
+msgid "Zoom In"
+msgstr "ойрхон"
+
+#: constants.py:33
+msgid "Zoom Out"
+msgstr "алс"
+
+#: constants.py:34
+msgid "Save"
+msgstr "хадгалах"
+
+#: constants.py:35
+msgid "Density"
+msgstr "Density"
+
+#: constants.py:36
+msgid "Saved Map"
+msgstr "газрын зураг"
+
+#: constants.py:37
+msgid "Describe Map"
+msgstr "тодорхойлох"
+
+#: constants.py:38
+msgid "Remove Map"
+msgstr "арилгах"
+
+#: constants.py:39
+msgid "Copy to Clipboard"
+msgstr "хувъ"
+
+#: constants.py:40
+msgid "Add Media"
+msgstr "фото нэмэх"
+
+#: constants.py:41
+msgid "Add Info"
+msgstr "бичих"
+
+#: constants.py:42
+msgid "Delete"
+msgstr "хадгалах"
+
+#: constants.py:43
+msgid "Library"
+msgstr "номын сан"
+
+#: constants.py:44
+msgid "Measure"
+msgstr "зай | орон зай"
+
+#: constants.py:45
+msgid "OurMaps"
+msgstr "хат газрын зураг"
+
+#: constants.py:46
+msgid "Latitude:"
+msgstr "широта:"
+
+#: constants.py:47
+msgid "Longitude:"
+msgstr "долгота:"
+
+#: constants.py:48
+msgid "Description:"
+msgstr "тодорхойлох:"
+
+#: constants.py:49
+msgid "lang=en"
+msgstr "lang=mn"
diff --git a/recorded.py b/recorded.py
new file mode 100644
index 0000000..2378f1d
--- /dev/null
+++ b/recorded.py
@@ -0,0 +1,82 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+import os
+import map
+import gtk
+
+from constants import Constants
+from instance import Instance
+
+class Recorded:
+ SOURCE_DATASTORE = 0
+ SOURCE_LOCAL = 1
+ SOURCE_BUNDLE = 2
+
+ def __init__(self):
+ self.name = None
+ self.recorded = None
+ self.colorStroke = None
+ self.colorFill = None
+ self.type = None
+
+ self.latitude = None
+ self.longitude = None
+
+ self.thumbFilename = None
+ self.fileName = None
+ self.datastoreOb = None
+ self.datastoreId = None
+ self.tags = ""
+
+ self.source = 0
+
+
+ def getFilepath(self):
+ if (self.source == self.__class__.SOURCE_BUNDLE):
+ path = os.path.join( Constants.htmlPath, self.fileName )
+ return path
+ elif (self.source == self.__class__.SOURCE_DATASTORE):
+ return self.datastoreOb.file_path
+ else:
+ return None
+
+
+ def getPixBuf(self):
+ pb = None
+
+ fp = self.getFilepath()
+ if (fp != None):
+ pb = gtk.gdk.pixbuf_new_from_file(fp)
+
+ return pb
+
+
+ def getThumbUrl(self):
+ return "http://127.0.0.1:" + str(map.Map.ajaxPort) + "/getImage.js?id="
+
+
+ def getThumbBasename(self):
+ return str(self.thumbFilename)
+
+
+ def getThumbPath(self):
+ thumbPath = os.path.join( Instance.instancePath, self.getThumbBasename() )
+ return thumbPath \ No newline at end of file
diff --git a/result.py b/result.py
new file mode 100644
index 0000000..a43b67a
--- /dev/null
+++ b/result.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+
+class ServerResult:
+ def __init__(self):
+ self.txt = ""
+ self.headers = [] \ No newline at end of file
diff --git a/savedmap.py b/savedmap.py
new file mode 100644
index 0000000..ca890c0
--- /dev/null
+++ b/savedmap.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+class SavedMap:
+
+ def __init__(self, lat, lng, zoom, imgFile, thumbFile, notes, tags):
+ self.lat = lat
+ self.lng = lng
+ self.zoom = zoom
+ self.imgPath = imgFile
+ self.thumbPath = thumbFile
+ self.notes = notes
+ self.tags = tags
+ self.density = " "
+
+ self.recdDatastoreId = None
+ self.recdLat = None
+ self.recdLng = None
+
+
+ def addViewedRecd(self, id, lat, lng):
+ self.recdDatastoreId = id
+ self.recdLat = lat
+ self.recdLng = lng
diff --git a/serialize.py b/serialize.py
new file mode 100644
index 0000000..3da2df0
--- /dev/null
+++ b/serialize.py
@@ -0,0 +1,206 @@
+import xml.dom.minidom
+from xml.dom.minidom import getDOMImplementation
+from xml.dom.minidom import parse
+import cStringIO
+import os
+import gtk
+
+from sugar.datastore import datastore
+
+from constants import Constants
+from instance import Instance
+import utils
+from recorded import Recorded
+import recorded
+from savedmap import SavedMap
+
+def fillMediaHash( index, m ):
+ doc = None
+ if (os.path.exists(index)):
+ try:
+ doc = parse( os.path.abspath(index) )
+ except:
+ doc = None
+ if (doc == None):
+ return
+
+ try:
+ lastLat = doc.documentElement.getAttributeNode(Constants.mapLat).nodeValue
+ lastLng = doc.documentElement.getAttributeNode(Constants.mapLng).nodeValue
+ lastZoom = doc.documentElement.getAttributeNode(Constants.mapZoom).nodeValue
+ m.ca.preComet()
+ m.ca.cometLogic.handleReceivedMap(lastLat,lastLng,lastZoom)
+ m.ca.postComet()
+ except:
+ # likely an old saved map
+ lastLat = 0
+
+ recdElements = doc.documentElement.getElementsByTagName(Constants.recdMapItem)
+ for el in recdElements:
+ _loadMediaIntoHash( el, m )
+
+ saveElements = doc.documentElement.getElementsByTagName(Constants.recdSavedMapItem)
+ for el in saveElements:
+ _loadSavedMap( el, m )
+
+ saveElements = doc.documentElement.getElementsByTagName(Constants.recdInfoMarker)
+ for el in saveElements:
+ _loadInfoMarker( el, m )
+
+ saveElements = doc.documentElement.getElementsByTagName(Constants.recdLine)
+ for el in saveElements:
+ _loadLine( el, m )
+
+def _loadSavedMap( el, m ):
+ latNode = el.getAttributeNode(Constants.recdLat)
+ lat = latNode.nodeValue
+ lngNode = el.getAttributeNode(Constants.recdLng)
+ lng = lngNode.nodeValue
+
+ zoomNode = el.getAttributeNode(Constants.recdZoom)
+ zoom = zoomNode.nodeValue
+ notesNode = el.getAttributeNode(Constants.recdNotes)
+ notes = notesNode.nodeValue
+ tagsNode = el.getAttributeNode(Constants.recdTags)
+ tags = tagsNode.nodeValue
+
+ m.ca.addSavedMap(lat,lng,zoom,notes,False)
+
+def _loadInfoMarker( el, m ):
+ latNode = el.getAttributeNode(Constants.recdLat)
+ lat = latNode.nodeValue
+ lngNode = el.getAttributeNode(Constants.recdLng)
+ lng = lngNode.nodeValue
+ infoNode = el.getAttributeNode(Constants.recdInfo)
+ info = infoNode.nodeValue
+ iconNode = el.getAttributeNode(Constants.recdIcon)
+ icon = iconNode.nodeValue
+ m.setInfo(lat,lng,info,icon)
+
+def _loadLine( el, m ):
+ idNode = el.getAttributeNode(Constants.lineID)
+ id = idNode.nodeValue
+ colorNode = el.getAttributeNode(Constants.lineColor)
+ color = colorNode.nodeValue
+ thickNode = el.getAttributeNode(Constants.lineThick)
+ thick = thickNode.nodeValue
+ ptsNode = el.getAttributeNode(Constants.linePts)
+ pts = ptsNode.nodeValue
+ m.setLine(id,color,thick,pts)
+
+def _loadMediaIntoHash( el, m ):
+ if (el.getAttributeNode(Constants.recdDatastoreId) == None):
+ return
+
+ datastoreNode = el.getAttributeNode(Constants.recdDatastoreId)
+ if (datastoreNode != None):
+ datastoreId = datastoreNode.nodeValue
+ if (datastoreId != None):
+ #quickly check: if you have a datastoreId that the file hasn't been deleted,
+ #cause if you do, we need to flag your removal
+ #2904 trac
+ datastoreOb = getMediaFromDatastore(datastoreId)
+ if (datastoreOb != None):
+
+ lat = 0
+ lng = 0
+
+ if (el.getAttributeNode(Constants.recdLat) != None):
+ latNode = el.getAttributeNode(Constants.recdLat)
+ lat = latNode.nodeValue
+ else:
+ return
+
+ if (el.getAttributeNode(Constants.recdLng) != None):
+ lngNode = el.getAttributeNode(Constants.recdLng)
+ lng = lngNode.nodeValue
+ else:
+ return
+
+ m.addMedia( lat, lng, datastoreOb )
+
+
+def getMediaFromDatastore( id ):
+ mediaObject = None
+ try:
+ mediaObject = datastore.get( id )
+ finally:
+ if (mediaObject == None):
+ return None
+
+ return mediaObject
+
+
+def fillRecdFromNode( recd, el ):
+ latNode = el.getAttributeNode(Constants.recdLat)
+ if (latNode != None):
+ recd.latitude = latNode.nodeValue
+ lngNode = el.getAttributeNode(Constants.recdLng)
+ if (lngNode != None):
+ recd.longitude = lngNode.nodeValue
+
+ return recd
+
+
+def _addRecdXmlAttrs( el, recd ):
+ el.setAttribute(Constants.recdDatastoreId, str(recd.datastoreId))
+ el.setAttribute(Constants.recdLat, str(recd.latitude))
+ el.setAttribute(Constants.recdLng, str(recd.longitude))
+
+def _addInfoXmlAttrs( el, recd ):
+ el.setAttribute(Constants.recdInfo, recd[2])
+ el.setAttribute(Constants.recdIcon, recd[3])
+ el.setAttribute(Constants.recdLat, str(recd[0]))
+ el.setAttribute(Constants.recdLng, str(recd[1]))
+
+def _addLineXmlAttrs( el, recd ):
+ el.setAttribute(Constants.lineID, recd[0])
+ el.setAttribute(Constants.lineColor, recd[1])
+ el.setAttribute(Constants.lineThick, recd[2])
+ el.setAttribute(Constants.linePts, recd[3])
+
+def _addSaveXmlAttrs( el, smap ):
+ el.setAttribute(Constants.recdLat, str(smap.lat))
+ el.setAttribute(Constants.recdLng, str(smap.lng))
+ el.setAttribute(Constants.recdZoom, str(smap.zoom))
+ el.setAttribute(Constants.recdNotes, str(smap.notes))
+ el.setAttribute(Constants.recdTags, smap.tags)
+ #el.setAttribute(Constants.recdDensity, str(smap.density))
+
+ if (smap.recdDatastoreId != None):
+ el.setAttribute(Constants.recdRecdId, smap.recdDatastoreId)
+ el.setAttribute(Constants.recdRecdLat, smap.recdLat)
+ el.setAttribute(Constants.recdRecdLng, smap.recdLng)
+
+
+def saveMediaHash( m ):
+ impl = getDOMImplementation()
+ album = impl.createDocument(None, Constants.recdAlbum, None)
+ root = album.documentElement
+ root.setAttribute(Constants.mapLat, m.ca.NOW_MAP_CENTER_LAT)
+ root.setAttribute(Constants.mapLng, m.ca.NOW_MAP_CENTER_LNG)
+ root.setAttribute(Constants.mapZoom, m.ca.NOW_MAP_ZOOM)
+
+ for i in range (0, len(m.recs)):
+ recd = m.recs[i]
+ mediaEl = album.createElement(Constants.recdMapItem)
+ root.appendChild( mediaEl )
+ _addRecdXmlAttrs( mediaEl, recd )
+
+ for k, iMarker in m.infoMarkers.iteritems():
+ mediaEl = album.createElement(Constants.recdInfoMarker)
+ root.appendChild( mediaEl )
+ _addInfoXmlAttrs( mediaEl, iMarker.split(";~") )
+
+ for k, iMarker in m.lineData.iteritems():
+ mediaEl = album.createElement(Constants.recdLine)
+ root.appendChild( mediaEl )
+ _addLineXmlAttrs( mediaEl, iMarker.split(";~") )
+
+ for i in range (0, len(m.savedMaps)):
+ smap = m.savedMaps[i]
+ saveEl = album.createElement(Constants.recdSavedMapItem)
+ root.appendChild( saveEl )
+ _addSaveXmlAttrs( saveEl, smap )
+
+ return album
diff --git a/server.py b/server.py
new file mode 100644
index 0000000..9a91ad4
--- /dev/null
+++ b/server.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2008, Media Modifications Ltd.
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+
+import urlparse
+import urllib
+import posixpath
+import SimpleHTTPServer
+import BaseHTTPServer
+
+
+class Server(BaseHTTPServer.HTTPServer):
+ def __init__(self, server_address, logic):
+ BaseHTTPServer.HTTPServer.__init__(self, server_address, RegHandler)
+ self.logic = logic
+
+
+#RegHandler extends SimpleHTTPServer.py (in python 2.4)
+class RegHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
+ def do_POST( self ):
+ self.translate_path()
+
+
+ def do_GET( self ):
+ self.translate_path()
+
+
+ def do_HEAD( self ):
+ self.translate_path()
+
+
+ def translate_path(self):
+ #todo: compare with send_head in parent
+ urlp = urlparse.urlparse(self.path)
+
+ urls = urlp[2]
+ urls = posixpath.normpath(urllib.unquote(urls))
+ urlPath = urls.split('/')
+ urlPath = filter(None, urlPath)
+
+ params = urlp[4]
+ parama = []
+ allParams = params.split('&')
+ for i in range (0, len(allParams)):
+ parama.append(allParams[i].split('='))
+
+ result = self.server.logic.doServerLogic(self.path, urlPath, parama)
+ self.send_response(200)
+ for i in range (0, len(result.headers)):
+ self.send_header( result.headers[i][0], result.headers[i][1] )
+ self.end_headers()
+ self.wfile.write( result.txt ) \ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..235a560
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,25 @@
+#!/bin/env python
+
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sugar.activity import bundlebuilder
+
+import os
+
+os.system("make")
+
+bundlebuilder.start("map") \ No newline at end of file
diff --git a/tray.py b/tray.py
new file mode 100644
index 0000000..d82e442
--- /dev/null
+++ b/tray.py
@@ -0,0 +1,207 @@
+# Copyright (C) 2007, One Laptop Per Child
+# Copyright (c) 2008, Media Modifications Ltd.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import gobject
+import gtk
+import hippo
+
+import sugar
+from sugar.graphics import style
+from sugar.graphics.palette import Palette, ToolInvoker
+from sugar.graphics.toolbutton import ToolButton
+from sugar.graphics.icon import Icon
+
+from constants import Constants
+
+_PREVIOUS_PAGE = 0
+_NEXT_PAGE = 1
+
+class _TrayViewport(gtk.Viewport):
+ __gproperties__ = {
+ 'can-scroll' : (bool, None, None, False,
+ gobject.PARAM_READABLE),
+ }
+
+ def __init__(self, orientation):
+ self.orientation = orientation
+ self._can_scroll = False
+
+ gobject.GObject.__init__(self)
+
+ self.set_shadow_type(gtk.SHADOW_NONE)
+
+ self.traybar = gtk.Toolbar()
+ self.traybar.set_orientation(orientation)
+ self.traybar.set_show_arrow(False)
+ self.add(self.traybar)
+ self.traybar.show()
+
+ self.connect('size_allocate', self._size_allocate_cb)
+
+ def scroll(self, direction):
+ if direction == _PREVIOUS_PAGE:
+ self._scroll_previous()
+ elif direction == _NEXT_PAGE:
+ self._scroll_next()
+
+ def _scroll_next(self):
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ adj = self.get_hadjustment()
+ new_value = adj.value + self.allocation.width
+ adj.value = min(new_value, adj.upper - self.allocation.width)
+ else:
+ adj = self.get_vadjustment()
+ new_value = adj.value + self.allocation.height
+ adj.value = min(new_value, adj.upper - self.allocation.height)
+
+ def _scroll_to_end(self):
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ adj = self.get_hadjustment()
+ adj.value = adj.upper# - self.allocation.width
+ else:
+ adj = self.get_vadjustment()
+ adj.value = adj.upper - self.allocation.height
+
+ def _scroll_previous(self):
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ adj = self.get_hadjustment()
+ new_value = adj.value - self.allocation.width
+ adj.value = max(adj.lower, new_value)
+ else:
+ adj = self.get_vadjustment()
+ new_value = adj.value - self.allocation.height
+ adj.value = max(adj.lower, new_value)
+
+ def do_size_request(self, requisition):
+ child_requisition = self.child.size_request()
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ requisition[0] = 0
+ requisition[1] = child_requisition[1]
+ else:
+ requisition[0] = child_requisition[0]
+ requisition[1] = 0
+
+ def do_get_property(self, pspec):
+ if pspec.name == 'can-scroll':
+ return self._can_scroll
+
+ def _size_allocate_cb(self, viewport, allocation):
+ bar_requisition = self.traybar.get_child_requisition()
+ if self.orientation == gtk.ORIENTATION_HORIZONTAL:
+ can_scroll = bar_requisition[0] > allocation.width
+ else:
+ can_scroll = bar_requisition[1] > allocation.height
+
+ if can_scroll != self._can_scroll:
+ self._can_scroll = can_scroll
+ self.notify('can-scroll')
+
+class _TrayScrollButton(gtk.Button):
+ def __init__(self, icon_name, scroll_direction):
+ gobject.GObject.__init__(self)
+
+ self._viewport = None
+
+ self._scroll_direction = scroll_direction
+
+ self.set_relief(gtk.RELIEF_NONE)
+ self.set_size_request(style.GRID_CELL_SIZE, style.GRID_CELL_SIZE)
+
+ icon = Icon(icon_name = icon_name,
+ icon_size=gtk.ICON_SIZE_SMALL_TOOLBAR)
+ self.set_image(icon)
+ icon.show()
+
+ self.connect('clicked', self._clicked_cb)
+
+ def set_viewport(self, viewport):
+ self._viewport = viewport
+ self._viewport.connect('notify::can-scroll',
+ self._viewport_can_scroll_changed_cb)
+
+ def _viewport_can_scroll_changed_cb(self, viewport, pspec):
+ #self.props.visible = self._viewport.props.can_scroll
+ self.set_sensitive(self._viewport.props.can_scroll)
+
+ def _clicked_cb(self, button):
+ self._viewport.scroll(self._scroll_direction)
+
+ viewport = property(fset=set_viewport)
+
+class HTray(gtk.VBox):
+ def __init__(self, **kwargs):
+ gobject.GObject.__init__(self, **kwargs)
+
+ separator = hippo.Canvas()
+ box = hippo.CanvasBox(
+ border_color=Constants.colorWhite.get_int(),
+ background_color=Constants.colorWhite.get_int(),
+ box_height=1,
+ border_bottom=1)
+ separator.set_root(box)
+ self.pack_start(separator, False)
+
+ hbox = gtk.HBox()
+ self.pack_start(hbox)
+
+ self.scroll_left = _TrayScrollButton('go-left', _PREVIOUS_PAGE)
+ self.scroll_left_event = gtk.EventBox()
+ self.scroll_left_event.add(self.scroll_left)
+ self.scroll_left_event.set_size_request(55, -1)
+ hbox.pack_start(self.scroll_left_event, False)
+
+ self.viewport = _TrayViewport(gtk.ORIENTATION_HORIZONTAL)
+ hbox.pack_start(self.viewport)
+ self.viewport.show()
+
+ self.scroll_right = _TrayScrollButton('go-right', _NEXT_PAGE)
+ self.scroll_right_event = gtk.EventBox()
+ self.scroll_right_event.add(self.scroll_right)
+ self.scroll_right_event.set_size_request(55, -1)
+ hbox.pack_start(self.scroll_right_event, False)
+
+ self.scroll_left.set_focus_on_click(False)
+ self.scroll_left_event.modify_bg(gtk.STATE_NORMAL, sugar.graphics.style.COLOR_TOOLBAR_GREY.get_gdk_color())
+ self.scroll_left.modify_bg(gtk.STATE_ACTIVE, sugar.graphics.style.COLOR_BUTTON_GREY.get_gdk_color())
+
+ self.scroll_right.set_focus_on_click(False)
+ self.scroll_right_event.modify_bg(gtk.STATE_NORMAL, sugar.graphics.style.COLOR_TOOLBAR_GREY.get_gdk_color())
+ self.scroll_right.modify_bg(gtk.STATE_ACTIVE, sugar.graphics.style.COLOR_BUTTON_GREY.get_gdk_color())
+
+ self.scroll_left.viewport = self.viewport
+ self.scroll_right.viewport = self.viewport
+
+ self.connect_after("size-allocate", self._sizeAllocateCb)
+
+ def _sizeAllocateCb(self, widget, event ):
+ self.viewport.notify('can-scroll')
+
+ def get_children(self):
+ return self.viewport.traybar.get_children()
+
+ def add_item(self, item, index=-1):
+ self.viewport.traybar.insert(item, index)
+
+ def remove_item(self, item):
+ self.viewport.traybar.remove(item)
+
+ def get_item_index(self, item):
+ return self.viewport.traybar.get_item_index(item)
+
+ def scroll_to_end(self):
+ self.viewport._scroll_to_end() \ No newline at end of file
diff --git a/utils.py b/utils.py
new file mode 100644
index 0000000..5085d66
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,110 @@
+import base64
+import rsvg
+import re
+import os
+import cairo
+import gc
+import gtk
+from hashlib import md5
+import time
+from time import strftime
+
+from sugar import util
+import _camera
+
+def getStringFromPixbuf(pixbuf):
+ data = [""]
+ pixbuf.save_to_callback(_saveDataToBufferCb, "png", {}, data)
+ return base64.b64encode(str(data[0]))
+
+
+def _saveDataToBufferCb(buf, data):
+ data[0] += buf
+ return True
+
+
+def getPixbufFromString(str):
+ pbl = gtk.gdk.PixbufLoader()
+ data = base64.b64decode( str )
+ pbl.write(data)
+ pbl.close()
+ return pbl.get_pixbuf()
+
+
+def loadSvg( data, stroke, fill ):
+ if ((stroke == None) or (fill == None)):
+ return rsvg.Handle( data=data )
+
+ entity = '<!ENTITY fill_color "%s">' % fill
+ data = re.sub('<!ENTITY fill_color .*>', entity, data)
+
+ entity = '<!ENTITY stroke_color "%s">' % stroke
+ data = re.sub('<!ENTITY stroke_color .*>', entity, data)
+
+ return rsvg.Handle( data=data )
+
+
+def getUniqueFilepath( path, i ):
+ pathOb = os.path.abspath( path )
+ newPath = os.path.join( os.path.dirname(pathOb), str( str(i) + os.path.basename(pathOb) ) )
+ if (os.path.exists(newPath)):
+ i = i + 1
+ return getUniqueFilepath( pathOb, i )
+ else:
+ return os.path.abspath( newPath )
+
+
+def md5File( filepath ):
+ md = md5()
+ f = file( filepath, 'rb' )
+ md.update( f.read() )
+ digest = md.hexdigest()
+ hash = util.printable_hash(digest)
+ return hash
+
+
+def generateThumbnail( pixbuf, scale, thumbw, thumbh ):
+ #need to generate thumbnail version here
+ thumbImg = cairo.ImageSurface(cairo.FORMAT_ARGB32, thumbw, thumbh)
+ tctx = cairo.Context(thumbImg)
+ img = _camera.cairo_surface_from_gdk_pixbuf(pixbuf)
+ tctx.scale(scale, scale)
+ tctx.set_source_surface(img, 0, 0)
+ tctx.paint()
+ gc.collect()
+ return thumbImg
+
+
+def scaleSvgToDim( handle, dim ):
+ #todo...
+ scale = 1.0
+
+ svgDim = handle.get_dimension_data()
+ if (svgDim[0] > dim[0]):
+ pass
+
+ return scale
+
+
+def getDateString( when ):
+ #todo: internationalize the date
+ return strftime( "%a, %b %d, %I:%M:%S %p", time.localtime(when) )
+
+
+def grayScalePixBuf2( pb, copy ):
+ arr = pb.get_pixels_array()
+ if (copy):
+ arr = arr.copy()
+ for row in arr:
+ for pxl in row:
+ y = 0.3*pxl[0][0]+0.59*pxl[1][0]+0.11*pxl[2][0]
+ pxl[0][0] = y
+ pxl[1][0] = y
+ pxl[2][0] = y
+ return gtk.gdk.pixbuf_new_from_array(arr, pb.get_colorspace(), pb.get_bits_per_sample())
+
+
+def grayScalePixBuf( pb, copy ):
+ pb2 = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, pb.get_width(), pb.get_height())
+ pb.saturate_and_pixelate(pb2, 0, 0)
+ return pb2 \ No newline at end of file
diff --git a/webviewer.py b/webviewer.py
new file mode 100644
index 0000000..8ce8201
--- /dev/null
+++ b/webviewer.py
@@ -0,0 +1,148 @@
+# Copyright (C) 2006, Red Hat, Inc.
+# Copyright (C) 2007, One Laptop Per Child
+# Copyright (c) 2008, Media Modifications Ltd.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sugar import env
+
+import logging
+import gobject
+import gtk
+import os
+import hulahop
+hulahop.startup(os.path.join(env.get_profile_path(), 'gecko'))
+import xpcom
+from xpcom.nsError import *
+from xpcom import components
+from xpcom.components import interfaces
+from hulahop.webview import WebView
+
+#import sessionstore
+#from dnd import DragDropHooks
+
+class WebViewer(WebView):
+ def __init__(self):
+ WebView.__init__(self)
+
+ window_creator = WindowCreator(self)
+ cls = components.classes['@mozilla.org/embedcomp/window-watcher;1']
+ window_watcher = cls.getService(interfaces.nsIWindowWatcher)
+ window_watcher.setWindowCreator(window_creator)
+
+ self.connect('realize', self._realize_cb)
+
+ def _realize_cb(self, widget):
+ #drag_drop_hooks = DragDropHooks(self)
+
+ cls = components.classes['@mozilla.org/embedcomp/command-params;1']
+ cmd_params = cls.createInstance('nsICommandParams')
+ #cmd_params.setISupportsValue('addhook', drag_drop_hooks)
+
+ requestor = self.browser.queryInterface(interfaces.nsIInterfaceRequestor)
+ command_manager = requestor.getInterface(interfaces.nsICommandManager)
+ command_manager.doCommand('cmd_clipboardDragDropHook', cmd_params, self.dom_window)
+
+ def get_session(self):
+ return sessionstore.get_session(self)
+
+ def set_session(self, session_data):
+ return sessionstore.set_session(self, session_data)
+
+class WindowCreator:
+ _com_interfaces_ = interfaces.nsIWindowCreator
+
+ def __init__(self, browser):
+ self._popup_creators = []
+ self._browser = browser
+
+ def createChromeWindow(self, parent, chrome_flags):
+ logging.debug('createChromeWindow: %r %r' % (parent, chrome_flags))
+
+ popup_creator = _PopupCreator(self._browser.get_toplevel())
+ popup_creator.connect('popup-created', self._popup_created_cb)
+
+ self._popup_creators.append(popup_creator)
+
+ browser = popup_creator.get_embed()
+
+ if chrome_flags & interfaces.nsIWebBrowserChrome.CHROME_OPENAS_CHROME:
+ logging.debug('Creating chrome window.')
+ browser.is_chrome = True
+ item = browser.browser.queryInterface(interfaces.nsIDocShellTreeItem)
+ item.itemType = interfaces.nsIDocShellTreeItem.typeChromeWrapper
+ else:
+ logging.debug('Creating browser window.')
+ item = browser.browser.queryInterface(interfaces.nsIDocShellTreeItem)
+ item.itemType = interfaces.nsIDocShellTreeItem.typeContentWrapper
+
+ browser.realize()
+
+ return browser.browser.containerWindow
+
+ def _popup_created_cb(self, creator):
+ self._popup_creators.remove(creator)
+
+class _PopupCreator(gobject.GObject):
+ __gsignals__ = {
+ 'popup-created': (gobject.SIGNAL_RUN_FIRST,
+ gobject.TYPE_NONE, ([])),
+ }
+
+ def __init__(self, parent_window):
+ gobject.GObject.__init__(self)
+
+ logging.debug('Creating the popup widget')
+
+ self._parent_window = parent_window
+
+ self._dialog = gtk.Window()
+ self._dialog.set_resizable(True)
+
+ self._dialog.realize()
+ self._dialog.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+
+ self._embed = Browser()
+ self._vis_sid = self._embed.connect('notify::visible', self._notify_visible_cb)
+ self._dialog.add(self._embed)
+
+ def _notify_visible_cb(self, embed, param):
+ self._embed.disconnect(self._vis_sid)
+
+ if self._embed.type == Browser.TYPE_POPUP or self._embed.is_chrome:
+ logging.debug('Show the popup')
+ self._embed.show()
+ self._dialog.set_transient_for(self._parent_window)
+ self._dialog.show()
+ else:
+ logging.debug('Open a new activity for the popup')
+ self._dialog.remove(self._embed)
+ self._dialog.destroy()
+ self._dialog = None
+
+ # FIXME We need a better way to handle this.
+ # It seem like a pretty special case though, I doubt
+ # other activities will need something similar.
+ from webactivity import WebActivity
+ from sugar.activity import activityfactory
+ from sugar.activity.activityhandle import ActivityHandle
+ handle = ActivityHandle(activityfactory.create_activity_id())
+ activity = WebActivity(handle, self._embed)
+ activity.show()
+
+ self.emit('popup-created')
+
+ def get_embed(self):
+ return self._embed \ No newline at end of file