[qet] [1405] Began implementing a WYSIWYG title block template editor.

[ Thread Index | Date Index | More lists.tuxfamily.org/qet Archives ]


Revision: 1405
Author:   xavier
Date:     2011-12-25 18:45:39 +0100 (Sun, 25 Dec 2011)
Log Message:
-----------
Began implementing a WYSIWYG title block template editor.

Modified Paths:
--------------
    branches/0.3/qelectrotech.pro
    branches/0.3/sources/qetapp.cpp
    branches/0.3/sources/titleblockcell.h
    branches/0.3/sources/titleblocktemplate.cpp
    branches/0.3/sources/titleblocktemplate.h

Added Paths:
-----------
    branches/0.3/sources/titleblock/
    branches/0.3/sources/titleblock/dimension.cpp
    branches/0.3/sources/titleblock/dimension.h
    branches/0.3/sources/titleblock/dimensionwidget.cpp
    branches/0.3/sources/titleblock/dimensionwidget.h
    branches/0.3/sources/titleblock/gridlayoutanimation.cpp
    branches/0.3/sources/titleblock/gridlayoutanimation.h
    branches/0.3/sources/titleblock/helpercell.cpp
    branches/0.3/sources/titleblock/helpercell.h
    branches/0.3/sources/titleblock/qettemplateeditor.cpp
    branches/0.3/sources/titleblock/qettemplateeditor.h
    branches/0.3/sources/titleblock/splittedhelpercell.cpp
    branches/0.3/sources/titleblock/splittedhelpercell.h
    branches/0.3/sources/titleblock/templatecellsset.cpp
    branches/0.3/sources/titleblock/templatecellsset.h
    branches/0.3/sources/titleblock/templatecellwidget.cpp
    branches/0.3/sources/titleblock/templatecellwidget.h
    branches/0.3/sources/titleblock/templatecommands.cpp
    branches/0.3/sources/titleblock/templatecommands.h
    branches/0.3/sources/titleblock/templatelogomanager.cpp
    branches/0.3/sources/titleblock/templatelogomanager.h
    branches/0.3/sources/titleblock/templateview.cpp
    branches/0.3/sources/titleblock/templateview.h
    branches/0.3/sources/titleblock/templatevisualcell.cpp
    branches/0.3/sources/titleblock/templatevisualcell.h
    branches/0.3/sources/titleblockcell.cpp

Modified: branches/0.3/qelectrotech.pro
===================================================================
--- branches/0.3/qelectrotech.pro	2011-12-24 18:37:00 UTC (rev 1404)
+++ branches/0.3/qelectrotech.pro	2011-12-25 17:45:39 UTC (rev 1405)
@@ -54,11 +54,11 @@
 
 TEMPLATE = app
 DEPENDPATH += .
-INCLUDEPATH += sources sources/editor
+INCLUDEPATH += sources sources/editor sources/titleblock
 
 # Fichiers sources
-HEADERS += sources/*.h   sources/editor/*.h
-SOURCES += sources/*.cpp sources/editor/*.cpp
+HEADERS += sources/*.h   sources/editor/*.h   sources/titleblock/*.h
+SOURCES += sources/*.cpp sources/editor/*.cpp sources/titleblock/*.cpp
 
 # Liste des fichiers qui seront incorpores au binaire en tant que ressources Qt
 RESOURCES += qelectrotech.qrc

Modified: branches/0.3/sources/qetapp.cpp
===================================================================
--- branches/0.3/sources/qetapp.cpp	2011-12-24 18:37:00 UTC (rev 1404)
+++ branches/0.3/sources/qetapp.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -25,6 +25,7 @@
 #include "fileelementscollection.h"
 #include "titleblocktemplate.h"
 #include "templateeditor.h"
+#include "qettemplateeditor.h"
 #include "qetproject.h"
 #include "qtextorientationspinboxwidget.h"
 #include "recentfiles.h"
@@ -862,13 +863,9 @@
 	launched for a template creation.
 */
 void QETApp::openTitleBlockTemplate(QETProject *project, const QString &template_name) {
-	TemplateEditor *editor = new TemplateEditor();
-	bool can_edit = editor -> edit(project, template_name);
-	if (can_edit) {
-		editor -> showNormal();
-	} else {
-		QMessageBox::warning(0, tr("Erreur"), tr("Impossible d'\351diter le template demand\351"));
-	}
+	QETTitleBlockTemplateEditor *qet_template_editor = new QETTitleBlockTemplateEditor();
+	qet_template_editor -> edit(project, template_name);
+	qet_template_editor -> showMaximized();
 }
 
 /**

Added: branches/0.3/sources/titleblock/dimension.cpp
===================================================================
--- branches/0.3/sources/titleblock/dimension.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/dimension.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,58 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "dimension.h"
+
+/**
+	Constructor
+	@param v Numeric value for this dimension
+	@param t Kind of length, determining how to interpret the numeric value
+*/
+TitleBlockDimension::TitleBlockDimension(int v, QET::TitleBlockColumnLength t) :
+	type(t),
+	value(v)
+{
+}
+
+/**
+	@return a string describing this dimension in a human-readable format.
+*/
+QString TitleBlockDimension::toString() const {
+	QString dim_str;
+	if (type == QET::Absolute) {
+		dim_str = QObject::tr("%1px", "titleblock: absolute width");
+	} else if (type == QET::RelativeToTotalLength) {
+		dim_str = QObject::tr("%1%", "titleblock: width relative to total length");
+	} else if (type == QET::RelativeToRemainingLength) {
+		dim_str = QObject::tr("%1% du restant", "titleblock: width relative to remaining length");
+	}
+	return(dim_str.arg(value));
+}
+
+/**
+	@return a string describing this dimension in a short format.
+*/
+QString TitleBlockDimension::toShortString() const {
+	QString short_string;
+	if (type == QET::RelativeToTotalLength) {
+		short_string = "t";
+	} else if (type == QET::RelativeToRemainingLength) {
+		short_string = "r";
+	}
+	short_string += QString("%1%2;").arg(value).arg(type == QET::Absolute ? "px" : "%");
+	return(short_string);
+}

Added: branches/0.3/sources/titleblock/dimension.h
===================================================================
--- branches/0.3/sources/titleblock/dimension.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/dimension.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,36 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_DIMENSION_H
+#define TITLEBLOCK_SLASH_DIMENSION_H
+#include "qet.h"
+
+/**
+	This struct is a simple container associating a length with its type.
+	@see TitleBlockColumnLength 
+*/
+struct TitleBlockDimension {
+	// constructor
+	TitleBlockDimension(int, QET::TitleBlockColumnLength = QET::Absolute);
+	// methods
+	QString toString() const;
+	QString toShortString() const;
+	// attribute
+	QET::TitleBlockColumnLength type; ///< Kind of length
+	int value;                        ///< Numeric value
+};
+#endif

Added: branches/0.3/sources/titleblock/dimensionwidget.cpp
===================================================================
--- branches/0.3/sources/titleblock/dimensionwidget.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/dimensionwidget.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,149 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "dimensionwidget.h"
+
+/**
+	Constructor
+	@param complete True for this dialog to show the radio buttons that allow
+	the user to specify whether the dimension is absolute, relative to the
+	total width or relative to the remaining width.
+	@param parent Parent QWidget
+*/
+TitleBlockDimensionWidget::TitleBlockDimensionWidget(bool complete, QWidget *parent) :
+	QDialog(parent),
+	complete_(complete)
+{
+	initWidgets();
+	initLayouts();
+}
+
+/**
+	Destructor
+*/
+TitleBlockDimensionWidget::~TitleBlockDimensionWidget() {
+}
+
+/**
+	@return true if this dialog shows the optional radio buttons
+*/
+bool TitleBlockDimensionWidget::isComplete() const {
+	return(complete_);
+}
+
+/**
+	@return a pointer to the label displayed right before the spinbox.
+	Useful to specify a custom text.
+*/
+QLabel *TitleBlockDimensionWidget::label() const {
+	return(spinbox_label_);
+}
+
+/**
+	@return a pointer to the spinbox
+	Useful to specify custom parameters, such as the minimum value
+*/
+QSpinBox *TitleBlockDimensionWidget::spinbox() const {
+	return(spinbox_);
+}
+
+/**
+	@return The dimension as currently shown by the dialog
+*/
+TitleBlockDimension TitleBlockDimensionWidget::value() const {
+	QET::TitleBlockColumnLength type = QET::Absolute;
+	if (complete_) {
+		type = static_cast<QET::TitleBlockColumnLength>(dimension_type_ -> checkedId());
+	}
+	return(TitleBlockDimension(spinbox_ -> value(), type));
+}
+
+/**
+	@param dim Dimension to be displayed and edited by this dialog
+*/
+void TitleBlockDimensionWidget::setValue(const TitleBlockDimension &dim) {
+	spinbox_ -> setValue(dim.value);
+	if (complete_) {
+		if (QAbstractButton *button = dimension_type_ -> button(dim.type)) {
+			button -> setChecked(true);
+		}
+	}
+	updateSpinBoxSuffix();
+}
+
+/**
+	Initialize the widgets composing the dialog.
+*/
+void TitleBlockDimensionWidget::initWidgets() {
+	// basic widgets: label + spinbox
+	spinbox_label_ = new QLabel(tr("Largeur :", "default dialog label"));
+	
+	spinbox_ = new QSpinBox();
+	spinbox_ -> setMinimum(5);
+	spinbox_ -> setMaximum(10000);
+	spinbox_ -> setValue(50);
+	
+	// extra widgets, for the user to specify whether the value is absolute, relative, etc.
+	if (complete_) {
+		absolute_button_  = new QRadioButton(tr("Absolu"));
+		relative_button_  = new QRadioButton(tr("Relatif au total"));
+		remaining_button_ = new QRadioButton(tr("Relatif au restant"));
+		dimension_type_   = new QButtonGroup(this);
+		dimension_type_ -> addButton(absolute_button_,  QET::Absolute);
+		dimension_type_ -> addButton(relative_button_,  QET::RelativeToTotalLength);
+		dimension_type_ -> addButton(remaining_button_, QET::RelativeToRemainingLength);
+		absolute_button_ -> setChecked(true);
+		connect(dimension_type_, SIGNAL(buttonClicked(int)), this, SLOT(updateSpinBoxSuffix()));
+	}
+	
+	updateSpinBoxSuffix();
+	
+	// buttons, for the user to validate its input
+	buttons_ = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+	connect(buttons_, SIGNAL(accepted()), this, SLOT(accept()));
+	connect(buttons_, SIGNAL(rejected()), this, SLOT(reject()));
+}
+
+/**
+	Initialize the layout of the dialog.
+*/
+void TitleBlockDimensionWidget::initLayouts() {
+	QHBoxLayout *hlayout0 = new QHBoxLayout();
+	hlayout0 -> addWidget(spinbox_label_);
+	hlayout0 -> addWidget(spinbox_);
+	QVBoxLayout *vlayout0 = new QVBoxLayout();
+	vlayout0 -> addLayout(hlayout0);
+	if (complete_) {
+		vlayout0 -> addWidget(absolute_button_);
+		vlayout0 -> addWidget(relative_button_);
+		vlayout0 -> addWidget(remaining_button_);
+	}
+	vlayout0 -> addWidget(buttons_);
+	setLayout(vlayout0);
+}
+
+/**
+	Ensure the suffix displayed by the spinbox matches the selected kind of length.
+*/
+void TitleBlockDimensionWidget::updateSpinBoxSuffix() {
+	if (complete_ && dimension_type_ -> checkedId() != QET::Absolute) {
+		spinbox_ -> setSuffix(tr("%", "spinbox suffix when changing the dimension of a row/column"));
+	} else {
+		spinbox_ -> setSuffix(tr("px", "spinbox suffix when changing the dimension of a row/column"));
+	}
+	spinbox_ -> selectAll();
+}

Added: branches/0.3/sources/titleblock/dimensionwidget.h
===================================================================
--- branches/0.3/sources/titleblock/dimensionwidget.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/dimensionwidget.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,62 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_DIMENSION_WIDGET_H
+#define TITLEBLOCK_SLASH_DIMENSION_WIDGET_H
+#include <QtGui>
+#include "dimension.h"
+
+/**
+	This class represents a dialog for the user to input a dimension: a row
+	height, a column width, etc.
+*/
+class TitleBlockDimensionWidget : public QDialog {
+	Q_OBJECT
+	
+	// constructors, destructor
+	public:
+	TitleBlockDimensionWidget(bool, QWidget * parent = 0);
+	virtual ~TitleBlockDimensionWidget();
+	private:
+	TitleBlockDimensionWidget(const TitleBlockDimensionWidget &);
+	
+	// methods
+	public:
+	bool isComplete() const;
+	QLabel *label() const;
+	QSpinBox *spinbox() const;
+	TitleBlockDimension value() const;
+	void setValue(const TitleBlockDimension &);
+	private:
+	void initWidgets();
+	void initLayouts();
+	
+	private slots:
+	void updateSpinBoxSuffix();
+	
+	// attributes
+	private:
+	bool complete_;                    ///< Whether or not this dialog is required to be complete, i.e. displaying also
+	QSpinBox *spinbox_;                ///< Spinbox displaying the length
+	QLabel *spinbox_label_;            ///< Label shown right before the spinbox
+	QRadioButton *absolute_button_;    ///< Radio button to indicate the length is absolute
+	QRadioButton *relative_button_;    ///< Radio button to indicate the length is relative to the total length
+	QRadioButton *remaining_button_;   ///< Radio button to indicate the length is relative to the remaining length
+	QButtonGroup *dimension_type_;     ///< QButtonGroup for the three radio buttons
+	QDialogButtonBox *buttons_;        ///< Buttons to validate the dialog
+};
+#endif

Added: branches/0.3/sources/titleblock/gridlayoutanimation.cpp
===================================================================
--- branches/0.3/sources/titleblock/gridlayoutanimation.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/gridlayoutanimation.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,90 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "gridlayoutanimation.h"
+
+/**
+	Constructor
+	@param grid Grid to be animated
+	@param parent Parent QObject
+*/
+GridLayoutAnimation::GridLayoutAnimation(QGraphicsGridLayout *grid, QObject *parent) :
+	QVariantAnimation(parent),
+	grid_(grid)
+{
+}
+
+/**
+	Destructor
+*/
+GridLayoutAnimation::~GridLayoutAnimation() {
+}
+
+/**
+	@return the animated grid
+*/
+QGraphicsGridLayout *GridLayoutAnimation::grid() {
+	return(grid_);
+}
+
+/**
+	@param grid The grid to be animated
+*/
+void GridLayoutAnimation::setGrid(QGraphicsGridLayout *grid) {
+	grid_ = grid;
+}
+
+/**
+	@return the index of the row/column to be animated
+*/
+int GridLayoutAnimation::index() const {
+	return(index_);
+}
+
+/**
+	@param index the index of the row/column to be animated
+*/
+void GridLayoutAnimation::setIndex(int index) {
+	index_ = index;
+}
+
+/**
+	@return true if this object acts on a row, false if it acts on a column.
+*/
+bool GridLayoutAnimation::actsOnRows() const {
+	return(row_);
+}
+
+/**
+	@param acts_on_row true for this object to act on a row, false for it to
+	act on a column.
+*/
+void GridLayoutAnimation::setActsOnRows(bool acts_on_row) {
+	row_ = acts_on_row;
+}
+
+/**
+	Implementation of QVariantAnimation::updateCurrentValue().
+*/
+void GridLayoutAnimation::updateCurrentValue(const QVariant &value) {
+	if (!grid_) return;
+	if (row_) {
+		grid_ -> setRowFixedHeight(index_, value.toReal());
+	} else {
+		grid_ -> setColumnFixedWidth(index_, value.toReal());
+	}
+}

Added: branches/0.3/sources/titleblock/gridlayoutanimation.h
===================================================================
--- branches/0.3/sources/titleblock/gridlayoutanimation.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/gridlayoutanimation.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,50 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_GRID_LAYOUT_ANIMATION_H
+#define TITLEBLOCK_SLASH_GRID_LAYOUT_ANIMATION_H
+#include <QtGui>
+
+/**
+	This class allows animating a dimension change for a QGraphicsGridLayout
+	row or column.
+*/
+class GridLayoutAnimation : public QVariantAnimation {
+	// Constructors, destructor
+	public:
+	GridLayoutAnimation(QGraphicsGridLayout * = 0, QObject * = 0);
+	virtual ~GridLayoutAnimation();
+	
+	// methods
+	public:
+	QGraphicsGridLayout *grid();
+	void setGrid(QGraphicsGridLayout *);
+	int index() const;
+	void setIndex(int);
+	bool actsOnRows() const;
+	void setActsOnRows(bool);
+	
+	protected:
+	void updateCurrentValue(const QVariant &);
+	
+	// attributes
+	private:
+	QGraphicsGridLayout *grid_; ///< Grid this class will animate
+	bool row_;                  ///< Whether we should animate a row or a column
+	int index_;                 ///< Index of the row/column to be animated
+};
+#endif

Added: branches/0.3/sources/titleblock/helpercell.cpp
===================================================================
--- branches/0.3/sources/titleblock/helpercell.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/helpercell.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,142 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "helpercell.h"
+
+/**
+	Constructor
+	@param parent Parent QGraphicsItem
+*/
+HelperCell::HelperCell(QGraphicsItem *parent) :
+	QGraphicsObject(parent),
+	QGraphicsLayoutItem(),
+	background_color(Qt::white),
+	foreground_color(Qt::black),
+	label(),
+	orientation(Qt::Horizontal),
+	index(-1)
+{
+	setGraphicsItem(this);
+	setFlag(QGraphicsItem::ItemIsSelectable, false);
+}
+
+/**
+	Destructor
+*/
+HelperCell::~HelperCell() {
+}
+
+/**
+	Ensure geometry changes are handled for both QGraphicsObject and
+	QGraphicsLayoutItem.
+	@param g New geometry
+*/
+void HelperCell::setGeometry(const QRectF &g) {
+	prepareGeometryChange();
+	QGraphicsLayoutItem::setGeometry(g);
+	setPos(g.topLeft());
+}
+
+/**
+	@param which Size hint to be modified
+	@param constraint New value for the size hint
+	@return the size hint for \a which using the width or height of \a constraint
+*/
+QSizeF HelperCell::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const {
+	Q_UNUSED(which);
+	return(constraint);
+}
+
+/**
+	@return the bounding rect of this helper cell
+*/
+QRectF HelperCell::boundingRect() const {
+	return QRectF(QPointF(0,0), geometry().size());
+}
+
+/**
+	Handles the helper cell visual rendering
+	@param painter QPainter to be used for the rendering
+	@param option Rendering options
+	@param widget QWidget being painted, if any
+*/
+void HelperCell::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+	Q_UNUSED(option);
+	Q_UNUSED(widget);
+	
+	QRectF drawing_rectangle(QPointF(0, 0), geometry().size());
+	
+	painter -> setPen(Qt::black);
+	painter -> setBrush(background_color);
+	painter -> drawRect(drawing_rectangle);
+	
+	painter -> setPen(foreground_color);
+	painter -> drawText(drawing_rectangle, Qt::AlignHCenter | Qt::AlignVCenter, label);
+}
+
+/**
+	@param type new type of this helper cell -- @see QET::TitleBlockColumnLength
+*/
+void HelperCell::setType(QET::TitleBlockColumnLength type) {
+	if (type == QET::Absolute) {
+		background_color = QColor("#C0FFFF");
+		foreground_color = Qt::black;
+	} else if (type == QET::RelativeToTotalLength) {
+		background_color = QColor("#FFA858");
+		foreground_color = Qt::black;
+	} else if (type == QET::RelativeToRemainingLength) {
+		background_color = QColor("#FFC0C0");
+		foreground_color = Qt::black;
+	}
+}
+
+/**
+	Set the list of actions displayed by the context menu of this helper cell.
+*/
+void HelperCell::setActions(const QList<QAction *> &actions) {
+	actions_ = actions;
+}
+
+/**
+	@return the list of actions displayed by the context menu of this helper cell.
+*/
+QList<QAction *> HelperCell::actions() const {
+	return actions_;
+}
+
+/**
+	Handle context menu events.
+	@param event Context menu event.
+*/
+void HelperCell::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
+	if (actions_.isEmpty()) return;
+	
+	QMenu context_menu;
+	foreach (QAction *action, actions_) {
+		context_menu.addAction(action);
+	}
+	emit(contextMenuTriggered(this));
+	context_menu.exec(event -> screenPos());
+}
+
+/**
+	Handle double click events.
+*/
+void HelperCell::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) {
+	
+	emit(doubleClicked(this));
+}

Added: branches/0.3/sources/titleblock/helpercell.h
===================================================================
--- branches/0.3/sources/titleblock/helpercell.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/helpercell.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,67 @@
+/*
+    Copyright 2006-2011 Xavier Guerrin
+    This file is part of QElectroTech.
+    
+    QElectroTech is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 2 of the License, or
+    (at your option) any later version.
+    
+    QElectroTech is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+    
+    You should have received a copy of the GNU General Public License
+    along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_HELPER_CELL_H
+#define TITLEBLOCK_SLASH_HELPER_CELL_H
+#include <QtGui>
+#include "qet.h"
+
+/**
+	This class implements a helper widget for cells that indicate the length of
+	columns and rows.
+*/
+class HelperCell : public QGraphicsObject, public QGraphicsLayoutItem {
+	Q_OBJECT
+	Q_INTERFACES(QGraphicsLayoutItem)
+	
+	// constructor, destructor
+	public:
+	HelperCell(QGraphicsItem * = 0);
+	virtual ~HelperCell();
+	private:
+	HelperCell(const HelperCell &);
+	
+	// attributes
+	public:
+	QColor background_color;     ///< Background color when rendering this cell
+	QColor foreground_color;     ///< Text color when rendering this cell
+	QString label;               ///< Label displayed in this cell
+	Qt::Orientation orientation; ///< Orientation of this cell
+	int index;                   ///< Index of this cell
+	
+	// methods
+	public:
+	virtual void setGeometry(const QRectF &);
+	virtual QSizeF sizeHint(Qt::SizeHint, const QSizeF & = QSizeF()) const;
+	virtual QRectF boundingRect() const;
+	void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0);
+	virtual void setType(QET::TitleBlockColumnLength);
+	virtual void setActions(const QList<QAction *> &);
+	virtual QList<QAction *> actions() const;
+	
+	protected:
+	void contextMenuEvent(QGraphicsSceneContextMenuEvent *);
+	void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *);
+	
+	signals:
+	void contextMenuTriggered(HelperCell *);
+	void doubleClicked(HelperCell *);
+	
+	private:
+	QList<QAction *> actions_; ///< List of actions displayed by the context menu
+};
+#endif

Added: branches/0.3/sources/titleblock/qettemplateeditor.cpp
===================================================================
--- branches/0.3/sources/titleblock/qettemplateeditor.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/qettemplateeditor.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,295 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "qettemplateeditor.h"
+#include "qeticons.h"
+#include "qetapp.h"
+#include "qetproject.h"
+#include "templatecellwidget.h"
+#include "templatecommands.h"
+#include "templateview.h"
+#include "templatelogomanager.h"
+#include "templatecellwidget.h"
+
+/**
+	@param parent parent QWidget of this window
+*/
+QETTitleBlockTemplateEditor::QETTitleBlockTemplateEditor(QWidget *parent) :
+	QMainWindow(parent),
+	read_only(false),
+	parent_project_(0),
+	tb_template_(0),
+	logo_manager_(0)
+{
+	initWidgets();
+	initActions();
+	initMenus();
+}
+
+/**
+	Destructor
+*/
+QETTitleBlockTemplateEditor::~QETTitleBlockTemplateEditor() {
+}
+
+/**
+	Edit the given template.
+	@param project Parent project of the template to edit.
+	@param template_name Name of the template to edit within its parent project.
+	@return true if this editor was able to edit the given template, false otherwise
+*/
+bool QETTitleBlockTemplateEditor::edit(QETProject *project, const QString &template_name) {
+	// we require a project we will rattach templates to
+	if (!project) return(false);
+	
+	// the template name may be empty to create a new one
+	const TitleBlockTemplate *tb_template_orig;
+	if (template_name.isEmpty()) {
+		// loads the default title block template provided by the application
+		// it will be used as a start point to design the title block
+		tb_template_orig = QETApp::defaultTitleBlockTemplate();
+	} else {
+		tb_template_orig = project -> getTemplateByName(template_name);
+	}
+	
+	if (!tb_template_orig) {
+		/// TODO The TBT does not exist, manage error
+		return(false);
+	}
+	
+	tb_template_ = tb_template_orig -> clone();
+	parent_project_ = project;
+	template_name_ = template_name;
+	template_edition_area_view_ -> setTitleBlockTemplate(tb_template_);
+	template_cell_editor_widget_ -> updateLogosComboBox(tb_template_);
+	updateEditorTitle();
+	return(true);
+}
+
+/**
+	Launches the logo manager widget, which allows the user to manage the
+	logos embedded within the edited template.
+*/
+void QETTitleBlockTemplateEditor::editLogos() {
+	if (tb_template_) {
+		if (!logo_manager_) {
+			logo_manager_ = new TitleBlockTemplateLogoManager(tb_template_);
+		}
+		logo_manager_ -> show();
+		template_cell_editor_widget_ -> updateLogosComboBox(tb_template_);
+	}
+}
+
+/**
+	Initialize the various actions.
+*/
+void QETTitleBlockTemplateEditor::initActions() {
+	QETApp *qet_app = QETApp::instance();
+	
+	save_           = new QAction(QET::Icons::DocumentSave,         tr("&Enregistrer"), this);
+	quit_           = new QAction(QET::Icons::ApplicationExit,      tr("&Quitter"),     this);
+	configure_      = new QAction(QET::Icons::Configure,            tr("&Configurer QElectroTech"),                  this);
+	about_qet_      = new QAction(QET::Icons::QETLogo,              tr("\300 &propos de QElectroTech"),              this);
+	about_qt_       = new QAction(QET::Icons::QtLogo,               tr("\300 propos de &Qt"),                        this);
+	merge_cells_    = new QAction(tr("&Fusionner les cellules"), this);
+	split_cell_     = new QAction(tr("&S\351parer les cellules"), this);
+	
+	save_             -> setShortcut(QKeySequence::Save);
+	quit_             -> setShortcut(QKeySequence(tr("Ctrl+Q", "shortcut to quit")));
+	merge_cells_      -> setShortcut(QKeySequence(tr("Ctrl+K", "shortcut to merge cells")));
+	split_cell_       -> setShortcut(QKeySequence(tr("Ctrl+J", "shortcut to split merged cell")));
+	
+	configure_    -> setStatusTip(tr("Permet de r\351gler diff\351rents param\350tres de QElectroTech", "status bar tip"));
+	about_qet_    -> setStatusTip(tr("Affiche des informations sur QElectroTech",                       "status bar tip"));
+	about_qt_     -> setStatusTip(tr("Affiche des informations sur la biblioth\350que Qt",              "status bar tip"));
+	
+	connect(save_,            SIGNAL(triggered()), this,     SLOT(save()));
+	connect(quit_,            SIGNAL(triggered()), this,     SLOT(quit()));
+	connect(configure_,       SIGNAL(triggered()), qet_app,  SLOT(configureQET()));
+	connect(about_qet_,       SIGNAL(triggered()), qet_app,  SLOT(aboutQET()));
+	connect(about_qt_,        SIGNAL(triggered()), qet_app,  SLOT(aboutQt()));
+	connect(merge_cells_,     SIGNAL(triggered()), template_edition_area_view_, SLOT(mergeSelectedCells()));
+	connect(split_cell_,      SIGNAL(triggered()), template_edition_area_view_, SLOT(splitSelectedCell()));
+}
+
+/**
+	Initialize the various menus.
+*/
+void QETTitleBlockTemplateEditor::initMenus() {
+	file_menu_    = new QMenu(tr("&Fichier"),       this);
+	edit_menu_    = new QMenu(tr("&\311dition"),    this);
+	config_menu_  = new QMenu(tr("&Configuration"), this);
+	help_menu_    = new QMenu(tr("&Aide"),          this);
+	
+	file_menu_    -> setTearOffEnabled(true);
+	edit_menu_    -> setTearOffEnabled(true);
+	config_menu_  -> setTearOffEnabled(true);
+	help_menu_    -> setTearOffEnabled(true);
+	
+	file_menu_    -> addAction(save_);
+	file_menu_    -> addSeparator();
+	file_menu_    -> addAction(quit_);
+	
+	edit_menu_   -> addAction(merge_cells_);
+	edit_menu_   -> addAction(split_cell_);
+	
+	config_menu_ -> addAction(configure_);
+	
+	help_menu_ -> addAction(about_qet_);
+	help_menu_ -> addAction(about_qt_);
+	
+	menuBar() -> addMenu(file_menu_);
+	menuBar() -> addMenu(edit_menu_);
+	menuBar() -> addMenu(config_menu_);
+	menuBar() -> addMenu(help_menu_);
+}
+
+/**
+	Initialize layouts and widgets
+*/
+void QETTitleBlockTemplateEditor::initWidgets() {
+	// undo list on the right
+	undo_stack_ = new QUndoStack(this);
+	undo_view_ = new QUndoView(undo_stack_);
+	undo_view_ -> setEmptyLabel(tr("Aucune modification", "label displayed in the undo list when empty"));
+	
+	undo_dock_widget_ = new QDockWidget(tr("Annulations", "dock title"));
+	undo_dock_widget_ -> setFeatures(QDockWidget::AllDockWidgetFeatures);
+	undo_dock_widget_ -> setWidget(undo_view_);
+	undo_dock_widget_ -> setMinimumWidth(290);
+	addDockWidget(Qt::RightDockWidgetArea, undo_dock_widget_);
+	
+	// WYSIWYG editor as central widget
+	template_edition_area_scene_ = new QGraphicsScene(this);
+	template_edition_area_view_  = new TitleBlockTemplateView(template_edition_area_scene_); 
+	setCentralWidget(template_edition_area_view_);
+	
+	// cell edition widget at the bottom
+	template_cell_editor_widget_ = new TitleBlockTemplateCellWidget(tb_template_);
+	template_cell_editor_dock_widget_ = new QDockWidget(tr("Propri\351t\351s de la cellule", "dock title"), this);
+	template_cell_editor_dock_widget_ -> setFeatures(QDockWidget::AllDockWidgetFeatures);
+	template_cell_editor_dock_widget_ -> setWidget(template_cell_editor_widget_);
+	template_cell_editor_dock_widget_ -> setMinimumWidth(180);
+	template_cell_editor_dock_widget_ -> setMinimumHeight(250);
+	addDockWidget(Qt::BottomDockWidgetArea, template_cell_editor_dock_widget_);
+	template_cell_editor_widget_ -> setVisible(false);
+	
+	connect(
+		template_edition_area_view_,
+		SIGNAL(selectedCellsChanged(QList<TitleBlockCell *>)),
+		this,
+		SLOT(selectedCellsChanged(QList<TitleBlockCell *>))
+	);
+	connect(template_cell_editor_widget_, SIGNAL(logoEditionRequested()), this, SLOT(editLogos()));
+	connect(
+		template_cell_editor_widget_,
+		SIGNAL(cellModified(ModifyTitleBlockCellCommand *)),
+		this,
+		SLOT(pushCellUndoCommand(ModifyTitleBlockCellCommand *))
+	);
+	connect(
+		template_edition_area_view_,
+		SIGNAL(gridModificationRequested(TitleBlockTemplateCommand *)),
+		this,
+		SLOT(pushGridUndoCommand(TitleBlockTemplateCommand *))
+	);
+}
+
+/**
+	Update various things when user changes the selected cells.
+	@param selected_cells List of selected cells.
+*/
+void QETTitleBlockTemplateEditor::selectedCellsChanged(QList<TitleBlockCell *> selected_cells) {
+	if (selected_cells.count() == 1) {
+		template_cell_editor_widget_ -> edit(selected_cells.at(0));
+		template_cell_editor_widget_ -> setVisible(true);
+	} else {
+		template_cell_editor_widget_ -> setVisible(false);
+	}
+}
+
+/**
+	Configure an undo Command before adding it to the undo stack.
+	@param command to be added to the undo stack
+*/
+void QETTitleBlockTemplateEditor::pushCellUndoCommand(ModifyTitleBlockCellCommand *command) {
+	command -> setView(template_edition_area_view_);
+	pushUndoCommand(command);
+}
+
+/**
+	Add an undo Command to the undo stack.
+	@param command QUndoCommand to be added to the undo stack
+*/
+void QETTitleBlockTemplateEditor::pushGridUndoCommand(TitleBlockTemplateCommand *command) {
+	pushUndoCommand(command);
+}
+
+/**
+	Add an undo Command to the undo stack.
+	@param command QUndoCommand to be added to the undo stack
+*/
+void QETTitleBlockTemplateEditor::pushUndoCommand(QUndoCommand *command) {
+	undo_stack_ -> push(command);
+}
+
+/**
+	Set the title of this editor.
+*/
+void QETTitleBlockTemplateEditor::updateEditorTitle() {
+	QString min_title(
+		tr(
+			"QElectroTech - \311diteur de mod\350le de cartouche",
+			"titleblock template editor: base window title"
+		)
+	);
+	
+	QString title;
+	if (template_name_.isEmpty()) {
+		title = min_title;
+	} else {
+		title = QString(
+			tr(
+				"%1 - %2",
+				"window title: %1 is the base window title, %2 is a template name"
+			)
+		).arg(min_title).arg(template_name_);
+	}
+	setWindowTitle(title);
+}
+
+/**
+	Save the currently edited title block template back to its parent project.
+*/
+void QETTitleBlockTemplateEditor::save() {
+	QDomDocument doc;
+	QDomElement elmt = doc.createElement("root");
+	tb_template_ -> saveToXmlElement(elmt);
+	doc.appendChild(elmt);
+	
+	if (parent_project_ && !template_name_.isEmpty()) {
+		parent_project_ -> setTemplateXmlDescription(template_name_, elmt);
+	}
+}
+
+/**
+	Close the current editor.
+*/
+void QETTitleBlockTemplateEditor::quit() {
+	/// TODO save if needed
+	close();
+}

Added: branches/0.3/sources/titleblock/qettemplateeditor.h
===================================================================
--- branches/0.3/sources/titleblock/qettemplateeditor.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/qettemplateeditor.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,95 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_QET_TEMPLATE_EDITOR_H
+#define TITLEBLOCK_SLASH_QET_TEMPLATE_EDITOR_H
+#include <QtGui>
+#include "qet.h"
+#include "templateview.h"
+class ModifyTitleBlockCellCommand;
+class TitleBlockTemplateCommand;
+class TitleBlockTemplateCellWidget;
+class TitleBlockTemplateLogoManager;
+class QETProject;
+
+/**
+	This class implements the main window of QElectroTech's titleblock template
+	editor. This editor aims at allowing users to easily create their own title
+	block templates.
+*/
+class QETTitleBlockTemplateEditor : public QMainWindow {
+	Q_OBJECT
+	
+	// constructor, destructor
+	public:
+	QETTitleBlockTemplateEditor(QWidget * = 0);
+	virtual ~QETTitleBlockTemplateEditor();
+	private:
+	QETTitleBlockTemplateEditor(const QETTitleBlockTemplateEditor &);
+	
+	// attributes
+	private:
+	/// is the template read-only?
+	bool read_only;
+	/// menus TODO
+	QMenu *file_menu_, *edit_menu_,/* *paste_from_menu_, *display_menu_, *tools_menu_,*/ *config_menu_, *help_menu_;
+	/// actions
+	QAction *save_, *quit_, *configure_, *about_qt_, *about_qet_, *merge_cells_, *split_cell_;
+	/// Parent project of the currently edited template
+	QETProject *parent_project_;
+	/// Name of the currently edited template
+	QString template_name_;
+	/// Template Object edited
+	TitleBlockTemplate *tb_template_;
+	/// Template preview
+	QGraphicsScene *template_edition_area_scene_;
+	TitleBlockTemplateView *template_edition_area_view_;
+	/// Individual cell widget edition
+	QDockWidget *template_cell_editor_dock_widget_;
+	TitleBlockTemplateCellWidget *template_cell_editor_widget_;
+	/// Logo manager widget
+	TitleBlockTemplateLogoManager *logo_manager_;
+	/// Undo interface
+	QUndoStack *undo_stack_;
+	QUndoView *undo_view_;
+	QDockWidget *undo_dock_widget_;
+	
+	// methods
+	public:
+	
+	protected:
+	
+	private:
+	void initActions();
+	void initMenus();
+	void initWidgets();
+	
+	public slots:
+	void selectedCellsChanged(QList<TitleBlockCell *>);
+	bool edit(QETProject *, const QString &);
+	void editLogos();
+	
+	private slots:
+	void pushCellUndoCommand(ModifyTitleBlockCellCommand *);
+	void pushGridUndoCommand(TitleBlockTemplateCommand *);
+	void pushUndoCommand(QUndoCommand *);
+	void updateEditorTitle();
+	void save();
+	void quit();
+};
+
+#endif

Added: branches/0.3/sources/titleblock/splittedhelpercell.cpp
===================================================================
--- branches/0.3/sources/titleblock/splittedhelpercell.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/splittedhelpercell.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,63 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "splittedhelpercell.h"
+
+/**
+	Constructor
+	@param parent Parent QGraphicsItem
+*/
+SplittedHelperCell::SplittedHelperCell(QGraphicsItem *parent) :
+	HelperCell(parent),
+	split_background_color(background_color),
+	split_foreground_color(foreground_color),
+	split_size(0)
+{
+}
+
+/**
+	Destructor
+*/
+SplittedHelperCell::~SplittedHelperCell() {
+}
+
+/**
+	Handles the splitted helper cell visual rendering
+	@param painter QPainter to be used for the rendering
+	@param option Rendering options
+	@param widget QWidget being painted, if any
+*/
+void SplittedHelperCell::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * widget) {
+	if (!split_size) {
+		HelperCell::paint(painter, option, widget);
+		return;
+	}
+	QRectF first_drawing_rectangle(QPointF(0, 0), geometry().adjusted(0, 0, -split_size, 0).size());
+	QRectF second_drawing_rectangle(first_drawing_rectangle.topRight(), QSize(split_size, first_drawing_rectangle.height()));
+	qDebug() << first_drawing_rectangle << second_drawing_rectangle;
+	
+	painter -> setPen(Qt::black);
+	painter -> setBrush(background_color);
+	painter -> drawRect(first_drawing_rectangle);
+	painter -> setBrush(split_background_color);
+	painter -> drawRect(second_drawing_rectangle);
+	
+	painter -> setPen(foreground_color);
+	painter -> drawText(first_drawing_rectangle, Qt::AlignHCenter | Qt::AlignVCenter, label);
+	painter -> setPen(split_foreground_color);
+	painter -> drawText(second_drawing_rectangle, Qt::AlignHCenter | Qt::AlignVCenter, split_label);
+}

Added: branches/0.3/sources/titleblock/splittedhelpercell.h
===================================================================
--- branches/0.3/sources/titleblock/splittedhelpercell.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/splittedhelpercell.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,45 @@
+/*
+    Copyright 2006-2011 Xavier Guerrin
+    This file is part of QElectroTech.
+    
+    QElectroTech is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 2 of the License, or
+    (at your option) any later version.
+    
+    QElectroTech is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+    
+    You should have received a copy of the GNU General Public License
+    along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_SPLITTED_HELPER_CELL_H
+#define TITLEBLOCK_SLASH_SPLITTED_HELPER_CELL_H
+#include "helpercell.h"
+
+/**
+	This class is a variant of HelperCell having the ability to display two
+	labels, with a split line between them.
+*/
+class SplittedHelperCell : public HelperCell {
+	Q_OBJECT
+	public:
+	SplittedHelperCell(QGraphicsItem * = 0);
+	virtual ~SplittedHelperCell();
+	private:
+	SplittedHelperCell(const SplittedHelperCell &);
+	
+	// methods
+	public:
+	void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0);
+	
+	// attributes
+	QColor split_background_color; ///< Background color on the split side
+	QColor split_foreground_color; ///< Text color on the split side
+	QString split_label;           ///< Text displayed on the split side
+	int split_size;                ///< Length of the split side
+};
+
+#endif

Added: branches/0.3/sources/titleblock/templatecellsset.cpp
===================================================================
--- branches/0.3/sources/titleblock/templatecellsset.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatecellsset.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,212 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "templatecellsset.h"
+#include "templatevisualcell.h"
+#include "templateview.h"
+#include "titleblockcell.h"
+
+/**
+	Constructor
+	@param parent_view View this set of cells are rattached to
+*/
+TitleBlockTemplateCellsSet::TitleBlockTemplateCellsSet(const TitleBlockTemplateView *parent_view) :
+	parent_view_(parent_view)
+{
+}
+
+/**
+	Copy constructor
+	@param copy TitleBlockTemplateCellsSet object to copy
+*/
+TitleBlockTemplateCellsSet::TitleBlockTemplateCellsSet(const TitleBlockTemplateCellsSet &copy) :
+	QList<TitleBlockTemplateVisualCell *>(copy),
+	parent_view_(copy.parent_view_)
+{
+}
+
+/**
+	Destructor
+*/
+TitleBlockTemplateCellsSet::~TitleBlockTemplateCellsSet() {
+}
+
+/**
+	@return a QPainterPath composed of the rectangles from cells within this set
+*/
+QPainterPath TitleBlockTemplateCellsSet::painterPath() const {
+	QPainterPath cells_path;
+	foreach (TitleBlockTemplateVisualCell *cell, *this) {
+		cells_path.addRect(cell -> geometry());
+	}
+	return(cells_path);
+}
+
+/**
+	@return true if the cells within this set are composing a rectangle shape,
+	false otherwise.
+*/
+bool TitleBlockTemplateCellsSet::isRectangle() const {
+	if (!count()) return(false);
+	if (count() == 1) return(true);
+	
+	QPolygonF points = painterPath().simplified().toFillPolygon();
+	if (points.isClosed()) points.pop_back();
+	
+	return(points.count() == 4 || points.count() == 5);
+}
+
+/**
+	@return true if all cells within this set are selected
+*/
+bool TitleBlockTemplateCellsSet::allCellsAreSelected() const {
+	foreach (TitleBlockTemplateVisualCell *cell, *this) {
+		if (!cell -> isSelected()) {
+			return(false);
+		}
+	}
+	return(true);
+}
+
+/**
+	@return true if this set includes at least one cell which is spanned by a
+	cell not present in this set, false otherwise.
+*/
+bool TitleBlockTemplateCellsSet::hasExternalSpan() const {
+	// fetches all cells concerned by this set
+	QSet<TitleBlockCell *> all_cells = cells(true);
+	
+	// look for cells spanned by cells that do not belong to this set
+	foreach (TitleBlockCell *cell, all_cells) {
+		if (cell -> spanner_cell && !all_cells.contains(cell -> spanner_cell)) {
+			return(true);
+		}
+	}
+	return(false);
+}
+
+/**
+	@return the top left cell within this set, or 0 if this set is empty
+*/
+TitleBlockTemplateVisualCell *TitleBlockTemplateCellsSet::topLeftCell() const {
+	if (empty()) return(0);
+	if (count() == 1) return(first());
+	
+	// look for cells at the top
+	QMultiMap<int, TitleBlockTemplateVisualCell *> top_cells;
+	foreach (TitleBlockTemplateVisualCell *cell_view, *this) {
+		if (TitleBlockCell *cell = cell_view -> cell()) {
+			top_cells.insertMulti(cell -> num_row, cell_view);
+		}
+	}
+	QList<TitleBlockTemplateVisualCell *> candidates = top_cells.values(top_cells.keys().first());
+	if (candidates.count() == 1) return(candidates.first());
+	
+	// look for the cell at the left
+	int lowest_num_col = 100000;
+	TitleBlockTemplateVisualCell *candidate = 0;
+	foreach (TitleBlockTemplateVisualCell *cell_view, candidates) {
+		if (TitleBlockCell *cell = cell_view -> cell()) {
+			if (cell -> num_col < lowest_num_col) {
+				lowest_num_col = cell -> num_col;
+				candidate = cell_view;
+			}
+		}
+	}
+	return(candidate);
+}
+
+/**
+	@return the bottom right cell within this set, or 0 if this set is empty
+*/
+TitleBlockTemplateVisualCell *TitleBlockTemplateCellsSet::bottomRightCell() const {
+	if (empty()) return(0);
+	if (count() == 1) return(first());
+	
+	// look for cells at the bottom
+	QMultiMap<qreal, TitleBlockTemplateVisualCell *> bottom_cells;
+	foreach (TitleBlockTemplateVisualCell *cell_view, *this) {
+		bottom_cells.insertMulti(cell_view -> geometry().bottom(), cell_view);
+	}
+	QList<TitleBlockTemplateVisualCell *> candidates = bottom_cells.values(bottom_cells.keys().last());
+	if (candidates.count() == 1) return(candidates.first());
+	
+	// look for the cell at the right
+	qreal highest_right = -100000;
+	TitleBlockTemplateVisualCell *candidate = 0;
+	foreach (TitleBlockTemplateVisualCell *cell_view, candidates) {
+		qreal right = cell_view -> geometry().right();
+		if (right > highest_right) {
+			highest_right = right;
+			candidate = cell_view;
+		}
+	}
+	return(candidate);
+}
+
+/**
+	@return the merge area, i.e. the rectangle delimited by the top left cell
+	and the bottom right cell within this cells set.
+*/
+QRectF TitleBlockTemplateCellsSet::mergeAreaRect() const {
+	QRectF merge_area;
+	if (!parent_view_) return(merge_area);
+	
+	TitleBlockTemplateVisualCell *top_left_cell = topLeftCell();
+	if (!top_left_cell) return(merge_area);
+	TitleBlockTemplateVisualCell *bottom_right_cell = bottomRightCell();
+	if (!bottom_right_cell) return(merge_area);
+	
+	merge_area.setTopLeft(top_left_cell -> geometry().topLeft());
+	merge_area.setBottomRight(bottom_right_cell -> geometry().bottomRight());
+	return(merge_area);
+}
+
+/**
+	@param rect (Optional) The merge area to be considered; if a null QRectF is
+	provided, this method will use mergeAreaRect().
+	@return the cells contained in the merge area of this cells set
+*/
+TitleBlockTemplateCellsSet TitleBlockTemplateCellsSet::mergeArea(const QRectF &rect) const {
+	TitleBlockTemplateCellsSet merge_area(parent_view_);
+	if (!parent_view_) return(merge_area);
+	
+	QRectF merge_area_rect = rect.isNull() ? mergeAreaRect() : rect;
+	
+	merge_area = parent_view_ -> cells(merge_area_rect);
+	return(merge_area);
+}
+
+/**
+	@return the list of cells rendered by the current selection
+	@param include_spanned whether to include spanned cells or not
+*/
+QSet<TitleBlockCell *> TitleBlockTemplateCellsSet::cells(bool include_spanned) const {
+	QSet<TitleBlockCell *> set;
+	foreach (TitleBlockTemplateVisualCell *cell_view, *this) {
+		if (TitleBlockCell *cell = cell_view -> cell()) {
+			if (include_spanned) {
+				foreach (TitleBlockCell *cell, cell_view -> cells()) {
+					set << cell;
+				}
+			} else {
+				set << cell;
+			}
+		}
+	}
+	return(set);
+}

Added: branches/0.3/sources/titleblock/templatecellsset.h
===================================================================
--- branches/0.3/sources/titleblock/templatecellsset.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatecellsset.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,51 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_TEMPLATE_CELLS_SET_H
+#define TITLEBLOCK_SLASH_TEMPLATE_CELLS_SET_H
+#include <QtGui>
+class TitleBlockCell;
+class TitleBlockTemplateVisualCell;
+class TitleBlockTemplateView;
+/**
+	This class represents a set of cells (selected or not) when editing a
+	title block template.
+*/
+class TitleBlockTemplateCellsSet : public QList<TitleBlockTemplateVisualCell *> {
+	// Constructors, destructor
+	public:
+	TitleBlockTemplateCellsSet(const TitleBlockTemplateView *);
+	TitleBlockTemplateCellsSet(const TitleBlockTemplateCellsSet &);
+	virtual ~TitleBlockTemplateCellsSet();
+	
+	// methods
+	public:
+	QPainterPath painterPath() const;
+	bool isRectangle() const;
+	bool allCellsAreSelected() const;
+	bool hasExternalSpan() const;
+	TitleBlockTemplateVisualCell *topLeftCell() const;
+	TitleBlockTemplateVisualCell *bottomRightCell() const;
+	QRectF mergeAreaRect() const;
+	TitleBlockTemplateCellsSet mergeArea(const QRectF & = QRectF()) const;
+	QSet<TitleBlockCell *> cells(bool = true) const;
+	
+	// attributes
+	public:
+	const TitleBlockTemplateView *parent_view_; ///< the view displaying the cells
+};
+#endif

Added: branches/0.3/sources/titleblock/templatecellwidget.cpp
===================================================================
--- branches/0.3/sources/titleblock/templatecellwidget.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatecellwidget.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,373 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "templatecellwidget.h"
+#include "titleblockcell.h"
+#include "nameslist.h"
+#include "nameslistwidget.h"
+#include "titleblocktemplate.h"
+#include "templatecommands.h"
+
+/**
+	Constructor
+	@param parent Parent QWidget
+*/
+TitleBlockTemplateCellWidget::TitleBlockTemplateCellWidget(TitleBlockTemplate *parent_template, QWidget *parent) :
+	QWidget(parent),
+	read_only(false)
+{
+	initWidgets();
+	updateLogosComboBox(parent_template);
+}
+
+/**
+	Destructor
+*/
+TitleBlockTemplateCellWidget::~TitleBlockTemplateCellWidget() {
+}
+
+/**
+	Initialize layout and widgets.
+*/
+void TitleBlockTemplateCellWidget::initWidgets() {
+	cell_type_label_ = new QLabel(tr("Type de cellule :"));
+	cell_type_input_ = new QComboBox();
+	cell_type_input_ -> addItem(tr("Vide"),  TitleBlockCell::EmptyCell);
+	cell_type_input_ -> addItem(tr("Texte"), TitleBlockCell::TextCell);
+	cell_type_input_ -> addItem(tr("Logo"),  TitleBlockCell::LogoCell);
+	
+	logo_label_ = new QLabel(tr("Logo"));
+	logo_input_ = new QComboBox();
+	logo_input_ -> addItem(tr("Aucun logo"));
+	add_logo_input_ = new QPushButton(tr("G\351rer les logos"));
+	
+	name_label_ = new QLabel(tr("Nom :"));
+	name_input_ = new QLineEdit();
+	label_checkbox_ = new QCheckBox(tr("Afficher un label :"));
+	label_input_ = new QLineEdit();
+	label_input_ -> setReadOnly(true);
+	label_edit_ = new QPushButton(tr("Editer"));
+	value_label_ = new QLabel(tr("Texte :"));
+	value_input_ = new QLineEdit();
+	value_input_ -> setReadOnly(true);
+	value_edit_ = new QPushButton(tr("Editer"));
+	align_label_ = new QLabel(tr("Alignement :"));
+	horiz_align_label_ = new QLabel(tr("horizontal :"));
+	horiz_align_input_ = new QComboBox();
+	horiz_align_input_ -> addItem(tr("Gauche"),      Qt::AlignLeft);
+	horiz_align_input_ -> addItem(tr("Centr\351"),   Qt::AlignHCenter);
+	horiz_align_input_ -> addItem(tr("Droite"),      Qt::AlignRight);
+	horiz_align_indexes_.insert(Qt::AlignLeft,    0);
+	horiz_align_indexes_.insert(Qt::AlignHCenter, 1);
+	horiz_align_indexes_.insert(Qt::AlignRight,   2);
+	vert_align_label_= new QLabel(tr("vertical :"));
+	vert_align_input_ = new QComboBox();
+	vert_align_input_ -> addItem(tr("Haut"),   Qt::AlignTop);
+	vert_align_input_ -> addItem(tr("Milieu"), Qt::AlignVCenter);
+	vert_align_input_ -> addItem(tr("Bas"),    Qt::AlignBottom);
+	vert_align_indexes_.insert(Qt::AlignTop,     0);
+	vert_align_indexes_.insert(Qt::AlignVCenter, 1);
+	vert_align_indexes_.insert(Qt::AlignBottom,  2);
+	font_size_label_ = new QLabel("Police :");
+	font_size_input_ = new QSpinBox();
+	font_adjust_input_ = new QCheckBox(tr("Ajuster la taille de police si besoin"));
+	
+	// layout
+	QHBoxLayout *label_edition = new QHBoxLayout();
+	label_edition -> addWidget(label_input_);
+	label_edition -> addWidget(label_edit_);
+	
+	QHBoxLayout *value_edition = new QHBoxLayout();
+	value_edition -> addWidget(value_input_);
+	value_edition -> addWidget(value_edit_);
+	
+	/// TODO fix widget alignment when switching cell type
+	cell_editor_text_layout_ = new QGridLayout();
+	cell_editor_text_layout_ -> addWidget(cell_type_label_,    0, 0);
+	cell_editor_text_layout_ -> addWidget(cell_type_input_,    0, 1);
+	cell_editor_text_layout_ -> addWidget(name_label_,         0, 2);
+	cell_editor_text_layout_ -> addWidget(name_input_,         0, 3);
+	cell_editor_text_layout_ -> addWidget(label_checkbox_,     2, 0);
+	cell_editor_text_layout_ -> addLayout(label_edition,       2, 1);
+	cell_editor_text_layout_ -> addWidget(value_label_,        3, 0);
+	cell_editor_text_layout_ -> addLayout(value_edition,       3, 1);
+	cell_editor_text_layout_ -> addWidget(align_label_,        1, 2, 1, 2, Qt::AlignHCenter);
+	cell_editor_text_layout_ -> addWidget(horiz_align_label_,  2, 2);
+	cell_editor_text_layout_ -> addWidget(horiz_align_input_,  2, 3);
+	cell_editor_text_layout_ -> addWidget(vert_align_label_,   3, 2);
+	cell_editor_text_layout_ -> addWidget(vert_align_input_,   3, 3);
+	cell_editor_text_layout_ -> addWidget(font_size_label_,    4, 0);
+	cell_editor_text_layout_ -> addWidget(font_size_input_,    4, 1);
+	cell_editor_text_layout_ -> addWidget(font_adjust_input_,  4, 2, 1, 2, Qt::AlignLeft);
+	cell_editor_text_layout_ -> addWidget(logo_label_,         5, 0);
+	cell_editor_text_layout_ -> addWidget(logo_input_,         5, 1);
+	cell_editor_text_layout_ -> addWidget(add_logo_input_,     5, 2);
+	cell_editor_text_layout_ -> setColumnStretch(4, 4000);
+	cell_editor_layout_ = new QVBoxLayout();
+	cell_editor_layout_ -> addLayout(cell_editor_text_layout_);
+	cell_editor_layout_ -> addStretch();
+	setLayout(cell_editor_layout_);
+	
+	// trigger the logo manager
+	connect(add_logo_input_, SIGNAL(released()), this, SIGNAL(logoEditionRequested()));
+	
+	// handle cell modifications
+	connect(cell_type_input_,   SIGNAL(activated(int)),           this, SLOT(updateFormType(int)));
+	connect(cell_type_input_,   SIGNAL(activated(int)),           this, SLOT(editType()));
+	connect(name_input_,        SIGNAL(editingFinished()),        this, SLOT(editName()));
+	connect(label_checkbox_,    SIGNAL(clicked(bool)),            this, SLOT(editLabelDisplayed()));
+	connect(label_edit_,        SIGNAL(released()),               this, SLOT(editLabel()));
+	connect(value_edit_,        SIGNAL(released()),               this, SLOT(editValue()));
+	connect(horiz_align_input_, SIGNAL(activated(int)),           this, SLOT(editAlignment()));
+	connect(vert_align_input_,  SIGNAL(activated(int)),           this, SLOT(editAlignment()));
+	connect(font_size_input_,   SIGNAL(valueChanged(int)),        this, SLOT(editFontSize()));
+	connect(font_adjust_input_, SIGNAL(clicked(bool)),            this, SLOT(editAdjust()));
+	connect(logo_input_,        SIGNAL(activated(int)),           this, SLOT(editLogo()));
+	
+	updateFormType(TitleBlockCell::TextCell);
+}
+
+/**
+	Shows or hides various widgets depending on the selected cell type
+*/
+void TitleBlockTemplateCellWidget::updateFormType(int cell_type) {
+	if (cell_type_input_ -> currentIndex() != cell_type) {
+		cell_type_input_ -> setCurrentIndex(cell_type);
+	}
+	
+	name_label_        -> setVisible(cell_type);
+	name_input_        -> setVisible(cell_type);
+	
+	logo_label_        -> setVisible(cell_type == TitleBlockCell::LogoCell);
+	logo_input_        -> setVisible(cell_type == TitleBlockCell::LogoCell);
+	add_logo_input_    -> setVisible(cell_type == TitleBlockCell::LogoCell);
+	
+	label_checkbox_    -> setVisible(cell_type == TitleBlockCell::TextCell);
+	label_input_       -> setVisible(cell_type == TitleBlockCell::TextCell);
+	label_edit_        -> setVisible(cell_type == TitleBlockCell::TextCell);
+	value_label_       -> setVisible(cell_type == TitleBlockCell::TextCell);
+	value_input_       -> setVisible(cell_type == TitleBlockCell::TextCell);
+	value_edit_        -> setVisible(cell_type == TitleBlockCell::TextCell);
+	align_label_       -> setVisible(cell_type == TitleBlockCell::TextCell);
+	horiz_align_label_ -> setVisible(cell_type == TitleBlockCell::TextCell);
+	horiz_align_input_ -> setVisible(cell_type == TitleBlockCell::TextCell);
+	vert_align_label_  -> setVisible(cell_type == TitleBlockCell::TextCell);
+	vert_align_input_  -> setVisible(cell_type == TitleBlockCell::TextCell);
+	font_size_label_   -> setVisible(cell_type == TitleBlockCell::TextCell);
+	font_size_input_   -> setVisible(cell_type == TitleBlockCell::TextCell);
+	font_adjust_input_ -> setVisible(cell_type == TitleBlockCell::TextCell);
+}
+
+/**
+	Set the title block cell to be edited. The cell pointer is stored by this
+	class; however, modifications made by the user are packaged as
+	ModifyTitleBlockCellCommand objects and emitted through the
+	cellModified() signal.
+	@param cell Title block cell to be edited
+*/
+void TitleBlockTemplateCellWidget::edit(TitleBlockCell *cell) {
+	if (!cell) return;
+	edited_cell_ = cell;
+	int type = cell -> type();
+	updateFormType(type);
+	
+	name_input_        -> setText(cell -> value_name);
+	label_checkbox_    -> setChecked(cell -> display_label);
+	label_input_       -> setText(cell -> label.name());
+	value_input_       -> setText(cell -> value.name());
+	font_adjust_input_ -> setChecked(cell -> hadjust);
+	horiz_align_input_ -> setCurrentIndex(horiz_align_indexes_[cell -> horizontalAlign()]);
+	vert_align_input_  -> setCurrentIndex(vert_align_indexes_[cell -> verticalAlign()]);
+	
+	font_size_input_   -> blockSignals(true); // QSpinBox has no signal triggered for each non-programmatic change
+	font_size_input_   -> setValue(TitleBlockTemplate::fontForCell(*cell).pointSize());
+	font_size_input_   -> blockSignals(false);
+	
+	logo_input_        -> setCurrentIndex(logo_input_ -> findData(cell -> logo_reference));
+}
+
+/**
+	Emit a type modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editType() {
+	emitModification("type", cell_type_input_ -> itemData(cell_type_input_ -> currentIndex()));
+}
+
+/**
+	Emit a name modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editName() {
+	emitModification("name", name_input_ -> text());
+}
+
+/**
+	Emit a modification command stating whether the label should be displayed or not.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editLabelDisplayed() {
+	emitModification("displaylabel", label_checkbox_ -> isChecked());
+}
+
+/**
+	Emit a label modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editLabel() {
+	if (!edited_cell_) return;
+	editTranslatableValue(edited_cell_ -> label, "label", tr("Label de cette cellule :"));
+}
+
+/**
+	Emit a value modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editValue() {
+	if (!edited_cell_) return;
+	editTranslatableValue(edited_cell_ -> value, "value", tr("Valeur de cette cellule :"));
+}
+
+/**
+	Emit an alignment modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editAlignment() {
+	emitModification("alignment", alignment());
+}
+
+/**
+	Emit a font size modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editFontSize() {
+	emitModification("fontsize", font_size_input_ -> value());
+}
+
+/**
+	Emit a modification command stating whether the text should be adjusted if needed.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editAdjust() {
+	emitModification("horizontal_adjust", font_adjust_input_ -> isChecked());
+}
+
+/**
+	Emit a logo modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+void TitleBlockTemplateCellWidget::editLogo() {
+	emitModification("logo", logo_input_ -> currentText());
+}
+
+/**
+	Updates the list of available logos
+	@param parent_template The title block template which contains the currently edited cell
+*/
+void TitleBlockTemplateCellWidget::updateLogosComboBox(const TitleBlockTemplate *parent_template) {
+	// saves the current value before erasing all entries
+	QVariant current_value = logo_input_ -> itemData(logo_input_ -> currentIndex());
+	logo_input_ -> clear();
+	
+	// default choice (the parent template may have no logo yet)
+	logo_input_ -> addItem(
+		tr("Aucun logo", "text displayed in the combo box when a template has no logo"),
+		QVariant(QString(""))
+	);
+	logo_input_ -> setCurrentIndex(0);
+	
+	if (!parent_template) return;
+	foreach (QString logo, parent_template -> logos()) {
+		logo_input_ -> addItem(logo, QVariant(logo));
+	}
+	int current_value_index = logo_input_ -> findData(current_value);
+	if (current_value_index != -1) {
+		logo_input_ -> setCurrentIndex(current_value_index);
+	}
+}
+
+/**
+	Emit a horizontal alignment modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+int TitleBlockTemplateCellWidget::horizontalAlignment() const {
+	return(horiz_align_indexes_.key(horiz_align_input_ -> currentIndex()));
+}
+
+/**
+	Emit a vertical alignment modification command.
+	@see ModifyTitleBlockCellCommand
+*/
+int TitleBlockTemplateCellWidget::verticalAlignment() const {
+	return(vert_align_indexes_.key(vert_align_input_ -> currentIndex()));
+}
+
+/**
+	@return the currently selected alignment.
+*/
+int TitleBlockTemplateCellWidget::alignment() const {
+	return(horizontalAlignment() | verticalAlignment());
+}
+
+/**
+	Allow the user to edit a translatable string (e.g. value or label).
+	If the user modified the string, this method emits a
+	ModifyTitleBlockCellCommand object through the cellModified() signal.
+	@param names Translatable string to be edited
+	@param attribute Name of the edited cell attribute
+	@param label Label to be displayed when editing the string
+*/
+void TitleBlockTemplateCellWidget::editTranslatableValue(NamesList &names, const QString &attribute, const QString &label) const {
+	NamesListWidget *names_widget = new NamesListWidget();
+	names_widget -> setNames(names);
+	QDialogButtonBox * buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
+	
+	QVBoxLayout *editor_layout = new QVBoxLayout();
+	editor_layout -> addWidget(new QLabel(label));
+	editor_layout -> addWidget(names_widget);
+	editor_layout -> addWidget(buttons);
+	
+	QDialog edit_dialog;
+	connect(buttons, SIGNAL(rejected()), &edit_dialog, SLOT(reject()));
+	connect(buttons, SIGNAL(accepted()), &edit_dialog, SLOT(accept()));
+	edit_dialog.setLayout(editor_layout);
+	if (edit_dialog.exec() == QDialog::Accepted) {
+		emitModification(attribute, qVariantFromValue(names_widget -> names()));
+	}
+}
+
+/**
+	Create a ModifyTitleBlockCellCommand object to change \a attribute to \a new_value.
+	This object is then emitted through the cellModified() signal.
+	@see ModifyTitleBlockCellCommand
+	@param attribute Modified cell attribute
+	@param new_value New value for the modified cell attribute
+*/
+void TitleBlockTemplateCellWidget::emitModification(const QString &attribute, const QVariant &new_value) const {
+	if (!edited_cell_) return;
+	
+	// avoid creating a QUndoCommand object when no modification was actually done
+	if (edited_cell_ -> attribute(attribute) == new_value) return;
+	
+	ModifyTitleBlockCellCommand *command = new ModifyTitleBlockCellCommand(edited_cell_);
+	command -> addModification(attribute, new_value);
+	command -> setText(
+		tr("\xC9dition d'une cellule : %1", "label of and undo command when editing a cell")
+		.arg(TitleBlockCell::attributeName(attribute))
+	);
+	emit(cellModified(command));
+}

Added: branches/0.3/sources/titleblock/templatecellwidget.h
===================================================================
--- branches/0.3/sources/titleblock/templatecellwidget.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatecellwidget.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,111 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_TEMPLATE_CELL_WIDGET_H
+#define TITLEBLOCK_SLASH_TEMPLATE_CELL_WIDGET_H
+#include <QtGui>
+#include "qet.h"
+class ModifyTitleBlockCellCommand;
+class TitleBlockTemplate;
+class TitleBlockCell;
+class NamesList;
+
+/**
+	This class implements an edition widget for cells that compose a title
+	block template.
+*/
+class TitleBlockTemplateCellWidget : public QWidget {
+	Q_OBJECT
+	
+	// constructor, destructor
+	public:
+	TitleBlockTemplateCellWidget(TitleBlockTemplate * = 0, QWidget * = 0);
+	virtual ~TitleBlockTemplateCellWidget();
+	private:
+	TitleBlockTemplateCellWidget(const TitleBlockTemplateCellWidget &);
+	
+	// attributes
+	private:
+	/// is the template read-only?
+	bool read_only;
+	QLabel    *cell_type_label_;
+	QComboBox *cell_type_input_;
+	
+	QLabel      *logo_label_;
+	QComboBox   *logo_input_;
+	QPushButton *add_logo_input_;
+	
+	QLabel        *name_label_;
+	QLineEdit     *name_input_;
+	QCheckBox     *label_checkbox_;
+	QLineEdit     *label_input_;
+	QPushButton   *label_edit_;
+	QLabel        *value_label_;
+	QLineEdit     *value_input_;
+	QPushButton   *value_edit_;
+	QLabel        *align_label_;
+	QLabel        *horiz_align_label_;
+	QComboBox     *horiz_align_input_;
+	QHash<int, int> horiz_align_indexes_;
+	QLabel        *vert_align_label_;
+	QComboBox     *vert_align_input_;
+	QHash<int, int> vert_align_indexes_;
+	QLabel        *font_size_label_;
+	QSpinBox      *font_size_input_;
+	QCheckBox     *font_adjust_input_;
+	QVBoxLayout   *cell_editor_layout_;
+	QGridLayout   *cell_editor_text_layout_;
+	QHBoxLayout   *cell_editor_image_layout_;
+	
+	TitleBlockCell *edited_cell_;
+	
+	// methods
+	public:
+	int horizontalAlignment() const;
+	int verticalAlignment() const;
+	int alignment() const;
+	
+	protected:
+	void editTranslatableValue(NamesList &, const QString &, const QString &) const;
+	void emitModification(const QString &, const QVariant &) const;
+	
+	private:
+	void initWidgets();
+	
+	public slots:
+	void updateFormType(int);
+	void edit(TitleBlockCell *);
+	void editType();
+	void editName();
+	void editLabelDisplayed();
+	void editLabel();
+	void editValue();
+	void editAlignment();
+	void editFontSize();
+	void editAdjust();
+	void editLogo();
+	void updateLogosComboBox(const TitleBlockTemplate *);
+	
+	private slots:
+	
+	
+	signals:
+	void logoEditionRequested();
+	void cellModified(ModifyTitleBlockCellCommand *) const;
+};
+
+#endif

Added: branches/0.3/sources/titleblock/templatecommands.cpp
===================================================================
--- branches/0.3/sources/titleblock/templatecommands.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatecommands.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,779 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "templatecommands.h"
+#include "templatevisualcell.h"
+#include "templateview.h"
+#include "titleblockcell.h"
+#include "dimension.h"
+#define TITLEBLOCK_DEFAULT_ROW_HEIGHT TitleBlockDimension(25)
+#define TITLEBLOCK_DEFAULT_COL_WIDTH  TitleBlockDimension(50)
+
+/**
+	Constructor
+	@param cell Modified cell
+	@param parent Parent QUndoCommand
+*/
+ModifyTitleBlockCellCommand::ModifyTitleBlockCellCommand(TitleBlockCell *cell, QUndoCommand *parent) :
+	QUndoCommand(parent),
+	view_(0),
+	modified_cell_(cell)
+{
+}
+
+/**
+	Destructor
+*/
+ModifyTitleBlockCellCommand::~ModifyTitleBlockCellCommand() {
+}
+
+/**
+	@see QUndoCommand::id()
+	@return the ID of this command.
+*/
+int ModifyTitleBlockCellCommand::id() const {
+	return(MODIFY_TITLE_BLOCK_CELL_COMMAND_ID);
+}
+
+/**
+	@see QUndoCommand::mergeWith()
+	@param command Command to merge with.
+	@return true on success, false otherwise
+*/
+bool ModifyTitleBlockCellCommand::mergeWith(const QUndoCommand *command) {
+	const ModifyTitleBlockCellCommand *other = static_cast<const ModifyTitleBlockCellCommand *>(command);
+	if (other) {
+		if (other -> modified_cell_ == modified_cell_) {
+			if (other -> new_values_.keys() == new_values_.keys()) {
+				qDebug() << Q_FUNC_INFO << "merging";
+				new_values_ = other -> new_values_;
+				return(true);
+			}
+		}
+	}
+	return(false);
+}
+
+/**
+	Undo the change.
+*/
+void ModifyTitleBlockCellCommand::undo() {
+	if (!modified_cell_) return;
+	foreach (QString attribute, old_values_.keys()) {
+		modified_cell_ -> setAttribute(attribute, old_values_[attribute]);
+	}
+	if (view_) view_ -> refresh();
+}
+
+/**
+	Redo the change.
+*/
+void ModifyTitleBlockCellCommand::redo() {
+	if (!modified_cell_) return;
+	foreach (QString attribute, new_values_.keys()) {
+		modified_cell_ -> setAttribute(attribute, new_values_[attribute]);
+	}
+	if (view_) view_ -> refresh();
+}
+
+/**
+	@return the cell modified by this command
+*/
+TitleBlockCell *ModifyTitleBlockCellCommand::cell() const {
+	return(modified_cell_);
+}
+
+/**
+	Set the cell modified by this command object
+	@param modified_cell the cell modified by this command
+*/
+void ModifyTitleBlockCellCommand::setCell(TitleBlockCell *modified_cell) {
+	modified_cell_ = modified_cell;
+}
+
+/**
+	@return the view to be updated after the cell modification
+*/
+TitleBlockTemplateView *ModifyTitleBlockCellCommand::view() const {
+	return(view_);
+}
+
+/**
+	Set the view to be updated after the cell modification
+	@param view the view to be updated after the cell modification
+*/
+void ModifyTitleBlockCellCommand::setView(TitleBlockTemplateView *view) {
+	view_ = view;
+}
+
+/**
+	Erase the known old/new values.
+*/
+void ModifyTitleBlockCellCommand::clear() {
+	old_values_.clear();
+	new_values_.clear();
+}
+
+/**
+	Register a new modification on a title block template cell; you may
+	indicate either the new value or the old one: this method will
+	systematically fetch the other one.
+	@param attribute Name of the modified attribute
+	@param value Old or new value of the modified attribute, depending on is_old_value
+	@param is_old_value (optional, defaults to false) Indicates whether the provided value is the old or the new one.
+*/
+void ModifyTitleBlockCellCommand::addModification(const QString &attribute, const QVariant &value, bool is_old_value) {
+	if (is_old_value) {
+		// the provided value is the old one; therefore, the one we fetch is the new one
+		old_values_[attribute] = value;
+		if (modified_cell_) {
+			new_values_[attribute] = modified_cell_ -> attribute(attribute);
+		}
+	} else {
+		// the provided value is the new one; therefore, we fetch the old one
+		if (modified_cell_) {
+			old_values_[attribute] = modified_cell_ -> attribute(attribute);
+		}
+		new_values_[attribute] = value;
+	}
+}
+
+/**
+	Constructor
+	@param tbtemplate Modified title block template
+	@param parent Parent QUndoCommand
+*/
+TitleBlockTemplateCommand::TitleBlockTemplateCommand(TitleBlockTemplate *tbtemplate, QUndoCommand *parent) :
+	QUndoCommand(parent),
+	tbtemplate_(tbtemplate),
+	view_(0)
+{
+}
+
+/**
+	Destructor
+*/
+TitleBlockTemplateCommand::~TitleBlockTemplateCommand() {
+}
+
+/**
+	@return the modified title block template.
+*/
+TitleBlockTemplate *TitleBlockTemplateCommand::titleBlockTemplate() const {
+	return(tbtemplate_);
+}
+
+/**
+	Set the modified title block template.
+	@param tbtemplate New modified title block template.
+*/
+void TitleBlockTemplateCommand::setTitleBlockTemplate(TitleBlockTemplate *tbtemplate) {
+	tbtemplate_ = tbtemplate;
+}
+
+/**
+	@return the view to be updated after the template modification
+*/
+TitleBlockTemplateView *TitleBlockTemplateCommand::view() const {
+	return(view_);
+}
+
+/**
+	Set the view to be updated after the template modification
+	@param view the view to be updated after the template modification
+*/
+void TitleBlockTemplateCommand::setView(TitleBlockTemplateView *view) {
+	view_ = view;
+}
+
+/**
+	This static method is a convenience to create a ModifyTemplateGridCommand
+	that adds a row to \a tbtemplate at \a index.
+	@param tbtemplate Modified title block template
+	@param index Index where the row should be inserted.
+	@return a ModifyTemplateGridCommand object, or 0 if something went wrong.
+*/
+ModifyTemplateGridCommand *ModifyTemplateGridCommand::addRow(TitleBlockTemplate *tbtemplate, int index) {
+	if (!tbtemplate) return(0);
+	
+	// create the command itself
+	ModifyTemplateGridCommand *add_row_command = new ModifyTemplateGridCommand(tbtemplate);
+	add_row_command -> setInsertion(true);
+	add_row_command -> setType(true);
+	add_row_command -> setCells(tbtemplate -> createRow());
+	add_row_command -> setDimension(TITLEBLOCK_DEFAULT_ROW_HEIGHT);
+	add_row_command -> setIndex(index);
+	
+	return(add_row_command);
+}
+
+/**
+	This static method is a convenience to create a ModifyTemplateGridCommand
+	that adds a column to \a tbtemplate at \a index.
+	@param tbtemplate Modified title block template.
+	@param index Index where the column should be inserted.
+	@return a ModifyTemplateGridCommand object, or 0 if something went wrong.
+*/
+ModifyTemplateGridCommand *ModifyTemplateGridCommand::addColumn(TitleBlockTemplate *tbtemplate, int index) {
+	if (!tbtemplate) return(0);
+	
+	// create the command itself
+	ModifyTemplateGridCommand *add_column_command = new ModifyTemplateGridCommand(tbtemplate);
+	add_column_command -> setInsertion(true);
+	add_column_command -> setType(false);
+	add_column_command -> setCells(tbtemplate -> createColumn());
+	add_column_command -> setDimension(TITLEBLOCK_DEFAULT_COL_WIDTH);
+	add_column_command -> setIndex(index);
+	
+	return(add_column_command);
+}
+
+/**
+	This static method is a convenience to create a ModifyTemplateGridCommand
+	that removes the row at \a index from \a tbtemplate.
+	@param tbtemplate Modified title block template.
+	@param index Index of the removed row.
+	@return a ModifyTemplateGridCommand object, or 0 if something went wrong.
+*/
+ModifyTemplateGridCommand *ModifyTemplateGridCommand::deleteRow(TitleBlockTemplate *tbtemplate, int index) {
+	if (!tbtemplate) return(0);
+	
+	// create the command itself
+	ModifyTemplateGridCommand *del_row_command = new ModifyTemplateGridCommand(tbtemplate);
+	del_row_command -> setInsertion(false);
+	del_row_command -> setType(true);
+	del_row_command -> setIndex(index);
+	
+	return(del_row_command);
+}
+
+/**
+	This static method is a convenience to create a ModifyTemplateGridCommand
+	that removes the column at \a index from \a tbtemplate.
+	@param tbtemplate Modified title block template.
+	@param index Index of the removed column.
+	@return a ModifyTemplateGridCommand object, or 0 if something went wrong.
+*/
+ModifyTemplateGridCommand *ModifyTemplateGridCommand::deleteColumn(TitleBlockTemplate *tbtemplate, int index) {
+	if (!tbtemplate) return(0);
+	
+	// create the command itself
+	ModifyTemplateGridCommand *del_column_command = new ModifyTemplateGridCommand(tbtemplate);
+	del_column_command -> setInsertion(false);
+	del_column_command -> setType(false);
+	del_column_command -> setIndex(index);
+	
+	return(del_column_command);
+}
+
+/**
+	Construct a default ModifyTemplateGridCommand, i.e. a command adding a 25px row at the bottom of the template.
+	@param tbtemplate Modified title block template
+	@param parent Parent QUndoCommand
+*/
+ModifyTemplateGridCommand::ModifyTemplateGridCommand(TitleBlockTemplate *tbtemplate, QUndoCommand *parent) :
+	TitleBlockTemplateCommand(tbtemplate, parent),
+	index_(-1),
+	type_(true),
+	dimension_(TITLEBLOCK_DEFAULT_ROW_HEIGHT),
+	insertion_(true)
+{
+	updateText();
+}
+
+/**
+	Destructor
+*/
+ModifyTemplateGridCommand::~ModifyTemplateGridCommand() {
+}
+
+/**
+	@return the index of the inserted/deleted row/column
+*/
+int ModifyTemplateGridCommand::index() const {
+	return(index_);
+}
+
+/**
+	Set the index of the inserted/deleted row/column.
+	@param index Index of the inserted/deleted row/column.
+*/
+void ModifyTemplateGridCommand::setIndex(int index) {
+	index_ = index;
+}
+
+/**
+	@return a list of pointers to cells composing the inserted/deleted row/column.
+*/
+QList<TitleBlockCell *> ModifyTemplateGridCommand::cells() const {
+	return(cells_);
+}
+
+/**
+	Set the cells composing the inserted/deleted row/column.
+	@param cells List of pointers to cells composing the inserted/deleted row/column.
+*/
+void ModifyTemplateGridCommand::setCells(const QList<TitleBlockCell *> &cells) {
+	cells_ = cells;
+}
+
+/**
+	@return the dimension of the inserted/deleted row/column.
+*/
+TitleBlockDimension ModifyTemplateGridCommand::dimension() const {
+	return dimension_;
+}
+
+/**
+	Set the dimension of the inserted/deleted row/column
+	@param dimension Dimension of the inserted/deleted row/column
+*/
+void ModifyTemplateGridCommand::setDimension(const TitleBlockDimension &dimension) {
+	dimension_ = dimension;
+}
+
+/**
+	@return true if this object is about inserting/deleting a row, false for a column.
+*/
+int ModifyTemplateGridCommand::type() const {
+	return(type_);
+}
+
+/**
+	Indicates whether this object inserts/deletes a row or a column.
+	@param type true if this object is about inserting/deleting a row, false for a column.
+*/
+void ModifyTemplateGridCommand::setType(bool type) {
+	type_ = type;
+	updateText();
+}
+
+/**
+	@return true if the row/column is inserted, false if it is deleted
+*/
+bool ModifyTemplateGridCommand::isInsertion() const {
+	return(insertion_);
+}
+
+/**
+	@param insertion true if the row/column is inserted, false if it is deleted
+*/
+void ModifyTemplateGridCommand::setInsertion(bool insertion) {
+	insertion_ = insertion;
+	updateText();
+}
+
+/**
+	Undo the change.
+*/
+void ModifyTemplateGridCommand::undo() {
+	apply(true);
+}
+
+/**
+	Redo the change.
+*/
+void ModifyTemplateGridCommand::redo() {
+	apply(false);
+}
+
+/**
+	Update the text describing what the command does.
+*/
+void ModifyTemplateGridCommand::updateText() {
+	if (type_) {
+		if (insertion_) {
+			setText(QObject::tr("Insertion d'une ligne", "label used in the title block template editor undo list"));
+		} else {
+			setText(QObject::tr("Suppression d'une ligne", "label used in the title block template editor undo list"));
+		}
+	} else {
+		if (insertion_) {
+			setText(QObject::tr("Insertion d'une colonne", "label used in the title block template editor undo list"));
+		} else {
+			setText(QObject::tr("Suppression d'une colonne", "label used in the title block template editor undo list"));
+		}
+	}
+}
+
+/*
+	This method takes care of the actual job when undoing / redoing a
+	row/column insertion/removal.
+	@param true to undo the change, false to apply it.
+*/
+void ModifyTemplateGridCommand::apply(bool undo) {
+	if (!tbtemplate_ || index_ == -1) return;
+	
+	if (insertion_ ^ undo) {
+		if (type_) {
+			tbtemplate_ -> insertRow(dimension_.value, cells_, index_);
+		} else {
+			tbtemplate_ -> insertColumn(dimension_, cells_, index_);
+		}
+	} else {
+		if (type_) {
+			dimension_.value = tbtemplate_ -> rowDimension(index_);
+			cells_ = tbtemplate_ -> takeRow(index_);
+		} else {
+			dimension_ = tbtemplate_ -> columnDimension(index_);
+			cells_ = tbtemplate_ -> takeColumn(index_);
+		}
+	}
+	
+	// update the view, if any
+	if (view_) {
+		view_ -> updateLayout();
+	}
+}
+
+/**
+	Construct a default ModifyTemplateDimension.
+	@param tbtemplate Modified title block template
+	@param parent Parent QUndoCommand
+*/
+ModifyTemplateDimension::ModifyTemplateDimension(TitleBlockTemplate *tbtemplate, QUndoCommand *parent) :
+	TitleBlockTemplateCommand(tbtemplate, parent),
+	index_(-1),
+	type_(true),
+	before_(TitleBlockDimension(-1)),
+	after_(TitleBlockDimension(-1))
+{
+}
+
+/**
+	Destructor
+*/
+ModifyTemplateDimension::~ModifyTemplateDimension() {
+}
+
+/**
+	@return the index of the resized row/column
+*/
+int ModifyTemplateDimension::index() const {
+	return(index_);
+}
+
+/**
+	Set the index of the resized row/column.
+	@param index Index of the resized row/column.
+*/
+void ModifyTemplateDimension::setIndex(int index) {
+	index_ = index;
+}
+
+/**
+	@return true if this object is about resizing a row, false for a column.
+*/
+int ModifyTemplateDimension::type() const {
+	return type_;
+}
+
+/**
+	Indicates whether this object resizes a row or a column.
+	@param type true if this object is about resizing a row, false for a column.
+*/
+void ModifyTemplateDimension::setType(bool type) {
+	type_ = type;
+	updateText();
+}
+
+/**
+	@return the dimension of the row/column before it is resized
+*/
+TitleBlockDimension ModifyTemplateDimension::dimensionBefore() const {
+	return(before_);
+}
+
+/**
+	@param dimension the dimension of the row/column before it is resized
+*/
+void ModifyTemplateDimension::setDimensionBefore(const TitleBlockDimension &dimension) {
+	before_ = dimension;
+}
+
+/**
+	@return the dimension of the row/column after it is resized
+*/
+TitleBlockDimension ModifyTemplateDimension::dimensionAfter() const {
+	return(after_);
+}
+
+/**
+	@param dimension the dimension of the row/column after it is resized
+*/
+void ModifyTemplateDimension::setDimensionAfter(const TitleBlockDimension &dimension) {
+	after_ = dimension;
+}
+
+/**
+	Restore the previous size of the row/column.
+*/
+void ModifyTemplateDimension::undo() {
+	apply(before_);
+}
+
+/**
+	Resize the row/column.
+*/
+void ModifyTemplateDimension::redo() {
+	apply(after_);
+}
+
+/**
+	Update the text describing what the command does.
+*/
+void ModifyTemplateDimension::updateText() {
+	if (type_) {
+		setText(QObject::tr("Modification d'une ligne", "label used in the title block template editor undo list"));
+	} else {
+		setText(QObject::tr("Modification d'une colonne", "label used in the title block template editor undo list"));
+	}
+}
+
+/**
+	Applies a given size to the row/column
+	@param dimension Size to apply
+*/
+void ModifyTemplateDimension::apply(const TitleBlockDimension &dimension) {
+	if (!tbtemplate_) return;
+	if (type_) {
+		tbtemplate_ -> setRowDimension(index_, dimension);
+	} else {
+		tbtemplate_ -> setColumnDimension(index_, dimension);
+	}
+	if (view_) {
+		if (type_) {
+			view_ -> rowsDimensionsChanged();
+		} else {
+			view_ -> columnsDimensionsChanged();
+		}
+	}
+}
+
+/**
+	Construct a command object that acts on \a tbtemplate in order to merge \a merged_cells.
+	Note: you should check the resulting object is valid using isValid().
+	@param merged_cells Cells to be merged together into a single one.
+	@param tbtemplate Modified title block template.
+	@param parent Parent QUndoCommand.
+*/
+MergeCellsCommand::MergeCellsCommand(const TitleBlockTemplateCellsSet &merged_cells, TitleBlockTemplate *tbtemplate, QUndoCommand *parent) :
+	TitleBlockTemplateCommand(tbtemplate, parent),
+	spanning_cell_(0),
+	row_span_after_(-1),
+	col_span_after_(-1)
+{
+	// basic check
+	if (merged_cells.count() < 2) return;
+	
+	// the spanning cell is the top left cell
+	TitleBlockTemplateVisualCell *top_left_cell = merged_cells.topLeftCell();
+	if (!top_left_cell) return;
+	spanning_cell_ = top_left_cell -> cell();
+	if (!spanning_cell_) return;
+	
+	// store the spanner_cell attribute of each cell implied in the merge
+	foreach(TitleBlockCell *cell, merged_cells.cells()) {
+		spanner_cells_before_merge_.insert(cell, cell -> spanner_cell);
+	}
+	
+	// store the former values of the row_span and col_span attributes of the spanning cell
+	row_span_before_ = spanning_cell_ -> row_span;
+	col_span_before_ = spanning_cell_ -> col_span;
+	
+	// calculate their new values after the merge operation
+	TitleBlockCell *bottom_right_cell = getBottomRightCell(merged_cells);
+	if (!bottom_right_cell) return;
+	row_span_after_ = bottom_right_cell -> num_row - spanning_cell_ -> num_row;
+	col_span_after_ = bottom_right_cell -> num_col - spanning_cell_ -> num_col;
+	
+	setText(
+		QString(
+			QObject::tr(
+				"Fusion de %1 cellules",
+				"label used in the title block template editor undo list; %1 is the number of merged cells"
+			)
+		).arg(merged_cells.count())
+	);
+}
+
+/**
+	Destructor
+*/
+MergeCellsCommand::~MergeCellsCommand() {
+}
+
+/**
+	@return true if this command object is valid and usable, false otherwise.
+*/
+bool MergeCellsCommand::isValid() const {
+	// we consider having a non-zero spanning cell and positive spans makes a MergeCellsCommand valid
+	return(spanning_cell_ && row_span_after_ != -1 && col_span_after_ != -1);
+}
+
+/**
+	Undo the merge operation.
+*/
+void MergeCellsCommand::undo() {
+	if (!isValid()) return;
+	
+	// restore the original spanning_cell attribute of all impacted cells
+	foreach (TitleBlockCell *cell, spanner_cells_before_merge_.keys()) {
+		cell -> spanner_cell = spanner_cells_before_merge_[cell];
+	}
+	
+	// restore the row_span and col_span attributes of the spanning cell
+	spanning_cell_ -> row_span = row_span_before_;
+	spanning_cell_ -> col_span = col_span_before_;
+	
+	if (view_) view_ -> updateLayout();
+}
+
+/**
+	Apply the merge operation
+*/
+void MergeCellsCommand::redo() {
+	if (!isValid()) return;
+	
+	// set the spanning_cell attributes of spanned cells to the spanning cell
+	foreach (TitleBlockCell *cell, spanner_cells_before_merge_.keys()) {
+		if (cell == spanning_cell_) continue;
+		cell -> spanner_cell = spanning_cell_;
+	}
+	
+	// set the new values of the row_span and col_span attributes
+	spanning_cell_ -> row_span = row_span_after_;
+	spanning_cell_ -> col_span = col_span_after_;
+	
+	if (view_) view_ -> updateLayout();
+}
+
+/**
+	@param cells_set Set of title block template visual cells.
+	@return the bottom right logical cell within a set of visual cells.
+*/
+TitleBlockCell *MergeCellsCommand::getBottomRightCell(const TitleBlockTemplateCellsSet &cells_set) {
+	// first, we get the visual cell at the bottom right
+	TitleBlockTemplateVisualCell *bottom_right_cell = cells_set.bottomRightCell();
+	if (!bottom_right_cell) return(0);
+	
+	// next, we get its logical cells: the painted one and the spanned ones (if any)
+	QSet<TitleBlockCell *> logical_cells = bottom_right_cell -> cells();
+	if (logical_cells.isEmpty()) return(0);
+	if (logical_cells.count() == 1) return(logical_cells.toList().first());
+	
+	// we then look for the bottom right logical cell
+	int max_num_row = -1, max_num_col = -1;
+	TitleBlockCell *candidate = 0;
+	foreach(TitleBlockCell *cell, logical_cells) {
+		if (cell -> num_row > max_num_row) max_num_row = cell -> num_row;
+		if (cell -> num_col > max_num_col) max_num_col = cell -> num_col;
+		if (cell -> num_row == max_num_row && cell -> num_col == max_num_col) {
+			candidate = cell;
+		}
+	}
+	return(candidate);
+}
+
+/**
+	Construct a command object that acts on \a tbtemplate in order to split
+	\a splitted_cells.
+	Note: you should check the resulting object is valid using isValid().
+	@param splitted_cells Cell to be splitted.
+	@param tbtemplate Modified title block template.
+	@param parent Parent QUndoCommand.
+*/
+SplitCellsCommand::SplitCellsCommand(const TitleBlockTemplateCellsSet &splitted_cells, TitleBlockTemplate *tbtemplate, QUndoCommand *parent) :
+	TitleBlockTemplateCommand(tbtemplate, parent),
+	spanning_cell_(0),
+	row_span_before_(-1),
+	col_span_before_(-1)
+{
+	// basic check: the command applies to a single visual cell only
+	if (splitted_cells.count() != 1) return;
+	
+	// fetch the spanning cell
+	spanning_cell_ = splitted_cells.first() -> cell();
+	if (!spanning_cell_) return;
+	
+	// ensure the cell spans over other cells and therefore can be splitted
+	if (!spanning_cell_ -> spans()) return;
+	
+	// retrieve values necessary for the undo operation
+	spanned_cells_ = tbtemplate_ -> spannedCells(spanning_cell_);
+	row_span_before_ = spanning_cell_ -> row_span;
+	col_span_before_ = spanning_cell_ -> col_span;
+	
+	setText(
+		QString(
+			QObject::tr(
+				"S\xE9paration d'une cellule en %1",
+				"label used in the title block template editor undo list; %1 is the number of cells after the split"
+			)
+		).arg(spanned_cells_.count() + 1)
+	);
+}
+
+/**
+	Destructor
+*/
+SplitCellsCommand::~SplitCellsCommand() {
+}
+
+/**
+	@return true if this command object is valid and usable, false otherwise.
+*/
+bool SplitCellsCommand::isValid() const {
+	// we consider having a non-zero spanning cell and at least one spanned cell makes a SplitCellsCommand valid
+	return(spanning_cell_ && spanned_cells_.count());
+}
+
+/**
+	Undo the split operation
+*/
+void SplitCellsCommand::undo() {
+	if (!isValid()) return;
+	
+	// the spanned cells are spanned again
+	foreach(TitleBlockCell *cell, spanned_cells_) {
+		cell -> spanner_cell = spanning_cell_;
+	}
+	
+	// the spanning cell span again
+	spanning_cell_ -> row_span = row_span_before_;
+	spanning_cell_ -> col_span = col_span_before_;
+	
+	if (view_) view_ -> updateLayout();
+}
+
+/**
+	Apply the split operation
+*/
+void SplitCellsCommand::redo() {
+	if (!isValid()) return;
+	
+	// the spanned cells are not spanned anymore
+	foreach(TitleBlockCell *cell, spanned_cells_) {
+		cell -> spanner_cell = 0;
+	}
+	
+	// the spanning cell does not span anymore
+	spanning_cell_ -> row_span = 0;
+	spanning_cell_ -> col_span = 0;
+	
+	if (view_) view_ -> updateLayout();
+}

Added: branches/0.3/sources/titleblock/templatecommands.h
===================================================================
--- branches/0.3/sources/titleblock/templatecommands.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatecommands.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,225 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_TEMPLATE_COMMANDS_H
+#define TITLEBLOCK_SLASH_TEMPLATE_COMMANDS_H
+#define MODIFY_TITLE_BLOCK_CELL_COMMAND_ID 6378
+#include <QtCore>
+#include <QUndoCommand>
+#include "dimension.h"
+#include "templatecellsset.h"
+class TitleBlockTemplateView;
+class TitleBlockCell;
+class TitleBlockTemplate;
+
+/**
+	This class represents a set of modification applied to a title block
+	template cell.
+*/
+class ModifyTitleBlockCellCommand : public QUndoCommand {
+	// constructor, destructor
+	public:
+	ModifyTitleBlockCellCommand(TitleBlockCell *, QUndoCommand * = 0);
+	virtual ~ModifyTitleBlockCellCommand();
+	private:
+	ModifyTitleBlockCellCommand(const ModifyTitleBlockCellCommand &);
+	
+	// methods
+	public:
+	virtual int id() const;
+	virtual bool mergeWith(const QUndoCommand *);
+	virtual void undo();
+	virtual void redo();
+	TitleBlockCell *cell() const;
+	void setCell(TitleBlockCell *);
+	TitleBlockTemplateView *view() const;
+	void setView(TitleBlockTemplateView *);
+	void clear();
+	void addModification(const QString &, const QVariant &, bool = false);
+	
+	// attributes
+	private:
+	TitleBlockTemplateView *view_;        ///< This class may trigger a view update
+	TitleBlockCell *modified_cell_;       ///< modified cell
+	QHash<QString, QVariant> old_values_; ///< values before the cell is modified
+	QHash<QString, QVariant> new_values_; ///< values after the cell has been modified
+};
+
+/**
+	This class is a base class for any UndoCommand that needs to work with a
+	title block template.
+*/
+class TitleBlockTemplateCommand : public QUndoCommand {
+	// Constructors, destructor
+	public:
+	TitleBlockTemplateCommand(TitleBlockTemplate * = 0, QUndoCommand * = 0);
+	virtual ~TitleBlockTemplateCommand();
+	private:
+	TitleBlockTemplateCommand(const TitleBlockTemplateCommand &);
+	
+	// methods
+	public:
+	TitleBlockTemplate *titleBlockTemplate() const;
+	void setTitleBlockTemplate(TitleBlockTemplate *);
+	TitleBlockTemplateView *view() const;
+	void setView(TitleBlockTemplateView *);
+	
+	// attributes
+	protected:
+	TitleBlockTemplate *tbtemplate_; ///< Modified TitleBlock Template
+	TitleBlockTemplateView *view_;   ///< This class may trigger a view update
+};
+
+/**
+	This class represents the action of adding or deleting a row or column
+	within a title block template.
+*/
+class ModifyTemplateGridCommand : public TitleBlockTemplateCommand {
+	// static factory methods
+	public:
+	static ModifyTemplateGridCommand *addRow(TitleBlockTemplate *, int = -1);
+	static ModifyTemplateGridCommand *addColumn(TitleBlockTemplate *, int = -1);
+	static ModifyTemplateGridCommand *deleteRow(TitleBlockTemplate *, int = -1);
+	static ModifyTemplateGridCommand *deleteColumn(TitleBlockTemplate *, int = -1);
+	
+	// Constructors, destructor
+	public:
+	ModifyTemplateGridCommand(TitleBlockTemplate * = 0, QUndoCommand * = 0);
+	virtual ~ModifyTemplateGridCommand();
+	private:
+	ModifyTemplateGridCommand(const ModifyTemplateGridCommand &);
+	
+	// methods
+	public:
+	int index() const;
+	void setIndex(int);
+	QList<TitleBlockCell *> cells() const;
+	void setCells(const QList<TitleBlockCell *> &);
+	TitleBlockDimension dimension() const;
+	void setDimension(const TitleBlockDimension &);
+	int type() const;
+	void setType(bool);
+	bool isInsertion() const;
+	void setInsertion(bool);
+	virtual void undo();
+	virtual void redo();
+	
+	private:
+	void updateText();
+	void apply(bool = false);
+	
+	// attributes
+	private:
+	int index_;                     ///< Index of the inserted/deleted row/column
+	QList<TitleBlockCell *> cells_; ///< Cells composing the inserted/deleted row/column
+	bool type_;                     ///< true for a row, false for a column
+	TitleBlockDimension dimension_; ///< width/height of the column/row, which interpretation depends on type_
+	bool insertion_;                /// true if the row/column is inserted, false if it is deleted
+};
+
+/**
+	This class represents the action of changing the width/height of a
+	specific row/column within a title block template.
+*/
+class ModifyTemplateDimension : public TitleBlockTemplateCommand {
+	// Constructor, destructor
+	public:
+	ModifyTemplateDimension(TitleBlockTemplate * = 0, QUndoCommand * = 0);
+	virtual ~ModifyTemplateDimension();
+	private:
+	ModifyTemplateDimension(const ModifyTemplateDimension &);
+	
+	// methods
+	public:
+	int index() const;
+	void setIndex(int);
+	int type() const;
+	void setType(bool);
+	TitleBlockDimension dimensionBefore() const;
+	void setDimensionBefore(const TitleBlockDimension &);
+	TitleBlockDimension dimensionAfter() const;
+	void setDimensionAfter(const TitleBlockDimension &);
+	virtual void undo();
+	virtual void redo();
+	
+	private:
+	void updateText();
+	void apply(const TitleBlockDimension &);
+	
+	// attributes
+	private:
+	int index_;                   ///< Index of the resized row/column
+	bool type_;                   ///< true for a row, false for a column
+	TitleBlockDimension before_;  ///< Size of the row/column before it is changed
+	TitleBlockDimension after_;   ///< Size of the row/column after it is changed
+};
+
+
+/**
+	This class represents the action of merging 2 to n cells into a single one.
+*/
+class MergeCellsCommand : public TitleBlockTemplateCommand {
+	// Constructor, destructor
+	public:
+	MergeCellsCommand(const TitleBlockTemplateCellsSet &, TitleBlockTemplate * = 0, QUndoCommand * = 0);
+	virtual ~MergeCellsCommand();
+	
+	// methods
+	public:
+	bool isValid() const;
+	virtual void undo();
+	virtual void redo();
+	private:
+	static TitleBlockCell *getBottomRightCell(const TitleBlockTemplateCellsSet &);
+	
+	// attributes
+	private:
+	/// the cell spanning over the other ones
+	TitleBlockCell *spanning_cell_;
+	/// hash associating spanned cells with their spanner_cell attribute
+	/// before the merge operation
+	QHash<TitleBlockCell *, TitleBlockCell *> spanner_cells_before_merge_;
+	int row_span_before_; ///< the row_span attribute of the spanning cell before the merge
+	int col_span_before_; ///< the col_span attribute of the spanning cell before the merge
+	int row_span_after_;  ///< the row_span attribute of the spanning cell after the merge
+	int col_span_after_;  ///< the col_span attribute of the spanning cell after the merge
+};
+
+/**
+	This class represents the action of splitting a visual cell into at least two logical cells
+*/
+class SplitCellsCommand : public TitleBlockTemplateCommand {
+	// Constructor, destructor
+	public:
+	SplitCellsCommand(const TitleBlockTemplateCellsSet &, TitleBlockTemplate * = 0, QUndoCommand * = 0);
+	virtual ~SplitCellsCommand();
+	
+	// methods
+	public:
+	bool isValid() const;
+	virtual void undo();
+	virtual void redo();
+	
+	// attributes
+	private:
+	TitleBlockCell *spanning_cell_;        ///< the cell spanning over the other ones
+	QSet<TitleBlockCell *> spanned_cells_; ///< the spanned cells
+	int row_span_before_;                  ///< the row_span attribute of the spanning cell after the merge
+	int col_span_before_;                  ///< the col_span attribute of the spanning cell after the merge
+};
+
+#endif

Added: branches/0.3/sources/titleblock/templatelogomanager.cpp
===================================================================
--- branches/0.3/sources/titleblock/templatelogomanager.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatelogomanager.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,321 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "templatelogomanager.h"
+#include "titleblocktemplate.h"
+
+/**
+	Constructor
+	@param managed_template Title block template this widget manages logos for.
+	@param parent Parent QWidget.
+*/
+TitleBlockTemplateLogoManager::TitleBlockTemplateLogoManager(TitleBlockTemplate *managed_template, QWidget *parent) :
+	QWidget(parent),
+	managed_template_(managed_template)
+{
+	initWidgets();
+	fillView();
+}
+
+/**
+	Destructor
+*/
+TitleBlockTemplateLogoManager::~TitleBlockTemplateLogoManager() {
+}
+
+/**
+	@return the name of the currently selected logo, or a null QString if none
+	is selected.
+*/
+QString TitleBlockTemplateLogoManager::currentLogo() const {
+	if (!managed_template_) return QString();
+	
+	QListWidgetItem *current_item = logos_view_ -> currentItem();
+	if (!current_item) return QString();
+	
+	return(current_item -> text());
+}
+
+/**
+	Initialize widgets composing the Logo manager
+*/
+void TitleBlockTemplateLogoManager::initWidgets() {
+	open_dialog_dir_ = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation);
+	
+	setWindowTitle(tr("Gestionnaire de logos"));
+	logos_label_ = new QLabel(tr("Logos embarqu\351s dans ce mod\350le :"));
+	logos_view_ = new QListWidget();
+	logos_view_ -> setViewMode(QListView::IconMode);
+	logos_view_ -> setGridSize(iconsize() * 1.4);
+	logos_view_ -> setMinimumSize(iconsize() * 2.9);
+	logos_view_ -> setIconSize(iconsize());
+	logos_view_ -> setWrapping(true);
+	logos_view_ -> setFlow(QListView::LeftToRight);
+	logos_view_ -> setMovement(QListView::Static);
+	logos_view_ -> setResizeMode(QListView::Adjust);
+	add_button_ = new QPushButton(tr("Ajouter un logo"));
+	delete_button_ = new QPushButton(tr("Supprimer ce logo"));
+	logo_box_ = new QGroupBox(tr("Propri\351t\351s"));
+	logo_name_label_ = new QLabel(tr("Nom :"));
+	logo_name_ = new QLineEdit();
+	rename_button_ = new QPushButton(tr("Renommer"));
+	logo_type_ = new QLabel(tr("Type :"));
+	buttons_ = new QDialogButtonBox(QDialogButtonBox::Ok);
+	
+	hlayout1_ = new QHBoxLayout();
+	hlayout1_ -> addWidget(logo_name_label_);
+	hlayout1_ -> addWidget(logo_name_);
+	hlayout1_ -> addWidget(rename_button_);
+	
+	hlayout0_ = new QHBoxLayout();
+	hlayout0_ -> addWidget(add_button_);
+	hlayout0_ -> addWidget(delete_button_);
+	
+	vlayout1_ = new QVBoxLayout();
+	vlayout1_ -> addLayout(hlayout1_);
+	vlayout1_ -> addWidget(logo_type_);
+	logo_box_ -> setLayout(vlayout1_);
+	
+	vlayout0_ = new QVBoxLayout();
+	vlayout0_ -> addWidget(logos_label_);
+	vlayout0_ -> addWidget(logos_view_);
+	vlayout0_ -> addLayout(hlayout0_);
+	vlayout0_ -> addWidget(logo_box_);
+	setLayout(vlayout0_);
+	
+	connect(
+		logos_view_,
+		SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)),
+		this,
+		SLOT(updateLogoInformations(QListWidgetItem *, QListWidgetItem *))
+	);
+	connect(add_button_, SIGNAL(released()), this, SLOT(addLogo()));
+	connect(delete_button_, SIGNAL(released()), this, SLOT(removeLogo()));
+	connect(rename_button_, SIGNAL(released()), this, SLOT(renameLogo()));
+}
+
+/**
+	Update the logos display.
+*/
+void TitleBlockTemplateLogoManager::fillView() {
+	if (!managed_template_) return;
+	logos_view_ -> clear();
+	
+	foreach (QString logo_name, managed_template_ -> logos()) {
+		QIcon current_icon;
+		QPixmap current_logo = managed_template_ -> bitmapLogo(logo_name);
+		if (!current_logo.isNull()) {
+			current_icon = QIcon(current_logo);
+		} else {
+			QSvgRenderer *svg_logo = managed_template_ -> vectorLogo(logo_name);
+			if (svg_logo) {
+				QPixmap *svg_pixmap = new QPixmap(iconsize());
+				svg_pixmap -> fill();
+				QPainter p;
+				p.begin(svg_pixmap);
+				svg_logo -> render(&p);
+				p.end();
+				current_icon = QIcon(*svg_pixmap);
+			}
+		}
+		QListWidgetItem *qlwi = new QListWidgetItem(current_icon, logo_name);
+		qlwi -> setTextAlignment(Qt::AlignBottom | Qt::AlignHCenter);
+		logos_view_ -> insertItem(0, qlwi);
+	}
+	
+	QListWidgetItem *current_item = logos_view_ -> currentItem();
+	updateLogoInformations(current_item, 0);
+}
+
+/**
+	@return the icon size to display the logos embedded within the managed
+	template.
+*/
+QSize TitleBlockTemplateLogoManager::iconsize() const {
+	return(QSize(80, 80));
+}
+
+/**
+	When adding a logo, it may occur its name is already used by another
+	pre-existing logo. This method asks users whether they want to erase the
+	existing logo, change the initial name or simply cancel the operation.
+	@param initial_name Initial name of the logo to be added
+	@return Either a null QString if the user cancelled the operation, or the
+	name to be used when adding the logo.
+*/
+QString TitleBlockTemplateLogoManager::confirmLogoName(const QString &initial_name) {
+	QString name = initial_name;
+	QDialog *rename_dialog = 0;
+	QLabel *rd_label = 0;
+	QLineEdit *rd_input = 0;
+	while (managed_template_ -> logos().contains(name)) {
+		if (!rename_dialog) {
+			rename_dialog = new QDialog(this);
+			rename_dialog -> setWindowTitle(tr("Logo d\351j\340 existant"));
+			
+			rd_label = new QLabel();
+			rd_label -> setWordWrap(true);
+			rd_input = new QLineEdit();
+			QDialogButtonBox *rd_buttons = new QDialogButtonBox();
+			QPushButton *replace_button = rd_buttons -> addButton(tr("Remplacer"), QDialogButtonBox::YesRole);
+			QPushButton *rename_button  = rd_buttons -> addButton(tr("Renommer"),  QDialogButtonBox::NoRole);
+			QPushButton *cancel_button  = rd_buttons -> addButton(QDialogButtonBox::Cancel);
+			
+			QVBoxLayout *rd_vlayout0 = new QVBoxLayout();
+			rd_vlayout0 -> addWidget(rd_label);
+			rd_vlayout0 -> addWidget(rd_input);
+			rd_vlayout0 -> addWidget(rd_buttons);
+			rename_dialog -> setLayout(rd_vlayout0);
+			
+			QSignalMapper *signal_mapper = new QSignalMapper(rename_dialog);
+			signal_mapper -> setMapping(replace_button, QDialogButtonBox::YesRole);
+			signal_mapper -> setMapping(rename_button,  QDialogButtonBox::NoRole);
+			signal_mapper -> setMapping(cancel_button,  QDialogButtonBox::RejectRole);
+			connect(replace_button, SIGNAL(clicked()), signal_mapper, SLOT(map()));
+			connect(rename_button,  SIGNAL(clicked()), signal_mapper, SLOT(map()));
+			connect(cancel_button,  SIGNAL(clicked()), signal_mapper, SLOT(map()));
+			connect(signal_mapper, SIGNAL(mapped(int)), rename_dialog, SLOT(done(int)));
+		}
+		rd_label -> setText(
+			QString(tr(
+				"Il existe d\351j\340 un logo portant le nom \"%1\" au sein de "
+				"ce mod\350le de cartouche. Voulez-vous le remplacer ou "
+				"pr\351f\351rez-vous sp\351cifier un autre nom pour ce nouveau "
+				"logo ?"
+			)).arg(name)
+		);
+		rd_input -> setText(name);
+		int answer = rename_dialog -> exec();
+		if (answer == QDialogButtonBox::YesRole) {
+			// we can use the initial name
+			break;
+		} else if (answer == QDialogButtonBox::NoRole) {
+			// the user provided another name
+			name = rd_input -> text();
+			/// TODO prevent the user from entering an empty name
+		} else {
+			// the user cancelled the operation
+			return(QString());
+		}
+	};
+	return(name);
+}
+
+/**
+	Update the displayed informations relative to the currently selected logo.
+	@param current  Newly selected logo item
+	@param previous Previously selected logo item
+*/
+void TitleBlockTemplateLogoManager::updateLogoInformations(QListWidgetItem *current, QListWidgetItem *previous) {
+	Q_UNUSED(previous);
+	if (current) {
+		QString logo_name = current -> text();
+		logo_name_ -> setText(logo_name);
+		if (managed_template_) {
+			QString logo_type = managed_template_ -> logoType(logo_name);
+			logo_type_ -> setText(tr("Type : %1").arg(logo_type));
+		}
+	} else {
+		logo_name_ -> setText(QString());
+		logo_type_ -> setText(tr("Type :"));
+	}
+}
+
+/**
+	Ask the user for a filepath, and add it as a new logo in the managed
+	template.
+*/
+void TitleBlockTemplateLogoManager::addLogo() {
+	if (!managed_template_) return;
+	
+	QString filepath = QFileDialog::getOpenFileName(
+		this,
+		tr("Choisir une image / un logo"),
+		open_dialog_dir_.absolutePath(),
+		tr("Images vectorielles (*.svg);;Images bitmap (*.png *.jpg *.jpeg *.gif *.bmp *.xpm);;Tous les fichiers (*)")
+	);
+	if (filepath.isEmpty()) return;
+	
+	// that filepath needs to point to a valid, readable file
+	QFileInfo filepath_info(filepath);
+	if (!filepath_info.exists() || !filepath_info.isReadable()) {
+		QMessageBox::critical(this, tr("Erreur"), tr("Impossible d'ouvrir le fichier sp\351cifi\351"));
+		return;
+	}
+	
+	// ensure we can use the file name to add the logo
+	QString logo_name = confirmLogoName(filepath_info.fileName());
+	if (logo_name.isNull()) return;
+	
+	open_dialog_dir_ = QDir(filepath);
+	if (managed_template_ -> addLogoFromFile(filepath, logo_name)) {
+		fillView();
+	}
+}
+
+/**
+	Delete the currently selected logo.
+*/
+void TitleBlockTemplateLogoManager::removeLogo() {
+	QString current_logo = currentLogo();
+	if (current_logo.isNull()) return;
+	
+	if (managed_template_ -> removeLogo(current_logo)) {
+		fillView();	
+	}
+}
+
+/**
+	Rename currently selected logo.
+*/
+void TitleBlockTemplateLogoManager::renameLogo() {
+	QString current_logo = currentLogo();
+	if (current_logo.isNull()) return;
+	
+	QString entered_name = logo_name_ -> text();
+	QString warning_title = tr("Renommer un logo");
+	if (entered_name == current_logo) {
+		QMessageBox::warning(
+			this,
+			warning_title,
+			tr("Vous devez saisir un nouveau nom.")
+		);
+		return;
+	}
+	
+	if (entered_name.trimmed().isEmpty()) {
+		QMessageBox::warning(
+			this,
+			warning_title,
+			tr("Le nouveau nom ne peut pas \352tre vide.")
+		);
+		return;
+	}
+	
+	if (managed_template_ -> logos().contains(entered_name)) {
+		QMessageBox::warning(
+			this,
+			warning_title,
+			tr("Le nom saisi est d\351j\340 utilis\351 par un autre logo.")
+		);
+		return;
+	}
+	
+	if (managed_template_ -> renameLogo(current_logo, entered_name)) {
+		fillView();
+	}
+}

Added: branches/0.3/sources/titleblock/templatelogomanager.h
===================================================================
--- branches/0.3/sources/titleblock/templatelogomanager.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatelogomanager.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,68 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_TEMPLATE_LOGO_MANAGER
+#define TITLEBLOCK_SLASH_TEMPLATE_LOGO_MANAGER
+#include <QtGui>
+class TitleBlockTemplate;
+
+/**
+	This widget allows users to manage (list, add, edit, delete) logos embedded
+	within a title block template.
+*/
+class TitleBlockTemplateLogoManager : public QWidget {
+	Q_OBJECT
+	// Constructor, destructor
+	public:
+	TitleBlockTemplateLogoManager(TitleBlockTemplate *, QWidget * = 0);
+	virtual ~TitleBlockTemplateLogoManager();
+	
+	// methods
+	public:
+	QString currentLogo() const;
+	
+	protected:
+	private:
+	void initWidgets();
+	void fillView();
+	QSize iconsize() const;
+	QString confirmLogoName(const QString &);
+	
+	private slots:
+	void updateLogoInformations(QListWidgetItem *, QListWidgetItem *);
+	void addLogo();
+	void removeLogo();
+	void renameLogo();
+	
+	// attributes
+	private:
+	TitleBlockTemplate *managed_template_; ///< title block template which this class manages logos
+	QVBoxLayout *vlayout0_, *vlayout1_;    ///< vertical layouts
+	QHBoxLayout *hlayout0_, *hlayout1_;    ///< horizontal layouts
+	QLabel *logos_label_;                  ///< simple displayed label
+	QListWidget *logos_view_;              ///< area showing the logos
+	QPushButton *add_button_;              ///< button to add a new logo
+	QPushButton *delete_button_;           ///< button to delete an embeded logo
+	QGroupBox *logo_box_;                  ///< current logo properties box
+	QLabel *logo_name_label_;              ///< "name:" label
+	QLineEdit *logo_name_;                 ///< current logo name
+	QPushButton *rename_button_;           ///< button to rename the current logo
+	QLabel *logo_type_;                    ///< current logo type
+	QDialogButtonBox *buttons_;            ///< ok/cancel buttons
+	QDir open_dialog_dir_;                 ///< last opened directory
+};
+#endif

Added: branches/0.3/sources/titleblock/templateview.cpp
===================================================================
--- branches/0.3/sources/titleblock/templateview.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/templateview.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,775 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "templateview.h"
+#include "templatevisualcell.h"
+#include "gridlayoutanimation.h"
+#include "helpercell.h"
+#include "splittedhelpercell.h"
+#include "templatecommands.h"
+#include "templatecellsset.h"
+#include "dimensionwidget.h"
+#include "qetapp.h"
+#define ROW_OFFSET 2
+#define COL_OFFSET 1
+#define DEFAULT_PREVIEW_WIDTH 600
+
+/**
+	Constructor
+	@param parent Parent QWidget.
+*/
+TitleBlockTemplateView::TitleBlockTemplateView(QWidget *parent) :
+	QGraphicsView(parent),
+	tbtemplate_(0),
+	tbgrid_(0),
+	form_(0),
+	preview_width_(DEFAULT_PREVIEW_WIDTH),
+	apply_columns_widths_count_(0),
+	apply_rows_heights_count_(0)
+{
+	init();
+}
+
+/**
+	Constructor
+	@param parent Parent QWidget.
+*/
+TitleBlockTemplateView::TitleBlockTemplateView(QGraphicsScene *scene, QWidget *parent) :
+	QGraphicsView(scene, parent),
+	tbtemplate_(0),
+	tbgrid_(0),
+	preview_width_(DEFAULT_PREVIEW_WIDTH),
+	apply_columns_widths_count_(0),
+	apply_rows_heights_count_(0)
+{
+	init();
+}
+
+/**
+	Destructor
+*/
+TitleBlockTemplateView::~TitleBlockTemplateView() {
+}
+
+/**
+	@param tbtemplate Title block template to be rendered by this view.
+	If set to zero, the View will render nothing.
+*/
+void TitleBlockTemplateView::setTitleBlockTemplate(TitleBlockTemplate *tbtemplate) {
+	loadTemplate(tbtemplate);
+}
+
+/**
+	@return The title block template object rendered by this view.
+*/
+TitleBlockTemplate *TitleBlockTemplateView::titleBlockTemplate() const {
+	return(tbtemplate_);
+}
+
+/**
+	Emits the selectedCellsChanged() signal with the currently selected cells.
+*/
+void TitleBlockTemplateView::selectionChanged() {
+	emit(selectedCellsChanged(selectedCells()));
+}
+
+/**
+	Zoom in by zoomFactor().
+	@see zoomFactor()
+*/
+void TitleBlockTemplateView::zoomIn() {
+	scale(zoomFactor(), zoomFactor());
+}
+
+/**
+	Zoom out by zoomFactor().
+	@see zoomFactor()
+*/
+void TitleBlockTemplateView::zoomOut() {
+	qreal zoom_factor = 1.0/zoomFactor();
+	scale(zoom_factor, zoom_factor);
+}
+
+/**
+	Add a column right before the last index selected when calling the context
+	menu.
+*/
+void TitleBlockTemplateView::addColumnBefore() {
+	int index = lastContextMenuCellIndex();
+	if (index == -1) return;
+	requestGridModification(ModifyTemplateGridCommand::addColumn(tbtemplate_, index));
+}
+
+/**
+	Add a row right before the last index selected when calling the context
+	menu.
+*/
+void TitleBlockTemplateView::addRowBefore() {
+	int index = lastContextMenuCellIndex();
+	if (index == -1) return;
+	requestGridModification(ModifyTemplateGridCommand::addRow(tbtemplate_, index));
+}
+
+/**
+	Add a column right after the last index selected when calling the context
+	menu.
+*/
+void TitleBlockTemplateView::addColumnAfter() {
+	int index = lastContextMenuCellIndex();
+	if (index == -1) return;
+	requestGridModification(ModifyTemplateGridCommand::addColumn(tbtemplate_, index + 1));
+}
+
+/**
+	Add a row right after the last index selected when calling the context
+	menu.
+*/
+void TitleBlockTemplateView::addRowAfter() {
+	int index = lastContextMenuCellIndex();
+	if (index == -1) return;
+	requestGridModification(ModifyTemplateGridCommand::addRow(tbtemplate_, index + 1));
+}
+
+/**
+	Edit the width of a column.
+	@param cell (optional) HelperCell of the column to be modified. If 0, this
+	method uses the last index selected when calling the context menu.
+*/
+void TitleBlockTemplateView::editColumn(HelperCell *cell) {
+	int index = cell ? cell -> index : lastContextMenuCellIndex();
+	if (index == -1) return;
+	
+	TitleBlockDimension dimension_before = tbtemplate_ -> columnDimension(index);
+	TitleBlockDimensionWidget dialog(true, this);
+	dialog.setWindowTitle(tr("Changer la largeur de la colonne", "window title when changing a column with"));
+	dialog.label() -> setText(tr("Largeur :", "text before the spinbox to change a column width"));
+	dialog.setValue(dimension_before);
+	if (dialog.exec() == QDialog::Accepted) {
+		ModifyTemplateDimension *command = new ModifyTemplateDimension(tbtemplate_);
+		command -> setType(false);
+		command -> setIndex(index);
+		command -> setDimensionBefore(dimension_before);
+		command -> setDimensionAfter(dialog.value());
+		requestGridModification(command);
+	}
+}
+
+/**
+	Edit the height of a row.
+	@param cell (optional) HelperCell of the row to be modified. If 0, this
+	method uses the last index selected when calling the context menu.
+*/
+void TitleBlockTemplateView::editRow(HelperCell *cell) {
+	int index = cell ? cell -> index : lastContextMenuCellIndex();
+	if (index == -1) return;
+	
+	TitleBlockDimension dimension_before = TitleBlockDimension(tbtemplate_ -> rowDimension(index));
+	TitleBlockDimensionWidget dialog(false, this);
+	dialog.setWindowTitle(tr("Changer la hauteur de la ligne", "window title when changing a row height"));
+	dialog.label() -> setText(tr("Hauteur :", "text before the spinbox to change a row height"));
+	dialog.setValue(dimension_before);
+	if (dialog.exec() == QDialog::Accepted) {
+		ModifyTemplateDimension *command = new ModifyTemplateDimension(tbtemplate_);
+		command -> setType(true);
+		command -> setIndex(index);
+		command -> setDimensionBefore(dimension_before);
+		command -> setDimensionAfter(dialog.value());
+		requestGridModification(command);
+	}
+}
+
+/**
+	Remove the column at the last index selected when calling the context menu.
+*/
+void TitleBlockTemplateView::deleteColumn() {
+	int index = lastContextMenuCellIndex();
+	if (index == -1) return;
+	requestGridModification(ModifyTemplateGridCommand::deleteColumn(tbtemplate_, index));
+}
+
+/**
+	Remove the row at the last index selected when calling the context menu.
+*/
+void TitleBlockTemplateView::deleteRow() {
+	int index = lastContextMenuCellIndex();
+	if (index == -1) return;
+	requestGridModification(ModifyTemplateGridCommand::deleteRow(tbtemplate_, index));
+}
+
+/**
+	Merge the selected cells.
+*/
+void TitleBlockTemplateView::mergeSelectedCells() {
+	// retrieve the selected cells
+	TitleBlockTemplateCellsSet selected_cells = selectedCellsSet();
+	
+	// merging applies only to cells composing a rectangle
+	if (!selected_cells.isRectangle()) {
+		qDebug() << "selected cells are not composing a rectangle";
+		return;
+	}
+	
+	// the merge area may also be too small
+	if (selected_cells.count() < 2) {
+		qDebug() << "the merge area does not even contain 2 selected and mergeable cells";
+		return;
+	}
+	
+	qDebug() << Q_FUNC_INFO << "ok, ready for cells merge";
+	MergeCellsCommand *merge_command = new MergeCellsCommand(selected_cells, tbtemplate_);
+	if (merge_command -> isValid()) requestGridModification(merge_command);
+}
+
+/**
+	Split the selected cell.
+*/
+void TitleBlockTemplateView::splitSelectedCell() {
+	// retrieve the selected cells
+	TitleBlockTemplateCellsSet selected_cells = selectedCellsSet();
+	
+	// we expect only one visual cell to be selected
+	if (selected_cells.count() != 1) {
+		qDebug() << "please select a single cell";
+		return;
+	}
+	
+	SplitCellsCommand *split_command = new SplitCellsCommand(selected_cells, tbtemplate_);
+	if (split_command -> isValid()) requestGridModification(split_command);
+}
+
+/**
+	Reimplement the way the background is drawn to render the title block
+	template.
+*/
+void TitleBlockTemplateView::drawBackground(QPainter *painter, const QRectF &rect) {
+	QGraphicsView::drawBackground(painter, rect);
+	if (!tbtemplate_) return; // TODO shouldn't we draw a large uniform rect?
+}
+
+/**
+	@return the selected logical cells, not including the spanned ones.
+*/
+QList<TitleBlockCell *> TitleBlockTemplateView::selectedCells() const {
+	return(selectedCellsSet().cells(false).toList());
+}
+
+/**
+	@return the selected visual cells.
+*/
+TitleBlockTemplateCellsSet TitleBlockTemplateView::selectedCellsSet() const {
+	return(makeCellsSetFromGraphicsItems(scene() -> selectedItems()));
+}
+
+/**
+	@return the visual cells contained in the \a rect
+	@param rect Rectangle in the coordinates of the QGraphicsWidget
+	representing the title block template.
+*/
+TitleBlockTemplateCellsSet TitleBlockTemplateView::cells(const QRectF &rect) const {
+	QPolygonF mapped_rect(form_ -> mapToScene(rect));
+	QList<QGraphicsItem *> items = scene() -> items(mapped_rect, Qt::IntersectsItemShape);
+	return(makeCellsSetFromGraphicsItems(items));
+}
+
+/**
+	Handles mouse wheel-related actions
+	@param e QWheelEvent describing the wheel event
+*/
+void TitleBlockTemplateView::wheelEvent(QWheelEvent *e) {
+	// si la touche Ctrl est enfoncee, on zoome / dezoome
+	if (e -> modifiers() & Qt::ControlModifier) {
+		if (e -> delta() > 0) { 
+			zoomIn();
+		} else {
+			zoomOut();
+		}
+	} else {
+		QAbstractScrollArea::wheelEvent(e);
+	}
+}
+
+/**
+	@return the zoom factor used by zoomIn() and zoomOut().
+*/
+qreal TitleBlockTemplateView::zoomFactor() const {
+	return(1.1);
+}
+
+/**
+	Initialize this view (actions, signals/slots connections, etc.)
+*/
+void TitleBlockTemplateView::init() {
+	add_column_before_    = new QAction(tr("Ajouter une colonne (avant)",              "context menu"), this);
+	add_row_before_       = new QAction(tr("Ajouter une ligne (avant)",                "context menu"), this);
+	add_column_after_     = new QAction(tr("Ajouter une colonne (apr\350s)",           "context menu"), this);
+	add_row_after_        = new QAction(tr("Ajouter une ligne (apr\350s)",             "context menu"), this);
+	edit_column_dim_      = new QAction(tr("Modifier les dimensions de cette colonne", "context menu"), this);
+	edit_row_dim_         = new QAction(tr("Modifier les dimensions de cette ligne",   "context menu"), this);
+	delete_column_        = new QAction(tr("Supprimer cette colonne",                  "context menu"), this);
+	delete_row_           = new QAction(tr("Supprimer cette ligne",                    "context menu"), this);
+	change_preview_width_ = new QAction(tr("Modifier la largeur de cet aper\347u",     "context menu"), this);
+	
+	connect(add_column_before_,    SIGNAL(triggered()), this, SLOT(addColumnBefore()));
+	connect(add_row_before_,       SIGNAL(triggered()), this, SLOT(addRowBefore()));
+	connect(add_column_after_,     SIGNAL(triggered()), this, SLOT(addColumnAfter()));
+	connect(add_row_after_,        SIGNAL(triggered()), this, SLOT(addRowAfter()));
+	connect(edit_column_dim_,      SIGNAL(triggered()), this, SLOT(editColumn()));
+	connect(edit_row_dim_,         SIGNAL(triggered()), this, SLOT(editRow()));
+	connect(delete_column_,        SIGNAL(triggered()), this, SLOT(deleteColumn()));
+	connect(delete_row_,           SIGNAL(triggered()), this, SLOT(deleteRow()));
+	connect(change_preview_width_, SIGNAL(triggered()), this, SLOT(changePreviewWidth()));
+	
+	setBackgroundBrush(QBrush(QColor(248, 255, 160)));
+	
+	connect(scene(), SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
+}
+
+/**
+	Apply the columns widths currently specified by the edited title block
+	template.
+	@param animate true to animate the change, false otherwise.
+*/
+void TitleBlockTemplateView::applyColumnsWidths(bool animate) {
+	// the first column is dedicated to helper cells showing the rows height
+	tbgrid_ -> setColumnFixedWidth(0, 50);
+	tbgrid_ -> setColumnSpacing(0, 0);
+	
+	// we apply the other columns width based on the title block template data
+	QList<int> widths = tbtemplate_ -> columnsWidth(preview_width_);
+	int total_applied_width = 0;
+	for (int i = 0 ; i < widths.count() ; ++ i) {
+		int applied_width = qMax(0, widths.at(i));
+		tbgrid_ -> setColumnSpacing(COL_OFFSET + i, 0);
+		if (!animate) {
+			// no animation on first call
+			tbgrid_ -> setColumnFixedWidth(COL_OFFSET + i, widths.at(i));
+		} else {
+			GridLayoutAnimation *animation = new GridLayoutAnimation(tbgrid_, form_);
+			animation -> setIndex(COL_OFFSET + i);
+			animation -> setActsOnRows(false);
+			animation -> setStartValue(QVariant(tbgrid_ -> columnMinimumWidth(COL_OFFSET + i)));
+			animation -> setEndValue(QVariant(1.0 * applied_width));
+			animation -> setDuration(500);
+			connect(animation, SIGNAL(finished()), this, SLOT(updateColumnsHelperCells()));
+			animation -> start(QAbstractAnimation::DeleteWhenStopped);
+		}
+		total_applied_width += applied_width;
+	}
+	if (!animate) updateColumnsHelperCells();
+	++ apply_columns_widths_count_;
+	
+	// we systematically parameter some cells
+	total_width_helper_cell_ -> split_size = 0;
+	tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count());
+	removeItem(extra_cells_width_helper_cell_);
+	
+	if (total_applied_width < preview_width_) {
+		// preview_width is greater than the sum of cells widths
+		// we add an extra column with a helper cell
+		tbgrid_ -> addItem(extra_cells_width_helper_cell_, ROW_OFFSET - 1, COL_OFFSET + widths.count(), tbtemplate_ -> rowsCount() + 1, 1);
+		tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count() + 1);
+		tbgrid_ -> setColumnFixedWidth(COL_OFFSET + widths.count(), preview_width_ - total_applied_width);
+		extra_cells_width_helper_cell_ -> label = QString(
+			tr("[%1px]","content of the extra cell added when the total width of cells is less than the preview width")
+		).arg(preview_width_ - total_applied_width);
+	} else if (total_applied_width > preview_width_) {
+		// preview width is smaller than the sum of cells widths
+		// we draw an extra header within th "preview width" cell.
+		tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, widths.count());
+		total_width_helper_cell_ -> split_background_color = QColor(Qt::red);
+		total_width_helper_cell_ -> split_foreground_color = QColor(Qt::black);
+		total_width_helper_cell_ -> split_label = QString(
+			tr("[%1px]", "content of the extra helper cell added when the total width of cells is greather than the preview width")
+		).arg(total_applied_width - preview_width_);
+		total_width_helper_cell_ -> split_size = total_applied_width - preview_width_;
+	}
+}
+
+/**
+	Apply the rows heights currently specified by the edited title block
+	template.
+	@param animate true to animate the change, false otherwise.
+*/
+void TitleBlockTemplateView::applyRowsHeights(bool animate) {
+	// the first row is dedicated to a helper cell showing the total width
+	tbgrid_ -> setRowFixedHeight(0, 15);
+	tbgrid_ -> setRowSpacing(0, 0);
+	// the second row is dedicated to helper cells showing the columns width
+	tbgrid_ -> setRowFixedHeight(1, 15);
+	tbgrid_ -> setRowSpacing(1, 0);
+	// the first column is dedicated to helper cells showing the rows height
+	tbgrid_ -> setColumnFixedWidth(0, 45);
+	tbgrid_ -> setColumnSpacing(0, 0);
+	
+	QList<int> heights = tbtemplate_ -> rowsHeights();
+	for (int i = 0 ; i < heights.count() ; ++ i) {
+		tbgrid_ -> setRowSpacing(ROW_OFFSET + i, 0);
+		if (!animate) {
+			// no animation on first call
+			tbgrid_ -> setRowFixedHeight(ROW_OFFSET + i, heights.at(i));
+		} else {
+			GridLayoutAnimation *animation = new GridLayoutAnimation(tbgrid_, form_);
+			animation -> setIndex(ROW_OFFSET + i);
+			animation -> setActsOnRows(true);
+			animation -> setStartValue(QVariant(tbgrid_ -> rowMinimumHeight(ROW_OFFSET + i)));
+			animation -> setEndValue(QVariant(1.0 * heights.at(i)));
+			animation -> setDuration(500);
+			connect(animation, SIGNAL(finished()), this, SLOT(updateRowsHelperCells()));
+			animation -> start(QAbstractAnimation::DeleteWhenStopped);
+		}
+		
+	}
+	if (!animate) updateRowsHelperCells();
+	++ apply_rows_heights_count_;
+}
+
+/**
+	Update the content (type and value) of rows helper cells.
+*/
+void TitleBlockTemplateView::updateRowsHelperCells() {
+	int row_count = tbtemplate_ -> rowsCount();
+	QList<int> heights = tbtemplate_ -> rowsHeights();
+	for (int i = 0 ; i < row_count ; ++ i) {
+		HelperCell *current_row_cell = static_cast<HelperCell *>(tbgrid_ -> itemAt(ROW_OFFSET + i, 0));
+		current_row_cell -> setType(QET::Absolute); // rows always have absolute heights
+		current_row_cell -> label = QString(tr("%1px", "format displayed in rows helper cells")).arg(heights.at(i));
+	}
+}
+
+/**
+	Update the content (type and value) of columns helper cells.
+*/
+void TitleBlockTemplateView::updateColumnsHelperCells() {
+	int col_count = tbtemplate_ -> columnsCount();
+	for (int i = 0 ; i < col_count ; ++ i) {
+		TitleBlockDimension current_col_dim = tbtemplate_ -> columnDimension(i);
+		HelperCell *current_col_cell = static_cast<HelperCell *>(tbgrid_ -> itemAt(1, COL_OFFSET + i));
+		current_col_cell -> setType(current_col_dim.type);
+		current_col_cell -> label = current_col_dim.toString();
+	}
+}
+
+/**
+	Add the cells (both helper cells and regular visual cells) to the scene to
+	get a visual representation of the edited title block template.
+*/
+void TitleBlockTemplateView::addCells() {
+	int col_count = tbtemplate_ -> columnsCount();
+	int row_count = tbtemplate_ -> rowsCount();
+	if (row_count < 1 || col_count < 1) return;
+	
+	// we add a big cell to show the total width
+	total_width_helper_cell_ = new SplittedHelperCell();
+	total_width_helper_cell_ -> setType(QET::Absolute);
+	updateTotalWidthLabel();
+	total_width_helper_cell_ -> orientation = Qt::Horizontal;
+	total_width_helper_cell_ -> setActions(QList<QAction *>() << change_preview_width_);
+	connect(total_width_helper_cell_, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *)));
+	connect(total_width_helper_cell_, SIGNAL(doubleClicked(HelperCell*)),         this, SLOT(changePreviewWidth()));
+	tbgrid_ -> addItem(total_width_helper_cell_, 0, COL_OFFSET, 1, col_count);
+	
+	// we also initialize an extra helper cells that shows the preview width is
+	// too long for the current cells widths
+	extra_cells_width_helper_cell_ = new HelperCell();
+	extra_cells_width_helper_cell_ -> background_color = QColor(Qt::red);
+	
+	// we add one cell per column to show their respective width
+	for (int i = 0 ; i < col_count ; ++ i) {
+		TitleBlockDimension current_col_dim = tbtemplate_ -> columnDimension(i);
+		HelperCell *current_col_cell = new HelperCell();
+		current_col_cell -> setType(current_col_dim.type);
+		current_col_cell -> label = current_col_dim.toString();
+		current_col_cell -> setActions(columnsActions());
+		current_col_cell -> orientation = Qt::Horizontal;
+		current_col_cell -> index = i;
+		connect(current_col_cell, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *)));
+		connect(current_col_cell, SIGNAL(doubleClicked(HelperCell*)),         this, SLOT(editColumn(HelperCell *)));
+		tbgrid_ -> addItem(current_col_cell, 1, COL_OFFSET + i, 1, 1);
+	}
+	
+	// we add one cell per row to show their respective height
+	QList<int> heights = tbtemplate_ -> rowsHeights();
+	for (int i = 0 ; i < row_count ; ++ i) {
+		HelperCell *current_row_cell = new HelperCell();
+		current_row_cell -> setType(QET::Absolute); // rows always have absolute heights
+		current_row_cell -> label = QString(tr("%1px")).arg(heights.at(i));
+		current_row_cell -> orientation = Qt::Vertical;
+		current_row_cell -> index = i;
+		current_row_cell -> setActions(rowsActions());
+		connect(current_row_cell, SIGNAL(contextMenuTriggered(HelperCell *)), this, SLOT(updateLastContextMenuCell(HelperCell *)));
+		connect(current_row_cell, SIGNAL(doubleClicked(HelperCell*)),         this, SLOT(editRow(HelperCell *)));
+		tbgrid_ -> addItem(current_row_cell, ROW_OFFSET + i, 0, 1, 1);
+	}
+	
+	// eventually we add the cells composing the titleblock template
+	for (int i = 0 ; i < col_count ; ++ i) {
+		for (int j = 0 ; j < row_count ; ++ j) {
+			TitleBlockCell *cell = tbtemplate_ -> cell(j, i);
+			if (cell -> spanner_cell) continue;
+			TitleBlockTemplateVisualCell *cell_item = new TitleBlockTemplateVisualCell();
+			cell_item -> setTemplateCell(tbtemplate_, cell);
+			tbgrid_ -> addItem(cell_item, ROW_OFFSET + j, COL_OFFSET + i, cell -> row_span + 1, cell -> col_span + 1);
+		}
+	}
+}
+
+/**
+	Refresh the regular cells.
+*/
+void TitleBlockTemplateView::refresh() {
+	int col_count = tbtemplate_ -> columnsCount();
+	int row_count = tbtemplate_ -> rowsCount();
+	if (row_count < 1 || col_count < 1) return;
+	
+	for (int i = 0 ; i < col_count ; ++ i) {
+		for (int j = 0 ; j < row_count ; ++ j) {
+			if (QGraphicsLayoutItem *item = tbgrid_ -> itemAt(ROW_OFFSET + j, COL_OFFSET + i)) {
+				if (QGraphicsItem *qgi = dynamic_cast<QGraphicsItem *>(item)) {
+					qgi -> update();
+				}
+			}
+		}
+	}
+}
+
+/**
+	Ask the user a new width for the preview
+*/
+void TitleBlockTemplateView::changePreviewWidth() {
+	TitleBlockDimensionWidget dialog(false, this);
+	dialog.setWindowTitle(tr("Changer la largeur de l'aper\347u"));
+	dialog.label() -> setText(tr("Largeur de l'aper\347u :"));
+	dialog.setValue(TitleBlockDimension(preview_width_));
+	if (dialog.exec() == QDialog::Accepted) {
+		setPreviewWidth(dialog.value().value);
+	}
+}
+
+/**
+	Fill the layout with empty cells where needed.
+*/
+void TitleBlockTemplateView::fillWithEmptyCells() {
+	int col_count = tbtemplate_ -> columnsCount();
+	int row_count = tbtemplate_ -> rowsCount();
+	if (row_count < 1 || col_count < 1) return;
+	
+	for (int i = 0 ; i < col_count ; ++ i) {
+		for (int j = 0 ; j < row_count ; ++ j) {
+			if (tbgrid_ -> itemAt(ROW_OFFSET + j, COL_OFFSET + i)) continue;
+			qDebug() << Q_FUNC_INFO << "looks like there is nothing there (" << j << "," << i << ")";
+			TitleBlockTemplateVisualCell *cell_item = new TitleBlockTemplateVisualCell();
+			if (TitleBlockCell *target_cell = tbtemplate_ -> cell(j, i)) {
+				qDebug() << Q_FUNC_INFO << "target_cell" << target_cell;
+				cell_item -> setTemplateCell(tbtemplate_, target_cell);
+			}
+			tbgrid_ -> addItem(cell_item, ROW_OFFSET + j, COL_OFFSET + i);
+		}
+	}
+}
+
+/**
+	Load the \a tbt title block template.
+	If a different template was previously loaded, it is deleted.
+	
+*/
+void TitleBlockTemplateView::loadTemplate(TitleBlockTemplate *tbt) {
+	if (tbgrid_) {
+		scene() -> removeItem(form_);
+		// also deletes TemplateCellPreview because, according to the
+		// documentation, QGraphicsGridLayout takes ownership of the items.
+		form_ -> deleteLater();
+	}
+	if (tbtemplate_ && tbtemplate_ != tbt) {
+		delete tbtemplate_;
+	}
+	
+	tbtemplate_ = tbt;
+	
+	// initialize a grid layout with no margin
+	tbgrid_ = new QGraphicsGridLayout();
+	tbgrid_ -> setContentsMargins(0, 0, 0, 0);
+	// add cells defined by the title block template in this layout
+	addCells();
+	// fill potential holes in the grid with empty cells
+	fillWithEmptyCells();
+	// apply rows and columns dimensions
+	applyColumnsWidths(false);
+	applyRowsHeights(false);
+	
+	// assign the layout to a basic QGraphicsWidget
+	form_ = new QGraphicsWidget();
+	form_ -> setLayout(tbgrid_);
+	scene() -> addItem(form_);
+	adjustSceneRect();
+}
+
+/**
+	@return the list of rows-specific actions.
+*/
+QList<QAction *> TitleBlockTemplateView::rowsActions() const {
+	return QList<QAction *>() << add_row_before_<< edit_row_dim_ << add_row_after_ << delete_row_;
+}
+
+/**
+	@return the list of columns-specific actions.
+*/
+QList<QAction *> TitleBlockTemplateView::columnsActions() const {
+	return QList<QAction *>() << add_column_before_ << edit_column_dim_ << add_column_after_ << delete_column_;
+}
+
+/**
+	Update the displayed layout. Call this function to refresh the display
+	after the rendered title block template has been "deeply" modified, e.g.
+	rows/columns have been added/modified or cells were merged/splitted.
+*/
+void TitleBlockTemplateView::updateLayout() {
+	// TODO we should try to update the grid instead of deleting-and-reloading it
+	loadTemplate(tbtemplate_);
+}
+
+/**
+	Update the displayed layout. Call this function when the dimensions of
+	rows changed.
+*/
+void TitleBlockTemplateView::rowsDimensionsChanged() {
+	applyRowsHeights();
+}
+
+/**
+	Update the displayed layout. Call this function when the dimensions of
+	columns changed.
+*/
+void TitleBlockTemplateView::columnsDimensionsChanged() {
+	applyColumnsWidths();
+}
+
+/**
+	Set the new preview width to width
+	@param width new preview width
+*/
+void TitleBlockTemplateView::setPreviewWidth(int width) {
+	if (preview_width_ == width) return;
+	preview_width_ = width;
+	applyColumnsWidths();
+	updateTotalWidthLabel();
+	//adjustSceneRect();
+	centerOn(form_);
+	/// TODO center again the preview()
+}
+
+/**
+	Update the label of the helper cell that indicates the preview width.
+*/
+void TitleBlockTemplateView::updateTotalWidthLabel() {
+	if (!total_width_helper_cell_) return;
+	total_width_helper_cell_ -> label = QString(
+		tr(
+			"Largeur totale pour cet aper\347u : %1px",
+			"displayed at the top of the preview when editing a title block template"
+		)
+	).arg(preview_width_);
+}
+
+/**
+	Emit the gridModificationRequested() signal with \a command after having set
+	its view component.
+	@see TitleBlockTemplateCommand::setView()
+	@param command A command object modifying the rendered title block template.
+*/
+void TitleBlockTemplateView::requestGridModification(TitleBlockTemplateCommand *command) {
+	if (!command) return;
+	command -> setView(this);
+	emit(gridModificationRequested(command));
+}
+
+/**
+	@return the last index selected when triggering the context menu.
+	@see updateLastContextMenuCell
+*/
+int TitleBlockTemplateView::lastContextMenuCellIndex() const {
+	if (last_context_menu_cell_) {
+		return(last_context_menu_cell_ -> index);
+	}
+	return(-1);
+}
+
+/**
+	@param item an item supposed to be contained in the grid layout.
+	@return the flat index if this item, or -1 if it could not be found.
+*/
+int TitleBlockTemplateView::indexOf(QGraphicsLayoutItem *item) {
+	for (int i = 0 ; i < tbgrid_ -> count() ; ++i) {
+		if (item == tbgrid_ -> itemAt(i)) return(i);
+	}
+	return(-1);
+}
+
+/**
+	Removes an item from the grid layout
+	@param item an item supposed to be contained in the grid layout.
+*/
+void TitleBlockTemplateView::removeItem(QGraphicsLayoutItem *item) {
+	int index = indexOf(item);
+	if (index != -1) {
+		tbgrid_ -> removeAt(index);
+		// trick: we also have to remove the item from the scene
+		if (QGraphicsScene *current_scene = scene()) {
+			if (QGraphicsItem *qgi = item -> graphicsItem()) {
+				current_scene -> removeItem(qgi);
+			}
+		}
+	}
+}
+
+/**
+	@param a list of QGraphicsItem
+	@return the corresponding TitleBlockTemplateCellsSet
+*/
+TitleBlockTemplateCellsSet TitleBlockTemplateView::makeCellsSetFromGraphicsItems(const QList<QGraphicsItem *> &items) const {
+	TitleBlockTemplateCellsSet set(this);
+	foreach (QGraphicsItem *item, items) {
+		if (TitleBlockTemplateVisualCell *cell_view = dynamic_cast<TitleBlockTemplateVisualCell *>(item)) {
+			if (cell_view -> cell() && cell_view -> cell() -> num_row != -1) {
+				set << cell_view;
+			}
+		}
+	}
+	return(set);
+}
+
+/**
+	Stores \a last_context_menu_cell as being the last helper cell the context
+	menu was triggered on.
+*/
+void TitleBlockTemplateView::updateLastContextMenuCell(HelperCell *last_context_menu_cell) {
+	last_context_menu_cell_ = last_context_menu_cell;
+}
+
+/**
+	Adjusts the bounding rect of the scene.
+*/
+void TitleBlockTemplateView::adjustSceneRect() {
+	QRectF old_scene_rect = sceneRect();
+	
+	// rectangle including everything on the scene
+	QRectF bounding_rect = scene() -> itemsBoundingRect();
+	setSceneRect(bounding_rect);
+	
+	// met a jour la scene
+	scene() -> update(old_scene_rect.united(bounding_rect));
+}
+

Added: branches/0.3/sources/titleblock/templateview.h
===================================================================
--- branches/0.3/sources/titleblock/templateview.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/templateview.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,122 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_TEMPLATE_VIEW_H
+#define TITLEBLOCK_SLASH_TEMPLATE_VIEW_H
+#include <QGraphicsView>
+#include "titleblocktemplate.h"
+class HelperCell;
+class SplittedHelperCell;
+class TitleBlockTemplateCommand;
+class TitleBlockTemplateCellsSet;
+
+/**
+	This QGraphicsView subclass is used in the title block template editor to
+	offer a graphical preview of the template being edited, but also to handle
+	cell selection and various actions.
+*/
+class TitleBlockTemplateView : public QGraphicsView {
+	Q_OBJECT
+	
+	// constructors, destructor
+	public:
+	TitleBlockTemplateView(QWidget * = 0);
+	TitleBlockTemplateView(QGraphicsScene *, QWidget * = 0);
+	virtual ~TitleBlockTemplateView();
+	private:
+	TitleBlockTemplateView(const TitleBlockTemplateView &);
+	
+	// methods and slots
+	public:
+	TitleBlockTemplate *titleBlockTemplate() const;
+	virtual QList<TitleBlockCell *> selectedCells() const;
+	virtual TitleBlockTemplateCellsSet selectedCellsSet() const;
+	virtual TitleBlockTemplateCellsSet cells(const QRectF &) const;
+	
+	public slots:
+	void setTitleBlockTemplate(TitleBlockTemplate *);
+	void selectionChanged();
+	void zoomIn();
+	void zoomOut();
+	void addColumnBefore();
+	void addRowBefore();
+	void addColumnAfter();
+	void addRowAfter();
+	void editColumn(HelperCell * = 0);
+	void editRow(HelperCell * = 0);
+	void deleteColumn();
+	void deleteRow();
+	void mergeSelectedCells();
+	void splitSelectedCell();
+	void refresh();
+	void changePreviewWidth();
+	void updateLayout();
+	void rowsDimensionsChanged();
+	void columnsDimensionsChanged();
+	
+	protected slots:
+	virtual void applyColumnsWidths(bool = true);
+	virtual void applyRowsHeights(bool = true);
+	virtual void updateRowsHelperCells();
+	virtual void updateColumnsHelperCells();
+	
+	protected:
+	virtual void drawBackground(QPainter *, const QRectF &);
+	virtual void addCells();
+	virtual void loadTemplate(TitleBlockTemplate *);
+	virtual void init();
+	virtual void wheelEvent(QWheelEvent *);
+	virtual qreal zoomFactor() const;
+	virtual void fillWithEmptyCells();
+	
+	signals:
+	void selectedCellsChanged(QList<TitleBlockCell *>);
+	void gridModificationRequested(TitleBlockTemplateCommand *);
+	
+	private:
+	QList<QAction *> rowsActions() const;
+	QList<QAction *> columnsActions() const;
+	void setPreviewWidth(int);
+	void updateTotalWidthLabel();
+	void requestGridModification(TitleBlockTemplateCommand *);
+	int lastContextMenuCellIndex() const;
+	int indexOf(QGraphicsLayoutItem *);
+	void removeItem(QGraphicsLayoutItem *);
+	TitleBlockTemplateCellsSet makeCellsSetFromGraphicsItems(const QList<QGraphicsItem *> &) const;
+	
+	private slots:
+	void updateLastContextMenuCell(HelperCell *);
+	void adjustSceneRect();
+	
+	// attributes
+	private:
+	TitleBlockTemplate *tbtemplate_;
+	QGraphicsGridLayout *tbgrid_;
+	QGraphicsWidget *form_;
+	int preview_width_;
+	SplittedHelperCell *total_width_helper_cell_;
+	HelperCell *extra_cells_width_helper_cell_;
+	QAction *add_column_before_, *add_row_before_;
+	QAction *add_column_after_, *add_row_after_;
+	QAction *edit_column_dim_, *edit_row_dim_;
+	QAction *delete_column_, *delete_row_;
+	QAction *change_preview_width_;
+	HelperCell *last_context_menu_cell_;
+	int apply_columns_widths_count_;
+	int apply_rows_heights_count_;
+};
+#endif

Added: branches/0.3/sources/titleblock/templatevisualcell.cpp
===================================================================
--- branches/0.3/sources/titleblock/templatevisualcell.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatevisualcell.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,136 @@
+/*
+	Copyright 2006-2011 Xavier Guerrin
+	This file is part of QElectroTech.
+	
+	QElectroTech is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 2 of the License, or
+	(at your option) any later version.
+	
+	QElectroTech is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+	
+	You should have received a copy of the GNU General Public License
+	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "templatevisualcell.h"
+#include "titleblocktemplate.h"
+#include "diagramcontext.h"
+
+/**
+	Constructor
+	@param parent Parent QGraphicsItem
+*/
+TitleBlockTemplateVisualCell::TitleBlockTemplateVisualCell(QGraphicsItem *parent) :
+	QGraphicsLayoutItem(),
+	QGraphicsItem(parent),
+	template_(0),
+	cell_(0)
+{
+	setGraphicsItem(this);
+	setFlag(QGraphicsItem::ItemIsSelectable, true);
+	
+}
+
+/**
+	Destructor
+*/
+TitleBlockTemplateVisualCell::~TitleBlockTemplateVisualCell() {
+}
+
+/**
+	Ensure geometry changes are handled for both QGraphicsObject and
+	QGraphicsLayoutItem.
+	@param g New geometry
+*/
+void TitleBlockTemplateVisualCell::setGeometry(const QRectF &g) {
+	prepareGeometryChange();
+	QGraphicsLayoutItem::setGeometry(g);
+	setPos(g.topLeft());
+}
+
+/**
+	@param which Size hint to be modified
+	@param constraint New value for the size hint
+	@return the size hint for \a which using the width or height of \a constraint
+*/
+QSizeF TitleBlockTemplateVisualCell::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const {
+	Q_UNUSED(which);
+	return constraint;
+}
+
+/**
+	@return the bounding rect of this helper cell
+*/
+QRectF TitleBlockTemplateVisualCell::boundingRect() const {
+	return QRectF(QPointF(0,0), geometry().size());
+}
+
+/**
+	Handles the helper cell visual rendering
+	@param painter QPainter to be used for the rendering
+	@param option Rendering options
+	@param widget QWidget being painted, if any
+*/
+void TitleBlockTemplateVisualCell::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+	Q_UNUSED(option);
+	Q_UNUSED(widget);
+	
+	QRectF drawing_rectangle(QPointF(0, 0), geometry().size() /*- QSizeF(1, 1)*/);
+	
+	if (template_ && cell_) {
+		template_ -> renderCell(*painter, *cell_, DiagramContext(), drawing_rectangle.toRect());
+	}
+	if (isSelected()) {
+		QBrush selection_brush = QApplication::palette().highlight();
+		QColor selection_color = selection_brush.color();
+		selection_color.setAlpha(127);
+		selection_brush.setColor(selection_color);
+		painter -> setPen(Qt::NoPen);
+		painter -> setBrush(selection_brush);
+		painter -> drawRect(drawing_rectangle/*.adjusted(1, 1, -1, -1)*/);
+	}
+}
+
+/**
+	Set the previewed title block cell.
+	@param tbt Parent title block template of the previewed cell
+	@param cell Previewed cell
+*/
+void TitleBlockTemplateVisualCell::setTemplateCell(TitleBlockTemplate *tbt, TitleBlockCell *cell) {
+	template_ = tbt;
+	cell_     = cell;
+}
+
+/**
+	@return the parent title block template of the previewed cell
+*/
+TitleBlockTemplate *TitleBlockTemplateVisualCell::titleBlockTemplate() const {
+	return(template_);
+}
+
+/**
+	@return the previewed title block cell
+*/
+TitleBlockCell *TitleBlockTemplateVisualCell::cell() const {
+	return(cell_);
+}
+
+/**
+	@return the title block cell previewed by this object, plus the cells it
+	spans over, if any
+*/
+QSet<TitleBlockCell *> TitleBlockTemplateVisualCell::cells() const {
+	QSet<TitleBlockCell *> set;
+	if (cell_) {
+		if (template_) {
+			set = template_ -> spannedCells(cell_);
+		}
+		
+		// the TitleBlockCell rendered by this object
+		set << cell_;
+	}
+	return(set);
+}

Added: branches/0.3/sources/titleblock/templatevisualcell.h
===================================================================
--- branches/0.3/sources/titleblock/templatevisualcell.h	                        (rev 0)
+++ branches/0.3/sources/titleblock/templatevisualcell.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,55 @@
+/*
+    Copyright 2006-2011 Xavier Guerrin
+    This file is part of QElectroTech.
+    
+    QElectroTech is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 2 of the License, or
+    (at your option) any later version.
+    
+    QElectroTech is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+    
+    You should have received a copy of the GNU General Public License
+    along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef TITLEBLOCK_SLASH_QET_TEMPLATE_VISUAL_CELL_H
+#define TITLEBLOCK_SLASH_QET_TEMPLATE_VISUAL_CELL_H
+#include <QtGui>
+#include "qet.h"
+class TitleBlockTemplate;
+#include "titleblockcell.h"
+
+/**
+	This class implements a preview widget for cells that compose a title
+	block template.
+*/
+class TitleBlockTemplateVisualCell : public QGraphicsLayoutItem, public QGraphicsItem {
+	// constructor, destructor
+	public:
+	TitleBlockTemplateVisualCell(QGraphicsItem * parent = 0);
+	virtual ~TitleBlockTemplateVisualCell();
+	private:
+	TitleBlockTemplateVisualCell(const TitleBlockTemplateVisualCell &);
+	
+	// methods
+	public:
+	virtual void setGeometry(const QRectF &);
+	virtual QSizeF sizeHint(Qt::SizeHint, const QSizeF & = QSizeF()) const;
+	virtual QRectF boundingRect() const;
+	void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0);
+	
+	public slots:
+	void setTemplateCell(TitleBlockTemplate *, TitleBlockCell *);
+	TitleBlockTemplate *titleBlockTemplate() const;
+	TitleBlockCell *cell() const;
+	QSet<TitleBlockCell *> cells() const;
+	
+	// attributes
+	private:
+	TitleBlockTemplate *template_; ///< Title block template of the previewed cell
+	TitleBlockCell *cell_;         ///< Previewed cell
+};
+#endif

Added: branches/0.3/sources/titleblockcell.cpp
===================================================================
--- branches/0.3/sources/titleblockcell.cpp	                        (rev 0)
+++ branches/0.3/sources/titleblockcell.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -0,0 +1,144 @@
+#include "titleblockcell.h"
+#include "titleblocktemplate.h"
+/**
+	Constructor
+*/
+TitleBlockCell::TitleBlockCell() {
+	cell_type = TitleBlockCell::EmptyCell;
+	num_row = num_col = -1;
+	row_span = col_span = 0;
+	spanner_cell = 0;
+	display_label = true;
+	alignment = Qt::AlignCenter | Qt::AlignVCenter;
+	font_size = 9;
+	hadjust = false;
+	logo_reference = QString("");
+}
+
+/**
+	Destructor
+*/
+TitleBlockCell::~TitleBlockCell() {
+}
+
+/**
+	@return A string representing the titleblock cell
+*/
+QString TitleBlockCell::toString() const {
+	if (cell_type == TitleBlockCell::EmptyCell) return("TitleBlockCell{null}");
+	QString span_desc = (row_span > 0 || col_span > 0) ? QString("+%1,%2").arg(row_span).arg(col_span) : QET::pointerString(spanner_cell);
+	QString base_desc = QString("TitleBlockCell{ [%1, %2] %3 }").arg(num_row).arg(num_col).arg(span_desc);
+	return(base_desc);
+}
+
+/**
+	@return the type of this cell
+*/
+TitleBlockCell::TemplateCellType TitleBlockCell::type() const {
+	return(cell_type);
+}
+
+/**
+	@return the horizontal alignment of this cell
+*/
+int TitleBlockCell::horizontalAlign() const {
+	return(alignment & Qt::AlignHorizontal_Mask);
+}
+
+/**
+	@return the vertical alignment of this cell
+*/
+int TitleBlockCell::verticalAlign() const {
+	return(alignment & Qt::AlignVertical_Mask);
+}
+
+/**
+	Set the new value \a attr_value to the attribute named \a attribute.
+	@param attribute Name of the cell attribute which value is to be changed
+	@param attr_value New value of the changed attribute
+*/
+void TitleBlockCell::setAttribute(const QString &attribute, const QVariant &attr_value) {
+	if (attribute == "type") {
+		int new_type = attr_value.toInt();
+		if (new_type <= TitleBlockCell::LogoCell) {
+			cell_type = static_cast<TitleBlockCell::TemplateCellType>(new_type);
+		}
+	} else if (attribute == "name") {
+		value_name = attr_value.toString();
+	} else if (attribute == "logo") {
+		logo_reference = attr_value.toString();
+	} else if (attribute == "label") {
+		label = qvariant_cast<NamesList>(attr_value);
+	} else if (attribute == "displaylabel") {
+		display_label = attr_value.toBool();
+	} else if (attribute == "value") {
+		value = qvariant_cast<NamesList>(attr_value);
+	} else if (attribute == "alignment") {
+		alignment = attr_value.toInt();
+	} else if (attribute == "fontsize") {
+		font_size = attr_value.toInt();
+	} else if (attribute == "horizontal_adjust") {
+		hadjust = attr_value.toBool();
+	}
+}
+
+/**
+	@param attribute Name of the cell attribute which value is wanted
+	@return the value of the required attribute
+*/
+QVariant TitleBlockCell::attribute(const QString &attribute) {
+	if (attribute == "type") {
+		return(type());
+	} else if (attribute == "name") {
+		return(value_name);
+	} else if (attribute == "logo") {
+		return(logo_reference);
+	} else if (attribute == "label") {
+		return(qVariantFromValue(label));
+	} else if (attribute == "displaylabel") {
+		return(display_label);
+	} else if (attribute == "value") {
+		return(qVariantFromValue(value));
+	} else if (attribute == "alignment") {
+		return(alignment);
+	} else if (attribute == "fontsize") {
+		return(TitleBlockTemplate::fontForCell(*this).pointSizeF());
+	} else if (attribute == "horizontal_adjust") {
+		return(hadjust);
+	}
+	return(QVariant());
+}
+
+/**
+	@param attribute Name of the cell attribute which we want the human, translated name
+	@return the human, translated name for this attribute.
+*/
+QString TitleBlockCell::attributeName(const QString &attribute) {
+	if (attribute == "type") {
+		return(QObject::tr("type", "cell property human name"));
+	} else if (attribute == "name") {
+		return(QObject::tr("nom", "cell property human name"));
+	} else if (attribute == "logo") {
+		return(QObject::tr("logo", "cell property human name"));
+	} else if (attribute == "label") {
+		return(QObject::tr("label", "cell property human name"));
+	} else if (attribute == "displaylabel") {
+		return(QObject::tr("affichage du label", "cell property human name"));
+	} else if (attribute == "value") {
+		return(QObject::tr("valeur affich\xE9e", "cell property human name"));
+	} else if (attribute == "alignment") {
+		return(QObject::tr("alignement du texte", "cell property human name"));
+	} else if (attribute == "fontsize") {
+		return(QObject::tr("taille du texte", "cell property human name"));
+	} else if (attribute == "horizontal_adjust") {
+		return(QObject::tr("ajustement horizontal", "cell property human name"));
+	}
+	return(QString());
+}
+
+/**
+	@return true if this cell spans over other cells, false otherwise.
+*/
+bool TitleBlockCell::spans() const {
+	return(row_span || col_span);
+}

Modified: branches/0.3/sources/titleblockcell.h
===================================================================
--- branches/0.3/sources/titleblockcell.h	2011-12-24 18:37:00 UTC (rev 1404)
+++ branches/0.3/sources/titleblockcell.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -17,27 +17,51 @@
 */
 #ifndef TITLEBLOCK_CELL_H
 #define TITLEBLOCK_CELL_H
+#include "nameslist.h"
+
 /**
-	This class is a container for the various parameters of an titleblock cell
+	This class is a container for the various parameters of a titleblock cell
 	@see TitleBlockColumnLength 
 */
 class TitleBlockCell {
 	public:
+	enum TemplateCellType {
+		EmptyCell,
+		TextCell,
+		LogoCell
+	};
+	
+	// Constructor, destructor
+	public:
 	TitleBlockCell();
+	virtual ~TitleBlockCell();
+	
+	// methods
+	public:
 	QString toString() const;
-	bool is_null;
-	int num_row;
-	int num_col;
-	int row_span;
-	int col_span;
-	TitleBlockCell *spanner_cell;
-	QString value_name;
-	QString value;
-	QString label;
-	bool display_label;
-	int alignment;
-	int font_size;
-	bool hadjust;
-	QString logo_reference;
+	TemplateCellType type() const;
+	int horizontalAlign() const;
+	int verticalAlign() const;
+	void setAttribute(const QString &, const QVariant &);
+	QVariant attribute(const QString &);
+	static QString attributeName(const QString &);
+	bool spans() const;
+	
+	// attributes
+	public:
+	TemplateCellType cell_type;        ///< Cell type: empty, text, logo?
+	int num_row;                       ///< y coordinate of the cell within its parent title block template grid
+	int num_col;                       ///< x coordinate of the cell within its parent title block template grid
+	int row_span;                      ///< number of extra rows spanned by this cell
+	int col_span;                      ///< number of extra columns spanned by this cell
+	TitleBlockCell *spanner_cell;      ///< Cell spanning this cell, if any
+	QString value_name;                ///< name of the cell; not displayed when the title block template is rendered
+	NamesList value;                   ///< Text displayed by the cell
+	NamesList label;                   ///< Label displayed by the cell
+	bool display_label;                ///< Whether to display the label or not
+	int alignment;                     ///< Where the label+text should be displayed within the visual cell
+	int font_size;                     ///< Font size the text should be rendered with
+	bool hadjust;                      ///< Whether to reduce the font size if the text does not fit in the cell
+	QString logo_reference;            ///< Logo displayed by this cell, it it is a logo cell
 };
 #endif

Modified: branches/0.3/sources/titleblocktemplate.cpp
===================================================================
--- branches/0.3/sources/titleblocktemplate.cpp	2011-12-24 18:37:00 UTC (rev 1404)
+++ branches/0.3/sources/titleblocktemplate.cpp	2011-12-25 17:45:39 UTC (rev 1405)
@@ -19,6 +19,8 @@
 #include "qet.h"
 #include "qetapp.h"
 #include "nameslist.h"
+// uncomment the line below to get more debug information
+//#define TITLEBLOCK_TEMPLATE_DEBUG
 
 /**
 	Constructor
@@ -34,31 +36,65 @@
 */
 TitleBlockTemplate::~TitleBlockTemplate() {
 	loadLogos(QDomElement(), true);
+	qDeleteAll(registered_cells_);
 }
 
 /**
+	Create a new cell and associate it with this template, which means that it
+	will be deleted when this template is destroyed.
+	@param existing_cell (optional) An existing cell that will be copied
+	@return A pointer to the newly created cell
+*/
+TitleBlockCell *TitleBlockTemplate::createCell(const TitleBlockCell *existing_cell) {
+	TitleBlockCell *new_cell = existing_cell ? new TitleBlockCell(*existing_cell) : new TitleBlockCell();
+	registered_cells_ << new_cell;
+	return(new_cell);
+}
+
+/**
+	@param count Number of cells expected in the list
+	@return a list containing count newly created (and registered) cells
+	@see createCell()
+*/
+QList<TitleBlockCell *> TitleBlockTemplate::createCellsList(int count) {
+	QList<TitleBlockCell *> new_list;
+	for (int i = 0 ; i < count ; ++ i) new_list << createCell();
+	return(new_list);
+}
+
+/**
+	@param cell An existing cell
+	@return The font that should be used to render this cell according to its properties.
+*/
+QFont TitleBlockTemplate::fontForCell(const TitleBlockCell &cell) {
+	return(QETApp::diagramTextsFont(cell.font_size));
+}
+
+/**
+	Load a titleblock template from an XML file.
 	@param filepath A file path to read the template from.
 	@return true if the reading succeeds, false otherwise.
 */
 bool TitleBlockTemplate::loadFromXmlFile(const QString &filepath) {
-	// opens the file
+	// open the file
 	QFile template_file(filepath);
 	if (!template_file.open(QIODevice::ReadOnly | QIODevice::Text)) {
-	    return(false);
+		return(false);
 	}
 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
 	qDebug() << Q_FUNC_INFO << filepath << "opened";
 #endif
 	
-	// parses its content as XML
-	bool xml_parsing = xml_description_.setContent(&template_file);
+	// parse its content as XML
+	QDomDocument xml_doc;
+	bool xml_parsing = xml_doc.setContent(&template_file);
 	if (!xml_parsing) {
 		return(false);
 	}
 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
 	qDebug() << Q_FUNC_INFO << filepath << "opened and parsed";
 #endif
-	return(loadFromXmlElement(xml_description_.documentElement()));
+	return(loadFromXmlElement(xml_doc.documentElement()));
 }
 
 /**
@@ -77,11 +113,79 @@
 	
 	loadLogos(xml_element, true);
 	loadGrid(xml_element);
+	
 	return(true);
 }
 
 /**
-	Imports the logos from a given XML titleblock template.
+	Save the title block template as XML.
+	@param xml_element The XMl element this title block template should be saved to.
+	@return true if the export succeeds, false otherwise
+*/
+bool TitleBlockTemplate::saveToXmlElement(QDomElement &xml_element) const {
+	// we are supposed to have at least one row/column and a name
+	if (!columnsCount() || !rowsCount() || name_.isEmpty()) return(false);
+	
+	xml_element.setTagName("titleblocktemplate");
+	xml_element.setAttribute("name", name_);
+	saveLogos(xml_element);
+	saveGrid(xml_element);
+	return(true);
+}
+
+/**
+	@return a deep copy of the current title block template (i.e. title block
+	cells are duplicated too and associated with their parent template).
+*/
+TitleBlockTemplate *TitleBlockTemplate::clone() const {
+	TitleBlockTemplate *copy = new TitleBlockTemplate();
+	copy -> name_ = name_;
+	
+	// this does not really duplicates pixmaps, only the objects that hold a key to the implicitly shared pixmaps
+	foreach (QString logo_key, bitmap_logos_.keys()) {
+		copy -> bitmap_logos_[logo_key] = QPixmap(bitmap_logos_[logo_key]);
+#ifdef TITLEBLOCK_TEMPLATE_DEBUG
+		qDebug() << Q_FUNC_INFO << "copying " << bitmap_logos_[logo_key] -> cacheKey() << "to" << copy -> bitmap_logos_[logo_key] -> cacheKey();
+#endif
+	}
+	
+	// we have to create new QSvgRenderer objects from the data (no copy constructor)
+	foreach (QString logo_key, vector_logos_.keys()) {
+		copy -> vector_logos_[logo_key] = new QSvgRenderer(data_logos_[logo_key]);
+	}
+	
+	copy -> data_logos_    = data_logos_;
+	copy -> storage_logos_ = storage_logos_;
+	copy -> type_logos_    = type_logos_;
+	copy -> rows_heights_  = rows_heights_;
+	copy -> columns_width_ = columns_width_;
+	
+	// copy cells basically
+	copy -> cells_ = cells_;
+	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
+		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
+			copy -> cells_[i][j] = copy -> createCell(cells_[i][j]);
+		}
+	}
+	
+	// ensure the copy has no spanner_cell attribute pointing to a cell from the original object
+	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
+		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
+			TitleBlockCell *current_cell = copy -> cells_[i][j];
+			if (TitleBlockCell *original_cell = current_cell -> spanner_cell) {
+				int original_cell_row = original_cell -> num_row;
+				int original_cell_col = original_cell -> num_col;
+				TitleBlockCell *copy_cell = copy -> cells_[original_cell_col][original_cell_row];
+				current_cell -> spanner_cell = copy_cell;
+			}
+		}
+	}
+	
+	return(copy);
+}
+
+/**
+	Import the logos from a given XML titleblock template.
 	@param xml_element An XML element representing an titleblock template.
 	@param reset true to delete all previously known logos before, false
 	otherwise.
@@ -91,8 +195,12 @@
 	if (reset) {
 		qDeleteAll(vector_logos_.begin(), vector_logos_.end());
 		vector_logos_.clear();
-		qDeleteAll(bitmap_logos_.begin(), bitmap_logos_.end());
+		
+		// Note: QPixmap are only a key to access the implicitly shared pixmap
 		bitmap_logos_.clear();
+		
+		data_logos_.clear();
+		storage_logos_.clear();
 	}
 	
 	// we look for //logos/logo elements
@@ -110,7 +218,7 @@
 }
 
 /**
-	Imports the logo from a given XML logo description.
+	Import the logo from a given XML logo description.
 	@param xml_element An XML element representing a logo within an titleblock
 	template.
 	@return true if the reading succeeds, false otherwise.
@@ -128,7 +236,6 @@
 	// we convert the available data to that format.
 	QByteArray logo_data;
 	if (logo_storage == "xml") {
-		// only svg uses xml storage
 		QDomNodeList svg_nodes = xml_element.elementsByTagName("svg");
 		if (svg_nodes.isEmpty()) {
 			return(false);
@@ -141,35 +248,16 @@
 	} else {
 		return(false);
 	}
+#ifdef TITLEBLOCK_TEMPLATE_DEBUG
+	qDebug() << Q_FUNC_INFO << logo_name << logo_type << logo_storage;
+#endif
+	addLogo(logo_name, &logo_data, logo_type, logo_storage);
 	
-	// we can now create our image object from the byte array
-	if (logo_type == "svg") {
-		// SVG format is handled by the QSvgRenderer class
-		QSvgRenderer *svg = new QSvgRenderer(logo_data);
-		vector_logos_.insert(logo_name, svg);
-		
-		/*QSvgWidget *test_svgwidget = new QSvgWidget();
-		test_svgwidget -> load(logo_data);
-		test_svgwidget -> show();*/
-	} else {
-		// bitmap formats are handled by the QPixmap class
-		QPixmap *logo_pixmap = new QPixmap();
-		logo_pixmap -> loadFromData(logo_data);
-		if (!logo_pixmap -> width() || !logo_pixmap -> height()) {
-			return(false);
-		}
-		bitmap_logos_.insert(logo_name, logo_pixmap);
-		
-		/*QLabel *test_label = new QLabel();
-		test_label -> setPixmap(*logo_pixmap);
-		test_label -> show();*/
-	}
-	
 	return(true);
 }
 
 /**
-	Imports the grid from a given XML titleblock template.
+	Import the grid from a given XML titleblock template.
 	@param xml_element An XML element representing an titleblock template.
 	@return true if the reading succeeds, false otherwise.
 */
@@ -189,12 +277,15 @@
 	
 	parseRows(grid_element.attribute("rows"));
 	parseColumns(grid_element.attribute("cols"));
+	initCells();
 	loadCells(grid_element);
+	applyRowColNums();
+	applyCellSpans();
 	return(true);
 }
 
 /**
-	Parses the rows heights
+	Parse the rows heights
 	@param rows_string A string describing the rows heights of the titleblock
 */
 void TitleBlockTemplate::parseRows(const QString &rows_string) {
@@ -216,7 +307,7 @@
 }
 
 /**
-	Parses the columns widths
+	Parse the columns widths
 	@param cols_string A string describing the columns widths of the titleblock
 */
 void TitleBlockTemplate::parseColumns(const QString &cols_string) {
@@ -230,11 +321,11 @@
 	foreach (QString cols_description, cols_descriptions) {
 		if (abs_col_size_format.exactMatch(cols_description)) {
 			int col_size = abs_col_size_format.capturedTexts().at(1).toInt(&conv_ok);
-			if (conv_ok) columns_width_ << TitleBlockColDimension(col_size, QET::Absolute);
+			if (conv_ok) columns_width_ << TitleBlockDimension(col_size, QET::Absolute);
 		} else if (rel_col_size_format.exactMatch(cols_description)) {
 			int col_size = rel_col_size_format.capturedTexts().at(2).toInt(&conv_ok);
 			QET::TitleBlockColumnLength col_type = rel_col_size_format.capturedTexts().at(1) == "t" ? QET::RelativeToTotalLength : QET::RelativeToRemainingLength;
-			if (conv_ok) columns_width_ << TitleBlockColDimension(col_size, col_type );
+			if (conv_ok) columns_width_ << TitleBlockDimension(col_size, col_type );
 		}
 	}
 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
@@ -248,80 +339,222 @@
 	Analyze an XML element, looking for grid cells. The grid cells are checked
 	and stored in this object.
 	@param xml_element XML element to analyze
+	@return systematically true
 */
 bool TitleBlockTemplate::loadCells(const QDomElement &xml_element) {
-	initCells();
 	// we are interested by the "logo" and "field" elements
 	QDomElement grid_element;
 	for (QDomNode n = xml_element.firstChild() ; !n.isNull() ; n = n.nextSibling()) {
 		if (!n.isElement()) continue;
 		QDomElement cell_element = n.toElement();
 		if (cell_element.tagName() == "field" || cell_element.tagName() == "logo") {
-			TitleBlockCell *loaded_cell;
-			if (!checkCell(cell_element, &loaded_cell)) continue;
-			
-			if (cell_element.tagName() == "logo") {
-				if (cell_element.hasAttribute("resource") && !cell_element.attribute("resource").isEmpty()) {
-					loaded_cell -> logo_reference = cell_element.attribute("resource");
-				}
-			} else if (cell_element.tagName() == "field") {
-				if (cell_element.hasAttribute("name") && !cell_element.attribute("name").isEmpty()) {
-					loaded_cell -> value_name = cell_element.attribute("name");
-				}
-				
-				QHash<QString, QString> names_options;
-				names_options["TagName"] = "translation";
-				
-				names_options["ParentTagName"] = "value";
-				NamesList value_nameslist;
-				value_nameslist.fromXml(cell_element, names_options);
-				if (!value_nameslist.name().isEmpty()) {
-					loaded_cell -> value = value_nameslist.name();
-				}
-				
-				names_options["ParentTagName"] = "label";
-				NamesList label_nameslist;
-				label_nameslist.fromXml(cell_element, names_options);
-				if (!label_nameslist.name().isEmpty()) {
-					loaded_cell -> label = label_nameslist.name();
-				}
-				
-				if (cell_element.hasAttribute("displaylabel") && cell_element.attribute("displaylabel").compare("false", Qt::CaseInsensitive) == 0) {
-					loaded_cell -> display_label = false;
-				}
-				int fontsize;
-				if (QET::attributeIsAnInteger(cell_element, "fontsize", &fontsize)) {
-					loaded_cell -> font_size = fontsize;
-				} else {
-					loaded_cell -> font_size = -1;
-				}
-				
-				// horiwontal and vertical alignments
-				loaded_cell -> alignment = 0;
-				
-				QString halignment = cell_element.attribute("align", "left");
-				if (halignment == "right") loaded_cell -> alignment |= Qt::AlignRight;
-				else if (halignment == "center") loaded_cell -> alignment |= Qt::AlignHCenter;
-				else loaded_cell -> alignment |= Qt::AlignLeft;
-				
-				QString valignment = cell_element.attribute("valign", "center");
-				if (valignment == "bottom") loaded_cell -> alignment |= Qt::AlignBottom;
-				else if (valignment == "top") loaded_cell -> alignment |= Qt::AlignTop;
-				else loaded_cell -> alignment |= Qt::AlignVCenter;
-				
-				// horizontal text adjustment
-				loaded_cell -> hadjust = cell_element.attribute("hadjust", "true") == "true";
+			loadCell(cell_element);
+		}
+	}
+	return(true);
+}
+
+/**
+	Load a cell into this template.
+	@param cell_element XML element describing a cell within a title block template
+*/
+void TitleBlockTemplate::loadCell(const QDomElement &cell_element) {
+	TitleBlockCell *loaded_cell;
+	if (!checkCell(cell_element, &loaded_cell)) return;
+	
+	// common properties
+	if (cell_element.hasAttribute("name") && !cell_element.attribute("name").isEmpty()) {
+		loaded_cell -> value_name = cell_element.attribute("name");
+	}
+	
+	// specific properties
+	if (cell_element.tagName() == "logo") {
+		if (cell_element.hasAttribute("resource") && !cell_element.attribute("resource").isEmpty()) {
+			loaded_cell -> cell_type = TitleBlockCell::LogoCell;
+			loaded_cell -> logo_reference = cell_element.attribute("resource");
+		}
+	} else if (cell_element.tagName() == "field") {
+		loaded_cell -> cell_type = TitleBlockCell::TextCell;
+		
+		QHash<QString, QString> names_options;
+		names_options["TagName"] = "translation";
+		
+		names_options["ParentTagName"] = "value";
+		NamesList value_nameslist;
+		value_nameslist.fromXml(cell_element, names_options);
+		if (!value_nameslist.name().isEmpty()) {
+			loaded_cell -> value = value_nameslist;
+		}
+		
+		names_options["ParentTagName"] = "label";
+		NamesList label_nameslist;
+		label_nameslist.fromXml(cell_element, names_options);
+		if (!label_nameslist.name().isEmpty()) {
+			loaded_cell -> label = label_nameslist;
+		}
+		
+		if (cell_element.hasAttribute("displaylabel")) {
+			if (cell_element.attribute("displaylabel").compare("false", Qt::CaseInsensitive) == 0) {
+				loaded_cell -> display_label = false;
 			}
 		}
+		int fontsize;
+		if (QET::attributeIsAnInteger(cell_element, "fontsize", &fontsize)) {
+			loaded_cell -> font_size = fontsize;
+		} else {
+			loaded_cell -> font_size = -1;
+		}
+		
+		// horizontal and vertical alignments
+		loaded_cell -> alignment = 0;
+		
+		QString halignment = cell_element.attribute("align", "left");
+		if (halignment == "right") loaded_cell -> alignment |= Qt::AlignRight;
+		else if (halignment == "center") loaded_cell -> alignment |= Qt::AlignHCenter;
+		else loaded_cell -> alignment |= Qt::AlignLeft;
+		
+		QString valignment = cell_element.attribute("valign", "center");
+		if (valignment == "bottom") loaded_cell -> alignment |= Qt::AlignBottom;
+		else if (valignment == "top") loaded_cell -> alignment |= Qt::AlignTop;
+		else loaded_cell -> alignment |= Qt::AlignVCenter;
+		
+		// horizontal text adjustment
+		loaded_cell -> hadjust = cell_element.attribute("hadjust", "true") == "true";
 	}
+}
+
+/**
+	Export this template's logos as XML
+	@param xml_element XML Element under which the \<logos\> element will be attached
+*/
+void TitleBlockTemplate::saveLogos(QDomElement &xml_element) const {
+	QDomElement logos_element = xml_element.ownerDocument().createElement("logos");
+	foreach(QString logo_name, type_logos_.keys()) {
+		QDomElement logo_element = xml_element.ownerDocument().createElement("logo");
+		saveLogo(logo_name, logo_element);
+		logos_element.appendChild(logo_element);
+	}
+	xml_element.appendChild(logos_element);
+}
+
+/**
+	Export a specific logo as XML
+	@param logo_name Name of the logo to be exported
+	@param xml_element XML element in which the logo will be exported
+*/
+void TitleBlockTemplate::saveLogo(const QString &logo_name, QDomElement &xml_element) const {
+	if (!type_logos_.contains(logo_name)) return;
 	
-	return(true);
+	xml_element.setAttribute("name", logo_name);
+	xml_element.setAttribute("type", type_logos_[logo_name]);
+	xml_element.setAttribute("storage", storage_logos_[logo_name]);
+	
+	if (storage_logos_[logo_name] == "xml" && type_logos_[logo_name] == "svg") {
+		QDomDocument svg_logo;
+		svg_logo.setContent(data_logos_[logo_name]);
+		QDomNode svg_logo_element = xml_element.ownerDocument().importNode(svg_logo.documentElement(), true);
+		xml_element.appendChild(svg_logo_element.toElement());
+	} else if (storage_logos_[logo_name] == "base64") {
+		QDomText base64_logo = xml_element.ownerDocument().createTextNode(data_logos_[logo_name].toBase64());
+		xml_element.appendChild(base64_logo);
+	}
 }
 
 /**
+	Export this template's cells grid as XML
+	@param xml_element XML element under which the \<grid\> element will be attached
+*/
+void TitleBlockTemplate::saveGrid(QDomElement &xml_element) const {
+	QDomElement grid_element = xml_element.ownerDocument().createElement("grid");
+	
+	QString rows_attr, cols_attr;
+	foreach(int row_height, rows_heights_) rows_attr += QString("%1;").arg(row_height);
+	foreach(TitleBlockDimension col_width, columns_width_) cols_attr += col_width.toShortString();
+	grid_element.setAttribute("rows", rows_attr);
+	grid_element.setAttribute("cols", cols_attr);
+	
+	saveCells(grid_element);
+	
+	xml_element.appendChild(grid_element);
+}
+
+/**
+	Export this template's cells as XML (without the grid-related information, usch as rows and cols)
+	@param xml_element XML element under which the \<cell\> elements will be attached
+*/
+void TitleBlockTemplate::saveCells(QDomElement &xml_element) const {
+	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
+		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
+			if (cells_[i][j] -> cell_type != TitleBlockCell::EmptyCell) {
+				saveCell(cells_[i][j], xml_element);
+			}
+		}
+	}
+}
+
+/**
+	Export a specific cell as XML
+	@param cell Cell to be exported as XML
+	@param xml_element XML element under which the \<cell\> element will be attached
+*/
+void TitleBlockTemplate::saveCell(TitleBlockCell *cell, QDomElement &xml_element) const {
+	if (!cell || cell -> cell_type == TitleBlockCell::EmptyCell) return;
+	if (cell -> spanner_cell) return;
+	
+	QDomElement cell_elmt = xml_element.ownerDocument().createElement("cell");
+	cell_elmt.setAttribute("name", cell -> value_name);
+	cell_elmt.setAttribute("row", cell -> num_row);
+	cell_elmt.setAttribute("col", cell -> num_col);
+	if (cell -> row_span) cell_elmt.setAttribute("rowspan", cell -> row_span);
+	if (cell -> col_span) cell_elmt.setAttribute("colspan", cell -> col_span);
+	
+	if (cell -> type() == TitleBlockCell::LogoCell) {
+		cell_elmt.setTagName("logo");
+		cell_elmt.setAttribute("resource", cell -> logo_reference);
+	} else {
+		cell_elmt.setTagName("field");
+		
+		QDomDocument parent_document = xml_element.ownerDocument();
+		
+		QHash<QString, QString> names_options;
+		names_options["TagName"] = "translation";
+		names_options["ParentTagName"] = "value";
+		cell_elmt.appendChild(cell -> value.toXml(parent_document, names_options));
+		names_options["ParentTagName"] = "label";
+		cell_elmt.appendChild(cell -> label.toXml(parent_document, names_options));
+		
+		cell_elmt.setAttribute("displaylabel", cell -> display_label ? "true" : "false");
+		if (cell -> font_size != -1) {
+			cell_elmt.setAttribute("fontsize", cell -> font_size);
+		}
+		
+		if (cell -> alignment & Qt::AlignRight) {
+			cell_elmt.setAttribute("align", "right");
+		} else if (cell -> alignment & Qt::AlignHCenter) {
+			cell_elmt.setAttribute("align", "center");
+		} else {
+			cell_elmt.setAttribute("align", "left");
+		}
+		
+		if (cell -> alignment & Qt::AlignBottom) {
+			cell_elmt.setAttribute("valign", "bottom");
+		} else if (cell -> alignment & Qt::AlignTop) {
+			cell_elmt.setAttribute("valign", "top");
+		} else {
+			cell_elmt.setAttribute("valign", "center");
+		}
+		
+		if (cell -> hadjust) cell_elmt.setAttribute("hadjust", "true");
+	}
+	
+	xml_element.appendChild(cell_elmt);
+}
+
+/**
+	Load the essential attributes of a cell: row and column indices and spans.
 	@param xml_element XML element representing a cell, i.e. either an titleblock
 	logo or an titleblock field.
-	@param titleblock_cell_ptr Pointer to an TitleBlockCell object pointer - if non-zero and if
+	@param titleblock_cell_ptr Pointer to a TitleBlockCell object pointer - if non-zero and if
 	this method returns true, will be filled with the created TitleBlockCell
 	@return TRUE if the cell appears to be ok, FALSE otherwise
 */
@@ -350,10 +583,13 @@
 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
 	qDebug() << Q_FUNC_INFO << "cell access" << col_num << row_num;
 #endif
-	TitleBlockCell *cell_ptr = &(cells_[col_num][row_num]);
-	if (!cell_ptr -> is_null || cell_ptr -> spanner_cell) {
+	TitleBlockCell *cell_ptr = cells_[col_num][row_num];
+	if (cell_ptr -> cell_type != TitleBlockCell::EmptyCell || cell_ptr -> spanner_cell) {
 		return(false);
 	}
+	// ensure the num_row and num_col attributes are alright
+	cell_ptr -> num_row = row_num;
+	cell_ptr -> num_col = col_num;
 	
 	// parse the rowspan and colspan attributes
 	if (QET::attributeIsAnInteger(xml_element, "rowspan", &row_span) && row_span > 0) {
@@ -367,69 +603,35 @@
 	}
 	
 	// check if we can span on the required area
-	if (has_row_span || has_col_span) {
-		for (int i = col_num ; i <= col_num + col_span ; ++ i) {
-			for (int j = row_num ; j <= row_num + row_span ; ++ j) {
-				if (i == col_num && j == row_num) continue;
-#ifdef TITLEBLOCK_TEMPLATE_DEBUG
-				qDebug() << Q_FUNC_INFO << "span check" << i << j;
-#endif
-				TitleBlockCell *current_cell = &(cells_[i][j]);
-				if (!current_cell -> is_null || current_cell -> spanner_cell) {
-					return(false);
-				}
-			}
-		}
-	}
+	//if (!checkCellSpan(cell_ptr)) return(false);
 	
 	// at this point, the cell is ok - we fill the adequate cells in the matrix
 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
 	qDebug() << Q_FUNC_INFO << "cell writing";
 #endif
-	cell_ptr -> num_row = row_num;
-	cell_ptr -> num_col = col_num;
 	if (has_row_span) cell_ptr -> row_span = row_span;
 	if (has_col_span) cell_ptr -> col_span = col_span;
-	cell_ptr -> is_null = false;
 	if (titleblock_cell_ptr) *titleblock_cell_ptr = cell_ptr;
 	
-	if (has_row_span || has_col_span) {
-		for (int i = col_num ; i <= col_num + col_span ; ++ i) {
-			for (int j = row_num ; j <= row_num + row_span ; ++ j) {
-				if (i == col_num && j == row_num) continue;
-#ifdef TITLEBLOCK_TEMPLATE_DEBUG
-				qDebug() << Q_FUNC_INFO << "span cells writing" << i << j;
-#endif
-				TitleBlockCell *current_cell = &(cells_[i][j]);
-				current_cell -> num_row = j;
-				current_cell -> num_col = i;
-				current_cell -> is_null = false;
-				current_cell -> spanner_cell = cell_ptr;
-			}
-		}
-	}
+	//applyCellSpan(cell_ptr);
 	
 	return(true);
 }
 
 /**
-	Initializes the internal cells grid with the row and column counts.
+	Initialize the internal cells grid with the row and column counts.
 	Note that this method does nothing if one of the internal lists
 	columns_width_ and rows_heights_ is empty.
 */
 void TitleBlockTemplate::initCells() {
 	if (columns_width_.count() < 1 || rows_heights_.count() < 1) return;
 	
-	cells_.resize(columns_width_.count());
-	int row_count = rows_heights_.count();
+	cells_.clear();
+	qDeleteAll(registered_cells_);
+	registered_cells_.clear();
 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
-		cells_[i].resize(row_count);
-		// ensure every cell is a null cell
-		for (int j = 0 ; j < row_count ; ++ j) {
-			cells_[i][j] = TitleBlockCell();
-		}
+		cells_ << createColumn();
 	}
-	
 #ifdef TITLEBLOCK_TEMPLATE_DEBUG
 	qDebug() << Q_FUNC_INFO << toString();
 #endif
@@ -443,7 +645,7 @@
 	QString str = "\n";
 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
-			str += cells_[i][j].toString() + "    ";
+			str += cells_[i][j] -> toString() + "    ";
 		}
 		str += "\n";
 	}
@@ -458,6 +660,68 @@
 }
 
 /**
+	@param i row index
+	@return the height of the row at index i
+*/
+int TitleBlockTemplate::rowDimension(int i) {
+	int index = (i == -1) ? rows_heights_.count() - 1 : i;
+	if (index >= 0 || index < rows_heights_.count()) {
+		return(rows_heights_.at(index));
+	}
+	return(-1);
+}
+
+/**
+	Set the height of a row
+	@param i row index
+	@param dimension New height of the row at index i
+*/
+void TitleBlockTemplate::setRowDimension(int i, const TitleBlockDimension &dimension) {
+	int index = (i == -1) ? rows_heights_.count() - 1 : i;
+	if (index >= 0 || index < rows_heights_.count()) {
+		rows_heights_[index] = dimension.value;
+	}
+}
+
+/**
+	@param i column index
+	@return the width of the column at index i
+*/
+TitleBlockDimension TitleBlockTemplate::columnDimension(int i) {
+	int index = (i == -1) ? columns_width_.count() - 1 : i;
+	if (index >= 0 || index < columns_width_.count()) {
+		return(columns_width_.at(index));
+	}
+	return(TitleBlockDimension(-1));
+}
+
+/**
+	Set the width of a column
+	@param i column index
+	@param dimension New width of the column at index i
+*/
+void TitleBlockTemplate::setColumnDimension(int i, const TitleBlockDimension &dimension) {
+	int index = (i == -1) ? columns_width_.count() - 1 : i;
+	if (index >= 0 || index < columns_width_.count()) {
+		columns_width_[index] = dimension;
+	}
+}
+
+/**
+	@return the number of columns in this template
+*/
+int TitleBlockTemplate::columnsCount() const {
+	return(columns_width_.count());
+}
+
+/**
+	@return the number of rows in this template
+*/
+int TitleBlockTemplate::rowsCount() const {
+	return(rows_heights_.count());
+}
+
+/**
 	@param total_width The total width of the titleblock to render
 	@return the list of the columns widths for this rendering
 */
@@ -469,7 +733,7 @@
 	int abs_widths_sum = 0;
 	
 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
-		TitleBlockColDimension icd = columns_width_.at(i);
+		TitleBlockDimension icd = columns_width_.at(i);
 		if (icd.type == QET::Absolute) {
 			abs_widths_sum += icd.value;
 			final_widths[i] = icd.value;
@@ -485,7 +749,7 @@
 	
 	// we do a second iteration to build the final widths list
 	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
-		TitleBlockColDimension icd = columns_width_.at(i);
+		TitleBlockDimension icd = columns_width_.at(i);
 		if (icd.type == QET::RelativeToRemainingLength) {
 			final_widths[i] = int(remaining_width * icd.value / 100);
 		}
@@ -493,6 +757,16 @@
 	return(final_widths.toList());
 }
 
+/**
+	@return the heights of all the rows in this template
+*/
+QList<int> TitleBlockTemplate::rowsHeights() const {
+	return(rows_heights_);
+}
+
+/**
+	@return the total height of this template
+*/
 int TitleBlockTemplate::height() const {
 	int height = 0;
 	foreach(int row_height, rows_heights_) {
@@ -502,6 +776,328 @@
 }
 
 /**
+	Move a row within this template.
+	@param from Index of the moved row
+	@param to Arrival index of the moved row
+*/
+bool TitleBlockTemplate::moveRow(int from, int to) {
+	// checks from and to
+	if (from >= rows_heights_.count()) return(false);
+	if (to   >= rows_heights_.count()) return(false);
+	for (int j = 0 ; j < columns_width_.count() ; ++ j) {
+		cells_[j].move(from, to);
+	}
+	rows_heights_.move(from, to);
+	rowColsChanged();
+	return(true);
+}
+
+/**
+	Add a new 25px-wide row at the provided index.
+	@param i Index of the added row, -1 meaning "last position"
+*/
+void TitleBlockTemplate::addRow(int i) {
+	insertRow(25, createRow(), i);
+}
+
+/**
+	@param dimension Size of the row to be added (always absolute, in pixels)
+	@param column Row to be added
+	@param i Index of the column after insertion, -1 meaning "last position"
+*/
+bool TitleBlockTemplate::insertRow(int dimension, const QList<TitleBlockCell *> &row, int i) {
+	int index = (i == -1) ? rows_heights_.count() : i;
+	
+	for (int j = 0 ; j < columns_width_.count() ; ++ j) {
+		cells_[j].insert(index, row[j]);
+	}
+	rows_heights_.insert(index, dimension);
+	rowColsChanged();
+	return(true);
+}
+
+/**
+	Removes the row at index i
+	@param i Index of the column to be removed
+	@return the removed column
+*/
+QList<TitleBlockCell *> TitleBlockTemplate::takeRow(int i) {
+	QList<TitleBlockCell *> row;
+	int index = (i == -1) ? rows_heights_.count() - 1 : i;
+	if (index < 0 || index >= rows_heights_.count()) return(row);
+	for (int j = 0 ; j < columns_width_.count() ; ++ j) {
+		row << cells_[j].takeAt(index);
+	}
+	rows_heights_.removeAt(index);
+	rowColsChanged();
+	return(row);
+}
+
+/**
+	@return a new row that fits the current grid
+*/
+QList<TitleBlockCell *> TitleBlockTemplate::createRow() {
+	return(createCellsList(columns_width_.count()));
+	
+}
+
+/**
+	Move the column at index "from" to index "to".
+	@param from Source index of the moved column
+	@param to   Target index of the moved column
+*/
+bool TitleBlockTemplate::moveColumn(int from, int to) {
+	// checks from and to
+	if (from >= columns_width_.count()) return(false);
+	if (to   >= columns_width_.count()) return(false);
+	cells_.move(from, to);
+	columns_width_.move(from, to);
+	rowColsChanged();
+	return(true);
+}
+
+/**
+	Add a new 50px-wide column at the provided index.
+	@param i Index of the added column, -1 meaning "last position"
+*/
+void TitleBlockTemplate::addColumn(int i) {
+	insertColumn(TitleBlockDimension(50, QET::Absolute), createColumn(), i);
+}
+
+/**
+	@param dimension Size of the column to be added
+	@param column Column to be added
+	@param i Index of the column after insertion, -1 meaning "last position"
+*/
+bool TitleBlockTemplate::insertColumn(const TitleBlockDimension &dimension, const QList<TitleBlockCell *> &column, int i) {
+	int index = (i == -1) ? columns_width_.count() : i;
+	cells_.insert(index, column);
+	columns_width_.insert(index, dimension);
+	rowColsChanged();
+	return(true);
+}
+
+/**
+	Removes the column at index i
+	@param i Index of the column to be removed
+	@return the removed column
+*/
+QList<TitleBlockCell *> TitleBlockTemplate::takeColumn(int i) {
+	int index = (i == -1) ? columns_width_.count() - 1 : i;
+	if (index < 0 || index >= columns_width_.count()) {
+		return(QList<TitleBlockCell *>());
+	}
+	QList<TitleBlockCell *> column = cells_.takeAt(i);
+	columns_width_.removeAt(i);
+	rowColsChanged();
+	return(column);
+}
+
+/**
+	@return a new column that fits the current grid
+*/
+QList<TitleBlockCell *> TitleBlockTemplate::createColumn() {
+	return(createCellsList(rows_heights_.count()));
+}
+
+/**
+	@param row A row number (starting from 0)
+	@param col A column number (starting from 0)
+	@return the cell located at (row, col)
+*/
+TitleBlockCell *TitleBlockTemplate::cell(int row, int col) const {
+	if (row >= rows_heights_.count()) return(0);
+	if (col >= columns_width_.count()) return(0);
+	
+	return(cells_[col][row]);
+}
+
+/**
+	@param cell A cell belonging to this title block template
+	@return the set of cells spanned by the provided cell
+	Note the returned set does not include the spanning, provided cell
+*/
+QSet<TitleBlockCell *> TitleBlockTemplate::spannedCells(const TitleBlockCell *given_cell) const {
+	QSet<TitleBlockCell *> set;
+	if (!given_cell || !given_cell -> spans()) return(set);
+	
+	for (int i = given_cell -> num_col ; i <= given_cell -> num_col + given_cell -> col_span ; ++ i) {
+		for (int j = given_cell -> num_row ; j <= given_cell -> num_row + given_cell -> row_span ; ++ j) {
+			if (i == given_cell -> num_col && j == given_cell -> num_row) continue;
+			TitleBlockCell *current_cell = cell(j, i);
+			if (current_cell) set << current_cell;
+		}
+	}
+	return(set);
+}
+
+/**
+	@param logo_name Logo name to be added / replaced
+	@param logo_data Logo data
+*/
+bool TitleBlockTemplate::addLogo(const QString &logo_name, QByteArray *logo_data, const QString &logo_type, const QString &logo_storage) {
+	if (data_logos_.contains(logo_name)) {
+		// we are replacing the logo
+		removeLogo(logo_name);
+	}
+	
+	// we can now create our image object from the byte array
+	if (logo_type == "svg") {
+		// SVG format is handled by the QSvgRenderer class
+		QSvgRenderer *svg = new QSvgRenderer();
+		if (!svg -> load(*logo_data)) {
+			return(false);
+		}
+		vector_logos_.insert(logo_name, svg);
+		
+		// we also memorize the way to store them in the final XML output
+		QString final_logo_storage = logo_storage;
+		if (logo_storage != "xml" && logo_storage != "base64") {
+			final_logo_storage = "xml";
+		}
+		storage_logos_.insert(logo_name, logo_storage);
+	} else {
+		
+		// bitmap formats are handled by the QPixmap class
+		QPixmap logo_pixmap;
+		logo_pixmap.loadFromData(*logo_data);
+		if (!logo_pixmap.width() || !logo_pixmap.height()) {
+			return(false);
+		}
+		bitmap_logos_.insert(logo_name, logo_pixmap);
+		
+		// bitmap logos can only be stored using a base64 encoding
+		storage_logos_.insert(logo_name, "base64");
+	}
+	
+	// we systematically store the raw data
+	data_logos_.insert(logo_name, *logo_data);
+	type_logos_.insert(logo_name, logo_type);
+	
+	return(true);
+}
+
+/**
+	@param filepath Path of the image file to add as a logo
+	@param logo_name Name used to store the logo; if none is provided, the
+	basename of the first argument is used.
+	@return true if the logo could be deleted, false otherwise
+*/
+bool TitleBlockTemplate::addLogoFromFile(const QString &filepath, const QString &name) {
+	QFileInfo filepath_info(filepath);
+	QString filename = name.isEmpty() ? filepath_info.fileName() : name;
+	QString filetype = filepath_info.suffix();
+	
+	// we read the provided logo
+	QFile logo_file(filepath);
+	if (!logo_file.open(QIODevice::ReadOnly)) return(false);
+	QByteArray file_content = logo_file.readAll();
+	
+	// first, we try to add it as an SVG image
+	if (addLogo(filename, &file_content, "svg", "xml")) return(true);
+	
+	// we then try to add it as a bitmap image
+	return addLogo(filename, &file_content, filepath_info.suffix(), "base64");
+}
+
+/**
+	@param logo_name Name of the logo to remove
+	@return true if the logo could be deleted, false otherwise
+*/
+bool TitleBlockTemplate::removeLogo(const QString &logo_name) {
+	if (!data_logos_.contains(logo_name)) {
+		return(false);
+	}
+	
+	/// TODO check existing cells using this logo.
+	if (vector_logos_.contains(logo_name)) {
+		delete vector_logos_.take(logo_name);
+	}
+	if (bitmap_logos_.contains(logo_name)) {
+		bitmap_logos_.remove(logo_name);
+	}
+	data_logos_.remove(logo_name);
+	storage_logos_.remove(logo_name);
+	return(true);
+}
+
+/**
+	Rename the \a logo_name logo to \a new_name
+	@param logo_name Name of the logo to be renamed
+	@param new_name New name of the renamed logo
+*/
+bool TitleBlockTemplate::renameLogo(const QString &logo_name, const QString &new_name) {
+	if (!data_logos_.contains(logo_name) || data_logos_.contains(new_name)) {
+		return(false);
+	}
+	
+	/// TODO check existing cells using this logo.
+	if (vector_logos_.contains(logo_name)) {
+		vector_logos_.insert(new_name, vector_logos_.take(logo_name));
+	}
+	if (bitmap_logos_.contains(logo_name)) {
+		bitmap_logos_.insert(new_name, bitmap_logos_.take(logo_name));
+	}
+	data_logos_.insert(new_name, data_logos_.take(logo_name));
+	storage_logos_.insert(new_name, storage_logos_.take(logo_name));
+	return(true);
+}
+
+/**
+	Set the kind of storage for the \a logo_name logo.
+	@param logo_name Name of the logo which kind of storage is to be changed
+	@param storage The kind of storage to use for the logo, e.g. "xml" or "base64".
+*/
+void TitleBlockTemplate::setLogoStorage(const QString &logo_name, const QString &storage) {
+	if (storage_logos_.contains(logo_name)) {
+		storage_logos_[logo_name] = storage;
+	}
+}
+
+/**
+	@return The names of logos embedded within this title block template.
+*/
+QList<QString> TitleBlockTemplate::logos() const {
+	return(data_logos_.keys());
+}
+
+/**
+	@param logo_name Name of a logo embedded within this title block template.
+	@return the kind of storage used for the required logo, or a null QString
+	if no such logo was found in this template.
+*/
+QString TitleBlockTemplate::logoType(const QString &logo_name) const {
+	if (type_logos_.contains(logo_name)) {
+		return type_logos_[logo_name];
+	}
+	return(QString());
+}
+
+/**
+	@param logo_name Name of a vector logo embedded within this title block template.
+	@return the rendering object for the required vector logo, or 0 if no such
+	vector logo was found in this template.
+*/
+QSvgRenderer *TitleBlockTemplate::vectorLogo(const QString &logo_name) const {
+	if (vector_logos_.contains(logo_name)) {
+		return vector_logos_[logo_name];
+	}
+	return(0);
+}
+
+/**
+	@param logo_name Name of a logo embedded within this title block template.  
+	@return the pixmap for the required bitmap logo, or a null pixmap if no
+	such bitmap logo was found in this template.
+*/
+QPixmap TitleBlockTemplate::bitmapLogo(const QString &logo_name) const {
+	if (bitmap_logos_.contains(logo_name)) {
+		return bitmap_logos_[logo_name];
+	}
+	return(QPixmap());
+}
+
+/**
 	Render the titleblock.
 	@param painter Painter to use to render the titleblock
 	@param diagram_context Diagram context to use to generate the titleblock strings
@@ -521,38 +1117,56 @@
 	// run through each inidividual cell
 	for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
 		for (int i = 0 ; i < columns_width_.count() ; ++ i) {
-			if (cells_[i][j].spanner_cell || cells_[i][j].is_null) continue;
+			if (cells_[i][j] -> spanner_cell || cells_[i][j] -> cell_type == TitleBlockCell::EmptyCell) continue;
 			
 			// calculate the border rect of the current cell
-			int x = lengthRange(0, cells_[i][j].num_col, widths); 
-			int y = lengthRange(0, cells_[i][j].num_row, rows_heights_);
-			int w = lengthRange(cells_[i][j].num_col, cells_[i][j].num_col + 1 + cells_[i][j].col_span, widths);
-			int h = lengthRange(cells_[i][j].num_row, cells_[i][j].num_row + 1 + cells_[i][j].row_span, rows_heights_);
+			int x = lengthRange(0, cells_[i][j] -> num_col, widths);
+			int y = lengthRange(0, cells_[i][j] -> num_row, rows_heights_);
+			int w = lengthRange(cells_[i][j] -> num_col, cells_[i][j] -> num_col + 1 + cells_[i][j] -> col_span, widths);
+			int h = lengthRange(cells_[i][j] -> num_row, cells_[i][j] -> num_row + 1 + cells_[i][j] -> row_span, rows_heights_);
 			QRect cell_rect(x, y, w, h);
 			
-			// draw the border rect of the current cell
-			painter.drawRect(cell_rect);
-			
-			// render the inner content of the current cell
-			if (!cells_[i][j].logo_reference.isEmpty()) {
-				// the current cell appear to be a logo - we first look for the
-				// logo reference in our vector logos list, since they offer a
-				// potentially better (or, at least, not resolution-limited) rendering
-				if (vector_logos_.contains(cells_[i][j].logo_reference)) {
-					vector_logos_[cells_[i][j].logo_reference] -> render(&painter, cell_rect);
-				} else if (bitmap_logos_.contains(cells_[i][j].logo_reference)) {
-					painter.drawPixmap(cell_rect, *(bitmap_logos_[cells_[i][j].logo_reference]));
-				}
-			} else {
-				QString final_text = finalTextForCell(cells_[i][j], diagram_context);
-				renderTextCell(painter, final_text, cells_[i][j], cell_rect);
+			renderCell(painter, *cells_[i][j], diagram_context, cell_rect);
+		}
+	}
+}
+
+/**
+	Render a titleblock cell.
+	@param painter Painter to use to render the titleblock
+	@param diagram_context Diagram context to use to generate the titleblock strings
+	@param rect Rectangle the cell must be rendered into.
+*/
+void TitleBlockTemplate::renderCell(QPainter &painter, const TitleBlockCell &cell, const DiagramContext &diagram_context, const QRect &cell_rect) const {
+	// draw the border rect of the current cell
+	QPen pen(QBrush(), 0.0, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
+	pen.setColor(Qt::black);
+	painter.setPen(pen);
+	painter.setBrush(Qt::white);
+	painter.drawRect(cell_rect);
+	
+	painter.save();
+	// render the inner content of the current cell
+	if (cell.type() == TitleBlockCell::LogoCell) {
+		if (!cell.logo_reference.isEmpty()) {
+			// the current cell appears to be a logo - we first look for the
+			// logo reference in our vector logos list, since they offer a
+			// potentially better (or, at least, not resolution-limited) rendering
+			if (vector_logos_.contains(cell.logo_reference)) {
+				vector_logos_[cell.logo_reference] -> render(&painter, cell_rect);
+			} else if (bitmap_logos_.contains(cell.logo_reference)) {
+				painter.drawPixmap(cell_rect, bitmap_logos_[cell.logo_reference]);
 			}
-			
-			// draw again the border rect of the current cell, without the brush this time
-			painter.setBrush(Qt::NoBrush);
-			painter.drawRect(cell_rect);
 		}
+	} else if (cell.type() == TitleBlockCell::TextCell) {
+		QString final_text = finalTextForCell(cell, diagram_context);
+		renderTextCell(painter, final_text, cell, cell_rect);
 	}
+	painter.restore();
+	
+	// draw again the border rect of the current cell, without the brush this time
+	painter.setBrush(Qt::NoBrush);
+	painter.drawRect(cell_rect);
 }
 
 /**
@@ -561,14 +1175,15 @@
 	@return the final text that has to be drawn in the given cell
 */
 QString TitleBlockTemplate::finalTextForCell(const TitleBlockCell &cell, const DiagramContext &diagram_context) const {
-	QString cell_text = cell.value;
+	QString cell_text = cell.value.name();
+	QString cell_label = cell.label.name();
 	
 	foreach (QString key, diagram_context.keys()) {
 		cell_text.replace("%{" + key + "}", diagram_context[key].toString());
 		cell_text.replace("%" + key,        diagram_context[key].toString());
 	}
 	if (cell.display_label && !cell.label.isEmpty()) {
-		cell_text = QString(tr(" %1 : %2", "titleblock content - please let the blank space at the beginning")).arg(cell.label).arg(cell_text);
+		cell_text = QString(tr(" %1 : %2", "titleblock content - please let the blank space at the beginning")).arg(cell_label).arg(cell_text);
 	} else {
 		cell_text = QString(tr(" %1")).arg(cell_text);
 	}
@@ -586,7 +1201,7 @@
 	@param cell_rect Rectangle delimiting the cell area
 */
 void TitleBlockTemplate::renderTextCell(QPainter &painter, const QString &text, const TitleBlockCell &cell, const QRectF &cell_rect) const {
-	QFont text_font = cell.font_size == -1 ? QETApp::diagramTextsFont() : QETApp::diagramTextsFont(cell.font_size);
+	QFont text_font = TitleBlockTemplate::fontForCell(cell);
 	painter.setFont(text_font);
 	
 	if (cell.hadjust) {
@@ -617,13 +1232,113 @@
 }
 
 /**
+	Set the spanner_cell attribute of every cell to 0.
+*/
+void TitleBlockTemplate::forgetSpanning() {
+	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
+		for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
+			cells_[i][j] -> spanner_cell = 0;
+		}
+	}
+}
+
+/**
+	Forget any previously applied span, then apply again all spans defined
+	by existing cells.
+*/
+void TitleBlockTemplate::applyCellSpans() {
+	forgetSpanning();
+	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
+		for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
+			if (checkCellSpan(cells_[i][j])) {
+				applyCellSpan(cells_[i][j]);
+			}
+		}
+	}
+}
+
+/**
+	Check whether a given cell can be spanned according to its row_span and col_span attributes
+	@param cell Cell we want to check
+	@return true if the spanned
+*/
+bool TitleBlockTemplate::checkCellSpan(TitleBlockCell *cell/*, int policy = TitleBlockTemplate::???*/) {
+	if (!cell) return(false);
+	if (!cell -> row_span && !cell -> col_span) return(true);
+	
+	// ensure the cell can span as far as required
+	if (cell -> num_col + cell -> col_span >= columnsCount()) return(false);
+	if (cell -> num_row + cell -> row_span >= rowsCount()) return(false);
+	
+	// ensure cells that will be spanned are free/empty
+	for (int i = cell -> num_col ; i <= cell -> num_col + cell -> col_span ; ++ i) {
+		for (int j = cell -> num_row ; j <= cell -> num_row + cell -> row_span ; ++ j) {
+			if (i == cell -> num_col && j == cell -> num_row) continue;
+#ifdef TITLEBLOCK_TEMPLATE_DEBUG
+			qDebug() << Q_FUNC_INFO << "span check" << i << j;
+#endif
+			TitleBlockCell *current_cell = cells_[i][j];
+			if (current_cell -> cell_type != TitleBlockCell::EmptyCell || (current_cell -> spanner_cell && current_cell -> spanner_cell != cell)) {
+				return(false);
+			}
+		}
+	}
+	return(true);
+}
+
+/**
+	Ensure the spans of the provided cell are applied within the grid structure.
+	Note: this function does not check whether the spans of the provided cell make sense.
+	@param cell Potentially spanning cell
+*/
+void TitleBlockTemplate::applyCellSpan(TitleBlockCell *cell) {
+	if (!cell || (!cell -> row_span && !cell -> col_span)) return;
+	
+	// goes through every spanned cell
+	for (int i = cell -> num_col ; i <= cell -> num_col + cell -> col_span ; ++ i) {
+		for (int j = cell -> num_row ; j <= cell -> num_row + cell -> row_span ; ++ j) {
+			// avoid the spanning cell itself
+			if (i == cell -> num_col && j == cell -> num_row) continue;
+#ifdef TITLEBLOCK_TEMPLATE_DEBUG
+			qDebug() << Q_FUNC_INFO << "marking cell at" << j << i <<  "as spanned by cell at" << cell -> num_row <<  cell -> num_col;
+#endif
+			// marks all spanned cells with the spanning cell
+			cells_[i][j] -> spanner_cell = cell;
+		}
+	}
+}
+
+/**
+	Ensure all cells have the right col+row numbers.
+*/
+void TitleBlockTemplate::applyRowColNums() {
+	for (int i = 0 ; i < columns_width_.count() ; ++ i) {
+		for (int j = 0 ; j < rows_heights_.count() ; ++ j) {
+			cells_[i][j] -> num_col = i;
+			cells_[i][j] -> num_row = j;
+		}
+	}
+}
+
+/**
+	Take care of consistency and span-related problematics when
+	adding/moving/deleting rows and columns.
+*/
+void TitleBlockTemplate::rowColsChanged() {
+	applyRowColNums();
+	applyCellSpans();
+}
+
+/**
 	@return the width between two borders
 	@param start start border number
 	@param end end border number
 */
 int TitleBlockTemplate::lengthRange(int start, int end, const QList<int> &lengths_list) const {
 	if (start > end || start >= lengths_list.count() || end > lengths_list.count()) {
+#ifdef TITLEBLOCK_TEMPLATE_DEBUG
 		qDebug() << Q_FUNC_INFO << "wont use" << start << "and" << end;
+#endif
 		return(0);
 	}
 	
@@ -634,24 +1349,3 @@
 	return(length);
 }
 
-
-
-/**
-	Constructor
-*/
-TitleBlockCell::TitleBlockCell() {
-	num_row = num_col = -1;
-	row_span = col_span = 0;
-	display_label = is_null = true;
-	spanner_cell = 0;
-}
-
-/**
-	@return A string representing the titleblock cell
-*/
-QString TitleBlockCell::toString() const {
-	if (is_null) return("TitleBlockCell{null}");
-	QString span_desc = (row_span > 0 || col_span > 0) ? QString("+%3,%4").arg(row_span).arg(col_span) : QET::pointerString(spanner_cell);
-	QString base_desc = QString("TitleBlockCell{ [%1, %2] %3 }").arg(num_row).arg(num_col).arg(span_desc);
-	return(base_desc);
-}

Modified: branches/0.3/sources/titleblocktemplate.h
===================================================================
--- branches/0.3/sources/titleblocktemplate.h	2011-12-24 18:37:00 UTC (rev 1404)
+++ branches/0.3/sources/titleblocktemplate.h	2011-12-25 17:45:39 UTC (rev 1405)
@@ -21,47 +21,71 @@
 #include <QtSvg>
 #include "diagramcontext.h"
 #include "titleblockcell.h"
+#include "dimension.h"
 #include "qet.h"
 
 /**
-	This struct is a simple container associating a length with its type.
-	@see TitleBlockColumnLength 
-*/
-struct TitleBlockColDimension {
-	TitleBlockColDimension(int v, QET::TitleBlockColumnLength t = QET::Absolute) {
-		value = v;
-		type = t;
-	}
-	QET::TitleBlockColumnLength type;
-	int value;
-};
-
-/**
-	This class represents an titleblock templ)ate for an electric diagram.
+	This class represents an title block template for an electric diagram.
 	It can read from an XML document the layout of the table that graphically
-	represents the titleblock, and can produce a graphical rendering of it from a
+	represents the title block, and can produce a graphical rendering of it from a
 	diagram context (object embedding the informations of the diagram we want to
-	represent the titleblock.
+	represent the title block.
 */
 class TitleBlockTemplate : public QObject {
 	Q_OBJECT
 	
-	// constructeurs, destructeur
+	// constructors, destructor
 	public:
 	TitleBlockTemplate(QObject * = 0);
 	virtual ~TitleBlockTemplate();
 	private:
 	TitleBlockTemplate(const TitleBlockTemplate &);
 	
-	// methodes
+	// methods
 	public:
+	TitleBlockCell *createCell(const TitleBlockCell * = 0);
+	static QFont fontForCell(const TitleBlockCell &);
 	bool loadFromXmlFile(const QString &);
 	bool loadFromXmlElement(const QDomElement &);
+	bool saveToXmlElement(QDomElement &) const;
+	TitleBlockTemplate *clone() const;
 	QString name() const;
+	int rowDimension(int);
+	void setRowDimension(int, const TitleBlockDimension &);
+	TitleBlockDimension columnDimension(int);
+	void setColumnDimension(int, const TitleBlockDimension &);
+	int columnsCount() const;
+	int rowsCount() const;
 	QList<int> columnsWidth(int) const;
+	QList<int> rowsHeights() const;
 	int height() const;
 	
+	bool moveRow(int, int);
+	void addRow(int = -1);
+	bool insertRow(int, const QList<TitleBlockCell *> &, int = -1);
+	QList<TitleBlockCell *> takeRow(int);
+	QList<TitleBlockCell *> createRow();
+	
+	bool moveColumn(int, int);
+	void addColumn(int = -1);
+	bool insertColumn(const TitleBlockDimension &, const QList<TitleBlockCell *> &, int = -1);
+	QList<TitleBlockCell *> takeColumn(int);
+	QList<TitleBlockCell *> createColumn();
+	
+	TitleBlockCell *cell(int, int) const;
+	QSet<TitleBlockCell *> spannedCells(const TitleBlockCell *) const;
+	bool addLogo(const QString &, QByteArray *, const QString & = "svg", const QString & = "xml");
+	bool addLogoFromFile(const QString &, const QString & = QString());
+	bool removeLogo(const QString &);
+	bool renameLogo(const QString &, const QString &);
+	void setLogoStorage(const QString &, const QString &);
+	QList<QString> logos() const;
+	QString logoType(const QString &) const;
+	QSvgRenderer *vectorLogo(const QString &) const;
+	QPixmap bitmapLogo(const QString &) const;
+	
 	void render(QPainter &, const DiagramContext &, int) const;
+	void renderCell(QPainter &, const TitleBlockCell &, const DiagramContext &, const QRect &) const;
 	QString toString() const;
 	
 	protected:
@@ -69,6 +93,13 @@
 	bool loadLogo(const QDomElement &);
 	bool loadGrid(const QDomElement &);
 	bool loadCells(const QDomElement &);
+	void loadCell(const QDomElement &);
+	void saveLogos(QDomElement &) const;
+	void saveLogo(const QString &, QDomElement &) const;
+	void saveGrid(QDomElement &) const;
+	void saveCells(QDomElement &) const;
+	void saveCell(TitleBlockCell *, QDomElement &) const;
+	QList<TitleBlockCell *> createCellsList(int);
 	
 	private:
 	void parseRows(const QString &);
@@ -79,15 +110,26 @@
 	int lengthRange(int, int, const QList<int> &) const;
 	QString finalTextForCell(const TitleBlockCell &, const DiagramContext &) const;
 	void renderTextCell(QPainter &, const QString &, const TitleBlockCell &, const QRectF &) const;
+	void applyCellSpans();
+	void forgetSpanning();
+	bool checkCellSpan(TitleBlockCell *);
+	void applyCellSpan(TitleBlockCell *);
+	void applyRowColNums();
+	void rowColsChanged();
 	
-	// attributs
+	// attributes
 	private:
-	QDomDocument xml_description_;
-	QString name_;
-	QHash<QString, QSvgRenderer *> vector_logos_;
-	QHash<QString, QPixmap *>      bitmap_logos_;
-	QList<int> rows_heights_;
-	QList<TitleBlockColDimension> columns_width_;
-	QVector< QVector<TitleBlockCell> > cells_;
+	QString name_;                                   ///< name identifying the Title Block Template within its parent project
+	
+	QHash<QString, QByteArray >    data_logos_;      ///< Logos raw data
+	QHash<QString, QString>        storage_logos_;   ///< Logos applied storage type (e.g. "xml" or "base64")
+	QHash<QString, QString>        type_logos_;      ///< Logos types (e.g. "png", "jpeg", "svg")
+	QHash<QString, QSvgRenderer *> vector_logos_;    ///< Rendered objects for vector logos
+	QHash<QString, QPixmap>        bitmap_logos_;    ///< Pixmaps for bitmap logos
+	
+	QList<int> rows_heights_;                        ///< rows heights -- simple integers
+	QList<TitleBlockDimension> columns_width_;    ///< columns widths -- @see TitleBlockColDimension
+	QList<TitleBlockCell *> registered_cells_;       ///< Cells objects created rattached to this template, but not mandatorily used
+	QList< QList<TitleBlockCell *> > cells_;         ///< Cells grid
 };
 #endif


Mail converted by MHonArc 2.6.19+ http://listengine.tuxfamily.org/