Files
qelectrotech-source-mirror/sources/dataBase/projectdatabase.h
T
ispyisail 5e6677ebec Keep the project database current when folios, symbols and wires change
The project database is kept up to date edit by edit, and a script's
qet.query() and the folio tables read it without a full rebuild. Three
edits left it out of date until the next rebuild:

- adding or removing a folio: the folios after it kept their old
  position and folio number, and a removed folio's diagram_info row
  stayed;
- moving a symbol: its folio cell (element.pos) stayed where the symbol
  was added;
- deleting a wire: its two ends stayed in the terminal table, which
  lists the terminals a wire ends on.

Positions and folio numbers are now written again after a folio is
added, removed or moved (one function for the three), the removed
folio's diagram_info row goes, a moved symbol's cell is written when
the database is next read (as drawing items are), and a deleted wire's
ends go unless another wire ends there.

Found by comparing the tables after random scripted edits with those
a fresh open of the saved project gives. Left alone: labels and wire
numbers made from a folio formula, which the drawing itself keeps until
the project is reopened.

Tests (tst_databasefromdocument): folioAddedAndRemoved,
movedElementCell, deletedWireTerminals; each fails without its part of
the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf
2026-10-10 19:07:03 +13:00

236 lines
8.6 KiB
C++

/*
Copyright 2006-2026 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/>.
*/
#ifndef PROJECTDATABASE_H
#define PROJECTDATABASE_H
#include <QObject>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QPointer>
#include <QFileDialog>
#include <QHash>
#include <QSet>
#include <QUuid>
class Element;
class QETProject;
class Diagram;
class Conductor;
class Terminal;
class QGraphicsItem;
class QDomDocument;
class BorderTitleBlock;
class DiagramContext;
class QDate;
/**
@brief The projectDataBase class
This class wraps a sqlite data base where you can find several things
about the content of a project.
*
@note this class is still in development.
*/
class projectDataBase : public QObject
{
Q_OBJECT
public:
projectDataBase(QETProject *project, QObject *parent = nullptr);
virtual ~projectDataBase() override;
void updateDB();
/**
updateDB() for a project just read from @p document: the
diagram, element, terminal and conductor tables are filled
from the document when it carries everything they need (see
populateFromDocument()), from the built folios otherwise.
*/
void updateDB(const QDomDocument &document);
/**
Suppress the full rebuild performed by updateDB().
While blocked, updateDB() returns immediately instead of
repopulating every table. Meant for bulk operations -- notably
loading a project, where each table model re-queries the
database as it is built and would otherwise trigger one
complete rebuild of it. The caller unblocks and calls
updateDB() once when done; @see QETProject::readProjectXml().
*/
void setUpdateBlocked(bool blocked);
QETProject *project() const;
QSqlQuery newQuery(const QString &query = QString(), QString *error = nullptr);
static bool isReadOnlySelect(const QString &query, QString *error = nullptr);
/**
The most rows any caller reads out of one query result.
A SELECT is not bounded by how much data the project holds:
SQLite produces rows lazily, so a query that never stops
producing them makes the loop that reads them never stop
either. A recursive CTE does exactly that in one line, and
a <graphics_table>'s <query> is stored in the .qet and run
on load -- so the text can arrive from a file rather than
from the person at the keyboard, and opening that file is
the whole attack.
100000 is far above any real result: the largest table in
the shipped examples is 396 rows. It is a backstop, not a
page size -- a caller that hits it has almost certainly
been handed something it should not run to completion, and
says so rather than truncating quietly.
*/
static constexpr int MaxResultRows = 100000;
QSqlDatabase database() const {return m_data_base;}
int excludedConductorCount() const;
void addElement (Element *element);
void removeElement (Element *element);
void elementInfoChanged (Element *element);
void elementInfoChanged (QList<Element *> elements);
void addDiagram (Diagram *diagram);
void removeDiagram (Diagram *diagram);
void diagramInfoChanged (Diagram *diagram);
void diagramOrderChanged();
void addConductor (Conductor *conductor);
void removeConductor (Conductor *conductor);
void updateConductor (Conductor *conductor);
//Shapes, independent texts and images: the folio's drawing
//furniture. Anything else passed here is ignored.
void addDrawingItem (QGraphicsItem *item);
void removeDrawingItem (QGraphicsItem *item);
void itemGroupChanged (QGraphicsItem *item);
private slots:
//Refresh the sender()'s row after Conductor::setProperties().
void conductorPropertiesChanged();
//Queue the sender()'s drawing-item row for rewriting.
void drawingItemChanged();
void drawingItemDestroyed(QObject *object);
//Queue the sender()'s link rows for rewriting.
void linksChanged();
//Queue the sender() element's folio cell for rewriting.
void elementMoved();
public:
signals:
void dataBaseUpdated();
private:
bool createDataBase();
void createElementNomenclatureView();
void createSummaryView();
void createWiringListView();
void createDrawingItemView();
void populateDiagramTable();
void updateFolioPositions();
void flushElementPositions();
void populateElementTable();
void populateElementInfoTable();
void populateDiagramInfoTable();
void populateConductorTable();
void populateLinkTable();
void flushLinks();
void populateDrawingItemTables();
bool populateFromDocument(const QDomDocument &document, QString *why = nullptr);
bool writeDrawingItem(QObject *object);
void flushDrawingItems();
void forgetDrawingItem(QObject *object);
void bindConductorValues(QSqlQuery &query, Conductor *conductor, Diagram *diagram);
void watchConductor(Conductor *conductor);
void insertTerminal(Terminal *terminal);
void insertTerminal(const QString &uuid, const QString &element_uuid,
const QString &name, const QVariant &index);
void prepareQuery();
static QHash<QString, QString> elementInfoToString(
Element *elmt);
void bindDiagramInfoValues(QSqlQuery &query, Diagram *diagram);
static void bindDiagramInfoValues(QSqlQuery &query, const QUuid &diagram_uuid,
const BorderTitleBlock &border);
static void bindDiagramInfoValues(QSqlQuery &query, const QUuid &diagram_uuid,
const DiagramContext &infos, const QDate &date);
static void bindElementValues(QSqlQuery &query, Element *element, Diagram *diagram);
static void bindElementInfoValues(QSqlQuery &query, Element *element);
static void bindElementInfoValues(QSqlQuery &query, const QString &element_uuid,
const DiagramContext &informations,
const QString &label);
private:
QPointer<QETProject> m_project;
bool m_update_blocked = false;
//Starts true : the database is empty until the first rebuild.
//Set by every method of this class that writes rows, cleared by
//updateDB(). Callers reach the database from outside only through
//newQuery(), and every such call site reads.
bool m_content_changed = true;
QSqlDatabase m_data_base;
QSqlQuery m_insert_elements_query,
m_insert_element_info_query,
m_remove_element_query,
m_remove_element_info_query,
m_update_element_query,
m_insert_diagram_query,
m_remove_diagram_query,
m_insert_diagram_info_query,
m_update_diagram_info_query,
m_diagram_order_changed,
m_diagram_info_order_changed,
m_insert_terminal_query,
m_insert_conductor_query,
m_update_conductor_query,
m_remove_conductor_query,
m_cascade_remove_element_info_query,
m_cascade_remove_terminal_query,
m_cascade_remove_conductor_query,
m_cascade_remove_element_query,
m_insert_shape_query,
m_insert_independent_text_query,
m_insert_image_query;
//Which uuid's row each drawing item last wrote, and which item
//wrote each row. A pasted copy is added to the folio still
//carrying its source's uuid and renewed only afterwards, so two
//live items can briefly share one: the row belongs to whichever
//wrote it, and the other waits in m_dirty_drawing_items until its
//uuid is its own. @see writeDrawingItem().
QHash<QObject *, QUuid> m_drawing_item_row;
QHash<QUuid, QObject *> m_drawing_row_owner;
QSet<QObject *> m_dirty_drawing_items;
//Elements whose links changed since their link rows were last
//written: a link is made first and its contact group set after,
//so the rows are written when next read, as drawing items are.
QList<QPointer<Element>> m_dirty_link_elements;
//Elements moved since their folio cell was last written: a move
//sends xChanged and yChanged for every step of a drag, so the
//cell is written when next read, as drawing items are.
QList<QPointer<Element>> m_moved_elements;
#ifdef QET_EXPORT_PROJECT_DB
public:
static void exportDb(projectDataBase *db,
QWidget *parent = nullptr,
const QString &caption = QString(),
const QString &dir = QString());
#endif
};
#endif // PROJECTDATABASE_H