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