merge
authorKatia Saurfelt <katia.saurfelt@logilab.fr>
Thu, 18 Dec 2008 11:04:10 +0100
changeset 224 408ce834899a
parent 223 1e75cac226df (current diff)
parent 222 64b8207a9ae6 (diff)
child 225 b90904dc3069
merge
--- a/.hgtags	Thu Dec 18 11:03:50 2008 +0100
+++ b/.hgtags	Thu Dec 18 11:04:10 2008 +0100
@@ -3,3 +3,5 @@
 aa7d98f8f07f94afe750769640161f948a8a0d07 cubicweb-debian-version-2_99_3-1
 76c1707200cfc5222672d1a5337b5e4b0b27bb0f cubicweb-version-2_99_4
 eaf1089ae49d19cd8ae81732ff20e1003af0a238 cubicweb-debian-version-2_99_4-1
+68b7a92468857ef741b91f3422ef14d78e3f5491 cubicweb-version-2_99_5
+91793edd3b2fb11320b5421895296331da952524 cubicweb-debian-version-2_99_5-1
--- a/__pkginfo__.py	Thu Dec 18 11:03:50 2008 +0100
+++ b/__pkginfo__.py	Thu Dec 18 11:04:10 2008 +0100
@@ -6,7 +6,7 @@
 distname = "cubicweb"
 modname = "cubicweb"
 
-numversion = (2, 99, 4)
+numversion = (2, 99, 5)
 version = '.'.join(str(num) for num in numversion)
 
 license = 'LCL'
--- a/cwvreg.py	Thu Dec 18 11:03:50 2008 +0100
+++ b/cwvreg.py	Thu Dec 18 11:04:10 2008 +0100
@@ -175,7 +175,7 @@
         """
         return [x for x in sorted(self.possible_objects(registry, *args, **kwargs),
                                   key=lambda x: x.propval('order'))
-                if x.propval('visible')]    
+                if x.propval('visible')]
         
     def possible_actions(self, req, rset, **kwargs):
         if rset is None:
--- a/debian/changelog	Thu Dec 18 11:03:50 2008 +0100
+++ b/debian/changelog	Thu Dec 18 11:04:10 2008 +0100
@@ -1,3 +1,9 @@
+cubicweb (2.99.5-1) DISTRIBUTION; urgency=low
+
+  * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr>  Wed, 17 Dec 2008 14:14:26 +0100
+
 cubicweb (2.99.4-1) DISTRIBUTION; urgency=low
 
   * new upstream release
--- a/doc/book/en/B142-install.en.txt	Thu Dec 18 11:03:50 2008 +0100
+++ b/doc/book/en/B142-install.en.txt	Thu Dec 18 11:04:10 2008 +0100
@@ -14,7 +14,8 @@
 - `LAX` is available as an extension of `CubicWeb` under the GPLv2
   license which can be downloaded from : http://cubicweb.org/
 
-Please follow instructions on how to install `CubicWeb` framework. 
+Please follow instructions on how to install `CubicWeb` framework
+(:ref:`CubicWebInstallation`). 
 
 Once ``cubicweb-ctl`` is installed, then you can create a Google
 App Engine extension of our framework by running the command ::
--- a/doc/book/en/C010-setup.en.txt	Thu Dec 18 11:03:50 2008 +0100
+++ b/doc/book/en/C010-setup.en.txt	Thu Dec 18 11:04:10 2008 +0100
@@ -5,7 +5,6 @@
 ===================================================
 Installation and set-up of a `CubicWeb` environment
 ===================================================
-.. contents::
 
 .. include:: C011-installation.en.txt
 .. include:: C012-create-instance.en.txt
--- a/doc/book/en/C011-installation.en.txt	Thu Dec 18 11:03:50 2008 +0100
+++ b/doc/book/en/C011-installation.en.txt	Thu Dec 18 11:04:10 2008 +0100
@@ -1,5 +1,7 @@
 .. -*- coding: utf-8 -*-
 
+.. _CubicWebInstallation:
+
 Installation
 ============
 
--- a/i18n/en.po	Thu Dec 18 11:03:50 2008 +0100
+++ b/i18n/en.po	Thu Dec 18 11:04:10 2008 +0100
@@ -59,6 +59,10 @@
 msgstr ""
 
 #, python-format
+msgid "%s"
+msgstr ""
+
+#, python-format
 msgid "%s days"
 msgstr ""
 
@@ -1157,6 +1161,12 @@
 msgid "components_rqlinput_description"
 msgstr "the rql box in the page's header"
 
+msgid "components_rss_feed_url"
+msgstr ""
+
+msgid "components_rss_feed_url_description"
+msgstr ""
+
 msgid "composite"
 msgstr ""
 
@@ -2161,9 +2171,6 @@
 msgid "ordernum"
 msgstr "order"
 
-msgid "owned by"
-msgstr ""
-
 msgid "owned_by"
 msgstr "owned by"
 
--- a/i18n/es.po	Thu Dec 18 11:03:50 2008 +0100
+++ b/i18n/es.po	Thu Dec 18 11:04:10 2008 +0100
@@ -23,8 +23,8 @@
 "url: %(url)s\n"
 msgstr ""
 "\n"
-"%(user)s a cambiado su estado de <%(previous_state)s> hacia <%(current_state)s> "
-"por la entidad\n"
+"%(user)s a cambiado su estado de <%(previous_state)s> hacia <%(current_state)"
+"s> por la entidad\n"
 "'%(title)s'\n"
 "\n"
 "%(comment)s\n"
@@ -64,6 +64,10 @@
 msgstr "%d años"
 
 #, python-format
+msgid "%s"
+msgstr ""
+
+#, python-format
 msgid "%s days"
 msgstr "%d días"
 
@@ -557,12 +561,11 @@
 
 msgid "You can use any of the following substitutions in your text"
 msgstr ""
-"Puede realizar cualquiera de las siguietes sustituciones "
-"en el contenido de su email."
+"Puede realizar cualquiera de las siguietes sustituciones en el contenido de "
+"su email."
 
 msgid "You have no access to this view or it's not applyable to current data"
-msgstr ""
-"No tiene acceso a esta vista o No es aplicable a los datos actuales"
+msgstr "No tiene acceso a esta vista o No es aplicable a los datos actuales"
 
 msgid ""
 "You're not authorized to access this page. If you think you should, please "
@@ -583,17 +586,16 @@
 "be available. This query may use X and U variables that will respectivly "
 "represents the current entity and the current user"
 msgstr ""
-"una expresión RQL deviendo regresado resultado para que la transición "
-"pueda ser realizada. Esta expresión puede utilizar las variables X y U "
-"que representan respectivamente la entidad en transición y el usuario" "actual. "
-
+"una expresión RQL deviendo regresado resultado para que la transición pueda "
+"ser realizada. Esta expresión puede utilizar las variables X y U que "
+"representan respectivamente la entidad en transición y el usuarioactual. "
 
 msgid ""
 "a card is a textual content used as documentation, reference, procedure "
 "reminder"
 msgstr ""
-"una ficha es un texto utilizado como documentación, referencia, memoria "
-"de procedimiento..."
+"una ficha es un texto utilizado como documentación, referencia, memoria de "
+"procedimiento..."
 
 msgid ""
 "a simple cache entity characterized by a name and a validity date. The "
@@ -883,8 +885,8 @@
 "added relation %(rtype)s from %(frometype)s #%(fromeid)s to %(toetype)s #%"
 "(toeid)s"
 msgstr ""
-"agregado de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %(toetype)"
-"s #%(toeid)s"
+"agregado de la relación %(rtype)s de %(frometype)s #%(fromeid)s hacia %"
+"(toetype)s #%(toeid)s"
 
 msgid "address"
 msgstr "dirección"
@@ -1011,7 +1013,8 @@
 msgstr "filtrar"
 
 msgid "boxes_filter_box_description"
-msgstr "caja permitiendo de realizar filtros sobre los resultados de una búsqueda"
+msgstr ""
+"caja permitiendo de realizar filtros sobre los resultados de una búsqueda"
 
 msgid "boxes_possible_views_box"
 msgstr "caja de vistas posibles"
@@ -1147,7 +1150,8 @@
 
 msgid "components_breadcrumbs_description"
 msgstr ""
-"muestra un camino que permite identificar el lugar donde se encuentra la página en el sitio"
+"muestra un camino que permite identificar el lugar donde se encuentra la "
+"página en el sitio"
 
 msgid "components_etypenavigation"
 msgstr "filtro por tipo"
@@ -1167,7 +1171,7 @@
 msgid "components_loggeduserlink_description"
 msgstr ""
 "muestra un enlace hacia el formulario de conexión para los usuarios "
-"anónimos, o una caja que contiene las ligas propias a el usuario" "conectado. "
+"anónimos, o una caja que contiene las ligas propias a el usuarioconectado. "
 
 msgid "components_logo"
 msgstr "logo"
@@ -1180,8 +1184,8 @@
 
 msgid "components_navigation_description"
 msgstr ""
-"componente aue permite distribuir sobre varias páginas las búsquedas "
-"que arrojan mayores resultados a un número previamente elegido"
+"componente aue permite distribuir sobre varias páginas las búsquedas que "
+"arrojan mayores resultados a un número previamente elegido"
 
 msgid "components_rqlinput"
 msgstr "barra rql"
@@ -1189,6 +1193,12 @@
 msgid "components_rqlinput_description"
 msgstr "la barre de demanda rql, en el encabezado de página"
 
+msgid "components_rss_feed_url"
+msgstr ""
+
+msgid "components_rss_feed_url_description"
+msgstr ""
+
 msgid "composite"
 msgstr "composite"
 
@@ -1232,31 +1242,31 @@
 msgstr "hilo de Ariadna"
 
 msgid "contentnavigation_breadcrumbs_description"
-msgstr ""
-"muestra un camino que permite localizar la página actual en el sitio"
+msgstr "muestra un camino que permite localizar la página actual en el sitio"
 
 msgid "contentnavigation_prevnext"
 msgstr "Elemento anterior / siguiente"
 
 msgid "contentnavigation_prevnext_description"
 msgstr ""
-"muestra las ligas que permiten pasar de una entidad a otra en las"
-"entidades que implementan la interface \"anterior/siguiente\"."
+"muestra las ligas que permiten pasar de una entidad a otra en lasentidades "
+"que implementan la interface \"anterior/siguiente\"."
 
 msgid "contentnavigation_seealso"
 msgstr "vea también"
 
 msgid "contentnavigation_seealso_description"
 msgstr ""
-"sección aue muestra las entidades ligadas por la relación \"vea también\" " ", si la entidad soporta esta relación."
+"sección aue muestra las entidades ligadas por la relación \"vea también\" , "
+"si la entidad soporta esta relación."
 
 msgid "contentnavigation_wfhistory"
 msgstr "histórico del workflow."
 
 msgid "contentnavigation_wfhistory_description"
 msgstr ""
-"sección que ofrece el reporte histórico del workflow para las entidades"
-" que posean un workflow."
+"sección que ofrece el reporte histórico del workflow para las entidades que "
+"posean un workflow."
 
 msgid "context"
 msgstr "contexto"
@@ -1280,21 +1290,22 @@
 "core relation giving to a group the permission to add an entity or relation "
 "type"
 msgstr ""
-"relación sistema que otorga a un grupo la autorización de agregar una"
-"entidad o una relación"
+"relación sistema que otorga a un grupo la autorización de agregar unaentidad "
+"o una relación"
 
 msgid ""
 "core relation giving to a group the permission to delete an entity or "
 "relation type"
 msgstr ""
-"relación sistema que otorga a un grupo la autorización de eliminar una " "entidad o relación"
+"relación sistema que otorga a un grupo la autorización de eliminar una "
+"entidad o relación"
 
 msgid ""
 "core relation giving to a group the permission to read an entity or relation "
 "type"
 msgstr ""
-"relación sistema que otorga a un grupo la autorización de leer una "
-"entidad o una relación "
+"relación sistema que otorga a un grupo la autorización de leer una entidad o "
+"una relación "
 
 msgid "core relation giving to a group the permission to update an entity type"
 msgstr ""
@@ -1309,9 +1320,9 @@
 "core relation indicating owners of an entity. This relation implicitly put "
 "the owner into the owners group for the entity"
 msgstr ""
-"relación sistema que indica el(los) propietario(s) de una entidad. "
-"Esta relación pone de manera implícita al propietario en el grupo "
-"de propietarios por una entidad"
+"relación sistema que indica el(los) propietario(s) de una entidad. Esta "
+"relación pone de manera implícita al propietario en el grupo de propietarios "
+"por una entidad"
 
 msgid "core relation indicating the original creator of an entity"
 msgstr "relación sistema que indica el creador de una entidad."
@@ -1321,7 +1332,9 @@
 
 msgid ""
 "core relation indicating the types (including specialized types) of an entity"
-msgstr "relación sistema indicando los tipos (incluídos los tipos padres) de una entidad"
+msgstr ""
+"relación sistema indicando los tipos (incluídos los tipos padres) de una "
+"entidad"
 
 msgid "cost"
 msgstr "costo"
@@ -1366,7 +1379,8 @@
 msgstr "creación de una dirección electrónica para el usuario %(linkto)s"
 
 msgid "creating RQLExpression (EEType %(linkto)s add_permission RQLExpression)"
-msgstr "creación de una expresión RQL para la autorización de agregar %(linkto)s"
+msgstr ""
+"creación de una expresión RQL para la autorización de agregar %(linkto)s"
 
 msgid ""
 "creating RQLExpression (EEType %(linkto)s delete_permission RQLExpression)"
@@ -1390,8 +1404,8 @@
 msgid ""
 "creating RQLExpression (ERType %(linkto)s delete_permission RQLExpression)"
 msgstr ""
-"creación de una expresión RQL para autorizar la eliminación de "
-"relaciones %(linkto)s"
+"creación de una expresión RQL para autorizar la eliminación de relaciones %"
+"(linkto)s"
 
 msgid ""
 "creating RQLExpression (ERType %(linkto)s read_permission RQLExpression)"
@@ -1475,21 +1489,21 @@
 "define a final relation: link a final relation type from a non final entity "
 "to a final entity type. used to build the application schema"
 msgstr ""
-"define una relación no final: liga un tipo de relación no final desde "
-"una entidad hacia un tipo de entidad no final. Utilizada para construir "
-"el esquema de la aplicación"
+"define una relación no final: liga un tipo de relación no final desde una "
+"entidad hacia un tipo de entidad no final. Utilizada para construir el "
+"esquema de la aplicación"
 
 msgid ""
 "define a non final relation: link a non final relation type from a non final "
 "entity to a non final entity type. used to build the application schema"
 msgstr ""
-"define una relación 'atributo', utilizada para construir el esquema de"
-"la aplicación"
+"define una relación 'atributo', utilizada para construir el esquema dela "
+"aplicación"
 
 msgid "define a relation type, used to build the application schema"
 msgstr ""
-"define un tipo de relación, utilizada para construir el esquema de "
-"la aplicación"
+"define un tipo de relación, utilizada para construir el esquema de la "
+"aplicación"
 
 msgid "define a rql expression used to define permissions"
 msgstr "Expresión RQL utilizada para definir los derechos de acceso"
@@ -1502,15 +1516,15 @@
 
 msgid "define an entity type, used to build the application schema"
 msgstr ""
-"define un tipo de entidad, utilizada para construir el esquema de " 
-"la aplicación"
+"define un tipo de entidad, utilizada para construir el esquema de la "
+"aplicación"
 
 msgid ""
 "defines what's the property is applied for. You must select this first to be "
 "able to set value"
 msgstr ""
-"define a que se aplica la propiedad . Usted debe seleccionar esto "
-"antes de poder fijar un valor"
+"define a que se aplica la propiedad . Usted debe seleccionar esto antes de "
+"poder fijar un valor"
 
 msgid "delete"
 msgstr "eliminar"
@@ -1594,8 +1608,8 @@
 "distinct label to distinguate between other permission entity of the same "
 "name"
 msgstr ""
-"etiqueta que permite distinguir esta autorización de otras que posean "
-"el mismo nombre"
+"etiqueta que permite distinguir esta autorización de otras que posean el "
+"mismo nombre"
 
 msgid "download"
 msgstr "descargar"
@@ -1652,7 +1666,8 @@
 "entity type that may be used to construct some advanced security "
 "configuration"
 msgstr ""
-"tipo de entidqd utilizada para definir una configuración de seguridad " "avanzada"
+"tipo de entidqd utilizada para definir una configuración de seguridad "
+"avanzada"
 
 msgid "entity types which may use this state"
 msgstr "tipo de entidades que pueden utilizar este estado"
@@ -1669,7 +1684,7 @@
 
 msgid "error while publishing ReST text"
 msgstr ""
-"se ha producido un error durante la interpretación del texto en formato" "ReST"
+"se ha producido un error durante la interpretación del texto en formatoReST"
 
 #, python-format
 msgid "error while querying source %s, some data may be missing"
@@ -1791,8 +1806,7 @@
 msgstr "gráfica del workflow por %s"
 
 msgid "group in which a user should be to be allowed to pass this transition"
-msgstr ""
-"grupo en el cual el usuario debe estar para poder pasar la transición"
+msgstr "grupo en el cual el usuario debe estar para poder pasar la transición"
 
 msgid "groups"
 msgstr "grupos"
@@ -1843,21 +1857,21 @@
 "how to format date and time in the ui (\"man strftime\" for format "
 "description)"
 msgstr ""
-"como formatear la fecha en la interface (\"man strftime\" por la "
-"descripción del formato)"
+"como formatear la fecha en la interface (\"man strftime\" por la descripción "
+"del formato)"
 
 msgid "how to format date in the ui (\"man strftime\" for format description)"
 msgstr ""
-"como formatear la fecha en la interface (\"man strftime\" por la "
-"descripción del formato)"
+"como formatear la fecha en la interface (\"man strftime\" por la descripción "
+"del formato)"
 
 msgid "how to format float numbers in the ui"
 msgstr "como formatear los números flotantes en la interface"
 
 msgid "how to format time in the ui (\"man strftime\" for format description)"
 msgstr ""
-"como formatear la hora en la interface (\"man strftime\" por la "
-"descripción del formato)"
+"como formatear la hora en la interface (\"man strftime\" por la descripción "
+"del formato)"
 
 msgid "html class of the component"
 msgstr "clase HTML de este componente"
@@ -1987,9 +2001,8 @@
 "is the subject/object entity of the relation composed of the other ? This "
 "implies that when the composite is deleted, composants are also deleted."
 msgstr ""
-"Es la entidad sujeto/objeto de la relación une agregación de el otro ? "
-"De ser así, el destruir el composite destruirá de igual manera "
-"sus componentes "
+"Es la entidad sujeto/objeto de la relación une agregación de el otro ? De "
+"ser así, el destruir el composite destruirá de igual manera sus componentes "
 
 msgid "is this attribute's value translatable"
 msgstr "es el valor de este atributo traducible ?"
@@ -2001,8 +2014,8 @@
 "is this relation physically inlined? you should know what you're doing if "
 "you are changing this!"
 msgstr ""
-"es esta relación puesta en línea en la base de datos  ? Usted debe saber "
-"lo que hace si cambia esto !"
+"es esta relación puesta en línea en la base de datos  ? Usted debe saber lo "
+"que hace si cambia esto !"
 
 msgid "is_instance_of"
 msgstr "es una instancia de"
@@ -2047,8 +2060,8 @@
 "link a property to the user which want this property customization. Unless "
 "you're a site manager, this relation will be handled automatically."
 msgstr ""
-"liga una propiedad a el usuario que desea esta personalización. A menos "
-"que usted sea un administrador del sistema, esta relación es gestionada "
+"liga una propiedad a el usuario que desea esta personalización. A menos que "
+"usted sea un administrador del sistema, esta relación es gestionada "
 "automáticamente."
 
 msgid "link a relation definition to its object entity type"
@@ -2253,9 +2266,6 @@
 msgid "ordernum"
 msgstr "ordre"
 
-msgid "owned by"
-msgstr "appartient ‡"
-
 msgid "owned_by"
 msgstr "appartient ‡"
 
@@ -2951,6 +2961,9 @@
 #~ msgstr ""
 #~ "nombre maximum d'entitÈs liÈes ‡ afficher dans la vue de restriction"
 
+#~ msgid "owned by"
+#~ msgstr "appartient ‡"
+
 #~ msgid "see also"
 #~ msgstr "voir aussi"
 
--- a/i18n/fr.po	Thu Dec 18 11:03:50 2008 +0100
+++ b/i18n/fr.po	Thu Dec 18 11:04:10 2008 +0100
@@ -64,6 +64,10 @@
 msgstr "%d années"
 
 #, python-format
+msgid "%s"
+msgstr ""
+
+#, python-format
 msgid "%s days"
 msgstr "%d jours"
 
@@ -1190,6 +1194,12 @@
 msgid "components_rqlinput_description"
 msgstr "la barre de requête rql, dans l'en-tête de page"
 
+msgid "components_rss_feed_url"
+msgstr ""
+
+msgid "components_rss_feed_url_description"
+msgstr ""
+
 msgid "composite"
 msgstr "composite"
 
@@ -1324,7 +1334,9 @@
 
 msgid ""
 "core relation indicating the types (including specialized types) of an entity"
-msgstr "relation système indiquant les types (y compris les types parents) d'une entité"
+msgstr ""
+"relation système indiquant les types (y compris les types parents) d'une "
+"entité"
 
 msgid "cost"
 msgstr "coût"
@@ -2255,9 +2267,6 @@
 msgid "ordernum"
 msgstr "ordre"
 
-msgid "owned by"
-msgstr "appartient à"
-
 msgid "owned_by"
 msgstr "appartient à"
 
@@ -2953,6 +2962,9 @@
 #~ msgstr ""
 #~ "nombre maximum d'entités liées à afficher dans la vue de restriction"
 
+#~ msgid "owned by"
+#~ msgstr "appartient à"
+
 #~ msgid "see also"
 #~ msgstr "voir aussi"
 
--- a/web/data/cubicweb.css	Thu Dec 18 11:03:50 2008 +0100
+++ b/web/data/cubicweb.css	Thu Dec 18 11:04:10 2008 +0100
@@ -107,6 +107,7 @@
 
 table td img {
   vertical-align: middle;
+  margin: 0px 10px 0px 0px;
 }
 
 ol {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/ui.core.js	Thu Dec 18 11:04:10 2008 +0100
@@ -0,0 +1,431 @@
+/*
+ * jQuery UI @VERSION
+ *
+ * Copyright (c) 2008 Paul Bakaus (ui.jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI
+ */
+;(function($) {
+
+/** jQuery core modifications and additions **/
+
+var _remove = $.fn.remove;
+$.fn.remove = function() {
+	$("*", this).add(this).triggerHandler("remove");
+	return _remove.apply(this, arguments );
+};
+
+function isVisible(element) {
+	function checkStyles(element) {
+		var style = element.style;
+		return (style.display != 'none' && style.visibility != 'hidden');
+	}
+	
+	var visible = checkStyles(element);
+	
+	(visible && $.each($.dir(element, 'parentNode'), function() {
+		return (visible = checkStyles(this));
+	}));
+	
+	return visible;
+}
+
+$.extend($.expr[':'], {
+	data: function(a, i, m) {
+		return $.data(a, m[3]);
+	},
+	
+	// TODO: add support for object, area
+	tabbable: function(a, i, m) {
+		var nodeName = a.nodeName.toLowerCase();
+		
+		return (
+			// in tab order
+			a.tabIndex >= 0 &&
+			
+			( // filter node types that participate in the tab order
+				
+				// anchor tag
+				('a' == nodeName && a.href) ||
+				
+				// enabled form element
+				(/input|select|textarea|button/.test(nodeName) &&
+					'hidden' != a.type && !a.disabled)
+			) &&
+			
+			// visible on page
+			isVisible(a)
+		);
+	}
+});
+
+$.keyCode = {
+	BACKSPACE: 8,
+	CAPS_LOCK: 20,
+	COMMA: 188,
+	CONTROL: 17,
+	DELETE: 46,
+	DOWN: 40,
+	END: 35,
+	ENTER: 13,
+	ESCAPE: 27,
+	HOME: 36,
+	INSERT: 45,
+	LEFT: 37,
+	NUMPAD_ADD: 107,
+	NUMPAD_DECIMAL: 110,
+	NUMPAD_DIVIDE: 111,
+	NUMPAD_ENTER: 108,
+	NUMPAD_MULTIPLY: 106,
+	NUMPAD_SUBTRACT: 109,
+	PAGE_DOWN: 34,
+	PAGE_UP: 33,
+	PERIOD: 190,
+	RIGHT: 39,
+	SHIFT: 16,
+	SPACE: 32,
+	TAB: 9,
+	UP: 38
+};
+
+// $.widget is a factory to create jQuery plugins
+// taking some boilerplate code out of the plugin code
+// created by Scott González and Jörn Zaefferer
+function getter(namespace, plugin, method, args) {
+	function getMethods(type) {
+		var methods = $[namespace][plugin][type] || [];
+		return (typeof methods == 'string' ? methods.split(/,?\s+/) : methods);
+	}
+	
+	var methods = getMethods('getter');
+	if (args.length == 1 && typeof args[0] == 'string') {
+		methods = methods.concat(getMethods('getterSetter'));
+	}
+	return ($.inArray(method, methods) != -1);
+}
+
+$.widget = function(name, prototype) {
+	var namespace = name.split(".")[0];
+	name = name.split(".")[1];
+	
+	// create plugin method
+	$.fn[name] = function(options) {
+		var isMethodCall = (typeof options == 'string'),
+			args = Array.prototype.slice.call(arguments, 1);
+		
+		// prevent calls to internal methods
+		if (isMethodCall && options.substring(0, 1) == '_') {
+			return this;
+		}
+		
+		// handle getter methods
+		if (isMethodCall && getter(namespace, name, options, args)) {
+			var instance = $.data(this[0], name);
+			return (instance ? instance[options].apply(instance, args)
+				: undefined);
+		}
+		
+		// handle initialization and non-getter methods
+		return this.each(function() {
+			var instance = $.data(this, name);
+			
+			// constructor
+			(!instance && !isMethodCall &&
+				$.data(this, name, new $[namespace][name](this, options)));
+			
+			// method call
+			(instance && isMethodCall && $.isFunction(instance[options]) &&
+				instance[options].apply(instance, args));
+		});
+	};
+	
+	// create widget constructor
+	$[namespace][name] = function(element, options) {
+		var self = this;
+		
+		this.widgetName = name;
+		this.widgetEventPrefix = $[namespace][name].eventPrefix || name;
+		this.widgetBaseClass = namespace + '-' + name;
+		
+		this.options = $.extend({},
+			$.widget.defaults,
+			$[namespace][name].defaults,
+			$.metadata && $.metadata.get(element)[name],
+			options);
+		
+		this.element = $(element)
+			.bind('setData.' + name, function(e, key, value) {
+				return self._setData(key, value);
+			})
+			.bind('getData.' + name, function(e, key) {
+				return self._getData(key);
+			})
+			.bind('remove', function() {
+				return self.destroy();
+			});
+		
+		this._init();
+	};
+	
+	// add widget prototype
+	$[namespace][name].prototype = $.extend({}, $.widget.prototype, prototype);
+	
+	// TODO: merge getter and getterSetter properties from widget prototype
+	// and plugin prototype
+	$[namespace][name].getterSetter = 'option';
+};
+
+$.widget.prototype = {
+	_init: function() {},
+	destroy: function() {
+		this.element.removeData(this.widgetName);
+	},
+	
+	option: function(key, value) {
+		var options = key,
+			self = this;
+		
+		if (typeof key == "string") {
+			if (value === undefined) {
+				return this._getData(key);
+			}
+			options = {};
+			options[key] = value;
+		}
+		
+		$.each(options, function(key, value) {
+			self._setData(key, value);
+		});
+	},
+	_getData: function(key) {
+		return this.options[key];
+	},
+	_setData: function(key, value) {
+		this.options[key] = value;
+		
+		if (key == 'disabled') {
+			this.element[value ? 'addClass' : 'removeClass'](
+				this.widgetBaseClass + '-disabled');
+		}
+	},
+	
+	enable: function() {
+		this._setData('disabled', false);
+	},
+	disable: function() {
+		this._setData('disabled', true);
+	},
+	
+	_trigger: function(type, e, data) {
+		var eventName = (type == this.widgetEventPrefix
+			? type : this.widgetEventPrefix + type);
+		e = e  || $.event.fix({ type: eventName, target: this.element[0] });
+		return this.element.triggerHandler(eventName, [e, data], this.options[type]);
+	}
+};
+
+$.widget.defaults = {
+	disabled: false
+};
+
+
+/** jQuery UI core **/
+
+$.ui = {
+	plugin: {
+		add: function(module, option, set) {
+			var proto = $.ui[module].prototype;
+			for(var i in set) {
+				proto.plugins[i] = proto.plugins[i] || [];
+				proto.plugins[i].push([option, set[i]]);
+			}
+		},
+		call: function(instance, name, args) {
+			var set = instance.plugins[name];
+			if(!set) { return; }
+			
+			for (var i = 0; i < set.length; i++) {
+				if (instance.options[set[i][0]]) {
+					set[i][1].apply(instance.element, args);
+				}
+			}
+		}	
+	},
+	cssCache: {},
+	css: function(name) {
+		if ($.ui.cssCache[name]) { return $.ui.cssCache[name]; }
+		var tmp = $('<div class="ui-gen">').addClass(name).css({position:'absolute', top:'-5000px', left:'-5000px', display:'block'}).appendTo('body');
+		
+		//if (!$.browser.safari)
+			//tmp.appendTo('body'); 
+		
+		//Opera and Safari set width and height to 0px instead of auto
+		//Safari returns rgba(0,0,0,0) when bgcolor is not set
+		$.ui.cssCache[name] = !!(
+			(!(/auto|default/).test(tmp.css('cursor')) || (/^[1-9]/).test(tmp.css('height')) || (/^[1-9]/).test(tmp.css('width')) || 
+			!(/none/).test(tmp.css('backgroundImage')) || !(/transparent|rgba\(0, 0, 0, 0\)/).test(tmp.css('backgroundColor')))
+		);
+		try { $('body').get(0).removeChild(tmp.get(0));	} catch(e){}
+		return $.ui.cssCache[name];
+	},
+	disableSelection: function(el) {
+		$(el)
+			.attr('unselectable', 'on')
+			.css('MozUserSelect', 'none')
+			.bind('selectstart.ui', function() { return false; });
+	},
+	enableSelection: function(el) {
+		$(el)
+			.attr('unselectable', 'off')
+			.css('MozUserSelect', '')
+			.unbind('selectstart.ui');
+	},
+	hasScroll: function(e, a) {
+		var scroll = (a && a == 'left') ? 'scrollLeft' : 'scrollTop',
+			has = false;
+		
+		if (e[scroll] > 0) { return true; }
+		
+		// TODO: determine which cases actually cause this to happen
+		// if the element doesn't have the scroll set, see if it's possible to
+		// set the scroll
+		e[scroll] = 1;
+		has = (e[scroll] > 0);
+		e[scroll] = 0;
+		return has;
+	}
+};
+
+
+/** Mouse Interaction Plugin **/
+
+$.ui.mouse = {
+	_mouseInit: function() {
+		var self = this;
+	
+		this.element.bind('mousedown.'+this.widgetName, function(e) {
+			return self._mouseDown(e);
+		});
+		
+		// Prevent text selection in IE
+		if ($.browser.msie) {
+			this._mouseUnselectable = this.element.attr('unselectable');
+			this.element.attr('unselectable', 'on');
+		}
+		
+		this.started = false;
+	},
+	
+	// TODO: make sure destroying one instance of mouse doesn't mess with
+	// other instances of mouse
+	_mouseDestroy: function() {
+		this.element.unbind('.'+this.widgetName);
+		
+		// Restore text selection in IE
+		($.browser.msie
+			&& this.element.attr('unselectable', this._mouseUnselectable));
+	},
+	
+	_mouseDown: function(e) {
+		// we may have missed mouseup (out of window)
+		(this._mouseStarted && this._mouseUp(e));
+		
+		this._mouseDownEvent = e;
+		
+		var self = this,
+			btnIsLeft = (e.which == 1),
+			elIsCancel = (typeof this.options.cancel == "string" ? $(e.target).parents().add(e.target).filter(this.options.cancel).length : false);
+		if (!btnIsLeft || elIsCancel || !this._mouseCapture(e)) {
+			return true;
+		}
+		
+		this.mouseDelayMet = !this.options.delay;
+		if (!this.mouseDelayMet) {
+			this._mouseDelayTimer = setTimeout(function() {
+				self.mouseDelayMet = true;
+			}, this.options.delay);
+		}
+		
+		if (this._mouseDistanceMet(e) && this._mouseDelayMet(e)) {
+			this._mouseStarted = (this._mouseStart(e) !== false);
+			if (!this._mouseStarted) {
+				e.preventDefault();
+				return true;
+			}
+		}
+		
+		// these delegates are required to keep context
+		this._mouseMoveDelegate = function(e) {
+			return self._mouseMove(e);
+		};
+		this._mouseUpDelegate = function(e) {
+			return self._mouseUp(e);
+		};
+		$(document)
+			.bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+			.bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+		
+		return false;
+	},
+	
+	_mouseMove: function(e) {
+		// IE mouseup check - mouseup happened when mouse was out of window
+		if ($.browser.msie && !e.button) {
+			return this._mouseUp(e);
+		}
+		
+		if (this._mouseStarted) {
+			this._mouseDrag(e);
+			return false;
+		}
+		
+		if (this._mouseDistanceMet(e) && this._mouseDelayMet(e)) {
+			this._mouseStarted =
+				(this._mouseStart(this._mouseDownEvent, e) !== false);
+			(this._mouseStarted ? this._mouseDrag(e) : this._mouseUp(e));
+		}
+		
+		return !this._mouseStarted;
+	},
+	
+	_mouseUp: function(e) {
+		$(document)
+			.unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+			.unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+		
+		if (this._mouseStarted) {
+			this._mouseStarted = false;
+			this._mouseStop(e);
+		}
+		
+		return false;
+	},
+	
+	_mouseDistanceMet: function(e) {
+		return (Math.max(
+				Math.abs(this._mouseDownEvent.pageX - e.pageX),
+				Math.abs(this._mouseDownEvent.pageY - e.pageY)
+			) >= this.options.distance
+		);
+	},
+	
+	_mouseDelayMet: function(e) {
+		return this.mouseDelayMet;
+	},
+	
+	// These are placeholder methods, to be overriden by extending plugin
+	_mouseStart: function(e) {},
+	_mouseDrag: function(e) {},
+	_mouseStop: function(e) {},
+	_mouseCapture: function(e) { return true; }
+};
+
+$.ui.mouse.defaults = {
+	cancel: null,
+	distance: 1,
+	delay: 0
+};
+
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/ui.tabs.css	Thu Dec 18 11:04:10 2008 +0100
@@ -0,0 +1,125 @@
+/* Caution! Ensure accessibility in print and other media types... */
+@media projection, screen { /* Use class for showing/hiding tab content, so that visibility can be better controlled in different media types... */
+    .ui-tabs-hide {
+        display: none;
+    }
+}
+
+/* Hide useless elements in print layouts... */
+@media print {
+    .ui-tabs-nav {
+        display: none;
+    }
+}
+
+/* Skin */
+.ui-tabs-nav, .ui-tabs-panel {
+    font-family: "Trebuchet MS", Trebuchet, Verdana, Helvetica, Arial, sans-serif;
+    font-size: 12px;
+
+}
+.ui-tabs-nav {
+    list-style: none;
+    margin: 0px;
+    padding: 0px 0px 0px 4px; 
+
+}
+.ui-tabs-nav:after { /* clearing without presentational markup, IE gets extra treatment */
+    display: block;
+    clear: both;
+    content: " ";
+}
+.ui-tabs-nav li {
+    float: left;
+    margin: 0 0 0 1px;
+    min-width: 84px; /* be nice to Opera */
+    list-style: none;
+    background: none;
+    padding: 0px 0px 1px 1px;
+}
+.ui-tabs-nav a, .ui-tabs-nav a span {
+    display: block;
+    padding: 0 10px;
+    background: url(tab.png) no-repeat;
+}
+.ui-tabs-nav a {
+    margin: 1px 0 0; /* position: relative makes opacity fail for disabled tab in IE */
+    padding-left: 0;
+    color: #27537a;
+    font-weight: bold;
+    line-height: 1.2;
+    text-align: center;
+    text-decoration: none;
+    white-space: nowrap; /* required in IE 6 */    
+    outline: 0; /* prevent dotted border in Firefox */
+}
+.ui-tabs-nav .ui-tabs-selected a {
+    position: relative;
+    top: 1px;
+    z-index: 2;
+    margin-top: 0;
+    color: #000;
+}
+.ui-tabs-nav a span {
+    width: 64px; /* IE 6 treats width as min-width */
+    min-width: 64px;
+    height: 18px; /* IE 6 treats height as min-height */
+    min-height: 18px;
+    padding-top: 6px;
+    padding-right: 0;
+}
+*>.ui-tabs-nav a span { /* hide from IE 6 */
+    width: auto;
+    height: auto;
+}
+.ui-tabs-nav .ui-tabs-selected a span {
+    padding-bottom: 1px;
+}
+.ui-tabs-nav .ui-tabs-selected a, .ui-tabs-nav a:hover, .ui-tabs-nav a:focus, .ui-tabs-nav a:active {
+    background-position: 100% -150px;
+}
+.ui-tabs-nav a, .ui-tabs-nav .ui-tabs-disabled a:hover, .ui-tabs-nav .ui-tabs-disabled a:focus, .ui-tabs-nav .ui-tabs-disabled a:active {
+    background-position: 100% -100px;
+}
+.ui-tabs-nav .ui-tabs-selected a span, .ui-tabs-nav a:hover span, .ui-tabs-nav a:focus span, .ui-tabs-nav a:active span {
+    background-position: 0 -50px;
+}
+.ui-tabs-nav a span, .ui-tabs-nav .ui-tabs-disabled a:hover span, .ui-tabs-nav .ui-tabs-disabled a:focus span, .ui-tabs-nav .ui-tabs-disabled a:active span {
+    background-position: 0 0;
+}
+.ui-tabs-nav .ui-tabs-selected a:link, .ui-tabs-nav .ui-tabs-selected a:visited, .ui-tabs-nav .ui-tabs-disabled a:link, .ui-tabs-nav .ui-tabs-disabled a:visited { /* @ Opera, use pseudo classes otherwise it confuses cursor... */
+    cursor: text;
+}
+.ui-tabs-nav a:hover, .ui-tabs-nav a:focus, .ui-tabs-nav a:active,
+.ui-tabs-nav .ui-tabs-unselect a:hover, .ui-tabs-nav .ui-tabs-unselect a:focus, .ui-tabs-nav .ui-tabs-unselect a:active { /* @ Opera, we need to be explicit again here now... */
+    cursor: pointer;
+}
+.ui-tabs-disabled {
+    opacity: .4;
+    filter: alpha(opacity=40);
+}
+.ui-tabs-panel {
+    border-top: 1px solid #97a5b0;
+    padding: 1em 8px;
+    margin-top:-1px;  /* Logilab style */
+    background: #fff; /* declare background color for container to avoid distorted fonts in IE while fading */
+}
+.ui-tabs-loading em {
+    padding: 0 0 0 20px;
+    background: url(loading.gif) no-repeat 0 50%;
+}
+
+/* Additional IE specific bug fixes... */
+* html .ui-tabs-nav { /* auto clear, @ IE 6 & IE 7 Quirks Mode */
+    display: inline-block;
+}
+*:first-child+html .ui-tabs-nav  { /* @ IE 7 Standards Mode - do not group selectors, otherwise IE 6 will ignore complete rule (because of the unknown + combinator)... */
+    display: inline-block;
+}
+
+/* ========= Lobilab styles =========== */
+
+/* added by katia */
+* html .ui-tabs-panel{  
+    width:100%;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/data/ui.tabs.js	Thu Dec 18 11:04:10 2008 +0100
@@ -0,0 +1,587 @@
+/*
+ * jQuery UI Tabs @VERSION
+ *
+ * Copyright (c) 2007, 2008 Klaus Hartl (stilbuero.de)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Tabs
+ *
+ * Depends:
+ *	ui.core.js
+ */
+(function($) {
+
+$.widget("ui.tabs", {
+	_init: function() {
+		this.options.event += '.tabs'; // namespace event
+		
+		// create tabs
+		this._tabify(true);
+	},
+	_setData: function(key, value) {
+		if ((/^selected/).test(key))
+			this.select(value);
+		else {
+			this.options[key] = value;
+			this._tabify();
+		}
+	},
+	length: function() {
+		return this.$tabs.length;
+	},
+	_tabId: function(a) {
+		return a.title && a.title.replace(/\s/g, '_').replace(/[^A-Za-z0-9\-_:\.]/g, '')
+			|| this.options.idPrefix + $.data(a);
+	},
+	ui: function(tab, panel) {
+		return {
+			options: this.options,
+			tab: tab,
+			panel: panel,
+			index: this.$tabs.index(tab)
+		};
+	},
+	_tabify: function(init) {
+
+		this.$lis = $('li:has(a[href])', this.element);
+		this.$tabs = this.$lis.map(function() { return $('a', this)[0]; });
+		this.$panels = $([]);
+
+		var self = this, o = this.options;
+
+		this.$tabs.each(function(i, a) {
+			// inline tab
+			if (a.hash && a.hash.replace('#', '')) // Safari 2 reports '#' for an empty hash
+				self.$panels = self.$panels.add(a.hash);
+			// remote tab
+			else if ($(a).attr('href') != '#') { // prevent loading the page itself if href is just "#"
+				$.data(a, 'href.tabs', a.href); // required for restore on destroy
+				$.data(a, 'load.tabs', a.href); // mutable
+				var id = self._tabId(a);
+				a.href = '#' + id;
+				var $panel = $('#' + id);
+				if (!$panel.length) {
+					$panel = $(o.panelTemplate).attr('id', id).addClass(o.panelClass)
+						.insertAfter( self.$panels[i - 1] || self.element );
+					$panel.data('destroy.tabs', true);
+				}
+				self.$panels = self.$panels.add( $panel );
+			}
+			// invalid tab href
+			else
+				o.disabled.push(i + 1);
+		});
+
+		// initialization from scratch
+		if (init) {
+
+			// attach necessary classes for styling if not present
+			this.element.addClass(o.navClass);
+			this.$panels.each(function() {
+				var $this = $(this);
+				$this.addClass(o.panelClass);
+			});
+
+			// Selected tab
+			// use "selected" option or try to retrieve:
+			// 1. from fragment identifier in url
+			// 2. from cookie
+			// 3. from selected class attribute on <li>
+			if (o.selected === undefined) {
+				if (location.hash) {
+					this.$tabs.each(function(i, a) {
+						if (a.hash == location.hash) {
+							o.selected = i;
+							// prevent page scroll to fragment
+							if ($.browser.msie || $.browser.opera) { // && !o.remote
+								var $toShow = $(location.hash), toShowId = $toShow.attr('id');
+								$toShow.attr('id', '');
+								setTimeout(function() {
+									$toShow.attr('id', toShowId); // restore id
+								}, 500);
+							}
+							scrollTo(0, 0);
+							return false; // break
+						}
+					});
+				}
+				else if (o.cookie) {
+					var index = parseInt($.cookie('ui-tabs-' + $.data(self.element[0])), 10);
+					if (index && self.$tabs[index])
+						o.selected = index;
+				}
+				else if (self.$lis.filter('.' + o.selectedClass).length)
+					o.selected = self.$lis.index( self.$lis.filter('.' + o.selectedClass)[0] );
+			}
+			o.selected = o.selected === null || o.selected !== undefined ? o.selected : 0; // first tab selected by default
+
+			// Take disabling tabs via class attribute from HTML
+			// into account and update option properly.
+			// A selected tab cannot become disabled.
+			o.disabled = $.unique(o.disabled.concat(
+				$.map(this.$lis.filter('.' + o.disabledClass),
+					function(n, i) { return self.$lis.index(n); } )
+			)).sort();
+			if ($.inArray(o.selected, o.disabled) != -1)
+				o.disabled.splice($.inArray(o.selected, o.disabled), 1);
+			
+			// highlight selected tab
+			this.$panels.addClass(o.hideClass);
+			this.$lis.removeClass(o.selectedClass);
+			if (o.selected !== null) {
+				this.$panels.eq(o.selected).show().removeClass(o.hideClass); // use show and remove class to show in any case no matter how it has been hidden before
+				this.$lis.eq(o.selected).addClass(o.selectedClass);
+				
+				// seems to be expected behavior that the show callback is fired
+				var onShow = function() {
+					self._trigger('show', null,
+						self.ui(self.$tabs[o.selected], self.$panels[o.selected]));
+				};
+
+				// load if remote tab
+				if ($.data(this.$tabs[o.selected], 'load.tabs'))
+					this.load(o.selected, onShow);
+				// just trigger show event
+				else
+					onShow();
+			}
+			
+			// clean up to avoid memory leaks in certain versions of IE 6
+			$(window).bind('unload', function() {
+				self.$tabs.unbind('.tabs');
+				self.$lis = self.$tabs = self.$panels = null;
+			});
+
+		}
+		// update selected after add/remove
+		else
+			o.selected = this.$lis.index( this.$lis.filter('.' + o.selectedClass)[0] );
+
+		// set or update cookie after init and add/remove respectively
+		if (o.cookie)
+			$.cookie('ui-tabs-' + $.data(self.element[0]), o.selected, o.cookie);
+		
+		// disable tabs
+		for (var i = 0, li; li = this.$lis[i]; i++)
+			$(li)[$.inArray(i, o.disabled) != -1 && !$(li).hasClass(o.selectedClass) ? 'addClass' : 'removeClass'](o.disabledClass);
+
+		// reset cache if switching from cached to not cached
+		if (o.cache === false)
+			this.$tabs.removeData('cache.tabs');
+		
+		// set up animations
+		var hideFx, showFx, baseFx = { 'min-width': 0, duration: 1 }, baseDuration = 'normal';
+		if (o.fx && o.fx.constructor == Array)
+			hideFx = o.fx[0] || baseFx, showFx = o.fx[1] || baseFx;
+		else
+			hideFx = showFx = o.fx || baseFx;
+
+		// reset some styles to maintain print style sheets etc.
+		var resetCSS = { display: '', overflow: '', height: '' };
+		if (!$.browser.msie) // not in IE to prevent ClearType font issue
+			resetCSS.opacity = '';
+
+		// Hide a tab, animation prevents browser scrolling to fragment,
+		// $show is optional.
+		function hideTab(clicked, $hide, $show) {
+			$hide.animate(hideFx, hideFx.duration || baseDuration, function() { //
+				$hide.addClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
+				if ($.browser.msie && hideFx.opacity)
+					$hide[0].style.filter = '';
+				if ($show)
+					showTab(clicked, $show, $hide);
+			});
+		}
+
+		// Show a tab, animation prevents browser scrolling to fragment,
+		// $hide is optional.
+		function showTab(clicked, $show, $hide) {
+			if (showFx === baseFx)
+				$show.css('display', 'block'); // prevent occasionally occuring flicker in Firefox cause by gap between showing and hiding the tab panels
+			$show.animate(showFx, showFx.duration || baseDuration, function() {
+				$show.removeClass(o.hideClass).css(resetCSS); // maintain flexible height and accessibility in print etc.
+				if ($.browser.msie && showFx.opacity)
+					$show[0].style.filter = '';
+
+				// callback
+				self._trigger('show', null, self.ui(clicked, $show[0]));
+			});
+		}
+
+		// switch a tab
+		function switchTab(clicked, $li, $hide, $show) {
+			/*if (o.bookmarkable && trueClick) { // add to history only if true click occured, not a triggered click
+				$.ajaxHistory.update(clicked.hash);
+			}*/
+			$li.addClass(o.selectedClass)
+				.siblings().removeClass(o.selectedClass);
+			hideTab(clicked, $hide, $show);
+		}
+
+		// attach tab event handler, unbind to avoid duplicates from former tabifying...
+		this.$tabs.unbind('.tabs').bind(o.event, function() {
+
+			//var trueClick = e.clientX; // add to history only if true click occured, not a triggered click
+			var $li = $(this).parents('li:eq(0)'),
+				$hide = self.$panels.filter(':visible'),
+				$show = $(this.hash);
+
+			// If tab is already selected and not unselectable or tab disabled or 
+			// or is already loading or click callback returns false stop here.
+			// Check if click handler returns false last so that it is not executed
+			// for a disabled or loading tab!
+			if (($li.hasClass(o.selectedClass) && !o.unselect)
+				|| $li.hasClass(o.disabledClass) 
+				|| $(this).hasClass(o.loadingClass)
+				|| self._trigger('select', null, self.ui(this, $show[0])) === false
+				) {
+				this.blur();
+				return false;
+			}
+
+			self.options.selected = self.$tabs.index(this);
+
+			// if tab may be closed
+			if (o.unselect) {
+				if ($li.hasClass(o.selectedClass)) {
+					self.options.selected = null;
+					$li.removeClass(o.selectedClass);
+					self.$panels.stop();
+					hideTab(this, $hide);
+					this.blur();
+					return false;
+				} else if (!$hide.length) {
+					self.$panels.stop();
+					var a = this;
+					self.load(self.$tabs.index(this), function() {
+						$li.addClass(o.selectedClass).addClass(o.unselectClass);
+						showTab(a, $show);
+					});
+					this.blur();
+					return false;
+				}
+			}
+
+			if (o.cookie)
+				$.cookie('ui-tabs-' + $.data(self.element[0]), self.options.selected, o.cookie);
+
+			// stop possibly running animations
+			self.$panels.stop();
+
+			// show new tab
+			if ($show.length) {
+
+				// prevent scrollbar scrolling to 0 and than back in IE7, happens only if bookmarking/history is enabled
+				/*if ($.browser.msie && o.bookmarkable) {
+					var showId = this.hash.replace('#', '');
+					$show.attr('id', '');
+					setTimeout(function() {
+						$show.attr('id', showId); // restore id
+					}, 0);
+				}*/
+
+				var a = this;
+				self.load(self.$tabs.index(this), $hide.length ? 
+					function() {
+						switchTab(a, $li, $hide, $show);
+					} :
+					function() {
+						$li.addClass(o.selectedClass);
+						showTab(a, $show);
+					}
+				);
+
+				// Set scrollbar to saved position - need to use timeout with 0 to prevent browser scroll to target of hash
+				/*var scrollX = window.pageXOffset || document.documentElement && document.documentElement.scrollLeft || document.body.scrollLeft || 0;
+				var scrollY = window.pageYOffset || document.documentElement && document.documentElement.scrollTop || document.body.scrollTop || 0;
+				setTimeout(function() {
+					scrollTo(scrollX, scrollY);
+				}, 0);*/
+
+			} else
+				throw 'jQuery UI Tabs: Mismatching fragment identifier.';
+
+			// Prevent IE from keeping other link focussed when using the back button
+			// and remove dotted border from clicked link. This is controlled in modern
+			// browsers via CSS, also blur removes focus from address bar in Firefox
+			// which can become a usability and annoying problem with tabsRotate.
+			if ($.browser.msie)
+				this.blur();
+
+			//return o.bookmarkable && !!trueClick; // convert trueClick == undefined to Boolean required in IE
+			return false;
+
+		});
+
+		// disable click if event is configured to something else
+		if (!(/^click/).test(o.event))
+			this.$tabs.bind('click.tabs', function() { return false; });
+
+	},
+	add: function(url, label, index) {
+		if (index == undefined) 
+			index = this.$tabs.length; // append by default
+
+		var o = this.options;
+		var $li = $(o.tabTemplate.replace(/#\{href\}/g, url).replace(/#\{label\}/g, label));
+		$li.data('destroy.tabs', true);
+
+		var id = url.indexOf('#') == 0 ? url.replace('#', '') : this._tabId( $('a:first-child', $li)[0] );
+
+		// try to find an existing element before creating a new one
+		var $panel = $('#' + id);
+		if (!$panel.length) {
+			$panel = $(o.panelTemplate).attr('id', id)
+				.addClass(o.hideClass)
+				.data('destroy.tabs', true);
+		}
+		$panel.addClass(o.panelClass);
+		if (index >= this.$lis.length) {
+			$li.appendTo(this.element);
+			$panel.appendTo(this.element[0].parentNode);
+		} else {
+			$li.insertBefore(this.$lis[index]);
+			$panel.insertBefore(this.$panels[index]);
+		}
+		
+		o.disabled = $.map(o.disabled,
+			function(n, i) { return n >= index ? ++n : n });
+			
+		this._tabify();
+
+		if (this.$tabs.length == 1) {
+			$li.addClass(o.selectedClass);
+			$panel.removeClass(o.hideClass);
+			var href = $.data(this.$tabs[0], 'load.tabs');
+			if (href)
+				this.load(index, href);
+		}
+
+		// callback
+		this._trigger('add', null, this.ui(this.$tabs[index], this.$panels[index]));
+	},
+	remove: function(index) {
+		var o = this.options, $li = this.$lis.eq(index).remove(),
+			$panel = this.$panels.eq(index).remove();
+
+		// If selected tab was removed focus tab to the right or
+		// in case the last tab was removed the tab to the left.
+		if ($li.hasClass(o.selectedClass) && this.$tabs.length > 1)
+			this.select(index + (index + 1 < this.$tabs.length ? 1 : -1));
+
+		o.disabled = $.map($.grep(o.disabled, function(n, i) { return n != index; }),
+			function(n, i) { return n >= index ? --n : n });
+
+		this._tabify();
+
+		// callback
+		this._trigger('remove', null, this.ui($li.find('a')[0], $panel[0]));
+	},
+	enable: function(index) {
+		var o = this.options;
+		if ($.inArray(index, o.disabled) == -1)
+			return;
+			
+		var $li = this.$lis.eq(index).removeClass(o.disabledClass);
+		if ($.browser.safari) { // fix disappearing tab (that used opacity indicating disabling) after enabling in Safari 2...
+			$li.css('display', 'inline-block');
+			setTimeout(function() {
+				$li.css('display', 'block');
+			}, 0);
+		}
+
+		o.disabled = $.grep(o.disabled, function(n, i) { return n != index; });
+
+		// callback
+		this._trigger('enable', null, this.ui(this.$tabs[index], this.$panels[index]));
+	},
+	disable: function(index) {
+		var self = this, o = this.options;
+		if (index != o.selected) { // cannot disable already selected tab
+			this.$lis.eq(index).addClass(o.disabledClass);
+
+			o.disabled.push(index);
+			o.disabled.sort();
+
+			// callback
+			this._trigger('disable', null, this.ui(this.$tabs[index], this.$panels[index]));
+		}
+	},
+	select: function(index) {
+		if (typeof index == 'string')
+			index = this.$tabs.index( this.$tabs.filter('[href$=' + index + ']')[0] );
+		this.$tabs.eq(index).trigger(this.options.event);
+	},
+	load: function(index, callback) { // callback is for internal usage only
+		
+		var self = this, o = this.options, $a = this.$tabs.eq(index), a = $a[0],
+				bypassCache = callback == undefined || callback === false, url = $a.data('load.tabs');
+
+		callback = callback || function() {};
+		
+		// no remote or from cache - just finish with callback
+		if (!url || !bypassCache && $.data(a, 'cache.tabs')) {
+			callback();
+			return;
+		}
+
+		// load remote from here on
+		
+		var inner = function(parent) {
+			var $parent = $(parent), $inner = $parent.find('*:last');
+			return $inner.length && $inner.is(':not(img)') && $inner || $parent;
+		};
+		var cleanup = function() {
+			self.$tabs.filter('.' + o.loadingClass).removeClass(o.loadingClass)
+						.each(function() {
+							if (o.spinner)
+								inner(this).parent().html(inner(this).data('label.tabs'));
+						});
+			self.xhr = null;
+		};
+		
+		if (o.spinner) {
+			var label = inner(a).html();
+			inner(a).wrapInner('<em></em>')
+				.find('em').data('label.tabs', label).html(o.spinner);
+		}
+
+		var ajaxOptions = $.extend({}, o.ajaxOptions, {
+			url: url,
+			success: function(r, s) {
+				$(a.hash).html(r);
+				cleanup();
+				
+				if (o.cache)
+					$.data(a, 'cache.tabs', true); // if loaded once do not load them again
+
+				// callbacks
+				self._trigger('load', null, self.ui(self.$tabs[index], self.$panels[index]));
+				o.ajaxOptions.success && o.ajaxOptions.success(r, s);
+				
+				// This callback is required because the switch has to take
+				// place after loading has completed. Call last in order to 
+				// fire load before show callback...
+				callback();
+			}
+		});
+		if (this.xhr) {
+			// terminate pending requests from other tabs and restore tab label
+			this.xhr.abort();
+			cleanup();
+		}
+		$a.addClass(o.loadingClass);
+		setTimeout(function() { // timeout is again required in IE, "wait" for id being restored
+			self.xhr = $.ajax(ajaxOptions);
+		}, 0);
+
+	},
+	url: function(index, url) {
+		this.$tabs.eq(index).removeData('cache.tabs').data('load.tabs', url);
+	},
+	destroy: function() {
+		var o = this.options;
+		this.element.unbind('.tabs')
+			.removeClass(o.navClass).removeData('tabs');
+		this.$tabs.each(function() {
+			var href = $.data(this, 'href.tabs');
+			if (href)
+				this.href = href;
+			var $this = $(this).unbind('.tabs');
+			$.each(['href', 'load', 'cache'], function(i, prefix) {
+				$this.removeData(prefix + '.tabs');
+			});
+		});
+		this.$lis.add(this.$panels).each(function() {
+			if ($.data(this, 'destroy.tabs'))
+				$(this).remove();
+			else
+				$(this).removeClass([o.selectedClass, o.unselectClass,
+					o.disabledClass, o.panelClass, o.hideClass].join(' '));
+		});
+	}
+});
+
+$.ui.tabs.defaults = {
+	// basic setup
+	unselect: false,
+	event: 'click',
+	disabled: [],
+	cookie: null, // e.g. { expires: 7, path: '/', domain: 'jquery.com', secure: true }
+	// TODO history: false,
+
+	// Ajax
+	spinner: 'Loading&#8230;',
+	cache: false,
+	idPrefix: 'ui-tabs-',
+	ajaxOptions: {},
+
+	// animations
+	fx: null, // e.g. { height: 'toggle', opacity: 'toggle', duration: 200 }
+
+	// templates
+	tabTemplate: '<li><a href="#{href}"><span>#{label}</span></a></li>',
+	panelTemplate: '<div></div>',
+
+	// CSS classes
+	navClass: 'ui-tabs-nav',
+	selectedClass: 'ui-tabs-selected',
+	unselectClass: 'ui-tabs-unselect',
+	disabledClass: 'ui-tabs-disabled',
+	panelClass: 'ui-tabs-panel',
+	hideClass: 'ui-tabs-hide',
+	loadingClass: 'ui-tabs-loading'
+};
+
+$.ui.tabs.getter = "length";
+
+/*
+ * Tabs Extensions
+ */
+
+/*
+ * Rotate
+ */
+$.extend($.ui.tabs.prototype, {
+	rotation: null,
+	rotate: function(ms, continuing) {
+		
+		continuing = continuing || false;
+		
+		var self = this, t = this.options.selected;
+		
+		function start() {
+			self.rotation = setInterval(function() {
+				t = ++t < self.$tabs.length ? t : 0;
+				self.select(t);
+			}, ms); 
+		}
+		
+		function stop(e) {
+			if (!e || e.clientX) { // only in case of a true click
+				clearInterval(self.rotation);
+			}
+		}
+		
+		// start interval
+		if (ms) {
+			start();
+			if (!continuing)
+				this.$tabs.bind(this.options.event, stop);
+			else
+				this.$tabs.bind(this.options.event, function() {
+					stop();
+					t = self.options.selected;
+					start();
+				});
+		}
+		// stop interval
+		else {
+			stop();
+			this.$tabs.unbind(this.options.event, stop);
+		}
+	}
+});
+
+})(jQuery);
--- a/web/htmlwidgets.py	Thu Dec 18 11:03:50 2008 +0100
+++ b/web/htmlwidgets.py	Thu Dec 18 11:04:10 2008 +0100
@@ -314,3 +314,4 @@
         self.w(u'<div class="progressbarback" title="%i %%">' % real_pourcent)
         self.w(u'<div class="progressbar %s" style="width: %spx; align: left;" ></div>' % (color, pourcent))
         self.w(u'</div>')
+
--- a/web/views/baseviews.py	Thu Dec 18 11:03:50 2008 +0100
+++ b/web/views/baseviews.py	Thu Dec 18 11:04:10 2008 +0100
@@ -128,6 +128,7 @@
     
     def cell_call(self, row, col):        
         self.row = row
+        # XXX move render_entity implementation here
         self.render_entity(self.complete_entity(row, col))
     
     def render_entity(self, entity):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/tabview.py	Thu Dec 18 11:04:10 2008 +0100
@@ -0,0 +1,51 @@
+from logilab.mtconverter import html_escape
+
+from cubicweb.common.view import EntityView
+
+# XXX
+# This is premature & tied to JPL
+# It should go away & be replaced by something like
+# a TabbedPrimaryView (that would really a primary view)
+
+class TabView(EntityView):
+    id = 'tabview'
+    accepts = ('Project',)
+
+    def active_tab(self):
+        cookie = self.req.get_cookie()
+        cookietab = cookie.get('active_tab')
+        if cookietab is None:
+            cookie['active_tab'] = 'project_main'
+            self.req.set_cookie(cookie, 'active_tab')
+        return cookietab and cookietab.value or 'project_main'
+
+    def cell_call(self, row, col, tabs):
+        self.req.add_css('ui.tabs.css')
+        self.req.add_js( ('ui.core.js', 'ui.tabs.js', 'cubes.jpl.primary.js') )
+        active_tab = self.active_tab()
+        self.req.html_headers.add_post_inline_script(u"""
+ jQuery(document).ready(function() {
+   jQuery('#entity-tabs > ul').tabs( { selected: %(tabindex)s });
+   set_tab('%(vid)s');
+ });
+ """ % {'tabindex' : tabs.index(active_tab),
+        'vid'      : active_tab})
+        # build the html structure
+        self.w(u'<div id="entity-tabs">')
+        self.w(u'<ul>')
+        for tab in tabs:
+            self.w(u'<li>')
+            self.w(u'<a href="#as-%s">' % tab)
+            cookie_setter = "set_tab('%s')" % tab
+            self.w(u'<span onclick="%s">' % cookie_setter)
+            self.w('%s' % self.req._(tab))
+            self.w(u'</span>')
+            self.w(u'</a>')
+            self.w(u'</li>')
+        self.w(u'</ul>')
+        self.w(u'</div>')
+        for tab in tabs:
+            self.w(u'<div id="as-%s">' % tab)
+            self.wview(tab, self.rset)
+            self.w(u'</div>')    
+