diff --git a/AppKit/AppKit.j b/AppKit/AppKit.j index 0f82e3e88..b9de2065b 100644 --- a/AppKit/AppKit.j +++ b/AppKit/AppKit.j @@ -101,6 +101,7 @@ @import "CPTabView.j" @import "CPText.j" @import "CPTextField.j" +@import "CPTextView.j" @import "CPTokenField.j" @import "CPToolbar.j" @import "CPToolbarItem.j" diff --git a/AppKit/CPColor.j b/AppKit/CPColor.j index 2e3d1d31a..813a0995a 100644 --- a/AppKit/CPColor.j +++ b/AppKit/CPColor.j @@ -100,8 +100,10 @@ var cachedBlackColor, + (CPDictionary)themeAttributes { return @{ - @"alternate-selected-control-color": [CPNull null], - @"secondary-selected-control-color" : [CPNull null] + @"alternate-selected-control-color": [CPNull null], + @"secondary-selected-control-color": [CPNull null], + @"selected-text-background-color": [CPNull null], + @"selected-text-inactive-background-color": [CPNull null] }; } @@ -498,6 +500,16 @@ var cachedBlackColor, return [[CPColor alloc] _initWithCSSString: aString]; } ++ (CPColor)selectedTextBackgroundColor +{ + return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-background-color"] || [CPColor colorWithHexString:"99CCFF"]; +} + ++ (CPColor)_selectedTextBackgroundColorUnfocussed +{ + return [[self _cachedThemeColor] valueForThemeAttribute:@"selected-text-inactive-background-color"] || [CPColor colorWithHexString:"CCCCCC"]; +} + /* @ignore */ - (id)_initWithCSSString:(CPString)aString { diff --git a/AppKit/CPControl.j b/AppKit/CPControl.j index 26aaca67d..bdb657ce2 100644 --- a/AppKit/CPControl.j +++ b/AppKit/CPControl.j @@ -25,7 +25,7 @@ @import "CPFont.j" @import "CPShadow.j" -@import "CPView.j" +@import "CPText.j" @import "CPKeyValueBinding.j" @import "CPTrackingArea.j" diff --git a/AppKit/CPEvent.j b/AppKit/CPEvent.j index d1763e8a1..42dbad35b 100644 --- a/AppKit/CPEvent.j +++ b/AppKit/CPEvent.j @@ -28,7 +28,6 @@ @import "CPCompatibility.j" @import "CGGeometry.j" -@import "CPText.j" @import "CPTrackingArea.j" @class CPTextField @@ -36,6 +35,9 @@ @class CPGraphicsContext @global CPApp +@global CPNewlineCharacter +@global CPCarriageReturnCharacter +@global CPEnterCharacter @typedef DOMEvent @typedef CPEventType diff --git a/AppKit/CPFont.j b/AppKit/CPFont.j index 24e8190cd..90bc28826 100644 --- a/AppKit/CPFont.j +++ b/AppKit/CPFont.j @@ -24,6 +24,7 @@ @import @import "CPView.j" +@import "CPFontDescriptor.j" CPFontDefaultSystemFontFace = @"Arial, sans-serif"; CPFontDefaultSystemFontSize = 12; @@ -433,6 +434,43 @@ following: @end +@implementation CPFont(DescriptorAdditions) + +- (id)_initWithFontDescriptor:(CPFontDescriptor)fontDescriptor +{ + var aName = [fontDescriptor objectForKey: CPFontNameAttribute] , + aSize = [fontDescriptor pointSize], + isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait, + isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait; + + return [self _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO]; +} + ++ (CPFont)fontWithDescriptor:(CPFontDescriptor)fontDescriptor size:(float)aSize +{ + var aName = [fontDescriptor objectForKey: CPFontNameAttribute], + isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait, + isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait; + + return [self _fontWithName:aName size:aSize || [fontDescriptor pointSize] bold:isBold italic:isItalic]; +} + +- (CPFontDescriptor)fontDescriptor +{ + var traits = 0; + + if ([self isBold]) + traits |= CPFontBoldTrait; + + if ([self isItalic]) + traits |= CPFontItalicTrait; + + return [[CPFontDescriptor fontDescriptorWithName:_name size:_size] fontDescriptorWithSymbolicTraits:traits]; +} + +@end + + var CPFontNameKey = @"CPFontNameKey", CPFontSizeKey = @"CPFontSizeKey", CPFontIsBoldKey = @"CPFontIsBoldKey", diff --git a/AppKit/CPFontManager.j b/AppKit/CPFontManager.j index b470025ba..ffffe6464 100644 --- a/AppKit/CPFontManager.j +++ b/AppKit/CPFontManager.j @@ -22,9 +22,12 @@ @import +@import "CPControl.j" @import "CPFont.j" +@import "CPFontDescriptor.j" @global CPApp +@class CPFontPanel CPItalicFontMask = 1 << 0; CPBoldFontMask = 1 << 1; @@ -41,7 +44,20 @@ CPUnitalicFontMask = 1 << 24; var CPSharedFontManager = nil, - CPFontManagerFactory = Nil; + CPFontManagerFactory = nil, + CPFontPanelFactory = nil; + +/* + modifyFont: sender's tag +*/ +CPNoFontChangeAction = 0; +CPViaPanelFontAction = 1; +CPAddTraitFontAction = 2; +CPSizeUpFontAction = 3; +CPSizeDownFontAction = 4; +CPHeavierFontAction = 5; +CPLighterFontAction = 6; +CPRemoveTraitFontAction = 7; /*! @ingroup appkit @@ -59,6 +75,8 @@ var CPSharedFontManager = nil, BOOL _multiple @accessors(getter=isMultiple, setter=setMultiple:); CPDictionary _activeChange; + + unsigned _fontAction; } // Getting the Shared Font Manager @@ -83,6 +101,15 @@ var CPSharedFontManager = nil, { CPFontManagerFactory = aClass; } +/*! + Sets the class that will be used to create the application's + Font panel. +*/ ++ (void)setFontPanelFactory:(Class)aClass +{ + CPFontPanelFactory = aClass; +} + - (id)init { @@ -210,6 +237,7 @@ var CPSharedFontManager = nil, { var tag = [sender tag]; _activeChange = tag === nil ? @{} : @{ @"addTraits": tag }; + _fontAction = CPAddTraitFontAction; [self sendAction]; } @@ -219,6 +247,185 @@ var CPSharedFontManager = nil, return [CPApp sendAction:_action to:_target from:self]; } + +/*! + This method open the font panel, create it if necessary. + @param sender The object that sent the message. +*/ +- (CPFontPanel)fontPanel:(BOOL)createIt +{ + var panel = nil, + panelExists = [CPFontPanelFactory sharedFontPanelExists]; + + if ((panelExists) || (!panelExists && createIt)) + panel = [CPFontPanelFactory sharedFontPanel]; + + return panel; +} + +/*! + Convert a font to have the specified Font traits. The font is unchanged expect for the specified Font traits. + Using CPUnboldFontMask or CPUnitalicFontMask will respectively remove Bold and Italic traits. + @param aFont The font to convert. + @param fontTrait The new font traits mask. + @result The converted font or \c aFont if the conversion failed. +*/ +- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)fontTrait +{ + var attributes = [[[aFont fontDescriptor] fontAttributes] copy], + symbolicTrait = [[aFont fontDescriptor] symbolicTraits]; + + if (fontTrait & CPBoldFontMask) + symbolicTrait |= CPFontBoldTrait; + + if (fontTrait & CPItalicFontMask) + symbolicTrait |= CPFontItalicTrait; + + if (fontTrait & CPUnboldFontMask) /* FIXME: this only change CPFontSymbolicTrait what about CPFontWeightTrait */ + symbolicTrait &= ~CPFontBoldTrait; + + if (fontTrait & CPUnitalicFontMask) + symbolicTrait &= ~CPFontItalicTrait; + + if (fontTrait & CPExpandedFontMask) + symbolicTrait |= CPFontExpandedTrait; + + if (fontTrait & CPSmallCapsFontMask) + symbolicTrait |= CPFontSmallCapsTrait; + + if (![attributes containsKey:CPFontTraitsAttribute]) + [attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait] + forKey:CPFontSymbolicTrait] + forKey:CPFontTraitsAttribute]; + else + [[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait] + forKey:CPFontSymbolicTrait]; + + return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0]; +} + +/*! + Convert a font to not have the specified Font traits. The font is unchanged expect for the specified Font traits. + @param aFont The font to convert. + @param fontTrait The font traits mask to remove. + @result The converted font or \c aFont if the conversion failed. +*/ +- (CPFont)convertFont:(CPFont)aFont toNotHaveTrait:(CPFontTraitMask)fontTrait +{ + var attributes = [[[aFont fontDescriptor] fontAttributes] copy], + symbolicTrait = [[aFont fontDescriptor] symbolicTraits]; + + if ((fontTrait & CPBoldFontMask) || (fontTrait & CPUnboldFontMask)) /* FIXME: see convertFont:toHaveTrait: about CPFontWeightTrait */ + symbolicTrait &= ~CPFontBoldTrait; + + if ((fontTrait & CPItalicFontMask) || (fontTrait & CPUnitalicFontMask)) + symbolicTrait &= ~CPFontItalicTrait; + + if (fontTrait & CPExpandedFontMask) + symbolicTrait &= ~CPFontExpandedTrait; + + if (fontTrait & CPSmallCapsFontMask) + symbolicTrait &= ~CPFontSmallCapsTrait; + + if (![attributes containsKey:CPFontTraitsAttribute]) + [attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait] + forKey:CPFontSymbolicTrait] + forKey:CPFontTraitsAttribute]; + else + [[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait] + forKey:CPFontSymbolicTrait]; + + return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0]; +} + +/*! + Convert a font to have specified size. The font is unchanged expect for the specified size. + @param aFont The font to convert. + @param aSize The new font size. + @result The converted font or \c aFont if the conversion failed. +*/ +- (CPFont)convertFont:(CPFont)aFont toSize:(float)aSize +{ + var descriptor = [aFont fontDescriptor]; + + return [[aFont class] fontWithDescriptor: descriptor size:aSize] +} + +- (void)orderFrontFontPanel:(id)sender +{ + [[self fontPanel:YES] orderFront:sender]; +} + +- (void)modifyFont:(id)sender +{ + _fontAction = [sender tag]; + [self sendAction]; + + if (_selectedFont) + [self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO]; +} + +/*! + This method causes the receiver to send its action message. + @param sender The object that sent the message. (a Font panel) +*/ +- (void)modifyFontViaPanel:(id)sender +{ + _fontAction = CPViaPanelFontAction; + if (_selectedFont) + [self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO]; + + [self sendAction]; +} + +/*! + Convert a font according to current font changes, provided by the object that initiated the font change. + @param aFont The font to convert. + @result The converted font or \c aFont if the conversion failed. +*/ +- (CPFont)convertFont:(CPFont)aFont +{ + var newFont = nil; + switch (_fontAction) + { + case CPNoFontChangeAction: + newFont = aFont; + break; + + case CPViaPanelFontAction: + newFont = [[self fontPanel:NO] panelConvertFont:aFont]; + break; + + case CPAddTraitFontAction: + newFont = aFont; + if (!_activeChange) + break; + + var addTraits = [_activeChange valueForKey:@"addTraits"]; + + if (addTraits) + newFont = [self convertFont:aFont toHaveTrait:addTraits]; + break; + + case CPSizeUpFontAction: + newFont = [self convertFont:aFont toSize:[aFont size] + 1.0]; /* any limit ? */ + break; + + case CPSizeDownFontAction: + if ([aFont size] > 1) + newFont = [self convertFont:aFont toSize:[aFont size] - 1.0]; + /* else CPBeep() :-p */ + break; + + default: + CPLog.trace(@"-[" + [self className] + " " + _cmd + "] unsupported font action: " + _fontAction + " aFont unchanged"); + newFont = aFont; + break; + } + + return newFont; +} + @end var _CPFontDetectSpan, diff --git a/AppKit/CPPasteboard.j b/AppKit/CPPasteboard.j index 4fe6f871b..4e75db8eb 100644 --- a/AppKit/CPPasteboard.j +++ b/AppKit/CPPasteboard.j @@ -44,6 +44,7 @@ CPStringPboardType = @"CPStringPboardType"; CPURLPboardType = @"CPURLPboardType"; CPImagesPboardType = @"CPImagesPboardType"; CPVideosPboardType = @"CPVideosPboardType"; +CPRTFPboardType = @"CPRTFPboardType"; UTF8PboardType = @"public.utf8-plain-text"; diff --git a/AppKit/CPStringDrawing.j b/AppKit/CPStringDrawing.j index 6c3ce7ce0..cbffafb85 100644 --- a/AppKit/CPStringDrawing.j +++ b/AppKit/CPStringDrawing.j @@ -24,9 +24,15 @@ @import "CGGeometry.j" @import "CPPlatformString.j" +@import "CPFont.j" +@import "CPCompatibility.j" -var CPStringSizeWithFontInWidthCache = {}; +var CPStringSizeWithFontInWidthCache = [], + CPStringSizeWithFontHeightCache = [], + CPStringSizeMeasuringContext, + CPStringSizeIsCanvasSizingInvalid, + CPStringSizeDidTestCanvasSizingValid; CPStringSizeCachingEnabled = YES; @@ -53,20 +59,71 @@ CPStringSizeCachingEnabled = YES; return [self sizeWithFont:aFont inWidth:NULL]; } +- (void) _initializeStringSizing +{ +#if PLATFORM(DOM) + CPStringSizeIsCanvasSizingInvalid = YES; + + if (CPFeatureIsCompatible(CPHTMLCanvasFeature)) + { + var aFont = [CPFont systemFontOfSize:12.0]; + + if (!CPStringSizeMeasuringContext) + CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate(); + + CPStringSizeMeasuringContext.font = [aFont cssString]; + var teststring = "0123456879abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,.-()"; + CPStringSizeIsCanvasSizingInvalid = ABS([CPPlatformString sizeOfString:teststring withFont:aFont forWidth:0].width - CPStringSizeMeasuringContext.measureText(teststring).width) > 2; + } +#endif +} + - (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth { + var size; + +#if PLATFORM(DOM) if (!CPStringSizeCachingEnabled) return [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth]; - var cacheKey = self + [aFont cssString] + aWidth, - size = CPStringSizeWithFontInWidthCache[cacheKey]; + var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self]; - if (size === undefined) + if (sizeCacheForFont === undefined) + sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = []; + + var cssString = [aFont cssString], + cacheKey = cssString + '_' + aWidth; + + size = sizeCacheForFont[cacheKey]; + + if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey)) + return CGSizeMakeCopy(size); + + if (CPStringSizeDidTestCanvasSizingValid === undefined) { - size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth]; - CPStringSizeWithFontInWidthCache[cacheKey] = size; + [self _initializeStringSizing]; + CPStringSizeDidTestCanvasSizingValid = YES; } + if (CPStringSizeIsCanvasSizingInvalid || aWidth > 0) + size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth]; + else + { + if (CPStringSizeMeasuringContext.font !== cssString) + CPStringSizeMeasuringContext.font = cssString; + + var fontHeight = CPStringSizeWithFontHeightCache[cssString]; + + if (fontHeight === undefined) + fontHeight = CPStringSizeWithFontHeightCache[cssString] = [aFont defaultLineHeightForFont]; + + size = CGSizeMake(CPStringSizeMeasuringContext.measureText(self).width, fontHeight); + } + + sizeCacheForFont[cacheKey] = size; +#else + size = CGSizeMake(0, 0); +#endif return CGSizeMakeCopy(size); } diff --git a/AppKit/CPText.j b/AppKit/CPText.j index bfbc25594..da3e47a8b 100644 --- a/AppKit/CPText.j +++ b/AppKit/CPText.j @@ -5,6 +5,13 @@ * Created by Alexander Ljungberg. * Copyright 2010, WireLoad, LLC. * + * additions from + * + * Daniel Boehringer on 8/02/2014. + * Copyright Daniel Boehringer on 8/02/2014. + * + * + * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either @@ -20,6 +27,27 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +@import + +@import "CPPasteboard.j" +@import "CPView.j" + +@global CPStringPboardType +@class CPAttributedString +@class _CPRTFParser + +@protocol CPTextDelegate + +- (BOOL)textShouldBeginEditing:(CPText)aTextObject; +- (BOOL)textShouldEndEditing:(CPText)aTextObject; +- (void)textDidBeginEditing:(CPNotification)aNotification; +- (void)textDidChange:(CPNotification)aNotification; +- (void)textDidEndEditing:(CPNotification)aNotification; + +@end + +CPParagraphSeparatorCharacter = 0x2029; +CPLineSeparatorCharacter = 0x2028; CPEnterCharacter = "\u0003"; CPBackspaceCharacter = "\u0008"; CPTabCharacter = "\u0009"; @@ -29,6 +57,7 @@ CPCarriageReturnCharacter = "\u000d"; CPBackTabCharacter = "\u0019"; CPDeleteCharacter = "\u007f"; +@typedef CPTextMovement CPIllegalTextMovement = 0; CPOtherTextMovement = 0; CPReturnTextMovement = 16; @@ -46,8 +75,274 @@ CPWritingDirectionLeftToRight = 0; CPWritingDirectionRightToLeft = 1; @typedef CPTextAlignment -CPLeftTextAlignment = 0; -CPRightTextAlignment = 1; -CPCenterTextAlignment = 2; -CPJustifiedTextAlignment = 3; -CPNaturalTextAlignment = 4; \ No newline at end of file +CPLeftTextAlignment = 0; +CPRightTextAlignment = 1; +CPCenterTextAlignment = 2; +CPJustifiedTextAlignment = 3; +CPNaturalTextAlignment = 4; + +/* + CPText notifications +*/ +CPTextDidBeginEditingNotification = @"CPTextDidBeginEditingNotification"; +CPTextDidChangeNotification = @"CPTextDidChangeNotification"; +CPTextDidEndEditingNotification = @"CPTextDidEndEditingNotification"; + +/* + CPTextView Notifications +*/ +CPTextViewDidChangeSelectionNotification = @"CPTextViewDidChangeSelectionNotification"; +CPTextViewDidChangeTypingAttributesNotification = @"CPTextViewDidChangeTypingAttributesNotification"; + +/* + FIXME: move these to CPAttributed string + Make use of attributed keys in AppKit +*/ +CPFontAttributeName = @"CPFontAttributeName"; +CPForegroundColorAttributeName = @"CPForegroundColorAttributeName"; +CPBackgroundColorAttributeName = @"CPBackgroundColorAttributeName"; +CPShadowAttributeName = @"CPShadowAttributeName"; +CPUnderlineStyleAttributeName = @"CPUnderlineStyleAttributeName"; +CPSuperscriptAttributeName = @"CPSuperscriptAttributeName"; +CPBaselineOffsetAttributeName = @"CPBaselineOffsetAttributeName"; +CPAttachmentAttributeName = @"CPAttachmentAttributeName"; +CPLigatureAttributeName = @"CPLigatureAttributeName"; +CPKernAttributeName = @"CPKernAttributeName"; + +@implementation CPText : CPView +{ + BOOL _isEditable @accessors(getter=isEditable, setter=setEditable:); + BOOL _isSelectable @accessors(getter=isSelectable, setter=setSelectable:); + BOOL _isRichText @accessors(getter=isRichText, setter=setRichText:); +} + +- (void)setSelectable:(BOOL)flag +{ + [self willChangeValueForKey:@"selectable"]; + _isSelectable = flag; + [self didChangeValueForKey:@"selectable"]; + + if (!flag) + [self setEditable:flag]; +} + +- (void)setEditable:(BOOL)flag +{ + [self willChangeValueForKey:@"editable"]; + _isEditable = flag; + [self didChangeValueForKey:@"editable"]; + + if (flag) + [self setSelectable:flag]; +} + +- (void)changeFont:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)copy:(id)sender +{ + var selectedRange = [self selectedRange]; + + if (selectedRange.length < 1) + return; + + var pasteboard = [CPPasteboard generalPasteboard]; + + // put plain representation on the pasteboad unconditionally + [pasteboard declareTypes:[CPStringPboardType] owner:nil]; + [pasteboard setString:[[self stringValue] substringWithRange:selectedRange] forType:CPStringPboardType]; +} + +- (id)_stringForPasting +{ + var pasteboard = [CPPasteboard generalPasteboard], + dataForPasting = [pasteboard stringForType:CPRTFPboardType], + stringForPasting = [pasteboard stringForType:CPStringPboardType]; + + if (dataForPasting || [stringForPasting hasPrefix:"{\\rtf1\\ansi"]) + stringForPasting = [[_CPRTFParser new] parseRTF:dataForPasting ? dataForPasting : stringForPasting]; + + if (![self isRichText] && [stringForPasting isKindOfClass:[CPAttributedString class]]) + stringForPasting = stringForPasting._string; + + return stringForPasting; +} + +- (void)paste:(id)sender +{ + var stringForPasting = [self _stringForPasting]; + + if (stringForPasting) + [self insertText:stringForPasting]; +} + +- (void)copyFont:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)delete:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (CPFont)font:(CPFont)aFont +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return nil; +} + +- (BOOL)isHorizontallyResizable +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return NO; +} + +- (BOOL)isRulerVisible +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return NO; +} + +- (BOOL)isVerticallyResizable +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return NO; +} + +- (CGSize)maxSize +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return CGSizeMake(0,0); +} + +- (CGSize)minSize +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + return CGSizeMake(0,0); +} + +- (void)pasteFont:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)scrollRangeToVisible:(CPRange)aRange +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)selectedAll:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (CPRange)selectedRange +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return CPMakeRange(CPNotFound, 0); +} + +- (void)setFont:(CPFont)aFont +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setFont:(CPFont)aFont range:(CPRange)aRange +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setHorizontallyResizable:(BOOL)flag +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setMaxSize:(CGSize)aSize +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setMinSize:(CGSize)aSize +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setString:(CPString)aString +{ + [self replaceCharactersInRange:CPMakeRange(0, [[self string] length]) withString:aString]; +} + +- (void)setUsesFontPanel:(BOOL)flag +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (void)setVerticallyResizable:(BOOL)flag +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (CPString)string +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return nil; +} + +- (void)underline:(id)sender +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +- (BOOL)usesFontPanel +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); + + return NO; +} + +@end + +var CPTextViewIsEditableKey = @"CPTextViewIsEditableKey", + CPTextViewIsSelectableKey = @"CPTextViewIsSelectableKey", + CPTextViewIsRichTextKey = @"CPTextViewIsRichTextKey"; + +@implementation CPText (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super initWithCoder:aCoder]; + + if (self) + { + [self setSelectable:[aCoder decodeBoolForKey:CPTextViewIsSelectableKey]]; + [self setEditable:[aCoder decodeBoolForKey:CPTextViewIsEditableKey]]; + [self setRichText:[aCoder decodeBoolForKey:CPTextViewIsRichTextKey]]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [super encodeWithCoder:aCoder]; + [aCoder encodeBool:_isEditable forKey:CPTextViewIsEditableKey]; + [aCoder encodeBool:_isSelectable forKey:CPTextViewIsSelectableKey]; + [aCoder encodeBool:_isRichText forKey:CPTextViewIsRichTextKey]; +} + +@end diff --git a/AppKit/CPTextView/CPFontDescriptor.j b/AppKit/CPTextView/CPFontDescriptor.j new file mode 100644 index 000000000..fb4065ddd --- /dev/null +++ b/AppKit/CPTextView/CPFontDescriptor.j @@ -0,0 +1,315 @@ +/* + * CPFontDescriptor.j + * AppKit + * + * Created by Emmanuel Maillard on 07/03/10. + * Copyright Emmanuel Maillard 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import +/* + Font descriptor dictionary keys +*/ + +/* + CPFontNameAttribute contains a CPString that specified the font name + (may be an name list like: 'Marker Felt, Lucida Grande, Helvetica') +*/ +CPFontNameAttribute = @"CPFontNameAttribute"; +/* + CPFontSizeAttribute contains a CPString that specified the font size + (as a float value) +*/ +CPFontSizeAttribute = @"CPFontSizeAttribute"; +/* + CPFontTraitsAttribute a CPDictionary that contains font traits keys + (CPFontSymbolicTrait or CPFontWeightTrait) +*/ +CPFontTraitsAttribute = @"CPFontTraitsAttribute"; + +// Font traits dictionary keys +/* + CPFontSymbolicTrait a CPNumber that contains CPFontFamilyClass and + typeface information flags. +*/ +CPFontSymbolicTrait = @"CPFontSymbolicTrait"; + +/* + CPFontWeightTrait + We use CPString with CSS string values for font weight + (normal | bold | bolder | lighter | 100 | 200 | 300 | 400 + | 500 | 600 | 700 | 800 | 900) + NOTE: Cocoa compatibility issue: NSFontWeightTrait are NSNumber for + font weight (from -1.0 to 1.0, 0.0 for normal weight). +*/ +CPFontWeightTrait = @"CPFontWeightTrait"; + +/* + CPFontFamilyClass +*/ +CPFontUnknownClass = 0 << 28; +CPFontOldStyleSerifsClass = 1 << 28; +CPFontTransitionalSerifsClass = 2 << 28; +CPFontModernSerifsClass = 3 << 28; +CPFontClarendonSerifsClass = 4 << 28; +CPFontSlabSerifsClass = 5 << 28; +CPFontFreeformSerifsClass = 7 << 28; +CPFontSansSerifClass = 8 << 28; + +CPFontSerifClass = (CPFontOldStyleSerifsClass | CPFontTransitionalSerifsClass | + CPFontModernSerifsClass | CPFontClarendonSerifsClass | + CPFontSlabSerifsClass | CPFontFreeformSerifsClass); + +CPFontFamilyClassMask = 0xF0000000; + +/* + Typeface information +*/ +CPFontItalicTrait = 1 << 0; +CPFontBoldTrait = 1 << 1; +CPFontExpandedTrait = 1 << 5; /* TODO: CCS 3 font-stretch */ +CPFontCondensedTrait = 1 << 6; +CPFontSmallCapsTrait = 1 << 7; + +/*! + @ingroup appkit + @class CPFontDescriptor +*/ +@implementation CPFontDescriptor : CPObject +{ + CPDictionary _attributes; +} + +/*! + Returns a font descriptor with the specified attributes. + + @param attributes a dictionary that describe the desired font descriptor + @return the requested font descriptor +*/ ++ (CPFontDescriptor)fontDescriptorWithFontAttributes:(CPDictionary)attributes +{ + return [[CPFontDescriptor alloc] initWithFontAttributes:attributes]; +} + +/*! + Returns a font descriptor with the specified name and size. + + @param fontName the name of the font + @param aSize the size of the font (in points) + @return the requested font descriptor +*/ ++ (CPFontDescriptor)fontDescriptorWithName:(CPString)fontName size:(float)size +{ + return [[CPFontDescriptor alloc] initWithFontAttributes:[CPDictionary dictionaryWithObjects:[fontName, [CPString stringWithString:size + '']] forKeys:[CPFontNameAttribute,CPFontSizeAttribute]]]; +} + +/*! + Initialize a font descriptor with the specified attributes. + + @param attributes a dictionary that describe the desired font descriptor + @return the requested font descriptor +*/ +- (id)initWithFontAttributes:(CPDictionary)attributes +{ + if (self = [super init]) + { + _attributes = [[CPMutableDictionary alloc] init]; + + if (attributes) + [_attributes addEntriesFromDictionary:attributes]; + } + + return self; +} + +/*! + Returns a new font descriptor that is the same as the receiver but with the + specified attributes taking precedence over the existing ones. + + @param attributes a dictionary that describe the desired font descriptor + @return the new font descriptor +*/ +- (CPFontDescriptor)fontDescriptorByAddingAttributes:(CPDictionary)attributes +{ + var attrib = [_attributes copy]; + + [attrib addEntriesFromDictionary:attributes]; + + return [[CPFontDescriptor alloc] initWithFontAttributes:attrib]; +} + +/*! + Returns a new font descriptor that is the same as the receiver but with the specified size taking precedence over the existing ones. + + @param aSize the new size + @return the new font descriptor +*/ +- (CPFontDescriptor)fontDescriptorWithSize:(float)aSize +{ + var attrib = [_attributes copy]; + + [attrib setObject:[CPString stringWithString:aSize + ''] forKey:CPFontSizeAttribute]; + + return [[CPFontDescriptor alloc] initWithFontAttributes:attrib]; +} + +/*! + Returns a new font descriptor that is the same as the receiver but with + the specified symbolic traits taking precedence over the existing ones. + + @param symbolicTraits the desired new symbolic traits + @return the new font descriptor +*/ +- (CPFontDescriptor)fontDescriptorWithSymbolicTraits:(CPFontSymbolicTraits)symbolicTraits +{ + var attrib = [_attributes copy]; + + if ([attrib objectForKey:CPFontTraitsAttribute]) + [[attrib objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTraits] + forKey:CPFontSymbolicTrait]; + else + [attrib setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTraits] + forKey:CPFontSymbolicTrait] forKey:CPFontTraitsAttribute]; + + return [[CPFontDescriptor alloc] initWithFontAttributes:attrib]; +} + +- (id)objectForKey:(id)aKey +{ + return [_attributes objectForKey:aKey]; +} + +- (CPDictionary)fontAttributes +{ + return _attributes; +} + +- (float)pointSize +{ + var value = [_attributes objectForKey:CPFontSizeAttribute]; + + return value ? [value floatValue] : 0.0; +} + +- (CPFontSymbolicTraits)symbolicTraits +{ + var traits = [_attributes objectForKey:CPFontTraitsAttribute]; + + return (traits && [traits objectForKey:CPFontSymbolicTrait]) ? [[traits objectForKey:CPFontSymbolicTrait] unsignedIntValue] : 0; +} + +@end + +var CPFontDescriptorAttributesKey = @"CPFontDescriptorAttributesKey"; + +@implementation CPFontDescriptor (CPCoding) + +/*! + Initializes the font descriptor from a coder. + + @param aCoder the coder from which to read the font descriptor data + @return the initialized font +*/ +- (id)initWithCoder:(CPCoder)aCoder +{ + return [self initWithFontAttributes:[aCoder decodeObjectForKey:CPFontDescriptorAttributesKey]]; +} + +/*! + Writes the font descriptor to a coder. + + @param aCoder the coder to which the data will be written +*/ +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:_attributes forKey:CPFontDescriptorAttributesKey]; +} + +@end + +var _wrapNameRegEx = new RegExp(/(\w+\s+\w+)(,*)/g); + +/* + Helper methods to CPFont for generating CSS font style +*/ +@implementation CPFontDescriptor (CPFontCSSHelper) + +- (CPString)fontStyleCSSString +{ + return [self symbolicTraits] & CPFontItalicTrait ? @"italic" : @"normal"; +} + +- (CPString)fontWeightCSSString +{ + var traitsAttributes = [_attributes objectForKey:CPFontTraitsAttribute]; + + if (traitsAttributes) + { + /* give preference to CPFontWeightTrait */ + if ([traitsAttributes objectForKey:CPFontWeightTrait]) + return [traitsAttributes objectForKey:CPFontWeightTrait]; + /* else fallback to facetype symbolic traits */ + if ([self symbolicTraits] & CPFontBoldTrait) + return @"bold"; + } + + return @"normal"; +} + +- (CPString)fontSizeCSSString +{ + return [_attributes objectForKey:CPFontSizeAttribute] ? [[_attributes objectForKey:CPFontSizeAttribute] intValue] + "px" : @""; +} + +- (CPString)fontFamilyCSSString +{ + var aName = @""; + + if ([_attributes objectForKey:CPFontNameAttribute]) + aName += [_attributes objectForKey:CPFontNameAttribute].replace(_wrapNameRegEx, '"$1"$2'); + + var symbolicTraits = [self symbolicTraits]; + + if (symbolicTraits) + { + if ((symbolicTraits & CPFontFamilyClassMask) & CPFontSansSerifClass) + aName += @", sans-serif"; + else if ((symbolicTraits & CPFontFamilyClassMask) & CPFontSerifClass) + aName += @", serif"; + } + + return aName; +} + +- (CPString)fontVariantCSSString +{ + if ([self symbolicTraits] & CPFontSmallCapsTrait) + return @"small-caps"; + + return @"normal"; +} + +- (CPString)cssString +{ + return [CPString stringWithString:[self fontStyleCSSString] + " " + + [self fontVariantCSSString] + " " + + [self fontWeightCSSString] + " " + + [self fontSizeCSSString] + " " + + [self fontFamilyCSSString]]; +} + +@end diff --git a/AppKit/CPTextView/CPFontPanel.j b/AppKit/CPTextView/CPFontPanel.j new file mode 100644 index 000000000..c4c4a6449 --- /dev/null +++ b/AppKit/CPTextView/CPFontPanel.j @@ -0,0 +1,502 @@ +/* + * CPFontPanel.j + * AppKit + * + * TODOs: + * 1. make browser-width for size smaller and fix columns + * 2. sampleview is currently not shown + * 3. add all the missing features from the MacOS X counterpart + * + * + * Created by Daniel Boehringer on 2/JAN/2014. + * All modifications copyright Daniel Boehringer 2013. + * Extensive code formatting and review by Andrew Hankinson + * Based on original work by + * Created by Emmanuel Maillard on 06/03/2010. + * Copyright Emmanuel Maillard 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +@import "CPPanel.j" +@import "CPColorWell.j" +@import "CPColorPanel.j" +@import "CPBrowser.j" +@import "CPText.j" +@import "CPFontManager.j" + + +@class CPTextStorage +@class CPLayoutManager +@class CPTextContainer +@class CPFontManager + +/* + Collection indexes +*/ +var kTypefaceIndex_Normal = 0, + kTypefaceIndex_Italic = 1, + kTypefaceIndex_Bold = 2, + kTypefaceIndex_BoldItalic = 3, + kToolbarHeight = 32, + kBorderSpacing = 6, + kInnerSpacing = 2, + kNothingChanged = 0, + kFontNameChanged = 1, + kTypefaceChanged = 2, + kSizeChanged = 3, + kTextColorChanged = 4, + kBackgroundColorChanged = 5, + kUnderlineChanged = 6, + kWeightChanged = 7, + _sharedFontPanel; + +// FIXME Locale support +var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"], + _availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"72", @"96"]; + +@implementation _CPFontPanelSampleView : CPView +{ + CPLayoutManager _layoutManager; + CPTextStorage _textStorage; + CPTextContainer _textContainer; +} + +- (id)initWithFrame:(CGRect)rect +{ + if (self = [super initWithFrame:rect]) + { + _textStorage = [[CPTextStorage alloc] init]; + _layoutManager = [[CPLayoutManager alloc] init]; + + _textContainer = [[CPTextContainer alloc] init]; + [_layoutManager addTextContainer:_textContainer]; + + [_textStorage addLayoutManager:_layoutManager]; + } + + return self; +} + +- (void)setAttributedString:(CPAttributedString)aSting +{ + [_textStorage replaceCharactersInRange:CPMakeRange(0, [_textStorage length]) + withAttributedString:aSting]; + + [self setNeedsDisplay:YES]; +} + +- (void)drawRect:(CGRect)rect +{ + var ctx = [[CPGraphicsContext currentContext] graphicsPort], + glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer], + usedRect = [_layoutManager usedRectForTextContainer:_textContainer], + bounds = [self bounds], + pos = CGPointMake((bounds.size.width - usedRect.size.width) / 2.0, (bounds.size.height - usedRect.size.height) / 2.0); + + CGContextSaveGState(ctx); + CGContextSetFillColor(ctx, [CPColor whiteColor]); + CGContextFillRect(ctx, bounds); + CGContextRestoreGState(ctx); + + [_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:pos]; +} + +@end + +/*! + @ingroup appkit + @class CPFontPanel +*/ +@implementation CPFontPanel : CPPanel +{ + id _fontBrowser; + id _traitBrowser; + id _sizeBrowser; + CPArray _availableFonts; + id _textColorWell; + CPColor _textColor; + int _currentColorButtonTag; + BOOL _setupDone; + int _fontChanges; + + _CPFontPanelSampleView _sampleView; +} + + +#pragma mark - +#pragma mark Class methods + +/*! + Check if the shared Font panel exists. +*/ ++ (BOOL)sharedFontPanelExists +{ + return _sharedFontPanel !== nil; +} + +/*! + Return the shared Font panel. +*/ ++ (CPFontPanel)sharedFontPanel +{ + if (!_sharedFontPanel) + _sharedFontPanel = [[CPFontPanel alloc] init]; + + return _sharedFontPanel; +} + + +#pragma mark - +#pragma mark Init methods + +/*! @ignore */ +- (id)init +{ + if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 394) styleMask:(CPTitledWindowMask | CPClosableWindowMask /*| CPResizableWindowMask*/ )]) + { + [[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]]; + [self setTitle:@"Font Panel"]; + [self setLevel:CPFloatingWindowLevel]; + [self setFloatingPanel:YES]; + [self setBecomesKeyOnlyIfNeeded:YES]; + [self setMinSize:CGSizeMake(378, 394)]; + + _availableFonts = [[CPFontManager sharedFontManager] availableFonts]; + _textColor = [CPColor blackColor]; + _setupDone = NO; + _fontChanges = kNothingChanged; + } + + return self; +} + +/*! @ignore */ +- (void)_setupToolbarView +{ + var colorPanel = [CPColorPanel sharedColorPanel]; + + _toolbarView = [[CPView alloc] initWithFrame:CGRectMake(0, kBorderSpacing, CGRectGetWidth([self frame]), kToolbarHeight)]; + [_toolbarView setAutoresizingMask:CPViewWidthSizable]; + + // Text color + _textColorWell = [[CPColorWell alloc] initWithFrame:CGRectMake(10, 0, 25, 25)]; + [_textColorWell setColor:_textColor]; + [_toolbarView addSubview:_textColorWell]; + [colorPanel setTarget:self]; + [colorPanel setAction:@selector(changeColor:)]; +} + +- (void)_setupBrowser:(CPBrowser)aBrowser +{ + [aBrowser setTarget:self]; + [aBrowser setAction:@selector(browserClicked:)]; + [aBrowser setDoubleAction:@selector(dblClicked:)]; + [aBrowser setAllowsEmptySelection:NO]; + [aBrowser setAllowsMultipleSelection:NO]; + [aBrowser setDelegate:self]; + [[self contentView] addSubview:aBrowser]; +} + +- (void)_setupContents +{ + if (_setupDone) + return; + + _setupDone = YES; + + [self _setupToolbarView]; + + var contentView = [self contentView], + label = [CPTextField labelWithTitle:@"Font name"], + contentBounds = [contentView bounds], + upperView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(contentBounds), CGRectGetHeight(contentBounds) - (kBorderSpacing + kToolbarHeight + kInnerSpacing))]; + + [contentView addSubview:_toolbarView]; + + _fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(10, 35, 150, 350)]; + _traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(155, 35, 150, 350)]; + _sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(300, 35, 140, 350)]; + + [self _setupBrowser:_fontBrowser]; + [self _setupBrowser:_traitBrowser]; + [self _setupBrowser:_sizeBrowser]; + + [[CPNotificationCenter defaultCenter] addObserver:self + selector:@selector(textViewDidChangeSelection:) + name:CPTextViewDidChangeSelectionNotification + object:nil]; +} + +- (void)textViewDidChangeSelection:(CPNotification)notification +{ + [self _refreshWithTextView:[notification object]]; + +} + +- (void)_refreshWithTextView:(CPTextView)textView +{ + if (![self isVisible]) + return; + + var attribs = [textView _attributesForFontPanel], + font = [attribs objectForKey:CPFontAttributeName] || [[textView textStorage] font] || [CPFont systemFontOfSize:12.0], + color = [attribs objectForKey:CPForegroundColorAttributeName]; + + if (!font) + return; + + var trait = kTypefaceIndex_Normal; + + if ([font isItalic] && [font isBold]) + trait = kTypefaceIndex_BoldItalic; + else if ([font isItalic]) + trait = kTypefaceIndex_Italic; + else if ([font isBold]) + trait = kTypefaceIndex_Bold; + + [self setCurrentFont:font]; + [self setCurrentTrait:trait]; + [self setCurrentSize:[font size] + ""]; //cast to string + + if (!color) + return; + + [_textColorWell setColor:color]; +} + +- (void)orderFront:(id)sender +{ + [self _setupContents]; + [super orderFront:sender]; + [self _refreshWithTextView:[[CPApp keyWindow] firstResponder]]; +} + +- (void)reloadDefaultFontFamilies +{ + _availableFonts = [[CPFontManager sharedFontManager] availableFonts]; +} + +- (BOOL)worksWhenModal +{ + return YES; +} + +/*! + @param aFont the font to convert. + @return The converted font or \c aFont if failed to convert. +*/ +- (CPFont)panelConvertFont:(CPFont)aFont +{ + var newFont = aFont, + index = 0; + + switch (_fontChanges) + { + case kFontNameChanged: + newFont = [CPFont fontWithDescriptor:[[aFont fontDescriptor] fontDescriptorByAddingAttributes: + [CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0]; + break; + + case kTypefaceChanged: + index = [self currentTrait]; + if (index == kTypefaceIndex_BoldItalic) + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPBoldFontMask | CPItalicFontMask]; + else if (index == kTypefaceIndex_Bold) + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPBoldFontMask]; + else if (index == kTypefaceIndex_Italic) + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toHaveTrait:CPItalicFontMask]; + else + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toNotHaveTrait:CPBoldFontMask | CPItalicFontMask]; + break; + + case kSizeChanged: + newFont = [[CPFontManager sharedFontManager] convertFont:aFont toSize:[self currentSize]]; + break; + + case kNothingChanged: + break; + + default: + CPLog.trace(@"FIXME: -[" + [self className] + " " + _cmd + "] unhandled _fontChanges: " + _fontChanges); + break; + } + + return newFont; +} + +- (void)setCurrentSize:(CGSize)aSize +{ + [_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0]; +} + +- (CPString)currentSize +{ + return [_sizeBrowser selectedItem]; +} + +- (void)setCurrentFont:(CPFont)aFont +{ + [_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0]; +} + +- (CPString)currentFont +{ + return [_fontBrowser selectedItem]; +} + +- (void)setCurrentTrait:(unsigned)aTrait +{ + var row = 0; + + switch (aTrait) + { + case kTypefaceIndex_Italic: + row = 1; + break; + + case kTypefaceIndex_Bold: + row = 2; + break; + + case kTypefaceIndex_BoldItalic: + row = 3; + break; + } + + [_traitBrowser selectRow:row inColumn:0]; +} + +// FIXME Locale support +- (unsigned)currentTrait +{ + var sel = [_traitBrowser selectedItem]; + + if (sel === "Italic") + return kTypefaceIndex_Italic; + + if (sel === "Bold") + return kTypefaceIndex_Bold; + + if (sel === "Bold Italic") + return kTypefaceIndex_BoldItalic; + + return kTypefaceIndex_Normal; +} + +/*! + Set the selected font in Font panel. + @param font the selected font + @param flag if \c the current selection have multiple fonts. +*/ +- (void)setPanelFont:(CPFont)font isMultiple:(BOOL)flag +{ + [self _setupContents]; + + if ([self currentFont] !== [font familyName]) + [self setCurrentFont:[font familyName]]; + + if ([self currentSize] != [font size]) + [self setCurrentSize:[font size]]; + + var typefaceIndex = kTypefaceIndex_Normal, + symbolicTraits = [[font fontDescriptor] symbolicTraits]; + + if ((symbolicTraits & CPFontItalicTrait) && (symbolicTraits & CPFontBoldTrait)) + typefaceIndex = kTypefaceIndex_BoldItalic; + else if (symbolicTraits & CPFontItalicTrait) + typefaceIndex = kTypefaceIndex_Italic; + else if (symbolicTraits & CPFontBoldTrait) + typefaceIndex = kTypefaceIndex_Bold; + + if ([self currentTrait] != typefaceIndex) + [self setCurrentTrait:typefaceIndex ]; + + [_sampleView setAttributedString: [[CPAttributedString alloc] initWithString:[font familyName] + attributes:[CPDictionary dictionaryWithObjects:[font, [CPColor blackColor]] + forKeys:[CPFontAttributeName, CPForegroundColorAttributeName]]]]; + + _fontChanges = kNothingChanged; +} + +- (void)changeColor:(id)sender +{ + _textColor = [sender color]; + _fontChanges = kTextColorChanged; + [[CPFontManager sharedFontManager] modifyFontViaPanel:self]; +} + +//////////////////////////////////////////////////////////////////// +// TODO: ask CPFontManager for traits // +- (void)browserClicked:(id)aBrowser +{ + if (aBrowser === _fontBrowser) + { + _fontChanges = kFontNameChanged; + [[CPFontManager sharedFontManager] modifyFontViaPanel:self]; + } + else if (aBrowser === _traitBrowser) + { + _fontChanges = kTypefaceChanged; + [[CPFontManager sharedFontManager] modifyFontViaPanel:self]; + } + else if (aBrowser === _sizeBrowser) + { + _fontChanges = kSizeChanged; + [[CPFontManager sharedFontManager] modifyFontViaPanel:self]; + } +} + +- (void)dblClicked:(id)sender +{ + // alert("DOUBLE"); +} + +- (id)browser:(id)aBrowser numberOfChildrenOfItem:(id)anItem +{ + if (aBrowser === _fontBrowser) + return [_availableFonts count]; + + if (aBrowser === _traitBrowser) + return [_availableTraits count] + + return [_availableSizes count] +} + +- (id)browser:(id)aBrowser child:(int)index ofItem:(id)anItem +{ + if (aBrowser === _fontBrowser) + return [_availableFonts objectAtIndex:index]; + + if (aBrowser === _traitBrowser) + return [_availableTraits objectAtIndex:index]; + + return [_availableSizes objectAtIndex:index]; +} + +- (id)browser:(id)aBrowser objectValueForItem:(id)anItem +{ + return anItem; +} + +- (BOOL)browser:(id)aBrowser isLeafItem:(id)anItem +{ + return YES; +} + +@end + +[CPFontManager setFontPanelFactory:[CPFontPanel class]]; diff --git a/AppKit/CPTextView/CPLayoutManager.j b/AppKit/CPTextView/CPLayoutManager.j new file mode 100644 index 000000000..99d403be3 --- /dev/null +++ b/AppKit/CPTextView/CPLayoutManager.j @@ -0,0 +1,1373 @@ +/* + * CPLayoutManager.j + * AppKit + * + * FIXME remove from DOM when scrolled out of visible area? (as done in CPTableView) + * + * + * Created by Daniel Boehringer on 27/12/2013. + * All modifications copyright Daniel Boehringer 2013. + * Extensive code formatting and review by Andrew Hankinson + * Based on original work by + * Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import "CPText.j" +@import "CPTextContainer.j" +@import "CGContext.j" +@import "CPTypesetter.j" +@import "CPFont.j" + +@global _MakeRangeFromAbs + +@class CPTextContainer +@class CPTextView + +function _isNewlineCharacter(chr) +{ + return (chr === '\n' || chr === '\r'); +} + +function _RectEqualToRectHorizontally(lhsRect, rhsRect) +{ + return (lhsRect.origin.x == rhsRect.origin.x && + lhsRect.size.width == rhsRect.size.width && + lhsRect.size.height == rhsRect.size.height); +} + +_oncontextmenuhandler = function () { return false; }; + + +/*! + @ingroup appkit + @class CPLayoutManager +*/ +@implementation CPLayoutManager : CPObject +{ + Class _lineFragmentFactory @accessors(setter=setLineFragmentFactory:); + CPMutableArray _textContainers @accessors(getter=textContainers); + CPTextStorage _textStorage @accessors(property=textStorage); + CPTypesetter _typesetter @accessors(property=typesetter); + + CPMutableArray _lineFragments; + CPMutableArray _lineFragmentsForRescue; + id _extraLineFragment; + + CPMutableArray _temporaryAttributes; + + BOOL _isValidatingLayoutAndGlyphs; + CPRange _removeInvalidLineFragmentsRange; +} + + +#pragma mark - +#pragma mark Init methods + +- (id)init +{ + if (self = [super init]) + { + [self _init]; + } + + return self; +} + +- (void)_init +{ + _isValidatingLayoutAndGlyphs = NO; + _lineFragmentFactory = [_CPLineFragment class]; + _lineFragments = [[CPMutableArray alloc] init]; + _textContainers = [[CPMutableArray alloc] init]; + _textStorage = [[CPTextStorage alloc] init]; + _typesetter = [CPTypesetter sharedSystemTypesetter]; + + [_textStorage addLayoutManager:self]; +} + + +#pragma mark - +#pragma mark Text containes method + +- (void)insertTextContainer:(CPTextContainer)aContainer atIndex:(int)index +{ + [_textContainers insertObject:aContainer atIndex:index]; + [aContainer setLayoutManager:self]; +} + +- (void)addTextContainer:(CPTextContainer)aContainer +{ + [_textContainers addObject:aContainer]; + [aContainer setLayoutManager:self]; +} + +- (void)removeTextContainerAtIndex:(int)index +{ + var container = [_textContainers objectAtIndex:index]; + [container setLayoutManager:nil]; + [_textContainers removeObjectAtIndex:index]; +} + +// fixme +- (int)numberOfGlyphs +{ + return [_textStorage length]; +} + +- (int)numberOfCharacters +{ + return [_textStorage length]; +} + +- (CPTextView)firstTextView +{ + return [_textContainers[0] textView]; +} + +// from cocoa (?) +- (CPTextView)textViewForBeginningOfSelection +{ + return [[_textContainers objectAtIndex:0] textView]; +} + +- (BOOL)layoutManagerOwnsFirstResponderInWindow:(CPWindow)aWindow +{ + var firstResponder = [aWindow firstResponder], + c = [_textContainers count]; + + for (var i = 0; i < c; i++) + { + if ([_textContainers[i] textView] === firstResponder) + return YES; + } + + return NO; +} + +- (CGRect)boundingRectForGlyphRange:(CGRange)aRange inTextContainer:(CPTextContainer)container +{ + if (![self numberOfGlyphs]) + return CGRectMake(0, 0, 1, 12); // crude hack to give a cursor in an empty doc. + + if (CPMaxRange(aRange) >= [self numberOfGlyphs]) + aRange = CPMakeRange([self numberOfGlyphs] - 1, 1); + + var fragments = _objectsInRange(_lineFragments, aRange), + rect = nil, + c = [fragments count]; + + for (var i = 0; i < c; i++) + { + var fragment = fragments[i]; + + if (fragment._textContainer === container) + { + var frames = [fragment glyphFrames], + l = frames ? frames.length : 0; + + for (var j = 0; j < l; j++) + { + if (CPLocationInRange(fragment._range.location + j, aRange)) + { + if (!rect) + rect = CGRectCreateCopy(frames[j]); + else + rect = CGRectUnion(rect, frames[j]); + } + } + } + } + + return rect ? rect : CGRectMakeZero(); +} + +- (CPRange)glyphRangeForTextContainer:(CPTextContainer)aTextContainer +{ + var range = nil, + c = [_lineFragments count]; + + for (var i = 0; i < c; i++) + { + var fragment = _lineFragments[i]; + + if (fragment._textContainer === aTextContainer) + { + if (!range) + range = CPMakeRangeCopy(fragment._range); + else + range = CPUnionRange(range, fragment._range); + } + } + + return range ? range : CPMakeRange(CPNotFound, 0); +} + +- (void)_removeInvalidLineFragments +{ + _lineFragmentsForRescue = [_lineFragments copy]; + [_lineFragmentsForRescue makeObjectsPerformSelector:@selector(_deinvalidate)]; + + if (_removeInvalidLineFragmentsRange && _removeInvalidLineFragmentsRange.length && _lineFragments.length) + { + // [[_lineFragments subarrayWithRange:_removeInvalidLineFragmentsRange] makeObjectsPerformSelector:@selector(invalidate)]; + [_lineFragments removeObjectsInRange:_removeInvalidLineFragmentsRange]; + [[_lineFragmentsForRescue subarrayWithRange:_removeInvalidLineFragmentsRange] makeObjectsPerformSelector:@selector(invalidate)]; + } + +} + +- (void)_cleanUpDOM +{ + var l = _lineFragmentsForRescue? _lineFragmentsForRescue.length : 0; + + for (var i = 0; i < l; i++) + { + if (_lineFragmentsForRescue[i]._isInvalid) + [_lineFragmentsForRescue[i] _removeFromDOM]; + } +} + +- (void)_validateLayoutAndGlyphs +{ + if (_isValidatingLayoutAndGlyphs) + return; + + _isValidatingLayoutAndGlyphs = YES; + + var startIndex = CPNotFound, + removeRange = CPMakeRange(0, 0), + l = _lineFragments.length; + + if (l) + { + for (var i = 0; i < l; i++) + { + if (_lineFragments[i]._isInvalid) + { + startIndex = _lineFragments[i]._range.location; + removeRange.location = i; + removeRange.length = l - i; + break; + } + } + + // start one line above current line to make sure that a word can jump up + if (startIndex == CPNotFound && CPMaxRange (_lineFragments[l - 1]._range) < [_textStorage length]) + startIndex = CPMaxRange(_lineFragments[l - 1]._range); + } + else + { + startIndex = 0; + } + + /* nothing to validate and layout */ + if (startIndex == CPNotFound) + { + _isValidatingLayoutAndGlyphs = NO; + return; + } + + if (removeRange.length) + _removeInvalidLineFragmentsRange = CPMakeRangeCopy(removeRange); + + // We erased all lines + if (!startIndex) + [self setExtraLineFragmentRect:CGRectMake(0, 0) usedRect:CGRectMake(0, 0) textContainer:nil]; + // document.title=startIndex; + + [_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil]; + +#if PLATFORM(DOM) + [self _cleanUpDOM]; +#endif + + _isValidatingLayoutAndGlyphs = NO; +} + +- (BOOL)_rescuingInvalidFragmentsWasPossibleForGlyphRange:(CPRange)aRange +{ + var l = _lineFragments.length, + location = aRange.location, + found = NO, + targetLine = 0; + + // try to find the first linefragment of the desired range + for (; targetLine < l; targetLine++) + { + if (CPLocationInRange(location, _lineFragments[targetLine]._range)) + { + found = YES; + break; + } + } + + if (!found) + return NO; + + if (!_lineFragmentsForRescue[targetLine]) + return NO; + + var startLineForDOMRemoval = targetLine, + isIdentical = YES, + newLineFragment= _lineFragments[targetLine], + oldLineFragment = _lineFragmentsForRescue[targetLine], + oldLength = CPMaxRange([_lineFragmentsForRescue lastObject]._range), + newLength = [[_textStorage string].length], + removalSkip = 1; + + // if (ABS(newLength - oldLength) > 1) + // return NO; + + if (![oldLineFragment isVisuallyIdenticalToFragment:newLineFragment]) + { + isIdentical = NO; + + // deleting newline in its own line-> move up instead of re-layouting + if (newLength < oldLength && oldLineFragment._range.length == 1 && newLineFragment._range.length > 1 && newLineFragment._range.location === oldLineFragment._range.location && oldLineFragment._isLast) + { + isIdentical = YES; + targetLine--; + removalSkip++; + } + + // newline entered in its own line-> move down instead of re-layouting + if (newLength > oldLength && newLineFragment._range.length == 1 && oldLineFragment._range.length > 1 && newLineFragment._range.location === oldLineFragment._range.location && newLineFragment._isLast) + { + isIdentical = YES; + startLineForDOMRemoval--; + } + } + + // patch the linefragments instead of re-layoutung + if (isIdentical) + { + var rangeOffset = CPMaxRange(_lineFragments[targetLine]._range) - CPMaxRange(_lineFragmentsForRescue[startLineForDOMRemoval]._range); + + if (ABS(rangeOffset) !== ABS(newLength - oldLength)) + return NO; + + var verticalOffset = _lineFragments[targetLine]._fragmentRect.origin.y - _lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect.origin.y, + l = _lineFragmentsForRescue.length, + newTargetLine = startLineForDOMRemoval + removalSkip; + + for (; newTargetLine < l; newTargetLine++) + { + _lineFragmentsForRescue[newTargetLine]._isInvalid = NO; // protect them from final removal + [_lineFragmentsForRescue[newTargetLine] _relocateVerticallyByY:verticalOffset rangeOffset:rangeOffset]; + _lineFragments.push(_lineFragmentsForRescue[newTargetLine]); + } + } + + return isIdentical; +} + +- (void)invalidateDisplayForGlyphRange:(CPRange)range +{ + var lineFragments = _objectsInRange(_lineFragments, range); + + for (var i = 0; i < lineFragments.length; i++) + [[lineFragments[i]._textContainer textView] setNeedsDisplayInRect:lineFragments[i]._fragmentRect]; +} + +- (void)invalidateLayoutForCharacterRange:(CPRange)aRange isSoft:(BOOL)flag actualCharacterRange:(CPRangePointer)actualCharRange +{ + var firstFragmentIndex = _lineFragments.length ? [_lineFragments _indexOfObject: aRange.location sortedByFunction:_sortRange context:nil] : CPNotFound; + + if (firstFragmentIndex == CPNotFound) + { + if (_lineFragments.length) + { + firstFragmentIndex = _lineFragments.length - 1; + } + else + { + if (actualCharRange) + { + actualCharRange.length = aRange.length; + actualCharRange.location = 0; + } + + return; + } + } + else + { + firstFragmentIndex = firstFragmentIndex + (firstFragmentIndex ? -1 : 0); + } + + var fragment = _lineFragments[firstFragmentIndex], + range = CPMakeRangeCopy(fragment._range); + + fragment._isInvalid = YES; + + /* invalidated all fragments that follow */ + for (var i = firstFragmentIndex + 1; i < _lineFragments.length; i++) + { + _lineFragments[i]._isInvalid = YES; + range = CPUnionRange(range, _lineFragments[i]._range); + } + + if (CPMaxRange(range) < CPMaxRange(aRange)) + range = CPUnionRange(range, aRange); + + if (actualCharRange) + { + actualCharRange.length = range.length; + actualCharRange.location = range.location; + } +} + +- (void)textStorage:(CPTextStorage)textStorage edited:(unsigned)mask range:(CPRange)charRange changeInLength:(int)delta invalidatedRange:(CPRange)invalidatedRange +{ + var actualRange = CPMakeRange(CPNotFound,0); + + [self invalidateLayoutForCharacterRange:invalidatedRange isSoft:NO actualCharacterRange:actualRange]; + [self invalidateDisplayForGlyphRange:actualRange]; + [self _validateLayoutAndGlyphs]; + [[self firstTextView] sizeToFit]; +} + +- (CPRange)glyphRangeForBoundingRect:(CGRect)aRect inTextContainer:(CPTextContainer)container +{ + var c = [_lineFragments count], + range; + + for (var i = 0; i < c; i++) + { + var fragment = _lineFragments[i]; + + if (fragment._textContainer === container) + { + if (CGRectContainsRect(aRect, fragment._fragmentRect)) + { + if (!range) + range = CPMakeRangeCopy(fragment._range); + else + range = CPUnionRange(range, fragment._range); + } + else + { + var glyphRange = CPMakeRange(CPNotFound, 0), + frames = [fragment glyphFrames]; + + for (var j = 0; j < frames.length; j++) + { + if (CGRectIntersectsRect(aRect, frames[j])) + { + if (glyphRange.location == CPNotFound) + glyphRange.location = fragment._range.location + j; + else + glyphRange.length++; + } + } + + if (glyphRange.location != CPNotFound) + { + if (!range) + range = CPMakeRangeCopy(glyphRange); + else + range = CPUnionRange(range, glyphRange); + } + } + } + } + + return range ? range : CPMakeRange(0,0); +} + +- (void)drawBackgroundForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint +{ + +} + +- (void)drawUnderlineForGlyphRange:(CPRange)glyphRange + underlineType:(int)underlineVal + baselineOffset:(float)baselineOffset + lineFragmentRect:(CGRect)lineFragmentRect + lineFragmentGlyphRange:(CPRange)lineGlyphRange + containerOrigin:(CGPoint)containerOrigin +{ +// FIXME +} + +- (void)drawGlyphsForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint +{ + var lineFragments = _objectsInRange(_lineFragments, aRange); + + if (!lineFragments.length) + return; + + var paintedRange = CPMakeRangeCopy(aRange), + l = lineFragments.length, + lineFragmentIndex, + ctx; + + for (lineFragmentIndex = 0; lineFragmentIndex < l; lineFragmentIndex++) + { + var currentFragment = lineFragments[lineFragmentIndex]; + [currentFragment drawInContext:ctx atPoint:aPoint forRange:paintedRange]; + } +} + +- (unsigned)glyphIndexForPoint:(CGPoint)point inTextContainer:(CPTextContainer)container fractionOfDistanceThroughGlyph:(FloatArray)partialFraction +{ + var c = [_lineFragments count]; + + for (var i = 0; i < c; i++) + { + var fragment = _lineFragments[i]; + + if (fragment._textContainer === container) + { + var frames = [fragment glyphFrames], + len = fragment._range.length; + + for (var j = 0; j < len; j++) + { + if (CGRectContainsPoint(frames[j], point)) + { + if (partialFraction) + partialFraction[0] = (point.x - frames[j].origin.x) / frames[j].size.width; + + return fragment._range.location + j; + } + } + } + } + + // Not found, maybe a point left to the last character was clicked -> search again with broader constraints + if ([[_textStorage string] length]) + { + for (var i = 0; i < c; i++) + { + var fragment = _lineFragments[i]; + + if (fragment._textContainer === container) + { + // Within the horizontal territory of the current (not-empty) line? + if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y && + point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height) + { + // Skip tabs and move on the last fragment in this line + if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y) + continue; + + var nlLoc = CPMaxRange(fragment._range), + lastFrame = [fragment glyphFrames][fragment._range.length - 1], + firstFrame = [fragment glyphFrames][0]; + + // stay on the line the newline character belongs to + if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0])) + nlLoc--; + + // Clicked right to the last character + if (point.x > CGRectGetMaxX(lastFrame)) + return nlLoc; + // Clicked left to the last character + else if (point.x <= CGRectGetMinX(firstFrame)) + return fragment._range.location; + else + return nlLoc; + } + } + } + } + + return point.y > 0 ? [[_textStorage string] length] : 0; +} + +- (unsigned)glyphIndexForPoint:(CGPoint)point inTextContainer:(CPTextContainer)container +{ + return [self glyphIndexForPoint:point inTextContainer:container fractionOfDistanceThroughGlyph:nil]; +} + +- (void)_setAttributes:(CPDictionary)attributes toTemporaryAttributes:(_CPTemporaryAttributes)tempAttributes +{ + tempAttributes._attributes = attributes; +} + +- (void)_addAttributes:(CPDictionary)attributes toTemporaryAttributes:(_CPTemporaryAttributes)tempAttributes +{ + [tempAttributes._attributes addEntriesFromDictionary:attributes]; +} + +- (void)_handleTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange withSelector:(SEL)attributesOperation +{ + // FIXME +} + +- (void)setTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange +{ + [self _handleTemporaryAttributes:attributes forCharacterRange:charRange withSelector:@selector(_setAttributes:toTemporaryAttributes:)]; +} + +- (void)addTemporaryAttributes:(CPDictionary)attributes forCharacterRange:(CPRange)charRange +{ + [self _handleTemporaryAttributes:attributes forCharacterRange:charRange withSelector:@selector(_addAttributes:toTemporaryAttributes:)]; +} + +- (void)removeTemporaryAttribute:(CPString)attributeName forCharacterRange:(CPRange)charRange +{ + // FIXME +} + +- (CPDictionary)temporaryAttributesAtCharacterIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveRange +{ + // FIXME +} + +- (void)textContainerChangedTextView:(CPTextContainer)aContainer +{ + // FIXME +} + +- (void)_appendNewLineFragmentInTextContainer:(CPTextContainer)aTextContainer forGlyphRange:(CPRange)glyphRange +{ + _lineFragments.push([[_lineFragmentFactory alloc] initWithRange:glyphRange textContainer:aTextContainer textStorage:_textStorage]); +} + +- (void)setTextContainer:(CPTextContainer)aTextContainer forGlyphRange:(CPRange)glyphRange +{ + var fragments = _objectsInRange(_lineFragments, glyphRange), + l = fragments.length; + + for (var i = 0; i < l; i++) + fragments[i]._textContainer = aTextContainer; +} + +- (id)_lineFragmentForLocation:(unsigned) aLoc +{ + var fragments = _objectsInRange(_lineFragments, CPMakeRange(aLoc, 0)), + l = fragments.length; + + if (l > 0) + return fragments[0]; + + return nil; +} + +- (id)_firstLineFragmentForLineFromLocation:(unsigned)location +{ + var l = _lineFragments.length; + + for (var i = 0; i < l; i++) + { + if (CPLocationInRange(location, _lineFragments[i]._range)) + { + var j = i; + + while (--j > 0 && !_lineFragments[j]._isLast) + { + // body intentionally left empty + } + + return _lineFragments[j + 1]; + } + } + + return nil; +} +- (id)_lastLineFragmentForLineFromLocation:(unsigned)location +{ + var l = _lineFragments.length; + + for (var i = 0; i < l; i++) + { + if (CPLocationInRange(location, _lineFragments[i]._range)) + { + var j = i; + + while (!_lineFragments[j]._isLast) + j++; + + return _lineFragments[j]; + } + } + + return nil; +} + +- (double)_characterOffsetAtLocation:(unsigned)location +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, location); + + if (!lineFragment) + return 0.0; + + var index = location - lineFragment._range.location; + + return lineFragment._glyphsOffsets[index]; +} + +- (double)_descentAtLocation:(unsigned)location +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, location); + + if (!lineFragment) + return 0.0; + + var index = location - lineFragment._range.location; + + return lineFragment._glyphsFrames[index]._descent; +} + +- (void)setLineFragmentRect:(CGRect)fragmentRect forGlyphRange:(CPRange)glyphRange usedRect:(CGRect)usedRect +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location); + + if (lineFragment) + { + lineFragment._fragmentRect = CGRectCreateCopy(fragmentRect); + lineFragment._usedRect = CGRectCreateCopy(usedRect); + } +} + +- (void)_setAdvancements:(CPArray)someAdvancements forGlyphRange:(CPRange)glyphRange +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location); + + if (lineFragment) + [lineFragment setAdvancements:someAdvancements]; +} + +- (void)setLocation:(CGPoint)aPoint forStartOfGlyphRange:(CPRange)glyphRange +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphRange.location); + + if (lineFragment) + lineFragment._location = CGPointCreateCopy(aPoint); +} + +- (CGRect)extraLineFragmentRect +{ + if (_extraLineFragment) + return CGRectCreateCopy(_extraLineFragment._fragmentRect); + + return CGRectMakeZero(); +} + +- (CPTextContainer)extraLineFragmentTextContainer +{ + if (_extraLineFragment) + return _extraLineFragment._textContainer; + + return nil; +} + +- (CGRect)extraLineFragmentUsedRect +{ + if (_extraLineFragment) + return CGRectCreateCopy(_extraLineFragment._usedRect); + + return CGRectMakeZero(); +} + +- (void)setExtraLineFragmentRect:(CGRect)rect usedRect:(CGRect)usedRect textContainer:(CPTextContainer)textContainer +{ + if (textContainer) + { + _extraLineFragment = {}; + _extraLineFragment._fragmentRect = CGRectCreateCopy(rect); + _extraLineFragment._usedRect = CGRectCreateCopy(usedRect); + _extraLineFragment._textContainer = textContainer; + } + else + { + _extraLineFragment = nil; + } +} + +- (CGRect)usedRectForTextContainer:(CPTextContainer)textContainer +{ + var rect, + l = _lineFragments.length; + + for (var i = 0; i < l; i++) + { + if (_lineFragments[i]._textContainer === textContainer) + { + if (rect) + rect = CGRectUnion(rect, _lineFragments[i]._usedRect); + else + rect = CGRectCreateCopy(_lineFragments[i]._usedRect); + } + } + + return rect ? rect : CGRectMakeZero(); +} + +- (CGRect)lineFragmentRectForGlyphAtIndex:(unsigned)glyphIndex effectiveRange:(CPRangePointer)effectiveGlyphRange +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphIndex); + + if (!lineFragment) + return CGRectMakeZero(); + + if (effectiveGlyphRange) + { + effectiveGlyphRange.location = lineFragment._range.location; + effectiveGlyphRange.length = lineFragment._range.length; + } + + return CGRectCreateCopy(lineFragment._fragmentRect); +} + +- (CGRect)lineFragmentUsedRectForGlyphAtIndex:(unsigned)glyphIndex effectiveRange:(CPRangePointer)effectiveGlyphRange +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, glyphIndex); + + if (!lineFragment) + return CGRectMakeZero(); + + if (effectiveGlyphRange) + { + effectiveGlyphRange.location = lineFragment._range.location; + effectiveGlyphRange.length = lineFragment._range.length; + } + + return CGRectCreateCopy(lineFragment._usedRect); +} + +- (CGPoint)locationForGlyphAtIndex:(unsigned)index +{ + if (_lineFragments.length > 0 && index >= [self numberOfGlyphs] - 1) + { + var lineFragment= _lineFragments[_lineFragments.length - 1], + glyphFrames = [lineFragment glyphFrames]; + + if (glyphFrames.length > 0) + return CGPointCreateCopy(glyphFrames[glyphFrames.length - 1].origin); + } + + var lineFragment = _objectWithLocationInRange(_lineFragments, index); + + if (lineFragment) + { + if (index == lineFragment._range.location) + return CGPointCreateCopy(lineFragment._location); + + var glyphFrames = [lineFragment glyphFrames]; + + return CGPointCreateCopy(glyphFrames[index - lineFragment._range.location].origin); + } + + return CGPointMakeZero(); +} + +- (CPTextContainer)textContainerForGlyphAtIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveGlyphRange withoutAdditionalLayout:(BOOL)flag +{ + var lineFragment = _objectWithLocationInRange(_lineFragments, index); + + if (lineFragment) + { + if (effectiveGlyphRange) + { + effectiveGlyphRange.location = lineFragment._range.location; + effectiveGlyphRange.length = lineFragment._range.length; + } + + return lineFragment._textContainer; + } + + return [_textContainers lastObject]; +} + +- (CPTextContainer)textContainerForGlyphAtIndex:(unsigned)index effectiveRange:(CPRangePointer)effectiveGlyphRange +{ + return [self textContainerForGlyphAtIndex:index effectiveRange:effectiveGlyphRange withoutAdditionalLayout:NO]; +} + +- (CPRange)characterRangeForGlyphRange:(CPRange)aRange actualGlyphRange:(CPRangePointer)actualRange +{ + return _MakeRangeFromAbs([self characterIndexForGlyphAtIndex:aRange.location], + [self characterIndexForGlyphAtIndex:CPMaxRange(aRange)]); +} + +- (unsigned)characterIndexForGlyphAtIndex:(unsigned)index +{ + /* FIXME: stub */ + return index; +} + +- (CPArray)rectArrayForCharacterRange:(CPRange)charRange + withinSelectedCharacterRange:(CPRange)selectedCharRange + inTextContainer:(CPTextContainer)container + rectCount:(CGRectPointer)rectCount +{ + + var rectArray = [], + lineFragments = _objectsInRange(_lineFragments, selectedCharRange); + + if (!lineFragments.length) + return rectArray; + + var containerSize = [container containerSize]; + + for (var i = 0; i < lineFragments.length; i++) + { + var fragment = lineFragments[i]; + + if (fragment._textContainer === container) + { + var frames = [fragment glyphFrames], + rect = nil, + len = fragment._range.length; + + for (var j = 0; j < len; j++) + { + if (CPLocationInRange(fragment._range.location + j, selectedCharRange)) + { + var correctedRect = CGRectCreateCopy(frames[j]); + correctedRect.size.height -= frames[j]._descent; + correctedRect.origin.y -= frames[j]._descent; + if (!rect) + rect = CGRectCreateCopy(correctedRect); + else + rect = CGRectUnion(rect, correctedRect); + + if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)])) + { + rect.size.width = containerSize.width - rect.origin.x; + } + } + } + + if (rect) + rectArray.push(rect); + } + } + + var len = rectArray.length; + + for (var i = 0; i < len - 1; i++) // extend the width of all but the last one + { + if (FLOOR(CGRectGetMaxY(rectArray[i])) == FLOOR(CGRectGetMaxY(rectArray[i + 1]))) + continue; + + rectArray[i].size.width = containerSize.width - rectArray[i].origin.x; + } + + return rectArray; +} + +@end + + +var CPLayoutManagerTextStorageKey = @"CPLayoutManagerTextStorageKey"; + +@implementation CPLayoutManager (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + if (self) + { + [self _init]; + + _textStorage = [aCoder decodeObjectForKey:CPLayoutManagerTextStorageKey]; + [_textStorage addLayoutManager:self]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeObject:_textStorage forKey:CPLayoutManagerTextStorageKey]; +} + +@end + + +@implementation CPArray (SortedSearching) + +- (unsigned)_indexOfObject:(id)anObject sortedByFunction:(Function)aFunction context:(id)aContext +{ + var length= self.length; + + if (!aFunction) + return CPNotFound; + + if (length === 0) + return -1; + + var mid, + c, + first = 0, + last = length - 1; + + while (first <= last) + { + mid = FLOOR((first + last) / 2); + c = aFunction(anObject, self[mid], aContext); + + if (c > 0) + { + first = mid + 1; + } + else if (c < 0) + { + last = mid - 1; + } + else + { + while (mid < length - 1 && aFunction(anObject, self[mid + 1], aContext) == CPOrderedSame) + mid++; + + return mid; + } + } + + var result = -first - 1; + + return result >= 0 ? result : CPNotFound; +} + +@end + +var _sortRange = function(location, anObject) +{ + if (CPLocationInRange(location, anObject._range)) + return CPOrderedSame; + else if (CPMaxRange(anObject._range) <= location) + return CPOrderedDescending; + else + return CPOrderedAscending; +} + +var _objectWithLocationInRange = function(aList, aLocation) +{ + var index = [aList _indexOfObject:aLocation sortedByFunction:_sortRange context:nil]; + + if (index != CPNotFound) + return aList[index]; + + return nil; +} + +var _objectsInRange = function(aList, aRange) +{ + var firstIndex = [aList _indexOfObject:aRange.location sortedByFunction:_sortRange context:nil], + lastIndex = [aList _indexOfObject:CPMaxRange(aRange) sortedByFunction:_sortRange context:nil]; + + if (firstIndex === CPNotFound) + firstIndex = 0; + + if (lastIndex === CPNotFound) + lastIndex = aList.length - 1; + + return aList.slice(firstIndex, lastIndex + 1); +} + +@implementation _CPLineFragment : CPObject +{ + CPArray _glyphsFrames @accessors(getter=glyphFrames); + CPArray _glyphsOffsets; + + BOOL _isInvalid; + BOOL _isLast; + CGRect _fragmentRect; + CGRect _usedRect; + CGPoint _location; + CPRange _range; + CPTextContainer _textContainer; + CPMutableArray _runs; +} + +#pragma mark - +#pragma mark Init methods + +- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)aColor +{ +#if PLATFORM(DOM) + var style, + span = document.createElement("span"); + + span.oncontextmenu = span.onmousedown = span.onselectstart = _oncontextmenuhandler; + // span.contentEditable = true; // this unfortunately does not work to make native pasting work on safari + + style = span.style; + style.position = "absolute"; + style.visibility = "visible"; + style.padding = "0px"; + style.margin = "0px"; + style.whiteSpace = "pre"; + style.backgroundColor = "transparent"; + style.font = [aFont cssString]; + + if (aColor) + style.color = [aColor cssString]; + + if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature)) + span.innerText = aString; + else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature)) + span.textContent = aString; + + // FIXME aString.replace(/&/g,'&') + return span; +#else + return nil; +#endif +} + +- (id)initWithRange:(CPRange)aRange textContainer:(CPTextContainer)aContainer textStorage:(CPTextStorage)textStorage +{ + if (self = [super init]) + { + var effectiveRange = CPMakeRange(0,0), + location; + + _fragmentRect = CGRectMakeZero(); + _usedRect = CGRectMakeZero(); + _location = CGPointMakeZero(); + _range = CPMakeRangeCopy(aRange); + _textContainer = aContainer; + _isInvalid = NO; + _runs = [[CPMutableArray alloc] init]; + + for (location = aRange.location; location < CPMaxRange(aRange); location = CPMaxRange(effectiveRange)) + { + var attributes = [textStorage attributesAtIndex:location effectiveRange:effectiveRange]; + + effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange; + + var string = [textStorage._string substringWithRange:effectiveRange], + font = [textStorage font] || [CPFont systemFontOfSize:12.0]; + + if ([attributes containsKey:CPFontAttributeName]) + font = [attributes objectForKey:CPFontAttributeName]; + + var color = [attributes objectForKey:CPForegroundColorAttributeName], + elem = [self createDOMElementWithText:string andFont:font andColor:color], + run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string}; + + _runs.push(run); + + if (!CPMaxRange(effectiveRange)) + break; + } + } + + return self; +} + +- (void)setAdvancements:(CPArray)someAdvancements +{ + var count = someAdvancements.length, + origin = CGPointMake(_fragmentRect.origin.x + _location.x, _fragmentRect.origin.y), + height = _usedRect.size.height; + + _glyphsFrames = new Array(count); + _glyphsOffsets = new Array(count); + + for (var i = 0; i < count; i++) + { + _glyphsFrames[i] = CGRectMake(origin.x, origin.y, someAdvancements[i].width, height); + _glyphsFrames[i]._descent = someAdvancements[i].descent + _glyphsOffsets[i] = height - someAdvancements[i].height; + origin.x += someAdvancements[i].width; + } +} + +- (void)_adjustForHeight:(double)height +{ + var count = _glyphsFrames.length; + + for (var i = 0; i < count; i++) + _glyphsFrames[i].origin.y += (height - _fragmentRect.size.height); + + _fragmentRect.size.height = height; +} + +- (CPString)description +{ + return [super description] + + "\n\t_fragmentRect="+CPStringFromRect(_fragmentRect) + + "\n\t_usedRect="+CPStringFromRect(_usedRect) + + "\n\t_location="+CPStringFromPoint(_location) + + "\n\t_range="+CPStringFromRange(_range); +} + +- (void)drawUnderlineForGlyphRange:(CPRange)glyphRange + underlineType:(int)underlineVal + baselineOffset:(float)baselineOffset + containerOrigin:(CGPoint)containerOrigin +{ +// FIXME +} + +- (void)invalidate +{ + _isInvalid = YES; +} + +- (void)_deinvalidate +{ + _isInvalid = NO; +} + +- (void)_removeFromDOM +{ + var l = _runs.length; + + for (var i = 0; i < l; i++) + { + if (_runs[i].elem && _runs[i].DOMactive) + _textContainer._textView._DOMElement.removeChild(_runs[i].elem); + + _runs[i].elem = nil; + _runs[i].DOMactive = NO; + } +} + +- (void)drawInContext:(CGContext)context atPoint:(CGPoint)aPoint forRange:(CPRange)aRange +{ + var runs = _objectsInRange(_runs, aRange), + c = runs.length, + orig = CGPointMake(_fragmentRect.origin.x, _fragmentRect.origin.y); + + for (var i = 0; i < c; i++) + { + var run = runs[i]; + + if (!run.elem && CPRectIntersectsRect([_textContainer._textView exposedRect], _fragmentRect)) + { + run.elem=[self createDOMElementWithText:run.string andFont:run.font andColor:run.color]; + } + + if (run.DOMactive && !run.DOMpatched) + continue; + + if (!_glyphsFrames) + continue; + + var loc = run._range.location - _runs[0]._range.location; + orig.x = _glyphsFrames[loc].origin.x + aPoint.x; + orig.y = _glyphsFrames[loc].origin.y + aPoint.y + _glyphsOffsets[loc]; + + if(run.elem) + { + run.elem.style.left = (orig.x) + "px"; + run.elem.style.top = (orig.y) + "px"; + + if (!run.DOMactive) + _textContainer._textView._DOMElement.appendChild(run.elem); + + run.DOMactive = YES; + } + + run.DOMpatched = NO; + + } +} + +- (void)backgroundColorForGlyphAtIndex:(unsigned)index +{ + var run = _objectWithLocationInRange(_runs, index); + + if (run) + return run.backgroundColor; + + return [CPColor clearColor]; +} + +- (BOOL)isVisuallyIdenticalToFragment:(_CPLineFragment)newLineFragment +{ + var newFragmentRuns= newLineFragment._runs, + oldFragmentRuns= _runs; + + if (!oldFragmentRuns || !newFragmentRuns || oldFragmentRuns.length !== newFragmentRuns.length) + return NO; + + var l = oldFragmentRuns.length; + + for (var i = 0; i < l; i++) + { + if (newFragmentRuns[i].string !== oldFragmentRuns[i].string) + return NO; + + if (!_RectEqualToRectHorizontally(newLineFragment._fragmentRect, _fragmentRect)) + return NO; + + if (newFragmentRuns[i].color !== oldFragmentRuns[i].color || newFragmentRuns[i].font !== oldFragmentRuns[i].font) + return NO; + + } + + return YES; +} + +- (void)_relocateVerticallyByY:(double)verticalOffset rangeOffset:(unsigned)rangeOffset +{ + var l = _runs.length; + + _range.location += rangeOffset; + + for (var i = 0; i < l; i++) + { + _runs[i]._range.location += rangeOffset; + + if (verticalOffset && _runs[i].elem) + { + _runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px'; + _runs[i].DOMpatched = YES; + } + } + + if (!verticalOffset) + return NO; + + _fragmentRect.origin.y += verticalOffset; + _usedRect.origin.y += verticalOffset; + + var l = _glyphsFrames.length; + + for (var i = 0; i < l ; i++) + { + _glyphsFrames[i].origin.y += verticalOffset; + } +} + +@end + +@implementation _CPTemporaryAttributes : CPObject +{ + CPDictionary _attributes; + CPRange _range; +} + +- (id)initWithRange:(CPRange)aRange attributes:(CPDictionary)attributes +{ + if (self = [super init]) + { + _attributes = attributes; + _range = CPMakeRangeCopy(aRange); + } + + return self; +} + +- (CPString)description +{ + return [super description] + + "\n\t_range="+CPStringFromRange(_range) + + "\n\t_attributes="+[_attributes description]; +} + +@end \ No newline at end of file diff --git a/AppKit/CPTextView/CPParagraphStyle.j b/AppKit/CPTextView/CPParagraphStyle.j new file mode 100644 index 000000000..54d88ee73 --- /dev/null +++ b/AppKit/CPTextView/CPParagraphStyle.j @@ -0,0 +1,223 @@ +/* + * CPParagraphStyle.j + * AppKit + * + * FIXME + * This is basically a stub. + * We need to store all the spacing informations as well as writing direction (among others) + * + * Created by Daniel Boehringer on 11/01/2014 + * Copyright Daniel Boehringer 2014. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import +@import + +@import "CPText.j" + +CPLeftTabStopType = 0; + +CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName"; + +var _sharedDefaultParagraphStyle, + _defaultTabStopArray; + +@implementation CPParagraphStyle : CPObject +{ + CPArray _tabStops @accessors(property=tabStops); + CPTextAlignment _alignment @accessors(property=alignment); + unsigned _firstLineHeadIndent @accessors(property=firstLineHeadIndent); + unsigned _headIndent @accessors(property=headIndent); + unsigned _tailIndent @accessors(property=tailIndent); + unsigned _paragraphSpacing @accessors(property=paragraphSpacing); + unsigned _minimumLineHeight @accessors(property=minimumLineHeight); + unsigned _maximumLineHeight @accessors(property=maximumLineHeight); + unsigned _lineSpacing @accessors(property=lineSpacing); +} + + +#pragma mark - +#pragma mark Class methods + ++ (CPParagraphStyle)defaultParagraphStyle +{ + if (!_sharedDefaultParagraphStyle) + _sharedDefaultParagraphStyle = [self new]; + + return _sharedDefaultParagraphStyle; +} + ++ (CPArray)_defaultTabStops +{ + if (!_defaultTabStopArray) + { + var i; + _defaultTabStopArray = []; + + // FIXME: Define constants for these magic numbers: 13, 28 + for (i = 1; i < 16 ; i++) + { + _defaultTabStopArray.push([[CPTextTab alloc] initWithType:CPLeftTabStopType location:i * 28]); + } + } + + return _defaultTabStopArray; +} + + +#pragma mark - +#pragma mark Init methods + +- (id)init +{ + [self _initWithDefaults]; + + return self; +} + +- (CPParagraphStyle)initWithParagraphStyle:(CPParagraphStyle)other +{ + self = [super init]; + + _tabStops = [other._tabStops copy]; + _alignment = other._alignment; + _firstLineHeadIndent = other._firstLineHeadIndent; + _headIndent = other._headIndent; + _tailIndent = other._tailIndent; + _paragraphSpacing = other._paragraphSpacing; + _minimumLineHeight = other._minimumLineHeight; + _maximumLineHeight = other._maximumLineHeight; + _lineSpacing = other._lineSpacing; + + return self; +} + +- (void)_initWithDefaults +{ + _alignment = CPLeftTextAlignment; + _tabStops = [[[self class] _defaultTabStops] copy]; +} + +- (void)addTabStop:(CPTextTab)aStop +{ + _tabStops.push(aStop); +} + +- (id)copy +{ + var other = [[self class] alloc]; + + return [other initWithParagraphStyle:self]; +} + +@end + + +var CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey", + CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey", + CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey", + CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey", + CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey", + CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey", + CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey", + CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey", + CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey"; + +@implementation CPParagraphStyle (CPCoding) + +- (id)initWithCoder:(id)aCoder +{ + self = [self init]; + + if (self) + { + _tabStops = [aCoder decodeObjectForKey:"CPParagraphStyleTabStopsKey"]; + _alignment = [aCoder decodeIntForKey:"CPParagraphStyleAlignmentKey"]; + _firstLineHeadIndent = [aCoder decodeIntForKey:"CPParagraphStyleFirstLineHeadIndentKey"]; + _headIndent = [aCoder decodeIntForKey:"CPParagraphStyleHeadIndentKey"]; + _tailIndent = [aCoder decodeIntForKey:"CPParagraphStyleTailIndentKey"]; + _paragraphSpacing = [aCoder decodeIntForKey:"CPParagraphStyleParagraphSpacingKey"]; + _minimumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMinimumLineHeightKey"]; + _maximumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMaximumLineHeightKey"]; + _lineSpacing = [aCoder decodeIntForKey:"CPParagraphStyleLineSpacingKey"]; + } + + return self; +} + +- (void)encodeWithCoder:(id)aCoder +{ + [aCoder encodeInt:_alignment forKey:"CPParagraphStyleAlignmentKey"]; + [aCoder encodeObject:_tabStops forKey:"CPParagraphStyleTabStopsKey"]; + [aCoder encodeInt:_firstLineHeadIndent forKey:"CPParagraphStyleFirstLineHeadIndentKey"]; + [aCoder encodeInt:_headIndent forKey:"CPParagraphStyleHeadIndentKey"]; + [aCoder encodeInt:_tailIndent forKey:"CPParagraphStyleTailIndentKey"]; + [aCoder encodeInt:_paragraphSpacing forKey:"CPParagraphStyleParagraphSpacingKey"]; + [aCoder encodeInt:_minimumLineHeight forKey:"CPParagraphStyleMinimumLineHeightKey"]; + [aCoder encodeInt:_maximumLineHeight forKey:"CPParagraphStyleMaximumLineHeightKey"]; + [aCoder encodeInt:_lineSpacing forKey:"CPParagraphStyleLineSpacingKey"]; +} + + +@end + + +@implementation CPTextTab : CPObject +{ + int _type @accessors(property = tabStopType); + double _location @accessors(property = location); +} + +- (id)initWithType:(CPTabStopType) aType location:(double) aLocation +{ + if ([self = [super init]]) + { + _type = aType; + _location = aLocation; + } + + return self; +} + +@end + + +var CPTextTabTypeKey = @"CPTextTabTypeKey", + CPTextTabLocationKey = @"CPTextTabLocationKey"; + +@implementation CPTextTab (CPCoding) + +- (id)initWithCoder:(id)aCoder +{ + self = [self init]; + + if (self) + { + _type = [aCoder decodeIntForKey:"CPTextTabTypeKey"]; + _location = [aCoder decodeDoubleForKey:"CPTextTabLocationKey"]; + } + + return self; +} + +- (void)encodeWithCoder:(id)aCoder +{ + [aCoder encodeInt:_type forKey:"CPTextTabTypeKey"]; + [aCoder encodeDouble:_location forKey:"CPTextTabLocationKey"]; +} + +@end diff --git a/AppKit/CPTextView/CPTextContainer.j b/AppKit/CPTextView/CPTextContainer.j new file mode 100644 index 000000000..5c3f485a9 --- /dev/null +++ b/AppKit/CPTextView/CPTextContainer.j @@ -0,0 +1,255 @@ +/* + * CPTextContainer.j + * AppKit + * + * Created by Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import +@import "CPLayoutManager.j" + +@class CPTextView +@class CPLayoutManager + +/* + @global + @group CPLineSweepDirection +*/ +CPLineSweepLeft = 0; +/* + @global + @group CPLineSweepDirection +*/ +CPLineSweepRight = 1; +/* + @global + @group CPLineSweepDirection +*/ +CPLineSweepDown = 2; +/* + @global + @group CPLineSweepDirection +*/ +CPLineSweepUp = 3; + +/* + @global + @group CPLineMovementDirection +*/ +CPLineDoesntMoves = 0; +/* + @global + @group CPLineMovementDirection +*/ +CPLineMovesLeft = 1; +/* + @global + @group CPLineMovementDirection +*/ +CPLineMovesRight = 2; +/* + @global + @group CPLineMovementDirection +*/ +CPLineMovesDown = 3; +/* + @global + @group CPLineMovementDirection +*/ +CPLineMovesUp = 4; + +/*! + @ingroup appkit + @class CPTextContainer +*/ +@implementation CPTextContainer : CPObject +{ + float _lineFragmentPadding @accessors(property=lineFragmentPadding); + CGSize _size @accessors(property=containerSize) + CPLayoutManager _layoutManager @accessors(property=layoutManager); + CPTextView _textView @accessors(property=textView); + BOOL _inResizing; +} + + +#pragma mark - +#pragma mark Init methods + +- (id)initWithContainerSize:(CGSize)aSize +{ + self = [super init]; + + if (self) + { + _size = aSize; + [self _init]; + } + + return self; +} + +- (id)init +{ + return [self initWithContainerSize:CPMakeSize(1e7, 1e7)]; +} + +- (void)_init +{ + _lineFragmentPadding = 0.0; + + _layoutManager = [[CPLayoutManager alloc] init]; + [_layoutManager addTextContainer:self]; +} + +#pragma mark - +#pragma mark Setter methods + +- (void)setContainerSize:(CGSize)someSize +{ + var oldSize = _size; + + _size = CGSizeMakeCopy(someSize); + + if (oldSize.width != _size.width) + { + _inResizing = YES; + [_layoutManager invalidateLayoutForCharacterRange:CPMakeRange(0, [[_layoutManager textStorage] length]) + isSoft:NO + actualCharacterRange:NULL]; + + [_layoutManager _validateLayoutAndGlyphs]; + [_textView sizeToFit]; // this is necessary to adopt the height of CPTextView in case of rewrapping + _inResizing = NO; + } +} + +// Controls whether the receiver adjusts the width of its bounding rectangle when its text view is resized. +- (void)setWidthTracksTextView:(BOOL)flag +{ + [_textView setPostsFrameChangedNotifications:flag]; + + if (flag) + { + [[CPNotificationCenter defaultCenter] addObserver:self + selector:@selector(textViewFrameChanged:) + name:CPViewFrameDidChangeNotification + object:_textView]; + } + else + { + [[CPNotificationCenter defaultCenter] removeObserver:self + name:CPViewFrameDidChangeNotification + object:_textView]; + } +} + +- (void)textViewFrameChanged:(CPNotification)aNotification +{ + var newSize = CGSizeMake([_textView frame].size.width, _size.height); + + [self setContainerSize:newSize]; +} + +- (void)setTextView:(CPTextView)aTextView +{ + if (_textView) + { + [self _removeAllLines]; + [_textView setTextContainer:nil]; + } + + _textView = aTextView; + + if (_textView) + [_textView setTextContainer:self]; + + [_layoutManager textContainerChangedTextView:self]; +} + +- (BOOL)containsPoint:(CGPoint)aPoint +{ + return CGRectContainsPoint(CGRectMake(0, 0, _size.width, _size.height), aPoint); +} + +- (BOOL)isSimpleRectangularTextContainer +{ + return YES; +} + +- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect + sweepDirection:(CPLineSweepDirection)sweep + movementDirection:(CPLineMovementDirection)movement + remainingRect:(CGRectPointer)remainingRect +{ + var resultRect = CGRectCreateCopy(proposedRect); + + if (sweep != CPLineSweepRight || movement != CPLineMovesDown) + { + CPLog.trace(@"FIXME: unsupported sweep (" + sweep + ") or movement (" + movement + ")"); + return CGRectMakeZero(); + } + + if (resultRect.origin.x + resultRect.size.width > _size.width) + resultRect.size.width = _size.width - resultRect.origin.x; + + if (resultRect.size.width < 0) + resultRect = CGRectMakeZero(); + + if (remainingRect) + { + remainingRect.origin.x = resultRect.origin.x + resultRect.size.width; + remainingRect.origin.y = resultRect.origin.y; + remainingRect.size.height = resultRect.size.height; + remainingRect.size.width = _size.width - (resultRect.origin.x + resultRect.size.width); + } + + return resultRect; +} + +@end + + +var CPTextContainerSizeKey = @"CPTextContainerSizeKey", + CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey"; + +@implementation CPTextContainer (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + if (self) + { + [self _init]; + + _size = [aCoder decodeSizeForKey:CPTextContainerSizeKey]; + + _layoutManager = [aCoder decodeObjectForKey:CPTextContainerLayoutManagerKey]; + [_layoutManager addTextContainer:self]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [aCoder encodeSize:_size forKey:CPTextContainerSizeKey]; + [aCoder encodeObject:_layoutManager forKey:CPTextContainerLayoutManagerKey]; +} + +@end \ No newline at end of file diff --git a/AppKit/CPTextView/CPTextStorage.j b/AppKit/CPTextView/CPTextStorage.j new file mode 100644 index 000000000..65a71cf1a --- /dev/null +++ b/AppKit/CPTextView/CPTextStorage.j @@ -0,0 +1,303 @@ +/* + * CPTextStorage.j + * AppKit + * + * Created by Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +@import +@import + +@import "CPText.j" +@import "CPFont.j" + +@class CPLayoutManager; + +CPTextStorageEditedAttributes = 1; +CPTextStorageEditedCharacters = 2; + +CPTextStorageWillProcessEditingNotification = @"CPTextStorageWillProcessEditingNotification"; +CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNotification"; + +@protocol CPTextStorageDelegate + +- (void)textStorageWillProcessEditing:(CPNotification)aNotification; +- (void)textStorageDidProcessEditing:(CPNotification)aNotification; + +@end + + +var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1, + CPTextStorageDelegate_textStorageDidProcessEditing_ = 1 << 2; + +/*! + @ingroup appkit + @class CPTextStorage +*/ +@implementation CPTextStorage : CPMutableAttributedString +{ + CPColor _foregroundColor @accessors(property=foregroundColor); + CPFont _font @accessors(property=font); + CPMutableArray _layoutManagers @accessors(getter=layoutManagers); + CPRange _editedRange @accessors(getter=editedRange); + id _delegate @accessors(property=delegate); + int _changeInLength @accessors(property=changeInLength); + unsigned _editedMask @accessors(property=editedMask); + + int _editCount; // {begin,end}Editing counter + unsigned _implementedDelegateMethods; +} + + +#pragma mark - +#pragma mark Init methods + +- (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes +{ + self = [super initWithString:aString attributes:attributes]; + + if (self) + { + _layoutManagers = [[CPMutableArray alloc] init]; + _editedRange = CPMakeRange(CPNotFound, 0); + _changeInLength = 0; + _editedMask = 0; + } + + return self; +} + +- (id)initWithString:(CPString)aString +{ + return [self initWithString:aString attributes:nil]; +} + +- (id)init +{ + return [self initWithString:@"" attributes:nil]; +} + + +#pragma mark - +#pragma mark Delegate methods + +- (void)setDelegate:(id )aDelegate +{ + if (_delegate === aDelegate) + return; + + _implementedDelegateMethods = 0; + _delegate = aDelegate; + + if (_delegate) + { + if ([_delegate respondsToSelector:@selector(textStorageWillProcessEditing:)]) + _implementedDelegateMethods |= CPTextStorageDelegate_textStorageWillProcessEditing_; + + if ([_delegate respondsToSelector:@selector(textStorageDidProcessEditing:)]) + _implementedDelegateMethods |= CPTextStorageDelegate_textStorageDidProcessEditing_; + } +} + + +#pragma mark - +#pragma mark Layout manager methods + +- (void)addLayoutManager:(CPLayoutManager)aManager +{ + if ([_layoutManagers containsObject:aManager]) + return + + [aManager setTextStorage:self]; + [_layoutManagers addObject:aManager]; +} + +- (void)removeLayoutManager:(CPLayoutManager)aManager +{ + if (![_layoutManagers containsObject:aManager]) + return + + [aManager setTextStorage:nil]; + [_layoutManagers removeObject:aManager]; +} + +- (void)invalidateAttributesInRange:(CPRange)aRange +{ + /* FIXME: stub */ +} + + +#pragma mark - +#pragma mark Editing methods + +- (void)processEditing +{ + [self _sendDelegateWillProcessEditingNotification]; + [self invalidateAttributesInRange:[self editedRange]]; + [self _sendDelegateDidProcessEditingNotification]; + + var c = [_layoutManagers count]; + + for (var i = 0; i < c; i++) + { + [[_layoutManagers objectAtIndex:i] textStorage:self + edited:_editedMask + range:_editedRange + changeInLength:_changeInLength + invalidatedRange:_editedRange]; + } + + _editedRange.location = CPNotFound; + _editedMask = 0; + _changeInLength = 0; +} + +- (void)beginEditing +{ + if (_editCount == 0) + _editedRange = CPMakeRange(CPNotFound, 0); + + _editCount++; +} + +- (void)endEditing +{ + _editCount--; + + if (_editCount == 0) + [self processEditing]; +} + +- (void)edited:(unsigned)editedMask range:(CPRange)aRange changeInLength:(int)lengthChange +{ + var copyRange = CPMakeRangeCopy(aRange); + + if (_editCount == 0) // used outside a beginEditing/endEditing + { + _editedMask = editedMask; + _changeInLength = lengthChange; + copyRange.length += lengthChange; + _editedRange = copyRange; + [self processEditing]; + } + else + { + _editedMask |= editedMask; + _changeInLength += lengthChange; + copyRange.length += lengthChange; + + if (_editedRange.location == CPNotFound) + _editedRange = copyRange; + else + _editedRange = CPUnionRange(_editedRange,copyRange); + } +} + +- (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange +{ + [self beginEditing]; + [super removeAttribute:anAttribute range:aRange]; + [self edited:CPTextStorageEditedAttributes range:aRange changeInLength:0]; + [self endEditing]; +} + +- (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange +{ + [self beginEditing]; + [super addAttributes:aDictionary range:aRange]; + [self edited:CPTextStorageEditedAttributes range:aRange changeInLength:0]; + [self endEditing]; +} + +- (void)deleteCharactersInRange:(CPRange)aRange +{ + [self beginEditing]; + [super deleteCharactersInRange:aRange]; + [self edited:CPTextStorageEditedCharacters range:aRange changeInLength:-aRange.length]; + [self endEditing]; +} + +- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString +{ + [self beginEditing]; + [super replaceCharactersInRange:aRange withString:aString]; + [self edited:CPTextStorageEditedCharacters range:aRange changeInLength:([aString length] - aRange.length)]; + [self endEditing]; +} + +- (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString +{ + [self beginEditing]; + [super replaceCharactersInRange:aRange withAttributedString:aString]; + [self edited:(CPTextStorageEditedAttributes | CPTextStorageEditedCharacters) range:aRange changeInLength:([aString length] - aRange.length)]; + [self endEditing]; +} + +- (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange +{ + if (!aRange.length) + return [CPAttributedString new]; + + return [super attributedSubstringFromRange:aRange]; +} + +@end + + +@implementation CPTextStorage (CPTextStorageDelegate) + +- (void)_sendDelegateWillProcessEditingNotification +{ + if (_implementedDelegateMethods & CPTextStorageDelegate_textStorageWillProcessEditing_) + [_delegate textStorageWillProcessEditing:[[CPNotification alloc] initWithName:CPTextStorageWillProcessEditingNotification object:self userInfo:nil]]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextStorageWillProcessEditingNotification object:self]; +} + +- (void)_sendDelegateDidProcessEditingNotification +{ + if (_implementedDelegateMethods & CPTextStorageDelegate_textStorageDidProcessEditing_) + [_delegate textStorageWillProcessEditing:[[CPNotification alloc] initWithName:CPTextStorageDidProcessEditingNotification object:self userInfo:nil]]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextStorageDidProcessEditingNotification object:self]; +} + +@end + + +@implementation CPTextStorage (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super initWithCoder:aCoder]; + + if (self) + { + + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [super encodeWithCoder:aCoder]; +} + +@end \ No newline at end of file diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j new file mode 100644 index 000000000..7080aa297 --- /dev/null +++ b/AppKit/CPTextView/CPTextView.j @@ -0,0 +1,2670 @@ +/* + * CPTextView.j + * AppKit + * + * Created by Daniel Boehringer on 27/12/2013. + * All modifications copyright Daniel Boehringer 2013. + * Extensive code formatting and review by Andrew Hankinson + * Based on original work by + * Created by Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import "CPText.j" +@import "CPPasteboard.j" +@import "CPColorPanel.j" +@import "CPFontManager.j" +@import "CPTextStorage.j" +@import "CPTextContainer.j" +@import "CPLayoutManager.j" + +@import "_CPRTFParser.j" +@import "_CPRTFProducer.j" + + +@class CPClipView; +@class _CPSelectionBox; +@class _CPCaret; +@class _CPNativeInputManager; + +@protocol CPTextViewDelegate + +- (BOOL)textView:(CPTextView)aTextView doCommandBySelector:(SEL)aSelector; +- (BOOL)textView:(CPTextView)aTextView shouldChangeTextInRange:(CPRange)affectedCharRange replacementString:(CPString)replacementString; +- (CPDictionary)textView:(CPTextView)textView shouldChangeTypingAttributes:(CPDictionary)oldTypingAttributes toAttributes:(CPDictionary)newTypingAttributes; +- (CPRange)textView:(CPTextView)aTextView willChangeSelectionFromCharacterRange:(CPRange)oldSelectedCharRange toCharacterRange:(CPRange)newSelectedCharRange; +- (void)textViewDidChangeSelection:(CPNotification)aNotification; +- (void)textViewDidChangeTypingAttributes:(CPNotification)aNotification; + +@end + +_MakeRangeFromAbs = function(a1, a2) +{ + return (a1 < a2) ? CPMakeRange(a1, a2 - a1) : CPMakeRange(a2, a1 - a2); +}; + +_MidRange = function(a1) +{ + return Math.floor((CPMaxRange(a1) + a1.location) / 2); +}; + +function _isWhitespaceCharacter(chr) +{ + return (chr === '\n' || chr === '\r' || chr === ' ' || chr === '\t'); +} + +_characterTripletFromStringAtIndex = function(string, index) +{ + if ([string isKindOfClass:CPAttributedString]) + string = string._string; + + var tripletRange = _MakeRangeFromAbs(MAX(0, index - 1), MIN(string.length, index + 2)); + + return [string substringWithRange:tripletRange]; +} + +_regexMatchesStringAtIndex=function(regex, string, index) +{ + var triplet = _characterTripletFromStringAtIndex(string, index); + + return regex.exec(triplet) !== null; +} + +// these two functions are to support chrome rich native paste +_CPwalkTheDOM = function(node, func) +{ + func(node); + node = node.firstChild; + while (node) + { + _CPwalkTheDOM(node, func); + node = node.nextSibling; + } +} + +/* + CPSelectionGranularity +*/ +@typedef CPSelectionGranularity +CPSelectByCharacter = 0; +CPSelectByWord = 1; +CPSelectByParagraph = 2; + +var kDelegateRespondsTo_textShouldBeginEditing = 1 << 0, + kDelegateRespondsTo_textView_doCommandBySelector = 1 << 1, + kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange = 1 << 2, + kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString = 1 << 3, + kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes = 1 << 4, + kDelegateRespondsTo_textView_textDidChange = 1 << 5, + kDelegateRespondsTo_textView_didChangeSelection = 1 << 6, + kDelegateRespondsTo_textView_didChangeTypingAttributes = 1 << 7; + +@class _CPCaret; + +/*! + @ingroup appkit + @class CPTextView +*/ +@implementation CPTextView : CPText +{ + BOOL _allowsUndo @accessors(property=allowsUndo); + BOOL _isHorizontallyResizable @accessors(getter=isHorizontallyResizable, setter=setHorinzontallyResizable:); + BOOL _isVerticallyResizable @accessors(getter=isVerticallyResizable, setter=setVerticallyResizable:); + BOOL _usesFontPanel @accessors(property=usesFontPanel); + CGPoint _textContainerOrigin @accessors(getter=textContainerOrigin); + CGSize _minSize @accessors(property=minSize); + CGSize _maxSize @accessors(property=maxSize); + CGSize _textContainerInset @accessors(property=textContainerInset); + CPColor _insertionPointColor @accessors(property=insertionPointColor); + CPColor _textColor @accessors(property=textColor); + CPDictionary _selectedTextAttributes @accessors(property=selectedTextAttributes); + CPDictionary _typingAttributes @accessors(property=typingAttributes); + CPFont _font @accessors(property=font); + CPLayoutManager _layoutManager @accessors(getter=layoutManager); + CPRange _selectionRange @accessors(getter=selectedRange); + CPSelectionGranularity _selectionGranularity @accessors(property=selectionGranularity); + + CPSelectionGranularity _previousSelectionGranularity; // private + CPSelectionGranularity _copySelectionGranularity; // private + + CPTextContainer _textContainer @accessors(property=textContainer); + CPTextStorage _textStorage @accessors(getter=textStorage); + id _delegate @accessors(property=delegate); + + unsigned _delegateRespondsToSelectorMask; + + int _startTrackingLocation; + + _CPCaret _caret; + CPTimer _scrollingTimer; + + BOOL _scrollingDownward; + + int _stickyXLocation; + + CPArray _selectionSpans; + CPView _observedClipView; + CGRect _exposedRect; + + CPTimer _scrollingTimer; +} + + +#pragma mark - +#pragma mark Class methods + +/* FIXME + just a testing characterSet + all of this depend of the current language. + Need some CPLocale support and maybe even a FSM... + */ + +#pragma mark - +#pragma mark Init methods + +- (id)initWithFrame:(CGRect)aFrame textContainer:(CPTextContainer)aContainer +{ + if (self = [super initWithFrame:aFrame]) + { + [self _init]; + [aContainer setTextView:self]; + + [self setEditable:YES]; + [self setSelectable:YES]; + [self setRichText:NO]; + [self setBackgroundColor:[CPColor whiteColor]]; + + _usesFontPanel = YES; + _allowsUndo = YES; + + _selectedTextAttributes = [CPDictionary dictionaryWithObject:[CPColor selectedTextBackgroundColor] + forKey:CPBackgroundColorAttributeName]; + + _insertionPointColor = [CPColor blackColor]; + + _textColor = [CPColor blackColor]; + _font = [CPFont systemFontOfSize:12.0]; + [self setFont:_font]; + + _typingAttributes = [[CPDictionary alloc] initWithObjects:[_font, _textColor] forKeys:[CPFontAttributeName, CPForegroundColorAttributeName]]; + } + + [self registerForDraggedTypes:[CPColorDragType]]; + + return self; +} + +- (id)initWithFrame:(CGRect)aFrame +{ + var container = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(aFrame.size.width, 1e7)]; + + return [self initWithFrame:aFrame textContainer:container]; +} + +- (void)_init +{ +#if PLATFORM(DOM) + _DOMElement.style.cursor = "text"; +#endif + + _selectionRange = CPMakeRange(0, 0); + _textContainerInset = CGSizeMake(2, 0); + _textContainerOrigin = CGPointMake(_bounds.origin.x, _bounds.origin.y); + + _selectionGranularity = CPSelectByCharacter; + + _minSize = CGSizeCreateCopy(_frame.size); + _maxSize = CGSizeMake(_frame.size.width, 1e7); + + _isVerticallyResizable = YES; + _isHorizontallyResizable = NO; + + _typingAttributes = [CPMutableDictionary new]; + _selectedTextAttributes = [CPMutableDictionary new]; + + _caret = [[_CPCaret alloc] initWithTextView:self]; + [_caret setRect:CGRectMake(0, 0, 1, 11)] +} + +- (void)_setObserveWindowKeyNotifications:(BOOL)shouldObserve +{ + if (shouldObserve) + { + [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidResignKey:) name:CPWindowDidResignKeyNotification object:[self window]]; + [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_windowDidBecomeKey:) name:CPWindowDidBecomeKeyNotification object:[self window]]; + } + else + { + [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidResignKeyNotification object:[self window]]; + [[CPNotificationCenter defaultCenter] removeObserver:self name:CPWindowDidBecomeKeyNotification object:[self window]]; + } +} + +- (void)_removeObservers +{ + if (!_isObserving) + return; + + [super _removeObservers]; + [self _setObserveWindowKeyNotifications:NO]; +} + +- (void)_addObservers +{ + if (_isObserving) + return; + + [super _addObservers]; + [self _setObserveWindowKeyNotifications:YES]; + [self _startObservingClipView]; +} +- (void)_startObservingClipView +{ + if (!_observedClipView) + return; + + var defaultCenter = [CPNotificationCenter defaultCenter]; + + [_observedClipView setPostsFrameChangedNotifications:YES]; + [_observedClipView setPostsBoundsChangedNotifications:YES]; + + [defaultCenter addObserver:self + selector:@selector(superviewFrameChanged:) + name:CPViewFrameDidChangeNotification + object:_observedClipView]; + + [defaultCenter addObserver:self + selector:@selector(superviewBoundsChanged:) + name:CPViewBoundsDidChangeNotification + object:_observedClipView]; +} +- (CGRect)exposedRect +{ + if (!_exposedRect) + { + var superview = [self superview]; + + if ([superview isKindOfClass:[CPClipView class]]) + _exposedRect = [superview bounds]; + else + _exposedRect = [self bounds]; + } + + return _exposedRect; +} + +/*! + @ignore +*/ +- (void)superviewBoundsChanged:(CPNotification)aNotification +{ + _exposedRect = nil; + [self setNeedsDisplay:YES]; +} + +/*! + @ignore +*/ +- (void)superviewFrameChanged:(CPNotification)aNotification +{ + _exposedRect = nil; +} + +- (void)viewWillMoveToSuperview:(CPView)aView +{ + if ([aView isKindOfClass:[CPClipView class]]) + _observedClipView = aView; + else + [self _stopObservingClipView]; + + [super viewWillMoveToSuperview:aView]; +} + +- (void)_stopObservingClipView +{ + if (!_observedClipView) + return; + + var defaultCenter = [CPNotificationCenter defaultCenter]; + + [defaultCenter removeObserver:self + name:CPViewFrameDidChangeNotification + object:_observedClipView]; + + [defaultCenter removeObserver:self + name:CPViewBoundsDidChangeNotification + object:_observedClipView]; + + _observedClipView = nil; +} + +- (void)_windowDidResignKey:(CPNotification)aNotification +{ + if (![[self window] isKeyWindow]) + [self resignFirstResponder]; +} + +- (void)_windowDidBecomeKey:(CPNotification)aNotification +{ + if ([self _isFocused]) + [self _becomeFirstResponder]; +} + +#pragma mark - +#pragma mark Copy and paste methods + +- (void)copy:(id)sender +{ + _copySelectionGranularity = _previousSelectionGranularity; + [super copy:sender]; + + if (![self isRichText]) + return; + + var selectedRange = [self selectedRange], + pasteboard = [CPPasteboard generalPasteboard], + stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)], + richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}]; + + [pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType] owner:nil]; + [pasteboard setString:stringForPasting._string forType:CPStringPboardType]; + [pasteboard setString:richData forType:CPRTFPboardType]; +} + +- (void)paste:(id)sender +{ + if (![sender isKindOfClass:_CPNativeInputManager] && [[CPApp currentEvent] type] != CPAppKitDefined) + return + + var stringForPasting = [self _stringForPasting]; + + if (!stringForPasting) + return; + + if (_copySelectionGranularity > 0 && _selectionRange.location > 0) + { + if (!_isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]) && + _selectionRange.location != [_layoutManager numberOfCharacters]) + { + [self insertText:" "]; + } + } + + if (_copySelectionGranularity == CPSelectByParagraph) + { + var peekStr = stringForPasting, + i = 0; + + if (![stringForPasting isKindOfClass:[CPString class]]) + peekStr = stringForPasting._string; + + while (_isWhitespaceCharacter([peekStr characterAtIndex:i])) + i++; + + if (i) + { + if ([stringForPasting isKindOfClass:[CPString class]]) + stringForPasting = [stringForPasting stringByReplacingCharactersInRange:CPMakeRange(0, i) withString:'']; + else + [stringForPasting replaceCharactersInRange:CPMakeRange(0, i) withString:'']; + } + } + + [self insertText:stringForPasting]; + + if (_copySelectionGranularity > 0) + { + if (!_isWhitespaceCharacter([[_textStorage string] characterAtIndex:CPMaxRange(_selectionRange)]) && + !_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)]) && + _selectionRange.location != [_layoutManager numberOfCharacters]) + { + [self insertText:" "]; + } + } +} + +#pragma mark - +#pragma mark Responders method + +- (BOOL)acceptsFirstResponder +{ + return [self isSelectable]; // editable textviews are automatically selectable +} + +- (void)_becomeFirstResponder +{ + [self updateInsertionPointStateAndRestartTimer:YES]; + [[CPFontManager sharedFontManager] setSelectedFont:[self font] isMultiple:NO]; + [self setNeedsDisplay:YES]; + [[CPRunLoop currentRunLoop] performSelector:@selector(focusForTextView:) target:[_CPNativeInputManager class] argument:self order:0 modes:[CPDefaultRunLoopMode]]; +} + + +- (BOOL)becomeFirstResponder +{ + [super becomeFirstResponder]; + [self _becomeFirstResponder]; + + return YES; +} + +- (BOOL)resignFirstResponder +{ + [_caret stopBlinking]; + [self setNeedsDisplay:YES]; + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; + + return YES; +} + + +#pragma mark - +#pragma mark Delegate methods + +/*! + TODO : documentation +*/ +- (void)setDelegate:(id )aDelegate +{ + if (aDelegate === _delegate) + return; + + _delegateRespondsToSelectorMask = 0; + _delegate = aDelegate; + + if (_delegate) + { + if ([_delegate respondsToSelector:@selector(textDidChange:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_textDidChange; + + if ([_delegate respondsToSelector:@selector(textViewDidChangeSelection:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_didChangeSelection; + + if ([_delegate respondsToSelector:@selector(textViewDidChangeTypingAttributes:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_didChangeTypingAttributes; + + if ([_delegate respondsToSelector:@selector(textView:doCommandBySelector:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_doCommandBySelector; + + if ([_delegate respondsToSelector:@selector(textShouldBeginEditing:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textShouldBeginEditing; + + if ([_delegate respondsToSelector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange; + + if ([_delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementString:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString; + + if ([_delegate respondsToSelector:@selector(textView:shouldChangeTypingAttributes:toAttributes:)]) + _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes; + } +} + + +#pragma mark - +#pragma mark Key window methods + +- (void)becomeKeyWindow +{ + [self setNeedsDisplay:YES]; +} + +/*! + @ignore +*/ +- (void)resignKeyWindow +{ + [self setNeedsDisplay:YES]; +} + +- (BOOL)_isFirstResponder +{ + return [[self window] firstResponder] === self; +} + +- (BOOL)_isFocused +{ + return [[self window] isKeyWindow] && [self _isFirstResponder]; +} + + +#pragma mark - +#pragma mark Undo redo methods + +- (void)undo:(id)sender +{ + if (_allowsUndo) + [[[self window] undoManager] undo]; +} + +- (void)redo:(id)sender +{ + if (_allowsUndo) + [[[self window] undoManager] redo]; +} + + +#pragma mark - +#pragma mark Accessors + +- (CPString)stringValue +{ + return _textStorage._string; +} + +// fixme: rich text should return attributed string, shouldn't it? +- (CPString)objectValue +{ + return [self stringValue]; +} + +- (void)setString:(CPString)aString +{ + [_textStorage replaceCharactersInRange:CPMakeRange(0, [_layoutManager numberOfCharacters]) withString:aString]; + + if (CPMaxRange(_selectionRange) > [_layoutManager numberOfCharacters]) + [self setSelectedRange:CPMakeRange([_layoutManager numberOfCharacters], 0)]; + + [self didChangeText]; + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self setNeedsDisplay:YES]; +} + +- (CPString)string +{ + return [_textStorage string]; +} + +// KVO support +- (void)setValue:(CPString)aValue +{ + [self setString:[aValue description]]; +} + +- (id)value +{ + [self string]; +} + +- (void)setTextContainer:(CPTextContainer)aContainer +{ + _textContainer = aContainer; + _layoutManager = [_textContainer layoutManager]; + _textStorage = [_layoutManager textStorage]; + [_textStorage setFont:_font]; + [_textStorage setForegroundColor:_textColor]; + + [self invalidateTextContainerOrigin]; +} + +- (void)setTextContainerInset:(CGSize)aSize +{ + _textContainerInset = aSize; + [self invalidateTextContainerOrigin]; +} + +- (void)invalidateTextContainerOrigin +{ + _textContainerOrigin.x = _bounds.origin.x; + _textContainerOrigin.x += _textContainerInset.width; + + _textContainerOrigin.y = _bounds.origin.y; + _textContainerOrigin.y += _textContainerInset.height; +} + +- (void)doCommandBySelector:(SEL)aSelector +{ + if (![self _sendDelegateDoCommandBySelector:aSelector]) + [super doCommandBySelector:aSelector]; +} + +- (void)didChangeText +{ + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextDidChangeNotification object:self]; + + if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_textDidChange) + [_delegate textDidChange:[[CPNotification alloc] initWithName:CPTextDidChangeNotification object:self userInfo:nil]]; +} + +- (BOOL)shouldChangeTextInRange:(CPRange)aRange replacementString:(CPString)aString +{ + if (![self isEditable]) + return NO; + + return [self _sendDelegateTextShouldBeginEditing] && [self _sendDelegateShouldChangeTextInRange:aRange replacementString:aString]; +} + + +#pragma mark - +#pragma mark Insert characters methods + +- (void)_fixupReplaceForRange:(CPRange)aRange +{ + [self setSelectedRange:aRange]; + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self scrollRangeToVisible:_selectionRange]; + [self setNeedsDisplay:YES]; +} + +- (void)_replaceCharactersInRange:aRange withAttributedString:(CPString)aString +{ + [[[[self window] undoManager] prepareWithInvocationTarget:self] + _replaceCharactersInRange:CPMakeRange(aRange.location, [aString length]) + withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(aRange)]]; + + [_textStorage replaceCharactersInRange:aRange withAttributedString:aString]; + [self _fixupReplaceForRange:CPMakeRange(CPMaxRange(aRange), 0)]; +} + +- (void)insertText:(CPString)aString +{ + var isAttributed = [aString isKindOfClass:CPAttributedString], + string = isAttributed ? [aString string]:aString; + + if (![self shouldChangeTextInRange:CPMakeRangeCopy(_selectionRange) replacementString:string]) + return; + + if (!isAttributed) + aString = [[CPAttributedString alloc] initWithString:aString attributes:_typingAttributes]; + + var undoManager = [[self window] undoManager]; + [undoManager setActionName:@"Replace/insert text"]; + + [[undoManager prepareWithInvocationTarget:self] + _replaceCharactersInRange:CPMakeRange(_selectionRange.location, [aString length]) + withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)]]; + + [_textStorage replaceCharactersInRange:CPMakeRangeCopy(_selectionRange) withAttributedString:aString]; + + [self _setSelectedRange:CPMakeRange(_selectionRange.location + [string length], 0) affinity:0 stillSelecting:NO overwriteTypingAttributes:NO]; + _startTrackingLocation = _selectionRange.location; + + [self didChangeText]; + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self scrollRangeToVisible:_selectionRange]; + _stickyXLocation = MAX(0, _caret._rect.origin.x - 1); +} + +#pragma mark - +#pragma mark Drawing methods + +- (void)drawInsertionPointInRect:(CGRect)aRect color:(CPColor)aColor turnedOn:(BOOL)flag +{ + [_caret setRect:aRect]; + [_caret setVisibility:flag stop:NO]; +} + +- (void)displayRectIgnoringOpacity:(CGRect)aRect inContext:(CPGraphicsContext)aGraphicsContext +{ if ([self isHidden]) + return; + + [self drawRect:aRect]; +} + +- (void)drawRect:(CGRect)aRect +{ +#if PLATFORM(DOM) + var range = [_layoutManager glyphRangeForBoundingRect:aRect inTextContainer:_textContainer]; + + for (var i = 0; i < [_selectionSpans count]; i++) + [_selectionSpans[i] removeFromTextView]; + + _selectionSpans = []; + + if (_selectionRange.length) + { + var rects = [_layoutManager rectArrayForCharacterRange:_selectionRange + withinSelectedCharacterRange:_selectionRange + inTextContainer:_textContainer + rectCount:nil], + effectiveSelectionColor = [self _isFocused] ? [_selectedTextAttributes objectForKey:CPBackgroundColorAttributeName] : [CPColor _selectedTextBackgroundColorUnfocussed], + lengthRect = rects.length; + + for (var i = 0; i < lengthRect; i++) + { + rects[i].origin.x += _textContainerOrigin.x; + rects[i].origin.y += _textContainerOrigin.y; + + var newSpan = [[_CPSelectionBox alloc] initWithTextView:self rect:rects[i] color:effectiveSelectionColor]; + [_selectionSpans addObject:newSpan]; + } + } + + if (range.length) + [_layoutManager drawGlyphsForGlyphRange:range atPoint:_textContainerOrigin]; + + if ([self shouldDrawInsertionPoint]) + { + [self updateInsertionPointStateAndRestartTimer:NO]; + [self drawInsertionPointInRect:_caret._rect color:_insertionPointColor turnedOn:_caret._drawCaret]; + } + else + [_caret setVisibility:NO]; +#endif +} + + +#pragma mark - +#pragma mark Select methods + +- (void)selectAll:(id)sender +{ + if ([self isSelectable]) + { + [_caret stopBlinking]; + [self setSelectedRange:CPMakeRange(0, [_layoutManager numberOfCharacters])]; + } +} + +- (void)setSelectedRange:(CPRange)range +{ + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; + [self setSelectedRange:range affinity:0 stillSelecting:NO]; +} + +- (void)setSelectedRange:(CPRange)range affinity:(CPSelectionAffinity)affinity stillSelecting:(BOOL)selecting +{ + [self _setSelectedRange:range affinity:affinity stillSelecting:selecting overwriteTypingAttributes:YES]; +} + +- (void)_setSelectedRange:(CPRange)range affinity:(CPSelectionAffinity)affinity stillSelecting:(BOOL)selecting overwriteTypingAttributes:(BOOL)doOverwrite +{ + var maxRange = CPMakeRange(0, [_layoutManager numberOfCharacters]); + + range = CPIntersectionRange(maxRange, range); + + if (!selecting && [self _delegateRespondsToWillChangeSelectionFromCharacterRangeToCharacterRange]) + { + _selectionRange = [self _sendDelegateWillChangeSelectionFromCharacterRange:_selectionRange toCharacterRange:range]; + } + else + { + _selectionRange = CPMakeRangeCopy(range); + _selectionRange = [self selectionRangeForProposedRange:_selectionRange granularity:[self selectionGranularity]]; + } + + if (_selectionRange.length) + [_layoutManager invalidateDisplayForGlyphRange:_selectionRange]; + else + [self setNeedsDisplay:YES]; + + if (!selecting) + { + if ([self _isFirstResponder]) + [self updateInsertionPointStateAndRestartTimer:((_selectionRange.length === 0) && ![_caret isBlinking])]; + + if (doOverwrite) + [self setTypingAttributes:[_textStorage attributesAtIndex:CPMaxRange(range) effectiveRange:nil]]; + + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeSelectionNotification object:self]; + + if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_didChangeSelection) + [_delegate textViewDidChangeSelection:[[CPNotification alloc] initWithName:CPTextViewDidChangeSelectionNotification object:self userInfo:nil]]; + } + + if (!selecting && _selectionRange.length > 0) + [_CPNativeInputManager focusForClipboardOfTextView:self]; +} + +#if PLATFORM(DOM) +- (CGPoint)_cumulativeOffset +{ + var top = 0, + left = 0, + element = self._DOMElement; + + do + { + top += element.offsetTop || 0; + left += element.offsetLeft || 0; + element = element.offsetParent; + } + while(element); + + return CGPointMake(left, top); +} +#endif + + +// interface to the _CPNativeInputManager +- (void)_activateNativeInputElement:(DOMElemet)aNativeField +{ + var attributes = [[self typingAttributes] copy]; + + // make it invisible + [attributes setObject:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] forKey:CPForegroundColorAttributeName]; + + // FIXME: this hack to provide the visual space for the inputmanager should at least bypass the undomanager + var placeholderString = [[CPAttributedString alloc] initWithString:aNativeField.innerHTML attributes:attributes]; + [self insertText:placeholderString]; + + var caretOrigin = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, _selectionRange.location - 1), 1) inTextContainer:_textContainer].origin; + caretOrigin.y += [_layoutManager _characterOffsetAtLocation:MAX(0, _selectionRange.location - 1)]; + caretOrigin.x += 2; // two pixel offset to the LHS character + var cumulativeOffset = [self _cumulativeOffset]; + + +#if PLATFORM(DOM) + aNativeField.style.left = (caretOrigin.x + cumulativeOffset.x) + "px"; + aNativeField.style.top = (caretOrigin.y + cumulativeOffset.y) + "px"; + aNativeField.style.font = [[_typingAttributes objectForKey:CPFontAttributeName] cssString]; + aNativeField.style.color = [[_typingAttributes objectForKey:CPForegroundColorAttributeName] cssString]; +#endif + + [_caret setVisibility:NO]; // hide our caret because now the system caret takes over +} + +- (CPArray)selectedRanges +{ + return [_selectionRange]; +} + +#pragma mark - +#pragma mark Keyboard events + +- (void)keyDown:(CPEvent)event +{ + + [[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome) + + if (![_CPNativeInputManager isNativeInputFieldActive] && [event charactersIgnoringModifiers].charCodeAt(0) != 229) // filter out 229 because this would be inserted in chrome on each deadkey + [self interpretKeyEvents:[event]]; + + [_caret setPermanentlyVisible:YES]; +} + +- (void)keyUp:(CPEvent)event +{ + [super keyUp:event]; + + setTimeout(function() { + [_caret setPermanentlyVisible:NO]; + }, 500); +} + + +#pragma mark - +#pragma mark Mouse Events + +- (void)mouseDown:(CPEvent)event +{ + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; + + var fraction = [], + point = [self convertPoint:[event locationInWindow] fromView:nil], + granularities = [CPNotFound, CPSelectByCharacter, CPSelectByWord, CPSelectByParagraph]; + + [_caret setVisibility:NO]; + + // convert to container coordinate + point.x -= _textContainerOrigin.x; + point.y -= _textContainerOrigin.y; + + _startTrackingLocation = [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction]; + + if (_startTrackingLocation === CPNotFound) + _startTrackingLocation = [_layoutManager numberOfCharacters]; + else if (fraction[0] > 0.5) + _startTrackingLocation++; + + [self setSelectionGranularity:granularities[[event clickCount]]]; + + var setRange = CPMakeRange(_startTrackingLocation, 0); + + if ([event modifierFlags] & CPShiftKeyMask) + setRange = _MakeRangeFromAbs(_startTrackingLocation < _MidRange(_selectionRange) ? CPMaxRange(_selectionRange) : _selectionRange.location, _startTrackingLocation); + else + _scrollingTimer = [CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(_supportScrolling:) userInfo:nil repeats:YES]; // fixme: only start if we are in the scrolling areas + + [self setSelectedRange:setRange affinity:0 stillSelecting:YES]; +} + +- (void)_supportScrolling:(CPTimer)aTimer +{ + [self mouseDragged:[CPApp currentEvent]]; +} + +- (void)mouseDragged:(CPEvent)event +{ + var fraction = [], + point = [self convertPoint:[event locationInWindow] fromView:nil]; + + // convert to container coordinate + point.x -= _textContainerOrigin.x; + point.y -= _textContainerOrigin.y; + + var oldRange = [self selectedRange], + index = [_layoutManager glyphIndexForPoint:point + inTextContainer:_textContainer + fractionOfDistanceThroughGlyph:fraction]; + + if (index === CPNotFound) + index = _scrollingDownward ? CPMaxRange(oldRange) : oldRange.location; + else if (fraction[0] > 0.5) + index++; + + if (index > oldRange.location) + _scrollingDownward = YES; + + if (index < CPMaxRange(oldRange)) + _scrollingDownward = NO; + + [self setSelectedRange:_MakeRangeFromAbs(index, _startTrackingLocation) + affinity:0 + stillSelecting:YES]; + + [self scrollRangeToVisible:CPMakeRange(index, 0)]; +} + +// handle all the other methods from CPKeyBinding.j + +- (void)mouseUp:(CPEvent)event +{ + /* will post CPTextViewDidChangeSelectionNotification */ + _previousSelectionGranularity = [self selectionGranularity]; + [self setSelectionGranularity:CPSelectByCharacter]; + [self setSelectedRange:[self selectedRange] affinity:0 stillSelecting:NO]; + + var point = [_layoutManager locationForGlyphAtIndex:[self selectedRange].location]; + _stickyXLocation = point.x; + _startTrackingLocation = _selectionRange.location; + + if (_scrollingTimer) + { + [_scrollingTimer invalidate]; + _scrollingTimer = nil; + } +} + +- (void)moveDown:(id)sender +{ + if (![self isSelectable]) + return; + + var fraction = [], + nglyphs = [_layoutManager numberOfCharacters], + sindex = CPMaxRange([self selectedRange]), + rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(sindex, 1) inTextContainer:_textContainer], + rectEnd = nglyphs ? [_layoutManager boundingRectForGlyphRange:CPMakeRange(nglyphs - 1, 1) inTextContainer:_textContainer] : rectSource, + point = rectSource.origin; + + if (_stickyXLocation) + point.x = _stickyXLocation; + + // FIXME: find a better way for getting the coordinates of the next line + point.y += 2 + rectSource.size.height; + + var dindex = point.y >= CGRectGetMaxY(rectEnd) ? nglyphs : [_layoutManager glyphIndexForPoint:point inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction], + oldStickyLoc = _stickyXLocation; + + if (fraction[0] > 0.5) + dindex++; + + [self _establishSelection:CPMakeRange(dindex, 0) byExtending:NO]; + _stickyXLocation = oldStickyLoc; + [self scrollRangeToVisible:CPMakeRange(dindex, 0)] + +} + +- (void)moveDownAndModifySelection:(id)sender +{ + if (![self isSelectable]) + return; + + var oldStartTrackingLocation = _startTrackingLocation; + + [self _performSelectionFixupForRange:CPMakeRange(_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange), 0)]; + [self moveDown:sender]; + _startTrackingLocation = oldStartTrackingLocation; + [self _performSelectionFixupForRange:_MakeRangeFromAbs(_startTrackingLocation, (_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange)))]; +} + +- (void)moveUp:(id)sender +{ + if (![self isSelectable]) + return; + + var dindex = [self selectedRange].location; + + if (dindex < 1) + return; + + var rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(dindex, 1) inTextContainer:_textContainer]; + + if (!(dindex === [_layoutManager numberOfCharacters] && _isNewlineCharacter([[_textStorage string] characterAtIndex:dindex - 1]))) + dindex = [_layoutManager glyphIndexForPoint:CGPointMake(0, rectSource.origin.y + 1) inTextContainer:_textContainer fractionOfDistanceThroughGlyph:nil]; + + if (dindex < 1) + return; + + var fraction = []; + rectSource = [_layoutManager boundingRectForGlyphRange:CPMakeRange(dindex - 1, 1) inTextContainer:_textContainer]; + dindex = [_layoutManager glyphIndexForPoint:CGPointMake(_stickyXLocation, rectSource.origin.y + 1) inTextContainer:_textContainer fractionOfDistanceThroughGlyph:fraction]; + + if (fraction[0] > 0.5) + dindex++; + + var oldStickyLoc = _stickyXLocation; + [self _establishSelection:CPMakeRange(dindex,0) byExtending:NO]; + _stickyXLocation = oldStickyLoc; + + [self scrollRangeToVisible:CPMakeRange(dindex, 0)]; +} + +- (void)moveUpAndModifySelection:(id)sender +{ + if (![self isSelectable]) + return; + + var oldStartTrackingLocation = _startTrackingLocation; + + [self _performSelectionFixupForRange:CPMakeRange(_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange), 0)]; + [self moveUp:sender]; + _startTrackingLocation = oldStartTrackingLocation; + [self _performSelectionFixupForRange:_MakeRangeFromAbs(_startTrackingLocation, (_selectionRange.location < _startTrackingLocation ? _selectionRange.location : CPMaxRange(_selectionRange)))]; +} + +- (void)_performSelectionFixupForRange:(CPRange)aSel +{ + aSel.location = MAX(0, aSel.location); + + if (CPMaxRange(aSel) > [_layoutManager numberOfCharacters]) + aSel = CPMakeRange([_layoutManager numberOfCharacters], 0); + + [self setSelectedRange:aSel]; + + var point = [_layoutManager locationForGlyphAtIndex:aSel.location]; + + _stickyXLocation = point.x; +} + +- (void)_establishSelection:(CPSelection)aSel byExtending:(BOOL)flag +{ + if (flag) + aSel = CPUnionRange(aSel, _selectionRange); + + [self _performSelectionFixupForRange:aSel]; + _startTrackingLocation = _selectionRange.location; +} + +- (unsigned)_calculateMoveSelectionFromRange:(CPRange)aRange intoDirection:(integer)move granularity:(CPSelectionGranularity)granularity +{ + var inWord = [self _isCharacterAtIndex:(move > 0 ? CPMaxRange(aRange) : aRange.location) + move granularity:granularity], + aSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aRange) : aRange.location) + move, 0) granularity:granularity], + bSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aSel) : aSel.location) + move, 0) granularity:granularity]; + + return move > 0 ? CPMaxRange(inWord? aSel:bSel) : (inWord? aSel:bSel).location; +} + +- (void)_moveSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity +{ + var pos = [self _calculateMoveSelectionFromRange:_selectionRange intoDirection:move granularity:granularity]; + + [self _performSelectionFixupForRange:CPMakeRange(pos, 0)]; + _startTrackingLocation = _selectionRange.location; +} + +- (void)_extendSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity +{ + var aSel = CPMakeRangeCopy(_selectionRange); + + if (granularity !== CPSelectByCharacter) + { + var pos = [self _calculateMoveSelectionFromRange:CPMakeRange(aSel.location < _startTrackingLocation ? aSel.location : CPMaxRange(aSel), 0) + intoDirection:move + granularity:granularity]; + aSel = CPMakeRange(pos, 0); + } + + else + aSel = CPMakeRange((aSel.location < _startTrackingLocation? aSel.location : CPMaxRange(aSel)) + move, 0); + + aSel = _MakeRangeFromAbs(_startTrackingLocation, aSel.location); + [self _performSelectionFixupForRange:aSel]; +} + +- (void)moveLeftAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:-1 granularity:CPSelectByCharacter]; +} + +- (void)moveBackward:(id)sender +{ + [self moveLeft:sender]; +} + +- (void)moveBackwardAndModifySelection:(id)sender +{ + [self moveLeftAndModifySelection:sender]; +} + +- (void)moveRightAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:1 granularity:CPSelectByCharacter]; +} + +- (void)moveLeft:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange(_selectionRange.location - (_selectionRange.length ? 0 : 1), 0) byExtending:NO]; +} + +- (void)moveToEndOfParagraph:(id)sender +{ + if (![self isSelectable]) + return; + + if (!_isNewlineCharacter([[_textStorage string] characterAtIndex:_selectionRange.location])) + [self _moveSelectionIntoDirection:1 granularity:CPSelectByParagraph]; + + if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)])) + [self moveLeft:sender]; +} + +- (void)moveToEndOfParagraphAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:1 granularity:CPSelectByParagraph]; +} + +- (void)moveParagraphForwardAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:1 granularity:CPSelectByParagraph]; +} + +- (void)moveParagraphForward:(id)sender +{ + if ([self isSelectable]) + [self _moveSelectionIntoDirection:1 granularity:CPSelectByParagraph]; +} + +- (void)moveWordBackwardAndModifySelection:(id)sender +{ + [self moveWordLeftAndModifySelection:sender]; +} + +- (void)moveWordBackward:(id)sender +{ + [self moveWordLeft:sender]; +} + +- (void)moveWordForwardAndModifySelection:(id)sender +{ + [self moveWordRightAndModifySelection:sender]; +} + +- (void)moveWordForward:(id)sender +{ + [self moveWordRight:sender]; +} + +- (void)moveToBeginningOfDocument:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange(0, 0) byExtending:NO]; +} + +- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange(0, 0) byExtending:YES]; +} + +- (void)moveToEndOfDocument:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange([_layoutManager numberOfCharacters], 0) byExtending:NO]; +} + +- (void)moveToEndOfDocumentAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange([_layoutManager numberOfCharacters], 0) byExtending:YES]; +} + +- (void)moveWordRight:(id)sender +{ + if ([self isSelectable]) + [self _moveSelectionIntoDirection:1 granularity:CPSelectByWord]; +} + +- (void)moveToBeginningOfParagraph:(id)sender +{ + if (![self isSelectable]) + return; + + if (!_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, _selectionRange.location - 1)])) + [self _moveSelectionIntoDirection:-1 granularity:CPSelectByParagraph]; +} + +- (void)moveToBeginningOfParagraphAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:-1 granularity:CPSelectByParagraph]; +} + +- (void)moveParagraphBackward:(id)sender +{ + if ([self isSelectable]) + [self _moveSelectionIntoDirection:-1 granularity:CPSelectByParagraph]; +} + +- (void)moveParagraphBackwardAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:-1 granularity:CPSelectByParagraph]; +} + +- (void)moveWordRightAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:+1 granularity:CPSelectByWord]; +} + +- (void)deleteToEndOfParagraph:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveToEndOfParagraphAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteToBeginningOfParagraph:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveToBeginningOfParagraphAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteToBeginningOfLine:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveToLeftEndOfLineAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteToEndOfLine:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveToRightEndOfLineAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteWordBackward:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveWordLeftAndModifySelection:self]; + [self delete:self]; +} + +- (void)deleteWordForward:(id)sender +{ + if (![self isSelectable] || ![self isEditable]) + return; + + [self moveWordRightAndModifySelection:self]; + [self delete:self]; +} + +- (void)moveToLeftEndOfLine:(id)sender byExtending:(BOOL)flag +{ + if (![self isSelectable]) + return; + + var nglyphs = [_layoutManager numberOfCharacters], + loc = nglyphs == _selectionRange.location ? MAX(0, _selectionRange.location - 1) : _selectionRange.location, + fragment = [_layoutManager _firstLineFragmentForLineFromLocation:loc]; + + if (fragment) + [self _establishSelection:CPMakeRange(fragment._range.location, 0) byExtending:flag]; +} + +- (void)moveToLeftEndOfLine:(id)sender +{ + [self moveToLeftEndOfLine:sender byExtending:NO]; +} + +- (void)moveToLeftEndOfLineAndModifySelection:(id)sender +{ + [self moveToLeftEndOfLine:sender byExtending:YES]; +} + +- (void)moveToRightEndOfLine:(id)sender byExtending:(BOOL)flag +{ + if (![self isSelectable]) + return; + + var fragment = [_layoutManager _lastLineFragmentForLineFromLocation:_selectionRange.location]; + + if (!fragment) + return; + + var nglyphs = [_layoutManager numberOfCharacters], + loc = nglyphs == CPMaxRange(fragment._range) ? nglyphs : MAX(0, CPMaxRange(fragment._range) - 1); + + [self _establishSelection:CPMakeRange(loc, 0) byExtending:flag]; +} + +- (void)moveToRightEndOfLine:(id)sender +{ + [self moveToRightEndOfLine:sender byExtending:NO]; +} + +- (void)moveToRightEndOfLineAndModifySelection:(id)sender +{ + [self moveToRightEndOfLine:sender byExtending:YES]; +} + +- (void)moveWordLeftAndModifySelection:(id)sender +{ + if ([self isSelectable]) + [self _extendSelectionIntoDirection:-1 granularity:CPSelectByWord]; +} + +- (void)moveWordLeft:(id)sender +{ + if ([self isSelectable]) + [self _moveSelectionIntoDirection:-1 granularity:CPSelectByWord] +} + +- (void)moveRight:(id)sender +{ + if ([self isSelectable]) + [self _establishSelection:CPMakeRange(CPMaxRange(_selectionRange) + (_selectionRange.length ? 0 : 1), 0) byExtending:NO]; +} + +- (void)_deleteForRange:(CPRange)changedRange +{ + if (![self shouldChangeTextInRange:changedRange replacementString:@""]) + return; + + changedRange = CPIntersectionRange(CPMakeRange(0, [_layoutManager numberOfCharacters]), changedRange); + + [[[_window undoManager] prepareWithInvocationTarget:self] _replaceCharactersInRange:CPMakeRange(changedRange.location, 0) withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(changedRange)]]; + [_textStorage deleteCharactersInRange:CPMakeRangeCopy(changedRange)]; + + [self setSelectedRange:CPMakeRange(changedRange.location, 0)]; + [self didChangeText]; + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + _stickyXLocation = _caret._rect.origin.x; +} + +- (void)cancelOperation:(id)sender +{ + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; // handle ESC during native input +} + +- (void)deleteBackward:(id)sender ignoreSmart:(BOOL)ignoreFlag +{ + var changedRange; + + if (CPEmptyRange(_selectionRange) && _selectionRange.location > 0) + changedRange = CPMakeRange(_selectionRange.location - 1, 1); + else + changedRange = _selectionRange; + + // smart delete + if (!ignoreFlag && _copySelectionGranularity > 0 && + changedRange.location > 0 && _isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]) && + changedRange.location < [[self string] length] && _isWhitespaceCharacter([[_textStorage string] characterAtIndex:CPMaxRange(changedRange)])) + changedRange.length++; + + [self _deleteForRange:changedRange]; + _startTrackingLocation = _selectionRange.location; +} + +- (void)deleteBackward:(id)sender +{ + _copySelectionGranularity = _previousSelectionGranularity; // smart delete + [self deleteBackward:self ignoreSmart:_selectionRange.length > 0? NO:YES]; +} + +- (void)deleteForward:(id)sender +{ + var changedRange; + + if (CPEmptyRange(_selectionRange) && _selectionRange.location < [_layoutManager numberOfCharacters]) + changedRange = CPMakeRange(_selectionRange.location, 1); + else + changedRange = _selectionRange; + + [self _deleteForRange:changedRange]; +} + +- (void)cut:(id)sender +{ + var selectedRange = [self selectedRange]; + + if (selectedRange.length < 1) + return; + + [self copy:sender]; + [self deleteBackward:sender ignoreSmart:NO]; +} + +- (void)insertLineBreak:(id)sender +{ + [self insertText:@"\n"]; +} + +- (void)insertTab:(id)sender +{ + [self insertText:@"\t"]; +} + +- (void)insertTabIgnoringFieldEditor:(id)sender +{ + [self insertTab:sender]; +} + +- (void)insertNewlineIgnoringFieldEditor:(id)sender +{ + [self insertLineBreak:sender]; +} + +- (void)insertNewline:(id)sender +{ + [self insertLineBreak:sender]; +} + +- (void)_enrichEssentialTypingAttributes:(CPDictionary)attributes +{ + if (![attributes containsKey:CPFontAttributeName]) + [attributes setObject:[self font] forKey:CPFontAttributeName]; + + if (![attributes containsKey:CPForegroundColorAttributeName]) + [attributes setObject:[self textColor] forKey:CPForegroundColorAttributeName]; +} + +- (void)setTypingAttributes:(CPDictionary)attributes +{ + if (!attributes) + attributes = [CPDictionary dictionary]; + + if ([self _delegateRespondsToShouldChangeTypingAttributesToAttributes]) + { + _typingAttributes = [self _sendDelegateShouldChangeTypingAttributes:_typingAttributes toAttributes:attributes]; + } + else + { + _typingAttributes = [attributes copy]; + + [self _enrichEssentialTypingAttributes:_typingAttributes]; + } + + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification + object:self]; + + if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_didChangeTypingAttributes) + [_delegate textViewDidChangeTypingAttributes:[[CPNotification alloc] initWithName:CPTextViewDidChangeTypingAttributesNotification object:self userInfo:nil]]; +} + +- (CPDictionary)_attributesForFontPanel +{ + var attributes = [[_textStorage attributesAtIndex:CPMaxRange(_selectionRange) effectiveRange:nil] copy]; + + [self _enrichEssentialTypingAttributes:attributes]; + + return attributes; +} + +- (void)delete:(id)sender +{ + [self deleteBackward:sender]; +} + + +#pragma mark - +#pragma mark Font methods + +- (void)setFont:(CPFont)font +{ + _font = font; + + var length = [_layoutManager numberOfCharacters]; + + if (length) + { + [_textStorage addAttribute:CPFontAttributeName value:_font range:CPMakeRange(0, length)]; + [_textStorage setFont:_font]; + [self scrollRangeToVisible:CPMakeRange(length, 0)]; + } +} + +- (void)setFont:(CPFont)font range:(CPRange)range +{ + if (![self isRichText]) + { + _font = font; + [_textStorage setFont:_font]; + } + + var currentAttributes = [_textStorage attributesAtIndex:range.location effectiveRange:nil] || _typingAttributes; + + [[[[self window] undoManager] prepareWithInvocationTarget:self] + setFont:[currentAttributes objectForKey:CPFontAttributeName] || _font + range:CPMakeRangeCopy(range)]; + + [_textStorage addAttribute:CPFontAttributeName value:font range:CPMakeRangeCopy(range)]; + [_layoutManager _validateLayoutAndGlyphs]; +} + +- (void)changeFont:(id)sender +{ + var currRange = CPMakeRange(_selectionRange.location, 0), + oldFont, + attributes, + scrollRange = CPMakeRange(CPMaxRange(_selectionRange), 0), + undoManager = [[self window] undoManager]; + + [undoManager beginUndoGrouping]; + + if ([self isRichText]) + { + if (!CPEmptyRange(_selectionRange)) + { + while (CPMaxRange(currRange) < CPMaxRange(_selectionRange)) // iterate all "runs" + { + attributes = [_textStorage attributesAtIndex:CPMaxRange(currRange) + longestEffectiveRange:currRange + inRange:_selectionRange]; + oldFont = [attributes objectForKey:CPFontAttributeName] || [self font]; + + [self setFont:[sender convertFont:oldFont] range:currRange]; + } + } + else + { + [_typingAttributes setObject:[sender selectedFont] forKey:CPFontAttributeName]; + } + } + else + { + var length = [_textStorage length]; + + oldFont = [self font]; + [self setFont:[sender convertFont:oldFont] range:CPMakeRange(0, length)]; + scrollRange = CPMakeRange(length, 0); + } + + [undoManager endUndoGrouping]; + + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self setNeedsDisplay:YES]; + [self scrollRangeToVisible:scrollRange]; +} + + +#pragma mark - +#pragma mark Color methods + +- (void)changeColor:(id)sender +{ + [self setTextColor:[sender color] range:_selectionRange]; +} + +- (void)setTextColor:(CPColor)aColor +{ + _textColor = [aColor copy]; + [self setTextColor:aColor range:CPMakeRange(0, [_layoutManager numberOfCharacters])]; + [_typingAttributes setObject:_textColor forKey:CPForegroundColorAttributeName]; +} + +- (void)setTextColor:(CPColor)aColor range:(CPRange)range +{ + var currentAttributes = [_textStorage attributesAtIndex:range.location effectiveRange:nil] || _typingAttributes; + + [[[[self window] undoManager] prepareWithInvocationTarget:self] + setTextColor:[currentAttributes objectForKey:CPForegroundColorAttributeName] || _textColor + range:CPMakeRangeCopy(range)]; + + if (!CPEmptyRange(range)) + { + if (aColor) + [_textStorage addAttribute:CPForegroundColorAttributeName value:aColor range:CPMakeRangeCopy(range)]; + else + [_textStorage removeAttribute:CPForegroundColorAttributeName range:CPMakeRangeCopy(range)]; + } + else + [_typingAttributes setObject:aColor forKey:CPForegroundColorAttributeName]; + + [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(range) changeInLength:0 invalidatedRange:CPMakeRangeCopy(range)]; +} + +- (void)underline:(id)sender +{ + if (![self shouldChangeTextInRange:_selectionRange replacementString:nil]) + return; + + if (!CPEmptyRange(_selectionRange)) + { + var attrib = [_textStorage attributesAtIndex:_selectionRange.location effectiveRange:nil]; + + if ([attrib containsKey:CPUnderlineStyleAttributeName] && [[attrib objectForKey:CPUnderlineStyleAttributeName] intValue]) + [_textStorage removeAttribute:CPUnderlineStyleAttributeName range:_selectionRange]; + else + [_textStorage addAttribute:CPUnderlineStyleAttributeName value:[CPNumber numberWithInt:1] range:CPMakeRangeCopy(_selectionRange)]; + } + else + { + if ([_typingAttributes containsKey:CPUnderlineStyleAttributeName] && [[_typingAttributes objectForKey:CPUnderlineStyleAttributeName] intValue]) + [_typingAttributes setObject:[CPNumber numberWithInt:0] forKey:CPUnderlineStyleAttributeName]; + else + [_typingAttributes setObject:[CPNumber numberWithInt:1] forKey:CPUnderlineStyleAttributeName]; + } + + [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(_selectionRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(_selectionRange)]; +} + +- (CPSelectionAffinity)selectionAffinity +{ + return 0; +} + +- (BOOL)isRulerVisible +{ + return NO; +} + +- (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString +{ + [_textStorage replaceCharactersInRange:aRange withString:aString]; +} + +- (void)setConstrainedFrameSize:(CGSize)desiredSize +{ + [self setFrameSize:desiredSize]; +} + +- (void)sizeToFit +{ + [self setFrameSize:[self frameSize]]; +} + +- (void)setFrameSize:(CGSize)aSize +{ + var minSize = [self minSize], + maxSize = [self maxSize], + desiredSize = CGSizeCreateCopy(aSize), + rect = CGRectUnion([_layoutManager boundingRectForGlyphRange:CPMakeRange(0, 1) inTextContainer:_textContainer], + [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, [_layoutManager numberOfCharacters] - 2), 1) inTextContainer:_textContainer]), + myClipviewSize = nil; + + if ([[self superview] isKindOfClass:[CPClipView class]]) + myClipviewSize = [[self superview] frame].size; + + if ([_layoutManager extraLineFragmentTextContainer] === _textContainer) + rect = CGRectUnion(rect, [_layoutManager extraLineFragmentRect]); + + if (_isHorizontallyResizable) + { + rect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(0, MAX(0, [_layoutManager numberOfCharacters] - 1)) inTextContainer:_textContainer]; // needs expensive "deep" recalculation + + desiredSize.width = rect.size.width + 2 * _textContainerInset.width; + + if (desiredSize.width < minSize.width) + desiredSize.width = minSize.width; + else if (desiredSize.width > maxSize.width) + desiredSize.width = maxSize.width; + } + + if (_isVerticallyResizable) + { + desiredSize.height = rect.size.height + 2 * _textContainerInset.height; + + if (desiredSize.height < minSize.height) + desiredSize.height = minSize.height; + else if (desiredSize.height > maxSize.height) + desiredSize.height = maxSize.height; + } + + if (myClipviewSize) + { + if (desiredSize.width < myClipviewSize.width) + desiredSize.width = myClipviewSize.width; + if (desiredSize.height < myClipviewSize.height) + desiredSize.height = myClipviewSize.height; + } + + [super setFrameSize:desiredSize]; +} + +- (void)scrollRangeToVisible:(CPRange)aRange +{ + var rect; + + if (CPEmptyRange(aRange)) + { + if (aRange.location >= [_layoutManager numberOfCharacters]) + rect = [_layoutManager extraLineFragmentRect]; + else + rect = [_layoutManager lineFragmentRectForGlyphAtIndex:aRange.location effectiveRange:nil]; + } + else + { + rect = [_layoutManager boundingRectForGlyphRange:aRange inTextContainer:_textContainer]; + } + + rect.origin.x += _textContainerOrigin.x; + rect.origin.y += _textContainerOrigin.y; + + [self scrollRectToVisible:rect]; +} + +- (BOOL)_isCharacterAtIndex:(unsigned)index granularity:(CPSelectionGranularity)granularity +{ + var characterSet; + + switch (granularity) + { + case CPSelectByWord: + characterSet = [[self class] _wordBoundaryRegex]; + break; + + case CPSelectByParagraph: + characterSet = [[self class] _paragraphBoundaryRegex]; + break; + default: + // FIXME if (!characterSet) croak! + } + + return _regexMatchesStringAtIndex(characterSet, [_textStorage string], index); +} + ++ (JSObject)_wordBoundaryRegex +{ + return /(^[0-9][\.,])|(^.[^-\.,+#'"!§$%&/\(<\[\]>\)=?`´*\s{}\|¶])/m; +} + ++ (JSObject)_paragraphBoundaryRegex +{ + return /^.[^\n\r]/m; +} + ++ (JSObject)_whitespaceRegex +{ + // do not include \n here or we will get cross paragraph selections + return /^.[ \t]+/m; +} + +- (CPRange)_characterRangeForIndex:(unsigned)index asDefinedByRegex:(JSObject)regex +{ + return [self _characterRangeForIndex:index asDefinedByLRegex:regex andRRegex:regex] +} + +- (CPRange)_characterRangeForIndex:(unsigned)index asDefinedByLRegex:(JSObject)lregex andRRegex:(JSObject)rregex +{ + var wordRange = CPMakeRange(index, 0), + numberOfCharacters = [_layoutManager numberOfCharacters], + string = [_textStorage string]; + + // extend to the left + for (var searchIndex = index - 1; searchIndex >= 0 && _regexMatchesStringAtIndex(lregex, string, searchIndex); searchIndex--) + wordRange.location = searchIndex; + + // extend to the right + searchIndex = index + 1; + + while (searchIndex < numberOfCharacters && _regexMatchesStringAtIndex(rregex, string, searchIndex)) + searchIndex++; + + return _MakeRangeFromAbs(wordRange.location, MIN(MAX(0, numberOfCharacters), searchIndex)); +} + +- (CPRange)selectionRangeForProposedRange:(CPRange)proposedRange granularity:(CPSelectionGranularity)granularity +{ + var textStorageLength = [_layoutManager numberOfCharacters]; + + if (textStorageLength == 0) + return CPMakeRange(0, 0); + + if (proposedRange.location >= textStorageLength) + proposedRange = CPMakeRange(textStorageLength, 0); + + if (CPMaxRange(proposedRange) > textStorageLength) + proposedRange.length = textStorageLength - proposedRange.location; + + var string = [_textStorage string], + lregex, + rregex, + lloc = proposedRange.location, + rloc = CPMaxRange(proposedRange); + + switch (granularity) + { + case CPSelectByWord: + lregex = _isWhitespaceCharacter([string characterAtIndex:lloc])? [[self class] _whitespaceRegex] : [[self class] _wordBoundaryRegex]; + rregex = _isWhitespaceCharacter([string characterAtIndex:CPMaxRange(proposedRange)])? [[self class] _whitespaceRegex] : [[self class] _wordBoundaryRegex]; + break; + case CPSelectByParagraph: + lregex = rregex = [[self class] _paragraphBoundaryRegex]; + + // triple click right in last line of a paragraph-> select this paragraph completely + if (lloc > 0 && _isNewlineCharacter([string characterAtIndex:lloc]) && + !_isNewlineCharacter([string characterAtIndex:lloc - 1])) + lloc--; + + if (rloc > 0 && _isNewlineCharacter([string characterAtIndex:rloc])) + rloc--; + + break; + default: + return proposedRange; + } + + var granularRange = [self _characterRangeForIndex:lloc + asDefinedByLRegex:lregex + andRRegex:rregex]; + + if (proposedRange.length == 0 && _isNewlineCharacter([string characterAtIndex:proposedRange.location])) + return _MakeRangeFromAbs(_isNewlineCharacter([string characterAtIndex:lloc])? proposedRange.location : granularRange.location, proposedRange.location + 1); + + if (proposedRange.length) + granularRange = CPUnionRange(granularRange, [self _characterRangeForIndex:rloc + asDefinedByLRegex:lregex + andRRegex:rregex]); + + // include the newline character in case of triple click selecting as is done by apple + if (granularity == CPSelectByParagraph && _isNewlineCharacter([string characterAtIndex:CPMaxRange(granularRange)])) + granularRange.length++; + + return granularRange; +} + +- (BOOL)shouldDrawInsertionPoint +{ + return (_selectionRange.length === 0 && [self _isFocused]); +} + +- (void)updateInsertionPointStateAndRestartTimer:(BOOL)flag +{ + var caretRect, + numberOfGlyphs= [_layoutManager numberOfCharacters]; + + if (_selectionRange.length) + [_caret setVisibility:NO]; + + if (_selectionRange.location >= numberOfGlyphs) // cursor is "behind" the last chacacter + { + caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0,_selectionRange.location - 1), 1) inTextContainer:_textContainer]; + + if (!numberOfGlyphs) + { + var font = [_typingAttributes objectForKey:CPFontAttributeName]; + + caretRect.size.height = [font size]; + caretRect.origin.y = ([font ascender] - [font descender]) * 0.5; + } + + caretRect.origin.x += caretRect.size.width; + + if (_selectionRange.location > 0 && [[_textStorage string] characterAtIndex:_selectionRange.location - 1] === '\n') + { + caretRect.origin.y += caretRect.size.height; + caretRect.origin.x = 0; + } + } + else + { + caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(_selectionRange.location, 1) inTextContainer:_textContainer]; + } + + var loc = (_selectionRange.location === numberOfGlyphs && numberOfGlyphs > 0) ? _selectionRange.location - 1 : _selectionRange.location, + caretOffset = [_layoutManager _characterOffsetAtLocation:loc], + oldYPosition = CGRectGetMaxY(caretRect), + caretDescend = [_layoutManager _descentAtLocation:loc]; + + if (caretOffset > 0) + { + caretRect.origin.y += caretOffset; + caretRect.size.height = oldYPosition - caretRect.origin.y; + } + if (caretDescend < 0) + { + caretRect.size.height -= caretDescend; + } + + caretRect.origin.x += _textContainerOrigin.x; + caretRect.origin.y += _textContainerOrigin.y; + caretRect.size.width = 1; + + [_caret setRect:caretRect]; + + if (flag) + [_caret startBlinking]; +} + + +#pragma mark - +#pragma mark Dragging operation + +- (void)performDragOperation:(CPDraggingInfo)aSender +{ + var location = [self convertPoint:[aSender draggingLocation] fromView:nil], + pasteboard = [aSender draggingPasteboard]; + + if (![pasteboard availableTypeFromArray:[CPColorDragType]]) + return NO; + + [self setTextColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] range:_selectionRange]; +} + +@end + + +@implementation CPTextView (CPTextViewDelegate) + +- (BOOL)_delegateRespondsToShouldChangeTypingAttributesToAttributes +{ + return _delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes; +} + +- (BOOL)_delegateRespondsToWillChangeSelectionFromCharacterRangeToCharacterRange +{ + return _delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange; +} + +- (BOOL)_sendDelegateDoCommandBySelector:(SEL)aSelector +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_doCommandBySelector)) + return NO; + + return [_delegate textView:self doCommandBySelector:aSelector]; +} + +- (BOOL)_sendDelegateTextShouldBeginEditing +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textShouldBeginEditing)) + return YES; + + return [_delegate textShouldBeginEditing:self]; +} + +- (BOOL)_sendDelegateShouldChangeTextInRange:(CPRange)aRange replacementString:(CPString)aString +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_shouldChangeTextInRange_replacementString)) + return YES; + + return [_delegate textView:self shouldChangeTextInRange:aRange replacementString:aString]; +} + +- (CPDictionary)_sendDelegateShouldChangeTypingAttributes:(CPDictionary)typingAttributes toAttributes:(CPDictionary)attributes +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_doCommandBySelector)) + return [CPDictionary dictionary]; + + return [_delegate textView:self shouldChangeTypingAttributes:typingAttributes toAttributes:attributes]; +} + +- (CPRange)_sendDelegateWillChangeSelectionFromCharacterRange:(CPRange)selectionRange toCharacterRange:(CPRange)range +{ + if (!(_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_willChangeSelectionFromCharacterRange_toCharacterRange)) + return CPMakeRange(0, 0); + + return [_delegate textView:self willChangeSelectionFromCharacterRange:selectionRange toCharacterRange:range]; +} + +@end + + +var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey", + CPTextViewUsesFontPanelKey = @"CPTextViewUsesFontPanelKey", + CPTextViewContainerKey = @"CPTextViewContainerKey", + CPTextViewLayoutManagerKey = @"CPTextViewLayoutManagerKey", + CPTextViewTextStorageKey = @"CPTextViewTextStorageKey", + CPTextViewInsertionPointColorKey = @"CPTextViewInsertionPointColorKey", + CPTextViewSelectedTextAttributesKey = @"CPTextViewSelectedTextAttributesKey", + CPTextViewDelegateKey = @"CPTextViewDelegateKey"; + +@implementation CPTextView (CPCoding) + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super initWithCoder:aCoder]; + + if (self) + { + [self _init]; + + [self setInsertionPointColor:[aCoder decodeObjectForKey:CPTextViewInsertionPointColorKey]]; + + var selectedTextAttributes = [aCoder decodeObjectForKey:CPTextViewSelectedTextAttributesKey], + enumerator = [selectedTextAttributes keyEnumerator], + key; + + while (key = [enumerator nextObject]) + [_selectedTextAttributes setObject:[selectedTextAttributes valueForKey:key] forKey:key]; + + if (![_selectedTextAttributes valueForKey:CPBackgroundColorAttributeName]) + [_selectedTextAttributes setObject:[CPColor selectedTextBackgroundColor] forKey:CPBackgroundColorAttributeName]; + + [self setAllowsUndo:[aCoder decodeBoolForKey:CPTextViewAllowsUndoKey]]; + [self setUsesFontPanel:[aCoder decodeBoolForKey:CPTextViewUsesFontPanelKey]]; + + [self setDelegate:[aCoder decodeObjectForKey:CPTextViewDelegateKey]]; + + var container = [aCoder decodeObjectForKey:CPTextViewContainerKey]; + [container setTextView:self]; + + _typingAttributes = [[_textStorage attributesAtIndex:0 effectiveRange:nil] copy]; + + if (![_typingAttributes valueForKey:CPForegroundColorAttributeName]) + [_typingAttributes setObject:[CPColor blackColor] forKey:CPForegroundColorAttributeName]; + + _textColor = [_typingAttributes valueForKey:CPForegroundColorAttributeName]; + [self setFont:[_typingAttributes valueForKey:CPFontAttributeName]]; + + [self setString:[_textStorage string]]; + } + + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [super encodeWithCoder:aCoder]; + + [aCoder encodeObject:_delegate forKey:CPTextViewDelegateKey]; + [aCoder encodeObject:_textContainer forKey:CPTextViewContainerKey]; + [aCoder encodeObject:_insertionPointColor forKey:CPTextViewInsertionPointColorKey]; + [aCoder encodeObject:_selectedTextAttributes forKey:CPTextViewSelectedTextAttributesKey]; + [aCoder encodeBool:_allowsUndo forKey:CPTextViewAllowsUndoKey]; + [aCoder encodeBool:_usesFontPanel forKey:CPTextViewUsesFontPanelKey]; +} + +@end + + +@implementation _CPSelectionBox : CPObject +{ + DOMElement _selectionBoxDOM; + CGRect _rect; + CPColor _color + CPTextView _textView; +} + +- (id)initWithTextView:(CPTextView)aTextView rect:(CGRect)aRect color:(CPColor)aColor +{ + if (self = [super init]) + { + _textView = aTextView; + _rect = aRect; + _color = aColor; + + [self _createSpan]; + _textView._DOMElement.appendChild(_selectionBoxDOM); + } + + return self; +} + +- (void)removeFromTextView +{ + _textView._DOMElement.removeChild(_selectionBoxDOM); +} + +- (void)_createSpan +{ + +#if PLATFORM(DOM) + _selectionBoxDOM = document.createElement("span"); + _selectionBoxDOM.style.position = "absolute"; + _selectionBoxDOM.style.visibility = "visible"; + _selectionBoxDOM.style.padding = "0px"; + _selectionBoxDOM.style.margin = "0px"; + _selectionBoxDOM.style.whiteSpace = "pre"; + _selectionBoxDOM.style.backgroundColor = [_color cssString]; + + _selectionBoxDOM.style.width = (_rect.size.width) + "px"; + _selectionBoxDOM.style.left = (_rect.origin.x) + "px"; + _selectionBoxDOM.style.top = (_rect.origin.y) + "px"; + _selectionBoxDOM.style.height = (_rect.size.height) + "px"; + _selectionBoxDOM.style.zIndex = -1000; + _selectionBoxDOM.oncontextmenu = _selectionBoxDOM.onmousedown = _selectionBoxDOM.onselectstart = function () { return false; }; +#endif + +} + +@end + + +@implementation _CPCaret : CPObject +{ + BOOL _drawCaret; + BOOL _permanentlyVisible @accessors(property=permanentlyVisible); + CGRect _rect; + CPTextView _textView; + CPTimer _caretTimer; + DOMElement _caretDOM; +} + +- (void)setRect:(CGRect)aRect +{ + _rect = CGRectCreateCopy(aRect); + +#if PLATFORM(DOM) + _caretDOM.style.left = (aRect.origin.x) + "px"; + _caretDOM.style.top = (aRect.origin.y) + "px"; + _caretDOM.style.height = (aRect.size.height) + "px"; +#endif +} + +- (id)initWithTextView:(CPTextView)aView +{ + if (self = [super init]) + { +#if PLATFORM(DOM) + var style; + + if (!_caretDOM) + { + _caretDOM = document.createElement("span"); + style = _caretDOM.style; + style.position = "absolute"; + style.visibility = "visible"; + style.padding = "0px"; + style.margin = "0px"; + style.whiteSpace = "pre"; + style.backgroundColor = "black"; + _caretDOM.style.width = "1px"; + _textView = aView; + _textView._DOMElement.appendChild(_caretDOM); + } +#endif + } + + return self; +} + +- (void)setVisibility:(BOOL)visibilityFlag stop:(BOOL)stopFlag +{ + +#if PLATFORM(DOM) + _caretDOM.style.visibility = visibilityFlag ? "visible" : "hidden"; +#endif + + if (!visibilityFlag && stopFlag) + [self stopBlinking]; +} + +- (void)setVisibility:(BOOL)visibilityFlag +{ + [self setVisibility:visibilityFlag stop:YES]; +} + +- (void)_blinkCaret:(CPTimer)aTimer +{ + _drawCaret = (!_drawCaret) || _permanentlyVisible; + [_textView setNeedsDisplayInRect:_rect]; +} + +- (void)startBlinking +{ + _drawCaret = YES; + + if ([self isBlinking]) + return; + + _caretTimer = [CPTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES]; +} + +- (void)isBlinking +{ + return [_caretTimer isValid]; +} + +- (void)stopBlinking +{ + _drawCaret = NO; + + if (_caretTimer) + { + [_caretTimer invalidate]; + _caretTimer = nil; + } +} + +@end + + +var _CPNativeInputField, + _CPNativeInputFieldKeyDownCalled, + _CPNativeInputFieldKeyUpCalled, + _CPNativeInputFieldKeyPressedCalled, + _CPNativeInputFieldActive; + +var _CPCopyPlaceholder = '-'; + +@implementation _CPNativeInputManager : CPObject + ++ (BOOL)isNativeInputFieldActive +{ + return _CPNativeInputFieldActive; +} + ++ (void)cancelCurrentNativeInputSession +{ + +#if PLATFORM(DOM) + _CPNativeInputField.innerHTML = ''; +#endif + + [self _endInputSessionWithString:_CPNativeInputField.innerHTML]; +} + ++ (void)cancelCurrentInputSessionIfNeeded +{ + if (!_CPNativeInputFieldActive) + return; + + [self cancelCurrentNativeInputSession]; +} + ++ (void)_endInputSessionWithString:(CPString)aStr +{ + _CPNativeInputFieldActive = NO; + + var currentFirstResponder = [[CPApp keyWindow] firstResponder], + placeholderRange = CPMakeRange([currentFirstResponder selectedRange].location - 1, 1); + + [currentFirstResponder setSelectedRange:placeholderRange]; + [currentFirstResponder insertText:aStr]; + _CPNativeInputField.innerHTML = ''; + + + [self hideInputElement]; + [currentFirstResponder updateInsertionPointStateAndRestartTimer:YES]; +} + ++ (void)initialize +{ +#if PLATFORM(DOM) + _CPNativeInputField = document.createElement("div"); + _CPNativeInputField.contentEditable = YES; + _CPNativeInputField.style.width = "64px"; + _CPNativeInputField.style.zIndex = 10000; + _CPNativeInputField.style.position = "absolute"; + _CPNativeInputField.style.visibility = "visible"; + _CPNativeInputField.style.padding = "0px"; + _CPNativeInputField.style.margin = "0px"; + _CPNativeInputField.style.whiteSpace = "pre"; + _CPNativeInputField.style.outline = "0px solid transparent"; + + document.body.appendChild(_CPNativeInputField); + + _CPNativeInputField.addEventListener("keyup", function(e) + { + _CPNativeInputFieldKeyUpCalled = YES; + + // filter out the shift-up, cursor keys and friends used to access the deadkeys + // fixme: e.which is depreciated(?) -> find a better way to identify the modifier-keyups + if (e.which < 27 || e.which == 91 || e.which == 93) // include apple command keys + { + if (e.which == 13) + _CPNativeInputField.innerHTML = ''; + + if (_CPNativeInputField.innerHTML.length == 0 || _CPNativeInputField.innerHTML.length > 2) // backspace + [self cancelCurrentInputSessionIfNeeded]; + + return false; // prevent the default behaviour + } + + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) + return false; // prevent the default behaviour + + var charCode = _CPNativeInputField.innerHTML.charCodeAt(0); + + // å and Å need to be filtered out in keyDown: due to chrome inserting 229 on a deadkey + if (charCode == 229 || charCode == 197) + { + [currentFirstResponder insertText:_CPNativeInputField.innerHTML]; + _CPNativeInputField.innerHTML = ''; + return; + } + + // chrome-trigger: keypressed is omitted for deadkeys + if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyPressedCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3) + { + _CPNativeInputFieldActive = YES; + [currentFirstResponder _activateNativeInputElement:_CPNativeInputField]; + } + else + { + if (_CPNativeInputFieldActive) + [self _endInputSessionWithString:_CPNativeInputField.innerHTML]; + + // prevent the copy placeholder beeing removed by cursor keys + if (_CPNativeInputFieldKeyPressedCalled) + _CPNativeInputField.innerHTML = ''; + } + + _CPNativeInputFieldKeyDownCalled = NO; + + return false; // prevent the default behaviour + }, true); + + _CPNativeInputField.addEventListener("keydown", function(e) + { + // this protects from heavy typing and the shift key + if (_CPNativeInputFieldKeyDownCalled) + return true; + + _CPNativeInputFieldKeyDownCalled = YES; + _CPNativeInputFieldKeyUpCalled = NO; + _CPNativeInputFieldKeyPressedCalled = NO; + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + // webkit-browsers: cursor keys do not emit keypressed and would otherwise activate deadkey mode + if (!CPBrowserIsEngine(CPGeckoBrowserEngine) && e.which >= 37 && e.which <= 40) + _CPNativeInputFieldKeyPressedCalled = YES; + + if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) + return; + + // FF-trigger: here the best way to detect a dead key is the missing keyup event + if (CPBrowserIsEngine(CPGeckoBrowserEngine)) + setTimeout(function(){ + _CPNativeInputFieldKeyDownCalled = NO; + + if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyUpCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3 && !e.repeat) + { + _CPNativeInputFieldActive = YES; + [currentFirstResponder _activateNativeInputElement:_CPNativeInputField]; + } + else if (!_CPNativeInputFieldActive) + [self hideInputElement]; + }, 200); + + return false; + }, true); // capture mode + + _CPNativeInputField.addEventListener("keypress", function(e) + { + _CPNativeInputFieldKeyUpCalled = YES; + _CPNativeInputFieldKeyPressedCalled = YES; + return false; + + }, true); // capture mode + + _CPNativeInputField.onpaste = function(e) + { + var nativeClipboard = (e.originalEvent || e).clipboardData, + richtext, + pasteboard = [CPPasteboard generalPasteboard], + rtfdata = [CPAttributedString new], + _CPDOMParsefunction = function(node) + { + if (node.nodeType === 1 && node.nodeName === 'SPAN') + { + var text = node.innerHTML, + style = window.getComputedStyle(node), + styleAttributes = @{}; + + // extract color from the DOM + var rgbmatch = style.getPropertyValue('color').match(new RegExp(/rgb\((\d+)[, ]+(\d+)[, ]+(\d+)\)/)); + + if (rgbmatch) + [styleAttributes setObject:[CPColor colorWithRed:rgbmatch[1]/255.0 green:rgbmatch[2]/255.0 blue:rgbmatch[3]/255.0 alpha:1] + forKey:CPForegroundColorAttributeName]; + + // extract font from the DOM + + var fontname = style.getPropertyValue('font-family'), + fontsize = parseInt(style.getPropertyValue('font-size'), 10); + + if (fontname && fontsize) + [styleAttributes setObject:[CPFont fontWithName:fontname size:fontsize italic:NO] forKey:CPFontAttributeName]; + + [rtfdata appendAttributedString:[[[CPAttributedString alloc] initWithString:text attributes:styleAttributes] _stringByParsingHTMLEntities]]; + } + }; + + // this is the native rich safari path + // the detection leverages the observation that safari puts a lot of cryptic types on the pasteboard (16 or so) + // this is not the case with any other browser that i have seen so far. + // safari does not currently provide data for any of the rich types that it advertises, though. + // for this reason, we have to let the paste execute and collect data from the DOM afterwards + // i did not get this working so far. the event is not forwarded for reasons that are beyond my understanding :-( + // for this reason, i disabled the code path so at least the plain content gets pasted + if (NO && nativeClipboard.types.length > 10) + { + // http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser/6804718#6804718 + function waitForPastedData(elem) + { + if (elem.childNodes && elem.childNodes.length > 0) + { + _CPwalkTheDOM(elem, _CPDOMParsefunction); + [pasteboard declareTypes:[CPRTFPboardType] owner:nil]; + [pasteboard setString:[_CPRTFProducer produceRTF:rtfdata documentAttributes:@{}] forType:CPRTFPboardType]; + + [[[CPApp keyWindow] firstResponder] paste:self]; + elem.innerHTML = _CPCopyPlaceholder; + } + else + { + setTimeout(function() + { + waitForPastedData(elem) + }, 20); + } + } + + waitForPastedData(_CPNativeInputField); + + return true; + } + + // this is the native rich chrome path: + // we have to construct an CPAttributedString whilst walking the dom and looking at the CSS attributes + if (richtext = nativeClipboard.getData('text/html')) + { + e.preventDefault(); + _CPNativeInputField.innerHTML = richtext; + _CPwalkTheDOM(_CPNativeInputField, _CPDOMParsefunction); + + [pasteboard declareTypes:[CPRTFPboardType] owner:nil]; + [pasteboard setString:[_CPRTFProducer produceRTF:rtfdata documentAttributes:@{}] forType:CPRTFPboardType]; + + [[[CPApp keyWindow] firstResponder] paste:self]; + _CPNativeInputField.innerHTML = _CPCopyPlaceholder; + return false; + } + + // this is the rich FF codepath (here we can use RTF directly) + if (richtext = nativeClipboard.getData('text/rtf')) + { + e.preventDefault(); + [pasteboard declareTypes:[CPRTFPboardType] owner:nil]; + [pasteboard setString:richtext forType:CPRTFPboardType]; + + // prevent dom-flickering (settimeout does not work here) + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + setTimeout(function(){ // prevent dom-flickering (only FF) + [currentFirstResponder paste:self]; + }, 20); + + return false; + } + + // plain is the same in all browsers... + + var data = e.clipboardData.getData('text/plain'), + cappString = [pasteboard stringForType:CPStringPboardType]; + + if (cappString != data) + { + [pasteboard declareTypes:[CPStringPboardType] owner:nil]; + [pasteboard setString:data forType:CPStringPboardType]; + } + + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + setTimeout(function(){ // prevent dom-flickering (only needed for FF) + [currentFirstResponder paste:self]; + }, 20); + + return false; + } + + if (CPBrowserIsEngine(CPGeckoBrowserEngine)) + { + _CPNativeInputField.oncopy = function(e) + { + var pasteboard = [CPPasteboard generalPasteboard], + string, + currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + [currentFirstResponder copy:self]; + + var stringForPasting = [pasteboard stringForType:CPStringPboardType]; + e.clipboardData.setData('text/plain', stringForPasting); + + return false; + } + + _CPNativeInputField.oncut = function(e) + { + var pasteboard = [CPPasteboard generalPasteboard], + string, + currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + // prevent dom-flickering + setTimeout(function(){ + [currentFirstResponder cut:self]; + }, 20); + + // this is necessary because cut will only execute in the future + [currentFirstResponder copy:self]; + + var stringForPasting = [pasteboard stringForType:CPStringPboardType]; + + e.clipboardData.setData('text/plain', stringForPasting); + + return false; + } + } +#endif +} + ++ (void)focusForTextView:(CPTextView)currentFirstResponder +{ + if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) + return; + + [self hideInputElement]; + +#if PLATFORM(DOM) + _CPNativeInputField.focus(); +#endif + +} + ++ (void)focusForClipboardOfTextView:(CPTextView)textview +{ + +#if PLATFORM(DOM) + if (!_CPNativeInputFieldActive && _CPNativeInputField.innerHTML.length == 0) + _CPNativeInputField.innerHTML = _CPCopyPlaceholder; // make sure we have a selection to allow the native pasteboard work in safari + + [self focusForTextView:textview]; + + // select all in the contenteditable div (http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div) + if (document.body.createTextRange) + { + var range = document.body.createTextRange(); + + range.moveToElementText(_CPNativeInputField); + range.select(); + } + else if (window.getSelection) + { + var selection = window.getSelection(), + range = document.createRange(); + + range.selectNodeContents(_CPNativeInputField); + selection.removeAllRanges(); + selection.addRange(range); + } +#endif + +} + ++ (void)hideInputElement +{ + +#if PLATFORM(DOM) + _CPNativeInputField.style.top = "-10000px"; + _CPNativeInputField.style.left = "-10000px"; +#endif + +} + +@end + +@implementation CPAttributedString(_MinimalHTMLParser) + +-(void) _setRegularExpression:(JSObject)re toFontTrait:(CPFontTrait)aTrait +{ + while (match = re.exec(_string)) + { + var attribs = [[self attributesAtIndex:match.index effectiveRange:nil] copy], + font = [attribs objectForKey:CPFontAttributeName]; + [attribs setObject:[[CPFontManager sharedFontManager] convertFont:font toHaveTrait:aTrait] forKey:CPFontAttributeName] + [self setAttributes:attribs range:CPMakeRange(match.index, match[0].length)]; + } +} + +-(void) _replaceEveryOccurenceOfRegularExpression:(JSObject)re withString:(CPString)aString +{ + while (match = re.exec(_string)) + [self replaceCharactersInRange:CPMakeRange(match.index, match[0].length) withString:aString]; +} + + +-(CPAttributedString) _stringByParsingHTMLEntities +{ + [self _setRegularExpression:/(.+?)<\/b>/gi toFontTrait:CPFontBoldTrait]; + [self _setRegularExpression:/(.+?)<\/i>/gi toFontTrait:CPFontItalicTrait]; + [self _replaceEveryOccurenceOfRegularExpression:/<[^>]+>/i withString:'']; + [self _replaceEveryOccurenceOfRegularExpression:/</i withString:'<']; + [self _replaceEveryOccurenceOfRegularExpression:/>/i withString:'>']; + [self _replaceEveryOccurenceOfRegularExpression:/&/i withString:'&']; + + return self; +} + +@end diff --git a/AppKit/CPTextView/CPTypesetter.j b/AppKit/CPTextView/CPTypesetter.j new file mode 100644 index 000000000..475e65f87 --- /dev/null +++ b/AppKit/CPTextView/CPTypesetter.j @@ -0,0 +1,430 @@ + +/* + * CPTypesetter.j + * AppKit + * + * Created by Daniel Boehringer on 27/12/2013. + * All modifications copyright Daniel Boehringer 2013. + * Extensive code formatting and review by Andrew Hankinson + * Based on original work by + * Emmanuel Maillard on 27/02/2010. + * Copyright Emmanuel Maillard 2010. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +@import "CPParagraphStyle.j" +@import "CPTextStorage.j" +@import "CPFont.j" + +// forward declare these classes for type matching +@class CPLayoutManager +@class CPTextContainer +@class CPTextView + +/* + CPTypesetterControlCharacterAction +*/ +CPTypesetterZeroAdvancementAction = 1 << 0; +CPTypesetterWhitespaceAction = 1 << 1; +CPSTypesetterHorizontalTabAction = 1 << 2; +CPTypesetterLineBreakAction = 1 << 3; +CPTypesetterParagraphBreakAction = 1 << 4; +CPTypesetterContainerBreakAction = 1 << 5; + +var CPSystemTypesetterFactory, + _sharedSimpleTypesetter; + +@implementation CPTypesetter : CPObject + + +#pragma mark - +#pragma mark Class methods + ++ (void)initialize +{ + [CPTypesetter _setSystemTypesetterFactory:[CPSimpleTypesetter class]]; +} + ++ (id)sharedSystemTypesetter +{ + return [CPSystemTypesetterFactory sharedInstance]; +} + ++ (void)_setSystemTypesetterFactory:(Class)aClass +{ + CPSystemTypesetterFactory = aClass; +} + +- (CPTypesetterControlCharacterAction)actionForControlCharacterAtIndex:(unsigned)charIndex +{ + return CPTypesetterZeroAdvancementAction; +} + +- (CPLayoutManager)layoutManager +{ + return nil; +} + +- (CPTextContainer)currentTextContainer +{ + return nil; +} + +- (CPArray)textContainers +{ + return nil; +} + +- (void)layoutGlyphsInLayoutManager:(CPLayoutManager)layoutManager + startingAtGlyphIndex:(unsigned)startGlyphIndex + maxNumberOfLineFragments:(unsigned)maxNumLines + nextGlyphIndex:(UIntegerReference)nextGlyph +{ + _CPRaiseInvalidAbstractInvocation(self, _cmd); +} + +@end + +@implementation CPSimpleTypesetter : CPTypesetter +{ + CPLayoutManager _layoutManager @accessors(property=layoutManager); + CPTextContainer _currentTextContainer @accessors(property=currentTextContainer); + CPTextStorage _textStorage; + + CPRange _attributesRange; + CPDictionary _currentAttributes; + CPParagraphStyle _currentParagraph; + + float _lineHeight; + float _lineBase; + float _lineWidth; + + unsigned _indexOfCurrentContainer; + + CPArray _lineFragments; +} + + +#pragma mark - +#pragma mark Class methods + ++ (id)sharedInstance +{ + if (!_sharedSimpleTypesetter) + _sharedSimpleTypesetter = [[CPSimpleTypesetter alloc] init]; + + return _sharedSimpleTypesetter; +} + +- (CPArray)textContainers +{ + return [_layoutManager textContainers]; +} + +- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction +{ + var tabStops = [_currentParagraph tabStops]; + + if (!tabStops) + tabStops = [CPParagraphStyle _defaultTabStops]; + + var l = tabStops.length; + + if (aWidth > tabStops[l - 1]._location) + return nil; + + for (var i = l - 1; i >= 0; i--) + { + if (aWidth > tabStops[i]._location) + { + if (i + 1 < l) + return tabStops[i + 1]; + } + } + + if (i === -1) + return tabStops[0]; + + return nil; +} + +- (BOOL)_flushRange:(CPRange)lineRange + lineOrigin:(CGPoint)lineOrigin + currentContainer:(CPTextContainer)aContainer + advancements:(CPArray)advancements + lineCount:(unsigned)lineCount + sameLine:(BOOL)sameLine +{ + var myX = 0, + rect = CGRectMake(lineOrigin.x, lineOrigin.y, _lineWidth, _lineHeight), + containerSize = aContainer._size; + + [_layoutManager _appendNewLineFragmentInTextContainer:_currentTextContainer forGlyphRange:lineRange] + + var fragment = [_layoutManager._lineFragments lastObject]; + fragment._isLast = !sameLine; + _lineFragments.push(fragment); + + [_layoutManager setLineFragmentRect:rect forGlyphRange:lineRange usedRect:rect]; + + switch ([_currentParagraph alignment]) + { + case CPLeftTextAlignment: + myX = 0; + break; + + case CPCenterTextAlignment: + myX = (containerSize.width - _lineWidth) / 2; + break; + + case CPRightTextAlignment: + myX = containerSize.width - _lineWidth; + break; + } + + [_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange]; + [_layoutManager _setAdvancements:advancements forGlyphRange:lineRange]; + + if (!sameLine) //fix the _lineFragments when fontsizes differ + { + var l = _lineFragments.length; + + for (var i = 0 ; i < l ; i++) + [_lineFragments[i] _adjustForHeight:_lineHeight]; + } + + if (!lineCount) // do not rescue on first line + return NO; + + if (aContainer._inResizing) + return NO; + + return ([_layoutManager _rescuingInvalidFragmentsWasPossibleForGlyphRange:lineRange]); +} + +- (void)layoutGlyphsInLayoutManager:(CPLayoutManager)layoutManager + startingAtGlyphIndex:(unsigned)glyphIndex + maxNumberOfLineFragments:(unsigned)maxNumLines + nextGlyphIndex:(UIntegerReference)nextGlyph +{ + var textContainers = [layoutManager textContainers], + textContainersCount = [textContainers count]; + + _layoutManager = layoutManager; + _textStorage = [_layoutManager textStorage]; + _indexOfCurrentContainer = MAX(0, [textContainers + indexOfObject:[_layoutManager textContainerForGlyphAtIndex:glyphIndex effectiveRange:nil withoutAdditionalLayout:YES] + inRange:CPMakeRange(0, textContainersCount)]); + + _currentTextContainer = textContainers[_indexOfCurrentContainer]; + + _attributesRange = CPMakeRange(0, 0); + _lineHeight = 0; + _lineBase = 0; + _lineWidth = 0; + + var containerSize = [_currentTextContainer containerSize], + containerSizeWidth = containerSize.width, + containerSizeHeight = containerSize.height, + lineRange = CPMakeRange(glyphIndex, 0), + wrapRange = CPMakeRange(0, 0), + wrapWidth = 0, + isNewline = NO, + isTabStop = NO, + isWordWrapped = NO, + numberOfGlyphs= [_textStorage length], + leading, + numLines = 0, + theString = [_textStorage string], + lineOrigin, + ascent, + descent, + advancements = [], + prevRangeWidth = 0, + measuringRange = CPMakeRange(glyphIndex, 0), + currentAnchor = 0, + currentFont, + currentFontLineHeight, + previousFont, + currentParagraphMinimumLineHeight, + currentParagraphMaximumLineHeight, + currentParagraphLineSpacing; + + if (glyphIndex > 0) + lineOrigin = CGPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin); + else if ([_layoutManager extraLineFragmentTextContainer]) + lineOrigin = CGPointMake(0, [_layoutManager extraLineFragmentUsedRect].origin.y); + else + lineOrigin = CGPointMake(0, 0); + + [_layoutManager _removeInvalidLineFragments]; + + if (![_textStorage length]) + return; + + _lineFragments = []; + + for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++) + { + // check whether there any change in the attributes from here on + if (!CPLocationInRange(glyphIndex, _attributesRange)) + { + _currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange]; + currentFont = [_currentAttributes objectForKey:CPFontAttributeName]; + _currentParagraph = [_currentAttributes objectForKey:CPParagraphStyleAttributeName] || [CPParagraphStyle defaultParagraphStyle]; + currentParagraphMinimumLineHeight = [_currentParagraph minimumLineHeight]; + currentParagraphMaximumLineHeight = [_currentParagraph maximumLineHeight]; + currentParagraphLineSpacing = [_currentParagraph lineSpacing]; + + if (!currentFont) + currentFont = [_textStorage font] || [CPFont systemFontOfSize:12.0]; + + ascent = [currentFont ascender] + descent = [currentFont descender] + leading = (ascent - descent) * 0.2; // FAKE leading + + currentFontLineHeight = ascent - descent + leading; + + if (previousFont !== currentFont) + { + measuringRange = CPMakeRange(glyphIndex, 0); + currentAnchor = prevRangeWidth; + previousFont = currentFont; + } + + } + + if (currentFontLineHeight > _lineHeight) + _lineHeight = currentFontLineHeight; + + if (ascent > _lineBase) + _lineBase = ascent; + + lineRange.length++; + measuringRange.length++; + + var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons + rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor; + + switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char. + { + case 9: // '\t' + { + var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0]; + + isTabStop = YES; + + if (nextTab) + rangeWidth = nextTab._location - lineOrigin.x; + else + rangeWidth += 28; //FIXME + } // fallthrough intentional + case 32: // ' ' + wrapRange = CPMakeRangeCopy(lineRange); + wrapWidth = rangeWidth; + wrapRange._height = _lineHeight; + wrapRange._base = _lineBase; + break; + + case 10: + case 13: + isNewline = YES; + } + + advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent}); + + prevRangeWidth = _lineWidth = rangeWidth; + + if (lineOrigin.x + rangeWidth > containerSizeWidth) + { + if (wrapWidth) + { + lineRange = wrapRange; + _lineWidth = wrapWidth; + _lineHeight = wrapRange._height; + _lineBase = wrapRange._base; + } + + isNewline = YES; + isWordWrapped = YES; + glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character + } + + if (isNewline || isTabStop) + { + if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline]) + return; + + if (isTabStop) + { + lineOrigin.x += rangeWidth; + isTabStop = NO; + } + + if (isNewline) + { + if (currentParagraphMinimumLineHeight && currentParagraphMinimumLineHeight > _lineHeight) + _lineHeight = currentParagraphMinimumLineHeight; + + if (currentParagraphMaximumLineHeight && currentParagraphMaximumLineHeight < _lineHeight) + _lineHeight = currentParagraphMaximumLineHeight; + + lineOrigin.y += _lineHeight; + + if (currentParagraphLineSpacing) + lineOrigin.y += currentParagraphLineSpacing; + + if (lineOrigin.y > containerSizeHeight && _indexOfCurrentContainer < textContainersCount - 1) + { + _currentTextContainer = textContainers[++_indexOfCurrentContainer]; + containerSize = [_currentTextContainer containerSize]; + containerSizeWidth = containerSize.width; + containerSizeHeight = containerSize.height; + } + + lineOrigin.x = 0; + numLines++; + isNewline = NO; + _lineFragments = []; + _lineHeight = 0; + _lineBase = ascent; + } + + _lineWidth = 0; + advancements = []; + currentAnchor = 0; + prevRangeWidth = 0; + lineRange = CPMakeRange(glyphIndex + 1, 0); + measuringRange = CPMakeRange(glyphIndex + 1, 0); + wrapRange = CPMakeRange(0, 0); + wrapWidth = 0; + isWordWrapped = NO; + } + } + + // this is to "flush" the remaining characters + if (lineRange.length) + { + [self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO]; + } + + var rect = CGRectMake(0, lineOrigin.y, containerSizeWidth, [_layoutManager._lineFragments lastObject]._usedRect.size.height - descent); + [_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer]; +} + +@end diff --git a/AppKit/CPTextView/_CPRTFParser.j b/AppKit/CPTextView/_CPRTFParser.j new file mode 100644 index 000000000..a94081c8d --- /dev/null +++ b/AppKit/CPTextView/_CPRTFParser.j @@ -0,0 +1,788 @@ +/* RTFParser.j + + Parse a RTF string into a CPAttributedString + + Copyright (C) 2014 Daniel Boehringer + +FIXME: this class should be redone using a 'real' parser + + * all paragraph spacing information is currently not parsed + + + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +@import +@import +@import "CPFontManager.j" +@import "CPParagraphStyle.j" + +@global CPLeftTextAlignment +@global CPRightTextAlignment +@global CPCenterTextAlignment +@global CPJustifiedTextAlignment +@global CPNaturalTextAlignment + +@global CPFontAttributeName +@global CPForegroundColorAttributeName + +var hexTable = []; + +// Hold the attributes of the current run +@implementation _RTFAttribute : CPObject +{ + CPRange _range; + CPParagraphStyle paragraph; + CPColor fgColour; + CPColor bgColour; + CPColor ulColour; + CPString fontName; + unsigned fontSize; + BOOL bold; + BOOL italic; + BOOL underline; + BOOL strikethrough; + BOOL script; + BOOL _tabChanged; +} + +- (id)init +{ + if (self = [super init]) + { + [self resetFont]; + [self resetParagraphStyle]; + _range = CPMakeRange(0, 0); + } + + return self; +} + +- (id)copy +{ + var mynew = [_RTFAttribute new]; + + mynew.paragraph = [paragraph copy]; + mynew.fontName = fontName; + mynew.fgColour = fgColour; + mynew.bgColour = bgColour; + mynew.ulColour = ulColour; + + return mynew; +} + +- (CPFont)currentFont +{ + var font = [CPFont _fontWithName:fontName size:fontSize bold:bold italic:italic]; + + if (font) + return font; + + //Before giving up and using a default font, we try if this is + //not the case of a font with a composite name, such as + //'Helvetica-Light'. In that case, even if we don't have + //exactly an 'Helvetica-Light' font family, we might have an + //'Helvetica' one. + var range = [fontName rangeOfString:@"-"]; + + if (range.location != CPNotFound) + { + var fontFamily = [fontName substringToIndex: range.location]; + + font = [CPFont fontWithName:fontFamily size:fontSize]; + } + + /* Last resort, default font. :-( */ + if (font == nil) + font = [CPFont systemFontOfSize:fontSize]; + + return font; +} + +- (CPNumber)script +{ + return [CPNumber numberWithInt: script]; +} + +- (CPNumber)underline +{ + if (underline != 0) + return [CPNumber numberWithInteger: underline]; + else + return nil; +} + +- (CPNumber)strikethrough +{ + if (strikethrough != 0) + return [CPNumber numberWithInteger: strikethrough]; + else + return nil; +} + +- (void)resetParagraphStyle +{ + paragraph = [[CPParagraphStyle defaultParagraphStyle] copy]; +} + +- (void)resetFont +{ + var font = [CPFont systemFontOfSize:12]; + + fontName = [font familyName]; + fontSize = 12.0; + italic = NO; + bold = NO; + underline = 0; + strikethrough = 0; + script = 0; +} + +- (void)addTab:(float)location type:(CPTextTabType)type +{ + var tab = [[CPTextTab alloc] initWithType:CPLeftTabStopType + location:location]; + + if (!_tabChanged) + { + [paragraph setTabStops:[tab]]; + _tabChanged = YES; + } + else + { + [paragraph addTabStop: tab]; + } +} + +- (CPDictionary)dictionary +{ + var ret = @{}; + [ret setObject:[self currentFont] forKey:CPFontAttributeName]; + [ret setObject:paragraph forKey:CPParagraphStyleAttributeName]; + + if (fgColour) + [ret setObject:fgColour forKey:CPForegroundColorAttributeName]; + + return ret; +} +@end + + +// based on https://github.com/lazygyu/RTF-parser + +var kRTFParserType_char = 0, + kRTFParserType_dest = 1, + kRTFParserType_prop = 2, + kRTFParserType_spec = 3; + +// Keyword descriptions +var kRgsymRtf = { + // keyword dflt fPassDflt kwd idx + "b" : [ "b", 1, false, kRTFParserType_prop, "propBold"], + "ul" : [ "ul", 1, false, kRTFParserType_prop, "propUnderline"], + "i" : [ "i", 1, false, kRTFParserType_prop, "propItalic"], + "li" : [ "li", 0, false, kRTFParserType_prop, "propPgnFormat"], + "pgnucltr" : [ "pgnucltr", "pgULtr", true, kRTFParserType_prop, "propPgnFormat"], + "pgnlcltr" : [ "pgnlcltr", "pgLLtr", true, kRTFParserType_prop, "propPgnFormat"], + "qc" : [ "qc", "justC", true, kRTFParserType_prop, "propJust"], + "ql" : [ "ql", "justL", true, kRTFParserType_prop, "propJust"], + "qr" : [ "qr", "justR", true, kRTFParserType_prop, "propJust"], + "qj" : [ "qj", "justF", true, kRTFParserType_prop, "propJust"], + "paperw" : [ "paperw", 12240, false, kRTFParserType_prop, "propXaPage"], + "paperh" : [ "paperh", 15480, false, kRTFParserType_prop, "propYaPage"], + "margl" : [ "margl", 1800, false, kRTFParserType_prop, "propXaLeft"], + "margr" : [ "margr", 1800, false, kRTFParserType_prop, "propXaRight"], + "margt" : [ "margt", 1440, false, kRTFParserType_prop, "propYaTop"], + "margb" : [ "margb", 1440, false, kRTFParserType_prop, "propYaBottom"], + "pgnstart" : [ "pgnstart", 1, true, kRTFParserType_prop, "propPgnStart"], + "facingp" : [ "facingp", 1, true, kRTFParserType_prop, "propFacingp"], + "landscape" : [ "landscape",1, true, kRTFParserType_prop, "propLandscape"], + "par" : [ "par", 0, false, kRTFParserType_char, "\n"], + "pard" : [ "pard", 0, false, kRTFParserType_prop, "propDefaultPara"], + "\0x0a" : [ "\0x0a", 0, false, kRTFParserType_char, "\n"], + "\0x0d" : [ "\0x0d", 0, false, kRTFParserType_char, ""], + "tab" : [ "tab", 0, false, kRTFParserType_char, "\t"], + "ldblquote" : [ "ldblquote",0, false, kRTFParserType_char, '"'], + "rdblquote" : [ "rdblquote",0, false, kRTFParserType_char, '"'], + "bin" : [ "bin", 0, false, kRTFParserType_spec, "ipfnBin"], + "*" : [ "*", 0, false, kRTFParserType_spec, "ipfnDestSkip"], + "'" : [ "'", 0, false, kRTFParserType_spec, "ipfnHex"], + "author" : [ "author", 0, false, kRTFParserType_dest, "destSkip"], + "buptim" : [ "buptim", 0, false, kRTFParserType_dest, "destSkip"], + "colortbl" : [ "colortbl", 0, false, kRTFParserType_dest, "destSkip"], + "comment" : [ "comment", 0, false, kRTFParserType_dest, "destSkip"], + "creatim" : [ "creatim", 0, false, kRTFParserType_dest, "destSkip"], + "doccomm" : [ "doccomm", 0, false, kRTFParserType_dest, "destSkip"], + "fonttbl" : [ "fonttbl", 0, false, kRTFParserType_dest, "destSkip"], + "footer" : [ "footer", 0, false, kRTFParserType_dest, "destSkip"], + "footerf" : [ "footerf", 0, false, kRTFParserType_dest, "destSkip"], + "footerl" : [ "footerl", 0, false, kRTFParserType_dest, "destSkip"], + "footerr" : [ "footerr", 0, false, kRTFParserType_dest, "destSkip"], + "footnote" : [ "footnote", 0, false, kRTFParserType_dest, "destSkip"], + "ftncn" : [ "ftncn", 0, false, kRTFParserType_dest, "destSkip"], + "ftnsep" : [ "ftnsep", 0, false, kRTFParserType_dest, "destSkip"], + "ftnsepc" : [ "ftnsepc", 0, false, kRTFParserType_dest, "destSkip"], + "fprq" : [ "fprq", 0, false, kRTFParserType_dest, "destSkip"], +// "fcharset" : [ "fcharset", 0, false, kRTFParserType_dest, "destSkip"], + "rquote" : [ "rquote", 0, false, kRTFParserType_char, "'"], +// "s" : [ "s", 0, false, kRTFParserType_dest, "destSkip"], + "header" : [ "header", 0, false, kRTFParserType_dest, "destSkip"], + "headerf" : [ "headerf", 0, false, kRTFParserType_dest, "destSkip"], + "headerl" : [ "headerl", 0, false, kRTFParserType_dest, "destSkip"], + "headerr" : [ "headerr", 0, false, kRTFParserType_dest, "destSkip"], + "info" : [ "info", 0, false, kRTFParserType_dest, "destSkip"], + "keywords" : [ "keywords", 0, false, kRTFParserType_dest, "destSkip"], + "operator" : [ "operator", 0, false, kRTFParserType_dest, "destSkip"], + "pict" : [ "pict", 0, false, kRTFParserType_dest, "destSkip"], + "printim" : [ "printim", 0, false, kRTFParserType_dest, "destSkip"], + "private1" : [ "private1", 0, false, kRTFParserType_dest, "destSkip"], + "revtim" : [ "revtim", 0, false, kRTFParserType_dest, "destSkip"], + "rxe" : [ "rxe", 0, false, kRTFParserType_dest, "destSkip"], + "stylesheet" : [ "stylesheet",0, false, kRTFParserType_dest, "destSkip"], + "subject" : [ "subject", 0, false, kRTFParserType_dest, "destSkip"], + "tc" : [ "tc", 0, false, kRTFParserType_dest, "destSkip"], + "title" : [ "title", 0, false, kRTFParserType_dest, "destSkip"], + "txe" : [ "txe", 0, false, kRTFParserType_dest, "destSkip"], + "xe" : [ "xe", 0, false, kRTFParserType_dest, "destSkip"], + "[" : [ "[", 0, false, kRTFParserType_char, '['], + " " : [ " ", 0, false, kRTFParserType_char, ' '], + "]" : [ "]", 0, false, kRTFParserType_char, ']'], + "{" : [ "{", 0, false, kRTFParserType_char, '{'], + "}" : [ "}", 0, false, kRTFParserType_char, '}'], + "\\" : [ "\\", 0, false, kRTFParserType_char, '\\'] + }; + +@implementation _CPRTFParser : CPObject +{ + CPString _codePage; + CGSize _paper; + CPString _rtf; + unsigned _curState; + CPArray _states; + unsigned _currentParseIndex; + BOOL _hexreturn; + _RTFAttribute _currentRun; + CPAttributedString _result; + CPArray _colorArray; + CPArray _fontArray; + CPString _freename; + BOOL _parsingFontTable; +} + +- (id)init +{ + if (self = [super init]) + { + _paper = CPMakeSize(0, 0); + _rtf = ""; + _curState = 0; // 0 = normal, 1 = skip + _states = []; + _currentParseIndex = 0; + _hexreturn = NO; + _result = [CPAttributedString new]; + _colorArray = []; + _fontArray = ['Arial']; // FIXME: should be name of system font + _freename = ""; + _parsingFontTable = NO; + } + + return self; +} + +- (CPString)_checkChar:(CPArray)sym parameter:(CPString)ch +{ + switch (_curState) + { + case 0: + if (sym && sym[4]) + return sym[4]; + + case 1: + // CPLogConsole("skipped : " + sym[4]); + return ''; + + default: + if (sym && sym[4]) + return sym[4]; + } +} + +- (BOOL)pushState +{ + _states.push["group"]; + return YES; +} + +- (BOOL)popState +{ + _states.pop(); + + if (_curState > 0) + _curState--; + + return YES; +} + +- (CPString)_parseSpec:(CPArray)sym parameter:(CPString)v +{ + var ch = ''; + + switch (sym[4]) + { + case "ipfnDestSkip": + _curState++; + return ''; + + case "ipfnHex": + ch = _rtf.charAt(++_currentParseIndex); + + var hex = ''; + + while (/[a-fA-F0-9\']/.test(ch)) + { + if (ch == "'") + { + _currentParseIndex++; + continue; + } + + hex += (ch + ''); + ch = _rtf.charAt(++_currentParseIndex); + } + //ch = parseInt(ch, 16); + //console.log("hex : " + hex); + _hexreturn = YES; + _currentParseIndex--; + + if (_curState !== 0) + return ''; + else + return hex; + break; + + case "codePage": + ch = _rtf.charAt(++_currentParseIndex); + + var code = ''; + + while (/[0-9]/.test(ch)) + { + code += (ch + ''); + ch = _rtf.charAt(++_currentParseIndex); + } + + _codePage = code; + _currentParseIndex--; + break; + } + + return ''; +} + +- (void)_flushCurrentRun +{ + var newOffset = 0; + + if (_currentRun) + { + if ([_result length] == _currentRun._range.location) + return; + + _currentRun._range.length = [_result length] - _currentRun._range.location; + newOffset = CPMaxRange(_currentRun._range); + + var dict = [_currentRun dictionary]; + + [_result setAttributes:dict range:_currentRun._range]; // flush previous run + _currentRun.fgColour = [CPColor blackColor]; + } + else + _currentRun = [_RTFAttribute new]; + + _currentRun._range = CPMakeRange(newOffset, 0); // open a new one +} + +- (CPString)_applyPropChange:sym parameter:param +{ + //console.log("prop : " + sym[0] + " / param : " + param+ ' '); + + switch (sym[0]) + { + case "pard": + [self _flushCurrentRun]; + break; + + case "b": // bold + if (param === 0) + { + if (_currentRun && _currentRun.bold) + [self _flushCurrentRun]; + + _currentRun.bold = NO + } + else + { + if (_currentRun && !_currentRun.bold) + [self _flushCurrentRun]; + + _currentRun.bold = YES; + } + + break; + + case "i": // italic + if (param === 0) + { + if (_currentRun && _currentRun.italic) + [self _flushCurrentRun]; + + _currentRun.italic = NO + } + else + { + if (_currentRun && !_currentRun.italic) + [self _flushCurrentRun]; + + _currentRun.italic = YES; + } + + break; + + case "qc": // paragraph center + [_currentRun.paragraph setAlignment:CPCenterTextAlignment]; + break; + + case "paperw": + _paper.width = param; + break; + + case "paperh": + _paper.height = param; + break; + } + + return ''; +} + + +- (CPString)_changeDest:(CPArray)sym +{ + switch (sym[0]) + { + case "colortbl": + _colorArray.push([CPColor blackColor]); + break; + + case "fonttbl": + _parsingFontTable = YES; + break; + } + + if (sym[4] == "destSkip") + { + CPLogConsole("Dest skip start : [" + sym[0] + "]"); + _curState++; + } + + return ''; +} + +- (CPString)_translateKeyword:(CPString)keyword parameter:(CPString)param fParameter:(BOOL)fParam +{ + if (kRgsymRtf[keyword] !== undefined) + { + var sym = kRgsymRtf[keyword]; + + switch (sym[3]) + { + case kRTFParserType_prop: + if (sym[2] || !fParam) + param = sym[1]; + + return [self _applyPropChange:sym parameter:param]; + + case kRTFParserType_char: + return [self _checkChar:sym parameter:param]; + + case kRTFParserType_dest: + return [self _changeDest:sym]; + + case kRTFParserType_spec: + return [self _parseSpec:sym parameter:param]; + + default: + return ''; + } + } + else + { + switch (keyword) + { + case "red": + var oldColor = [_colorArray lastObject], + green = [oldColor greenComponent], + blue = [oldColor blueComponent]; + + _colorArray.pop(); + _colorArray.push([CPColor colorWithRed:parseInt(param) / 255 green:green blue:blue alpha:1.0]); + break; + + case "green": + var oldColor = [_colorArray lastObject], + red = [oldColor redComponent], + blue = [oldColor blueComponent]; + + _colorArray.pop(); + _colorArray.push([CPColor colorWithRed:red green: parseInt(param) / 255 blue:blue alpha:1.0]); + break; + + case "blue": + var oldColor = [_colorArray lastObject], + green = [oldColor greenComponent], + red = [oldColor redComponent]; + + _colorArray.pop(); + _colorArray.push([CPColor colorWithRed:red green:green blue:parseInt(param) / 255 alpha:1.0]); + _colorArray.push([CPColor blackColor]); // placeholder for next color + break; + + case "cf": // change foreground color + [self _flushCurrentRun]; + var fontIndex = parseInt(param) - 1; + + if (_currentRun && fontIndex >= 0) + _currentRun.fgColour = _colorArray[fontIndex]; + + break; + + case "f": // change font + [self _flushCurrentRun]; + var fontIndex = parseInt(param); + + if (_currentRun && fontIndex >= 0 && fontIndex < _fontArray.length) + _currentRun.fontName = _fontArray[fontIndex]; + break; + + case "fs": // change font size + [self _flushCurrentRun]; + _currentRun.fontSize = parseInt(param) / 2; + break; + + case "tx": // tabstop + var location = parseInt(param) / 20; + + if (_currentRun) + [_currentRun addTab:location type:CPLeftTabStopType]; + + break; + + default: + CPLogConsole("skip : " + keyword + " param: " + param); + + } + + if (_states.length > 0) + _curState = 1; + + return ''; + } +} + +- (CPString)_parseKeyword:(CPString)rtf length:(unsigned)len +{ + var ch = '', + fParam = false, + fNeg = false, + keyword = '', + param = ''; + + _rtf = rtf; + + if (++_currentParseIndex >= len) + return len; + + ch = rtf.charAt(_currentParseIndex); + + if (!/[a-zA-Z]/.test(ch)) + return [self _translateKeyword:ch parameter:nil fParameter:fParam]; + + while (/[a-zA-Z]/.test(ch)) + { + keyword += ch; + ch = rtf.charAt(++_currentParseIndex); + } + + if (ch == '-') + { + fNeg = true; + ch = rtf.charAt(++_currentParseIndex); + } + + fParam = true; + + while (/[0-9]/.test(ch)) + { + param += (ch + ''); + ch = rtf.charAt(++_currentParseIndex); + } + + _currentParseIndex--; + param = parseInt(param); + + if (fNeg) + param *= -1; + + return [self _translateKeyword:keyword parameter:param fParameter:fParam]; +} + +- (void)_appendPlainString:(CPString) aString +{ + [_result replaceCharactersInRange:CPMakeRange([_result length], 0) withString:aString]; + +} +- (CPAttributedString)parseRTF:(CPString)rtf +{ + if (rtf.length == 0) + return ''; + + _currentParseIndex = -1; + + var len = rtf.length, + tmp = '', + ch = '', + hex = '', + lastchar = 0; + + while (_currentParseIndex < len) + { + tmp = rtf.charAt(++_currentParseIndex); + + if (tmp !== "\\" && hex.length > 0) + { + [self _appendPlainString: String.fromCharCode(parseInt((hex), 16))]; + hex = ''; + } + + switch (tmp) + { + case " ": + if (lastchar == 1) + { + lastchar = 0; + } + else + { + _freename += tmp; + [self _appendPlainString:tmp]; + } + + break; + + case "{": + if ([self pushState]) + CPLogConsole("push"); + + break; + + case "}": + if ([self popState]) + CPLogConsole("pop"); + + if (_freename) + { + CPLogConsole(_freename); + + if (_parsingFontTable) + { + _fontArray.push(_freename); + _parsingFontTable = NO; + } + + _freename = ""; + } + + [self _flushCurrentRun] + break; + + case "\\": + _freename = ''; + ch = [self _parseKeyword:rtf length:len]; + + if (!_hexreturn && ch.length == 0) + lastchar = 1; + else + lastchar = 0; + + if (_hexreturn) + { + if (ch.length > 0) + { + if (parseInt(ch, 16) & 0x80) + { + hex += ch.toUpperCase(); + } + else + { + [self _appendPlainString: String.fromCharCode(parseInt((hex + ch), 16))]; + hex = ''; + } + + if (hex.length == 4) + { + var temp = parseInt(hex, 16); + + if (hexTable && hexTable[hex.toUpperCase()] !== undefined) + temp = parseInt(hexTable[hex.toUpperCase()], 16); + + [self _appendPlainString: String.fromCharCode(temp)] + hex = ''; + } + } + else + { + CPLogConsole("hex skipped"); + } + + _hexreturn = NO; + } + else if (ch !== undefined && _curState === 0) + { + [self _appendPlainString:ch]; + } + + break; + + case 0x0d: + case 0x0a: + case '\n': + case '\r': + break; + + default: + lastchar = 0; + + if (_curState == 0) + [self _appendPlainString:tmp]; + else if (tmp !== ';') + _freename += tmp; + + break; + } + } + + return _result; +} + +@end \ No newline at end of file diff --git a/AppKit/CPTextView/_CPRTFProducer.j b/AppKit/CPTextView/_CPRTFProducer.j new file mode 100644 index 000000000..ef540ef3d --- /dev/null +++ b/AppKit/CPTextView/_CPRTFProducer.j @@ -0,0 +1,615 @@ +/* + RTFProducer.j + + Serialize CPAttributedString to a RTF String + + Copyright (C) 2014 Daniel Boehringer + This file is based on the RTFProducer from GNUStep + (which i co-authored with Fred Kiefer in 1999) + + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import +@import "CPParagraphStyle.j" +@import "CPColor.j" +@import "CPGraphics.j" +@import "CPFontManager.j" + +@global CPForegroundColorAttributeName +@global CPBackgroundColorAttributeName +@global CPUnderlineStyleAttributeName +@global CPSuperscriptAttributeName +@global CPBaselineOffsetAttributeName +@global CPAttachmentAttributeName +@global CPLigatureAttributeName +@global CPKernAttributeName + +@global CPLeftTextAlignment +@global CPRightTextAlignment +@global CPCenterTextAlignment +@global CPJustifiedTextAlignment +@global CPNaturalTextAlignment + +var PAPERSIZE = @"PaperSize", + LEFTMARGIN = @"LeftMargin", + RIGHTMARGIN = @"RightMargin", + TOPMARGIN = @"TopMargin", + BUTTOMMARGIN = @"ButtomMargin"; + +function _points2twips(a) { return (a) * 20.0; } + +@implementation _CPRTFProducer : CPObject +{ + CPAttributedString text; + CPMutableDictionary fontDict; + CPMutableDictionary colorDict; + CPDictionary docDict; + CPMutableArray attachments; + CPFont currentFont; + CPColor fgColor; + CPColor bgColor; + CPColor ulColor; +} + +#pragma mark - +#pragma mark Class methods + + ++ (CPString)produceRTF:(CPAttributedString)aText documentAttributes:(CPDictionary)dict +{ + var mynew = [self new]; + + return [mynew RTFDStringFromAttributedString:aText documentAttributes:dict]; +} + + +#pragma mark - +#pragma mark init methods + +- (id)init +{ + if (self = [super init]) + { + // maintain a dictionary for the used colours + // (for rtf-header generation) + colorDict = [CPMutableDictionary new]; + + //maintain a dictionary for the used fonts + //(for rtf-header generation) + fontDict = [CPMutableDictionary new]; + + fgColor = [CPColor blackColor]; + bgColor= [CPColor whiteColor]; + } + + return self; +} + +// private stuff follows +- (CPString)fontTable +{ + if (![fontDict count]) + return @""; + + var fontlistString = "", + fontEnum, + currFont, + keyArray; + + keyArray = [fontDict allKeys]; + keyArray = [keyArray sortedArrayUsingSelector:@selector(compare:)]; + fontEnum = [keyArray objectEnumerator]; + + while ((currFont = [fontEnum nextObject]) !== nil) + { + var fontFamily, + detail; + + if ([currFont isEqualToString:@"Symbol"]) + fontFamily = @"tech"; + else if ([currFont isEqualToString:@"Helvetica"]) + fontFamily = @"swiss"; + else if ([currFont isEqualToString:@"Arial"]) + fontFamily = @"swiss"; + else if ([currFont isEqualToString:@"Courier"]) + fontFamily = @"modern"; + else if ([currFont isEqualToString:@"Times"]) + fontFamily = @"roman"; + else fontFamily = @"nil"; + + detail = [CPString stringWithFormat:@"%@\\f%@ %@;", [fontDict objectForKey:currFont], fontFamily, currFont]; + fontlistString += detail; + } + + return [CPString stringWithFormat:@"{\\fonttbl%@}\n", fontlistString]; +} + +- (CPString)colorTable +{ + if (![colorDict count]) + return @""; + + var result, + count = [colorDict count], + list = [CPMutableArray arrayWithCapacity:count], + keyEnum = [colorDict keyEnumerator], + next, + i; + + while ((next = [keyEnum nextObject]) !== nil) + { + var cn = [colorDict objectForKey:next]; + [list insertObject:[CPColor colorWithCSSString:next] atIndex:[cn intValue]-1]; + } + + result = [CPString stringWithString:@"{\\colortbl;"]; + + for (i = 0; i < count; i++) + { + var color = [[list objectAtIndex:i] + colorUsingColorSpaceName:CPCalibratedRGBColorSpace]; + + result += [CPString stringWithFormat:@"\\red%d\\green%d\\blue%d;", + ([color redComponent] * 255), + ([color greenComponent] * 255), + ([color blueComponent] * 255)]; + } + + result += @"}\n"; + + return result; +} + +- (CPString)documentAttributes +{ + if (!docDict) + return @""; + + var result, + detail, + val, + num; + + result = [CPString string]; + + val = [docDict objectForKey:PAPERSIZE]; + + if (val) + { + var size = [val sizeValue]; + + detail = [CPString stringWithFormat:@"\\paperw%d \\paperh%d", + _points2twips(size.width), + _points2twips(size.height)]; + + result += detail; + } + + num = [docDict objectForKey:LEFTMARGIN]; + + if (num) + { + var f = [num floatValue]; + + detail = [CPString stringWithFormat:@"\\margl%d", _points2twips(f)]; + result += detail; + } + + num = [docDict objectForKey:RIGHTMARGIN]; + + if (num) + { + var f = [num floatValue]; + + detail = [CPString stringWithFormat:@"\\margr%d", _points2twips(f)]; + result += detail; + } + + num = [docDict objectForKey:TOPMARGIN]; + + if (num) + { + var f = [num floatValue]; + + detail = [CPString stringWithFormat:@"\\margt%d", _points2twips(f)]; + result += detail; + } + + num = [docDict objectForKey:BUTTOMMARGIN]; + + if (num) + { + var f = [num floatValue]; + + detail = [CPString stringWithFormat:@"\\margb%d", _points2twips(f)]; + result += detail; + } + + return result; +} + +- (CPString)headerString +{ + var result; + + result = [CPString stringWithString:@"{\\rtf1\\ansi"]; + result += [self fontTable]; + result += [self colorTable]; + result += [self documentAttributes]; + + return result; +} + +- (CPString)trailerString +{ + return @"}"; +} + +- (CPString)fontToken:(CPString) fontName +{ + var fCount = [fontDict objectForKey:fontName]; + + if (fCount == nil) + { + var count = [fontDict count]; + + fCount = [CPString stringWithFormat:@"\\f%d", count]; + [fontDict setObject:fCount forKey:fontName]; + } + + return fCount; +} + +- (int)numberForColor:(CPColor)color +{ + var num = [colorDict objectForKey:[color cssString]]; + + if (!num) + [colorDict setObject:num = [CPNumber numberWithInt:[colorDict count] + 1] + forKey:[color cssString]]; + + return [num intValue]; +} + +- (CPString)paragraphStyle:(CPParagraphStyle)paraStyle +{ + var headerString = [CPString stringWithString:@"\\pard"], + twips; + + if (paraStyle == nil) + return headerString; + + switch ([paraStyle alignment]) + { + case CPRightTextAlignment: + headerString += @"\\qr"; + break; + + case CPCenterTextAlignment: + headerString += @"\\qc"; + break; + + case CPLeftTextAlignment: + headerString += @"\\ql"; + break; + + case CPJustifiedTextAlignment: + headerString += @"\\qj"; + break; + + default: + headerString += @"\\ql"; + break; + } + + // write first line indent and left indent + var twips = _points2twips([paraStyle firstLineHeadIndent]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\fi%d", twips]; + + twips = _points2twips([paraStyle headIndent]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\li%d", twips]; + + twips = _points2twips([paraStyle tailIndent]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\ri%d", twips]; + + twips = _points2twips([paraStyle paragraphSpacing]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\sa%d", twips]; + + twips = _points2twips([paraStyle minimumLineHeight]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\sl%d", twips]; + + twips = _points2twips([paraStyle maximumLineHeight]); + + if (twips != 0.0) + headerString += [CPString stringWithFormat:@"\\sl-%d", twips]; + + var enumerator, + tab; + + enumerator = [[paraStyle tabStops] objectEnumerator]; + + while ((tab = [enumerator nextObject])) + { + switch ([tab tabStopType]) + { + case CPLeftTabStopType: + // no tabkind emission needed + break; +/* case NSRightTabStopType: + headerString += @"\\tqr"; + break; + case NSCenterTabStopType: + headerString += @"\\tqc"; + break; + case NSDecimalTabStopType: + headerString += @"\\tqdec"; + break; + default: + NSLog(@"Unknown tab stop type."); +*/ + } + + headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])]; + } + + return headerString; +} + +- (CPString)runStringForString:(CPString) substring + attributes:(CPDictionary) attributes + paragraphStart:(BOOL) first +{ + var result = "", + headerString = "", + trailerString = "", + attribEnum, + currAttrib; + + if (first) + { + var paraStyle = [attributes objectForKey:CPParagraphStyleAttributeName]; + headerString += [self paragraphStyle:paraStyle]; + } + + /* + * analyze attributes of current run + * + * FIXME: All the character attributes should be output relative to the font + * attributes of the paragraph. So if the paragraph has underline on it should + * still be possible to switch it off for some characters, which currently is + * not possible. + */ + attribEnum = [attributes keyEnumerator]; + + while ((currAttrib = [attribEnum nextObject]) != nil) + { + if ([currAttrib isEqualToString:CPFontAttributeName]) + { + /* + * handle fonts + */ + var font, + fontName, + traits; + + font = [attributes objectForKey:CPFontAttributeName]; + fontName = [font familyName]; + traits = [[CPFontManager sharedFontManager] traitsOfFont:font]; + + /* + * font name + */ + if (currentFont == nil || ![fontName isEqualToString:[currentFont familyName]]) + headerString += [self fontToken:fontName]; + + /* + * font size + */ + if (currentFont == nil || [font size] != [currentFont size]) + { + var points = [font size] * 2, + pString; + + pString = [CPString stringWithFormat:@"\\fs%d", points]; + headerString += pString; + } + /* + * font attributes + */ + if (traits & CPItalicFontMask) + { + headerString += @"\\i"; + trailerString += @"\\i0"; + } + + if (traits & CPBoldFontMask) + { + headerString += @"\\b"; + trailerString += @"\\b0"; + } + + if (first) + currentFont = font; + } + else if ([currAttrib isEqualToString:CPForegroundColorAttributeName]) + { + var color = [attributes objectForKey:CPForegroundColorAttributeName]; + + if (![color isEqual:fgColor]) + { + headerString += [CPString stringWithFormat:@"\\cf%d", [self numberForColor:color]]; + trailerString += @"\\cf0"; + } + } + else if ([currAttrib isEqualToString:CPBackgroundColorAttributeName]) + { + var color = [attributes objectForKey:CPBackgroundColorAttributeName]; + + if (![color isEqual:bgColor]) + { + headerString += [CPString stringWithFormat:@"\\cb%d", [self numberForColor:color]]; + trailerString += @"\\cb0"; + } + } + else if ([currAttrib isEqualToString:CPUnderlineStyleAttributeName]) + { + headerString += @"\\ul"; + trailerString += @"\\ulnone"; + } + else if ([currAttrib isEqualToString:CPSuperscriptAttributeName]) + { + var value = [attributes objectForKey:CPSuperscriptAttributeName], + svalue = [value intValue] * 6; + + if (svalue > 0) + { + headerString += [CPString stringWithFormat:@"\\up%d", svalue]; + trailerString += @"\\up0"; + } + else if (svalue < 0) + { + headerString += [CPString stringWithFormat:@"\\dn-%d", svalue]; + trailerString += @"\\dn0"; + } + } + else if ([currAttrib isEqualToString:CPBaselineOffsetAttributeName]) + { + var value = [attributes objectForKey:CPBaselineOffsetAttributeName], + svalue = [value floatValue] * 2; + + if (svalue > 0) + { + headerString += [CPString stringWithFormat:@"\\up%d", svalue]; + trailerString += @"\\up0"; + } + else if (svalue < 0) + { + headerString += [CPString stringWithFormat:@"\\dn-%d", svalue]; + trailerString += @"\\dn0"; + } + } + else if ([currAttrib isEqualToString:CPAttachmentAttributeName]) + { + } + else if ([currAttrib isEqualToString:CPLigatureAttributeName]) + { + } + else if ([currAttrib isEqualToString:CPKernAttributeName]) + { + } + } + + substring = substring.replace(/\\/g, '\\\\'); + substring = substring.replace(/\n/g, '\\par\n'); + substring = substring.replace(/\t/g, '\\tab'); + substring = substring.replace(/{/g, '\\{'); + substring = substring.replace(/}/g, '\\}'); + // FIXME: All characters not in the standard encoding must be + // replaced by \'xx + + if (!first) + { + var braces; + + if ([headerString length]) + braces = [CPString stringWithFormat:@"{%@ %@}", headerString, substring]; + else + braces = substring; + + result += braces; + } + else + { + var nobraces; + + if ([headerString length]) + nobraces = [CPString stringWithFormat:@"%@ %@", headerString, substring]; + else + nobraces = substring; + + result += nobraces; + } + + return result + trailerString; +} + +- (CPString)bodyString +{ + var string = [text string], + result = "", + loc = 0, + length = [string length], + currRange = CPMakeRange(loc, 0), + completeRange = CPMakeRange(0, length), + first = YES; + + // FIXME split along newline characters and run as outer loop + while (CPMaxRange(currRange) < CPMaxRange(completeRange)) // save all "runs" + { + var attributes, + substring, + runString; + + attributes = [text attributesAtIndex:CPMaxRange(currRange) + longestEffectiveRange:currRange + inRange:completeRange]; + substring = [string substringWithRange:currRange]; + runString = [self runStringForString:substring + attributes:attributes + paragraphStart:YES]; + + result += runString; + first = NO; + } + + return result; +} + + +- (CPString)RTFDStringFromAttributedString:(CPAttributedString)aText + documentAttributes:(CPDictionary)dict +{ + var output = [CPString string], + headerString, + trailerString, + bodyString; + + text = aText; + docDict = dict; + + /* + * do not change order! (esp. body has to be generated first; builds context) + */ + bodyString = [self bodyString]; + trailerString = [self trailerString]; + headerString = [self headerString]; + + output += headerString; + output += bodyString; + output += trailerString; + return output; +} +@end \ No newline at end of file diff --git a/AppKit/CPView.j b/AppKit/CPView.j index 483124b85..cadcdf356 100644 --- a/AppKit/CPView.j +++ b/AppKit/CPView.j @@ -2906,8 +2906,8 @@ setBoundsOrigin: return YES; } -/* - FIXME Not yet implemented +/*! + Scrolls the view’s CPClipView in the direction of a mouse event that occurs outside of it. */ - (BOOL)autoscroll:(CPEvent)anEvent { diff --git a/AppKit/Platform/DOM/CPPlatformPasteboard.j b/AppKit/Platform/DOM/CPPlatformPasteboard.j index e749a1fae..9ca0b3141 100644 --- a/AppKit/Platform/DOM/CPPlatformPasteboard.j +++ b/AppKit/Platform/DOM/CPPlatformPasteboard.j @@ -384,9 +384,10 @@ Return true if the event may be a copy and paste event, but the target is not an if ([value length]) { - var pasteboard = [CPPasteboard generalPasteboard]; + var pasteboard = [CPPasteboard generalPasteboard], + cappString = [pasteboard stringForType:CPStringPboardType]; - if ([pasteboard _stateUID] != value) + if (cappString != value) { [pasteboard declareTypes:[CPStringPboardType] owner:self]; [pasteboard setString:value forType:CPStringPboardType]; @@ -450,9 +451,10 @@ Return true if the event may be a copy and paste event, but the target is not an if ([value length]) { - var pasteboard = [CPPasteboard generalPasteboard]; + var pasteboard = [CPPasteboard generalPasteboard], + cappString = [pasteboard stringForType:CPStringPboardType]; - if ([pasteboard _stateUID] != value) + if (cappString != value) { [pasteboard declareTypes:[CPStringPboardType] owner:self]; [pasteboard setString:value forType:CPStringPboardType]; diff --git a/AppKit/Platform/DOM/CPPlatformString.j b/AppKit/Platform/DOM/CPPlatformString.j index 844eeea3e..fb440d36d 100644 --- a/AppKit/Platform/DOM/CPPlatformString.j +++ b/AppKit/Platform/DOM/CPPlatformString.j @@ -171,7 +171,9 @@ var DOMFixedWidthSpanElement = nil, span.style.width = ROUND(aWidth) + "px"; } - span.style.font = [(aFont || DefaultFont) cssString]; + var effectiveFontCSSString = [(aFont || DefaultFont) cssString]; + if (span.style.font !== effectiveFontCSSString) + span.style.font = effectiveFontCSSString; if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature)) span.innerText = aString; diff --git a/AppKit/Themes/Aristo/ThemeDescriptors.j b/AppKit/Themes/Aristo/ThemeDescriptors.j index 31bd445ac..6902f92c7 100755 --- a/AppKit/Themes/Aristo/ThemeDescriptors.j +++ b/AppKit/Themes/Aristo/ThemeDescriptors.j @@ -389,8 +389,11 @@ var themedButtonValues = nil, var color = [CPColor blackColor], themedColorValues = [ - [@"alternate-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]], - [@"secondary-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]] + [@"alternate-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]], + [@"secondary-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]], + [@"selected-text-background-color", [CPColor colorWithHexString:"99CCFF"]], + [@"selected-text-inactive-background-color", [CPColor colorWithHexString:"CCCCCC"]] + ]; [self registerThemeValues:themedColorValues forObject:color]; diff --git a/AppKit/Themes/Aristo2/ThemeDescriptors.j b/AppKit/Themes/Aristo2/ThemeDescriptors.j index ee6917791..365cadba0 100644 --- a/AppKit/Themes/Aristo2/ThemeDescriptors.j +++ b/AppKit/Themes/Aristo2/ThemeDescriptors.j @@ -106,8 +106,10 @@ var themedButtonValues = nil, var color = [CPColor redColor], themedColorValues = [ - [@"alternate-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]], - [@"secondary-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]] + [@"alternate-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.22, 0.46, 0.84, 1.0]]], + [@"secondary-selected-control-color", [[CPColor alloc] _initWithRGBA:[0.83, 0.83, 0.83, 1.0]]], + [@"selected-text-background-color", [CPColor colorWithHexString:"99CCFF"]], + [@"selected-text-inactive-background-color", [CPColor colorWithHexString:"CCCCCC"]] ]; [self registerThemeValues:themedColorValues forObject:color]; diff --git a/Tests/AppKit/CPTextViewTest.j b/Tests/AppKit/CPTextViewTest.j new file mode 100644 index 000000000..343b28dbc --- /dev/null +++ b/Tests/AppKit/CPTextViewTest.j @@ -0,0 +1,87 @@ +@import +@import + +@implementation CPTextViewTest : OJTestCase +{ + CPWindow theWindow; + CPTextView textView; + + CPString stringValue; + + OJMoqSpy delegateSpy +} + +- (void)setUp +{ + // This will init the global var CPApp which are used internally in the AppKit + [[CPApplication alloc] init]; + + // setup a reasonable table + theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 1024.0, 768.0) styleMask:CPWindowNotSizable]; + + textView = [[CPTextView alloc] initWithFrame:CGRectMake(0,0,300,300)]; + + stringValue = @"My string is here"; + + [textView setString:stringValue]; + [textView setDelegate:self]; + + [[theWindow contentView] addSubview:textView]; + + delegateSpy = spy(self); +} + +- (void)tearDown +{ + [delegateSpy reset]; +} + +- (void)testMakeCPTextViewInstance +{ + [self assertNotNull:textView]; +} + +- (void)testTextViewSetStringMethod +{ + [self assert:stringValue equals:[textView stringValue]]; +} + +- (void)testTextViewSelectionRange +{ + //TODO : uncomment once ojtest will be up to date on travis + var range; + // + //[delegateSpy selector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:) times:1 arguments:[textView, CPMakeRange(0,0), CPMakeRange(0, 18)]]; + //[delegateSpy selector:@selector(textViewDidChangeSelection:) times:1]; + //[textView selectAll:self]; + //range = [[textView selectedRanges] firstObject]; + //[self assert:0 equals:range.location]; + //[self assert:18 equals:range.length]; + // [delegateSpy verifyThatAllExpectationsHaveBeenMet]; + // + // + // [delegateSpy reset]; + // [delegateSpy selector:@selector(textView:willChangeSelectionFromCharacterRange:toCharacterRange:) times:1 arguments:[textView, CPMakeRange(0, 18), CPMakeRange(3, 6)]]; + // [delegateSpy selector:@selector(textViewDidChangeSelection:) times:1]; + //[textView setSelectedRange:CPMakeRange(3, 6)]; + //range = [[textView selectedRanges] firstObject]; + //[self assert:3 equals:range.location]; + //[self assert:6 equals:range.length]; + // [delegateSpy verifyThatAllExpectationsHaveBeenMet]; +} + +@end + +@implementation CPTextViewTest (CPTextViewTestDelegate) + +- (CPRange)textView:(CPTextView)aTextView willChangeSelectionFromCharacterRange:(CPRange)oldSelectedCharRange toCharacterRange:(CPRange)newSelectedCharRange +{ + return newSelectedCharRange; +} + +- (void)textViewDidChangeSelection:(CPNotification)aNotification +{ + +} + +@end \ No newline at end of file diff --git a/Tests/Manual/CPTextView/AppController.j b/Tests/Manual/CPTextView/AppController.j new file mode 100755 index 000000000..e9e1d6ed7 --- /dev/null +++ b/Tests/Manual/CPTextView/AppController.j @@ -0,0 +1,105 @@ +/* + * AppController.j + * + * Manual test application for the cappuccino text system + * Copyright (C) 2014 Daniel Boehringer + */ + +@import +@import +@import + +@implementation AppController : CPObject +{ + CPTextView _textView; + CPTextView _textView2; +} + +- (void)orderFrontFontPanel:(id)sender +{ + [[CPFontManager sharedFontManager] orderFrontFontPanel:self]; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + // CPLogRegister(CPLogConsole); + + var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask], + contentView = [theWindow contentView]; + + [contentView setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]]; + + _textView = [[CPTextView alloc] initWithFrame:CGRectMake(0,0,500,500)]; + [_textView setRichText:YES]; + + _textView2 = [[CPTextView alloc] initWithFrame:CGRectMake(0,0,500,500)]; + _textView2._isRichText = NO; + [_textView setBackgroundColor:[CPColor whiteColor]]; + [_textView2 setBackgroundColor:[CPColor whiteColor]]; + + var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(20, 20,520,510)]; + var scrollView2 = [[CPScrollView alloc] initWithFrame:CGRectMake(560, 20,520,510)]; + // [scrollView setAutohidesScrollers:YES]; + [scrollView setDocumentView:_textView]; + [scrollView2 setDocumentView:_textView2]; + // + [contentView addSubview: scrollView]; + [contentView addSubview: scrollView2]; + // + // [_textView setDelegate:self]; + // + // build our menu + var mainMenu = [CPApp mainMenu]; + + while ([mainMenu numberOfItems] > 0) + [mainMenu removeItemAtIndex:0]; + + var item = [mainMenu insertItemWithTitle:@"Edit" action:nil keyEquivalent:nil atIndex:0], + editMenu = [[CPMenu alloc] initWithTitle:@"Edit Menu"]; + + [editMenu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"]; + [editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"]; + [editMenu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"]; + [editMenu addItemWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""]; + [editMenu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"]; + [editMenu addItemWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"]; + [editMenu addItemWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"]; + + [mainMenu setSubmenu:editMenu forItem:item]; + + item = [mainMenu insertItemWithTitle:@"Format" action:nil keyEquivalent:nil atIndex:0]; + var formatMenu = [[CPMenu alloc] initWithTitle:@"Format Menu"]; + [formatMenu addItemWithTitle:@"Font panel" action:@selector(orderFrontFontPanel:) keyEquivalent:@"f"]; + [mainMenu setSubmenu:formatMenu forItem:item]; + + // + // var centeredParagraph=[CPParagraphStyle new]; + // [centeredParagraph setAlignment: CPCenterTextAlignment]; + // [_textView insertText:[[CPAttributedString alloc] initWithString:@"Fusce\n" + // attributes:[CPDictionary dictionaryWithObjects:[centeredParagraph, [CPFont boldFontWithName:"Arial" size:18], [CPColor redColor]] + // forKeys:[CPParagraphStyleAttributeName, CPFontAttributeName, CPForegroundColorAttributeName]]]]; + // + // [_textView insertText: [[CPAttributedString alloc] initWithString:@"lectus neque cr as eget lectus neque cr as eget lectus cr as eget lectus" + // attributes:[CPDictionary dictionaryWithObjects:[ [CPFont fontWithName:"Arial" size:12]] forKeys: [CPFontAttributeName]]]]; + // + // [_textView insertText:[[CPAttributedString alloc] initWithString:@" proin, this is text in boldface " + // attributes:[CPDictionary dictionaryWithObjects:[ [CPFont boldFontWithName:"Arial" size:12]] forKeys: [CPFontAttributeName]]]]; + // [_textView insertText:[[CPAttributedString alloc] initWithString:@"111111 neque cr as eget lectus neque cr as eget lectus cr as eget lectus" + // attributes:[CPDictionary dictionaryWithObjects:[ [CPFont fontWithName:"Arial" size:12.0]] forKeys: [CPFontAttributeName]]]]; + // + [theWindow orderFront:self]; + [CPMenu setMenuBarVisible:YES]; +} + +// +// - (void) makeRTF:sender +// { +// [_textView2 setString: [_CPRTFProducer produceRTF:[_textView textStorage] documentAttributes: @{}] ]; +// var tc = [_CPRTFParser new]; +// var mystr=[tc parseRTF:[_textView2 stringValue]]; +// [_textView selectAll: self]; +// [_textView insertText: mystr]; +// +// } + +@end diff --git a/Tests/Manual/CPTextView/Info.plist b/Tests/Manual/CPTextView/Info.plist new file mode 100644 index 000000000..68f9e7d32 --- /dev/null +++ b/Tests/Manual/CPTextView/Info.plist @@ -0,0 +1,12 @@ + + + + + CPApplicationDelegateClass + AppController + CPBundleName + CPTextViewTest + CPPrincipalClass + CPApplication + + diff --git a/Tests/Manual/CPTextView/Jakefile b/Tests/Manual/CPTextView/Jakefile new file mode 100644 index 000000000..bd57b7a0d --- /dev/null +++ b/Tests/Manual/CPTextView/Jakefile @@ -0,0 +1,94 @@ +/* + * Jakefile + * CPLevelIndicator + * + * Created by Alexander Ljungberg on May 28, 2011. + * Copyright 2011, WireLoad All rights reserved. + */ + +var ENV = require("system").env, + FILE = require("file"), + JAKE = require("jake"), + task = JAKE.task, + FileList = JAKE.FileList, + app = require("cappuccino/jake").app, + configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", + OS = require("os"); + +app ("CPLevelIndicator", function(task) +{ + task.setBuildIntermediatesPath(FILE.join("Build", "CPLevelIndicator.build", configuration)); + task.setBuildPath(FILE.join("Build", configuration)); + + task.setProductName("CPLevelIndicator"); + task.setIdentifier("com.yourcompany.CPLevelIndicator"); + task.setVersion("1.0"); + task.setAuthor("WireLoad"); + task.setEmail("feedback @nospam@ yourcompany.com"); + task.setSummary("CPLevelIndicator"); + task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**"))); + task.setResources(new FileList("Resources/**")); + task.setIndexFilePath("index.html"); + task.setInfoPlistPath("Info.plist"); + task.setNib2CibFlags("-R Resources/"); + + if (configuration === "Debug") + task.setCompilerFlags("-DDEBUG -g"); + else + task.setCompilerFlags("-O"); +}); + +task ("default", ["CPLevelIndicator"], function() +{ + printResults(configuration); +}); + +task ("build", ["default"]); + +task ("debug", function() +{ + ENV["CONFIGURATION"] = "Debug"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("release", function() +{ + ENV["CONFIGURATION"] = "Release"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("run", ["debug"], function() +{ + OS.system(["open", FILE.join("Build", "Debug", "CPLevelIndicator", "index.html")]); +}); + +task ("run-release", ["release"], function() +{ + OS.system(["open", FILE.join("Build", "Release", "CPLevelIndicator", "index.html")]); +}); + +task ("deploy", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Deployment", "CPLevelIndicator")); + OS.system(["press", "-f", FILE.join("Build", "Release", "CPLevelIndicator"), FILE.join("Build", "Deployment", "CPLevelIndicator")]); + printResults("Deployment") +}); + +task ("desktop", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Desktop", "CPLevelIndicator")); + require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "CPLevelIndicator"), FILE.join("Build", "Desktop", "CPLevelIndicator", "CPLevelIndicator.app")); + printResults("Desktop") +}); + +task ("run-desktop", ["desktop"], function() +{ + OS.system([FILE.join("Build", "Desktop", "CPLevelIndicator", "CPLevelIndicator.app", "Contents", "MacOS", "NativeHost"), "-i"]); +}); + +function printResults(configuration) +{ + print("----------------------------"); + print(configuration+" app built at path: "+FILE.join("Build", configuration, "CPLevelIndicator")); + print("----------------------------"); +} diff --git a/Tests/Manual/CPTextView/Resources/spinner.gif b/Tests/Manual/CPTextView/Resources/spinner.gif new file mode 100644 index 000000000..a5e705f6c Binary files /dev/null and b/Tests/Manual/CPTextView/Resources/spinner.gif differ diff --git a/Tests/Manual/CPTextView/index-debug.html b/Tests/Manual/CPTextView/index-debug.html new file mode 100644 index 000000000..1097bcf1b --- /dev/null +++ b/Tests/Manual/CPTextView/index-debug.html @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + CPLevelIndicator + + + + + + + + + + + + + + +
+
+ + + +
+
+ + + diff --git a/Tests/Manual/CPTextView/index.html b/Tests/Manual/CPTextView/index.html new file mode 100644 index 000000000..e08fb0b01 --- /dev/null +++ b/Tests/Manual/CPTextView/index.html @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + CPTextView + + + + + + + + + + + + +
+
+ + + +
+
+ + + diff --git a/Tests/Manual/CPTextView/main.j b/Tests/Manual/CPTextView/main.j new file mode 100644 index 000000000..7a956f1f5 --- /dev/null +++ b/Tests/Manual/CPTextView/main.j @@ -0,0 +1,18 @@ +/* + * AppController.j + * CPLevelIndicator + * + * Created by Alexander Ljungberg on May 28, 2011. + * Copyright 2011, WireLoad All rights reserved. + */ + +@import +@import + +@import "AppController.j" + + +function main(args, namedArgs) +{ + CPApplicationMain(args, namedArgs); +} diff --git a/Tests/Manual/CPTextViewCibTest/AppController.j b/Tests/Manual/CPTextViewCibTest/AppController.j new file mode 100644 index 000000000..82b6d6ec8 --- /dev/null +++ b/Tests/Manual/CPTextViewCibTest/AppController.j @@ -0,0 +1,34 @@ +/* + * AppController.j + * CPTextViewCibTest + * + * Created by You on June 4, 2014. + * Copyright 2014, Your Company All rights reserved. + */ + +@import +@import + + +@implementation AppController : CPObject +{ + @outlet CPWindow theWindow; + @outlet CPTextView textView; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + // This is called when the application is done loading. +} + +- (void)awakeFromCib +{ + // This is called when the cib is done loading. + // You can implement this method on any object instantiated from a Cib. + // It's a useful hook for setting up current UI values, and other things. + + // In this case, we want the window from Cib to become our full browser window + [theWindow setFullPlatformWindow:YES]; +} + +@end diff --git a/Tests/Manual/CPTextViewCibTest/Info.plist b/Tests/Manual/CPTextViewCibTest/Info.plist new file mode 100644 index 000000000..f24e33a9a --- /dev/null +++ b/Tests/Manual/CPTextViewCibTest/Info.plist @@ -0,0 +1,14 @@ + + + + + Main cib file base name + MainMenu.cib + CPBundleName + CPTextViewCibTest + CPBundleVersion + 1.0 + CPHumanReadableCopyright + Copyright © 2014, Your Company All rights reserved. + + diff --git a/Tests/Manual/CPTextViewCibTest/Jakefile b/Tests/Manual/CPTextViewCibTest/Jakefile new file mode 100644 index 000000000..eb85f3135 --- /dev/null +++ b/Tests/Manual/CPTextViewCibTest/Jakefile @@ -0,0 +1,184 @@ +/* + * Jakefile + * CPTextViewCibTest + * + * Created by You on June 4, 2014. + * Copyright 2014, Your Company All rights reserved. + */ + +var ENV = require("system").env, + FILE = require("file"), + JAKE = require("jake"), + task = JAKE.task, + FileList = JAKE.FileList, + app = require("cappuccino/jake").app, + configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", + OS = require("os"), + projectName = "CPTextViewCibTest"; + +app (projectName, function(task) +{ + ENV["OBJJ_INCLUDE_PATHS"] = "Frameworks"; + + if (configuration === "Debug") + ENV["OBJJ_INCLUDE_PATHS"] = FILE.join(ENV["OBJJ_INCLUDE_PATHS"], configuration); + + task.setBuildIntermediatesPath(FILE.join("Build", "CPTextViewCibTest.build", configuration)); + task.setBuildPath(FILE.join("Build", configuration)); + + task.setProductName("CPTextViewCibTest"); + task.setIdentifier("com.yourcompany.CPTextViewCibTest"); + task.setVersion("1.0"); + task.setAuthor("Your Company"); + task.setEmail("feedback @nospam@ yourcompany.com"); + task.setSummary("CPTextViewCibTest"); + task.setSources(new FileList("**/*.j").exclude(FILE.join("Build", "**")).exclude(FILE.join("Frameworks", "Source", "**"))); + task.setResources(new FileList("Resources/**")); + task.setIndexFilePath("index.html"); + task.setInfoPlistPath("Info.plist"); + + if (configuration === "Debug") + task.setCompilerFlags("-DDEBUG -g"); + else + task.setCompilerFlags("-O"); +}); + +task ("default", [projectName], function() +{ + printResults(configuration); +}); + +task ("build", ["default"], function() +{ + updateApplicationSize(); +}); + +task ("debug", function() +{ + ENV["CONFIGURATION"] = "Debug"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("release", function() +{ + ENV["CONFIGURATION"] = "Release"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("run", ["debug"], function() +{ + OS.system(["open", FILE.join("Build", "Debug", projectName, "index.html")]); +}); + +task ("run-release", ["release"], function() +{ + OS.system(["open", FILE.join("Build", "Release", projectName, "index.html")]); +}); + +task ("deploy", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Deployment", projectName)); + OS.system(["press", "-f", FILE.join("Build", "Release", projectName), FILE.join("Build", "Deployment", projectName)]); + printResults("Deployment") +}); + +task ("desktop", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Desktop", projectName)); + require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", projectName), FILE.join("Build", "Desktop", projectName, "CPTextViewCibTest.app")); + printResults("Desktop") +}); + +task ("run-desktop", ["desktop"], function() +{ + OS.system([FILE.join("Build", "Desktop", projectName, "CPTextViewCibTest.app", "Contents", "MacOS", "NativeHost"), "-i"]); +}); + +function printResults(configuration) +{ + print("----------------------------"); + print(configuration+" app built at path: "+FILE.join("Build", configuration, projectName)); + print("----------------------------"); +} + +function updateApplicationSize() +{ + print("Calculating application file sizes..."); + + var contents = FILE.read(FILE.join("Build", ENV["CONFIGURATION"], projectName, "Info.plist"), { charset:"UTF-8" }), + format = CFPropertyList.sniffedFormatOfString(contents), + plist = CFPropertyList.propertyListFromString(contents), + totalBytes = {executable:0, data:0, mhtml:0}; + + // Get the size of all framework executables and sprite data + var frameworksDir = "Frameworks"; + + if (ENV["CONFIGURATION"] === "Debug") + frameworksDir = FILE.join(frameworksDir, "Debug"); + + var frameworks = FILE.list(frameworksDir); + + frameworks.forEach(function(framework) + { + if (framework !== "Source") + addBundleFileSizes(FILE.join(frameworksDir, framework), totalBytes); + }); + + // Read in the default theme name, and attempt to get its size + var themeName = plist.valueForKey("CPDefaultTheme") || "Aristo2", + themePath = nil; + + if (themeName === "Aristo" || themeName === "Aristo2") + themePath = FILE.join(frameworksDir, "AppKit", "Resources", themeName + ".blend"); + else + themePath = FILE.join("Frameworks", "Resources", themeName + ".blend"); + + if (FILE.isDirectory(themePath)) + addBundleFileSizes(themePath, totalBytes); + + // Add sizes for the app + addBundleFileSizes(FILE.join("Build", ENV["CONFIGURATION"], projectName), totalBytes); + + print("Executables: " + totalBytes.executable + ", sprite data: " + totalBytes.data + ", total: " + (totalBytes.executable + totalBytes.data)); + + var dict = new CFMutableDictionary(); + + dict.setValueForKey("executable", totalBytes.executable); + dict.setValueForKey("data", totalBytes.data); + dict.setValueForKey("mhtml", totalBytes.mhtml); + + plist.setValueForKey("CPApplicationSize", dict); + + FILE.write(FILE.join("Build", ENV["CONFIGURATION"], projectName, "Info.plist"), CFPropertyList.stringFromPropertyList(plist, format), { charset:"UTF-8" }); +} + +function addBundleFileSizes(bundlePath, totalBytes) +{ + var bundleName = FILE.basename(bundlePath), + environment = bundleName === "Foundation" ? "Objj" : "Browser", + bundlePath = FILE.join(bundlePath, environment + ".environment"); + + if (FILE.isDirectory(bundlePath)) + { + var filename = bundleName + ".sj", + filePath = new FILE.Path(FILE.join(bundlePath, filename)); + + if (filePath.exists()) + totalBytes.executable += filePath.size(); + + filePath = new FILE.Path(FILE.join(bundlePath, "dataURLs.txt")); + + if (filePath.exists()) + totalBytes.data += filePath.size(); + + filePath = new FILE.Path(FILE.join(bundlePath, "MHTMLData.txt")); + + if (filePath.exists()) + totalBytes.mhtml += filePath.size(); + + filePath = new FILE.Path(FILE.join(bundlePath, "MHTMLPaths.txt")); + + if (filePath.exists()) + totalBytes.mhtml += filePath.size(); + } +} diff --git a/Tests/Manual/CPTextViewCibTest/Resources/MainMenu.cib b/Tests/Manual/CPTextViewCibTest/Resources/MainMenu.cib new file mode 100644 index 000000000..7aded24f7 --- /dev/null +++ b/Tests/Manual/CPTextViewCibTest/Resources/MainMenu.cib @@ -0,0 +1 @@ +280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;0E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;4E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;0E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;1;7E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;1;9E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;11E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;13E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;15E;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;17E;D;K;6;CP$UIDd;2;18E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;19E;D;K;6;CP$UIDd;2;21E;D;K;6;CP$UIDd;2;23E;D;K;6;CP$UIDd;2;25E;D;K;6;CP$UIDd;2;27E;D;K;6;CP$UIDd;2;29E;D;K;6;CP$UIDd;2;31E;D;K;6;CP$UIDd;2;32E;D;K;6;CP$UIDd;2;33E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;21E;D;K;6;CP$UIDd;2;23E;D;K;6;CP$UIDd;2;25E;D;K;6;CP$UIDd;2;27E;D;K;6;CP$UIDd;2;25E;D;K;6;CP$UIDd;2;25E;D;K;6;CP$UIDd;2;11E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;34E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;12E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;2;35E;E;D;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;11E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;33E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;36E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;33E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;29E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;37E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;33E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;21E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;38E;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;29E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;33E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;36E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;34E;E;D;K;10;$classnameS;20;_CPCibWindowTemplateK;8;$classesA;S;20;_CPCibWindowTemplateS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;20E;K;30;_CPCibWindowTemplateMaxSizeKeyD;K;6;CP$UIDd;2;39E;K;32;_CPCibWindowTemplateViewClassKeyD;K;6;CP$UIDd;1;0E;K;34;_CPCibWindowTemplateWindowClassKeyD;K;6;CP$UIDd;2;40E;K;30;_CPCibWindowTemplateWTFlagsKeyD;K;6;CP$UIDd;2;41E;K;33;_CPCibWindowTemplateWindowRectKeyD;K;6;CP$UIDd;2;42E;K;33;_CPCibWindowTemplateScreenRectKeyD;K;6;CP$UIDd;2;43E;K;30;_CPCibWindowTempatStyleMaskKeyD;K;6;CP$UIDd;2;44E;K;34;_CPCibWindowTemplateWindowTitleKeyD;K;6;CP$UIDd;2;45E;K;33;_CPCibWindowTemplateWindowViewKeyD;K;6;CP$UIDd;2;23E;E;D;K;10;$classnameS;6;CPViewK;8;$classesA;S;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;46E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;47E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;47E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;2;48E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;50E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;50E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;51E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;52E;E;D;K;10;$classnameS;12;CPScrollViewK;8;$classesA;S;12;CPScrollViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;24E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;23E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;46E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;53E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;54E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;2;55E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;23E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;56E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;57E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;50E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;50E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;51E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;58E;K;23;CPScrollViewContentViewD;K;6;CP$UIDd;2;27E;K;29;CPScrollViewHeaderClipViewKeyD;K;6;CP$UIDd;1;0E;K;21;CPScrollViewVScrollerD;K;6;CP$UIDd;2;32E;K;21;CPScrollViewHScrollerD;K;6;CP$UIDd;2;31E;K;23;CPScrollViewVLineScrollD;K;6;CP$UIDd;2;59E;K;23;CPScrollViewVPageScrollD;K;6;CP$UIDd;2;59E;K;23;CPScrollViewHLineScrollD;K;6;CP$UIDd;2;59E;K;23;CPScrollViewHPageScrollD;K;6;CP$UIDd;2;59E;K;24;CPScrollViewHasVScrollerD;K;6;CP$UIDd;2;60E;K;24;CPScrollViewHasHScrollerD;K;6;CP$UIDd;2;51E;K;29;CPScrollViewAutohidesScrollerD;K;6;CP$UIDd;2;51E;K;25;CPScrollViewCornerViewKeyD;K;6;CP$UIDd;1;0E;K;31;CPScrollViewBottomCornerViewKeyD;K;6;CP$UIDd;2;61E;K;25;CPScrollViewBorderTypeKeyD;K;6;CP$UIDd;2;62E;K;28;CPScrollViewScrollerStyleKeyD;K;6;CP$UIDd;1;0E;K;32;CPScrollViewScrollerKnobStyleKeyD;K;6;CP$UIDd;2;46E;E;D;K;10;$classnameS;10;CPClipViewK;8;$classesA;S;10;CPClipViewS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;26E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;25E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;46E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;63E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;64E;K;17;CPViewSubviewsKeyD;K;6;CP$UIDd;2;65E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;25E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;66E;K;21;CPViewBackgroundColorD;K;6;CP$UIDd;2;68E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;50E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;50E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;51E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;69E;K;24;CPScrollViewDocumentViewD;K;6;CP$UIDd;2;29E;E;D;K;10;$classnameS;10;CPTextViewK;8;$classesA;S;10;CPTextViewS;6;CPTextS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;28E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;27E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;46E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;64E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;64E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;27E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;66E;K;21;CPViewBackgroundColorD;K;6;CP$UIDd;2;68E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;K;20;CPReuseIdentifierKeyD;K;6;CP$UIDd;2;70E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;50E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;50E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;51E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;71E;K;23;CPTextViewIsEditableKeyD;K;6;CP$UIDd;2;60E;K;25;CPTextViewIsSelectableKeyD;K;6;CP$UIDd;2;60E;K;23;CPTextViewIsRichTextKeyD;K;6;CP$UIDd;2;60E;K;21;CPTextViewDelegateKeyD;K;6;CP$UIDd;1;0E;K;22;CPTextViewContainerKeyD;K;6;CP$UIDd;2;73E;K;32;CPTextViewInsertionPointColorKeyD;K;6;CP$UIDd;2;74E;K;35;CPTextViewSelectedTextAttributesKeyD;K;6;CP$UIDd;2;76E;K;23;CPTextViewAllowsUndoKeyD;K;6;CP$UIDd;2;60E;K;26;CPTextViewUsesFontPanelKeyD;K;6;CP$UIDd;2;60E;E;D;K;10;$classnameS;10;CPScrollerK;8;$classesA;S;10;CPScrollerS;9;CPControlS;6;CPViewS;11;CPResponderS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;30E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;25E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;46E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;77E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;78E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;25E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;79E;K;17;CPViewIsHiddenKeyD;K;6;CP$UIDd;2;80E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;81E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;82E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;50E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;50E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;51E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;83E;K;17;CPControlValueKeyD;K;6;CP$UIDd;2;46E;K;18;CPControlTargetKeyD;K;6;CP$UIDd;2;25E;K;18;CPControlActionKeyD;K;6;CP$UIDd;2;84E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;2;85E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;1;0E;K;33;CPControlBaseWrittingDirectionKeyD;K;6;CP$UIDd;1;0E;K;23;CPScrollerIsVerticalKeyD;K;6;CP$UIDd;2;51E;K;24;CPScrollerKnobProportionD;K;6;CP$UIDd;2;86E;K;18;CPScrollerStyleKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;30E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;25E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;46E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;87E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;88E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;25E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;79E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;81E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;82E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;50E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;50E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;51E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;89E;K;17;CPControlValueKeyD;K;6;CP$UIDd;2;46E;K;18;CPControlTargetKeyD;K;6;CP$UIDd;2;25E;K;18;CPControlActionKeyD;K;6;CP$UIDd;2;90E;K;24;CPControlSendActionOnKeyD;K;6;CP$UIDd;2;85E;K;23;CPControlControlSizeKeyD;K;6;CP$UIDd;1;0E;K;33;CPControlBaseWrittingDirectionKeyD;K;6;CP$UIDd;1;0E;K;23;CPScrollerIsVerticalKeyD;K;6;CP$UIDd;2;60E;K;24;CPScrollerKnobProportionD;K;6;CP$UIDd;2;86E;K;18;CPScrollerStyleKeyD;K;6;CP$UIDd;1;0E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;91E;E;S;13;CPApplicationD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;21E;E;E;S;8;delegateS;8;textViewS;9;theWindowS;32;{10000000000000, 10000000000000}S;8;CPWindowd;10;1879048192S;24;{{335, 128}, {480, 360}}S;21;{{0, 0}, {1440, 878}}d;1;7S;6;Windowd;1;0S;20;{{0, 0}, {480, 360}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;25E;E;E;S;6;normalS;6;{1, 1}F;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;22;{{20, 20}, {448, 320}}S;20;{{0, 0}, {448, 320}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;27E;D;K;6;CP$UIDd;2;31E;D;K;6;CP$UIDd;2;32E;D;K;6;CP$UIDd;2;61E;E;E;d;2;36S;10;scrollviewD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;d;2;10T;D;K;6;$classD;K;6;CP$UIDd;2;22E;K;27;CPResponderNextResponderKeyD;K;6;CP$UIDd;2;25E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;92E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;92E;K;18;CPViewSuperviewKeyD;K;6;CP$UIDd;2;25E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;50E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;50E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;51E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;93E;E;d;1;2S;20;{{1, 1}, {446, 318}}S;20;{{0, 0}, {446, 318}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;29E;E;E;d;2;18D;K;10;$classnameS;7;CPColorK;8;$classesA;S;7;CPColorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;67E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;2;94E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;95E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;6;vfokrtD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;15;CPTextContainerK;8;$classesA;S;15;CPTextContainerS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;72E;K;22;CPTextContainerSizeKeyD;K;6;CP$UIDd;2;96E;K;31;CPTextContainerLayoutManagerKeyD;K;6;CP$UIDd;2;98E;E;D;K;6;$classD;K;6;CP$UIDd;2;67E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;2;99E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;95E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;E;D;K;10;$classnameS;12;CPDictionaryK;8;$classesA;S;12;CPDictionaryS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;75E;K;10;CP.objectsD;K;7;NSColorD;K;6;CP$UIDd;2;74E;K;17;NSBackgroundColorD;K;6;CP$UIDd;3;100E;E;E;S;23;{{-100, 405}, {87, 15}}S;18;{{0, 0}, {87, 15}}d;1;8d;11;-2147483648S;8;scrollerS;27;controlSizeRegular+disabledD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;29;_horizontalScrollerDidScroll:d;1;4d;1;1S;21;{{432, 1}, {15, 318}}S;19;{{0, 0}, {15, 318}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;27;_verticalScrollerDidScroll:S;13;AppControllerS;16;{{0, 0}, {0, 0}}D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;86E;D;K;6;CP$UIDd;2;86E;D;K;6;CP$UIDd;2;86E;D;K;6;CP$UIDd;2;86E;E;E;S;5;colorS;15;{446, 10000000}D;K;10;$classnameS;15;CPLayoutManagerK;8;$classesA;S;15;CPLayoutManagerS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;97E;K;29;CPLayoutManagerTextStorageKeyD;K;6;CP$UIDd;3;102E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;46E;D;K;6;CP$UIDd;2;46E;D;K;6;CP$UIDd;2;46E;D;K;6;CP$UIDd;2;86E;E;E;D;K;6;$classD;K;6;CP$UIDd;2;67E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;3;103E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;95E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;E;D;K;10;$classnameS;13;CPTextStorageK;8;$classesA;S;13;CPTextStorageS;25;CPMutableAttributedStringS;18;CPAttributedStringS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;101E;K;24;CPAttributedStringStringD;K;6;CP$UIDd;3;104E;K;24;CPAttributedStringRangesD;K;6;CP$UIDd;3;105E;K;28;CPAttributedStringAttributesD;K;6;CP$UIDd;3;106E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;107E;D;K;6;CP$UIDd;3;107E;D;K;6;CP$UIDd;3;107E;D;K;6;CP$UIDd;2;86E;E;E;S;21;Je suis un CPTextViewD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;109E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;3;110E;E;E;f;12;0.6666666667D;K;10;$classnameS;21;_CPKeyedArchiverValueK;8;$classesA;S;21;_CPKeyedArchiverValueS;7;CPValueS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;108E;K;15;CPValueValueKeyD;K;6;CP$UIDd;3;111E;E;D;K;6;$classD;K;6;CP$UIDd;2;75E;K;10;CP.objectsD;K;30;CPForegroundColorAttributeNameD;K;6;CP$UIDd;3;112E;K;19;CPFontAttributeNameD;K;6;CP$UIDd;3;114E;K;29;CPParagraphStyleAttributeNameD;K;6;CP$UIDd;3;116E;E;E;S;26;{"location":0,"length":21}D;K;6;$classD;K;6;CP$UIDd;2;67E;K;20;CPColorComponentsKeyD;K;6;CP$UIDd;3;117E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;2;95E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;49E;E;D;K;10;$classnameS;6;CPFontK;8;$classesA;S;6;CPFontS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;113E;K;13;CPFontNameKeyD;K;6;CP$UIDd;3;118E;K;13;CPFontSizeKeyD;K;6;CP$UIDd;3;119E;K;15;CPFontIsBoldKeyD;K;6;CP$UIDd;2;60E;K;17;CPFontIsItalicKeyD;K;6;CP$UIDd;2;51E;K;17;CPFontIsSystemKeyD;K;6;CP$UIDd;2;51E;E;D;K;10;$classnameS;16;CPParagraphStyleK;8;$classesA;S;16;CPParagraphStyleS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;3;115E;K;28;CPParagraphStyleAlignmentKeyD;K;6;CP$UIDd;2;85E;K;27;CPParagraphStyleTabStopsKeyD;K;6;CP$UIDd;1;0E;K;38;CPParagraphStyleFirstLineHeadIndentKeyD;K;6;CP$UIDd;2;46E;K;29;CPParagraphStyleHeadIndentKeyD;K;6;CP$UIDd;2;46E;K;29;CPParagraphStyleTailIndentKeyD;K;6;CP$UIDd;2;46E;K;35;CPParagraphStyleParagraphSpacingKeyD;K;6;CP$UIDd;2;46E;K;36;CPParagraphStyleMinimumLineHeightKeyD;K;6;CP$UIDd;2;46E;K;36;CPParagraphStyleMaximumLineHeightKeyD;K;6;CP$UIDd;2;46E;K;30;CPParagraphStyleLineSpacingKeyD;K;6;CP$UIDd;2;46E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;86E;D;K;6;CP$UIDd;3;120E;D;K;6;CP$UIDd;3;121E;D;K;6;CP$UIDd;2;86E;E;E;S;11;Arial Blackd;2;16f;12;0.3049308391f;12;0.3742420204E;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E; \ No newline at end of file diff --git a/Tests/Manual/CPTextViewCibTest/Resources/MainMenu.xib b/Tests/Manual/CPTextViewCibTest/Resources/MainMenu.xib new file mode 100644 index 000000000..3b53186d8 --- /dev/null +++ b/Tests/Manual/CPTextViewCibTest/Resources/MainMenu.xib @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/Manual/CPTextViewCibTest/index-debug.html b/Tests/Manual/CPTextViewCibTest/index-debug.html new file mode 100644 index 000000000..ed8c41c09 --- /dev/null +++ b/Tests/Manual/CPTextViewCibTest/index-debug.html @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + CPTextViewCibTest + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/CPTextViewCibTest/index.html b/Tests/Manual/CPTextViewCibTest/index.html new file mode 100644 index 000000000..9dd3013c4 --- /dev/null +++ b/Tests/Manual/CPTextViewCibTest/index.html @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + CPTextViewCibTest + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/CPTextViewCibTest/main.j b/Tests/Manual/CPTextViewCibTest/main.j new file mode 100644 index 000000000..a9d2a4c48 --- /dev/null +++ b/Tests/Manual/CPTextViewCibTest/main.j @@ -0,0 +1,18 @@ +/* + * AppController.j + * CPTextViewCibTest + * + * Created by You on June 4, 2014. + * Copyright 2014, Your Company All rights reserved. + */ + +@import +@import + +@import "AppController.j" + + +function main(args, namedArgs) +{ + CPApplicationMain(args, namedArgs); +} diff --git a/Tools/nib2cib/NSAppKit.j b/Tools/nib2cib/NSAppKit.j index 812a94d5a..5962f4c6b 100644 --- a/Tools/nib2cib/NSAppKit.j +++ b/Tools/nib2cib/NSAppKit.j @@ -45,6 +45,7 @@ @import "NSImage.j" @import "NSImageView.j" @import "NSLayoutConstraint.j" +@import "NSLayoutManager.j" @import "NSLevelIndicator.j" @import "NSLocalizableString.j" @import "NSMatrix.j" @@ -54,6 +55,7 @@ @import "NSNibConnector.j" @import "NSObjectController.j" @import "NSOutlineView.j" +@import "NSParagraphStyle.j" @import "NSPopUpButton.j" @import "NSPredicateEditor.j" @import "NSResponder.j" @@ -73,7 +75,12 @@ @import "NSTableView.j" @import "NSTabView.j" @import "NSTabViewItem.j" +@import "NSText.j" +@import "NSTextContainer.j" @import "NSTextField.j" +@import "NSTextStorage.j" +@import "NSTextView.j" +@import "NSTextViewSharedData.j" @import "NSTokenField.j" @import "NSToolbar.j" @import "NSToolbarFlexibleSpaceItem.j" @@ -92,7 +99,6 @@ @import "NSAppearance.j" @import "NSVisualEffectView.j" - function CP_NSMapClassName(aClassName) { if (aClassName.indexOf("NS") === 0) diff --git a/Tools/nib2cib/NSAttributedString.j b/Tools/nib2cib/NSAttributedString.j index 0f4c86695..166dc2088 100644 --- a/Tools/nib2cib/NSAttributedString.j +++ b/Tools/nib2cib/NSAttributedString.j @@ -40,5 +40,7 @@ @implementation NSMutableAttributedString : NSAttributedString { + } + @end diff --git a/Tools/nib2cib/NSLayoutManager.j b/Tools/nib2cib/NSLayoutManager.j new file mode 100644 index 000000000..eb3e3f56c --- /dev/null +++ b/Tools/nib2cib/NSLayoutManager.j @@ -0,0 +1,63 @@ +/* + * NSText.j + * nib2cib + * + * Created by Alexendre Wilhelm. + * Copyright 2014 The Cappuccino Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +@class Nib2Cib + +@implementation CPLayoutManager (NSCoding) + +- (id)NS_initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + _textStorage = [aCoder decodeObjectForKey:@"NSTextStorage"]; + [_textStorage addLayoutManager:self]; + + return self; +} + +@end + +@implementation NSLayoutManager : CPLayoutManager +{ + +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [self NS_initWithCoder:aCoder]; + + if (self) + { + + } + + return self; +} + +- (Class)classForKeyedArchiver +{ + return [CPLayoutManager class]; +} + +@end diff --git a/Tools/nib2cib/NSParagraphStyle.j b/Tools/nib2cib/NSParagraphStyle.j new file mode 100644 index 000000000..420735706 --- /dev/null +++ b/Tools/nib2cib/NSParagraphStyle.j @@ -0,0 +1,66 @@ +/* + * NSParagraphStyle.j + * nib2cib + * + * Created by Alexendre Wilhelm. + * Copyright 2014 The Cappuccino Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +@class Nib2Cib + +@implementation CPParagraphStyle (NSCoding) + +- (id)NS_initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + return self; +} + +@end + +@implementation NSParagraphStyle : CPParagraphStyle +{ +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [self NS_initWithCoder:aCoder]; + + if (self) + { + _alignment = [aCoder decodeIntForKey:@"NSAlignment"]; + _firstLineHeadIndent = [aCoder decodeIntForKey:@"NSFirstLineHeadIndent"]; + _headIndent = [aCoder decodeIntForKey:@"NSHeadIndent"]; + _lineSpacing = [aCoder decodeIntForKey:@"NSLineSpacing"]; + _maximumLineHeight = [aCoder decodeIntForKey:@"NSMaxLineHeight"]; + _minimumLineHeight = [aCoder decodeIntForKey:@"NSMinLineHeight"]; + _paragraphSpacing = [aCoder decodeIntForKey:@"NSParagraphSpacing"]; + _tailIndent = [aCoder decodeIntForKey:@"NSTailIndent"]; + } + + return self; +} + +- (Class)classForKeyedArchiver +{ + return [CPParagraphStyle class]; +} + +@end \ No newline at end of file diff --git a/Tools/nib2cib/NSText.j b/Tools/nib2cib/NSText.j new file mode 100644 index 000000000..9aa4c1c43 --- /dev/null +++ b/Tools/nib2cib/NSText.j @@ -0,0 +1,61 @@ +/* + * NSText.j + * nib2cib + * + * Created by Alexendre Wilhelm. + * Copyright 2014 The Cappuccino Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +@class Nib2Cib + +@implementation CPText (NSCoding) + +- (id)NS_initWithCoder:(CPCoder)aCoder +{ + if (self = [super NS_initWithCoder:aCoder]) + { + } + + return self; +} + +@end + +@implementation NSText : CPText +{ + +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [self NS_initWithCoder:aCoder]; + + if (self) + { + } + + return self; +} + +- (Class)classForKeyedArchiver +{ + return [CPText class]; +} + +@end diff --git a/Tools/nib2cib/NSTextContainer.j b/Tools/nib2cib/NSTextContainer.j new file mode 100644 index 000000000..eb21910a0 --- /dev/null +++ b/Tools/nib2cib/NSTextContainer.j @@ -0,0 +1,64 @@ +/* + * NSTextContainer.j + * nib2cib + * + * Created by Alexendre Wilhelm. + * Copyright 2014 The Cappuccino Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +@class Nib2Cib + +@implementation CPTextContainer (NSCoding) + +- (id)NS_initWithCoder:(CPCoder)aCoder +{ + self = [super init]; + + _size = CGSizeMake([aCoder decodeIntForKey:@"NSWidth"], 1e7); + + _layoutManager = [aCoder decodeObjectForKey:@"NSLayoutManager"]; + [_layoutManager addTextContainer:self]; + + return self; +} + +@end + +@implementation NSTextContainer : CPTextContainer +{ +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [self NS_initWithCoder:aCoder]; + + if (self) + { + + } + + return self; +} + +- (Class)classForKeyedArchiver +{ + return [CPTextContainer class]; +} + +@end diff --git a/Tools/nib2cib/NSTextStorage.j b/Tools/nib2cib/NSTextStorage.j new file mode 100644 index 000000000..3c6b1e548 --- /dev/null +++ b/Tools/nib2cib/NSTextStorage.j @@ -0,0 +1,74 @@ +/* + * NSTextStorage.j + * nib2cib + * + * Created by Alexendre Wilhelm. + * Copyright 2014 The Cappuccino Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +@class Nib2Cib + +@global CPForegroundColorAttributeName + +@implementation CPTextStorage (NSCoding) + +- (id)NS_initWithCoder:(CPCoder)aCoder +{ + var xibAttributes = [aCoder decodeObjectForKey:@"NSAttributes"], + cibAttributes = @{}; + + if ([xibAttributes containsKey:@"NSColor"]) + [cibAttributes setObject:[xibAttributes valueForKey:@"NSColor"] forKey:CPForegroundColorAttributeName]; + + if ([xibAttributes containsKey:@"NSFont"]) + [cibAttributes setObject:[xibAttributes valueForKey:@"NSFont"] forKey:CPFontAttributeName]; + + if ([xibAttributes containsKey:@"NSParagraphStyle"]) + [cibAttributes setObject:[xibAttributes valueForKey:@"NSParagraphStyle"] forKey:CPParagraphStyleAttributeName]; + + self = [super initWithString:[aCoder decodeObjectForKey:@"NSString"] attributes:cibAttributes]; + + return self; +} + +@end + +@implementation NSTextStorage : CPTextStorage +{ + +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [self NS_initWithCoder:aCoder]; + + if (self) + { + + } + + return self; +} + +- (Class)classForKeyedArchiver +{ + return [CPTextStorage class]; +} + +@end diff --git a/Tools/nib2cib/NSTextView.j b/Tools/nib2cib/NSTextView.j new file mode 100644 index 000000000..2c7492ead --- /dev/null +++ b/Tools/nib2cib/NSTextView.j @@ -0,0 +1,74 @@ +/* + * NSTextView.j + * nib2cib + * + * Created by Alexendre Wilhelm. + * Copyright 2014 The Cappuccino Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import + +@import "NSTextViewSharedData.j" + +@class Nib2Cib + +@implementation CPTextView (NSCoding) + +- (id)NS_initWithCoder:(CPCoder)aCoder textViewSharedData:(CPTextViewSharedData)aTextViewSharedData +{ + if (self = [super NS_initWithCoder:aCoder]) + { + _textContainer = [aCoder decodeObjectForKey:@"NSTextContainer"]; + + [self setEditable:[aTextViewSharedData isEditable]]; + [self setSelectable:[aTextViewSharedData isSelectable]]; + [self setRichText:[aTextViewSharedData isRichText]]; + [self setAllowsUndo:[aTextViewSharedData allowsUndo]]; + [self setUsesFontPanel:[aTextViewSharedData usesFontPanel]]; + + [self setBackgroundColor:[aTextViewSharedData backgroundColor]]; + [self setInsertionPointColor:[aTextViewSharedData insertionColor]]; + [self setSelectedTextAttributes:[aTextViewSharedData selectedTextAttributes]]; + [[self textContainer] setWidthTracksTextView:YES]; + } + + return self; +} + +@end + +@implementation NSTextView : CPTextView +{ + +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + if (self = [self NS_initWithCoder:aCoder textViewSharedData:[aCoder decodeObjectForKey:@"NSSharedData"]]) + { + + } + + return self; +} + +- (Class)classForKeyedArchiver +{ + return [CPTextView class]; +} + +@end diff --git a/Tools/nib2cib/NSTextViewSharedData.j b/Tools/nib2cib/NSTextViewSharedData.j new file mode 100644 index 000000000..8c4ad513f --- /dev/null +++ b/Tools/nib2cib/NSTextViewSharedData.j @@ -0,0 +1,100 @@ +/* + * NSTextView.j + * nib2cib + * + * Created by Alexendre Wilhelm. + * Copyright 2014 The Cappuccino Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +@import +@import +@import + +@class Nib2Cib + +@implementation CPTextViewSharedData : CPObject +{ + BOOL _allowsUndo @accessors(getter=allowsUndo); + BOOL _editable @accessors(getter=isEditable); + BOOL _richText @accessors(getter=isRichText); + BOOL _selectable @accessors(getter=isSelectable); + BOOL _usesFontPanel @accessors(getter=usesFontPanel); + CPColor _backgroundColor @accessors(getter=backgroundColor); + CPColor _insertionColor @accessors(getter=insertionColor); + CPDictionary _selectedTextAttributes @accessors(getter=selectedTextAttributes); +} + +- (id)init +{ + if (self = [super init]) + { + + } + + return self; +} + +@end + + +@implementation CPTextViewSharedData (NSCoding) + +- (id)NS_initWithCoder:(CPCoder)aCoder +{ + if (self = [super init]) + { + var flags = [aCoder decodeIntForKey:@"NSFlags"]; + + _allowsUndo = (flags & 0x0000400) ? YES : NO; + _editable = (flags & 0x00000002) ? YES : NO; + _richText = (flags & 0x00000004) ? YES : NO; + _selectable = (flags & 0x00000001) ? YES : NO; + _usesFontPanel = (flags & 0x00000020) ? YES : NO; + + _backgroundColor = [aCoder decodeObjectForKey:@"NSBackgroundColor"]; + _insertionColor = [aCoder decodeObjectForKey:@"NSInsertionColor"]; + _selectedTextAttributes = [aCoder decodeObjectForKey:@"NSSelectedAttributes"]; + } + + return self; +} + +@end + +@implementation NSTextViewSharedData : CPTextViewSharedData +{ + +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [self NS_initWithCoder:aCoder]; + + if (self) + { + + } + + return self; +} + +- (Class)classForKeyedArchiver +{ + return [CPTextViewSharedData class]; +} + +@end