| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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.framework.SpellingConfiguration; |
| import com.adobe.linguistics.spelling.framework.SpellingService; |
| import com.adobe.linguistics.spelling.framework.ui.HaloHighlighter; |
| import com.adobe.linguistics.spelling.framework.ui.HaloWordProcessor; |
| import com.adobe.linguistics.spelling.framework.ui.IHighlighter; |
| import com.adobe.linguistics.spelling.framework.ui.IWordProcessor; |
| import com.adobe.linguistics.spelling.framework.ui.SparkHighlighter; |
| import com.adobe.linguistics.spelling.framework.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 flashx.textLayout.edit.SelectionManager; |
| import flashx.textLayout.tlf_internal; |
| |
| 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; |
| |
| 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 = "SpellingConfig.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 dictionary unload function |
| private static var _cache: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>, <code>RichTextEditor</code> |
| (both spark and mx), or a <code>RichTextEditor</code>. |
| * @param lang The language of the dictionary to use when checking the text spelling in <code>comp</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]/SpellingConfig.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 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 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 textModel A Flex UI component to include spell-check capability. |
| * @param lang The language code to use for spell checking. |
| * |
| * @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 { |
| if(hh != null) |
| { |
| hh.clearSquiggles(); |
| } |
| |
| if(scm != null) |
| { |
| 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); |
| } |
| |
| } |
| } |