mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-10 06:04:12 +02:00
Spread several dropped pictures over the folio in a grid
Pictures dropped together were cascaded 20 px apart from the drop point; once scaled to fit the folio they lay almost on top of each other. They are now laid out side by side in a roughly square grid over the folio's drawing area, inside the same 20 % margin, each shrunk into its cell with its proportions kept. A single dropped picture still lands on the drop point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+40
-20
@@ -429,11 +429,12 @@ void DiagramView::handleTextDrop(QDropEvent *e) {
|
||||
|
||||
/**
|
||||
@brief DiagramView::handleImageFilesDrop
|
||||
Add the picture files dropped from the file manager. The first one is
|
||||
centred on the drop point and the others are cascaded from it. A
|
||||
picture too large for the folio is scaled down to leave a free margin
|
||||
of ImageDrop::frameMargin to the frame on every side, and every
|
||||
picture is kept inside the frame. One undo step removes them all. Files that cannot be used are
|
||||
Add the picture files dropped from the file manager. A single one is
|
||||
centred on the drop point and kept inside the frame; a picture too
|
||||
large for the folio is scaled down to leave a free margin of
|
||||
ImageDrop::frameMargin to the frame on every side. Several pictures
|
||||
are spread side by side in a grid over that same area
|
||||
(ImageDrop::gridLayout). One undo step removes them all. Files that cannot be used are
|
||||
listed once, after the others have been placed.
|
||||
@param e the QDropEvent describing the current drag'n drop
|
||||
*/
|
||||
@@ -446,32 +447,51 @@ void DiagramView::handleImageFilesDrop(QDropEvent *e)
|
||||
const QPointF drop_pos = mapToScene(e -> position().toPoint());
|
||||
const QRectF frame = m_diagram -> border_and_titleblock.insideBorderRect();
|
||||
|
||||
auto *undo = new QUndoCommand();
|
||||
QStringList refused;
|
||||
int placed = 0;
|
||||
QList<DiagramImageItem *> items;
|
||||
for (const QString &file : files)
|
||||
{
|
||||
QString error;
|
||||
const QImage image = ImageDrop::load(file, &error);
|
||||
if (image.isNull()) {
|
||||
if (image.isNull())
|
||||
refused << QStringLiteral("%1 : %2").arg(QFileInfo(file).fileName(), error);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
items << new DiagramImageItem(QPixmap::fromImage(image));
|
||||
}
|
||||
|
||||
auto *item = new DiagramImageItem(QPixmap::fromImage(image));
|
||||
const qreal scale = ImageDrop::fitScale(item -> boundingRect().size(), frame);
|
||||
// Where each picture goes, as it will look on the folio: one picture
|
||||
// is centred on the drop point and fitted to the frame, several are
|
||||
// spread over the frame in a grid.
|
||||
QList<QRectF> targets;
|
||||
if (items.size() == 1)
|
||||
{
|
||||
const QSizeF size = items.first() -> mapRectToScene(items.first() -> boundingRect()).size();
|
||||
const qreal scale = ImageDrop::fitScale(size, frame);
|
||||
QRectF r(QPointF(), size * scale);
|
||||
r.moveCenter(drop_pos);
|
||||
targets << ImageDrop::keepInside(r, scale < 1.0 ? ImageDrop::innerFrame(frame) : frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
QList<QSizeF> sizes;
|
||||
for (DiagramImageItem *item : items)
|
||||
sizes << item -> mapRectToScene(item -> boundingRect()).size();
|
||||
targets = ImageDrop::gridLayout(sizes, ImageDrop::innerFrame(frame));
|
||||
}
|
||||
|
||||
auto *undo = new QUndoCommand();
|
||||
const int placed = int(items.size());
|
||||
for (int i = 0 ; i < placed ; ++i)
|
||||
{
|
||||
DiagramImageItem *item = items.at(i);
|
||||
const QRectF natural = item -> mapRectToScene(item -> boundingRect());
|
||||
const qreal scale = natural.width() > 0 ? targets.at(i).width() / natural.width() : 1.0;
|
||||
if (scale < 1.0) {
|
||||
item -> setScaleFactorX(scale);
|
||||
item -> setScaleFactorY(scale);
|
||||
}
|
||||
// Centre the picture, as transformed, on its drop point, then keep
|
||||
// it inside the frame -- a scaled-down one also off the margin.
|
||||
QRectF rect = item -> mapRectToScene(item -> boundingRect());
|
||||
const QPointF offset = rect.topLeft() - item -> pos();
|
||||
rect.moveCenter(drop_pos + ImageDrop::cascadeOffset(placed));
|
||||
rect = ImageDrop::keepInside(rect, scale < 1.0 ? ImageDrop::innerFrame(frame) : frame);
|
||||
new AddGraphicsObjectCommand(item, m_diagram, rect.topLeft() - offset, undo);
|
||||
++placed;
|
||||
const QPointF offset = item -> mapRectToScene(item -> boundingRect()).topLeft() - item -> pos();
|
||||
new AddGraphicsObjectCommand(item, m_diagram, targets.at(i).topLeft() - offset, undo);
|
||||
}
|
||||
|
||||
if (placed) {
|
||||
|
||||
+22
-2
@@ -24,6 +24,7 @@
|
||||
#include <QUrl>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -119,7 +120,26 @@ QRectF ImageDrop::keepInside(const QRectF &picture, const QRectF &area)
|
||||
return result;
|
||||
}
|
||||
|
||||
QPointF ImageDrop::cascadeOffset(int index)
|
||||
QList<QRectF> ImageDrop::gridLayout(const QList<QSizeF> &sizes, const QRectF &area)
|
||||
{
|
||||
return QPointF(cascadeStep * index, cascadeStep * index);
|
||||
QList<QRectF> rects;
|
||||
const int count = int(sizes.size());
|
||||
if (count == 0)
|
||||
return rects;
|
||||
const int columns = int(std::ceil(std::sqrt(qreal(count))));
|
||||
const int rows = (count + columns - 1) / columns;
|
||||
const QSizeF cell(area.width() / columns, area.height() / rows);
|
||||
const QSizeF room = cell * (1.0 - gridGap);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
const QSizeF size = sizes.at(i);
|
||||
qreal scale = 1.0;
|
||||
if (!size.isEmpty() && !room.isEmpty())
|
||||
scale = std::min({1.0, room.width() / size.width(), room.height() / size.height()});
|
||||
QRectF r(QPointF(), size * scale);
|
||||
r.moveCenter(area.topLeft() + QPointF(cell.width() * (i % columns + 0.5),
|
||||
cell.height() * (i / columns + 0.5)));
|
||||
rects << r;
|
||||
}
|
||||
return rects;
|
||||
}
|
||||
|
||||
+12
-4
@@ -22,6 +22,7 @@
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QSizeF>
|
||||
#include <QList>
|
||||
#include <QStringList>
|
||||
|
||||
class QMimeData;
|
||||
@@ -37,8 +38,9 @@ namespace ImageDrop
|
||||
constexpr qint64 maxFileBytes = 10LL * 1024 * 1024;
|
||||
/// Refuses decompression bombs before any pixel is allocated.
|
||||
constexpr qint64 maxPixels = 64LL * 1024 * 1024;
|
||||
/// Offset between the pictures of one drop, in scene units.
|
||||
constexpr qreal cascadeStep = 20.0;
|
||||
/// Space left between the pictures of one drop laid out in a grid, as
|
||||
/// a share of a grid cell.
|
||||
constexpr qreal gridGap = 0.05;
|
||||
/// Free margin left between a scaled-down picture and the folio frame,
|
||||
/// as a share of the frame on each side.
|
||||
constexpr qreal frameMargin = 0.2;
|
||||
@@ -76,8 +78,14 @@ namespace ImageDrop
|
||||
*/
|
||||
QRectF keepInside(const QRectF &picture, const QRectF &area);
|
||||
|
||||
/// Offset of the @a index-th picture of one drop from the first one.
|
||||
QPointF cascadeOffset(int index);
|
||||
/**
|
||||
Lay out pictures of @a sizes, dropped together, side by side in a
|
||||
grid filling @a area: as many columns as needed for a roughly
|
||||
square grid, every picture shrunk -- never enlarged -- into its
|
||||
cell, proportions kept, and centred in it.
|
||||
@return where each picture goes, in the order of @a sizes
|
||||
*/
|
||||
QList<QRectF> gridLayout(const QList<QSizeF> &sizes, const QRectF &area);
|
||||
}
|
||||
|
||||
#endif // IMAGEDROP_H
|
||||
|
||||
@@ -192,11 +192,61 @@ private slots:
|
||||
QCOMPARE(ImageDrop::keepInside(picture, QRectF(0, 0, 1000, 800)), expected);
|
||||
}
|
||||
|
||||
void picturesOfOneDropCascade()
|
||||
// Several pictures dropped together are spread side by side: a
|
||||
// roughly square grid over the area, each shrunk into its cell.
|
||||
void severalPicturesAreSpreadInAGrid_data()
|
||||
{
|
||||
QCOMPARE(ImageDrop::cascadeOffset(0), QPointF(0, 0));
|
||||
QCOMPARE(ImageDrop::cascadeOffset(2), QPointF(2 * ImageDrop::cascadeStep,
|
||||
2 * ImageDrop::cascadeStep));
|
||||
QTest::addColumn<int>("count");
|
||||
QTest::addColumn<int>("columns");
|
||||
QTest::addColumn<int>("rows");
|
||||
QTest::newRow("2") << 2 << 2 << 1;
|
||||
QTest::newRow("3") << 3 << 2 << 2;
|
||||
QTest::newRow("4") << 4 << 2 << 2;
|
||||
QTest::newRow("5") << 5 << 3 << 2;
|
||||
QTest::newRow("9") << 9 << 3 << 3;
|
||||
}
|
||||
|
||||
void severalPicturesAreSpreadInAGrid()
|
||||
{
|
||||
QFETCH(int, count);
|
||||
QFETCH(int, columns);
|
||||
QFETCH(int, rows);
|
||||
const QRectF area(100, 50, 600, 480);
|
||||
const QList<QSizeF> sizes(count, QSizeF(3024, 4032));
|
||||
const QList<QRectF> rects = ImageDrop::gridLayout(sizes, area);
|
||||
QCOMPARE(rects.size(), count);
|
||||
|
||||
const QSizeF cell(area.width() / columns, area.height() / rows);
|
||||
for (int i = 0 ; i < count ; ++i)
|
||||
{
|
||||
const QRectF r = rects.at(i);
|
||||
// inside the area, proportions kept
|
||||
QVERIFY(area.contains(r));
|
||||
QVERIFY(qAbs(r.width() / r.height() - 3024.0 / 4032.0) < 1e-6);
|
||||
// centred in its own cell, row by row
|
||||
const QPointF centre = area.topLeft() + QPointF(cell.width() * (i % columns + 0.5),
|
||||
cell.height() * (i / columns + 0.5));
|
||||
QVERIFY(qAbs(r.center().x() - centre.x()) < 1e-6);
|
||||
QVERIFY(qAbs(r.center().y() - centre.y()) < 1e-6);
|
||||
// and overlapping no other
|
||||
for (int j = i + 1 ; j < count ; ++j)
|
||||
QVERIFY2(!r.intersects(rects.at(j)), qPrintable(QString("%1 and %2").arg(i).arg(j)));
|
||||
}
|
||||
}
|
||||
|
||||
void smallPicturesAreNotEnlargedInTheGrid()
|
||||
{
|
||||
const QList<QRectF> rects = ImageDrop::gridLayout({QSizeF(40, 20), QSizeF(30, 30)},
|
||||
QRectF(0, 0, 600, 480));
|
||||
QCOMPARE(rects.at(0).size(), QSizeF(40, 20));
|
||||
QCOMPARE(rects.at(1).size(), QSizeF(30, 30));
|
||||
QCOMPARE(rects.at(0).center(), QPointF(150, 240));
|
||||
QCOMPARE(rects.at(1).center(), QPointF(450, 240));
|
||||
}
|
||||
|
||||
void emptyDropHasNoLayout()
|
||||
{
|
||||
QVERIFY(ImageDrop::gridLayout({}, QRectF(0, 0, 600, 480)).isEmpty());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user