Merge remote-tracking branch 'upstream/main' into Aristo3

This commit is contained in:
daboe01 committed 2026-10-24 22:00:51 +02:00
commit 3ad080b788
142 files changed
+14126 -3630

No files matched your search

+4
View File
@@ -84,6 +84,8 @@
@import "CPRadio.j"
@import "CPResponder.j"
@import "CPRuleEditor.j"
@import "CPRulerMarker.j"
@import "CPRulerView.j"
@import "CPScroller.j"
@import "CPScrollView.j"
@import "CPSearchField.j"
@@ -99,7 +101,9 @@
@import "CPTableView.j"
@import "CPTabView.j"
@import "CPText.j"
@import "CPTextAttachment.j"
@import "CPTextField.j"
@import "CPTextList.j"
@import "CPTextView.j"
@import "CPTokenField.j"
@import "CPToolbar.j"
+2 -1
View File
@@ -137,7 +137,8 @@ var bottomHeight = 71;
@delegate -(void)alertDidEnd:(CPAlert)theAlert returnCode:(int)returnCode;
Called when the user dismisses the alert by clicking one of the buttons.
@param theAlert the alert panel that the user dismissed
@param returnCode the index of the button that the user clicked (starting from 0,
@param returnCode the tag of the button that the user clicked. Unless a button's
tag has been changed, it is the button's index (starting from 0,
representing the first button added to the alert which appears on the
right, 1 representing the next button to the left and so on)
*/
+257 -24
View File
@@ -37,6 +37,8 @@
@import "CPWindowController.j"
@import "_CPPopoverWindow.j"
@global CPWindowObjectList
@typedef CPModalSession
var CPMainCibFile = @"CPMainCibFile",
@@ -121,6 +123,9 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
CPPanel _aboutPanel;
CPMenu _windowsMenu;
CPMenuItem _windowsMenuSeparator;
CPThemeBlend _themeBlend @accessors(property=themeBlend);
// OS behavior
@@ -248,9 +253,10 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
// At this point we clear the window.status to eliminate Safari's "Cancelled" error message
// The message shouldn't be displayed, because only an XHR is cancelled, but it is a usability issue.
// We do it here so that applications can change it in willFinish or didFinishLaunching
#if PLATFORM(DOM)
window.status = " ";
#endif
if (CPDOMAvailable)
{
window.status = " ";
}
// We also want to set the default cursor on the body, so that buttons and things don't have an iBeam
[[CPCursor arrowCursor] set];
@@ -280,9 +286,10 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
var needsUntitled = !!_documentController,
URLStrings = nil;
#if PLATFORM(DOM)
URLStrings = window.cpOpeningURLStrings && window.cpOpeningURLStrings();
#endif
if (CPDOMAvailable)
{
URLStrings = window.cpOpeningURLStrings && window.cpOpeningURLStrings();
}
var index = 0,
count = [URLStrings count];
@@ -375,7 +382,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
to the following respective keys in your info.plist file.
<pre>
CPBundleName
CPBundleDisplayName, or CPBundleName when there is no display name
CPApplicationIcon (through a call to -applicationIconImage, see documentation for that method for more details)
CPBundleVersion
CPBundleShortVersionString
@@ -389,7 +396,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
if (!_aboutPanel)
{
var mainInfo = [[CPBundle mainBundle] infoDictionary],
applicationTitle = [options objectForKey:"ApplicationName"] || [mainInfo objectForKey:@"CPBundleName"],
applicationTitle = [options objectForKey:"ApplicationName"] || [self _applicationName],
applicationIcon = [options objectForKey:@"ApplicationIcon"] || [self applicationIconImage],
version = [options objectForKey:@"Version"] || [mainInfo objectForKey:@"CPBundleVersion"],
applicationVersion = [options objectForKey:@"ApplicationVersion"] || [mainInfo objectForKey:@"CPBundleShortVersionString"],
@@ -734,11 +741,10 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
*/
- (CPArray)orderedWindows
{
#if PLATFORM(DOM)
return CPWindowObjectList();
#else
if (CPDOMAvailable)
return CPWindowObjectList();
return [];
#endif
}
- (void)hide:(id)aSender
@@ -775,11 +781,238 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
if ([CPPlatform supportsNativeMainMenu])
window.cpSetMainMenu([self menu]);
// The new main menu may contain a different Windows menu, so find it again and list
// the windows that were ordered in before the main menu was set.
_windowsMenu = nil;
_windowsMenuSeparator = nil;
for (var i = 0, count = [_windows count]; i < count; i++)
{
var aWindow = _windows[i];
// _windows is indexed by window number and holds a placeholder at index 0.
if ([aWindow isKindOfClass:[CPWindow class]])
[aWindow _updateWindowsMenuItem];
}
}
else
[aMenu _setMenuName:@"CPMainMenu"];
}
/*
@ignore
Returns the application's name as users see it: CPBundleDisplayName from the main bundle's
Info.plist, or CPBundleName when there is no display name.
*/
- (CPString)_applicationName
{
var mainBundle = [CPBundle mainBundle];
return [mainBundle objectForInfoDictionaryKey:@"CPBundleDisplayName"] || [mainBundle objectForInfoDictionaryKey:@"CPBundleName"];
}
// MARK: Managing the Windows Menu
/*!
Returns the application's Windows menu, which lists the application's windows.
Unless a menu has been set with \c -setWindowsMenu:, this is the submenu of the main menu
named \c _CPWindowsMenu. nib2cib gives that name to the menu marked as the Windows menu in
Interface Builder.
@return the Windows menu, or \c nil if the application has none
*/
- (CPMenu)windowsMenu
{
if (!_windowsMenu)
_windowsMenu = [[self mainMenu] _menuWithName:@"_CPWindowsMenu"];
return _windowsMenu;
}
/*!
Makes \c aMenu the application's Windows menu, and moves the items for the listed windows
from the previous Windows menu to it.
@param aMenu the new Windows menu
*/
- (void)setWindowsMenu:(CPMenu)aMenu
{
var previousMenu = [self windowsMenu];
if (previousMenu === aMenu)
return;
var listedWindows = [self _windowsInWindowsMenu];
[listedWindows makeObjectsPerformSelector:@selector(_removeFromWindowsMenu)];
_windowsMenu = aMenu;
_windowsMenuSeparator = nil;
[listedWindows makeObjectsPerformSelector:@selector(_updateWindowsMenuItem)];
}
/*!
Adds an item for \c aWindow to the Windows menu. The item makes the window key and orders it
front when chosen. Does nothing if the menu already has an item for \c aWindow.
@param aWindow the window to list
@param aString the item's title
@param isFilename if \c YES, \c aString is a file path, shown as the file name followed by its
directory
*/
- (void)addWindowsItem:(CPWindow)aWindow title:(CPString)aString filename:(BOOL)isFilename
{
var menu = [self windowsMenu];
if (!menu || [self _windowsMenuItemForWindow:aWindow])
return;
// Separate the window list from the menu's own items.
if (!_windowsMenuSeparator && [menu numberOfItems] > 0)
{
_windowsMenuSeparator = [CPMenuItem separatorItem];
[menu addItem:_windowsMenuSeparator];
}
var item = [[CPMenuItem alloc] initWithTitle:[self _windowsMenuTitle:aString filename:isFilename]
action:@selector(makeKeyAndOrderFront:)
keyEquivalent:@""];
[item setTarget:aWindow];
[menu addItem:item];
[self updateWindowsItem:aWindow];
}
/*!
Changes the title of the Windows menu item for \c aWindow, adding the item if the menu has
none for it.
@param aWindow the listed window
@param aString the item's new title
@param isFilename if \c YES, \c aString is a file path, shown as the file name followed by its
directory
*/
- (void)changeWindowsItem:(CPWindow)aWindow title:(CPString)aString filename:(BOOL)isFilename
{
var item = [self _windowsMenuItemForWindow:aWindow];
if (item)
[item setTitle:[self _windowsMenuTitle:aString filename:isFilename]];
else
[self addWindowsItem:aWindow title:aString filename:isFilename];
}
/*!
Removes the Windows menu item for \c aWindow, and removes the separator above the window
list when the last window item is removed.
@param aWindow the window to remove from the list
*/
- (void)removeWindowsItem:(CPWindow)aWindow
{
var item = [self _windowsMenuItemForWindow:aWindow];
if (!item)
return;
var menu = [self windowsMenu];
[menu removeItem:item];
if (_windowsMenuSeparator && ![[self _windowsInWindowsMenu] count])
{
[menu removeItem:_windowsMenuSeparator];
_windowsMenuSeparator = nil;
}
}
/*!
Updates the state of the Windows menu item for \c aWindow: a check mark for the main window,
the mixed state for a miniaturized window, and no mark otherwise.
@param aWindow the listed window
*/
- (void)updateWindowsItem:(CPWindow)aWindow
{
var item = [self _windowsMenuItemForWindow:aWindow];
if (!item)
return;
if ([aWindow isMiniaturized])
[item setState:CPMixedState];
else
[item setState:aWindow === [self mainWindow] ? CPOnState : CPOffState];
}
/*!
Orders front every window listed in the Windows menu that is not miniaturized, keeping their
order relative to each other.
@param aSender the object requesting this
*/
- (void)arrangeInFront:(id)aSender
{
var listedWindows = [self _windowsInWindowsMenu],
orderedWindows = [self orderedWindows],
index = [orderedWindows count];
// orderedWindows lists front to back, so order front from the back.
while (index--)
{
var aWindow = orderedWindows[index];
if ([listedWindows containsObjectIdenticalTo:aWindow] && ![aWindow isMiniaturized])
[aWindow orderFront:aSender];
}
}
/* @ignore */
- (CPMenuItem)_windowsMenuItemForWindow:(CPWindow)aWindow
{
var items = [[self windowsMenu] itemArray],
count = [items count];
for (var i = 0; i < count; i++)
{
var item = items[i];
if ([item target] === aWindow && [item action] === @selector(makeKeyAndOrderFront:))
return item;
}
return nil;
}
/* @ignore */
- (CPArray)_windowsInWindowsMenu
{
var items = [[self windowsMenu] itemArray],
count = [items count],
listedWindows = [];
for (var i = 0; i < count; i++)
{
var item = items[i];
if ([item action] === @selector(makeKeyAndOrderFront:) && [[item target] isKindOfClass:[CPWindow class]])
listedWindows.push([item target]);
}
return listedWindows;
}
/* @ignore */
- (CPString)_windowsMenuTitle:(CPString)aString filename:(BOOL)isFilename
{
if (!isFilename)
return aString;
var directory = [aString stringByDeletingLastPathComponent];
if (![directory length])
return [aString lastPathComponent];
return [aString lastPathComponent] + @" — " + directory;
}
/*!
Opens the shared color panel.
@param aSender
@@ -1349,22 +1582,22 @@ _CPRunModalLoop = function(anEvent)
function CPApplicationMain(args, namedArgs)
{
#if PLATFORM(DOM)
// hook to allow recorder, etc to manipulate things before starting AppKit
if (window.parent !== window && typeof window.parent._childAppIsStarting === "function")
if (CPDOMAvailable)
{
try
// hook to allow recorder, etc to manipulate things before starting AppKit
if (window.parent !== window && typeof window.parent._childAppIsStarting === "function")
{
window.parent._childAppIsStarting(window);
}
catch(err)
{
// This could happen if we're in an iframe without access to the parent frame.
CPLog.warn("Failed to call parent frame's _childAppIsStarting().");
try
{
window.parent._childAppIsStarting(window);
}
catch(err)
{
// This could happen if we're in an iframe without access to the parent frame.
CPLog.warn("Failed to call parent frame's _childAppIsStarting().");
}
}
}
#endif
var mainBundle = [CPBundle mainBundle],
principalClass = [mainBundle principalClass];
+5 -8
View File
@@ -563,9 +563,10 @@ var cachedBlackColor,
{
// Fix to avoid a problem when compiling a theme (missing the alpha component as theme compiler doesn't have CSS rgba capability)
var hasAlpha = YES;
#if PLATFORM(DOM)
hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
#endif
if (CPDOMAvailable)
{
hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
}
_cssString = (hasAlpha ? "rgba(" : "rgb(") +
parseInt(_components[0] * 255.0) + ", " +
@@ -1001,19 +1002,16 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
+ (void)restorePreviousCSSState:(CPArrayRef)aPreviousStateRef forDOMElement:(DOMElement)aDOMElement
{
#if PLATFORM(DOM)
var aPreviousState = @deref(aPreviousStateRef);
for (var i = 0, count = aPreviousState.length; i < count; i++)
aDOMElement.style[aPreviousState[i][0]] = aPreviousState[i][1];
@deref(aPreviousStateRef) = @[];
#endif
}
- (DOMElement)applyCSSColorForView:(CPView)aView onDOMElement:(DOMElement)aDOMElement styleNode:(DOMElement)aStyleNode previousState:(CPArrayRef)aPreviousStateRef
{
#if PLATFORM(DOM)
var aPreviousState = @deref(aPreviousStateRef);
[_cssDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
@@ -1086,7 +1084,6 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
@deref(aPreviousStateRef) = aPreviousState;
return aStyleNode;
#endif
}
@end
@@ -1494,7 +1491,7 @@ var patternColorsFromPattern = function(pattern, attributes, imageFactory)
else
{
numParts = 3;
isVertical = orientation === PatternIsVertical;
isVertical = orientation === CPColorPatternIsVertical;
if (isVertical)
{
+33 -30
View File
@@ -23,6 +23,7 @@
@import <Foundation/CPObject.j>
@import "CPView.j"
@import "CPCompatibility.j"
@class CPSlider
@class CPColorPanel
@@ -235,24 +236,25 @@
{
if (self = [super initWithFrame:aFrame])
{
#if PLATFORM(DOM)
var path = [[CPBundle bundleForClass:CPColorPicker] pathForResource:@"wheel.png"];
if (CPDOMAvailable)
{
var path = [[CPBundle bundleForClass:CPColorPicker] pathForResource:@"wheel.png"];
_wheelImage = new Image();
_wheelImage.src = path;
_wheelImage.style.position = "absolute";
_wheelImage = new Image();
_wheelImage.src = path;
_wheelImage.style.position = "absolute";
path = [[CPBundle bundleForClass:CPColorPicker] pathForResource:@"wheel_black.png"];
path = [[CPBundle bundleForClass:CPColorPicker] pathForResource:@"wheel_black.png"];
_blackWheelImage = new Image();
_blackWheelImage.src = path;
_blackWheelImage.style.opacity = "0";
_blackWheelImage.style.filter = "alpha(opacity=0)";
_blackWheelImage.style.position = "absolute";
_blackWheelImage = new Image();
_blackWheelImage.src = path;
_blackWheelImage.style.opacity = "0";
_blackWheelImage.style.filter = "alpha(opacity=0)";
_blackWheelImage.style.position = "absolute";
_DOMElement.appendChild(_wheelImage);
_DOMElement.appendChild(_blackWheelImage);
#endif
_DOMElement.appendChild(_wheelImage);
_DOMElement.appendChild(_blackWheelImage);
}
[self setWheelSize:aFrame.size];
@@ -272,10 +274,10 @@
- (void)setWheelBrightness:(float)brightness
{
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
_blackWheelImage.style.opacity = 1.0 - brightness;
_blackWheelImage.style.filter = "alpha(opacity=" + (1.0 - brightness) * 100 + ")"
#endif
}
- (void)setFrameSize:(CGSize)aSize
@@ -288,21 +290,22 @@
{
var min = MIN(aSize.width, aSize.height);
#if PLATFORM(DOM)
_blackWheelImage.style.width = min;
_blackWheelImage.style.height = min;
_blackWheelImage.width = min;
_blackWheelImage.height = min;
_blackWheelImage.style.top = (aSize.height - min) / 2.0 + "px";
_blackWheelImage.style.left = (aSize.width - min) / 2.0 + "px";
if (CPDOMAvailable)
{
_blackWheelImage.style.width = min;
_blackWheelImage.style.height = min;
_blackWheelImage.width = min;
_blackWheelImage.height = min;
_blackWheelImage.style.top = (aSize.height - min) / 2.0 + "px";
_blackWheelImage.style.left = (aSize.width - min) / 2.0 + "px";
_wheelImage.style.width = min;
_wheelImage.style.height = min;
_wheelImage.width = min;
_wheelImage.height = min;
_wheelImage.style.top = (aSize.height - min) / 2.0 + "px";
_wheelImage.style.left = (aSize.width - min) / 2.0 + "px";
#endif
_wheelImage.style.width = min;
_wheelImage.style.height = min;
_wheelImage.width = min;
_wheelImage.height = min;
_wheelImage.style.top = (aSize.height - min) / 2.0 + "px";
_wheelImage.style.left = (aSize.width - min) / 2.0 + "px";
}
_radius = min / 2.0;
-2
View File
@@ -846,7 +846,6 @@ var CPComboBoxTextSubview = @"text",
if (!shouldResign)
{
#if PLATFORM(DOM)
// In FireFox this needs to be done in setTimeout, otherwise there is no caret
// We have to save the input element now, when we lose focus it will change.
var element = [self _inputElement];
@@ -861,7 +860,6 @@ var CPComboBoxTextSubview = @"text",
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
#endif
return NO;
}
+23 -5
View File
@@ -24,6 +24,29 @@
@import "CPPlatform.j"
/*
CPDOMAvailable is the authoritative runtime predicate for DOM capability.
It replaces the former `#if PLATFORM(DOM)` preprocessor conditional, which
selected code at build time between the browser (DOM) and CommonJS (Node)
targets. It is a CAPABILITY test: YES only when a
functioning document object with createElement exists.
The predicate subsumes all direct local probing for the presence of a DOM.
YES - browsers (the only deployment target)
YES - any environment providing a working DOM (e.g. jsdom), where DOM
operations genuinely function
NO - CommonJS/Node without a DOM (legacy tooling: theme blending, nib2cib)
Rules for consumers:
- Platform-conditional code MUST test CPDOMAvailable.
- Do NOT reference `document` (or test for it) anywhere else in AppKit;
this declaration is the single environment peek.
- The preprocessor form must not return: there is no preprocessor.
*/
CPDOMAvailable = (typeof document !== "undefined") && document && (typeof document.createElement === "function");
// Browser Engines
CPUnknownBrowserEngine = 0;
CPGeckoBrowserEngine = 1 << 0;
@@ -55,8 +78,6 @@ CPJavaScriptClipboardAccessFeature = 12;
CPJavaScriptCanvasDrawFeature = 13;
CPJavaScriptCanvasTransformFeature = 14;
CPVMLFeature = 15;
CPJavaScriptRemedialKeySupport = 16;
CPJavaScriptShadowFeature = 20;
@@ -133,7 +154,6 @@ else if (typeof window !== "undefined" && (window.attachEvent || (!(window.Activ
PLATFORM_ENGINE |= CPInternetExplorerBrowserEngine;
// Features we can only be sure of with IE (no known independent tests)
PLATFORM_FEATURES[CPVMLFeature] = YES;
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES;
@@ -388,7 +408,6 @@ function CPBrowserStyleProperty(aProperty)
{
var r = nil;
#if PLATFORM(DOM)
var testElement = document.createElement('div');
switch (lowerProperty)
@@ -455,7 +474,6 @@ function CPBrowserStyleProperty(aProperty)
break;
}
#endif
PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] = r;
}
+3 -2
View File
@@ -1043,7 +1043,8 @@ var CPControlBlackColor = [CPColor blackColor];
_baseWritingDirection = writingDirection;
[self didChangeValueForKey:@"baseWritingDirection"];
#if PLATFORM(DOM)
if (CPDOMAvailable)
{
var style;
@@ -1066,7 +1067,7 @@ var CPControlBlackColor = [CPColor blackColor];
}
_DOMElement.style.direction = style;
#endif
}
}
@end
+23 -17
View File
@@ -22,6 +22,7 @@
@import <Foundation/CPObject.j>
@import <Foundation/CPString.j>
@import "CPCompatibility.j"
/*!
@@ -95,32 +96,37 @@
else
domain = "";
#if PLATFORM(DOM)
document.cookie = _cookieName + "=" + value + expires + "; path=/" + domain;
#else
_cookieValue = value;
_expires = expires;
#endif
if (CPDOMAvailable)
{
document.cookie = _cookieName + "=" + value + expires + "; path=/" + domain;
}
else
{
_cookieValue = value;
_expires = expires;
}
}
/* @ignore */
- (CPString)_readCookieValue
{
#if PLATFORM(DOM)
var nameEQ = _cookieName + "=",
ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++)
if (CPDOMAvailable)
{
var c = ca[i];
var nameEQ = _cookieName + "=",
ca = document.cookie.split(';');
while (c.charAt(0) == ' ')
c = c.substring(1, c.length);
for (var i = 0; i < ca.length; i++)
{
var c = ca[i];
if (c.indexOf(nameEQ) == 0)
return c.substring(nameEQ.length, c.length);
while (c.charAt(0) == ' ')
c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0)
return c.substring(nameEQ.length, c.length);
}
}
#endif
return "";
}
-6
View File
@@ -135,9 +135,7 @@ CPCursorPlatformBoth = 3;
currentCursor = self;
#if PLATFORM(DOM)
[[self class] _setCursorCSS:_cssString];
#endif
}
- (void)mouseEntered:(CPEvent)event
@@ -155,12 +153,10 @@ CPCursorPlatformBoth = 3;
+ (void)_setCursorCSS:(CPString)aString
{
#if PLATFORM(DOM)
var platformWindows = [[CPPlatformWindow visiblePlatformWindows] allObjects];
for (var i = 0, count = [platformWindows count]; i < count; i++)
platformWindows[i]._DOMBodyElement.style.cursor = aString;
#endif
}
// Internal method that is used to return the system cursors. Caches the system cursors for performance.
@@ -217,7 +213,6 @@ CPCursorPlatformBoth = 3;
+ (BOOL)_nativeCursorExists:(CPString)cursorCSSName
{
#if PLATFORM(DOM)
// FIXME: Trick until FF/Win & Chrome/Win correctly implement context-menu cursor
// They will answer that they implement it but they actually don't
@@ -240,7 +235,6 @@ CPCursorPlatformBoth = 3;
return doesExist;
}
#endif
return NO;
}
+6 -4
View File
@@ -30,6 +30,7 @@
@import <Foundation/CPDate.j>
@import <Foundation/CPLocale.j>
@import <Foundation/CPTimeZone.j>
@import "CPCompatibility.j"
@class CPStepper
@class CPApp
@@ -258,10 +259,11 @@ CPEraDatePickerElementFlag = 0x0100;
[_datePickerComponent setDatePickerElements:_datePickerElements];
// FIXME: Don't know why but next line will cause theme compilation to fail...
// Workaround: added "if PLATFORM(DOM)"
#if PLATFORM(DOM)
[_datePickerComponent setEnabled:[self isEnabled]];
#endif
// Workaround: guarded with CPDOMAvailable (was: "if PLATFORM(DOM)")
if (CPDOMAvailable)
{
[_datePickerComponent setEnabled:[self isEnabled]];
}
if (_isTextual)
// We need to transmit text color to the text field version (Cocoa doesn't permit adapting the calendar view text color)
+327 -184
View File
@@ -24,11 +24,15 @@
@import <Foundation/CPKeyedUnarchiver.j>
@import "CPView.j"
@import "CPTextField.j"
@import "CPImageView.j"
@import "CPImage.j"
@import "CALayer.j"
@import "CPCompatibility.j"
@class _CPCibCustomResource
@class CPDatePicker
@class HandImageLayer
@class HoursLayer
@class HandLayer
@global CPHourMinuteSecondDatePickerElementFlag
@global CPTextFieldAndStepperDatePickerStyle
@@ -44,19 +48,15 @@ _CPDatePickerClockSeconds = 3;
@implementation _CPDatePickerClock : CPControl
{
BOOL _isEnabled;
// Pure DOM Views
CPImageView _hourHandView;
CPImageView _minuteHandView;
CPImageView _secondHandView;
CPImageView _middleHandView;
CPArray _hourLabels;
HoursLayer _rootLayer;
HandLayer _hourHandLayer;
HandLayer _minuteHandLayer;
HandLayer _secondHandLayer;
CALayer _middleHandLayer;
CPDatePicker _datePicker;
CPTextField _PMAMTextField;
CPDatePicker _datePicker;
CPView _currentHandView;
CALayer _currentHandLayer;
_CPDatePickerClockHand _currentHand;
CPInteger _currentRepresentedValue;
float _currentValueShift;
@@ -68,14 +68,10 @@ _CPDatePickerClockSeconds = 3;
CPInteger _representedSeconds;
BOOL _representedHourIsPM;
// Angles for Hit-Testing
float _hourAngle;
float _minuteAngle;
float _secondAngle;
CPInteger _datePickerElements @accessors(getter=datePickerElements);
CPInteger _datePickerElements @accessors(getter=datePickerElements);
}
// MARK: -
// MARK: Init methods
@@ -86,53 +82,69 @@ _CPDatePickerClockSeconds = 3;
_datePicker = aDatePicker;
_datePickerElements = [_datePicker datePickerElements];
_trackingHand = NO;
_isEnabled = YES;
// 1. Initialize AM/PM Label
_PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_PMAMTextField setAlignment:CPCenterTextAlignment];
[_PMAMTextField setVerticalAlignment:CPCenterVerticalTextAlignment];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[self addSubview:_PMAMTextField];
// 2. Initialize Number Labels (1 to 12)
_hourLabels = [CPArray array];
var middleHandSize = [_datePicker valueForThemeAttribute:@"middle-hand-size"],
minuteHandSize = [_datePicker valueForThemeAttribute:@"minute-hand-size"],
hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"],
secondHandSize = [_datePicker valueForThemeAttribute:@"second-hand-size"];
for (var i = 1; i <= 12; i++)
{
var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[label setStringValue:String(i)];
[label setAlignment:CPCenterTextAlignment];
[label setVerticalAlignment:CPCenterVerticalTextAlignment];
[self addSubview:label];
[_hourLabels addObject:label];
}
// We use layer to make the rotation possible
_hourHandLayer = [[HandLayer alloc] initWithSize:hourHandSize];
[_hourHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_hourHandLayer setAnchorPoint:CGPointMakeZero()];
[_hourHandLayer setPosition:CGPointMake(0.0, 0.0)];
// 3. Initialize Hand Views
_hourHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_minuteHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_secondHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_middleHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_minuteHandLayer = [[HandLayer alloc] initWithSize:minuteHandSize];
[_minuteHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_minuteHandLayer setAnchorPoint:CGPointMakeZero()];
[_minuteHandLayer setPosition:CGPointMake(0.0, 0.0)];
// Ensure images stretch accurately across the bounds of the image view
[_hourHandView setImageScaling:CPImageScaleAxesIndependently];
[_minuteHandView setImageScaling:CPImageScaleAxesIndependently];
[_secondHandView setImageScaling:CPImageScaleAxesIndependently];
[_middleHandView setImageScaling:CPImageScaleAxesIndependently];
_secondHandLayer = [[HandLayer alloc] initWithSize:secondHandSize];
[_secondHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_secondHandLayer setAnchorPoint:CGPointMakeZero()];
[_secondHandLayer setPosition:CGPointMake(0.0, 0.0)];
// 4. Add subviews in correct Z-Order
[self addSubview:_hourHandView];
[self addSubview:_minuteHandView];
_middleHandLayer = [[HandLayer alloc] initWithSize:middleHandSize];
[_middleHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_middleHandLayer setAnchorPoint:CGPointMakeZero()];
[_middleHandLayer setPosition:CGPointMake(0.0, 0.0)];
_rootLayer = [[HoursLayer alloc] init];
[self setWantsLayer:YES];
[self setLayer:_rootLayer];
[self _initHands];
[_rootLayer addSublayer:_hourHandLayer];
[_rootLayer addSublayer:_minuteHandLayer];
if ([_datePicker valueForThemeAttribute:@"clock-second-hand-over"])
{
[self addSubview:_middleHandView];
[self addSubview:_secondHandView];
[_rootLayer addSublayer:_middleHandLayer];
[_rootLayer addSublayer:_secondHandLayer];
}
else
{
[self addSubview:_secondHandView];
[self addSubview:_middleHandView];
[_rootLayer addSublayer:_secondHandLayer];
[_rootLayer addSublayer:_middleHandLayer];
}
[_rootLayer setDrawsHours:[_datePicker valueForThemeAttribute:@"clock-draws-hours"]];
[_rootLayer setNeedsDisplay];
}
return self;
@@ -140,58 +152,60 @@ _CPDatePickerClockSeconds = 3;
- (void)_initHands
{
// FIX: Using 'duplicate' prevents CPImageViews from stealing the DOM element from each other!
[_middleHandView setImage:[[_datePicker currentValueForThemeAttribute:@"middle-hand-image"] duplicate]];
[_hourHandView setImage:[[_datePicker currentValueForThemeAttribute:@"hour-hand-image"] duplicate]];
[_minuteHandView setImage:[[_datePicker currentValueForThemeAttribute:@"minute-hand-image"] duplicate]];
[_secondHandView setImage:[[_datePicker currentValueForThemeAttribute:@"second-hand-image"] duplicate]];
var middleHandImage = [_datePicker currentValueForThemeAttribute:@"middle-hand-image"],
hourHandImage = [_datePicker currentValueForThemeAttribute:@"hour-hand-image"],
minuteHandImage = [_datePicker currentValueForThemeAttribute:@"minute-hand-image"],
secondHandImage = [_datePicker currentValueForThemeAttribute:@"second-hand-image"];
var font = [_datePicker currentValueForThemeAttribute:@"clock-font"],
textColor = [_datePicker currentValueForThemeAttribute:@"clock-text-color"],
shadowCol = [_datePicker currentValueForThemeAttribute:@"clock-text-shadow-color"],
shadowOff = [_datePicker currentValueForThemeAttribute:@"clock-text-shadow-offset"];
// If hand images are true CPImage, we have to duplicate them to avoid
// the multiple delegates bug when multiple clocks are displayed
if (font)
[_PMAMTextField setFont:font];
if ([middleHandImage isKindOfClass:[CPImage class]])
middleHandImage = [middleHandImage duplicate];
if (textColor)
[_PMAMTextField setTextColor:textColor];
if ([hourHandImage isKindOfClass:[CPImage class]])
hourHandImage = [hourHandImage duplicate];
if (shadowCol)
[_PMAMTextField setTextShadowColor:shadowCol];
if ([minuteHandImage isKindOfClass:[CPImage class]])
minuteHandImage = [minuteHandImage duplicate];
if (shadowOff)
[_PMAMTextField setTextShadowOffset:shadowOff];
if ([secondHandImage isKindOfClass:[CPImage class]])
secondHandImage = [secondHandImage duplicate];
var hoursFont = [_datePicker currentValueForThemeAttribute:@"clock-hours-font"],
hoursColor = [_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"],
drawsHours = [_datePicker currentValueForThemeAttribute:@"clock-draws-hours"];
[_middleHandLayer setImage:middleHandImage];
[_hourHandLayer setImage:hourHandImage];
[_minuteHandLayer setImage:minuteHandImage];
[_secondHandLayer setImage:secondHandImage];
for (var i = 0; i < 12; i++)
{
var label = _hourLabels[i];
[label setHidden:!drawsHours];
if (drawsHours) {
if (hoursFont) [label setFont:hoursFont];
if (hoursColor) [label setTextColor:hoursColor];
[label sizeToFit];
}
}
[_hourHandLayer setNeedsDisplay];
[_middleHandLayer setNeedsDisplay];
[_secondHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_rootLayer setFont:[_datePicker currentValueForThemeAttribute:@"clock-hours-font"]];
[_rootLayer setTextColor:[_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"]];
[_rootLayer setRadius:[_datePicker currentValueForThemeAttribute:@"clock-hours-radius"]];
[_rootLayer setNeedsDisplay];
}
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
{
_datePickerElements = aDatePickerElements;
[_secondHandView setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
// Check if we have to display the hand second
// FIXME: Don't know why but next line will cause theme compilation to fail...
// Workaround: guarded with CPDOMAvailable (was: "if PLATFORM(DOM)")
if (!CPDOMAvailable) return;
[_secondHandLayer setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
}
// MARK: Layout methods
- (void)layoutSubviews
{
[self _initHands];
// While tracking a hand, we don't want the whole thing to be relayouted at each mouse movement
if (_trackingHand)
return;
@@ -206,65 +220,31 @@ _CPDatePickerClockSeconds = 3;
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
// Hours are expressed in 24 hours format, we need 12 hours format
_representedHours -= (_representedHourIsPM ? 12 : 0);
var bounds = [self bounds],
centerX = bounds.size.width / 2.0,
centerY = bounds.size.height / 2.0;
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
[_PMAMTextField sizeToFit];
[_PMAMTextField setFrameOrigin:CGPointMake(centerX - [_PMAMTextField frameSize].width / 2.0, centerY + 15.0)];
if ([_datePicker currentValueForThemeAttribute:@"clock-draws-hours"])
{
var radius = [_datePicker currentValueForThemeAttribute:@"clock-hours-radius"] || 50.0;
for (var i = 0, angle = 60.0; i < 12; i++, angle -= 30.0)
{
var label = _hourLabels[i],
size = [label frameSize],
x = centerX + radius * COS(angle * RADIANS) - size.width / 2.0,
y = centerY - radius * SIN(angle * RADIANS) - size.height / 2.0;
[label setFrameOrigin:CGPointMake(x, y)];
}
}
var centerView = function(view, size) {[view setFrame:CGRectMake(centerX - size.width / 2.0, centerY - size.height / 2.0, size.width, size.height)];
};
var hSize = [_datePicker currentValueForThemeAttribute:@"hour-hand-size"] || CGSizeMake(4, 64),
mSize = [_datePicker currentValueForThemeAttribute:@"minute-hand-size"] || CGSizeMake(4, 96),
sSize = [_datePicker currentValueForThemeAttribute:@"second-hand-size"] || CGSizeMake(4, 96),
midSize = [_datePicker currentValueForThemeAttribute:@"middle-hand-size"] || CGSizeMake(8, 8);
centerView(_hourHandView, hSize);
centerView(_minuteHandView, mSize);
centerView(_secondHandView, sSize);
centerView(_middleHandView, midSize);
[self _updateHands];
}
// Applies Pure CSS Transforms to rotate the elements natively in the browser
- (void)_rotateView:(CPView)view byAngle:(float)radians
{
#if PLATFORM(DOM)
var style = view._DOMElement.style;
style[CPBrowserStyleProperty("transformOrigin")] = "50% 50%";
style[CPBrowserStyleProperty("transform")] = "rotate(" + radians + "rad)";
#endif
// FIXME: Workaround. Seems that CALayer doesn't redraw without an event
[CALayer runLoopUpdateLayers];
}
- (void)_updateHands
{
_hourAngle = (360.0 * (_representedHours + _representedMinutes / 60.0) / 12.0) * RADIANS;
_minuteAngle = (360.0 * (_representedMinutes + _representedSeconds / 60.0) / 60.0) * RADIANS;
_secondAngle = (360.0 * _representedSeconds / 60.0) * RADIANS;
var bounds = [self bounds];
[self _rotateView:_hourHandView byAngle:_hourAngle];
[self _rotateView:_minuteHandView byAngle:_minuteAngle];
[self _rotateView:_secondHandView byAngle:_secondAngle];
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
[_PMAMTextField sizeToFit];
[_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)];
[_hourHandLayer setRotationRadians:(360 * (_representedHours + _representedMinutes / 60) / 12) * RADIANS];
[_minuteHandLayer setRotationRadians:(360 * (_representedMinutes + _representedSeconds / 60) / 60) * RADIANS];
[_secondHandLayer setRotationRadians:(360 * _representedSeconds / 60) * RADIANS];
[_hourHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_secondHandLayer setNeedsDisplay];
// [_middleHandLayer setNeedsDisplay];
}
// MARK: Accessors
@@ -278,40 +258,12 @@ _CPDatePickerClockSeconds = 3;
_isEnabled = shouldEnable;
[self _initHands];
[self setNeedsLayout];
}
// MARK: Mouse actions
// Since we rotate using Pure CSS, Cappuccino's `convertPoint:` doesn't know about it.
// So we use standard Trigonometry to perfectly hit-test the rotated hands!
- (BOOL)_hitTestHandWithSize:(CGSize)size angle:(float)radians atPoint:(CGPoint)aPoint
{
var bounds = [self bounds],
centerX = bounds.size.width / 2.0,
centerY = bounds.size.height / 2.0;
// 1. Move point to center
var tx = aPoint.x - centerX,
ty = aPoint.y - centerY;
// 2. Rotate point backwards by the angle of the hand
var cosA = COS(-radians),
sinA = SIN(-radians),
rx = tx * cosA - ty * sinA,
ry = tx * sinA + ty * cosA;
// 3. Test if point is within the unrotated hand's rectangle
// The visual needle is in the top half of the hand's box (y from -h/2 to 0)
var w2 = size.width / 2.0,
h2 = size.height / 2.0;
if (rx >= -w2 && rx <= w2 && ry >= -h2 && ry <= 0)
return YES;
return NO;
}
- (void)mouseDown:(CPEvent)anEvent
{
if (!_isEnabled)
@@ -319,29 +271,25 @@ _CPDatePickerClockSeconds = 3;
var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil];
var sSize = [_datePicker currentValueForThemeAttribute:@"second-hand-size"] || CGSizeMake(4, 96),
mSize = [_datePicker currentValueForThemeAttribute:@"minute-hand-size"] || CGSizeMake(4, 96),
hSize = [_datePicker currentValueForThemeAttribute:@"hour-hand-size"] || CGSizeMake(4, 64);
if (![_secondHandView isHidden] && [self _hitTestHandWithSize:sSize angle:_secondAngle atPoint:currentLocation])
if ([_secondHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandView = _secondHandView;
_currentHandLayer = _secondHandLayer;
_currentHand = _CPDatePickerClockSeconds;
_currentRepresentedValue = _representedSeconds;
_currentValueShift = 0;
_numberOfUnits = 60;
}
else if (![_minuteHandView isHidden] && [self _hitTestHandWithSize:mSize angle:_minuteAngle atPoint:currentLocation])
else if ([_minuteHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandView = _minuteHandView;
_currentHandLayer = _minuteHandLayer;
_currentHand = _CPDatePickerClockMinutes;
_currentRepresentedValue = _representedMinutes;
_currentValueShift = _representedSeconds / 60;
_numberOfUnits = 60;
}
else if (![_hourHandView isHidden] && [self _hitTestHandWithSize:hSize angle:_hourAngle atPoint:currentLocation])
else if ([_hourHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandView = _hourHandView;
_currentHandLayer = _hourHandLayer;
_currentHand = _CPDatePickerClockHours;
_currentRepresentedValue = _representedHours;
_currentValueShift = _representedMinutes / 60;
@@ -349,14 +297,14 @@ _CPDatePickerClockSeconds = 3;
}
else
{
_currentHandView = nil;
_currentHandLayer = nil;
_currentHand = CPNotFound;
_currentRepresentedValue = CPNotFound;
_currentValueShift = CPNotFound;
_numberOfUnits = CPNotFound;
}
if (_currentHandView)
if (_currentHandLayer)
[self trackMouse:anEvent];
}
@@ -368,12 +316,13 @@ _CPDatePickerClockSeconds = 3;
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
_trackingHand = YES;
return YES;
}
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
{
var dx = aPoint.x - _bounds.size.width / 2,
var dx = aPoint.x -_bounds.size.width / 2,
dy = _bounds.size.height / 2 - aPoint.y,
angle = (PI_2 - ATAN2(dy,dx) + PI2) % PI2,
value = ROUND(angle * _numberOfUnits / PI2 - _currentValueShift) % _numberOfUnits;
@@ -395,10 +344,14 @@ _CPDatePickerClockSeconds = 3;
_representedHourIsPM = !_representedHourIsPM;
if (movedForward && !_representedHourIsPM)
// Day++
dateValue.setDate(dateValue.getDate() + 1);
else if (movedBackward && _representedHourIsPM)
// Day--
dateValue.setDate(dateValue.getDate() - 1);
}
dateValue.setHours(value + (_representedHourIsPM ? 12 : 0));
break;
@@ -429,29 +382,34 @@ _CPDatePickerClockSeconds = 3;
break;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
// We have to adapt represented values
_representedHours = dateValue.getHours();
_representedMinutes = dateValue.getMinutes();
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
// Hours are expressed in 24 hours format, we need 12 hours format
_representedHours -= (_representedHourIsPM ? 12 : 0);
switch (_currentHand) {
case _CPDatePickerClockHours:
_currentRepresentedValue = _representedHours;
break;
case _CPDatePickerClockMinutes:
_currentRepresentedValue = _representedMinutes;
break;
case _CPDatePickerClockSeconds:
_currentRepresentedValue = _representedSeconds;
break;
@@ -469,3 +427,188 @@ _CPDatePickerClockSeconds = 3;
}
@end
// MARK: -
@implementation HandLayer : CALayer
{
CPImage _image;
HandImageLayer _imageLayer;
float _rotationRadians;
}
// MARK: Init methods
- (id)initWithSize:(CGSize)aSize
{
if (self = [super init])
{
_imageLayer = [HandImageLayer layer];
_rotationRadians = 0;
[_imageLayer setDelegate:self];
[_imageLayer setBounds:CGRectMake(0.0, 0.0, aSize.width, aSize.height)];
[self addSublayer:_imageLayer];
}
return self;
}
// MARK: Setter Getter methods
/*!
Set the bounds of the layer. The imageLayer will be at the center of this bounds.
*/
- (void)setBounds:(CGRect)aRect
{
[super setBounds:aRect];
[_imageLayer setPosition:CGPointMake(CGRectGetMidX(aRect), CGRectGetMidY(aRect))];
}
- (void)setImage:(CPImage)anImage
{
if (_image === anImage)
return;
if ([anImage isKindOfClass:[_CPCibCustomResource class]])
_image = [anImage imageFromCoder:nil];
else
_image = anImage;
[_imageLayer setNeedsDisplay];
}
- (void)setRotationRadians:(float)radians
{
if (_rotationRadians === radians)
return;
_rotationRadians = radians;
[_imageLayer setAffineTransform:CGAffineTransformScale(
CGAffineTransformMakeRotation(_rotationRadians),
1.0, 1.0)];
}
- (void)imageDidLoad:(CPImage)anImage
{
[_imageLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer)aLayer inContext:(CGContext)aContext
{
if ([_image loadStatus] != CPImageLoadStatusCompleted)
[_image setDelegate:self];
else
CGContextDrawImage(aContext, [aLayer bounds], _image);
}
- (BOOL)handIsHitAtPoint:(CGPoint)aPoint
{
return (!_isHidden && [_imageLayer hitTest:aPoint] === _imageLayer);
}
- (void)setNeedsDisplay
{
[super setNeedsDisplay];
[_imageLayer setNeedsDisplay];
}
@end
// MARK: -
@implementation HandImageLayer : CALayer
{
CGRect _handBounds;
}
// We have to adapt hitTest so it only takes the hand into account (that's the top half of the image layer)
// We are also sure there's no sublayers
- (CALayer)hitTest:(CGPoint)aPoint
{
if (_isHidden)
return nil;
var point = CGPointApplyAffineTransform(aPoint, _transformToLayer);
return CGRectContainsPoint(_handBounds, point) ? self : nil;
}
- (void)setBounds:(CGRect)aBounds
{
if (CGRectEqualToRect(_bounds, aBounds))
return;
_handBounds = CGRectMakeCopy(aBounds);
_handBounds.size.height = _handBounds.size.height / 2;
[super setBounds:aBounds];
}
@end
// MARK: -
@implementation HoursLayer : CALayer
{
BOOL _drawsHours;
CPFont _font @accessors(property=font);
CPColor _textColor @accessors(property=textColor);
float _radius @accessors(property=radius);
}
- (id)init
{
if (self = [super init])
{
_drawsHours = NO;
}
return self;
}
- (void)setDrawsHours:(BOOL)shouldDrawHours
{
shouldDrawHours = !!shouldDrawHours;
if (_drawsHours === shouldDrawHours)
return;
_drawsHours = shouldDrawHours;
[self setNeedsDisplay];
}
- (void)drawInContext:(CGContext)aContext
{
[super drawInContext:aContext];
if (_drawsHours)
{
var bounds = [self bounds],
centerX = bounds.size.width / 2,
centerY = bounds.size.height / 2;
CGContextSelectFont(aContext, _font);
CGContextSetFillColor(aContext, _textColor);
aContext.textBaseline = @"middle";
aContext.textAlign = @"center";
for (var i = 1, angle = 60.0, x, y; i < 13; i++, angle -= 30.0)
{
x = centerX + _radius * COS(angle * RADIANS);
y = centerY - _radius * SIN(angle * RADIANS);
aContext.fillText(i, x, y);
}
}
}
@end
@@ -20,6 +20,7 @@
*/
@import "CPTextField.j"
@import "CPCompatibility.j"
@class CPDatePicker
@class _CPDatePickerElementView
@@ -471,13 +472,15 @@ CPAMPMDateType = 6;
newDateValue.setSeconds(newDateValue.getSeconds() + secondsFromGMT - secondsFromGMTTimeZone);
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
[_datePicker _setDateValue:newDateValue timeInterval:[_datePicker timeInterval]];
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
}
@@ -21,6 +21,7 @@
@import "CPControl.j"
@import "_CPDatePickerElementTextField.j"
@import "CPCompatibility.j"
@class CPDatePicker
@@ -403,13 +404,15 @@
dateValue.setHours(dateValue.getHours() - 12);
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
}
+17 -12
View File
@@ -23,6 +23,7 @@
@import "CPControl.j"
@import "_CPDatePickerDayView.j"
@import "CPCompatibility.j"
@class CPDatePicker
@@ -457,9 +458,10 @@
_indexDayTile = -1;
_eventDragged = nil;
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
// Check if we have to change or not the month of the component
if ([dayTile date].getMonth() == _date.getMonth())
@@ -514,9 +516,10 @@
[_delegate _displayNextMonth];
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
}
/*! Mouse dragged event
@@ -534,9 +537,10 @@
_indexDayTile = [self indexOfTileForEvent:anEvent];
_eventDragged = anEvent;
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
if ([_datePicker datePickerMode] == CPSingleDateMode)
{
@@ -580,9 +584,10 @@
}
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
}
- (void)mouseUp:(CPEvent)anEvent
+7 -5
View File
@@ -34,6 +34,7 @@
@class _CPDatePickerElementView
@import "_CPDatePickerElementView.j"
@import "CPCompatibility.j"
@global CPSingleDateMode
@global CPRangeDateMode
@@ -89,17 +90,18 @@
[self setNeedsLayout];
[self setNeedsDisplay:YES];
#if PLATFORM(DOM)
if ([_datePicker currentValueForThemeAttribute:@"uses-focus-ring"])
{
// As with overflow:hidden, views are clipping their content, in order
// to show a focus ring (which is external to a view), we need to let
// content extend outside the view.
_datePicker._DOMElement.style.overflow = "visible";
_DOMElement.style.overflow = "visible";
_datePickerElementView._DOMElement.style.overflow = "visible";
if (CPDOMAvailable)
{
_datePicker._DOMElement.style.overflow = "visible";
_DOMElement.style.overflow = "visible";
_datePickerElementView._DOMElement.style.overflow = "visible";
}
}
#endif
}
+4 -1
View File
@@ -33,7 +33,10 @@
@global CPApp
#define DRAGGING_WINDOW(anObject) ([anObject isKindOfClass:[CPWindow class]] ? anObject : [anObject window])
function DRAGGING_WINDOW(anObject)
{
return ([anObject isKindOfClass:[CPWindow class]] ? anObject : [anObject window]);
}
var CPDragServerPreviousEvent = nil,
CPDragServerPeriodicUpdateInterval = 0.05;
-6
View File
@@ -82,10 +82,8 @@ var _CPEventPeriodicEventPeriod = 0,
float _scrollingDeltaY;
BOOL _hasPreciseScrollingDeltas;
#if PLATFORM(DOM)
BOOL _suppressCappuccinoCut;
BOOL _suppressCappuccinoPaste;
#endif
CPTrackingArea _trackingArea;
}
@@ -609,11 +607,7 @@ var _CPEventPeriodicEventPeriod = 0,
*/
- (BOOL)_platformIsEffectingCutOrPaste
{
#if PLATFORM(DOM)
return _suppressCappuccinoCut || _suppressCappuccinoPaste;
#else
return NO;
#endif
}
+33 -7
View File
@@ -51,14 +51,40 @@ var _CPFontCache = {},
_CPFontSystemFontFaceSpecified = NO;
#define _CPRealFontSize(aSize) (aSize <= 0 ? _CPFontSystemFontSize : aSize)
#define _CPFontNormalizedNames(aName) _CPFontNormalizedNameArray(aName).join(", ")
#define _CPCachedFont(aName, aSize, isBold, isItalic) _CPFontCache[_CPFontCreateCSSString(_CPFontNormalizedNames(aName), aSize, isBold, isItalic)]
#define _CPUserFont(aName, aSize, isBold, isItalic) _CPCachedFont(aName, aSize, isBold, isItalic) || [[CPFont alloc] _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO]
function _CPRealFontSize(aSize)
{
return (aSize <= 0 ? _CPFontSystemFontSize : aSize);
}
#define _CPSystemFontCacheKey(aSize, isBold) (String(aSize) + (isBold ? "b" : ""))
#define _CPCachedSystemFont(aSize, isBold) _CPSystemFontCache[_CPSystemFontCacheKey(aSize, isBold)]
#define _CPSystemFont(aSize, isBold) (_CPCachedSystemFont(aSize, isBold) || [[CPFont alloc] _initWithName:_CPFontSystemFacePlaceholder size:aSize bold:isBold italic:NO system:YES])
function _CPFontNormalizedNames(aName)
{
return _CPFontNormalizedNameArray(aName).join(", ");
}
function _CPCachedFont(aName, aSize, isBold, isItalic)
{
return _CPFontCache[_CPFontCreateCSSString(_CPFontNormalizedNames(aName), aSize, isBold, isItalic)];
}
function _CPUserFont(aName, aSize, isBold, isItalic)
{
return _CPCachedFont(aName, aSize, isBold, isItalic) || [[CPFont alloc] _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO];
}
function _CPSystemFontCacheKey(aSize, isBold)
{
return (String(aSize) + (isBold ? "b" : ""));
}
function _CPCachedSystemFont(aSize, isBold)
{
return _CPSystemFontCache[_CPSystemFontCacheKey(aSize, isBold)];
}
function _CPSystemFont(aSize, isBold)
{
return (_CPCachedSystemFont(aSize, isBold) || [[CPFont alloc] _initWithName:_CPFontSystemFacePlaceholder size:aSize bold:isBold italic:NO system:YES]);
}
/*!
@ingroup appkit
+24 -20
View File
@@ -25,6 +25,7 @@
@import "CPControl.j"
@import "CPFont.j"
@import "CPFontDescriptor.j"
@import "CPCompatibility.j"
@global CPApp
@class CPFontPanel
@@ -132,28 +133,31 @@ CPRemoveTraitFontAction = 7;
{
_availableFonts = [];
#if PLATFORM(DOM)
_CPFontDetectSpan = document.createElement("span");
_CPFontDetectSpan.fontSize = "24px";
_CPFontDetectSpan.appendChild(document.createTextNode("mmmmmmmmmml"));
var div = document.createElement("div");
div.style.position = "absolute";
div.style.top = "-1000px";
div.appendChild(_CPFontDetectSpan);
document.getElementsByTagName("body")[0].appendChild(div);
_CPFontDetectReferenceFonts = _CPFontDetectPickTwoDifferentFonts(["monospace", "serif", "sans-serif", "cursive"]);
for (var i = 0; i < _CPFontDetectAllFonts.length; i++)
if (CPDOMAvailable)
{
var available = _CPFontDetectFontAvailable(_CPFontDetectAllFonts[i]);
if (available)
_availableFonts.push(_CPFontDetectAllFonts[i]);
_CPFontDetectSpan = document.createElement("span");
_CPFontDetectSpan.fontSize = "24px";
_CPFontDetectSpan.appendChild(document.createTextNode("mmmmmmmmmml"));
var div = document.createElement("div");
div.style.position = "absolute";
div.style.top = "-1000px";
div.appendChild(_CPFontDetectSpan);
document.getElementsByTagName("body")[0].appendChild(div);
_CPFontDetectReferenceFonts = _CPFontDetectPickTwoDifferentFonts(["monospace", "serif", "sans-serif", "cursive"]);
for (var i = 0; i < _CPFontDetectAllFonts.length; i++)
{
var available = _CPFontDetectFontAvailable(_CPFontDetectAllFonts[i]);
if (available)
_availableFonts.push(_CPFontDetectAllFonts[i]);
}
}
else
{
// If there's no font detection, just assume all fonts are available.
_availableFonts = _CPFontDetectAllFonts;
}
#else
// If there's no font detection, just assume all fonts are available.
_availableFonts = _CPFontDetectAllFonts;
#endif
}
return _availableFonts;
}
+6 -6
View File
@@ -239,7 +239,8 @@ function CPAppKitImage(aFilename, aSize)
*/
- (CPData)data
{
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
var dataURL;
if ([_filename hasPrefix:@"data:image"])
@@ -261,7 +262,6 @@ function CPAppKitImage(aFilename, aSize)
var base64 = dataURL.replace(/^data:image\/png;base64,/, "");
return [CPData dataWithBase64:base64];
#endif
}
/*!
@@ -384,7 +384,8 @@ function CPAppKitImage(aFilename, aSize)
_loadStatus = CPImageLoadStatusLoading;
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
_image = new Image();
var isSynchronous = YES;
@@ -431,7 +432,6 @@ function CPAppKitImage(aFilename, aSize)
// onload and friends may fire after this point but BEFORE the end of the run loop,
// crazy, I know. So don't set isSynchronous here, rather wait a bit longer.
window.setTimeout(function() { isSynchronous = NO; }, 0);
#endif
}
- (BOOL)isSingleImage
@@ -641,7 +641,8 @@ function CPAppKitImage(aFilename, aSize)
- (DOMElement)applyCSSImageForView:(CPView)aView onDOMElement:(DOMElement)aDOMElement styleNode:(DOMElement)aStyleNode previousState:(CPArrayRef)aPreviousStateRef
{
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
// First, restore previous CSS styling before applying the new one
var aPreviousState = @deref(aPreviousStateRef);
@@ -724,7 +725,6 @@ function CPAppKitImage(aFilename, aSize)
@deref(aPreviousStateRef) = aPreviousState;
return aStyleNode;
#endif
}
- (BOOL)_shouldBeResized
+1
View File
@@ -88,6 +88,7 @@ CPStandardKeyBindings[@"~" + CPDeleteFunctionKey] = @"deleteWordForward:";
CPStandardKeyBindings[CPTabCharacter] = @"insertTab:";
CPStandardKeyBindings[@"~" + CPTabCharacter] = @"insertTabIgnoringFieldEditor:";
CPStandardKeyBindings[@"^" + CPTabCharacter] = @"selectNextKeyView:";
CPStandardKeyBindings[@"$" + CPTabCharacter] = @"insertBacktab:";
CPStandardKeyBindings[CPBackTabCharacter] = @"insertBacktab:";
CPStandardKeyBindings[@"^" + CPBackTabCharacter] = @"selectPreviousKeyView:";
+12 -7
View File
@@ -28,6 +28,7 @@
@import "CPKeyValueBinding.j"
@import "CPMenuItem.j"
@import "CALayer.j"
@import "CPCompatibility.j"
@global CPApp
@@ -51,7 +52,8 @@ CPMenuDidEndTrackingNotification = @"CPMenuDidEndTrackingNotification";
var _CPMenuBarVisible = NO,
_CPMenuBarTitle = @"",
_CPMenuBarAttributes = nil,
_CPMenuBarSharedWindow = nil;
_CPMenuBarSharedWindow = nil,
_CPMenuBarImage = nil;
/*!
@ingroup appkit
@@ -136,9 +138,10 @@ var _CPMenuBarVisible = NO,
[_CPMenuBarSharedWindow orderOut:self];
// FIXME: There must be a better way to do this.
#if PLATFORM(DOM)
[[CPPlatformWindow primaryPlatformWindow] resizeEvent:nil];
#endif
if (CPDOMAvailable)
{
[[CPPlatformWindow primaryPlatformWindow] resizeEvent:nil];
}
}
+ (void)setMenuBarTitle:(CPString)aTitle
@@ -1119,14 +1122,14 @@ var _CPMenuBarVisible = NO,
anEvent._isKeyEquivalent = YES; // prevent the menu keystroke from beeing inserted into textview
[self performActionForItemAtIndex:index];
#if PLATFORM(DOM)
// we are done with this event in cappuccino space. do not let the browser do something weird additionally (e.g. command-o).
// but we must not stop copy/paste events as these can only be handled by the browser at this time even if they are in our menu
// (until we move to CPTextView as the fieleditor)
if (characters != "c" && characters != "x" && characters != "v")
if (CPDOMAvailable && characters != "c" && characters != "x" && characters != "v")
{
_CPDOMEventStop(anEvent._DOMEvent);
#endif
}
}
else
{
@@ -1433,3 +1436,5 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
@import "_CPMenuBarWindow.j"
@import "_CPMenuWindow.j"
@global _CPDOMEventStop
+11 -9
View File
@@ -22,6 +22,7 @@
@import "CPPanel.j"
@import "_CPMenuManager.j"
@import "CPCompatibility.j"
@class _CPMenuView
@class CPMenu
@@ -100,16 +101,17 @@
- (void)setTitle:(CPString)aTitle
{
#if PLATFORM(DOM)
var bundleName = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPBundleName"];
if (CPDOMAvailable)
{
var bundleName = [CPApp _applicationName];
if (![bundleName length])
document.title = aTitle;
else if ([aTitle length])
document.title = aTitle + @" - " + bundleName;
else
document.title = bundleName;
#endif
if (![bundleName length])
document.title = aTitle;
else if ([aTitle length])
document.title = aTitle + @" - " + bundleName;
else
document.title = bundleName;
}
[_titleField setStringValue:aTitle];
[_titleField sizeToFit];
+26 -17
View File
@@ -895,6 +895,17 @@ CPControlKeyMask
return [super description] + @" target: " + [self target] + @" action: " + CPStringFromSelector([self action]);
}
- (id)_decodeValueForKey:(CPString)aKey defaultValue:(id)aDefaultValue coder:(CPCoder)aCoder
{
return [aCoder containsValueForKey:aKey] ? [aCoder decodeObjectForKey:aKey] : aDefaultValue;
}
- (void)_encodeValue:(id)aValue forKey:(CPString)aKey ifNotEqualTo:(id)aDefaultValue coder:(CPCoder)aCoder
{
if (aValue !== aDefaultValue)
[aCoder encodeObject:aValue forKey:aKey];
}
@end
// MARK: -
@@ -940,9 +951,6 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
CPMenuItemRepresentedObjectKey = @"CPMenuItemRepresentedObjectKey",
CPMenuItemViewKey = @"CPMenuItemViewKey";
#define DEFAULT_VALUE(aKey, aDefaultValue) [aCoder containsValueForKey:(aKey)] ? [aCoder decodeObjectForKey:(aKey)] : (aDefaultValue)
#define ENCODE_IFNOT(aKey, aValue, aDefaultValue) if ((aValue) !== (aDefaultValue)) [aCoder encodeObject:(aValue) forKey:(aKey)];
@implementation CPMenuItem (CPCoding)
/*!
Initializes the menu item from a coder.
@@ -965,7 +973,7 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
_target = [aCoder decodeObjectForKey:CPMenuItemTargetKey];
_action = [aCoder decodeObjectForKey:CPMenuItemActionKey];
_isEnabled = DEFAULT_VALUE(CPMenuItemIsEnabledKey, YES);
_isEnabled = [self _decodeValueForKey:CPMenuItemIsEnabledKey defaultValue:YES coder:aCoder];
_isHidden = [aCoder decodeBoolForKey:CPMenuItemIsHiddenKey];
_tag = [aCoder decodeIntForKey:CPMenuItemTagKey];
_state = [aCoder decodeIntForKey:CPMenuItemStateKey];
@@ -1012,20 +1020,20 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
[aCoder encodeObject:_target forKey:CPMenuItemTargetKey];
[aCoder encodeObject:_action forKey:CPMenuItemActionKey];
ENCODE_IFNOT(CPMenuItemIsEnabledKey, _isEnabled, YES);
ENCODE_IFNOT(CPMenuItemIsHiddenKey, _isHidden, NO);
[self _encodeValue:_isEnabled forKey:CPMenuItemIsEnabledKey ifNotEqualTo:YES coder:aCoder]
[self _encodeValue:_isHidden forKey:CPMenuItemIsHiddenKey ifNotEqualTo:NO coder:aCoder]
ENCODE_IFNOT(CPMenuItemTagKey, _tag, 0);
ENCODE_IFNOT(CPMenuItemStateKey, _state, CPOffState);
[self _encodeValue:_tag forKey:CPMenuItemTagKey ifNotEqualTo:0 coder:aCoder]
[self _encodeValue:_state forKey:CPMenuItemStateKey ifNotEqualTo:CPOffState coder:aCoder]
ENCODE_IFNOT(CPMenuItemImageKey, _image, nil);
ENCODE_IFNOT(CPMenuItemAlternateImageKey, _alternateImage, nil);
ENCODE_IFNOT(CPMenuItemOnStateImageKey, _onStateImage, nil);
ENCODE_IFNOT(CPMenuItemOffStateImageKey, _offStateImage, nil);
ENCODE_IFNOT(CPMenuItemMixedStateImageKey, _mixedStateImage, nil);
[self _encodeValue:_image forKey:CPMenuItemImageKey ifNotEqualTo:nil coder:aCoder]
[self _encodeValue:_alternateImage forKey:CPMenuItemAlternateImageKey ifNotEqualTo:nil coder:aCoder]
[self _encodeValue:_onStateImage forKey:CPMenuItemOnStateImageKey ifNotEqualTo:nil coder:aCoder]
[self _encodeValue:_offStateImage forKey:CPMenuItemOffStateImageKey ifNotEqualTo:nil coder:aCoder]
[self _encodeValue:_mixedStateImage forKey:CPMenuItemMixedStateImageKey ifNotEqualTo:nil coder:aCoder]
ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil);
ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil);
[self _encodeValue:_submenu forKey:CPMenuItemSubmenuKey ifNotEqualTo:nil coder:aCoder]
[self _encodeValue:_menu forKey:CPMenuItemMenuKey ifNotEqualTo:nil coder:aCoder]
if (_keyEquivalent && _keyEquivalent.length)
[aCoder encodeObject:_keyEquivalent forKey:CPMenuItemKeyEquivalentKey];
@@ -1036,8 +1044,9 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
if (_indentationLevel > 0)
[aCoder encodeInt:_indentationLevel forKey:CPMenuItemIndentationLevelKey];
ENCODE_IFNOT(CPMenuItemRepresentedObjectKey, _representedObject, nil);
ENCODE_IFNOT(CPMenuItemViewKey, _view, nil);
[self _encodeValue:_representedObject forKey:CPMenuItemRepresentedObjectKey ifNotEqualTo:nil coder:aCoder]
[self _encodeValue:_view forKey:CPMenuItemViewKey ifNotEqualTo:nil coder:aCoder]
}
@end
+8 -2
View File
@@ -87,9 +87,15 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
CPOutlineViewCoalesceSelectionNotificationStateOn = 1,
CPOutlineViewCoalesceSelectionNotificationStateDid = 2;
#define SELECTION_SHOULD_CHANGE(anOutlineView) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_selectionShouldChangeInOutlineView_) || [(anOutlineView)._outlineViewDelegate selectionShouldChangeInOutlineView:(anOutlineView)])
function SELECTION_SHOULD_CHANGE(anOutlineView)
{
return (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_selectionShouldChangeInOutlineView_) || [(anOutlineView)._outlineViewDelegate selectionShouldChangeInOutlineView:(anOutlineView)]);
}
#define SHOULD_SELECT_ITEM(anOutlineView, anItem) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_) || [(anOutlineView)._outlineViewDelegate outlineView:(anOutlineView) shouldSelectItem:(anItem)])
function SHOULD_SELECT_ITEM(anOutlineView, anItem)
{
return (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_) || [(anOutlineView)._outlineViewDelegate outlineView:(anOutlineView) shouldSelectItem:(anItem)]);
}
@protocol CPOutlineViewDelegate <CPObject>
+1 -3
View File
@@ -45,6 +45,7 @@ CPURLPboardType = @"CPURLPboardType";
CPImagesPboardType = @"CPImagesPboardType";
CPVideosPboardType = @"CPVideosPboardType";
CPRTFPboardType = @"CPRTFPboardType";
CPRulerPboardType = @"CPRulerPboardType";
_CPSmartPboardType = @"_CPSmartPboardType";
_CPASPboardType = @"_CPASPboardType";
@@ -323,8 +324,6 @@ var CPPasteboards = nil;
@end
#if PLATFORM(DOM)
var DOMDataTransferPasteboard = nil;
@implementation _CPDOMDataTransferPasteboard : CPPasteboard
@@ -388,4 +387,3 @@ var DOMDataTransferPasteboard = nil;
@end
#endif
+10
View File
@@ -290,6 +290,16 @@ CPDeleteForwardKeyCode = 46;
{
}
/*
@ignore
Returns YES if the receiver handles the Tab key itself, as an editable text view does by
inserting a tab character. Otherwise the window uses Tab to move to the next key view.
*/
- (BOOL)_wantsTabKey
{
return NO;
}
- (void)insertBackTab:(id)sender
{
}
+6 -4
View File
@@ -33,6 +33,7 @@
@import "_CPRuleEditorViewSliceRow.j"
@import "_CPRuleEditorLocalizer.j"
@import "CPCompatibility.j"
@class CPCompoundPredicate
@class CPComparisonPredicate
@@ -2260,10 +2261,11 @@ TODO: implement
firstSlice = [_slices objectAtIndex:firstIndex],
dragview = [[CPView alloc] initWithFrame:[firstSlice frame]];
#if PLATFORM(DOM)
var html = firstSlice._DOMElement.innerHTML;
dragview._DOMElement.innerHTML = [html copy];
#endif
if (CPDOMAvailable)
{
var html = firstSlice._DOMElement.innerHTML;
dragview._DOMElement.innerHTML = [html copy];
}
[dragview setBackgroundColor:[firstSlice backgroundColor]];
[dragview setAlphaValue:0.7];
+3 -4
View File
@@ -42,11 +42,10 @@
*/
- (CGRect)visibleFrame
{
#if PLATFORM(DOM)
return CGRectMake(window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight);
#else
if (CPDOMAvailable)
return CGRectMake(window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight);
return CGRectMakeZero();
#endif
}
@end
+29 -22
View File
@@ -30,11 +30,11 @@
@import "CPScroller.j"
@import "CPView.j"
@import "CPRulerView.j"
@import "CPCompatibility.j"
@class CPTableView
@class CPRulerView
#define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden])
@protocol CPScrollViewDelegate <CPObject>
@@ -49,7 +49,8 @@
/*! @ignore */
var _isBrowserUsingOverlayScrollers = function()
{
#if PLATFORM(DOM)
if (!CPDOMAvailable) return NO;
/*
Even if the system supports overlay (Lion) scrollers,
the browser (e.g. FireFox *cough*) may not.
@@ -85,9 +86,6 @@ var _isBrowserUsingOverlayScrollers = function()
document.body.removeChild(outer);
return usingOverlayScrollers;
#else
return NO;
#endif
};
var TIMER_INTERVAL = 0.2,
@@ -223,7 +221,7 @@ var _CPScrollViews;
bounds.size.height -= [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
if (verticalScrollerClass)
bounds.size.width -= [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
bounds.size.width -= [verticalScrollerClass scrollerWidthInStyle:scrollerStyle];
return bounds.size;
}
@@ -252,11 +250,11 @@ var _CPScrollViews;
heightInset = contentSize.height - bounds.size.height,
frameSize = CGSizeMake(contentSize.width + widthInset, contentSize.height + heightInset);
if (hFlag)
if (horizontalScrollerClass)
frameSize.height += [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
if (vFlag)
frameSize.width += [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
if (verticalScrollerClass)
frameSize.width += [verticalScrollerClass scrollerWidthInStyle:scrollerStyle];
return frameSize;
}
@@ -917,7 +915,10 @@ Notifies the delegate when the scroll view has finished scrolling.
_horizontalRuler = aRulerView;
if (_horizontalRuler)
{
[_horizontalRuler setScrollView:self];
[self addSubview:_horizontalRuler];
}
[self tile];
}
@@ -936,7 +937,10 @@ Notifies the delegate when the scroll view has finished scrolling.
_verticalRuler = aRulerView;
if (_verticalRuler)
{
[_verticalRuler setScrollView:self];
[self addSubview:_verticalRuler];
}
[self tile];
}
@@ -1037,7 +1041,7 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_cornerView)
{
[_cornerView setHidden:!SHOULD_SHOW_CORNER_VIEW()];
[_cornerView setHidden:![self _shouldShowCornerView]];
[self addSubview:_cornerView];
}
}
@@ -1080,7 +1084,7 @@ Notifies the delegate when the scroll view has finished scrolling.
frame.size.height = CGRectGetHeight([headerView frame]);
if (SHOULD_SHOW_CORNER_VIEW())
if ([self _shouldShowCornerView])
frame.size.width -= CGRectGetWidth([self _cornerViewFrame]);
return frame;
@@ -1319,8 +1323,8 @@ Notifies the delegate when the scroll view has finished scrolling.
var showHorizontalRuler = _rulersVisible && _hasHorizontalRuler && _horizontalRuler,
showVerticalRuler = _rulersVisible && _hasVerticalRuler && _verticalRuler;
var horizRulerThickness = showHorizontalRuler ? ([_horizontalRuler respondsToSelector:@selector(ruleThickness)] ? [_horizontalRuler ruleThickness] : 16.0) : 0.0,
vertRulerThickness = showVerticalRuler ? ([_verticalRuler respondsToSelector:@selector(ruleThickness)] ? [_verticalRuler ruleThickness] : 24.0) : 0.0;
var horizRulerThickness = showHorizontalRuler ? [_horizontalRuler requiredThickness] : 0.0,
vertRulerThickness = showVerticalRuler ? [_verticalRuler requiredThickness] : 0.0;
if (showHorizontalRuler)
{
@@ -1443,7 +1447,7 @@ Notifies the delegate when the scroll view has finished scrolling.
[_headerClipView setFrame:[self _headerClipViewFrame]];
[[_headerClipView documentView] setNeedsDisplay:YES];
if (SHOULD_SHOW_CORNER_VIEW())
if ([self _shouldShowCornerView])
{
[_cornerView setFrame:[self _cornerViewFrame]];
[_cornerView setHidden:NO];
@@ -1469,10 +1473,7 @@ Notifies the delegate when the scroll view has finished scrolling.
horizRulerThickness
)];
if ([_horizontalRuler respondsToSelector:@selector(updateRuler)])
[_horizontalRuler updateRuler];
else
[_horizontalRuler setNeedsDisplay:YES];
[_horizontalRuler setNeedsDisplay:YES];
}
if (showVerticalRuler)
@@ -1484,10 +1485,7 @@ Notifies the delegate when the scroll view has finished scrolling.
CGRectGetHeight(contentFrame)
)];
if ([_verticalRuler respondsToSelector:@selector(updateRuler)])
[_verticalRuler updateRuler];
else
[_verticalRuler setNeedsDisplay:YES];
[_verticalRuler setNeedsDisplay:YES];
}
--_recursionCount;
@@ -1763,6 +1761,10 @@ Notifies the delegate when the scroll view has finished scrolling.
[_headerClipView scrollToPoint:CGPointMake(contentBounds.origin.x, 0)];
}
- (BOOL)_shouldShowCornerView
{
return (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden]);
}
@end
// MARK: -
@@ -1907,6 +1909,10 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
_verticalRuler = [aCoder decodeObjectForKey:CPScrollViewVRulerKey];
_horizontalRuler = [aCoder decodeObjectForKey:CPScrollViewHRulerKey];
// A ruler does not archive its scroll view, which owns it.
[_verticalRuler setScrollView:self];
[_horizontalRuler setScrollView:self];
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
@@ -1967,4 +1973,5 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
[aCoder encodeObject:_horizontalRuler forKey:CPScrollViewHRulerKey];
}
@end
+1 -3
View File
@@ -1002,15 +1002,13 @@ var AFFINITY = 5;
}
}
#if PLATFORM(DOM)
// Don't do this in nib2cib !..
if (_allowsTickMarkValuesOnly)
if (CPDOMAvailable && _allowsTickMarkValuesOnly)
{
[self setObjectValue:[self closestTickMarkValueToValue:[self doubleValue]]];
_currentTickMarkSegment = _closestTickMarkIndex;
[self sendAction:[self action] to:[self target]];
}
#endif
}
/*! @ignore */
+45 -34
View File
@@ -62,28 +62,6 @@ var CPSplitViewDelegate_splitView_canCollapseSubview_
CPSplitViewDelegate_splitViewDidResizeSubviews_ = 1 << 9,
CPSplitViewDelegate_splitViewWillResizeSubviews_ = 1 << 10;
#define SPLIT_VIEW_MAYBE_POST_WILL_RESIZE() \
if ((_suppressResizeNotificationsMask & DidPostWillResizeNotification) === 0) \
{ \
[self _postNotificationWillResize]; \
_suppressResizeNotificationsMask |= DidPostWillResizeNotification; \
}
#define SPLIT_VIEW_MAYBE_POST_DID_RESIZE() \
if ((_suppressResizeNotificationsMask & ShouldSuppressResizeNotifications) !== 0) \
_suppressResizeNotificationsMask |= DidSuppressResizeNotification; \
else \
[self _postNotificationDidResize];
#define SPLIT_VIEW_DID_SUPPRESS_RESIZE_NOTIFICATION() \
((_suppressResizeNotificationsMask & DidSuppressResizeNotification) !== 0)
#define SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(shouldSuppress) \
if (shouldSuppress) \
_suppressResizeNotificationsMask |= ShouldSuppressResizeNotifications; \
else \
_suppressResizeNotificationsMask = 0;
CPSplitViewDidResizeSubviewsNotification = @"CPSplitViewDidResizeSubviewsNotification";
CPSplitViewWillResizeSubviewsNotification = @"CPSplitViewWillResizeSubviewsNotification";
@@ -733,7 +711,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
*/
- (void)adjustSubviews
{
SPLIT_VIEW_MAYBE_POST_WILL_RESIZE();
[self _maybePostWillResizeNotification];
[self _postNotificationWillResize];
// 2 possibilities : we have, or not, enough flexible space to accomodate fixed size subviews
@@ -815,7 +793,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[self _distribute:remainingSpace amoung:fixedCount onFlexible:NO fromIndex:0 toIndex:nbSubviews-1];
}
SPLIT_VIEW_MAYBE_POST_DID_RESIZE();
[self _maybePostDidResizeNotification];
[self layoutSubviews];
}
@@ -1217,7 +1195,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
// autosave.
_shouldRestoreFromAutosaveUnlessFrameSize = nil;
SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(YES);
[self _setResizeNotificationsSuppressed:YES];
var realPosition = [self _realPositionForPosition:position ofDividerAtIndex:dividerIndex],
viewA = _arrangedSubviews[dividerIndex],
@@ -1234,9 +1212,9 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
if (preSize !== frameA.size[_sizeComponent])
{
SPLIT_VIEW_MAYBE_POST_WILL_RESIZE();
[self _maybePostWillResizeNotification];
[_arrangedSubviews[dividerIndex] setFrame:frameA];
SPLIT_VIEW_MAYBE_POST_DID_RESIZE();
[self _maybePostDidResizeNotification];
}
preSize = frameB.size[_sizeComponent];
@@ -1251,9 +1229,9 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
if (preSize !== frameB.size[_sizeComponent] || preOrigin !== frameB.origin[_originComponent])
{
SPLIT_VIEW_MAYBE_POST_WILL_RESIZE();
[self _maybePostWillResizeNotification];
[_arrangedSubviews[dividerIndex + 1] setFrame:frameB];
SPLIT_VIEW_MAYBE_POST_DID_RESIZE();
[self _maybePostDidResizeNotification];
}
if (preCollapsePosition)
@@ -1266,10 +1244,10 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[self _updateRatios];
if (SPLIT_VIEW_DID_SUPPRESS_RESIZE_NOTIFICATION())
if ([self _didSuppressResizeNotifications])
[self _postNotificationDidResize];
SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(NO);
[self _setResizeNotificationsSuppressed:NO];
}
/*!
@@ -1291,8 +1269,11 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
- (void)setButtonBar:(CPButtonBar)aButtonBar forDividerAtIndex:(CPUInteger)dividerIndex
{
// For compatibility with previous behavior
[aButtonBar setAutomaticResizeControl:YES];
[self attachButtonBar:aButtonBar];
if ([aButtonBar respondsToSelector:@selector(setAutomaticResizeControl:)])
{
[aButtonBar setAutomaticResizeControl:YES];
[self attachButtonBar:aButtonBar];
}
}
- (void)attachButtonBar:(CPButtonBar)aButtonBar
@@ -1489,6 +1470,36 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
return @"CPSplitView Subview Precollapse Positions " + theAutosaveName;
}
- (void)_maybePostWillResizeNotification
{
if ((_suppressResizeNotificationsMask & DidPostWillResizeNotification) === 0)
{
[self _postNotificationWillResize];
_suppressResizeNotificationsMask |= DidPostWillResizeNotification;
}
}
- (void)_maybePostDidResizeNotification
{
if ((_suppressResizeNotificationsMask & ShouldSuppressResizeNotifications) !== 0)
_suppressResizeNotificationsMask |= DidSuppressResizeNotification;
else
[self _postNotificationDidResize];
}
- (BOOL)_didSuppressResizeNotifications
{
return ((_suppressResizeNotificationsMask & DidSuppressResizeNotification) !== 0);
}
- (void)_setResizeNotificationsSuppressed:(BOOL)shouldSuppress
{
if (shouldSuppress)
_suppressResizeNotificationsMask |= ShouldSuppressResizeNotifications;
else
_suppressResizeNotificationsMask = 0;
}
@end
// MARK: -
@@ -1902,5 +1913,5 @@ var CPSplitViewDelegateKey = @"CPSplitViewDelegateKey",
[self setDividerStyle:(shouldBePaneSplitter ? CPSplitViewDividerStylePaneSplitter : CPSplitViewDividerStyleThin)];
}
@end
@end
+31 -29
View File
@@ -62,7 +62,8 @@ CPCanvasStringSizingIsFunctional = NO;
if ([self class] != [CPString class])
return;
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
{
if (!CPStringSizeMeasuringContext)
@@ -73,46 +74,47 @@ CPCanvasStringSizingIsFunctional = NO;
CPCanvasStringSizingIsFunctional = NO;
}
#endif
}
- (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
{
var size;
#if PLATFORM(DOM)
var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self];
if (sizeCacheForFont === undefined)
sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = [];
var cssString = [aFont cssString],
cacheKey = cssString + '_' + (aWidth ? aWidth : '0');
size = sizeCacheForFont[cacheKey];
if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey))
return CGSizeMakeCopy(size);
if (!CPCanvasStringSizingIsFunctional || aWidth)
size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
else
if (CPDOMAvailable)
{
if (CPPlatformHasBug(CPTextSizingAlwaysNeedsSetFontBug) || CPStringSizeMeasuringContext.font !== cssString)
CPStringSizeMeasuringContext.font = cssString;
var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self];
var fontHeight = CPStringSizeWithFontHeightCache[cssString];
if (sizeCacheForFont === undefined)
sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = [];
if (fontHeight === undefined)
fontHeight = CPStringSizeWithFontHeightCache[cssString] = [aFont defaultLineHeightForFont];
var cssString = [aFont cssString],
cacheKey = cssString + '_' + (aWidth ? aWidth : '0');
size = CGSizeMake(CPStringSizeMeasuringContext.measureText(self).width, fontHeight);
size = sizeCacheForFont[cacheKey];
if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey))
return CGSizeMakeCopy(size);
if (!CPCanvasStringSizingIsFunctional || aWidth)
size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
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);
sizeCacheForFont[cacheKey] = size;
#else
size = CGSizeMake(0, 0);
#endif
return CGSizeMakeCopy(size);
}
-2
View File
@@ -982,8 +982,6 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
@end
# pragma mark -
// This subclass of CPSegmentedControl implements specific behaviour needed by CPTabView, which
// differs in some ways from normal CPSegmentedControl habits :
//
+206 -209
View File
@@ -279,9 +279,10 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
// MARK: -
#if PLATFORM(DOM)
- (DOMElement)_inputElement
{
if (!CPDOMAvailable) return nil;
if (!CPTextFieldDOMTextAreaElement)
{
CPTextFieldDOMTextAreaElement = document.createElement("textarea");
@@ -384,7 +385,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return CPTextFieldDOMCurrentElement;
}
#endif
- (id)initWithFrame:(CGRect)aFrame
{
@@ -675,123 +675,122 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
_stringValue = [self stringValue];
#if PLATFORM(DOM)
[self _setCSSStyleForInputElement];
var element = [self _inputElement];
element.value = _stringValue;
if (element.parentNode !== _DOMElement)
if (CPDOMAvailable)
{
var previousOwner = CPTextFieldInputOwner;
[self _setCSSStyleForInputElement];
// Otherwise Chrome will fire the blur during appendChild (NotFoundError).
_CPTextFieldDetachInputElement(element);
var element = [self _inputElement];
element.value = _stringValue;
try
if (element.parentNode !== _DOMElement)
{
_DOMElement.appendChild(element);
}
catch (e)
{
// Race with the Blur-Handler: retry once and yield silently.
var previousOwner = CPTextFieldInputOwner;
// Otherwise Chrome will fire the blur during appendChild (NotFoundError).
_CPTextFieldDetachInputElement(element);
try
{
_DOMElement.appendChild(element);
}
catch (e2)
catch (e)
{
[self unsetThemeState:CPThemeStateEditing];
[self _updatePlaceholderState];
[self setNeedsLayout];
return NO;
// Race with the Blur-Handler: retry once and yield silently.
_CPTextFieldDetachInputElement(element);
try
{
_DOMElement.appendChild(element);
}
catch (e2)
{
[self unsetThemeState:CPThemeStateEditing];
[self _updatePlaceholderState];
[self setNeedsLayout];
return NO;
}
}
if (previousOwner && previousOwner !== self)
{
[previousOwner unsetThemeState:CPThemeStateEditing];
[previousOwner setNeedsLayout];
}
}
if (previousOwner && previousOwner !== self)
CPTextFieldInputIsActive = YES;
if (document.attachEvent)
{
[previousOwner unsetThemeState:CPThemeStateEditing];
[previousOwner setNeedsLayout];
CPTextFieldCachedSelectStartFunction = [[self window] platformWindow]._DOMBodyElement.onselectstart;
CPTextFieldCachedDragFunction = [[self window] platformWindow]._DOMBodyElement.ondrag;
[[self window] platformWindow]._DOMBodyElement.ondrag = function () {};
[[self window] platformWindow]._DOMBodyElement.onselectstart = function () {};
}
}
CPTextFieldInputIsActive = YES;
CPTextFieldInputOwner = self;
if (document.attachEvent)
{
CPTextFieldCachedSelectStartFunction = [[self window] platformWindow]._DOMBodyElement.onselectstart;
CPTextFieldCachedDragFunction = [[self window] platformWindow]._DOMBodyElement.ondrag;
[[self window] platformWindow]._DOMBodyElement.ondrag = function () {};
[[self window] platformWindow]._DOMBodyElement.onselectstart = function () {};
}
CPTextFieldInputOwner = self;
[[CPRunLoop mainRunLoop] performBlock:function()
{
if (CPTextFieldInputOwner !== self)
return;
// This will prevent to jump to the focused element
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
element.focus();
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
// Select the text if the textfield became first responder through keyboard interaction
if (!_willBecomeFirstResponderByClick)
[[CPRunLoop mainRunLoop] performBlock:function()
{
[self _selectText:self immediately:YES];
}
else
{
var x = [self convertPointFromBase:[[CPApp currentEvent] locationInWindow]].x,
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
text = [self stringValue],
font = [self font];
if (CPTextFieldInputOwner !== self)
return;
switch ([self alignment]) {
case CPCenterTextAlignment:
// This will prevent to jump to the focused element
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
element.focus();
x -= (contentWidth - textWidth) / 2 + contentInset.left;
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
break;
// Select the text if the textfield became first responder through keyboard interaction
if (!_willBecomeFirstResponderByClick)
{
[self _selectText:self immediately:YES];
}
else
{
var x = [self convertPointFromBase:[[CPApp currentEvent] locationInWindow]].x,
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
text = [self stringValue],
font = [self font];
case CPRightTextAlignment:
switch ([self alignment]) {
case CPCenterTextAlignment:
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
x -= (contentWidth - textWidth) + contentInset.left;
x -= (contentWidth - textWidth) / 2 + contentInset.left;
break;
break;
default: // CPLeftTextAlignment, CPJustifiedTextAlignment, CPNaturalTextAlignment
case CPRightTextAlignment:
x -= contentInset.left;
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
break;
x -= (contentWidth - textWidth) + contentInset.left;
break;
default: // CPLeftTextAlignment, CPJustifiedTextAlignment, CPNaturalTextAlignment
x -= contentInset.left;
break;
}
var position = [CPPlatformString charPositionOfString:text withFont:font forPoint:CGPointMake(x, 0)];
[self setSelectedRange:CPMakeRange(position, 0)];
}
var position = [CPPlatformString charPositionOfString:text withFont:font forPoint:CGPointMake(x, 0)];
_willBecomeFirstResponderByClick = NO;
[self setSelectedRange:CPMakeRange(position, 0)];
}
_willBecomeFirstResponderByClick = NO;
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
#endif
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
}
return YES;
}
@@ -802,13 +801,14 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
*/
- (void)_setEnableCSSSelection:(BOOL)shouldEnable
{
#if PLATFORM (DOM)
if (!CPDOMAvailable)
return;
if (CPTexFieldCurrentCSSSelectableField)
CPTexFieldCurrentCSSSelectableField._DOMElement.style[CPBrowserStyleProperty(@"user-select")] = @"none";
CPTexFieldCurrentCSSSelectableField = self;
_DOMElement.style[CPBrowserStyleProperty(@"user-select")] = shouldEnable ? @"text" : @"none";
#endif
}
/*!
@@ -817,7 +817,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
*/
- (void)_setCSSStyleForInputElement
{
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
var element = [self _inputElement],
font = [self font],
@@ -880,41 +881,40 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (isTextArea)
element.style.whiteSpace = _wraps ? "pre" : "nowrap";
#endif
}
/*! @ignore */
- (BOOL)resignFirstResponder
{
#if PLATFORM(DOM)
// We might have been the first responder without actually editing.
if (_isEditing && CPTextFieldInputOwner === self)
if (CPDOMAvailable)
{
var element = [self _inputElement],
newValue = element.value,
error = @"";
if (newValue !== _stringValue)
// We might have been the first responder without actually editing.
if (_isEditing && CPTextFieldInputOwner === self)
{
[self _setStringValue:newValue];
}
var element = [self _inputElement],
newValue = element.value,
error = @"";
// If there is a formatter, always give it a chance to reject the resignation,
// even if the value has not changed.
if ([self _valueIsValid:newValue] === NO)
{
// This will prevent to jump to the focused element
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
if (newValue !== _stringValue)
{
[self _setStringValue:newValue];
}
element.focus();
// If there is a formatter, always give it a chance to reject the resignation,
// even if the value has not changed.
if ([self _valueIsValid:newValue] === NO)
{
// This will prevent to jump to the focused element
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
element.focus();
return NO;
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
return NO;
}
}
}
#endif
// When we are no longer the first responder we don't worry about the key status of our window anymore.
[self _setObserveWindowKeyNotifications:NO];
@@ -949,7 +949,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _updatePlaceholderState];
[self setNeedsLayout];
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
var element = [self _inputElement];
@@ -992,8 +992,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
CPTextFieldCachedSelectStartFunction = nil;
CPTextFieldCachedDragFunction = nil;
}
#endif
}
- (void)_setObserveWindowKeyNotifications:(BOOL)shouldObserve
@@ -1050,42 +1048,41 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
- (BOOL)_valueIsValid:(CPString)aValue
{
#if PLATFORM(DOM)
var error = @"";
if ([self _setStringValue:aValue isNewValue:NO errorDescription:@ref(error)] === NO)
if (CPDOMAvailable)
{
var acceptInvalidValue = NO;
var error = @"";
if (_implementedDelegateMethods & CPTextFieldDelegate_control_didFailToFormatString_errorDescription_)
if ([self _setStringValue:aValue isNewValue:NO errorDescription:@ref(error)] === NO)
{
var currentEvent = [CPApp currentEvent];
var acceptInvalidValue = NO;
if (currentEvent &&
CPTextFieldLastValidationFailureField === self &&
CPTextFieldLastValidationFailureString === aValue &&
CPTextFieldLastValidationFailureEvent === currentEvent)
if (_implementedDelegateMethods & CPTextFieldDelegate_control_didFailToFormatString_errorDescription_)
{
acceptInvalidValue = CPTextFieldLastValidationFailureResult;
}
else
{
acceptInvalidValue = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
var currentEvent = [CPApp currentEvent];
CPTextFieldLastValidationFailureField = self;
CPTextFieldLastValidationFailureString = aValue;
CPTextFieldLastValidationFailureEvent = currentEvent;
CPTextFieldLastValidationFailureResult = acceptInvalidValue;
if (currentEvent &&
CPTextFieldLastValidationFailureField === self &&
CPTextFieldLastValidationFailureString === aValue &&
CPTextFieldLastValidationFailureEvent === currentEvent)
{
acceptInvalidValue = CPTextFieldLastValidationFailureResult;
}
else
{
acceptInvalidValue = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
CPTextFieldLastValidationFailureField = self;
CPTextFieldLastValidationFailureString = aValue;
CPTextFieldLastValidationFailureEvent = currentEvent;
CPTextFieldLastValidationFailureResult = acceptInvalidValue;
}
}
if (acceptInvalidValue === NO)
return NO;
}
if (acceptInvalidValue === NO)
return NO;
}
#endif
return YES;
}
@@ -1193,17 +1190,18 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (!([self isEnabled] && [self isEditable]))
return;
#if PLATFORM(DOM)
var newValue = [self _inputElement].value;
if (newValue !== _stringValue)
if (CPDOMAvailable)
{
[self _setStringValue:newValue];
[self _didEdit];
}
var newValue = [self _inputElement].value;
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
#endif
if (newValue !== _stringValue)
{
[self _setStringValue:newValue];
[self _didEdit];
}
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
}
- (void)keyDown:(CPEvent)anEvent
@@ -1300,7 +1298,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (!([self isEnabled] && [self isEditable]))
return;
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
var oldValue = _stringValue,
range = [self selectedRange],
@@ -1318,8 +1317,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
{
[self _didEdit];
}
#endif
}
- (void)textDidBlur:(CPNotification)note
@@ -1482,13 +1479,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
else
_stringValue = [self stringValue];
#if PLATFORM(DOM)
if ((CPTextFieldInputOwner === self || [[self window] firstResponder] === self) && [[self window] isKeyWindow])
if (CPDOMAvailable && (CPTextFieldInputOwner === self || [[self window] firstResponder] === self) && [[self window] isKeyWindow])
[self _inputElement].value = _stringValue;
#endif
[self _updatePlaceholderState];
}
@@ -1618,31 +1611,34 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
{
var wind = [self window];
#if PLATFORM(DOM)
if ([self isEditable])
if (CPDOMAvailable)
{
var element = [self _inputElement];
if ([wind firstResponder] === self)
if ([self isEditable])
{
if (immediately)
element.select();
else
[[CPRunLoop mainRunLoop] performBlock:function(){ element.select(); } argument:nil order:0 modes:[CPDefaultRunLoopMode]];
var element = [self _inputElement];
if ([wind firstResponder] === self)
{
if (immediately)
element.select();
else
[[CPRunLoop mainRunLoop] performBlock:function(){ element.select(); } argument:nil order:0 modes:[CPDefaultRunLoopMode]];
}
else if (wind != nil && [wind makeFirstResponder:self])
[self _selectText:sender immediately:immediately];
}
else
{
[self setSelectedRange:CPMakeRange(0, _stringValue.length)];
}
else if (wind != nil && [wind makeFirstResponder:self])
[self _selectText:sender immediately:immediately];
}
else
{
[self setSelectedRange:CPMakeRange(0, _stringValue.length)];
// Even if we can't actually select the text we need to preserve the first
// responder side effect.
if (wind != nil && [wind firstResponder] !== self)
[wind makeFirstResponder:self];
}
#else
// Even if we can't actually select the text we need to preserve the first
// responder side effect.
if (wind != nil && [wind firstResponder] !== self)
[wind makeFirstResponder:self];
#endif
}
}
@@ -1665,9 +1661,10 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
{
// selectedRange won't work if we're displaying our text using a <div>. Instead we have to ask the browser
// what's selected and hope it's right in a Cappuccino context as well.
#if PLATFORM(DOM)
stringToCopy = [[[self window] platformWindow] _selectedText];
#endif
if (CPDOMAvailable)
{
stringToCopy = [[[self window] platformWindow] _selectedText];
}
}
var pasteboard = [CPPasteboard generalPasteboard];
@@ -1747,35 +1744,34 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if ([[self window] firstResponder] !== self)
return CPMakeRange(0, 0);
#if PLATFORM(DOM)
// we wrap this in try catch because firefox will throw an exception in certain instances
try
if (CPDOMAvailable)
{
var inputElement = [self _inputElement],
selectionStart = inputElement.selectionStart,
selectionEnd = inputElement.selectionEnd;
if ([selectionStart isKindOfClass:CPNumber])
return CPMakeRange(selectionStart, selectionEnd - selectionStart);
// browsers which don't support selectionStart/selectionEnd (aka IE).
var theDocument = inputElement.ownerDocument || inputElement.document,
selectionRange = theDocument.selection.createRange(),
range = inputElement.createTextRange();
if (range.inRange(selectionRange))
// we wrap this in try catch because firefox will throw an exception in certain instances
try
{
range.setEndPoint('EndToStart', selectionRange);
return CPMakeRange(range.text.length, selectionRange.text.length);
var inputElement = [self _inputElement],
selectionStart = inputElement.selectionStart,
selectionEnd = inputElement.selectionEnd;
if ([selectionStart isKindOfClass:CPNumber])
return CPMakeRange(selectionStart, selectionEnd - selectionStart);
// browsers which don't support selectionStart/selectionEnd (aka IE).
var theDocument = inputElement.ownerDocument || inputElement.document,
selectionRange = theDocument.selection.createRange(),
range = inputElement.createTextRange();
if (range.inRange(selectionRange))
{
range.setEndPoint('EndToStart', selectionRange);
return CPMakeRange(range.text.length, selectionRange.text.length);
}
}
catch (e)
{
// fall through to the return
}
}
catch (e)
{
// fall through to the return
}
#endif
return CPMakeRange(0, 0);
}
@@ -1785,7 +1781,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (![[self window] firstResponder] === self)
return;
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
if (![self isEditable])
{
@@ -1829,7 +1826,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
{
}
}
#endif
}
- (void)selectAll:(id)sender
@@ -1903,10 +1899,11 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _setObjectValue:newValue useFormatter:NO];
[self setSelectedRange:CPMakeRange(range.location, 0)];
#if PLATFORM(DOM)
// Since we just performed the deletion manually, we don't need the browser to do anything else.
[[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
#endif
if (CPDOMAvailable)
{
// Since we just performed the deletion manually, we don't need the browser to do anything else.
[[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
}
}
[self _didEdit];
+16 -8
View File
@@ -25,10 +25,10 @@
*/
@import "CPText.j"
@import "CPTextContainer.j"
@import "CGContext.j"
@import "CPTypesetter.j"
@import "CPFont.j"
@import "CPCompatibility.j"
@global _MakeRangeFromAbs
@global document
@@ -298,9 +298,10 @@ _oncontextmenuhandler = function () { return false; };
[_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil];
#if PLATFORM(DOM)
[self _cleanUpDOM];
#endif
if (CPDOMAvailable)
{
[self _cleanUpDOM];
}
_isValidatingLayoutAndGlyphs = NO;
}
@@ -1124,6 +1125,7 @@ var _objectsInRange = function(aList, aRange)
BOOL _isInvalid;
BOOL _isLast;
float _wordSpacing;
CGRect _fragmentRect;
CGRect _usedRect;
CGPoint _location;
@@ -1146,7 +1148,8 @@ var _objectsInRange = function(aList, aRange)
if (!aString || aString.length === 0)
return nil;
#if PLATFORM(DOM)
if (!CPDOMAvailable) return nil;
var style,
span = document.createElement("span");
@@ -1160,6 +1163,10 @@ var _objectsInRange = function(aList, aRange)
style.whiteSpace = "pre";
style.backgroundColor = "transparent";
style.font = [aFont cssString];
// A justified line widens its spaces; the typesetter has added the same width to their advancements.
if (_wordSpacing)
style.wordSpacing = _wordSpacing + "px";
if (aUnderline)
{
@@ -1197,9 +1204,6 @@ var _objectsInRange = function(aList, aRange)
span.textContent = aString;
return span;
#else
return nil;
#endif
}
- (id)initWithRange:(CPRange)aRange textContainer:(CPTextContainer)aContainer textStorage:(CPTextStorage)textStorage
@@ -1215,6 +1219,7 @@ var _objectsInRange = function(aList, aRange)
_range = CPMakeRangeCopy(aRange);
_textContainer = aContainer;
_isInvalid = NO;
_wordSpacing = 0.0;
_runs = [];
_glyphsFrames = [];
_glyphsOffsets = [];
@@ -1495,6 +1500,9 @@ var _objectsInRange = function(aList, aRange)
if (!oldFragmentRuns || !newFragmentRuns || oldFragmentRuns.length !== newFragmentRuns.length)
return NO;
if (newLineFragment._wordSpacing !== _wordSpacing)
return NO;
var l = oldFragmentRuns.length;
for (var i = 0; i < l; i++)
+36 -1
View File
@@ -106,6 +106,24 @@ var kDefaultTabInterval = 28.0;
// MARK: - CPParagraphStyle Implementation
/*
Returns YES if both arrays hold the same text lists, in the same order. A text list is
identified by the object itself, so the lists are compared by identity.
*/
var _CPParagraphStyleTextListsAreIdentical = function(someLists, otherLists)
{
var count = someLists ? someLists.length : 0;
if (count !== (otherLists ? otherLists.length : 0))
return NO;
for (var i = 0; i < count; i++)
if (someLists[i] !== otherLists[i])
return NO;
return YES;
};
var _sharedDefaultParagraphStyle = nil;
@implementation CPParagraphStyle : CPObject
@@ -124,6 +142,7 @@ var _sharedDefaultParagraphStyle = nil;
float _paragraphSpacingBefore @accessors(readonly, property=paragraphSpacingBefore);
float _defaultTabInterval @accessors(readonly, property=defaultTabInterval);
CPArray _tabStops @accessors(readonly, property=tabStops);
CPArray _textLists @accessors(readonly, property=textLists);
}
+ (CPParagraphStyle)defaultParagraphStyle
@@ -168,6 +187,8 @@ var _sharedDefaultParagraphStyle = nil;
[_tabStops addObject:[[CPTextTab alloc] initWithType:CPLeftTextAlignment
location:i * kDefaultTabInterval]];
}
_textLists = [];
}
return self;
}
@@ -190,6 +211,7 @@ var _sharedDefaultParagraphStyle = nil;
_paragraphSpacingBefore = [other paragraphSpacingBefore];
_defaultTabInterval = [other defaultTabInterval];
_tabStops = [[other tabStops] copy];
_textLists = [[other textLists] copy] || [];
}
return self;
}
@@ -229,6 +251,7 @@ var _sharedDefaultParagraphStyle = nil;
_paragraphSpacingBefore = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacingBefore"];
_defaultTabInterval = [aCoder decodeFloatForKey:@"CPParagraphStyleDefaultTabInterval"];
_tabStops = [aCoder decodeObjectForKey:@"CPParagraphStyleTabStops"];
_textLists = [aCoder decodeObjectForKey:@"CPParagraphStyleTextLists"] || [];
}
return self;
}
@@ -249,6 +272,9 @@ var _sharedDefaultParagraphStyle = nil;
[aCoder encodeFloat:_paragraphSpacingBefore forKey:@"CPParagraphStyleParagraphSpacingBefore"];
[aCoder encodeFloat:_defaultTabInterval forKey:@"CPParagraphStyleDefaultTabInterval"];
[aCoder encodeObject:_tabStops forKey:@"CPParagraphStyleTabStops"];
if ([_textLists count])
[aCoder encodeObject:_textLists forKey:@"CPParagraphStyleTextLists"];
}
// MARK: - Equality
@@ -265,7 +291,8 @@ var _sharedDefaultParagraphStyle = nil;
_tailIndent === [other tailIndent] &&
_firstLineHeadIndent === [other firstLineHeadIndent] &&
_lineBreakMode === [other lineBreakMode] &&
[_tabStops isEqualToArray:[other tabStops]];
[_tabStops isEqualToArray:[other tabStops]] &&
_CPParagraphStyleTextListsAreIdentical(_textLists, [other textLists]);
}
@end
@@ -377,6 +404,14 @@ var _sharedDefaultParagraphStyle = nil;
_tabStops = [newTabStops mutableCopy];
}
/*!
Sets the text lists that the paragraph belongs to, outermost first.
*/
- (void)setTextLists:(CPArray)someTextLists
{
_textLists = [someTextLists copy] || [];
}
- (id)copyWithZone:(CPZone)aZone
{
// Return an immutable copy
+504
View File
@@ -0,0 +1,504 @@
/*
* CPRulerMarker.j
* AppKit
*
* 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 "CGGeometry.j"
@import "CPEvent.j"
@import "CPGraphicsContext.j"
@import "CPImage.j"
@class CPRulerView
@global CPApp
@global CPHorizontalRuler
/*!
Distance, in points, that the pointer must move beyond the ruler before a removable
marker is removed when it is released.
*/
var CPRulerMarkerRemovalDistance = 0.0;
/*!
@ingroup appkit
@class CPRulerMarker
A CPRulerMarker shows a location on a CPRulerView, such as a tab stop or a paragraph
indent. The marker is drawn by its ruler view as an image, positioned so that the
image's \c imageOrigin lies at the marker's location on the ruler's baseline. The
marker's location is expressed in the coordinate system of the ruler's client view.
The user can drag a movable marker along the ruler, and drag a removable marker off the
ruler to remove it. The ruler's client view is consulted through the CPRulerView client
methods before and after each change.
*/
@implementation CPRulerMarker : CPObject
{
CPRulerView _ruler;
float _markerLocation;
CPImage _image;
CGPoint _imageOrigin;
BOOL _isMovable;
BOOL _isRemovable;
BOOL _isDragging;
BOOL _isOffRuler;
id _representedObject;
float _trackingStartLocation;
float _trackingStartPoint;
float _trackingStartCrossPoint;
float _trackingCrossOffset;
BOOL _trackingIsAdding;
BOOL _trackingMoved;
}
/*!
Initializes a marker for \c aRulerView at \c aLocation, drawn with \c anImage. The
marker is associated with the ruler view, but not added to it. A new marker is movable
and not removable.
@param aRulerView the ruler view the marker belongs to
@param aLocation the marker's location, in the client view's coordinate system
@param anImage the image drawn for the marker
@param anImageOrigin the point in the image, measured from its top left corner, placed
at the marker's location on the ruler's baseline
@return the initialized marker
*/
- (id)initWithRulerView:(CPRulerView)aRulerView markerLocation:(float)aLocation image:(CPImage)anImage imageOrigin:(CGPoint)anImageOrigin
{
self = [super init];
if (self)
{
_ruler = aRulerView;
_markerLocation = aLocation;
_image = anImage;
_imageOrigin = CGPointMakeCopy(anImageOrigin);
_isMovable = YES;
_isRemovable = NO;
_isDragging = NO;
_isOffRuler = NO;
}
return self;
}
// MARK: Getting the Ruler View
/*!
Returns the ruler view the marker belongs to.
*/
- (CPRulerView)ruler
{
return _ruler;
}
// MARK: Setting the Image
/*!
Returns the image drawn for the marker.
*/
- (CPImage)image
{
return _image;
}
/*!
Sets the image drawn for the marker.
@param anImage the new image
*/
- (void)setImage:(CPImage)anImage
{
if (_image === anImage)
return;
_image = anImage;
[_ruler setNeedsDisplay:YES];
}
/*!
Returns the point in the image, measured from its top left corner, that is placed at
the marker's location on the ruler's baseline.
*/
- (CGPoint)imageOrigin
{
return _imageOrigin;
}
/*!
Sets the point in the image, measured from its top left corner, that is placed at the
marker's location on the ruler's baseline.
@param aPoint the new image origin
*/
- (void)setImageOrigin:(CGPoint)aPoint
{
_imageOrigin = CGPointMakeCopy(aPoint);
[_ruler setNeedsDisplay:YES];
}
/*!
Returns the rectangle that the marker's image occupies, in the ruler view's coordinate
system.
*/
- (CGRect)imageRectInRuler
{
var imageSize = [_image size] || CGSizeMakeZero(),
point = [_ruler _rulerPointForLocation:_markerLocation],
baseline = [_ruler baselineLocation];
// Whole-point positions keep every marker image sharp and identical, wherever it lies on the ruler.
// While the user drags the marker outward, it follows the pointer across the ruler.
var crossPosition = baseline + (_isDragging ? _trackingCrossOffset : 0.0);
if ([_ruler orientation] === CPHorizontalRuler)
return CGRectMake(ROUND(point - _imageOrigin.x), ROUND(crossPosition - _imageOrigin.y), imageSize.width, imageSize.height);
return CGRectMake(ROUND(crossPosition - _imageOrigin.x), ROUND(point - _imageOrigin.y), imageSize.width, imageSize.height);
}
/*!
Returns the extent of the marker's image on the marker side of the ruler's baseline:
above the baseline on a horizontal ruler, and to its left on a vertical ruler.
*/
- (float)thicknessRequiredInRuler
{
return [_ruler orientation] === CPHorizontalRuler ? _imageOrigin.y : _imageOrigin.x;
}
// MARK: Setting Movability
/*!
Returns \c YES if the user can drag the marker along the ruler.
*/
- (BOOL)isMovable
{
return _isMovable;
}
/*!
Sets whether the user can drag the marker along the ruler.
@param shouldBeMovable \c YES to let the user move the marker
*/
- (void)setMovable:(BOOL)shouldBeMovable
{
_isMovable = shouldBeMovable;
}
/*!
Returns \c YES if the user can remove the marker by dragging it off the ruler.
*/
- (BOOL)isRemovable
{
return _isRemovable;
}
/*!
Sets whether the user can remove the marker by dragging it off the ruler.
@param shouldBeRemovable \c YES to let the user remove the marker
*/
- (void)setRemovable:(BOOL)shouldBeRemovable
{
_isRemovable = shouldBeRemovable;
}
// MARK: Setting the Location
/*!
Returns the marker's location, in the client view's coordinate system.
*/
- (float)markerLocation
{
return _markerLocation;
}
/*!
Sets the marker's location, in the client view's coordinate system.
@param aLocation the new location
*/
- (void)setMarkerLocation:(float)aLocation
{
if (_markerLocation === aLocation)
return;
_markerLocation = aLocation;
[_ruler setNeedsDisplay:YES];
}
// MARK: Setting the Represented Object
/*!
Returns the object the marker represents, such as a CPTextTab.
*/
- (id)representedObject
{
return _representedObject;
}
/*!
Sets the object the marker represents.
@param anObject the represented object
*/
- (void)setRepresentedObject:(id)anObject
{
_representedObject = anObject;
}
// MARK: Drawing and Event Handling
/*!
Returns \c YES while the user is dragging the marker.
*/
- (BOOL)isDragging
{
return _isDragging;
}
/*!
Draws the marker's image if it lies within \c aRect, in the ruler view's coordinate
system. While a removable marker is dragged off the ruler, it is drawn faded.
@param aRect the rectangle being drawn
*/
- (void)drawRect:(CGRect)aRect
{
if (!_image || [_image loadStatus] !== CPImageLoadStatusCompleted)
return;
var imageRect = [self imageRectInRuler];
if (!CGRectIntersectsRect(imageRect, aRect))
return;
var context = [[CPGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
// Off the ruler's outer edge the marker follows the pointer and is clipped by the ruler.
// Off its inner edge it stays on its line, so it is drawn faded instead.
if (_isOffRuler && _trackingCrossOffset === 0.0)
CGContextSetAlpha(context, 0.4);
CGContextDrawImage(context, imageRect, _image);
CGContextRestoreGState(context);
}
/*!
Starts tracking the user's manipulation of the marker, from the mouse-down event
\c anEvent. Tracking continues until the mouse button is released.
If \c isAdding is \c YES, the marker is not yet in the ruler: it is added when the user
releases it over the ruler, if the client view agrees. Otherwise the marker is moved,
and removed if it is removable and released away from the ruler.
@param anEvent the mouse-down event that started the manipulation
@param isAdding \c YES if the marker is being added to the ruler
@return \c YES if tracking started, or \c NO if the marker cannot be moved or removed,
or the client view refused
*/
- (BOOL)trackMouse:(CPEvent)anEvent adding:(BOOL)isAdding
{
if (!isAdding && !_isMovable && !_isRemovable)
return NO;
if (isAdding && ![_ruler _clientShouldAddMarker:self])
return NO;
if (!isAdding && _isMovable && ![_ruler _clientShouldMoveMarker:self])
return NO;
_trackingIsAdding = isAdding;
_trackingMoved = NO;
_trackingStartLocation = _markerLocation;
_trackingStartPoint = [self _axisPointForEvent:anEvent];
_trackingStartCrossPoint = [self _crossPointForEvent:anEvent];
_trackingCrossOffset = 0.0;
_isDragging = YES;
_isOffRuler = NO;
if (isAdding)
{
_markerLocation = [_ruler _clientWillAddMarker:self atLocation:_markerLocation];
[_ruler _setTrackedMarker:self];
}
[_ruler setNeedsDisplay:YES];
[CPApp setTarget:self
selector:@selector(_trackMouseWithEvent:)
forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask
untilDate:nil
inMode:nil
dequeue:YES];
return YES;
}
/* @ignore */
- (void)_trackMouseWithEvent:(CPEvent)anEvent
{
var type = [anEvent type];
if (type === CPLeftMouseDragged)
{
_trackingMoved = YES;
_isOffRuler = [self _isEventOffRuler:anEvent] && (_trackingIsAdding || _isRemovable);
// As in Cocoa, the marker stays on its line while the pointer is on the ruler, and follows
// the pointer out over the ruler's outer edge, where the ruler clips it.
_trackingCrossOffset = (_trackingIsAdding || _isRemovable) ? MIN(0.0, [self _crossPointForEvent:anEvent] - _trackingStartCrossPoint) : 0.0;
if (_trackingIsAdding || _isMovable)
{
var proposedLocation = _trackingStartLocation + [self _axisPointForEvent:anEvent] - _trackingStartPoint;
_markerLocation = _trackingIsAdding ? [_ruler _clientWillAddMarker:self atLocation:proposedLocation]
: [_ruler _clientWillMoveMarker:self toLocation:proposedLocation];
}
[_ruler setNeedsDisplay:YES];
[CPApp setTarget:self
selector:@selector(_trackMouseWithEvent:)
forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask
untilDate:nil
inMode:nil
dequeue:YES];
return;
}
if (type !== CPLeftMouseUp)
return;
var wasOffRuler = _isOffRuler;
_trackingCrossOffset = 0.0;
_isDragging = NO;
_isOffRuler = NO;
if (_trackingIsAdding)
{
[_ruler _setTrackedMarker:nil];
if (!wasOffRuler)
{
[_ruler addMarker:self];
[_ruler _clientDidAddMarker:self];
}
}
else if (wasOffRuler && [_ruler _clientShouldRemoveMarker:self])
{
[_ruler removeMarker:self];
[_ruler _clientDidRemoveMarker:self];
}
else if (_trackingMoved)
{
if (wasOffRuler)
_markerLocation = _trackingStartLocation;
if (_markerLocation !== _trackingStartLocation)
[_ruler _clientDidMoveMarker:self];
}
[_ruler setNeedsDisplay:YES];
}
/* @ignore */
- (float)_crossPointForEvent:(CPEvent)anEvent
{
var point = [_ruler convertPoint:[anEvent locationInWindow] fromView:nil];
return [_ruler orientation] === CPHorizontalRuler ? point.y : point.x;
}
/* @ignore */
- (float)_axisPointForEvent:(CPEvent)anEvent
{
var point = [_ruler convertPoint:[anEvent locationInWindow] fromView:nil];
return [_ruler orientation] === CPHorizontalRuler ? point.x : point.y;
}
/* @ignore */
- (BOOL)_isEventOffRuler:(CPEvent)anEvent
{
// Only distance across the ruler counts. Dragging past either end of the ruler moves the marker.
var point = [_ruler convertPoint:[anEvent locationInWindow] fromView:nil],
bounds = [_ruler bounds];
if ([_ruler orientation] === CPHorizontalRuler)
return point.y < CGRectGetMinY(bounds) - CPRulerMarkerRemovalDistance || point.y > CGRectGetMaxY(bounds) + CPRulerMarkerRemovalDistance;
return point.x < CGRectGetMinX(bounds) - CPRulerMarkerRemovalDistance || point.x > CGRectGetMaxX(bounds) + CPRulerMarkerRemovalDistance;
}
@end
var CPRulerMarkerRulerKey = @"CPRulerMarkerRulerKey",
CPRulerMarkerLocationKey = @"CPRulerMarkerLocationKey",
CPRulerMarkerImageKey = @"CPRulerMarkerImageKey",
CPRulerMarkerImageOriginKey = @"CPRulerMarkerImageOriginKey",
CPRulerMarkerMovableKey = @"CPRulerMarkerMovableKey",
CPRulerMarkerRemovableKey = @"CPRulerMarkerRemovableKey",
CPRulerMarkerRepresentedObjectKey = @"CPRulerMarkerRepresentedObjectKey";
@implementation CPRulerMarker (CPCoding)
/*!
Initializes a marker from an archive.
*/
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super init];
if (self)
{
_ruler = [aCoder decodeObjectForKey:CPRulerMarkerRulerKey];
_markerLocation = [aCoder decodeFloatForKey:CPRulerMarkerLocationKey];
_image = [aCoder decodeObjectForKey:CPRulerMarkerImageKey];
_imageOrigin = [aCoder decodePointForKey:CPRulerMarkerImageOriginKey];
_isMovable = [aCoder decodeBoolForKey:CPRulerMarkerMovableKey];
_isRemovable = [aCoder decodeBoolForKey:CPRulerMarkerRemovableKey];
_representedObject = [aCoder decodeObjectForKey:CPRulerMarkerRepresentedObjectKey];
_isDragging = NO;
_isOffRuler = NO;
_trackingCrossOffset = 0.0;
}
return self;
}
/*!
Archives the marker. The ruler is archived conditionally.
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeConditionalObject:_ruler forKey:CPRulerMarkerRulerKey];
[aCoder encodeFloat:_markerLocation forKey:CPRulerMarkerLocationKey];
[aCoder encodeObject:_image forKey:CPRulerMarkerImageKey];
[aCoder encodePoint:_imageOrigin forKey:CPRulerMarkerImageOriginKey];
[aCoder encodeBool:_isMovable forKey:CPRulerMarkerMovableKey];
[aCoder encodeBool:_isRemovable forKey:CPRulerMarkerRemovableKey];
[aCoder encodeObject:_representedObject forKey:CPRulerMarkerRepresentedObjectKey];
}
@end
+367
View File
@@ -0,0 +1,367 @@
/*
* CPTextAttachment.j
* AppKit
*
* Created by David Richardson on 25/10/2026.
* Copyright David Richardson 2026.
*
* 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/CPData.j>
@import <Foundation/CPNotificationCenter.j>
@import "CPImage.j"
@import "CPImageView.j"
/*
Posted when an attachment's displayed size changes, for example when its image has loaded.
A text view that contains the attachment lays its text out again.
*/
_CPTextAttachmentDidChangeSizeNotification = @"_CPTextAttachmentDidChangeSizeNotification";
/*!
@ingroup appkit
@class CPTextAttachment
A CPTextAttachment holds an image in rich text, as NSTextAttachment does in Cocoa. The text
holds the attachment as one attachment character (CPAttachmentCharacter), whose
CPAttachmentAttributeName attribute is the attachment. The attachment is edited, copied and
undone as that one character.
The attachment keeps the image's data and type, and shows the image in a view. As in TextKit
2, an attachment is drawn by a view, not by a cell. The text system lays the view out as the
character, at the view's size, with its bottom on the baseline. That size is the attachment's
bounds, or the image's natural size, scaled down to fit the width of the text, as in TextEdit.
*/
@implementation CPTextAttachment : CPObject
{
CPData _contents;
CPString _fileType;
CPImage _image;
CGRect _bounds;
CPImageView _imageView;
float _maximumWidth;
}
/*!
Initialises an attachment with image data.
@param contentData the image's data
@param aType the image's type, as a MIME type such as \c @"image/png", or a uniform type
identifier such as \c @"public.png"
*/
- (id)initWithData:(CPData)contentData ofType:(CPString)aType
{
if (self = [super init])
{
_contents = contentData;
_fileType = aType;
_bounds = CGRectMakeZero();
_maximumWidth = 0.0;
}
return self;
}
- (id)init
{
return [self initWithData:nil ofType:nil];
}
/* @ignore
Returns an attachment made from a data URL, such as the browser's FileReader produces.
*/
+ (CPTextAttachment)_attachmentWithDataURL:(CPString)aDataURL
{
var match = /^data:([^;,]+)(;base64)?,(.*)$/.exec(aDataURL || "");
if (!match)
return nil;
var data = match[2] ? [CPData dataWithBase64:match[3]] : [CPData dataWithRawString:decodeURIComponent(match[3])];
return [[self alloc] initWithData:data ofType:match[1]];
}
/*!
Returns the image's data.
*/
- (CPData)contents
{
return _contents;
}
/*!
Sets the image's data. The image is made again from the data.
*/
- (void)setContents:(CPData)contentData
{
_contents = contentData;
[self _setImage:nil];
}
/*!
Returns the image's type.
*/
- (CPString)fileType
{
return _fileType;
}
/*!
Sets the image's type.
*/
- (void)setFileType:(CPString)aType
{
_fileType = aType;
}
/*!
Returns the image. It is made from the attachment's data the first time it is needed.
*/
- (CPImage)image
{
if (!_image && _contents)
[self _setImage:[[CPImage alloc] initWithContentsOfFile:[self _dataURL]]];
return _image;
}
/*!
Sets the image. An image set this way has no data of its own unless it was made from a data
URL, so an attachment that must be archived or copied should be made with
-initWithData:ofType:.
*/
- (void)setImage:(CPImage)anImage
{
[self _setImage:anImage];
}
/*!
Returns the attachment's bounds in the text. With a zero size, the attachment takes the
image's natural size.
*/
- (CGRect)bounds
{
return CGRectMakeCopy(_bounds);
}
/*!
Sets the attachment's bounds in the text.
*/
- (void)setBounds:(CGRect)aRect
{
_bounds = CGRectMakeCopy(aRect);
[self _updateViewSize];
}
// MARK: Display
/* @ignore
Returns the view that shows the attachment in the text. The text system lays it out as the
attachment character.
*/
- (CPView)_attachmentView
{
if (!_imageView)
{
_imageView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[_imageView setImageScaling:CPImageScaleAxesIndependently];
[_imageView setImage:[self image]];
[self _updateViewSize];
}
return _imageView;
}
/* @ignore
Sets the widest the attachment may be shown, which is the width of the text. Returns YES if
the displayed size changes.
*/
- (BOOL)_setMaximumWidth:(float)aWidth
{
if (_maximumWidth === aWidth)
return NO;
_maximumWidth = aWidth;
return [self _updateViewSize];
}
/* @ignore
Returns the size at which the attachment is shown: its bounds, or the image's natural size,
scaled down in proportion to fit the maximum width. The size is zero until the image has
loaded.
*/
- (CGSize)_displaySize
{
var size = CGSizeMakeCopy(_bounds.size);
if (!size.width || !size.height)
{
var image = [self image];
if (!image || [image loadStatus] !== CPImageLoadStatusCompleted)
return CGSizeMakeZero();
size = CGSizeMakeCopy([image size]);
}
if (_maximumWidth > 0.0 && size.width > _maximumWidth)
{
size.height = ROUND(size.height * _maximumWidth / size.width);
size.width = _maximumWidth;
}
return size;
}
/* @ignore
Gives the view the displayed size. Returns YES if the size changes.
*/
- (BOOL)_updateViewSize
{
if (!_imageView)
return NO;
var size = [self _displaySize];
if (CGSizeEqualToSize(size, [_imageView frameSize]))
return NO;
[_imageView setFrameSize:size];
return YES;
}
/* @ignore
Sets the image, and waits for it to load when it has not yet loaded.
*/
- (void)_setImage:(CPImage)anImage
{
if (_image)
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPImageDidLoadNotification object:_image];
_image = anImage;
if (_image && [_image loadStatus] !== CPImageLoadStatusCompleted)
{
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_imageDidLoad:) name:CPImageDidLoadNotification object:_image];
[_image load];
}
if (_imageView)
{
[_imageView setImage:_image];
if ([self _updateViewSize])
[[CPNotificationCenter defaultCenter] postNotificationName:_CPTextAttachmentDidChangeSizeNotification object:self];
}
}
/* @ignore
The image's natural size is known once it has loaded, so the view takes its size, and the
text that contains the attachment is laid out again.
*/
- (void)_imageDidLoad:(CPNotification)aNotification
{
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPImageDidLoadNotification object:_image];
if (_imageView)
[_imageView setImage:_image];
if ([self _updateViewSize])
[[CPNotificationCenter defaultCenter] postNotificationName:_CPTextAttachmentDidChangeSizeNotification object:self];
}
/* @ignore
Returns the attachment's data as a data URL, which the browser loads as an image.
*/
- (CPString)_dataURL
{
return "data:" + [self _MIMEType] + ";base64," + [_contents base64];
}
/* @ignore
Returns the image's MIME type. A uniform type identifier is mapped to its MIME type.
*/
- (CPString)_MIMEType
{
var type = _fileType || @"image/png";
if (type.indexOf("/") !== -1)
return type;
switch (type)
{
case @"public.jpeg":
return @"image/jpeg";
case @"com.compuserve.gif":
return @"image/gif";
case @"public.svg-image":
return @"image/svg+xml";
case @"org.webmproject.webp":
return @"image/webp";
default:
return @"image/png";
}
}
@end
var CPTextAttachmentContentsKey = @"CPTextAttachmentContents",
CPTextAttachmentFileTypeKey = @"CPTextAttachmentFileType",
CPTextAttachmentBoundsKey = @"CPTextAttachmentBounds";
@implementation CPTextAttachment (CPCoding)
/*!
Decodes the attachment. The image is made again from the archived data.
*/
- (id)initWithCoder:(CPCoder)aCoder
{
var contentsString = [aCoder decodeObjectForKey:CPTextAttachmentContentsKey];
self = [self initWithData:contentsString ? [CPData dataWithBase64:contentsString] : nil
ofType:[aCoder decodeObjectForKey:CPTextAttachmentFileTypeKey]];
if (self && [aCoder containsValueForKey:CPTextAttachmentBoundsKey])
_bounds = [aCoder decodeRectForKey:CPTextAttachmentBoundsKey];
return self;
}
/*!
Encodes the attachment's data, type and bounds. The view is not encoded.
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
if (_contents)
[aCoder encodeObject:[_contents base64] forKey:CPTextAttachmentContentsKey];
if (_fileType)
[aCoder encodeObject:_fileType forKey:CPTextAttachmentFileTypeKey];
if (!CGRectIsEmpty(_bounds))
[aCoder encodeRect:_bounds forKey:CPTextAttachmentBoundsKey];
}
@end
+18 -5
View File
@@ -112,7 +112,8 @@ CPLineMovesUp = 4;
- (void)_init
{
_lineFragmentPadding = 0.0;
// Cocoa's default: room at each end of a line fragment, so that text does not touch the container's edges.
_lineFragmentPadding = 5.0;
_layoutManager = [[CPLayoutManager alloc] init];
[_layoutManager addTextContainer:self];
@@ -129,6 +130,9 @@ CPLineMovesUp = 4;
if (oldSize.width != _size.width)
{
// Image attachments fit the new width before the text is laid out again.
[_textView _setTextAttachmentsMaximumWidth:MAX(0.0, _size.width - 2.0 * _lineFragmentPadding)];
_inResizing = YES;
[_layoutManager invalidateLayoutForCharacterRange:CPMakeRange(0, [[_layoutManager textStorage] length])
isSoft:NO
@@ -193,13 +197,16 @@ CPLineMovesUp = 4;
- (void)textViewFrameChanged:(CPNotification)aNotification
{
var newSize = CGSizeMake(_size.width, _size.height);
// As in Cocoa, a tracking container fills the text view inside its text container inset on both sides.
var newSize = CGSizeMake(_size.width, _size.height),
frameSize = [_textView frame].size,
inset = [_textView textContainerInset];
if (_widthTracksTextView)
newSize.width = [_textView frame].size.width;
newSize.width = MAX(0.0, frameSize.width - 2.0 * inset.width);
if (_heightTracksTextView)
newSize.height = [_textView frame].size.height;
newSize.height = MAX(0.0, frameSize.height - 2.0 * inset.height);
[self setContainerSize:newSize];
}
@@ -271,7 +278,8 @@ CPLineMovesUp = 4;
var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey",
CPTextContainerWidthTracksTextViewKey = @"CPTextContainerWidthTracksTextViewKey",
CPTextContainerHeightTracksTextViewKey = @"CPTextContainerHeightTracksTextViewKey";
CPTextContainerHeightTracksTextViewKey = @"CPTextContainerHeightTracksTextViewKey",
CPTextContainerLineFragmentPaddingKey = @"CPTextContainerLineFragmentPaddingKey";
@implementation CPTextContainer (CPCoding)
@@ -290,6 +298,10 @@ var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
_widthTracksTextView = [aCoder decodeBoolForKey:CPTextContainerWidthTracksTextViewKey];
_heightTracksTextView = [aCoder decodeBoolForKey:CPTextContainerHeightTracksTextViewKey];
// Archives made before the padding was archived keep the default from _init.
if ([aCoder containsValueForKey:CPTextContainerLineFragmentPaddingKey])
_lineFragmentPadding = [aCoder decodeFloatForKey:CPTextContainerLineFragmentPaddingKey];
}
return self;
@@ -301,6 +313,7 @@ var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
[aCoder encodeObject:_layoutManager forKey:CPTextContainerLayoutManagerKey];
[aCoder encodeBool:_widthTracksTextView forKey:CPTextContainerWidthTracksTextViewKey];
[aCoder encodeBool:_heightTracksTextView forKey:CPTextContainerHeightTracksTextViewKey];
[aCoder encodeFloat:_lineFragmentPadding forKey:CPTextContainerLineFragmentPaddingKey];
}
@end
+403
View File
@@ -0,0 +1,403 @@
/*
* CPTextList.j
* AppKit
*
* Created by David Richardson on 24/10/2026.
* Copyright David Richardson 2026.
*
* 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/CPAttributedString.j>
@import "CPParagraphStyle.j"
/*
CPTextListOptions
*/
CPTextListPrependEnclosingMarker = 1 << 0;
/*
CPTextListMarkerFormat
*/
CPTextListMarkerBox = @"{box}";
CPTextListMarkerCheck = @"{check}";
CPTextListMarkerCircle = @"{circle}";
CPTextListMarkerDiamond = @"{diamond}";
CPTextListMarkerDisc = @"{disc}";
CPTextListMarkerHyphen = @"{hyphen}";
CPTextListMarkerSquare = @"{square}";
CPTextListMarkerLowercaseHexadecimal = @"{lower-hexadecimal}";
CPTextListMarkerUppercaseHexadecimal = @"{upper-hexadecimal}";
CPTextListMarkerOctal = @"{octal}";
CPTextListMarkerLowercaseAlpha = @"{lower-alpha}";
CPTextListMarkerUppercaseAlpha = @"{upper-alpha}";
CPTextListMarkerLowercaseLatin = @"{lower-latin}";
CPTextListMarkerUppercaseLatin = @"{upper-latin}";
CPTextListMarkerLowercaseRoman = @"{lower-roman}";
CPTextListMarkerUppercaseRoman = @"{upper-roman}";
CPTextListMarkerDecimal = @"{decimal}";
/*
The characters that the unordered marker formats stand for.
*/
var CPTextListBulletCharacters = {
"box": "□",
"check": "✓",
"circle": "◦",
"diamond": "◆",
"disc": "•",
"hyphen": "⁃",
"square": "▪"
},
CPTextListPlaceholderPattern = /\{([a-z-]+)\}/;
/*
Returns anItemNumber as a letter sequence: 1 is "a", 26 is "z", 27 is "aa".
*/
var _CPTextListAlphaString = function(anItemNumber)
{
var result = "";
while (anItemNumber > 0)
{
anItemNumber--;
result = String.fromCharCode(97 + anItemNumber % 26) + result;
anItemNumber = Math.floor(anItemNumber / 26);
}
return result;
};
/*
Returns anItemNumber as a lowercase Roman numeral.
*/
var _CPTextListRomanString = function(anItemNumber)
{
var values = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1],
numerals = ["m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i"],
result = "";
for (var i = 0; i < values.length; i++)
{
while (anItemNumber >= values[i])
{
result += numerals[i];
anItemNumber -= values[i];
}
}
return result;
};
/*!
@ingroup appkit
@class CPTextList
A CPTextList describes one list in rich text: the format of its markers, its options and the
number of its first item. A paragraph belongs to the lists in its paragraph style's
\c textLists, outermost first. As in Cocoa, the markers are ordinary text at the start of
each item: a tab, the marker and another tab, placed by the paragraph style's indents and
tab stops. A list is identified by the object itself, so the paragraphs of one list share
the same CPTextList.
*/
@implementation CPTextList : CPObject
{
CPString _markerFormat;
unsigned _listOptions;
int _startingItemNumber;
}
/*!
Initialises a list.
@param aFormat the marker format: one of the CPTextListMarker constants, optionally with
text around it, such as \c @"({decimal})"
@param aMask the list options, such as CPTextListPrependEnclosingMarker
*/
- (id)initWithMarkerFormat:(CPString)aFormat options:(unsigned)aMask
{
return [self initWithMarkerFormat:aFormat options:aMask startingItemNumber:1];
}
/*!
Initialises a list whose first item has the number anItemNumber.
*/
- (id)initWithMarkerFormat:(CPString)aFormat options:(unsigned)aMask startingItemNumber:(int)anItemNumber
{
if (self = [super init])
{
_markerFormat = aFormat || CPTextListMarkerDisc;
_listOptions = aMask;
_startingItemNumber = anItemNumber;
}
return self;
}
/*!
Returns the marker format.
*/
- (CPString)markerFormat
{
return _markerFormat;
}
/*!
Returns the list options.
*/
- (unsigned)listOptions
{
return _listOptions;
}
/*!
Returns the number of the list's first item. The default is 1.
*/
- (int)startingItemNumber
{
return _startingItemNumber;
}
/*!
Sets the number of the list's first item.
*/
- (void)setStartingItemNumber:(int)anItemNumber
{
_startingItemNumber = anItemNumber;
}
/*!
Returns \c YES if the markers are numbers or letters, and \c NO if they are bullets.
*/
- (BOOL)isOrdered
{
var match = _markerFormat.match(CPTextListPlaceholderPattern);
return !!match && !CPTextListBulletCharacters.hasOwnProperty(match[1]);
}
/*!
Returns the marker for the item with the number anItemNumber, without the tabs around it.
The marker format's placeholder is replaced by the bullet or the formatted number, and the
text around the placeholder is kept.
*/
- (CPString)markerForItemNumber:(int)anItemNumber
{
var match = _markerFormat.match(CPTextListPlaceholderPattern);
if (!match)
return _markerFormat;
var name = match[1],
marker;
if (CPTextListBulletCharacters.hasOwnProperty(name))
marker = CPTextListBulletCharacters[name];
else
{
switch (name)
{
case "lower-hexadecimal":
marker = anItemNumber.toString(16);
break;
case "upper-hexadecimal":
marker = anItemNumber.toString(16).toUpperCase();
break;
case "octal":
marker = anItemNumber.toString(8);
break;
case "lower-alpha":
case "lower-latin":
marker = anItemNumber > 0 ? _CPTextListAlphaString(anItemNumber) : String(anItemNumber);
break;
case "upper-alpha":
case "upper-latin":
marker = anItemNumber > 0 ? _CPTextListAlphaString(anItemNumber).toUpperCase() : String(anItemNumber);
break;
case "lower-roman":
marker = anItemNumber > 0 ? _CPTextListRomanString(anItemNumber) : String(anItemNumber);
break;
case "upper-roman":
marker = anItemNumber > 0 ? _CPTextListRomanString(anItemNumber).toUpperCase() : String(anItemNumber);
break;
default:
marker = String(anItemNumber);
}
}
return _markerFormat.replace(CPTextListPlaceholderPattern, marker);
}
- (CPString)description
{
return [super description] + " markerFormat=" + _markerFormat + " startingItemNumber=" + _startingItemNumber;
}
@end
var CPTextListMarkerFormatKey = @"CPTextListMarkerFormat",
CPTextListOptionsKey = @"CPTextListOptions",
CPTextListStartingItemNumberKey = @"CPTextListStartingItemNumber";
@implementation CPTextList (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
if (self = [super init])
{
_markerFormat = [aCoder decodeObjectForKey:CPTextListMarkerFormatKey] || CPTextListMarkerDisc;
_listOptions = [aCoder decodeIntForKey:CPTextListOptionsKey];
_startingItemNumber = [aCoder containsValueForKey:CPTextListStartingItemNumberKey] ? [aCoder decodeIntForKey:CPTextListStartingItemNumberKey] : 1;
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeObject:_markerFormat forKey:CPTextListMarkerFormatKey];
[aCoder encodeInt:_listOptions forKey:CPTextListOptionsKey];
[aCoder encodeInt:_startingItemNumber forKey:CPTextListStartingItemNumberKey];
}
@end
/*
Returns YES if the character code separates paragraphs.
*/
var _CPTextListIsParagraphSeparator = function(aCharCode)
{
return aCharCode === 10 || aCharCode === 13 || aCharCode === 0x2029;
};
@implementation CPAttributedString (CPTextListAdditions)
/* @ignore
Returns the range of the paragraph containing anIndex, including its paragraph separator.
*/
- (CPRange)_paragraphRangeForIndex:(unsigned)anIndex
{
var string = [self string],
length = string.length,
start = MIN(anIndex, length),
end = start;
while (start > 0 && !_CPTextListIsParagraphSeparator(string.charCodeAt(start - 1)))
start--;
while (end < length && !_CPTextListIsParagraphSeparator(string.charCodeAt(end)))
end++;
if (end < length)
end++;
return CPMakeRange(start, end - start);
}
/* @ignore
Returns the text lists of the paragraph that starts at aParagraphStart, outermost first.
*/
- (CPArray)_textListsOfParagraphAtIndex:(unsigned)aParagraphStart
{
if (aParagraphStart >= [self length])
return [];
var paragraphStyle = [self attribute:CPParagraphStyleAttributeName atIndex:aParagraphStart effectiveRange:nil];
return [paragraphStyle textLists] || [];
}
/*!
Returns the range of the paragraphs that belong to aList, around anIndex. The paragraphs
are contiguous: the range ends at the first paragraph on either side that does not belong
to the list, at any level. Returns a range with the location CPNotFound if the paragraph
at anIndex does not belong to aList.
*/
- (CPRange)rangeOfTextList:(CPTextList)aList atIndex:(unsigned)anIndex
{
var paragraphRange = [self _paragraphRangeForIndex:anIndex];
if (![[self _textListsOfParagraphAtIndex:paragraphRange.location] containsObjectIdenticalTo:aList])
return CPMakeRange(CPNotFound, 0);
var start = paragraphRange.location,
end = CPMaxRange(paragraphRange),
length = [self length];
while (start > 0)
{
var previousRange = [self _paragraphRangeForIndex:start - 1];
if (![[self _textListsOfParagraphAtIndex:previousRange.location] containsObjectIdenticalTo:aList])
break;
start = previousRange.location;
}
while (end < length)
{
var nextRange = [self _paragraphRangeForIndex:end];
if (![[self _textListsOfParagraphAtIndex:nextRange.location] containsObjectIdenticalTo:aList])
break;
end = CPMaxRange(nextRange);
}
return CPMakeRange(start, end - start);
}
/*!
Returns the number of the item of aList at anIndex. The items of a list are the paragraphs
whose innermost list it is; paragraphs of lists nested in it do not count. For a paragraph
of a nested list, the result is the number of the enclosing item. Returns 0 if the
paragraph at anIndex does not belong to aList.
*/
- (int)itemNumberInTextList:(CPTextList)aList atIndex:(unsigned)anIndex
{
var listRange = [self rangeOfTextList:aList atIndex:anIndex];
if (listRange.location === CPNotFound)
return 0;
var target = [self _paragraphRangeForIndex:anIndex].location,
itemNumber = [aList startingItemNumber] - 1,
location = listRange.location;
while (location <= target)
{
var paragraphRange = [self _paragraphRangeForIndex:location];
if ([[self _textListsOfParagraphAtIndex:location] lastObject] === aList)
itemNumber++;
if (!paragraphRange.length)
break;
location = CPMaxRange(paragraphRange);
}
return MAX(itemNumber, [aList startingItemNumber]);
}
@end
+13 -5
View File
@@ -26,6 +26,7 @@
@import "CPText.j"
@import "CPFont.j"
@import "CPTextAttachment.j"
@class CPLayoutManager;
@@ -263,16 +264,23 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
/*!
Returns an instance of CPTextStorage that contains the provided instance of CPView.
This can be used to insert arbitrary views into the text. These views are treated as individual characters during editing.
This works only with views that conform to the CPCoding protocol
Returns an instance of CPTextStorage that contains one attachment character for anAttachment.
The attachment is treated as an individual character during editing.
anAttachment is either a CPTextAttachment, as in Cocoa, or a view. A CPTextAttachment is
stored as the character's CPAttachmentAttributeName attribute, and its view is laid out as
the character. A view is inserted into the text directly; this works only with views that
conform to the CPCoding protocol.
*/
+ (id)attributedStringWithAttachment:(CPView)someView
+ (id)attributedStringWithAttachment:(id)anAttachment
{
var result = [[self alloc] initWithString:_CPAttachmentCharacterAsString];
[result setAttributes:@{_CPAttachmentView:someView} range:CPMakeRange(0, 1)];
if ([anAttachment isKindOfClass:[CPTextAttachment class]])
[result setAttributes:@{CPAttachmentAttributeName:anAttachment, _CPAttachmentView:[anAttachment _attachmentView]} range:CPMakeRange(0, 1)];
else
[result setAttributes:@{_CPAttachmentView:anAttachment} range:CPMakeRange(0, 1)];
return result;
}
File diff suppressed because it is too large. Load diff
+97 -12
View File
@@ -49,7 +49,23 @@ CPTypesetterParagraphBreakAction = 1 << 4;
CPTypesetterContainerBreakAction = 1 << 5;
var CPSystemTypesetterFactory,
_sharedSimpleTypesetter;
_sharedSimpleTypesetter,
_CPTypesetterCachedDecimalSeparator = nil;
/*
Returns the decimal separator of the user's locale, which a decimal tab stop aligns on.
*/
var _CPTypesetterDecimalSeparator = function()
{
if (!_CPTypesetterCachedDecimalSeparator)
{
var formatted = (1.5).toLocaleString();
_CPTypesetterCachedDecimalSeparator = formatted.length === 3 ? formatted.charAt(1) : @".";
}
return _CPTypesetterCachedDecimalSeparator;
};
@implementation CPTypesetter : CPObject
@@ -170,10 +186,32 @@ var CPSystemTypesetterFactory,
advancements:(CPArray)advancements
lineCount:(unsigned)lineCount
sameLine:(BOOL)sameLine
wordWrapped:(BOOL)wordWrapped
rightMargin:(float)rightMargin
{
var myX = 0,
rect = CGRectMake(lineOrigin.x, lineOrigin.y, _lineWidth, _lineHeight),
containerSize = aContainer._size;
// Line positions are computed inside the line fragment padding. The padding is added here, where
// the line fragment rectangle is stored, and alignment uses the width inside the padding.
var padding = [aContainer lineFragmentPadding],
myX = 0,
rect = CGRectMake(lineOrigin.x + padding, lineOrigin.y, _lineWidth, _lineHeight);
// As in Cocoa, text is aligned between the indents, and whitespace at the end of the line hangs
// outside the alignment: it does not count in the line's visible width.
var theString = [_textStorage string],
availableWidth = rightMargin - lineOrigin.x,
visibleLength = lineRange.length,
visibleWidth = _lineWidth;
while (visibleLength > 0)
{
var trailingCharCode = theString.charCodeAt(lineRange.location + visibleLength - 1);
if (trailingCharCode !== 32 && trailingCharCode !== 10 && trailingCharCode !== 13)
break;
visibleLength--;
visibleWidth -= advancements[visibleLength].width;
}
[_layoutManager _appendNewLineFragmentInTextContainer:_currentTextContainer forGlyphRange:lineRange];
@@ -190,11 +228,41 @@ var CPSystemTypesetterFactory,
break;
case CPCenterTextAlignment:
myX = (containerSize.width - _lineWidth) / 2;
myX = (availableWidth - visibleWidth) / 2;
break;
case CPRightTextAlignment:
myX = containerSize.width - _lineWidth;
myX = availableWidth - visibleWidth;
break;
case CPJustifiedTextAlignment:
// Only a line that wraps is justified. The last line of a paragraph, and the text
// before a tab on the same line, stay left aligned, as in Cocoa. The spare width is
// spread evenly over the spaces inside the visible part of the line. The line fragment
// draws its spans with the same extra word spacing.
if (wordWrapped && !sameLine)
{
var spaceIndexes = [];
for (var i = 0; i < visibleLength; i++)
{
var charCode = theString.charCodeAt(lineRange.location + i);
if (charCode === 32 || charCode === 160)
spaceIndexes.push(i);
}
var spaceCount = spaceIndexes.length,
extraWidth = spaceCount ? (availableWidth - visibleWidth) / spaceCount : 0.0;
if (extraWidth > 0.0)
{
for (var i = 0; i < spaceCount; i++)
advancements[spaceIndexes[i]].width += extraWidth;
fragment._wordSpacing = extraWidth;
}
}
break;
}
@@ -237,8 +305,10 @@ var CPSystemTypesetterFactory,
_lineBase = 0;
_lineWidth = 0;
// containerSizeWidth is the width inside the line fragment padding at both ends.
var containerSize = [_currentTextContainer containerSize],
containerSizeWidth = containerSize.width,
lineFragmentPadding = [_currentTextContainer lineFragmentPadding],
containerSizeWidth = containerSize.width - 2.0 * lineFragmentPadding,
containerSizeHeight = containerSize.height,
lineRange = CPMakeRange(glyphIndex, 0),
wrapRange = CPMakeRange(0, 0),
@@ -274,7 +344,11 @@ var CPSystemTypesetterFactory,
rightMargin = containerSizeWidth;
if (glyphIndex > 0)
{
// Stored line fragment rectangles include the padding, which the layout below does not.
lineOrigin = CGPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin);
lineOrigin.x -= lineFragmentPadding;
}
else if ([_layoutManager extraLineFragmentTextContainer])
lineOrigin = CGPointMake(0, [_layoutManager extraLineFragmentUsedRect].origin.y);
else
@@ -469,7 +543,16 @@ var CPSystemTypesetterFactory,
tabAlignment = [nextTab alignment];
// Mathematically offset the tab character's right boundary
if (tabAlignment === CPCenterTextAlignment)
if ([nextTab tabStopType] === CPDecimalTabStopType)
{
// As in Cocoa, the first decimal separator of the user's locale starts at the stop.
// A segment without one ends at the stop, like a right tab.
var separatorIndex = segmentString.indexOf(_CPTypesetterDecimalSeparator()),
widthBeforeSeparator = separatorIndex === -1 ? nextSegmentWidth : [segmentString.substring(0, separatorIndex) sizeWithFont:currentFont inWidth:NULL].width;
rangeWidth = (tabLocation - widthBeforeSeparator) - lineOrigin.x;
}
else if (tabAlignment === CPCenterTextAlignment)
{
rangeWidth = (tabLocation - nextSegmentWidth / 2.0) - lineOrigin.x;
}
@@ -533,7 +616,7 @@ var CPSystemTypesetterFactory,
if (isNewline || isTabStop || isAttachment)
{
if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline])
if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline wordWrapped:isWordWrapped rightMargin:rightMargin])
return;
if (isTabStop || isAttachment)
@@ -556,7 +639,8 @@ var CPSystemTypesetterFactory,
{
_currentTextContainer = textContainers[++_indexOfCurrentContainer];
containerSize = [_currentTextContainer containerSize];
containerSizeWidth = containerSize.width;
lineFragmentPadding = [_currentTextContainer lineFragmentPadding];
containerSizeWidth = containerSize.width - 2.0 * lineFragmentPadding;
containerSizeHeight = containerSize.height;
}
@@ -587,9 +671,10 @@ var CPSystemTypesetterFactory,
// Flush remaining characters
if (lineRange.length)
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO];
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO wordWrapped:NO rightMargin:rightMargin];
var rect = CGRectMake(1, lineOrigin.y - descent, containerSizeWidth, currentFontLineHeight);
// The empty line after the text starts inside the padding, and at least 1 point in, so the insertion point stays visible.
var rect = CGRectMake(MAX(1.0, lineFragmentPadding), lineOrigin.y - descent, containerSizeWidth, currentFontLineHeight);
[_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer];
var fragment = [_layoutManager._lineFragments lastObject];
+61 -72
View File
@@ -32,6 +32,7 @@
@import "CPText.j"
@import "CPTextField.j"
@import "CPWindow_Constants.j"
@import "CPCompatibility.j"
@class _CPTokenFieldTokenCloseButton
@class _CPTokenFieldTokenDisclosureButton
@@ -63,8 +64,6 @@ var CPTokenFieldDelegate_tokenField_hasMenuForRepresentedObject_
CPTokenFieldDelegate_tokenField_representedObjectForEditingString_ = 1 << 6;
#if PLATFORM(DOM)
var CPTokenFieldDOMInputElement = nil,
CPTokenFieldDOMPasswordInputElement = nil,
CPTokenFieldDOMStandardInputElement = nil,
@@ -79,8 +78,6 @@ var CPTokenFieldDOMInputElement = nil,
CPTokenFieldBlurHandler = nil;
#endif
var CPScrollDestinationNone = 0,
CPScrollDestinationLeft = 1,
CPScrollDestinationRight = 2;
@@ -394,10 +391,10 @@ CPTokenFieldDeleteButtonType = 1;
if (![super becomeFirstResponder])
return NO;
#if PLATFORM(DOM)
if (CPTokenFieldInputOwner && [CPTokenFieldInputOwner window] !== [self window])
if (CPDOMAvailable && CPTokenFieldInputOwner && [CPTokenFieldInputOwner window] !== [self window])
{
[[CPTokenFieldInputOwner window] makeFirstResponder:nil];
#endif
}
// As long as we are the first responder we need to monitor the key status of our window.
[self _setObserveWindowKeyNotifications:YES];
@@ -423,75 +420,74 @@ CPTokenFieldDeleteButtonType = 1;
[self setNeedsLayout];
#if PLATFORM(DOM)
var string = [self stringValue],
element = [self _inputElement],
font = [self currentValueForThemeAttribute:@"font"];
element.value = nil;
element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
element.style.font = [font cssString];
element.style.zIndex = 1000;
switch ([self alignment])
if (CPDOMAvailable)
{
case CPCenterTextAlignment:
element.style.textAlign = "center";
break;
var string = [self stringValue],
element = [self _inputElement],
font = [self currentValueForThemeAttribute:@"font"];
case CPRightTextAlignment:
element.style.textAlign = "right";
break;
element.value = nil;
element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
element.style.font = [font cssString];
element.style.zIndex = 1000;
default:
element.style.textAlign = "left";
}
switch ([self alignment])
{
case CPCenterTextAlignment:
element.style.textAlign = "center";
break;
var contentRect = [self contentRectForBounds:[self bounds]];
case CPRightTextAlignment:
element.style.textAlign = "right";
break;
element.style.top = CGRectGetMinY(contentRect) + "px";
element.style.left = (CGRectGetMinX(contentRect) - 1) + "px"; // <input> element effectively imposes a 1px left margin
element.style.width = CGRectGetWidth(contentRect) + "px";
element.style.height = [font defaultLineHeightForFont] + "px";
default:
element.style.textAlign = "left";
}
[[CPRunLoop mainRunLoop] performBlock:function()
{
[_tokenScrollView documentView]._DOMElement.appendChild(element);
var contentRect = [self contentRectForBounds:[self bounds]];
// Removed so CPTokenField doesn't fire the notification the moment it becomes the first responder, but instead defers to the first keystroke, just like Cocoa (see keyDown: in CPTextField).
// [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
element.style.top = CGRectGetMinY(contentRect) + "px";
element.style.left = (CGRectGetMinX(contentRect) - 1) + "px"; // <input> element effectively imposes a 1px left margin
element.style.width = CGRectGetWidth(contentRect) + "px";
element.style.height = [font defaultLineHeightForFont] + "px";
[[CPRunLoop mainRunLoop] performBlock:function()
{
// This will prevent to jump to the focused element
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
[_tokenScrollView documentView]._DOMElement.appendChild(element);
element.focus();
// Removed so CPTokenField doesn't fire the notification the moment it becomes the first responder, but instead defers to the first keystroke, just like Cocoa (see keyDown: in CPTextField).
// [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
[[CPRunLoop mainRunLoop] performBlock:function()
{
// This will prevent to jump to the focused element
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
CPTokenFieldInputOwner = self;
element.focus();
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
CPTokenFieldInputOwner = self;
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
CPTokenFieldInputIsActive = YES;
CPTokenFieldInputIsActive = YES;
if (document.attachEvent)
{
CPTokenFieldCachedSelectStartFunction = document.body.onselectstart;
CPTokenFieldCachedDragFunction = document.body.ondrag;
if (document.attachEvent)
{
CPTokenFieldCachedSelectStartFunction = document.body.onselectstart;
CPTokenFieldCachedDragFunction = document.body.ondrag;
document.body.ondrag = function () {};
document.body.onselectstart = function () {};
document.body.ondrag = function () {};
document.body.onselectstart = function () {};
}
}
#endif
return YES;
}
@@ -527,7 +523,7 @@ CPTokenFieldDeleteButtonType = 1;
[self _updatePlaceholderState];
[self setNeedsLayout];
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
var element = [self _inputElement];
@@ -561,8 +557,6 @@ CPTokenFieldDeleteButtonType = 1;
document.body.ondrag = CPTokenFieldCachedDragFunction;
document.body.onselectstart = CPTokenFieldCachedSelectStartFunction;
}
#endif
}
- (void)mouseDown:(CPEvent)anEvent
@@ -630,16 +624,12 @@ CPTokenFieldDeleteButtonType = 1;
[objectValue addObject:[token representedObject]];
}
#if PLATFORM(DOM)
if ([self _editorValue])
if (CPDOMAvailable && [self _editorValue])
{
var token = [self _representedObjectForEditingString:[self _editorValue]];
[objectValue insertObject:token atIndex:_selectedRange.location];
}
#endif
return objectValue;
}
@@ -755,9 +745,10 @@ CPTokenFieldDeleteButtonType = 1;
// =============
// = TEXTFIELD =
// =============
#if PLATFORM(DOM)
- (DOMElement)_inputElement
{
if (!CPDOMAvailable) return nil;
if (!CPTokenFieldDOMInputElement)
{
CPTokenFieldDOMInputElement = document.createElement("input");
@@ -798,7 +789,6 @@ CPTokenFieldDeleteButtonType = 1;
return CPTokenFieldDOMInputElement;
}
#endif
- (CPString)_editorValue
{
@@ -1036,9 +1026,10 @@ CPTokenFieldDeleteButtonType = 1;
- (void)keyDown:(CPEvent)anEvent
{
#if PLATFORM(DOM)
CPTokenFieldTextDidChangeValue = [self stringValue];
#endif
if (CPDOMAvailable)
{
CPTokenFieldTextDidChangeValue = [self stringValue];
}
// Has to be enabled, and it also has to be editable or selectable.
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
@@ -1062,12 +1053,10 @@ CPTokenFieldDeleteButtonType = 1;
- (void)keyUp:(CPEvent)anEvent
{
#if PLATFORM(DOM)
if ([self stringValue] !== CPTokenFieldTextDidChangeValue)
if (CPDOMAvailable && [self stringValue] !== CPTokenFieldTextDidChangeValue)
{
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
}
#endif
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
+158 -154
View File
@@ -26,9 +26,11 @@
@import "_CPObject+Theme.j"
@import "CGAffineTransform.j"
@import "CPDOMDisplayServer.j"
@import "CGGeometry.j"
@import "CPAppearance.j"
@import "CPColor.j"
@import "CPCompatibility.j"
@import "CPGraphicsContext.j"
@import "CPResponder.j"
@import "CPTheme.j"
@@ -50,8 +52,6 @@
@typedef _CPViewFullScreenModeState
#if PLATFORM(DOM)
if (typeof(appkit_tag_dom_elements) !== "undefined" && appkit_tag_dom_elements)
{
AppKitTagDOMElement = function(owner, element)
@@ -68,8 +68,6 @@ else
}
}
#endif
/*
@global
@group CPViewAutoresizingMasks
@@ -123,7 +121,6 @@ CPViewFrameDidChangeNotification = @"CPViewFrameDidChangeNotification";
var CachedNotificationCenter = nil;
#if PLATFORM(DOM)
var DOMElementPrototype = nil,
BackgroundTrivialColor = 0,
@@ -132,7 +129,6 @@ var DOMElementPrototype = nil,
BackgroundNinePartImage = 3,
BackgroundTransparentColor = 4,
BackgroundCSSStyling = 5;
#endif
var CPViewFlags = { },
CPViewHasCustomDrawRect = 1 << 0,
@@ -189,7 +185,6 @@ var CPViewHighDPIDrawingEnabled = YES;
BOOL _inLiveResize;
BOOL _isSuperviewAClipView;
#if PLATFORM(DOM)
DOMElement _DOMElement;
DOMElement _DOMContentsElement;
@@ -201,7 +196,6 @@ var CPViewHighDPIDrawingEnabled = YES;
// CSS styling
CPArray _cssStylePreviousState;
DOMElement _cssStyleNode;
#endif
CGRect _dirtyRect;
@@ -230,7 +224,6 @@ var CPViewHighDPIDrawingEnabled = YES;
// Layout Support
BOOL _needsLayout;
JSObject _ephemeralSubviews;
JSObject _ephemeralSubviewsForNames;
CPSet _ephemeralSubviews;
@@ -270,16 +263,17 @@ var CPViewHighDPIDrawingEnabled = YES;
if (self !== [CPView class])
return;
#if PLATFORM(DOM)
DOMElementPrototype = document.createElement("div");
if (CPDOMAvailable)
{
DOMElementPrototype = document.createElement("div");
var style = DOMElementPrototype.style;
var style = DOMElementPrototype.style;
style.overflow = "hidden";
style.position = "absolute";
style.visibility = "visible";
style.zIndex = 0;
#endif
style.overflow = "hidden";
style.position = "absolute";
style.visibility = "visible";
style.zIndex = 0;
}
CachedNotificationCenter = [CPNotificationCenter defaultCenter];
}
@@ -410,18 +404,19 @@ var CPViewHighDPIDrawingEnabled = YES;
_theme = [CPTheme defaultTheme];
_themeState = CPThemeStateNormal;
#if PLATFORM(DOM)
_DOMElement = DOMElementPrototype.cloneNode(false);
AppKitTagDOMElement(self, _DOMElement);
if (CPDOMAvailable)
{
_DOMElement = DOMElementPrototype.cloneNode(false);
AppKitTagDOMElement(self, _DOMElement);
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, CGRectGetMinX(aFrame), CGRectGetMinY(aFrame));
CPDOMDisplayServerSetStyleSize(_DOMElement, width, height);
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, CGRectGetMinX(aFrame), CGRectGetMinY(aFrame));
CPDOMDisplayServerSetStyleSize(_DOMElement, width, height);
_DOMImageParts = [];
_DOMImageSizes = [];
_DOMImageParts = [];
_DOMImageSizes = [];
_cssStylePreviousState = @[];
#endif
_cssStylePreviousState = @[];
}
_animator = nil;
_animationsDictionary = @{};
@@ -484,20 +479,21 @@ var CPViewHighDPIDrawingEnabled = YES;
if (!_toolTipFunctionOut)
_toolTipFunctionOut = function(e) { [_CPToolTip invalidateCurrentToolTipIfNeeded]; };
#if PLATFORM(DOM)
if (_DOMElement.addEventListener)
if (CPDOMAvailable)
{
_DOMElement.addEventListener("mouseover", _toolTipFunctionIn, YES);
_DOMElement.addEventListener("keypress", _toolTipFunctionOut, YES);
_DOMElement.addEventListener("mouseout", _toolTipFunctionOut, YES);
if (_DOMElement.addEventListener)
{
_DOMElement.addEventListener("mouseover", _toolTipFunctionIn, YES);
_DOMElement.addEventListener("keypress", _toolTipFunctionOut, YES);
_DOMElement.addEventListener("mouseout", _toolTipFunctionOut, YES);
}
else if (_DOMElement.attachEvent)
{
_DOMElement.attachEvent("onmouseover", _toolTipFunctionIn);
_DOMElement.attachEvent("onkeypress", _toolTipFunctionOut);
_DOMElement.attachEvent("onmouseout", _toolTipFunctionOut);
}
}
else if (_DOMElement.attachEvent)
{
_DOMElement.attachEvent("onmouseover", _toolTipFunctionIn);
_DOMElement.attachEvent("onkeypress", _toolTipFunctionOut);
_DOMElement.attachEvent("onmouseout", _toolTipFunctionOut);
}
#endif
_toolTipInstalled = YES;
}
@@ -511,20 +507,21 @@ var CPViewHighDPIDrawingEnabled = YES;
if (!_toolTipInstalled)
return;
#if PLATFORM(DOM)
if (_DOMElement.removeEventListener)
if (CPDOMAvailable)
{
_DOMElement.removeEventListener("mouseover", _toolTipFunctionIn, YES);
_DOMElement.removeEventListener("keypress", _toolTipFunctionOut, YES);
_DOMElement.removeEventListener("mouseout", _toolTipFunctionOut, YES);
if (_DOMElement.removeEventListener)
{
_DOMElement.removeEventListener("mouseover", _toolTipFunctionIn, YES);
_DOMElement.removeEventListener("keypress", _toolTipFunctionOut, YES);
_DOMElement.removeEventListener("mouseout", _toolTipFunctionOut, YES);
}
else if (_DOMElement.detachEvent)
{
_DOMElement.detachEvent("onmouseover", _toolTipFunctionIn);
_DOMElement.detachEvent("onkeypress", _toolTipFunctionOut);
_DOMElement.detachEvent("onmouseout", _toolTipFunctionOut);
}
}
else if (_DOMElement.detachEvent)
{
_DOMElement.detachEvent("onmouseover", _toolTipFunctionIn);
_DOMElement.detachEvent("onkeypress", _toolTipFunctionOut);
_DOMElement.detachEvent("onmouseout", _toolTipFunctionOut);
}
#endif
_toolTipFunctionIn = nil;
_toolTipFunctionOut = nil;
@@ -593,10 +590,8 @@ var CPViewHighDPIDrawingEnabled = YES;
{
if (aSubview === self)
[CPException raise:CPInvalidArgumentException reason:"can't add a view as a subview of itself"];
#if DEBUG
if (!aSubview._superview && _subviews.indexOf(aSubview) !== CPNotFound)
[CPException raise:CPInvalidArgumentException reason:"can't insert a subview in duplicate (probably partially decoded)"];
#endif
// Notify the subview that it will be moving.
[aSubview viewWillMoveToSuperview:self];
@@ -620,9 +615,10 @@ var CPViewHighDPIDrawingEnabled = YES;
[_subviews removeObjectAtIndex:index];
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, aSubview._DOMElement);
#endif
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_DOMElement, aSubview._DOMElement);
}
if (anIndex > index)
--anIndex;
@@ -648,25 +644,28 @@ var CPViewHighDPIDrawingEnabled = YES;
{
_subviews.push(aSubview);
#if PLATFORM(DOM)
// Attach the actual node.
CPDOMDisplayServerAppendChild(_DOMElement, aSubview._DOMElement);
#endif
if (CPDOMAvailable)
{
// Attach the actual node.
CPDOMDisplayServerAppendChild(_DOMElement, aSubview._DOMElement);
}
}
else
{
_subviews.splice(anIndex, 0, aSubview);
#if PLATFORM(DOM)
// Attach the actual node.
CPDOMDisplayServerInsertBefore(_DOMElement, aSubview._DOMElement, _subviews[anIndex + 1]._DOMElement);
#endif
if (CPDOMAvailable)
{
// Attach the actual node.
CPDOMDisplayServerInsertBefore(_DOMElement, aSubview._DOMElement, _subviews[anIndex + 1]._DOMElement);
}
}
#if PLATFORM(DOM)
var origin = aSubview._frame.origin;
CPDOMDisplayServerSetStyleLeftTop(aSubview._DOMElement, _boundsTransform, origin.x, origin.y);
#endif
if (CPDOMAvailable)
{
var origin = aSubview._frame.origin;
CPDOMDisplayServerSetStyleLeftTop(aSubview._DOMElement, _boundsTransform, origin.x, origin.y);
}
[aSubview setNextResponder:self];
[aSubview _scaleSizeUnitSquareToSize:[self _hierarchyScaleSize]];
@@ -738,9 +737,10 @@ var CPViewHighDPIDrawingEnabled = YES;
[_superview._subviews removeObjectIdenticalTo:self];
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_superview._DOMElement, _DOMElement);
#endif
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_superview._DOMElement, _DOMElement);
}
// If the view is not hidden and one of its ancestors is hidden,
// notify the view that it is now unhidden.
@@ -823,7 +823,9 @@ var CPViewHighDPIDrawingEnabled = YES;
_subviews = [newSubviews copy];
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
var index = 0,
count = [_subviews count];
@@ -834,7 +836,6 @@ var CPViewHighDPIDrawingEnabled = YES;
CPDOMDisplayServerRemoveChild(_DOMElement, subview._DOMElement);
CPDOMDisplayServerAppendChild(_DOMElement, subview._DOMElement);
}
#endif
}
/* @ignore */
@@ -1160,14 +1161,11 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications)
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
#if PLATFORM(DOM)
if (!_inhibitDOMUpdates)
if (CPDOMAvailable && !_inhibitDOMUpdates)
{
var transform = _superview ? _superview._boundsTransform : NULL;
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, transform, origin.x, origin.y);
}
#endif
if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
[self _updateTrackingAreasWithRecursion:YES];
@@ -1206,7 +1204,8 @@ var CPViewHighDPIDrawingEnabled = YES;
[self setNeedsLayout];
[self setNeedsDisplay:YES];
#if PLATFORM(DOM)
if (CPDOMAvailable)
{
[self _setDisplayServerSetStyleSize:size];
if (_backgroundType !== BackgroundTrivialColor)
@@ -1315,7 +1314,7 @@ var CPViewHighDPIDrawingEnabled = YES;
}
}
}
#endif
}
if (_postsFrameChangedNotifications && !_inhibitFrameAndBoundsChangedNotifications)
[CachedNotificationCenter postNotificationName:CPViewFrameDidChangeNotification object:self];
@@ -1334,7 +1333,9 @@ var CPViewHighDPIDrawingEnabled = YES;
*/
- (void)_setDisplayServerSetStyleSize:(CGSize)aSize
{
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
var scale = [self scaleSize];
if (!_inhibitDOMUpdates)
@@ -1347,7 +1348,6 @@ var CPViewHighDPIDrawingEnabled = YES;
_needToSetTransformMatrix = YES;
}
#endif
}
/*!
@@ -1426,7 +1426,8 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_layer)
[_layer _owningViewBoundsChanged];
#if PLATFORM(DOM)
if (CPDOMAvailable)
{
var index = _subviews.length;
while (index--)
@@ -1436,7 +1437,7 @@ var CPViewHighDPIDrawingEnabled = YES;
CPDOMDisplayServerSetStyleLeftTop(view._DOMElement, _boundsTransform, origin.x, origin.y);
}
#endif
}
if (_postsBoundsChangedNotifications && !_inhibitFrameAndBoundsChangedNotifications)
[CachedNotificationCenter postNotificationName:CPViewBoundsDidChangeNotification object:self];
@@ -1688,11 +1689,12 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_isHidden === aFlag)
return;
// FIXME: Should we return to visibility? This breaks in FireFox, Opera, and IE.
// _DOMElement.style.visibility = (_isHidden = aFlag) ? "hidden" : "visible";
#if PLATFORM(DOM)
_DOMElement.style.display = aFlag ? "none" : "block";
#endif
// FIXME: Should we return to visibility? This breaks in FireFox, Opera, and IE.
// _DOMElement.style.visibility = (_isHidden = aFlag) ? "hidden" : "visible";
if (CPDOMAvailable)
{
_DOMElement.style.display = aFlag ? "none" : "block";
}
if (aFlag)
{
@@ -1787,9 +1789,10 @@ var CPViewHighDPIDrawingEnabled = YES;
_isHiddenOrHasHiddenAncestor = YES;
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view _recursiveGainedHiddenAncestor];
}];
{
[view _recursiveGainedHiddenAncestor];
}
];
}
/*!
@@ -1807,9 +1810,10 @@ var CPViewHighDPIDrawingEnabled = YES;
_clipsToBounds = shouldClip;
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
_DOMElement.style.overflow = _clipsToBounds ? "hidden" : "visible";
#endif
}
- (BOOL)clipsToBounds
@@ -1829,7 +1833,8 @@ var CPViewHighDPIDrawingEnabled = YES;
_opacity = anAlphaValue;
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
if (CPFeatureIsCompatible(CPOpacityRequiresFilterFeature))
{
@@ -1840,8 +1845,6 @@ var CPViewHighDPIDrawingEnabled = YES;
}
else
_DOMElement.style.opacity = anAlphaValue;
#endif
}
/*!
@@ -2031,7 +2034,9 @@ var CPViewHighDPIDrawingEnabled = YES;
_backgroundColor = aColor;
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
if (_backgroundType === BackgroundCSSStyling)
[CPColor restorePreviousCSSState:@ref(_cssStylePreviousState) forDOMElement:_DOMElement];
@@ -2309,7 +2314,6 @@ var CPViewHighDPIDrawingEnabled = YES;
}
}
}
#endif
}
/*!
@@ -2655,8 +2659,7 @@ setBoundsOrigin:
*/
- (void)_applyCSSScalingTranformations
{
#if PLATFORM(DOM)
if (_isScaled)
if (CPDOMAvailable && _isScaled)
{
var scale = [self scaleSize],
browserPropertyTransform = CPBrowserStyleProperty(@"transform"),
@@ -2667,7 +2670,6 @@ setBoundsOrigin:
[self _setDisplayServerSetStyleSize:[self frameSize]];
}
#endif
}
// Displaying
@@ -2750,14 +2752,15 @@ setBoundsOrigin:
if ([self isHidden])
return;
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
[self lockFocus];
CGContextClearRect([[CPGraphicsContext currentContext] graphicsPort], aRect);
[self drawRect:aRect];
[self unlockFocus];
#endif
}
- (void)viewWillDraw
@@ -2773,42 +2776,41 @@ setBoundsOrigin:
{
var graphicsPort = CGBitmapGraphicsContextCreate();
#if PLATFORM(DOM)
var width = CGRectGetWidth(_frame),
height = CGRectGetHeight(_frame),
devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio = CPBrowserBackingStorePixelRatio(graphicsPort);
if (CPDOMAvailable)
{
var width = CGRectGetWidth(_frame),
height = CGRectGetHeight(_frame),
devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio = CPBrowserBackingStorePixelRatio(graphicsPort);
_highDPIRatio = CPViewHighDPIDrawingEnabled ? (devicePixelRatio / backingStoreRatio) : 1;
_highDPIRatio = CPViewHighDPIDrawingEnabled ? (devicePixelRatio / backingStoreRatio) : 1;
_DOMContentsElement = graphicsPort.DOMElement;
_DOMContentsElement = graphicsPort.DOMElement;
_DOMContentsElement.style.zIndex = -100;
_DOMContentsElement.style.zIndex = -100;
_DOMContentsElement.style.overflow = "hidden";
_DOMContentsElement.style.position = "absolute";
_DOMContentsElement.style.visibility = "visible";
_DOMContentsElement.style.overflow = "hidden";
_DOMContentsElement.style.position = "absolute";
_DOMContentsElement.style.visibility = "visible";
CPDOMDisplayServerSetSize(_DOMContentsElement, width * _highDPIRatio, height * _highDPIRatio);
CPDOMDisplayServerSetSize(_DOMContentsElement, width * _highDPIRatio, height * _highDPIRatio);
CPDOMDisplayServerSetStyleLeftTop(_DOMContentsElement, NULL, 0.0, 0.0);
CPDOMDisplayServerSetStyleSize(_DOMContentsElement, width, height);
CPDOMDisplayServerSetStyleLeftTop(_DOMContentsElement, NULL, 0.0, 0.0);
CPDOMDisplayServerSetStyleSize(_DOMContentsElement, width, height);
// The performance implications of this aren't clear, but without this subviews might not be redrawn when this
// view moves.
if (CPPlatformHasBug(CPCanvasParentDrawErrorsOnMovementBug))
_DOMElement.style.webkitTransform = 'translateX(0)';
// The performance implications of this aren't clear, but without this subviews might not be redrawn when this
// view moves.
if (CPPlatformHasBug(CPCanvasParentDrawErrorsOnMovementBug))
_DOMElement.style.webkitTransform = 'translateX(0)';
CPDOMDisplayServerAppendChild(_DOMElement, _DOMContentsElement);
#endif
CPDOMDisplayServerAppendChild(_DOMElement, _DOMContentsElement);
}
_graphicsContext = [CPGraphicsContext graphicsContextWithGraphicsPort:graphicsPort flipped:YES];
_needToSetTransformMatrix = YES;
}
#if PLATFORM(DOM)
if (_needToSetTransformMatrix && _highDPIRatio !== 1)
if (CPDOMAvailable && _needToSetTransformMatrix && _highDPIRatio !== 1)
[_graphicsContext graphicsPort].setTransform(_highDPIRatio, 0, 0 , _highDPIRatio, 0, 0);
#endif
_needToSetTransformMatrix = NO;
[CPGraphicsContext setCurrentContext:_graphicsContext];
@@ -3250,10 +3252,8 @@ setBoundsOrigin:
if (_layer)
{
_layer._owningView = nil;
#if PLATFORM(DOM)
if (_layer._DOMElement && _layer._DOMElement.parentNode === _DOMElement)
if (CPDOMAvailable && _layer._DOMElement && _layer._DOMElement.parentNode === _DOMElement)
_DOMElement.removeChild(_layer._DOMElement);
#endif
}
_layer = aLayer;
@@ -3263,10 +3263,11 @@ setBoundsOrigin:
[_layer _setOwningView:self];
[_layer setFrame:[self bounds]]; // Sync layer frame with view bounds
#if PLATFORM(DOM)
_layer._DOMElement.style.zIndex = 100;
_DOMElement.appendChild(_layer._DOMElement);
#endif
if (CPDOMAvailable)
{
_layer._DOMElement.style.zIndex = 100;
_DOMElement.appendChild(_layer._DOMElement);
}
}
}
@@ -3598,9 +3599,10 @@ setBoundsOrigin:
- (void)setDOMClassName:(CPString)aClassName
{
#if PLATFORM(DOM)
if (!CPDOMAvailable)
return;
_DOMElement.className = aClassName;
#endif
}
- (BOOL)isCSSBased
@@ -3874,10 +3876,11 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
// will decode our superview before we are done decoding, at which point
// we have to have an element to place in the tree. Perhaps there is
// a more "elegant" way to do this...?
#if PLATFORM(DOM)
_DOMElement = DOMElementPrototype.cloneNode(false);
AppKitTagDOMElement(self, _DOMElement);
#endif
if (CPDOMAvailable)
{
_DOMElement = DOMElementPrototype.cloneNode(false);
AppKitTagDOMElement(self, _DOMElement);
}
// Also decode these "early".
_frame = [aCoder decodeRectForKey:CPViewFrameKey];
@@ -3934,24 +3937,25 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
[self _installToolTipEventHandlers];
// DOM SETUP
#if PLATFORM(DOM)
_cssStylePreviousState = @[];
_DOMImageParts = [];
_DOMImageSizes = [];
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, CGRectGetMinX(_frame), CGRectGetMinY(_frame));
[self _setDisplayServerSetStyleSize:_frame.size];
var index = 0,
count = _subviews.length;
for (; index < count; ++index)
if (CPDOMAvailable)
{
CPDOMDisplayServerAppendChild(_DOMElement, _subviews[index]._DOMElement);
//_subviews[index]._superview = self;
_cssStylePreviousState = @[];
_DOMImageParts = [];
_DOMImageSizes = [];
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, CGRectGetMinX(_frame), CGRectGetMinY(_frame));
[self _setDisplayServerSetStyleSize:_frame.size];
var index = 0,
count = _subviews.length;
for (; index < count; ++index)
{
CPDOMDisplayServerAppendChild(_DOMElement, _subviews[index]._DOMElement);
//_subviews[index]._superview = self;
}
}
#endif
[self setHidden:[aCoder decodeBoolForKey:CPViewIsHiddenKey]];
_isHiddenOrHasHiddenAncestor = NO;
+5 -3
View File
@@ -23,6 +23,7 @@
@import <Foundation/Foundation.j>
@import "CPAppearance.j"
@import "CPView.j"
@import "CPCompatibility.j"
@typedef CPVisualEffectMaterial
CPVisualEffectMaterialAppearanceBased = 0;
@@ -126,9 +127,10 @@ CPVisualEffectStateInactive = 2;
[self setBackgroundColor:finalColor];
#if PLATFORM(DOM)
self._DOMElement.style[prop] = shouldEnable ? "blur(30px)" : nil;
#endif
if (CPDOMAvailable)
{
self._DOMElement.style[prop] = shouldEnable ? "blur(30px)" : nil;
}
}
+5 -3
View File
@@ -22,6 +22,7 @@
@import "CPView.j"
@import "CPScrollView.j"
@import "CPCompatibility.j"
@class CPWebScriptObject
@@ -1031,9 +1032,10 @@ CPWebViewAppKitScrollMaxPollCount = 3;
_forwardStack = [];
_scrollMode = CPWebViewScrollAuto;
#if PLATFORM(DOM)
[self _initDOMWithFrame:[self frame]];
#endif
if (CPDOMAvailable)
{
[self _initDOMWithFrame:[self frame]];
}
if (![self backgroundColor])
[self setBackgroundColor:[CPColor whiteColor]];
+219 -111
View File
@@ -30,9 +30,7 @@
@import "CPDragServer.j"
@import "CPEvent.j"
@import "CPPlatformWindow.j"
#if PLATFORM(BROWSER)
@import "CPPlatformWindow+DOM.j"
#endif
@import "CPResponder.j"
@import "CPScreen.j"
@import "CPText.j"
@@ -179,6 +177,7 @@ var CPWindowActionMessageKeys = [
BOOL _isVisible;
BOOL _hasBeenOrderedIn @accessors;
BOOL _isMiniaturized;
BOOL _isExcludedFromWindowsMenu;
BOOL _isAnimating @accessors(setter=_setAnimating:);
BOOL _hasShadow;
BOOL _isMovableByWindowBackground;
@@ -247,9 +246,7 @@ var CPWindowActionMessageKeys = [
BOOL _sharesChromeWithPlatformWindow;
// Bridge Support
#if PLATFORM(DOM)
DOMElement _DOMElement;
#endif
unsigned _autoresizingMask;
@@ -373,21 +370,22 @@ CPTexturedBackgroundWindowMask
[_windowView setNextResponder:self];
// CSS Styling
#if PLATFORM(DOM)
var radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-top-left-radius"])
_windowView._DOMElement.style.borderTopLeftRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-top-right-radius"])
_windowView._DOMElement.style.borderTopRightRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-bottom-left-radius"])
_windowView._DOMElement.style.borderBottomLeftRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-bottom-right-radius"])
_windowView._DOMElement.style.borderBottomRightRadius = radius;
#endif
if (CPDOMAvailable)
{
var radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-top-left-radius"])
_windowView._DOMElement.style.borderTopLeftRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-top-right-radius"])
_windowView._DOMElement.style.borderTopRightRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-bottom-left-radius"])
_windowView._DOMElement.style.borderBottomLeftRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-bottom-right-radius"])
_windowView._DOMElement.style.borderBottomRightRadius = radius;
}
// Size calculation needs _windowView
_minSize = [self _calculateMinSizeForProposedSize:CGSizeMake(0.0, 0.0)];
@@ -400,21 +398,22 @@ CPTexturedBackgroundWindowMask
_firstResponder = self;
#if PLATFORM(DOM)
_DOMElement = document.createElement("div");
_DOMElement.style.position = "absolute";
_DOMElement.style.visibility = "visible";
_DOMElement.style.zIndex = 0;
if (![self _sharesChromeWithPlatformWindow])
if (CPDOMAvailable)
{
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, CGRectGetMinX(_frame), CGRectGetMinY(_frame));
}
_DOMElement = document.createElement("div");
CPDOMDisplayServerSetStyleSize(_DOMElement, 1, 1);
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
#endif
_DOMElement.style.position = "absolute";
_DOMElement.style.visibility = "visible";
_DOMElement.style.zIndex = 0;
if (![self _sharesChromeWithPlatformWindow])
{
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, CGRectGetMinX(_frame), CGRectGetMinY(_frame));
}
CPDOMDisplayServerSetStyleSize(_DOMElement, 1, 1);
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
}
[self setNextResponder:CPApp];
@@ -580,16 +579,18 @@ CPTexturedBackgroundWindowMask
[oldWindowView _setWindow:nil];
[oldWindowView noteToolbarChanged];
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, oldWindowView._DOMElement);
#endif
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_DOMElement, oldWindowView._DOMElement);
}
}
if (_windowView)
{
#if PLATFORM(DOM)
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
#endif
if (CPDOMAvailable)
{
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
}
var contentRect = [_contentView convertRect:[_contentView bounds] toView:nil];
@@ -785,12 +786,10 @@ CPTexturedBackgroundWindowMask
origin.x = newOrigin.x;
origin.y = newOrigin.y;
#if PLATFORM(DOM)
if (![self _sharesChromeWithPlatformWindow])
if (CPDOMAvailable && ![self _sharesChromeWithPlatformWindow])
{
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, origin.x, origin.y);
}
#endif
// reposition sheet
if ([self attachedSheet])
@@ -802,14 +801,15 @@ CPTexturedBackgroundWindowMask
var size = _frame.size,
newSize = frame.size;
// The delegate may change the proposed size, so ask it before the size is stored in _frame.
if (!_isAnimating && !CGSizeEqualToSize(size, newSize))
newSize = [self _sendDelegateWindowWillResizeToSize:CGSizeMakeCopy(newSize)];
if (!CGSizeEqualToSize(size, newSize))
{
size.width = newSize.width;
size.height = newSize.height;
if (!_isAnimating)
size = [self _sendDelegateWindowWillResizeToSize:size];
[_windowView setFrameSize:size];
if (_hasShadow)
@@ -987,25 +987,25 @@ CPTexturedBackgroundWindowMask
- (void)_orderFront
{
#if PLATFORM(DOM)
if (CPDOMAvailable)
{
if (!_isVisible)
[_platformWindow _setShouldUpdateContentRect:NO];
if (!_isVisible)
[_platformWindow _setShouldUpdateContentRect:NO];
// -dw- if a sheet is clicked, the parent window should come up too
if (_isSheet)
[_parentView orderFront:self];
// -dw- if a sheet is clicked, the parent window should come up too
if (_isSheet)
[_parentView orderFront:self];
// Save the boolean since it will be updated in the method order:window:relativeTo:
var wasVisible = _isVisible;
// Save the boolean since it will be updated in the method order:window:relativeTo:
var wasVisible = _isVisible;
[_platformWindow orderFront:self];
[_platformWindow order:CPWindowAbove window:self relativeTo:nil];
[_platformWindow orderFront:self];
[_platformWindow order:CPWindowAbove window:self relativeTo:nil];
// setFrame is set after ordering the window as this method can send some notifications
if (!wasVisible)
[self _setFrame:_frame display:YES animate:NO constrainWidth:YES constrainHeight:YES];
#endif
// setFrame is set after ordering the window as this method can send some notifications
if (!wasVisible)
[self _setFrame:_frame display:YES animate:NO constrainWidth:YES constrainHeight:YES];
}
if (!CPApp._keyWindow)
[self makeKeyWindow];
@@ -1094,15 +1094,16 @@ CPTexturedBackgroundWindowMask
if (recursive)
[_childWindows makeObjectsPerformSelector:@selector(_orderOutRecursively:) withObject:recursive];
#if PLATFORM(DOM)
if ([self _sharesChromeWithPlatformWindow])
[_platformWindow orderOut:self];
if (CPDOMAvailable)
{
if ([self _sharesChromeWithPlatformWindow])
[_platformWindow orderOut:self];
if (_isFullPlatformWindow && _platformWindow != [CPPlatformWindow primaryPlatformWindow])
[_platformWindow orderOut:self];
if (_isFullPlatformWindow && _platformWindow != [CPPlatformWindow primaryPlatformWindow])
[_platformWindow orderOut:self];
[_platformWindow order:CPWindowOut window:self relativeTo:nil];
#endif
[_platformWindow order:CPWindowOut window:self relativeTo:nil];
}
[self makeFirstResponder:nil];
[self _updateMainAndKeyWindows];
@@ -1127,10 +1128,13 @@ CPTexturedBackgroundWindowMask
[self _orderFront];
else if (orderingMode === CPWindowBelow && otherWindowNumber === 0)
[self _orderBack];
#if PLATFORM(DOM)
else
[_platformWindow order:orderingMode window:self relativeTo:CPApp._windows[otherWindowNumber]];
#endif
{
if (CPDOMAvailable)
[_platformWindow order:orderingMode window:self relativeTo:CPApp._windows[otherWindowNumber]];
}
[self _updateWindowsMenuItem];
}
/*!
@@ -1427,9 +1431,10 @@ CPTexturedBackgroundWindowMask
{
if (_shadowView)
{
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
#endif
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
}
_shadowView = nil;
}
@@ -1443,23 +1448,24 @@ CPTexturedBackgroundWindowMask
// When using CSS theming, we get rid of _shadowView and add CSS shadowing directly
// on the _windowView
#if PLATFORM(DOM)
// We must check that _windowView exists as _updateShadow can be called by
// _setSharesChromeWithPlatformWindow whereas _windowView is not yet created
if (_windowView && _windowView._DOMElement)
if (CPDOMAvailable)
{
var currentBoxShadow = _windowView._DOMElement.style.boxShadow;
if (_hasShadow && (currentBoxShadow.length == 0))
// We must check that _windowView exists as _updateShadow can be called by
// _setSharesChromeWithPlatformWindow whereas _windowView is not yet created
if (_windowView && _windowView._DOMElement)
{
_windowView._DOMElement.style.boxShadow = [_windowView actualValueForThemeAttribute:@"window-shadow-color"];
}
else if (!_hasShadow && (currentBoxShadow.length > 0))
{
_windowView._DOMElement.style.boxShadow = "";
var currentBoxShadow = _windowView._DOMElement.style.boxShadow;
if (_hasShadow && (currentBoxShadow.length == 0))
{
_windowView._DOMElement.style.boxShadow = [_windowView actualValueForThemeAttribute:@"window-shadow-color"];
}
else if (!_hasShadow && (currentBoxShadow.length > 0))
{
_windowView._DOMElement.style.boxShadow = "";
}
}
}
#endif
}
else
{
@@ -1471,15 +1477,17 @@ CPTexturedBackgroundWindowMask
[_shadowView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_shadowView setNeedsLayout];
#if PLATFORM(DOM)
CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement);
#endif
if (CPDOMAvailable)
{
CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement);
}
}
else if (!_hasShadow && _shadowView)
{
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
#endif
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
}
_shadowView = nil;
}
}
@@ -1790,6 +1798,54 @@ CPTexturedBackgroundWindowMask
[_windowView setTitle:aTitle];
[_platformWindow _setTitle:_title window:self];
[self _updateWindowsMenuItem];
}
/*!
Returns \c YES if the window is never listed in the application's Windows menu.
*/
- (BOOL)isExcludedFromWindowsMenu
{
return _isExcludedFromWindowsMenu;
}
/*!
Sets whether the window is excluded from the application's Windows menu. By default, a window
is listed while it is visible or miniaturized, has a title and can become the main window.
@param shouldBeExcluded \c YES to exclude the window from the Windows menu
*/
- (void)setExcludedFromWindowsMenu:(BOOL)shouldBeExcluded
{
if (_isExcludedFromWindowsMenu === shouldBeExcluded)
return;
_isExcludedFromWindowsMenu = shouldBeExcluded;
[self _updateWindowsMenuItem];
}
/* @ignore */
- (void)_updateWindowsMenuItem
{
if (_isExcludedFromWindowsMenu || ![_title length])
[self _removeFromWindowsMenu];
else if ([self canBecomeMainWindow])
[CPApp changeWindowsItem:self title:_title filename:NO];
// A miniaturized window stays listed, so that the user can bring it back from the menu.
else if (_isMiniaturized)
[CPApp updateWindowsItem:self];
else
[self _removeFromWindowsMenu];
}
/* @ignore */
- (void)_removeFromWindowsMenu
{
[CPApp removeWindowsItem:self];
}
/*!
@@ -1954,34 +2010,42 @@ CPTexturedBackgroundWindowMask
return [[self firstResponder] keyUp:anEvent];
case CPKeyDown:
// Tab and Shift-Tab move to the next and previous key view, unless the first responder takes
// them itself, as an editable text view does to insert a tab character or change a list
// item's level. Control-Tab always moves on, as in Cocoa.
var modifierFlags = [anEvent modifierFlags],
firstResponderTakesTab = !(modifierFlags & CPControlKeyMask) && [[self firstResponder] _wantsTabKey];
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter &&
!([anEvent modifierFlags] & (CPAlternateKeyMask | CPCommandKeyMask)))
!(modifierFlags & (CPAlternateKeyMask | CPCommandKeyMask)) &&
!firstResponderTakesTab)
{
if ([anEvent modifierFlags] & CPShiftKeyMask)
[self selectPreviousKeyView:self];
else
[self selectNextKeyView:self];
#if PLATFORM(DOM)
// Make sure the browser doesn't try to do its own tab handling.
// This is important or the browser might blur the shared text field or token field input field,
// even that we just moved it to a new first responder.
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
#endif
if (CPDOMAvailable)
{
// Make sure the browser doesn't try to do its own tab handling.
// This is important or the browser might blur the shared text field or token field input field,
// even that we just moved it to a new first responder.
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
}
return;
}
else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter)
else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter && !firstResponderTakesTab)
{
var didTabBack = [self selectPreviousKeyView:self];
if (didTabBack)
{
#if PLATFORM(DOM)
// Make sure the browser doesn't try to do its own tab handling.
// This is important or the browser might blur the shared text field or token field input field,
// even that we just moved it to a new first responder.
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
#endif
if (CPDOMAvailable)
{
// Make sure the browser doesn't try to do its own tab handling.
// This is important or the browser might blur the shared text field or token field input field,
// even that we just moved it to a new first responder.
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
}
}
return didTabBack;
}
@@ -1990,6 +2054,10 @@ CPTexturedBackgroundWindowMask
return;
}
// A first responder that takes Tab gets the key, and the browser must not also move its own focus.
if (firstResponderTakesTab && [anEvent charactersIgnoringModifiers] === CPTabCharacter && CPDOMAvailable)
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
[[self firstResponder] keyDown:anEvent];
// Trigger the default button if needed
@@ -2434,6 +2502,8 @@ CPTexturedBackgroundWindowMask
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidMiniaturizeNotification object:self];
_isMiniaturized = YES;
[self _updateWindowsMenuItem];
}
/*!
@@ -2446,6 +2516,8 @@ CPTexturedBackgroundWindowMask
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidDeminiaturizeNotification object:self];
_isMiniaturized = NO;
[self _updateWindowsMenuItem];
}
/*!
@@ -2550,6 +2622,9 @@ CPTexturedBackgroundWindowMask
[self _orderOutRecursively:NO];
[self _detachFromChildrenClosing:!_parentWindow];
// A closed window leaves the Windows menu, even if it was miniaturized.
[self _removeFromWindowsMenu];
if (_releasedWhenClosed)
[_contentView _releaseRecursively];
}
@@ -2631,6 +2706,8 @@ CPTexturedBackgroundWindowMask
[_windowView noteMainWindowStateChanged];
[CPApp updateWindowsItem:self];
[[CPNotificationCenter defaultCenter]
postNotificationName:CPWindowDidBecomeMainNotification
object:self];
@@ -2650,6 +2727,8 @@ CPTexturedBackgroundWindowMask
_platformWindow._currentMainWindow = nil;
[_windowView noteMainWindowStateChanged];
[CPApp updateWindowsItem:self];
}
- (void)_updateMainAndKeyWindows
@@ -2764,6 +2843,37 @@ CPTexturedBackgroundWindowMask
[toolbar setVisible:![toolbar isVisible]];
}
/*!
Validates the user interface items whose actions the window handles.
For \c -toggleToolbarShown:, the item's title becomes "Hide Toolbar" or "Show Toolbar"
to reflect the toolbar's visibility, and the item is enabled only when the window has
a toolbar. \c -undo: and \c -redo: are enabled only when the window's undo manager has a
change to undo or redo. Every other item is enabled.
@param anItem the item to validate
@return \c YES if the item should be enabled
*/
- (BOOL)validateUserInterfaceItem:(id /*<CPValidatedUserInterfaceItem>*/)anItem
{
var action = [anItem action];
if (action === @selector(undo:))
return [[self undoManager] canUndo];
if (action === @selector(redo:))
return [[self undoManager] canRedo];
if (action !== @selector(toggleToolbarShown:))
return YES;
var toolbar = [self toolbar];
if ([anItem respondsToSelector:@selector(setTitle:)])
[anItem setTitle:[toolbar isVisible] ? @"Hide Toolbar" : @"Show Toolbar"];
return !!toolbar;
}
- (void)_noteToolbarChanged
{
var frame = CGRectMakeCopy([self frame]),
@@ -3718,12 +3828,10 @@ var keyViewComparator = function(lhs, rhs, context)
*/
- (CGPoint)_nativeScrollOffset
{
#if PLATFORM(DOM)
return CGPointMake(_windowView._DOMElement.scrollLeft, _windowView._DOMElement.scrollTop);
#else
CGPointMake(0, 0);
#endif
if (CPDOMAvailable)
return CGPointMake(_windowView._DOMElement.scrollLeft, _windowView._DOMElement.scrollTop);
CGPointMake(0, 0);
}
/*!
@@ -21,6 +21,7 @@
*/
@import "_CPWindowView.j"
@import "CPCompatibility.j"
@implementation _CPBorderlessBridgeWindowView : _CPWindowView
{
@@ -106,10 +107,11 @@
if (_frame.size.width < theWindow._minSize.width || _frame.size.height < theWindow._minSize.height)
[theWindow._contentView setFrameSize:CGSizeMake(MAX(_frame.size.width, theWindow._minSize.width), MAX(_frame.size.height, theWindow._minSize.height))];
#if PLATFORM(DOM)
_DOMElement.style.overflowX = (_frame.size.width < theWindow._minSize.width)? "scroll":"hidden";
_DOMElement.style.overflowY = (_frame.size.height < theWindow._minSize.height)? "scroll":"hidden";
#endif
if (CPDOMAvailable)
{
_DOMElement.style.overflowX = (_frame.size.width < theWindow._minSize.width)? "scroll":"hidden";
_DOMElement.style.overflowY = (_frame.size.height < theWindow._minSize.height)? "scroll":"hidden";
}
}
@end
+22
View File
@@ -715,6 +715,28 @@ _CPWindowViewResizeSlop = 3;
frame.size.height += dy;
}
// Let the delegate adjust the size. When resizing from the left or top, move the origin
// by the adjustment so that the edge opposite the one being dragged stays in place.
var delegateSize = [theWindow _sendDelegateWindowWillResizeToSize:CGSizeMakeCopy(frame.size)];
switch (_resizeRegion)
{
case _CPWindowViewResizeRegionTopLeft:
case _CPWindowViewResizeRegionLeft:
case _CPWindowViewResizeRegionBottomLeft:
frame.origin.x -= delegateSize.width - frame.size.width;
}
switch (_resizeRegion)
{
case _CPWindowViewResizeRegionTopLeft:
case _CPWindowViewResizeRegionTop:
case _CPWindowViewResizeRegionTopRight:
frame.origin.y -= delegateSize.height - frame.size.height;
}
frame.size = delegateSize;
[theWindow _setFrame:frame display:YES animate:NO constrainWidth:NO constrainHeight:NO];
[self setCursorForLocation:location resizing:YES];
}
-2
View File
@@ -81,9 +81,7 @@ var _CPCibCustomObjectClassName = @"_CPCibCustomObjectClassName";
if (!theClass)
{
#if DEBUG
CPLog("Unknown class \"" + _className + "\" in cib file");
#endif
theClass = [CPObject class];
}
-2
View File
@@ -82,9 +82,7 @@ var _CPCibCustomViewClassNameKey = @"_CPCibCustomViewClassNameKey";
// FIXME: Should we instead throw an exception?
if (!theClass)
{
#if DEBUG
CPLog("Unknown class \"" + _className + "\" in cib file, using CPView instead.");
#endif
theClass = [CPView class];
}
-2
View File
@@ -91,9 +91,7 @@
if (!windowClass)
{
#if DEBUG
CPLog.warn("Unknown class \"%@\" in cib file, using CPWindow instead.", [self windowClass]);
#endif
windowClass = [CPWindow class];
}
+4 -1
View File
@@ -23,7 +23,10 @@
@import "CGGeometry.j"
@import "CPCompatibility.j"
#define PIXEL(pixels) pixels + "px";
function PIXEL(pixels)
{
return pixels + "px";
}
function CABackingStoreGetContext(aBackingStore)
+67 -58
View File
@@ -28,11 +28,15 @@
@import "CGContext.j"
@import "CGGeometry.j"
@import "CPColor.j"
@import "CPCompatibility.j"
@import "CPView.j"
@import "CAMediaTimingFunction.j"
#define DOM(aLayer) aLayer._DOMElement
function DOM(aLayer)
{
return aLayer._DOMElement;
}
var CALayerGeometryBoundsMask = 1,
CALayerGeometryPositionMask = 2,
@@ -166,18 +170,19 @@ var CALayerRegisteredRunLoopUpdates = nil;
_activeAnimations = [CPMutableDictionary dictionary];
#if PLATFORM(DOM)
_DOMElement = document.createElement("div");
if (CPDOMAvailable)
{
_DOMElement = document.createElement("div");
_DOMElement.style.overflow = "visible";
_DOMElement.style.position = "absolute";
_DOMElement.style.visibility = "visible";
_DOMElement.style.top = "0px";
_DOMElement.style.left = "0px";
_DOMElement.style.zIndex = 0;
_DOMElement.style.width = "0px";
_DOMElement.style.height = "0px";
#endif
_DOMElement.style.overflow = "visible";
_DOMElement.style.position = "absolute";
_DOMElement.style.visibility = "visible";
_DOMElement.style.top = "0px";
_DOMElement.style.left = "0px";
_DOMElement.style.zIndex = 0;
_DOMElement.style.width = "0px";
_DOMElement.style.height = "0px";
}
}
return self;
@@ -503,47 +508,48 @@ var CALayerRegisteredRunLoopUpdates = nil;
*/
- (void)display
{
#if PLATFORM(DOM)
if (!_context)
if (CPDOMAvailable)
{
_context = CGBitmapGraphicsContextCreate();
if (!_context)
{
_context = CGBitmapGraphicsContextCreate();
_DOMContentsElement = _context.DOMElement;
_DOMContentsElement = _context.DOMElement;
_DOMContentsElement.style.zIndex = -100;
_DOMContentsElement.style.zIndex = -100;
_DOMContentsElement.style.overflow = "hidden";
_DOMContentsElement.style.position = "absolute";
_DOMContentsElement.style.visibility = "visible";
_DOMContentsElement.style.overflow = "hidden";
_DOMContentsElement.style.position = "absolute";
_DOMContentsElement.style.visibility = "visible";
_DOMContentsElement.width = ROUND(CGRectGetWidth(_backingStoreFrame));
_DOMContentsElement.height = ROUND(CGRectGetHeight(_backingStoreFrame));
_DOMContentsElement.width = ROUND(CGRectGetWidth(_backingStoreFrame));
_DOMContentsElement.height = ROUND(CGRectGetHeight(_backingStoreFrame));
_DOMContentsElement.style.top = "0px";
_DOMContentsElement.style.left = "0px";
_DOMContentsElement.style.width = ROUND(CGRectGetWidth(_backingStoreFrame)) + "px";
_DOMContentsElement.style.height = ROUND(CGRectGetHeight(_backingStoreFrame)) + "px";
_DOMContentsElement.style.top = "0px";
_DOMContentsElement.style.left = "0px";
_DOMContentsElement.style.width = ROUND(CGRectGetWidth(_backingStoreFrame)) + "px";
_DOMContentsElement.style.height = ROUND(CGRectGetHeight(_backingStoreFrame)) + "px";
_DOMElement.appendChild(_DOMContentsElement);
_DOMElement.appendChild(_DOMContentsElement);
}
if (USE_BUFFER)
{
if (_delegateRespondsToDisplayLayerSelector)
return [_delegate displayLayer:self];
if (CGRectGetWidth(_backingStoreFrame) == 0.0 || CGRectGetHeight(_backingStoreFrame) == 0.0)
return;
if (!_contents)
_contents = CABackingStoreCreate();
CABackingStoreSetSize(_contents, _bounds.size);
[self drawInContext:CABackingStoreGetContext(_contents)];
}
}
if (USE_BUFFER)
{
if (_delegateRespondsToDisplayLayerSelector)
return [_delegate displayLayer:self];
if (CGRectGetWidth(_backingStoreFrame) == 0.0 || CGRectGetHeight(_backingStoreFrame) == 0.0)
return;
if (!_contents)
_contents = CABackingStoreCreate();
CABackingStoreSetSize(_contents, _bounds.size);
[self drawInContext:CABackingStoreGetContext(_contents)];
}
#endif
[self composite];
}
@@ -664,10 +670,6 @@ var CALayerRegisteredRunLoopUpdates = nil;
return _superlayer;
}
#define ADJUST_CONTENTS_ZINDEX(aLayer)\
if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)\
_DOMContentsElement.style.zIndex -= 100.0;\
/*!
Adds the specified layer as a sublayer of the receiver.
*/
@@ -720,16 +722,17 @@ if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)
else if (superlayer != nil)
[aLayer removeFromSuperlayer];
ADJUST_CONTENTS_ZINDEX(aLayer);
[self _adjustContentsZIndexForLayer:aLayer];
[_sublayers insertObject:aLayer atIndex:anIndex];
#if PLATFORM(DOM)
if (anIndex >= _sublayers.length - 1)
_DOMElement.appendChild(DOM(aLayer));
else
_DOMElement.insertBefore(DOM(aLayer), _sublayers[anIndex + 1]._DOMElement);
#endif
if (CPDOMAvailable)
{
if (anIndex >= _sublayers.length - 1)
_DOMElement.appendChild(DOM(aLayer));
else
_DOMElement.insertBefore(DOM(aLayer), _sublayers[anIndex + 1]._DOMElement);
}
aLayer._superlayer = self;
@@ -782,7 +785,7 @@ if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)
return;
}
ADJUST_CONTENTS_ZINDEX(aLayer);
[self _adjustContentsZIndexForLayer:aLayer];
[_sublayers replaceObjectAtIndex:[_sublayers indexOfObjectIdenticalTo:aSublayer] withObject:aLayer];
_DOMElement.replaceChild(DOM(aSublayer), DOM(aLayer));
@@ -1267,6 +1270,12 @@ if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)
window.loop = false;
}
- (void)_adjustContentsZIndexForLayer:(CALayer)aLayer
{
if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)
_DOMContentsElement.style.zIndex -= 100.0;
}
@end
function _CALayerUpdateSublayerTransformForSublayers(aLayer)
@@ -1284,7 +1293,8 @@ function _CALayerUpdateSublayerTransformForSublayers(aLayer)
function _CALayerUpdateDOM(aLayer, aMask)
{
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
var DOMElementStyle = aLayer._DOMElement.style;
if (aMask & CALayerZPositionUpdateMask)
@@ -1315,7 +1325,6 @@ function _CALayerUpdateDOM(aLayer, aMask)
DOMContentsElement.style.height = height + "px";
}
}
#endif
}
function _CALayerRecalculateGeometry(aLayer, aGeometryChange)
-34
View File
@@ -70,9 +70,6 @@ var _CPAnimationContextStack = nil,
{
if (!_animationFlushingObserver)
{
#if (DEBUG)
CPLog.debug("create new observer");
#endif
_animationFlushingObserver = CFRunLoopObserverCreate(2, true, 0, _animationFlushingObserverCallback, 0);
CFRunLoopAddObserver([CPRunLoop mainRunLoop], _animationFlushingObserver);
}
@@ -101,9 +98,6 @@ var _CPAnimationContextStack = nil,
[context _flushAnimations];
[_CPAnimationContextStack removeLastObject];
#if (DEBUG)
CPLog.debug(_cmd + "context stack =" + _CPAnimationContextStack);
#endif
return YES;
}
@@ -234,9 +228,6 @@ var _CPAnimationContextStack = nil,
{
if (_completionHandlerAgent)
{
#if (DEBUG)
CPLog.debug("No animations are scheduled. Firing completion handler");
#endif
_completionHandlerAgent.fire();
}
}
@@ -266,9 +257,6 @@ var _CPAnimationContextStack = nil,
{
if (n == 0)
{
#if (DEBUG)
CPLog.debug("Animations are not needed. Firing completion handler");
#endif
_completionHandlerAgent.fire();
}
else
@@ -278,18 +266,12 @@ var _CPAnimationContextStack = nil,
// start timers
while(k--)
{
#if (DEBUG)
CPLog.debug("START TIMER " + timers[k].description());
#endif
timers[k].start();
}
// start css animations
while(n--)
{
#if (DEBUG)
CPLog.debug("START ANIMATION " + cssAnimations[n].description());
#endif
cssAnimations[n].start();
}
}
@@ -350,9 +332,6 @@ var _CPAnimationContextStack = nil,
action.completion = function()
{
[aSubview setFrame:targetFrame];
#if (DEBUG)
CPLog.debug(aSubview + " setFrame: " + CPStringFromRect(targetFrame));
#endif
if (idx == count - 1)
[animatorClass stopUpdaterWithIdentifier:[anAction.root UID]];
};
@@ -401,9 +380,6 @@ var _CPAnimationContextStack = nil,
if (aCompletionHandler)
{
_completionHandlerAgent = new CompletionHandlerAgent(aCompletionHandler);
#if (DEBUG)
CPLog.debug("created a new completion Agent with id " + _completionHandlerAgent.id);
#endif
}
else
{
@@ -563,9 +539,6 @@ CompletionHandlerAgent.prototype.invalidate = function()
var _animationFlushingObserverCallback = function()
{
#if (DEBUG)
CPLog.debug("_animationFlushingObserverCallback");
#endif
if ([_CPAnimationContextStack count] == 1)
{
var context = [_CPAnimationContextStack lastObject];
@@ -573,15 +546,8 @@ var _animationFlushingObserverCallback = function()
[_CPAnimationContextStack removeLastObject];
}
#if (DEBUG)
CPLog.debug("_animationFlushingObserver "+_animationFlushingObserver+" stack:" + [_CPAnimationContextStack count]);
#endif
if (_animationFlushingObserver && ![_CPAnimationContextStack count])
{
#if (DEBUG)
CPLog.debug("removeObserver");
#endif
CFRunLoopObserverInvalidate([CPRunLoop mainRunLoop], _animationFlushingObserver);
_animationFlushingObserver = nil;
}
+3 -4
View File
@@ -394,11 +394,10 @@ var frameToCSSTranslationTransformMatrix = function(start, current)
- (Object)_DOMElement
{
#if PLATFORM(DOM)
return _DOMElement;
#else
if (CPDOMAvailable)
return _DOMElement;
return nil;
#endif
}
- (CPString)debug_description
+1 -1
View File
@@ -212,7 +212,7 @@ function CGColorEqualToColor(lhs, rhs)
if (lhs.pattern != rhs.pattern)
return false;
if (CGColorSpaceEqualToColorSpace(lhs.colorspace, rhs.colorspace))
if (lhs.colorspace !== rhs.colorspace)
return false;
return true;
+28 -22
View File
@@ -69,8 +69,10 @@ kCGColorSpaceSRGB = "CGColorSpaceSRGB";
var _CGNamedColorSpaces = {};
#define _CGColorSpaceCreateWithModel(aModel, aComponentCount, aBaseColorSpace) \
{ model:aModel, count:aComponentCount, base:aBaseColorSpace }
function _CGColorSpaceCreateWithModel(aModel, aComponentCount, aBaseColorSpace)
{
return { model:aModel, count:aComponentCount, base:aBaseColorSpace };
}
function CGColorSpaceCreateCalibratedGray(aWhitePoint, aBlackPoint, gamma)
{
@@ -208,24 +210,6 @@ function CGColorSpaceRetain(aColorSpace)
}
// FIXME: We should refer to some default values.
#define STANDARDIZE(components, index, minimum, maximum, multiplier) \
{ \
if (index > components.length) \
{ \
components[index] = maximum; \
return; \
} \
\
var component = components[index]; \
\
if (component < minimum) \
components[index] = minimum; \
else if (component > maximum) \
components[index] = maximum; \
else \
components[index] = ROUND(component * multiplier) / multiplier; \
}
function CGColorSpaceStandardizeComponents(aColorSpace, components)
{
var count = aColorSpace.count;
@@ -233,7 +217,20 @@ function CGColorSpaceStandardizeComponents(aColorSpace, components)
// Standardize the alpha value. We allow the alpha value to have a
// higher precision than other components since it is not ultimately
// bound to 256 bits like RGB.
STANDARDIZE(components, count, 0, 1, 1000);
if (count > components.length)
{
components[count] = 1;
return;
}
var component = components[count];
if (component < 0)
components[count] = 0;
else if (component > 1)
components[count] = 1;
else
components[count] = ROUND(component * 1000) / 1000;
if (aColorSpace.base)
aColorSpace = aColorSpace.base;
@@ -245,7 +242,16 @@ function CGColorSpaceStandardizeComponents(aColorSpace, components)
case kCGColorSpaceModelCMYK:
case kCGColorSpaceModelDeviceN:
while (count--)
STANDARDIZE(components, count, 0, 1, 255);
{
var standardizingComponent = components[count];
if (standardizingComponent < 0)
components[count] = 0;
else if (standardizingComponent > 1)
components[count] = 1;
else
components[count] = ROUND(standardizingComponent * 255) / 255;
}
break;
// We don't currently support these color spaces.
+650 -4
View File
@@ -730,11 +730,657 @@ function CGContextStrokeRoundedRectangleInRect(aContext, aRect, aRadius, ne, se,
*/
if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
{
#include "CGContextCanvas.j"
}
else if (CPFeatureIsCompatible(CPVMLFeature))
// --- begin CoreGraphics/CGContextCanvas.j (inlined; include fragment removed for preprocessor-free build) ---
var CANVAS_LINECAP_TABLE = [ "butt", "round", "square" ],
CANVAS_LINEJOIN_TABLE = [ "miter", "round", "bevel" ],
CANVAS_COMPOSITE_TABLE = [ "source-over", "source-over", "source-over", "source-over", "darker",
"lighter", "source-over", "source-over", "source-over", "source-over",
"source-over", "source-over", "source-over", "source-over", "source-over",
"source-over", "source-over",
"copy", "source-in", "source-out", "source-atop",
"destination-over", "destination-in", "destination-out", "destination-atop",
"xor", "source-over", "source-over" ];
function _CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, anticlockwise)
{
#include "CGContextVML.j"
return aContext.arc(x, y, radius, startAngle, endAngle, anticlockwise);
}
function _CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius)
{
return aContext.arcTo(x1, y1, x2, y2, radius);
}
function _CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y)
{
return aContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
function _CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y)
{
return aContext.quadraticCurveTo(cpx, cpy, x, y);
}
function _CGContextAddLineToPointCanvas(aContext, x, y)
{
return aContext.lineTo(x, y);
}
function _CGContextClosePathCanvas(aContext)
{
return aContext.closePath();
}
function _CGContextMoveToPointCanvas(aContext, x, y)
{
return aContext.moveTo(x, y);
}
function _CGContextAddRectCanvas(aContext, aRect)
{
return aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
}
function _CGContextBeginPathCanvas(aContext)
{
return aContext.beginPath();
}
function _CGContextFillRectCanvas(aContext, aRect)
{
return aContext.fillRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
}
function _CGContextClipCanvas(aContext)
{
return aContext.clip();
}
// In Cocoa, all primitives excepts rects and arcs cannot be added to the context's path
// until a move to point has been done, because an empty path has no current point.
var hasPath = function(aContext, methodName)
{
if (!aContext.hasPath)
CPLog.error(methodName + ": no current point");
return aContext.hasPath;
}
function CGContextSaveGState(aContext)
{
aContext.save();
}
function CGContextRestoreGState(aContext)
{
aContext.restore();
}
function CGContextSetLineCap(aContext, aLineCap)
{
aContext.lineCap = CANVAS_LINECAP_TABLE[aLineCap];
}
function CGContextSetLineDash(aContext, aPhase, someDashes)
{
if (aContext.setLineDash)
{
aContext.setLineDash(someDashes);
aContext.lineDashOffset = aPhase;
}
else if (typeof aContext['webkitLineDash'] !== 'undefined')
{
aContext.webkitLineDash = someDashes;
aContext.webkitLineDashOffset = aPhase;
}
else if (typeof aContext['mozDash'] !== 'undefined')
{
aContext.mozDash = someDashes;
aContext.mozDashOffset = aPhase;
}
else if (someDashes)
{
CPLog.warn("CGContextSetLineDash not implemented in this environment.")
}
}
function CGContextSetLineJoin(aContext, aLineJoin)
{
aContext.lineJoin = CANVAS_LINEJOIN_TABLE[aLineJoin];
}
function CGContextSetLineWidth(aContext, aLineWidth)
{
aContext.lineWidth = aLineWidth;
}
function CGContextSetMiterLimit(aContext, aMiterLimit)
{
aContext.miterLimit = aMiterLimit;
}
function CGContextSetBlendMode(aContext, aBlendMode)
{
aContext.globalCompositeOperation = CANVAS_COMPOSITE_TABLE[aBlendMode];
}
function CGContextAddArc(aContext, x, y, radius, startAngle, endAngle, clockwise)
{
// Despite the documentation saying otherwise, the last parameter is anti-clockwise not clockwise.
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes#Arcs
_CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, !clockwise);
// AddArc implicitly starts a path
aContext.hasPath = YES;
}
function CGContextAddArcToPoint(aContext, x1, y1, x2, y2, radius)
{
if (!hasPath(aContext, "CGContextAddArcToPoint"))
return;
_CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius);
}
function CGContextAddCurveToPoint(aContext, cp1x, cp1y, cp2x, cp2y, x, y)
{
if (!hasPath(aContext, "CGContextAddCurveToPoint"))
return;
_CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y);
}
function CGContextAddLines(aContext, points, count)
{
// implementation mirrors that of CGPathAddLines()
if (count == null)
count = points.length;
if (count < 1)
return;
_CGContextMoveToPointCanvas(aContext, points[0].x, points[0].y);
for (var i = 1; i < count; ++i)
_CGContextAddLineToPointCanvas(aContext, points[i].x, points[i].y);
aContext.hasPath = YES;
}
function CGContextAddLineToPoint(aContext, x, y)
{
if (!hasPath(aContext, "CGContextAddLineToPoint"))
return;
_CGContextAddLineToPointCanvas(aContext, x, y);
}
function CGContextAddPath(aContext, aPath)
{
if (!aContext || CGPathIsEmpty(aPath))
return;
// If the context does not have a path, explicitly begin one
if (!aContext.hasPath)
_CGContextBeginPathCanvas(aContext);
// We must implicitly move to the start of the path
_CGContextMoveToPointCanvas(aContext, aPath.start.x, aPath.start.y);
var elements = aPath.elements,
i = 0,
count = aPath.count;
for (; i < count; ++i)
{
var element = elements[i],
type = element.type;
switch (type)
{
case kCGPathElementMoveToPoint:
_CGContextMoveToPointCanvas(aContext, element.x, element.y);
break;
case kCGPathElementAddLineToPoint:
_CGContextAddLineToPointCanvas(aContext, element.x, element.y);
break;
case kCGPathElementAddQuadCurveToPoint:
_CGContextAddQuadCurveToPointCanvas(aContext, element.cpx, element.cpy, element.x, element.y);
break;
case kCGPathElementAddCurveToPoint:
_CGContextAddCurveToPointCanvas(aContext, element.cp1x, element.cp1y, element.cp2x, element.cp2y, element.x, element.y);
break;
case kCGPathElementCloseSubpath:
_CGContextClosePathCanvas(aContext);
break;
case kCGPathElementAddArc:
_CGContextAddArcCanvas(aContext, element.x, element.y, element.radius, element.startAngle, element.endAngle, element.clockwise);
break;
case kCGPathElementAddArcToPoint:
_CGContextAddArcToPointCanvas(aContext, element.p1x, element.p1y, element.p2x, element.p2y, element.radius);
break;
}
}
aContext.hasPath = YES;
}
function CGContextAddRect(aContext, aRect)
{
_CGContextAddRectCanvas(aContext, aRect);
aContext.hasPath = YES;
}
function CGContextAddQuadCurveToPoint(aContext, cpx, cpy, x, y)
{
if (!hasPath(aContext, "CGContextAddQuadCurveToPoint"))
return;
_CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y);
}
function CGContextAddRects(aContext, rects, count)
{
if (count == null)
count = rects.length;
for (var i = 0; i < count; ++i)
{
var rect = rects[i];
_CGContextAddRectCanvas(aContext, rect);
}
aContext.hasPath = YES;
}
function CGContextBeginPath(aContext)
{
_CGContextBeginPathCanvas(aContext);
aContext.hasPath = NO;
}
function CGContextClosePath(aContext)
{
_CGContextClosePathCanvas(aContext);
}
function CGContextIsPathEmpty(aContext)
{
return !aContext.hasPath;
}
function CGContextMoveToPoint(aContext, x, y)
{
_CGContextMoveToPointCanvas(aContext, x, y);
aContext.hasPath = YES;
}
function CGContextClearRect(aContext, aRect)
{
aContext.clearRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
aContext.hasPath = NO;
}
function CGContextDrawPath(aContext, aMode)
{
if (!aContext.hasPath)
return;
if (aMode === kCGPathFill || aMode === kCGPathFillStroke)
aContext.fill();
else if (aMode === kCGPathStroke || aMode === kCGPathFillStroke || aMode === kCGPathEOFillStroke)
aContext.stroke();
else if (aMode === kCGPathEOFill || aMode === kCGPathEOFillStroke)
CPLog.warn("Unimplemented fill mode in CGContextDrawPath: %d", aMode);
aContext.hasPath = NO;
}
function CGContextFillRect(aContext, aRect)
{
_CGContextFillRectCanvas(aContext, aRect);
aContext.hasPath = NO;
}
function CGContextFillRects(aContext, rects, count)
{
if (count == null)
count = rects.length;
for (var i = 0; i < count; ++i)
{
var rect = rects[i];
_CGContextFillRectCanvas(aContext, rect);
}
aContext.hasPath = NO;
}
function CGContextStrokeRect(aContext, aRect)
{
aContext.strokeRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
aContext.hasPath = NO;
}
function CGContextClip(aContext)
{
_CGContextClipCanvas(aContext);
aContext.hasPath = NO;
}
function CGContextClipToRect(aContext, aRect)
{
_CGContextBeginPathCanvas(aContext);
_CGContextAddRectCanvas(aContext, aRect);
_CGContextClosePathCanvas(aContext);
_CGContextClipCanvas(aContext);
aContext.hasPath = NO;
}
function CGContextClipToRects(aContext, rects, count)
{
if (count == null)
count = rects.length;
_CGContextBeginPathCanvas(aContext);
CGContextAddRects(aContext, rects, count);
_CGContextClipCanvas(aContext);
aContext.hasPath = NO;
}
function CGContextSetAlpha(aContext, anAlpha)
{
aContext.globalAlpha = anAlpha;
}
function CGContextSetFillColor(aContext, aColor)
{
var patternImage = [aColor patternImage];
if ([patternImage isSingleImage])
{
var pattern = aContext.createPattern([patternImage image], "repeat");
aContext.fillStyle = pattern;
}
else
aContext.fillStyle = [aColor cssString];
}
/*!
Creates a context into which you can render a fill pattern
of the given size. Once the pattern is rendered, you can
set the fill or stroke pattern to the rendered pattern
with CGContextSetFillPattern or CGContextSetStrokePattern.
*/
function CGContextCreatePatternContext(aContext, aSize)
{
var pattern = document.createElement("canvas");
pattern.width = aSize.width;
pattern.height = aSize.height;
return pattern.getContext("2d");
}
/*!
Sets the fill pattern for aContext to the rendered pattern context
returned by CGContextCreatePatternContext.
*/
function CGContextSetFillPattern(aContext, aPatternContext)
{
var pattern = aContext.createPattern(aPatternContext.canvas, "repeat");
aContext.fillStyle = pattern;
}
/*!
Sets the stroke pattern for aContext to the rendered pattern context
returned by CGContextCreatePatternContext.
*/
function CGContextSetStrokePattern(aContext, aPatternContext)
{
var pattern = aContext.createPattern(aPatternContext.canvas, "repeat");
aContext.strokeStyle = pattern;
}
function CGContextSetStrokeColor(aContext, aColor)
{
var patternImage = [aColor patternImage];
if ([patternImage isSingleImage])
{
var pattern = aContext.createPattern([patternImage image], "repeat");
aContext.strokeStyle = pattern;
}
else
aContext.strokeStyle = [aColor cssString];
}
function CGContextSetShadow(aContext, aSize, aBlur)
{
aContext.shadowOffsetX = aSize.width;
aContext.shadowOffsetY = aSize.height;
aContext.shadowBlur = aBlur;
aContext.shadowColor = [[CPColor shadowColor] cssString];
}
function CGContextSetShadowWithColor(aContext, aSize, aBlur, aColor)
{
aContext.shadowOffsetX = aSize.width;
aContext.shadowOffsetY = aSize.height;
aContext.shadowBlur = aBlur;
aContext.shadowColor = [aColor cssString];
}
function CGContextRotateCTM(aContext, anAngle)
{
aContext.rotate(anAngle);
}
function CGContextScaleCTM(aContext, sx, sy)
{
aContext.scale(sx, sy);
}
function CGContextTranslateCTM(aContext, tx, ty)
{
aContext.translate(tx, ty);
}
var scale_rotate = function(a, b, c, d)
{
var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0,
a2 = (ATAN2(b, d) + ATAN2(-sign * c, sign * a)) / 2.0,
cos = COS(a2),
sin = SIN(a2),
abs_cos,
abs_sin;
if (cos === 0)
{
sx = -c / sin;
sy = b / sin;
}
else if (sin === 0)
{
sx = a / cos;
sy = d / cos;
}
else
{
abs_cos = ABS(cos);
abs_sin = ABS(sin);
sx = (abs_cos * a / cos + abs_sin * -c / sin) / (abs_cos + abs_sin);
sy = (abs_cos * d / cos + abs_sin * b / sin) / (abs_cos + abs_sin);
}
};
var rotate_scale = function(a, b, c, d)
{
var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0,
a1 = (ATAN2(sign * b, sign * a) + ATAN2(-c, d)) / 2.0,
cos = COS(a1),
sin = SIN(a1),
abs_cos,
abs_sin;
if (cos === 0)
{
sx = b / sin;
sy = -c / sin;
}
else if (sin === 0)
{
sx = a / cos;
sy = d / cos;
}
else
{
abs_cos = ABS(cos);
abs_sin = ABS(sin);
sx = (abs_cos * a / cos + abs_sin * b / sin) / (abs_cos + abs_sin);
sy = (abs_cos * d / cos + abs_sin * -c / sin) / (abs_cos + abs_sin);
}
};
function eigen(anAffineTransform)
{
CPLog.warn("Unimplemented function: eigen");
}
if (CPFeatureIsCompatible(CPJavaScriptCanvasTransformFeature))
{
CGContextConcatCTM = function(aContext, anAffineTransform)
{
aContext.transform(anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty);
};
}
else
{
CGContextConcatCTM = function(aContext, anAffineTransform)
{
var a = anAffineTransform.a,
b = anAffineTransform.b,
c = anAffineTransform.c,
d = anAffineTransform.d,
tx = anAffineTransform.tx,
ty = anAffineTransform.ty,
sx = 1.0,
sy = 1.0,
a1 = 0.0,
a2 = 0.0;
// Detect the simple case of just scaling.
if (b === 0.0 && c === 0.0)
{
sx = a;
sy = d;
}
// a scale followed by a rotate
else if (a * b === -c * d)
{
scale_rotate(a, b, c, d)
}
// rotate, then scale.
else if (a * c === -b * d)
{
rotate_scale(a, b, c, d)
}
else
{
var transpose = CGAffineTransformMake(a, c, b, d, 0.0, 0.0), // inline
u = eigen(CGAffineTransformConcat(anAffineTransform, transpose)),
v = eigen(CGAffineTransformConcat(transpose, anAffineTransform)),
U = CGAffineTransformMake(u.vector_1.x, u.vector_2.x, u.vector_1.y, u.vector_2.y, 0.0, 0.0), // inline
VT = CGAffineTransformMake(v.vector_1.x, v.vector_1.y, v.vector_2.x, v.vector_2.y, 0.0, 0.0),
S = CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformInvert(U), anAffineTransform), CGAffineTransformInvert(VT));
a = VT.a;
b = VT.b;
c = VT.c;
d = VT.d;
scale_rotate(a, b, c, d);
S.a *= sx;
S.d *= sy;
a = U.a;
b = U.b;
c = U.c;
d = U.d;
rotate_scale(a, b, c, d);
sx = S.a * sx;
sy = S.d * sy;
}
if (tx != 0 || ty != 0)
CGContextTranslateCTM(aContext, tx, ty);
if (a1 != 0.0)
CGContextRotateCTM(aContext, a1);
if (sx != 1.0 || sy != 1.0)
CGContextScaleCTM(aContext, sx, sy);
if (a2 != 0.0)
CGContextRotateCTM(aContext, a2);
};
}
function CGContextDrawImage(aContext, aRect, anImage)
{
aContext.drawImage(anImage._image, CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
aContext.hasPath = NO;
}
function to_string(aColor)
{
return "rgba(" + ROUND(aColor.components[0] * 255) + ", " + ROUND(aColor.components[1] * 255) + ", " + ROUND(255 * aColor.components[2]) + ", " + aColor.components[3] + ")";
}
function CGContextDrawLinearGradient(aContext, aGradient, aStartPoint, anEndPoint, options)
{
var colors = aGradient.colors,
count = colors.length,
linearGradient = aContext.createLinearGradient(aStartPoint.x, aStartPoint.y, anEndPoint.x, anEndPoint.y);
while (count--)
linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count]));
aContext.fillStyle = linearGradient;
aContext.fill();
aContext.hasPath = NO;
}
function CGContextDrawRadialGradient(aContext, aGradient, aStartCenter, aStartRadius, anEndCenter, anEndRadius, options)
{
var colors = aGradient.colors,
count = colors.length,
linearGradient = aContext.createRadialGradient(aStartCenter.x, aStartCenter.y, aStartRadius, anEndCenter.x, anEndCenter.y, anEndRadius);
while (count--)
linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count]));
aContext.fillStyle = linearGradient;
aContext.fill();
aContext.hasPath = NO;
}
function CGBitmapGraphicsContextCreate()
{
var DOMElement = document.createElement("canvas"),
context = DOMElement.getContext("2d");
context.DOMElement = DOMElement;
// canvas gives us no way to query whether the path is empty or not, so we have to track it ourselves
context.hasPath = NO;
return context;
}
}
else
{
-635
View File
@@ -1,635 +0,0 @@
/*
* CGContextCanvas.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 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
*/
var CANVAS_LINECAP_TABLE = [ "butt", "round", "square" ],
CANVAS_LINEJOIN_TABLE = [ "miter", "round", "bevel" ],
CANVAS_COMPOSITE_TABLE = [ "source-over", "source-over", "source-over", "source-over", "darker",
"lighter", "source-over", "source-over", "source-over", "source-over",
"source-over", "source-over", "source-over", "source-over", "source-over",
"source-over", "source-over",
"copy", "source-in", "source-out", "source-atop",
"destination-over", "destination-in", "destination-out", "destination-atop",
"xor", "source-over", "source-over" ];
#define _CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, anticlockwise) aContext.arc(x, y, radius, startAngle, endAngle, anticlockwise)
#define _CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius) aContext.arcTo(x1, y1, x2, y2, radius)
#define _CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y) aContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
#define _CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y) aContext.quadraticCurveTo(cpx, cpy, x, y)
#define _CGContextAddLineToPointCanvas(aContext, x, y) aContext.lineTo(x, y)
#define _CGContextClosePathCanvas(aContext) aContext.closePath()
#define _CGContextMoveToPointCanvas(aContext, x, y) aContext.moveTo(x, y)
#define _CGContextAddRectCanvas(aContext, aRect) aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect))
#define _CGContextBeginPathCanvas(aContext) aContext.beginPath()
#define _CGContextFillRectCanvas(aContext, aRect) aContext.fillRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect))
#define _CGContextClipCanvas(aContext) aContext.clip()
// In Cocoa, all primitives excepts rects and arcs cannot be added to the context's path
// until a move to point has been done, because an empty path has no current point.
var hasPath = function(aContext, methodName)
{
if (!aContext.hasPath)
CPLog.error(methodName + ": no current point");
return aContext.hasPath;
}
function CGContextSaveGState(aContext)
{
aContext.save();
}
function CGContextRestoreGState(aContext)
{
aContext.restore();
}
function CGContextSetLineCap(aContext, aLineCap)
{
aContext.lineCap = CANVAS_LINECAP_TABLE[aLineCap];
}
function CGContextSetLineDash(aContext, aPhase, someDashes)
{
if (aContext.setLineDash)
{
aContext.setLineDash(someDashes);
aContext.lineDashOffset = aPhase;
}
else if (typeof aContext['webkitLineDash'] !== 'undefined')
{
aContext.webkitLineDash = someDashes;
aContext.webkitLineDashOffset = aPhase;
}
else if (typeof aContext['mozDash'] !== 'undefined')
{
aContext.mozDash = someDashes;
aContext.mozDashOffset = aPhase;
}
else if (someDashes)
{
CPLog.warn("CGContextSetLineDash not implemented in this environment.")
}
}
function CGContextSetLineJoin(aContext, aLineJoin)
{
aContext.lineJoin = CANVAS_LINEJOIN_TABLE[aLineJoin];
}
function CGContextSetLineWidth(aContext, aLineWidth)
{
aContext.lineWidth = aLineWidth;
}
function CGContextSetMiterLimit(aContext, aMiterLimit)
{
aContext.miterLimit = aMiterLimit;
}
function CGContextSetBlendMode(aContext, aBlendMode)
{
aContext.globalCompositeOperation = CANVAS_COMPOSITE_TABLE[aBlendMode];
}
function CGContextAddArc(aContext, x, y, radius, startAngle, endAngle, clockwise)
{
// Despite the documentation saying otherwise, the last parameter is anti-clockwise not clockwise.
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes#Arcs
_CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, !clockwise);
// AddArc implicitly starts a path
aContext.hasPath = YES;
}
function CGContextAddArcToPoint(aContext, x1, y1, x2, y2, radius)
{
if (!hasPath(aContext, "CGContextAddArcToPoint"))
return;
_CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius);
}
function CGContextAddCurveToPoint(aContext, cp1x, cp1y, cp2x, cp2y, x, y)
{
if (!hasPath(aContext, "CGContextAddCurveToPoint"))
return;
_CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y);
}
function CGContextAddLines(aContext, points, count)
{
// implementation mirrors that of CGPathAddLines()
if (count == null)
count = points.length;
if (count < 1)
return;
_CGContextMoveToPointCanvas(aContext, points[0].x, points[0].y);
for (var i = 1; i < count; ++i)
_CGContextAddLineToPointCanvas(aContext, points[i].x, points[i].y);
aContext.hasPath = YES;
}
function CGContextAddLineToPoint(aContext, x, y)
{
if (!hasPath(aContext, "CGContextAddLineToPoint"))
return;
_CGContextAddLineToPointCanvas(aContext, x, y);
}
function CGContextAddPath(aContext, aPath)
{
if (!aContext || CGPathIsEmpty(aPath))
return;
// If the context does not have a path, explicitly begin one
if (!aContext.hasPath)
_CGContextBeginPathCanvas(aContext);
// We must implicitly move to the start of the path
_CGContextMoveToPointCanvas(aContext, aPath.start.x, aPath.start.y);
var elements = aPath.elements,
i = 0,
count = aPath.count;
for (; i < count; ++i)
{
var element = elements[i],
type = element.type;
switch (type)
{
case kCGPathElementMoveToPoint:
_CGContextMoveToPointCanvas(aContext, element.x, element.y);
break;
case kCGPathElementAddLineToPoint:
_CGContextAddLineToPointCanvas(aContext, element.x, element.y);
break;
case kCGPathElementAddQuadCurveToPoint:
_CGContextAddQuadCurveToPointCanvas(aContext, element.cpx, element.cpy, element.x, element.y);
break;
case kCGPathElementAddCurveToPoint:
_CGContextAddCurveToPointCanvas(aContext, element.cp1x, element.cp1y, element.cp2x, element.cp2y, element.x, element.y);
break;
case kCGPathElementCloseSubpath:
_CGContextClosePathCanvas(aContext);
break;
case kCGPathElementAddArc:
_CGContextAddArcCanvas(aContext, element.x, element.y, element.radius, element.startAngle, element.endAngle, element.clockwise);
break;
case kCGPathElementAddArcToPoint:
_CGContextAddArcToPointCanvas(aContext, element.p1x, element.p1y, element.p2x, element.p2y, element.radius);
break;
}
}
aContext.hasPath = YES;
}
function CGContextAddRect(aContext, aRect)
{
_CGContextAddRectCanvas(aContext, aRect);
aContext.hasPath = YES;
}
function CGContextAddQuadCurveToPoint(aContext, cpx, cpy, x, y)
{
if (!hasPath(aContext, "CGContextAddQuadCurveToPoint"))
return;
_CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y);
}
function CGContextAddRects(aContext, rects, count)
{
if (count == null)
count = rects.length;
for (var i = 0; i < count; ++i)
{
var rect = rects[i];
_CGContextAddRectCanvas(aContext, rect);
}
aContext.hasPath = YES;
}
function CGContextBeginPath(aContext)
{
_CGContextBeginPathCanvas(aContext);
aContext.hasPath = NO;
}
function CGContextClosePath(aContext)
{
_CGContextClosePathCanvas(aContext);
}
function CGContextIsPathEmpty(aContext)
{
return !aContext.hasPath;
}
function CGContextMoveToPoint(aContext, x, y)
{
_CGContextMoveToPointCanvas(aContext, x, y);
aContext.hasPath = YES;
}
function CGContextClearRect(aContext, aRect)
{
aContext.clearRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
aContext.hasPath = NO;
}
function CGContextDrawPath(aContext, aMode)
{
if (!aContext.hasPath)
return;
if (aMode === kCGPathFill || aMode === kCGPathFillStroke)
aContext.fill();
else if (aMode === kCGPathStroke || aMode === kCGPathFillStroke || aMode === kCGPathEOFillStroke)
aContext.stroke();
else if (aMode === kCGPathEOFill || aMode === kCGPathEOFillStroke)
CPLog.warn("Unimplemented fill mode in CGContextDrawPath: %d", aMode);
aContext.hasPath = NO;
}
function CGContextFillRect(aContext, aRect)
{
_CGContextFillRectCanvas(aContext, aRect);
aContext.hasPath = NO;
}
function CGContextFillRects(aContext, rects, count)
{
if (count == null)
count = rects.length;
for (var i = 0; i < count; ++i)
{
var rect = rects[i];
_CGContextFillRectCanvas(aContext, rect);
}
aContext.hasPath = NO;
}
function CGContextStrokeRect(aContext, aRect)
{
aContext.strokeRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
aContext.hasPath = NO;
}
function CGContextClip(aContext)
{
_CGContextClipCanvas(aContext);
aContext.hasPath = NO;
}
function CGContextClipToRect(aContext, aRect)
{
_CGContextBeginPathCanvas(aContext);
_CGContextAddRectCanvas(aContext, aRect);
_CGContextClosePathCanvas(aContext);
_CGContextClipCanvas(aContext);
aContext.hasPath = NO;
}
function CGContextClipToRects(aContext, rects, count)
{
if (count == null)
count = rects.length;
_CGContextBeginPathCanvas(aContext);
CGContextAddRects(aContext, rects, count);
_CGContextClipCanvas(aContext);
aContext.hasPath = NO;
}
function CGContextSetAlpha(aContext, anAlpha)
{
aContext.globalAlpha = anAlpha;
}
function CGContextSetFillColor(aContext, aColor)
{
var patternImage = [aColor patternImage];
if ([patternImage isSingleImage])
{
var pattern = aContext.createPattern([patternImage image], "repeat");
aContext.fillStyle = pattern;
}
else
aContext.fillStyle = [aColor cssString];
}
/*!
Creates a context into which you can render a fill pattern
of the given size. Once the pattern is rendered, you can
set the fill or stroke pattern to the rendered pattern
with CGContextSetFillPattern or CGContextSetStrokePattern.
*/
function CGContextCreatePatternContext(aContext, aSize)
{
var pattern = document.createElement("canvas");
pattern.width = aSize.width;
pattern.height = aSize.height;
return pattern.getContext("2d");
}
/*!
Sets the fill pattern for aContext to the rendered pattern context
returned by CGContextCreatePatternContext.
*/
function CGContextSetFillPattern(aContext, aPatternContext)
{
var pattern = aContext.createPattern(aPatternContext.canvas, "repeat");
aContext.fillStyle = pattern;
}
/*!
Sets the stroke pattern for aContext to the rendered pattern context
returned by CGContextCreatePatternContext.
*/
function CGContextSetStrokePattern(aContext, aPatternContext)
{
var pattern = aContext.createPattern(aPatternContext.canvas, "repeat");
aContext.strokeStyle = pattern;
}
function CGContextSetStrokeColor(aContext, aColor)
{
var patternImage = [aColor patternImage];
if ([patternImage isSingleImage])
{
var pattern = aContext.createPattern([patternImage image], "repeat");
aContext.strokeStyle = pattern;
}
else
aContext.strokeStyle = [aColor cssString];
}
function CGContextSetShadow(aContext, aSize, aBlur)
{
aContext.shadowOffsetX = aSize.width;
aContext.shadowOffsetY = aSize.height;
aContext.shadowBlur = aBlur;
aContext.shadowColor = [[CPColor shadowColor] cssString];
}
function CGContextSetShadowWithColor(aContext, aSize, aBlur, aColor)
{
aContext.shadowOffsetX = aSize.width;
aContext.shadowOffsetY = aSize.height;
aContext.shadowBlur = aBlur;
aContext.shadowColor = [aColor cssString];
}
function CGContextRotateCTM(aContext, anAngle)
{
aContext.rotate(anAngle);
}
function CGContextScaleCTM(aContext, sx, sy)
{
aContext.scale(sx, sy);
}
function CGContextTranslateCTM(aContext, tx, ty)
{
aContext.translate(tx, ty);
}
var scale_rotate = function(a, b, c, d)
{
var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0,
a2 = (ATAN2(b, d) + ATAN2(-sign * c, sign * a)) / 2.0,
cos = COS(a2),
sin = SIN(a2);
if (cos === 0)
{
sx = -c / sin;
sy = b / sin;
}
else if (sin === 0)
{
sx = a / cos;
sy = d / cos;
}
else
{
abs_cos = ABS(cos);
abs_sin = ABS(sin);
sx = (abs_cos * a / cos + abs_sin * -c / sin) / (abs_cos + abs_sin);
sy = (abs_cos * d / cos + abs_sin * b / sin) / (abs_cos + abs_sin);
}
};
var rotate_scale = function(a, b, c, d)
{
var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0,
a1 = (ATAN2(sign * b, sign * a) + ATAN2(-c, d)) / 2.0,
cos = COS(a1),
sin = SIN(a1);
if (cos === 0)
{
sx = b / sin;
sy = -c / sin;
}
else if (sin === 0)
{
sx = a / cos;
sy = d / cos;
}
else
{
abs_cos = ABS(cos);
abs_sin = ABS(sin);
sx = (abs_cos * a / cos + abs_sin * b / sin) / (abs_cos + abs_sin);
sy = (abs_cos * d / cos + abs_sin * -c / sin) / (abs_cos + abs_sin);
}
};
function eigen(anAffineTransform)
{
CPLog.warn("Unimplemented function: eigen");
}
if (CPFeatureIsCompatible(CPJavaScriptCanvasTransformFeature))
{
CGContextConcatCTM = function(aContext, anAffineTransform)
{
aContext.transform(anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty);
};
}
else
{
CGContextConcatCTM = function(aContext, anAffineTransform)
{
var a = anAffineTransform.a,
b = anAffineTransform.b,
c = anAffineTransform.c,
d = anAffineTransform.d,
tx = anAffineTransform.tx,
ty = anAffineTransform.ty,
sx = 1.0,
sy = 1.0,
a1 = 0.0,
a2 = 0.0;
// Detect the simple case of just scaling.
if (b === 0.0 && c === 0.0)
{
sx = a;
sy = d;
}
// a scale followed by a rotate
else if (a * b === -c * d)
{
scale_rotate(a, b, c, d)
}
// rotate, then scale.
else if (a * c === -b * d)
{
rotate_scale(a, b, c, d)
}
else
{
var transpose = CGAffineTransformMake(a, c, b, d, 0.0, 0.0), // inline
u = eigen(CGAffineTransformConcat(anAffineTransform, transpose)),
v = eigen(CGAffineTransformConcat(transpose, anAffineTransform)),
U = CGAffineTransformMake(u.vector_1.x, u.vector_2.x, u.vector_1.y, u.vector_2.y, 0.0, 0.0), // inline
VT = CGAffineTransformMake(v.vector_1.x, v.vector_1.y, v.vector_2.x, v.vector_2.y, 0.0, 0.0),
S = CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformInvert(U), anAffineTransform), CGAffineTransformInvert(VT));
a = VT.a;
b = VT.b;
c = VT.c;
d = VT.d;
scale_rotate(a, b, c, d);
S.a *= sx;
S.d *= sy;
a = U.a;
b = U.b;
c = U.c;
d = U.d;
rotate_scale(a, b, c, d);
sx = S.a * sx;
sy = S.d * sy;
}
if (tx != 0 || ty != 0)
CGContextTranslateCTM(aContext, tx, ty);
if (a1 != 0.0)
CGContextRotateCTM(aContext, a1);
if (sx != 1.0 || sy != 1.0)
CGContextScaleCTM(aContext, sx, sy);
if (a2 != 0.0)
CGContextRotateCTM(aContext, a2);
};
}
function CGContextDrawImage(aContext, aRect, anImage)
{
aContext.drawImage(anImage._image, CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect));
aContext.hasPath = NO;
}
function to_string(aColor)
{
return "rgba(" + ROUND(aColor.components[0] * 255) + ", " + ROUND(aColor.components[1] * 255) + ", " + ROUND(255 * aColor.components[2]) + ", " + aColor.components[3] + ")";
}
function CGContextDrawLinearGradient(aContext, aGradient, aStartPoint, anEndPoint, options)
{
var colors = aGradient.colors,
count = colors.length,
linearGradient = aContext.createLinearGradient(aStartPoint.x, aStartPoint.y, anEndPoint.x, anEndPoint.y);
while (count--)
linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count]));
aContext.fillStyle = linearGradient;
aContext.fill();
aContext.hasPath = NO;
}
function CGContextDrawRadialGradient(aContext, aGradient, aStartCenter, aStartRadius, anEndCenter, anEndRadius, options)
{
var colors = aGradient.colors,
count = colors.length,
linearGradient = aContext.createRadialGradient(aStartCenter.x, aStartCenter.y, aStartRadius, anEndCenter.x, anEndCenter.y, anEndRadius);
while (count--)
linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count]));
aContext.fillStyle = linearGradient;
aContext.fill();
aContext.hasPath = NO;
}
function CGBitmapGraphicsContextCreate()
{
var DOMElement = document.createElement("canvas"),
context = DOMElement.getContext("2d");
context.DOMElement = DOMElement;
// canvas gives us no way to query whether the path is empty or not, so we have to track it ourselves
context.hasPath = NO;
return context;
}
+1 -1
View File
@@ -37,7 +37,7 @@ function CGContextSetTextMatrix(/* CGContext */ aContext, /* CGAffineTransform *
function CGContextGetTextPosition(/* CGContext */ aContext)
{
return aContext._textPosition || _CGPointMakeZero();
return aContext._textPosition || CGPointMakeZero();
}
function CGContextSetTextPosition(/* CGContext */ aContext, /* float */ x, /* float */ y)
-335
View File
@@ -1,335 +0,0 @@
/*
* CGContextVML.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 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
*/
var VML_TRUTH_TABLE = [ "f", "t"],
VML_LINECAP_TABLE = [ "flat", "round", "square" ],
VML_LINEJOIN_TABLE = [ "miter", "round", "bevel" ],
VML_ELEMENT_TABLE = [ " m ", " l ", "qb", " c ", " x ", [" at ", " wa "]];
var _CGBitmapGraphicsContextCreate = CGBitmapGraphicsContextCreate;
function CGBitmapGraphicsContextCreate()
{
// The first time around, we have to set up our environment to support vml.
document.namespaces.add("cg_vml_", "urn:schemas-microsoft-com:vml");
document.createStyleSheet().cssText = "cg_vml_\\:*{behavior:url(#default#VML)}";
CGBitmapGraphicsContextCreate = _CGBitmapGraphicsContextCreate;
return _CGBitmapGraphicsContextCreate();
}
function CGContextSetFillColor(aContext, aColor)
{
if ([aColor patternImage])
// Prefix a marker character to the string so we know it's a pattern image filename
aContext.gState.fillStyle = "!" + [[aColor patternImage] filename];
else
aContext.gState.fillStyle = [aColor cssString];
}
// FIXME: aRect is ignored.
function CGContextClearRect(aContext, aRect)
{
if (aContext.buffer != nil)
aContext.buffer = "";
else
aContext.DOMElement.innerHTML = "";
aContext.path = NULL;
}
var W = 10.0,
H = 10.0,
Z = 10.0,
Z_2 = Z / 2.0;
#define COORD(aCoordinate) (aCoordinate === 0.0 ? 0 : ROUND(Z * (aCoordinate) - Z_2))
function CGContextDrawImage(aContext, aRect, anImage)
{
var string = "";
if (anImage.buffer != nil)
string = anImage.buffer;
else
{
var ctm = aContext.gState.CTM,
origin = CGPointApplyAffineTransform(aRect.origin, ctm),
similarity = ctm.a == ctm.d && ctm.b == -ctm.c,
vml = ["<cg_vml_:group coordsize=\"1,1\" coordorigin=\"0,0\" style=\"width:1;height:1;position:absolute"];
/*if (similarity)
{
var angle = CGPointMake(1.0, 0.0);
angle = CGPointApplyAffineTransform(angle, ctm);
vml.push(";rotation:", ATAN2(angle.y - ctm.ty, angle.x - ctm.tx) * 180 / PI);
}*/
// Only create a filter if absolutely necessary. This actually
// turns out to only be the case if our transform matrix is not
// a similarity matrix, that is to say, the transform actually
// morphs the shape beyond scaling and rotation.
//if (!similarity && (ctm.a != 1.0 || ctm.b || ctm.c || ctm.d != 1.0))
{
var transformedRect = CGRectApplyAffineTransform(aRect, ctm);
vml.push( ";padding:0 ", ROUND(CGRectGetMaxX(transformedRect)), "px ", ROUND(CGRectGetMaxY(transformedRect)),
"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
"M11='", ctm.a, "',M12='", ctm.c, "',M21='", ctm.b, "',M22='", ctm.d, "',",
"Dx='", ROUND(origin.x), "', Dy='", ROUND(origin.y), "', sizingmethod='clip');");
}
//else
// vml.push(";top:", ROUND(origin.y - 0.5), "px;left:", ROUND(origin.x - 0.5), "px;");
vml.push( "\"><cg_vml_:image src=\"", anImage._image.src,
"\" style=\"width:", CGRectGetWidth(aRect), "px;height:", CGRectGetHeight(aRect),
"px;\"/></g_vml_:group>");
string = vml.join("");
}
if (aContext.buffer != nil)
aContext.buffer += string;
else
aContext.DOMElement.insertAdjacentHTML("BeforeEnd", string);
}
function CGContextDrawPath(aContext, aMode)
{
if (!aContext || CGPathIsEmpty(aContext.path))
return;
var elements = aContext.path.elements,
i = 0,
count = aContext.path.count,
gState = aContext.gState,
fill = (aMode == kCGPathFill || aMode == kCGPathFillStroke) ? 1 : 0,
stroke = (aMode == kCGPathStroke || aMode == kCGPathFillStroke) ? 1 : 0,
opacity = gState.alpha,
vml = ["<cg_vml_:shape"];
if (gState.fillStyle.charAt(0) !== "!")
vml.push(" fillcolor=\"", gState.fillStyle, "\"");
vml.push( " filled=\"", VML_TRUTH_TABLE[fill],
"\" style=\"position:absolute;width:", W, ";height:", H,
";\" coordorigin=\"0 0\" coordsize=\"", Z * W, " ", Z * H,
"\" stroked=\"", VML_TRUTH_TABLE[stroke],
"\" strokeweight=\"", gState.lineWidth,
"\" strokecolor=\"", gState.strokeStyle,
"\" path=\"");
for (; i < count; ++i)
{
var element = elements[i],
type = element.type;
switch (type)
{
case kCGPathElementMoveToPoint:
case kCGPathElementAddLineToPoint: vml.push(VML_ELEMENT_TABLE[type], COORD(element.x), ',', COORD(element.y));
break;
case kCGPathElementAddQuadCurveToPoint: vml.push(VML_ELEMENT_TABLE[type],
COORD(element.cpx), ',', COORD(element.cpy), ',',
COORD(element.x), ',', COORD(element.y));
break;
case kCGPathElementAddCurveToPoint: vml.push(VML_ELEMENT_TABLE[type],
COORD(element.cp1x), ',', COORD(element.cp1y), ',',
COORD(element.cp2x), ',', COORD(element.cp2y), ',',
COORD(element.x), ',', COORD(element.y));
break;
case kCGPathElementCloseSubpath: vml.push(VML_ELEMENT_TABLE[type]);
break;
case kCGPathElementAddArc: var x = element.x,
y = element.y,
radius = element.radius,
clockwise = element.clockwise ? 1 : 0,
endAngle = element.endAngle,
startAngle = element.startAngle,
start = CGPointMake(x + radius * COS(startAngle), y + radius * SIN(startAngle));
// If the angle's are equal, then we won't actually draw an arc, but instead
// simply move to its start/end to get the proper fill.
// We only need this special case for anti-clockwise because start == end is
// interpreted as a full circle with anti-clockwise, but empty for clockwise.
if (startAngle == endAngle && !clockwise)
{
vml.push(VML_ELEMENT_TABLE[kCGPathElementMoveToPoint], COORD(start.x), ',', COORD(start.y));
continue;
}
var end = CGPointMake(x + radius * COS(endAngle), y + radius * SIN(endAngle));
// Only do the start correction if the angles aren't equal. If they are, then
// let the circle be empty.
// FIXME: Should this be |star.x - end.x| < 0.125 ?
if (clockwise && startAngle != endAngle && CGPointEqualToPoint(start, end))
if (start.x >= x)
{
if (start.y < y)
start.x += 0.125;
else
start.y += 0.125;
}
else
{
if (end.y <= y)
end.x += 0.125;
else
end.y += 0.125;
}
vml.push(VML_ELEMENT_TABLE[type][clockwise],
COORD(x - radius), ',', COORD(y - radius), " ",
COORD(x + radius), ',', COORD(y + radius), " ",
COORD(start.x), ',', COORD(start.y), " ",
COORD(end.x), ',', COORD(end.y));
break;
case kCGPathElementAddArcToPoint: break;
}
// TODO: Following is broken for curves due to
// move to proper paths.
// Figure out dimensions so we can do gradient fills
// properly
/*if(c) {
if (min.x == null || c.x < min.x) {
min.x = c.x;
}
if (max.x == null || c.x > max.x) {
max.x = c.x;
}
if (min.y == null || c.y < min.y) {
min.y = c.y;
}
if (max.y == null || c.y > max.y) {
max.y = c.y;
}
}*/
}
vml.push("\">");
if (gState.gradient)
vml.push(gState.gradient);
else if (fill)
{
if (gState.fillStyle.charAt(0) === "!")
vml.push("<cg_vml_:fill type=\"tile\" src=\"", gState.fillStyle.substring(1), "\" opacity=\"", opacity, "\" />");
else // should be a CSS color spec
vml.push("<cg_vml_:fill color=\"", gState.fillStyle, "\" opacity=\"", opacity, "\" />");
}
if (stroke)
vml.push( "<cg_vml_:stroke opacity=\"", opacity,
"\" joinstyle=\"", VML_LINEJOIN_TABLE[gState.lineJoin],
"\" miterlimit=\"", gState.miterLimit,
"\" endcap=\"", VML_LINECAP_TABLE[gState.lineCap],
"\" weight=\"", gState.lineWidth, "",
"px\" color=\"", gState.strokeStyle,"\" />");
var shadowColor = gState.shadowColor;
//\"", [shadowColor cssString], "\"
if (shadowColor)
{
var shadowOffset = gState.shadowOffset;
vml.push("<cg_vml_:shadow on=\"t\" offset=\"",
shadowOffset.width, "pt ", shadowOffset.height, "pt\" opacity=\"", [shadowColor alphaComponent], "\" color=black />");
}
vml.push("</cg_vml_:shape>");
if (aContext.buffer != nil)
aContext.buffer += vml.join("");
else
aContext.DOMElement.insertAdjacentHTML("BeforeEnd", vml.join(""));
}
function to_string(aColor)
{
return "rgb(" + ROUND(aColor.components[0] * 255) + ", " + ROUND(aColor.components[1] * 255) + ", " + ROUND(255 * aColor.components[2]) + ")";
}
function CGContextDrawLinearGradient(aContext, aGradient, aStartPoint, anEndPoint, options)
{
if (!aContext || !aGradient)
return;
var vml = nil;
if (aGradient.vml_gradient)
{
var stops = [[aGradient.vml_gradient stops] sortedArrayUsingSelector:@selector(comparePosition:)],
count = [stops count];
vml = ["<cg_vml_:fill type=\"gradient\" method=\"linear sigma\" "];
vml.push("angle=\"" + ([aGradient.vml_gradient angle] + 90) + "\" ");
vml.push("colors=\"");
for (var i = 0; i < count; i++)
{
vml.push(([stops[i] position] * 100).toFixed(0) + "% ");
vml.push([[[stops[i] color] colorForSlideBase:nil] cssString]);
if (i < count - 1)
vml.push(",");
}
vml.push("\" />");
}
else
{
var colors = aGradient.colors,
count = colors.length;
vml = ["<cg_vml_:fill type=\"gradient\" "];
vml.push("colors=\"");
for (var i = 0; i < count; i++)
vml.push((aGradient.locations[i] * 100).toFixed(0)+"% " + to_string(colors[i])+(i < count - 1 ? "," : ""));
vml.push("\" />");
}
aContext.gState.gradient = vml.join("");
// if (aContext.buffer != nil)
// aContext.buffer += vml.join("");
// else
// aContext.DOMElement.innerHTML = vml.join("");
}
+1 -4
View File
@@ -9,10 +9,7 @@ const utilsFile = ObjectiveJ.utils.file;
$BUILD_PATH = path.join($BUILD_DIR, $CONFIGURATION, 'AppKit');
AppKitFiles = new FileList("**/*.j").exclude('CoreGraphics/CGContextCanvas.j', 'CoreGraphics/CGContextVML.j', 'Themes/**/*', 'Tools/**/*', "Platform/DOM/CPPlatform.j", "Platform/DOM/CPPlatformString.j");
FIXME_fileDependency (path.join("Platform", "CPPlatform.j"), path.join("Platform", "DOM", "CPPlatform.j"));
FIXME_fileDependency (path.join("Platform", "CPPlatformString.j"), path.join("Platform", "DOM", "CPPlatformString.j"));
AppKitFiles = new FileList("**/*.j").exclude('CoreGraphics/CGContextCanvas.j', 'CoreGraphics/CGContextVML.j', 'Themes/**/*', 'Tools/**/*');
appKitTask = framework ("AppKit", function(appKitTask)
{
+162 -4
View File
@@ -70,11 +70,169 @@
@end
#if PLATFORM(DOM)
#include "DOM/CPPlatform.j"
#else
// --- begin AppKit/Platform/DOM/CPPlatform.j (inlined; file removed to resolve capp-build basename clash) ---
@global CPApp
@global document
if (typeof window["CPPlatformEnableHTMLDragAndDrop"] === "undefined")
CPPlatformEnableHTMLDragAndDrop = NO;
CPPlatformDidClearBodyElementNotification = @"CPPlatformDidClearBodyElementNotification";
CPPlatformWillClearBodyElementNotification = @"CPPlatformWillClearBodyElementNotification";
var screenNeedsInitialization = NO,
mainBodyElement = nil,
elementRemovalTest = new RegExp("\\bcpdontremove\\b", "g");
@implementation CPPlatform : CPBasePlatform
{
}
+ (void)initialize
{
if (self !== [CPPlatform class])
return;
screenNeedsInitialization = [CPPlatform isBrowser];
// We do this here because doing it later breaks IE.
if (typeof document !== 'undefined' && document.documentElement)
document.documentElement.style.overflow = "hidden";
if ([CPPlatform isBrowser])
{
// This differ from cocoa, where shouldTerminate is called in the method terminate of CPApp
// Cappuccino acts like this because we can not close a window openend by the user with a script (so not possible in terminate), and we can only prevent the action in the method onbeforeunload in js.
window.onbeforeunload = function()
{
if ([CPApp _sendDelegateApplicationShouldTerminate] != CPTerminateNow)
return [CPApp _sendDelegateApplicationShouldTerminateMessage];
};
var terminateApplication = function()
{
[self closeAllPlatformWindows];
[CPApp terminate:nil];
};
// "unload" is blocked by the permissions policy in current Chrome and prevents the
// back/forward cache. "pagehide" fires reliably instead. If event.persisted is true the
// page goes into the bfcache and may be restored, so the application must not terminate.
if ("onpagehide" in window)
{
window.addEventListener("pagehide", function(anEvent)
{
if (!anEvent.persisted)
terminateApplication();
}, NO);
}
else
window.onunload = terminateApplication;
}
}
+ (BOOL)isBrowser
{
if (!CPDOMAvailable)
return NO;
return typeof window.cpIsDesktop === "undefined";
}
+ (BOOL)supportsDragAndDrop
{
if (!CPDOMAvailable)
return NO;
return CPFeatureIsCompatible(CPHTMLDragAndDropFeature) && (CPPlatformEnableHTMLDragAndDrop || ![self isBrowser]);
}
+ (BOOL)supportsNativeMainMenu
{
return (typeof window["cpSetMainMenu"] === "function");
}
+ (void)terminateApplication
{
if (typeof window["cpTerminate"] === "function")
window.cpTerminate();
}
+ (void)activateIgnoringOtherApps:(BOOL)shouldIgnoreOtherApps
{
if (typeof window["cpActivateIgnoringOtherApps"] === "function")
window.cpActivateIgnoringOtherApps(!!shouldIgnoreOtherApps);
}
+ (void)deactivate
{
if (typeof window["cpDeactivate"] === "function")
window.cpDeactivate();
}
+ (void)hideOtherApplications:(id)aSender
{
if (typeof window["cpHideOtherApplications"] === "function")
window.cpHideOtherApplications();
}
+ (void)hide:(id)aSender
{
if (typeof window["cpHide"] === "function")
window.cpHide();
}
+ (DOMElement)mainBodyElement
{
if (!mainBodyElement)
mainBodyElement = document.getElementById("cappuccino-body") || document.body;
return mainBodyElement;
}
+ (void)initializeScreenIfNecessary
{
if (!screenNeedsInitialization)
return;
screenNeedsInitialization = NO;
[[CPNotificationCenter defaultCenter]
postNotificationName:CPPlatformWillClearBodyElementNotification
object:self];
var bodyElement = [self mainBodyElement];
// Get rid of any of the original contents of the page.
var children = bodyElement.childNodes,
length = children.length;
while (length--)
{
var element = children[length];
if (!element.className || element.className.match(elementRemovalTest) === null)
bodyElement.removeChild(element);
}
bodyElement.style.overflow = "hidden";
[[CPNotificationCenter defaultCenter]
postNotificationName:CPPlatformDidClearBodyElementNotification
object:self];
}
+ (void)closeAllPlatformWindows
{
var platformWindows = [CPPlatformWindow visiblePlatformWindows],
primaryPlatformWindow = [CPPlatformWindow primaryPlatformWindow],
platformWindowEnumerator = [platformWindows objectEnumerator],
platformWindow = nil;
while ((platformWindow = [platformWindowEnumerator nextObject]) != nil)
{
if (platformWindow != primaryPlatformWindow)
[platformWindow orderOut:self];
}
}
@end
#endif
+185 -6
View File
@@ -38,24 +38,203 @@
@end
#if PLATFORM(DOM)
#include "DOM/CPPlatformString.j"
#else
// --- begin AppKit/Platform/DOM/CPPlatformString.j (inlined; file removed to resolve capp-build basename clash) ---
@global document
var DOMFixedWidthSpanElement = nil,
DOMFlexibleWidthSpanElement = nil,
DOMMetricsDivElement = nil,
DOMMetricsImgElement = nil,
DefaultFont = nil;
@implementation CPPlatformString : CPBasePlatformString
{
}
+ (void)initialize
{
if (self !== [CPPlatformString class])
return;
DefaultFont = [CPFont systemFontOfSize:CPFontCurrentSystemSize];
}
+ (void)bootstrap
{
[self createDOMElements];
}
+ (void)createDOMElements
{
if (!CPDOMAvailable) return;
var style,
bodyElement = [CPPlatform mainBodyElement];
DOMFlexibleWidthSpanElement = document.createElement("span");
DOMFlexibleWidthSpanElement.className = "cpdontremove";
style = DOMFlexibleWidthSpanElement.style;
style.position = "absolute";
style.left = "-100000px";
style.zIndex = -10000;
style.visibility = "visible";
style.padding = "0px";
style.margin = "0px";
style.whiteSpace = "pre";
DOMFixedWidthSpanElement = document.createElement("span");
DOMFixedWidthSpanElement.className = "cpdontremove";
style = DOMFixedWidthSpanElement.style;
style.display = "block";
style.position = "absolute";
style.left = "-100000px";
style.zIndex = -10000;
style.visibility = "visible";
style.padding = "0px";
style.margin = "0px";
style.width = "1px";
style.wordWrap = "break-word";
try
{
style.whiteSpace = "pre";
style.whiteSpace = "-o-pre-wrap";
style.whiteSpace = "-pre-wrap";
style.whiteSpace = "-moz-pre-wrap";
style.whiteSpace = "pre-wrap";
}
catch(e)
{
//some versions of IE throw exceptions for unsupported properties.
style.whiteSpace = "pre";
}
bodyElement.appendChild(DOMFlexibleWidthSpanElement);
bodyElement.appendChild(DOMFixedWidthSpanElement);
}
+ (void)createDOMMetricsElements
{
if (!CPDOMAvailable) return;
var style,
bodyElement = [CPPlatform mainBodyElement];
DOMMetricsDivElement = document.createElement("div");
DOMMetricsDivElement.className = "cpdontremove";
style = DOMMetricsDivElement.style;
style.position = "absolute";
style.left = "-100000px";
style.zIndex = -10000;
style.width = "100000px";
style.whiteSpace = "nowrap";
style.lineHeight = "1em";
style.padding = "0px";
style.margin = "0px";
DOMMetricsDivElement.innerHTML = "x";
bodyElement.appendChild(DOMMetricsDivElement);
var imgPath = [[CPBundle bundleForClass:[CPView class]] pathForResource:@"empty.png"];
DOMMetricsImgElement = document.createElement("img");
DOMMetricsImgElement.className = "cpdontremove";
DOMMetricsImgElement.setAttribute("src", imgPath);
DOMMetricsImgElement.setAttribute("width", "1");
DOMMetricsImgElement.setAttribute("height", "1");
DOMMetricsImgElement.setAttribute("alt", "");
style = DOMMetricsImgElement.style;
style.zIndex = -10000;
style.visibility = "visible";
style.padding = "0px";
style.margin = "0px";
style.border = "none";
style.verticalAlign = "baseline";
DOMMetricsDivElement.appendChild(DOMMetricsImgElement);
}
+ (int)charPositionOfString:(CPString)aString withFont:(CPFont)aFont forPoint:(CGPoint)aPoint
{
if (!CPDOMAvailable) return 0;
if (!aString)
return 0;
var position = 0,
stringLength = aString.length,
currentString = "";
for (var i = 0; i < stringLength; i++)
{
var lastChar = aString[i];
currentString += lastChar;
var sizeOfString = [self sizeOfString:currentString withFont:aFont forWidth:nil].width,
sizeLastChar = [self sizeOfString:lastChar withFont:aFont forWidth:nil].width;
if (sizeOfString - sizeLastChar / 2 < aPoint.x)
position++;
else
break;
}
return position;
}
+ (CGSize)sizeOfString:(CPString)aString withFont:(CPFont)aFont forWidth:(float)aWidth
{
return CGSizeMakeZero();
if (!CPDOMAvailable) return CGSizeMakeZero();
if (!DOMFixedWidthSpanElement)
[self createDOMElements];
var span;
if (!aWidth)
span = DOMFlexibleWidthSpanElement;
else
{
span = DOMFixedWidthSpanElement;
span.style.width = ROUND(aWidth) + "px";
}
var effectiveFontCSSString = [(aFont || DefaultFont) cssString];
if (span.style.font !== effectiveFontCSSString)
span.style.font = effectiveFontCSSString;
if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature))
span.innerText = aString;
else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature))
span.textContent = aString;
return CGSizeMake(span.clientWidth, span.clientHeight);
}
+ (CPDictionary)metricsOfFont:(CPFont)aFont
{
return @{
if (!CPDOMAvailable)
return @{
@"ascender": 0,
@"descender": 0,
@"lineHeight": 0,
};
if (!DOMMetricsDivElement)
[self createDOMMetricsElements];
DOMMetricsDivElement.style.font = [(aFont || DefaultFont) cssString];
var lineHeight = DOMMetricsDivElement.offsetHeight,
baseline = DOMMetricsImgElement.offsetTop + DOMMetricsImgElement.offsetHeight,
descender = baseline - lineHeight;
return @{
@"ascender": baseline,
@"descender": descender,
@"lineHeight": lineHeight,
};
}
@end
#endif
+21 -29
View File
@@ -24,6 +24,7 @@
@import <Foundation/CPObject.j>
@import "CPKeyBinding.j"
@import "CPPlatform.j"
@import "CPCompatibility.j"
@class CPMenu
@class CPPlatformPasteboard
@@ -46,7 +47,6 @@ var PrimaryPlatformWindow = NULL;
BOOL _shouldUpdateContentRect;
BOOL _hasInitializeInstanceWithWindow;
#if PLATFORM(DOM)
DOMWindow _DOMWindow;
DOMElement _DOMBodyElement;
@@ -90,8 +90,6 @@ var PrimaryPlatformWindow = NULL;
float _lastTouchMoveTimestamp;
float _lastMomentumTimestamp;
BOOL _isTwoFingerScrolling;
#endif
}
+ (CPSet)visiblePlatformWindows
@@ -101,11 +99,10 @@ var PrimaryPlatformWindow = NULL;
+ (BOOL)supportsMultipleInstances
{
#if PLATFORM(DOM)
return !CPBrowserIsEngine(CPInternetExplorerBrowserEngine);
#else
if (CPDOMAvailable)
return !CPBrowserIsEngine(CPInternetExplorerBrowserEngine);
return NO;
#endif
}
+ (CPPlatformWindow)primaryPlatformWindow
@@ -126,14 +123,15 @@ var PrimaryPlatformWindow = NULL;
{
_contentRect = CGRectMakeCopy(aRect);
#if PLATFORM(DOM)
_windowLevels = [];
_windowLayers = @{};
if (CPDOMAvailable)
{
_windowLevels = [];
_windowLayers = @{};
_charCodes = {};
_charCodes = {};
_platformPasteboard = [CPPlatformPasteboard new];
#endif
_platformPasteboard = [CPPlatformPasteboard new];
}
}
return self;
@@ -198,9 +196,10 @@ var PrimaryPlatformWindow = NULL;
_contentRect = CGRectMakeCopy(aRect);
#if PLATFORM(DOM)
[self updateNativeContentRect];
#endif
if (CPDOMAvailable)
{
[self updateNativeContentRect];
}
}
- (void)updateFromNativeContentRect
@@ -224,11 +223,10 @@ var PrimaryPlatformWindow = NULL;
- (BOOL)isVisible
{
#if PLATFORM(DOM)
return _DOMWindow !== NULL && _DOMWindow !== undefined;
#else
if (CPDOMAvailable)
return _DOMWindow !== NULL && _DOMWindow !== undefined;
return NO;
#endif
}
- (void)deminiaturize:(id)sender
@@ -241,7 +239,8 @@ var PrimaryPlatformWindow = NULL;
- (void)moveWindow:(CPWindow)aWindow fromLevel:(int)fromLevel toLevel:(int)toLevel
{
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
if (!aWindow._isVisible)
return;
@@ -250,7 +249,6 @@ var PrimaryPlatformWindow = NULL;
[fromLayer removeWindow:aWindow];
[toLayer insertWindow:aWindow atIndex:CPNotFound];
#endif
}
- (void)setLevel:(CPInteger)aLevel
@@ -277,14 +275,10 @@ var PrimaryPlatformWindow = NULL;
{
_title = aTitle;
#if PLATFORM(DOM)
if (_DOMWindow &&
_DOMWindow.document &&
([aWindow isFullPlatformWindow]))
if (CPDOMAvailable && _DOMWindow && _DOMWindow.document && ([aWindow isFullPlatformWindow]))
{
_DOMWindow.document.title = _title;
}
#endif
}
- (CPString)title
@@ -310,6 +304,4 @@ var PrimaryPlatformWindow = NULL;
@end
#if PLATFORM(BROWSER)
//@import "CPPlatformWindow+DOM.j"
#endif
-68
View File
@@ -1,68 +0,0 @@
/*
* CPDOMDisplayServer.h
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 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
*/
#define CPDOMDisplayServerSetStyleLeftTop(aDOMElement, aTransform, aLeft, aTop) \
if (aTransform) var ____p = CGPointApplyAffineTransform(CGPointMake(aLeft, aTop), aTransform); \
else var ____p = CGPointMake(aLeft, aTop); \
aDOMElement.style.left = ROUND(____p.x) + "px";\
aDOMElement.style.top = ROUND(____p.y) + "px";
#define CPDOMDisplayServerSetStyleRightTop(aDOMElement, aTransform, aRight, aTop) \
if (aTransform) var ____p = CGPointApplyAffineTransform(CGPointMake(aRight, aTop), aTransform); \
else var ____p = CGPointMake(aRight, aTop); \
aDOMElement.style.right = ROUND(____p.x) + "px";\
aDOMElement.style.top = ROUND(____p.y) + "px";
#define CPDOMDisplayServerSetStyleLeftBottom(aDOMElement, aTransform, aLeft, aBottom) \
if (aTransform) var ____p = CGPointApplyAffineTransform(CGPointMake(aLeft, aBottom), aTransform); \
else var ____p = CGPointMake(aLeft, aBottom); \
aDOMElement.style.left = ROUND(____p.x) + "px";\
aDOMElement.style.bottom = ROUND(____p.y) + "px";
#define CPDOMDisplayServerSetStyleRightBottom(aDOMElement, aTransform, aRight, aBottom) \
if (aTransform) var ____p = CGPointApplyAffineTransform(CGPointMake(aRight, aBottom), aTransform); \
else var ____p = CGPointMake(aRight, aBottom); \
aDOMElement.style.right = ROUND(____p.x) + "px";\
aDOMElement.style.bottom = ROUND(____p.y) + "px";
#define CPDOMDisplayServerSetStyleSize(aDOMElement, aWidth, aHeight) \
aDOMElement.style.width = MAX(0.0, ROUND(aWidth)) + "px";\
aDOMElement.style.height = MAX(0.0, ROUND(aHeight)) + "px";
#define CPDOMDisplayServerSetSize(aDOMElement, aWidth, aHeight) \
aDOMElement.width = MAX(0.0, ROUND(aWidth));\
aDOMElement.height = MAX(0.0, ROUND(aHeight));
#define CPDOMDisplayServerSetStyleBackgroundSize(aDOMElement, aWidth, aHeight)\
aDOMElement.style.backgroundSize = aWidth + ' ' + aHeight;
#define CPDOMDisplayServerAppendChild(aParentElement, aChildElement) \
aParentElement.appendChild(aChildElement);
#define CPDOMDisplayServerInsertBefore(aParentElement, aChildElement, aBeforeElement) \
aParentElement.insertBefore(aChildElement, aBeforeElement);
#define CPDOMDisplayServerRemoveChild(aParentElement, aChildElement) \
aParentElement.removeChild(aChildElement);
#define PREPARE_DOM_OPTIMIZATION()
#define EXECUTE_DOM_INSTRUCTIONS()
+96
View File
@@ -0,0 +1,96 @@
/*
* CPDOMDisplayServer.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 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
*/
/* Formerly a force-included header of function-like macros; promoted to a
real source file for the preprocessor-free build. */
function CPDOMDisplayServerSetStyleLeftTop(aDOMElement, aTransform, aLeft, aTop)
{
if (aTransform) var ____p = CGPointApplyAffineTransform(CGPointMake(aLeft, aTop), aTransform);
else var ____p = CGPointMake(aLeft, aTop);
aDOMElement.style.left = ROUND(____p.x) + "px";
aDOMElement.style.top = ROUND(____p.y) + "px";
}
function CPDOMDisplayServerSetStyleRightTop(aDOMElement, aTransform, aRight, aTop)
{
if (aTransform) var ____p = CGPointApplyAffineTransform(CGPointMake(aRight, aTop), aTransform);
else var ____p = CGPointMake(aRight, aTop);
aDOMElement.style.right = ROUND(____p.x) + "px";
aDOMElement.style.top = ROUND(____p.y) + "px";
}
function CPDOMDisplayServerSetStyleLeftBottom(aDOMElement, aTransform, aLeft, aBottom)
{
if (aTransform) var ____p = CGPointApplyAffineTransform(CGPointMake(aLeft, aBottom), aTransform);
else var ____p = CGPointMake(aLeft, aBottom);
aDOMElement.style.left = ROUND(____p.x) + "px";
aDOMElement.style.bottom = ROUND(____p.y) + "px";
}
function CPDOMDisplayServerSetStyleRightBottom(aDOMElement, aTransform, aRight, aBottom)
{
if (aTransform) var ____p = CGPointApplyAffineTransform(CGPointMake(aRight, aBottom), aTransform);
else var ____p = CGPointMake(aRight, aBottom);
aDOMElement.style.right = ROUND(____p.x) + "px";
aDOMElement.style.bottom = ROUND(____p.y) + "px";
}
function CPDOMDisplayServerSetStyleSize(aDOMElement, aWidth, aHeight)
{
aDOMElement.style.width = MAX(0.0, ROUND(aWidth)) + "px";
aDOMElement.style.height = MAX(0.0, ROUND(aHeight)) + "px";
}
function CPDOMDisplayServerSetSize(aDOMElement, aWidth, aHeight)
{
aDOMElement.width = MAX(0.0, ROUND(aWidth));
aDOMElement.height = MAX(0.0, ROUND(aHeight));
}
function CPDOMDisplayServerSetStyleBackgroundSize(aDOMElement, aWidth, aHeight)
{
aDOMElement.style.backgroundSize = aWidth + ' ' + aHeight;
}
function CPDOMDisplayServerAppendChild(aParentElement, aChildElement)
{
aParentElement.appendChild(aChildElement);
}
function CPDOMDisplayServerInsertBefore(aParentElement, aChildElement, aBeforeElement)
{
aParentElement.insertBefore(aChildElement, aBeforeElement);
}
function CPDOMDisplayServerRemoveChild(aParentElement, aChildElement)
{
aParentElement.removeChild(aChildElement);
}
function PREPARE_DOM_OPTIMIZATION()
{
}
function EXECUTE_DOM_INSTRUCTIONS()
{
}
-180
View File
@@ -1,180 +0,0 @@
/*
* CPPlatform+DOM.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2010, 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
*/
@global CPApp
if (typeof window["CPPlatformEnableHTMLDragAndDrop"] === "undefined")
CPPlatformEnableHTMLDragAndDrop = NO;
CPPlatformDidClearBodyElementNotification = @"CPPlatformDidClearBodyElementNotification";
CPPlatformWillClearBodyElementNotification = @"CPPlatformWillClearBodyElementNotification";
var screenNeedsInitialization = NO,
mainBodyElement = nil,
elementRemovalTest = new RegExp("\\bcpdontremove\\b", "g");
@implementation CPPlatform : CPBasePlatform
{
}
+ (void)initialize
{
if (self !== [CPPlatform class])
return;
screenNeedsInitialization = [CPPlatform isBrowser];
// We do this here because doing it later breaks IE.
if (typeof document !== 'undefined' && document.documentElement)
document.documentElement.style.overflow = "hidden";
if ([CPPlatform isBrowser])
{
// This differ from cocoa, where shouldTerminate is called in the method terminate of CPApp
// Cappuccino acts like this because we can not close a window openend by the user with a script (so not possible in terminate), and we can only prevent the action in the method onbeforeunload in js.
window.onbeforeunload = function()
{
if ([CPApp _sendDelegateApplicationShouldTerminate] != CPTerminateNow)
return [CPApp _sendDelegateApplicationShouldTerminateMessage];
};
var terminateApplication = function()
{
[self closeAllPlatformWindows];
[CPApp terminate:nil];
};
// "unload" is blocked by the permissions policy in current Chrome and prevents the
// back/forward cache. "pagehide" fires reliably instead. If event.persisted is true the
// page goes into the bfcache and may be restored, so the application must not terminate.
if ("onpagehide" in window)
{
window.addEventListener("pagehide", function(anEvent)
{
if (!anEvent.persisted)
terminateApplication();
}, NO);
}
else
window.onunload = terminateApplication;
}
}
+ (BOOL)isBrowser
{
return typeof window.cpIsDesktop === "undefined";
}
+ (BOOL)supportsDragAndDrop
{
return CPFeatureIsCompatible(CPHTMLDragAndDropFeature) && (CPPlatformEnableHTMLDragAndDrop || ![self isBrowser]);
}
+ (BOOL)supportsNativeMainMenu
{
return (typeof window["cpSetMainMenu"] === "function");
}
+ (void)terminateApplication
{
if (typeof window["cpTerminate"] === "function")
window.cpTerminate();
}
+ (void)activateIgnoringOtherApps:(BOOL)shouldIgnoreOtherApps
{
if (typeof window["cpActivateIgnoringOtherApps"] === "function")
window.cpActivateIgnoringOtherApps(!!shouldIgnoreOtherApps);
}
+ (void)deactivate
{
if (typeof window["cpDeactivate"] === "function")
window.cpDeactivate();
}
+ (void)hideOtherApplications:(id)aSender
{
if (typeof window["cpHideOtherApplications"] === "function")
window.cpHideOtherApplications();
}
+ (void)hide:(id)aSender
{
if (typeof window["cpHide"] === "function")
window.cpHide();
}
+ (DOMElement)mainBodyElement
{
if (!mainBodyElement)
mainBodyElement = document.getElementById("cappuccino-body") || document.body;
return mainBodyElement;
}
+ (void)initializeScreenIfNecessary
{
if (!screenNeedsInitialization)
return;
screenNeedsInitialization = NO;
[[CPNotificationCenter defaultCenter]
postNotificationName:CPPlatformWillClearBodyElementNotification
object:self];
var bodyElement = [self mainBodyElement];
// Get rid of any of the original contents of the page.
var children = bodyElement.childNodes,
length = children.length;
while (length--)
{
var element = children[length];
if (!element.className || element.className.match(elementRemovalTest) === null)
bodyElement.removeChild(element);
}
bodyElement.style.overflow = "hidden";
[[CPNotificationCenter defaultCenter]
postNotificationName:CPPlatformDidClearBodyElementNotification
object:self];
}
+ (void)closeAllPlatformWindows
{
var platformWindows = [CPPlatformWindow visiblePlatformWindows],
primaryPlatformWindow = [CPPlatformWindow primaryPlatformWindow],
platformWindowEnumerator = [platformWindows objectEnumerator],
platformWindow = nil;
while ((platformWindow = [platformWindowEnumerator nextObject]) != nil)
{
if (platformWindow != primaryPlatformWindow)
[platformWindow orderOut:self];
}
}
@end
+4 -10
View File
@@ -35,11 +35,6 @@
// From CPPlatformWindow+DOM.j
@global _CPDOMEventStop
#if PLATFORM(DOM)
#define SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent) anEvent._suppressCappuccinoCut = YES
#define SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent) anEvent._suppressCappuccinoPaste = YES
var hasEditableTarget = function(aDOMEvent)
{
var target = aDOMEvent.target || aDOMEvent.srcElement;
@@ -278,9 +273,9 @@ var hasEditableTarget = function(aDOMEvent)
if (!currentEventShouldBeSuppressed)
{
if (characters === "v")
SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent);
anEvent._suppressCappuccinoPaste = YES;
else if (characters === "x")
SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent);
anEvent._suppressCappuccinoCut = YES;
}
}
@@ -479,7 +474,7 @@ Return true if the event may be a copy and paste event, but the target is not an
var anEvent = [self _fakeClipboardEvent:aDOMEvent type:"v"],
platformWindow = [[anEvent window] platformWindow];
SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent);
anEvent._suppressCappuccinoPaste = YES;
// By default we'll stop the native handling of the event since we're handling it ourselves. However, we need to
// stop it before we send the event so that the event can overrule our choice. CPTextField for instance wants the
@@ -505,7 +500,7 @@ Return true if the event may be a copy and paste event, but the target is not an
var anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type.indexOf("cut") != CPNotFound ? "x" : "c")],
platformWindow = [[anEvent window] platformWindow];
SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent);
anEvent._suppressCappuccinoCut = YES;
[platformWindow _propagateCurrentDOMEvent:NO];
@@ -539,4 +534,3 @@ Return true if the event may be a copy and paste event, but the target is not an
@end
#endif
-204
View File
@@ -1,204 +0,0 @@
/*
* CPPlatformString.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2010, 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
*/
var DOMFixedWidthSpanElement = nil,
DOMFlexibleWidthSpanElement = nil,
DOMMetricsDivElement = nil,
DOMMetricsImgElement = nil,
DefaultFont = nil;
@implementation CPPlatformString : CPBasePlatformString
{
}
+ (void)initialize
{
if (self !== [CPPlatformString class])
return;
DefaultFont = [CPFont systemFontOfSize:CPFontCurrentSystemSize];
}
+ (void)bootstrap
{
[self createDOMElements];
}
+ (void)createDOMElements
{
var style,
bodyElement = [CPPlatform mainBodyElement];
DOMFlexibleWidthSpanElement = document.createElement("span");
DOMFlexibleWidthSpanElement.className = "cpdontremove";
style = DOMFlexibleWidthSpanElement.style;
style.position = "absolute";
style.left = "-100000px";
style.zIndex = -10000;
style.visibility = "visible";
style.padding = "0px";
style.margin = "0px";
style.whiteSpace = "pre";
DOMFixedWidthSpanElement = document.createElement("span");
DOMFixedWidthSpanElement.className = "cpdontremove";
style = DOMFixedWidthSpanElement.style;
style.display = "block";
style.position = "absolute";
style.left = "-100000px";
style.zIndex = -10000;
style.visibility = "visible";
style.padding = "0px";
style.margin = "0px";
style.width = "1px";
style.wordWrap = "break-word";
try
{
style.whiteSpace = "pre";
style.whiteSpace = "-o-pre-wrap";
style.whiteSpace = "-pre-wrap";
style.whiteSpace = "-moz-pre-wrap";
style.whiteSpace = "pre-wrap";
}
catch(e)
{
//some versions of IE throw exceptions for unsupported properties.
style.whiteSpace = "pre";
}
bodyElement.appendChild(DOMFlexibleWidthSpanElement);
bodyElement.appendChild(DOMFixedWidthSpanElement);
}
+ (void)createDOMMetricsElements
{
var style,
bodyElement = [CPPlatform mainBodyElement];
DOMMetricsDivElement = document.createElement("div");
DOMMetricsDivElement.className = "cpdontremove";
style = DOMMetricsDivElement.style;
style.position = "absolute";
style.left = "-100000px";
style.zIndex = -10000;
style.width = "100000px";
style.whiteSpace = "nowrap";
style.lineHeight = "1em";
style.padding = "0px";
style.margin = "0px";
DOMMetricsDivElement.innerHTML = "x";
bodyElement.appendChild(DOMMetricsDivElement);
var imgPath = [[CPBundle bundleForClass:[CPView class]] pathForResource:@"empty.png"];
DOMMetricsImgElement = document.createElement("img");
DOMMetricsImgElement.className = "cpdontremove";
DOMMetricsImgElement.setAttribute("src", imgPath);
DOMMetricsImgElement.setAttribute("width", "1");
DOMMetricsImgElement.setAttribute("height", "1");
DOMMetricsImgElement.setAttribute("alt", "");
style = DOMMetricsImgElement.style;
style.zIndex = -10000;
style.visibility = "visible";
style.padding = "0px";
style.margin = "0px";
style.border = "none";
style.verticalAlign = "baseline";
DOMMetricsDivElement.appendChild(DOMMetricsImgElement);
}
+ (int)charPositionOfString:(CPString)aString withFont:(CPFont)aFont forPoint:(CGPoint)aPoint
{
if (!aString)
return 0;
var position = 0,
stringLength = aString.length,
currentString = "";
for (var i = 0; i < stringLength; i++)
{
var lastChar = aString[i];
currentString += lastChar;
var sizeOfString = [self sizeOfString:currentString withFont:aFont forWidth:nil].width,
sizeLastChar = [self sizeOfString:lastChar withFont:aFont forWidth:nil].width;
if (sizeOfString - sizeLastChar / 2 < aPoint.x)
position++;
else
break;
}
return position;
}
+ (CGSize)sizeOfString:(CPString)aString withFont:(CPFont)aFont forWidth:(float)aWidth
{
if (!DOMFixedWidthSpanElement)
[self createDOMElements];
var span;
if (!aWidth)
span = DOMFlexibleWidthSpanElement;
else
{
span = DOMFixedWidthSpanElement;
span.style.width = ROUND(aWidth) + "px";
}
var effectiveFontCSSString = [(aFont || DefaultFont) cssString];
if (span.style.font !== effectiveFontCSSString)
span.style.font = effectiveFontCSSString;
if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature))
span.innerText = aString;
else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature))
span.textContent = aString;
return CGSizeMake(span.clientWidth, span.clientHeight);
}
+ (CPDictionary)metricsOfFont:(CPFont)aFont
{
if (!DOMMetricsDivElement)
[self createDOMMetricsElements];
DOMMetricsDivElement.style.font = [(aFont || DefaultFont) cssString];
var lineHeight = DOMMetricsDivElement.offsetHeight,
baseline = DOMMetricsImgElement.offsetTop + DOMMetricsImgElement.offsetHeight,
descender = baseline - lineHeight;
return @{
@"ascender": baseline,
@"descender": descender,
@"lineHeight": lineHeight,
};
}
@end
+18 -11
View File
@@ -246,9 +246,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
// When scrolling with an old-style scroll wheel with discete steps ('clicks'), the scroll amount can indicate how many "lines" to
// scroll.
#define SCROLLWHEEL_LINE_PIXELS 6.0
#if PLATFORM(DOM)
var SCROLLWHEEL_LINE_PIXELS = 6.0;
@implementation CPPlatformWindow (DOM)
@@ -258,16 +256,26 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
if (self)
{
_DOMWindow = window;
_contentRect = CGRectMakeZero();
if (CPDOMAvailable)
{
_DOMWindow = window;
_contentRect = CGRectMakeZero();
_windowLevels = [];
_windowLayers = @{};
_windowLevels = [];
_windowLayers = @{};
_platformPasteboard = [CPPlatformPasteboard new];
_platformPasteboard = [CPPlatformPasteboard new];
[self registerDOMWindow];
[self updateFromNativeContentRect];
[self registerDOMWindow];
[self updateFromNativeContentRect];
}
else
{
_contentRect = CGRectMakeZero();
_windowLevels = [];
_windowLayers = @{};
}
_charCodes = {};
@@ -1928,7 +1936,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio
}
@end
#endif
var CPEventClass = [CPEvent class];
-23
View File
@@ -1,23 +0,0 @@
/*
* Platform.h
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 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
*/
#define PLATFORM(FEATURE) (defined( PLATFORM_##FEATURE ) && PLATFORM_##FEATURE)
+1
View File
@@ -21,6 +21,7 @@
*/
@import <Foundation/CPRunLoop.j>
@import "CPDOMDisplayServer.j"
PREPARE_DOM_OPTIMIZATION();
+399 -400
View File
@@ -28,6 +28,7 @@
@import "CPView.j"
@import "CPControl.j"
@import "CPPlatform.j"
@import "CPCompatibility.j"
var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
@@ -71,11 +72,9 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
unsigned _flags;
#if PLATFORM(DOM)
DOMElement _DOMImageElement;
DOMElement _DOMTextElement;
DOMElement _DOMTextShadowElement;
#endif
}
@global document
@@ -132,7 +131,8 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
_alignment = anAlignment;
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
switch (_alignment)
{
case CPLeftTextAlignment:
@@ -155,7 +155,6 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
_DOMElement.style.textAlign = "";
break;
}
#endif
}
- (CPTextAlignment)alignment
@@ -250,9 +249,10 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
_textColor = aTextColor;
#if PLATFORM(DOM)
_DOMElement.style.color = [_textColor cssString];
#endif
if (CPDOMAvailable)
{
_DOMElement.style.color = [_textColor cssString];
}
}
- (CPColor)textColor
@@ -330,8 +330,7 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
var textFrame = CGRectMakeZero();
#if PLATFORM(DOM)
if (_DOMTextElement)
if (CPDOMAvailable && _DOMTextElement)
{
var textStyle = _DOMTextElement.style;
@@ -343,7 +342,6 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
textFrame.size.width += _textShadowOffset.width;
textFrame.size.height += _textShadowOffset.height;
}
#endif
return textFrame;
}
@@ -422,427 +420,428 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
- (void)layoutSubviews
{
#if PLATFORM(DOM)
var needsDOMTextElement = _imagePosition !== CPImageOnly && ([_text length] > 0),
hasDOMTextElement = !!_DOMTextElement;
// Create or destroy the DOM Text Element as necessary
if (needsDOMTextElement !== hasDOMTextElement)
if (CPDOMAvailable)
{
if (hasDOMTextElement)
var needsDOMTextElement = _imagePosition !== CPImageOnly && ([_text length] > 0),
hasDOMTextElement = !!_DOMTextElement;
// Create or destroy the DOM Text Element as necessary
if (needsDOMTextElement !== hasDOMTextElement)
{
_DOMElement.removeChild(_DOMTextElement);
_DOMTextElement = nil;
hasDOMTextElement = NO;
}
else
{
_DOMTextElement = document.createElement("div");
var textStyle = _DOMTextElement.style;
textStyle.position = "absolute";
textStyle.whiteSpace = "pre";
textStyle.zIndex = 200;
textStyle.overflow = "hidden";
_DOMElement.appendChild(_DOMTextElement);
hasDOMTextElement = YES;
// We have to set all these values now.
_flags |= _CPImageAndTextViewTextChangedFlag | _CPImageAndTextViewFontChangedFlag | _CPImageAndTextViewLineBreakModeChangedFlag | _CPImageAndTextViewTextUnderlineChangedFlag;
}
}
var textStyle = hasDOMTextElement ? _DOMTextElement.style : nil;
// If theme is CSS based and if shadow color is a CSS dictionary, we don't need a separate DOM element,
// we simply put shadow styling on the regular text element
if (hasDOMTextElement && [[self actualTheme] isCSSBased] && [_textShadowColor cssDictionary])
{
[[_textShadowColor cssDictionary] enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
_DOMTextElement.style[aKey] = anObject;
}];
}
else
{
// Create or destroy the DOM Text Shadow element as necessary.
// If _textShadowColor's alphaComponent is 0, don't bother drawing anything (issue #1412).
// This improves performance as we get rid of an invisible element, and makes IE <9.0 capable
// of correctly 'rendering' shadows with [CPColor clearColor].
var needsDOMTextShadowElement = hasDOMTextElement && [_textShadowColor alphaComponent] > 0.0,
hasDOMTextShadowElement = !!_DOMTextShadowElement;
if (needsDOMTextShadowElement !== hasDOMTextShadowElement)
{
if (hasDOMTextShadowElement)
if (hasDOMTextElement)
{
_DOMElement.removeChild(_DOMTextShadowElement);
_DOMTextShadowElement = nil;
hasDOMTextShadowElement = NO;
_DOMElement.removeChild(_DOMTextElement);
_DOMTextElement = nil;
hasDOMTextElement = NO;
}
else
{
_DOMTextShadowElement = document.createElement("div");
_DOMTextElement = document.createElement("div");
var shadowStyle = _DOMTextShadowElement.style,
font = (_font || [CPFont systemFontOfSize:CPFontCurrentSystemSize]);
var textStyle = _DOMTextElement.style;
textStyle.position = "absolute";
textStyle.whiteSpace = "pre";
textStyle.zIndex = 200;
textStyle.overflow = "hidden";
shadowStyle.font = [font cssString];
shadowStyle.position = "absolute";
shadowStyle.whiteSpace = textStyle.whiteSpace;
shadowStyle.wordWrap = textStyle.wordWrap;
shadowStyle.color = [_textShadowColor cssString];
shadowStyle.lineHeight = [font defaultLineHeightForFont] + "px";
_DOMElement.appendChild(_DOMTextElement);
hasDOMTextElement = YES;
shadowStyle.zIndex = 150;
shadowStyle.textOverflow = textStyle.textOverflow;
if (document.attachEvent)
{
shadowStyle.overflow = textStyle.overflow;
}
else
{
shadowStyle.overflowX = textStyle.overflowX;
shadowStyle.overflowY = textStyle.overflowY;
}
_DOMElement.appendChild(_DOMTextShadowElement);
hasDOMTextShadowElement = YES;
_flags |= _CPImageAndTextViewTextChangedFlag; //sigh...
}
}
}
var shadowStyle = hasDOMTextShadowElement ? _DOMTextShadowElement.style : nil;
if (hasDOMTextElement)
{
// Update the text contents if necessary.
if (_flags & _CPImageAndTextViewTextChangedFlag)
if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature))
{
_DOMTextElement.innerText = _text;
if (_DOMTextShadowElement)
_DOMTextShadowElement.innerText = _text;
}
else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature))
{
_DOMTextElement.textContent = _text;
if (_DOMTextShadowElement)
_DOMTextShadowElement.textContent = _text;
}
if (_flags & _CPImageAndTextViewFontChangedFlag)
{
var font = (_font || [CPFont systemFontOfSize:CPFontCurrentSystemSize]),
fontStyle = [font cssString];
textStyle.font = fontStyle;
textStyle.lineHeight = [font defaultLineHeightForFont] + "px";
if (shadowStyle)
{
shadowStyle.font = fontStyle;
shadowStyle.lineHeight = [font defaultLineHeightForFont] + "px";
// We have to set all these values now.
_flags |= _CPImageAndTextViewTextChangedFlag | _CPImageAndTextViewFontChangedFlag | _CPImageAndTextViewLineBreakModeChangedFlag | _CPImageAndTextViewTextUnderlineChangedFlag;
}
}
if (_flags & _CPImageAndTextViewTextUnderlineChangedFlag)
var textStyle = hasDOMTextElement ? _DOMTextElement.style : nil;
// If theme is CSS based and if shadow color is a CSS dictionary, we don't need a separate DOM element,
// we simply put shadow styling on the regular text element
if (hasDOMTextElement && [[self actualTheme] isCSSBased] && [_textShadowColor cssDictionary])
{
textStyle.textDecoration = _textUnderline ? "underline" : "";
}
// Update the line break mode if necessary.
if (_flags & _CPImageAndTextViewLineBreakModeChangedFlag)
{
switch (_lineBreakMode)
{
case CPLineBreakByClipping:
textStyle.overflow = "hidden";
textStyle.textOverflow = "clip";
textStyle.whiteSpace = "pre";
textStyle.wordWrap = "normal";
break;
case CPLineBreakByTruncatingHead:
case CPLineBreakByTruncatingMiddle: // Don't have support for these (yet?), so just degrade to truncating tail.
case CPLineBreakByTruncatingTail:
textStyle.textOverflow = "ellipsis";
textStyle.whiteSpace = "pre";
textStyle.overflow = "hidden";
textStyle.wordWrap = "normal";
break;
case CPLineBreakByCharWrapping:
case CPLineBreakByWordWrapping:
textStyle.wordWrap = "break-word";
try
{
textStyle.whiteSpace = "pre";
textStyle.whiteSpace = "-o-pre-wrap";
textStyle.whiteSpace = "-pre-wrap";
textStyle.whiteSpace = "-moz-pre-wrap";
textStyle.whiteSpace = "pre-wrap";
}
catch (e)
{
//internet explorer doesn't like these properties
textStyle.whiteSpace = "pre";
}
textStyle.overflow = "hidden";
textStyle.textOverflow = "clip";
break;
}
if (shadowStyle)
{
if (document.attachEvent)
{
shadowStyle.overflow = textStyle.overflow;
}
else
{
shadowStyle.overflowX = textStyle.overflowX;
shadowStyle.overflowY = textStyle.overflowY;
}
shadowStyle.wordWrap = textStyle.wordWrap;
shadowStyle.whiteSpace = textStyle.whiteSpace;
shadowStyle.textOverflow = textStyle.textOverflow;
}
}
}
var needsDOMImageElement = _image != nil && _imagePosition !== CPNoImage,
hasDOMImageElement = !!_DOMImageElement,
// For CSS theming
isCSSBasedImage = [_image isCSSBased],
isIMGImageElement = hasDOMImageElement && (_DOMImageElement.nodeName == "IMG");
// First, check if we need to destroy a current DOM image element. This is the case if :
// - we have one but don't need it anymore
// - we have one but not the right one (that is a DIV but needing an IMG, and vice versa)
if (hasDOMImageElement)
{
if (!needsDOMImageElement || (isIMGImageElement && isCSSBasedImage) || (!isIMGImageElement && !isCSSBasedImage))
{
// OK, destroy it
_DOMElement.removeChild(_DOMImageElement);
_DOMImageElement = nil;
hasDOMImageElement = NO;
// CSS styling cleaning
_cssStylePreviousState = @[];
_cssStyleNode = nil;
}
}
// Now, if we need a DOM image element and if we don't have one, create a new one
if (needsDOMImageElement && !hasDOMImageElement)
{
_DOMImageElement = document.createElement(isCSSBasedImage ? "div" : "img");
if ([CPPlatform supportsDragAndDrop])
{
_DOMImageElement.setAttribute("draggable", "true");
_DOMImageElement.style["-khtml-user-drag"] = "element";
}
var imageStyle = _DOMImageElement.style;
imageStyle.top = "0px";
imageStyle.left = "0px";
imageStyle.position = "absolute";
imageStyle.zIndex = 100;
_DOMElement.appendChild(_DOMImageElement);
hasDOMImageElement = YES;
}
var size = [self bounds].size,
textRect = CGRectMake(0.0, 0.0, size.width, size.height);
if (hasDOMImageElement)
{
if (!imageStyle)
var imageStyle = _DOMImageElement.style;
var centerX = size.width / 2.0,
centerY = size.height / 2.0,
imageSize = [_image size],
imageWidth = imageSize.width,
imageHeight = imageSize.height;
if (_imageScaling === CPImageScaleAxesIndependently)
{
imageWidth = size.width;
imageHeight = size.height;
}
else if (_imageScaling === CPImageScaleProportionallyDown)
{
var scale = MIN(MIN(size.width, imageWidth) / imageWidth, MIN(size.height, imageHeight) / imageHeight);
imageWidth *= scale;
imageHeight *= scale;
}
else if (_imageScaling === CPImageScaleProportionallyUpOrDown)
{
var scale = MIN(size.width / imageWidth, size.height / imageHeight);
imageWidth *= scale;
imageHeight *= scale;
}
if (CPFeatureIsCompatible(CPOpacityRequiresFilterFeature))
imageStyle.filter = @"alpha(opacity=" + _shouldDimImage ? 50 : 100 + ")";
else
imageStyle.opacity = _shouldDimImage ? 0.5 : 1.0;
_DOMImageElement.width = imageWidth;
_DOMImageElement.height = imageHeight;
imageStyle.width = MAX(imageWidth, 0) + "px";
imageStyle.height = MAX(imageHeight, 0) + "px";
if (_imagePosition === CPImageBelow)
{
imageStyle.left = FLOOR(centerX - imageWidth / 2.0) + "px";
imageStyle.top = FLOOR(size.height - imageHeight) + "px";
textRect.size.height = size.height - imageHeight - _imageOffset;
}
else if (_imagePosition === CPImageAbove)
{
imageStyle.left = FLOOR(centerX - imageWidth / 2.0) + "px";
imageStyle.top = 0 + "px";
textRect.origin.y += imageHeight + _imageOffset;
textRect.size.height = size.height - imageHeight - _imageOffset;
}
else if (_imagePosition === CPImageLeft)
{
imageStyle.top = _imageVerticalOffset + FLOOR(centerY - imageHeight / 2.0) + "px";
imageStyle.left = "0px";
textRect.origin.x = imageWidth + _imageOffset;
textRect.size.width -= imageWidth + _imageOffset;
}
else if (_imagePosition === CPImageRight)
{
imageStyle.top = _imageVerticalOffset + FLOOR(centerY - imageHeight / 2.0) + "px";
imageStyle.left = FLOOR(size.width - imageWidth) + "px";
textRect.size.width -= imageWidth + _imageOffset;
}
else if (_imagePosition === CPImageOnly || _imagePosition == CPImageOverlaps)
{
imageStyle.top = _imageVerticalOffset + FLOOR(centerY - imageHeight / 2.0) + "px";
imageStyle.left = FLOOR(centerX - imageWidth / 2.0) + "px";
}
if (_flags & _CPImageAndTextViewImageChangedFlag)
{
if (isCSSBasedImage)
{
// For material icons images & co.
if ([_image _shouldBeResized])
[_image _setDisplaySize:CGSizeMake(imageWidth, imageHeight)];
_cssStyleNode = [_image applyCSSImageForView:self
onDOMElement:_DOMImageElement
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
}
else
_DOMImageElement.src = [_image filename];
}
}
if (hasDOMTextElement)
{
var textRectX = CGRectGetMinX(textRect),
textRectY = CGRectGetMinY(textRect),
textRectWidth = CGRectGetWidth(textRect),
textRectHeight = CGRectGetHeight(textRect);
if (textRectWidth <= 0 || textRectHeight <= 0)
{
// Don't bother trying to position the text in an empty rect.
textRectWidth = 0;
textRectHeight = 0;
[[_textShadowColor cssDictionary] enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
_DOMTextElement.style[aKey] = anObject;
}];
}
else
{
if (_verticalAlignment !== CPTopVerticalTextAlignment)
{
if (!_textSize)
{
if (_lineBreakMode === CPLineBreakByCharWrapping ||
_lineBreakMode === CPLineBreakByWordWrapping)
{
_textSize = [_text sizeWithFont:_font inWidth:textRectWidth];
// Create or destroy the DOM Text Shadow element as necessary.
// If _textShadowColor's alphaComponent is 0, don't bother drawing anything (issue #1412).
// This improves performance as we get rid of an invisible element, and makes IE <9.0 capable
// of correctly 'rendering' shadows with [CPColor clearColor].
var needsDOMTextShadowElement = hasDOMTextElement && [_textShadowColor alphaComponent] > 0.0,
hasDOMTextShadowElement = !!_DOMTextShadowElement;
if (_usesSingleLineMode)
_textSize.height = [_font defaultLineHeightForFont];
if (needsDOMTextShadowElement !== hasDOMTextShadowElement)
{
if (hasDOMTextShadowElement)
{
_DOMElement.removeChild(_DOMTextShadowElement);
_DOMTextShadowElement = nil;
hasDOMTextShadowElement = NO;
}
else
{
_DOMTextShadowElement = document.createElement("div");
var shadowStyle = _DOMTextShadowElement.style,
font = (_font || [CPFont systemFontOfSize:CPFontCurrentSystemSize]);
shadowStyle.font = [font cssString];
shadowStyle.position = "absolute";
shadowStyle.whiteSpace = textStyle.whiteSpace;
shadowStyle.wordWrap = textStyle.wordWrap;
shadowStyle.color = [_textShadowColor cssString];
shadowStyle.lineHeight = [font defaultLineHeightForFont] + "px";
shadowStyle.zIndex = 150;
shadowStyle.textOverflow = textStyle.textOverflow;
if (document.attachEvent)
{
shadowStyle.overflow = textStyle.overflow;
}
else
{
_textSize = [_text sizeWithFont:_font];
// Account for possible fractional pixels at right edge
_textSize.width += 1;
shadowStyle.overflowX = textStyle.overflowX;
shadowStyle.overflowY = textStyle.overflowY;
}
// Account for possible fractional pixels at bottom edge
_textSize.height += 1;
}
_DOMElement.appendChild(_DOMTextShadowElement);
if (_verticalAlignment === CPCenterVerticalTextAlignment)
{
// Since we added +1 px height above to show fractional pixels on the bottom, we have to remove that when calculating vertical centre.
textRectY = textRectY + (textRectHeight - _textSize.height + 1.0) / 2.0;
textRectHeight = _textSize.height;
}
hasDOMTextShadowElement = YES;
else // if (_verticalAlignment === CPBottomVerticalTextAlignment)
{
textRectY = textRectY + textRectHeight - _textSize.height;
textRectHeight = _textSize.height;
_flags |= _CPImageAndTextViewTextChangedFlag; //sigh...
}
}
}
textStyle.top = ROUND(textRectY) + "px";
textStyle.left = ROUND(textRectX) + "px";
textStyle.width = MAX(CEIL(textRectWidth), 0) + "px";
textStyle.height = MAX(CEIL(textRectHeight), 0) + "px";
textStyle.verticalAlign = @"top";
var shadowStyle = hasDOMTextShadowElement ? _DOMTextShadowElement.style : nil;
if (shadowStyle)
if (hasDOMTextElement)
{
if (_flags & _CPImageAndTextViewTextShadowColorChangedFlag)
shadowStyle.color = [_textShadowColor cssString];
// Update the text contents if necessary.
if (_flags & _CPImageAndTextViewTextChangedFlag)
if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature))
{
_DOMTextElement.innerText = _text;
shadowStyle.top = ROUND(textRectY + _textShadowOffset.height) + "px";
shadowStyle.left = ROUND(textRectX + _textShadowOffset.width) + "px";
shadowStyle.width = MAX(CEIL(textRectWidth), 0) + "px";
shadowStyle.height = MAX(CEIL(textRectHeight), 0) + "px";
if (_DOMTextShadowElement)
_DOMTextShadowElement.innerText = _text;
}
else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature))
{
_DOMTextElement.textContent = _text;
if (_DOMTextShadowElement)
_DOMTextShadowElement.textContent = _text;
}
if (_flags & _CPImageAndTextViewFontChangedFlag)
{
var font = (_font || [CPFont systemFontOfSize:CPFontCurrentSystemSize]),
fontStyle = [font cssString];
textStyle.font = fontStyle;
textStyle.lineHeight = [font defaultLineHeightForFont] + "px";
if (shadowStyle)
{
shadowStyle.font = fontStyle;
shadowStyle.lineHeight = [font defaultLineHeightForFont] + "px";
}
}
if (_flags & _CPImageAndTextViewTextUnderlineChangedFlag)
{
textStyle.textDecoration = _textUnderline ? "underline" : "";
}
// Update the line break mode if necessary.
if (_flags & _CPImageAndTextViewLineBreakModeChangedFlag)
{
switch (_lineBreakMode)
{
case CPLineBreakByClipping:
textStyle.overflow = "hidden";
textStyle.textOverflow = "clip";
textStyle.whiteSpace = "pre";
textStyle.wordWrap = "normal";
break;
case CPLineBreakByTruncatingHead:
case CPLineBreakByTruncatingMiddle: // Don't have support for these (yet?), so just degrade to truncating tail.
case CPLineBreakByTruncatingTail:
textStyle.textOverflow = "ellipsis";
textStyle.whiteSpace = "pre";
textStyle.overflow = "hidden";
textStyle.wordWrap = "normal";
break;
case CPLineBreakByCharWrapping:
case CPLineBreakByWordWrapping:
textStyle.wordWrap = "break-word";
try
{
textStyle.whiteSpace = "pre";
textStyle.whiteSpace = "-o-pre-wrap";
textStyle.whiteSpace = "-pre-wrap";
textStyle.whiteSpace = "-moz-pre-wrap";
textStyle.whiteSpace = "pre-wrap";
}
catch (e)
{
//internet explorer doesn't like these properties
textStyle.whiteSpace = "pre";
}
textStyle.overflow = "hidden";
textStyle.textOverflow = "clip";
break;
}
if (shadowStyle)
{
if (document.attachEvent)
{
shadowStyle.overflow = textStyle.overflow;
}
else
{
shadowStyle.overflowX = textStyle.overflowX;
shadowStyle.overflowY = textStyle.overflowY;
}
shadowStyle.wordWrap = textStyle.wordWrap;
shadowStyle.whiteSpace = textStyle.whiteSpace;
shadowStyle.textOverflow = textStyle.textOverflow;
}
}
}
var needsDOMImageElement = _image != nil && _imagePosition !== CPNoImage,
hasDOMImageElement = !!_DOMImageElement,
// For CSS theming
isCSSBasedImage = [_image isCSSBased],
isIMGImageElement = hasDOMImageElement && (_DOMImageElement.nodeName == "IMG");
// First, check if we need to destroy a current DOM image element. This is the case if :
// - we have one but don't need it anymore
// - we have one but not the right one (that is a DIV but needing an IMG, and vice versa)
if (hasDOMImageElement)
{
if (!needsDOMImageElement || (isIMGImageElement && isCSSBasedImage) || (!isIMGImageElement && !isCSSBasedImage))
{
// OK, destroy it
_DOMElement.removeChild(_DOMImageElement);
_DOMImageElement = nil;
hasDOMImageElement = NO;
// CSS styling cleaning
_cssStylePreviousState = @[];
_cssStyleNode = nil;
}
}
// Now, if we need a DOM image element and if we don't have one, create a new one
if (needsDOMImageElement && !hasDOMImageElement)
{
_DOMImageElement = document.createElement(isCSSBasedImage ? "div" : "img");
if ([CPPlatform supportsDragAndDrop])
{
_DOMImageElement.setAttribute("draggable", "true");
_DOMImageElement.style["-khtml-user-drag"] = "element";
}
var imageStyle = _DOMImageElement.style;
imageStyle.top = "0px";
imageStyle.left = "0px";
imageStyle.position = "absolute";
imageStyle.zIndex = 100;
_DOMElement.appendChild(_DOMImageElement);
hasDOMImageElement = YES;
}
var size = [self bounds].size,
textRect = CGRectMake(0.0, 0.0, size.width, size.height);
if (hasDOMImageElement)
{
if (!imageStyle)
var imageStyle = _DOMImageElement.style;
var centerX = size.width / 2.0,
centerY = size.height / 2.0,
imageSize = [_image size],
imageWidth = imageSize.width,
imageHeight = imageSize.height;
if (_imageScaling === CPImageScaleAxesIndependently)
{
imageWidth = size.width;
imageHeight = size.height;
}
else if (_imageScaling === CPImageScaleProportionallyDown)
{
var scale = MIN(MIN(size.width, imageWidth) / imageWidth, MIN(size.height, imageHeight) / imageHeight);
imageWidth *= scale;
imageHeight *= scale;
}
else if (_imageScaling === CPImageScaleProportionallyUpOrDown)
{
var scale = MIN(size.width / imageWidth, size.height / imageHeight);
imageWidth *= scale;
imageHeight *= scale;
}
if (CPFeatureIsCompatible(CPOpacityRequiresFilterFeature))
imageStyle.filter = @"alpha(opacity=" + _shouldDimImage ? 50 : 100 + ")";
else
imageStyle.opacity = _shouldDimImage ? 0.5 : 1.0;
_DOMImageElement.width = imageWidth;
_DOMImageElement.height = imageHeight;
imageStyle.width = MAX(imageWidth, 0) + "px";
imageStyle.height = MAX(imageHeight, 0) + "px";
if (_imagePosition === CPImageBelow)
{
imageStyle.left = FLOOR(centerX - imageWidth / 2.0) + "px";
imageStyle.top = FLOOR(size.height - imageHeight) + "px";
textRect.size.height = size.height - imageHeight - _imageOffset;
}
else if (_imagePosition === CPImageAbove)
{
imageStyle.left = FLOOR(centerX - imageWidth / 2.0) + "px";
imageStyle.top = 0 + "px";
textRect.origin.y += imageHeight + _imageOffset;
textRect.size.height = size.height - imageHeight - _imageOffset;
}
else if (_imagePosition === CPImageLeft)
{
imageStyle.top = _imageVerticalOffset + FLOOR(centerY - imageHeight / 2.0) + "px";
imageStyle.left = "0px";
textRect.origin.x = imageWidth + _imageOffset;
textRect.size.width -= imageWidth + _imageOffset;
}
else if (_imagePosition === CPImageRight)
{
imageStyle.top = _imageVerticalOffset + FLOOR(centerY - imageHeight / 2.0) + "px";
imageStyle.left = FLOOR(size.width - imageWidth) + "px";
textRect.size.width -= imageWidth + _imageOffset;
}
else if (_imagePosition === CPImageOnly || _imagePosition == CPImageOverlaps)
{
imageStyle.top = _imageVerticalOffset + FLOOR(centerY - imageHeight / 2.0) + "px";
imageStyle.left = FLOOR(centerX - imageWidth / 2.0) + "px";
}
if (_flags & _CPImageAndTextViewImageChangedFlag)
{
if (isCSSBasedImage)
{
// For material icons images & co.
if ([_image _shouldBeResized])
[_image _setDisplaySize:CGSizeMake(imageWidth, imageHeight)];
_cssStyleNode = [_image applyCSSImageForView:self
onDOMElement:_DOMImageElement
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
}
else
_DOMImageElement.src = [_image filename];
}
}
if (hasDOMTextElement)
{
var textRectX = CGRectGetMinX(textRect),
textRectY = CGRectGetMinY(textRect),
textRectWidth = CGRectGetWidth(textRect),
textRectHeight = CGRectGetHeight(textRect);
if (textRectWidth <= 0 || textRectHeight <= 0)
{
// Don't bother trying to position the text in an empty rect.
textRectWidth = 0;
textRectHeight = 0;
}
else
{
if (_verticalAlignment !== CPTopVerticalTextAlignment)
{
if (!_textSize)
{
if (_lineBreakMode === CPLineBreakByCharWrapping ||
_lineBreakMode === CPLineBreakByWordWrapping)
{
_textSize = [_text sizeWithFont:_font inWidth:textRectWidth];
if (_usesSingleLineMode)
_textSize.height = [_font defaultLineHeightForFont];
}
else
{
_textSize = [_text sizeWithFont:_font];
// Account for possible fractional pixels at right edge
_textSize.width += 1;
}
// Account for possible fractional pixels at bottom edge
_textSize.height += 1;
}
if (_verticalAlignment === CPCenterVerticalTextAlignment)
{
// Since we added +1 px height above to show fractional pixels on the bottom, we have to remove that when calculating vertical centre.
textRectY = textRectY + (textRectHeight - _textSize.height + 1.0) / 2.0;
textRectHeight = _textSize.height;
}
else // if (_verticalAlignment === CPBottomVerticalTextAlignment)
{
textRectY = textRectY + textRectHeight - _textSize.height;
textRectHeight = _textSize.height;
}
}
}
textStyle.top = ROUND(textRectY) + "px";
textStyle.left = ROUND(textRectX) + "px";
textStyle.width = MAX(CEIL(textRectWidth), 0) + "px";
textStyle.height = MAX(CEIL(textRectHeight), 0) + "px";
textStyle.verticalAlign = @"top";
if (shadowStyle)
{
if (_flags & _CPImageAndTextViewTextShadowColorChangedFlag)
shadowStyle.color = [_textShadowColor cssString];
shadowStyle.top = ROUND(textRectY + _textShadowOffset.height) + "px";
shadowStyle.left = ROUND(textRectX + _textShadowOffset.width) + "px";
shadowStyle.width = MAX(CEIL(textRectWidth), 0) + "px";
shadowStyle.height = MAX(CEIL(textRectHeight), 0) + "px";
}
}
}
#endif
_flags = 0;
}
@@ -901,10 +900,10 @@ var _CPimageAndTextViewFrameSizeChangedFlag = 1 << 0,
- (void)setSelectedRange:(CPRange)aRange
{
#if PLATFORM(DOM)
if (!CPDOMAvailable) return;
if (_DOMTextElement)
[[[self window] platformWindow] setSelectedRange:aRange inElement:_DOMTextElement];
#endif
}
@end
-18
View File
@@ -76,12 +76,8 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
- (BOOL)hasThemeState:(ThemeState)aState
{
#if DEBUG
// TODO: To allow aState to be an array is now deprecated. An exception is thrown only in Debug version as
// the preformance cost for this check is to high. We should remove this check in a future release.
if (aState && aState.isa && [aState isKindOfClass:CPArray])
[CPException raise:CPInvalidArgumentException reason:@"aState can't be an array. Please use 'hasThemeStates: instead: " + aState];
#endif
return _themeState.hasThemeState(aState);
}
@@ -99,12 +95,8 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
- (BOOL)setThemeState:(ThemeState)aState
{
#if DEBUG
// TODO: To allow aState to be an array is now deprecated. An exception is thrown only in Debug version as
// the preformance cost for this check is to high. We should remove this check in a future release.
if (aState && aState.isa && [aState isKindOfClass:CPArray])
[CPException raise:CPInvalidArgumentException reason:@"aState can't be an array. Please use 'setThemeStates: instead: " + aState];
#endif
if (_themeState.hasThemeState(aState))
return NO;
@@ -116,12 +108,8 @@ var CPViewThemeClassKey = @"CPViewThemeClassKey",
- (BOOL)unsetThemeState:(ThemeState)aState
{
#if DEBUG
// TODO: To allow aState to be an array is now deprecated. An exception is thrown only in Debug version as
// the preformance cost for this check is to high. We should remove this check in a future release.
if (aState && aState.isa && [aState isKindOfClass:CPArray])
[CPException raise:CPInvalidArgumentException reason:@"aState can't be an array. Please use 'unsetThemeStates: instead: " + aState];
#endif
var oldThemeState = _themeState;
@@ -318,12 +306,8 @@ var NULL_THEME = {};
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName inState:(ThemeState)aState
{
#if DEBUG
// TODO: To allow aState to be an array is now deprecated. An exception is thrown only in Debug version as
// the preformance cost for this check is to high. We should remove this check in a future release.
if (aState.isa && [aState isKindOfClass:CPArray])
[CPException raise:CPInvalidArgumentException reason:self + @": aState can't be an array. Please use 'setValue:forThemeAttribute:inStates:' instead: " + aState];
#endif
var themeAttr = _themeAttributes && _themeAttributes[aName];
@@ -361,10 +345,8 @@ var NULL_THEME = {};
- (id)valueForThemeAttribute:(CPString)aName inState:(ThemeState)aState
{
#if DEBUG
if (aState.isa && [aState isKindOfClass:CPArray])
[CPException raise:CPInvalidArgumentException reason:@"aState can't be an array. Please use 'valueForThemeAttribute:inStates:' instead: " + aState];
#endif
var themeAttr = _themeAttributes && _themeAttributes[aName];
+52 -44
View File
@@ -26,6 +26,7 @@
@import "CPMenu.j"
@import "CPPanel.j"
@import "CPAnimationContext.j"
@import "CPCompatibility.j"
// Use forward declaration because this file is imported by CPPopover
@class CPPopover
@@ -415,12 +416,12 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
/*! @ignore */
- (void)setCSS3Property:(CPString)aProperty value:(CPString)value
{
if (!CPDOMAvailable) return;
var browserProperty = CPBrowserStyleProperty(aProperty);
#if PLATFORM(DOM)
if (browserProperty)
_DOMElement.style[browserProperty] = value;
#endif
}
/*! @ignore */
@@ -537,14 +538,16 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
window.setTimeout(function()
{
// We are watching opacity, so this triggers the next transition
#if PLATFORM(DOM)
// We force the style to recalculate the values, this is needed to avoid a transition issue
// More information here : https://code.google.com/p/chromium/issues/detail?id=388082
[self _currentTransformMatrix];
_DOMElement.style.opacity = 1;
_DOMElement.style.height = frame.size.height + @"px";
_DOMElement.style.width = frame.size.width + @"px";
#endif
if (CPDOMAvailable)
{
// We force the style to recalculate the values, this is needed to avoid a transition issue
// More information here : https://code.google.com/p/chromium/issues/detail?id=388082
[self _currentTransformMatrix];
_DOMElement.style.opacity = 1;
_DOMElement.style.height = frame.size.height + @"px";
_DOMElement.style.width = frame.size.width + @"px";
}
// Set up the pop-out transition
[self setCSS3Property:@"Transform" value:@"scale(1.1)"];
@@ -552,9 +555,10 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
_orderFrontTransitionFunction = function()
{
#if PLATFORM(DOM)
_DOMElement.removeEventListener(CPBrowserStyleProperty('transitionend'), _orderFrontTransitionFunction, YES);
#endif
if (CPDOMAvailable)
{
_DOMElement.removeEventListener(CPBrowserStyleProperty('transitionend'), _orderFrontTransitionFunction, YES);
}
// Now set up the pop-in to normal size transition.
// Because we are watching the -webkit-transform, it will occur now.
@@ -566,33 +570,37 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
_transitionCompleteFunction = function()
{
#if PLATFORM(DOM)
_DOMElement.removeEventListener(CPBrowserStyleProperty('transitionend'), _transitionCompleteFunction, YES);
if (CPDOMAvailable)
{
_DOMElement.removeEventListener(CPBrowserStyleProperty('transitionend'), _transitionCompleteFunction, YES);
// Make sure to clear these properties when the animation is done. Without this,
// the window becomes blurry in Chrome, presumably because the browser composits
// a layer with a transform differently even when it's an identity transform.
[self setCSS3Property:@"Transform" value:nil];
[self setCSS3Property:@"TransformOrigin" value:nil];
[self setCSS3Property:@"Transition" value:nil];
}
// Make sure to clear these properties when the animation is done. Without this,
// the window becomes blurry in Chrome, presumably because the browser composits
// a layer with a transform differently even when it's an identity transform.
[self setCSS3Property:@"Transform" value:nil];
[self setCSS3Property:@"TransformOrigin" value:nil];
[self setCSS3Property:@"Transition" value:nil];
#endif
_isOpening = NO;
[_delegate _popoverWindowDidShow];
};
#if PLATFORM(DOM)
_DOMElement.addEventListener(CPBrowserStyleProperty('transitionend'), _transitionCompleteFunction, YES);
#endif
if (CPDOMAvailable)
{
_DOMElement.addEventListener(CPBrowserStyleProperty('transitionend'), _transitionCompleteFunction, YES);
}
}, 0); // There are some weird random conditions happening in Chrome 44. If we don't put a timeout to 0 for the end of
// the transition, the popover is blinking. It happens on Opera as well...
// When the condition is met, the popover will lag a bit when opening...nothing crazy ;)
// An issue has been opened here : https://code.google.com/p/chromium/issues/detail?id=523044&thanks=523044&ts=1440095724
};
#if PLATFORM(DOM)
_DOMElement.addEventListener(CPBrowserStyleProperty('transitionend'), _orderFrontTransitionFunction, YES);
#endif
if (CPDOMAvailable)
{
_DOMElement.addEventListener(CPBrowserStyleProperty('transitionend'), _orderFrontTransitionFunction, YES);
}
}, 10); // There are some weird race conditions happening in Chrome 34. If this is set to 0
// the transitionend is randomly not called correctly. Setting the timeout to 10ms is not noticealble for the
// user, and seems to fix the issue.
@@ -601,9 +609,11 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
{
_isOpening = NO;
[self setCSS3Property:@"Transition" value:@""];
#if PLATFORM(DOM)
_DOMElement.style.opacity = 1;
#endif
if (CPDOMAvailable)
{
_DOMElement.style.opacity = 1;
}
}
}
@@ -639,9 +649,8 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
if (_animates && _browserAnimates)
{
if (_isOpening)
if (CPDOMAvailable && _isOpening)
{
#if PLATFORM(DOM)
_DOMElement.removeEventListener(CPBrowserStyleProperty('transitionend'), _orderFrontTransitionFunction, YES);
_DOMElement.removeEventListener(CPBrowserStyleProperty('transitionend'), _transitionCompleteFunction, YES);
@@ -649,15 +658,16 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
currentScale = (matrix.split('(')[1]).split(',')[0];
[self setCSS3Property:@"Transform" value:@"scale(" + currentScale + ")"];
#endif
}
// Tell the element to fade out when the opacity changes
[self setCSS3Property:@"Transition" value:@"opacity 250ms linear"];
#if PLATFORM(DOM)
_DOMElement.style.opacity = 0;
_DOMElement.addEventListener(CPBrowserStyleProperty("transitionend"), _orderOutTransitionFunction, YES);
#endif
if (CPDOMAvailable)
{
_DOMElement.style.opacity = 0;
_DOMElement.addEventListener(CPBrowserStyleProperty("transitionend"), _orderOutTransitionFunction, YES);
}
}
else
{
@@ -668,10 +678,10 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
- (void)_orderOutRecursively:(BOOL)recursive
{
// Make absolutely sure no dangling event listeners are left
#if PLATFORM(DOM)
if (_animates && _browserAnimates)
if (CPDOMAvailable && _animates && _browserAnimates)
{
_DOMElement.removeEventListener(CPBrowserStyleProperty("transitionend"), _orderOutTransitionFunction, YES);
#endif
}
[self _removeFrameObserver];
[_parentWindow removeChildWindow:self];
@@ -702,9 +712,7 @@ var _CPPopoverWindow_shouldClose_ = 1 << 4,
- (CPString)_currentTransformMatrix
{
#if PLATFORM(DOM)
return window.getComputedStyle(_DOMElement, null)[CPBrowserStyleProperty(@"transform")];
#endif
if (CPDOMAvailable) return window.getComputedStyle(_DOMElement, null)[CPBrowserStyleProperty(@"transform")];
}
- (BOOL)_hasOnlyTransientChild:(_CPPopoverWindow)aWindow
+6 -3
View File
@@ -577,7 +577,8 @@ function CPDecimalAdd(result, leftOperand, rightOperand, roundingMode, longMode)
// below is equiv. of simple compare
var comp = 0,
ll = n1._mantissa.length,
lr = n2._mantissa.length;
lr = n2._mantissa.length,
adderror;
if (ll == lr)
comp = CPOrderedSame;
@@ -607,7 +608,7 @@ function CPDecimalAdd(result, leftOperand, rightOperand, roundingMode, longMode)
// swap sign over over/underflow exception
if (CPCalculationUnderflow == adderror)
adderror = CPCalculationOverflow;
else if (CPCalculationUnderflow == adderror)
else if (CPCalculationOverflow == adderror)
adderror = CPCalculationUnderflow;
}
else
@@ -790,6 +791,7 @@ function _SimpleDivide(result, leftOperand, rightOperand, roundingMode)
k = 0,
firsttime = YES,
stopk = CPDecimalMaxDigits + 1,
error1,
used = 0; // How many digits of l have been used?
_CPDecimalSetZero(result);
@@ -957,7 +959,8 @@ function _SimpleMultiply(result, leftOperand, rightOperand, roundingMode, powerM
var error = CPCalculationNoError,
carry = 0,
exp = 0,
n = CPDecimalMakeZero();
n = CPDecimalMakeZero(),
error1;
_CPDecimalSetZero(result);
+1 -1
View File
@@ -79,7 +79,7 @@
*/
+ (id)functionOperationWithFunction:(JSObject)jsFunction
{
functionOp = [[CPFunctionOperation alloc] init];
var functionOp = [[CPFunctionOperation alloc] init];
[functionOp addExecutionFunction:jsFunction];
return functionOp;
+2 -1
View File
@@ -177,7 +177,8 @@ var _CPRunLoopPerformPool = [],
@end
var CPRunLoopLastNativeRunLoop = 0;
var CPRunLoopLastNativeRunLoop = 0,
CPMainRunLoop = nil;
/*!
@class CPRunLoop
+4
View File
@@ -347,8 +347,12 @@ if (_currentGroup == nil)
*/
- (void)undo
{
// Closing the grouping here also cancels the run loop's scheduled close of the event grouping.
if ([self groupingLevel] === 1)
{
[self endUndoGrouping];
[self _unregisterWithRunLoop];
}
[self undoNestedGroup];
}
-10
View File
@@ -179,16 +179,6 @@ WebDAVPropertiesForURLKeys[CPURLIsDirectoryKey] = @"resourcetype";
var XMLDocumentFromString = function(anXMLString)
{
if (typeof window["ActiveXObject"] !== "undefined")
{
var XMLDocument = new ActiveXObject("Microsoft.XMLDOM");
XMLDocument.async = false;
XMLDocument.loadXML(anXMLString);
return XMLDocument;
}
return new DOMParser().parseFromString(anXMLString,"text/xml");
};
-2
View File
@@ -53,8 +53,6 @@ foundationTask = framework ("Foundation", function(foundationTask)
return "--include \"" + aFilename + "\"";
}).join(" ");
INCLUDES = "--include \"../AppKit/Platform/Platform.h\" " + INCLUDES;
if ($CONFIGURATION === "Release")
foundationTask.setCompilerFlags("-O2 -Wno-unused-but-set-variable " + INCLUDES);
else
-2
View File
@@ -20,8 +20,6 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
//#define _IS_NUMERIC(n) (!isNaN(parseFloat(n)) && isFinite(n))
/*
Objective-J is a strict superset of JavaScript and compiles down to a shared global runtime scope.
This file (_CPFoundationUtilities.j) is the canonical place for otherwise "homeless" low-level
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CPBundleIdentifier</key>
<string>com.280n.Cappuccino</string>
<key>CPBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CPBundleName</key>
<string>Cappuccino</string>
<key>CPBundlePackageType</key>
<string>CPKG</string>
<key>CPBundleVersion</key>
<string>1.3.1</string>
<key>CPBuildRuntime</key>
<string>Objective-J</string>
<key>CPBuildFrameworks</key>
<array>
<string>Foundation</string>
<string>AppKit</string>
<string>AppKit/Themes/BlendKit</string>
</array>
<key>CPBuildThemes</key>
<array>
<string>AppKit/Themes/Aristo</string>
<string>AppKit/Themes/Aristo2</string>
</array>
</dict>
</plist>
-2
View File
@@ -59,9 +59,7 @@ Environment.prototype.spritesImages = function()
exports.Environment = Environment;
exports.ObjJ = new Environment("ObjJ");
var CommonJS = new Environment("CommonJS");
CommonJS.setCompilerFlags(["-DPLATFORM_COMMONJS"]);
exports.CommonJS = CommonJS;
var Browser = new Environment("Browser");
Browser.setCompilerFlags(["-DPLATFORM_BROWSER", "-DPLATFORM_DOM"]);
Browser.setSpritesImages(true);
exports.Browser = Browser;
+4
View File
@@ -70,6 +70,10 @@ if (typeof OBJJ_COMPILER_FLAGS !== 'undefined')
case "SourceMap":
flags.sourceMap = true;
break;
case "SourceMapIncludeSource":
flags.sourceMapIncludeSource = true;
break;
}
}
FileExecutable.setCurrentCompilerFlags(flags);
+19
View File
@@ -171,6 +171,25 @@ CFDictionary.prototype.toString = function()
DISPLAY_NAME(CFDictionary.prototype.toString);
CFDictionary.prototype.toJSObject = function()
{
var jsObject = new Object(),
keys = this._keys,
index = 0,
count = this._count;
for (; index < count; ++index)
{
var key = keys[index];
jsObject[key] = this.valueForKey(key);
}
return jsObject;
};
DISPLAY_NAME(CFDictionary.prototype.toJSObject);
GLOBAL(CFMutableDictionary) = function(/*CFDictionary*/ aDictionary)
{
CFDictionary.apply(this, []);
+1
View File
@@ -192,6 +192,7 @@ GLOBAL(CPLogColorize) = function(aString, aLevel)
GLOBAL(CPLogPrint) = function(aString, aLevel, aTitle, aFormatter)
{
if (CPLogDisable) return;
if (stream === undefined) {
try {
stream = require("narwhal/term").stream;
@@ -51,13 +51,10 @@ exports.ObjJ = new Environment("ObjJ");
var CommonJS = new Environment("CommonJS");
CommonJS.setCompilerFlags(["-DPLATFORM_COMMONJS"]);
exports.CommonJS = CommonJS;
var Browser = new Environment("Browser");
Browser.setCompilerFlags(["-DPLATFORM_BROWSER", "-DPLATFORM_DOM"]);
Browser.setSpritesImages(true);
exports.Browser = Browser;
+2
View File
@@ -219,6 +219,8 @@ Executable.prototype.setCode = function(code, sourceMap)
{
this._code = code;
if (code == null) return;
var parameters = this.functionParameters().join(",");
var sourceMapBase64;
-1
View File
@@ -19,7 +19,6 @@
//{
[self assert:YES equals:!CPFeatureIsCompatible(CPHTMLCanvasFeature)];
//}
[self assert:YES equals:!CPFeatureIsCompatible(CPVMLFeature)];
}
- (void)testGStateCreate
+172
View File
@@ -0,0 +1,172 @@
@import <AppKit/CPApplication.j>
@import <AppKit/CPMenu.j>
@import <AppKit/CPPanel.j>
@import <AppKit/CPWindow.j>
@implementation CPApplicationWindowsMenuTest : OJTestCase
{
CPApplication app;
CPMenu windowsMenu;
}
- (void)setUp
{
app = [[CPApplication alloc] init];
// A main menu whose Window submenu carries the name nib2cib gives the Windows menu.
var mainMenu = [[CPMenu alloc] initWithTitle:@"Main"],
windowsMenuItem = [[CPMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
windowsMenu = [[CPMenu alloc] initWithTitle:@"Window"];
[windowsMenu _setMenuName:@"_CPWindowsMenu"];
[windowsMenu addItemWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
[windowsMenuItem setSubmenu:windowsMenu];
[mainMenu addItem:windowsMenuItem];
[app setMainMenu:mainMenu];
}
- (void)tearDown
{
CPApp = nil;
}
- (CPWindow)visibleWindowWithTitle:(CPString)aTitle
{
var aWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 200.0, 100.0) styleMask:CPTitledWindowMask];
// There is no DOM in the test runner, so ordering a window in does not make it visible.
aWindow._isVisible = YES;
[aWindow setTitle:aTitle];
return aWindow;
}
- (void)testWindowsMenuIsFoundInMainMenu
{
[self assert:windowsMenu equals:[app windowsMenu]];
}
- (void)testVisibleTitledWindowIsListedBelowSeparator
{
var aWindow = [self visibleWindowWithTitle:@"Report"];
[self assert:3 equals:[windowsMenu numberOfItems]];
[self assertTrue:[[windowsMenu itemAtIndex:1] isSeparatorItem] message:@"a separator should precede the window list"];
var item = [windowsMenu itemAtIndex:2];
[self assert:@"Report" equals:[item title]];
[self assert:aWindow equals:[item target]];
[self assert:@selector(makeKeyAndOrderFront:) equals:[item action]];
}
- (void)testRetitlingWindowChangesItsItem
{
var aWindow = [self visibleWindowWithTitle:@"Report"];
[aWindow setTitle:@"Final Report"];
[self assert:3 equals:[windowsMenu numberOfItems]];
[self assert:@"Final Report" equals:[[windowsMenu itemAtIndex:2] title]];
}
- (void)testUntitledWindowIsNotListed
{
[self visibleWindowWithTitle:@""];
[self assert:1 equals:[windowsMenu numberOfItems]];
}
- (void)testExcludedWindowIsNotListed
{
var aWindow = [self visibleWindowWithTitle:@"Report"];
[aWindow setExcludedFromWindowsMenu:YES];
[self assert:1 equals:[windowsMenu numberOfItems]];
[aWindow setExcludedFromWindowsMenu:NO];
[self assert:3 equals:[windowsMenu numberOfItems]];
}
- (void)testPanelIsNotListed
{
var panel = [[CPPanel alloc] initWithContentRect:CGRectMake(0.0, 0.0, 200.0, 100.0) styleMask:CPTitledWindowMask];
panel._isVisible = YES;
[panel setTitle:@"Inspector"];
[self assert:1 equals:[windowsMenu numberOfItems]];
}
- (void)testMainWindowIsChecked
{
var firstWindow = [self visibleWindowWithTitle:@"First"],
secondWindow = [self visibleWindowWithTitle:@"Second"];
[firstWindow makeMainWindow];
[self assert:CPOnState equals:[[windowsMenu itemAtIndex:2] state]];
[self assert:CPOffState equals:[[windowsMenu itemAtIndex:3] state]];
[secondWindow makeMainWindow];
[self assert:CPOffState equals:[[windowsMenu itemAtIndex:2] state]];
[self assert:CPOnState equals:[[windowsMenu itemAtIndex:3] state]];
}
- (void)testClosingLastWindowRemovesItemAndSeparator
{
var aWindow = [self visibleWindowWithTitle:@"Report"];
[aWindow close];
[self assert:1 equals:[windowsMenu numberOfItems]];
[self assert:@"Minimize" equals:[[windowsMenu itemAtIndex:0] title]];
}
- (void)testClosingOneWindowKeepsSeparatorForOthers
{
var firstWindow = [self visibleWindowWithTitle:@"First"];
[self visibleWindowWithTitle:@"Second"];
[firstWindow close];
[self assert:3 equals:[windowsMenu numberOfItems]];
[self assert:@"Second" equals:[[windowsMenu itemAtIndex:2] title]];
}
- (void)testFilenameItemShowsNameThenDirectory
{
var aWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0.0, 0.0, 200.0, 100.0) styleMask:CPTitledWindowMask];
[app addWindowsItem:aWindow title:@"/Users/me/Report.txt" filename:YES];
[self assert:@"Report.txt — /Users/me" equals:[[windowsMenu itemAtIndex:2] title]];
}
- (void)testAddingListedWindowAgainDoesNothing
{
var aWindow = [self visibleWindowWithTitle:@"Report"];
[app addWindowsItem:aWindow title:@"Other" filename:NO];
[self assert:3 equals:[windowsMenu numberOfItems]];
[self assert:@"Report" equals:[[windowsMenu itemAtIndex:2] title]];
}
- (void)testSetWindowsMenuMovesWindowItems
{
var aWindow = [self visibleWindowWithTitle:@"Report"],
otherMenu = [[CPMenu alloc] initWithTitle:@"Windows"];
[app setWindowsMenu:otherMenu];
[self assert:1 equals:[windowsMenu numberOfItems]];
[self assert:1 equals:[otherMenu numberOfItems]];
[self assert:aWindow equals:[[otherMenu itemAtIndex:0] target]];
}
@end
+65
View File
@@ -0,0 +1,65 @@
@import <Foundation/Foundation.j>
@import <AppKit/CPCompatibility.j>
var SafariUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/27.0.1 Safari/605.1.15";
@implementation CPCompatibilityTest : OJTestCase
/*!
When CPCompatibility.j loads, it identifies the browser engine from the user
agent string. For WebKit and Blink browsers (user agent containing
"AppleWebKit/"), that detection calls +[CPPlatform isBrowser], which reads
CPDOMAvailable, so CPDOMAvailable must already be assigned at that point.
The test suite runs outside a browser, with no user agent, so this code is
never reached when the frameworks load for testing. The test reproduces a
WebKit or Blink browser load instead: it supplies an AppleWebKit user agent,
removes CPDOMAvailable, and runs CPCompatibility.j again.
*/
- (void)testLoadUnderWebKitUserAgentDoesNotReadCPDOMAvailableBeforeAssignment
{
var executable = compatibilityExecutable(),
runtimeWindow = ObjectiveJ.window,
savedNavigator = runtimeWindow.navigator,
savedDOMAvailable = CPDOMAvailable,
loadException = nil;
[self assertNotNull:executable message:@"CPCompatibility.j executable not found"];
runtimeWindow.navigator = { userAgent: SafariUserAgent };
delete global.CPDOMAvailable;
try
{
executable.execute(YES);
}
catch (anException)
{
loadException = anException;
}
// Restore the Node environment and re-run detection so later tests see it.
if (savedNavigator === undefined)
delete runtimeWindow.navigator;
else
runtimeWindow.navigator = savedNavigator;
CPDOMAvailable = savedDOMAvailable;
executable.execute(YES);
if (loadException)
[self fail:@"CPCompatibility.j threw while loading under a WebKit user agent: " + loadException];
}
@end
function compatibilityExecutable()
{
var executables = ObjectiveJ.FileExecutable.allFileExecutables();
for (var i = 0; i < executables.length; i++)
if (/\/CPCompatibility\.j$/.test(executables[i].URL().absoluteString()))
return executables[i];
return nil;
}
+1 -1
View File
@@ -1,6 +1,6 @@
@import <OJUnit/OJTestCase.j>
@import <AppKit/CPDOMDisplayServer.j>
#include "../../AppKit/Platform/DOM/CPDOMDisplayServer.h"
@implementation CPDOMDisplayServerTest : OJTestCase
{
+7 -9
View File
@@ -1,8 +1,6 @@
@import <AppKit/CPLevelIndicator.j>
// You should never do this. This is only for testing purposes.
#define GET_LEVEL_INDICATOR_SEGMENT(levelIndicator, i) [(levelIndicator) layoutEphemeralSubviewNamed:@"segment-bezel-" + (i) positioned:CPWindowAbove relativeToEphemeralSubviewNamed:"bezel"]
// You should never access a view's ephemeral layout subviews directly. This is only for testing purposes.
@implementation CPLevelIndicatorTest : OJTestCase
{
}
@@ -41,7 +39,7 @@
[levelIndicator setObjectValue:5];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
[self assert:[CPColor greenColor] equals:[GET_LEVEL_INDICATOR_SEGMENT(levelIndicator, 0) backgroundColor]];
[self assert:[CPColor greenColor] equals:[[levelIndicator layoutEphemeralSubviewNamed:@"segment-bezel-0" positioned:CPWindowAbove relativeToEphemeralSubviewNamed:"bezel"] backgroundColor]];
}
- (void)testLevelIndicatorWithLowWarningAndCriticalShouldShowWarningColor
@@ -51,7 +49,7 @@
[levelIndicator setObjectValue:4];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
[self assert:[CPColor yellowColor] equals:[GET_LEVEL_INDICATOR_SEGMENT(levelIndicator, 0) backgroundColor]];
[self assert:[CPColor yellowColor] equals:[[levelIndicator layoutEphemeralSubviewNamed:@"segment-bezel-0" positioned:CPWindowAbove relativeToEphemeralSubviewNamed:"bezel"] backgroundColor]];
}
- (void)testLevelIndicatorWithLowWarningAndCriticalShouldShowCriticalColor
@@ -61,7 +59,7 @@
[levelIndicator setObjectValue:2];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
[self assert:[CPColor redColor] equals:[GET_LEVEL_INDICATOR_SEGMENT(levelIndicator, 0) backgroundColor]];
[self assert:[CPColor redColor] equals:[[levelIndicator layoutEphemeralSubviewNamed:@"segment-bezel-0" positioned:CPWindowAbove relativeToEphemeralSubviewNamed:"bezel"] backgroundColor]];
}
- (void)testLevelIndicatorWithHighWarningAndCriticalShouldShowNormalColor
@@ -71,7 +69,7 @@
[levelIndicator setObjectValue:5];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
[self assert:[CPColor greenColor] equals:[GET_LEVEL_INDICATOR_SEGMENT(levelIndicator, 0) backgroundColor]];
[self assert:[CPColor greenColor] equals:[[levelIndicator layoutEphemeralSubviewNamed:@"segment-bezel-0" positioned:CPWindowAbove relativeToEphemeralSubviewNamed:"bezel"] backgroundColor]];
}
- (void)testLevelIndicatorWithHighWarningAndCriticalShouldShowWarningColor
@@ -81,7 +79,7 @@
[levelIndicator setObjectValue:6];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
[self assert:[CPColor yellowColor] equals:[GET_LEVEL_INDICATOR_SEGMENT(levelIndicator, 0) backgroundColor]];
[self assert:[CPColor yellowColor] equals:[[levelIndicator layoutEphemeralSubviewNamed:@"segment-bezel-0" positioned:CPWindowAbove relativeToEphemeralSubviewNamed:"bezel"] backgroundColor]];
}
- (void)testLevelIndicatorWithHighWarningAndCriticalShouldShowCriticalColor
@@ -91,6 +89,6 @@
[levelIndicator setObjectValue:8];
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
[self assert:[CPColor redColor] equals:[GET_LEVEL_INDICATOR_SEGMENT(levelIndicator, 0) backgroundColor]];
[self assert:[CPColor redColor] equals:[[levelIndicator layoutEphemeralSubviewNamed:@"segment-bezel-0" positioned:CPWindowAbove relativeToEphemeralSubviewNamed:"bezel"] backgroundColor]];
}
@end
+107
View File
@@ -0,0 +1,107 @@
@import <AppKit/CPApplication.j>
@import <AppKit/CPRulerView.j>
@import <AppKit/CPScrollView.j>
@import <AppKit/CPTextView.j>
@implementation CPRulerViewTest : OJTestCase
{
CPScrollView scrollView;
CPRulerView ruler;
}
- (void)setUp
{
[[CPApplication alloc] init];
scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 200.0)];
[scrollView setHasHorizontalRuler:YES];
ruler = [scrollView horizontalRulerView];
}
- (id)roundTrip:(id)anObject
{
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:anObject]];
}
- (void)testRequiredThicknessCombinesTheRulerAreas
{
[ruler setRuleThickness:16.0];
[ruler setReservedThicknessForMarkers:9.0];
[ruler setReservedThicknessForAccessoryView:5.0];
[self assert:30.0 equals:[ruler requiredThickness]];
[self assert:14.0 equals:[ruler baselineLocation]];
}
- (void)testMeasurementUnitsAcceptOnlyRegisteredUnits
{
[ruler setMeasurementUnits:CPRulerViewUnitPoints];
[self assert:CPRulerViewUnitPoints equals:[ruler measurementUnits]];
[ruler setMeasurementUnits:@"Furlongs"];
[self assert:CPRulerViewUnitPoints equals:[ruler measurementUnits]];
[CPRulerView registerUnitWithName:@"Furlongs" abbreviation:@"fur" unitToPointsConversionFactor:570240.0 stepUpCycle:[2.0] stepDownCycle:[0.5]];
[ruler setMeasurementUnits:@"Furlongs"];
[self assert:@"Furlongs" equals:[ruler measurementUnits]];
}
- (void)testRulerViewArchiving
{
var marker = [[CPRulerMarker alloc] initWithRulerView:ruler markerLocation:42.0 image:nil imageOrigin:CGPointMake(4.0, 8.0)];
[marker setRemovable:YES];
[marker setRepresentedObject:@"Tab"];
[ruler setRuleThickness:18.0];
[ruler setReservedThicknessForMarkers:9.0];
[ruler setOriginOffset:7.0];
[ruler setMeasurementUnits:CPRulerViewUnitPicas];
[ruler setMarkers:[marker]];
var decodedScrollView = [self roundTrip:scrollView],
decodedRuler = [decodedScrollView horizontalRulerView],
decodedMarker = [[decodedRuler markers] firstObject];
[self assert:decodedScrollView equals:[decodedRuler scrollView] message:@"the scroll view should reconnect its decoded ruler"];
[self assert:CPHorizontalRuler equals:[decodedRuler orientation]];
[self assert:18.0 equals:[decodedRuler ruleThickness]];
[self assert:9.0 equals:[decodedRuler reservedThicknessForMarkers]];
[self assert:7.0 equals:[decodedRuler originOffset]];
[self assert:CPRulerViewUnitPicas equals:[decodedRuler measurementUnits]];
[self assert:42.0 equals:[decodedMarker markerLocation]];
[self assert:4.0 equals:[decodedMarker imageOrigin].x];
[self assert:8.0 equals:[decodedMarker imageOrigin].y];
[self assertTrue:[decodedMarker isMovable]];
[self assertTrue:[decodedMarker isRemovable]];
[self assert:@"Tab" equals:[decodedMarker representedObject]];
[self assert:decodedRuler equals:[decodedMarker ruler]];
}
- (void)testTextViewUsesRulerArchiving
{
var textView = [[CPTextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 200.0)];
[self assertTrue:[textView usesRuler] message:@"a text view uses a ruler by default"];
[textView setUsesRuler:NO];
[self assertFalse:[[self roundTrip:textView] usesRuler]];
[textView setUsesRuler:YES];
[self assertTrue:[[self roundTrip:textView] usesRuler]];
}
- (void)testTextViewWithoutRulerCannotShowIt
{
var textView = [[CPTextView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 200.0)];
[scrollView setDocumentView:textView];
[textView setUsesRuler:NO];
[textView setRulerVisible:YES];
[self assertFalse:[textView isRulerVisible]];
}
@end
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff. Show more