From e11acf7b5793b598d0eddf0f856791c6764da742 Mon Sep 17 00:00:00 2001 From: xavier Date: Sun, 19 Dec 2010 18:08:08 +0000 Subject: [PATCH] Introduction of classes, structs and enums related to inset templates. git-svn-id: svn+ssh://svn.tuxfamily.org/svnroot/qet/qet/branches/0.3@1129 bfdf4180-ca20-0410-9c96-a3a8aa849046 --- sources/diagramcontext.cpp | 65 ++++ sources/diagramcontext.h | 40 +++ sources/insetcell.h | 41 +++ sources/insettemplate.cpp | 580 ++++++++++++++++++++++++++++++ sources/insettemplate.h | 92 +++++ sources/insettemplaterenderer.cpp | 96 +++++ sources/insettemplaterenderer.h | 45 +++ sources/qet.cpp | 13 + sources/qet.h | 8 + 9 files changed, 980 insertions(+) create mode 100644 sources/diagramcontext.cpp create mode 100644 sources/diagramcontext.h create mode 100644 sources/insetcell.h create mode 100644 sources/insettemplate.cpp create mode 100644 sources/insettemplate.h create mode 100644 sources/insettemplaterenderer.cpp create mode 100644 sources/insettemplaterenderer.h diff --git a/sources/diagramcontext.cpp b/sources/diagramcontext.cpp new file mode 100644 index 000000000..ce56d0bcd --- /dev/null +++ b/sources/diagramcontext.cpp @@ -0,0 +1,65 @@ +/* + Copyright 2006-2010 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 "diagramcontext.h" +#include + +/** + @return a list containing all the keys in the context object. +*/ +QList DiagramContext::keys() const { + return(content_.keys()); +} + +/** + @param key string key + @return true if that key is known to the diagram context, false otherwise +*/ +bool DiagramContext::contains(const QString &key) const { + return(content_.contains(key)); +} + +/** + @param key +*/ +const QVariant DiagramContext::operator[](const QString &key) const { + return(content_[key]); +} + +/** + @param key key to insert in the context - the key may only contain lowercase + letters and dashes + @see DiagramContext::keyIsAcceptable() + @param value value to insert in the context + @return true if the insertion succeeds, false otherwise +*/ +bool DiagramContext::addValue(const QString &key, const QVariant &value) { + if (keyIsAcceptable(key)) { + content_.insert(key, value); + return(true); + } + return(false); +} + +/** + @param key a key string + @return true if that key is acceptable, false otherwise +*/ +bool DiagramContext::keyIsAcceptable(const QString &key) const { + static QRegExp re("^[a-z-]+$"); + return(re.exactMatch(key)); +} diff --git a/sources/diagramcontext.h b/sources/diagramcontext.h new file mode 100644 index 000000000..50bfc55ae --- /dev/null +++ b/sources/diagramcontext.h @@ -0,0 +1,40 @@ +/* + Copyright 2006-2010 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 DIAGRAM_CONTEXT_H +#define DIAGRAM_CONTEXT_H +#include +#include +#include +/** + This class represents a diagram context, i.e. the data (a list of key/value + pairs) of a diagram at a given time. It is notably used by inset templates + to fetch the informations they need to do their rendering. +*/ +class DiagramContext { + public: + QList keys() const; + bool contains(const QString &) const; + const QVariant operator[](const QString &) const; + bool addValue(const QString &, const QVariant &); + + private: + bool keyIsAcceptable(const QString &) const; + /// Diagram context data (key/value pairs) + QHash content_; +}; +#endif diff --git a/sources/insetcell.h b/sources/insetcell.h new file mode 100644 index 000000000..4027cfdec --- /dev/null +++ b/sources/insetcell.h @@ -0,0 +1,41 @@ +/* + Copyright 2006-2010 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 INSET_CELL_H +#define INSET_CELL_H +/** + This class is a container for the various parameters of an inset cell + @see InsetColumnLength +*/ +class InsetCell { + public: + InsetCell(); + QString toString() const; + bool is_null; + int num_row; + int num_col; + int row_span; + int col_span; + InsetCell *spanner_cell; + QString value_name; + QString value; + QString label; + bool display_label; + int alignment; + QString logo_reference; +}; +#endif diff --git a/sources/insettemplate.cpp b/sources/insettemplate.cpp new file mode 100644 index 000000000..745370c87 --- /dev/null +++ b/sources/insettemplate.cpp @@ -0,0 +1,580 @@ +/* + Copyright 2006-2010 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 "insettemplate.h" +#include "qet.h" +#include "qetapp.h" + +/** + Constructor + @param parent parent QObject +*/ +InsetTemplate::InsetTemplate(QObject *parent) : + QObject(parent) +{ +} + +/** + Destructor +*/ +InsetTemplate::~InsetTemplate() { + loadLogos(QDomElement(), true); +} + +/** + @param filepath A file path to read the template from. + @return true if the reading succeeds, false otherwise. +*/ +bool InsetTemplate::loadFromXmlFile(const QString &filepath) { + // opens the file + QFile template_file(filepath); + if (!template_file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return(false); + } + + // parses its content as XML + bool xml_parsing = xml_description_.setContent(&template_file); + if (!xml_parsing) { + return(false); + } +#ifdef INSET_TEMPLATE_DEBUG + qDebug() << Q_FUNC_INFO << filepath << "opened"; +#endif + return(loadFromXmlElement(xml_description_.documentElement())); +} + +/** + @param xml_element An XML document to read the template from. + @return true if the reading succeeds, false otherwise. +*/ +bool InsetTemplate::loadFromXmlElement(const QDomElement &xml_element) { + // we expect the XML element to be an + if (xml_element.tagName() != "insettemplate") { + return(false); + } + + loadLogos(xml_element, true); + loadGrid(xml_element); + return(true); +} + +/** + Imports the logos from a given XML inset template. + @param xml_element An XML element representing an inset template. + @param reset true to delete all previously known logos before, false + otherwise. + @return true if the reading succeeds, false otherwise. +*/ +bool InsetTemplate::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()); + bitmap_logos_.clear(); + } + + // we look for //logos/logo elements + for (QDomNode n = xml_element.firstChild() ; !n.isNull() ; n = n.nextSibling()) { + if (n.isElement() && n.toElement().tagName() == "logos") { + for (QDomNode p = n.firstChild() ; !p.isNull() ; p = p.nextSibling()) { + if (p.isElement() && p.toElement().tagName() == "logo") { + loadLogo(p.toElement()); + } + } + } + } + + return(true); +} + +/** + Imports the logo from a given XML logo description. + @param xml_element An XML element representing a logo within an inset + template. + @return true if the reading succeeds, false otherwise. +*/ +bool InsetTemplate::loadLogo(const QDomElement &xml_element) { + // we require a name + if (!xml_element.hasAttribute("name")) { + return(false); + } + QString logo_name = xml_element.attribute("name"); + QString logo_type = xml_element.attribute("type", "png"); + QString logo_storage = xml_element.attribute("storage", "base64"); + + // Both QSvgRenderer and QPixmap read their data from a QByteArray, so + // 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); + } + QDomElement svg_element = svg_nodes.at(0).toElement(); + QTextStream xml_to_byte_array(&logo_data); + svg_element.save(xml_to_byte_array, 0); + } else if (logo_storage == "base64") { + logo_data = QByteArray::fromBase64(xml_element.text().toAscii()); + } 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();*/ + } + + return(true); +} + +/** + Imports the grid from a given XML inset template. + @param xml_element An XML element representing an inset template. + @return true if the reading succeeds, false otherwise. +*/ +bool InsetTemplate::loadGrid(const QDomElement &xml_element) { + // we parse the first available "grid" XML element + QDomElement grid_element; + for (QDomNode n = xml_element.firstChild() ; !n.isNull() ; n = n.nextSibling()) { + if (n.isElement() && n.toElement().tagName() == "grid") { + grid_element = n.toElement(); + break; + } + } + + if (!grid_element.hasAttribute("rows") || !grid_element.hasAttribute("cols")) { + return(false); + } + + parseRows(grid_element.attribute("rows")); + parseColumns(grid_element.attribute("cols")); + loadCells(grid_element); + return(true); +} + +/** + Parses the rows heights + @param rows_string A string describing the rows heights of the inset +*/ +void InsetTemplate::parseRows(const QString &rows_string) { + rows_heights_.clear(); + // parse the rows attribute: we expect a serie of absolute heights + QRegExp row_size_format("^([0-9]+)(?:px)?$", Qt::CaseInsensitive); + bool conv_ok; + + QStringList rows_descriptions = rows_string.split(QChar(';'), QString::SkipEmptyParts); + foreach (QString rows_description, rows_descriptions) { + if (row_size_format.exactMatch(rows_description)) { + int row_size = row_size_format.capturedTexts().at(1).toInt(&conv_ok); + if (conv_ok) rows_heights_ << row_size; + } + } +#ifdef INSET_TEMPLATE_DEBUG + qDebug() << Q_FUNC_INFO << "Rows heights:" << rows_heights_; +#endif +} + +/** + Parses the columns widths + @param cols_string A string describing the columns widths of the inset +*/ +void InsetTemplate::parseColumns(const QString &cols_string) { + columns_width_.clear(); + // parse the cols attribute: we expect a serie of absolute or relative widths + QRegExp abs_col_size_format("^([0-9]+)(?:px)?$", Qt::CaseInsensitive); + QRegExp rel_col_size_format("^([rt])([0-9]+)%$", Qt::CaseInsensitive); + bool conv_ok; + + QStringList cols_descriptions = cols_string.split(QChar(';'), QString::SkipEmptyParts); + 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_ << InsetColDimension(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::InsetColumnLength col_type = rel_col_size_format.capturedTexts().at(1) == "t" ? QET::RelativeToTotalLength : QET::RelativeToRemainingLength; + if (conv_ok) columns_width_ << InsetColDimension(col_size, col_type ); + } + } +#ifdef INSET_TEMPLATE_DEBUG + foreach (InsetColDimension icd, columns_width_) { + qDebug() << Q_FUNC_INFO << QString("%1 [%2]").arg(icd.value).arg(QET::insetColumnLengthToString(icd.type)); + } +#endif +} + +/** + 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 +*/ +bool InsetTemplate::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") { + InsetCell *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"); + } + if (cell_element.hasAttribute("value") && !cell_element.attribute("value").isEmpty()) { + loaded_cell -> value = cell_element.attribute("value"); + } + if (cell_element.hasAttribute("label") && !cell_element.attribute("label").isEmpty()) { + loaded_cell -> label = cell_element.attribute("label"); + } + if (cell_element.hasAttribute("displaylabel") && cell_element.attribute("displaylabel").compare("false", Qt::CaseInsensitive) == 0) { + loaded_cell -> display_label = false; + } + + // 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 (halignment == "bottom") loaded_cell -> alignment |= Qt::AlignBottom; + else if (halignment == "top") loaded_cell -> alignment |= Qt::AlignTop; + else loaded_cell -> alignment |= Qt::AlignVCenter; + } + } + } + + return(true); +} + +/** + @param xml_element XML element representing a cell, i.e. either an inset + logo or an inset field. + @param inset_cell_ptr Pointer to an InsetCell object pointer - if non-zero and if + this method returns true, will be filled with the created InsetCell + @return TRUE if the cell appears to be ok, FALSE otherwise +*/ +bool InsetTemplate::checkCell(const QDomElement &xml_element, InsetCell **inset_cell_ptr) { + int col_count = columns_width_.count(), row_count = rows_heights_.count(); + +#ifdef INSET_TEMPLATE_DEBUG + qDebug() << Q_FUNC_INFO << "begin" << row_count << col_count; +#endif + + int row_num, col_num, row_span, col_span; + bool has_row_span = false; + bool has_col_span = false; + row_num = col_num = -1; + row_span = col_span = 0; + + // parse the row and col attributes + if (!QET::attributeIsAnInteger(xml_element, "row", &row_num) || row_num < 0 || row_num >= row_count) { + return(false); + } + if (!QET::attributeIsAnInteger(xml_element, "col", &col_num) || col_num < 0 || col_num >= col_count) { + return(false); + } + + // check whether the target cell can be used or not +#ifdef INSET_TEMPLATE_DEBUG + qDebug() << Q_FUNC_INFO << "cell access" << col_num << row_num; +#endif + InsetCell *cell_ptr = &(cells_[col_num][row_num]); + if (!cell_ptr -> is_null || cell_ptr -> spanner_cell) { + return(false); + } + + // parse the rowspan and colspan attributes + if (QET::attributeIsAnInteger(xml_element, "rowspan", &row_span) && row_span > 0) { + if (row_num + row_span >= row_count) row_span = row_count - 1 - row_num; + has_row_span = true; + } + + if (QET::attributeIsAnInteger(xml_element, "colspan", &col_span) && col_span > 0) { + if (col_num + col_span >= col_count) col_span = col_count - 1 - col_num; + has_col_span = true; + } + + // 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 INSET_TEMPLATE_DEBUG + qDebug() << Q_FUNC_INFO << "span check" << i << j; +#endif + InsetCell *current_cell = &(cells_[i][j]); + if (!current_cell -> is_null || current_cell -> spanner_cell) { + return(false); + } + } + } + } + + // at this point, the cell is ok - we fill the adequate cells in the matrix +#ifdef INSET_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 (inset_cell_ptr) *inset_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 INSET_TEMPLATE_DEBUG + qDebug() << Q_FUNC_INFO << "span cells writing" << i << j; +#endif + InsetCell *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; + } + } + } + + return(true); +} + +/** + Initializes 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 InsetTemplate::initCells() { + if (columns_width_.count() < 1 || rows_heights_.count() < 1) return; + + cells_.resize(columns_width_.count()); + int row_count = rows_heights_.count(); + 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] = InsetCell(); + } + } + +#ifdef INSET_TEMPLATE_DEBUG + qDebug() << Q_FUNC_INFO << toString(); +#endif +} + +/** + @return A string representing the inset template + @see InsetCell::toString() +*/ +QString InsetTemplate::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 += "\n"; + } + return(str); +} + +/** + @param total_width The total width of the inset to render + @return the list of the columns widths for this rendering +*/ +QList InsetTemplate::columnsWidth(int total_width) const { + if (total_width < 0) return(QList()); + + // we first iter to determine the absolute and total-width-related widths + QVector final_widths(columns_width_.count()); + int abs_widths_sum = 0; + + for (int i = 0 ; i < columns_width_.count() ; ++ i) { + InsetColDimension icd = columns_width_.at(i); + if (icd.type == QET::Absolute) { + abs_widths_sum += icd.value; + final_widths[i] = icd.value; + } else if (icd.type == QET::RelativeToTotalLength) { + int abs_value = int(total_width * icd.value / 100); + abs_widths_sum += abs_value; + final_widths[i] = abs_value; + } + } + + // we can now deduce the remaining width + int remaining_width = total_width - abs_widths_sum; + + // we do a second iteration to build the final widths list + for (int i = 0 ; i < columns_width_.count() ; ++ i) { + InsetColDimension icd = columns_width_.at(i); + if (icd.type == QET::RelativeToRemainingLength) { + final_widths[i] = int(remaining_width * icd.value / 100); + } + } + return(final_widths.toList()); +} + +int InsetTemplate::height() const { + int height = 0; + foreach(int row_height, rows_heights_) { + height += row_height; + } + return(height); +} + +/** + Render the inset. + @param painter Painter to use to render the inset + @param diagram_context Diagram context to use to generate the inset strings + @param inset_width Width of the inset to render +*/ +void InsetTemplate::render(QPainter &painter, const DiagramContext &diagram_context, int inset_width) const { + QList widths = columnsWidth(inset_width); + int inset_height = height(); + + // prepare the QPainter + painter.setPen(Qt::black); + painter.setBrush(Qt::white); + painter.setFont(QETApp::diagramTextsFont()); + + // draw the inset border + painter.drawRect(QRect(0, 0, inset_width, inset_height)); + + // 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) 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_); + 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 { + painter.drawText(cell_rect, cells_[i][j].alignment, finalTextForCell(cells_[i][j], diagram_context)); + } + + // 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 InsetTemplate::finalTextForCell(const InsetCell &cell, const DiagramContext &diagram_context) const { + QString cell_text = cell.value; + + 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", "inset content - please let the blank space at the beginning")).arg(cell.label).arg(cell_text); + } else { + cell_text = QString(tr(" %1")).arg(cell_text); + } + return(cell_text); +} + +/** + @return the width between two borders + @param start start border number + @param end end border number +*/ +int InsetTemplate::lengthRange(int start, int end, const QList &lengths_list) const { + if (start > end || start >= lengths_list.count() || end > lengths_list.count()) { + qDebug() << Q_FUNC_INFO << "wont use" << start << "and" << end; + return(0); + } + + int length = 0; + for (int i = start ; i < end ; ++i) { + length += lengths_list[i]; + } + return(length); +} + + + +/** + Constructor +*/ +InsetCell::InsetCell() { + num_row = num_col = -1; + row_span = col_span = 0; + display_label = is_null = true; + spanner_cell = 0; +} + +/** + @return A string representing the inset cell +*/ +QString InsetCell::toString() const { + if (is_null) return("InsetCell{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("InsetCell{ [%1, %2] %3 }").arg(num_row).arg(num_col).arg(span_desc); + return(base_desc); +} diff --git a/sources/insettemplate.h b/sources/insettemplate.h new file mode 100644 index 000000000..15c018dfb --- /dev/null +++ b/sources/insettemplate.h @@ -0,0 +1,92 @@ +/* + Copyright 2006-2010 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 INSET_TEMPLATE_H +#define INSET_TEMPLATE_H +#include +#include +#include "diagramcontext.h" +#include "insetcell.h" +#include "qet.h" + +/** + This struct is a simple container associating a length with its type. + @see InsetColumnLength +*/ +struct InsetColDimension { + InsetColDimension(int v, QET::InsetColumnLength t = QET::Absolute) { + value = v; + type = t; + } + QET::InsetColumnLength type; + int value; +}; + +/** + This class represents an inset templ)ate for an electric diagram. + It can read from an XML document the layout of the table that graphically + represents the inset, and can produce a graphical rendering of it from a + diagram context (object embedding the informations of the diagram we want to + represent the inset. +*/ +class InsetTemplate : public QObject { + Q_OBJECT + + // constructeurs, destructeur + public: + InsetTemplate(QObject * = 0); + virtual ~InsetTemplate(); + private: + InsetTemplate(const InsetTemplate &); + + // methodes + public: + bool loadFromXmlFile(const QString &); + bool loadFromXmlElement(const QDomElement &); + void setContext(const DiagramContext &); + + QList columnsWidth(int) const; + int height() const; + + void render(QPainter &, const DiagramContext &, int) const; + QString toString() const; + + protected: + bool loadLogos(const QDomElement &, bool = false); + bool loadLogo(const QDomElement &); + bool loadGrid(const QDomElement &); + bool loadCells(const QDomElement &); + + private: + void parseRows(const QString &); + void parseColumns(const QString &); + bool checkCell(const QDomElement &, InsetCell ** = 0); + void flushCells(); + void initCells(); + int lengthRange(int, int, const QList &) const; + QString finalTextForCell(const InsetCell &, const DiagramContext &) const; + + // attributs + private: + QDomDocument xml_description_; + QHash vector_logos_; + QHash bitmap_logos_; + QList rows_heights_; + QList columns_width_; + QVector< QVector > cells_; +}; +#endif diff --git a/sources/insettemplaterenderer.cpp b/sources/insettemplaterenderer.cpp new file mode 100644 index 000000000..fdce778b2 --- /dev/null +++ b/sources/insettemplaterenderer.cpp @@ -0,0 +1,96 @@ +#include "insettemplaterenderer.h" +#include "insettemplate.h" + +/** + Constructor + @param parnet Parent QObject of this renderer +*/ +InsetTemplateRenderer::InsetTemplateRenderer(QObject *parent) : + QObject(parent), + inset_template_(0), + last_known_inset_width_(-1) +{ +} + +/** + Destructor +*/ +InsetTemplateRenderer::~InsetTemplateRenderer() { +} + +/** + @return the inset template used for the rendering +*/ +const InsetTemplate *InsetTemplateRenderer::insetTemplate() const { + return(inset_template_); +} + +/** + @param inset_template Inset template to render. +*/ +void InsetTemplateRenderer::setInsetTemplate(const InsetTemplate *inset_template) { + if (inset_template != inset_template_) { + inset_template_ = inset_template; + invalidateRenderedTemplate(); + } +} + +/** + @param context Diagram Context to use when rendering the inset +*/ +void InsetTemplateRenderer::setContext(const DiagramContext &context) { + context_ = context; + invalidateRenderedTemplate(); +} + +/** + @return the height of the rendered template, or -1 if no template has been + set for this renderer. + @see InsetTemplate::height() +*/ +int InsetTemplateRenderer::height() const { + if (!inset_template_) return(-1); + return(inset_template_ -> height()); +} + +/** + Render the inset. + @param provided_painter QPainter to use to render the inset. + @param inset_width The total width of the inset to render +*/ +void InsetTemplateRenderer::render(QPainter *provided_painter, int inset_width) { + if (!inset_template_) return; + + // Do we really need to calculate all this again? + if (inset_width != last_known_inset_width_ || rendered_template_.isNull()) { + renderToQPicture(inset_width); + } + + provided_painter -> save(); + rendered_template_.play(provided_painter); + provided_painter -> restore(); +} + +/** + Renders the inset to the internal QPicture + @param inset_width Width of the inset to render +*/ +void InsetTemplateRenderer::renderToQPicture(int inset_width) { + if (!inset_template_) return; + + // we render the template on our internal QPicture + QPainter painter(&rendered_template_); + + inset_template_ -> render(painter, context_, inset_width); + + // memorize the last known width + last_known_inset_width_ = inset_width; +} + +/** + Invalidates the previous rendering of the template by resetting the internal + QPicture. +*/ +void InsetTemplateRenderer::invalidateRenderedTemplate() { + rendered_template_ = QPicture(); +} diff --git a/sources/insettemplaterenderer.h b/sources/insettemplaterenderer.h new file mode 100644 index 000000000..9ac2e83ce --- /dev/null +++ b/sources/insettemplaterenderer.h @@ -0,0 +1,45 @@ +/* + Copyright 2006-2010 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 INSET_TEMPLATE_RENDERER_H +#define INSET_TEMPLATE_RENDERER_H +#include +#include "diagramcontext.h" +class InsetTemplate; +class InsetTemplateRenderer : public QObject { + Q_OBJECT + + public: + InsetTemplateRenderer(QObject * = 0); + virtual ~InsetTemplateRenderer(); + const InsetTemplate *insetTemplate() const; + void setInsetTemplate(const InsetTemplate *); + void setContext(const DiagramContext &context); + int height() const; + void render(QPainter *, int); + + private: + void renderToQPicture(int); + void invalidateRenderedTemplate(); + + private: + const InsetTemplate *inset_template_; + QPicture rendered_template_; + DiagramContext context_; + int last_known_inset_width_; +}; +#endif diff --git a/sources/qet.cpp b/sources/qet.cpp index 4c79a87a7..f49cc2837 100644 --- a/sources/qet.cpp +++ b/sources/qet.cpp @@ -515,3 +515,16 @@ bool QET::compareCanonicalFilePaths(const QString &first, const QString &second) return(first_canonical_path == second_canonical_path); } + +/** + @param icl an InsetColumnLength object + @see InsetColumnLength + @return a string describing the type of this InsetColumnLength object +*/ +QString QET::insetColumnLengthToString(const InsetColumnLength &icl) { + QString type_str; + if (icl== Absolute) type_str = "absolute"; + else if (icl == RelativeToTotalLength) type_str = "relative to total"; + else if (icl == RelativeToRemainingLength) type_str = "relative to remaining"; + return(type_str); +} diff --git a/sources/qet.h b/sources/qet.h index cc758315a..80401a7ea 100644 --- a/sources/qet.h +++ b/sources/qet.h @@ -97,6 +97,13 @@ namespace QET { ElementsArea ///< Exporte le contenu du schema sans le cadre et le cartouche }; + /// enum used to specify the type of a length + enum InsetColumnLength { + Absolute, ///< the length is absolute and should be applied as is + RelativeToTotalLength, ///< the length is just a fraction of the total available length + RelativeToRemainingLength ///< the length is just a fraction of the length that is still available when other types of lengths have been removed + }; + QET::Orientation nextOrientation(QET::Orientation); QET::Orientation previousOrientation(QET::Orientation); QET::Orientation orientationFromString(const QString &); @@ -125,5 +132,6 @@ namespace QET { QString pointerString(void *); qreal correctAngle(const qreal &); bool compareCanonicalFilePaths(const QString &, const QString &); + QString insetColumnLengthToString(const InsetColumnLength &); } #endif