diff --git a/sources/ui/borderpropertieswidget.cpp b/sources/ui/borderpropertieswidget.cpp index f59ddc9e6..7c29e1182 100644 --- a/sources/ui/borderpropertieswidget.cpp +++ b/sources/ui/borderpropertieswidget.cpp @@ -18,8 +18,85 @@ #include "borderpropertieswidget.h" #include "../diagram.h" +#include "../qetapp.h" +#include "../titleblocktemplate.h" #include "ui_borderpropertieswidget.h" +#include +#include + +namespace { + /// ISO 216 A sizes in mm, landscape + struct PaperSize { const char *name; int width_mm; int height_mm; }; + const PaperSize paper_sizes[] = { + {"A0", 1189, 841}, + {"A1", 841, 594}, + {"A2", 594, 420}, + {"A3", 420, 297}, + {"A4", 297, 210}, + {"A5", 210, 148} + }; + + /// The PDF export draws one scene unit as one pixel at 96 dpi + int mmToPx(int mm) { return qRound(mm * 96.0 / 25.4); } + + /// The PDF export rounds a page within 3 pt of a paper size to that + /// size, and 4 px left over already rounds to 3.0 pt and misses + const int exact_fit = 3; + + /// Smallest column width or row height a paper size may give + const int min_cell = 10; + + /** + @brief fitCells + Split @p available pixels into cells close to @p current pixels. + First choice: cells within 15 %, then 30 %, of the current size + that leave at most exact_fit pixels, the closest to the current + size. Failing that, cells within 15 % that leave the fewest pixels. + @return the number of cells and their size + */ + QPair fitCells(int available, int current, int min_size, int max_count) + { + current = qMax(current, min_size); + auto size_for = [&](int count, double spread) { + const int size = available / count; + return (size < min_size || qAbs(size - current) > current * spread) + ? 0 : size; + }; + + for (double spread : {0.15, 0.30}) + { + QPair best(0, 0); + for (int count = 1 ; count <= max_count ; ++count) + { + const int size = size_for(count, spread); + if (size && available - count * size <= exact_fit && + (!best.first || qAbs(size - current) < qAbs(best.second - current))) + best = qMakePair(count, size); + } + if (best.first) return best; + } + + QPair best(0, 0); + std::tuple best_score(available, available); + for (int count = 1 ; count <= max_count ; ++count) + { + const int size = size_for(count, 0.15); + const std::tuple score(available - count * size, + qAbs(size - current)); + if (size && score < best_score) { + best_score = score; + best = qMakePair(count, size); + } + } + if (best.first) return best; + + //No size near the current one when the cell count is capped + const int count = qBound(1, qRound(double(available) / current), max_count); + return qMakePair(count, available / count); + } +} + /** @brief BorderPropertiesWidget::BorderPropertiesWidget default constructor @@ -31,6 +108,30 @@ BorderPropertiesWidget::BorderPropertiesWidget(const BorderProperties &bp, QWidg ui(new Ui::BorderPropertiesWidget) { ui->setupUi(this); + + ui->m_paper_cb->addItem(tr("Personnalisé")); + for (const PaperSize &paper : paper_sizes) + ui->m_paper_cb->addItem(QString::fromLatin1(paper.name)); + ui->m_orientation_cb->addItem(tr("Paysage")); + ui->m_orientation_cb->addItem(tr("Portrait")); + + if (TitleBlockTemplate *tbt = QETApp::defaultTitleBlockTemplate()) + m_titleblock_height = tbt->height(); + + //activated() is only emitted for a choice the user makes + auto paper_picked = [this]() { + m_paper_picked = ui->m_paper_cb->currentIndex() > 0; + applyPaperSize(); + }; + connect(ui->m_paper_cb, qOverload(&QComboBox::activated), + this, paper_picked); + connect(ui->m_orientation_cb, qOverload(&QComboBox::activated), + this, paper_picked); + for (QSpinBox *sb : {ui->m_colums_count_sp, ui->m_columns_width_sp, + ui->m_rows_count_sp, ui->m_rows_height_sp}) + connect(sb, qOverload(&QSpinBox::valueChanged), + this, &BorderPropertiesWidget::showMatchingPaperSize); + setProperties(bp); } @@ -57,6 +158,7 @@ void BorderPropertiesWidget::setProperties(const BorderProperties &bp) ui -> m_rows_count_sp ->setValue (m_properties.rows_count); ui -> m_rows_height_sp ->setValue (m_properties.rows_height); ui -> m_display_rows_cb ->setChecked (m_properties.display_rows); + showMatchingPaperSize(); } /** @@ -83,3 +185,125 @@ void BorderPropertiesWidget::setReadOnly(const bool &ro) { ui->border_gb->setDisabled(ro); } + +/** + @brief BorderPropertiesWidget::setTitleBlockSize + Tell the widget the title block the folio uses: its height counts in + the size of the page. If the user picked a paper size in this widget, + the columns and rows are fitted again so the folio keeps that size; + otherwise they are left alone. + @param height : height of the title block template + @param edge : edge of the folio the title block sits on +*/ +void BorderPropertiesWidget::setTitleBlockSize(int height, Qt::Edge edge) +{ + m_titleblock_height = height; + m_titleblock_edge = edge; + if (m_paper_picked) + applyPaperSize(); + else + showMatchingPaperSize(); +} + +/** + @brief BorderPropertiesWidget::applyPaperSize + Set the columns and rows so the folio, title block included, has the + size of the chosen paper. The column width and row height stay close + to the current ones. +*/ +void BorderPropertiesWidget::applyPaperSize() +{ + const int index = ui->m_paper_cb->currentIndex() - 1; + ui->m_orientation_cb->setEnabled(index >= 0); + if (index < 0) return; + + int columns, column_width, rows, row_height; + if (!fitPaperSize(index, ui->m_orientation_cb->currentIndex() == 0, + columns, column_width, rows, row_height)) + { + //The title block leaves no room on this paper + showMatchingPaperSize(); + return; + } + + m_updating = true; + ui->m_colums_count_sp->setValue(columns); + ui->m_columns_width_sp->setValue(column_width); + ui->m_rows_count_sp->setValue(rows); + ui->m_rows_height_sp->setValue(row_height); + m_updating = false; +} + +/** + @brief BorderPropertiesWidget::fitPaperSize + Compute the columns and rows that give the folio, title block + included, the size of a paper, with cells close to the current ones. + @param index : index of the paper in paper_sizes + @param landscape : true for landscape, false for portrait + @return false if the paper is too small for the title block +*/ +bool BorderPropertiesWidget::fitPaperSize(int index, bool landscape, + int &columns, int &column_width, + int &rows, int &row_height) const +{ + const PaperSize &paper = paper_sizes[index]; + const int page_width = mmToPx(landscape ? paper.width_mm : paper.height_mm); + const int page_height = mmToPx(landscape ? paper.height_mm : paper.width_mm); + + //The page is one pixel larger than the border, for its line + int width = page_width - 1 - qRound(m_properties.rows_header_width); + int height = page_height - 1 - qRound(m_properties.columns_header_height); + if (m_titleblock_edge == Qt::BottomEdge) + height -= m_titleblock_height; + else + width -= m_titleblock_height; + if (width < min_cell || height < min_cell) return false; + + const QPair c = fitCells(width, + ui->m_columns_width_sp->value(), + min_cell, + ui->m_colums_count_sp->maximum()); + const QPair r = fitCells(height, + ui->m_rows_height_sp->value(), + min_cell, + ui->m_rows_count_sp->maximum()); + columns = c.first; + column_width = c.second; + rows = r.first; + row_height = r.second; + return true; +} + +/** + @brief BorderPropertiesWidget::showMatchingPaperSize + Show the paper size the folio has, or "Custom" if it has none: the + folio has a paper size when choosing it would change nothing. +*/ +void BorderPropertiesWidget::showMatchingPaperSize() +{ + if (m_updating) return; + m_paper_picked = false; + + int found = 0; + int orientation = ui->m_orientation_cb->currentIndex(); + for (int i = 0 ; i < int(std::size(paper_sizes)) && !found ; ++i) + { + for (int o = 0 ; o < 2 ; ++o) + { + int columns, column_width, rows, row_height; + if (fitPaperSize(i, o == 0, columns, column_width, rows, row_height) && + columns == ui->m_colums_count_sp->value() && + column_width == ui->m_columns_width_sp->value() && + rows == ui->m_rows_count_sp->value() && + row_height == ui->m_rows_height_sp->value()) + { + found = i + 1; + orientation = o; + break; + } + } + } + ui->m_paper_cb->setCurrentIndex(found); + ui->m_orientation_cb->setCurrentIndex(orientation); + ui->m_orientation_cb->setEnabled(found > 0); +} diff --git a/sources/ui/borderpropertieswidget.h b/sources/ui/borderpropertieswidget.h index 01669c761..1042e23c4 100644 --- a/sources/ui/borderpropertieswidget.h +++ b/sources/ui/borderpropertieswidget.h @@ -41,12 +41,26 @@ class BorderPropertiesWidget : public QWidget void setProperties(const BorderProperties &bp); const BorderProperties &properties(); void setReadOnly (const bool &ro); - - private slots: + void setTitleBlockSize(int height, Qt::Edge edge); private: + void applyPaperSize(); + void showMatchingPaperSize(); + bool fitPaperSize(int index, bool landscape, + int &columns, int &column_width, + int &rows, int &row_height) const; + Ui::BorderPropertiesWidget *ui; BorderProperties m_properties; + /// height of the title block the folio is drawn with, and + /// the edge it sits on: it counts in the size of the page + int m_titleblock_height = 0; + Qt::Edge m_titleblock_edge = Qt::BottomEdge; + /// true while the widget itself changes the spin boxes + bool m_updating = false; + /// true once the user picked a paper size, until the columns or + /// rows are edited by hand + bool m_paper_picked = false; }; #endif // BORDERPROPERTIESWIDGET_H diff --git a/sources/ui/borderpropertieswidget.ui b/sources/ui/borderpropertieswidget.ui index 7fcee9bfd..63021ab12 100644 --- a/sources/ui/borderpropertieswidget.ui +++ b/sources/ui/borderpropertieswidget.ui @@ -80,7 +80,24 @@ + + + + Format : + + + + + + Règle les colonnes et les lignes pour que le folio, cartouche compris, ait la taille de ce format de papier, à 96 points par pouce comme l'export PDF. + + + + + + + Qt::Vertical @@ -105,6 +122,8 @@ m_rows_height_sp m_display_columns_cb m_display_rows_cb + m_paper_cb + m_orientation_cb diff --git a/sources/ui/configpage/configpages.cpp b/sources/ui/configpage/configpages.cpp index b9ac8562c..fb3ffc687 100644 --- a/sources/ui/configpage/configpages.cpp +++ b/sources/ui/configpage/configpages.cpp @@ -139,6 +139,10 @@ NewDiagramPage::NewDiagramPage(QETProject *project, } connect(ipw, &TitleBlockPropertiesWidget::openAutoNumFolioEditor, this, &NewDiagramPage::changeToAutoFolioTab); + bpw -> setTitleBlockSize(ipw -> currentTitleBlockHeight(), + ipw -> currentTitleBlockEdge()); + connect(ipw, &TitleBlockPropertiesWidget::titleBlockSizeChanged, + bpw, &BorderPropertiesWidget::setTitleBlockSize); // main tab widget QTabWidget *tab_widget = new QTabWidget(this); diff --git a/sources/ui/diagrampropertiesdialog.cpp b/sources/ui/diagrampropertiesdialog.cpp index cc3046b9c..15386bbfe 100644 --- a/sources/ui/diagrampropertiesdialog.cpp +++ b/sources/ui/diagrampropertiesdialog.cpp @@ -65,6 +65,10 @@ DiagramPropertiesDialog::DiagramPropertiesDialog(Diagram *diagram, QWidget *pare titleblock_infos -> setReadOnly(diagram_is_read_only); connect(titleblock_infos, &TitleBlockPropertiesWidget::openAutoNumFolioEditor, this, &DiagramPropertiesDialog::editAutoFolioNum); + border_infos -> setTitleBlockSize(titleblock_infos -> currentTitleBlockHeight(), + titleblock_infos -> currentTitleBlockEdge()); + connect(titleblock_infos, &TitleBlockPropertiesWidget::titleBlockSizeChanged, + border_infos, &BorderPropertiesWidget::setTitleBlockSize); //titleblock_infos->setMinimumSize(590,480); //Minimum Size needed for correct display //Conductor widget diff --git a/sources/ui/titleblockpropertieswidget.cpp b/sources/ui/titleblockpropertieswidget.cpp index 88bdd4842..7b83c9155 100644 --- a/sources/ui/titleblockpropertieswidget.cpp +++ b/sources/ui/titleblockpropertieswidget.cpp @@ -336,6 +336,30 @@ QString TitleBlockPropertiesWidget::currentTitleBlockTemplateName() const return QString(); } +/** + @brief TitleBlockPropertiesWidget::currentTitleBlockHeight + @return the height of the current title block template +*/ +int TitleBlockPropertiesWidget::currentTitleBlockHeight() const +{ + const TitleBlockTemplate *tbt = nullptr; + if (!currentTitleBlockTemplateName().isEmpty()) + tbt = templateForIndex(ui -> m_tbt_cb -> currentIndex()); + if (!tbt) + tbt = QETApp::defaultTitleBlockTemplate(); + return tbt ? tbt -> height() : 0; +} + +/** + @brief TitleBlockPropertiesWidget::currentTitleBlockEdge + @return the edge of the folio the title block is displayed at +*/ +Qt::Edge TitleBlockPropertiesWidget::currentTitleBlockEdge() const +{ + return ui -> m_display_at_cb -> currentIndex() == 0 ? Qt::BottomEdge + : Qt::RightEdge; +} + /** @brief TitleBlockPropertiesWidget::addCollection add a collection of title block available in the combo box @@ -388,6 +412,15 @@ void TitleBlockPropertiesWidget::initDialog( this, &TitleBlockPropertiesWidget::changeCurrentTitleBlockTemplate); + auto size_changed = [this]() { + emit titleBlockSizeChanged(currentTitleBlockHeight(), + currentTitleBlockEdge()); + }; + connect(ui->m_tbt_cb, qOverload(&QComboBox::currentIndexChanged), + this, size_changed); + connect(ui->m_display_at_cb, qOverload(&QComboBox::currentIndexChanged), + this, size_changed); + if (project!= nullptr){ keys_2 = project -> folioAutoNum().keys(); foreach (QString str, keys_2) { ui -> auto_page_cb -> addItem(str); } diff --git a/sources/ui/titleblockpropertieswidget.h b/sources/ui/titleblockpropertieswidget.h index 127f04c9c..4804f9837 100644 --- a/sources/ui/titleblockpropertieswidget.h +++ b/sources/ui/titleblockpropertieswidget.h @@ -70,6 +70,8 @@ class TitleBlockPropertiesWidget : public QWidget void setTitleBlockTemplatesVisible(const bool &visible); void setReadOnly (const bool &ro); + int currentTitleBlockHeight() const; + Qt::Edge currentTitleBlockEdge() const; static void addTemplateVariables( DiagramContext &context, @@ -96,6 +98,7 @@ class TitleBlockPropertiesWidget : public QWidget signals: void set_auto_page_num() const; void openAutoNumFolioEditor (QString); + void titleBlockSizeChanged(int height, Qt::Edge edge); private: Ui::TitleBlockPropertiesWidget *ui;