Compare commits

..
Author SHA1 Message Date
David Richardson 048c7e269c Update README to reflect status as tombstone branch ‘legacy-1.4.0’
Change README file extension from .markdown to .md for universal editor support.
2026-08-04 14:38:15 -06:00
1182 changed files with 164829 additions and 87241 deletions

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [24.x]
node-version: [20.x, 21.x, 22.x, 23.x, 24.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
@@ -26,8 +26,6 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
- run: echo "${PWD}/dist/cappuccino/bin" >> $GITHUB_PATH
- run: echo "${PWD}/dist/objective-j/bin" >> $GITHUB_PATH
-2
View File
@@ -26,5 +26,3 @@ node_modules
/dist/cappuccino/package.json
/dist/cappuccino/lib
/dist/cappuccino/bin
Tests/Manual/.Frameworks
/Tests/Manual/index.html
-5
View File
@@ -84,8 +84,6 @@
@import "CPRadio.j"
@import "CPResponder.j"
@import "CPRuleEditor.j"
@import "CPRulerMarker.j"
@import "CPRulerView.j"
@import "CPScroller.j"
@import "CPScrollView.j"
@import "CPSearchField.j"
@@ -95,15 +93,12 @@
@import "CPSlider.j"
@import "CPSound.j"
@import "CPSplitView.j"
@import "CPStackView.j"
@import "CPStepper.j"
@import "CPTableColumn.j"
@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"
+33 -84
View File
@@ -137,8 +137,7 @@ 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 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,
@param returnCode the index of the button that the user clicked (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)
*/
@@ -419,25 +418,20 @@ var bottomHeight = 71;
*/
- (void)addButtonWithTitle:(CPString)aTitle
{
var count = [_buttons count],
var bounds = [[_window contentView] bounds],
count = [_buttons count],
button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
[button setTitle:aTitle];
[button setTag:count];
[button setTarget:self];
[button setAction:@selector(_takeReturnCodeFrom:)];
[button setBezelStyle:CPSmallSquareBezelStyle];
// Only add subview if the window has been created.
// Otherwise, _createWindowWithStyle will handle adding the buttons from the _buttons array later.
if (_window)
[[_window contentView] addSubview:button];
[[_window contentView] addSubview:button];
if (count == 0)
{
[button setKeyEquivalent:CPCarriageReturnCharacter];
[button setBezelStyle:CPRoundedBezelStyle];
}
else if ([aTitle lowercaseString] === @"cancel")
[button setKeyEquivalent:CPEscapeFunctionKey];
@@ -536,9 +530,6 @@ var bottomHeight = 71;
[[_window contentView] addSubview:_suppressionButton];
}
/*!
@ignore
*/
/*!
@ignore
*/
@@ -548,17 +539,25 @@ var bottomHeight = 71;
minimumSize = [_themeView currentValueForThemeAttribute:@"size"],
buttonOffset = [_themeView currentValueForThemeAttribute:@"button-offset"],
helpLeftOffset = [_themeView currentValueForThemeAttribute:@"help-image-left-offset"],
aRepresentativeButton = [_buttons objectAtIndex:0],
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
panelSize = [[_window contentView] frame].size,
buttonsOriginY,
buttonMarginY,
buttonMarginX,
theme = [self theme],
offsetX;
var isHUD = (_defaultWindowStyle & CPHUDBackgroundWindowMask) || (theme === [CPTheme defaultHudTheme]);
[aRepresentativeButton setTheme:[self theme]];
[aRepresentativeButton sizeToFit];
panelSize.height = CGRectGetMaxY([lastView frame]) + defaultElementsMargin + [aRepresentativeButton frameSize].height;
if (panelSize.height < minimumSize.height)
panelSize.height = minimumSize.height;
buttonsOriginY = panelSize.height - [aRepresentativeButton frameSize].height + buttonOffset;
offsetX = panelSize.width - inset.right;
// 1. Determine Margins (Moved up so we can use them in height calculation)
switch ([_window styleMask])
{
case _CPModalWindowMask:
@@ -572,61 +571,27 @@ var bottomHeight = 71;
break;
}
// 2. Prepare buttons and get row height
var maxButtonHeight = 0.0;
for (var i = 0; i < [_buttons count]; i++)
{
var btn = _buttons[i];
[btn sizeToFit];
if (isHUD)
[btn setThemeState:CPThemeStateHUD];
else
[btn unsetThemeState:CPThemeStateHUD];
maxButtonHeight = MAX(maxButtonHeight, CGRectGetHeight([btn frame]));
}
// 3. Calculate Content Height
var lastViewMaxY = CGRectGetMaxY([lastView frame]);
// Use bottomHeight (71) to reserve space for the footer
var requiredContentHeight = lastViewMaxY + bottomHeight;
var finalContentSize = CGSizeMake(
[[_window contentView] frame].size.width,
MAX(requiredContentHeight, minimumSize.height)
);
// 4. Position Buttons
// Calculate the top Y coordinate to vertically center the button row within the bottomHeight area
// Center Y of footer = Height - (bottomHeight / 2.0)
// Top Y of button = Center Y - (maxButtonHeight / 2.0)
buttonsOriginY = finalContentSize.height - ((bottomHeight + maxButtonHeight) / 2.0) - buttonMarginY;
offsetX = finalContentSize.width - inset.right;
// Loop and set frames
for (var i = [_buttons count] - 1; i >= 0 ; i--)
{
var button = _buttons[i],
buttonFrame = [button frame],
var button = _buttons[i];
[button setTheme:[self theme]];
[button sizeToFit];
var buttonFrame = [button frame],
width = MAX(80.0, CGRectGetWidth(buttonFrame)),
height = CGRectGetHeight(buttonFrame),
yOffset = FLOOR((maxButtonHeight - height) / 2.0);
height = CGRectGetHeight(buttonFrame);
offsetX -= width;
[button setFrame:CGRectMake(offsetX + buttonMarginX, buttonsOriginY + buttonMarginY + yOffset, width, height)];
[button setFrame:CGRectMake(offsetX + buttonMarginX, buttonsOriginY + buttonMarginY, width, height)];
offsetX -= 10;
}
// Position Help Button if needed
if (_showHelp)
{
var helpImage = [_themeView currentValueForThemeAttribute:@"help-image"],
helpImagePressed = [_themeView currentValueForThemeAttribute:@"help-image-pressed"],
helpImageSize = helpImage ? [helpImage size] : CGSizeMakeZero(),
helpYOffset = FLOOR((maxButtonHeight - helpImageSize.height) / 2.0),
helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY + buttonMarginY + helpYOffset, helpImageSize.width, helpImageSize.height);
helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY, helpImageSize.width, helpImageSize.height);
[_alertHelpButton setImage:helpImage];
[_alertHelpButton setAlternateImage:helpImagePressed];
@@ -634,7 +599,8 @@ var bottomHeight = 71;
[_alertHelpButton setFrame:helpFrame];
}
return finalContentSize;
panelSize.height += [aRepresentativeButton frameSize].height + inset.bottom + buttonOffset;
return panelSize;
}
/*!
@@ -648,14 +614,9 @@ var bottomHeight = 71;
if (!_window)
[self _createWindowWithStyle:nil];
// Ensure the theme view knows if we are in HUD mode so it picks up the right specificities
if ((_defaultWindowStyle & CPHUDBackgroundWindowMask) || ([self theme] === [CPTheme defaultHudTheme]))
[_themeView setThemeState:CPThemeStateHUD];
else
[_themeView unsetThemeState:CPThemeStateHUD];
var iconOffset = [_themeView currentValueForThemeAttribute:@"image-offset"],
theImage = _icon;
theImage = _icon,
finalSize;
if (!theImage)
switch (_alertStyle)
@@ -687,17 +648,11 @@ var bottomHeight = 71;
else if (_accessoryView)
lastView = _accessoryView;
// 1. Get the size needed for the *content* (text, buttons, padding)
var finalContentSize = [self _layoutButtonsFromView:lastView];
// 2. Convert Content Size -> Frame Size
// This accounts for the Title Bar and borders automatically.
var contentRect = CGRectMake(0.0, 0.0, finalContentSize.width, finalContentSize.height);
var frameRect = [[_window class] frameRectForContentRect:contentRect styleMask:[_window styleMask]];
// 3. Apply the calculated Frame Size
[_window setFrameSize:frameRect.size];
finalSize = [self _layoutButtonsFromView:lastView];
if ([_window styleMask] & CPDocModalWindowMask)
finalSize.height -= 26; // adjust the absence of title bar
[_window setFrameSize:finalSize];
[_window center];
if ([_window styleMask] & _CPModalWindowMask || [_window styleMask] & CPHUDBackgroundWindowMask)
@@ -797,12 +752,6 @@ var bottomHeight = 71;
var frame = CGRectMakeZero();
frame.size = [_themeView currentValueForThemeAttribute:@"size"];
// Propagate CPHUDBackgroundWindowMask from _defaultWindowStyle to forceStyle.
// This ensures that even if we force CPDocModalWindowMask (for sheets),
// the window still knows it should be a HUD.
if (_defaultWindowStyle & CPHUDBackgroundWindowMask)
forceStyle |= CPHUDBackgroundWindowMask;
_window = [[CPPanel alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle];
[_window setLevel:CPStatusWindowLevel];
[_window setPlatformWindow:[[CPApp keyWindow] platformWindow]];
+26 -332
View File
@@ -37,8 +37,6 @@
@import "CPWindowController.j"
@import "_CPPopoverWindow.j"
@global CPWindowObjectList
@typedef CPModalSession
var CPMainCibFile = @"CPMainCibFile",
@@ -123,14 +121,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
CPPanel _aboutPanel;
CPMenu _windowsMenu;
CPMenuItem _windowsMenuSeparator;
CPThemeBlend _themeBlend @accessors(property=themeBlend);
// OS behavior
CPApplicationOSBehavior _OSBehavior;
BOOL _simulatesWindows;
}
/*!
@@ -163,9 +154,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
_eventListenerInsertionIndex = 0;
_windows = [[CPNull null]];
_OSBehavior = CPApplicationLegacyOSBehavior;
_simulatesWindows = NO;
}
return self;
@@ -253,10 +241,9 @@ 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 (CPDOMAvailable)
{
window.status = " ";
}
#if PLATFORM(DOM)
window.status = " ";
#endif
// We also want to set the default cursor on the body, so that buttons and things don't have an iBeam
[[CPCursor arrowCursor] set];
@@ -286,10 +273,9 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
var needsUntitled = !!_documentController,
URLStrings = nil;
if (CPDOMAvailable)
{
URLStrings = window.cpOpeningURLStrings && window.cpOpeningURLStrings();
}
#if PLATFORM(DOM)
URLStrings = window.cpOpeningURLStrings && window.cpOpeningURLStrings();
#endif
var index = 0,
count = [URLStrings count];
@@ -382,7 +368,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
to the following respective keys in your info.plist file.
<pre>
CPBundleDisplayName, or CPBundleName when there is no display name
CPBundleName
CPApplicationIcon (through a call to -applicationIconImage, see documentation for that method for more details)
CPBundleVersion
CPBundleShortVersionString
@@ -396,7 +382,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
if (!_aboutPanel)
{
var mainInfo = [[CPBundle mainBundle] infoDictionary],
applicationTitle = [options objectForKey:"ApplicationName"] || [self _applicationName],
applicationTitle = [options objectForKey:"ApplicationName"] || [mainInfo objectForKey:@"CPBundleName"],
applicationIcon = [options objectForKey:@"ApplicationIcon"] || [self applicationIconImage],
version = [options objectForKey:@"Version"] || [mainInfo objectForKey:@"CPBundleVersion"],
applicationVersion = [options objectForKey:@"ApplicationVersion"] || [mainInfo objectForKey:@"CPBundleShortVersionString"],
@@ -741,10 +727,11 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
*/
- (CPArray)orderedWindows
{
if (CPDOMAvailable)
return CPWindowObjectList();
#if PLATFORM(DOM)
return CPWindowObjectList();
#else
return [];
#endif
}
- (void)hide:(id)aSender
@@ -781,238 +768,11 @@ 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
@@ -1500,45 +1260,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
+ (CPString)defaultThemeName
{
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo3");
}
// See CPApplication_Constants.j for comments
- (void)setOSBehavior:(CPApplicationOSBehavior)anOSBehavior
{
// Verify if provided OS behavior is valid
if ([[CPApplicationOSBehaviors allKeysForObject:anOSBehavior] count] == 0)
{
CPLog.warn("CPApplication setOSBehavior: invalid CPApplicationOSBehavior (received "+anOSBehavior+"). Ignored.");
return;
}
_OSBehavior = anOSBehavior;
[[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationOSBehaviorDidChangeNotification object:CPApp userInfo:nil];
}
- (CPApplicationOSBehavior)OSBehavior
{
return _OSBehavior;
}
- (BOOL)shouldMimicWindows
{
return (_OSBehavior == CPApplicationFollowOSBehavior) && (CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) || _simulatesWindows);
}
- (void)setSimulatesWindows:(BOOL)shouldSimulateWindows
{
if (_simulatesWindows === shouldSimulateWindows)
return;
_simulatesWindows = shouldSimulateWindows;
}
- (BOOL)simulatesWindows
{
return _simulatesWindows;
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo2");
}
@end
@@ -1582,22 +1304,22 @@ _CPRunModalLoop = function(anEvent)
function CPApplicationMain(args, namedArgs)
{
if (CPDOMAvailable)
#if PLATFORM(DOM)
// hook to allow recorder, etc to manipulate things before starting AppKit
if (window.parent !== window && typeof window.parent._childAppIsStarting === "function")
{
// hook to allow recorder, etc to manipulate things before starting AppKit
if (window.parent !== window && typeof window.parent._childAppIsStarting === "function")
try
{
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().");
}
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];
@@ -1653,7 +1375,7 @@ var _CPAppBootstrapperActions = nil;
var defaultThemeName = [CPApplication defaultThemeName],
themeURL = nil;
if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2" || defaultThemeName === @"Aristo3")
if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2")
themeURL = [[CPBundle bundleForClass:[CPApplication class]] pathForResource:defaultThemeName + @".blend"];
else
themeURL = [[CPBundle mainBundle] pathForResource:defaultThemeName + @".blend"];
@@ -1669,34 +1391,6 @@ var _CPAppBootstrapperActions = nil;
[[CPApplication sharedApplication] setThemeBlend:aThemeBlend];
[CPTheme setDefaultTheme:[CPTheme themeNamed:[CPApplication defaultThemeName]]];
// Search in the Info.plist if the special CPApplicationSimulateWindowsOS flag is set (for testing)
[CPApp setSimulatesWindows:!![[CPBundle mainBundle] objectForInfoDictionaryKey:"CPApplicationSimulateWindowsOS"]];
// Before loading the main CIB, try to find if a CPApplicationOSBehavior is specified in the Info.plist or in the user defaults
// (with user defaults precedence). Value stored must be a string representing the name of the OS behavior.
var plistOSBehavior = [[CPBundle mainBundle] objectForInfoDictionaryKey:"CPApplicationOSBehavior"],
userOSBehavior = [[CPUserDefaults standardUserDefaults] objectForKey:@"CPApplicationOSBehavior"];
if (userOSBehavior)
{
var osBehavior = [CPApplicationOSBehaviors objectForKey:userOSBehavior];
if (osBehavior)
[CPApp setOSBehavior:osBehavior];
else
CPLog.warn("Invalid CPApplicationOSBehavior specified in user defaults (found:"+userOSBehavior+"). Ignored.");
}
else if (plistOSBehavior)
{
var osBehavior = [CPApplicationOSBehaviors objectForKey:plistOSBehavior];
if (osBehavior)
[CPApp setOSBehavior:osBehavior];
else
CPLog.warn("Invalid CPApplicationOSBehavior specified in Info.plist (found:"+plistOSBehavior+"). Ignored.");
}
[self performActions];
}
-12
View File
@@ -39,15 +39,3 @@ CPTerminateLater = -1; // not currently supported
CPRunStoppedResponse = -1000;
CPRunAbortedResponse = -1001;
CPRunContinuesResponse = -1002;
// Should the application follow Cappuccino UX-UI (which is OSX like) or OS UX-UI (mainly Windows) ?
// See explanation on https://github.com/cappuccino/cappuccino/wiki/CPApplicationSelectedOSBehavior
@typedef CPApplicationOSBehavior
CPApplicationLegacyOSBehavior = 1;
CPApplicationFollowOSBehavior = 2;
CPApplicationOSBehaviorDidChangeNotification = @"CPApplicationOSBehaviorDidChangeNotification";
CPApplicationOSBehaviors = @{
@"CPApplicationLegacyOSBehavior": CPApplicationLegacyOSBehavior,
@"CPApplicationFollowOSBehavior": CPApplicationFollowOSBehavior
};
+2 -2
View File
@@ -285,7 +285,7 @@ CPBelowBottom = 6;
// MARK: borderColor
- (CPColor)borderColor
{
return [self currentValueForThemeAttribute:@"border-color"];
return [self valueForThemeAttribute:@"border-color"];
}
- (void)setBorderColor:(CPColor)color
@@ -785,7 +785,7 @@ CPBelowBottom = 6;
[_boxView setFrame:CGRectMake(0,2,bounds.size.width,1)];
}
[_boxView setBackgroundColor:[self currentValueForThemeAttribute:@"border-color"]];
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"border-color"]];
return;
}
+1 -1
View File
@@ -153,7 +153,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
_prototypeView = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_prototypeView setVerticalAlignment:CPCenterVerticalTextAlignment];
[_prototypeView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateSelectedDataView.and(CPThemeStateTableDataView)];
[_prototypeView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateSelectedDataView];
[_prototypeView setLineBreakMode:CPLineBreakByTruncatingTail];
_horizontalScrollView = [[CPScrollView alloc] initWithFrame:[self bounds]];
+4 -10
View File
@@ -98,8 +98,7 @@ var CPButtonBezelStyleStateMap = @{
CPRegularSquareBezelStyle: CPButtonStateBezelStyleRegularSquare,
CPTexturedSquareBezelStyle: CPButtonStateBezelStyleTextured,
CPDisclosureBezelStyle: CPButtonStateBezelStyleDisclosure,
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure,
CPHUDBezelStyle: CPThemeStateHUD
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure
};
/// @cond IGNORE
@@ -708,17 +707,12 @@ CPButtonImageOffset = 3.0;
// Note : We have to split content and image visual states as, for example, radio buttons don't follow push buttons behavior
- (CPThemeState)_contentVisualState
{
var visualState = [self themeState] || CPThemeStateNormal,
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state],
buttonIsOn = (currentState !== CPOffState);
// Define masks that imply the background changes color (Blue/Gray)
// If the background changes, we usually want the text to turn White (Highlighted state).
var highlightMask = CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask;
// Only add CPThemeStateHighlighted if the button is configured to highlight visually (Background change)
if ((_isHighlighted && (_highlightsBy & highlightMask)) ||
(((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
// If the button is pushed (_isHighlighted), always add the highlighted state
if (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
visualState = visualState.and(CPThemeStateHighlighted);
else
visualState = visualState.without(CPThemeStateHighlighted);
+168 -1683
View File
File diff suppressed because it is too large. Load diff
+8 -5
View File
@@ -563,10 +563,9 @@ 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 (CPDOMAvailable)
{
hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
}
#if PLATFORM(DOM)
hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
#endif
_cssString = (hasAlpha ? "rgba(" : "rgb(") +
parseInt(_components[0] * 255.0) + ", " +
@@ -1002,16 +1001,19 @@ 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)
@@ -1084,6 +1086,7 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
@deref(aPreviousStateRef) = aPreviousState;
return aStyleNode;
#endif
}
@end
@@ -1491,7 +1494,7 @@ var patternColorsFromPattern = function(pattern, attributes, imageFactory)
else
{
numParts = 3;
isVertical = orientation === CPColorPatternIsVertical;
isVertical = orientation === PatternIsVertical;
if (isVertical)
{
+30 -33
View File
@@ -23,7 +23,6 @@
@import <Foundation/CPObject.j>
@import "CPView.j"
@import "CPCompatibility.j"
@class CPSlider
@class CPColorPanel
@@ -236,25 +235,24 @@
{
if (self = [super initWithFrame:aFrame])
{
if (CPDOMAvailable)
{
var path = [[CPBundle bundleForClass:CPColorPicker] pathForResource:@"wheel.png"];
#if PLATFORM(DOM)
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);
}
_DOMElement.appendChild(_wheelImage);
_DOMElement.appendChild(_blackWheelImage);
#endif
[self setWheelSize:aFrame.size];
@@ -274,10 +272,10 @@
- (void)setWheelBrightness:(float)brightness
{
if (!CPDOMAvailable) return;
#if PLATFORM(DOM)
_blackWheelImage.style.opacity = 1.0 - brightness;
_blackWheelImage.style.filter = "alpha(opacity=" + (1.0 - brightness) * 100 + ")"
#endif
}
- (void)setFrameSize:(CGSize)aSize
@@ -290,22 +288,21 @@
{
var min = MIN(aSize.width, aSize.height);
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";
#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";
_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";
}
_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
_radius = min / 2.0;
+21 -10
View File
@@ -91,7 +91,7 @@ var CPComboBoxTextSubview = @"text",
{
return @{
@"popup-button-size": CGSizeMake(21.0, 29.0),
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0)
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0),
};
}
@@ -533,7 +533,7 @@ var CPComboBoxTextSubview = @"text",
CPComboBoxFocusRingWidth = inset.bottom;
}
[_listDelegate popUpRelativeToRect:[self bounds] view:self offset:CPComboBoxFocusRingWidth - 1];
[_listDelegate popUpRelativeToRect:[self _borderFrame] view:self offset:CPComboBoxFocusRingWidth - 1];
[self _selectMatchingItem];
}
@@ -570,14 +570,6 @@ var CPComboBoxTextSubview = @"text",
_selectedStringValue = selectedStringValue;
[self setStringValue:_selectedStringValue];
[self _updatePlaceholderState];
if (CPDOMAvailable)
{
[self _setCSSStyleForInputElement];
}
[self _reverseSetBinding];
return YES;
@@ -847,6 +839,7 @@ 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,6 +854,7 @@ var CPComboBoxTextSubview = @"text",
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
#endif
return NO;
}
@@ -981,6 +975,23 @@ var CPComboBoxTextSubview = @"text",
}
}
/*!
Calculate the frame in base coordinates that will nestle just below the visible border of the text field.
@ignore
*/
- (CGRect)_borderFrame
{
var inset = [self currentValueForThemeAttribute:@"border-inset"],
frame = [self bounds];
frame.origin.x += inset.left;
frame.origin.y += inset.top;
frame.size.width -= inset.left + inset.right;
frame.size.height -= inset.top + inset.bottom;
return frame;
}
/* @ignore */
- (void)_popUpButtonWasClicked
{
+5 -23
View File
@@ -24,29 +24,6 @@
@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;
@@ -78,6 +55,8 @@ CPJavaScriptClipboardAccessFeature = 12;
CPJavaScriptCanvasDrawFeature = 13;
CPJavaScriptCanvasTransformFeature = 14;
CPVMLFeature = 15;
CPJavaScriptRemedialKeySupport = 16;
CPJavaScriptShadowFeature = 20;
@@ -154,6 +133,7 @@ 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;
@@ -408,6 +388,7 @@ function CPBrowserStyleProperty(aProperty)
{
var r = nil;
#if PLATFORM(DOM)
var testElement = document.createElement('div');
switch (lowerProperty)
@@ -474,6 +455,7 @@ function CPBrowserStyleProperty(aProperty)
break;
}
#endif
PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] = r;
}
+3 -8
View File
@@ -48,10 +48,9 @@
CPRegularControlSize = 0;
CPSmallControlSize = 1;
CPMiniControlSize = 2;
CPLargeControlSize = 3; // Since MacOS 11, there's a new control size "Large"
// To get the theme state corresponding to a control size, use CPControlSizeThemeStates[controlSize]
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini, CPThemeStateControlSizeLarge];
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini];
@typedef CPLineBreakMode
CPLineBreakByWordWrapping = 0;
@@ -249,9 +248,6 @@ var CPControlBlackColor = [CPColor blackColor];
case CPMiniControlSize:
return CPThemeStateControlSizeMini;
case CPLargeControlSize:
return CPThemeStateControlSizeLarge;
case CPRegularControlSize:
default:
@@ -1043,8 +1039,7 @@ var CPControlBlackColor = [CPColor blackColor];
_baseWritingDirection = writingDirection;
[self didChangeValueForKey:@"baseWritingDirection"];
if (CPDOMAvailable)
{
#if PLATFORM(DOM)
var style;
@@ -1067,7 +1062,7 @@ var CPControlBlackColor = [CPColor blackColor];
}
_DOMElement.style.direction = style;
}
#endif
}
@end
+17 -23
View File
@@ -22,7 +22,6 @@
@import <Foundation/CPObject.j>
@import <Foundation/CPString.j>
@import "CPCompatibility.j"
/*!
@@ -96,37 +95,32 @@
else
domain = "";
if (CPDOMAvailable)
{
document.cookie = _cookieName + "=" + value + expires + "; path=/" + domain;
}
else
{
_cookieValue = value;
_expires = expires;
}
#if PLATFORM(DOM)
document.cookie = _cookieName + "=" + value + expires + "; path=/" + domain;
#else
_cookieValue = value;
_expires = expires;
#endif
}
/* @ignore */
- (CPString)_readCookieValue
{
if (CPDOMAvailable)
#if PLATFORM(DOM)
var nameEQ = _cookieName + "=",
ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++)
{
var nameEQ = _cookieName + "=",
ca = document.cookie.split(';');
var c = ca[i];
for (var i = 0; i < ca.length; i++)
{
var c = ca[i];
while (c.charAt(0) == ' ')
c = c.substring(1, c.length);
while (c.charAt(0) == ' ')
c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0)
return c.substring(nameEQ.length, c.length);
}
if (c.indexOf(nameEQ) == 0)
return c.substring(nameEQ.length, c.length);
}
#endif
return "";
}
+6
View File
@@ -135,7 +135,9 @@ CPCursorPlatformBoth = 3;
currentCursor = self;
#if PLATFORM(DOM)
[[self class] _setCursorCSS:_cssString];
#endif
}
- (void)mouseEntered:(CPEvent)event
@@ -153,10 +155,12 @@ 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.
@@ -213,6 +217,7 @@ 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
@@ -235,6 +240,7 @@ CPCursorPlatformBoth = 3;
return doesExist;
}
#endif
return NO;
}
+4 -6
View File
@@ -30,7 +30,6 @@
@import <Foundation/CPDate.j>
@import <Foundation/CPLocale.j>
@import <Foundation/CPTimeZone.j>
@import "CPCompatibility.j"
@class CPStepper
@class CPApp
@@ -259,11 +258,10 @@ CPEraDatePickerElementFlag = 0x0100;
[_datePickerComponent setDatePickerElements:_datePickerElements];
// FIXME: Don't know why but next line will cause theme compilation to fail...
// Workaround: guarded with CPDOMAvailable (was: "if PLATFORM(DOM)")
if (CPDOMAvailable)
{
[_datePickerComponent setEnabled:[self isEnabled]];
}
// Workaround: added "if PLATFORM(DOM)"
#if PLATFORM(DOM)
[_datePickerComponent setEnabled:[self isEnabled]];
#endif
if (_isTextual)
// We need to transmit text color to the text field version (Cocoa doesn't permit adapting the calendar view text color)
+9 -12
View File
@@ -26,7 +26,6 @@
@import "CPTextField.j"
@import "CPImage.j"
@import "CALayer.j"
@import "CPCompatibility.j"
@class _CPCibCustomResource
@class CPDatePicker
@@ -195,10 +194,10 @@ _CPDatePickerClockSeconds = 3;
// 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;
// Workaround: added "if PLATFORM(DOM)"
#if PLATFORM(DOM)
[_secondHandLayer setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
#endif
}
// MARK: Layout methods
@@ -382,15 +381,13 @@ _CPDatePickerClockSeconds = 3;
break;
}
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
// We have to adapt represented values
_representedHours = dateValue.getHours();
@@ -20,7 +20,6 @@
*/
@import "CPTextField.j"
@import "CPCompatibility.j"
@class CPDatePicker
@class _CPDatePickerElementView
@@ -472,15 +471,13 @@ CPAMPMDateType = 6;
newDateValue.setSeconds(newDateValue.getSeconds() + secondsFromGMT - secondsFromGMTTimeZone);
}
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:newDateValue timeInterval:[_datePicker timeInterval]];
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
@@ -21,7 +21,6 @@
@import "CPControl.j"
@import "_CPDatePickerElementTextField.j"
@import "CPCompatibility.j"
@class CPDatePicker
@@ -404,15 +403,13 @@
dateValue.setHours(dateValue.getHours() - 12);
}
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
+12 -17
View File
@@ -23,7 +23,6 @@
@import "CPControl.j"
@import "_CPDatePickerDayView.j"
@import "CPCompatibility.j"
@class CPDatePicker
@@ -458,10 +457,9 @@
_indexDayTile = -1;
_eventDragged = nil;
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
// Check if we have to change or not the month of the component
if ([dayTile date].getMonth() == _date.getMonth())
@@ -516,10 +514,9 @@
[_delegate _displayNextMonth];
}
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
/*! Mouse dragged event
@@ -537,10 +534,9 @@
_indexDayTile = [self indexOfTileForEvent:anEvent];
_eventDragged = anEvent;
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = YES;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
if ([_datePicker datePickerMode] == CPSingleDateMode)
{
@@ -584,10 +580,9 @@
}
}
if (CPDOMAvailable)
{
_datePicker._invokedByUserEvent = NO;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
- (void)mouseUp:(CPEvent)anEvent
+5 -7
View File
@@ -34,7 +34,6 @@
@class _CPDatePickerElementView
@import "_CPDatePickerElementView.j"
@import "CPCompatibility.j"
@global CPSingleDateMode
@global CPRangeDateMode
@@ -90,18 +89,17 @@
[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.
if (CPDOMAvailable)
{
_datePicker._DOMElement.style.overflow = "visible";
_DOMElement.style.overflow = "visible";
_datePickerElementView._DOMElement.style.overflow = "visible";
}
_datePicker._DOMElement.style.overflow = "visible";
_DOMElement.style.overflow = "visible";
_datePickerElementView._DOMElement.style.overflow = "visible";
}
#endif
}
+1 -4
View File
@@ -33,10 +33,7 @@
@global CPApp
function DRAGGING_WINDOW(anObject)
{
return ([anObject isKindOfClass:[CPWindow class]] ? anObject : [anObject window]);
}
#define DRAGGING_WINDOW(anObject) ([anObject isKindOfClass:[CPWindow class]] ? anObject : [anObject window])
var CPDragServerPreviousEvent = nil,
CPDragServerPeriodicUpdateInterval = 0.05;
+6
View File
@@ -82,8 +82,10 @@ var _CPEventPeriodicEventPeriod = 0,
float _scrollingDeltaY;
BOOL _hasPreciseScrollingDeltas;
#if PLATFORM(DOM)
BOOL _suppressCappuccinoCut;
BOOL _suppressCappuccinoPaste;
#endif
CPTrackingArea _trackingArea;
}
@@ -607,7 +609,11 @@ var _CPEventPeriodicEventPeriod = 0,
*/
- (BOOL)_platformIsEffectingCutOrPaste
{
#if PLATFORM(DOM)
return _suppressCappuccinoCut || _suppressCappuccinoPaste;
#else
return NO;
#endif
}
+8 -34
View File
@@ -51,40 +51,14 @@ var _CPFontCache = {},
_CPFontSystemFontFaceSpecified = NO;
function _CPRealFontSize(aSize)
{
return (aSize <= 0 ? _CPFontSystemFontSize : aSize);
}
#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 _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]);
}
#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])
/*!
@ingroup appkit
@@ -246,7 +220,7 @@ following:
if (systemFontStyle)
{
// Yes, so install it in the DOM Style element
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPath:systemFontStyle];
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPathInString:systemFontStyle];
}
}
+18 -22
View File
@@ -25,7 +25,6 @@
@import "CPControl.j"
@import "CPFont.j"
@import "CPFontDescriptor.j"
@import "CPCompatibility.j"
@global CPApp
@class CPFontPanel
@@ -133,31 +132,28 @@ CPRemoveTraitFontAction = 7;
{
_availableFonts = [];
if (CPDOMAvailable)
{
_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);
#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"]);
_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
for (var i = 0; i < _CPFontDetectAllFonts.length; i++)
{
// If there's no font detection, just assume all fonts are available.
_availableFonts = _CPFontDetectAllFonts;
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;
#endif
}
return _availableFonts;
}
+6 -6
View File
@@ -239,8 +239,7 @@ function CPAppKitImage(aFilename, aSize)
*/
- (CPData)data
{
if (!CPDOMAvailable) return;
#if PLATFORM(DOM)
var dataURL;
if ([_filename hasPrefix:@"data:image"])
@@ -262,6 +261,7 @@ function CPAppKitImage(aFilename, aSize)
var base64 = dataURL.replace(/^data:image\/png;base64,/, "");
return [CPData dataWithBase64:base64];
#endif
}
/*!
@@ -384,8 +384,7 @@ function CPAppKitImage(aFilename, aSize)
_loadStatus = CPImageLoadStatusLoading;
if (!CPDOMAvailable) return;
#if PLATFORM(DOM)
_image = new Image();
var isSynchronous = YES;
@@ -432,6 +431,7 @@ 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,8 +641,7 @@ function CPAppKitImage(aFilename, aSize)
- (DOMElement)applyCSSImageForView:(CPView)aView onDOMElement:(DOMElement)aDOMElement styleNode:(DOMElement)aStyleNode previousState:(CPArrayRef)aPreviousStateRef
{
if (!CPDOMAvailable) return;
#if PLATFORM(DOM)
// First, restore previous CSS styling before applying the new one
var aPreviousState = @deref(aPreviousStateRef);
@@ -725,6 +724,7 @@ function CPAppKitImage(aFilename, aSize)
@deref(aPreviousStateRef) = aPreviousState;
return aStyleNode;
#endif
}
- (BOOL)_shouldBeResized
+41 -56
View File
@@ -25,7 +25,6 @@
@import "CPControl.j"
@import "CPImage.j"
@import "CPShadowView.j"
@import "CPDOMDisplayServer.j"
@global CPImagesPboardType
@global appkit_tag_dom_elements
@@ -90,10 +89,9 @@ var CPImageViewEmptyPlaceholderImage = nil;
if (self)
{
if (CPDOMAvailable)
{
[self _createDOMImageElement];
}
#if PLATFORM(DOM)
[self _createDOMImageElement];
#endif
}
return self;
@@ -101,9 +99,7 @@ var CPImageViewEmptyPlaceholderImage = nil;
- (void)_createDOMImageElement
{
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
var image = [self objectValue],
isCSSBasedImage = [image isCSSBased],
isIMGImageElement = _DOMImageElement && (_DOMImageElement.nodeName == "IMG");
@@ -144,6 +140,7 @@ var CPImageViewEmptyPlaceholderImage = nil;
AppKitTagDOMElement(self, _DOMImageElement);
CPDOMDisplayServerAppendChild(_DOMElement, _DOMImageElement);
#endif
}
/*!
@@ -176,18 +173,17 @@ var CPImageViewEmptyPlaceholderImage = nil;
var newImage = [self objectValue];
if (CPDOMAvailable)
{
[self _createDOMImageElement];
#if PLATFORM(DOM)
[self _createDOMImageElement];
if ([newImage isCSSBased])
_cssStyleNode = [newImage applyCSSImageForView:self
onDOMElement:_DOMImageElement
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
else
_DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
}
if ([newImage isCSSBased])
_cssStyleNode = [newImage applyCSSImageForView:self
onDOMElement:_DOMImageElement
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
else
_DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
#endif
var size = [newImage size];
@@ -195,11 +191,10 @@ var CPImageViewEmptyPlaceholderImage = nil;
{
[defaultCenter addObserver:self selector:@selector(imageDidLoad:) name:CPImageDidLoadNotification object:newImage];
if (CPDOMAvailable)
{
_DOMImageElement.width = 0;
_DOMImageElement.height = 0;
}
#if PLATFORM(DOM)
_DOMImageElement.width = 0;
_DOMImageElement.height = 0;
#endif
[_shadowView setHidden:YES];
}
@@ -242,8 +237,6 @@ var CPImageViewEmptyPlaceholderImage = nil;
if (_hasShadow)
{
[self setClipsToBounds:NO];
_shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
[self addSubview:_shadowView];
@@ -294,10 +287,12 @@ var CPImageViewEmptyPlaceholderImage = nil;
{
[super setImageScaling:anImageScaling];
if (CPDOMAvailable && [self currentValueForThemeAttribute:@"image-scaling"] === CPImageScaleAxesIndependently)
#if PLATFORM(DOM)
if ([self currentValueForThemeAttribute:@"image-scaling"] === CPImageScaleAxesIndependently)
{
CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, 0.0, 0.0);
}
#endif
[self setNeedsLayout];
[self setNeedsDisplay:YES];
@@ -315,18 +310,16 @@ var CPImageViewEmptyPlaceholderImage = nil;
{
if (![self image])
{
if (CPDOMAvailable)
{
_DOMImageElement.style.visibility = "hidden";
}
#if PLATFORM(DOM)
_DOMImageElement.style.visibility = "hidden";
#endif
[_shadowView setHidden:YES];
}
else
{
if (CPDOMAvailable)
{
_DOMImageElement.style.visibility = "visible";
}
#if PLATFORM(DOM)
_DOMImageElement.style.visibility = "visible";
#endif
[_shadowView setHidden:NO];
}
}
@@ -361,11 +354,10 @@ var CPImageViewEmptyPlaceholderImage = nil;
if (imageScaling === CPImageScaleAxesIndependently)
{
if (CPDOMAvailable)
{
#if PLATFORM(DOM)
_DOMImageElement.width = ROUND(width);
_DOMImageElement.height = ROUND(height);
}
#endif
}
else
{
@@ -403,11 +395,10 @@ var CPImageViewEmptyPlaceholderImage = nil;
break;
}
if (CPDOMAvailable)
{
#if PLATFORM(DOM)
_DOMImageElement.width = ROUND(width);
_DOMImageElement.height = ROUND(height);
}
#endif
var x,
y;
@@ -450,13 +441,13 @@ var CPImageViewEmptyPlaceholderImage = nil;
break;
}
if (CPDOMAvailable)
{
CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, x, y);
}
#if PLATFORM(DOM)
CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, x, y);
#endif
}
if (CPDOMAvailable && [image isCSSBased] && [image _shouldBeResized])
#if PLATFORM(DOM)
if ([image isCSSBased] && [image _shouldBeResized])
{
[image _setDisplaySize:CGSizeMake(ROUND(width), ROUND(height))];
@@ -465,12 +456,7 @@ var CPImageViewEmptyPlaceholderImage = nil;
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
}
if (CPDOMAvailable && [image isCSSBased])
{
_DOMImageElement.style.width = _DOMImageElement.width + 'px';
_DOMImageElement.style.height = _DOMImageElement.height + 'px';
}
#endif
_imageRect = CGRectMake(x, y, width, height);
@@ -592,10 +578,9 @@ var CPImageViewImageKey = @"CPImageViewImageKey",
if (self)
{
if (CPDOMAvailable)
{
[self _createDOMImageElement];
}
#if PLATFORM(DOM)
[self _createDOMImageElement];
#endif
[self setHasShadow:[aCoder decodeBoolForKey:CPImageViewHasShadowKey]];
[self setImageAlignment:[aCoder decodeIntForKey:CPImageViewImageAlignmentKey]];
-7
View File
@@ -26,12 +26,6 @@
@import "CPEvent_Constants.j"
@import "CPText.j"
// TODO: These are the macOS standard key bindings and are applied on every
// platform. "@" is always Command, and the Emacs-style Control bindings
// (^a, ^e, ^k, ^v, ^y, ...) and arrow/Home/End behaviour conflict with
// Windows and Linux conventions. Provide per-platform tables keyed off
// CPBrowserIsOperatingSystem, mapping the primary modifier through
// CPPlatformActionKeyMask.
CPStandardKeyBindings = {
@"@.": @"cancelOperation:",
@@ -88,7 +82,6 @@ CPStandardKeyBindings[@"~" + CPDeleteFunctionKey] = @"deleteWordForward:";
CPStandardKeyBindings[CPTabCharacter] = @"insertTab:";
CPStandardKeyBindings[@"~" + CPTabCharacter] = @"insertTabIgnoringFieldEditor:";
CPStandardKeyBindings[@"^" + CPTabCharacter] = @"selectNextKeyView:";
CPStandardKeyBindings[@"$" + CPTabCharacter] = @"insertBacktab:";
CPStandardKeyBindings[CPBackTabCharacter] = @"insertBacktab:";
CPStandardKeyBindings[@"^" + CPBackTabCharacter] = @"selectPreviousKeyView:";
+22 -26
View File
@@ -88,61 +88,43 @@ CPRatingLevelIndicatorStyle = 3;
- (void)layoutSubviews
{
// 1. Calculate the Theme State
// We explicitly check the window style mask to see if we are in a HUD.
// This allows us to pass CPThemeStateHUD to the theme system, even if the control
// itself isn't explicitly set to HUD, inheriting the style from the window.
var themeState = [self themeState];
if ([[self window] styleMask] & CPHUDBackgroundWindowMask)
themeState = themeState.and(CPThemeStateHUD);
// 2. Layout the Bezel
var bezelView = [self layoutEphemeralSubviewNamed:"bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color" inState:themeState]];
// TODO Make themable.
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color"]];
var segmentCount = _maxValue - _minValue;
if (segmentCount <= 0)
return;
// 3. Determine the Color based on Value and Thresholds
// We pass 'themeState' here. If it contains CPThemeStateHUD, the ThemeDescriptor
// will return the monochrome color for normal/warning/critical.
// If not, it returns Green/Yellow/Red.
var filledColor = [self valueForThemeAttribute:@"color-normal" inState:themeState],
var filledColor = [self valueForThemeAttribute:@"color-normal"],
value = [self doubleValue];
if (_warningValue < _criticalValue)
{
// Standard ascending scale (e.g. Volume)
if (value >= _criticalValue)
filledColor = [self valueForThemeAttribute:@"color-critical" inState:themeState];
filledColor = [self valueForThemeAttribute:@"color-critical"];
else if (value >= _warningValue)
filledColor = [self valueForThemeAttribute:@"color-warning" inState:themeState];
filledColor = [self valueForThemeAttribute:@"color-warning"];
}
else
{
// Descending scale (e.g. Battery Life)
if (value <= _criticalValue)
filledColor = [self valueForThemeAttribute:@"color-critical" inState:themeState];
filledColor = [self valueForThemeAttribute:@"color-critical"];
else if (value <= _warningValue)
filledColor = [self valueForThemeAttribute:@"color-warning" inState:themeState];
filledColor = [self valueForThemeAttribute:@"color-warning"];
}
var emptyColor = [self valueForThemeAttribute:@"color-empty" inState:themeState];
// 4. Paint Segments
for (var i = 0; i < segmentCount; i++)
{
var segmentView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:bezelView];
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : emptyColor];
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : [self valueForThemeAttribute:@"color-empty"]];
}
}
@@ -323,6 +305,20 @@ CPRatingLevelIndicatorStyle = 3;
[self setNeedsLayout];
}
/*
- (CPTickMarkPosition)tickMarkPosition;
- (void)setTickMarkPosition:(CPTickMarkPosition)position;
- (int)numberOfTickMarks;
- (void)setNumberOfTickMarks:(int)count;
- (int)numberOfMajorTickMarks;
- (void)setNumberOfMajorTickMarks:(int)count;
- (double)tickMarkValueAtIndex:(int)index;
- (CGRect)rectOfTickMarkAtIndex:(int)index;
*/
@end
var CPLevelIndicatorStyleKey = "CPLevelIndicatorStyleKey",
+7 -56
View File
@@ -28,7 +28,6 @@
@import "CPKeyValueBinding.j"
@import "CPMenuItem.j"
@import "CALayer.j"
@import "CPCompatibility.j"
@global CPApp
@@ -52,8 +51,7 @@ CPMenuDidEndTrackingNotification = @"CPMenuDidEndTrackingNotification";
var _CPMenuBarVisible = NO,
_CPMenuBarTitle = @"",
_CPMenuBarAttributes = nil,
_CPMenuBarSharedWindow = nil,
_CPMenuBarImage = nil;
_CPMenuBarSharedWindow = nil;
/*!
@ingroup appkit
@@ -138,10 +136,9 @@ var _CPMenuBarVisible = NO,
[_CPMenuBarSharedWindow orderOut:self];
// FIXME: There must be a better way to do this.
if (CPDOMAvailable)
{
[[CPPlatformWindow primaryPlatformWindow] resizeEvent:nil];
}
#if PLATFORM(DOM)
[[CPPlatformWindow primaryPlatformWindow] resizeEvent:nil];
#endif
}
+ (void)setMenuBarTitle:(CPString)aTitle
@@ -277,8 +274,6 @@ var _CPMenuBarVisible = NO,
_autoenablesItems = YES;
_showsStateColumn = YES;
_themeState = CPThemeStateNormal;
[self setMinimumWidth:0];
}
@@ -291,37 +286,6 @@ var _CPMenuBarVisible = NO,
return [self initWithTitle:@""];
}
// Managing Theme States (HUD Support)
- (void)setThemeState:(CPThemeState)aState
{
if ([self hasThemeState:aState])
return;
_themeState = _themeState.and(aState);
// Propagate to the view if the menu is currently visible
if (_menuWindow)
[[_menuWindow _menuView] setThemeState:_themeState];
}
- (void)unsetThemeState:(CPThemeState)aState
{
if (![self hasThemeState:aState])
return;
_themeState = _themeState.without(aState);
// Propagate to the view if the menu is currently visible
if (_menuWindow)
[[_menuWindow _menuView] setThemeState:_themeState];
}
- (CPThemeState)themeState
{
return _themeState;
}
// Setting Up Menu Commands
/*!
Inserts a menu item at the specified index.
@@ -834,10 +798,6 @@ var _CPMenuBarVisible = NO,
// Create the window for our menu.
var menuWindow = [_CPMenuWindow menuWindowWithMenu:self font:[self font]];
// This pushes the state (e.g., CPThemeStateHUD) to the actual view that renders the menu
if (_themeState)
[[menuWindow _menuView] setThemeState:_themeState];
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
if (anItem)
@@ -928,10 +888,6 @@ var _CPMenuBarVisible = NO,
var theWindow = [aView window],
menuWindow = [_CPMenuWindow menuWindowWithMenu:aMenu font:aFont];
// --- APPLY THEME STATE FROM MENU OBJECT TO VIEW ---
if ([aMenu respondsToSelector:@selector(themeState)])
[[menuWindow _menuView] setThemeState:[aMenu themeState]];
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
var constraintRect = [CPMenu _constraintRectForView:aView],
@@ -1122,14 +1078,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 (CPDOMAvailable && characters != "c" && characters != "x" && characters != "v")
{
if (characters != "c" && characters != "x" && characters != "v")
_CPDOMEventStop(anEvent._DOMEvent);
}
#endif
}
else
{
@@ -1372,9 +1328,6 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
_autoenablesItems = ![aCoder containsValueForKey:CPMenuAutoEnablesItemsKey] || [aCoder decodeBoolForKey:CPMenuAutoEnablesItemsKey];
// Ensure theme state is initialized to avoid undefined issues.
_themeState = CPThemeStateNormal;
[self setMinimumWidth:0];
}
@@ -1436,5 +1389,3 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
@import "_CPMenuBarWindow.j"
@import "_CPMenuWindow.j"
@global _CPDOMEventStop
+9 -11
View File
@@ -22,7 +22,6 @@
@import "CPPanel.j"
@import "_CPMenuManager.j"
@import "CPCompatibility.j"
@class _CPMenuView
@class CPMenu
@@ -101,17 +100,16 @@
- (void)setTitle:(CPString)aTitle
{
if (CPDOMAvailable)
{
var bundleName = [CPApp _applicationName];
#if PLATFORM(DOM)
var bundleName = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPBundleName"];
if (![bundleName length])
document.title = aTitle;
else if ([aTitle length])
document.title = aTitle + @" - " + bundleName;
else
document.title = bundleName;
}
if (![bundleName length])
document.title = aTitle;
else if ([aTitle length])
document.title = aTitle + @" - " + bundleName;
else
document.title = bundleName;
#endif
[_titleField setStringValue:aTitle];
[_titleField sizeToFit];
+15 -62
View File
@@ -62,9 +62,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
// Do this so that coordinates will be accurate.
[menuWindow setFrameOrigin:CGPointMakeZero()];
// we need to reset the HUD state as this may be a recycled instance with HUD rund on non-HUD
[[menuWindow _windowView] unsetThemeState:CPThemeStateHUD];
[[menuWindow _menuView] unsetThemeState:CPThemeStateHUD];
}
else
menuWindow = [[_CPMenuWindow alloc] init];
@@ -111,33 +108,23 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[contentView addSubview:_menuClipView];
_moreAboveView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[_moreAboveView setImage:[_menuView valueForThemeAttribute:@"menu-window-more-above-image"]];
[_moreAboveView setFrameSize:[[_menuView valueForThemeAttribute:@"menu-window-more-above-image"] size]];
[contentView addSubview:_moreAboveView];
_moreBelowView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[contentView addSubview:_moreBelowView];
// Initial setup using default attributes
[self updateScrollArrows];
[_moreBelowView setImage:[_menuView valueForThemeAttribute:@"menu-window-more-below-image"]];
[_moreBelowView setFrameSize:[[_menuView valueForThemeAttribute:@"menu-window-more-below-image"] size]];
[contentView addSubview:_moreBelowView];
}
return self;
}
- (void)updateScrollArrows
{
if (!_menuView)
return;
var aboveImage = [_menuView currentValueForThemeAttribute:@"menu-window-more-above-image"],
belowImage = [_menuView currentValueForThemeAttribute:@"menu-window-more-below-image"];
[_moreAboveView setImage:aboveImage];
[_moreAboveView setFrameSize:[aboveImage size]];
[_moreBelowView setImage:belowImage];
[_moreBelowView setFrameSize:[belowImage size]];
}
+ (float)_standardLeftMargin
{
return [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-window-margin-inset" forClass:_CPMenuView].left;
@@ -175,12 +162,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
- (void)setBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle
{
var color = [_menuView currentValueForThemeAttribute:@"menu-window-pop-up-background-style-color"];
if (!color)
color = [[self class] backgroundColorForBackgroundStyle:aBackgroundStyle];
[self setBackgroundColor:color];
[self setBackgroundColor:[[self class] backgroundColorForBackgroundStyle:aBackgroundStyle]];
}
- (void)setMenu:(CPMenu)aMenu
@@ -255,29 +237,16 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
// If we are a submenu and we are being displayed off the right of the screen,
// we should try and display on the left of our supermenu.
var supermenu = [[self menu] supermenu];
if (supermenu)
if (supermenu && (CGRectGetMaxX(_unconstrainedFrame) > CGRectGetMaxX(_constraintRect)))
{
if (CGRectGetMaxX(_unconstrainedFrame) > CGRectGetMaxX(_constraintRect))
var supermenuWindow = supermenu._menuWindow;
if (supermenuWindow)
{
var supermenuWindow = supermenu._menuWindow;
if (supermenuWindow)
{
var supermenuFrame = [supermenuWindow frame];
_unconstrainedFrame.origin.x = CGRectGetMinX(supermenuFrame) - CGRectGetWidth(_unconstrainedFrame);
}
var supermenuFrame = [supermenuWindow frame];
_unconstrainedFrame.origin.x = CGRectGetMinX(supermenuFrame) - CGRectGetWidth(_unconstrainedFrame);
}
// Shift true submenus vertically to fit within the screen if they extend past the bottom
if (supermenu !== [CPApp mainMenu] && CGRectGetMaxY(_unconstrainedFrame) > CGRectGetMaxY(_constraintRect))
_unconstrainedFrame.origin.y -= CGRectGetMaxY(_unconstrainedFrame) - CGRectGetMaxY(_constraintRect);
}
// Ensure no menu starts above the visible screen area, preventing it from incorrectly
// starting scrolled with a top arrow.
if (CGRectGetMinY(_unconstrainedFrame) < CGRectGetMinY(_constraintRect))
_unconstrainedFrame.origin.y = CGRectGetMinY(_constraintRect);
var constrainedFrame = CGRectIntersection(_unconstrainedFrame, _constraintRect),
marginInset = [_menuView valueForThemeAttribute:@"menu-window-margin-inset"],
scrollIndicatorHeight = [_menuView valueForThemeAttribute:@"menu-window-scroll-indicator-height"];
@@ -299,8 +268,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[super setFrame:constrainedFrame display:shouldDisplay animate:shouldAnimate];
// This needs to happen before changing the frame.
// Base the view origin on our modified _unconstrainedFrame instead of aFrame
var menuViewOrigin = CGPointMake(CGRectGetMinX(_unconstrainedFrame) + marginInset.left, CGRectGetMinY(_unconstrainedFrame) + marginInset.top),
var menuViewOrigin = CGPointMake(CGRectGetMinX(aFrame) + marginInset.left, CGRectGetMinY(aFrame) + marginInset.top),
moreAbove = menuViewOrigin.y < CGRectGetMinY(constrainedFrame) + marginInset.top,
moreBelow = menuViewOrigin.y + CGRectGetHeight([_menuView frame]) > CGRectGetMaxY(constrainedFrame) - marginInset.bottom,
@@ -534,21 +502,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
};
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[_menuItemViews makeObjectsPerformSelector:@selector(setThemeState:) withObject:aState];
if ([[self window] respondsToSelector:@selector(updateScrollArrows)])
[[self window] updateScrollArrows];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[_menuItemViews makeObjectsPerformSelector:@selector(unsetThemeState:) withObject:aState];
}
- (unsigned)numberOfUnhiddenItems
{
return _visibleMenuItemInfos.length;
+17 -26
View File
@@ -895,17 +895,6 @@ 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: -
@@ -951,6 +940,9 @@ 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.
@@ -973,7 +965,7 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
_target = [aCoder decodeObjectForKey:CPMenuItemTargetKey];
_action = [aCoder decodeObjectForKey:CPMenuItemActionKey];
_isEnabled = [self _decodeValueForKey:CPMenuItemIsEnabledKey defaultValue:YES coder:aCoder];
_isEnabled = DEFAULT_VALUE(CPMenuItemIsEnabledKey, YES);
_isHidden = [aCoder decodeBoolForKey:CPMenuItemIsHiddenKey];
_tag = [aCoder decodeIntForKey:CPMenuItemTagKey];
_state = [aCoder decodeIntForKey:CPMenuItemStateKey];
@@ -1020,20 +1012,20 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
[aCoder encodeObject:_target forKey:CPMenuItemTargetKey];
[aCoder encodeObject:_action forKey:CPMenuItemActionKey];
[self _encodeValue:_isEnabled forKey:CPMenuItemIsEnabledKey ifNotEqualTo:YES coder:aCoder]
[self _encodeValue:_isHidden forKey:CPMenuItemIsHiddenKey ifNotEqualTo:NO coder:aCoder]
ENCODE_IFNOT(CPMenuItemIsEnabledKey, _isEnabled, YES);
ENCODE_IFNOT(CPMenuItemIsHiddenKey, _isHidden, NO);
[self _encodeValue:_tag forKey:CPMenuItemTagKey ifNotEqualTo:0 coder:aCoder]
[self _encodeValue:_state forKey:CPMenuItemStateKey ifNotEqualTo:CPOffState coder:aCoder]
ENCODE_IFNOT(CPMenuItemTagKey, _tag, 0);
ENCODE_IFNOT(CPMenuItemStateKey, _state, CPOffState);
[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(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:_submenu forKey:CPMenuItemSubmenuKey ifNotEqualTo:nil coder:aCoder]
[self _encodeValue:_menu forKey:CPMenuItemMenuKey ifNotEqualTo:nil coder:aCoder]
ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil);
ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil);
if (_keyEquivalent && _keyEquivalent.length)
[aCoder encodeObject:_keyEquivalent forKey:CPMenuItemKeyEquivalentKey];
@@ -1044,9 +1036,8 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
if (_indentationLevel > 0)
[aCoder encodeInt:_indentationLevel forKey:CPMenuItemIndentationLevelKey];
[self _encodeValue:_representedObject forKey:CPMenuItemRepresentedObjectKey ifNotEqualTo:nil coder:aCoder]
[self _encodeValue:_view forKey:CPMenuItemViewKey ifNotEqualTo:nil coder:aCoder]
ENCODE_IFNOT(CPMenuItemRepresentedObjectKey, _representedObject, nil);
ENCODE_IFNOT(CPMenuItemViewKey, _view, nil);
}
@end
+14 -44
View File
@@ -145,26 +145,6 @@
return self;
}
- (void)setThemeState:(CPThemeState)aState
{
var oldState = [self themeState];
[super setThemeState:aState];
// If the state changed (e.g. adding HUD), we must re-run update
// to fetch the new text color defined for that state.
if (oldState !== [self themeState])
[self update];
}
- (void)unsetThemeState:(CPThemeState)aState
{
var oldState = [self themeState];
[super unsetThemeState:aState];
if (oldState !== [self themeState])
[self update];
}
- (CPColor)textColor
{
if (![_menuItem isEnabled])
@@ -173,7 +153,7 @@
if (_highlighted)
return [CPColor whiteColor];
return [self currentValueForThemeAttribute:@"menu-item-text-color"];
return [self valueForThemeAttribute:@"menu-item-text-color"];
}
- (CPColor)textShadowColor
@@ -184,7 +164,7 @@
if (_highlighted)
return nil;
return [self currentValueForThemeAttribute:@"menu-item-text-shadow-color"];
return [self valueForThemeAttribute:@"menu-item-text-shadow-color"];
}
- (void)setFont:(CPFont)aFont
@@ -198,11 +178,6 @@
return _font || [_menuItem font] || [CPFont systemFontOfSize:CPFontCurrentSystemSize];
}
// override needed to cancel out the standard HUD propagation
- (void)viewDidMoveToWindow
{
}
// FIXME: update is called 2 times at each display. Find why and fix.
- (void)update
{
@@ -213,8 +188,6 @@
// When possible, use specific vertical margin/offset value based on font size (which could have been set by control size)
correspondingControlSize = [myFont controlSizeCorrespondingToFontSize],
controlSizeState = CPControlSizeThemeStates[correspondingControlSize],
queryState = [self themeState] ? [self themeState].and(controlSizeState) : controlSizeState,
verticalMargin = [self valueForThemeAttribute:@"vertical-margin" inState:CPControlSizeThemeStates[correspondingControlSize]],
verticalOffset = [self valueForThemeAttribute:@"vertical-offset" inState:CPControlSizeThemeStates[correspondingControlSize]];
@@ -226,15 +199,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:queryState] || [_menuItem onStateImage]];
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPOffState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:queryState] || [_menuItem offStateImage]];
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPMixedState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:queryState] || [_menuItem mixedStateImage]];
[_stateView setImage:[_menuItem mixedStateImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
default:
@@ -353,10 +326,7 @@
_highlighted = shouldHighlight;
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize],
// Construct the query state including the view's current theme state (e.g. HUD)
controlSizeState = CPControlSizeThemeStates[correspondingControlSize],
queryState = [self themeState] ? [self themeState].and(controlSizeState) : controlSizeState;
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize];
[_imageAndTextView setTextColor:[self textColor]];
[_keyEquivalentView setTextColor:[self textColor]];
@@ -369,7 +339,7 @@
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:queryState]];
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
else
[_submenuIndicatorView setColor:[self textColor]];
}
@@ -379,7 +349,7 @@
[_imageAndTextView setImage:[_menuItem image]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:queryState]];
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
else
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
}
@@ -391,15 +361,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:queryState]];
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:queryState]];
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:queryState]];
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
default:
@@ -411,15 +381,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:queryState]];
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:queryState]];
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:queryState]];
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break;
default:
-22
View File
@@ -83,28 +83,6 @@
return self;
}
// override needed to cancel out the standard HUD propagation
- (void)viewDidMoveToWindow
{
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
// Propagate state to the actual content view (StandardView, Separator, etc.)
if ([_view respondsToSelector:@selector(setThemeState:)])
[_view setThemeState:aState];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
if ([_view respondsToSelector:@selector(unsetThemeState:)])
[_view unsetThemeState:aState];
}
- (CGSize)minSize
{
return _minSize;
+57 -604
View File
@@ -23,8 +23,6 @@
@import "CPButton.j"
@import "CPTableColumn.j"
@import "CPTableView.j"
@import "CPViewAnimation.j"
@import "CPTreeNode.j"
@global CPApp
@@ -87,15 +85,9 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
CPOutlineViewCoalesceSelectionNotificationStateOn = 1,
CPOutlineViewCoalesceSelectionNotificationStateDid = 2;
function SELECTION_SHOULD_CHANGE(anOutlineView)
{
return (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_selectionShouldChangeInOutlineView_) || [(anOutlineView)._outlineViewDelegate selectionShouldChangeInOutlineView:(anOutlineView)]);
}
#define SELECTION_SHOULD_CHANGE(anOutlineView) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_selectionShouldChangeInOutlineView_) || [(anOutlineView)._outlineViewDelegate selectionShouldChangeInOutlineView:(anOutlineView)])
function SHOULD_SELECT_ITEM(anOutlineView, anItem)
{
return (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_) || [(anOutlineView)._outlineViewDelegate outlineView:(anOutlineView) shouldSelectItem:(anItem)]);
}
#define SHOULD_SELECT_ITEM(anOutlineView, anItem) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_) || [(anOutlineView)._outlineViewDelegate outlineView:(anOutlineView) shouldSelectItem:(anItem)])
@protocol CPOutlineViewDelegate <CPObject>
@@ -293,12 +285,6 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
CPArray _pendingItemToClean;
CPArray _itemAddedDuringLastLoading;
CPIndexSet _animatingDisclosureRows;
BOOL _animates @accessors(property=animates);
CPViewAnimation _rowsAnimation;
CPArray _animationGhosts;
}
- (id)initWithFrame:(CGRect)aFrame
@@ -329,9 +315,6 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
[super setDelegate:self];
[self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
_animatingDisclosureRows = [CPIndexSet indexSet];
_animates = YES;
}
return self;
@@ -342,22 +325,12 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
_BlockDeselectView = function(view, row, column)
{
[view unsetThemeState:CPThemeStateSelectedDataView];
// Ensure we clear the focus state so the text goes back to normal color
[view unsetThemeState:CPThemeStateFirstResponder];
[_disclosureControlsForRows[row] unsetThemeState:CPThemeStateSelected];
};
_BlockSelectView = function(view, row, column)
{
[view setThemeState:CPThemeStateSelectedDataView];
// If the table is focused, apply the state that turns text white immediately
if ([self _isFocused])
[view setThemeState:CPThemeStateFirstResponder];
else
[view unsetThemeState:CPThemeStateFirstResponder];
[_disclosureControlsForRows[row] setThemeState:CPThemeStateSelected];
};
}
@@ -509,60 +482,6 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
return itemInfo.shouldShowOutlineDisclosureControl;
}
- (void)_noteDisclosureAnimationWillStartForRow:(CPInteger)aRow
{
[_animatingDisclosureRows addIndex:aRow];
// We do not need to setNeedsDisplay here because the button itself
// will trigger a redraw when its state changes or when the animation setup occurs.
}
- (void)_noteDisclosureAnimationDidStopForRow:(CPInteger)aRow
{
[_animatingDisclosureRows removeIndex:aRow];
// Explicitly find the button for this row and force it to redraw.
// The button was returning early from drawRect during the animation.
// Now that the flag is cleared, we must tell it to paint again.
if (_disclosureControlsForRows && aRow < _disclosureControlsForRows.length)
{
var button = _disclosureControlsForRows[aRow];
if (button)
[button display];
}
}
- (BOOL)_isRowAnimatingDisclosure:(CPInteger)aRow
{
return [_animatingDisclosureRows containsIndex:aRow];
}
- (CPInteger)rowForView:(CPView)aView
{
// 1. Try the standard lookup (for normal cells)
var row = [super rowForView:aView];
if (row !== CPNotFound)
return row;
// 2. If not found, check if it is a disclosure button
if ([aView isKindOfClass:[CPDisclosureButton class]])
{
// Check the list of active disclosure buttons
// _disclosureControlsForRows is a native JS array, so we use indexOf
var index = _disclosureControlsForRows.indexOf(aView);
if (index > -1)
return index;
// 3. Check if it is an animation clone (from the fix in the previous step)
// The clone is not in the array, but it carries the row index.
if (aView._animatingRowIndex !== undefined && aView._animatingRowIndex !== null)
return aView._animatingRowIndex;
}
return CPNotFound;
}
/*!
Used to find if an item is already expanded.
@@ -702,9 +621,6 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
*/
- (void)expandItem:(id)anItem expandChildren:(BOOL)shouldExpandChildren
{
var oldRowCount = [self numberOfRows],
parentIndex = [self rowForItem:anItem];
if ([self _delegateRespondsToShouldExpandItem])
if ([_outlineViewDelegate outlineView:self shouldExpandItem:anItem] == NO)
return;
@@ -747,7 +663,7 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
if (rowCountDelta)
{
var selection = [self selectedRowIndexes],
expandIndex = parentIndex + 1;
expandIndex = [self rowForItem:anItem] + 1;
if ([selection intersectsIndexesInRange:CPMakeRange(expandIndex, _itemsForRows.length)])
{
@@ -774,17 +690,6 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
if (r === CPOutlineViewCoalesceSelectionNotificationStateDid)
[self _noteSelectionDidChange];
}
// FIX: Animation logic moved to the end to match Version 1 behavior.
// This ensures views are ready and handles both single and recursive expansion.
var newRowCount = [self numberOfRows],
addedCount = newRowCount - oldRowCount;
if (_animates && addedCount > 0)
{
[self layoutSubviews]; // Force views to spawn so we can animate them
[self _animateExpandFromIndex:parentIndex addedCount:addedCount];
}
}
/*!
@@ -797,30 +702,29 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
if (!anItem)
return;
var collapseTopIndex = [self rowForItem:anItem];
// FIX 1: Safety check to prevent "jumping".
// If the parent row isn't found or invisible, we cannot animate.
if (collapseTopIndex === CPNotFound || collapseTopIndex < 0)
return;
if ([self _delegateRespondsToShouldCollapseItem])
if ([_outlineViewDelegate outlineView:self shouldCollapseItem:anItem] == NO)
return;
var itemInfo = _itemInfosForItems[[anItem UID]];
if (!itemInfo || !itemInfo.isExpanded)
if (!itemInfo)
return;
if (!itemInfo.isExpanded)
return;
// Don't spam notifications.
_coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
[self _noteItemWillCollapse:anItem];
var topLevel = [self levelForRow:collapseTopIndex],
// Update selections:
// * Deselect items inside the collapsed item.
// * Shift row selections below the collapsed item so that the same logical items remain selected.
var collapseTopIndex = [self rowForItem:anItem],
topLevel = [self levelForRow:collapseTopIndex],
collapseEndIndex = collapseTopIndex;
// Calculate how many items are being removed
while (collapseEndIndex + 1 < _itemsForRows.length && [self levelForRow:collapseEndIndex + 1] > topLevel)
collapseEndIndex++;
@@ -834,288 +738,31 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
{
[self _noteSelectionIsChanging];
[selection removeIndexesInRange:collapseRange];
[self _setSelectedRowIndexes:selection];
[self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
}
// Shift any selected rows below upwards.
if ([selection intersectsIndexesInRange:CPMakeRange(collapseEndIndex + 1, _itemsForRows.length)])
{
[self _noteSelectionIsChanging];
[selection shiftIndexesStartingAtIndex:collapseEndIndex + 1 by:-collapseRange.length];
[self _setSelectedRowIndexes:selection];
[self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
}
}
// Capture visual ghosts BEFORE the data is removed
var ghosts = nil;
if (_animates && collapseRange.length > 0)
ghosts = [self _createGhostsForRowsFrom:collapseRange.location to:CPMaxRange(collapseRange) - 1];
// Collapse the model
itemInfo.isExpanded = NO;
[self reloadItem:anItem reloadChildren:YES];
[self _noteItemDidCollapse:anItem];
// Trigger Animation
if (_animates && collapseRange.length > 0)
{
[self layoutSubviews];
[self _animateCollapseFromIndex:collapseTopIndex removedCount:collapseRange.length ghosts:ghosts];
}
// Send selection notifications only after the items have loaded so that
// the new selection is consistent with the actual rows for any observers.
var r = _coalesceSelectionNotificationState;
_coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
if (r === CPOutlineViewCoalesceSelectionNotificationStateDid)
[self _noteSelectionDidChange];
}
- (void)_animateCollapseFromIndex:(CPInteger)parentIndex removedCount:(CPInteger)removedCount ghosts:(CPArray)ghosts
{
// Ensure we don't animate if parent is invalid
if (parentIndex < 0)
return;
if (_rowsAnimation && [_rowsAnimation isAnimating])
[_rowsAnimation stopAnimation];
var viewAnimations = [],
// The Y position the ghosts will slide INTO (the parent row's Y position)
parentY = [self frameOfDataViewAtColumn:0 row:parentIndex].origin.y;
// Calculate shift offset based on ghost height or row height
var shiftOffset = 0;
if (ghosts && ghosts.length > 0)
{
var firstGhostFrame = [ghosts[0] frame],
maxY = CGRectGetMaxY(firstGhostFrame);
for(var i = 1; i < ghosts.length; i++)
maxY = MAX(maxY, CGRectGetMaxY([ghosts[i] frame]));
shiftOffset = maxY - firstGhostFrame.origin.y;
}
else
{
shiftOffset = removedCount * [self rowHeight];
}
// 1. Ghost views slide up into parent and fade out
if (ghosts)
{
for (var i = 0; i < ghosts.length; i++)
{
var ghost = ghosts[i],
startFrame = [ghost frame],
targetFrame = CGRectMake(startFrame.origin.x, parentY, startFrame.size.width, startFrame.size.height);
// FIX 3: Removed [self addSubview:positioned:relativeTo:]
// Ghosts remain at the top of the view stack (where they were created)
// ensuring they are visible and don't get hidden behind the background.
[viewAnimations addObject:@{
CPViewAnimationTargetKey: ghost,
CPViewAnimationStartFrameKey: startFrame,
CPViewAnimationEndFrameKey: targetFrame,
CPViewAnimationEffectKey: CPViewAnimationFadeOutEffect
}];
}
}
// 2. Siblings (rows below the collapsed group) slide UP to fill the gap
var columns = [self tableColumns],
colIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, columns.length)],
startIndex = parentIndex + 1,
rowCount = [self numberOfRows];
// FIX 4: Only animate rows that actually exist after the parent
if (startIndex < rowCount)
{
var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, rowCount - startIndex)];
[self _enumerateViewsInRows:rowIndexes columns:colIndexes usingBlock:function(view, row, column, stop) {
var targetFrame = [view frame];
// Siblings start lower (at target Y + shift) and move UP to target Y
var startFrame = CGRectMake(targetFrame.origin.x, targetFrame.origin.y + shiftOffset, targetFrame.size.width, targetFrame.size.height);
[view setFrame:startFrame];
[viewAnimations addObject:@{
CPViewAnimationTargetKey: view,
CPViewAnimationStartFrameKey: startFrame,
CPViewAnimationEndFrameKey: targetFrame
}];
}];
// Also animate disclosure triangles for the siblings
var disclosureRows = [];
[rowIndexes getIndexes:disclosureRows maxCount:-1 inIndexRange:nil];
for (var i = 0; i < disclosureRows.length; i++)
{
var btn = _disclosureControlsForRows[disclosureRows[i]];
if (btn)
{
var targetBtnFrame = [btn frame],
startBtnFrame = CGRectMake(targetBtnFrame.origin.x, targetBtnFrame.origin.y + shiftOffset, targetBtnFrame.size.width, targetBtnFrame.size.height);
[btn setFrame:startBtnFrame];
[viewAnimations addObject:@{
CPViewAnimationTargetKey: btn,
CPViewAnimationStartFrameKey: startBtnFrame,
CPViewAnimationEndFrameKey: targetBtnFrame
}];
}
}
}
[self _runAnimation:viewAnimations withGhosts:ghosts];
}
// MARK: - CPRuleEditor Style Animations
- (CPArray)_createGhostsForRowsFrom:(CPInteger)startRow to:(CPInteger)endRow
{
var ghosts = [];
var columns = [self tableColumns];
var rowIndexes =[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startRow, endRow - startRow + 1)];
var colIndexes =[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, columns.length)];[self _enumerateViewsInRows:rowIndexes columns:colIndexes usingBlock:function(view, row, column, stop) {
var ghost = [[CPView alloc] initWithFrame:[view frame]];
if (view._DOMElement)
ghost._DOMElement.innerHTML = view._DOMElement.innerHTML;
[self addSubview:ghost];
[ghosts addObject:ghost];
}];
var disclosureRows = [];[rowIndexes getIndexes:disclosureRows maxCount:-1 inIndexRange:nil];
for (var i = 0; i < disclosureRows.length; i++)
{
var btn = _disclosureControlsForRows[disclosureRows[i]];
if (btn)
{
var ghostBtn = [[CPView alloc] initWithFrame:[btn frame]];
if (btn._DOMElement)
ghostBtn._DOMElement.innerHTML = btn._DOMElement.innerHTML;[self addSubview:ghostBtn];
[ghosts addObject:ghostBtn];
}
}
return ghosts;
}
- (void)_animateExpandFromIndex:(CPInteger)parentIndex addedCount:(CPInteger)addedCount
{
if (_rowsAnimation && [_rowsAnimation isAnimating])
[_rowsAnimation stopAnimation];
var viewAnimations = [];
var columns = [self tableColumns];
var colIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, columns.length)];
var startIndex = parentIndex + 1;
var rowIndexes =[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startIndex, [self numberOfRows] - startIndex)];
var parentY = parentIndex >= 0 ? [self frameOfDataViewAtColumn:0 row:parentIndex].origin.y : 0;
// Safely calculate the shift gap even with variable row heights
var shiftOffset = 0;
if (addedCount > 0 && (startIndex + addedCount) < [self numberOfRows])
{
var firstNewTarget = [self frameOfDataViewAtColumn:0 row:startIndex].origin.y;
var firstDisplacedTarget = [self frameOfDataViewAtColumn:0 row:startIndex + addedCount].origin.y;
shiftOffset = firstDisplacedTarget - firstNewTarget;
}
else
shiftOffset = addedCount * [self rowHeight];[self _enumerateViewsInRows:rowIndexes columns:colIndexes usingBlock:function(view, row, column, stop) {
var isNewRow = (row < startIndex + addedCount);
var targetFrame = [view frame];
var startFrame;
var animDict = @{ CPViewAnimationTargetKey: view, CPViewAnimationEndFrameKey: targetFrame };
if (isNewRow)
{
startFrame = CGRectMake(targetFrame.origin.x, parentY, targetFrame.size.width, targetFrame.size.height);
[animDict setObject:CPViewAnimationFadeInEffect forKey:CPViewAnimationEffectKey];[view setAlphaValue:0.0];
}
else
startFrame = CGRectMake(targetFrame.origin.x, targetFrame.origin.y - shiftOffset, targetFrame.size.width, targetFrame.size.height);
[view setFrame:startFrame];[animDict setObject:startFrame forKey:CPViewAnimationStartFrameKey];
[viewAnimations addObject:animDict];
}];
var disclosureRows = [];
[rowIndexes getIndexes:disclosureRows maxCount:-1 inIndexRange:nil];
for (var i = 0; i < disclosureRows.length; i++)
{
var r = disclosureRows[i];
var btn = _disclosureControlsForRows[r];
if (btn)
{
var isNewRow = (r < startIndex + addedCount);
var targetBtnFrame = [btn frame];
var startBtnFrame;
var animBtnDict = @{ CPViewAnimationTargetKey: btn, CPViewAnimationEndFrameKey: targetBtnFrame };
if (isNewRow)
{
startBtnFrame = CGRectMake(targetBtnFrame.origin.x, parentY, targetBtnFrame.size.width, targetBtnFrame.size.height);[animBtnDict setObject:CPViewAnimationFadeInEffect forKey:CPViewAnimationEffectKey];[btn setAlphaValue:0.0];
}
else
startBtnFrame = CGRectMake(targetBtnFrame.origin.x, targetBtnFrame.origin.y - shiftOffset, targetBtnFrame.size.width, targetBtnFrame.size.height);
[btn setFrame:startBtnFrame];[animBtnDict setObject:startBtnFrame forKey:CPViewAnimationStartFrameKey];
[viewAnimations addObject:animBtnDict];
}
}
[self _runAnimation:viewAnimations withGhosts:nil];
}
- (void)_runAnimation:(CPArray)viewAnimations withGhosts:(CPArray)ghosts
{
// Clean up interrupted animations
if (_animationGhosts)
{
for (var i = 0; i < _animationGhosts.length; i++)
[_animationGhosts[i] removeFromSuperview];
}
_animationGhosts = ghosts;
if (viewAnimations.length > 0)
{
_rowsAnimation = [[CPViewAnimation alloc] initWithViewAnimations:viewAnimations];
[_rowsAnimation setDuration:0.25];[_rowsAnimation setAnimationCurve:CPAnimationEaseInOut];
[_rowsAnimation setDelegate:self];
[_rowsAnimation startAnimation];
}
else if (ghosts)
{
for (var i = 0; i < ghosts.length; i++)
[ghosts[i] removeFromSuperview];
_animationGhosts = nil;
}
}
- (void)animationDidEnd:(CPViewAnimation)animation
{
var animations = [animation viewAnimations];
for (var i = 0; i < animations.length; i++)
{
var view = animations[i][CPViewAnimationTargetKey];
if (!_animationGhosts || ![_animationGhosts containsObject:view])[view setAlphaValue:1.0];
}
if (_animationGhosts)
{
for (var i = 0; i < _animationGhosts.length; i++)
[_animationGhosts[i] removeFromSuperview];
_animationGhosts = nil;
}
[self setNeedsLayout];
}
/*!
Reloads the data for an item.
@@ -1139,11 +786,6 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
*/
- (void)reloadItem:(id)anItem reloadChildren:(BOOL)shouldReloadChildren
{
if (_rowsAnimation && [_rowsAnimation isAnimating])
{
[_rowsAnimation stopAnimation];
[self animationDidEnd:_rowsAnimation];
}
_pendingItemToClean = [];
_itemAddedDuringLastLoading = [];
@@ -1522,12 +1164,8 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
{
if (_disclosureControlsForRows.length)
{
var indexes = [_selectedRowIndexes copy],
diff = _itemsForRows.length - _disclosureControlsForRows.length;
if (diff > 0)
[indexes removeIndexesInRange:CPMakeRange(_disclosureControlsForRows.length, diff)];
var indexes = [_selectedRowIndexes copy];
[indexes removeIndexesInRange:CPMakeRange(_disclosureControlsForRows.length, _itemsForRows.length - _disclosureControlsForRows.length)];
[[_disclosureControlsForRows objectsAtIndexes:indexes] makeObjectsPerformSelector:@selector(unsetThemeState:) withObject:CPThemeStateSelected];
}
@@ -1535,12 +1173,8 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
if (_disclosureControlsForRows.length)
{
var indexes = [_selectedRowIndexes copy],
diff = _itemsForRows.length - _disclosureControlsForRows.length;
if (diff > 0)
[indexes removeIndexesInRange:CPMakeRange(_disclosureControlsForRows.length, diff)];
var indexes = [_selectedRowIndexes copy];
[indexes removeIndexesInRange:CPMakeRange(_disclosureControlsForRows.length, _itemsForRows.length - _disclosureControlsForRows.length)];
[[_disclosureControlsForRows objectsAtIndexes:indexes] makeObjectsPerformSelector:@selector(setThemeState:) withObject:CPThemeStateSelected];
}
}
@@ -2012,17 +1646,6 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
return rect;
}
/*!
@ignore
*/
- (CGRect)_rectForDropHighlightViewOnRow:(CPInteger)theRowIndex
{
var rect = [super _rectForDropHighlightViewOnRow:theRowIndex];
rect.size.height += 2.0;
return rect;
}
/*!
@ignore
We need to move the disclosure control too.
@@ -2606,15 +2229,7 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
@implementation CPDisclosureButton : CPButton
{
float _angle;
// Animation state tracking
BOOL _isAnimatingClone;
CPInteger _animatingRowIndex;
CPOutlineView _parentOutlineView;
DOMElement _arrowElement;
CPString _lastColorCSS;
float _angle;
}
- (id)initWithFrame:(CGRect)aFrame
@@ -2622,224 +2237,66 @@ function SHOULD_SELECT_ITEM(anOutlineView, anItem)
self = [super initWithFrame:aFrame];
if (self)
{
[self setBordered:NO];
[self setWantsLayer:YES];
[self setHighlightsBy:0];
[self _setupDisclosureButton];
}
return self;
}
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
[self _setupDisclosureButton];
}
return self;
}
- (void)_setupDisclosureButton
{
_isAnimatingClone = NO; // Default for real buttons
if (CPDOMAvailable)
{
// Add a dedicated DOM element for the SVG arrow
_arrowElement = document.createElement("div");
_arrowElement.style.position = "absolute";
_arrowElement.style.top = "0px";
_arrowElement.style.left = "0px";
_arrowElement.style.width = "100%";
_arrowElement.style.height = "100%";
_arrowElement.style.backgroundPosition = "center center";
_arrowElement.style.backgroundRepeat = "no-repeat";
// 24px maps the 24x24 viewBox to exact pixels, making the 10x5 path scale perfectly
_arrowElement.style.backgroundSize = "24px 24px";
_arrowElement.style.pointerEvents = "none";
self._DOMElement.appendChild(_arrowElement);
}
}
- (void)setAngle:(float)anAngle
{
_angle = anAngle;
// Rotate the DOM element directly using standard CSS transforms
var deg = _angle * (180.0 / Math.PI);
if (CPDOMAvailable)
{
_arrowElement.style.transform = "rotate(" + deg + "deg)";
}
[self display];
}
- (float)angle
{
return _angle;
}
- (void)setState:(CPInteger)aState
{
[super setState:aState];
// Using setAngle immediately triggers the transform
if ([self state] === CPOnState)
[self setAngle:0.0];
_angle = 0.0;
else
[self setAngle:-PI_2];
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)point mouseIsUp:(BOOL)mouseIsUp
{
var bounds = [self bounds];
if (CGRectContainsPoint(bounds, point))
{
var outlineView = [self superview],
row = [outlineView rowForView:self];
if ([outlineView isKindOfClass:[CPOutlineView class]] && row !== CPNotFound)
{
// 1. Tell OutlineView to suppress drawing of the real button
[outlineView _noteDisclosureAnimationWillStartForRow:row];
// 2. Create the clone
var clone = [[CPDisclosureButton alloc] initWithFrame:[self frame]];
[clone setWantsLayer:YES];
[clone setThemeState:[self themeState]];
[clone unsetThemeState:CPThemeStateHighlighted];
[clone setHighlighted:NO];
[clone setAngle:_angle];
[clone setHitTests:NO];
// 3. Mark this as a clone so drawRect knows to draw it
clone._isAnimatingClone = YES;
clone._animatingRowIndex = row;
clone._parentOutlineView = outlineView;[outlineView addSubview:clone positioned:CPWindowAbove relativeTo:self];
// 4. Calculate angles
var isCurrentlyExpanded = ([self state] === CPOnState),
targetAngle = isCurrentlyExpanded ? -PI_2 : 0.0;
// 5. Setup Animation
var anim = [CABasicAnimation animationWithKeyPath:@"angle"];
[anim setFromValue:_angle];
[anim setToValue:targetAngle];
[anim setDuration:0.25];
[anim setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[clone layer] setDelegate:clone];
[anim setDelegate:clone];
[[clone layer] addAnimation:anim forKey:@"angle"];
// Set final angle on clone to avoid flicker at end
[clone setAngle:targetAngle];
}
}
[super stopTracking:lastPoint at:point mouseIsUp:mouseIsUp];
}
- (void)animationDidStop:(CAAnimation)anim finished:(BOOL)flag
{
// This runs on the CLONE
if (_parentOutlineView)
[_parentOutlineView _noteDisclosureAnimationDidStopForRow:_animatingRowIndex];
[self removeFromSuperview];
_angle = -PI_2;
}
- (void)drawRect:(CGRect)aRect
{
var bounds = [self bounds],
context = [[CPGraphicsContext currentContext] graphicsPort],
width = CGRectGetWidth(bounds),
height = CGRectGetHeight(bounds);
if (CPDOMAvailable)
CGContextBeginPath(context);
if (_angle)
{
if (_arrowElement && _arrowElement.parentNode !== self._DOMElement)
self._DOMElement.appendChild(_arrowElement);
var centre = CGPointMake(FLOOR(width / 2.0), FLOOR(height / 2.0));
CGContextTranslateCTM(context, centre.x, centre.y);
CGContextRotateCTM(context, _angle);
CGContextTranslateCTM(context, -centre.x, -centre.y);
}
// If I am NOT the clone, I must check if my row is animating.
// If it is animating, the clone is drawing on top of me, so I should be invisible.
if (!_isAnimatingClone)
{
var outlineView = [self superview];
if ([outlineView isKindOfClass:[CPOutlineView class]])
{
var row = [outlineView rowForView:self];
if (row !== CPNotFound && [outlineView _isRowAnimatingDisclosure:row])
{
// Center, but crisp.
CGContextTranslateCTM(context, FLOOR((width - 9.0) / 2.0), FLOOR((height - 8.0) / 2.0));
if (CPDOMAvailable)
{
_arrowElement.style.display = "none";
}
return; // Suppress the real button
}
}
}
CGContextMoveToPoint(context, 0.0, 0.0);
CGContextAddLineToPoint(context, 9.0, 0.0);
CGContextAddLineToPoint(context, 4.5, 8.0);
CGContextClosePath(context);
_arrowElement.style.display = "block";
var isSelected = [self hasThemeState:CPThemeStateSelected],
isHighlighted = [self hasThemeState:CPThemeStateHighlighted],
isKeyWindow = [self hasThemeState:CPThemeStateKeyWindow],
isHUD = [self window] && ([[self window] styleMask] & CPHUDBackgroundWindowMask),
triangleColor = nil;
CGContextSetFillColor(context,
colorForDisclosureTriangle([self hasThemeState:CPThemeStateSelected],
[self hasThemeState:CPThemeStateHighlighted]));
CGContextFillPath(context);
// Establish the required color based on the state/outline selection
if (isHUD)
{
triangleColor = isSelected ? [CPColor blackColor] : [CPColor whiteColor];
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0.0, 0.0);
CGContextAddLineToPoint(context, 4.5, 8.0);
if (isHighlighted)
triangleColor = [triangleColor colorWithAlphaComponent:0.5];
}
else
{
if (isSelected)
triangleColor = isKeyWindow ? [CPColor whiteColor] : [CPColor blackColor];
else triangleColor = [CPColor colorWithCalibratedWhite:0.45 alpha: 1.0];
if (_angle === 0.0)
CGContextAddLineToPoint(context, 9.0, 0.0);
if (isHighlighted)
{
if (isSelected && isKeyWindow)
triangleColor = [CPColor colorWithCalibratedWhite:0.9 alpha: 1.0];
else if (isSelected && !isKeyWindow)
triangleColor = [CPColor colorWithCalibratedWhite:0.2 alpha: 1.0];
else
triangleColor = [CPColor colorWithCalibratedWhite:0.25 alpha: 1.0];
}
}
var colorCSS = [triangleColor cssString];
// Convert and inject the updated SVG only when the required color has changed
if (_lastColorCSS !== colorCSS)
{
_lastColorCSS = colorCSS;
var svgString = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7 10L12 15L17 10H7Z" fill="' + colorCSS + '"/></svg>';
var b64 = window.btoa(svgString);
if (CPDOMAvailable)
{
_arrowElement.style.backgroundImage = "url('data:image/svg+xml;charset=utf-8;base64," + b64 + "')";
}
}
CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha: 0.7]);
CGContextStrokePath(context);
}
@end
var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey",
CPOutlineViewOutlineTableColumnKey = @"CPOutlineViewOutlineTableColumnKey",
CPOutlineViewDataSourceKey = @"CPOutlineViewDataSourceKey",
@@ -2873,8 +2330,6 @@ var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey"
[super setDelegate:self];
[self _updateIsViewBased];
_animatingDisclosureRows = [CPIndexSet indexSet];
}
return self;
@@ -3076,8 +2531,6 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
selector:@selector(outlineViewSelectionDidChange:)
name:CPOutlineViewSelectionDidChangeNotification
object:aSource];
return self;
}
+ (void)unbind:(CPString)aBinding forObject:(id)anObject
+3 -1
View File
@@ -45,7 +45,6 @@ CPURLPboardType = @"CPURLPboardType";
CPImagesPboardType = @"CPImagesPboardType";
CPVideosPboardType = @"CPVideosPboardType";
CPRTFPboardType = @"CPRTFPboardType";
CPRulerPboardType = @"CPRulerPboardType";
_CPSmartPboardType = @"_CPSmartPboardType";
_CPASPboardType = @"_CPASPboardType";
@@ -324,6 +323,8 @@ var CPPasteboards = nil;
@end
#if PLATFORM(DOM)
var DOMDataTransferPasteboard = nil;
@implementation _CPDOMDataTransferPasteboard : CPPasteboard
@@ -387,3 +388,4 @@ var DOMDataTransferPasteboard = nil;
@end
#endif
-8
View File
@@ -79,8 +79,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
if (self)
{
[self setBezelStyle:CPRoundedBezelStyle];
[self selectItemAtIndex:CPNotFound];
_preferredEdge = CPMaxYEdge;
@@ -691,12 +689,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
// FIXME: setFont: should set the font on the menu.
[menu setFont:[self font]];
// Propagate the HUD theme state to the menu
if ([self hasThemeState:CPThemeStateHUD])
[menu setThemeState:CPThemeStateHUD];
else
[menu unsetThemeState:CPThemeStateHUD];
if ([self pullsDown])
{
var positionedItem = nil,
+33 -121
View File
@@ -43,6 +43,7 @@ CPProgressIndicatorSpinningStyle = 1;
*/
CPProgressIndicatorHUDBarStyle = 2;
var CPProgressIndicatorSpinningStyleColors = [];
/*!
@ingroup appkit
@@ -70,43 +71,6 @@ CPProgressIndicatorHUDBarStyle = 2;
BOOL _isDisplayedWhenStopped;
}
// Inject CSS Keyframes for spinning animation (Standard + WebKit)
+ (void)initialize
{
if (self !== [CPProgressIndicator class])
return;
if (CPDOMAvailable)
{
if (document.getElementById("cp-progress-indicator-style"))
return;
var style = document.createElement("style");
style.id = "cp-progress-indicator-style";
style.type = "text/css";
// We define two animations:
// 1. cp-progress-indicator-spin: Rotates 360 degrees (for spinners)
// 2. cp-progress-indicator-bar-slide: Moves background-position (for striped bars)
var css =
"@keyframes cp-progress-indicator-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } " +
"@-webkit-keyframes cp-progress-indicator-spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } } " +
"@keyframes cp-progress-indicator-bar-slide { 0% { background-position: 0 0; } 100% { background-position: 30px 0; } } " +
"@-webkit-keyframes cp-progress-indicator-bar-slide { 0% { background-position: 0 0; } 100% { background-position: 30px 0; } }";
if (style.styleSheet)
style.styleSheet.cssText = css;
else
style.appendChild(document.createTextNode(css));
var head = document.getElementsByTagName("head")[0];
if (head)
head.appendChild(style);
}
}
+ (CPString)defaultThemeClass
{
return @"progress-indicator";
@@ -119,19 +83,12 @@ CPProgressIndicatorHUDBarStyle = 2;
@"bar-color": [CPNull null],
@"default-height": 20,
@"bezel-color": [CPNull null],
// CSS Spinner Attributes
@"spinner-color": [CPColor grayColor],
@"spinner-track-color": [CPColor colorWithWhite:0.9 alpha:1.0],
@"spinner-line-width": 3.0,
@"circular-border-color": [CPNull null],
@"circular-border-size": 1,
@"circular-color": [CPNull null],
@"spinning-mini-gif": [CPNull null],
@"spinning-small-gif": [CPNull null],
@"spinning-regular-gif": [CPNull null]
@"spinning-regular-gif": [CPNull null],
@"circular-border-color": [CPNull null],
@"circular-border-size": 1,
@"circular-color": [CPNull null]
};
}
@@ -181,7 +138,6 @@ CPProgressIndicatorHUDBarStyle = 2;
_isAnimating = YES;
[self _hideOrDisplay];
[self setNeedsLayout]; // Trigger layout to update CSS animation state
}
/*!
@@ -193,7 +149,6 @@ CPProgressIndicatorHUDBarStyle = 2;
_isAnimating = NO;
[self _hideOrDisplay];
[self setNeedsLayout]; // Trigger layout to remove CSS animation state
}
/*!
@@ -278,7 +233,7 @@ CPProgressIndicatorHUDBarStyle = 2;
_controlSize = aControlSize;
[self setNeedsLayout];
[self updateBackgroundColor];
}
/*!
@@ -330,7 +285,7 @@ CPProgressIndicatorHUDBarStyle = 2;
_indeterminate = indeterminate;
[self setNeedsLayout];
[self updateBackgroundColor];
}
/*!
@@ -352,7 +307,9 @@ CPProgressIndicatorHUDBarStyle = 2;
_style = aStyle;
[self setNeedsLayout];
[self setTheme:(_style === CPProgressIndicatorHUDBarStyle) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
[self updateBackgroundColor];
}
/*!
@@ -361,14 +318,7 @@ CPProgressIndicatorHUDBarStyle = 2;
- (void)sizeToFit
{
if (_style == CPProgressIndicatorSpinningStyle)
{
var size = 32.0;
if (_controlSize === CPMiniControlSize) size = 16.0;
else if (_controlSize === CPSmallControlSize) size = 24.0;
else if (_controlSize === CPRegularControlSize) size = 32.0;
[self setFrameSize:CGSizeMake(size, size)];
}
[self setFrameSize:[[CPProgressIndicatorSpinningStyleColors[_controlSize] patternImage] size]];
else
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), [self valueForThemeAttribute:@"default-height"])];
}
@@ -431,7 +381,6 @@ CPProgressIndicatorHUDBarStyle = 2;
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
{
// Handle the standard Bar View
if (aViewName === @"bar-view" && _style !== CPProgressIndicatorSpinningStyle)
{
var width = CGRectGetWidth([self bounds]),
@@ -445,78 +394,41 @@ CPProgressIndicatorHUDBarStyle = 2;
return CGRectMake(0, 0, barWidth, [self valueForThemeAttribute:@"default-height"]);
}
// Handle the Spinning View (CSS Spinner)
if (aViewName === @"spinner-view" && _style == CPProgressIndicatorSpinningStyle && _indeterminate)
return nil;
}
/* @ignore */
- (void)updateBackgroundColor
{
if ([CPProgressIndicatorSpinningStyleColors count] === 0)
{
return [self bounds];
CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [self valueForThemeAttribute:@"spinning-mini-gif"];
CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [self valueForThemeAttribute:@"spinning-small-gif"];
CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [self valueForThemeAttribute:@"spinning-regular-gif"];
}
// Return nil for views that shouldn't appear in the current style
return nil;
[self setNeedsLayout];
}
- (void)layoutSubviews
{
if (YES)//_isBezeled)
{
// === SPINNING STYLE ===
if (_style == CPProgressIndicatorSpinningStyle)
{
// If indeterminate, use CSS spinner
if (_indeterminate)
{
var spinnerView = [self layoutEphemeralSubviewNamed:"spinner-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:nil];
// Ensure other views are hidden
[self layoutEphemeralSubviewNamed:"bar-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
if (!_indeterminate)
return;
// Configure CSS on the subview, NOT self._DOMElement, to avoid transform conflicts
var domEl = spinnerView._DOMElement,
spinnerColor = [self currentValueForThemeAttribute:@"spinner-color"],
trackColor = [self currentValueForThemeAttribute:@"spinner-track-color"],
lineWidth = [self currentValueForThemeAttribute:@"spinner-line-width"],
widthStr = lineWidth + "px";
// This will cause the bar view to go away due to having a nil rect when _style == CPProgressIndicatorSpinningStyle.
[self layoutEphemeralSubviewNamed:"bar-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
domEl.style.boxSizing = "border-box";
domEl.style.borderRadius = "50%";
domEl.style.borderStyle = "solid";
domEl.style.borderWidth = widthStr;
domEl.style.borderColor = [trackColor cssString];
domEl.style.borderTopColor = [spinnerColor cssString];
// Animation logic
if (_isAnimating)
{
var anim = "cp-progress-indicator-spin 1s linear infinite";
domEl.style.animation = anim;
domEl.style.WebkitAnimation = anim;
}
else
{
domEl.style.animation = "none";
domEl.style.WebkitAnimation = "none";
}
// Ensure main view is transparent
[self setBackgroundColor:nil];
}
else
{
// Determinate Spinner (Pie Chart drawn in drawRect)
// Hide CSS spinner
[self layoutEphemeralSubviewNamed:"spinner-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
[self setBackgroundColor:nil];
}
[self setBackgroundColor:CPProgressIndicatorSpinningStyleColors[_controlSize]];
}
// === BAR STYLE ===
else
{
// Hide spinner
[self layoutEphemeralSubviewNamed:"spinner-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
var barView = [self layoutEphemeralSubviewNamed:"bar-view"
@@ -535,8 +447,6 @@ CPProgressIndicatorHUDBarStyle = 2;
- (void)drawRect:(CGRect)aRect
{
// Handle determinate state for Spinning style (Pie chart progress) via CoreGraphics.
// If indeterminate, the CSS animation (spinner-view) takes over and we draw nothing.
if (_style == CPProgressIndicatorSpinningStyle && !_indeterminate)
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
@@ -602,7 +512,7 @@ CPProgressIndicatorHUDBarStyle = 2;
_isDisplayedWhenStoppedSet = [aCoder decodeObjectForKey:@"_isDisplayedWhenStoppedSet"];
_isDisplayedWhenStopped = [aCoder decodeObjectForKey:@"_isDisplayedWhenStopped"];
[self setNeedsLayout];
[self updateBackgroundColor];
}
return self;
@@ -610,6 +520,8 @@ CPProgressIndicatorHUDBarStyle = 2;
- (void)encodeWithCoder:(CPCoder)aCoder
{
// Don't encode the background colour. It can be recreated based on the flags
// and if encoded causes hardcoded image paths in the cib while just wasting space.
var backgroundColor = [self backgroundColor];
[self setBackgroundColor:nil];
[super encodeWithCoder:aCoder];
-10
View File
@@ -290,16 +290,6 @@ 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
{
}
+16 -30
View File
@@ -33,7 +33,6 @@
@import "_CPRuleEditorViewSliceRow.j"
@import "_CPRuleEditorLocalizer.j"
@import "CPCompatibility.j"
@class CPCompoundPredicate
@class CPComparisonPredicate
@@ -1958,63 +1957,57 @@ TODO: implement
- (CPArray)_backgroundColors
{
return [self currentValueForThemeAttribute:@"alternating-row-colors"];
return [self valueForThemeAttribute:@"alternating-row-colors"];
}
- (CPColor)_selectedRowColor
{
return [self currentValueForThemeAttribute:@"selected-color"];
return [self valueForThemeAttribute:@"selected-color"];
}
- (CPColor)_sliceTopBorderColor
{
return [self currentValueForThemeAttribute:@"slice-top-border-color"];
return [self valueForThemeAttribute:@"slice-top-border-color"];
}
- (CPColor)_sliceBottomBorderColor
{
return [self currentValueForThemeAttribute:@"slice-bottom-border-color"];
return [self valueForThemeAttribute:@"slice-bottom-border-color"];
}
- (CPColor)_sliceLastBottomBorderColor
{
return [self currentValueForThemeAttribute:@"slice-last-bottom-border-color"];
return [self valueForThemeAttribute:@"slice-last-bottom-border-color"];
}
- (CPFont)font
{
return [self currentValueForThemeAttribute:@"font"];
return [self valueForThemeAttribute:@"font"];
}
- (CPColor)_fontColor
{
return [self currentValueForThemeAttribute:@"font-color"];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[_slices makeObjectsPerformSelector:@selector(setThemeState:) withObject:aState];
return [self valueForThemeAttribute:@"font-color"];
}
- (CPImage)_imageAdd
{
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateNormal.and([self themeState])];
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateNormal];
}
- (CPImage)_imageAddHighlighted
{
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateHighlighted.and([self themeState])];
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateHighlighted];
}
- (CPImage)_imageRemove
{
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateNormal.and([self themeState])];
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateNormal];
}
- (CPImage)_imageRemoveHighlighted
{
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateHighlighted.and([self themeState])];
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateHighlighted];
}
- (CPVerticalTextAlignment)_verticalAlignment
@@ -2226,13 +2219,7 @@ TODO: implement
return;
var point = [self convertPoint:[event locationInWindow] fromView:nil],
index = FLOOR(MAX(0, point.y) / _sliceHeight);
// Check bounds before accessing the array to prevent CPRangeException
if (index >= [_slices count])
return;
var view = [_slices objectAtIndex:FLOOR(MAX(0, point.y) / _sliceHeight)];
view = [_slices objectAtIndex:FLOOR(MAX(0, point.y) / _sliceHeight)];
if ([self _dragShouldBeginFromMouseDown:view])
[self _performDragForSlice:view withEvent:event];
@@ -2261,11 +2248,10 @@ TODO: implement
firstSlice = [_slices objectAtIndex:firstIndex],
dragview = [[CPView alloc] initWithFrame:[firstSlice frame]];
if (CPDOMAvailable)
{
var html = firstSlice._DOMElement.innerHTML;
dragview._DOMElement.innerHTML = [html copy];
}
#if PLATFORM(DOM)
var html = firstSlice._DOMElement.innerHTML;
dragview._DOMElement.innerHTML = [html copy];
#endif
[dragview setBackgroundColor:[firstSlice backgroundColor]];
[dragview setAlphaValue:0.7];
+23 -67
View File
@@ -49,13 +49,6 @@
return self;
}
- (void)setRowIndex:(int)anIndex
{
_rowIndex = anIndex;
[self _updateBackgroundColor];
[self setNeedsDisplay:YES];
}
- (void)_setSelected:(BOOL)select
{
if (select == _selected)
@@ -64,47 +57,6 @@
var selector = select ? @selector(setThemeState:) : @selector(unsetThemeState:);
[[self subviews] makeObjectsPerformSelector:selector withObject:CPThemeStateSelectedDataView];
_selected = select;
[self _updateBackgroundColor];
[self setNeedsDisplay:YES];
}
- (void)_updateBackgroundColor
{
var color = nil;
if ([self _isSelected])
{
color = [_ruleEditor _selectedRowColor];
}
else
{
var colors = [_ruleEditor _backgroundColors],
count = [colors count];
if (count > 0)
color = [colors objectAtIndex:(_rowIndex % count)];
}
[self setBackgroundColor:color];
}
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
[self _updateBackgroundColor];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[self _updateBackgroundColor];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self _updateBackgroundColor];
}
- (void)drawRect:(CGRect)rect
@@ -114,31 +66,35 @@
maxX = CGRectGetWidth(bounds),
maxY = CGRectGetHeight(bounds);
// Note: Background is now handled by setBackgroundColor in _updateBackgroundColor
// to support CSS-based colors and transparency correctly.
// Draw background
if ([self _isSelected])
_backgroundColor = [_ruleEditor _selectedRowColor];
else
{
var colors = [_ruleEditor _backgroundColors],
count = [colors count];
_backgroundColor = [colors objectAtIndex:(_rowIndex % count)];
}
CGContextSetFillColor(context, _backgroundColor);
CGContextFillRect(context, rect);
// Draw Top Border
var topColor = [_ruleEditor _sliceTopBorderColor];
if (topColor)
{
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, maxX, 0);
CGContextSetStrokeColor(context, topColor);
CGContextStrokePath(context);
}
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, maxX, 0);
CGContextSetStrokeColor(context, [_ruleEditor _sliceTopBorderColor]);
CGContextStrokePath(context);
// Draw Bottom Border
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, maxY);
CGContextAddLineToPoint(context, maxX, maxY);
var bottomColor = (_rowIndex == [_ruleEditor _lastRow]) ? [_ruleEditor _sliceLastBottomBorderColor] : [_ruleEditor _sliceBottomBorderColor];
if (bottomColor)
{
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, maxY);
CGContextAddLineToPoint(context, maxX, maxY);
CGContextSetStrokeColor(context, bottomColor);
CGContextStrokePath(context);
}
CGContextSetStrokeColor(context, bottomColor);
CGContextStrokePath(context);
}
- (void)mouseDown:(CPEvent)theEvent
@@ -67,23 +67,6 @@
[self setAutoresizingMask:CPViewWidthSizable];
}
- (void)_setSelected:(BOOL)isSelected
{
[super _setSelected:isSelected];
[self _updateButtonImages];
}
- (void)_updateButtonImages
{
var rowState = [self themeState];
if ([self _isSelected])
rowState = rowState.and(CPThemeStateSelected);
[_addButton setImage:[_ruleEditor valueForThemeAttribute:@"add-image" inState:rowState]];
[_subtractButton setImage:[_ruleEditor valueForThemeAttribute:@"remove-image" inState:rowState]];
}
- (CPButton)_createRowButton
{
var button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
@@ -106,8 +89,6 @@
var button = [self _createRowButton];
[button setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
// Initial setup, _updateButtonImages will be called later to set correct state-based images
[button setValue:[_ruleEditor _imageAdd] forThemeAttribute:@"image" inState:CPThemeStateNormal];
[button setValue:[_ruleEditor _imageAddHighlighted] forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
@@ -122,8 +103,6 @@
var button = [self _createRowButton];
[button setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
// Initial setup
[button setValue:[_ruleEditor _imageRemove] forThemeAttribute:@"image" inState:CPThemeStateNormal];
[button setValue:[_ruleEditor _imageRemoveHighlighted] forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
@@ -546,23 +525,9 @@
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
[self _updateButtonImages];
[self layoutSubviews];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[self _updateButtonImages];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self _updateButtonImages];
}
- (void)_addObservers
{
if (_isObserving)
+4 -3
View File
@@ -42,10 +42,11 @@
*/
- (CGRect)visibleFrame
{
if (CPDOMAvailable)
return CGRectMake(window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight);
#if PLATFORM(DOM)
return CGRectMake(window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight);
#else
return CGRectMakeZero();
#endif
}
@end
+40 -278
View File
@@ -30,11 +30,11 @@
@import "CPScroller.j"
@import "CPView.j"
@import "CPRulerView.j"
@import "CPCompatibility.j"
@class CPTableView
@class CPRulerView
#define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden])
@protocol CPScrollViewDelegate <CPObject>
@@ -49,8 +49,7 @@
/*! @ignore */
var _isBrowserUsingOverlayScrollers = function()
{
if (!CPDOMAvailable) return NO;
#if PLATFORM(DOM)
/*
Even if the system supports overlay (Lion) scrollers,
the browser (e.g. FireFox *cough*) may not.
@@ -86,6 +85,9 @@ var _isBrowserUsingOverlayScrollers = function()
document.body.removeChild(outer);
return usingOverlayScrollers;
#else
return NO;
#endif
};
var TIMER_INTERVAL = 0.2,
@@ -101,21 +103,6 @@ var CPScrollViewWillStartLiveScrollNotification = @"CPScrollViewWillStartLiveScr
var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
CPScrollerStyleGlobalChangeNotification = @"CPScrollerStyleGlobalChangeNotification";
var CPScrollViewBorderSuffixes = @[@"no-border", @"line-border", @"bezel-border", @"groove-border"];
// _CPScrollViews will hold all created CPScrollView's in order to propagate changes
// of scroller global style
var _CPScrollViews;
/*
The default smallest and largest magnifications. These are not arbitrary: they are the
defaults that Apple documents for NSScrollView's minMagnification and maxMagnification, so a
scroll view behaves as in Cocoa unless its limits are set. They apply to a scroll view made in
code, and to one decoded from an archive that has no valid limits.
*/
var CPScrollViewDefaultMinMagnification = 0.25,
CPScrollViewDefaultMaxMagnification = 4.0;
/*!
@ingroup appkit
@class CPScrollView
@@ -163,11 +150,6 @@ var CPScrollViewDefaultMinMagnification = 0.25,
CPRulerView _verticalRuler;
CPRulerView _horizontalRuler;
// Magnification Support
BOOL _allowsMagnification;
float _minMagnification;
float _maxMagnification;
}
@@ -185,8 +167,6 @@ var CPScrollViewDefaultMinMagnification = 0.25,
CPScrollerStyleGlobal = _isBrowserUsingOverlayScrollers() ? CPScrollerStyleOverlay : CPScrollerStyleLegacy
else
CPScrollerStyleGlobal = globalValue;
_CPScrollViews = @[];
}
+ (CPString)defaultThemeClass
@@ -198,15 +178,7 @@ var CPScrollViewDefaultMinMagnification = 0.25,
{
return @{
@"bottom-corner-color": [CPColor whiteColor],
@"border-color": [CPColor blackColor],
@"content-inset-no-border": CGInsetMake(0, 0, 0, 0),
@"content-inset-line-border": CGInsetMake(1, 1, 1, 1),
@"content-inset-bezel-border": CGInsetMake(1, 1, 1, 1),
@"content-inset-groove-border": CGInsetMake(2, 2, 2, 2),
@"background-color-no-border": [CPNull null],
@"background-color-line-border": [CPNull null],
@"background-color-bezel-border": [CPNull null],
@"background-color-groove-border": [CPNull null]
@"border-color": [CPColor blackColor]
};
}
@@ -235,7 +207,7 @@ var CPScrollViewDefaultMinMagnification = 0.25,
bounds.size.height -= [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
if (verticalScrollerClass)
bounds.size.width -= [verticalScrollerClass scrollerWidthInStyle:scrollerStyle];
bounds.size.width -= [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
return bounds.size;
}
@@ -264,46 +236,30 @@ var CPScrollViewDefaultMinMagnification = 0.25,
heightInset = contentSize.height - bounds.size.height,
frameSize = CGSizeMake(contentSize.width + widthInset, contentSize.height + heightInset);
if (horizontalScrollerClass)
if (hFlag)
frameSize.height += [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
if (verticalScrollerClass)
frameSize.width += [verticalScrollerClass scrollerWidthInStyle:scrollerStyle];
if (vFlag)
frameSize.width += [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
return frameSize;
}
+ (CGRect)_insetBounds:(CGRect)bounds borderType:(CPBorderType)borderType
{
// First, we have to check if we are compiling a theme or running an application because if working on a theme,
// we can't use theme attributes to determine the inset ! This would be a kind of circular reference...
var compilingATheme = [[[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPApplicationDelegateClass"] isEqualToString:@"BKShowcaseController"];
if (compilingATheme)
return bounds;
var contentInset = [[CPTheme defaultTheme] valueForAttributeWithName:@"content-inset-"+CPScrollViewBorderSuffixes[borderType] forClass:CPScrollView];
// As this is a class method, we don't have object attributes, so if the theme doesn't declare content insets, we don't automatically get default value.
// Get it by hand.
if (!contentInset)
contentInset = [[self themeAttributes] objectForKey:@"content-inset-"+CPScrollViewBorderSuffixes[borderType]];
switch (borderType)
{
case CPNoBorder:
case CPLineBorder:
case CPBezelBorder:
return CGRectInsetByInset(bounds, contentInset);
return CGRectInset(bounds, 1.0, 1.0);
case CPGrooveBorder:
// FIXME: Do something better with this
bounds = CGRectInsetByInset(bounds, contentInset);
bounds = CGRectInset(bounds, 2.0, 2.0);
++bounds.origin.y;
--bounds.size.height;
return bounds;
case CPNoBorder:
default:
return bounds;
}
@@ -325,10 +281,7 @@ var CPScrollViewDefaultMinMagnification = 0.25,
+ (void)setGlobalScrollerStyle:(CPScrollerStyle)aStyle
{
CPScrollerStyleGlobal = aStyle;
// We propagate the new scroller global style to all existing CPScrollView's
for (var i = 0, count = [_CPScrollViews count]; i < count; i++)
[_CPScrollViews[i] setScrollerStyle:CPScrollerStyleGlobal];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollerStyleGlobalChangeNotification object:nil];
}
@@ -367,15 +320,9 @@ var CPScrollViewDefaultMinMagnification = 0.25,
_hasHorizontalRuler = NO;
_rulersVisible = NO;
_allowsMagnification = NO;
_minMagnification = CPScrollViewDefaultMinMagnification;
_maxMagnification = CPScrollViewDefaultMaxMagnification;
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
[_CPScrollViews addObject:self];
}
return self;
@@ -861,118 +808,6 @@ Notifies the delegate when the scroll view has finished scrolling.
}
// MARK: -
// MARK: Magnification
/*!
Returns \c YES if the user may change the magnification. The default is \c NO. As in Cocoa,
-setMagnification: and -setMagnification:centeredAtPoint: change it in code either way.
*/
- (BOOL)allowsMagnification
{
return _allowsMagnification;
}
/*!
Sets whether the user may change the magnification.
*/
- (void)setAllowsMagnification:(BOOL)shouldAllowMagnification
{
_allowsMagnification = !!shouldAllowMagnification;
}
/*!
Returns the smallest magnification. The default is 0.25, as in Cocoa.
*/
- (float)minMagnification
{
return _minMagnification;
}
/*!
Sets the smallest magnification.
*/
- (void)setMinMagnification:(float)aMagnification
{
_minMagnification = aMagnification;
}
/*!
Returns the largest magnification. The default is 4.0, as in Cocoa.
*/
- (float)maxMagnification
{
return _maxMagnification;
}
/*!
Sets the largest magnification.
*/
- (void)setMaxMagnification:(float)aMagnification
{
_maxMagnification = aMagnification;
}
/*!
Returns the magnification of the document view: 1.0 at actual size, 2.0 at twice the size.
*/
- (float)magnification
{
var boundsWidth = CGRectGetWidth([_contentView bounds]);
return boundsWidth ? CGRectGetWidth([_contentView frame]) / boundsWidth : 1.0;
}
/*!
Sets the magnification, keeping the centre of the visible part of the document in place.
@param aMagnification the magnification, limited to the minimum and maximum magnification
*/
- (void)setMagnification:(float)aMagnification
{
var visibleRect = [_contentView bounds];
[self setMagnification:aMagnification centeredAtPoint:CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect))];
}
/*!
Sets the magnification, and scrolls so that aPoint is at the centre of the visible area. As
in Cocoa, the clip view's bounds are scaled: at a magnification of 2.0 its bounds are half its
frame, so the document view is drawn at twice its size. A document view that resizes its width
with the clip view, as a text view that wraps to the window does, takes the new bounds width.
@param aMagnification the magnification, limited to the minimum and maximum magnification
@param aPoint the point in the clip view's bounds, which are the document view's coordinates,
to show at the centre
*/
- (void)setMagnification:(float)aMagnification centeredAtPoint:(CGPoint)aPoint
{
var magnification = MIN(MAX(aMagnification, _minMagnification), _maxMagnification),
frameSize = [_contentView frameSize];
/*
If the limits are not numbers, the clamp above gives NaN. The clip view's bounds would
then become NaN, which the browser ignores, and -magnification would still report 1.0,
so the zoom would silently do nothing. This happened with scroll views loaded from cibs
whose limits were never set; see -initWithCoder: and -encodeWithCoder:. A magnification
that is not a positive number is therefore ignored.
*/
if (!(magnification > 0.0) || !frameSize.width || !frameSize.height)
return;
var boundsSize = CGSizeMake(frameSize.width / magnification, frameSize.height / magnification),
documentView = [self documentView];
[_contentView setBoundsSize:boundsSize];
// The clip view applies its scale to its element when it is marked for display.
[_contentView setNeedsDisplay:YES];
if ([documentView autoresizingMask] & CPViewWidthSizable)
[documentView setFrameSize:CGSizeMake(boundsSize.width, [documentView frameSize].height)];
[_contentView scrollToPoint:CGPointMake(aPoint.x - boundsSize.width / 2.0, aPoint.y - boundsSize.height / 2.0)];
[self reflectScrolledClipView:_contentView];
}
// MARK: -
// MARK: Rulers
@@ -1045,10 +880,7 @@ Notifies the delegate when the scroll view has finished scrolling.
_horizontalRuler = aRulerView;
if (_horizontalRuler)
{
[_horizontalRuler setScrollView:self];
[self addSubview:_horizontalRuler];
}
[self tile];
}
@@ -1067,10 +899,7 @@ Notifies the delegate when the scroll view has finished scrolling.
_verticalRuler = aRulerView;
if (_verticalRuler)
{
[_verticalRuler setScrollView:self];
[self addSubview:_verticalRuler];
}
[self tile];
}
@@ -1171,7 +1000,7 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_cornerView)
{
[_cornerView setHidden:![self _shouldShowCornerView]];
[_cornerView setHidden:!SHOULD_SHOW_CORNER_VIEW()];
[self addSubview:_cornerView];
}
}
@@ -1214,7 +1043,7 @@ Notifies the delegate when the scroll view has finished scrolling.
frame.size.height = CGRectGetHeight([headerView frame]);
if ([self _shouldShowCornerView])
if (SHOULD_SHOW_CORNER_VIEW())
frame.size.width -= CGRectGetWidth([self _cornerViewFrame]);
return frame;
@@ -1453,8 +1282,8 @@ Notifies the delegate when the scroll view has finished scrolling.
var showHorizontalRuler = _rulersVisible && _hasHorizontalRuler && _horizontalRuler,
showVerticalRuler = _rulersVisible && _hasVerticalRuler && _verticalRuler;
var horizRulerThickness = showHorizontalRuler ? [_horizontalRuler requiredThickness] : 0.0,
vertRulerThickness = showVerticalRuler ? [_verticalRuler requiredThickness] : 0.0;
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;
if (showHorizontalRuler)
{
@@ -1577,7 +1406,7 @@ Notifies the delegate when the scroll view has finished scrolling.
[_headerClipView setFrame:[self _headerClipViewFrame]];
[[_headerClipView documentView] setNeedsDisplay:YES];
if ([self _shouldShowCornerView])
if (SHOULD_SHOW_CORNER_VIEW())
{
[_cornerView setFrame:[self _cornerViewFrame]];
[_cornerView setHidden:NO];
@@ -1587,9 +1416,7 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_scrollerStyle === CPScrollerStyleLegacy)
{
var bottomCornerFrame = [self _bottomCornerViewFrame];
[[self bottomCornerView] setFrame:bottomCornerFrame];
[[self bottomCornerView] setHidden:CGRectIsEmpty(bottomCornerFrame)];
[[self bottomCornerView] setFrame:[self _bottomCornerViewFrame]];
[[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]];
}
@@ -1603,7 +1430,10 @@ Notifies the delegate when the scroll view has finished scrolling.
horizRulerThickness
)];
[_horizontalRuler setNeedsDisplay:YES];
if ([_horizontalRuler respondsToSelector:@selector(updateRuler)])
[_horizontalRuler updateRuler];
else
[_horizontalRuler setNeedsDisplay:YES];
}
if (showVerticalRuler)
@@ -1615,7 +1445,10 @@ Notifies the delegate when the scroll view has finished scrolling.
CGRectGetHeight(contentFrame)
)];
[_verticalRuler setNeedsDisplay:YES];
if ([_verticalRuler respondsToSelector:@selector(updateRuler)])
[_verticalRuler updateRuler];
else
[_verticalRuler setNeedsDisplay:YES];
}
--_recursionCount;
@@ -1661,6 +1494,7 @@ Notifies the delegate when the scroll view has finished scrolling.
// MARK: -
// MARK: Overrides
- (void)_removeObservers
{
if (!_isObserving)
@@ -1678,6 +1512,9 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_isObserving)
return;
//Make sure to have the last global style for the scroller
[self _didReceiveDefaultStyleChange:nil];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_didReceiveDefaultStyleChange:)
name:CPScrollerStyleGlobalChangeNotification
@@ -1686,11 +1523,13 @@ Notifies the delegate when the scroll view has finished scrolling.
[super _addObservers];
}
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if ([self isCSSBased] || (_borderType == CPNoBorder))
if (_borderType == CPNoBorder)
return;
var strokeRect = [self bounds],
@@ -1891,45 +1730,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: -
@implementation CPScrollView (CSSTheming)
- (void)layoutSubviews
{
if (![self isCSSBased])
return;
if (_borderType !== CPNoBorder)
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color-"+CPScrollViewBorderSuffixes[_borderType]]];
if (_scrollerStyle === CPScrollerStyleLegacy)
[[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]];
}
- (BOOL)isCSSBased
{
return [[self theme] isCSSBased];
}
- (void)refreshDisplay
{
if ([self isCSSBased])
[self setNeedsLayout:YES];
else
[self setNeedsDisplay:YES];
}
@end
// pragma mark -
@implementation CPScrollView (FirstResponder)
// Those 4 next methods are needed to (un)set CPThemeStateFirstResponder based on content view
@@ -1996,10 +1800,7 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
CPScrollViewHasHRulerKey = @"CPScrollViewHasHRuler",
CPScrollViewRulersVisibleKey = @"CPScrollViewRulersVisible",
CPScrollViewVRulerKey = @"CPScrollViewVRuler",
CPScrollViewHRulerKey = @"CPScrollViewHRuler",
CPScrollViewAllowsMagnificationKey = @"CPScrollViewAllowsMagnification",
CPScrollViewMinMagnificationKey = @"CPScrollViewMinMagnification",
CPScrollViewMaxMagnificationKey = @"CPScrollViewMaxMagnification";
CPScrollViewHRulerKey = @"CPScrollViewHRuler";
@implementation CPScrollView (CPCoding)
@@ -2034,26 +1835,6 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
_cornerView = [aCoder decodeObjectForKey:CPScrollViewCornerViewKey];
_bottomCornerView = [aCoder decodeObjectForKey:CPScrollViewBottomCornerViewKey];
// Magnification decoding
_allowsMagnification = [aCoder decodeBoolForKey:CPScrollViewAllowsMagnificationKey];
_minMagnification = [aCoder decodeFloatForKey:CPScrollViewMinMagnificationKey];
_maxMagnification = [aCoder decodeFloatForKey:CPScrollViewMaxMagnificationKey];
/*
Cibs compiled by nib2cib can hold limits that are not numbers. nib2cib builds a
scroll view from a xib through NSScrollView's -NS_initWithCoder:, which does not run
-initWithFrame:, so the limits are never set; it then archives the scroll view with
-encodeWithCoder: below, which wrote the unset limits into the cib. nib2cib belongs
to the legacy toolchain, which is frozen, so the problem is handled here: a limit
that is missing or not a positive number gets the Cocoa default. Without this, a
scroll view loaded from such a cib could not change its magnification.
*/
if (!(_minMagnification > 0.0))
_minMagnification = CPScrollViewDefaultMinMagnification;
if (!(_maxMagnification > 0.0))
_maxMagnification = CPScrollViewDefaultMaxMagnification;
// Ruler decoding
_hasVerticalRuler = [aCoder decodeBoolForKey:CPScrollViewHasVRulerKey];
_hasHorizontalRuler = [aCoder decodeBoolForKey:CPScrollViewHasHRulerKey];
@@ -2062,10 +1843,6 @@ 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;
@@ -2073,7 +1850,10 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
_scrollerStyle = [aCoder decodeObjectForKey:CPScrollViewScrollerStyleKey] || CPScrollerStyleGlobal;
_scrollerKnobStyle = [aCoder decodeObjectForKey:CPScrollViewScrollerKnobStyleKey] || CPScrollerKnobStyleDefault;
[_CPScrollViews addObject:self];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_didReceiveDefaultStyleChange:)
name:CPScrollerStyleGlobalChangeNotification
object:nil];
}
return self;
@@ -2117,23 +1897,6 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
[aCoder encodeInt:_scrollerStyle forKey:CPScrollViewScrollerStyleKey];
[aCoder encodeInt:_scrollerKnobStyle forKey:CPScrollViewScrollerKnobStyleKey];
// Magnification encoding
[aCoder encodeBool:_allowsMagnification forKey:CPScrollViewAllowsMagnificationKey];
/*
nib2cib archives scroll views with this method, after building them from a xib through
NSScrollView's -NS_initWithCoder:, which does not run -initWithFrame:. Their limits are
then unset, and writing them put values that are not numbers into the cib. nib2cib
belongs to the legacy toolchain, which is frozen, so the problem is handled here: only
limits that are positive numbers are written, and -initWithCoder: supplies the defaults
for the rest.
*/
if (_minMagnification > 0.0)
[aCoder encodeFloat:_minMagnification forKey:CPScrollViewMinMagnificationKey];
if (_maxMagnification > 0.0)
[aCoder encodeFloat:_maxMagnification forKey:CPScrollViewMaxMagnificationKey];
// Ruler encoding
[aCoder encodeBool:_hasVerticalRuler forKey:CPScrollViewHasVRulerKey];
[aCoder encodeBool:_hasHorizontalRuler forKey:CPScrollViewHasHRulerKey];
@@ -2143,5 +1906,4 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
[aCoder encodeObject:_horizontalRuler forKey:CPScrollViewHRulerKey];
}
@end
+4 -21
View File
@@ -23,11 +23,12 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPAnimation.j"
@import "CPControl.j"
@import "CPViewAnimation.j"
@import "CPWindow_Constants.j"
@import "CPViewAnimation.j"
@global CPApp
@@ -258,7 +259,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
*/
- (void)setKnobProportion:(float)aProportion
{
if (!CPIsNumeric(aProportion))
if (!_IS_NUMERIC(aProportion))
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric, was: "+aProportion];
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
@@ -320,25 +321,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
if (![self hasThemeState:CPThemeStateSelected] && ![self hasThemeState:CPThemeStateScrollViewLegacy])
return CPScrollerNoPart;
// Fetch the visual rect for the knob
var bounds = [self bounds],
knobRect = [self rectForPart:CPScrollerKnob],
hitKnobRect = CGRectMake(CGRectGetMinX(knobRect), CGRectGetMinY(knobRect), CGRectGetWidth(knobRect), CGRectGetHeight(knobRect));
// Expand the hit test rect to span the entire track on the minor axis
// so dragging works smoothly even if the user clicks slightly off-center.
if ([self isVertical])
{
hitKnobRect.origin.x = 0;
hitKnobRect.size.width = CGRectGetWidth(bounds);
}
else
{
hitKnobRect.origin.y = 0;
hitKnobRect.size.height = CGRectGetHeight(bounds);
}
if (CGRectContainsPoint(hitKnobRect, aPoint))
if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
return CPScrollerKnob;
if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementPage], aPoint))
+1 -9
View File
@@ -104,8 +104,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
_sendsWholeSearchString = NO;
_sendsSearchStringImmediately = NO;
_recentsAutosaveName = nil;
[self setPlaceholderString:@"Search"];
[self _init];
}
@@ -124,14 +122,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[self setContinuous:YES];
var bounds = [self bounds],
cancelButton = nil,
searchButton = nil;
if (CPDOMAvailable)
{
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]];
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]],
searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
}
[self setCancelButton:cancelButton];
[self resetCancelButton];
+149 -279
View File
@@ -71,7 +71,7 @@ CPSegmentSwitchTrackingMomentary = 2;
@"right-segment-bezel-color": [CPNull null],
@"center-segment-bezel-color": [CPNull null],
@"divider-bezel-color": [CPNull null],
@"divider-thickness": 1.0
@"divider-thickness": 1.0,
};
}
@@ -316,7 +316,7 @@ CPSegmentSwitchTrackingMomentary = 2;
// Working with Individual Segments
/*!
Sets the width of the specified segment.
@param aWidth the new width for the segment (0 for automatic width)
@param aWidth the new width for the segment
@param aSegment the segment to set the width for
@throws CPRangeException if \c aSegment is out of bounds
*/
@@ -420,14 +420,7 @@ CPSegmentSwitchTrackingMomentary = 2;
[segment setSelected:isSelected];
if (_themeStates[aSegment] == undefined)
_themeStates[aSegment] = CPThemeStateNormal;
// Update state using .and() / .without() to preserve existing states (like Disabled)
if (isSelected)
_themeStates[aSegment] = _themeStates[aSegment].and(CPThemeStateSelected);
else
_themeStates[aSegment] = _themeStates[aSegment].without(CPThemeStateSelected);
_themeStates[aSegment] = isSelected ? CPThemeStateSelected : CPThemeStateNormal;
// We need to do some cleanup if we only allow one selection.
if (isSelected)
@@ -439,10 +432,7 @@ CPSegmentSwitchTrackingMomentary = 2;
if (_trackingMode == CPSegmentSwitchTrackingSelectOne && oldSelectedSegment != aSegment && oldSelectedSegment != -1 && oldSelectedSegment < _segments.length)
{
[_segments[oldSelectedSegment] setSelected:NO];
// Update old segment using .without() to preserve other states
// Previously: _themeStates[oldSelectedSegment] = CPThemeStateNormal;
_themeStates[oldSelectedSegment] = _themeStates[oldSelectedSegment].without(CPThemeStateSelected);
_themeStates[oldSelectedSegment] = CPThemeStateNormal;
[self drawSegmentBezel:oldSelectedSegment highlight:NO];
}
@@ -540,24 +530,12 @@ CPSegmentSwitchTrackingMomentary = 2;
- (float)_leftOffsetForSegment:(unsigned)segment
{
if ([[self actualTheme] isCSSBased])
{
// CSS styling
if (segment == 0)
return 0;
if (segment == 0)
return [self currentValueForThemeAttribute:@"bezel-inset"].left;
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) - 2; // FIXME: -2 for collapse of borders
}
else
{
// Legacy styling
if (segment == 0)
return [self currentValueForThemeAttribute:@"bezel-inset"].left;
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) + thickness;
}
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) + thickness;
}
- (unsigned)_indexOfLastSegment
@@ -591,28 +569,19 @@ CPSegmentSwitchTrackingMomentary = 2;
}
else if (aName.indexOf("segment-bezel") === 0)
{
if ([[self actualTheme] isCSSBased])
{
var segment = parseInt(aName.substring("segment-bezel-".length), 10);
return [self bezelFrameForSegment:segment];
}
else
{
var segment = parseInt(aName.substring("segment-bezel-".length), 10),
var segment = parseInt(aName.substring("segment-bezel-".length), 10),
frame = CGRectCreateCopy([self frameForSegment:segment]);
if (segment === 0)
{
frame.origin.x += contentInset.left;
frame.size.width -= contentInset.left;
}
if (segment === [self segmentCount] - 1)
frame.size.width = CGRectGetWidth([self bounds]) - contentInset.right - frame.origin.x;
return frame;
if (segment === 0)
{
frame.origin.x += contentInset.left;
frame.size.width -= contentInset.left;
}
if (segment === [self segmentCount] - 1)
frame.size.width = CGRectGetWidth([self bounds]) - contentInset.right - frame.origin.x;
return frame;
}
else if (aName.indexOf("divider-bezel") === 0)
{
@@ -651,86 +620,48 @@ CPSegmentSwitchTrackingMomentary = 2;
if ([self segmentCount] <= 0)
return;
// Check for HUD state globally
var isHUD = [self hasThemeState:CPThemeStateHUD];
var themeState = _themeStates[0],
isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini];
if ([[self actualTheme] isCSSBased])
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
var leftCapColor = [self valueForThemeAttribute:@"left-segment-bezel-color"
inState:themeState],
leftBezelView = [self layoutEphemeralSubviewNamed:@"left-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[leftBezelView setBackgroundColor:leftCapColor];
var themeState = _themeStates[_themeStates.length - 1];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
var rightCapColor = [self valueForThemeAttribute:@"right-segment-bezel-color"
inState:themeState],
rightBezelView = [self layoutEphemeralSubviewNamed:@"right-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[rightBezelView setBackgroundColor:rightCapColor];
for (var i = 0, count = _themeStates.length; i < count; i++)
{
// CSS Styling
var isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini],
isKeyWindow = [self hasThemeState:CPThemeStateKeyWindow];
for (var i = 0, count = _themeStates.length; i < count; i++)
{
var themeState = _themeStates[i];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
// Apply HUD state to lookup
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
themeState = isKeyWindow ? themeState.and(CPThemeStateKeyWindow) : themeState;
var bezelColor,
segment = _segments[i],
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:"segment-bezel-" + i];
if (i == 0)
bezelColor = [self valueForThemeAttribute:@"left-segment-bezel-color" inState:themeState];
else if (i < count - 1)
bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color" inState:themeState];
else
bezelColor = [self valueForThemeAttribute:@"right-segment-bezel-color" inState:themeState];
[bezelView setBackgroundColor:bezelColor];
// Trick : Put selected segments over unselected ones to automaticaly manage borders
if (CPDOMAvailable)
{
contentView._DOMElement.style.zIndex = ([segment selected] ? 1 : 0);
bezelView._DOMElement.style.zIndex = ([segment selected] ? 1 : 0);
}
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:([segment enabled] ? themeState : themeState.and(CPThemeStateDisabled))]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
}
}
else
{
// Legacy (Canvas) Styling
var themeState = _themeStates[0],
isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini];
var themeState = _themeStates[i];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
@@ -739,114 +670,59 @@ CPSegmentSwitchTrackingMomentary = 2;
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Left Cap
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
var bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color"
inState:themeState],
var leftCapColor = [self valueForThemeAttribute:@"left-segment-bezel-color"
inState:themeState],
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
leftBezelView = [self layoutEphemeralSubviewNamed:@"left-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:bezelColor];
[leftBezelView setBackgroundColor:leftCapColor];
// layout image/title views
var segment = _segments[i],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
var themeState = _themeStates[_themeStates.length - 1];
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:themeState]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
if (i == count - 1)
continue;
var borderState = _themeStates[i].and(_themeStates[i + 1]);
borderState = isDisabled ? borderState.and(CPThemeStateDisabled) : borderState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
borderState = borderState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
borderState = borderState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Right Cap
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
var rightCapColor = [self valueForThemeAttribute:@"right-segment-bezel-color"
inState:themeState],
rightBezelView = [self layoutEphemeralSubviewNamed:@"right-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[rightBezelView setBackgroundColor:rightCapColor];
for (var i = 0, count = _themeStates.length; i < count; i++)
{
var themeState = _themeStates[i];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Center Segments
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
var bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color"
inState:themeState],
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:bezelColor];
// layout image/title views
var segment = _segments[i],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:themeState]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
if (i == count - 1)
continue;
var borderState = _themeStates[i].and(_themeStates[i + 1]);
borderState = isDisabled ? borderState.and(CPThemeStateDisabled) : borderState;
if (isControlSizeSmall)
borderState = borderState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
borderState = borderState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Dividers
if (isHUD)
borderState = borderState.and(CPThemeStateHUD);
var borderColor = [self valueForThemeAttribute:@"divider-bezel-color"
inState:borderState],
var borderColor = [self valueForThemeAttribute:@"divider-bezel-color"
inState:borderState],
borderView = [self layoutEphemeralSubviewNamed:"divider-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[borderView setBackgroundColor:borderColor];
}
[borderView setBackgroundColor:borderColor];
}
}
@@ -927,10 +803,8 @@ CPSegmentSwitchTrackingMomentary = 2;
label = [segment label],
image = [segment image];
contentInsetWidth += ([[self actualTheme] isCSSBased] && ((aSegment == 0) || (aSegment == [self _indexOfLastSegment])) ? [self valueForThemeAttribute:@"divider-thickness" inState:themeState] : 0);
// add 1 pixel to account for possible fractional pixels at right edge
width = CEIL((label ? [label sizeWithFont:[self font]].width + 1: 4.0) + (image ? [image size].width : 0) + contentInsetWidth);
width = (label ? [label sizeWithFont:[self font]].width + 1 : 4.0) + (image ? [image size].width : 0) + contentInsetWidth;
}
return CGRectMake(left, top, width, height);
@@ -938,14 +812,6 @@ CPSegmentSwitchTrackingMomentary = 2;
- (CGRect)contentFrameForSegment:(unsigned)aSegment
{
if ([[self actualTheme] isCSSBased])
{
var bezelFrame = [self bezelFrameForSegment:aSegment],
contentInset = [self currentValueForThemeAttribute:@"content-inset"];
return CGRectInsetByInset(bezelFrame, contentInset);
}
var height = [self currentValueForThemeAttribute:@"min-size"].height,
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
width = CGRectGetWidth([self frameForSegment:aSegment]),
@@ -1008,64 +874,68 @@ CPSegmentSwitchTrackingMomentary = 2;
var type = [anEvent type],
location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
if (type == CPLeftMouseUp)
switch (type)
{
if (_trackingSegment == -1)
case CPLeftMouseUp:
if (_trackingSegment === CPNotFound)
return;
if (_trackingSegment === [self testSegment:location])
{
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
{
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
// With ANY, _selectedSegment means last pressed.
_selectedSegment = _trackingSegment;
}
else
[self setSelected:YES forSegment:_trackingSegment];
[self sendAction:[self action] to:[self target]];
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
{
[self setSelected:NO forSegment:_trackingSegment];
_selectedSegment = CPNotFound;
}
}
[self drawSegmentBezel:_trackingSegment highlight:NO];
_trackingSegment = CPNotFound;
return;
if (_trackingSegment === [self testSegment:location])
{
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
case CPLeftMouseDown:
var trackingSegment = [self testSegment:location];
if (trackingSegment > CPNotFound && [self isEnabledForSegment:trackingSegment])
{
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
// With ANY, _selectedSegment means last pressed.
_selectedSegment = _trackingSegment;
_trackingHighlighted = YES;
_trackingSegment = trackingSegment;
[self drawSegmentBezel:_trackingSegment highlight:YES];
}
else
[self setSelected:YES forSegment:_trackingSegment];
[self sendAction:[self action] to:[self target]];
break;
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
case CPLeftMouseDragged:
if (_trackingSegment === CPNotFound)
return;
var highlighted = [self testSegment:location] === _trackingSegment;
if (highlighted != _trackingHighlighted)
{
[self setSelected:NO forSegment:_trackingSegment];
_trackingHighlighted = highlighted;
_selectedSegment = CPNotFound;
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
}
}
[self drawSegmentBezel:_trackingSegment highlight:NO];
_trackingSegment = -1;
return;
}
if (type == CPLeftMouseDown)
{
var trackingSegment = [self testSegment:location];
if (trackingSegment > -1 && [self isEnabledForSegment:trackingSegment])
{
_trackingHighlighted = YES;
_trackingSegment = trackingSegment;
[self drawSegmentBezel:_trackingSegment highlight:YES];
}
}
else if (type == CPLeftMouseDragged)
{
if (_trackingSegment == -1)
return;
var highlighted = [self testSegment:location] === _trackingSegment;
if (highlighted != _trackingHighlighted)
{
_trackingHighlighted = highlighted;
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
}
break;
}
[CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
+1 -1
View File
@@ -102,7 +102,6 @@ CPThemeStateShadowViewHeavy = CPThemeState("shadowview-style-heavy");
[self setWeight:CPLightShadow];
[self setHitTests:NO];
[self setClipsToBounds:NO];
}
return self;
@@ -170,6 +169,7 @@ CPThemeStateShadowViewHeavy = CPThemeState("shadowview-style-heavy");
- (void)layoutSubviews
{
[super layoutSubviews];
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
}
+3 -1
View File
@@ -1002,13 +1002,15 @@ var AFFINITY = 5;
}
}
#if PLATFORM(DOM)
// Don't do this in nib2cib !..
if (CPDOMAvailable && _allowsTickMarkValuesOnly)
if (_allowsTickMarkValuesOnly)
{
[self setObjectValue:[self closestTickMarkValueToValue:[self doubleValue]]];
_currentTickMarkSegment = _closestTickMarkIndex;
[self sendAction:[self action] to:[self target]];
}
#endif
}
/*! @ignore */
+41 -56
View File
@@ -23,11 +23,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPButtonBar.j"
@import "CPCursor.j"
@import "CPImage.j"
@import "CPTrackingArea.j"
@import "CPView.j"
@import "CPCursor.j"
@import "CPTrackingArea.j"
@class CPUserDefaults
@global CPApp
@@ -62,6 +64,28 @@ 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";
@@ -711,7 +735,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
*/
- (void)adjustSubviews
{
[self _maybePostWillResizeNotification];
SPLIT_VIEW_MAYBE_POST_WILL_RESIZE();
[self _postNotificationWillResize];
// 2 possibilities : we have, or not, enough flexible space to accomodate fixed size subviews
@@ -793,16 +817,13 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[self _distribute:remainingSpace amoung:fixedCount onFlexible:NO fromIndex:0 toIndex:nbSubviews-1];
}
[self _maybePostDidResizeNotification];
SPLIT_VIEW_MAYBE_POST_DID_RESIZE();
[self layoutSubviews];
}
- (void)layoutSubviews
{
// Retrieve the current color based on the current theme state (Standard vs HUD)
var currentDividerColor = [self dividerColor];
for (var i = 0, count = _arrangedSubviews.length, position = 0, origin; i < count; i++)
{
origin = CGPointMakeZero();
@@ -817,9 +838,6 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
origin[_originComponent] = position;
position += [_dividerSubviews[i] frame].size[_sizeComponent];
// Apply the color to the specific divider view
[_dividerSubviews[i] setBackgroundColor:currentDividerColor];
[_dividerSubviews[i] setFrameOrigin:origin];
}
}
@@ -1153,7 +1171,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
// Silently ignore bad positions which could result from odd delegate responses. We don't want these
// bad results to go into the system and cause havoc with frame sizes as the split view tries to resize
// its subviews.
if (CPIsNumeric(proposedPosition))
if (_IS_NUMERIC(proposedPosition))
position = proposedPosition;
var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex],
@@ -1163,10 +1181,10 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
proposedActualMin = [self _sendDelegateSplitViewConstrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex],
proposedActualMax = [self _sendDelegateSplitViewConstrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex];
if (CPIsNumeric(proposedActualMin))
if (_IS_NUMERIC(proposedActualMin))
actualMin = proposedActualMin;
if (CPIsNumeric(proposedActualMax))
if (_IS_NUMERIC(proposedActualMax))
actualMax = proposedActualMax;
var viewA = _arrangedSubviews[dividerIndex],
@@ -1195,7 +1213,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
// autosave.
_shouldRestoreFromAutosaveUnlessFrameSize = nil;
[self _setResizeNotificationsSuppressed:YES];
SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(YES);
var realPosition = [self _realPositionForPosition:position ofDividerAtIndex:dividerIndex],
viewA = _arrangedSubviews[dividerIndex],
@@ -1212,9 +1230,9 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
if (preSize !== frameA.size[_sizeComponent])
{
[self _maybePostWillResizeNotification];
SPLIT_VIEW_MAYBE_POST_WILL_RESIZE();
[_arrangedSubviews[dividerIndex] setFrame:frameA];
[self _maybePostDidResizeNotification];
SPLIT_VIEW_MAYBE_POST_DID_RESIZE();
}
preSize = frameB.size[_sizeComponent];
@@ -1229,9 +1247,9 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
if (preSize !== frameB.size[_sizeComponent] || preOrigin !== frameB.origin[_originComponent])
{
[self _maybePostWillResizeNotification];
SPLIT_VIEW_MAYBE_POST_WILL_RESIZE();
[_arrangedSubviews[dividerIndex + 1] setFrame:frameB];
[self _maybePostDidResizeNotification];
SPLIT_VIEW_MAYBE_POST_DID_RESIZE();
}
if (preCollapsePosition)
@@ -1244,10 +1262,10 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[self _updateRatios];
if ([self _didSuppressResizeNotifications])
if (SPLIT_VIEW_DID_SUPPRESS_RESIZE_NOTIFICATION())
[self _postNotificationDidResize];
[self _setResizeNotificationsSuppressed:NO];
SPLIT_VIEW_SUPPRESS_RESIZE_NOTIFICATIONS(NO);
}
/*!
@@ -1269,11 +1287,8 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
- (void)setButtonBar:(CPButtonBar)aButtonBar forDividerAtIndex:(CPUInteger)dividerIndex
{
// For compatibility with previous behavior
if ([aButtonBar respondsToSelector:@selector(setAutomaticResizeControl:)])
{
[aButtonBar setAutomaticResizeControl:YES];
[self attachButtonBar:aButtonBar];
}
[aButtonBar setAutomaticResizeControl:YES];
[self attachButtonBar:aButtonBar];
}
- (void)attachButtonBar:(CPButtonBar)aButtonBar
@@ -1470,36 +1485,6 @@ 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: -
@@ -1913,5 +1898,5 @@ var CPSplitViewDelegateKey = @"CPSplitViewDelegateKey",
[self setDividerStyle:(shouldBePaneSplitter ? CPSplitViewDividerStylePaneSplitter : CPSplitViewDividerStyleThin)];
}
@end
+19 -134
View File
@@ -20,80 +20,9 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* PLACEHOLDER IMPLEMENTATION — READ BEFORE USE OR MODIFICATION.
*
* This class lays out views by direct, procedural arithmetic. It has no
* constraint solver. It cannot compress, expand, or negotiate space among
* views the way NSStackView does; it only places views at their existing
* frame size, in order, separated by fixed spacing.
*
* Known, accepted limitations:
* - `distribution` is stored but has no effect on layout. Fill,
* FillEqually, FillProportionally, and EqualSpacing are unimplemented.
* - Center-gravity views are clamped against the Leading edge only; if
* Leading + Center + Trailing content overflows the container, Center
* views can overlap Trailing views instead of compressing.
* - `visibilityPriority:forView:` only supports the two extreme values
* (MustHold / NotVisible). Intermediate priorities are accepted but
* have no defined effect.
* - No guarantee is made of correctness beyond what a single manual
* test (Tests/Manual/CPStackViewTest) exercises: orientation, the
* three gravity areas, alignment switching, spacing, and hidden-view
* detachment. Insertion, removal, custom spacing, and the CPCoding
* archive path are implemented but not verified by that test.
*
* This exists to give AppKit a working CPStackView symbol now, not to be
* a durable design. It is expected to be replaced by a constraint-solver
* based implementation (Kiwi.js) when time permits. Do not build on its
* internal layout algorithm as if it were a stable foundation.
*/
#include "../Foundation/Foundation.h"
@import "CPView.j"
@import <Foundation/CPMapTable.j>
// MARK: -
// MARK: Minimal local type definitions
//
// These types support this file only. They are not shared with the rest
// of AppKit. A future constraint-solver based Auto Layout engine will
// replace them. Numeric values match the equivalent Cocoa constants
// (NSUserInterfaceLayoutOrientation, NSLayoutAttribute) so that a later,
// solver-based CPLayoutAttribute can reuse these numbers without a
// renumbering pass.
@typedef CPUserInterfaceLayoutOrientation
CPUserInterfaceLayoutOrientationHorizontal = 0;
CPUserInterfaceLayoutOrientationVertical = 1;
@typedef CPLayoutAttribute
CPLayoutAttributeLeft = 1;
CPLayoutAttributeRight = 2;
CPLayoutAttributeTop = 3;
CPLayoutAttributeBottom = 4;
CPLayoutAttributeLeading = 5;
CPLayoutAttributeTrailing = 6;
CPLayoutAttributeWidth = 7;
CPLayoutAttributeHeight = 8;
CPLayoutAttributeCenterX = 9;
CPLayoutAttributeCenterY = 10;
@typedef CPEdgeInsets
/*!
Creates a CPEdgeInsets. Argument order matches Cocoa's NSEdgeInsetsMake
(top, left, bottom, right). Storage reuses the existing CGInset struct,
whose field order is (top, right, bottom, left).
*/
function CPEdgeInsetsMake(top, left, bottom, right)
{
return CGInsetMake(top, right, bottom, left);
}
function CPEdgeInsetsEqualToEdgeInsets(lhsInsets, rhsInsets)
{
return CGInsetEqualToInset(lhsInsets, rhsInsets);
}
// Gravity Areas
@typedef CPStackViewGravity
@@ -133,7 +62,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
CPUserInterfaceLayoutOrientation _orientation;
CPLayoutAttribute _alignment;
CPStackViewDistribution _distribution;
float _spacing;
CPEdgeInsets _edgeInsets;
@@ -173,7 +101,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
_orientation = CPUserInterfaceLayoutOrientationHorizontal;
_alignment = CPLayoutAttributeCenterY; // Default alignment
_distribution = CPStackViewDistributionGravityAreas;
_spacing = 8.0; // Default Cocoa spacing
_edgeInsets = CPEdgeInsetsMake(0, 0, 0, 0);
_detachesHiddenViews = YES;
@@ -244,27 +171,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
[self setNeedsLayout:YES];
}
/*!
The distribution mode for the stack view.
@note Not yet applied to layout. All views are laid out at their
existing frame size regardless of this value, pending the
constraint-solver based layout engine. The value is stored and
returned so client code can read back what was set.
*/
- (CPStackViewDistribution)distribution
{
return _distribution;
}
- (void)setDistribution:(CPStackViewDistribution)aDistribution
{
if (_distribution === aDistribution)
return;
_distribution = aDistribution;
[self setNeedsLayout:YES];
}
/*!
The minimum spacing, in points, between adjacent views in the stack view.
*/
@@ -329,21 +235,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
return _viewsLeading; // Leading or Top
}
/*!
Rebuilds _arrangedSubviews from the three gravity containers, in
Leading, Center, Trailing order. Call after any change to a gravity
container so _arrangedSubviews stays a correct, single source of truth
for ordering, rather than an incrementally and separately maintained
(and error-prone) copy.
*/
- (void)_rebuildArrangedSubviews
{
_arrangedSubviews = [[CPMutableArray alloc] init];
[_arrangedSubviews addObjectsFromArray:_viewsLeading];
[_arrangedSubviews addObjectsFromArray:_viewsCenter];
[_arrangedSubviews addObjectsFromArray:_viewsTrailing];
}
/*!
Adds a view to the end of the stack view gravity area.
*/
@@ -356,7 +247,7 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
[self removeView:aView];
[container addObject:aView];
[self _rebuildArrangedSubviews];
[_arrangedSubviews addObject:aView];
// Add as actual subview
if ([aView superview] !== self)
@@ -380,7 +271,7 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
else
[container insertObject:aView atIndex:index];
[self _rebuildArrangedSubviews];
[_arrangedSubviews addObject:aView];
if ([aView superview] !== self)
[self addSubview:aView];
@@ -395,9 +286,13 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
var container = [self _containerForGravity:gravity];
// Remove old views from superview
// Remove old views from arranged list and superview
for (var i = 0; i < [container count]; i++)
[container[i] removeFromSuperview];
{
var oldView = container[i];
[oldView removeFromSuperview];
[_arrangedSubviews removeObject:oldView];
}
[container removeAllObjects];
@@ -405,10 +300,10 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
var newView = views[i];
[container addObject:newView];
[_arrangedSubviews addObject:newView];
[self addSubview:newView];
}
[self _rebuildArrangedSubviews];
[self setNeedsLayout:YES];
}
@@ -423,7 +318,7 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
[_viewsLeading removeObject:aView];
[_viewsCenter removeObject:aView];
[_viewsTrailing removeObject:aView];
[self _rebuildArrangedSubviews];
[_arrangedSubviews removeObject:aView];
[aView removeFromSuperview];
@@ -638,13 +533,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
var limit = (dir === 1) ? count : -1;
var step = (dir === 1) ? 1 : -1;
// Spacing is applied as a gap *before* placing an element (except the
// first placed one), rather than trailing off the end after the last
// element. This keeps the returned cursor at the true content edge,
// with no phantom spacing past the final view.
var hasPlacedAny = false;
var pendingSpacing = 0;
for (; i !== limit; i += step)
{
var view = views[i];
@@ -652,9 +540,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
if (_detachesHiddenViews && [view isHidden])
continue;
if (hasPlacedAny)
cursor += (dir === 1) ? pendingSpacing : -pendingSpacing;
var viewFrame = [view frame];
var viewSizePrimary = isVert ? CGRectGetHeight(viewFrame) : CGRectGetWidth(viewFrame);
@@ -734,10 +619,11 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
if (dir === 1) {
originY = cursor;
cursor += sizeH;
cursor += sizeH + [self _spacingAfterView:view];
} else {
cursor -= sizeH;
originY = cursor;
cursor -= [self _spacingAfterView:view];
}
}
else
@@ -749,17 +635,15 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
if (dir === 1) {
originX = cursor;
cursor += sizeW;
cursor += sizeW + [self _spacingAfterView:view];
} else {
cursor -= sizeW;
originX = cursor;
cursor -= [self _spacingAfterView:view];
}
}
[view setFrame:CGRectMake(originX, originY, sizeW, sizeH)];
pendingSpacing = [self _spacingAfterView:view];
hasPlacedAny = true;
}
return cursor;
@@ -775,7 +659,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
{
_orientation = [aCoder decodeIntForKey:@"CPStackViewOrientation"];
_alignment = [aCoder decodeIntForKey:@"CPStackViewAlignment"];
_distribution = [aCoder decodeIntForKey:@"CPStackViewDistribution"];
_spacing = [aCoder decodeFloatForKey:@"CPStackViewSpacing"];
_edgeInsets = [aCoder decodeObjectForKey:@"CPStackViewEdgeInsets"]; // Assuming CPEdgeInsets supports obj coding or manual decode
if (!_edgeInsets) _edgeInsets = CPEdgeInsetsMake(0,0,0,0);
@@ -787,7 +670,10 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
_viewsTrailing = [aCoder decodeObjectForKey:@"CPStackViewViewsTrailing"] || [];
// Rebuild arranged subviews cache
[self _rebuildArrangedSubviews];
_arrangedSubviews = [[CPMutableArray alloc] init];
[_arrangedSubviews addObjectsFromArray:_viewsLeading];
[_arrangedSubviews addObjectsFromArray:_viewsCenter];
[_arrangedSubviews addObjectsFromArray:_viewsTrailing];
_customSpacings = [aCoder decodeObjectForKey:@"CPStackViewCustomSpacings"] || [[CPMapTable alloc] init];
_visibilityPriorities = [[CPMapTable alloc] init]; // usually not persisted
@@ -800,7 +686,6 @@ var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
[super encodeWithCoder:aCoder];
[aCoder encodeInt:_orientation forKey:@"CPStackViewOrientation"];
[aCoder encodeInt:_alignment forKey:@"CPStackViewAlignment"];
[aCoder encodeInt:_distribution forKey:@"CPStackViewDistribution"];
[aCoder encodeFloat:_spacing forKey:@"CPStackViewSpacing"];
[aCoder encodeObject:_edgeInsets forKey:@"CPStackViewEdgeInsets"];
[aCoder encodeBool:_detachesHiddenViews forKey:@"CPStackViewDetachesHiddenViews"];
+7 -67
View File
@@ -175,53 +175,12 @@
[super setFrame:frame];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
// Force a layout update because the internal buttons (_buttonUp and _buttonDown)
// rely on layoutSubviews to receive the new theme attributes (like HUD colors).
[self setNeedsLayout];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self setNeedsLayout];
}
/*! @ignore */
- (void)layoutSubviews
{
var controlSizeThemeState = [self _controlSizeThemeState],
aFrame = [self frame],
isHUD = [self hasThemeState:CPThemeStateHUD],
// 1. Prepare Lookup States (To fetch the correct image/color from the Stepper's theme)
normalLookupStates = [controlSizeThemeState, CPThemeStateBordered],
disabledLookupStates = [controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled],
highlightedLookupStates = [controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted],
// 2. Prepare Target States (To tell the child buttons when to use this image)
normalTargetStates = [CPThemeStateBordered, CPButtonStateBezelStyleRoundRect],
disabledTargetStates = [CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect],
highlightedTargetStates = [CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect];
// If we are in HUD mode, add CPThemeStateHUD to both lookup and target arrays
if (isHUD)
{
// Lookup: Ask theme for "HUD" version of the stepper arrows
normalLookupStates.push(CPThemeStateHUD);
disabledLookupStates.push(CPThemeStateHUD);
highlightedLookupStates.push(CPThemeStateHUD);
// Target: Tell the child buttons "Use this when you are in HUD state"
normalTargetStates.push(CPThemeStateHUD);
disabledTargetStates.push(CPThemeStateHUD);
highlightedTargetStates.push(CPThemeStateHUD);
}
var upSize = [self valueForThemeAttribute:@"up-button-size" inState:controlSizeThemeState],
upSize = [self valueForThemeAttribute:@"up-button-size" inState:controlSizeThemeState],
downSize = [self valueForThemeAttribute:@"down-button-size" inState:controlSizeThemeState],
upFrame = CGRectMake(0, 0, upSize.width, upSize.height),
downFrame = CGRectMake(0, upSize.height, downSize.width, downSize.height);
@@ -229,31 +188,12 @@
[_buttonUp setFrame:upFrame];
[_buttonDown setFrame:downFrame];
// Apply Up Button Attributes
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:normalLookupStates]
forThemeAttribute:@"bezel-color"
inStates:normalTargetStates];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:disabledLookupStates]
forThemeAttribute:@"bezel-color"
inStates:disabledTargetStates];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:highlightedLookupStates]
forThemeAttribute:@"bezel-color"
inStates:highlightedTargetStates];
// Apply Down Button Attributes
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:normalLookupStates]
forThemeAttribute:@"bezel-color"
inStates:normalTargetStates];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:disabledLookupStates]
forThemeAttribute:@"bezel-color"
inStates:disabledTargetStates];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:highlightedLookupStates]
forThemeAttribute:@"bezel-color"
inStates:highlightedTargetStates];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect]];
}
- (void)_sizeToFit
+28 -30
View File
@@ -62,8 +62,7 @@ CPCanvasStringSizingIsFunctional = NO;
if ([self class] != [CPString class])
return;
if (!CPDOMAvailable) return;
#if PLATFORM(DOM)
if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
{
if (!CPStringSizeMeasuringContext)
@@ -74,47 +73,46 @@ CPCanvasStringSizingIsFunctional = NO;
CPCanvasStringSizingIsFunctional = NO;
}
#endif
}
- (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
{
var size;
if (CPDOMAvailable)
{
var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self];
#if PLATFORM(DOM)
var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self];
if (sizeCacheForFont === undefined)
sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = [];
if (sizeCacheForFont === undefined)
sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = [];
var cssString = [aFont cssString],
cacheKey = cssString + '_' + (aWidth ? aWidth : '0');
var cssString = [aFont cssString],
cacheKey = cssString + '_' + (aWidth ? aWidth : '0');
size = sizeCacheForFont[cacheKey];
size = sizeCacheForFont[cacheKey];
if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey))
return CGSizeMakeCopy(size);
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;
}
if (!CPCanvasStringSizingIsFunctional || aWidth)
size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
else
size = CGSizeMake(0, 0);
{
if (CPPlatformHasBug(CPTextSizingAlwaysNeedsSetFontBug) || CPStringSizeMeasuringContext.font !== cssString)
CPStringSizeMeasuringContext.font = cssString;
var fontHeight = CPStringSizeWithFontHeightCache[cssString];
if (fontHeight === undefined)
fontHeight = CPStringSizeWithFontHeightCache[cssString] = [aFont defaultLineHeightForFont];
size = CGSizeMake(CPStringSizeMeasuringContext.measureText(self).width, fontHeight);
}
sizeCacheForFont[cacheKey] = size;
#else
size = CGSizeMake(0, 0);
#endif
return CGSizeMakeCopy(size);
}
+3 -1
View File
@@ -117,7 +117,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
var height = [_tabs valueForThemeAttribute:@"min-size"].height;
[_tabs setFrameSize:CGSizeMake(0, height)];
_box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
_box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
[_box setTabView:self];
[_box setContentInset:[self currentValueForThemeAttribute:@"box-content-inset"]];
[_box setContentViewMargins:CGSizeMakeZero()];
@@ -982,6 +982,8 @@ 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 :
//
+23 -231
View File
@@ -25,10 +25,6 @@
@import "CPCursor.j"
@import "_CPImageAndTextView.j"
@import "CPTrackingArea.j"
@import "CPAnimationContext.j"
@import "CPViewAnimator.j"
@import "CPScrollView.j"
@import <Foundation/CPGeometry.j>
@class CPTableView
@@ -54,16 +50,10 @@
@"text-color": [CPNull null],
@"font": [CPNull null],
@"text-shadow-color": [CPNull null],
@"text-shadow-offset": CGSizeMakeZero(),
@"dont-draw-separator": NO
@"text-shadow-offset": CGSizeMakeZero()
};
}
- (BOOL)acceptsFirstMouse:(CPEvent)anEvent
{
return YES;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
@@ -76,22 +66,18 @@
- (void)_init
{
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
var inset = [self valueForThemeAttribute:@"text-inset"];
_textField = [[_CPImageAndTextView alloc] initWithFrame:
CGRectMake(inset.left, inset.top, CGRectGetWidth([self bounds]) - (inset.left + inset.right), CGRectGetHeight([self bounds]) - (inset.top + inset.bottom))];
CGRectMake(5.0, 0.0, CGRectGetWidth([self bounds]) - 10.0, CGRectGetHeight([self bounds]))];
[_textField setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_textField setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode"]];
[_textField setTextColor:[self valueForThemeAttribute:@"text-color"]];
[_textField setFont:[self valueForThemeAttribute:@"font"]];
[_textField setAlignment:[self valueForThemeAttribute:@"text-alignment"]];
[_textField setLineBreakMode:CPLineBreakByTruncatingTail];
[_textField setTextColor:[CPColor colorWithRed:51.0 / 255.0 green:51.0 / 255.0 blue:51.0 / 255.0 alpha:1.0]];
[_textField setFont:[CPFont boldSystemFontOfSize:12.0]];
[_textField setAlignment:CPLeftTextAlignment];
[_textField setVerticalAlignment:CPCenterVerticalTextAlignment];
[_textField setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color"]];
[_textField setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset"]];
[_textField setTextShadowColor:[CPColor whiteColor]];
[_textField setTextShadowOffset:CGSizeMake(0,1)];
[self addSubview:_textField];
}
@@ -202,9 +188,6 @@
- (void)drawRect:(CGRect)aRect
{
if ([self valueForThemeAttribute:@"dont-draw-separator"])
return;
var bounds = [self bounds];
if (!CGRectIntersectsRect(aRect, bounds))
@@ -240,8 +223,7 @@ var _CPTableColumnHeaderViewStringValueKey = @"_CPTableColumnHeaderViewStringVal
[self _init];
[self _setIndicatorImage:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewImageKey]];
[self setStringValue:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewStringValueKey]];
// FIXME: pourquoi dans actif, font=null ?
// [self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
[self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
}
return self;
@@ -275,13 +257,11 @@ var CPTableHeaderViewResizeZone = 3.0,
BOOL _isResizing;
BOOL _isDragging;
BOOL _isAnimating;
BOOL _canDragColumn;
CPView _columnDragView;
CPView _columnDragHeaderView;
CPView _columnDragClipView;
CPScrollView _columnDragScrollView;
float _columnOldWidth;
@@ -298,9 +278,7 @@ var CPTableHeaderViewResizeZone = 3.0,
return @{
@"background-color": [CPNull null],
@"divider-color": [CPColor grayColor],
@"divider-thickness": 1.0,
@"swap-animation": [CPNull null],
@"return-animation": [CPNull null]
@"divider-thickness": 1.0
};
}
@@ -362,24 +340,7 @@ var CPTableHeaderViewResizeZone = 3.0,
- (CPInteger)columnAtPoint:(CGPoint)aPoint
{
var tableView = [self tableView],
tableColumns = [tableView tableColumns],
count = [tableColumns count],
bounds = [self bounds],
// Create a point that keeps the X position but forces Y to be safely
// in the middle of the header view.
constrainedPoint = CGPointMake(aPoint.x, CGRectGetMidY(bounds));
// Iterate through columns to find which one contains the constrained X coordinate
for (var i = 0; i < count; i++)
{
// headerRectOfColumn: is a utility method defined in CPTableHeaderView
// that handles the coordinate conversion from the table view relative to the header.
if (CGRectContainsPoint([self headerRectOfColumn:i], constrainedPoint))
return i;
}
return -1;
return [_tableView columnAtPoint:aPoint];
}
- (CGRect)headerRectOfColumn:(CPInteger)aColumnIndex
@@ -404,8 +365,6 @@ var CPTableHeaderViewResizeZone = 3.0,
- (void)layoutSubviews
{
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
var tableColumns = [_tableView tableColumns],
count = [tableColumns count];
@@ -483,14 +442,6 @@ var CPTableHeaderViewResizeZone = 3.0,
}
else if (_isDragging)
{
// First, we have to avoid a running condition where user stops dragging while a swap animation is running
if (_isAnimating)
{
[CPTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(_retry_mouseUp:) userInfo:theEvent repeats:NO];
return;
}
[self _stopDraggingTableColumn:_activeColumn];
}
else if (_activeColumn != -1)
@@ -507,11 +458,6 @@ var CPTableHeaderViewResizeZone = 3.0,
_activeColumn = -1;
}
- (void)_retry_mouseUp:(CPTimer)aTimer
{
[self mouseUp:[aTimer userInfo]];
}
@end
@implementation CPTableHeaderView (CPTrackingArea)
@@ -616,21 +562,7 @@ var CPTableHeaderViewResizeZone = 3.0,
pressure:[theEvent pressure]];
[self autoscroll:constrainedEvent];
var contentView = [_tableView superview],
boundsOriginBefore = [contentView boundsOrigin];
[_tableView autoscroll:constrainedEvent];
var boundsOriginAfter = [contentView boundsOrigin],
deltaX = boundsOriginAfter.x - boundsOriginBefore.x;
if (_isDragging)
{
var dragContentView = [_columnDragScrollView contentView],
dragContentBoundsOrigin = [dragContentView boundsOrigin];
[dragContentView setBoundsOrigin:CGPointMake(dragContentBoundsOrigin.x + deltaX, dragContentBoundsOrigin.y)];
}
}
- (CGRect)_headerRectOfLastVisibleColumn
@@ -652,91 +584,16 @@ var CPTableHeaderViewResizeZone = 3.0,
- (CGPoint)_constrainDragPoint:(CGPoint)aPoint
{
// This effectively clamps the value between the minimum and maximum
var tableFrame = [_tableView frame],
dragFrame = [_columnDragView frame],
maxX = tableFrame.size.width - dragFrame.size.width,
point = CGPointMake(MAX(MIN(aPoint.x, maxX),0), aPoint.y);
var visibleRect = [_tableView visibleRect],
lastColumnRect = [self _headerRectOfLastVisibleColumn],
activeColumnRect = [self headerRectOfColumn:_activeColumn],
maxX = CGRectGetMaxX(lastColumnRect) - CGRectGetWidth(activeColumnRect) - CGRectGetMinX(visibleRect),
point = CGPointMake(MAX(MIN(aPoint.x, maxX), -CGRectGetMinX(visibleRect)), aPoint.y);
return point;
}
- (void)_moveColumn:(CPInteger)aFromIndex toColumn:(CPInteger)aToIndex
{
if (_isAnimating)
return;
var swapAnimation = [self currentValueForThemeAttribute:@"swap-animation"];
if (swapAnimation)
{
_isAnimating = YES;
// There's a theme defined animation function, just use it
objj_eval("("+swapAnimation+")")(self, aFromIndex, aToIndex, _columnDragClipView, _columnDragView);
// var animatedColumn = [[_tableView tableColumns] objectAtIndex:aToIndex],
// animatedHeader = [animatedColumn headerView],
// animatedHeaderOrigin = [animatedHeader frameOrigin],
//
// destinationX,
// draggedHeader = [[[_tableView tableColumns] objectAtIndex:aFromIndex] headerView],
//
// scrollView = [self enclosingScrollView],
// animatedView = [_tableView _animationViewForColumn:aToIndex],
// animatedOrigin = [animatedView frameOrigin];
//
// [_columnDragClipView addSubview:animatedView positioned:CPWindowBelow relativeTo:_columnDragView];
//
// [[animatedHeader subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:YES];
// [animatedHeader setThemeState:CPThemeStateVertical];
//
// if (aFromIndex < aToIndex)
// destinationX = CGRectGetMinX([_tableView rectOfColumn:aFromIndex]);
// else
// destinationX = animatedOrigin.x + CGRectGetWidth([_tableView rectOfColumn:aFromIndex]);
//
// [CPAnimationContext beginGrouping];
//
// var context = [CPAnimationContext currentContext];
//
// [context setDuration:0.15];
// [context setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
// [context setCompletionHandler:function() {
// [animatedView removeFromSuperview];
//
// [self _finalize_moveColumn:aFromIndex toColumn:aToIndex];
//
// [animatedHeader unsetThemeState:CPThemeStateVertical];
// [[animatedHeader subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:NO];
//
// if ([animatedView isSelected])
// {
// [animatedHeader setThemeState:CPThemeStateSelected];
//
// // We have to reselect the animated column
// [[_tableView selectedColumnIndexes] addIndex:aFromIndex];
// }
//
// // Reload animated column
// var columnVisRect = CGRectIntersection([_tableView rectOfColumn:aFromIndex], [_tableView visibleRect]),
// rowsIndexes = [CPIndexSet indexSetWithIndexesInRange:[_tableView rowsInRect:columnVisRect]],
// columnsIndexes = [CPIndexSet indexSetWithIndex:aFromIndex];
//
// [_tableView _loadDataViewsInRows:rowsIndexes columns:columnsIndexes];
// [_tableView _layoutViewsForRowIndexes:rowsIndexes columnIndexes:columnsIndexes];
//
// [_tableView._tableDrawView displayRect:columnVisRect];
// }];
//
// [[animatedView animator] setFrameOrigin:CGPointMake(destinationX, animatedOrigin.y)];
//
// [CPAnimationContext endGrouping];
}
else
[self _finalize_moveColumn:aFromIndex toColumn:aToIndex];
}
- (void)_finalize_moveColumn:(CPInteger)aFromIndex toColumn:(CPInteger)aToIndex
{
[_tableView moveColumn:aFromIndex toColumn:aToIndex];
_activeColumn = aToIndex;
@@ -745,8 +602,6 @@ var CPTableHeaderViewResizeZone = 3.0,
[_tableView _setDraggedColumn:_activeColumn];
[self setNeedsDisplay:YES];
_isAnimating = NO;
}
- (BOOL)isDragging
@@ -763,30 +618,17 @@ var CPTableHeaderViewResizeZone = 3.0,
// Create a new clip view for the drag view that clips to the header + visible content
var headerHeight = CGRectGetHeight([self frame]),
scrollView = [self enclosingScrollView],
contentFrame = [[scrollView contentView] frame],
contentBounds = [[scrollView contentView] bounds];
contentFrame = [[scrollView contentView] frame];
contentFrame.origin.y -= headerHeight;
contentFrame.size.height += headerHeight;
_columnDragScrollView = [[CPScrollView alloc] initWithFrame:contentFrame];
[_columnDragScrollView setHasHorizontalScroller:NO];
[_columnDragScrollView setHasVerticalScroller:NO];
[_columnDragScrollView setBorderType:CPNoBorder];
var tableFrame = [_tableView frame],
clipFrame = CGRectMake(0, 0, tableFrame.size.width, contentFrame.size.height);
_columnDragClipView = [[CPView alloc] initWithFrame:clipFrame];
_columnDragClipView = [[CPView alloc] initWithFrame:contentFrame];
[_columnDragClipView addSubview:_columnDragView];
[_columnDragScrollView setDocumentView:_columnDragClipView];
[[_columnDragScrollView contentView] setBoundsOrigin:CGPointMake(contentBounds.origin.x, 0)];
// Insert the clip view above the table header (and content)
[scrollView addSubview:_columnDragScrollView positioned:CPWindowAbove relativeTo:self];
[scrollView addSubview:_columnDragClipView positioned:CPWindowAbove relativeTo:self];
// Hide the underlying column header subviews, we just want to draw the chrome
var headerView = [[[_tableView tableColumns] objectAtIndex:aColumnIndex] headerView];
@@ -796,9 +638,6 @@ var CPTableHeaderViewResizeZone = 3.0,
// The underlying column header shows normal state
[headerView unsetThemeStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
// FIXME: Just a little hack to get a special background (using an unused theme state)
[headerView setThemeState:CPThemeStateVertical];
// Keep track of the location within the column header where the original mousedown occurred
_columnDragHeaderView = [_columnDragView viewWithTag:CPTableHeaderViewDragColumnHeaderTag];
@@ -853,43 +692,10 @@ var CPTableHeaderViewResizeZone = 3.0,
}
- (void)_stopDraggingTableColumn:(CPInteger)aColumnIndex
{
var returnAnimation = [self currentValueForThemeAttribute:@"return-animation"];
if (returnAnimation)
{
_isAnimating = YES;
// There's a theme defined animation function, just use it
objj_eval("("+returnAnimation+")")(self, aColumnIndex, _columnDragView);
// var animatedColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
// animatedHeader = [animatedColumn headerView],
// animatedHeaderOrigin = [animatedHeader frameOrigin];
//
// [CPAnimationContext beginGrouping];
//
// var context = [CPAnimationContext currentContext];
//
// [context setDuration:0.15];
// [context setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
// [context setCompletionHandler:function() {
//
// [self _finalize_stopDraggingTableColumn:aColumnIndex];
// }];
//
// [[_columnDragView animator] setFrameOrigin:CGPointMake(animatedHeaderOrigin.x, 0)];
//
// [CPAnimationContext endGrouping];
}
else
[self _finalize_stopDraggingTableColumn:aColumnIndex];
}
- (void)_finalize_stopDraggingTableColumn:(CPInteger)aColumnIndex
{
_isDragging = NO;
[_columnDragClipView removeFromSuperview];
[_tableView _setDraggedColumn:-1];
var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
@@ -897,30 +703,16 @@ var CPTableHeaderViewResizeZone = 3.0,
[[headerView subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:NO];
// Restore headerView background
[headerView unsetThemeState:CPThemeStateVertical];
if (_tableView._draggedColumnIsSelected)
[headerView setThemeState:CPThemeStateSelected];
// Reload animated column
var columnVisRect = CGRectIntersection([_tableView rectOfColumn:aColumnIndex], [_tableView visibleRect]),
rowsIndexes = [CPIndexSet indexSetWithIndexesInRange:[_tableView rowsInRect:columnVisRect]],
columnsIndexes = [CPIndexSet indexSetWithIndex:aColumnIndex];
[_tableView _reloadDataViews];
[[_tableView headerView] setNeedsLayout];
[_tableView _loadDataViewsInRows:rowsIndexes columns:columnsIndexes];
[_tableView _layoutViewsForRowIndexes:rowsIndexes columnIndexes:columnsIndexes];
[_tableView _updateDataViewsFocusState];
[_tableView._tableDrawView displayRect:columnVisRect];
[[CPCursor arrowCursor] set]; // FIXME: retirer ?
[[CPCursor arrowCursor] set];
[self updateTrackingAreas];
[_columnDragScrollView removeFromSuperview];
[_tableView _sendDelegateDidDragTableColumn:tableColumn];
_isAnimating = NO;
}
- (BOOL)_shouldResizeTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
+508 -531
View File
File diff suppressed because it is too large. Load diff
+216 -282
View File
@@ -108,26 +108,6 @@ function CPTextFieldHandleBlur(anEvent, ownerRef)
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}
function _CPTextFieldDetachInputElement(anElement)
{
if (!anElement || !anElement.parentNode)
return;
var wasResigning = CPTextFieldInputResigning;
CPTextFieldInputResigning = YES;
try
{
anElement.parentNode.removeChild(anElement);
}
catch (e)
{
// Bereits durch einen Blur-Handler entfernt – nichts zu tun.
}
CPTextFieldInputResigning = wasResigning;
CPTextFieldInputDidBlur = NO;
}
@implementation CPString (CPTextFieldAdditions)
@@ -279,10 +259,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
// MARK: -
#if PLATFORM(DOM)
- (DOMElement)_inputElement
{
if (!CPDOMAvailable) return nil;
if (!CPTextFieldDOMTextAreaElement)
{
CPTextFieldDOMTextAreaElement = document.createElement("textarea");
@@ -385,6 +364,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return CPTextFieldDOMCurrentElement;
}
#endif
- (id)initWithFrame:(CGRect)aFrame
{
@@ -675,123 +655,89 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
_stringValue = [self stringValue];
if (CPDOMAvailable)
#if PLATFORM(DOM)
[self _setCSSStyleForInputElement];
var element = [self _inputElement];
element.value = _stringValue;
_DOMElement.appendChild(element);
CPTextFieldInputIsActive = YES;
if (document.attachEvent)
{
[self _setCSSStyleForInputElement];
CPTextFieldCachedSelectStartFunction = [[self window] platformWindow]._DOMBodyElement.onselectstart;
CPTextFieldCachedDragFunction = [[self window] platformWindow]._DOMBodyElement.ondrag;
var element = [self _inputElement];
element.value = _stringValue;
if (element.parentNode !== _DOMElement)
{
var previousOwner = CPTextFieldInputOwner;
// Otherwise Chrome will fire the blur during appendChild (NotFoundError).
_CPTextFieldDetachInputElement(element);
try
{
_DOMElement.appendChild(element);
}
catch (e)
{
// 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];
}
}
CPTextFieldInputIsActive = YES;
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)
{
[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];
switch ([self alignment]) {
case CPCenterTextAlignment:
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
x -= (contentWidth - textWidth) / 2 + contentInset.left;
break;
case CPRightTextAlignment:
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
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)];
}
_willBecomeFirstResponderByClick = NO;
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
[[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)
{
[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];
switch ([self alignment]) {
case CPCenterTextAlignment:
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
x -= (contentWidth - textWidth) / 2 + contentInset.left;
break;
case CPRightTextAlignment:
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
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)];
}
_willBecomeFirstResponderByClick = NO;
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
#endif
return YES;
}
@@ -801,14 +747,13 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
*/
- (void)_setEnableCSSSelection:(BOOL)shouldEnable
{
if (!CPDOMAvailable)
return;
#if PLATFORM (DOM)
if (CPTexFieldCurrentCSSSelectableField)
CPTexFieldCurrentCSSSelectableField._DOMElement.style[CPBrowserStyleProperty(@"user-select")] = @"none";
CPTexFieldCurrentCSSSelectableField = self;
_DOMElement.style[CPBrowserStyleProperty(@"user-select")] = shouldEnable ? @"text" : @"none";
#endif
}
/*!
@@ -817,8 +762,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
*/
- (void)_setCSSStyleForInputElement
{
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
var element = [self _inputElement],
font = [self font],
@@ -827,27 +771,33 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"],
left = CGRectGetMinX(contentRect);
// If the browser has a built in left padding, compensate for it. We need the input text to be exactly on top of the original text.
if (CPFeatureIsCompatible(CPInput1PxLeftPadding))
left -= 1;
switch (verticalAlign)
{
case CPTopVerticalTextAlignment:
var topPoint = CEIL(CGRectGetMinY(contentRect)) + "px";
var topPoint = CGRectGetMinY(contentRect) + "px";
break;
case CPCenterVerticalTextAlignment:
var topPoint = CEIL((CGRectGetMidY(contentRect) - (lineHeight / 2))) + "px";
var topPoint = (CGRectGetMidY(contentRect) - (lineHeight / 2)) + "px";
break;
case CPBottomVerticalTextAlignment:
var topPoint = CEIL((CGRectGetMaxY(contentRect) - lineHeight)) + "px";
var topPoint = (CGRectGetMaxY(contentRect) - lineHeight) + "px";
break;
default:
var topPoint = CEIL(CGRectGetMinY(contentRect)) + "px";
var topPoint = CGRectGetMinY(contentRect) + "px";
break;
}
// Use currentValueForThemeAttribute to respect all current states (HUD, Placeholder, Editing, etc.)
element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
if ([self hasThemeState:CPTextFieldStatePlaceholder])
element.style.color = [[self valueForThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder] cssString];
else
element.style.color = [[self valueForThemeAttribute:@"text-color" inState:CPThemeStateEditing] cssString];
switch ([self alignment])
{
@@ -881,40 +831,41 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (isTextArea)
element.style.whiteSpace = _wraps ? "pre" : "nowrap";
#endif
}
/*! @ignore */
- (BOOL)resignFirstResponder
{
if (CPDOMAvailable)
#if PLATFORM(DOM)
// We might have been the first responder without actually editing.
if (_isEditing && CPTextFieldInputOwner === self)
{
// We might have been the first responder without actually editing.
if (_isEditing && CPTextFieldInputOwner === self)
var element = [self _inputElement],
newValue = element.value,
error = @"";
if (newValue !== _stringValue)
{
var element = [self _inputElement],
newValue = element.value,
error = @"";
[self _setStringValue:newValue];
}
if (newValue !== _stringValue)
{
[self _setStringValue:newValue];
}
// 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 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];
element.focus();
element.focus();
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
return NO;
}
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 +900,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _updatePlaceholderState];
[self setNeedsLayout];
if (!CPDOMAvailable) return;
#if PLATFORM(DOM)
var element = [self _inputElement];
@@ -962,16 +913,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
CPTextFieldBlurHandler();
if (element.parentNode == _DOMElement)
{
try
{
element.parentNode.removeChild(element);
}
catch (e)
{
// Bereits durch den Blur-Handler entfernt.
}
}
element.parentNode.removeChild(element);
// Previosly, we unflagged CPTextFieldInputDidBlur and CPTextFieldInputResigning before
// the call to removeChild. This resulted in DOM exceptions in Chrome under certain conditions.
@@ -992,6 +934,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
CPTextFieldCachedSelectStartFunction = nil;
CPTextFieldCachedDragFunction = nil;
}
#endif
}
- (void)_setObserveWindowKeyNotifications:(BOOL)shouldObserve
@@ -1048,41 +992,42 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
- (BOOL)_valueIsValid:(CPString)aValue
{
if (CPDOMAvailable)
#if PLATFORM(DOM)
var error = @"";
if ([self _setStringValue:aValue isNewValue:NO errorDescription:@ref(error)] === NO)
{
var error = @"";
var acceptInvalidValue = NO;
if ([self _setStringValue:aValue isNewValue:NO errorDescription:@ref(error)] === NO)
if (_implementedDelegateMethods & CPTextFieldDelegate_control_didFailToFormatString_errorDescription_)
{
var acceptInvalidValue = NO;
var currentEvent = [CPApp currentEvent];
if (_implementedDelegateMethods & CPTextFieldDelegate_control_didFailToFormatString_errorDescription_)
if (currentEvent &&
CPTextFieldLastValidationFailureField === self &&
CPTextFieldLastValidationFailureString === aValue &&
CPTextFieldLastValidationFailureEvent === currentEvent)
{
var currentEvent = [CPApp currentEvent];
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;
}
acceptInvalidValue = CPTextFieldLastValidationFailureResult;
}
else
{
acceptInvalidValue = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
if (acceptInvalidValue === NO)
return NO;
CPTextFieldLastValidationFailureField = self;
CPTextFieldLastValidationFailureString = aValue;
CPTextFieldLastValidationFailureEvent = currentEvent;
CPTextFieldLastValidationFailureResult = acceptInvalidValue;
}
}
if (acceptInvalidValue === NO)
return NO;
}
#endif
return YES;
}
@@ -1190,18 +1135,17 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (!([self isEnabled] && [self isEditable]))
return;
if (CPDOMAvailable)
#if PLATFORM(DOM)
var newValue = [self _inputElement].value;
if (newValue !== _stringValue)
{
var newValue = [self _inputElement].value;
if (newValue !== _stringValue)
{
[self _setStringValue:newValue];
[self _didEdit];
}
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
[self _setStringValue:newValue];
[self _didEdit];
}
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
#endif
}
- (void)keyDown:(CPEvent)anEvent
@@ -1298,8 +1242,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (!([self isEnabled] && [self isEditable]))
return;
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
var oldValue = _stringValue,
range = [self selectedRange],
@@ -1317,6 +1260,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
{
[self _didEdit];
}
#endif
}
- (void)textDidBlur:(CPNotification)note
@@ -1356,17 +1301,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[super textDidChange:note];
}
- (void)validateEditing
{
if (!CPDOMAvailable)
return;
var element = [self _inputElement];
if (element)
[self _setStringValue:element.value isNewValue:YES errorDescription:nil];
}
- (void)textDidBeginEditing:(CPNotification)note
{
//this looks to prevent false propagation of notifications for other objects
@@ -1480,9 +1414,13 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
else
_stringValue = [self stringValue];
if (CPDOMAvailable && (CPTextFieldInputOwner === self || [[self window] firstResponder] === self) && [[self window] isKeyWindow])
#if PLATFORM(DOM)
if ((CPTextFieldInputOwner === self || [[self window] firstResponder] === self) && [[self window] isKeyWindow])
[self _inputElement].value = _stringValue;
#endif
[self _updatePlaceholderState];
}
@@ -1612,34 +1550,31 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
{
var wind = [self window];
if (CPDOMAvailable)
#if PLATFORM(DOM)
if ([self isEditable])
{
if ([self isEditable])
{
var element = [self _inputElement];
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
if ([wind firstResponder] === self)
{
[self setSelectedRange:CPMakeRange(0, _stringValue.length)];
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
{
// 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];
[self setSelectedRange:CPMakeRange(0, _stringValue.length)];
}
#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
}
}
@@ -1662,10 +1597,9 @@ 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 (CPDOMAvailable)
{
stringToCopy = [[[self window] platformWindow] _selectedText];
}
#if PLATFORM(DOM)
stringToCopy = [[[self window] platformWindow] _selectedText];
#endif
}
var pasteboard = [CPPasteboard generalPasteboard];
@@ -1745,34 +1679,35 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if ([[self window] firstResponder] !== self)
return CPMakeRange(0, 0);
if (CPDOMAvailable)
#if PLATFORM(DOM)
// we wrap this in try catch because firefox will throw an exception in certain instances
try
{
// we wrap this in try catch because firefox will throw an exception in certain instances
try
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))
{
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
range.setEndPoint('EndToStart', selectionRange);
return CPMakeRange(range.text.length, selectionRange.text.length);
}
}
catch (e)
{
// fall through to the return
}
#endif
return CPMakeRange(0, 0);
}
@@ -1782,8 +1717,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (![[self window] firstResponder] === self)
return;
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
if (![self isEditable])
{
@@ -1827,6 +1761,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
{
}
}
#endif
}
- (void)selectAll:(id)sender
@@ -1900,11 +1835,10 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _setObjectValue:newValue useFormatter:NO];
[self setSelectedRange:CPMakeRange(range.location, 0)];
if (CPDOMAvailable)
{
// Since we just performed the deletion manually, we don't need the browser to do anything else.
[[[self window] platformWindow] _propagateCurrentDOMEvent:NO];
}
#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
}
[self _didEdit];
-1
View File
@@ -36,7 +36,6 @@
@class CPLayoutManager
@class CPTextContainer
@class CPFontManager
@class _CPFontPanelPreviewView
/*
Collection indexes
+11 -27
View File
@@ -25,10 +25,10 @@
*/
@import "CPText.j"
@import "CPTextContainer.j"
@import "CGContext.j"
@import "CPTypesetter.j"
@import "CPFont.j"
@import "CPCompatibility.j"
@global _MakeRangeFromAbs
@global document
@@ -298,10 +298,9 @@ _oncontextmenuhandler = function () { return false; };
[_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil];
if (CPDOMAvailable)
{
[self _cleanUpDOM];
}
#if PLATFORM(DOM)
[self _cleanUpDOM];
#endif
_isValidatingLayoutAndGlyphs = NO;
}
@@ -1125,7 +1124,6 @@ var _objectsInRange = function(aList, aRange)
BOOL _isInvalid;
BOOL _isLast;
float _wordSpacing;
CGRect _fragmentRect;
CGRect _usedRect;
CGPoint _location;
@@ -1148,8 +1146,7 @@ var _objectsInRange = function(aList, aRange)
if (!aString || aString.length === 0)
return nil;
if (!CPDOMAvailable) return nil;
#if PLATFORM(DOM)
var style,
span = document.createElement("span");
@@ -1163,10 +1160,6 @@ 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)
{
@@ -1204,6 +1197,9 @@ var _objectsInRange = function(aList, aRange)
span.textContent = aString;
return span;
#else
return nil;
#endif
}
- (id)initWithRange:(CPRange)aRange textContainer:(CPTextContainer)aContainer textStorage:(CPTextStorage)textStorage
@@ -1219,7 +1215,6 @@ var _objectsInRange = function(aList, aRange)
_range = CPMakeRangeCopy(aRange);
_textContainer = aContainer;
_isInvalid = NO;
_wordSpacing = 0.0;
_runs = [];
_glyphsFrames = [];
_glyphsOffsets = [];
@@ -1500,9 +1495,6 @@ 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++)
@@ -1541,19 +1533,11 @@ var _objectsInRange = function(aList, aRange)
if (verticalOffset)
{
// An attachment view is moved through setFrameOrigin:, which also moves it on the
// screen. Changing its frame directly would leave it in place, because the next draw's
// setFrameOrigin: would then find it already at the new origin and do nothing.
if (_runs[i].view)
{
var viewOrigin = [_runs[i].view frameOrigin];
_runs[i].view._frame.origin.y += verticalOffset;
[_runs[i].view setFrameOrigin:CGPointMake(viewOrigin.x, viewOrigin.y + verticalOffset)];
}
// A span that has been placed moves with its line. One that has not is placed when drawn.
if (_runs[i].elem && _runs[i].elem.style.top)
_runs[i].elem.style.top = (parseFloat(_runs[i].elem.style.top) + verticalOffset) + 'px';
if (_runs[i].elem)
_runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
_runs[i].DOMpatched = YES;
}
+2 -37
View File
@@ -26,7 +26,7 @@
CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName";
@typedef CPTabStopType
// Define missing global tab stop type constants
CPLeftTabStopType = 0;
CPRightTabStopType = 1;
CPCenterTabStopType = 2;
@@ -106,24 +106,6 @@ 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
@@ -142,7 +124,6 @@ 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
@@ -187,8 +168,6 @@ var _sharedDefaultParagraphStyle = nil;
[_tabStops addObject:[[CPTextTab alloc] initWithType:CPLeftTextAlignment
location:i * kDefaultTabInterval]];
}
_textLists = [];
}
return self;
}
@@ -211,7 +190,6 @@ var _sharedDefaultParagraphStyle = nil;
_paragraphSpacingBefore = [other paragraphSpacingBefore];
_defaultTabInterval = [other defaultTabInterval];
_tabStops = [[other tabStops] copy];
_textLists = [[other textLists] copy] || [];
}
return self;
}
@@ -251,7 +229,6 @@ var _sharedDefaultParagraphStyle = nil;
_paragraphSpacingBefore = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacingBefore"];
_defaultTabInterval = [aCoder decodeFloatForKey:@"CPParagraphStyleDefaultTabInterval"];
_tabStops = [aCoder decodeObjectForKey:@"CPParagraphStyleTabStops"];
_textLists = [aCoder decodeObjectForKey:@"CPParagraphStyleTextLists"] || [];
}
return self;
}
@@ -272,9 +249,6 @@ 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
@@ -291,8 +265,7 @@ var _sharedDefaultParagraphStyle = nil;
_tailIndent === [other tailIndent] &&
_firstLineHeadIndent === [other firstLineHeadIndent] &&
_lineBreakMode === [other lineBreakMode] &&
[_tabStops isEqualToArray:[other tabStops]] &&
_CPParagraphStyleTextListsAreIdentical(_textLists, [other textLists]);
[_tabStops isEqualToArray:[other tabStops]];
}
@end
@@ -404,14 +377,6 @@ var _sharedDefaultParagraphStyle = nil;
_tabStops = [newTabStops mutableCopy];
}
/*!
Sets the text lists that the paragraph belongs to, outermost first.
*/
- (void)setTextLists:(CPArray)someTextLists
{
_textLists = [someTextLists copy] || [];
}
- (id)copyWithZone:(CPZone)aZone
{
// Return an immutable copy
-504
View File
@@ -1,504 +0,0 @@
/*
* 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
File diff suppressed because it is too large. Load diff
-367
View File
@@ -1,367 +0,0 @@
/*
* 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
+5 -18
View File
@@ -112,8 +112,7 @@ CPLineMovesUp = 4;
- (void)_init
{
// Cocoa's default: room at each end of a line fragment, so that text does not touch the container's edges.
_lineFragmentPadding = 5.0;
_lineFragmentPadding = 0.0;
_layoutManager = [[CPLayoutManager alloc] init];
[_layoutManager addTextContainer:self];
@@ -130,9 +129,6 @@ 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
@@ -197,16 +193,13 @@ CPLineMovesUp = 4;
- (void)textViewFrameChanged:(CPNotification)aNotification
{
// 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];
var newSize = CGSizeMake(_size.width, _size.height);
if (_widthTracksTextView)
newSize.width = MAX(0.0, frameSize.width - 2.0 * inset.width);
newSize.width = [_textView frame].size.width;
if (_heightTracksTextView)
newSize.height = MAX(0.0, frameSize.height - 2.0 * inset.height);
newSize.height = [_textView frame].size.height;
[self setContainerSize:newSize];
}
@@ -278,8 +271,7 @@ CPLineMovesUp = 4;
var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey",
CPTextContainerWidthTracksTextViewKey = @"CPTextContainerWidthTracksTextViewKey",
CPTextContainerHeightTracksTextViewKey = @"CPTextContainerHeightTracksTextViewKey",
CPTextContainerLineFragmentPaddingKey = @"CPTextContainerLineFragmentPaddingKey";
CPTextContainerHeightTracksTextViewKey = @"CPTextContainerHeightTracksTextViewKey";
@implementation CPTextContainer (CPCoding)
@@ -298,10 +290,6 @@ 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;
@@ -313,7 +301,6 @@ var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
[aCoder encodeObject:_layoutManager forKey:CPTextContainerLayoutManagerKey];
[aCoder encodeBool:_widthTracksTextView forKey:CPTextContainerWidthTracksTextViewKey];
[aCoder encodeBool:_heightTracksTextView forKey:CPTextContainerHeightTracksTextViewKey];
[aCoder encodeFloat:_lineFragmentPadding forKey:CPTextContainerLineFragmentPaddingKey];
}
@end
-403
View File
@@ -1,403 +0,0 @@
/*
* 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
+5 -13
View File
@@ -26,7 +26,6 @@
@import "CPText.j"
@import "CPFont.j"
@import "CPTextAttachment.j"
@class CPLayoutManager;
@@ -264,23 +263,16 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
/*!
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.
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
*/
+ (id)attributedStringWithAttachment:(id)anAttachment
+ (id)attributedStringWithAttachment:(CPView)someView
{
var result = [[self alloc] initWithString:_CPAttachmentCharacterAsString];
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)];
[result setAttributes:@{_CPAttachmentView:someView} range:CPMakeRange(0, 1)];
return result;
}
File diff suppressed because it is too large. Load diff
+54 -131
View File
@@ -49,23 +49,7 @@ CPTypesetterParagraphBreakAction = 1 << 4;
CPTypesetterContainerBreakAction = 1 << 5;
var CPSystemTypesetterFactory,
_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;
};
_sharedSimpleTypesetter;
@implementation CPTypesetter : CPObject
@@ -154,28 +138,32 @@ var _CPTypesetterDecimalSeparator = function()
return [_layoutManager textContainers];
}
// Retrieves correct CPTextTab stop accounting for custom stops and default intervals
// Retrieves correct CPTextTab stop accounting for CPArray properties
- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction
{
var tabStops = [_currentParagraph tabStops],
defaultInterval = [_currentParagraph defaultTabInterval] || 28.0;
var tabStops = [_currentParagraph tabStops];
var l = tabStops ? [tabStops count] : 0;
if (!tabStops)
tabStops = [[CPParagraphStyle defaultParagraphStyle] tabStops];
// 1. If custom tab stops exist ahead of current position, use the first one encountered
if (l > 0)
var l = [tabStops count];
if (l === 0)
return nil;
// Find the first tab stop that is strictly greater than the current width
for (var i = 0; i < l; i++)
{
for (var i = 0; i < l; i++)
{
var tab = [tabStops objectAtIndex:i];
var tab = [tabStops objectAtIndex:i];
if ([tab location] > aWidth)
return tab;
}
if ([tab location] > aWidth)
return tab;
}
// 2. Otherwise (or when all custom tab stops are behind the text), advance to the next default interval
var nextLocation = (Math.floor(aWidth / defaultInterval) + 1) * defaultInterval;
// If aWidth exceeds the last tab stop, dynamically calculate the next
// tab location using the default tab interval.
var defaultInterval = [_currentParagraph defaultTabInterval] || 28.0;
var nextLocation = CEIL((aWidth + 1.0) / defaultInterval) * defaultInterval;
return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextLocation];
}
@@ -186,32 +174,10 @@ var _CPTypesetterDecimalSeparator = function()
advancements:(CPArray)advancements
lineCount:(unsigned)lineCount
sameLine:(BOOL)sameLine
wordWrapped:(BOOL)wordWrapped
rightMargin:(float)rightMargin
{
// 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;
}
var myX = 0,
rect = CGRectMake(lineOrigin.x, lineOrigin.y, _lineWidth, _lineHeight),
containerSize = aContainer._size;
[_layoutManager _appendNewLineFragmentInTextContainer:_currentTextContainer forGlyphRange:lineRange];
@@ -228,48 +194,18 @@ var _CPTypesetterDecimalSeparator = function()
break;
case CPCenterTextAlignment:
myX = (availableWidth - visibleWidth) / 2;
myX = (containerSize.width - _lineWidth) / 2;
break;
case CPRightTextAlignment:
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;
}
}
myX = containerSize.width - _lineWidth;
break;
}
[_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange];
[_layoutManager _setAdvancements:advancements forGlyphRange:lineRange];
// fix the _lineFragments when fontsizes differ
//fix the _lineFragments when fontsizes differ
var l = _lineFragments.length;
for (var i = 0 ; i < l ; i++)
@@ -305,10 +241,8 @@ var _CPTypesetterDecimalSeparator = function()
_lineBase = 0;
_lineWidth = 0;
// containerSizeWidth is the width inside the line fragment padding at both ends.
var containerSize = [_currentTextContainer containerSize],
lineFragmentPadding = [_currentTextContainer lineFragmentPadding],
containerSizeWidth = containerSize.width - 2.0 * lineFragmentPadding,
containerSizeWidth = containerSize.width,
containerSizeHeight = containerSize.height,
lineRange = CPMakeRange(glyphIndex, 0),
wrapRange = CPMakeRange(0, 0),
@@ -317,7 +251,7 @@ var _CPTypesetterDecimalSeparator = function()
isTabStop = NO,
isAttachment = NO,
isWordWrapped = NO,
numberOfGlyphs = [_textStorage length],
numberOfGlyphs= [_textStorage length],
leading,
numLines = 0,
theString = [_textStorage string],
@@ -344,11 +278,7 @@ var _CPTypesetterDecimalSeparator = function()
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
@@ -363,7 +293,7 @@ var _CPTypesetterDecimalSeparator = function()
for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++)
{
// check whether there is any change in the attributes from here on
// check whether there any change in the attributes from here on
if (!CPLocationInRange(glyphIndex, _attributesRange))
{
_currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange];
@@ -397,6 +327,15 @@ var _CPTypesetterDecimalSeparator = function()
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
isFirstLineOfLayout = NO;
}
// Calculate the right wrapping margin based on tail indent
var tailIndent = [_currentParagraph tailIndent];
if (tailIndent > 0.0)
rightMargin = tailIndent;
else if (tailIndent < 0.0)
rightMargin = containerSizeWidth + tailIndent;
else
rightMargin = containerSizeWidth;
// Handle the layout's very first line indentation
if (isFirstLineOfLayout)
@@ -480,10 +419,10 @@ var _CPTypesetterDecimalSeparator = function()
// We are processing characters, so we are no longer at the start of a physical line
isStartOfPhysicalLine = NO;
var currentCharCode = theString.charCodeAt(glyphIndex),
var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons
rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor;
switch (currentCharCode)
switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char.
{
case CPAttachmentCharacter:
{
@@ -510,23 +449,12 @@ var _CPTypesetterDecimalSeparator = function()
if (viewSize.height > _lineHeight)
_lineHeight = viewSize.height - descent + leading;
// An attachment wider than the container stays on the line it overflows, so a break
// there keeps the line's height and baseline with the attachment's size applied.
// Otherwise the attachment would be placed on the baseline of the text before it,
// and drawn above its line.
if (CPMaxRange(wrapRange) === CPMaxRange(lineRange))
{
wrapRange._height = _lineHeight;
wrapRange._base = _lineBase;
}
ascent = viewSize.height;
break;
}
case 9: // '\t'
{
// Measure against the actual text position before the tab stop
var nextTab = [self textTabForWidth:prevRangeWidth + lineOrigin.x writingDirection:0];
var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0];
isTabStop = YES;
@@ -553,16 +481,7 @@ var _CPTypesetterDecimalSeparator = function()
tabAlignment = [nextTab alignment];
// Mathematically offset the tab character's right boundary
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)
if (tabAlignment === CPCenterTextAlignment)
{
rangeWidth = (tabLocation - nextSegmentWidth / 2.0) - lineOrigin.x;
}
@@ -582,7 +501,7 @@ var _CPTypesetterDecimalSeparator = function()
}
else
{
rangeWidth = prevRangeWidth + 28.0; // standard fallback spacer
rangeWidth += 28.0; // standard fallback spacer
}
break;
}
@@ -592,6 +511,10 @@ var _CPTypesetterDecimalSeparator = function()
wrapRange._height = _lineHeight;
wrapRange._base = _lineBase;
// Optimization: Start measuring from the next character to avoid O(n^2)
// string width calculation within a line since spaces do not carry ligatures or kerning.
// Only reset the measuring range if the next character is NOT another space.
// This prevents compounded subpixel rounding errors with contiguous spaces.
if (theString.charCodeAt(glyphIndex + 1) !== 32)
{
currentAnchor = rangeWidth;
@@ -621,12 +544,12 @@ var _CPTypesetterDecimalSeparator = function()
isNewline = YES;
isWordWrapped = YES;
glyphIndex = CPMaxRange(lineRange) - 1;
glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character
}
if (isNewline || isTabStop || isAttachment)
{
if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline wordWrapped:isWordWrapped rightMargin:rightMargin])
if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline])
return;
if (isTabStop || isAttachment)
@@ -649,11 +572,12 @@ var _CPTypesetterDecimalSeparator = function()
{
_currentTextContainer = textContainers[++_indexOfCurrentContainer];
containerSize = [_currentTextContainer containerSize];
lineFragmentPadding = [_currentTextContainer lineFragmentPadding];
containerSizeWidth = containerSize.width - 2.0 * lineFragmentPadding;
containerSizeWidth = containerSize.width;
containerSizeHeight = containerSize.height;
}
// If this is a soft wrap (isWordWrapped), next line gets headIndent.
// If it was a paragraph return, it gets firstLineHeadIndent.
isFirstLineOfParagraph = !isWordWrapped;
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
@@ -679,12 +603,11 @@ var _CPTypesetterDecimalSeparator = function()
}
}
// Flush remaining characters
// this is to "flush" the remaining characters
if (lineRange.length)
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO wordWrapped:NO rightMargin:rightMargin];
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO];
// 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);
var rect = CGRectMake(1, lineOrigin.y - descent, containerSizeWidth, currentFontLineHeight);
[_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer];
var fragment = [_layoutManager._lineFragments lastObject];
+4 -353
View File
@@ -25,7 +25,6 @@
@import "CPFontManager.j"
@import "CPParagraphStyle.j"
@import "_CPTableTextAttachment.j"
@import "CPTextList.j"
@global CPLeftTextAlignment
@global CPRightTextAlignment
@@ -58,209 +57,6 @@ var cp1252Map = {
0x9E: 0x017E, 0x9F: 0x0178
};
/*
The marker format names of the RTF number formats (\levelnfc). 23 is a bullet.
*/
var _CPRTFNumberFormatNames = {
0: "decimal",
1: "upper-roman",
2: "lower-roman",
3: "upper-alpha",
4: "lower-alpha"
},
_CPRTFBulletFormats = {
"\u2022": "{disc}",
"\u25E6": "{circle}",
"\u25AA": "{square}",
"\u2043": "{hyphen}",
"\u25C6": "{diamond}",
"\u2713": "{check}",
"\u25A1": "{box}"
};
/*
Returns the index just after the group that opens with the brace at anOpenIndex in rtf. Escaped
braces, \{ and \}, do not open or close groups.
*/
var _CPRTFGroupEnd = function(rtf, anOpenIndex)
{
var depth = 0;
for (var i = anOpenIndex; i < rtf.length; i++)
{
var character = rtf.charAt(i);
if (character === "\\")
i++;
else if (character === "{")
depth++;
else if (character === "}" && --depth === 0)
return i + 1;
}
return rtf.length;
};
/*
Returns the groups in rtf that open with aPattern, a regular expression that matches the
opening brace and the group's first control word.
*/
var _CPRTFGroups = function(rtf, aPattern)
{
var groups = [],
match;
aPattern.lastIndex = 0;
while ((match = aPattern.exec(rtf)))
{
var end = _CPRTFGroupEnd(rtf, match.index);
groups.push(rtf.substring(match.index, end));
aPattern.lastIndex = end;
}
return groups;
};
/*
Decodes the text of a list level, the content of its \leveltext destination after the length
byte, into parts: strings, and numbers for the levels whose item numbers take their place. A
\'hh escape with a value below 9 is a level number; any other is a character.
*/
var _CPRTFLevelTextParts = function(aLevelText)
{
var parts = [],
literal = "",
i = 0;
while (i < aLevelText.length)
{
var character = aLevelText.charAt(i);
if (character === ";")
break;
if (character !== "\\")
{
literal += character;
i++;
continue;
}
var hexMatch = /^\\'([0-9a-fA-F]{2})/.exec(aLevelText.substring(i)),
unicodeMatch = /^\\u(-?\d+) ?\??/.exec(aLevelText.substring(i)),
wordMatch = /^\\[a-z]+-?\d* ?/.exec(aLevelText.substring(i));
if (hexMatch)
{
var value = parseInt(hexMatch[1], 16);
if (value < 9)
{
if (literal)
parts.push(literal);
literal = "";
parts.push(value);
}
else
literal += String.fromCharCode(value);
i += hexMatch[0].length;
}
else if (unicodeMatch)
{
var code = parseInt(unicodeMatch[1], 10);
literal += String.fromCharCode(code < 0 ? code + 65536 : code);
i += unicodeMatch[0].length;
}
else if (wordMatch)
i += wordMatch[0].length;
else
{
literal += aLevelText.charAt(i + 1);
i += 2;
}
}
if (literal)
parts.push(literal);
return parts;
};
/*
Returns the definition of one list level, {format, options, start}, from its \listlevel group.
Cocoa writes the marker format in \levelmarker. For other writers, the format is made from the
number format and the level's text.
*/
var _CPRTFListLevelDefinition = function(aLevelGroup, aLevel)
{
var markerMatch = /\{\\\*\\levelmarker ?((?:\\.|[^\\}])*)\}/.exec(aLevelGroup),
startMatch = /\\levelstartat(-?\d+)/.exec(aLevelGroup),
numberFormatMatch = /\\levelnfc(\d+)/.exec(aLevelGroup),
levelTextMatch = /\{\\leveltext(?:\\leveltemplateid-?\d+)? ?\\'[0-9a-fA-F]{2}((?:\\.|[^\\}])*)\}/.exec(aLevelGroup),
parts = levelTextMatch ? _CPRTFLevelTextParts(levelTextMatch[1]) : [],
prepends = /\\levelprepend(?![a-z])/.test(aLevelGroup),
format = nil;
for (var i = 0; i < parts.length; i++)
if (typeof parts[i] === "number" && parts[i] < aLevel)
prepends = YES;
if (markerMatch)
format = markerMatch[1].replace(/\\([\\{}])/g, "$1");
else
{
var numberFormat = numberFormatMatch ? parseInt(numberFormatMatch[1], 10) : 0;
if (numberFormat === 23)
{
var bullet = parts.join("");
format = _CPRTFBulletFormats[bullet] || bullet || CPTextListMarkerDisc;
}
else
{
// The text around this level's own number. Text after an enclosing level's number
// ends that level's marker, which is prepended, so only text before any number is
// this level's prefix. Its suffix runs up to the next number.
var name = "{" + (_CPRTFNumberFormatNames[numberFormat] || "decimal") + "}",
prefix = "",
suffix = "",
found = NO,
seenEnclosingNumber = NO;
for (var i = 0; i < parts.length; i++)
{
if (parts[i] === aLevel)
found = YES;
else if (typeof parts[i] === "number")
{
if (found)
break;
seenEnclosingNumber = YES;
}
else if (found)
suffix += parts[i];
else if (!seenEnclosingNumber)
prefix += parts[i];
}
format = prefix + name + suffix;
}
}
return {
format: format,
options: prepends ? CPTextListPrependEnclosingMarker : 0,
start: startMatch ? parseInt(startMatch[1], 10) : 1
};
};
// Hold the attributes of the current run
@implementation _RTFAttribute : CPObject
{
@@ -280,13 +76,6 @@ var _CPRTFListLevelDefinition = function(aLevelGroup, aLevel)
CPTabStopType _nextTabType;
int superscript;
float baselineOffset;
// The first line indent, \fi, is an offset from the left indent, \li, in RTF.
float _firstLineOffset;
// The RTF list (\lsN) and level (\ilvlN) of a list item, or 0 and 0.
int _listNumber;
int _listLevel;
}
- (id)init
@@ -321,9 +110,6 @@ var _CPRTFListLevelDefinition = function(aLevelGroup, aLevel)
mynew._nextTabType = _nextTabType;
mynew.superscript = superscript;
mynew.baselineOffset = baselineOffset;
mynew._firstLineOffset = _firstLineOffset;
mynew._listNumber = _listNumber;
mynew._listLevel = _listLevel;
return mynew;
}
@@ -376,9 +162,6 @@ var _CPRTFListLevelDefinition = function(aLevelGroup, aLevel)
paragraph = [[CPParagraphStyle defaultParagraphStyle] mutableCopy];
_tabChanged = NO;
_nextTabType = CPLeftTabStopType;
_firstLineOffset = 0.0;
_listNumber = 0;
_listLevel = 0;
}
- (void)resetFont
@@ -559,17 +342,6 @@ var kRgsymRtf = {
CPMutableArray _tableRows;
CPMutableArray _currentRow;
CPString _currentCellText;
// Text lists: the level definitions of each RTF list by its \listid, the list of each
// override by its \ls number, and the text lists made for each override, one for each level.
Object _listDefinitions;
Object _listOverrides;
Object _textListsByOverride;
// Unicode: the number of fallback characters after each \uN (\ucN), and how many of them are
// still to be skipped.
int _unicodeFallbackCount;
int _fallbackCharactersToSkip;
}
- (id)init
@@ -595,13 +367,6 @@ var kRgsymRtf = {
_currentRow = nil;
_currentCellText = "";
_listDefinitions = {};
_listOverrides = {};
_textListsByOverride = {};
_unicodeFallbackCount = 1;
_fallbackCharactersToSkip = 0;
// Safe Initialization
_currentRun = [_RTFAttribute new];
}
@@ -1069,51 +834,16 @@ var kRgsymRtf = {
_currentRun.fontSize = parseInt(param) / 2;
break;
case "fi": // first line indent, an offset from the left indent
case "fi": // first line indent
if (_currentRun)
{
_currentRun._firstLineOffset = parseInt(param) / 20.0;
[_currentRun.paragraph setFirstLineHeadIndent:[_currentRun.paragraph headIndent] + _currentRun._firstLineOffset];
}
[_currentRun.paragraph setFirstLineHeadIndent:parseInt(param) / 20.0];
break;
case "li": // left indent / head indent
if (_currentRun)
{
[_currentRun.paragraph setHeadIndent:parseInt(param) / 20.0];
[_currentRun.paragraph setFirstLineHeadIndent:[_currentRun.paragraph headIndent] + _currentRun._firstLineOffset];
}
break;
case "ls": // the RTF list of a list item
if (_currentRun)
{
_currentRun._listNumber = parseInt(param);
[self _applyTextListsToCurrentRun];
}
break;
case "ilvl": // the level of a list item
if (_currentRun)
{
_currentRun._listLevel = parseInt(param);
[self _applyTextListsToCurrentRun];
}
break;
case "uc": // the number of fallback characters after each Unicode character
_unicodeFallbackCount = parseInt(param) || 0;
break;
case "u": // a Unicode character, followed by its fallback characters
var code = parseInt(param);
// Fallback characters are skipped only in text that is kept.
if (_curState === 0)
_fallbackCharactersToSkip = _unicodeFallbackCount;
return String.fromCharCode(code < 0 ? code + 65536 : code);
case "ri": // right indent / tail indent
if (_currentRun)
[_currentRun.paragraph setTailIndent:parseInt(param) / 20.0];
@@ -1202,76 +932,6 @@ var kRgsymRtf = {
return [self _translateKeyword:keyword parameter:param fParameter:fParam];
}
/*
Reads the list table and the list override table, which are destinations the parser otherwise
skips, before the text is parsed. Paragraphs then refer to the lists with \lsN and \ilvlN.
*/
- (void)_parseListTables:(CPString)rtf
{
var lists = _CPRTFGroups(rtf, /\{\\list(?![a-z])/g);
for (var i = 0; i < lists.length; i++)
{
// Only the list itself has a \listid; its levels have \leveltemplateid.
var idMatch = /\\listid(-?\d+)/.exec(lists[i]),
levels = _CPRTFGroups(lists[i], /\{\\listlevel(?![a-z])/g),
definitions = [];
if (!idMatch)
continue;
for (var level = 0; level < levels.length; level++)
definitions.push(_CPRTFListLevelDefinition(levels[level], level));
_listDefinitions[idMatch[1]] = definitions;
}
var overrides = _CPRTFGroups(rtf, /\{\\listoverride(?![a-z])/g);
for (var i = 0; i < overrides.length; i++)
{
var idMatch = /\\listid(-?\d+)/.exec(overrides[i]),
numberMatch = /\\ls(\d+)/.exec(overrides[i]);
if (idMatch && numberMatch)
_listOverrides[numberMatch[1]] = idMatch[1];
}
}
/*
Gives the current run's paragraph the text lists of its RTF list and level. The text lists of
an RTF list are made once, one for each level, so the items of a list share them and are
numbered together.
*/
- (void)_applyTextListsToCurrentRun
{
var listNumber = _currentRun._listNumber;
if (!listNumber)
{
[_currentRun.paragraph setTextLists:[]];
return;
}
var definitions = _listDefinitions[_listOverrides[listNumber]] || [],
textLists = _textListsByOverride[listNumber];
if (!textLists)
textLists = _textListsByOverride[listNumber] = [];
for (var level = 0; level <= _currentRun._listLevel; level++)
{
if (!textLists[level])
{
var definition = definitions[level] || {format: CPTextListMarkerDisc, options: 0, start: 1};
textLists[level] = [[CPTextList alloc] initWithMarkerFormat:definition.format options:definition.options startingItemNumber:definition.start];
}
}
[_currentRun.paragraph setTextLists:textLists.slice(0, _currentRun._listLevel + 1)];
}
- (void)_appendPlainString:(CPString) aString
{
if (_inTableActive)
@@ -1293,8 +953,6 @@ var kRgsymRtf = {
_currentParseIndex = -1;
[self _parseListTables:rtf];
var len = rtf.length,
tmp = '',
ch = '',
@@ -1377,9 +1035,7 @@ var kRgsymRtf = {
if (_hexreturn)
{
// Only append decoded characters if we are in the active body state
if (ch.length > 0 && _curState === 0 && _fallbackCharactersToSkip > 0)
_fallbackCharactersToSkip--;
else if (ch.length > 0 && _curState === 0)
if (ch.length > 0 && _curState === 0)
{
var byteVal = parseInt(ch, 16);
var unicodeVal = byteVal;
@@ -1409,12 +1065,7 @@ var kRgsymRtf = {
default:
lastchar = 0;
if (_curState == 0 && _fallbackCharactersToSkip > 0)
{
// A fallback character for the Unicode character before it.
_fallbackCharactersToSkip--;
}
else if (_curState == 0)
if (_curState == 0)
{
[self _appendPlainString:tmp];
}
+4 -254
View File
@@ -28,7 +28,6 @@
@import "CPGraphics.j"
@import "CPFontManager.j"
@import "_CPTableTextAttachment.j"
@import "CPTextList.j"
@global CPForegroundColorAttributeName
@global CPBackgroundColorAttributeName
@@ -58,44 +57,6 @@ var PAPERSIZE = @"PaperSize",
function _points2twips(a) { return (a) * 20.0; }
/*
Escapes text for RTF. Backslashes and braces are escaped, and every character outside ASCII
is written as a Unicode control word, \uN, with no fallback characters (\uc0), as Cocoa
writes it. Values above 32767 are written as negative numbers, as RTF requires.
*/
var _CPRTFEscapeUnicode = function(aString)
{
return aString.replace(/[^\x00-\x7f]/g, function(aCharacter)
{
var code = aCharacter.charCodeAt(0);
return "\\uc0\\u" + (code > 32767 ? code - 65536 : code) + " ";
});
};
/*
Returns aNumber as two hexadecimal digits, for the \'hh escapes of a list level's text.
*/
var _CPRTFHexByte = function(aNumber)
{
return ("0" + (aNumber & 0xff).toString(16)).slice(-2);
};
/*
The RTF number format (\levelnfc) of each marker format name. Bullets are 23. Hexadecimal and
octal have no RTF number format, so other readers show them as decimal numbers; Cocoa reads
the marker format itself from \levelmarker.
*/
var _CPRTFLevelNumberFormats = {
"decimal": 0,
"upper-roman": 1,
"lower-roman": 2,
"upper-alpha": 3,
"upper-latin": 3,
"lower-alpha": 4,
"lower-latin": 4
};
@implementation _CPRTFProducer : CPObject
{
CPAttributedString text;
@@ -107,11 +68,6 @@ var _CPRTFLevelNumberFormats = {
CPColor fgColor;
CPColor bgColor;
CPColor ulColor;
// The outermost text lists in the text, each written as one RTF list, and for each, the text
// list of every level, as first met.
CPArray textLists;
CPArray textListLevels;
}
// MARK: -
@@ -138,9 +94,6 @@ var _CPRTFLevelNumberFormats = {
fgColor = [CPColor blackColor];
bgColor= [CPColor whiteColor];
textLists = [];
textListLevels = [];
}
return self;
@@ -293,7 +246,6 @@ var _CPRTFLevelNumberFormats = {
result = [CPString stringWithString:@"{\\rtf1\\ansi"];
result += [self fontTable];
result += [self colorTable];
result += [self listTables];
result += [self documentAttributes];
return result;
@@ -325,7 +277,7 @@ var _CPRTFLevelNumberFormats = {
if (!num)
[colorDict setObject:num = [CPNumber numberWithInt:[colorDict count] + 1]
forKey:[color cssString]];
forKey:[color cssString]];
return [num intValue];
}
@@ -361,8 +313,7 @@ var _CPRTFLevelNumberFormats = {
break;
}
// In RTF the first line indent is an offset from the left indent.
var twips = _points2twips([paraStyle firstLineHeadIndent] - [paraStyle headIndent]);
var twips = _points2twips([paraStyle firstLineHeadIndent]);
if (twips != 0.0)
headerString += [CPString stringWithFormat:@"\\fi%d", twips];
@@ -424,165 +375,12 @@ var _CPRTFLevelNumberFormats = {
headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])];
}
// A list item names its RTF list and its level.
var lists = [paraStyle textLists];
if ([lists count])
headerString += [CPString stringWithFormat:@"\\ls%d\\ilvl%d", [self listNumberForTextLists:lists], [lists count] - 1];
return headerString;
}
// MARK: -
// MARK: Text lists
/*
Returns the number of the RTF list for a paragraph's text lists, and records the list of each
level. Each outermost text list becomes one RTF list, with one level for each nesting depth.
*/
- (int)listNumberForTextLists:(CPArray)someLists
{
var index = [textLists indexOfObjectIdenticalTo:someLists[0]];
if (index === CPNotFound)
{
index = textLists.length;
textLists.push(someLists[0]);
textListLevels.push([]);
}
var levels = textListLevels[index];
for (var level = 0; level < someLists.length; level++)
if (!levels[level])
levels[level] = someLists[level];
return index + 1;
}
/*
Returns the list table and the list override table for the text lists in the text. Each RTF
list is a hybrid list with one level for each nesting depth, and one override, \lsN, that
paragraphs refer to.
*/
- (CPString)listTables
{
if (!textLists.length)
return @"";
var listTable = @"{\\*\\listtable",
overrideTable = @"{\\*\\listoverridetable";
for (var i = 0; i < textLists.length; i++)
{
var listNumber = i + 1,
levels = textListLevels[i];
listTable += "{\\list\\listtemplateid" + listNumber + "\\listhybrid";
for (var level = 0; level < levels.length; level++)
listTable += [self listLevelString:levels atLevel:level templateID:listNumber * 100 + level + 1];
listTable += "{\\listname ;}\\listid" + listNumber + "}";
overrideTable += "{\\listoverride\\listid" + listNumber + "\\listoverridecount0\\ls" + listNumber + "}";
}
return listTable + "}\n" + overrideTable + "}\n";
}
/*
Returns the RTF definition of one list level. Cocoa reads the marker format from the
\levelmarker destination and the prepending of the enclosing marker from \levelprepend. Other
readers use the number format (\levelnfc), the starting number (\levelstartat) and the
level's text (\leveltext), in which \'0N stands for the number of level N.
*/
- (CPString)listLevelString:(CPArray)someLevels atLevel:(int)aLevel templateID:(int)aTemplateID
{
var list = someLevels[aLevel],
format = [list markerFormat],
match = format.match(/\{([a-z-]+)\}/),
name = match ? match[1] : "",
numberFormat = [list isOrdered] ? (_CPRTFLevelNumberFormats[name] || 0) : 23,
parts = [self levelTextParts:someLevels atLevel:aLevel],
levelText = "",
levelNumbers = "",
length = 0,
indent = 720 * (aLevel + 1);
for (var i = 0; i < parts.length; i++)
{
var part = parts[i];
if (typeof part === "number")
{
length++;
levelText += "\\'" + _CPRTFHexByte(part);
levelNumbers += "\\'" + _CPRTFHexByte(length);
}
else
{
length += part.length;
levelText += _CPRTFEscapeUnicode(part.replace(/[\\{};]/g, function(aCharacter)
{
return "\\'" + _CPRTFHexByte(aCharacter.charCodeAt(0));
}));
}
}
return "{\\listlevel\\levelnfc" + numberFormat + "\\levelnfcn" + numberFormat +
"\\leveljc0\\leveljcn0\\levelfollow0\\levelstartat" + [list startingItemNumber] +
"\\levelspace360\\levelindent0{\\*\\levelmarker " + _CPRTFEscapeUnicode(format.replace(/([\\{}])/g, "\\$1")) + "}" +
(([list listOptions] & CPTextListPrependEnclosingMarker) ? "\\levelprepend" : "") +
"{\\leveltext\\leveltemplateid" + aTemplateID + "\\'" + _CPRTFHexByte(length) + levelText + ";}" +
"{\\levelnumbers" + levelNumbers + ";}\\fi-720\\li" + indent + "\\lin" + indent + " }";
}
/*
Returns the parts of a list level's text: strings, and the level numbers whose item numbers
take their place. A bullet level is its bullet. A numbered level is the text around its number,
after the enclosing level's text when the list prepends the enclosing marker.
*/
- (CPArray)levelTextParts:(CPArray)someLevels atLevel:(int)aLevel
{
var list = someLevels[aLevel],
format = [list markerFormat];
if (![list isOrdered])
return [[list markerForItemNumber:1]];
var match = format.match(/\{[a-z-]+\}/),
parts = [];
if (aLevel > 0 && ([list listOptions] & CPTextListPrependEnclosingMarker))
parts = [self levelTextParts:someLevels atLevel:aLevel - 1];
if (match.index > 0)
parts.push(format.substring(0, match.index));
parts.push(aLevel);
if (match.index + match[0].length < format.length)
parts.push(format.substring(match.index + match[0].length));
return parts;
}
- (CPString)runStringForString:(CPString) substring
attributes:(CPDictionary) attributes
paragraphStart:(BOOL) first
{
return [self runStringForString:substring attributes:attributes paragraphStart:first listMarker:NO];
}
/*
As -runStringForString:attributes:paragraphStart:. When isListMarker is YES, the run is the
marker at the start of a list item, and is written in a \listtext group, as Cocoa writes it.
Readers that understand lists number the items themselves; others show the marker text.
*/
- (CPString)runStringForString:(CPString) substring
attributes:(CPDictionary) attributes
paragraphStart:(BOOL) first
listMarker:(BOOL) isListMarker
{
var unwrap = function(obj) {
if (!obj) return null;
@@ -906,18 +704,11 @@ var _CPRTFLevelNumberFormats = {
}
}
// An image attachment is not written yet, so its attachment character is left out.
substring = substring.replace(/\uFFFC/g, '');
substring = substring.replace(/\\/g, '\\\\');
substring = substring.replace(/\n/g, '\\par\n');
substring = substring.replace(/\t/g, '\\tab ');
substring = substring.replace(/{/g, '\\{');
substring = substring.replace(/}/g, '\\}');
substring = _CPRTFEscapeUnicode(substring);
if (isListMarker)
substring = "{\\listtext " + substring + "}";
if (!first)
{
@@ -961,29 +752,13 @@ var _CPRTFLevelNumberFormats = {
substring,
runString;
var runStart = CPMaxRange(currRange);
attributes = [text attributesAtIndex:runStart
attributes = [text attributesAtIndex:CPMaxRange(currRange)
longestEffectiveRange:currRange
inRange:completeRange];
// The longest effective range can reach back before the run's start, after a list marker
// that shares its attributes was written as a run of its own.
if (currRange.location < runStart)
currRange = CPMakeRange(runStart, CPMaxRange(currRange) - runStart);
// A list item's marker, a tab, the marker and a tab, is written as a run of its own.
var markerLength = paragraphStart ? [self listMarkerLengthAtIndex:currRange.location attributes:attributes] : 0,
isListMarker = markerLength > 0 && markerLength <= currRange.length;
if (isListMarker)
currRange.length = markerLength;
substring = [string substringWithRange:currRange];
runString = [self runStringForString:substring
attributes:attributes
paragraphStart:paragraphStart
listMarker:isListMarker];
paragraphStart:paragraphStart];
result += runString;
@@ -997,31 +772,6 @@ var _CPRTFLevelNumberFormats = {
}
/*
Returns the length of the marker at the start of a list item, a tab, the marker and a tab, or
0 when the paragraph is not a list item or does not start with a marker.
*/
- (unsigned)listMarkerLengthAtIndex:(unsigned)anIndex attributes:(CPDictionary)attributes
{
var string = [text string];
if (![[[attributes objectForKey:CPParagraphStyleAttributeName] textLists] count] || string.charAt(anIndex) !== "\t")
return 0;
for (var i = anIndex + 1; i < string.length; i++)
{
var character = string.charAt(i);
if (character === "\t")
return i - anIndex + 1;
if (character === "\n")
break;
}
return 0;
}
- (CPString)RTFDStringFromAttributedString:(CPAttributedString)aText
documentAttributes:(CPDictionary)dict
{
+1 -38
View File
@@ -19,8 +19,7 @@
*/
@import "CPView.j"
@class CPTextView;
@class CPTextContainer;
@import "CPTextView.j"
@import "CPTextField.j"
@import <Foundation/CPAttributedString.j>
@@ -556,39 +555,3 @@
}
@end
var _CPTableTextAttachmentHeadersKey = @"_CPTableTextAttachmentHeaders",
_CPTableTextAttachmentRowsKey = @"_CPTableTextAttachmentRows",
_CPTableTextAttachmentWidthKey = @"_CPTableTextAttachmentWidth",
_CPTableTextAttachmentIsEditableKey = @"_CPTableTextAttachmentIsEditable",
_CPTableTextAttachmentAcceptsRichTextKey = @"_CPTableTextAttachmentAcceptsRichText";
@implementation _CPTableTextAttachment (CPCoding)
/*
Decodes the table from its headers, rows, width and settings, and builds its cells again.
The cells are not decoded as subviews; they are made from the decoded text.
*/
- (id)initWithCoder:(CPCoder)aCoder
{
return [self initWithHeaders:[aCoder decodeObjectForKey:_CPTableTextAttachmentHeadersKey]
rows:[aCoder decodeObjectForKey:_CPTableTextAttachmentRowsKey]
width:[aCoder decodeFloatForKey:_CPTableTextAttachmentWidthKey] || 500.0
isEditable:[aCoder decodeBoolForKey:_CPTableTextAttachmentIsEditableKey]
acceptsRichText:[aCoder decodeBoolForKey:_CPTableTextAttachmentAcceptsRichTextKey]];
}
/*
Encodes the table's current text, including edits made in its cells, its width and its
settings. The cell views are not encoded.
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeObject:[self headers] forKey:_CPTableTextAttachmentHeadersKey];
[aCoder encodeObject:[self rows] forKey:_CPTableTextAttachmentRowsKey];
[aCoder encodeFloat:CGRectGetWidth([self frame]) forKey:_CPTableTextAttachmentWidthKey];
[aCoder encodeBool:_isEditable forKey:_CPTableTextAttachmentIsEditableKey];
[aCoder encodeBool:_acceptsRichText forKey:_CPTableTextAttachmentAcceptsRichTextKey];
}
@end
+1 -3
View File
@@ -814,12 +814,10 @@ CPThemeStateKeyWindow = CPThemeState("keyWindow");
CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular");
CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall");
CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
CPThemeStateControlSizeLarge = CPThemeState("controlSizeLarge");
CPThemeStateAlternateState = CPThemeState("alternate");
CPThemeStateComposedControl = CPThemeState("composed");
CPThemeStateWindowsPlatform = CPThemeState("windowsPlatform");
CPThemeStateNormalString = String(CPThemeStateNormal);
CPThemeStateHUD = CPThemeState("hud");
@implementation _CPThemeAttribute : CPObject
+72 -61
View File
@@ -32,7 +32,6 @@
@import "CPText.j"
@import "CPTextField.j"
@import "CPWindow_Constants.j"
@import "CPCompatibility.j"
@class _CPTokenFieldTokenCloseButton
@class _CPTokenFieldTokenDisclosureButton
@@ -64,6 +63,8 @@ var CPTokenFieldDelegate_tokenField_hasMenuForRepresentedObject_
CPTokenFieldDelegate_tokenField_representedObjectForEditingString_ = 1 << 6;
#if PLATFORM(DOM)
var CPTokenFieldDOMInputElement = nil,
CPTokenFieldDOMPasswordInputElement = nil,
CPTokenFieldDOMStandardInputElement = nil,
@@ -78,6 +79,8 @@ var CPTokenFieldDOMInputElement = nil,
CPTokenFieldBlurHandler = nil;
#endif
var CPScrollDestinationNone = 0,
CPScrollDestinationLeft = 1,
CPScrollDestinationRight = 2;
@@ -391,10 +394,10 @@ CPTokenFieldDeleteButtonType = 1;
if (![super becomeFirstResponder])
return NO;
if (CPDOMAvailable && CPTokenFieldInputOwner && [CPTokenFieldInputOwner window] !== [self window])
{
#if PLATFORM(DOM)
if (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];
@@ -420,74 +423,75 @@ CPTokenFieldDeleteButtonType = 1;
[self setNeedsLayout];
if (CPDOMAvailable)
#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])
{
var string = [self stringValue],
element = [self _inputElement],
font = [self currentValueForThemeAttribute:@"font"];
case CPCenterTextAlignment:
element.style.textAlign = "center";
break;
element.value = nil;
element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
element.style.font = [font cssString];
element.style.zIndex = 1000;
case CPRightTextAlignment:
element.style.textAlign = "right";
break;
switch ([self alignment])
{
case CPCenterTextAlignment:
element.style.textAlign = "center";
break;
default:
element.style.textAlign = "left";
}
case CPRightTextAlignment:
element.style.textAlign = "right";
break;
var contentRect = [self contentRectForBounds:[self bounds]];
default:
element.style.textAlign = "left";
}
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";
var contentRect = [self contentRectForBounds:[self bounds]];
[[CPRunLoop mainRunLoop] performBlock:function()
{
[_tokenScrollView documentView]._DOMElement.appendChild(element);
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";
// 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]];
[[CPRunLoop mainRunLoop] performBlock:function()
{
[_tokenScrollView documentView]._DOMElement.appendChild(element);
// This will prevent to jump to the focused element
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
// 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.focus();
[[CPRunLoop mainRunLoop] performBlock:function()
{
// This will prevent to jump to the focused element
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
element.focus();
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
CPTokenFieldInputOwner = self;
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
CPTokenFieldInputOwner = self;
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
[self textDidFocus:[CPNotification notificationWithName:CPTextFieldDidFocusNotification object:self userInfo:nil]];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
CPTokenFieldInputIsActive = YES;
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
if (document.attachEvent)
{
CPTokenFieldCachedSelectStartFunction = document.body.onselectstart;
CPTokenFieldCachedDragFunction = document.body.ondrag;
CPTokenFieldInputIsActive = YES;
document.body.ondrag = function () {};
document.body.onselectstart = function () {};
}
if (document.attachEvent)
{
CPTokenFieldCachedSelectStartFunction = document.body.onselectstart;
CPTokenFieldCachedDragFunction = document.body.ondrag;
document.body.ondrag = function () {};
document.body.onselectstart = function () {};
}
#endif
return YES;
}
@@ -523,7 +527,7 @@ CPTokenFieldDeleteButtonType = 1;
[self _updatePlaceholderState];
[self setNeedsLayout];
if (!CPDOMAvailable) return;
#if PLATFORM(DOM)
var element = [self _inputElement];
@@ -557,6 +561,8 @@ CPTokenFieldDeleteButtonType = 1;
document.body.ondrag = CPTokenFieldCachedDragFunction;
document.body.onselectstart = CPTokenFieldCachedSelectStartFunction;
}
#endif
}
- (void)mouseDown:(CPEvent)anEvent
@@ -624,12 +630,16 @@ CPTokenFieldDeleteButtonType = 1;
[objectValue addObject:[token representedObject]];
}
if (CPDOMAvailable && [self _editorValue])
#if PLATFORM(DOM)
if ([self _editorValue])
{
var token = [self _representedObjectForEditingString:[self _editorValue]];
[objectValue insertObject:token atIndex:_selectedRange.location];
}
#endif
return objectValue;
}
@@ -745,10 +755,9 @@ CPTokenFieldDeleteButtonType = 1;
// =============
// = TEXTFIELD =
// =============
#if PLATFORM(DOM)
- (DOMElement)_inputElement
{
if (!CPDOMAvailable) return nil;
if (!CPTokenFieldDOMInputElement)
{
CPTokenFieldDOMInputElement = document.createElement("input");
@@ -789,6 +798,7 @@ CPTokenFieldDeleteButtonType = 1;
return CPTokenFieldDOMInputElement;
}
#endif
- (CPString)_editorValue
{
@@ -1026,10 +1036,9 @@ CPTokenFieldDeleteButtonType = 1;
- (void)keyDown:(CPEvent)anEvent
{
if (CPDOMAvailable)
{
CPTokenFieldTextDidChangeValue = [self stringValue];
}
#if PLATFORM(DOM)
CPTokenFieldTextDidChangeValue = [self stringValue];
#endif
// Has to be enabled, and it also has to be editable or selectable.
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
@@ -1053,10 +1062,12 @@ CPTokenFieldDeleteButtonType = 1;
- (void)keyUp:(CPEvent)anEvent
{
if (CPDOMAvailable && [self stringValue] !== CPTokenFieldTextDidChangeValue)
#if PLATFORM(DOM)
if ([self stringValue] !== CPTokenFieldTextDidChangeValue)
{
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
}
#endif
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
+75 -94
View File
@@ -50,9 +50,6 @@
[self exposeBinding:@"contentArray"];
[self exposeBinding:@"sortDescriptors"];
[self exposeBinding:@"selectionIndexPaths"];
[self exposeBinding:@"selectionIndexPath"];
[self exposeBinding:@"selectedObjects"];
}
+ (CPSet)keyPathsForValuesAffectingContentArray
@@ -62,7 +59,7 @@
+ (CPSet)keyPathsForValuesAffectingArrangedObjects
{
return [CPSet setWithObjects:@"content", @"contentArray", @"sortDescriptors", @"childrenKeyPath"];
return [CPSet setWithObjects:@"content", @"sortDescriptors", @"childrenKeyPath"];
}
+ (CPSet)keyPathsForValuesAffectingSelectionIndexPath
@@ -72,27 +69,27 @@
+ (CPSet)keyPathsForValuesAffectingSelectedObjects
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"arrangedObjects"];
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingSelectedNodes
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"arrangedObjects"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsert
{
return [CPSet setWithObjects:@"editable"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsertChild
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"editable"];
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanAddChild
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"editable"];
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsert
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsertChild
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
- (id)init
@@ -118,8 +115,7 @@
}
- (void)prepareContent
{
[self _setContentArray:[CPArray arrayWithObject:[self newObject]]];
{[self _setContentArray:[CPArray arrayWithObject:[self newObject]]];
}
- (BOOL)preservesSelection { return _preservesSelection; }
@@ -148,8 +144,7 @@
- (void)setChildrenKeyPath:(CPString)aKeyPath
{
if (_childrenKeyPath === aKeyPath) return;
_childrenKeyPath = aKeyPath;
[self rearrangeObjects];
_childrenKeyPath = aKeyPath;[self rearrangeObjects];
}
- (CPString)countKeyPath { return _countKeyPath; }
@@ -171,21 +166,14 @@
if (![value isKindOfClass:[CPArray class]])
value = [CPArray arrayWithObject:value];
if (_contentObject === value)
return;
var oldSelectedObjects = nil,
oldSelectionIndexPaths = nil;
oldSelectionIndexPaths = nil;
if ([self preservesSelection])
oldSelectedObjects = [self selectedObjects];
else
oldSelectionIndexPaths = [self selectionIndexPaths];
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
[self willChangeValueForKey:@"contentArray"];
_contentObject = value;
[self _rearrangeObjects];
@@ -194,32 +182,23 @@
[self __setSelectedObjects:oldSelectedObjects];
else
[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
[self didChangeValueForKey:@"contentArray"];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)_setContentArray:(id)anArray { [self setContent:anArray]; }
- (void)_setContentArray:(id)anArray {[self setContent:anArray]; }
- (id)contentArray { return _contentObject; }
- (id)arrangedObjects { return _arrangedObjects; }
- (void)rearrangeObjects
{
[self _selectionWillChange];
[self willChangeValueForKey:@"arrangedObjects"];
[self _rearrangeObjects];
[self didChangeValueForKey:@"arrangedObjects"];
[self _selectionDidChange];
}
- (void)_rearrangeObjects
{
var oldSelectedObjects = nil,
oldSelectionIndexPaths = nil;
oldSelectionIndexPaths = nil;
if ([self preservesSelection])
oldSelectedObjects = [self selectedObjects];
@@ -237,7 +216,7 @@
- (void)__rebuildArrangedObjectsTree
{
var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil],
contentArray = [self contentArray];
contentArray = [self contentArray];
if (contentArray && [contentArray count] > 0)
{
@@ -265,7 +244,7 @@
for (var i = 0; i < count; i++)
{
var obj = [sortedObjects objectAtIndex:i],
node = [[CPTreeNode alloc] initWithRepresentedObject:obj];
node = [[CPTreeNode alloc] initWithRepresentedObject:obj];
if (_childrenKeyPath)
{
@@ -299,7 +278,11 @@
- (BOOL)setSelectionIndexPaths:(CPArray)indexPaths
{
return [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO];
[self _selectionWillChange];
var result = [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO];
[self _selectionDidChange];
return result;
}
- (void)_ensureTreeNodesExistForIndexPaths:(CPArray)indexPaths
@@ -351,8 +334,7 @@
if (needsRebuild)
{
[childNodes removeAllObjects];
var newNodes = [self _buildTreeNodesForObjects:expectedChildObjects];
[childNodes addObjectsFromArray:newNodes];
var newNodes = [self _buildTreeNodesForObjects:expectedChildObjects];[childNodes addObjectsFromArray:newNodes];
}
}
@@ -384,13 +366,21 @@
if ([_selectionIndexPaths isEqualToArray:newPaths])
return NO;
[self _selectionWillChange];
[self willChangeValueForKey:@"selectionIndexPaths"];
_selectionIndexPaths = [newPaths copy];
var binderClass = [[self class] _binderClassForBinding:@"selectionIndexPaths"];
if (binderClass)
{
var binding = [binderClass getBinding:@"selectionIndexPaths" forObject:self];
if (binding)
[binding reverseSetValueFor:@"selectionIndexPaths"];
}
[self didChangeValueForKey:@"selectionIndexPaths"];
[self _selectionDidChange];
return YES;
}
@@ -398,7 +388,9 @@
- (BOOL)addSelectionIndexPaths:(CPArray)indexPaths
{
var newPaths = [_selectionIndexPaths mutableCopy];
[newPaths addObjectsFromArray:indexPaths];
return [self setSelectionIndexPaths:newPaths];
}
@@ -412,7 +404,7 @@
- (CPArray)selectedNodes
{
var nodes = [CPMutableArray array],
count = [_selectionIndexPaths count];
count = [_selectionIndexPaths count];
for (var i = 0; i < count; i++)
{
@@ -426,17 +418,13 @@
- (CPArray)selectedObjects
{
var objects = [CPMutableArray array],
nodes = [self selectedNodes],
count = [nodes count];
nodes = [self selectedNodes],
count = [nodes count];
for (var i = 0; i < count; i++)
{
var representedObject = [[nodes objectAtIndex:i] representedObject];
if (representedObject)
[objects addObject:representedObject];
}
[objects addObject:[[nodes objectAtIndex:i] representedObject]];
return [_CPObservableArray arrayWithArray:objects];
return objects;
}
- (BOOL)__setSelectedObjects:(CPArray)objects
@@ -474,22 +462,22 @@
}
- (BOOL)canInsert { return [self isEditable]; }
- (BOOL)canInsertChild { return [self isEditable] && [_selectionIndexPaths count] > 0; }
- (BOOL)canInsertChild { return [self isEditable] &&[_selectionIndexPaths count] > 0; }
- (BOOL)canAddChild { return [self canInsertChild]; }
- (void)add:(id)sender
{
if (![self canInsert]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
selectionPath = [self selectionIndexPath];
var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject],
selectionPath = [self selectionIndexPath];
if (!selectionPath)
selectionPath = [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]];
var length = [selectionPath length],
lastIndex = [selectionPath indexAtPosition:length - 1],
insertPath = [selectionPath indexPathByRemovingLastIndex];
lastIndex = [selectionPath indexAtPosition:length - 1],
insertPath = [selectionPath indexPathByRemovingLastIndex];
insertPath = [insertPath indexPathByAddingIndex:lastIndex + 1];
@@ -501,10 +489,10 @@
if (![self canAddChild])
return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]],
childCount = [[parentNode childNodes] count],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]],
childCount = [[parentNode childNodes] count],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
@@ -513,8 +501,8 @@
{
if (![self canInsert]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0];
var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject],
indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0];
[self insertObject:newObject atArrangedObjectIndexPath:indexPath];
}
@@ -524,7 +512,7 @@
if (![self canInsertChild]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0];
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
@@ -536,7 +524,6 @@
- (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths
{
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
_disableSetContent = YES;
@@ -544,22 +531,21 @@
for (var i = 0; i < count; i++)
{
var object = [objects objectAtIndex:i],
path = [indexPaths objectAtIndex:i],
length = [path length];
path = [indexPaths objectAtIndex:i],
length = [path length];
if (length === 1)
{
[_contentObject insertObject:object atIndex:[path indexAtPosition:0]];
{[_contentObject insertObject:object atIndex:[path indexAtPosition:0]];
}
else
{
var parentPath = [path indexPathByRemovingLastIndex],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
if (parentNode)
{
var parentObj = [parentNode representedObject],
childIndex = [path indexAtPosition:length - 1];
childIndex = [path indexAtPosition:length - 1];
var children = [parentObj valueForKeyPath:_childrenKeyPath];
if (!children)
@@ -582,11 +568,9 @@
_disableSetContent = NO;
[self _rearrangeObjects];
if ([self selectsInsertedObjects])
[self setSelectionIndexPaths:indexPaths];
if ([self selectsInsertedObjects])[self setSelectionIndexPaths:indexPaths];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)remove:(id)sender
@@ -601,17 +585,16 @@
- (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths
{
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
_disableSetContent = YES;
var sortedPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)],
count = [sortedPaths count];
count = [sortedPaths count];
for (var i = count - 1; i >= 0; i--)
{
var path = [sortedPaths objectAtIndex:i],
length = [path length];
length = [path length];
if (length === 1)
{
@@ -620,15 +603,15 @@
else
{
var parentPath = [path indexPathByRemovingLastIndex],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
if (parentNode)
{
var parentObj = [parentNode representedObject],
childIndex = [path indexAtPosition:length - 1],
mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
childIndex = [path indexAtPosition:length - 1],
mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
if (mutableChildren && childIndex < [mutableChildren count])
if (mutableChildren && childIndex <[mutableChildren count])
[mutableChildren removeObjectAtIndex:childIndex];
}
}
@@ -641,7 +624,6 @@
_disableSetContent = NO;
[self _rearrangeObjects];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)moveNode:(CPTreeNode)node toIndexPath:(CPIndexPath)indexPath
@@ -650,19 +632,18 @@
}
- (void)moveNodes:(CPArray)nodes toIndexPath:(CPIndexPath)startingIndexPath
{
[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
{[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
}
@end
var CPTreeControllerAvoidsEmptySelection = @"CPTreeControllerAvoidsEmptySelection",
CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection",
CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects",
CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker",
CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath",
CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath",
CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath";
CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection",
CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects",
CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker",
CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath",
CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath",
CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath";
@implementation CPTreeController (CPCoding)
+64 -299
View File
@@ -17,41 +17,17 @@
*
* 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
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import <Foundation/CPIndexPath.j>
@import <Foundation/CPArray.j>
/*
* CPTreeNode implements the NSTreeNode contract.
* The _childNodes array contains only CPTreeNode instances.
* The representedObject property contains the application data.
* The _parentNode and _childNodes properties maintain a strict bidirectional relationship.
* The KVC mutation methods are the public mechanism to change the tree structure.
*/
@implementation CPTreeNode : CPObject
{
/*
* KVO notifications on parentNode are never delivered: there is no
* setParentNode: for the swizzling machinery to intercept, so there is
* no selector to instrument, in any code path.
*
* KVO notifications on childNodes are reliable for
* insertObject:inChildNodesAtIndex:, including same-parent and
* cross-parent moves: all detach paths for that method route through
* the KVC accessors. replaceObjectInChildNodesAtIndex:withObject:
* still detaches a same-parent replacement node by mutating
* _childNodes directly, bypassing the KVC proxy for that step; an
* observer of that node's former parent's childNodes can miss that
* specific removal, or see it reported as the wrong kind of change.
* Do not rely on childNodes observation during a same-parent replace;
* rely only on the state after the call returns.
*/
id _representedObject @accessors(readonly, property=representedObject);
CPTreeNode _parentNode @accessors(readonly, property=parentNode);
id _representedObject @accessors(property=representedObject);
CPTreeNode _parentNode @accessors(property=parentNode);
CPMutableArray _childNodes;
}
@@ -73,107 +49,27 @@
return self;
}
/*
* Route plain init through the designated initializer.
* Without this override, [[CPTreeNode alloc] init] leaves _childNodes unset.
* The first mutation call then fails against an undefined array.
*/
- (id)init
{
return [self initWithRepresentedObject:nil];
}
/*
* Return YES if adding aTreeNode below self makes a cycle.
* This method walks the parent chain.
* The operation time is proportional to the tree depth.
*/
- (BOOL)_wouldCreateCycleWithNode:(CPTreeNode)aTreeNode
{
for (var node = self; node; node = node._parentNode)
{
if (node === aTreeNode)
return YES;
}
return NO;
}
/*
* Enforce the NSTreeNode abstraction boundary.
* All children must be CPTreeNode instances.
*/
- (void)_validateChildNode:(id)aTreeNode
{
if (![aTreeNode isKindOfClass:[CPTreeNode class]])
{
[CPException raise:CPInvalidArgumentException
reason:"CPTreeNode children must be CPTreeNode instances."];
}
}
/*
* Remove a child node by delegating to the public KVC accessor.
* Use this method for internal structural changes across a parent
* boundary, so an observer of this node's childNodes sees the removal.
*/
- (void)_removeChildNode:(CPTreeNode)aNode
{
var index = [_childNodes indexOfObjectIdenticalTo:aNode];
/*
* A caller reaches this method only when aNode.parentNode already equals
* self (see the two call sites below). If self._childNodes does not
* actually contain aNode at that point, the parent/child relationship
* is already broken. indexPath raises for this identical class of
* inconsistency; silently returning here would hide the same problem
* instead of surfacing it.
*/
if (index === CPNotFound)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
}
[self removeObjectFromChildNodesAtIndex:index];
}
- (CPIndexPath)indexPath
{
if (!_parentNode)
return [CPIndexPath indexPathWithIndexes:[]];
var indexes = [],
node = self;
while (node._parentNode)
// If we have a parent, calculate path based on parent's path + our index
if (_parentNode)
{
var parent = node._parentNode,
index = [parent._childNodes indexOfObjectIdenticalTo:node];
if (index === CPNotFound)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
}
[indexes addObject:index];
node = parent;
// Search the parent's child nodes, not our own!
var index = [[_parentNode childNodes] indexOfObjectIdenticalTo:self];
// If the parent is the root (and technically has no path itself in some implementations),
// we might get nil. Handle that gracefully.
var parentPath = [_parentNode indexPath];
if (parentPath)
return [parentPath indexPathByAddingIndex:index];
return [CPIndexPath indexPathWithIndex:index];
}
/*
* indexes was collected leaf-to-root. Build a second array in
* root-to-leaf order by walking indexes backward. CPArray has no
* -reverse selector; count/objectAtIndex:/addObject: are the verified,
* already-used-elsewhere primitives.
*/
var orderedIndexes = [],
count = [indexes count];
while (count--)
[orderedIndexes addObject:[indexes objectAtIndex:count]];
return [CPIndexPath indexPathWithIndexes:orderedIndexes];
// If we are the root, we don't have an index path in the context of a tree controller usually,
// or we are [] (empty path). Returning nil is acceptable for the absolute root.
return nil;
}
- (BOOL)isLeaf
@@ -183,10 +79,7 @@
- (CPArray)childNodes
{
/*
* Return a copy.
* This prevents external changes that bypass the KVC methods.
*/
// Return a copy to prevent external modification without KVC
return [_childNodes copy];
}
@@ -195,190 +88,78 @@
return [self mutableArrayValueForKey:@"childNodes"];
}
/*
* KVC compliance methods.
* The mutableArrayValueForKey: method uses these names.
*/
// MARK: - KVC Compliance Methods
- (void)insertObject:(CPTreeNode)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
{
var count = [_childNodes count];
if (anIndex < 0 || anIndex > count)
// Optional: Auto-detach from old parent if strictly moving nodes
if ([aTreeNode isKindOfClass:[CPTreeNode class]] && aTreeNode._parentNode)
{
[CPException raise:CPRangeException
reason:"index (" + anIndex + ") beyond bounds (0 .. " + count + ") for insertObject:inChildNodesAtIndex:"];
[[aTreeNode._parentNode mutableChildNodes] removeObjectIdenticalTo:aTreeNode];
}
[self _validateChildNode:aTreeNode];
// Direct ivar access is allowed here since we are inside the class implementation
if ([aTreeNode isKindOfClass:[CPTreeNode class]])
aTreeNode._parentNode = self;
if ([self _wouldCreateCycleWithNode:aTreeNode])
{
[CPException raise:CPInvalidArgumentException
reason:"Inserting a CPTreeNode beneath itself or one of its descendants makes a cycle."];
}
/*
* Detach the node from its old parent first.
* The code validated the index before this change.
*/
if (aTreeNode._parentNode)
{
if (aTreeNode._parentNode === self)
{
var originalIndex = [_childNodes indexOfObjectIdenticalTo:aTreeNode];
/*
* Route the detach through the KVC accessor, not direct array
* mutation, so an observer of childNodes sees the removal.
*/
[self removeObjectFromChildNodesAtIndex:originalIndex];
/*
* No index adjustment here. anIndex is the target position in
* the final array, per the KVC to-many contract. The array
* above is already one element short from the removal, so
* inserting at anIndex against it lands the node correctly.
*/
}
else
{
/*
* Detach the node from its old parent.
* Use the internal method to bypass KVO overhead.
*/
[aTreeNode._parentNode _removeChildNode:aTreeNode];
}
}
aTreeNode._parentNode = self;
[_childNodes insertObject:aTreeNode atIndex:anIndex];
}
- (void)removeObjectFromChildNodesAtIndex:(CPInteger)anIndex
{
var node = [_childNodes objectAtIndex:anIndex];
if ([node isKindOfClass:[CPTreeNode class]])
node._parentNode = nil;
node._parentNode = nil;
[_childNodes removeObjectAtIndex:anIndex];
}
- (void)replaceObjectInChildNodesAtIndex:(CPInteger)anIndex withObject:(CPTreeNode)aTreeNode
- (void)replaceObjectFromChildNodesAtIndex:(CPInteger)anIndex withObject:(id)aTreeNode
{
var oldTreeNode = [_childNodes objectAtIndex:anIndex];
[self _validateChildNode:aTreeNode];
if (oldTreeNode === aTreeNode)
return;
if ([self _wouldCreateCycleWithNode:aTreeNode])
{
[CPException raise:CPInvalidArgumentException
reason:"Replacing a child with itself or one of its ancestors makes a cycle."];
}
/*
* If the replacement node is already a child of this parent, remove it first.
* The removal shifts the array elements.
* Adjust the target index before the replace operation.
* This matches the Cocoa KVC mutation semantics.
*/
var oldParent = aTreeNode._parentNode;
if (oldParent === self)
{
var replacementIndex = [_childNodes indexOfObjectIdenticalTo:aTreeNode];
/*
* aTreeNode.parentNode already equals self at this point. If
* self._childNodes does not actually contain aTreeNode, the
* parent/child relationship is already broken. indexPath raises
* for this identical class of inconsistency; proceeding here would
* silently tolerate the same problem instead of surfacing it.
*/
if (replacementIndex === CPNotFound)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
}
/*
* Bypass KVO for this internal structural adjustment.
*/
aTreeNode._parentNode = nil;
[_childNodes removeObjectAtIndex:replacementIndex];
/*
* Unlike insertObject:inChildNodesAtIndex:, anIndex here cannot be
* treated as a plain final-array position: replace requires an
* existing slot, it cannot append past the end. The removal above
* already took a slot out of the array ahead of the target
* whenever the replacement's original position was before it.
* Shift anIndex down by one in that case, to keep it pointing at
* the same physical slot the caller named.
*/
if (replacementIndex < anIndex)
--anIndex;
}
else if (oldParent)
{
/*
* Detach the node from its old parent.
* Use the internal method to bypass KVO overhead.
*/
[oldParent _removeChildNode:aTreeNode];
}
oldTreeNode._parentNode = nil;
aTreeNode._parentNode = self;
if ([oldTreeNode isKindOfClass:[CPTreeNode class]])
oldTreeNode._parentNode = nil;
if ([aTreeNode isKindOfClass:[CPTreeNode class]])
aTreeNode._parentNode = self;
[_childNodes replaceObjectAtIndex:anIndex withObject:aTreeNode];
}
// MARK: - Convenience Accessors
- (id)objectInChildNodesAtIndex:(CPInteger)anIndex
{
return [_childNodes objectAtIndex:anIndex];
}
- (CPInteger)countOfChildNodes
- (CPInteger)count
{
return [_childNodes count];
}
- (id)objectAtIndex:(CPInteger)anIndex
{
return [_childNodes objectAtIndex:anIndex];
}
// MARK: - Utility
- (void)sortWithSortDescriptors:(CPArray)sortDescriptors recursively:(BOOL)shouldSortRecursively
{
[_childNodes sortUsingDescriptors:sortDescriptors];
if (!shouldSortRecursively)
{
[_childNodes sortUsingDescriptors:sortDescriptors];
return;
}
/*
* Use an explicit stack, not recursion.
* The recursive form is not a tail call.
* The sibling loop continues after each child call returns.
* Only JavaScriptCore performs tail call optimization.
* The explicit stack prevents stack overflow on deep trees.
*/
var stack = [];
[stack addObject:self];
while ([stack count])
var count = [_childNodes count];
while (count--)
{
var node = [stack lastObject];
[stack removeLastObject];
[node._childNodes sortUsingDescriptors:sortDescriptors];
var count = [node._childNodes count];
while (count--)
{
[stack addObject:[node._childNodes objectAtIndex:count]];
}
var child = [_childNodes objectAtIndex:count];
if ([child respondsToSelector:@selector(sortWithSortDescriptors:recursively:)])
[child sortWithSortDescriptors:sortDescriptors recursively:YES];
}
}
@@ -388,17 +169,17 @@
return self;
var node = self,
length = [indexPath length];
length = [indexPath length];
for (var i = 0; i < length; i++)
{
var index = [indexPath indexAtPosition:i],
count = [node countOfChildNodes];
if (index < 0 || index >= count)
count = [node count];
if (index >= count || index < 0)
return nil;
node = [node objectInChildNodesAtIndex:index];
node = [node objectAtIndex:index];
}
return node;
@@ -406,6 +187,7 @@
@end
// Coding implementation remains correct
var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
CPTreeNodeParentNodeKey = @"CPTreeNodeParentNodeKey",
CPTreeNodeChildNodesKey = @"CPTreeNodeChildNodesKey";
@@ -421,27 +203,10 @@ var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
_representedObject = [aCoder decodeObjectForKey:CPTreeNodeRepresentedObjectKey];
_parentNode = [aCoder decodeObjectForKey:CPTreeNodeParentNodeKey];
_childNodes = [aCoder decodeObjectForKey:CPTreeNodeChildNodesKey];
// Safety check to ensure decoding gave us a CPArray
if (!_childNodes)
_childNodes = [];
if (![_childNodes isKindOfClass:[CPMutableArray class]])
_childNodes = [_childNodes mutableCopy];
/*
* The child array is the authoritative structure.
* Re-establish the parent links.
* This makes the decoded tree match the tree built by the mutation methods.
*/
var count = [_childNodes count];
while (count--)
{
var child = [_childNodes objectAtIndex:count];
[self _validateChildNode:child];
child._parentNode = self;
}
_childNodes = [[CPMutableArray alloc] init];
}
return self;
+158 -222
View File
@@ -26,11 +26,9 @@
@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"
@@ -52,6 +50,8 @@
@typedef _CPViewFullScreenModeState
#if PLATFORM(DOM)
if (typeof(appkit_tag_dom_elements) !== "undefined" && appkit_tag_dom_elements)
{
AppKitTagDOMElement = function(owner, element)
@@ -68,6 +68,8 @@ else
}
}
#endif
/*
@global
@group CPViewAutoresizingMasks
@@ -121,6 +123,7 @@ CPViewFrameDidChangeNotification = @"CPViewFrameDidChangeNotification";
var CachedNotificationCenter = nil;
#if PLATFORM(DOM)
var DOMElementPrototype = nil,
BackgroundTrivialColor = 0,
@@ -129,6 +132,7 @@ var DOMElementPrototype = nil,
BackgroundNinePartImage = 3,
BackgroundTransparentColor = 4,
BackgroundCSSStyling = 5;
#endif
var CPViewFlags = { },
CPViewHasCustomDrawRect = 1 << 0,
@@ -185,6 +189,7 @@ var CPViewHighDPIDrawingEnabled = YES;
BOOL _inLiveResize;
BOOL _isSuperviewAClipView;
#if PLATFORM(DOM)
DOMElement _DOMElement;
DOMElement _DOMContentsElement;
@@ -196,6 +201,7 @@ var CPViewHighDPIDrawingEnabled = YES;
// CSS styling
CPArray _cssStylePreviousState;
DOMElement _cssStyleNode;
#endif
CGRect _dirtyRect;
@@ -224,6 +230,7 @@ var CPViewHighDPIDrawingEnabled = YES;
// Layout Support
BOOL _needsLayout;
JSObject _ephemeralSubviews;
JSObject _ephemeralSubviewsForNames;
CPSet _ephemeralSubviews;
@@ -263,17 +270,16 @@ var CPViewHighDPIDrawingEnabled = YES;
if (self !== [CPView class])
return;
if (CPDOMAvailable)
{
DOMElementPrototype = document.createElement("div");
#if PLATFORM(DOM)
DOMElementPrototype = document.createElement("div");
var style = DOMElementPrototype.style;
var style = DOMElementPrototype.style;
style.overflow = "hidden";
style.position = "absolute";
style.visibility = "visible";
style.zIndex = 0;
}
style.overflow = "hidden";
style.position = "absolute";
style.visibility = "visible";
style.zIndex = 0;
#endif
CachedNotificationCenter = [CPNotificationCenter defaultCenter];
}
@@ -328,9 +334,8 @@ var CPViewHighDPIDrawingEnabled = YES;
{
return @{
@"css-based": NO,
@"nib2cib-adjustment-frame": [CPNull null],
@"direct-nib2cib-adjustment": NO,
@"dynamic-set": [CPNull null]
@"dynamic-set": [CPNull null],
@"nib2cib-adjustment-frame": CGRectMakeZero()
};
}
@@ -404,19 +409,18 @@ var CPViewHighDPIDrawingEnabled = YES;
_theme = [CPTheme defaultTheme];
_themeState = CPThemeStateNormal;
if (CPDOMAvailable)
{
_DOMElement = DOMElementPrototype.cloneNode(false);
AppKitTagDOMElement(self, _DOMElement);
#if PLATFORM(DOM)
_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 = @[];
}
_cssStylePreviousState = @[];
#endif
_animator = nil;
_animationsDictionary = @{};
@@ -479,21 +483,20 @@ var CPViewHighDPIDrawingEnabled = YES;
if (!_toolTipFunctionOut)
_toolTipFunctionOut = function(e) { [_CPToolTip invalidateCurrentToolTipIfNeeded]; };
if (CPDOMAvailable)
#if PLATFORM(DOM)
if (_DOMElement.addEventListener)
{
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);
}
_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);
}
#endif
_toolTipInstalled = YES;
}
@@ -507,21 +510,20 @@ var CPViewHighDPIDrawingEnabled = YES;
if (!_toolTipInstalled)
return;
if (CPDOMAvailable)
#if PLATFORM(DOM)
if (_DOMElement.removeEventListener)
{
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);
}
_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);
}
#endif
_toolTipFunctionIn = nil;
_toolTipFunctionOut = nil;
@@ -590,8 +592,10 @@ 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];
@@ -615,10 +619,9 @@ var CPViewHighDPIDrawingEnabled = YES;
[_subviews removeObjectAtIndex:index];
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_DOMElement, aSubview._DOMElement);
}
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, aSubview._DOMElement);
#endif
if (anIndex > index)
--anIndex;
@@ -644,28 +647,25 @@ var CPViewHighDPIDrawingEnabled = YES;
{
_subviews.push(aSubview);
if (CPDOMAvailable)
{
// Attach the actual node.
CPDOMDisplayServerAppendChild(_DOMElement, aSubview._DOMElement);
}
#if PLATFORM(DOM)
// Attach the actual node.
CPDOMDisplayServerAppendChild(_DOMElement, aSubview._DOMElement);
#endif
}
else
{
_subviews.splice(anIndex, 0, aSubview);
if (CPDOMAvailable)
{
// Attach the actual node.
CPDOMDisplayServerInsertBefore(_DOMElement, aSubview._DOMElement, _subviews[anIndex + 1]._DOMElement);
}
#if PLATFORM(DOM)
// Attach the actual node.
CPDOMDisplayServerInsertBefore(_DOMElement, aSubview._DOMElement, _subviews[anIndex + 1]._DOMElement);
#endif
}
if (CPDOMAvailable)
{
var origin = aSubview._frame.origin;
CPDOMDisplayServerSetStyleLeftTop(aSubview._DOMElement, _boundsTransform, origin.x, origin.y);
}
#if PLATFORM(DOM)
var origin = aSubview._frame.origin;
CPDOMDisplayServerSetStyleLeftTop(aSubview._DOMElement, _boundsTransform, origin.x, origin.y);
#endif
[aSubview setNextResponder:self];
[aSubview _scaleSizeUnitSquareToSize:[self _hierarchyScaleSize]];
@@ -737,10 +737,9 @@ var CPViewHighDPIDrawingEnabled = YES;
[_superview._subviews removeObjectIdenticalTo:self];
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_superview._DOMElement, _DOMElement);
}
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_superview._DOMElement, _DOMElement);
#endif
// If the view is not hidden and one of its ancestors is hidden,
// notify the view that it is now unhidden.
@@ -823,9 +822,7 @@ var CPViewHighDPIDrawingEnabled = YES;
_subviews = [newSubviews copy];
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
var index = 0,
count = [_subviews count];
@@ -836,6 +833,7 @@ var CPViewHighDPIDrawingEnabled = YES;
CPDOMDisplayServerRemoveChild(_DOMElement, subview._DOMElement);
CPDOMDisplayServerAppendChild(_DOMElement, subview._DOMElement);
}
#endif
}
/* @ignore */
@@ -930,13 +928,6 @@ var CPViewHighDPIDrawingEnabled = YES;
*/
- (void)viewDidMoveToWindow
{
var window = [self window];
// If the view is inside a window with the HUD style mask, turn on the HUD state
if (window && ([window styleMask] & CPHUDBackgroundWindowMask))
[self setThemeState:CPThemeStateHUD];
else
[self unsetThemeState:CPThemeStateHUD];
}
/*!
@@ -1161,11 +1152,14 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications)
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
if (CPDOMAvailable && !_inhibitDOMUpdates)
#if PLATFORM(DOM)
if (!_inhibitDOMUpdates)
{
var transform = _superview ? _superview._boundsTransform : NULL;
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, transform, origin.x, origin.y);
}
#endif
if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
[self _updateTrackingAreasWithRecursion:YES];
@@ -1204,8 +1198,7 @@ var CPViewHighDPIDrawingEnabled = YES;
[self setNeedsLayout];
[self setNeedsDisplay:YES];
if (CPDOMAvailable)
{
#if PLATFORM(DOM)
[self _setDisplayServerSetStyleSize:size];
if (_backgroundType !== BackgroundTrivialColor)
@@ -1314,7 +1307,7 @@ var CPViewHighDPIDrawingEnabled = YES;
}
}
}
}
#endif
if (_postsFrameChangedNotifications && !_inhibitFrameAndBoundsChangedNotifications)
[CachedNotificationCenter postNotificationName:CPViewFrameDidChangeNotification object:self];
@@ -1333,9 +1326,7 @@ var CPViewHighDPIDrawingEnabled = YES;
*/
- (void)_setDisplayServerSetStyleSize:(CGSize)aSize
{
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
var scale = [self scaleSize];
if (!_inhibitDOMUpdates)
@@ -1348,6 +1339,7 @@ var CPViewHighDPIDrawingEnabled = YES;
_needToSetTransformMatrix = YES;
}
#endif
}
/*!
@@ -1426,8 +1418,7 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_layer)
[_layer _owningViewBoundsChanged];
if (CPDOMAvailable)
{
#if PLATFORM(DOM)
var index = _subviews.length;
while (index--)
@@ -1437,7 +1428,7 @@ var CPViewHighDPIDrawingEnabled = YES;
CPDOMDisplayServerSetStyleLeftTop(view._DOMElement, _boundsTransform, origin.x, origin.y);
}
}
#endif
if (_postsBoundsChangedNotifications && !_inhibitFrameAndBoundsChangedNotifications)
[CachedNotificationCenter postNotificationName:CPViewBoundsDidChangeNotification object:self];
@@ -1483,24 +1474,6 @@ var CPViewHighDPIDrawingEnabled = YES;
origin.y *= size.height / frameSize.height;
}
var newScaleSize;
if (size && size.width !== 0 && size.height !== 0 && frameSize)
newScaleSize = CGSizeMake(frameSize.width / size.width, frameSize.height / size.height);
else
newScaleSize = CGSizeMake(1.0, 1.0);
// Only update and propagate if the scale factor has actually changed
if (!CGSizeEqualToSize(_scaleSize, newScaleSize))
{
[self willChangeValueForKey:@"scaleSize"];
_scaleSize = newScaleSize;
_isScaled = (_scaleSize.width !== 1.0 || _scaleSize.height !== 1.0);
[self didChangeValueForKey:@"scaleSize"];
[self _scaleSizeUnitSquareToSize:CGSizeMake(1.0, 1.0)];
}
if (_layer)
[_layer _owningViewBoundsChanged];
@@ -1514,6 +1487,7 @@ var CPViewHighDPIDrawingEnabled = YES;
[self _updateTrackingAreasWithRecursion:YES];
}
/*!
Notifies subviews that the superview changed size.
@param aSize the size of the old superview
@@ -1689,12 +1663,11 @@ 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 (CPDOMAvailable)
{
_DOMElement.style.display = aFlag ? "none" : "block";
}
// 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
if (aFlag)
{
@@ -1789,10 +1762,9 @@ var CPViewHighDPIDrawingEnabled = YES;
_isHiddenOrHasHiddenAncestor = YES;
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view _recursiveGainedHiddenAncestor];
}
];
{
[view _recursiveGainedHiddenAncestor];
}];
}
/*!
@@ -1810,10 +1782,9 @@ var CPViewHighDPIDrawingEnabled = YES;
_clipsToBounds = shouldClip;
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
_DOMElement.style.overflow = _clipsToBounds ? "hidden" : "visible";
#endif
}
- (BOOL)clipsToBounds
@@ -1833,8 +1804,7 @@ var CPViewHighDPIDrawingEnabled = YES;
_opacity = anAlphaValue;
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
if (CPFeatureIsCompatible(CPOpacityRequiresFilterFeature))
{
@@ -1845,6 +1815,8 @@ var CPViewHighDPIDrawingEnabled = YES;
}
else
_DOMElement.style.opacity = anAlphaValue;
#endif
}
/*!
@@ -2034,9 +2006,7 @@ var CPViewHighDPIDrawingEnabled = YES;
_backgroundColor = aColor;
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
if (_backgroundType === BackgroundCSSStyling)
[CPColor restorePreviousCSSState:@ref(_cssStylePreviousState) forDOMElement:_DOMElement];
@@ -2314,6 +2284,7 @@ var CPViewHighDPIDrawingEnabled = YES;
}
}
}
#endif
}
/*!
@@ -2635,7 +2606,7 @@ setBoundsOrigin:
*/
- (void)_scaleSizeUnitSquareToSize:(CGSize)aSize
{
_hierarchyScaleSize = _superview ? CGSizeMakeCopy([_superview _hierarchyScaleSize]) : CGSizeMake(1.0, 1.0);
_hierarchyScaleSize = CGSizeMakeCopy([_superview _hierarchyScaleSize]);
if (_isScaled)
{
@@ -2659,14 +2630,11 @@ setBoundsOrigin:
*/
- (void)_applyCSSScalingTranformations
{
if (!CPDOMAvailable)
return;
var browserPropertyTransform = CPBrowserStyleProperty(@"transform");
#if PLATFORM(DOM)
if (_isScaled)
{
var scale = [self scaleSize],
browserPropertyTransform = CPBrowserStyleProperty(@"transform"),
browserPropertyTransformOrigin = CPBrowserStyleProperty(@"transformOrigin");
self._DOMElement.style[browserPropertyTransform] = 'scale(' + scale.width + ', ' + scale.height + ')';
@@ -2674,13 +2642,7 @@ setBoundsOrigin:
[self _setDisplayServerSetStyleSize:[self frameSize]];
}
else if (self._DOMElement.style[browserPropertyTransform])
{
// A view whose scale has returned to 1.0 drops the scale it was drawn with.
self._DOMElement.style[browserPropertyTransform] = '';
[self _setDisplayServerSetStyleSize:[self frameSize]];
}
#endif
}
// Displaying
@@ -2763,15 +2725,14 @@ setBoundsOrigin:
if ([self isHidden])
return;
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
[self lockFocus];
CGContextClearRect([[CPGraphicsContext currentContext] graphicsPort], aRect);
[self drawRect:aRect];
[self unlockFocus];
#endif
}
- (void)viewWillDraw
@@ -2787,41 +2748,42 @@ setBoundsOrigin:
{
var graphicsPort = CGBitmapGraphicsContextCreate();
if (CPDOMAvailable)
{
var width = CGRectGetWidth(_frame),
height = CGRectGetHeight(_frame),
devicePixelRatio = window.devicePixelRatio || 1,
backingStoreRatio = CPBrowserBackingStorePixelRatio(graphicsPort);
#if PLATFORM(DOM)
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);
}
CPDOMDisplayServerAppendChild(_DOMElement, _DOMContentsElement);
#endif
_graphicsContext = [CPGraphicsContext graphicsContextWithGraphicsPort:graphicsPort flipped:YES];
_needToSetTransformMatrix = YES;
}
if (CPDOMAvailable && _needToSetTransformMatrix && _highDPIRatio !== 1)
#if PLATFORM(DOM)
if (_needToSetTransformMatrix && _highDPIRatio !== 1)
[_graphicsContext graphicsPort].setTransform(_highDPIRatio, 0, 0 , _highDPIRatio, 0, 0);
#endif
_needToSetTransformMatrix = NO;
[CPGraphicsContext setCurrentContext:_graphicsContext];
@@ -3263,8 +3225,10 @@ setBoundsOrigin:
if (_layer)
{
_layer._owningView = nil;
if (CPDOMAvailable && _layer._DOMElement && _layer._DOMElement.parentNode === _DOMElement)
#if PLATFORM(DOM)
if (_layer._DOMElement && _layer._DOMElement.parentNode === _DOMElement)
_DOMElement.removeChild(_layer._DOMElement);
#endif
}
_layer = aLayer;
@@ -3274,11 +3238,10 @@ setBoundsOrigin:
[_layer _setOwningView:self];
[_layer setFrame:[self bounds]]; // Sync layer frame with view bounds
if (CPDOMAvailable)
{
_layer._DOMElement.style.zIndex = 100;
_DOMElement.appendChild(_layer._DOMElement);
}
#if PLATFORM(DOM)
_layer._DOMElement.style.zIndex = 100;
_DOMElement.appendChild(_layer._DOMElement);
#endif
}
}
@@ -3610,10 +3573,9 @@ setBoundsOrigin:
- (void)setDOMClassName:(CPString)aClassName
{
if (!CPDOMAvailable)
return;
#if PLATFORM(DOM)
_DOMElement.className = aClassName;
#endif
}
- (BOOL)isCSSBased
@@ -3887,11 +3849,10 @@ 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 (CPDOMAvailable)
{
_DOMElement = DOMElementPrototype.cloneNode(false);
AppKitTagDOMElement(self, _DOMElement);
}
#if PLATFORM(DOM)
_DOMElement = DOMElementPrototype.cloneNode(false);
AppKitTagDOMElement(self, _DOMElement);
#endif
// Also decode these "early".
_frame = [aCoder decodeRectForKey:CPViewFrameKey];
@@ -3948,25 +3909,24 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
[self _installToolTipEventHandlers];
// DOM SETUP
if (CPDOMAvailable)
#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)
{
_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;
}
CPDOMDisplayServerAppendChild(_DOMElement, _subviews[index]._DOMElement);
//_subviews[index]._superview = self;
}
#endif
[self setHidden:[aCoder decodeBoolForKey:CPViewIsHiddenKey]];
_isHiddenOrHasHiddenAncestor = NO;
@@ -4211,27 +4171,3 @@ var _CPViewGetTransform = function(/*CPView*/ fromView, /*CPView */ toView)
return transform;
};
@implementation CPView (ThemingAdditions)
- (void)_setThemeStateRecursively:(ThemeState)aState
{
[_subviews makeObjectsPerformSelector:@selector(_setThemeStateRecursively:) withObject:aState];
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view setThemeState:aState];
}];
}
- (void)_unsetThemeStateRecursively:(ThemeState)aState
{
[_subviews makeObjectsPerformSelector:@selector(_unsetThemeStateRecursively:) withObject:aState];
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view unsetThemeState:aState];
}];
}
@end
+11 -34
View File
@@ -28,8 +28,6 @@ CPViewAnimationTargetKey = @"CPViewAnimationTargetKey";
CPViewAnimationStartFrameKey = @"CPViewAnimationStartFrameKey";
CPViewAnimationEndFrameKey = @"CPViewAnimationEndFrameKey";
CPViewAnimationEffectKey = @"CPViewAnimationEffectKey";
CPViewAnimationStartOpacityKey = @"CPViewAnimationStartOpacityKey";
CPViewAnimationEndOpacityKey = @"CPViewAnimationEndOpacityKey";
CPViewAnimationFadeInEffect = @"CPViewAnimationFadeInEffect";
CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
@@ -134,24 +132,12 @@ CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
[view setFrame:intermediateFrame];
// Check for explicit Opacity keys first
var startOpacity = [dictionary objectForKey:CPViewAnimationStartOpacityKey],
endOpacity = [dictionary objectForKey:CPViewAnimationEndOpacityKey];
if (startOpacity != nil && endOpacity != nil)
{
// Interpolate alpha
[view setAlphaValue:startOpacity + (endOpacity - startOpacity) * value];
}
else
{
// Fallback to legacy Effect keys
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0 * value];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:1.0 + ( 0.0 - 1.0 ) * value];
}
// Update the view's alpha value
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0 * value];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:1.0 + ( 0.0 - 1.0 ) * value];
if (progress === 1.0)
[self _targetView:view setHidden:CGRectIsEmpty(endFrame) || [view alphaValue] === 0.0];
@@ -169,20 +155,11 @@ CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
[view setFrame:endFrame];
var endOpacity = [dictionary objectForKey:CPViewAnimationEndOpacityKey];
if (endOpacity != nil)
{
[view setAlphaValue:endOpacity];
}
else
{
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:0.0];
}
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:0.0];
[self _targetView:view setHidden:CGRectIsEmpty(endFrame) || [view alphaValue] === 0.0];
}
+3 -5
View File
@@ -23,7 +23,6 @@
@import <Foundation/Foundation.j>
@import "CPAppearance.j"
@import "CPView.j"
@import "CPCompatibility.j"
@typedef CPVisualEffectMaterial
CPVisualEffectMaterialAppearanceBased = 0;
@@ -127,10 +126,9 @@ CPVisualEffectStateInactive = 2;
[self setBackgroundColor:finalColor];
if (CPDOMAvailable)
{
self._DOMElement.style[prop] = shouldEnable ? "blur(30px)" : nil;
}
#if PLATFORM(DOM)
self._DOMElement.style[prop] = shouldEnable ? "blur(30px)" : nil;
#endif
}
+3 -5
View File
@@ -22,7 +22,6 @@
@import "CPView.j"
@import "CPScrollView.j"
@import "CPCompatibility.j"
@class CPWebScriptObject
@@ -1032,10 +1031,9 @@ CPWebViewAppKitScrollMaxPollCount = 3;
_forwardStack = [];
_scrollMode = CPWebViewScrollAuto;
if (CPDOMAvailable)
{
[self _initDOMWithFrame:[self frame]];
}
#if PLATFORM(DOM)
[self _initDOMWithFrame:[self frame]];
#endif
if (![self backgroundColor])
[self setBackgroundColor:[CPColor whiteColor]];
+111 -226
View File
@@ -30,7 +30,9 @@
@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"
@@ -177,7 +179,6 @@ var CPWindowActionMessageKeys = [
BOOL _isVisible;
BOOL _hasBeenOrderedIn @accessors;
BOOL _isMiniaturized;
BOOL _isExcludedFromWindowsMenu;
BOOL _isAnimating @accessors(setter=_setAnimating:);
BOOL _hasShadow;
BOOL _isMovableByWindowBackground;
@@ -246,7 +247,9 @@ var CPWindowActionMessageKeys = [
BOOL _sharesChromeWithPlatformWindow;
// Bridge Support
#if PLATFORM(DOM)
DOMElement _DOMElement;
#endif
unsigned _autoresizingMask;
@@ -370,22 +373,21 @@ CPTexturedBackgroundWindowMask
[_windowView setNextResponder:self];
// CSS Styling
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;
}
#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
// Size calculation needs _windowView
_minSize = [self _calculateMinSizeForProposedSize:CGSizeMake(0.0, 0.0)];
@@ -398,23 +400,22 @@ CPTexturedBackgroundWindowMask
_firstResponder = self;
if (CPDOMAvailable)
#if PLATFORM(DOM)
_DOMElement = document.createElement("div");
_DOMElement.style.position = "absolute";
_DOMElement.style.visibility = "visible";
_DOMElement.style.zIndex = 0;
if (![self _sharesChromeWithPlatformWindow])
{
_DOMElement = document.createElement("div");
_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);
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, CGRectGetMinX(_frame), CGRectGetMinY(_frame));
}
CPDOMDisplayServerSetStyleSize(_DOMElement, 1, 1);
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
#endif
[self setNextResponder:CPApp];
[self setHasShadow:aStyleMask !== CPBorderlessWindowMask];
@@ -579,18 +580,16 @@ CPTexturedBackgroundWindowMask
[oldWindowView _setWindow:nil];
[oldWindowView noteToolbarChanged];
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_DOMElement, oldWindowView._DOMElement);
}
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, oldWindowView._DOMElement);
#endif
}
if (_windowView)
{
if (CPDOMAvailable)
{
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
}
#if PLATFORM(DOM)
CPDOMDisplayServerAppendChild(_DOMElement, _windowView._DOMElement);
#endif
var contentRect = [_contentView convertRect:[_contentView bounds] toView:nil];
@@ -786,10 +785,12 @@ CPTexturedBackgroundWindowMask
origin.x = newOrigin.x;
origin.y = newOrigin.y;
if (CPDOMAvailable && ![self _sharesChromeWithPlatformWindow])
#if PLATFORM(DOM)
if (![self _sharesChromeWithPlatformWindow])
{
CPDOMDisplayServerSetStyleLeftTop(_DOMElement, NULL, origin.x, origin.y);
}
#endif
// reposition sheet
if ([self attachedSheet])
@@ -801,15 +802,14 @@ 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)
@@ -979,33 +979,30 @@ CPTexturedBackgroundWindowMask
- (void)orderFront:(id)aSender
{
[self orderWindow:CPWindowAbove relativeTo:0];
if (_styleMask & CPHUDBackgroundWindowMask)
[_contentView _setThemeStateRecursively:CPThemeStateHUD];
}
- (void)_orderFront
{
if (CPDOMAvailable)
{
if (!_isVisible)
[_platformWindow _setShouldUpdateContentRect:NO];
#if PLATFORM(DOM)
// -dw- if a sheet is clicked, the parent window should come up too
if (_isSheet)
[_parentView orderFront:self];
if (!_isVisible)
[_platformWindow _setShouldUpdateContentRect:NO];
// Save the boolean since it will be updated in the method order:window:relativeTo:
var wasVisible = _isVisible;
// -dw- if a sheet is clicked, the parent window should come up too
if (_isSheet)
[_parentView orderFront:self];
[_platformWindow orderFront:self];
[_platformWindow order:CPWindowAbove window:self relativeTo:nil];
// Save the boolean since it will be updated in the method order:window:relativeTo:
var wasVisible = _isVisible;
// 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];
}
[_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
if (!CPApp._keyWindow)
[self makeKeyWindow];
@@ -1094,16 +1091,15 @@ CPTexturedBackgroundWindowMask
if (recursive)
[_childWindows makeObjectsPerformSelector:@selector(_orderOutRecursively:) withObject:recursive];
if (CPDOMAvailable)
{
if ([self _sharesChromeWithPlatformWindow])
[_platformWindow orderOut:self];
#if PLATFORM(DOM)
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];
}
[_platformWindow order:CPWindowOut window:self relativeTo:nil];
#endif
[self makeFirstResponder:nil];
[self _updateMainAndKeyWindows];
@@ -1128,13 +1124,10 @@ CPTexturedBackgroundWindowMask
[self _orderFront];
else if (orderingMode === CPWindowBelow && otherWindowNumber === 0)
[self _orderBack];
#if PLATFORM(DOM)
else
{
if (CPDOMAvailable)
[_platformWindow order:orderingMode window:self relativeTo:CPApp._windows[otherWindowNumber]];
}
[self _updateWindowsMenuItem];
[_platformWindow order:orderingMode window:self relativeTo:CPApp._windows[otherWindowNumber]];
#endif
}
/*!
@@ -1431,10 +1424,9 @@ CPTexturedBackgroundWindowMask
{
if (_shadowView)
{
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
}
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
#endif
_shadowView = nil;
}
@@ -1448,24 +1440,23 @@ CPTexturedBackgroundWindowMask
// When using CSS theming, we get rid of _shadowView and add CSS shadowing directly
// on the _windowView
if (CPDOMAvailable)
#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)
{
// We must check that _windowView exists as _updateShadow can be called by
// _setSharesChromeWithPlatformWindow whereas _windowView is not yet created
if (_windowView && _windowView._DOMElement)
var currentBoxShadow = _windowView._DOMElement.style.boxShadow;
if (_hasShadow && (currentBoxShadow.length == 0))
{
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 = "";
}
_windowView._DOMElement.style.boxShadow = [_windowView actualValueForThemeAttribute:@"window-shadow-color"];
}
else if (!_hasShadow && (currentBoxShadow.length > 0))
{
_windowView._DOMElement.style.boxShadow = "";
}
}
#endif
}
else
{
@@ -1477,17 +1468,15 @@ CPTexturedBackgroundWindowMask
[_shadowView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_shadowView setNeedsLayout];
if (CPDOMAvailable)
{
CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement);
}
#if PLATFORM(DOM)
CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement);
#endif
}
else if (!_hasShadow && _shadowView)
{
if (CPDOMAvailable)
{
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
}
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
#endif
_shadowView = nil;
}
}
@@ -1798,54 +1787,6 @@ 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];
}
/*!
@@ -2010,42 +1951,34 @@ 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 &&
!(modifierFlags & (CPAlternateKeyMask | CPCommandKeyMask)) &&
!firstResponderTakesTab)
!([anEvent modifierFlags] & (CPAlternateKeyMask | CPCommandKeyMask)))
{
if ([anEvent modifierFlags] & CPShiftKeyMask)
[self selectPreviousKeyView:self];
else
[self selectNextKeyView:self];
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];
}
#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
return;
}
else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter && !firstResponderTakesTab)
else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter)
{
var didTabBack = [self selectPreviousKeyView:self];
if (didTabBack)
{
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];
}
#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
}
return didTabBack;
}
@@ -2054,10 +1987,6 @@ 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
@@ -2502,8 +2431,6 @@ CPTexturedBackgroundWindowMask
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidMiniaturizeNotification object:self];
_isMiniaturized = YES;
[self _updateWindowsMenuItem];
}
/*!
@@ -2516,8 +2443,6 @@ CPTexturedBackgroundWindowMask
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowDidDeminiaturizeNotification object:self];
_isMiniaturized = NO;
[self _updateWindowsMenuItem];
}
/*!
@@ -2614,17 +2539,10 @@ CPTexturedBackgroundWindowMask
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillCloseNotification object:self];
// Give a chance to the _windowView to do things before closing (for example, remove an observer)
if ([_windowView respondsToSelector:@selector(_close)])
[_windowView _close];
[_parentWindow removeChildWindow:self];
[self _orderOutRecursively:NO];
[self _detachFromChildrenClosing:!_parentWindow];
// A closed window leaves the Windows menu, even if it was miniaturized.
[self _removeFromWindowsMenu];
if (_releasedWhenClosed)
[_contentView _releaseRecursively];
}
@@ -2706,8 +2624,6 @@ CPTexturedBackgroundWindowMask
[_windowView noteMainWindowStateChanged];
[CPApp updateWindowsItem:self];
[[CPNotificationCenter defaultCenter]
postNotificationName:CPWindowDidBecomeMainNotification
object:self];
@@ -2727,8 +2643,6 @@ CPTexturedBackgroundWindowMask
_platformWindow._currentMainWindow = nil;
[_windowView noteMainWindowStateChanged];
[CPApp updateWindowsItem:self];
}
- (void)_updateMainAndKeyWindows
@@ -2843,37 +2757,6 @@ 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]),
@@ -3828,10 +3711,12 @@ var keyViewComparator = function(lhs, rhs, context)
*/
- (CGPoint)_nativeScrollOffset
{
if (CPDOMAvailable)
return CGPointMake(_windowView._DOMElement.scrollLeft, _windowView._DOMElement.scrollTop);
#if PLATFORM(DOM)
return CGPointMake(_windowView._DOMElement.scrollLeft, _windowView._DOMElement.scrollTop);
#else
CGPointMake(0, 0);
#endif
}
/*!
@@ -21,7 +21,6 @@
*/
@import "_CPWindowView.j"
@import "CPCompatibility.j"
@implementation _CPBorderlessBridgeWindowView : _CPWindowView
{
@@ -40,22 +39,6 @@
};
}
- (id)initWithFrame:(CGRect)aFrame styleMask:(unsigned)aStyleMask
{
self = [super initWithFrame:aFrame styleMask:aStyleMask];
if (self)
{
// Check if the mask contains the HUD flag
if (aStyleMask & CPHUDBackgroundWindowMask)
{
[self setBackgroundColor:[CPColor blackColor]];
}
}
return self;
}
- (void)setShowsResizeIndicator:(BOOL)shouldShowResizeIndicator
{
// We don't ever want to show the resize indicator.
@@ -107,11 +90,10 @@
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 (CPDOMAvailable)
{
_DOMElement.style.overflowX = (_frame.size.width < theWindow._minSize.width)? "scroll":"hidden";
_DOMElement.style.overflowY = (_frame.size.height < theWindow._minSize.height)? "scroll":"hidden";
}
#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
}
@end
+55 -331
View File
@@ -22,7 +22,6 @@
@import "CPButton.j"
@import "_CPTitleableWindowView.j"
@import "CPApplication_Constants.j"
@class _CPDocModalWindowView
@@ -44,11 +43,7 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
+ (CPDictionary)themeAttributes
{
return @{
@"close-active-image-button": [CPNull null],
@"minimize-active-image-button": [CPNull null],
@"zoom-active-image-button": [CPNull null],
};
return @{};
}
- (id)initWithFrame:(CGRect)aFrame windowView:(_CPWindowView)parentView
@@ -76,26 +71,19 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
bounds = [self bounds],
bezelHeadColor = [[CPTheme defaultTheme] valueForAttributeWithName:_isSheet ? @"bezel-head-sheet-color" : @"bezel-head-color" inState:[_parentView themeState] forClass:_CPStandardWindowView];
// Apply the border/background color to self (the container) so borders are drawn on the outside
[self setBackgroundColor:bezelHeadColor];
[_gradientView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), gradientHeight)];
[_gradientView setBackgroundColor:bezelHeadColor];
// Set x=0.0 to avoid 1px gap. Width is reduced by 2.0 to fit inside the 1px borders on each side.
[_gradientView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds) - 2.0, gradientHeight)];
// Set x=0.0 to avoid 1px gap. Width is reduced by 2.0.
[_solidView setFrame:CGRectMake(0.0, gradientHeight, CGRectGetWidth(bounds) - 2.0, CGRectGetHeight(bounds) - gradientHeight)];
[_solidView setFrame:CGRectMake(0.0, gradientHeight, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - gradientHeight)];
[_solidView setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"solid-color" forClass:_CPStandardWindowView]];
}
- (void)resizeSubviewsWithOldSize:(CGSize)aSize
{
var bounds = [self bounds],
width = CGRectGetWidth(bounds) - 2.0; // Account for border inset
var bounds = [self bounds];
if (width < 0) width = 0;
[_gradientView setFrameSize:CGSizeMake(width, [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
[_solidView setFrameSize:CGSizeMake(width, CGRectGetHeight(bounds) - [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
[_gradientView setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
[_solidView setFrameSize:CGSizeMake(CGRectGetWidth(bounds), CGRectGetHeight(bounds) - [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
}
@end
@@ -109,16 +97,9 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
CPButton _closeButton;
CPButton _minimizeButton;
CPButton _zoomButton;
BOOL _isDocumentEdited;
BOOL _isSheet;
CPTrackingArea _closeButtonTrackingArea;
CPTrackingArea _minimizeButtonTrackingArea;
CPTrackingArea _zoomButtonTrackingArea;
int _buttonsWidth;
}
+ (CPString)defaultThemeClass
@@ -142,8 +123,6 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
@"close-image-highlighted-button": [CPNull null],
@"unsaved-image-button": [CPNull null],
@"unsaved-image-highlighted-button": [CPNull null],
@"zoom-image-button": [CPNull null],
@"zoom-image-highlighted-button": [CPNull null]
};
}
@@ -175,7 +154,7 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
var theClass = [self class],
bounds = [self bounds];
_headView = [[_CPTexturedWindowHeadView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), _titleBarHeight) windowView:self];
_headView = [[_CPTexturedWindowHeadView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), [self valueForThemeAttribute:@"title-bar-height"]) windowView:self];
[_headView setAutoresizingMask:CPViewWidthSizable];
[_headView setHitTests:NO];
@@ -202,50 +181,30 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
if (_styleMask & CPClosableWindowMask)
{
_closeButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
_closeButton = [[CPButton alloc] initWithFrame:CGRectMake(8.0, 8.0, 16.0, 16.0)];
[_closeButton setButtonType:CPMomentaryChangeButton];
[_closeButton setBordered:NO];
[self _updateCloseButton];
[self addSubview:_closeButton];
}
if (_styleMask & CPMiniaturizableWindowMask)
if (_styleMask & CPMiniaturizableWindowMask && ![CPPlatform isBrowser])
{
_minimizeButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
_minimizeButton = [[CPButton alloc] initWithFrame:CGRectMake(27.0, 7.0, 16.0, 16.0)];
[_minimizeButton setButtonType:CPMomentaryChangeButton];
[_minimizeButton setBordered:NO];
[self addSubview:_minimizeButton];
}
if (_styleMask & CPResizableWindowMask)
{
_zoomButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
[_zoomButton setButtonType:CPMomentaryChangeButton];
[_zoomButton setBordered:NO];
[self addSubview:_zoomButton];
}
[self _updateWindowButtons:YES];
[self tile];
// Observe CPApplicationOSBehaviorDidChangeNotification
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_osBehaviorDidChange:) name:CPApplicationOSBehaviorDidChangeNotification object:CPApp];
}
return self;
}
// This will be called by CPWindow -close so the observer can be removed
- (void)_close
{
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPApplicationOSBehaviorDidChangeNotification object:CPApp];
}
- (void)viewDidMoveToWindow
{
[_closeButton setTarget:[self window]];
@@ -253,14 +212,11 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_minimizeButton setTarget:[self window]];
[_minimizeButton setAction:@selector(performMiniaturize:)];
[_zoomButton setTarget:[self window]];
[_zoomButton setAction:@selector(performZoom:)];
}
- (CGSize)toolbarOffset
{
return CGSizeMake(0.0, _titleBarHeight);
return CGSizeMake(0.0, [self valueForThemeAttribute:@"title-bar-height"]);
}
- (void)tile
@@ -280,7 +236,6 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_headView setFrameSize:CGSizeMake(width, headHeight)];
// Set divider to full width to prevent gaps/holes at the sides.
[_dividerView setFrame:CGRectMake(0.0, headHeight, width, _CPStandardWindowViewDividerViewHeight)];
var dividerMinY = 0,
@@ -291,179 +246,52 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_bodyView setFrame:CGRectMake(0.0, dividerMinY, width, CGRectGetHeight(bounds) - dividerMinY)];
[_titleField setFrame:CGRectMake(_buttonsWidth, 0, width - _buttonsWidth * 2.0, _titleBarHeight)];
var leftOffset = 8;
if (_closeButton)
leftOffset += 19.0;
if (_minimizeButton)
leftOffset += 19.0;
[_titleField setFrame:CGRectMake(leftOffset, 0, width - leftOffset * 2.0, [self valueForThemeAttribute:@"title-bar-height"])];
var contentFrame = [_bodyView frame];
[[theWindow contentView] setFrame:CGRectInset(contentFrame, 1.0, 1.0)];
}
- (void)_updateWindowButtons:(BOOL)shouldRefreshLayout
/*
- (void)setAnimatingToolbar:(BOOL)isAnimatingToolbar
{
if (shouldRefreshLayout)
[super setAnimatingToolbar:isAnimatingToolbar];
if ([self isAnimatingToolbar])
{
var mimicWindows = [CPApp shouldMimicWindows];
[[self toolbarView] setAutoresizingMask:CPViewHeightSizable];
if (mimicWindows)
[self setThemeState:CPThemeStateWindowsPlatform];
else
[self unsetThemeState:CPThemeStateWindowsPlatform];
[_headView setAutoresizingMask:CPViewHeightSizable];
[_dividerView setAutoresizingMask:CPViewMinYMargin];
[_bodyView setAutoresizingMask:CPViewMinYMargin];
// Remember that on Cappuccino, buttons are (left to right) : close - minimize - zoom
// and that on Windows, (also left to right) : minimize - zoom - close
var offset,
mask,
closeThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"close-image-origin"] || CGPointMakeZero()),
minimizeThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"minimize-image-origin"] || CGPointMakeZero()),
zoomThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"zoom-image-origin"] || CGPointMakeZero()),
closeThemeSize = CGSizeMakeZero(),
minimizeThemeSize = CGSizeMakeZero(),
zoomThemeSize = CGSizeMakeZero(),
delta1,
delta2;
// For retro-compatibility
if (![self isCSSBased])
{
if (mimicWindows)
{
// There's no zoom button in Aristo2
closeThemeOrigin = CGPointMake(-24.0, 8.0);
minimizeThemeOrigin = CGPointMake(-43.0, 8.0);
// FIXME: if someone designs a zoom button in Aristo2 one day, use those values
// closeThemeOrigin = CGPointMake(-24.0, 8.0);
// minimizeThemeOrigin = CGPointMake(-62.0, 8.0);
// zoomThemeOrigin = CGPointMake(-43.0, 8.0);
}
else
{
closeThemeOrigin = CGPointMake(8.0, 8.0);
minimizeThemeOrigin = CGPointMake(27.0, 8.0);
zoomThemeOrigin = CGPointMake(46.0, 8.0);
}
}
if (mimicWindows)
{
offset = [self bounds].size.width;
mask = CPViewMinXMargin;
delta1 = zoomThemeOrigin.x - closeThemeOrigin.x;
delta2 = minimizeThemeOrigin.x - zoomThemeOrigin.x;
}
else
{
offset = 0;
mask = CPViewMaxXMargin;
delta1 = minimizeThemeOrigin.x - closeThemeOrigin.x;
delta2 = zoomThemeOrigin.x - minimizeThemeOrigin.x;
}
_buttonsWidth = 0;
if (_styleMask & CPClosableWindowMask)
{
closeThemeSize = [self currentValueForThemeAttribute:@"close-image-size"];
// For retro-compatibility:
if (!closeThemeSize)
closeThemeSize = CGSizeMake(16.0, 16.0);
[_closeButton setFrame:CGRectMake(closeThemeOrigin.x + offset, closeThemeOrigin.y, closeThemeSize.width, closeThemeSize.height)];
[_closeButton setAutoresizingMask:mask];
_buttonsWidth = ABS(closeThemeOrigin.x) + (mimicWindows ? 0 : closeThemeSize.width);
}
else
{
minimizeThemeOrigin.x -= delta1;
zoomThemeOrigin.x -= delta1;
}
if (mimicWindows)
{
if (_styleMask & CPResizableWindowMask)
{
zoomThemeSize = [self currentValueForThemeAttribute:@"zoom-image-size"];
// For retro-compatibility:
if (!zoomThemeSize)
zoomThemeSize = CGSizeMake(16.0, 16.0);
[_zoomButton setFrame:CGRectMake(zoomThemeOrigin.x + offset, zoomThemeOrigin.y, zoomThemeSize.width, zoomThemeSize.height)];
[_zoomButton setAutoresizingMask:mask];
_buttonsWidth = ABS(zoomThemeOrigin.x);
}
else
minimizeThemeOrigin.x -= delta2;
if (_styleMask & CPMiniaturizableWindowMask)
{
minimizeThemeSize = [self currentValueForThemeAttribute:@"minimize-image-size"];
// For retro-compatibility:
if (!minimizeThemeSize)
minimizeThemeSize = CGSizeMake(16.0, 16.0);
[_minimizeButton setFrame:CGRectMake(minimizeThemeOrigin.x + offset, minimizeThemeOrigin.y, minimizeThemeSize.width, minimizeThemeSize.height)];
[_minimizeButton setAutoresizingMask:mask];
_buttonsWidth = ABS(minimizeThemeOrigin.x);
}
if (_buttonsWidth > 0)
_buttonsWidth += ABS(closeThemeOrigin.x) - closeThemeSize.width;
}
else // not win
{
if (_styleMask & CPMiniaturizableWindowMask)
{
minimizeThemeSize = [self currentValueForThemeAttribute:@"minimize-image-size"];
// For retro-compatibility:
if (!minimizeThemeSize)
minimizeThemeSize = CGSizeMake(16.0, 16.0);
[_minimizeButton setFrame:CGRectMake(minimizeThemeOrigin.x + offset, minimizeThemeOrigin.y, minimizeThemeSize.width, minimizeThemeSize.height)];
[_minimizeButton setAutoresizingMask:mask];
_buttonsWidth = minimizeThemeOrigin.x + minimizeThemeSize.width;
}
else
zoomThemeOrigin.x -= delta2;
if (_styleMask & CPResizableWindowMask)
{
zoomThemeSize = [self currentValueForThemeAttribute:@"zoom-image-size"];
// For retro-compatibility:
if (!zoomThemeSize)
zoomThemeSize = CGSizeMake(16.0, 16.0);
[_zoomButton setFrame:CGRectMake(zoomThemeOrigin.x + offset, zoomThemeOrigin.y, zoomThemeSize.width, zoomThemeSize.height)];
[_zoomButton setAutoresizingMask:mask];
_buttonsWidth = zoomThemeOrigin.x + zoomThemeSize.width;
}
if (_buttonsWidth > 0)
_buttonsWidth += closeThemeOrigin.x;
}
[self updateTrackingAreas];
[[[self window] contentView] setAutoresizingMask:CPViewNotSizable];
}
else
{
[[self toolbarView] setAutoresizingMask:CPViewWidthSizable];
[self _updateCloseButton];
[_headView setAutoresizingMask:CPViewWidthSizable];
[_dividerView setAutoresizingMask:CPViewWidthSizable];
[_bodyView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_minimizeButton setImage:[self currentValueForThemeAttribute:@"minimize-image-button"]];
[_minimizeButton setAlternateImage:[self currentValueForThemeAttribute:@"minimize-image-highlighted-button"]];
[_zoomButton setImage:[self currentValueForThemeAttribute:@"zoom-image-button"]];
[_zoomButton setAlternateImage:[self currentValueForThemeAttribute:@"zoom-image-highlighted-button"]];
[[[self window] contentView] setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
}
}
*/
- (void)_updateCloseButton
{
[_closeButton setFrameSize:[self valueForThemeAttribute:@"close-image-size"]];
[_closeButton setFrameOrigin:[self valueForThemeAttribute:@"close-image-origin"]];
if (_isDocumentEdited)
{
[_closeButton setImage:[self currentValueForThemeAttribute:@"unsaved-image-button"]];
@@ -476,15 +304,9 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
}
}
- (void)_osBehaviorDidChange:(id)application
{
[self _updateWindowButtons:YES];
}
- (void)setDocumentEdited:(BOOL)isEdited
{
_isDocumentEdited = isEdited;
[self _updateCloseButton];
}
@@ -515,10 +337,9 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_dividerView setHidden:enable];
}
[_closeButton setHidden:enable];
[_closeButton setHidden:enable];
[_minimizeButton setHidden:enable];
[_zoomButton setHidden:enable];
[_titleField setHidden:enable];
[_titleField setHidden:enable];
[[self window] setMovable:!enable];
@@ -560,35 +381,28 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
- (void)layoutSubviews
{
var width = [self bounds].size.width,
mimicWindows = [CPApp shouldMimicWindows];
var bounds = [self bounds];
[super layoutSubviews];
[self _updateCloseButton];
if (_closeButton || _minimizeButton || _zoomButton)
[self _updateWindowButtons:NO];
[_minimizeButton setImage:[self valueForThemeAttribute:@"minimize-image-button"]];
[_minimizeButton setAlternateImage:[self valueForThemeAttribute:@"minimize-image-highlighted-button"]];
[_dividerView setBackgroundColor:[self valueForThemeAttribute:@"divider-color"]];
[_bodyView setBackgroundColor:[self valueForThemeAttribute:@"body-color"]];
[_headView setNeedsLayout];
if (width - 2 * _buttonsWidth < _minimumTitleFieldSize)
{
if (width - _buttonsWidth - _titleMargin < _minimumTitleFieldSize)
[_titleField setFrame:CGRectMake((mimicWindows ? _titleMargin : _buttonsWidth), 0, width - _buttonsWidth - _titleMargin, _titleBarHeight)];
else
[_titleField setFrame:CGRectMake((mimicWindows ? width - _buttonsWidth - _minimumTitleFieldSize : _buttonsWidth), 0, _minimumTitleFieldSize, _titleBarHeight)];
}
}
- (CGSize)_minimumResizeSize
{
// The minimum width is such that the close/minimize/zoom button(s) would always be visible.
// We give the same margin to the right of the button(s) as there is to the left.
var size = CGSizeMakeCopy([super _minimumResizeSize]);
// The minimum width is such that the close button would always be visible.
// We give the same margin to the right of the button as there is to the left.
var size = [super _minimumResizeSize],
closeSize = [self valueForThemeAttribute:@"close-image-size"],
closeOrigin = [self valueForThemeAttribute:@"close-image-origin"];
size.width = _buttonsWidth;
size.width = closeSize.width + (closeOrigin.x * 2);
size.height += _CPStandardWindowViewDividerViewHeight;
return size;
@@ -599,94 +413,4 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
return [_bodyView frame].origin.y;
}
// pragma mark -
// pragma mark Hover management for buttons
- (void)updateTrackingAreas
{
if (_closeButtonTrackingArea)
[_closeButton removeTrackingArea:_closeButtonTrackingArea];
if (_minimizeButtonTrackingArea)
[_minimizeButton removeTrackingArea:_minimizeButtonTrackingArea];
if (_zoomButtonTrackingArea)
[_zoomButton removeTrackingArea:_zoomButtonTrackingArea];
if (_closeButton)
{
_closeButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_closeButton];
[_closeButton addTrackingArea:_closeButtonTrackingArea];
}
if (_minimizeButton)
{
_minimizeButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_minimizeButton];
[_minimizeButton addTrackingArea:_minimizeButtonTrackingArea];
}
if (_zoomButton)
{
_zoomButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_zoomButton];
[_zoomButton addTrackingArea:_zoomButtonTrackingArea];
}
[super updateTrackingAreas];
}
- (void)mouseEntered:(CPEvent)anEvent
{
var triggeredButton = [[anEvent trackingArea] userInfo],
state = CPThemeStateHovered;
if ([CPApp shouldMimicWindows])
state = state.and(CPThemeStateWindowsPlatform);
if (triggeredButton === _closeButton)
{
if (_isDocumentEdited)
[_closeButton setImage:[self valueForThemeAttribute:@"unsaved-image-button" inState:state]];
else
[_closeButton setImage:[self valueForThemeAttribute:@"close-image-button" inState:state]];
}
else if (triggeredButton === _minimizeButton)
{
[_minimizeButton setImage:[self valueForThemeAttribute:@"minimize-image-button" inState:state]];
}
else if (triggeredButton === _zoomButton)
{
[_zoomButton setImage:[self valueForThemeAttribute:@"zoom-image-button" inState:state]];
}
}
- (void)mouseExited:(CPEvent)anEvent
{
var triggeredButton = [[anEvent trackingArea] userInfo];
if (triggeredButton === _closeButton)
{
if (_isDocumentEdited)
[_closeButton setImage:[self currentValueForThemeAttribute:@"unsaved-image-button"]];
else
[_closeButton setImage:[self currentValueForThemeAttribute:@"close-image-button"]];
}
else if (triggeredButton === _minimizeButton)
{
[_minimizeButton setImage:[self currentValueForThemeAttribute:@"minimize-image-button"]];
}
else if (triggeredButton === _zoomButton)
{
[_zoomButton setImage:[self currentValueForThemeAttribute:@"zoom-image-button"]];
}
}
@end
+4 -13
View File
@@ -27,9 +27,6 @@
@implementation _CPTitleableWindowView : _CPWindowView
{
CPTextField _titleField;
int _minimumTitleFieldSize;
int _titleBarHeight;
int _titleMargin;
}
+ (int)titleBarHeight
@@ -50,7 +47,7 @@
+ (CGRect)frameRectForContentRect:(CGRect)aContentRect
{
var frameRect = [super frameRectForContentRect:aContentRect],
var frameRect = CGRectMakeCopy(aContentRect),
titleBarHeight = [self titleBarHeight];
frameRect.origin.y -= titleBarHeight;
@@ -65,19 +62,15 @@
if (self)
{
// We cache some values for optimization
_titleBarHeight = [[self class] titleBarHeight];
_titleMargin = [self currentValueForThemeAttribute:@"title-margin"];
_titleField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_titleField setHitTests:NO];
[_titleField setStringValue:@"Untitled"];
[_titleField sizeToFit];
[_titleField setAutoresizingMask:CPViewWidthSizable];
[self setTitle:@""];
[_titleField setStringValue:@""];
[_titleField setFrame:CGRectMake(_titleMargin, 3.0, CGRectGetWidth([self bounds]) - 2 * _titleMargin, CGRectGetHeight([_titleField frame]))];
[_titleField setFrame:CGRectMake(20.0, 3.0, CGRectGetWidth([self bounds]) - 40.0, CGRectGetHeight([_titleField frame]))];
[self addSubview:_titleField];
@@ -90,8 +83,6 @@
- (void)setTitle:(CPString)aTitle
{
[_titleField setStringValue:aTitle];
_minimumTitleFieldSize = [_titleField _minimumFrameSize].width;
}
- (void)tile
@@ -104,7 +95,7 @@
// The vertical alignment of the title is set by the theme, so just give it all available space. By default
// the title will vertically centre within.
[_titleField setFrame:CGRectMake(_titleMargin, 0, width - 2 * _titleMargin, _titleBarHeight)];
[_titleField setFrame:CGRectMake(20.0, 0, width - 40.0, [[self class] titleBarHeight])];
}
- (void)layoutSubviews
+40 -33
View File
@@ -25,8 +25,8 @@
@implementation _CPToolTipWindowView : _CPWindowView
{
BOOL _mouseDownPressed @accessors(getter=isMouseDownPressed, setter=setMouseDownPressed:);
unsigned _gravity @accessors(property=gravity);
BOOL _mouseDownPressed @accessors(getter=isMouseDownPressed, setter=setMouseDownPressed:);
unsigned _gravity @accessors(property=gravity);
}
@@ -41,30 +41,21 @@
+ (CPDictionary)themeAttributes
{
return @{
// 1. The DOM-based CSS rendering attributes.
// _CPWindowView will natively apply this to the outer window bounds.
@"bezel-color":[CPColor colorWithCSSDictionary:@{
@"background-color": @"#FFFFCA",
@"border": @"1px solid #B0B0B0",
@"border-radius": @"2px",
@"box-sizing": @"border-box",
@"box-shadow": @"0px 1px 3px rgba(0,0,0,0.25)"
}],
@"color": [CPColor blackColor],
// 2. Legacy attributes zeroed out to satisfy the build process/theme inheritance
@"background-color": [CPColor clearColor],
@"stroke-color": [CPColor clearColor],
@"stroke-width": 0.0,
@"border-radius": 0.0
};
@"stroke-color": [CPColor colorWithHexString:@"E3E3E3"],
@"background-color": [CPColor colorWithHexString:@"FFFFCA"],
@"border-radius": 2.0,
@"stroke-width": 1.0,
@"color": [CPColor blackColor],
};
}
/*! compute the contentView frame from a given window frame
@param aFrameRect the window frame
*/
+ (CGRect)contentRectForFrameRect:(CGRect)aFrameRect
{
var contentRect = [super contentRectForFrameRect:aFrameRect];
// This pushes the text inwards so it doesn't touch the outer CSS border
contentRect.origin.x += 3;
contentRect.origin.y += 3;
contentRect.size.width -= 6;
@@ -73,34 +64,50 @@
return contentRect;
}
/*! compute the window frame from a given contentView frame
@param aContentRect the contentView frame
*/
+ (CGRect)frameRectForContentRect:(CGRect)aContentRect
{
var aFrameRect = CGRectMakeCopy(aContentRect);
aFrameRect.origin.x -= 3;
aFrameRect.origin.y -= 3;
aFrameRect.size.width += 9;
aFrameRect.size.height += 9;
aFrameRect.size.width += 6;
aFrameRect.size.height += 6;
return aFrameRect;
}
// MARK: -
// MARK: drawing
- (void)layoutSubviews
{
[super layoutSubviews];
// Apply the CSS dictionary to the standard CPView subview (contentView).
// This bypasses the _CPWindowView canvas interceptor and applies directly to the DOM.
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
}
- (void)drawRect:(CGRect)aRect
{
// Intentionally empty to disable legacy Canvas drawing.
[super drawRect:aRect];
var context = [[CPGraphicsContext currentContext] graphicsPort],
radius = [self currentValueForThemeAttribute:@"border-radius"],
strokeWidth = [self currentValueForThemeAttribute:@"stroke-width"],
strokeColor = [self currentValueForThemeAttribute:@"stroke-color"],
bgColor = [self currentValueForThemeAttribute:@"background-color"];
CGContextSetStrokeColor(context, strokeColor);
CGContextSetFillColor(context, bgColor);
CGContextSetLineWidth(context, strokeWidth);
aRect.origin.x += strokeWidth;
aRect.origin.y += strokeWidth;
aRect.size.width -= strokeWidth * 2;
aRect.size.height -= strokeWidth * 2;
var path = CGPathWithRoundedRectangleInRect(aRect, radius, radius, YES, YES, YES, YES);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGContextAddPath(context, path);
CGContextFillPath(context);
}
@end
-22
View File
@@ -715,28 +715,6 @@ _CPWindowViewResizeSlop = 3;
frame.size.height += dy;
}
// Let the delegate adjust the size. When resizing from the left or top, move the origin
// by the adjustment so that the edge opposite the one being dragged stays in place.
var delegateSize = [theWindow _sendDelegateWindowWillResizeToSize:CGSizeMakeCopy(frame.size)];
switch (_resizeRegion)
{
case _CPWindowViewResizeRegionTopLeft:
case _CPWindowViewResizeRegionLeft:
case _CPWindowViewResizeRegionBottomLeft:
frame.origin.x -= delegateSize.width - frame.size.width;
}
switch (_resizeRegion)
{
case _CPWindowViewResizeRegionTopLeft:
case _CPWindowViewResizeRegionTop:
case _CPWindowViewResizeRegionTopRight:
frame.origin.y -= delegateSize.height - frame.size.height;
}
frame.size = delegateSize;
[theWindow _setFrame:frame display:YES animate:NO constrainWidth:NO constrainHeight:NO];
[self setCursorForLocation:location resizing:YES];
}
+2
View File
@@ -81,7 +81,9 @@ var _CPCibCustomObjectClassName = @"_CPCibCustomObjectClassName";
if (!theClass)
{
#if DEBUG
CPLog("Unknown class \"" + _className + "\" in cib file");
#endif
theClass = [CPObject class];
}
+2
View File
@@ -82,7 +82,9 @@ var _CPCibCustomViewClassNameKey = @"_CPCibCustomViewClassNameKey";
// FIXME: Should we instead throw an exception?
if (!theClass)
{
#if DEBUG
CPLog("Unknown class \"" + _className + "\" in cib file, using CPView instead.");
#endif
theClass = [CPView class];
}
+2
View File
@@ -91,7 +91,9 @@
if (!windowClass)
{
#if DEBUG
CPLog.warn("Unknown class \"%@\" in cib file, using CPWindow instead.", [self windowClass]);
#endif
windowClass = [CPWindow class];
}
+1 -4
View File
@@ -23,10 +23,7 @@
@import "CGGeometry.j"
@import "CPCompatibility.j"
function PIXEL(pixels)
{
return pixels + "px";
}
#define PIXEL(pixels) pixels + "px";
function CABackingStoreGetContext(aBackingStore)
+58 -67
View File
@@ -28,15 +28,11 @@
@import "CGContext.j"
@import "CGGeometry.j"
@import "CPColor.j"
@import "CPCompatibility.j"
@import "CPView.j"
@import "CAMediaTimingFunction.j"
function DOM(aLayer)
{
return aLayer._DOMElement;
}
#define DOM(aLayer) aLayer._DOMElement
var CALayerGeometryBoundsMask = 1,
CALayerGeometryPositionMask = 2,
@@ -170,19 +166,18 @@ var CALayerRegisteredRunLoopUpdates = nil;
_activeAnimations = [CPMutableDictionary dictionary];
if (CPDOMAvailable)
{
_DOMElement = document.createElement("div");
#if PLATFORM(DOM)
_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";
}
_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
}
return self;
@@ -508,48 +503,47 @@ var CALayerRegisteredRunLoopUpdates = nil;
*/
- (void)display
{
if (CPDOMAvailable)
#if PLATFORM(DOM)
if (!_context)
{
if (!_context)
{
_context = CGBitmapGraphicsContextCreate();
_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);
}
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)];
}
_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)];
}
#endif
[self composite];
}
@@ -670,6 +664,10 @@ 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.
*/
@@ -722,17 +720,16 @@ var CALayerRegisteredRunLoopUpdates = nil;
else if (superlayer != nil)
[aLayer removeFromSuperlayer];
[self _adjustContentsZIndexForLayer:aLayer];
ADJUST_CONTENTS_ZINDEX(aLayer);
[_sublayers insertObject:aLayer atIndex:anIndex];
if (CPDOMAvailable)
{
if (anIndex >= _sublayers.length - 1)
_DOMElement.appendChild(DOM(aLayer));
else
_DOMElement.insertBefore(DOM(aLayer), _sublayers[anIndex + 1]._DOMElement);
}
#if PLATFORM(DOM)
if (anIndex >= _sublayers.length - 1)
_DOMElement.appendChild(DOM(aLayer));
else
_DOMElement.insertBefore(DOM(aLayer), _sublayers[anIndex + 1]._DOMElement);
#endif
aLayer._superlayer = self;
@@ -785,7 +782,7 @@ var CALayerRegisteredRunLoopUpdates = nil;
return;
}
[self _adjustContentsZIndexForLayer:aLayer];
ADJUST_CONTENTS_ZINDEX(aLayer);
[_sublayers replaceObjectAtIndex:[_sublayers indexOfObjectIdenticalTo:aSublayer] withObject:aLayer];
_DOMElement.replaceChild(DOM(aSublayer), DOM(aLayer));
@@ -1270,12 +1267,6 @@ var CALayerRegisteredRunLoopUpdates = nil;
window.loop = false;
}
- (void)_adjustContentsZIndexForLayer:(CALayer)aLayer
{
if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)
_DOMContentsElement.style.zIndex -= 100.0;
}
@end
function _CALayerUpdateSublayerTransformForSublayers(aLayer)
@@ -1293,8 +1284,7 @@ function _CALayerUpdateSublayerTransformForSublayers(aLayer)
function _CALayerUpdateDOM(aLayer, aMask)
{
if (!CPDOMAvailable) return;
#if PLATFORM(DOM)
var DOMElementStyle = aLayer._DOMElement.style;
if (aMask & CALayerZPositionUpdateMask)
@@ -1325,6 +1315,7 @@ function _CALayerUpdateDOM(aLayer, aMask)
DOMContentsElement.style.height = height + "px";
}
}
#endif
}
function _CALayerRecalculateGeometry(aLayer, aGeometryChange)
+34
View File
@@ -70,6 +70,9 @@ var _CPAnimationContextStack = nil,
{
if (!_animationFlushingObserver)
{
#if (DEBUG)
CPLog.debug("create new observer");
#endif
_animationFlushingObserver = CFRunLoopObserverCreate(2, true, 0, _animationFlushingObserverCallback, 0);
CFRunLoopAddObserver([CPRunLoop mainRunLoop], _animationFlushingObserver);
}
@@ -98,6 +101,9 @@ var _CPAnimationContextStack = nil,
[context _flushAnimations];
[_CPAnimationContextStack removeLastObject];
#if (DEBUG)
CPLog.debug(_cmd + "context stack =" + _CPAnimationContextStack);
#endif
return YES;
}
@@ -228,6 +234,9 @@ var _CPAnimationContextStack = nil,
{
if (_completionHandlerAgent)
{
#if (DEBUG)
CPLog.debug("No animations are scheduled. Firing completion handler");
#endif
_completionHandlerAgent.fire();
}
}
@@ -257,6 +266,9 @@ var _CPAnimationContextStack = nil,
{
if (n == 0)
{
#if (DEBUG)
CPLog.debug("Animations are not needed. Firing completion handler");
#endif
_completionHandlerAgent.fire();
}
else
@@ -266,12 +278,18 @@ 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();
}
}
@@ -332,6 +350,9 @@ 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]];
};
@@ -380,6 +401,9 @@ var _CPAnimationContextStack = nil,
if (aCompletionHandler)
{
_completionHandlerAgent = new CompletionHandlerAgent(aCompletionHandler);
#if (DEBUG)
CPLog.debug("created a new completion Agent with id " + _completionHandlerAgent.id);
#endif
}
else
{
@@ -539,6 +563,9 @@ CompletionHandlerAgent.prototype.invalidate = function()
var _animationFlushingObserverCallback = function()
{
#if (DEBUG)
CPLog.debug("_animationFlushingObserverCallback");
#endif
if ([_CPAnimationContextStack count] == 1)
{
var context = [_CPAnimationContextStack lastObject];
@@ -546,8 +573,15 @@ 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;
}
+4 -3
View File
@@ -394,10 +394,11 @@ var frameToCSSTranslationTransformMatrix = function(start, current)
- (Object)_DOMElement
{
if (CPDOMAvailable)
return _DOMElement;
#if PLATFORM(DOM)
return _DOMElement;
#else
return nil;
#endif
}
- (CPString)debug_description
+1 -1
View File
@@ -212,7 +212,7 @@ function CGColorEqualToColor(lhs, rhs)
if (lhs.pattern != rhs.pattern)
return false;
if (lhs.colorspace !== rhs.colorspace)
if (CGColorSpaceEqualToColorSpace(lhs.colorspace, rhs.colorspace))
return false;
return true;
+22 -28
View File
@@ -69,10 +69,8 @@ kCGColorSpaceSRGB = "CGColorSpaceSRGB";
var _CGNamedColorSpaces = {};
function _CGColorSpaceCreateWithModel(aModel, aComponentCount, aBaseColorSpace)
{
return { model:aModel, count:aComponentCount, base:aBaseColorSpace };
}
#define _CGColorSpaceCreateWithModel(aModel, aComponentCount, aBaseColorSpace) \
{ model:aModel, count:aComponentCount, base:aBaseColorSpace }
function CGColorSpaceCreateCalibratedGray(aWhitePoint, aBlackPoint, gamma)
{
@@ -210,6 +208,24 @@ 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;
@@ -217,20 +233,7 @@ 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.
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;
STANDARDIZE(components, count, 0, 1, 1000);
if (aColorSpace.base)
aColorSpace = aColorSpace.base;
@@ -242,16 +245,7 @@ function CGColorSpaceStandardizeComponents(aColorSpace, components)
case kCGColorSpaceModelCMYK:
case kCGColorSpaceModelDeviceN:
while (count--)
{
var standardizingComponent = components[count];
if (standardizingComponent < 0)
components[count] = 0;
else if (standardizingComponent > 1)
components[count] = 1;
else
components[count] = ROUND(standardizingComponent * 255) / 255;
}
STANDARDIZE(components, count, 0, 1, 255);
break;
// We don't currently support these color spaces.
+3 -649
View File
@@ -730,657 +730,11 @@ function CGContextStrokeRoundedRectangleInRect(aContext, aRect, aRadius, ne, se,
*/
if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
{
// --- 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)
{
return aContext.arc(x, y, radius, startAngle, endAngle, anticlockwise);
#include "CGContextCanvas.j"
}
function _CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius)
else if (CPFeatureIsCompatible(CPVMLFeature))
{
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;
}
#include "CGContextVML.j"
}
else
{
+635
View File
@@ -0,0 +1,635 @@
/*
* 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;
}
Loaded 100 of 1182 files, more files were not shown because too many files have changed in this diff. Show more