mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-31 04:17:36 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
048c7e269c |
No files matched your search
@@ -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
|
||||
|
||||
@@ -26,5 +26,3 @@ node_modules
|
||||
/dist/cappuccino/package.json
|
||||
/dist/cappuccino/lib
|
||||
/dist/cappuccino/bin
|
||||
Tests/Manual/.Frameworks
|
||||
/Tests/Manual/index.html
|
||||
@@ -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
@@ -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
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+8
-5
@@ -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
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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 "";
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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]];
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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];
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
@@ -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];
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+216
-282
@@ -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];
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
@class CPLayoutManager
|
||||
@class CPTextContainer
|
||||
@class CPFontManager
|
||||
@class _CPFontPanelPreviewView
|
||||
|
||||
/*
|
||||
Collection indexes
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
+583
-954
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+319
-2150
File diff suppressed because it is too large.
Load diff
@@ -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];
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -81,7 +81,9 @@ var _CPCibCustomObjectClassName = @"_CPCibCustomObjectClassName";
|
||||
|
||||
if (!theClass)
|
||||
{
|
||||
#if DEBUG
|
||||
CPLog("Unknown class \"" + _className + "\" in cib file");
|
||||
#endif
|
||||
theClass = [CPObject class];
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,9 @@
|
||||
|
||||
if (!windowClass)
|
||||
{
|
||||
#if DEBUG
|
||||
CPLog.warn("Unknown class \"%@\" in cib file, using CPWindow instead.", [self windowClass]);
|
||||
#endif
|
||||
windowClass = [CPWindow class];
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,7 @@
|
||||
@import "CGGeometry.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
function PIXEL(pixels)
|
||||
{
|
||||
return pixels + "px";
|
||||
}
|
||||
#define PIXEL(pixels) pixels + "px";
|
||||
|
||||
|
||||
function CABackingStoreGetContext(aBackingStore)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
Reference in new issue
Block a user