/**
 * realbuzz.com Route Mapper v1.5
 * Copyright: ©2006-2007 realbuzz.com Ltd.
 */


//	{{{ Constants
function debug(){}
function error(){}
if (window.console) {
	if (window.console.debug) debug = console.debug;
	if (window.console.error) error = console.error;
}

var LANG = "en-gb";

if (typeof RB_MAP_PATH == 'undefined') var RB_MAP_PATH = 'routemap/';
if (typeof ADMIN_MODE == 'undefined') var ADMIN_MODE = false;

// Tools
var RB_TOOL_NONE   = 0;
var RB_TOOL_SEARCH = 1;
var RB_TOOL_LINE   = 2;
var RB_TOOL_PIN    = 3;

// Windows
var RB_WINDOW_IDLE = 1;
var RB_WINDOW_MOVE = 2;
var RB_WINDOW_RESIZE = 3;
var RB_WINDOW_CENTRE = -999;

var RB_WINDOW_OPACITY_MIN = 0.85;
var RB_WINDOW_OPACITY_MAX = 0.95;

var RB_KILOMETERS = 1000;
var RB_MILES = 1609.344;

var MAX_MARKERS = 99;

// Colours
var COLOURS = [
	'#ff0000',
	'#ff8f00',
	'#00981d',
	'#2300d4',
	'#8a00c4'
];

// Errors
var RB_ERROR_LOGIN = 1;


var PAGE_PARAMS = httpGetString();

var _mVectorSupport = false;
//	}}} Constants
//	{{{ Objects
//		{{{ RBMap object
function RBMap(id, nointro)
{
	if (!GBrowserIsCompatible()) {
		alert('Sorry, but your browser isn\'t supported.\n\nCurrently supported browsers include:\nMozilla Firefox (www.mozilla.com)\nOpera (www.opera.com)\nInternet Explorer (www.microsoft.com/ie)\nSafari (www.apple.com/safari)');
		return false;
	}
	
	window.isIE = false;
	// turn on SVG if available
	if (window.SVGElement) {
				_mSvgEnabled = true;
		_mSvgForced  = true;
		_mVectorSupport = true;
	}
	else {
		// check for IE and enable vector support
		var ie = navigator.appVersion.match(/MSIE (\d\.\d)/);
		var opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
		if (ie && ie[1] >= 6 && !opera) {
			_mVectorSupport = true;
			window.isIE = true;
		}
	}


	this.loggedIn = false;

	this.routes = [];
	this.markers = [];
	this.lines = [];

	// unit markers
	this.mileMarkers = false;

	// slap in the google map
	this.container = document.getElementById(id);
	this.mapContainer = document.createElement('div');
	with (this.mapContainer.style) {
		height = "100%";
	}
	this.mapContainer = this.container.appendChild(this.mapContainer);
	this.mapContainer.id = 'maptool';

	this.gMap = new GMap2(this.mapContainer);
	this.gMap.enableContinuousZoom();
	this.gMap.enableScrollWheelZoom();
	this.gMap.parent = this;
	//this.gMap.enableDoubleClickZoom();
	
	this.windowCollection = new RBWindowCollection(this);

	this.busySignal = new RBBusyBlocker(this);
	this.busySignal.show();

	// add controls and whatnot
	//this.addControl(new RBToolbox());
	this.gMap.addControl(new RBLogo());
	this.gMap.addControl(new GLargeMapControl());
	this.gMap.addControl(new GMapTypeControl());
	this.gMap.addControl(new GOverviewMapControl(new GSize(130, 130)));

	// jump to saved location
	if (getCookie('s_x') != null) {
		this.gMap.setCenter(new GLatLng(parseFloat(getCookie('s_y')), parseFloat(getCookie('s_x'))), parseInt(getCookie('s_z')));
	}
	else {
		if (LANG == 'en-gb') {
			this.gMap.setCenter(new GLatLng(40.9893, -72.1859), 12);
		} else {
			this.gMap.setCenter(new GLatLng(40.9893, -72.1859), 12);
		}
	}

	// add event listeners
	GEvent.addListener(this.gMap, "click", this.method('onClick'));
	GEvent.addListener(this.gMap, "movestart", this.windowCollection.method('deactivateWindow'));
	GEvent.addListener(this.gMap, "moveend", this.method('updateServerMarkers'));
	//GEvent.addDomListener(this.mapContainer, "DOMMouseScroll", this.method('wheelZoom')); // Firefox
	//GEvent.addDomListener(this.mapContainer, "mousewheel", this.method('wheelZoom')); // IE 
    GEvent.addListener(this.gMap, "mousemove", function (point) { latestKnownHoveringPoint = point;  }); 
	


	// create first route
	this.selectedRoute = false;

	// create fancy windows
	//this.overview	= new RBOverview(this);
	this.search     = new RBSearch(this);
	this.search.hide();
	this.controls   = new RBControls(this);
	//this.controls.hide();
	this.results    = new RBResults(this);
	this.results.hide();
	this.properties = new RBProperties(this, this.selectedRoute);
	this.properties.hide();
	this.loginBox   = new RBLogin(this);
	this.loginBox.hide();
	this.emailRouteBox   = new RBEmailRoute(this);
	this.emailRouteBox.hide();
	this.signUpBox  = new RBSignUp(this);
	this.signUpBox.hide();
	this.myAccount  = new RBMyAccount(this);
	this.myAccount.hide();
	this.pinBox     = new RBPinBox(this);
	this.pinBox.hide();
//	this.toolbox    = new RBToolbox(this);
	this.myRuns     = new RBMyRuns(this);
	//this.miniSearch = new RBMiniSearch(this);

	// Set the default tool
	if (PAGE_PARAMS['edit'] === undefined) {
		this.setTool(RB_TOOL_NONE);
	}
	else {
		this.setTool(RB_TOOL_LINE);
	}

	// pick a default pin
	this.selectPin('small_blue');

	// Download the markers (sponsors, places of interest, etc);
	if (!nointro) {
		// Download the markers (sponsors, places of interest, etc);
		this.updateServerMarkers();
	}


	// jump to linked route or show welcome window
	

	if (PAGE_PARAMS['register']) {
		this.signUp();
	}
	else if (PAGE_PARAMS['logon']) {
		this.login();
	}
	else if (PAGE_PARAMS['account']) {
		this.editAccount();
	}
	else if (PAGE_PARAMS['logoff']) {
		this.logout();
	}
	else if (PAGE_PARAMS['r']) {
		var refs = PAGE_PARAMS['r'].split(',');
		for (var i=0; i<refs.length; i++) {
			this.loadRoute(refs[i]);
		}
	}
	else if (PAGE_PARAMS['llz']) {
		var coords = PAGE_PARAMS['llz'].split(',');
		this.gMap.setCenter(new GLatLng(coords[0], coords[1]), parseInt(coords[2]));
	}
	else if (PAGE_PARAMS['m']) {
		this.centreOnMarker(PAGE_PARAMS['m']);
	}
	else if (!nointro) {
		if (PAGE_PARAMS['postcode']) {
			this.jumpToPostcode(PAGE_PARAMS['postcode']);
		}
		// create welcome window
		var welcomeWindow = this.windowCollection.createDialogWindow(
				450, 165,
				'Rite Aid Cleveland Marathon Route Planner',
				'Would you like to search the routes created by other people or create/edit your own? Or if you are new, you may want to read the \'Starting Out Instructions\' first.',
				[
				{name: 'Search', action: this.method('setTool', RB_TOOL_SEARCH)},
				{name: 'Create/edit', action: this.method('setTool', RB_TOOL_LINE)},
                {name: 'Starting out instructions', action: function() { window.location.href = 'http://www.realbuzz.com/route_cleveland/help.php#start' } }
				]);
	}

	this.checkLogin();
}
//			{{{ RBMap Methods
RBMap.prototype.toString = function()
{
	return '[object RBMap]';
}
RBMap.prototype.updateServerMarkers = function()
{
	var sw = this.gMap.getBounds().getSouthWest();
	var ne = this.gMap.getBounds().getNorthEast();
	
	
	if (this.serverMarkersxmlReq) this.serverMarkersxmlReq.abort();
	this.serverMarkersxmlReq = this.GetXML(RB_MAP_PATH+'scripts/get_markers.php?z='+this.gMap.getZoom()+'&s='+sw.lng()+'&w='+sw.lat()+'&n='+ne.lng()+'&e='+ne.lat(), this.method('processXML'));
}
RBMap.prototype.getTotalLength = function()
{
	var total = 0;
	for (var i=0; i<this.routes.length; i++) {
		var r = this.routes[i];
		if (r) {
			total += r.getLength();
		}
	}
	return total;
}
RBMap.prototype.removeAllRoutes = function()
{
		this.myRuns.dehighlightAll();
	for (var i=0; i<this.routes.length; i++) {
		this.removeRoute(i);
	}
	this.routes = [];
	this.selectedRoute = false;
	this.myRuns.mapUnEdited = true;
	GEvent.trigger(this, 'scenechanged');
}
RBMap.prototype.removeRoute = function(i)
{
	if (this.routes[i] !== undefined) {
		this.routes[i].removeAllPoints();
		if (this.routes[i].pins !== undefined) this.routes[i].pins.removeAllPoints();
	}
	this.controls.updateDistances();
	delete this.routes[i];
	GEvent.trigger(this, 'scenechanged');
}
RBMap.prototype.setTool = function(tool, nowarning)
{
	if (typeof tool == 'object') tool = arguments[1];	// skip event object
	if (isNaN(tool)) {
		error('RBMap: Cannot change tool, tool is NaN :', arguments);
		return false;
	}
		tool = parseInt(tool);
	if (this.gMap) this.gMap.closeInfoWindow();


	nowarning = this.myRuns.mapUnEdited;

	// Confirm if they wish to change from edit mode
	if (tool == RB_TOOL_SEARCH && (this.currentTool == RB_TOOL_LINE || this.currentTool == RB_TOOL_PIN)
			&& (!nowarning && !confirm('Are you sure you wish to leave edit mode?\n\nYou will lose your unsaved route if you click OK'))) return;

	// remove route when changing to edit mode
	if (!ADMIN_MODE && (this.currentTool == RB_TOOL_NONE || this.currentTool == RB_TOOL_SEARCH) && (tool == RB_TOOL_PIN || tool == RB_TOOL_LINE)) {
		this.removeAllRoutes();
		this.windowCollection.createMessageWindow(400, 230, 'Edit mode', 'You are now in edit mode and can start to plot a route, or edit routes if you have them already saved.\n\n<strong>To start a new route:</strong>\nEnter your Postcode or place name into the \'Postcode or place name search\' (top right of screen) to locate your starting position. Then, click directly onto the map to start plotting your route. Keep clicking along your route to plot it on the map. Then click the \'SAVE YOUR ROUTE\' button when you are happy with it. You can move the map along by dragging and dropping it.', 'information.png');
	}
	// remove routes when switching from edit mode to search mode
	else if ((tool == RB_TOOL_NONE || tool == RB_TOOL_SEARCH) && (this.currentTool == RB_TOOL_PIN || this.currentTool == RB_TOOL_LINE)) {
		this.removeAllRoutes();
	}


	switch (tool) {
		case RB_TOOL_NONE:
			this.search.hide();
//			this.toolbox.hideEditTools();
			hideEditTools();
			this.selectedRoute = false;
			this.properties.setRoute(false);
			this.pinBox.hide();
			this.loginBox.hide();
			this.signUpBox.hide();
			break;
		case RB_TOOL_SEARCH:
//			this.toolbox.hideEditTools();
			hideEditTools();
			this.properties.hide();
			this.search.show();
			this.search.getWindow().moveTo(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE);
			this.search.getWindow().activate();
			this.selectedRoute = false;
			this.properties.setRoute(false);
			this.pinBox.hide();
			this.loginBox.hide();
			this.signUpBox.hide();
			break;
		case RB_TOOL_LINE:
			this.loginRequired();
//			this.toolbox.lineButton.changeImage('edit_button_hi.png');
//			this.toolbox.pinButton.changeImage('pin_button.png');
//			this.toolbox.showEditTools();
			showEditTools();
			enableLineButton();
			this.search.hide();
			this.results.hide();
			if (this.selectedRoute === false) this.selectedRoute = this.createRoute();
			this.properties.setRoute(this.selectedRoute);
			this.pinBox.hide();
			break;
		case RB_TOOL_PIN:
			this.loginRequired();
//			this.toolbox.pinButton.changeImage('pin_button_hi.png');
//			this.toolbox.lineButton.changeImage('edit_button.png');
//			this.toolbox.showEditTools();
			showEditTools();
			enablePinsButton();
			this.search.hide();
			this.results.hide();
			if (this.selectedRoute === false) this.selectedRoute = this.createRoute();
			this.properties.setRoute(this.selectedRoute);
			this.pinBox.show();
			break;
		default:
			error ('RBMap: Unknown tool:', tool);
			break;
	}

	this.currentTool = tool;
	this.redraw();
}
RBMap.prototype.selectPin = function(pin)
{
		this.selectedPin = pin;
}
RBMap.prototype.getSelectedPin = function()
{
	return Pins[this.selectedPin];
}
RBMap.prototype.getSelectedPinName = function()
{
	return this.selectedPin;
}
RBMap.prototype.routeProperties = function()
{
	this.properties.populate(this.getSelectedRoute().properties);
	this.properties.show();
	if (this.getSelectedRoute() && this.getSelectedRoute().properties && this.getSelectedRoute().properties.ref) {
		this.properties.saveAsButton.style.display = '';
	} else {
		this.properties.saveAsButton.style.display = 'none';
	}
	this.properties.getWindow().moveTo(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE);
	this.windowCollection.activateWindow(this.properties.getWindow().index);
	this.loginRequired();
}
RBMap.prototype.resetEditor = function()
{
	if (this.myRuns.mapUnEdited || confirm('Are you sure you wish to clear this route and start again, you will lose all unsaved information.')) {
		this.removeAllRoutes();
		this.selectedRoute = this.createRoute();
		this.properties.setRoute(this.selectedRoute);
	}
}
RBMap.prototype.undoEdit = function()
{
	var r = this.getSelectedRoute();
	switch (this.getCurrentTool()) {
		case RB_TOOL_PIN:
			r.pins.popPoint();
			r.pins.redraw();
			break;
		case RB_TOOL_LINE:
			r.popPoint();
			r.redraw();
			this.controls.updateDistances();
			break;
	}
}
RBMap.prototype.getCurrentTool = function()
{
	return this.currentTool;
}
RBMap.prototype.getOverview = function()
{
	return document.getElementById(this.getMapContainer().id+'_overview');
}
RBMap.prototype.getMarker = function(index)
{
	return this.markers[index];
}
RBMap.prototype.getContainer = function()
{
	return this.container;
}
RBMap.prototype.getMapContainer = function()
{
	return this.mapContainer;
}
RBMap.prototype.addControl = function(control)
{
	if (!control.isRB()){
		error('Not an RBControl', control);
		return false;
	}
	control.initialize(this);
	control.parent = this;
}
RBMap.prototype.enableMileMarkers = function()
{
		this.mileMarkers = true;
	this.redraw();
}
RBMap.prototype.disableMileMarkers = function()
{
		this.mileMarkers = false;
	this.redraw();
}
RBMap.prototype.enableKilometreMarkers = function()
{
		this.kilometreMarkers = true;
	this.redraw();
}
RBMap.prototype.disableKilometreMarkers = function()
{
		this.kilometreMarkers = false;
	this.redraw();
}
RBMap.prototype.mileMarkersEnabled = function()
{
	return this.mileMarkers;
}
RBMap.prototype.kilometreMarkersEnabled = function()
{
	return this.kilometreMarkers;
}
RBMap.prototype.saveLocation = function()
{
	
	if (getCookie('s_x') && !confirm('Are you sure you wish to override your previous home location with the current location?')) {
		return false;
	}
	var today = new Date();
	today.setTime( today.getTime() );

	var p = this.gMap.getCenter();
	var expires = new Date( today.getTime() + (365 * 1000 * 60 * 60 * 24) );
	setCookie('s_x', p.x, expires);
	setCookie('s_y', p.y, expires);
	setCookie('s_z', this.gMap.getZoom(), expires);
	this.gMap.savePosition();
	alert('This is now your default location.');
}

/**
 * Adds a marker to the map, these are stored in this.markers[]
 * if an index is passed, it's forced into that array position (BE CAREFUL NOT TO OVERWRITE AN EXISTING ITEM)
 * returns the marker's array index
 */
RBMap.prototype.addMarker = function(point, icon, draggable, index, inert)
{
	if (!icon) icon = G_DEFAULT_ICON;
	if (typeof draggable == 'undefined') draggable = true;
		var m;
	if (inert) m = new GMarker(point, icon, inert);
	else m = new GMarker(point, {icon: icon, draggable: draggable});
	try {
		this.gMap.addOverlay(m);
	}
	catch(e){}
	if (typeof index != 'undefined' && index !== false) {
		this.markers[index] = m;
		return index;
	}
	else {
		this.markers.push(m);
		return this.markers.length-1;
	}
}
RBMap.prototype.emailRoute = function()
{
	this.emailRouteBox.show();
	this.emailRouteBox.getWindow().moveTo(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE);
	this.emailRouteBox.activate();
}
RBMap.prototype.removeMarker = function(index)
{
	var m = this.markers[index];
	this.gMap.removeOverlay(m);
	delete this.markers[index];
}
/**
 * Updates a marker, moving it to the new position and changing it's icon
 * if no update is required, it does nothing, thus saving CPU cycles
 * if marker doesn't exist, it is created
 * returns the index of the marker
 */
// FIXME: this needs optimising for IE
RBMap.prototype.updateMarker = function(index, point, icon, draggable, inert)
{
		if (index === undefined) index = false;
	if (!icon) icon = G_DEFAULT_ICON;
	if (!inert) inert = false;
	if (typeof draggable == 'undefined') draggable = true;

	// update point
	var m = this.markers[index];
	if (index !== false && typeof m != 'undefined') {
		// has the icon changed? if so we need to destory and recreate the marker from scratch
		if (m.getIcon() != icon) {
						this.gMap.removeOverlay(m);
			if (inert) this.markers[index] = new GMarker(point, icon, inert);
			else this.markers[index] = new GMarker(point, {icon: icon, draggable: draggable});
			this.gMap.addOverlay(this.markers[index]);	
		}
		// has it moved?
		else if (!m.getPoint().equals(point)) {
						m.setPoint(point);
		}
		// dragging ability changed?
		else if (m.draggingEnabled() != draggable) {
			if (draggable) {
								try { // FIXME Causes an error, but everything seems to work???
					m.enableDragging();
				}
				catch(e){}
			}
			else {
								m.disableDragging();
			}
		}
		return index;
	}
	// add point
	else {
				return this.addMarker(point, icon, draggable, index, inert);
	}
}
/**
 * Creates a GPolyLine on the map
 * Returns the array index to the line
 */
RBMap.prototype.addLine = function(points, colour, width, opacity)
{
		if (!colour) colour = '#1E5F0A';
	if (!width) width = 6;
	if (!opacity) opacity = 0.75;
	var thick = new GPolyline(points, '#ffffff', width, opacity);
	var thin = new GPolyline(points, colour, width-4, opacity*0.75);
	var lines = {thick: thick, thin: thin};
	this.gMap.addOverlay(thick);
	this.gMap.addOverlay(thin);
	return (this.lines.push(lines))-1;
}
/**
 * Deletes a line from the map
 * Returns it's old index
 */
RBMap.prototype.removeLine = function(index)
{
		if (index === undefined || index === false) return;
	this.gMap.removeOverlay(this.lines[index]['thick']);
	this.gMap.removeOverlay(this.lines[index]['thin']);
	// delete line object, but don't remove index
	delete this.lines[index];
}
/**
 * Adds a new route
 */
RBMap.prototype.createRoute = function(ref)
{

	return this.routes.push(new RBRoute(this, this.routes.length, ref))-1;
}
/**
 * Returns the first route with a given ref
 */
RBMap.prototype.getRouteByRef = function(ref)
{
	for (var i=0; i<this.routes.length; i++) {
		if (this.routes[i] && this.routes[i].ref == ref) return i;
	}
	return false;
}
RBMap.prototype.removeRouteByRef = function(ref)
{
	this.myRuns.dehighlightRef(ref);
	GEvent.trigger(this, 'scenechanged');
	return this.removeRoute(this.getRouteByRef(ref));
}
/**
 * Returns the currently selected route
 */
RBMap.prototype.getSelectedRoute = function()
{
	return this.routes[this.selectedRoute];
}
RBMap.prototype.getRoute = function(index)
{
	return this.routes[index];
}
RBMap.prototype.redraw = function()
{
	if (!this.routes) return;
	for (var i=0; i<this.routes.length; i++) {
		var r = this.routes[i];
		if (typeof r != 'undefined') {
			r.redraw();
		}
	}
}
RBMap.prototype.getAllGPXRoutes = function()
{
	var refs = '';
	if (this.routes) {
		for (var i=0; i<this.routes.length; i++) {
			var r = this.routes[i];
			if (typeof r != 'undefined') {
				if (refs) refs += ',';
				refs += r.ref;
			}
		}
	}
	if (!refs) {
		alert('You have no routes loaded.');
	} else {
		this.gpxRoute(refs);
	}
}
/**
 *	Redraws the line and all markers representing a given route
 */
RBMap.prototype.redrawRoute = function(index)
{
	this.routes[index].redraw();
}
/**
 *	Redraws the currently selected route
 */
RBMap.prototype.redrawSelectedRoute = function()
{
	return this.redrawRoute(this.selectedRoute);
}
/**
 * Load route from server
 */
RBMap.prototype.loadRoute = function(ref)
{
	ref = escape(ref);
	if (this.currentTool == RB_TOOL_PIN || this.currentTool == RB_TOOL_LINE) ref += '&edit';
	this.loadRouteXMLReq = this.GetXML(RB_MAP_PATH+'scripts/load_route.php?r='+ref, this.method('processXML'));
	this.busySignal.show();
}
RBMap.prototype.centreOnMarker = function(id)
{
	this.GetXML(RB_MAP_PATH+'scripts/get_single_marker.php?m='+id, this.method('processXML'));
}
RBMap.prototype.processUploadedGPX = function(data)
{
	if (this.getCurrentTool() == RB_TOOL_SEARCH || this.getCurrentTool() == RB_TOOL_NONE)
		this.setTool(RB_TOOL_LINE);
	else
		this.resetEditor();

	this.myRuns.mapUnEdited = false;
	var r = this.getSelectedRoute();
	for (var i=0; i<data.length; i++) {
		point = new GLatLng(data[i]['lat'], data[i]['lng']);
		r.pushPoint(point);
	}
	r.redraw();
	this.controls.updateDistances();
	this.fitMapToAllRoutes();
	alert('Your GPS file has been loaded, don\'t forget to save your route.');
}
RBMap.prototype.uploadGPX = function()
{
	if (this._uploadGPXPopup && this._uploadGPXPopup.close) this._uploadGPXPopup.close();
	this._uploadGPXPopup = window.open('gpx_upload.php', '', 'width=450,height=300,status=0,toolbar=0,scrollbars=0');
}
/**
 * GPX route from server
 */
RBMap.prototype.gpxRoute = function(ref)
{
	ref = escape(ref);
	oldonload = window.onbeforeunload;
	window.onbeforeunload = function() {}
	window.location.href = RB_MAP_PATH+'scripts/gpx_route.php?r='+ref;
	setTimeout(function () {window.onbeforeunload = oldonload;}, 1);
}
/**
 * TODO KML route from server
 */
RBMap.prototype.kmlRoute = function(ref)
{
	ref = escape(ref);
	oldonload = window.onbeforeunload;
	window.onbeforeunload = function() {}
	window.location.href = RB_MAP_PATH+'scripts/kml_route.php?r='+ref;
	setTimeout(function () {window.onbeforeunload = oldonload;}, 1);
}
/**
 * Posts all the route data to the server for saving
 */
RBMap.prototype.save = function()
{
		if (!this.loginRequired()) return;
	this.gMap.closeInfoWindow();

	// XXX: Each line has it's own properties... ??? (multi-line is currently not used, so this isn't an issue)
	var p = this.routes[this.selectedRoute].properties;
	if (!p.title) {
		this.routeProperties(true);
		return;
	}
	this.busySignal.show();

	var data = '';
	data += 'title='+escape(p.title);
	data += '&postcode='+escape(p.postcode);
	data += '&terrain='+escape(p.terrain);
	data += '&gradient='+escape(p.gradient);
	data += '&shared='+escape(p.shared);
	data += '&running=true';
	data += '&walking=false';
	data += '&cycling=false';
//	data += '&notes='+escape(p.notes);
	data += '&length='+escape(this.routes[this.selectedRoute].getLength());
	if (p.ref) data += '&ref='+escape(p.ref);

	// loop through routes
	for (var i=0; i<this.routes.length; i++) {
		var r = this.routes[i].getPoints();
		data += '&route['+i+']=';
		// loop through points of route
		for (var j=0; j<r.length; j++) {
			var p = r[j];
			data += p.lat()+','+p.lng()+',';
		}
	}

	data += '&markers=';
	// loop through markers
	var routePins = this.routes[this.selectedRoute].pins;
	var pins = routePins.getPoints();
	for (var j=0; j<pins.length; j++) {
		var p = pins[j];
		data += p.lat()+','+p.lng()+',';
		// save icon
		data += routePins.pointIcons[j]+',';
	}
	// add notes
	for (var j=0; j<pins.length; j++) {
		var note = routePins.getMarker(j).note;
		if (note === undefined) note = '';
		data += '&markernotes['+j+']='+escape(note);
	}



	this.PostXML(RB_MAP_PATH+'scripts/save_route.php', data, this.method('processXML'));
	this.myRuns.mapUnEdited = true;
}
/**
 * Universal function to handle all incoming XML
 */
RBMap.prototype.processXML = function(request)
{
		if (request.responseXML == null) {
		//error('RBMap: no XML given');
		return false;
	}
	var fc = request.responseXML.firstChild;
	if (!fc) {
		error('RBMap: no root node');
		return false;
	}
	switch (fc.tagName) {
		// Error server side :(
		case 'error':

			// improved geocoding
			if (fc.getAttribute('code') == 'postcode') {
				var geocoder = new GClientGeocoder();
				var _this = this;
				geocoder.getLatLng(
					fc.getAttribute('query'),
					function(point) {
						if (!point) {
							_this.windowCollection.createMessageWindow(320, 130, 'Error', 'The location "'+fc.getAttribute('query')+'" was not found.', 'error.png');
						} else {
							_this.gMap.panTo(point);
						}
					}
				);
				break;
			}



			error('RBMap: **Server side error** -- '+fc.firstChild.nodeValue);
			this.busySignal.hide();
			if (fc.getAttribute('code') == RB_ERROR_LOGIN) this.login();
			else this.windowCollection.createMessageWindow(320, 130, 'Error', fc.firstChild.nodeValue, 'error.png');
			break;

		// Saving a route
		case 'save':
						this.windowCollection.createMessageWindow(300, 125, 'Saved successfully', 'Your route has been saved successfully', 'information.png');
			this.routes[0].properties.ref = fc.firstChild.nodeValue;
			this.busySignal.hide();
			this.myRuns.refresh();
			break;

		// Markers received
		case 'markers':
		case 'singlemarker':
						if (this.serverMarkers === undefined) {
				this.serverMarkers = [];
			}
			var xM = fc.getElementsByTagName('marker');
			if (!ADMIN_MODE) for (var i=0; i<this.serverMarkers.length; i++) {
				if (this.serverMarkers[i] !== undefined) this.serverMarkers[i].removeAllPoints();
			}

			for (var i=0; i<xM.length; i++) {
				var id = xM[i].getAttribute('index');
				var cat = xM[i].getAttribute('category');
				var point = new GLatLng(xM[i].getAttribute('lat'), xM[i].getAttribute('lng'));
				var note = xM[i].firstChild.nodeValue;
				if (!ADMIN_MODE) {
					switch (cat) {
					case '2': // Puma
						note = '<div class="marker_balloon clinic_balloon">'+note+'</div>';
						break;
					case '3': // Puma
						note = '<div class="marker_balloon puma_balloon">'+note+'</div>';
						break;
					case '4':	// Holiday Inn
						note = '<div class="marker_balloon holidayinn_balloon">'+note+'</div>';
						break;
					default:
						note = '<div class="marker_balloon">'+note+'</div>';
						break;
					}
				}
				var icon = xM[i].getAttribute('caticon');
				if (!icon) icon = 'default_pin';

				if (this.serverMarkers[cat] === undefined) this.serverMarkers[cat] = new RBServerPins(this);
				this.serverMarkers[cat].pushPoint(point, note, icon, id, ADMIN_MODE);

				this.serverMarkers[cat].redraw();
				if (fc.tagName == 'singlemarker') {
					this.gMap.setCenter(point, 15);
					this.serverMarkers[cat].showNoteById(id);
					this.updateServerMarkers();
				}
			}
			break;

		// Search results received
		case 'search':
						if (this.getCurrentTool() !== RB_TOOL_SEARCH) return false;
			var xR = fc.getElementsByTagName('result');
			this.results.clearResults();
			this.results.show();
			this.results.getWindow().moveTo(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE);
			this.results.activate();
			for (var i=0; i<xR.length; i++) {
				var r = xR[i];
				var unit = (r.getAttribute('unit') == 'Miles')?RB_MILES:RB_KILOMETERS;
				this.results.addResult(
						r.getAttribute('ref'),
						r.getAttribute('title'),
//						r.getAttribute('author'),
						Math.round2DP(r.getAttribute('length')/unit)+' '+r.getAttribute('unit'),
						r.getAttribute('postcode')
					);
			}

			this.setTool(RB_TOOL_NONE);
			break;

		case 'gpxroute':
			break;
		// Load a route
		case 'route':
						this.busySignal.hide();

			// is this result for a different mode? if so then ignore it (lagged load)
			if ( !(fc.getAttribute('mode') != 'edit' && (this.currentTool == RB_TOOL_LINE || this.currentTool == RB_TOOL_PIN)) ) {

				if (fc.getAttribute('mode') == 'edit') {
					this.removeAllRoutes();
				}
				this.selectedRoute = this.createRoute(fc.getAttribute('ref'));
				this.properties.setRoute(this.selectedRoute);
				var r = this.getSelectedRoute();

				this.myRuns.highlightRef(fc.getAttribute('ref'), r.colour);

				var xP = fc.getElementsByTagName('point');
				for (var i=0; i<xP.length; i++) {
					var p = xP[i];
					r.pushPoint(new GLatLng(p.getAttribute('lat'), p.getAttribute('lng')));
				}

				// populate the routes properties
				var rp = r.properties;
				rp.ref = fc.getAttribute('ref');
				rp.title = fc.getAttribute('title');
				rp.postcode = fc.getAttribute('postcode');
				rp.terrain = fc.getAttribute('terrain');
				rp.gradient = fc.getAttribute('gradient');
				rp.shared = fc.getAttribute('shared');
				rp.cycling = fc.getAttribute('cycling');
				rp.walking = fc.getAttribute('walking');
				rp.running = fc.getAttribute('running');

				// FIXME: Safari returns null for .firstChild
				if (fc.getElementsByTagName('notes')[0].firstChild) {
					rp.notes = fc.getElementsByTagName('notes')[0].firstChild.nodeValue;
				}
				else rp.notes = '';




				// Draw the pins
				var xM = fc.getElementsByTagName('marker');
				if (r.pins !== undefined) r.pins.removeAllPoints();
				if (fc.getAttribute('mode') == 'edit') {
					r.pins = new RBPins(this);
				}
				else {
					r.pins = new RBServerPins(this);
				}
				for (var i=0; i<xM.length; i++) {
					var point = new GLatLng(xM[i].getAttribute('lat'), xM[i].getAttribute('lng'));
					var note = (xM[i].firstChild)?xM[i].firstChild.nodeValue:false;

					var icon = xM[i].getAttribute('icon');
					r.pins.pushPoint(point, note, icon);

				}
				r.pins.redraw();



				r.redraw();
				this.controls.updateDistances();
				this.fitMapToAllRoutes();
				this.myRuns.mapUnEdited = true;
				GEvent.trigger(this, 'routeload');
				GEvent.trigger(this, 'scenechanged');
			}
			break;

		// Jump to a location from mini search
		case 'jump':
						var p = fc.getAttribute('postcode');
			if (p == this._lastPostcodeJump) {
				//this.gMap.setCenter(new GLatLng(fc.getAttribute('lat'), fc.getAttribute('lng')), 14);
				this.gMap.panTo(new GLatLng(fc.getAttribute('lat'), fc.getAttribute('lng')));
			}
			break;

		case 'login':
						if (fc.getAttribute('status') == 'failed') {
				this.windowCollection.createMessageWindow(330, 130, 'Log on Failed', 'Log on failed, please check that your username and password is correct.', 'error.png', this.method('login'));
			}
			else {
				this.loginBox.hide();
				this.loggedIn = true;
				this.myRuns.refresh();
				document.getElementById('login_items').style.display = 'none';
				document.getElementById('logout_items').style.display = 'inline';

				/*
				document.getElementById('main-nav-sign').style.display = "none";
				document.getElementById('main-nav-signout').style.display = "";
				document.getElementById('main-nav-register').style.display = "none";
				document.getElementById('main-nav-account').style.display = "";
				*/

				//this.toolbox.loginButton.hide();
				//this.toolbox.logoutButton.show();
			}
			if (fc.getAttribute('status') == 'created') {
				this.signUpBox.hide();
				this.windowCollection.createMessageWindow(330, 130, 'Account Created', 'Your account was created successfully and you have been automatically logged in.', 'information.png');
			}
			this.busySignal.hide();
			break;
		case 'logout':
			this.loggedIn = false;
			document.getElementById('login_items').style.display = 'inline';
			document.getElementById('logout_items').style.display = 'none';
			/*
			document.getElementById('main-nav-sign').style.display = "";
			document.getElementById('main-nav-signout').style.display = "none";
			document.getElementById('main-nav-register').style.display = "";
			document.getElementById('main-nav-account').style.display = "none";
			*/
			//this.toolbox.loginButton.show();
			//this.toolbox.logoutButton.hide();
			this.myRuns.clear();
			this.removeAllRoutes();
			this.setTool(RB_TOOL_NONE, true);
			this.busySignal.hide();
			this.windowCollection.createMessageWindow(330, 105, 'Logged out', 'You have been logged out successfully', 'information.png');
			break;
		case 'emailed':
			this.windowCollection.createMessageWindow(330, 105, 'Emailed', 'The route has been emailed to your friend successfully.', 'information.png');
			break;

		// my runs received
		case 'myroutes':
			var xR = fc.getElementsByTagName('route');
			if (xR.length > 0) {
				this.myRuns.clearList();
				for (var i=0; i<xR.length; i++) {
					var r = xR[i];
					this.myRuns.addRun(r.getAttribute('title'), r.getAttribute('ref'))
				}
			}
			else this.myRuns.noRuns();

//			this.myRuns.catBox.value = fc.getAttribute('cat');
			break;

		// routes deleted
		case 'deleted':
			this.myRuns.refresh();
			this.busySignal.hide();
			this.windowCollection.createMessageWindow(330, 105, 'Rotues Deleted', 'Routes deleted successfully', 'information.png');
			break;
		// returned by login_check when user isn't logged in
		case 'guest':
			this.busySignal.hide();
			break;
		default:
			error('RBMap: Unknown XML data - ignored');
			break;
	}
}
RBMap.prototype.jumpToPostcode = function(postcode)
{
	this._lastPostcodeJump = postcode;
	this.xmlReq = this.GetXML(RB_MAP_PATH+'scripts/get_location.php?p='+escape(postcode), this.method('processXML'));
}
RBMap.prototype.signUp = function()
{
	window.open("http://"+window.location.host+"/en-gb/Signup/index?pageID=19");
	return;
	this.loginBox.hide();
	this.signUpBox.show();
}
RBMap.prototype.editAccount = function()
{
	if (!this.loginRequired()) return;
	this.loginBox.hide();
	this.myAccount.show();
}

/**
 * Asks the server if we're logged in, useful if they reload the page and the session still exists
 */
RBMap.prototype.checkLogin = function()
{
	this.busySignal.show();
	this.GetXML(RB_MAP_PATH+'scripts/check_login.php', this.method('processXML'));
}
RBMap.prototype.login = function()
{
	this.signUpBox.hide();
	this.loginBox.show();
	this.loginBox.form.password.value = '';
	this.loginBox.getWindow().moveTo(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE);
	this.loginBox.activate();
	this.loginBox.form.username.focus();
}
RBMap.prototype.logout = function()
{
//	this.toolbox.logoutButton.hide();
//	this.toolbox.loginButton.show();
	this.loginBox.hide();
	this.loggedIn = false;
	this.myRuns.clear();
	this.GetXML(RB_MAP_PATH+'scripts/logout.php', this.method('processXML'));
}
RBMap.prototype.loginRequired = function()
{
	if (!this.loggedIn) this.login();
	return this.loggedIn;
}
//			}}} RBMap Methods

//			{{{ XML Controls
RBMap.prototype.GetXML = function(file, func)
{
	var request = document.createXMLHttpRequest();

	request.open("GET", file, true);
	if (typeof(request.setRequestHeader) != "undefined") {
		request.setRequestHeader("Connection", "close");
	}
	if (func) {
		request.onreadystatechange = function() {
			if (request.readyState == 4) {
				func(request);
			}
		}
	}
	request.send(null);
	return request;
}
RBMap.prototype.PostXML = function(file, data, func)
{
	var request = document.createXMLHttpRequest();

	request.open("POST", file, true);
	if (typeof(request.setRequestHeader) != "undefined") {
		request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=ISO-8859-1');
		request.setRequestHeader("Connection", "close");
	}
	if (func) {
		request.onreadystatechange = function() {
			if (request.readyState == 4) {
				func(request);
			}
		}
	}
	request.send(data);
}
//			}}} XML Controls

//			{{{ RBMap Events
RBMap.prototype.wheelZoom = function(e)
{
	if (e.cancelable) e.preventDefault();
	this.gMap.setCenter(latestKnownHoveringPoint);
	if ((e.detail || -e.wheelDelta) < 0 ) this.gMap.zoomIn();
	else this.gMap.zoomOut();
	return false; 

}
RBMap.prototype.onClick = function(marker, point)
{
	// did we click a marker?
	if (marker) {
	}
	// if info window is open then just close it (bug screws up marker placment if info window is open)
	else if (!this.gMap.getInfoWindow().isHidden()) {
		this.gMap.closeInfoWindow();
	}
	// nope, we clicked a clear area
	else {
		this.myRuns.mapUnEdited = false;
		var r = this.getSelectedRoute();
		switch (this.getCurrentTool()) {
			case RB_TOOL_PIN:
				var id = r.pins.pushPoint(point, '', this.getSelectedPinName());
				r.pins.redraw();
				var m = this.getMarker(r.pins.pointMarkers[id-1]);
				r.pins.editNote(m);
				break;
			case RB_TOOL_LINE:
				r.pushPoint(point);
				r.redraw();
				this.controls.updateDistances();
				break;
		}
		if (r && r.checkEndMarkers) r.checkEndMarkers();
	}
	this.windowCollection.deactivateWindow();
}
RBMap.prototype.loopBack = function()
{
	var r = this.getSelectedRoute();
	r.loopBack();
}
RBMap.prototype.fitMapToRoute = function(route)
{
	var box = new GLatLngBounds();
	var points = route.getPoints();
	for(var i=0;i<points.length;i++){
		box.extend(points[i]);
	};
	var lngCentre = (box.getNorthEast().lng() + box.getSouthWest().lng()) / 2;
	var latCentre = (box.getNorthEast().lat() + box.getSouthWest().lat()) / 2;
	var point = new GLatLng(latCentre,lngCentre);
	this.gMap.setCenter(point, this.gMap.getBoundsZoomLevel(box));
}
RBMap.prototype.fitMapToAllRoutes = function()
{
	var box = new GLatLngBounds();
	for (var j=0; j<this.routes.length; j++) {
		if (this.routes[j]) {
			var points = this.routes[j].getPoints();
			for(var i=0;i<points.length;i++){
				box.extend(points[i]);
			}
		}
	}
	var lngCentre = (box.getNorthEast().lng() + box.getSouthWest().lng()) / 2;
	var latCentre = (box.getNorthEast().lat() + box.getSouthWest().lat()) / 2;
	var point = new GLatLng(latCentre,lngCentre);
	this.gMap.setCenter(point, this.gMap.getBoundsZoomLevel(box));
}
RBMap.prototype.getUrlToRoutes = function()
{
	var ref = '';
	for (var j=0; j<this.routes.length; j++) {
		if (this.routes[j]) {
			if (ref) ref += ',';
			ref += this.routes[j].ref;
		}
	}
	if (ref == '') return false;
	return window.location.protocol+'//' + window.location.host + window.location.pathname + '?r='+ref;
}
//			}}} RBMap Events
//		}}} RBMap object
//		{{{ RBRoute
function RBRoute(map, colourID, ref)
{
	if (!map) return;

	// loop colour ids
	while (colourID >= COLOURS.length) colourID -= COLOURS.length

	this.rbMap = map;	// the map the route is attached to
	this.points = [];	// all route points
	this.pointMarkers = [];	// ID of marker for each route point
	this.line = false;	// index of lines on RBMap
	this.markers = [];	// indexes of markers on RBMap
	this.unitMarkers = [];
	this.colour = COLOURS[colourID];
	this.ref = ref;
	this.pins = new RBPins(map);

	this.properties = {
		title: '',
		postcode: '',
		terrain: '',
		gradient: '',
		notes: '',
		ref: false,
		shared: false,
		walking: false,
		running: false,
		cycling: false
	};

	this.segmented = false;	// true = draw 1 joining each 2 points, false = draw 1 long line for all points
}
RBRoute.prototype.toString = function()
{
	return '[object RBRoute]';
}
RBRoute.prototype.loopBack = function()
{
	var l = this.points.length-1;
	for (var i=l; i>=0; i--) {
		this.pushPoint(this.points[i]);
	}
	this.redraw();
	this.rbMap.controls.updateDistances();
}
/**
 * Redraws the route on the map
 */
// FIXME: add ability to only redraw a segment of the route to increase speed in IE
//        XXX: possiblty add a flag onto markers stating if it needs updating
RBRoute.prototype.redraw = function()
{
	/*if (this.points.length == 0) {
				return;
	}*/
		var editing = (this.rbMap.getCurrentTool() == RB_TOOL_LINE);
	// draw markers
	for (var i=0; i<this.points.length; i++) {
		var p = this.points[i];
		var icon = G_DEFAULT_ICON;
		

		// Which pin to use?
		if (i == 0) icon = RB_BIGPIN_START;
		else if (i == this.points.length-1) icon = RB_BIGPIN_END;
		else if (editing) icon = RB_SMALLPIN_RED;
		else icon = false;


		if (icon) {
			var index = this.rbMap.updateMarker(this.pointMarkers[i], p, icon, editing);
			this.pointMarkers[i] = index;
			var m = this.rbMap.getMarker(index);
			// Add events to the pin for non-edito mode allownig the user to vote and comment on the route
			/*if (!m._voteEvent && !editing) {
				GEvent.bind(m, 'click', m, function() {
					var n = document.createElement('div');
					n.appendChild(document.createTextNode('Comment stuff goes here?\nWho knows...'));
					this.openInfoWindow(n);
				});
				m._voteEvent = true;
			} else*/ if (!m._dragEventAdded && editing) {
				// if we have SVG then we can do realtime dragging (very slick :)
				if (_mVectorSupport) {
					GEvent.addListener(m,'drag', this.method('movePointToMarker', i, m));
				}
				else {
					GEvent.addListener(m,'dragend', this.method('movePointToMarker', i, m));
				}
				GEvent.addListener(m,'dragend', this.method('checkEndMarkers'));
				m._dragEventAdded = true;
				GEvent.addListener(m, "dragstart", this.rbMap.windowCollection.method('deactivateWindow'));
			}
		}
		else {
						if (this.pointMarkers[i]) {
				this.rbMap.removeMarker(this.pointMarkers[i]);
				delete this.pointMarkers[i];
			}
		}
	}

	// remove any left over markers
	for (var i=this.points.length; i<this.pointMarkers.length; i++) {
		if (this.pointMarkers[i]) {
			this.rbMap.removeMarker(this.pointMarkers[i]);
			delete this.pointMarkers[i];
		}
	}


	// draw line
	// TODO make line segmented and only update parts needed
	if (this.points.length > 0) {
		this.rbMap.removeLine(this.line);
		this.line = this.rbMap.addLine(this.points, this.colour);
	}

	if (this.rbMap.mileMarkersEnabled()) this.redrawUnitMarkers(RB_MILES);
	else this.clearUnitMarkers(RB_MILES);
	if (this.rbMap.kilometreMarkersEnabled()) this.redrawUnitMarkers(RB_KILOMETERS);
	else this.clearUnitMarkers(RB_KILOMETERS);

	this.pins.redraw();
	return true;
}
RBRoute.prototype.clearUnitMarkers = function(metres)
{
	if (!this.unitMarkers || this.unitMarkers[metres] === undefined) return;
		for (var i=0; i<this.unitMarkers[metres].length; i++) {
		this.rbMap.removeMarker(this.unitMarkers[metres][i]);
	}
	delete this.unitMarkers[metres];
}
/**
 * Returns an array of points every 'metres' along the route
 */
RBRoute.prototype.getUnitPoints = function(metres)
{
	var distance = 0;
	var lastUnit = 0;
	var markerCount = 0;
	var unitPoints = [];
	var leaveLoop = false;
	for (var i=1; i<this.points.length && !leaveLoop; i++) {
		var p1 = this.points[i-1];
		var p2 = this.points[i];
		var dp1 = this.rbMap.gMap.fromLatLngToDivPixel(p1);
		var dp2 = this.rbMap.gMap.fromLatLngToDivPixel(p2);
		var distanceInMetres = p1.distanceFrom(p2);
		distance += distanceInMetres;

		var distanceInPixels = Math.sqrt(Math.pow((dp2.x - dp1.x), 2) + Math.pow((dp2.y - dp1.y), 2));
		var pixelsPerUnit = distanceInPixels / (distanceInMetres / metres);

		// Calculate where point should be along the line
		var adj = dp2.x - dp1.x;
		var opp = dp2.y - dp1.y;
		var rad = Math.atan2(adj, opp);

		if (pixelsPerUnit-lastUnit < distanceInPixels) {
			var speed = pixelsPerUnit-lastUnit;	// the distance to move
			while (speed > 0 && speed < distanceInPixels && !leaveLoop) {
				var unitX = dp1.x + (Math.sin(rad) * speed);
				var unitY = dp1.y + (Math.cos(rad) * speed);

				var unit = new GPoint(unitX, unitY);
				dp1 = unit;
				distanceInPixels = Math.sqrt(Math.pow((dp2.x - dp1.x), 2) + Math.pow((dp2.y - dp1.y), 2));
				unit = this.rbMap.gMap.fromDivPixelToLatLng(unit);

				unitPoints.push(unit);
				lastUnit = distanceInPixels;
				speed = pixelsPerUnit;
				if (unitPoints.length >= MAX_MARKERS) leaveLoop = true;
			}
		}
		else lastUnit += distanceInPixels;
	}
	return unitPoints;
}
/**
 * Draw a marker every given number of meters, e.g. 1000 for a marker every KM
 */
RBRoute.prototype.redrawUnitMarkers = function(metres)
{
	if (this.unitMarkers[metres] === undefined) this.unitMarkers[metres] = [];
	var unitPonts = this.getUnitPoints(metres);

	// spit out the markers
	for (var i=0; i<unitPonts.length; i++) {
		var label = (i+1);
		var icon = createLabledPin(label, metres);	// Create a pin with a number on it
		this.unitMarkers[metres][i] = this.rbMap.updateMarker(this.unitMarkers[metres][i], unitPonts[i], icon, false, true);
	}

	// remove any left overs
	for (var i=unitPonts.length; i<this.unitMarkers[metres].length; i++) {
		this.rbMap.removeMarker(this.unitMarkers[metres][i]);
	}


	return;

}
RBRoute.prototype.getLength = function()
{
	var distance = 0;
	
	for (var i=1; i<this.points.length; i++) {
		var p1 = this.points[i-1];
		var p2 = this.points[i];
		var l1 = new GLatLng(p1.lat(), p1.lng());
		var l2 = new GLatLng(p2.lat(), p2.lng());
		var d = l1.distanceFrom(l2);
		distance += d;
	}
	return distance;
}
/**
 * Returns a array of all the GPoints making the route
 */
RBRoute.prototype.getPoints = function()
{
	return this.points;
}
RBRoute.prototype.getMarker = function(i)
{
	return this.rbMap.getMarker(this.pointMarkers[i]);
}
/**
 * Moves a route point to the same position as a given marker
 */
RBRoute.prototype.movePointToMarker = function(index, marker)
{
		this.movePoint(index, marker.getPoint());
	this.rbMap.myRuns.mapUnEdited = false;

}
/**
 * Checks if the end markers are close together, if they are they share a point
 */
RBRoute.prototype.checkEndMarkers = function()
{
	var p1 = this.rbMap.gMap.fromLatLngToDivPixel(this.points[0]);
	var p2 = this.rbMap.gMap.fromLatLngToDivPixel(this.points[this.points.length-1]);


	var xd = p1.x-p2.x;
	var yd = p1.y-p2.y;

	var gap = Math.round(Math.sqrt(xd*xd + yd*yd));

	// if end and start at only 10 pixels apart then snap them together
	if (gap <= 10) {
		this.movePoint(this.points.length-1, this.points[0]);
	}
}
/**
 * Moves a point in the route to a given GPoint
 */
RBRoute.prototype.movePoint = function(index, point)
{
		point.index = this.points[index].index;
	this.points[index] = point;
	this.rbMap.controls.updateDistances();
	this.redraw();
}
/**
 * Add point onto end of list
 */
RBRoute.prototype.pushPoint = function(point)
{
		var p = this.points.push(point);
	this.pointMarkers.push(false);
	return p;
}
/**
 * Add point onto start of list
 */
RBRoute.prototype.unshiftPoint = function(point)
{
	return this.points.unshift(point);
}
/**
 * Remove point from start of list
 */
RBRoute.prototype.shiftPoint = function(point)
{
	return this.points.shift(point);
}
/**
 * Remove point from end of list
 */
RBRoute.prototype.popPoint = function(point)
{
	return this.points.pop(point);
}
RBRoute.prototype.removeAllPoints = function()
{
		for(var i=0; i<this.pointMarkers.length; i++) {
		if (this.pointMarkers[i] !== false) {
        this.rbMap.removeMarker(this.pointMarkers[i]);
}
	}
	this.rbMap.removeLine(this.line);
	this.points = [];
	this.pointMarkers = [];
	this.clearUnitMarkers(RB_MILES);
	this.clearUnitMarkers(RB_KILOMETERS);
}
//		}}}	RBRoute
//		{{{ RBPins
/**
 * Based on RBRoute, but doesn't draw lines and has custom markers on every point
 */
function RBPins(map)
{
	this.rbMap = map;	// the map the route is attached to
	this.points = [];	// all route points
	this.pointMarkers = [];	// ID of marker for each route point
	this.pointNotes = [];	// the notes attached to pins
	this.pointIcons = [];	// The icon to use for each point, index of this mathces index of points
}
RBPins.prototype = new RBRoute();
RBPins.prototype.checkEndMarkers = function(){} // don't want to snap pins
RBPins.prototype.redraw = function()
{
		for (var i=0; i<this.points.length; i++) {
		var p = this.points[i];

		// TODO: set pin to selection
		var icon = Pins[this.pointIcons[i]];
		//var icon = G_DEFAULT_ICON;
		var editing = (this.rbMap.getCurrentTool() == RB_TOOL_PIN || ADMIN_MODE);
		if (icon) {
			var index = this.rbMap.updateMarker(this.pointMarkers[i], p, icon, editing);
			this.pointMarkers[i] = index;
			var m = this.rbMap.getMarker(index);
			if (!m._dragEventAdded && editing) {
				GEvent.addListener(m,'dragend', this.method('movePointToMarker', i, m));
				GEvent.addListener(m, "dragstart", this.rbMap.windowCollection.method('deactivateWindow'));
				GEvent.addListener(m, "dragstart", this.method('closeNote', m));
				GEvent.addListener(m, "click", this.method('editNote', m, i));
				GEvent.addListener(m, "infowindowclose", this.method('saveNote', m, i));
				m._dragEventAdded = true;
			}
		}
		else {
						this.rbMap.removeMarker(this.pointMarkers[i]);
			delete this.pointMarkers[i];
			delete this.pointIcons[i];
		}
	}


	// remove any left over markers
	for (var i=this.points.length; i<this.pointMarkers.length; i++) {
		this.rbMap.removeMarker(this.pointMarkers[i]);
		delete this.pointMarkers[i];
		delete this.pointIcons[i];
	}
}
RBPins.prototype.pushPoint = function(point, note, pin)
{
		var p = this.points.push(point);
	this.pointIcons.push(pin);
	this.pointMarkers.push(false);
	this.pointNotes.push(note);
	return p;
}
RBPins.prototype.editNote = function(marker, index)
{
	if (arguments.length == 3) {
		marker = arguments[1];
		index = arguments[2];
	}
		if (this.rbMap.getCurrentTool() !== RB_TOOL_PIN) return;	// Only edit notes in pin mode
	if (!marker.noteForm) {
		var note = [
			{ name: 'note', label: 'Enter your note', type: 'textarea' }
		];
		if (ADMIN_MODE) {
			var point = marker.getPoint();
			var llz = point.lat()+','+point.lng()+',14';
			var code = (point.index)?point.index:'You must save to obtain an ID';
			note[1] = { label: 'ID: '+code, type: 'paragraph' };
		}
		var form = createForm(note, 300);
		form.getElementsByTagName('textarea')[0].style.height = '200px';
		marker.noteForm = form;
		if (index !== undefined) marker.noteForm['note'].value = (this.pointNotes[index])?this.pointNotes[index]:'';
	}
	var infoWin = marker.openInfoWindow(marker.noteForm);
}
RBPins.prototype.closeNote = function(marker, index)
{
		this.rbMap.gMap.closeInfoWindow();
}
RBPins.prototype.saveNote = function(marker)
{
		marker.note = marker.noteForm['note'].value;
}
RBPins.prototype.removeAllPoints = function()
{
	RBRoute.prototype.removeAllPoints.apply(this);
	this.pointNotes = [];
	this.pointIcons = [];
}
//		}}} RBPins
//		{{{ RBServerPins
function RBServerPins(map)
{
	this.rbMap = map;	// the map the route is attached to
	this.points = [];	// all route points
	this.pointNotes = [];	// ID of marker for each route point
	this.pointMarkers = [];	// ID of marker for each route point
	this.pointIcons = [];	// ---
}
RBServerPins.prototype = new RBPins();
RBServerPins.prototype.removeAllPoints = function()
{
		RBPins.prototype.removeAllPoints.call(this);
	this.notes = [];
}
/**
 * Add point onto end of list
 */
RBServerPins.prototype.pushPoint = function(point, note, icon, index, skip)
{
	
	if (skip) {
		for (var i=0; i<this.points.length; i++) {
			if (this.points[i] && this.points[i].index == index) {
								return;
			}
		}
	}

	var p = this.points.push(point);
	this.pointNotes.push(note);
	this.pointMarkers.push(false);
	this.pointIcons.push(icon);
	this.points[p-1].index = index;
	return p;
}
RBServerPins.prototype.redraw = function()
{
		for (var i=0; i<this.points.length; i++) {
		var p = this.points[i];

		var icon = Pins[this.pointIcons[i]];

		if (icon) {
			var oldMarker = this.rbMap.getMarker(this.pointMarkers[i]);
			var isDraggable = (oldMarker)?oldMarker.draggingEnabled():false;
			var index = this.rbMap.updateMarker(this.pointMarkers[i], p, icon, isDraggable);
			this.pointMarkers[i] = index;
			var m = this.rbMap.getMarker(index);
			if (!m._eventAdded) {
				if (ADMIN_MODE) {
					GEvent.addListener(m, "dragstart", this.method('closeNote', m));
					GEvent.addListener(m, "click", this.method('editNote', m, i));
					GEvent.addListener(m, "infowindowclose", this.method('saveNote', m, i));
					m.note = this.pointNotes[i];
				}
				else GEvent.addListener(m, "click", this.method('showNote', m, this.pointNotes[i]));
				GEvent.addListener(m,'dragend', this.method('movePointToMarker', i, m));
				m._eventAdded = true;
			}
		}
		else {
						this.rbMap.removeMarker(this.pointMarkers[i]);
			delete this.pointMarkers[i];
		}
	}
}
RBServerPins.prototype.showNote = function(marker, note)
{
	// skip event object
	if (arguments.length == 3) {
		marker = arguments[1];
		note = arguments[2];
	}
		if (!note) return;
	//var p = document.createElement('pre');
//	p.style.fontFamily = 'arial, sans-serif';
//	p.appendChild(document.createTextNode(note));
//	var infoWin = marker.openInfoWindowHtml(p);
	// We use a map info window rather than a marker info window so it's not close automatically
	var point = marker.getPoint();
	var offset = new GSize(
		marker.getIcon().infoWindowAnchor.x
		- marker.getIcon().iconAnchor.x,
		marker.getIcon().infoWindowAnchor.y
		- marker.getIcon().iconAnchor.y);


	var infoWin = this.rbMap.gMap.openInfoWindowHtml(point, note, {pixelOffset: offset});
}
//		}}} RBServerPins
//		{{{ RBRunner
/**
 * Animated character that runs the route
 */
function RBRunner(route)
{
	this.route = route;
	this.rbMap = route.rbMap;
	this.marker = this.rbMap.addMarker(false, route.getPoints()[0]);
}
RBRunner.prototype.update = function()
{
	this.points = this.route.getUnitPoints(200);	// get an array of points every 200 meters
}
//		}}} RBRunner
//		{{{ Windows
//			{{{ Window collection
function RBWindowCollection(parent) {
	this.rbMap = parent;
	this.parent = parent;

	this.windows = [];	// array of all open windows
	this.activeWindow = false;
	GEvent.addDomListener(document.body, 'mousemove', this.method('dragWindow'));
	GEvent.addDomListener(document.body, 'mousedown', this.method('deactivateWindow'));
}
RBWindowCollection.prototype.toString = function()
{
	return '[object RBWindowCollection]';
}
RBWindowCollection.prototype.createWindow = function(x, y, width, height, title, solid)
{
	var newWin = new RBWindow(this, x, y, width, height, title, solid);
	newWin.index = this.windows.length;
	GEvent.addDomListener(newWin.getFrame(), 'mousedown', this.method('activateWindow', newWin.index));
	GEvent.addDomListener(newWin.getFrame(), 'mousedown', burstBubble);

	if (!solid) setOpacity(newWin.getFrame(), 0.1);

	this.windows.push(newWin);
	this.activateWindow(newWin.index);
	return newWin;
}
RBWindowCollection.prototype.createDialogWindow = function(width, height, title, message, buttons)
{
	var newWin = this.createWindow(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE, width, height, title, false);
	var container = newWin.getContainer();
	var p = container.appendChild(document.createElement('p'));
	with (p.style) {
		textAlign = 'left';
		margin = 0;
		padding = '1em 0.25em';
		marginLeft = '64px';
	}
	var msgs = message;
	if (i > 0) p.appendChild(document.createElement('br'));
	var b = p.appendChild(document.createElement('strong'));
	b.appendChild(document.createTextNode('Welcome to the Rite Aid Cleveland Marathon Training Route Planner.'));
	p.appendChild(document.createElement('br'));
	p.appendChild(document.createElement('br'));
	var div = document.createElement('div');
	div.innerHTML = msgs;
	p.appendChild(div);

	var ass = div.getElementsByTagName('a');
	for (var j=0; j<ass.length; j++) {
		GEvent.addDomListener(ass[j], 'click', newWin.method('close'));
	}

	// Add the icon graphic to the window
	var ico = document.createElement('div');
	var icon = 'question.png';
	with (ico.style) {
		width = '48px';
		height = '48px';
		position = 'absolute';
		left = '16px';
		top = '24px';
	}
	alphaBackground(ico, RB_MAP_PATH+'images/'+icon);
	container.appendChild(ico);

	// Create the buttons container
	p = container.appendChild(document.createElement('p'));
	p.className = 'RBWindow_buttons';
	with (p.style) {
		textAlign = 'center';
		margin = 0;
		//marginLeft = '64px';
	}

	// Add the buttons
	for (var i=0; i<buttons.length; i++) {
		var button = p.appendChild(document.createElement('button'));
		button.appendChild(document.createTextNode(buttons[i].name));
		button.className = 'RBWindow_button';
		GEvent.addDomListener(button, 'click', newWin.method('close'));
		GEvent.addDomListener(button, 'click', buttons[i].action);
		//button.focus();

		// BODGE
		if (i == 2) button.style.width = '15em';
	}

	return newWin;
}
RBWindowCollection.prototype.createMessageWindow = function(width, height, title, message, icon, action)
{
	var newWin = this.createWindow(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE, width, height, title, false);
	var container = newWin.getContainer();
	var p = container.appendChild(document.createElement('p'));
	with (p.style) {
		textAlign = 'left';
		margin = 0;
		padding = '1em 1em 1em 0.25em';
		if (icon) marginLeft = '64px';
	}
	var msgs = message.split('\n');
	for (var i=0; i<msgs.length; i++) {
		if (i > 0) p.appendChild(document.createElement('br'));
		p.innerHTML += msgs[i];
	}

	// Add the icon graphic to the window
	if (icon) {
		var ico = document.createElement('div');
		with (ico.style) {
			width = '48px';
			height = '48px';
			position = 'absolute';
			left = '16px';
			top = '24px';
		}
		alphaBackground(ico, RB_MAP_PATH+'images/'+icon);
		container.appendChild(ico);
	}

	// Create the buttons container
	p = container.appendChild(document.createElement('p'));
	p.className = 'RBWindow_buttons';
	with (p.style) {
		textAlign = 'center';
		margin = 0;
		//if (icon) marginLeft = '64px';
	}

	// Add the button
	var button = p.appendChild(document.createElement('button'));
	button.appendChild(document.createTextNode('Okay'));
	button.className = 'RBWindow_button';
	GEvent.addDomListener(button, 'click', function() {
		newWin.close()
		if (action) action();
	});
	button.focus();

	return newWin;
}
RBWindowCollection.prototype.closeWindow = function(index)
{
		this.bringToFront(index);
	var win = this.getWindow(index);
	var winFrame = win.getFrame();
	winFrame.parentNode.removeChild(winFrame);
	delete this.windows[index];
}
RBWindowCollection.prototype.activateWindow = function(index)
{
	if (typeof index == 'object') index = arguments[1];	// skip event object

	if (this.activeWindow !== false) {
		if (index == this.activeWindow) return;
		this.deactivateWindow();
	}

	
	var win = this.getWindow(index);

	// show hidden selects to work around IE lameness
	if (window.isIE) {
		var selects = win.getFrame().getElementsByTagName('select');
		for (var i=0; i<selects.length; i++) {
			selects[i].style.visibility = '';
		}
	}

	this.bringToFront(index);

	this.activeWindow = index;
	win.focus();
	win.fadeOpacity(RB_WINDOW_OPACITY_MAX);
}
RBWindowCollection.prototype.deactivateWindow = function(index)
{
	if (typeof index == 'object') {
		var e = index;
		index = arguments[1];	// skip event object
	}
	
	if (typeof index == 'undefined') index = this.activeWindow;
	
	var win = this.getWindow(index);

	// hide selects to work around IE lameness
	if (window.isIE && win) {
		var selects = win.getFrame().getElementsByTagName('select');
		for (var i=0; i<selects.length; i++) {
			selects[i].style.visibility = 'hidden';
		}
	}

	if (win) {
		win.blur();
		win.fadeOpacity(RB_WINDOW_OPACITY_MIN);
	}
	this.activeWindow = false;
}
RBWindowCollection.prototype.bringToFront = function(index)
{
	// This puts a 10 unit gap between the zIndex of windows to allow them to easily be layered ontop of blocker divs
	var win = this.getWindow(index).getFrame();
	var tZ = parseInt(win.style.zIndex);
	win.style.zIndex = (tZ)?tZ:9999999;
	var c = 0;
	for (var i=0; i<this.windows.length; i++) {
		var w = this.windows[i];
		if (w !== undefined && w.getFrame() != win) {
			w = w.getFrame();
//			w.style.zIndex = parseInt(parseInt(w.style.zIndex)/10);
			if (win.style.zIndex && parseInt(w.style.zIndex) > parseInt(win.style.zIndex)) {
				w.style.zIndex = parseInt(w.style.zIndex)-10;
			}
			c++;
			//w.style.zIndex = parseInt(w.style.zIndex)*10;
		}
	}
	win.style.zIndex = (100 + c)*10;
}
RBWindowCollection.prototype.getWindow = function(index)
{
	return this.windows[index];
}
RBWindowCollection.prototype.getActiveWindow = function()
{
	if (this.activeWindow === false) return false;
	return this.getWindow(this.activeWindow);
}
RBWindowCollection.prototype.dragWindow = function(e)
{
	var win = this.getActiveWindow();
	if(win) win.dragWindow(e);
}
//			}}} Window collection
//			{{{ Window
function RBWindow(collection, x, y, w, h, title, solid)
{
	if (!title) title = 'Untitled window';
	this.solid = (solid)?true:false;
	this.hidden = false;
	this.collection = collection;
	this.snap = 12;
	this.snapPad = 6;
	this.windowFrame = document.createElement('div');
	this.windowFrame.className = 'RBWindow gmnoprint';
	this.minimised = false;

	this.targetOpacity = RB_WINDOW_OPACITY_MAX;	// opacity to fade to
	this.fadeStep = 0.05;		// units to increase opacity by to reach target

	var con = this.collection.parent.getContainer();
	var pos = getStyle(con, 'position');

	this.defaultWidth = w;
	this.defaultHeight = h;

	with (this.windowFrame.style) {
		 position = 'absolute';
		 left = x+'px';
		 top = y+'px';
		 width = w+'px';
		 height = h+'px';
		 background = '#fff';
		 border = '1px solid #000';
	}

	// Window title bar
	this.titleBar = document.createElement('div');
	this.titleBar.className = 'RBWindow_titleBar';
	with (this.titleBar.style) {
		position = 'absolute';
		width = '100%';
		height = '16px';
		fontSize = '14px';
		lineHeight = '16px';
		overflow = 'hidden';
		textAlign = 'center';
		cursor = 'move';
		MozUserSelect = 'none';
		textShadow = '#000 2px 2px 3px';
	}
	this.titleBar.onselectstart = function() { return false; }
	this.titleBar.appendChild(document.createTextNode(title));
	this.windowFrame.title = 'Double click the title to minimise this window or click and drag the title to move';
	this.windowFrame.appendChild(this.titleBar);
	GEvent.addDomListener(this.titleBar, 'mousedown', this.method('startMove'));
	GEvent.addDomListener(this.titleBar, 'dblclick', this.method('toggleMinimise'));
	GEvent.addDomListener(document.body, 'mouseup', this.method('stopMove'));
	
	// Editable part of window
	this.container = document.createElement('div');
	this.container.className = 'window_container';
	with (this.container.style) {
		position = 'absolute';
		left = 0;
		top = '16px';
		width = '100%';
		height = (h-16)+'px';
		fontSize = '0.9em';
		color = '#000';
		fontFamily = 'arial, sans-serif';
		overflow = 'hidden';
	}
	this.windowFrame.appendChild(this.container);

	this.collection.parent.getContainer().appendChild(this.windowFrame);
	this.moveTo(x, y);
}
RBWindow.prototype.resize = function(w, h)
{
	if (w !== false) {
		this.getFrame().style.width = w+'px';
		this.defaultWidth = w;
	}
	if (h !== false) {
		this.getFrame().style.height = h+'px';
		this.getContainer().style.height = (h-16)+'px';
		this.defaultHeight = h;
	}
}
RBWindow.prototype.close = function()
{
	this.collection.closeWindow(this.index);
}
RBWindow.prototype.toggleMinimise = function()
{
	if (this.minimsed) this.restore();
	else this.minimise();
}
RBWindow.prototype.minimise = function()
{
		if (this.minimsed) return;
	this.windowFrame.style.height = '16px';
	this.windowFrame.style.width = '150px';
	this.container.style.display = 'none';
	this.minimsed = true;
	this.stopMove();
}
RBWindow.prototype.restore = function()
{
		if (!this.minimsed) return;
	this.windowFrame.style.height = this.defaultHeight+'px';
	this.windowFrame.style.width = this.defaultWidth+'px';
	this.container.style.display = '';
	this.minimsed = false;
	this.stopMove();
}
RBWindow.prototype.hide = function()
{
	this.getFrame().style.display = 'none';
	this.hidden = true;
}
RBWindow.prototype.show = function()
{
	this.restore();
	if (!this.hidden) return;
	this.getFrame().style.display = '';
	setOpacity(this.getFrame(), 0.1);
	this.targetOpacity = RB_WINDOW_OPACITY_MIN;
	this.fadeEffect();
	this.hidden = false;
}
RBWindow.prototype.fadeOpacity = function(opacity)
{
	if (this.solid) return;
		if (typeof opacity != 'undefined') this.targetOpacity = opacity;
	this.fadeEffect();
}
RBWindow.prototype.toString = function()
{
	return '[object RBWindow]';
}
RBWindow.prototype.fadeEffect = function()
{
	if (this.solid) return;
	if (typeof this._timer != 'undefined') {
		clearTimeout(this._timer);
		delete this._timer;
	}
	var op = this.getFrame().opacity;
	if (op < this.targetOpacity) {
		setOpacity(this.getFrame(), op+this.fadeStep);
		op = this.getFrame().opacity;
		if (op > this.targetOpacity) setOpacity(this.getFrame(), this.targetOpacity);
		else this._timer = setTimeout(this.method('fadeEffect'), 10);
	}
	else if (op > this.targetOpacity) {
		setOpacity(this.getFrame(), op-this.fadeStep);
		op = this.getFrame().opacity;
		if (op < this.targetOpacity) setOpacity(this.getFrame(), this.targetOpacity);
		else this._timer = setTimeout(this.method('fadeEffect'), 10);
	}	
}
RBWindow.prototype.getFrame = function()
{
	return this.windowFrame;
}
RBWindow.prototype.getContainer = function()
{
	return this.container;
}
RBWindow.prototype.moveTo = function(x, y, snap)
{
	// check if snapped to edge of map
	var con = this.collection.parent.getContainer();
	var pos = getStyle(con, 'position');
	var frameX = this.snapPad;
	var frameY = this.snapPad;
	var frameW = 0;
	var frameH = 0;
	if (pos == 'relative' || pos == 'absolute') {
		frameW = con.offsetWidth-(this.snapPad*2);
		frameH = con.offsetHeight-(this.snapPad*2);
	}
	else {
		frameW = document.body.offsetWidth-(this.snapPad*2);
		frameH = document.body.offsetHeight-(this.snapPad*2);
	}
	if (x == RB_WINDOW_CENTRE) {
		x = (con.clientWidth/2) - (this.getFrame().offsetWidth/2);
		if (pos != 'relative' && pos != 'absolute') x += con.offsetLeft;
	}
	if (y == RB_WINDOW_CENTRE) {
		y = (con.clientHeight/2) - (this.getFrame().offsetHeight/2);
		if (pos != 'relative' && pos != 'absolute') y += con.offsetTop;
	}

	if (snap) {
		var snappedX = false;
		var snappedY = false;
	
		// check if snapped to window
		for (var i=-1; i<this.collection.windows.length; i++) {
			var w;
			// -1 is a fake window used to snap to the document edge
			if (i > -1) w = this.collection.windows[i];
			else w = true;

			if (w && w != this && !w.hidden) {
				var wx, wy, ww, wh;
				if (i > -1) {
					wx = w.getFrame().offsetLeft;
					wy = w.getFrame().offsetTop;
					ww = w.getFrame().offsetWidth;
					wh = w.getFrame().offsetHeight;
				}
				else {
					// check if snapped to edge of map
					wx = frameX;
					wy = frameY;
					ww = frameW;
					wh = frameH;
				}
				var tw = this.getFrame().offsetWidth;
				var th = this.getFrame().offsetHeight;
				if (!snappedX) {
					if (y+th >= wy-this.snap-this.snapPad && y <= wy+wh+this.snap+this.snapPad) {
						// dragged left edge hits idle left edge
						if (wx >= x-this.snap && wx <= x+this.snap) {
							x = wx;
							snappedX = true;	
						}
						// dragged left edge hits idle right edge
						else if (wx+ww+this.snapPad >= x-this.snap && wx+ww+this.snapPad <= x+this.snap) {
							x = wx+ww+this.snapPad;
							snappedX = true;
						}
						// dragged right edge his idle right edge
						else if (x+tw >= wx+ww-this.snap && x+tw <= wx+ww+this.snap) {
							x = (wx+ww)-tw;
							snappedX = true;
						}
						// dragged right edge his idle left edge
						else if (x+tw >= wx-this.snap-this.snapPad && x+tw <= wx+this.snap-this.snapPad) {
							x = wx-tw-this.snapPad;
						}
					}
				}
				if (!snappedY) {
					if (x+tw >= wx-this.snap-this.snapPad && x <= wx+ww+this.snap+this.snapPad) {
						// dragged top edge hits idle top edge
						if (wy >= y-this.snap && wy <= y+this.snap) {
							y = wy;
							snappedY = true;	
						}
						// dragged top edge hits idle bottom edge
						else if (wy+wh+this.snapPad >= y-this.snap && wy+wh+this.snapPad <= y+this.snap) {
							y = wy+wh+this.snapPad;
							snappedY = true;
						}
						// dragged bottom edge his idle bottom edge
						else if (y+th >= wy+wh-this.snap && y+th <= wy+wh+this.snap) {
							y = (wy+wh)-th;
							snappedY = true;
						}
						// dragged bottom edge his idle top edge
						else if (y+th >= wy-this.snap-this.snapPad && y+th <= wy+this.snap-this.snapPad) {
							y = wy-th-this.snapPad;
							snappedY = true;
						}
					}
				}
			}
		}

	}

	
	if (y > frameH-16) y = frameH -16;
	else if (y < frameY) y = frameY;
	if (x < -(this.windowFrame.offsetWidth/1.5)+frameX) x = -(this.windowFrame.offsetWidth/1.5)+frameX;
	else if (x > frameW-(this.windowFrame.offsetWidth/3)+frameX) x = frameW-(this.windowFrame.offsetWidth/3)+frameX;

	this.windowFrame.style.left = x +'px';
	this.windowFrame.style.top = y +'px';
}
RBWindow.prototype.setTitle = function(str)
{
	this.titleBar.removeChild(this.titleBar.firstChild);
	this.titleBar.appendChild(document.createTextNode(str));
}
//				{{{ Window events
RBWindow.prototype.dragWindow = function(e)
{
	switch (this.state) {
		case RB_WINDOW_IDLE:
			return;
			break;
		case RB_WINDOW_MOVE:
			this.moveTo(e.clientX-this._offsetX, e.clientY-this._offsetY, true);
			break;
		case RB_WINDOW_RESIZE:

			break;
	}
}
RBWindow.prototype.startMove = function(e)
{
		this.state = RB_WINDOW_MOVE;
	this._offsetX = e.clientX - this.windowFrame.offsetLeft;
	this._offsetY = e.clientY - this.windowFrame.offsetTop;
}
RBWindow.prototype.stopMove = function(e)
{
	if (this.state != RB_WINDOW_MOVE) return;
		this.state = RB_WINDOW_IDLE;
}
RBWindow.prototype.focus = function()
{

}
RBWindow.prototype.blur = function()
{
}
RBWindow.prototype.activate = function()
{
	this.collection.activateWindow(this.index);
}
//				}}} Window events
//			}}}	Window
//			{{{ RBFancyWindow
function RBFancyWindow() {
	this.container = null;
}
RBFancyWindow.prototype.getWindow = function()
{
	return this.window;
}
RBFancyWindow.prototype.getContainer = function()
{
	return this.container;
}
RBFancyWindow.prototype.show = function()
{
	this.window.show();
}
RBFancyWindow.prototype.hide = function()
{
	this.window.hide();
}
RBFancyWindow.prototype.activate = function()
{
	this.window.activate();
}
//			}}} RBFancyWindow
//			{{{ RBOverview
function RBOverview(parent)
{
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(6, 6, 128, 128, 'Mini map', true);
	setTimeout(this.method('embedOverview'), 1);
}
RBOverview.prototype = new RBFancyWindow();
RBOverview.prototype.embedOverview = function()
{
	var overview = this.parent.getOverview();
	overview = overview.parentNode.removeChild(overview);
	overviewContainer = document.createElement('div');
	with (overviewContainer.style) {
		position = "relative";
		width = "128px";
		height = "112px";
		overflow = "hidden";
		margin = 0
	}

	this.window.getContainer().appendChild(overviewContainer);
	overviewContainer.appendChild(overview);

	// remove minimiser
	var imgs = overview.getElementsByTagName('img');
	for (var i=0; i<imgs.length; i++) {
		if (imgs[i].src.indexOf('overcontract.gif') > -1) {
			imgs[i].parentNode.removeChild(imgs[i]);
		}
	}

	// clean up styles
	// move copyright notice
	this.parent.getMapContainer().childNodes[1].style.right = "4px";	// Terms
//	this.parent.getMapContainer().childNodes[2].style.left = "7px";	// Google logo
	with (overview.style) {
		left = 0;
		top = 0;
		width = "100%";
		height = "100%";
	}
	with (overview.firstChild.style) {
		left = 0;
		top = 0;
		width = "100%";
		height = "100%";
		border = "none";
		background = "transparent";
	}
	with (overview.firstChild.firstChild.style) {
		left = 0;
		top = 0;
		width = "100%";
		height = "100%";
		border = "none";
	}

	// add event to focus window
	GEvent.addDomListener(overview, 'click', this.parent.windowCollection.method('activateWindow', this.window.index));
	return overviewContainer;
}
//			}}} RBOverview
//			{{{ RBSignUp
function RBSignUp(parent, realform)
{
	if (!parent) return;
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE, 400, 370, 'Register');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}
	container.appendChild(document.createElement('p').appendChild(document.createTextNode('Enter your details below to create an account.')));

	var form;
	if (realform) form = realform;
	else {
		form = [
			{ name: 'username', label: 'Username', type: 'text', required: true },
			{ name: 'fullname', label: 'Real Name', type: 'text', required: true },
			{ name: 'email', label: 'Email Address', type: 'text', required: true },
			{ name: 'password', label: 'Password', type: 'password', required: true },
			{ name: 'password2', label: 'Confirm Password', type: 'password', required: true },
			{ name: 'mail_london', label: 'I am happy to receive emails from London Marathon', type: 'checkbox', required: false },
			{ name: 'mail_third', label: 'I am happy to receive emails from third parties', type: 'checkbox', required: false },
			{ name: 'submit', label: 'Sign up', type: 'submit' },
		];
	}

	this.form = createForm(form, 380, this.method('signup'))



	var close = document.createElement('input');
	close.type = 'button';
	close.className = 'RBForm_button';
	close.value = 'Cancel';
	GEvent.addDomListener(close, 'click', this.method('hide'));

	this.form.lastChild.appendChild(close);

	container.appendChild(this.form);



	// blocker
	this.blocker = document.createElement('div');
	with (this.blocker.style) {
		position = 'absolute';
		left = 0;
		top = 0;
		width = '100%';
		height = '100%';
		zIndex = 5000;
		display = 'none';
	}
	alphaBackground(this.blocker, RB_MAP_PATH+'images/dull.png');
	this.parent.getContainer().appendChild(this.blocker);
	this.window.focus = this.method('showBlocker');
	this.window.blur = this.method('showBlocker');
}
RBSignUp.prototype = new RBFancyWindow();
RBSignUp.prototype.focus = function()
{
	this.activate();
	this.form.username.focus();
}
RBSignUp.prototype.signup = function(details)
{
	if (details !== false) {
		if (this.form.password.value != this.form.password2.value) {
			this.parent.windowCollection.createMessageWindow(
					375, 130,
					'Password mismatch',
					'The passwords you entered do not match.\nPlease retype both passwords to make sure they\'re correct.',
					'warning.png',
					this.method('focus')
					);
		}
		else this.parent.PostXML(RB_MAP_PATH+'scripts/create_account.php', 'u='+escape(this.form.username.value)+'&p='+escape(this.form.password.value)+'&n='+escape(this.form.fullname.value)+'&e='+escape(this.form.email.value)+'&ml='+((this.form.mail_london.checked)?'true':'false')+'&mt='+((this.form.mail_third.checked)?'true':'false'), this.parent.method('processXML'));
	}
	else {
		this.parent.windowCollection.createMessageWindow(
				350, 105,
				'Missing fields',
				'You have missed some required fields.\nPlease fill them in and try again.',
				'warning.png',
				this.method('focus')
			);
	}
}
RBSignUp.prototype.showBlocker = function()
{
	if (this.getWindow().getFrame().style.display == 'none') return;
	this.blocker.style.display = 'block';
	this.blocker.style.zIndex = parseInt(this.getWindow().getFrame().style.zIndex)-5;
}
RBSignUp.prototype.hideBlocker = function()
{
	this.blocker.style.display = 'none';
}
RBSignUp.prototype.hide = function()
{
	RBFancyWindow.prototype.hide.apply(this);
	this.hideBlocker();
	this.form.reset();
}
RBSignUp.prototype.show = function()
{
	RBFancyWindow.prototype.show.apply(this);
	this.activate();
	this.form.username.focus();

	// XXX tiny delay to stop IE slappign it on top of the login box
	setTimeout(this.method('showBlocker'), 1);
}
//			}}} RBSignUp
//			{{{ RBMyAccount
function RBMyAccount(parent)
{
	var form = [
		{ name: 'fullname', label: 'Real Name', type: 'text', required: true },
		{ name: 'email', label: 'Email address', type: 'text', required: true },
		{ name: 'password', label: 'Password', type: 'password', required: false },
		{ name: 'password2', label: 'Confirm Password', type: 'password', required: false },
		{ name: 'submit', label: 'Update', type: 'submit' }
	];
	RBSignUp.call(this, parent, form);
	this.window.resize(270, 330);

	this.window.setTitle('My Account');
	var c = this.window.getContainer().firstChild;
	c.removeChild(c.firstChild);
	c.insertBefore(document.createTextNode('Modify your account details below.\nLeave the password blank if you do not wish to update it.'), c.firstChild);
	var inputs = c.getElementsByTagName('input');
	/*
	this.userbox = document.createElement('span');
	this.userbox.appendChild(
		document.createTextNode('username')
	);

	inputs[0].parentNode.insertBefore(
		this.userbox,
		inputs[0].previousSibling
		);
		*/
}
RBMyAccount.prototype = new RBSignUp();
RBMyAccount.prototype.showBlocker = function(){} // we don't use a blocker this time
RBMyAccount.prototype.focus = function()
{
	this.activate();
}
RBMyAccount.prototype.signup = function(details)
{
	if (details !== false) {
		if (this.form.password.value != this.form.password2.value) {
			this.parent.windowCollection.createMessageWindow(
					375, 130,
					'Password mismatch',
					'The passwords you entered do not match.\nPlease retype both passwords to make sure they\'re correct.',
					'warning.png',
					this.method('focus')
					);
		}
		else {
			this.parent.PostXML(RB_MAP_PATH+'scripts/update_account.php', 'p='+escape(this.form.password.value)+'&n='+escape(this.form.fullname.value)+'&e='+escape(this.form.email.value));
			this.hide();
			this.parent.windowCollection.createMessageWindow(
					350, 125,
					'Updated',
					'Your account details were updated successfully.',
					'information.png',
					this.method('focus')
				);
		}
	}
	else {
		this.parent.windowCollection.createMessageWindow(
				350, 105,
				'Missing fields',
				'You have missed some required fields.\nPlease fill them in and try again.',
				'warning.png',
				this.method('focus')
			);
	}
}
RBMyAccount.prototype.show = function()
{
	this.xmlReq = this.parent.GetXML(RB_MAP_PATH+'scripts/get_user_details.php', this.method('insertData'));
}
RBMyAccount.prototype.insertData = function()
{
	var x = this.xmlReq.responseXML.getElementsByTagName('user')[0];
	this.form.fullname.value = x.getAttribute('fullname');
	this.form.email.value = x.getAttribute('email');
	//this.userbox.replaceChild(document.createTextNode(x.getAttribute('username')), this.userbox.firstChild);

	RBFancyWindow.prototype.show.apply(this);
	this.activate();
	this.form.fullname.focus();
}
//			}}} RBMyAccount
//			{{{ RBLogin
function RBLogin(parent)
{
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE, 270, 295, 'Log on');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}
	

	var form = [
		{ name: 'username', label: 'Username or email address', type: 'text', required: true },
		{ name: 'password', label: 'Password', type: 'password', required: true },
		{ name: 'submit', label: 'Log on', type: 'submit' },
	];

	this.form = createForm(form, 250, this.method('login'))

	this.form.insertBefore(document.createTextNode('Log on below, if you do not have an account click the "Sign up" button.'), this.form.firstChild);

	var forgotPass = document.createElement('div');
	forgotPass.appendChild(document.createTextNode('If you have forgotton your password then '));
	var forgotPassLink = document.createElement('span');
	forgotPassLink.appendChild(document.createTextNode('click here.'));
	GEvent.addDomListener(forgotPassLink, 'click', this.method('forgotPassword'));

	with (forgotPassLink.style) {
		cursor = 'pointer';
		color = '#00a';
	}
	forgotPass.appendChild(forgotPassLink);
	this.form.insertBefore(forgotPass, this.form.childNodes[1]);

	// Bodge: add signup button
	var signup = document.createElement('input');
	signup.type = 'button';
	signup.className = 'RBForm_button';
	signup.value = 'Sign up';
	GEvent.addDomListener(signup, 'click', this.parent.method('signUp'));

	this.form.lastChild.appendChild(signup);

	
	var p = document.createElement('p');
	p.appendChild(document.createTextNode('or click continue to plot your route'));
	p.style.textAlign = 'center';
	
	this.form.appendChild(p);



	var close = document.createElement('input');
	close.type = 'button';
	close.className = 'RBForm_button';
	close.value = 'Continue';
	close.style.display = 'block';
	close.style.margin = '10px auto 0 auto';
	GEvent.addDomListener(close, 'click', this.method('hide'));

	this.form.appendChild(close);
	container.appendChild(this.form);



	// FORGOT PASSWORD FORM

	form = [
		{ name: 'email', label: 'Email', type: 'text', required: true },
		{ name: 'submit', label: 'Submit', type: 'submit' },
	];

	this.forgotForm = createForm(form, 250, this.method('recoverPassword'))
	this.forgotForm.style.display = 'none';

	this.forgotForm.insertBefore(document.createTextNode('Enter your email address below and we will email you your account details.'), this.forgotForm.firstChild);

	container.appendChild(this.forgotForm);


	close = document.createElement('input');
	close.type = 'button';
	close.className = 'RBForm_button';
	close.value = 'Cancel';
	close.style.margin = '10px auto 0 auto';
	GEvent.addDomListener(close, 'click', this.method('hide'));

	this.forgotForm.lastChild.appendChild(close);




	// blocker
	this.blocker = document.createElement('div');
	with (this.blocker.style) {
		position = 'absolute';
		left = 0;
		top = 0;
		width = '100%';
		height = '100%';
		zIndex = 5000;
		display = 'none';
	}
	alphaBackground(this.blocker, RB_MAP_PATH+'images/dull.png');
	this.parent.getContainer().appendChild(this.blocker);
	this.window.focus = this.method('showBlocker');
	this.window.blur = this.method('showBlocker');
}
RBLogin.prototype = new RBFancyWindow();
RBLogin.prototype.focus = function()
{
	this.activate();
	this.form.username.focus();
}
RBLogin.prototype.login = function(details)
{
	if (details !== false) this.parent.PostXML(RB_MAP_PATH+'scripts/login.php', 'u='+escape(this.form.username.value)+'&p='+escape(this.form.password.value), this.parent.method('processXML'));
	else {
		this.parent.windowCollection.createMessageWindow(
				350, 105,
				'Missing fields',
				'You have missed some required fields.\nPlease fill them in and try again.',
				'warning.png',
				this.method('focus')
			);
	}
}
RBLogin.prototype.showBlocker = function()
{
	if (this.getWindow().getFrame().style.display == 'none') return;
	this.blocker.style.display = 'block';
	this.blocker.style.zIndex = parseInt(this.getWindow().getFrame().style.zIndex)-5;
}
RBLogin.prototype.hideBlocker = function()
{
	this.blocker.style.display = 'none';
}
RBLogin.prototype.hide = function()
{
	RBFancyWindow.prototype.hide.apply(this);
	this.forgotForm.email.value = '';
	this.hideBlocker();
	this.form.reset();
}
RBLogin.prototype.show = function()
{
	this.window.resize(false, 295);	// original height 295
	this.form.style.display = 'block';
	this.forgotForm.style.display = 'none';
	RBFancyWindow.prototype.show.apply(this);
	this.activate();
	this.form.username.focus();

	// XXX tiny delay to stop IE slapping it on top of the login box
	setTimeout(this.method('showBlocker'), 1);
}
RBLogin.prototype.forgotPassword = function()
{
	window.open("http://"+window.location.host+"/en-gb/Forgotten_password/index?pageID=18");
	return;
	this.window.resize(false, 190);	// original height 295
	this.form.style.display = 'none';
	this.forgotForm.style.display = 'block';
}
RBLogin.prototype.recoverPassword = function(details)
{
	if (details !== false) {
		this.parent.PostXML(RB_MAP_PATH+'scripts/recover_password.php', 'u='+escape(this.forgotForm.email.value), false);
		this.parent.windowCollection.createMessageWindow(
				350, 105,
				'Account recovery',
				'You should receive an email shortly containing your account log on details.',
				'information.png',
				this.method('hide')
			);
	}
	else {
		this.parent.windowCollection.createMessageWindow(
				350, 105,
				'Missing fields',
				'You have missed some required fields.\nPlease fill them in and try again.',
				'warning.png',
				this.method('focus')
			);
	}
}
//			}}} RBLogin
//			{{{ RBEmailRoute
function RBEmailRoute(parent)
{
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(6, 6, 300, 300, 'Email this route');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}
	container.appendChild(document.createTextNode('Enter your friend\'s email address and a message.'));

	var form = [
		{ name: 'email', label: 'Friend\'s email', type: 'text', required: true },
		{ name: 'message', label: 'Message', type: 'textarea', required: true },
		{ name: 'submit', label: 'Send', type: 'submit' },
	];

	this.form = createForm(form, 280, this.method('emailLink'))

	var close = document.createElement('input');
	close.type = 'button';
	close.className = 'RBForm_button';
	close.value = 'Cancel';
	GEvent.addDomListener(close, 'click', this.method('hide'));
	this.form.lastChild.appendChild(close);

	container.appendChild(this.form);
}
RBEmailRoute.prototype = new RBFancyWindow();
RBEmailRoute.prototype.focus = function()
{
	this.activate();
	this.form.email.focus();
}
RBEmailRoute.prototype.emailLink = function(details)
{

	if (details === false) {
		this.parent.windowCollection.createMessageWindow(
			350, 105,
			'Missing fields',
			'You have missed some required fields.\nPlease fill them in and try again.',
			'warning.png',
			this.method('focus')
		);
		return;
	}


	var ref = '';
	for (var j=0; j<this.parent.routes.length; j++) {
		if (this.parent.routes[j]) {
			if (ref) ref += ',';
			ref += this.parent.routes[j].ref;
		}
	}
	var data = 'email='+escape(this.form.email.value);
	data += '&message='+escape(this.form.message.value);
	data += '&ref='+escape(ref);
	this.parent.PostXML(RB_MAP_PATH+'scripts/email_route.php', data, this.parent.method('processXML'));
	this.hide();
}
RBEmailRoute.prototype.show = function()
{
	this.form.email.value = '';
	this.form.message.value = '';

	this.getWindow().show();
	this.form.email.focus();
}
//			}}} RBEmailRoute
//			{{{ RBMiniSearch
function RBMiniSearch(parent)
{
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(6, 6, 170, 128, 'Jump to...');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}

	var form = [
		{ name: 'postcode', label: 'Jump to Postcode or place name', type: 'text' },
		{ name: 'submit', label: 'Go', type: 'submit' },
	];

	this.form = createForm(form, 150, this.method('jumpTo'))
	container.appendChild(this.form);
}
RBMiniSearch.prototype = new RBFancyWindow();
RBMiniSearch.prototype.jumpTo = function(details)
{
	this.parent.jumpToPostcode(this.form.postcode.value);
}
//			}}} RBMiniSearch
//			{{{ RBSearch
function RBSearch(parent)
{
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(6, 142, 270, 380, 'Search');

	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}

	container.appendChild(document.createTextNode('Use the form below to enter your search criteria.'));

	var form = [
		{ name: 'postcode', label: 'Postcode or place name', type: 'text' },
		{ name: 'minlength', label: 'Minimum Length', type: 'distance', range: [0, 99] },
		{ name: 'maxlength', label: 'Maximum Length', type: 'distance', range: [99, 1] },
		{ name: 'terrain',  label: 'Terrain', type: 'combo', options: ['Any', 'Park', 'Road', 'Towpath', 'Trail', 'Multiple terrains'] },
		{ name: 'gradient', label: 'Gradient', type: 'combo', options: ['Any', 'Flat', 'Bumpy', 'Hilly'] },
		{ name: 'submit', label: 'Search', type: 'submit' },
		];
	this.form = createForm(form, 250, this.method('search'));

	// Bodge: add cancel button
	var close = document.createElement('input');
	close.type = 'button';
	close.className = 'RBForm_button';
	close.value = 'Close';
	GEvent.addDomListener(close, 'click', this.parent.method('setTool', RB_TOOL_NONE));
	this.form.lastChild.appendChild(close);

	container.appendChild(this.form);
}
RBSearch.prototype = new RBFancyWindow();
RBSearch.prototype.search = function(details)
{
	
	if (details) {
				if (this.xmlReq) this.xmlReq.abort();
		var min = this.form.minlength.value;
		var max = this.form.maxlength.value;
		
		if (this.form.minlength_unit.value == 'Miles') min *= RB_MILES;
		else min *= RB_KILOMETERS;
		if (this.form.maxlength_unit.value == 'Miles') max *= RB_MILES;
		else max *= RB_KILOMETERS;

		var url = RB_MAP_PATH+'scripts/search_routes.php?p='+escape(this.form.postcode.value)+'&min='+escape(min)+'&max='+escape(max)+'&t='+escape(this.form.terrain.value)+'&g='+escape(this.form.gradient.value)+'&u='+escape(this.form.minlength_unit.value)+'&type=RUNNING';

		this.xmlReq = this.parent.GetXML(url, this.parent.method('processXML'));
		// TODO show results window in advance with "please wait..." animation
		this.parent.results.clearResults();
		this.parent.results.show();
		this.parent.results.getWindow().moveTo(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE);
		this.parent.results.activate();
	}
	else {
		this.parent.windowCollection.createMessageWindow(
				350, 105,
				'Missing fields',
				'You have missed some required fields.\nPlease fill them in and try again.',
				'warning.png',
				this.method('focus')
			);
	}

}
RBSearch.prototype.show = function()
{
	this.getWindow().show();
	this.form.postcode.focus();
}
//			}}} RBSearch
//			{{{ RBControls
function RBControls(parent)
{
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(184, 6, 155, 110, 'Route info');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '0.5em';
	}

	var chk;
	var label;
	var chk_id;
	var ut = new Date();


	// toggle mile markers check box
	chk_id = 'chk_'+ut.getTime()+rnd(0, 9999);
	chk = document.createElement('input');
	chk.type = 'checkbox';
	chk.setAttribute('id', chk_id);
	GEvent.addDomListener(chk, 'click', this.method('toggleMiles'));
	this.milesCheckBox = chk;

	label = document.createElement('label');
	label.setAttribute('for', chk_id);
	label.appendChild(document.createTextNode('Mile markers'));

	container.appendChild(chk);
	container.appendChild(label);


	container.appendChild(document.createElement('br'));


	// toggle kilometer markers checkbox
	chk_id = 'chk_'+ut.getTime()+rnd(0, 9999);
	chk = document.createElement('input');
	chk.type = 'checkbox';
	chk.setAttribute('id', chk_id);
	GEvent.addDomListener(chk, 'click', this.method('toggleKilometres'));
	this.kilometresCheckBox = chk;

	label = document.createElement('label');
	label.setAttribute('for', chk_id);
	label.appendChild(document.createTextNode('Kilometre markers'));

	container.appendChild(chk);
	container.appendChild(label);

	var p = container.appendChild(document.createElement('p'));
	p.appendChild(document.createTextNode('This route is:'));
	p.style.paddingLeft = '5px';
	p.style.paddingBottom = '3px';

	// total distance
	// Miles
	var div = document.createElement('div');
	div.style.fontWeight = 'bold';
	div.style.paddingLeft = '5px';
	div.style.cssFloat = 'left';
	div.style.styleFloat = 'left';
	div.style.fontSize = '0.9em';
	this.totalMiles = div.appendChild(document.createElement('span'));
	div.appendChild(document.createTextNode(' Miles'));

	container.appendChild(div);

	// KMs
	div = document.createElement('div');
	div.style.fontWeight = 'bold';
	div.style.paddingRight = '5px';
	div.style.cssFloat = 'right';
	div.style.styleFloat = 'right';
	div.style.fontSize = '0.9em';
	this.totalKilometres = div.appendChild(document.createElement('span'));
	div.appendChild(document.createTextNode(' Km'));

	this.updateDistances();

	container.appendChild(div);
/*
	// fit to window button
	var btn = document.createElement('input');
	btn.type = 'button';
	btn.value = 'Fit view to routes';
	btn.className = 'RBForm_button';
	btn.style.width = '140px';
	btn.style.padding = '0 10px';
	btn.style.marginTop = '5px';

	GEvent.addDomListener(btn, 'click', this.parent.method('fitMapToAllRoutes'));

	container.appendChild(btn);
	*/
}
RBControls.prototype = new RBFancyWindow();
RBControls.prototype.updateDistances = function()
{
	var distance = this.parent.getTotalLength();
	if (this.totalMiles.firstChild) this.totalMiles.removeChild(this.totalMiles.firstChild);
	this.totalMiles.appendChild(document.createTextNode(Math.round1DP(distance/RB_MILES)));
	if (this.totalKilometres.firstChild) this.totalKilometres.removeChild(this.totalKilometres.firstChild);
	this.totalKilometres.appendChild(document.createTextNode(Math.round1DP(distance/RB_KILOMETERS)));
}
RBControls.prototype.toggleMiles = function()
{
	if (this.milesCheckBox.checked) this.parent.enableMileMarkers();
	else this.parent.disableMileMarkers();
}
RBControls.prototype.toggleKilometres = function()
{
	if (this.kilometresCheckBox.checked) this.parent.enableKilometreMarkers();
	else this.parent.disableKilometreMarkers();
}
//			}}} RBControls
//			{{{ RBMyRuns
// XXX This steals a lot of code from RBResults, maybe it should extend it
function RBMyRuns(parent)
{
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(6, 6, 170, 144, 'My routes');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}

	this.container = container;
	this.clear();
	this.mapUnEdited = true;
}
RBMyRuns.prototype = new RBFancyWindow();
RBMyRuns.prototype.clear = function()
{
	// remove entire contents
	while (this.container.firstChild) this.container.removeChild(this.container.firstChild);
	var p = document.createElement('p');
	p.style.textAlign = 'center';
	p.appendChild(document.createTextNode('You need to log on to view your routes'));
	this.container.appendChild(p);

	this.window.resize(false, 80);
}
RBMyRuns.prototype.refresh = function()
{
	var container = this.container;
	// remove entire contents
	while (container.firstChild) container.removeChild(container.firstChild);

	// show please wait type message
	var p = document.createElement('p');
	p.style.textAlign = 'center';
	p.appendChild(document.createTextNode('Loading your routes...'));
	container.appendChild(p);

	// go get the runs!
	if (this.xmlreq) this.xmlreq.abort();
	var cat = (this.catBox)?this.catBox.value:'running'; // default cat = running
	this.xmlreq = this.parent.GetXML(RB_MAP_PATH+'scripts/get_myruns.php?c='+cat, this.parent.method('processXML'));
}
RBMyRuns.prototype.noRuns = function()
{
	// remove entire contents
	while (this.container.firstChild) this.container.removeChild(this.container.firstChild);
	var p = document.createElement('div');
	p.style.textAlign = 'center';
	p.appendChild(document.createTextNode('You have not created any routes yet.'));
	with (p.style) {
		fontSize = '0.8em';
		paddingBottom = '3px';
	}
	this.container.appendChild(p);
	
	/*
	var p = document.createElement('select');
	p.style.width = '150px';
	var types = [
		['All my routes', 'all'],
		['My running routes', 'running'],
		['My walking routes', 'walking'],
		['My cycling routes', 'cycling']
	];
	for (var i=0; i<types.length; i++) {
		p.options[i] = new Option(types[i][0], types[i][1]);
	}
	GEvent.bindDom(p, 'change', this, function()
	{
		this.refresh();
	});
	this.catBox = p;


	this.container.appendChild(p);
	 */

	this.window.resize(false, 90);
}
RBMyRuns.prototype.clearList = function()
{
	var container = this.container;
	while (container.firstChild) container.removeChild(container.firstChild);

	var p = document.createElement('div');
	//p.innerHTML = 'Click on the route name below that you wish to view or, <a href="#" onclick="routeMapper.fitMapToAllRoutes()">click here</a> to view all your highlighted routes on the same page.';
	p.innerHTML = 'Click on the route name below that you wish to view.';
	with (p.style) {
		fontSize = '0.8em';
		paddingBottom = '3px';
	}
	this.container.appendChild(p);

/*
	
	var p = document.createElement('select');
	p.style.width = '150px';
	var types = [
		['All my routes', 'all'],
		['My running routes', 'running'],
		['My walking routes', 'walking'],
		['My cycling routes', 'cycling']
	];
	for (var i=0; i<types.length; i++) {
		p.options[i] = new Option(types[i][0], types[i][1]);
	}
	GEvent.bindDom(p, 'change', this, function()
	{
		this.refresh();
	});
	this.catBox = p;
	this.container.appendChild(p);
 */

	// frame containing the table
	var resultFrame = document.createElement('div');
	resultFrame.className = 'RBResults_frame';
	with (resultFrame.style) {
		fontSize = '12px';
		width = '148px';
		overflow = 'hidden';
	}
	this.resultFrame = resultFrame;

/*
 * // table used just for the headers
	this.resultHeaders = document.createElement('table');
	this.resultHeaders.className = 'RBResults_table';
	this.resultHeaders.style.height = '24px';
	this.resultHeaders.style.width = '100%';
	var tHead = this.resultHeaders.appendChild(document.createElement('thead'));
	tHead = tHead.appendChild(document.createElement('tr'));

	var headers = ['Title', 90, 'Length', false];
	for (var i=0; i<headers.length; i+=2) {
		var th = tHead.appendChild(document.createElement('th'));
		th.appendChild(document.createTextNode(headers[i]));
		th.setWidth = headers[i+1];
		if (headers[i+1]) th.style.width = headers[i+1] +'px';
	}
	tHead.firstChild.className = 'first';
	tHead.lastChild.className = 'last';
	resultFrame.appendChild(this.resultHeaders);
*/
	// Scrolling container for table
	var resultContainer = document.createElement('div');
	with (resultContainer.style) {
		height = '176px';
		overflow = 'auto';
		if (window.isIE) {
			overflowY = 'scroll';
			overflowX = 'hidden';
		}
		width = '148px';
	}
	this.resultContainer = resultContainer;

	// Table where results appear
	this.resultTable = document.createElement('table');
	this.resultTable.className = 'RBResults_table';
	this.resultTable.appendChild(document.createElement('tbody'));
	this.resultTable.style.width = '100%';
	resultContainer.appendChild(this.resultTable);
	resultFrame.appendChild(resultContainer);



	container.appendChild(resultFrame);



	var p = document.createElement('input');
	p.type = 'button';
	p.value = 'Delete marked routes';
	p.className = 'RBWindow_button';
	p.style.width = '150px';
	p.style.margin = '0';
	p.style.fontSize = '0.8em';

	var _rbMap = this.parent;
	GEvent.addDomListener(p, 'click', function() {
		if (!confirm('Are you sure you wish to deleted the selected routes.')) return;
		var boxes = document.getElementsByName('myRoute');
		var delStr = '';

		for (var i=0; i<boxes.length; i++) {
			if (boxes[i].checked) {
				if (delStr != '') delStr += ',';
				delStr += boxes[i].value
			}
		}
		_rbMap.busySignal.show();
		_rbMap.PostXML(RB_MAP_PATH+'scripts/delete_routes.php', 'r='+delStr, _rbMap.method('processXML'));
	});
	this.container.appendChild(p);
}
RBMyRuns.prototype.addRun = function(title, ref)
{
		var tr = this.resultTable.tBodies[0].appendChild(document.createElement('tr'));
	tr.style.cursor = 'pointer';
	tr.id = 'RBMyRuns_ref_'+ref;
	

	// delete me checkbox
	var td = tr.appendChild(document.createElement('td'));
	var input = document.createNamedElement('input', 'myRoute');
	input.type = 'checkbox';
	input.value = ref;
	input = td.appendChild(input);


	//var headers = this.resultHeaders.firstChild.firstChild.childNodes;
	//for (var i=0; i<headers.length; i++) {
	td = tr.appendChild(document.createElement('td'));
	td.appendChild(document.createTextNode(title));
	//	if (arguments[i]) td.appendChild(document.createTextNode(arguments[i]));
	//	if (headers[i].setWidth) td.style.width = headers[i].setWidth +'px';
	//}
	var isOdd = (this.resultTable.tBodies[0].childNodes.length % 2 != 0);
	tr.isOdd = isOdd;
	if (isOdd) tr.className = 'odd';
	GEvent.addDomListener(td, 'mouseover', function() { if (tr.className != 'highlight') tr.className = 'hover'; } );
	GEvent.addDomListener(td, 'mouseout', function() { if (tr.className != 'highlight') tr.className = (isOdd)?'odd':''; } );
	var _rbMap = this.parent;
	GEvent.addDomListener(td, 'click', function(e) {
		// Load route
		if (_rbMap.getRouteByRef(ref) === false) {
			if ( (_rbMap.getCurrentTool() == RB_TOOL_PIN || _rbMap.getCurrentTool() == RB_TOOL_LINE) &&
				(!_rbMap.myRuns.mapUnEdited && !confirm('You will lose all unsaved changes to your current route if you load this route, are you sure you want to do this?'))
				) {
				return false;
				}
			_rbMap.loadRoute(ref);
		}
		// remove route
		else if (_rbMap.getCurrentTool() != RB_TOOL_PIN && _rbMap.getCurrentTool() != RB_TOOL_LINE) {
			_rbMap.removeRouteByRef(ref);
		}
		// we're in edit mode, so reset when unloading
		else if (_rbMap.myRuns.mapUnEdited || confirm('You will lose all unsaved changes to this route if you unload it, are you sure you want to do this?')) {
			_rbMap.removeAllRoutes();
			if (_rbMap.selectedRoute === false) _rbMap.selectedRoute = _rbMap.createRoute();
			_rbMap.properties.setRoute(_rbMap.selectedRoute);
		}
	} );
	if (this.parent.getRouteByRef(ref) !== false) {
		var rt = this.parent.getRouteByRef(ref);
		this.highlightRef(ref, this.parent.getRoute(rt).colour);
	}
	// XXX Bodge to work around IE's screw up of not incluing scrollbars in the width
	if (window.isIE) {
		var overLap = this.resultFrame.scrollWidth - this.resultFrame.clientWidth;
		if (overLap) this.resultTable.style.width = (this.resultFrame.clientWidth-overLap)  +'px';
	}
	this.fixSize();
}
/**
 * Checks if a given route has it's checkbox checked
 * XXX Redundent, checkboxes no longer used
 */
RBMyRuns.prototype.isRouteChecked = function(ref)
{
	var chks = this.resultTable.getElementsByTagName('input');
	for (var i=0; i<chks.length; i++) {
		if (chks[i].value == ref) return chks[i].checked;
	}
	return false;
}
/**
 * Highlight a given row using it's ref
 */
RBMyRuns.prototype.highlightRef = function(ref, colour)
{
	var tr = document.getElementById('RBMyRuns_ref_'+ref);
	if (tr) {
		tr.style.backgroundColor = colour;
		tr.active = true;
		tr.className = 'highlight';
	}
}
RBMyRuns.prototype.dehighlightRef = function(ref)
{
	var tr = document.getElementById('RBMyRuns_ref_'+ref);
	if (tr) {
		tr.style.backgroundColor = '';
		tr.active = false;
		tr.className = (tr.isOdd)?'odd':'';
	}
}
RBMyRuns.prototype.dehighlightAll = function()
{
	if (!this.resultTable) return;
	var trs = this.resultTable.tBodies[0].getElementsByTagName('tr');
	for (var i=0; i<trs.length; i++) {
		trs[i].style.backgroundColor = '';
		trs[i].active = false;
		trs[i].className = (trs[i].isOdd)?'odd':'';
	}
}
RBMyRuns.prototype.fixSize = function()
{
	var newHeight = this.resultTable.offsetHeight;
	if (newHeight > 185) newHeight = 185;
	this.window.resize(false, newHeight+120);
	this.resultContainer.style.height = newHeight+'px';
}
//			}}} RBMyRuns
//			{{{ RBResults
function RBResults(parent)
{
	this.parent = parent;
	this.window = parent.windowCollection.createWindow(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE, 400, 350, 'Search Results');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}

	var p = document.createElement('p');
	p.appendChild(document.createTextNode('Below are the results to your search query.'));
	container.appendChild(p);
	p = document.createElement('p');
	p.appendChild(document.createTextNode('If this window is blocking your view you can double click the title bar to minimise it.'));
	container.appendChild(p);

	// frame containing the table
	var resultFrame = document.createElement('div');
	resultFrame.className = 'RBResults_frame';
	with (resultFrame.style) {
		marginTop = '1em';
		fontSize = '12px';
		width = '378px';
		overflow = 'hidden';
	}
	this.resultFrame = resultFrame;

	// table used just for the headers
	this.resultHeaders = document.createElement('table');
	this.resultHeaders.className = 'RBResults_table';
	this.resultHeaders.style.height = '24px';
	this.resultHeaders.style.width = '100%';
	var tHead = this.resultHeaders.appendChild(document.createElement('thead'));
	tHead = tHead.appendChild(document.createElement('tr'));

	//var headers = ['Ref', 75, 'Title', 250, 'Author', 125, 'Length', 75, 'Postcode or place name', false];
	var headers = ['Ref', 75, 'Title', 200, 'Length', false];
	for (var i=0; i<headers.length; i+=2) {
		var th = tHead.appendChild(document.createElement('th'));
		th.appendChild(document.createTextNode(headers[i]));
		th.setWidth = headers[i+1];
		if (headers[i+1]) th.style.width = headers[i+1] +'px';
	}
	tHead.firstChild.className = 'first';
	tHead.lastChild.className = 'last';
	resultFrame.appendChild(this.resultHeaders);

	// Scrolling container for table
	var resultContainer = document.createElement('div');
	with (resultContainer.style) {
		height = '176px';
		overflow = 'auto';
		if (window.isIE) overflowY = 'scroll';
	}
	this.resultContainer = resultContainer;

	// Table where results appear
	this.resultTable = document.createElement('table');
	this.resultTable.className = 'RBResults_table';
	this.resultTable.appendChild(document.createElement('tbody'));
	this.resultTable.style.width = '100%';
	resultContainer.appendChild(this.resultTable);
	resultFrame.appendChild(resultContainer);

	container.appendChild(resultFrame);

	// close button
	var close = document.createElement('button');
	// XXX: IE can't set type (it defaults to button anyway)
	try {
		close.type = 'button';
	}
	catch(e){}
	close.className = 'RBForm_button';
	close.appendChild(document.createTextNode('Close'));
	with (close.style) {
		position = 'absolute';
		right = '5px';
		bottom = '10px';
	}
	GEvent.addDomListener(close, 'click', this.method('hide'));
	this.window.getContainer().appendChild(close);
}
RBResults.prototype = new RBFancyWindow();
RBResults.prototype.clearResults = function()
{
	var tb = this.resultTable.tBodies[0];
	while (tb.firstChild) {
		tb.removeChild(tb.firstChild);
	}
	this.fixSize();
}
RBResults.prototype.addResult = function(ref, title, author, length, postcode)
{
		var tr = this.resultTable.tBodies[0].appendChild(document.createElement('tr'));
	tr.style.cursor = 'pointer';
	var headers = this.resultHeaders.firstChild.firstChild.childNodes;
	for (var i=0; i<headers.length; i++) {
		var td = tr.appendChild(document.createElement('td'));
		if (arguments[i]) td.appendChild(document.createTextNode(arguments[i]));
		if (headers[i].setWidth) td.style.width = headers[i].setWidth +'px';
	}
	var isOdd = (this.resultTable.tBodies[0].childNodes.length % 2 != 0);
	if (isOdd) tr.className = 'odd';
	GEvent.addDomListener(tr, 'mouseover', function() { tr.className = 'hover'; } );
	GEvent.addDomListener(tr, 'mouseout', function() { tr.className = (isOdd)?'odd':''; } );
	var _rbMap = this.parent;
	GEvent.addDomListener(tr, 'click', function() {
		_rbMap.removeAllRoutes();
		_rbMap.loadRoute(ref);
	} );

	// hide search after loading route
	GEvent.addDomListener(tr, 'click', this.method('hide'));

	// XXX Bodge to work around IE's screw up of not incluing scrollbars in the width
	if (window.isIE) {
		var overLap = this.resultFrame.scrollWidth - this.resultFrame.clientWidth;
		if (overLap) this.resultTable.style.width = (this.resultFrame.clientWidth-overLap)  +'px';
	}
	this.fixSize();
}
RBResults.prototype.fixSize = function()
{
	var newHeight = this.resultTable.offsetHeight;
	if (newHeight > 200) newHeight = 200;
	this.window.resize(false, newHeight+175);
	this.resultContainer.style.height = newHeight+'px';
	this.getWindow().moveTo(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE);
}
//			}}} RBResults
//			{{{ RBProperties
function RBProperties(parent, route)
{
	this.parent = parent;
	this.selectedRoute = this.parent.getRoute(route);
	this.window = parent.windowCollection.createWindow(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE, 350, 350, 'Route properties');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}

	var form = [
		{ name: 'title',    label: 'Name your route',     type: 'text', required: true },
		{ name: 'postcode', label: 'Postcode or place name',            type: 'text', required: false },
		{ label: 'What is this route suitable for?',      type: 'paragraph' },
		{ name: 'terrain',  label: 'Terrain',             type: 'combo', options: ['Park', 'Road', 'Towpath', 'Trail', 'Multiple terrains'] },
		{ name: 'gradient', label: 'Gradient',            type: 'combo', options: ['Flat', 'Bumpy', 'Hilly'] },
//		{ name: 'notes',    label: 'Note or description', type: 'textarea' },
		{ name: 'shared',   label: 'Share this route with the public', type: 'checkbox' },
		{ name: 'save',     label: 'Save',          type: 'submit' },
		];

	this.form = createForm(form, 330, this.method('update'));

	// Bodge: add save-as button
	var saveas = document.createElement('input');
	saveas.type = 'button';
	saveas.className = 'RBForm_button';
	saveas.value = 'Save copy';
	GEvent.bindDom(saveas, 'click', this, function() {
		this.selectedRoute.properties.ref = false;
		GEvent.trigger(this.form, 'submit');
	});
	this.form.lastChild.appendChild(saveas);
	this.saveAsButton = saveas;

	// Bodge: add close button
	var close = document.createElement('input');
	close.type = 'button';
	close.className = 'RBForm_button';
	close.value = 'Cancel';
	GEvent.addDomListener(close, 'click', this.method('hide'));
	this.form.lastChild.appendChild(close);

	container.appendChild(this.form);

}
RBProperties.prototype = new RBFancyWindow();
RBProperties.prototype.setRoute = function(route)
{
	this.selectedRoute = this.parent.getRoute(route);
}
RBProperties.prototype.update = function(details)
{
	if (!this.parent.loginRequired()) {
		return false;
	}
	else if (details) {
				var p = this.selectedRoute.properties;
		for (var i=0; i<details.length; i++) {
			var f = details[i];
			if (p[f.name] !== undefined) p[f.name] = f.value;
		}
		this.getWindow().hide();
		this.parent.save();
	}
	else {
		this.parent.windowCollection.createMessageWindow(
				350, 105,
				'Missing fields',
				'You have missed some required fields.\nPlease fill them in and try again.',
				'warning.png',
				this.method('focus')
			);
	}
}
RBProperties.prototype.focus = function()
{
	this.parent.windowCollection.activateWindow(this.getWindow().index);
}
RBProperties.prototype.show = function()
{
	var p = this.selectedRoute.properties;
	this.form.items['title'].value = p.title;

	this.getWindow().show();
	this.form.title.focus();
}
RBProperties.prototype.populate = function(data)
{
	this.form['title'].value = data.title;
	this.form.postcode.value = data.postcode;
	this.form.terrain.value = data.terrain;
	this.form.gradient.value = data.gradient;
//	this.form.notes.value = data.notes;
	this.form.shared.checked = (data.shared == 'true');
	//this.form.running.checked = (data.running == 'true');
	//this.form.walking.checked = (data.walking == 'true');
	//this.form.cycling.checked = (data.cycling == 'true');
}
//			}}} RBProperties
//			{{{ RBPinBox
function RBPinBox(parent)
{
	var pinsPerRow = 2; // Number of pins to show on each row

	this.parent = parent;
	this.window = parent.windowCollection.createWindow(RB_WINDOW_CENTRE, RB_WINDOW_CENTRE, 160, 266, 'Pins');
	var container = this.window.getContainer();
	container = container.appendChild(document.createElement('div'));
	with (container.style) {
		padding = '10px';
	}

	// populate with pins
	var table = document.createElement('table');
	table.style.width = '80px';
	var tb = table.appendChild(document.createElement('tbody'));
	var c = 0;
	var tr;
	for (var x in Pins) {
		var ico = Pins[x];
		if ( typeof ico == 'object' && ((!ico.private && !ADMIN_MODE) || (ico.private && ADMIN_MODE)) ) {
			// add pin graphic
			if (c%pinsPerRow == 0) tr = tb.appendChild(document.createElement('tr'));
			var td = tr.appendChild(document.createElement('td'));
			td.pin = x;
			td.id = '_selectpin_'+x;
			var holder = td.appendChild(document.createElement('div'));
			with (holder.style) {
				width = ico.iconSize.width +'px';
				height = ico.iconSize.height +'px';
				margin = 'auto';
				if(window.isIE) filter = 'alpha(opacity=100)';
			}
			holder.className = 'RBPinBox_pin';
			var img = holder.appendChild(document.createElement('div'));
			img.style.width = ico.iconSize.width +'px';
			img.style.height = ico.iconSize.height +'px';
			alphaBackground(img, ico.image);



			// add events to cell
			GEvent.addDomListener(td, 'click', this.method('selectPin', x));


			c++;
		}
	}
	container.appendChild(table);
	this.table = table;
}
RBPinBox.prototype = new RBFancyWindow();
RBPinBox.prototype.selectPin = function(pin)
{
	if (typeof pin == 'object') pin = arguments[1];
	this.parent.selectPin(pin);


	this.highlightPin(pin);
}
RBPinBox.prototype.highlightPin = function(pin)
{
	var td = document.getElementById('_selectpin_'+pin);
	var tds = this.table.getElementsByTagName('td');
	for (var i=0; i<tds.length; i++) {
		tds[i].firstChild.className = 'RBPinBox_pin';
	}
	td.firstChild.className = 'RBPinBox_selectedPin';
}
RBPinBox.prototype.show = function()
{
	this.window.show();
	this.highlightPin(this.parent.selectedPin);
}
//			}}} RBPinBox
//		}}} Windows
//		{{{ RBToolbox
function RBToolbox(parent)
{
	this.parent = parent;
	var _self = this;

	this.container = document.createElement('div');
	with (this.container.style) {
		position = 'absolute';
		left = '50%';
		top = '6px';
		width = '240px';
		zIndex = 1;
		marginLeft = '-120px';
	}
	setOpacity(this.container, 0.9);


	// XXX: The styling on these is a bit of a bodge
	this.searchButton = new RBButton('search_button.png', 78, 30);
	searchButton = this.searchButton.initialize(this);
	searchButton.style.cssFloat = 'left';
	searchButton.style.styleFloat = 'left';
	searchButton.title = 'Search for routes created by other people';
	GEvent.addDomListener(searchButton, 'click', this.method('activateSearchButton'));

	this.createButton = new RBButton('create_button.png', 78, 30);
	createButton = this.createButton.initialize(this);
	createButton.style.cssFloat = 'left';
	createButton.style.styleFloat = 'left';
	createButton.title = 'Create your own route';
	GEvent.addDomListener(createButton, 'click', this.method('activateCreateButton'));

/*	this.loginButton = new RBButton('login_button.png', 78, 30);
	loginButton = this.loginButton.initialize(this);
	loginButton.style.cssFloat = 'left';
	loginButton.style.styleFloat = 'left';
	loginButton.title = 'Login';
	GEvent.addDomListener(loginButton, 'click', this.parent.method('login'));
*/
	this.logoutButton = new RBButton('logout_button.png', 78, 30);
	logoutButton = this.logoutButton.initialize(this);
	logoutButton.style.cssFloat = 'left';
	logoutButton.style.styleFloat = 'left';
	logoutButton.title = 'logout';
	GEvent.addDomListener(logoutButton, 'click', this.parent.method('logout'));
	this.logoutButton.hide();

	this.mainButtons = this.container;

	// Editor buttons
	this.container = document.createElement('div');
	with (this.container.style) {
		position = 'absolute';
		left = '50%';
		top = '40px';
		width = '270px';
		zIndex = 1;
		marginLeft = '-135px';
	}
	setOpacity(this.container, 0.9);


	this.newButton = new RBButton('new_button.png', 54, 54);
	newButton = this.newButton.initialize(this);
	newButton.style.cssFloat = 'left';
	newButton.style.styleFloat = 'left';
	newButton.title = 'Clear this route and start again';
	GEvent.addDomListener(newButton, 'click', this.parent.method('resetEditor'));

	this.saveButton = new RBButton('save_button.png', 54, 54);
	saveButton = this.saveButton.initialize(this);
	with (saveButton.style) {
		cssFloat = 'left';
		styleFloat = 'left';
	}
	saveButton.title = 'Save your route';
	GEvent.addDomListener(saveButton, 'click', this.parent.method('routeProperties'));

	this.lineButton = new RBButton('edit_button_hi.png', 54, 54);
	lineButton = this.lineButton.initialize(this);
	lineButton.style.cssFloat = 'left';
	lineButton.style.styleFloat = 'left';
	lineButton.title = 'Edit your route';
	GEvent.addDomListener(lineButton, 'click', this.method('activateLineButton'));

	this.pinButton = new RBButton('pin_button.png', 54, 54);
	pinButton = this.pinButton.initialize(this);
	pinButton.style.cssFloat = 'left';
	pinButton.style.styleFloat = 'left';
	pinButton.title = 'Add pins to your route';
	GEvent.addDomListener(pinButton, 'click', this.method('activatePinButton'));
	
	this.undoButton = new RBButton('undo_button.png', 54, 54);
	undoButton = this.undoButton.initialize(this);
	undoButton.style.cssFloat = 'left';
	undoButton.style.styleFloat = 'left';
	undoButton.title = 'Undo your last addition';
	GEvent.addDomListener(undoButton, 'click', this.parent.method('undoEdit'));

	this.editorButtons = this.container;



	this.parent.getContainer().appendChild(this.mainButtons);
	this.parent.getContainer().appendChild(this.editorButtons);


//	this.hideEditTools();
	hideEditTools();


	/*
	this.window = parent.windowCollection.createWindow(142, 6, 165, 52, 'Toolbox', true);
	this._buttons = [];

	this.container = this.window.getContainer();
	this.container = this.container.appendChild(document.createElement('div'));
	with (this.container.style) {
		textAlign = 'left';
		padding = '4px 0 4px 4px';
	}

	this.addButton(new RBToolboxButton('disk.png', 'Save route'), this.parent.method('save'));
	this.addButton(new RBToolboxButton('edit.png', 'View route'), this.parent.method('setTool', RB_TOOL_VIEW));
	this.addButton(new RBToolboxButton('edit.png', 'Modify route'), this.parent.method('setTool', RB_TOOL_LINE));
	this.addButton(new RBToolboxButton('edit.png', 'Add markers'), this.parent.method('setTool', RB_TOOL_PIN));
	this.addButton(new RBToolboxButton('properties.png', 'Edit route\'s details'), this.parent.method('routeProperties'));
*/
}
RBToolbox.prototype.toString = function()
{
	return '[object RBToolbox]';
}
RBToolbox.prototype.hideEditTools = function()
{
	this.editorButtons.style.display = 'none';
}
RBToolbox.prototype.showEditTools = function()
{
	this.editorButtons.style.display = '';
}
RBToolbox.prototype.activateSearchButton = function()
{
	if (this.parent.getCurrentTool() != RB_TOOL_SEARCH) {
		this.parent.setTool(RB_TOOL_SEARCH);
	}
}
RBToolbox.prototype.activateCreateButton = function()
{
	if (this.parent.getCurrentTool() == RB_TOOL_SEARCH || this.parent.getCurrentTool() == RB_TOOL_NONE) {
		this.parent.setTool(RB_TOOL_LINE);
	}
}
RBToolbox.prototype.activateLineButton = function()
{
	this.parent.setTool(RB_TOOL_LINE);
}
RBToolbox.prototype.activatePinButton = function()
{
	this.parent.setTool(RB_TOOL_PIN);
}
//RBToolbox.prototype = new RBFancyWindow();
RBToolbox.prototype.getContainer = function()
{
	return this.container;
}
RBToolbox.prototype.hide = function()
{
	this.container.style.display = 'none';
}
RBToolbox.prototype.show = function()
{
	this.container.style.display = 'block';
}
/*RBToolbox.prototype.addButton = function(button, func)
{
	if (func) GEvent.addDomListener(button.getContainer(), 'click', func);

	var x = 4;
	if (this._buttons.length > 0) {
		var lastButton = this._buttons[this._buttons.length-1].getContainer();
		x = lastButton.offsetLeft+lastButton.offsetWidth+4;
	}

	this._buttons.push(button);
	return this.getContainer().appendChild(button.getContainer());
}*/
//			{{{ RBToolboxButton
function RBToolboxButton(icon, tooltip)
{
	this.container = document.createElement('div');
	with (this.container.style) {
		width = '22px';
		height = '22px';
		border = '1px solid #555';
		padding = '2px';
		marginRight = '4px';
		cssFloat = 'left';
		styleFloat = 'left';
		cursor = 'pointer';
	}
	this.container.title = tooltip;
	var ico = this.container.appendChild(document.createElement('div'));
	with (ico.style) {
		width = '22px';
		height = '22px';
	}
	alphaBackground(ico, RB_MAP_PATH+'images/'+icon);
}
RBToolboxButton.prototype.getContainer = function()
{
	return this.container;
}
//			}}} RBToolboxButton
//		}}} RBToolbox
//		{{{ RBBusyBlocker
function RBBusyBlocker(parent)
{
	this.parent = parent;
	this.container = document.createElement('div');
	with (this.container.style) {
		position = 'absolute';
		left = 0;
		top = 0;
		width = '100%';
		height = '100%';
		zIndex = 5000;
		display = 'none';
		cursor = 'wait';
	}
	alphaBackground(this.container, RB_MAP_PATH+'images/dull.png');

	var img = this.container.appendChild(document.createElement('img'));
	img.src = RB_MAP_PATH+'images/indicator_circle.gif';
	with (img.style) {
		position = 'absolute';
		left = '50%';
		top = '50%';
		marginLeft = '-50px';
		marginTop = '-50px';
	}
	this.container.appendChild(img);

	this.parent.getContainer().appendChild(this.container);
}
RBBusyBlocker.prototype.show = function()
{
	this.container.style.display = 'block';
}
RBBusyBlocker.prototype.hide = function()
{
	this.container.style.display = 'none';
}
//		}}} RBBusyBlocker
//	}}} Objects
//	{{{ Controls
GControl.prototype.isRB = function() { return false; }
//		{{{ RBControl
function RBControl(printable, selectable) {
	if (printable) this.printable = function() { return true; }
	else this.printable = function() { return false; }
	if (selectable) this.selectable = function() { return true; }
	else this.selectable = function() { return false; }
}
RBControl.prototype = new GControl();
RBControl.prototype.isRB = function() { return true; }
RBControl.prototype.toString = function() { return '[object RBControl]'; }
RBControl.prototype.addControl = function(control)
{
	var el = this.getContainer().appendChild(control.initialize(this));
	control.parent = this;
	return el;
}
RBControl.prototype.getContainer = function()
{
	if (typeof this.container == 'undefined') this.container = document.createElement('div');
	return this.container;
}
//		}}} RBControl
//		{{{ Move map type controls
GMapTypeControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(75, 8));
}
GLargeMapControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(4, 32));
}
//		}}} Move map type controls

//		{{{ RBMarkerBox
function RBMarkerBox(){}
RBMarkerBox.prototype = new RBControl();
RBMarkerBox.prototype.initialize = function(parent)
{
	var container = this.getContainer();
	with (container.style) {
		margin = "5px";
		width = "130px";
		height = "200px";
		background = "url('"+RB_MAP_PATH+"images/marker_box.gif')";
	}

	// box that containers the list of markers
	var markerList = document.createElement('div');
	with (markerList.style) {
		width = "100%";
		height = "100%";
		overflow = "auto";
		position = "relative";
	}
	container.appendChild(markerList);

	// populate with markers
	var x = 8;
	var y = 8;
	for (var i=0; i<RB_MARKERS.length; i++) {
		var m = RB_MARKERS[i];
		var div = document.createElement('div');
		with (div.style) {
			width = m.iconSize.width +"px";
			height = m.iconSize.height +"px";
			position = "absolute";
			left = x +"px";
			top = y +"px";
		}
		alphaBackground(div, m.image);
		div._marker = m;
		markerList.appendChild(div);
		GEvent.addDomListener(div, "click", this.method('activateMarker', div));

		x += 40;
		if (x > 120) {
			x = 8;
			y += 40;
		}
	}

	// stick in the markers

	parent.getContainer().appendChild(container);
	return container;
}
RBMarkerBox.prototype.activateMarker = function(e, marker)
{
	marker.style.border = "2px solid blue";
	marker.style.margin = "-2px";
}
//		}}} RBMarkerBox

//		{{{ RBButton
function RBButton(image, width, height) {
	this._image = RB_MAP_PATH+"images/"+image;
	this._width = width;
	this._height = height;
}
RBButton.prototype = new RBControl();
RBButton.prototype.printable = function() { return false }
RBButton.prototype.initialize = function(parent) {
	var container = this.getContainer();
	with (container.style) {
		backgroundImage = "url('"+this._image+"')";
		width = this._width+"px";
		height = this._height+"px";
		cursor = "pointer";
		overflow = "hidden";
		if (typeof filter == "string") {
			filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+this._image+"')";
			background = "transparent";
		}
	}
	parent.getContainer().appendChild(container);
	return container;
}
RBButton.prototype.changeImage = function(image)
{
	this._image = RB_MAP_PATH+"images/"+image;
	var container = this.getContainer();
	with (container.style) {
		backgroundImage = "url('"+this._image+"')";
		if (typeof filter == "string") {
			filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+this._image+"')";
			background = "transparent";
		}
	}
}
RBButton.prototype.hide = function()
{
	this.container.style.display = "none";
}
RBButton.prototype.show = function()
{
	this.container.style.display = "block";
}
// where should it be positioned?
RBButton.prototype.getDefaultPosition = function() {
	return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(4, 4));
}
//		}}} RBButton
//		{{{ realbuzz.com logo
function RBLogo() {}
RBLogo.prototype = new RBButton('mini_realbuzz_logo.png', 98, 24);
RBLogo.superclass = RBButton.prototype;
RBLogo.prototype.printable = function() { return true }
RBLogo.prototype.initialize = function(map) {
	var realbuzz = RBLogo.superclass.initialize.apply(this, arguments);
	GEvent.addDomListener(realbuzz, "click", function() {
		window.open('http://realbuzz.com/');
	});
	map.getContainer().appendChild(realbuzz);
	return realbuzz;
}
// where should it be positioned?
RBLogo.prototype.getDefaultPosition = function() {
	return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(4, 4));
}
//		}}} realbuzz.com logo
//	}}} Controls
//	{{{ Markers
//		{{