//
//  MSInkableView.h
//  MSInkFramework
//
//  Created by Raleigh Ledet on Wed Sep 24 2003.
//  Copyright (c) 2003-2008 Mage Software. All rights reserved.
//

#import <Cocoa/Cocoa.h>
#import <Carbon/Carbon.h>
#import "MSInkFindPanel.h"

/**************************************************************************************************
*****************************************************
******************* MSInkableView *******************

An MSInkableView, (sometimes refered to as inkableview) is a Cocoa view that the user can draw
into. This drawing is refered to as "Inking" because Apple's handwriting recognition Ink(well) is
used to recognize what the user is writing. This allows the user to make free form notes that are
searchable. In other words, the user can search their own handwriting.

*****************************
********* Ink Theory ********
The inkableview's abilites are closely tied to Inkwell's abilites and how it works. I suggest that
you attach a Wacom graphics tablet and open the Ink System Preference Pane. Click on every tab
and every button. Read all the text! That should give you a decent handle on what Ink can do, but
here is a crib sheet of important info.

As of 10.4.9 Ink:
* Only recognizes PRINTED ROMAN CHARACTERS! (the user can turn on an option to recognize European glyphs)
* Ink only recognizes things written HORIZONTALLY! (to be more precise, parallel to the menu bar!)
* Gestures are very cool! Use them!
* Avoid touching characters together when inking. This will help recognition accuracy
* Ink must be turned on for the InkableView to work! (note, "write anywhere" can be on or off.
* As Inkwell handwriting improves, the MSInkableView gets those enhancements for free. In other
  words, if Inkwell started recognizing cursive or things written at an angle, so too would the
  MSInkableView. If you could programmatically add your own list of technical words to the
  Inkwell dictionary, it would help recognition accuracy for your technical field.
* FILE HANDWRITING RECOGNITION BUGS and ENHANCEMENTS WITH APPLE @ http://radar.apple.com

How Ink Works:
As the user Inks in the inkable view, Inkwell is recording thier strokes. When the user pauses for
an adjustable amount of time, Inkwell recognizes everything they just wrote. A stroke is the from
mouse down until mouse up. The group of strokes that ink performs recognition on as a set are
called a phase. An ink phrase is not just terminated by inactivity, it it can also be terminated by
leaving the proximity of the tablet (a partiuclar problem for integrated LCD tablets like the
Cintiq) or leaving the bounds of the inkableview.

When a phase consists of only one stoke, Inkwell will try to determine if that stroke is a gesture.
Gestures can only be comprised of a one stroke phrease. If it is a gesture, a gesture event is sent
to the inkableview.

Normally, a phrase consits of many strokes. Inkwell attempts to combine strokes that belong to the
same word. Each word (its strokes, and a list of possible recognitions) are then sent to the
MSInkableView. The MSInkableView uses that info to creates an appropriate MSInkObject.

The user can bypass Inking by holding the cursor still for a moment after initially clicking before
a phrase has started. Doing so turns Ink off at the Inkwell layer, the pen then acts like a recular
mouse. That is the pen performs selecting and dragging instead of inking. This allows the user to make
selctions and re-arrange ink objects on the page without having to switch the MSInkableView into
edit mode.

Wacom is currently the only known tablet company that follws the OS X tablet specification.
Particularly so when it comes to proximity events. Ink is fairly forgiving. MSInkableView is not.
Thus, MSInkableView really only works with a Wacom graphics tablet.

*****************************
*** MSInkable View Theory ***
The MSInkableView is modeled somewhat after NSTableView. That is, it requires a data source. A major
difference though, is that the data source must also store the layout of the data. Since inking is
free form, there is no structured layout that the inkableview can enforce (or predict).

Like an NSTableView, You can optionally assign a delegate to an MSInkableView. The delegate comes
in handy to do some things like modiy contextual menus, or draw the recognized text on top of the
ink image.

You must also supply an MSGraphicsEngine. The graphics engine manages the various brushes that
eventually do the actual drawing on the screen. You set the current drawing options by selecting
a brush and modifying the brush's attributes.

Bindings
You can bind some of the MSInkableView attributes, but not the data.
See the sample code for an example of how to work around this.

*/


@class MSGraphicsEngine, MSBrush, MSMutableInkData, MSTextArea;

typedef enum
{
	MSInkableViewModeInvalid = 0,			// Invalid, don't use this.
	MSInkableViewModeInk	= 1,				// Ink mode: Objects handwriting recognition is performed.
	MSInkableViewModeSketch = 1 << 1,	// Sketch mode: No handwriting recognition. All strokes in an ink phrase are recognized as one sketch object.
	MSInkableViewModeEdit   = 1 << 2,	// Edit Mode: Selection and moving of objects
	MSInkableViewModeText	= 1 << 3,	// Text Mode: A text field is inserted at the click point allowing the user to type instead of inking.
	MSInkableViewModeErase  = 1 << 4		// Erase Mode: Deletes all objects that fall inside the bounds of the erase stroke.
} MSInkableViewMode;

@interface MSInkableView : NSView
{
	IBOutlet id			_delegate;
	IBOutlet id			_dataSource;
	IBOutlet MSGraphicsEngine *_graphicsEngine;

@private	
	MSInkableViewMode   _preferredInkingMode;
	MSInkableViewMode   reserved1;
	BOOL				_recognizeGesturesDuringInking;
	BOOL				_recognizeGesturesDuringSketching;
	BOOL				_penInProximity;
	
	BOOL				_isSelectable;
	BOOL				_isSelecting;
	BOOL				_isEditable;
	BOOL				_requireFocusBeforeInking;
	NSMutableArray		*_selectedObjects;
	UInt32				_findOptions;
	MSInkFindGranularity _findGranularity;
	
	BOOL					_drawsBackground;
	NSColor				*_backgroundColor;
	
	NSFont				*_inkTextFont;
	NSTextView			*_textEditField;
	MSTextArea			*_textArea;
	
	MSMutableInkData	*_inkingData;
	NSRange				_undrawnInkingRange;
	NSPoint				_lastInkingPoint;
	TabletPointRec		_lastInkingTabletRec;
	NSImage				*_inkingImage;
	NSImageRep			*_inkingImageRep;
	NSTimer				*_inkingDrawTimer;
	NSRect				_inkingDirtyRect;
	NSTrackingRectTag   _inkTrackingRectTag;
	BOOL				_mouseInsideView;
	
	BOOL				_recognizedTextDefaultDrawMode;
	
	BOOL				_reloadData;
	
	NSPoint			_dragStartPoint;
	NSPoint			_dragOffset;
	NSRect			_resizeRect;
	int				_resizeIndex;
	BOOL				_isDragging;
	BOOL				_isInking;
	BOOL				_isResizing;
	
	BOOL				_performInkBug4688004Workaround;
	NSTimer			*_inkBugCursorRefreshTimer;
	
	NSMutableDictionary	*_drawingCells;
}

- (id)delegate;
- (void)setDelegate:(id)delegate;

- (id)dataSource;
- (void)setDataSource:(id)dataSource;

// The Graphics Engine is what actualy does the drawing.
- (void)setGraphicsEngine:(MSGraphicsEngine*)engine;
- (MSGraphicsEngine*)graphicsEngine;

// On intel Macs 10.4.x, After ink is recognized, Inkell corrupts the cursor Icon. The MSInkableView tries to work around this by using a timer to reset the cursor icon. This is set to YES by default.
- (void)setPerformInkBug4688004Workaround:(BOOL)perform;
- (BOOL)performInkBug4688004Workaround;

// The inking modes are described above (MSInkableViewMode)
// set/get the preferred inking mode. The actual inking mode used may be overridden by the current brush. For example, the eraser overrides this setting and only does erasing!
- (void)setInkingMode:(MSInkableViewMode)mode;
- (MSInkableViewMode)inkingMode;

// Returns the actual inking mode considering the current brush's override and the modifer overrides
- (MSInkableViewMode)actualInkingMode;

// Gestures are a quick an easy way to delete, select and modiy objects in the MSInkableView. However, they can sometimes get in the way. The following methods allow you to set if the MSInkableView should recognize what Inkwell considers a gesture, or to treat the gesture as a one stroke object.
// MSInkableView recognizes the following gestures: Clear, Delete, Join, Select All, Cut, Copy, Paste
// By default, Inking recognizes gestures (It is rare that a user tries to draw a one stroke word.)
// By deafult, Sketching does not recognize gestures. While sketching, it's more likely that the user may draw a one line object (box, circle, underline, etc..).
- (void)setRecognizeGesturesDuringInking:(BOOL)allow;
- (BOOL)recognizeGesturesDuringInking;
- (void)setRecognizeGesturesDuringSketching:(BOOL)allow;
- (BOOL)recognizeGesturesDuringSketching;

// Set's if the MSInkableView is editiable or not. If isEditable is set to NO, then the user is not allowed to ink in the view or modify any of the items. They are allowed, however, to select items and copy them to the clipboard depending on the isSelectable attribute.
- (BOOL)isEditable;
- (void)setIsEditable:(BOOL)editable;

// Should Inking only be allowed when the MSInkableView is the first responder?
// When set to YES, the user must click or oterwise, select the MSInkableView before they can ink in it. When set to NO, the MSInkableView will assume firstResponder status and allow inking with the very first stroke.
// On the one hand, user's expect to be able to Ink right away, and may be confused that thier first stroke does not draw. On the other hand, user's may find it hard to use Inkwell's Write Anywhere feature to hand wright values for other text fields. If the MSInkableView is large enough so that it is hard to avoid when trying to use the Inkwell yellow write anywhere pad, a setting here of NO will cause early termination of the user's Ink, or may even take focus away from the text field and start inking directy in the MSInkableView.
// Default. YES.
- (BOOL)requireFocusBeforeInking;
- (void)setRequireFocusBeforeInking:(BOOL)requireFocus;

// Can the user select objects in the MSInkableView. Note: This does not prevent progromatic selection.
- (BOOL)isSelectable;
- (void)setIsSelectable:(BOOL)selectable;

// return an array of the currently selected objects.
- (NSArray*)selection;

// Modifies the selection based on the supplied range. The MSInkableView will find the corresponding ink objects from the dataSource and add them to the selection.
- (void)setSelection:(NSRange)newSelection;
- (void)addToSelection:(NSRange)newSelection;

// When in inking mode (MSInkableViewModeInk), MSInkObject are created in response to user inking. These objects can either draw themselves with either the brush settings used when Inked, or as text that expands to fit inked area. The MSInkableView applies the setting here to newly created MSInkObjects. See MSInkOBject.h for the different draw modes.
// Defaults to: kMSInkObjecDrawTypeInk 
- (UInt32)recognizedTextDefaultDrawMode;
- (void)setRecognizedTextDefaultDrawMode:(UInt32)defaultDrawMode;

// The MSInkFindPanel options to use when doing a find search. See MSInkFindPanel.h.
- (UInt32)findOptions;
- (void)setFindOptions:(UInt32)options;

// The MSInkFindPanel enum to use that describes the granularity of a find search. See MSInkFindPanel.h.
- (MSInkFindGranularity)findGranularity;
- (void)setFindGranularity:(MSInkFindGranularity)granularity;

// Shoud the MSInkableView draw the background.
- (BOOL)drawsBackground;
- (void)setDrawsBackground:(BOOL)doesDraw;

// If the MSInkableView should draw it's background, this is the color it uses.
- (NSColor*)backgroundColor;
- (void)setBackgroundColor:(NSColor*)newColor;

// The deafult font to apply to text fields when the user clicks in the content area when the MSInkableView is in Text mode (MSInkableViewModeText).
- (NSFont*)defaultInkTextFont;
- (void)setDefaultInkTextFont:(NSFont*)font;

// Force the MSInkableView to reload the data from the dataSource.
- (void)reloadData;

// Manage the cells used to draw the actual values.
// MSInkableView provied default cells for MSInkData, MSAttributedInkData, MSInkObject, NSImage, NSString
// If no exact class match is found for the object, then -isKindOfClass: is used to dtermine which cell to use.
- (void)setCell:(NSCell*)cell forClass:(Class)class;
- (NSCell*)cellForClass:(Class)class;

@end

@interface MSInkableView (LayoutSupport)
// Returns an array of the objects in the MSInkableView that are inside or intersect the rect
-(NSArray*)objectsInRect:(NSRect)frame;

// Retuns the index of the best object in the MSInkableView that contains the point.
// Returns NSNotFound if no object is found, 
// outObj is optional pointer used to return the object found.
-(UInt32)bestObject:(id*)outObj atPoint:(NSPoint)point;
@end

// This is the protocal that an MSInkableView Data Source should implement.
// Note: It is the responsability for the Data Source to keep track of the layout of the Ink objects. The ink objects themselves have no concept of layout.
// Hint: One way to implement multiple pages is to change the data dource whenever the user switches to another page.
@interface NSObject(MSInkableViewDataSource)
- (int)numberOfObjectsInInkableView:(MSInkableView *)inkableView;
- (NSPoint)inkableView:(MSInkableView *)inkableView locationOfObjectAtIndex:(int)idx;
- (NSRect)inkableView:(MSInkableView *)inkableView boundsOfObjectAtIndex:(int)idx;
- (NSSize)inkableView:(MSInkableView *)inkableView sizeOfObjectAtIndex:(int)idx;
- (void)inkableView:(MSInkableView *)inkableView setLocation:(NSPoint)loc ofObjectAtIndex:(int)idx;
- (id)inkableView:(MSInkableView *)inkableView objectAtIndex:(int)idx;

- (void)inkableView:(MSInkableView *)inkableView addObject:(id)obj atLocation:(NSPoint)loc;
- (void)inkableView:(MSInkableView *)inkableView insertObject:(id)obj
												 atLocation:(NSPoint)loc
												 atIndex:(UInt32)idx;
- (void)inkableView:(MSInkableView *)inkableView removeObject:(id)obj;
- (void)inkableView:(MSInkableView *)inkableView removeObjectsInArray:(NSArray*)objs;


// New Methods - It is strongly suggested that you implement these methods. Though, because of backwards compatability they are not required.
//
/////// Added in version 1.5.0 ///////
//

// Resize - The existence of this method will turn on the resize capability.
- (void)inkableView:(MSInkableView *)inkableView setSize:(NSSize)size ofObjectAtIndex:(int)idx;

//
//
// Optional methods - Unlike New Methods, these methods are completely optional.
//
//
// Drag and drop methods 
- (BOOL)inkableView:(MSInkableView *)inkableView writeObjects:(NSArray*)objs toPasteboard:(NSPasteboard*)pboard;
- (NSDragOperation)inkableView:(MSInkableView *)inkableView validateDrop:(id <NSDraggingInfo>)info;
- (BOOL)inkableView:(MSInkableView *)inkableView acceptDropFromPasteBoard:(NSPasteboard*)pboard atLoc:(NSPoint)loc;
@end

// These are the methods that a delegate of MSInkableVIew may choose to reposnd to. The delegate does not need to implement all of these methods.
@interface NSObject(MSInkableViewDelegateMethods)
- (void)selectionDidChange:(id)sender;

// The size of the MSInkableView itself changed.
- (void)inkableViewSizeDidChange:(MSInkableView*)sender;

// The MSInkableView caused some change to the data. Since you cannot use bindings to bind the MSInkableView to any data, this is a good trap point to manually do things yourself.
- (void)inkableViewDidChangeData:(MSInkableView *)inkableView;

// This method is called right before an inkObject is drawn. The MSInkableView has locked drawing focus. This is a good time to draw something behind the inkObject.
- (void)inkableView:(MSInkableView *)inkableView willDisplayObject:(id)obj	 atLoc:(NSPoint)loc;

// This method is called right after an inkObject is drawn. The MSInkableView has locked drawing focus. This is a good time to draw something on top of an inkObject. (Say, for example, the recognized word!)
- (void)inkableView:(MSInkableView *)inkableView didDisplayObject:(id)obj atLoc:(NSPoint)loc;

// This method is called right before the MSInkableView starts dragging the selected objects.
// Note: If the draggin operation turns into a drag-n-drop operation, didEndDraggingObjects is immeditaly called
- (void)inkableView:(MSInkableView *)inkableView willBeginDraggingObjects:(NSArray*)objects;

// This method is called right before the MSInkableView ends dragging the selected objects.
// Note: If the draggin operation turns into a drag-n-drop operation, didEndDraggingObjects is immeditaly called
- (void)inkableView:(MSInkableView *)inkableView didEndDraggingObjects:(NSArray*)objects;

// This method is called right before the MSInkableView display's a contextual menu.
- (void)inkableView:(MSInkableView *)inkableView willDisplayMenu:(NSMenu*)menu;

// This method allows the delegate to substitue a custom cursor for cursor changes. When the inkableView will display the Arrow, I-Beam, or Hand cursor, the brush is nil. Actually, this call is made when setting up the cursor rects, not when the cursor will actually be changed. Note: If the inkableView is not the active control and does not require focus before inking, it does not modify the cursor, and thus does not make this call.
- (void)inkableView:(MSInkableView *)inkableView willDisplayCursor:(NSCursor**)cursorPtr forBrush:(MSBrush*)brush;

// This method is sent right before searching begins.
- (BOOL)shouldPerformSearchForInkableView:(MSInkableView *)inkableView forward:(BOOL)forward wrapps:(BOOL)wrap;

// This method is called when searching has reached the end of the data source objects and still has not found a match. This is most useful to chagne data sources for mutliple pages.
- (BOOL)continuePerformSearchForInkableView:(MSInkableView *)inkableView forward:(BOOL)forward wrapps:(BOOL)wrap;

// This method is called right after searching has completed either due to finding a match, or running out of objects to search through.
- (void)didPerformSearchForInkableView:(MSInkableView *)inkableView forward:(BOOL)forward wrapps:(BOOL)wrap;
@end

// MSInkableView responds to the following actions typically found on the Edit menu.
@interface MSInkableView (EditMenuAdditions)
- (BOOL)performFindInRange:(NSRange)range forward:(BOOL)forward;
- (IBAction)performFindPanelAction:(id)sender;
- (IBAction)cut:(id)sender;
- (IBAction)copy:(id)sender;
- (IBAction)paste:(id)sender;
- (IBAction)delete:(id)sender;
- (IBAction)selectAll:(id)sender;

// These are found on the Edit menu, but they may come in handy.

// Converts any selected Sketch objects into Ink objects. This includes sending the inking data back through Inkwell to re-recognize it.
- (IBAction)makeSelectionInkObject:(id)sender;

// Convert any selected Ink objects into Sketch objects discarding any recognition associated with the ink strokes.
- (IBAction)makeSelectionSketch:(id)sender;

// Convert any selected Ink objects such that each stroke is turned into its own Sketch object
- (IBAction)explodeSelection:(id)sender;

// Combine any selected Ink and Sketch objects together into one object. If any of the objects are Ink objects, the resulting object is an Ink object, otherwise the resulting object is a Sketch object. If the resulting object is an Ink Object, then the ink data for the new object is sent back through Inkwell for recognition.
- (IBAction)joinSelection:(id)sender;

@end

// MSInk Paste board types

// An aray of Ink Objects and thier locations.
extern NSString *MSInkArrayPboardType;

// An NSPoint decribing the offest of an InkObject in an InkArray from the base point.
// Deprecated: This works great for pasteboards, but is not endian safe when savinf to files. Use MSInkLocXPboardType, MSInkLocYPboardType instead!
extern NSString *MSInkLocPboardType;

// The horizontal offest of an InkObject in an InkArray from the base point.
extern NSString *MSInkLocXPboardType;

// The vertical offest of an InkObject in an InkArray from the base point.
extern NSString *MSInkLocYPboardType;

//// Contextual Menu Options ////
extern NSString *kMakeSketchString;
extern NSString *kMakeInkObjString;
extern NSString *kJoinInkObjsString;
extern NSString *kDrawAsText;
extern NSString *kDrawAsInk;
