mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-08 01:48:04 +00:00
Merge branch 'master' of github.com:cappuccino/cappuccino
This commit is contained in:
commit
ef1d6c78ac
94 files changed
+13279
-2726
No files matched your search
@@ -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"
|
||||
|
||||
+14
-2
@@ -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
|
||||
{
|
||||
|
||||
+27
-14
@@ -26,11 +26,12 @@
|
||||
|
||||
// Browser Engines
|
||||
CPUnknownBrowserEngine = 0;
|
||||
CPGeckoBrowserEngine = 1;
|
||||
CPInternetExplorerBrowserEngine = 2;
|
||||
CPKHTMLBrowserEngine = 3;
|
||||
CPOperaBrowserEngine = 4;
|
||||
CPWebKitBrowserEngine = 5;
|
||||
CPGeckoBrowserEngine = 1 << 0;
|
||||
CPInternetExplorerBrowserEngine = 1 << 1;
|
||||
CPKHTMLBrowserEngine = 1 << 2;
|
||||
CPOperaBrowserEngine = 1 << 3;
|
||||
CPWebKitBrowserEngine = 1 << 4; // Safari + Chrome
|
||||
CPBlinkBrowserEngine = 1 << 5; // Recent Chrome
|
||||
|
||||
// Operating Systems
|
||||
CPMacOperatingSystem = 0;
|
||||
@@ -84,14 +85,23 @@ CPAltEnterTextAreaFeature = 32;
|
||||
|
||||
/*
|
||||
When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen. This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203
|
||||
*/
|
||||
*/
|
||||
CPCanvasParentDrawErrorsOnMovementBug = 1 << 0;
|
||||
|
||||
// The paste event is only sent if an input or textarea has focus.
|
||||
CPJavaScriptPasteRequiresEditableTarget = 1 << 1;
|
||||
CPJavaScriptPasteRequiresEditableTarget = 1 << 1;
|
||||
// Redirecting the focus of the browser on keydown to an input for Cmd-V or Ctrl-V makes the paste fail.
|
||||
CPJavaScriptPasteCantRefocus = 1 << 2;
|
||||
|
||||
/*
|
||||
Safari calculates incorrect text size unless you set the canvas font even if it is already set
|
||||
You can see the bug after disabling the workaround and opening any panel while typing.
|
||||
You can use the font panel in the manual test for CPTextView.
|
||||
Look out for a displaced cursor, i.e. after typing letters of small width, such as the 'i'.
|
||||
https://bugs.webkit.org/show_bug.cgi?id=150224
|
||||
*/
|
||||
CPTextSizingAlwaysNeedsSetFontBug = 1 << 3;
|
||||
|
||||
|
||||
var USER_AGENT = "",
|
||||
PLATFORM_ENGINE = CPUnknownBrowserEngine,
|
||||
@@ -110,7 +120,7 @@ if (typeof window !== "undefined" && typeof window.navigator !== "undefined")
|
||||
// Opera
|
||||
if (typeof window !== "undefined" && window.opera)
|
||||
{
|
||||
PLATFORM_ENGINE = CPOperaBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPOperaBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES;
|
||||
}
|
||||
@@ -118,7 +128,7 @@ if (typeof window !== "undefined" && window.opera)
|
||||
// Internet Explorer
|
||||
else if (typeof window !== "undefined" && (window.attachEvent || (!(window.ActiveXObject) && "ActiveXObject" in window))) // Must follow Opera check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPInternetExplorerBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPInternetExplorerBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with IE (no known independent tests)
|
||||
PLATFORM_FEATURES[CPVMLFeature] = YES;
|
||||
@@ -136,10 +146,10 @@ else if (typeof window !== "undefined" && (window.attachEvent || (!(window.Activ
|
||||
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES;
|
||||
}
|
||||
|
||||
// WebKit
|
||||
// Safari + Chrome (WebKit and Blink)
|
||||
else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
{
|
||||
PLATFORM_ENGINE = CPWebKitBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPWebKitBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with WebKit (no known independent tests)
|
||||
PLATFORM_FEATURES[CPCSSRGBAFeature] = YES;
|
||||
@@ -179,7 +189,10 @@ else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
PLATFORM_BUGS |= CPJavaScriptPasteRequiresEditableTarget;
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=39689
|
||||
PLATFORM_BUGS |= CPJavaScriptPasteCantRefocus;
|
||||
PLATFORM_BUGS |= CPTextSizingAlwaysNeedsSetFontBug;
|
||||
}
|
||||
else if ((window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window)
|
||||
PLATFORM_ENGINE |= CPBlinkBrowserEngine;
|
||||
|
||||
// Assume this bug was introduced around Safari 5.1/Chrome 16. This could probably be tighter.
|
||||
if (majorVersion > 533)
|
||||
@@ -189,13 +202,13 @@ else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
// KHTML
|
||||
else if (USER_AGENT.indexOf("KHTML") != -1) // Must follow WebKit check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPKHTMLBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPKHTMLBrowserEngine;
|
||||
}
|
||||
|
||||
// Gecko
|
||||
else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPGeckoBrowserEngine;
|
||||
PLATFORM_ENGINE |= CPGeckoBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES;
|
||||
|
||||
@@ -286,7 +299,7 @@ function CPPlatformHasBug(aBug)
|
||||
|
||||
function CPBrowserIsEngine(anEngine)
|
||||
{
|
||||
return PLATFORM_ENGINE === anEngine;
|
||||
return PLATFORM_ENGINE & anEngine;
|
||||
}
|
||||
|
||||
function CPBrowserIsOperatingSystem(anOperatingSystem)
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@
|
||||
|
||||
@import "CPFont.j"
|
||||
@import "CPShadow.j"
|
||||
@import "CPView.j"
|
||||
@import "CPText.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
@import "CPTrackingArea.j"
|
||||
|
||||
@@ -169,7 +169,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Reverse set the binding iff the CPContinuouslyUpdatesValueBindingOption is set.
|
||||
Reverse set the binding if the CPContinuouslyUpdatesValueBindingOption is set.
|
||||
*/
|
||||
- (void)_continuouslyReverseSetBinding
|
||||
{
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@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",
|
||||
|
||||
+208
-1
@@ -22,9 +22,12 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@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,
|
||||
|
||||
@@ -44,6 +44,7 @@ CPStringPboardType = @"CPStringPboardType";
|
||||
CPURLPboardType = @"CPURLPboardType";
|
||||
CPImagesPboardType = @"CPImagesPboardType";
|
||||
CPVideosPboardType = @"CPVideosPboardType";
|
||||
CPRTFPboardType = @"CPRTFPboardType";
|
||||
|
||||
UTF8PboardType = @"public.utf8-plain-text";
|
||||
|
||||
|
||||
@@ -24,9 +24,13 @@
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CPPlatformString.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
|
||||
var CPStringSizeWithFontInWidthCache = {};
|
||||
var CPStringSizeWithFontInWidthCache = [],
|
||||
CPStringSizeWithFontHeightCache = [],
|
||||
CPStringSizeMeasuringContext;
|
||||
|
||||
CPStringSizeCachingEnabled = YES;
|
||||
|
||||
@@ -53,20 +57,57 @@ CPStringSizeCachingEnabled = YES;
|
||||
return [self sizeWithFont:aFont inWidth:NULL];
|
||||
}
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
if ([self class] != [CPString class])
|
||||
return;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if (CPFeatureIsCompatible(CPHTMLCanvasFeature) && !CPStringSizeMeasuringContext)
|
||||
CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate();
|
||||
#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 (!CPFeatureIsCompatible(CPHTMLCanvasFeature) || aWidth > 0)
|
||||
size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
|
||||
CPStringSizeWithFontInWidthCache[cacheKey] = size;
|
||||
else
|
||||
{
|
||||
if (CPPlatformHasBug(CPTextSizingAlwaysNeedsSetFontBug) || 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);
|
||||
}
|
||||
|
||||
|
||||
+41
-3
@@ -136,6 +136,8 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
|
||||
- (void)_insertTabViewItems:(CPArray)tabViewItems atIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
var prevItemsCount = [self numberOfTabViewItems];
|
||||
|
||||
[_tabs insertSegments:tabViewItems atIndexes:indexes];
|
||||
[tabViewItems makeObjectsPerformSelector:@selector(_setTabView:) withObject:self];
|
||||
|
||||
@@ -143,6 +145,10 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
[self _reverseSetContent];
|
||||
|
||||
[self _sendDelegateTabViewDidChangeNumberOfTabViewItems];
|
||||
|
||||
// Do not allow empty selection if selection bindings are not enabled.
|
||||
if (prevItemsCount == 0 && [self numberOfTabViewItems] > 0 && ![self _isSelectionBinded])
|
||||
[self _selectTabViewItemAtIndex:0];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -169,7 +175,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
- (void)_didRemoveTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPInteger)idx
|
||||
{
|
||||
// If the selection is managed by bindings, let the binder do that.
|
||||
if ([self binderForBinding:CPSelectionIndexesBinding] || [self binderForBinding:CPSelectedIndexBinding])
|
||||
if ([self _isSelectionBinded])
|
||||
return;
|
||||
|
||||
if (_selectedTabViewItem == aTabViewItem)
|
||||
@@ -335,13 +341,40 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
|
||||
[_tabs setSelectedSegment:anIndex];
|
||||
_selectedTabViewItem = aTabViewItem;
|
||||
[self _displayItemView:[aTabViewItem view]];
|
||||
[self _loadTabViewItem:aTabViewItem];
|
||||
|
||||
[self _sendDelegateDidSelectTabViewItem:aTabViewItem];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)_loadTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
var controller = [aTabViewItem viewController];
|
||||
|
||||
if (controller !== nil && ![controller isViewLoaded])
|
||||
{
|
||||
[controller loadViewWithCompletionHandler:function(view, error)
|
||||
{
|
||||
if (error !== nil)
|
||||
{
|
||||
CPLog.warn("Could not load the view for item " + aTabViewItem + ". " + error);
|
||||
}
|
||||
else if (view !== nil)
|
||||
{
|
||||
[aTabViewItem setView:view];
|
||||
|
||||
if ([self selectedTabViewItem] == aTabViewItem)
|
||||
[self _displayItemView:view];
|
||||
}
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self _displayItemView:[aTabViewItem view]];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current item being displayed.
|
||||
@return the tab view item currenly being displayed by the receiver
|
||||
@@ -588,6 +621,11 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
|
||||
return [cls getBinding:aBinding forObject:self];
|
||||
}
|
||||
|
||||
- (BOOL)_isSelectionBinded
|
||||
{
|
||||
return [self binderForBinding:CPSelectionIndexesBinding] || [self binderForBinding:CPSelectedIndexBinding];
|
||||
}
|
||||
|
||||
- (void)setItems:(CPArray)tabViewItems
|
||||
{
|
||||
if ([tabViewItems isEqualToArray:[_tabs segments]])
|
||||
@@ -848,4 +886,4 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
|
||||
return [super hitTest:aPoint];
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
@@ -270,7 +270,7 @@ CPPressedTab = 2;
|
||||
[self setLabel:title];
|
||||
|
||||
if ([_tabView selectedTabViewItem] == self)
|
||||
[_tabView _displayItemView:[_viewController view]];
|
||||
[_tabView _loadTabViewItem:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+13
-17
@@ -5097,14 +5097,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
}
|
||||
}
|
||||
else if (!_isViewBased && [aView isKindOfClass:[CPControl class]] && ![aView isKindOfClass:[CPTextField class]])
|
||||
{
|
||||
[self getColumn:@ref(column) row:@ref(row) forView:aView];
|
||||
|
||||
_editingColumn = column;
|
||||
_editingRow = row;
|
||||
|
||||
[aView addObserver:self forKeyPath:@"objectValue" options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:"editing"];
|
||||
}
|
||||
|
||||
return aView;
|
||||
}
|
||||
@@ -5236,7 +5229,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
if (!_isViewBased)
|
||||
{
|
||||
[self _setEditingState:NO forView:textField];
|
||||
[self _commitDataViewObjectValue:textField];
|
||||
[self _commitDataViewObjectValue:textField forColumn:_editingColumn andRow:_editingRow];
|
||||
}
|
||||
else
|
||||
[textField setBezeled:NO];
|
||||
@@ -5286,19 +5279,19 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
The action for any dataview that supports editing. This will only be called when the value was changed.
|
||||
The table view becomes the first responder after user is done editing a dataview.
|
||||
*/
|
||||
- (void)_commitDataViewObjectValue:(id)aDataView
|
||||
- (void)_commitDataViewObjectValue:(id)aDataView forColumn:(CPInteger)column andRow:(CPInteger)row
|
||||
{
|
||||
var editingTableColumn = _tableColumns[_editingColumn];
|
||||
var editingTableColumn = _tableColumns[column];
|
||||
|
||||
if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
|
||||
[_dataSource tableView:self setObjectValue:[aDataView objectValue] forTableColumn:editingTableColumn row:_editingRow];
|
||||
[_dataSource tableView:self setObjectValue:[aDataView objectValue] forTableColumn:editingTableColumn row:row];
|
||||
|
||||
// Allow the column binding to do a reverse set. Note that we do this even if the data source method above
|
||||
// is implemented.
|
||||
[editingTableColumn _reverseSetDataView:aDataView forRow:_editingRow];
|
||||
[editingTableColumn _reverseSetDataView:aDataView forRow:row];
|
||||
|
||||
if (_editingRow !== CPNotFound && _editingColumn !== CPNotFound)
|
||||
[self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:_editingRow] columnIndexes:[CPIndexSet indexSetWithIndex:_editingColumn]];
|
||||
if (row !== CPNotFound && column !== CPNotFound)
|
||||
[self _reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:row] columnIndexes:[CPIndexSet indexSetWithIndex:column]];
|
||||
}
|
||||
|
||||
- (void)_setEditingState:(BOOL)editingState forView:(CPView)aView
|
||||
@@ -5348,9 +5341,12 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
if (context === "editing" && [object superview] === self)
|
||||
{
|
||||
[object removeObserver:self forKeyPath:keyPath];
|
||||
[self _commitDataViewObjectValue:object];
|
||||
_editingRow = CPNotFound;
|
||||
_editingColumn = CPNotFound;
|
||||
|
||||
var row,
|
||||
column;
|
||||
|
||||
[self getColumn:@ref(column) row:@ref(row) forView:object];
|
||||
[self _commitDataViewObjectValue:object forColumn:column andRow:row];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+300
-5
@@ -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 <Foundation/Foundation.j>
|
||||
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@global CPStringPboardType
|
||||
@class CPAttributedString
|
||||
@class _CPRTFParser
|
||||
|
||||
@protocol CPTextDelegate <CPObject>
|
||||
|
||||
- (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;
|
||||
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
|
||||
@@ -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 <Foundation/CPObject.j>
|
||||
/*
|
||||
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
|
||||
@@ -0,0 +1,443 @@
|
||||
/*
|
||||
* CPFontPanel.j
|
||||
* AppKit
|
||||
*
|
||||
* TODOs:
|
||||
* 1. make browser-width for size smaller and fix columns
|
||||
* 2. add all the missing features from the MacOS X counterpart (sampleview)
|
||||
*
|
||||
*
|
||||
* 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"];
|
||||
|
||||
|
||||
/*!
|
||||
@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;
|
||||
}
|
||||
|
||||
|
||||
#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
|
||||
{
|
||||
_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];
|
||||
}
|
||||
|
||||
- (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 ];
|
||||
|
||||
_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]];
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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 <Foundation/CPObject.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@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
|
||||
@@ -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 <Foundation/CPGeometry.j>
|
||||
@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
|
||||
@@ -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 <Foundation/CPNotificationCenter.j>
|
||||
@import <Foundation/CPAttributedString.j>
|
||||
|
||||
@import "CPText.j"
|
||||
@import "CPFont.j"
|
||||
|
||||
@class CPLayoutManager;
|
||||
|
||||
CPTextStorageEditedAttributes = 1;
|
||||
CPTextStorageEditedCharacters = 2;
|
||||
|
||||
CPTextStorageWillProcessEditingNotification = @"CPTextStorageWillProcessEditingNotification";
|
||||
CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNotification";
|
||||
|
||||
@protocol CPTextStorageDelegate <CPObject>
|
||||
|
||||
- (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 <CPTextStorageDelegate> _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 <CPTextStorageDelegate>)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
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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 <Foundation/CPObject.j>
|
||||
|
||||
@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
|
||||
@@ -0,0 +1,791 @@
|
||||
/* 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 <Foundation/CPAttributedString.j>
|
||||
@import <Foundation/CPGeometry.j>
|
||||
@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:
|
||||
if((param + '') !== 'NaN' && (param + '').length)
|
||||
_currentParseIndex -= (param + '').length;
|
||||
|
||||
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
|
||||
@@ -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 <Foundation/CPAttributedString.j>
|
||||
@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
|
||||
+34
-32
@@ -416,7 +416,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
*/
|
||||
- (void)setToolTip:(CPString)aToolTip
|
||||
{
|
||||
if (_toolTip == aToolTip)
|
||||
if (_toolTip === aToolTip)
|
||||
return;
|
||||
|
||||
if (aToolTip && ![aToolTip isKindOfClass:CPString])
|
||||
@@ -574,7 +574,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self window] _dirtyKeyViewLoop];
|
||||
|
||||
// If this is already one of our subviews, remove it.
|
||||
if (aSubview._superview == self)
|
||||
if (aSubview._superview === self)
|
||||
{
|
||||
var index = [_subviews indexOfObjectIdenticalTo:aSubview];
|
||||
|
||||
@@ -863,7 +863,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
do
|
||||
{
|
||||
if (view == aView)
|
||||
if (view === aView)
|
||||
return YES;
|
||||
} while(view = [view superview])
|
||||
|
||||
@@ -978,7 +978,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
|
||||
- (CPView)viewWithTag:(CPInteger)aTag
|
||||
{
|
||||
if ([self tag] == aTag)
|
||||
if ([self tag] === aTag)
|
||||
return self;
|
||||
|
||||
var index = 0,
|
||||
@@ -1030,7 +1030,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas)
|
||||
[self _updateTrackingAreas];
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1103,7 +1103,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
#endif
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreas];
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1257,7 +1257,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreas];
|
||||
[self _updateTrackingAreasWithRecursion:!_autoresizesSubviews];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1306,7 +1306,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas)
|
||||
[self _updateTrackingAreas];
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1374,7 +1374,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreas];
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1418,7 +1418,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
|
||||
[self _updateTrackingAreas];
|
||||
[self _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
|
||||
@@ -1430,7 +1430,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
var mask = [self autoresizingMask];
|
||||
|
||||
if (mask == CPViewNotSizable)
|
||||
if (mask === CPViewNotSizable)
|
||||
return;
|
||||
|
||||
var frame = _superview._frame,
|
||||
@@ -1611,7 +1611,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
{
|
||||
do
|
||||
{
|
||||
if (self == view)
|
||||
if (self === view)
|
||||
{
|
||||
[_window makeFirstResponder:[self nextValidKeyView]];
|
||||
break;
|
||||
@@ -1737,7 +1737,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
*/
|
||||
- (void)setAlphaValue:(float)anAlphaValue
|
||||
{
|
||||
if (_opacity == anAlphaValue)
|
||||
if (_opacity === anAlphaValue)
|
||||
return;
|
||||
|
||||
_opacity = anAlphaValue;
|
||||
@@ -1936,10 +1936,10 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
*/
|
||||
- (void)setBackgroundColor:(CPColor)aColor
|
||||
{
|
||||
if (_backgroundColor == aColor)
|
||||
if (_backgroundColor === aColor)
|
||||
return;
|
||||
|
||||
if (aColor == [CPNull null])
|
||||
if (aColor === [CPNull null])
|
||||
aColor = nil;
|
||||
|
||||
_backgroundColor = aColor;
|
||||
@@ -1982,7 +1982,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
var image = slices[i],
|
||||
size = [image size];
|
||||
|
||||
if (!size || (size.width == 0 && size.height == 0))
|
||||
if (!size || (size.width === 0 && size.height === 0))
|
||||
size = nil;
|
||||
|
||||
_DOMImageSizes[i] = size;
|
||||
@@ -2037,10 +2037,12 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
CPDOMDisplayServerSetStyleSize(_DOMImageParts[0], size.width, size.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
_DOMElement.style.background = colorCSS;
|
||||
|
||||
if (patternImage)
|
||||
CPDOMDisplayServerSetStyleBackgroundSize(_DOMElement, [patternImage size].width + "px", [patternImage size].height + "px");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2074,7 +2076,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
partIndex++;
|
||||
}
|
||||
|
||||
if (_backgroundType == BackgroundNinePartImage)
|
||||
if (_backgroundType === BackgroundNinePartImage)
|
||||
{
|
||||
var left = _DOMImageSizes[0] ? _DOMImageSizes[0].width : 0,
|
||||
right = _DOMImageSizes[2] ? _DOMImageSizes[2].width : 0,
|
||||
@@ -2135,7 +2137,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
CPDOMDisplayServerSetStyleRightBottom(_DOMImageParts[partIndex], NULL, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
else if (_backgroundType == BackgroundVerticalThreePartImage)
|
||||
else if (_backgroundType === BackgroundVerticalThreePartImage)
|
||||
{
|
||||
var top = _DOMImageSizes[0] ? _DOMImageSizes[0].height : 0,
|
||||
bottom = _DOMImageSizes[2] ? _DOMImageSizes[2].height : 0;
|
||||
@@ -2167,7 +2169,7 @@ var CPViewHighDPIDrawingEnabled = YES;
|
||||
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], frameSize.width, bottom);
|
||||
}
|
||||
}
|
||||
else if (_backgroundType == BackgroundHorizontalThreePartImage)
|
||||
else if (_backgroundType === BackgroundHorizontalThreePartImage)
|
||||
{
|
||||
var left = _DOMImageSizes[0] ? _DOMImageSizes[0].width : 0,
|
||||
right = _DOMImageSizes[2] ? _DOMImageSizes[2].width : 0;
|
||||
@@ -2906,8 +2908,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
|
||||
{
|
||||
@@ -3129,7 +3131,7 @@ setBoundsOrigin:
|
||||
*/
|
||||
- (void)setLayer:(CALayer)aLayer
|
||||
{
|
||||
if (_layer == aLayer)
|
||||
if (_layer === aLayer)
|
||||
return;
|
||||
|
||||
if (_layer)
|
||||
@@ -3590,21 +3592,21 @@ setBoundsOrigin:
|
||||
[_trackingAreas removeObjectIdenticalTo:trackingArea];
|
||||
}
|
||||
|
||||
- (void)_updateTrackingAreas
|
||||
- (void)_updateTrackingAreasWithRecursion:(BOOL)shouldCallRecursively
|
||||
{
|
||||
_inhibitUpdateTrackingAreas = YES;
|
||||
|
||||
[self _recursivelyUpdateTrackingAreas];
|
||||
|
||||
_inhibitUpdateTrackingAreas = NO;
|
||||
}
|
||||
|
||||
- (void)_recursivelyUpdateTrackingAreas
|
||||
{
|
||||
[self _updateTrackingAreasForOwners:[self _calcTrackingAreaOwners]];
|
||||
|
||||
for (var i = 0; i < _subviews.length; i++)
|
||||
[_subviews[i] _recursivelyUpdateTrackingAreas];
|
||||
if (shouldCallRecursively)
|
||||
{
|
||||
// Now, call _updateTrackingAreasWithRecursion on subviews
|
||||
|
||||
for (var i = 0; i < _subviews.length; i++)
|
||||
[_subviews[i] _updateTrackingAreasWithRecursion:YES];
|
||||
}
|
||||
|
||||
_inhibitUpdateTrackingAreas = NO;
|
||||
}
|
||||
|
||||
- (CPArray)_calcTrackingAreaOwners
|
||||
|
||||
@@ -4098,6 +4098,9 @@ var interpolate = function(fromValue, toValue, progress)
|
||||
[overlappingTrackingAreas addObject:aTrackingArea];
|
||||
}
|
||||
|
||||
if (overlappingTrackingAreas.length === 0)
|
||||
return;
|
||||
|
||||
var frontmostTrackingArea = overlappingTrackingAreas[0],
|
||||
frontmostView = [frontmostTrackingArea view];
|
||||
|
||||
|
||||
@@ -163,9 +163,13 @@ CPLog.debug(_cmd + "context stack =" + _CPAnimationContextStack);
|
||||
{
|
||||
if (needsFrameTimer)
|
||||
[self stopFrameUpdaterWithIdentifier:objectId];
|
||||
else if (animationCompletion)
|
||||
|
||||
if (animationCompletion)
|
||||
animationCompletion();
|
||||
|
||||
if (needsFrameTimer || animationCompletion)
|
||||
[[CPRunLoop currentRunLoop] performSelectors];
|
||||
|
||||
if (_completionHandlerAgent)
|
||||
_completionHandlerAgent.decrement();
|
||||
};
|
||||
@@ -289,7 +293,7 @@ CPLog.debug(_cmd + "context stack =" + _CPAnimationContextStack);
|
||||
cssAnimations.push(cssAnimation);
|
||||
}
|
||||
|
||||
var css_mapping = [[aTargetView class] cssPropertiesForKeyPath:keyPath];
|
||||
var css_mapping = [[aTargetView class] _cssPropertiesForKeyPath:keyPath];
|
||||
|
||||
[css_mapping enumerateObjectsUsingBlock:function(aDict, anIndex, stop)
|
||||
{
|
||||
@@ -299,9 +303,6 @@ CPLog.debug(_cmd + "context stack =" + _CPAnimationContextStack);
|
||||
|
||||
cssAnimation.addPropertyAnimation(property, getter, duration, anAction.keytimes, anAction.values, timingFunctions, completionFunction);
|
||||
}];
|
||||
|
||||
if (needsFrameTimer)
|
||||
cssAnimation.setRemoveAnimationPropertyOnCompletion(false);
|
||||
}
|
||||
|
||||
if (needsFrameTimer)
|
||||
@@ -611,46 +612,42 @@ CFRunLoopRemoveObserver = function(runloop, observer, mode)
|
||||
var FrameUpdater = function(anIdentifier)
|
||||
{
|
||||
this._identifier = anIdentifier;
|
||||
this._requestId = null;
|
||||
this._duration = 0;
|
||||
this._stop = false;
|
||||
this._targets = [];
|
||||
this._callbacks = [];
|
||||
|
||||
var frameUpdater = this;
|
||||
|
||||
this._updateFunction = function(timestamp)
|
||||
{
|
||||
if (frameUpdater._startDate == null)
|
||||
frameUpdater._startDate = timestamp;
|
||||
|
||||
if (frameUpdater._stop)
|
||||
return;
|
||||
|
||||
if (this._startDate == null)
|
||||
this._startDate = timestamp;
|
||||
|
||||
for (var i = 0; i < frameUpdater._callbacks.length; i++)
|
||||
frameUpdater._callbacks[i]();
|
||||
|
||||
if (timestamp - this._startDate < frameUpdater._duration * 1000)
|
||||
if (timestamp - frameUpdater._startDate < frameUpdater._duration * 1000)
|
||||
window.requestAnimationFrame(frameUpdater._updateFunction);
|
||||
};
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.start = function()
|
||||
{
|
||||
window.requestAnimationFrame(this._updateFunction);
|
||||
this._requestId = window.requestAnimationFrame(this._updateFunction);
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.stop = function()
|
||||
{
|
||||
CPLog.debug("stop FrameUpdater with id " + this.identifier());
|
||||
// window.cancelAnimationFrame support is Chrome 24, Firefox 23, IE 10, Opera 15, Safari 6.1
|
||||
if (window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame(this._requestId);
|
||||
|
||||
this._stop = true;
|
||||
|
||||
var targets = this._targets;
|
||||
|
||||
for (var i = 0; i < targets.length; i++)
|
||||
{
|
||||
CPLog.debug(targets[i] + " Remove animation-name property");
|
||||
targets[i]._DOMElement.style.removeProperty(CPBrowserCSSProperty("animation-name"));
|
||||
}
|
||||
};
|
||||
|
||||
FrameUpdater.prototype.updateFunction = function()
|
||||
|
||||
@@ -6,35 +6,6 @@
|
||||
{
|
||||
}
|
||||
|
||||
- (void)viewWillMoveToSuperview:(CPView)aSuperview
|
||||
{
|
||||
var orderInAnim = [self animationForKey:@"CPAnimationTriggerOrderIn"];
|
||||
|
||||
if (orderInAnim && [orderInAnim isKindOfClass:[CAPropertyAnimation class]])
|
||||
{
|
||||
[_target setValue:[orderInAnim fromValue] forKeyPath:[orderInAnim keyPath]];
|
||||
}
|
||||
|
||||
[_target viewWillMoveToSuperview:aSuperview];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToSuperview
|
||||
{
|
||||
var orderInAnim = [self animationForKey:@"CPAnimationTriggerOrderIn"];
|
||||
|
||||
if (orderInAnim && [orderInAnim isKindOfClass:[CAPropertyAnimation class]])
|
||||
{
|
||||
[self _setTargetValue:YES withKeyPath:@"CPAnimationTriggerOrderIn" fallback:nil completion:function()
|
||||
{
|
||||
[_target setValue:[orderInAnim toValue] forKeyPath:[orderInAnim keyPath]];
|
||||
}];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_target viewDidMoveToSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeFromSuperview
|
||||
{
|
||||
[self _setTargetValue:nil withKeyPath:@"CPAnimationTriggerOrderOut" setter:_cmd];
|
||||
@@ -139,7 +110,7 @@ var DEFAULT_CSS_PROPERTIES = nil;
|
||||
|
||||
@implementation CPView (CPAnimatablePropertyContainer)
|
||||
|
||||
+ (CPDictionary)defaultCSSProperties
|
||||
+ (CPDictionary)_defaultCSSProperties
|
||||
{
|
||||
if (DEFAULT_CSS_PROPERTIES == nil)
|
||||
{
|
||||
@@ -160,9 +131,9 @@ var DEFAULT_CSS_PROPERTIES = nil;
|
||||
return DEFAULT_CSS_PROPERTIES;
|
||||
}
|
||||
|
||||
+ (CPArray)cssPropertiesForKeyPath:(CPString)aKeyPath
|
||||
+ (CPArray)_cssPropertiesForKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
return [[self defaultCSSProperties] objectForKey:aKeyPath];
|
||||
return [[self _defaultCSSProperties] objectForKey:aKeyPath];
|
||||
}
|
||||
|
||||
+ (Class)animatorClass
|
||||
@@ -190,7 +161,14 @@ var DEFAULT_CSS_PROPERTIES = nil;
|
||||
|
||||
+ (CAAnimation)defaultAnimationForKey:(CPString)aKey
|
||||
{
|
||||
if ([self cssPropertiesForKeyPath:aKey] !== nil)
|
||||
// TODO: remove when supported.
|
||||
if (aKey == @"CPAnimationTriggerOrderIn")
|
||||
{
|
||||
CPLog.warn("CPView animated key path CPAnimationTriggerOrderIn is not supported yet.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([self _cssPropertiesForKeyPath:aKey] !== nil)
|
||||
return [CAAnimation animation];
|
||||
|
||||
return nil;
|
||||
@@ -219,4 +197,4 @@ var DEFAULT_CSS_PROPERTIES = nil;
|
||||
_animationsDictionary = [animationsDict copy];
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
@@ -51,7 +51,6 @@ CSSAnimation = function(aTarget/*DOM Element*/, anIdentifier)
|
||||
this.animationsdurations = [];
|
||||
this.islive = false;
|
||||
this.didBuildDOMElements = false;
|
||||
this.removeAnimationPropertyOnCompletion = true;
|
||||
|
||||
animation = this;
|
||||
CURRENT_ANIMATIONS[anIdentifier] = animation;
|
||||
@@ -177,21 +176,21 @@ CSSAnimation.prototype.endEventListener = function()
|
||||
if (idx !== -1)
|
||||
inFlightAnimationsNames.splice(idx, 1);
|
||||
|
||||
if (inFlightAnimationsNames.length == 0)
|
||||
if (inFlightAnimationsNames.length > 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < animationsNames.length; i++)
|
||||
{
|
||||
for (var i = 0; i < animationsNames.length; i++)
|
||||
{
|
||||
var completion = animation.completionFunctionForAnimationName(animationsNames[i]);
|
||||
if (completion)
|
||||
completion();
|
||||
}
|
||||
var completion = animation.completionFunctionForAnimationName(animationsNames[i]);
|
||||
if (completion)
|
||||
completion();
|
||||
}
|
||||
|
||||
var eventTarget = event.target,
|
||||
style = eventTarget.style;
|
||||
|
||||
if (animation.removeAnimationPropertyOnCompletion)
|
||||
style.removeProperty(ANIMATION_NAME_PROPERTY);
|
||||
var eventTarget = event.target,
|
||||
style = eventTarget.style;
|
||||
|
||||
try {
|
||||
style.removeProperty(ANIMATION_NAME_PROPERTY);
|
||||
style.removeProperty(ANIMATION_DURATION_PROPERTY);
|
||||
style.removeProperty(ANIMATION_FILL_MODE_PROPERTY);
|
||||
style.removeProperty("-webkit-backface-visibility");
|
||||
@@ -204,6 +203,8 @@ CSSAnimation.prototype.endEventListener = function()
|
||||
eventTarget.removeEventListener(ANIMATION_END_EVENT_NAME, AnimationEndListener);
|
||||
animation.listener = null;
|
||||
delete (CURRENT_ANIMATIONS[animation.identifier]);
|
||||
} catch (err) {
|
||||
CPLog.warn("CSSAnimation.j - " + err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -260,11 +261,6 @@ CSSAnimation.prototype.buildDOMElements = function()
|
||||
this.didBuildDOMElements = true;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.setRemoveAnimationPropertyOnCompletion = function(flag)
|
||||
{
|
||||
this.removeAnimationPropertyOnCompletion = flag;
|
||||
}
|
||||
|
||||
CSSAnimation.prototype.start = function()
|
||||
{
|
||||
if (this.propertyanimations.length == 0 || this.islive)
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
@import "CPCompatibility.j"
|
||||
@import "CGGeometry.j"
|
||||
@import "CGPath.j"
|
||||
@import "CGContextText.j"
|
||||
|
||||
@typedef CGContext
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* CGContextText.j
|
||||
* CoreText
|
||||
*
|
||||
* Created by Nicholas Small.
|
||||
* Copyright 2011, 280 North, Inc.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
kCGTextFill = 0;
|
||||
kCGTextStroke = 1;
|
||||
kCGTextFillStroke = 2;
|
||||
kCGTextInvisible = 3;
|
||||
|
||||
function CGContextGetTextMatrix(/* CGContext */ aContext)
|
||||
{
|
||||
return aContext._textMatrix;
|
||||
}
|
||||
|
||||
function CGContextSetTextMatrix(/* CGContext */ aContext, /* CGAffineTransform */ aTransform)
|
||||
{
|
||||
aContext._textMatrix = aTransform;
|
||||
}
|
||||
|
||||
function CGContextGetTextPosition(/* CGContext */ aContext)
|
||||
{
|
||||
return aContext._textPosition || _CGPointMakeZero();
|
||||
}
|
||||
|
||||
function CGContextSetTextPosition(/* CGContext */ aContext, /* float */ x, /* float */ y)
|
||||
{
|
||||
aContext._textPosition = CGPointMake(x, y);
|
||||
}
|
||||
|
||||
function CGContextGetFont(/* CGContext */ aContext)
|
||||
{
|
||||
return aContext._CPFont;
|
||||
}
|
||||
|
||||
function CGContextSelectFont(/* CGContext */ aContext, /* CPFont */ aFont)
|
||||
{
|
||||
aContext.font = [aFont cssString];
|
||||
aContext._CPFont = aFont;
|
||||
}
|
||||
|
||||
function CGContextSetTextDrawingMode(/* CGContext */ aContext, /* CGTextDrawingMode */ aMode)
|
||||
{
|
||||
aContext._textDrawingMode = aMode;
|
||||
}
|
||||
|
||||
function CGContextShowText(/* CGContext */ aContext, /* CPString */ aString)
|
||||
{
|
||||
CGContextShowTextAtPoint(aContext, aContext._textPosition.x, aContext._textPosition.y, aString);
|
||||
}
|
||||
|
||||
function CGContextShowTextAtPoint(/* CGContext */ aContext, /* float */ x, /* float */ y, /* CPString */ aString)
|
||||
{
|
||||
aContext.textBaseline = @"middle";
|
||||
aContext.textAlign = @"left";
|
||||
|
||||
var mode = aContext._textDrawingMode;
|
||||
if (!mode && mode !== 0)
|
||||
mode = kCGTextFill;
|
||||
|
||||
var width = aContext.measureText(aString).width;
|
||||
|
||||
if (mode === kCGTextFill || mode === kCGTextFillStroke)
|
||||
aContext.fillText(aString, x, y);
|
||||
if (mode === kCGTextStroke || mode === kCGTextFillStroke)
|
||||
aContext.strokeText(aString, x, y);
|
||||
|
||||
aContext._textPosition = CGPointMake(x + width, y);
|
||||
}
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in new issue
Block a user