!import
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 *
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 *
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
15 *
16 * The Original Code is Mozilla Communicator client code, released
17 * March 31, 1998.
18 *
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 * Kin Blas <kin@netscape.com>
26 * Akkana Peck <akkana@netscape.com>
27 *
28 * Alternatively, the contents of this file may be used under the terms of
29 * either of the GNU General Public License Version 2 or later (the "GPL"),
30 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
39 *
40 * ***** END LICENSE BLOCK ***** */
41
42 var gReplaceDialog; // Quick access to document/form elements.
43 var gFindInst; // nsIWebBrowserFind that we're going to use
44 var gFindService; // Global service which remembers find params
45 var gEditor; // the editor we're using
46
initDialogObject
47 function initDialogObject()
48 {
49 // Create gReplaceDialog object and initialize.
50 gReplaceDialog = {};
51 gReplaceDialog.findInput = document.getElementById("dialog.findInput");
52 gReplaceDialog.replaceInput = document.getElementById("dialog.replaceInput");
53 gReplaceDialog.caseSensitive = document.getElementById("dialog.caseSensitive");
54 gReplaceDialog.wrap = document.getElementById("dialog.wrap");
55 gReplaceDialog.searchBackwards = document.getElementById("dialog.searchBackwards");
56 gReplaceDialog.findNext = document.getElementById("findNext");
57 gReplaceDialog.replace = document.getElementById("replace");
58 gReplaceDialog.replaceAndFind = document.getElementById("replaceAndFind");
59 gReplaceDialog.replaceAll = document.getElementById("replaceAll");
60 }
61
loadDialog
62 function loadDialog()
63 {
64 // Set initial dialog field contents.
65 // Set initial dialog field contents. Use the gFindInst attributes first,
66 // this is necessary for window.find()
67 gReplaceDialog.findInput.value = (gFindInst.searchString
68 ? gFindInst.searchString
69 : gFindService.searchString);
70 gReplaceDialog.replaceInput.value = gFindService.replaceString;
71 gReplaceDialog.caseSensitive.checked = (gFindInst.matchCase
72 ? gFindInst.matchCase
73 : gFindService.matchCase);
74 gReplaceDialog.wrap.checked = (gFindInst.wrapFind
75 ? gFindInst.wrapFind
76 : gFindService.wrapFind);
77 gReplaceDialog.searchBackwards.checked = (gFindInst.findBackwards
78 ? gFindInst.findBackwards
79 : gFindService.findBackwards);
80
81 doEnabling();
82 }
83
onLoad
84 function onLoad()
85 {
86 // Get the xul <editor> element:
87 var editorElement = window.arguments[0];
88
89 // If we don't get the editor, then we won't allow replacing.
90 gEditor = editorElement.getEditor(editorElement.contentWindow);
91 if (!gEditor)
92 {
93 window.close();
94 return;
95 }
96
97 // Get the nsIWebBrowserFind service:
98 gFindInst = editorElement.webBrowserFind;
99
100 try {
101 // get the find service, which stores global find state
102 gFindService = Components.classes["@mozilla.org/find/find_service;1"]
103 .getService(Components.interfaces.nsIFindService);
104 } catch(e) { dump("No find service!\n"); gFindService = 0; }
105
106 // Init gReplaceDialog.
107 initDialogObject();
108
109 // Change "OK" to "Find".
110 //dialog.find.label = document.getElementById("fBLT").getAttribute("label");
111
112 // Fill dialog.
113 loadDialog();
114
115 if (gReplaceDialog.findInput.value)
116 gReplaceDialog.findInput.select();
117 else
118 gReplaceDialog.findInput.focus();
119 }
120
onUnload
121 function onUnload() {
122 // Disconnect context from this dialog.
123 gFindReplaceData.replaceDialog = null;
124 }
125
saveFindData
126 function saveFindData()
127 {
128 // Set data attributes per user input.
129 if (gFindService)
130 {
131 gFindService.searchString = gReplaceDialog.findInput.value;
132 gFindService.matchCase = gReplaceDialog.caseSensitive.checked;
133 gFindService.wrapFind = gReplaceDialog.wrap.checked;
134 gFindService.findBackwards = gReplaceDialog.searchBackwards.checked;
135 }
136 }
137
setUpFindInst
138 function setUpFindInst()
139 {
140 gFindInst.searchString = gReplaceDialog.findInput.value;
141 gFindInst.matchCase = gReplaceDialog.caseSensitive.checked;
142 gFindInst.wrapFind = gReplaceDialog.wrap.checked;
143 gFindInst.findBackwards = gReplaceDialog.searchBackwards.checked;
144 }
145
onFindNext
146 function onFindNext()
147 {
148 // Transfer dialog contents to the find service.
149 saveFindData();
150 // set up the find instance
151 setUpFindInst();
152
153 // Search.
154 var result = gFindInst.findNext();
155
156 if (!result)
157 {
158 var bundle = document.getElementById("findBundle");
159 AlertWithTitle(null, bundle.getString("notFoundWarning"));
160 SetTextboxFocus(gReplaceDialog.findInput);
161 gReplaceDialog.findInput.select();
162 gReplaceDialog.findInput.focus();
163 return false;
164 }
165 return true;
166 }
167
onReplace
168 function onReplace()
169 {
170 if (!gEditor)
171 return false;
172
173 // Does the current selection match the find string?
174 var selection = gEditor.selection;
175
176 var selStr = selection.toString();
177 var specStr = gReplaceDialog.findInput.value;
178 if (!gReplaceDialog.caseSensitive.checked)
179 {
180 selStr = selStr.toLowerCase();
181 specStr = specStr.toLowerCase();
182 }
183 // Unfortunately, because of whitespace we can't just check
184 // whether (selStr == specStr), but have to loop ourselves.
185 // N chars of whitespace in specStr can match any M >= N in selStr.
186 var matches = true;
187 var specLen = specStr.length;
188 var selLen = selStr.length;
189 if (selLen < specLen)
190 matches = false;
191 else
192 {
193 var specArray = specStr.match(/\S+|\s+/g);
194 var selArray = selStr.match(/\S+|\s+/g);
195 if ( specArray.length != selArray.length)
196 matches = false;
197 else
198 {
199 for (var i=0; i<selArray.length; i++)
200 {
201 if (selArray[i] != specArray[i])
202 {
203 if ( /\S/.test(selArray[i][0]) || /\S/.test(specArray[i][0]) )
204 {
205 // not a space chunk -- match fails
206 matches = false;
207 break;
208 }
209 else if ( selArray[i].length < specArray[i].length )
210 {
211 // if it's a space chunk then we only care that sel be
212 // at least as long as spec
213 matches = false;
214 break;
215 }
216 }
217 }
218 }
219 }
220
221 // If the current selection doesn't match the pattern,
222 // then we want to find the next match, but not do the replace.
223 // That's what most other apps seem to do.
224 // So here, just return.
225 if (!matches)
226 return false;
227
228 // Transfer dialog contents to the find service.
229 saveFindData();
230
231 // For reverse finds, need to remember the caret position
232 // before current selection
233 var newRange;
234 if (gReplaceDialog.searchBackwards.checked && selection.rangeCount > 0)
235 {
236 newRange = selection.getRangeAt(0).cloneRange();
237 newRange.collapse(true);
238 }
239
240 // nsPlaintextEditor::InsertText fails if the string is empty,
241 // so make that a special case:
242 var replStr = gReplaceDialog.replaceInput.value;
243 if (replStr == "")
244 gEditor.deleteSelection(0);
245 else
246 gEditor.insertText(replStr);
247
248 // For reverse finds, need to move caret just before the replaced text
249 if (gReplaceDialog.searchBackwards.checked && newRange)
250 {
251 gEditor.selection.removeAllRanges();
252 gEditor.selection.addRange(newRange);
253 }
254
255 return true;
256 }
257
onReplaceAll
258 function onReplaceAll()
259 {
260 if (!gEditor)
261 return;
262
263 var findStr = gReplaceDialog.findInput.value;
264 var repStr = gReplaceDialog.replaceInput.value;
265
266 // Transfer dialog contents to the find service.
267 saveFindData();
268
269 var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"].createInstance().QueryInterface(Components.interfaces.nsIFind);
270
271 finder.caseSensitive = gReplaceDialog.caseSensitive.checked;
272 finder.findBackwards = gReplaceDialog.searchBackwards.checked;
273
274 // We want the whole operation to be undoable in one swell foop,
275 // so start a transaction:
276 gEditor.beginTransaction();
277
278 // and to make sure we close the transaction, guard against exceptions:
279 try {
280 // Make a range containing the current selection,
281 // so we don't go past it when we wrap.
282 var selection = gEditor.selection;
283 var selecRange;
284 if (selection.rangeCount > 0)
285 selecRange = selection.getRangeAt(0);
286 var origRange = selecRange.cloneRange();
287
288 // We'll need a range for the whole document:
289 var wholeDocRange = gEditor.document.createRange();
290 var rootNode = gEditor.rootElement.QueryInterface(Components.interfaces.nsIDOMNode);
291 wholeDocRange.selectNodeContents(rootNode);
292
293 // And start and end points:
294 var endPt = gEditor.document.createRange();
295
296 if (gReplaceDialog.searchBackwards.checked)
297 {
298 endPt.setStart(wholeDocRange.startContainer, wholeDocRange.startOffset);
299 endPt.setEnd(wholeDocRange.startContainer, wholeDocRange.startOffset);
300 }
301 else
302 {
303 endPt.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset);
304 endPt.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset);
305 }
306
307 // Find and replace from here to end (start) of document:
308 var foundRange;
309 var searchRange = wholeDocRange.cloneRange();
310 while ((foundRange = finder.Find(findStr, searchRange,
311 selecRange, endPt)) != null)
312 {
313 gEditor.selection.removeAllRanges();
314 gEditor.selection.addRange(foundRange);
315
316 // The editor will leave the caret at the end of the replaced text.
317 // For reverse finds, we need it at the beginning,
318 // so save the next position now.
319 if (gReplaceDialog.searchBackwards.checked)
320 {
321 selecRange = foundRange.cloneRange();
322 selecRange.setEnd(selecRange.startContainer, selecRange.startOffset);
323 }
324
325 // nsPlaintextEditor::InsertText fails if the string is empty,
326 // so make that a special case:
327 if (repStr == "")
328 gEditor.deleteSelection(0);
329 else
330 gEditor.insertText(repStr);
331
332 // If we're going forward, we didn't save selecRange before, so do it now:
333 if (!gReplaceDialog.searchBackwards.checked)
334 {
335 selection = gEditor.selection;
336 if (selection.rangeCount <= 0) {
337 gEditor.endTransaction();
338 return;
339 }
340 selecRange = selection.getRangeAt(0).cloneRange();
341 }
342 }
343
344 // If no wrapping, then we're done
345 if (!gReplaceDialog.wrap.checked) {
346 gEditor.endTransaction();
347 return;
348 }
349
350 // If wrapping, find from start/end of document back to start point.
351 if (gReplaceDialog.searchBackwards.checked)
352 {
353 // Collapse origRange to end
354 origRange.setStart(origRange.endContainer, origRange.endOffset);
355 // Set current position to document end
356 selecRange.setEnd(wholeDocRange.endContainer, wholeDocRange.endOffset);
357 selecRange.setStart(wholeDocRange.endContainer, wholeDocRange.endOffset);
358 }
359 else
360 {
361 // Collapse origRange to start
362 origRange.setEnd(origRange.startContainer, origRange.startOffset);
363 // Set current position to document start
364 selecRange.setStart(wholeDocRange.startContainer,
365 wholeDocRange.startOffset);
366 selecRange.setEnd(wholeDocRange.startContainer, wholeDocRange.startOffset);
367 }
368
369 while ((foundRange = finder.Find(findStr, wholeDocRange,
370 selecRange, origRange)) != null)
371 {
372 gEditor.selection.removeAllRanges();
373 gEditor.selection.addRange(foundRange);
374
375 // Save insert point for backward case
376 if (gReplaceDialog.searchBackwards.checked)
377 {
378 selecRange = foundRange.cloneRange();
379 selecRange.setEnd(selecRange.startContainer, selecRange.startOffset);
380 }
381
382 // nsPlaintextEditor::InsertText fails if the string is empty,
383 // so make that a special case:
384 if (repStr == "")
385 gEditor.deleteSelection(0);
386 else
387 gEditor.insertText(repStr);
388
389 // Get insert point for forward case
390 if (!gReplaceDialog.searchBackwards.checked)
391 {
392 selection = gEditor.selection;
393 if (selection.rangeCount <= 0) {
394 gEditor.endTransaction();
395 return;
396 }
397 selecRange = selection.getRangeAt(0);
398 }
399 }
400 } // end try
401 catch (e) { }
402
403 gEditor.endTransaction();
404 }
405
doEnabling
406 function doEnabling()
407 {
408 var findStr = gReplaceDialog.findInput.value;
409 var repStr = gReplaceDialog.replaceInput.value;
410 gReplaceDialog.enabled = findStr;
411 gReplaceDialog.findNext.disabled = !findStr;
412 gReplaceDialog.replace.disabled = !findStr;
413 gReplaceDialog.replaceAndFind.disabled = !findStr;
414 gReplaceDialog.replaceAll.disabled = !findStr;
415 }