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:
Beat Hangartner
2026-10-04 23:08:23 +02:00
parent c76bc5eb4e
commit 360065036c
4 changed files with 128 additions and 30 deletions
+40 -20
View File
@@ -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
View File
@@ -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
View File
@@ -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