mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-06 19:54:13 +02:00
649f06b3b8
A submenu under View (Affichage > Afficher) with one tick per kind: symbol texts, wire texts, free texts, shapes, pictures, tables and cross-references. Unticking one hides it on every folio of every open project; the status bar says how many kinds are hidden. Session only, not saved in the project. Each item is tagged with its kind by its constructor (ShownKinds::tag), so an item created while its kind is hidden starts hidden whatever created it. A tag rather than type(): the cross-reference under a contact's label is a plain QGraphicsTextItem. Items that already hide themselves (wire text switched off, one text per potential, a cross-reference snapped elsewhere) now go through ShownKinds::setVisible(): the kind state can only veto a show, and apply() re-shows only what it hid itself (hidden_key), never what an item hid. Conductor::updateTextVisibility() replaces the same visibility rule pasted in diagrampropertiesdialog.cpp and qetscriptapi.cpp. Hidden items are not selectable (Qt), so Select All, copy and delete skip them. Print, PDF and image export render the scene and leave them out; the DXF export walks items itself and skips them through ShownKinds::isHidden(). CrossRefItem::linkedChanged() keeps following its slaves while hidden only by kind, so its label is right when shown. Known limitation: QGraphicsScene::itemsBoundingRect() still counts hidden items, so Zoom to fit and borderless image export keep their space. Verified: tst_shownkinds (12 cases, two mutation checks) and ctest 55/55; GUI hide/show of symbol texts, wire texts (incl. one text per potential), free texts, shapes and cross-references returns pixel-identical folios; with nothing hidden, --export-dxf of the 24 examples is byte-identical to master (133 files) and --export-pdf renders identically (133 pages). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015FPuYPS4T7QuEwjNu22rXD
1107 lines
30 KiB
C++
1107 lines
30 KiB
C++
/*
|
|
Copyright 2006-2026 The QElectroTech Team
|
|
This file is part of QElectroTech.
|
|
|
|
QElectroTech is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
QElectroTech is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "qetgraphicstableitem.h"
|
|
#include "../../shownkinds.h"
|
|
#include "../../qetproject.h"
|
|
#include "../../QPropertyUndoCommand/qpropertyundocommand.h"
|
|
#include "../../createdxf.h"
|
|
#include "../../diagram.h"
|
|
#include "../../elementprovider.h"
|
|
#include "../../qetmessagebox.h"
|
|
#include "../../qetxml.h"
|
|
#include "../../utils/qetutils.h"
|
|
#include "projectdbmodel.h"
|
|
#include "qetgraphicsheaderitem.h"
|
|
|
|
#include <QAbstractItemModel>
|
|
#include <QFontMetrics>
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsSceneMouseEvent>
|
|
#include <QPainter>
|
|
|
|
static int no_model_height = 20;
|
|
static int no_model_width = 40;
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::adjustTableToFolio
|
|
Adjust the table to fit at best the folio
|
|
@param table : table to adjust
|
|
@param margins : margins between table and folio.
|
|
*/
|
|
void QetGraphicsTableItem::adjustTableToFolio(
|
|
QetGraphicsTableItem *table,
|
|
QMargins margins)
|
|
{
|
|
if (!table->diagram()) {
|
|
return;
|
|
}
|
|
|
|
auto drawable_rect = table->diagram()->border_and_titleblock.insideBorderRect();
|
|
table->setPos(
|
|
drawable_rect.topLeft().x() + margins.left(),
|
|
drawable_rect.topLeft().y()
|
|
+ margins.top()
|
|
+ table->headerItem()->rect().height());
|
|
|
|
auto size_ = table->size();
|
|
size_.setWidth(int(drawable_rect.width() - (margins.left() + margins.right())));
|
|
//Size must be a multiple of 10, because the table adjusts itself by step of 10.
|
|
while (size_.width()%10) {
|
|
--size_.rwidth();
|
|
}
|
|
table->setSize(size_);
|
|
|
|
//Calculate the maximum row to display to fit the nomenclature into diagram
|
|
auto available_height = drawable_rect.height() - table->pos().y();
|
|
auto min_row_height = table->minimumRowHeight();
|
|
table->setDisplayNRow(int(floor(available_height/min_row_height))); //Convert a double to int, but max_row_to_display is already rounded an integer so we assume everything is ok
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::checkInsufficientRowsCount
|
|
Check if the number of rows of table + linked table is enough
|
|
to display all content of the model,
|
|
if not open a dialog to advise user what to do.
|
|
@param table
|
|
*/
|
|
void QetGraphicsTableItem::checkInsufficientRowsCount(
|
|
QetGraphicsTableItem *table)
|
|
{
|
|
if (!table->diagram() || !table->model()) {
|
|
return;
|
|
}
|
|
|
|
auto first_table = table;
|
|
while (first_table->previousTable())
|
|
first_table = first_table->previousTable();
|
|
|
|
if (first_table->displayNRow() <= 0) //displayed rows is unlimited
|
|
return;
|
|
|
|
int count_ = first_table->displayNRow();
|
|
bool several_table = false;
|
|
while (first_table->nextTable())
|
|
{
|
|
several_table = true;
|
|
first_table = first_table->nextTable();
|
|
if (first_table->displayNRow() <= 0) { //displayed rows is unlimited
|
|
return;
|
|
} else {
|
|
count_ += first_table->displayNRow();
|
|
first_table->displayNRowOffset();
|
|
}
|
|
}
|
|
|
|
if (count_ < first_table->model()->rowCount())
|
|
{
|
|
QWidget *parent = nullptr;
|
|
if (first_table->diagram() && first_table->diagram()->views().size())
|
|
parent = first_table->diagram()->views().first();
|
|
|
|
QString text;
|
|
if (several_table) {
|
|
text = tr("Les information à afficher sont supérieurs à la quantité maximal pouvant être affiché par les tableaux.\n"
|
|
"Veuillez ajouter un nouveau tableau ou regler les tableaux existant afin d'afficher l'integralité des informations.");
|
|
} else {
|
|
text = tr("Les information à afficher sont supérieurs à la quantité maximal pouvant être affiché par le tableau.\n"
|
|
"Veuillez ajouter un nouveau tableau ou regler le tableau existant afin d'afficher l'integralité des informations.");
|
|
}
|
|
QET::QetMessageBox::information(parent, tr("Limitation de tableau"), text);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::QetGraphicsTableItem
|
|
Default constructor
|
|
@param parent
|
|
*/
|
|
QetGraphicsTableItem::QetGraphicsTableItem(QGraphicsItem *parent) :
|
|
QetGraphicsItem(parent)
|
|
{
|
|
ShownKinds::tag(this, ShownKinds::Tables);
|
|
setFlag(QGraphicsItem::ItemIsMovable, true);
|
|
setFlag(QGraphicsItem::ItemIsSelectable, true);
|
|
setAcceptHoverEvents(true);
|
|
setUpHandler();
|
|
|
|
//A litle bounding rect before model is set,
|
|
//then user can already grab this item, even if model is not already set
|
|
m_bounding_rect.setRect(m_br_margin/-2, m_br_margin/-2, 50, 50);
|
|
|
|
connect(this, &QetGraphicsTableItem::xChanged,
|
|
this, &QetGraphicsTableItem::adjustHandlerPos);
|
|
connect(this, &QetGraphicsTableItem::yChanged,
|
|
this, &QetGraphicsTableItem::adjustHandlerPos);
|
|
|
|
m_header_item = new QetGraphicsHeaderItem(this);
|
|
connect(m_header_item, &QetGraphicsHeaderItem::heightResized,
|
|
this, [this]()
|
|
{
|
|
m_header_item->setPos(0, 0-m_header_item->rect().height());
|
|
});
|
|
//Init the size of table without a model
|
|
setModel();
|
|
}
|
|
|
|
QetGraphicsTableItem::~QetGraphicsTableItem()
|
|
{
|
|
if (m_previous_table) {
|
|
if (m_next_table) {
|
|
m_previous_table->setNextTable(m_next_table);
|
|
} else {
|
|
m_previous_table->setNextTable(nullptr);
|
|
}
|
|
} else if (m_next_table) {
|
|
m_next_table->setPreviousTable(nullptr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setModel
|
|
Set the model presented by this item.
|
|
Since QetGraphicsTableItem don't take ownership of model,
|
|
if item already have a model, it's your responsibility to delete it.
|
|
@param model
|
|
*/
|
|
void QetGraphicsTableItem::setModel(QAbstractItemModel *model)
|
|
{
|
|
if (m_model)
|
|
{
|
|
disconnect(m_model, &QAbstractItemModel::dataChanged,
|
|
this, &QetGraphicsTableItem::dataChanged);
|
|
disconnect(m_model, &QAbstractItemModel::modelReset,
|
|
this, &QetGraphicsTableItem::modelReseted);
|
|
}
|
|
m_model = model;
|
|
m_header_item->setModel(m_model);
|
|
|
|
setUpColumnAndRowMinimumSize();
|
|
adjustSize();
|
|
|
|
m_header_item->setPos(0, -m_header_item->rect().height());
|
|
if (m_model)
|
|
{
|
|
connect(m_model, &QAbstractItemModel::dataChanged,
|
|
this, &QetGraphicsTableItem::dataChanged);
|
|
connect(m_model, &QAbstractItemModel::modelReset,
|
|
this, &QetGraphicsTableItem::modelReseted);
|
|
}
|
|
|
|
if (m_next_table) {
|
|
m_next_table->setModel(m_model);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::model
|
|
@return The model that this item is presenting
|
|
*/
|
|
QAbstractItemModel *QetGraphicsTableItem::model() const
|
|
{
|
|
return m_model.data();
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::boundingRect
|
|
Reimplemented from QGraphicsObject
|
|
@return
|
|
*/
|
|
QRectF QetGraphicsTableItem::boundingRect() const
|
|
{
|
|
return m_bounding_rect;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::paint
|
|
Draw the table
|
|
@param painter
|
|
@param option
|
|
@param widget
|
|
*/
|
|
void QetGraphicsTableItem::paint(
|
|
QPainter *painter,
|
|
const QStyleOptionGraphicsItem *option,
|
|
QWidget *widget)
|
|
{
|
|
Q_UNUSED(option)
|
|
Q_UNUSED(widget)
|
|
|
|
painter->save();
|
|
|
|
QPen pen;
|
|
pen.setWidthF(0.7);
|
|
pen.setColor(Qt::black);
|
|
painter->setPen(pen);
|
|
painter->drawRect(0,0, m_header_item->rect().width(), m_current_size.height());
|
|
|
|
if (isSelected())
|
|
{
|
|
painter->save();
|
|
QColor color(Qt::darkBlue);
|
|
color.setAlpha(20);
|
|
painter->setBrush(QBrush (color));
|
|
painter->setPen(Qt::NoPen);
|
|
painter->drawRect(0,0, m_header_item->rect().width(), m_current_size.height());
|
|
painter->restore();
|
|
}
|
|
|
|
if(!m_model)
|
|
{
|
|
painter->restore();
|
|
return;
|
|
}
|
|
painter->setFont(m_model->data(m_model->index(0,0), Qt::FontRole).value<QFont>());
|
|
|
|
//Draw vertical lines
|
|
auto offset= 0;
|
|
for(auto i=0 ; i<m_model->columnCount() ; ++i)
|
|
{
|
|
QPointF p1(offset+m_header_item->sectionSize(i), 0);
|
|
QPointF p2(offset+m_header_item->sectionSize(i), m_current_size.height());
|
|
painter->drawLine(p1, p2);
|
|
offset += m_header_item->sectionSize(i);
|
|
}
|
|
|
|
//calculate the number of rows to display.
|
|
auto row_count = m_model->rowCount();
|
|
|
|
if (m_previous_table) //Remove the number of row already displayed by previous tables
|
|
row_count -= m_previous_table->displayNRowOffset();
|
|
|
|
if (m_number_of_row_to_display > 0) //User override the number of row to display
|
|
row_count = std::min(row_count, m_number_of_row_to_display);
|
|
|
|
//Draw horizontal lines
|
|
auto cell_height = static_cast<double>(m_current_size.height())/static_cast<double>(row_count);
|
|
for(auto i= 1 ; i-1<row_count ; ++i)
|
|
{
|
|
QPointF p1(m_header_item->rect().left(), cell_height*i);
|
|
QPointF p2(m_header_item->rect().right(), cell_height*i);
|
|
painter->drawLine(p1, p2);
|
|
}
|
|
|
|
//Write text of each cell
|
|
for (auto i=0 ; i<row_count ; ++i)
|
|
{
|
|
auto margin_ = QETUtils::marginsFromString(m_model->index(0,0).data(Qt::UserRole+1).toString());
|
|
QPointF top_left(margin_.left(), i==0? margin_.top() : cell_height*i + margin_.top());
|
|
|
|
for(auto j= 0 ; j<m_model->columnCount() ; ++j)
|
|
{
|
|
//In first iteration the top left X is margin left,
|
|
// in all other iteration the top left X is stored in m_column_size
|
|
if (j>0) {
|
|
top_left.setX(top_left.x() + m_header_item->sectionSize(j-1));
|
|
}
|
|
QSize size(m_header_item->sectionSize(j) - margin_.left() - margin_.right(),
|
|
static_cast<int>(cell_height) - margin_.top() - margin_.bottom());
|
|
auto index_row = m_previous_table ? i + m_previous_table->displayNRowOffset() : i;
|
|
painter->drawText(
|
|
QRectF(top_left, size),
|
|
m_model->data(
|
|
m_model->index(0,0),
|
|
Qt::TextAlignmentRole
|
|
).toInt()
|
|
| Qt::AlignVCenter,
|
|
m_model->index(index_row, j)
|
|
.data().toString());
|
|
}
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setSize
|
|
Set the current size of the table to size
|
|
@param size
|
|
*/
|
|
void QetGraphicsTableItem::setSize(const QSize &size)
|
|
{
|
|
auto new_size = size;
|
|
if (new_size.width() < minimumSize().width()) {
|
|
new_size.setWidth(minimumSize().width());
|
|
}
|
|
if (new_size.height() < minimumSize().height()) {
|
|
new_size.setHeight(minimumSize().height());
|
|
}
|
|
while (new_size.width()%10) {
|
|
new_size.rwidth()++;
|
|
}
|
|
|
|
prepareGeometryChange();
|
|
m_current_size = new_size;
|
|
adjustColumnsWidth();
|
|
setUpBoundingRect();
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::size
|
|
@return The current size of the table
|
|
*/
|
|
QSize QetGraphicsTableItem::size() const
|
|
{
|
|
QSize size_(m_header_item->rect().width(), m_current_size.height());
|
|
return size_;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::minimumSize
|
|
@return the minimum size the table can be
|
|
The returned size take care of the table's minimum width,
|
|
but also the header item's minimum width
|
|
*/
|
|
QSize QetGraphicsTableItem::minimumSize() const
|
|
{
|
|
if (!m_model) {
|
|
return QSize(no_model_width, no_model_height);
|
|
}
|
|
|
|
auto row_count = m_model->rowCount();
|
|
if (m_previous_table) {
|
|
row_count -= m_previous_table->displayNRowOffset();
|
|
}
|
|
|
|
if (m_number_of_row_to_display > 0)
|
|
row_count = std::min(row_count, m_number_of_row_to_display);
|
|
|
|
|
|
//m_minimum_column_width already take in count the minimum size of header
|
|
QSize size_(
|
|
std::accumulate(
|
|
m_minimum_column_width.begin(),
|
|
m_minimum_column_width.end(),
|
|
0),
|
|
m_minimum_row_height*row_count);
|
|
//make sure that the width is a multiple of 10
|
|
while (size_.width()%10) {
|
|
size_.rwidth()++;
|
|
}
|
|
return size_;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setDisplayNRow
|
|
Limit the number of row to display
|
|
@param number : set to 0 or less to disabled the limit of row to display
|
|
*/
|
|
void QetGraphicsTableItem::setDisplayNRow(const int &number)
|
|
{
|
|
m_number_of_row_to_display = number;
|
|
setToMinimumHeight();
|
|
if (m_next_table)
|
|
m_next_table->previousTableDisplayRowChanged();
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::displayNRow
|
|
@return the number of row displayed.
|
|
A value of 0 or less mean there is no limit
|
|
*/
|
|
int QetGraphicsTableItem::displayNRow() const
|
|
{
|
|
return m_number_of_row_to_display;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setPreviousTable
|
|
Set the previous table to table.
|
|
If this table already have a previous table,
|
|
the previous table will be replaced.
|
|
Set new table to nullptr to remove an existing previous table.
|
|
The table uses the model of the new previous table.
|
|
Since the table does not take ownership of the model,
|
|
it is your responsibility to manage the old model.
|
|
Linked tables (table with next and/or previous table)
|
|
share the same model, a table always take the model of the previous table.
|
|
When remove a previous table (set to nullptr) from a table,
|
|
the model is also removed, you need to set a new model
|
|
@param table
|
|
*/
|
|
void QetGraphicsTableItem::setPreviousTable(QetGraphicsTableItem *table)
|
|
{
|
|
if (m_previous_table == table ||
|
|
this == table) {
|
|
return;
|
|
}
|
|
|
|
auto old_previous_table = m_previous_table;
|
|
m_previous_table = table;
|
|
if (m_previous_table) //set previous table and get her model
|
|
{
|
|
m_previous_table->setNextTable(this);
|
|
setModel(m_previous_table->m_model);
|
|
}
|
|
else //Copie the model of old previous table
|
|
{
|
|
setModel(new ProjectDBModel(*static_cast<ProjectDBModel *>(old_previous_table->model())));
|
|
}
|
|
|
|
if (old_previous_table &&
|
|
old_previous_table->nextTable() == this) {
|
|
old_previous_table->setNextTable(nullptr);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setNextTable
|
|
Set the next table to table
|
|
nullptr will remove an existing next table.
|
|
@param table
|
|
*/
|
|
void QetGraphicsTableItem::setNextTable(QetGraphicsTableItem *table)
|
|
{
|
|
if (m_next_table == table ||
|
|
this == table) {
|
|
return;
|
|
}
|
|
|
|
auto old_next_table = m_next_table;
|
|
|
|
m_next_table = table;
|
|
if (m_next_table) {
|
|
m_next_table->setPreviousTable(this);
|
|
}
|
|
|
|
if (old_next_table &&
|
|
old_next_table->previousTable() == this) {
|
|
old_next_table->setPreviousTable(nullptr);
|
|
}
|
|
|
|
}
|
|
|
|
void QetGraphicsTableItem::setTableName(const QString &name)
|
|
{
|
|
m_name = name;
|
|
}
|
|
|
|
QString QetGraphicsTableItem::tableName() const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::displayNRowOffset
|
|
@return the offset (aka the last displayed row) of displayed row.
|
|
If this item have a previous table, the previous offset is added.
|
|
*/
|
|
int QetGraphicsTableItem::displayNRowOffset() const
|
|
{
|
|
auto offset_ = m_number_of_row_to_display;
|
|
if(m_previous_table)
|
|
offset_ += m_previous_table->displayNRowOffset();
|
|
|
|
return offset_;
|
|
}
|
|
|
|
/**
|
|
* @brief QetGraphicsTableItem::displayedRowCount
|
|
* @return the number of row displayed by this table
|
|
*/
|
|
int QetGraphicsTableItem::displayedRowCount() const
|
|
{
|
|
if (!m_model) {
|
|
return 0;
|
|
}
|
|
|
|
//calculate the number of rows to display.
|
|
auto row_count = m_model->rowCount();
|
|
|
|
if (m_previous_table) //Remove the number of row already displayed by previous tables
|
|
row_count -= m_previous_table->displayNRowOffset();
|
|
|
|
if (m_number_of_row_to_display > 0) //User override the number of row to display
|
|
row_count = std::min(row_count, m_number_of_row_to_display);
|
|
|
|
return row_count;
|
|
}
|
|
|
|
QPointer<QetGraphicsTableItem> QetGraphicsTableItem::previousTable() const
|
|
{
|
|
return m_previous_table;
|
|
}
|
|
|
|
QPointer<QetGraphicsTableItem> QetGraphicsTableItem::nextTable() const
|
|
{
|
|
return m_next_table;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setToMinimumHeight
|
|
Set the height to the the minimum.
|
|
The width stay unchanged.
|
|
*/
|
|
void QetGraphicsTableItem::setToMinimumHeight()
|
|
{
|
|
auto size_ = size();
|
|
size_.setHeight(1);
|
|
setSize(size_);
|
|
}
|
|
|
|
void QetGraphicsTableItem::initLink()
|
|
{
|
|
if (!m_pending_previous_table_uuid.isNull())
|
|
{
|
|
ElementProvider provider_(this->diagram()->project());
|
|
if (auto previous_table = provider_.tableFromUuid(m_pending_previous_table_uuid)) {
|
|
setPreviousTable(previous_table);
|
|
}
|
|
m_pending_previous_table_uuid = QUuid(); //Set to null in case initLink is called again
|
|
}
|
|
setSize(m_pending_size);
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::minimumRowHeight
|
|
@return the minimum height of a row
|
|
*/
|
|
int QetGraphicsTableItem::minimumRowHeight() const
|
|
{
|
|
return m_minimum_row_height;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::toXml
|
|
Save the table to xml
|
|
@param dom_document : parent document
|
|
@return the dom_element that describe the table
|
|
*/
|
|
QDomElement QetGraphicsTableItem::toXml(QDomDocument &dom_document) const
|
|
{
|
|
auto dom_table = dom_document.createElement(xmlTagName());
|
|
dom_table.setAttribute("x", QString::number(pos().x()));
|
|
dom_table.setAttribute("y", QString::number(pos().y()));
|
|
dom_table.setAttribute("width", QString::number(m_current_size.width()));
|
|
dom_table.setAttribute("height", QString::number(m_current_size.height()));
|
|
dom_table.setAttribute("uuid", m_uuid.toString());
|
|
dom_table.setAttribute("name", m_name);
|
|
dom_table.setAttribute("display_n_row", QString::number(m_number_of_row_to_display));
|
|
|
|
//Add the header xml
|
|
dom_table.appendChild(m_header_item->toXml(dom_document));
|
|
|
|
//Add previous table, the model is save by the previous table
|
|
if (m_previous_table)
|
|
{
|
|
auto dom_previous_table = dom_document.createElement("previous_table");
|
|
dom_previous_table.setAttribute("uuid", m_previous_table->m_uuid.toString());
|
|
dom_table.appendChild(dom_previous_table);
|
|
}
|
|
else if (m_model) //There is not a previous table, we need to save the model
|
|
{
|
|
//Add model
|
|
auto dom_model = dom_document.createElement("model");
|
|
auto project_db_model = static_cast<ProjectDBModel *>(m_model.data());
|
|
dom_model.appendChild(project_db_model->toXml(dom_document));
|
|
dom_table.appendChild(dom_model);
|
|
|
|
}
|
|
|
|
return dom_table;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::fromXml
|
|
Restore the table from xml.
|
|
Make this item is already in a diagram to
|
|
@param dom_element
|
|
*/
|
|
void QetGraphicsTableItem::fromXml(const QDomElement &dom_element)
|
|
{
|
|
if (dom_element.tagName() != xmlTagName()) {
|
|
return;
|
|
}
|
|
|
|
this->setPos(
|
|
dom_element.attribute(
|
|
"x",
|
|
QString::number(10)).toDouble(),
|
|
dom_element.attribute(
|
|
"y",
|
|
QString::number(10)).toDouble());
|
|
//Size is not set now because will change during the whole process of opening a project from the xml
|
|
m_pending_size = QSize(
|
|
dom_element.attribute(
|
|
"width",
|
|
QString::number(no_model_width)).toInt(),
|
|
dom_element.attribute(
|
|
"height",
|
|
QString::number(no_model_height)).toInt());
|
|
|
|
m_uuid = QUuid(
|
|
dom_element.attribute(
|
|
"uuid",
|
|
QUuid::createUuid().toString()));
|
|
m_name = dom_element.attribute("name");
|
|
m_number_of_row_to_display = dom_element.attribute(
|
|
"display_n_row",
|
|
QString::number(0)).toInt();
|
|
|
|
auto vector_ = QETXML::directChild(dom_element, "previous_table");
|
|
if (vector_.size()) { //Table have a previous table
|
|
m_pending_previous_table_uuid = QUuid(
|
|
vector_.first().attribute("uuid"));
|
|
}
|
|
else if (this->diagram()) //The table haven't got a previous table, so there should be a model save to xml
|
|
{
|
|
//Get table
|
|
auto model_ = new ProjectDBModel(
|
|
this->diagram()->project(),
|
|
this->diagram()->project());
|
|
model_->fromXml(
|
|
dom_element
|
|
.firstChildElement("model")
|
|
.firstChildElement(
|
|
ProjectDBModel::xmlTagName()));
|
|
this->setModel(model_);
|
|
}
|
|
|
|
//Restore the header from xml
|
|
m_header_item->fromXml(
|
|
dom_element.firstChildElement(
|
|
QetGraphicsHeaderItem::xmlTagName()));
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::toDXF
|
|
Draw this table to the dxf document
|
|
@param filepath file path of the the dxf document
|
|
@return true if draw success
|
|
*/
|
|
bool QetGraphicsTableItem::toDXF(const QString &filepath)
|
|
{
|
|
// Header
|
|
m_header_item->toDXF(filepath);
|
|
|
|
//QRectF rect = boundingRect();
|
|
QRectF rect(0,0, m_header_item->rect().width(), m_current_size.height());
|
|
QPolygonF poly(rect);
|
|
Createdxf::drawPolygon(filepath,mapToScene(poly),0);
|
|
|
|
//Draw vertical lines
|
|
auto offset= 0;
|
|
for(auto i=0 ; i<m_model->columnCount() ; ++i)
|
|
{
|
|
QPointF p1(offset+m_header_item->sectionSize(i), 0);
|
|
QPointF p2(
|
|
offset+m_header_item->sectionSize(i),
|
|
m_current_size.height());
|
|
Createdxf::drawLine(
|
|
filepath,
|
|
QLineF(mapToScene(p1),mapToScene(p2)),
|
|
0);
|
|
offset += m_header_item->sectionSize(i);
|
|
}
|
|
//Calculate the number of rows to display.
|
|
auto row_count = m_model->rowCount();
|
|
|
|
if (m_previous_table) //Remove the number of row already displayed by previous tables
|
|
row_count -= m_previous_table->displayNRowOffset();
|
|
|
|
if (m_number_of_row_to_display > 0) //User override the number of row to display
|
|
row_count = std::min(row_count, m_number_of_row_to_display);
|
|
|
|
//Draw horizontal lines
|
|
auto cell_height = static_cast<double>(m_current_size.height())/static_cast<double>(row_count);
|
|
for(auto i= 1 ; i-1<row_count ; ++i)
|
|
{
|
|
QPointF p1(m_header_item->rect().left(), cell_height*i);
|
|
QPointF p2(m_header_item->rect().right(), cell_height*i);
|
|
Createdxf::drawLine(
|
|
filepath,
|
|
QLineF(mapToScene(p1),mapToScene(p2))
|
|
,0);
|
|
}
|
|
|
|
//Write text of each cell
|
|
for (auto i=0 ; i<row_count ; ++i)
|
|
{
|
|
auto margin_ = QETUtils::marginsFromString(m_model->index(0,0).data(Qt::UserRole+1).toString());
|
|
QPointF top_left(margin_.left(), i==0? margin_.top() : cell_height*i + margin_.top());
|
|
|
|
for(auto j= 0 ; j<m_model->columnCount() ; ++j)
|
|
{
|
|
//In first iteration the top left X is margin left,
|
|
// in all other iteration the top left X is stored in m_column_size
|
|
if (j>0) {
|
|
top_left.setX(top_left.x() + m_header_item->sectionSize(j-1));
|
|
}
|
|
QSize size(m_header_item->sectionSize(j) - margin_.left() - margin_.right(),
|
|
static_cast<int>(cell_height) - margin_.top() - margin_.bottom());
|
|
auto index_row = m_previous_table ? i + m_previous_table->displayNRowOffset() : i;
|
|
|
|
QPointF qm = mapToScene(top_left);
|
|
qreal h = size.height();// * Createdxf::yScale;
|
|
qreal x = qm.x() * Createdxf::xScale;
|
|
qreal y = Createdxf::sheetHeight - ((qm.y() + h/2) * Createdxf::yScale);
|
|
qreal h1 = h * 0.5 * Createdxf::yScale;
|
|
|
|
int valign = 2;
|
|
|
|
Createdxf::drawTextAligned(
|
|
filepath,
|
|
m_model->index(index_row, j).data().toString(),
|
|
x,y,h1,0,0,0,valign,x,0,0);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::hoverEnterEvent
|
|
Reimplemented from QetGraphicsItem
|
|
@param event
|
|
*/
|
|
void QetGraphicsTableItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
|
|
{
|
|
if (m_model)
|
|
{
|
|
adjustHandlerPos();
|
|
this->scene()->addItem(&m_handler_item);
|
|
m_handler_item.setSize(QETUtils::graphicsHandlerSize(this));
|
|
m_handler_item.installSceneEventFilter(this);
|
|
}
|
|
QGraphicsObject::hoverEnterEvent(event);
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::hoverLeaveEvent
|
|
Reimplemented from QetGraphicsItem
|
|
@param event
|
|
*/
|
|
void QetGraphicsTableItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
|
|
{
|
|
if (m_model &&
|
|
!m_handler_item.contains(mapToItem(&m_handler_item, event->pos())))
|
|
{
|
|
m_handler_item.scene()->removeItem(&m_handler_item);
|
|
}
|
|
QGraphicsObject::hoverLeaveEvent(event);
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::sceneEventFilter
|
|
Reimplemented from QetGraphicsItem
|
|
@param watched
|
|
@param event
|
|
@return
|
|
*/
|
|
bool QetGraphicsTableItem::sceneEventFilter(QGraphicsItem *watched, QEvent *event)
|
|
{
|
|
if (watched == &m_handler_item)
|
|
{
|
|
if(event->type() == QEvent::GraphicsSceneMousePress) //Click
|
|
{
|
|
handlerMousePressEvent(static_cast<QGraphicsSceneMouseEvent *>(event));
|
|
return true;
|
|
}
|
|
else if(event->type() == QEvent::GraphicsSceneMouseMove) //Move
|
|
{
|
|
handlerMouseMoveEvent(static_cast<QGraphicsSceneMouseEvent *>(event));
|
|
return true;
|
|
}
|
|
else if (event->type() == QEvent::GraphicsSceneMouseRelease) //Release
|
|
{
|
|
handlerMouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent *>(event));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::itemChange
|
|
@param change
|
|
@param value
|
|
@return
|
|
*/
|
|
QVariant QetGraphicsTableItem::itemChange(
|
|
QGraphicsItem::GraphicsItemChange change, const QVariant &value)
|
|
{
|
|
//item was removed from scene, we remove the handler
|
|
if (change == ItemSceneHasChanged) {
|
|
if (!scene() && m_handler_item.scene()) {
|
|
m_handler_item.scene()->removeItem(&m_handler_item);
|
|
}
|
|
}
|
|
|
|
return QetGraphicsItem::itemChange(change, value);
|
|
}
|
|
|
|
/**
|
|
* @brief QetGraphicsTableItem::modelReseted
|
|
*/
|
|
void QetGraphicsTableItem::modelReseted()
|
|
{
|
|
dataChanged(m_model->index(0,0), m_model->index(0,0), QVector<int>());
|
|
setToMinimumHeight();
|
|
|
|
if (!previousTable()) //this is the head table
|
|
{
|
|
checkInsufficientRowsCount(this);
|
|
removeUselessNextTable();
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setUpColumnAndRowMinimumSize
|
|
Calculate the minimum row height and the minimum column width for each columns
|
|
this function doesn't change the geometry of the table.
|
|
*/
|
|
void QetGraphicsTableItem::setUpColumnAndRowMinimumSize()
|
|
{
|
|
if (!m_model)
|
|
{
|
|
m_minimum_row_height = no_model_height;
|
|
m_minimum_column_width = m_header_item->minimumSectionWidth();
|
|
return;
|
|
}
|
|
|
|
QFontMetrics metrics(m_model->data(model()->index(0,0), Qt::FontRole).value<QFont>());
|
|
auto margin_ = QETUtils::marginsFromString(model()->index(0,0).data(Qt::UserRole+1).toString());
|
|
//Set the height of row;
|
|
m_minimum_row_height = metrics.boundingRect("HEIGHT TEST").height() + margin_.top() + margin_.bottom();
|
|
|
|
m_minimum_column_width = m_header_item->minimumSectionWidth();
|
|
|
|
//Get the maximum width of each columns
|
|
for (auto row= 0 ; row<m_model->rowCount() ; ++row)
|
|
{
|
|
for(auto col= 0 ; col<m_model->columnCount() ; ++col)
|
|
{
|
|
auto index = m_model->index(row, col);
|
|
auto width = metrics.boundingRect(index.data().toString()).width();
|
|
m_minimum_column_width.replace(
|
|
col,
|
|
std::max(
|
|
m_minimum_column_width.at(col),
|
|
width + margin_.left() + margin_.right()));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setUpBoundingRect
|
|
*/
|
|
void QetGraphicsTableItem::setUpBoundingRect()
|
|
{
|
|
QSize header_size = m_header_item->rect().size();
|
|
QRect rect(
|
|
0,
|
|
-header_size.height(),
|
|
header_size.width(),
|
|
m_current_size.height() + header_size.height());
|
|
m_bounding_rect = rect.adjusted(
|
|
-m_br_margin,
|
|
-m_br_margin,
|
|
m_br_margin,
|
|
m_br_margin);
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::adjustHandlerPos
|
|
Adjust the pos of the handler item
|
|
*/
|
|
void QetGraphicsTableItem::adjustHandlerPos()
|
|
{
|
|
m_handler_item.setPos(mapToScene(QRect(QPoint(0,0), size()).bottomRight()));
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::setUpHandler
|
|
*/
|
|
void QetGraphicsTableItem::setUpHandler()
|
|
{
|
|
m_handler_item.setColor(Qt::blue);
|
|
m_handler_item.setZValue(this->zValue() + 1);
|
|
}
|
|
|
|
void QetGraphicsTableItem::handlerMousePressEvent(
|
|
QGraphicsSceneMouseEvent *event)
|
|
{
|
|
Q_UNUSED(event)
|
|
diagram()->clearSelection();
|
|
this->setSelected(true);
|
|
m_old_size = size();
|
|
}
|
|
|
|
void QetGraphicsTableItem::handlerMouseMoveEvent(
|
|
QGraphicsSceneMouseEvent *event)
|
|
{
|
|
auto new_handler_pos = Diagram::snapToGrid(event->scenePos());
|
|
QSize size_ = QRectF(QPointF(0,0), mapFromScene(new_handler_pos)).size().toSize();
|
|
|
|
QPoint new_pos(
|
|
std::max(minimumSize().width(), size_.width()),
|
|
std::max(minimumSize().height(), size_.height()));
|
|
m_handler_item.setPos(mapToScene(new_pos));
|
|
|
|
QSize new_size(new_pos.x(), new_pos.y());
|
|
if (new_size != size()) {
|
|
setSize(new_size);
|
|
}
|
|
}
|
|
|
|
void QetGraphicsTableItem::handlerMouseReleaseEvent(
|
|
QGraphicsSceneMouseEvent *event)
|
|
{
|
|
Q_UNUSED(event)
|
|
if (diagram())
|
|
{
|
|
auto undo = new QPropertyUndoCommand(this, "size", m_old_size, size());
|
|
undo->setAnimated();
|
|
undo->setText(tr("Modifier la géometrie d'un tableau"));
|
|
diagram()->undoStack().push(undo);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::adjustColumnsWidth
|
|
Adjust the size of each column according to the current table width
|
|
by setting the sectionSize of the header item
|
|
*/
|
|
void QetGraphicsTableItem::adjustColumnsWidth()
|
|
{
|
|
if (!m_model)
|
|
{
|
|
auto h_ = m_header_item->minimumSectionWidth();
|
|
for (auto i=0 ; i<h_.size() ; ++i) {
|
|
m_header_item->resizeSection(i, h_.at(i));
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto a = m_current_size.width() - minimumSize().width();
|
|
auto b = a/std::max(1,m_model->columnCount()); //avoid divide by 0
|
|
|
|
int sum_=0;
|
|
for(auto i= 0 ; i<m_model->columnCount() ; ++i)
|
|
{
|
|
auto at_a = std::min((int)m_minimum_column_width.size()-1, i); //In case of the I is higher than m_minimum_column_width or
|
|
auto at_b = std::min((int)m_header_item->minimumSectionWidth().size()-1, i); //m_header_item->minimumSectionWidth().size()
|
|
m_header_item->resizeSection(
|
|
i,
|
|
std::max(
|
|
m_minimum_column_width.at(at_a),
|
|
m_header_item->minimumSectionWidth().at(at_b))+b);
|
|
sum_+= m_header_item->sectionSize(i);
|
|
}
|
|
|
|
|
|
//The sum of the header sections width can be less than width of
|
|
// @m_current_size we adjust it in order to have the same width
|
|
if (m_model->columnCount() > 0 &&
|
|
sum_ < m_current_size.width())
|
|
{
|
|
//add the quotient of the division to each columns
|
|
auto result = (m_current_size.width()-sum_)/m_model->columnCount();
|
|
for(auto i= 0 ; i<m_model->columnCount() ; ++i) {
|
|
m_header_item->resizeSection(
|
|
i,
|
|
m_header_item->sectionSize(i) + result);
|
|
}
|
|
|
|
//add the rest of the division to the last column
|
|
auto last_section = m_model->columnCount()-1;
|
|
m_header_item->resizeSection(
|
|
last_section,
|
|
m_header_item->sectionSize(last_section)
|
|
+ ((m_current_size.width()-sum_)%m_model->columnCount()));
|
|
}
|
|
|
|
update();
|
|
}
|
|
|
|
void QetGraphicsTableItem::dataChanged(
|
|
const QModelIndex &topLeft,
|
|
const QModelIndex &bottomRight,
|
|
const QVector<int> &roles)
|
|
{
|
|
Q_UNUSED(topLeft)
|
|
Q_UNUSED(bottomRight)
|
|
Q_UNUSED(roles)
|
|
|
|
setUpColumnAndRowMinimumSize();
|
|
adjustSize();
|
|
update();
|
|
}
|
|
|
|
/**
|
|
@brief QetGraphicsTableItem::adjustSize
|
|
If needed, this function resize the current height and width of table and/or the size of columns.
|
|
according to there minimum
|
|
*/
|
|
void QetGraphicsTableItem::adjustSize()
|
|
{
|
|
//If there is no model, set the size to minimum
|
|
if (!m_model) {
|
|
setSize(minimumSize());
|
|
return;
|
|
}
|
|
|
|
if (m_current_size.height() < minimumSize().height())
|
|
{
|
|
prepareGeometryChange();
|
|
m_current_size.setHeight(minimumSize().height());
|
|
setUpBoundingRect();
|
|
update();
|
|
}
|
|
|
|
if (m_current_size.width() < minimumSize().width()) //The current width is shorter than the the minimum width, adjust it
|
|
{
|
|
prepareGeometryChange();
|
|
m_current_size.setWidth(minimumSize().width());
|
|
adjustColumnsWidth();
|
|
setUpBoundingRect();
|
|
}
|
|
else { //The current width is ok, but we need to adjust all columns width
|
|
adjustColumnsWidth();
|
|
}
|
|
}
|
|
|
|
void QetGraphicsTableItem::previousTableDisplayRowChanged()
|
|
{
|
|
setToMinimumHeight();
|
|
if (m_next_table) {
|
|
m_next_table->previousTableDisplayRowChanged();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief QetGraphicsTableItem::removeUselessNextTable
|
|
* Remove next table if useless, an useless table with 0 row displayed.
|
|
* If \p recursive is true check and remove for all sub next table.
|
|
* @param recursive
|
|
*/
|
|
void QetGraphicsTableItem::removeUselessNextTable(bool recursive)
|
|
{
|
|
if (m_next_table)
|
|
{
|
|
if (recursive) {
|
|
m_next_table->removeUselessNextTable();
|
|
}
|
|
if (m_next_table->displayedRowCount() <= 0)
|
|
{
|
|
m_next_table.data()->deleteLater();
|
|
m_next_table.clear();
|
|
}
|
|
}
|
|
}
|