diff --git a/qelectrotech.pro b/qelectrotech.pro index 191356000..60494adf1 100644 --- a/qelectrotech.pro +++ b/qelectrotech.pro @@ -54,11 +54,11 @@ DEFINES += QET_ALLOW_OVERRIDE_CD_OPTION 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 diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index e69284f70..a88785917 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -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 @@ void QETApp::openElementLocations(const QList &locations_list) 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(); } /** diff --git a/sources/titleblock/dimension.cpp b/sources/titleblock/dimension.cpp new file mode 100644 index 000000000..daf575cfd --- /dev/null +++ b/sources/titleblock/dimension.cpp @@ -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 . +*/ +#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); +} diff --git a/sources/titleblock/dimension.h b/sources/titleblock/dimension.h new file mode 100644 index 000000000..e84982195 --- /dev/null +++ b/sources/titleblock/dimension.h @@ -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 . +*/ +#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 diff --git a/sources/titleblock/dimensionwidget.cpp b/sources/titleblock/dimensionwidget.cpp new file mode 100644 index 000000000..f36c60ec1 --- /dev/null +++ b/sources/titleblock/dimensionwidget.cpp @@ -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 . +*/ +#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(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(); +} diff --git a/sources/titleblock/dimensionwidget.h b/sources/titleblock/dimensionwidget.h new file mode 100644 index 000000000..1c30ab208 --- /dev/null +++ b/sources/titleblock/dimensionwidget.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_DIMENSION_WIDGET_H +#define TITLEBLOCK_SLASH_DIMENSION_WIDGET_H +#include +#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 diff --git a/sources/titleblock/gridlayoutanimation.cpp b/sources/titleblock/gridlayoutanimation.cpp new file mode 100644 index 000000000..ab5b2f4ab --- /dev/null +++ b/sources/titleblock/gridlayoutanimation.cpp @@ -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 . +*/ +#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()); + } +} diff --git a/sources/titleblock/gridlayoutanimation.h b/sources/titleblock/gridlayoutanimation.h new file mode 100644 index 000000000..cf84454fb --- /dev/null +++ b/sources/titleblock/gridlayoutanimation.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_GRID_LAYOUT_ANIMATION_H +#define TITLEBLOCK_SLASH_GRID_LAYOUT_ANIMATION_H +#include + +/** + 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 diff --git a/sources/titleblock/helpercell.cpp b/sources/titleblock/helpercell.cpp new file mode 100644 index 000000000..a15607a4e --- /dev/null +++ b/sources/titleblock/helpercell.cpp @@ -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 . +*/ +#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 &actions) { + actions_ = actions; +} + +/** + @return the list of actions displayed by the context menu of this helper cell. +*/ +QList 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)); +} diff --git a/sources/titleblock/helpercell.h b/sources/titleblock/helpercell.h new file mode 100644 index 000000000..1558ce74c --- /dev/null +++ b/sources/titleblock/helpercell.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_HELPER_CELL_H +#define TITLEBLOCK_SLASH_HELPER_CELL_H +#include +#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 &); + virtual QList actions() const; + + protected: + void contextMenuEvent(QGraphicsSceneContextMenuEvent *); + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *); + + signals: + void contextMenuTriggered(HelperCell *); + void doubleClicked(HelperCell *); + + private: + QList actions_; ///< List of actions displayed by the context menu +}; +#endif diff --git a/sources/titleblock/qettemplateeditor.cpp b/sources/titleblock/qettemplateeditor.cpp new file mode 100644 index 000000000..5234db160 --- /dev/null +++ b/sources/titleblock/qettemplateeditor.cpp @@ -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 . +*/ +#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)), + this, + SLOT(selectedCellsChanged(QList)) + ); + 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 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(); +} diff --git a/sources/titleblock/qettemplateeditor.h b/sources/titleblock/qettemplateeditor.h new file mode 100644 index 000000000..01dd6d52e --- /dev/null +++ b/sources/titleblock/qettemplateeditor.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_QET_TEMPLATE_EDITOR_H +#define TITLEBLOCK_SLASH_QET_TEMPLATE_EDITOR_H +#include +#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); + 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 diff --git a/sources/titleblock/splittedhelpercell.cpp b/sources/titleblock/splittedhelpercell.cpp new file mode 100644 index 000000000..149aa7eb9 --- /dev/null +++ b/sources/titleblock/splittedhelpercell.cpp @@ -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 . +*/ +#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); +} diff --git a/sources/titleblock/splittedhelpercell.h b/sources/titleblock/splittedhelpercell.h new file mode 100644 index 000000000..3dc3d5117 --- /dev/null +++ b/sources/titleblock/splittedhelpercell.h @@ -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 . +*/ +#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 diff --git a/sources/titleblock/templatecellsset.cpp b/sources/titleblock/templatecellsset.cpp new file mode 100644 index 000000000..4d42ce0ff --- /dev/null +++ b/sources/titleblock/templatecellsset.cpp @@ -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 . +*/ +#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 ©) : + QList(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 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 top_cells; + foreach (TitleBlockTemplateVisualCell *cell_view, *this) { + if (TitleBlockCell *cell = cell_view -> cell()) { + top_cells.insertMulti(cell -> num_row, cell_view); + } + } + QList 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 bottom_cells; + foreach (TitleBlockTemplateVisualCell *cell_view, *this) { + bottom_cells.insertMulti(cell_view -> geometry().bottom(), cell_view); + } + QList 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 TitleBlockTemplateCellsSet::cells(bool include_spanned) const { + QSet 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); +} diff --git a/sources/titleblock/templatecellsset.h b/sources/titleblock/templatecellsset.h new file mode 100644 index 000000000..30b658190 --- /dev/null +++ b/sources/titleblock/templatecellsset.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_TEMPLATE_CELLS_SET_H +#define TITLEBLOCK_SLASH_TEMPLATE_CELLS_SET_H +#include +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 { + // 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 cells(bool = true) const; + + // attributes + public: + const TitleBlockTemplateView *parent_view_; ///< the view displaying the cells +}; +#endif diff --git a/sources/titleblock/templatecellwidget.cpp b/sources/titleblock/templatecellwidget.cpp new file mode 100644 index 000000000..0c116e023 --- /dev/null +++ b/sources/titleblock/templatecellwidget.cpp @@ -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 . +*/ +#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("Édition d'une cellule : %1", "label of and undo command when editing a cell") + .arg(TitleBlockCell::attributeName(attribute)) + ); + emit(cellModified(command)); +} diff --git a/sources/titleblock/templatecellwidget.h b/sources/titleblock/templatecellwidget.h new file mode 100644 index 000000000..4ff1fc64c --- /dev/null +++ b/sources/titleblock/templatecellwidget.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_TEMPLATE_CELL_WIDGET_H +#define TITLEBLOCK_SLASH_TEMPLATE_CELL_WIDGET_H +#include +#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 horiz_align_indexes_; + QLabel *vert_align_label_; + QComboBox *vert_align_input_; + QHash 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 diff --git a/sources/titleblock/templatecommands.cpp b/sources/titleblock/templatecommands.cpp new file mode 100644 index 000000000..53994e514 --- /dev/null +++ b/sources/titleblock/templatecommands.cpp @@ -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 . +*/ +#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(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 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 &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 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éparation 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(); +} diff --git a/sources/titleblock/templatecommands.h b/sources/titleblock/templatecommands.h new file mode 100644 index 000000000..40a7723c1 --- /dev/null +++ b/sources/titleblock/templatecommands.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_TEMPLATE_COMMANDS_H +#define TITLEBLOCK_SLASH_TEMPLATE_COMMANDS_H +#define MODIFY_TITLE_BLOCK_CELL_COMMAND_ID 6378 +#include +#include +#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 old_values_; ///< values before the cell is modified + QHash 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 cells() const; + void setCells(const QList &); + 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 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 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 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 diff --git a/sources/titleblock/templatelogomanager.cpp b/sources/titleblock/templatelogomanager.cpp new file mode 100644 index 000000000..a655beae6 --- /dev/null +++ b/sources/titleblock/templatelogomanager.cpp @@ -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 . +*/ +#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(); + } +} diff --git a/sources/titleblock/templatelogomanager.h b/sources/titleblock/templatelogomanager.h new file mode 100644 index 000000000..17cccc939 --- /dev/null +++ b/sources/titleblock/templatelogomanager.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_TEMPLATE_LOGO_MANAGER +#define TITLEBLOCK_SLASH_TEMPLATE_LOGO_MANAGER +#include +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 diff --git a/sources/titleblock/templateview.cpp b/sources/titleblock/templateview.cpp new file mode 100644 index 000000000..34cb490ac --- /dev/null +++ b/sources/titleblock/templateview.cpp @@ -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 . +*/ +#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 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 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 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 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 heights = tbtemplate_ -> rowsHeights(); + for (int i = 0 ; i < row_count ; ++ i) { + HelperCell *current_row_cell = static_cast(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(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() << 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 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(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 TitleBlockTemplateView::rowsActions() const { + return QList() << add_row_before_<< edit_row_dim_ << add_row_after_ << delete_row_; +} + +/** + @return the list of columns-specific actions. +*/ +QList TitleBlockTemplateView::columnsActions() const { + return QList() << 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 &items) const { + TitleBlockTemplateCellsSet set(this); + foreach (QGraphicsItem *item, items) { + if (TitleBlockTemplateVisualCell *cell_view = dynamic_cast(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)); +} + diff --git a/sources/titleblock/templateview.h b/sources/titleblock/templateview.h new file mode 100644 index 000000000..d26a6aa13 --- /dev/null +++ b/sources/titleblock/templateview.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_TEMPLATE_VIEW_H +#define TITLEBLOCK_SLASH_TEMPLATE_VIEW_H +#include +#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 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); + void gridModificationRequested(TitleBlockTemplateCommand *); + + private: + QList rowsActions() const; + QList columnsActions() const; + void setPreviewWidth(int); + void updateTotalWidthLabel(); + void requestGridModification(TitleBlockTemplateCommand *); + int lastContextMenuCellIndex() const; + int indexOf(QGraphicsLayoutItem *); + void removeItem(QGraphicsLayoutItem *); + TitleBlockTemplateCellsSet makeCellsSetFromGraphicsItems(const QList &) 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 diff --git a/sources/titleblock/templatevisualcell.cpp b/sources/titleblock/templatevisualcell.cpp new file mode 100644 index 000000000..597630868 --- /dev/null +++ b/sources/titleblock/templatevisualcell.cpp @@ -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 . +*/ +#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 TitleBlockTemplateVisualCell::cells() const { + QSet set; + if (cell_) { + if (template_) { + set = template_ -> spannedCells(cell_); + } + + // the TitleBlockCell rendered by this object + set << cell_; + } + return(set); +} diff --git a/sources/titleblock/templatevisualcell.h b/sources/titleblock/templatevisualcell.h new file mode 100644 index 000000000..cdc984a3e --- /dev/null +++ b/sources/titleblock/templatevisualcell.h @@ -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 . +*/ +#ifndef TITLEBLOCK_SLASH_QET_TEMPLATE_VISUAL_CELL_H +#define TITLEBLOCK_SLASH_QET_TEMPLATE_VISUAL_CELL_H +#include +#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 cells() const; + + // attributes + private: + TitleBlockTemplate *template_; ///< Title block template of the previewed cell + TitleBlockCell *cell_; ///< Previewed cell +}; +#endif diff --git a/sources/titleblockcell.cpp b/sources/titleblockcell.cpp new file mode 100644 index 000000000..57e19a6a1 --- /dev/null +++ b/sources/titleblockcell.cpp @@ -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(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(attr_value); + } else if (attribute == "displaylabel") { + display_label = attr_value.toBool(); + } else if (attribute == "value") { + value = qvariant_cast(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ée", "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); +} diff --git a/sources/titleblockcell.h b/sources/titleblockcell.h index 6e466a62c..7841dafb0 100644 --- a/sources/titleblockcell.h +++ b/sources/titleblockcell.h @@ -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 diff --git a/sources/titleblocktemplate.cpp b/sources/titleblocktemplate.cpp index 87172333f..98ed80e86 100644 --- a/sources/titleblocktemplate.cpp +++ b/sources/titleblocktemplate.cpp @@ -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(QObject *parent) : */ 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 TitleBlockTemplate::createCellsList(int count) { + QList 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 @@ bool TitleBlockTemplate::loadFromXmlElement(const QDomElement &xml_element) { 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 @@ bool TitleBlockTemplate::loadLogos(const QDomElement &xml_element, bool reset) { 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 @@ bool TitleBlockTemplate::loadLogos(const QDomElement &xml_element, bool reset) { } /** - 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 @@ bool TitleBlockTemplate::loadLogo(const QDomElement &xml_element) { // 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 @@ bool TitleBlockTemplate::loadLogo(const QDomElement &xml_element) { } else { return(false); } - - // 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();*/ - } +#ifdef TITLEBLOCK_TEMPLATE_DEBUG + qDebug() << Q_FUNC_INFO << logo_name << logo_type << logo_storage; +#endif + addLogo(logo_name, &logo_data, logo_type, logo_storage); 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 @@ bool TitleBlockTemplate::loadGrid(const QDomElement &xml_element) { 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 @@ void TitleBlockTemplate::parseRows(const QString &rows_string) { } /** - 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 @@ void TitleBlockTemplate::parseColumns(const QString &cols_string) { 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 @@ void TitleBlockTemplate::parseColumns(const QString &cols_string) { 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 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 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 \ 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; + + 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 \ 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 \ 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 \ 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 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 @@ bool TitleBlockTemplate::checkCell(const QDomElement &xml_element, TitleBlockCel #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 @@ bool TitleBlockTemplate::checkCell(const QDomElement &xml_element, TitleBlockCel } // 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 TitleBlockTemplate::toString() const { 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"; } @@ -457,6 +659,68 @@ QString TitleBlockTemplate::name() const { return(name_); } +/** + @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 @@ QList TitleBlockTemplate::columnsWidth(int total_width) const { 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 @@ QList TitleBlockTemplate::columnsWidth(int total_width) const { // 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 @@ QList TitleBlockTemplate::columnsWidth(int total_width) const { return(final_widths.toList()); } +/** + @return the heights of all the rows in this template +*/ +QList 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_) { @@ -501,6 +775,328 @@ int TitleBlockTemplate::height() const { return(height); } +/** + 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 &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 TitleBlockTemplate::takeRow(int i) { + QList 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 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 &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 TitleBlockTemplate::takeColumn(int i) { + int index = (i == -1) ? columns_width_.count() - 1 : i; + if (index < 0 || index >= columns_width_.count()) { + return(QList()); + } + QList column = cells_.takeAt(i); + columns_width_.removeAt(i); + rowColsChanged(); + return(column); +} + +/** + @return a new column that fits the current grid +*/ +QList 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 TitleBlockTemplate::spannedCells(const TitleBlockCell *given_cell) const { + QSet 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 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 @@ -521,54 +1117,73 @@ void TitleBlockTemplate::render(QPainter &painter, const DiagramContext &diagram // 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); - } - - // draw again the border rect of the current cell, without the brush this time - painter.setBrush(Qt::NoBrush); - painter.drawRect(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]); + } + } + } 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); +} + /** @param cell A cell from this template @param diagram_context Diagram context to use to generate the final text for the given cell @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 @@ QString TitleBlockTemplate::finalTextForCell(const TitleBlockCell &cell, const D @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) { @@ -616,6 +1231,104 @@ void TitleBlockTemplate::renderTextCell(QPainter &painter, const QString &text, painter.drawText(cell_rect, cell.alignment, text); } +/** + 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 @@ -623,7 +1336,9 @@ void TitleBlockTemplate::renderTextCell(QPainter &painter, const QString &text, */ int TitleBlockTemplate::lengthRange(int start, int end, const QList &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 @@ int TitleBlockTemplate::lengthRange(int start, int end, const QList &length 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); -} diff --git a/sources/titleblocktemplate.h b/sources/titleblocktemplate.h index 858e29a17..036f3b039 100644 --- a/sources/titleblocktemplate.h +++ b/sources/titleblocktemplate.h @@ -21,47 +21,71 @@ #include #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 columnsWidth(int) const; + QList rowsHeights() const; int height() const; + bool moveRow(int, int); + void addRow(int = -1); + bool insertRow(int, const QList &, int = -1); + QList takeRow(int); + QList createRow(); + + bool moveColumn(int, int); + void addColumn(int = -1); + bool insertColumn(const TitleBlockDimension &, const QList &, int = -1); + QList takeColumn(int); + QList createColumn(); + + TitleBlockCell *cell(int, int) const; + QSet 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 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 @@ class TitleBlockTemplate : public QObject { 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 createCellsList(int); private: void parseRows(const QString &); @@ -79,15 +110,26 @@ class TitleBlockTemplate : public QObject { int lengthRange(int, int, const QList &) 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 vector_logos_; - QHash bitmap_logos_; - QList rows_heights_; - QList columns_width_; - QVector< QVector > cells_; + QString name_; ///< name identifying the Title Block Template within its parent project + + QHash data_logos_; ///< Logos raw data + QHash storage_logos_; ///< Logos applied storage type (e.g. "xml" or "base64") + QHash type_logos_; ///< Logos types (e.g. "png", "jpeg", "svg") + QHash vector_logos_; ///< Rendered objects for vector logos + QHash bitmap_logos_; ///< Pixmaps for bitmap logos + + QList rows_heights_; ///< rows heights -- simple integers + QList columns_width_; ///< columns widths -- @see TitleBlockColDimension + QList registered_cells_; ///< Cells objects created rattached to this template, but not mandatorily used + QList< QList > cells_; ///< Cells grid }; #endif