Skip to content
This repository was archived by the owner on Mar 11, 2026. It is now read-only.

Commit 2473cef

Browse files
committed
Allow searching on two strings.
1 parent 3d51abf commit 2473cef

8 files changed

Lines changed: 532 additions & 145 deletions

File tree

doc/layersearch.jpg

9.91 KB
Loading

doc/layersearch2.jpg

73.1 KB
Loading

index.html

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,24 @@ <h1>Search Layers Plugin</h1>
4141
<p>Search Layers is located in the QGIS Plugins menu under <em>"Plugins-&gt;Search Layers-&gt;Search Layers"</em> or by selecting the tool bar icon. <img alt="Toolbar Icon" src="icon.png" /></p>
4242
<p>The following dialog box is displayed when "Search Layers" is launched.</p>
4343
<p><img alt="Search Layers Dialog" src="doc/layersearch.jpg" /></p>
44-
<p>Under <strong>Search String</strong>, enter the search string. <strong>Search Layers</strong> specifies whether the search will be on <em>&lt;All Layers&gt;</em>, <em>&lt;Selected layers&gt;</em>, or on any of the vector layers in the QGIS project. If a specific layer is selected then <strong>Search Fields</strong> will be enabled and by default <em>&lt;All Fields&gt;</em> will be selected, but any field can be selected from the layer and the search will only search on that layer and field.</p>
45-
<p><strong>Comparison</strong> is the matching criteria and is as follows.</p>
44+
<p>Under <strong>Search criteria</strong>, you can enter up to two search strings. The <strong>NOT</strong> check box will negate the search results for the applicable string.</p>
45+
<p>Under each search string text box a drop down menu specifies how the search string is to match the contents in the attribute table. The options are: </p>
4646
<ul>
47-
<li><strong>=</strong> - This requires an exact match including case.</li>
48-
<li><strong>Contains</strong> - This performs a case independent search in which a match is made if a field contains the search string.</li>
49-
<li><strong>Begins with</strong> - This is a case independent search in which the search finds any field that begins with the search string.</li>
47+
<li><strong>equals</strong> - This requires an exact match.</li>
48+
<li><strong>contains</strong> - This looks for a field that contains the search string.</li>
49+
<li><strong>begins with</strong> - This search finds any field that begins with the search string.</li>
50+
<li><strong>ends with</strong> - This search finds any field that ends with the search string.</li>
5051
</ul>
51-
<p>Click the <strong>Search</strong> button to begin the search. In the case of a large data set, clicking on <strong>Stop</strong> will halt the process. Note that the plugin stops after finding 1500 matches.</p>
52-
<p>When matches are found and clicked on, the features are highlighted on the map. If <strong>Automatically zoom to selected features</strong> is checked, QGIS also zooms to the selected features. The matches can be examined even before the search process has been completed.</p></body>
52+
<p>By default the search is case independent, but by checking the <strong>Case sensitive</strong> check box the search string must match the case of the text.</p>
53+
<p><strong>Search Layers</strong> specifies whether the search will be on <em>&lt;All Layers&gt;</em>, <em>&lt;Selected layers&gt;</em>, or on any of the vector layers in the QGIS project. If a specific layer is selected then <strong>Search Fields</strong> will be enabled and by default <em>&lt;All Fields&gt;</em> will be selected, but any field can be selected from the layer and the search will only search on that layer and field.</p>
54+
<p>It is possible that a search string could match the contents in one or more attribute fields. By checking <strong>Report one result per feature</strong>, only the first match per feature will be reported.</p>
55+
<p>When two search strings are used checking <strong>Constrain two search strings to match within an attribute field rather than across attribute fields</strong> will constrain the search criteria to match within an attribute field; otherwise, one string may match one attribute field and the other string may match another attribute field. The results are either <strong>AND</strong>ed or <strong>OR</strong>ed together. Here is an axample of a two string search.</p>
56+
<p><img alt="Search Layers Dialog" src="doc/layersearch2.jpg" /></p>
57+
<p>Click the <strong>Search</strong> button to begin the search. In the case of a large data set, clicking on <strong>Abort</strong> will halt the process. Note that the plugin stops after finding 1500 matches.</p>
58+
<p>When matches are found and clicked on the feature(s) will be highlighted. You can you Shift-&gt;Click to highlight a range of features, Ctrl-&gt;Click to toggle whether a feature is highlighted or not. The drop down menu <strong>Action when selecting features below</strong> controls one of the following motion actions. </p>
59+
<ul>
60+
<li><strong>Do nothing</strong> - No action takes place.</li>
61+
<li><strong>Zoom to selected features</strong> - QGIS will zoom in on the selected features.</li>
62+
<li><strong>Pan to selected features</strong> - QGIS will pan to the selected features.</li>
63+
</ul>
64+
<p>Note that the search is very quick when selecting a single vector layer under <strong>Search Layers</strong> and a single field under <strong>Search Fields</strong>. If this is not the case regular expression searches are used and are very slow. In the future this may change.</p></body>

metadata.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name=Search Layers
33
qgisMinimumVersion=3.0
44
description=Enhanced textual searching on all QGIS layers and fields.
5-
version=3.0.2
5+
version=3.0.3
66
author=C Hamilton
77
email=adenaculture@gmail.com
88
about=The Search Layers plugin features enhanced textual vector layer searching in QGIS. This plugin can search across all layers and all fields.
@@ -15,6 +15,7 @@ icon=icon.png
1515
experimental=False
1616
deprecated=False
1717
changelog=
18+
3.0.3 - Add second search string with AND, OR, and NOT operators
1819
3.0.2 - Highlight a range of features, add check box to automatically zoom to selected
1920
3.0.1 - Added Feature Id in the result table.
2021
3.0.0 First release for QGIS 3

readme.md

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,31 @@ The following dialog box is displayed when "Search Layers" is launched.
88

99
![Search Layers Dialog](doc/layersearch.jpg)
1010

11-
Under **Search String**, enter the search string. **Search Layers** specifies whether the search will be on *&lt;All Layers&gt;*, *&lt;Selected layers&gt;*, or on any of the vector layers in the QGIS project. If a specific layer is selected then **Search Fields** will be enabled and by default *&lt;All Fields&gt;* will be selected, but any field can be selected from the layer and the search will only search on that layer and field.
11+
Under **Search criteria**, you can enter up to two search strings. The **NOT** check box will negate the search results for the applicable string.
1212

13-
**Comparison** is the matching criteria and is as follows.
13+
Under each search string text box a drop down menu specifies how the search string is to match the contents in the attribute table. The options are:
1414

15-
* **=** - This requires an exact match including case.
16-
* **Contains** - This performs a case independent search in which a match is made if a field contains the search string.
17-
* **Begins with** - This is a case independent search in which the search finds any field that begins with the search string.
15+
* **equals** - This requires an exact match.
16+
* **contains** - This looks for a field that contains the search string.
17+
* **begins with** - This search finds any field that begins with the search string.
18+
* **ends with** - This search finds any field that ends with the search string.
1819

19-
Click the **Search** button to begin the search. In the case of a large data set, clicking on **Stop** will halt the process. Note that the plugin stops after finding 1500 matches.
20+
By default the search is case independent, but by checking the **Case sensitive** check box the search string must match the case of the text.
2021

21-
When matches are found and clicked on, the features are highlighted on the map. If **Automatically zoom to selected features** is checked, QGIS also zooms to the selected features. The matches can be examined even before the search process has been completed.
22+
**Search Layers** specifies whether the search will be on *&lt;All Layers&gt;*, *&lt;Selected layers&gt;*, or on any of the vector layers in the QGIS project. If a specific layer is selected then **Search Fields** will be enabled and by default *&lt;All Fields&gt;* will be selected, but any field can be selected from the layer and the search will only search on that layer and field.
2223

24+
It is possible that a search string could match the contents in one or more attribute fields. By checking **Report one result per feature**, only the first match per feature will be reported.
25+
26+
When two search strings are used checking **Constrain two search strings to match within an attribute field rather than across attribute fields** will constrain the search criteria to match within an attribute field; otherwise, one string may match one attribute field and the other string may match another attribute field. The results are either **AND**ed or **OR**ed together. Here is an axample of a two string search.
27+
28+
![Search Layers Dialog](doc/layersearch2.jpg)
29+
30+
Click the **Search** button to begin the search. In the case of a large data set, clicking on **Abort** will halt the process. Note that the plugin stops after finding 1500 matches.
31+
32+
When matches are found and clicked on the feature(s) will be highlighted. You can you Shift->Click to highlight a range of features, Ctrl->Click to toggle whether a feature is highlighted or not. The drop down menu **Action when selecting features below** controls one of the following motion actions.
33+
34+
* **Do nothing** - No action takes place.
35+
* **Zoom to selected features** - QGIS will zoom in on the selected features.
36+
* **Pan to selected features** - QGIS will pan to the selected features.
37+
38+
Note that the search is very quick when selecting a single vector layer under **Search Layers** and a single field under **Search Fields**. If this is not the case regular expression searches are used and are very slow. In the future this may change.

searchDialog.py

Lines changed: 70 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import re
33

44
from qgis.PyQt.uic import loadUiType
5-
from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QTableWidgetItem
6-
from qgis.PyQt.QtCore import QThread
5+
from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QTableWidget, QTableWidgetItem
6+
from qgis.PyQt.QtCore import Qt, QThread
77

88
from qgis.core import QgsVectorLayer, Qgis, QgsProject, QgsMapLayer
99
from .searchWorker import Worker
@@ -23,26 +23,26 @@ def __init__(self, iface, parent):
2323
# Notify us when vector items ared added and removed in QGIS
2424
QgsProject.instance().layersAdded.connect(self.updateLayers)
2525
QgsProject.instance().layersRemoved.connect(self.updateLayers)
26-
26+
2727
self.doneButton.clicked.connect(self.closeDialog)
2828
self.stopButton.clicked.connect(self.killWorker)
2929
self.searchButton.clicked.connect(self.runSearch)
3030
self.clearButton.clicked.connect(self.clearResults)
3131
self.layerListComboBox.activated.connect(self.layerSelected)
3232
self.searchFieldComboBox.addItems(['<All Fields>'])
3333
self.maxResults = 1500
34+
self.resultsTable.setEditTriggers(QTableWidget.NoEditTriggers)
3435
self.resultsTable.setColumnCount(4)
35-
self.resultsTable.setSortingEnabled(False)
36-
self.resultsTable.setHorizontalHeaderLabels(['Value','Layer','Field','Feature Id'])
36+
self.resultsTable.setSortingEnabled(True)
37+
self.resultsTable.setHorizontalHeaderLabels(['Layer','Feature ID','Field','Search Results'])
3738
self.resultsTable.setSelectionBehavior(QAbstractItemView.SelectRows)
38-
self.comparisonComboBox.addItems(['=','contains','begins with'])
3939
self.resultsTable.itemSelectionChanged.connect(self.select_feature)
4040
self.worker = None
4141

4242
def closeDialog(self):
4343
'''Close the dialog box when the Close button is pushed'''
4444
self.hide()
45-
45+
4646
def updateLayers(self):
4747
'''Called when a layer has been added or deleted in QGIS.
4848
It forces the dialog to reload.'''
@@ -51,7 +51,7 @@ def updateLayers(self):
5151
if self.isVisible():
5252
self.populateLayerListComboBox()
5353
self.clearResults()
54-
54+
5555
def select_feature(self):
5656
'''A feature has been selected from the list so we need to select
5757
and zoom to it'''
@@ -68,31 +68,36 @@ def select_feature(self):
6868
selectedLayer = None
6969
for item in selectedItems:
7070
selectedRow = item.row()
71-
selectedLayer = self.results[selectedRow][0]
72-
selectedFeature = self.results[selectedRow][1]
71+
foundid = self.resultsTable.item(selectedRow, 0).data(Qt.UserRole)
72+
selectedLayer = self.results[foundid][0]
73+
selectedFeature = self.results[foundid][1]
7374
selectedLayer.select(selectedFeature.id())
7475
# Zoom to the selected feature
75-
if selectedLayer and self.autoZoomCheckBox.isChecked():
76-
self.canvas.zoomToSelected(selectedLayer)
77-
76+
zoom_pan = self.zoomPanComboBox.currentIndex()
77+
if selectedLayer and zoom_pan:
78+
if zoom_pan == 1:
79+
self.canvas.zoomToSelected(selectedLayer)
80+
else:
81+
self.canvas.panToSelected(selectedLayer)
82+
7883
def layerSelected(self):
7984
'''The user has made a selection so we need to initialize other
8085
parts of the dialog box'''
8186
self.initFieldList()
82-
87+
8388
def showEvent(self, event):
8489
'''The dialog is being shown. We need to initialize it.'''
8590
super(LayerSearchDialog, self).showEvent(event)
8691
self.populateLayerListComboBox()
87-
92+
8893
def populateLayerListComboBox(self):
8994
'''Find all the vector layers and add them to the layer list
9095
that the user can select. In addition the user can search on all
9196
layers or all selected layers.'''
9297
layerlist = ['<All Layers>','<Selected Layers>']
9398
self.searchLayers = [None, None] # This is same size as layerlist
9499
layers = QgsProject.instance().mapLayers().values()
95-
100+
96101
for layer in layers:
97102
if layer.type() == QgsMapLayer.VectorLayer:
98103
layerlist.append(layer.name())
@@ -101,7 +106,7 @@ def populateLayerListComboBox(self):
101106
self.layerListComboBox.clear()
102107
self.layerListComboBox.addItems(layerlist)
103108
self.initFieldList()
104-
109+
105110
def initFieldList(self):
106111
selectedLayer = self.layerListComboBox.currentIndex()
107112
self.searchFieldComboBox.clear()
@@ -113,21 +118,41 @@ def initFieldList(self):
113118
else:
114119
self.searchFieldComboBox.setCurrentIndex(0)
115120
self.searchFieldComboBox.setEnabled(False)
116-
121+
122+
def initSearchResultsTable(self):
123+
self.clearResults()
124+
if self.is_single_string or self.two_string_match_single:
125+
self.resultsTable.setColumnCount(4)
126+
self.resultsTable.setHorizontalHeaderLabels(['Layer','Feature ID','Field','Results'])
127+
else:
128+
self.resultsTable.setColumnCount(6)
129+
self.resultsTable.setHorizontalHeaderLabels(['Layer','Feature ID','Field 1','Results 1','Field 2','Results 2'])
130+
117131
def runSearch(self):
118132
'''Called when the user pushes the Search button'''
119133
selectedLayer = self.layerListComboBox.currentIndex()
120134
comparisonMode = self.comparisonComboBox.currentIndex()
135+
comparisonMode2 = self.comparison2ComboBox.currentIndex()
136+
and_or = self.andOrComboBox.currentIndex()
137+
case_sensitive = self.caseSensitiveCheckBox.isChecked()
138+
case_sensitive2 = self.caseSensitive2CheckBox.isChecked()
139+
self.two_string_match_single = self.twoStringMatchCheckBox.isChecked()
140+
self.first_match_only = self.firstMatchCheckBox.isChecked()
121141
self.noSelection = True
122142
try:
123143
sstr = self.findStringEdit.text()
144+
sstr2 = self.findString2Edit.text()
124145
except:
125146
self.showErrorMessage('Invalid Search String')
126147
return
127148

128-
if str == '':
149+
if sstr == '':
129150
self.showErrorMessage('Search string is empty')
130151
return
152+
if sstr2 == '':
153+
self.is_single_string = True
154+
else:
155+
self.is_single_string = False
131156
if selectedLayer == 0:
132157
# Include all vector layers
133158
layers = QgsProject.instance().mapLayers().values()
@@ -147,22 +172,26 @@ def runSearch(self):
147172
return
148173

149174
# vlayers contains the layers that we will search in
175+
self.initSearchResultsTable()
150176
self.searchButton.setEnabled(False)
151177
self.stopButton.setEnabled(True)
152178
self.doneButton.setEnabled(False)
153179
self.clearButton.setEnabled(False)
154-
self.clearResults()
155180
self.resultsLabel.setText('')
156181
infield = self.searchFieldComboBox.currentIndex() >= 1
157182
if infield is True:
158183
selectedField = self.searchFieldComboBox.currentText()
159184
else:
160185
selectedField = None
186+
not_search = self.notCheckBox.isChecked()
187+
not_search2 = self.not2CheckBox.isChecked()
161188

162189
# Because this could take a lot of time, set up a separate thread
163190
# for a worker function to do the searching.
164191
thread = QThread()
165-
worker = Worker(self.vlayers, infield, sstr, comparisonMode, selectedField, self.maxResults)
192+
worker = Worker(self.vlayers, infield, sstr, comparisonMode, case_sensitive, not_search,
193+
and_or, sstr2, comparisonMode2, case_sensitive2, not_search2,
194+
selectedField, self.maxResults, self.first_match_only, self.two_string_match_single)
166195
worker.moveToThread(thread)
167196
thread.started.connect(worker.run)
168197
worker.finished.connect(self.workerFinished)
@@ -207,15 +236,29 @@ def clearResults(self):
207236
self.resultsTable.setRowCount(0)
208237
self.noSelection = False
209238

210-
def addFoundItem(self, layer, feature, attrname, value):
239+
def addFoundItem(self, layer, feature, attrname1, results1, attrname2, results2):
211240
'''We found an item so add it to the found list.'''
241+
# Don't allow sorting while adding new results
242+
self.resultsTable.setSortingEnabled(False)
212243
self.resultsTable.insertRow(self.found)
213244
self.results.append([layer, feature])
214-
self.resultsTable.setItem(self.found, 0, QTableWidgetItem(value))
215-
self.resultsTable.setItem(self.found, 1, QTableWidgetItem(layer.name()))
216-
self.resultsTable.setItem(self.found, 2, QTableWidgetItem(attrname))
217-
self.resultsTable.setItem(self.found, 3, QTableWidgetItem(str(feature.id())))
218-
self.found += 1
245+
# Save the search found position in the first element of the table. This way
246+
# we can allow the user to sort the table, but be able to know which entry it is.
247+
item = QTableWidgetItem(layer.name())
248+
item.setData(Qt.UserRole, self.found)
249+
self.resultsTable.setItem(self.found, 0, item)
250+
self.resultsTable.setItem(self.found, 1, QTableWidgetItem(str(feature.id())))
251+
if self.is_single_string or self.two_string_match_single:
252+
self.resultsTable.setItem(self.found, 2, QTableWidgetItem(attrname1))
253+
self.resultsTable.setItem(self.found, 3, QTableWidgetItem(results1))
254+
else:
255+
self.resultsTable.setItem(self.found, 2, QTableWidgetItem(attrname1))
256+
self.resultsTable.setItem(self.found, 3, QTableWidgetItem(results1))
257+
self.resultsTable.setItem(self.found, 4, QTableWidgetItem(attrname2))
258+
self.resultsTable.setItem(self.found, 5, QTableWidgetItem(results2))
259+
self.found += 1
260+
# Restore sorting
261+
self.resultsTable.setSortingEnabled(True)
219262

220263
def showErrorMessage(self, message):
221264
'''Display an error message.'''

0 commit comments

Comments
 (0)