/*
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 .
*/
#ifndef PROJECTDATABASE_H
#define PROJECTDATABASE_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../diagramcontext.h"
#include "../titleblockproperties.h"
#include "../conductorproperties.h"
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);
/**
Why updateDB(document) will fill the tables from the folios
rather than from @p document, for the reasons the document
alone can tell; empty when it knows of none. Read before the
folios are: a project whose tables will come from the folios
builds every folio as it opens, even with QET_LAZY_FOLIOS.
*/
static QString documentRefusal(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 's 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;
//The information of every symbol placed in the project, by
//symbol uuid: the store each symbol's information is written
//through. A symbol not placed
//(a preview, the symbol editor) keeps its own.
DiagramContext elementInformation(const QUuid &element) const;
bool hasElementInformation(const QUuid &element) const;
//How many placed symbols hold @p element: more than one in a
//file whose symbols were copied by an old version (F100), or
//for a moment while a copy's uuid is renewed
int placedElementCount(const QUuid &element) const;
bool storeElementInformation(const QUuid &element, const DiagramContext &information);
void elementInformationChanged(Element *element, const DiagramContext &information);
void forgetElementInformation(const QUuid &element);
void elementUuidChanged(Element *element, const QUuid &old_uuid);
//Placed symbols whose information differs from the store's,
//each as "uuid: why"; empty when they all agree
QStringList elementInformationMismatches() const;
//The title block properties of every folio of the project, by
//folio uuid: the store each folio's title block is written
//through. A folio not in a
//project keeps its own.
TitleBlockProperties folioTitleBlock(const QUuid &folio) const;
const TitleBlockProperties *storedFolioTitleBlock(const QUuid &folio) const;
Diagram *placedFolio(const QUuid &folio) const;
void storeFolioTitleBlock(const QUuid &folio, const TitleBlockProperties &properties);
void placeFolio(Diagram *folio);
void folioUuidChanged(Diagram *folio, const QUuid &old_uuid);
QStringList folioTitleBlockMismatches() const;
//The properties of every wire placed in the project, by wire
//uuid: the store each wire's properties are written through
//A wire not placed keeps its own.
ConductorProperties conductorProperties(const QUuid &conductor) const;
bool hasConductorProperties(const QUuid &conductor) const;
int placedConductorCount(const QUuid &conductor) const;
void conductorPropertiesStored(Conductor *conductor, const ConductorProperties &properties);
void conductorUuidChanged(Conductor *conductor, const QUuid &old_uuid);
QStringList conductorPropertiesMismatches() const;
//Fill the three stores from a project's document before its
//folios are built, then, once
//they are, say how many of those values building agreed with.
void prefillFromDocument(const QDomDocument &document);
void endPrefill(bool keep_unplaced = false);
//A folio of a project opened with QET_LAZY_FOLIOS is being built:
//its rows came from the document already, so none are written
//and the database is not marked changed
void setBuildingFolio(bool building);
bool isBuildingFolio() const {return m_building_folios > 0;}
bool rowsFromDocument() const {return m_rows_from_document;}
QString schemaStamp() const;
/// A drawn table of a folio and the table it continues
struct FolioTable { QUuid table; QUuid folio; QUuid previous; };
QByteArray indexFile(const QList> &folio_headers,
const QList &tables = QList());
void discardFill();
/// The tables are right as they are (filled from an index)
void markCurrent() {m_content_changed = false;}
bool fillFromIndex(const QByteArray &index, QList> *folio_headers,
QString *why = nullptr,
std::optional> *folio_tables = nullptr);
QString prefillReport() const;
QSet foliosWithUnjoinedWires() const;
QSet folioUuidsOfElements(const QSet &elements) const;
QHash elementTypesOnFolios(const QSet &folios) const;
void addElement (Element *element);
void removeElement (Element *element);
void elementInfoChanged (Element *element);
void elementInfoChanged (QList 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 populateElementInfoTableFromFolios();
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 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 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,
m_store_remove_query,
m_store_insert_query,
m_remove_links_query,
m_conductor_properties_insert_query,
m_conductor_property_remove_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 m_drawing_item_row;
QHash m_drawing_row_owner;
QSet 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> 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> m_moved_elements;
QHash m_element_information;
//The placed symbols holding each uuid: a paste or a folio copy
//briefly gives a copy its original's uuid, so one uuid can have
//two symbols until the copy's is renewed.
QMultiHash> m_placed_elements;
QHash m_folio_titleblocks;
QHash> m_placed_folios;
QHash m_conductor_properties;
//The rows of conductor_properties, to write only what changes
QHash> m_conductor_attributes;
QSet m_dirty_conductor_properties;
int m_building_folios = 0;
QSet m_folios_with_unjoined_wires;
//The folios whose rows a rebuild keeps (keepUnbuiltFolioRows())
QSet m_kept_folios;
//The tables' rows were filled from the document: those of a
//folio not built yet are right (QET_LAZY_FOLIOS)
bool m_rows_from_document = false;
void keepUnbuiltFolioRows();
void restoreKeptRows(const QString &table, Diagram *folio, const QString &where);
void dropKeptRows();
bool m_changed_before_folio = false, m_blocked_before_folio = false;
QHash m_prefilled_elements;
QHash m_prefilled_folios;
QHash m_prefilled_conductors;
struct PrefillTally {
int agree = 0, differ = 0, unplaced = 0;
QMap fields;
};
PrefillTally m_prefill_tally[3];
void flushConductorProperties();
QMultiHash> m_placed_conductors;
bool storeConductorProperties(const QUuid &conductor, const ConductorProperties &properties);
void placeConductor(Conductor *conductor);
void unplaceConductor(Conductor *conductor, const QUuid &uuid);
Conductor *otherConductorHolder(const QUuid &uuid, const Conductor *conductor) const;
void writeConductorRow(Conductor *conductor);
void removeConductorRow(const QUuid &uuid);
void unplaceFolio(Diagram *folio, const QUuid &uuid);
void placeElement(Element *element);
Element *otherHolder(const QUuid &uuid, const Element *element) const;
void writeElementRows(Element *element);
void queueLinks(Element *element);
void removeElementRows(const QUuid &uuid);
void unplaceElement(Element *element, const QUuid &uuid);
#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