diff --git a/sources/factory/qetgraphicstablefactory.h b/sources/factory/qetgraphicstablefactory.h index f3cc5ba4c..0a93b0b5b 100644 --- a/sources/factory/qetgraphicstablefactory.h +++ b/sources/factory/qetgraphicstablefactory.h @@ -32,8 +32,14 @@ class QetGraphicsTableFactory static void createAndAddNomenclature(Diagram *diagram); static void createAndAddSummary(Diagram *diagram); - private: + // Public so a caller that has already built and configured an + // AddTableDialog itself (never shown or exec'd -- the two + // methods above always exec() one, which the scripting API + // cannot use headlessly) can create a table from it directly. + // create() only reads settings already on the dialog; nothing + // about it depends on the dialog having been shown. static void create(Diagram *diagram, AddTableDialog *dialog); + private: static QetGraphicsTableItem *newTable( Diagram *diagram, AddTableDialog *dialog, diff --git a/sources/factory/ui/addtabledialog.cpp b/sources/factory/ui/addtabledialog.cpp index 7c5624383..b2de24aad 100644 --- a/sources/factory/ui/addtabledialog.cpp +++ b/sources/factory/ui/addtabledialog.cpp @@ -72,6 +72,15 @@ bool AddTableDialog::adjustTableToFolio() const return ui->m_adjust_table_size_cb->isChecked(); } +/** + @brief AddTableDialog::setAdjustTableToFolio + @param set +*/ +void AddTableDialog::setAdjustTableToFolio(bool set) +{ + ui->m_adjust_table_size_cb->setChecked(set); +} + /** @brief AddTableDialog::addNewTableToNewDiagram @return @@ -81,6 +90,15 @@ bool AddTableDialog::addNewTableToNewDiagram() const return ui->m_add_table_and_folio->isChecked(); } +/** + @brief AddTableDialog::setAddNewTableToNewDiagram + @param set +*/ +void AddTableDialog::setAddNewTableToNewDiagram(bool set) +{ + ui->m_add_table_and_folio->setChecked(set); +} + /** @brief AddTableDialog::tableName @return @@ -90,6 +108,18 @@ QString AddTableDialog::tableName() const return ui->m_table_name_le->text(); } +/** + @brief AddTableDialog::setTableName + Set the name field directly, so a caller that builds this dialog to + read from (never shows or execs it -- the scripting API's addTable()) + does not need a name typed by a user who was never there to type one. + @param name +*/ +void AddTableDialog::setTableName(const QString &name) +{ + ui->m_table_name_le->setText(name); +} + /** @brief AddTableDialog::headerMargins @return diff --git a/sources/factory/ui/addtabledialog.h b/sources/factory/ui/addtabledialog.h index 9ac188d00..f95a985b3 100644 --- a/sources/factory/ui/addtabledialog.h +++ b/sources/factory/ui/addtabledialog.h @@ -44,9 +44,12 @@ class AddTableDialog : public QDialog void setQueryWidget(QWidget *widget); bool adjustTableToFolio() const; + void setAdjustTableToFolio(bool set); bool addNewTableToNewDiagram() const; + void setAddNewTableToNewDiagram(bool set); QString tableName() const; + void setTableName(const QString &name); QMargins headerMargins() const; Qt::Alignment headerAlignment() const; diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index a0a71eba7..a216b3338 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -21,10 +21,15 @@ #include "../QPropertyUndoCommand/qpropertyundocommand.h" #include "../cli_export.h" #include "../diagram.h" +#include "../dataBase/ui/elementquerywidget.h" +#include "../dataBase/ui/summaryquerywidget.h" #include "../diagramcontent.h" #include "../diagramview.h" #include "../factory/elementfactory.h" +#include "../factory/qetgraphicstablefactory.h" +#include "../factory/ui/addtabledialog.h" #include "../qet.h" +#include "../qetgraphicsitem/ViewItem/qetgraphicstableitem.h" #include "../qetgraphicsitem/element.h" #include "../qetmessagebox.h" #include "../dataBase/projectdatabase.h" @@ -1016,9 +1021,9 @@ namespace { is for both kinds. */ template -QList sortedByPosition(const QSet &items) +QList sortedByPosition(const QList &items_in) { - QList list(items.cbegin(), items.cend()); + QList list = items_in; std::sort(list.begin(), list.end(), [](T *a, T *b) { const QPointF pa = a->sceneBoundingRect().topLeft(); const QPointF pb = b->sceneBoundingRect().topLeft(); @@ -1031,6 +1036,16 @@ QList sortedByPosition(const QSet &items) return list; } +template +QList sortedByPosition(const QSet &items) +{ + // Qt 6 makes QVector an alias of QList, so this one overload also + // serves m_tables (a QVector) without a + // separate one -- adding one was a redefinition error, not a second + // overload, on this Qt version. + return sortedByPosition(QList(items.cbegin(), items.cend())); +} + } // namespace QList QetScriptApi::sortedTexts(int folioIndex) const @@ -1843,6 +1858,179 @@ bool QetScriptApi::sortTerminalStrip(int stripIndex) return true; } +QList QetScriptApi::sortedTables(int folioIndex) const +{ + if (!m_project) return {}; + const QList diagrams = m_project->diagrams(); + if (folioIndex < 0 || folioIndex >= diagrams.count()) return {}; + DiagramContent content(diagrams.at(folioIndex), false); + return sortedByPosition(content.m_tables); +} + +QStringList QetScriptApi::tables(int folioIndex) const +{ + QStringList list; + const QList all = sortedTables(folioIndex); + for (int i = 0 ; i < all.count() ; ++i) + { + QetGraphicsTableItem *t = all.at(i); + list << QStringLiteral("%1: '%2' at (%3, %4), %5 row(s)") + .arg(i).arg(t->tableName()).arg(t->pos().x()).arg(t->pos().y()) + .arg(t->model() ? t->model()->rowCount() : 0); + } + return list; +} + +/** + @brief QetScriptApi::addTable + Place a BOM/nomenclature or summary (table of contents) table, through + QetGraphicsTableFactory::create() -- the same factory call the "add + table" menu action makes, minus the modal AddTableDialog it collects + its settings from first. That dialog is built here too, off-screen and + never shown or exec'd: create() reads the table's name and the query + widget's identifier/query string from it rather than taking them as + plain arguments, so the dialog exists only to be read from, and its two + checkboxes -- "adjust to folio" and "add a new folio if the table + overflows" -- are forced off despite defaulting to checked in the .ui + file, so one call creates exactly the one table asked for. A script + that wants either behaviour can resize the result or add a folio itself. + + kind is "nomenclature" (an ElementQueryWidget, over placed elements) or + "summary" (a SummaryQueryWidget, over folios); query is required, since + both widgets otherwise build their own from a set of checkboxes that + default to none checked, and "SELECT with no columns" is not a useful + table -- query() against element_nomenclature_view or + project_summary_view is the way to find one that is. + + Not undoable: newTable(), which create() calls, calls + Diagram::addItem() directly, with no undo command of its own, in the + stock action as much as here. Which of the (possibly several) tables + create() left in the diagram is the new one is found by set difference + against the folio's table listing taken just before the call, since + create()'s return type is void and newTable() itself is private. + @return the table's index in tables(), or -1 +*/ +int QetScriptApi::addTable(int folioIndex, const QString &kind, const QString &name, + const QString &query) +{ + if (!m_project) return -1; + const QString caller = QStringLiteral("addTable"); + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.%1: project is read-only").arg(caller)); + return -1; + } + const QList diagrams = m_project->diagrams(); + if (folioIndex < 0 || folioIndex >= diagrams.count()) { + log(QStringLiteral("qet.%1: no folio at index %2").arg(caller).arg(folioIndex)); + return -1; + } + if (query.isEmpty()) { + // Both widgets build their query from a set of checkboxes that + // default to none checked, so "no query" is not "the sensible + // default" here the way it might look -- it is SELECT with no + // columns. Measured: a table left to that default reports 0 rows + // against a folio that plainly has some. A real SELECT is required + // instead, over query() -- the same project database and the same + // two views this project already exposes, element_nomenclature_view + // for a nomenclature table and project_summary_view for a summary. + log(QStringLiteral("qet.%1: a query is required -- try qet.query() against " + "element_nomenclature_view or project_summary_view first " + "to find one that returns what is wanted").arg(caller)); + return -1; + } + + QWidget *content = nullptr; + if (kind == QLatin1String("nomenclature")) { + auto *w = new ElementQueryWidget(); + w->setQuery(query); + content = w; + } else if (kind == QLatin1String("summary")) { + auto *w = new SummaryQueryWidget(); + w->setQuery(query); + content = w; + } else { + log(QStringLiteral("qet.%1: unknown kind '%2'; expected nomenclature or summary").arg(caller, kind)); + return -1; + } + + AddTableDialog dialog(content); + dialog.setTableName(name); + // QetGraphicsTableFactory::newTable() is private -- only create() (its + // own class) may call it -- and create()'s own two checkboxes both + // default to checked in the .ui file: "adjust to folio" and "add a new + // folio if the table overflows". Forced off here rather than left at + // that default, since a script calling addTable() once should create + // exactly the one table it asked for, not possibly several across + // folios it never asked to add. + dialog.setAdjustTableToFolio(false); + dialog.setAddNewTableToNewDiagram(false); + + const QList before = sortedTables(folioIndex); + QetGraphicsTableFactory::create(diagrams.at(folioIndex), &dialog); + const QList after = sortedTables(folioIndex); + for (QetGraphicsTableItem *t : after) { + if (!before.contains(t)) return after.indexOf(t); + } + log(QStringLiteral("qet.%1: the table could not be created").arg(caller)); + return -1; +} + +bool QetScriptApi::deleteTable(int folioIndex, int tableIndex) +{ + if (!m_project) return false; + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.deleteTable: project is read-only")); + return false; + } + const QList diagrams = m_project->diagrams(); + if (folioIndex < 0 || folioIndex >= diagrams.count()) return false; + Diagram *diagram = diagrams.at(folioIndex); + + const QList all = sortedTables(folioIndex); + if (tableIndex < 0 || tableIndex >= all.count()) { + log(QStringLiteral("qet.deleteTable: folio %1 has %2 table(s), no index %3") + .arg(folioIndex).arg(all.count()).arg(tableIndex)); + return false; + } + DiagramContent to_remove; + to_remove.m_tables << all.at(tableIndex); + diagram->undoStack().push(new DeleteQGraphicsItemCommand(diagram, to_remove)); + return true; +} + +/** + @brief QetScriptApi::setTablePosition + Move a table on its folio through QPropertyUndoCommand, the same + mechanism setElementPosition() uses -- QetGraphicsTableFactory::newTable() + places every new table at a fixed (50, 50), so a script adding more than + one table must reposition all but the first itself or they stack exactly + on top of each other. +*/ +bool QetScriptApi::setTablePosition(int folioIndex, int tableIndex, double x, double y) +{ + if (!m_project) return false; + if (m_project->isReadOnly()) { + log(QStringLiteral("qet.setTablePosition: project is read-only")); + return false; + } + const QList all = sortedTables(folioIndex); + if (tableIndex < 0 || tableIndex >= all.count()) { + log(QStringLiteral("qet.setTablePosition: folio %1 has %2 table(s), no index %3") + .arg(folioIndex).arg(all.count()).arg(tableIndex)); + return false; + } + QetGraphicsTableItem *table = all.at(tableIndex); + + const QVariant old_value = table->pos(); + const QVariant new_value = QPointF(x, y); + if (old_value == new_value) return true; // already there; nothing to push + + auto *cmd = new QPropertyUndoCommand(table, "pos", old_value, new_value); + cmd->setText(QObject::tr("Déplacer %1").arg(table->tableName())); + m_project->undoStack()->push(cmd); + return true; +} + namespace { QHash autoNumTable(QETProject *project, const QString &kind, bool *ok) { diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index d582a72a6..2e694b59a 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -33,6 +33,7 @@ class IndependentTextItem; class QetShapeItem; class DiagramImageItem; class DynamicElementTextItem; +class QetGraphicsTableItem; /** @brief The QetScriptApi class @@ -187,6 +188,27 @@ class DynamicElementTextItem; button applies, not a rule reimplemented here. sortTerminalStrip() reorders the strip's physical positions into the canonical order the editor's own sort button computes. + - @b Tables: a BOM/nomenclature or a summary (table of contents) placed + on a folio, through QetGraphicsTableFactory::create() -- the same + factory call the "add table" menu action makes, minus the modal + AddTableDialog it collects its settings from first. That dialog is + still built here, off-screen and never shown or exec'd: addTable() + calls setTableName() and the query widget's setQuery() on it, the + same as a user filling in the form, and forces its two checkboxes + ("adjust table to folio" and "add a new folio if the table overflows" + it) off regardless of their .ui-file default of checked -- a script + calling addTable() once should create exactly the one table it asked + for, not possibly several spread across folios it never asked to add. + A script that wants either behaviour can resize the result itself or + add its own folio. + + Neither creating nor deleting a table is undoable: + QetGraphicsTableFactory::newTable(), which create() calls, calls + Diagram::addItem() directly, with no undo command of its own, in the + stock "add table" action as much as here -- a pre-existing gap in the + application, not something introduced by this API. Tables are + addressed by index in a position-sorted listing, like texts, shapes + and images. - @b Auto-numbering: define a named numbering context of kind "conductor", "element" or "folio", built from parts written "type[:value[:increase]]" -- types are the ones the auto-numbering @@ -426,6 +448,13 @@ class QetScriptApi : public QObject Q_INVOKABLE bool bridgeTerminals(int stripIndex, const QVariantList &realTerminalIndices); Q_INVOKABLE bool sortTerminalStrip(int stripIndex); + // -- a BOM/nomenclature or summary table placed on a folio -- + Q_INVOKABLE QStringList tables(int folioIndex) const; + Q_INVOKABLE int addTable(int folioIndex, const QString &kind, const QString &name, + const QString &query); + Q_INVOKABLE bool deleteTable(int folioIndex, int tableIndex); + Q_INVOKABLE bool setTablePosition(int folioIndex, int tableIndex, double x, double y); + // -- auto-numbering contexts (conductor, element, folio) -- Q_INVOKABLE QStringList autoNums(const QString &kind) const; Q_INVOKABLE bool addAutoNum(const QString &kind, const QString &name, const QStringList &parts); @@ -500,6 +529,7 @@ class QetScriptApi : public QObject const QString &caller); QList sortedTexts(int folioIndex) const; QList sortedShapes(int folioIndex) const; + QList sortedTables(int folioIndex) const; QList sortedImages(int folioIndex) const; DynamicElementTextItem *findElementText(int folioIndex, const QString &elementUuid, int textIndex, const QString &caller) const;