Skip to content

Commit dde3094

Browse files
authoredNov 29, 2018
Merge pull request #8560 from 3nids/locator_context
Add context menu for locator filter results
2 parents 7c43bcd + 157c8f4 commit dde3094

16 files changed

+180
-16
lines changed
 

‎python/core/auto_generated/locator/qgslocatorfilter.sip.in

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111

1212

13+
1314
class QgsLocatorResult
1415
{
1516
%Docstring
@@ -47,8 +48,31 @@ Constructor for QgsLocatorResult.
4748

4849
QString group;
4950

51+
struct ResultAction
52+
{
53+
public:
54+
ResultAction();
55+
%Docstring
56+
Constructor for ResultAction
57+
%End
58+
59+
ResultAction( int id, QString text, QString iconPath = QString() );
60+
%Docstring
61+
Constructor for ResultAction
62+
The ``id`` used to recognized the action when the result is triggered.
63+
It should be 0 or greater as otherwise, the result will be triggered
64+
normally.
65+
%End
66+
int id;
67+
QString text;
68+
QString iconPath;
69+
};
70+
71+
QList<QgsLocatorResult::ResultAction> actions;
5072
};
5173

74+
75+
5276
class QgsLocatorFilter : QObject
5377
{
5478
%Docstring
@@ -172,6 +196,16 @@ by a user. The filter subclass must implement logic here
172196
to perform the desired operation for the search result.
173197
E.g. a file search filter would open file associated with the triggered
174198
result.
199+
%End
200+
201+
virtual void triggerResultFromAction( const QgsLocatorResult &result, const int actionId );
202+
%Docstring
203+
Triggers a filter ``result`` from this filter for an entry in the context menu.
204+
The entry is identified by its ``actionId`` as specified in the result of this filter.
205+
206+
.. seealso:: :py:func:`triggerResult`
207+
208+
.. versionadded:: 3.6
175209
%End
176210

177211
virtual void clearPreviousResults();

‎python/core/auto_generated/locator/qgslocatormodel.sip.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ in order to ensure correct sorting of results by priority and match level.
3636
ResultScoreRole,
3737
ResultFilterNameRole,
3838
ResultFilterGroupSortingRole,
39+
ResultActionsRole,
3940
};
4041

4142
QgsLocatorModel( QObject *parent /TransferThis/ = 0 );

‎python/core/auto_generated/locator/qgslocatormodelbridge.sip.in

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313

1414

15+
1516
class QgsLocatorModelBridge : QObject
1617
{
1718
%Docstring
@@ -56,9 +57,9 @@ Returns true if some text to be search is pending in the queue
5657
Returns true if the a search is currently running
5758
%End
5859

59-
void triggerResult( const QModelIndex &index );
60+
void triggerResult( const QModelIndex &index, const int actionId = -1 );
6061
%Docstring
61-
Triggers the result at given index
62+
Triggers the result at given ``index`` and with optional ``actionId`` if an additional action was triggered
6263
%End
6364

6465
signals:

‎scripts/sipify.pl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,8 @@ sub detect_non_method_member{
10061006
};
10071007

10081008
# remove struct member assignment
1009-
if ( $SIP_RUN != 1 && $ACCESS[$#ACCESS] == PUBLIC && $LINE =~ m/^(\s*\w+[\w<> *&:,]* \*?\w+) = [\-\w\:\.]+(\([^()]*\))?\s*;/ ){
1009+
# https://regex101.com/r/tWRGkY/2
1010+
if ( $SIP_RUN != 1 && $ACCESS[$#ACCESS] == PUBLIC && $LINE =~ m/^(\s*\w+[\w<> *&:,]* \*?\w+) = [\-\w\:\.]+(<\w+( \*)?>)?(\([^()]*\))?\s*;/ ){
10101011
dbg_info("remove struct member assignment");
10111012
$LINE = "$1;";
10121013
}

‎src/app/locator/qgsinbuiltlocatorfilters.cpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
* *
1616
***************************************************************************/
1717

18+
#include <QToolButton>
19+
#include <QClipboard>
1820

1921
#include "qgsinbuiltlocatorfilters.h"
2022
#include "qgsproject.h"
@@ -25,8 +27,7 @@
2527
#include "qgsmaplayermodel.h"
2628
#include "qgslayoutmanager.h"
2729
#include "qgsmapcanvas.h"
28-
#include <QToolButton>
29-
#include <QClipboard>
30+
#include "qgsfeatureaction.h"
3031

3132
QgsLayerTreeLocatorFilter::QgsLayerTreeLocatorFilter( QObject *parent )
3233
: QgsLocatorFilter( parent )
@@ -400,6 +401,8 @@ void QgsAllLayersFeaturesLocatorFilter::fetchResults( const QString &string, con
400401
result.userData = QVariantList() << f.id() << preparedLayer.layerId;
401402
result.icon = preparedLayer.layerIcon;
402403
result.score = static_cast< double >( string.length() ) / result.displayString.size();
404+
405+
result.actions << QgsLocatorResult::ResultAction( OpenForm, tr( "Open form…" ) );
403406
emit resultFetched( result );
404407

405408
foundInCurrentLayer++;
@@ -413,15 +416,41 @@ void QgsAllLayersFeaturesLocatorFilter::fetchResults( const QString &string, con
413416
}
414417

415418
void QgsAllLayersFeaturesLocatorFilter::triggerResult( const QgsLocatorResult &result )
419+
{
420+
triggerResultFromAction( result, NoEntry );
421+
}
422+
423+
void QgsAllLayersFeaturesLocatorFilter::triggerResultFromAction( const QgsLocatorResult &result, const int actionId )
416424
{
417425
QVariantList dataList = result.userData.toList();
418-
QgsFeatureId id = dataList.at( 0 ).toLongLong();
426+
QgsFeatureId fid = dataList.at( 0 ).toLongLong();
419427
QString layerId = dataList.at( 1 ).toString();
420428
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProject::instance()->mapLayer( layerId ) );
421429
if ( !layer )
422430
return;
423431

424-
QgisApp::instance()->mapCanvas()->zoomToFeatureIds( layer, QgsFeatureIds() << id );
432+
if ( actionId == OpenForm )
433+
{
434+
QgsFeature f;
435+
QgsFeatureRequest request;
436+
request.setFilterFid( fid );
437+
bool fetched = layer->getFeatures( request ).nextFeature( f );
438+
if ( !fetched )
439+
return;
440+
QgsFeatureAction action( tr( "Attributes changed" ), f, layer, QString(), -1, QgisApp::instance() );
441+
if ( layer->isEditable() )
442+
{
443+
action.editFeature( false );
444+
}
445+
else
446+
{
447+
action.viewFeatureForm();
448+
}
449+
}
450+
else
451+
{
452+
QgisApp::instance()->mapCanvas()->zoomToFeatureIds( layer, QgsFeatureIds() << fid );
453+
}
425454
}
426455

427456
//

‎src/app/locator/qgsinbuiltlocatorfilters.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ class APP_EXPORT QgsAllLayersFeaturesLocatorFilter : public QgsLocatorFilter
119119
Q_OBJECT
120120

121121
public:
122+
enum ContextMenuEntry
123+
{
124+
NoEntry,
125+
OpenForm
126+
};
127+
122128
struct PreparedLayer
123129
{
124130
public:
@@ -128,7 +134,7 @@ class APP_EXPORT QgsAllLayersFeaturesLocatorFilter : public QgsLocatorFilter
128134
QString layerName;
129135
QString layerId;
130136
QIcon layerIcon;
131-
} ;
137+
};
132138

133139
QgsAllLayersFeaturesLocatorFilter( QObject *parent = nullptr );
134140
QgsAllLayersFeaturesLocatorFilter *clone() const override;
@@ -140,6 +146,7 @@ class APP_EXPORT QgsAllLayersFeaturesLocatorFilter : public QgsLocatorFilter
140146
void prepare( const QString &string, const QgsLocatorContext &context ) override;
141147
void fetchResults( const QString &string, const QgsLocatorContext &context, QgsFeedback *feedback ) override;
142148
void triggerResult( const QgsLocatorResult &result ) override;
149+
void triggerResultFromAction( const QgsLocatorResult &result, const int actionId ) override;
143150

144151
private:
145152
int mMaxResultsPerLayer = 6;

‎src/core/locator/qgslocatorfilter.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ QgsLocatorFilter::Flags QgsLocatorFilter::flags() const
3333
return nullptr;
3434
}
3535

36+
void QgsLocatorFilter::triggerResultFromAction( const QgsLocatorResult &result, const int actionId )
37+
{
38+
Q_UNUSED( result );
39+
Q_UNUSED( actionId );
40+
}
41+
3642
bool QgsLocatorFilter::stringMatches( const QString &candidate, const QString &search )
3743
{
3844
return !search.isEmpty() && candidate.contains( search, Qt::CaseInsensitive );

‎src/core/locator/qgslocatorfilter.h

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
#ifndef QGSLOCATORFILTER_H
1919
#define QGSLOCATORFILTER_H
2020

21+
#include <QAction>
22+
#include <QIcon>
23+
#include <QString>
24+
#include <QVariant>
25+
2126
#include "qgis_core.h"
2227
#include "qgslocatorcontext.h"
2328
#include "qgslogger.h"
24-
#include <QString>
25-
#include <QVariant>
26-
#include <QIcon>
2729

2830
class QgsFeedback;
2931
class QgsLocatorFilter;
@@ -87,12 +89,50 @@ class CORE_EXPORT QgsLocatorResult
8789
* If left as empty string, this means that results are all shown without being grouped.
8890
* If a group is given, the results will be grouped by \a group under a header.
8991
* \note This should be translated.
90-
* \since 3.2
92+
* \since QGIS 3.2
9193
*/
9294
QString group = QString();
9395

96+
/**
97+
* The ResultAction stores basic information for additional
98+
* actions to be used in a locator widget for the result.
99+
* They could be used in a context menu for instance.
100+
* \since QGIS 3.6
101+
*/
102+
struct CORE_EXPORT ResultAction
103+
{
104+
public:
105+
//! Constructor for ResultAction
106+
ResultAction() = default;
107+
108+
/**
109+
* Constructor for ResultAction
110+
* The \a id used to recognized the action when the result is triggered.
111+
* It should be 0 or greater as otherwise, the result will be triggered
112+
* normally.
113+
*/
114+
ResultAction( int id, QString text, QString iconPath = QString() )
115+
: id( id )
116+
, text( text )
117+
, iconPath( iconPath )
118+
{}
119+
int id = -1;
120+
QString text;
121+
QString iconPath;
122+
};
123+
124+
/**
125+
* Additional actions to be used in a locator widget
126+
* for the given result. They could be displayed in
127+
* a context menu.
128+
* \since QGIS 3.6
129+
*/
130+
QList<QgsLocatorResult::ResultAction> actions;
94131
};
95132

133+
Q_DECLARE_METATYPE( QgsLocatorResult::ResultAction )
134+
135+
96136
/**
97137
* \class QgsLocatorFilter
98138
* \ingroup core
@@ -209,6 +249,14 @@ class CORE_EXPORT QgsLocatorFilter : public QObject
209249
*/
210250
virtual void triggerResult( const QgsLocatorResult &result ) = 0;
211251

252+
/**
253+
* Triggers a filter \a result from this filter for an entry in the context menu.
254+
* The entry is identified by its \a actionId as specified in the result of this filter.
255+
* \see triggerResult()
256+
* \since QGIS 3.6
257+
*/
258+
virtual void triggerResultFromAction( const QgsLocatorResult &result, const int actionId );
259+
212260
/**
213261
* This method will be called on main thread on the original filter (not a clone)
214262
* before fetching results or before triggering a result to clear any change made

‎src/core/locator/qgslocatormodel.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ QVariant QgsLocatorModel::data( const QModelIndex &index, int role ) const
160160
return 1;
161161
else
162162
return 0;
163+
164+
case ResultActionsRole:
165+
return QVariant::fromValue( mResults.at( index.row() ).result.actions );
163166
}
164167

165168
return QVariant();
@@ -188,6 +191,7 @@ QHash<int, QByteArray> QgsLocatorModel::roleNames() const
188191
roles[ResultScoreRole] = "ResultScore";
189192
roles[ResultFilterNameRole] = "ResultFilterName";
190193
roles[ResultFilterGroupSortingRole] = "ResultFilterGroupSorting";
194+
roles[ResultActionsRole] = "ResultContextMenuActions";
191195
roles[Qt::DisplayRole] = "Text";
192196
return roles;
193197
}

‎src/core/locator/qgslocatormodel.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class CORE_EXPORT QgsLocatorModel : public QAbstractTableModel
5656
ResultScoreRole, //!< Result match score, used by QgsLocatorProxyModel for sorting roles.
5757
ResultFilterNameRole, //!< Associated filter name which created the result
5858
ResultFilterGroupSortingRole, //!< Group results within the same filter results
59+
ResultActionsRole, //!< The actions to be shown for the given result in a context menu
5960
};
6061

6162
/**

‎src/core/locator/qgslocatormodelbridge.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,17 @@ bool QgsLocatorModelBridge::isRunning() const
3737
return mIsRunning;
3838
}
3939

40-
void QgsLocatorModelBridge::triggerResult( const QModelIndex &index )
40+
void QgsLocatorModelBridge::triggerResult( const QModelIndex &index, const int actionId )
4141
{
4242
mLocator->clearPreviousResults();
4343
QgsLocatorResult result = mProxyModel->data( index, QgsLocatorModel::ResultDataRole ).value< QgsLocatorResult >();
4444
if ( result.filter )
45-
result.filter->triggerResult( result );
45+
{
46+
if ( actionId >= 0 )
47+
result.filter->triggerResultFromAction( result, actionId );
48+
else
49+
result.filter->triggerResult( result );
50+
}
4651
}
4752

4853
void QgsLocatorModelBridge::setIsRunning( bool isRunning )

‎src/core/locator/qgslocatormodelbridge.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include "qgscoordinatereferencesystem.h"
2525
#include "qgsrectangle.h"
2626

27+
class QAction;
28+
2729
class QgsLocatorResult;
2830
class QgsLocator;
2931
class QgsLocatorContext;
@@ -61,8 +63,8 @@ class CORE_EXPORT QgsLocatorModelBridge : public QObject
6163
//! Returns true if the a search is currently running
6264
bool isRunning() const;
6365

64-
//! Triggers the result at given index
65-
void triggerResult( const QModelIndex &index );
66+
//! Triggers the result at given \a index and with optional \a actionId if an additional action was triggered
67+
void triggerResult( const QModelIndex &index, const int actionId = -1 );
6668

6769
signals:
6870
//! Emitted when a result is added

‎src/gui/locator/qgslocatorwidget.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,12 @@ QgsLocatorWidget::QgsLocatorWidget( QWidget *parent )
7474
mResultsView->setUniformRowHeights( true );
7575
mResultsView->setIconSize( QSize( 16, 16 ) );
7676
mResultsView->recalculateSize();
77+
mResultsView->setContextMenuPolicy( Qt::CustomContextMenu );
7778

7879
connect( mLineEdit, &QLineEdit::textChanged, this, &QgsLocatorWidget::scheduleDelayedPopup );
7980
connect( mResultsView, &QAbstractItemView::activated, this, &QgsLocatorWidget::acceptCurrentEntry );
81+
connect( mResultsView, &QAbstractItemView::customContextMenuRequested, this, &QgsLocatorWidget::showContextMenu );
82+
8083
connect( mModelBridge, &QgsLocatorModelBridge::resultAdded, this, &QgsLocatorWidget::resultAdded );
8184
connect( mModelBridge, &QgsLocatorModelBridge::isRunningChanged, this, [ = ]() {mLineEdit->setShowSpinner( mModelBridge->isRunning() );} );
8285
connect( mModelBridge, & QgsLocatorModelBridge::resultsCleared, this, [ = ]() {mHasSelectedResult = false;} );
@@ -172,6 +175,25 @@ void QgsLocatorWidget::resultAdded()
172175
}
173176
}
174177

178+
void QgsLocatorWidget::showContextMenu( const QPoint &point )
179+
{
180+
QModelIndex index = mResultsView->indexAt( point );
181+
if ( !index.isValid() )
182+
return;
183+
184+
const QList<QgsLocatorResult::ResultAction> actions = mResultsView->model()->data( index, QgsLocatorModel::ResultActionsRole ).value<QList<QgsLocatorResult::ResultAction>>();
185+
QMenu *contextMenu = new QMenu( mResultsView );
186+
for ( auto resultAction : actions )
187+
{
188+
QAction *menuAction = new QAction( resultAction.text, contextMenu );
189+
if ( !resultAction.iconPath.isEmpty() )
190+
menuAction->setIcon( QIcon( resultAction.iconPath ) );
191+
connect( menuAction, &QAction::triggered, this, [ = ]() {mModelBridge->triggerResult( index, resultAction.id );} );
192+
contextMenu->addAction( menuAction );
193+
}
194+
contextMenu->exec( mResultsView->viewport()->mapToGlobal( point ) );
195+
}
196+
175197
void QgsLocatorWidget::performSearch()
176198
{
177199
mPopupTimer.stop();

‎src/gui/locator/qgslocatorwidget.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class GUI_EXPORT QgsLocatorWidget : public QWidget
9494
void configMenuAboutToShow();
9595
void scheduleDelayedPopup();
9696
void resultAdded();
97+
void showContextMenu( const QPoint &point );
9798

9899
private:
99100
QgsLocatorModelBridge *mModelBridge = nullptr;

‎tests/code_layout/sipifyheader.expected.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ typedef QtClass<QVariant> QtClassQVariantBase;
9393
QString mName;
9494
int mCount;
9595
QgsMapLayer *mLayer;
96+
QList<QAction *> contextMenuActions;
9697
};
9798

9899
static const int MONTHS;

‎tests/code_layout/sipifyheader.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ class CORE_EXPORT QgsSipifyHeader : public QtClass<QVariant>, private Ui::QgsBas
134134
QString mName;
135135
int mCount = 100;
136136
QgsMapLayer *mLayer = nullptr;
137+
QList<QAction *> contextMenuActions = QList<QAction *>();
137138
};
138139

139140
static const int MONTHS = 60 * 60 * 24 * 30; // something

0 commit comments

Comments
 (0)
Please sign in to comment.