JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.2
[ckeditor.git] / _source / plugins / find / dialogs / find.js
1 /*\r
2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license\r
4 */\r
5 \r
6 (function()\r
7 {\r
8         function guardDomWalkerNonEmptyTextNode( node )\r
9         {\r
10                 return ( node.type == CKEDITOR.NODE_TEXT && node.getLength() > 0 );\r
11         }\r
12 \r
13         /**\r
14          * Elements which break characters been considered as sequence.\r
15         */\r
16         function checkCharactersBoundary ( node )\r
17         {\r
18                 var dtd = CKEDITOR.dtd;\r
19                 return node.isBlockBoundary(\r
20                         CKEDITOR.tools.extend( {}, dtd.$empty, dtd.$nonEditable ) );\r
21         }\r
22 \r
23         /**\r
24          * Get the cursor object which represent both current character and it's dom\r
25          * position thing.\r
26          */\r
27         var cursorStep = function()\r
28         {\r
29                 return {\r
30                         textNode : this.textNode,\r
31                         offset : this.offset,\r
32                         character : this.textNode ?\r
33                                 this.textNode.getText().charAt( this.offset ) : null,\r
34                         hitMatchBoundary : this._.matchBoundary\r
35                 };\r
36         };\r
37 \r
38         var pages = [ 'find', 'replace' ],\r
39                 fieldsMapping = [\r
40                 [ 'txtFindFind', 'txtFindReplace' ],\r
41                 [ 'txtFindCaseChk', 'txtReplaceCaseChk' ],\r
42                 [ 'txtFindWordChk', 'txtReplaceWordChk' ],\r
43                 [ 'txtFindCyclic', 'txtReplaceCyclic' ] ];\r
44 \r
45         /**\r
46          * Synchronize corresponding filed values between 'replace' and 'find' pages.\r
47          * @param {String} currentPageId        The page id which receive values.\r
48          */\r
49         function syncFieldsBetweenTabs( currentPageId )\r
50         {\r
51                 var sourceIndex, targetIndex,\r
52                         sourceField, targetField;\r
53 \r
54                 sourceIndex = currentPageId === 'find' ? 1 : 0;\r
55                 targetIndex = 1 - sourceIndex;\r
56                 var i, l = fieldsMapping.length;\r
57                 for ( i = 0 ; i < l ; i++ )\r
58                 {\r
59                         sourceField = this.getContentElement( pages[ sourceIndex ],\r
60                                         fieldsMapping[ i ][ sourceIndex ] );\r
61                         targetField = this.getContentElement( pages[ targetIndex ],\r
62                                         fieldsMapping[ i ][ targetIndex ] );\r
63 \r
64                         targetField.setValue( sourceField.getValue() );\r
65                 }\r
66         }\r
67 \r
68         var findDialog = function( editor, startupPage )\r
69         {\r
70                 // Style object for highlights.\r
71                 var highlightStyle = new CKEDITOR.style( editor.config.find_highlight );\r
72 \r
73                 /**\r
74                  * Iterator which walk through the specified range char by char. By\r
75                  * default the walking will not stop at the character boundaries, until\r
76                  * the end of the range is encountered.\r
77                  * @param { CKEDITOR.dom.range } range\r
78                  * @param {Boolean} matchWord Whether the walking will stop at character boundary.\r
79                  */\r
80                 var characterWalker = function( range , matchWord )\r
81                 {\r
82                         var walker =\r
83                                 new CKEDITOR.dom.walker( range );\r
84                         walker[ matchWord ? 'guard' : 'evaluator' ] =\r
85                                 guardDomWalkerNonEmptyTextNode;\r
86                         walker.breakOnFalse = true;\r
87 \r
88                         this._ = {\r
89                                 matchWord : matchWord,\r
90                                 walker : walker,\r
91                                 matchBoundary : false\r
92                         };\r
93                 };\r
94 \r
95                 characterWalker.prototype = {\r
96                         next : function()\r
97                         {\r
98                                 return this.move();\r
99                         },\r
100 \r
101                         back : function()\r
102                         {\r
103                                 return this.move( true );\r
104                         },\r
105 \r
106                         move : function( rtl )\r
107                         {\r
108                                 var currentTextNode = this.textNode;\r
109                                 // Already at the end of document, no more character available.\r
110                                 if (  currentTextNode === null )\r
111                                         return cursorStep.call( this );\r
112 \r
113                                 this._.matchBoundary = false;\r
114 \r
115                                 // There are more characters in the text node, step forward.\r
116                                 if ( currentTextNode\r
117                                     && rtl\r
118                                         && this.offset > 0 )\r
119                                 {\r
120                                         this.offset--;\r
121                                         return cursorStep.call( this );\r
122                                 }\r
123                                 else if ( currentTextNode\r
124                                         && this.offset < currentTextNode.getLength() - 1 )\r
125                                 {\r
126                                         this.offset++;\r
127                                         return cursorStep.call( this );\r
128                                 }\r
129                                 else\r
130                                 {\r
131                                         currentTextNode = null;\r
132                                         // At the end of the text node, walking foward for the next.\r
133                                         while ( !currentTextNode )\r
134                                         {\r
135                                                 currentTextNode =\r
136                                                         this._.walker[ rtl ? 'previous' : 'next' ].call( this._.walker );\r
137 \r
138                                                 // Stop searching if we're need full word match OR\r
139                                                 // already reach document end.\r
140                                                 if ( this._.matchWord && !currentTextNode\r
141                                                          ||this._.walker._.end )\r
142                                                         break;\r
143 \r
144                                                 // Marking as match character boundaries.\r
145                                                 if ( !currentTextNode\r
146                                                    && checkCharactersBoundary( this._.walker.current ) )\r
147                                                         this._.matchBoundary = true;\r
148 \r
149                                         }\r
150                                         // Found a fresh text node.\r
151                                         this.textNode = currentTextNode;\r
152                                         if ( currentTextNode )\r
153                                                 this.offset = rtl ? currentTextNode.getLength() - 1 : 0;\r
154                                         else\r
155                                                 this.offset = 0;\r
156                                 }\r
157 \r
158                                 return cursorStep.call( this );\r
159                         }\r
160 \r
161                 };\r
162 \r
163                 /**\r
164                  * A range of cursors which represent a trunk of characters which try to\r
165                  * match, it has the same length as the pattern  string.\r
166                  */\r
167                 var characterRange = function( characterWalker, rangeLength )\r
168                 {\r
169                         this._ = {\r
170                                 walker : characterWalker,\r
171                                 cursors : [],\r
172                                 rangeLength : rangeLength,\r
173                                 highlightRange : null,\r
174                                 isMatched : false\r
175                         };\r
176                 };\r
177 \r
178                 characterRange.prototype = {\r
179                         /**\r
180                          * Translate this range to {@link CKEDITOR.dom.range}\r
181                          */\r
182                         toDomRange : function()\r
183                         {\r
184                                 var cursors = this._.cursors;\r
185                                 if ( cursors.length < 1 )\r
186                                         return null;\r
187 \r
188                                 var first = cursors[0],\r
189                                         last = cursors[ cursors.length - 1 ],\r
190                                         range = new CKEDITOR.dom.range( editor.document );\r
191 \r
192                                 range.setStart( first.textNode, first.offset );\r
193                                 range.setEnd( last.textNode, last.offset + 1 );\r
194                                 return range;\r
195                         },\r
196                         /**\r
197                          * Reflect the latest changes from dom range.\r
198                          */\r
199                         updateFromDomRange : function( domRange )\r
200                         {\r
201                                 var cursor,\r
202                                                 walker = new characterWalker( domRange );\r
203                                 this._.cursors = [];\r
204                                 do\r
205                                 {\r
206                                         cursor = walker.next();\r
207                                         if ( cursor.character )\r
208                                                 this._.cursors.push( cursor );\r
209                                 }\r
210                                 while ( cursor.character );\r
211                                 this._.rangeLength = this._.cursors.length;\r
212                         },\r
213 \r
214                         setMatched : function()\r
215                         {\r
216                                 this._.isMatched = true;\r
217                         },\r
218 \r
219                         clearMatched : function()\r
220                         {\r
221                                 this._.isMatched = false;\r
222                         },\r
223 \r
224                         isMatched : function()\r
225                         {\r
226                                 return this._.isMatched;\r
227                         },\r
228 \r
229                         /**\r
230                          * Hightlight the current matched chunk of text.\r
231                          */\r
232                         highlight : function()\r
233                         {\r
234                                 // Do not apply if nothing is found.\r
235                                 if ( this._.cursors.length < 1 )\r
236                                         return;\r
237 \r
238                                 // Remove the previous highlight if there's one.\r
239                                 if ( this._.highlightRange )\r
240                                         this.removeHighlight();\r
241 \r
242                                 // Apply the highlight.\r
243                                 var range = this.toDomRange();\r
244                                 highlightStyle.applyToRange( range );\r
245                                 this._.highlightRange = range;\r
246 \r
247                                 // Scroll the editor to the highlighted area.\r
248                                 var element = range.startContainer;\r
249                                 if ( element.type != CKEDITOR.NODE_ELEMENT )\r
250                                         element = element.getParent();\r
251                                 element.scrollIntoView();\r
252 \r
253                                 // Update the character cursors.\r
254                                 this.updateFromDomRange( range );\r
255                         },\r
256 \r
257                         /**\r
258                          * Remove highlighted find result.\r
259                          */\r
260                         removeHighlight : function()\r
261                         {\r
262                                 if ( !this._.highlightRange )\r
263                                         return;\r
264 \r
265                                 highlightStyle.removeFromRange( this._.highlightRange );\r
266                                 this.updateFromDomRange( this._.highlightRange );\r
267                                 this._.highlightRange = null;\r
268                         },\r
269 \r
270                         moveBack : function()\r
271                         {\r
272                                 var retval = this._.walker.back(),\r
273                                         cursors = this._.cursors;\r
274 \r
275                                 if ( retval.hitMatchBoundary )\r
276                                         this._.cursors = cursors = [];\r
277 \r
278                                 cursors.unshift( retval );\r
279                                 if ( cursors.length > this._.rangeLength )\r
280                                         cursors.pop();\r
281 \r
282                                 return retval;\r
283                         },\r
284 \r
285                         moveNext : function()\r
286                         {\r
287                                 var retval = this._.walker.next(),\r
288                                         cursors = this._.cursors;\r
289 \r
290                                 // Clear the cursors queue if we've crossed a match boundary.\r
291                                 if ( retval.hitMatchBoundary )\r
292                                         this._.cursors = cursors = [];\r
293 \r
294                                 cursors.push( retval );\r
295                                 if ( cursors.length > this._.rangeLength )\r
296                                         cursors.shift();\r
297 \r
298                                 return retval;\r
299                         },\r
300 \r
301                         getEndCharacter : function()\r
302                         {\r
303                                 var cursors = this._.cursors;\r
304                                 if ( cursors.length < 1 )\r
305                                         return null;\r
306 \r
307                                 return cursors[ cursors.length - 1 ].character;\r
308                         },\r
309 \r
310                         getNextCharacterRange : function( maxLength )\r
311                         {\r
312                                 var lastCursor,\r
313                                                 nextRangeWalker,\r
314                                                 cursors = this._.cursors;\r
315 \r
316                                 if ( ( lastCursor = cursors[ cursors.length - 1 ] ) )\r
317                                         nextRangeWalker = new characterWalker( getRangeAfterCursor( lastCursor ) );\r
318                                 // In case it's an empty range (no cursors), figure out next range from walker (#4951).\r
319                                 else\r
320                                         nextRangeWalker = this._.walker;\r
321 \r
322                                 return new characterRange( nextRangeWalker, maxLength );\r
323                         },\r
324 \r
325                         getCursors : function()\r
326                         {\r
327                                 return this._.cursors;\r
328                         }\r
329                 };\r
330 \r
331 \r
332                 // The remaining document range after the character cursor.\r
333                 function getRangeAfterCursor( cursor , inclusive )\r
334                 {\r
335                         var range = new CKEDITOR.dom.range();\r
336                         range.setStart( cursor.textNode,\r
337                                                    ( inclusive ? cursor.offset : cursor.offset + 1 ) );\r
338                         range.setEndAt( editor.document.getBody(),\r
339                                                         CKEDITOR.POSITION_BEFORE_END );\r
340                         return range;\r
341                 }\r
342 \r
343                 // The document range before the character cursor.\r
344                 function getRangeBeforeCursor( cursor )\r
345                 {\r
346                         var range = new CKEDITOR.dom.range();\r
347                         range.setStartAt( editor.document.getBody(),\r
348                                                         CKEDITOR.POSITION_AFTER_START );\r
349                         range.setEnd( cursor.textNode, cursor.offset );\r
350                         return range;\r
351                 }\r
352 \r
353                 var KMP_NOMATCH = 0,\r
354                         KMP_ADVANCED = 1,\r
355                         KMP_MATCHED = 2;\r
356                 /**\r
357                  * Examination the occurrence of a word which implement KMP algorithm.\r
358                  */\r
359                 var kmpMatcher = function( pattern, ignoreCase )\r
360                 {\r
361                         var overlap = [ -1 ];\r
362                         if ( ignoreCase )\r
363                                 pattern = pattern.toLowerCase();\r
364                         for ( var i = 0 ; i < pattern.length ; i++ )\r
365                         {\r
366                                 overlap.push( overlap[i] + 1 );\r
367                                 while ( overlap[ i + 1 ] > 0\r
368                                         && pattern.charAt( i ) != pattern\r
369                                                 .charAt( overlap[ i + 1 ] - 1 ) )\r
370                                         overlap[ i + 1 ] = overlap[ overlap[ i + 1 ] - 1 ] + 1;\r
371                         }\r
372 \r
373                         this._ = {\r
374                                 overlap : overlap,\r
375                                 state : 0,\r
376                                 ignoreCase : !!ignoreCase,\r
377                                 pattern : pattern\r
378                         };\r
379                 };\r
380 \r
381                 kmpMatcher.prototype =\r
382                 {\r
383                         feedCharacter : function( c )\r
384                         {\r
385                                 if ( this._.ignoreCase )\r
386                                         c = c.toLowerCase();\r
387 \r
388                                 while ( true )\r
389                                 {\r
390                                         if ( c == this._.pattern.charAt( this._.state ) )\r
391                                         {\r
392                                                 this._.state++;\r
393                                                 if ( this._.state == this._.pattern.length )\r
394                                                 {\r
395                                                         this._.state = 0;\r
396                                                         return KMP_MATCHED;\r
397                                                 }\r
398                                                 return KMP_ADVANCED;\r
399                                         }\r
400                                         else if ( !this._.state )\r
401                                                 return KMP_NOMATCH;\r
402                                         else\r
403                                                 this._.state = this._.overlap[ this._.state ];\r
404                                 }\r
405 \r
406                                 return null;\r
407                         },\r
408 \r
409                         reset : function()\r
410                         {\r
411                                 this._.state = 0;\r
412                         }\r
413                 };\r
414 \r
415                 var wordSeparatorRegex =\r
416                 /[.,"'?!;: \u0085\u00a0\u1680\u280e\u2028\u2029\u202f\u205f\u3000]/;\r
417 \r
418                 var isWordSeparator = function( c )\r
419                 {\r
420                         if ( !c )\r
421                                 return true;\r
422                         var code = c.charCodeAt( 0 );\r
423                         return ( code >= 9 && code <= 0xd )\r
424                                 || ( code >= 0x2000 && code <= 0x200a )\r
425                                 || wordSeparatorRegex.test( c );\r
426                 };\r
427 \r
428                 var finder = {\r
429                         searchRange : null,\r
430                         matchRange : null,\r
431                         find : function( pattern, matchCase, matchWord, matchCyclic, highlightMatched, cyclicRerun )\r
432                         {\r
433                                 if ( !this.matchRange )\r
434                                         this.matchRange =\r
435                                                 new characterRange(\r
436                                                         new characterWalker( this.searchRange ),\r
437                                                         pattern.length );\r
438                                 else\r
439                                 {\r
440                                         this.matchRange.removeHighlight();\r
441                                         this.matchRange = this.matchRange.getNextCharacterRange( pattern.length );\r
442                                 }\r
443 \r
444                                 var matcher = new kmpMatcher( pattern, !matchCase ),\r
445                                         matchState = KMP_NOMATCH,\r
446                                         character = '%';\r
447 \r
448                                 while ( character !== null )\r
449                                 {\r
450                                         this.matchRange.moveNext();\r
451                                         while ( ( character = this.matchRange.getEndCharacter() ) )\r
452                                         {\r
453                                                 matchState = matcher.feedCharacter( character );\r
454                                                 if ( matchState == KMP_MATCHED )\r
455                                                         break;\r
456                                                 if ( this.matchRange.moveNext().hitMatchBoundary )\r
457                                                         matcher.reset();\r
458                                         }\r
459 \r
460                                         if ( matchState == KMP_MATCHED )\r
461                                         {\r
462                                                 if ( matchWord )\r
463                                                 {\r
464                                                         var cursors = this.matchRange.getCursors(),\r
465                                                                 tail = cursors[ cursors.length - 1 ],\r
466                                                                 head = cursors[ 0 ];\r
467 \r
468                                                         var headWalker = new characterWalker( getRangeBeforeCursor( head ), true ),\r
469                                                                 tailWalker = new characterWalker( getRangeAfterCursor( tail ), true );\r
470 \r
471                                                         if ( ! ( isWordSeparator( headWalker.back().character )\r
472                                                                                 && isWordSeparator( tailWalker.next().character ) ) )\r
473                                                                 continue;\r
474                                                 }\r
475                                                 this.matchRange.setMatched();\r
476                                                 if ( highlightMatched !== false )\r
477                                                         this.matchRange.highlight();\r
478                                                 return true;\r
479                                         }\r
480                                 }\r
481 \r
482                                 this.matchRange.clearMatched();\r
483                                 this.matchRange.removeHighlight();\r
484                                 // Clear current session and restart with the default search\r
485                                 // range.\r
486                                 // Re-run the finding once for cyclic.(#3517)\r
487                                 if ( matchCyclic && !cyclicRerun )\r
488                                 {\r
489                                         this.searchRange = getSearchRange( true );\r
490                                         this.matchRange = null;\r
491                                         return arguments.callee.apply( this,\r
492                                                 Array.prototype.slice.call( arguments ).concat( [ true ] ) );\r
493                                 }\r
494 \r
495                                 return false;\r
496                         },\r
497 \r
498                         /**\r
499                          * Record how much replacement occurred toward one replacing.\r
500                          */\r
501                         replaceCounter : 0,\r
502 \r
503                         replace : function( dialog, pattern, newString, matchCase, matchWord,\r
504                                 matchCyclic , isReplaceAll )\r
505                         {\r
506                                 // Successiveness of current replace/find.\r
507                                 var result = false;\r
508 \r
509                                 // 1. Perform the replace when there's already a match here.\r
510                                 // 2. Otherwise perform the find but don't replace it immediately.\r
511                                 if ( this.matchRange && this.matchRange.isMatched()\r
512                                                 && !this.matchRange._.isReplaced )\r
513                                 {\r
514                                         // Turn off highlight for a while when saving snapshots.\r
515                                         this.matchRange.removeHighlight();\r
516                                         var domRange = this.matchRange.toDomRange();\r
517                                         var text = editor.document.createText( newString );\r
518                                         if ( !isReplaceAll )\r
519                                         {\r
520                                                 // Save undo snaps before and after the replacement.\r
521                                                 var selection = editor.getSelection();\r
522                                                 selection.selectRanges( [ domRange ] );\r
523                                                 editor.fire( 'saveSnapshot' );\r
524                                         }\r
525                                         domRange.deleteContents();\r
526                                         domRange.insertNode( text );\r
527                                         if ( !isReplaceAll )\r
528                                         {\r
529                                                 selection.selectRanges( [ domRange ] );\r
530                                                 editor.fire( 'saveSnapshot' );\r
531                                         }\r
532                                         this.matchRange.updateFromDomRange( domRange );\r
533                                         if ( !isReplaceAll )\r
534                                                 this.matchRange.highlight();\r
535                                         this.matchRange._.isReplaced = true;\r
536                                         this.replaceCounter++;\r
537                                         result = true;\r
538                                 }\r
539                                 else\r
540                                         result = this.find( pattern, matchCase, matchWord, matchCyclic, !isReplaceAll );\r
541 \r
542                                 return result;\r
543                         }\r
544                 };\r
545 \r
546                 /**\r
547                  * The range in which find/replace happened, receive from user\r
548                  * selection prior.\r
549                  */\r
550                 function getSearchRange( isDefault )\r
551                 {\r
552                         var searchRange,\r
553                                 sel = editor.getSelection(),\r
554                                 body = editor.document.getBody();\r
555                         if ( sel && !isDefault )\r
556                         {\r
557                                 searchRange = sel.getRanges()[ 0 ].clone();\r
558                                 searchRange.collapse( true );\r
559                         }\r
560                         else\r
561                         {\r
562                                 searchRange = new CKEDITOR.dom.range();\r
563                                 searchRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );\r
564                         }\r
565                         searchRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );\r
566                         return searchRange;\r
567                 }\r
568 \r
569                 return {\r
570                         title : editor.lang.findAndReplace.title,\r
571                         resizable : CKEDITOR.DIALOG_RESIZE_NONE,\r
572                         minWidth : 350,\r
573                         minHeight : 165,\r
574                         buttons : [ CKEDITOR.dialog.cancelButton ],             //Cancel button only.\r
575                         contents : [\r
576                                 {\r
577                                         id : 'find',\r
578                                         label : editor.lang.findAndReplace.find,\r
579                                         title : editor.lang.findAndReplace.find,\r
580                                         accessKey : '',\r
581                                         elements : [\r
582                                                 {\r
583                                                         type : 'hbox',\r
584                                                         widths : [ '230px', '90px' ],\r
585                                                         children :\r
586                                                         [\r
587                                                                 {\r
588                                                                         type : 'text',\r
589                                                                         id : 'txtFindFind',\r
590                                                                         label : editor.lang.findAndReplace.findWhat,\r
591                                                                         isChanged : false,\r
592                                                                         labelLayout : 'horizontal',\r
593                                                                         accessKey : 'F'\r
594                                                                 },\r
595                                                                 {\r
596                                                                         type : 'button',\r
597                                                                         align : 'left',\r
598                                                                         style : 'width:100%',\r
599                                                                         label : editor.lang.findAndReplace.find,\r
600                                                                         onClick : function()\r
601                                                                         {\r
602                                                                                 var dialog = this.getDialog();\r
603                                                                                 if ( !finder.find( dialog.getValueOf( 'find', 'txtFindFind' ),\r
604                                                                                                         dialog.getValueOf( 'find', 'txtFindCaseChk' ),\r
605                                                                                                         dialog.getValueOf( 'find', 'txtFindWordChk' ),\r
606                                                                                                         dialog.getValueOf( 'find', 'txtFindCyclic' ) ) )\r
607                                                                                         alert( editor.lang.findAndReplace\r
608                                                                                                 .notFoundMsg );\r
609                                                                         }\r
610                                                                 }\r
611                                                         ]\r
612                                                 },\r
613                                                 {\r
614                                                         type : 'vbox',\r
615                                                         padding : 0,\r
616                                                         children :\r
617                                                         [\r
618                                                                 {\r
619                                                                         type : 'checkbox',\r
620                                                                         id : 'txtFindCaseChk',\r
621                                                                         isChanged : false,\r
622                                                                         style : 'margin-top:28px',\r
623                                                                         label : editor.lang.findAndReplace.matchCase\r
624                                                                 },\r
625                                                                 {\r
626                                                                         type : 'checkbox',\r
627                                                                         id : 'txtFindWordChk',\r
628                                                                         isChanged : false,\r
629                                                                         label : editor.lang.findAndReplace.matchWord\r
630                                                                 },\r
631                                                                 {\r
632                                                                         type : 'checkbox',\r
633                                                                         id : 'txtFindCyclic',\r
634                                                                         isChanged : false,\r
635                                                                         'default' : true,\r
636                                                                         label : editor.lang.findAndReplace.matchCyclic\r
637                                                                 }\r
638                                                         ]\r
639                                                 }\r
640                                         ]\r
641                                 },\r
642                                 {\r
643                                         id : 'replace',\r
644                                         label : editor.lang.findAndReplace.replace,\r
645                                         accessKey : 'M',\r
646                                         elements : [\r
647                                                 {\r
648                                                         type : 'hbox',\r
649                                                         widths : [ '230px', '90px' ],\r
650                                                         children :\r
651                                                         [\r
652                                                                 {\r
653                                                                         type : 'text',\r
654                                                                         id : 'txtFindReplace',\r
655                                                                         label : editor.lang.findAndReplace.findWhat,\r
656                                                                         isChanged : false,\r
657                                                                         labelLayout : 'horizontal',\r
658                                                                         accessKey : 'F'\r
659                                                                 },\r
660                                                                 {\r
661                                                                         type : 'button',\r
662                                                                         align : 'left',\r
663                                                                         style : 'width:100%',\r
664                                                                         label : editor.lang.findAndReplace.replace,\r
665                                                                         onClick : function()\r
666                                                                         {\r
667                                                                                 var dialog = this.getDialog();\r
668                                                                                 if ( !finder.replace( dialog,\r
669                                                                                                         dialog.getValueOf( 'replace', 'txtFindReplace' ),\r
670                                                                                                         dialog.getValueOf( 'replace', 'txtReplace' ),\r
671                                                                                                         dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ),\r
672                                                                                                         dialog.getValueOf( 'replace', 'txtReplaceWordChk' ),\r
673                                                                                                         dialog.getValueOf( 'replace', 'txtReplaceCyclic' ) ) )\r
674                                                                                         alert( editor.lang.findAndReplace\r
675                                                                                                 .notFoundMsg );\r
676                                                                         }\r
677                                                                 }\r
678                                                         ]\r
679                                                 },\r
680                                                 {\r
681                                                         type : 'hbox',\r
682                                                         widths : [ '230px', '90px' ],\r
683                                                         children :\r
684                                                         [\r
685                                                                 {\r
686                                                                         type : 'text',\r
687                                                                         id : 'txtReplace',\r
688                                                                         label : editor.lang.findAndReplace.replaceWith,\r
689                                                                         isChanged : false,\r
690                                                                         labelLayout : 'horizontal',\r
691                                                                         accessKey : 'R'\r
692                                                                 },\r
693                                                                 {\r
694                                                                         type : 'button',\r
695                                                                         align : 'left',\r
696                                                                         style : 'width:100%',\r
697                                                                         label : editor.lang.findAndReplace.replaceAll,\r
698                                                                         isChanged : false,\r
699                                                                         onClick : function()\r
700                                                                         {\r
701                                                                                 var dialog = this.getDialog();\r
702                                                                                 var replaceNums;\r
703 \r
704                                                                                 finder.replaceCounter = 0;\r
705 \r
706                                                                                 // Scope to full document.\r
707                                                                                 finder.searchRange = getSearchRange( true );\r
708                                                                                 if ( finder.matchRange )\r
709                                                                                 {\r
710                                                                                         finder.matchRange.removeHighlight();\r
711                                                                                         finder.matchRange = null;\r
712                                                                                 }\r
713                                                                                 editor.fire( 'saveSnapshot' );\r
714                                                                                 while ( finder.replace( dialog,\r
715                                                                                         dialog.getValueOf( 'replace', 'txtFindReplace' ),\r
716                                                                                         dialog.getValueOf( 'replace', 'txtReplace' ),\r
717                                                                                         dialog.getValueOf( 'replace', 'txtReplaceCaseChk' ),\r
718                                                                                         dialog.getValueOf( 'replace', 'txtReplaceWordChk' ),\r
719                                                                                         false, true ) )\r
720                                                                                 { /*jsl:pass*/ }\r
721 \r
722                                                                                 if ( finder.replaceCounter )\r
723                                                                                 {\r
724                                                                                         alert( editor.lang.findAndReplace.replaceSuccessMsg.replace( /%1/, finder.replaceCounter ) );\r
725                                                                                         editor.fire( 'saveSnapshot' );\r
726                                                                                 }\r
727                                                                                 else\r
728                                                                                         alert( editor.lang.findAndReplace.notFoundMsg );\r
729                                                                         }\r
730                                                                 }\r
731                                                         ]\r
732                                                 },\r
733                                                 {\r
734                                                         type : 'vbox',\r
735                                                         padding : 0,\r
736                                                         children :\r
737                                                         [\r
738                                                                 {\r
739                                                                         type : 'checkbox',\r
740                                                                         id : 'txtReplaceCaseChk',\r
741                                                                         isChanged : false,\r
742                                                                         label : editor.lang.findAndReplace\r
743                                                                                 .matchCase\r
744                                                                 },\r
745                                                                 {\r
746                                                                         type : 'checkbox',\r
747                                                                         id : 'txtReplaceWordChk',\r
748                                                                         isChanged : false,\r
749                                                                         label : editor.lang.findAndReplace\r
750                                                                                 .matchWord\r
751                                                                 },\r
752                                                                 {\r
753                                                                         type : 'checkbox',\r
754                                                                         id : 'txtReplaceCyclic',\r
755                                                                         isChanged : false,\r
756                                                                         'default' : true,\r
757                                                                         label : editor.lang.findAndReplace\r
758                                                                                 .matchCyclic\r
759                                                                 }\r
760                                                         ]\r
761                                                 }\r
762                                         ]\r
763                                 }\r
764                         ],\r
765                         onLoad : function()\r
766                         {\r
767                                 var dialog = this;\r
768 \r
769                                 //keep track of the current pattern field in use.\r
770                                 var patternField, wholeWordChkField;\r
771 \r
772                                 //Ignore initial page select on dialog show\r
773                                 var isUserSelect = false;\r
774                                 this.on('hide', function()\r
775                                                 {\r
776                                                         isUserSelect = false;\r
777                                                 } );\r
778                                 this.on('show', function()\r
779                                                 {\r
780                                                         isUserSelect = true;\r
781                                                 } );\r
782 \r
783                                 this.selectPage = CKEDITOR.tools.override( this.selectPage, function( originalFunc )\r
784                                         {\r
785                                                 return function( pageId )\r
786                                                 {\r
787                                                         originalFunc.call( dialog, pageId );\r
788 \r
789                                                         var currPage = dialog._.tabs[ pageId ];\r
790                                                         var patternFieldInput, patternFieldId, wholeWordChkFieldId;\r
791                                                         patternFieldId = pageId === 'find' ? 'txtFindFind' : 'txtFindReplace';\r
792                                                         wholeWordChkFieldId = pageId === 'find' ? 'txtFindWordChk' : 'txtReplaceWordChk';\r
793 \r
794                                                         patternField = dialog.getContentElement( pageId,\r
795                                                                 patternFieldId );\r
796                                                         wholeWordChkField = dialog.getContentElement( pageId,\r
797                                                                 wholeWordChkFieldId );\r
798 \r
799                                                         // prepare for check pattern text filed 'keyup' event\r
800                                                         if ( !currPage.initialized )\r
801                                                         {\r
802                                                                 patternFieldInput = CKEDITOR.document\r
803                                                                         .getById( patternField._.inputId );\r
804                                                                 currPage.initialized = true;\r
805                                                         }\r
806 \r
807                                                         if ( isUserSelect )\r
808                                                                 // synchronize fields on tab switch.\r
809                                                                 syncFieldsBetweenTabs.call( this, pageId );\r
810                                                 };\r
811                                         } );\r
812 \r
813                         },\r
814                         onShow : function()\r
815                         {\r
816                                 // Establish initial searching start position.\r
817                                 finder.searchRange = getSearchRange();\r
818 \r
819                                 this.selectPage( startupPage );\r
820                         },\r
821                         onHide : function()\r
822                         {\r
823                                 if ( finder.matchRange && finder.matchRange.isMatched() )\r
824                                 {\r
825                                         finder.matchRange.removeHighlight();\r
826                                         editor.focus();\r
827                                         editor.getSelection().selectRanges(\r
828                                                 [ finder.matchRange.toDomRange() ] );\r
829                                 }\r
830 \r
831                                 // Clear current session before dialog close\r
832                                 delete finder.matchRange;\r
833                         },\r
834                         onFocus : function()\r
835                         {\r
836                                 if ( startupPage == 'replace' )\r
837                                         return this.getContentElement( 'replace', 'txtFindReplace' );\r
838                                 else\r
839                                         return this.getContentElement( 'find', 'txtFindFind' );\r
840                         }\r
841                 };\r
842         };\r
843 \r
844         CKEDITOR.dialog.add( 'find', function( editor )\r
845                 {\r
846                         return findDialog( editor, 'find' );\r
847                 });\r
848 \r
849         CKEDITOR.dialog.add( 'replace', function( editor )\r
850                 {\r
851                         return findDialog( editor, 'replace' );\r
852                 });\r
853 })();\r