'From Squeak2.9alpha of 13 June 2000 [latest update: #3412] on 20 February 2001 at 10:33:12 pm'! "Change Set: Genie-Engine Date: 20 February 2001 Author: Nathanael Scharli Genie is a character and gesture recognition system for Squeak. This changeset contains all the classes involved in recognizing a gesture and looking it up in a dictionary. NOTE: To get a ready-to-use Genie environment, file in the following changesets in this order: a) Genie-Engine.cs b) Genie-UI.cs c) Genie-Integration.cs"! Object subclass: #AGenieIntroduction instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !AGenieIntroduction commentStamp: '' prior: 0! NS 8/11/2000 14:08 This text is also available as comment of the class AGenieIntroduction *** Genie: An introduction *** Genie is a character and gesture recognition system inside Squeak. It is completely configurable and allows you to do everything in Squeak by just using a pen. Contents of this introduction 1) Getting started 1.1) Filing it in 1.2) General options and preferences 1.3) Changing the gesture dictionary of a morph 1.4) Inspecting, editing and browsing dictionaries 1.4.1) Browsing a dictionary 1.4.2) Adding new gestures to a character 1.4.3) Adding new characters or editing characters 1.4.4) Dictionary inheritance 1.5) The different character types 1.6) Inspecting and editing display properties 2) Gesture event flow 3) How a Morph can influence gesture handling and dispatching 4) Examples 1) Getting started 1.1) Filing it in The genie system consists of the following 3 change-sets: a) Genie engine: Contains all the classes involved in recognizing a gesture and looking it up in a dictionary. [Category: Genie-Engine]. b) Genie UI: The UI to configure the gesture recognizer. (Inspect/edit/load/save/copy/delete gesture dictionaries, display properties, etc.) [Category: Genie-UI] c) Genie Integration: This code integrates Genie into Morphic. It consists of system integration code in classes like HandMorph, Morph, TextMorph, MorphicEvent, etc. To get a ready-to-use Genie environment, you have to file in the change-sets in the order a) to c). (It would also be possible to create a Genie environment that has no UI or only reduced integration into the system.) 1.2) General options and preferences There are 3 items in a World's help menu that are genie specific: - enable / disable Genie: Enables / disables Genie for the current hand. - Genie character dictionaries: Inspect and edit the named genie character dictionaries. - Genie display properties: Inspect and edit the named genie display properties. Further there is a preference option in the morphic category to determine whether the recognizer should be enabled for newly created hand morphs. Note that you can load gesture dictionaries and display properties directly from the Squeak file list. If Genie is enabled for the current hand, the recognizer catches and redirects every mouse event as follows: For every click, the hand determines the target Morph. If the target Morph handles gestures (that means basically that it has an associated gesture dictionary) Genie starts to recognize a gesture and the result is sent to the target morph. However, if the target morph doesn't handle gestures (e.g. it has no associated gesture dictionary), a mouseDown event is sent to the target Morph as it happened before. NOTE: Every dictionary can define an 'escape time'. If the cursor is not moved away from the start position during this time, the recognizer escapes and simulates default mouse events. Like this it is possible to use default mouse events even with a morph that handles gestures. (There are also other special commands that escape the recognizer and allow for simulation of mouse events with any possible mouse button). 1.3) Changing the gesture dictionary of Morphs There are a lot of different ways how to change the gesture dictionary of Morph subclasses. A very easy way to do this is to override the method gestureDictionaryOrName and return the *exported* name of the gesture dictionary that should be used to lookup gestures for the Morph subclass (return the name as a Symbol!!). If this method is not overridden, the following happens per default: Every Morph subclass tries to use a dictionary with an exported name that is equal to the class name. If there is no such dictionary available, it tries to find a dictionary with an exported name that is equal to a superclass name, and uses this one. If there is also no such dictionary available, the Morph subclass doesn't handle gestures. An exception to this rule about the default gesture dictionary of Morphs are the classes that support text editing (TextMorph, PluggableTextMorph, etc): All these classes use per default the gesture dictionary named 'Text'. Example: To define a dictionary for the instances of the class EllipseMorph, set the exported name of the dictionary to 'EllipseMorph'. Or, if every Morph subclass (that doesn't define a special gesture dictionary) should use a certain dictionary, just export it with the name 'Morph'. NOTE: As for all the other events, EventHandler can be used to add special behavior to certain Morph instances. As soon as there is an EventHandler assigned to a Morph instance, the EventHandler determines the Morph's behavior on gesture events. This is the reason why Morphs with an associated EventHandler often don't respond to gesture events per default. Besides that, it's also possible to change the gesture dictionary of each individual instance of a Morph subclass. In the red halo menu of every morph, there is an item called 'change gesture dictionary'. It can be used to assign an exported gesture dictionary to the individual Morph instance or to create a new dictionary for the instance. Every Morph (sub-)instance that has an associated gesture dictionary provides also other options in the red halo menu: - inspect gesture dictionary: Inspect / edit the associated gesture dictionary - make own copy of gesture dictionary: Makes a copy of the gesture dictionary that is then only assigned to this individual Morph. (This dictionary doesn't even have an exported name and it is referenced directly by the Morph instance.) Like this, it is possible to modify the gesture dictionary of the individual instance without affecting other Morphs. - make own sub-dictionary: Similar to the previous item, but instead of copying the associated dictionary, an empty dictionary that inherits from the previously assigned dictionary is created and assigned to the Morph instance. For more information please read the section 2) and 3). 1.4) Inspecting, editing and browsing dictionaries There are many possibilities to open the tool to inspect / edit dictionaries. You can use the help menu of the World and select the item 'genie gesture dictionaries'. This opens a tool with all the named dictionaries in the system. Every dictionary shows its contents using 5 tabs. Use the 'Basic' tab to modify basic properties of the dictionary. The menu of the dictionaries browser (click button labeled 'M') allows you to save or load dictionaries to browse parent dictionaries, etc. (Besides that, dictionaries and display properties can also be loaded from within the Squeak file list.) Note: Every dictionary has its own preferences and it's even possible to inherit from dictionaries that have completely different properties. Further, the inheritance mechanism of Genie automatically resolves cycles in the parent hierarchy. 1.4.1) Browsing a dictionary To browse the contents of the dictionary, choose either 'Browse' from the menu (button labeled 'M') or select the 'Browse' tab. A browser consists of two nested panes. The outer pane can be used to browse through all the characters definied in the dictionary. The current character is shown in the top left corner of the pane. For every character, the inner pane can be used to browse all the associated gestures. (There is often only one gesture associated to one character.) The leftmost gesture in the inner pane is the gesture that is associated to the character, while the gestures to the right are the most similar gestures in the dictionary. The text above the graphics says what characters are most similar and what's the distance to this characters (the distance is normalized so that 100 is the maximum). Use the menu button (labeled 'M') of the outer pane to change properties (appearance, etc.) of the browser. 1.4.2) Adding new gestures for a character To add a new gesture for an already existing character, click 'Add' in the inner pane. This replaces the inner pane by a pane with a purple background. Enter a new gesture by drawing a stroke starting at any point in the purple area. When finished, the new stroke is displayed in the left part of the purple pane. The most similar gestures of the dictionary are shown at the right side and the text at the top informs about the distances to these gestures. (Note: The maximum distance is 100.) Basically it's enough to enter each gesture only once. Thus, you can now hit 'Add' to assign the new gesture to the character. However, if a stroke is entered only once, there is a certain probability that it is accidentally not completely entered as it should be. Therefore, Genie allows the user to enter the stroke for a gesture as many times as he wants to. Just start a new stroke in the purple area to enter a new variant. Note the number in the top left corner that indicates how many strokes were entered for the gesture. If there is more than one stroke entered for a new gesture, the avarage of all these strokes is finally associated to the character. The menu (button labeled 'M' in the purple pane) can be used to remove the last entered stroke, or to switch between showing the last entered stroke or the avarage stroke. Further, the menu can be used to assign a hotspot to the stroke. Hit 'Reset' to remove all the previously entered strokes for the new gesture. Important: Don't mix up the possibility to enter the stroke for a single gesture more than once with the possibility to assign more than one gesture to a single character!! In the first case, the user enters the stroke for one and the same gesture more than once to reduce noise that happens per accident. (Once the 'Add' button is hit, only the avarage gesture is assigned to the character). The second case the user assigns more than one (possibly completely different) gestures to one single character). Hint: If the system fails to recognize a certain character several times, it often helps to enter the gesture again. (Just enter the gesture for the character again. You don't have to delete the gesture(s) that are already assigned to the character). Even if two gestures that are assigned to a character look nearly the same, there can be relevant differences in the way they have been captured. 1.4.3) Adding new characters or editing characters Use the 'Add' button in the browser's outer pane to add a new character to the dictionary. This action automatically opens the purple pane to enter a gesture for the character. Click on the character representation shown in the top left corner of the outer pane to inspect or edit a character. Whenever you are prompted to enter / edit a character, there is a context menu available that presents some predefined characters and their meaning. 1.4.4) Dictionary inheritance Gesture dictionaries can inherit from one or more parent dictionaries. Whenever a gesture is looked up in a dictionary with parents, the gesture is compared to all the gestures in all the dictionaries that are accessible using the inheritance hierarchy. (Cycles in the parent hierarchies are automatically resolved). Using the concept of dictionary inheritance a user should never have to define a gesture for a certain character in more than one dictionary. For example, a user can define all the gestures for basic mouse and command key support in a dictionary 'Mouse'. Then he can inherit from this dictionary for all the other dictionaries that should have basic mouse and keyboard support. Further he can define a dictionary 'BasicText' containing all the basic characters. In another dictionary he can enter special programming macros and when he makes this dictionary inheriting from 'BasicText', there is a dictionary containing both text characters and programming macros. Note that dictionaries in an inheritance hierarchy can still have different parameters (like speed, size relevance, etc). Dictionary inheritance is really nice but there is one disadvantage: It takes more time to look up gestures in nested dictionaries than otherwise. So, if performance is an important issue (slow systems, etc), too much inheritance should be avoided. 1.5) The different character types Originally, characters were real characters (one single ascii character), but now, a character is much more general. Basically, there are 3 different types of characters: - Sequence of characters (keystrokes): This category consists of any character sequence not starting with a #. Sequences starting with a # can be entered by enclosing them into ''. (e.g. '#keystrokes' instead of #keystrokes) - Command: A command is basically a symbol. Commands are used to tell the target Morph to do certain actions. There are a lot of predefinied commands available (#inspectLastGesture, #switchCase, #inspectDictionaries, etc.). But new commands can be added all the times. Whenever you have to enter / edit a character, there is a context menu available that shows the predfinied commands and more... (click the yellow (2) mouse button to open the context menu) - Code: A Genie character can contain any kind of Squeak code. The format to enter a code character is the following: #Header#Code, whereas Code is general Squeak code and Header is a descriptive string that can be omitted. (Example: '#beep#Smalltalk beep' or just: '##Smalltalk beep'). When the gesture assigned to the code character is recognized, a MorphicGestureEvent is sent to the target Morph. Then the entered code gets executed with the MorphicGestureEvent as the receiver. 1.6) Inspecting and editing display properties One of the imortant features of Genie is the platform independence of the gesture dictionaries. This means that all the gsture dictionaries can be created, changed and used on platforms with completely different properties (PC, Laptop, PDA, ...). If a user wants to use Genie on a specific platform, he has to tell the Genie system about the display properties of the platform. There can be many different display properties in a Squeak image, but only one of them is active at a time. To create, delete, inspect, edit or activate/deactivate display properties, select the item 'Genie display properties' from the World's help menu. See the balloon help assigned with the different options for more information. 2) Gesture event flow Here is a short description of how gestures are generated and how the corresponding events are transmitted to the target morph. See the next section to find out more about how a Morph can influence gesture handling and dispatching. 1) Whenever a mouse down occurs, the HandMorph determines the target Morph (recipientForMouseDown:). This is done by asking every morph under the cursor whether he wants to handle gestures (handlesGestureStart:) or mouse down events (handlesMouseDown:) (if it wants to handle both, gestures have priority). Basically, the innermost submorph is asked first, but there are methods in Morph that can modify this behavior (preemtsMouseDown:, trumpsMouseDown:, preemptsGestureStart:, trumpsGestureStart:). 2) The HandMorph sends gestureDictionary: to the target Morph to obtain its specific gesture dictionary. Afterwards it captures the gesture and does some basic preprocessing (updating keyboard and mouse button state, checking for reject / alert, etc.). (However, the target morph can avoid this preprocessing via the method permitsGesturePreprocessing.) 3) HandMorph builds a MorphicGestureEvent that contains all the information about the gesture and sends it as an argument of the message gesture: to the target morph. 4) The Morph can dispatch the event by doing any general or specific action. (See next section.) 3) How a Morph can influence gesture handling and dispatching Every Morph has several possibilities to influence gesture handling and dispatching: a) What events are handled? Every Morph can decide whether it wants to handle gesture or whether it just wants to get general mouse events. Related Morph methods: handlesMouseDown:, handlesGestureStart:, preemptsMouseDown:, trumpsMouseDown, preemptsGestureStart:, trumpsGestureStart:. b) What gesture dictionary is used? Every Morph can determine which gesture dictionary gets used. Related Morph methods: gestureDictionary, defaultGestureDictionaryOrName, gestureDictionaryOrName, gestureDictionaryOrName: c) How are gesture events dispatched? After a gesture is captured, the HandMorph creates a MorphicGestureEvent containing all the related information and sends it as an argument to the method gesture: of the target morph. Now, the morph can decide how to dispatch the event. Related Morph methods: gesture:, handleGesture:, permitsGesturePreprocessing As all the other events, gesture events can be handled by an EventHandler that is associated to a certain Morph instance. See Morph>>onGestureUse:send:to: and the gesture event related methods in EventHandler. 4) Examples The following example dictionaries are available: (Please note that the gestures in these dictionaries are quite specific to my handwriting and that they may not be very useful for other users. However, they should be a good example to show how you can use and configure genie). - MorphExample [No parents] Includes the basic mouse, halo and command/modifier key operations. Further it includes Genie specific operations (inspect last gesture, inspect dictionaries, etc.). - BasicTextExample [Parents: 'MorphExample'] Includes letters, numbers and some special keys (return, backspace, tab, ...) Further there are some special gestures. (E.g., you can change the alignment of text to {left, center, right} by drawing a long vertical line in the {left, center, right} of the pane). - TextExample [Parents: 'BasicTextExample'] Includes all letters, numbers, special characters and special keys available on a general keyboard. - WorldExample [Parents: 'MorphExample'] Includes some gestures to open new Morphs in the World. (Workspace, Browser, EllispeMorph, StarMorph, TextMorph, etc). Note: The gesturs in this dictionary must be size and shape independent. Therefore, some parameters in the 'Basic' resp. 'Advanced' section have been changed!! - ProgrammingExample [Parents: 'TextExample'] Adds some programming macros (ifTrue: [], ifFalse: [], etc). - CapitalTextExample [Parents: TextExample'] Adds capital letters to the text dictionary. To get a nice demo environment you can export these dictionaries as follows: - Export 'BasicTextExample' (or 'TextExample' or 'CapitalTextExample' or 'ProgrammingExample') as 'Text'. - Export 'WorldExample' as 'PasteUpMorph'. - Export 'MorphExample' as 'MorphExample'. Change the method defaultGestureDictionaryOrName of the classes EllipseMorph and StarMorph to: defaultGestureDictionaryOrName ^ #MorphExample. Now you can open new Workspaces, Browsers, etc. by drawing directly into the World. Also StarMorph and EllipseMorph can be opened like this. Further StarMorph and EllipseMorph support gesture handling. For example, you can delete them using a gesture. If you want to enable this basic gesture handling for all the Morphs that have no special behavior definied (for example in an Eventhandler), just export 'MorphExample' as 'Morph'... These examples give a little taste of Genie's possibilities. However, these are very basic examples and there is much more possible. Just adjust the dictionaries and write integration code for the individual Morphs... I hope you enjoy!! Nathanael ! Object subclass: #CRChar instanceVariableNames: 'string ' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CRChar commentStamp: '' prior: 0! This class represents a genie character. Originally, characters were real characters (one single ascii character), but now, a character is much more general. Basically, there are 3 different types of characters: - Sequence of character (keystrokes) [type = #strokes]: This category consists of any character sequence not starting with a #. Sequences starting with a # can be entered by enclosing them inti ''. (e.g. '#keystrokes' instead of #keystrokes) - Command [type = #command]: A command is basically a symbol. Commands are used to tell the target Morph to do certain actions. There are a lot of predefinied commands available (#inspectLastGesture, #switchCase, #inspectDictionaries, etc.). But new commands can be added all the times. - Code [type = #code]: A genie character can contain any kind of Squeak code. The format to enter a code character is the following: #Header#Code, whereas Code is general Squeak code and Header is a descriptive string that can be omitted. (Example: '#beep#Smalltalk beep' or just: '##Smalltalk beep'). When the gesture assigned to the code character is recognized, a MorphicGestureEvent is sent to the target Morph. Then the entered code gets executed with the MorphicGestureEvent as the receiver. In each case, the contents of the character is stored in the instance variable 'string'.! CRChar class instanceVariableNames: ''! Model subclass: #CRDictionary instanceVariableNames: 'name dictionary invertedDictionary exportedName parameters capturedPoints maxMultipleDefinitions maxStrokeDistance parents storeParents storedName ' classVariableNames: 'ActiveInstance ExportedInstanceDictionary InstanceBrowser ' poolDictionaries: '' category: 'Genie-Engine'! !CRDictionary commentStamp: '' prior: 0! This class represents a genie character dictionary. The important instance variables and its functions are: name The name is used to identify the dictionary. The name can be empty. In this case, the dictionary doesn't appear in the dictionary instance browser (the browser showing all the dictionaries). This can be useful to define simple dictionaries, that are assigned to special Morph instances. If a dictionary has a name, the name must be unique. Dictionary names are used to specify parent dictionaries. exportedName The exported-name of a dictionary is used to assign dictionaries to Morph subclasses and special Morph instances. This means that all the dictionary names specified by morphs are exported names!! Usage example: Lets assume that there are two users, Peter and Paul with dictionaries called 'Peter Text', 'Peter Text with Capital letters" and 'Paul Text'. Let's further assume that all the text-based Morphs refer to a dictionary with the exported name 'Text'. The active dictionary for all these Morphs can now be easily changed just by exporting the right dictionary as 'Text'. parents All the direct parents of the dictiionaries. There is no parent limit and parent hierarchies can contain cycles!! parameters There are a lot of different parameters that can be assigned to character dictionaries (relevance of the feature size, alert if match is too bad, ...). The parameters of each dictionary are stored here. NOTE: Inherited dictionaries can have different parameters!! This can e.g. be very useful if someone wants to have size independent strokes in a dictionary that is basically size sensitive.! CRDictionary class instanceVariableNames: ''! Model subclass: #CRDisplayProperties instanceVariableNames: 'name aspectRatio maxSize minCaptureDistance minMoveDistance ' classVariableNames: 'ActiveInstance InstanceBrowser ' poolDictionaries: '' category: 'Genie-Engine'! !CRDisplayProperties commentStamp: '' prior: 0! Instances of this class describe the properties of a certain display (and input) device. Basically, all the features are stored in a completely device independent format. Thus, character dictionaries can be used without any modification on all the different platforms running Squeak. The recognizing engine uses the display properties to know how to interpret new features (input) and how to display existing features (output). There can be several display properties in the system, but only one of them is active at a time.! CRDisplayProperties class instanceVariableNames: ''! Object subclass: #CRFeature instanceVariableNames: 'time ' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CRFeature commentStamp: '' prior: 0! This is the abstract base class to describe features of a gesture. There exists 3 concrete subclasses (CREmptyFeature, CRDotFeature and CRStrokeFeature), but two of them (CREmptyFeature and CRDotFeature) are trivial. Features are created by the recognizer (CRRecognizer) and are afterwads compared to other features in a dictionary and/or added to a dictionary. The comparing algorithms, that are the heart of genie recognition engine, are part of this class (resp. its subclasses). (The comparing algorithms of CRDotFeature and CREmptyFeature are quite trivial, but the ones of CRStrokeFeature are pretty sophisticated).! CRFeature subclass: #CRDotFeature instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CRDotFeature commentStamp: '' prior: 0! This specieal case of a CRFeature is used to describe a feature containing only one point.! CRFeature subclass: #CREmptyFeature instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CREmptyFeature commentStamp: '' prior: 0! This special case of a CRFeature is used to describe an empty feature (Not even one point).! CRFeature class instanceVariableNames: ''! Morph subclass: #CRLineMorph instanceVariableNames: 'quadrant ' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! Object subclass: #CRLookupItem instanceVariableNames: 'distance feature char ' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CRLookupItem commentStamp: '' prior: 0! Instances of this class are used to build the result of a dictionary lookup. In general, a lookup result consists not only of the most similar fature, but of a certain amount of similar features. These matches are hold in instances of this class. Besides the feature itself, the associated character and the distance to the feature that was looked up are also stored.! CRLookupItem class instanceVariableNames: ''! SortedCollection subclass: #CRLookupResult instanceVariableNames: 'minSize dictionary lookupIndex ' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CRLookupResult commentStamp: '' prior: 0! Whenever a feature is looked up in a dictionary, the result is stored in an instance of this class. In general, the result doesn't contain only the most similar feature. Much more, it contains a certain amount of similar features ordered by distance. Besides other access possibilities, instances of this class provide an iterator to iterate through the result. ! CRLookupResult class instanceVariableNames: ''! Object subclass: #CRParameters instanceVariableNames: 'isAlertEnabled alertDistancePercentage alertRelativeDistanceDifferencePercentage alertDistanceDifferencePercentage angleSector minAngle minDirectionLengthPercentage shapeRelevance sizeRelevance startEndRelevance strokeRelevance timeRelevance centroidRelevance angleRelevance speedPercentage escapeTime rejectDistanceDifference isRejectEnabled rejectRelativeDistanceDifference alertDistance acuteAngleRelevance alertDistanceDifference alertRelativeDistanceDifference rejectDistance ' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CRParameters commentStamp: '' prior: 0! Each dictionary contains a set of parameters that determine capturing and the lookup process. Instances of this class are used to store and access these parameters.! CRParameters class instanceVariableNames: ''! Object subclass: #CRRecognizer instanceVariableNames: 'dictionary displayProperties points coordinates lastPoint startTime endTime directions directionPoints escapePossible isEchoEnabled echo rasteredPoints ' classVariableNames: 'CharacterDictionary ' poolDictionaries: '' category: 'Genie-Engine'! !CRRecognizer commentStamp: '' prior: 0! The class CRRecognizer is the heart of the genie recognition engine. Instances of it control the whole process of capturing a new stroke, decomposing it into appropriate vectors and building the according feature (a subclass of CRFeature). The recognizing process consists of two states: 1) In the first step, the points (in global display coordinates) are just added to the collection called points of the recognizer. The recognizer doesn't do much processing in this first step: It just checks wheter the new points is at least a given distance (in pixel) from the last one. If not, the point is ignored. If echo is enabled, the recognizer shows an cho feedback of the stroke on the screen (Using an array of LineMorphs). 2) In the second step, the recognizer builds the feature according to the stroke. It translates the points from global display coordinates into normalized feature coordinates (using the current display properties), it determines the siginificant points, etc. This step is more time consuming than the first, but compared to the dictionary lookup (the feature comparing algorithms) it's nothing. NOTE: To optimize speeds on PDA, the recognizer doesn't use floating point operations. This makes it sometimes a little bit more complicated. Instance variables: dictionary The dictionary the recognizer is recognizing for. In fact, The recognizer needs not really the dictionary but much more the associated parameters. displayProperties The display properties the recognizer uses to translate from global screen coordinates (pixel) into normalized feature coordinates. points The captured points in global screen coordinates. coordinates Informations about the location of the recognized stroke in global screen coordinates. lastPoint The last captured points that was captured. (Needs to be stored seperately because captured points are only added to points when the distance to the last point is big enough. startTime Time when the recognizer started. endTime Time when the recognizer stopped. directionPoints Collection of all the points the recognizer considers as significant (in normalized feature coordinates) escapePossible Is it possible to escape the recognizer by leaving the pen at the startposition for a while? isEchoEnabled Shall the recognizer generate a graphical echo of the feature? echo Collection holding all the LineMorphs representing the echo! CRRecognizer class instanceVariableNames: ''! Object subclass: #CRRecognizerCoordinates instanceVariableNames: 'start end left top bottom right ' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CRRecognizerCoordinates commentStamp: '' prior: 0! This class stores the source coordinates of the entered stroke (in global pixel). It contains the bounding box, start and end point, top-most point, right-most point, etc. It can be retrieved from the recognizer and can be used to execute some actions depending on where the stroke was drawn.! CRRecognizerCoordinates class instanceVariableNames: ''! CRFeature subclass: #CRStrokeFeature instanceVariableNames: 'maxStrokeDistance capturedPoints directionVectors extent startPoint relativePoints squaredWeightedLengths posAngleSum negAngleSum absAngleSum endPoint acuteAngleCount acuteAnglePosition originalStartPoint hotspot globalAngles ' classVariableNames: 'StrokeReferenceFeature1 ' poolDictionaries: '' category: 'Genie-Engine'! CRDisplayProperties subclass: #CRTempDisplayProperties instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Genie-Engine'! !CRTempDisplayProperties commentStamp: '' prior: 0! This class inherits from CRDisplayProperties and it's basically the same. The one and only difference is that instances of this class can have names that are already used by other CRDisplayProperties instances.! !CRChar methodsFor: 'testing' stamp: 'NS 8/7/2000 16:44'! isCode "Is the character Squeak code (e.g. #beep#Smalltalk beep)" ^ self type = #code! ! !CRChar methodsFor: 'testing' stamp: 'NS 8/7/2000 16:44'! isCommand "Is the character a command (e.g. #inspectLastGesture)" ^ self type = #command! ! !CRChar methodsFor: 'testing' stamp: 'NS 8/7/2000 16:45'! isStrokes "Is the character a sequence of real characters (keystrokes) (e.g. Hello)" ^ self type = #strokes! ! !CRChar methodsFor: 'printing' stamp: 'NS 7/27/2000 15:48'! printOn: aStream ^ self headerString do: [:each | aStream nextPut: each].! ! !CRChar methodsFor: 'comparing' stamp: 'NS 7/19/2000 15:14'! < aCRChar (aCRChar isKindOf: self class) ifFalse: [^ false]. ^ self string < aCRChar string! ! !CRChar methodsFor: 'comparing' stamp: 'NS 7/19/2000 15:14'! <= aCRChar (aCRChar isKindOf: self class) ifFalse: [^ false]. ^ self string <= aCRChar string! ! !CRChar methodsFor: 'comparing' stamp: 'NS 7/19/2000 15:13'! = aCRChar (aCRChar isKindOf: self class) ifFalse: [^ false]. ^ self string = aCRChar string! ! !CRChar methodsFor: 'comparing' stamp: 'NS 7/19/2000 15:14'! > aCRChar (aCRChar isKindOf: self class) ifFalse: [^ false]. ^ self string > aCRChar string! ! !CRChar methodsFor: 'comparing' stamp: 'NS 7/19/2000 15:14'! >= aCRChar (aCRChar isKindOf: self class) ifFalse: [^ false]. ^ self string >= aCRChar string! ! !CRChar methodsFor: 'comparing' stamp: 'NS 7/19/2000 14:48'! hash ^ string hash! ! !CRChar methodsFor: 'accessing' stamp: 'NS 8/7/2000 16:45'! codeString "Return the code string of the character" | pos | self isCode ifFalse: [^ nil]. pos _ string findString: '#' startingAt: 2. ^ string copyFrom: pos + 1 to: string size. ! ! !CRChar methodsFor: 'accessing' stamp: 'NS 8/14/2000 12:18'! correspondingKeystrokes "Return the keystrokes corresponding to the characters." "If character corresponds to string, return string" self isStrokes ifTrue: [^ self normalized]. "If character corresponds to command, try to translate command to character" self isCommand ifTrue: [ string = '#return' ifTrue: [^ Character cr asString]. string = '#tab' ifTrue: [^ Character tab asString]. string = '#insert' ifTrue: [^ Character insert asString]. string = '#delete' ifTrue: [^ Character delete asString]. string = '#backspace' ifTrue: [^ Character backspace asString]. string = '#escape' ifTrue: [^ Character escape asString]. string = '#home' ifTrue: [^ Character home asString]. string = '#end' ifTrue: [^ Character end asString]. string = '#pageUp' ifTrue: [^ Character pageUp asString]. string = '#pageDown' ifTrue: [^ Character pageDown asString]. string = '#arrowUp' ifTrue: [^ Character arrowUp asString]. string = '#arrowDown' ifTrue: [^ Character arrowDown asString]. string = '#arrowLeft' ifTrue: [^ Character arrowLeft asString]. string = '#arrowRight' ifTrue: [^ Character arrowRight asString]]. "Return nil if no corresponding keystrokes" ^ nil. ! ! !CRChar methodsFor: 'accessing' stamp: 'NS 2/19/2001 17:24'! correspondingMouseEventsHand: aHandMorph position: aPoint buttons: anInteger | events | (self isCommand and: [string = '#blueClick' or: [string = '#redClick' or: [string = '#yellowClick']]]) ifFalse: [^ nil]. events _ OrderedCollection new. events add: (MouseButtonEvent new setType: #mouseDown position: aPoint which: anInteger buttons: anInteger hand: aHandMorph stamp: Time millisecondClockValue). events add: (MouseButtonEvent new setType: #mouseUp position: aPoint which: anInteger buttons: anInteger hand: aHandMorph stamp: Time millisecondClockValue). string = '#blueClick' ifTrue: [events do: [:each | each toggleBlueButton]]. string = '#redClick' ifTrue: [events do: [:each | each toggleRedButton]]. string = '#yellowClick' ifTrue: [events do: [:each | each toggleYellowButton]]. ^ events! ! !CRChar methodsFor: 'accessing' stamp: 'NS 8/7/2000 16:48'! correspondsToKeystrokes "Does the character correspond to a sequence of keystrokes?" ^ self correspondingKeystrokes notNil ! ! !CRChar methodsFor: 'accessing' stamp: 'NS 2/19/2001 17:24'! correspondsToMouseEvents "Does the character correspond to a mouse event" ^ (self correspondingMouseEventsHand: World currentHand position: 0@0 buttons: 0) notNil.! ! !CRChar methodsFor: 'accessing' stamp: 'NS 2/13/2001 15:47'! evaluateCodeIn: aCRGesture "Evaluate the code represented by the character with the given CRGesture as the receiver" | codeString | codeString _ self codeString. codeString isNil ifTrue: [^ false]. aCRGesture class compilerClass new evaluate: codeString in: nil to: aCRGesture notifying: nil ifFail: [^ false]. ^ true! ! !CRChar methodsFor: 'accessing' stamp: 'NS 7/27/2000 15:46'! headerString ^ self headerString: 25.! ! !CRChar methodsFor: 'accessing' stamp: 'NS 8/14/2000 12:15'! headerString: maxLenInteger | pos tempString | tempString _ string. (self isCode and: [tempString first = $# and: [(pos _ tempString findString: '#' startingAt: 2) > 2]]) ifTrue: [tempString _ tempString copyFrom: 1 to: pos]. tempString size > maxLenInteger ifTrue: [tempString _ tempString copyFrom: 1 to: maxLenInteger]. ^ tempString! ! !CRChar methodsFor: 'accessing' stamp: 'NS 8/14/2000 12:10'! normalized "Return the normalized character. If the character typ is #strokes, this is usually just the instance variable string. However, if '' are used to escape #, return the string without ''. If the type is #code, return the whole code string including a possible header. If the type is #command, return the command as a symbol" ^ self isCode ifTrue: [self string] ifFalse: [self isCommand ifTrue: [self string allButFirst asSymbol] ifFalse: [(string size > 2 and: [string first = $' and: [string second = $# and: [string last = $']]]) ifTrue: [string allButFirst allButLast] ifFalse: [string]]].! ! !CRChar methodsFor: 'accessing' stamp: 'NS 7/19/2000 14:10'! string ^ string! ! !CRChar methodsFor: 'accessing' stamp: 'NS 7/19/2000 14:10'! string: aString string _ aString! ! !CRChar methodsFor: 'accessing' stamp: 'NS 8/7/2000 16:52'! type "Type of the character: #code, #command or #strokes" string isNil ifTrue: [^ nil]. (string first = $# and: [(string findString: '#' startingAt: 2) > 0]) ifTrue: [^ #code]. (string first = $# and: [string size > 1]) ifTrue: [^ #command]. ^ #strokes! ! !CRChar class methodsFor: 'instance creation' stamp: 'NS 7/19/2000 14:31'! string: aString ^ super new string: aString! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/7/2000 16:56'! addDistances: addCollection to: targetCollection weight: weightNumber eliminatedDist: limitNumber "Add all the distances of addCollection wieghted by weightNumber to targetCollection, but do this only if the distances are smaller than limitNumber" 1 to: targetCollection size do: [:index | | dist1 | (dist1 _ targetCollection at: index) < limitNumber ifTrue: [ | dist2 | (dist2 _ addCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: dist1 + (weightNumber * dist2 // 1000)] ifFalse: [targetCollection at: index put: limitNumber]]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/7/2000 16:56'! addIndirectParentsTo: aCollection "Add all the direct and indirect parents to aCollection" self parents do: [:each | ((each ~~ self) and: [(aCollection includes: each) not]) ifTrue: [aCollection add: each. each addIndirectParentsTo: aCollection]]. ! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/14/2000 14:05'! addToInvertedDictionaryFeature: aCRFeature char: charObject ((self invertedDictionary includesKey: charObject) not or: [(self invertedDictionary at: charObject) isNil]) ifTrue: [self invertedDictionary at: charObject put: OrderedCollection new]. (self invertedDictionary at: charObject) add: aCRFeature.! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/7/2000 17:23'! capturedPoints: aSymbol "Shall captured points be stored? Possible symbols: #all. #reduced. #none" capturedPoints _ aSymbol! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/15/2000 09:28'! createDistinctName: aStringOrSymbol collection: aCollection "Create a name similar to aString that is not in aCollection" | newName index conflict | newName _ aStringOrSymbol copy. index _ 1. conflict _ 1. [conflict notNil] whileTrue: [index _ index + 1. conflict _ aCollection detect: [:each | each ~~ self and: [each name = newName]] ifNone: []. conflict ifNotNil: [newName _ aStringOrSymbol , ' (' , index printString , ')']]. ^ newName.! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/15/2000 10:12'! createParentsCollection ^ Set new.! ! !CRDictionary methodsFor: 'private' stamp: 'NS 6/13/2000 11:02'! dictionary ^ dictionary! ! !CRDictionary methodsFor: 'private' stamp: 'NS 6/13/2000 11:12'! dictionary: aDictionary dictionary _ aDictionary! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/26/2000 15:00'! eliminateDistancesFrom: targetCollection conditionCollection: conditionCollection limit: limitNumber minSize: minSizeInteger eliminatedDist: eliminatedNumber | sorted limit | sorted _ SortedCollection new: minSizeInteger. 1 to: targetCollection size do: [:index | (targetCollection at: index) < eliminatedNumber ifTrue: [sorted addLast: (conditionCollection at: index)]]. sorted reSort. sorted size < minSizeInteger ifTrue: [^ self]. limit _ limitNumber max: (sorted at: minSizeInteger). 1 to: targetCollection size do: [:index | (conditionCollection at: index) > limit ifTrue: [targetCollection at: index put: eliminatedNumber]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/14/2000 12:21'! invertedDictionary ^ invertedDictionary! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/14/2000 12:21'! invertedDictionary: aDictionary invertedDictionary _ aDictionary! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/9/2000 15:08'! localLookup: aCRFeature minResultSize: anInteger symmetric: aBoolean maxSpeed: speedIntegerOrNil "Lookup aCRFeature in the local dictionary. The returned result contains at least anInteger entries. If aBoolean is true, symmetric distances are used for the lookup (means (A dist: B) = (B dist: A)). speedIntegerOrNil determines the maximum speed (0 max accuracy; 100 max. seed)." | features realMinSize maxNormDist eliminatedDist compSequence relevantDistances eliminateDistances minRelevance tempDistances minNotEliminatedCount eliminateWeightTotalSum eliminateWeightSum relevanceSum result strokeDistanceSelector speed | self isEmpty ifTrue: [^ CRLookupResult dictionary: self]. "Set speed. If speed is nil take speed in assigned preferences" speed _ speedIntegerOrNil isNil ifTrue: [self parameters speedPercentage] ifFalse: [speedIntegerOrNil min: self parameters speedPercentage]. features _ OrderedCollection new: self size. self dictionary keysDo: [:each | features add: each]. realMinSize _ anInteger min: self size. relevantDistances _ Array new: self size. eliminateDistances _ Array new: self size. 1 to: eliminateDistances size do: [:index | eliminateDistances at: index put: 0. relevantDistances at: index put: 0]. tempDistances _ Array new: self size. maxNormDist _ CRFeature maxNormDistance. eliminatedDist _ SmallInteger maxVal // 10. minRelevance _ 10. minNotEliminatedCount _ (2 * realMinSize max: 4) min: self size. "Stroke selector depending on wheter the distance is symmetric" strokeDistanceSelector _ aBoolean ifTrue: [#putSymmetricStrokeDistancesFrom:to:into:eliminateCollection:limit:] ifFalse: [#putStrokeDistancesFrom:to:into:eliminateCollection:limit:]. "There are a lot of independent metrics for features. (Each of these metrics returns a value between 0 and CRFeature maxNormDistance). The computation sequence array determines in what order this distances are calculated. Basically, the order doesn't matter, but because some of the distances need a lot of time to be calculated. the order can help to minimize the time consumption. The table consists of multiple rows each of them representing a distance metrics: Here the fields of a row: 1. priority (determines order of execution) 2. eliminateWeight (how important is this distance for eliminating bad features) 3. relevance (relevance of the distance as it is definied in the dictionary's parameters) 4. selector name to calculate this distance. 5. eliminateLimit (if the distance to a feature is bigger than this, the feature gets eliminated and will not be considered in feature distance calculations) 6. totalEliminateLimit (if the total distance (the distance of the newly calculated distance added to the previously calculated distances) to a feature is bigger than this, the feature gets eliminated). 7. Similar to 5. an 6., but here the distances are weighted by the relevance entered in the dictionary's parameters." compSequence _ SortedCollection sortBlock: [:a :b | a first < b first]. compSequence add: {1. 10. 0. #putStrokeSizeDistancesFrom:to:into:eliminateCollection:limit:. speed * 3 // 4. 0. 0}. compSequence add: {2. 5. self parameters relevancePromilleForPrimary: self parameters startEndRelevance. #putStartEndDistancesFrom:to:into:eliminateCollection:limit:. speed // 2. speed * 3 // 4. 0}. compSequence add: {3. 5. self parameters relevancePromilleForPrimary: self parameters angleRelevance. #putAngleDistancesFrom:to:into:eliminateCollection:limit:. 0. speed * 5 // 6. 0}. compSequence add: {4. 5. self parameters relevancePromilleForPrimary: self parameters shapeRelevance. #putShapeDistancesFrom:to:into:eliminateCollection:limit:. 0. speed. 0. 0}. compSequence add: {5. 0. self parameters relevancePromilleForPrimary: self parameters strokeRelevance. strokeDistanceSelector. 0. 0. speed}. compSequence add: {6. 0. self parameters relevancePromilleForSecondary: self parameters acuteAngleRelevance. #putAcuteAngleDistancesFrom:to:into:eliminateCollection:limit:. 0. 0. 0}. compSequence add: {7. 0. self parameters relevancePromilleForSecondary: self parameters sizeRelevance. #putSizeDistancesFrom:to:into:eliminateCollection:limit:. 0. 0. 0}. compSequence add: {8. 0. self parameters relevancePromilleForSecondary: self parameters timeRelevance. #putTimeDistancesFrom:to:into:eliminateCollection:limit:. 0. 0. 0}. eliminateWeightTotalSum _ compSequence inject: 0 into: [:sum :each | sum + (each at: 2)]. eliminateWeightSum _ 0. relevanceSum _ 0. compSequence do: [:each | | eliminateWeight relevance selector eliminateSpeed totalEliminateSpeed totalRelevantSpeed tempLimit | eliminateWeight _ each at: 2. relevance _ each at: 3. selector _ each at: 4. eliminateSpeed _ each at: 5. totalEliminateSpeed _ each at: 6. totalRelevantSpeed _ each at: 7. eliminateWeightSum _ eliminateWeightSum + eliminateWeight. relevanceSum _ relevanceSum + relevance. relevance < minRelevance ifTrue: [relevance _ 0]. (relevance > 0 or: [eliminateWeight > 0]) ifTrue: [ | args | "Set arguments and calculate distance" args _ {aCRFeature. features. tempDistances. eliminateDistances. eliminatedDist}. self perform: selector withArguments: args. "Eliminate regarding only the newly calculated distance" eliminateSpeed > 0 ifTrue: [tempLimit _ (100 - eliminateSpeed) * maxNormDist // 100. self eliminateDistancesFrom: tempDistances conditionCollection: tempDistances limit: tempLimit minSize: minNotEliminatedCount eliminatedDist: eliminatedDist. "Transcript show: 'E: ', (tempDistances select: [:each2 | each2 < eliminatedDist]) size printString , ' '"]. "Add newly calculated distance to the total distance weighted by eliminateWeight" eliminateWeight > 0 ifTrue: [self addDistances: tempDistances to: eliminateDistances weight: (1000 * eliminateWeight // eliminateWeightTotalSum) eliminatedDist: eliminatedDist]. "Eliminate regarding the total distance" totalEliminateSpeed > 0 ifTrue: [tempLimit _ (100 - totalEliminateSpeed) * maxNormDist * eliminateWeightSum // (eliminateWeightTotalSum * 100). self eliminateDistancesFrom: eliminateDistances conditionCollection: eliminateDistances limit: tempLimit minSize: minNotEliminatedCount eliminatedDist: eliminatedDist. "Transcript show: 'TE: ', (eliminateDistances select: [:each2 | each2 < eliminatedDist]) size printString , ' '"]. "Add the newly created distance to the total distance weighted by the actual weight definied in the dictionary's parameters" relevance > 0 ifTrue: [self addDistances: tempDistances to: relevantDistances weight: relevance eliminatedDist: eliminatedDist]. "Eliminate regarding the total distance weighted according to the parameters" totalRelevantSpeed > 0 ifTrue: [tempLimit _ (100 - totalRelevantSpeed) * maxNormDist * relevanceSum // 100000. self eliminateDistancesFrom: eliminateDistances conditionCollection: relevantDistances limit: tempLimit minSize: minNotEliminatedCount eliminatedDist: eliminatedDist. "Transcript show: 'TR: ', (eliminateDistances select: [:each2 | each2 < eliminatedDist]) size printString , ' '"]]. "Transcript show: '/ '"]. "Transcript show: ' '." "Build and return result" result _ CRLookupResult dictionary: self minSize: anInteger. 1 to: relevantDistances size do: [:index | (eliminateDistances at: index) < eliminatedDist ifTrue: [ | feature | feature _ features at: index. result addFeature: feature char: (self dictionary at: feature) distance: ((relevantDistances at: index) min: maxNormDist)]]. ^ result.! ! !CRDictionary methodsFor: 'private' stamp: 'NS 6/13/2000 11:11'! parameters: aCRParameters parameters _ aCRParameters! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/26/2000 09:40'! putAcuteAngleDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature acuteAngleDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/25/2000 17:37'! putAngleDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature angleDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/25/2000 17:49'! putShapeDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature shapeDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/25/2000 17:49'! putSizeDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature sizeDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/25/2000 17:50'! putStartEndDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature startEndDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/25/2000 17:49'! putStrokeDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature strokeDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/25/2000 17:51'! putStrokeSizeDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature strokeSizeDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/25/2000 17:50'! putSymmetricStrokeDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature symmetricStrokeDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 7/25/2000 17:49'! putTimeDistancesFrom: aCRFeature to: featureCollection into: targetCollection eliminateCollection: eliminateCollection limit: limitNumber 1 to: eliminateCollection size do: [:index | (eliminateCollection at: index) < limitNumber ifTrue: [targetCollection at: index put: (aCRFeature timeDistance: (featureCollection at: index))]].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/7/2000 17:44'! reduceCapturedPoints "Reduce the captured points of all my features" self dictionary keysDo: [:each | each reduceCapturedPoints].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/7/2000 17:45'! removeCapturedPoints "Remove the captured points of all my features" self dictionary keysDo: [:each | each removeCapturedPoints].! ! !CRDictionary methodsFor: 'private' stamp: 'NS 8/15/2000 17:48'! replaceBy: aCRDictionary "Replace this dictionary by aCRDictionary. All the children's are updated and this dictionary is deleted (name is set to nil)" self dependents ifNotNil: [self dependents do: [:each | ((each isKindOf: self class) and: [each parents includes: self]) ifTrue: [each removeParent: self. each addParent: aCRDictionary]]]. self name: nil.! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/30/2000 20:24'! addChild: aCRDictionary self addChild: aCRDictionary! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:46'! atChar: anObject ^ self invertedDictionary at: anObject! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:46'! atChar: anObject ifAbsent: aBlock ^ self invertedDictionary at: anObject ifAbsent: aBlock! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/21/2000 09:09'! atCharString: aString ^ self invertedDictionary at: (CRChar string: aString) ! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:36'! atFeature: aCRFeature ^ self dictionary at: aCRFeature.! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:36'! atFeature: aCRFeature ifAbsent: aBlock ^ self dictionary at: aCRFeature ifAbsent: aBlock! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/21/2000 09:19'! atFeature: aCRFeature put: anObject self removeFeature: aCRFeature ifAbsent: []. self hasReducedCapturedPoints ifTrue: [anObject reduceCapturedPoints]. self hasNoCapturedPoints ifTrue: [aCRFeature removeCapturedPoints]. self dictionary at: aCRFeature put: anObject. self addToInvertedDictionaryFeature: aCRFeature char: anObject. self updateMaxMultipleDefinitions: anObject.! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:38'! charSize ^ self invertedDictionary size! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:37'! featureSize ^ self dictionary size! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:39'! featuresAndCharsDo: aBlock ^ self dictionary keysAndValuesDo: aBlock! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:49'! includesChar: anObject ^ self invertedDictionary includesKey: anObject! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:37'! includesFeature: anObject ^ self dictionary includesKey: anObject! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 8/1/2000 15:16'! indirectParentIncludesFeature: anObject self indirectParents do: [:each | (each includesFeature: anObject) ifTrue: [^ true]]. ^ false.! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 13:11'! isEmpty ^ self size = 0! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 13:10'! notEmpty ^ self isEmpty not! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:51'! removeChar: anObject ^ self removeChar: anObject ifAbsent: [self invertedDictionary errorKeyNotFound]! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:52'! removeChar: anObject ifAbsent: aBlock | return | return _ self invertedDictionary removeKey: anObject ifAbsent: [^ aBlock]. self dictionary keysAndValuesDo: [:key :value | value = anObject ifTrue: [self dictionary removeKey: key]]. ^ return.! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 14:00'! removeFeature: aCRFeature ^ self removeFeature: aCRFeature ifAbsent: [self dictionary errorKeyNotFound]! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/21/2000 09:20'! removeFeature: aCRFeature ifAbsent: aBlock | removed char | removed _ self dictionary includesKey: aCRFeature. char _ self dictionary removeKey: aCRFeature ifAbsent: [aBlock]. removed ifTrue: [(self invertedDictionary at: char) remove: aCRFeature. self updateMaxMultipleDefinitions]. ^ char. ! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/19/2000 13:08'! renameChar: anObject to: newObject | features | features _ self invertedDictionary removeKey: anObject. self invertedDictionary at: newObject put: features. features ifNotNil: [features do: [:each | self dictionary at: each put: newObject]]. ! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 8/1/2000 15:17'! selfOrIndirectParentIncludesFeature: anObject ^ (self includesFeature: anObject) or: [self indirectParentIncludesFeature: anObject]! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 13:11'! size ^ self featureSize! ! !CRDictionary methodsFor: 'accessing dictionaries' stamp: 'NS 7/14/2000 12:28'! updateInvertedDictionary self invertedDictionary keysDo: [:each | self invertedDictionary at: each put: nil]. self dictionary keysAndValuesDo: [:key :value | self addToInvertedDictionaryFeature: key char: value].! ! !CRDictionary methodsFor: 'views' stamp: 'NS 8/7/2000 17:45'! asCloseableMorph "Return a closable morph representing this dictionary" ^ (self newMorph hasCloseButton: true) ensureLayout! ! !CRDictionary methodsFor: 'views' stamp: 'NS 8/7/2000 17:46'! asMorph "Return a morph (with layout) representing this dictionary" ^ self newMorph ensureLayout! ! !CRDictionary methodsFor: 'views' stamp: 'NS 8/8/2000 12:01'! browserAppModel "Return the application model of browsers displaying my contents. To save space, usually only one model is created" ^ self dependents detect: [:each | each isKindOf: CRDictionaryBrowserAppModel] ifNone: [CRDictionaryBrowserAppModel dictionary: self]! ! !CRDictionary methodsFor: 'views' stamp: 'NS 8/7/2000 17:47'! newBrowser "Create a new browser displaying my contents" ^ self browserAppModel newBrowser! ! !CRDictionary methodsFor: 'views' stamp: 'NS 8/7/2000 17:48'! newCloseableMorph "Create a new closeable morph representing me" | morph | morph _ self newMorph. morph hasCloseButton: true. ^ morph.! ! !CRDictionary methodsFor: 'views' stamp: 'NS 8/7/2000 17:48'! newMorph "Create a new morph representing me" | morph | morph _ CRDictionaryMorph dictionary: self. ^ morph.! ! !CRDictionary methodsFor: 'views' stamp: 'NS 8/7/2000 17:48'! openBrowser "Open a new browser showing my contents" ^ self newBrowser asMorph openInWorld! ! !CRDictionary methodsFor: 'views' stamp: 'NS 8/7/2000 17:48'! openMorph "Open a new morph representing me" self newCloseableMorph openInWorld! ! !CRDictionary methodsFor: 'updating' stamp: 'NS 8/7/2000 17:54'! update: aParameter | object symbol | "Split argument into object and symbol" (aParameter isKindOf: Array) ifTrue: [object _ aParameter first. symbol _ aParameter second] ifFalse: [object _ aParameter]. "Avoid infinite (circular) update calls (In case of circles in the parent hierarchie)" object == self ifTrue: [^ self]. "If parent name is deleted, remove it" ((self parents includes: object) and: [symbol = #deleteName]) ifTrue: [self removeParent: object]. "Necessary to avoid infin ite (circular) update calls. (In case of circles in the parent hierarchie)" (object isKindOf: self class) ifFalse: [object _ self]. self changed: {object. symbol}.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/1/2000 14:07'! addParent: aCRDictionary (aCRDictionary isNil or: [aCRDictionary == self]) ifTrue: [^ nil]. aCRDictionary addDependent: self. parents add: aCRDictionary. ^ aCRDictionary.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/1/2000 14:18'! addParentName: aString | ref | ref _ self parentFromName: aString. ^ self addParent: ref.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 6/24/2000 17:36'! capturedPoints ^ capturedPoints! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/14/2000 14:17'! exportedName ^ exportedName! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 17:55'! exportedName: aSymbolOrString "Set exported name. If it's nil or empty, remove it from the instance browser" | changed n | n _ aSymbolOrString isString ifTrue: [aSymbolOrString asSymbol] ifFalse: [aSymbolOrString]. changed _ exportedName ~= n. exportedName _ n. self class allInstances do: [:each | (each ~~ self and: [n isEmptyOrNil not and: [each exportedName = n]]) ifTrue: [each exportedName: #'']]. changed ifTrue: [self class updateExportedInstanceDictionary].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/19/2000 19:13'! exportedNameAsString ^ self exportedName isNil ifTrue: [self exportedName printString] ifFalse: [self exportedName asString].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/6/2000 07:49'! fillFeaturesCache self dictionary keysDo: [:each | each fillCache].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 17:57'! getParentsAndErrorForString: aString "Takes a string of parent dictionary names and returns an array containing the references to the parent dictionaries named in the string and an error message string" | errors end start tempName p newP | p _ Set new. aString isEmptyOrNil ifTrue: [^ {p. nil}]. errors _ Set new. end _ 1. [end > 0] whileTrue: [ start _ aString findString: '''' startingAt: end. end _ 0. start > 0 ifTrue: [start _ start + 1. end _ aString findString: '''' startingAt: start. end > 0 ifTrue: [tempName _ aString copyFrom: start to: end - 1. (newP _ self parentFromName: tempName) isNil ifTrue: [errors add: tempName] ifFalse: [p add: newP]. end _ end + 1]]]. p isEmpty ifTrue: [^ {p. aString}]. ^ errors isEmpty ifTrue: [{p. nil}] ifFalse: [ | s | s _ ''. errors do: [:each | s isEmpty ifFalse: [s _ s , ' ']. s _ s , '''' , each, '''']. {p. s}].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 17:57'! indirectParentCount "Sum of direct and indirect parents!!" ^ self indirectParents size! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 17:58'! indirectParents "Direct and indirect parents" | all | all _ Set with: self. self addIndirectParentsTo: all. all remove: self. ^ all.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 17:59'! lookup: aCRFeature "Lookup a feature with the default parameters of this dictionary" | minSize | "In case of alert / reject, we need at least one different character in the result" minSize _ (self parameters isAlertEnabled or: [self parameters isRejectEnabled]) ifTrue: [self totalMaxMultipleDefinitions + 1 max: 3] ifFalse: [3]. ^ self lookup: aCRFeature minResultSize: minSize.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/26/2000 16:04'! lookup: aCRFeature minResultSize: anInteger ^ self lookup: aCRFeature minResultSize: anInteger symmetric: false! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/30/2000 19:55'! lookup: aCRFeature minResultSize: anInteger symmetric: aBoolean ^ self lookup: aCRFeature minResultSize: anInteger symmetric: aBoolean maxSpeed: nil includeParents: true.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/1/2000 15:12'! lookup: aCRFeature minResultSize: anInteger symmetric: aBoolean maxSpeed: speedIntegerOrNil includeParents: parentsBoolean | result | result _ self localLookup: aCRFeature minResultSize: anInteger symmetric: aBoolean maxSpeed: speedIntegerOrNil. parentsBoolean ifTrue: [self indirectParents do: [:each | | parentResult feature | feature _ (self parameters hasSameCapturing: each parameters) ifTrue: [aCRFeature] ifFalse: [(CRRecognizer dictionary: each) calculateNewFeature: aCRFeature]. parentResult _ each lookup: feature minResultSize: anInteger symmetric: aBoolean maxSpeed: speedIntegerOrNil includeParents: false. result addResult: parentResult]]. ^ result.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 17:59'! maxMultipleDefinitions "Max. number of different strokes for one character" ^ maxMultipleDefinitions! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 6/24/2000 18:07'! maxMultipleDefinitions: anInteger maxMultipleDefinitions _ anInteger! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 6/22/2000 18:05'! name ^ name! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/31/2000 11:51'! name: aSymbolOrString ^ self name: aSymbolOrString makeDistinct: true.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 18:00'! name: aSymbolOrString makeDistinct: aBoolean "Set the name of the dictionary and make it distinct if aBooleean is true" | n changed | n _ aSymbolOrString isString ifTrue: [aSymbolOrString asSymbol] ifFalse: [aSymbolOrString]. n isEmptyOrNil not ifTrue: [aBoolean ifTrue: [n _ self createDistinctName: n collection: self class allInstances] ifFalse: [(self class allInstances detect: [:each | each ~~ self and: [each name = n]] ifNone: []) ifNotNil: [^ false]]]. changed _ name ~= n. name _ n. (changed and: [name isEmptyOrNil]) ifTrue: [self changed: {self. #deleteName}]. ^ true.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/19/2000 19:11'! nameAsString ^ self name isNil ifTrue: [self name printString] ifFalse: [self name asString].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 6/13/2000 11:02'! parameters ^ parameters! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/30/2000 15:52'! parentCount ^ self parents size! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/1/2000 14:18'! parentFromName: aString | ref | ref _ aString isEmptyOrNil ifFalse: [self class allInstances detect: [:each | each nameAsString = aString and: [each ~~ self]] ifNone: []]. ^ ref! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 18:00'! parentMenuSelector: aSymbol "Menu of all parents" | menu addBlock | menu _ MenuMorph new. addBlock _ [:dict | | s | s _ dict nameAsString. dict exportedName isEmptyOrNil ifFalse: [s _ s , ' -> ' , dict exportedNameAsString]. menu add: s target: dict selector: aSymbol]. self parents do: [:each | addBlock value: each]. self indirectParents size > self parents size ifTrue: [menu addLine. self indirectParents do: [:each | (self parents includes: each) ifFalse: [addBlock value: each]]]. ^ menu! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/30/2000 20:19'! parents ^ parents! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/30/2000 21:59'! parents: aCollection (parents isKindOf: Collection) ifTrue: [self removeAllParents] ifFalse: [parents _ Set new]. aCollection do: [:each | self addParent: each].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/30/2000 14:57'! parentsAsString | s | s _ ''. self parents do: [:each | s isEmpty ifFalse: [s _ s , ' ']. s _ s , '''', each nameAsString, '''']. ^ s.! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/1/2000 14:21'! parentsFromString: aString | array | array _ (self getParentsAndErrorForString: aString). self removeAllParents. self parents: array first. ^ array second. ! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/14/2000 11:16'! recalculate "Calculate all the dictionary features with the current parameters" | recognizer newDictionary barValue | self hasAllCapturedPoints ifFalse: [^ false]. self releaseFeaturesCache. recognizer _ CRRecognizer dictionary: self. newDictionary _ self dictionary class new: self dictionary size. barValue _ 0. 'Recalculating...' displayProgressAt: Sensor cursorPoint from: 0 to: self dictionary size during: [:bar | self dictionary keysAndValuesDo: [:key :value | bar value: (barValue _ barValue + 1). newDictionary at: (recognizer calculateNewFeature: key) fillCache put: value]]. self dictionary: newDictionary. self updateInvertedDictionary. self changed: {self. #recalculate}. ^ true! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/6/2000 07:53'! releaseFeaturesCache self dictionary keysDo: [:each | each releaseCache].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/30/2000 22:00'! removeAllParents self parents do: [:each | self removeParent: each].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 7/30/2000 20:28'! removeParent: aCRDictionary aCRDictionary removeDependent: self. parents remove: aCRDictionary ifAbsent: [].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 18:02'! storeAllCapturedPoints "Store all captured points for festures in the dictionary. Affects both, old and new features!!" ^ self hasAllCapturedPoints or: [self isEmpty and: [self capturedPoints: #all. true]]! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 18:03'! storeNoCapturedPoints "Store no captured points for festures in the dictionary. Affects both, old and new features!!" (self hasAllCapturedPoints or: [self hasReducedCapturedPoints]) ifTrue: [self removeCapturedPoints. self capturedPoints: #none]. ^ true! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 18:03'! storeReducedCapturedPoints "Store reduced captured points for festures in the dictionary. Affects both, old and new features!!" self hasAllCapturedPoints ifTrue: [self reduceCapturedPoints. self capturedPoints: #reduced. ^ true]. self hasReducedCapturedPoints ifTrue: [^ true]. (self hasNoCapturedPoints and: [self isEmpty]) ifTrue: [self capturedPoints: #reduced. ^ true]. ^ false! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/7/2000 18:03'! totalMaxMultipleDefinitions "As maxMultipleDefinitions, but including all the parent dictionaries" ^ self indirectParents inject: self maxMultipleDefinitions into: [:max :each | max max: each maxMultipleDefinitions].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/1/2000 15:33'! totalParentCount ^ self indirectParentCount! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 8/1/2000 15:34'! totalSize ^ self indirectParents inject: self size into: [:sum :each | sum + each size].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 6/24/2000 18:19'! updateMaxMultipleDefinitions self maxMultipleDefinitions: 0. self dictionary valuesDo: [:each | self updateMaxMultipleDefinitions: each].! ! !CRDictionary methodsFor: 'accessing' stamp: 'NS 6/24/2000 18:11'! updateMaxMultipleDefinitions: anObject | count | count _ 0. self dictionary valuesDo: [:each | each = anObject ifTrue: [count _ count + 1]]. self maxMultipleDefinitions: (self maxMultipleDefinitions max: count).! ! !CRDictionary methodsFor: 'initialize-release' stamp: 'NS 8/15/2000 11:24'! initialize self dictionary: Dictionary new. self invertedDictionary: Dictionary new. self parameters: CRParameters new. self capturedPoints: self class capturedPoints. self name: #'No name'. self exportedName: #''. self maxMultipleDefinitions: 0. self parents: self createParentsCollection. self storeParents: nil. self storedName: nil! ! !CRDictionary methodsFor: 'copying' stamp: 'NS 7/31/2000 11:51'! copy | dict | dict _ CRDictionary new. dict name: self nameAsString asSymbol. dict dictionary: self dictionary copy. dict capturedPoints: self capturedPoints copy. dict maxMultipleDefinitions: self maxMultipleDefinitions copy. dict parameters: self parameters copy. dict invertedDictionary: self invertedDictionary copy. dict parents: self parents copy. dict storeParents: self storeParents copy. ^ dict.! ! !CRDictionary methodsFor: 'objects from disk' stamp: 'NS 8/15/2000 09:48'! basicStoreDataOn: aDataStream aDataStream beginInstance: self class size: 9. ('Saving ''', self nameAsString , ''' ...') displayProgressAt: Sensor cursorPoint from: 0 to: 20 during: [:bar | aDataStream nextPut: name. bar value: 1. aDataStream nextPut: dictionary. bar value: 10. aDataStream nextPut: capturedPoints. bar value: 11. aDataStream nextPut: maxMultipleDefinitions. bar value: 12. aDataStream nextPut: parameters. bar value: 15. aDataStream nextPut: invertedDictionary. bar value: 19. aDataStream nextPut: exportedName. bar value: 20. aDataStream nextPut: self parentsAsString]. self storeParents ifTrue: [aDataStream nextPut: parents] ifFalse: [aDataStream nextPut: parents class new]. ! ! !CRDictionary methodsFor: 'objects from disk' stamp: 'NS 8/15/2000 17:57'! readDataFrom: aDataStream size: varsOnDisk | parentsString sameNameInstance choice text tempParents array | varsOnDisk ~= 9 ifTrue: [self error: 'Wrong file format']. aDataStream beginReference: self. "Initialize first to always ensure a consistent state" self initialize. "Test wheter there is already a dictionary with the same name. If yes, ask the user what to do" self storedName: aDataStream next. (sameNameInstance _ self class name: self storedName) notNil ifTrue: [choice _ (PopUpMenu labels: 'use existing dictionary instead load dictionary with modified name replace existing dictionary') startUpWithCaption: '''', self storedName asString , ''' already exists.'. choice = 1 ifTrue: [self name: nil. text _ 'Skipping ''', self storedName asString, ''' ...']. choice = 3 ifTrue: [sameNameInstance replaceBy: self]. choice > 1 ifTrue: [self name: self storedName. text _ 'Loading ''', self nameAsString, ''' ...']] ifFalse: [self name: self storedName. text _ 'Loading ''', self nameAsString, ''' ...']. "Load or skip the dictionary depending on the user's choice" text displayProgressAt: Sensor cursorPoint from: 0 to: 20 during: [:bar | bar value: 1. dictionary _ aDataStream next. bar value: 10. capturedPoints _ aDataStream next. bar value: 11. maxMultipleDefinitions _ aDataStream next. bar value: 12. parameters _ aDataStream next. bar value: 15. invertedDictionary _ aDataStream next. bar value: 19. exportedName _ #''. "Don't assign exported name!!" aDataStream next. bar value: 20. parentsString _ aDataStream next]. "Read parent dictionaries, but don't connect them!!" parents _ self createParentsCollection. tempParents _ aDataStream next. "If not skipped: Connect parents" (choice isNil or: [choice > 1]) ifTrue: ["Connect stored parents that have been really reloaded" tempParents do: [:each | each name isEmptyOrNil not ifTrue: [self addParent: each]]. "Connect parents that by name" array _ (self getParentsAndErrorForString: parentsString). array first do: [:new | (self parents detect: [:old | old storedName = new name] ifNone: []) ifNil: [self addParent: new]]. (array second notNil or: [self hasParents]) ifTrue: [ text _ 'Dictionary ''' , self nameAsString, ''':'. self hasParents ifTrue: [ text _ text , ' The following parents have been reconnected: ', self parentsAsString]. array second ifNotNil: [ text _ text , ' The following parents are not available: ' , array second]. self inform: text]].! ! !CRDictionary methodsFor: 'objects from disk' stamp: 'NS 7/31/2000 11:19'! storeDataOn: aDataStream self releaseFeaturesCache. self basicStoreDataOn: aDataStream ! ! !CRDictionary methodsFor: 'objects from disk' stamp: 'NS 8/15/2000 11:48'! storeParents "Shall parents be stored too? Only used while storing" ^ storeParents! ! !CRDictionary methodsFor: 'objects from disk' stamp: 'NS 8/15/2000 11:48'! storeParents: aBoolean "Shall parents be stored too? Only used while storing" storeParents _ aBoolean. self parents do: [:each | each storeParents: aBoolean].! ! !CRDictionary methodsFor: 'objects from disk' stamp: 'NS 8/15/2000 11:47'! storedName "Only for internal use while loading dictionaries" ^ storedName! ! !CRDictionary methodsFor: 'objects from disk' stamp: 'NS 8/15/2000 11:47'! storedName: aString "Only for internal use while loading dictionaries" storedName _ aString! ! !CRDictionary methodsFor: 'testing' stamp: 'NS 6/24/2000 17:37'! hasAllCapturedPoints ^ self capturedPoints = #all! ! !CRDictionary methodsFor: 'testing' stamp: 'NS 6/24/2000 17:37'! hasNoCapturedPoints ^ self capturedPoints = #none! ! !CRDictionary methodsFor: 'testing' stamp: 'NS 7/30/2000 19:49'! hasParents ^ self parents isEmpty not! ! !CRDictionary methodsFor: 'testing' stamp: 'NS 6/24/2000 17:37'! hasReducedCapturedPoints ^ self capturedPoints = #reduced! ! !CRDictionary methodsFor: 'testing' stamp: 'NS 7/30/2000 12:19'! isActive ^ self exportedName isEmptyOrNil not or: [self name isEmptyOrNil].! ! !CRDictionary methodsFor: 'comparing' stamp: 'NS 8/7/2000 18:04'! < aCRDictionary "Used to sort dictionaries in the instance browser" ^ self name < aCRDictionary name! ! !CRDictionary class methodsFor: 'instance browser' stamp: 'NS 9/6/2000 17:07'! initializeInstanceBrowser "Create a new instance browser holding all the named dictionary instances. An instance browser is stoed in the variable InstanceBrowser and can be accessed using: CRDictionary instanceBrowser " "Check wheter class CRDictionaryInstanceBrowser is available. If not: exit" Smalltalk at: #CRDictionaryInstanceBrowser ifAbsent: [^ self]. InstanceBrowser ifNotNil: [InstanceBrowser removeAll]. InstanceBrowser _ CRDictionaryInstanceBrowser new. ! ! !CRDictionary class methodsFor: 'instance browser' stamp: 'NS 6/27/2000 14:40'! instanceBrowser ^ InstanceBrowser! ! !CRDictionary class methodsFor: 'instance browser' stamp: 'NS 7/20/2000 15:20'! instanceBrowserMorph ^ InstanceBrowser asMorph! ! !CRDictionary class methodsFor: 'instance browser' stamp: 'NS 7/20/2000 15:21'! openInstanceBrowserMorph self instanceBrowserMorph openInWorld! ! !CRDictionary class methodsFor: 'class initialization'! initialize " CRDictionary initialize " self initializeExportedInstanceDictionary. self initializeInstanceBrowser. ! ! !CRDictionary class methodsFor: 'instance creation' stamp: 'NS 6/13/2000 11:15'! new ^ super new initialize! ! !CRDictionary class methodsFor: 'default values' stamp: 'NS 8/8/2000 10:27'! capturedPoints "This is the default value for a newly created dictionary. It determines, how the captured points of features in the dictionary are stored: #all: All the captured points are stored. #reduced: Only a reduced set of the captured points are stored. #node: None of the captured points are stored." ^ #none! ! !CRDictionary class methodsFor: 'named instances' stamp: 'NS 9/14/2000 15:14'! name: aSymbol "Returns the instance with the given exported name." aSymbol isEmptyOrNil ifTrue: [^ nil]. ^ self allInstances detect: [:each | each name = aSymbol] ifNone: [self allInstances detect: [:each | each name notNil and: [each name sameAs: aSymbol]] ifNone: []].! ! !CRDictionary class methodsFor: 'exported instances' stamp: 'NS 8/8/2000 10:28'! exportedName: aSymbol "Returns the instance with the given exported name. For a fast lookup, the instances with an exported name are stored in a dictionary of the class (ExportedInstanceDictionary)". aSymbol isEmptyOrNil ifTrue: [^ nil]. ^ ExportedInstanceDictionary at: aSymbol ifAbsent: [].! ! !CRDictionary class methodsFor: 'exported instances' stamp: 'NS 8/8/2000 10:29'! initializeExportedInstanceDictionary "Initialize the dictionary of all the instances with an exported name. The exported instances could also be accessed using the instance collection, but it would be slower." " CRDictionary initializeExportedInstanceDictionary " ExportedInstanceDictionary _ Dictionary new. self allInstances do: [:each | (each exportedName isEmptyOrNil not) ifTrue: [ExportedInstanceDictionary at: each exportedName put: each]].! ! !CRDictionary class methodsFor: 'exported instances' stamp: 'NS 8/8/2000 10:29'! updateExportedInstanceDictionary "Update the dictionary containing the instances with an exported name" self initializeExportedInstanceDictionary! ! !CRDictionary class methodsFor: 'constants' stamp: 'NS 8/8/2000 10:30'! fileNameSuffix "The default file name suffix for a Genie Gesture Dictionary" ^ 'ggd'! ! !CRDisplayProperties methodsFor: 'private' stamp: 'NS 8/8/2000 14:35'! createDistinctName: aString collection: aCollection "Create a name similar to aString, that is not in aCollection" | newName index conflict | newName _ aString copy. index _ 1. conflict _ 1. [conflict notNil] whileTrue: [index _ index + 1. conflict _ aCollection detect: [:each | each ~~ self and: [each name = newName]] ifNone: []. conflict ifNotNil: [newName _ aString, ' (' , index printString , ')']]. ^ newName.! ! !CRDisplayProperties methodsFor: 'private' stamp: 'NS 8/8/2000 14:35'! deactivate: aCRDisplayProperties "Deactivate the current instance" ^ self changed: {self. #activeInstance. aCRDisplayProperties}.! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 8/8/2000 14:35'! activate "Activate the current instance" | old | self isActive ifTrue: [^ self]. old _ self class activeInstance. self class activeInstance: self. old deactivate: self.! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 6/12/2000 13:36'! aspectRatio ^ aspectRatio! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 6/12/2000 13:36'! aspectRatio: aPoint aspectRatio _ aPoint! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 6/27/2000 19:11'! isActive ^ self class activeInstance == self! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 6/13/2000 12:10'! maxSize ^ maxSize! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 6/13/2000 12:11'! maxSize: aNumber maxSize _ aNumber! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 6/12/2000 13:36'! minCaptureDistance ^ minCaptureDistance! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 6/12/2000 13:36'! minCaptureDistance: aPoint ^ minCaptureDistance _ aPoint! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 7/6/2000 09:52'! minMoveDistance ^ minMoveDistance! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 7/6/2000 09:52'! minMoveDistance: anInteger minMoveDistance _ anInteger! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 6/27/2000 17:43'! name ^ name! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 7/31/2000 12:00'! name: aSymbolOrString ^ self name: aSymbolOrString makeDistinct: true.! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 8/8/2000 14:37'! name: aSymbolOrString makeDistinct: aBoolean "Set the name to the given symbol or string. If aBoolean is true and the name is not distinct, create a distinct name absed on the given name" | n changed | n _ aSymbolOrString isString ifTrue: [aSymbolOrString asSymbol] ifFalse: [aSymbolOrString]. n isEmptyOrNil not ifTrue: [aBoolean ifTrue: [n _ self createDistinctName: n collection: self class allInstances] ifFalse: [(self class allInstances detect: [:each | each ~~ self and: [each name = n]] ifNone: []) ifNotNil: [^ false]]]. changed _ name ~= n. name _ n. (changed and: [name isEmptyOrNil]) ifTrue: [self changed: {self. #deleteName}]. ^ true.! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 7/19/2000 19:11'! nameAsString ^ self name isNil ifTrue: [self name printString] ifFalse: [self name asString].! ! !CRDisplayProperties methodsFor: 'accessing' stamp: 'NS 7/6/2000 09:47'! set: aCRDisplayProperties self name: aCRDisplayProperties name. self aspectRatio: aCRDisplayProperties aspectRatio. self maxSize: aCRDisplayProperties maxSize. self minCaptureDistance: aCRDisplayProperties minCaptureDistance. self minMoveDistance: aCRDisplayProperties minMoveDistance. ! ! !CRDisplayProperties methodsFor: 'initialize-release' stamp: 'NS 8/14/2000 11:41'! initializeName: aStringOrSymbol aspectRatio: aspectPoint maxSize: sizeInteger minCaptureDistance: captureInteger minMoveDistance: moveInteger self name: aStringOrSymbol. self aspectRatio: aspectPoint. self maxSize: sizeInteger. self minCaptureDistance: captureInteger. self minMoveDistance: moveInteger. ! ! !CRDisplayProperties methodsFor: 'copying' stamp: 'NS 7/31/2000 12:05'! copy | copy | copy _ self class new. copy set: self. copy name: self nameAsString asSymbol. ^ copy! ! !CRDisplayProperties methodsFor: 'views' stamp: 'NS 8/8/2000 14:38'! asMorph ^ self newMorph ensureLayout! ! !CRDisplayProperties methodsFor: 'views' stamp: 'NS 8/8/2000 14:37'! newMorph | morph | morph _ CRDisplayPropertiesMorph properties: self. ^ morph.! ! !CRDisplayProperties methodsFor: 'objects from disk' stamp: 'NS 7/6/2000 09:50'! readDataFrom: aDataStream size: varsOnDisk varsOnDisk ~= 5 ifTrue: [self error: 'Wrong file format']. aDataStream beginReference: self. self name: aDataStream next. self aspectRatio: aDataStream next. self maxSize: aDataStream next. self minCaptureDistance: aDataStream next. self minMoveDistance: aDataStream next. ! ! !CRDisplayProperties methodsFor: 'objects from disk' stamp: 'NS 7/6/2000 09:48'! storeDataOn: aDataStream aDataStream beginInstance: self class size: 5. aDataStream nextPut: self name. aDataStream nextPut: self aspectRatio. aDataStream nextPut: self maxSize. aDataStream nextPut: self minCaptureDistance. aDataStream nextPut: self minMoveDistance! ! !CRDisplayProperties methodsFor: 'comparing' stamp: 'NS 8/7/2000 11:21'! < aCRDisplayProperties ^ self name < aCRDisplayProperties name! ! !CRDisplayProperties methodsFor: 'comparing' stamp: 'NS 8/8/2000 14:38'! hasSameContents: aCRDisplayProperties "Determine wheter the contents of this instance is the same as the contents of aCRDisplayProperties." (aCRDisplayProperties isKindOf: self class) ifFalse: [^ false]. aCRDisplayProperties == self ifTrue: [^ true]. self name = aCRDisplayProperties name ifFalse: [^ false]. self aspectRatio = aCRDisplayProperties aspectRatio ifFalse: [^ false]. self maxSize = aCRDisplayProperties maxSize ifFalse: [^ false]. self minCaptureDistance = aCRDisplayProperties minCaptureDistance ifFalse: [^ false]. self minMoveDistance = aCRDisplayProperties minMoveDistance ifFalse: [^ false]. ^ true! ! !CRDisplayProperties class methodsFor: 'default parameter' stamp: 'NS 6/12/2000 13:36'! aspectRatio ^ 1@1! ! !CRDisplayProperties class methodsFor: 'default parameter' stamp: 'NS 6/13/2000 10:08'! maxSize ^ 200! ! !CRDisplayProperties class methodsFor: 'default parameter' stamp: 'NS 6/12/2000 13:36'! minCaptureDistance ^ 3! ! !CRDisplayProperties class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:48'! minMoveDistance ^ 4! ! !CRDisplayProperties class methodsFor: 'instance creation' stamp: 'NS 8/14/2000 11:42'! name: aStringOrSymbol aspectRatio: aspectPoint maxSize: sizeInteger minCaptureDistance: captureInteger minMoveDistance: moveInteger ^ super new initializeName: aStringOrSymbol aspectRatio: aspectPoint maxSize: sizeInteger minCaptureDistance: captureInteger minMoveDistance: moveInteger! ! !CRDisplayProperties class methodsFor: 'instance creation' stamp: 'NS 8/14/2000 12:17'! new ^ self name: #'No name' aspectRatio: self aspectRatio maxSize: self maxSize minCaptureDistance: self minCaptureDistance minMoveDistance: self minMoveDistance.! ! !CRDisplayProperties class methodsFor: 'accessing' stamp: 'NS 7/31/2000 21:46'! activeInstance ActiveInstance isNil ifTrue: [self initialize]. ^ ActiveInstance! ! !CRDisplayProperties class methodsFor: 'accessing' stamp: 'NS 6/27/2000 17:45'! activeInstance: aCRDisplayProperties ActiveInstance _ aCRDisplayProperties! ! !CRDisplayProperties class methodsFor: 'class initialization' stamp: 'NS 8/14/2000 11:50'! initialize " CRDisplayProperties initialize " ActiveInstance ifNil: [ActiveInstance _ self new name: #Default; yourself]. self initializeInstanceBrowser ! ! !CRDisplayProperties class methodsFor: 'instance browser' stamp: 'NS 9/6/2000 17:08'! initializeInstanceBrowser "Create a new instance browser holding all the named display properties instances. An instance browser is stored in the variable InstanceBrowser and can be accessed using: CRDisplayProperties instanceBrowser " "Check wheter class CRDisplayPropertiesInstanceBrowser is available. If not: exit" Smalltalk at: #CRDisplayPropertiesInstanceBrowser ifAbsent: [^ self]. InstanceBrowser ifNotNil: [InstanceBrowser removeAll]. InstanceBrowser _ CRDisplayPropertiesInstanceBrowser new. ! ! !CRDisplayProperties class methodsFor: 'instance browser' stamp: 'NS 6/27/2000 17:49'! instanceBrowser ^ InstanceBrowser ! ! !CRDisplayProperties class methodsFor: 'instance browser' stamp: 'NS 7/20/2000 15:22'! instanceBrowserMorph ^ InstanceBrowser asMorph ! ! !CRDisplayProperties class methodsFor: 'instance browser' stamp: 'NS 7/20/2000 15:22'! openInstanceBrowserMorph self instanceBrowserMorph openInWorld! ! !CRDisplayProperties class methodsFor: 'constants' stamp: 'NS 7/20/2000 15:39'! fileNameSuffix ^ 'gdp'! ! !CRFeature methodsFor: 'testing' stamp: 'NS 6/20/2000 09:29'! hasSomeCapturedPoints ^ false! ! !CRFeature methodsFor: 'testing' stamp: 'NS 7/10/2000 19:33'! isCacheFull ^ false! ! !CRFeature methodsFor: 'testing' stamp: 'NS 7/18/2000 11:11'! isDot ^ false! ! !CRFeature methodsFor: 'testing' stamp: 'NS 7/18/2000 11:11'! isEmpty ^ false! ! !CRFeature methodsFor: 'testing' stamp: 'NS 7/18/2000 11:10'! isStroke ^ false! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 2/16/2001 11:19'! asMorphSize: sizeNumber border: aNumberOrPoint relative: relativeBoolean properties: aCRDisplayProperties showPoints: pointsBoolean orientation: aSymbol "Return a morph representing this feature" | morph subMorph | morph _ AlignmentMorph newColumn color: Color transparent.. pointsBoolean ifTrue: [subMorph _ AlignmentMorph new color: Color transparent. morph addMorphBack: subMorph. subMorph listDirection: aSymbol. subMorph addMorphBack: (self capturedPointsMorphSize: sizeNumber border: aNumberOrPoint relative: relativeBoolean properties: aCRDisplayProperties)] ifFalse: [subMorph _ morph]. subMorph addMorphFront: (self featureMorphSize: sizeNumber border: aNumberOrPoint relative: relativeBoolean properties: aCRDisplayProperties). morph addMorphFront: self textMorph. ^ morph.! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:08'! capturedPointsFormSize: sizeNumber border: aNumberOrPoint relative: aBoolean properties: aCRDisplayProperties "Return a form containing the graphics with all the captured points" | form | form _ Form extent: (sizeNumber + (aNumberOrPoint * 2)) asPoint depth: Display depth. self drawCapturedPointsOn: form size: sizeNumber topLeft: aNumberOrPoint asPoint relative: aBoolean properties: aCRDisplayProperties. ^ form! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:09'! capturedPointsMorphSize: sizeNumber border: aNumberOrPoint relative: aBoolean properties: aCRDisplayProperties "Return a Morph representing the graphics of all the captured points" ^ (self capturedPointsFormSize: sizeNumber border: aNumberOrPoint relative: aBoolean properties: aCRDisplayProperties) asMorph! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:09'! drawCapturedPointsOn: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean properties: aCRDisplayProperties "Draw the captured points on the given form. Refinied in subclasses" ^ self! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:10'! drawFeatureOn: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean properties: aCRDisplayProperties "Draw the feature on the given form. Refinied in subclasses" ^ self! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:10'! featureFormSize: sizeNumber border: aNumberOrPoint relative: aBoolean properties: aCRDisplayProperties "Return a form with the graphical representation of the feature" | form | form _ Form extent: (sizeNumber + (aNumberOrPoint * 2)) asPoint depth: Display depth. self drawFeatureOn: form size: sizeNumber topLeft: aNumberOrPoint asPoint relative: aBoolean properties: aCRDisplayProperties. ^ form! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:10'! featureMorphSize: sizeNumber border: aNumberOrPoint relative: aBoolean properties: aCRDisplayProperties "Return a morph holding the graphical representation of the feature" ^ (self featureFormSize: sizeNumber border: aNumberOrPoint relative: aBoolean properties: aCRDisplayProperties) asMorph! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 7/10/2000 19:35'! fillCache ^ self! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:12'! hotspot "What's the hotspot of the feature? Possibilities: #start, #end, #top, #bottom, #left, #right, #topLeft, #bottomRight, #topRight, #bottomLeft" ^ #start! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:12'! hotspot: aSymbol "What's the hotspot of the feature? Possibilities: #start, #end, #top, #bottom, #left, #right, #topLeft, #bottomRight, #topRight, #bottomLeft" ^ self! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 6/16/2000 21:42'! maxStrokeDistance ^ nil.! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 6/20/2000 09:29'! reduceCapturedPoints ^ self! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:12'! relativeSize "Return the size relative to the maximum feature size in percent" ^ 100 * self size // self class maxSize! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 7/10/2000 19:33'! releaseCache ^ self! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 6/20/2000 09:30'! removeCapturedPoints ^ self! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:13'! size "Absolute size of the feaure" ^ 0.! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:13'! strokeSize "Number of stroke segments" ^ 0! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:15'! textMorph "A morph describing certain parts of the feature in a textual way" ^ StringMorph contents: self textString.! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:15'! time "The time the user used to capture the feature" ^ time! ! !CRFeature methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:15'! time: aNumber "The time the user used to capture the feature" time _ aNumber! ! !CRFeature methodsFor: 'private' stamp: 'NS 8/8/2000 15:16'! textString "A string describing certain parts of the feature" ^ 'T: ' , (self time / 1000 roundTo: 0.01) printString , 's; S: ' , self relativeSize printString , '%'. ! ! !CRFeature methodsFor: 'private comparing' stamp: 'NS 7/25/2000 13:14'! sameClassAcuteAngleDistance: aCRFeature ^ 0! ! !CRFeature methodsFor: 'private comparing' stamp: 'NS 6/22/2000 09:01'! sameClassAngleDistance: aCRFeature ^ 0! ! !CRFeature methodsFor: 'private comparing' stamp: 'NS 6/22/2000 08:59'! sameClassShapeDistance: aCRFeature ^ 0! ! !CRFeature methodsFor: 'private comparing' stamp: 'NS 6/22/2000 08:59'! sameClassStartEndDistance: aCRFeature ^ 0! ! !CRFeature methodsFor: 'private comparing' stamp: 'NS 7/21/2000 09:26'! sameClassStrokeDistance: aCRFeature ^ 0! ! !CRFeature methodsFor: 'private comparing' stamp: 'NS 7/25/2000 14:09'! sameClassStrokeSizeDistance: aCRFeature ^ 0! ! !CRFeature methodsFor: 'private comparing' stamp: 'NS 7/21/2000 09:26'! sameClassSymmetricStrokeDistance: aCRFeature ^ 0! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:16'! acuteAngleDistance: aCRFeature "This distance measures the difference between two features regading acute angles" ^ aCRFeature class = self class ifTrue: [self sameClassAcuteAngleDistance: aCRFeature] ifFalse: [self class maxNormDistance]! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:17'! angleDistance: aCRFeature "This distance measures the difference between two features regading angles" ^ aCRFeature class = self class ifTrue: [self sameClassAngleDistance: aCRFeature] ifFalse: [self class maxNormDistance]! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:17'! shapeDistance: aCRFeature "This distance measures the difference between two features regading the shape of the bounding boxes" ^ aCRFeature class = self class ifTrue: [self sameClassShapeDistance: aCRFeature] ifFalse: [self class maxNormDistance]! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:48'! sizeDistance: aCRFeature "This distance measures the difference between two features regading the sizes." | mult div | "Calculate the relative size difference and multiply it by 10" mult _ aCRFeature size max: self size. div _ aCRFeature size min: self size. mult _ mult - div. div _ div + (self class maxSize // 40) max: 1. mult _ 10 * mult // div. "If the ratio is smaller than 8, use a quadratic function to map into the normalized distance space. (8 is mapped to 1/2 of the maximum distance). Otherwise use a linear function. The total function is continuous and 30 (or more) is mapped to the maximum distance" ^ mult < 8 ifTrue: [mult * mult * self class maxNormDistance // 128 "(2 * 64)" ] ifFalse: [self class maxNormDistance // 2 + ((30 min: mult) - 8 * self class maxNormDistance // 44 "(2 * 22)" )] ! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:43'! startEndDistance: aCRFeature "This distance measures the difference between two features regading the relative start- and endpoint." ^ aCRFeature class = self class ifTrue: [self sameClassStartEndDistance: aCRFeature] ifFalse: [self class maxNormDistance]! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:44'! strokeDistance: aCRFeature "This distance measures the difference between two features regading the whole stroke." ^ aCRFeature class = self class ifTrue: [self sameClassStrokeDistance: aCRFeature] ifFalse: [self class maxNormDistance]! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:45'! strokeSizeDistance: aCRFeature "This distance measures the difference between two features regading the number of vectors the stroke is decomposed into." ^ aCRFeature class = self class ifTrue: [self sameClassStrokeSizeDistance: aCRFeature] ifFalse: [self class maxNormDistance]! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:45'! symmetricStrokeDistance: aCRFeature "Similar to strokeDistance, but in this case, the distance is symmetric" ^ aCRFeature class = self class ifTrue: [self sameClassSymmetricStrokeDistance: aCRFeature] ifFalse: [self class maxNormDistance]! ! !CRFeature methodsFor: 'comparing' stamp: 'NS 8/8/2000 15:50'! timeDistance: aCRFeature "This distance measures the difference between two features regading the time, to user spend while drawing the gesture." | mult div | "Calculate the relative time difference and multiply it by 10" mult _ aCRFeature time max: self time. div _ aCRFeature time min: self time. mult _ mult - div. div _ div + 10. mult _ 10 * mult // div. "If the ratio is smaller than 10, use a quadratic function to map into the normalized distance space. (10 is mapped to 1/2 of the maximum distance). Otherwise use a linear function. The total function is continuous and 30 (or more) is mapped to the maximum distance" ^ mult < 10 ifTrue: [mult * mult * self class maxNormDistance // 200 "(2 * 100)"] ifFalse: [self class maxNormDistance // 2 + ((30 min: mult) - 10 * self class maxNormDistance // 40 "(2 * 20)")] ! ! !CRDotFeature methodsFor: 'accessing' stamp: 'NS 6/18/2000 18:55'! drawCapturedPointsOn: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean properties: aCRDisplayProperties ^ self drawFeatureOn: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean properties: aCRDisplayProperties! ! !CRDotFeature methodsFor: 'accessing' stamp: 'NS 6/18/2000 18:55'! drawFeatureOn: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean properties: aCRDisplayProperties | pen | pen _ Pen newOnForm: aForm. pen place: tlPoint + (sizeNumber // 2). pen color: Color black. pen roundNib: 2. pen go: 1.! ! !CRDotFeature methodsFor: 'testing' stamp: 'NS 7/18/2000 11:11'! isDot ^ true! ! !CREmptyFeature methodsFor: 'comparing' stamp: 'NS 6/15/2000 14:59'! timeDistance: aCRFeature max: maxNumber ^ 0! ! !CREmptyFeature methodsFor: 'private' stamp: 'NS 6/16/2000 15:31'! textString ^ ''! ! !CREmptyFeature methodsFor: 'testing' stamp: 'NS 7/18/2000 11:11'! isEmpty ^ true! ! !CRFeature class methodsFor: 'constants' stamp: 'NS 6/16/2000 18:24'! maxNormDistance ^ 1000! ! !CRFeature class methodsFor: 'constants' stamp: 'NS 6/13/2000 13:26'! maxSize ^ 1000! ! !CRLineMorph methodsFor: 'private' stamp: 'NS 2/19/2001 20:34'! getPoint: aNumber aNumber = 1 ifTrue: [^ bounds bottomLeft + (1@-1)]. aNumber = 2 ifTrue: [^ bounds bottomRight + (-1@-1)]. aNumber = 3 ifTrue: [^ bounds topRight + (-1@1)]. aNumber = 4 ifTrue: [^ bounds topLeft + (1@1)]. ! ! !CRLineMorph methodsFor: 'drawing' stamp: 'NS 2/19/2001 20:35'! drawOn: aCanvas aCanvas line: self start to: self end width: 1 color: color. ! ! !CRLineMorph methodsFor: 'initialize-release' stamp: 'NS 2/19/2001 20:35'! initializeFrom: startPoint to: endPoint self color: Color black. self privateSetStart: startPoint end: endPoint! ! !CRLineMorph methodsFor: 'accessing' stamp: 'NS 2/19/2001 20:33'! end ^ self getPoint: (quadrant + 1 \\ 4 + 1).! ! !CRLineMorph methodsFor: 'accessing' stamp: 'NS 2/19/2001 20:36'! morphicLayerNumber ^ 2! ! !CRLineMorph methodsFor: 'accessing' stamp: 'NS 2/19/2001 20:34'! privateSetStart: startPoint end: endPoint | xDiff yDiff | xDiff _ endPoint x - startPoint x. yDiff _ endPoint y - startPoint y. quadrant _ (yDiff <= 0 ifTrue: [xDiff >= 0 ifTrue: [1] ifFalse: [2]] ifFalse: [xDiff >= 0 ifTrue: [4] ifFalse: [3]]). bounds _ ((endPoint x min: startPoint x) - 1) @ ((endPoint y min: startPoint y) - 1) corner: ((endPoint x max: startPoint x) + 1) @ ((endPoint y max: startPoint y) + 1).! ! !CRLineMorph methodsFor: 'accessing' stamp: 'NS 2/19/2001 20:33'! setStart: startPoint end: endPoint self changed. self privateSetStart: startPoint end: endPoint. self layoutChanged. self changed. ! ! !CRLineMorph methodsFor: 'accessing' stamp: 'NS 2/19/2001 20:34'! start ^ self getPoint: quadrant! ! !CRLineMorph class methodsFor: 'instance creation' stamp: 'NS 2/19/2001 20:35'! from: startPoint to: endPoint ^ super new initializeFrom: startPoint to: endPoint! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 8/8/2000 15:57'! charType ^ self char type! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 7/19/2000 14:13'! correspondingKeystrokes ^ self char correspondingKeystrokes! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 2/19/2001 17:28'! correspondingMouseEventsHand: aHandMorph position: aPoint buttons: anInteger ^ self char correspondingMouseEventsHand: aHandMorph position: aPoint buttons: anInteger! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 7/19/2000 14:13'! correspondsToKeystrokes ^ self char correspondsToKeystrokes! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 2/19/2001 17:25'! correspondsToMouseEvents ^ self char correspondsToMouseEvents.! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 7/19/2000 15:58'! evaluateCodeIn: aMGEvent ^ self char evaluateCodeIn: aMGEvent! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 7/19/2000 14:14'! isCode ^ self char isCode! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 7/19/2000 14:14'! isCommand ^ self char isCommand! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 7/19/2000 14:33'! isStrokes ^ self char isStrokes! ! !CRLookupItem methodsFor: 'char accessing' stamp: 'NS 8/14/2000 12:20'! normalizedChar ^ self char normalized! ! !CRLookupItem methodsFor: 'accessing' stamp: 'NS 8/8/2000 15:59'! char ^ char! ! !CRLookupItem methodsFor: 'accessing' stamp: 'NS 6/28/2000 15:12'! distance ^ distance! ! !CRLookupItem methodsFor: 'accessing' stamp: 'NS 6/28/2000 15:12'! feature ^ feature! ! !CRLookupItem methodsFor: 'initialize-release' stamp: 'NS 6/28/2000 15:12'! initializeFeature: aCRFeature char: anObject distance: aNumber feature _ aCRFeature. char _ anObject. distance _ aNumber! ! !CRLookupItem methodsFor: 'comparing' stamp: 'NS 6/28/2000 15:30'! < aCRLookupItem (aCRLookupItem isKindOf: self class) ifFalse: [^ true]. ^ self distance < aCRLookupItem distance! ! !CRLookupItem methodsFor: 'comparing' stamp: 'NS 6/28/2000 16:07'! <= aCRLookupItem ^ self < aCRLookupItem or: [self = aCRLookupItem].! ! !CRLookupItem methodsFor: 'comparing' stamp: 'NS 6/28/2000 16:06'! = aCRLookupItem (aCRLookupItem isKindOf: self class) ifFalse: [^ false]. aCRLookupItem == self ifTrue: [^ true]. self distance = aCRLookupItem distance ifFalse: [^ false]. ^ true.! ! !CRLookupItem methodsFor: 'printing' stamp: 'NS 7/27/2000 15:49'! printOn: aStream ^ aStream nextPutAll: '{ '; nextPutAll: self char printString; nextPutAll: ', '; nextPutAll: self distance printString; nextPutAll: ' }'.! ! !CRLookupItem class methodsFor: 'instance creation' stamp: 'NS 6/28/2000 15:11'! feature: aCRFeature char: anObject distance: aNumber ^ super new initializeFeature: aCRFeature char: anObject distance: aNumber ! ! !CRLookupResult methodsFor: 'private' stamp: 'NS 8/8/2000 16:07'! isAvailable: indexNumber "Does the instance contain at least indexNumber features?" ^ indexNumber notNil and: [self size >= indexNumber]! ! !CRLookupResult methodsFor: 'private' stamp: 'NS 8/8/2000 16:08'! isDistinctCharAvailable: indexNumber "Does the instance contain at least indexNumber features that are assigned to a distinct character? (Features assigned to the same character count only once)" ^ self distinctCharSize >= indexNumber! ! !CRLookupResult methodsFor: 'private' stamp: 'NS 8/8/2000 16:14'! isWorseThanDistance: distanceNumber difference: diffNumber relativeDifference: relDiffNumber "Determines wheter the lookup suffice to the given quality requirements: 1) The distance to the best match must be no more than distanceNumber (distanceNumber is given in percent of the maximum distance). 2) The distance between the best and the second best match must be bigger than diffNumber (distanceNumber is given in percent of the maximum distance) OR: The relative distance from the best to the second best match must be bigger than relDiffNumber (relDiffNumber is given in percent)." | val | (val _ self distanceAt: 1) isNil ifTrue: [^ false]. val > (10 * distanceNumber) ifTrue: [^ true]. (val _ self distanceDifferenceToDistinctChar) isNil ifTrue: [^ false]. (val < (10 * diffNumber) and: [self relativeDistanceDifferenceToDistinctChar < relDiffNumber]) ifTrue: [^ true]. ^ false. ! ! !CRLookupResult methodsFor: 'testing' stamp: 'NS 8/8/2000 16:16'! isAlert: aCRParameters "Is match so bad (ambiguous) that user should be warned?" aCRParameters isAlertEnabled ifFalse: [^ false]. ^ self isWorseThanDistance: aCRParameters alertDistance difference: aCRParameters alertDistanceDifference relativeDifference: aCRParameters alertRelativeDistanceDifference.! ! !CRLookupResult methodsFor: 'testing' stamp: 'NS 8/8/2000 16:16'! isReject: aCRParameters "Is match too bad (ambiguous) to be accepted?" aCRParameters isRejectEnabled ifFalse: [^ false]. ^ self isWorseThanDistance: aCRParameters rejectDistance difference: aCRParameters rejectDistanceDifference relativeDifference: aCRParameters rejectRelativeDistanceDifference.! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:16'! add: aCRLookupItem "Add a new lookup item to the result" ^ (self size < self minSize or: [(self distanceAt: self size) > aCRLookupItem distance]) and: [super add: aCRLookupItem. true]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:17'! addFeature: aCRFeature char: anObject distance: aNumber "Construct new lookup item and add to result" ^ self add: (CRLookupItem feature: aCRFeature char: anObject distance: aNumber)! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:17'! addResult: aCRLookupResult "Add all the items of the other result to this result" aCRLookupResult do: [:each | self add: each].! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 6/29/2000 17:48'! at: anInteger ^ (self isAvailable: anInteger) ifTrue: [super at: anInteger].! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:18'! char "return character at iterator position" ^ self charAt: self lookupIndex.! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 6/28/2000 15:34'! charAt: indexNumber ^ (self isAvailable: indexNumber) ifTrue: [(self at: indexNumber) char]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:19'! charType "Return type of character at iterator position" ^ self charTypeAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/12/2000 15:12'! charTypeAt: anInteger ^ (self isAvailable: anInteger) ifTrue: [(self at: anInteger) charType]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:19'! copyWithoutParentFeatures "Copy the result and eliminate all the features that are not in the base dictionary" ^ self copy eliminateParentFeatures! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:20'! correspondingKeystrokes "Return the corredponsing keystrokes of the charater at the iterator position" ^ self correspondingKeystrokesAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/13/2000 09:24'! correspondingKeystrokesAt: anInteger ^ (self isAvailable: anInteger) ifTrue: [(self at: anInteger) correspondingKeystrokes]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 2/19/2001 17:29'! correspondingMouseEventsHand: aHandMorph position: aPoint buttons: anInteger ^ self correspondingMouseEventsHand: aHandMorph position: aPoint buttons: anInteger at: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 2/19/2001 17:28'! correspondingMouseEventsHand: aHandMorph position: aPoint buttons: buttonInteger at: atInteger ^ (self isAvailable: atInteger) ifTrue: [(self at: atInteger) correspondingMouseEventsHand: aHandMorph position: aPoint buttons: buttonInteger]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:20'! correspondsToKeystrokes "Does the character at the iterator positon correspond to a keystroke?" ^ self correspondsToKeystrokesAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/13/2000 09:21'! correspondsToKeystrokesAt: anInteger ^ (self isAvailable: anInteger) ifTrue: [(self at: anInteger) correspondsToKeystrokes]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 2/19/2001 17:27'! correspondsToMouseEvents "Does the character at the iterator position corrpesond to a mouse event?" ^ self correspondsToMouseEventsAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 2/19/2001 17:25'! correspondsToMouseEventsAt: anInteger ^ (self isAvailable: anInteger) ifTrue: [(self at: anInteger) correspondsToMouseEvents]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/9/2000 22:17'! dictionary ^ dictionary! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/9/2000 22:18'! dictionary: aCRDictionary dictionary _ aCRDictionary! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:21'! distance "Return the distance at the iterator position" ^ self distanceAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 6/28/2000 15:35'! distanceAt: indexNumber ^ (self isAvailable: indexNumber) ifTrue: [(self at: indexNumber) distance]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:21'! distanceDifference "Distance difference between best and second best match" ^ self distanceDifferenceFrom: 1 to: 2! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:22'! distanceDifferenceFrom: smallInteger to: highInteger "Distance difference between two matches identified by their position." ^ (self isAvailable: highInteger) ifTrue: [(self distanceAt: highInteger) - (self distanceAt: smallInteger)].! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:23'! distanceDifferenceFrom: startInteger toDistinctChar: distinctInteger "Distance between two matches with different assigned characters!!" | highIndex | highIndex _ self indexOfDistinctChar: distinctInteger startAt: startInteger. highIndex ifNil: [^ nil]. ^ self distanceDifferenceFrom: startInteger to: highIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:25'! distanceDifferenceToDistinctChar "Distance from the best match to the next best feature that is associated to a distinct character" ^ self distanceDifferenceFrom: 1 toDistinctChar: 1! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:26'! distinctCharItem: distinctInteger "Return the item of the n-th dictinct character" | index | index _ self indexOfDistinctChar: distinctInteger startAt: 1. ^ index ifNotNil: [self at: index].! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:26'! distinctCharSize "Number of matches with dictinct characters" | chars count | chars _ Set new. count _ 0. self do: [:each | (chars includes: each char) ifFalse: [count _ count + 1. chars add: each char]]. ^ count.! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:26'! eliminateParentFeatures "Eliminate all the features that are not included in the dictionary" self removeAllSuchThat: [:each | (self dictionary includesFeature: each feature) not]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:39'! evaluateCodeIn: aMGEvent "Evaluate the code of the character at the current iterator position" ^ self evaluateCodeIn: aMGEvent at: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/19/2000 16:00'! evaluateCodeIn: aMGEvent at: anInteger ^ (self isAvailable: anInteger) ifTrue: [(self at: anInteger) evaluateCodeIn: aMGEvent]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:54'! feature "Feature at the iterator position" ^ self featureAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 6/28/2000 15:35'! featureAt: indexNumber ^ (self isAvailable: indexNumber) ifTrue: [(self at: indexNumber) feature]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:54'! indexOfDistinctChar: anInteger "Index of the n-th distinct char" ^ self indexOfDistinctChar: anInteger startAt: self lookupIndex.! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:55'! indexOfDistinctChar: distinctInteger startAt: startInteger "Index of the n-th dictinct char from startInteger" | count chars | self isEmpty ifTrue: [^ nil]. count _ 0. chars _ Set new. self withIndexDo: [:each :index | (chars includes: each char) not ifTrue: [index > startInteger ifTrue: [count _ count + 1]. chars add: each char]. count >= distinctInteger ifTrue: [^ index]]. ^ nil! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:56'! isCode "Is character at interator position code?" ^ self isCodeAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/12/2000 15:19'! isCodeAt: indexNumber ^ (self isAvailable: indexNumber) and: [(self at: indexNumber) isCode]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:56'! isCommand "Is character at interator position command?" ^ self isCommandAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/12/2000 15:18'! isCommandAt: indexNumber ^ (self isAvailable: indexNumber) and: [(self at: indexNumber) isCommand]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:56'! isStrokes "Is character at interator position strokes?" ^ self isStrokesAt: self lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/19/2000 13:55'! isStrokesAt: indexNumber ^ (self isAvailable: indexNumber) and: [(self at: indexNumber) isStrokes]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:57'! lookupIndex "Iterator position" ^ lookupIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:57'! lookupIndex: anIntegerOrNil "iterator position" ^ (anIntegerOrNil notNil and: [anIntegerOrNil > 0 and: [anIntegerOrNil <= self size]]) and: [lookupIndex _ anIntegerOrNil. true].! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:57'! minSize "Minimum size (number of matches) of the result" ^ minSize! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 16:57'! minSize: anInteger "Minimum size (number of matches) of the result" minSize _ anInteger! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:09'! nextDistinctCharMatch "Move the iterator to the next dictinct character" ^ self nextDistinctCharMatchRollover: true! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:10'! nextDistinctCharMatchRollover: aBoolean "Move the iterator to the next dictinct character" | index | index _ self indexOfDistinctChar: 1 startAt: self lookupIndex. ^ (self lookupIndex: index) or: [aBoolean ifTrue: [self lookupIndex: 1] ifFalse: [self lookupIndex: self lookupResult size]. false]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:10'! nextMatch "Move the iterator to the next match" ^ self nextMatchRollover: true! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:10'! nextMatchRollover: aBoolean "Move the iterator to the next match" ^ (self lookupIndex: self lookupIndex + 1) or: [aBoolean ifTrue: [self lookupIndex: 1] ifFalse: [self lookupIndex: self lookupResult size]. false]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/14/2000 12:21'! normalizedCharAt: anInteger ^ (self isAvailable: anInteger) ifTrue: [(self at: anInteger) normalizedChar]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 6/17/2000 09:44'! relativeDistanceDifference ^ self relativeDistanceDifferenceFrom: 1 to: 2.! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:23'! relativeDistanceDifferenceFrom: smallInteger to: highInteger "Relative distance between two matches" ^ (self isAvailable: highInteger) ifTrue: [(self distanceAt: smallInteger) = 0 ifTrue: [0] ifFalse: [100 * (self distanceDifferenceFrom: smallInteger to: highInteger) // (self distanceAt: smallInteger)]]! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/13/2000 10:37'! relativeDistanceDifferenceFrom: startInteger toDistinctChar: dictinctInteger | highIndex | highIndex _ self indexOfDistinctChar: dictinctInteger startAt: dictinctInteger. highIndex ifNil: [^ nil]. ^ self relativeDistanceDifferenceFrom: startInteger to: highIndex! ! !CRLookupResult methodsFor: 'accessing' stamp: 'NS 7/13/2000 10:56'! relativeDistanceDifferenceToDistinctChar ^ self relativeDistanceDifferenceFrom: 1 toDistinctChar: 1! ! !CRLookupResult methodsFor: 'initialize-release' stamp: 'NS 7/13/2000 11:11'! initializeDictionary: aCRDictionary minSize: anInteger self minSize: anInteger. self dictionary: aCRDictionary. lookupIndex _ 1.! ! !CRLookupResult methodsFor: 'copying' stamp: 'NS 2/13/2001 15:39'! copy | c | c _ super copy. c dictionary: self dictionary. c lookupIndex: self lookupIndex copy. ^ c! ! !CRLookupResult class methodsFor: 'instance creation' stamp: 'NS 7/9/2000 22:19'! dictionary: aCRDictionary ^ self dictionary: aCRDictionary minSize: 2.! ! !CRLookupResult class methodsFor: 'instance creation' stamp: 'NS 7/9/2000 22:18'! dictionary: aCRDictionary minSize: anInteger ^ super new initializeDictionary: aCRDictionary minSize: anInteger! ! !CRParameters methodsFor: 'initialize-release' stamp: 'NS 7/26/2000 09:17'! initialize self isAlertEnabled: self class isAlertEnabled. self alertDistance: self class alertDistance. self alertDistanceDifference: self class alertDistanceDifference. self alertRelativeDistanceDifference: self class alertRelativeDistanceDifference. self isRejectEnabled: self class isRejectEnabled. self rejectDistance: self class rejectDistance. self rejectDistanceDifference: self class rejectDistanceDifference. self rejectRelativeDistanceDifference: self class rejectRelativeDistanceDifference. self angleSector: self class angleSector. self minAngle: self class minAngle. self minDirectionLengthPercentage: self class minDirectionLengthPercentage. self acuteAngleRelevance: self class acuteAngleRelevance. self shapeRelevance: self class shapeRelevance. self sizeRelevance: self class sizeRelevance. self startEndRelevance: self class startEndRelevance. self strokeRelevance: self class strokeRelevance. self timeRelevance: self class timeRelevance. self speedPercentage: self class speedPercentage. self angleRelevance: self class angleRelevance. self escapeTime: self class escapeTime.! ! !CRParameters methodsFor: 'private' stamp: 'NS 8/8/2000 17:26'! primaryRelevanceSum "Return the sum of the relevances of all the primary features. The primary features ar all the size-independent geometric featues" ^ self shapeRelevance + self startEndRelevance + self strokeRelevance + self acuteAngleRelevance + angleRelevance! ! !CRParameters methodsFor: 'private' stamp: 'NS 8/8/2000 17:26'! secondaryRelevanceSum "return the sum of the secondary features" ^ self timeRelevance + self sizeRelevance! ! !CRParameters methodsFor: 'private' stamp: 'NS 6/21/2000 09:45'! totalWeightedRelevanceSum ^ self primaryRelevanceSum * (100 + self secondaryRelevanceSum) // 100! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:18'! acuteAngleRelevance ^ acuteAngleRelevance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:15'! acuteAngleRelevance: aNumber acuteAngleRelevance _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:06'! alertDistance ^ alertDistance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:06'! alertDistance: aNumber alertDistance _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:05'! alertDistanceDifference ^ alertDistanceDifference! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:06'! alertDistanceDifference: aNumber alertDistanceDifference _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:06'! alertRelativeDistanceDifference ^ alertRelativeDistanceDifference! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:06'! alertRelativeDistanceDifference: aNumber alertRelativeDistanceDifference _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/20/2000 12:03'! angleRelevance ^ angleRelevance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/20/2000 12:03'! angleRelevance: aNumber angleRelevance _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/5/2000 19:00'! angleSector ^ angleSector! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/5/2000 19:00'! angleSector: degreeNumber angleSector _ degreeNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/6/2000 08:53'! escapeTime ^ escapeTime! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/6/2000 08:53'! escapeTime: aNumber escapeTime _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/24/2000 15:57'! isAlertEnabled ^ isAlertEnabled! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/24/2000 15:57'! isAlertEnabled: aBoolean isAlertEnabled _ aBoolean! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:11'! isRejectEnabled ^ isRejectEnabled! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:11'! isRejectEnabled: aBoolean isRejectEnabled _ aBoolean! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/5/2000 19:00'! minAngle ^ minAngle! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/5/2000 19:01'! minAngle: degreeNumber minAngle _ degreeNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/13/2000 11:59'! minDirectionLengthPercentage ^ minDirectionLengthPercentage! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/13/2000 12:02'! minDirectionLengthPercentage: aNumber minDirectionLengthPercentage _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:10'! rejectDistance ^ rejectDistance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:10'! rejectDistance: aNumber rejectDistance _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:10'! rejectDistanceDifference ^ rejectDistanceDifference! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:10'! rejectDistanceDifference: aNumber rejectDistanceDifference _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:11'! rejectRelativeDistanceDifference ^ rejectRelativeDistanceDifference! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 7/26/2000 09:11'! rejectRelativeDistanceDifference: aNumber rejectRelativeDistanceDifference _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:31'! relevancePromilleForPrimary: aNumber "Transforms the given absolute relevance number of a primary feature into a relative one" ^ 1000 * aNumber // self totalWeightedRelevanceSum! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:31'! relevancePromilleForSecondary: aNumber "Transforms the given absolute relevance number of a secondary feature into a relative one" ^ 10 * aNumber * self primaryRelevanceSum // self totalWeightedRelevanceSum! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:27'! set: aCRParameters "Set the variables of this instance to the ones of the other instance" self setBasic: aCRParameters. self setAdvanced: aCRParameters. self setCapturing: aCRParameters! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:28'! setAdvanced: aCRParameters "Set the advanced values of this instances to the ones of the other instance" self strokeRelevance: aCRParameters strokeRelevance; shapeRelevance: aCRParameters shapeRelevance; startEndRelevance: aCRParameters startEndRelevance; angleRelevance: aCRParameters angleRelevance; acuteAngleRelevance: aCRParameters acuteAngleRelevance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:28'! setBasic: aCRParameters "Set the basic values of this instances to the ones of the other instance" self speedPercentage: aCRParameters speedPercentage; sizeRelevance: aCRParameters sizeRelevance; timeRelevance: aCRParameters timeRelevance; isAlertEnabled: aCRParameters isAlertEnabled; alertDistance: aCRParameters alertDistance; alertDistanceDifference: aCRParameters alertDistanceDifference; alertRelativeDistanceDifference: aCRParameters alertRelativeDistanceDifference; isRejectEnabled: aCRParameters isRejectEnabled; rejectDistance: aCRParameters rejectDistance; rejectDistanceDifference: aCRParameters rejectDistanceDifference; rejectRelativeDistanceDifference: aCRParameters rejectRelativeDistanceDifference; escapeTime: aCRParameters escapeTime.! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 8/8/2000 17:28'! setCapturing: aCRParameters "Set the captured values of this instances to the ones of the other instance" self angleSector: aCRParameters angleSector; minAngle: aCRParameters minAngle; minDirectionLengthPercentage: aCRParameters minDirectionLengthPercentage. ! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:26'! shapeRelevance ^ shapeRelevance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:27'! shapeRelevance: aNumber shapeRelevance _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:25'! sizeRelevance ^ sizeRelevance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:25'! sizeRelevance: aNumber sizeRelevance _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/21/2000 11:04'! speedPercentage ^ speedPercentage! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/21/2000 11:04'! speedPercentage: aNumber speedPercentage _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:24'! startEndRelevance ^ startEndRelevance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:24'! startEndRelevance: aNumber startEndRelevance_ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:24'! strokeRelevance ^ strokeRelevance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:24'! strokeRelevance: aNumber strokeRelevance _ aNumber! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:23'! timeRelevance ^ timeRelevance! ! !CRParameters methodsFor: 'accessing' stamp: 'NS 6/19/2000 18:23'! timeRelevance: aNumber timeRelevance _ aNumber! ! !CRParameters methodsFor: 'copying' stamp: 'NS 6/27/2000 20:07'! copy ^ CRParameters new set: self.! ! !CRParameters methodsFor: 'testing' stamp: 'NS 8/8/2000 17:29'! hasSameCapturing: aCRParameters "Are the variables controlling the capturing the same in this and the other instance?" (aCRParameters isKindOf: self class) ifFalse: [^ false]. aCRParameters == self ifTrue: [^ true]. self angleSector = aCRParameters angleSector ifFalse: [^ false]. self minAngle = aCRParameters minAngle ifFalse: [^ false]. self minDirectionLengthPercentage = aCRParameters minDirectionLengthPercentage ifFalse: [^ false]. ^ true! ! !CRParameters methodsFor: 'comparing' stamp: 'NS 6/27/2000 11:24'! = aCRParameters | index | (aCRParameters isKindOf: self class) ifFalse: [^ false]. aCRParameters == self ifTrue: [^ true]. index _ self class instSize. [index > 0] whileTrue: [(aCRParameters instVarAt: index) = (self instVarAt: index) ifFalse: [^ false]. index _ index - 1]. ^ true! ! !CRParameters methodsFor: 'comparing' stamp: 'NS 6/27/2000 11:34'! hash | index hash | index _ self class instSize. hash _ index > 0 ifTrue: [(self instVarAt: index) hash] ifFalse: [nil hash]. index _ index - 1. [index > 0] whileTrue: [hash _ hash bitXor: (self instVarAt: index) hash. index _ index - 1]. ^ hash! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 8/28/2000 10:42'! acuteAngleRelevance ^ 15! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:52'! alertDistance ^ 40! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:52'! alertDistanceDifference ^ 3! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:52'! alertRelativeDistanceDifference ^ 15! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:52'! angleRelevance ^ 15! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/5/2000 18:59'! angleSector ^ 45! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/6/2000 08:52'! escapeTime ^ 200! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 6/24/2000 15:58'! isAlertEnabled ^ false! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/26/2000 09:09'! isRejectEnabled ^ false! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/5/2000 18:59'! minAngle ^ 22! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 6/16/2000 16:43'! minDirectionLengthPercentage ^ 5! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:54'! rejectDistance ^ 40! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:54'! rejectDistanceDifference ^ 3! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:54'! rejectRelativeDistanceDifference ^ 15! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:55'! shapeRelevance ^ 10! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:55'! sizeRelevance ^ 20! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:55'! speedPercentage ^ 60! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 7/31/2000 15:55'! startEndRelevance ^ 30! ! !CRParameters class methodsFor: 'default parameter' stamp: 'NS 6/19/2000 17:30'! strokeRelevance ^ 100! ! !CRParameters class methodsFor: 'default parameter' stamp: 'ls 6/23/2000 15:41'! timeRelevance ^ 0! ! !CRParameters class methodsFor: 'instance creation' stamp: 'NS 6/12/2000 17:33'! new ^ super new initialize! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/8/2000 17:34'! absDirectionDifference: degreeNumber1 and: degreeNumber2 "Absolute difference between this two angles." | diff | diff _ (degreeNumber1 - degreeNumber2) abs. ^ diff > 180 ifTrue: [360 - diff] ifFalse: [diff]. ! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 7/6/2000 10:07'! absMaxCoord: aPoint ^ aPoint x abs max: aPoint y abs! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 7/6/2000 10:01'! absMaxCoordDistance: aPoint and: bPoint ^ (aPoint x - bPoint x) abs max: (aPoint y - bPoint y) abs! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/8/2000 17:57'! addDirectionTowardsPoint: newPoint minSquaredLength: lenNumber minAngle: angleNumber last: aBoolean "Add the direction (vector) towards newPoint to the sequence of direction vectors and also add newPoint to the sequence of points. The method ensures that all the direction vectors have at least a length of lenNumber that the angle difference to the neighbour vectors is at least angleNumber. NOTE: This method adds new vectors only if they point into another global segment. (E.g., the segment size can be 45 degrees). This GLOBAL view is much better than a relative view (only angle differences are considered) because it is much less sensitive to different start angles or 'noise vectors' in the middle of a stroke!!" | lastDir newDir preLastDir lastP tooShort | self directionPoints isEmpty ifTrue: [self directionPoints add: newPoint. ^ true]. lastP _ self directionPoints last. newDir _ self directionFrom: lastP to: newPoint. tooShort _ (self squaredDistanceFrom: lastP to: newPoint) < lenNumber. self directions isEmpty ifTrue: [tooShort ifTrue: [^ false] ifFalse: [self directions add: newDir. self directionPoints add: newPoint. ^ true]]. "If it's not the last vector and the vector points into the same global segment then the last one, replace the endpoint of the last vector by the new point using a recursive call" lastDir _ self directions last. (aBoolean not and: [(self normalizedDirection: lastDir) = (self normalizedDirection: newDir)]) ifTrue: [self directions removeLast. self directionPoints removeLast. self directions add: (self directionFrom: self directionPoints last to: newPoint). self directionPoints add: newPoint. ^ true]. "If the new vector points into another global segment than the old one, and the vector is long enough, check wheter the new angles have the minimum size. If not: Remove the unnecessary points and make a recusrive call" ((self normalizedDirection: lastDir) ~= (self normalizedDirection: newDir) and: [self directions size > 1 and: [tooShort not]]) ifTrue: [preLastDir _ self directions at: self directions size - 1. (self absDirectionDifference: lastDir and: preLastDir) <= angleNumber ifTrue: [self directions removeLast; removeLast. self directionPoints removeLast; removeLast. self addDirectionTowardsPoint: lastP minSquaredLength: lenNumber minAngle: angleNumber last: false. ^ self addDirectionTowardsPoint: newPoint minSquaredLength: lenNumber minAngle: angleNumber last: aBoolean]]. "If it's the last point: Add the new point in any case. If it would generate a too small angle, remove previous points and make recursive call." (aBoolean and: [tooShort or: [(self absDirectionDifference: newDir and: lastDir) <= angleNumber]]) ifTrue: [self directions removeLast. self directionPoints removeLast. ^ self addDirectionTowardsPoint: newPoint minSquaredLength: lenNumber minAngle: angleNumber last: aBoolean]. "This is the general case: Add the new point and the vector to this point" ^ tooShort not and: [self directions add: newDir. self directionPoints add: newPoint. true].! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 2/19/2001 20:39'! addPointWithoutTest: aPoint "This method adds aPoint to the collection of all the captured points (in global display coordinates). It doesn't test wheter the distance to the last one is big enough" self isEchoEnabled ifTrue: [echo isNil ifTrue: [self echo: OrderedCollection new. echo add: (CRLineMorph from: aPoint to: aPoint + 1) openInWorld] ifFalse: [echo add: (CRLineMorph from: self points last to: aPoint) openInWorld]]. self lastPoint: aPoint. self coordinates top isNil ifTrue: [self coordinates top: aPoint. self coordinates bottom: aPoint. self coordinates left: aPoint. self coordinates right: aPoint] ifFalse: [self coordinates top y > aPoint y ifTrue: [self coordinates top: aPoint]. self coordinates bottom y < aPoint y ifTrue: [self coordinates bottom: aPoint]. self coordinates left x > aPoint x ifTrue: [self coordinates left: aPoint]. self coordinates right x < aPoint x ifTrue: [self coordinates right: aPoint]]. self points add: aPoint ! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:53'! bottomRight "Bottom right of stroke's bounding box" ^ self coordinates bottomRight! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:53'! calcNormExtent "Calculates the normalized extent of the stroke" | targetMaxSize | targetMaxSize _ CRFeature maxSize. ^ targetMaxSize * self extent * self displayProperties aspectRatio // self displayProperties maxSize min: targetMaxSize @ targetMaxSize max: 1 @ 1! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:54'! calcPointNormDivisor "Calculates the number display points have to be diveded by to get normalized points" | div | div _ self displayProperties aspectRatio * self extent. ^ (div x max: div y) ceiling.! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:54'! calcPointNormFactor "Calcualtes the number display points have to be multiplied with to get normalized points" ^ CRFeature maxSize * self displayProperties aspectRatio ! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:55'! calcPointNormShiftVector "Calculates the vector that has to be added to translate display points into normalized points" | vector | vector _ self extent * self calcPointNormFactor // self calcPointNormDivisor. ^ CRFeature maxSize - vector // 2. ! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:55'! clearPoints "Clear all the points collections" self points: OrderedCollection new. self coordinates: CRRecognizerCoordinates new. self lastPoint: nil.! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:55'! coordinates "This instance variable stores the coordinates of the source stroke" ^ coordinates! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:56'! coordinates: aCRRecognizerCoordinates "This instance variable stores the coordinates of the source stroke" coordinates _ aCRRecognizerCoordinates! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 7/3/2000 12:46'! directionFrom: startPoint to: endPoint ^ (endPoint - startPoint) degrees rounded! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:57'! directionPoints "This instance variable contains the points for the significant direction vectors" ^ directionPoints! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:57'! directionPoints: aSequenceableCollection "This instance variable contains the points for the significant direction vectors" directionPoints _ aSequenceableCollection! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:58'! directionVectors "Calculat the significant directions basd on the siginificant points" | vectors | vectors _ Array new: self directionPoints size - 1. 1 to: self directionPoints size - 1 do: [:index | vectors at: index put: (self directionPoints at: index + 1) - (self directionPoints at: index)]. ^ vectors! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:58'! directions "This instance variable stores the significant directions in degrees" ^ directions! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:58'! directions: aSequenceableCollection "This instance variable stores the significant directions in degrees" directions _ aSequenceableCollection! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:59'! echo "Collection of all the LineMorphs for the graphical echo" ^ echo! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 09:59'! echo: anOrderedCollection "Collection of all the LineMorphs for the graphical echo" echo _ anOrderedCollection! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 6/12/2000 20:17'! endTime ^ endTime! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 6/12/2000 20:17'! endTime: aNumber endTime _ aNumber! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/28/2000 10:45'! ensureMaxDirectionLength: allPointsSequenceableCollection "Ensures that all the siginificant directions are not longer than a certain length. I didn't use this method dor a long time and the results were not much worse without it. However, sometimes it's very useful to match a stroke containing one very long direction vector and a very similar stroke containing two shorter vectors with a very small intermdiate angle. Note: This method partitions a vector that is too loong into smaller vectors all of which having exactly the same length. An older version used captured points to introduce new vectors, but it turned out to be worse than this." | oldP maxDistance newDirPoints | maxDistance _ CRFeature maxNormDistance * 8 // 10. maxDistance _ maxDistance * maxDistance. oldP _ self directionPoints first. newDirPoints _ OrderedCollection with: oldP. 2 to: self directionPoints size do: [:dirPointsIndex | | p factor | p _ self directionPoints at: dirPointsIndex. factor _ (self squaredDistanceFrom: oldP to: p) // maxDistance. factor > 0 ifTrue: [ | part | "Avoid unnecessary root calculations" factor _ factor < 4 ifTrue: [1] ifFalse: [factor sqrt truncated]. part _ p - oldP // (factor + 1). 1 to: factor do: [:index | newDirPoints add: (oldP + (part * index))]]. newDirPoints add: p. oldP _ p. ]. self directionPoints: newDirPoints.! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:02'! escapePossible "Shall the recognizer esacpe when leaving the pen at the startposition for a certain amount of time?" ^ escapePossible! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:02'! escapePossible: aBoolean "Shall the recognizer esacpe when leaving the pen at the startposition for a certain amount of time?" escapePossible _ aBoolean! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:03'! extent "Extent of the stroke's bounding box" ^ (self bottomRight - self topLeft) max: 1@1! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:04'! lastPoint "The last captured point. Captured points are only added if the distance to the last point are at least a certain amount of pixels. But, the last point of a stroke has to be added anyway and therefore it has to be stored" ^ lastPoint! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:04'! lastPoint: aPoint "The last captured point. Captured points are only added if the distance to the last point are at least a certain amount of pixels. But, the last point of a stroke has to be added anyway and therefore it has to be stored" lastPoint _ aPoint! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:07'! normalizedDirection: degreeNumber "Given a direction in degrees. Return the nearest direction that points to the start of a segment. Example: If the segment size is 45 degrees, a value of 0, 45, 90, 135, 180, ..., 270, 315 degrees is returned. For 24 degres, 45 degrees is returned." | angleSector | angleSector _ self parameters angleSector. ^ degreeNumber + (angleSector // 2) // angleSector * angleSector \\ 360! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:07'! points: aSequenceableCollection "The captured points in global display coordinates" points _ aSequenceableCollection! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:08'! resetTempCollections "Reset the temporary collection holding direction degree numbers and significant points" self directions: OrderedCollection new. self directionPoints: OrderedCollection new.! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/14/2000 11:43'! setIdentityDisplayProperties "Set the display properties as if the source device would already be in normalized feature coordinates. This is used to recalculate a fature after it's coordinates are translated to normalized feature coordinates" self displayProperties: (CRDisplayProperties name: nil aspectRatio: 1@1 maxSize: CRFeature maxSize minCaptureDistance: 0 minMoveDistance: 0). ! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 7/4/2000 09:57'! squaredDistanceFrom: aPoint to: bPoint | xDiff yDiff | xDiff _ aPoint x - bPoint x. yDiff _ aPoint y - bPoint y. ^ xDiff * xDiff + (yDiff * yDiff). ! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 6/12/2000 20:17'! startTime ^ startTime! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 6/12/2000 20:17'! startTime: aNumber startTime _ aNumber! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:11'! time "The time used to draw the stroke" | end | ^ self startTime notNil ifTrue: [end _ self endTime isNil ifTrue: [Time millisecondClockValue] ifFalse: [self endTime]. end - self startTime]! ! !CRRecognizer methodsFor: 'private' stamp: 'NS 8/9/2000 10:11'! topLeft "TopLft of the stroke's bounding box." ^ self coordinates topLeft! ! !CRRecognizer methodsFor: 'initialize-release' stamp: 'NS 7/9/2000 22:23'! initializeDictionary: aCRDictionary displayProperties: aCRDisplayProperties self dictionary: aCRDictionary. self displayProperties: aCRDisplayProperties. self isEchoEnabled: true. ! ! !CRRecognizer methodsFor: 'testing' stamp: 'NS 8/9/2000 10:11'! isEchoEnabled "Shall the recognizer generate a graphical echo?" ^ isEchoEnabled! ! !CRRecognizer methodsFor: 'testing' stamp: 'NS 8/9/2000 10:12'! isRunning "Is the recognize running?" ^ self startTime notNil and: [self endTime isNil]! ! !CRRecognizer methodsFor: 'testing' stamp: 'NS 2/17/2001 09:33'! shouldEscape ^ self escapePossible and: [Time millisecondClockValue - self startTime > self parameters escapeTime].! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/9/2000 10:14'! addAllPoints: aSequenceableCollection "Add the given collection of points in global display coordinates (pixel) to the collection of captured points" | oldEcho | oldEcho _ self echo. aSequenceableCollection do: [:each | self addPoint: each]. self echo: oldEcho.! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 2/17/2001 10:16'! addPoint: aPoint "Add the given point in global screen coordinates (pixel) to the collection of captured points. The points is only added if the distance to the last one is big enough" | first | (self points isEmptyOrNil not and: [(self absMaxCoordDistance: aPoint and: self points first) >= self displayProperties minMoveDistance]) ifTrue: [self escapePossible: false]. first _ self lastPoint isNil. self lastPoint: aPoint. (first or: [(self absMaxCoordDistance: self points last and: aPoint) >= self displayProperties minCaptureDistance]) ifTrue: [self addPointWithoutTest: aPoint].! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/9/2000 10:18'! calculateNewFeature: aCRFeature "Treat the captured points of a previously calculated feature as source points to calculate a new feature. This allows to recalculate an existing fature with different properties. NOTE: To do this recalculation, the captured points must be available in the feature" | newFeature | ((aCRFeature isDot) or: [aCRFeature isEmpty]) ifTrue: [^ aCRFeature class new time: aCRFeature time]. self isEchoEnabled: false. self resetAndStart. self setIdentityDisplayProperties. self addAllPoints: aCRFeature capturedPoints. newFeature _ self stopAndCalculateFeature. newFeature time: aCRFeature time. (newFeature isStroke) ifTrue: [newFeature extent: aCRFeature extent]. ^ newFeature! ! !CRRecognizer methodsFor: 'accessing'! dictionary ^ dictionary.! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 6/12/2000 20:25'! dictionary: aCRDictionary dictionary _ aCRDictionary! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/9/2000 10:19'! displayProperties "The display properties the recognizer uses to translate between global scren coordinates and normalized feature coordinates. This instance variable can contain a CRDisplayPoperties or #activeInstance to refer to the currently active display property instance." ^ displayProperties = #activeInstance ifTrue: [CRDisplayProperties activeInstance] ifFalse: [displayProperties].! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/9/2000 10:19'! displayProperties: aCRDisplayProperties "The display properties the recognizer uses to translate between global scren coordinates and normalized feature coordinates. This instance variable can contain a CRDisplayPoperties or #activeInstance to refer to the currently active display property instance." displayProperties _ aCRDisplayProperties! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/9/2000 10:19'! isEchoEnabled: aBoolean "Is graphical echo wanted?" isEchoEnabled _ aBoolean! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 6/12/2000 20:24'! parameters ^ self dictionary parameters! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/9/2000 10:20'! points "The captured points in global display coordinates" ^ points! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/9/2000 10:21'! resetAndStart "Reset the recognize and start capturing points. This starts the clock measuring how long it takes to capture the points" self clearPoints. self endTime: nil. self escapePossible: true. self startTime: Time millisecondClockValue. ! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/9/2000 10:22'! stop "Stop capturing points. This stops the clock measuring how long it took to capture the points. It returns the informations about the coordinates of the stroke (in global screen coordinates (pixel))" self endTime ifNil: [self endTime: Time millisecondClockValue. self points last ~= self lastPoint ifTrue: [self addPointWithoutTest: self lastPoint]]. self echo ifNotNil: [self echo do: [:each | each delete]. self echo: nil]. ^ self coordinates start: self points first; end: self points last.! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 7/6/2000 16:50'! stopAndCalculateCoordinates ^ self stop! ! !CRRecognizer methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:27'! stopAndCalculateFeature "Stop the recognizer and calculate the feature according to the captured points" | feature factor divisor beforeVector afterVector transPoints p size minSquaredLen minAngle | self stop. "Special features" self points size = 0 ifTrue: [^ nil]. (self points size = 1 or: [(self absMaxCoord: self extent) < self displayProperties minMoveDistance]) ifTrue: [^ CRDotFeature new time: self time]. "Real stroke feature" self resetTempCollections. transPoints _ Array new: self points size. "Calculate and store values to translate points from global screen coordinates (pixels) into normalized feature coordinates. Looks a little bit complicated but this is necessary to avoid floating point operations!!" factor _ self calcPointNormFactor. divisor _ self calcPointNormDivisor. beforeVector _ self topLeft. afterVector _ self calcPointNormShiftVector. size _ self points size. minSquaredLen _ (CRFeature maxSize * self parameters minDirectionLengthPercentage // 100). minSquaredLen _ minSquaredLen * minSquaredLen. minAngle _ self parameters minAngle. "Iterate over the captured points" self points withIndexDo: [:each :index | p _ each - beforeVector * factor // divisor + afterVector. transPoints at: index put: p. self addDirectionTowardsPoint: p minSquaredLength: minSquaredLen minAngle: minAngle last: index = size]. "Ensure that no direction vector is longer than a certain maximum" self ensureMaxDirectionLength: transPoints. "Generate stroke feature" feature _ CRStrokeFeature new. feature time: self time; capturedPoints: transPoints; directionVectors: self directionVectors; extent: self calcNormExtent; startPoint: self directionPoints first; calcAndSetSecondaryValues. ^ feature! ! !CRRecognizer class methodsFor: 'instance creation' stamp: 'NS 7/9/2000 20:23'! dictionary: aCRDictionary ^ self dictionary: aCRDictionary displayProperties: #activeInstance! ! !CRRecognizer class methodsFor: 'instance creation' stamp: 'NS 6/16/2000 13:05'! dictionary: aCRDictionary displayProperties: aCRDisplayProperties ^ super new initializeDictionary: aCRDictionary displayProperties: aCRDisplayProperties! ! !CRRecognizerCoordinates methodsFor: 'initialize-release' stamp: 'NS 7/13/2000 16:33'! initializeStart: startPoint end: endPoint top: topPoint left: leftpoint bottom: bottomPoint right: rightPoint self start: startPoint. self end: endPoint. self top: topPoint. self right: rightPoint. self left: leftpoint. self bottom: bottomPoint.! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:32'! bottom ^ bottom! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:31'! bottom: aPoint bottom _ aPoint! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/18/2000 16:36'! bottomLeft ^ self left x @ self bottom y! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/18/2000 16:36'! bottomRight ^ self right x @ self bottom y! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/6/2000 08:15'! end ^ end! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/6/2000 08:15'! end: aPoint end _ aPoint! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 8/14/2000 17:37'! extent ^ self bottomRight - self topLeft! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:51'! isOnBorder: aPoint ^ (self isOnXBorder: aPoint) or: [self isOnXBorder: aPoint]! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 17:00'! isOnXBorder: aPoint ^ aPoint y = self top y or: [aPoint y = self bottom y].! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 17:01'! isOnYBorder: aPoint ^ aPoint x = self right x or: [aPoint x = self left y].! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:32'! left ^ left! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:31'! left: aPoint left _ aPoint! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/18/2000 16:30'! pointAt: aSymbol ^ self perform: aSymbol withArguments: Array new.! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:32'! right ^ right! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:31'! right: aPoint right _ aPoint! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/6/2000 08:15'! start ^ start! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/6/2000 08:15'! start: aPoint start _ aPoint! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:32'! top ^ top! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:31'! top: aPoint top _ aPoint! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/13/2000 16:41'! topLeft ^ self left x @ self top y! ! !CRRecognizerCoordinates methodsFor: 'accessing' stamp: 'NS 7/18/2000 16:36'! topRight ^ self right x @ self top y! ! !CRRecognizerCoordinates class methodsFor: 'instance creation' stamp: 'NS 7/13/2000 16:38'! new ^ super new! ! !CRRecognizerCoordinates class methodsFor: 'instance creation' stamp: 'NS 7/13/2000 16:35'! start: startPoint end: endPoint top: topPoint left: leftPoint bottom: bottomPoint right: rightpoint ^ super new initializeStart: startPoint end: endPoint top: topPoint left: leftPoint bottom: bottomPoint right: rightpoint! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! absAngleSum: aNumber absAngleSum _ aNumber! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! acuteAngleCount: aNumber acuteAngleCount _ aNumber! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! acuteAnglePosition: aNumber acuteAnglePosition _ aNumber! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! addSecondaryValues: aCRFeature "Add the secondary values (all the values that can be derived from other values) from the given feature to the values of the current fature" self posAngleSum: self posAngleSum + aCRFeature posAngleSum. self negAngleSum: self negAngleSum + aCRFeature negAngleSum. self absAngleSum: self absAngleSum + aCRFeature absAngleSum. self acuteAngleCount: self acuteAngleCount + aCRFeature acuteAngleCount. self acuteAnglePosition: self acuteAnglePosition + aCRFeature acuteAnglePosition. self endPoint: self endPoint + aCRFeature endPoint. ! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! angleDifferenceFrom: startDegreeNumber to: endDegreeNumber | a | a _ endDegreeNumber - startDegreeNumber. ^ a > 180 ifTrue: [a - 360] ifFalse: [a < -180 ifTrue: [a + 360] ifFalse: [a]]! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! areSecondaryValuesAvailable "Are the secondary values (the values that can be derived from other values) already calculated and cached?" ^ absAngleSum notNil! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcAbsAngleSum ^ self calcPosAngleSum: self angles abs squaredLengths: self squaredLengths maxAngle: nil.! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcAcuteAngleArray ^ self calcAcuteAngleArray: self angles squaredLengths: self squaredLengths.! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcAcuteAngleArray: angleCollection squaredLengths: squaredLenCollection "This method calculates several key-values describing the acute angles of the feature. In fact, it calculates the sum of the acute angles (weighted by heuristics) and the avarage position of the acute angles within the stroke. Acute ngle in this context mean angles, that are either very big or that don't have angles with the same sign in their neighbourhood" | acutePos totalLenSum maxAngleSum weightedAcuteCount lenCollection minAngle fullLen fullAngle | minAngle _ 60. "Minumum angle to be possibly considered an acute angle" fullAngle _ 150. "If an angle is at least that big, it is 100% acute" maxAngleSum _ 30. "The angle in the neighbourhood of an acute angle are not allowed to be bigger than that." fullLen _ self class maxSize // 4. "The neighbourhood of an acute angle is searched for other angles within this distance" lenCollection _ squaredLenCollection collect: [:each | each sqrt rounded]. weightedAcuteCount _ 0. acutePos _ 0. totalLenSum _ 0. "Iterate through all the angles an check wheter they are acute" angleCollection withIndexDo: [:angle :index | | absAngle angleSign | totalLenSum _ totalLenSum + (lenCollection at: index). absAngle _ angle abs. angleSign _ angle sign. absAngle > minAngle ifTrue: [ | factor minFactor | minFactor _ 100. "Search the neighbourhood in both direction for other angled" {-1. 1} do: [:sign | | angleIndex lenIndex lenSum angleSum tempAngleSum | angleSum _ 0. tempAngleSum _ 0. lenSum _ 0. angleIndex _ index + sign. lenIndex _ sign = 1 ifTrue: [index + 1] ifFalse: [index]. [angleIndex >= 0 and: [angleIndex <= (angleCollection size + 1) and: [lenSum < fullLen and: [tempAngleSum < maxAngleSum]]]] whileTrue: [ | a | lenSum _ lenSum + (lenCollection at: lenIndex). (angleIndex >= 1 and: [angleIndex <= angleCollection size]) ifTrue: [a _ angleCollection at: angleIndex. a sign = angleSign ifTrue: [angleSum _ tempAngleSum. tempAngleSum _ tempAngleSum + a abs]]. angleIndex _ angleIndex + sign. lenIndex _ lenIndex + sign]. "Calculate the factor that describes how acute the current angle really is" factor _ maxAngleSum - angleSum. factor _ 100 * factor * factor // (maxAngleSum * maxAngleSum) * lenSum // fullLen * lenSum // fullLen. minFactor _ factor min: minFactor]. minFactor > 0 ifTrue: [ (index = 1 or: [index = angleCollection size]) ifTrue: [ | len | len _ index = 1 ifTrue: [squaredLenCollection first] ifFalse: [squaredLenCollection last]. absAngle _ self weightedStartEndAngle: absAngle squaredLength: len]. absAngle < fullAngle ifTrue: [minFactor _ minFactor * absAngle * absAngle // (fullAngle * fullAngle)]. "Update the total values" weightedAcuteCount _ weightedAcuteCount + minFactor. acutePos _ acutePos + (totalLenSum // 100 * minFactor)]]]. totalLenSum _ totalLenSum + lenCollection last. acutePos _ weightedAcuteCount = 0 ifTrue: [0] ifFalse: [acutePos // weightedAcuteCount * 10000 // totalLenSum]. ^ {weightedAcuteCount. acutePos}. ! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcAngles "Calculate the (relative) angles" | gA | gA _ self globalAngles. ^ (1 to: gA size - 1) collect: [:index | (self angleDifferenceFrom: (gA at: index) to: (gA at: index + 1))]! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcEndPoint ^ self points last! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcGlobalAngles ^ directionVectors collect: [:each | each degrees rounded].! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcNegAngleSum "Calculate the sum of the negative angles. The negative angles are only fully counted up to a value of 135 degrees. This is important because angles around 180 degrees can be positive or negative without significantly changing the stroke." ^ self calcPosAngleSum: self angles negated squaredLengths: self squaredLengths maxAngle: 135.! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcPoints "Calculate the start/end points of the direction vectors" | collection | collection _ Array new: self directionVectors size + 1. collection at: 1 put: self originalStartPoint. self directionVectors withIndexDo: [:each :index | collection at: index + 1 put: (collection at: index) + each]. ^ collection! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcPosAngleSum "Calculate the sum of the positive angles. The positive angles are only fully counted up to a value of 135 degrees. This is important because angles around 180 degrees can be positive or negative without significantly changing the stroke." ^ self calcPosAngleSum: self angles squaredLengths: self squaredLengths maxAngle: 135.! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcPosAngleSum: angleCollection squaredLengths: lenCollection maxAngle: maxAngleNumber "Calculate the sum of the positive angles in angleCollection. The angles are only fuly counted if they are smaller than maxAngleNumber. Angles at the start or the end of the stroke are only fully counted if the start/end line is long enough. (There is often noise at the start or at the end)" | angleSum max size | angleSum _ 0. max _ maxAngleNumber isNil ifTrue: [SmallInteger maxVal - 100] ifFalse: [maxAngleNumber]. size _ angleCollection size. angleCollection withIndexDo: [:each :index | each > 0 ifTrue: [| a | a _ each > max ifTrue: [each * (180 - each) * (180 - each) // (180 - maxAngleNumber * (180 - maxAngleNumber))] ifFalse: [each]. a _ (index = 1 or: [index = size]) ifTrue: [| len | len _ index = 1 ifTrue: [lenCollection first] ifFalse: [lenCollection last]. self weightedStartEndAngle: a squaredLength: len] ifFalse: [a]. angleSum _ angleSum + a]]. ^ angleSum! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcRelativePoints "Old version returned points relative to centroid. But it had negative effects!!" ^ self points.! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcSquaredLengths ^ self directionVectors collect: [:each | | x y | x _ each x abs. y _ each y abs. (x * x + (y * y))]! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! calcSquaredWeightedLengths "Calculate the sqared length and weight them by a heuristic functions. This function makes long lengths even longer. This is nice because eliminating long distances in a edit distance algorithm must be very expensive!!" | max | max _ CRFeature maxSize. max _ max * max. ^ self squaredLengths collect: [:each | each * ((each sqrt rounded // 6) min: 40) // 10].! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! directionVectors: aSequenceableCollection directionVectors _ aSequenceableCollection asArray! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! drawPoints: aSequenceableCollection on: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean aspectRatio: arPoint self drawPoints: aSequenceableCollection on: aForm topLeft: tlPoint + (self outputShiftVectorForSize: sizeNumber relative: aBoolean aspectRatio: arPoint) scaleFactor: (self outputFactorForSize: sizeNumber relative: aBoolean aspectRatio: arPoint)! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! drawPoints: aSequenceableCollection on: aForm topLeft: aPoint scaleFactor: aNumberOrPoint | pen r g b colorStep | g _ 0. r _ 1. b _ 0. colorStep _ 1 / aSequenceableCollection size. pen _ Pen newOnForm: aForm. pen place: aPoint + (aSequenceableCollection first * aNumberOrPoint). pen color: (Color r: r g: g b: b). pen roundNib: 2. pen go: 1. pen roundNib: 1. aSequenceableCollection do: [:each | r _ r - colorStep. b _ b + colorStep. pen color: (Color r: r g: g b: b); goto: aPoint + (each * aNumberOrPoint)]. pen roundNib: 2. pen go: 1. ^ aForm! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! endPoint: aPoint endPoint _ aPoint! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! extent: aPoint extent _ aPoint! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! globalAngles: aSequenceableCollection globalAngles _ aSequenceableCollection! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! maxStrokeDistance: aNumber maxStrokeDistance _ aNumber! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! negAngleSum: aNumber negAngleSum _ aNumber! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! outputFactorForSize: aNumber relative: aBoolean aspectRatio: aPoint "Calculates the scale factor to display the feature with the given size on the screen" | factor | factor _ aNumber / (self class maxSize * aPoint). ^ aBoolean ifTrue: [factor * self size / self class maxSize] ifFalse: [factor]! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! outputShiftVectorForSize: aNumber relative: aBoolean aspectRatio: aPoint "Calculates the shift vector to display the feature with the given size on the screen" ^ aNumber asPoint - ((self outputFactorForSize: aNumber relative: aBoolean aspectRatio: aPoint) * self class maxSize) // 2! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! posAngleSum: aNumber posAngleSum _ aNumber! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! relativePoints: aSequenceableCollection relativePoints _ aSequenceableCollection! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! resetSecondaryValues "Reset all the secondary values (the values that can be derived from other values)." self posAngleSum: 0. self negAngleSum: 0. self absAngleSum: 0. self acuteAngleCount: 0. self acuteAnglePosition: 0. self endPoint: 0@0. ! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! squaredDistanceFrom: aPoint to: bPoint | xDiff yDiff | xDiff _ (bPoint x - aPoint x). yDiff _ (bPoint y - aPoint y). ^ (xDiff * xDiff + (yDiff * yDiff))! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! squaredWeightedLengths: aSequenceableCollection squaredWeightedLengths _ aSequenceableCollection! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! substAngleFactorFrom: startDegreeNumber to: endDegreeNumber "This is a heuristic method. When a vector of one feature should be matched with a vector of another feature, the global angle difference is an important criteria. In the current implementation, the matching costs are first calculated independent of the gobal angle difference. But then, they are multiplied with this factor and then diveded by 10. (Actually, this factor is 10 times more than it should be to avoid floating point ops)." | absDiff | absDiff _ (endDegreeNumber - startDegreeNumber) abs. absDiff _ absDiff > 180 ifTrue: [360 - absDiff] ifFalse: [absDiff]. ^ 315 * absDiff * absDiff // (180 * 180).! ! !CRStrokeFeature methodsFor: 'private' stamp: 'NS 8/14/2000 15:25'! weightedStartEndAngle: degreeNumber squaredLength: lenNumber "At the start/end of a stroke there is often a very short vector in a completely wrong direction before the real stroke starts/ends. Therefore, there are often quite big angles that are just noise. This method reduces this noise by weighting typical 'start/end noise angles' with a small value" | lenLimit | lenLimit _ self class maxSize // 8. lenLimit _ lenLimit * lenLimit. ^ lenNumber < lenLimit ifTrue: [degreeNumber * lenNumber // lenLimit] ifFalse: [degreeNumber]. ! ! !CRStrokeFeature methodsFor: 'initialize-release' stamp: 'NS 8/14/2000 15:25'! initialize ^ self hotspot: #start.! ! !CRStrokeFeature methodsFor: 'testing' stamp: 'NS 8/14/2000 15:25'! hasSomeCapturedPoints ^ self capturedPoints notNil! ! !CRStrokeFeature methodsFor: 'testing' stamp: 'NS 8/14/2000 15:25'! isCacheFull ^ relativePoints notNil! ! !CRStrokeFeature methodsFor: 'testing' stamp: 'NS 8/14/2000 15:25'! isStroke ^ true! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! absAngleSum ^ absAngleSum! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! acuteAngleCount ^ acuteAngleCount! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! acuteAnglePosition ^ acuteAnglePosition! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! angles ^ self calcAngles! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! calcAndSetSecondaryValues "Calculate the secondary values of the feature. If there is only one reference feature for a stroke, the secondary values are just calculated and store like this. But, if there are more reference features for one single character, the secondary values are set to the avarage." | acuteArray | self isCacheFull ifFalse: [self fillCache]. self posAngleSum: self calcPosAngleSum. self negAngleSum: self calcNegAngleSum. self absAngleSum: self calcAbsAngleSum. acuteArray _ self calcAcuteAngleArray. self acuteAngleCount: acuteArray first. "self acuteAngleSum: acuteArray second." self acuteAnglePosition: acuteArray second. self endPoint: self calcEndPoint. ! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! capturedPoints ^ capturedPoints! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! capturedPoints: aSequenceableCollection capturedPoints _ aSequenceableCollection! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! directionVectors ^ directionVectors! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! drawCapturedPointsOn: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean properties: aCRDisplayProperties self capturedPoints ifNil: [^ self]. self drawPoints: self capturedPoints on: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean aspectRatio: aCRDisplayProperties aspectRatio ! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! drawFeatureOn: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean properties: aCRDisplayProperties self drawPoints: self points on: aForm size: sizeNumber topLeft: tlPoint relative: aBoolean aspectRatio: aCRDisplayProperties aspectRatio! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! endPoint ^ endPoint! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! extent ^ extent! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! fillCache "Cache often used values" self relativePoints: self calcRelativePoints. self squaredWeightedLengths: self calcSquaredWeightedLengths. self globalAngles: self calcGlobalAngles. ! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! globalAngles self isCacheFull ifFalse: [self fillCache]. ^ globalAngles! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! hotspot ^ hotspot! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! hotspot: aSymbol hotspot _ aSymbol! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! maxStrokeDistance maxStrokeDistance isNil ifTrue: [self updateMaxStrokeDistance. ^ maxStrokeDistance]. ^ maxStrokeDistance! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! negAngleSum ^ negAngleSum! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! originalStartPoint ^ originalStartPoint isNil ifTrue: [self startPoint] ifFalse: [originalStartPoint].! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! originalStartPoint: aPoint originalStartPoint _ aPoint.! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! points ^ self calcPoints! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! posAngleSum ^ posAngleSum! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! reduceCapturedPoints "Reduce the amount of captured points. Even after some captured points are reduced, the quality of a picture showing the captured feature is still pretty good. However, a feature can only be recalculated with other parameters if all the originally captured points are available" | newPoints sum angle | newPoints _ OrderedCollection new. newPoints add: self capturedPoints first. sum _ 0. 2 to: self capturedPoints size - 1 do: [:index | angle _ (((self capturedPoints at: index + 1) - (self capturedPoints at: index)) degrees - ((self capturedPoints at: index) - (self capturedPoints at: index - 1)) degrees) abs. angle > 180 ifTrue: [angle _ 360 - angle]. sum _ sum + angle. sum > self class capturedPointsReduceAngle ifTrue: [newPoints add: (self capturedPoints at: index). sum _ 0]]. newPoints add: self capturedPoints last. self capturedPoints: newPoints! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! relativePoints self isCacheFull ifFalse: [self fillCache]. ^ relativePoints! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! relativeSize ^ super relativeSize max: 1.! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! releaseCache "Delete the cache" self relativePoints: nil. self squaredWeightedLengths: nil. self globalAngles: nil! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! removeCapturedPoints self capturedPoints: nil.! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! setSecondaryValues: aCRFeature "Set the secondary values (values that can be derived from others) to the values of the given feature" self resetSecondaryValues. self addSecondaryValues: aCRFeature. self isCacheFull ifTrue: [self fillCache]. ! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! setSecondaryValuesToAvg: aCollection "Set the secondary values (the values that can be derived from others) to the avarage of the values of the features in the collection. If there is only one reference feature for a stroke, the secondary values are just derived. But, if there are more reference features for one single character, the secondary values are set to the avarage using this method" | count | count _ 0. aCollection do: [:each | each isStroke ifTrue: [count _ count + 1]]. count = 0 ifTrue: [^ self]. self resetSecondaryValues. aCollection do: [:each | each isStroke ifTrue: [self addSecondaryValues: each]]. self posAngleSum: self posAngleSum // count. self negAngleSum: self negAngleSum // count. self absAngleSum: self absAngleSum // count. self acuteAngleCount: self acuteAngleCount // count. self acuteAnglePosition: self acuteAnglePosition // count. self endPoint: self endPoint // count. self isCacheFull ifTrue: [self fillCache].! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! setValuesToAvg: aCollection "Set all the values except the direction vectors to the avarage values of the features in the collection. This is used if there is more than one reference feature for a character" | count | count _ 0. aCollection do: [:each | each isStroke ifTrue: [count _ count + 1]]. count = 0 ifFalse: [ self extent: 0@0. self time: 0. self originalStartPoint: self startPoint. self startPoint: 0@0. aCollection do: [:each | each isStroke ifTrue: [self extent: self extent + each extent. self time: self time + each time. self startPoint: self startPoint + each startPoint]]. self startPoint: self startPoint // count. self extent: self extent // count. self time: self time // count]. self setSecondaryValuesToAvg: aCollection. ! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! size "The size of the feature" ^ self extent x max: self extent y. ! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! squaredLengths ^ self calcSquaredLengths! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! squaredWeightedLengths self isCacheFull ifFalse: [self fillCache]. ^ squaredWeightedLengths! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! startPoint ^ startPoint! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! startPoint: aPoint startPoint _ aPoint! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! strokeSize "The number of vectors the corresponding stroke got decomposed in" ^ directionVectors size! ! !CRStrokeFeature methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! updateMaxStrokeDistance "Update the maximum stroke-distance. This maximum stroke-distance is used to normalize the absolute stroke-distance between two features. The value has to be divided by strokeSize because there are only insert/remove operations and no substitutions. According to the stroke-distance algorithm, successive insert/remove costs are multiplied with the number of successive costs. NOTE: This method should perhaps be changed when the method sameClassAbsoluteStrokeDistance:forReference: is changed" self maxStrokeDistance: (self sameClassAbsoluteStrokeDistance: self class strokeReferenceFeature1 forReference: true) // (self strokeSize + 3).! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassAbsoluteStrokeDistance: aCRFeature forReference: aBoolean "Calculate a distance measuring the costs of matching all the direction vectors of the two features. The used algorithm is known as an algorithm to compute the edit distance between two strings or DNA-sequences. Basically it iterates through all the direction vectors of the two features and tries to make them identical. To achieve this goal, the algorithm can insert, replace or substitute direction vectors. Each of these 3 operations has costs assigned to it and the algorithm finds the sequence of them that generates the minimum costs. The alghorithm has a time usage of n * m (if n and m are the numbers of vectors in the two features). The main part of this implementation is not the basic algorithm, but th heuristics to calculate the costs for the operations. Besides the direction vectors, also the points where the vector starts / ends, the global angles of the vectors, the number of successive insert/remove operations without an intermediate substitution, etc. If aBoolean is true, the algorithm doesn't add special additional costs for successive insert/ remove ops. Further their is no substitution possible. This mode is used to calculate the distance to a reference feature that consists only of one point. Thus, this distance is an approximation of the costs that are used to build the feature from scratch. When th stroke-distance costs have to be normalized, this specieal reference costs are considered as the maximum distance NOTE: If this method is changed, the method updateStrokeDistance should perhaps also be changed" | myPoints otherPoints myVectors otherVectors mySquaredLengths otherSquaredLengths base rowInsertRemove rowBase rowInsertRemoveCount insertRemove myAngles otherAngles additionalMultiInsertRemoveCost | "Additional costs if there are multiple succesive insert removes without an intermediate substitution" additionalMultiInsertRemoveCost _ aBoolean ifTrue: [0] ifFalse: [self class maxSize * self class maxSize // 800]. "Store the used arrays in local variables" myPoints _ self relativePoints. otherPoints _ aCRFeature relativePoints. myVectors _ self directionVectors. otherVectors _ aCRFeature directionVectors. mySquaredLengths _ self squaredWeightedLengths. otherSquaredLengths _ aCRFeature squaredWeightedLengths. myAngles _ self globalAngles. otherAngles _ aCRFeature globalAngles. "rowBase stores the base values of the current/last row. rowInsertRemove stores the insert/remove costs. rowInsertRmoveCount stores the number of successive insert/remove operations." rowBase _ Array new: otherVectors size + 1. rowInsertRemove _ Array new: otherVectors size + 1. rowInsertRemoveCount _ Array new: otherVectors size + 1. "Initialize row. First row is a special case" rowBase at: 1 put: 0. rowInsertRemove at: 1 put: 0. rowInsertRemoveCount at: 1 put: 2. insertRemove _ additionalMultiInsertRemoveCost negated. 2 to: rowBase size do: [:j | insertRemove _ insertRemove + ((otherSquaredLengths at: j - 1) + (self squaredDistanceFrom: (otherPoints at: j - 1) to: (myPoints at: 1)) // 100) + additionalMultiInsertRemoveCost. rowInsertRemove at: j put: insertRemove. rowBase at: j put: insertRemove * (j - 1). rowInsertRemoveCount at: j put: j]. "Nested iteration through all the vectors of both of the features" insertRemove _ (rowInsertRemove at: 1) - additionalMultiInsertRemoveCost. 2 to: myVectors size + 1 do: [:i | | substBase | "Subst base: Base cost for a possible substitution" substBase _ rowBase at: 1. insertRemove _ insertRemove + ((mySquaredLengths at: i - 1) + (self squaredDistanceFrom: (myPoints at: i - 1) to: (otherPoints at: 1)) // 100) + additionalMultiInsertRemoveCost. rowInsertRemove at: 1 put: insertRemove. rowBase at: 1 put: insertRemove * (i - 1). rowInsertRemoveCount at: 1 put: i. 2 to: otherVectors size + 1 do: [:j | | insert remove subst removeBase insertBase insertRemoveCount | "RemoveBase / insertBase: Base costs for possible removals / insertions" removeBase _ rowBase at: j. insertBase _ rowBase at: j - 1. "Calculate the costs for a removal at the current position" remove _ (mySquaredLengths at: i - 1) + (self squaredDistanceFrom: (myPoints at: i - 1) to: (otherPoints at: j)) // 100. (insertRemove _ rowInsertRemove at: j) = 0 ifTrue: [removeBase _ removeBase + remove] ifFalse: [removeBase _ removeBase + insertRemove + (remove * (rowInsertRemoveCount at: j)). remove _ remove + insertRemove]. "Calculate the costs for an insertion at the current position" insert _ (otherSquaredLengths at: j - 1) + (self squaredDistanceFrom: (otherPoints at: j - 1) to: (myPoints at: i)) // 100. (insertRemove _ rowInsertRemove at: j - 1) = 0 ifTrue: [insertBase _ insertBase + insert] ifFalse: [insertBase _ insertBase + insertRemove + (insert * (rowInsertRemoveCount at: j - 1)). insert _ insert + insertRemove]. "Calculate the costs for a substitution at the current position" aBoolean ifTrue: [substBase _ SmallInteger maxVal] ifFalse: [subst _ ((self squaredDistanceFrom: (otherVectors at: j - 1) to: (myVectors at: i - 1)) + (self squaredDistanceFrom: (otherPoints at: j - 1) to: (myPoints at: i - 1))) * (10 + (self substAngleFactorFrom: (otherAngles at: j - 1) to: (myAngles at: i - 1))) // 1000. substBase _ substBase + subst]. "Find best operation at the current position" ((substBase <= removeBase) and: [substBase <= insertBase]) ifTrue: [base _ substBase. insertRemove _ 0. insertRemoveCount _ 1] ifFalse: [(removeBase <= insertBase) ifTrue: [base _ removeBase. insertRemove _ remove + additionalMultiInsertRemoveCost. insertRemoveCount _ (rowInsertRemoveCount at: j) + 1] ifFalse: [base _ insertBase. insertRemove _ insert + additionalMultiInsertRemoveCost. insertRemoveCount _ (rowInsertRemoveCount at: j - 1) + 1]]. "Update costs" substBase _ rowBase at: j. rowBase at: j put: base. rowInsertRemove at: j put: insertRemove. rowInsertRemoveCount at: j put: insertRemoveCount]. insertRemove _ rowInsertRemove at: 1]. ^ base! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassAcuteAngleDistance: aCRFeature "This mainly heuristic method measures the distance between two features in terms of acute angles and their positions" | myVal otherVal factor diff minCount significantCount | "The position is only considered if the acute angle count is at least this value (An acute angle count of 100 means that there is one completely acute angle)." minCount _ 50. myVal _ self acuteAngleCount. otherVal _ aCRFeature acuteAngleCount. significantCount _ myVal max: otherVal. diff _ (myVal - otherVal) abs. factor _ 500 * diff * diff // (200 * 200). significantCount > minCount ifTrue: [myVal _ self acuteAnglePosition. otherVal _ aCRFeature acuteAnglePosition. diff _ (myVal - otherVal) abs. factor _ factor + (500 * diff * diff // 1600)]. ^ self class maxNormDistance min: self class maxNormDistance * factor // 1000.! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassAngleDistance: aCRFeature "This heuristic method measures the distance between two features in terms of angle sums" | myVal otherVal factor diff min additionalMinAngle | additionalMinAngle _ 75. "Negative angle sum" myVal _ self negAngleSum. otherVal _ aCRFeature negAngleSum. diff _ (myVal - otherVal) abs. min _ (myVal min: otherVal) + additionalMinAngle. factor _ 1000 min: 250 * diff // min * diff // min. "Positive angle sum" myVal _ self posAngleSum. otherVal _ aCRFeature posAngleSum. diff _ (myVal - otherVal) abs. min _ (myVal min: otherVal) + additionalMinAngle. factor _ 1000 min: factor + (250 * diff // min * diff // min). "Absolute angle sum" myVal _ self absAngleSum. otherVal _ aCRFeature absAngleSum. diff _ (myVal - otherVal) abs. min _ (myVal min: otherVal) + additionalMinAngle. factor _ factor + (1000 min: 500 * diff // min * diff // min). ^ self class maxNormDistance min: self class maxNormDistance * factor // 2000.! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassShapeDistance: aCRFeature "This heuristic function measures the distance between two features in terms of their bounding box' shapes." | t1 t2 mult div | "Calculate the relative shape difference and multiply it by 10" t1 _ self extent x * aCRFeature extent y. t2 _ self extent y * aCRFeature extent x. t1 > t2 ifTrue: [mult _ t1 - t2. div _ t2] ifFalse: [mult _ t2 - t1. div _ t1]. div = 0 ifTrue: [^ mult = 0 ifTrue: [0] ifFalse: [self class maxNormDistance]]. mult _ 10 * mult // div. "If the ratio is smaller than 7, use a quadratic function to map into the normalized distance space. (7 is mapped to 4/10 of the maximum distance). Otherwise use a linear function. The total function is continuous and 20 (or more) is mapped to the maximum distance" ^ mult < 7 ifTrue: [4 * mult * mult * self class maxNormDistance // 490] ifFalse: [4 * self class maxNormDistance // 10 + ((20 min: mult) - 7 * 6 * self class maxNormDistance // 130)]! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassStartEndDistance: aCRFeature "This is a heuristic method that measures the distance between two features in terms of their relative start/end points." | startDistance distance endDistance diffX diffY xDiv yDiv | "Raster the start and end points. If the aspect ratio is extreme (e.g. 5:1), the algorithm must be more sensitive to differences in the smaller direction" xDiv _ (100 * self extent x // self size + 100) * self class maxSize. xDiv _ xDiv + ((100 * aCRFeature extent x // aCRFeature size + 100) * self class maxSize) // 400. yDiv _ (100 * self extent y // self size + 100) * self class maxSize. yDiv _ yDiv + ((100 * aCRFeature extent y // aCRFeature size + 100) * self class maxSize) // 400. "Calculate the start and end distances" diffX _ (self startPoint x - aCRFeature startPoint x) abs * 12 // xDiv. diffY _ (self startPoint y - aCRFeature startPoint y) abs * 12 // yDiv. startDistance _ diffX * diffX + (diffY * diffY). diffX _ (self endPoint x - aCRFeature endPoint x) abs * 12 // self class maxSize. diffY _ (self endPoint y - aCRFeature endPoint y) abs * 12 // self class maxSize. endDistance _ diffX * diffX + (diffY * diffY). "Heuristics: When either start or endpoint distance is very bad, the result should be bad even if the other distance is quite well" distance _ (3 * startDistance) + (3 * endDistance) + (2 * (startDistance max: endDistance)) // 3. ^ self class maxNormDistance min: distance * self class maxNormDistance // 144 "12 * 12"! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassStrokeDistance: aCRFeature "Calculate an asymmetric distance measuring the costs of matching all the direction vectors of the two features. Basically this matching operation is completely symmetric. But the result of the operation is just a number that must be normalized somehow. (At the end the distance has to be in the interval [0, CRFeature maxNormDistance]). The problem is that the not yet normalized distance value can vary a lot depending on the number of direction vectors in the two features. That's the reason why the normalization process can't be done independent of the two currently compared features!! Usually one feature is compared to all the features in a dictionary. To be fair, the normalization process has to be the same for one dictionary lookup and thus this process can only use properties of the feature that is looked up. As a consequence, this distance is not symmetric ((A dist: B) ~= (B dist: A)). Whenever a new feature is looked up in a dictionary, the asymmetric stroke distance is used. For all the distances between features inside the dictionary, the symmetric stroke distance is used. (It would be really confusing if the distance from 'a' to 'b' would be different than the one from 'b' to 'a'!!)" ^ self sameClassStrokeDistance: aCRFeature max: self maxStrokeDistance! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassStrokeDistance: aCRFeature max: maxNumber "Calculate the stroke-distance and normalize by a linear function mapping maxNumber to the maximum distance" ^ self class maxNormDistance min: (self sameClassAbsoluteStrokeDistance: aCRFeature forReference: false) // (maxNumber // self class maxNormDistance).! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassStrokeSizeDistance: aCRFeature "This distance measures the difference between two features regading the number of vectors the stroke is decomposed into." | myVal otherVal diff min additionalMinStrokeSize | additionalMinStrokeSize _ 4. myVal _ self strokeSize. otherVal _ aCRFeature strokeSize. diff _ (myVal - otherVal) abs. min _ (myVal min: otherVal) + additionalMinStrokeSize. ^ self class maxNormDistance min: 1000 * diff // min * diff // min. ! ! !CRStrokeFeature methodsFor: 'private comparing' stamp: 'NS 8/14/2000 15:25'! sameClassSymmetricStrokeDistance: aCRFeature "Calculate a symmetric distance measuring the costs of matching all the direction vectors of the two features. Basically this matching operation is completely symmetric. But the result of the operation is just a number that must be normalized somehow. (At the end the distance has to be in the interval [0, CRFeature maxNormDistance]). The problem is that the not yet normalized distance value can vary a lot depending on the number of direction vectors in the two features. That's the reason why the normalization process can't be done independent of the two currently compared features!! Usually one feature is compared to all the features in a dictionary. To be fair, the normalization process has to be the same for one dictionary lookup and thus this process can only use properties of the feature that is looked up. As a consequence, this distance is not symmetric ((A dist: B) ~= (B dist: A)). Whenever a new feature is looked up in a dictionary, the asymmetric stroke distance is used. For all the distances between features inside the dictionary, the symmetric stroke distance is used. (It would be really confusing if the distance from 'a' to 'b' would be different than the one from 'b' to 'a'!!)" ^ self sameClassStrokeDistance: aCRFeature max: (self maxStrokeDistance min: aCRFeature maxStrokeDistance)! ! !CRStrokeFeature class methodsFor: 'accessing' stamp: 'NS 8/14/2000 15:25'! strokeReferenceFeature1 ^ StrokeReferenceFeature1! ! !CRStrokeFeature class methodsFor: 'class initialization' stamp: 'NS 8/14/2000 15:25'! initialize " CRStrokeFeature initialize " "Build a feature that consists only of one point. Therefore the absolute storke-distance between this reference feature and another feature are approximately the costs to build the other feature from scratch. This value is then used to normalize the absolute stroke-distance." StrokeReferenceFeature1 _ self new startPoint: 0 @ 0; directionVectors: (Array with: 0 @ 1); calcAndSetSecondaryValues.! ! !CRStrokeFeature class methodsFor: 'instance creation' stamp: 'NS 8/14/2000 15:25'! new ^ super new initialize! ! !CRStrokeFeature class methodsFor: 'constants' stamp: 'NS 8/14/2000 15:25'! capturedPointsReduceAngle ^ 30! ! !CRTempDisplayProperties methodsFor: 'accessing' stamp: 'NS 7/31/2000 12:14'! name: aSymbolOrString makeDistinct: aBoolean name _ aSymbolOrString isString ifTrue: [aSymbolOrString asSymbol] ifFalse: [aSymbolOrString].! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! arrowDown ^ self value: 31! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! arrowLeft ^ self value: 28! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! arrowRight ^ self value: 29! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! arrowUp ^ self value: 30! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:19'! delete ^ self value: 127! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:21'! end ^ self value: 4! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:21'! home ^ self value: 1! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:19'! insert ^ self value: 5! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:20'! pageDown ^ self value: 12! ! !Character class methodsFor: 'accessing untypeable characters' stamp: 'NS 7/11/2000 09:21'! pageUp ^ self value: 11! ! CRStrokeFeature initialize! !CRLookupItem reorganize! ('char accessing' charType correspondingKeystrokes correspondingMouseEventsHand:position:buttons: correspondsToKeystrokes correspondsToMouseEvents evaluateCodeIn: isCode isCommand isStrokes normalizedChar) ('accessing' char distance feature) ('initialize-release' initializeFeature:char:distance:) ('comparing' < <= =) ('printing' printOn:) ! CRDisplayProperties initialize! CRDictionary initialize! !CRDictionary reorganize! ('private' addDistances:to:weight:eliminatedDist: addIndirectParentsTo: addToInvertedDictionaryFeature:char: capturedPoints: createDistinctName:collection: createParentsCollection dictionary dictionary: eliminateDistancesFrom:conditionCollection:limit:minSize:eliminatedDist: invertedDictionary invertedDictionary: localLookup:minResultSize:symmetric:maxSpeed: parameters: putAcuteAngleDistancesFrom:to:into:eliminateCollection:limit: putAngleDistancesFrom:to:into:eliminateCollection:limit: putShapeDistancesFrom:to:into:eliminateCollection:limit: putSizeDistancesFrom:to:into:eliminateCollection:limit: putStartEndDistancesFrom:to:into:eliminateCollection:limit: putStrokeDistancesFrom:to:into:eliminateCollection:limit: putStrokeSizeDistancesFrom:to:into:eliminateCollection:limit: putSymmetricStrokeDistancesFrom:to:into:eliminateCollection:limit: putTimeDistancesFrom:to:into:eliminateCollection:limit: reduceCapturedPoints removeCapturedPoints replaceBy:) ('accessing dictionaries' addChild: atChar: atChar:ifAbsent: atCharString: atFeature: atFeature:ifAbsent: atFeature:put: charSize featureSize featuresAndCharsDo: includesChar: includesFeature: indirectParentIncludesFeature: isEmpty notEmpty removeChar: removeChar:ifAbsent: removeFeature: removeFeature:ifAbsent: renameChar:to: selfOrIndirectParentIncludesFeature: size updateInvertedDictionary) ('views' asCloseableMorph asMorph browserAppModel newBrowser newCloseableMorph newMorph openBrowser openMorph) ('updating' update:) ('accessing' addParent: addParentName: capturedPoints exportedName exportedName: exportedNameAsString fillFeaturesCache getParentsAndErrorForString: indirectParentCount indirectParents lookup: lookup:minResultSize: lookup:minResultSize:symmetric: lookup:minResultSize:symmetric:maxSpeed:includeParents: maxMultipleDefinitions maxMultipleDefinitions: name name: name:makeDistinct: nameAsString parameters parentCount parentFromName: parentMenuSelector: parents parents: parentsAsString parentsFromString: recalculate releaseFeaturesCache removeAllParents removeParent: storeAllCapturedPoints storeNoCapturedPoints storeReducedCapturedPoints totalMaxMultipleDefinitions totalParentCount totalSize updateMaxMultipleDefinitions updateMaxMultipleDefinitions:) ('initialize-release' initialize) ('copying' copy) ('objects from disk' basicStoreDataOn: readDataFrom:size: storeDataOn: storeParents storeParents: storedName storedName:) ('testing' hasAllCapturedPoints hasNoCapturedPoints hasParents hasReducedCapturedPoints isActive) ('comparing' <) ! !CRChar class reorganize! ('instance creation' string:) ! !CRChar reorganize! ('testing' isCode isCommand isStrokes) ('printing' printOn:) ('comparing' < <= = > >= hash) ('accessing' codeString correspondingKeystrokes correspondingMouseEventsHand:position:buttons: correspondsToKeystrokes correspondsToMouseEvents evaluateCodeIn: headerString headerString: normalized string string: type) ! !AGenieIntroduction reorganize! ('read class comment') !