mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-31 12:27:36 +00:00
Merge remote-tracking branch 'upstream/main' into Aristo3
This commit is contained in:
commit
3ad080b788
142 files changed
+14126
-3630
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 "";
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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];
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+2145
-401
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -81,9 +81,7 @@ var _CPCibCustomObjectClassName = @"_CPCibCustomObjectClassName";
|
||||
|
||||
if (!theClass)
|
||||
{
|
||||
#if DEBUG
|
||||
CPLog("Unknown class \"" + _className + "\" in cib file");
|
||||
#endif
|
||||
theClass = [CPObject class];
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -91,9 +91,7 @@
|
||||
|
||||
if (!windowClass)
|
||||
{
|
||||
#if DEBUG
|
||||
CPLog.warn("Unknown class \"%@\" in cib file, using CPWindow instead.", [self windowClass]);
|
||||
#endif
|
||||
windowClass = [CPWindow class];
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
@import "CGGeometry.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
#define PIXEL(pixels) pixels + "px";
|
||||
function PIXEL(pixels)
|
||||
{
|
||||
return pixels + "px";
|
||||
}
|
||||
|
||||
|
||||
function CABackingStoreGetContext(aBackingStore)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
@import <Foundation/CPRunLoop.j>
|
||||
@import "CPDOMDisplayServer.j"
|
||||
|
||||
PREPARE_DOM_OPTIMIZATION();
|
||||
|
||||
|
||||
+399
-400
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
*/
|
||||
+ (id)functionOperationWithFunction:(JSObject)jsFunction
|
||||
{
|
||||
functionOp = [[CPFunctionOperation alloc] init];
|
||||
var functionOp = [[CPFunctionOperation alloc] init];
|
||||
[functionOp addExecutionFunction:jsFunction];
|
||||
|
||||
return functionOp;
|
||||
|
||||
@@ -177,7 +177,8 @@ var _CPRunLoopPerformPool = [],
|
||||
|
||||
@end
|
||||
|
||||
var CPRunLoopLastNativeRunLoop = 0;
|
||||
var CPRunLoopLastNativeRunLoop = 0,
|
||||
CPMainRunLoop = nil;
|
||||
|
||||
/*!
|
||||
@class CPRunLoop
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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>
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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, []);
|
||||
|
||||
@@ -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;
|
||||
@@ -219,6 +219,8 @@ Executable.prototype.setCode = function(code, sourceMap)
|
||||
{
|
||||
this._code = code;
|
||||
|
||||
if (code == null) return;
|
||||
|
||||
var parameters = this.functionParameters().join(",");
|
||||
var sourceMapBase64;
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
//{
|
||||
[self assert:YES equals:!CPFeatureIsCompatible(CPHTMLCanvasFeature)];
|
||||
//}
|
||||
[self assert:YES equals:!CPFeatureIsCompatible(CPVMLFeature)];
|
||||
}
|
||||
|
||||
- (void)testGStateCreate
|
||||
|
||||
@@ -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
|
||||
@@ -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,6 +1,6 @@
|
||||
@import <OJUnit/OJTestCase.j>
|
||||
@import <AppKit/CPDOMDisplayServer.j>
|
||||
|
||||
#include "../../AppKit/Platform/DOM/CPDOMDisplayServer.h"
|
||||
|
||||
@implementation CPDOMDisplayServerTest : OJTestCase
|
||||
{
|
||||
|
||||
@@ -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
|
||||
@@ -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
Reference in new issue
Block a user