http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/HaloWordProcessor.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/HaloWordProcessor.as b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/HaloWordProcessor.as new file mode 100644 index 0000000..9360f0b --- /dev/null +++ b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/HaloWordProcessor.as @@ -0,0 +1,109 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling.ui +{ + import com.adobe.linguistics.utils.ITokenizer; + import com.adobe.linguistics.utils.TextTokenizer; + import com.adobe.linguistics.utils.Token; + + import flash.text.TextField; + import flash.text.TextFormat; + + import mx.controls.TextArea; + import mx.controls.TextInput; + + + public class HaloWordProcessor implements IWordProcessor + { + private var mTextField:TextField; + + public function HaloWordProcessor(textField:TextField) + { + if (textField == null ) throw new Error("illegal argument."); + mTextField = textField; + } + + + public function replaceText(startIndex:int, endIndex:int, replacement:String):void { + + if ( replacement == null ) return; + + if (mTextField.text.length<endIndex || startIndex<0) { + return; + } + + var _misspellStart:int = startIndex; + var _misspellEnd:int = endIndex; + // Try to preserve the format, this works if the whole misspelled word is the same format + var tf:TextFormat = mTextField.getTextFormat(_misspellStart, _misspellEnd); + mTextField.replaceText(_misspellStart, _misspellEnd, replacement); + mTextField.setTextFormat(tf, _misspellStart, _misspellStart+replacement.length); + + var ta:TextArea = mTextField.parent as TextArea; + var ti:TextInput = mTextField.parent as TextInput; + + if (ta != null) { + ta.selectionBeginIndex = _misspellStart + replacement.length; + ta.selectionEndIndex = _misspellStart + replacement.length; + } + else if (ti != null) { + ti.selectionBeginIndex = _misspellStart + replacement.length; + ti.selectionEndIndex = _misspellStart + replacement.length; + } + else { + // Do nothing if it's not a valid text component + } + } + + + public function getWordAtPoint(x:uint, y:uint, externalTokenizer:ITokenizer=null):Token + { + var _token:Token = tryGetWordAtPoint(x,y, externalTokenizer); + return _token; + } + + private function tryGetWordAtPoint(x:uint, y:uint, externalTokenizer:ITokenizer=null):Token { + // TODO: use a better alternative than _misspellStart, end + var index:uint = mTextField.getCharIndexAtPoint(x + mTextField.scrollH, y); + if (index >= mTextField.text.length) return null; + + var tmpToken:Token = new Token(index,index); + var tokenizer:ITokenizer; + if ( externalTokenizer == null ) { + tokenizer = new TextTokenizer(mTextField.text); + }else { + tokenizer = externalTokenizer; + } + + var result:Token = new Token(0,0); + + var preToken:Token = tokenizer.getPreviousToken(tmpToken); + var nextToken:Token = tokenizer.getNextToken(tmpToken); + if ( preToken.last == nextToken.first ) { + result.first = preToken.first; + result.last = nextToken.last; + return result; + }else { + return null; + } + } + + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/IHighlighter.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/IHighlighter.as b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/IHighlighter.as new file mode 100644 index 0000000..ea702da --- /dev/null +++ b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/IHighlighter.as @@ -0,0 +1,34 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling.ui +{ + import com.adobe.linguistics.utils.Token; + import __AS3__.vec.Vector; + import flash.geom.Point; + + public interface IHighlighter + { + function drawSquiggles(tokens:Vector.<Token>):void; + function clearSquiggles():void; + function set offsetPoint(op:Point):void; + function get offsetPoint():Point; + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/IWordProcessor.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/IWordProcessor.as b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/IWordProcessor.as new file mode 100644 index 0000000..919acbd --- /dev/null +++ b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/IWordProcessor.as @@ -0,0 +1,30 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling.ui +{ + import com.adobe.linguistics.utils.ITokenizer; + import com.adobe.linguistics.utils.Token; + + public interface IWordProcessor + { + function getWordAtPoint(x:uint, y:uint, externalTokenizer:ITokenizer=null):Token; + function replaceText(startIndex:int, endIndex:int, replacement:String):void; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SparkHighlighter.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SparkHighlighter.as b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SparkHighlighter.as new file mode 100644 index 0000000..f847584 --- /dev/null +++ b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SparkHighlighter.as @@ -0,0 +1,190 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling.ui +{ + import com.adobe.linguistics.utils.Token; + import com.adobe.linguistics.utils.TextTokenizer; + import flash.geom.Point; + + import flash.display.Shape; + import flash.geom.Rectangle; + import flash.text.engine.TextLine; + + import flashx.textLayout.compose.TextFlowLine; + import flashx.textLayout.edit.SelectionManager; + import flashx.textLayout.elements.TextFlow; + import flashx.textLayout.tlf_internal; + + import spark.components.RichEditableText; + use namespace tlf_internal; + + public class SparkHighlighter implements IHighlighter + { + + private var mTextField:RichEditableText; + private var mHighlighter:Shape; + /* + * offset point: + */ + private var _offsetPoint:Point; + + + public function SparkHighlighter( textField:RichEditableText ) + { + if (textField == null ) throw new Error("illegal argument."); + mTextField = textField; + mHighlighter = null; + this._offsetPoint = new Point(0,0); + } + + public function drawSquiggles(tokens:Vector.<Token>):void + { + spellCheckRange(tokens); + } + + public function clearSquiggles():void + { + if (mHighlighter) { + mTextField.removeChild(mHighlighter); + mHighlighter=null; + } + } + + public function set offsetPoint(op:Point):void{ + _offsetPoint = op; + } + + public function get offsetPoint():Point{ + return _offsetPoint; + } + + + + // TODO: refactor this code to share with halo components, and support words that cross lines + private function spellCheckRange(tokens:Vector.<Token>):void { + + var ta:RichEditableText = mTextField; + if (!ta) return; + + mHighlighter= new Shape(); + mHighlighter.graphics.clear(); + + for ( var i:int = 0; i< tokens.length; ++i ) { + var _token:Token = tokens[i]; + drawSquigglyLineForRange(_token.first, _token.last); + } + + // Just adjust the left padding, top padding is not an issue + //var pleft:uint = mTextField.getStyle("paddingLeft"); + //mHighlighter.x += pleft; + mTextField.addChild(mHighlighter); + + + } + + // Draw squiggly line + private function drawSquigglyLineForRange(start:Number, end:Number):void + { + // draw squiggly line + var tf:TextFlow = mTextField.textFlow; + var tflFirst:TextFlowLine = tf.flowComposer.findLineAtPosition(start); + var tflLast:TextFlowLine = tf.flowComposer.findLineAtPosition(end); + var tflIndexFirst:int = tf.flowComposer.findLineIndexAtPosition(start); + var tflIndexLast:int = tf.flowComposer.findLineIndexAtPosition(end); + + // Pointer + var tflIndex:int = tflIndexFirst; + var tfl:TextFlowLine = tflFirst; + + if (tflIndexFirst == tflIndexLast) { + // Draw one line + drawSquigglyLineAtIndex(tflIndexFirst, start - tflFirst.absoluteStart, end - tflFirst.absoluteStart); + } else { + // Multiple lines (very long word) + drawSquigglyLineAtIndex(tflIndexFirst, start - tflFirst.absoluteStart); + + tflIndex++; + while (tflIndex != tflIndexLast) { + drawSquigglyLineAtIndex(tflIndex); + tflIndex++; + } + + drawSquigglyLineAtIndex(tflIndexLast, 0, end - tflLast.absoluteStart); + } + } + + // Draw a squiggly line at specific line for specific index range + private function drawSquigglyLineAtIndex(lineIndex:Number, startIndex:Number=0, endIndex:Number=0x7FFFFFFF):void + { + var tf:TextFlow = mTextField.textFlow; + var tfl:TextFlowLine = tf.flowComposer.getLineAt(lineIndex); + var rectLine:Rectangle = tfl.getBounds(); + if (endIndex == 0x7FFFFFFF) { + drawSquigglyLineAtPoint(rectLine.left, rectLine.bottom, rectLine.right - rectLine.left); + } + else { + // Force to have a valid TextLine + var tl:TextLine = tfl.getTextLine(true); + + // TODO: atom index and char index is not matching for some chars, use try/catch to avoid crash + try { + var rectFirst:Rectangle = tl.getAtomBounds(startIndex); + var rectLast:Rectangle = tl.getAtomBounds(endIndex); + drawSquigglyLineAtPoint(rectFirst.left + tfl.x, rectLine.bottom, rectLast.right - rectFirst.left); + } catch (err:Error) + { + //TODO: report error + } + } + + } + // Draw a squiggly from point x,y with given width, the line is drew in mHighlighter + private function drawSquigglyLineAtPoint(x:Number, y:Number, width:Number):void + { + mHighlighter.graphics.lineStyle(1, 0xfa0707, .65); + mHighlighter.graphics.moveTo(x, y); + var upDirection:Boolean = false; + var offset:uint = 0; + var stepLength:uint = 2; + for ( var i:uint = 1; offset <= width; i++) { + offset = offset + stepLength; + if ( upDirection ) + mHighlighter.graphics.lineTo(x+offset,y); + else + mHighlighter.graphics.lineTo(x+offset,y+stepLength); + upDirection = !upDirection; + } + } + + private function getValidFirstWordIndex():int{ + var index:int = SelectionManager.computeSelectionIndex(mTextField.textFlow, mTextField, mTextField, 0 + mTextField.horizontalScrollPosition, 0 + mTextField.verticalScrollPosition); + return index; + + + } + + private function getValidLastWordIndex():int{ + var index:int = SelectionManager.computeSelectionIndex(mTextField.textFlow, mTextField, mTextField, mTextField.width+mTextField.horizontalScrollPosition, mTextField.height+mTextField.verticalScrollPosition); + return index; + + } + + } +} http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SparkWordProcessor.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SparkWordProcessor.as b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SparkWordProcessor.as new file mode 100644 index 0000000..81e155b --- /dev/null +++ b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SparkWordProcessor.as @@ -0,0 +1,104 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling.ui +{ + import com.adobe.linguistics.utils.TextTokenizer; + import com.adobe.linguistics.utils.Token; + import com.adobe.linguistics.utils.ITokenizer; + + import flashx.textLayout.edit.SelectionManager; + import flashx.textLayout.tlf_internal; + + import spark.components.RichEditableText; + + use namespace tlf_internal; + + public class SparkWordProcessor implements IWordProcessor + { + private var mTextField:RichEditableText; + + public function SparkWordProcessor(textField:RichEditableText) + { + if (textField == null ) throw new Error("illegal argument."); + mTextField = textField; + } + + + public function replaceText(startIndex:int, endIndex:int, replacement:String):void { + var ta:RichEditableText = mTextField; + var end:int = getValidLastWordIndex(); + + if ( replacement == null ) return; + + if (mTextField.text.length<endIndex || startIndex<0) { + return; + } + + var _misspellStart:int = startIndex; + var _misspellEnd:int = endIndex; + + // Workaround for Spark: changes in inactive components will trigger strange behavior + ta.setFocus(); + ta.text = ta.text.substr(0, _misspellStart) + replacement + ta.text.substr(_misspellEnd); + ta.textFlow; + ta.selectRange(_misspellStart + replacement.length, _misspellStart + replacement.length); + + // Workaround for unexpected jump + ta.scrollToRange(end, end); + } + + public function getWordAtPoint(x:uint, y:uint, externalTokenizer:ITokenizer=null):Token + { + // TODO: use a better alternative than _misspellStart, end + var ta:RichEditableText = mTextField; + var index:int = SelectionManager.computeSelectionIndex(ta.textFlow, ta, ta, x, y); + + if (index >= ta.text.length) return null; + + var tmpToken:Token = new Token(index,index); + var tokenizer:ITokenizer; + if ( externalTokenizer == null ) { + tokenizer = new TextTokenizer(mTextField.text); + }else { + tokenizer = externalTokenizer; + } + + var result:Token = new Token(0,0); + var preToken:Token = tokenizer.getPreviousToken(tmpToken); + var nextToken:Token = tokenizer.getNextToken(tmpToken); + if ( preToken.last == nextToken.first ) { + result.first = preToken.first; + result.last = nextToken.last; + return result; + }else { + return null; + } + + } + + // TODO: workaround for unexpected jump when word replaced, to be refactored for code sharing + private function getValidLastWordIndex():int{ + var index:int = SelectionManager.computeSelectionIndex(mTextField.textFlow, mTextField, mTextField, mTextField.width+mTextField.horizontalScrollPosition, mTextField.height+mTextField.verticalScrollPosition); + return index; + } + + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SpellingHighlighter.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SpellingHighlighter.as b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SpellingHighlighter.as new file mode 100644 index 0000000..2292a61 --- /dev/null +++ b/Squiggly/main/SpellingUIAPI/src/com/adobe/linguistics/spelling/ui/SpellingHighlighter.as @@ -0,0 +1,179 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling.ui +{ + import flash.display.Shape; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.text.TextLineMetrics; + + import mx.core.IUITextField; + import mx.flash.UIMovieClip; + + + public class SpellingHighlighter extends UIMovieClip + { + /* + * offset point: + */ + private var _offsetPoint:Point; + + /* + * Target TextField. + */ + private var _textField:IUITextField; + private static var InvalidIndexValue:int = -2; + public function SpellingHighlighter(textField:IUITextField) { + super(); + this._textField = textField; + this._offsetPoint = new Point(0,0); + } + + public function drawSquigglyLine(firstCharIndex:int, lastCharIndex:int ):void { + var validFirstCharIndex:int = getValidFirstCharIndex(firstCharIndex); + var validLastCharIndex:int = getValidLastCharIndex(lastCharIndex); + if ( validFirstCharIndex == InvalidIndexValue || validLastCharIndex == InvalidIndexValue ){ + return; + } + /* draw squiggly line here. */ + if ( validFirstCharIndex <= validLastCharIndex ) { + var firstLine:int = _textField.getLineIndexOfChar(validFirstCharIndex); + var lastLine:int = _textField.getLineIndexOfChar(validLastCharIndex); + //only one line case. + if(lastLine==firstLine) + { + drawSingleSquigglyLine(validFirstCharIndex, validLastCharIndex); + return; + } + //more than one line. + //first line + drawSingleSquigglyLine(validFirstCharIndex, _textField.getLineOffset(firstLine)+_textField.getLineLength(firstLine)-1); + //middle.... + for(var i:int=firstLine+1;i<lastLine;i++) + { + drawSingleSquigglyLine(_textField.getLineOffset(i), _textField.getLineOffset(i)+_textField.getLineLength(i)-1); + } + //last lines. + drawSingleSquigglyLine(_textField.getLineOffset(lastLine), validLastCharIndex); + } + } + + public function drawSingleSquigglyLine(firstCharIndex:int, lastCharIndex:int ):void { + var firstLine:int = _textField.getLineIndexOfChar(firstCharIndex); + var lastLine:int = _textField.getLineIndexOfChar(lastCharIndex); + if ( firstLine != lastLine ) { + return; + }else { + var rect1:Rectangle = _textField.getCharBoundaries(firstCharIndex); + var rect2:Rectangle = _textField.getCharBoundaries(lastCharIndex); + var x:Number = rect1.x+_offsetPoint.x - _textField.scrollH; + var y:Number = rect1.y + rect1.height + 2; + var width:Number = rect2.x+rect2.width-rect1.x; + + // Avoid drawing outside the textField + if (x<0) + { + if (x+width > 0) { + width += x; + x = 0; + } + else + return; + } + if (x+width > _textField.width) + { + if (x < _textField.width) { + width = textField.width - x; + } + else + return; + } + + // The rectangle that bound the string you want + // actual work here. + var myShape:Shape = new Shape(); + myShape.graphics.clear(); + //myShape.graphics.beginFill(0x0099CC, .35); + myShape.graphics.lineStyle(1, 0xfa0707, .65); + myShape.graphics.moveTo(x, y); + var upDirection:Boolean = false; + var offset:uint = 0; + var stepLength:uint = 2; + for ( var i:uint = 1; offset <= width; i++) { + offset = offset + stepLength; + if ( upDirection ) + myShape.graphics.lineTo(x+offset,y); + else + myShape.graphics.lineTo(x+offset,y+stepLength); + upDirection = !upDirection; + } + //myShape.graphics.endFill(); + this.addChild(myShape); + } + } + + private function getValidFirstCharIndex(firstCharIndex:int):int{ + if(firstCharIndex<0 || firstCharIndex>_textField.text.length-1) + { + return InvalidIndexValue; + } + var firstLine:Number = _textField.getLineIndexOfChar(firstCharIndex); + + if(firstLine<_textField.scrollV-1) + { + firstLine = _textField.scrollV-1; + return _textField.getLineOffset(firstLine); + } + return firstCharIndex; + } + + private function getValidLastCharIndex(lastCharIndex:int):int{ + if(lastCharIndex<0 || lastCharIndex>_textField.text.length-1) + { + return InvalidIndexValue; + } + var lastLine:Number = _textField.getLineIndexOfChar(lastCharIndex); + if(lastLine>_textField.bottomScrollV-1) + { + lastLine = _textField.bottomScrollV-1; + return _textField.getLineOffset(lastLine)+_textField.getLineLength(lastLine)-1; + } + return lastCharIndex; + } + + public function set textField(tf:IUITextField):void{ + _textField = tf; + } + + public function get textField():IUITextField{ + return _textField; + } + + public function set offsetPoint(op:Point):void{ + _offsetPoint = op; + } + + public function get offsetPoint():Point{ + return _offsetPoint; + } + + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUIEx/src/com/adobe/linguistics/spelling/SpellUI.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUIEx/src/com/adobe/linguistics/spelling/SpellUI.as b/Squiggly/main/SpellingUIEx/src/com/adobe/linguistics/spelling/SpellUI.as new file mode 100644 index 0000000..69c9aab --- /dev/null +++ b/Squiggly/main/SpellingUIEx/src/com/adobe/linguistics/spelling/SpellUI.as @@ -0,0 +1,577 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling +{ + import com.adobe.linguistics.spelling.UserDictionary; + import com.adobe.linguistics.spelling.framework.ResourceTable; + import com.adobe.linguistics.spelling.framework.SpellingConfiguration; + import com.adobe.linguistics.spelling.framework.SpellingService; + import com.adobe.linguistics.spelling.ui.HaloHighlighter; + import com.adobe.linguistics.spelling.ui.IHighlighter; + import com.adobe.linguistics.spelling.ui.SparkHighlighter; + import com.adobe.linguistics.spelling.ui.HaloWordProcessor; + import com.adobe.linguistics.spelling.ui.IWordProcessor; + import com.adobe.linguistics.spelling.ui.SparkWordProcessor; + import com.adobe.linguistics.utils.TextTokenizer; + import com.adobe.linguistics.utils.Token; + + import flash.events.Event; + import flash.events.FocusEvent; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.net.SharedObject; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.text.TextField; + import flash.utils.Dictionary; + + import mx.controls.RichTextEditor; + import mx.controls.TextArea; + import mx.controls.TextInput; + import mx.core.UIComponent; + import mx.core.mx_internal; + import mx.events.ScrollEvent; + + import spark.components.RichEditableText; + import spark.components.TextArea; + import spark.components.TextInput; + + import flashx.textLayout.tlf_internal; + import flashx.textLayout.compose.TextFlowLine; + import flashx.textLayout.edit.SelectionManager; + import flashx.textLayout.elements.TextFlow; + + use namespace mx_internal; + + use namespace tlf_internal; + /** + * UI implementation for spelling. + * + * <p>This class is a simple UI for some standard Flex UI components. + * It is not intended to address a complete user interface. + * Instead, it presents a basic user interface for some commonly used Flex UI components.</p> + * + * <p>For advanced text editing applications, more complex features are likely required. + * For those applications, we recommend bypassing this class and utilizing the <code>SpellChecker</code> class directly.</p> + * + * @playerversion Flash 10 + * @langversion 3.0 + */ + public class SpellUI + { + private var hh:IHighlighter; + private var hw:IWordProcessor; + + private var _checkLastWord:Boolean = true; + private var _spellingEnabled:Boolean; + + private var _actualParent:*; + private var isHaloComponent:Boolean; + private var isSparkComponent:Boolean; + + //New Added below + private var mTextField:*; + + //private var mTextField:RichEditableText; + + private var _dictname:String = new String(); + private var _hundict:HunspellDictionary = new HunspellDictionary(); + private var _userdict:UserDictionary = null; + private var _sharedobj:SharedObject = null; + private var scm:SpellingContextMenu; + + private var _newchecker:SpellChecker = null; + private var _resource_locale:Object = null; + private var _spellingservice:SpellingService = null; + + private static var _contextMenuEntries:Object = {enable:"Enable Spelling", disable:"Disable Spelling", add:"Add to dictionary"}; + private static var _spellingConfigUrl:String = "AdobeSpellingConfig.xml"; + private static var _UITable:Dictionary= new Dictionary(); + private static var _parentTable:Dictionary= new Dictionary(); + private static var _cacheDictTable:Dictionary= new Dictionary(); + + private static var _configXML:XML = null; + private static var _configXMLLoading:Boolean = false; + private static var _configXMLLoader:URLLoader = new URLLoader(); + + // Work around for the memory usage problem, ideally a better fix is to provide a dicitonary unload function + private static var _cache:Object = new Object(); + + /** + * Enables the spell checking feature for a UI component. + * + * <p>Note: This version provides only enabling function but no disabling function.</p> + * + * @param comp A text editing Flex UI component. + It can be a <code>TextArea</code>, <code>TextInput</code> or <code>RichTextEditor</code>. + * @param dict A URL for the dictionary to be used with the <code>SpellChecker.</code> + * + * @includeExample Examples/Flex/SquigglyUIExample/src/SquigglyUIExample.mxml + * @playerversion Flash 10 + * @langversion 3.0 + */ + public static function enableSpelling(comp:UIComponent, lang:String):void + { + if ( lang == null ) return; + // TODO: Change dict parameter type to a SpellCheck class or a URL string. + var txt:* = getComponentTextModel(comp); + /*var comp1:UIComponent = txt.parent; + var comp2:UIComponent = txt.owner; + var comp3:UIComponent = txt.parentApplication; + var comp4:UIComponent = txt.parentDocument; + var comp5:UIComponent = txt.parentDocument.hostComponent; <--spark parent UICOmponent*/ + if ( txt==null || _UITable[comp]!=undefined ) + return; + + // TODO: dangerous, is garbage collection going to clear this? + _UITable[comp]=new SpellUI(txt, lang); + _parentTable[txt] = comp; + _cacheDictTable[comp]=lang; + } + + // Customize the spelling related entry in spelling contextMenu + public static function setSpellingMenuEntries(entries:Object):Boolean + { + if (entries.enable && entries.disable && entries.add && (entries.enable != "") && (entries.disable != "") && (entries.add != "")) + { + _contextMenuEntries = entries; + return true; + } + else + return false; + } + + /** + * Get the spelling context menu entries. + * + * @return A flex <code>Object</code> containing the spelling context menu entries. If you haven't customized the entries, you get the default associative array <code>{enable:"Enable Spelling", disable:"Disable Spelling", add:"Add to dictionary"}</code> + * @playerversion Flash 10 + * @langversion 3.0 + */ + public static function getSpellingMenuEntries():Object + { + return _contextMenuEntries; + } + + /** + * The URL for the spelling config xml file. If you haven't specify it, the default URL is [applicationDirectory]/AdobeSpellingConfig.xml. Note that we don't validate the URL, if the file doesn't exist, you will get an error when calling enableSpelling() function. + * + * @example The following code customize the spellingConfigUrl before enabling spell checking. + * <listing version="3.0"> + * SpellUI.spellingConfigUrl = "./config/MySpellingConfig.xml"; + * SpellUI.enableSpelling(textArea, "en_US"); + * </listing> + */ + public static function get spellingConfigUrl():String + { + return _spellingConfigUrl; + } + + public static function set spellingConfigUrl(url:String):void + { + if (url == null) throw new Error("URL can't be null"); + _spellingConfigUrl = url; + } + + + + /** + * Disable the spell checking feature for a UI component. + * + * @param comp A text editing Flex UI component. + * @playerversion Flash 10 + * @langversion 3.0 + */ + public static function disableSpelling(comp:UIComponent):void{ + if ( _UITable[comp] == undefined ) + return; + var _ui:SpellUI = _UITable[comp]; + if ( _ui != null) _ui.cleanUp(); + var dictName:String = _cacheDictTable[comp]; + var cleanUPDictionaryCount:int = 0; + for each ( var _dictName:String in _cacheDictTable ) { + if ( _dictName == dictName ) + cleanUPDictionaryCount++; + } + if ( cleanUPDictionaryCount == 1 ) { + _cache[dictName] = undefined; + } + delete _UITable[comp]; + delete _cacheDictTable[comp]; + + } + + /** + @private + (This property is for Squiggly Developer use only.) + */ + public static function get UITable():Dictionary { + return _UITable; + } + + /** + @private + (This property is for Squiggly Developer use only.) + */ + public function set spellingEnabled(value:Boolean):void { + _spellingEnabled = value; + } + + /** + @private + (This property is for Squiggly Developer use only.) + */ + public static function get parentComp():Dictionary { + return _parentTable; + } + + private static function getComponentTextModel(comp:UIComponent):* { + var txt:TextField = null; + var txt2:RichEditableText = null; + if ( (comp == null) || !( (comp is mx.controls.TextArea) || (comp is mx.controls.TextInput) || (comp is RichTextEditor) + || (comp is spark.components.TextArea) || (comp is spark.components.TextInput) || (comp is spark.components.RichEditableText)) ) + return null; + if ((comp as RichTextEditor) != null) { + txt = (comp as RichTextEditor).textArea.getTextField() as TextField; + } + else if ((comp as mx.controls.TextArea) != null){ + txt = (comp as mx.controls.TextArea).getTextField() as TextField; + } + else if ((comp as mx.controls.TextInput) != null) { + txt = (comp as mx.controls.TextInput).getTextField() as TextField; + } + else if ((comp as spark.components.TextArea) != null) { + if ((comp as spark.components.TextArea).textDisplay is TextField) + txt = (comp as spark.components.TextArea).textDisplay as TextField; + else + txt2 = (comp as spark.components.TextArea).textDisplay as RichEditableText; + } + else if ((comp as spark.components.TextInput) != null) { + if ((comp as spark.components.TextInput).textDisplay is TextField) + txt = (comp as spark.components.TextInput).textDisplay as TextField; + else + txt2 = (comp as spark.components.TextInput).textDisplay as RichEditableText; + } + else if ((comp as spark.components.RichEditableText) !=null) { + txt2 = comp as RichEditableText; + } + else { + // do nothing if it's not a valid text component + return null; + } + if (txt != null) + return txt; + else + return txt2; + } + + /** + * Constructs a SpellUI object. + * + * @param textFiled A Flex UI component to include spell-check capability + * @param dict A URL for Squiggly spelling dictionary. + * + * @playerversion Flash 10 + * @langversion 3.0 + */ + public function SpellUI(textModel:*, lang:String) + { + // TODO: Consider making this method invisible to user, only expose the static function. + if ( textModel is TextField ) { + isHaloComponent=true; + //New Added below -- check if text field needs to be extracted from textModel + //mTextField = textModel; + }else if (textModel is RichEditableText ) { + isSparkComponent=true; + textModel.setFocus(); + textModel.selectRange(textModel.text.length, textModel.text.length); + //New Added below -- check if text field needs to be extracted from textModel + //mTextField = textModel ; + }else { + // do nothing, we only accept textField and TextFlow here.... + return; + } + _actualParent = textModel; + mTextField = textModel ; + + mTextField.addEventListener(FocusEvent.FOCUS_OUT, handleFocusOut); + mTextField.addEventListener(FocusEvent.FOCUS_IN, handleFocusIn); + mTextField.addEventListener(ScrollEvent.SCROLL, spellCheckScreen); + mTextField.parent.addEventListener(Event.RENDER, spellCheckScreen); + mTextField.parent.addEventListener(Event.CHANGE, handleChangeEvent); + _dictname = lang; + loadConfig(); + + } + + private function spellCheckScreen(event:Event):void + { + doSpellingJob(); + } + + private function handleFocusOut(event:FocusEvent):void + { + _checkLastWord = true; + doSpellingJob(); + } + + private function handleFocusIn(event:FocusEvent):void + { + _checkLastWord = false; + doSpellingJob(); + } + + private function handleChangeEvent( event:Event ) :void { + _checkLastWord = false; + spellCheckScreen(event); + } + + /*private function doSpelling():void + { + _checkLastWord = true; + doSpellingJob(); + }*/ + + /** + @private + (This property is for Squiggly Developer use only.) + */ + public function doSpellingJob():void + { + if (_spellingEnabled == false) return; + spellCheckRange(getValidFirstWordIndex(), getValidLastWordIndex()); + } + + private function spellCheckRange(start:uint, end:uint):void { + //hh.preSpellCheckRange(start, end); + hh.clearSquiggles(); + if ( isHaloComponent ) { + //if (end <= start) return; + var firstLine:int = mTextField.getLineIndexOfChar(start); + var rect:Rectangle = mTextField.getCharBoundaries(start); + var counter:uint = start; + var numLines:Number = 0; + + /* mTextField.getCharBoundaries returns null for blank lines and for end of line characters. Placing this workaround + to count line heights until a non-null bounding rectangle is found */ + while (rect == null) { + if (++counter > end) { + rect = new Rectangle(0,0,0,0); + break; + } + numLines += mTextField.getLineMetrics(firstLine).height; + firstLine++; + rect = mTextField.getCharBoundaries(counter); + } + + var yoffset:Number = rect.y - numLines; + var pleft:uint = (mTextField.parent as UIComponent).getStyle("paddingLeft"); + var ptop:uint = (mTextField.parent as UIComponent).getStyle("paddingTop"); + + var offsetPoint:Point = new Point(pleft, ptop-yoffset); + + hh.offsetPoint = offsetPoint; + } + + var tokenizer:TextTokenizer = new TextTokenizer(mTextField.text.substring(start,end)); + var tokens:Vector.<Token> = new Vector.<Token>(); + + for ( var token:Token = tokenizer.getFirstToken(); token != tokenizer.getLastToken(); token= tokenizer.getNextToken(token) ) { + var result:Boolean=_spellingservice.checkWord(mTextField.text.substring(token.first+start, token.last+start)); + if (!result){ + if (_checkLastWord || (token.last+start != mTextField.text.length)) + //hh.highlightWord(token.first+start, token.last+start-1); + //tokens.push(new Token(token.first+start, token.last+start-1)); + hh.drawSquiggleAt(new Token(token.first+start, token.last+start-1)); + } + + } + //hh.postSpellCheckRange(start, end); + //hh.offsetPoint = offsetPoint; + //hh.drawSquiggles(tokens); + } + + + + private function getValidFirstWordIndex():int{ + var index:int; + if ( mTextField is TextField ) { + index = mTextField.getLineOffset(mTextField.scrollV-1); + }else if (mTextField is RichEditableText ) { + // Check for computeSelectionIndexInContainer which throws when lineindex == 0 + try { + index = SelectionManager.computeSelectionIndex(mTextField.textFlow, mTextField, mTextField, 0 + mTextField.horizontalScrollPosition, 0 + mTextField.verticalScrollPosition); + + } catch (err:Error) + { + //TODO: report error + index = 0; + } + } + return index; + } + + private function getValidLastWordIndex():int{ + var index:int; + if ( mTextField is TextField ) { + index = mTextField.getLineOffset(mTextField.bottomScrollV-1)+mTextField.getLineLength(mTextField.bottomScrollV-1); + }else if (mTextField is RichEditableText ) { + // Check for computeSelectionIndexInContainer which throws when lineindex == 0 + try { + index = SelectionManager.computeSelectionIndex(mTextField.textFlow, mTextField, mTextField, mTextField.width+mTextField.horizontalScrollPosition, mTextField.height+mTextField.verticalScrollPosition); + } catch (err:Error) + { + //TODO: report error + index = 0; + } + } + return index; + } + + private function loadConfig():void{ + _resource_locale = SpellingConfiguration.resourceTable.getResource(_dictname); + + if ((_resource_locale != null) || (SpellUI._configXML != null)) + loadConfigComplete(null); + else { + SpellUI._configXMLLoader.addEventListener(Event.COMPLETE, loadConfigComplete); + + if (SpellUI._configXMLLoading == false) + { + SpellUI._configXMLLoader.load(new URLRequest(_spellingConfigUrl)); + SpellUI._configXMLLoading = true; + } + } + } + + private function loadConfigComplete(evt:Event):void{ + if (_resource_locale == null) { + if (SpellUI._configXML == null) + SpellUI._configXML= new XML(evt.target.data); + + SpellingConfiguration.resourceTable.setResource(_dictname,{rule:SpellUI._configXML.LanguageResource.(@languageCode==_dictname).@ruleFile, + dict:SpellUI._configXML.LanguageResource.(@languageCode==_dictname).@dictionaryFile}); + } + //New Added + _spellingservice = new SpellingService(_dictname); + _spellingservice.addEventListener(Event.COMPLETE, loadDictComplete); + _spellingservice.init(); + } + + + + + private function loadDictComplete(evt:Event):void + { + //_newchecker = new SpellChecker(_hundict); + + // Lazy loading the UD only when the main dict is loaded successfully + if ((SpellUI._cache["Squiggly_UD"] as UserDictionary) == null) + { + _sharedobj = SharedObject.getLocal("Squiggly_v03"); + var vec:Vector.<String> = new Vector.<String>(); + if (_sharedobj.data.ud) { + for each (var w:String in _sharedobj.data.ud) + vec.push(w); + } + _userdict = new UserDictionary(vec); + + SpellUI._cache["Squiggly_SO"] = _sharedobj; + SpellUI._cache["Squiggly_UD"] = _userdict; + } + else + { + _sharedobj = SpellUI._cache["Squiggly_SO"]; + _userdict = SpellUI._cache["Squiggly_UD"]; + } + _spellingservice.addUserDictionary(_userdict); + + + // Add the context menu, this might be not successful + scm = null; + try { + addContextMenu(null); + } + catch (err:Error) + { + // TODO: error handling here + } + _actualParent.addEventListener(Event.ADDED_TO_STAGE, addContextMenu); + } + + + private function addContextMenu(event:Event):void + { + if ( scm != null ) return; + if ( isHaloComponent ) { + hh= new HaloHighlighter( _actualParent); + hw= new HaloWordProcessor( _actualParent ); + }else if ( isSparkComponent ){ + hh = new SparkHighlighter( _actualParent); + hw = new SparkWordProcessor( _actualParent); + } else { + trace("error now, later will be true"); + } + + scm = new SpellingContextMenu(hh, hw, _spellingservice, _actualParent, _actualParent.contextMenu); + scm.setIgnoreWordCallback( addWordToUserDictionary ); + + // Halo need this + if (_actualParent.contextMenu == null) + { + _actualParent.contextMenu = scm.contextMenu; + } + + //hh.spellingEnabled=true; + _spellingEnabled = true; + try { + doSpellingJob(); + } + catch (err:Error) + { + // If it fails here, it should later triggered by the render event, so no need to do anything + } + } + + private function addWordToUserDictionary(word:String):void + { + _userdict.addWord(word); + + // TODO: serialization here might affect ther performance + _sharedobj.data.ud = _userdict.wordList; + + } + /** + * @private + */ + private function cleanUp():void { + hh.clearSquiggles(); + scm.cleanUp(); + _actualParent.removeEventListener(Event.ADDED_TO_STAGE, addContextMenu); + + mTextField.removeEventListener(ScrollEvent.SCROLL, spellCheckScreen); + mTextField.parent.removeEventListener(Event.RENDER, spellCheckScreen); + mTextField.parent.removeEventListener(Event.CHANGE, handleChangeEvent); + mTextField.removeEventListener(FocusEvent.FOCUS_OUT, handleFocusOut); + mTextField.removeEventListener(FocusEvent.FOCUS_IN, handleFocusIn); + } + + } +} http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUIEx/src/com/adobe/linguistics/spelling/SpellingContextMenu.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUIEx/src/com/adobe/linguistics/spelling/SpellingContextMenu.as b/Squiggly/main/SpellingUIEx/src/com/adobe/linguistics/spelling/SpellingContextMenu.as new file mode 100644 index 0000000..fc71ce3 --- /dev/null +++ b/Squiggly/main/SpellingUIEx/src/com/adobe/linguistics/spelling/SpellingContextMenu.as @@ -0,0 +1,243 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling +{ + import com.adobe.linguistics.spelling.SpellUI; + import com.adobe.linguistics.spelling.framework.SpellingService; + import com.adobe.linguistics.spelling.ui.*; + import com.adobe.linguistics.utils.Token; + import com.adobe.linguistics.utils.TextTokenizer; + + import flash.events.ContextMenuEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + + import flash.ui.ContextMenu; + import flash.ui.ContextMenuItem; + + import flash.text.TextField; + import mx.core.UIComponent; + + import flash.geom.Point; + import flash.geom.Rectangle; + + public class SpellingContextMenu + { + private var disableMenuItem:ContextMenuItem = new ContextMenuItem("Disable spell checking",true); + private var enableMenuItem:ContextMenuItem = new ContextMenuItem("Enable spell checking"); + private var controlMenuItemList:Array = new Array(); + private var suggestionMenuItemList:Array = new Array(); + private var _spellingEnabled:Boolean; + private var _contextMenu:ContextMenu; + private var mTextHighlighter:IHighlighter; + private var mWordProcessor:IWordProcessor; + private var mSpellEngine:SpellingService; + private var mParentTextField:*; + private var _ignoreWordFunctionProcessor:Function; + private var _misspelledToken:Token; + private var _misspelled:String; + public function SpellingContextMenu(textHighlighter:IHighlighter, wordProcessor:IWordProcessor, engine:SpellingService, actualParent:*, contextMenu:ContextMenu=null) + { + + if ( textHighlighter == null || wordProcessor == null || engine == null) throw new Error("illegal argument."); + mTextHighlighter = textHighlighter; + mWordProcessor = wordProcessor; + mSpellEngine = engine; + mParentTextField = actualParent; + if (contextMenu != null) { + _contextMenu =contextMenu; + }else { + _contextMenu = new ContextMenu(); + } + enableMenuItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handleEnableSpellCheck); + disableMenuItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handleDisableSpellCheck); + controlMenuItemList.push(enableMenuItem); + controlMenuItemList.push(disableMenuItem); + + _contextMenu.customItems.push(disableMenuItem); + _contextMenu.customItems.push(enableMenuItem); + spellingEnabled = true; //default value + //spellingEnabled= mTextHighlighter.spellingEnabled; + _contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, handleContextMenuSelect); + _ignoreWordFunctionProcessor=null; + } + + public function cleanUp():void + { + mTextHighlighter=null; + mWordProcessor=null; + spellingEnabled = false; + _ignoreWordFunctionProcessor=null; + + _contextMenu.removeEventListener(ContextMenuEvent.MENU_SELECT, handleContextMenuSelect); + + var removedNum:int = 0; + var count:uint = _contextMenu.customItems.length; + for (var j:uint=count; j>0; j--) { + if ( isWordItem(_contextMenu.customItems[j-1]) || isControlItem(_contextMenu.customItems[j-1]) ) { + _contextMenu.customItems.splice(j-1,1); + removedNum++ + } + } + if ( removedNum != suggestionMenuItemList.length + controlMenuItemList.length ) { + trace("internal error"); + } + + suggestionMenuItemList = null; + controlMenuItemList = null; + } + + public function get contextMenu():ContextMenu { + return this._contextMenu; + } + + private function handleContextMenuSelect(event:ContextMenuEvent):void + { + /* Clear the context menu */ + //spellingEnabled= mTextHighlighter.spellingEnabled; + //SpellUI.doSpelling1(); + var removedNum:int = 0; + var count:uint = _contextMenu.customItems.length; + for (var j:uint=count; j>0; j--) { + if ( isWordItem(_contextMenu.customItems[j-1]) ) { + _contextMenu.customItems.splice(j-1,1); + removedNum++ + } + } + if ( removedNum != suggestionMenuItemList.length ) { + trace("internal error"); + } + + + suggestionMenuItemList = new Array(); + + // localized entries + var entries:Object = SpellUI.getSpellingMenuEntries(); + disableMenuItem.caption = entries.disable; + enableMenuItem.caption = entries.enable; + + if (spellingEnabled == true) { + _misspelledToken = mWordProcessor.getWordAtPoint(mParentTextField.mouseX, mParentTextField.mouseY); + if (_misspelledToken==null) return; + _misspelled = mParentTextField.text.substring(_misspelledToken.first, _misspelledToken.last); + if ((_misspelled==null) || (_misspelled == "")) return; + + if (mSpellEngine.checkWord(_misspelled)==true) return; + + var suseAddToItem:ContextMenuItem = new ContextMenuItem(entries.add); + suseAddToItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handleAddToItemSelect); + suggestionMenuItemList.push(suseAddToItem); + _contextMenu.customItems.splice(0,0,suseAddToItem); + //var result:Array = mWordProcessor.getSuggestionsAtPoint(); + var resultVector:Vector.<String> = mSpellEngine.getSuggestions(_misspelled); + var result:Array = new Array(); + if (resultVector) { + for each (var w:String in resultVector) + result.push(w); + } + if (result!=null) { + for (var i:int=result.length-1;i>=0;i-- ) { + var suseMenuItem:ContextMenuItem = new ContextMenuItem(result[i]); + suseMenuItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handleSuseItemSelect); + suggestionMenuItemList.push(suseMenuItem); + //_contextMenu.customItems.push(suseMenuItem); + _contextMenu.customItems.splice(0,0,suseMenuItem); + } + } + } + + + + } + + public function setIgnoreWordCallback(func:Function ) :void { + if ( func != null ) + _ignoreWordFunctionProcessor = func; + } + + private function handleAddToItemSelect(event:ContextMenuEvent):void + { + if ( _ignoreWordFunctionProcessor == null ) return; + + /* + var menuEntry:String = (event.currentTarget as ContextMenuItem).caption; + var start:uint = 5; + var end:uint = menuEntry.length - 15; + var word:String = menuEntry.substring(start, end); + */ + _ignoreWordFunctionProcessor(_misspelled); + //SpellUI.UITable[SpellUI.parentComp[mParentTextField]].doSpellingJob(); + //now implicitly calling dospelling on all text areas + for each (var tempUIComponent:SpellUI in SpellUI.UITable) + { + tempUIComponent.doSpellingJob(); + } + } + + private function isWordItem(item:ContextMenuItem):Boolean { + + for ( var i:int=0; i<suggestionMenuItemList.length; ++i ) { + if ( suggestionMenuItemList[i] == item ) return true; + } + return false; + } + + private function isControlItem(item:ContextMenuItem):Boolean { + for (var i:int=0; i<controlMenuItemList.length; ++i) { + if ( controlMenuItemList[i] == item) return true; + } + return false; + } + + private function handleSuseItemSelect(event:ContextMenuEvent):void + { + mWordProcessor.replaceText(_misspelledToken, (event.currentTarget as ContextMenuItem).caption ); + SpellUI.UITable[SpellUI.parentComp[mParentTextField]].doSpellingJob(); + } + + + private function set spellingEnabled(value:Boolean) :void { + _spellingEnabled = value; + disableMenuItem.visible=spellingEnabled; + enableMenuItem.visible=!spellingEnabled; + } + private function get spellingEnabled():Boolean { + return this._spellingEnabled; + } + private function handleEnableSpellCheck(event:ContextMenuEvent):void + { + spellingEnabled= true; + //mTextHighlighter.spellingEnabled= spellingEnabled; + //SpellUI.doSpellingJob(); + //dispatchEvent(new Event(Event.RENDER)); + + SpellUI.UITable[SpellUI.parentComp[mParentTextField]].spellingEnabled = spellingEnabled; + SpellUI.UITable[SpellUI.parentComp[mParentTextField]].doSpellingJob(); + //spellCheckRange(getValidFirstWordIndex(), getValidLastWordIndex()); + } + private function handleDisableSpellCheck(event:ContextMenuEvent):void + { + spellingEnabled= false; + SpellUI.UITable[SpellUI.parentComp[mParentTextField]].spellingEnabled = spellingEnabled; + mTextHighlighter.clearSquiggles(); + } + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUITLF/src/com/adobe/linguistics/spelling/SpellUIForTLF.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUITLF/src/com/adobe/linguistics/spelling/SpellUIForTLF.as b/Squiggly/main/SpellingUITLF/src/com/adobe/linguistics/spelling/SpellUIForTLF.as new file mode 100644 index 0000000..4128cd4 --- /dev/null +++ b/Squiggly/main/SpellingUITLF/src/com/adobe/linguistics/spelling/SpellUIForTLF.as @@ -0,0 +1,522 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling +{ + import com.adobe.linguistics.spelling.UserDictionary; + import com.adobe.linguistics.spelling.framework.ResourceTable; + import com.adobe.linguistics.spelling.framework.SpellingConfiguration; + import com.adobe.linguistics.spelling.framework.SpellingService; + import com.adobe.linguistics.spelling.ui.IHighlighter; + import com.adobe.linguistics.spelling.ui.IWordProcessor; + import com.adobe.linguistics.spelling.ui.TLFHighlighter; + import com.adobe.linguistics.spelling.ui.TLFWordProcessor; + import com.adobe.linguistics.utils.TextTokenizer; + import com.adobe.linguistics.utils.Token; + + import flash.events.Event; + import flash.events.FocusEvent; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.net.SharedObject; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.utils.Dictionary; + + import flashx.textLayout.compose.StandardFlowComposer; + import flashx.textLayout.compose.TextFlowLine; + import flashx.textLayout.container.ContainerController; + import flashx.textLayout.conversion.TextConverter; + import flashx.textLayout.edit.SelectionManager; + import flashx.textLayout.elements.FlowLeafElement; + import flashx.textLayout.elements.ParagraphElement; + import flashx.textLayout.elements.TextFlow; + import flashx.textLayout.events.CompositionCompleteEvent; + + import flashx.textLayout.tlf_internal; + + + use namespace tlf_internal; + /** + * <p>A comprehensive convenience class that bundles a context menu UI, + * the spelling engine, the dictionary loader, and user dictionary support + * to enable a single-line integration of spell checking functionality into + * any custom UI component built around TLF TextFlow. </p> + * + * + * <p>For advanced text editing applications, more complex features are likely required. + * For those applications, we recommend bypassing this class and utilizing the <code>SpellChecker</code> class directly.</p> + * + * In order to display Squiggly custom right-click context menu, SpellUIForTLF extends TLF <code>ContainerController</code> class and overrides createContextMenu() method + * of <code>ContainerController</code>. This could have the following side-effects for a Squiggly client: + * <ul> + * <li>In case the Squiggly client application uses a derived controller that inherits from ContainerController class in order to show custom context menu items, Squiggly + * context menu will override that. That essentially means that after SpellUIForTLF.enableSpelling() is called, client's own custom right-click menu items, if any, will not be + * accessible. </li> + * <li>Incase any ContainerController api needs to be called after SpellUIForTLF.enableSpelling() has been called, the client application will need to get the controller objects afresh + * from the TextFlow's FlowComposer. This is needed since SpellUIForTLF replaces ContainerController objects with new SquigglyCustomContainerController(derived from ContainerController) objects. </li> + * </ul> + * + * + * <p><code>SpellUIForTLF</code> uses the AdobeSpellingConfig.xml file to lookup corresponding resource files for a given locale. + * The default location of AdobeSpellingConfig.xml is [yourapplicationDirectory]/AdobeSpellingConfig.xml. This could be customized using + * <code>spellingConfigUrl</code> property of <code>SpellUI</code>. You don't have to change the content of this file. However, + * if you want to add a new language, to use an alternative dictionary or to customize the location for your dictionaries, you can modify it. + * There's an known issue with IIS web server when loading dictionary files with unknown extensions, in which case you can modify the XML to work around it.</p><p> + * A sample AdobeSpellingConfig.xml will look as follows: + * <listing version="3.0"> + * <pre class="preWrapper"> + * <?xml version="1.0" encoding='UTF-8'?> + * <SpellingConfig> + * <LanguageResource language="English" languageCode="en_US" ruleFile="dictionaries/en_US/en_US.aff" dictionaryFile="dictionaries/en_US/en_US.dic"/> + * <LanguageResource language="Spanish" languageCode="es_ES" ruleFile="dictionaries/es_ES/es_ES.aff" dictionaryFile="dictionaries/es_ES/es_ES.dic"/> + * <LanguageResource language="Portuguese" languageCode="pt_PT" ruleFile="dictionaries/pt_PT/pt_PT.aff" dictionaryFile="dictionaries/pt_PT/pt_PT.dic"/> + * <LanguageResource language="Italian" languageCode="it_IT" ruleFile="dictionaries/it_IT/it_IT.aff" dictionaryFile="dictionaries/it_IT/it_IT.dic"/> + * </SpellingConfig></pre> + * + * </listing> + * Note: The languageCode can be an arbitrary value, as long as you are consistent when passing them to the Squiggly classes. + * However, we highly encourage you to follow the two part Unicode language identifier format. + * For more information, please consult the latest Unicode Technical Standard that can be found at: http://unicode.org/reports/tr35/.</p> + * + * @playerversion Flash 10 + * @langversion 3.0 + */ + public class SpellUIForTLF + { + private var hh:IHighlighter; + private var hw:IWordProcessor; + + private var _checkLastWord:Boolean = true; + private var _spellingEnabled:Boolean; + + private var _actualParent:*; + + + //New Added below + private var mTextFlow:TextFlow; + + + private var _dictname:String = new String(); + + private var _userdict:UserDictionary = null; + private var _sharedobj:SharedObject = null; + private var scm:SpellingContextMenuForTLF; + + private var _newchecker:SpellChecker = null; + private var _resource_locale:Object = null; + private var _spellingservice:SpellingService = null; + + private static var _contextMenuEntries:Object = {enable:"Enable Spelling", disable:"Disable Spelling", add:"Add to dictionary"}; + private static var _spellingConfigUrl:String = "AdobeSpellingConfig.xml"; + private static var _UITable:Dictionary= new Dictionary(); + private static var _parentTable:Dictionary= new Dictionary(); + private static var _cacheDictTable:Dictionary= new Dictionary(); + + private static var _configXML:XML = null; + private static var _configXMLLoading:Boolean = false; + private static var _configXMLLoader:URLLoader = new URLLoader(); + + // Work around for the memory usage problem, ideally a better fix is to provide a dicitonary unload function + private static var _cache:Object = new Object(); + + /** + * Enables the spell checking feature for a TLF TextFlow. Once a TextFlow is spell checking enabled, misspelled words will be highlighted with a squiggly line. Users can + * right click on a misspelled word to see the suggestions in the context menu. + * + * @param comp A TLF TextFlow object + * @param lang The language code used for spell checking, for example <code>en_US</code>. it will lookup the AdobeSpellingConfig.xml file to access corresponding resource files. + * AdobeSpellingConfig.xml should located at the same folder as your main mxml source file. You don't have to change the content of this file. However, + * if you want to add a new language, to use an alternative dictionary or to customize the location for your dictionaries, you can modify it. There's an known issue with + * IIS web server when loading dictionary files with unknown extensions, in which case you can modify the XML to work around it. + * + * + * @includeExample Examples/ActionScript/SquigglyTLFExample/src/SquigglyTLFExample.as + * @playerversion Flash 10 + * @langversion 3.0 + */ + + public static function enableSpelling(comp:TextFlow, lang:String):void + { + if ( lang == null ) return; + if ( comp==null || _UITable[comp]!=undefined ) + return; + + // TODO: dangerous, is garbage collection going to clear this? + _UITable[comp]=new SpellUIForTLF(comp, lang); + _parentTable[comp] = comp; + _cacheDictTable[comp]=lang; + } + + /** + * Set the spelling context menu entries. This uses the ActionScript Object class as an associative array for extensibility. + * <code>entries</code> should have all the customized contextMenu entries including <code>enable (spelling), + * disable (spelling) and add (to dictionary)</code>. To ensure a consistent contextMenu within your application, + * the spelling context menu entries you provide here are applied to all UI components. We recommend you use this API + * to localize the context menu strings. + * + * @param entries A Object that looks like <code>entries:Object = {enable:"Enable Spelling", disable:"Disable Spelling", + * add:"Add to dictionary"}</code>. If you don't customize the contextMenu, the default contextMenu in English will be used. + * + * @return <code>True</code> if the spelling menu is successfully customized, <code>false</code> if it fails. Possible failure + * reasons include passing the wrong object or missing some required entries. If the function fails, the contextMenu is left unchanged. + * + * + * @IncludeExample Examples/Flex/CustomContextMenu/src/CustomContextMenu.mxml + * @IncludeExample Examples/Flex/ContextMenuWithResource/src/ContextMenuWithResource.mxml + * @playerversion Flash 10 + * @langversion 3.0 + */ + public static function setSpellingMenuEntries(entries:Object):Boolean + { + if (entries.enable && entries.disable && entries.add && (entries.enable != "") && (entries.disable != "") && (entries.add != "")) + { + _contextMenuEntries = entries; + return true; + } + else + return false; + } + + /** + * Get the spelling context menu entries. + * + * @return A actionScript <code>Object</code> containing the spelling context menu entries. If you haven't customized the entries, you get the default associative array <code>{enable:"Enable Spelling", disable:"Disable Spelling", add:"Add to dictionary"}</code> + * @playerversion Flash 10 + * @langversion 3.0 + */ + public static function getSpellingMenuEntries():Object + { + return _contextMenuEntries; + } + + /** + * The URL for the spelling config xml file. If you haven't specify it, the default URL is [applicationDirectory]/AdobeSpellingConfig.xml. Note that we don't validate the URL, if the file doesn't exist, you will get an error when calling enableSpelling() function. + * + * @example The following code customize the spellingConfigUrl before enabling spell checking. + * <listing version="3.0"> + * SpellUIForTLF.spellingConfigUrl = "./config/MySpellingConfig.xml"; + * SpellUIForTLF.enableSpelling(textFlow, "en_US"); + * </listing> + */ + public static function get spellingConfigUrl():String + { + return _spellingConfigUrl; + } + + public static function set spellingConfigUrl(url:String):void + { + if (url == null) throw new Error("URL can't be null"); + _spellingConfigUrl = url; + } + + + + /** + * Disable the spell checking feature for a TLF TextFlow. + * + * @param comp TLF TextFlow object on which to disable spell check. + * @playerversion Flash 10 + * @langversion 3.0 + */ + public static function disableSpelling(comp:TextFlow):void{ + if ( _UITable[comp] == undefined ) + return; + var _ui:SpellUIForTLF = _UITable[comp]; + if ( _ui != null) _ui.cleanUp(); + var dictName:String = _cacheDictTable[comp]; + var cleanUPDictionaryCount:int = 0; + for each ( var _dictName:String in _cacheDictTable ) { + if ( _dictName == dictName ) + cleanUPDictionaryCount++; + } + if ( cleanUPDictionaryCount == 1 ) { + _cache[dictName] = undefined; + } + delete _UITable[comp]; + delete _cacheDictTable[comp]; + + } + + /** + @private + (This property is for Squiggly Developer use only.) + */ + public static function get UITable():Dictionary { + return _UITable; + } + + /** + @private + (This property is for Squiggly Developer use only.) + */ + public function set spellingEnabled(value:Boolean):void { + _spellingEnabled = value; + } + + /** + @private + (This property is for Squiggly Developer use only.) + */ + public static function get parentComp():Dictionary { + return _parentTable; + } + + + /** + * Constructs a SpellUI object. + * @private + * @param textFiled A Flex UI component to include spell-check capability + * @param dict A URL for Squiggly spelling dictionary. + * + * @playerversion Flash 10 + * @langversion 3.0 + */ + public function SpellUIForTLF(textModel:TextFlow, lang:String) + { + + _actualParent = textModel; + mTextFlow = textModel; + + mTextFlow.addEventListener(flashx.textLayout.events.CompositionCompleteEvent.COMPOSITION_COMPLETE, spellCheckScreen,false, 0,true); + //mTextFlow.addEventListener(flashx.textLayout.events.StatusChangeEvent.INLINE_GRAPHIC_STATUS_CHANGE, spellCheckScreen); + + _dictname = lang; + loadConfig(); + } + + private function spellCheckScreen(event:Event):void + { + doSpellingJob(); + } + + + /** + @private + (This property is for Squiggly Developer use only.) + */ + public function doSpellingJob():void + { + if (_spellingEnabled == false) return; + + hh.clearSquiggles(); + for (var idx:int = 0; idx < mTextFlow.flowComposer.numControllers; idx++) + { + var testController:ContainerController = mTextFlow.flowComposer.getControllerAt(idx); + //if (getValidFirstWordIndexTLF(testController) != -1) + spellCheckRangeTLF(getValidFirstWordIndexTLF(testController), getValidLastWordIndexTLF(testController)); + } + + } + + + private function spellCheckRangeTLF(start:uint, end:uint):void { + var tokenizer:TextTokenizer; + //hh.clearSquiggles(); + var tt:TextFlow = mTextFlow; + //var currentLeaf:FlowLeafElement = tt.getFirstLeaf(); + var currentLeaf:FlowLeafElement = tt.findLeaf(start); + var currentParagraph:ParagraphElement = currentLeaf ? currentLeaf.getParagraph() : null; + while (currentParagraph) { // iterate over all paragraphs in the text flow + var paraStart:uint = currentParagraph.getAbsoluteStart(); + if (paraStart > end) + break; + + //var offsetPoint:Point = new Point(currentParagraph.paddingLeft, currentParagraph.paddingTop); + //hh.offsetPoint = offsetPoint; + tokenizer = new TextTokenizer(currentParagraph.getText().substring()); + //var tokens:Vector.<Token> = new Vector.<Token>(); + + for ( var token:Token = tokenizer.getFirstToken(); token != tokenizer.getLastToken(); token= tokenizer.getNextToken(token) ) { + var result:Boolean=_spellingservice.checkWord(currentParagraph.getText().substring(token.first, token.last)); + if (!result){ + //if (_checkLastWord || (token.last+paraStart != currentParagraph.getText().length)) + //hh.highlightWord(token.first+start, token.last+start-1); + //tokens.push(new Token(token.first+start, token.last+start-1)); + hh.drawSquiggleAt(new Token(token.first+paraStart, token.last+paraStart-1)); + } + + } + currentParagraph = currentParagraph.getNextParagraph(); + + } + //hh.postSpellCheckRange(start, end); + //hh.offsetPoint = offsetPoint; + //hh.drawSquiggles(tokens); + } + private function getValidFirstWordIndexTLF(containerController:ContainerController):int{ + var index:int; + + // Check for computeSelectionIndexInContainer which throws when lineindex == 0 + try { + //index = SelectionManager.computeSelectionIndex(mTextFlow, containerController.container, containerController.container, 0 + containerController.horizontalScrollPosition, 0 + containerController.verticalScrollPosition); + // SelectionManager.computeSelectionIndex() sometimes gives index as -1. in the same scenarios below logic works better + var tl:TextFlowLine = containerController.getFirstVisibleLine(); + var firstVisiblePosition:int = tl.absoluteStart; + index = firstVisiblePosition; + + } catch (err:Error) + { + //TODO: report error + index = 0; + } + + return index; + } + + private function getValidLastWordIndexTLF(containerController:ContainerController):int{ + var index:int; + + // Check for computeSelectionIndexInContainer which throws when lineindex == 0 + try { + //index = SelectionManager.computeSelectionIndex(mTextFlow, containerController.container, containerController.container, containerController.container.width+containerController.horizontalScrollPosition, containerController.container.height+containerController.verticalScrollPosition); + var tl:TextFlowLine = containerController.getLastVisibleLine(); + var lastVisiblePosition:int = tl.absoluteStart + tl.textLength; + index = lastVisiblePosition; + } catch (err:Error) + { + //TODO: report error + index = 0; + } + + return index; + } + + + private function loadConfig():void{ + _resource_locale = SpellingConfiguration.resourceTable.getResource(_dictname); + + if ((_resource_locale != null) || (SpellUIForTLF._configXML != null)) + loadConfigComplete(null); + else { + SpellUIForTLF._configXMLLoader.addEventListener(Event.COMPLETE, loadConfigComplete); + + if (SpellUIForTLF._configXMLLoading == false) + { + SpellUIForTLF._configXMLLoader.load(new URLRequest(_spellingConfigUrl)); + SpellUIForTLF._configXMLLoading = true; + } + } + } + + private function loadConfigComplete(evt:Event):void{ + if (_resource_locale == null) { + if (SpellUIForTLF._configXML == null) + SpellUIForTLF._configXML= new XML(evt.target.data); + + SpellingConfiguration.resourceTable.setResource(_dictname,{rule:SpellUIForTLF._configXML.LanguageResource.(@languageCode==_dictname).@ruleFile, + dict:SpellUIForTLF._configXML.LanguageResource.(@languageCode==_dictname).@dictionaryFile}); + } + //New Added + _spellingservice = new SpellingService(_dictname); + _spellingservice.addEventListener(Event.COMPLETE, loadDictComplete); + _spellingservice.init(); + } + + + + + private function loadDictComplete(evt:Event):void + { + //_newchecker = new SpellChecker(_hundict); + + // Lazy loading the UD only when the main dict is loaded successfully + if ((SpellUIForTLF._cache["Squiggly_UD"] as UserDictionary) == null) + { + _sharedobj = SharedObject.getLocal("Squiggly_v03"); + var vec:Vector.<String> = new Vector.<String>(); + if (_sharedobj.data.ud) { + for each (var w:String in _sharedobj.data.ud) + vec.push(w); + } + _userdict = new UserDictionary(vec); + + SpellUIForTLF._cache["Squiggly_SO"] = _sharedobj; + SpellUIForTLF._cache["Squiggly_UD"] = _userdict; + } + else + { + _sharedobj = SpellUIForTLF._cache["Squiggly_SO"]; + _userdict = SpellUIForTLF._cache["Squiggly_UD"]; + } + _spellingservice.addUserDictionary(_userdict); + + + // Add the context menu, this might be not successful + scm = null; + try { + addContextMenu(null); + } + catch (err:Error) + { + // TODO: error handling here + } + _actualParent.addEventListener(Event.ADDED_TO_STAGE, addContextMenu); + } + + + private function addContextMenu(event:Event):void + { + if ( scm != null ) return; + + hh = new TLFHighlighter( _actualParent); + hw = new TLFWordProcessor( _actualParent); + + scm = new SpellingContextMenuForTLF(hh, hw, _spellingservice, _actualParent, addWordToUserDictionary); + //scm.setIgnoreWordCallback( addWordToUserDictionary ); + + // Halo need this + //if (_actualParent.contextMenu == null) + //{ + //_actualParent.contextMenu = scm.contextMenu; + //} + + //hh.spellingEnabled=true; + _spellingEnabled = true; + try { + doSpellingJob(); + } + catch (err:Error) + { + // If it fails here, it should later triggered by the render event, so no need to do anything + } + } + + private function addWordToUserDictionary(word:String):void + { + _userdict.addWord(word); + + // TODO: serialization here might affect ther performance + _sharedobj.data.ud = _userdict.wordList; + + } + /** + * @private + */ + private function cleanUp():void { + hh.clearSquiggles(); + scm.cleanUp(); + _actualParent.removeEventListener(Event.ADDED_TO_STAGE, addContextMenu); + + mTextFlow.removeEventListener(flashx.textLayout.events.CompositionCompleteEvent.COMPOSITION_COMPLETE, spellCheckScreen); + //mTextFlow.removeEventListener(flashx.textLayout.events.StatusChangeEvent.INLINE_GRAPHIC_STATUS_CHANGE, spellCheckScreen); + } + + } +} http://git-wip-us.apache.org/repos/asf/flex-utilities/blob/4e4f9830/Squiggly/main/SpellingUITLF/src/com/adobe/linguistics/spelling/SpellingContextMenuForTLF.as ---------------------------------------------------------------------- diff --git a/Squiggly/main/SpellingUITLF/src/com/adobe/linguistics/spelling/SpellingContextMenuForTLF.as b/Squiggly/main/SpellingUITLF/src/com/adobe/linguistics/spelling/SpellingContextMenuForTLF.as new file mode 100644 index 0000000..ccc3c78 --- /dev/null +++ b/Squiggly/main/SpellingUITLF/src/com/adobe/linguistics/spelling/SpellingContextMenuForTLF.as @@ -0,0 +1,249 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.adobe.linguistics.spelling +{ + import com.adobe.linguistics.spelling.SpellUIForTLF; + import com.adobe.linguistics.spelling.SquigglyCustomContainerController; + import com.adobe.linguistics.spelling.framework.SpellingService; + import com.adobe.linguistics.spelling.ui.IHighlighter; + import com.adobe.linguistics.spelling.ui.IWordProcessor; + import com.adobe.linguistics.spelling.ui.TLFHighlighter; + import com.adobe.linguistics.spelling.ui.TLFWordProcessor; + import com.adobe.linguistics.utils.TextTokenizer; + import com.adobe.linguistics.utils.Token; + + import flash.events.ContextMenuEvent; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.ui.ContextMenu; + import flash.ui.ContextMenuItem; + import flash.utils.describeType; + import flash.utils.getQualifiedClassName; + + import flashx.textLayout.compose.StandardFlowComposer; + import flashx.textLayout.compose.TextFlowLine; + import flashx.textLayout.container.ContainerController; + import flashx.textLayout.conversion.TextConverter; + import flashx.textLayout.edit.SelectionManager; + import flashx.textLayout.elements.FlowLeafElement; + import flashx.textLayout.elements.ParagraphElement; + import flashx.textLayout.elements.TextFlow; + import flashx.textLayout.events.CompositionCompleteEvent; + import flashx.textLayout.events.StatusChangeEvent; + import flashx.textLayout.tlf_internal; + + + use namespace tlf_internal; + + public class SpellingContextMenuForTLF + { + private var disableMenuItem:ContextMenuItem = new ContextMenuItem("Disable spell checking",true); + private var enableMenuItem:ContextMenuItem = new ContextMenuItem("Enable spell checking"); + + private var controlMenuItemList:Array = new Array(); + private var suggestionMenuItemList:Array = new Array(); + private var _spellingEnabled:Boolean; + private var _contextMenu:ContextMenu; + private var mTextHighlighter:IHighlighter; + private var mWordProcessor:IWordProcessor; + private var mSpellEngine:SpellingService; + private var mTextFlow:TextFlow; + private var _ignoreWordFunctionProcessor:Function; + private var _misspelledToken:Token; + private var _misspelled:String; + public function SpellingContextMenuForTLF(textHighlighter:IHighlighter, wordProcessor:IWordProcessor, engine:SpellingService, actualParent:*, func:Function) + { + + if ( textHighlighter == null || wordProcessor == null || engine == null) throw new Error("illegal argument."); + mTextHighlighter = textHighlighter; + mWordProcessor = wordProcessor; + mSpellEngine = engine; + mTextFlow = actualParent; + + + var numControllers:int = mTextFlow.flowComposer.numControllers; + for (var idx:int = 0; idx < numControllers; idx++) + { + var containerController:ContainerController = mTextFlow.flowComposer.getControllerAt(idx); + var squigglyContainerController:SquigglyCustomContainerController = new SquigglyCustomContainerController(containerController.container, mTextHighlighter, mWordProcessor, + mSpellEngine, func, containerController.compositionWidth, containerController.compositionHeight); + copyObject(containerController, squigglyContainerController); + mTextFlow.flowComposer.removeController(containerController); + containerController = null; // make it null so that the associated memory is garbage collected + mTextFlow.flowComposer.addControllerAt(squigglyContainerController, idx); + + } + mTextFlow.flowComposer.updateAllControllers(); + + + spellingEnabled = true; //default value + //spellingEnabled= mTextHighlighter.spellingEnabled; + + _ignoreWordFunctionProcessor=null; + } + + /** + * copies a source object to a destination object + * @param sourceObject the source object + * @param destinationObject the destination object + * + */ + private function copyObject(sourceObject:ContainerController, destinationObject:SquigglyCustomContainerController):void + { + // check if the objects are not null + if((sourceObject) && (destinationObject)) { + try + { + //retrive information about the source object via XML + var sourceInfo:XML = describeType(sourceObject); + var objectProperty:XML; + var propertyName:String; + + // loop through the properties + for each(objectProperty in sourceInfo.variable) + { + propertyName = objectProperty.@name; + if(sourceObject[objectProperty.@name] != null) + { + if(destinationObject.hasOwnProperty(objectProperty.@name)) { + destinationObject[objectProperty.@name] = sourceObject[objectProperty.@name]; + } + } + } + //loop through the accessors + for each(objectProperty in sourceInfo.accessor) { + if(objectProperty.@access == "readwrite") { + propertyName = objectProperty.@name; + if(sourceObject[objectProperty.@name] != null) + { + if(destinationObject.hasOwnProperty(objectProperty.@name)) { + destinationObject[objectProperty.@name] = sourceObject[objectProperty.@name]; + } + } + } + } + } + catch (err:*) { + ; + } + } + } + + public function cleanUp():void + { + mTextHighlighter=null; + mWordProcessor=null; + spellingEnabled = false; + _ignoreWordFunctionProcessor=null; + + var numControllers:int = mTextFlow.flowComposer.numControllers; + for (var idx:int = 0; idx < numControllers; idx++) + { + //if (getQualifiedClassName(mTextFlow.flowComposer.getControllerAt(idx)) == "SquigglyCustomContainerController"){ + var containerController:ContainerController = mTextFlow.flowComposer.getControllerAt(idx); + //Use try-catch incase some controller not of type SquigglyCustomContainerController comes across + try { + (containerController as SquigglyCustomContainerController).cleanUpContextMenu(); + } + catch (err:Error) + { + // TODO: error handling here + } + //} + + } + + } + + public function get contextMenu():ContextMenu { + return this._contextMenu; + } + + public function setIgnoreWordCallback(func:Function ) :void { + if ( func != null ) + _ignoreWordFunctionProcessor = func; + } + + private function handleAddToItemSelect(event:ContextMenuEvent):void + { + if ( _ignoreWordFunctionProcessor == null ) return; + + /* + var menuEntry:String = (event.currentTarget as ContextMenuItem).caption; + var start:uint = 5; + var end:uint = menuEntry.length - 15; + var word:String = menuEntry.substring(start, end); + */ + _ignoreWordFunctionProcessor(_misspelled); + SpellUIForTLF.UITable[SpellUIForTLF.parentComp[mTextFlow]].doSpellingJob(); + } + + private function isWordItem(item:ContextMenuItem):Boolean { + + for ( var i:int=0; i<suggestionMenuItemList.length; ++i ) { + if ( suggestionMenuItemList[i] == item ) return true; + } + return false; + } + + private function isControlItem(item:ContextMenuItem):Boolean { + for (var i:int=0; i<controlMenuItemList.length; ++i) { + if ( controlMenuItemList[i] == item) return true; + } + return false; + } + + private function handleSuseItemSelect(event:ContextMenuEvent):void + { + mWordProcessor.replaceText(_misspelledToken, (event.currentTarget as ContextMenuItem).caption ); + SpellUIForTLF.UITable[SpellUIForTLF.parentComp[mTextFlow]].doSpellingJob(); + } + + + private function set spellingEnabled(value:Boolean) :void { + _spellingEnabled = value; + disableMenuItem.visible=spellingEnabled; + enableMenuItem.visible=!spellingEnabled; + } + private function get spellingEnabled():Boolean { + return this._spellingEnabled; + } + private function handleEnableSpellCheck(event:ContextMenuEvent):void + { + spellingEnabled= true; + //mTextHighlighter.spellingEnabled= spellingEnabled; + //SpellUI.doSpellingJob(); + //dispatchEvent(new Event(Event.RENDER)); + + SpellUIForTLF.UITable[SpellUIForTLF.parentComp[mTextFlow]].spellingEnabled = spellingEnabled; + SpellUIForTLF.UITable[SpellUIForTLF.parentComp[mTextFlow]].doSpellingJob(); + //spellCheckRange(getValidFirstWordIndex(), getValidLastWordIndex()); + } + private function handleDisableSpellCheck(event:ContextMenuEvent):void + { + spellingEnabled= false; + SpellUIForTLF.UITable[SpellUIForTLF.parentComp[mTextFlow]].spellingEnabled = spellingEnabled; + mTextHighlighter.clearSquiggles(); + } + + } +}
