Skip to content

Commit

Permalink
Port selection actions to layout
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Oct 6, 2017
1 parent de96530 commit b494a71
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 1 deletion.
23 changes: 23 additions & 0 deletions python/gui/layout/qgslayoutview.sip
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,29 @@ class QgsLayoutView: QGraphicsView

void emitZoomLevelChanged();


void selectAll();
%Docstring
Selects all items in the view.
.. seealso:: deselectAll()
.. seealso:: invertSelection()
%End

void deselectAll();
%Docstring
Deselects all items in the view.
.. seealso:: selectAll()
.. seealso:: invertSelection()
%End

void invertSelection();
%Docstring
Inverts the current selection, selecting deselected items
and deselecting and selected items.
.. seealso:: selectAll()
.. seealso:: deselectAll()
%End

void viewChanged();
%Docstring
Updates associated rulers and other widgets after view extent or zoom has changed.
Expand Down
4 changes: 4 additions & 0 deletions src/app/layout/qgslayoutdesignerdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ QgsLayoutDesignerDialog::QgsLayoutDesignerDialog( QWidget *parent, Qt::WindowFla
connect( mActionZoomActual, &QAction::triggered, mView, &QgsLayoutView::zoomActual );
connect( mActionZoomToWidth, &QAction::triggered, mView, &QgsLayoutView::zoomWidth );

connect( mActionSelectAll, &QAction::triggered, mView, &QgsLayoutView::selectAll );
connect( mActionDeselectAll, &QAction::triggered, mView, &QgsLayoutView::deselectAll );
connect( mActionInvertSelection, &QAction::triggered, mView, &QgsLayoutView::invertSelection );

connect( mActionAddPages, &QAction::triggered, this, &QgsLayoutDesignerDialog::addPages );

//create status bar labels
Expand Down
75 changes: 75 additions & 0 deletions src/gui/layout/qgslayoutview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,81 @@ void QgsLayoutView::emitZoomLevelChanged()
emit zoomLevelChanged();
}

void QgsLayoutView::selectAll()
{
if ( !currentLayout() )
{
return;
}

//select all items in layout
QgsLayoutItem *focusedItem = nullptr;
const QList<QGraphicsItem *> itemList = currentLayout()->items();
for ( QGraphicsItem *graphicsItem : itemList )
{
QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( graphicsItem );
if ( item && !paperItem )
{
if ( !item->isLocked() )
{
item->setSelected( true );
if ( !focusedItem )
focusedItem = item;
}
else
{
//deselect all locked items
item->setSelected( false );
}
}
}
emit itemFocused( focusedItem );
}

void QgsLayoutView::deselectAll()
{
if ( !currentLayout() )
{
return;
}

currentLayout()->deselectAll();
}

void QgsLayoutView::invertSelection()
{
if ( !currentLayout() )
{
return;
}

QgsLayoutItem *focusedItem = nullptr;
//check all items in layout
const QList<QGraphicsItem *> itemList = currentLayout()->items();
for ( QGraphicsItem *graphicsItem : itemList )
{
QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
QgsLayoutItemPage *paperItem = dynamic_cast<QgsLayoutItemPage *>( graphicsItem );
if ( item && !paperItem )
{
//flip selected state for items (and deselect any locked items)
if ( item->isSelected() || item->isLocked() )
{
item->setSelected( false );
}
else
{
item->setSelected( true );
if ( !focusedItem )
focusedItem = item;
}
}
}
if ( focusedItem )
emit itemFocused( focusedItem );
}

void QgsLayoutView::mousePressEvent( QMouseEvent *event )
{
mSnapMarker->setVisible( false );
Expand Down
27 changes: 27 additions & 0 deletions src/gui/layout/qgslayoutview.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,33 @@ class GUI_EXPORT QgsLayoutView: public QGraphicsView
// methods also adds noise to the API.
void emitZoomLevelChanged();

// Why are these select methods in the view and not in the scene (QgsLayout)?
// Well, in my opinion selections are purely a GUI concept. Ideally
// NONE of the selection handling would be done in core, but we're restrained
// by the QGraphicsScene API here.

/**
* Selects all items in the view.
* \see deselectAll()
* \see invertSelection()
*/
void selectAll();

/**
* Deselects all items in the view.
* \see selectAll()
* \see invertSelection()
*/
void deselectAll();

/**
* Inverts the current selection, selecting deselected items
* and deselecting and selected items.
* \see selectAll()
* \see deselectAll()
*/
void invertSelection();

/**
* Updates associated rulers and other widgets after view extent or zoom has changed.
* This should be called after calling any of the QGraphicsView
Expand Down
70 changes: 70 additions & 0 deletions src/ui/layout/qgslayoutdesignerbase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@
</property>
<addaction name="mActionUndo"/>
<addaction name="mActionRedo"/>
<addaction name="separator"/>
<addaction name="mActionSelectAll"/>
<addaction name="mActionDeselectAll"/>
<addaction name="mActionInvertSelection"/>
<addaction name="mActionSelectNextBelow"/>
<addaction name="mActionSelectNextAbove"/>
</widget>
<addaction name="mLayoutMenu"/>
<addaction name="menuEdit"/>
Expand Down Expand Up @@ -466,6 +472,70 @@
<string>Ctrl+Alt+;</string>
</property>
</action>
<action name="mActionDeselectAll">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionDeselectAll.svg</normaloff>:/images/themes/default/mActionDeselectAll.svg</iconset>
</property>
<property name="text">
<string>D&amp;eselect All</string>
</property>
<property name="toolTip">
<string>Deselect all</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+A</string>
</property>
</action>
<action name="mActionSelectAll">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionSelectAll.svg</normaloff>:/images/themes/default/mActionSelectAll.svg</iconset>
</property>
<property name="text">
<string>&amp;Select All</string>
</property>
<property name="toolTip">
<string>Select all items</string>
</property>
<property name="shortcut">
<string>Ctrl+A</string>
</property>
</action>
<action name="mActionInvertSelection">
<property name="icon">
<iconset resource="../../../images/images.qrc">
<normaloff>:/images/themes/default/mActionInvertSelection.svg</normaloff>:/images/themes/default/mActionInvertSelection.svg</iconset>
</property>
<property name="text">
<string>&amp;Invert Selection</string>
</property>
<property name="toolTip">
<string>Invert selection</string>
</property>
</action>
<action name="mActionSelectNextBelow">
<property name="text">
<string>Select Next Item &amp;Below</string>
</property>
<property name="toolTip">
<string>Select next item below</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+[</string>
</property>
</action>
<action name="mActionSelectNextAbove">
<property name="text">
<string>Select Next Item &amp;Above</string>
</property>
<property name="toolTip">
<string>Select next item above</string>
</property>
<property name="shortcut">
<string>Ctrl+Alt+]</string>
</property>
</action>
</widget>
<resources>
<include location="../../../images/images.qrc"/>
Expand Down
110 changes: 109 additions & 1 deletion tests/src/python/test_qgslayoutview.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@

import qgis # NOQA

from qgis.core import QgsProject, QgsLayout, QgsUnitTypes
from qgis.core import (QgsProject,
QgsLayout,
QgsUnitTypes,
QgsLayoutItemMap)
from qgis.gui import QgsLayoutView
from qgis.PyQt.QtCore import QRectF
from qgis.PyQt.QtGui import QTransform
from qgis.PyQt.QtTest import QSignalSpy

from qgis.testing import start_app, unittest

Expand Down Expand Up @@ -71,6 +75,110 @@ def testLayoutScalePixels(self):
view.setZoomLevel(0.5)
self.assertEqual(view.transform().m11(), 0.5)

def testSelectAll(self):
p = QgsProject()
l = QgsLayout(p)

# add some items
item1 = QgsLayoutItemMap(l)
l.addItem(item1)
item2 = QgsLayoutItemMap(l)
l.addItem(item2)
item3 = QgsLayoutItemMap(l)
item3.setLocked(True)
l.addItem(item3)

view = QgsLayoutView()
# no layout, no crash
view.selectAll()

view.setCurrentLayout(l)

focused_item_spy = QSignalSpy(view.itemFocused)

view.selectAll()
self.assertTrue(item1.isSelected())
self.assertTrue(item2.isSelected())
self.assertFalse(item3.isSelected()) # locked

self.assertEqual(len(focused_item_spy), 1)

item3.setSelected(True) # locked item selection should be cleared
view.selectAll()
self.assertTrue(item1.isSelected())
self.assertTrue(item2.isSelected())
self.assertFalse(item3.isSelected()) # locked

def testDeselectAll(self):
p = QgsProject()
l = QgsLayout(p)

# add some items
item1 = QgsLayoutItemMap(l)
l.addItem(item1)
item2 = QgsLayoutItemMap(l)
l.addItem(item2)
item3 = QgsLayoutItemMap(l)
item3.setLocked(True)
l.addItem(item3)

view = QgsLayoutView()
# no layout, no crash
view.deselectAll()

view.setCurrentLayout(l)

focused_item_spy = QSignalSpy(view.itemFocused)

view.deselectAll()
self.assertFalse(item1.isSelected())
self.assertFalse(item2.isSelected())
self.assertFalse(item3.isSelected())

self.assertEqual(len(focused_item_spy), 1)

item1.setSelected(True)
item2.setSelected(True)
item3.setSelected(True)
view.deselectAll()
self.assertFalse(item1.isSelected())
self.assertFalse(item2.isSelected())
self.assertFalse(item3.isSelected())

def testInvertSelection(self):
p = QgsProject()
l = QgsLayout(p)

# add some items
item1 = QgsLayoutItemMap(l)
l.addItem(item1)
item2 = QgsLayoutItemMap(l)
l.addItem(item2)
item3 = QgsLayoutItemMap(l)
item3.setLocked(True)
l.addItem(item3)

view = QgsLayoutView()
# no layout, no crash
view.invertSelection()

view.setCurrentLayout(l)

focused_item_spy = QSignalSpy(view.itemFocused)

view.invertSelection()
self.assertTrue(item1.isSelected())
self.assertTrue(item2.isSelected())
self.assertFalse(item3.isSelected()) # locked

self.assertEqual(len(focused_item_spy), 1)

item3.setSelected(True) # locked item selection should be cleared
view.invertSelection()
self.assertFalse(item1.isSelected())
self.assertFalse(item2.isSelected())
self.assertFalse(item3.isSelected()) # locked


if __name__ == '__main__':
unittest.main()

0 comments on commit b494a71

Please sign in to comment.