Compare commits

...

28 Commits

Author SHA1 Message Date
Valentin Buira
31787bb2e7
Merge e5e3b9a44a8bb26da7cdacc3fb545b090d991836 into b927df884feb840b67724af5c82c8088a9d20bfe 2025-07-02 10:18:28 +02:00
Loïc Bartoletti
b927df884f
Merge pull request #62484 from jef-n/fix-62478
fix #62478 (followup eac401c009)
2025-07-02 06:09:23 +02:00
qgis-bot
fe299930cd auto sipify 🍺 2025-07-01 23:40:57 +00:00
Mathieu Pellerin
8c7edbeec2 Rename rotation enum keys 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
ce2c8ac21f Address review 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
faf4afcba7 Fix case warning 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
4bad76d876 Where did my pre-commit hook go? 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
1f6c1277dd Lock rotation handle UI/UX behind an enabled boolean 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
ae463d8b75 Implement a ctrl modifier to snap to common angles when rotating item(s) 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
fb0d20942d Show rotation (delta) value in status bar 2025-07-02 11:38:05 +12:00
Mathieu Pellerin
1680a6cd2e [layouts] Brand new layout item rotation handles 2025-07-02 11:38:05 +12:00
Juergen E. Fischer
6fd32d92fd fix #62478 (followup eac401c009) 2025-07-01 17:54:58 +02:00
Valentin Buira
e5e3b9a44a Adress comments
superfluous centerOn
2025-06-30 11:51:11 +02:00
Valentin Buira
88dd13a0ca Fix view jumping around 2025-06-27 18:35:53 +02:00
Valentin Buira
e67201a2aa
Apply suggestions from code review
Co-authored-by: Stefanos Natsis <uclaros@gmail.com>
2025-05-14 13:56:01 +02:00
Valentin Buira
f2c61f819f Avoid needless extra method 2025-05-14 13:29:40 +02:00
Valentin Buira
55f2cbe674 Simplify restaure the previous viewed rect 2025-05-14 12:36:45 +02:00
Valentin Buira
9853fa2da1 Remove the good superfluous method this time 2025-05-14 12:18:59 +02:00
Valentin Buira
d678daf1d5 Revert "Superfluous method"
This reverts commit 13f020ef86c3f1501e1c1f69792f3b16616d4b4a.
2025-05-14 11:05:53 +02:00
Valentin Buira
13f020ef86 Superfluous method 2025-05-14 10:58:34 +02:00
Valentin Buira
32b0327dbf connect signal to method instead of individual calls 2025-05-14 10:48:28 +02:00
Valentin Buira
7ccd8f0435 Adress more comments 2025-05-14 10:44:45 +02:00
Valentin Buira
021467a178 Adress comments 2025-05-13 22:37:09 +02:00
Valentin Buira
88f59e4c37 Fixing test and add more case 2025-05-13 20:56:25 +02:00
Valentin Buira
a06f6ee8b1 Attempt to debug CI test 2025-05-13 11:44:12 +02:00
Valentin Buira
ee23cf7c44 Tidy up PR 2025-05-13 06:26:36 +02:00
Valentin Buira
86f372948e Fix scrollback to (0,0) on each repaintModel / rebuild of the model 2025-05-13 05:39:34 +02:00
Valentin Buira
ccdb476318 Unlimited modeler canvas size 2025-05-12 17:35:20 +02:00
27 changed files with 550 additions and 65 deletions

View File

@ -11505,6 +11505,10 @@ Qgis.MouseHandlesAction.ResizeLeftUp.__doc__ = "Resize left up (Top left handle)
Qgis.MouseHandlesAction.ResizeRightUp.__doc__ = "Resize right up (Top right handle)" Qgis.MouseHandlesAction.ResizeRightUp.__doc__ = "Resize right up (Top right handle)"
Qgis.MouseHandlesAction.ResizeLeftDown.__doc__ = "Resize left down (Bottom left handle)" Qgis.MouseHandlesAction.ResizeLeftDown.__doc__ = "Resize left down (Bottom left handle)"
Qgis.MouseHandlesAction.ResizeRightDown.__doc__ = "Resize right down (Bottom right handle)" Qgis.MouseHandlesAction.ResizeRightDown.__doc__ = "Resize right down (Bottom right handle)"
Qgis.MouseHandlesAction.RotateTopLeft.__doc__ = "Rotate from top left handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateTopRight.__doc__ = "Rotate from top right handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateBottomLeft.__doc__ = "Rotate from bottom left handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateBottomRight.__doc__ = "Rotate right bottom right handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.SelectItem.__doc__ = "Select item" Qgis.MouseHandlesAction.SelectItem.__doc__ = "Select item"
Qgis.MouseHandlesAction.NoAction.__doc__ = "No action" Qgis.MouseHandlesAction.NoAction.__doc__ = "No action"
Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles
@ -11520,6 +11524,22 @@ Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles
* ``ResizeRightUp``: Resize right up (Top right handle) * ``ResizeRightUp``: Resize right up (Top right handle)
* ``ResizeLeftDown``: Resize left down (Bottom left handle) * ``ResizeLeftDown``: Resize left down (Bottom left handle)
* ``ResizeRightDown``: Resize right down (Bottom right handle) * ``ResizeRightDown``: Resize right down (Bottom right handle)
* ``RotateTopLeft``: Rotate from top left handle.
.. versionadded:: 4.0
* ``RotateTopRight``: Rotate from top right handle.
.. versionadded:: 4.0
* ``RotateBottomLeft``: Rotate from bottom left handle.
.. versionadded:: 4.0
* ``RotateBottomRight``: Rotate right bottom right handle.
.. versionadded:: 4.0
* ``SelectItem``: Select item * ``SelectItem``: Select item
* ``NoAction``: No action * ``NoAction``: No action

View File

@ -3347,6 +3347,10 @@ The development version
ResizeRightUp, ResizeRightUp,
ResizeLeftDown, ResizeLeftDown,
ResizeRightDown, ResizeRightDown,
RotateTopLeft,
RotateTopRight,
RotateBottomLeft,
RotateBottomRight,
SelectItem, SelectItem,
NoAction NoAction
}; };

View File

@ -1,14 +1,14 @@
Qgis.defaultProjectScales: src/core/qgis.h#L6068 Qgis.defaultProjectScales: src/core/qgis.h#L6072
Qgis.devVersion: src/core/qgis.h#L89 Qgis.devVersion: src/core/qgis.h#L89
Qgis.geoNone: src/core/qgis.h#L6113 Qgis.geoNone: src/core/qgis.h#L6117
Qgis.geoProj4: src/core/qgis.h#L6143 Qgis.geoProj4: src/core/qgis.h#L6147
Qgis.geoWkt: src/core/qgis.h#L6134 Qgis.geoWkt: src/core/qgis.h#L6138
Qgis.geographicCrsAuthId: src/core/qgis.h#L6123 Qgis.geographicCrsAuthId: src/core/qgis.h#L6127
Qgis.geosVersion: src/core/qgis.h#L6103 Qgis.geosVersion: src/core/qgis.h#L6107
Qgis.geosVersionInt: src/core/qgis.h#L6075 Qgis.geosVersionInt: src/core/qgis.h#L6079
Qgis.geosVersionMajor: src/core/qgis.h#L6082 Qgis.geosVersionMajor: src/core/qgis.h#L6086
Qgis.geosVersionMinor: src/core/qgis.h#L6089 Qgis.geosVersionMinor: src/core/qgis.h#L6093
Qgis.geosVersionPatch: src/core/qgis.h#L6096 Qgis.geosVersionPatch: src/core/qgis.h#L6100
Qgis.releaseName: src/core/qgis.h#L79 Qgis.releaseName: src/core/qgis.h#L79
Qgis.version: src/core/qgis.h#L65 Qgis.version: src/core/qgis.h#L65
Qgis.versionInt: src/core/qgis.h#L72 Qgis.versionInt: src/core/qgis.h#L72

View File

@ -173,6 +173,16 @@ full details (``longMessage``).
%Docstring %Docstring
Requests a complete rebuild of a model by emitting the according signal Requests a complete rebuild of a model by emitting the according signal
.. versionadded:: 3.44
%End
void updateBounds();
%Docstring
Updates the scene rect based on the bounds of the model.
The bounding rectangle of the model is calculated off all components of
the model, with an additional margin arounds items.
.. versionadded:: 3.44 .. versionadded:: 3.44
%End %End

View File

@ -57,6 +57,15 @@ widget.
void setModelScene( QgsModelGraphicsScene *scene ); void setModelScene( QgsModelGraphicsScene *scene );
%Docstring %Docstring
Sets the related ``scene``. Sets the related ``scene``.
%End
void friendlySetSceneRect();
%Docstring
Sets the scene rect used for scrollbar without disturbing the user i.e:
- We growth the scene rect as the model growth - We shrink only if the
model scene rect is outside the current viewed viewport
Called each time the view viewport moved or the model scene changed
%End %End
QgsModelGraphicsScene *modelScene() const; QgsModelGraphicsScene *modelScene() const;
@ -134,12 +143,10 @@ Pastes items from clipboard, using the specified ``mode``.
%End %End
public slots: public slots:
void snapSelected(); void snapSelected();
%Docstring %Docstring
Snaps the selected items to the grid. Snaps the selected items to the grid.
%End %End
signals: signals:
void algorithmDropped( const QString &algorithmId, const QPointF &pos ); void algorithmDropped( const QString &algorithmId, const QPointF &pos );

View File

@ -11411,6 +11411,10 @@ Qgis.MouseHandlesAction.ResizeLeftUp.__doc__ = "Resize left up (Top left handle)
Qgis.MouseHandlesAction.ResizeRightUp.__doc__ = "Resize right up (Top right handle)" Qgis.MouseHandlesAction.ResizeRightUp.__doc__ = "Resize right up (Top right handle)"
Qgis.MouseHandlesAction.ResizeLeftDown.__doc__ = "Resize left down (Bottom left handle)" Qgis.MouseHandlesAction.ResizeLeftDown.__doc__ = "Resize left down (Bottom left handle)"
Qgis.MouseHandlesAction.ResizeRightDown.__doc__ = "Resize right down (Bottom right handle)" Qgis.MouseHandlesAction.ResizeRightDown.__doc__ = "Resize right down (Bottom right handle)"
Qgis.MouseHandlesAction.RotateTopLeft.__doc__ = "Rotate from top left handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateTopRight.__doc__ = "Rotate from top right handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateBottomLeft.__doc__ = "Rotate from bottom left handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.RotateBottomRight.__doc__ = "Rotate right bottom right handle. \n.. versionadded:: 4.0"
Qgis.MouseHandlesAction.SelectItem.__doc__ = "Select item" Qgis.MouseHandlesAction.SelectItem.__doc__ = "Select item"
Qgis.MouseHandlesAction.NoAction.__doc__ = "No action" Qgis.MouseHandlesAction.NoAction.__doc__ = "No action"
Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles
@ -11426,6 +11430,22 @@ Qgis.MouseHandlesAction.__doc__ = """Action to be performed by the mouse handles
* ``ResizeRightUp``: Resize right up (Top right handle) * ``ResizeRightUp``: Resize right up (Top right handle)
* ``ResizeLeftDown``: Resize left down (Bottom left handle) * ``ResizeLeftDown``: Resize left down (Bottom left handle)
* ``ResizeRightDown``: Resize right down (Bottom right handle) * ``ResizeRightDown``: Resize right down (Bottom right handle)
* ``RotateTopLeft``: Rotate from top left handle.
.. versionadded:: 4.0
* ``RotateTopRight``: Rotate from top right handle.
.. versionadded:: 4.0
* ``RotateBottomLeft``: Rotate from bottom left handle.
.. versionadded:: 4.0
* ``RotateBottomRight``: Rotate right bottom right handle.
.. versionadded:: 4.0
* ``SelectItem``: Select item * ``SelectItem``: Select item
* ``NoAction``: No action * ``NoAction``: No action

View File

@ -3347,6 +3347,10 @@ The development version
ResizeRightUp, ResizeRightUp,
ResizeLeftDown, ResizeLeftDown,
ResizeRightDown, ResizeRightDown,
RotateTopLeft,
RotateTopRight,
RotateBottomLeft,
RotateBottomRight,
SelectItem, SelectItem,
NoAction NoAction
}; };

View File

@ -1,14 +1,14 @@
Qgis.defaultProjectScales: src/core/qgis.h#L6068 Qgis.defaultProjectScales: src/core/qgis.h#L6072
Qgis.devVersion: src/core/qgis.h#L89 Qgis.devVersion: src/core/qgis.h#L89
Qgis.geoNone: src/core/qgis.h#L6113 Qgis.geoNone: src/core/qgis.h#L6117
Qgis.geoProj4: src/core/qgis.h#L6143 Qgis.geoProj4: src/core/qgis.h#L6147
Qgis.geoWkt: src/core/qgis.h#L6134 Qgis.geoWkt: src/core/qgis.h#L6138
Qgis.geographicCrsAuthId: src/core/qgis.h#L6123 Qgis.geographicCrsAuthId: src/core/qgis.h#L6127
Qgis.geosVersion: src/core/qgis.h#L6103 Qgis.geosVersion: src/core/qgis.h#L6107
Qgis.geosVersionInt: src/core/qgis.h#L6075 Qgis.geosVersionInt: src/core/qgis.h#L6079
Qgis.geosVersionMajor: src/core/qgis.h#L6082 Qgis.geosVersionMajor: src/core/qgis.h#L6086
Qgis.geosVersionMinor: src/core/qgis.h#L6089 Qgis.geosVersionMinor: src/core/qgis.h#L6093
Qgis.geosVersionPatch: src/core/qgis.h#L6096 Qgis.geosVersionPatch: src/core/qgis.h#L6100
Qgis.releaseName: src/core/qgis.h#L79 Qgis.releaseName: src/core/qgis.h#L79
Qgis.version: src/core/qgis.h#L65 Qgis.version: src/core/qgis.h#L65
Qgis.versionInt: src/core/qgis.h#L72 Qgis.versionInt: src/core/qgis.h#L72

View File

@ -173,6 +173,16 @@ full details (``longMessage``).
%Docstring %Docstring
Requests a complete rebuild of a model by emitting the according signal Requests a complete rebuild of a model by emitting the according signal
.. versionadded:: 3.44
%End
void updateBounds();
%Docstring
Updates the scene rect based on the bounds of the model.
The bounding rectangle of the model is calculated off all components of
the model, with an additional margin arounds items.
.. versionadded:: 3.44 .. versionadded:: 3.44
%End %End

View File

@ -57,6 +57,15 @@ widget.
void setModelScene( QgsModelGraphicsScene *scene ); void setModelScene( QgsModelGraphicsScene *scene );
%Docstring %Docstring
Sets the related ``scene``. Sets the related ``scene``.
%End
void friendlySetSceneRect();
%Docstring
Sets the scene rect used for scrollbar without disturbing the user i.e:
- We growth the scene rect as the model growth - We shrink only if the
model scene rect is outside the current viewed viewport
Called each time the view viewport moved or the model scene changed
%End %End
QgsModelGraphicsScene *modelScene() const; QgsModelGraphicsScene *modelScene() const;
@ -134,12 +143,10 @@ Pastes items from clipboard, using the specified ``mode``.
%End %End
public slots: public slots:
void snapSelected(); void snapSelected();
%Docstring %Docstring
Snaps the selected items to the grid. Snaps the selected items to the grid.
%End %End
signals: signals:
void algorithmDropped( const QString &algorithmId, const QPointF &pos ); void algorithmDropped( const QString &algorithmId, const QPointF &pos );

View File

@ -71,7 +71,6 @@ pluginPath = os.path.split(os.path.dirname(__file__))[0]
class ModelerDialog(QgsModelDesignerDialog): class ModelerDialog(QgsModelDesignerDialog):
CANVAS_SIZE = 4000
update_model = pyqtSignal() update_model = pyqtSignal()
@ -97,7 +96,6 @@ class ModelerDialog(QgsModelDesignerDialog):
self.setStyleSheet(iface.mainWindow().styleSheet()) self.setStyleSheet(iface.mainWindow().styleSheet())
scene = ModelerScene(self) scene = ModelerScene(self)
scene.setSceneRect(QRectF(0, 0, self.CANVAS_SIZE, self.CANVAS_SIZE))
self.setModelScene(scene) self.setModelScene(scene)
self.view().ensureVisible(0, 0, 10, 10) self.view().ensureVisible(0, 0, 10, 10)
@ -244,8 +242,6 @@ class ModelerDialog(QgsModelDesignerDialog):
def repaintModel(self, showControls=True): def repaintModel(self, showControls=True):
scene = ModelerScene(self) scene = ModelerScene(self)
scene.setSceneRect(QRectF(0, 0, self.CANVAS_SIZE, self.CANVAS_SIZE))
if not showControls: if not showControls:
scene.setFlag(QgsModelGraphicsScene.Flag.FlagHideControls) scene.setFlag(QgsModelGraphicsScene.Flag.FlagHideControls)
@ -259,6 +255,7 @@ class ModelerDialog(QgsModelDesignerDialog):
self.setModelScene(scene) self.setModelScene(scene)
# create items later that setModelScene to setup link to messageBar to the scene # create items later that setModelScene to setup link to messageBar to the scene
scene.createItems(self.model(), context) scene.createItems(self.model(), context)
scene.updateBounds()
def create_widget_context(self): def create_widget_context(self):
""" """
@ -347,7 +344,7 @@ class ModelerDialog(QgsModelDesignerDialog):
for i in list(self.model().parameterComponents().values()) for i in list(self.model().parameterComponents().values())
] ]
) )
newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) newX = MARGIN + BOX_WIDTH + maxX
else: else:
newX = MARGIN + BOX_WIDTH / 2 newX = MARGIN + BOX_WIDTH / 2
return QPointF(newX, MARGIN + BOX_HEIGHT / 2) return QPointF(newX, MARGIN + BOX_HEIGHT / 2)
@ -415,8 +412,8 @@ class ModelerDialog(QgsModelDesignerDialog):
for alg in list(self.model().childAlgorithms().values()) for alg in list(self.model().childAlgorithms().values())
] ]
) )
newX = min(MARGIN + BOX_WIDTH + maxX, self.CANVAS_SIZE - BOX_WIDTH) newX = MARGIN + BOX_WIDTH + maxX
newY = min(MARGIN + BOX_HEIGHT + maxY, self.CANVAS_SIZE - BOX_HEIGHT) newY = MARGIN + BOX_HEIGHT + maxY
else: else:
newX = MARGIN + BOX_WIDTH / 2 newX = MARGIN + BOX_WIDTH / 2
newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2 newY = MARGIN * 2 + BOX_HEIGHT + BOX_HEIGHT / 2

View File

@ -658,6 +658,9 @@ class Repositories(QObject):
.text() .text()
.strip() .strip()
) )
supports_qt6 = pluginNodes.item(i).firstChildElement(
"supports_qt6"
).text().strip().upper() in ["TRUE", "YES"]
if not qgisMaximumVersion: if not qgisMaximumVersion:
if qgisMinimumVersion[0] == "3" and supports_qt6: if qgisMinimumVersion[0] == "3" and supports_qt6:
qgisMaximumVersion = "4.99" qgisMaximumVersion = "4.99"

View File

@ -5897,6 +5897,10 @@ class CORE_EXPORT Qgis
ResizeRightUp, //!< Resize right up (Top right handle) ResizeRightUp, //!< Resize right up (Top right handle)
ResizeLeftDown, //!< Resize left down (Bottom left handle) ResizeLeftDown, //!< Resize left down (Bottom left handle)
ResizeRightDown, //!< Resize right down (Bottom right handle) ResizeRightDown, //!< Resize right down (Bottom right handle)
RotateTopLeft, //!< Rotate from top left handle. \since QGIS 4.0
RotateTopRight, //!< Rotate from top right handle. \since QGIS 4.0
RotateBottomLeft, //!< Rotate from bottom left handle. \since QGIS 4.0
RotateBottomRight, //!< Rotate right bottom right handle. \since QGIS 4.0
SelectItem, //!< Select item SelectItem, //!< Select item
NoAction //!< No action NoAction //!< No action
}; };

View File

@ -196,6 +196,10 @@ void QgsLayoutGuiUtils::registerGuiForKnownItemTypes( QgsMapCanvas *mapCanvas )
case Qgis::MouseHandlesAction::MoveItem: case Qgis::MouseHandlesAction::MoveItem:
case Qgis::MouseHandlesAction::NoAction: case Qgis::MouseHandlesAction::NoAction:
case Qgis::MouseHandlesAction::SelectItem: case Qgis::MouseHandlesAction::SelectItem:
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
return; return;
case Qgis::MouseHandlesAction::ResizeUp: case Qgis::MouseHandlesAction::ResizeUp:

View File

@ -44,6 +44,8 @@ QgsLayoutMouseHandles::QgsLayoutMouseHandles( QgsLayout *layout, QgsLayoutView *
, mLayout( layout ) , mLayout( layout )
, mView( view ) , mView( view )
{ {
setRotationEnabled( true );
//listen for selection changes, and update handles accordingly //listen for selection changes, and update handles accordingly
connect( mLayout, &QGraphicsScene::selectionChanged, this, &QgsLayoutMouseHandles::selectionChanged ); connect( mLayout, &QGraphicsScene::selectionChanged, this, &QgsLayoutMouseHandles::selectionChanged );
@ -251,6 +253,16 @@ void QgsLayoutMouseHandles::moveItem( QGraphicsItem *item, double deltaX, double
qgis::down_cast<QgsLayoutItem *>( item )->attemptMoveBy( deltaX, deltaY ); qgis::down_cast<QgsLayoutItem *>( item )->attemptMoveBy( deltaX, deltaY );
} }
void QgsLayoutMouseHandles::rotateItem( QGraphicsItem *item, double deltaDegree, double deltaCenterX, double deltaCenterY )
{
QgsLayoutItem *itm = qgis::down_cast<QgsLayoutItem *>( item );
QgsLayoutItem::ReferencePoint previousReferencePoint = itm->referencePoint();
itm->setReferencePoint( QgsLayoutItem::Middle );
itm->attemptMoveBy( deltaCenterX, deltaCenterY );
itm->setItemRotation( itm->itemRotation() + deltaDegree, true );
itm->setReferencePoint( previousReferencePoint );
}
void QgsLayoutMouseHandles::setItemRect( QGraphicsItem *item, QRectF rect ) void QgsLayoutMouseHandles::setItemRect( QGraphicsItem *item, QRectF rect )
{ {
QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item ); QgsLayoutItem *layoutItem = dynamic_cast<QgsLayoutItem *>( item );

View File

@ -74,6 +74,7 @@ class GUI_EXPORT QgsLayoutMouseHandles : public QgsGraphicsViewMouseHandles
void expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const override; void expandItemList( const QList<QGraphicsItem *> &items, QList<QGraphicsItem *> &collected ) const override;
void expandItemList( const QList<QgsLayoutItem *> &items, QList<QGraphicsItem *> &collected ) const; void expandItemList( const QList<QgsLayoutItem *> &items, QList<QGraphicsItem *> &collected ) const;
void moveItem( QGraphicsItem *item, double deltaX, double deltaY ) override; void moveItem( QGraphicsItem *item, double deltaX, double deltaY ) override;
void rotateItem( QGraphicsItem *item, double deltaDegree, double deltaCenterX, double deltaCenterY ) override;
void setItemRect( QGraphicsItem *item, QRectF rect ) override; void setItemRect( QGraphicsItem *item, QRectF rect ) override;
void showStatusMessage( const QString &message ) override; void showStatusMessage( const QString &message ) override;
void hideAlignItems() override; void hideAlignItems() override;

View File

@ -440,7 +440,7 @@ void QgsModelDesignerDialog::setModel( QgsProcessingModelAlgorithm *model )
mGroupEdit->setText( mModel->group() ); mGroupEdit->setText( mModel->group() );
mNameEdit->setText( mModel->displayName() ); mNameEdit->setText( mModel->displayName() );
repaintModel(); repaintModel( true );
updateVariablesGui(); updateVariablesGui();
mView->centerOn( 0, 0 ); mView->centerOn( 0, 0 );
@ -480,7 +480,6 @@ void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
mScene->setModel( mModel.get() ); mScene->setModel( mModel.get() );
mScene->setMessageBar( mMessageBar ); mScene->setMessageBar( mMessageBar );
const QPointF center = mView->mapToScene( mView->viewport()->rect().center() );
mView->setModelScene( mScene ); mView->setModelScene( mScene );
mSelectTool->resetCache(); mSelectTool->resetCache();
@ -499,8 +498,6 @@ void QgsModelDesignerDialog::setModelScene( QgsModelGraphicsScene *scene )
connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs, this, &QgsModelDesignerDialog::showChildAlgorithmOutputs ); connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmOutputs, this, &QgsModelDesignerDialog::showChildAlgorithmOutputs );
connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog, this, &QgsModelDesignerDialog::showChildAlgorithmLog ); connect( mScene, &QgsModelGraphicsScene::showChildAlgorithmLog, this, &QgsModelDesignerDialog::showChildAlgorithmLog );
mView->centerOn( center );
if ( oldScene ) if ( oldScene )
oldScene->deleteLater(); oldScene->deleteLater();
} }

View File

@ -33,6 +33,8 @@ QgsModelGraphicsScene::QgsModelGraphicsScene( QObject *parent )
: QGraphicsScene( parent ) : QGraphicsScene( parent )
{ {
setItemIndexMethod( QGraphicsScene::NoIndex ); setItemIndexMethod( QGraphicsScene::NoIndex );
connect( this, &QgsModelGraphicsScene::componentChanged, this, &QgsModelGraphicsScene::updateBounds );
} }
QgsProcessingModelAlgorithm *QgsModelGraphicsScene::model() QgsProcessingModelAlgorithm *QgsModelGraphicsScene::model()
@ -60,6 +62,28 @@ void QgsModelGraphicsScene::mousePressEvent( QGraphicsSceneMouseEvent *event )
QGraphicsScene::mousePressEvent( event ); QGraphicsScene::mousePressEvent( event );
} }
void QgsModelGraphicsScene::updateBounds()
{
//start with an empty rectangle
QRectF bounds;
//add all items
const QList<QGraphicsItem *> constItems = items();
for ( QGraphicsItem *item : constItems )
{
QgsModelComponentGraphicItem *componentItem = dynamic_cast<QgsModelComponentGraphicItem *>( item );
if ( componentItem )
bounds = bounds.united( componentItem->sceneBoundingRect() );
}
if ( bounds.isValid() )
{
bounds.adjust( -SCENE_COMPONENT_MARGIN, -SCENE_COMPONENT_MARGIN, SCENE_COMPONENT_MARGIN, SCENE_COMPONENT_MARGIN );
}
setSceneRect( bounds );
}
QgsModelComponentGraphicItem *QgsModelGraphicsScene::createParameterGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelParameter *param ) const QgsModelComponentGraphicItem *QgsModelGraphicsScene::createParameterGraphicItem( QgsProcessingModelAlgorithm *model, QgsProcessingModelParameter *param ) const
{ {
return new QgsModelParameterGraphicItem( param, model, nullptr ); return new QgsModelParameterGraphicItem( param, model, nullptr );

View File

@ -181,6 +181,15 @@ class GUI_EXPORT QgsModelGraphicsScene : public QGraphicsScene
*/ */
void requestRebuildRequired(); void requestRebuildRequired();
/**
* Updates the scene rect based on the bounds of the model.
* The bounding rectangle of the model is calculated off all components of the model, with an additional margin arounds items.
*
* \since QGIS 3.44
*/
void updateBounds();
signals: signals:
/** /**
@ -282,6 +291,8 @@ class GUI_EXPORT QgsModelGraphicsScene : public QGraphicsScene
QMap<QString, QgsModelComponentGraphicItem *> mGroupBoxItems; QMap<QString, QgsModelComponentGraphicItem *> mGroupBoxItems;
QgsProcessingModelResult mLastResult; QgsProcessingModelResult mLastResult;
static constexpr int SCENE_COMPONENT_MARGIN = 50;
QgsMessageBar *mMessageBar = nullptr; QgsMessageBar *mMessageBar = nullptr;
}; };

View File

@ -52,6 +52,9 @@ QgsModelGraphicsView::QgsModelGraphicsView( QWidget *parent )
mMidMouseButtonPanTool = new QgsModelViewToolTemporaryMousePan( this ); mMidMouseButtonPanTool = new QgsModelViewToolTemporaryMousePan( this );
mSpaceZoomTool = new QgsModelViewToolTemporaryKeyZoom( this ); mSpaceZoomTool = new QgsModelViewToolTemporaryKeyZoom( this );
connect( horizontalScrollBar(), &QScrollBar::sliderReleased, this, [=] { friendlySetSceneRect(); } );
connect( verticalScrollBar(), &QScrollBar::sliderReleased, this, [=] { friendlySetSceneRect(); } );
mSnapper.setSnapToGrid( true ); mSnapper.setSnapToGrid( true );
} }
@ -397,6 +400,8 @@ void QgsModelGraphicsView::setModelScene( QgsModelGraphicsScene *scene )
{ {
setScene( scene ); setScene( scene );
connect( scene, &QgsModelGraphicsScene::sceneRectChanged, this, [=] { friendlySetSceneRect(); } );
// IMPORTANT! // IMPORTANT!
// previous snap markers, snap lines are owned by previous layout - so don't delete them here! // previous snap markers, snap lines are owned by previous layout - so don't delete them here!
mSnapMarker = new QgsModelViewSnapMarker(); mSnapMarker = new QgsModelViewSnapMarker();
@ -468,7 +473,6 @@ void QgsModelGraphicsView::endCommand()
emit commandEnded(); emit commandEnded();
} }
void QgsModelGraphicsView::snapSelected() void QgsModelGraphicsView::snapSelected()
{ {
QgsModelGraphicsScene *s = modelScene(); QgsModelGraphicsScene *s = modelScene();
@ -492,6 +496,21 @@ void QgsModelGraphicsView::snapSelected()
endMacroCommand(); endMacroCommand();
} }
void QgsModelGraphicsView::friendlySetSceneRect()
{
QRectF modelSceneRect = modelScene()->sceneRect();
QRectF viewSceneRect = sceneRect();
QRectF visibleRect = mapToScene( viewport()->rect() ).boundingRect();
viewSceneRect.setLeft( std::min( modelSceneRect.left(), visibleRect.left() ) );
viewSceneRect.setRight( std::max( modelSceneRect.right(), visibleRect.right() ) );
viewSceneRect.setTop( std::min( modelSceneRect.top(), visibleRect.top() ) );
viewSceneRect.setBottom( std::max( modelSceneRect.bottom(), visibleRect.bottom() ) );
setSceneRect( viewSceneRect );
}
void QgsModelGraphicsView::copySelectedItems( QgsModelGraphicsView::ClipboardOperation operation ) void QgsModelGraphicsView::copySelectedItems( QgsModelGraphicsView::ClipboardOperation operation )
{ {
copyItems( modelScene()->selectedComponentItems(), operation ); copyItems( modelScene()->selectedComponentItems(), operation );

View File

@ -66,6 +66,16 @@ class GUI_EXPORT QgsModelGraphicsView : public QGraphicsView
*/ */
void setModelScene( QgsModelGraphicsScene *scene ); void setModelScene( QgsModelGraphicsScene *scene );
/**
* Sets the scene rect used for scrollbar without disturbing the user
* i.e:
* - We growth the scene rect as the model growth
* - We shrink only if the model scene rect is outside the current viewed viewport
*
* Called each time the view viewport moved or the model scene changed
*/
void friendlySetSceneRect();
/** /**
* Returns the scene associated with the tool. * Returns the scene associated with the tool.
* \see view() * \see view()
@ -158,12 +168,10 @@ class GUI_EXPORT QgsModelGraphicsView : public QGraphicsView
void pasteItems( PasteMode mode ); void pasteItems( PasteMode mode );
public slots: public slots:
/** /**
* Snaps the selected items to the grid. * Snaps the selected items to the grid.
*/ */
void snapSelected(); void snapSelected();
signals: signals:
/** /**

View File

@ -81,6 +81,7 @@ void QgsModelViewToolPan::modelReleaseEvent( QgsModelViewMouseEvent *event )
mIsPanning = false; mIsPanning = false;
view()->viewport()->setCursor( Qt::OpenHandCursor ); view()->viewport()->setCursor( Qt::OpenHandCursor );
view()->friendlySetSceneRect();
} }
void QgsModelViewToolPan::deactivate() void QgsModelViewToolPan::deactivate()

View File

@ -37,6 +37,7 @@ void QgsModelViewToolTemporaryKeyPan::keyReleaseEvent( QKeyEvent *event )
if ( event->key() == Qt::Key_Space && !event->isAutoRepeat() ) if ( event->key() == Qt::Key_Space && !event->isAutoRepeat() )
{ {
view()->setTool( mPreviousViewTool ); view()->setTool( mPreviousViewTool );
view()->friendlySetSceneRect();
} }
} }

View File

@ -37,6 +37,7 @@ void QgsModelViewToolTemporaryMousePan::modelReleaseEvent( QgsModelViewMouseEven
if ( event->button() == Qt::MiddleButton ) if ( event->button() == Qt::MiddleButton )
{ {
view()->setTool( mPreviousViewTool ); view()->setTool( mPreviousViewTool );
view()->friendlySetSceneRect();
} }
} }

View File

@ -15,10 +15,13 @@
* * * *
***************************************************************************/ ***************************************************************************/
#include "qgsgraphicsviewmousehandles.h"
#include "moc_qgsgraphicsviewmousehandles.cpp" #include "moc_qgsgraphicsviewmousehandles.cpp"
#include "qgsrendercontext.h"
#include "qgis.h" #include "qgis.h"
#include "qgsgraphicsviewmousehandles.h"
#include "qgslayoututils.h"
#include "qgsrendercontext.h"
#include <QGraphicsView> #include <QGraphicsView>
#include <QGraphicsSceneHoverEvent> #include <QGraphicsSceneHoverEvent>
#include <QPainter> #include <QPainter>
@ -34,6 +37,28 @@ QgsGraphicsViewMouseHandles::QgsGraphicsViewMouseHandles( QGraphicsView *view )
{ {
//accept hover events, required for changing cursor to resize cursors //accept hover events, required for changing cursor to resize cursors
setAcceptHoverEvents( true ); setAcceptHoverEvents( true );
//prepare rotation handle path
mRotationHandlePath.moveTo( 0, 14 );
mRotationHandlePath.lineTo( 6, 20 );
mRotationHandlePath.lineTo( 12, 14 );
mRotationHandlePath.arcTo( 8, 8, 12, 12, 180, -90 );
mRotationHandlePath.lineTo( 14, 12 );
mRotationHandlePath.lineTo( 20, 6 );
mRotationHandlePath.lineTo( 14, 0 );
mRotationHandlePath.arcTo( 4, 4, 20, 20, 90, 90 );
mRotationHandlePath.lineTo( 0, 14 );
}
void QgsGraphicsViewMouseHandles::setRotationEnabled( bool enable )
{
if ( mRotationEnabled == enable )
{
return;
}
mRotationEnabled = enable;
update();
} }
void QgsGraphicsViewMouseHandles::paintInternal( QPainter *painter, bool showHandles, bool showStaticBoundingBoxes, bool showTemporaryBoundingBoxes, const QStyleOptionGraphicsItem *, QWidget * ) void QgsGraphicsViewMouseHandles::paintInternal( QPainter *painter, bool showHandles, bool showStaticBoundingBoxes, bool showTemporaryBoundingBoxes, const QStyleOptionGraphicsItem *, QWidget * )
@ -62,6 +87,11 @@ QRectF QgsGraphicsViewMouseHandles::storedItemRect( QGraphicsItem *item ) const
return itemRect( item ); return itemRect( item );
} }
void QgsGraphicsViewMouseHandles::rotateItem( QGraphicsItem *, double, double, double )
{
QgsDebugError( QStringLiteral( "Rotation is not implemented for this class" ) );
}
void QgsGraphicsViewMouseHandles::previewItemMove( QGraphicsItem *, double, double ) void QgsGraphicsViewMouseHandles::previewItemMove( QGraphicsItem *, double, double )
{ {
} }
@ -108,7 +138,7 @@ void QgsGraphicsViewMouseHandles::drawHandles( QPainter *painter, double rectHan
painter->setBrush( Qt::NoBrush ); painter->setBrush( Qt::NoBrush );
painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) ); painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
//draw resize handles, using a filled white box //draw resize handles, using filled white boxes
painter->setBrush( QColor( 255, 255, 255, 255 ) ); painter->setBrush( QColor( 255, 255, 255, 255 ) );
//top left //top left
painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) ); painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
@ -126,6 +156,63 @@ void QgsGraphicsViewMouseHandles::drawHandles( QPainter *painter, double rectHan
painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); painter->drawRect( QRectF( ( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
//bottom right //bottom right
painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) ); painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
if ( isRotationEnabled() )
{
//draw rotate handles
const double scale = rectHandlerSize / mHandleSize;
const bool drawBottomRotationHandles = ( rectHandlerSize * 2 ) + ( mRotationHandleSize * scale * 2 ) < rect().height();
const bool drawRightRotationHandles = ( rectHandlerSize * 2 ) + ( mRotationHandleSize * scale * 2 ) < rect().width();
QTransform transform;
//top left
transform.reset();
transform.translate( rectHandlerSize, rectHandlerSize );
transform.scale( scale, scale );
painter->save();
painter->setTransform( transform, true );
painter->drawPath( mRotationHandlePath );
painter->restore();
//top right
if ( drawRightRotationHandles )
{
transform.reset();
transform.translate( rect().width() - rectHandlerSize, rectHandlerSize );
transform.rotate( 90 );
transform.scale( scale, scale );
painter->save();
painter->setTransform( transform, true );
painter->drawPath( mRotationHandlePath );
painter->restore();
}
if ( drawBottomRotationHandles )
{
//bottom left
transform.reset();
transform.translate( rectHandlerSize, rect().height() - rectHandlerSize );
transform.rotate( 270 );
transform.scale( scale, scale );
painter->save();
painter->setTransform( transform, true );
painter->drawPath( mRotationHandlePath );
painter->restore();
}
if ( drawBottomRotationHandles && drawRightRotationHandles )
{
//bottom right
transform.reset();
transform.translate( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize );
transform.rotate( 180 );
transform.scale( scale, scale );
painter->save();
painter->setTransform( transform, true );
painter->drawPath( mRotationHandlePath );
painter->restore();
}
}
} }
void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter ) void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
@ -181,6 +268,17 @@ void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect ); relativeResizeRect( thisItemRect, QRectF( -mResizeMoveX, -mResizeMoveY, mBeginHandleWidth, mBeginHandleHeight ), mResizeRect );
itemBounds = QPolygonF( thisItemRect ); itemBounds = QPolygonF( thisItemRect );
} }
else if ( isRotating() && !itemIsLocked( item ) )
{
const QPolygonF itemSceneBounds = item->mapToScene( itemRect( item ) );
const QPointF rotationCenter = sceneTransform().map( rect().center() );
QTransform transform;
transform.translate( rotationCenter.x(), rotationCenter.y() );
transform.rotate( mRotationDelta );
transform.translate( -rotationCenter.x(), -rotationCenter.y() );
itemBounds = mapFromScene( transform.map( itemSceneBounds ) );
}
else else
{ {
// not resizing or moving, so just map the item's bounds to the mouse handle item's coordinate system // not resizing or moving, so just map the item's bounds to the mouse handle item's coordinate system
@ -195,7 +293,7 @@ void QgsGraphicsViewMouseHandles::drawSelectedItemBounds( QPainter *painter )
} }
} }
double QgsGraphicsViewMouseHandles::rectHandlerBorderTolerance() double QgsGraphicsViewMouseHandles::rectHandlerBorderTolerance() const
{ {
if ( !mView ) if ( !mView )
return 0; return 0;
@ -312,6 +410,12 @@ Qt::CursorShape QgsGraphicsViewMouseHandles::cursorForPosition( QPointF itemCoor
} }
case Qgis::MouseHandlesAction::SelectItem: case Qgis::MouseHandlesAction::SelectItem:
return Qt::ArrowCursor; return Qt::ArrowCursor;
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
return Qt::PointingHandCursor;
} }
return Qt::ArrowCursor; return Qt::ArrowCursor;
@ -324,8 +428,14 @@ Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QP
bool nearLowerBorder = false; bool nearLowerBorder = false;
bool nearUpperBorder = false; bool nearUpperBorder = false;
bool nearLeftInner = false;
bool nearRightInner = false;
bool nearLowerInner = false;
bool nearUpperInner = false;
bool withinWidth = false; bool withinWidth = false;
bool withinHeight = false; bool withinHeight = false;
if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() ) if ( itemCoordPos.x() >= 0 && itemCoordPos.x() <= rect().width() )
{ {
withinWidth = true; withinWidth = true;
@ -336,23 +446,40 @@ Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QP
} }
double borderTolerance = rectHandlerBorderTolerance(); double borderTolerance = rectHandlerBorderTolerance();
double innerTolerance = mRotationHandleSize * borderTolerance / mHandleSize;
if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance ) if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
{ {
nearLeftBorder = true; nearLeftBorder = true;
} }
else if ( isRotationEnabled() && itemCoordPos.x() >= borderTolerance && itemCoordPos.x() < ( borderTolerance + innerTolerance ) )
{
nearLeftInner = true;
}
if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance ) if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
{ {
nearUpperBorder = true; nearUpperBorder = true;
} }
else if ( isRotationEnabled() && itemCoordPos.y() >= borderTolerance && itemCoordPos.y() < ( borderTolerance + innerTolerance ) )
{
nearUpperInner = true;
}
if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) ) if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
{ {
nearRightBorder = true; nearRightBorder = true;
} }
else if ( isRotationEnabled() && itemCoordPos.x() <= ( rect().width() - borderTolerance ) && itemCoordPos.x() > ( rect().width() - borderTolerance - innerTolerance ) )
{
nearRightInner = true;
}
if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) ) if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
{ {
nearLowerBorder = true; nearLowerBorder = true;
} }
else if ( isRotationEnabled() && itemCoordPos.y() <= ( rect().height() - borderTolerance ) && itemCoordPos.y() > ( rect().height() - borderTolerance - innerTolerance ) )
{
nearLowerInner = true;
}
if ( nearLeftBorder && nearUpperBorder ) if ( nearLeftBorder && nearUpperBorder )
{ {
@ -386,6 +513,22 @@ Qgis::MouseHandlesAction QgsGraphicsViewMouseHandles::mouseActionForPosition( QP
{ {
return Qgis::MouseHandlesAction::ResizeDown; return Qgis::MouseHandlesAction::ResizeDown;
} }
else if ( nearLeftInner && nearUpperInner )
{
return Qgis::MouseHandlesAction::RotateTopLeft;
}
else if ( nearRightInner && nearUpperInner )
{
return Qgis::MouseHandlesAction::RotateTopRight;
}
else if ( nearLeftInner && nearLowerInner )
{
return Qgis::MouseHandlesAction::RotateBottomLeft;
}
else if ( nearRightInner && nearLowerInner )
{
return Qgis::MouseHandlesAction::RotateBottomRight;
}
//find out if cursor position is over a selected item //find out if cursor position is over a selected item
QPointF scenePoint = mapToScene( itemCoordPos ); QPointF scenePoint = mapToScene( itemCoordPos );
@ -489,19 +632,42 @@ void QgsGraphicsViewMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent *eve
hideAlignItems(); hideAlignItems();
if ( mCurrentMouseMoveAction == Qgis::MouseHandlesAction::MoveItem ) switch ( mCurrentMouseMoveAction )
{ {
case Qgis::MouseHandlesAction::MoveItem:
//moving items //moving items
mIsDragging = true; mIsDragging = true;
} break;
else if ( mCurrentMouseMoveAction != Qgis::MouseHandlesAction::SelectItem && mCurrentMouseMoveAction != Qgis::MouseHandlesAction::NoAction )
{ case Qgis::MouseHandlesAction::ResizeUp:
case Qgis::MouseHandlesAction::ResizeDown:
case Qgis::MouseHandlesAction::ResizeLeft:
case Qgis::MouseHandlesAction::ResizeRight:
case Qgis::MouseHandlesAction::ResizeLeftUp:
case Qgis::MouseHandlesAction::ResizeRightUp:
case Qgis::MouseHandlesAction::ResizeLeftDown:
case Qgis::MouseHandlesAction::ResizeRightDown:
//resizing items //resizing items
mIsResizing = true; mIsResizing = true;
mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight ); mResizeRect = QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight );
mResizeMoveX = 0; mResizeMoveX = 0;
mResizeMoveY = 0; mResizeMoveY = 0;
mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos ); mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos );
break;
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
mIsRotating = true;
mRotationCenter = sceneTransform().map( rect().center() );
mRotationBegin = std::atan2( mMouseMoveStartPos.y() - mRotationCenter.y(), mMouseMoveStartPos.x() - mRotationCenter.x() ) * 180 / M_PI;
mRotationCurrent = 0.0;
break;
case Qgis::MouseHandlesAction::SelectItem:
case Qgis::MouseHandlesAction::NoAction:
break;
} }
} }
@ -537,6 +703,12 @@ void QgsGraphicsViewMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent *even
//resize from center if alt depressed //resize from center if alt depressed
resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier ); resizeMouseMove( event->lastScenePos(), event->modifiers() & Qt::ShiftModifier, event->modifiers() & Qt::AltModifier );
} }
else if ( isRotating() )
{
//currently rotating a selection
//snap to common angles if ctrl is pressed
rotateMouseMove( event->lastScenePos(), event->modifiers() & Qt::ControlModifier );
}
} }
void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event ) void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *event )
@ -567,12 +739,13 @@ void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *e
{ {
mIsDragging = false; mIsDragging = false;
mIsResizing = false; mIsResizing = false;
mIsRotating = false;
update(); update();
hideAlignItems(); hideAlignItems();
return; return;
} }
if ( mCurrentMouseMoveAction == Qgis::MouseHandlesAction::MoveItem ) if ( mIsDragging )
{ {
//move selected items //move selected items
startMacroCommand( tr( "Move Items" ) ); startMacroCommand( tr( "Move Items" ) );
@ -597,8 +770,10 @@ void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *e
endItemCommand( item ); endItemCommand( item );
} }
endMacroCommand(); endMacroCommand();
mIsDragging = false;
} }
else if ( mCurrentMouseMoveAction != Qgis::MouseHandlesAction::NoAction ) else if ( mIsResizing )
{ {
//resize selected items //resize selected items
startMacroCommand( tr( "Resize Items" ) ); startMacroCommand( tr( "Resize Items" ) );
@ -635,17 +810,44 @@ void QgsGraphicsViewMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent *e
endItemCommand( item ); endItemCommand( item );
} }
endMacroCommand(); endMacroCommand();
mIsResizing = false;
}
else if ( mIsRotating )
{
const QPointF itemRotationCenter = sceneTransform().map( rect().center() );
//move selected items
startMacroCommand( tr( "Rotate Items" ) );
//move all selected items
const QList<QGraphicsItem *> selectedItems = selectedSceneItems( false );
for ( QGraphicsItem *item : selectedItems )
{
if ( itemIsLocked( item ) || ( item->flags() & QGraphicsItem::ItemIsSelectable ) == 0 || itemIsGroupMember( item ) )
{
//don't move locked items, or grouped items (group takes care of that)
continue;
}
const QPointF itemCenter = item->mapToScene( itemRect( item ) ).boundingRect().center();
QTransform transform;
transform.translate( itemRotationCenter.x(), itemRotationCenter.y() );
transform.rotate( mRotationDelta );
transform.translate( -itemRotationCenter.x(), -itemRotationCenter.y() );
const QPointF rotatedItemCenter = transform.map( itemCenter );
createItemCommand( item );
rotateItem( item, mRotationDelta, rotatedItemCenter.x() - itemCenter.x(), rotatedItemCenter.y() - itemCenter.y() );
endItemCommand( item );
}
endMacroCommand();
mIsRotating = false;
} }
hideAlignItems(); hideAlignItems();
if ( mIsDragging )
{
mIsDragging = false;
}
if ( mIsResizing )
{
mIsResizing = false;
}
//reset default action //reset default action
mCurrentMouseMoveAction = Qgis::MouseHandlesAction::MoveItem; mCurrentMouseMoveAction = Qgis::MouseHandlesAction::MoveItem;
@ -721,6 +923,36 @@ void QgsGraphicsViewMouseHandles::updateHandles()
update(); update();
} }
void QgsGraphicsViewMouseHandles::rotateMouseMove( QPointF currentPosition, bool snapToCommonAngles )
{
if ( !scene() )
{
return;
}
mRotationCurrent = std::atan2( currentPosition.y() - mRotationCenter.y(), currentPosition.x() - mRotationCenter.x() ) * 180 / M_PI;
mRotationDelta = mRotationCurrent - mRotationBegin;
if ( snapToCommonAngles )
{
mRotationDelta = QgsLayoutUtils::snappedAngle( mRotationDelta );
}
const double itemRotationRadian = rotation() * M_PI / 180;
const double deltaX = ( rect().width() / 2 ) * cos( itemRotationRadian ) - ( rect().height() / 2 ) * sin( itemRotationRadian );
const double deltaY = ( rect().width() / 2 ) * sin( itemRotationRadian ) + ( rect().height() / 2 ) * cos( itemRotationRadian );
QTransform rotateTransform;
rotateTransform.translate( deltaX, deltaY );
rotateTransform.rotate( mRotationDelta );
rotateTransform.translate( -deltaX, -deltaY );
setTransform( rotateTransform );
//show current selection rotation in status bar
showStatusMessage( tr( "rotation: %1°" ).arg( QString::number( mRotationDelta, 'f', 2 ) ) );
return;
}
void QgsGraphicsViewMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMovement, bool preventSnap ) void QgsGraphicsViewMouseHandles::dragMouseMove( QPointF currentPosition, bool lockMovement, bool preventSnap )
{ {
if ( !scene() ) if ( !scene() )
@ -985,6 +1217,10 @@ void QgsGraphicsViewMouseHandles::resizeMouseMove( QPointF currentPosition, bool
break; break;
} }
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
case Qgis::MouseHandlesAction::MoveItem: case Qgis::MouseHandlesAction::MoveItem:
case Qgis::MouseHandlesAction::SelectItem: case Qgis::MouseHandlesAction::SelectItem:
case Qgis::MouseHandlesAction::NoAction: case Qgis::MouseHandlesAction::NoAction:
@ -1099,6 +1335,10 @@ QSizeF QgsGraphicsViewMouseHandles::calcCursorEdgeOffset( QPointF cursorPos )
case Qgis::MouseHandlesAction::ResizeLeftDown: case Qgis::MouseHandlesAction::ResizeLeftDown:
return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() ); return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
case Qgis::MouseHandlesAction::RotateTopLeft:
case Qgis::MouseHandlesAction::RotateTopRight:
case Qgis::MouseHandlesAction::RotateBottomLeft:
case Qgis::MouseHandlesAction::RotateBottomRight:
case Qgis::MouseHandlesAction::MoveItem: case Qgis::MouseHandlesAction::MoveItem:
case Qgis::MouseHandlesAction::SelectItem: case Qgis::MouseHandlesAction::SelectItem:
case Qgis::MouseHandlesAction::NoAction: case Qgis::MouseHandlesAction::NoAction:

View File

@ -77,11 +77,37 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
//! Returns TRUE is user is currently resizing with the handles //! Returns TRUE is user is currently resizing with the handles
bool isResizing() const { return mIsResizing; } bool isResizing() const { return mIsResizing; }
/**
* Returns TRUE is user is currently rotating with the handles.
*
* \since QGIS 4.0
*/
bool isRotating() const { return mIsRotating; }
bool shouldBlockEvent( QInputEvent *event ) const; bool shouldBlockEvent( QInputEvent *event ) const;
//! Initializes a drag operation \since QGIS 3.34 //! Initializes a drag operation \since QGIS 3.34
void startMove( QPointF sceneCoordPos ); void startMove( QPointF sceneCoordPos );
/**
* Returns TRUE if rotation functionality is enabled.
*
* Rotation is not enabled by default.
*
* \since QGIS 4.0
*/
bool isRotationEnabled() const { return mRotationEnabled; }
/**
* Sets whether rotation functionality is enabled.
*
* Rotation is not enabled by default. Subclasses must implement the
* rotateItem() method in order to support rotation.
*
* \since QGIS 4.0
*/
void setRotationEnabled( bool enable );
public slots: public slots:
//! Redraws handles when selected item size changes //! Redraws handles when selected item size changes
@ -111,6 +137,7 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
virtual QRectF itemRect( QGraphicsItem *item ) const = 0; virtual QRectF itemRect( QGraphicsItem *item ) const = 0;
virtual QRectF storedItemRect( QGraphicsItem *item ) const; virtual QRectF storedItemRect( QGraphicsItem *item ) const;
virtual void moveItem( QGraphicsItem *item, double deltaX, double deltaY ) = 0; virtual void moveItem( QGraphicsItem *item, double deltaX, double deltaY ) = 0;
virtual void rotateItem( QGraphicsItem *item, double deltaDegree, double deltaCenterX, double deltaCenterY );
virtual void previewItemMove( QGraphicsItem *item, double deltaX, double deltaY ); virtual void previewItemMove( QGraphicsItem *item, double deltaX, double deltaY );
virtual void setItemRect( QGraphicsItem *item, QRectF rect ) = 0; virtual void setItemRect( QGraphicsItem *item, QRectF rect ) = 0;
@ -158,6 +185,9 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
//! Handles resizing of items during mouse move //! Handles resizing of items during mouse move
void resizeMouseMove( QPointF currentPosition, bool lockAspect, bool fromCenter ); void resizeMouseMove( QPointF currentPosition, bool lockAspect, bool fromCenter );
//! Handles rotating of tiems during mouse move
void rotateMouseMove( QPointF currentPosition, bool snapToCommonAngles );
void setHandleSize( double size ); void setHandleSize( double size );
//! Finds out which mouse move action to choose depending on the cursor position inside the widget //! Finds out which mouse move action to choose depending on the cursor position inside the widget
@ -194,6 +224,8 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
QGraphicsView *mView = nullptr; QGraphicsView *mView = nullptr;
double mHandleSize = 10; double mHandleSize = 10;
double mRotationHandleSize = 20;
QPainterPath mRotationHandlePath;
QSizeF mCursorOffset; QSizeF mCursorOffset;
double mResizeMoveX = 0; double mResizeMoveX = 0;
@ -205,6 +237,16 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
QRectF mResizeRect; QRectF mResizeRect;
bool mRotationEnabled = false;
//! Center point around which rotation occurs
QPointF mRotationCenter;
//! The starting rotation angle from center point
double mRotationBegin = 0.0;
//! The current rotation angle from center point
double mRotationCurrent = 0.0;
//! The rotation angle delta to be applied (can be snapped to common angle)
double mRotationDelta = 0.0;
//! Start point of the last mouse move action (in scene coordinates) //! Start point of the last mouse move action (in scene coordinates)
QPointF mMouseMoveStartPos; QPointF mMouseMoveStartPos;
@ -215,6 +257,8 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
bool mIsDragging = false; bool mIsDragging = false;
//! True is user is currently resizing items //! True is user is currently resizing items
bool mIsResizing = false; bool mIsResizing = false;
//! True is user is currently rotating items
bool mIsRotating = false;
//! Position of the mouse at beginning of move/resize (in scene coordinates) //! Position of the mouse at beginning of move/resize (in scene coordinates)
QPointF mBeginMouseEventPos; QPointF mBeginMouseEventPos;
@ -232,7 +276,7 @@ class GUI_EXPORT QgsGraphicsViewMouseHandles : public QObject, public QGraphicsR
* Returns the current (zoom level dependent) tolerance to decide if mouse position is close enough to the * Returns the current (zoom level dependent) tolerance to decide if mouse position is close enough to the
* item border for resizing. * item border for resizing.
*/ */
double rectHandlerBorderTolerance(); double rectHandlerBorderTolerance() const;
//! Finds out the appropriate cursor for the current mouse position in the widget (e.g. move in the middle, resize at border) //! Finds out the appropriate cursor for the current mouse position in the widget (e.g. move in the middle, resize at border)
Qt::CursorShape cursorForPosition( QPointF itemCoordPos ); Qt::CursorShape cursorForPosition( QPointF itemCoordPos );

View File

@ -11455,6 +11455,42 @@ void TestProcessingGui::testModelGraphicsView()
// should not exist // should not exist
QVERIFY( !layerCommentItem ); QVERIFY( !layerCommentItem );
//check model bounds
scene2.updateBounds();
QRectF modelRect = scene2.sceneRect();
QGSCOMPARENEAR( modelRect.height(), 624.4, 3 ); // Sligtly higher threeshold because of various font size can marginally change the bounding rect
QGSCOMPARENEAR( modelRect.width(), 655.00, 0.01 );
QGSCOMPARENEAR( modelRect.left(), -252.0, 0.01 );
QGSCOMPARENEAR( modelRect.top(), -232.0, 0.01 );
// test model large modelRect
QgsProcessingModelAlgorithm model2;
QgsProcessingModelChildAlgorithm algc2;
algc2.setChildId( "buffer" );
algc2.setAlgorithmId( "native:buffer" );
algc2.setPosition( QPointF( 4250, 4250 ) );
QgsProcessingModelParameter param1;
param1.setParameterName( QStringLiteral( "LAYER" ) );
param1.setSize( QSizeF( 500, 400 ) );
param1.setPosition( QPointF( -250, -250 ) );
model2.addModelParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "LAYER" ) ), param );
algc2.addParameterSources( QStringLiteral( "INPUT" ), QList<QgsProcessingModelChildParameterSource>() << QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "LAYER" ) ) );
model2.addChildAlgorithm( algc2 );
QgsModelGraphicsScene scene3;
scene3.setModel( &model2 );
scene3.createItems( &model2, context );
scene3.updateBounds();
QRectF modelRect2 = scene3.sceneRect();
QGSCOMPARENEAR( modelRect2.height(), 4505.4, 3 ); // Sligtly higher threeshold because of various font size can marginally change the bounding rect
QGSCOMPARENEAR( modelRect2.width(), 4603.0, 0.01 );
QGSCOMPARENEAR( modelRect2.left(), -201.0, 0.01 );
QGSCOMPARENEAR( modelRect2.top(), -150.0, 0.01 );
QgsModelGraphicsScene scene; QgsModelGraphicsScene scene;
QVERIFY( !scene.model() ); QVERIFY( !scene.model() );