Thursday, November 26, 2009

An address to point (lat,lng) google maps converting class with mootools

Hi,
today I'll post here the code of a class useful to obtain a conversion of a string address into geographical coordinates latitude and longitude.
Maybe sometimes you have to represent some points (i.e. hotels) on a map, dinamically, that is getting datas from a database. That means that you need to insert the hotel geographical coordinates in the database, as it's surely better to make the conversion before inserting data in the DB than on the fly when showing the map, that because the conversion can take a bit of time, or even fail.
So the situation I considered is the following one: we have a form with some fields like address, cap and city, and two fields hidden or readonly that contains the latitude and longitude values. The action of this form inserts this values in the DB, so these latitude and longitude are the same that will be used to generate the map in the public view (soon another post about that).
So the html part of the form could be something like
<input type="text" name="address" id="address" />
<input type="text" name="city" id="city" />
<input type="text" name="cap" id="cap" />
<input type="button" value="convert" onclick="[see more over]" />
<input type="text" name="lat" id="lat" readonly="readonly" />
<input type="text" name="lng" id="lng" readonly="readonly" />
Now the goal was to write a javascript class that shows a map with a marker in the position associated to the address already inserted in the form (that is make the conversion), make the marker draggable and  insert the latitude and longitude values into the 'lat' and 'lng' input fields, so that when submitting the form they could be written in the database.
So here goes my javascript class that clearly requires mootools, I omit here mootools inclusion.
/*
 * Class AddressToPointConverter, requires mootools v>=1.2
 * Written by abidibo, 26/11/2009
 * Copyright: what fuck is copyright?!
 *
 * Converts an address in geographical coordinates (lat,lng) using
 * the googlemaps geocoder, and inserts these values in form fields
 * defined in the constructor
 *
 */
var AddressToPointConverter = new Class({
  
    Implements: [Options],
    options: {
        canvasPosition: 'over', // over | inside
        canvasW: '400px',
        canvasH: '300px',
        zoom: '13',
        noResZoom: '5',
        dftLat: '45',
        dftLng: '7'
    },
    initialize: function(element, latField, lngField, address, options) {
  
        if($defined(options)) this.setOptions(options);
        this.checkOptions();

        this.element = $type(element)=='element'? element:$(element);
        this.latField = $type(latField)=='element'? latField:$(latField);
        this.lngField = $type(lngField)=='element'? lngField:$(lngField);
        this.address = address;

    },
    checkOptions: function() {
        if(this.options.canvasPosition == 'over') {
            var rexp = /[0-9]+px/;
            if(!rexp.test(this.options.canvasW)) this.options.canvasW = '400px';
            if(!rexp.test(this.options.canvasH)) this.options.canvasW = '300px';
        }
    },
    showMap: function() {
        this.renderContainer();
        this.renderCanvas();
        this.canvasContainer.setStyle('width', (this.canvas.getCoordinates().width)+'px');
        this.renderCtrl();
        this.renderMap();
    },
    renderContainer: function() {
        this.canvasContainer = new Element('div', {'id':'map_canvas_container'});
        this.canvasContainer.setStyles({
                'padding': '1px',
                'background-color': '#000',
                'border': '1px solid #000'
            })
        if(this.options.canvasPosition == 'inside') {
            this.canvasContainer.inject(this.element);
        }
        else { // over
            var elementCoord = this.element.getCoordinates();
            this.canvasContainer.setStyles({
                'position': 'absolute',
                'top': elementCoord.top+'px',
                'left':elementCoord.left+'px'
            })
            this.canvasContainer.inject(document.body);
        }
        document.body.addEvent('mousedown', this.checkDisposeContainer.bind(this));  
    },
    renderCanvas: function() {
        this.canvas = new Element('div', {'id':'map_canvas'});
        this.canvas.setStyles({
            'width': this.options.canvasW,
            'height': this.options.canvasH
        })
        this.canvas.inject(this.canvasContainer, 'top');
    },
    renderCtrl: function() {
        var divCtrl = new Element('div').setStyles({'background-color': '#ccc', 'padding': '2px 0px', 'text-align': 'center'});
        var convertButton = new Element('input', {'type':'button', 'value':'convert'});
        convertButton.setStyles({'cursor': 'pointer', 'border': '1px solid #999', 'margin-top': '2px'});
        divCtrl.inject(this.canvasContainer, 'bottom');      
        convertButton.inject(divCtrl, 'top');
        convertButton.addEvent('click', function() {
            this.latField.value = this.point.lat();
            this.lngField.value = this.point.lng();
            this.canvasContainer.dispose();
        }.bind(this));
    },
    checkDisposeContainer: function(evt) {
        if(evt.page.x<this.canvasContainer.getCoordinates().left ||
           evt.page.x>this.canvasContainer.getCoordinates().right ||
           evt.page.y<this.canvasContainer.getCoordinates().top ||
           evt.page.y>this.canvasContainer.getCoordinates().bottom) {
            this.canvasContainer.dispose();
            for(var prop in this) this[prop] = null;
            document.body.removeEvent('mousedown', this.checkDisposeContainer);
        }
         
    },
    renderMap: function() {
        var mapOptions = {
                  zoom: this.options.zoom.toInt(),
                  mapTypeId: google.maps.MapTypeId.ROADMAP
            };
        var map = new google.maps.Map(this.canvas, mapOptions);
        var point;
        var geocoder = new google.maps.Geocoder();
        geocoder.geocode({'address':this.address}, function(results, status) {
            if(status == google.maps.GeocoderStatus.OK) {
                this.point = results[0].geometry.location;
                this.insertMarker(map);
            }  
            else {
                alert("Geocode was not successfull for the following reason: "+status);
                this.point = new google.maps.LatLng(this.options.dftLat, this.options.dftLng);
                map.setZoom(this.options.noResZoom.toInt());
                this.insertMarker(map);
            }
        }.bind(this))

    },
    insertMarker: function(map) {
        map.setCenter(this.point);    
        var marker = new google.maps.Marker({
                      map: map,
                      position: this.point,
            draggable: true,
            title: this.point.lat()+' - '+this.point.lng()
              });
        google.maps.event.addListener(marker, 'mouseup', function() {this.point = marker.getPosition();}.bind(this))

    }
})
Some annotations:
HOW TO CONSTRUCT IT
var myConverter = new AddressToPointConverter(element, latField, lngField, address, options);
  • element: the DOM element or its id of the referer, the element from which we inject the map into the document, taking care of some options explained below.
  • latField: the DOM element or its id of the input field we want to write the latitude to
  • lngField: the DOM element or its id of the input field we want to write the longitude to
  • address: (string) the address to convert
  • options: some options in the form {'opt1':'val1', 'opt2':'val2'...}
OPTIONS
  • (string) canvasPosition : inside | over,  default over. Is the position of the map object we create. inside: the map container is inserted into the element defined in the constructor. over: the map container is positioned absolutely, its top left corner in the same position as the top left corner of element
  • (string) canvasW: default '400px'. The style width of the map (i.e. 400px, 100%) . The % values are allowed only for canvasPosition='inside'.
  • (string) canvasH: default '300px'. The style height of the map.
  • (int) zoom: default '13'. The zoom value when a result is found
  • (int) noResZoom: default '7'. The zoom when no result is found (with the conversion)
  • (float) dftLat: default: '45'. The default latitude of the center of the map when no result is found
  • (float) dftLng: default '7'. The default longitude of the center of the map when no result is found
The conversion may give a result or not. If not the map is centered in a point defined by (dftLat, dftLng) and with zoom = noResZoom.
In this case but even if the conversion is not exactly what we expect, we may drag the marker in order to positioning it in the desired position. Than click on the button convert and the computed point will be the point represented by the marker new position.

That was the class code, let's we see now an example about its usage, always considering our form written above
<input type="text" name="address" id="address" />
<input type="text" name="city" id="city" />
<input type="text" name="cap" id="cap" />
<script type="text/javascript">
function convert() {
                var addressConverter = new AddressToPointConverter('converter', 'lat', 'lng', $('address').value+','+$('cap').value+' '+$('city').value, {'canvasPosition':'over', 'canvasW':'600px', 'canvasH':'400px'});
                addressConverter.showMap();
}
</script>
<input type="button" value="convert" onclick="Asset.javascript('http://maps.google.com/maps/api/js?sensor=true&callback=convert')">
<input type="text" name="lat" id="lat" readonly="readonly" />
<input type="text" name="lng" id="lng" readonly="readonly" />
The map is generated when clicking the "convert" button, first the google library is charged, passing a reference to our function as callback. In our function convert() the object is constructed and the method showMap is invocated. The map is shown. To dispose it it's enough to click outside the map object.
That's all for now, soon a class to show a map with many markers and info windows.

Thursday, October 8, 2009

Tables using FPDF Library

Hi!
This time we'll speak about the exportation of tables in pdf format. I needed a way to print some dockets regarding persons data. This dockets have to be printed using special paper, that is that paper which already have the dockets ready and cut. So the goal of my work was to found a way to generate a table with perfectly equal cells of a given width and height. These cells must contain some text, which is not fixed, but may contain different informations:
Surname - name, Company (optional), Address (may stay on 2 lines), CAP City.
Why didn't I use html and the javascript print function to make it work? Clear, because I was glad to study something new and because printing to a pdf file is a more professional way to solve this problem.
One difficulty was represented by the variability of the text, so I assumed that each cell could have a maximum of 5 lines:
- surname and name
- company (optional)
- address 1 line
- address 2 line (if needed)
- cap and city
So there can be dockets containing text between 3 an 5 lines.
I decided to use the fpdf library which is free of course. You may download it from here (I used the last version 1.6).
Now this library has many useful functions but I extended it in order to get my desired table.
So here is my class and below how to instanciate and use it. Good reading.
<?php
/*
 * CLASS TblFPDF
 * 08/09/2009
 * written by abidibo <abidibo@gmail.com>
 * Copyright: FUCK COPYRIGHT, NO LICENCE TAKE THIS CODE AND DO WHAT YOU WANT
 *
 * Description
 *     This class allows to create a pdf table made by cells all of the same dimensions
 *     (width and height) and containing some text. This text must follow some rules, as
 *     this class was written in order to print some dockets, and dockets hava a fixed
 *     structure. More informations later.
 *
 * Parameters of __construct function
 *      (int) $nRows : number or rows for document page
 *      (int) $nCols : number of columns
 *      (float) $cellWidth : width of every single cell in mm
 *      (float) $cellHeight : height of every single cell in mm
 *      (mix) $cellBorder : cell border property
 *               0 -> no border
 *               1 -> border
 *               string containing one ore more characters:
 *               L -> left border
 *               T -> top border
 *               R -> right border
 *               B -> bottom border
 *
 * Parameters of render function
 *     (array) $textArray : an array where each element represent the string
 *                          to insert in a cell. Break lines represented by \n
 *     (int) $cellLines : the maximum number of lines inside a cell.
 *
 *     The $textArray[$i] text foreach $i has to stay inside the cell, that is has to have
 *     a maximum of ($cellLines-1) break lines or less if the text between two
 *     break lines is so long that can't stay in a single line of width $cellWidth.
 *     That's a very important condition!
 *
 */

// include fpdf library and set the constant it used to charge font folder
// font folder is given with the library
define('FPDF_FONTPATH',dirname(__FILE__).'/font/');
require('fpdf.php');

class TblFPDF extends FPDF {
    function __construct($nRows, $nCols, $cellWidth, $cellHeight, $cellBorder) {
 
        parent::__construct();
        $this->_nrows = $nRows;           // rows for page
        $this->_ncols = $nCols;           // columns
        $this->_cellw = $cellWidth;
        $this->_cellh = $cellHeight;
        $this->_cellb = $cellBorder;
    }

    public function render($textArray, $cellLines) {

        $count = 0;
        reset($textArray);
        $totCell = count($textArray);
        $totPages = ceil($totCell/($this->_nrows*$this->_ncols));
      
        for($p=0;$p<$totPages;$p++) {
            $this->AddPage();
            for($r=0;$r<$this->_nrows;$r++) {
                for($c=0;$c<$this->_ncols;$c++) {
                    if($count>=$totCell) break;       // all cells has been already printed
                    $this->setXY($c*$this->_cellw, $r*$this->_cellh);    // set x and y position of the top left corner of the new cell
                    $this->renderCell(current($textArray), $cellLines);
                    next($textArray);
                    $count++;
                }
            }
        }
    }

    private function renderCell($text, $cellLines) {

        $totLines = 0;
        $text_lines = explode("\n", $text);
        for($i=0;$i<count($text_lines);$i++) {
            $totLines++;
            if($this->getStringWidth($text_lines[$i])>=$this->_cellw-2.6) $totLines++;
        }
        if($totLines<$cellLines) $text .= "\n";
        for($i=0;$i<($cellLines-$totLines);$i++) $text .= "\n";

        $this->MultiCell($this->_cellw, ($this->_cellh/$cellLines), $text, $this->_cellb, "L");
    }
}
?>

So this was the class I used. It's well commented, but let's see how to use it

<?php

require_once(class.TblFPDF.php);

$textArray = array(
                          0=>"Name Surname\nCompany\nAddress\nCAP City",
                          1=>"Gino Pinotto\nTalisker spa\n5th 23/q\n10384 New York"
                      );
$pdfDoc = new TblFPDF(8,3,70,37,1);
$pdfDoc->render($textArray);
$pdfDoc->Output();
exit();

?>

In this example the output will be a pdf document with two dockets drawned starting from the point (0,0) of the page, containing the informations included in $textArray. If many text elements are given the full page will have 8 rows for 3 columns, each cell will have a width of 7 cm and an height of 3.7 cm, with a black border. The text inside the cell must occupy not more than 5 lines, that's the only recomendation.
Hope is useful.
Bye!
PS. Yesterday Italy's top court stripped Mr. Berlusconi of his legal immnuity (I gloat for this), hoping justice may be done I'll have a great party!

Thursday, September 10, 2009

MoocanvasShadow reviewed - shadows for everyone

Hi all,
well, this time I'd like to write about a nice class implemented using mootools libraries used to paint shadows around blocks. His name is mooCanvasShadow, written by Arian Stolwijk, and here the link to the project. This class uses the mooCanvas class of ibolmo's.
When I used this class for some projects I encountered some difficulties and some strange behavioures, so I tried to reniew this class which was shared under the MIT license. There was a problem when applying a shadow to a container div which was centered in the page when resizing the browser window. The problem was that while resizing the browser window the container centered is moved in order to keep the centered position, but the canvas area painted in order to get the shadow was static and positioned absolute relative to the body, so couldn't follow the container in motion. The solution is to paint the canvas area relative to the container we want to apply shadows to (changes made to the method createCanvasDiv). This way if it moves than the canvas follows it.
Another adjustment was made in a regular expression matching colors, it didn't consider not capital letters and so always put the default color if you write colors codes this way: #ffffff (like me).
Above the major things, now the code:

//AbiCanvasShadow, Mootools Canvas Dropshadow Abidibo extended
/*
Script: CanvasShadow.js
    Contains the Canvas class.
Dependencies:
    MooTools, <http: mootools.net="">
        Element, and its dependencies
        Element.Dimensions
    MooCanvas, <http: ibolmo.com="" moocanvas="" projects="">
        Canvas,
        Paths
Author:
    Arian Stolwijk, <http: www.aryweb.nl="">
Adjusted by
    Abidibo, <http: abidibo.otto.to.it="">
License:
    MIT License, <http: en.wikipedia.org="" mit_license="" wiki="">
*/

var AbiCanvasShadow = new Class({
    
    /**
     * This function creates a new canvas element of MooCanvas
     * The elements will be placed at the right position, so the
     * canvas element is right behind the to-shadow element
     *
     * @param {Object} shadowDiv this div will get a shadow
     * @param {Object} options the options:
     *         size: The size of the shadow
     *         radius: the radius of the shadow corners
     *         opacity: The opacity of the shadow
     *         color: The shadow color, this should be like #FF9900,
     *             or an array with the rgb colors [255,0,255]
     *         overwrite: if the canvas element already exists,
     *             it wil dispose that element and create a new one
     */    
    initialize: function(shadowDiv,options){        
        // Set shadow div
        this.shadowDiv = shadowDiv;
        // Get the coordinates of the element
        this.position = shadowDiv.getCoordinates();
        // Set some options
        this.size = options.size;
        var radius = options.radius;
        var opacity = options.opacity;
        
        if($(shadowDiv.get('id')) == false){
            this.giveId(shadowDiv);
        }
        
        // Dispose the already existing element, if needed
        if ($type(options.overwrite) == 'boolean' &amp;&amp; options.overwrite == true) {
            if ($type($(shadowDiv.get('id') + '_mooShadow')) == 'element') {
                $(shadowDiv.get('id') + '_mooShadow').dispose();
            }
        }

        // Create a new Canvas object, of MooCanvas
        this.canvas = new Canvas({
            'width': this.position.width + (this.size * 2),
            'height': this.position.height + (this.size * 2),
            'id': shadowDiv.get('id')+'_mooShadow'
        });
        
        // Create a div and put the canvas element in it to set it at the right position
        this.createCanvasDiv();

        // Create the context
        var ctx = this.canvas.getContext("2d");
        
        // Create the shadow color
        if(options.color.test(/#[0-9A-Za-z]{6}|[0-9A-Za-z]{3}/)){
            options.color = options.color.hexToRgb(true);
        }
        if($type(options.color) == 'array' &amp;&amp; $type(options.color[2]) != 'undefined'){
            var color = options.color[0]+','+options.color[1]+','+options.color[2];    
        }else{
            var color = '0,0,0';
        }

        // Create the retangles/ the actual shadow
        for (var i = this.size; i &gt;= 0; i--) {
            this.roundedRect(ctx,
                i,
                i,
                (this.position.width-(i*2)+2*this.size),
                (this.position.height-(2*i)+2*this.size),
                radius
            );
            ctx.fillStyle = "rgba("+color+", "+opacity/(this.size-i)+")";
            ctx.fill();
        }

    },
    
    /**
     * This function creates a div where the canvas element is in inserted
     * This element will position behind the to-shadow div
     */
    createCanvasDiv: function(){
        this.canvasDiv = new Element('div',{
            styles:{
                'width': this.position.width+(this.size*2),
                'height': this.position.height+(this.size*2),
                'z-index': -1,
                'position': 'absolute',
                'left': '-'+this.size+'px',
                'top':  '-'+this.size+'px'
            }
        }).adopt(this.canvas).inject(this.shadowDiv);
    },
    
    /**
     * This method creates a rounded rectangle
     * @param {Object} ctx the canvas context
     * @param {Object} x the upper left x-axis position
     * @param {Object} y the upper left y-axis positoin
     * @param {Object} width the retangle width
     * @param {Object} height the retangle height
     * @param {Object} radius the corner radius
     */
    roundedRect: function (ctx,x,y,width,height,radius){

        ctx.beginPath();
        ctx.moveTo(x,y+radius);
        ctx.lineTo(x,y+height-radius);
        ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
        ctx.lineTo(x+width-radius,y+height);
        ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
        ctx.lineTo(x+width,y+radius);
        ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
        ctx.lineTo(x+radius,y);
        ctx.quadraticCurveTo(x,y,x,y+radius);
    },
    
    /**
     * This method set an random ID to an element
     * @param {Object} div
     */
    giveId: function(div){
        div.set('id',$rand()+'_mooShadow');
    }
    
});

/**
 *
 * @param {Object} options: the same options as the mooCanvasShadow() class
 */
Element.implement({
    AbiCanvasShadow: function (options){
        new AbiCanvasShadow(this,options);        
        return this;
    }
});

and how to use it:

window.addEvent('domready',function(){
    // 'mainContainer' is the ID of the element
    $('mainContainer').AbiCanvasShadow({
        opacity: 0.2,
        size: 14,
        radius: 10,
        color: '#333333'
    });
}.delay(800));

The html structure must be this way:

<div id="mainContainer">
     <div id="container">   
     your site, your content
     </div>
</div>

that because of... guess it?   IE. Si seƱores, another self behaviour for IE7 and IE6 and I don't know if IE8 too. Whit these horrible browsers using only one container causes the below canvas area to affect the background of the element even if a color was setted. So some points:
  • Use two container and apply the effect to the outer
  • Very important: set the position property of mainContainer as relative, that because the canvas is positioned absolute relative to it
  • Set the width of 'mainContainer' equal to the width of 'container'
  • Set a background color for 'container'
  • Pay attention if using padding or margins, that is properties affecting the position of the element 
Last thing. You may see that I've setted a delay of 800 ms to the function called at the event domready. This value may be 0 or higher, it depends. It is useful because with FF there's a different behaviour in applyng css styles when recharging a page or get it through a link. When recharging a page the css seems to be applied a bit later, that causes for example that an horizontal menu may be displayed for a while in his "<ul><li>" form (vertically), and after rendered in horizontal, saving space. But if the coordinates of the element are taken in the instant the menu is higher then the canvas is painted with these values. After the menu becomes horizontal, saves vertical space and then the dimension of the element and his shadows are different. Using this delay this problem is prevented. Another way could be to call the function not at the event 'domready' but at the event onload, that is:
window.onload = function ()...
but in this case the interval between the appearence of the element and its shadow is grater, especially if the element contains iframe or heavy elements.
That's all guys, hasta la proxima!

Wednesday, September 2, 2009

Ext.Window max height

Ext is an amazing javascript library. It let you do things humans can never imagine. It's enough to have a look at the samples page to understand what I'm saying. Moreover ext provides a complete UI structure that let you have beautiful windows, forms, tables... with no work, that is, ext provides the css and images necessary to the UI.
I discovered ext not so early, that's why really I don't use it as my favourite library (which still is mootools) but only to do extraordinary things.
In my last works I come to a problem. I wanted to open windows (like normal desktop windows) when opening a product details in a catalogue. All works properly, but what happens if details content is extremly long? Clearly the height of the window follow it.
That's a problem I think, because ext.window class doesn't support any configuration paramether or method to set a max-height attribute! That is your window will follow the height of his content with no way to stop that.
Here clearly is helpful to find a way to pass by this lackage and set a max Height attribute, that is what I've done for my work and here I post my code.

Here is my class which extends Ext.Window

Ext.productWindow = Ext.extend(Ext.Window, {
        homefile: 'index.php',  // not important for you
        maxHeight: 9999,
        open: function() {
             // not important for you, is an ajax call that makes an icon appear in the bottom bar of the application
            sendPost('index.php?pt[catalogue-viewedItems]', 'id='+this.prdID, 'viewedItems'); 
            this.show();
        },
        minimize: function(window) {
            this.hide();
        },
        listeners: {
            close: function(window)    {
                // not important for you, is an ajax call that make disappear the icon in the bottom bar
                sendPost('index.php?pt[catalogue-removeProduct]', 'id='+this.prdID, 'scriptPaper', '', true);
            }
        },
         // that's the method  I use: if the height passed is greater than the maxHeight property
        // than the window is resized by the setHeight function.
        setMaxHeight: function(h) {
            if(h>this.maxHeight) this.setHeight(this.maxHeight);
        }
       
    })

That was the class implementation, now I use a function to construct a window object, here it is:

function newWindow(homefile, id) {

    var url = homefile+'?pt[catalogue-viewProductDataAjax]&id='+id;

    win[id] = new Ext.productWindow({
        homefile: homefile,
        prdID: id,
        width:800,
        maxHeight:500,  // here I set the maxHeight value for this instance of productWindow
        y:50,
        id: 'prdWindow'+id,
        autoScroll:true,
        autoLoad:{
            // when loading the windo content....
            // first: I load the response to the request represented by url
            url:url,
            // second: after charging contents, I call the method setMaxHeight passing it the height of the contents of the window,
            // then this method sets the height of the window to the maxHeight property(500)
            // if the contents hight is greater, let the height as it is if minor
            callback: function(el) {prd_window.setMaxHeight(el.getHeight());}
        },
        title:'productCard',
        collapsible:true,
        minimizable:true,
        resizable:true
     });

    return win[id];

}

Finally to create a window:
prd_window = newWindow(homefile, id);

This way you have your maxHeight setable window. Only some more things:

    * the product window is constructed and associated to an array element, that because you may have more windows open at once.
    * for the same reason before creating a new window there is a check (that i've omitted here) to control if the window represented by its id already exists, if yes than only is showed, otherwise it's created.

The really first (I hope)

Well, hi all!
Starting from now Abidiblog changes a lot his purposals, his language, and many other things...
All previous posts (all in italian language) may be founded here:
http://abidibo.otto.to.it
under the abidiblog section.
Now what happens? Simply I began studying more deeply some php concepts, some js libraries and so on, and I think time has come to share some informations to a larger public, and in a more professional way.
So I decided to re-start my blog, not using my site and CMS to save time in writing articles and to center the entire stuff on Informatic, excluding all other things like maps, personal informations, services, news, calendars and many other things you may always find at the previous address. For the same reason I decided to write in English, so my first posts and not only the firsts' will be full of errors because my english was a bit supressed by spanish, but that may be even a good way to refresh it properly.
Here all the explaination, from the next posts only usefull (I hope) stuff.