1 |
|
# Orca |
2 |
|
# |
3 |
|
# Copyright 2006 Sun Microsystems Inc. |
4 |
|
# |
5 |
|
# This library is free software; you can redistribute it and/or |
6 |
|
# modify it under the terms of the GNU Library General Public |
7 |
|
# License as published by the Free Software Foundation; either |
8 |
|
# version 2 of the License, or (at your option) any later version. |
9 |
|
# |
10 |
|
# This library is distributed in the hope that it will be useful, |
11 |
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 |
|
# Library General Public License for more details. |
14 |
|
# |
15 |
|
# You should have received a copy of the GNU Library General Public |
16 |
|
# License along with this library; if not, write to the |
17 |
|
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
18 |
|
# Boston, MA 02111-1307, USA. |
19 |
|
|
20 |
1 |
"""Provides support for a flat review find.""" |
21 |
|
|
22 |
1 |
__id__ = "$Id: find.py 2234 2007-04-02 18:36:08Z wwalker $" |
23 |
1 |
__version__ = "$Revision: 2234 $" |
24 |
1 |
__date__ = "$Date: 2007-04-02 14:36:08 -0400 (Mon, 02 Apr 2007) $" |
25 |
1 |
__copyright__ = "Copyright (c) 2005-2006 Sun Microsystems Inc." |
26 |
1 |
__license__ = "LGPL" |
27 |
|
|
28 |
|
|
29 |
1 |
import copy |
30 |
1 |
import re |
31 |
|
|
32 |
1 |
import braille |
33 |
1 |
import debug |
34 |
1 |
import flat_review |
35 |
1 |
import orca_state |
36 |
1 |
import speech |
37 |
|
|
38 |
1 |
from orca_i18n import _ # for gettext support |
39 |
|
|
40 |
2 |
class SearchQuery: |
41 |
1 |
"""Represents a search that the user wants to perform.""" |
42 |
|
|
43 |
1 |
def __init__(self): |
44 |
|
"""Creates a new SearchQuery. A searchQuery has the following |
45 |
|
properties: |
46 |
|
|
47 |
|
searchString - the string to find |
48 |
|
searchBackwards - if true, search upward for matches |
49 |
|
caseSensitive - if true, case counts |
50 |
|
matchEntireWord - if true, only match on the entire string |
51 |
|
startAtTop - if true, begin the search from the top of |
52 |
|
the window, rather than at the current |
53 |
|
location |
54 |
|
windowWrap - if true, when the top/bottom edge of the |
55 |
|
window is reached wrap to the bottom/top |
56 |
|
and continue searching |
57 |
|
""" |
58 |
|
|
59 |
0 |
self.searchString = "" |
60 |
0 |
self.searchBackwards = False |
61 |
0 |
self.caseSensitive = False |
62 |
0 |
self.matchEntireWord = False |
63 |
0 |
self.windowWrap = False |
64 |
0 |
self.startAtTop = False |
65 |
|
|
66 |
0 |
self.debugLevel = debug.LEVEL_FINEST |
67 |
|
|
68 |
1 |
def debugContext(self, context, string): |
69 |
0 |
debug.println(self.debugLevel, \ |
70 |
0 |
"------------------------------------------------------------") |
71 |
0 |
debug.println(self.debugLevel, \ |
72 |
0 |
"findQuery: %s line=%d zone=%d word=%d char=%d" \ |
73 |
0 |
% (string, context.lineIndex, context.zoneIndex, \ |
74 |
0 |
context.wordIndex, context.charIndex)) |
75 |
|
|
76 |
0 |
debug.println(self.debugLevel, \ |
77 |
0 |
"Number of lines: %d" % len(context.lines)) |
78 |
0 |
debug.println(self.debugLevel, \ |
79 |
0 |
"Number of zones in current line: %d" % \ |
80 |
0 |
len(context.lines[context.lineIndex].zones)) |
81 |
0 |
debug.println(self.debugLevel, \ |
82 |
0 |
"Number of words in current zone: %d" % \ |
83 |
0 |
len(context.lines[context.lineIndex].zones[context.zoneIndex].words)) |
84 |
|
|
85 |
0 |
debug.println(self.debugLevel, \ |
86 |
0 |
"==========================================================\n\n") |
87 |
|
|
88 |
1 |
def dumpContext(self, context): |
89 |
0 |
print "DUMP" |
90 |
0 |
for i in range(0, len(context.lines)): |
91 |
0 |
print " Line %d" % i |
92 |
0 |
for j in range(0, len(context.lines[i].zones)): |
93 |
0 |
print " Zone: %d" % j |
94 |
0 |
for k in range(0, len(context.lines[i].zones[j].words)): |
95 |
0 |
print " Word %d = `%s` len(word): %d" % \ |
96 |
0 |
(k, context.lines[i].zones[j].words[k].string, \ |
97 |
0 |
len(context.lines[i].zones[j].words[k].string)) |
98 |
|
|
99 |
1 |
def findQuery(self, context, justEnteredFlatReview): |
100 |
|
"""Performs a search on the string specified in searchQuery. |
101 |
|
|
102 |
|
Arguments: |
103 |
|
- context: The context from active script |
104 |
|
- justEnteredFlatReview: If true, we began the search in focus |
105 |
|
tracking mode. |
106 |
|
|
107 |
|
Returns: |
108 |
|
- The context of the match, if found |
109 |
|
""" |
110 |
|
|
111 |
|
# Get the starting context so that we can restore it at the end. |
112 |
|
# |
113 |
0 |
originalLineIndex = context.lineIndex |
114 |
0 |
originalZoneIndex = context.zoneIndex |
115 |
0 |
originalWordIndex = context.wordIndex |
116 |
0 |
originalCharIndex = context.charIndex |
117 |
|
|
118 |
0 |
debug.println(self.debugLevel, \ |
119 |
0 |
"findQuery: original context line=%d zone=%d word=%d char=%d" \ |
120 |
0 |
% (originalLineIndex, originalZoneIndex, \ |
121 |
0 |
originalWordIndex, originalCharIndex)) |
122 |
|
# self.dumpContext(context) |
123 |
|
|
124 |
0 |
flags = re.LOCALE |
125 |
0 |
if not self.caseSensitive: |
126 |
0 |
flags = flags | re.IGNORECASE |
127 |
0 |
if self.matchEntireWord: |
128 |
0 |
regexp = "\\b" + self.searchString + "\\b" |
129 |
|
else: |
130 |
0 |
regexp = self.searchString |
131 |
0 |
pattern = re.compile(regexp,flags) |
132 |
|
|
133 |
0 |
debug.println(self.debugLevel, \ |
134 |
0 |
"findQuery: startAtTop: %d regexp: `%s`" \ |
135 |
0 |
% (self.startAtTop, regexp)) |
136 |
|
|
137 |
0 |
if self.startAtTop: |
138 |
0 |
context.goBegin(flat_review.Context.WINDOW) |
139 |
0 |
self.debugContext(context, "go begin") |
140 |
|
|
141 |
0 |
location = None |
142 |
0 |
found = False |
143 |
0 |
wrappedYet = False |
144 |
|
|
145 |
0 |
doneWithLine = False |
146 |
0 |
while not found: |
147 |
|
# Check the current line for the string. |
148 |
|
# |
149 |
|
[currentLine, x, y, width, height] = \ |
150 |
0 |
context.getCurrent(flat_review.Context.LINE) |
151 |
0 |
debug.println(self.debugLevel, \ |
152 |
0 |
"findQuery: current line=`%s` x=%d y=%d width=%d height=%d" \ |
153 |
0 |
% (currentLine, x, y, width, height)) |
154 |
|
|
155 |
0 |
if re.search(pattern, currentLine) and not doneWithLine: |
156 |
|
# It's on this line. Check the current zone for the string. |
157 |
|
# |
158 |
0 |
while not found: |
159 |
|
[currentZone, x, y, width, height] = \ |
160 |
0 |
context.getCurrent(flat_review.Context.ZONE) |
161 |
0 |
debug.println(self.debugLevel, \ |
162 |
0 |
"findQuery: current zone=`%s` x=%d y=%d width=%d height=%d" \ |
163 |
0 |
% (currentZone, x, y, width, height)) |
164 |
|
|
165 |
0 |
if re.search(pattern, currentZone): |
166 |
|
# It's in this zone at least once. |
167 |
|
# |
168 |
0 |
theZone = context.lines[context.lineIndex] \ |
169 |
0 |
.zones[context.zoneIndex] |
170 |
|
startedInThisZone = \ |
171 |
0 |
(originalLineIndex == context.lineIndex) and \ |
172 |
0 |
(originalZoneIndex == context.zoneIndex) |
173 |
|
|
174 |
0 |
if theZone.accessible.text and \ |
175 |
0 |
theZone.accessible.text.characterCount: |
176 |
|
# Make a list of the character offsets for the |
177 |
|
# matches in this zone. |
178 |
|
# |
179 |
0 |
allMatches = re.finditer(pattern, currentZone) |
180 |
0 |
offsets = [] |
181 |
0 |
for m in allMatches: |
182 |
0 |
offsets.append(m.start(0)) |
183 |
0 |
if self.searchBackwards: |
184 |
0 |
offsets.reverse() |
185 |
|
|
186 |
0 |
i = 0 |
187 |
0 |
while not found and (i < len(offsets)): |
188 |
|
[nextInstance, offset] = \ |
189 |
0 |
theZone.getWordAtOffset(offsets[i]) |
190 |
0 |
offsetDiff=nextInstance.index-context.wordIndex |
191 |
0 |
if self.searchBackwards and \ |
192 |
0 |
(offsetDiff < 0) or \ |
193 |
0 |
not self.searchBackwards and \ |
194 |
0 |
(offsetDiff > 0): |
195 |
0 |
context.wordIndex = nextInstance.index |
196 |
0 |
context.charIndex = 0 |
197 |
0 |
found = True |
198 |
0 |
elif not offsetDiff and \ |
199 |
0 |
(not startedInThisZone or justEnteredFlatReview): |
200 |
|
# We landed on a match by happenstance. |
201 |
|
# This can occur when the nextInstance is |
202 |
|
# the first thing we come across. |
203 |
|
# |
204 |
0 |
found = True |
205 |
|
else: |
206 |
0 |
i += 1 |
207 |
0 |
if not found: |
208 |
|
# Locate the next zone to try again. |
209 |
|
# |
210 |
0 |
if self.searchBackwards: |
211 |
0 |
moved = context.goPrevious( \ |
212 |
0 |
flat_review.Context.ZONE, \ |
213 |
0 |
flat_review.Context.WRAP_LINE) |
214 |
0 |
self.debugContext(context, "[1] go previous") |
215 |
0 |
context.goEnd(flat_review.Context.ZONE) |
216 |
0 |
self.debugContext(context, "[1] go end") |
217 |
|
else: |
218 |
0 |
moved = context.goNext( \ |
219 |
0 |
flat_review.Context.ZONE, \ |
220 |
0 |
flat_review.Context.WRAP_LINE) |
221 |
0 |
self.debugContext(context, "[1] go next") |
222 |
0 |
if not moved: |
223 |
0 |
doneWithLine = True |
224 |
0 |
break |
225 |
|
else: |
226 |
|
# Locate the next line to try again. |
227 |
|
# |
228 |
0 |
if self.searchBackwards: |
229 |
0 |
moved = context.goPrevious(flat_review.Context.LINE, \ |
230 |
0 |
flat_review.Context.WRAP_LINE) |
231 |
0 |
self.debugContext(context, "[2] go previous") |
232 |
|
else: |
233 |
0 |
moved = context.goNext(flat_review.Context.LINE, \ |
234 |
0 |
flat_review.Context.WRAP_LINE) |
235 |
0 |
self.debugContext(context, "[2] go next") |
236 |
0 |
if moved: |
237 |
0 |
if self.searchBackwards: |
238 |
0 |
moved = context.goEnd(flat_review.Context.LINE) |
239 |
0 |
self.debugContext(context, "[2] go end") |
240 |
|
else: |
241 |
|
# Then we're at the screen's edge. |
242 |
|
# |
243 |
0 |
if self.windowWrap and not wrappedYet: |
244 |
0 |
doneWithLine = False |
245 |
0 |
wrappedYet = True |
246 |
0 |
if self.searchBackwards: |
247 |
|
# Translators: the Orca "Find" dialog |
248 |
|
# allows a user to search for text in a |
249 |
|
# window and then move focus to that text. |
250 |
|
# For example, they may want to find the |
251 |
|
# "OK" button. This message indicates |
252 |
|
# that a find operation in the reverse |
253 |
|
# direction is wrapping from the top of |
254 |
|
# the window down to the bottom. |
255 |
|
# |
256 |
0 |
speech.speak(_("Wrapping to Bottom")) |
257 |
0 |
moved = context.goPrevious( \ |
258 |
0 |
flat_review.Context.LINE, \ |
259 |
0 |
flat_review.Context.WRAP_ALL) |
260 |
0 |
self.debugContext(context, "[3] go previous") |
261 |
|
else: |
262 |
|
# Translators: the Orca "Find" dialog |
263 |
|
# allows a user to search for text in a |
264 |
|
# window and then move focus to that text. |
265 |
|
# For example, they may want to find the |
266 |
|
# "OK" button. This message indicates |
267 |
|
# that a find operation in the forward |
268 |
|
# direction is wrapping from the bottom of |
269 |
|
# the window up to the top. |
270 |
|
# |
271 |
0 |
speech.speak(_("Wrapping to Top")) |
272 |
0 |
moved = context.goNext( \ |
273 |
0 |
flat_review.Context.LINE, \ |
274 |
0 |
flat_review.Context.WRAP_ALL) |
275 |
0 |
self.debugContext(context, "[3] go next") |
276 |
0 |
if not moved: |
277 |
0 |
debug.println(self.debugLevel, \ |
278 |
0 |
"findQuery: cannot wrap") |
279 |
0 |
break |
280 |
|
else: |
281 |
0 |
break |
282 |
0 |
if found: |
283 |
0 |
location = copy.copy(context) |
284 |
|
|
285 |
0 |
self.debugContext(context, "before setting original") |
286 |
0 |
context.setCurrent(originalLineIndex, originalZoneIndex, \ |
287 |
0 |
originalWordIndex, originalCharIndex) |
288 |
0 |
self.debugContext(context, "after setting original") |
289 |
|
|
290 |
0 |
if location: |
291 |
0 |
debug.println(self.debugLevel, \ |
292 |
0 |
"findQuery: returning line=%d zone=%d word=%d char=%d" \ |
293 |
0 |
% (location.lineIndex, location.zoneIndex, \ |
294 |
0 |
location.wordIndex, location.charIndex)) |
295 |
|
|
296 |
0 |
return location |
297 |
|
|
298 |
1 |
def getLastQuery(): |
299 |
|
"""Grabs the last search query performed from orca_state. |
300 |
|
|
301 |
|
Returns: |
302 |
|
- A copy of the last search query, if it exists |
303 |
|
""" |
304 |
|
|
305 |
0 |
lastQuery = copy.copy(orca_state.searchQuery) |
306 |
0 |
return lastQuery |