diff --git a/cmake/qet_compilation_vars.cmake b/cmake/qet_compilation_vars.cmake
index 5721e4111..1bf68b48d 100644
--- a/cmake/qet_compilation_vars.cmake
+++ b/cmake/qet_compilation_vars.cmake
@@ -168,6 +168,9 @@ set(QET_SRC_FILES
${QET_DIR}/sources/borderproperties.h
${QET_DIR}/sources/bordertitleblock.cpp
${QET_DIR}/sources/bordertitleblock.h
+ ${QET_DIR}/sources/bordercelllabels.h
+ ${QET_DIR}/sources/cellruler.cpp
+ ${QET_DIR}/sources/cellruler.h
${QET_DIR}/sources/conductorautonumerotation.cpp
${QET_DIR}/sources/conductorautonumerotation.h
${QET_DIR}/sources/conductornumexport.cpp
diff --git a/sources/bordercelllabels.h b/sources/bordercelllabels.h
new file mode 100644
index 000000000..d5dbb6e29
--- /dev/null
+++ b/sources/bordercelllabels.h
@@ -0,0 +1,51 @@
+/*
+ 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 .
+*/
+#ifndef BORDERCELLLABELS_H
+#define BORDERCELLLABELS_H
+
+#include
+
+/// The labels the border of a folio writes on its rows and columns, shared
+/// by BorderTitleBlock::draw() and the cell rulers of DiagramView so the
+/// two cannot disagree. Header-only so it can be unit-tested directly --
+/// see tests/qttest/tst_bordercelllabels.cpp.
+namespace BorderCellLabels {
+
+ /// @return the label of row \a row, counted from 1: A..Z, then AA, AB...
+ /// (the sequence BorderTitleBlock::incrementLetters() walks through).
+ inline QString rowLabel(int row)
+ {
+ QString label;
+ while (row > 0) {
+ --row;
+ label.prepend(QChar('A' + row % 26));
+ row /= 26;
+ }
+ return label;
+ }
+
+ /// @return the label of column \a column, counted from 1. When
+ /// \a starts_at_zero (the "border-columns_0" setting) the first column
+ /// is labelled 0.
+ inline QString columnLabel(int column, bool starts_at_zero)
+ {
+ return QString::number(starts_at_zero ? column - 1 : column);
+ }
+}
+
+#endif // BORDERCELLLABELS_H
diff --git a/sources/bordertitleblock.cpp b/sources/bordertitleblock.cpp
index c6913ba46..7878d1a9f 100644
--- a/sources/bordertitleblock.cpp
+++ b/sources/bordertitleblock.cpp
@@ -17,6 +17,7 @@
*/
#include "bordertitleblock.h"
+#include "bordercelllabels.h"
#include "createdxf.h"
#include "diagram.h"
#include "diagramposition.h"
@@ -534,6 +535,8 @@ void BorderTitleBlock::draw(QPainter *painter)
//Draw the nums of columns
if (display_border_ && display_columns_) {
+ const bool columns_start_at_zero =
+ settings.value("border-columns_0", true).toBool();
for (int i = 1 ; i <= columns_count_ ; ++ i) {
QRectF numbered_rectangle = QRectF(
diagram_rect_.topLeft().x()
@@ -544,23 +547,15 @@ void BorderTitleBlock::draw(QPainter *painter)
columns_header_height_
);
painter -> drawRect(numbered_rectangle);
- if (settings.value("border-columns_0", true).toBool()){
painter -> drawText(numbered_rectangle,
Qt::AlignVCenter
| Qt::AlignCenter,
- QString("%1").arg(i - 1));
- }else{
- painter -> drawText(numbered_rectangle,
- Qt::AlignVCenter
- | Qt::AlignCenter,
- QString("%1").arg(i));
- }
+ BorderCellLabels::columnLabel(i, columns_start_at_zero));
}
}
//Draw the nums of rows
if (display_border_ && display_rows_) {
- QString row_string("A");
for (int i = 1 ; i <= rows_count_ ; ++ i) {
QRectF lettered_rectangle = QRectF(
diagram_rect_.topLeft().x(),
@@ -576,8 +571,7 @@ void BorderTitleBlock::draw(QPainter *painter)
painter -> drawText(lettered_rectangle,
Qt::AlignVCenter
| Qt::AlignCenter,
- row_string);
- row_string = incrementLetters(row_string);
+ BorderCellLabels::rowLabel(i));
}
}
diff --git a/sources/cellruler.cpp b/sources/cellruler.cpp
new file mode 100644
index 000000000..299d70eb8
--- /dev/null
+++ b/sources/cellruler.cpp
@@ -0,0 +1,170 @@
+/*
+ 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 .
+*/
+#include "cellruler.h"
+
+#include "bordercelllabels.h"
+#include "diagram.h"
+#include "diagramview.h"
+
+#include
+#include
+
+/**
+ @brief CellRuler::CellRuler
+ @param orientation : Qt::Horizontal for the column numbers along the
+ top, Qt::Vertical for the row letters along the left
+ @param view : the view this ruler is placed on
+*/
+CellRuler::CellRuler(Qt::Orientation orientation, DiagramView *view) :
+ QWidget(view),
+ m_orientation(orientation),
+ m_view(view)
+{
+ setAttribute(Qt::WA_OpaquePaintEvent);
+ hide();
+}
+
+/**
+ @brief CellRuler::thickness
+ @return the height of the top ruler, which is also the width of the
+ side ruler, so the corner they share is square. Constant whatever the
+ zoom.
+*/
+int CellRuler::thickness() const
+{
+ const QFontMetrics metrics = fontMetrics();
+ return qMax(metrics.height(), metrics.horizontalAdvance(QStringLiteral("WW"))) + 6;
+}
+
+/**
+ @brief CellRuler::setLeadingSpace
+ @param space : pixels left empty before the first pixel of the
+ viewport, along this ruler
+*/
+void CellRuler::setLeadingSpace(int space)
+{
+ m_leading_space = space;
+ update();
+}
+
+/**
+ @brief CellRuler::paintEvent
+ Draw a cell for each column (or row) of the folio border, at the
+ position the view shows it. When the cells get too small for their
+ labels, only one label every 2, 5, 10... cells is written.
+*/
+void CellRuler::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event)
+
+ QPainter painter(this);
+ painter.fillRect(rect(), palette().color(QPalette::Button));
+
+ const bool horizontal = m_orientation == Qt::Horizontal;
+ const int length = horizontal ? width() : height();
+ const int depth = horizontal ? height() : width();
+
+ //Separate the ruler from the drawing
+ painter.setPen(palette().color(QPalette::Dark));
+ if (horizontal) {
+ painter.drawLine(0, depth - 1, length, depth - 1);
+ } else {
+ painter.drawLine(depth - 1, 0, depth - 1, length);
+ }
+
+ Diagram *diagram = m_view->diagram();
+ if (!diagram) {
+ return;
+ }
+ const BorderTitleBlock &border = diagram->border_and_titleblock;
+ const QRectF inside = border.insideBorderRect();
+ const QTransform transform = m_view->viewportTransform();
+
+ const int count = horizontal ? border.columnsCount() : border.rowsCount();
+ const qreal cell_size = horizontal ? border.columnsWidth() : border.rowsHeight();
+ const qreal first = horizontal ? inside.left() : inside.top();
+ const qreal scale = horizontal ? transform.m11() : transform.m22();
+ const qreal offset = (horizontal ? transform.dx() : transform.dy()) + m_leading_space;
+ const qreal cell_pixels = cell_size * scale;
+ if (count < 1 || cell_pixels <= 0) {
+ return;
+ }
+
+ const bool columns_start_at_zero =
+ QSettings().value("border-columns_0", true).toBool();
+ auto label = [&](int index) {
+ return horizontal ? BorderCellLabels::columnLabel(index, columns_start_at_zero)
+ : BorderCellLabels::rowLabel(index);
+ };
+
+ //Room one label needs along the ruler, and the smallest step
+ //between written labels that gives it that room
+ const QFontMetrics metrics = fontMetrics();
+ const int label_room = horizontal
+ ? metrics.horizontalAdvance(label(count)) + 6
+ : metrics.height() + 2;
+ int step = 1;
+ for (int candidate : {1, 2, 5, 10, 20, 50, 100, 200, 500}) {
+ step = candidate;
+ if (candidate * cell_pixels >= label_room) {
+ break;
+ }
+ }
+
+ for (int i = 1 ; i <= count ; ++i) {
+ const qreal start = offset + (first + (i - 1) * cell_size) * scale;
+ const qreal end = start + cell_pixels;
+ if (end < m_leading_space || start > length) {
+ continue;
+ }
+
+ //Cell edges, drawn only when the cells are wide enough
+ //for them to read as cells rather than as a hatching
+ painter.setPen(palette().color(QPalette::Dark));
+ if (cell_pixels >= 4) {
+ if (horizontal) {
+ painter.drawLine(QPointF(start, 0), QPointF(start, depth - 1));
+ if (i == count) painter.drawLine(QPointF(end, 0), QPointF(end, depth - 1));
+ } else {
+ painter.drawLine(QPointF(0, start), QPointF(depth - 1, start));
+ if (i == count) painter.drawLine(QPointF(0, end), QPointF(depth - 1, end));
+ }
+ }
+
+ //Written labels are the ones a multiple of step: 0, 5, 10...
+ //for the columns, A, F, K... for the rows
+ const int position = (horizontal && !columns_start_at_zero) ? i : i - 1;
+ if (position % step != 0) {
+ continue;
+ }
+
+ painter.setPen(palette().color(QPalette::ButtonText));
+ const qreal centre = (start + end) / 2;
+ const QRectF text_rect = horizontal
+ ? QRectF(centre - label_room / 2.0, 0, label_room, depth - 1)
+ : QRectF(0, centre - label_room / 2.0, depth - 1, label_room);
+ painter.drawText(text_rect, Qt::AlignCenter | Qt::TextDontClip, label(i));
+ }
+
+ //Keep the corner empty: the other ruler's labels do not belong there
+ if (m_leading_space > 0) {
+ painter.fillRect(horizontal ? QRect(0, 0, m_leading_space, depth - 1)
+ : QRect(0, 0, depth - 1, m_leading_space),
+ palette().color(QPalette::Button));
+ }
+}
diff --git a/sources/cellruler.h b/sources/cellruler.h
new file mode 100644
index 000000000..619b2d52e
--- /dev/null
+++ b/sources/cellruler.h
@@ -0,0 +1,54 @@
+/*
+ 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 .
+*/
+#ifndef CELLRULER_H
+#define CELLRULER_H
+
+#include
+
+class DiagramView;
+
+/**
+ @brief The CellRuler class
+ A bar along the top or the left edge of a DiagramView that repeats the
+ column numbers or the row letters of the folio border, aligned with the
+ cells at any zoom, so they stay in sight however far the view is
+ scrolled. It sits in the view's margins, outside of the scene: printing
+ and exporting are unaffected.
+*/
+class CellRuler : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ CellRuler(Qt::Orientation orientation, DiagramView *view);
+
+ int thickness() const;
+ void setLeadingSpace(int space);
+
+ protected:
+ void paintEvent(QPaintEvent *event) override;
+
+ private:
+ Qt::Orientation m_orientation;
+ DiagramView *m_view;
+ /// Pixels before the viewport starts, left empty: the corner the
+ /// side ruler fills when both rulers are shown.
+ int m_leading_space = 0;
+};
+
+#endif // CELLRULER_H
diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp
index bef745a9c..918b6bbbf 100644
--- a/sources/diagramview.cpp
+++ b/sources/diagramview.cpp
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see .
*/
#include "diagramview.h"
+#include "cellruler.h"
#include "lastusedstyle.h"
#include "qetproject.h"
#include "QPropertyUndoCommand/qpropertyundocommand.h"
@@ -108,6 +109,13 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) :
connect(&(m_diagram -> border_and_titleblock), &BorderTitleBlock::informationChanged, this, &DiagramView::updateWindowTitle);
connect(diagram, &Diagram::findElementRequired, this, &DiagramView::findElementRequired);
+ m_top_ruler = new CellRuler(Qt::Horizontal, this);
+ m_side_ruler = new CellRuler(Qt::Vertical, this);
+ m_cell_rulers_shown = QSettings().value("diagrameditor/cell_rulers", false).toBool();
+ connect(&m_diagram->border_and_titleblock, &BorderTitleBlock::borderChanged, this, &DiagramView::updateCellRulers);
+ connect(&m_diagram->border_and_titleblock, &BorderTitleBlock::displayChanged, this, &DiagramView::updateCellRulers);
+ updateCellRulers();
+
QShortcut *edit_conductor_color_shortcut = new QShortcut(QKeySequence(Qt::Key_F2), this);
connect(edit_conductor_color_shortcut, &QShortcut::activated, [this]()
{
@@ -1188,6 +1196,82 @@ void DiagramView::paintingInverted(bool inverted)
m_diagram->setInvertedLightness(inverted);
}
+/**
+ @brief DiagramView::setCellRulersShown
+ Show or hide the rulers that keep the column numbers and the row
+ letters of the folio border in sight along the edges of this view.
+ @param shown
+*/
+void DiagramView::setCellRulersShown(bool shown)
+{
+ m_cell_rulers_shown = shown;
+ updateCellRulers();
+}
+
+/**
+ @brief DiagramView::updateCellRulers
+ Show each ruler when the rulers are wanted and the folio shows the
+ matching header, and give it room in the margins of the view. The
+ part of the folio in sight stays in sight when the viewport resizes.
+*/
+void DiagramView::updateCellRulers()
+{
+ const BorderTitleBlock &border = m_diagram->border_and_titleblock;
+ const bool top = m_cell_rulers_shown
+ && border.borderIsDisplayed() && border.columnsAreDisplayed();
+ const bool side = m_cell_rulers_shown
+ && border.borderIsDisplayed() && border.rowsAreDisplayed();
+ const int thickness = m_top_ruler->thickness();
+
+ m_top_ruler->setVisible(top);
+ m_side_ruler->setVisible(side);
+ m_side_ruler->setLeadingSpace(top ? thickness : 0);
+
+ const QMargins margins(side ? thickness : 0, top ? thickness : 0, 0, 0);
+ if (margins != viewportMargins()) {
+ const QPointF centre = mapToScene(viewport()->rect().center());
+ setViewportMargins(margins);
+ centerOn(centre);
+ }
+ placeCellRulers();
+ m_top_ruler->update();
+ m_side_ruler->update();
+}
+
+/**
+ @brief DiagramView::placeCellRulers
+ Lay the rulers along the top and the left edges of the viewport, the
+ side ruler covering the corner too when both are shown.
+*/
+void DiagramView::placeCellRulers()
+{
+ if (!m_top_ruler) {
+ return;
+ }
+ const QRect viewport_rect = viewport()->geometry();
+ const int thickness = m_top_ruler->thickness();
+ const int corner = m_top_ruler->isHidden() ? 0 : thickness;
+ m_top_ruler->setGeometry(viewport_rect.left(), viewport_rect.top() - thickness,
+ viewport_rect.width(), thickness);
+ m_side_ruler->setGeometry(viewport_rect.left() - thickness, viewport_rect.top() - corner,
+ thickness, viewport_rect.height() + corner);
+}
+
+/**
+ @brief DiagramView::viewportEvent
+ Keep the rulers along the viewport when it resizes, which it also does
+ without the view resizing, when the scroll bars come and go.
+ @param event
+ @return what QGraphicsView::viewportEvent() returns
+*/
+bool DiagramView::viewportEvent(QEvent *event)
+{
+ if (event->type() == QEvent::Resize) {
+ placeCellRulers();
+ }
+ return PaletteGraphicsView::viewportEvent(event);
+}
+
/**
@brief DiagramView::paintEvent
Reimplemented from QGraphicsView
@@ -1197,6 +1281,13 @@ void DiagramView::paintEvent(QPaintEvent *event)
{
PaletteGraphicsView::paintEvent(event);
+ //Scrolling and zooming both repaint the viewport: follow them
+ if (viewportTransform() != m_rulers_transform) {
+ m_rulers_transform = viewportTransform();
+ m_top_ruler->update();
+ m_side_ruler->update();
+ }
+
if (m_free_rubberbanding && m_free_rubberband.count() >= 3)
{
QPainter painter(viewport());
diff --git a/sources/diagramview.h b/sources/diagramview.h
index a03b49ebd..9550e4898 100644
--- a/sources/diagramview.h
+++ b/sources/diagramview.h
@@ -24,6 +24,7 @@
#include
#include "palettegraphicsview.h"
+class CellRuler;
class Conductor;
class Diagram;
class QETDiagramEditor;
@@ -62,6 +63,11 @@ class DiagramView : public PaletteGraphicsView
QList m_separators;
QPolygonF m_free_rubberband;
bool m_free_rubberbanding = false;
+ CellRuler *m_top_ruler = nullptr;
+ CellRuler *m_side_ruler = nullptr;
+ bool m_cell_rulers_shown = false;
+ /// Last viewport transform the rulers were painted for
+ QTransform m_rulers_transform;
public:
@@ -78,6 +84,7 @@ class DiagramView : public PaletteGraphicsView
/// cursor query (QCursor::pos()/setPos() are silently ignored by
/// several window managers and compositors, Wayland included).
QPoint lastMousePos() const { return m_last_mouse_pos; }
+ void setCellRulersShown(bool shown);
protected:
void mouseDoubleClickEvent(QMouseEvent *) override;
@@ -91,6 +98,7 @@ class DiagramView : public PaletteGraphicsView
///Set for one call only, by the Escape handler, to let focus leave the view.
bool m_releasing_focus = false;
void paintEvent(QPaintEvent *event) override;
+ bool viewportEvent(QEvent *event) override;
void paintingInverted(bool inverted) override;
void mousePressEvent(QMouseEvent *) override;
void mouseMoveEvent(QMouseEvent *) override;
@@ -113,6 +121,8 @@ class DiagramView : public PaletteGraphicsView
QRectF viewedSceneRect() const;
bool mustIntegrateTitleBlockTemplate(const TitleBlockTemplateLocation &) const;
bool gestures() const;
+ void updateCellRulers();
+ void placeCellRulers();
/// Lowest and highest allowed value of the view transform scale (m11).
/// Prevents wheel-zoom from driving the transform to overflow, which
diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp
index 7c5f2ddb2..c97613ba7 100644
--- a/sources/qetdiagrameditor.cpp
+++ b/sources/qetdiagrameditor.cpp
@@ -522,6 +522,18 @@ void QETDiagramEditor::setUpActions()
}
});
+ //Keep the column numbers and row letters of the folio in sight
+ m_cell_rulers = new QAction(tr("Garder les en-têtes visibles"), this);
+ m_cell_rulers->setStatusTip(tr("Garde les numéros de colonne et les lettres de ligne du folio visibles au bord de la vue"));
+ m_cell_rulers->setCheckable(true);
+ m_cell_rulers->setChecked(settings.value("diagrameditor/cell_rulers", false).toBool());
+ connect(m_cell_rulers, &QAction::triggered, [this](bool checked) {
+ QSettings().setValue("diagrameditor/cell_rulers", checked);
+ foreach (ProjectView *prjv, this->openedProjects())
+ foreach (DiagramView *dv, prjv->diagram_views())
+ dv->setCellRulersShown(checked);
+ });
+
//Edit current diagram properties
m_edit_diagram_properties = new QAction(QET::Icons::DialogInformation, tr("Propriétés du folio"), this);
ShortcutManager::instance().registerAction(m_edit_diagram_properties, "diagrameditor.edit_diagram_properties", tr("Éditeur de schémas"), Qt::CTRL | Qt::Key_L);
@@ -1147,6 +1159,7 @@ void QETDiagramEditor::setUpMenu()
menu_affichage -> addAction(m_draw_grid);
menu_affichage -> addMenu(m_text_grid_menu);
menu_affichage -> addAction(m_draw_guides);
+ menu_affichage -> addAction(m_cell_rulers);
menu_affichage -> addMenu(m_background_color_button->menu());
menu_affichage -> addSeparator();
menu_affichage -> addActions(m_zoom_actions_group.actions());
@@ -1978,6 +1991,7 @@ void QETDiagramEditor::slot_updateActions()
m_background_color_button-> setEnabled(opened_diagram);
m_draw_grid-> setEnabled(opened_diagram);
m_draw_guides-> setEnabled(opened_diagram);
+ m_cell_rulers-> setEnabled(opened_diagram);
//Project menu
m_project_edit_properties -> setEnabled(opened_project);
diff --git a/sources/qetdiagrameditor.h b/sources/qetdiagrameditor.h
index fdcc6a88c..c0bece505 100644
--- a/sources/qetdiagrameditor.h
+++ b/sources/qetdiagrameditor.h
@@ -212,6 +212,7 @@ class QETDiagramEditor : public QETMainWindow
*m_auto_break_conductor, ///< Enable/Disable the use of auto break conductor
*m_draw_grid, ///< Switch the background grid display or not
*m_draw_guides = nullptr, ///< Switch the custom guides display or not
+ *m_cell_rulers = nullptr, ///< Keep the folio column/row headers in sight or not
*m_project_edit_properties, ///< Edit the properties of the current project.
*m_project_add_diagram, ///< Add a diagram to the current project.
*m_remove_diagram_from_project, ///< Delete a diagram from the current project
diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt
index 63766e0a1..3296bc016 100644
--- a/tests/qttest/CMakeLists.txt
+++ b/tests/qttest/CMakeLists.txt
@@ -84,6 +84,12 @@ add_test(NAME tst_diagramsortkeys COMMAND tst_diagramsortkeys)
target_include_directories(tst_diagramsortkeys PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_diagramsortkeys PRIVATE Qt::Test)
+# bordercelllabels.h is header-only too.
+add_executable(tst_bordercelllabels tst_bordercelllabels.cpp)
+add_test(NAME tst_bordercelllabels COMMAND tst_bordercelllabels)
+target_include_directories(tst_bordercelllabels PRIVATE ${QET_DIR}/sources)
+target_link_libraries(tst_bordercelllabels PRIVATE Qt::Test)
+
# contactusage.h is a header-only helper holding the contact counting
# rules, so this test builds independently of the rest of the QET sources.
add_executable(tst_contactusage tst_contactusage.cpp)
diff --git a/tests/qttest/tst_bordercelllabels.cpp b/tests/qttest/tst_bordercelllabels.cpp
new file mode 100644
index 000000000..7c4ca074b
--- /dev/null
+++ b/tests/qttest/tst_bordercelllabels.cpp
@@ -0,0 +1,55 @@
+#include
+
+#include "bordercelllabels.h"
+
+class tst_bordercelllabels : public QObject
+{
+ Q_OBJECT
+
+ // A copy of BorderTitleBlock::incrementLetters(), the walk the folio
+ // border used to draw its row labels with, as the reference rowLabel()
+ // must reproduce.
+ static QString incrementLetters(const QString &string)
+ {
+ if (string.isEmpty())
+ return QStringLiteral("A");
+ const QString first_digits(string.left(string.length() - 1));
+ const QChar last_digit(string.at(string.length() - 1));
+ if (last_digit != QLatin1Char('Z'))
+ return first_digits + QChar(last_digit.unicode() + 1);
+ return incrementLetters(first_digits) + QLatin1Char('A');
+ }
+
+private slots:
+ void rowLabelsFollowTheBorderSequence()
+ {
+ QString expected(QStringLiteral("A"));
+ for (int row = 1; row <= 1000; ++row) {
+ QCOMPARE(BorderCellLabels::rowLabel(row), expected);
+ expected = incrementLetters(expected);
+ }
+ }
+
+ void rowLabelSamples()
+ {
+ QCOMPARE(BorderCellLabels::rowLabel(1), QStringLiteral("A"));
+ QCOMPARE(BorderCellLabels::rowLabel(26), QStringLiteral("Z"));
+ QCOMPARE(BorderCellLabels::rowLabel(27), QStringLiteral("AA"));
+ QCOMPARE(BorderCellLabels::rowLabel(52), QStringLiteral("AZ"));
+ QCOMPARE(BorderCellLabels::rowLabel(53), QStringLiteral("BA"));
+ QCOMPARE(BorderCellLabels::rowLabel(702), QStringLiteral("ZZ"));
+ QCOMPARE(BorderCellLabels::rowLabel(703), QStringLiteral("AAA"));
+ }
+
+ void columnLabels()
+ {
+ QCOMPARE(BorderCellLabels::columnLabel(1, true), QStringLiteral("0"));
+ QCOMPARE(BorderCellLabels::columnLabel(1, false), QStringLiteral("1"));
+ QCOMPARE(BorderCellLabels::columnLabel(17, true), QStringLiteral("16"));
+ QCOMPARE(BorderCellLabels::columnLabel(17, false), QStringLiteral("17"));
+ }
+};
+
+QTEST_APPLESS_MAIN(tst_bordercelllabels)
+
+#include "tst_bordercelllabels.moc"