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