Index: /extensions/rv_gmaps/tags/2.0.c/admin/admin.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/admin/admin.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/admin/admin.php	(revision 4071)
@@ -0,0 +1,31 @@
+<?php 
+if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!');
+include_once(PHPWG_ROOT_PATH.'admin/include/tabsheet.class.php');
+
+$template->set_filename('plugin_admin_content', dirname(__FILE__).'/admin.tpl');
+
+if (!isset($_GET['tab']))
+  $page['tab'] = 'config';
+else
+  $page['tab'] = $_GET['tab'];
+
+$my_base_url = get_admin_plugin_menu_link(__FILE__);
+
+$tabsheet = new tabsheet();
+$tabsheet->add( 'config', 'Configuration', add_url_params( $my_base_url, array('tab'=>'config') ) );
+$tabsheet->add( 'edit', 'Edit', add_url_params( $my_base_url, array('tab'=>'edit') ) );
+$tabsheet->add( 'sync', 'Synchronize', add_url_params( $my_base_url, array('tab'=>'sync') ) );
+$tabsheet->select($page['tab']);
+
+$tabsheet->assign();
+
+$my_base_url = $tabsheet->sheets[ $page['tab'] ]['url'];
+$template->set_filename( 'tab_data', dirname(__FILE__).'/admin_'.$page['tab'].'.tpl' );
+include_once( dirname(__FILE__).'/admin_'.$page['tab'].'.php');
+$template->assign_var_from_handle( 'TAB_DATA', 'tab_data');
+$template->assign_var_from_handle( 'ADMIN_CONTENT', 'plugin_admin_content');
+
+if ( empty($conf['gmaps_api_key']) )
+  $page['infos'][] = 'Please define the Google maps api key';
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/admin/admin.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/admin/admin.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/admin/admin.tpl	(revision 4071)
@@ -0,0 +1,5 @@
+<div class="titrePage">
+  <h2>RV Maps & Earth {$TABSHEET_TITLE}</h2>
+</div>
+
+{$TAB_DATA}
Index: /extensions/rv_gmaps/tags/2.0.c/admin/admin_config.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/admin/admin_config.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/admin/admin_config.php	(revision 4071)
@@ -0,0 +1,66 @@
+<?php 
+if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!');
+
+include_once( dirname(__FILE__).'/../include/functions_map.php' );
+
+if ( isset($_POST['submit']) and !is_adviser() )
+{
+  $query = '
+UPDATE '.CONFIG_TABLE.'
+  SET value="'.$_POST['gmaps_api_key'].'"
+  WHERE param="gmaps_api_key"
+  LIMIT 1';
+  pwg_query($query);
+  list($conf['gmaps_api_key']) = array_from_query('SELECT value FROM '.CONFIG_TABLE.' WHERE param="gmaps_api_key"', 'value');
+
+  $gm_config = rvm_get_config();
+
+  $n  = intval($_POST['nb_markers']);
+  if ($n>0)
+    $gm_config['nb_markers'] = $n;
+  else
+    $page['errors'][] = 'The number of markers must be >0';
+
+  $n  = intval($_POST['nb_images_per_marker']);
+  if ($n>1)
+    $gm_config['nb_images_per_marker'] = $n;
+  else
+    $page['errors'][] = 'The number of iamges per marker must be >1';
+
+  $gm_config['marker_icon'] = $_POST['marker_icon'];
+  mkgetdir( dirname(rvm_get_config_file_name()) );
+  $fp = fopen( rvm_get_config_file_name(), 'w');
+  fwrite( $fp, serialize($gm_config) );
+  fclose($fp);
+}
+
+$query = 'SELECT COUNT(*) FROM '.IMAGES_TABLE.' WHERE lat IS NOT NULL';
+list($nb_geotagged) = mysql_fetch_array( pwg_query($query) );
+
+$template->assign(
+    array(
+      'NB_GEOTAGGED' => $nb_geotagged,
+      'GMAPS_API_KEY' => $conf['gmaps_api_key'],
+      'NB_MARKERS' => rvm_get_config_var('nb_markers',40),
+      'NB_IMAGES_PER_MARKER' => rvm_get_config_var('nb_images_per_marker',20),
+    )
+  );
+
+$path=PHPWG_PLUGINS_PATH.$rvm_dir.'/template/markers/';
+$dir_contents = opendir($path);
+while (($filename = readdir($dir_contents)) !== false)
+{
+  if (!is_file($path.'/'.$filename) or get_extension($filename)!='tpl')
+    continue;
+  $filename = get_filename_wo_extension( $filename );
+  $template->append('marker_icons',
+      array(
+        $filename => str_replace( '_', ' ', $filename),
+      ),
+      true
+    );
+}
+$template->assign('selected_marker_icon', rvm_get_config_var('marker_icon', '') );
+
+closedir($dir_contents);
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/admin/admin_config.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/admin/admin_config.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/admin/admin_config.tpl	(revision 4071)
@@ -0,0 +1,55 @@
+
+You have {$NB_GEOTAGGED} geotagged images.
+
+<form method="post" action="" class="properties">
+<fieldset>
+  <legend>Map configuration</legend>
+  <ul>
+  
+  <li>
+  <label>
+    Google Maps API Key:
+    <input type="text" size="48" name="gmaps_api_key" value="{$GMAPS_API_KEY}" />
+  </label>
+    <br/>Signup for the key here: <a href="http://www.google.com/apis/maps/signup.html" target="_blank">http://www.google.com/apis/maps/signup.html</a>.
+  </li>
+  
+  <br/>
+  
+  <li>
+  <label>
+    Maximum number of markers to show:
+    <input type="text" size="3" name="nb_markers" value="{$NB_MARKERS}" />
+  </label>
+    <br/><small>The images will be "clustered" based on this number and the visible region on the map.</small>
+  </li>
+  
+  <br/>
+  
+  <li>
+  <label>
+    Maximum number of images per marker:
+    <input type="text" size="3" name="nb_images_per_marker" value="{$NB_IMAGES_PER_MARKER}" />
+  </label>
+    <br/><small>When a marker is clicked, the user will be able to navigate images in the info window. This data is sent to the browser once for all the markers. If you have many images, reduce this number in order to reduce traffic and speed up data decoding on the browser side.</small>
+  </li>
+
+  <br/>
+
+  <li>
+  <label>
+    Marker image:
+    <select name="marker_icon">
+      <option value="">Default</option>
+      {html_options options=$marker_icons selected=$selected_marker_icon}
+    </select>
+  </label>
+  </li>
+
+  </ul>
+
+  <p>
+    <input class="submit" type="submit" value="{'Submit'|@translate}" name="submit" {$TAG_INPUT_ENABLED}/>
+  </p>
+</fieldset>
+</form>
Index: /extensions/rv_gmaps/tags/2.0.c/admin/admin_edit.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/admin/admin_edit.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/admin/admin_edit.php	(revision 4071)
@@ -0,0 +1,141 @@
+<?php 
+if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!');
+
+$admin_url = PHPWG_ROOT_PATH.'admin.php';
+
+if ( !isset($_GET['cat']) )
+  $_GET['cat'] = 'caddie';
+$_GET['mode'] = 'map';
+include (PHPWG_ROOT_PATH.'admin/element_set.php');
+
+$template->concat('TABSHEET_TITLE', ' '.l10n_dec('%d element', '%d elements', count($page['cat_elements_id'])).' - '.$page['title']);
+
+if ( isset($_POST['submit']) and !is_adviser() )
+{
+  $collection = array();
+
+  switch ($_POST['target'])
+  {
+    case 'all' :
+      $collection = $page['cat_elements_id'];
+      break;
+    case 'selection' :
+      if (!isset($_POST['selection']) or count($_POST['selection']) == 0)
+        array_push($page['errors'], l10n('Select at least one picture'));
+      else
+        $collection = $_POST['selection'];
+      break;
+  }
+  if ( count($collection)>0 )
+  {
+    $lat = trim($_POST['lat']);
+    $lon = trim($_POST['lon']);
+
+    if ( strlen($lat)>0 and strlen($lon)>0 )
+    {
+      if ( (double)$lat<=90 and (double)$lat>=-90
+          and (double)$lon<=180 and (double)$lat>=-180 )
+        $update_query = 'lat='.$lat.', lon='.$lon;
+      else
+        $page['errors'][] = 'Invalid lat or lon value';
+    }
+    elseif ( strlen($lat)==0 and strlen($lon)==0 )
+      $update_query = 'lat=NULL, lon=NULL';
+    else
+      $page['errors'][] = 'Both lat/lon must be empty or not empty';
+
+    if (isset($update_query))
+    {
+      $update_query = '
+UPDATE '.IMAGES_TABLE.' SET '.$update_query.'
+  WHERE id IN ('.implode(',',$collection).')';
+      pwg_query($update_query);
+      rvm_invalidate_cache();
+    }
+  }
+}
+
+
+$template->append(
+  'specials',
+  array(
+		$admin_url.get_query_string_diff(array('start','cat')).'&amp;cat=caddie' => l10n('Caddie')
+    ),
+	true
+  );
+
+$query = '
+SELECT id,name,uppercats,global_rank
+  FROM '.CATEGORIES_TABLE.'
+;';
+$result = pwg_query($query);
+$categories = array();
+$selecteds = array();
+if (!empty($result))
+{
+  while ($row = mysql_fetch_assoc($result))
+  {
+    $url = $admin_url.get_query_string_diff(array('start','cat')).'&amp;cat='.$row['id'];
+    if ( $row['id']==$_GET['cat'] ) $selecteds[] = $url;
+    $row['id']=$url;
+    array_push($categories, $row);
+  }
+}
+usort($categories, 'global_rank_compare');
+display_select_categories($categories, $selecteds, 'categories', false);
+
+if (!empty($_GET['display']))
+{
+  if ('all' == $_GET['display'])
+    $page['nb_images'] = count($page['cat_elements_id']);
+  else
+    $page['nb_images'] = intval($_GET['display']);
+}
+else
+  $page['nb_images'] = 20;
+
+if ( !empty($page['cat_elements_id']) )
+{
+  $nav_bar = create_navigation_bar(
+    $admin_url.get_query_string_diff(array('start')),
+    count($page['cat_elements_id']),
+    $page['start'],
+    $page['nb_images']
+    );
+  $template->assign('NAV_BAR', $nav_bar);
+}
+
+$images=array();
+if ( !empty($page['cat_elements_id']) )
+{
+  $query='
+SELECT id,tn_ext,name,path,file,lat,lon FROM '.IMAGES_TABLE.'
+  WHERE id IN ('.implode(',',$page['cat_elements_id']).')
+  '.$conf['order_by'].'
+  LIMIT '.$page['start'].', '.$page['nb_images'].'
+;';
+  $result = pwg_query($query);
+  while ( $row=mysql_fetch_assoc($result) )
+    $images[] = $row;
+}
+
+foreach ($images as $image)
+{
+	$tpl_var = array_merge( $image,
+      array(
+        'U_TN' => get_thumbnail_url($image),
+        'TITLE' => get_thumbnail_title($image)
+      )
+    );
+  if ( isset($image['lat']) )
+    $tpl_var['U_MAP'] = rvm_make_map_picture_url( array('image_id'=>$image['id'], 'image_file'=>$image['file']) );
+	$template->append('thumbnails', $tpl_var);
+}
+
+$template->assign(
+    array(
+      'U_DISPLAY'=> $admin_url.get_query_string_diff(array('display'))
+    ) 
+  );
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/admin/admin_edit.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/admin/admin_edit.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/admin/admin_edit.tpl	(revision 4071)
@@ -0,0 +1,74 @@
+
+<form>
+Set:
+<select onchange="document.location = this.options[this.selectedIndex].value;">
+  <optgroup label="Specials">
+    {html_options options=$specials}
+  </optgroup>
+  <optgroup label="{'Categories'|@translate}">
+  	{html_options options=$categories selected=$categories_selected}
+  </optgroup>
+  <!-- <optgroup label="{'Tags'|@translate}"></optgroup> -->
+</select>
+</form>
+
+<fieldset>
+  <legend>{'Display options'|@translate}</legend>
+  <p>{'elements per page'|@translate}:
+      <a href="{$U_DISPLAY}&amp;display=20">20</a>
+    | <a href="{$U_DISPLAY}&amp;display=50">50</a>
+    | <a href="{$U_DISPLAY}&amp;display=100">100</a>
+    | <a href="{$U_DISPLAY}&amp;display=all">{'all'|@translate}</a>
+  </p>
+</fieldset>
+
+<form action="" method="post">
+
+<fieldset>
+	<legend>{'Elements'|@translate}</legend>
+
+	<div class="navigationBar">{$NAV_BAR}</div>
+  {if not empty($thumbnails)}
+	<ul class="thumbnails">
+		{foreach from=$thumbnails item=thumbnail}
+		<li><span class="wrap1">
+			<label>
+				<span class="wrap2"><span>
+					<img src="{$thumbnail.U_TN}" alt="{$thumbnail.file}" title="{$thumbnail.TITLE}" class="thumbnail" />
+				</span></span>
+				<input type="checkbox" name="selection[]" value="{$thumbnail.id}" />
+			</label>
+
+			<span class="thumbLegend">
+			{if isset($thumbnail.U_MAP)}
+			<a href="{$thumbnail.U_MAP}" target="_blank">{$thumbnail.lat},{$thumbnail.lon}</a>
+			{/if}
+			</span>
+
+			</span>
+		</li>
+		{/foreach}
+	</ul>
+	{/if}
+</fieldset>
+
+
+<fieldset>
+  <legend>{'Form'|@translate}</legend>
+
+  <label>Latitude (-90=S to 90=N)
+    <input type="text" size="8" name="lat" />
+  </label>
+  <label>Longitude (-180=E to 180=W)
+    <input type="text" size="9" name="lon" />
+  </label>
+  <p>
+    {'target'|@translate}
+    <label><input type="radio" name="target" value="all" /> {'all'|@translate}</label>
+    <label><input type="radio" name="target" value="selection" checked="checked" /> {'selection'|@translate}</label>
+  </p>
+
+  <p><input class="submit" type="submit" value="{'Submit'|@translate}" name="submit" {$TAG_INPUT_ENABLED}/></p>
+</fieldset>
+
+</form>
Index: /extensions/rv_gmaps/tags/2.0.c/admin/admin_sync.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/admin/admin_sync.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/admin/admin_sync.php	(revision 4071)
@@ -0,0 +1,141 @@
+<?php
+if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!');
+
+function parse_fract( $f )
+{
+  $nd = explode( '/', $f );
+  return $nd[0]/$nd[1];
+}
+
+function parse_lat_lon( $arr )
+{
+  $v=0;
+  $v += parse_fract( $arr[0] );
+  $v += parse_fract( $arr[1] )/60;
+  $v += parse_fract( $arr[2] )/3600;
+  return $v;
+}
+
+if ( isset($_POST['submit']) )
+  $sync_options = array(
+//    'no_overwrite' => isset($_POST['no_overwrite']),
+    'simulate' => is_adviser() ? true : isset($_POST['simulate']),
+    'cat_id' => isset($_POST['cat_id']) ? (int)$_POST['cat_id'] : 0,
+    'subcats_included' => isset($_POST['subcats_included']),
+    );
+else
+  $sync_options = array(
+//    'no_overwrite' => true,
+    'simulate' => true,
+    'cat_id' => 0,
+    'subcats_included' => true,
+    );
+
+
+if ( isset($_POST['submit']) )
+{
+  $where_clauses = array();
+  if ( $sync_options['cat_id']!=0 )
+  {
+    $query='
+SELECT id FROM '.CATEGORIES_TABLE.'
+  WHERE ';
+    if ( $sync_options['subcats_included'])
+      $query .= 'uppercats REGEXP \'(^|,)'.$sync_options['cat_id'].'(,|$)\'';
+    else
+      $query .= 'id='.$sync_options['cat_id'];
+    $cat_ids = array_from_query($query, 'id');
+
+    $query='
+SELECT id,path,lat,lon
+  FROM '.IMAGES_TABLE.' INNER JOIN '.IMAGE_CATEGORY_TABLE.' ON id=image_id
+  WHERE category_id IN ('.implode(',', $cat_ids).')
+  GROUP BY id';
+  }
+  else
+  {
+    $query='
+SELECT id,path,lat,lon
+  FROM '.IMAGES_TABLE;
+  }
+
+  $images = hash_from_query( $query, 'id');
+  $datas = array();
+  $errors = array();
+  foreach ($images as $image)
+  {
+    $exif = @read_exif_data( $image['path'] );
+    if ( empty($exif) )
+      continue;
+    $exif = array_intersect_key( $exif, array_flip( array('GPSLatitudeRef', 'GPSLatitude', 'GPSLongitudeRef', 'GPSLongitude') ) );
+    if ( count($exif)!=4 )
+      continue;
+    if ( !in_array($exif['GPSLatitudeRef'], array('S', 'N') ) )
+    {
+      $errors[] = $image['path']. ': GPSLatitudeRef not S or N';
+      continue;
+    }
+    if ( !in_array($exif['GPSLongitudeRef'], array('W', 'E') ) )
+    {
+      $errors[] = $image['path']. ': GPSLongitudeRef not W or E';
+      continue;
+    }
+    if (!is_array($exif['GPSLatitude']) or !is_array($exif['GPSLongitude']) )
+    {
+      $errors[] = $image['path']. ': GPSLatitude and GPSLongitude are not arrays';
+      continue;
+    }
+    $lat = parse_lat_lon( $exif['GPSLatitude'] );
+    if ( $exif['GPSLatitudeRef']=='S' )
+      $lat = -$lat;
+    $lon = parse_lat_lon( $exif['GPSLongitude'] );
+    if ( $exif['GPSLongitudeRef']=='W' )
+      $lon = -$lon;
+    $datas[] = array (
+      'id' => $image['id'],
+      'lat' => $lat,
+      'lon' => $lon,
+      );
+  }
+  $template->assign( 'sync_errors', $errors );
+
+  if ( count($datas)>0 and !$sync_options['simulate'] )
+  {
+    mass_updates(
+        IMAGES_TABLE,
+        array(
+          'primary' => array('id'),
+          'update'  => array('lat', 'lon')
+        ),
+        $datas
+      );
+    rvm_invalidate_cache();
+  }
+
+  $template->assign(
+    'metadata_result',
+    array(
+      'NB_ELEMENTS_DONE' => count($datas),
+      'NB_ELEMENTS_CANDIDATES' => count($images),
+      'NB_ERRORS' => count($errors),
+      ));
+
+}
+
+
+$query = '
+SELECT id,
+  CONCAT(name, IF(dir IS NULL, " (V)", "") ) AS name,
+  uppercats, global_rank
+  FROM '.CATEGORIES_TABLE;
+display_select_cat_wrapper($query,
+                           array( $sync_options['cat_id'] ),
+                           'categories',
+                           false);
+$template->assign(
+    array(
+      'SUBCATS_INCLUDED_CHECKED' => $sync_options['subcats_included'] ? 'checked="checked"' : ''
+    )
+  );
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/admin/admin_sync.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/admin/admin_sync.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/admin/admin_sync.tpl	(revision 4071)
@@ -0,0 +1,48 @@
+<p>Synchronization of EXIF gps metadata information with the database.</p>
+
+{if isset($metadata_result)}
+<h3>{'update_result_metadata'|@translate}</h3>
+<ul>
+	<li>{$metadata_result.NB_ELEMENTS_DONE} {'update_nb_elements_metadata_sync'|@translate}</li>
+	<li>{$metadata_result.NB_ELEMENTS_CANDIDATES} {'update_nb_elements_metadata_available'|@translate}</li>
+	<li>{$metadata_result.NB_ERRORS} {'update_nb_errors'|@translate}</li>
+</ul>
+{/if}
+
+{if not empty($sync_errors)}
+<h3>{'update_error_list_title'|@translate}</h3>
+<div class="errors">
+<ul>
+  {foreach from=$sync_error item=error}
+	<li>{$error}</li>
+	{/foreach}
+</ul>
+</div>
+{/if}
+
+<form action="" method="post" id="update">
+
+  <fieldset id="syncOptions">
+    <legend>synchronization options</legend>
+    <ul>
+      <li><label><input type="checkbox" name="simulate" value="1" checked="checked" {$TAG_INPUT_ENABLED} /> {'update_simulate'|@translate}</label></li>
+    </ul>
+  </fieldset>
+
+  <fieldset id="catSubset">
+    <legend>{'update_cats_subset'|@translate}</legend>
+    <ul>
+    <li>
+    <select class="categoryList" name="cat_id" size="10">
+    	{html_options options=$categories selected=$categories_selected}
+    </select>
+    </li>
+
+    <li><label><input type="checkbox" name="subcats_included" value="1" {$SUBCATS_INCLUDED_CHECKED} /> {'search_subcats_included'|@translate}</label></li>
+    </ul>
+  </fieldset>
+
+  <p>
+    <input class="submit" type="submit" value="{'Submit'|@translate}" name="submit" />
+  </p>
+</form>
Index: /extensions/rv_gmaps/tags/2.0.c/changelog.txt
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/changelog.txt	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/changelog.txt	(revision 4071)
@@ -0,0 +1,65 @@
+2.0.c
+  - google maps api is loaded using the same language as piwigo
+  - polish and italian languages thanks to external contributors
+
+2.0.b
+  - remove some unused/unnecessary php code
+  - added Czech translation (thanks to Pavel B.)
+  - improve compatibility of exif gps synchronisation for images uploaded by pLoader
+
+2.0.a
+  - new PWG version compatible
+  - fix issue on the map page when left side longitude > right side longitude
+  
+1.7.h
+  - fix issue when zlib.output_compression was set in php.ini
+  - update prototype javascript to version 1.6.0.2
+  - better navigation in map list (categories & tags)
+  - compatible with pwg 1.7.2 (issue on searches with previous version)
+
+1.7.g
+  - bug - map on recent pictures had a mysql error
+  - bug - Google api ~ v2.93 the overview map (bottom right side) has changed behavior resulting in an exception
+  - added new physical map type
+  - replaced the old map type control with the new hierarchical type
+
+1.7.f
+  - localhost do not require a google api key
+  - updated calls to new version of google maps apis
+  - improved click/double click behaviour on markers
+  - update prototype javascript to version 1.5.1.1
+  - fixed small bug in javascript when recycling markers (current thumbnail index reset now)
+  
+1.7.e
+  - include french language (thanks dmaille)
+  - the marker title was not correctly set when marker recycled (used undocumented code that changed with api version); now we still use undocumented features but we "discover" the right function names at runtime
+  - output map data using the zip compression (if available)
+  - removed a sleep in the code that was used for debugging
+  
+1.7.d
+  - added some rel nofollow and meta robots noindex,nofollow
+  - attempt to remove a php warning from the admin config page on some ISPs
+
+1.7.c
+  - the map page opened from the picture page is not on google anymore
+  - the picture map page has a link "More pictures near this location"
+  - allow custom markers instead of the default one
+  - on startup, the map page is automatically centered and zoomed to the images (all cases except when viewing the entire gallery or when an url generated by Link to this page is used)
+  - improved link to this page for different map types
+  - security: checks for guests for galleries that do not allow visits as guest
+  - security: checks for adivser administrators
+  - javascript improvements to avoid memory leaks in IE
+
+1.7.b
+ - added an input to search for an address/city/country on the map page
+ - double click on a marker zooms to that location
+ - the info window automatically shows a vertical scroll bar if required
+ - performance: ability to limit the number of images shown in the info window per marker
+ - performance: do not destroy/create the markers in the browser, but reuse existing ones
+ - bug fix and improvement of the clustering algorithm
+ - added .kml extension to the file name when opening the kml file
+ - fix: kml from map page is always in flat mode (categories not structured)
+ - fix: kml from map page takes into account the current map viewport
+
+1.7.a
+ - initial release
Index: /extensions/rv_gmaps/tags/2.0.c/include/cluster_maker.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/include/cluster_maker.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/include/cluster_maker.php	(revision 4071)
@@ -0,0 +1,185 @@
+<?php
+
+function cluster_area_compare( &$a, &$b )
+{
+	$aa = bounds_lat_range($a['bounds']) * bounds_lon_range($a['bounds']);
+	$ab = bounds_lat_range($b['bounds']) * bounds_lon_range($b['bounds']);
+	return -$aa+$ab; // biggest first
+}
+
+class ClusterMaker
+{
+	private $_image_map;
+	private $_image_ranks;
+
+	var $bounds;
+	var $debug_str;
+
+	function make_clusters($images, $maxLatPrecision, $maxLonPrecision, $maxNbMarkers)
+	{
+		$this->_image_map = $images;
+		$this->_image_ranks = array();
+		$this->debug_str = '';
+
+		$start = get_moment();
+		$total_iterations = 0;
+		$total_generations = 0;
+
+		$this->bounds = array();
+		foreach($images as $img)
+			$this->bounds = bounds_add($this->bounds, $img['lat'], $img['lon'] );
+
+		$pending_split = array(
+				array(
+					'bounds' => $this->bounds,
+					'items' => array_keys( $this->_image_map ),
+				)
+			);
+		$result = array();
+
+		while ( count($pending_split) )
+		{
+			$total_generations++;
+			$next_level_to_split = array();
+			while ( count($pending_split) )
+			{
+				$current = array_shift( $pending_split );
+				$splitted = $this->_split_cluster($current, $maxLatPrecision, $maxLonPrecision );
+				if ( count($splitted)>1 )
+				{
+					/*echo('splitted: ' . var_export($current['bounds'],true). ' in '. count($splitted)."\n" );
+					foreach( $splitted as $k => $split )
+						echo("sub $k: " . var_export($split['bounds'],true)."\n" );*/
+					$total_iterations += count($current['items']);
+					$next_level_to_split = array_merge($next_level_to_split, $splitted );
+					if ( count($result)+count($pending_split)+count($next_level_to_split) >= $maxNbMarkers )
+					{
+						$result = array_merge($result, $pending_split, $next_level_to_split );
+						$pending_split = $next_level_to_split = array();
+						break;
+					}
+				}
+				else
+					array_push( $result, $current );
+			}
+			$pending_split = $next_level_to_split;
+			usort( $pending_split, 'cluster_area_compare' );
+		}
+		$merged = $this->_try_merge_clusters( $result, $maxLatPrecision, $maxLonPrecision );
+		$this->debug_str = get_elapsed_time($start, get_moment()) .' gen:'.$total_generations.' alg:'.count($this->_image_map).'+'.$total_iterations;
+		if ($merged)
+			$this->debug_str .= " merged:$merged";
+		$this->_image_map = null;
+		return $result;
+	}
+
+	function _try_merge_clusters(&$clusters, $maxLatPrecision, $maxLonPrecision)
+	{
+		$ret = 0;
+		for ($i=0; $i<count($clusters)-1; $i++)
+		{
+			$ci = bounds_center( $clusters[$i]['bounds'] );
+			for ($j=$i+1; $j<count($clusters); $j++)
+			{
+				$cj = bounds_center( $clusters[$j]['bounds'] );
+				if ( abs($ci['lat']-$cj['lat']) < $maxLatPrecision/2
+					and abs($ci['lon']-$cj['lon']) < $maxLonPrecision/2 )
+				{
+					//print_r( "i=$i; j=$j \n"); var_export( $clusters[$i]['bounds'] ); var_export( $clusters[$j]['bounds'] );
+					$clusters[$i]['items'] = array_merge( $clusters[$i]['items'], $clusters[$j]['items'] );
+					if ( empty($this->_image_ranks) )
+						$this->_image_ranks = array_flip( array_keys($this->_image_map) );
+					usort( $clusters[$i]['items'], array($this, '_image_rank_compare') );
+					$clusters[$i]['bounds'] = bounds_union($clusters[$i]['bounds'], $clusters[$j]['bounds'] );
+					array_splice( $clusters, $j, 1);
+					$j--;
+					$ci = bounds_center( $clusters[$i]['bounds'] );
+					$ret++;
+				}
+			}
+		}
+		return $ret;
+	}
+
+	function _image_rank_compare($a, $b)
+	{
+		return $this->_image_ranks[$a] - $this->_image_ranks[$b];
+	}
+
+	function _split_cluster($cluster, $maxLatPrecision, $maxLonPrecision)
+	{
+		$latRange = bounds_lat_range($cluster['bounds']);
+		$lonRange = bounds_lon_range($cluster['bounds']);
+
+		$lat_nb_tiles = max( 1, $latRange/$maxLatPrecision );
+		$lon_nb_tiles = max( 1, $lonRange/$maxLonPrecision );
+		//echo('lat_nb '.$lat_nb_tiles.' lon_nb '.$lon_nb_tiles."\n");
+		if ($lat_nb_tiles<2 and $lon_nb_tiles<2)
+			return array();
+
+		$ll_tile_factor = $lat_nb_tiles/$lon_nb_tiles;
+
+		if ($ll_tile_factor > 3)
+		{ // 1x3
+			$lat_step = $latRange/3;
+			$lon_step = $lonRange;
+		}
+		elseif ($ll_tile_factor < 1/3)
+		{ // 3x1
+			$lat_step = $latRange;
+			$lon_step = $lonRange/3;
+		}
+		elseif ($ll_tile_factor > 2)
+		{ // 2x3
+			$lat_step = max( $latRange/3, $maxLatPrecision );
+			$lon_step = max( $lonRange/2, $maxLonPrecision );
+		}
+		elseif ($ll_tile_factor < 1/2)
+		{ // 3x2
+			$lat_step = max( $latRange/2, $maxLatPrecision );
+			$lon_step = max( $lonRange/3, $maxLonPrecision );
+		}
+		else
+		{ // 3x3
+			$lat_step = max( $latRange/3, $maxLatPrecision );
+			$lon_step = max( $lonRange/3, $maxLonPrecision );
+		}
+
+		if ( $cluster['bounds']['count'] > 200 )
+		{
+			if ($lat_step>4*$maxLatPrecision) $lat_step = $lat_step/2;
+			if ($lon_step>4*$maxLonPrecision) $lon_step = $lon_step/2;
+		}
+
+		$lat_step += 1e-7;
+		$lon_step += 1e-7;
+		//echo ( "$lat_step $latRange x $lon_step $lonRange tiles $lat_nb_tiles x $lon_nb_tiles\n" );
+
+		$lon_nb_tiles = ceil( $lonRange / $lon_step );
+
+		$clusters = array();
+		foreach ( $cluster['items'] as $id )
+		{
+			$lon = $this->_image_map[$id]['lon'];
+			$lat = $this->_image_map[$id]['lat'];
+
+			$idx_lon = floor ( ( $lon - $cluster['bounds']['w'] ) / $lon_step );
+			$idx_lat = floor ( ( $lat - $cluster['bounds']['s'] ) / $lat_step );
+
+			$idx = $lon_nb_tiles * $idx_lat + $idx_lon;
+
+			if ( !isset($clusters[$idx]) )
+			{
+				$clusters[$idx] = array(
+					'bounds'  => array(),
+					'items'   => array(),
+					);
+			}
+			array_push( $clusters[$idx]['items'], $id );
+			$clusters[$idx]['bounds'] = bounds_add( $clusters[$idx]['bounds'] , $lat, $lon );
+		}
+		return $clusters;
+	}
+}
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/include/functions.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/include/functions.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/include/functions.php	(revision 4071)
@@ -0,0 +1,106 @@
+<?php
+
+function rvm_get_cache_file_name()
+{
+	global $conf;
+	$dir = $conf['local_data_dir'];
+	return $dir.'/tmp/_rvgm_cat_cache.dat';
+}
+
+function rvm_invalidate_cache()
+{
+	@unlink(  rvm_get_cache_file_name() );
+}
+
+function rvm_load_language()
+{
+	global $lang,$lang_info,$conf;
+	if ( isset($lang['Map']) or ($lang_info['code']=='en' and !$conf['debug_l10n']) )
+		return;
+	load_language('lang', dirname(__FILE__).'/../');
+}
+
+function rvm_items_have_latlon($items)
+{
+  $query = '
+SELECT id FROM '.IMAGES_TABLE.'
+WHERE lat IS NOT NULL
+  AND id IN ('.implode(',', $items).')
+ORDER BY NULL
+LIMIT 0,1';
+	if ( mysql_num_rows(pwg_query($query))> 0)
+		return true;
+	return false;
+}
+
+function rvm_make_map_picture_url($params)
+{
+	global $conf;
+	if ( empty($conf['gmaps_api_key']) and $_SERVER['SERVER_ADDR']!='127.0.0.1' )
+		return "";
+	$map_url = make_picture_url($params);
+	return add_url_params($map_url, array('map'=>null) );
+}
+
+function rvm_duplicate_map_picture_url()
+{
+	global $conf;
+	if ( empty($conf['gmaps_api_key']) and $_SERVER['SERVER_ADDR']!='127.0.0.1' )
+		return "";
+	$map_url = duplicate_picture_url();
+	return add_url_params($map_url, array('map'=>null) );
+}
+
+function rvm_make_map_index_url($params=array())
+{
+	global $conf, $rvm_dir;
+	if ( empty($conf['gmaps_api_key']) and $_SERVER['SERVER_ADDR']!='127.0.0.1' )
+		return "";
+	$url = get_root_url().'map';
+	if ($conf['php_extension_in_urls'])
+		$url .= '.php';
+	if ($conf['question_mark_in_urls'])
+		$url .= '?';
+	$url .= make_section_in_url($params);
+	$url = add_well_known_params_in_url($url, array_intersect_key($params, array('flat'=>1) ) );
+	return $url;
+}
+
+function rvm_duplicate_map_index_url($redefined=array(), $removed=array())
+{
+	return rvm_make_map_index_url(
+		params_for_duplication($redefined, $removed)
+		);
+}
+
+function rvm_duplicate_kml_index_url($redefined=array(), $removed=array())
+{
+	return rvm_make_kml_index_url(
+		params_for_duplication($redefined, $removed)
+		);
+}
+
+function rvm_make_kml_index_url($params)
+{
+	global $conf, $rvm_dir;
+	$url = get_root_url().'plugins/'.$rvm_dir.'/kml.php';
+	if ($conf['question_mark_in_urls'])
+		$url .= '?';
+
+	$url .= make_section_in_url($params);
+	unset( $params['start'] );
+	if ( 'categories'!=$params['section']) unset( $params['flat'] );
+	$url = add_well_known_params_in_url($url, $params);
+	$get_params = array();
+	if ( isset($params['box']) and !empty($params['box']) )
+	{
+		include_once( dirname(__FILE__).'/functions_map.php' );
+		if ( ! bounds_is_world($params['box']) )
+			$get_params['box'] = bounds_to_url($params['box']);
+	}
+	if ( isset($params['ll']) and !empty($params['ll']) )
+		$get_params['ll'] = $params['ll']['lat'].','.$params['ll']['lon'];
+	$url = add_url_params($url, $get_params );
+	return $url;
+}
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/include/functions_map.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/include/functions_map.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/include/functions_map.php	(revision 4071)
@@ -0,0 +1,566 @@
+<?php
+/* Reminder about urls
+map      unused but passes flat to map_data ...
+map_data automatic show flat, start is unused
+mapl     automatic show flat, start is used in GET only (blowup)
+kml      if flat network links, start is unused*/
+
+function rvm_get_config_file_name()
+{
+  global $page, $conf;
+  unset( $page['__rvm_config__'] );
+  return $conf['local_data_dir'].'/plugins/'.basename(dirname(dirname(__FILE__))).'.dat';
+}
+
+function rvm_get_config()
+{
+  global $page;
+  if (isset($page['__rvm_config__']))
+    return $page['__rvm_config__'];
+  $x = @file_get_contents( rvm_get_config_file_name() );
+  if ($x!==false)
+    $page['__rvm_config__'] = unserialize($x);
+  if ( !is_array(@$page['__rvm_config__']) )
+    $page['__rvm_config__'] = array();
+  return $page['__rvm_config__'];
+}
+
+function rvm_get_config_var($var, $default)
+{
+  global $page;
+  if (!isset($page['__rvm_config__']))
+    rvm_get_config();
+  if ( array_key_exists($var,$page['__rvm_config__']) )
+    return $page['__rvm_config__'][$var];
+  return $default;
+}
+
+function rvm_parse_map_data_url($tokens, &$next_token)
+{
+  $page=array();
+
+  $page = array_merge($page, parse_section_url($tokens, $next_token) );
+  if ( !isset($page['section']) )
+    $page['section'] = 'categories';
+
+  $page = array_merge( $page, parse_well_known_params_url( $tokens, $next_token) );
+  $page['start']=0;
+  $page['box'] = rvm_bounds_from_url( @$_GET['box'] );
+  return $page;
+}
+
+function rvm_parse_kml_url($tokens, &$next_token)
+{
+  $page=array();
+
+  $page = array_merge($page, parse_section_url($tokens, $next_token) );
+  if ( !isset($page['section']) )
+    $page['section'] = 'categories';
+
+  $page = array_merge( $page, parse_well_known_params_url( $tokens, $next_token) );
+  $page['start']=0;
+  $page['box'] = rvm_bounds_from_url( @$_GET['box'] );
+  if ( isset($_GET['ll']) )
+    $page['ll'] = rvm_ll_from_url( $_GET['ll'] );
+  return $page;
+}
+
+function rvm_duplicate_blowup_url($redefined=array(), $removed=array())
+{
+  return rvm_make_blowup_url(
+    params_for_duplication($redefined, $removed)
+    );
+}
+
+function rvm_make_blowup_url($params)
+{
+  global $conf, $rvm_dir;
+  if ( file_exists(PHPWG_ROOT_PATH.'mapl.php') )
+    $url = get_root_url().'mapl';
+  else
+    $url = get_root_url().'plugins/'.$rvm_dir.'/mapl';
+  if ($conf['php_extension_in_urls'])
+    $url .= '.php';
+  if ($conf['question_mark_in_urls'])
+    $url .= '?';
+  $url .= make_section_in_url($params);
+  $url = add_well_known_params_in_url($url, array_intersect_key($params, array('flat'=>1) ) );
+
+  $get_params = array();
+  if ( isset($params['box']) and !empty($params['box']) and !bounds_is_world($params['box']) )
+    $get_params['box'] = bounds_to_url($params['box']);
+  elseif ( isset($params['ll']) and !empty($params['ll']) )
+    $get_params['ll'] = $params['ll']['lat'].','.$params['ll']['lon'];
+  if ( isset($params['start']) and $params['start']>0 )
+    $get_params['start'] = $params['start'];
+  $url = add_url_params($url, $get_params );
+  return $url;
+}
+
+function rvm_parse_blowup_url($tokens, &$next_token)
+{
+  $page=array();
+  $page = array_merge($page, parse_section_url($tokens, $next_token) );
+  if ( !isset($page['section']) )
+    $page['section'] = 'categories';
+  $page = array_merge( $page, parse_well_known_params_url( $tokens, $next_token) );
+  $page['start']=0;
+  if ( isset($_GET['start']) )
+    $page['start']=$_GET['start'];
+  $page['box'] = rvm_bounds_from_url( @$_GET['box'] );
+  if ( isset($_GET['ll']) )
+    $page['ll'] = rvm_ll_from_url( $_GET['ll'] );
+  return $page;
+}
+
+function rvm_bounds_from_url($str)
+{
+  if ( !isset($str) or strlen($str)==0 )
+    return null;
+  $r = explode(',', $str );
+  if ( count($r) != 4)
+    bad_request( $str.' is not a valid geographical bound' );
+  $b = array(
+      's' => $r[0],
+      'w' => $r[1],
+      'n' => $r[2],
+      'e' => $r[3],
+    );
+  return $b;
+}
+
+function rvm_bounds_to_sql( $b )
+{
+  if ( !isset($b) or empty($b) )
+    return null;
+  $sql_where = 'i.lat BETWEEN '.$b['s'].' AND '.($b['n']);
+  $sql_where .= ' AND ';
+  if ($b['e'] >= $b['w'])
+    $sql_where .= 'i.lon BETWEEN '.$b['w'].' AND '.($b['e']);
+  else
+    $sql_where .= 'i.lon NOT BETWEEN '.($b['e']+1e-7).' AND '.($b['w']-1e-7);
+  return $sql_where;
+}
+
+function bounds_to_url($b, $p = 6 )
+{
+  if ( empty($b) )
+    return '';
+  return round($b['s'],$p).','.round($b['w'],$p).','.round($b['n'],$p).','.round($b['e'],$p);
+}
+
+function rvm_ll_from_url($str)
+{
+  if ( !isset($str) or strlen($str)==0 )
+    return null;
+  $r = explode(',', $str );
+  if ( count($r) != 2)
+    bad_request( $str.' is not a valid geographical position' );
+  $b = array('lat'=>$r[0],'lon'=>$r[1]);
+  return $b;
+}
+
+function rvm_ll_to_sql( $ll, &$order_by )
+{
+  $cos_lat = max( 1e-2, cos($ll['lat']*M_PI/180) );
+  $dlat = 3; // 1 degree is approx between 111 and 116 km
+  $dlon = min( 3/$cos_lat, 180 );
+  $bounds = array(
+      's' => $ll['lat'] - $dlat,
+      'w' => $ll['lon'] - $dlon,
+      'n' => $ll['lat'] + $dlat,
+      'e' => $ll['lon'] + $dlon,
+    );
+  if ($bounds['s']<-90) $bounds['s']=-90;
+  if ($bounds['w']<-180) $bounds['w']+=360;
+  if ($bounds['n']>90) $bounds['n']=90;
+  if ($bounds['e']>180) $bounds['e']-=360;
+  $where_sql = rvm_bounds_to_sql( $bounds );
+  $order_by = 'ORDER BY POW('.$ll['lat'].'-i.lat,2)+POW('.$cos_lat.'*('.$ll['lon'].'-i.lon),2)';
+  return $where_sql;
+}
+
+
+function bounds_is_world( $b )
+{
+  if (empty($b)) return false;
+  return $b['n']>=90 and $b['s']<=-90 and $b['w']<=-180 and $b['e']>=180;
+}
+
+function bounds_add($bounds, $lat, $lon)
+{
+  if ( empty($bounds) )
+  {
+    $bounds = array(
+      's' => $lat,
+      'n' => $lat,
+      'w' => $lon,
+      'e' => $lon,
+      'count' => 1
+      );
+  }
+  else
+  {
+    if ( $lat>$bounds['n'] )
+      $bounds['n']=$lat;
+    elseif ( $lat<$bounds['s'] )
+      $bounds['s']=$lat;
+    if ( $lon>$bounds['e'] )
+      $bounds['e']=$lon;
+    elseif ( $lon<$bounds['w'] )
+      $bounds['w']=$lon;
+    $bounds['count']=$bounds['count']+1;
+  }
+
+  return $bounds;
+}
+
+function bounds_union($b1, $b2)
+{
+  $total_count = $b1['count'] + $b2['count'];
+  $res =
+    array_merge(
+      $b1,
+      array(
+        's' => min( $b1['s'], $b2['s'] ),
+        'n' => max( $b1['n'], $b2['n'] ),
+        'w' => min( $b1['w'], $b2['w'] ),
+        'e' => max( $b1['e'], $b2['e'] ),
+        'count' => $total_count,
+      )
+    );
+  if ( isset($b2['min_date']) )
+    $res = array_merge(
+      $res,
+      array(
+        'min_date' => min( $b1['min_date'], $b2['max_date'] ),
+        'max_date' => max( $b1['max_date'], $b2['max_date'] ),
+      )
+    );
+  return $res;
+}
+
+function bounds_center($b)
+{
+  if (empty($b))
+    return array();
+  return array(
+    'lat' => ($b['s']+$b['n'])/2,
+    'lon' => ($b['w']+$b['e'])/2,
+    );
+}
+
+function bounds_sw($b)
+{
+  if (empty($b))
+    return array();
+  return array(
+    'lat' => $b['s'],
+    'lon' => $b['w'],
+    );
+}
+
+function bounds_ne($b)
+{
+  if (empty($b))
+    return array();
+  return array(
+    'lat' => $b['n'],
+    'lon' => $b['e'],
+    );
+}
+
+
+function bounds_lat_range($b)
+{
+  if (empty($b))
+    return 0;
+  return $b['n'] - $b['s'];
+}
+
+function bounds_lon_range($b)
+{
+  if (empty($b))
+    return 0;
+  return $b['e'] - $b['w'];
+}
+
+
+function rvm_get_cat_bounds()
+{
+  $cache_file_name = rvm_get_cache_file_name();
+  if ( file_exists($cache_file_name) )
+  {
+    return unserialize( file_get_contents($cache_file_name) );
+  }
+  $forbidden = get_sql_condition_FandF(
+        array
+          (
+            'forbidden_categories' => 'category_id',
+            'visible_categories' => 'category_id',
+            'visible_images' => 'i.id'
+          ),
+        'AND'
+    );
+
+  $query = '
+SELECT category_id cat_id, uppercats, MAX(lat) AS n, MIN(lat) AS s, MAX(lon) AS e, MIN(lon) AS w, COUNT(i.id) count, MIN(i.date_creation) min_date, MAX(i.date_creation) max_date
+  FROM
+    '.CATEGORIES_TABLE.' as c
+      INNER JOIN
+    '.IMAGE_CATEGORY_TABLE.' ON category_id = c.id
+      INNER JOIN
+    '.IMAGES_TABLE.' i ON image_id=i.id
+  WHERE lat IS NOT NULL '.$forbidden.'
+  GROUP BY category_id
+;';
+
+  $result = pwg_query($query);
+  $uppercats_list = array();
+  $cat_bounds = array();
+  while ($row = mysql_fetch_assoc($result))
+  {
+    array_push($uppercats_list, $row['uppercats']);
+    $cat_id = $row['cat_id'];
+    unset( $row['cat_id'], $row['uppercats'] );
+    $cat_bounds[ $cat_id ] = $row;
+    $cat_bounds[ $cat_id ]['self'] = $row;
+  }
+  natsort($uppercats_list);
+  //echo "<pre>" . var_export($uppercats_list,true);
+
+  foreach ( array_reverse($uppercats_list) as $uppercats)
+  {
+    $cat_ids = explode(',', $uppercats);
+    $bounds = $cat_bounds[ $cat_ids[count($cat_ids)-1] ];
+    for ($i=count($cat_ids)-2; $i>=0; $i--)
+    {
+      $this_bounds = @$cat_bounds[ $cat_ids[$i] ];
+      if ( !isset($this_bounds) )
+      {
+        $this_bounds = $bounds;
+        unset($this_bounds['self']);
+      }
+      else
+      {
+//        if ($cat_ids[$i]==43) echo "<pre>Before\n" . var_export($this_bounds,true);
+//        if ($cat_ids[$i]==43) echo "<pre>Union\n" . var_export($bounds,true);
+        $this_bounds = bounds_union($this_bounds, $bounds);
+      }
+      $this_bounds['nb_cats'] = 1 + @$this_bounds['nb_cats'];
+//      if ($cat_ids[$i]==43) echo "<pre>" . var_export($this_bounds,true);
+      $cat_bounds[ $cat_ids[$i] ] = $this_bounds;
+    }
+  }
+  //echo "<pre>" . var_export($cat_bounds,true);
+	mkgetdir( dirname($cache_file_name) );
+  $file = fopen($cache_file_name , 'w' );
+  fwrite($file, serialize($cat_bounds) );
+  fclose( $file );
+
+  return $cat_bounds;
+}
+
+
+function marray_from_query($query)
+{
+  $ret = array();
+  $result = pwg_query($query);
+  while ($row = mysql_fetch_assoc($result))
+    $ret[] = $row;
+  return $ret;
+}
+
+define('RVM_BUILD_ARRAY',     0);
+define('RVM_BUILD_HASH',      1);
+define('RVM_BUILD_AGGREGATE', 2);
+
+function rvm_build_section_items($img_fields, $where_sql, $mode, $order_by=null)
+{
+  global $page, $conf, $user;
+
+  $page['items'] = array();
+
+  if (empty($where_sql))
+    $where_sql .= 'i.lat IS NOT NULL';
+  $where_sql_images_only = $where_sql;
+  $where_sql .= get_sql_condition_FandF(
+      array
+        (
+          'forbidden_categories' => 'category_id',
+          'visible_categories' => 'category_id',
+          'visible_images' => 'i.id'
+        ),
+      "\n  AND"
+  );
+
+  switch ($mode)
+  {
+    case RVM_BUILD_ARRAY:
+      $func = 'marray_from_query';
+      $group_by = '
+  GROUP BY i.id';
+      break;
+    case RVM_BUILD_HASH:
+      $func = create_function( '$q', 'return hash_from_query($q, "id");' );
+      $group_by = '
+  GROUP BY i.id';
+      break;
+    case RVM_BUILD_AGGREGATE:
+      $func = 'marray_from_query';
+      $group_by = '';
+      break;
+  }
+
+  if ($mode != RVM_BUILD_AGGREGATE )
+  {
+    if ($order_by==null and pwg_get_session_var('image_order',0) > 0)
+    {
+      $orders = get_category_preferred_image_orders();
+
+      $conf['order_by'] = str_replace(
+        'ORDER BY ',
+        'ORDER BY '.$orders[ pwg_get_session_var('image_order',0) ][1].',',
+        $conf['order_by']
+        );
+      $page['super_order_by'] = true;
+    }
+    elseif ($order_by!=null)
+    {
+      $conf['order_by']=$order_by;
+      $page['super_order_by'] = true;
+    }
+    elseif ( 'categories' == $page['section'] and isset($page['category']['image_order']) )
+    {
+      $conf['order_by'] = 'ORDER BY '.$page['category']['image_order'];
+    }
+  }
+  else
+  {
+    $conf['order_by'] = 'ORDER BY NULL';
+    $page['super_order_by'] = true;
+  }
+
+  if ('categories' == $page['section'])
+  {
+    if ( isset($page['flat']) or isset($page['category']) )
+    {
+      if (isset($page['flat']))
+      {
+        if ( isset($page['category']) )
+        {
+          $subcat_ids = get_subcat_ids( array($page['category']['id']) );
+          $where_sql .= ' AND category_id IN ('.implode(',',$subcat_ids).')';
+        }
+      }
+      else
+      {
+        $where_sql .= ' AND category_id='.$page['category']['id'];
+      }
+
+      $query='
+SELECT '.$img_fields.'
+  FROM '.IMAGES_TABLE.' i INNER JOIN '.IMAGE_CATEGORY_TABLE.' ON i.id=image_id
+  WHERE '.$where_sql.$group_by.'
+  '.$conf['order_by'];
+
+      $page['items'] = $func($query);
+    }
+    if ( isset($page['category']) )
+    {
+      $page['title'] = $page['category']['name'];
+      $page['comment'] = $page['category']['comment'];
+    }
+    else
+      $page['title'] = $conf['gallery_title'];
+  }
+  else if ('tags' == $page['section'])
+  {
+    $items = get_image_ids_for_tags( array($page['tags'][0]['id']) );
+    if ( !empty($items) )
+    {
+      $query = '
+SELECT '.$img_fields.'
+  FROM '.IMAGE_CATEGORY_TABLE.' INNER JOIN '.IMAGES_TABLE.' i ON i.id=image_id
+  WHERE '.$where_sql.'
+    AND image_id IN ('.implode(',', $items).')'
+  .$group_by.'
+  '.$conf['order_by'];
+
+      $page['items'] = $func($query);
+    }
+    $page['title'] = strip_tags( get_tags_content_title() );
+  }
+  elseif ('search' == $page['section'])
+  {
+    include_once( PHPWG_ROOT_PATH .'include/functions_search.inc.php' );
+    $search_result = get_search_results($page['search'], @$page['super_order_by'], $where_sql_images_only);
+    if ( !empty($search_result['items']) )
+    {
+      $query = '
+SELECT '.$img_fields.'
+  FROM '.IMAGES_TABLE.' i
+  WHERE id IN ('.implode(',', $search_result['items']).')'
+  .$group_by.'
+  '.$conf['order_by'].'
+;';
+
+      if ($mode != RVM_BUILD_AGGREGATE )
+      {
+        $page['items'] = hash_from_query($query, 'id' );
+
+        global $item_ranks;
+        $item_ranks = array_flip($search_result['items']);
+        function cmp_item_hash($a, $b)
+        {
+          global $item_ranks;
+          return $item_ranks [ $a['id'] ] - $item_ranks [ $b['id'] ];
+        }
+        uasort( $page['items'], 'cmp_item_hash' );
+        unset( $item_ranks );
+      }
+      else
+        $page['items'] = $func($query);
+    }
+
+    $page['title'] = l10n('search_result');
+  }
+  elseif ('recent_pics' == $page['section'])
+  {
+    $conf['order_by'] = ' ORDER BY hit DESC, file ASC';
+
+    $query ='
+SELECT '.$img_fields.'
+  FROM '.IMAGES_TABLE.' i
+    INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id
+  WHERE date_available >= SUBDATE(
+      CURRENT_DATE,INTERVAL '.$user['recent_period'].' DAY)
+    AND '.$where_sql
+  .$group_by.'
+  '.$conf['order_by'].'
+  LIMIT 0, '.$conf['top_number'].'
+;';
+
+    $page['items'] = $func($query);
+    $page['title'] = l10n('recent_pics_cat');
+  }
+  else if ('list'==$page['section'])
+  {
+    $query ='
+SELECT '.$img_fields.'
+  FROM '.IMAGES_TABLE.' i
+    INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id
+  WHERE image_id IN ('.implode(',', $page['list']).')
+    AND '.$where_sql
+  .$group_by.'
+  '.$conf['order_by'].'
+;';
+
+    $page['items'] = $func($query);
+    $page['title'] = l10n('random_cat');
+  }
+  else
+    fatal_error( 'section '.$page['section']. ' not handled '. __FILE__);
+}
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/include/picture_map.inc.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/include/picture_map.inc.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/include/picture_map.inc.php	(revision 4071)
@@ -0,0 +1,63 @@
+<?php
+global $page, $template, $conf, $rvm_dir;
+
+$template->concat('PLUGIN_PICTURE_ACTIONS', '
+  <a href="'.duplicate_picture_url().'" title="'.l10n('mode_normal_hint').'"><img src="'.get_root_url().'plugins/'.$rvm_dir.'/icons/map_m.png" class="button" alt="map"></a>
+  ');
+
+$template->assign(
+  array(
+    'RVM_PLUGIN_VERSION' => RVM_PLUGIN_VERSION,
+    'GMAPS_API_KEY' => $conf['gmaps_api_key'],
+    'PLUGIN_ROOT_URL' => get_absolute_root_url().'plugins/'.$rvm_dir,
+  )
+  );
+
+$page['meta_robots']=array('noindex'=>1, 'nofollow'=>1);
+
+
+add_event_handler(
+  'render_element_content',
+  'rvm_picture_map_content',
+  EVENT_HANDLER_PRIORITY_NEUTRAL-5,
+  2
+  );
+
+add_event_handler( 'loc_end_picture', 'rvm_end_picture'  );
+
+
+function rvm_picture_map_content($content, $picture)
+{
+  include_once( dirname(__FILE__) .'/functions_map.php');
+  global $template;
+  $template->set_filename( 'map_content', dirname(__FILE__).'/../template/picture_map_content.tpl' );
+  $template->assign(
+    array(
+      'TN_SRC' => $picture['thumbnail'],
+      'U_NO_MAP' => duplicate_picture_url(),
+      'U_BLOWUP' => rvm_make_blowup_url( array('ll'=>$picture), array('start','box') ),
+      'COMMENT_IMG' => nl2br($picture['comment'])
+    )
+  );
+  if ( isset($picture['lat']) )
+  {
+    $template->assign( 'coordinates',
+        array(
+          'LAT' => $picture['lat'],
+          'LON' => $picture['lon'],
+        )
+      );
+  }
+  return $template->parse( 'map_content', true);
+}
+
+function rvm_end_picture()
+{
+  global $template;
+  $template->assign( 'COMMENT_IMG', null);  // no legend below picture (we put it left)
+  $rating = & $template->get_template_vars('rating');
+  if (isset($rating))
+    $rating['F_ACTION'] = add_url_params( $rating['F_ACTION'], array('map'=>null) );
+}
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/kml.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/kml.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/kml.php	(revision 4071)
@@ -0,0 +1,182 @@
+<?php
+define('PHPWG_ROOT_PATH','../../');
+
+include_once( PHPWG_ROOT_PATH.'include/common.inc.php' );
+
+if (!isset($rvm_dir))
+  access_denied( 'Plugin not installed' );
+
+include_once( dirname(__FILE__) .'/include/functions_map.php');
+include_once( dirname(__FILE__) .'/include/functions.php');
+
+rvm_load_language();
+set_make_full_url();
+
+$section = '';
+if ( $conf['question_mark_in_urls']==false and
+     isset($_SERVER["PATH_INFO"]) and !empty($_SERVER["PATH_INFO"]) )
+{
+  $section = $_SERVER["PATH_INFO"];
+  $section = str_replace('//', '/', $section);
+}
+else
+{
+  foreach ($_GET as $key=>$value)
+  {
+    if (!strlen($value)) $section=$key;
+    break;
+  }
+}
+// deleting first "/" if displayed
+$tokens = explode(
+  '/',
+  preg_replace('#^/#', '', $section)
+  );
+$next_token = 0;
+$result = rvm_parse_kml_url($tokens, $next_token);
+$page = array_merge( $page, $result );
+
+$order_by=null;
+if ( isset($page['ll']) )
+  $where_sql = rvm_ll_to_sql($page['ll'], $order_by);
+else
+  $where_sql = rvm_bounds_to_sql( $page['box'] );
+
+
+$img_fields = ' i.id,i.tn_ext,i.name,i.comment,i.file,i.path,i.lat,i.lon,i.date_creation';
+
+rvm_build_section_items($img_fields, $where_sql, RVM_BUILD_ARRAY, $order_by);
+
+
+$dataTpl = new Template( PHPWG_ROOT_PATH );
+$dataTpl->set_filename('main', dirname(__FILE__).'/template/earth_kml.tpl' );
+
+$dataTpl->assign(
+  array(
+    'PAGE_TITLE' => strip_tags(@$page['title']),
+    'CONTENT_ENCODING' => get_pwg_charset(),
+    'PAGE_COMMENT' => strip_tags( @$page['comment'], '<a><br><p><b><i><small><strong><font>'),
+    'U_INDEX' => duplicate_index_url( array('start'=>0) ),
+  )
+);
+
+if ( !empty($page['items']) )
+  $dataTpl->assign( 'NB_ITEMS_DESC', sprintf( l10n('%d elements'), count($page['items']) ).'<br/>' );
+
+
+// +-----------------------------------------------------------------------+
+// |              sub categories network links                             |
+// +-----------------------------------------------------------------------+
+if ( 'categories'==$page['section'] and !isset($page['flat']) )
+{
+  $query = '
+SELECT id, name, permalink, comment, uppercats, representative_picture_id
+  FROM '.CATEGORIES_TABLE.' INNER JOIN '.USER_CACHE_CATEGORIES_TABLE.'
+  ON id = cat_id and user_id = '.$user['id'].'
+  WHERE id_uppercat '.
+  (!isset($page['category']) ? 'is NULL' : '= '.$page['category']['id']).'
+  ORDER BY rank
+;';
+//  echo "<pre>" . var_export($query,true);
+  $result = pwg_query($query);
+  $categories = array();
+  $thumbnail_ids=array();
+  while ($row = mysql_fetch_assoc($result))
+  {
+    array_push($categories, $row);
+    if (isset($row['representative_picture_id']) and is_numeric($row['representative_picture_id']))
+      array_push($thumbnail_ids, $row['representative_picture_id'] );
+  }
+  $thumbnail_src_of = array();
+  if ( count($thumbnail_ids) )
+  {
+    $query = '
+SELECT id, path, tn_ext
+  FROM '.IMAGES_TABLE.'
+  WHERE id IN ('.implode(',', $thumbnail_ids).')';
+    $result = pwg_query($query);
+    while ($row = mysql_fetch_assoc($result))
+      $thumbnail_src_of[$row['id']] = get_thumbnail_url($row);
+    unset($thumbnail_ids);
+  }
+
+  $cat_bounds = rvm_get_cat_bounds();
+
+  foreach ($categories as $category)
+  {
+    if ( !isset( $cat_bounds[ $category['id'] ] ) )
+      continue; // without geo tagged images
+
+    $bounds = $cat_bounds[ $category['id'] ];
+
+    if ( isset($bounds['self']) )
+    {
+      $count_desc = l10n_dec('%d element', '%d elements', $bounds['self']['count']);
+      if ( $bounds['self']['count'] == $bounds['count'] )
+        $count_desc .= ' '.l10n('images_available_cpl');
+      else
+        $count_desc .= '/'.l10n_dec('%d element', '%d elements', $bounds['count']).' '.l10n_dec('images_available_cat','images_available_cats', $bounds['nb_cats'] );
+    }
+    else
+      $count_desc = l10n_dec('%d element', '%d elements', $bounds['count'])
+        .' '.l10n_dec('images_available_cat','images_available_cats', $bounds['nb_cats'] );
+
+		$tpl_var = array(
+				'NAME'    => $category['name'],
+				'COMMENT' => trigger_event('render_category_literal_description', trigger_event('render_category_description', @$category['comment']) ),
+				'U_CATEGORY' => make_index_url( array('category'=>$category) ),
+				'U_KML'   => rvm_make_kml_index_url( array('section'=>'categories', 'category'=>$category) ),
+				'U_MAP'   => rvm_make_map_index_url( array('section'=>'categories', 'category'=>$category) ),
+				'COUNT_DESC' => $count_desc,
+				'region'  => $bounds,
+      );
+    if ( isset( $thumbnail_src_of[$category['representative_picture_id']] ) )
+      $tpl_var['TN_SRC'] = $thumbnail_src_of[$category['representative_picture_id']];
+
+    $dataTpl->append( 'categories', $tpl_var );
+  }
+}
+
+
+
+// +-----------------------------------------------------------------------+
+// | generate content for items                                            |
+// +-----------------------------------------------------------------------+
+$bounds = array();
+foreach( $page['items'] as $img )
+{
+	$bounds = bounds_add($bounds, $img['lat'], $img['lon']);
+	$page_url = duplicate_picture_url(array('image_id' => $img['id'],'image_file' => $img['file']),  array('start') );
+
+	if (!empty( $img['name'] ) )
+		$title = $img['name'];
+	else
+		$title = str_replace('_', ' ', get_filename_wo_extension($img['file']));
+
+  $tpl_var = array(
+      'U_PAGE'=> $page_url,
+      'TN_SRC'  => get_thumbnail_url($img),
+      'TITLE'  => $title,
+      'DESCRIPTION'  => $img['comment'],
+      'LAT'   => $img['lat'],
+      'LON'   => $img['lon'],
+      'DATE_CREATION' => $img['date_creation'],
+    );
+	$dataTpl->append('images', $tpl_var);
+}
+
+if ( !empty($bounds) )
+  $dataTpl->assign( 'region', $bounds );
+
+/*header('Cache-Control: public' );
+header('Expires: '.gmdate('D, d M Y H:i:s', time()+600).' GMT');
+header('Pragma:');*/
+header('Content-Disposition: inline; filename="'.str2url($page['title']).'.kml";' );
+header('Content-Type: application/vnd.google-earth.kml+xml; charset='.get_pwg_charset());
+if (isset($_GET['debug']))
+  header('Content-Type: text/xml; charset='.get_pwg_charset());
+
+
+$dataTpl->pparse('main');
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/language/cz_CZ/lang.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/language/cz_CZ/lang.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/language/cz_CZ/lang.php	(revision 4071)
@@ -0,0 +1,25 @@
+<?php
+$lang['Map']='Mapa';
+
+$lang['displays %s on a map']='zobrazí %s na mapě';
+$lang['opens %s in Google Earth']='otevře %s v aplikaci Google Earth (kterou musíte mít nainstalovánu v počítači)';
+
+$lang['Go to']='Jít na';
+
+$lang['go to %s']='jít na %s';
+$lang['displays all images on a map']='zobrazí všechny fotografie na mapě';
+$lang['Viewing']='Prohlížení';
+$lang['Link to this page']='Odkaz na tuto stránku';
+$lang['Show all']='Zobrazit vše';
+$lang['show all pictures around this location']='zobrazit fotografie v okolí';
+$lang['Search a location...']='Najít polohu...';
+
+$lang['Loading']='Nahrávání';
+$lang['Failed']='Nepodařilo se';
+
+$lang['Prev']='Předcházející';
+$lang['Next']='Další';
+$lang['out of %d']='z %d';
+
+$lang['More pictures near this location']='Více fotografií v okolí';
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/language/en_UK/lang.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/language/en_UK/lang.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/language/en_UK/lang.php	(revision 4071)
@@ -0,0 +1,25 @@
+<?php
+$lang['Map']='Map';
+
+$lang['displays %s on a map']='displays %s on a map';
+$lang['opens %s in Google Earth']='opens %s in Google Earth (must be installed on your machine)';
+
+$lang['Go to']='Go to';
+
+$lang['go to %s']='go to %s';
+$lang['displays all images on a map']='displays all images on a map';
+$lang['Viewing']='Viewing';
+$lang['Link to this page']='Link to this page';
+$lang['Show all']='Show all';
+$lang['show all pictures around this location']='show all pictures around this location';
+$lang['Search a location...']='Search a location...';
+
+$lang['Loading']='Loading';
+$lang['Failed']='Failed';
+
+$lang['Prev']='Prev';
+$lang['Next']='Next';
+$lang['out of %d']='out of %d';
+
+$lang['More pictures near this location']='More pictures near this location';
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/language/fr_FR/lang.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/language/fr_FR/lang.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/language/fr_FR/lang.php	(revision 4071)
@@ -0,0 +1,25 @@
+<?php
+$lang['Map']='Carte';
+
+$lang['displays %s on a map']='afficher %s sur la carte';
+$lang['opens %s in Google Earth']='Ouvre %s avec Google Earth (doit être installé sur votre PC)';
+
+$lang['Go to']='Aller à';
+
+$lang['go to %s']='aller à %s';
+$lang['displays all images on a map']='affiche toutes les images sur la carte';
+$lang['Viewing']='Aperçu';
+$lang['Link to this page']='Lien vers cette page';
+$lang['Show all']='Présente tout';
+$lang['show all pictures around this location']='Présente toutes les images à proximité de ces coordonnées';
+$lang['Search a location...']='Rechercher un lieu...';
+
+$lang['Loading']='Chargement';
+$lang['Failed']='Echec';
+
+$lang['Prev']='Préc';
+$lang['Next']='Suiv';
+$lang['out of %d']='sur %d';
+
+$lang['More pictures near this location']='Plus d\'images près de ce lieu';
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/language/it_IT/lang.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/language/it_IT/lang.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/language/it_IT/lang.php	(revision 4071)
@@ -0,0 +1,25 @@
+<?php
+$lang['Map']='Cartine';
+
+$lang['displays %s on a map']='visualizzare %s sulla cartina';
+$lang['opens %s in Google Earth']='Aprire %s con Google Earth (deve essere installato sul vostro PC)';
+
+$lang['Go to']='Andare a';
+
+$lang['go to %s']='andare a %s';
+$lang['displays all images on a map']='visualizzare tutte le immagini sulla cartina';
+$lang['Viewing']='Anteprima';
+$lang['Link to this page']='Link verso questa pagina';
+$lang['Show all']='Presenta tutto';
+$lang['show all pictures around this location']='Presenta tutte le immagini in prossimità di queste coordinate';
+$lang['Search a location...']='Cecare un luogo...';
+
+$lang['Loading']='Caricamento';
+$lang['Failed']='Insucesso';
+
+$lang['Prev']='Prec';
+$lang['Next']='Succ';
+$lang['out of %d']='su %d';
+
+$lang['More pictures near this location']='Più immagini vicino a questo luogo';
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/language/pl_PL/lang.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/language/pl_PL/lang.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/language/pl_PL/lang.php	(revision 4071)
@@ -0,0 +1,26 @@
+<?php
+//Polish translation by voyteckst
+$lang['Map']='Mapa';
+
+$lang['displays %s on a map']='wywietla %s na mapie';
+$lang['opens %s in Google Earth']='otwiera %s w Google Earth (aplikacjam musi byæ zainstalowana na komputerze)';
+
+$lang['Go to']='Przejd do';
+
+$lang['go to %s']='przejd do %s';
+$lang['displays all images on a map']='wywietla wszystkie obrazy na mapie';
+$lang['Viewing']='Przegl¹danie';
+$lang['Link to this page']='Link do tej strony';
+$lang['Show all']='Poka¿ wszystko';
+$lang['show all pictures around this location']='poka¿ wszystkie obrazy w pobli¿u tej lokalizacji';
+$lang['Search a location...']='Szukaj lokalizacji...';
+
+$lang['Loading']='Wczytywanie';
+$lang['Failed']='B³¹d';
+
+$lang['Prev']='Poprzednie';
+$lang['Next']='Nastepne';
+$lang['out of %d']='z %d';
+
+$lang['More pictures near this location']='Wiêcej brazów w pobliu tej lokalizacji';
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/main.inc.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/main.inc.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/main.inc.php	(revision 4071)
@@ -0,0 +1,136 @@
+<?php /*
+Plugin Name: RV Maps&Earth
+Version: 2.0.c
+Description: Extend your gallery with Google Maps and Google Earth ...
+Plugin URI: http://piwigo.org/ext/extension_view.php?eid=122
+Author: rvelices
+Author URI: http://www.modusoptimus.com/
+*/
+define( 'RVM_PLUGIN_VERSION', '2.0.c');
+if (!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!');
+
+add_event_handler('loc_end_index', 'rvm_end_index' );
+add_event_handler('loc_begin_index_category_thumbnails', 'rvm_index_cat_thumbs_displayed');
+add_event_handler('loc_begin_index_thumbnails', 'rvm_begin_index_thumbnails');
+
+add_event_handler('picture_pictures_data', 'rvm_picture_pictures_data' );
+add_event_handler('get_admin_plugin_menu_links', 'rvm_plugin_admin_menu' );
+
+global $rvm_dir;
+$rvm_dir = basename( dirname(__FILE__) );
+
+
+function rvm_index_cat_thumbs_displayed()
+{
+	global $page;
+	$page['rvm_cat_thumbs_displayed'] = true;
+}
+
+function rvm_begin_index_thumbnails( $pictures )
+{
+	global $page;
+	foreach ($pictures as $picture)
+	{
+		if ( isset($picture['lat']) )
+		{
+			$page['rvm_items_have_latlon'] = true;
+			break;
+		}
+	}
+}
+
+function rvm_end_index()
+{
+	global $page, $filter, $template, $rvm_dir;
+
+	//if ( !is_admin() ) return; // TEMP
+	if ( isset($page['chronology_field']) || $filter['enabled'] )
+		return;
+
+	if ( 'categories' == @$page['section'])
+	{ // flat or no flat ; has subcats or not;  ?
+		if ( ! @$page['rvm_cat_thumbs_displayed'] and empty($page['items']) )
+			return;
+	}
+	else
+	{
+		if (
+			!in_array( @$page['section'], array('tags','search','recent_pics','list') )
+			)
+			return;
+		if ( empty($page['items']) )
+			return;
+	}
+
+	include_once( dirname(__FILE__) .'/include/functions.php');
+
+	if ( !empty($page['items']) )
+	{
+		if (!@$page['rvm_items_have_latlon'] and ! rvm_items_have_latlon( $page['items'] ) )
+			return;
+	}
+	rvm_load_language();
+
+	$map_url = rvm_duplicate_map_index_url( array(), array('start') );
+	if ( !empty($map_url) )
+	{
+		$link_title = sprintf( l10n('displays %s on a map'), strip_tags($page['title']) );
+		$template->concat( 'PLUGIN_INDEX_ACTIONS' ,'
+<li><a href="'.$map_url.'" title="'.$link_title.'" rel="nofollow"><img src="'.get_root_url().'plugins/'.$rvm_dir.'/icons/map_m.png" class="button" alt="map"></a></li>
+');
+ 	}
+
+//  if ( empty($map_url) )
+	{
+		$kml_url = rvm_duplicate_kml_index_url( array(), array('start') );
+		$link_title = sprintf( l10n('opens %s in Google Earth'), strip_tags($page['title']) );
+		$template->concat( 'PLUGIN_INDEX_ACTIONS' ,'
+<li><a href="'.$kml_url.'" title="'.$link_title.'" rel="nofollow" type="application/vnd.google-earth.kml+xml"><img src="'.get_root_url().'plugins/'.$rvm_dir.'/icons/earth_m.png" class="button" alt="earth"></a></li>
+');
+	}
+}
+
+function rvm_picture_pictures_data($pictures)
+{
+	if ( isset($pictures['current']['lat']) and isset($pictures['current']['lon']) )
+	{
+		global $template;
+		$template->append('head_elements', '<meta name="geo.position" content="'.$pictures['current']['lat'].';'.$pictures['current']['lon'].'">' );
+		include_once( dirname(__FILE__) .'/include/functions.php');
+		rvm_load_language();
+		if ( isset($_GET['map']) )
+			include_once( dirname(__FILE__) .'/include/picture_map.inc.php');
+		else
+		{
+			global $rvm_dir;
+			$map_url = rvm_duplicate_map_picture_url();
+			if (!empty($map_url))
+			{
+				$link_title = sprintf( l10n('displays %s on a map'), strip_tags($pictures['current']['name']) );
+				$template->concat('PLUGIN_PICTURE_ACTIONS', '
+<a href="'.$map_url.'" title="'.$link_title.'" rel="nofollow" target="_top"><img src="'.get_root_url().'plugins/'.$rvm_dir.'/icons/map_m.png" class="button" alt="map"></a>
+');
+			}
+		}
+	}
+	else
+		if ( isset($_GET['map']) )
+			redirect( duplicate_picture_url() );
+	return $pictures;
+}
+
+function rvm_plugin_admin_menu($menu)
+{
+	include_once( dirname(__FILE__) .'/include/functions.php');
+	add_event_handler('invalidate_user_cache', 'rvm_invalidate_cache' );
+
+	array_push($menu,
+			array(
+				'NAME' => 'Maps & Earth',
+				'URL' => get_admin_plugin_menu_link(dirname(__FILE__).'/admin/admin.php')
+			)
+		);
+	return $menu;
+}
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/maintain.inc.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/maintain.inc.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/maintain.inc.php	(revision 4071)
@@ -0,0 +1,76 @@
+<?php
+function plugin_install()
+{
+  $q = '
+ALTER TABLE '.IMAGES_TABLE.' ADD COLUMN `lat` DOUBLE(8,6)';
+  pwg_query($q);
+  
+  $q = '
+ALTER TABLE '.IMAGES_TABLE.' ADD INDEX images_lat(`lat`)';
+  pwg_query($q);
+
+  $q = '
+ALTER TABLE '.IMAGES_TABLE.' ADD COLUMN `lon` DOUBLE(9,6)';
+  pwg_query($q);
+  
+  $q = '
+INSERT INTO '.CONFIG_TABLE.' (param,value,comment)
+  VALUES
+  ("gmaps_api_key","","Google Maps API key")
+;';
+  pwg_query($q);
+}
+
+function plugin_activate()
+{
+  $dir_name = basename( dirname(__FILE__) );
+  $c = <<<EOD
+<?php
+define('PHPWG_ROOT_PATH','./');
+include_once( PHPWG_ROOT_PATH. 'plugins/$dir_name/map.php');
+?>
+EOD;
+  $fp = fopen( PHPWG_ROOT_PATH.'map.php', 'w' );
+  fwrite( $fp, $c);
+  fclose( $fp );
+
+	$old = dirname(__FILE__).'/_rvgm_config.dat';
+	if ( is_file($old) )
+	{
+	  global $conf;
+		$dest = $conf['local_data_dir'].'/plugins/'.basename(dirname(__FILE__)).'.dat';
+	  if (!file_exists($dest) )
+	  {
+	    mkgetdir( dirname($dest) );
+	  	copy( $old, $dest );
+	  }
+	  unlink( $old );
+	}
+}
+
+function plugin_deactivate()
+{
+  @unlink( PHPWG_ROOT_PATH.'map.php' );
+  rvm_invalidate_cache();
+}
+
+function plugin_uninstall()
+{
+  $q = '
+ALTER TABLE '.IMAGES_TABLE.' DROP COLUMN `lat`';
+  pwg_query( $q );
+
+  $q = '
+ALTER TABLE '.IMAGES_TABLE.' DROP COLUMN `lon`';
+  pwg_query( $q );
+
+  $q = '
+DELETE FROM '.CONFIG_TABLE.' WHERE param="gmaps_api_key" LIMIT 1';
+  pwg_query( $q );
+  
+  global $conf;
+	$dest = $conf['local_data_dir'].'/plugins/'.basename(dirname(__FILE__)).'.dat';
+  @unlink( $dest );
+}
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/map.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/map.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/map.php	(revision 4071)
@@ -0,0 +1,89 @@
+<?php
+if ( !defined('PHPWG_ROOT_PATH') )
+  define('PHPWG_ROOT_PATH','../../');
+
+include_once( PHPWG_ROOT_PATH.'include/common.inc.php' );
+include_once( dirname(__FILE__) .'/include/functions.php');
+include_once( dirname(__FILE__) .'/include/functions_map.php');
+
+check_status(ACCESS_GUEST);
+if (!isset($rvm_dir))
+  access_denied( 'Plugin not installed' );
+
+rvm_load_language();
+
+$section = '';
+if ( $conf['question_mark_in_urls']==false and
+     isset($_SERVER["PATH_INFO"]) and !empty($_SERVER["PATH_INFO"]) )
+{
+  $section = $_SERVER["PATH_INFO"];
+  $section = str_replace('//', '/', $section);
+  $path_count = count( explode('/', $section) );
+  $page['root_path'] = PHPWG_ROOT_PATH.str_repeat('../', $path_count-1);
+}
+else
+{
+  foreach ($_GET as $key=>$value)
+  {
+    if (!strlen($value)) $section=$key;
+    break;
+  }
+}
+
+// deleting first "/" if displayed
+$tokens = explode(
+  '/',
+  preg_replace('#^/#', '', $section)
+  );
+$next_token = 0;
+$result = rvm_parse_map_data_url($tokens, $next_token);
+$page = array_merge( $page, $result );
+
+
+if (isset($page['category']))
+  check_restrictions($page['category']['id']);
+
+if ( !isset($_GET['ll']) and ($page['section']!='categories' or isset($page['category']) ) )
+{
+  $img_fields = 'MIN(i.lat) s, MIN(i.lon) w, MAX(i.lat) n, MAX(i.lon) e';
+  $page['flat']=true;
+  rvm_build_section_items($img_fields, null, RVM_BUILD_AGGREGATE);
+  //var_export( $page['items'] );
+  if ( count($page['items']) )
+    $template->assign('initial_bounds', $page['items'][0] );
+  unset( $page['items'] );
+}
+
+
+$map_data_url  = get_absolute_root_url().'plugins/'.$rvm_dir.'/map_data.php?';
+$map_data_url .= $section;
+
+$template->set_filename( 'map', dirname(__FILE__).'/template/map.tpl' );
+
+$template->assign(
+  array(
+    'CONTENT_ENCODING' => get_pwg_charset(),
+    'RVM_PLUGIN_VERSION' => RVM_PLUGIN_VERSION,
+    'GMAPS_API_KEY' => $conf['gmaps_api_key'],
+    'PLUGIN_ROOT_URL' => get_absolute_root_url().'plugins/'.$rvm_dir,
+    'U_MAP_DATA' => $map_data_url,
+    'GALLERY_TITLE' => $conf['gallery_title'],
+    'U_HOME' => make_index_url(),
+    'U_HOME_MAP' => rvm_make_map_index_url(),
+  )
+  );
+
+$marker_js_file = rvm_get_config_var('marker_icon', '');
+if ( !empty($marker_js_file) )
+  $marker_js_file = dirname(__FILE__).'/template/markers/'.$marker_js_file.'.tpl';
+if ( !empty($marker_js_file) and file_exists($marker_js_file) )
+{
+  $template->set_filename( 'map_marker_icon', $marker_js_file );
+  $template->assign_var_from_handle('MAP_MARKER_ICON_JS', 'map_marker_icon');
+}
+else
+  $template->assign('MAP_MARKER_ICON_JS', 'return G_DEFAULT_ICON;');
+
+$template->pparse('map');
+$template->p();
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/map_data.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/map_data.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/map_data.php	(revision 4071)
@@ -0,0 +1,184 @@
+<?php
+if ( !defined('PHPWG_ROOT_PATH') )
+  define('PHPWG_ROOT_PATH','../../');
+include_once( PHPWG_ROOT_PATH.'include/common.inc.php' );
+
+if (!isset($rvm_dir))
+  access_denied( 'Plugin not installed' );
+
+include_once( dirname(__FILE__) .'/include/functions_map.php');
+include_once( dirname(__FILE__) .'/include/functions.php');
+
+
+//set_make_full_url();
+$page['root_path']=get_absolute_root_url(false);
+
+$rewritten = '';
+foreach (array_keys($_GET) as $key)
+{
+  $rewritten = $key;
+  break;
+}
+// deleting first "/" if displayed
+$tokens = explode(
+  '/',
+  preg_replace('#^/#', '', $rewritten)
+  );
+$next_token = 0;
+$result = rvm_parse_map_data_url($tokens, $next_token);
+$page = array_merge( $page, $result );
+
+$where_sql = rvm_bounds_to_sql( $page['box'] );
+
+$img_fields = ' i.id,i.tn_ext,i.name,i.comment,i.path,i.lat,i.lon';
+
+$was_flat = @$page['flat'];
+$page['flat']=true;
+rvm_build_section_items($img_fields, $where_sql, RVM_BUILD_HASH);
+if (!$was_flat) unset($page['flat']);
+
+/*header('Cache-Control: public' );
+header('Expires: '.gmdate('D, d M Y H:i:s', time()+600).' GMT');
+header('Pragma:');*/
+header('Content-Type: text/plain; charset='.get_pwg_charset());
+
+if ( extension_loaded('zlib') && !ini_get('zlib.output_compression') )
+  ob_start('ob_gzhandler');
+
+$clusters = array();
+$cluster_debug = '';
+if ( !empty($page['items']) )
+{
+  include_once( dirname(__FILE__) .'/include/cluster_maker.php');
+  $cm = new ClusterMaker();
+  $clusters = $cm->make_clusters(
+      $page['items'],
+      isset($_GET['lap']) ? $_GET['lap'] : 0.01,
+      isset($_GET['lop']) ? $_GET['lop'] : 0.01,
+      isset($_GET['n']) ? $_GET['n'] : rvm_get_config_var('nb_markers',40)
+    );
+  $cluster_debug .= ' cluster: '. $cm->debug_str. ';';
+}
+
+function jsgm_position( $position )
+{
+  return 'new GLatLng(' . $position['lat'] . ',' . $position['lon'] . ')';
+}
+
+function jsgm_bounds( $bounds )
+{
+  return 'new GLatLngBounds(' . jsgm_position(bounds_sw($bounds)) . ',' . jsgm_position(bounds_ne($bounds)) . ')';
+}
+
+function jsgm_str( $str )
+{
+  return '"'. str_replace(array("\\",'"',"\n","\r","\t"), array("\\\\",'\"',"\\n","\\r","\\t"), $str) .'"';
+}
+
+$out='';
+function rvo($s) {
+  global $out; $out.=$s;
+}
+
+function rvf() {
+  global $out; echo $out; $out='';
+  ob_flush();
+}
+
+$start_output = get_moment();
+
+rvo( '{' );
+
+rvo( "\ntitle: ".jsgm_str( strip_tags($page['title']) ) );
+rvo( ",\n" );
+
+rvo( "page_url: ".jsgm_str( duplicate_index_url( array('start'=>0) ) ) );
+rvo( ",\n" );
+
+rvo( "blowup_url: ".jsgm_str( rvm_duplicate_blowup_url( array('start'=>0) ) ) );
+rvo( ",\n" );
+
+rvo( "kml_url: ".jsgm_str( rvm_duplicate_kml_index_url( array('start'=>0, 'flat'=>1) ) ) );
+rvo( ",\n" );
+
+rvo( "nb_items: ".count($page['items']) );
+rvo( ",\n" );
+
+rvo( "bounds: " );
+if ( isset($cm) )
+  rvo( jsgm_bounds( $cm->bounds ) );
+else
+  rvo( "null" );
+rvo( ",\n" );
+
+
+rvo( "\nnb_clusters: ".count($clusters) );
+rvo( "," );
+
+rvo( "\nimage_clusters: [\n" );
+$i=0;
+foreach( $clusters as $c )
+{
+  if ($i) rvo( ",\n\n" );
+  rvo( "{" );
+  rvo( 'position: '. jsgm_position( bounds_center($c['bounds']) ) );
+  rvo( ",\n" );
+  rvo( 'bounds: '. jsgm_bounds( $c['bounds'] ) );
+  rvo( ",\n" );
+  rvo( 'nb_items: '.count($c['items']) );
+  rvo( ",\n" );
+
+  rvo( 'blowup_url: "'.rvm_duplicate_blowup_url(array('box'=>$c['bounds']), array('start') ). '"' );
+  rvo( ",\n" );
+
+  rvo( 'kml_url: "'.rvm_duplicate_kml_index_url(array('box'=>$c['bounds'], 'flat'=>1), array('start') ). '"' );
+  rvo( ",\n" );
+
+  $max_per_cluster = rvm_get_config_var('nb_images_per_marker',20);
+  if ( count($c['items']) >  $max_per_cluster )
+  {
+    $c['items'] = array_slice($c['items'], 0, $max_per_cluster);
+  }
+  rvo( 'items: [' );
+  for ($j=0; $j<count($c['items']); $j++)
+  {
+    $img = $page['items'] [ $c['items'][$j] ];
+    $img['file'] = basename( $img['path'] );
+    if (!empty( $img['name'] ) )
+      $title = $img['name'];
+    else
+      $title = str_replace('_', ' ', get_filename_wo_extension($img['file']));
+
+    $params = array(
+        'image_id' => $img['id'],
+        'image_file' => $img['file'],
+        'flat' => 1,
+      );
+    $page_url = duplicate_picture_url($params, array('start') );
+
+    if ($j) rvo( "," );
+    rvo( "{" );
+    rvo( "tn: ".jsgm_str( get_thumbnail_url( $img ) ) );
+    rvo( "," );
+    rvo( "name: ".jsgm_str( $title ) );
+    rvo( "," );
+    rvo( "comment: ".jsgm_str( $img['comment'] ) );
+    rvo( "," );
+    rvo( "page_url: ".jsgm_str( $page_url ) );
+    rvo( "}" );
+  }
+  rvo( ']' );
+  rvo( "\n" );
+  rvo( "}" );
+  rvf();
+  $i++;
+}
+rvo( "] /*clusters*/\n" );
+
+$time = get_elapsed_time($t2, get_moment());
+$page['queries_time'] = number_format($page['queries_time'],3,'.',' ');
+rvo( "\n,debug:'$time; out:".get_elapsed_time($start_output,get_moment()).";$cluster_debug queries:".$page['count_queries']." in ".$page['queries_time']."s'\n" );
+
+rvo( '}' );
+rvf();
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/mapl.php
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/mapl.php	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/mapl.php	(revision 4071)
@@ -0,0 +1,182 @@
+<?php
+if ( !defined('PHPWG_ROOT_PATH') )
+  define('PHPWG_ROOT_PATH','../../');
+
+include_once( PHPWG_ROOT_PATH.'include/common.inc.php' );
+include_once( dirname(__FILE__) .'/include/functions.php');
+include_once( dirname(__FILE__) .'/include/functions_map.php');
+
+check_status(ACCESS_GUEST);
+if (!isset($rvm_dir))
+  access_denied( 'Plugin not installed' );
+
+rvm_load_language();
+
+$section = '';
+if ( $conf['question_mark_in_urls']==false and
+     isset($_SERVER["PATH_INFO"]) and !empty($_SERVER["PATH_INFO"]) )
+{
+  $section = $_SERVER["PATH_INFO"];
+  $section = str_replace('//', '/', $section);
+  if ( !isset($page['root_path']) )
+  {
+    $path_count = count( explode('/', $section) );
+    $page['root_path'] = PHPWG_ROOT_PATH.str_repeat('../', $path_count-1);
+  }
+}
+else
+{
+  foreach ($_GET as $key=>$value)
+  {
+    if (!strlen($value)) $section=$key;
+    break;
+  }
+}
+
+// deleting first "/" if displayed
+$tokens = explode(
+  '/',
+  preg_replace('#^/#', '', $section)
+  );
+$next_token = 0;
+$result = rvm_parse_blowup_url($tokens, $next_token);
+$page = array_merge( $page, $result );
+
+$order_by=null;
+if ( isset($page['ll']) )
+  $where_sql = rvm_ll_to_sql($page['ll'], $order_by);
+else
+  $where_sql = rvm_bounds_to_sql( $page['box'] );
+
+$img_fields = ' i.id,i.tn_ext,i.name,i.path,i.lat,i.lon';
+
+$was_flat = @$page['flat'];
+$page['flat']=true;
+rvm_build_section_items($img_fields, $where_sql, RVM_BUILD_HASH, $order_by);
+if (!$was_flat) unset($page['flat']);
+
+$template->set_filename( 'map', dirname(__FILE__).'/template/mapl.tpl');
+
+if (!empty($page['items']))
+{
+/* GENERATE THE CATEGORY LIST *************************************************/
+$where_sql = 'i.id IN ('.implode(',', array_keys($page['items']) ).')';
+$where_sql .= get_sql_condition_FandF(
+        array( 'forbidden_categories' => 'category_id' ),
+        ' AND'
+      );
+$query = '
+SELECT DISTINCT c.id, c.name, c.permalink, COUNT(DISTINCT i.id) counter
+  FROM '.IMAGES_TABLE.' i INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON i.id=image_id
+    INNER JOIN '.CATEGORIES_TABLE.' c ON c.id=category_id
+  WHERE '.$where_sql.'
+  GROUP BY category_id
+  ORDER BY counter DESC
+  LIMIT 0,5
+;';
+$result = pwg_query($query);
+$categories=array();
+while ($row=mysql_fetch_assoc($result))
+  array_push($categories, $row);
+$categories = add_level_to_tags($categories);
+
+foreach( $categories as $category)
+{
+  $template->append(
+    'related_categories', array(
+      'U_MAP' => rvm_make_map_index_url( array( 'category' => $category ) ),
+      'URL' => make_index_url( array( 'category' => $category ) ),
+      'NAME' => $category['name'],
+      'TITLE' => l10n_dec( '%d element', '%d elements', $category['counter'] ),
+      'CLASS' => 'tagLevel'.$category['level']
+    )
+    );
+}
+
+/* GENERATE THE TAG LIST ******************************************************/
+$tags = get_common_tags( array_keys($page['items']), $conf['content_tag_cloud_items_number'], null);
+$tags = add_level_to_tags($tags);
+function counter_compare($a, $b)
+{
+  $d = $a['counter'] - $b['counter'];
+  if ($d==0)
+    return strcmp($a['name'], $b['name']);
+  return -$d;
+}
+usort($tags, 'counter_compare');
+foreach ($tags as $tag)
+{
+  $template->append(
+    'related_tags',
+    array_merge( $tag,
+      array(
+        'U_MAP' => rvm_make_map_index_url( array( 'tags' => array($tag) ) ),
+        'URL' => make_index_url( array( 'tags' => array($tag) ) ),
+        'TITLE' => l10n_dec( '%d element', '%d elements', $tag['counter'] ),
+      )
+    )
+  );
+}
+} // end !empty items
+
+
+$title = '<a target="_top" title="'.sprintf( l10n('go to %s'),$page['title']).'" href="'.duplicate_index_url( array('start'=>0) ).'">'.$page['title'].'</a>';
+if ( count($page['items']) > 0)
+  $title.=' ['.count($page['items']).']';
+
+$template->assign(
+  array(
+    'PLUGIN_ROOT_URL' => get_absolute_root_url().'plugins/'.$rvm_dir,
+    'TITLE'   => $title,
+    'U_HOME'  => make_index_url(),
+    'U_KML'   => rvm_duplicate_kml_index_url( array('flat'=>1), array('start') ),
+    'KML_LINK_TITLE' => sprintf( l10n('opens %s in Google Earth'), strip_tags($page['title']) ),
+  )
+  );
+
+$url = rvm_duplicate_blowup_url(array('start'=>0));
+$navbar = create_navigation_bar($url, count( $page['items'] ), $page['start'], 48);
+$template->assign('NAVBAR', $navbar);
+
+$page['items'] = array_slice(
+  $page['items'],
+  $page['start'],
+  48
+  );
+
+foreach ($page['items'] as $img)
+{
+  $img['file'] = basename( $img['path'] );
+  $thumbnail_url = get_thumbnail_url($img);
+  $page_url = duplicate_picture_url(
+        array(
+          'image_id' => $img['id'],
+          'image_file' => $img['file'],
+          'flat' => 1,
+        ),
+        array('start')
+      );
+  if (!empty( $img['name'] ) )
+    $title = $img['name'];
+  else
+    $title = str_replace('_', ' ', get_filename_wo_extension($img['file']));
+
+  $template->append(
+    'thumbnails',
+    array(
+      'TN_SRC'       => $thumbnail_url,
+      'URL'  => $page_url,
+      'TN_ALT'   => $img['file'],
+      'TN_TITLE'   => $title,
+      )
+    );
+}
+
+$title = $page['title'];
+$page['body_id'] = 'theMapListPage';
+
+include(PHPWG_ROOT_PATH.'include/page_header.php');
+$template->parse('map');
+include(PHPWG_ROOT_PATH.'include/page_tail.php');
+
+?>
Index: /extensions/rv_gmaps/tags/2.0.c/template/data_handler.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/data_handler.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/data_handler.js	(revision 4071)
@@ -0,0 +1,297 @@
+
+function PwgDataHandler( map, opts )
+{
+	this._map = map;
+	this.options = Object.extend(
+		{
+			icon: G_DEFAULT_ICON,
+			show_all_img_src: null
+		}
+		, opts || {} );
+
+	GEvent.addListener( map, "infowindowclose", function() {map.getInfoWindow().pwgMarker=null;} );
+}
+
+PwgDataHandler.prototype =  {
+
+_map: null,
+options: {},
+_markers: [],
+_navHtmlIds: ["gotoPrevImage", "gotoNextImage"],
+_timerBindPictureNavigation: null,
+_prevResult: { nb_items:0 },
+
+
+terminate: function()
+{
+	this._map = null;
+	try {
+		for (i=0; i<this._markers.length; i++)
+			this._markers[i].pwg = null;
+		this._markers.length = 0;
+	}
+	catch(e){}
+},
+
+handle: function( data )
+{
+	var i;
+	if (document.title != data.title )
+		document.title = data.title;
+	var elt = document.getElementById("aPageUrl");
+	if (elt && elt.href!=data.page_url.urlNoHtm() )
+	{
+		elt.href = data.page_url.urlNoHtm();
+		elt.innerHTML = data.title;
+		elt.title = Localization.fmt1("go to %s", data.title);
+	}
+	var elt = document.getElementById("aBlowup");
+	if (elt && elt.href!=data.blowup_url.urlNoHtm())
+		 elt.href = data.blowup_url.urlNoHtm();
+
+	var elt = document.getElementById("aKml");
+	if (elt && elt.href!=data.kml_url.urlNoHtm())
+	{
+		 elt.href = data.kml_url.urlNoHtm();
+		 elt.title = Localization.fmt1("opens %s in Google Earth", data.title);
+	}
+	var changed = true;
+	if (this._prevResult.nb_items==data.nb_items && this._markers.length==data.image_clusters.length )
+	{
+		changed = false;
+		if (this._markers.length>0 && !this._markers[0].getLatLng().equals( data.image_clusters[0].position ) )
+			changed=true;
+	}
+
+	if (changed)
+	{
+		var newMarkers = [];
+		var infoWindowMarker = this._map.getInfoWindow().pwgMarker;
+
+		for (i=0; i<data.image_clusters.length; i++)
+		{
+			var cluster = data.image_clusters[i];
+			var theTitle = "";
+			if (cluster.nb_items>1)
+				theTitle = Localization.fmt1("%d elements", cluster.nb_items);
+			else
+				theTitle = cluster.items[0].name;
+
+			var marker;
+			marker = this._markers.pop();
+			if (marker && marker==infoWindowMarker)
+			{
+				this._map.removeOverlay( marker );
+				GEvent.clearListeners(marker, "click" );
+				GEvent.clearListeners(marker, "dblclick" );
+				this._map.getInfoWindow().pwgMarker = infoWindowMarker = null;
+				if (document.is_debug) GLog.write('removed marker with infoWindow');
+				marker = this._markers.pop();
+			}
+
+			if (!marker)
+			{
+				marker = new GMarker( cluster.position, {title:  theTitle, icon: this.options.icon } );
+				GEvent.addListener( marker, "click", this._onMarkerClick.bind(this, marker) );
+				GEvent.addListener( marker, "dblclick", this._onMarkerDblClick.bind(this, marker) );
+				this._map.addOverlay( marker );
+			}
+			else
+			{
+				marker.currentImageIndex=0;
+				marker.setLatLng( cluster.position );
+				// changing the marker title is undocumented so we hack it
+				if (!this.hack)
+				{
+					this.hack = {};
+					for (var prop in marker)
+					{
+						if ( typeof(marker[prop])!='object') continue;
+						if (!this.hack.markerHtmlElemWithTitle )
+						{
+							try {
+								if (eval("typeof marker." + prop + "[0].src") == "string" && eval("typeof marker." + prop + "[0].title") == "string" )
+									this.hack.markerHtmlElemWithTitle = prop;
+							}
+							catch (e) {}
+						}
+						if (!this.hack.markerOptions)
+							try {
+								if ( eval("typeof marker."+prop+".title")=="string" && eval("typeof marker."+prop+".src")=="undefined")
+									this.hack.markerOptions = prop;
+							}
+							catch (e) {}
+					}
+				}
+				//undocumented marker.K , marker.ch and marker.jb and marker.l
+				if (this.hack.markerOptions)
+					eval( 'marker.'+this.hack.markerOptions+'.title=theTitle');
+				if (this.hack.markerHtmlElemWithTitle)
+					eval( 'marker.'+this.hack.markerHtmlElemWithTitle+'[0].title=theTitle');
+			}
+
+			newMarkers.push(marker);
+
+			marker.pwg = {
+				nb_items: cluster.nb_items,
+				images: cluster.items,
+				bounds: cluster.bounds,
+				blowup_url: cluster.blowup_url
+				};
+		}
+
+		for (i=0; i<this._markers.length; i++)
+		{
+			this._map.removeOverlay( this._markers[i] );
+			GEvent.clearListeners(this._markers[i], "click" );
+			GEvent.clearListeners(this._markers[i], "dblclick" );
+		}
+
+		this._markers = newMarkers;
+
+		this._prevResult.nb_items=data.nb_items;
+	}
+
+	document.getElementById("dataLoadStatus").innerHTML = Localization.fmt1("%d elements", data.nb_items);
+},
+
+_onMarkerClick: function( marker )
+{
+	if (this._map.getInfoWindow().pwgMarker == marker )
+		return; // already open
+  var content = "";
+  if ( !marker.currentImageIndex )
+    marker.currentImageIndex = 0;
+  content += '<div class="gmiw_header" style="' + (marker.pwg.images.length>1 ? '': 'display:none')+ '">';
+  content += '<span id="pwgImageCounters">'+this.buildCurrentPictureCounterHtml(marker)+'</span> ';
+  content += '<a href="javascript:void(0);" id="'+this._navHtmlIds[0]+'">';
+  content +=    '<span>'+ "&laquo; " + Localization.get('Prev') + '</span>';
+  content += '</a>';
+  content += " ";
+  content += '<a href="javascript:void(0);" id="'+this._navHtmlIds[1]+'">';
+  content +=    '<span>'+Localization.get('Next') + " &raquo;"+'</span>';
+  content += '</a>';
+  content += " ";
+  var imgShowAll = '';
+  if (this.options.show_all_img_src)
+    imgShowAll = '<img src="'+this.options.show_all_img_src+'" alt="" style="border:0" /> ';
+  content += '<a id="pwgImageBlowup" href="'+marker.pwg.blowup_url+'" onclick="return PwgDataHandler.blowupUrl(this.href);" title='+Localization.getQ('show all pictures around this location')+'>'+
+            imgShowAll+ '<span>'+Localization.get('Show all')+'</span>'+
+          '</a>';
+  content += '</div>';
+  content += '<div id="pwgImageDetail">' + this.buildCurrentPictureHtml( marker ) + '</div>';
+
+  marker.openInfoWindowHtml( content );
+
+  // bind to next / prev a little later because sometimes the nodes are not immediately created
+  if (this._timerBindPictureNavigation) clearTimeout(this._timerBindPictureNavigation);
+  this._timerBindPictureNavigation = null;
+  if (marker.pwg.images.length>1)
+    this._timerBindPictureNavigation = setTimeout( this._onTimeoutBindPictureNavigation.bind(this, marker), 250 );
+},
+
+_onMarkerDblClick: function( marker )
+{
+  this._map.setCenter( marker.pwg.bounds.getCenter(), this._map.getBoundsZoomLevel( marker.pwg.bounds ) );
+},
+
+buildCurrentPictureHtml: function( marker )
+{
+  var imageDetail = marker.pwg.images[marker.currentImageIndex];
+  var res = "";
+  res += '<div class="gmiw_imageTitle">' + imageDetail.name + "</div>";
+  res +=
+'<div class="gmiw_imageContent">' +
+'<div class="gmiw_imageWrap">'+
+  '<a href="'+imageDetail.page_url+'">' +
+  '<img src="' + imageDetail.tn + '" alt="thumb" />'+
+'</a></div>' +
+'<div class="gmiw_imageComment">' + imageDetail.comment + '</div>' +
+'</div>';
+  return res;
+},
+
+buildCurrentPictureCounterHtml: function( marker )
+{
+  var res =
+    '<b>'+(marker.currentImageIndex+1)+'</b>'+
+    '/' +
+    '<b>'+marker.pwg.images.length+'</b>';
+  if (marker.pwg.nb_items>marker.pwg.images.length)
+    res+= " "+Localization.fmt1("out of %d", marker.pwg.nb_items );
+  return res;
+},
+
+
+_onTimeoutBindPictureNavigation: function( marker )
+{
+  clearTimeout(this._timerBindPictureNavigation);
+  this._timerBindPictureNavigation = null;
+  this._map.getInfoWindow().pwgMarker = marker;
+  for (var i=0; i< this._navHtmlIds.length; i++)
+  {
+    var elt = document.getElementById( this._navHtmlIds[i] );
+    if (elt)
+      GEvent.addDomListener(elt, "click", this._onPictureNavigate.bind(this, marker, i) );
+  }
+},
+
+_onPictureNavigate: function(marker, dir )
+{
+  if (dir==0) dir=-1;
+  marker.currentImageIndex += dir;
+  if (marker.currentImageIndex<0)
+    marker.currentImageIndex = marker.pwg.images.length-1;
+  else if (marker.currentImageIndex >= marker.pwg.images.length)
+    marker.currentImageIndex = 0;
+
+  try {
+    var elt = document.getElementById( "pwgImageDetail" );
+    elt.innerHTML = this.buildCurrentPictureHtml( marker );
+  }
+  catch (e)
+  {
+    alert (e.message);
+  }
+
+  try {
+    var elt = document.getElementById( "pwgImageCounters" );
+    elt.innerHTML = this.buildCurrentPictureCounterHtml( marker );
+  }
+  catch (e)
+  {
+    alert (e.message);
+  }
+  return false;
+}
+
+}
+
+PwgDataHandler.blowupUrl = function(theUrl)
+{
+  var win = new Window({
+    title: document.title,
+    url: theUrl,
+    classname: "alphacube",
+    left: 0, top: 0,
+    width:"640", height:"480",
+    minimizable: false,
+    maximizable: false,
+    draggable: false,
+    resizable: false,
+    destroyOnClose: true,
+    hideEffect: Element.hide,
+    showEffectOptions: {duration:1.5}
+  });
+
+  win.setConstraint(true, {top:20, left:20, bottom:30, right:20} );
+  win.maximize();
+  win.showCenter(true, 20, 20);
+  return false;
+}
+
+
+String.prototype.urlNoHtm = function()
+{
+  return this.replace( "&amp;", "&" );
+}
Index: /extensions/rv_gmaps/tags/2.0.c/template/data_loader.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/data_loader.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/data_loader.js	(revision 4071)
@@ -0,0 +1,189 @@
+function PwgDataLoader( map, opts )
+{
+  this._map = map;
+  this.options = Object.extend(
+    {
+      reload_data_timeout: 1800,
+      rectangle_of_confusion: G_DEFAULT_ICON.iconSize
+    }
+    , opts || {} );
+  this.options.rectangle_of_confusion = new GSize( this.options.rectangle_of_confusion.width, this.options.rectangle_of_confusion.height );
+  if (this.options.rectangle_of_confusion.width<16) this.options.rectangle_of_confusion.width=16;
+  if (this.options.rectangle_of_confusion.height<16) this.options.rectangle_of_confusion.height=16;
+}
+
+
+// returns the number of digits (between 0 and 6) to round a lat/lon so that it does not vary by more than factor*pow(-exp)
+getLatLonDigits = function(d, factor, exp)
+{
+  if (d<0) return getLatLonDigits(-d,factor,exp);
+  var digits = Math.ceil( exp - Math.log(d*factor)/Math.LN10 );
+  return digits<0 ? 0 : (digits>6 ? 6 : digits);
+}
+
+Math.roundN = function(d, digits) { var c = Math.pow(10,digits); return Math.round(d*c)/c; }
+
+
+PwgDataLoader.prototype =  {
+
+
+_urlMapData: null,
+_timerReloadData: null,
+_dataLoading: false,
+
+_previousLoadDataReq : {box: null, zoom:0, resultBounds:null},
+
+start: function(urlMapData)
+{
+  this._urlMapData = urlMapData;
+  GEvent.bind( this._map, "movestart", this, this.clearTimerReloadData );
+  GEvent.bind( this._map, "moveend", this, this._onMapMoveEnd );
+  this._loadData();
+},
+
+terminate: function()
+{
+  this.clearTimerReloadData();
+  this._map = null;
+},
+
+clearTimerReloadData : function()
+{
+  if (this._timerReloadData)
+  {
+    clearTimeout( this._timerReloadData );
+    this._timerReloadData = null;
+    return true;
+  }
+  return false;
+},
+
+_onMapMoveEnd: function()
+{
+  this.clearTimerReloadData();
+  this._timerReloadData = setTimeout(
+    this._onTimeoutLoadData.bind(this),
+    this.options.reload_data_timeout);
+},
+
+
+_onTimeoutLoadData: function ()
+{
+  if (this._dataLoading) return; // still in progress
+  this.clearTimerReloadData();
+  this._loadData();
+},
+
+_loadData: function()
+{
+  var bounds = this._map.getBounds();
+
+  var latRange = bounds.toSpan().lat();
+  var latPrec = latRange * this.options.rectangle_of_confusion.height / this._map.getSize().height;
+
+  var lonRange = bounds.toSpan().lng();
+  var lonPrec = ( lonRange>=0 ? lonRange : 360-lonRange )* this.options.rectangle_of_confusion.width / this._map.getSize().width;
+
+  if ( this._previousLoadDataReq.box!=null )
+  { // not the first time
+    if ( this._previousLoadDataReq.box.containsBounds( bounds ) )
+    {
+      if ( this._previousLoadDataReq.resultBounds == null )
+        return; // no images here
+      if ( this._map.getZoom() <= this._previousLoadDataReq.zoom )
+        return;
+      else
+      {
+        if (
+          this._map.getZoom() == this._previousLoadDataReq.zoom + 1
+          && this._map.getZoom() < this._map.getBoundsZoomLevel(this._previousLoadDataReq.resultBounds)
+          )
+        {
+          if (document.is_debug) GLog.write('no load: zoom crt: '+this._map.getZoom()+'; prev: '+this._previousLoadDataReq.zoom+'; target: '+this._map.getBoundsZoomLevel(this._previousLoadDataReq.resultBounds));
+          return;
+        }
+      }
+    }
+    else
+    {
+    }
+  }
+
+  var nd=0, sd=0, ed=0, wd=0;
+  if ( !bounds.isFullLat() )
+  {
+    nd = latRange*12/100;
+    sd = latRange*4/100;
+  }
+  if ( !bounds.isFullLng() )
+  {
+    ed = lonRange*9/100;
+    wd = lonRange*7/100;
+  }
+  var digits = Math.max( getLatLonDigits(latRange,4,2), getLatLonDigits(lonRange,4,2) );
+  var box = new GLatLngBounds(
+      new GLatLng( Math.roundN(bounds.getSouthWest().lat()-sd,digits), Math.roundN( bounds.getSouthWest().lng()-wd,digits ) ),
+      new GLatLng( Math.roundN(bounds.getNorthEast().lat()+nd,digits), Math.roundN(bounds.getNorthEast().lng()+ed,digits) )
+    );
+  
+  var url = this._urlMapData;
+  url += "&box=" + box.getSouthWest().toUrlValue(5) + "," + box.getNorthEast().toUrlValue(5);
+  url += "&lap=" +  Math.roundN( latPrec, getLatLonDigits(latPrec,1,1) ).toString();
+  url += "&lop=" +  Math.roundN( lonPrec, getLatLonDigits(lonPrec,1,1) ).toString();
+  
+  if (document.is_debug) {
+    GLog.write( "bounds: " + bounds.getSouthWest().toUrlValue() + " " + bounds.getNorthEast().toUrlValue() +"; zoom: "+this._map.getZoom() +"; size: "+this._map.getSize().toString() +"; c: "+this._map.getCenter().toUrlValue() );
+    GLog.writeUrl( url );
+  }
+  
+  this._previousLoadDataReq.box = box;
+  this._previousLoadDataReq.zoom = this._map.getZoom();
+  this._previousLoadDataReq.resultBounds = null;
+  this._dataLoading = true;
+
+  try {
+    GEvent.trigger( this, "dataloading" );
+    GDownloadUrl(url, this._onDataReceived.bind(this) );
+  }
+  catch (e) {
+    this._dataLoading = false;
+    this._previousLoadDataReq.box=null;
+    GEvent.trigger( this, "dataloadfailed", 600, e );
+  }
+},
+
+_onDataReceived: function(data, responseCode)
+{
+  var resp;
+  try
+  {
+    if (responseCode!=200)
+    {
+      this._previousLoadDataReq.box=null;
+      GEvent.trigger( this, "dataloadfailed", responseCode, null );
+    }
+    else
+    try
+    {
+  		eval('resp = ' + data);
+  		if (resp.nb_items == undefined)
+        throw new Error( "DATA DECODING ERROR" );
+
+  		this._previousLoadDataReq.resultBounds = resp.bounds;
+  		if (document.is_debug && resp.debug) GLog.write( resp.debug );
+  		GEvent.trigger( this, "dataloaded", resp );
+  	}
+  	catch (e)	{
+      this._previousLoadDataReq.box=null;
+  	  GEvent.trigger( this, "dataloadfailed", responseCode, e );
+  		var s = e.message;
+  		s += '\n' + data.substr(0,1000);
+  		alert( s );
+  	}
+  }
+  finally {
+    this._dataLoading = false;
+  }
+}
+
+}
Index: /extensions/rv_gmaps/tags/2.0.c/template/earth_kml.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/earth_kml.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/earth_kml.tpl	(revision 4071)
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="{$CONTENT_ENCODING}"?>
+<kml xmlns="http://earth.google.com/kml/2.2">
+<Document>
+	<Style id="img_normal">
+		<IconStyle> 
+			<Icon><href>http://maps.google.com/mapfiles/kml/shapes/camera.png</href></Icon>
+			<scale>1</scale>
+		</IconStyle> 
+	</Style>
+	<Style id="img_highlight">
+		<IconStyle> 
+			<Icon><href>http://maps.google.com/mapfiles/kml/shapes/camera.png</href></Icon>
+			<scale>1</scale>
+		</IconStyle> 
+	</Style>
+	<StyleMap id="img">
+		<Pair>
+			<key>normal</key><styleUrl>#img_normal</styleUrl>
+		</Pair>
+		<Pair>
+			<key>highlight</key><styleUrl>#img_highlight</styleUrl>
+		</Pair>
+	</StyleMap>
+  <name><![CDATA[{$PAGE_TITLE}]]></name>
+  <description><![CDATA[{$NB_ITEMS_DESC|@default}<a href="{$U_INDEX}">{'Go to'|@translate} {$PAGE_TITLE}</a><br/>{$PAGE_COMMENT}
+]]></description>
+{if not empty($region)}
+<Region> 
+  <LatLonAltBox> 
+    <north>{$region.n}</north>
+    <south>{$region.s}</south>
+    <east>{$region.e}</east>
+    <west>{$region.w}</west>
+  </LatLonAltBox> 
+</Region> 
+{/if}
+
+{if not empty($categories)}
+{foreach from=$categories item=category}
+<NetworkLink>
+  <visibility>0</visibility>
+  <name><![CDATA[{$category.NAME}]]></name>
+  <description><![CDATA[{$category.COMMENT}
+	{if not empty($category.TN_SRC)}
+    <br/><img src="{$category.TN_SRC}" />
+	{/if}
+]]></description>
+  <Link>
+    <href>{$category.U_KML}</href>
+  </Link>   
+{if not empty($category.region)}
+  <Region>
+    <LatLonAltBox>
+      <north>{$category.region.n}</north>
+      <south>{$category.region.s}</south>
+      <east>{$category.region.e}</east>
+      <west>{$category.region.w}</west>
+    </LatLonAltBox>
+  </Region>
+  <TimeSpan>
+    <begin>{$category.region.min_date}</begin>
+    <end>{$category.region.max_date}</end>
+  </TimeSpan>
+{/if}
+</NetworkLink>
+{/foreach}
+{/if}
+
+{if not empty($images)}
+{foreach from=$images item=img}
+	<Placemark>
+		<visibility>1</visibility>
+		<open>1</open>
+		<name><![CDATA[{$img.TITLE}]]></name>
+		<Snippet>{$img.DESCRIPTION|@strip_tags:false|@truncate:80}</Snippet>
+		<description><![CDATA[
+{$img.DESCRIPTION}<br/>
+<a href="{$img.U_PAGE}"><img src="{$img.TN_SRC}"/></a><br/>
+]]></description>
+		<styleUrl>#img</styleUrl>
+		<Point>
+			<coordinates>{$img.LON},{$img.LAT},0</coordinates>
+		</Point>
+		{if not empty($img.date_creation)}
+		<TimeStamp>
+			<when>{$img.date_creation}</when>
+		</TimeStamp>
+		{/if}
+	</Placemark>
+{/foreach}
+{/if}
+</Document>
+</kml>
Index: /extensions/rv_gmaps/tags/2.0.c/template/map.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/map.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/map.tpl	(revision 4071)
@@ -0,0 +1,225 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
+<head>
+<meta http-equiv="content-type" content="text/html; charset={$CONTENT_ENCODING}" />
+<meta name="robots" content="noindex,nofollow" />
+<title>{$GALLERY_TITLE}</title>
+<link rel="stylesheet" type="text/css" href="{$PLUGIN_ROOT_URL}/template/style.css?v={$RVM_PLUGIN_VERSION}" />
+<!--[if IE]>
+<style type="text/css">
+v\:* {ldelim}
+  behavior:url(#default#VML);
+}
+</style>
+<![endif]-->
+<!--[if lt IE 7]>
+<style type="text/css">
+	#map {ldelim} position:auto; height: 100%; }
+</style>
+<![endif]-->
+
+<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key={$GMAPS_API_KEY}&amp;hl={$lang_info.code}" type="text/javascript"></script>
+<script src="{$PLUGIN_ROOT_URL}/template/data_loader.js?v={$RVM_PLUGIN_VERSION}" type="text/javascript"></script>
+<script src="{$PLUGIN_ROOT_URL}/template/data_handler.js?v={$RVM_PLUGIN_VERSION}" type="text/javascript"></script>
+<script src="{$PLUGIN_ROOT_URL}/template/page_linker.js?v={$RVM_PLUGIN_VERSION}" type="text/javascript"></script>
+
+<script src="{$PLUGIN_ROOT_URL}/template/windows_13/javascripts/prototype.js" type="text/javascript"></script>
+<link href="{$PLUGIN_ROOT_URL}/template/windows_13/themes/default.css" rel="stylesheet" type="text/css" />
+<link href="{$PLUGIN_ROOT_URL}/template/windows_13/themes/alphacube.css" rel="stylesheet" type="text/css" />
+
+<script type="text/javascript">{literal}
+//<![CDATA[
+var map;
+var geocoder;
+
+document.is_debug = false;
+if ( document.location.search.match(/[\?&]debug/) ) {
+  document.is_debug = true;
+  document._window = window;
+}
+  
+function load()
+{
+	if (!GBrowserIsCompatible())
+	return;
+
+	var mapElement = document.getElementById("map");
+	map = new GMap2( mapElement );
+
+	map.addMapType( G_PHYSICAL_MAP );
+	map.addControl(new GLargeMapControl());
+	map.addControl(new GHierarchicalMapTypeControl());
+	map.addControl(new GScaleControl());
+
+	pwgPageLinker = new PageLinker(map, "aLinkToThisPage" );
+	if ( !map.isLoaded() )
+	{
+{/literal}
+		{if isset($initial_bounds)}
+    var bounds = new GLatLngBounds( new GLatLng({$initial_bounds.s},{$initial_bounds.w}), new GLatLng({$initial_bounds.n},{$initial_bounds.e}) );
+    map.setCenter( bounds.getCenter(), map.getBoundsZoomLevel( bounds ) );
+		{/if}
+{literal}
+		if ( !map.isLoaded() )
+			map.setCenter( new GLatLng(0,0), 2 );
+  }
+
+  try {
+		// the overview must be added after setting map center
+		var ovcontrol = new GOverviewMapControl(new GSize(165,165));
+		map.addControl(ovcontrol);
+  }
+  catch(e){ alert( e.message ); }
+  
+	map.enableScrollWheelZoom();
+	map.enableDoubleClickZoom();
+  
+	var pwgMarkerIcon = (function(){ {/literal}{$MAP_MARKER_ICON_JS}{literal} }).call(null);
+
+	map.pwgDataLoader = new PwgDataLoader(map, {rectangle_of_confusion: pwgMarkerIcon.iconSize} );
+	GEvent.addListener(map.pwgDataLoader, "dataloading", function() {
+		var pre = '<img src="{/literal}{$PLUGIN_ROOT_URL}{literal}/icons/progress_s.gif" width="16" height="16" alt="~" /> ';
+		document.getElementById("dataLoadStatus").innerHTML = pre + Localization.get("Loading");
+		}
+	);
+  
+	GEvent.addListener(map.pwgDataLoader, "dataloadfailed", function(responseCode) {
+		document.getElementById("dataLoadStatus").innerHTML = Localization.get("Failed") + " "+responseCode;
+		}
+		);
+
+	map.pwgDataHandler = new PwgDataHandler(map, {icon: pwgMarkerIcon, show_all_img_src: "{/literal}{$PLUGIN_ROOT_URL}{literal}/icons/pic_s.gif" } );
+	GEvent.addListener(map.pwgDataLoader, "dataloaded", map.pwgDataHandler.handle.bind(map.pwgDataHandler) );
+
+	map.pwgDataLoader.start( "{/literal}{$U_MAP_DATA}{literal}" );
+
+	if (document.is_debug)
+	{
+		GEvent.addListener(map, "moveend", function() { GLog.write("movend c:"+map.getCenter().toUrlValue());} );
+		GEvent.addListener(map, "maptypechanged", function() { GLog.write("maptypechanged");} );
+		GEvent.addListener(map, "infowindowopen", function() { GLog.write("infowindowopen");} );
+		GEvent.addListener(map, "infowindowclose", function() { GLog.write("infowindowclose");} );
+  }
+}
+
+function unload()
+{
+	if (map)
+	{
+		!map.pwgDataLoader || map.pwgDataLoader.terminate();
+		!map.pwgDataHandler || map.pwgDataHandler.terminate();
+	}
+	GUnload();
+}
+
+function searchLocation(q)
+{
+	if (!q.length) return false;
+	if (!geocoder)
+		geocoder = new GClientGeocoder();
+	geocoder.getLocations(q, function(resp)
+	  {
+	    if ( resp && resp.Status )
+	    {
+	      document._geoResponse = resp;
+	      if (resp.Status.code==200)
+	      {
+	        var zoom = 2;
+	        switch (resp.Placemark[0].AddressDetails.Accuracy)
+	        {
+	          case 1: zoom=5; break; //country
+	          case 2: zoom=7; break; //region
+	          case 3: zoom=8; break; //county
+	          case 4: zoom=12; break; //town
+	          case 5: zoom=13; break; //post code
+	          case 6: zoom=14; break; //street
+	          case 7: case 8: zoom=16; break; //intersection/exact
+	        }
+	        map.setCenter( new GLatLng( resp.Placemark[0].Point.coordinates[1], resp.Placemark[0].Point.coordinates[0] ), zoom);
+	      }
+	      else
+	        alert("This address has not been found\nCode:"+resp.Status.code);
+	    }
+	  });
+	return false;
+}
+
+
+var Localization =
+{
+  _strings: {
+{/literal}
+"go to %s": "{'go to %s'|@translate|@escape:javascript}",
+"Next": "{'Next'|@translate|@escape:javascript}",
+"Prev": "{'Prev'|@translate|@escape:javascript}",
+"out of %d": "{'out of %d'|@translate|@escape:javascript}",
+"Loading": "{'Loading'|@translate|@escape:javascript}",
+"Failed": "{'Failed'|@translate|@escape:javascript}",
+"Show all": "{'Show all'|@translate|@escape:javascript}",
+"show all pictures around this location": "{'show all pictures around this location'|@translate|@escape:javascript}",
+"displays %s on a map" : "{'displays %s on a map'|@translate|@escape:javascript}",
+"opens %s in Google Earth" : "{'opens %s in Google Earth'|@translate|@escape:javascript}",
+"%d elements" : "{'%d elements'|@translate|@escape:javascript}",
+{literal}
+dontMindTheComma: ""
+    },
+
+  get: function( str ) {
+		var lang_str = this._strings[str];
+		if (lang_str == undefined)
+		{
+			if (document.is_debug) GLog.write("Language string undefined '"+ str+"'");
+			return str;
+		}
+		return lang_str;
+	},
+
+  getQ: function( str ) {
+		return '"'+this.get(str)+'"';
+	},
+
+  fmt1: function () {
+		var args = $A(arguments);
+		var str = args.shift();
+		str = this.get(str);
+		str = str.replace( '%s', "#\{0\}" ).replace( '%d', "#\{0\}" );
+		return new Template(str).evaluate( args );
+	}
+}
+{/literal}
+//]]>
+</script>
+</head>
+
+<body onload="load()" onunload="unload()">
+
+<div id="titlebar">
+  <div class="titlebar_links" >
+    <span id="dataLoadStatus"></span>
+    <a id="aKml" href="" type="application/vnd.google-earth.kml+xml" class="gmnoprint"><img src="http://maps.google.com/mapfiles/ms/view_as_kml.png" width="16" height="16" alt="kml" /> <span>KML</span></a>
+    <a id="aLinkToThisPage" href="" class="gmnoprint"><img src="http://maps.google.com/mapfiles/bar_icon_link.gif" alt="&lt;-&gt;" width="16" height="16" /> <span>{'Link to this page'|@translate}</span></a>
+    <a id="aBlowup" href="" onclick="return PwgDataHandler.blowupUrl(this.href);" class="gmnoprint" title="{'show all pictures around this location'|@translate}">
+			<img src="{$PLUGIN_ROOT_URL}/icons/pic_s.gif" alt="" title="{'show all pictures around this location'|@translate}" />
+			<span>{'Show all'|@translate}</span>
+		</a>
+    <span class="gmnoprint">
+			<form action="#" onsubmit="if (this.q.value!='' &amp;&amp; this.q.value!='{'Search a location...'|@translate|@escape:javascript}') searchLocation(this.q.value);return false;">
+				<input type="text" size="32" name="q"
+					value="{'Search a location...'|@translate}"
+					onfocus="if (value=='{'Search a location...'|@translate|@escape:javascript}') value='';"
+					onblur="if (value=='') value='{'Search a location...'|@translate|@escape:javascript}'" />
+				<input type="submit" value="{'Search'|@translate}"/>
+			</form>
+		</span>
+  </div>
+  <div class="titlebar_title">
+    <a href="{$U_HOME}" title="{'return to homepage'|@translate}" class="gmnoprint">{'home'|@translate}</a>
+    <a href="{$U_HOME_MAP}" title="{'displays all images on a map'|@translate}"><img src="{$PLUGIN_ROOT_URL}/icons/map_sw.gif" width="32" height="17" alt="map"/></a>
+    {'Viewing'|@translate}: <a id="aPageUrl" href=""></a>
+  </div>
+</div>
+
+<div id="map"> </div>
+<script src="{$PLUGIN_ROOT_URL}/template/windows_13/javascripts/window.js" type="text/javascript" defer="defer"></script>
+</body>
+</html>
Index: /extensions/rv_gmaps/tags/2.0.c/template/mapl.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/mapl.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/mapl.tpl	(revision 4071)
@@ -0,0 +1,63 @@
+{html_head}
+<style type="text/css">
+.fullTagCloud {ldelim}
+	text-align: justify;
+	padding: 0;
+	margin: 0.5em 1em 0.5em 1em;
+}
+.fullTagCloud LI {ldelim}
+	display: inline;white-space: nowrap;
+}
+</style>
+{/html_head}
+
+<div id="content" class="content" style="margin: 0;">
+	<div class="titrePage">
+		<ul class="categoryActions">
+			<li><a href="{$U_KML}" title="{$KML_LINK_TITLE}" rel="nofollow" type="application/vnd.google-earth.kml+xml"><img src="{$PLUGIN_ROOT_URL}/icons/earth_m.png" class="button" alt="earth"></a></li>
+			<li><a target="_top" href="{$U_HOME}" title="{'return to homepage'|@translate}"><img src="{$ROOT_URL}{$themeconf.icon_dir}/home.png" class="button" alt="{'home'|@translate}"/></a></li>
+		</ul>
+		<h2>{$TITLE}</h2>
+	</div>
+
+{assign var='displays_x_on_a_map' value='displays %s on a map'|@translate }
+{if not empty($related_categories)}
+	<ul class="fullTagCloud">
+		<li>{'Categories'|@translate}:</li>
+		{foreach from=$related_categories item=cat}
+		<li>{strip}
+			<a target="_top" href="{$cat.URL}" class="{$cat.CLASS}" title="{$cat.TITLE}">{$cat.NAME}</a>
+			<a target="_top" href="{$cat.U_MAP}" title="{$pwg->sprintf($displays_x_on_a_map, $cat.NAME)}" ><img src="{$PLUGIN_ROOT_URL}/icons/map_s.png" alt="map" class="button" /></a>
+		{/strip}</li>&nbsp;
+		{/foreach}
+	</ul>
+{/if}
+
+{if not empty($related_tags)}
+	<ul class="fullTagCloud">
+		<li>{'Tags'|@translate}:</li>
+		{foreach from=$related_tags item=tag}
+		<li>{strip}
+			<a target="_top" href="{$tag.URL}" class="tagLevel{$tag.level}{$tag.radu_class|@default}" title="{$tag.TITLE}">{$tag.name}</a>
+			<a target="_top" href="{$tag.U_MAP}" title="{$pwg->sprintf($displays_x_on_a_map, $tag.name)}"><img src="{$PLUGIN_ROOT_URL}/icons/map_s.png" alt="map" class="button" /></a>
+		{/strip}</li>&nbsp;
+		{/foreach}
+	</ul>
+{/if}
+
+{if not empty($NAVBAR)}<div class="navigationBar">{$NAVBAR}</div>{/if}
+
+{if not empty($thumbnails)}
+<ul class="thumbnails">
+	{foreach from=$thumbnails item=thumbnail}
+	<li><span class="wrap1"><span class="wrap2">
+			<a href="{$thumbnail.URL}" title="{$thumbnail.TN_TITLE}">
+				<img class="thumbnail" src="{$thumbnail.TN_SRC}" alt="{$thumbnail.TN_ALT}" />
+			</a>
+			</span></span>
+	</li>
+	{/foreach}
+</ul>
+{/if}
+
+</div>
Index: /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_orange.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_orange.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_orange.tpl	(revision 4071)
@@ -0,0 +1,6 @@
+var icon = new GIcon();
+icon.image = "{$PLUGIN_ROOT_URL}/template/markers/small_bullet_orange.png";
+icon.iconSize = new GSize(11, 11);
+icon.iconAnchor = new GPoint(5, 5);
+icon.infoWindowAnchor = new GPoint(5, 0);
+return icon;
Index: /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_red.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_red.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_red.tpl	(revision 4071)
@@ -0,0 +1,6 @@
+var icon = new GIcon();
+icon.image = "{$PLUGIN_ROOT_URL}/template/markers/small_bullet_red.png";
+icon.iconSize = new GSize(11, 11);
+icon.iconAnchor = new GPoint(5, 5);
+icon.infoWindowAnchor = new GPoint(5, 0);
+return icon;
Index: /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_yellow.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_yellow.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/markers/small_bullet_yellow.tpl	(revision 4071)
@@ -0,0 +1,6 @@
+var icon = new GIcon();
+icon.image = "{$PLUGIN_ROOT_URL}/template/markers/small_bullet_yellow.png";
+icon.iconSize = new GSize(11, 11);
+icon.iconAnchor = new GPoint(5, 5);
+icon.infoWindowAnchor = new GPoint(5, 0);
+return icon;
Index: /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_blue.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_blue.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_blue.tpl	(revision 4071)
@@ -0,0 +1,8 @@
+var icon = new GIcon();
+icon.image = "http://labs.google.com/ridefinder/images/mm_20_blue.png";
+icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
+icon.iconSize = new GSize(12, 20);
+icon.shadowSize = new GSize(22, 20);
+icon.iconAnchor = new GPoint(6, 20);
+icon.infoWindowAnchor = new GPoint(5, 1);
+return icon;
Index: /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_green.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_green.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_green.tpl	(revision 4071)
@@ -0,0 +1,8 @@
+var icon = new GIcon();
+icon.image = "http://labs.google.com/ridefinder/images/mm_20_green.png";
+icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
+icon.iconSize = new GSize(12, 20);
+icon.shadowSize = new GSize(22, 20);
+icon.iconAnchor = new GPoint(6, 20);
+icon.infoWindowAnchor = new GPoint(5, 1);
+return icon;
Index: /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_purple.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_purple.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_purple.tpl	(revision 4071)
@@ -0,0 +1,8 @@
+var icon = new GIcon();
+icon.image = "http://labs.google.com/ridefinder/images/mm_20_purple.png";
+icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
+icon.iconSize = new GSize(12, 20);
+icon.shadowSize = new GSize(22, 20);
+icon.iconAnchor = new GPoint(6, 20);
+icon.infoWindowAnchor = new GPoint(5, 1);
+return icon;
Index: /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_red.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_red.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/markers/small_dot_red.tpl	(revision 4071)
@@ -0,0 +1,8 @@
+var icon = new GIcon();
+icon.image = "http://labs.google.com/ridefinder/images/mm_20_red.png";
+icon.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
+icon.iconSize = new GSize(12, 20);
+icon.shadowSize = new GSize(22, 20);
+icon.iconAnchor = new GPoint(6, 20);
+icon.infoWindowAnchor = new GPoint(5, 1);
+return icon;
Index: /extensions/rv_gmaps/tags/2.0.c/template/page_linker.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/page_linker.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/page_linker.js	(revision 4071)
@@ -0,0 +1,88 @@
+function PageLinker(map, aElementId)
+{
+  this._map = map;
+  this._elementId = aElementId;
+
+  PageLinker.url2Map( map );
+  
+  GEvent.bind( this._map, "maptypechanged", this, this._regenerateUrl );
+  GEvent.bind( this._map, "moveend", this, this._regenerateUrl );
+  if ( this._map.isLoaded() )
+    this._regenerateUrl();
+}
+
+PageLinker.getQueryVars = function()
+{
+  var vars = {};
+  var qString = unescape( top.location.search.substring(1) );
+  if (qString.length==0)
+    return vars;
+  var pairs = qString.split(/\&/);
+  for (var i=0; i<pairs.length; i++ )
+  {
+    var nameVal = pairs[i].split(/\=/);
+    vars[nameVal[0]] = nameVal[1];
+  }
+  return vars;
+};
+
+PageLinker.map2Url = function( map )
+{
+  var vars = PageLinker.getQueryVars();
+  vars['ll'] = map.getCenter().toUrlValue(5);
+  vars['z'] = map.getZoom();
+  if ( map.getCurrentMapType()===G_NORMAL_MAP )
+    { if (vars['t']) vars['t']=null; }
+  else
+    vars['t']=map.getCurrentMapType().getUrlArg();
+
+  var url = document.location.protocol+'//'+document.location.hostname+document.location.pathname;
+  var bFirst = true;
+  for (var key in vars)
+  {
+    url += bFirst ? "?" : "&";
+    bFirst = false;
+    url += key;
+    if (vars[key]!=null)
+      url += "="+vars[key];
+  }
+  return url;
+};
+
+PageLinker.url2Map = function( map )
+{
+  var vars = PageLinker.getQueryVars();
+  if ( !( (vars['z'] && vars['ll']) || vars['t'] ) )
+    return;
+
+  var mapType = G_NORMAL_MAP;
+  if  (vars['t'])
+  for (var i=0; i<map.getMapTypes().length; i++)
+    if ( map.getMapTypes()[i].getUrlArg()==vars['t'] )
+    {
+      mapType = map.getMapTypes()[i];break;
+    }
+  
+  if (vars['z'] && vars['ll'])
+  {
+    var zoom = parseFloat(vars['z']);
+    var ll = vars['ll'].split( "," );
+    if (ll.length==2)
+    {
+      var center = new GLatLng( ll[0], ll[1] );
+      map.setCenter( center, zoom, mapType );
+    }
+  }
+}
+
+
+PageLinker.prototype = {
+
+_regenerateUrl: function()
+{
+  var elt = document.getElementById( this._elementId );
+  if (!elt) return;
+  elt.href = PageLinker.map2Url(this._map);
+}
+
+}
Index: /extensions/rv_gmaps/tags/2.0.c/template/picture_map_content.tpl
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/picture_map_content.tpl	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/picture_map_content.tpl	(revision 4071)
@@ -0,0 +1,99 @@
+{html_head}
+<script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key={$GMAPS_API_KEY}&amp;hl={$lang_info.code}" type="text/javascript"></script>
+<script src="{$PLUGIN_ROOT_URL}/template/windows_13/javascripts/prototype.js" type="text/javascript"></script>
+<script src="{$PLUGIN_ROOT_URL}/template/windows_13/javascripts/window.js" type="text/javascript" defer="defer"></script>
+<link href="{$PLUGIN_ROOT_URL}/template/windows_13/themes/default.css" rel="stylesheet" type="text/css">
+<link href="{$PLUGIN_ROOT_URL}/template/windows_13/themes/alphacube.css" rel="stylesheet" type="text/css">
+
+<style type="text/css">{literal}
+#mapPicture {
+	position:absolute;
+	width: 310px;
+}
+
+#map {
+	position:relative;
+	left: 320px;
+	margin-right: 320px;
+	height: 512px;
+	min-height: 512px;
+}
+
+#thumbPrev, #thumbNext {display:none;}
+{/literal}</style>
+{/html_head}
+
+<div id="mapPicture">
+<a href="{$U_NO_MAP}" title="{'mode_normal_hint'|@translate}" rel="nofollow"><img src="{$TN_SRC}" alt="thumb"/></a>
+<br/>
+<a href="{$U_BLOWUP}" onclick="return blowupUrl(this.href);">{'More pictures near this location'|@translate}</a>
+<br/>
+{$COMMENT_IMG}
+<br/>
+</div>
+
+<div id="map"></div>
+
+<div style="clear:both"></div>
+<script type="text/javascript">{literal}
+//<![CDATA[
+function onLoad()
+{
+  if (!GBrowserIsCompatible())
+    return;
+
+  var mapElement = document.getElementById("map");
+  var map = new GMap2( mapElement );
+  
+  map.addControl(new GLargeMapControl());
+  map.addControl(new GMapTypeControl());
+  map.addControl(new GScaleControl());
+{/literal}{if isset($coordinates)}
+  var coord = new GLatLng( {$coordinates.LAT}, {$coordinates.LON} );
+  map.setCenter( coord, 12 );
+  var marker = new GMarker(coord);
+  map.addOverlay(marker);
+{/if}{literal}
+  if ( !map.isLoaded() )
+    map.setCenter( new GLatLng(0,0), 2);
+  try {
+    // the overview must be added after setting map center
+    var ovcontrol = new GOverviewMapControl(new GSize(165,165));
+		map.addControl(ovcontrol);
+		var ov_map = ovcontrol.getOverviewMap();
+		GEvent.addListener(map, 'maptypechanged', function(){
+			ov_map.setMapType(G_NORMAL_MAP);
+		});
+  }
+  catch(e){ alert( e.message ); }
+  
+  map.enableDoubleClickZoom();
+}
+
+function blowupUrl(theUrl)
+{
+  var win = new Window({
+    title: document.title,
+    url: theUrl,
+    classname: "alphacube",
+    left: 0, top: 0,
+    width:"640", height:"480",
+    minimizable: false,
+    maximizable: false,
+    draggable: false,
+    resizable: false,
+    destroyOnClose: true,
+    hideEffect: Element.hide,
+    showEffectOptions: {duration:1.5}
+  });
+
+  win.setConstraint(true, {top:20, left:20, bottom:30, right:20} );
+  win.maximize();
+  win.showCenter(true, 20, 20);
+  return false;
+}
+
+Event.observe(window, 'load', onLoad );
+Event.observe(window, 'unload', GUnload );
+//]]>
+{/literal}</script>
Index: /extensions/rv_gmaps/tags/2.0.c/template/style.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/style.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/style.css	(revision 4071)
@@ -0,0 +1,147 @@
+
+HTML, BODY {
+	height: 100%;
+	max-height: 100%;
+	margin: 0;
+	padding: 0;
+	border: 0;
+}
+
+HTML {
+/* hide overflow:hidden from IE5/Mac */
+	overflow: hidden; /*get rid of scroll bars in IE */
+	/* */
+}
+
+BODY {
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 12px;
+}
+
+
+#titlebar {
+	width: 100%;
+	height: 18px;
+	overflow: hidden;
+}
+
+
+#map {
+	overflow: auto;
+	position: absolute;
+	left: 0; width: 100%;
+	top: 18px; bottom: 0;
+}
+
+
+
+
+#titlebar .titlebar_links { float:right; }
+
+#titlebar .titlebar_title {
+  margin-left: 5px;
+  margin-right: 5px;
+}
+
+#titlebar .titlebar_title A {
+  font-size: 14px;
+  font-weight: bold;
+}
+
+#titlebar FORM {
+  display:inline;
+  margin:0;
+}
+
+#titlebar FORM INPUT {
+  border: 1px solid gray;
+  font-size:80%;
+}
+
+#titlebar FORM INPUT {
+  color: #404040;
+  background-color: #dddddd;
+}
+
+#titlebar FORM INPUT:focus {
+  color: black;
+  background-color: #ffffff;
+}
+
+#titlebar .titlebar_links A {
+  border: none;
+  text-decoration: none;
+}
+
+#titlebar .titlebar_links A SPAN {
+  text-decoration: underline;
+}
+
+#titlebar IMG {
+  border: none;
+  vertical-align: bottom;
+}
+
+
+#titlebar #dataLoadStatus {
+	min-width: 150px;
+	width: 150px;
+	margin-right: 10px;
+	margin-left: 5px;
+	font-weight: bold;
+}
+
+
+/* Info Window Structure is
+DIV.gmiw_header
+  SPAN#pwgImageCounters
+  A#gotoPrevImage
+  A#gotoNextImage
+  A#pwgImageBlowup
+DIV#pwgImageDetail
+  DIV.gmiw_imageTitle
+  DIV.gmiw_imageContent
+    DIV.gmiw_imageWrap
+      A IMG
+    DIV.gmiw_imageComment
+*/
+
+.gmiw_header A {
+	margin-left: 5px;
+	border: none;
+	text-decoration: none;
+}
+
+.gmiw_header IMG { vertical-align: bottom; }
+
+.gmiw_header A SPAN { text-decoration: underline; }
+
+.gmiw_header A#pwgImageBlowup {
+	margin-left: 15px;
+	font-weight: bold;
+}
+
+.gmiw_imageTitle {
+	font-size: 14px;
+	font-weight: bold;
+}
+
+.gmiw_imageContent {
+	min-width: 325px;
+	width: 325px;
+	min-height: 168px;
+	height: 168px;
+	overflow-y: auto;
+}
+
+.gmiw_imageWrap {
+	margin: 0 5px -3px 0; /* - for bottom something wrong with Geko and Opera ignored by IE6*/
+	float:left;
+	display:inline;
+}
+
+.gmiw_imageWrap A IMG { border-width: 1px; }
+
+.gmiw_imageComment {  display:inline; }
+
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/MIT-LICENSE
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/MIT-LICENSE	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/MIT-LICENSE	(revision 4071)
@@ -0,0 +1,19 @@
+Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/index.html
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/index.html	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/index.html	(revision 4071)
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+	"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" version="-//W3C//DTD XHTML 1.1//EN" xml:lang="en">
+<head>
+	<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
+
+  <link href="../themes/default.css" rel="stylesheet" type="text/css"/>	
+  <link href="../themes/lighting.css" rel="stylesheet" type="text/css"/>
+  <link href="../themes/mac_os_x.css" rel="stylesheet" type="text/css"/>
+  <link href="./pwc-os.css" rel="stylesheet" type="text/css"/>
+
+  <script type="text/javascript" src="../javascripts/prototype.js"> </script>
+  <script type="text/javascript" src="../javascripts/effects.js"> </script>
+  <script type="text/javascript" src="../javascripts/window.js"> </script>
+  <script type="text/javascript" src="../javascripts/window_effects.js"> </script>
+  <script type="text/javascript" src="../javascripts/debug.js"> </script>
+  <script type="text/javascript" src="./pwc-os.js"> </script>
+	<title>PWC OS</title>
+	
+</head>
+
+<body>
+<div id="dock"> 
+  <div id="theme"> 
+    Theme:
+    <select>
+      <option>Mac OS X</option>
+      <option>Blue lighting</option>
+      <option>Green lighting</option>
+    </select>
+  </div>          
+</div>          
+
+
+</body>
+</html>
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/pwc-os.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/pwc-os.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/pwc-os.css	(revision 4071)
@@ -0,0 +1,36 @@
+body {
+  margin:0;
+  padding:0;
+  overflow: hidden;
+  background: #FFF8DD url(body-bg.png) no-repeat fixed center 10px;
+}         
+
+#dock {
+  position:absolute;
+  bottom:0;
+  left:0;
+  width:100%;
+  height:26px;
+  background:transparent url(dock-bg.gif) repeat-x 0 0;
+  z-index: 100000;	
+}       
+
+.dock_icon {
+  float:left;
+  width:102px;
+  height:21px;
+  margin-top:3px;
+  background: url(button-bg.gif);
+  text-align:center;
+  font-size: 14px;
+  color: #000;
+  line-height: 22px;    
+  margin-right:10px;
+  margin-left:10px;
+}       
+
+#theme {       
+  float:right; 
+  margin-top:3px;
+  margin-right:15px;
+}
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/pwc-os.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/pwc-os.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/PWC-OS/pwc-os.js	(revision 4071)
@@ -0,0 +1,103 @@
+// Overide WindowUtilities getPageSize to remove dock height (for maximized windows)
+WindowUtilities._oldGetPageSize = WindowUtilities.getPageSize;
+WindowUtilities.getPageSize = function() {
+  var size = WindowUtilities._oldGetPageSize();
+  var dockHeight = $('dock').getHeight();
+  
+  size.pageHeight -= dockHeight;
+  size.windowHeight -= dockHeight;
+  return size;
+};    
+
+
+// Overide Windows minimize to move window inside dock  
+Object.extend(Windows, {
+  // Overide minimize function
+  minimize: function(id, event) {
+    var win = this.getWindow(id)
+    if (win && win.visible) {
+      // Hide current window
+      win.hide();            
+    
+      // Create a dock element
+      var element = document.createElement("span");
+      element.className = "dock_icon"; 
+      element.style.display = "none";
+      element.win = win;
+      $('dock').appendChild(element);
+      Event.observe(element, "mouseup", Windows.restore);
+      $(element).update(win.getTitle());
+    
+      new Effect.Appear(element)
+    }
+    Event.stop(event);
+  },                 
+  
+  // Restore function
+  restore: function(event) { 
+    var element = Event.element(event);
+    // Show window
+    element.win.show();
+    //Windows.focus(element.win.getId());                    
+    element.win.toFront();
+    // Fade and destroy icon
+    new Effect.Fade(element, {afterFinish: function() {element.remove()}})
+  }
+})
+
+// blur focused window if click on document
+Event.observe(document, "click", function(event) {   
+  var e = Event.element(event);
+  var win = e.up(".dialog");
+  var dock = e == $('dock') || e.up("#dock"); 
+  if (!win && !dock && Windows.focusedWindow) {
+    Windows.blur(Windows.focusedWindow.getId());                    
+  }
+})               
+
+// Chnage theme callback
+var currentTheme = 0;
+function changeTheme(event) {
+  var index = Event.element(event).selectedIndex;
+  if (index == currentTheme)
+    return;
+
+  var theme, blurTheme;
+  switch (index) {
+    case 0:
+      theme = "mac_os_x";
+      blurTheme = "blur_os_x";
+      break;
+    case 1:
+      theme = "bluelighting";
+      blurTheme = "greylighting";
+      break;
+    case 2:
+      theme = "greenlighting";
+      blurTheme = "greylighting";
+      break;
+  }
+  Windows.windows.each(function(win) {
+    win.options.focusClassName = theme; 
+    win.options.blurClassName = blurTheme;
+    win.changeClassName(blurTheme)
+  });
+  Windows.focusedWindow.changeClassName(theme);
+  currentTheme = index;
+}
+
+// Init webOS, create 3 windows
+function initWebOS() {         
+  // Create 3 windows
+  $R(1,3).each(function(index) {
+    var win = new Window({className: "mac_os_x", blurClassName: "blur_os_x", title: "window #"+index, width:250, height:150, top: 100 + index*50, left:100 + index*50}); 
+    win.getContent().update("<h1>Window #" + index + "</h1>");
+    win.show();    
+  })                 
+  //
+  $$("#theme select").first().selectedIndex = currentTheme;
+  Event.observe($$("#theme select").first(), "change", changeTheme);  
+}
+Event.observe(window, "load", initWebOS)
+               
+               
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/README
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/README	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/README	(revision 4071)
@@ -0,0 +1,182 @@
+== Installation/Usage
+
+Just copy windows.js in your javascript directory, and default.css + default directory in your stylesheets directory 
+See samples/index.html for more details and go on my web page : http://prototype-window.xilinus.com
+
+== Change log        
+- 04/23/07  V 1.3
+            - Added: getTitle
+            - Added: blur/focus function on Windows module
+            - Added: onBlur event  
+            - Fixed: WindowCloseKey works with URL content (iframe) 
+            - Fixed: Modal window with a parent != document.body
+            - Updated: prototype 1.5RC3
+            - Updated: Dialog handle resizable,minimizable, maximizable, draggable and closable options
+- 02/27/07  V 1.2
+            - Added: gridX and gridY constructor's options to snap move and resize 
+            - Added: Effect on modal overlay (fade/appear) only if effects.js in included.
+              You can change effect options (Windows.overlayShowEffectOptions and Windows.overlayHideEffectOptions).
+              
+            - Fixed: Multimodal mode.
+            - Fixed: Works on WebKit.
+              
+            - Beta: effects on minimize and maximize. You need to include window_effects.js to have them.
+            
+- 02/17/07  V 1.1
+            - Constructor has been simplified, now you can just do win = new Window(). By default id is automatically generated and can be passed as options
+              win = new Window({id: "my_id", width: 100, height: 100}) 
+              Backward compatibility with old constructor win = new Window("my_id", {width: 100, height: 100}) 
+            - Observer event can be passed as window option:   win = new Window({onClose: function() {alert('close')}}) 
+            - parent option can be id or element
+            - delegate has been removed (not really usefull) and0 setCloseCallback has been addedinstead. (It could be also passed as a constructor's option closeCallback: your_callcabck)
+              your_callcabck must return true to be able to close the window
+            - add onMove event
+            - fix constraint for minimized window
+            - destroyOnClose could be passed as constructor's option:  win = new Window({destroyOnClose: true}) 
+            - constraint works for maximized windows
+            - Dialog ok and cancel parameters has been renamed to onOk and onCancel for coherence (ok and cancel still works)
+            - Update to Prototype 1.5 and script.aculo.us 1.7
+
+- 01/14/07  V 1.00
+            - add changeClassName to change look and feel dynamically.
+            - add constraint move. Constraint can be on a div or document.
+            - full top and bottom bar are use to move window.
+            - fixed computation of window width or height.
+            - add setURL/getURL/refresh and setHTMLContent. Content can be change dynamically. 
+            - add tooltip.js add on. It's an add-on to add dynamically tooltips on a webpage (see samples/tooltips/tooltip.html)
+              Thanks to Jonathan Modell of  2moromedia.com.
+            
+- 12/06/06  V 0.99
+            - remove addClass that automatically tries to include default.css
+            - add wired move/resize
+            - fix recenterAuto
+            - add show to WindowStore to be able to open a window the first time, wihtout any cookie (check samples/window_store/html)
+
+- 11/06/06  V 0.98
+            - new optional behavior for multi-level modal window.
+            - Two new add-ons (in window_ext.js file) 
+               + WindowStore to save open/close window status.
+               + WindowCloseKey to handle escape key (or any keys) to close windows/dialogs
+
+- 10/26/06  V 0.97
+            - add recenterModal to constructor
+            - setAjaxContent eval response request
+            - modal window multi level
+            - fix close/closeAll issues
+            - add addCss (auto add default.css)
+            
+- 09/26/06  V 0.96.3
+      			- Fixed onClose, no more memory leak and nore issues with sound on IE (even on dialogs)
+      			- add getLocation
+      			- Debug select problem on Firefox
+      			- change mouseup event to onclick event
+      			- Fixed event propagation on mininize/maximize/close
+      			- Add frameborder=0
+      			- Add prototype_window_class_helper.rb by Jorge Díaz (http://xurde.info)
+      			
+- 07/22/06  V 0.96.2
+            - Fixed select issue in modal window			
+            
+- 07/15/06  V 0.96.1
+            - Bugs fixed
+            - Add isVisible()
+            - Update debug.js
+            
+- 07/11/06  V 0.96
+            - New events onShow, onHide, onFocus
+            - isVisible()
+            - Autofit width or height if width or (NOT AND) height is set to null in the constructor
+            - updateWidth / updateHeight if you need to update width or height (useful after changing window content if you do not want scrollbars)
+            - Add top, left to showCenter(modal, top, left) optional arguments if you need to center only left or top value.
+            
+- 06/30/06  V 0.95
+            - Now you can set windows or dialogs content with an Ajax request!!
+            - Fixed IE issue when you destroy window with an url that embeds mp3.
+            - Fixed buttonClass issue for Dialog.
+            - Update samples
+            
+- 06/24/06  V 0.90
+            - Valid XHTML 1.0 Strict!
+            - Fixed minimize function
+            - Fixed destroy on window without hide effects
+            - No more text selection while dragging
+            - Add onMinimize/onMaximize event
+            
+- 06/19/06  V 0.85.2
+            - Remove undeclared vars
+            - Set top/left to 0 if not specify
+            - Destroy objet after hide effect instead of before effect instead
+            - getSize
+            - add extended_debug.js (from Jason Pollard)
+            
+- 06/13/06  V 0.85.1
+            - IE bug fixed
+            
+- 06/12/06  V 0.85
+            - Autofit width or height for Dialog
+            - Better Move/Resize over 
+            - Allow select in modal window (even on IE)
+            - WARNING, ok callback for Dialog should returns true to close the dialog
+            - better window HTML code (no more div inside the td)
+            - Add themes
+            
+- 05/23/06  V 0.80
+            - Add setTitle
+            - Add setStatusBar
+            - Store minimize/maximize in the cookie (Thanks to Ifran)
+            - Add onload constructor parameter (Thanks to Ifran)
+            - Add button class for dialog (Thanks to Felix Shnir)
+            
+- 05/09/06  V 0.75
+            - Update with Script.aculo.us 1.6.1 and Prototype 1.5.0_rc1
+            - Remove PNG for dialog overlay, use opacity as done in lightbox V2
+            - Add Windows.focusedWindow and  Windows.closeAll
+            - Add name to iframe in case of url window
+            - Clean up code, use _ for private function (just name convention)
+            - Add Dialog.info function, usefull for for submit or notice info (in Rails)
+            - Add minimize and maximize buttons
+            - Add alert_lite.css without any images
+            - Debug
+            
+- 04/15/06  V 0.70
+					  - Add autoposition in setContent. The window will at the element location
+					  - Add draggable/closable parameter if you need to specify is the window is draggable/closable or not
+					  - Add parent parameter if you need a specific parent instead of body
+					  - Better resize
+					  - Add setCookie to store window location/size in a cookie
+					  - Add parent.html sample
+
+- 04/05/06  V 0.65 
+           - Update to Prototype 1.5.0_pre1, script.aculo.us 1.6.0
+				   - Add setDestoyOnClose
+				   - Add Windows Observer with onStartResize(), onEndResize(), onStartMove(), onEndMove(), onClose(), onDestroy() events
+				   - Add setContent(id, autoresize)
+
+- 03/29/06  V 0.6  
+           - Add Window delegate to manage close action
+			     - Add modal mode and Dialog class with common panels: alert, confirm
+				   - Clean HTML code and change caracters to lowercase to be XHTML compliant (thanks to nuxygen and Joseph)
+				   - Add showEffectOptions, hideEffectOptions, effectOptions to Window constructor (thanks to Jon)
+				   - Fix checkbox IE bug (big thanks to JCA)
+				   - Fix other little bugs (thanks to nuxygen, Dennis, and all who sent me emails)
+				   - Update samples/index.html
+				   - Add new sample usng frame (samples/inset.html and samples/inframe.html but use only samples/inset.html)
+				
+- 03/27/06 	V 0.51 
+           - New CSS theme structure 
+           - Add url: constructor parameter to have a window with an URL content
+				   - Add bottom/right constructor parameters	
+				   - Update sample files.
+			
+- 03/24/06  V 0.50 Initial revision
+
+
+== License
+
+it is licensed under the terms of the MIT License, see the included MIT-LICENSE file.
+
+== Thanks
+To all of you who sent me bugs, patches and feature requests
+
+http://www.ciudadmovil.com.co/q/mod/mapa/conexion.php
+http://www.desyr.net/
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/helper/prototype_window_class_helper.rb
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/helper/prototype_window_class_helper.rb	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/helper/prototype_window_class_helper.rb	(revision 4071)
@@ -0,0 +1,58 @@
+# Prototype Window Class Helper (http://pwc-helper.xurdeonrails.com)
+# by Jorge Díaz (http://xurde.info)
+# thanks to Sebastien Gruhier for his Prototype Window Class (http://prototype-window.xilinus.com/)
+
+#Quick use:
+#Reference this helper in your rails applicaction adding -> helper :prototype_window_class in your application.rb
+#You must include in the template header the prototype window class javascripts and the .css theme you want to use.
+#This code in your template might be enough:
+
+    #  <%= stylesheet_link_tag 'default' %> (or theme you wanna use)
+    #  <%= stylesheet_link_tag 'alert' %>
+    #  <%= javascript_include_tag :defaults %>
+    #  <%= javascript_include_tag 'window'%>
+
+
+module PrototypeWindowClassHelper
+
+    def params_for_javascript(params) #options_for_javascript doesn't works fine
+        
+        '{' + params.map {|k, v| "#{k}: #{ 
+            case v
+              when Hash then params_for_javascript( v )
+              when String then "'#{v}'"          
+            else v   #Isn't neither Hash or String
+            end }"}.sort.join(', ') + '}'
+    end
+    
+    
+    
+    def link_to_prototype_dialog( name, content, dialog_kind = 'alert', options = { :windowParameters => {} } , html_options = {} )
+    
+        #dialog_kind: 'alert' (default), 'confirm' or 'info' (info dialogs should be destroyed with a javascript function call 'win.destroy')
+        #options for this helper depending the dialog_kind: http://prototype-window.xilinus.com/documentation.html#alert (#confirm or #info)
+    
+        js_code ="Dialog.#{dialog_kind}( '#{content}',  #{params_for_javascript(options) } ); "
+        content_tag(
+               "a", name, 
+               html_options.merge({ 
+                 :href => html_options[:href] || "#", 
+                 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + js_code }))
+    end
+    
+    
+    
+    def link_to_prototype_window( name, window_id, options = { :windowParameters => {} } , html_options = {} )
+        
+        #window_id must be unique and it's destroyed on window close.
+        #options for this helper: http://prototype-window.xilinus.com/documentation.html#initialize
+      
+        js_code ="var win = new Window( '#{window_id}', #{params_for_javascript(options) } );  win.show();  win.setDestroyOnClose();"
+        content_tag(
+               "a", name, 
+               html_options.merge({ 
+                 :href => html_options[:href] || "#", 
+                 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + js_code }))
+    end
+
+end
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/debug.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/debug.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/debug.js	(revision 4071)
@@ -0,0 +1,137 @@
+var debugWindow = null;
+function debug(text, reverse) {
+	if (debugWindow == null)
+ 		return;
+
+	time = "-"; //new Date();
+	if (reverse) {
+		$('debug').innerHTML = time + " " + text + "<br>"+ 	$('debug').innerHTML;
+		debugWindow.getContent().scrollTop=0;
+	}
+	else {
+		$('debug').innerHTML +=  time + " " + text + "<br>";
+		debugWindow.getContent().scrollTop=10000; // Far away 
+	}
+}
+
+function hideDebug() {
+	if (debugWindow) {
+		debugWindow.destroy();
+		debugWindow = null;
+	}
+}
+
+function showDebug(bShow) {
+ if (debugWindow == null) {
+  debugWindow = new Window('debug_window', {className: 'dialog',width:250, height:100, right:4, bottom:42, zIndex:1000, opacity:1, showEffect: Element.show, resizable: true, title: "Debug"})
+  debugWindow.getContent().innerHTML = "<style>#debug_window .dialog_content {background:#000;}</style> <div id='debug'></div>";
+  date=new Date;
+    date.setMonth(date.getMonth()+3);
+    
+  //debugWindow.setCookie(null, date);
+ }
+ if( typeof bShow == 'undefined' || bShow)debugWindow.show()
+}
+
+
+function clearDebug() {
+	if (debugWindow == null)
+ 		return;
+	$('debug').innerHTML = "";
+}
+
+/**
+ * document.createElement convenience wrapper
+ *
+ * The data parameter is an object that must have the "tag" key, containing
+ * a string with the tagname of the element to create.  It can optionally have
+ * a "children" key which can be: a string, "data" object, or an array of "data"
+ * objects to append to this element as children.  Any other key is taken as an
+ * attribute to be applied to this tag.
+ *
+ * Available under an MIT license:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * @param {Object} data The data representing the element to create
+ * @return {Element} The element created.
+ */
+function $E(data) {
+  var el;
+  if ('string'==typeof data) {
+      el=document.createTextNode(data);
+  } else {
+    //create the element
+    el=document.createElement(data.tag);
+    delete(data.tag);
+
+    //append the children
+    if ('undefined'!=typeof data.children) {
+      if ('string'==typeof data.children ||'undefined'==typeof data.children.length) {
+        //strings and single elements
+        el.appendChild($E(data.children));
+      } else {
+        //arrays of elements
+        for (var i=0, child=null; 'undefined'!=typeof (child=data.children[i]); i++) {
+            el.appendChild($E(child));
+        }
+      }
+      delete(data.children);
+    }
+
+    //any other data is attributes
+    for (attr in data) {
+      el[attr]=data[attr];
+    }
+  }
+
+  return el;
+}
+
+// FROM Nick Hemsley
+var Debug = {
+	inspectOutput: function (container, within) {
+		within = within || debugWindow.getContent()
+		
+		if (debugWindow == null)
+ 			return;
+
+		within.appendChild(container)
+	},
+	
+	inspect: function(object) {
+		var cont = $E({tag: "div", className: "inspector"})
+		Debug.inspectObj(object, cont)
+		debugWindow.getContent().appendChild(cont)
+	},
+	
+	inspectObj: function (object, container) {
+		for (prop in object) {
+			Debug.inspectOutput(Debug.inspectable(object, prop), container)
+		}
+	},
+	
+	inspectable: function(object, prop) {
+		cont = $E({tag: 'div', className: 'inspectable', children: [prop + " value: " + object[prop] ]})
+		cont.toInspect = object[prop]
+		Event.observe(cont, 'click', Debug.inspectClicked, false)
+		return cont
+	},
+	
+	inspectClicked: function(e) {
+		Debug.inspectContained(Event.element(e))
+		Event.stop(e)
+	},
+	
+	inspectContained: function(container) {
+		if (container.opened) {
+			container.parentNode.removeChild(container.opened)
+			delete(container.opened)
+		} else {
+			sibling = container.parentNode.insertBefore($E({tag: "div", className: "child"}), container.nextSibling)
+			if (container.toInspect)
+				Debug.inspectObj(container.toInspect, sibling)
+			container.opened = sibling
+		}
+	}
+}
+var inspect = Debug.inspect;
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/effects.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/effects.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/effects.js	(revision 4071)
@@ -0,0 +1,1094 @@
+// script.aculo.us effects.js v1.7.1_beta1, Mon Mar 12 14:40:50 +0100 2007
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/ 
+
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';
+  if(this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
+  } else {  
+    if(this.slice(0,1) == '#') {  
+      if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if(this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return(color.length==7 ? color : (arguments[0] || this));  
+}
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+}
+
+Element.collectTextNodesIgnoreClass = function(element, className) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
+        Element.collectTextNodesIgnoreClass(node, className) : ''));
+  }).flatten().join('');
+}
+
+Element.setContentZoom = function(element, percent) {
+  element = $(element);  
+  element.setStyle({fontSize: (percent/100) + 'em'});   
+  if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+  return element;
+}
+
+Element.getInlineOpacity = function(element){
+  return $(element).style.opacity || '';
+}
+
+Element.forceRerendering = function(element) {
+  try {
+    element = $(element);
+    var n = document.createTextNode(' ');
+    element.appendChild(n);
+    element.removeChild(n);
+  } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Array.prototype.call = function() {
+  var args = arguments;
+  this.each(function(f){ f.apply(this, args) });
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  _elementDoesNotExistError: {
+    name: 'ElementDoesNotExistError',
+    message: 'The specified DOM element does not exist, but is required for this effect to operate'
+  },
+  tagifyText: function(element) {
+    if(typeof Builder == 'undefined')
+      throw("Effect.tagifyText requires including script.aculo.us' builder.js library");
+      
+    var tagifyStyle = 'position:relative';
+    if(Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+    
+    element = $(element);
+    $A(element.childNodes).each( function(child) {
+      if(child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
+          element.insertBefore(
+            Builder.node('span',{style: tagifyStyle},
+              character == ' ' ? String.fromCharCode(160) : character), 
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if(((typeof element == 'object') || 
+        (typeof element == 'function')) && 
+       (element.length))
+      elements = element;
+    else
+      elements = $(element).childNodes;
+      
+    var options = Object.extend({
+      speed: 0.1,
+      delay: 0.0
+    }, arguments[2] || {});
+    var masterDelay = options.delay;
+
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect) {
+    element = $(element);
+    effect = (effect || 'appear').toLowerCase();
+    var options = Object.extend({
+      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+    }, arguments[2] || {});
+    Effect[element.visible() ? 
+      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
+  }
+};
+
+var Effect2 = Effect; // deprecated
+
+/* ------------- transitions ------------- */
+
+Effect.Transitions = {
+  linear: Prototype.K,
+  sinoidal: function(pos) {
+    return (-Math.cos(pos*Math.PI)/2) + 0.5;
+  },
+  reverse: function(pos) {
+    return 1-pos;
+  },
+  flicker: function(pos) {
+    var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+    return (pos > 1 ? 1 : pos);
+  },
+  wobble: function(pos) {
+    return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+  },
+  pulse: function(pos, pulses) { 
+    pulses = pulses || 5; 
+    return (
+      Math.round((pos % (1/pulses)) * pulses) == 0 ? 
+            ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : 
+        1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2))
+      );
+  },
+  none: function(pos) {
+    return 0;
+  },
+  full: function(pos) {
+    return 1;
+  }
+};
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create();
+Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;    
+  },
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
+  add: function(effect) {
+    var timestamp = new Date().getTime();
+    
+    var position = (typeof effect.options.queue == 'string') ? 
+      effect.options.queue : effect.options.queue.position;
+    
+    switch(position) {
+      case 'front':
+        // move unstarted effects after this effect  
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
+        break;
+      case 'end':
+        // start effect after last queued effect has finished
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
+        break;
+    }
+    
+    effect.startOn  += timestamp;
+    effect.finishOn += timestamp;
+
+    if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+      this.effects.push(effect);
+    
+    if(!this.interval)
+      this.interval = setInterval(this.loop.bind(this), 15);
+  },
+  remove: function(effect) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if(this.effects.length == 0) {
+      clearInterval(this.interval);
+      this.interval = null;
+    }
+  },
+  loop: function() {
+    var timePos = new Date().getTime();
+    for(var i=0, len=this.effects.length;i<len;i++) 
+      this.effects[i] && this.effects[i].loop(timePos);
+  }
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if(typeof queueName != 'string') return queueName;
+    
+    if(!this.instances[queueName])
+      this.instances[queueName] = new Effect.ScopedQueue();
+      
+    return this.instances[queueName];
+  }
+}
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.DefaultOptions = {
+  transition: Effect.Transitions.sinoidal,
+  duration:   1.0,   // seconds
+  fps:        100,   // 100= assume 66fps max.
+  sync:       false, // true for combining
+  from:       0.0,
+  to:         1.0,
+  delay:      0.0,
+  queue:      'parallel'
+}
+
+Effect.Base = function() {};
+Effect.Base.prototype = {
+  position: null,
+  start: function(options) {
+    function codeForEvent(options,eventName){
+      return (
+        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+      );
+    }
+    if(options.transition === false) options.transition = Effect.Transitions.linear;
+    this.options      = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
+    this.currentFrame = 0;
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
+    this.finishOn     = this.startOn+(this.options.duration*1000);
+    this.fromToDelta  = this.options.to-this.options.from;
+    this.totalTime    = this.finishOn-this.startOn;
+    this.totalFrames  = this.options.fps*this.options.duration;
+    
+    eval('this.render = function(pos){ '+
+      'if(this.state=="idle"){this.state="running";'+
+      codeForEvent(options,'beforeSetup')+
+      (this.setup ? 'this.setup();':'')+ 
+      codeForEvent(options,'afterSetup')+
+      '};if(this.state=="running"){'+
+      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+      'this.position=pos;'+
+      codeForEvent(options,'beforeUpdate')+
+      (this.update ? 'this.update(pos);':'')+
+      codeForEvent(options,'afterUpdate')+
+      '}}');
+    
+    this.event('beforeStart');
+    if(!this.options.sync)
+      Effect.Queues.get(typeof this.options.queue == 'string' ? 
+        'global' : this.options.queue.scope).add(this);
+  },
+  loop: function(timePos) {
+    if(timePos >= this.startOn) {
+      if(timePos >= this.finishOn) {
+        this.render(1.0);
+        this.cancel();
+        this.event('beforeFinish');
+        if(this.finish) this.finish(); 
+        this.event('afterFinish');
+        return;  
+      }
+      var pos   = (timePos - this.startOn) / this.totalTime,
+          frame = Math.round(pos * this.totalFrames);
+      if(frame > this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
+    }
+  },
+  cancel: function() {
+    if(!this.options.sync)
+      Effect.Queues.get(typeof this.options.queue == 'string' ? 
+        'global' : this.options.queue.scope).remove(this);
+    this.state = 'finished';
+  },
+  event: function(eventName) {
+    if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if(this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    var data = $H();
+    for(property in this)
+      if(typeof this[property] != 'function') data[property] = this[property];
+    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+  }
+}
+
+Effect.Parallel = Class.create();
+Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    this.effects.invoke('render', position);
+  },
+  finish: function(position) {
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if(effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
+  }
+});
+
+Effect.Event = Class.create();
+Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), {
+  initialize: function() {
+    var options = Object.extend({
+      duration: 0
+    }, arguments[0] || {});
+    this.start(options);
+  },
+  update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create();
+Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    if(!this.element) throw(Effect._elementDoesNotExistError);
+    // make this work on IE on elements without 'layout'
+    if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+      this.element.setStyle({zoom: 1});
+    var options = Object.extend({
+      from: this.element.getOpacity() || 0.0,
+      to:   1.0
+    }, arguments[1] || {});
+    this.start(options);
+  },
+  update: function(position) {
+    this.element.setOpacity(position);
+  }
+});
+
+Effect.Move = Class.create();
+Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    if(!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || {});
+    this.start(options);
+  },
+  setup: function() {
+    // Bug in Opera: Opera returns the "real" position of a static element or
+    // relative element that does not have top/left explicitly set.
+    // ==> Always set top and left for position relative elements in your stylesheets 
+    // (to 0 if you do not need them) 
+    this.element.makePositioned();
+    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
+    if(this.options.mode == 'absolute') {
+      // absolute movement, so we need to calc deltaX and deltaY
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
+  },
+  update: function(position) {
+    this.element.setStyle({
+      left: Math.round(this.options.x  * position + this.originalLeft) + 'px',
+      top:  Math.round(this.options.y  * position + this.originalTop)  + 'px'
+    });
+  }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element, 
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
+};
+
+Effect.Scale = Class.create();
+Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
+  initialize: function(element, percent) {
+    this.element = $(element);
+    if(!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      scaleX: true,
+      scaleY: true,
+      scaleContent: true,
+      scaleFromCenter: false,
+      scaleMode: 'box',        // 'box' or 'contents' or {} with provided values
+      scaleFrom: 100.0,
+      scaleTo:   percent
+    }, arguments[2] || {});
+    this.start(options);
+  },
+  setup: function() {
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = this.element.getStyle('position');
+    
+    this.originalStyle = {};
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+      
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+    
+    var fontSize = this.element.getStyle('font-size') || '100%';
+    ['em','px','%','pt'].each( function(fontSizeType) {
+      if(fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+    
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+    
+    this.dims = null;
+    if(this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if(/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if(!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
+  },
+  update: function(position) {
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if(this.options.scaleContent && this.fontSize)
+      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = {};
+    if(this.options.scaleX) d.width = Math.round(width) + 'px';
+    if(this.options.scaleY) d.height = Math.round(height) + 'px';
+    if(this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if(this.elementPositioning == 'absolute') {
+        if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      } else {
+        if(this.options.scaleY) d.top = -topd + 'px';
+        if(this.options.scaleX) d.left = -leftd + 'px';
+      }
+    }
+    this.element.setStyle(d);
+  }
+});
+
+Effect.Highlight = Class.create();
+Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    if(!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
+    this.start(options);
+  },
+  setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if(this.element.getStyle('display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = {};
+    if (!this.options.keepBackgroundImage) {
+      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+      this.element.setStyle({backgroundImage: 'none'});
+    }
+    if(!this.options.endcolor)
+      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+    if(!this.options.restorecolor)
+      this.options.restorecolor = this.element.getStyle('background-color');
+    // init color calculations
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+  },
+  update: function(position) {
+    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
+  },
+  finish: function() {
+    this.element.setStyle(Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
+  }
+});
+
+Effect.ScrollTo = Class.create();
+Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    this.start(arguments[1] || {});
+  },
+  setup: function() {
+    Position.prepare();
+    var offsets = Position.cumulativeOffset(this.element);
+    if(this.options.offset) offsets[1] += this.options.offset;
+    var max = window.innerHeight ? 
+      window.height - window.innerHeight :
+      document.body.scrollHeight - 
+        (document.documentElement.clientHeight ? 
+          document.documentElement.clientHeight : document.body.clientHeight);
+    this.scrollStart = Position.deltaY;
+    this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
+  },
+  update: function(position) {
+    Position.prepare();
+    window.scrollTo(Position.deltaX, 
+      this.scrollStart + (position*this.delta));
+  }
+});
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  var options = Object.extend({
+  from: element.getOpacity() || 1.0,
+  to:   0.0,
+  afterFinishInternal: function(effect) { 
+    if(effect.options.to!=0) return;
+    effect.element.hide().setStyle({opacity: oldOpacity}); 
+  }}, arguments[1] || {});
+  return new Effect.Opacity(element,options);
+}
+
+Effect.Appear = function(element) {
+  element = $(element);
+  var options = Object.extend({
+  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+  to:   1.0,
+  // force Safari to render floated elements properly
+  afterFinishInternal: function(effect) {
+    effect.element.forceRerendering();
+  },
+  beforeSetup: function(effect) {
+    effect.element.setOpacity(effect.options.from).show(); 
+  }}, arguments[1] || {});
+  return new Effect.Opacity(element,options);
+}
+
+Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = { 
+    opacity: element.getInlineOpacity(), 
+    position: element.getStyle('position'),
+    top:  element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height
+  };
+  return new Effect.Parallel(
+   [ new Effect.Scale(element, 200, 
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
+     Object.extend({ duration: 1.0, 
+      beforeSetupInternal: function(effect) {
+        Position.absolutize(effect.effects[0].element)
+      },
+      afterFinishInternal: function(effect) {
+         effect.effects[0].element.hide().setStyle(oldStyle); }
+     }, arguments[1] || {})
+   );
+}
+
+Effect.BlindUp = function(element) {
+  element = $(element);
+  element.makeClipping();
+  return new Effect.Scale(element, 0,
+    Object.extend({ scaleContent: false, 
+      scaleX: false, 
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping();
+      } 
+    }, arguments[1] || {})
+  );
+}
+
+Effect.BlindDown = function(element) {
+  element = $(element);
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false,
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping();
+    }
+  }, arguments[1] || {}));
+}
+
+Effect.SwitchOff = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  return new Effect.Appear(element, Object.extend({
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, { 
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) { 
+          effect.element.makePositioned().makeClipping();
+        },
+        afterFinishInternal: function(effect) {
+          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+        }
+      })
+    }
+  }, arguments[1] || {}));
+}
+
+Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left'),
+    opacity: element.getInlineOpacity() };
+  return new Effect.Parallel(
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+    Object.extend(
+      { duration: 0.5,
+        beforeSetup: function(effect) {
+          effect.effects[0].element.makePositioned(); 
+        },
+        afterFinishInternal: function(effect) {
+          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+        } 
+      }, arguments[1] || {}));
+}
+
+Effect.Shake = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left') };
+    return new Effect.Move(element, 
+      { x:  20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  40, y: 0, duration: 0.1,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
+        effect.element.undoPositioned().setStyle(oldStyle);
+  }}) }}) }}) }}) }}) }});
+}
+
+Effect.SlideDown = function(element) {
+  element = $(element).cleanWhitespace();
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false, 
+    scaleFrom: window.opera ? 0 : 1,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if(window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+    }, arguments[1] || {})
+  );
+}
+
+Effect.SlideUp = function(element) {
+  element = $(element).cleanWhitespace();
+  var oldInnerBottom = element.down().getStyle('bottom');
+  return new Effect.Scale(element, window.opera ? 0 : 1,
+   Object.extend({ scaleContent: false, 
+    scaleX: false, 
+    scaleMode: 'box',
+    scaleFrom: 100,
+    restoreAfterFinish: true,
+    beforeStartInternal: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if(window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().show();
+    },  
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom});
+      effect.element.down().undoPositioned();
+    }
+   }, arguments[1] || {})
+  );
+}
+
+// Bug in opera makes the TD containing this element expand for a instance after finish 
+Effect.Squish = function(element) {
+  return new Effect.Scale(element, window.opera ? 1 : 0, { 
+    restoreAfterFinish: true,
+    beforeSetup: function(effect) {
+      effect.element.makeClipping(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping(); 
+    }
+  });
+}
+
+Effect.Grow = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || {});
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();    
+  var initialMoveX, initialMoveY;
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      initialMoveX = initialMoveY = moveX = moveY = 0; 
+      break;
+    case 'top-right':
+      initialMoveX = dims.width;
+      initialMoveY = moveY = 0;
+      moveX = -dims.width;
+      break;
+    case 'bottom-left':
+      initialMoveX = moveX = 0;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
+      break;
+    case 'bottom-right':
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
+      break;
+    case 'center':
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
+    duration: 0.01, 
+    beforeSetup: function(effect) {
+      effect.element.hide().makeClipping().makePositioned();
+    },
+    afterFinishInternal: function(effect) {
+      new Effect.Parallel(
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) {
+               effect.effects[0].element.setStyle({height: '0px'}).show(); 
+             },
+             afterFinishInternal: function(effect) {
+               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
+             }
+           }, options)
+      )
+    }
+  });
+}
+
+Effect.Shrink = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || {});
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      moveX = moveY = 0;
+      break;
+    case 'top-right':
+      moveX = dims.width;
+      moveY = 0;
+      break;
+    case 'bottom-left':
+      moveX = 0;
+      moveY = dims.height;
+      break;
+    case 'bottom-right':
+      moveX = dims.width;
+      moveY = dims.height;
+      break;
+    case 'center':  
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Parallel(
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+    ], Object.extend({            
+         beforeStartInternal: function(effect) {
+           effect.effects[0].element.makePositioned().makeClipping(); 
+         },
+         afterFinishInternal: function(effect) {
+           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+       }, options)
+  );
+}
+
+Effect.Pulsate = function(element) {
+  element = $(element);
+  var options    = arguments[1] || {};
+  var oldOpacity = element.getInlineOpacity();
+  var transition = options.transition || Effect.Transitions.sinoidal;
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
+  reverser.bind(transition);
+  return new Effect.Opacity(element, 
+    Object.extend(Object.extend({  duration: 2.0, from: 0,
+      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+    }, options), {transition: reverser}));
+}
+
+Effect.Fold = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  element.makeClipping();
+  return new Effect.Scale(element, 5, Object.extend({   
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, { 
+      scaleContent: false, 
+      scaleY: false,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping().setStyle(oldStyle);
+      } });
+  }}, arguments[1] || {}));
+};
+
+Effect.Morph = Class.create();
+Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), {
+  initialize: function(element) {
+    this.element = $(element);
+    if(!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      style: {}
+    }, arguments[1] || {});
+    if (typeof options.style == 'string') {
+      if(options.style.indexOf(':') == -1) {
+        var cssText = '', selector = '.' + options.style;
+        $A(document.styleSheets).reverse().each(function(styleSheet) {
+          if (styleSheet.cssRules) cssRules = styleSheet.cssRules;
+          else if (styleSheet.rules) cssRules = styleSheet.rules;
+          $A(cssRules).reverse().each(function(rule) {
+            if (selector == rule.selectorText) {
+              cssText = rule.style.cssText;
+              throw $break;
+            }
+          });
+          if (cssText) throw $break;
+        });
+        this.style = cssText.parseStyle();
+        options.afterFinishInternal = function(effect){
+          effect.element.addClassName(effect.options.style);
+          effect.transforms.each(function(transform) {
+            if(transform.style != 'opacity')
+              effect.element.style[transform.style] = '';
+          });
+        }
+      } else this.style = options.style.parseStyle();
+    } else this.style = $H(options.style)
+    this.start(options);
+  },
+  setup: function(){
+    function parseColor(color){
+      if(!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+      color = color.parseColor();
+      return $R(0,2).map(function(i){
+        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
+      });
+    }
+    this.transforms = this.style.map(function(pair){
+      var property = pair[0], value = pair[1], unit = null;
+
+      if(value.parseColor('#zzzzzz') != '#zzzzzz') {
+        value = value.parseColor();
+        unit  = 'color';
+      } else if(property == 'opacity') {
+        value = parseFloat(value);
+        if(Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+          this.element.setStyle({zoom: 1});
+      } else if(Element.CSS_LENGTH.test(value)) {
+          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+          value = parseFloat(components[1]);
+          unit = (components.length == 3) ? components[2] : null;
+      }
+
+      var originalValue = this.element.getStyle(property);
+      return { 
+        style: property.camelize(), 
+        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
+        targetValue: unit=='color' ? parseColor(value) : value,
+        unit: unit
+      };
+    }.bind(this)).reject(function(transform){
+      return (
+        (transform.originalValue == transform.targetValue) ||
+        (
+          transform.unit != 'color' &&
+          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+        )
+      )
+    });
+  },
+  update: function(position) {
+    var style = {}, transform, i = this.transforms.length;
+    while(i--)
+      style[(transform = this.transforms[i]).style] = 
+        transform.unit=='color' ? '#'+
+          (Math.round(transform.originalValue[0]+
+            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+          (Math.round(transform.originalValue[1]+
+            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+          (Math.round(transform.originalValue[2]+
+            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+        transform.originalValue + Math.round(
+          ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit;
+    this.element.setStyle(style, true);
+  }
+});
+
+Effect.Transform = Class.create();
+Object.extend(Effect.Transform.prototype, {
+  initialize: function(tracks){
+    this.tracks  = [];
+    this.options = arguments[1] || {};
+    this.addTracks(tracks);
+  },
+  addTracks: function(tracks){
+    tracks.each(function(track){
+      var data = $H(track).values().first();
+      this.tracks.push($H({
+        ids:     $H(track).keys().first(),
+        effect:  Effect.Morph,
+        options: { style: data }
+      }));
+    }.bind(this));
+    return this;
+  },
+  play: function(){
+    return new Effect.Parallel(
+      this.tracks.map(function(track){
+        var elements = [$(track.ids) || $$(track.ids)].flatten();
+        return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) });
+      }).flatten(),
+      this.options
+    );
+  }
+});
+
+Element.CSS_PROPERTIES = $w(
+  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
+  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+  'fontSize fontWeight height left letterSpacing lineHeight ' +
+  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+  'right textIndent top width wordSpacing zIndex');
+  
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.prototype.parseStyle = function(){
+  var element = document.createElement('div');
+  element.innerHTML = '<div style="' + this + '"></div>';
+  var style = element.childNodes[0].style, styleRules = $H();
+  
+  Element.CSS_PROPERTIES.each(function(property){
+    if(style[property]) styleRules[property] = style[property]; 
+  });
+  if(Prototype.Browser.IE && this.indexOf('opacity') > -1) {
+    styleRules.opacity = this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1];
+  }
+  return styleRules;
+};
+
+Element.morph = function(element, style) {
+  new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {}));
+  return element;
+};
+
+['getInlineOpacity','forceRerendering','setContentZoom',
+ 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( 
+  function(f) { Element.Methods[f] = Element[f]; }
+);
+
+Element.Methods.visualEffect = function(element, effect, options) {
+  s = effect.dasherize().camelize();
+  effect_class = s.charAt(0).toUpperCase() + s.substring(1);
+  new Effect[effect_class](element, options);
+  return $(element);
+};
+
+Element.addMethods();
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/extended_debug.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/extended_debug.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/extended_debug.js	(revision 4071)
@@ -0,0 +1,113 @@
+var commandHistory;
+var historyIndex;
+
+function showExtendedDebug() {
+	if (debugWindow != null) {
+	  hideDebug();
+  }
+  
+	if (debugWindow == null) {
+	 	commandHistory = new Array();
+	 	historyIndex = 0;
+		
+		debugWindow = new Window('debug_window', {className: 'dialog',width:250, height:100, right:4, minWidth:250, bottom:42, zIndex:1000, opacity:1, showEffect: Element.show, resizable: true, title: "Debug"})
+		debugWindow.getContent().innerHTML = "<style>#debug_window .dialog_content {background:#000;}</style> <div font='monaco' id='debug' style='padding:3px;color:#0F0;font-family:monaco'></div>";
+
+		//create hourglass icon and attach events to it.
+		var cont = "<div id=\"debug_window_inspect\" style=\"width: 15px; height: 15px; background: transparent url(themes/default/inspect.gif) no-repeat 0 0; position:absolute; top:5px; left:70px; cursor:pointer; z-index:3000;\"></div>";
+
+		new Insertion.After('debug_window_maximize', cont);
+		Event.observe('debug_window_inspect', 'click', enterInspectionMode, false);
+
+		//create command text box
+		cont = "Eval:<input id=\"debug_window_command\" type=\"textbox\" style=\"width:150px; height: 12px; color: black;\">"
+		debugWindow.setStatusBar(cont);
+
+		Event.observe('debug_window_command', 'mousedown', donothing);
+		Event.observe('debug_window_command', 'keypress', evalJS, false);
+	}
+	debugWindow.show();		
+}
+
+function donothing(evt){
+		Field.activate('debug_window_command');
+		return false;
+}
+
+function evalJS(evt){
+	if(evt.keyCode == Event.KEY_RETURN){
+		var js = $F('debug_window_command');
+		try{
+			var ret = eval(js);
+			if(ret != null)
+				debug(ret);
+		}catch(e){
+			debug(e);
+		}
+		$('debug_window_command').value = '';
+
+		Field.activate('debug_window_command');
+		commandHistory.push(js);
+		historyIndex = 0;
+	}
+
+	if(evt.keyCode == Event.KEY_UP){
+		if(commandHistory.length > historyIndex){
+			historyIndex++;
+			var js = commandHistory[commandHistory.length-historyIndex];
+			$('debug_window_command').value = js;
+			Event.stop(evt);
+			Field.activate('debug_window_command');
+		}
+	}
+
+	if(evt.keyCode == Event.KEY_DOWN){
+		if(commandHistory.length >= historyIndex && historyIndex > 1){
+			historyIndex--;
+			var js = commandHistory[commandHistory.length-historyIndex];
+			$('debug_window_command').value = js;
+			Event.stop(evt);
+			Field.activate('debug_window_command');
+		}
+	}	
+}
+
+function enterInspectionMode(evt){
+	//stop observing magnifying glass
+	Event.stopObserving('debug_window_inspect', 'click', enterInspectionMode, false);
+	//change pointer
+	document.body.style.cursor='help';
+	//start observing mouse clicks
+	Event.observe(window, 'click', inspectItem, false);		
+}
+
+function inspectItem(evt){
+	// the element that triggered the event
+	var element = Event.element(evt);
+	if(element.id!="debug_window_inspect"){
+		clearDebug()
+		//change pointer
+		document.body.style.cursor='default';
+		debug(element.id);
+		inspect(element);
+		//stop observing mouse clicks
+		Event.stopObserving(window, 'click', inspectItem, false);
+		//alert('doing something');
+		//start observing mag
+		Event.observe('debug_window_inspect', 'click', enterInspectionMode, false);
+	}
+}
+
+function clearDebug() {
+	var win = $('debug');
+	if (win == null)
+		return;
+		
+	win.innerHTML=" ";
+	//clear inspections too
+	var divs = document.getElementsByClassName('inspector');
+	divs.each(function(div){
+		Element.remove(div);
+	});
+}
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/prototype.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/prototype.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/prototype.js	(revision 4071)
@@ -0,0 +1,4221 @@
+/*  Prototype JavaScript framework, version 1.6.0.2
+ *  (c) 2005-2008 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.2',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
+  }
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+  for (var property in source)
+    destination[property] = source[property];
+  return destination;
+};
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return object && object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object != null && typeof object == "object" &&
+      'splice' in object && 'join' in object;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
+  }
+});
+
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+    return names.length == 1 && !names[0] ? [] : names;
+  },
+
+  bind: function() {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
+
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
+  }
+});
+
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = new Element('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+  each: function(iterator, context) {
+    var index = 0;
+    iterator = iterator.bind(context);
+    try {
+      this._each(function(value) {
+        iterator(value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  },
+
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
+    return this.map(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+};
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
+});
+function $A(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  $A = function(iterable) {
+    if (!iterable) return [];
+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length || 0, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
+  };
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(Object.isArray(value) ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&');
+        }
+        return toQueryPair(key, values);
+      }).join('&');
+    },
+
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
+  },
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).select("*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      element.select(expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (element.sourceIndex && !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+      }
+      if (nextAncestor && nextAncestor.sourceIndex)
+       return (e > a && e < nextAncestor.sourceIndex);
+    }
+
+    while (element = element.parentNode)
+      if (element == originalAncestor) return true;
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value) {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  // IE doesn't report offsets correctly for static elements, so we change them
+  // to "relative" to get the values, then change them back.
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+    function(proceed, element) {
+      element = $(element);
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    }
+  );
+
+  $w('positionedOffset viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+        // Trigger hasLayout on the offset parent so that IE6 reports
+        // accurate offsetTop and offsetLeft values for position: fixed.
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
+      }
+    }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if ('outerHTML' in document.createElement('div')) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+    });
+    return dimensions;
+  },
+
+  getWidth: function() {
+    return this.getDimensions().width;
+  },
+
+  getHeight: function() {
+    return this.getDimensions().height;
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
+  },
+
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    if (this.shouldUseXPath())
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = _true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._countedByPrototype = Prototype.emptyFunction;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._countedByPrototype) {
+          n._countedByPrototype = Prototype.emptyFunction;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._countedByPrototype) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._countedByPrototype) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = $$(expression), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._countedByPrototype) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    expressions = Selector.split(expressions.join(','));
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    // IE returns comment nodes on getElementsByTagName("*").
+    // Filter them out.
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    },
+
+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node.removeAttribute('_countedByPrototype');
+      return nodes;
+    }
+  });
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
+
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
+      event.preventDefault();
+      event.stopPropagation();
+      event.stopped = true;
+    }
+  };
+})();
+
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._prototypeEventID) return element._prototypeEventID[0];
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._prototypeEventID = [++arguments.callee.id];
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event);
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
+    }
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
+    }
+
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  // Deprecation layer -- use newer Element methods now (1.5.2).
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods();
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/tooltip.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/tooltip.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/tooltip.js	(revision 4071)
@@ -0,0 +1,241 @@
+// Singleton class TooltipWindow
+// This class works with special className. The tooltip content could be in your HTML page as an hidden element or
+// can be retreive by an AJAX call.
+//
+// To work, You just need to set two class name on elements that should show tooltips
+// - One to say to TooltipManager that this element must have a tooltip ('tooltip' by default)
+// - Another to indicate how to find the tooltip content
+//   It could be html_XXXX if tootltip content is somewhere hidden in your page, XXX must be DOM ID of this hidden element
+//   It could be ajax_XXXX if tootltip content must be find by an ajax request, XXX will be the string send as id parameter to your server. 
+// Check samples/tooltips/tooltip.html to see how it works
+//
+TooltipManager = {
+  options: {cssClassName: 'tooltip', delayOver: 200, delayOut: 1000, shiftX: 10, shiftY: 10,
+            className: 'alphacube', width: 200, height: null, 
+            draggable: false, minimizable: false, maximizable: false, showEffect: Element.show, hideEffect: Element.hide},
+  ajaxInfo: null,
+  elements: null,
+  showTimer: null,
+  hideTimer: null,
+
+  // Init tooltip manager
+  // parameters:
+  // - cssClassName (string) : CSS class name where tooltip should be shown. 
+  // - ajaxOptions  (hash)   : Ajax options for ajax tooltip. 
+  //                           For examples {url: "/tooltip/get.php", options: {method: 'get'}} 
+  //                           see Ajax.Request documentation for details
+  //- tooltipOptions (hash)  : available keys
+  //                           - delayOver: int in ms (default 10) delay before showing tooltip
+  //                           - delayOut:  int in ms (default 1000) delay before hidding tooltip
+  //                           - shiftX:    int in pixels (default 10) left shift of the tooltip window 
+  //                           - shiftY:    int in pixels (default 10) top shift of the tooltip window 
+  //                           and All window options like showEffect: Element.show, hideEffect: Element.hide to remove animation
+  //                           default: {className: 'alphacube', width: 200, height: null, draggable: false, minimizable: false, maximizable: false}
+  
+  init: function(cssClassName, ajaxInfo, tooltipOptions) {
+    TooltipManager.options = Object.extend(TooltipManager.options, tooltipOptions || {});
+    
+    cssClassName = TooltipManager.options.cssClassName || "tooltip";
+    TooltipManager.ajaxInfo = ajaxInfo;
+    TooltipManager.elements = $$("." + cssClassName);
+    TooltipManager.elements.each(function(element) {
+      element = $(element)
+      var info = TooltipManager._getInfo(element);
+      if (info.ajax) {
+        element.ajaxId = info.id;
+        element.ajaxInfo = ajaxInfo;
+      }
+      else {
+        element.tooltipElement = $(info.id);
+      }
+      element.observe("mouseover", TooltipManager._mouseOver);
+      element.observe("mouseout", TooltipManager._mouseOut);
+    });
+    Windows.addObserver(this);
+  },
+  
+  addHTML: function(element, tooltipElement) {
+    element = $(element);
+    tooltipElement = $(tooltipElement);
+    element.tooltipElement = tooltipElement;
+    
+    element.observe("mouseover", TooltipManager._mouseOver);
+    element.observe("mouseout", TooltipManager._mouseOut);
+  },
+  
+  addAjax: function(element, ajaxInfo) {
+    element = $(element);
+    element.ajaxInfo = ajaxInfo;
+    element.observe("mouseover", TooltipManager._mouseOver);
+    element.observe("mouseout", TooltipManager._mouseOut);    
+  },
+    
+  addURL: function(element, url, width, height) {
+    element = $(element);
+    element.url = url;
+    element.frameWidth = width;
+    element.frameHeight = height;
+    element.observe("mouseover", TooltipManager._mouseOver);
+    element.observe("mouseout", TooltipManager._mouseOut);    
+  },
+    
+  close: function() {
+    if (TooltipManager.tooltipWindow)
+      TooltipManager.tooltipWindow.hide();
+  },
+  
+  preloadImages: function(path, images, extension) {
+    if (!extension)
+      extension = ".gif";
+      
+    //preload images
+    $A(images).each(function(i) {
+      var image = new Image(); 
+      image.src= path + "/" + i + extension; 
+    });
+  },
+  
+  _showTooltip: function(element) {
+    if (this.element == element)
+      return;
+    // Get original element
+    while (element && (!element.tooltipElement && !element.ajaxInfo && !element.url)) 
+      element = element.parentNode;
+    this.element = element;
+    
+    TooltipManager.showTimer = null;
+    if (TooltipManager.hideTimer)
+      clearTimeout(TooltipManager.hideTimer);
+    
+    var position = Position.cumulativeOffset(element);
+    var dimension = element.getDimensions();
+
+    if (! this.tooltipWindow)
+      this.tooltipWindow = new Window("__tooltip__", TooltipManager.options);
+      
+    this.tooltipWindow.hide();
+    this.tooltipWindow.setLocation(position[1] + dimension.height + TooltipManager.options.shiftY, position[0] + TooltipManager.options.shiftX);
+
+    Event.observe(this.tooltipWindow.element, "mouseover", function(event) {TooltipManager._tooltipOver(event, element)});
+    Event.observe(this.tooltipWindow.element, "mouseout", function(event) {TooltipManager._tooltipOut(event, element)});
+
+    // Reset width/height for computation
+    this.tooltipWindow.height = TooltipManager.options.height;
+    this.tooltipWindow.width = TooltipManager.options.width;
+
+    // Ajax content
+    if (element.ajaxInfo) {
+      var p = element.ajaxInfo.options.parameters;
+      var saveParam = p;
+      
+      // Set by CSS
+      if (element.ajaxId) {
+        if (p)
+          p += "&id=" + element.ajaxId;
+        else
+          p = "id=" + element.ajaxId;
+      }
+      element.ajaxInfo.options.parameters = p || "";
+      this.tooltipWindow.setHTMLContent("");
+      this.tooltipWindow.setAjaxContent(element.ajaxInfo.url, element.ajaxInfo.options);
+      element.ajaxInfo.options.parameters = saveParam;    
+    } 
+    // URL content
+    else if (element.url) {
+      this.tooltipWindow.setURL(element.url);
+      this.tooltipWindow.setSize(element.frameWidth, element.frameHeight);
+
+      // Set tooltip size
+      this.tooltipWindow.height = element.frameHeight;
+      this.tooltipWindow.width = element.frameWidth;
+    }
+    // HTML content
+    else
+      this.tooltipWindow.setHTMLContent(element.tooltipElement.innerHTML);
+
+    if (!element.ajaxInfo) {
+      this.tooltipWindow.show();
+      this.tooltipWindow.toFront();
+    }
+  },
+  
+  _hideTooltip: function(element) {
+    if (this.tooltipWindow) {
+      this.tooltipWindow.hide();
+      this.element = null;
+    }
+  },
+  
+  _mouseOver: function (event) {
+    var element = Event.element(event);
+    if (TooltipManager.showTimer) 
+      clearTimeout(TooltipManager.showTimer);
+    
+    TooltipManager.showTimer = setTimeout(function() {TooltipManager._showTooltip(element)}, TooltipManager.options.delayOver)
+  },
+  
+  _mouseOut: function(event) {
+    var element = Event.element(event);
+    if (TooltipManager.showTimer) {
+      clearTimeout(TooltipManager.showTimer);
+      TooltipManager.showTimer = null;
+      return;
+    }
+    if (TooltipManager.tooltipWindow)
+      TooltipManager.hideTimer = setTimeout(function() {TooltipManager._hideTooltip(element)}, TooltipManager.options.delayOut)
+  },
+  
+  _tooltipOver: function(event, element) {
+    if (TooltipManager.hideTimer) {
+      clearTimeout(TooltipManager.hideTimer);
+      TooltipManager.hideTimer = null;
+    }
+  },
+  
+  _tooltipOut: function(event, element) {
+    if (TooltipManager.hideTimer == null)
+      TooltipManager.hideTimer = setTimeout(function() {TooltipManager._hideTooltip(element)}, TooltipManager.options.delayOut)
+  },
+  
+  _getInfo: function(element) {
+    // Find html_ for static content
+    var id = element.className.split(' ').detect(function(name) {return name.indexOf("html_") == 0});
+    var ajax = true;
+    if (id)
+      ajax = false;
+    else 
+      // Find ajax_ for ajax content
+      id = element.className.split(' ').detect(function(name) {return name.indexOf("ajax_") == 0});
+    
+    id = id.substr(id.indexOf('_')+1, id.length)
+    return id ? {ajax: ajax, id: id} : null;
+  },
+  
+  onBeforeShow: function(eventName, win) {
+     var top = parseFloat(win.getLocation().top);
+     var dim = win.element.getDimensions();
+    
+     if (top + dim.height > TooltipManager._getScrollTop() + TooltipManager._getPageHeight()) {
+       var position = Position.cumulativeOffset(this.element);
+
+       var top = position[1] - TooltipManager.options.shiftY - dim.height;
+       win.setLocation(top, position[0] + TooltipManager.options.shiftX)
+     }
+   },
+
+	_getPageWidth: function(){
+		return window.innerWidth || document.documentElement.clientWidth || 0;
+	},
+	
+	_getPageHeight: function(){
+		return window.innerHeight || document.documentElement.clientHeight || 0;
+	},
+
+	_getScrollTop: function(){
+		return document.documentElement.scrollTop || window.pageYOffset || 0;
+	},
+
+	_getScrollLeft: function(){
+		return document.documentElement.scrollLeft || window.pageXOffset || 0;
+	}	
+};
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window.js	(revision 4071)
@@ -0,0 +1,1843 @@
+// Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// VERSION 1.3
+
+var Window = Class.create();
+
+Window.keepMultiModalWindow = false;
+Window.hasEffectLib = (typeof Effect != 'undefined');
+Window.resizeEffectDuration = 0.4;
+
+Window.prototype = {
+  // Constructor
+  // Available parameters : className, blurClassName, title, minWidth, minHeight, maxWidth, maxHeight, width, height, top, left, bottom, right, resizable, zIndex, opacity, recenterAuto, wiredDrag
+  //                        hideEffect, showEffect, showEffectOptions, hideEffectOptions, effectOptions, url, draggable, closable, minimizable, maximizable, parent, onload
+  //                        add all callbacks (if you do not use an observer)
+  //                        onDestroy onStartResize onStartMove onResize onMove onEndResize onEndMove onFocus onBlur onBeforeShow onShow onHide onMinimize onMaximize onClose
+  
+  initialize: function() {
+    var id;
+    var optionIndex = 0;
+    // For backward compatibility like win= new Window("id", {...}) instead of win = new Window({id: "id", ...})
+    if (arguments.length > 0) {
+      if (typeof arguments[0] == "string" ) {
+        id = arguments[0];
+        optionIndex = 1;
+      }
+      else
+        id = arguments[0] ? arguments[0].id : null;
+    }
+    
+    // Generate unique ID if not specified
+    if (!id)
+      id = "window_" + new Date().getTime();
+      
+    if ($(id))
+      alert("Window " + id + " is already registered in the DOM! Make sure you use setDestroyOnClose() or destroyOnClose: true in the constructor");
+
+    this.options = Object.extend({
+      className:         "dialog",
+      blurClassName:     null,
+      minWidth:          100, 
+      minHeight:         20,
+      resizable:         true,
+      closable:          true,
+      minimizable:       true,
+      maximizable:       true,
+      draggable:         true,
+      userData:          null,
+      showEffect:        (Window.hasEffectLib ? Effect.Appear : Element.show),
+      hideEffect:        (Window.hasEffectLib ? Effect.Fade : Element.hide),
+      showEffectOptions: {},
+      hideEffectOptions: {},
+      effectOptions:     null,
+      parent:            document.body,
+      title:             "&nbsp;",
+      url:               null,
+      onload:            Prototype.emptyFunction,
+      width:             200,
+      height:            300,
+      opacity:           1,
+      recenterAuto:      true,
+      wiredDrag:         false,
+      closeCallback:     null,
+      destroyOnClose:    false,
+      gridX:             1, 
+      gridY:             1      
+    }, arguments[optionIndex] || {});
+    if (this.options.blurClassName)
+      this.options.focusClassName = this.options.className;
+      
+    if (typeof this.options.top == "undefined" &&  typeof this.options.bottom ==  "undefined") 
+      this.options.top = this._round(Math.random()*500, this.options.gridY);
+    if (typeof this.options.left == "undefined" &&  typeof this.options.right ==  "undefined") 
+      this.options.left = this._round(Math.random()*500, this.options.gridX);
+
+    if (this.options.effectOptions) {
+      Object.extend(this.options.hideEffectOptions, this.options.effectOptions);
+      Object.extend(this.options.showEffectOptions, this.options.effectOptions);
+      if (this.options.showEffect == Element.Appear)
+        this.options.showEffectOptions.to = this.options.opacity;
+    }
+    if (Window.hasEffectLib) {
+      if (this.options.showEffect == Effect.Appear)
+        this.options.showEffectOptions.to = this.options.opacity;
+    
+      if (this.options.hideEffect == Effect.Fade)
+        this.options.hideEffectOptions.from = this.options.opacity;
+    }
+    if (this.options.hideEffect == Element.hide)
+      this.options.hideEffect = function(){ Element.hide(this.element); if (this.options.destroyOnClose) this.destroy(); }.bind(this)
+    
+    if (this.options.parent != document.body)  
+      this.options.parent = $(this.options.parent);
+      
+    this.element = this._createWindow(id);       
+    this.element.win = this;
+    
+    // Bind event listener
+    this.eventMouseDown = this._initDrag.bindAsEventListener(this);
+    this.eventMouseUp   = this._endDrag.bindAsEventListener(this);
+    this.eventMouseMove = this._updateDrag.bindAsEventListener(this);
+    this.eventOnLoad    = this._getWindowBorderSize.bindAsEventListener(this);
+    this.eventMouseDownContent = this.toFront.bindAsEventListener(this);
+    this.eventResize = this._recenter.bindAsEventListener(this);
+ 
+    this.topbar = $(this.element.id + "_top");
+    this.bottombar = $(this.element.id + "_bottom");
+    this.content = $(this.element.id + "_content");
+    
+    Event.observe(this.topbar, "mousedown", this.eventMouseDown);
+    Event.observe(this.bottombar, "mousedown", this.eventMouseDown);
+    Event.observe(this.content, "mousedown", this.eventMouseDownContent);
+    Event.observe(window, "load", this.eventOnLoad);
+    Event.observe(window, "resize", this.eventResize);
+    Event.observe(window, "scroll", this.eventResize);
+    Event.observe(this.options.parent, "scroll", this.eventResize);
+    
+    if (this.options.draggable)  {
+      var that = this;
+      [this.topbar, this.topbar.up().previous(), this.topbar.up().next()].each(function(element) {
+        element.observe("mousedown", that.eventMouseDown);
+        element.addClassName("top_draggable");
+      });
+      [this.bottombar.up(), this.bottombar.up().previous(), this.bottombar.up().next()].each(function(element) {
+        element.observe("mousedown", that.eventMouseDown);
+        element.addClassName("bottom_draggable");
+      });
+      
+    }    
+    
+    if (this.options.resizable) {
+      this.sizer = $(this.element.id + "_sizer");
+      Event.observe(this.sizer, "mousedown", this.eventMouseDown);
+    }  
+    
+    this.useLeft = null;
+    this.useTop = null;
+    if (typeof this.options.left != "undefined") {
+      this.element.setStyle({left: parseFloat(this.options.left) + 'px'});
+      this.useLeft = true;
+    }
+    else {
+      this.element.setStyle({right: parseFloat(this.options.right) + 'px'});
+      this.useLeft = false;
+    }
+    
+    if (typeof this.options.top != "undefined") {
+      this.element.setStyle({top: parseFloat(this.options.top) + 'px'});
+      this.useTop = true;
+    }
+    else {
+      this.element.setStyle({bottom: parseFloat(this.options.bottom) + 'px'});      
+      this.useTop = false;
+    }
+      
+    this.storedLocation = null;
+    
+    this.setOpacity(this.options.opacity);
+    if (this.options.zIndex)
+      this.setZIndex(this.options.zIndex)
+
+    if (this.options.destroyOnClose)
+      this.setDestroyOnClose(true);
+
+    this._getWindowBorderSize();
+    this.width = this.options.width;
+    this.height = this.options.height;
+    this.visible = false;
+    
+    this.constraint = false;
+    this.constraintPad = {top: 0, left:0, bottom:0, right:0};
+    
+    if (this.width && this.height)
+      this.setSize(this.options.width, this.options.height);
+    this.setTitle(this.options.title)
+    Windows.register(this);      
+  },
+  
+  // Destructor
+  destroy: function() {
+    this._notify("onDestroy");
+    Event.stopObserving(this.topbar, "mousedown", this.eventMouseDown);
+    Event.stopObserving(this.bottombar, "mousedown", this.eventMouseDown);
+    Event.stopObserving(this.content, "mousedown", this.eventMouseDownContent);
+    
+    Event.stopObserving(window, "load", this.eventOnLoad);
+    Event.stopObserving(window, "resize", this.eventResize);
+    Event.stopObserving(window, "scroll", this.eventResize);
+    
+    Event.stopObserving(this.content, "load", this.options.onload);
+
+    if (this._oldParent) {
+      var content = this.getContent();
+      var originalContent = null;
+      for(var i = 0; i < content.childNodes.length; i++) {
+        originalContent = content.childNodes[i];
+        if (originalContent.nodeType == 1) 
+          break;
+        originalContent = null;
+      }
+      if (originalContent)
+        this._oldParent.appendChild(originalContent);
+      this._oldParent = null;
+    }
+
+    if (this.sizer)
+        Event.stopObserving(this.sizer, "mousedown", this.eventMouseDown);
+
+    if (this.options.url) 
+      this.content.src = null
+
+     if(this.iefix) 
+      Element.remove(this.iefix);
+
+    Element.remove(this.element);
+    Windows.unregister(this);      
+  },
+    
+  // Sets close callback, if it sets, it should return true to be able to close the window.
+  setCloseCallback: function(callback) {
+    this.options.closeCallback = callback;
+  },
+  
+  // Gets window content
+  getContent: function () {
+    return this.content;
+  },
+  
+  // Sets the content with an element id
+  setContent: function(id, autoresize, autoposition) {
+    var element = $(id);
+    if (null == element) throw "Unable to find element '" + id + "' in DOM";
+    this._oldParent = element.parentNode;
+
+    var d = null;
+    var p = null;
+
+    if (autoresize) 
+      d = Element.getDimensions(element);
+    if (autoposition) 
+      p = Position.cumulativeOffset(element);
+
+    var content = this.getContent();
+    // Clear HTML (and even iframe)
+    this.setHTMLContent("");
+    content = this.getContent();
+    
+    content.appendChild(element);
+    element.show();
+    if (autoresize) 
+      this.setSize(d.width, d.height);
+    if (autoposition) 
+      this.setLocation(p[1] - this.heightN, p[0] - this.widthW);    
+  },
+  
+  setHTMLContent: function(html) {
+    // It was an url (iframe), recreate a div content instead of iframe content
+    if (this.options.url) {
+      this.content.src = null;
+      this.options.url = null;
+      
+  	  var content ="<div id=\"" + this.getId() + "_content\" class=\"" + this.options.className + "_content\"> </div>";
+      $(this.getId() +"_table_content").innerHTML = content;
+      
+      this.content = $(this.element.id + "_content");
+    }
+      
+    this.getContent().innerHTML = html;
+  },
+  
+  setAjaxContent: function(url, options, showCentered, showModal) {
+    this.showFunction = showCentered ? "showCenter" : "show";
+    this.showModal = showModal || false;
+  
+    options = options || {};
+
+    // Clear HTML (and even iframe)
+    this.setHTMLContent("");
+ 
+    this.onComplete = options.onComplete;
+    if (! this._onCompleteHandler)
+      this._onCompleteHandler = this._setAjaxContent.bind(this);
+    options.onComplete = this._onCompleteHandler;
+
+    new Ajax.Request(url, options);    
+    options.onComplete = this.onComplete;
+  },
+  
+  _setAjaxContent: function(originalRequest) {
+    Element.update(this.getContent(), originalRequest.responseText);
+    if (this.onComplete)
+      this.onComplete(originalRequest);
+    this.onComplete = null;
+    this[this.showFunction](this.showModal)
+  },
+  
+  setURL: function(url) {
+    // Not an url content, change div to iframe
+    if (this.options.url) 
+      this.content.src = null;
+    this.options.url = url;
+    var content= "<iframe frameborder='0' name='" + this.getId() + "_content'  id='" + this.getId() + "_content' src='" + url + "' width='" + this.width + "' height='" + this.height + "'> </iframe>";
+    $(this.getId() +"_table_content").innerHTML = content;
+    
+    this.content = $(this.element.id + "_content");
+  },
+
+  getURL: function() {
+  	return this.options.url ? this.options.url : null;
+  },
+
+  refresh: function() {
+    if (this.options.url)
+	    $(this.element.getAttribute('id') + '_content').src = this.options.url;
+  },
+  
+  // Stores position/size in a cookie, by default named with window id
+  setCookie: function(name, expires, path, domain, secure) {
+    name = name || this.element.id;
+    this.cookie = [name, expires, path, domain, secure];
+    
+    // Get cookie
+    var value = WindowUtilities.getCookie(name)
+    // If exists
+    if (value) {
+      var values = value.split(',');
+      var x = values[0].split(':');
+      var y = values[1].split(':');
+
+      var w = parseFloat(values[2]), h = parseFloat(values[3]);
+      var mini = values[4];
+      var maxi = values[5];
+
+      this.setSize(w, h);
+      if (mini == "true")
+        this.doMinimize = true; // Minimize will be done at onload window event
+      else if (maxi == "true")
+        this.doMaximize = true; // Maximize will be done at onload window event
+
+      this.useLeft = x[0] == "l";
+      this.useTop = y[0] == "t";
+
+      this.element.setStyle(this.useLeft ? {left: x[1]} : {right: x[1]});
+      this.element.setStyle(this.useTop ? {top: y[1]} : {bottom: y[1]});
+    }
+  },
+  
+  // Gets window ID
+  getId: function() {
+    return this.element.id;
+  },
+  
+  // Detroys itself when closing 
+  setDestroyOnClose: function() {
+    this.options.destroyOnClose = true;
+  },
+  
+  setConstraint: function(bool, padding) {
+    this.constraint = bool;
+    this.constraintPad = Object.extend(this.constraintPad, padding || {});
+    // Reset location to apply constraint
+    if (this.useTop && this.useLeft)
+      this.setLocation(parseFloat(this.element.style.top), parseFloat(this.element.style.left));
+  },
+  
+  // initDrag event
+
+  _initDrag: function(event) {
+    // No resize on minimized window
+    if (Event.element(event) == this.sizer && this.isMinimized())
+      return;
+
+    // No move on maximzed window
+    if (Event.element(event) != this.sizer && this.isMaximized())
+      return;
+      
+    if (Prototype.Browser.IE && this.heightN == 0)
+      this._getWindowBorderSize();
+    
+    // Get pointer X,Y
+    this.pointer = [this._round(Event.pointerX(event), this.options.gridX), this._round(Event.pointerY(event), this.options.gridY)];
+    if (this.options.wiredDrag) 
+      this.currentDrag = this._createWiredElement();
+    else
+      this.currentDrag = this.element;
+      
+    // Resize
+    if (Event.element(event) == this.sizer) {
+      this.doResize = true;
+      this.widthOrg = this.width;
+      this.heightOrg = this.height;
+      this.bottomOrg = parseFloat(this.element.getStyle('bottom'));
+      this.rightOrg = parseFloat(this.element.getStyle('right'));
+      this._notify("onStartResize");
+    }
+    else {
+      this.doResize = false;
+
+      // Check if click on close button, 
+      var closeButton = $(this.getId() + '_close');
+      if (closeButton && Position.within(closeButton, this.pointer[0], this.pointer[1])) {
+        this.currentDrag = null;
+        return;
+      }
+
+      this.toFront();
+
+      if (! this.options.draggable) 
+        return;
+      this._notify("onStartMove");
+    }    
+    // Register global event to capture mouseUp and mouseMove
+    Event.observe(document, "mouseup", this.eventMouseUp, false);
+    Event.observe(document, "mousemove", this.eventMouseMove, false);
+    
+    // Add an invisible div to keep catching mouse event over iframes
+    WindowUtilities.disableScreen('__invisible__', '__invisible__', this.overlayOpacity);
+
+    // Stop selection while dragging
+    document.body.ondrag = function () { return false; };
+    document.body.onselectstart = function () { return false; };
+    
+    this.currentDrag.show();
+    Event.stop(event);
+  },
+  
+  _round: function(val, round) {
+    return round == 1 ? val  : val = Math.floor(val / round) * round;
+  },
+
+  // updateDrag event
+  _updateDrag: function(event) {
+    var pointer =  [this._round(Event.pointerX(event), this.options.gridX), this._round(Event.pointerY(event), this.options.gridY)];  
+    var dx = pointer[0] - this.pointer[0];
+    var dy = pointer[1] - this.pointer[1];
+    
+    // Resize case, update width/height
+    if (this.doResize) {
+      var w = this.widthOrg + dx;
+      var h = this.heightOrg + dy;
+      
+      dx = this.width - this.widthOrg
+      dy = this.height - this.heightOrg
+      
+      // Check if it's a right position, update it to keep upper-left corner at the same position
+      if (this.useLeft) 
+        w = this._updateWidthConstraint(w)
+      else 
+        this.currentDrag.setStyle({right: (this.rightOrg -dx) + 'px'});
+      // Check if it's a bottom position, update it to keep upper-left corner at the same position
+      if (this.useTop) 
+        h = this._updateHeightConstraint(h)
+      else
+        this.currentDrag.setStyle({bottom: (this.bottomOrg -dy) + 'px'});
+        
+      this.setSize(w , h);
+      this._notify("onResize");
+    }
+    // Move case, update top/left
+    else {
+      this.pointer = pointer;
+      
+      if (this.useLeft) {
+        var left =  parseFloat(this.currentDrag.getStyle('left')) + dx;
+        var newLeft = this._updateLeftConstraint(left);
+        // Keep mouse pointer correct
+        this.pointer[0] += newLeft-left;
+        this.currentDrag.setStyle({left: newLeft + 'px'});
+      }
+      else 
+        this.currentDrag.setStyle({right: parseFloat(this.currentDrag.getStyle('right')) - dx + 'px'});
+      
+      if (this.useTop) {
+        var top =  parseFloat(this.currentDrag.getStyle('top')) + dy;
+        var newTop = this._updateTopConstraint(top);
+        // Keep mouse pointer correct
+        this.pointer[1] += newTop - top;
+        this.currentDrag.setStyle({top: newTop + 'px'});
+      }
+      else 
+        this.currentDrag.setStyle({bottom: parseFloat(this.currentDrag.getStyle('bottom')) - dy + 'px'});
+
+      this._notify("onMove");
+    }
+    if (this.iefix) 
+      this._fixIEOverlapping(); 
+      
+    this._removeStoreLocation();
+    Event.stop(event);
+  },
+
+   // endDrag callback
+   _endDrag: function(event) {
+    // Remove temporary div over iframes
+     WindowUtilities.enableScreen('__invisible__');
+    
+    if (this.doResize)
+      this._notify("onEndResize");
+    else
+      this._notify("onEndMove");
+    
+    // Release event observing
+    Event.stopObserving(document, "mouseup", this.eventMouseUp,false);
+    Event.stopObserving(document, "mousemove", this.eventMouseMove, false);
+
+    Event.stop(event);
+    
+    this._hideWiredElement();
+
+    // Store new location/size if need be
+    this._saveCookie()
+      
+    // Restore selection
+    document.body.ondrag = null;
+    document.body.onselectstart = null;
+  },
+
+  _updateLeftConstraint: function(left) {
+    if (this.constraint && this.useLeft && this.useTop) {
+      var width = this.options.parent == document.body ? WindowUtilities.getPageSize().windowWidth : this.options.parent.getDimensions().width;
+
+      if (left < this.constraintPad.left)
+        left = this.constraintPad.left;
+      if (left + this.width + this.widthE + this.widthW > width - this.constraintPad.right) 
+        left = width - this.constraintPad.right - this.width - this.widthE - this.widthW;
+    }
+    return left;
+  },
+  
+  _updateTopConstraint: function(top) {
+    if (this.constraint && this.useLeft && this.useTop) {        
+      var height = this.options.parent == document.body ? WindowUtilities.getPageSize().windowHeight : this.options.parent.getDimensions().height;
+      
+      var h = this.height + this.heightN + this.heightS;
+
+      if (top < this.constraintPad.top)
+        top = this.constraintPad.top;
+      if (top + h > height - this.constraintPad.bottom) 
+        top = height - this.constraintPad.bottom - h;
+    }
+    return top;
+  },
+  
+  _updateWidthConstraint: function(w) {
+    if (this.constraint && this.useLeft && this.useTop) {
+      var width = this.options.parent == document.body ? WindowUtilities.getPageSize().windowWidth : this.options.parent.getDimensions().width;
+      var left =  parseFloat(this.element.getStyle("left"));
+
+      if (left + w + this.widthE + this.widthW > width - this.constraintPad.right) 
+        w = width - this.constraintPad.right - left - this.widthE - this.widthW;
+    }
+    return w;
+  },
+  
+  _updateHeightConstraint: function(h) {
+    if (this.constraint && this.useLeft && this.useTop) {
+      var height = this.options.parent == document.body ? WindowUtilities.getPageSize().windowHeight : this.options.parent.getDimensions().height;
+      var top =  parseFloat(this.element.getStyle("top"));
+
+      if (top + h + this.heightN + this.heightS > height - this.constraintPad.bottom) 
+        h = height - this.constraintPad.bottom - top - this.heightN - this.heightS;
+    }
+    return h;
+  },
+  
+  
+  // Creates HTML window code
+  _createWindow: function(id) {
+    var className = this.options.className;
+    var win = document.createElement("div");
+    win.setAttribute('id', id);
+    win.className = "dialog";
+
+    var content;
+    if (this.options.url)
+      content= "<iframe frameborder=\"0\" name=\"" + id + "_content\"  id=\"" + id + "_content\" src=\"" + this.options.url + "\"> </iframe>";
+    else
+      content ="<div id=\"" + id + "_content\" class=\"" +className + "_content\"> </div>";
+
+    var closeDiv = this.options.closable ? "<div class='"+ className +"_close' id='"+ id +"_close' onclick='Windows.close(\""+ id +"\", event)'> </div>" : "";
+    var minDiv = this.options.minimizable ? "<div class='"+ className + "_minimize' id='"+ id +"_minimize' onclick='Windows.minimize(\""+ id +"\", event)'> </div>" : "";
+    var maxDiv = this.options.maximizable ? "<div class='"+ className + "_maximize' id='"+ id +"_maximize' onclick='Windows.maximize(\""+ id +"\", event)'> </div>" : "";
+    var seAttributes = this.options.resizable ? "class='" + className + "_sizer' id='" + id + "_sizer'" : "class='"  + className + "_se'";
+    var blank = "../themes/default/blank.gif";
+    
+    win.innerHTML = closeDiv + minDiv + maxDiv + "\
+      <table id='"+ id +"_row1' class=\"top table_window\">\
+        <tr>\
+          <td class='"+ className +"_nw'></td>\
+          <td class='"+ className +"_n'><div id='"+ id +"_top' class='"+ className +"_title title_window'>"+ this.options.title +"</div></td>\
+          <td class='"+ className +"_ne'></td>\
+        </tr>\
+      </table>\
+      <table id='"+ id +"_row2' class=\"mid table_window\">\
+        <tr>\
+          <td class='"+ className +"_w'></td>\
+            <td id='"+ id +"_table_content' class='"+ className +"_content' valign='top'>" + content + "</td>\
+          <td class='"+ className +"_e'></td>\
+        </tr>\
+      </table>\
+        <table id='"+ id +"_row3' class=\"bot table_window\">\
+        <tr>\
+          <td class='"+ className +"_sw'></td>\
+            <td class='"+ className +"_s'><div id='"+ id +"_bottom' class='status_bar'><span style='float:left; width:1px; height:1px'></span></div></td>\
+            <td " + seAttributes + "></td>\
+        </tr>\
+      </table>\
+    ";
+    Element.hide(win);
+    this.options.parent.insertBefore(win, this.options.parent.firstChild);
+    Event.observe($(id + "_content"), "load", this.options.onload);
+    return win;
+  },
+  
+  
+  changeClassName: function(newClassName) {    
+    var className = this.options.className;
+    var id = this.getId();
+    $A(["_close", "_minimize", "_maximize", "_sizer", "_content"]).each(function(value) { this._toggleClassName($(id + value), className + value, newClassName + value) }.bind(this));
+    this._toggleClassName($(id + "_top"), className + "_title", newClassName + "_title");
+    $$("#" + id + " td").each(function(td) {td.className = td.className.sub(className,newClassName); });
+    this.options.className = newClassName;
+  },
+  
+  _toggleClassName: function(element, oldClassName, newClassName) { 
+    if (element) {
+      element.removeClassName(oldClassName);
+      element.addClassName(newClassName);
+    }
+  },
+  
+  // Sets window location
+  setLocation: function(top, left) {
+    top = this._updateTopConstraint(top);
+    left = this._updateLeftConstraint(left);
+
+    var e = this.currentDrag || this.element;
+    e.setStyle({top: top + 'px'});
+    e.setStyle({left: left + 'px'});
+
+    this.useLeft = true;
+    this.useTop = true;
+  },
+    
+  getLocation: function() {
+    var location = {};
+    if (this.useTop)
+      location = Object.extend(location, {top: this.element.getStyle("top")});
+    else
+      location = Object.extend(location, {bottom: this.element.getStyle("bottom")});
+    if (this.useLeft)
+      location = Object.extend(location, {left: this.element.getStyle("left")});
+    else
+      location = Object.extend(location, {right: this.element.getStyle("right")});
+    
+    return location;
+  },
+  
+  // Gets window size
+  getSize: function() {
+    return {width: this.width, height: this.height};
+  },
+    
+  // Sets window size
+  setSize: function(width, height, useEffect) {    
+    width = parseFloat(width);
+    height = parseFloat(height);
+    
+    // Check min and max size
+    if (!this.minimized && width < this.options.minWidth)
+      width = this.options.minWidth;
+
+    if (!this.minimized && height < this.options.minHeight)
+      height = this.options.minHeight;
+      
+    if (this.options. maxHeight && height > this.options. maxHeight)
+      height = this.options. maxHeight;
+
+    if (this.options. maxWidth && width > this.options. maxWidth)
+      width = this.options. maxWidth;
+
+    
+    if (this.useTop && this.useLeft && Window.hasEffectLib && Effect.ResizeWindow && useEffect) {
+      new Effect.ResizeWindow(this, null, null, width, height, {duration: Window.resizeEffectDuration});
+    } else {
+      this.width = width;
+      this.height = height;
+      var e = this.currentDrag ? this.currentDrag : this.element;
+
+      e.setStyle({width: width + this.widthW + this.widthE + "px"})
+      e.setStyle({height: height  + this.heightN + this.heightS + "px"})
+
+      // Update content size
+      if (!this.currentDrag || this.currentDrag == this.element) {
+        var content = $(this.element.id + '_content');
+        content.setStyle({height: height  + 'px'});
+        content.setStyle({width: width  + 'px'});
+      }
+    }
+  },
+  
+  updateHeight: function() {
+    this.setSize(this.width, this.content.scrollHeight, true);
+  },
+  
+  updateWidth: function() {
+    this.setSize(this.content.scrollWidth, this.height, true);
+  },
+  
+  // Brings window to front
+  toFront: function() {
+    if (this.element.style.zIndex < Windows.maxZIndex)  
+      this.setZIndex(Windows.maxZIndex + 1);
+    if (this.iefix) 
+      this._fixIEOverlapping(); 
+  },
+   
+  getBounds: function(insideOnly) {
+    if (! this.width || !this.height || !this.visible)  
+      this.computeBounds();
+    var w = this.width;
+    var h = this.height;
+
+    if (!insideOnly) {
+      w += this.widthW + this.widthE;
+      h += this.heightN + this.heightS;
+    }
+    var bounds = Object.extend(this.getLocation(), {width: w + "px", height: h + "px"});
+    return bounds;
+  },
+      
+  computeBounds: function() {
+     if (! this.width || !this.height) {
+      var size = WindowUtilities._computeSize(this.content.innerHTML, this.content.id, this.width, this.height, 0, this.options.className)
+      if (this.height)
+        this.width = size + 5
+      else
+        this.height = size + 5
+    }
+
+    this.setSize(this.width, this.height);
+    if (this.centered)
+      this._center(this.centerTop, this.centerLeft);    
+  },
+  
+  // Displays window modal state or not
+  show: function(modal) {
+    this.visible = true;
+    if (modal) {
+      // Hack for Safari !!
+      if (typeof this.overlayOpacity == "undefined") {
+        var that = this;
+        setTimeout(function() {that.show(modal)}, 10);
+        return;
+      }
+      Windows.addModalWindow(this);
+      
+      this.modal = true;      
+      this.setZIndex(Windows.maxZIndex + 1);
+      Windows.unsetOverflow(this);
+    }
+    else    
+      if (!this.element.style.zIndex) 
+        this.setZIndex(Windows.maxZIndex + 1);        
+      
+    // To restore overflow if need be
+    if (this.oldStyle)
+      this.getContent().setStyle({overflow: this.oldStyle});
+      
+    this.computeBounds();
+    
+    this._notify("onBeforeShow");   
+    if (this.options.showEffect != Element.show && this.options.showEffectOptions)
+      this.options.showEffect(this.element, this.options.showEffectOptions);  
+    else
+      this.options.showEffect(this.element);  
+      
+    this._checkIEOverlapping();
+    WindowUtilities.focusedWindow = this
+    this._notify("onShow");   
+  },
+  
+  // Displays window modal state or not at the center of the page
+  showCenter: function(modal, top, left) {
+    this.centered = true;
+    this.centerTop = top;
+    this.centerLeft = left;
+
+    this.show(modal);
+  },
+  
+  isVisible: function() {
+    return this.visible;
+  },
+  
+  _center: function(top, left) {    
+    var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);    
+    var pageSize = WindowUtilities.getPageSize(this.options.parent);    
+    if (typeof top == "undefined")
+      top = (pageSize.windowHeight - (this.height + this.heightN + this.heightS))/2;
+    top += windowScroll.top
+    
+    if (typeof left == "undefined")
+      left = (pageSize.windowWidth - (this.width + this.widthW + this.widthE))/2;
+    left += windowScroll.left      
+    this.setLocation(top, left);
+    this.toFront();
+  },
+  
+  _recenter: function(event) {     
+    if (this.centered) {
+      var pageSize = WindowUtilities.getPageSize(this.options.parent);
+      var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);    
+
+      // Check for this stupid IE that sends dumb events
+      if (this.pageSize && this.pageSize.windowWidth == pageSize.windowWidth && this.pageSize.windowHeight == pageSize.windowHeight && 
+          this.windowScroll.left == windowScroll.left && this.windowScroll.top == windowScroll.top) 
+        return;
+      this.pageSize = pageSize;
+      this.windowScroll = windowScroll;
+      // set height of Overlay to take up whole page and show
+      if ($('overlay_modal')) 
+        $('overlay_modal').setStyle({height: (pageSize.pageHeight + 'px')});
+      
+      if (this.options.recenterAuto)
+        this._center(this.centerTop, this.centerLeft);    
+    }
+  },
+  
+  // Hides window
+  hide: function() {
+    this.visible = false;
+    if (this.modal) {
+      Windows.removeModalWindow(this);
+      Windows.resetOverflow();
+    }
+    // To avoid bug on scrolling bar
+    this.oldStyle = this.getContent().getStyle('overflow') || "auto"
+    this.getContent().setStyle({overflow: "hidden"});
+
+    this.options.hideEffect(this.element, this.options.hideEffectOptions);  
+
+     if(this.iefix) 
+      this.iefix.hide();
+
+    if (!this.doNotNotifyHide)
+      this._notify("onHide");
+  },
+
+  close: function() {
+    // Asks closeCallback if exists
+    if (this.visible) {
+      if (this.options.closeCallback && ! this.options.closeCallback(this)) 
+        return;
+
+      if (this.options.destroyOnClose) {
+        var destroyFunc = this.destroy.bind(this);
+        if (this.options.hideEffectOptions.afterFinish) {
+          var func = this.options.hideEffectOptions.afterFinish;
+          this.options.hideEffectOptions.afterFinish = function() {func();destroyFunc() }
+        }
+        else 
+          this.options.hideEffectOptions.afterFinish = function() {destroyFunc() }
+      }
+      Windows.updateFocusedWindow();
+      
+      this.doNotNotifyHide = true;
+      this.hide();
+      this.doNotNotifyHide = false;
+      this._notify("onClose");
+    }
+  },
+  
+  minimize: function() {
+    if (this.resizing)
+      return;
+    
+    var r2 = $(this.getId() + "_row2");
+    
+    if (!this.minimized) {
+      this.minimized = true;
+
+      var dh = r2.getDimensions().height;
+      this.r2Height = dh;
+      var h  = this.element.getHeight() - dh;
+
+      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
+        new Effect.ResizeWindow(this, null, null, null, this.height -dh, {duration: Window.resizeEffectDuration});
+      } else  {
+        this.height -= dh;
+        this.element.setStyle({height: h + "px"});
+        r2.hide();
+      }
+
+      if (! this.useTop) {
+        var bottom = parseFloat(this.element.getStyle('bottom'));
+        this.element.setStyle({bottom: (bottom + dh) + 'px'});
+      }
+    } 
+    else {      
+      this.minimized = false;
+      
+      var dh = this.r2Height;
+      this.r2Height = null;
+      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
+        new Effect.ResizeWindow(this, null, null, null, this.height + dh, {duration: Window.resizeEffectDuration});
+      }
+      else {
+        var h  = this.element.getHeight() + dh;
+        this.height += dh;
+        this.element.setStyle({height: h + "px"})
+        r2.show();
+      }
+      if (! this.useTop) {
+        var bottom = parseFloat(this.element.getStyle('bottom'));
+        this.element.setStyle({bottom: (bottom - dh) + 'px'});
+      }
+      this.toFront();
+    }
+    this._notify("onMinimize");
+    
+    // Store new location/size if need be
+    this._saveCookie()
+  },
+  
+  maximize: function() {
+    if (this.isMinimized() || this.resizing)
+      return;
+  
+    if (Prototype.Browser.IE && this.heightN == 0)
+      this._getWindowBorderSize();
+      
+    if (this.storedLocation != null) {
+      this._restoreLocation();
+      if(this.iefix) 
+        this.iefix.hide();
+    }
+    else {
+      this._storeLocation();
+      Windows.unsetOverflow(this);
+      
+      var windowScroll = WindowUtilities.getWindowScroll(this.options.parent);
+      var pageSize = WindowUtilities.getPageSize(this.options.parent);    
+      var left = windowScroll.left;
+      var top = windowScroll.top;
+      
+      if (this.options.parent != document.body) {
+        windowScroll =  {top:0, left:0, bottom:0, right:0};
+        var dim = this.options.parent.getDimensions();
+        pageSize.windowWidth = dim.width;
+        pageSize.windowHeight = dim.height;
+        top = 0; 
+        left = 0;
+      }
+      
+      if (this.constraint) {
+        pageSize.windowWidth -= Math.max(0, this.constraintPad.left) + Math.max(0, this.constraintPad.right);
+        pageSize.windowHeight -= Math.max(0, this.constraintPad.top) + Math.max(0, this.constraintPad.bottom);
+        left +=  Math.max(0, this.constraintPad.left);
+        top +=  Math.max(0, this.constraintPad.top);
+      }
+      
+      var width = pageSize.windowWidth - this.widthW - this.widthE;
+      var height= pageSize.windowHeight - this.heightN - this.heightS;
+
+      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow) {
+        new Effect.ResizeWindow(this, top, left, width, height, {duration: Window.resizeEffectDuration});
+      }
+      else {
+        this.setSize(width, height);
+        this.element.setStyle(this.useLeft ? {left: left} : {right: left});
+        this.element.setStyle(this.useTop ? {top: top} : {bottom: top});
+      }
+        
+      this.toFront();
+      if (this.iefix) 
+        this._fixIEOverlapping(); 
+    }
+    this._notify("onMaximize");
+
+    // Store new location/size if need be
+    this._saveCookie()
+  },
+  
+  isMinimized: function() {
+    return this.minimized;
+  },
+  
+  isMaximized: function() {
+    return (this.storedLocation != null);
+  },
+  
+  setOpacity: function(opacity) {
+    if (Element.setOpacity)
+      Element.setOpacity(this.element, opacity);
+  },
+  
+  setZIndex: function(zindex) {
+    this.element.setStyle({zIndex: zindex});
+    Windows.updateZindex(zindex, this);
+  },
+
+  setTitle: function(newTitle) {
+    if (!newTitle || newTitle == "") 
+      newTitle = "&nbsp;";
+      
+    Element.update(this.element.id + '_top', newTitle);
+  },
+   
+  getTitle: function() {
+    return $(this.element.id + '_top').innerHTML;
+  },
+  
+  setStatusBar: function(element) {
+    var statusBar = $(this.getId() + "_bottom");
+
+    if (typeof(element) == "object") {
+      if (this.bottombar.firstChild)
+        this.bottombar.replaceChild(element, this.bottombar.firstChild);
+      else
+        this.bottombar.appendChild(element);
+    }
+    else
+      this.bottombar.innerHTML = element;
+  },
+
+  _checkIEOverlapping: function() {
+    if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (navigator.userAgent.indexOf('Opera')<0) && (this.element.getStyle('position')=='absolute')) {
+        new Insertion.After(this.element.id, '<iframe id="' + this.element.id + '_iefix" '+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+        this.iefix = $(this.element.id+'_iefix');
+    }
+    if(this.iefix) 
+      setTimeout(this._fixIEOverlapping.bind(this), 50);
+  },
+
+  _fixIEOverlapping: function() {
+      Position.clone(this.element, this.iefix);
+      this.iefix.style.zIndex = this.element.style.zIndex - 1;
+      this.iefix.show();
+  },
+  
+  _getWindowBorderSize: function(event) {
+    // Hack to get real window border size!!
+    var div = this._createHiddenDiv(this.options.className + "_n")
+    this.heightN = Element.getDimensions(div).height;    
+    div.parentNode.removeChild(div)
+
+    var div = this._createHiddenDiv(this.options.className + "_s")
+    this.heightS = Element.getDimensions(div).height;    
+    div.parentNode.removeChild(div)
+
+    var div = this._createHiddenDiv(this.options.className + "_e")
+    this.widthE = Element.getDimensions(div).width;    
+    div.parentNode.removeChild(div)
+
+    var div = this._createHiddenDiv(this.options.className + "_w")
+    this.widthW = Element.getDimensions(div).width;
+    div.parentNode.removeChild(div);
+    
+    var div = document.createElement("div");
+    div.className = "overlay_" + this.options.className ;
+    document.body.appendChild(div);
+    //alert("no timeout:\nopacity: " + div.getStyle("opacity") + "\nwidth: " + document.defaultView.getComputedStyle(div, null).width);
+    var that = this;
+    
+    // Workaround for Safari!!
+    setTimeout(function() {that.overlayOpacity = ($(div).getStyle("opacity")); div.parentNode.removeChild(div);}, 10);
+    
+    // Workaround for IE!!
+    if (Prototype.Browser.IE) {
+      this.heightS = $(this.getId() +"_row3").getDimensions().height;
+      this.heightN = $(this.getId() +"_row1").getDimensions().height;
+    }
+
+    // Safari size fix
+    if (Prototype.Browser.WebKit && Prototype.Browser.WebKitVersion < 420)
+      this.setSize(this.width, this.height);
+    if (this.doMaximize)
+      this.maximize();
+    if (this.doMinimize)
+      this.minimize();
+  },
+ 
+  _createHiddenDiv: function(className) {
+    var objBody = document.body;
+    var win = document.createElement("div");
+    win.setAttribute('id', this.element.id+ "_tmp");
+    win.className = className;
+    win.style.display = 'none';
+    win.innerHTML = '';
+    objBody.insertBefore(win, objBody.firstChild);
+    return win;
+  },
+  
+  _storeLocation: function() {
+    if (this.storedLocation == null) {
+      this.storedLocation = {useTop: this.useTop, useLeft: this.useLeft, 
+                             top: this.element.getStyle('top'), bottom: this.element.getStyle('bottom'),
+                             left: this.element.getStyle('left'), right: this.element.getStyle('right'),
+                             width: this.width, height: this.height };
+    }
+  },
+  
+  _restoreLocation: function() {
+    if (this.storedLocation != null) {
+      this.useLeft = this.storedLocation.useLeft;
+      this.useTop = this.storedLocation.useTop;
+      
+      if (this.useLeft && this.useTop && Window.hasEffectLib && Effect.ResizeWindow)
+        new Effect.ResizeWindow(this, this.storedLocation.top, this.storedLocation.left, this.storedLocation.width, this.storedLocation.height, {duration: Window.resizeEffectDuration});
+      else {
+        this.element.setStyle(this.useLeft ? {left: this.storedLocation.left} : {right: this.storedLocation.right});
+        this.element.setStyle(this.useTop ? {top: this.storedLocation.top} : {bottom: this.storedLocation.bottom});
+        this.setSize(this.storedLocation.width, this.storedLocation.height);
+      }
+      
+      Windows.resetOverflow();
+      this._removeStoreLocation();
+    }
+  },
+  
+  _removeStoreLocation: function() {
+    this.storedLocation = null;
+  },
+  
+  _saveCookie: function() {
+    if (this.cookie) {
+      var value = "";
+      if (this.useLeft)
+        value += "l:" +  (this.storedLocation ? this.storedLocation.left : this.element.getStyle('left'))
+      else
+        value += "r:" + (this.storedLocation ? this.storedLocation.right : this.element.getStyle('right'))
+      if (this.useTop)
+        value += ",t:" + (this.storedLocation ? this.storedLocation.top : this.element.getStyle('top'))
+      else
+        value += ",b:" + (this.storedLocation ? this.storedLocation.bottom :this.element.getStyle('bottom'))
+        
+      value += "," + (this.storedLocation ? this.storedLocation.width : this.width);
+      value += "," + (this.storedLocation ? this.storedLocation.height : this.height);
+      value += "," + this.isMinimized();
+      value += "," + this.isMaximized();
+      WindowUtilities.setCookie(value, this.cookie)
+    }
+  },
+  
+  _createWiredElement: function() {
+    if (! this.wiredElement) {
+      if (Prototype.Browser.IE)
+        this._getWindowBorderSize();
+      var div = document.createElement("div");
+      div.className = "wired_frame " + this.options.className + "_wired_frame";
+      
+      div.style.position = 'absolute';
+      this.options.parent.insertBefore(div, this.options.parent.firstChild);
+      this.wiredElement = $(div);
+    }
+    if (this.useLeft) 
+      this.wiredElement.setStyle({left: this.element.getStyle('left')});
+    else 
+      this.wiredElement.setStyle({right: this.element.getStyle('right')});
+      
+    if (this.useTop) 
+      this.wiredElement.setStyle({top: this.element.getStyle('top')});
+    else 
+      this.wiredElement.setStyle({bottom: this.element.getStyle('bottom')});
+
+    var dim = this.element.getDimensions();
+    this.wiredElement.setStyle({width: dim.width + "px", height: dim.height +"px"});
+
+    this.wiredElement.setStyle({zIndex: Windows.maxZIndex+30});
+    return this.wiredElement;
+  },
+  
+  _hideWiredElement: function() {
+    if (! this.wiredElement || ! this.currentDrag)
+      return;
+    if (this.currentDrag == this.element) 
+      this.currentDrag = null;
+    else {
+      if (this.useLeft) 
+        this.element.setStyle({left: this.currentDrag.getStyle('left')});
+      else 
+        this.element.setStyle({right: this.currentDrag.getStyle('right')});
+
+      if (this.useTop) 
+        this.element.setStyle({top: this.currentDrag.getStyle('top')});
+      else 
+        this.element.setStyle({bottom: this.currentDrag.getStyle('bottom')});
+
+      this.currentDrag.hide();
+      this.currentDrag = null;
+      if (this.doResize)
+        this.setSize(this.width, this.height);
+    } 
+  },
+  
+  _notify: function(eventName) {
+    if (this.options[eventName])
+      this.options[eventName](this);
+    else
+      Windows.notify(eventName, this);
+  }
+};
+
+// Windows containers, register all page windows
+var Windows = {
+  windows: [],
+  modalWindows: [],
+  observers: [],
+  focusedWindow: null,
+  maxZIndex: 0,
+  overlayShowEffectOptions: {duration: 0.5},
+  overlayHideEffectOptions: {duration: 0.5},
+
+  addObserver: function(observer) {
+    this.removeObserver(observer);
+    this.observers.push(observer);
+  },
+  
+  removeObserver: function(observer) {  
+    this.observers = this.observers.reject( function(o) { return o==observer });
+  },
+  
+  // onDestroy onStartResize onStartMove onResize onMove onEndResize onEndMove onFocus onBlur onBeforeShow onShow onHide onMinimize onMaximize onClose
+  notify: function(eventName, win) {  
+    this.observers.each( function(o) {if(o[eventName]) o[eventName](eventName, win);});
+  },
+
+  // Gets window from its id
+  getWindow: function(id) {
+    return this.windows.detect(function(d) { return d.getId() ==id });
+  },
+
+  // Gets the last focused window
+  getFocusedWindow: function() {
+    return this.focusedWindow;
+  },
+
+  updateFocusedWindow: function() {
+    this.focusedWindow = this.windows.length >=2 ? this.windows[this.windows.length-2] : null;    
+  },
+  
+  // Registers a new window (called by Windows constructor)
+  register: function(win) {
+    this.windows.push(win);
+  },
+    
+  // Add a modal window in the stack
+  addModalWindow: function(win) {
+    // Disable screen if first modal window
+    if (this.modalWindows.length == 0) {
+      WindowUtilities.disableScreen(win.options.className, 'overlay_modal', win.overlayOpacity, win.getId(), win.options.parent);
+    }
+    else {
+      // Move overlay over all windows
+      if (Window.keepMultiModalWindow) {
+        $('overlay_modal').style.zIndex = Windows.maxZIndex + 1;
+        Windows.maxZIndex += 1;
+        WindowUtilities._hideSelect(this.modalWindows.last().getId());
+      }
+      // Hide current modal window
+      else
+        this.modalWindows.last().element.hide();
+      // Fucking IE select issue
+      WindowUtilities._showSelect(win.getId());
+    }      
+    this.modalWindows.push(win);    
+  },
+  
+  removeModalWindow: function(win) {
+    this.modalWindows.pop();
+    
+    // No more modal windows
+    if (this.modalWindows.length == 0)
+      WindowUtilities.enableScreen();     
+    else {
+      if (Window.keepMultiModalWindow) {
+        this.modalWindows.last().toFront();
+        WindowUtilities._showSelect(this.modalWindows.last().getId());        
+      }
+      else
+        this.modalWindows.last().element.show();
+    }
+  },
+  
+  // Registers a new window (called by Windows constructor)
+  register: function(win) {
+    this.windows.push(win);
+  },
+  
+  // Unregisters a window (called by Windows destructor)
+  unregister: function(win) {
+    this.windows = this.windows.reject(function(d) { return d==win });
+  }, 
+  
+  // Closes all windows
+  closeAll: function() {  
+    this.windows.each( function(w) {Windows.close(w.getId())} );
+  },
+  
+  closeAllModalWindows: function() {
+    WindowUtilities.enableScreen();     
+    this.modalWindows.each( function(win) {if (win) win.close()});    
+  },
+
+  // Minimizes a window with its id
+  minimize: function(id, event) {
+    var win = this.getWindow(id)
+    if (win && win.visible)
+      win.minimize();
+    Event.stop(event);
+  },
+  
+  // Maximizes a window with its id
+  maximize: function(id, event) {
+    var win = this.getWindow(id)
+    if (win && win.visible)
+      win.maximize();
+    Event.stop(event);
+  },
+
+  // Closes a window with its id
+  close: function(id, event) {
+    var win = this.getWindow(id);
+    if (win) 
+      win.close();
+    if (event)
+      Event.stop(event);
+  },
+  
+  blur: function(id) {
+    var win = this.getWindow(id);  
+    if (!win)
+      return;
+    if (win.options.blurClassName)
+      win.changeClassName(win.options.blurClassName);
+    if (this.focusedWindow == win)  
+      this.focusedWindow = null;
+    win._notify("onBlur");  
+  },
+  
+  focus: function(id) {
+    var win = this.getWindow(id);  
+    if (!win)
+      return;       
+    if (this.focusedWindow)
+      this.blur(this.focusedWindow.getId())
+
+    if (win.options.focusClassName)
+      win.changeClassName(win.options.focusClassName);  
+    this.focusedWindow = win;
+    win._notify("onFocus");
+  },
+  
+  unsetOverflow: function(except) {    
+    this.windows.each(function(d) { d.oldOverflow = d.getContent().getStyle("overflow") || "auto" ; d.getContent().setStyle({overflow: "hidden"}) });
+    if (except && except.oldOverflow)
+      except.getContent().setStyle({overflow: except.oldOverflow});
+  },
+
+  resetOverflow: function() {
+    this.windows.each(function(d) { if (d.oldOverflow) d.getContent().setStyle({overflow: d.oldOverflow}) });
+  },
+
+  updateZindex: function(zindex, win) { 
+    if (zindex > this.maxZIndex) {   
+      this.maxZIndex = zindex;    
+      if (this.focusedWindow) 
+        this.blur(this.focusedWindow.getId())
+    }
+    this.focusedWindow = win;
+    if (this.focusedWindow) 
+      this.focus(this.focusedWindow.getId())
+  }
+};
+
+var Dialog = {
+  dialogId: null,
+  onCompleteFunc: null,
+  callFunc: null, 
+  parameters: null, 
+    
+  confirm: function(content, parameters) {
+    // Get Ajax return before
+    if (content && typeof content != "string") {
+      Dialog._runAjaxRequest(content, parameters, Dialog.confirm);
+      return 
+    }
+    content = content || "";
+    
+    parameters = parameters || {};
+    var okLabel = parameters.okLabel ? parameters.okLabel : "Ok";
+    var cancelLabel = parameters.cancelLabel ? parameters.cancelLabel : "Cancel";
+
+    // Backward compatibility
+    parameters = Object.extend(parameters, parameters.windowParameters || {});
+    parameters.windowParameters = parameters.windowParameters || {};
+
+    parameters.className = parameters.className || "alert";
+
+    var okButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " ok_button'" 
+    var cancelButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " cancel_button'" 
+    var content = "\
+      <div class='" + parameters.className + "_message'>" + content  + "</div>\
+        <div class='" + parameters.className + "_buttons'>\
+          <input type='button' value='" + okLabel + "' onclick='Dialog.okCallback()' " + okButtonClass + "/>\
+          <input type='button' value='" + cancelLabel + "' onclick='Dialog.cancelCallback()' " + cancelButtonClass + "/>\
+        </div>\
+    ";
+    return this._openDialog(content, parameters)
+  },
+  
+  alert: function(content, parameters) {
+    // Get Ajax return before
+    if (content && typeof content != "string") {
+      Dialog._runAjaxRequest(content, parameters, Dialog.alert);
+      return 
+    }
+    content = content || "";
+    
+    parameters = parameters || {};
+    var okLabel = parameters.okLabel ? parameters.okLabel : "Ok";
+
+    // Backward compatibility    
+    parameters = Object.extend(parameters, parameters.windowParameters || {});
+    parameters.windowParameters = parameters.windowParameters || {};
+    
+    parameters.className = parameters.className || "alert";
+    
+    var okButtonClass = "class ='" + (parameters.buttonClass ? parameters.buttonClass + " " : "") + " ok_button'" 
+    var content = "\
+      <div class='" + parameters.className + "_message'>" + content  + "</div>\
+        <div class='" + parameters.className + "_buttons'>\
+          <input type='button' value='" + okLabel + "' onclick='Dialog.okCallback()' " + okButtonClass + "/>\
+        </div>";                  
+    return this._openDialog(content, parameters)
+  },
+  
+  info: function(content, parameters) {   
+    // Get Ajax return before
+    if (content && typeof content != "string") {
+      Dialog._runAjaxRequest(content, parameters, Dialog.info);
+      return 
+    }
+    content = content || "";
+     
+    // Backward compatibility
+    parameters = parameters || {};
+    parameters = Object.extend(parameters, parameters.windowParameters || {});
+    parameters.windowParameters = parameters.windowParameters || {};
+    
+    parameters.className = parameters.className || "alert";
+    
+    var content = "<div id='modal_dialog_message' class='" + parameters.className + "_message'>" + content  + "</div>";
+    if (parameters.showProgress)
+      content += "<div id='modal_dialog_progress' class='" + parameters.className + "_progress'>  </div>";
+
+    parameters.ok = null;
+    parameters.cancel = null;
+    
+    return this._openDialog(content, parameters)
+  },
+  
+  setInfoMessage: function(message) {
+    $('modal_dialog_message').update(message);
+  },
+  
+  closeInfo: function() {
+    Windows.close(this.dialogId);
+  },
+  
+  _openDialog: function(content, parameters) {
+    var className = parameters.className;
+    
+    if (! parameters.height && ! parameters.width) {
+      parameters.width = WindowUtilities.getPageSize(parameters.options.parent || document.body).pageWidth / 2;
+    }
+    if (parameters.id)
+      this.dialogId = parameters.id;
+    else { 
+      var t = new Date();
+      this.dialogId = 'modal_dialog_' + t.getTime();
+      parameters.id = this.dialogId;
+    }
+
+    // compute height or width if need be
+    if (! parameters.height || ! parameters.width) {
+      var size = WindowUtilities._computeSize(content, this.dialogId, parameters.width, parameters.height, 5, className)
+      if (parameters.height)
+        parameters.width = size + 5
+      else
+        parameters.height = size + 5
+    }
+    parameters.effectOptions = parameters.effectOptions ;
+    parameters.resizable   = parameters.resizable || false;
+    parameters.minimizable = parameters.minimizable || false;
+    parameters.maximizable = parameters.maximizable ||  false;
+    parameters.draggable   = parameters.draggable || false;
+    parameters.closable    = parameters.closable || false;
+    
+    var win = new Window(parameters);
+    win.getContent().innerHTML = content;
+    
+    win.showCenter(true, parameters.top, parameters.left);  
+    win.setDestroyOnClose();
+    
+    win.cancelCallback = parameters.onCancel || parameters.cancel; 
+    win.okCallback = parameters.onOk || parameters.ok;
+    
+    return win;    
+  },
+  
+  _getAjaxContent: function(originalRequest)  {
+      Dialog.callFunc(originalRequest.responseText, Dialog.parameters)
+  },
+  
+  _runAjaxRequest: function(message, parameters, callFunc) {
+    if (message.options == null)
+      message.options = {}  
+    Dialog.onCompleteFunc = message.options.onComplete;
+    Dialog.parameters = parameters;
+    Dialog.callFunc = callFunc;
+    
+    message.options.onComplete = Dialog._getAjaxContent;
+    new Ajax.Request(message.url, message.options);
+  },
+  
+  okCallback: function() {
+    var win = Windows.focusedWindow;
+    if (!win.okCallback || win.okCallback(win)) {
+      // Remove onclick on button
+      $$("#" + win.getId()+" input").each(function(element) {element.onclick=null;})
+      win.close();
+    }
+  },
+
+  cancelCallback: function() {
+    var win = Windows.focusedWindow;
+    // Remove onclick on button
+    $$("#" + win.getId()+" input").each(function(element) {element.onclick=null})
+    win.close();
+    if (win.cancelCallback)
+      win.cancelCallback(win);
+  }
+}
+/*
+  Based on Lightbox JS: Fullsize Image Overlays 
+  by Lokesh Dhakar - http://www.huddletogether.com
+
+  For more information on this script, visit:
+  http://huddletogether.com/projects/lightbox/
+
+  Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
+  (basically, do anything you want, just leave my name and link)
+*/
+
+if (Prototype.Browser.WebKit) {
+  var array = navigator.userAgent.match(new RegExp(/AppleWebKit\/([\d\.\+]*)/));
+  Prototype.Browser.WebKitVersion = parseFloat(array[1]);
+}
+
+var WindowUtilities = {  
+  // From dragdrop.js
+  getWindowScroll: function(parent) {
+    var T, L, W, H;
+    parent = parent || document.body;              
+    if (parent != document.body) {
+      T = parent.scrollTop;
+      L = parent.scrollLeft;
+      W = parent.scrollWidth;
+      H = parent.scrollHeight;
+    } 
+    else {
+      var w = window;
+      with (w.document) {
+        if (w.document.documentElement && documentElement.scrollTop) {
+          T = documentElement.scrollTop;
+          L = documentElement.scrollLeft;
+        } else if (w.document.body) {
+          T = body.scrollTop;
+          L = body.scrollLeft;
+        }
+        if (w.innerWidth) {
+          W = w.innerWidth;
+          H = w.innerHeight;
+        } else if (w.document.documentElement && documentElement.clientWidth) {
+          W = documentElement.clientWidth;
+          H = documentElement.clientHeight;
+        } else {
+          W = body.offsetWidth;
+          H = body.offsetHeight
+        }
+      }
+    }
+    return { top: T, left: L, width: W, height: H };
+  }, 
+  //
+  // getPageSize()
+  // Returns array with page width, height and window width, height
+  // Core code from - quirksmode.org
+  // Edit for Firefox by pHaez
+  //
+  getPageSize: function(parent){
+    parent = parent || document.body;              
+    var windowWidth, windowHeight;
+    var pageHeight, pageWidth;
+    if (parent != document.body) {
+      windowWidth = parent.getWidth();
+      windowHeight = parent.getHeight();                                
+      pageWidth = parent.scrollWidth;
+      pageHeight = parent.scrollHeight;                                
+    } 
+    else {
+      var xScroll, yScroll;
+
+      if (window.innerHeight && window.scrollMaxY) {  
+        xScroll = document.body.scrollWidth;
+        yScroll = window.innerHeight + window.scrollMaxY;
+      } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
+        xScroll = document.body.scrollWidth;
+        yScroll = document.body.scrollHeight;
+      } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
+        xScroll = document.body.offsetWidth;
+        yScroll = document.body.offsetHeight;
+      }
+
+
+      if (self.innerHeight) {  // all except Explorer
+        windowWidth = self.innerWidth;
+        windowHeight = self.innerHeight;
+      } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
+        windowWidth = document.documentElement.clientWidth;
+        windowHeight = document.documentElement.clientHeight;
+      } else if (document.body) { // other Explorers
+        windowWidth = document.body.clientWidth;
+        windowHeight = document.body.clientHeight;
+      }  
+
+      // for small pages with total height less then height of the viewport
+      if(yScroll < windowHeight){
+        pageHeight = windowHeight;
+      } else { 
+        pageHeight = yScroll;
+      }
+
+      // for small pages with total width less then width of the viewport
+      if(xScroll < windowWidth){  
+        pageWidth = windowWidth;
+      } else {
+        pageWidth = xScroll;
+      }
+    }             
+    return {pageWidth: pageWidth ,pageHeight: pageHeight , windowWidth: windowWidth, windowHeight: windowHeight};
+  },
+
+  disableScreen: function(className, overlayId, overlayOpacity, contentId, parent) {
+    WindowUtilities.initLightbox(overlayId, className, function() {this._disableScreen(className, overlayId, overlayOpacity, contentId)}.bind(this), parent || document.body);
+  },
+
+  _disableScreen: function(className, overlayId, overlayOpacity, contentId) {
+    // prep objects
+    var objOverlay = $(overlayId);
+
+    var pageSize = WindowUtilities.getPageSize(objOverlay.parentNode);
+
+    // Hide select boxes as they will 'peek' through the image in IE, store old value
+    if (contentId && Prototype.Browser.IE) {
+      WindowUtilities._hideSelect();
+      WindowUtilities._showSelect(contentId);
+    }  
+  
+    // set height of Overlay to take up whole page and show
+    objOverlay.style.height = (pageSize.pageHeight + 'px');
+    objOverlay.style.display = 'none'; 
+    if (overlayId == "overlay_modal" && Window.hasEffectLib && Windows.overlayShowEffectOptions) {
+      objOverlay.overlayOpacity = overlayOpacity;
+      new Effect.Appear(objOverlay, Object.extend({from: 0, to: overlayOpacity}, Windows.overlayShowEffectOptions));
+    }
+    else
+      objOverlay.style.display = "block";
+  },
+  
+  enableScreen: function(id) {
+    id = id || 'overlay_modal';
+    var objOverlay =  $(id);
+    if (objOverlay) {
+      // hide lightbox and overlay
+      if (id == "overlay_modal" && Window.hasEffectLib && Windows.overlayHideEffectOptions)
+        new Effect.Fade(objOverlay, Object.extend({from: objOverlay.overlayOpacity, to:0}, Windows.overlayHideEffectOptions));
+      else {
+        objOverlay.style.display = 'none';
+        objOverlay.parentNode.removeChild(objOverlay);
+      }
+      
+      // make select boxes visible using old value
+      if (id != "__invisible__") 
+        WindowUtilities._showSelect();
+    }
+  },
+
+  _hideSelect: function(id) {
+    if (Prototype.Browser.IE) {
+      id = id ==  null ? "" : "#" + id + " ";
+      $$(id + 'select').each(function(element) {
+        if (! WindowUtilities.isDefined(element.oldVisibility)) {
+          element.oldVisibility = element.style.visibility ? element.style.visibility : "visible";
+          element.style.visibility = "hidden";
+        }
+      });
+    }
+  },
+  
+  _showSelect: function(id) {
+    if (Prototype.Browser.IE) {
+      id = id ==  null ? "" : "#" + id + " ";
+      $$(id + 'select').each(function(element) {
+        if (WindowUtilities.isDefined(element.oldVisibility)) {
+          // Why?? Ask IE
+          try {
+            element.style.visibility = element.oldVisibility;
+          } catch(e) {
+            element.style.visibility = "visible";
+          }
+          element.oldVisibility = null;
+        }
+        else {
+          if (element.style.visibility)
+            element.style.visibility = "visible";
+        }
+      });
+    }
+  },
+
+  isDefined: function(object) {
+    return typeof(object) != "undefined" && object != null;
+  },
+  
+  // initLightbox()
+  // Function runs on window load, going through link tags looking for rel="lightbox".
+  // These links receive onclick events that enable the lightbox display for their targets.
+  // The function also inserts html markup at the top of the page which will be used as a
+  // container for the overlay pattern and the inline image.
+  initLightbox: function(id, className, doneHandler, parent) {
+    // Already done, just update zIndex
+    if ($(id)) {
+      Element.setStyle(id, {zIndex: Windows.maxZIndex + 1});
+      Windows.maxZIndex++;
+      doneHandler();
+    }
+    // create overlay div and hardcode some functional styles (aesthetic styles are in CSS file)
+    else {
+      var objOverlay = document.createElement("div");
+      objOverlay.setAttribute('id', id);
+      objOverlay.className = "overlay_" + className
+      objOverlay.style.display = 'none';
+      objOverlay.style.position = 'absolute';
+      objOverlay.style.top = '0';
+      objOverlay.style.left = '0';
+      objOverlay.style.zIndex = Windows.maxZIndex + 1;
+      Windows.maxZIndex++;
+      objOverlay.style.width = '100%';
+      parent.insertBefore(objOverlay, parent.firstChild);
+      if (Prototype.Browser.WebKit && id == "overlay_modal") {
+        setTimeout(function() {doneHandler()}, 10);
+      }
+      else
+        doneHandler();
+    }    
+  },
+  
+  setCookie: function(value, parameters) {
+    document.cookie= parameters[0] + "=" + escape(value) +
+      ((parameters[1]) ? "; expires=" + parameters[1].toGMTString() : "") +
+      ((parameters[2]) ? "; path=" + parameters[2] : "") +
+      ((parameters[3]) ? "; domain=" + parameters[3] : "") +
+      ((parameters[4]) ? "; secure" : "");
+  },
+
+  getCookie: function(name) {
+    var dc = document.cookie;
+    var prefix = name + "=";
+    var begin = dc.indexOf("; " + prefix);
+    if (begin == -1) {
+      begin = dc.indexOf(prefix);
+      if (begin != 0) return null;
+    } else {
+      begin += 2;
+    }
+    var end = document.cookie.indexOf(";", begin);
+    if (end == -1) {
+      end = dc.length;
+    }
+    return unescape(dc.substring(begin + prefix.length, end));
+  },
+    
+  _computeSize: function(content, id, width, height, margin, className) {
+    var objBody = document.body;
+    var tmpObj = document.createElement("div");
+    tmpObj.setAttribute('id', id);
+    tmpObj.className = className + "_content";
+
+    if (height)
+      tmpObj.style.height = height + "px"
+    else
+      tmpObj.style.width = width + "px"
+  
+    tmpObj.style.position = 'absolute';
+    tmpObj.style.top = '0';
+    tmpObj.style.left = '0';
+    tmpObj.style.display = 'none';
+
+    tmpObj.innerHTML = content;
+    objBody.insertBefore(tmpObj, objBody.firstChild);
+
+    var size;
+    if (height)
+      size = $(tmpObj).getDimensions().width + margin;
+    else
+      size = $(tmpObj).getDimensions().height + margin;
+    objBody.removeChild(tmpObj);
+    return size;
+  }  
+}
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window_effects.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window_effects.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window_effects.js	(revision 4071)
@@ -0,0 +1,157 @@
+Effect.ResizeWindow = Class.create();
+Object.extend(Object.extend(Effect.ResizeWindow.prototype, Effect.Base.prototype), {
+  initialize: function(win, top, left, width, height) {
+    this.window = win;
+    this.window.resizing = true;
+    
+    var size = win.getSize();
+    this.initWidth    = parseFloat(size.width);
+    this.initHeight   = parseFloat(size.height);
+
+    var location = win.getLocation();
+    this.initTop    = parseFloat(location.top);
+    this.initLeft   = parseFloat(location.left);
+
+    this.width    = width != null  ? parseFloat(width)  : this.initWidth;
+    this.height   = height != null ? parseFloat(height) : this.initHeight;
+    this.top      = top != null    ? parseFloat(top)    : this.initTop;
+    this.left     = left != null   ? parseFloat(left)   : this.initLeft;
+
+    this.dx     = this.left   - this.initLeft;
+    this.dy     = this.top    - this.initTop;
+    this.dw     = this.width  - this.initWidth;
+    this.dh     = this.height - this.initHeight;
+    
+    this.r2      = $(this.window.getId() + "_row2");
+    this.content = $(this.window.getId() + "_content");
+        
+    this.contentOverflow = this.content.getStyle("overflow") || "auto";
+    this.content.setStyle({overflow: "hidden"});
+    
+    // Wired mode
+    if (this.window.options.wiredDrag) {
+      this.window.currentDrag = win._createWiredElement();
+      this.window.currentDrag.show();
+      this.window.element.hide();
+    }
+
+    this.start(arguments[5]);
+  },
+  
+  update: function(position) {
+    var width  = Math.floor(this.initWidth  + this.dw * position);
+    var height = Math.floor(this.initHeight + this.dh * position);
+    var top    = Math.floor(this.initTop    + this.dy * position);
+    var left   = Math.floor(this.initLeft   + this.dx * position);
+
+    if (window.ie) {
+      if (Math.floor(height) == 0)  
+        this.r2.hide();
+      else if (Math.floor(height) >1)  
+        this.r2.show();
+    }      
+    this.r2.setStyle({height: height});
+    this.window.setSize(width, height);
+    this.window.setLocation(top, left);
+  },
+  
+  finish: function(position) {
+    // Wired mode
+    if (this.window.options.wiredDrag) {
+      this.window._hideWiredElement();
+      this.window.element.show();
+    }
+
+    this.window.setSize(this.width, this.height);
+    this.window.setLocation(this.top, this.left);
+    this.r2.setStyle({height: null});
+    
+    this.content.setStyle({overflow: this.contentOverflow});
+      
+    this.window.resizing = false;
+  }
+});
+
+Effect.ModalSlideDown = function(element) {
+  var windowScroll = WindowUtilities.getWindowScroll();    
+  var height = element.getStyle("height");  
+  element.setStyle({top: - (parseFloat(height) - windowScroll.top) + "px"});
+  
+  element.show();
+  return new Effect.Move(element, Object.extend({ x: 0, y: parseFloat(height) }, arguments[1] || {}));
+};
+
+
+Effect.ModalSlideUp = function(element) {
+  var height = element.getStyle("height");
+  return new Effect.Move(element, Object.extend({ x: 0, y: -parseFloat(height) }, arguments[1] || {}));
+};
+
+PopupEffect = Class.create();
+PopupEffect.prototype = {    
+  initialize: function(htmlElement) {
+    this.html = $(htmlElement);      
+    this.options = Object.extend({className: "popup_effect", duration: 0.4}, arguments[1] || {});
+    
+  },
+  show: function(element, options) { 
+    var position = Position.cumulativeOffset(this.html);      
+    var size = this.html.getDimensions();
+    var bounds = element.win.getBounds();
+    this.window =  element.win;      
+    // Create a div
+    if (!this.div) {
+      this.div = document.createElement("div");
+      this.div.className = this.options.className;
+      this.div.style.height = size.height + "px";
+      this.div.style.width  = size.width  + "px";
+      this.div.style.top    = position[1] + "px";
+      this.div.style.left   = position[0] + "px";   
+      this.div.style.position = "absolute"
+      document.body.appendChild(this.div);
+    }                                                   
+    if (this.options.fromOpacity)
+      this.div.setStyle({opacity: this.options.fromOpacity})
+    this.div.show();          
+    var style = "top:" + bounds.top + ";left:" +bounds.left + ";width:" + bounds.width +";height:" + bounds.height;
+    if (this.options.toOpacity)
+      style += ";opacity:" + this.options.toOpacity;
+    
+    new Effect.Morph(this.div ,{style: style, duration: this.options.duration, afterFinish: this._showWindow.bind(this)});    
+  },
+
+  hide: function(element, options) {     
+    var position = Position.cumulativeOffset(this.html);      
+    var size = this.html.getDimensions();    
+    this.window.visible = true; 
+    var bounds = this.window.getBounds();
+    this.window.visible = false; 
+
+    this.window.element.hide();
+
+    this.div.style.height = bounds.height;
+    this.div.style.width  = bounds.width;
+    this.div.style.top    = bounds.top;
+    this.div.style.left   = bounds.left;
+    
+    if (this.options.toOpacity)
+      this.div.setStyle({opacity: this.options.toOpacity})
+
+    this.div.show();                                 
+    var style = "top:" + position[1] + "px;left:" + position[0] + "px;width:" + size.width +"px;height:" + size.height + "px";
+
+    if (this.options.fromOpacity)
+      style += ";opacity:" + this.options.fromOpacity;
+    new Effect.Morph(this.div ,{style: style, duration: this.options.duration, afterFinish: this._hideDiv.bind(this)});    
+  },
+  
+  _showWindow: function() {
+    this.div.hide();
+    this.window.element.show(); 
+  },
+  
+  _hideDiv: function() {
+    this.div.hide();
+  }
+}
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window_ext.js
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window_ext.js	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/javascripts/window_ext.js	(revision 4071)
@@ -0,0 +1,115 @@
+// Copyright (c) 2006 Sébastien Gruhier (http://xilinus.com, http://itseb.com)
+// YOU MUST INCLUDE window.js BEFORE
+//
+// Object to store hide/show windows status in a cookie
+// Just add at the end of your HTML file this javascript line: WindowStore.init()
+WindowStore = {
+  doSetCookie: false,
+  cookieName:  "__window_store__",
+  expired:     null,
+  
+  // Init function with two optional parameters
+  // - cookieName (default = __window_store__)
+  // - expiration date (default 3 years from now)
+  init: function(cookieName, expired) {
+    WindowStore.cookieName = cookieName || WindowStore.cookieName
+
+    if (! expired) {
+      var today = new Date();
+      today.setYear(today.getYear()+1903);
+      WindowStore.expired = today;
+    }
+    else
+      WindowStore.expired = expired;
+
+    Windows.windows.each(function(win) {
+      win.setCookie(win.getId(), WindowStore.expired);
+    });
+
+    // Create observer on show/hide events
+    var myObserver = {
+    	onShow: function(eventName, win) {
+    	  WindowStore._saveCookie();
+    	},
+    	
+    	onClose: function(eventName, win) {
+    	  WindowStore._saveCookie();
+  	  },
+  	  
+    	onHide: function(eventName, win) {
+    	  WindowStore._saveCookie();
+    	}
+    }
+    Windows.addObserver(myObserver);
+
+    WindowStore._restoreWindows();
+    WindowStore._saveCookie();
+  },
+  
+  show: function(win) {
+    eval("var cookie = " + WindowUtilities.getCookie(WindowStore.cookieName));
+    if (cookie != null) {
+      if (cookie[win.getId()])
+        win.show();
+    }
+    else
+      win.show();
+  },
+
+  // Function to store windows show/hide status in a cookie 
+  _saveCookie: function() {
+    if (!doSetCookie)
+      return;
+    
+    var cookieValue = "{";
+    Windows.windows.each(function(win) {
+      if (cookieValue != "{")
+        cookieValue += ","
+      cookieValue += win.getId() + ": " + win.isVisible();
+    });
+    cookieValue += "}"
+  
+    WindowUtilities.setCookie(cookieValue, [WindowStore.cookieName, WindowStore.expired]);  
+  },
+
+  // Function to restore windows show/hide status from a cookie if exists
+  _restoreWindows: function() {
+    eval("var cookie = " + WindowUtilities.getCookie(WindowStore.cookieName));
+    if (cookie != null) {
+      doSetCookie = false;
+      Windows.windows.each(function(win) {
+        if (cookie[win.getId()])
+          win.show();
+      });
+    }
+    doSetCookie = true;
+  }
+}
+
+// Object to set a close key an all windows
+WindowCloseKey = {
+  keyCode: Event.KEY_ESC,
+  
+  init: function(keyCode) {
+    if (keyCode)
+      WindowCloseKey.keyCode = keyCode;      
+      
+    Event.observe(document, 'keydown', this._closeCurrentWindow.bindAsEventListener(this));   
+  },
+  
+  _closeCurrentWindow: function(event) {
+    var e = event || window.event
+  	var characterCode = e.which || e.keyCode;
+  	
+  	// Check if there is a top window (it means it's an URL content)
+  	var win = top.Windows.focusedWindow;
+    if (characterCode == WindowCloseKey.keyCode && win) {
+      if (win.cancelCallback) 
+        top.Dialog.cancelCallback();      
+      else if (win.okCallback) 
+        top.Dialog.okCallback();
+      else
+        top.Windows.close(top.Windows.focusedWindow.getId());
+    }
+  }
+}
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alert.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alert.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alert.css	(revision 4071)
@@ -0,0 +1,119 @@
+.overlay_alert {
+	background-color: #85BBEF;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.alert_nw {
+	width: 5px;
+	height: 5px;
+	background: transparent url(alert/top_left.gif) no-repeat bottom left;			
+}
+
+.alert_n {
+	height: 5px;
+	background: transparent url(alert/top.gif) repeat-x bottom left;			
+}
+
+.alert_ne {
+	width: 5px;
+	height: 5px;
+	background: transparent url(alert/top_right.gif) no-repeat bottom left			
+}
+
+.alert_e {
+	width: 5px;
+	background: transparent url(alert/right.gif) repeat-y 0 0;			
+}
+
+.alert_w {
+	width: 5px;
+	background: transparent url(alert/left.gif) repeat-y 0 0;			
+}
+
+.alert_sw {
+	width: 5px;
+	height: 5px;
+	background: transparent url(alert/bottom_left.gif) no-repeat 0 0;			
+}
+
+.alert_s {
+	height: 5px;
+	background: transparent url(alert/bottom.gif) repeat-x 0 0;			
+}
+
+.alert_se, .alert_sizer {
+	width: 5px;
+	height: 5px;
+	background: transparent url(alert/bottom_right.gif) no-repeat 0 0;			
+}
+
+.alert_close {
+	width:0px;
+	height:0px;
+	display:none;
+}
+
+.alert_minimize {
+	width:0px;
+	height:0px;
+	display:none;
+}
+
+.alert_maximize {
+	width:0px;
+	height:0px;
+	display:none;
+}
+
+.alert_title {
+	float:left;
+	height:1px;
+	width:100%;
+}
+
+.alert_content {
+	overflow:visible;
+	color: #000;
+	font-family: Tahoma, Arial, sans-serif;
+  font: 12px arial;
+	background: #FFF;
+}
+
+/* For alert/confirm dialog */
+.alert_window {
+	background: #FFF;
+	padding:20px;
+	margin-left:auto;
+	margin-right:auto;
+	width:400px;
+}
+
+.alert_message {
+  font: 12px arial;
+	width:100%;
+	color:#F00;
+	padding-bottom:10px;
+}
+
+.alert_buttons {
+	text-align:center;
+	width:100%;
+}
+
+.alert_buttons input {
+	width:20%;
+	margin:10px;
+}
+
+.alert_progress {
+	float:left;
+	margin:auto;
+	text-align:center;
+	width:100%;
+	height:16px;
+	background: #FFF url('alert/progress.gif') no-repeat center center
+}
+
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alert_lite.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alert_lite.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alert_lite.css	(revision 4071)
@@ -0,0 +1,88 @@
+.overlay_alert_lite {
+	background-color: #85BBEF;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.alert_lite_sizer {
+	width:0px;
+	height:0px;
+	display:none;
+}
+
+.alert_lite_close {
+	width:0px;
+	height:0px;
+	display:none;
+}
+
+.alert_lite_minimize {
+	width:0px;
+	height:0px;
+	display:none;
+}
+
+.alert_lite_maximize {
+	width:0px;
+	height:0px;
+	display:none;
+}
+
+.alert_lite_title {
+	width:0px;
+	height:0px;
+	display:none;
+}
+
+.alert_lite_content {
+	overflow:auto;
+	color: #000;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size: 10px;
+	background: #FFF;
+}
+
+
+/* For alert/confirm dialog */
+.alert_lite_window {
+	border:1px solid #F00;	
+	background: #FFF;
+	padding:20px;
+	margin-left:auto;
+	margin-right:auto;
+	width:400px;
+}
+
+.alert_lite_message {
+	font-size:16px;
+	text-align:center;
+	width:100%;
+	color:#F00;
+	padding-bottom:10px;
+}
+
+.alert_lite_buttons {
+	text-align:center;
+	width:100%;
+}
+
+.alert_lite_buttons input {
+	width:20%;
+	margin:10px;
+}
+
+.alert_lite_progress {
+	float:left;
+	margin:auto;
+	text-align:center;
+	width:100%;
+	height:16px;
+	background: #FFF url('alert/progress.gif') no-repeat center center
+}
+
+table.alert_lite_header  {
+  border:1px solid #F00;
+  background:#FFF
+}
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alphacube.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alphacube.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/alphacube.css	(revision 4071)
@@ -0,0 +1,150 @@
+.overlay_alphacube {
+	background-color: #85BBEF;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.alphacube_nw {
+	background: transparent url(alphacube/left-top.gif) no-repeat 0 0;			
+  width:10px;
+  height:25px;
+}
+
+.alphacube_n {
+  background: transparent url(alphacube/top-middle.gif) repeat-x 0 0;			
+  height:25px;
+}
+
+.alphacube_ne {
+  background: transparent url(alphacube/right-top.gif) no-repeat 0 0;			
+  width:10px;	  
+  height:25px;
+}
+
+.alphacube_w {
+  background: transparent url(alphacube/frame-left.gif) repeat-y top left;			
+  width:7px;
+}
+
+.alphacube_e {
+  background: transparent url(alphacube/frame-right.gif) repeat-y top right;			
+  width:7px;	  
+}
+
+.alphacube_sw {
+  background: transparent url(alphacube/bottom-left-c.gif) no-repeat 0 0;			
+  width:7px;
+  height:7px;
+}
+
+.alphacube_s {
+  background: transparent url(alphacube/bottom-middle.gif) repeat-x 0 0;			
+  height:7px;
+}
+
+.alphacube_se, .alphacube_sizer  {
+  background: transparent url(alphacube/bottom-right-c.gif) no-repeat 0 0;			
+  width:7px;
+  height:7px;
+}
+
+.alphacube_sizer {
+	cursor:se-resize;	
+}
+
+.alphacube_close {
+	width: 23px;
+	height: 23px;
+	background: transparent url(alphacube/button-close-focus.gif) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:11px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.alphacube_minimize {
+	width: 23px;
+	height: 23px;
+	background: transparent url(alphacube/button-min-focus.gif) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:55px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.alphacube_maximize {
+	width: 23px;
+	height: 23px;
+	background: transparent url(alphacube/button-max-focus.gif) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:33px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.alphacube_title {
+	float:left;
+	height:14px;
+	font-size:14px;
+	text-align:center;
+	margin-top:2px;
+	width:100%;
+	color:#123456;
+}
+
+.alphacube_content {
+	overflow:auto;
+	color: #000;
+	font-family: Tahoma, Arial, sans-serif;
+  font: 12px arial;
+	background:#FDFDFD;
+}
+
+/* For alert/confirm dialog */
+.alphacube_window {
+	border:1px solid #F00;	
+	background: #FFF;
+	padding:20px;
+	margin-left:auto;
+	margin-right:auto;
+	width:400px;
+}
+
+.alphacube_message {
+  font: 12px arial;
+	text-align:center;
+	width:100%;
+	padding-bottom:10px;
+}
+
+.alphacube_buttons {
+	text-align:center;
+	width:100%;
+}
+
+.alphacube_buttons input {
+	width:20%;
+	margin:10px;
+}
+
+.alphacube_progress {
+	float:left;
+	margin:auto;
+	text-align:center;
+	width:100%;
+	height:16px;
+	background: #FFF url('alert/progress.gif') no-repeat center center
+}
+
+.alphacube_wired_frame {
+	background: #FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;	
+}
+
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/behavior.htc
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/behavior.htc	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/behavior.htc	(revision 4071)
@@ -0,0 +1,51 @@
+<public:component>
+<public:attach event="onpropertychange" onevent="propertyChanged()" />
+<script>
+
+var supported = /MSIE (5\.5)|[6789]/.test(navigator.userAgent) && navigator.platform == "Win32";
+var realSrc;
+var blankSrc = "blank.gif";
+
+if (supported) fixImage();
+
+function propertyChanged() {
+   if (!supported) return;
+
+   var pName = event.propertyName;
+   if (pName != "src") return;
+   // if not set to blank
+   if ( ! new RegExp(blankSrc).test(src))
+      fixImage();
+};
+
+function fixImage() {
+   // get src
+   var src = element.src;
+
+   // check for real change
+   if (src == realSrc) {
+      element.src = blankSrc;
+      return;
+   }
+
+   if ( ! new RegExp(blankSrc).test(src)) {
+      // backup old src
+      realSrc = src;
+   }
+
+   // test for png
+   if ( /\.png$/.test( realSrc.toLowerCase() ) ) {
+      // set blank image
+      element.src = blankSrc;
+      // set filter
+      element.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
+                                     src + "',sizingMethod='scale')";
+   }
+   else {
+      // remove filter
+      element.runtimeStyle.filter = "";
+   }
+}
+
+</script>
+</public:component>
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/darkX.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/darkX.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/darkX.css	(revision 4071)
@@ -0,0 +1,121 @@
+.overlay_darkX {
+	background-color: #85BBEF;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.darkX_nw {
+	background: transparent url(darkX/titlebar-left-focused.png) no-repeat 0 0;			
+  width:6px;
+  height:21px;
+}
+.darkX_n {
+  background: transparent url(darkX/titlebar-mid-focused.png) repeat-x 0 0;			
+  height:21px;
+}
+.darkX_ne {
+  background: transparent url(darkX/titlebar-right-focused.png) no-repeat 0 0;			
+  width:6px;	  
+  height:21px;
+}
+.darkX_w {
+  background: transparent url(darkX/frame-left-focused.png) repeat-y top left;			
+  width:3px;
+}
+
+.darkX_e {
+  background: transparent url(darkX/frame-right-focused.png) repeat-y top right;			
+  width:3px;	  
+}
+
+.darkX_sw {
+  background: transparent url(darkX/frame-bottom-left-focused.png) no-repeat 0 0;			
+  width:5px;
+  height:3px;
+}
+.darkX_s {
+  background: transparent url(darkX/frame-bottom-mid-focused.png) repeat-x 0 0;			
+  height:3px;
+}
+.darkX_se, .darkX_sizer {
+  background: transparent url(darkX/frame-bottom-right-focused.png) no-repeat 0 0;			
+  width:5px;
+  height:3px;
+}
+
+.darkX_sizer {
+	cursor:se-resize;	
+}
+
+.darkX_close {
+	width: 21px;
+	height: 21px;
+	background: transparent url(darkX/button-close-focused.png) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:5px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.darkX_minimize {
+	width: 21px;
+	height: 21px;
+	background: transparent url(darkX/button-minimize-focused.png) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:26px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.darkX_maximize {
+	width: 21px;
+	height: 21px;
+	background: transparent url(darkX/button-maximize-focused.png) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:47px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+
+.darkX_title {
+	float:left;
+	height:14px;
+	font-size:12px;
+	text-align:center;
+	margin-top:2px;
+	width:100%;
+	color:#FFF;
+}
+
+.darkX_content {
+	overflow:auto;
+	color: #E6DF2A;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size: 14px;
+	background:#5E5148;
+}
+
+
+/* FOR IE */
+* html .darkX_minimize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/darkX/button-minimize-focused.png", sizingMethod="crop");
+}
+
+* html .darkX_maximize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/darkX/button-maximize-focused.png", sizingMethod="scale");
+}
+
+* html .darkX_close {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/darkX/button-close-focused.png", sizingMethod="crop");
+}
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/debug.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/debug.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/debug.css	(revision 4071)
@@ -0,0 +1,25 @@
+div.inspector div.inspectable {
+ padding: 0.25em 0 0.25em 1em;
+ background-color: Gray;
+ color: white;
+ border: outset 2px white;
+ cursor: pointer;
+}
+ 
+div.inspector div.child {
+ margin: 0 0 0 1em;
+}
+ 
+#debug_window_content { /*  DIV container for debug sizing*/
+ width:250px;
+ height:100px;
+ background-color:#000;
+}
+ 
+#debug {         /*  DIV container for debug contents*/
+ padding:3px;
+ color:#0f0;
+ font-family:monaco, Tahoma, Verdana, Arial, Helvetica, sans-serif;
+ font-size:10px;
+}
+ 
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/default.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/default.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/default.css	(revision 4071)
@@ -0,0 +1,155 @@
+.overlay_dialog {
+	background-color: #666666;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.overlay___invisible__ {
+  background-color: #666666;
+	filter:alpha(opacity=0);
+	-moz-opacity: 0;
+	opacity: 0;
+}
+
+.dialog_nw {
+	width: 9px;
+	height: 23px;
+	background: transparent url(default/top_left.gif) no-repeat 0 0;		
+}
+
+.dialog_n {
+	background: transparent url(default/top_mid.gif) repeat-x 0 0;	
+	height: 23px;
+}
+
+.dialog_ne {
+	width: 9px;
+	height: 23px;
+	background: transparent url(default/top_right.gif) no-repeat 0 0;		
+}
+
+.dialog_e {
+	width: 2px;
+	background: transparent url(default/center_right.gif) repeat-y 0 0;	
+}
+
+.dialog_w {
+	width: 2px;
+	background: transparent url(default/center_left.gif) repeat-y 0 0;		
+}
+
+.dialog_sw {
+	width: 9px;
+	height: 19px;
+	background: transparent url(default/bottom_left.gif) no-repeat 0 0;			
+}
+
+.dialog_s {
+	background: transparent url(default/bottom_mid.gif) repeat-x 0 0;		
+	height: 19px;
+}
+
+.dialog_se {
+	width: 9px;
+	height: 19px;
+	background: transparent url(default/bottom_right.gif) no-repeat 0 0;			
+}
+
+.dialog_sizer {
+	width: 9px;
+	height: 19px;
+	background: transparent url(default/sizer.gif) no-repeat 0 0;	
+	cursor:se-resize;	
+}
+
+.dialog_close {
+	width: 14px;
+	height: 14px;
+	background: transparent url(default/close.gif) no-repeat 0 0;			
+	position:absolute;
+	top:5px;
+	left:8px;
+	cursor:pointer;
+	z-index:2000;
+}
+
+.dialog_minimize {
+	width: 14px;
+	height: 15px;
+	background: transparent url(default/minimize.gif) no-repeat 0 0;			
+	position:absolute;
+	top:5px;
+	left:28px;
+	cursor:pointer;
+	z-index:2000;
+}
+
+.dialog_maximize {
+	width: 14px;
+	height: 15px;
+	background: transparent url(default/maximize.gif) no-repeat 0 0;			
+	position:absolute;
+	top:5px;
+	left:49px;
+	cursor:pointer;
+	z-index:2000;
+}
+
+.dialog_title {
+	float:left;
+	height:14px;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size:12px;
+	text-align:center;
+	width:100%;
+	color:#000;
+}
+
+.dialog_content {
+	overflow:auto;
+	color: #DDD;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size: 10px;
+	background-color:#123;
+}
+
+.top_draggable, .bottom_draggable {
+  cursor:move;
+}
+
+.status_bar {
+  font-size:12px;
+}
+.status_bar input{
+  font-size:12px;
+}
+
+.wired_frame {
+	display: block;
+  position: absolute;
+  border: 1px #000 dashed;
+}
+
+/* DO NOT CHANGE THESE VALUES*/
+.dialog {
+	display: block;
+	position: absolute;
+}
+
+.dialog table.table_window  { 
+  border-collapse: collapse; 
+  border-spacing: 0; 
+  width: 100%;
+	margin: 0px;
+	padding:0px;
+}
+
+.dialog table.table_window td , .dialog table.table_window th { 
+  padding: 0; 
+}
+
+.dialog .title_window {
+  -moz-user-select:none;
+}                                                    
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/iefix/iepngfix.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/iefix/iepngfix.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/iefix/iepngfix.css	(revision 4071)
@@ -0,0 +1,3 @@
+/* PNG fix for all themes that uses PNG images on IE */
+td, div { behavior: url(../themes/iefix/iepngfix.htc) }
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/iefix/iepngfix.htc
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/iefix/iepngfix.htc	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/iefix/iepngfix.htc	(revision 4071)
@@ -0,0 +1,54 @@
+<public:component>
+<public:attach event="onpropertychange" onevent="doFix()" />
+
+<script type="text/javascript">
+
+// IE5.5+ PNG Alpha Fix v1.0RC4
+// (c) 2004-2005 Angus Turnbull http://www.twinhelix.com
+
+// This is licensed under the CC-GNU LGPL, version 2.1 or later.
+// For details, see: http://creativecommons.org/licenses/LGPL/2.1/
+
+// Modified/Simplified on 04/23/2007 by Sebastien Gruhier (http://www.xilinus.com) 
+// To work only on background and to handle repeat bg  
+                     
+// This must be a path to a blank image. That's all the configuration you need.
+if (typeof blankImg == 'undefined') var blankImg = 'blank.gif';
+
+var f = 'DXImageTransform.Microsoft.AlphaImageLoader';
+
+function filt(s, m)
+{
+ if (filters[f])
+ {
+  filters[f].enabled = s ? true : false;
+  if (s) with (filters[f]) { src = s; sizingMethod = m }
+ }
+ else if (s) style.filter = 'progid:'+f+'(src="'+s+'",sizingMethod="'+m+'")';
+}
+
+function doFix()
+{         
+ // Assume IE7 is OK.
+ if (!/MSIE (5\.5|6\.)/.test(navigator.userAgent) ||
+  (event && !/(background|src)/.test(event.propertyName))) return;
+
+ var bgImg = currentStyle.backgroundImage || style.backgroundImage;  
+ var bgRepeat = currentStyle.backgroundRepeat || style.backgroundRepeat;        
+ if (bgImg && bgImg != 'none')
+ {
+  if (bgImg.match(/^url[("']+(.*\.png)[)"']+$/i))
+  {
+   var s = RegExp.$1;
+   if (currentStyle.width == 'auto' && currentStyle.height == 'auto')
+    style.width = offsetWidth + 'px';
+   style.backgroundImage = 'none';
+   filt(s, bgRepeat == "no-repeat" ? 'crop' : 'scale');
+  }
+ }
+}
+
+doFix();
+
+</script>
+</public:component>
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/lighting.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/lighting.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/lighting.css	(revision 4071)
@@ -0,0 +1,960 @@
+.overlay___invisible__ {
+  background-color: #666;
+	filter:alpha(opacity=0);
+	-moz-opacity: 0;
+	opacity: 0;
+}
+
+.top_draggable, .bottom_draggable {
+	cursor:move;
+}
+
+.status_bar {
+	font-size:12px;
+}
+.status_bar input{
+	font-size:12px;
+}
+
+.wired_frame {
+	display:block;
+	position:absolute;
+	border:1px #000 dashed;
+}
+
+
+
+.overlay_bluelighting {
+	background-color:#FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity:0.6;
+	opacity:0.6;
+}
+
+.bluelighting_wired_frame {
+	background:#FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity:0.6;
+	opacity:0.6;	
+}
+
+.bluelighting_nw {
+	background:transparent url(lighting/top-left-blue.png) no-repeat 0 0;			
+	width:9px;
+	height:28px;
+}
+
+.bluelighting_n {
+	background:transparent url(lighting/top-middle-blue.png) repeat-x 0 0;			
+	height:28px;
+}
+
+.bluelighting_ne {
+	background:transparent url(lighting/top-right-blue.png) no-repeat 0 0;			
+	width:15px;	  
+	height:28px;
+}
+
+.bluelighting_w {
+	background:transparent url(lighting/left-blue.png) repeat-y top left;			
+	width:9px;
+}
+
+.bluelighting_e {
+	background:transparent url(lighting/right-blue.png) repeat-y top right;			
+	width:15px;	  
+}
+
+.bluelighting_sw {
+	background:transparent url(lighting/bottom-left-blue.png) no-repeat 0 0;			
+	width:9px;
+	height:15px;
+}
+
+.bluelighting_s {
+	background:transparent url(lighting/bottom-middle-blue.png) repeat-x 0 0;			
+	height:15px;
+}
+
+.bluelighting_se, .bluelighting_sizer  {
+	background:transparent url(lighting/bottom-right-blue.png) no-repeat 0 0;			
+	width:15px;
+	height:15px;
+}
+
+.bluelighting_sizer {
+	cursor:se-resize;	
+}
+
+.bluelighting_close {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-close-blue.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:10px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.bluelighting_maximize {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-maximize-blue.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:25px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.bluelighting_minimize {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-minimize-blue.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:40px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.bluelighting_title {
+	float:left;
+	height:14px;
+	font-size:14px;
+	font-weight:bold;
+	font-family:Verdana, Arial, sans-serif;
+	text-align:center;
+	margin-top:2px;
+	width:100%;
+	color:#17385B;
+}
+
+.bluelighting_content {
+	overflow:auto;
+	color:#000;
+	font-family:Verdana, Arial, sans-serif;
+	font-size:12px;
+	background:#BFDBFF;
+}
+
+/* For alert/confirm dialog */
+.bluelighting_window {
+	border:1px solid #F00;	
+	background:#FFF;
+	padding:20px;
+	margin-left:auto;
+	margin-right:auto;
+	width:400px;
+}
+
+.bluelighting_message {
+  	font-size:12px;
+	text-align:center;
+	width:100%;
+	padding-bottom:10px;
+}
+
+.bluelighting_buttons {
+	text-align:center;
+	width:100%;
+}
+
+.bluelighting_buttons input {
+	border:1px solid #999;
+	border-top-color:#CCC;
+	border-left-color:#CCC;
+	padding:2px;
+	background-color:#FFF;
+	color:#333;
+	background-image:url(lighting/background_buttons.gif);
+	background-repeat:repeat-x;
+	font-family:Verdana, Arial, sans-serif;
+	font-size:10px;
+	font-weight:bold;
+	text-align:center;
+}
+
+.bluelighting_progress {
+	float:left;
+	margin:auto;
+	text-align:center;
+	width:100%;
+	height:16px;
+	background:transparent url('lighting/spinner.gif') no-repeat center center
+}
+
+/* FOR IE */
+* html .bluelighting_nw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-left-blue.png", sizingMethod="crop");
+}
+
+* html .bluelighting_n {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-middle-blue.png", sizingMethod="scale");
+}
+
+* html .bluelighting_ne {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-right-blue.png", sizingMethod="crop");
+}
+
+* html .bluelighting_w {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/left-blue.png", sizingMethod="scale");
+}
+
+* html .bluelighting_e {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/right-blue.png", sizingMethod="scale");
+}
+
+* html .bluelighting_sw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-left-blue.png", sizingMethod="crop");
+}
+
+* html .bluelighting_s {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-middle-blue.png", sizingMethod="scale");
+}
+
+* html .bluelighting_se, * html .bluelighting_sizer {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-right-blue.png", sizingMethod="crop");
+}
+
+* html .bluelighting_close {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-close-blue.png", sizingMethod="crop");
+}
+
+* html .bluelighting_minimize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-minimize-blue.png", sizingMethod="crop");
+}
+
+* html .bluelighting_maximize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-maximize-blue.png", sizingMethod="crop");
+}
+
+* html .bluelighting_content {
+	background:#B8D7FF;
+}
+
+
+
+.overlay_greylighting {
+	background-color:#FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity:0.6;
+	opacity:0.6;
+}
+
+.greylighting_wired_frame {
+	background:#FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity:0.6;
+	opacity:0.6;	
+}
+
+.greylighting_nw {
+	background:transparent url(lighting/top-left-grey.png) no-repeat 0 0;			
+	width:9px;
+	height:28px;
+}
+
+.greylighting_n {
+	background:transparent url(lighting/top-middle-grey.png) repeat-x 0 0;			
+	height:28px;
+}
+
+.greylighting_ne {
+	background:transparent url(lighting/top-right-grey.png) no-repeat 0 0;			
+	width:15px;	  
+	height:28px;
+}
+
+.greylighting_w {
+	background:transparent url(lighting/left-grey.png) repeat-y top left;			
+	width:9px;
+}
+
+.greylighting_e {
+	background:transparent url(lighting/right-grey.png) repeat-y top right;			
+	width:15px;	  
+}
+
+.greylighting_sw {
+	background:transparent url(lighting/bottom-left-grey.png) no-repeat 0 0;			
+	width:9px;
+	height:15px;
+}
+
+.greylighting_s {
+	background:transparent url(lighting/bottom-middle-grey.png) repeat-x 0 0;			
+	height:15px;
+}
+
+.greylighting_se, .greylighting_sizer  {
+	background:transparent url(lighting/bottom-right-grey.png) no-repeat 0 0;			
+	width:15px;
+	height:15px;
+}
+
+.greylighting_sizer {
+	cursor:se-resize;	
+}
+
+.greylighting_close {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-close-grey.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:10px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.greylighting_maximize {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-maximize-grey.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:25px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.greylighting_minimize {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-minimize-grey.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:40px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.greylighting_title {
+	float:left;
+	height:14px;
+	font-size:14px;
+	font-weight:bold;
+	font-family:Verdana, Arial, sans-serif;
+	text-align:center;
+	margin-top:2px;
+	width:100%;
+	color:#525252;
+}
+
+.greylighting_content {
+	overflow:auto;
+	color:#000;
+	font-family:Verdana, Arial, sans-serif;
+	font-size:12px;
+	background:#CDCDCD;
+}
+
+/* For alert/confirm dialog */
+.greylighting_window {
+	border:1px solid #F00;	
+	background:#FFF;
+	padding:20px;
+	margin-left:auto;
+	margin-right:auto;
+	width:400px;
+}
+
+.greylighting_message {
+  	font-size:12px;
+	text-align:center;
+	width:100%;
+	padding-bottom:10px;
+}
+
+.greylighting_buttons {
+	text-align:center;
+	width:100%;
+}
+
+.greylighting_buttons input {
+	border:1px solid #999;
+	border-top-color:#CCC;
+	border-left-color:#CCC;
+	padding:2px;
+	background-color:#FFF;
+	color:#333;
+	background-image:url(lighting/background_buttons.gif);
+	background-repeat:repeat-x;
+	font-family:Verdana, Arial, sans-serif;
+	font-size:10px;
+	font-weight:bold;
+	text-align:center;
+}
+
+.greylighting_progress {
+	float:left;
+	margin:auto;
+	text-align:center;
+	width:100%;
+	height:16px;
+	background:transparent url('lighting/spinner.gif') no-repeat center center
+}
+
+/* FOR IE */
+* html .greylighting_nw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-left-grey.png", sizingMethod="crop");
+}
+
+* html .greylighting_n {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-middle-grey.png", sizingMethod="scale");
+}
+
+* html .greylighting_ne {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-right-grey.png", sizingMethod="crop");
+}
+
+* html .greylighting_w {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/left-grey.png", sizingMethod="scale");
+}
+
+* html .greylighting_e {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/right-grey.png", sizingMethod="scale");
+}
+
+* html .greylighting_sw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-left-grey.png", sizingMethod="crop");
+}
+
+* html .greylighting_s {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-middle-grey.png", sizingMethod="scale");
+}
+
+* html greylighting_se, * html .greylighting_sizer {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-right-grey.png", sizingMethod="crop");
+}
+
+* html .greylighting_close {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-close-grey.png", sizingMethod="crop");
+}
+
+* html .greylighting_minimize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-minimize-grey.png", sizingMethod="crop");
+}
+
+* html .greylighting_maximize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-maximize-grey.png", sizingMethod="crop");
+}
+
+* html .greylighting_content {
+	background:#C7C7C7;
+}
+
+
+
+.overlay_greenlighting {
+	background-color:#FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity:0.6;
+	opacity:0.6;
+}
+
+.greenlighting_wired_frame {
+	background:#FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity:0.6;
+	opacity:0.6;	
+}
+
+.greenlighting_nw {
+	background:transparent url(lighting/top-left-green.png) no-repeat 0 0;			
+	width:9px;
+	height:28px;
+}
+
+.greenlighting_n {
+	background:transparent url(lighting/top-middle-green.png) repeat-x 0 0;			
+	height:28px;
+}
+
+.greenlighting_ne {
+	background:transparent url(lighting/top-right-green.png) no-repeat 0 0;			
+	width:15px;	  
+	height:28px;
+}
+
+.greenlighting_w {
+	background:transparent url(lighting/left-green.png) repeat-y top left;			
+	width:9px;
+}
+
+.greenlighting_e {
+	background:transparent url(lighting/right-green.png) repeat-y top right;			
+	width:15px;	  
+}
+
+.greenlighting_sw {
+	background:transparent url(lighting/bottom-left-green.png) no-repeat 0 0;			
+	width:9px;
+	height:15px;
+}
+
+.greenlighting_s {
+	background:transparent url(lighting/bottom-middle-green.png) repeat-x 0 0;			
+	height:15px;
+}
+
+.greenlighting_se, .greenlighting_sizer  {
+	background:transparent url(lighting/bottom-right-green.png) no-repeat 0 0;			
+	width:15px;
+	height:15px;
+}
+
+.greenlighting_sizer {
+	cursor:se-resize;	
+}
+
+.greenlighting_close {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-close-green.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:10px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.greenlighting_maximize {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-maximize-green.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:25px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.greenlighting_minimize {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-minimize-green.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:40px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.greenlighting_title {
+	float:left;
+	height:14px;
+	font-size:14px;
+	font-weight:bold;
+	font-family:Verdana, Arial, sans-serif;
+	text-align:center;
+	margin-top:2px;
+	width:100%;
+	color:#2A6002;
+}
+
+.greenlighting_content {
+	overflow:auto;
+	color:#000;
+	font-family:Verdana, Arial, sans-serif;
+	font-size:12px;
+	background:#ACFCAF;
+}
+
+/* For alert/confirm dialog */
+.greenlighting_window {
+	border:1px solid #F00;	
+	background:#FFF;
+	padding:20px;
+	margin-left:auto;
+	margin-right:auto;
+	width:400px;
+}
+
+.greenlighting_message {
+  	font-size:12px;
+	text-align:center;
+	width:100%;
+	padding-bottom:10px;
+}
+
+.greenlighting_buttons {
+	text-align:center;
+	width:100%;
+}
+
+.greenlighting_buttons input {
+	border:1px solid #999;
+	border-top-color:#CCC;
+	border-left-color:#CCC;
+	padding:2px;
+	background-color:#FFF;
+	color:#333;
+	background-image:url(lighting/background_buttons.gif);
+	background-repeat:repeat-x;
+	font-family:Verdana, Arial, sans-serif;
+	font-size:10px;
+	font-weight:bold;
+	text-align:center;
+}
+
+.greenlighting_progress {
+	float:left;
+	margin:auto;
+	text-align:center;
+	width:100%;
+	height:16px;
+	background:transparent url('lighting/spinner.gif') no-repeat center center
+}
+
+/* FOR IE */
+* html .greenlighting_nw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-left-green.png", sizingMethod="crop");
+}
+
+* html .greenlighting_n {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-middle-green.png", sizingMethod="scale");
+}
+
+* html .greenlighting_ne {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-right-green.png", sizingMethod="crop");
+}
+
+* html .greenlighting_w {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/left-green.png", sizingMethod="scale");
+}
+
+* html .greenlighting_e {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/right-green.png", sizingMethod="scale");
+}
+
+* html .greenlighting_sw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-left-green.png", sizingMethod="crop");
+}
+
+* html .greenlighting_s {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-middle-green.png", sizingMethod="scale");
+}
+
+* html greenlighting_se, * html .greenlighting_sizer {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-right-green.png", sizingMethod="crop");
+}
+
+* html .greenlighting_close {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-close-green.png", sizingMethod="crop");
+}
+
+* html .greenlighting_minimize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-minimize-green.png", sizingMethod="crop");
+}
+
+* html .greenlighting_maximize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-maximize-green.png", sizingMethod="crop");
+}
+
+* html .greenlighting_content {
+	background:#A4FCA7;
+}
+
+
+
+.overlay_darkbluelighting {
+	background-color:#FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity:0.6;
+	opacity:0.6;
+}
+
+.darkbluelighting_wired_frame {
+	background:#FFF;
+	filter:alpha(opacity=60);
+	-moz-opacity:0.6;
+	opacity:0.6;	
+}
+
+.darkbluelighting_nw {
+	background:transparent url(lighting/top-left-darkblue.png) no-repeat 0 0;			
+	width:9px;
+	height:28px;
+}
+
+.darkbluelighting_n {
+	background:transparent url(lighting/top-middle-darkblue.png) repeat-x 0 0;			
+	height:28px;
+}
+
+.darkbluelighting_ne {
+	background:transparent url(lighting/top-right-darkblue.png) no-repeat 0 0;			
+	width:15px;	  
+	height:28px;
+}
+
+.darkbluelighting_w {
+	background:transparent url(lighting/left-darkblue.png) repeat-y top left;			
+	width:9px;
+}
+
+.darkbluelighting_e {
+	background:transparent url(lighting/right-darkblue.png) repeat-y top right;			
+	width:15px;	  
+}
+
+.darkbluelighting_sw {
+	background:transparent url(lighting/bottom-left-darkblue.png) no-repeat 0 0;			
+	width:9px;
+	height:15px;
+}
+
+.darkbluelighting_s {
+	background:transparent url(lighting/bottom-middle-darkblue.png) repeat-x 0 0;			
+	height:15px;
+}
+
+.darkbluelighting_se, .darkbluelighting_sizer  {
+	background:transparent url(lighting/bottom-right-darkblue.png) no-repeat 0 0;			
+	width:15px;
+	height:15px;
+}
+
+.darkbluelighting_sizer {
+	cursor:se-resize;	
+}
+
+.darkbluelighting_close {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-close-darkblue.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:10px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.darkbluelighting_maximize {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-maximize-darkblue.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:25px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.darkbluelighting_minimize {
+	width:15px;
+	height:9px;
+	background:transparent url(lighting/button-minimize-darkblue.png) no-repeat 0 0;			
+	position:absolute;
+	top:11px;
+	right:40px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.darkbluelighting_title {
+	float:left;
+	height:14px;
+	font-size:14px;
+	font-weight:bold;
+	font-family:Verdana, Arial, sans-serif;
+	text-align:center;
+	margin-top:2px;
+	width:100%;
+	color:#E4EFFD;
+}
+
+.darkbluelighting_content {
+	overflow:auto;
+	color:#FFF;
+	font-family:Verdana, Arial, sans-serif;
+	font-size:12px;
+	background:#0413C0;
+}
+
+/* For alert/confirm dialog */
+.darkbluelighting_window {
+	border:1px solid #F00;	
+	background:#FFF;
+	padding:20px;
+	margin-left:auto;
+	margin-right:auto;
+	width:400px;
+}
+
+.darkbluelighting_message {
+  	font-size:12px;
+	text-align:center;
+	width:100%;
+	padding-bottom:10px;
+}
+
+.darkbluelighting_buttons {
+	text-align:center;
+	width:100%;
+}
+
+.darkbluelighting_buttons input {
+	border:1px solid #999;
+	border-top-color:#CCC;
+	border-left-color:#CCC;
+	padding:2px;
+	background-color:#FFF;
+	color:#333;
+	background-image:url(lighting/background_buttons.gif);
+	background-repeat:repeat-x;
+	font-family:Verdana, Arial, sans-serif;
+	font-size:10px;
+	font-weight:bold;
+	text-align:center;
+}
+
+.darkbluelighting_progress {
+	float:left;
+	margin:auto;
+	text-align:center;
+	width:100%;
+	height:16px;
+	background:transparent url('lighting/spinner.gif') no-repeat center center
+}
+
+/* FOR IE */
+* html .darkbluelighting_nw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-left-darkblue.png", sizingMethod="crop");
+}
+
+* html .darkbluelighting_n {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-middle-darkblue.png", sizingMethod="scale");
+}
+
+* html .darkbluelighting_ne {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/top-right-darkblue.png", sizingMethod="crop");
+}
+
+* html .darkbluelighting_w {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/left-darkblue.png", sizingMethod="scale");
+}
+
+* html .darkbluelighting_e {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/right-darkblue.png", sizingMethod="scale");
+}
+
+* html .darkbluelighting_sw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-left-darkblue.png", sizingMethod="crop");
+}
+
+* html .darkbluelighting_s {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-middle-darkblue.png", sizingMethod="scale");
+}
+
+* html darkbluelighting_se, * html .darkbluelighting_sizer {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/bottom-right-darkblue.png", sizingMethod="crop");
+}
+
+* html .darkbluelighting_close {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-close-darkblue.png", sizingMethod="crop");
+}
+
+* html .darkbluelighting_minimize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-minimize-darkblue.png", sizingMethod="crop");
+}
+
+* html .darkbluelighting_maximize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/lighting/button-maximize-darkblue.png", sizingMethod="crop");
+}
+
+* html .darkbluelighting_content {
+	background:#020EBA;
+}
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/lighting/pngbehavior.htc
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/lighting/pngbehavior.htc	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/lighting/pngbehavior.htc	(revision 4071)
@@ -0,0 +1,67 @@
+<public:component>
+<public:attach event="onpropertychange" onevent="doFix()" />
+
+<script type="text/javascript">
+
+// IE5.5+ PNG Alpha Fix v1.0RC4
+// (c) 2004-2005 Angus Turnbull http://www.twinhelix.com
+
+// This is licensed under the CC-GNU LGPL, version 2.1 or later.
+// For details, see: http://creativecommons.org/licenses/LGPL/2.1/
+
+
+// This must be a path to a blank image. That's all the configuration you need.
+if (typeof blankImg == 'undefined') var blankImg = 'blank.gif';
+
+
+var f = 'DXImageTransform.Microsoft.AlphaImageLoader';
+
+function filt(s, m)
+{
+ if (filters[f])
+ {
+  filters[f].enabled = s ? true : false;
+  if (s) with (filters[f]) { src = s; sizingMethod = m }
+ }
+ else if (s) style.filter = 'progid:'+f+'(src="'+s+'",sizingMethod="'+m+'")';
+}
+
+function doFix()
+{           
+  alert('ok')
+  
+ // Assume IE7 is OK.
+ if (!/MSIE (5\.5|6\.)/.test(navigator.userAgent) ||
+  (event && !/(background|src)/.test(event.propertyName))) return;
+
+ var bgImg = currentStyle.backgroundImage || style.backgroundImage;
+
+ if (tagName == 'IMG')
+ {
+  if ((/\.png$/i).test(src))
+  {
+   if (currentStyle.width == 'auto' && currentStyle.height == 'auto')
+    style.width = offsetWidth + 'px';
+   filt(src, 'scale');
+   src = blankImg;
+  }
+  else if (src.indexOf(blankImg) < 0) filt();
+ }
+ else if (bgImg && bgImg != 'none')
+ {
+  if (bgImg.match(/^url[("']+(.*\.png)[)"']+$/i))
+  {
+   var s = RegExp.$1;
+   if (currentStyle.width == 'auto' && currentStyle.height == 'auto')
+    style.width = offsetWidth + 'px';
+   style.backgroundImage = 'none';
+   filt(s, 'crop');
+   // IE link fix.
+   for (var n = 0; n < childNodes.length; n++)
+    if (childNodes[n].style) childNodes[n].style.position = 'relative';
+  }
+  else filt();
+ }
+}
+
+doFix();
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/mac_os_x.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/mac_os_x.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/mac_os_x.css	(revision 4071)
@@ -0,0 +1,333 @@
+/* Focused windows */
+.overlay_mac_os_x {
+	background-color: #85BBEF;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.mac_os_x_nw {
+	background: transparent url(mac_os_x/TL_Main.png) no-repeat 0 0;			
+  width:24px;
+  height:30px;
+}
+
+.mac_os_x_n {
+  background: transparent url(mac_os_x/T_Main.png) repeat-x 0 0;			
+  height:30px;
+}
+
+.mac_os_x_ne {
+  background: transparent url(mac_os_x/TR_Main.png) no-repeat 0 0;			
+  width:31px;	  
+  height:30px;
+}
+
+.mac_os_x_w {
+  background: transparent url(mac_os_x/L_Main.png) repeat-y top left;			
+  width:16px;
+}
+
+.mac_os_x_e {
+  background: transparent url(mac_os_x/R_Main.png) repeat-y top right;			
+  width:16px;	  
+}
+
+.mac_os_x_sw {
+  background: transparent url(mac_os_x/BL_Main.png) no-repeat 0 0;			
+  width:31px;
+  height:40px;
+}
+
+.mac_os_x_s {
+  background: transparent url(mac_os_x/B_Main.png) repeat-x 0 0;			
+  height:40px;
+}
+
+.mac_os_x_se, .mac_os_x_sizer {
+  background: transparent url(mac_os_x/BR_Main.png) no-repeat 0 0;			
+  width:31px;
+  height:40px;
+}
+
+.mac_os_x_sizer {
+	cursor:se-resize;	
+}
+
+.mac_os_x_close {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x/close.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:25px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.mac_os_x_minimize {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x/minimize.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:45px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.mac_os_x_maximize {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x/maximize.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:65px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.mac_os_x_title {
+	float:left;
+	height:14px;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size:12px;
+	text-align:center;
+	margin-top:8px;
+	width:100%;
+	color:#000;
+}
+
+.mac_os_x_content {
+	overflow:auto;
+	color: #222;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size: 10px;
+	background:#FFF;
+}
+.mac_os_x_s .status_bar {
+  padding-bottom:24px;
+}
+
+/* FOR IE */
+* html .mac_os_x_nw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/TL_Main.png", sizingMethod="crop");
+}
+
+* html .mac_os_x_n {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/T_Main.png", sizingMethod="scale");
+}
+
+* html .mac_os_x_ne {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/TR_Main.png", sizingMethod="crop");
+}
+
+* html .mac_os_x_w {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/L_Main.png", sizingMethod="scale");
+}
+
+* html .mac_os_x_e {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/R_Main.png", sizingMethod="scale");
+}
+
+* html .mac_os_x_sw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/BL_Main.png", sizingMethod="crop");
+}
+
+* html .mac_os_x_s {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/B_Main.png", sizingMethod="scale");
+}
+
+* html .mac_os_x_se {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/BR_Main.png", sizingMethod="crop");
+}
+
+* html .mac_os_x_sizer {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/BR_Main.png", sizingMethod="crop");
+}
+
+
+/* Focused windows */
+.overlay_blur_os_x {
+	background-color: #85BBEF;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.blur_os_x_nw {
+	background: transparent url(mac_os_x/TL.png) no-repeat 0 0;			
+  width:24px;
+  height:30px;
+}
+
+.blur_os_x_n {
+  background: transparent url(mac_os_x/T.png) repeat-x 0 0;			
+  height:30px;
+}
+
+.blur_os_x_ne {
+  background: transparent url(mac_os_x/TR.png) no-repeat 0 0;			
+  width:31px;	  
+  height:30px;
+}
+
+.blur_os_x_w {
+  background: transparent url(mac_os_x/L.png) repeat-y top left;			
+  width:16px;
+}
+
+.blur_os_x_e {
+  background: transparent url(mac_os_x/R.png) repeat-y top right;			
+  width:16px;	  
+}
+
+.blur_os_x_sw {
+  background: transparent url(mac_os_x/BL.png) no-repeat 0 0;			
+  width:31px;
+  height:40px;
+}
+
+.blur_os_x_s {
+  background: transparent url(mac_os_x/B.png) repeat-x 0 0;			
+  height:40px;
+}
+
+.blur_os_x_se, .blur_os_x_sizer {
+  background: transparent url(mac_os_x/BR.png) no-repeat 0 0;			
+  width:31px;
+  height:40px;
+}
+
+.blur_os_x_sizer {
+	cursor:se-resize;	
+}
+
+.blur_os_x_close {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x/close.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:25px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.blur_os_x_minimize {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x/minimize.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:45px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.blur_os_x_maximize {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x/maximize.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:65px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.blur_os_x_title {
+	float:left;
+	height:14px;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size:12px;
+	text-align:center;
+	margin-top:8px;
+	width:100%;
+	color:#000;
+}
+
+.blur_os_x_content {
+	overflow:auto;
+	color: #222;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size: 10px;
+	background:#FFF;
+}
+.blur_os_x_s .status_bar {
+  padding-bottom:24px;
+}
+
+/* FOR IE */
+* html .blur_os_x_nw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/TL.png", sizingMethod="crop");
+}
+
+* html .blur_os_x_n {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/T.png", sizingMethod="scale");
+}
+
+* html .blur_os_x_ne {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/TR.png", sizingMethod="crop");
+}
+
+* html .blur_os_x_w {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/L.png", sizingMethod="scale");
+}
+
+* html .blur_os_x_e {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/R.png", sizingMethod="scale");
+}
+
+* html .blur_os_x_sw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/BL.png", sizingMethod="crop");
+}
+
+* html .blur_os_x_s {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/B.png", sizingMethod="scale");
+}
+
+* html .blur_os_x_se {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/BR.png", sizingMethod="crop");
+}
+
+* html .blur_os_x_sizer {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x/BR.png", sizingMethod="crop");
+}
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/mac_os_x_dialog.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/mac_os_x_dialog.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/mac_os_x_dialog.css	(revision 4071)
@@ -0,0 +1,160 @@
+.overlay_mac_os_x_dialog {
+	background-color: #FF7224;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.mac_os_x_dialog_nw {
+  background: transparent url(mac_os_x_dialog/L.png) repeat-y top left;			
+  width:16px;
+  height:16px;
+}
+
+.mac_os_x_dialog_n {
+	background: transparent url(mac_os_x_dialog/bg.gif) repeat 0 0;			
+  height:18px;
+}
+
+.mac_os_x_dialog_ne {
+  background: transparent url(mac_os_x_dialog/R.png) repeat-y top left;
+  width:16px;
+  height:16px;
+}
+
+.mac_os_x_dialog_w {
+  background: transparent url(mac_os_x_dialog/L.png) repeat-y top left;			
+  width:16px;
+}
+
+.mac_os_x_dialog_e {
+  background: transparent url(mac_os_x_dialog/R.png) repeat-y top right;			
+  width:16px;	  
+}
+
+.mac_os_x_dialog_sw {
+  background: transparent url(mac_os_x_dialog/BL.png) no-repeat 0 0;			
+  width:31px;
+  height:40px;
+}
+
+.mac_os_x_dialog_s {
+  background: transparent url(mac_os_x_dialog/B.png) repeat-x 0 0;			
+  height:40px;
+}
+
+.mac_os_x_dialog_se, .mac_os_x_dialog_sizer {
+  background: transparent url(mac_os_x_dialog/BR.png) no-repeat 0 0;			
+  width:31px;
+  height:40px;
+}
+
+.mac_os_x_dialog_sizer {
+	cursor:se-resize;	
+}
+
+.mac_os_x_dialog_close {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x_dialog/close.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:25px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.mac_os_x_dialog_minimize {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x_dialog/minimize.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:45px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.mac_os_x_dialog_maximize {
+	width: 19px;
+	height: 19px;
+	background: transparent url(mac_os_x_dialog/maximize.gif) no-repeat 0 0;			
+	position:absolute;
+	top:12px;
+	left:65px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.mac_os_x_dialog_title {
+	float:left;
+	height:14px;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size:12px;
+	text-align:center;
+	margin-top:6px;
+	width:100%;
+	color:#000;
+}
+
+.mac_os_x_dialog_content {
+	overflow:auto;
+	color: #222;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size: 10px;
+	background: transparent url(mac_os_x_dialog/bg.gif) repeat 0 0;			
+}
+
+.mac_os_x_dialog_buttons {
+  text-align: center;
+}
+/* FOR IE */
+* html .mac_os_x_dialog_nw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x_dialog/L.png", sizingMethod="scale");
+}
+
+
+* html .mac_os_x_dialog_ne {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x_dialog/R.png", sizingMethod="scale");
+}
+
+* html .mac_os_x_dialog_w {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x_dialog/L.png", sizingMethod="scale");
+}
+
+* html .mac_os_x_dialog_e {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x_dialog/R.png", sizingMethod="scale");
+}
+
+* html .mac_os_x_dialog_sw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x_dialog/BL.png", sizingMethod="crop");
+}
+
+* html .mac_os_x_dialog_s {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x_dialog/B.png", sizingMethod="scale");
+}
+
+* html .mac_os_x_dialog_se {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x_dialog/BR.png", sizingMethod="crop");
+}
+
+* html .mac_os_x_dialog_sizer {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/mac_os_x_dialog/BR.png", sizingMethod="crop");
+}
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/nuncio.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/nuncio.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/nuncio.css	(revision 4071)
@@ -0,0 +1,164 @@
+.overlay_nuncio img { border: none; }
+
+.overlay_nuncio {
+	background-color: #666666;
+}
+
+.nuncio_nw {
+	width: 12px;
+	height: 28px;
+	background: url(nuncio/top_left.png) no-repeat;		
+}
+
+.nuncio_n {
+	background: url(nuncio/top_mid.png) repeat-x;
+	height: 28px;
+}
+
+.nuncio_ne {
+	width: 21px;
+	height: 28px;
+	background: url(nuncio/top_right.png) no-repeat;		
+}
+
+.nuncio_e {
+	width: 21px;
+	background: url(nuncio/center_right.png)  repeat-y top right;		
+}
+
+.nuncio_w {
+	width: 12px;
+	background: url(nuncio/center_left.png) repeat-y top left;			
+}
+
+.nuncio_sw {
+	width: 12px;
+	height: 18px;
+	background: url(nuncio/bottom_left.png) no-repeat;			
+}
+
+.nuncio_s {
+	background: url(nuncio/bottom_mid.png) repeat-x 0 0;				
+	height: 18px;
+}
+
+.nuncio_se, .nuncio_sizer {
+	width: 21px;
+	height: 18px;
+	background: url(nuncio/bottom_right.png) no-repeat;			
+}
+
+.nuncio_close {
+	width: 14px;
+	height: 14px;
+	background: url(nuncio/close.png) no-repeat;			
+	position:absolute;
+	top:10px;
+	right:22px;
+	cursor:pointer;
+	z-index:2000;
+}
+
+.nuncio_minimize {
+	width: 14px;
+	height: 15px;
+	background: url(nuncio/minimize.png) no-repeat;			
+	position:absolute;
+	top:10px;
+	right:40px;
+	cursor:pointer;
+	z-index:2000;
+}
+
+.nuncio_title {
+	float:left;
+	font-size:11px;
+	font-weight: bold;
+	font-style: italic;
+	color: #fff;
+	width: 100%
+}
+
+.nuncio_content {
+	background: url(nuncio/overlay.png) repeat;
+	overflow:auto;
+	color: #ddd;
+	font-family: Tahoma, Arial, "sans-serif";
+	font-size: 10px;
+}
+
+.nuncio_sizer {
+	cursor:se-resize;	
+}
+
+
+.top_draggable, .bottom_draggable {
+  cursor:move
+}
+/* FOR IE */
+* html .nuncio_nw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/top_left.png", sizingMethod="crop");
+}
+
+* html .nuncio_n {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/top_mid.png", sizingMethod="scale");
+}
+
+* html .nuncio_ne {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/top_right.png", sizingMethod="crop");
+}
+
+* html .nuncio_w {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/center_left.png", sizingMethod="scale");
+}
+
+* html .nuncio_e {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/center_right.png", sizingMethod="scale");
+}
+
+* html .nuncio_sw {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/bottom_left.png", sizingMethod="crop");
+}
+
+* html .nuncio_s {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/bottom_mid.png", sizingMethod="scale");
+}
+
+* html .nuncio_se {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/bottom_right.png", sizingMethod="crop");
+}
+
+* html .nuncio_sizer {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/bottom_right.png", sizingMethod="crop");
+}
+
+* html .nuncio_close {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/close.png", sizingMethod="crop");
+}
+
+* html .nuncio_minimize {
+	background-color: transparent;
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="../themes/nuncio/minimize.png", sizingMethod="crop");
+}
+
Index: /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/spread.css
===================================================================
--- /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/spread.css	(revision 4071)
+++ /extensions/rv_gmaps/tags/2.0.c/template/windows_13/themes/spread.css	(revision 4071)
@@ -0,0 +1,108 @@
+.overlay_spread {
+	background-color: #85BBEF;
+	filter:alpha(opacity=60);
+	-moz-opacity: 0.6;
+	opacity: 0.6;
+}
+
+.spread_nw {
+	background: transparent url(spread/left-top.gif) no-repeat 0 0;			
+  width:10px;
+  height:25px;
+}
+
+.spread_n {
+  background: transparent url(spread/top-middle.gif) repeat-x 0 0;			
+  height:25px;
+}
+
+.spread_ne {
+  background: transparent url(spread/right-top.gif) no-repeat 0 0;			
+  width:10px;	  
+  height:25px;
+}
+
+.spread_w {
+  background: transparent url(spread/frame-left.gif) repeat-y top left;			
+  width:7px;
+}
+
+.spread_e {
+  background: transparent url(spread/frame-right.gif) repeat-y top right;			
+  width:7px;	  
+}
+
+.spread_sw {
+  background: transparent url(spread/bottom-left-c.gif) no-repeat 0 0;			
+  width:7px;
+  height:7px;
+}
+
+.spread_s  {
+  background: transparent url(spread/bottom-middle.gif) repeat-x 0 0;			
+  height:7px;
+}
+
+.spread_se, .spread_sizer {
+  background: transparent url(spread/bottom-right-c.gif) no-repeat 0 0;			
+  width:7px;
+  height:7px;
+}
+
+.spread_sizer {
+	cursor:se-resize;	
+}
+
+.spread_close {
+	width: 23px;
+	height: 23px;
+	background: transparent url(spread/button-close-focus.gif) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:11px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.spread_minimize {
+	width: 23px;
+	height: 23px;
+	background: transparent url(spread/button-min-focus.gif) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:55px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.spread_maximize {
+	width: 23px;
+	height: 23px;
+	background: transparent url(spread/button-max-focus.gif) no-repeat 0 0;			
+	position:absolute;
+	top:0px;
+	right:33px;
+	cursor:pointer;
+	z-index:1000;
+}
+
+.spread_title {
+	float:left;
+	height:14px;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size:14px;
+	font-weight:bold;
+	text-align:left;
+	margin-top:2px;
+	width:100%;
+	color:#E47211;
+}
+
+.spread_content {
+	overflow:auto;
+	color: #222;
+	font-family: Tahoma, Arial, sans-serif;
+	font-size: 10px;
+	background:#A9EA00;
+}
+
