Compare commits

..
1747 changed files with 191464 additions and 153241 deletions
-12
View File
@@ -1,12 +0,0 @@
# EditorConfig reference: https://editorconfig.org
# https://editorconfig.org/#file-format-details
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
-34
View File
@@ -1,34 +0,0 @@
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node build
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [24.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: echo "${PWD}/dist/cappuccino/bin" >> $GITHUB_PATH
- run: echo "${PWD}/dist/objective-j/bin" >> $GITHUB_PATH
- run: npm install
- run: npm update
- run: jake dist
- run: jake test-only
@@ -1,180 +0,0 @@
name: Main branch - Build Testbook with fresh frameworks and manual tests & deploy to GitHub Pages
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: write
pages: write # required by deploy-pages
id-token: write # required by deploy-pages
pull-requests: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout cappuccino (this repo)
uses: actions/checkout@v5
- name: Checkout Cappuccino-Testbook into ./testbook
uses: actions/checkout@v5
with:
repository: ArgosOz/Cappuccino-Testbook
path: testbook
- 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
- run: npm install
- run: npm update
- run: jake dist
- run: jake test-only
# Refresh Frameworks from dist → testbook/Frameworks
- name: Refresh Frameworks
run: |
set -euo pipefail
if [ ! -d dist ]; then
echo "❌ dist/ not found at repo root" >&2
exit 1
fi
mkdir -p testbook/Frameworks
rm -rf testbook/Frameworks/*
cp -a dist/cappuccino/Frameworks/{Foundation,AppKit} testbook/Frameworks/
cp -a dist/objective-j/Frameworks/Objective-J testbook/Frameworks/
echo "Frameworks populated:" && ls -la testbook/Frameworks || true
- name: Generate manifest of manual test directories
if: ${{ hashFiles('Tests/Manual/**') != '' }}
run: |
set -euo pipefail
mkdir -p artifacts
OUT="testbook/Resources/recipes.txt"
: > "$OUT"
if [ ! -d "Tests/Manual" ]; then
echo "❌ Tests/Manual not found at repo root" >&2
exit 1
fi
# Iterate over immediate subdirectories of Tests/Manual (null-safe for spaces)
while IFS= read -r -d '' d; do
name=$(basename "$d")
ts=$(date +%s%3N) # milliseconds since epoch
printf '%s|%s|\n' "$ts" "$name" >> "$OUT"
# Optional tiny sleep to help keep timestamps distinct:
sleep 0.01
done < <(find "Tests/Manual" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
echo "Wrote $OUT:"
cat "$OUT"
# Replace subdirectories inside testbook/Resources with Tests/Manual subdirectories
- name: Sync Resources from Tests/Manual
run: |
set -euo pipefail
if [ ! -d "Tests/Manual" ]; then
echo "❌ Tests/Manual not found at repo root" >&2
exit 1
fi
mkdir -p testbook/Resources
# Remove only immediate subdirectories (keep stray files if any)
find testbook/Resources -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -r rm -rf
# Copy each immediate subdirectory from Tests/Manual → testbook/Resources
while IFS= read -r -d '' d; do
cp -a "$d" testbook/Resources/
done < <(find Tests/Manual -mindepth 1 -maxdepth 1 -type d -print0)
echo "Resources now contains:" && ls -la testbook/Resources || true
# Ensure each test Index.html includes the include path line before OBJJ_MAIN_FILE
- name: Inject OBJJ_INCLUDE_PATHS into Index.html files
run: |
set -euo pipefail
found=0
while IFS= read -r -d '' f; do
found=1
if grep -q 'OBJJ_INCLUDE_PATHS' "$f"; then
echo "Already updated: $f"
continue
fi
perl -0777 -i -pe 's/OBJJ_MAIN_FILE\s*=\s*"main\.j";/OBJJ_INCLUDE_PATHS = ["..\/..\/Frameworks"];\nOBJJ_MAIN_FILE = "main.j";/i' "$f"
perl -0777 -i -pe 's/Frameworks\/Objective-J\/Objective-J.js/\.\.\/\.\.\/Frameworks\/Objective-J\/Objective-J.js/' "$f"
echo "Updated: $f"
done < <(find testbook/Resources -mindepth 2 -maxdepth 2 -type f -iname "index.html" -print0)
if [ "$found" -eq 0 ]; then
echo "⚠️ No index.html files found under testbook/Resources/*/" >&2
fi
# Optional: prune VCS/CI metadata from the served content
- name: Clean testbook for Pages (optional)
run: |
set -euo pipefail
test -f testbook/index.html
test -d testbook/Resources
test -d testbook/Frameworks
rm -rf testbook/.git testbook/.github || true
find testbook -maxdepth 1 -type f -name ".git*" -delete || true
- name: Publish to gh-pages (preserve PR previews)
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: testbook
clean-exclude: pr-preview/ # keep PR preview folders
force: false # avoid force-push so previews survive
- name: Checkout gh-pages ./gh-pages
uses: actions/checkout@v5
with:
path: gh-pages
ref: gh-pages
- name: Clean gh-pages for Pages
run: |
set -euo pipefail
test -d gh-pages
rm -rf gh-pages/.git gh-pages/.github || true
find gh-pages -maxdepth 1 -type f -name ".git*" -delete || true
find gh-pages -type f -name "*.xib" -delete || true
find gh-pages -type f -name "*xcode*" -delete || true
find gh-pages -name "Jakefile" -delete || true
- name: Upload Pages artifact (testbook/)
uses: actions/upload-pages-artifact@v4
with:
path: gh-pages
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
concurrency:
group: pages
cancel-in-progress: true
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- name: Comment Pages URL on PR
if: ${{ github.event_name == 'pull_request' }}
uses: actions/github-script@v7
with:
script: |
const url = `${{ toJSON(steps.deployment.outputs.page_url) }}`;
const body = `📄 GitHub Pages preview for this PR:\n\n${url}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
+1 -11
View File
@@ -17,14 +17,4 @@ Tests/Manual/**/*.xcodeproj
*.sublime-project
*.sublime-workspace
*.tm_properties
*.idea
node_modules
*.vscode
/dist/objective-j/objj-executable
/dist/objective-j/package.json
/dist/objective-j/lib
/dist/cappuccino/package.json
/dist/cappuccino/lib
/dist/cappuccino/bin
Tests/Manual/.Frameworks
/Tests/Manual/index.html
*.idea
-28
View File
@@ -1,28 +0,0 @@
.DS_Store
Frameworks
!/dist/objective-j/Frameworks
!/dist/cappuccino-j/Frameworks
Build
Demos
./Aristo
WebSite
.push-package
*.xcodeproj*
*.xcodeproj/*.pbxuser
*.xcodeproj/*.perspectivev3
xcuserdata/
!*.xcodeproj/project.pbxproj
*.xCodeSupport/
*.XcodeSupport/
*XcodeSupport/
Tests/Manual/**/*.xcodeproj
*.sublime-project
*.sublime-workspace
*.tm_properties
*.idea
node_modules
*.vscode
/dist/objective-j/objj-executable
/dist/objective-j/package.json
/dist/cappuccino/package.json
+3 -3
View File
@@ -62,6 +62,8 @@
@import "CPDocument.j"
@import "CPDocumentController.j"
@import "CPEvent.j"
@import "CPFlashMovie.j"
@import "CPFlashView.j"
@import "CPFont.j"
@import "CPFontManager.j"
@import "CPGradient.j"
@@ -114,6 +116,4 @@
@import "CPWebView.j"
@import "CPWindow.j"
@import "CPWindowController.j"
@import "CPWorkspace.j"
@import "CPFontPanel.j"
@import "CPTreeController.j"
@import "CPWorkspace.j"
+40 -90
View File
@@ -172,7 +172,7 @@ var bottomHeight = 71;
BOOL _needsLayout;
}
// MARK: Creating Alerts
#pragma mark Creating Alerts
/*!
Returns a CPAlert object with the provided info
@@ -249,8 +249,8 @@ var bottomHeight = 71;
}
// MARK: -
// MARK: Delegate
#pragma mark -
#pragma mark Delegate
/*!
Set the delegate of the receiver
@@ -272,7 +272,7 @@ var bottomHeight = 71;
}
// MARK: Accessors
#pragma mark Accessors
- (CPTheme)theme
{
@@ -402,7 +402,7 @@ var bottomHeight = 71;
_needsLayout = YES;
}
// MARK: Accessing Buttons
#pragma mark Accessing Buttons
/*!
Adds a button with a given title to the receiver.
@@ -418,32 +418,27 @@ 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];
[_buttons insertObject:button atIndex:0];
}
// MARK: Layout
#pragma mark Layout
/*!
@ignore
@@ -535,9 +530,6 @@ var bottomHeight = 71;
[[_window contentView] addSubview:_suppressionButton];
}
/*!
@ignore
*/
/*!
@ignore
*/
@@ -547,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:
@@ -571,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];
@@ -633,7 +599,8 @@ var bottomHeight = 71;
[_alertHelpButton setFrame:helpFrame];
}
return finalContentSize;
panelSize.height += [aRepresentativeButton frameSize].height + inset.bottom + buttonOffset;
return panelSize;
}
/*!
@@ -647,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)
@@ -686,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)
@@ -708,7 +664,7 @@ var bottomHeight = 71;
_needsLayout = NO;
}
// MARK: Displaying Alerts
#pragma mark Displaying Alerts
/*!
Displays the \c CPAlert panel as a modal dialog. The user will not be
@@ -786,7 +742,7 @@ var bottomHeight = 71;
[self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
}
// MARK: Private
#pragma mark Private
/*!
@ignore
@@ -796,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]];
+9 -9
View File
@@ -63,8 +63,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
// MARK: -
// MARK: Class Methods
#pragma mark -
#pragma mark Class Methods
/*! Returns the current default CPAppearance
*/
@@ -99,8 +99,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
/*! Creates a CPAppearance object initialized to the specified appearance file in the specified bundle
This method does actually nothing special. It just creates a default appearance object
@@ -122,8 +122,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
// MARK: -
// MARK: Implementation
#pragma mark -
#pragma mark Implementation
- (BOOL)isEqual:(id)anObject
{
@@ -139,8 +139,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
// MARK: -
// MARK: CPCoding
#pragma mark -
#pragma mark CPCoding
- (id)initWithCoder:(CPCoder)aCoder
{
@@ -159,4 +159,4 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
[aCoder encodeBool:_allowsVibrancy forKey:@"_allowsVibrancy"];
}
@end
@end
+9 -82
View File
@@ -122,10 +122,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
CPPanel _aboutPanel;
CPThemeBlend _themeBlend @accessors(property=themeBlend);
// OS behavior
CPApplicationOSBehavior _OSBehavior;
BOOL _simulatesWindows;
}
/*!
@@ -136,7 +132,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
+ (CPApplication)sharedApplication
{
if (!CPApp)
CPApp = [[self alloc] init];
CPApp = [[CPApplication alloc] init];
return CPApp;
}
@@ -158,9 +154,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
_eventListenerInsertionIndex = 0;
_windows = [[CPNull null]];
_OSBehavior = CPApplicationLegacyOSBehavior;
_simulatesWindows = NO;
}
return self;
@@ -486,7 +479,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
[self _didResignActive];
}
- (BOOL)isActive
- (void)isActive
{
return _isActive;
}
@@ -635,6 +628,11 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
var theWindow = [anEvent window];
// Check if this is a candidate for key equivalent...
if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
// The key equivalent was handled.
return;
if ([anEvent type] == CPMouseMoved)
{
if (theWindow !== _lastMouseMoveWindow)
@@ -671,11 +669,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
_eventListenerInsertionIndex = _eventListeners.length;
// Check if this is a candidate for key equivalent...
if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
// The key equivalent was handled.
return;
if (theWindow)
[theWindow sendEvent:anEvent];
}
@@ -1267,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
@@ -1420,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"];
@@ -1436,34 +1391,6 @@ var _CPAppBootstrapperActions = nil;
[[CPApplication sharedApplication] setThemeBlend:aThemeBlend];
[CPTheme setDefaultTheme:[CPTheme themeNamed:[CPApplication defaultThemeName]]];
// Search in the Info.plist if the special CPApplicationSimulateWindowsOS flag is set (for testing)
[CPApp setSimulatesWindows:!![[CPBundle mainBundle] objectForInfoDictionaryKey:"CPApplicationSimulateWindowsOS"]];
// Before loading the main CIB, try to find if a CPApplicationOSBehavior is specified in the Info.plist or in the user defaults
// (with user defaults precedence). Value stored must be a string representing the name of the OS behavior.
var plistOSBehavior = [[CPBundle mainBundle] objectForInfoDictionaryKey:"CPApplicationOSBehavior"],
userOSBehavior = [[CPUserDefaults standardUserDefaults] objectForKey:@"CPApplicationOSBehavior"];
if (userOSBehavior)
{
var osBehavior = [CPApplicationOSBehaviors objectForKey:userOSBehavior];
if (osBehavior)
[CPApp setOSBehavior:osBehavior];
else
CPLog.warn("Invalid CPApplicationOSBehavior specified in user defaults (found:"+userOSBehavior+"). Ignored.");
}
else if (plistOSBehavior)
{
var osBehavior = [CPApplicationOSBehaviors objectForKey:plistOSBehavior];
if (osBehavior)
[CPApp setOSBehavior:osBehavior];
else
CPLog.warn("Invalid CPApplicationOSBehavior specified in Info.plist (found:"+plistOSBehavior+"). Ignored.");
}
[self performActions];
}
-12
View File
@@ -39,15 +39,3 @@ CPTerminateLater = -1; // not currently supported
CPRunStoppedResponse = -1000;
CPRunAbortedResponse = -1001;
CPRunContinuesResponse = -1002;
// Should the application follow Cappuccino UX-UI (which is OSX like) or OS UX-UI (mainly Windows) ?
// See explanation on https://github.com/cappuccino/cappuccino/wiki/CPApplicationSelectedOSBehavior
@typedef CPApplicationOSBehavior
CPApplicationLegacyOSBehavior = 1;
CPApplicationFollowOSBehavior = 2;
CPApplicationOSBehaviorDidChangeNotification = @"CPApplicationOSBehaviorDidChangeNotification";
CPApplicationOSBehaviors = @{
@"CPApplicationLegacyOSBehavior": CPApplicationLegacyOSBehavior,
@"CPApplicationFollowOSBehavior": CPApplicationFollowOSBehavior
};
+25 -24
View File
@@ -219,6 +219,17 @@
_clearsFilterPredicateOnInsertion = aFlag;
}
/*!
Whether the receiver will always return the multiple values marker when multiple
items are selected, even if the items have the same value.
@return BOOL YES if the receiver always uses the multiple values marker
*/
- (BOOL)alwaysUsesMultipleValuesMarker
{
return _alwaysUsesMultipleValuesMarker;
}
/*!
Sets whether the receiver should always return the multiple values marker when multiple
items are selected, even if the items have the same value.
@@ -271,7 +282,7 @@
if (_disableSetContent)
return;
if (value == nil)
if (value === nil)
value = [];
if (![value isKindOfClass:[CPArray class]])
@@ -297,18 +308,27 @@
class.
*/
if (_clearsFilterPredicateOnInsertion)
[self willChangeValueForKey:@"filterPredicate"];
// Don't use [super setContent:] as that would fire the contentObject change.
// We need to be in control of when notifications fire.
// Note that if we have a contentArray binding, setting the content does /not/
// cause a reverse binding set.
_contentObject = value;
[self _rearrangeObjects];
if (_clearsFilterPredicateOnInsertion && _filterPredicate != nil)
[self __setFilterPredicate:nil]; // Causes a _rearrangeObjects.
else
[self _rearrangeObjects];
if ([self preservesSelection])
[self __setSelectedObjects:oldSelectedObjects];
else
[self __setSelectionIndexes:oldSelectionIndexes];
if (_clearsFilterPredicateOnInsertion)
[self didChangeValueForKey:@"filterPredicate"];
}
/*!
@@ -609,25 +629,6 @@
return YES;
}
- (void)_selectionWillChange
{
// Push back all data from the dirty editors before it is too late.
var editorsCount = [_editors count];
while (editorsCount--)
{
var allBindings = [CPBinder allBindingsForObject:_editors[editorsCount]],
allKeys = [allBindings allKeys],
keysCount = allKeys.length;
while (keysCount--)
[[allBindings objectForKey:allKeys[keysCount]] reverseSetValueFor:allKeys[keysCount]];
}
[super _selectionWillChange];
}
/*!
Returns an array of the selected objects.
@@ -773,7 +774,7 @@
_filterPredicate = nil;
[self _rearrangeObjects];
}
else if (_filterPredicate == nil || [_filterPredicate evaluateWithObject:object])
else if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
{
// Insert directly into the array.
var pos = [_arrangedObjects insertObject:object inArraySortedByDescriptors:_sortDescriptors];
@@ -786,7 +787,7 @@
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:1];
}
/*
else if (_filterPredicate != nil)
else if (_filterPredicate !== nil)
...
// Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does
// not appear in arrangedObjects and we do not have to update at all.
@@ -886,7 +887,7 @@
_disableSetContent = NO;
if (_filterPredicate == nil || [_filterPredicate evaluateWithObject:object])
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object])
{
// selectionIndexes change notification will be fired as a result of the
// content change. Don't fire manually.
+2 -46
View File
@@ -231,7 +231,7 @@ var DefaultLineWidth = 1.0;
/*!
Cocoa compatibility.
*/
- (void)getLineDash:(CPArrayRef)patternRef count:(CPInteger)count phase:(CGFloatRef)phaseRef
- (void)getLineDash:(CPArrayRef)patternRef count:(NSInteger)count phase:(CGFloatRef)phaseRef
{
return [self getLineDash:patternRef phase:phaseRef];
}
@@ -250,7 +250,7 @@ var DefaultLineWidth = 1.0;
/*!
Cocoa compatibility.
*/
- (void)setLineDash:(CPArray)aPattern count:(CPInteger)count phase:(CGFloat)aPhase
- (void)setLineDash:(CPArray)aPattern count:(NSInteger)count phase:(CGFloat)aPhase
{
[self setLineDash:aPattern phase:aPhase];
}
@@ -378,47 +378,3 @@ var DefaultLineWidth = 1.0;
}
@end
@implementation CPBezierPath (AnimationAdditions)
- (CPString)SVGString
{
var pathString = "";
var elements = _path.elements;
var count = _path.count;
for (var i = 0; i < count; i++)
{
var element = elements[i];
// Use the kCGPathElement* constants defined in CGPath.j
switch (element.type)
{
case kCGPathElementMoveToPoint:
pathString += "M " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddLineToPoint:
pathString += "L " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddQuadCurveToPoint:
pathString += "Q " + element.cpx + " " + element.cpy + " " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddCurveToPoint:
pathString += "C " + element.cp1x + " " + element.cp1y + " " + element.cp2x + " " + element.cp2y + " " + element.x + " " + element.y + " ";
break;
case kCGPathElementCloseSubpath:
pathString += "Z ";
break;
}
}
return pathString.trim();
}
@end
+73 -367
View File
@@ -22,14 +22,13 @@
@import "CPTextField.j"
@import "CPView.j"
@import <Foundation/CPGeometry.j>
// CPBoxType
@typedef CPBoxType
CPBoxPrimary = 0;
CPBoxSecondary = 1; // Deprecated
CPBoxSecondary = 1;
CPBoxSeparator = 2;
CPBoxOldStyle = 3; // Deprecated
CPBoxOldStyle = 3;
CPBoxCustom = 4;
// CPBorderType
@@ -59,16 +58,12 @@ CPBelowBottom = 6;
@implementation CPBox : CPView
{
CPBoxType _boxType;
CPBorderType _borderType; // deprecated
CPBorderType _borderType;
CPView _contentView;
CPView _boxView; // needed for CSS theming, will be transparent for non CSS themes
BOOL _transparent @accessors(getter=isTransparent);
CPString _title @accessors(getter=title);
int _titlePosition @accessors(getter=titlePosition);
CPString _title @accessors(getter=title);
int _titlePosition @accessors(getter=titlePosition);
CPTextField _titleView;
BOOL _cachedAutoresizesSubviews;
}
+ (Class)_binderClassForBinding:(CPString)aBinding
@@ -95,14 +90,6 @@ CPBelowBottom = 6;
@"inner-shadow-size": 6.0,
@"inner-shadow-color": [CPNull null],
@"content-margin": CGSizeMakeZero(),
@"title-font": [CPNull null],
@"title-left-offset": 5.0,
@"title-top-offset": 0.0,
@"title-color": [CPNull null],
@"nib2cib-adjustment-primary-frame": CGRectMake(4, -4, -8, -6),
@"content-adjustment": CGRectMakeZero(),
@"min-y-correction-no-title": 0,
@"min-y-correction-title": 0
};
}
@@ -127,26 +114,16 @@ CPBelowBottom = 6;
if (self)
{
_borderType = CPGrooveBorder; // Was CPBezelBorder but Cocoa default is CPGrooveBorder
_boxType = CPBoxPrimary;
_borderType = CPBezelBorder;
_titlePosition = CPNoTitle;
_titleView = [CPTextField labelWithTitle:@""];
[_titleView setFont:[self titleFont]];
[_titleView setTextColor:[self titleColor]];
_boxView = [[CPView alloc] initWithFrame:[self bounds]];
[_boxView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
_contentView = [[CPView alloc] initWithFrame:[self bounds]];
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[self setAutoresizesSubviews:YES];
[self addSubview:_boxView];
[_boxView setAutoresizesSubviews:YES];
[_boxView addSubview:_contentView];
[self sizeToFit];
[self addSubview:_contentView];
}
return self;
@@ -178,8 +155,6 @@ CPBelowBottom = 6;
*/
- (CPBorderType)borderType
{
CPLog.warn("CPBox borderType is deprecated.");
return _borderType;
}
@@ -202,8 +177,7 @@ CPBelowBottom = 6;
return;
_borderType = aBorderType;
[self refreshDisplay];
[self setNeedsDisplay:YES];
}
/*!
@@ -243,72 +217,26 @@ CPBelowBottom = 6;
*/
- (void)setBoxType:(CPBoxType)aBoxType
{
if ((aBoxType == CPBoxSecondary) || (aBoxType == CPBoxOldStyle))
CPLog.warn("CPBox setBoxType: CPBoxSecondary and CPBoxOldStyle are deprecated.");
if (_boxType === aBoxType)
return;
_boxType = aBoxType;
[self refreshDisplay];
[self setNeedsDisplay:YES];
}
- (void)setTransparent:(BOOL)shouldBeTransparent
{
if (_transparent == shouldBeTransparent)
return;
_transparent = shouldBeTransparent;
[self _manageTitlePositioning];
}
// MARK: -
// MARK: Style properties which override theme values
/*!
The borderColor, borderWidth, cornerRadius and fillColor properties for the receiver
are only supported for boxes with boxType === CPBoxCustom and borderType === CPLineBorder.
Boxes with the Primary boxType have fixed values which are defined by the system theme.
Apple does support lineTypes of Groove and Bezel for boxes of type CPBoxCustom, CPBoxSecondary and CPBoxOldStyle,
but they are deprecated as of macOS 10.15.
Cappuccino has supported these in the past but no longer does so - both to simplify CSS-based theming and
to avoid the effort needed for supporting something which will be very short-lived.
These styles can be recreated as custom theme elements by developers, as needed.
Additionally, boxes with boxType === CPBoxSeparator (horizontal and vertical lines) have never allowed changing these values.
No warnings are generated for separator boxes.
*/
// See discussion above.
// MARK: borderColor
- (CPColor)borderColor
{
return [self currentValueForThemeAttribute:@"border-color"];
return [self valueForThemeAttribute:@"border-color"];
}
- (void)setBorderColor:(CPColor)color
{
if (_boxType === CPBoxSeparator)
{
return;
}
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setBorderColor: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setBorderColor. Property is ignored.");
return;
}
if ([color isEqual:[self borderColor]])
return;
[self setValue:color forThemeAttribute:@"border-color"];
}
// See discussion above.
// MARK: borderWidth
- (float)borderWidth
{
return [self valueForThemeAttribute:@"border-width"];
@@ -316,25 +244,12 @@ CPBelowBottom = 6;
- (void)setBorderWidth:(float)width
{
if (_boxType === CPBoxSeparator)
{
return;
}
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setBorderWidth: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setBorderWidth. Property is ignored.");
return;
}
if (width === [self borderWidth])
return;
[self setValue:width forThemeAttribute:@"border-width"];
}
// See discussion above.
// MARK: cornerRadius
- (float)cornerRadius
{
return [self valueForThemeAttribute:@"corner-radius"];
@@ -342,25 +257,12 @@ CPBelowBottom = 6;
- (void)setCornerRadius:(float)radius
{
if (_boxType === CPBoxSeparator)
{
return;
}
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setCornerRadius: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setCornerRadius. Property is ignored.");
return;
}
if (radius === [self cornerRadius])
return;
[self setValue:radius forThemeAttribute:@"corner-radius"];
}
// See discussion above.
// MARK: fillColor
- (CPColor)fillColor
{
return [self valueForThemeAttribute:@"background-color"];
@@ -368,17 +270,6 @@ CPBelowBottom = 6;
- (void)setFillColor:(CPColor)color
{
if (_boxType === CPBoxSeparator)
{
return;
}
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setFillColor: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setFillColor. Property is ignored.");
return;
}
if ([color isEqual:[self fillColor]])
return;
@@ -395,20 +286,21 @@ CPBelowBottom = 6;
if (aView === _contentView)
return;
var borderWidth = [self borderWidth],
contentMargin = [self valueForThemeAttribute:@"content-margin"];
[aView setFrame:CGRectInset([self bounds], contentMargin.width + borderWidth, contentMargin.height + borderWidth)];
[aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
// A nil contentView is allowed (tested in Cocoa 2013-02-22).
if (!aView)
[_contentView removeFromSuperview];
else if (_contentView)
[_boxView replaceSubview:_contentView with:aView];
[self replaceSubview:_contentView with:aView];
else
[_boxView addSubview:aView];
[self addSubview:aView];
_contentView = aView;
[self sizeToFit];
[self refreshDisplay];
}
- (CGSize)contentViewMargins
@@ -428,14 +320,9 @@ CPBelowBottom = 6;
{
var offset = [self _titleHeightOffset],
borderWidth = [self borderWidth],
contentMargin = [self valueForThemeAttribute:@"content-margin"],
contentAdjustment = [self valueForThemeAttribute:@"content-adjustment"],
minYCorrection = [self valueForThemeAttribute:(_titlePosition === CPNoTitle ? @"min-y-correction-no-title" : @"min-y-correction-title")];
contentMargin = [self valueForThemeAttribute:@"content-margin"];
[self setFrame:CGRectMake(aRect.origin.x - contentAdjustment.origin.x - contentMargin.width + borderWidth,
aRect.origin.y - contentAdjustment.origin.y - contentMargin.height + borderWidth - minYCorrection,
aRect.size.width + 2 * contentMargin.width - contentAdjustment.size.width,
aRect.size.height + 2 * contentMargin.height - contentAdjustment.size.height)];
[self setFrame:CGRectInset(aRect, -(contentMargin.width + borderWidth), -(contentMargin.height + offset[0] + borderWidth))];
}
- (void)setTitle:(CPString)aTitle
@@ -460,42 +347,14 @@ CPBelowBottom = 6;
- (CPFont)titleFont
{
if ([self hasThemeAttribute:@"title-font"])
return [self valueForThemeAttribute:@"title-font"];
else
return [_titleView font];
return [_titleView font];
}
- (void)setTitleFont:(CPFont)aFont
{
if ([aFont isEqual:[self titleFont]])
return;
if ([self hasThemeAttribute:@"title-font"])
[self setValue:aFont forThemeAttribute:@"title-font"];
[_titleView setFont:aFont];
}
- (CPColor)titleColor
{
if ([self hasThemeAttribute:@"title-color"])
return [self valueForThemeAttribute:@"title-color"];
else
return [_titleView textColor];
}
- (void)setTitleColor:(CPColor)aColor
{
if ([aColor isEqual:[self titleColor]])
return;
if ([self hasThemeAttribute:@"title-color"])
[self setValue:aColor forThemeAttribute:@"title-color"];
[_titleView setTextColor:aColor];
}
/*!
Return the text field used to display the receiver's title.
@@ -506,39 +365,25 @@ CPBelowBottom = 6;
return _titleView;
}
/*!
Return the rectangle in which the receivers title is drawn.
*/
- (CGRect)titleRect
{
return [_titleView frame];
}
- (void)_manageTitlePositioning
{
if ((_titlePosition == CPNoTitle) || _transparent)
if (_titlePosition == CPNoTitle)
{
[_titleView removeFromSuperview];
if (_boxType !== CPBoxSeparator)
[self sizeToFit];
[self refreshDisplay];
[self setNeedsDisplay:YES];
return;
}
[_titleView setStringValue:_title];
[_titleView sizeToFit];
var titleLeftOffset = [self valueForThemeAttribute:@"title-left-offset"],
titleTopOffset = [self valueForThemeAttribute:@"title-top-offset"];
[self addSubview:_titleView];
switch (_titlePosition)
{
case CPAtTop:
case CPAboveTop:
case CPBelowTop:
[_titleView setFrameOrigin:CGPointMake(titleLeftOffset, titleTopOffset)]; // FIXME: was 0.0
[_titleView setFrameOrigin:CGPointMake(5.0, 0.0)];
[_titleView setAutoresizingMask:CPViewNotSizable];
break;
@@ -546,51 +391,39 @@ CPBelowBottom = 6;
case CPAtBottom:
case CPBelowBottom:
var h = [_titleView frameSize].height;
[_titleView setFrameOrigin:CGPointMake(titleLeftOffset, [self frameSize].height - h - titleTopOffset)];
[_titleView setFrameOrigin:CGPointMake(5.0, [self frameSize].height - h)];
[_titleView setAutoresizingMask:CPViewMinYMargin];
break;
}
if (!_transparent)
[self addSubview:_titleView];
[self sizeToFit];
[self refreshDisplay];
[self setNeedsDisplay:YES];
}
- (void)sizeToFit
{
var offset = [self _titleHeightOffset],
size = [self frameSize];
var contentFrame = [_contentView frame],
offset = [self _titleHeightOffset],
contentMargin = [self valueForThemeAttribute:@"content-margin"];
[_boxView setFrame:CGRectMake(0, offset[1], size.width, size.height - offset[0])];
if (!_contentView)
if (!contentFrame)
return;
var boxSize = [_boxView frameSize],
contentMargin = [self valueForThemeAttribute:@"content-margin"],
contentAdjustment = [self valueForThemeAttribute:@"content-adjustment"],
borderWidth = [self valueForThemeAttribute:@"border-width"],
minYCorrection = [self valueForThemeAttribute:(_titlePosition === CPNoTitle ? @"min-y-correction-no-title" : @"min-y-correction-title")];
[_contentView setFrame:CGRectMake(contentAdjustment.origin.x + contentMargin.width - borderWidth,
contentAdjustment.origin.y + contentMargin.height - borderWidth + minYCorrection,
boxSize.width - 2 * contentMargin.width + contentAdjustment.size.width,
boxSize.height - 2 * contentMargin.height + contentAdjustment.size.height)];
[_contentView setFrameOrigin:CGPointMake(contentMargin.width, contentMargin.height + offset[1])];
}
- (CPArray)_titleHeightOffset
- (float)_titleHeightOffset
{
var titleTopOffset = [self valueForThemeAttribute:@"title-top-offset"];
if (_titlePosition == CPNoTitle)
return [0.0, 0.0];
switch (_titlePosition)
{
case CPAtTop:
return [[_titleView frameSize].height + titleTopOffset, [_titleView frameSize].height + titleTopOffset];
return [[_titleView frameSize].height, [_titleView frameSize].height];
case CPAtBottom:
return [[_titleView frameSize].height + titleTopOffset, 0.0];
return [[_titleView frameSize].height, 0.0];
default:
return [0.0, 0.0];
@@ -607,23 +440,20 @@ CPBelowBottom = 6;
- (void)drawRect:(CGRect)rect
{
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
return;
var bounds = [self bounds];
if (_boxType === CPBoxSeparator)
switch (_boxType)
{
// NSBox does not include a horizontal flag for the separator type. We have to determine
// the type of separator to draw by the width and height of the frame.
if (CGRectGetWidth(bounds) === 5.0)
return [self _drawVerticalSeparatorInRect:bounds];
else if (CGRectGetHeight(bounds) === 5.0)
return [self _drawHorizontalSeparatorInRect:bounds];
}
case CPBoxSeparator:
// NSBox does not include a horizontal flag for the separator type. We have to determine
// the type of separator to draw by the width and height of the frame.
if (CGRectGetWidth(bounds) === 5.0)
return [self _drawVerticalSeparatorInRect:bounds];
else if (CGRectGetHeight(bounds) === 5.0)
return [self _drawHorizontalSeparatorInRect:bounds];
if (_transparent)
return;
break;
}
if (_titlePosition == CPAtTop)
{
@@ -645,6 +475,9 @@ CPBelowBottom = 6;
switch (_borderType)
{
case CPBezelBorder:
[self _drawBezelBorderInRect:bounds];
break;
case CPGrooveBorder:
case CPLineBorder:
[self _drawLineBorderInRect:bounds];
@@ -761,83 +594,12 @@ CPBelowBottom = 6;
@end
// MARK: -
@implementation CPBox (CSSTheming)
- (void)layoutSubviews
{
if (![self isCSSBased] || (_boxType === CPBoxCustom))
return;
var bounds = [self bounds];
if (_boxType === CPBoxSeparator)
{
if (bounds.size.width === 5.0)
{
// Vertical separator
[_boxView setFrame:CGRectMake(2,0,1,bounds.size.height)];
}
else
{
// Horizontal separator
[_boxView setFrame:CGRectMake(0,2,bounds.size.width,1)];
}
[_boxView setBackgroundColor:[self currentValueForThemeAttribute:@"border-color"]];
return;
}
// All types of boxes (beside custom which is not covered here) always draw the same way, unless they are CPNoBorder.
if ((_borderType !== CPNoBorder) && !_transparent)
{
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"background-color"]];
return;
}
// No border or transparent
[_boxView setBackgroundColor:nil];
}
- (BOOL)isCSSBased
{
return [[self theme] isCSSBased];
}
- (void)refreshDisplay
{
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
[self setNeedsLayout:YES];
else
[self setNeedsDisplay:YES];
}
- (void)setAutoresizesSubviews:(BOOL)flag
{
// CPBox should always resize its subviews, like in Cocoa, whatever is the corresponding flag set.
// We have to keep the flag value as we could have to return it in -autoresizesSubview method.
_cachedAutoresizesSubviews = !!flag;
[super setAutoresizesSubviews:YES];
}
- (BOOL)autoresizesSubview
{
return _cachedAutoresizesSubviews;
}
@end
// MARK: -
var CPBoxTypeKey = @"CPBoxTypeKey",
CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
CPBoxTitleKey = @"CPBoxTitleKey",
CPBoxTitlePositionKey = @"CPBoxTitlePositionKey",
CPBoxTitleViewKey = @"CPBoxTitleViewKey",
CPBoxContentViewKey = @"CPBoxContentViewKey",
CPBoxBoxViewKey = @"CPBoxBoxViewKey";
CPBoxTitle = @"CPBoxTitle",
CPBoxTitlePosition = @"CPBoxTitlePosition",
CPBoxTitleView = @"CPBoxTitleView",
CPBoxContentView = @"CPBoxContentView";
@implementation CPBox (CPCoding)
@@ -850,56 +612,33 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
_boxType = [aCoder decodeIntForKey:CPBoxTypeKey];
_borderType = [aCoder decodeIntForKey:CPBoxBorderTypeKey];
_title = [aCoder decodeObjectForKey:CPBoxTitleKey];
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePositionKey];
_title = [aCoder decodeObjectForKey:CPBoxTitle];
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePosition];
_titleView = [aCoder decodeObjectForKey:CPBoxTitleView] || [CPTextField labelWithTitle:_title];
// Important : see comment on encodeWithCoder below
_boxView = [aCoder decodeObjectForKey:CPBoxBoxViewKey];
if (!_boxView)
if (_boxType != CPBoxSeparator)
{
// We're coming from nib2cib.
// FIXME: we have a problem with CIB decoding here.
// We should be able to simply add : _contentView = [self subviews][0]
// but first box subview seems to be malformed (badly decoded).
// For example, when deployed, this view doesn't have its _trackingAreas array initialized.
// As a (temporary) workaround, we encode/decode the _contentView property. We then transfer the subview hierarchy
// and replace the first (and only) box subview with this _contentView
_boxView = [[CPView alloc] initWithFrame:[self bounds]];
_titleView = [CPTextField labelWithTitle:_title];
_contentView = [aCoder decodeObjectForKey:CPBoxContentView] || [[CPView alloc] initWithFrame:[self bounds]];
var malformedContentView = [self subviews][0];
[_contentView setSubviews:[malformedContentView subviews]];
[self replaceSubview:malformedContentView with:_contentView];
}
else
{
// We're coming from elsewhere
_titleView = [aCoder decodeObjectForKey:CPBoxTitleViewKey];
_titlePosition = CPNoTitle;
}
_contentView = [aCoder decodeObjectForKey:CPBoxContentViewKey];
// FIXME: super-mega-hyper-trick : _contentView has a superview which is not normal !
// FIXME: (see encodeWithCoder to understand why this is not possible)
// FIXME: we fix this by hand. This is horrible so please find a structural solution !
if (_contentView)
_contentView._superview = nil;
[self setAutoresizesSubviews:YES];
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_boxView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_boxView setAutoresizesSubviews:YES];
[self setAutoresizesSubviews:YES];
if (_contentView)
[_boxView setSubviews:@[_contentView]];
[self addSubview:_boxView];
[self addSubview:_titleView];
if (_boxType === CPBoxSeparator)
_titlePosition = CPNoTitle;
[_titleView setFont:[self titleFont]];
[_titleView setTextColor:[self titleColor]];
[self _manageTitlePositioning];
[self refreshDisplay];
}
return self;
@@ -907,47 +646,14 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
- (void)encodeWithCoder:(CPCoder)aCoder
{
// We have to distinguish between 2 cases :
// - we come from nib2cib
// - we come from elsewhere
//
// When coming from nib2cib, we have no _boxView, _contentView, _titleView.
// We fix _contentView to be the first (and only) subview.
// They will have to be added on decoding.
//
// When coming from elsewhere, we remove _boxView (and thus _contentView) and _titleView
// from the view hierarchy as we'll already encode them via variables.
// They will be putted back during decoding. This way, we reduce the space and speed needed for coding.
var subviews = [self subviews];
if (!_boxView)
{
// We're coming from nib2cib.
_contentView = subviews[0];
[_contentView removeFromSuperview];
}
else
{
// We're coming from elsewhere.
[_boxView removeFromSuperview];
[_titleView removeFromSuperview];
}
[super encodeWithCoder:aCoder];
[self setSubviews:subviews];
[aCoder encodeInt:_boxType forKey:CPBoxTypeKey];
[aCoder encodeInt:_borderType forKey:CPBoxBorderTypeKey];
[aCoder encodeObject:_title forKey:CPBoxTitleKey];
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePositionKey];
[aCoder encodeConditionalObject:_contentView forKey:CPBoxContentViewKey];
[aCoder encodeConditionalObject:_titleView forKey:CPBoxTitleViewKey];
[aCoder encodeConditionalObject:_boxView forKey:CPBoxBoxViewKey];
[aCoder encodeObject:_title forKey:CPBoxTitle];
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePosition];
[aCoder encodeObject:_titleView forKey:CPBoxTitleView];
[aCoder encodeObject:_contentView forKey:CPBoxContentView];
}
@end
+20 -15
View File
@@ -42,6 +42,7 @@
- (CPDragOperation)browser:(CPBrowser)browser validateDrop:(id)info proposedRow:(CPInteger)row column:(CPInteger)column dropOperation:(CPTableViewDropOperation)dropOperation;
- (CPImage)browser:(CPBrowser)browser imageValueForItem:(id)anItem;
- (CPImage)browser:(CPBrowser)browser draggingImageForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
- (CPImage)browser:(CPBrowser)browser imageValueForItem:(id)item;
- (CPIndexSet)browser:(CPBrowser)browser selectionIndexesForProposedSelection:(CPIndexSet)proposedSelectionIndexes inColumn:(CPInteger)column;
- (CPInteger)browser:(CPBrowser)browser numberOfChildrenOfItem:(id)item;
- (CPView)browser:(CPBrowser)browser draggingViewForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
@@ -61,18 +62,19 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
CPBrowserDelegate_browser_shouldSelectRowIndexes_inColumn_ = 1 << 4,
CPBrowserDelegate_browser_writeRowsWithIndexes_inColumn_toPasteboard_ = 1 << 5,
CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_ = 1 << 6,
CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 7,
CPBrowserDelegate_browser_imageValueForItem_ = 1 << 8,
CPBrowserDelegate_browser_selectionIndexesForProposedSelection_inColumn_ = 1 << 9,
CPBrowserDelegate_browser_numberOfChildrenOfItem_ = 1 << 10,
CPBrowserDelegate_browser_draggingViewForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 11,
CPBrowserDelegate_browser_child_ofItem_ = 1 << 12,
CPBrowserDelegate_browser_objectValueForItem_ = 1 << 13,
CPBrowserDelegate_rootItemForBrowser_ = 1 << 14,
CPBrowserDelegate_browser_didChangeLastColumn_toColumn_ = 1 << 15,
CPBrowserDelegate_browser_didResizeColumn_ = 1 << 16,
CPBrowserDelegate_browserSelectionIsChanging_ = 1 << 17,
CPBrowserDelegate_browserSelectionDidChange_ = 1 << 18;
CPBrowserDelegate_browser_imageValueForItem_ = 1 << 7,
CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 8,
CPBrowserDelegate_browser_imageValueForItem_ = 1 << 9,
CPBrowserDelegate_browser_selectionIndexesForProposedSelection_inColumn_ = 1 << 10,
CPBrowserDelegate_browser_numberOfChildrenOfItem_ = 1 << 11,
CPBrowserDelegate_browser_draggingViewForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 12,
CPBrowserDelegate_browser_child_ofItem_ = 1 << 13,
CPBrowserDelegate_browser_objectValueForItem_ = 1 << 14,
CPBrowserDelegate_rootItemForBrowser_ = 1 << 15,
CPBrowserDelegate_browser_didChangeLastColumn_toColumn_ = 1 << 16,
CPBrowserDelegate_browser_didResizeColumn_ = 1 << 17,
CPBrowserDelegate_browserSelectionIsChanging_ = 1 << 18,
CPBrowserDelegate_browserSelectionDidChange_ = 1 << 19;
/*!
@ingroup appkit
@@ -153,7 +155,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]];
@@ -209,6 +211,9 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
if ([_delegate respondsToSelector:@selector(browser:validateDrop:proposedRow:column:dropOperation:)])
_implementedDelegateMethods |= CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_;
if ([_delegate respondsToSelector:@selector(browser:imageValueForItem:)])
_implementedDelegateMethods |= CPBrowserDelegate_browser_imageValueForItem_;
if ([_delegate respondsToSelector:@selector(browser:draggingImageForRowsWithIndexes:inColumn:withEvent:offset:)])
_implementedDelegateMethods |= CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_;
@@ -463,7 +468,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
- (id)itemAtRow:(CPInteger)row inColumn:(CPInteger)column
{
return [_tableDelegates[column] childAtIndex:row] || nil;
return [_tableDelegates[column] childAtIndex:row];
}
- (BOOL)isLeafItem:(id)item
@@ -473,7 +478,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
- (id)parentForItemsInColumn:(CPInteger)column
{
return [_tableDelegates[column] _item] || nil;
return [_tableDelegates[column] _item];
}
- (CPSet)selectedItems
+170 -256
View File
@@ -44,7 +44,6 @@ CPTexturedRoundedBezelStyle = 11; // Round Textured
CPRoundRectBezelStyle = 12; // Round Rect
CPRecessedBezelStyle = 13; // Recessed
CPRoundedDisclosureBezelStyle = 14; // Disclosure
CPInlineBezelStyle = 15; // Inline
CPHUDBezelStyle = -1;
@@ -73,33 +72,14 @@ CPPushInCellMask = CPPushInButtonMask;
CPChangeGrayCellMask = CPGrayButtonMask;
CPChangeBackgroundCellMask = CPBackgroundButtonMask;
CPButtonStateMixed = CPThemeState("mixed");
CPButtonStateBezelStyleRounded = CPThemeState("rounded"); // IB style : Push
CPButtonStateBezelStyleShadowlessSquare = CPThemeState("square"); // IB style : Square
CPButtonStateBezelStyleSmallSquare = CPThemeState("gradient"); // IB style : Gradient
CPButtonStateBezelStyleTexturedRounded = CPThemeState("textured-rounded"); // IB style : Textured rounded
CPButtonStateBezelStyleRoundRect = CPThemeState("roundRect"); // IB style : Round rect
CPButtonStateBezelStyleRecessed = CPThemeState("recessed"); // IB style : Recessed
CPButtonStateBezelStyleInline = CPThemeState("inline"); // IB style : Inline
CPButtonStateBezelStyleRegularSquare = CPThemeState("bevel"); // IB style : Bevel
CPButtonStateBezelStyleTextured = CPThemeState("textured"); // IB style : Textured
CPButtonStateBezelStyleDisclosure = CPThemeState("disclosure"); // IB style : Disclosure triangle
CPButtonStateBezelStyleRoundedDisclosure = CPThemeState("rounded-disclosure"); // IB style : Rounded disclosure
CPButtonStateMixed = CPThemeState("mixed");
CPButtonStateBezelStyleRounded = CPThemeState("rounded");
CPButtonStateBezelStyleRoundRect = CPThemeState("roundRect");
// add all future correspondance between bezel styles and theme state here.
var CPButtonBezelStyleStateMap = @{
CPRoundedBezelStyle: CPButtonStateBezelStyleRounded,
CPShadowlessSquareBezelStyle: CPButtonStateBezelStyleShadowlessSquare,
CPSmallSquareBezelStyle: CPButtonStateBezelStyleSmallSquare,
CPTexturedRoundedBezelStyle: CPButtonStateBezelStyleTexturedRounded,
CPRoundRectBezelStyle: CPButtonStateBezelStyleRoundRect,
CPRecessedBezelStyle: CPButtonStateBezelStyleRecessed,
CPInlineBezelStyle: CPButtonStateBezelStyleInline,
CPRegularSquareBezelStyle: CPButtonStateBezelStyleRegularSquare,
CPTexturedSquareBezelStyle: CPButtonStateBezelStyleTextured,
CPDisclosureBezelStyle: CPButtonStateBezelStyleDisclosure,
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure,
CPHUDBezelStyle: CPThemeStateHUD
CPRoundedBezelStyle: CPButtonStateBezelStyleRounded,
CPRoundRectBezelStyle: CPButtonStateBezelStyleRoundRect,
};
/// @cond IGNORE
@@ -128,7 +108,6 @@ CPButtonImageOffset = 3.0;
// NS-style Display Properties
CPBezelStyle _bezelStyle;
ThemeState _bezelState;
CPString _keyEquivalent;
unsigned _keyEquivalentModifierMask;
@@ -138,7 +117,7 @@ CPButtonImageOffset = 3.0;
float _periodicDelay;
float _periodicInterval;
BOOL _isHighlighted;
BOOL _isTracking;
}
+ (Class)_binderClassForBinding:(CPString)aBinding
@@ -175,17 +154,9 @@ CPButtonImageOffset = 3.0;
return @{
@"image": [CPNull null],
@"image-offset": 0.0,
@"image-vertical-offset": 0.0,
@"bezel-inset": CGInsetMakeZero(),
@"content-inset": CGInsetMakeZero(),
@"bezel-color": [CPNull null],
@"image-position": CPImageLeft,
@"vertical-alignment": CPCenterVerticalTextAlignment,
@"alignment": CPCenterTextAlignment,
@"image-scaling": CPImageScaleNone,
@"invert-image": NO,
@"invert-image-on-push": NO,
@"image-color": [CPNull null] // If null, image color follows text color
};
}
@@ -200,6 +171,12 @@ CPButtonImageOffset = 3.0;
if (self)
{
// Should we instead override the defaults?
[self setValue:CPCenterTextAlignment forThemeAttribute:@"alignment"];
[self setValue:CPCenterVerticalTextAlignment forThemeAttribute:@"vertical-alignment"];
[self setValue:CPImageLeft forThemeAttribute:@"image-position"];
[self setValue:CPImageScaleNone forThemeAttribute:@"image-scaling"];
[self setBezelStyle:CPRoundRectBezelStyle];
[self setBordered:YES];
@@ -221,8 +198,8 @@ CPButtonImageOffset = 3.0;
[self setButtonType:CPMomentaryPushInButton];
}
// MARK: -
// MARK: Control Size
#pragma mark -
#pragma mark Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
@@ -233,7 +210,7 @@ CPButtonImageOffset = 3.0;
}
// MARK: -
#pragma mark -
// Setting the state
/*!
@@ -281,6 +258,30 @@ CPButtonImageOffset = 3.0;
anObjectValue = CPOnState;
[super setObjectValue:anObjectValue];
switch ([self objectValue])
{
case CPMixedState:
[self unsetThemeState:CPThemeStateSelected];
[self setThemeState:CPButtonStateMixed];
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self setThemeState:CPThemeStateHighlighted];
else
[self unsetThemeState:CPThemeStateHighlighted];
break;
case CPOnState:
[self unsetThemeState:CPButtonStateMixed];
[self setThemeState:CPThemeStateSelected];
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self setThemeState:CPThemeStateHighlighted];
else
[self unsetThemeState:CPThemeStateHighlighted];
break;
case CPOffState:
[self unsetThemeStates:[CPThemeStateSelected, CPButtonStateMixed, CPThemeStateHighlighted]];
}
}
/*!
@@ -377,21 +378,12 @@ CPButtonImageOffset = 3.0;
- (void)setImage:(CPImage)anImage
{
// This is needed when compiling themes
if (!_bezelState)
_bezelState = CPThemeStateNormal;
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState];
// if we omit this, images will disappear as soon as the button becomes disabled
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateDisabled)];
[self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateNormal];
}
- (CPImage)image
{
if (!_bezelState)
_bezelState = CPThemeStateNormal;
return [self valueForThemeAttribute:@"image" inState:_bezelState];
return [self valueForThemeAttribute:@"image" inState:CPThemeStateNormal];
}
/*!
@@ -400,8 +392,7 @@ CPButtonImageOffset = 3.0;
*/
- (void)setAlternateImage:(CPImage)anImage
{
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHighlighted)];
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateSelected)];
[self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
}
/*!
@@ -409,17 +400,7 @@ CPButtonImageOffset = 3.0;
*/
- (CPImage)alternateImage
{
return [self valueForThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateSelected)];
}
- (void)setHoveredImage:(CPImage)anImage
{
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHovered)];
}
- (CPImage)hoveredImage
{
return [self valueForThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHovered)];
return [self valueForThemeAttribute:@"image" inState:CPThemeStateHighlighted];
}
- (void)setImageOffset:(float)theImageOffset
@@ -442,6 +423,11 @@ CPButtonImageOffset = 3.0;
_showsStateBy = aMask;
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask) && [self state] != CPOffState)
[self setThemeState:CPThemeStateHighlighted];
else
[self unsetThemeState:CPThemeStateHighlighted];
[self setNeedsDisplay:YES];
[self setNeedsLayout];
}
@@ -458,8 +444,11 @@ CPButtonImageOffset = 3.0;
_highlightsBy = aMask;
[self setNeedsDisplay:YES];
[self setNeedsLayout];
if ([self hasThemeState:CPThemeStateHighlighted])
{
[self setNeedsDisplay:YES];
[self setNeedsLayout];
}
}
- (CPInteger)highlightsBy
@@ -544,20 +533,9 @@ CPButtonImageOffset = 3.0;
_periodicInterval = anInterval;
}
- (void)highlight:(BOOL)shouldHighlight
{
if (_isHighlighted == shouldHighlight)
return;
_isHighlighted = shouldHighlight;
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
- (void)mouseDown:(CPEvent)anEvent
{
if ([self isEnabled] && [self isContinuous])
if ([self isContinuous])
{
_continuousDelayTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicDelay callback: function()
{
@@ -577,12 +555,46 @@ CPButtonImageOffset = 3.0;
[_target performSelector:_action withObject:self];
}
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
_isTracking = YES;
var startedTracking = [super startTrackingAt:aPoint];
if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
{
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self highlight:[self state] == CPOffState];
else
[self highlight:YES];
}
else
{
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self highlight:[self state] != CPOffState];
else
[self highlight:NO];
}
return startedTracking;
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
{
_isTracking = NO;
if (mouseIsUp && CGRectContainsPoint([self bounds], aPoint))
[self setNextState];
else
{
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self highlight:[self state] != CPOffState];
else
[self highlight:NO];
}
[self highlight:NO];
[self setNeedsLayout];
[self setNeedsDisplay:YES];
[self invalidateTimers];
}
@@ -603,7 +615,7 @@ CPButtonImageOffset = 3.0;
- (CGRect)contentRectForBounds:(CGRect)bounds
{
var contentInset = [self valueForThemeAttribute:@"content-inset" inState:[self _contentVisualState]];
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
return CGRectInsetByInset(bounds, contentInset);
}
@@ -630,7 +642,7 @@ CPButtonImageOffset = 3.0;
size = [contentView frameSize];
}
else
size = [([self title] || " ") sizeWithFont:[self font]];
size = [([self title] || " ") sizeWithFont:[self currentValueForThemeAttribute:@"font"]];
var contentInset = [self currentValueForThemeAttribute:@"content-inset"],
minSize = [self currentValueForThemeAttribute:@"min-size"],
@@ -686,169 +698,86 @@ CPButtonImageOffset = 3.0;
return [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
}
- (CPThemeState)_backgroundVisualState
{
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state],
buttonIsOn = (currentState !== CPOffState);
if (_isHighlighted && (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask)))
visualState = visualState.and(CPThemeStateHighlighted);
else
visualState = visualState.without(CPThemeStateHighlighted);
if (buttonIsOn && (_showsStateBy & (CPPushInCellMask | CPChangeGrayCellMask)))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
else
visualState = visualState.without(CPThemeStateSelected);
return visualState;
}
// 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,
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))
visualState = visualState.and(CPThemeStateHighlighted);
else
visualState = visualState.without(CPThemeStateHighlighted);
if (buttonIsOn && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
else
visualState = visualState.without(CPThemeStateSelected);
return visualState;
}
- (CPThemeState)_imageVisualState
{
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state],
buttonIsOn = (currentState !== CPOffState);
// Remove highlighted & selected theme states
visualState = visualState.without(CPThemeStateHighlighted);
visualState = visualState.without(CPThemeStateSelected);
// Note : We have to deal with special case where button is ON, highlightsBy and showsStateBy use content, and button is pushed
// BUT this should not be used for disclosure buttons !
if (_isHighlighted && buttonIsOn && (_highlightsBy & CPContentsCellMask) && (_showsStateBy & CPContentsCellMask) && (_bezelStyle !== CPDisclosureBezelStyle))
return visualState;
if (_isHighlighted && ((_highlightsBy & CPContentsCellMask) || (_highlightsBy & CPChangeGrayCellMask)))
visualState = visualState.and(CPThemeStateHighlighted);
if (buttonIsOn && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
}
- (CPString)_currentTitle
{
var buttonIsOn = ([self state] !== CPOffState);
// Note : We have to deal with special case where button is ON, highlightsBy and showsStateBy use content, and button is pushed
if (_isHighlighted && buttonIsOn && (_highlightsBy & CPContentsCellMask) && (_showsStateBy & CPContentsCellMask))
return _title;
else if (_alternateTitle && ((_isHighlighted && (_highlightsBy & CPContentsCellMask)) || (buttonIsOn && (_showsStateBy & CPContentsCellMask))))
return _alternateTitle;
else
return _title;
}
- (CPImage)_currentImage
{
var visualState = [self _imageVisualState],
currentImage = [self valueForThemeAttribute:@"image" inState:visualState],
imageColor = [self valueForThemeAttribute:@"image-color" inState:visualState],
buttonIsOn = ([self state] !== CPOffState);
if ([currentImage isMaterialIconImage])
{
if (([self valueForThemeAttribute:@"invert-image" inState:visualState] || ([self valueForThemeAttribute:@"invert-image-on-push" inState:visualState] && (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn)))))
currentImage = [currentImage invertedImage];
else if (imageColor && [imageColor isKindOfClass:CPColor])
// In some buttons, image color doesn't follow text color !
currentImage = [currentImage imageVersionWithColor:imageColor];
else
// By default, image color follows text color
currentImage = [currentImage imageVersionWithColor:[self valueForThemeAttribute:@"text-color" inState:[self _contentVisualState]]];
}
return currentImage;
}
- (void)layoutSubviews
{
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"],
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
contentView = [self layoutEphemeralSubviewNamed:@"content-view"
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"],
relativeToEphemeralSubviewNamed:@"bezel-view"];
image = [self _currentImage],
contentVisualState = [self _contentVisualState];
if (contentView)
{
var title = nil,
image = nil;
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color" inState:[self _backgroundVisualState]]];
[contentView setText:[self _currentTitle]];
[contentView setImage:image];
if (_isTracking)
{
if (_highlightsBy & CPContentsCellMask)
{
if (_showsStateBy & CPContentsCellMask)
{
title = ([self state] == CPOffState && _alternateTitle) ? _alternateTitle : _title;
image = ([self state] == CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
}
else
{
title = [self alternateTitle];
image = [self alternateImage];
}
}
else if (_showsStateBy & CPContentsCellMask)
{
title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
}
else
{
title = _title;
image = [self image];
}
}
else
{
if (_showsStateBy & CPContentsCellMask)
{
title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
}
else
{
title = _title;
image = [self image];
}
}
[contentView setImageOffset:[self valueForThemeAttribute:@"image-offset" inState:contentVisualState]];
[contentView setImageVerticalOffset:[self valueForThemeAttribute:@"image-vertical-offset" inState:contentVisualState]];
[contentView setText:title];
[contentView setImage:image];
[contentView setImageOffset:[self currentValueForThemeAttribute:@"image-offset"]];
[contentView setFont:[self font]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:contentVisualState]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:contentVisualState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:contentVisualState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:contentVisualState]];
[contentView _setUsesSingleLineMode:YES];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:contentVisualState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:contentVisualState]];
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position"]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling"]];
// We don't automatically dim material icon images as the color is driven by the theme
[contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled && ![image isMaterialIconImage]];
[contentView setFont:[self currentValueForThemeAttribute:@"font"]];
[contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
[contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
[contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
[contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
[contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
[contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
[contentView setImagePosition:[self currentValueForThemeAttribute:@"image-position"]];
[contentView setImageScaling:[self currentValueForThemeAttribute:@"image-scaling"]];
[contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled];
}
}
- (void)setBordered:(BOOL)shouldBeBordered
{
if (shouldBeBordered)
{
[self setThemeState:CPThemeStateBordered];
if (_bezelState)
_bezelState = _bezelState.and(CPThemeStateBordered);
else
_bezelState = CPThemeStateBordered;
}
else
{
[self unsetThemeState:CPThemeStateBordered];
if (_bezelState)
_bezelState = _bezelState.without(CPThemeStateBordered);
else
_bezelState = CPThemeStateNormal;
}
}
- (BOOL)isBordered
@@ -878,7 +807,7 @@ CPButtonImageOffset = 3.0;
{
var selfWindow = [self window];
if (selfWindow === aWindow || aWindow == nil)
if (selfWindow === aWindow || aWindow === nil)
return;
if ([selfWindow defaultButton] === self)
@@ -944,7 +873,17 @@ CPButtonImageOffset = 3.0;
[self setState:[self nextState]];
[self highlight:YES];
var shouldHighlight = NO;
if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
{
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
shouldHighlight = [self state] == CPOffState;
else
shouldHighlight = YES;
}
[self highlight:shouldHighlight];
try
{
@@ -956,7 +895,8 @@ CPButtonImageOffset = 3.0;
}
finally
{
[CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(unhighlightButtonTimerDidFinish:) userInfo:nil repeats:NO];
if (shouldHighlight)
[CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(unhighlightButtonTimerDidFinish:) userInfo:nil repeats:NO];
}
}
@@ -979,22 +919,6 @@ CPButtonImageOffset = 3.0;
[self setThemeState:newState];
_bezelStyle = aBezelStyle;
if (_bezelState && newState)
{
if (currentState)
_bezelState =_bezelState.without(currentState);
_bezelState = _bezelState.and(newState);
}
else
_bezelState = newState || CPThemeStateNormal;
// For disclosure triangle and rounded, we have to move away from
// what Xcode tells us as we implement visual behavior with images (so content)
// and not background
if ((_bezelStyle === CPDisclosureBezelStyle) || (_bezelStyle === CPRoundedDisclosureBezelStyle))
[self setShowsStateBy:CPContentsCellMask];
}
- (unsigned)bezelStyle
@@ -1018,8 +942,7 @@ var CPButtonImageKey = @"CPButtonImageKey",
CPButtonPeriodicDelayKey = @"CPButtonPeriodicDelayKey",
CPButtonPeriodicIntervalKey = @"CPButtonPeriodicIntervalKey",
CPButtonHighlightsByKey = @"CPButtonHighlightsByKey",
CPButtonShowsStateByKey = @"CPButtonShowsStateByKey",
CPButtonBezelStyleKey = @"CPButtonBezelStyleKey";
CPButtonShowsStateByKey = @"CPButtonShowsStateByKey";
@implementation CPButton (CPCoding)
@@ -1068,12 +991,6 @@ var CPButtonImageKey = @"CPButtonImageKey",
_keyEquivalentModifierMask = [aCoder decodeIntForKey:CPButtonKeyEquivalentMaskKey];
if ([aCoder containsValueForKey:CPButtonIsBorderedKey])
[self setBordered:[aCoder decodeBoolForKey:CPButtonIsBorderedKey]];
if ([aCoder containsValueForKey:CPButtonBezelStyleKey])
[self setBezelStyle:[aCoder decodeIntForKey:CPButtonBezelStyleKey]];
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
@@ -1108,9 +1025,6 @@ var CPButtonImageKey = @"CPButtonImageKey",
[aCoder encodeObject:_periodicDelay forKey:CPButtonPeriodicDelayKey];
[aCoder encodeObject:_periodicInterval forKey:CPButtonPeriodicIntervalKey];
[aCoder encodeBool:[self isBordered] forKey:CPButtonIsBorderedKey];
[aCoder encodeInt: [self bezelStyle] forKey:CPButtonBezelStyleKey];
}
@end
+149 -1725
View File
File diff suppressed because it is too large Load Diff
+19 -49
View File
@@ -94,9 +94,26 @@ CPCheckBoxImageOffset = 4.0;
[self takeStateFromKeyPath:aKeyPath ofObjects:objects];
}
- (CPImage)image
{
return [self currentValueForThemeAttribute:@"image"];
}
// MARK: -
// MARK: Override methods from CPButton
- (CPImage)alternateImage
{
return [self currentValueForThemeAttribute:@"image"];
}
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
var startedTracking = [super startTrackingAt:aPoint];
[self highlight:YES];
return startedTracking;
}
#pragma mark -
#pragma mark Override methods from CPButton
- (CGSize)_minimumFrameSize
{
@@ -118,36 +135,6 @@ CPCheckBoxImageOffset = 4.0;
return size;
}
- (CPThemeState)_contentVisualState
{
// Note : Behavior differs from CPButton as title doesn't follow the highlightsBy content flag
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state];
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
}
- (CPThemeState)_imageVisualState
{
// Note : Behavior differs from CPButton as we don't force "not selected" theme state
// when button is highglighted and selected
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state];
if (_isHighlighted && (_highlightsBy & CPContentsCellMask))
visualState = visualState.and(CPThemeStateHighlighted);
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
}
@end
@implementation _CPCheckBoxValueBinder : CPBinder
@@ -176,20 +163,3 @@ CPCheckBoxImageOffset = 4.0;
}
@end
// MARK: -
@implementation CPCheckBox (TableDataView)
// We overide here _CPObject+Theme setValue:forThemeAttribute as CPCheckBox can be used as tableView data view
// So, when outside a table data view, setValue:forThemeAttribute should store the value with the CPThemeStateNormal (default behavior)
// When inside a table data view, it should store the value with the CPThemeStateTableDataView. If not, the value won't be used if the
// theme defined a value for this attribute for state CPThemeStateTableDataView
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
{
[super setValue:aValue forThemeAttribute:aName];
[super setValue:aValue forThemeAttribute:aName inState:CPThemeStateTableDataView];
}
@end
+7 -14
View File
@@ -202,8 +202,8 @@ var HORIZONTAL_MARGIN = 2;
// MARK: -
// MARK: Delegate
#pragma mark -
#pragma mark Delegate
/*!
Set the delegate of the receiver
@@ -600,12 +600,9 @@ var HORIZONTAL_MARGIN = 2;
if (_maxNumberOfRows > 0)
numberOfRows = MIN(numberOfRows, _maxNumberOfRows);
// calculate the required height: (sum of item heights) + (sum of margins between items).
var requiredHeight = (numberOfRows * _minItemSize.height) + (MAX(0, numberOfRows - 1) * _verticalMargin);
height = MAX(height, requiredHeight);
height = MAX(height, numberOfRows * (_minItemSize.height + _verticalMargin));
// calculate individual item height based on the total available height.
var itemSizeHeight = (numberOfRows > 0) ? FLOOR((height - (MAX(0, numberOfRows - 1) * _verticalMargin)) / numberOfRows) : 0;
var itemSizeHeight = FLOOR(height / numberOfRows) - _verticalMargin;
if (maxItemSizeHeight > 0)
itemSizeHeight = MIN(itemSizeHeight, maxItemSizeHeight);
@@ -624,7 +621,7 @@ var HORIZONTAL_MARGIN = 2;
_horizontalMargin = _uniformSubviewsResizing ? FLOOR((aFrameSize.width - numberOfColumns * anItemSize.width) / (numberOfColumns + 1)) : HORIZONTAL_MARGIN;
var x = _horizontalMargin,
y = -anItemSize.height;
y = -anItemSize.height;
[displayItems enumerateObjectsUsingBlock:function(item, idx, stop)
{
@@ -639,11 +636,7 @@ var HORIZONTAL_MARGIN = 2;
if (idx % numberOfColumns == 0)
{
x = _horizontalMargin;
// For the first row, don't add a margin. For all subsequent rows, add the margin.
if (idx === 0)
y += anItemSize.height;
else
y += _verticalMargin + anItemSize.height;
y += _verticalMargin + anItemSize.height;
}
[view setFrameOrigin:CGPointMake(x, y)];
@@ -746,7 +739,7 @@ var HORIZONTAL_MARGIN = 2;
*/
- (void)setMinItemSize:(CGSize)aSize
{
if (aSize == nil)
if (aSize === nil || aSize === undefined)
[CPException raise:CPInvalidArgumentException reason:"Invalid value provided for minimum size"];
if (CGSizeEqualToSize(_minItemSize, aSize))
+12 -19
View File
@@ -87,10 +87,10 @@ var cachedBlackColor,
CPImage _patternImage;
CPString _cssString;
}
@global document
// MARK: -
// MARK: Theming
#pragma mark -
#pragma mark Theming
+ (CPString)defaultThemeClass
{
@@ -109,8 +109,8 @@ var cachedBlackColor,
}
// MARK: -
// MARK: Static methods
#pragma mark -
#pragma mark Static methods
/*!
Creates a color in the RGB colorspace, with an alpha value.
@@ -267,7 +267,7 @@ var cachedBlackColor,
@return an initialized RGB color
*/
+ (CPColor)colorWithHexString:(CPString)hex
+ (CPColor)colorWithHexString:(string)hex
{
var rgba = hexToRGB(hex);
return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
@@ -561,11 +561,7 @@ var cachedBlackColor,
- (void)_initCSSStringFromComponents
{
// Fix to avoid a problem when compiling a theme (missing the alpha component as theme compiler doesn't have CSS rgba capability)
var hasAlpha = YES;
#if PLATFORM(DOM)
hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
#endif
var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
_cssString = (hasAlpha ? "rgba(" : "rgb(") +
parseInt(_components[0] * 255.0) + ", " +
@@ -804,9 +800,6 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
var description = [super description],
patternImage = [self patternImage];
if ([self isCSSBased])
return description + "\n" + [self cssDictionary]+ "\nBefore:\n" + [self cssBeforeDictionary] + "\nAfter:\n" + [self cssAfterDictionary];
if (!patternImage)
return description + " " + [self cssString];
@@ -884,8 +877,8 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
@end
// MARK: -
// MARK: CSS Theming
#pragma mark -
#pragma mark CSS Theming
// The code below adds support for CSS theming with 100% compatibility with current theming system.
// The idea is to extend CPColor (and CPImage) with CSS components and adapt low level UI components to
@@ -940,7 +933,7 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
// You can use -(BOOL)isCSSBased to determine how to cope with it in your code.
// -(BOOL)hasCSSDictionary, -(BOOL)hasCSSBeforeDictionary and -(BOOL)hasCSSAfterDictionary are convience methods you can use.
//
// Remark : +(void)restorePreviousCSSState and -(DOMElement)applyCSSColorForView are meant to be used by low level UI widgets (like CPView) to implement
// Remark : -(void)restorePreviousCSSState and -(DOMElement)applyCSSColorForView are meant to be used by low level UI widgets (like CPView) to implement
// CSS theme support.
@implementation CPColor (CSSTheming)
@@ -999,7 +992,7 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
return ([_cssAfterDictionary count] > 0);
}
+ (void)restorePreviousCSSState:(CPArrayRef)aPreviousStateRef forDOMElement:(DOMElement)aDOMElement
- (void)restorePreviousCSSState:(CPArrayRef)aPreviousStateRef forDOMElement:(DOMElement)aDOMElement
{
#if PLATFORM(DOM)
var aPreviousState = @deref(aPreviousStateRef);
@@ -1091,7 +1084,7 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
@end
// MARK: -
#pragma mark -
/// @cond IGNORE
var CPColorComponentsKey = @"CPColorComponentsKey",
+33 -81
View File
@@ -51,7 +51,6 @@ CPColorPickerViewWidth = 265;
CPColorPickerViewHeight = 370;
CPColorPanelColorDidChangeNotification = @"CPColorPanelColorDidChangeNotification";
CPColorDragType = CPColorPboardType;
var PREVIEW_HEIGHT = 20.0,
TOOLBAR_HEIGHT = 32.0,
@@ -130,6 +129,8 @@ var SharedColorPanel = nil,
if (self)
{
//[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
[self setTitle:@"Color Panel"];
[self setLevel:CPFloatingWindowLevel];
@@ -148,37 +149,20 @@ var SharedColorPanel = nil,
*/
- (void)setColor:(CPColor)aColor
{
if ([_color isEqual:aColor])
return;
_color = aColor;
[_previewView setBackgroundColor:_color];
// Check if the color change originated from user interaction inside the panel itself.
// We only broadcast `changeColor:` if the user picked a color via the panel's UI.
// If an external CPColorWell called `setColor:` programmatically, broadcasting it
// back down the responder chain would incorrectly change the previous First Responder.
var currentEvent = [CPApp currentEvent],
isFromPanel = currentEvent && ([currentEvent window] === self);
[CPApp sendAction:@selector(changeColor:) to:nil from:self];
if (isFromPanel)
{
// Push color via Responder Chain (targets First Responder, i.e., the active CPColorWell)
[CPApp sendAction:@selector(changeColor:) to:nil from:self];
if (_target && _action)
[CPApp sendAction:_action to:_target from:self];
}
if (_target && _action)
[CPApp sendAction:_action to:_target from:self];
[[CPNotificationCenter defaultCenter]
postNotificationName:CPColorPanelColorDidChangeNotification
object:self];
if (_activePicker)
[_activePicker setColor:_color];
if (_opacitySlider)
[_opacitySlider setFloatValue:[_color alphaComponent]];
[_activePicker setColor:_color];
[_opacitySlider setFloatValue:[_color alphaComponent]];
}
/*!
@@ -190,7 +174,7 @@ var SharedColorPanel = nil,
{
[self setColor:aColor];
if (bool && _activePicker)
if (bool)
[_activePicker setColor:_color];
}
@@ -348,7 +332,7 @@ var SharedColorPanel = nil,
buttonForLater = button;
}
// Preview
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var previewBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10, CGRectGetWidth(bounds) - 86, PREVIEW_HEIGHT)];
_previewView = [[_CPColorPanelPreview alloc] initWithFrame:CGRectInset([previewBox bounds], 2.0, 2.0)];
@@ -366,7 +350,7 @@ var SharedColorPanel = nil,
[_previewLabel setTextColor:[CPColor blackColor]];
[_previewLabel setAlignment:CPRightTextAlignment];
// Swatches
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var swatchBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5, CGRectGetWidth(bounds) - 86, SWATCH_HEIGHT + 2.0)];
[swatchBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
@@ -407,6 +391,8 @@ var SharedColorPanel = nil,
[contentView addSubview:opacityLabel];
[contentView addSubview:_opacitySlider];
_target = nil;
_action = nil;
_activePicker = nil;
[_previewView setBackgroundColor:_color];
@@ -426,6 +412,8 @@ var SharedColorPanel = nil,
@end
CPColorDragType = "CPColorDragType";
var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
/* @ignore */
@@ -435,14 +423,12 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
CPColor _dragColor;
CPColorPanel _colorPanel;
CPCookie _swatchCookie;
CGPoint _mouseDownPoint;
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
_mouseDownPoint = CGPointMake(0, 0);
[self setBackgroundColor:[CPColor grayColor]];
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
@@ -456,6 +442,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
for (var i = 0; i < 50; i++)
{
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var view = [[CPView alloc] initWithFrame:CGRectMake(13 * i + 1, 1, 12, 12)],
fillView = [[CPView alloc] initWithFrame:CGRectInset([view bounds], 1.0, 1.0)];
@@ -496,7 +483,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
];
}
var cookieValue = JSON.parse(cookieValue);
var cookieValue = eval(cookieValue);
return [cookieValue arrayByApplyingBlock:function(value)
{
@@ -507,6 +494,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
- (CPArray)saveColorList
{
var result = [];
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
for (var i = 0; i < _swatches.length; i++)
result.push([[[_swatches[i] subviews][0] backgroundColor] hexString]);
@@ -533,44 +521,39 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
- (void)setColor:(CPColor)aColor atIndex:(int)index
{
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
[[_swatches[index] subviews][0] setBackgroundColor:aColor];
[self saveColorList];
}
- (void)mouseDown:(CPEvent)anEvent
{
_mouseDownPoint = [anEvent locationInWindow];
}
- (void)mouseUp:(CPEvent)anEvent
{
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil],
bounds = [self bounds];
if (!CGRectContainsPoint(bounds, point) || point.x > [self bounds].size.width - 1 || point.x < 1)
return;
return NO;
[_colorPanel setColor:[self colorAtIndex:FLOOR(point.x / 13)] updatePicker:YES];
}
- (void)mouseDragged:(CPEvent)anEvent
{
var windowPoint = [anEvent locationInWindow];
// Prevent accidental drags from rapid clicking causing small micro-movements
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
var point = [self convertPoint:windowPoint fromView:nil];
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
if (point.x > [self bounds].size.width - 1 || point.x < 1)
return;
return NO;
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
var swatch = _swatches[FLOOR(point.x / 13)];
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
_dragColor = [[swatch subviews][0] backgroundColor];
var bounds = CGRectMakeCopy([swatch bounds]);
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
@@ -579,15 +562,11 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:CPColorDragType];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:pasteboard
pasteboard:nil
source:self
slideBack:YES];
}
@@ -598,7 +577,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:aType];
}
- (BOOL)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
- (void)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
{
var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
pasteboard = [aSender draggingPasteboard],
@@ -608,13 +587,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return NO;
[self setColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] atIndex:FLOOR(location.x / 13)];
return YES;
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
@end
@@ -623,13 +595,11 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
@implementation _CPColorPanelPreview : CPView
{
CPColorPanel _colorPanel;
CGPoint _mouseDownPoint;
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
_mouseDownPoint = CGPointMake(0, 0);
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
@@ -646,7 +616,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return _colorPanel;
}
- (BOOL)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
- (void)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
{
var pasteboard = [aSender draggingPasteboard];
@@ -655,8 +625,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
var color = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]];
[_colorPanel setColor:color updatePicker:YES];
return YES;
}
- (BOOL)isOpaque
@@ -664,22 +632,15 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return YES;
}
- (void)mouseDown:(CPEvent)anEvent
{
_mouseDownPoint = [anEvent locationInWindow];
}
- (void)mouseDragged:(CPEvent)anEvent
{
var windowPoint = [anEvent locationInWindow];
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
var point = [self convertPoint:windowPoint fromView:nil];
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPColorDragType] owner:self];
var bounds = CGRectMake(0, 0, 15, 15);
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
@@ -688,15 +649,11 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:CPColorDragType];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:pasteboard
pasteboard:nil
source:self
slideBack:YES];
}
@@ -707,11 +664,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:aType];
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
@end
@import "CPColorPicker.j"
+136 -200
View File
@@ -25,7 +25,7 @@
@import "CPView.j"
@import "CPColor.j"
@import "CPColorPanel.j"
@import "CPPasteboard.j"
var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiveNotification";
@@ -39,10 +39,10 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
*/
@implementation CPColorWell : CPControl
{
BOOL _active;
BOOL _bordered;
CPColor _color;
BOOL _isChangingColorFromPanel; // Guard flag to prevent recursion
CGPoint _mouseDownPoint;
}
+ (Class)_binderClassForBinding:(CPString)aBinding
@@ -77,54 +77,52 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[theBinding reverseSetValueFor:@"color"];
}
- (BOOL)isFirstResponder
{
return [[self window] firstResponder] === self;
}
- (BOOL)acceptsFirstResponder
{
return [self isEnabled];
}
- (void)activate:(BOOL)shouldBeExclusive
{
[[self window] makeFirstResponder:self];
[[CPColorPanel sharedColorPanel] orderFront:self];
}
- (BOOL)isActive
{
return [self isFirstResponder] && [self isEnabled];
}
/*!
Deactivates the color well.
*/
- (void)deactivate
{
if ([self isFirstResponder])
[[self window] makeFirstResponder:nil];
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
_active = NO;
_color = [CPColor whiteColor];
_mouseDownPoint = CGPointMakeZero();
[self setBordered:YES];
[self registerForDraggedTypes:[CPArray arrayWithObject:CPColorPboardType]];
}
return self;
}
// MARK: -
// MARK: Draw
- (void)_registerNotifications
{
var defaultCenter = [CPNotificationCenter defaultCenter];
[defaultCenter
addObserver:self
selector:@selector(colorWellDidBecomeExclusive:)
name:_CPColorWellDidBecomeExclusiveNotification
object:nil];
[defaultCenter
addObserver:self
selector:@selector(colorPanelWillClose:)
name:CPWindowWillCloseNotification
object:[CPColorPanel sharedColorPanel]];
}
- (void)_removeNotifications
{
var defaultCenter = [CPNotificationCenter defaultCenter];
[defaultCenter
removeObserver:self
name:_CPColorWellDidBecomeExclusiveNotification
object:nil];
[defaultCenter
removeObserver:self
name:CPWindowWillCloseNotification
object:[CPColorPanel sharedColorPanel]];
}
/*!
Sets whether the color well is bordered.
@@ -145,8 +143,7 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
return [self hasThemeState:CPThemeStateBordered];
}
// MARK: -
// MARK: Managing Color
// Managing Color From Color Wells
/*!
Returns the color well's current color.
@@ -161,17 +158,12 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
*/
- (void)setColor:(CPColor)aColor
{
if ([_color isEqual:aColor])
if (_color == aColor)
return;
_color = aColor;
[self setNeedsLayout];
// Only push back to the panel if we initiated the change (not if the panel pushed it to us)
// AND if we are the current focus.
if (!_isChangingColorFromPanel && [self isFirstResponder])
[[CPColorPanel sharedColorPanel] setColor:_color];
}
/*!
@@ -183,175 +175,96 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[self setColor:[aSender color]];
}
/*!
Standard action method sent by CPColorPanel via the Responder Chain.
*/
- (void)changeColor:(id)aSender
{
if ([aSender isKindOfClass:[CPColorPanel class]])
{
_isChangingColorFromPanel = YES;
[self setColor:[aSender color]];
_isChangingColorFromPanel = NO;
// Forward the action to our target (e.g. controller)
[self sendAction:[self action] to:[self target]];
}
}
// MARK: -
// MARK: Activating and Deactivating
// Activating and Deactivating Color Wells
/*!
Activates the color well, displays the color panel, and makes the panel's current color the same as its own.
If exclusive is \c YES, deactivates any other CPColorWells. \c NO, keeps them active.
@param shouldBeExclusive whether other color wells should be deactivated.
*/
- (BOOL)becomeFirstResponder
- (void)activate:(BOOL)shouldBeExclusive
{
[self setThemeState:CPThemeStateFirstResponder];
var panel = [CPColorPanel sharedColorPanel];
// EXPLICITLY set ourselves as the target.
// This ensures that when the user clicks the panel (making Panel key),
// the panel still knows to send messages back to us.
[panel setTarget:self];
[panel setAction:@selector(changeColor:)];
// Sync panel to our current color
[panel setColor:_color];
if (shouldBeExclusive)
// FIXME: make this queue!
[[CPNotificationCenter defaultCenter]
postNotificationName:_CPColorWellDidBecomeExclusiveNotification
object:self];
[[CPNotificationCenter defaultCenter] postNotificationName:_CPColorWellDidBecomeExclusiveNotification object:self];
return YES;
}
- (BOOL)resignFirstResponder
{
[self unsetThemeState:CPThemeStateFirstResponder];
var panel = [CPColorPanel sharedColorPanel];
// Clean up if we were the target
if ([panel target] == self)
[panel setTarget:nil];
return YES;
}
// MARK: -
// MARK: Event Handling
- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEnabled])
if ([self isActive])
return;
_active = YES;
[[CPNotificationCenter defaultCenter]
addObserver:self
selector:@selector(colorPanelDidChangeColor:)
name:CPColorPanelColorDidChangeNotification
object:[CPColorPanel sharedColorPanel]];
}
/*!
Deactivates the color well.
*/
- (void)deactivate
{
if (![self isActive])
return;
_active = NO;
[[CPNotificationCenter defaultCenter]
removeObserver:self
name:CPColorPanelColorDidChangeNotification
object:[CPColorPanel sharedColorPanel]];
}
/*!
Returns \c YES if the color well is active.
*/
- (BOOL)isActive
{
return _active;
}
- (void)colorPanelDidChangeColor:(CPNotification)aNotification
{
[self takeColorFrom:[aNotification object]];
[self sendAction:[self action] to:[self target]];
}
- (void)colorWellDidBecomeExclusive:(CPNotification)aNotification
{
if (self != [aNotification object])
[self deactivate];
}
- (void)colorPanelWillClose:(CPNotification)aNotification
{
[self deactivate];
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
{
[self highlight:NO];
if (!mouseIsUp || !CGRectContainsPoint([self bounds], aPoint) || ![self isEnabled])
return;
_mouseDownPoint = [anEvent locationInWindow];
[self activate:YES];
var colorPanel = [CPColorPanel sharedColorPanel];
[colorPanel setPlatformWindow:[[self window] platformWindow]];
[colorPanel setColor:_color];
[colorPanel orderFront:self];
}
- (void)mouseDragged:(CPEvent)anEvent
{
if (![self isEnabled])
return;
var windowPoint = [anEvent locationInWindow];
// Prevent accidental drags from rapid clicking causing small micro-movements
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
var bounds = CGRectMake(0, 0, 15, 15);
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
[dragView setBackgroundColor:[CPColor blackColor]];
[dragFillView setBackgroundColor:_color];
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorPboardType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_color] forType:CPColorPboardType];
var point = [self convertPoint:windowPoint fromView:nil];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:pasteboard
source:self
slideBack:YES];
}
// MARK: -
// MARK: Drag and Drop
- (void)draggingEntered:(id)sender
{
var pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:CPColorPboardType])
{
[self setThemeState:CPThemeStateHighlighted];
return CPDragOperationCopy;
}
return CPDragOperationNone;
}
- (void)draggingExited:(id)sender
{
[self unsetThemeState:CPThemeStateHighlighted];
}
- (BOOL)performDragOperation:(id)sender
{
var pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:CPColorPboardType])
{
var data = [pasteboard dataForType:CPColorPboardType],
newColor = [CPKeyedUnarchiver unarchiveObjectWithData:data];
if (newColor && [newColor isKindOfClass:[CPColor class]])
{
[self setColor:newColor];
[self sendAction:[self action] to:[self target]];
// Activate nicely after drop
[self activate:YES];
[self unsetThemeState:CPThemeStateHighlighted];
return YES;
}
}
return NO;
}
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
{
if (aType == CPColorPboardType)
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_color] forType:aType];
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
// MARK: -
// MARK: Layout
- (CGRect)contentRectForBounds:(CGRect)bounds
{
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
return CGRectInsetByInset(bounds, contentInset);
}
@@ -405,6 +318,7 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
[contentView setBackgroundColor:_color];
var contentBorderView = [self layoutEphemeralSubviewNamed:@"content-border-view"
@@ -414,6 +328,28 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[contentBorderView setBackgroundColor:[self currentValueForThemeAttribute:@"content-border-color"]];
}
#pragma mark -
#pragma mark Observers method
- (void)_addObservers
{
if (_isObserving)
return;
[super _addObservers];
[self _registerNotifications];
}
- (void)_removeObservers
{
if (!_isObserving)
return;
[super _removeObservers];
[self _removeNotifications];
}
@end
@implementation CPColorWellValueBinder : CPBinder
@@ -462,9 +398,9 @@ var CPColorWellColorKey = "CPColorWellColorKey",
if (self)
{
_active = NO;
_color = [aCoder decodeObjectForKey:CPColorWellColorKey];
[self setBordered:[aCoder decodeBoolForKey:CPColorWellBorderedKey]];
[self registerForDraggedTypes:[CPArray arrayWithObject:CPColorPboardType]];
}
return self;
+32 -22
View File
@@ -91,7 +91,7 @@ var CPComboBoxTextSubview = @"text",
{
return @{
@"popup-button-size": CGSizeMake(21.0, 29.0),
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0)
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0),
};
}
@@ -133,7 +133,7 @@ var CPComboBoxTextSubview = @"text",
[self setThemeState:CPComboBoxStateButtonBordered];
}
// MARK: Setting Display Attributes
#pragma mark Setting Display Attributes
- (BOOL)hasVerticalScroller
{
@@ -214,7 +214,7 @@ var CPComboBoxTextSubview = @"text",
_numberOfVisibleItems = MAX(visibleItems, 1);
}
// MARK: Setting a Delegate
#pragma mark Setting a Delegate
- (id <CPComboBoxDelegate>)delegate
{
@@ -254,7 +254,7 @@ var CPComboBoxTextSubview = @"text",
[super setDelegate:aDelegate];
}
// MARK: Setting a Data Source
#pragma mark Setting a Data Source
- (id <CPComboBoxDataSource>)dataSource
{
@@ -305,7 +305,7 @@ var CPComboBoxTextSubview = @"text",
[self reloadData];
}
// MARK: Working with an Internal List
#pragma mark Working with an Internal List
- (void)addItemsWithObjectValues:(CPArray)objects
{
@@ -378,7 +378,7 @@ var CPComboBoxTextSubview = @"text",
return _items.length;
}
// MARK: Manipulating the Displayed List
#pragma mark Manipulating the Displayed List
/*!
Returns the delegate to be used when creating the pop up list.
@@ -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];
}
@@ -564,19 +564,12 @@ var CPComboBoxTextSubview = @"text",
var selectedStringValue = [_listDelegate selectedStringValue];
if (selectedStringValue == nil)
if (selectedStringValue === nil)
return NO;
else
_selectedStringValue = selectedStringValue;
[self setStringValue:_selectedStringValue];
[self _updatePlaceholderState];
#if PLATFORM(DOM)
[self _setCSSStyleForInputElement];
#endif
[self _reverseSetBinding];
return YES;
@@ -601,7 +594,7 @@ var CPComboBoxTextSubview = @"text",
[self sendAction:[self action] to:[self target]];
}
// MARK: Manipulating the Selection
#pragma mark Manipulating the Selection
- (void)deselectItemAtIndex:(int)index
{
@@ -653,7 +646,7 @@ var CPComboBoxTextSubview = @"text",
[self selectItemAtIndex:index];
}
// MARK: Completing the Text Field
#pragma mark Completing the Text Field
- (BOOL)completes
{
@@ -700,7 +693,7 @@ var CPComboBoxTextSubview = @"text",
_forceSelection = !!flag;
}
// MARK: CPTextField Delegate Methods and Overrides
#pragma mark CPTextField Delegate Methods and Overrides
/*! @ignore */
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
@@ -900,7 +893,7 @@ var CPComboBoxTextSubview = @"text",
[_listDelegate setAlignment:alignment];
}
// MARK: Pop Up Button Layout
#pragma mark Pop Up Button Layout
- (CGRect)popupButtonRectForBounds:(CGRect)bounds
{
@@ -945,7 +938,7 @@ var CPComboBoxTextSubview = @"text",
relativeToEphemeralSubviewNamed:@"content-view"];
}
// MARK: Internal Helpers
#pragma mark Internal Helpers
/*! @ignore */
- (void)_dataSourceWarningForMethod:(SEL)cmd condition:(CPString)flag
@@ -982,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
{
@@ -1006,8 +1016,8 @@ var CPComboBoxTextSubview = @"text",
}
// MARK: -
// MARK: Observers method
#pragma mark -
#pragma mark Observers method
- (void)_addObservers
{
-28
View File
@@ -32,7 +32,6 @@ CPKHTMLBrowserEngine = 1 << 2;
CPOperaBrowserEngine = 1 << 3;
CPWebKitBrowserEngine = 1 << 4; // Safari + Chrome
CPBlinkBrowserEngine = 1 << 5; // Recent Chrome
CPEdgeBrowserEngine = 1 << 6;
// Operating Systems
CPMacOperatingSystem = 0;
@@ -84,7 +83,6 @@ CPAltEnterTextAreaFeature = 32;
CPCSSAnimationFeature = 33;
CPBackspaceTriggersPageBack = 34;
/*
When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen. This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203
*/
@@ -148,28 +146,6 @@ else if (typeof window !== "undefined" && (window.attachEvent || (!(window.Activ
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES;
}
// Edge
else if (USER_AGENT.indexOf("Edge/") != -1)
{
PLATFORM_ENGINE |= CPEdgeBrowserEngine;
PLATFORM_FEATURES[CPCSSRGBAFeature] = YES;
PLATFORM_FEATURES[CPHTMLContentEditableFeature] = YES;
PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES;
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = NO;
PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES;
var versionStart = USER_AGENT.indexOf("Edge/") + "Edge/".length,
versionEnd = USER_AGENT.indexOf(" ", versionStart),
versionString = USER_AGENT.substring(versionStart, versionEnd),
versionDivision = versionString.indexOf('.'),
majorVersion = parseInt(versionString.substring(0, versionDivision)),
minorVersion = parseInt(versionString.substr(versionDivision + 1));
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
}
// Safari + Chrome (WebKit and Blink)
else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
{
@@ -235,7 +211,6 @@ else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
PLATFORM_ENGINE |= CPGeckoBrowserEngine;
PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES;
PLATFORM_FEATURES[CPBackspaceTriggersPageBack] = YES;
var index = USER_AGENT.indexOf("Firefox"),
version = (index === -1) ? 2.0 : parseFloat(USER_AGENT.substring(index + "Firefox".length + 1));
@@ -246,9 +221,6 @@ else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
if (version < 3.0)
PLATFORM_FEATURES[CPJavaScriptMouseWheelValues_8_15] = YES;
if (version >= 66)
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
// Some day this might be fixed and should be version prefixed. No known fixed version yet.
PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES;
+20 -36
View File
@@ -23,13 +23,12 @@
@import <Foundation/CPFormatter.j>
@import <Foundation/CPTimer.j>
@import "CPFont.j"
@import "CPShadow.j"
@import "CPText.j"
@import "CPKeyValueBinding.j"
@import "CPTrackingArea.j"
@class CPFont
@global CPApp
@protocol CPControlTextEditingDelegate <CPObject>
@@ -48,10 +47,6 @@
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];
@typedef CPLineBreakMode
CPLineBreakByWordWrapping = 0;
@@ -136,13 +131,14 @@ var CPControlBlackColor = [CPColor blackColor];
@"vertical-alignment": CPTopVerticalTextAlignment,
@"line-break-mode": CPLineBreakByClipping,
@"text-color": [CPColor blackColor],
@"font": [CPNull null],
@"font": [CPFont systemFontOfSize:CPFontCurrentSystemSize],
@"text-shadow-color": [CPNull null],
@"text-shadow-offset": CGSizeMakeZero(),
@"image-position": CPImageLeft,
@"image-scaling": CPScaleToFit,
@"min-size": CGSizeMakeZero(),
@"max-size": CGSizeMake(-1.0, -1.0)
@"max-size": CGSizeMake(-1.0, -1.0),
@"nib2cib-adjustment-frame": CGRectMakeZero()
};
}
@@ -200,16 +196,13 @@ var CPControlBlackColor = [CPColor blackColor];
{
_sendActionOn = CPLeftMouseUpMask;
_trackingMouseDownFlags = 0;
[self setControlSize:CPThemeStateControlSizeRegular];
[self updateTrackingAreas];
}
return self;
}
// MARK: -
// MARK: Control Size
#pragma mark -
#pragma mark Control Size
/*!
Returns the control's control size
@@ -249,9 +242,6 @@ var CPControlBlackColor = [CPColor blackColor];
case CPMiniControlSize:
return CPThemeStateControlSizeMini;
case CPLargeControlSize:
return CPThemeStateControlSizeLarge;
case CPRegularControlSize:
default:
@@ -290,7 +280,7 @@ var CPControlBlackColor = [CPColor blackColor];
}
// MARK: -
#pragma mark -
/*!
Sets the receiver's target action.
@@ -630,15 +620,15 @@ var CPControlBlackColor = [CPColor blackColor];
*/
- (CPString)stringValue
{
if (_formatter && _value != nil)
if (_formatter && _value !== undefined)
{
var formattedValue = [self hasThemeState:CPThemeStateEditing] ? [_formatter editingStringForObjectValue:_value] : [_formatter stringForObjectValue:_value];
if (formattedValue != nil)
if (formattedValue !== nil && formattedValue !== undefined)
return formattedValue;
}
return _value == nil ? @"" : String(_value);
return (_value === undefined || _value === nil) ? @"" : String(_value);
}
/*!
@@ -647,7 +637,7 @@ var CPControlBlackColor = [CPColor blackColor];
- (void)setStringValue:(CPString)aString
{
// Cocoa raises an invalid parameter assertion and returns if you pass nil.
if (aString == nil)
if (aString === nil || aString === undefined)
{
CPLog.warn("nil or undefined sent to CPControl -setStringValue");
return;
@@ -813,7 +803,7 @@ var CPControlBlackColor = [CPColor blackColor];
CPBottomVerticalTextAlignment
</pre>
*/
- (void)setVerticalAlignment:(CPVerticalTextAlignment)alignment
- (void)setVerticalAlignment:(CPTextVerticalAlignment)alignment
{
[self setValue:alignment forThemeAttribute:@"vertical-alignment"];
}
@@ -858,12 +848,7 @@ var CPControlBlackColor = [CPColor blackColor];
*/
- (void)setTextColor:(CPColor)aColor
{
[self setValue:aColor forThemeAttribute:@"text-color" inState:[self themeState]];
}
- (void)setTextColor:(CPColor)aColor inThemeStates:(CPArray)themeStates
{
[self setValue:aColor forThemeAttribute:@"text-color" inStates:themeStates];
[self setValue:aColor forThemeAttribute:@"text-color"];
}
/*!
@@ -921,7 +906,7 @@ var CPControlBlackColor = [CPColor blackColor];
*/
- (CPFont)font
{
return [self currentValueForThemeAttribute:@"font"] || [CPFont systemFontForControlSize:_controlSize];
return [self valueForThemeAttribute:@"font"];
}
/*!
@@ -1027,8 +1012,8 @@ var CPControlBlackColor = [CPColor blackColor];
}
// MARK: -
// MARK: Base writing direction
#pragma mark -
#pragma mark Base writing direction
/*!
Sets the initial writing direction of the receiver
@@ -1131,7 +1116,6 @@ var CPControlActionKey = @"CPControlActionKey",
[self setControlSize:[aCoder decodeIntForKey:CPControlControlSizeKey]];
[self setBaseWritingDirection:[aCoder decodeIntForKey:CPControlBaseWrittingDirectionKey]];
[self updateTrackingAreas];
}
return self;
@@ -1151,18 +1135,18 @@ var CPControlActionKey = @"CPControlActionKey",
var objectValue = [self objectValue];
if (objectValue != nil)
if (objectValue !== nil)
[aCoder encodeObject:objectValue forKey:CPControlValueKey];
if (_target != nil)
if (_target !== nil)
[aCoder encodeConditionalObject:_target forKey:CPControlTargetKey];
if (_action != nil)
if (_action !== nil)
[aCoder encodeObject:_action forKey:CPControlActionKey];
[aCoder encodeInt:_sendActionOn forKey:CPControlSendActionOnKey];
if (_formatter != nil)
if (_formatter !== nil)
[aCoder encodeObject:_formatter forKey:CPControlFormatterKey];
[aCoder encodeInt:_controlSize forKey:CPControlControlSizeKey];
-2
View File
@@ -37,8 +37,6 @@
CPString _expires;
}
@global document
/*!
Initializes a cookie with a given name \c aName.
@param the name for the cookie
Regular → Executable
+44 -112
View File
@@ -23,7 +23,6 @@ Cursor support by browser:
@import <Foundation/CPObject.j>
@import "CPImage.j"
@import "CPCompatibility.j"
@global CPApp
@@ -32,12 +31,6 @@ var currentCursor = nil,
cursors = {},
ieCursorMap = {};
@typedef CPCursorPlatform
CPCursorPlatformNone = 0;
CPCursorPlatformMac = 1;
CPCursorPlatformWindows = 2;
CPCursorPlatformBoth = 3;
@implementation CPCursor : CPObject
{
CPString _cssString @accessors(readonly);
@@ -164,7 +157,7 @@ CPCursorPlatformBoth = 3;
}
// Internal method that is used to return the system cursors. Caches the system cursors for performance.
+ (CPCursor)_nativeSystemCursorWithName:(CPString)cursorName cssString:(CPString)aString
+ (CPCursor)_systemCursorWithName:(CPString)cursorName cssString:(CPString)aString hasImage:(BOOL)doesHaveImage
{
var cursor = cursors[cursorName];
@@ -172,216 +165,155 @@ CPCursorPlatformBoth = 3;
{
var cssString;
// IE <= 8 does not support some cursors, map them to supported cursors
var ieLessThan9 = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPFeatureIsCompatible(CPHTMLCanvasFeature);
if (doesHaveImage)
{
var themeResourcePath = [[[CPApp themeBlend] bundle] resourcePath],
extension = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) ? @"cur" : @"png";
if (ieLessThan9)
cssString = ieCursorMap[aString] || aString;
else
cssString = aString;
cursors[cursorName] = cursor = [[CPCursor alloc] initWithCSSString:cssString];
}
return cursor;
}
+ (CPCursor)_imageCursorWithName:(CPString)cursorName cssString:(CPString)aString
{
var cursor = cursors[cursorName];
if (typeof cursor === "undefined")
{
var themeResourcePath = [[[CPApp themeBlend] bundle] resourcePath],
extension = CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) ? @"cur" : @"png",
cssString = [CPString stringWithFormat:@"url(%@cursors/%@.%@), %@", themeResourcePath, cursorName, extension, aString];
}
cursors[cursorName] = cursor = [[CPCursor alloc] initWithCSSString:cssString];
else
{
// IE <= 8 does not support some cursors, map them to supported cursors
var ieLessThan9 = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPFeatureIsCompatible(CPHTMLCanvasFeature);
if (ieLessThan9)
cssString = ieCursorMap[aString] || aString;
else
cssString = aString;
}
cursor = [[CPCursor alloc] initWithCSSString:cssString];
cursors[cursorName] = cursor;
}
return cursor;
}
+ (CPCursor)_tryUsingNativeSystemCursorWithName:(CPString)cursorName cssString:(CPString)cssName onPlatform:(CPCursorPlatform)shouldUseNativeCursorOn fallingBackWithImageAndCSSPointer:(CPString)aString
{
var useNativeSystemCursor = (((shouldUseNativeCursorOn == CPCursorPlatformBoth) ||
((shouldUseNativeCursorOn == CPCursorPlatformMac) && CPBrowserIsOperatingSystem(CPMacOperatingSystem)) ||
((shouldUseNativeCursorOn == CPCursorPlatformWindows) && CPBrowserIsOperatingSystem(CPWindowsOperatingSystem)))
&& [CPCursor _nativeCursorExists:cssName]);
if (useNativeSystemCursor)
return [CPCursor _nativeSystemCursorWithName:cursorName cssString:cssName];
else
return [CPCursor _imageCursorWithName:cursorName cssString:aString];
}
+ (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
if ([cursorCSSName isEqualToString:@"context-menu"] && CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) && !CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPBrowserIsEngine(CPEdgeBrowserEngine))
return NO;
// Normal usage : try to set the cursor and check if resulting cursor is the one we tried to set.
// If yes, then the browser implements the cursor. If no (and usually we get "default"), then it doesn't.
var platformWindows = [[CPPlatformWindow visiblePlatformWindows] allObjects],
count = [platformWindows count];
if (count > 0)
{
var currentPlatformCursor = platformWindows[0]._DOMBodyElement.style.cursor;
platformWindows[0]._DOMBodyElement.style.cursor = cursorCSSName;
var doesExist = (platformWindows[0]._DOMBodyElement.style.cursor == cursorCSSName);
platformWindows[0]._DOMBodyElement.style.cursor = currentPlatformCursor;
return doesExist;
}
#endif
return NO;
}
+ (CPCursor)arrowCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"default"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"default" hasImage:NO];
}
+ (CPCursor)crosshairCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"crosshair"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"crosshair" hasImage:NO];
}
+ (CPCursor)IBeamCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"text"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"text" hasImage:NO];
}
+ (CPCursor)pointingHandCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"pointer"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"pointer" hasImage:NO];
}
+ (CPCursor)resizeNorthwestCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nw-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nw-resize" hasImage:NO];
}
+ (CPCursor)resizeNorthwestSoutheastCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nwse-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nwse-resize" hasImage:NO];
}
+ (CPCursor)resizeNortheastCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ne-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ne-resize" hasImage:NO];
}
+ (CPCursor)resizeNortheastSouthwestCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nesw-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nesw-resize" hasImage:NO];
}
+ (CPCursor)resizeSouthwestCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"sw-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"sw-resize" hasImage:NO];
}
+ (CPCursor)resizeSoutheastCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"se-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"se-resize" hasImage:NO];
}
+ (CPCursor)resizeDownCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"s-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"s-resize" hasImage:NO];
}
+ (CPCursor)resizeUpCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"n-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"n-resize" hasImage:NO];
}
+ (CPCursor)resizeLeftCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"w-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"w-resize" hasImage:NO];
}
+ (CPCursor)resizeRightCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"e-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"e-resize" hasImage:NO];
}
+ (CPCursor)resizeLeftRightCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"col-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"col-resize" hasImage:NO];
}
+ (CPCursor)resizeEastWestCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ew-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ew-resize" hasImage:NO];
}
+ (CPCursor)resizeUpDownCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-resize" hasImage:NO];
}
+ (CPCursor)resizeNorthSouthCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ns-resize"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ns-resize" hasImage:NO];
}
+ (CPCursor)operationNotAllowedCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"not-allowed"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"not-allowed" hasImage:NO];
}
+ (CPCursor)dragCopyCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"copy"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"copy" hasImage:YES];
}
+ (CPCursor)dragLinkCursor
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"alias"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"alias" hasImage:YES];
}
+ (CPCursor)contextualMenuCursor
{
return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
cssString:@"context-menu"
onPlatform:CPCursorPlatformBoth
fallingBackWithImageAndCSSPointer:@"default"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"context-menu" hasImage:YES];
}
+ (CPCursor)openHandCursor
{
return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
cssString:@"grab"
onPlatform:CPCursorPlatformMac
fallingBackWithImageAndCSSPointer:@"default"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"move" hasImage:YES];
}
+ (CPCursor)closedHandCursor
{
return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
cssString:@"grabbing"
onPlatform:CPCursorPlatformMac
fallingBackWithImageAndCSSPointer:@"default"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"-moz-grabbing" hasImage:YES];
}
+ (CPCursor)disappearingItemCursor
{
return [CPCursor _imageCursorWithName:CPStringFromSelector(_cmd) cssString:@"default"];
}
+ (CPCursor)IBeamCursorForVerticalLayout
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"vertical-text"];
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"auto" hasImage:YES];
}
@end
+105 -204
View File
@@ -1,7 +1,7 @@
/* CPDatePicker.j
* AppKit
*
* Created by Alexandre Wilhelm
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* This library is free software; you can redistribute it and/or
@@ -59,31 +59,31 @@ CPEraDatePickerElementFlag = 0x0100;
*/
@implementation CPDatePicker : CPControl
{
BOOL _isBordered @accessors(getter=isBordered, setter=setBordered:);
BOOL _isBezeled @accessors(getter=isBezeled, setter=setBezeled:);
BOOL _drawsBackground @accessors(property=drawsBackground);
CPDate _dateValue @accessors(property=dateValue);
CPDate _minDate @accessors(property=minDate);
CPDate _maxDate @accessors(property=maxDate);
CPFont _textFont @accessors(property=textFont);
CPLocale _locale @accessors(property=locale);
//CPCalendar _calendar @accessors(property=calendar);
CPTimeZone _timeZone @accessors(property=timeZone);
id _delegate @accessors(property=delegate);
CPInteger _datePickerElements @accessors(property=datePickerElements);
CPInteger _datePickerMode @accessors(property=datePickerMode);
CPInteger _datePickerStyle @accessors(property=datePickerStyle);
CPInteger _timeInterval @accessors(property=timeInterval);
BOOL _isBordered @accessors(getter=isBordered, setter=setBordered:);
BOOL _isBezeled @accessors(getter=isBezeled, setter=setBezeled:);
BOOL _drawsBackground @accessors(property=drawsBackground);
CPDate _dateValue @accessors(property=dateValue);
CPDate _minDate @accessors(property=minDate);
CPDate _maxDate @accessors(property=maxDate);
CPFont _textFont @accessors(property=textFont);
CPLocale _locale @accessors(property=locale);
//CPCalendar _calendar @accessors(property=calendar);
CPTimeZone _timeZone @accessors(property=timeZone);
id _delegate @accessors(property=delegate);
CPInteger _datePickerElements @accessors(property=datePickerElements);
CPInteger _datePickerMode @accessors(property=datePickerMode);
CPInteger _datePickerStyle @accessors(property=datePickerStyle);
CPInteger _timeInterval @accessors(property=timeInterval);
BOOL _invokedByUserEvent;
_CPDatePickerTextField _datePickerTextfield;
_CPDatePickerCalendar _datePickerCalendar;
unsigned _implementedCDatePickerDelegateMethods;
BOOL _isTextual;
id _datePickerComponent;
}
// MARK: -
// MARK: Theme methods
#pragma mark -
#pragma mark Theme methods
+ (CPString)defaultThemeClass
{
@@ -144,38 +144,12 @@ CPEraDatePickerElementFlag = 0x0100;
@"hour-hand-size": CGSizeMakeZero(),
@"middle-hand-size": CGSizeMakeZero(),
@"minute-hand-size": CGSizeMakeZero(),
@"previous-button-size": CGSizeMakeZero(),
@"current-button-size": CGSizeMakeZero(),
@"next-button-size": CGSizeMakeZero(),
@"title-inset": [CPNull null],
@"day-label-inset": [CPNull null],
@"tile-content-inset": CGInsetMakeZero(),
@"tile-margin": [CPNull null],
@"tile-inset": CGInsetMakeZero(),
@"separator-color": [CPNull null],
@"separator-margin-width": 0,
@"separator-height": 0,
@"bezel-color-calendar-left": [CPNull null],
@"bezel-color-calendar-middle": [CPNull null],
@"bezel-color-calendar-right": [CPNull null],
@"tile-vertical-alignment": CPCenterVerticalTextAlignment,
@"tile-text-alignment": CPCenterTextAlignment,
@"hour-ampm-margin": 2,
@"time-separator-content-inset": CGInsetMakeZero(),
@"clock-second-hand-over": NO,
@"clock-draws-hours": NO,
@"clock-hours-font": [CPNull null],
@"clock-hours-text-color": [CPColor clearColor],
@"clock-hours-radius": 0,
@"calendar-clock-margin": 10,
@"clock-only-nib2cib-adjustment-frame": CPRectMakeZero(),
@"uses-focus-ring": NO
};
}
// MARK: -
// MARK: Binding methods
#pragma mark -
#pragma mark Binding methods
+ (Class)_binderClassForBinding:(CPString)theBinding
{
@@ -200,8 +174,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)initWithFrame:(CGRect)aFrame
{
@@ -232,61 +206,34 @@ CPEraDatePickerElementFlag = 0x0100;
if (!_locale)
_locale = [CPLocale currentLocale];
_datePickerComponent = nil;
_datePickerTextfield = [[_CPDatePickerTextField alloc] initWithFrame:[self bounds] withDatePicker:self];
[_datePickerTextfield setDateValue:_dateValue];
[self _createComponents];
_datePickerCalendar = [[_CPDatePickerCalendar alloc] initWithFrame:[self bounds] withDatePicker:self];
[_datePickerCalendar setDateValue:_dateValue];
// We might have been unarchived in a disabled state.
[_datePickerTextfield setEnabled:[self isEnabled]];
[self setNeedsDisplay:YES];
[self setNeedsLayout];
}
- (void)_createComponents
{
_isTextual = (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle) || (_datePickerStyle == CPTextFieldDatePickerStyle);
if (_datePickerComponent)
{
[_datePickerComponent removeFromSuperview];
_datePickerComponent = nil;
}
_datePickerComponent = [[(_isTextual ? _CPDatePickerTextField : _CPDatePickerCalendar) alloc] initWithFrame:[self bounds] withDatePicker:self];
[_datePickerComponent setDateValue:_dateValue];
[_datePickerComponent setControlSize:[self controlSize]];
[_datePickerComponent setDatePickerElements:_datePickerElements];
// FIXME: Don't know why but next line will cause theme compilation to fail...
// Workaround: added "if PLATFORM(DOM)"
#if PLATFORM(DOM)
[_datePickerComponent setEnabled:[self isEnabled]];
#endif
if (_isTextual)
// We need to transmit text color to the text field version (Cocoa doesn't permit adapting the calendar view text color)
[_datePickerComponent setTextColor:[self textColor]];
[self addSubview:_datePickerComponent];
}
// MARK: -
// MARK: Control Size
#pragma mark -
#pragma mark Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
[super setControlSize:aControlSize];
[_datePickerComponent setControlSize:aControlSize];
if (_isTextual)
if ([self datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [self datePickerStyle] == CPTextFieldDatePickerStyle)
[self _sizeToControlSize];
}
// MARK: -
// MARK: Delegate methods
#pragma mark -
#pragma mark Delegate methods
/*! Set the delegate of the datePicker
@param aDelegate delegate of the datePicker
@@ -302,19 +249,42 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: Layout method
#pragma mark -
#pragma mark Layout method
/*! Layout the subviews
*/
- (void)layoutSubviews
{
[_datePickerComponent setNeedsLayout];
[_datePickerComponent setNeedsDisplay:YES];
[super layoutSubviews];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
{
if (![_datePickerTextfield superview])
[self addSubview:_datePickerTextfield];
if ([_datePickerCalendar superview])
[_datePickerCalendar removeFromSuperview];
[_datePickerTextfield setControlSize:[self controlSize]];
[_datePickerTextfield setNeedsLayout];
[_datePickerTextfield setNeedsDisplay:YES];
}
else
{
if (![_datePickerCalendar superview])
[self addSubview:_datePickerCalendar];
if ([_datePickerTextfield superview])
[_datePickerTextfield removeFromSuperview];
[_datePickerCalendar setNeedsLayout];
[_datePickerCalendar setNeedsDisplay:YES];
}
}
// MARK: -
// MARK: Setter
#pragma mark -
#pragma mark Setter
/*! Return the objectValue of the datePicker. The objectValue should take the timeZoneEffect
*/
@@ -371,7 +341,10 @@ CPEraDatePickerElementFlag = 0x0100;
if ([aDateValue isEqualToDate:_dateValue] && aTimeInterval == _timeInterval)
{
[_datePickerComponent setDateValue:_dateValue];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
[_datePickerTextfield setDateValue:_dateValue];
else
[_datePickerCalendar setDateValue:_dateValue];
return;
}
@@ -399,7 +372,10 @@ CPEraDatePickerElementFlag = 0x0100;
if (_invokedByUserEvent)
[self sendAction:[self action] to:[self target]];
[_datePickerComponent setDateValue:_dateValue];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
[_datePickerTextfield setDateValue:_dateValue];
else
[_datePickerCalendar setDateValue:_dateValue];
}
/*! Set the minDate of the datePicker
@@ -407,9 +383,6 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setMinDate:(CPDate)aMinDate
{
if (_minDate === aMinDate)
return;
[self willChangeValueForKey:@"minDate"];
_minDate = aMinDate;
[self didChangeValueForKey:@"minDate"];
@@ -422,9 +395,6 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setMaxDate:(CPDate)aMaxDate
{
if (_maxDate === aMaxDate)
return;
[self willChangeValueForKey:@"maxDate"];
_maxDate = aMaxDate;
[self didChangeValueForKey:@"maxDate"];
@@ -437,32 +407,10 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setDatePickerStyle:(CPInteger)aDatePickerStyle
{
if (_datePickerStyle === aDatePickerStyle)
return;
_datePickerStyle = aDatePickerStyle;
// This is needed in order to specify different theme attributes values for textual / graphical date picker
if (_datePickerStyle === CPClockAndCalendarDatePickerStyle)
[self setThemeState:CPThemeStateAlternateState];
else
[self unsetThemeState:CPThemeStateAlternateState];
// This is needed in order to specify different theme attributes values for with / without stepper textual date picker
if (_datePickerStyle === CPTextFieldAndStepperDatePickerStyle)
[self setThemeState:CPThemeStateComposedControl];
else
[self unsetThemeState:CPThemeStateComposedControl];
[self setControlSize:[self controlSize]];
if (_datePickerComponent)
{
// We already have a component so we need to update it
[_datePickerComponent resignFirstResponder];
[self _createComponents];
}
[self setNeedsDisplay:YES];
[self setNeedsLayout];
}
@@ -472,14 +420,8 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
{
if (_datePickerElements === aDatePickerElements)
return;
_datePickerElements = aDatePickerElements;
// Notify the component of the new value
[_datePickerComponent setDatePickerElements:_datePickerElements];
[self setNeedsDisplay:YES];
[self setNeedsLayout];
}
@@ -489,9 +431,6 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setDatePickerMode:(CPInteger)aDatePickerMode
{
if (_datePickerMode === aDatePickerMode)
return;
_datePickerMode = aDatePickerMode;
if (_datePickerMode == CPSingleDateMode)
@@ -517,9 +456,6 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setLocale:(CPLocale)aLocale
{
if (_locale === aLocale)
return;
_locale = aLocale;
if (_formatter)
@@ -530,9 +466,7 @@ CPEraDatePickerElementFlag = 0x0100;
}
// This will update the textFields (usefull when changing with a date with pm and am)
if (_isTextual)
[_datePickerComponent setDateValue:_dateValue];
[_datePickerTextfield setDateValue:_dateValue];
[self setNeedsDisplay:YES];
[self setNeedsLayout];
}
@@ -543,9 +477,6 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setBezeled:(BOOL)shouldBeBezeled
{
if (_isBezeled === shouldBeBezeled)
return;
_isBezeled = shouldBeBezeled;
if (shouldBeBezeled)
@@ -560,9 +491,6 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setBordered:(BOOL)shouldBeBordered
{
if (_isBordered === shouldBeBordered)
return;
_isBordered = shouldBeBordered;
if (shouldBeBordered)
@@ -578,22 +506,6 @@ CPEraDatePickerElementFlag = 0x0100;
- (void)setTextFont:(CPFont)aFont
{
[self setFont:aFont];
if (_isTextual)
[_datePickerComponent setTextFont:aFont];
}
/*!
Sets the color of the control.
@param aColor
*/
- (void)setTextColor:(CPColor)aColor
{
[super setTextColor:aColor];
if (_isTextual)
[_datePickerComponent setTextColor:aColor];
// REM: in Cocoa, setTextColor has no effect on calendar view
}
/*! Sets the enabled status of the control. Controls that are not enabled can not be used by the user and obtain the CPThemeStateDisabled theme state.
@@ -603,10 +515,8 @@ CPEraDatePickerElementFlag = 0x0100;
{
[super setEnabled:aBoolean];
[_datePickerComponent setEnabled:aBoolean];
if (!aBoolean)
[self resignFirstResponder];
[_datePickerTextfield setEnabled:aBoolean];
[_datePickerCalendar setEnabled:aBoolean];
}
/*! Set the background color of the datePicker
@@ -623,9 +533,6 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setDrawsBackground:(BOOL)aBoolean
{
if (_drawsBackground === aBoolean)
return;
[self willChangeValueForKey:@"drawsBackground"];
_drawsBackground = aBoolean;
[self didChangeValueForKey:@"drawsBackground"];
@@ -638,32 +545,32 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (void)setTimeZone:(CPTimeZone)aTimeZone
{
if (_timeZone === aTimeZone)
return;
[self willChangeValueForKey:@"timeZone"];
_timeZone = aTimeZone;
[self didChangeValueForKey:@"timeZone"];
[self setNeedsLayout];
[_datePickerComponent setDateValue:_dateValue];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
[_datePickerTextfield setDateValue:_dateValue];
else
[_datePickerCalendar setDateValue:_dateValue];
}
// MARK: -
// MARK: First responder methods
#pragma mark -
#pragma mark First responder methods
/*! Return YES if style is set to CPTextFieldAndStepperDatePickerStyle or CPTextFieldDatePickerStyle
*/
- (BOOL)becomeFirstResponder
{
if (_isTextual)
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
{
if (![super becomeFirstResponder])
return NO;
[_datePickerComponent _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]];
[_datePickerTextfield _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]];
return YES;
}
@@ -682,15 +589,15 @@ CPEraDatePickerElementFlag = 0x0100;
*/
- (BOOL)resignFirstResponder
{
if (_isTextual)
[_datePickerComponent resignFirstResponder];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
[_datePickerTextfield resignFirstResponder];
return YES;
}
// MARK: -
// MARK: getter
#pragma mark -
#pragma mark getter
/*!
Returns \c YES if the textfield is bezeled.
@@ -723,16 +630,16 @@ CPEraDatePickerElementFlag = 0x0100;
return [[_locale objectForKey:CPLocaleCountryCode] isEqualToString:@"US"];
}
// MARK: -
// MARK: Key event
#pragma mark -
#pragma mark Key event
/*! Key down event
@param anEvent
*/
- (void)keyDown:(CPEvent)anEvent
{
if (_isTextual)
[_datePickerComponent keyDown:anEvent];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
[_datePickerTextfield keyDown:anEvent];
}
@end
@@ -758,22 +665,20 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
if (self)
{
_backgroundColor = [aCoder decodeObjectForKey:CPBackgroundColorKey];
[self setBordered:[aCoder decodeBoolForKey:CPBorderedKey]];
[self setDrawsBackground:[aCoder decodeBoolForKey:CPDrawsBackgroundKey]];
[self setDatePickerElements:[aCoder decodeIntForKey:CPDatePickerElementsKey]];
[self setDatePickerMode:[aCoder decodeIntForKey:CPDatePickerModeKey]];
_textFont = [aCoder decodeObjectForKey:CPTextFontKey];
_minDate = [aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast];
_maxDate = [aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture];
_timeInterval = [aCoder decodeDoubleForKey:CPIntervalKey];
_datePickerMode = [aCoder decodeIntForKey:CPDatePickerModeKey];
_datePickerElements = [aCoder decodeIntForKey:CPDatePickerElementsKey];
[self setDatePickerStyle:[aCoder decodeIntForKey:CPDatePickerStyleKey]];
[self setMinDate:[aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast]];
[self setMaxDate:[aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture]];
[self setLocale:[aCoder decodeObjectForKey:CPLocaleKey]];
_locale = [aCoder decodeObjectForKey:CPLocaleKey];
_dateValue = [aCoder decodeObjectForKey:CPDateValueKey];
_backgroundColor = [aCoder decodeObjectForKey:CPBackgroundColorKey];
_drawsBackground = [aCoder decodeBoolForKey:CPDrawsBackgroundKey];
_isBordered = [aCoder decodeBoolForKey:CPBorderedKey];
[self _init];
[self setTextFont:[aCoder decodeObjectForKey:CPTextFontKey]];
[self setTimeInterval:[aCoder decodeDoubleForKey:CPIntervalKey]];
[self setDateValue:[aCoder decodeObjectForKey:CPDateValueKey]];
}
return self
@@ -781,10 +686,7 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
- (void)encodeWithCoder:(CPCoder)aCoder
{
// Before encoding, we remove all subviews as we'll recreate them at loading
while ([[self subviews] count] > 0)
[[[self subviews] lastObject] removeFromSuperview];
// FIXME Do we need to encode _datePickerTextfield and _datePickerCalendar? As subviews they'll be encoded, but when we decode we recreate them anyhow.
[super encodeWithCoder:aCoder];
[aCoder encodeDouble:_timeInterval forKey:CPIntervalKey];
@@ -793,7 +695,7 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
[aCoder encodeInt:_datePickerElements forKey:CPDatePickerElementsKey];
[aCoder encodeObject:_minDate forKey:CPMinDateKey];
[aCoder encodeObject:_maxDate forKey:CPMaxDateKey];
[aCoder encodeObject:_dateValue forKey:CPDateValueKey];
[aCoder encodeObject:_dateValue forKey:CPDateValueKey];;
[aCoder encodeObject:_textFont forKey:CPTextFontKey];
[aCoder encodeObject:_locale forKey:CPLocaleKey];
[aCoder encodeObject:_backgroundColor forKey:CPBackgroundColorKey];
@@ -804,7 +706,6 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
@end
// FIXME: add support for CPEditorRegistrationProtocol as implemented for CPTextField
@implementation _CPDatePickerValueBinder : CPBinder
{
}
-98
View File
@@ -1,98 +0,0 @@
/* _CPDatePickerBox.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* 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/Foundation.j>
@import "CPView.j"
@class CPDatePicker
@implementation _CPDatePickerBox : CPView
{
CPDatePicker _datePicker @accessors(property=datePicker);
}
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if ([_datePicker isCSSBased])
return;
if ([_datePicker isBordered])
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"] / 2;
CGContextBeginPath(context);
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]);
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
CGContextMoveToPoint(context, borderWidth, borderWidth);
CGContextAddLineToPoint(context, aRect.size.width - borderWidth, borderWidth);
CGContextAddLineToPoint(context, aRect.size.width - borderWidth, aRect.size.height - borderWidth);
CGContextAddLineToPoint(context, borderWidth, aRect.size.height - borderWidth);
CGContextAddLineToPoint(context, borderWidth,borderWidth);
CGContextStrokePath(context);
CGContextClosePath(context);
}
}
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
{
if (aName === "bezel-view")
return [self bounds];
return [super rectForEphemeralSubviewNamed:aName];
}
- (CPView)createEphemeralSubviewNamed:(CPString)aName
{
if (aName === "bezel-view")
{
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[view setHitTests:NO];
return view;
}
return [super createEphemeralSubviewNamed:aName];
}
- (void)layoutSubviews
{
if ([_datePicker isCSSBased])
{
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color"]];
}
if ([_datePicker drawsBackground])
[self setBackgroundColor:[_datePicker backgroundColor]];
else
[self setBackgroundColor:[CPColor clearColor]];
}
@end
File diff suppressed because it is too large Load Diff
+215 -373
View File
@@ -20,12 +20,10 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPKeyedArchiver.j>
@import <Foundation/CPKeyedUnarchiver.j>
@import "CPView.j"
@import "CPTextField.j"
@import "CPImageView.j"
@import "CPImage.j"
@import "CALayer.j"
@class _CPCibCustomResource
@class CPDatePicker
@@ -36,238 +34,168 @@
var RADIANS = Math.PI / 180;
@typedef _CPDatePickerClockHand
_CPDatePickerClockHours = 1;
_CPDatePickerClockMinutes = 2;
_CPDatePickerClockSeconds = 3;
@implementation _CPDatePickerClock : CPControl
@implementation _CPDatePickerClock : CPView
{
BOOL _isEnabled;
// Pure DOM Views
CPImageView _hourHandView;
CPImageView _minuteHandView;
CPImageView _secondHandView;
CPImageView _middleHandView;
CPArray _hourLabels;
CPTextField _PMAMTextField;
CPDatePicker _datePicker;
CPView _currentHandView;
_CPDatePickerClockHand _currentHand;
CPInteger _currentRepresentedValue;
float _currentValueShift;
CPInteger _numberOfUnits;
BOOL _trackingHand;
CPInteger _representedHours;
CPInteger _representedMinutes;
CPInteger _representedSeconds;
BOOL _representedHourIsPM;
// Angles for Hit-Testing
float _hourAngle;
float _minuteAngle;
float _secondAngle;
CPInteger _datePickerElements @accessors(getter=datePickerElements);
BOOL _isEnabled;
CALayer _rootLayer;
CALayer _hourHandLayer;
CALayer _minuteHandLayer;
CALayer _secondHandLayer;
CALayer _middleHandLayer;
CPDatePicker _datePicker;
CPTextField _PMAMTextField;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker
{
if (self = [super initWithFrame:aFrame])
{
_datePicker = aDatePicker;
_datePickerElements = [_datePicker datePickerElements];
_trackingHand = NO;
_isEnabled = YES;
_datePicker = aDatePicker;
// 1. Initialize AM/PM Label
_PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_PMAMTextField setAlignment:CPCenterTextAlignment];
[_PMAMTextField setVerticalAlignment:CPCenterVerticalTextAlignment];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[self addSubview:_PMAMTextField];
// 2. Initialize Number Labels (1 to 12)
_hourLabels = [CPArray array];
var middleHandSize = [_datePicker valueForThemeAttribute:@"middle-hand-size"],
minuteHandSize = [_datePicker valueForThemeAttribute:@"minute-hand-size"],
hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"],
secondHandSize = [_datePicker valueForThemeAttribute:@"second-hand-size"];
for (var i = 1; i <= 12; i++)
{
var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[label setStringValue:String(i)];
[label setAlignment:CPCenterTextAlignment];
[label setVerticalAlignment:CPCenterVerticalTextAlignment];
[self addSubview:label];
[_hourLabels addObject:label];
}
// We use layer to make the rotation possible
_hourHandLayer = [[HandLayer alloc] initWithSize:hourHandSize];
[_hourHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_hourHandLayer setAnchorPoint:CGPointMakeZero()];
[_hourHandLayer setPosition:CGPointMake(0.0, 0.0)];
// 3. Initialize Hand Views
_hourHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_minuteHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_secondHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_middleHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_minuteHandLayer = [[HandLayer alloc] initWithSize:minuteHandSize];
[_minuteHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_minuteHandLayer setAnchorPoint:CGPointMakeZero()];
[_minuteHandLayer setPosition:CGPointMake(0.0, 0.0)];
// Ensure images stretch accurately across the bounds of the image view
[_hourHandView setImageScaling:CPImageScaleAxesIndependently];
[_minuteHandView setImageScaling:CPImageScaleAxesIndependently];
[_secondHandView setImageScaling:CPImageScaleAxesIndependently];
[_middleHandView setImageScaling:CPImageScaleAxesIndependently];
_secondHandLayer = [[HandLayer alloc] initWithSize:secondHandSize];
[_secondHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_secondHandLayer setAnchorPoint:CGPointMakeZero()];
[_secondHandLayer setPosition:CGPointMake(0.0, 0.0)];
// 4. Add subviews in correct Z-Order
[self addSubview:_hourHandView];
[self addSubview:_minuteHandView];
_middleHandLayer = [[HandLayer alloc] initWithSize:middleHandSize];
[_middleHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_middleHandLayer setAnchorPoint:CGPointMakeZero()];
[_middleHandLayer setPosition:CGPointMake(0.0, 0.0)];
if ([_datePicker valueForThemeAttribute:@"clock-second-hand-over"])
{
[self addSubview:_middleHandView];
[self addSubview:_secondHandView];
}
else
{
[self addSubview:_secondHandView];
[self addSubview:_middleHandView];
}
_rootLayer = [CALayer layer];
[self setWantsLayer:YES];
[self setLayer:_rootLayer];
[_hourHandLayer setNeedsDisplay];
[_middleHandLayer setNeedsDisplay];
[_secondHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_rootLayer addSublayer:_hourHandLayer];
[_rootLayer addSublayer:_minuteHandLayer];
[_rootLayer addSublayer:_secondHandLayer];
[_rootLayer addSublayer:_middleHandLayer];
[_rootLayer setNeedsDisplay];
}
return self;
}
- (void)_initHands
{
// FIX: Using 'duplicate' prevents CPImageViews from stealing the DOM element from each other!
[_middleHandView setImage:[[_datePicker currentValueForThemeAttribute:@"middle-hand-image"] duplicate]];
[_hourHandView setImage:[[_datePicker currentValueForThemeAttribute:@"hour-hand-image"] duplicate]];
[_minuteHandView setImage:[[_datePicker currentValueForThemeAttribute:@"minute-hand-image"] duplicate]];
[_secondHandView setImage:[[_datePicker currentValueForThemeAttribute:@"second-hand-image"] duplicate]];
var font = [_datePicker currentValueForThemeAttribute:@"clock-font"],
textColor = [_datePicker currentValueForThemeAttribute:@"clock-text-color"],
shadowCol = [_datePicker currentValueForThemeAttribute:@"clock-text-shadow-color"],
shadowOff = [_datePicker currentValueForThemeAttribute:@"clock-text-shadow-offset"];
if (font)
[_PMAMTextField setFont:font];
if (textColor)
[_PMAMTextField setTextColor:textColor];
if (shadowCol)
[_PMAMTextField setTextShadowColor:shadowCol];
if (shadowOff)
[_PMAMTextField setTextShadowOffset:shadowOff];
var hoursFont = [_datePicker currentValueForThemeAttribute:@"clock-hours-font"],
hoursColor = [_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"],
drawsHours = [_datePicker currentValueForThemeAttribute:@"clock-draws-hours"];
for (var i = 0; i < 12; i++)
{
var label = _hourLabels[i];
[label setHidden:!drawsHours];
if (drawsHours) {
if (hoursFont) [label setFont:hoursFont];
if (hoursColor) [label setTextColor:hoursColor];
[label sizeToFit];
}
}
}
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
{
_datePickerElements = aDatePickerElements;
[_secondHandView setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
}
// MARK: Layout methods
#pragma mark -
#pragma mark Layout methods
- (void)layoutSubviews
{
[self _initHands];
if (_trackingHand)
if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle)
return;
[self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]];
[super layoutSubviews];
var dateValue = [[_datePicker dateValue] copy];
var bounds = [self bounds],
dateValue = [[_datePicker dateValue] copy];
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
_representedHours = dateValue.getHours();
_representedMinutes = dateValue.getMinutes();
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
[self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]];
[_middleHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"middle-hand-image"]];
[_hourHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"hour-hand-image"]];
[_minuteHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"minute-hand-image"]];
[_secondHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"second-hand-image"]];
_representedHours -= (_representedHourIsPM ? 12 : 0);
var bounds = [self bounds],
centerX = bounds.size.width / 2.0,
centerY = bounds.size.height / 2.0;
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
[_PMAMTextField sizeToFit];
[_PMAMTextField setFrameOrigin:CGPointMake(centerX - [_PMAMTextField frameSize].width / 2.0, centerY + 15.0)];
if ([_datePicker currentValueForThemeAttribute:@"clock-draws-hours"])
if ([_datePicker _isAmericanFormat])
{
var radius = [_datePicker currentValueForThemeAttribute:@"clock-hours-radius"] || 50.0;
for (var i = 0, angle = 60.0; i < 12; i++, angle -= 30.0)
{
var label = _hourLabels[i],
size = [label frameSize],
x = centerX + radius * COS(angle * RADIANS) - size.width / 2.0,
y = centerY - radius * SIN(angle * RADIANS) - size.height / 2.0;
if (dateValue.getHours() > 11)
[_PMAMTextField setStringValue:@"PM"];
else
[_PMAMTextField setStringValue:@"AM"];
[label setFrameOrigin:CGPointMake(x, y)];
}
[_PMAMTextField sizeToFit];
[_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)];
[_PMAMTextField setHidden:NO];
}
else
{
[_PMAMTextField setHidden:YES];
}
var centerView = function(view, size) {[view setFrame:CGRectMake(centerX - size.width / 2.0, centerY - size.height / 2.0, size.width, size.height)];
};
[_hourHandLayer setRotationRadians:[self _hourPositionRadianForDate:dateValue]];
[_minuteHandLayer setRotationRadians:[self _minutePositionRadianForDate:dateValue]];
[_secondHandLayer setRotationRadians:[self _secondPositionRadianForDate:dateValue]];
var hSize = [_datePicker currentValueForThemeAttribute:@"hour-hand-size"] || CGSizeMake(4, 64),
mSize = [_datePicker currentValueForThemeAttribute:@"minute-hand-size"] || CGSizeMake(4, 96),
sSize = [_datePicker currentValueForThemeAttribute:@"second-hand-size"] || CGSizeMake(4, 96),
midSize = [_datePicker currentValueForThemeAttribute:@"middle-hand-size"] || CGSizeMake(8, 8);
[_PMAMTextField setEnabled:_isEnabled];
[_hourHandLayer setEnabled:_isEnabled];
[_middleHandLayer setEnabled:_isEnabled];
[_secondHandLayer setEnabled:_isEnabled];
[_minuteHandLayer setEnabled:_isEnabled];
centerView(_hourHandView, hSize);
centerView(_minuteHandView, mSize);
centerView(_secondHandView, sSize);
centerView(_middleHandView, midSize);
// Check if we have to display the hand second
if (([_datePicker datePickerElements] & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)
[_secondHandLayer setHidden:NO];
else
[_secondHandLayer setHidden:YES];
[self _updateHands];
[_rootLayer setNeedsDisplay];
}
// Applies Pure CSS Transforms to rotate the elements natively in the browser
- (void)_rotateView:(CPView)view byAngle:(float)radians
#pragma mark -
#pragma mark Accessors
- (float)_hourPositionRadianForDate:(CPDate)aDate
{
#if PLATFORM(DOM)
var style = view._DOMElement.style;
style[CPBrowserStyleProperty("transformOrigin")] = "50% 50%";
style[CPBrowserStyleProperty("transform")] = "rotate(" + radians + "rad)";
#endif
var hours = aDate.getHours() + aDate.getMinutes() / 60;
return (360 * hours / 12) * RADIANS;
}
- (void)_updateHands
- (float)_secondPositionRadianForDate:(CPDate)aDate
{
_hourAngle = (360.0 * (_representedHours + _representedMinutes / 60.0) / 12.0) * RADIANS;
_minuteAngle = (360.0 * (_representedMinutes + _representedSeconds / 60.0) / 60.0) * RADIANS;
_secondAngle = (360.0 * _representedSeconds / 60.0) * RADIANS;
[self _rotateView:_hourHandView byAngle:_hourAngle];
[self _rotateView:_minuteHandView byAngle:_minuteAngle];
[self _rotateView:_secondHandView byAngle:_secondAngle];
return (360 * aDate.getSeconds() / 60) * RADIANS;
}
// MARK: Accessors
- (float)_minutePositionRadianForDate:(CPDate)aDate
{
var minutes = aDate.getMinutes() + aDate.getSeconds() / 60;
return (360 * minutes / 60) * RADIANS;
}
- (void)setEnabled:(BOOL)shouldEnable
{
@@ -277,195 +205,109 @@ _CPDatePickerClockSeconds = 3;
return;
_isEnabled = shouldEnable;
[self setNeedsLayout];
}
// MARK: Mouse actions
// Since we rotate using Pure CSS, Cappuccino's `convertPoint:` doesn't know about it.
// So we use standard Trigonometry to perfectly hit-test the rotated hands!
- (BOOL)_hitTestHandWithSize:(CGSize)size angle:(float)radians atPoint:(CGPoint)aPoint
{
var bounds = [self bounds],
centerX = bounds.size.width / 2.0,
centerY = bounds.size.height / 2.0;
// 1. Move point to center
var tx = aPoint.x - centerX,
ty = aPoint.y - centerY;
// 2. Rotate point backwards by the angle of the hand
var cosA = COS(-radians),
sinA = SIN(-radians),
rx = tx * cosA - ty * sinA,
ry = tx * sinA + ty * cosA;
// 3. Test if point is within the unrotated hand's rectangle
// The visual needle is in the top half of the hand's box (y from -h/2 to 0)
var w2 = size.width / 2.0,
h2 = size.height / 2.0;
if (rx >= -w2 && rx <= w2 && ry >= -h2 && ry <= 0)
return YES;
return NO;
}
- (void)mouseDown:(CPEvent)anEvent
{
if (!_isEnabled)
return;
var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil];
var sSize = [_datePicker currentValueForThemeAttribute:@"second-hand-size"] || CGSizeMake(4, 96),
mSize = [_datePicker currentValueForThemeAttribute:@"minute-hand-size"] || CGSizeMake(4, 96),
hSize = [_datePicker currentValueForThemeAttribute:@"hour-hand-size"] || CGSizeMake(4, 64);
if (![_secondHandView isHidden] && [self _hitTestHandWithSize:sSize angle:_secondAngle atPoint:currentLocation])
{
_currentHandView = _secondHandView;
_currentHand = _CPDatePickerClockSeconds;
_currentRepresentedValue = _representedSeconds;
_currentValueShift = 0;
_numberOfUnits = 60;
}
else if (![_minuteHandView isHidden] && [self _hitTestHandWithSize:mSize angle:_minuteAngle atPoint:currentLocation])
{
_currentHandView = _minuteHandView;
_currentHand = _CPDatePickerClockMinutes;
_currentRepresentedValue = _representedMinutes;
_currentValueShift = _representedSeconds / 60;
_numberOfUnits = 60;
}
else if (![_hourHandView isHidden] && [self _hitTestHandWithSize:hSize angle:_hourAngle atPoint:currentLocation])
{
_currentHandView = _hourHandView;
_currentHand = _CPDatePickerClockHours;
_currentRepresentedValue = _representedHours;
_currentValueShift = _representedMinutes / 60;
_numberOfUnits = 12;
}
else
{
_currentHandView = nil;
_currentHand = CPNotFound;
_currentRepresentedValue = CPNotFound;
_currentValueShift = CPNotFound;
_numberOfUnits = CPNotFound;
}
if (_currentHandView)
[self trackMouse:anEvent];
}
- (BOOL)tracksMouseOutsideOfFrame
{
return YES;
}
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
_trackingHand = YES;
return YES;
}
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
{
var dx = aPoint.x - _bounds.size.width / 2,
dy = _bounds.size.height / 2 - aPoint.y,
angle = (PI_2 - ATAN2(dy,dx) + PI2) % PI2,
value = ROUND(angle * _numberOfUnits / PI2 - _currentValueShift) % _numberOfUnits;
if (value !== _currentRepresentedValue)
{
var movedForward = (_currentRepresentedValue > _numberOfUnits * 3/4) && (value < _numberOfUnits / 4),
movedBackward = (_currentRepresentedValue < _numberOfUnits / 4) && (value > _numberOfUnits * 3/4),
dateValue = [[_datePicker dateValue] copy];
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
switch (_currentHand)
{
case _CPDatePickerClockHours:
if (movedForward || movedBackward)
{
_representedHourIsPM = !_representedHourIsPM;
if (movedForward && !_representedHourIsPM)
dateValue.setDate(dateValue.getDate() + 1);
else if (movedBackward && _representedHourIsPM)
dateValue.setDate(dateValue.getDate() - 1);
}
dateValue.setHours(value + (_representedHourIsPM ? 12 : 0));
break;
case _CPDatePickerClockMinutes:
if (movedForward)
// Hours++
dateValue.setHours(dateValue.getHours() + 1);
else if (movedBackward)
// Hours--
dateValue.setHours(dateValue.getHours() - 1);
dateValue.setMinutes(value);
break;
case _CPDatePickerClockSeconds:
if (movedForward)
// Minutes++
dateValue.setMinutes(dateValue.getMinutes() + 1);
else if (movedBackward)
// Minutes--
dateValue.setMinutes(dateValue.getMinutes() - 1);
dateValue.setSeconds(value);
break;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
_representedHours = dateValue.getHours();
_representedMinutes = dateValue.getMinutes();
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
_representedHours -= (_representedHourIsPM ? 12 : 0);
switch (_currentHand) {
case _CPDatePickerClockHours:
_currentRepresentedValue = _representedHours;
break;
case _CPDatePickerClockMinutes:
_currentRepresentedValue = _representedMinutes;
break;
case _CPDatePickerClockSeconds:
_currentRepresentedValue = _representedSeconds;
break;
}
[self _updateHands];
}
return YES;
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
{
_trackingHand = NO;
// FIXME: This is a workaround for an apparent bug in CALayer.
// Without pumping the event loop, the sublayers of _rootLayer
// (the hands) are not redrawn until an event occurs.
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
}
@end
@implementation HandLayer : CALayer
{
BOOL _isEnabled @accessors(setter=setEnabled:, getter=isEnabled);
CPImage _image;
CALayer _imageLayer;
float _rotationRadians;
}
#pragma mark -
#pragma mark Init methods
- (id)initWithSize:(CGSize)aSize
{
if (self = [super init])
{
_isEnabled = YES;
_imageLayer = [CALayer layer];
_rotationRadians = 0;
[_imageLayer setDelegate:self];
[_imageLayer setBounds:CGRectMake(0.0, 0.0, aSize.width, aSize.height)];
[self addSublayer:_imageLayer];
}
return self;
}
#pragma mark -
#pragma mark Setter Getter methods
/*!
Set the bounds of the layer. The imageLayer will be at the center of this bounds.
*/
- (void)setBounds:(CGRect)aRect
{
[super setBounds:aRect];
[_imageLayer setPosition:CGPointMake(CGRectGetMidX(aRect), CGRectGetMidY(aRect))];
}
- (void)setImage:(CPImage)anImage
{
if (_image === anImage)
return;
if ([anImage isKindOfClass:[_CPCibCustomResource class]])
_image = [anImage imageFromCoder:nil];
else
_image = anImage;
[_imageLayer setNeedsDisplay];
}
- (void)setRotationRadians:(float)radians
{
if (_rotationRadians === radians)
return;
_rotationRadians = radians;
[_imageLayer setAffineTransform:CGAffineTransformScale(
CGAffineTransformMakeRotation(_rotationRadians),
1.0, 1.0)];
}
- (void)setEnabled:(BOOL)shouldEnable
{
shouldEnable = !!shouldEnable;
if (_isEnabled === shouldEnable)
return;
_isEnabled = shouldEnable;
[self setNeedsDisplay];
[_imageLayer setNeedsDisplay];
}
- (void)imageDidLoad:(CPImage)anImage
{
[_imageLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer)aLayer inContext:(CGContext)aContext
{
if ([_image loadStatus] != CPImageLoadStatusCompleted)
[_image setDelegate:self];
else
CGContextDrawImage(aContext, [aLayer bounds], _image);
}
@end
-280
View File
@@ -1,280 +0,0 @@
/* _CPDatePickerDayView.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* 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/Foundation.j>
@import "_CPDatePickerDayViewTextField.j"
@class CPDatePicker
@implementation _CPDatePickerDayView : CPControl
{
CPDate _date @accessors(property=date);
BOOL _isDisabled;
BOOL _isHighlighted;
BOOL _isSelected;
CPDatePicker _datePicker;
CPTextField _textField;
CPInteger _dayInWeek @accessors(property=dayInWeek);
BOOL _firstSelected @accessors(property=firstSelected);
BOOL _lastSelected @accessors(property=lastSelected);
}
// MARK: Init methods
/*! Create a new instance of _CPDatePickerDayView
@param aFrame
@param aDatePicker
@return a new instance of _CPDatePickerDayView
*/
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
{
if (self = [super initWithFrame:aFrame])
{
[self setHitTests:NO];
_datePicker = aDatePicker;
// FIXME: Beginning with Aristo3, the text field is directly themed based on a new class _CPDatePickerDayViewTextField
if ([self isCSSBased])
{
_textField = [[_CPDatePickerDayViewTextField alloc] initWithFrame:aFrame];
}
else
{
_textField = [[CPTextField alloc] initWithFrame:aFrame];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-alignment"] forThemeAttribute:@"alignment"];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-vertical-alignment"] forThemeAttribute:@"vertical-alignment"];
var contentInset = [_datePicker valueForThemeAttribute:@"tile-content-inset"];
if (contentInset)
[_textField setValue:contentInset forThemeAttribute:@"content-inset"];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateSelected] forThemeAttribute:@"font" inState:CPThemeStateSelected];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-color" inState:CPThemeStateSelected];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateHighlighted] forThemeAttribute:@"font" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-color" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
}
[self addSubview:_textField];
[self setNeedsLayout];
}
return self;
}
// MARK: -
// MARK: Theme methods
/*! Set a theme
*/
- (BOOL)setThemeState:(ThemeState)aState
{
[_textField setThemeState:aState];
[super setThemeState:aState];
}
/*! Unset a theme
*/
- (BOOL)unsetThemeState:(ThemeState)aState
{
[_textField unsetThemeState:aState];
[super unsetThemeState:aState];
}
// MARK: -
// MARK: Getter methods
/*! Select the tile
*/
- (void)setSelected:(BOOL)shouldBeSelected
{
if (_isSelected === shouldBeSelected)
return;
_isSelected = shouldBeSelected;
if (_isSelected)
[self setThemeState:CPThemeStateSelected];
else
[self unsetThemeState:CPThemeStateSelected];
}
/*! Disabled the tile (used for previous and next month tile)
*/
- (void)setDisabled:(BOOL)shouldBeDisabled
{
if (_isDisabled === shouldBeDisabled)
return;
_isDisabled = shouldBeDisabled;
if (_isDisabled)
[self setThemeState:CPThemeStateDisabled];
else
[self unsetThemeState:CPThemeStateDisabled];
}
- (BOOL)isDisabled
{
return _isDisabled;
}
/*! Highlight the tile (used for current day)
*/
- (void)setHighlighted:(BOOL)shouldBeHighlighted
{
if (_isHighlighted === shouldBeHighlighted)
return;
_isHighlighted = shouldBeHighlighted;
if (_isHighlighted)
[self setThemeState:CPThemeStateHighlighted];
else
[self unsetThemeState:CPThemeStateHighlighted];
}
/*! Set the stringValue of the tile
@param aStringValue
*/
- (void)setStringValue:(CPString)aStringValue
{
[_textField setStringValue:aStringValue];
}
// MARK: -
// MARK: Layout methods
/*! Layout the subviews
*/
- (void)layoutSubviews
{
if ([_datePicker isCSSBased])
{
var attributeName = @"bezel-color-calendar";
if (_isSelected)
{
if (_firstSelected && !_lastSelected)
attributeName = @"bezel-color-calendar-left";
else if (_lastSelected && !_firstSelected)
attributeName = @"bezel-color-calendar-right";
else if (!_firstSelected && !_lastSelected)
{
if (_dayInWeek == 0)
attributeName = @"bezel-color-calendar-left";
else if (_dayInWeek == 6)
attributeName = @"bezel-color-calendar-right";
else
attributeName = @"bezel-color-calendar-middle";
}
}
[self setBackgroundColor:[_datePicker valueForThemeAttribute:attributeName inState:[self themeState]]];
return;
}
var bounds = [self bounds];
[_textField sizeToFit];
[_textField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_textField frameSize].width / 2 + [_datePicker valueForThemeAttribute:@"border-width"], bounds.size.height / 2 - [_textField frameSize].height / 2)];
}
- (void)setFrame:(CGRect)aFrame
{
[super setFrame:aFrame];
[_textField setFrame:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
}
/*! Drawrect
*/
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if ([_datePicker isCSSBased])
return;
var themeState = [self themeState],
context = [[CPGraphicsContext currentContext] graphicsPort];
if (themeState.hasThemeState(CPThemeStateSelected))
{
[self setBackgroundColor:[_datePicker valueForThemeAttribute:@"bezel-color-calendar" inState:themeState]];
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:themeState]);
CGContextAddRect(context, [self bounds]);
CGContextStrokeRect(context, [self bounds]);
}
else
{
// Clear color, because the original color of a tile is handle by his superview
[self setBackgroundColor:[CPColor clearColor]];
}
}
@end
@@ -1,48 +0,0 @@
/* _CPDatePickerDayViewTextField.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* 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/Foundation.j>
@import "CPTextField.j"
@implementation _CPDatePickerDayViewTextField : CPTextField
+ (CPString)defaultThemeClass
{
return @"datePickerDayViewTextField";
}
+ (CPDictionary)themeAttributes
{
return @{
@"min-size": CGSizeMakeZero(),
@"content-inset": CGInsetMakeZero(),
@"text-color": [CPColor blackColor],
@"text-shadow-color": [CPColor clearColor],
@"text-shadow-offset": CGSizeMakeZero(),
@"font": [CPNull null],
@"vertical-alignment": CPCenterVerticalTextAlignment,
@"alignment": CPCenterTextAlignment,
};
}
@end
@@ -1,604 +0,0 @@
/* _CPDatePickerElementTextField.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* 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 "CPTextField.j"
@class CPDatePicker
@class _CPDatePickerElementView
CPDatePickerElementTextFieldBecomeFirstResponder = @"CPDatePickerElementTextFieldBecomeFirstResponder";
CPDatePickerElementTextFieldAMPMChangedNotification = @"CPDatePickerElementTextFieldAMPMChangedNotification";
// Removed hardcoded KeyCodes (CPZeroKeyCode, etc) as they are unreliable across browsers/layouts.
CPMonthDateType = 0;
CPDayDateType = 1;
CPYearDateType = 2;
CPHourDateType = 3;
CPSecondDateType = 4;
CPMinuteDateType = 5;
CPAMPMDateType = 6;
/*! An element textField
*/
@implementation _CPDatePickerElementTextField : CPTextField
{
_CPDatePickerElementTextField _nextTextField @accessors(property=nextTextField);
_CPDatePickerElementTextField _previousTextField @accessors(property=previousTextField);
_CPDatePickerElementView _datePickerElementView @accessors(property=datePickerElementView);
CPDatePicker _datePicker @accessors(setter=setDatePicker:);
int _dateType @accessors(getter=dateType);
int _maxNumber @accessors(getter=maxNumber);
int _minNumber @accessors(getter=minNumber);
BOOL _firstEvent;
CPTimer _timerEdition;
}
+ (CPString)defaultThemeClass
{
return @"datePickerElementTextField";
}
+ (CPDictionary)themeAttributes
{
return @{
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
@"bezel-color": [CPNull null],
@"min-size": CGSizeMakeZero()
};
}
- (id)init
{
if (self = [super init])
{
_firstEvent = YES;
}
return self;
}
/*! @ignore */
- (BOOL)acceptsFirstResponder
{
return [_datePicker isEnabled];
}
/*! Set the dateType of the textField
*/
- (void)setDateType:(int)aDateType
{
_dateType = aDateType;
switch (aDateType)
{
case CPMonthDateType:
_minNumber = 1;
_maxNumber = 12;
break;
case CPDayDateType:
_minNumber = 1;
_maxNumber = 31;
break;
case CPYearDateType:
_minNumber = 0;
_maxNumber = 9999;
break;
case CPHourDateType:
_minNumber = 0;
_maxNumber = 23;
break;
case CPSecondDateType:
_minNumber = 0;
_maxNumber = 59;
break;
case CPMinuteDateType:
_minNumber = 0;
_maxNumber = 59;
break;
}
}
/*! Return the maxNumber of the textField
*/
- (int)maxNumber
{
if (_dateType == CPDayDateType)
return [[_datePicker dateValue] _daysInMonth];
return _maxNumber;
}
/*! Return the maxNumber of the textField depending of the maxDate
*/
- (int)_maxNumberWithMaxDate
{
var maxDate = [_datePicker maxDate],
date = [_datePicker dateValue];
if (maxDate)
{
switch (_dateType)
{
case CPMonthDateType:
if (maxDate.getFullYear() == date.getFullYear())
return maxDate.getMonth();
break;
case CPDayDateType:
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth())
return maxDate.getDate();
break;
case CPYearDateType:
return maxDate.getFullYear();
case CPHourDateType:
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate())
return maxDate.getHours();
break;
case CPSecondDateType:
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours() && maxDate.getMinutes() == date.getMinutes())
return maxDate.getSeconds();
break;
case CPMinuteDateType:
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours())
return maxDate.getMinutes();
break;
}
}
return _maxNumber;
}
/*! Set the stringValue of the textField. This is going to check if there is 2 or 4 letters. If not it adds a space. Check also the maxDate
It's called when the user is editing with the keyboard
@param aStringValue a CPString
*/
- (void)setValueForKeyEvent:(CPEvent)anEvent
{
var keyCode = [anEvent keyCode],
characters = [anEvent characters];
// Check if the event is a deletion
var isDelete = (keyCode === CPDeleteKeyCode || keyCode === CPDeleteForwardKeyCode);
// Check if the event is a numeric input.
// By testing the character string against a regex, we support num-pads and
// international keyboards correctly, rather than relying on keyCode ranges.
var isNumeric = (characters && [characters length] > 0 && /^[0-9]$/.test(characters));
// If it is neither a delete command nor a digit, we ignore it.
if (!isDelete && !isNumeric)
return;
var newValue = [self stringValue].replace(/\s/g, ''),
length = [newValue length];
if (isDelete)
{
[_timerEdition invalidate];
_timerEdition = nil;
// Ensure we don't substring if length is 0
if (length > 0)
newValue = [newValue substringToIndex:(length - 1)];
}
else
{
// Since isNumeric is true, characters is a valid digit string
var eventKeyValue = characters;
if (!_timerEdition)
{
_timerEdition = [CPTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(_timerKeyEvent:) userInfo:nil repeats:NO];
if (_firstEvent || !length)
newValue = eventKeyValue;
else
newValue = parseInt(newValue).toString() + eventKeyValue;
}
else
{
var newFireDate = [CPDate date];
newFireDate.setSeconds(newFireDate.getSeconds() + 2);
[_timerEdition setFireDate:newFireDate];
newValue = parseInt(newValue).toString() + eventKeyValue;
}
}
// Safety check for NaN before comparison
var numericValue = parseInt(newValue);
if (isNaN(numericValue))
numericValue = 0;
if (numericValue > [self _maxNumberWithMaxDate] || ([_datePicker _isAmericanFormat] && _dateType == CPHourDateType && numericValue > 12))
return;
_firstEvent = NO;
[super setObjectValue:newValue];
}
/*!
End of the timer
*/
- (void)_timerKeyEvent:(id)sender
{
var stringValue = [self stringValue];
_timerEdition = nil;
if ([stringValue length])
{
if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType)
{
var isAMHour = [[self superview] _isAMHour];
if (!isAMHour && stringValue != 12)
stringValue = parseInt(stringValue) + 12;
if (stringValue == 12 && !isAMHour)
stringValue = 12;
else if (stringValue == 12)
stringValue = 0;
}
[self setObjectValue:stringValue];
}
}
/*!
We force to end the timer
*/
- (void)_invalidTimer
{
if (_timerEdition)
{
[_timerEdition invalidate];
_timerEdition = nil;
}
}
/*!
We force to end the timer and to update the objectValue of the datePicker
*/
- (void)_endEditing
{
if (_timerEdition)
[_timerEdition invalidate];
_timerEdition = nil;
var objectValue = [self stringValue];
if (![objectValue length])
objectValue = [self objectValue];
if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType)
{
var isAMHour = [[self superview] _isAMHour];
if (!isAMHour && objectValue != 12)
objectValue = parseInt(objectValue) + 12;
if (objectValue == 12 && !isAMHour)
objectValue = 12;
else if (objectValue == 12)
objectValue = 0;
}
[self setObjectValue:objectValue];
}
/*! Set the stringValue of the TextField. Add some zeros of there isn't 2/4 letters in the value. It's called at the end of the editing process
@param aStringValue a CPString
*/
- (void)setStringValue:(CPString)aStringValue
{
if (_dateType == CPYearDateType)
{
while ([aStringValue length] < 4)
aStringValue = "0" + aStringValue;
}
else if (_dateType != CPAMPMDateType)
{
if (_dateType == CPHourDateType && [_datePicker _isAmericanFormat])
{
var value = parseInt(aStringValue);
if (value == 0)
value = 12;
else if (value > 12)
value = value - 12;
aStringValue = value.toString();
}
while ([aStringValue length] < 2)
{
if (_dateType == CPSecondDateType || _dateType == CPMinuteDateType)
aStringValue = @"0" + aStringValue;
else
aStringValue = @" " + aStringValue;
}
}
[super setObjectValue:aStringValue];
}
/*! Set the objectValue. This will update the dateValue of the datePicker also. It's called with the binding of the stepper or arrows
This is not going to update the objectValue of the control !!! It updates the dateValue of the datePicker who's going to update the datePickerTextField if necessary
It's a bit tricky
@param aObjectValue
*/
- (void)setObjectValue:(id)anObjectValue
{
var dateValue = [[_datePicker dateValue] copy],
lengthString = [[self stringValue] length],
objectValue = parseInt(anObjectValue);
switch (_dateType)
{
case CPMonthDateType:
if (objectValue == 0 || !lengthString)
{
[self setStringValue:(dateValue.getMonth() + 1).toString()];
return;
}
var dateNextMonth = [dateValue copy];
dateNextMonth.setDate(1);
dateNextMonth.setMonth(parseInt(anObjectValue) - 1);
var numberDayNextMonth = [dateNextMonth _daysInMonth];
if (numberDayNextMonth < [dateValue _daysInMonth] && dateValue.getDate() > numberDayNextMonth)
[_datePickerElementView setDayDateValue:numberDayNextMonth.toString()];
[super setObjectValue:objectValue];
break;
case CPDayDateType:
if (objectValue == 0 || !lengthString)
{
[self setStringValue:dateValue.getDate().toString()];
return;
}
// if we enter a day that is too high for the current month
// we need to increase the month by one
// if we do not do this, the user input would be silently reset
// very poor user experience
if (parseInt(anObjectValue, 10) > [dateValue _daysInMonth])
{
[_datePickerElementView._textFieldMonth setIntValue:(dateValue.getMonth() + 2)];
[super setObjectValue:objectValue];
return;
}
[super setObjectValue:objectValue];
break;
case CPYearDateType:
if (objectValue == 0 || !lengthString)
{
[self setStringValue:dateValue.getFullYear().toString()];
return;
}
[super setObjectValue:objectValue];
break;
case CPHourDateType:
if (!lengthString)
{
[self setStringValue:dateValue.getHours().toString()];
return;
}
[super setObjectValue:objectValue];
break;
case CPSecondDateType:
if (!lengthString)
{
[self setStringValue:dateValue.getSeconds().toString()];
return;
}
[super setObjectValue:objectValue];
break;
case CPMinuteDateType:
if (!lengthString)
{
[self setStringValue:dateValue.getMinutes().toString()];
return;
}
[super setObjectValue:objectValue];
break;
}
var newDateValue = [_datePickerElementView dateValue],
timeZone = [_datePicker timeZone];
if (timeZone)
{
var secondsFromGMT = [timeZone secondsFromGMTForDate:newDateValue],
secondsFromGMTTimeZone = [timeZone secondsFromGMT];
newDateValue.setSeconds(newDateValue.getSeconds() + secondsFromGMT - secondsFromGMTTimeZone);
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:newDateValue timeInterval:[_datePicker timeInterval]];
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
// MARK: -
// MARK: Mouse event
/*! Mouse down event. Launch a notification to notif the new first responder textField
*/
- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEnabled])
return;
[super mouseDown:anEvent];
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldBecomeFirstResponder object:[[self superview] superview] userInfo:[CPDictionary dictionaryWithObject:self forKey:@"textField"]];
}
// MARK: -
// MARK: Theme functions
/*! Set the theme CPThemeStateSelected
*/
- (void)makeSelectable
{
[self setThemeState:CPThemeStateSelected];
[_datePicker setThemeState:CPThemeStateEditing];
}
/*! Unsert the theme CPThemeStateSelected
*/
- (void)makeDeselectable
{
_firstEvent = YES;
[self unsetThemeState:CPThemeStateSelected];
[_datePicker unsetThemeState:CPThemeStateEditing];
}
// MARK: -
// MARK: Override
/*!
We override this method to get all the time the good width
*/
- (CGSize)_minimumFrameSize
{
var frameSize = [self frameSize],
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
minSize = [self currentValueForThemeAttribute:@"min-size"],
maxSize = [self currentValueForThemeAttribute:@"max-size"],
lineBreakMode = [self lineBreakMode],
text = (_dateType == CPYearDateType) ? @"0000" : (_dateType == CPMonthDateType) ? @"10" : @"00",
textSize = CGSizeMakeCopy(frameSize),
font = [self currentValueForThemeAttribute:@"font"];
textSize.width -= contentInset.left + contentInset.right;
textSize.height -= contentInset.top + contentInset.bottom;
if (_dateType == CPAMPMDateType)
text = [self stringValue];
if (frameSize.width !== 0 &&
![self isBezeled] &&
(lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
{
textSize = [text sizeWithFont:font inWidth:textSize.width];
}
else
{
textSize = [text sizeWithFont:font];
// Account for possible fractional pixels at right edge
textSize.width += 1;
}
// Account for possible fractional pixels at bottom edge
textSize.height += 1;
frameSize.height = textSize.height + contentInset.top + contentInset.bottom;
if ([self isBezeled])
{
frameSize.height = MAX(frameSize.height, minSize.height);
if (maxSize.width > 0.0)
frameSize.width = MIN(frameSize.width, maxSize.width);
if (maxSize.height > 0.0)
frameSize.height = MIN(frameSize.height, maxSize.height);
}
else
frameSize.width = textSize.width + contentInset.left + contentInset.right;
frameSize.width = MAX(frameSize.width, minSize.width);
return frameSize;
}
- (CGRect)bezelRectForBounds:(CGRect)bounds
{
return CGRectMakeCopy(bounds);
}
@end
// MARK: -
@implementation _CPDatePickerElementSeparator : CPTextField
+ (CPString)defaultThemeClass
{
return @"datePickerElementSeparator";
}
+ (CPDictionary)themeAttributes
{
return @{
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
@"min-size": CGSizeMakeZero()
};
}
@end
File diff suppressed because it is too large Load Diff
@@ -1,306 +0,0 @@
/* _CPDatePickerHeaderView.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* 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/Foundation.j>
@import "CPControl.j"
@import "CPTextField.j"
@import "CPButton.j"
@class CPDatePicker
var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"],
CPShortWeekDayNameArrayUS = [@"Su", @"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa"],
CPShortWeekDayNameArrayFr = [@"L", @"M", @"M", @"J", @"V", @"S", @"D"],
CPShortWeekDayNameArrayDe = [@"M", @"D", @"M", @"D", @"F", @"S", @"S"],
CPShortWeekDayNameArrayEs = [@"L", @"M", @"X", @"J", @"V", @"S", @"D"],
CPShortMonthNameArrayEn = [@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"],
CPShortMonthNameArrayFr = [@"janv.", String.fromCharCode(102, 233, 118, 46), @"mars", @"apr.", @"mai", @"juin", @"juil.", String.fromCharCode(97, 111, 251, 116), @"sept.", @"oct.", @"nov.", String.fromCharCode(100, 233, 99, 46)],
CPShortMonthNameArrayDe = [@"Jan", @"Feb", String.fromCharCode(77, 228, 114), @"Apr", @"Mai", @"Jun", @"Jul", @"Aug", @"Sep", @"Okt", @"Nov", @"Dez"],
CPShortMonthNameArrayEs = [@"ene", @"feb", @"mar", @"abr", @"may", @"jun", @"jul", @"ago", @"sep", @"oct", @"nov", @"dic"];
@implementation _CPDatePickerHeaderView : CPControl
{
CPArray _dayLabels;
CPArray _monthNames;
CPButton _nextButton;
CPButton _previousButton;
CPButton _currentButton;
CPDatePicker _datePicker;
CPDate _date;
CPTextField _title;
}
// MARK: Init methods
/*! Init a new instance of _CPDatePickerHeaderView
@param aFrame
@param aDatePicker
@return a new instance of _CPDatePickerHeaderView
*/
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate
{
self = [super initWithFrame:aFrame];
if (self)
{
_datePicker = aDatePicker;
// Title
_title = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[self addSubview:_title];
_dayLabels = [CPArray array];
// Days
for (var i = 0, count = [[self _dayNames] count]; i < count; i++)
{
var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[label setValue:[_datePicker valueForThemeAttribute:@"tile-text-alignment"] forThemeAttribute:@"alignment"];
var contentInset = [_datePicker valueForThemeAttribute:@"tile-content-inset"];
if (contentInset)
[label setValue:contentInset forThemeAttribute:@"content-inset"];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[_dayLabels addObject:label];
[self addSubview:label];
}
// Arrows
var size = [_datePicker valueForThemeAttribute:@"previous-button-size"];
_previousButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[_previousButton setButtonType:CPMomentaryChangeButton];
[_previousButton setBordered:NO];
[_previousButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-left"]];
[_previousButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-left-highlighted"]];
[self addSubview:_previousButton];
size = [_datePicker valueForThemeAttribute:@"next-button-size"];
_nextButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[_nextButton setButtonType:CPMomentaryChangeButton];
[_nextButton setBordered:NO];
[_nextButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-right"]];
[_nextButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-right-highlighted"]];
[self addSubview:_nextButton];
size = [_datePicker valueForThemeAttribute:@"current-button-size"];
_currentButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[_currentButton setButtonType:CPMomentaryChangeButton];
[_currentButton setBordered:NO];
[_currentButton setImage:[_datePicker valueForThemeAttribute:@"circle-image"]];
[_currentButton setAlternateImage:[_datePicker valueForThemeAttribute:@"circle-image-highlighted"]];
[self addSubview:_currentButton];
[_previousButton setTarget:aDelegate];
[_previousButton setAction:@selector(_clickArrowPrevious:)];
[_previousButton setContinuous:YES];
[_nextButton setTarget:aDelegate];
[_nextButton setAction:@selector(_clickArrowNext:)];
[_nextButton setContinuous:YES];
[_currentButton setTarget:aDelegate];
[_currentButton setAction:@selector(_currentMonth:)];
[self setNeedsLayout];
}
return self;
}
// MARK: -
// MARK: Getter Setter methods
/*! Return the day names depending on the CPLocale of the datePicker
@return an array
*/
- (CPArray)_dayNames
{
switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode])
{
case @"en":
// Check if it's in the american format. If yes the week will begin the sunday
if ([_datePicker _isAmericanFormat])
return CPShortWeekDayNameArrayUS;
else
return CPShortWeekDayNameArrayEn;
break;
case @"es":
return CPShortWeekDayNameArrayEs;
break;
case @"de":
return CPShortWeekDayNameArrayDe;
break;
case @"fr":
return CPShortWeekDayNameArrayFr;
break;
default:
return CPShortWeekDayNameArrayEn;
break;
}
}
/*! Return the month names depending on the CPLocale of the datePicker
@return an array
*/
- (CPArray)_monthNames
{
switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode])
{
case @"en":
return CPShortMonthNameArrayEn;
break;
case @"es":
return CPShortMonthNameArrayEs;
break;
case @"de":
return CPShortMonthNameArrayDe;
break;
case @"fr":
return CPShortMonthNameArrayFr;
break;
default:
return CPShortMonthNameArrayEn;
break;
}
}
/*! Set the monthDate of the header
@aMonthDate the new monthDate
*/
- (void)setMonthForDate:(CPDate)aMonthDate
{
_date = aMonthDate;
[self setNeedsLayout];
}
/*! Set enabled
@param aBoolean
*/
- (void)setEnabled:(BOOL)aBoolean
{
[_previousButton setEnabled:aBoolean];
[_nextButton setEnabled:aBoolean];
[_currentButton setEnabled:aBoolean];
[_dayLabels makeObjectsPerformSelector:@selector(setEnabled:) withObject:aBoolean];
[_title setEnabled:aBoolean];
}
// MARK: -
// MARK: Layout methods
/*! Layout the subviews
*/
- (void)layoutSubviews
{
var bounds = [self bounds],
dayNames = [self _dayNames],
width = CGRectGetWidth(bounds),
buttonInset = [_datePicker valueForThemeAttribute:@"arrow-inset"],
numberOfLabels = [_dayLabels count],
labelWidth = width / numberOfLabels,
sizeButtonLeft = [[_datePicker valueForThemeAttribute:@"arrow-image-left"] size],
sizeButtonRight = [[_datePicker valueForThemeAttribute:@"arrow-image-right"] size],
sizeButtonCircle = [[_datePicker valueForThemeAttribute:@"circle-image"] size],
sizeTileWidth = [_datePicker valueForThemeAttribute:@"size-tile"].width,
titleInset = [_datePicker valueForThemeAttribute:@"title-inset"],
dayLabelInset = [_datePicker valueForThemeAttribute:@"day-label-inset"];
// Arrows
[_nextButton setFrame:CGRectMake(width - [_nextButton frameSize].width - buttonInset.right, buttonInset.top, sizeButtonRight.width, sizeButtonRight.height)];
[_currentButton setFrame:CGRectMake(CGRectGetMinX([_nextButton frame]) - sizeButtonCircle.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonCircle.width, sizeButtonCircle.height)];
[_previousButton setFrame:CGRectMake(CGRectGetMinX([_currentButton frame]) - sizeButtonLeft.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonLeft.width, sizeButtonLeft.height)];
var firstDayTileX;
// Weekday label
for (var i = 0; i < numberOfLabels; i++)
{
var dayLabel = _dayLabels[i];
[dayLabel setStringValue:dayNames[i]];
if (dayLabelInset) // Beginning with Aristo3
{
var thisWidth = ROUND((i+1) * sizeTileWidth) - ROUND(i * sizeTileWidth);
[dayLabel sizeToFit];
[dayLabel setFrame:CGRectMake(dayLabelInset.left + ROUND(i * sizeTileWidth), dayLabelInset.top, thisWidth, [dayLabel frameSize].height)];
}
else
{
[dayLabel sizeToFit];
[dayLabel setFrameOrigin:CGPointMake(sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2, 23)];
if (i == 0)
firstDayTileX = sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2;
}
}
// Title
[_title setStringValue:[CPString stringWithFormat:@"%s %i", [self _monthNames][_date.getMonth()], _date.getFullYear()]];
[_title sizeToFit];
if (titleInset) // Beginning with Aristo3
[_title setFrameOrigin:CGPointMake(titleInset.left, titleInset.top)];
else
[_title setFrameOrigin:CGPointMake(firstDayTileX, 6)];
}
@end
@@ -1,636 +0,0 @@
/* _CPDatePickerMonthView.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* 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/Foundation.j>
@import "CPControl.j"
@import "_CPDatePickerDayView.j"
@class CPDatePicker
@global CPSingleDateMode
@global CPRangeDateMode
@implementation _CPDatePickerMonthView : CPControl
{
BOOL _isMonthJustChanged;
CPArray _dayTiles;
CPDate _clickDate;
CPDate _dragDate;
CPDate _date;
CPDate _previousMonth @accessors(property=previousMonth);
CPDate _nextMonth @accessors(property=nextMonth);
CPDatePicker _datePicker;
CPEvent _eventDragged;
CPTimer _timerMonth;
id _delegate;
int _indexDayTile;
}
// MARK: Init methods
/*! Init a _CPDatePickerMonthView
@param aFrame
@param aDatePicker
@return a new _CPDatePickerMonthView
*/
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate
{
if (self = [super initWithFrame:aFrame])
{
_delegate = aDelegate;
_isMonthJustChanged = NO;
_indexDayTile = -1;
_datePicker = aDatePicker;
_dayTiles = [CPArray array];
// Create tiles
for (var i = 0; i < 42; i++)
{
var dayView = [[_CPDatePickerDayView alloc] initWithFrame:CGRectMakeZero() withDatePicker:_datePicker];
[self addSubview:dayView];
[_dayTiles addObject:dayView];
}
[self setNeedsLayout];
}
return self;
}
// MARK: -
// MARK: Getter Setter methods
/*! Set the monthDate of the component
@param aDate
*/
- (void)setMonthForDate:(CPDate)aDate
{
if (_dragDate)
{
if (_dragDate.getMonth() != _date.getMonth())
_isMonthJustChanged = YES;
_date = [_dragDate copy];
}
else
{
_date = [aDate copy];
}
if (![aDate isEqualToDate:[CPDate distantFuture]])
{
// Reset the date to the first day of the month & midnight
_date.setDate(1);
[_date _resetToMidnight];
// There must be a better way to do this.
var firstDay = [_date copy];
firstDay.setDate(1);
// Set the previous and next month date. This is usefull for the tile of the next/previous month
_previousMonth = new Date(firstDay.getTime() - 86400000);
_previousMonth.setDate(1);
_nextMonth = new Date(firstDay.getTime() + (([_date _daysInMonth] + 1) * 86400000));
_nextMonth.setDate(1);
}
[self reloadData];
if (_isMonthJustChanged)
{
var dayTile = [_dayTiles objectAtIndex:_indexDayTile];
if ([dayTile date].getMonth() == _date.getMonth())
{
[self mouseDragged:_eventDragged];
}
else
{
if ([dayTile date].getMonth() - _date.getMonth() == 1 || [dayTile date].getFullYear() - _date.getFullYear() == 1)
_timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerNextMonthEvent:) userInfo:nil repeats:NO];
else
_timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerPreviousMonthEvent:) userInfo:nil repeats:NO];
}
}
}
- (void)monthDate
{
return _date;
}
/*! Return the size of a tile
*/
- (CGSize)tileSize
{
return [_datePicker valueForThemeAttribute:@"size-tile"];
}
/*! Return the first index day of the month
*/
- (int)startOfWeekForDate:(CPDate)aDate
{
var day = aDate.getDay();
// American people begins the week the sunday
if (![_datePicker _isAmericanFormat])
return (day + 6) % 7;
return day;
}
/*! Set enabled
@param aBoolean
*/
- (void)setEnabled:(BOOL)aBoolean
{
[super setEnabled:aBoolean];
[self reloadData];
}
/*! Return the index of tile depending of the giving event
@param anEvent
@return an index
*/
- (CPInteger)indexOfTileForEvent:(CPEvent)anEvent
{
var locationInView = [self convertPoint:[anEvent locationInWindow] fromView:nil],
tileSize = [self tileSize],
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"],
margin = [_datePicker valueForThemeAttribute:@"tile-margin"] || CGSizeMakeZero(),
tileInset = [_datePicker valueForThemeAttribute:@"tile-inset"] || CGInsetMakeZero();
// Get the week row
var rowIndex = FLOOR((locationInView.y - tileInset.top - margin.height) / (tileSize.height + 2 * margin.height + borderWidth)),
columnIndex = FLOOR((locationInView.x - tileInset.left) / (tileSize.width + 2 * margin.width + borderWidth));
columnIndex = MIN(MAX(columnIndex, 0), 6);
rowIndex = MIN(MAX(rowIndex, 0), 5);
var tileIndex = (rowIndex * 7) + columnIndex;
return tileIndex;
}
// MARK: -
// MARK: Reload data
/*! Reload the data
*/
- (void)reloadData
{
if (!_date)
return;
var currentMonth = _date,
startOfMonthDay = [self startOfWeekForDate:currentMonth],
daysInPreviousMonth = [_previousMonth _daysInMonth],
firstDayToShowInPreviousMonth = daysInPreviousMonth - startOfMonthDay,
currentDate = new Date(_previousMonth.getFullYear(), _previousMonth.getMonth(), firstDayToShowInPreviousMonth),
now = [CPDate date],
dateValue = [_datePicker dateValue];
// Update the tiles
for (var i = 0; i < [_dayTiles count]; i++)
{
var dayTile = _dayTiles[i];
// Increment to next day
currentDate.setTime(currentDate.getTime() + 90000000);
[currentDate _resetToMidnight];
var isPresentMonth = (now.getMonth() == currentDate.getMonth()
&& now.getFullYear() == currentDate.getFullYear());
[dayTile setDate:[currentDate copy]];
[dayTile setStringValue:currentDate.getDate()];
[dayTile setDisabled:/*![self isEnabled] ||*/ currentDate.getMonth() !== currentMonth.getMonth() || currentDate < [_datePicker minDate] || currentDate > [_datePicker maxDate]];
[dayTile setHighlighted:isPresentMonth && currentDate.getDate() == now.getDate()];
}
// Select the dates
[self _selectDate:[_datePicker dateValue] timeInterval:[_datePicker timeInterval]];
}
// MARK: -
// MARK: Select methods
/*! Select one date or several date depending of the giving interval
@param aStartDate
@param anInterval;
*/
- (void)_selectDate:(CPDate)aStartDate timeInterval:(CPInteger)anInterval
{
var endDate = [[CPDate alloc] initWithTimeInterval:anInterval sinceDate:aStartDate],
tilesCount = [_dayTiles count];
aStartDate = [aStartDate copy];
[aStartDate _resetToMidnight];
[endDate _resetToMidnight];
var firstSelected = NO;
for (var i = 0; i < tilesCount; i++)
{
var tile = _dayTiles[i],
tileDate = [[tile date] copy],
selected = NO;
[tileDate _resetToMidnight];
if (aStartDate)
selected = tileDate >= aStartDate && tileDate <= endDate;
// Select a tile
[tile setSelected:selected];
// If we are disabled, we have to disable selected tiles so they will appear disabled
[tile setDisabled:[tile isDisabled] || (selected && ![self isEnabled])];
if (selected)
{
if (!firstSelected)
{
firstSelected = YES;
[tile setFirstSelected:YES];
}
else
[tile setFirstSelected:NO];
[tile setLastSelected:NO];
}
else
{
if (firstSelected)
{
firstSelected = NO;
// As there was a first selected and we are now on an unselected tile,
// we are sure that i > 0
[_dayTiles[i-1] setLastSelected:YES];
}
}
}
}
// MARK: -
// MARK: Layout methods
/*! Tile the view
*/
- (void)tile
{
var tileSize = [self tileSize],
width = tileSize.width,
height = tileSize.height,
tilesCount = [_dayTiles count],
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"],
margin = [_datePicker valueForThemeAttribute:@"tile-margin"],
tileInset = [_datePicker valueForThemeAttribute:@"tile-inset"],
thisWidth,
thisX,
dayInWeek,
weekInMonth,
tileFrame,
tileIndex;
// Set the frame of the tiles
for (tileIndex = 0; tileIndex < tilesCount; tileIndex++)
{
dayInWeek = tileIndex % 7;
weekInMonth = (tileIndex - dayInWeek) / 7;
tileFrame;
if (margin) // Beginning with Aristo3
{
thisX = ROUND(dayInWeek * (width + 2 * margin.width));
thisWidth = ROUND((dayInWeek+1) * (width + 2 * margin.width)) - thisX;
tileFrame = CGRectMake(tileInset.left + thisX, tileInset.top + margin.height + weekInMonth * (height + 2 * margin.height), thisWidth + borderWidth, height + borderWidth);
}
else
tileFrame = CGRectMake(dayInWeek * width, weekInMonth * height, width + borderWidth, height + borderWidth);
[_dayTiles[tileIndex] setFrame:tileFrame];
[_dayTiles[tileIndex] setDayInWeek:dayInWeek];
}
[self reloadData];
}
/*! Layout the subviews
*/
- (void)layoutSubviews
{
[super layoutSubviews];
[self tile];
[_dayTiles makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
/*! Draw the component. This draws the border of the tile.
The selected tile are drawed in the drawRect method of the tile. But the unselected tile here.
It avoids some problems with tiles over other tiles (otherwise the color of the tile border would be different).
Rememeber that the first pixel of a tile are over the last pixel of the last tile (because the border)
*/
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if ([_datePicker isCSSBased])
{
// We just have to draw the separator (if any)
// No separator color means no separator
var separatorColor = [_datePicker valueForThemeAttribute:@"separator-color"],
separatorHeight = [_datePicker valueForThemeAttribute:@"separator-height"],
separatorMarginWidth = [_datePicker valueForThemeAttribute:@"separator-margin-width"];
if (separatorColor)
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
bounds = [self bounds];
CGContextBeginPath(context);
CGContextSetStrokeColor(context, separatorColor);
CGContextSetLineWidth(context, separatorHeight);
CGContextMoveToPoint(context, separatorMarginWidth, 0.5);
CGContextAddLineToPoint(context, bounds.size.width - separatorMarginWidth, 0.5);
CGContextStrokePath(context);
CGContextClosePath(context);
}
return;
}
var context = [[CPGraphicsContext currentContext] graphicsPort],
width = [self tileSize].width,
height = [self tileSize].height,
isBorderPair = ([_datePicker valueForThemeAttribute:@"border-width"] % 2) == 0;
CGContextBeginPath(context);
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]);
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
if ([_datePicker isBordered])
{
for (var i = 0; i < 6; i++)
{
var y = i * height;
// Very usefull to avoid to have a line of two pixels instead one
if (!isBorderPair)
y += 0.5;
CGContextMoveToPoint(context, 0, y);
CGContextAddLineToPoint(context, [self bounds].size.width, y);
}
for (var i = 0; i < 7; i++)
{
var x = i * width;
// Very usefull to avoid to have a line of two pixels instead one
if (!isBorderPair)
x += 0.5;
CGContextMoveToPoint(context, x, 0);
CGContextAddLineToPoint(context, x, [self bounds].size.height);
}
}
else
{
var y = 0;
// Very usefull to avoid to have a line of two pixels instead one
if (!isBorderPair)
y += 0.5;
CGContextMoveToPoint(context, 0, y);
CGContextAddLineToPoint(context, [self bounds].size.width, y);
}
CGContextStrokePath(context);
CGContextClosePath(context);
}
// MARK: -
// MARK: Mouse event
/*! Mouse down event
*/
- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEnabled])
return;
var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]],
dateTile = [[dayTile date] copy],
dateValue = [_datePicker dateValue];
_clickDate = [dateTile copy];
_dragDate = nil;
_indexDayTile = -1;
_eventDragged = nil;
#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())
{
if ([_datePicker datePickerMode] == CPRangeDateMode && [anEvent modifierFlags] & CPShiftKeyMask)
{
var dateValueAtMidnight = [[_datePicker dateValue] copy];
[dateValueAtMidnight _resetToMidnight];
if (dateTile < dateValueAtMidnight)
{
var interval;
if (dateTile == dateValueAtMidnight)
interval = [_datePicker timeInterval];
else
interval = ([dateValueAtMidnight timeIntervalSinceDate:dateTile] + [_datePicker timeInterval]);
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:interval];
}
else if ([[dayTile date] isEqualToDate:dateValueAtMidnight])
{
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
}
else
{
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:[dateValueAtMidnight copy]] timeInterval:([dateTile timeIntervalSinceDate:dateValueAtMidnight])];
}
// Be sure to display the good month
[_delegate setDateValue:dateTile];
}
else
{
var minDate = [[_datePicker minDate] copy],
maxDate = [[_datePicker maxDate] copy];
[minDate _resetToMidnight];
[maxDate _resetToLastSeconds];
if (dateTile >= minDate && dateTile <= maxDate)
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
}
}
else
{
// Check the year and the month. The year is usefull when changing from Jan to Dec.
if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1)
[_delegate _displayPreviousMonth];
else
[_delegate _displayNextMonth];
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
/*! Mouse dragged event
*/
- (void)mouseDragged:(CPEvent)anEvent
{
if (![self isEnabled] || !CGRectContainsPoint([self bounds],[self convertPoint:[anEvent locationInWindow] fromView:nil]))
return;
var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]],
dateTile = [[dayTile date] copy],
dateValue = [_datePicker dateValue];
_dragDate = [dateTile copy];
_indexDayTile = [self indexOfTileForEvent:anEvent];
_eventDragged = anEvent;
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
if ([_datePicker datePickerMode] == CPSingleDateMode)
{
// Check if we have to change or not the month of the component
if ([dayTile date].getMonth() == _date.getMonth())
{
[_timerMonth invalidate];
_isMonthJustChanged = NO;
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
}
else if (!_isMonthJustChanged)
{
[_timerMonth invalidate];
_isMonthJustChanged = NO;
// Check the year and the month. The year is usefull when changing from Jan to Dec.
if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1)
[_delegate _displayPreviousMonth];
else
[_delegate _displayNextMonth];
}
}
else
{
if (dateTile.getMonth() == _date.getMonth() || !_isMonthJustChanged)
{
[_timerMonth invalidate];
_isMonthJustChanged = NO;
var dateValueAtMidnight = [[_datePicker dateValue] copy];
[dateValueAtMidnight _resetToMidnight];
if (dateTile < _clickDate)
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:[_clickDate timeIntervalSinceDate:dateTile]];
else if ([[dayTile date] isEqualToDate:_clickDate])
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
else
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:_clickDate] timeInterval:[dateTile timeIntervalSinceDate:dateValueAtMidnight]];
}
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
- (void)mouseUp:(CPEvent)anEvent
{
[_timerMonth invalidate];
_dragDate = nil;
_clickDate = nil;
_isMonthJustChanged = NO;
_indexDayTile = -1;
_eventDragged = nil;
}
// MARK: -
// MARK: Timer
- (void)_timerNextMonthEvent:(CPEvent)anEvent
{
if (_isMonthJustChanged)
{
_dragDate.setMonth(_date.getMonth() + 1);
[_delegate _displayNextMonth];
}
}
- (void)_timerPreviousMonthEvent:(CPEvent)anEvent
{
if (_isMonthJustChanged)
{
_dragDate.setMonth(_date.getMonth() - 1);
[_delegate _displayPreviousMonth];
}
}
// MARK: -
// MARK: Date methods
- (CPDate)_hoursMinutesSecondsFromDatePickerForDate:(CPDate)aDate
{
var dateValue = [_datePicker dateValue];
aDate.setHours(dateValue.getHours());
aDate.setMinutes(dateValue.getMinutes());
aDate.setSeconds(dateValue.getSeconds());
return aDate;
}
@end
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -69,7 +69,7 @@
aNewObject._controller = self;
aNewObject._key = aKey;
if (aValue != nil)
if (aValue !== nil)
[aNewObject setValue:aValue];
return aNewObject;
@@ -98,7 +98,7 @@
var iter = [[CPSet setWithArray:allKeys] objectEnumerator],
obj;
while ((obj = [iter nextObject]) != nil)
while ((obj = [iter nextObject]) !== nil)
if (![_excludedKeys containsObject:obj])
[array addObject:[self _newObjectWithKey:obj value:nil]];
+2 -5
View File
@@ -871,13 +871,10 @@ var CPDocumentUntitledCount = 0;
{
var theDelegate = context.delegate;
// Only close the document explicitly if there is NO delegate to handle the action.
// If a delegate exists (e.g., the CPWindow), it is responsible for performing the close
// upon receiving the callback below. Calling [self close] here would cause a double-close.
if (aDocument === self && shouldClose && theDelegate == nil)
if (aDocument === self && shouldClose)
[self close];
if (theDelegate)
if (theDelegate != null)
theDelegate.isa.objj_msgSend3(theDelegate, context.selector, aDocument, shouldClose, context.context);
}
+2 -2
View File
@@ -106,7 +106,7 @@ var CPSharedDocumentController = nil;
@param aType the type of the new document
@param shouldDisplay whether to display the document on screen
*/
- (CPDocument)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
- (void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
{
var theDocument = [self makeUntitledDocumentOfType:aType error:nil];
@@ -297,7 +297,7 @@ var CPSharedDocumentController = nil;
var iter = [_documents objectEnumerator],
obj;
while ((obj = [iter nextObject]) != nil)
while ((obj = [iter nextObject]) !== nil)
{
if ([obj isDocumentEdited])
return YES;
+2 -56
View File
@@ -26,7 +26,6 @@
@import "CPPasteboard.j"
@import "CPView.j"
@import "CPWindow_Constants.j"
@import "CPViewAnimation.j"
@class CPWindow // This file is imported by CPWindow.j
@class _CPDOMDataTransferPasteboard
@@ -133,10 +132,6 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
unsigned _dragOperation;
CPTimer _draggingUpdateTimer;
// Animation State
CGPoint _pendingEndLocation;
CPDragOperation _pendingEndOperation;
}
/*
@@ -266,19 +261,10 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
{
var contentView = [scrollView contentView],
bounds = [contentView bounds],
insetBounds = CGRectInset(bounds, 30, 30),
eventLocation = [contentView convertPoint:_draggingLocation fromView:nil],
deltaX = 0,
deltaY = 0,
insetSize = 30;
if ([contentView respondsToSelector:@selector(documentView)] &&
[[contentView documentView] respondsToSelector:@selector(rowHeight)])
{
// Adjust inset bounds based on CPTableView row height
insetSize = MAX(insetSize, [[contentView documentView] rowHeight]);
}
var insetBounds = CGRectInset(bounds, insetSize, insetSize);
deltaY = 0;
if (!CGRectContainsPoint(insetBounds, eventLocation))
{
@@ -330,46 +316,6 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
[_draggingUpdateTimer invalidate];
_draggingUpdateTimer = nil;
// Check if we should slide back (drag failed + slideBack requested)
if (![CPPlatform supportsDragAndDrop] && _shouldSlideBack && anOperation === CPDragOperationNone)
{
// Store state to finalize drag after animation completes
_pendingEndLocation = aLocation;
_pendingEndOperation = anOperation;
var currentFrame = [_draggedWindow frame],
targetFrame = CGRectMake(_startDragLocation.x, _startDragLocation.y, currentFrame.size.width, currentFrame.size.height);
// We use CPViewAnimation. Even though _draggedWindow is a CPWindow,
// CPViewAnimation supports targets that respond to setFrame: (like NSViewAnimation does for NSWindow).
var animation = [[CPViewAnimation alloc] initWithViewAnimations:[
[CPDictionary dictionaryWithObjects:[_draggedWindow, currentFrame, targetFrame]
forKeys:[CPViewAnimationTargetKey, CPViewAnimationStartFrameKey, CPViewAnimationEndFrameKey]]
]];
[animation setAnimationCurve:CPAnimationEaseOut];
[animation setDuration:0.25];
[animation setDelegate:self];
[animation startAnimation];
return;
}
[self _performFinalCleanupWithLocation:aLocation operation:anOperation];
}
- (void)animationDidEnd:(CPAnimation)anAnimation
{
[self _performFinalCleanupWithLocation:_pendingEndLocation operation:_pendingEndOperation];
}
- (void)animationDidStop:(CPAnimation)anAnimation
{
[self _performFinalCleanupWithLocation:_pendingEndLocation operation:_pendingEndOperation];
}
- (void)_performFinalCleanupWithLocation:(CGPoint)aLocation operation:(CPDragOperation)anOperation
{
[_draggedView removeFromSuperview];
if (![CPPlatform supportsDragAndDrop])
+4 -32
View File
@@ -70,7 +70,6 @@ var _CPEventPeriodicEventPeriod = 0,
BOOL _isARepeat;
unsigned _keyCode;
DOMEvent _DOMEvent;
BOOL _isActionKey;
int _data1;
int _data2;
short _subtype;
@@ -111,28 +110,17 @@ var _CPEventPeriodicEventPeriod = 0,
@param unmodCharacters the string of keys pressed without the presence of any modifiers other than Shift
@param repeatKey \c YES if this is caused by the system repeat as opposed to the user pressing the key again
@param code a number associated with the keyboard key of this event
@param isAnActionKey a BOOL indicating whether this key is an action key (e.g. a function key)
@throws CPInternalInconsistencyException if \c anEventType is not a CPKeyDown,
CPKeyUp or CPFlagsChanged
@return the keyboard event
*/
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:isAnActionKey];
}
// for backwards compatibility only
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:NO];
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code];
}
/*!
@@ -264,7 +252,7 @@ var _CPEventPeriodicEventPeriod = 0,
/* @ignore */
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code
{
if (self = [self _initWithType:anEventType])
{
@@ -276,7 +264,6 @@ var _CPEventPeriodicEventPeriod = 0,
_charactersIgnoringModifiers = unmodCharacters;
_isARepeat = isARepeat;
_keyCode = code;
_isActionKey = isAnActionKey;
_windowNumber = aWindowNumber;
}
@@ -584,21 +571,6 @@ var _CPEventPeriodicEventPeriod = 0,
return !firstResponderIsText;
}
- (BOOL)_isActionOrCommandEvent
{
// This method is now platform-agnostic. It checks for abstract properties
// of the event, including the _isActionKey flag that was set at creation time.
return (
// Is it a command shortcut?
(_modifierFlags & (CPCommandKeyMask | CPControlKeyMask)) ||
// Is it a key that doesn't produce a character?
([_characters length] === 0) ||
// Was it identified as an action key by the platform-specific layer?
_isActionKey
);
}
/*!
Return YES if this event is a part of processing a browser controlled cut or paste event
where the browser will go ahead and do the work of cutting or pasting within the input
@@ -636,7 +608,7 @@ var _CPEventPeriodicEventPeriod = 0,
*/
+ (void)stopPeriodicEvents
{
if (_CPEventPeriodicEventTimer == nil)
if (_CPEventPeriodicEventTimer === nil)
return;
window.clearTimeout(_CPEventPeriodicEventTimer);
+85
View File
@@ -0,0 +1,85 @@
/*
* CPFlashMovie.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
*/
@import <Foundation/CPObject.j>
/*!
@ingroup appkit
@class CPFlashMovie
CPFlashMovie is used to represent a Flash movie in the Cappuccino framework.
*/
@implementation CPFlashMovie : CPObject
{
CPString _filename;
}
/*!
Creates a new Flash movie with the swf at \c aFileName.
@param aFilename the swf to load
@return the initialized CPFlashMovie
*/
+ (id)flashMovieWithFile:(CPString)aFilename
{
return [[self alloc] initWithFile:aFilename];
}
/*!
Initializes the Flash movie.
@param aFilename the swf to load
@return the initialized CPFlashMovie
*/
- (id)initWithFile:(CPString)aFilename
{
self = [super init];
if (self)
_filename = aFilename;
return self;
}
- (CPString)filename
{
return _filename;
}
@end
var CPFlashMovieFilenameKey = "CPFlashMovieFilenameKey";
@implementation CPFlashMovie (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
_filename = [aCoder decodeObjectForKey:CPFlashMovieFilenameKey];
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeObject:_filename forKey:CPFlashMovieFilenameKey];
}
@end
+212
View File
@@ -0,0 +1,212 @@
/*
* CPFlashView.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
*/
@import "CPFlashMovie.j"
@import "CPView.j"
var IEFlashCLSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
/*!
@ingroup appkit
*/
@implementation CPFlashView : CPView
{
CPFlashMovie _flashMovie;
CPDictionary _params;
CPDictionary _paramElements;
#if PLATFORM(DOM)
DOMElement _DOMParamElement;
DOMElement _DOMObjectElement;
#endif
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
#if PLATFORM(DOM)
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
{
_DOMObjectElement = document.createElement(@"object");
_DOMObjectElement.id = [self elementID];
_DOMObjectElement.width = @"100%";
_DOMObjectElement.height = @"100%";
_DOMObjectElement.style.top = @"0px";
_DOMObjectElement.style.left = @"0px";
_DOMObjectElement.type = @"application/x-shockwave-flash";
_DOMParamElement = document.createElement(@"param");
_DOMParamElement.name = @"movie";
_DOMObjectElement.appendChild(_DOMParamElement);
_DOMElement.appendChild(_DOMObjectElement);
}
else
[self _rebuildIEObjects];
#endif
}
return self;
}
- (void)setFlashMovie:(CPFlashMovie)aFlashMovie
{
if (_flashMovie == aFlashMovie)
return;
_flashMovie = aFlashMovie;
#if PLATFORM(DOM)
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
{
_DOMParamElement.value = [aFlashMovie filename];
_DOMObjectElement.data = [aFlashMovie filename];
}
else
[self _rebuildIEObjects];
#endif
}
- (CPFlashMovie)flashMovie
{
return _flashMovie;
}
- (void)setFlashVars:(CPDictionary)aDictionary
{
var varString = @"",
enumerator = [aDictionary keyEnumerator],
key;
if (key = [enumerator nextObject])
varString = [varString stringByAppendingFormat:@"%@=%@", key, [aDictionary objectForKey:key]];
while (key = [enumerator nextObject])
varString = [varString stringByAppendingFormat:@"&%@=%@", key, [aDictionary objectForKey:key]];
if (!_params)
_params = @{};
[_params setObject:varString forKey:@"flashvars"];
[self setParameters:_params];
}
- (CPDictionary)flashVars
{
return [_params objectForKey:@"flashvars"];
}
- (void)setParameters:(CPDictionary)aDictionary
{
#if PLATFORM(DOM)
if (_paramElements && !CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
{
var elements = [_paramElements allValues],
count = [elements count];
for (var i = 0; i < count; i++)
_DOMObjectElement.removeChild([elements objectAtIndex:i]);
}
#endif
if (!_params)
_params = aDictionary;
else
[_params addEntriesFromDictionary:aDictionary];
#if PLATFORM(DOM)
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
{
_paramElements = @{};
var enumerator = [_params keyEnumerator],
key;
while (_DOMObjectElement && (key = [enumerator nextObject]) !== nil)
{
var param = document.createElement(@"param");
param.name = key;
param.value = [_params objectForKey:key];
_DOMObjectElement.appendChild(param);
[_paramElements setObject:param forKey:key];
}
}
else
[self _rebuildIEObjects];
#endif
}
- (CPDictionary)parameters
{
return _params;
}
#if PLATFORM(DOM)
- (void)_rebuildIEObjects
{
_DOMElement.innerHTML = @"";
if (![_flashMovie filename])
return;
var paramString = [CPString stringWithFormat:@"<param name='movie' value='%@' />", [_flashMovie filename]],
paramEnumerator = [_params keyEnumerator],
key;
while ((key = [paramEnumerator nextObject]) !== nil)
paramString = [paramString stringByAppendingFormat:@"<param name='%@' value='%@' />", key, [_params objectForKey:key]];
_DOMObjectElement = document.createElement(@"object");
_DOMElement.appendChild(_DOMObjectElement);
_DOMObjectElement.outerHTML = [CPString stringWithFormat:@"<object id=%@ classid=%@ width=%@ height=%@>%@</object>", [self elementID], IEFlashCLSID, CGRectGetWidth([self bounds]), CGRectGetHeight([self bounds]), paramString];
}
#endif
- (CPString)elementID
{
return @"CPFV_" + [self UID];
}
- (void)mouseMoved:(CPEvent)sommit
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
- (void)mouseDragged:(CPEvent)anEvent
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
- (void)mouseDown:(CPEvent)anEvent
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
- (void)mouseUp:(CPEvent)anEvent
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
@end
+11 -131
View File
@@ -25,8 +25,6 @@
@import "CPView.j"
@import "CPFontDescriptor.j"
@import "_CPObject+Theme.j"
@import "CPControl.j"
CPFontDefaultSystemFontFace = @"Arial, sans-serif";
CPFontDefaultSystemFontSize = 12;
@@ -40,15 +38,12 @@ CPFontCurrentSystemSize = -1;
// For internal use only by this class and subclasses
_CPFontSystemFacePlaceholder = "_CPFontSystemFacePlaceholder";
var _CPFontCache = {},
_CPSystemFontCache = {},
_CPFontSystemFontFace = CPFontDefaultSystemFontFace,
_CPFontSystemFontSize = CPFontDefaultSystemFontSize,
_CPFontSystemFontSizeSmall = CPFontDefaultSystemFontSize - 1,
_CPFontSystemFontSizeMini = CPFontDefaultSystemFontSize - 2,
_CPFontFallbackFaces = CPFontDefaultSystemFontFace.split(", "),
_CPFontStripRegExp = new RegExp("(^\\s*[\"']?|[\"']?\\s*$)", "g"),
_CPFontSystemFontFaceSpecified = NO;
var _CPFontCache = {},
_CPSystemFontCache = {},
_CPFontSystemFontFace = CPFontDefaultSystemFontFace,
_CPFontSystemFontSize = 12,
_CPFontFallbackFaces = CPFontDefaultSystemFontFace.split(", "),
_CPFontStripRegExp = new RegExp("(^\\s*[\"']?|[\"']?\\s*$)", "g");
#define _CPRealFontSize(aSize) (aSize <= 0 ? _CPFontSystemFontSize : aSize)
@@ -114,7 +109,7 @@ following:
<string>Asap</string>
@endcode
*/
@implementation CPFont : CPObject <CPTheme>
@implementation CPFont : CPObject
{
CPString _name;
float _size;
@@ -128,22 +123,6 @@ following:
CPString _cssString;
}
+ (CPString)defaultThemeClass
{
return @"font";
}
+ (CPDictionary)themeAttributes
{
return @{
@"system-font-face": [CPNull null],
@"system-font-style": [CPNull null],
@"system-font-size-regular": [CPNull null],
@"system-font-size-small": [CPNull null],
@"system-font-size-mini": [CPNull null]
};
}
+ (void)initialize
{
if (self !== [CPFont class])
@@ -155,10 +134,7 @@ following:
systemFontFace = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontFace"];
if (systemFontFace)
{
_CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
_CPFontSystemFontFaceSpecified = YES;
}
var systemFontSize = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontSize"];
@@ -166,62 +142,7 @@ following:
systemFontSize = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontSize"];
if (systemFontSize)
{
_CPFontSystemFontSize = systemFontSize;
_CPFontSystemFontFaceSpecified = YES;
}
}
+ (void)initializeSystemFontFromTheme:(CPTheme)aTheme
{
// If something was specified via +initialize (from an Info.plist file), don't do anything
if (_CPFontSystemFontFaceSpecified)
return;
// Reset all system font caches
_CPSystemFontCache = {};
// Now, try to get information from the theme
var systemFontFace = [aTheme valueForAttributeWithName:@"system-font-face" forClass:[CPFont class]];
if (systemFontFace)
{
[self _invalidateSystemFontCache];
_CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
}
var systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-regular" forClass:[CPFont class]];
if (systemFontSize)
{
[self _invalidateSystemFontCache];
_CPFontSystemFontSize = systemFontSize;
}
systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-small" forClass:[CPFont class]];
if (systemFontSize)
{
[self _invalidateSystemFontCache];
_CPFontSystemFontSizeSmall = systemFontSize;
}
systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-mini" forClass:[CPFont class]];
if (systemFontSize)
{
[self _invalidateSystemFontCache];
_CPFontSystemFontSizeMini = systemFontSize;
}
// Is there something to add to the global syle definition ?
var systemFontStyle = [aTheme valueForAttributeWithName:@"system-font-style" forClass:[CPFont class]];
if (systemFontStyle)
{
// Yes, so install it in the DOM Style element
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPath:systemFontStyle];
}
}
/*!
@@ -256,13 +177,14 @@ following:
+ (CPFont)systemFontForControlSize:(CPControlSize)aSize
{
// TODO These sizes should be themable or made less arbitrary in some other way.
switch (aSize)
{
case CPSmallControlSize:
return [self systemFontOfSize:_CPFontSystemFontSizeSmall];
return [self systemFontOfSize:_CPFontSystemFontSize - 1];
case CPMiniControlSize:
return [self systemFontOfSize:_CPFontSystemFontSizeMini];
return [self systemFontOfSize:_CPFontSystemFontSize - 2];
case CPRegularControlSize:
default:
@@ -393,10 +315,6 @@ following:
_isItalic = isItalic;
_isSystem = isSystem;
_theme = [CPTheme defaultTheme];
_themeState = CPThemeStateNormal;
[self _loadThemeAttributes];
if (isSystem)
{
_name = aName;
@@ -464,22 +382,6 @@ following:
return _CPRealFontSize(_size);
}
/*!
Returns the font size. Cocoa/AppKit compatibility alias for -size.
*/
- (float)pointSize
{
return [self size];
}
/*!
Returns the font name. Cocoa/AppKit compatibility alias for -familyName.
*/
- (CPString)fontName
{
return [self familyName];
}
/*!
Returns the font as a CSS string
*/
@@ -530,22 +432,6 @@ following:
_lineHeight = [metrics objectForKey:@"lineHeight"];
}
- (CPControlSize)controlSizeCorrespondingToFontSize
{
switch (_size)
{
case _CPFontSystemFontSizeSmall:
return CPSmallControlSize;
case _CPFontSystemFontSizeMini:
return CPMiniControlSize;
default:
// If we can't find a corresponding size, return regular control size
return CPRegularControlSize;
}
}
@end
@implementation CPFont(DescriptorAdditions)
@@ -606,11 +492,7 @@ var CPFontNameKey = @"CPFontNameKey",
isItalic = [aCoder decodeBoolForKey:CPFontIsItalicKey],
isSystem = [aCoder decodeBoolForKey:CPFontIsSystemKey];
self = [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem];
[self _decodeThemeObjectsWithCoder:aCoder];
return self;
return [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem];
}
/*!
@@ -624,8 +506,6 @@ var CPFontNameKey = @"CPFontNameKey",
[aCoder encodeBool:_isBold forKey:CPFontIsBoldKey];
[aCoder encodeBool:_isItalic forKey:CPFontIsItalicKey];
[aCoder encodeBool:_isSystem forKey:CPFontIsSystemKey];
[self _encodeThemeObjectsWithCoder:aCoder];
}
@end
+31 -10
View File
@@ -29,8 +29,6 @@
@global CPApp
@class CPFontPanel
@global document
CPItalicFontMask = 1 << 0;
CPBoldFontMask = 1 << 1;
CPUnboldFontMask = 1 << 2;
@@ -192,6 +190,35 @@ CPRemoveTraitFontAction = 7;
return ([aFont isBold] ? CPBoldFontMask : 0) | ([aFont isItalic] ? CPItalicFontMask : 0);
}
- (CPFont)convertFont:(CPFont)aFont
{
if (!_activeChange)
return aFont;
var addTraits = [_activeChange valueForKey:@"addTraits"];
if (addTraits)
aFont = [self convertFont:aFont toHaveTrait:addTraits];
return aFont;
}
- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)addTraits
{
if (!aFont)
return nil;
var shouldBeBold = ([aFont isBold] || (addTraits & CPBoldFontMask)) && !(addTraits & CPUnboldFontMask),
shouldBeItalic = ([aFont isItalic] || (addTraits & CPItalicFontMask)) && !(addTraits & CPUnitalicFontMask),
shouldBeSize = [aFont size];
// XXX On the current platform there will always be a bold/italic version of each font, but still leave
// || aFont in here for future platforms.
aFont = [CPFont _fontWithName:[aFont familyName] size:shouldBeSize bold:shouldBeBold italic:shouldBeItalic] || aFont;
return aFont;
}
- (CPFont)convertFont:(CPFont)aFont toFace:(CPString)aTypeface
{
if (!aFont)
@@ -208,12 +235,8 @@ CPRemoveTraitFontAction = 7;
- (@action)addFontTrait:(id)sender
{
var tag = sender;
if ([sender respondsToSelector:@selector(tag)])
tag = [sender tag];
_activeChange = tag == nil ? @{} : @{ @"addTraits": tag };
var tag = [sender tag];
_activeChange = tag === nil ? @{} : @{ @"addTraits": tag };
_fontAction = CPAddTraitFontAction;
[self sendAction];
@@ -363,7 +386,6 @@ CPRemoveTraitFontAction = 7;
- (CPFont)convertFont:(CPFont)aFont
{
var newFont = nil;
switch (_fontAction)
{
case CPNoFontChangeAction:
@@ -376,7 +398,6 @@ CPRemoveTraitFontAction = 7;
case CPAddTraitFontAction:
newFont = aFont;
if (!_activeChange)
break;
+1 -1
View File
@@ -92,7 +92,7 @@ var CPGraphicsContextCurrent = nil,
@param aGraphicsPort the graphics port to initialize with
@return the initialized context
*/
- (id)initWithGraphicsPort:(CGContext)aGraphicsPort
- (id)initWithGraphicsPort:(CPContext)aGraphicsPort
{
self = [super init];
+8 -244
View File
@@ -26,15 +26,10 @@
@import <Foundation/CPRunLoop.j>
@import <Foundation/CPString.j>
@import <Foundation/CPData.j>
@import <Foundation/CPKeyedArchiver.j>
@import <Foundation/CPKeyedUnarchiver.j>
@import "CGGeometry.j"
@import "CPCompatibility.j"
@import "CPGraphicsContext.j"
@class CPColor
@global document
@protocol CPImageDelegate <CPObject>
@@ -90,7 +85,9 @@ function CPImageInBundle()
if (typeof(arguments[1]) === "number")
{
size = CGSizeMake(arguments[1], arguments[2]);
if (arguments[1] !== nil && arguments[1] !== undefined)
size = CGSizeMake(arguments[1], arguments[2]);
bundle = arguments[3];
}
else if (typeof(arguments[1]) === "object")
@@ -164,7 +161,7 @@ function CPAppKitImage(aFilename, aSize)
- (id)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize
{
// Quietly return nil like in Cocoa, rather than crashing later.
if (aFilename == nil)
if (aFilename === undefined || aFilename === nil)
return nil;
self = [super init];
@@ -449,11 +446,6 @@ function CPAppKitImage(aFilename, aSize)
return NO;
}
- (BOOL)isMaterialIconImage
{
return NO;
}
- (CPString)description
{
var filename = [self filename],
@@ -517,8 +509,8 @@ function CPAppKitImage(aFilename, aSize)
@end
// MARK: -
// MARK: CSS Theming
#pragma mark -
#pragma mark CSS Theming
// The code below adds support for CSS theming with 100% compatibility with current theming system.
// The idea is to extend CPImage (and CPColor) with CSS components and adapt low level UI components to
@@ -572,7 +564,6 @@ function CPAppKitImage(aFilename, aSize)
CPDictionary _cssDictionary @accessors(property=cssDictionary);
CPDictionary _cssBeforeDictionary @accessors(property=cssBeforeDictionary);
CPDictionary _cssAfterDictionary @accessors(property=cssAfterDictionary);
CGSize _displaySize;
}
+ (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary size:(CGSize)aSize
@@ -591,16 +582,6 @@ function CPAppKitImage(aFilename, aSize)
return [[CPImage alloc] initWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:nil size:aSize];
}
+ (CPImage)imageWithMaterialIconNamed:(CPString)iconName size:(CGSize)size
{
return [_CPMaterialIconImage imageWithIconNamed:iconName size:size];
}
+ (CPImage)imageWithMaterialIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color
{
return [_CPMaterialIconImage imageWithIconNamed:iconName size:size color:color];
}
- (id)initWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary size:(CGSize)aSize
{
self = [super init];
@@ -613,7 +594,6 @@ function CPAppKitImage(aFilename, aSize)
_cssDictionary = aDictionary;
_cssBeforeDictionary = beforeDictionary;
_cssAfterDictionary = afterDictionary;
_displaySize = CGSizeMakeCopy(aSize);
}
return self;
@@ -705,7 +685,7 @@ function CPAppKitImage(aFilename, aSize)
aStyleNode.replaceChild(styleDescription, aStyleNode.firstChild);
}
aDOMElement.className = @"CP"+[aView UID];
[aView setDOMClassName:@"CP"+[aView UID]];
}
else
{
@@ -727,18 +707,13 @@ function CPAppKitImage(aFilename, aSize)
#endif
}
- (BOOL)_shouldBeResized
{
return NO;
}
@end
var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
CPImageCSSBeforeDictionaryKey = @"CPImageCSSBeforeDictionaryKey",
CPImageCSSAfterDictionaryKey = @"CPImageCSSAfterDictionaryKey";
// MARK: -
#pragma mark -
@implementation CPImage (CPCoding)
@@ -775,206 +750,6 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
@end
// MARK: -
// MARK: Drawing
@implementation CPImage (Drawing)
- (void)drawAtPoint:(CGPoint)point fromRect:(CPRect)fromRect operation:(CGBlendMode)op fraction:(float)delta
{
if (_loadStatus !== CPImageLoadStatusCompleted)
return;
var context = [CPGraphicsContext currentContext].graphicsPort;
if (!context)
return;
CGContextSaveGState(context);
CGContextSetBlendMode(context, op);
CGContextSetAlpha(context, delta);
context.drawImage(
_image,
fromRect.origin.x,
fromRect.origin.y,
fromRect.size.width,
fromRect.size.height,
point.x,
point.y,
fromRect.size.width,
fromRect.size.height
);
CGContextRestoreGState(context);
}
@end
// MARK: -
@implementation _CPMaterialIconImage : CPImage
{
CPMutableDictionary _cachedColorVersions;
CPColor _cachedInvertedColor;
}
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size
{
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size];
}
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color
{
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size color:color];
}
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color additionalCSSDictionary:(CPDictionary)additionalCSSDictionary
{
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size color:color additionalCSSDictionary:additionalCSSDictionary];
}
- (CPDictionary)_baseMaterialIconCSSDictionaryForIconName:(CPString)iconName size:(CGSize)size
{
return @{
@"width": size.width + @"px",
@"height": size.height + @"px",
@"top": @"0px",
@"left": @"0px",
@"content": @"'" + iconName + @"'",
@"position": @"absolute",
@"z-index": @"300",
@"font-family": @"'Material Icons'",
@"font-weight": @"normal",
@"font-style": @"normal",
@"font-size": MIN(size.width, size.height) + @"px",
@"display": @"inline-block",
@"line-height": @"1",
@"text-transform": @"none",
@"letter-spacing": @"normal",
@"word-wrap": @"normal",
@"white-space": @"nowrap",
@"direction": @"ltr",
@"-webkit-font-smoothing": @"antialiased",
@"text-rendering": @"optimizeLegibility",
@"-moz-osx-font-smoothing": @"grayscale",
@"font-feature-settings": @"'liga'"
};
}
- (_CPMaterialIconImage)initWithIconName:(CPString)iconName size:(CGSize)size
{
return [super initWithCSSDictionary:@{}
beforeDictionary:@{}
afterDictionary:[self _baseMaterialIconCSSDictionaryForIconName:iconName size:size]
size:size];
}
- (_CPMaterialIconImage)initWithIconName:(CPString)iconName size:(CGSize)size color:(CPColor)color
{
var materialIconCSSDictionary = [self _baseMaterialIconCSSDictionaryForIconName:iconName size:size];
[materialIconCSSDictionary setObject:[color cssString] forKey:@"color"];
return [super initWithCSSDictionary:@{}
beforeDictionary:@{}
afterDictionary:materialIconCSSDictionary
size:size];
}
- (void)addRotationEffectWithAngle:(float)angle
{
[self addCSSDictionary:@{
@"transform": @"rotate("+angle+"deg)",
@"transition": @"transform 0.35s ease"
}];
}
- (void)addCSSDictionary:(CPDictionary)additionalCSSDictionary
{
[_cssAfterDictionary addEntriesFromDictionary:additionalCSSDictionary];
}
- (void)setSize:(CGSize)aSize
{
[self _setDisplaySize:aSize];
[super setSize:aSize];
}
- (void)_setDisplaySize:(CGSize)aSize
{
if (CGSizeEqualToSize(_displaySize, aSize))
return;
_displaySize = CGSizeMakeCopy(aSize);
[_cssAfterDictionary setObject:(aSize.width + @"px") forKey:@"width"];
[_cssAfterDictionary setObject:(aSize.height + @"px") forKey:@"height"];
[_cssAfterDictionary setObject:(MIN(aSize.width, aSize.height) + @"px") forKey:@"font-size"];
}
- (BOOL)_shouldBeResized
{
return YES;
}
- (BOOL)isMaterialIconImage
{
return YES;
}
- (_CPMaterialIconImage)invertedImage
{
if (!_cachedInvertedColor)
{
var sourceCSSColor = [_cssAfterDictionary objectForKey:@"color"] || @"rgba(0,0,0,1)",
sourceColor = [CPColor colorWithCSSString:sourceCSSColor];
_cachedInvertedColor = [CPColor colorWithRed:(1-[sourceColor redComponent])
green:(1-[sourceColor greenComponent])
blue:(1-[sourceColor blueComponent])
alpha:[sourceColor alphaComponent]];
}
return [self imageVersionWithColor:_cachedInvertedColor];
}
- (_CPMaterialIconImage)imageVersionWithColor:(CPColor)aColor
{
// We can't just set the color in the cssAfterDictionary as this would not be noticed as a new image,
// so -setImage won't do anything, so no visual refresh won't occur.
// The trick here is to keep in cache a clone of this image for each needed color.
var colorCSSString = [aColor cssString];
if (!_cachedColorVersions)
_cachedColorVersions = @{};
var cachedColorVersion = [_cachedColorVersions objectForKey:colorCSSString];
if (!cachedColorVersion)
{
cachedColorVersion = [self duplicate];
[cachedColorVersion _setCSSColor:colorCSSString];
[_cachedColorVersions setObject:cachedColorVersion forKey:colorCSSString];
}
return cachedColorVersion;
}
- (void)_setCSSColor:(CPString)aCSSColor
{
[_cssAfterDictionary setObject:aCSSColor forKey:@"color"];
}
@end
// MARK: -
@implementation CPThreePartImage : CPObject
{
CPArray _imageSlices;
@@ -1115,14 +890,3 @@ var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
}
@end
// MARK: -
@implementation CPImage (Duplication)
- (CPImage)duplicate
{
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:self]];
}
@end
+2 -24
View File
@@ -29,8 +29,6 @@
@global CPImagesPboardType
@global appkit_tag_dom_elements
@global document
@typedef CPImageAlignment
CPImageAlignCenter = 0;
CPImageAlignTop = 1;
@@ -101,8 +99,8 @@ var CPImageViewEmptyPlaceholderImage = nil;
{
#if PLATFORM(DOM)
var image = [self objectValue],
isCSSBasedImage = [image isCSSBased],
isIMGImageElement = _DOMImageElement && (_DOMImageElement.nodeName == "IMG");
isCSSBasedImage = [image isCSSBased],
isIMGImageElement = _DOMImageElement && (_DOMImageElement.nodeName == "IMG");
// First, check if we need to destroy a current DOM image element. This is the case if :
// - we have one but not the right one (that is a DIV but needing an IMG, and vice versa)
@@ -237,8 +235,6 @@ var CPImageViewEmptyPlaceholderImage = nil;
if (_hasShadow)
{
[self setClipsToBounds:NO];
_shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
[self addSubview:_shadowView];
@@ -448,24 +444,6 @@ var CPImageViewEmptyPlaceholderImage = nil;
#endif
}
#if PLATFORM(DOM)
if ([image isCSSBased] && [image _shouldBeResized])
{
[image _setDisplaySize:CGSizeMake(ROUND(width), ROUND(height))];
_cssStyleNode = [image applyCSSImageForView:self
onDOMElement:_DOMImageElement
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
}
if ([image isCSSBased])
{
_DOMImageElement.style.width = _DOMImageElement.width + 'px';
_DOMImageElement.style.height = _DOMImageElement.height + 'px';
}
#endif
_imageRect = CGRectMake(x, y, width, height);
if (_hasShadow)
-1
View File
@@ -30,7 +30,6 @@ CPStandardKeyBindings = {
@"@.": @"cancelOperation:",
@"@a": @"selectAll:",
@"@~$v": @"pasteAsPlainText:",
@"^a": @"moveToBeginningOfParagraph:",
@"^$a": @"moveToBeginningOfParagraphAndModifySelection:",
@"^b": @"moveBackward:",
+4 -8
View File
@@ -208,11 +208,7 @@ var CPBindingOperationAnd = 0,
options = [_info objectForKey:CPOptionsKey],
newValue = [destination valueForKeyPath:keyPath];
// give nil values the chance to be transformed to true via CPNegateBoolean (issue #1986)
if ((newValue == nil || CPIsControllerMarker(newValue)) && [options objectForKey:CPValueTransformerNameBindingOption] === CPNegateBooleanTransformerName)
[self setValue:[self transformValue:NO withOptions:options] forBinding:theBinding];
else if (CPIsControllerMarker(newValue))
if (CPIsControllerMarker(newValue))
{
[self raiseIfNotApplicable:newValue forKeyPath:keyPath options:options];
@@ -297,7 +293,7 @@ var CPBindingOperationAnd = 0,
// If the value is nil AND the source doesn't respond to setPlaceholderString: then
// we set the value to the placeholder. Otherwise, we do not want to short cut the process
// of setting the placeholder that is based on the fact that the value is nil.
if ((aValue == nil || aValue === [CPNull null])
if ((aValue === undefined || aValue === nil || aValue === [CPNull null])
&& ![_source respondsToSelector:@selector(setPlaceholderString:)])
aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
@@ -657,7 +653,7 @@ var CPBindingOperationAnd = 0,
keyPath = [info objectForKey:CPObservedKeyPathKey],
value = [object valueForKeyPath:keyPath];
if (value == nil)
if (value === nil || value === undefined)
{
[_source setEnabled:NO];
return;
@@ -800,7 +796,7 @@ var CPBindingOperationAnd = 0,
else
value = [theBinding transformValue:value withOptions:options];
if (value == nil)
if (value === nil || value === undefined)
value = @"";
result.value = result.value.replace("%{" + _patternPlaceholder + count + "}@", [value description]);
+28 -27
View File
@@ -22,10 +22,15 @@
@import "CPControl.j"
@import "CPWindow_Constants.j"
@import "CPSlider.j"
@global CPApp
@typedef CPTickMarkPosition
CPTickMarkBelow = 0;
CPTickMarkAbove = 1;
CPTickMarkLeft = CPTickMarkAbove;
CPTickMarkRight = CPTickMarkBelow;
@typedef CPLevelIndicatorStyle
CPRelevancyLevelIndicatorStyle = 0;
CPContinuousCapacityLevelIndicatorStyle = 1;
@@ -88,61 +93,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 +310,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",
+4 -141
View File
@@ -27,7 +27,6 @@
@import "CPKeyValueBinding.j"
@import "CPMenuItem.j"
@import "CALayer.j"
@global CPApp
@@ -165,7 +164,7 @@ var _CPMenuBarVisible = NO,
+ (void)_setOrRemoveMenuBarAttribute:(id)aValue forKey:(id)aKey
{
if (aValue == nil)
if (aValue === nil)
[_CPMenuBarAttributes removeObjectForKey:aKey];
else
[_CPMenuBarAttributes setObject:aValue forKey:aKey];
@@ -269,13 +268,10 @@ var _CPMenuBarVisible = NO,
if (self)
{
_title = aTitle;
// Use CPMutableArray instead of raw JS array for consistency with removeAllItems
_items = [CPMutableArray array];
_items = [];
_autoenablesItems = YES;
_showsStateColumn = YES;
_themeState = CPThemeStateNormal;
[self setMinimumWidth:0];
}
@@ -288,37 +284,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.
@@ -408,10 +373,6 @@ var _CPMenuBarVisible = NO,
[self willChangeValueForKey:@"items"];
_items = [CPMutableArray array];
[self didChangeValueForKey:@"items"];
// Ensure the main menu updates if cleared
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
/*!
@@ -428,9 +389,6 @@ var _CPMenuBarVisible = NO,
if ([aMenuItem menu] !== self || !_items)
return;
if (_menuWindow)
[[_menuWindow _menuView] tile];
[aMenuItem setValue:[aMenuItem valueForKey:@"changeCount"] + 1 forKey:@"changeCount"];
[[CPNotificationCenter defaultCenter]
@@ -831,10 +789,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)
@@ -925,10 +879,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],
@@ -1066,10 +1016,6 @@ var _CPMenuBarVisible = NO,
// an additional mouse move after all this, but not in other browsers.
// This will be fixed correctly with the coming run loop changes.
[_CPDisplayServer run];
// If layers are updated within the menu callback, the re-draw & re-layout doesn't occur until a mouse move.
// We force the update here.
[CALayer runLoopUpdateLayers];
}
/* @ignore */
@@ -1111,23 +1057,7 @@ var _CPMenuBarVisible = NO,
if ([anEvent _triggersKeyEquivalent:[item keyEquivalent] withModifierMask:[item keyEquivalentModifierMask]])
{
if ([item isEnabled])
{
// Flash the top-level item if this is the Main Menu
if (self === [CPApp mainMenu])
[self _flashItemAtIndex:index];
anEvent._isKeyEquivalent = YES; // prevent the menu keystroke from beeing inserted into textview
[self performActionForItemAtIndex:index];
#if PLATFORM(DOM)
// we are done with this event in cappuccino space. do not let the browser do something weird additionally (e.g. command-o).
// but we must not stop copy/paste events as these can only be handled by the browser at this time even if they are in our menu
// (until we move to CPTextView as the fieleditor)
if (characters != "c" && characters != "x" && characters != "v")
_CPDOMEventStop(anEvent._DOMEvent);
#endif
}
else
{
//beep?
@@ -1137,13 +1067,7 @@ var _CPMenuBarVisible = NO,
}
if ([[item submenu] performKeyEquivalent:anEvent])
{
// Flash the top-level item if a submenu handled the event
if (self === [CPApp mainMenu])
[self _flashItemAtIndex:index];
return YES;
}
}
return NO;
@@ -1207,7 +1131,7 @@ var _CPMenuBarVisible = NO,
}
}
- (CPMenu)_menuWithName:(CPString)aName
- (void)_menuWithName:(CPString)aName
{
if (aName === _name)
return self;
@@ -1223,25 +1147,6 @@ var _CPMenuBarVisible = NO,
return nil;
}
//
/*
@ignore
*/
- (void)_flashItemAtIndex:(int)anIndex
{
// If we are using a native bridge (like a desktop wrapper), let the OS handle the visual feedback.
if ([CPPlatform supportsNativeMainMenu])
return;
[self _highlightItemAtIndex:anIndex];
[self performSelector:@selector(_stopFlashingItem) withObject:nil afterDelay:0.2];
}
- (void)_stopFlashingItem
{
[self _highlightItemAtIndex:CPNotFound];
}
@end
@@ -1313,11 +1218,6 @@ var _CPMenuBarVisible = NO,
postNotificationName:CPMenuDidAddItemNotification
object:self
userInfo:@{ @"CPMenuItemIndex": anIndex }];
// FIX #1222: If this is the main menu, force the shared menu bar window to refresh its layout.
// This ensures new items are positioned correctly (e.g. not pushed to the far right by previous layout states).
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
- (void)removeObjectFromItemsAtIndex:(CPUInteger)anIndex
@@ -1333,10 +1233,6 @@ var _CPMenuBarVisible = NO,
postNotificationName:CPMenuDidRemoveItemNotification
object:self
userInfo:@{ @"CPMenuItemIndex": anIndex }];
// FIX #1222: Ensure the shared menu bar updates layout when items are removed.
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
@end
@@ -1369,9 +1265,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];
}
@@ -1400,36 +1293,6 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
@end
// MARK: -
@implementation CPMenu (CSSTheming)
+ (void)setNamedValue:(CPString)aName forKey:(CPString)aKey inAttributes:(CPDictionary)aDictionary forTheme:(CPTheme)aTheme
{
var value = [aTheme valueForAttributeWithName:aName forClass:_CPMenuView];
if (value)
[aDictionary setObject:value forKey:aKey];
else
[aDictionary removeObjectForKey:aKey];
}
+ (void)updateMenuBarAttributesWithTheme:(CPTheme)aTheme
{
var newAttributes = @{};
[CPMenu setNamedValue:@"menu-bar-text-color" forKey:@"CPMenuBarTextColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-title-color" forKey:@"CPMenuBarTitleColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-text-shadow-color" forKey:@"CPMenuBarTextShadowColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-title-shadow-color" forKey:@"CPMenuBarTitleShadowColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-highlight-color" forKey:@"CPMenuBarHighlightColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-highlight-text-color" forKey:@"CPMenuBarHighlightTextColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-highlight-text-shadow-color" forKey:@"CPMenuBarHighlightTextShadowColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setMenuBarAttributes:newAttributes];
}
@end
@import "_CPMenuBarWindow.j"
@import "_CPMenuWindow.j"
+26 -67
View File
@@ -25,14 +25,11 @@
@class _CPMenuView
@class CPMenu
@class CPMenuItem
@global CPMenuDidAddItemNotification
@global CPMenuDidChangeItemNotification
@global CPMenuDidRemoveItemNotification
@global document
@implementation _CPMenuBarWindow : CPPanel
{
CPView _highlightView;
@@ -132,12 +129,10 @@
- (void)setColor:(CPColor)aColor
{
var targetView = [[CPTheme defaultTheme] valueForAttributeWithName:@"css-based" forClass:CPView] ? [[self contentView] superview] : [self contentView];
if (!aColor)
[targetView setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-background-color" forClass:_CPMenuView]];
[[self contentView] setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-background-color" forClass:_CPMenuView]];
else
[targetView setBackgroundColor:aColor];
[[self contentView] setBackgroundColor:aColor];
}
- (void)setTextColor:(CPColor)aColor
@@ -282,16 +277,6 @@
[menuItemView setTextColor:_textColor];
[menuItemView setHidden:[item isHidden]];
// If first menu item has tag -1 and if there is a special theme value menu-bar-window-first-item-font,
// set the corresponding font. This is used to set bold on the first item of the menubar (à la Cocoa)
if ((index == 0) && ([item tag] == -1))
{
var firstItemFont = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-first-item-font" forClass:_CPMenuView];
if (firstItemFont)
[item setFont:firstItemFont];
}
[menuItemView synchronizeWithMenuItem];
[contentView addSubview:menuItemView];
@@ -365,46 +350,17 @@
- (CPFont)font
{
return [CPFont systemFontOfSize:[CPFont systemFontSize]];
[CPFont systemFontOfSize:[CPFont systemFontSize]];
}
- (void)tile
{
var bounds = [[self contentView] bounds],
height = CGRectGetHeight(bounds),
x = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-left-margin" forClass:_CPMenuView];
// 1. Layout the Icon (if present)
if (_iconImageView && ![_iconImageView isHidden])
{
var iconFrame = [_iconImageView frame];
iconFrame.origin.x = x;
// Vertically center
iconFrame.origin.y = (height - CGRectGetHeight(iconFrame)) / 2.0;
[_iconImageView setFrame:iconFrame];
x = CGRectGetMaxX(iconFrame) + 6.0; // Spacing between icon and title
}
// 2. Layout the Title (if present)
if (_titleField && [_titleField stringValue] && [[_titleField stringValue] length] > 0)
{
var titleFrame = [_titleField frame];
titleFrame.origin.x = x;
titleFrame.origin.y = (height - CGRectGetHeight(titleFrame)) / 2.0;
[_titleField setFrame:titleFrame];
x = CGRectGetMaxX(titleFrame) + 12.0; // Spacing between title and menu items
}
// 3. Layout the Menu Items
var items = [_menu itemArray],
index = 0,
count = items ? items.length : 0,
count = items.length,
x = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-left-margin" forClass:_CPMenuView],
y = 0.0,
isLeftAligned = YES;
for (; index < count; ++index)
@@ -419,18 +375,11 @@
continue;
}
// Fix for #1742: If a main menu item does not have a submenu, it should not appear in the menu bar.
if ([item isHidden] || ![item submenu])
{
[[item _menuItemView] setHidden:YES];
if ([item isHidden])
continue;
}
var menuItemView = [item _menuItemView];
[menuItemView setHidden:NO];
var frame = [menuItemView frame];
var menuItemView = [item _menuItemView],
frame = [menuItemView frame];
if (isLeftAligned)
{
@@ -445,6 +394,21 @@
x = CGRectGetMinX([menuItemView frame]);
}
}
var bounds = [[self contentView] bounds],
titleFrame = [_titleField frame];
if ([_iconImageView isHidden])
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(titleFrame)) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
else
{
var iconFrame = [_iconImageView frame],
iconWidth = CGRectGetWidth(iconFrame),
totalWidth = iconWidth + CGRectGetWidth(titleFrame);
[_iconImageView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(iconFrame)) / 2.0)];
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0 + iconWidth, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
}
}
- (void)setFrame:(CGRect)aRect display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate
@@ -485,18 +449,13 @@
{
var item = items[index];
if ([item isHidden] || [item isSeparatorItem] || ![item submenu])
if ([item isHidden] || [item isSeparatorItem])
continue;
if (CGRectContainsPoint([self rectForItemAtIndex:index], aPoint))
return index;
}
// If the mouse is within the menu bar bounds but not over an item
// (e.g. dragging far left or right), force the menu to unhighlight.
if (CGRectContainsPoint([[self contentView] bounds], aPoint))
[_menu _highlightItemAtIndex:CPNotFound];
return CPNotFound;
}
+26 -39
View File
@@ -28,8 +28,6 @@
@class CPWindow
@class _CPMenuWindow
@class _CPMenuView
@class CPMenuItem
@global CPApp
@@ -225,7 +223,7 @@ var STICKY_TIME_INTERVAL = 0.4,
_lastGlobalLocation = globalLocation;
// If the item isn't enabled its as if we clicked on nothing.
if ([activeItem _isMenuBarButton])
if (![activeItem isEnabled] || [activeItem _isMenuBarButton])
{
activeItemIndex = CPNotFound;
activeItem = nil;
@@ -354,23 +352,7 @@ var STICKY_TIME_INTERVAL = 0.4,
if ([activeMenuContainer isMenuBar])
newMenuOrigin = CGPointMake(CGRectGetMinX(activeItemRect), CGRectGetMaxY(activeItemRect));
else
{
// New theme attributes to have more precise submenus positioning
var defaultTheme = [CPTheme defaultTheme],
themeDeltaX = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-delta-x" forClass:_CPMenuView],
themeDeltaY = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-delta-y" forClass:_CPMenuView],
themeFirstDeltaY = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-first-level-delta-y" forClass:_CPMenuView],
activeMenuIndex = [_menuContainerStack indexOfObject:activeMenuContainer],
deltaX = themeDeltaX ? themeDeltaX : 0,
deltaY = themeDeltaY ? themeDeltaY : 0;
if (themeFirstDeltaY && (activeMenuIndex == 1) && [_menuContainerStack[0] isMenuBar])
deltaY += themeFirstDeltaY;
newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect)+deltaX, CGRectGetMinY(activeItemRect)+deltaY);
}
newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect), CGRectGetMinY(activeItemRect));
newMenuOrigin = [activeMenuContainer convertBaseToGlobal:newMenuOrigin];
@@ -571,7 +553,7 @@ var STICKY_TIME_INTERVAL = 0.4,
var iter = [selectorNames objectEnumerator],
obj;
while ((obj = [iter nextObject]) != nil)
while ((obj = [iter nextObject]) !== nil)
{
var aSelector = CPSelectorFromString(obj);
@@ -598,8 +580,6 @@ var STICKY_TIME_INTERVAL = 0.4,
[self selectNextItemBeginningWith:_keyBuffer inMenu:menu];
_lastGlobalLocation = nil;
}
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
}
- (void)selectNextItemBeginningWith:(CPString)characters inMenu:(CPMenu)menu
@@ -607,7 +587,7 @@ var STICKY_TIME_INTERVAL = 0.4,
var iter = [[menu itemArray] objectEnumerator],
obj;
while ((obj = [iter nextObject]) != nil)
while ((obj = [iter nextObject]) !== nil)
{
if ([obj isHidden] || ![obj isEnabled])
continue;
@@ -745,32 +725,39 @@ var STICKY_TIME_INTERVAL = 0.4,
- (void)moveDown:(CPMenu)menu
{
var index = menu._highlightedIndex + 1,
item;
// Search for the next enabled item
while ((index < [menu numberOfItems]) && (item = [menu itemAtIndex:index]) && ([item isSeparatorItem] || [item isHidden] || ![item isEnabled]))
index++;
var index = menu._highlightedIndex + 1;
if (index < [menu numberOfItems])
{
[menu _highlightItemAtIndex:index];
var item = [menu highlightedItem];
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
[self moveDown:menu];
}
else if (menu == [CPApp mainMenu])
[menu _highlightItemAtIndex:0];
}
- (void)moveUp:(CPMenu)menu
{
var index = menu._highlightedIndex - 1,
item;
var index = menu._highlightedIndex - 1;
// Search for the previous enabled item
while ((index >= 0) && (item = [menu itemAtIndex:index]) && ([item isSeparatorItem] || [item isHidden] || ![item isEnabled]))
index--;
if (index < 0)
{
if (index != CPNotFound || menu == [CPApp mainMenu])
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
if (index >= 0)
[menu _highlightItemAtIndex:index];
else if (menu == [CPApp mainMenu])
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
return;
}
[menu _highlightItemAtIndex:index];
var item = [menu highlightedItem];
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
[self moveUp:menu];
}
- (void)insertNewline:(CPMenu)menu
+10 -106
View File
@@ -26,7 +26,6 @@
@import "_CPMenuManager.j"
@class _CPMenuView
@class CPMenuItem
var _CPMenuWindowPool = [],
_CPMenuWindowPoolCapacity = 5,
@@ -62,9 +61,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 +107,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 +161,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
@@ -252,32 +233,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
// FIXME: This gets called far too often.
_unconstrainedFrame = CGRectMakeCopy(aFrame);
// 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 (CGRectGetMaxX(_unconstrainedFrame) > CGRectGetMaxX(_constraintRect))
{
var supermenuWindow = supermenu._menuWindow;
if (supermenuWindow)
{
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 +254,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,
@@ -465,21 +419,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end
// MARK: -
@implementation _CPMenuWindow (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[[self contentView] _setThemeIncludingDescendants:aTheme];
[_menuView _setThemeIncludingDescendants:aTheme];
[_menuView tile];
}
@end
// MARK: -
/*
@ignore
*/
@@ -510,7 +449,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@"menu-bar-window-background-color": [CPNull null],
@"menu-bar-window-background-selected-color": [CPNull null],
@"menu-bar-window-font": [CPNull null],
@"menu-bar-window-first-item-font": [CPNull null],
@"menu-bar-window-height": 30.0,
@"menu-bar-window-margin": 10.0,
@"menu-bar-window-left-margin": 10.0,
@@ -528,27 +466,9 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@"menu-general-icon-new": [CPNull null],
@"menu-general-icon-save": [CPNull null],
@"menu-general-icon-open": [CPNull null],
@"menu-window-submenu-delta-x": 0.0,
@"menu-window-submenu-delta-y": 0.0,
@"menu-window-submenu-first-level-delta-y": 0.0
};
}
- (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;
@@ -632,7 +552,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[view setFrameOrigin:CGPointMake(0.0, y)];
[view _setThemeIncludingDescendants:[CPTheme defaultTheme]];
[self addSubview:view];
var size = [view minSize],
@@ -663,18 +582,3 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
}
@end
// MARK: -
@implementation _CPMenuView (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[_menuItemViews makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
for (var i = 0, items = [[self menu] itemArray], count = [items count]; i < count; i++)
[[items[i] _menuItemView] _setThemeIncludingDescendants:aTheme];
}
@end
+5 -25
View File
@@ -826,7 +826,7 @@ CPControlKeyMask
return [[self menu] highlightedItem] == self;
}
// MARK: CPObject Overrides
#pragma mark CPObject Overrides
/*!
Returns a copy of the item. The copy does not belong If the item has a submenu, it is NOT copied.
@@ -867,7 +867,7 @@ CPControlKeyMask
return [self copy];
}
// MARK: Internal
#pragma mark Internal
/*
@ignore
@@ -897,20 +897,6 @@ CPControlKeyMask
@end
// MARK: -
@implementation CPMenuItem (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[_view _setThemeIncludingDescendants:aTheme];
[_menuItemView _setThemeIncludingDescendants:aTheme];
}
@end
// MARK: -
var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
CPMenuItemTitleKey = @"CPMenuItemTitleKey",
@@ -925,9 +911,6 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
CPMenuItemImageKey = @"CPMenuItemImageKey",
CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey",
CPMenuItemOnStateImageKey = @"CPMenuItemOnStateImageKey",
CPMenuItemOffStateImageKey = @"CPMenuItemOffStateImageKey",
CPMenuItemMixedStateImageKey = @"CPMenuItemMixedStateImageKey",
CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey",
CPMenuItemMenuKey = @"CPMenuItemMenuKey",
@@ -972,9 +955,9 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
_image = [aCoder decodeObjectForKey:CPMenuItemImageKey];
_alternateImage = [aCoder decodeObjectForKey:CPMenuItemAlternateImageKey];
_onStateImage = [aCoder decodeObjectForKey:CPMenuItemOnStateImageKey];
_offStateImage = [aCoder decodeObjectForKey:CPMenuItemOffStateImageKey];
_mixedStateImage = [aCoder decodeObjectForKey:CPMenuItemMixedStateImageKey];
// CPImage _onStateImage;
// CPImage _offStateImage;
// CPImage _mixedStateImage;
// This order matters because setSubmenu: needs _menu to be around.
_menu = [aCoder decodeObjectForKey:CPMenuItemMenuKey];
@@ -1020,9 +1003,6 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
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);
ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil);
ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil);
+4 -19
View File
@@ -31,9 +31,7 @@
+ (id)view
{
var themedHeight = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-view-height" forClass:_CPMenuItemStandardView];
return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, (themedHeight ? themedHeight : 10.0))];
return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 10.0)];
}
- (id)initWithFrame:(CGRect)aFrame
@@ -49,25 +47,12 @@
- (void)drawRect:(CGRect)aRect
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
bounds = [self bounds],
height = CGRectGetMaxY(bounds),
themedHeight = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-height" forClass:_CPMenuItemStandardView],
lineHeight = themedHeight ? themedHeight : 1.0;
bounds = [self bounds];
CGContextBeginPath(context);
CGContextSetLineWidth(context, lineHeight);
if (!!((height - lineHeight) % 2))
{
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
}
else
{
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)));
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)));
}
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
CGContextSetStrokeColor(context, [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-color" forClass:_CPMenuItemStandardView]);
CGContextStrokePath(context);
+23 -128
View File
@@ -41,8 +41,6 @@
_CPImageAndTextView _imageAndTextView;
_CPImageAndTextView _keyEquivalentView;
CPView _submenuIndicatorView;
BOOL _hasSubmenuIndicatorImage;
}
+ (CPString)defaultThemeClass
@@ -65,17 +63,12 @@
@"menu-item-default-mixed-state-image": [CPNull null],
@"menu-item-default-mixed-state-highlighted-image": [CPNull null],
@"menu-item-separator-color": [CPNull null],
@"menu-item-separator-height": 1.0,
@"menu-item-separator-view-height": 10.0,
@"left-margin": 3.0,
@"right-margin": 17.0,
@"state-column-width": 14.0,
@"indentation-width": 17.0,
@"vertical-margin": 4.0,
@"vertical-offset": 0.0,
@"right-columns-margin": 30.0,
@"submenu-indicator-image": [CPNull null],
@"submenu-indicator-highlighted-image": [CPNull null]
};
}
@@ -98,7 +91,6 @@
_stateView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)];
[_stateView setImageScaling:CPImageScaleNone];
[_stateView setImageAlignment:CPImageAlignCenter];
[self addSubview:_stateView];
@@ -117,24 +109,9 @@
[self addSubview:_keyEquivalentView];
// Do we have a submenu indicator image specified in the theme ?
_hasSubmenuIndicatorImage = !![self valueForThemeAttribute:@"submenu-indicator-image"];
if (_hasSubmenuIndicatorImage)
{
// Yes, then use an imageView
_submenuIndicatorView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[_submenuIndicatorView setImageAlignment:CPImageAlignCenter];
}
else
{
// No, then use self drawing _CPMenuItemSubmenuIndicatorView
_submenuIndicatorView = [[_CPMenuItemSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 8.0, 10.0)];
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
}
_submenuIndicatorView = [[_CPMenuItemSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 8.0, 10.0)];
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
[_submenuIndicatorView setAutoresizingMask:CPViewMinXMargin];
[self addSubview:_submenuIndicatorView];
@@ -145,26 +122,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 +130,7 @@
if (_highlighted)
return [CPColor whiteColor];
return [self currentValueForThemeAttribute:@"menu-item-text-color"];
return [self valueForThemeAttribute:@"menu-item-text-color"];
}
- (CPColor)textShadowColor
@@ -184,7 +141,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
@@ -192,31 +149,11 @@
_font = aFont;
}
- (CPFont)font
{
// Menu item font is forced local font or _menuItem font or system font
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
{
var x = [self valueForThemeAttribute:@"left-margin"] + [_menuItem indentationLevel] * [self valueForThemeAttribute:@"indentation-width"],
height = 0.0,
hasStateColumn = [[_menuItem menu] showsStateColumn],
myFont = [self font],
// 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]];
hasStateColumn = [[_menuItem menu] showsStateColumn];
if (hasStateColumn)
{
@@ -226,32 +163,27 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:queryState] || [_menuItem onStateImage]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image"]];
break;
case CPOffState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:queryState] || [_menuItem offStateImage]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image"]];
break;
case CPMixedState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:queryState] || [_menuItem mixedStateImage]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image"]];
break;
default:
break;
}
var stateViewFrameOrigin = [_stateView frameOrigin];
stateViewFrameOrigin.x = x;
[_stateView setFrameOrigin:stateViewFrameOrigin];
x += [self valueForThemeAttribute:@"state-column-width"];
}
else
[_stateView setHidden:YES];
[_imageAndTextView setFont:myFont];
[_imageAndTextView setFont:[_menuItem font] || _font];
[_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment];
[_imageAndTextView setImage:[_menuItem image]];
[_imageAndTextView setText:[_menuItem title]];
@@ -264,7 +196,7 @@
imageAndTextViewFrame.origin.x = x;
x += CGRectGetWidth(imageAndTextViewFrame);
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame)); // FIXME: here, height = 0 -> MAX useless
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame));
var hasKeyEquivalent = !![_menuItem keyEquivalent],
hasSubmenu = [_menuItem hasSubmenu];
@@ -274,14 +206,14 @@
if (hasKeyEquivalent)
{
[_keyEquivalentView setFont:myFont];
[_keyEquivalentView setFont:[_menuItem font] || _font];
[_keyEquivalentView setVerticalAlignment:CPCenterVerticalTextAlignment];
[_keyEquivalentView setImage:[_menuItem image]];
[_keyEquivalentView setText:[_menuItem keyEquivalentStringRepresentation]];
[_keyEquivalentView setTextColor:[self textColor]];
[_keyEquivalentView setTextShadowColor:[self textShadowColor]];
[_keyEquivalentView setTextShadowOffset:CGSizeMake(0, 1)];
[_keyEquivalentView setFrameOrigin:CGPointMake(x, verticalMargin)];
[_keyEquivalentView setFrameOrigin:CGPointMake(x, [self valueForThemeAttribute:@"vertical-margin"])];
[_keyEquivalentView sizeToFit];
var keyEquivalentViewFrame = [_keyEquivalentView frame];
@@ -298,14 +230,6 @@
if (hasSubmenu)
{
if (_hasSubmenuIndicatorImage)
{
var submenuIndicatorImage = [self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]];
[_submenuIndicatorView setImage:submenuIndicatorImage];
[_submenuIndicatorView setFrameSize:[submenuIndicatorImage size]];
}
[_submenuIndicatorView setHidden:NO];
var submenuViewFrame = [_submenuIndicatorView frame];
@@ -318,9 +242,9 @@
else
[_submenuIndicatorView setHidden:YES];
height += 2.0 * verticalMargin;
height += 2.0 * [self valueForThemeAttribute:@"vertical-margin"];
imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0) + verticalOffset;
imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0);
[_imageAndTextView setFrame:imageAndTextViewFrame];
if (hasStateColumn)
@@ -328,7 +252,7 @@
if (hasKeyEquivalent)
{
keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0) + verticalOffset;
keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0);
[_keyEquivalentView setFrame:keyEquivalentViewFrame];
}
@@ -353,11 +277,6 @@
_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;
[_imageAndTextView setTextColor:[self textColor]];
[_keyEquivalentView setTextColor:[self textColor]];
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
@@ -367,21 +286,13 @@
{
[self setBackgroundColor:[self valueForThemeAttribute:@"menu-item-selection-color"]];
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:queryState]];
else
[_submenuIndicatorView setColor:[self textColor]];
[_submenuIndicatorView setColor:[self textColor]];
}
else
{
[self setBackgroundColor:nil];
[_imageAndTextView setImage:[_menuItem image]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:queryState]];
else
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
}
if ([[_menuItem menu] showsStateColumn])
@@ -391,15 +302,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:queryState]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image"]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:queryState]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image"]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:queryState]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image"]];
break;
default:
@@ -411,15 +322,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:queryState]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image"]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:queryState]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image"]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:queryState]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image"]];
break;
default:
@@ -436,22 +347,6 @@
@end
// MARK: -
@implementation _CPMenuItemStandardView (CSSTheming)
// MARK: Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[[self subviews] makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
}
@end
// MARK: -
@implementation _CPMenuItemSubmenuIndicatorView : CPView
{
CPColor _color;
-47
View File
@@ -83,28 +83,6 @@
return self;
}
// override needed to cancel out the standard HUD propagation
- (void)viewDidMoveToWindow
{
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
// Propagate state to the actual content view (StandardView, Separator, etc.)
if ([_view respondsToSelector:@selector(setThemeState:)])
[_view setThemeState:aState];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
if ([_view respondsToSelector:@selector(unsetThemeState:)])
[_view unsetThemeState:aState];
}
- (CGSize)minSize
{
return _minSize;
@@ -274,31 +252,6 @@
@end
// MARK: -
@implementation _CPMenuItemView (CSSTheming)
// MARK: Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[_view _setThemeIncludingDescendants:aTheme];
// Items must also perform this (without this, only the selected item does)
[_imageAndTextView _setThemeIncludingDescendants:aTheme];
[_submenuView _setThemeIncludingDescendants:aTheme];
[[_menuItem view] _setThemeIncludingDescendants:aTheme];
if ([_view respondsToSelector:@selector(update)])
[_view update];
}
@end
// MARK: -
@implementation _CPMenuItemArrowView : CPView
{
CPColor _color;
+4 -27
View File
@@ -384,35 +384,13 @@
*/
- (void)_selectionDidChange
{
if (_selection == nil)
if (_selection === undefined || _selection === nil)
_selection = [[CPControllerSelectionProxy alloc] initWithController:self];
[_selection controllerDidChange];
[self didChangeValueForKey:@"selection"];
}
/*!
@ignore
These two private methods map CPTextField notifications to the CPEditorRegistration protocol
This should be generalized in the future:
The CPEditorRegistrationProtocol can be implemented in all controls that support editing, not just CPTextField.
In CPArrayController there are other cases than selection change when we need to review all editor pending changes. They should be covered, including the selection change, by the wider concept described by the methods commitEditing: (forces to end editing) and discardEditing: (pending changes are lost).
*/
- (void)_objectDidBeginEditing:(CPNotification)notification
{
[self objectDidBeginEditing:[notification object]];
}
/*!
@ignore
*/
- (void) _objectDidEndEditing:(CPNotification)notification
{
[self objectDidEndEditing:[notification object]];
}
/*!
@return id - Returns the keys which are being observed.
*/
@@ -792,7 +770,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
}
}
if (value == nil || value.isa && [value isEqual:[CPNull null]])
if (value === nil || value.isa && [value isEqual:[CPNull null]])
value = CPNullMarker;
return value;
@@ -808,8 +786,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
var value = [self _controllerMarkerForValues:values];
[_cachedValues setObject:value forKey:theKeyPath];
// Apple's implementation returns nil instead of CPNullMarker
return value === CPNullMarker ? nil : value;
return value;
}
else
return values;
@@ -949,4 +926,4 @@ var CPManagedProxyEntityNameKey = @"CPManagedProxyEntity
[aCoder encodeObject:[self fetchPredicate] forKey:CPManagedProxyFetchPredicateKey];
}
@end
@end
+142 -1014
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -121,8 +121,8 @@ CPDocModalWindowMask = 1 << 6;
}
// MARK: -
// MARK: Overrides
#pragma mark -
#pragma mark Overrides
/*!
@ignore
-1
View File
@@ -46,7 +46,6 @@ CPImagesPboardType = @"CPImagesPboardType";
CPVideosPboardType = @"CPVideosPboardType";
CPRTFPboardType = @"CPRTFPboardType";
_CPSmartPboardType = @"_CPSmartPboardType";
_CPASPboardType = @"_CPASPboardType";
UTF8PboardType = @"public.utf8-plain-text";
+4 -29
View File
@@ -45,13 +45,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
return "popup-button";
}
+ (CPDictionary)themeAttributes
{
return @{
@"menu-offset": CGSizeMake(0, 0)
};
}
+ (CPSet)keyPathsForValuesAffectingSelectedIndex
{
return [CPSet setWithObject:@"objectValue"];
@@ -79,8 +72,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
if (self)
{
[self setBezelStyle:CPRoundedBezelStyle];
[self selectItemAtIndex:CPNotFound];
_preferredEdge = CPMaxYEdge;
@@ -500,17 +491,9 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
if (index < 0)
{
[self addItemWithTitle:aTitle];
// this ist to match cocoa where setting an empty string does not add it but simply clears the title
// and sets objectValue to -1
if (aTitle === '')
[self selectItemAtIndex:-1];
else
{
[self addItemWithTitle:aTitle];
index = [self numberOfItems] - 1;
}
index = [self numberOfItems] - 1;
}
[self selectItemAtIndex:index];
@@ -691,25 +674,17 @@ 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,
menuOffset = [self currentValueForThemeAttribute:@"menu-offset"],
location = CGPointMake(menuOffset.width, CGRectGetMaxY(bounds) + menuOffset.height);
location = CGPointMake(0.0, CGRectGetMaxY(bounds) - 1);
}
else
{
var contentRect = [self contentRectForBounds:bounds],
positionedItem = [self selectedItem],
standardLeftMargin = [_CPMenuWindow _standardLeftMargin] + [_CPMenuItemStandardView _standardLeftMargin],
menuOffset = [self currentValueForThemeAttribute:@"menu-offset"],
location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin + menuOffset.width, menuOffset.height);
location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin, 0.0);
minimumWidth += standardLeftMargin;
+10 -10
View File
@@ -84,8 +84,8 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
/*!
Initialize the CPPopover witn default values
@@ -105,8 +105,8 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
}
// MARK: -
// MARK: Getters / Setters
#pragma mark -
#pragma mark Getters / Setters
/*!
Returns the current rect of the popover
@@ -220,8 +220,8 @@ Set the behavior of the CPPopover. It can be:
_implementedDelegateMethods |= CPPopoverDelegate_popover_didClose_;
}
// MARK: -
// MARK: Positioning
#pragma mark -
#pragma mark Positioning
/*!
Show the popover
@@ -298,8 +298,8 @@ Set the behavior of the CPPopover. It can be:
}
// MARK: -
// MARK: Action
#pragma mark -
#pragma mark Action
/*!
Close the popover
@@ -318,8 +318,8 @@ Set the behavior of the CPPopover. It can be:
}
// MARK: -
// MARK: Delegates
#pragma mark -
#pragma mark Delegates
/*! @ignore */
- (BOOL)_popoverWindowShouldClose
+33 -121
View File
@@ -43,6 +43,7 @@ CPProgressIndicatorSpinningStyle = 1;
*/
CPProgressIndicatorHUDBarStyle = 2;
var CPProgressIndicatorSpinningStyleColors = [];
/*!
@ingroup appkit
@@ -70,43 +71,6 @@ CPProgressIndicatorHUDBarStyle = 2;
BOOL _isDisplayedWhenStopped;
}
// Inject CSS Keyframes for spinning animation (Standard + WebKit)
+ (void)initialize
{
if (self !== [CPProgressIndicator class])
return;
#if PLATFORM(DOM)
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);
#endif
}
+ (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];
+11 -109
View File
@@ -67,18 +67,6 @@ CPRadioImageOffset = 4.0;
[[button1 radioGroup] selectedRadio] returns the currently selected
option.
UPDATE 09/2020 : Implementation of modern Cocoa behavior :
As in Cocoa, radio buttons grouping is now automatic.
To be associated in a common group (and so being mutually exclusive),
radio buttons must combine 2 criteria :
- same superview (enclosing view)
- same action
TODO: This first implementation uses "as is" CPRadioGroup. This could
be simplified (no more need for radio group action, for example)
*/
@implementation CPRadio : CPButton
{
@@ -172,71 +160,6 @@ CPRadioImageOffset = 4.0;
[CPApp sendAction:[_radioGroup action] to:[_radioGroup target] from:_radioGroup];
}
- (void)viewDidMoveToSuperview
{
[self _setRadioGroup];
[super viewDidMoveToSuperview];
}
- (void)setAction:(SEL)anAction
{
if (anAction === _action)
return;
[super setAction:anAction];
[self _setRadioGroup];
}
// MARK: Private methods
- (void)_setRadioGroup
{
// Implementation of modern Cocoa behavior : automatic radio group
// If no action is set or no superview, no grouping can be done.
if (![self action] || ![self superview])
{
// If this radio is in a group (size > 1), remove it.
if ([[self radioGroup] size] > 1)
{
[self setRadioGroup:[CPRadioGroup new]];
if ([self state] === CPOnState)
[_radioGroup _setSelectedRadio:self];
}
return;
}
// Search in superview subviews for other radio buttons having the same action.
// Take the one with the radio group having the greatest number of members.
var radioGroup;
for (var i = 0, superviewSubviews = [[self superview] subviews], count = [superviewSubviews count], aSubview, myAction = [self action], radioGroupSize = -1; (i < count); i++)
{
aSubview = superviewSubviews[i];
if ([aSubview isKindOfClass:CPRadio] && (aSubview !== self) && ([aSubview action] === myAction) && ([[aSubview radioGroup] size] > radioGroupSize))
{
radioGroup = [aSubview radioGroup];
radioGroupSize = [radioGroup size];
}
}
if (radioGroup)
[self setRadioGroup:radioGroup];
else
// No other radio buttons to group with found.
// It may be because this radio button was in a radio group and its action was changed.
// If this is the case, we must reisolate it in a new radio group.
if ([_radioGroup size] > 1)
[self setRadioGroup:[CPRadioGroup new]];
if ([self state] === CPOnState)
[_radioGroup _setSelectedRadio:self];
}
@end
var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
@@ -260,37 +183,21 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
[aCoder encodeObject:_radioGroup forKey:CPRadioRadioGroupKey];
}
// MARK: -
// MARK: Override methods from CPButton
- (CPThemeState)_contentVisualState
- (CPImage)image
{
// Note : Behavior differs from CPButton as title doesn't follow the highlightsBy content flag
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state];
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
return [self currentValueForThemeAttribute:@"image"];
}
- (CPThemeState)_imageVisualState
- (CPImage)alternateImage
{
// Note : Behavior differs from CPButton as we don't force "not selected" theme state
// when button is highglighted and selected
return [self currentValueForThemeAttribute:@"image"];
}
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state];
if (_isHighlighted && (_highlightsBy & CPContentsCellMask))
visualState = visualState.and(CPThemeStateHighlighted);
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
var startedTracking = [super startTrackingAt:aPoint];
[self highlight:YES];
return startedTracking;
}
@end
@@ -397,11 +304,6 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
return _radios;
}
- (int)size
{
return [_radios count];
}
- (void)setEnabled:(BOOL)enabled
{
[_radios makeObjectsPerformSelector:@selector(setEnabled:) withObject:enabled];
@@ -412,7 +314,7 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
[_radios makeObjectsPerformSelector:@selector(setHidden:) withObject:hidden];
}
// MARK: Private
#pragma mark Private
- (void)_addRadio:(CPRadio)aRadio
{
+1 -1
View File
@@ -397,7 +397,7 @@ var CPResponderNextResponderKey = @"CPResponderNextResponderKey",
- (void)encodeWithCoder:(CPCoder)aCoder
{
// This will come out nil on the other side with decodeObjectForKey:
if (_nextResponder != nil)
if (_nextResponder !== nil)
[aCoder encodeConditionalObject:_nextResponder forKey:CPResponderNextResponderKey];
[aCoder encodeObject:_menu forKey:CPResponderMenuKey];
+6 -11
View File
@@ -34,7 +34,7 @@
SEL _predicateAction @accessors(property=action);
}
// MARK: public methods
#pragma mark public methods
/*!
@ingroup appkit
@class CPPredicateEditor
@@ -251,7 +251,7 @@
return tree;
}
// MARK: Set the Predicate
#pragma mark Set the Predicate
- (void)setObjectValue:(id)objectValue
{
@@ -269,8 +269,6 @@
_currentAnimation = nil;
_sendAction = NO;
var rows = [];
if (predicate != nil)
{
if ((_nestingMode == CPRuleEditorNestingModeSimple || _nestingMode == CPRuleEditorNestingModeCompound)
@@ -278,13 +276,10 @@
predicate = [[CPCompoundPredicate alloc] initWithType:[self _compoundPredicateTypeForRootRows] subpredicates:[CPArray arrayWithObject:predicate]];
var row = [self _rowObjectFromPredicate:predicate];
if (row != nil)
[rows addObject:row];
[_boundArrayOwner setValue:[CPArray arrayWithObject:row] forKey:_boundArrayKeyPath];
}
[_boundArrayOwner setValue:rows forKey:_boundArrayKeyPath];
[self setAnimation:animation];
}
@@ -374,7 +369,7 @@
return row;
}
// MARK: Get the predicate
#pragma mark Get the predicate
- (void)_updatePredicate
{
@@ -452,7 +447,7 @@
return CPAndPredicateType;
}
// MARK: Control delegate
#pragma mark Control delegate
- (void)_sendRuleAction
{
@@ -487,7 +482,7 @@
}
*/
// MARK: RuleEditor delegate methods
#pragma mark RuleEditor delegate methods
- (int)_queryNumberOfChildrenOfItem:(id)rowItem withRowType:(CPRuleEditorRowType)type
{
+70 -189
View File
@@ -80,35 +80,6 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
@n An ordered to-many relation containing the display values for the row.
@n@n @c @@"criteria"
@n An ordered to-many relation containing the criteria for the row.
@n@n
Localization & Positional Reordering
@n
CPRuleEditor supports complete localization of menu items and grammatical positional reordering (sentence structure layout adjustment) via strings resource files (.strings) or custom programmatic CPDictionary tables.
@n@n
Since sentence structures vary significantly across languages, the editor can dynamically reposition views (such as popups, static labels, and text fields) from left to right to form grammatically correct sentences.
@n@n
Formatting Keys (English representation):
@n @c %[%]@@
@n Represents a popup button displaying its selected value (e.g. @c %[firstName]@@).
@n @c %@@
@n Represents an editable text input field.
@n Static text represents a literal label placed directly inside the formatting key.
@n@n
Example English format key:
@n @c "%[firstName]@ %[is equal to]@ %@"
@n@n
Translation Patterns (Target language):
@n Positional specifiers such as @c %1$@@, @c %2$@@, @c %3$@@ dictate the visual order of views from left to right.
@n Bracketed values inside positional specifiers (e.g. @c %1$[Nombre]@@) define the localized title for popup selection items.
@n Literal text outside the specifiers (such as @c "y" or @c "und") is automatically instantiated as static text labels positioned between controls.
@n@n
Example translations:
@n@n
Spanish (Reorders to: [1: Name] y [3: Value] [2: are equal]):
@n @c "%[firstName]@ %[is equal to]@ %@" = "%1$[Nombre]@ y %3$@ %2$[son iguales]@";
@n@n
German (Reorders to: [1: First Name] und [3: Value] [2: are equal]):
@n @c "%[firstName]@ %[is equal to]@ %@" = "%1$[Vorname]@ und %3$@ %2$[sind gleich]@";
*/
@implementation CPRuleEditor : CPControl
@@ -156,7 +127,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
BOOL _isKeyDown;
BOOL _nestingModeDidChange;
_CPRuleEditorLocalizer _standardLocalizer;
_CPRuleEditorLocalizer _standardLocalizer @accessors(property=standardLocalizer);
CPDictionary _itemsAndValuesToAddForRowType;
}
@@ -186,7 +157,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
if (self !== nil)
{
_slices = [[CPMutableArray alloc] init];
@@ -236,34 +207,8 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType,nil]];
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_ruleEditorLocalizerDidLoad:)
name:@"_CPRuleEditorLocalizerDidLoadNotification"
object:nil];
}
- (void)_ruleEditorLocalizerDidLoad:(CPNotification)aNotification
{
if ([aNotification object] === [self standardLocalizer])
{
// Defer execution to the next run loop cycle so that any active slice
// insertions have fully completed and are present in the `_slices` array.
[[CPRunLoop mainRunLoop] performBlock:function() {
var count = [_slices count];
for (var i = 0; i < count; i++)
{
var slice = [_slices objectAtIndex:i];
[slice _reconfigureSubviews];
[slice _updateButtonVisibilities]; // Force updates on row button tooltips
}
[self _updatePredicate];
[self _sendRuleAction];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
}
}
/*! @endcond */
/*!
@@ -439,7 +384,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/
- (CPDictionary)formattingDictionary
{
return [[self standardLocalizer] dictionary];
return [_standardLocalizer dictionary];
}
/*!
@@ -451,9 +396,6 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/
- (void)setFormattingDictionary:(CPDictionary)dictionary
{
if (_standardLocalizer == nil)
_standardLocalizer = [_CPRuleEditorLocalizer new];
[_standardLocalizer setDictionary:dictionary];
_stringsFilename = nil;
}
@@ -477,7 +419,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/
- (void)setFormattingStringsFilename:(CPString)stringsFilename
{
if (_standardLocalizer == nil)
if (_standardLocalizer === nil)
_standardLocalizer = [_CPRuleEditorLocalizer new];
if (_stringsFilename !== stringsFilename)
@@ -485,32 +427,19 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
// Convert an empty string to nil
_stringsFilename = stringsFilename || nil;
if (stringsFilename != nil)
if (stringsFilename !== nil)
{
if (![stringsFilename hasSuffix:@".strings"])
stringsFilename = stringsFilename + @".strings";
var path = [[CPBundle mainBundle] pathForResource:stringsFilename];
if (path != nil)
if (path !== nil)
[_standardLocalizer loadContentOfURL:[CPURL URLWithString:path]];
}
}
}
- (_CPRuleEditorLocalizer)standardLocalizer
{
if (_standardLocalizer == nil)
_standardLocalizer = [_CPRuleEditorLocalizer new];
return _standardLocalizer;
}
- (void)setStandardLocalizer:(_CPRuleEditorLocalizer)aLocalizer
{
_standardLocalizer = aLocalizer;
}
/*!
@name Providing Data
*/
@@ -536,7 +465,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/
- (void)setCriteria:(CPArray)criteria andDisplayValues:(CPArray)values forRowAtIndex:(int)rowIndex
{
if (criteria == nil || values == nil)
if (criteria === nil || values === nil)
[CPException raise:CPInvalidArgumentException reason:_cmd + @". criteria and values parameters must not be nil."];
if (rowIndex < 0 || rowIndex >= [self numberOfRows])
@@ -609,7 +538,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
if ([self rowTypeForRow:current_index] === CPRuleEditorRowTypeCompound)
{
var candidate = [[self _rowCacheForIndex:current_index] rowObject],
subObjects = [self _subrowObjectsOfObject:candidate]; // Standard direct array query
subObjects = [[self _subrowObjectsOfObject:candidate] _representedObject];
if ([subObjects indexOfObjectIdenticalTo:targetObject] !== CPNotFound)
return current_index;
@@ -676,7 +605,7 @@ TODO: implement
for (var i = rowIndex + 1; i < count; i++)
{
var candidate = [[self _rowCacheForIndex:i] rowObject],
indexInSubrows = [subobjects indexOfObjectIdenticalTo:candidate]; // Standard direct array query
indexInSubrows = [[subobjects _representedObject] indexOfObjectIdenticalTo:candidate];
if (indexInSubrows !== CPNotFound)
{
@@ -845,7 +774,7 @@ TODO: implement
while (current_index !== CPNotFound)
{
var rowObject = [[self _rowCacheForIndex:current_index] rowObject],
relativeChildIndex = [subrows indexOfObjectIdenticalTo:rowObject]; // Standard direct array query
relativeChildIndex = [[subrows _representedObject] indexOfObjectIdenticalTo:rowObject];
if (relativeChildIndex !== CPNotFound)
[childsIndexes addIndex:relativeChildIndex];
@@ -902,6 +831,7 @@ TODO: implement
for (i = 0; i < count; i++)
{
var item = [items objectAtIndex:i],
//var displayValue = [self _queryValueForItem:item inRow:aRow]; Ask the delegate or get cached value ?.
displayValue = [[self displayValuesForRow:aRow] objectAtIndex:i],
predpart = [self _sendDelegateRuleEditorPredicatePartsForCriterion:item withDisplayValue:displayValue inRow:aRow];
@@ -919,11 +849,10 @@ TODO: implement
return nil;
var current_index = [subrowsIndexes firstIndex];
while (current_index !== CPNotFound)
{
var subpredicate = [self predicateForRow:current_index];
if (subpredicate != nil)
if (subpredicate !== nil)
[subpredicates addObject:subpredicate];
current_index = [subrowsIndexes indexGreaterThanIndex:current_index];
@@ -959,33 +888,33 @@ TODO: implement
modifier = [predicateParts objectForKey:CPRuleEditorPredicateComparisonModifier],
selector = CPSelectorFromString([predicateParts objectForKey:CPRuleEditorPredicateCustomSelector]);
if (lhs == nil)
if (lhs === nil)
{
CPLogConsole(@"missing left expression in predicate parts dictionary");
return NULL;
}
if (rhs == nil)
if (rhs === nil)
{
CPLogConsole(@"missing right expression in predicate parts dictionary");
return NULL;
}
if (selector == nil && operator == nil)
if (selector === nil && operator === nil)
{
CPLogConsole(@"missing operator and selector in predicate parts dictionary");
return NULL;
}
if (modifier == nil)
if (modifier === nil)
CPLogConsole(@"missing modifier in predicate parts dictionary. Setting default: CPDirectPredicateModifier");
if (options == nil)
if (options === nil)
CPLogConsole(@"missing options in predicate parts dictionary. Setting default: CPCaseInsensitivePredicateOption");
try
{
if (selector != nil)
if (selector !== nil)
predicate = [CPComparisonPredicate predicateWithLeftExpression:lhs
rightExpression:rhs
customSelector:selector
@@ -1238,7 +1167,7 @@ TODO: implement
- (BOOL)_wantsRowAnimations
{
return (_currentAnimation != nil);
return (_currentAnimation !== nil);
}
- (void)_updateButtonVisibilities
@@ -1304,7 +1233,7 @@ TODO: implement
return shouldHide;
}
// MARK: Rows management
#pragma mark Rows management
- (id)_rowCacheForIndex:(int)index
{
@@ -1349,28 +1278,23 @@ TODO: implement
while (current_index !== CPNotFound)
{
var parentIndex = [self parentRowForRow:current_index];
// If the row has a valid parent in the editor (i.e. not a root row)
if (parentIndex !== -1)
var parentIndex = [self parentRowForRow:current_index],
subrowsIndexes = [self subrowIndexesForRow:parentIndex];
if ([subrowsIndexes count] === 1)
{
var subrowsIndexes = [self subrowIndexesForRow:parentIndex];
if (parentIndex !== -1)
return [CPIndexSet indexSetWithIndex:0];
// If deleting this row leaves the parent with no remaining child rows
if ([subrowsIndexes count] === 1)
{
[childlessParents addIndex:parentIndex];
// Recursively check if deleting this parent row leaves the grandparent childless
var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]];
[childlessParents addIndexes:childlessGranPa];
}
var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]];
[childlessParents addIndexes:childlessGranPa];
}
current_index = [indexes indexGreaterThanIndex:current_index];
}
return childlessParents;
// (id)-[RuleEditor _includeSubslicesForSlicesAtIndexes:]
}
- (CPIndexSet)_includeSubslicesForSlicesAtIndexes:(CPIndexSet)indexes
@@ -1432,25 +1356,8 @@ TODO: implement
if ([self rowTypeForRow:row] === type && itemIndex < [aCriteria count])
{
// Verify that this row's parent path matches the path currently being built
var pathMatches = true;
for (var p = 0; p < itemIndex; p++)
{
var criterionA = [aCriteria objectAtIndex:p],
criterionB = [items objectAtIndex:p];
if (criterionA !== criterionB && (typeof criterionA.isEqual !== "function" || ![criterionA isEqual:criterionB]))
{
pathMatches = false;
break;
}
}
if (pathMatches)
{
var crit = [aCriteria objectAtIndex:itemIndex];
[current_criterions addObject:crit];
}
var crit = [aCriteria objectAtIndex:itemIndex];
[current_criterions addObject:crit];
}
}
@@ -1566,7 +1473,7 @@ TODO: implement
return row;
}
// MARK: Key value observing
#pragma mark Key value observing
- (void)_startObservingRowObjectsRecursively:(CPArray)rowObjects
{
@@ -1846,7 +1753,7 @@ TODO: implement
{
var subpredicate = [self predicateForRow:current_index];
if (subpredicate != nil)
if (subpredicate !== nil)
[subpredicates addObject:subpredicate];
current_index = [subindexes indexGreaterThanIndex:current_index];
@@ -1867,12 +1774,7 @@ TODO: implement
- (_CPRuleEditorViewSliceRow)_createNewSliceWithFrame:(CGRect)frame ruleEditorView:(CPRuleEditor)editor
{
var slice = [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor];
// Ensure the slice resizes with the editor
[slice setAutoresizingMask:CPViewWidthSizable];
return slice;
return [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor];
}
- (void)_reconfigureSubviewsAnimate:(BOOL)animate
@@ -1893,7 +1795,7 @@ TODO: implement
startRect = [aslice frame],
startIndex = [aslice rowIndex] - 1;
if ([aslice superview] == nil)
if ([aslice superview] === nil)
{
startRect = CGRectMake(0, startIndex * _sliceHeight, CGRectGetWidth(startRect), _sliceHeight);
[aslice _reconfigureSubviews];
@@ -1957,63 +1859,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
@@ -2023,17 +1919,17 @@ TODO: implement
- (CPString)_toolTipForAddCompoundRowButton
{
return [[self standardLocalizer] localizedStringForString:@"Add compound row"];
return [_standardLocalizer localizedStringForString:@"Add compound row"];
}
- (CPString)_toolTipForAddSimpleRowButton
{
return [[self standardLocalizer] localizedStringForString:@"Add row"];
return [_standardLocalizer localizedStringForString:@"Add row"];
}
- (CPString)_toolTipForDeleteRowButton
{
return [[self standardLocalizer] localizedStringForString:@"Delete row"];
return [_standardLocalizer localizedStringForString:@"Delete row"];
}
- (void)_updateSliceIndentations
@@ -2225,13 +2121,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];
@@ -2239,7 +2129,7 @@ TODO: implement
- (BOOL)_dragShouldBeginFromMouseDown:(CPView)view
{
return (([self nestingMode] === CPRuleEditorNestingModeList || [view rowIndex] !== 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows == nil);
return (([self nestingMode] === CPRuleEditorNestingModeList || [view rowIndex] !== 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows === nil);
}
- (BOOL)_performDragForSlice:(id)slice withEvent:(CPEvent)event
@@ -2354,41 +2244,33 @@ TODO: implement
- (BOOL)performDragOperation:(id /*< CPDraggingInfo >*/)info
{
var object;
var aboveInsertIndexCount = 0,
object,
removeIndex;
var rowObjects = [_rowCache valueForKey:@"rowObject"],
index = [_draggingRows lastIndex];
var firstDraggingIndex = [_draggingRows firstIndex],
parentRowIndex = [self parentRowForRow:firstDraggingIndex],
var parentRowIndex = [self parentRowForRow:index], // first index of draggingrows
parentRowObject = (parentRowIndex === -1) ? _boundArrayOwner : [[self _rowCacheForIndex:parentRowIndex] rowObject],
insertIndex = _subviewIndexOfDropLine;
while (index !== CPNotFound)
{
// Identify if this row's parent is also inside the dragging set.
// If it is, we skip it because it will move automatically with its parent.
var parentOfCurrent = [self parentRowForRow:index];
if (parentOfCurrent !== -1 && [_draggingRows containsIndex:parentOfCurrent])
if (index >= insertIndex)
{
index = [_draggingRows indexLessThanIndex:index];
continue;
removeIndex = index + aboveInsertIndexCount;
aboveInsertIndexCount += 1;
}
else
{
removeIndex = index;
insertIndex -= 1;
}
object = [rowObjects objectAtIndex:index];
// Find the current live index of the object inside the editor
var cache = [self _searchCacheForRowObject:object];
var liveIndex = [_rowCache indexOfObjectIdenticalTo:cache];
if (liveIndex !== CPNotFound)
{
if (liveIndex < insertIndex)
insertIndex -= 1;
[self removeRowAtIndex:liveIndex];
[[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1];
}
object = [rowObjects objectAtIndex:removeIndex];
[self removeRowAtIndex:removeIndex];
[[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1];
index = [_draggingRows indexLessThanIndex:index];
}
@@ -2398,7 +2280,6 @@ TODO: implement
return YES;
}
- (CPIndexSet)_draggingTypes
{
return [CPIndexSet indexSetWithIndex:CPDragOperationMove];
@@ -2440,7 +2321,7 @@ TODO: implement
- (void)_postRowCountChangedNotificationOfType:(CPString)notificationName indexes:indexes
{
var userInfo = indexes == nil ? @{} : @{ "indexes": indexes };
var userInfo = indexes === nil ? @{} : @{ "indexes": indexes };
[[CPNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:userInfo];
}
@@ -2505,7 +2386,7 @@ TODO: implement
var criteria = [self criteriaForRow:aRow];
indexofCriterion = [criteria indexOfObject:criterion];
if (parentItem != nil
if (parentItem !== nil
&& indexofCriterion !== CPNotFound
&& indexofCriterion < [criteria count] - 1)
{
@@ -2588,7 +2469,7 @@ var CPRuleEditorAlignmentGridWidthKey = @"CPRuleEditorAlignmentGridWidth",
- (id)initWithCoder:(CPCoder)coder
{
self = [super initWithCoder:coder];
if (self)
if (self !== nil)
{
[self setFormattingStringsFilename:[coder decodeObjectForKey:CPRuleEditorStringsFilenameKey]];
_alignmentGridWidth = [coder decodeFloatForKey:CPRuleEditorAlignmentGridWidthKey];
@@ -2672,7 +2553,7 @@ var CriteriaKey = @"criteria",
- (id)initWithCoder:(CPCoder)coder
{
self = [super init];
if (self)
if (self !== nil)
{
subrows = [coder decodeObjectForKey:SubrowsKey];
criteria = [coder decodeObjectForKey:CriteriaKey];
@@ -131,7 +131,7 @@
{
var title = [self title];
if (title != nil)
if (title !== nil)
return title;
return [self templateView];
+4 -234
View File
@@ -40,7 +40,7 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
- (void)reloadIfNeeded
{
if (connection != nil) // Connection waiting
if (connection !== nil) // Connection waiting
{
connection = nil;
@@ -51,7 +51,7 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
- (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)rawString
{
if (connection != nil && rawString != nil)
if (connection !== nil && rawString !== nil)
[self loadContent:rawString];
connection = nil;
@@ -77,251 +77,21 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
}
_dictionary = [CPDictionary dictionaryWithDictionary:dict];
// Post notification to let the rule editor know the translation dictionary is ready
[[CPNotificationCenter defaultCenter] postNotificationName:@"_CPRuleEditorLocalizerDidLoadNotification" object:self];
}
- (CPString)localizedStringForString:(CPString)aString
{
[self reloadIfNeeded];
if (_dictionary != nil && aString != nil)
if (_dictionary !== nil && aString !== nil)
{
var localized = [_dictionary objectForKey:aString];
if (localized != nil)
if (localized !== nil)
return localized;
}
return aString;
}
// MARK: - Formatting & Reordering Helpers
- (CPString)_englishRepresentationForView:(id)aView
{
if ([aView isKindOfClass:[CPPopUpButton class]])
{
var selectedItem = [aView selectedItem];
if (selectedItem)
{
var originalTitle = selectedItem._originalTitle;
// Fallback: If not cached directly, inspect representedObject payload dictionary
if (!originalTitle)
{
var rep = [selectedItem representedObject];
if (rep && typeof rep === "object" && [rep respondsToSelector:@selector(objectForKey:)])
{
originalTitle = [rep objectForKey:@"value"];
}
else if (rep && typeof rep === "string")
{
originalTitle = rep;
}
}
if (!originalTitle)
{
originalTitle = [selectedItem title];
}
return "%[" + originalTitle + "]@";
}
return "%[]@";
}
else if ([aView isKindOfClass:[CPTextField class]] && ![aView isEditable])
{
return aView._originalText || [aView stringValue];
}
else
{
return "%@";
}
}
- (CPString)formattingKeyForViews:(CPArray)views
{
var keyParts = [];
var count = [views count];
for (var i = 0; i < count; i++)
{
var view = [views objectAtIndex:i];
[keyParts addObject:[self _englishRepresentationForView:view]];
}
return [keyParts componentsJoinedByString:@" "];
}
- (void)localizeMenuItemsForViews:(CPArray)views
{
var count = [views count];
for (var i = 0; i < count; i++)
{
var view = [views objectAtIndex:i];
if ([view isKindOfClass:[CPPopUpButton class]])
{
var menuItems = [view itemArray];
var menuItemsCount = [menuItems count];
var selectedItem = [view selectedItem];
for (var j = 0; j < menuItemsCount; j++)
{
var item = [menuItems objectAtIndex:j];
if (!item._originalTitle)
{
var rep = [item representedObject];
if (rep && typeof rep === "object" && [rep respondsToSelector:@selector(objectForKey:)])
{
item._originalTitle = [rep objectForKey:@"value"];
}
else
{
item._originalTitle = [item title];
}
}
// Temporarily select item to generate formatting key context
[view selectItem:item];
var tempKey = [self formattingKeyForViews:views];
var tempPattern = [self localizedStringForString:tempKey];
if (tempPattern !== tempKey)
{
var regex = /%(\d+)\$(?:\[([^\]]+)\])?@/g;
var match;
while ((match = regex.exec(tempPattern)) !== null)
{
var position = parseInt(match[1], 10) - 1;
var translatedValue = match[2];
if (position === i && translatedValue)
{
[item setTitle:translatedValue];
break;
}
}
}
else
{
[item setTitle:item._originalTitle];
}
}
if (selectedItem)
{
[view selectItem:selectedItem];
}
}
}
}
- (CPArray)localizeAndReorderViews:(CPArray)views
{
var key = [self formattingKeyForViews:views];
var localizedPattern = [self localizedStringForString:key];
if (localizedPattern === key)
{
var count = [views count];
for (var i = 0; i < count; i++)
{
var originalView = [views objectAtIndex:i];
if ([originalView isKindOfClass:[CPPopUpButton class]])
{
var selectedItem = [originalView selectedItem];
if (selectedItem && selectedItem._originalTitle)
{
[selectedItem setTitle:selectedItem._originalTitle];
}
}
else if ([originalView respondsToSelector:@selector(setStringValue:)] && originalView._originalText)
{
[originalView setStringValue:originalView._originalText];
if ([originalView isKindOfClass:[CPTextField class]] && ![originalView isEditable])
{
var font = [originalView font] || [CPFont systemFontOfSize:[CPFont systemFontSize]],
size = [originalView._originalText sizeWithFont:font];
[originalView setFrameSize:CGSizeMake(size.width + 4, CGRectGetHeight([originalView frame]))];
}
}
}
return views;
}
var newViews = [CPMutableArray array];
var regex = /%(\d+)\$(?:\[([^\]]+)\])?@/g;
var lastIndex = 0;
var match;
while ((match = regex.exec(localizedPattern)) !== null)
{
var literalText = localizedPattern.substring(lastIndex, match.index);
// Only add a label if there are actual non-whitespace characters (like 'y')
if (literalText.length > 0 && /\S/.test(literalText))
{
var label = [CPTextField labelWithTitle:literalText];
[newViews addObject:label];
}
var position = parseInt(match[1], 10) - 1;
var translatedValue = match[2];
if (position >= 0 && position < [views count])
{
var originalView = [views objectAtIndex:position];
if (translatedValue !== undefined && translatedValue !== null)
{
if ([originalView isKindOfClass:[CPPopUpButton class]])
{
var selectedItem = [originalView selectedItem];
if (selectedItem)
{
if (!selectedItem._originalTitle)
{
selectedItem._originalTitle = [selectedItem title];
}
[selectedItem setTitle:translatedValue];
}
}
else if ([originalView respondsToSelector:@selector(setStringValue:)])
{
[originalView setStringValue:translatedValue];
// Recalculate frame size if it is a static CPTextField to avoid visual clipping
if ([originalView isKindOfClass:[CPTextField class]] && ![originalView isEditable])
{
var font = [originalView font] || [CPFont systemFontOfSize:[CPFont systemFontSize]],
size = [translatedValue sizeWithFont:font];
[originalView setFrameSize:CGSizeMake(size.width + 4, CGRectGetHeight([originalView frame]))];
}
}
}
[newViews addObject:originalView];
}
lastIndex = regex.lastIndex;
}
if (lastIndex < localizedPattern.length)
{
var literalText = localizedPattern.substring(lastIndex);
// Only add a label if there are actual non-whitespace characters
if (literalText.length > 0 && /\S/.test(literalText))
{
var label = [CPTextField labelWithTitle:literalText];
[newViews addObject:label];
}
}
return newViews;
}
@end
+23 -67
View File
@@ -49,13 +49,6 @@
return self;
}
- (void)setRowIndex:(int)anIndex
{
_rowIndex = anIndex;
[self _updateBackgroundColor];
[self setNeedsDisplay:YES];
}
- (void)_setSelected:(BOOL)select
{
if (select == _selected)
@@ -64,47 +57,6 @@
var selector = select ? @selector(setThemeState:) : @selector(unsetThemeState:);
[[self subviews] makeObjectsPerformSelector:selector withObject:CPThemeStateSelectedDataView];
_selected = select;
[self _updateBackgroundColor];
[self setNeedsDisplay:YES];
}
- (void)_updateBackgroundColor
{
var color = nil;
if ([self _isSelected])
{
color = [_ruleEditor _selectedRowColor];
}
else
{
var colors = [_ruleEditor _backgroundColors],
count = [colors count];
if (count > 0)
color = [colors objectAtIndex:(_rowIndex % count)];
}
[self setBackgroundColor:color];
}
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
[self _updateBackgroundColor];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[self _updateBackgroundColor];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self _updateBackgroundColor];
}
- (void)drawRect:(CGRect)rect
@@ -114,31 +66,35 @@
maxX = CGRectGetWidth(bounds),
maxY = CGRectGetHeight(bounds);
// Note: Background is now handled by setBackgroundColor in _updateBackgroundColor
// to support CSS-based colors and transparency correctly.
// Draw background
if ([self _isSelected])
_backgroundColor = [_ruleEditor _selectedRowColor];
else
{
var colors = [_ruleEditor _backgroundColors],
count = [colors count];
_backgroundColor = [colors objectAtIndex:(_rowIndex % count)];
}
CGContextSetFillColor(context, _backgroundColor);
CGContextFillRect(context, rect);
// Draw Top Border
var topColor = [_ruleEditor _sliceTopBorderColor];
if (topColor)
{
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, maxX, 0);
CGContextSetStrokeColor(context, topColor);
CGContextStrokePath(context);
}
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, maxX, 0);
CGContextSetStrokeColor(context, [_ruleEditor _sliceTopBorderColor]);
CGContextStrokePath(context);
// Draw Bottom Border
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, maxY);
CGContextAddLineToPoint(context, maxX, maxY);
var bottomColor = (_rowIndex == [_ruleEditor _lastRow]) ? [_ruleEditor _sliceLastBottomBorderColor] : [_ruleEditor _sliceBottomBorderColor];
if (bottomColor)
{
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, maxY);
CGContextAddLineToPoint(context, maxX, maxY);
CGContextSetStrokeColor(context, bottomColor);
CGContextStrokePath(context);
}
CGContextSetStrokeColor(context, bottomColor);
CGContextStrokePath(context);
}
- (void)mouseDown:(CPEvent)theEvent
+18 -86
View File
@@ -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];
@@ -135,32 +114,8 @@
- (CPMenuItem)_createMenuItemWithTitle:(CPString )title
{
var originalTitle = title;
var localizedTitle = [[_ruleEditor standardLocalizer] localizedStringForString:title];
var item = [[CPMenuItem alloc] initWithTitle:localizedTitle action:nil keyEquivalent:@""];
// Cache the raw English title for pattern-matching
item._originalTitle = originalTitle;
return item;
}
- (CPTextField)_createStaticTextFieldWithStringValue:(CPString)text
{
var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()],
ruleEditorFont = [_ruleEditor font],
font = [CPFont fontWithName:[ruleEditorFont familyName] size:[ruleEditorFont size] + 2],
localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text],
size = [localizedText sizeWithFont:font];
[textField setFrameSize:CGSizeMake(size.width + 4, [_ruleEditor rowHeight])];
[textField setValue:font forThemeAttribute:@"font"];
[textField setValue:[_ruleEditor _verticalAlignment] forThemeAttribute:@"vertical-alignment"];
[textField setStringValue:localizedText];
// Cache the raw English text for pattern-matching
textField._originalText = text;
return textField;
title = [[_ruleEditor standardLocalizer] localizedStringForString:title];
return [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
}
- (CPPopUpButton)_createPopUpButtonWithItems:(CPArray)itemsArray selectedItemIndex:(int)index
@@ -187,6 +142,22 @@
return [CPMenuItem separatorItem];
}
- (CPTextField)_createStaticTextFieldWithStringValue:(CPString)text
{
var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()],
ruleEditorFont = [_ruleEditor font],
font = [CPFont fontWithName:[ruleEditorFont familyName] size:[ruleEditorFont size] + 2],
localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text],
size = [localizedText sizeWithFont:font];
[textField setFrameSize:CGSizeMake(size.width + 4, [_ruleEditor rowHeight])];
[textField setValue:font forThemeAttribute:@"font"];
[textField setValue:[_ruleEditor _verticalAlignment] forThemeAttribute:@"vertical-alignment"];
[textField setStringValue:localizedText];
return textField;
}
- (void)_addOption:(id)sender
{
if (_rowIndex == [_ruleEditor numberOfRows] - 1)
@@ -363,28 +334,6 @@
[_correspondingRuleItems setArray:ruleItems];
// Localize drop-down options in context and reorder/insert intermediate labels natively
var localizer = [_ruleEditor standardLocalizer];
if (localizer)
{
[localizer localizeMenuItemsForViews:_ruleOptionViews];
_ruleOptionViews = [localizer localizeAndReorderViews:_ruleOptionViews];
}
// Rebuild frame configurations to match the new localized layout order
[_ruleOptionFrames removeAllObjects];
[_ruleOptionInitialViewFrames removeAllObjects];
var newCount = [_ruleOptionViews count];
for (var i = 0; i < newCount; i++)
{
var view = [_ruleOptionViews objectAtIndex:i],
frame = [view frame];
[_ruleOptionFrames addObject:frame];
[_ruleOptionInitialViewFrames addObject:frame];
}
if (!_editable)
[self _updateEnabledStateForSubviews];
@@ -461,9 +410,6 @@
{
[_addButton setHidden:[_ruleEditor _shouldHideAddButtonForSlice:self]];
[_subtractButton setHidden:[_ruleEditor _shouldHideSubtractButtonForSlice:self]];
[_addButton setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
[_subtractButton setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
}
- (void)_configurePlusButtonByRowType:(CPRuleEditorRowType)type
@@ -546,23 +492,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)
+60 -411
View File
@@ -29,10 +29,6 @@
@import "CPClipView.j"
@import "CPScroller.j"
@import "CPView.j"
@import "CPRulerView.j"
@class CPTableView
@class CPRulerView
#define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden])
@@ -96,19 +92,9 @@ var TIMER_INTERVAL = 0.2,
CPScrollViewFadeOutTime = 1.3;
var CPScrollViewWillStartLiveScrollNotification = @"CPScrollViewWillStartLiveScrollNotification",
CPScrollViewDidLiveScrollNotification = @"CPScrollViewDidLiveScrollNotification",
CPScrollViewDidEndLiveScrollNotification = @"CPScrollViewDidEndLiveScrollNotification";
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;
/*!
@ingroup appkit
@class CPScrollView
@@ -148,19 +134,11 @@ var _CPScrollViews;
int _scrollerStyle;
int _scrollerKnobStyle;
// Ruler Support
BOOL _hasVerticalRuler;
BOOL _hasHorizontalRuler;
BOOL _rulersVisible;
CPRulerView _verticalRuler;
CPRulerView _horizontalRuler;
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
+ (void)initialize
{
@@ -169,12 +147,10 @@ var _CPScrollViews;
var globalValue = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPScrollersGlobalStyle"];
if (globalValue == nil || globalValue === -1)
if (globalValue === nil || globalValue === -1)
CPScrollerStyleGlobal = _isBrowserUsingOverlayScrollers() ? CPScrollerStyleOverlay : CPScrollerStyleLegacy
else
CPScrollerStyleGlobal = globalValue;
_CPScrollViews = @[];
}
+ (CPString)defaultThemeClass
@@ -186,112 +162,56 @@ var _CPScrollViews;
{
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]
};
}
/*! Deprecated
*/
+ (CGSize)contentSizeForFrameSize:(CGSize)frameSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType
{
return [self contentSizeForFrameSize:frameSize
horizontalScrollerClass:hFlag ? [CPScroller class] : nil
verticalScrollerClass:vFlag ? [CPScroller class] : nil
borderType:borderType
controlSize:CPRegularControlSize
scrollerStyle:CPScrollerStyleGlobal];
}
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, frameSize.width, frameSize.height) borderType:borderType],
scrollerWidth = [CPScroller scrollerWidth];
+ (CGSize)contentSizeForFrameSize:(CGSize)frameSize
horizontalScrollerClass:(Class)horizontalScrollerClass
verticalScrollerClass:(Class)verticalScrollerClass
borderType:(CPBorderType)borderType
controlSize:(CPControlSize)controlSize
scrollerStyle:(CPScrollerStyle)scrollerStyle
{
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, frameSize.width, frameSize.height) borderType:borderType];
if (hFlag)
bounds.size.height -= scrollerWidth;
if (horizontalScrollerClass)
bounds.size.height -= [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
if (verticalScrollerClass)
bounds.size.width -= [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
if (vFlag)
bounds.size.width -= scrollerWidth;
return bounds.size;
}
/*! Deprecated
*/
+ (CGSize)frameSizeForContentSize:(CGSize)contentSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType
{
return [self frameSizeForContentSize:contentSize
horizontalScrollerClass:hFlag ? [CPScroller class] : nil
verticalScrollerClass:vFlag ? [CPScroller class] : nil
borderType:borderType
controlSize:CPRegularControlSize
scrollerStyle:CPScrollerStyleGlobal];
}
+ (CGSize)frameSizeForContentSize:(CGSize)contentSize
horizontalScrollerClass:(Class)horizontalScrollerClass
verticalScrollerClass:(Class)verticalScrollerClass
borderType:(CPBorderType)borderType
controlSize:(CPControlSize)controlSize
scrollerStyle:(CPScrollerStyle)scrollerStyle
{
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, contentSize.width, contentSize.height) borderType:borderType],
widthInset = contentSize.width - bounds.size.width,
heightInset = contentSize.height - bounds.size.height,
frameSize = CGSizeMake(contentSize.width + widthInset, contentSize.height + heightInset);
frameSize = CGSizeMake(contentSize.width + widthInset, contentSize.height + heightInset),
scrollerWidth = [CPScroller scrollerWidth];
if (hFlag)
frameSize.height += [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
frameSize.height += scrollerWidth;
if (vFlag)
frameSize.width += [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
frameSize.width += scrollerWidth;
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;
}
@@ -300,7 +220,7 @@ var _CPScrollViews;
/*!
Get the system wide scroller style.
*/
+ (CPScrollerStyle)globalScrollerStyle
+ (int)globalScrollerStyle
{
return CPScrollerStyleGlobal;
}
@@ -310,18 +230,15 @@ var _CPScrollViews;
@param aStyle the scroller style to set all scroller views to use (CPScrollerStyleLegacy or CPScrollerStyleOverlay)
*/
+ (void)setGlobalScrollerStyle:(CPScrollerStyle)aStyle
+ (void)setGlobalScrollerStyle:(int)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];
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
- (id)initWithFrame:(CGRect)aFrame
{
@@ -351,23 +268,17 @@ var _CPScrollViews;
_scrollerKnobStyle = CPScrollerKnobStyleDefault;
[self setScrollerStyle:CPScrollerStyleGlobal];
_hasVerticalRuler = NO;
_hasHorizontalRuler = NO;
_rulersVisible = NO;
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
[_CPScrollViews addObject:self];
}
return self;
}
// MARK: -
// MARK: Getters / Setters
#pragma mark -
#pragma mark Getters / Setters
/*!
The delegate of the scroll view
@@ -400,7 +311,7 @@ Notifies the delegate when the scroll view has finished scrolling.
_delegate = aDelegate;
_implementedDelegateMethods = 0;
if (_delegate == nil)
if (_delegate === nil)
return;
if ([_delegate respondsToSelector:@selector(scrollViewWillScroll:)])
@@ -410,7 +321,7 @@ Notifies the delegate when the scroll view has finished scrolling.
_implementedDelegateMethods |= CPScrollViewDelegate_scrollViewDidScroll_;
}
- (CPScrollerStyle)scrollerStyle
- (int)scrollerStyle
{
return _scrollerStyle;
}
@@ -421,7 +332,7 @@ Notifies the delegate when the scroll view has finished scrolling.
- CPScrollerStyleLegacy: Standard scrollers like Windows or Mac OS X prior to 10.7
- CPScrollerStyleOverlay: scrollers like those in Mac OS X 10.7+
*/
- (void)setScrollerStyle:(CPScrollerStyle)aStyle
- (void)setScrollerStyle:(int)aStyle
{
if (_scrollerStyle === aStyle)
return;
@@ -618,8 +529,8 @@ Notifies the delegate when the scroll view has finished scrolling.
{
var bounds = [self _insetBounds];
[self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, MAX(CGRectGetWidth(bounds), [_horizontalScroller scrollerWidth] + 1), [_horizontalScroller scrollerWidth])]];
[[self horizontalScroller] setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [_horizontalScroller scrollerWidth])];
[self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, MAX(CGRectGetWidth(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle] + 1), [CPScroller scrollerWidthInStyle:_scrollerStyle])]];
[[self horizontalScroller] setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle])];
}
[self reflectScrolledClipView:_contentView];
@@ -683,8 +594,8 @@ Notifies the delegate when the scroll view has finished scrolling.
{
var bounds = [self _insetBounds];
[self setVerticalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, [_verticalScroller scrollerWidth], MAX(CGRectGetHeight(bounds), [_verticalScroller scrollerWidth] + 1))]];
[[self verticalScroller] setFrameSize:CGSizeMake([_verticalScroller scrollerWidth], CGRectGetHeight(bounds))];
[self setVerticalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, [CPScroller scrollerWidthInStyle:_scrollerStyle], MAX(CGRectGetHeight(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle] + 1))]];
[[self verticalScroller] setFrameSize:CGSizeMake([CPScroller scrollerWidthInStyle:_scrollerStyle], CGRectGetHeight(bounds))];
}
[self reflectScrolledClipView:_contentView];
@@ -845,105 +756,8 @@ Notifies the delegate when the scroll view has finished scrolling.
}
// MARK: -
// MARK: Rulers
- (BOOL)hasHorizontalRuler
{
return _hasHorizontalRuler;
}
- (void)setHasHorizontalRuler:(BOOL)shouldHaveHorizontalRuler
{
if (_hasHorizontalRuler === shouldHaveHorizontalRuler)
return;
_hasHorizontalRuler = shouldHaveHorizontalRuler;
if (_hasHorizontalRuler && !_horizontalRuler)
{
_horizontalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationHorizontal];
}
[self tile];
}
- (BOOL)hasVerticalRuler
{
return _hasVerticalRuler;
}
- (void)setHasVerticalRuler:(BOOL)shouldHaveVerticalRuler
{
if (_hasVerticalRuler === shouldHaveVerticalRuler)
return;
_hasVerticalRuler = shouldHaveVerticalRuler;
if (_hasVerticalRuler && !_verticalRuler)
{
_verticalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationVertical];
}
[self tile];
}
- (BOOL)rulersVisible
{
return _rulersVisible;
}
- (void)setRulersVisible:(BOOL)areRulersVisible
{
if (_rulersVisible === areRulersVisible)
return;
_rulersVisible = areRulersVisible;
[self tile];
}
- (CPRulerView)horizontalRulerView
{
return _horizontalRuler;
}
- (void)setHorizontalRulerView:(CPRulerView)aRulerView
{
if (_horizontalRuler === aRulerView)
return;
[_horizontalRuler removeFromSuperview];
_horizontalRuler = aRulerView;
if (_horizontalRuler)
[self addSubview:_horizontalRuler];
[self tile];
}
- (CPRulerView)verticalRulerView
{
return _verticalRuler;
}
- (void)setVerticalRulerView:(CPRulerView)aRulerView
{
if (_verticalRuler === aRulerView)
return;
[_verticalRuler removeFromSuperview];
_verticalRuler = aRulerView;
if (_verticalRuler)
[self addSubview:_verticalRuler];
[self tile];
}
// MARK: -
// MARK: Privates
#pragma mark -
#pragma mark Privates
/* @ignore */
- (void)_updateScrollerStyle
@@ -1097,8 +911,8 @@ Notifies the delegate when the scroll view has finished scrolling.
bottomCornerFrame.origin.x = CGRectGetMinX(verticalFrame);
bottomCornerFrame.origin.y = CGRectGetMaxY(verticalFrame);
bottomCornerFrame.size.width = [_verticalScroller scrollerWidth];
bottomCornerFrame.size.height = [_horizontalScroller scrollerWidth];
bottomCornerFrame.size.width = [CPScroller scrollerWidthInStyle:_scrollerStyle];
bottomCornerFrame.size.height = [CPScroller scrollerWidthInStyle:_scrollerStyle];
return bottomCornerFrame;
}
@@ -1139,8 +953,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[self _sendDelegateMessages];
[_contentView scrollToPoint:contentBounds.origin];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
}
/* @ignore */
@@ -1179,8 +991,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView scrollToPoint:contentBounds.origin];
[_headerClipView scrollToPoint:CGPointMake(contentBounds.origin.x, 0.0)];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
}
/* @ignore */
@@ -1191,7 +1001,6 @@ Notifies the delegate when the scroll view has finished scrolling.
if (!_scrollTimer)
{
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewWillStartLiveScrollNotification object:self];
[self _scrollViewWillScroll];
_scrollTimer = [CPTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(_scrollViewDidScroll) userInfo:nil repeats:YES];
}
@@ -1230,8 +1039,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView scrollToPoint:constrainedOrigin];
[_headerClipView scrollToPoint:CGPointMake(constrainedOrigin.x, 0.0)];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
if (extraX || extraY)
[enclosingScrollView _respondToScrollWheelEventWithDeltaX:extraX deltaY:extraY];
}
@@ -1251,8 +1058,6 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_implementedDelegateMethods & CPScrollViewDelegate_scrollViewDidScroll_)
[_delegate scrollViewDidScroll:self];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidEndLiveScrollNotification object:self];
}
/*! @ignore*/
@@ -1263,15 +1068,18 @@ Notifies the delegate when the scroll view has finished scrolling.
// MARK: -
// MARK: Utilities
#pragma mark -
#pragma mark Utilities
/*!
Lays out the scroll view's components.
*/
- (void)tile
{
[self reflectScrolledClipView:_contentView];
// yuck.
// RESIZE: tile->setHidden AND refl
// Outside Change: refl->tile->setHidden AND refl
// scroll: refl.
}
/*!
@@ -1315,44 +1123,9 @@ Notifies the delegate when the scroll view has finished scrolling.
contentFrame.origin.y += headerClipViewHeight;
contentFrame.size.height -= headerClipViewHeight;
// Adjust content view based on horizontal / vertical ruler presence
var showHorizontalRuler = _rulersVisible && _hasHorizontalRuler && _horizontalRuler,
showVerticalRuler = _rulersVisible && _hasVerticalRuler && _verticalRuler;
var horizRulerThickness = showHorizontalRuler ? ([_horizontalRuler respondsToSelector:@selector(ruleThickness)] ? [_horizontalRuler ruleThickness] : 16.0) : 0.0,
vertRulerThickness = showVerticalRuler ? ([_verticalRuler respondsToSelector:@selector(ruleThickness)] ? [_verticalRuler ruleThickness] : 24.0) : 0.0;
if (showHorizontalRuler)
{
if ([_horizontalRuler superview] !== self)
[self addSubview:_horizontalRuler];
[_horizontalRuler setHidden:NO];
}
else if (_horizontalRuler)
{
[_horizontalRuler setHidden:YES];
}
if (showVerticalRuler)
{
if ([_verticalRuler superview] !== self)
[self addSubview:_verticalRuler];
[_verticalRuler setHidden:NO];
}
else if (_verticalRuler)
{
[_verticalRuler setHidden:YES];
}
contentFrame.origin.y += horizRulerThickness;
contentFrame.size.height -= horizRulerThickness;
contentFrame.origin.x += vertRulerThickness;
contentFrame.size.width -= vertRulerThickness;
var difference = CGSizeMake(CGRectGetWidth(documentFrame) - CGRectGetWidth(contentFrame), CGRectGetHeight(documentFrame) - CGRectGetHeight(contentFrame)),
verticalScrollerWidth = [_verticalScroller scrollerWidth],
horizontalScrollerHeight = [_horizontalScroller scrollerWidth],
verticalScrollerWidth = [CPScroller scrollerWidthInStyle:[_verticalScroller style]],
horizontalScrollerHeight = [CPScroller scrollerWidthInStyle:[_horizontalScroller style]],
hasVerticalScroll = difference.height > 0.0,
hasHorizontalScroll = difference.width > 0.0,
shouldShowVerticalScroller = _hasVerticalScroller && (!_autohidesScrollers || hasVerticalScroll),
@@ -1442,7 +1215,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView setFrame:contentFrame];
[_headerClipView setFrame:[self _headerClipViewFrame]];
[[_headerClipView documentView] setNeedsDisplay:YES];
if (SHOULD_SHOW_CORNER_VIEW())
{
[_cornerView setFrame:[self _cornerViewFrame]];
@@ -1453,43 +1225,10 @@ 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"]];
}
// Position and redraw rulers to track viewport updates
if (showHorizontalRuler)
{
[_horizontalRuler setFrame:CGRectMake(
CGRectGetMinX(contentFrame),
CGRectGetMinY(contentFrame) - horizRulerThickness,
CGRectGetWidth(contentFrame),
horizRulerThickness
)];
if ([_horizontalRuler respondsToSelector:@selector(updateRuler)])
[_horizontalRuler updateRuler];
else
[_horizontalRuler setNeedsDisplay:YES];
}
if (showVerticalRuler)
{
[_verticalRuler setFrame:CGRectMake(
CGRectGetMinX(contentFrame) - vertRulerThickness,
CGRectGetMinY(contentFrame),
vertRulerThickness,
CGRectGetHeight(contentFrame)
)];
if ([_verticalRuler respondsToSelector:@selector(updateRuler)])
[_verticalRuler updateRuler];
else
[_verticalRuler setNeedsDisplay:YES];
}
--_recursionCount;
}
@@ -1530,8 +1269,9 @@ Notifies the delegate when the scroll view has finished scrolling.
return [_contentView documentVisibleRect];
}
// MARK: -
// MARK: Overrides
#pragma mark -
#pragma mark Overrides
- (void)_removeObservers
{
@@ -1550,6 +1290,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
@@ -1558,11 +1301,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],
@@ -1646,8 +1391,8 @@ Notifies the delegate when the scroll view has finished scrolling.
y = maxY - 1.5;
CGContextMoveToPoint(maxX - 1.0, y);
CGContextAddLineToPoint(minX + 2.0, y);
CGContextMoveToPoint(context, maxX - 1.0, y);
CGContextAddLineToPoint(context, minX + 2.0, y);
x = minX + 0.5;
@@ -1765,82 +1510,6 @@ Notifies the delegate when the scroll view has finished scrolling.
@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
- (void)viewWillMoveToWindow:(CPWindow)aWindow
{
[super viewWillMoveToWindow:aWindow];
[self _stopObservingFirstResponderForWindow:[self window]];
if (aWindow)
[self _startObservingFirstResponderForWindow:aWindow];
}
- (void)_startObservingFirstResponderForWindow:(CPWindow)aWindow
{
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_firstResponderDidChange:) name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
}
- (void)_stopObservingFirstResponderForWindow:(CPWindow)aWindow
{
[[CPNotificationCenter defaultCenter] removeObserver:self name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
}
- (void)_firstResponderDidChange:(CPNotification)aNotification
{
var responder = [[self window] firstResponder],
// FIXME: We add focus ring only on table views right now. When focus ring management will be added, this must be adapted.
shouldAddFocusRing = [responder isKindOfClass:[CPTableView class]],
found;
while (!(found = (responder === self)) && responder)
responder = [responder superview];
if (found && shouldAddFocusRing)
[self setThemeState:CPThemeStateFirstResponder];
else
[self unsetThemeState:CPThemeStateFirstResponder];
}
@end
// MARK: -
var CPScrollViewContentViewKey = @"CPScrollViewContentView",
CPScrollViewHeaderClipViewKey = @"CPScrollViewHeaderClipViewKey",
@@ -1857,14 +1526,7 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
CPScrollViewBottomCornerViewKey = @"CPScrollViewBottomCornerViewKey",
CPScrollViewBorderTypeKey = @"CPScrollViewBorderTypeKey",
CPScrollViewScrollerStyleKey = @"CPScrollViewScrollerStyleKey",
CPScrollViewScrollerKnobStyleKey = @"CPScrollViewScrollerKnobStyleKey",
// Ruler Coding Keys
CPScrollViewHasVRulerKey = @"CPScrollViewHasVRuler",
CPScrollViewHasHRulerKey = @"CPScrollViewHasHRuler",
CPScrollViewRulersVisibleKey = @"CPScrollViewRulersVisible",
CPScrollViewVRulerKey = @"CPScrollViewVRuler",
CPScrollViewHRulerKey = @"CPScrollViewHRuler";
CPScrollViewScrollerKnobStyleKey = @"CPScrollViewScrollerKnobStyleKey";
@implementation CPScrollView (CPCoding)
@@ -1899,14 +1561,6 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
_cornerView = [aCoder decodeObjectForKey:CPScrollViewCornerViewKey];
_bottomCornerView = [aCoder decodeObjectForKey:CPScrollViewBottomCornerViewKey];
// Ruler decoding
_hasVerticalRuler = [aCoder decodeBoolForKey:CPScrollViewHasVRulerKey];
_hasHorizontalRuler = [aCoder decodeBoolForKey:CPScrollViewHasHRulerKey];
_rulersVisible = [aCoder decodeBoolForKey:CPScrollViewRulersVisibleKey];
_verticalRuler = [aCoder decodeObjectForKey:CPScrollViewVRulerKey];
_horizontalRuler = [aCoder decodeObjectForKey:CPScrollViewHRulerKey];
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
@@ -1914,7 +1568,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;
@@ -1957,14 +1614,6 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
[aCoder encodeInt:_scrollerStyle forKey:CPScrollViewScrollerStyleKey];
[aCoder encodeInt:_scrollerKnobStyle forKey:CPScrollViewScrollerKnobStyleKey];
// Ruler encoding
[aCoder encodeBool:_hasVerticalRuler forKey:CPScrollViewHasVRulerKey];
[aCoder encodeBool:_hasHorizontalRuler forKey:CPScrollViewHasHRulerKey];
[aCoder encodeBool:_rulersVisible forKey:CPScrollViewRulersVisibleKey];
[aCoder encodeObject:_verticalRuler forKey:CPScrollViewVRulerKey];
[aCoder encodeObject:_horizontalRuler forKey:CPScrollViewHRulerKey];
}
@end
+25 -51
View File
@@ -23,11 +23,12 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPAnimation.j"
@import "CPControl.j"
@import "CPViewAnimation.j"
@import "CPWindow_Constants.j"
@import "CPViewAnimation.j"
@global CPApp
@@ -61,7 +62,6 @@ NAMES_FOR_PARTS[CPScrollerKnobSlot] = @"knob-slot";
NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
@typedef CPScrollerStyle
CPScrollerStyleLegacy = 0;
CPScrollerStyleOverlay = 1;
@@ -101,8 +101,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
+ (CPString)defaultThemeClass
{
@@ -134,7 +134,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
/*!
Returns the CPScroller's width for a CPRegularControlSize.
*/
+ (float)scrollerWidthInStyle:(CPScrollerStyle)aStyle
+ (float)scrollerWidthInStyle:(int)aStyle
{
if (!_CACHED_THEME_SCROLLER)
_CACHED_THEME_SCROLLER = [[self alloc] init];
@@ -166,8 +166,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
- (id)initWithFrame:(CGRect)aFrame
{
@@ -202,13 +202,13 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Getters / Setters
#pragma mark -
#pragma mark Getters / Setters
/*!
Returns the scroller's style
*/
- (int)style
- (void)style
{
return _style;
}
@@ -217,7 +217,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
Set the scroller's control size
@param aStyle the scroller style: CPScrollerStyleLegacy or CPScrollerStyleOverlay
*/
- (void)setStyle:(CPScrollerStyle)aStyle
- (void)setStyle:(id)aStyle
{
if (_style != nil && _style === aStyle)
return;
@@ -226,7 +226,6 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
if (_style === CPScrollerStyleLegacy)
{
_allowFadingOut = NO;
[self fadeIn];
[self setThemeState:CPThemeStateScrollViewLegacy];
}
@@ -258,8 +257,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
*/
- (void)setKnobProportion:(float)aProportion
{
if (!CPIsNumeric(aProportion))
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric, was: "+aProportion];
if (!_IS_NUMERIC(aProportion))
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric"];
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
@@ -268,8 +267,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Privates
#pragma mark -
#pragma mark Privates
/*! @ignore */
- (void)_adjustScrollerSize
@@ -294,8 +293,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Utilities
#pragma mark -
#pragma mark Utilities
- (CGRect)rectForPart:(CPScrollerPart)aPart
{
@@ -320,25 +319,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))
@@ -485,8 +466,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Drawing
#pragma mark -
#pragma mark Drawing
/*!
Draws the specified arrow and sets the highlight.
@@ -720,8 +701,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Overrides
#pragma mark -
#pragma mark Overrides
- (id)currentValueForThemeAttribute:(CPString)anAttributeName
{
@@ -777,7 +758,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
if ([self isHidden] || ![self isEnabled] || !_isMouseOver)
return;
_allowFadingOut = (_style !== CPScrollerStyleLegacy);
_allowFadingOut = YES;
_isMouseOver = NO;
if (_timerFadeOut)
@@ -789,16 +770,9 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
_timerFadeOut = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_performFadeOut:) userInfo:nil repeats:NO];
}
- (float)scrollerWidth
{
if (_style == CPScrollerStyleLegacy)
return [self valueForThemeAttribute:@"scroller-width" inState:CPThemeStateScrollViewLegacy];
return [self currentValueForThemeAttribute:@"scroller-width"];
}
// MARK: -
// MARK: Delegates
#pragma mark -
#pragma mark Delegates
- (void)animationDidEnd:(CPAnimation)animation
{
+39 -251
View File
@@ -24,12 +24,8 @@
@import "CPMenu.j"
@import "CPMenuItem.j"
@import "CPTextField.j"
@import "CPAnimationContext.j"
@import "CPViewAnimator.j"
@import "CPArrayController.j"
@class CPUserDefaults
@class CALayer
@global CPApp
@@ -62,9 +58,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
BOOL _sendsSearchStringImmediately;
BOOL _canResignFirstResponder;
CPTimer _partialStringTimer;
CPView _contentView;
BOOL _isBecomingFirstResponder;
}
+ (CPString)defaultThemeClass
@@ -81,10 +74,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@"image-cancel-pressed": [CPNull null],
@"image-search-inset" : CGInsetMake(0, 0, 0, 5),
@"image-cancel-inset" : CGInsetMake(0, 5, 0, 0),
@"search-button-rect-function": [CPNull null],
@"layout-function": [CPNull null],
@"search-right-margin": 2,
@"search-menu-offset": CGPointMake(10, -4)
@"search-right-margin": 2
};
}
@@ -104,8 +94,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
_sendsWholeSearchString = NO;
_sendsSearchStringImmediately = NO;
_recentsAutosaveName = nil;
[self setPlaceholderString:@"Search"];
[self _init];
}
@@ -124,13 +112,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[self setContinuous:YES];
var bounds = [self bounds],
cancelButton = nil,
searchButton = nil;
#if PLATFORM(DOM)
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]];
searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
#endif
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]],
searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
[self setCancelButton:cancelButton];
[self resetCancelButton];
@@ -139,12 +122,11 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[self resetSearchButton];
_canResignFirstResponder = YES;
_isBecomingFirstResponder = NO;
}
// MARK: -
// MARK: Override observers
#pragma mark -
#pragma mark Override observers
- (void)_removeObservers
{
@@ -200,7 +182,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)resetSearchButton
{
var button = [self searchButton],
searchButtonImage = (_searchMenuTemplate == nil) ? [self currentValueForThemeAttribute:@"image-search"] : [self currentValueForThemeAttribute:@"image-find"];
searchButtonImage = (_searchMenuTemplate === nil) ? [self currentValueForThemeAttribute:@"image-search"] : [self currentValueForThemeAttribute:@"image-find"];
[button setBordered:NO];
[button setImageScaling:CPImageScaleAxesIndependently];
@@ -247,8 +229,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
var button = [self cancelButton];
[button setBordered:NO];
[button setImageScaling:CPImageScaleAxesIndependently];
[button setImage:[self currentValueForThemeAttribute:@"image-cancel"]];
[button setAlternateImage:[self currentValueForThemeAttribute:@"image-cancel-pressed"]];
[button setImage:[self valueForThemeAttribute:@"image-cancel"]];
[button setAlternateImage:[self valueForThemeAttribute:@"image-cancel-pressed"]];
[button setAutoresizingMask:CPViewMinXMargin];
[button setTarget:self];
[button setAction:@selector(cancelOperation:)];
@@ -270,7 +252,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
if (_searchButton)
{
var searchBounds = [self searchButtonRectForBounds:bounds],
rightMargin = [self _potentialCurrentValueForThemeAttribute:@"search-right-margin"];
rightMargin = [self currentValueForThemeAttribute:@"search-right-margin"];
leftOffset = CGRectGetMaxX(searchBounds) + rightMargin;
}
@@ -290,18 +272,10 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
*/
- (CGRect)searchButtonRectForBounds:(CGRect)rect
{
var themedRectFunction = [self _potentialCurrentValueForThemeAttribute:@"search-button-rect-function"];
var size = [[self currentValueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero(),
inset = [self currentValueForThemeAttribute:@"image-search-inset"];
if (themedRectFunction)
// There's a theme defined positioning function, just use it
return objj_eval("("+themedRectFunction+")")(self, rect);
else
{
var size = [[self _potentialCurrentValueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero(),
inset = [self _potentialCurrentValueForThemeAttribute:@"image-search-inset"];
return CGRectMake(inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height);
}
return CGRectMake(inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height);
}
/*!
@@ -311,8 +285,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
*/
- (CGRect)cancelButtonRectForBounds:(CGRect)rect
{
var size = [[self _potentialCurrentValueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero(),
inset = [self _potentialCurrentValueForThemeAttribute:@"image-cancel-inset"];
var size = [[self currentValueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero(),
inset = [self currentValueForThemeAttribute:@"image-cancel-inset"];
return CGRectMake(CGRectGetWidth(rect) - size.width + inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.width) / 2, size.height, size.height);
}
@@ -420,7 +394,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
{
var max = MIN([self maximumRecents], [searches count]);
_recentSearches = [searches subarrayWithRange:CPMakeRange(0, max)];
searches = [searches subarrayWithRange:CPMakeRange(0, max)];
_recentSearches = searches;
[self _autosaveRecentSearchList];
}
@@ -505,7 +480,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
{
[self selectAll:nil];
[super sendAction:anAction to:anObject];
[_partialStringTimer invalidate];
@@ -516,7 +490,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)_addStringToRecentSearches:(CPString)string
{
if (string == nil || string === @"" || [_recentSearches containsObject:string])
if (string === nil || string === @"" || [_recentSearches containsObject:string])
return;
var searches = [CPMutableArray arrayWithArray:_recentSearches];
@@ -604,8 +578,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[item setTarget:self];
[template addItem:item];
[self _addSeparatorToMenu:template];
item = [[CPMenuItem alloc] initWithTitle:@"Recent Searches"
action:nil
keyEquivalent:@""];
@@ -625,7 +597,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)_updateSearchMenu
{
if (_searchMenuTemplate == nil)
if (_searchMenuTemplate === nil)
return;
var menu = [[CPMenu alloc] init],
@@ -643,6 +615,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
case CPSearchFieldRecentsTitleMenuItemTag:
if (countOfRecents === 0)
continue;
if ([menu numberOfItems] > 0)
[self _addSeparatorToMenu:menu];
break;
case CPSearchFieldRecentsMenuItemTag:
@@ -665,6 +640,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
if (countOfRecents === 0)
continue;
if ([menu numberOfItems] > 0)
[self _addSeparatorToMenu:menu];
[item setAction:@selector(_searchFieldClearRecents:)];
[item setTarget:self];
break;
@@ -672,6 +650,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
case CPSearchFieldNoRecentsMenuItemTag:
if (countOfRecents !== 0)
continue;
if ([menu numberOfItems] > 0)
[self _addSeparatorToMenu:menu];
break;
}
@@ -688,7 +669,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
{
var separator = [CPMenuItem separatorItem];
[separator setEnabled:NO];
[separator setTag:CPSearchFieldRecentsTitleMenuItemTag];
[aMenu addItem:separator];
}
@@ -706,12 +686,11 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)_showMenu
{
if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![self isEnabled])
if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![self isEnabled] || ([_recentSearches count] === 0))
return;
var aFrame = [[self superview] convertRect:[self frame] toView:nil],
offset = [self currentValueForThemeAttribute:@"search-menu-offset"],
location = CGPointMake(aFrame.origin.x + offset.x, aFrame.origin.y + aFrame.size.height + offset.y);
location = CGPointMake(aFrame.origin.x + 10, aFrame.origin.y + aFrame.size.height - 4);
var anEvent = [CPEvent mouseEventWithType:CPRightMouseDown location:location modifierFlags:0 timestamp:[[CPApp currentEvent] timestamp] windowNumber:[[self window] windowNumber] context:nil eventNumber:1 clickCount:1 pressure:0];
@@ -727,14 +706,10 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)cancelOperation:(id)sender
{
// If something was entered, the search field must deactivate itself (else, do nothing)
if ([[self stringValue] length] > 0)
{
[self setObjectValue:@""];
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
[self setObjectValue:@""];
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
[[self window] makeFirstResponder:[self window]];
}
[self _updateCancelButtonVisibility];
}
- (void)_searchFieldSearch:(id)sender
@@ -784,197 +759,15 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)_loadRecentSearchList
{
var name = [self recentsAutosaveName];
if (name == nil)
if (name === nil)
return;
var list = [[CPUserDefaults standardUserDefaults] objectForKey:name];
if (list != nil)
if (list !== nil)
_recentSearches = list;
}
@end
// MARK: -
@implementation CPSearchField (ThemingAdditions)
// Overwrite CPTextField method to permit themed layout functions
- (void)layoutSubviews
{
if (!_contentView)
{
// Search for the CPImageAndTextView subview of mine
for (var i = 0, subviews = [self subviews], nb = [subviews count]; (!_contentView && (i < nb)); i++)
if ([subviews[i] isKindOfClass:_CPImageAndTextView])
_contentView = subviews[i];
}
var bezelColor = [self currentValueForThemeAttribute:@"bezel-color"];
if ([bezelColor isCSSBased])
{
// CSS Styling
// We don't need bezelView as we apply CSS styling directly on the search view itself
[self _setBackgroundColor:bezelColor];
if (!_contentView)
_contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:nil];
}
else
{
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
[bezelView setBackgroundColor:bezelColor];
if (!_contentView)
_contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
}
if (_contentView)
{
[_contentView setHidden:(_stringValue && _stringValue.length > 0) && [self hasThemeState:CPThemeStateEditing]];
[_contentView setText:[self hasThemeState:CPTextFieldStatePlaceholder] ? [self placeholderString] : _stringValue];
[_contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
[_contentView setFont:[self font]]; //[self currentValueForThemeAttribute:@"font"]];
[_contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
[_contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
[_contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
[_contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
[_contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
}
if (_isEditing)
[self _setCSSStyleForInputElement];
[self updateSearchButton];
[self updateTrackingAreas];
}
// As CPTextField redefines setBackgroundColor, we have to bypass it
- (void)_setBackgroundColor:(CPColor)aColor
{
var grannyMethod = class_getInstanceMethod([[self superclass] superclass], @selector(setBackgroundColor:)),
grannyImplementation = method_getImplementation(grannyMethod);
grannyImplementation(self, @selector(setBackgroundColor:), aColor);
}
- (void)_layoutSubviews
{
var themedLayoutFunction = [self currentValueForThemeAttribute:@"layout-function"];
if (themedLayoutFunction)
// There's a theme defined layout function, just use it
objj_eval("("+themedLayoutFunction+")")(self);
else
// Call directly the completion handler
[self themedLayoutFunctionCompletionHandler];
}
- (void)updateSearchButton
{
[_searchButton setImage:(_searchMenuTemplate ? [self currentValueForThemeAttribute:@"image-find"] : [self currentValueForThemeAttribute:@"image-search"])];
[self updateTrackingAreas];
}
- (void)themedLayoutFunctionCompletionHandler
{
if (_isBecomingFirstResponder)
{
_isBecomingFirstResponder = NO;
// Call the real _becomeFirstKeyResponder to finish setting the input element
[super _becomeFirstKeyResponder];
// For some (unknown yet) reason, input field doesn't focus and text field visual state is not updated
var element = [self _inputElement];
element.focus();
[self layoutSubviews];
[CALayer runLoopUpdateLayers]; // Thank you @daboe01 for suggesting adding this
}
else
[self layoutSubviews];
}
// We override CPTextField method in order to delay the display of the input element until the end of the animation
- (BOOL)_becomeFirstKeyResponder
{
// As we have to return a result, we check if response could be NO
if (![self _isWithinUsablePlatformRect] || ![self isEditable])
return NO;
// We are now sure that response will be YES
_isBecomingFirstResponder = YES;
// We have to do this now in order to avoid running conditions messing things among multiple textfields
_stringValue = [self stringValue];
var element = [self _inputElement];
element.value = _stringValue;
[self _layoutSubviews];
return YES;
}
- (void)textDidEndEditing:(CPNotification)note
{
if ([note object] != self)
return;
[self _layoutSubviews];
[super textDidEndEditing:note];
}
- (void)_windowDidResignKey:(CPNotification)aNotification
{
// When the window resigns key, if the search field is empty, it must cancel first responder
if ([[self stringValue] length] == 0)
[[self window] makeFirstResponder:[self window]];
[super _windowDidResignKey:aNotification];
[self _layoutSubviews];
}
- (void)setPlaceholderString:(CPString)aStringValue
{
[super setPlaceholderString:aStringValue];
[self _layoutContent];
}
- (void)_layoutContent
{
[_searchButton setFrame:[self searchButtonRectForBounds:[self bounds]]];
[_contentView setFrame:[self contentRectForBounds:[self bounds]]];
[_cancelButton setFrame:[self cancelButtonRectForBounds:[self bounds]]];
[self updateTrackingAreas];
}
- (void)setFrameSize:(CGSize)aSize
{
[super setFrameSize:aSize];
[self _layoutContent];
}
- (id)_potentialCurrentValueForThemeAttribute:(CPString)aName
{
if (_isBecomingFirstResponder)
return [self valueForThemeAttribute:aName inState:[self themeState].and(CPThemeStateEditing)];
else
return [self currentValueForThemeAttribute:aName];
}
- (void)unbind:(CPString)aBinding
{
[super unbind:aBinding];
@@ -987,7 +780,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@end
// MARK: -
#pragma mark -
@implementation CPSearchField (CPTrackingArea)
{
@@ -1019,7 +812,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[self addTrackingArea:_searchButtonTrackingArea];
}
if (_cancelButton && ![_cancelButton isHidden])
if (_cancelButton)
{
_cancelButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:[_cancelButton frame]
options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
@@ -1042,7 +835,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@end
// MARK: -
#pragma mark -
var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
CPSendsWholeSearchStringKey = @"CPSendsWholeSearchStringKey",
@@ -1120,21 +913,16 @@ var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
var destination = [_info objectForKey:CPObservedObjectKey],
keyPath = [_info objectForKey:CPObservedKeyPathKey];
var formatString = _predicateFormat.replace(/\$value/g, "%@");
[self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
if (aValue)
{
var values = @[],
formatString = _predicateFormat.replace(/\$value/g, function(x) {[values addObject:aValue]; return "%@";});
[_controller setFilterPredicate:[CPPredicate predicateWithFormat:formatString argumentArray:values]];
}
[_controller setFilterPredicate:[CPPredicate predicateWithFormat:formatString, aValue]];
else
[_controller setFilterPredicate:nil];
[self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
}
- (CPString)searchFieldValue
{
return [_source stringValue];
+153 -284
View File
@@ -71,7 +71,7 @@ CPSegmentSwitchTrackingMomentary = 2;
@"right-segment-bezel-color": [CPNull null],
@"center-segment-bezel-color": [CPNull null],
@"divider-bezel-color": [CPNull null],
@"divider-thickness": 1.0
@"divider-thickness": 1.0,
};
}
@@ -316,7 +316,7 @@ CPSegmentSwitchTrackingMomentary = 2;
// Working with Individual Segments
/*!
Sets the width of the specified segment.
@param aWidth the new width for the segment (0 for automatic width)
@param aWidth the new width for the segment
@param aSegment the segment to set the width for
@throws CPRangeException if \c aSegment is out of bounds
*/
@@ -420,14 +420,7 @@ CPSegmentSwitchTrackingMomentary = 2;
[segment setSelected:isSelected];
if (_themeStates[aSegment] == undefined)
_themeStates[aSegment] = CPThemeStateNormal;
// Update state using .and() / .without() to preserve existing states (like Disabled)
if (isSelected)
_themeStates[aSegment] = _themeStates[aSegment].and(CPThemeStateSelected);
else
_themeStates[aSegment] = _themeStates[aSegment].without(CPThemeStateSelected);
_themeStates[aSegment] = isSelected ? CPThemeStateSelected : CPThemeStateNormal;
// We need to do some cleanup if we only allow one selection.
if (isSelected)
@@ -439,10 +432,7 @@ CPSegmentSwitchTrackingMomentary = 2;
if (_trackingMode == CPSegmentSwitchTrackingSelectOne && oldSelectedSegment != aSegment && oldSelectedSegment != -1 && oldSelectedSegment < _segments.length)
{
[_segments[oldSelectedSegment] setSelected:NO];
// Update old segment using .without() to preserve other states
// Previously: _themeStates[oldSelectedSegment] = CPThemeStateNormal;
_themeStates[oldSelectedSegment] = _themeStates[oldSelectedSegment].without(CPThemeStateSelected);
_themeStates[oldSelectedSegment] = CPThemeStateNormal;
[self drawSegmentBezel:oldSelectedSegment highlight:NO];
}
@@ -540,24 +530,12 @@ CPSegmentSwitchTrackingMomentary = 2;
- (float)_leftOffsetForSegment:(unsigned)segment
{
if ([[self actualTheme] isCSSBased])
{
// CSS styling
if (segment == 0)
return 0;
if (segment == 0)
return [self currentValueForThemeAttribute:@"bezel-inset"].left;
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) - 2; // FIXME: -2 for collapse of borders
}
else
{
// Legacy styling
if (segment == 0)
return [self currentValueForThemeAttribute:@"bezel-inset"].left;
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) + thickness;
}
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) + thickness;
}
- (unsigned)_indexOfLastSegment
@@ -583,36 +561,26 @@ CPSegmentSwitchTrackingMomentary = 2;
}
else if (aName === "right-segment-bezel")
{
// we have to FLOOR the coordinates to prevent a 1px glitch in Safari
return CGRectMake(FLOOR(CGRectGetWidth([self bounds]) - contentInset.right),
FLOOR(bezelInset.top),
FLOOR(contentInset.right),
FLOOR(height));
return CGRectMake(CGRectGetWidth([self bounds]) - contentInset.right,
bezelInset.top,
contentInset.right,
height);
}
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,85 +619,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 PLATFORM(DOM)
contentView._DOMElement.style.zIndex = ([segment selected] ? 1 : 0);
bezelView._DOMElement.style.zIndex = ([segment selected] ? 1 : 0);
#endif
[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;
@@ -738,114 +669,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];
}
}
@@ -926,10 +802,7 @@ 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 : 4.0) + (image ? [image size].width : 0) + contentInsetWidth;
}
return CGRectMake(left, top, width, height);
@@ -937,14 +810,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]),
@@ -1007,64 +872,68 @@ CPSegmentSwitchTrackingMomentary = 2;
var type = [anEvent type],
location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
if (type == CPLeftMouseUp)
switch (type)
{
if (_trackingSegment == -1)
case CPLeftMouseUp:
if (_trackingSegment === CPNotFound)
return;
if (_trackingSegment === [self testSegment:location])
{
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
{
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
// With ANY, _selectedSegment means last pressed.
_selectedSegment = _trackingSegment;
}
else
[self setSelected:YES forSegment:_trackingSegment];
[self sendAction:[self action] to:[self target]];
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
{
[self setSelected:NO forSegment:_trackingSegment];
_selectedSegment = CPNotFound;
}
}
[self drawSegmentBezel:_trackingSegment highlight:NO];
_trackingSegment = CPNotFound;
return;
if (_trackingSegment === [self testSegment:location])
{
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
case CPLeftMouseDown:
var trackingSegment = [self testSegment:location];
if (trackingSegment > CPNotFound && [self isEnabledForSegment:trackingSegment])
{
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
// With ANY, _selectedSegment means last pressed.
_selectedSegment = _trackingSegment;
_trackingHighlighted = YES;
_trackingSegment = trackingSegment;
[self drawSegmentBezel:_trackingSegment highlight:YES];
}
else
[self setSelected:YES forSegment:_trackingSegment];
[self sendAction:[self action] to:[self target]];
break;
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
case CPLeftMouseDragged:
if (_trackingSegment === CPNotFound)
return;
var highlighted = [self testSegment:location] === _trackingSegment;
if (highlighted != _trackingHighlighted)
{
[self setSelected:NO forSegment:_trackingSegment];
_trackingHighlighted = highlighted;
_selectedSegment = CPNotFound;
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
}
}
[self drawSegmentBezel:_trackingSegment highlight:NO];
_trackingSegment = -1;
return;
}
if (type == CPLeftMouseDown)
{
var trackingSegment = [self testSegment:location];
if (trackingSegment > -1 && [self isEnabledForSegment:trackingSegment])
{
_trackingHighlighted = YES;
_trackingSegment = trackingSegment;
[self drawSegmentBezel:_trackingSegment highlight:YES];
}
}
else if (type == CPLeftMouseDragged)
{
if (_trackingSegment == -1)
return;
var highlighted = [self testSegment:location] === _trackingSegment;
if (highlighted != _trackingHighlighted)
{
_trackingHighlighted = highlighted;
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
}
break;
}
[CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
+1 -1
View File
@@ -102,7 +102,6 @@ CPThemeStateShadowViewHeavy = CPThemeState("shadowview-style-heavy");
[self setWeight:CPLightShadow];
[self setHitTests:NO];
[self setClipsToBounds:NO];
}
return self;
@@ -170,6 +169,7 @@ CPThemeStateShadowViewHeavy = CPThemeState("shadowview-style-heavy");
- (void)layoutSubviews
{
[super layoutSubviews];
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
}
+152 -810
View File
File diff suppressed because it is too large Load Diff
+11 -12
View File
@@ -32,7 +32,6 @@
@end
@global document
var CPSoundDelegate_sound_didFinishPlaying_ = 1 << 1;
@@ -66,8 +65,8 @@ CPSoundPlayBackStatePause = 2;
unsigned _implementedDelegateMethods;
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
- (id)init
{
@@ -147,8 +146,8 @@ CPSoundPlayBackStatePause = 2;
}
// MARK: -
// MARK: Delegate methods
#pragma mark -
#pragma mark Delegate methods
/*!
Sets the sound's delegate.
@@ -166,8 +165,8 @@ CPSoundPlayBackStatePause = 2;
_implementedDelegateMethods |= CPSoundDelegate_sound_didFinishPlaying_;
}
// MARK: -
// MARK: Events listener
#pragma mark -
#pragma mark Events listener
/*! @ignore
*/
@@ -199,8 +198,8 @@ CPSoundPlayBackStatePause = 2;
}
// MARK: -
// MARK: Media controls
#pragma mark -
#pragma mark Media controls
/*!
Play the sound.
@@ -324,8 +323,8 @@ CPSoundPlayBackStatePause = 2;
_audioTag.volume = aVolume;
}
// MARK: -
// MARK: Accessors
#pragma mark -
#pragma mark Accessors
/*!
Returns the duration in seconds of the sound.
@@ -364,4 +363,4 @@ CPSoundPlayBackStatePause = 2;
[_delegate sound:self didFinishPlaying:finishedPlaying];
}
@end
@end
+519 -911
View File
File diff suppressed because it is too large Load Diff
-388
View File
@@ -1,388 +0,0 @@
/*
* CPSplitViewController.j
*
* Created by Daniel Boehringer on September 2, 2025.
* Copyright (c) 2025 Daniel Boehringer. All rights reserved.
*
* 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 "CPViewController.j"
@import "CPSplitView.j"
var CPSplitViewControllerAutomaticDimension = -1.0;
/**
* A container view controller that manages two or more child view
* controllers in a split view interface.
*
* This class provides a controller-level abstraction for CPSplitView,
* managing the addition, removal, and arrangement of view controllers
* through CPSplitViewItem instances.
*
* This implementation was synthesized with the assistance of an LLM,
* directed by the author.
*/
@implementation CPSplitViewController : CPViewController <CPSplitViewDelegate>
{
/** @private The underlying split view that arranges the child views. */
CPSplitView _splitView;
/** @private An array of CPSplitViewItem objects managed by this controller. */
CPMutableArray _splitViewItems;
/** @private The minimum thickness for sidebars to be displayed inline. */
CPNumber _minimumThicknessForInlineSidebars;
}
// MARK: - Initialization
- (id)init
{
if (self = [super init])
{
_splitViewItems = [CPMutableArray array];
_minimumThicknessForInlineSidebars = 20.0;
}
return self;
}
// MARK: - View Lifecycle
- (void)loadView
{
if (!_splitView)
{
_splitView = [[CPSplitView alloc] initWithFrame:CGRectMake(0,0,400,400)];
[_splitView setDelegate:self];
}
[self setView:_splitView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
for (var i = 0; i < [_splitViewItems count]; i++)
{
var viewController = [[_splitViewItems objectAtIndex:i] viewController];
[[self splitView] addArrangedSubview:[viewController view]];
}
}
// MARK: - Accessors
/**
* Returns the CPSplitView instance managed by the controller.
*
* @returns {CPSplitView} The split view.
*/
- (CPSplitView)splitView
{
return _splitView;
}
/**
* Sets a custom split view for the controller.
*
* @param {CPSplitView} splitView The custom split view to use.
*/
- (void)setSplitView:(CPSplitView)splitView
{
if (_splitView !== splitView)
{
_splitView = splitView;
[_splitView setDelegate:self];
if ([self isViewLoaded])
[self setView:_splitView];
}
}
/**
* Returns the array of split view items.
*
* @returns {CPArray} The array of CPSplitViewItem objects.
*/
- (CPArray)splitViewItems
{
return _splitViewItems;
}
/**
* Sets the array of split view items, replacing any existing items.
*
* @param {CPArray} splitViewItems An array of CPSplitViewItem objects.
*/
- (void)setSplitViewItems:(CPArray)splitViewItems
{
// Remove all existing items
while ([_splitViewItems count] > 0)
{
[self removeSplitViewItem:[_splitViewItems lastObject]];
}
// Add new items
for (var i = 0; i < [splitViewItems count]; i++)
{
[self addSplitViewItem:[splitViewItems objectAtIndex:i]];
}
}
// MARK: - Managing Split View Items
/**
* Adds a split view item to the end of the split view.
*
* @param {CPSplitViewItem} splitViewItem The split view item to add.
*/
- (void)addSplitViewItem:(CPSplitViewItem)splitViewItem
{
[self insertSplitViewItem:splitViewItem atIndex:[_splitViewItems count]];
}
/**
* Inserts a split view item at a specific index.
*
* @param {CPSplitViewItem} splitViewItem The split view item to insert.
* @param {CPInteger} index The zero-based index at which to insert the item.
*/
- (void)insertSplitViewItem:(CPSplitViewItem)splitViewItem atIndex:(CPInteger)index
{
[splitViewItem _setSplitViewController:self];
[_splitViewItems insertObject:splitViewItem atIndex:index];
[self addChildViewController:[splitViewItem viewController]];
if ([self isViewLoaded])
[[self splitView] insertArrangedSubview:[[splitViewItem viewController] view] atIndex:index];
}
/**
* Removes the specified split view item.
*
* @param {CPSplitViewItem} splitViewItem The split view item to remove.
*/
- (void)removeSplitViewItem:(CPSplitViewItem)splitViewItem
{
var viewController = [splitViewItem viewController];
if ([self isViewLoaded])
[[viewController view] removeFromSuperview];
[viewController removeFromParentViewController];
[splitViewItem _setSplitViewController:nil];
[_splitViewItems removeObject:splitViewItem];
}
/**
* Retrieves the split view item associated with a given view controller.
*
* @param {CPViewController} viewController The view controller to find.
* @returns {CPSplitViewItem | null} The corresponding split view item, or nil if not found.
*/
- (CPSplitViewItem)splitViewItemForViewController:(CPViewController)viewController
{
for (var i = 0; i < [_splitViewItems count]; i++)
{
var item = [_splitViewItems objectAtIndex:i];
if ([item viewController] === viewController)
return item;
}
return nil;
}
// MARK: - Managing Sidebars and Inspectors
/**
* Toggles the collapsed state of the first split view item, typically a sidebar.
*
* @param {id} sender The object that initiated the action.
*/
- (void)toggleSidebar:(id)sender
{
if ([_splitViewItems count] > 0)
{
var sidebarItem = [_splitViewItems objectAtIndex:0];
[sidebarItem setCollapsed:![sidebarItem isCollapsed]];
}
}
/**
* Toggles the collapsed state of the last split view item, typically an inspector.
*
* @param {id} sender The object that initiated the action.
*/
- (void)toggleInspector:(id)sender
{
if ([_splitViewItems count] > 1)
{
var inspectorItem = [_splitViewItems lastObject];
[inspectorItem setCollapsed:![inspectorItem isCollapsed]];
}
}
/**
* Returns the minimum thickness for sidebars to be displayed inline.
*
* @returns {CPNumber} The minimum thickness.
*/
- (CPNumber)minimumThicknessForInlineSidebars
{
return _minimumThicknessForInlineSidebars;
}
// MARK: - CPSplitViewDelegate Methods
// Note: A more complete implementation would forward these delegate methods
// to a separate delegate property on the CPSplitViewController itself.
// For now, these are stubbed to demonstrate where they would be handled.
- (BOOL)splitView:(CPSplitView)splitView canCollapseSubview:(CPView)subview
{
// Default behavior: allow all subviews to be collapsed.
return YES;
}
- (CGFloat)splitView:(CPSplitView)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(CPInteger)dividerIndex
{
return proposedMinimumPosition;
}
- (CGFloat)splitView:(CPSplitView)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(CPInteger)dividerIndex
{
return proposedMaximumPosition;
}
- (CGRect)splitView:(CPSplitView)splitView effectiveRect:(CGRect)proposedEffectiveRect forDrawnRect:(CGRect)drawnRect ofDividerAtIndex:(CPInteger)dividerIndex
{
// Default behavior: return the proposed rectangle.
// This can be overridden to provide a larger or custom hit area for the divider.
return proposedEffectiveRect;
}
- (void)splitViewDidResizeSubviews:(CPNotification)notification
{
// Can be used to respond to user-initiated resizing.
}
@end
/**
* An object that manages a view controller within a CPSplitViewController.
*
* A CPSplitViewItem acts as a wrapper around a CPViewController,
* maintaining properties like its collapsed state within the parent
* split view controller.
*/
@implementation CPSplitViewItem : CPObject
{
/** @private The view controller managed by this item. */
CPViewController _viewController;
/** @private A boolean indicating whether the item is collapsed. */
BOOL _isCollapsed;
/** @private A weak reference to the owning split view controller. */
CPSplitViewController _splitViewController;
}
// MARK: - Class Methods
/**
* Creates and returns a new split view item with the specified view controller.
*
* @param {CPViewController} viewController The view controller for the new item.
* @returns {instancetype} A new CPSplitViewItem instance.
*/
+ (instancetype)splitViewItemWithViewController:(CPViewController)viewController
{
return [[self alloc] initWithViewController:viewController];
}
// MARK: - Initialization
/**
* Initializes a new split view item with the specified view controller.
*
* @param {CPViewController} viewController The view controller for the new item.
* @returns {id} The initialized CPSplitViewItem instance.
*/
- (id)initWithViewController:(CPViewController)viewController
{
if (self = [super init])
{
_viewController = viewController;
_isCollapsed = NO;
}
return self;
}
// MARK: - Accessors
/**
* Returns the view controller associated with the item.
*
* @returns {CPViewController} The associated view controller.
*/
- (CPViewController)viewController
{
return _viewController;
}
/**
* Returns a boolean value indicating whether the item is collapsed.
*
* @returns {BOOL} YES if the item is collapsed, otherwise NO.
*/
- (BOOL)isCollapsed
{
return _isCollapsed;
}
/**
* Sets the collapsed state of the item.
* When collapsed, the view controller's view is hidden.
*
* @param {BOOL} shouldCollapse YES to collapse the item, NO to expand it.
*/
- (void)setCollapsed:(BOOL)shouldCollapse
{
if (_isCollapsed === shouldCollapse)
return;
_isCollapsed = shouldCollapse;
[[_viewController view] setHidden:shouldCollapse];
}
/**
* Returns the split view controller that owns this item.
*
* @returns {CPSplitViewController | null} The parent split view controller.
*/
- (CPSplitViewController)splitViewController
{
return _splitViewController;
}
/**
* @private
* Sets the owning split view controller. This method is for internal use by
* CPSplitViewController.
*
* @param {CPSplitViewController} splitViewController The parent controller.
*/
- (void)_setSplitViewController:(CPSplitViewController)splitViewController
{
_splitViewController = splitViewController;
}
@end
-698
View File
@@ -1,698 +0,0 @@
/*
* CPStackView.j
* AppKit
*
* Created by Daniel Boehringer.
* Copyright 2025, Cappuccino Project.
*
* 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 "CPView.j"
// Gravity Areas
@typedef CPStackViewGravity
CPStackViewGravityTop = 1;
CPStackViewGravityLeading = 1;
CPStackViewGravityCenter = 2;
CPStackViewGravityBottom = 3;
CPStackViewGravityTrailing = 3;
// Distribution (Deprecated in modern macOS, but kept for compatibility/logic)
@typedef CPStackViewDistribution
CPStackViewDistributionGravityAreas = 0;
CPStackViewDistributionFill = 1;
CPStackViewDistributionFillEqually = 2;
CPStackViewDistributionFillProportionally = 3;
CPStackViewDistributionEqualSpacing = 4;
CPStackViewDistributionEqualCentering = 5;
// Visibility Priority
@typedef CPStackViewVisibilityPriority
CPStackViewVisibilityPriorityMustHold = 1000.0;
CPStackViewVisibilityPriorityNotVisible = 0.0;
var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
/*!
@ingroup appkit
@class CPStackView
CPStackView arranges an array of views horizontally or vertically and updates
their placement and sizing when the window size changes.
Unlike a simple list, CPStackView supports "Gravity Areas" (Leading, Center, Trailing),
allowing you to pin groups of views to specific sections of the layout.
*/
@implementation CPStackView : CPView
{
CPUserInterfaceLayoutOrientation _orientation;
CPLayoutAttribute _alignment;
float _spacing;
CPEdgeInsets _edgeInsets;
BOOL _detachesHiddenViews;
// View Storage by Gravity
CPMutableArray _viewsLeading;
CPMutableArray _viewsCenter;
CPMutableArray _viewsTrailing;
// Internal cache of all arranged subviews to maintain order for hittesting/iterating
CPMutableArray _arrangedSubviews;
// Custom Spacing storage
CPMapTable _customSpacings;
// Visibility Priorities
CPMapTable _visibilityPriorities;
}
// MARK: -
// MARK: Initialization
+ (CPStackView)stackViewWithViews:(CPArray)views
{
var stackView = [[CPStackView alloc] initWithFrame:CGRectMakeZero()];
for (var i = 0, count = [views count]; i < count; i++)
[stackView addView:views[i] inGravity:CPStackViewGravityLeading];
return stackView;
}
- (id)initWithFrame:(CGRect)aFrame
{
if (self = [super initWithFrame:aFrame])
{
_orientation = CPUserInterfaceLayoutOrientationHorizontal;
_alignment = CPLayoutAttributeCenterY; // Default alignment
_spacing = 8.0; // Default Cocoa spacing
_edgeInsets = CPEdgeInsetsMake(0, 0, 0, 0);
_detachesHiddenViews = YES;
_viewsLeading = [[CPMutableArray alloc] init];
_viewsCenter = [[CPMutableArray alloc] init];
_viewsTrailing = [[CPMutableArray alloc] init];
_arrangedSubviews = [[CPMutableArray alloc] init];
_customSpacings = [[CPMapTable alloc] init];
_visibilityPriorities = [[CPMapTable alloc] init];
}
return self;
}
// MARK: -
// MARK: Configuration
/*!
The horizontal or vertical layout direction of the stack view.
*/
- (CPUserInterfaceLayoutOrientation)orientation
{
return _orientation;
}
- (void)setOrientation:(CPUserInterfaceLayoutOrientation)anOrientation
{
if (_orientation === anOrientation)
return;
_orientation = anOrientation;
// Reset default alignment based on new orientation if needed,
// though usually developer sets alignment explicitly.
// If switching to Vertical, CenterY makes less sense, usually CenterX.
if (_orientation === CPUserInterfaceLayoutOrientationVertical)
{
if (_alignment === CPLayoutAttributeCenterY)
_alignment = CPLayoutAttributeCenterX;
}
else
{
if (_alignment === CPLayoutAttributeCenterX)
_alignment = CPLayoutAttributeCenterY;
}
[self setNeedsLayout:YES];
}
/*!
The view alignment within the stack view.
Common values:
Horizontal: CPLayoutAttributeTop, CPLayoutAttributeBottom, CPLayoutAttributeCenterY, CPLayoutAttributeHeight (fill)
Vertical: CPLayoutAttributeLeading, CPLayoutAttributeTrailing, CPLayoutAttributeCenterX, CPLayoutAttributeWidth (fill)
*/
- (CPLayoutAttribute)alignment
{
return _alignment;
}
- (void)setAlignment:(CPLayoutAttribute)anAlignment
{
if (_alignment === anAlignment)
return;
_alignment = anAlignment;
[self setNeedsLayout:YES];
}
/*!
The minimum spacing, in points, between adjacent views in the stack view.
*/
- (float)spacing
{
return _spacing;
}
- (void)setSpacing:(float)aSpacing
{
if (_spacing === aSpacing)
return;
_spacing = aSpacing;
[self setNeedsLayout:YES];
}
/*!
The geometric padding, in points, inside the stack view, surrounding its views.
*/
- (CPEdgeInsets)edgeInsets
{
return _edgeInsets;
}
- (void)setEdgeInsets:(CPEdgeInsets)insets
{
if (CPEdgeInsetsEqualToEdgeInsets(_edgeInsets, insets))
return;
_edgeInsets = insets;
[self setNeedsLayout:YES];
}
/*!
A Boolean value that indicates whether the stack view removes hidden views from its view hierarchy.
*/
- (BOOL)detachesHiddenViews
{
return _detachesHiddenViews;
}
- (void)setDetachesHiddenViews:(BOOL)shouldDetach
{
if (_detachesHiddenViews === shouldDetach)
return;
_detachesHiddenViews = shouldDetach;
[self setNeedsLayout:YES];
}
// MARK: -
// MARK: Managing Views in Gravity Areas
- (CPArray)_containerForGravity:(CPStackViewGravity)gravity
{
if (gravity === CPStackViewGravityCenter)
return _viewsCenter;
else if (gravity === CPStackViewGravityTrailing) // or Bottom
return _viewsTrailing;
return _viewsLeading; // Leading or Top
}
/*!
Adds a view to the end of the stack view gravity area.
*/
- (void)addView:(CPView)aView inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
// Check if view is already in a container
if ([_arrangedSubviews containsObject:aView])
[self removeView:aView];
[container addObject:aView];
[_arrangedSubviews addObject:aView];
// Add as actual subview
if ([aView superview] !== self)
[self addSubview:aView];
[self setNeedsLayout:YES];
}
/*!
Adds a view to a stack view gravity area at a specified index position.
*/
- (void)insertView:(CPView)aView atIndex:(CPInteger)index inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
if ([_arrangedSubviews containsObject:aView])
[self removeView:aView];
if (index >= [container count])
[container addObject:aView];
else
[container insertObject:aView atIndex:index];
[_arrangedSubviews addObject:aView];
if ([aView superview] !== self)
[self addSubview:aView];
[self setNeedsLayout:YES];
}
/*!
Specifies an array of views for a specified gravity area in the stack view, replacing any previous views in that area.
*/
- (void)setViews:(CPArray)views inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
// Remove old views from arranged list and superview
for (var i = 0; i < [container count]; i++)
{
var oldView = container[i];
[oldView removeFromSuperview];
[_arrangedSubviews removeObject:oldView];
}
[container removeAllObjects];
for (var i = 0; i < [views count]; i++)
{
var newView = views[i];
[container addObject:newView];
[_arrangedSubviews addObject:newView];
[self addSubview:newView];
}
[self setNeedsLayout:YES];
}
/*!
Removes a specified view from the stack view.
*/
- (void)removeView:(CPView)aView
{
if (![_arrangedSubviews containsObject:aView])
return;
[_viewsLeading removeObject:aView];
[_viewsCenter removeObject:aView];
[_viewsTrailing removeObject:aView];
[_arrangedSubviews removeObject:aView];
[aView removeFromSuperview];
[self setNeedsLayout:YES];
}
/*!
Returns the array of views in the specified gravity area in the stack view.
*/
- (CPArray)viewsInGravity:(CPStackViewGravity)gravity
{
return [[self _containerForGravity:gravity] copy];
}
/*!
The array of views arranged by the stack view.
*/
- (CPArray)arrangedSubviews
{
return [_arrangedSubviews copy];
}
/*!
Adds the specified view to the end of the arranged subviews list.
(Defaults to Leading gravity if not specified).
*/
- (void)addArrangedSubview:(CPView)view
{
[self addView:view inGravity:CPStackViewGravityLeading];
}
/*!
Removes the provided view from the stacks array of arranged subviews.
*/
- (void)removeArrangedSubview:(CPView)view
{
[self removeView:view];
}
// MARK: -
// MARK: Custom Spacing
- (float)customSpacingAfterView:(CPView)aView
{
var val = [_customSpacings objectForKey:aView];
if (val)
return [val floatValue];
return CPStackViewSpacingUseDefault;
}
- (void)setCustomSpacing:(float)spacing afterView:(CPView)aView
{
if (spacing === CPStackViewSpacingUseDefault)
[_customSpacings removeObjectForKey:aView];
else
[_customSpacings setObject:spacing forKey:aView];
[self setNeedsLayout:YES];
}
- (float)_spacingAfterView:(CPView)aView
{
var custom = [self customSpacingAfterView:aView];
if (custom !== CPStackViewSpacingUseDefault)
return custom;
return _spacing;
}
// MARK: -
// MARK: Visibility Priority
- (void)setVisibilityPriority:(float)priority forView:(CPView)aView
{
[_visibilityPriorities setObject:priority forKey:aView];
if (priority === CPStackViewVisibilityPriorityNotVisible)
{
[aView setHidden:YES];
}
else if (priority === CPStackViewVisibilityPriorityMustHold)
{
[aView setHidden:NO];
}
// Note: Intermediate priorities require complex constraint logic
// or a multi-pass layout system to determine fitting, which is
// simplified here to basic Hidden/Visible states.
[self setNeedsLayout:YES];
}
- (float)visibilityPriorityForView:(CPView)aView
{
var val = [_visibilityPriorities objectForKey:aView];
if (val)
return [val floatValue];
return CPStackViewVisibilityPriorityMustHold;
}
// MARK: -
// MARK: Layout
- (void)resizeSubviewsWithOldSize:(CGSize)oldSize
{
[self layoutSubviews];
}
- (void)layoutSubviews
{
if (_orientation === CPUserInterfaceLayoutOrientationVertical)
[self _layoutVertical];
else
[self _layoutHorizontal];
}
- (void)_layoutHorizontal
{
var bounds = [self bounds],
availWidth = CGRectGetWidth(bounds) - _edgeInsets.left - _edgeInsets.right,
availHeight = CGRectGetHeight(bounds) - _edgeInsets.top - _edgeInsets.bottom,
currentX = _edgeInsets.left;
// 1. Layout Leading Views
currentX = [self _layoutViews:_viewsLeading startOffset:currentX availableOrthogonalSize:availHeight direction:1];
// 2. Layout Trailing Views
// We layout backwards from the right
var startRight = CGRectGetWidth(bounds) - _edgeInsets.right;
[self _layoutViews:_viewsTrailing startOffset:startRight availableOrthogonalSize:availHeight direction:-1];
// 3. Layout Center Views
if ([_viewsCenter count] > 0)
{
// Calculate total width of center stack
var centerStackWidth = 0.0;
for (var i = 0; i < [_viewsCenter count]; i++)
{
var view = _viewsCenter[i];
if (_detachesHiddenViews && [view isHidden]) continue;
centerStackWidth += CGRectGetWidth([view frame]);
if (i < [_viewsCenter count] - 1)
centerStackWidth += [self _spacingAfterView:view];
}
var centerStart = (CGRectGetWidth(bounds) / 2.0) - (centerStackWidth / 2.0);
// Clamp to prevent overlap with Leading (simplified collision logic)
// ideally stack view compresses views, but here we just shift/clip
if (centerStart < currentX)
centerStart = currentX;
[self _layoutViews:_viewsCenter startOffset:centerStart availableOrthogonalSize:availHeight direction:1];
}
}
- (void)_layoutVertical
{
var bounds = [self bounds],
availWidth = CGRectGetWidth(bounds) - _edgeInsets.left - _edgeInsets.right,
availHeight = CGRectGetHeight(bounds) - _edgeInsets.top - _edgeInsets.bottom,
currentY = _edgeInsets.top;
// 1. Layout Top (Leading) Views
currentY = [self _layoutViews:_viewsLeading startOffset:currentY availableOrthogonalSize:availWidth direction:1];
// 2. Layout Bottom (Trailing) Views
var startBottom = CGRectGetHeight(bounds) - _edgeInsets.bottom;
[self _layoutViews:_viewsTrailing startOffset:startBottom availableOrthogonalSize:availWidth direction:-1];
// 3. Layout Center Views
if ([_viewsCenter count] > 0)
{
var centerStackHeight = 0.0;
for (var i = 0; i < [_viewsCenter count]; i++)
{
var view = _viewsCenter[i];
if (_detachesHiddenViews && [view isHidden]) continue;
centerStackHeight += CGRectGetHeight([view frame]);
if (i < [_viewsCenter count] - 1)
centerStackHeight += [self _spacingAfterView:view];
}
var centerStart = (CGRectGetHeight(bounds) / 2.0) - (centerStackHeight / 2.0);
if (centerStart < currentY)
centerStart = currentY;
[self _layoutViews:_viewsCenter startOffset:centerStart availableOrthogonalSize:availWidth direction:1];
}
}
// Helper to layout a specific array of views in one direction
// Returns the ending offset
- (float)_layoutViews:(CPArray)views startOffset:(float)offset availableOrthogonalSize:(float)orthoSize direction:(int)dir
{
var cursor = offset;
var isVert = (_orientation === CPUserInterfaceLayoutOrientationVertical);
// If direction is -1 (Trailing/Bottom), we iterate backwards
// However, the standard behavior for trailing gravity is that the *last* view added is at the *end*.
// Leading: [A] [B] ->
// Trailing: -> [C] [D] (where D is rightmost)
// To support Trailing logic: We start at Right Edge, move left by Width(D), place D, move left by Spacing...
var count = [views count];
if (count === 0) return cursor;
// If direction is negative (Trailing), we process list in reverse order to stack them from edge inwards
var i = (dir === 1) ? 0 : count - 1;
var limit = (dir === 1) ? count : -1;
var step = (dir === 1) ? 1 : -1;
for (; i !== limit; i += step)
{
var view = views[i];
if (_detachesHiddenViews && [view isHidden])
continue;
var viewFrame = [view frame];
var viewSizePrimary = isVert ? CGRectGetHeight(viewFrame) : CGRectGetWidth(viewFrame);
// Handle Alignment (Orthogonal Axis)
var orthoPos = 0.0;
var viewOrthoSize = isVert ? CGRectGetWidth(viewFrame) : CGRectGetHeight(viewFrame);
// Apply Stretch/Fill Alignment
if (isVert)
{
// Vertical Stack, dealing with Width
if (_alignment === CPLayoutAttributeWidth || _alignment === CPLayoutAttributeLeading || _alignment === CPLayoutAttributeTrailing)
{
// Note: CPLayoutAttributeLeading/Trailing in this context implies filling width usually,
// or aligning to edges. Let's assume Width/Fill for Leading/Trailing/Left/Right
// in this simplified implementation, or strictly left/right.
if (_alignment === CPLayoutAttributeWidth || _alignment === CPLayoutAttributeLeft || _alignment === CPLayoutAttributeLeading)
{
// Fill width if explicit, or just align left
if (_alignment === CPLayoutAttributeWidth) viewOrthoSize = orthoSize;
orthoPos = _edgeInsets.left;
}
else if (_alignment === CPLayoutAttributeRight || _alignment === CPLayoutAttributeTrailing)
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize);
}
else // CenterX
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize) / 2.0;
}
}
else // Default CenterX
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize) / 2.0;
}
}
else
{
// Horizontal Stack, dealing with Height
if (_alignment === CPLayoutAttributeHeight || _alignment === CPLayoutAttributeTop || _alignment === CPLayoutAttributeBottom)
{
if (_alignment === CPLayoutAttributeHeight)
{
viewOrthoSize = orthoSize;
orthoPos = _edgeInsets.top;
}
else if (_alignment === CPLayoutAttributeTop)
{
orthoPos = _edgeInsets.top;
}
else if (_alignment === CPLayoutAttributeBottom)
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize);
}
else // CenterY
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize) / 2.0;
}
}
else // Default CenterY
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize) / 2.0;
}
}
// Calculate Position
var originX = 0.0, originY = 0.0;
var sizeW = 0.0, sizeH = 0.0;
if (isVert)
{
// Vertical
sizeH = viewSizePrimary;
sizeW = viewOrthoSize;
originX = orthoPos;
if (dir === 1) {
originY = cursor;
cursor += sizeH + [self _spacingAfterView:view];
} else {
cursor -= sizeH;
originY = cursor;
cursor -= [self _spacingAfterView:view];
}
}
else
{
// Horizontal
sizeW = viewSizePrimary;
sizeH = viewOrthoSize;
originY = orthoPos;
if (dir === 1) {
originX = cursor;
cursor += sizeW + [self _spacingAfterView:view];
} else {
cursor -= sizeW;
originX = cursor;
cursor -= [self _spacingAfterView:view];
}
}
[view setFrame:CGRectMake(originX, originY, sizeW, sizeH)];
}
return cursor;
}
// MARK: -
// MARK: CPCoding
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
_orientation = [aCoder decodeIntForKey:@"CPStackViewOrientation"];
_alignment = [aCoder decodeIntForKey:@"CPStackViewAlignment"];
_spacing = [aCoder decodeFloatForKey:@"CPStackViewSpacing"];
_edgeInsets = [aCoder decodeObjectForKey:@"CPStackViewEdgeInsets"]; // Assuming CPEdgeInsets supports obj coding or manual decode
if (!_edgeInsets) _edgeInsets = CPEdgeInsetsMake(0,0,0,0);
_detachesHiddenViews = [aCoder decodeBoolForKey:@"CPStackViewDetachesHiddenViews"];
_viewsLeading = [aCoder decodeObjectForKey:@"CPStackViewViewsLeading"] || [];
_viewsCenter = [aCoder decodeObjectForKey:@"CPStackViewViewsCenter"] || [];
_viewsTrailing = [aCoder decodeObjectForKey:@"CPStackViewViewsTrailing"] || [];
// Rebuild arranged subviews cache
_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
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeInt:_orientation forKey:@"CPStackViewOrientation"];
[aCoder encodeInt:_alignment forKey:@"CPStackViewAlignment"];
[aCoder encodeFloat:_spacing forKey:@"CPStackViewSpacing"];
[aCoder encodeObject:_edgeInsets forKey:@"CPStackViewEdgeInsets"];
[aCoder encodeBool:_detachesHiddenViews forKey:@"CPStackViewDetachesHiddenViews"];
[aCoder encodeObject:_viewsLeading forKey:@"CPStackViewViewsLeading"];
[aCoder encodeObject:_viewsCenter forKey:@"CPStackViewViewsCenter"];
[aCoder encodeObject:_viewsTrailing forKey:@"CPStackViewViewsTrailing"];
[aCoder encodeObject:_customSpacings forKey:@"CPStackViewCustomSpacings"];
}
@end
+15 -76
View File
@@ -42,8 +42,8 @@
CPButton _buttonUp;
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
/*!
Initializes a CPStepper with given values.
@@ -147,8 +147,8 @@
[self _sizeToFit];
}
// MARK: -
// MARK: Superclass overrides
#pragma mark -
#pragma mark Superclass overrides
/*!
Set if the CPStepper is enabled or not.
@@ -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" inState:CPThemeStateBordered];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted]];
}
- (void)_sizeToFit
@@ -290,8 +230,8 @@
[super setDoubleValue:aValue];
}
// MARK: -
// MARK: Actions
#pragma mark -
#pragma mark Actions
/*! @ignore */
- (IBAction)_buttonDidClick:(id)aSender
@@ -326,8 +266,8 @@
}
// MARK: -
// MARK: Theming
#pragma mark -
#pragma mark Theming
+ (CPString)defaultThemeClass
{
@@ -383,7 +323,6 @@ var CPStepperMinValue = @"CPStepperMinValue",
_autorepeat = [aCoder decodeBoolForKey:CPStepperAutorepeat];
[self _init];
[self setEnabled:[self isEnabled]];
}
return self;
+19 -15
View File
@@ -32,7 +32,7 @@ var CPStringSizeWithFontInWidthCache = [],
CPStringSizeWithFontHeightCache = [],
CPStringSizeMeasuringContext;
CPCanvasStringSizingIsFunctional = NO;
CPStringSizeCachingEnabled = YES;
@implementation CPString (CPStringDrawing)
@@ -63,38 +63,36 @@ CPCanvasStringSizingIsFunctional = NO;
return;
#if PLATFORM(DOM)
if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
{
if (!CPStringSizeMeasuringContext)
CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate();
// This is to make sure that Canvas based string sizing is functional before we use it.
// Unfortunately, as of today canvas sizing is not funtional any more. Neither in Chrome nor in FF
CPCanvasStringSizingIsFunctional = NO;
}
if (CPFeatureIsCompatible(CPHTMLCanvasFeature) && !CPStringSizeMeasuringContext)
CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate();
#endif
}
- (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
- (CGSize)_sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
{
var size;
#if PLATFORM(DOM)
if (!CPStringSizeCachingEnabled)
return [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self];
if (sizeCacheForFont === undefined)
sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = [];
if (!aWidth)
aWidth = '0';
var cssString = [aFont cssString],
cacheKey = cssString + '_' + (aWidth ? aWidth : '0');
cacheKey = cssString + '_' + aWidth;
size = sizeCacheForFont[cacheKey];
if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey))
return CGSizeMakeCopy(size);
if (!CPCanvasStringSizingIsFunctional || aWidth)
if (!CPFeatureIsCompatible(CPHTMLCanvasFeature) || aWidth > 0)
size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
else
{
@@ -111,9 +109,15 @@ CPCanvasStringSizingIsFunctional = NO;
sizeCacheForFont[cacheKey] = size;
#else
size = CGSizeMake(0, 0);
size = CGSizeMake(0, 0);
#endif
return CGSizeMakeCopy(size);
}
- (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
{
var size = [self _sizeWithFont:aFont inWidth:aWidth];
return CGSizeMake(CEIL(size.width), size.height);
}
@end
+21 -122
View File
@@ -79,20 +79,6 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
unsigned _delegateSelectors;
}
+ (CPString)defaultThemeClass
{
return @"tab-view";
}
+ (CPDictionary)themeAttributes
{
return @{
@"nib2cib-adjustment-frame": [CPNull null],
@"should-center-on-border": NO,
@"box-content-inset": CGInsetMakeZero()
};
}
- (id)initWithFrame:(CGRect)aFrame
{
if (self = [super initWithFrame:aFrame])
@@ -109,18 +95,14 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
{
_tabs = [[_CPSegmentedControl alloc] initWithFrame:CGRectMakeZero()];
[_tabs setTabView:self];
[_tabs setHitTests:NO];
[_tabs setSegments:[CPArray array]];
[_tabs setAction:@selector(_reflectSelectedTab:)];
[_tabs setTarget:self];
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()];
[self setBackgroundColor:[CPColor colorWithCalibratedWhite:0.95 alpha:1.0]];
[self addSubview:_box];
[self addSubview:_tabs];
@@ -150,10 +132,10 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
*/
- (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPUInteger)anIndex
{
[self _insertTabViewItems:[aTabViewItem] atIndexes:[CPIndexSet indexSetWithIndex:anIndex] canUpdateSelectedTab:YES];
[self _insertTabViewItems:[aTabViewItem] atIndexes:[CPIndexSet indexSetWithIndex:anIndex]];
}
- (void)_insertTabViewItems:(CPArray)tabViewItems atIndexes:(CPIndexSet)indexes canUpdateSelectedTab:(BOOL)canUpdateSelectedTab
- (void)_insertTabViewItems:(CPArray)tabViewItems atIndexes:(CPIndexSet)indexes
{
var prevItemsCount = [self numberOfTabViewItems];
@@ -166,7 +148,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
[self _sendDelegateTabViewDidChangeNumberOfTabViewItems];
// Do not allow empty selection if selection bindings are not enabled.
if (prevItemsCount == 0 && [self numberOfTabViewItems] > 0 && ![self _isSelectionBinded] && canUpdateSelectedTab)
if (prevItemsCount == 0 && [self numberOfTabViewItems] > 0 && ![self _isSelectionBinded])
[self _selectTabViewItemAtIndex:0];
}
@@ -293,7 +275,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
*/
- (void)selectNextTabViewItem:(id)aSender
{
if (_selectedTabViewItem == nil)
if (_selectedTabViewItem === nil)
return;
var nextIndex = [self indexOfTabViewItem:_selectedTabViewItem] + 1;
@@ -311,7 +293,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
*/
- (void)selectPreviousTabViewItem:(id)aSender
{
if (_selectedTabViewItem == nil)
if (_selectedTabViewItem === nil)
return;
var previousIndex = [self indexOfTabViewItem:_selectedTabViewItem] - 1;
@@ -371,15 +353,15 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
{
var controller = [aTabViewItem viewController];
if (controller != nil && ![controller isViewLoaded])
if (controller !== nil && ![controller isViewLoaded])
{
[controller loadViewWithCompletionHandler:function(view, error)
{
if (error != nil)
if (error !== nil)
{
CPLog.warn("Could not load the view for item " + aTabViewItem + ". " + error);
}
else if (view != nil)
else if (view !== nil)
{
[aTabViewItem setView:view];
@@ -481,10 +463,10 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
{
var aFrame = [self frame],
segmentedHeight = CGRectGetHeight([_tabs frame]),
borderWidth = [self currentValueForThemeAttribute:@"should-center-on-border"] ? [_box borderWidth] : 0,
origin = _type === CPTopTabsBezelBorder ? (segmentedHeight - borderWidth) / 2 : 0;
origin = _type === CPTopTabsBezelBorder ? segmentedHeight / 2 : 0;
[_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame), CGRectGetHeight(aFrame) - (segmentedHeight - borderWidth) / 2)];
[_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame),
CGRectGetHeight(aFrame) - segmentedHeight / 2)];
[self _repositionTabs];
}
@@ -544,11 +526,6 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
return [_box backgroundColor];
}
- (void)mouseDown:(CPEvent)anEvent
{
[_tabs trackSegment:anEvent];
}
- (void)_repositionTabs
{
var horizontalCenterOfSelf = CGRectGetWidth([self bounds]) / 2,
@@ -565,11 +542,6 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
[_box setContentView:aView];
}
- (void)_reflectSelectedTab:(id)aSender
{
[self selectTabViewItemAtIndex:[_tabs selectedSegment]];
}
// DELEGATE METHODS
- (BOOL)_sendDelegateShouldSelectTabViewItem:(CPTabViewItem)aTabViewItem
@@ -627,7 +599,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
{
var theBinder = [self binderForBinding:CPSelectionIndexesBinding];
if (theBinder != nil)
if (theBinder !== nil)
[theBinder reverseSetValueFor:@"selectionIndexes"];
else
{
@@ -696,8 +668,8 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
[self _displayItemView:_placeholderView];
}
// MARK: -
// MARK: Override
#pragma mark -
#pragma mark Override
/*!
Enabled controls accept first mouse by default.
@@ -824,7 +796,7 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
[_tabs setFont:_font];
var items = [aCoder decodeObjectForKey:CPTabViewItemsKey] || [CPArray array];
[self _insertTabViewItems:items atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [items count])] canUpdateSelectedTab:NO];
[self _insertTabViewItems:items atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [items count])]];
[self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
@@ -886,47 +858,14 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
@end
// MARK: -
@implementation CPTabView (CSSTheming)
// MARK: Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[[self subviews] makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
// Items must also perform this (without this, only the selected item does)
for (var i = 0, allItems = [self items], count = allItems.length; i < count; i++)
if (allItems[i] != _selectedTabViewItem)
[[allItems[i] view] _setThemeIncludingDescendants:aTheme];
}
@end
// MARK: -
@implementation _CPTabViewBox : CPBox
{
CPTabView _tabView @accessors(property=tabView);
CGInset _contentInset @accessors(property=contentInset);
CPTabView _tabView @accessors(property=tabView);
}
// MARK: -
// MARK: Override
- (id)initWithFrame:(CGRect)aFrame
{
if (self = [super initWithFrame:aFrame])
{
[self setBoxType:CPBoxPrimary];
}
return self;
}
#pragma mark -
#pragma mark Override
- (CPView)hitTest:(CGPoint)aPoint
{
@@ -940,46 +879,6 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
return [super hitTest:aPoint];
}
// Some CPBox overrides because CPTabView use of CPBox differs from legacy CPBox regarding layout.
- (void)setContentView:(CPView)aView
{
if (aView === _contentView)
return;
if (_contentInset)
[aView setFrame:CGRectInsetByInset([self bounds], _contentInset)];
[aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
// A nil contentView is allowed (tested in Cocoa 2013-02-22).
if (!aView)
[_contentView removeFromSuperview];
else if (_contentView)
[_boxView replaceSubview:_contentView with:aView];
else
[_boxView addSubview:aView];
_contentView = aView;
[self refreshDisplay];
}
- (void)sizeToFit
{
var offset = [self _titleHeightOffset],
size = [self frameSize];
[_boxView setFrame:CGRectMake(0, offset[1], size.width, size.height - offset[0])];
if (!_contentView)
return;
var boxSize = [_boxView frameSize];
if (_contentInset)
[_contentView setFrame:CGRectMake(_contentInset.left, _contentInset.top, boxSize.width - _contentInset.left - _contentInset.right, boxSize.height - _contentInset.top - _contentInset.bottom)];
}
@end
# pragma mark -
@@ -1015,7 +914,7 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
{
[self setSelected:YES forSegment:_trackingSegment];
_selectedSegment = _trackingSegment;
[self sendAction:[self action] to:[self target]];
[_tabView selectTabViewItemAtIndex:_selectedSegment];
}
[self drawSegmentBezel:_trackingSegment highlight:NO];
+9 -55
View File
@@ -593,7 +593,7 @@ CPTableColumnUserResizingMask = 1 << 1;
{
var options = [_info objectForKey:CPOptionsKey],
optionValue = [options objectForKey:CPCreatesSortDescriptorBindingOption];
return optionValue == nil ? YES : [optionValue boolValue];
return optionValue === nil ? YES : [optionValue boolValue];
}
@end
@@ -639,53 +639,6 @@ CPTableColumnUserResizingMask = 1 << 1;
}
}
/*!
@ignore
This method will return the object at a row in the first found CPArray in the key path
that is divided in a first and second part.
The first part is never a combined key path. The second part can be a combined key path.
If this optimization is not done we will create an array with the valueForKeyPath value on each row and then pick
the wanted value for the row and throw away all the other rows. It is much more effective to first
pick the row and then do the valueForKeyPath on the rest of the key path.
When the second part is depleated it will stop the search for a CPArray and return the
current object for the first part. The second part will then be nil.
The secondPartRef will always be updated with the rest of the key path that can be applied
to the returned object.
It will stop the search if the object is nil
*/
- (CPValueCoding)_firstObjectInArrayUsingKeyPathFirstPart:(CPString)firstPart secondPart:(CPStringRef)secondPartRef sourceObject:(CPValueCoding)source forRow:(unsigned)aRow
{
var firstValue = [source valueForKeyPath:firstPart];
if (firstValue == nil)
return firstValue;
if ([firstValue isKindOfClass:CPArray])
return [firstValue objectAtIndex:aRow];
var secondPart = @deref(secondPartRef);
if (secondPart == nil)
return firstValue;
var dotIndex = secondPart.indexOf(".");
if (dotIndex === CPNotFound)
{
firstPart = secondPart;
@deref(secondPartRef) = nil;
}
else
{
firstPart = secondPart.substring(0, dotIndex);
@deref(secondPartRef) = secondPart.substring(dotIndex + 1);
}
return [self _firstObjectInArrayUsingKeyPathFirstPart:firstPart secondPart:secondPartRef sourceObject:firstValue forRow:aRow];
}
/*!
@ignore
*/
@@ -702,7 +655,7 @@ CPTableColumnUserResizingMask = 1 << 1;
bindingInfo = binding._info,
destination = [bindingInfo objectForKey:CPObservedObjectKey],
keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
dotIndex = keyPath.indexOf("."),
dotIndex = keyPath.lastIndexOf("."),
value;
if (dotIndex === CPNotFound)
@@ -717,16 +670,17 @@ CPTableColumnUserResizingMask = 1 << 1;
The optimization is to get the array and access the value directly. This
turns the operation into a single access regardless of how long the model
array is or how long the key path is.
array is.
*/
var firstPart = keyPath.substring(0, dotIndex),
secondPart = keyPath.substring(dotIndex + 1);
secondPart = keyPath.substring(dotIndex + 1),
firstValue = [destination valueForKeyPath:firstPart];
value = [self _firstObjectInArrayUsingKeyPathFirstPart:firstPart secondPart:@ref(secondPart) sourceObject:destination forRow:aRow];
if (secondPart != nil)
value = [value valueForKeyPath:secondPart];
if ([firstValue isKindOfClass:CPArray])
value = [[firstValue objectAtIndex:aRow] valueForKeyPath:secondPart];
else
value = [[firstValue valueForKeyPath:secondPart] objectAtIndex:aRow];
}
value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]];
+25 -233
View File
@@ -25,10 +25,6 @@
@import "CPCursor.j"
@import "_CPImageAndTextView.j"
@import "CPTrackingArea.j"
@import "CPAnimationContext.j"
@import "CPViewAnimator.j"
@import "CPScrollView.j"
@import <Foundation/CPGeometry.j>
@class CPTableView
@@ -54,16 +50,10 @@
@"text-color": [CPNull null],
@"font": [CPNull null],
@"text-shadow-color": [CPNull null],
@"text-shadow-offset": CGSizeMakeZero(),
@"dont-draw-separator": NO
@"text-shadow-offset": CGSizeMakeZero()
};
}
- (BOOL)acceptsFirstMouse:(CPEvent)anEvent
{
return YES;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
@@ -76,22 +66,18 @@
- (void)_init
{
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
var inset = [self valueForThemeAttribute:@"text-inset"];
_textField = [[_CPImageAndTextView alloc] initWithFrame:
CGRectMake(inset.left, inset.top, CGRectGetWidth([self bounds]) - (inset.left + inset.right), CGRectGetHeight([self bounds]) - (inset.top + inset.bottom))];
CGRectMake(5.0, 0.0, CGRectGetWidth([self bounds]) - 10.0, CGRectGetHeight([self bounds]))];
[_textField setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_textField setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode"]];
[_textField setTextColor:[self valueForThemeAttribute:@"text-color"]];
[_textField setFont:[self valueForThemeAttribute:@"font"]];
[_textField setAlignment:[self valueForThemeAttribute:@"text-alignment"]];
[_textField setLineBreakMode:CPLineBreakByTruncatingTail];
[_textField setTextColor:[CPColor colorWithRed:51.0 / 255.0 green:51.0 / 255.0 blue:51.0 / 255.0 alpha:1.0]];
[_textField setFont:[CPFont boldSystemFontOfSize:12.0]];
[_textField setAlignment:CPLeftTextAlignment];
[_textField setVerticalAlignment:CPCenterVerticalTextAlignment];
[_textField setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color"]];
[_textField setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset"]];
[_textField setTextShadowColor:[CPColor whiteColor]];
[_textField setTextShadowOffset:CGSizeMake(0,1)];
[self addSubview:_textField];
}
@@ -122,7 +108,7 @@
return [_textField text];
}
- (CPTextField)textField
- (void)textField
{
return _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)
@@ -604,7 +550,7 @@ var CPTableHeaderViewResizeZone = 3.0,
- (void)_autoscroll:(CPEvent)theEvent localLocation:(CGPoint)theLocation
{
// Constrain the y coordinate so we don't autoscroll vertically
var constrainedLocation = CGPointMake(theLocation.x, CGRectGetMaxY([self frame])),
var constrainedLocation = CGPointMake(theLocation.x, CGRectGetMinY([_tableView visibleRect])),
constrainedEvent = [CPEvent mouseEventWithType:CPLeftMouseDragged
location:[self convertPoint:constrainedLocation toView:nil]
modifierFlags:[theEvent modifierFlags]
@@ -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
+492 -581
View File
File diff suppressed because it is too large Load Diff
+1 -17
View File
@@ -84,18 +84,6 @@ CPCenterTextAlignment = 2;
CPJustifiedTextAlignment = 3;
CPNaturalTextAlignment = 4;
@typedef CPUnderlineStyle
CPUnderlineStyleNone = 0;
CPUnderlineStyleSingle = 1;
CPUnderlineStyleThick = 2;
CPUnderlineStyleDouble = 3;
CPUnderlineStylePatternSolid = 4;
CPUnderlineStylePatternDot = 5;
CPUnderlineStylePatternDash = 6;
CPUnderlineStylePatternDashDot = 7;
CPUnderlineStylePatternDashDotDot = 8;
CPUnderlineStyleByWord = 9;
/*
CPText notifications
*/
@@ -179,11 +167,7 @@ CPKernAttributeName = @"CPKernAttributeName";
{
var pasteboard = [CPPasteboard generalPasteboard],
dataForPasting = [pasteboard stringForType:CPRTFPboardType],
stringForPasting = [pasteboard stringForType:CPStringPboardType],
attributedStringData = [pasteboard stringForType:_CPASPboardType];
if ([self isRichText] && attributedStringData)
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPData dataWithRawString:attributedStringData]];
stringForPasting = [pasteboard stringForType:CPStringPboardType];
if (dataForPasting || [stringForPasting hasPrefix:"{\\rtf1\\ansi"])
stringForPasting = [[_CPRTFParser new] parseRTF:dataForPasting ? dataForPasting : stringForPasting];
+52 -300
View File
@@ -34,8 +34,6 @@
@global CPStringPboardType
@global CPCursor
@global document
@protocol CPTextFieldDelegate <CPControlTextEditingDelegate>
@end
@@ -68,11 +66,7 @@ var CPTextFieldDOMCurrentElement = nil,
CPTextFieldCachedDragFunction = nil,
CPTextFieldBlurHandler = nil,
CPTextFieldInputFunction = nil,
CPTexFieldCurrentCSSSelectableField = nil,
CPTextFieldLastValidationFailureEvent = nil,
CPTextFieldLastValidationFailureString = nil,
CPTextFieldLastValidationFailureField = nil,
CPTextFieldLastValidationFailureResult = NO;
CPTexFieldCurrentCSSSelectableField = nil;
var CPSecureTextFieldCharacter = "\u2022";
@@ -175,8 +169,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
+ (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
{
var minSize = aTheme ? [aTheme valueForAttributeWithName:@"min-size" forClass:CPTextField] : CGSizeMake(0,0),
textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, minSize.height)];
var textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
[textField setTheme:aTheme];
[textField setStringValue:aStringValue];
@@ -197,8 +190,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
+ (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
{
var minSize = aTheme ? [aTheme valueForAttributeWithName:@"min-size" forClass:CPTextField] : CGSizeMake(0,0),
textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, minSize.height)];
var textField = [[CPTextField alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)];
[textField setTheme:aTheme];
[textField setStringValue:aStringValue];
@@ -239,14 +231,12 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
@"bezel-inset": CGInsetMakeZero(),
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
@"bezel-color": [CPNull null],
@"min-size": CGSizeMake(0, 29),
@"background-inset": CGInsetMakeZero()
};
}
// MARK: -
// MARK: Control Size
#pragma mark -
#pragma mark Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
@@ -257,7 +247,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
// MARK: -
#pragma mark -
#if PLATFORM(DOM)
- (DOMElement)_inputElement
@@ -336,8 +326,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
characters:nil
charactersIgnoringModifiers:nil
isARepeat:NO
keyCode:nil
isActionKey:NO];
keyCode:nil];
[CPTextFieldInputOwner keyUp:cappEvent];
@@ -376,12 +365,14 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self setPlaceholderString:@""];
_sendActionOn = CPKeyUpMask | CPKeyDownMask;
[self setValue:CPNaturalTextAlignment forThemeAttribute:@"alignment"];
}
return self;
}
// MARK: Controlling Editability and Selectability
#pragma mark Controlling Editability and Selectability
/*!
Sets whether or not the receiver text field can be edited. If NO, any
@@ -566,9 +557,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
Sets the background color, which is shown for non-bezeled text fields with drawsBackground set to YES
@param aColor The background color
*/
- (void)setBackgroundColor:(CPColor)aColor
- (void)setTextFieldBackgroundColor:(CPColor)aColor
{
if (_backgroundColor == aColor)
if (_textFieldBackgroundColor == aColor)
return;
_textFieldBackgroundColor = aColor;
@@ -580,7 +571,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
/*!
Returns the background color.
*/
- (CPColor)backgroundColor
- (CPColor)textFieldBackgroundColor
{
return _textFieldBackgroundColor;
}
@@ -695,38 +686,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
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)];
var point = CGPointMake([self convertPointFromBase:[[CPApp currentEvent] locationInWindow]].x - [self currentValueForThemeAttribute:@"content-inset"].left, 0),
position = [CPPlatformString charPositionOfString:[self stringValue] withFont:[self font] forPoint:point];
[self setSelectedRange:CPMakeRange(position, 0)];
}
@@ -765,33 +726,39 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
#if PLATFORM(DOM)
var element = [self _inputElement],
font = [self font],
font = [self currentValueForThemeAttribute:@"font"],
lineHeight = [font defaultLineHeightForFont],
contentRect = [self contentRectForBounds:[self bounds]],
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])
{
@@ -816,7 +783,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
element.style.zIndex = 1000;
element.style.top = topPoint;
element.style.lineHeight = ROUND(lineHeight) + "px";
element.style.height = isTextArea ? CGRectGetHeight(contentRect) + "px" : ROUND(lineHeight) + "px";
element.style.height = isTextArea ? CGRectGetHeight(contentRect) + "px" : ROUND(lineHeight) + "px";;
element.style.width = CGRectGetWidth(contentRect) + "px";
element.style.left = left + "px";
element.style.verticalAlign = "top";
@@ -995,26 +962,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
var acceptInvalidValue = NO;
if (_implementedDelegateMethods & CPTextFieldDelegate_control_didFailToFormatString_errorDescription_)
{
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 = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
if (acceptInvalidValue === NO)
return NO;
@@ -1144,16 +1092,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
- (void)keyDown:(CPEvent)anEvent
{
// Has to be enabled, and it also has to be editable or selectable.
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
if (!([self isEnabled] && [self isEditable]))
return;
if ([self isEditable] && !_isEditing)
{
_isEditing = YES;
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
}
// CPTextField uses an HTML input element to take the input so we need to
// propagate the dom event so the element is updated. This has to be done
// before interpretKeyEvents: though so individual commands have a chance
@@ -1295,16 +1236,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[super textDidChange:note];
}
- (void)validateEditing
{
#if PLATFORM(DOM)
var element = [self _inputElement];
if (element)
[self _setStringValue:element.value isNewValue:YES errorDescription:nil];
#endif
}
- (void)textDidBeginEditing:(CPNotification)note
{
//this looks to prevent false propagation of notifications for other objects
@@ -1400,7 +1331,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
// If there is a formatter, make sure the object value can be formatted successfully
var formattedString = [self hasThemeState:CPThemeStateEditing] ? [formatter editingStringForObjectValue:aValue] : [formatter stringForObjectValue:aValue];
if (formattedString == nil)
if (formattedString === nil)
{
var value = nil;
@@ -1410,7 +1341,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
value = undefined;
[super setObjectValue:value];
_stringValue = (value == nil) ? @"" : String(value);
_stringValue = (value === nil || value === undefined) ? @"" : String(value);
}
else
_stringValue = formattedString;
@@ -1497,7 +1428,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
lineBreakMode = [self lineBreakMode],
text = (_stringValue || @" "),
textSize = CGSizeMakeCopy(frameSize),
font = [self font];
font = [self currentValueForThemeAttribute:@"font"];
textSize.width -= contentInset.left + contentInset.right;
textSize.height -= contentInset.top + contentInset.bottom;
@@ -1566,7 +1497,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
else
[[CPRunLoop mainRunLoop] performBlock:function(){ element.select(); } argument:nil order:0 modes:[CPDefaultRunLoopMode]];
}
else if (wind != nil && [wind makeFirstResponder:self])
else if (wind !== nil && [wind makeFirstResponder:self])
[self _selectText:sender immediately:immediately];
}
else
@@ -1576,7 +1507,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
#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)
if (wind !== nil && [wind firstResponder] !== self)
[wind makeFirstResponder:self];
#endif
}
@@ -1848,7 +1779,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _didEdit];
}
// MARK: Setting the Delegate
#pragma mark Setting the Delegate
- (void)setDelegate:(id <CPTextFieldDelegate>)aDelegate
{
@@ -1901,12 +1832,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (aName === "bezel-view")
return [self bezelRectForBounds:[self bounds]];
else if (aName === "background-view")
{
var backgroundInset = [self currentValueForThemeAttribute:@"background-inset"];
return CGRectInsetByInset([self bounds], backgroundInset);
}
else if (aName === "content-view")
return [self contentRectForBounds:[self bounds]];
@@ -1923,14 +1848,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return view;
}
else if (aName === "background-view")
{
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[view setHitTests:NO];
return view;
}
else
{
var view = [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
@@ -1945,37 +1862,16 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
- (void)layoutSubviews
{
var bezelColor = [self currentValueForThemeAttribute:@"bezel-color"];
if ([bezelColor isCSSBased])
{
// CSS Styling
// We don't need bezelView as we apply CSS styling directly on the text field view itself
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
// We need to call [super setBackgroundColor:] as we have redefined it here
[super setBackgroundColor:bezelColor];
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:nil],
backgroundView = [self layoutEphemeralSubviewNamed:@"background-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
if (bezelView)
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
[backgroundView setBackgroundColor:(_drawsBackground ? _textFieldBackgroundColor : [CPColor clearColor])];
}
else
{
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
[bezelView setBackgroundColor:bezelColor];
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
}
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
if (contentView)
{
@@ -1996,7 +1892,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[contentView setText:string];
[contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
[contentView setFont:[self font]];
[contentView setFont:[self currentValueForThemeAttribute:@"font"]];
[contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
[contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
[contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
@@ -2008,43 +1904,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _setCSSStyleForInputElement];
}
// MARK: Overrides
/*!
Sets the font of the receiver.
@param aFont - A CPFont object.
*/
- (void)setFont:(CPFont)aFont
{
if ([self currentValueForThemeAttribute:@"font"] === aFont)
return;
// Apply the font to the default/normal state
[self setValue:aFont forThemeAttribute:@"font"];
// Apply to standard editing and border states
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateEditing];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateBezeled];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateBordered];
[self setValue:aFont forThemeAttribute:@"font" inState:CPTextFieldStateRounded];
// Use CPThemeState() function to create composite states instead of array literals
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPTextFieldStateRounded, CPThemeStateEditing)];
// Apply across all standard control size states
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeRegular];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeSmall];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeMini];
// Apply to table data view states (ensuring Interface Builder-style lists respect the font)
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateTableDataView];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPThemeStateTableDataView, CPThemeStateSelectedDataView)];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPThemeStateTableDataView, CPThemeStateSelectedDataView, CPThemeStateFirstResponder, CPThemeStateKeyWindow)];
[self layoutSubviews];
}
- (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
{
var count = objects.length,
@@ -2061,7 +1920,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
}
// MARK: Overrides
#pragma mark Overrides
/*!
Sets the text color of the receiver.
@@ -2073,28 +1932,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
// We don't want to change the text-color of the placeHolder of the textField
var placeholderColor = [self valueForThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder];
// If the text field is a cell based table data view, we need to fix the color for all possible states
if ([self hasThemeState:CPThemeStateTableDataView])
{
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView]];
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView, CPThemeStateSelectedDataView]];
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView, CPThemeStateSelectedDataView, CPThemeStateFirstResponder, CPThemeStateKeyWindow]];
}
else
{
if ([self hasThemeState:CPTextFieldStateRounded])
{
[self setTextColor:aColor inThemeStates:[CPTextFieldStateRounded]];
[self setTextColor:aColor inThemeStates:[CPTextFieldStateRounded, CPThemeStateEditing]];
}
[self setTextColor:aColor inThemeStates:[CPThemeStateNormal]];
[self setTextColor:aColor inThemeStates:[CPThemeStateEditing]];
}
[super setTextColor:aColor];
[self setValue:placeholderColor forThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder];
[self layoutSubviews];
}
- (void)viewDidHide
@@ -2130,7 +1969,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return YES;
}
// MARK: Private
#pragma mark Private
- (BOOL)_isWithinUsablePlatformRect
{
@@ -2236,7 +2075,8 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
[self setSelectable:[aCoder decodeBoolForKey:CPTextFieldIsSelectableKey]];
[self setDrawsBackground:[aCoder decodeBoolForKey:CPTextFieldDrawsBackgroundKey]];
[self setBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
[self setTextFieldBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
[self setLineBreakMode:[aCoder decodeIntForKey:CPTextFieldLineBreakModeKey]];
[self setAlignment:[aCoder decodeIntForKey:CPTextFieldAlignmentKey]];
@@ -2246,7 +2086,6 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
[self _setUsesSingleLineMode:[aCoder decodeBoolForKey:CPTextFieldUsesSingleLineMode]];
[self _setWraps:[aCoder decodeBoolForKey:CPTextFieldWraps]];
[self _setScrolls:[aCoder decodeBoolForKey:CPTextFieldScrolls]];
[self updateTrackingAreas];
}
return self;
@@ -2281,56 +2120,6 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
@implementation _CPTextFieldValueBinder : CPBinder
+ (void)unbind:(CPString)aBinding forObject:(id)anObject
{
var theBinding = [self getBinding:aBinding forObject:anObject],
notificationCenter = [CPNotificationCenter defaultCenter];
if (theBinding)
{
[notificationCenter removeObserver:[theBinding._info objectForKey:CPObservedObjectKey]
name:CPControlTextDidBeginEditingNotification
object:anObject];
[notificationCenter removeObserver:[theBinding._info objectForKey:CPObservedObjectKey]
name:CPControlTextDidEndEditingNotification
object:anObject];
[super unbind:aBinding forObject:anObject];
}
}
- (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
{
self = [super initWithBinding:aBinding
name:aName
to:aDestination
keyPath:aKeyPath
options:options
from:aSource];
var notificationCenter = [CPNotificationCenter defaultCenter];
// This gives us support for the CPEditorRegistration informal protocol
if ([aDestination respondsToSelector:@selector(_objectDidBeginEditing:)])
{
[notificationCenter addObserver:aDestination
selector:@selector(_objectDidBeginEditing:)
name:CPControlTextDidBeginEditingNotification
object:aSource];
}
if ([aDestination respondsToSelector:@selector(_objectDidEndEditing:)])
{
[notificationCenter addObserver:aDestination
selector:@selector(_objectDidEndEditing:)
name:CPControlTextDidEndEditingNotification
object:aSource];
}
return self;
}
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
{
[super _updatePlaceholdersWithOptions:options];
@@ -2363,7 +2152,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
newValue = [self valueForBinding:aBinding],
value = [destination valueForKeyPath:keyPath];
if (CPIsControllerMarker(value) && newValue == nil)
if (CPIsControllerMarker(value) && newValue === nil)
return;
newValue = [self reverseTransformValue:newValue withOptions:options];
@@ -2428,40 +2217,3 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
}
@end
// MARK: -
@implementation CPTextField (TableDataView)
// We overide here _CPObject+Theme setValue:forThemeAttribute as CPTextField can be used as tableView data view
// So, when outside a table data view, setValue:forThemeAttribute should store the value with the CPThemeStateNormal (default behavior)
// When inside a table data view, it should store the value with the CPThemeStateTableDataView. If not, the value won't be used if the
// theme defined a value for this attribute for state CPThemeStateTableDataView
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
{
[super setValue:aValue forThemeAttribute:aName];
[super setValue:aValue forThemeAttribute:aName inState:CPThemeStateTableDataView];
}
@end
// MARK: -
@implementation CPTextField (Deprecated)
- (void)setTextFieldBackgroundColor:(CPColor)aColor
{
CPLog.error("[CPTextField setTextFieldBackgroundColor:] is deprecated, use [CPTextField setBackgroundColor:] instead.");
[self setBackgroundColor:aColor];
}
- (CPColor)textFieldBackgroundColor
{
CPLog.info("[CPTextField textFieldBackgroundColor] is deprecated, use [CPTextField backgroundColor] instead.");
return [self backgroundColor];
}
@end
+71 -263
View File
@@ -1,29 +1,34 @@
/*
CPFontPanel.j
AppKit
* CPFontPanel.j
* AppKit
*
* TODOs:
* 1. make browser-width for size smaller and fix columns
* 2. add all the missing features from the MacOS X counterpart (sampleview)
*
*
* Created by Daniel Boehringer on 2/JAN/2014.
* All modifications copyright Daniel Boehringer 2013.
* Extensive code formatting and review by Andrew Hankinson
* Based on original work by
* Created by Emmanuel Maillard on 06/03/2010.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
Created by Daniel Boehringer on 2/JAN/2014.
All modifications copyright Daniel Boehringer 2013.
Extensive code formatting and review by Andrew Hankinson
Based on original work by
Created by Emmanuel Maillard on 06/03/2010.
Copyright Emmanuel Maillard 2010.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPPanel.j"
@import "CPColorWell.j"
@@ -32,6 +37,7 @@
@import "CPText.j"
@import "CPFontManager.j"
@class CPTextStorage
@class CPLayoutManager
@class CPTextContainer
@@ -40,30 +46,27 @@
/*
Collection indexes
*/
var kTypefaceIndex_Normal = 0,
kTypefaceIndex_Italic = 1,
kTypefaceIndex_Bold = 2,
var kTypefaceIndex_Normal = 0,
kTypefaceIndex_Italic = 1,
kTypefaceIndex_Bold = 2,
kTypefaceIndex_BoldItalic = 3,
kToolbarHeight = 32,
kPreviewHeight = 70,
kBorderSpacing = 6,
kInnerSpacing = 2,
kNothingChanged = 0,
kFontNameChanged = 1,
kTypefaceChanged = 2,
kSizeChanged = 3,
kTextColorChanged = 4,
kBackgroundColorChanged = 5,
kUnderlineChanged = 6,
kWeightChanged = 7,
kToolbarHeight = 32,
kBorderSpacing = 6,
kInnerSpacing = 2,
kNothingChanged = 0,
kFontNameChanged = 1,
kTypefaceChanged = 2,
kSizeChanged = 3,
kTextColorChanged = 4,
kBackgroundColorChanged = 5,
kUnderlineChanged = 6,
kWeightChanged = 7,
_sharedFontPanel;
// FIXME<!> Locale support
var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"64", @"72", @"96", @"144", @"288"];
var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"72", @"96"];
/*!
@ingroup appkit
@@ -74,10 +77,6 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
id _fontBrowser;
id _traitBrowser;
id _sizeBrowser;
// Preview
_CPFontPanelPreviewView _previewView;
CPArray _availableFonts;
id _textColorWell;
CPColor _textColor;
@@ -86,15 +85,16 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
int _fontChanges;
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
/*!
Check if the shared Font panel exists.
*/
+ (BOOL)sharedFontPanelExists
{
return _sharedFontPanel != nil;
return _sharedFontPanel !== nil;
}
/*!
@@ -108,18 +108,14 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
return _sharedFontPanel;
}
- (BOOL)acceptsFirstResponder
{
return NO;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
/*! @ignore */
- (id)init
{
if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 420) styleMask:(CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask)])
if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 394) styleMask:(CPTitledWindowMask | CPClosableWindowMask /*| CPResizableWindowMask*/ )])
{
[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
[self setTitle:@"Font Panel"];
@@ -156,49 +152,10 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
[aBrowser setDoubleAction:@selector(dblClicked:)];
[aBrowser setAllowsEmptySelection:NO];
[aBrowser setAllowsMultipleSelection:NO];
// Config Scrollers
//[aBrowser setHasHorizontalScroller:NO];
//[aBrowser setHasVerticalScroller:YES];
//[aBrowser setAutohidesScrollers:YES];
//[aBrowser setMaxVisibleColumns:1];
[aBrowser setDelegate:self];
[[self contentView] addSubview:aBrowser];
}
- (void)_layoutBrowsers
{
var contentView = [self contentView],
contentBounds = [contentView bounds],
previewY = kBorderSpacing + kToolbarHeight + kInnerSpacing,
browserY = previewY + kPreviewHeight + 10,
browserHeight = CGRectGetHeight(contentBounds) - browserY - 10,
availableWidth = CGRectGetWidth(contentBounds) - 20; // 10px padding L/R
// Layout Calculations
// Increase sizeWidth slightly to 60 to allow space for the vertical scrollbar without clipping text
var sizeWidth = 90,
spacing = 5,
remainingWidth = availableWidth - sizeWidth - (spacing * 2),
// Split remaining roughly 60% font name, 40% trait
fontWidth = FLOOR(remainingWidth * 0.60),
traitWidth = remainingWidth - fontWidth;
// Apply frames and column constraints
[_fontBrowser setFrame:CGRectMake(10, browserY, fontWidth, browserHeight)];
[_fontBrowser setDefaultColumnWidth:fontWidth];
[_fontBrowser setLastColumn:0];
[_traitBrowser setFrame:CGRectMake(10 + fontWidth + spacing, browserY, traitWidth, browserHeight)];
[_traitBrowser setDefaultColumnWidth:traitWidth];
[_traitBrowser setLastColumn:0];
[_sizeBrowser setFrame:CGRectMake(10 + fontWidth + traitWidth + (spacing * 2), browserY, sizeWidth, browserHeight)];
[_sizeBrowser setDefaultColumnWidth:sizeWidth];
[_sizeBrowser setLastColumn:0];
}
- (void)_setupContents
{
if (_setupDone)
@@ -206,53 +163,33 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_setupDone = YES;
// We set ourselves as delegate to handle resizing layout manually
[self setDelegate:self];
[self _setupToolbarView];
var contentView = [self contentView],
contentBounds = [contentView bounds];
label = [CPTextField labelWithTitle:@"Font name"],
contentBounds = [contentView bounds],
upperView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(contentBounds), CGRectGetHeight(contentBounds) - (kBorderSpacing + kToolbarHeight + kInnerSpacing))];
[contentView addSubview:_toolbarView];
// Preview View
var previewY = kBorderSpacing + kToolbarHeight + kInnerSpacing;
_previewView = [[_CPFontPanelPreviewView alloc] initWithFrame:CGRectMake(10, previewY, CGRectGetWidth(contentBounds) - 20, kPreviewHeight)];
[_previewView setAutoresizingMask:CPViewWidthSizable];
[contentView addSubview:_previewView];
// Initialize Browsers with zero rect, _layoutBrowsers will size them
_fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
_traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
_sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
// Disable autoresizing masks because we are laying out manually in windowDidResize
[_fontBrowser setAutoresizingMask:CPViewNotSizable];
[_traitBrowser setAutoresizingMask:CPViewNotSizable];
[_sizeBrowser setAutoresizingMask:CPViewNotSizable];
_fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(10, 35, 150, 350)];
_traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(155, 35, 150, 350)];
_sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(300, 35, 140, 350)];
[self _setupBrowser:_fontBrowser];
[self _setupBrowser:_traitBrowser];
[self _setupBrowser:_sizeBrowser];
// Perform initial layout
[self _layoutBrowsers];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewDidChangeSelection:)
name:CPTextViewDidChangeSelectionNotification
object:nil];
}
- (void)windowDidResize:(CPNotification)aNotification
{
[self _layoutBrowsers];
}
- (void)textViewDidChangeSelection:(CPNotification)notification
{
[self _refreshWithTextView:[notification object]];
[self _refreshWithTextView:[notification object]];
}
- (void)_refreshWithTextView:(CPTextView)textView
@@ -260,9 +197,6 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if (![self isVisible])
return;
if (![textView respondsToSelector:@selector(_attributesForFontPanel)])
return;
var attribs = [textView _attributesForFontPanel],
font = [attribs objectForKey:CPFontAttributeName] || [[textView textStorage] font] || [CPFont systemFontOfSize:12.0],
color = [attribs objectForKey:CPForegroundColorAttributeName];
@@ -283,9 +217,6 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
[self setCurrentTrait:trait];
[self setCurrentSize:[font size] + ""]; //cast to string
// Update Preview
[_previewView setPreviewFont:font];
if (!color)
return;
@@ -322,7 +253,7 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
{
case kFontNameChanged:
newFont = [CPFont fontWithDescriptor:[[aFont fontDescriptor] fontDescriptorByAddingAttributes:
[CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0];
[CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0];
break;
case kTypefaceChanged:
@@ -341,13 +272,12 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toSize:[self currentSize]];
break;
case kNothingChanged:
case kNothingChanged:
break;
default:
CPLog.trace(@"FIXME: -[" + [self className] + " " + _cmd + "] unhandled _fontChanges: " + _fontChanges);
break;
}
return newFont;
@@ -355,7 +285,7 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
- (void)setCurrentSize:(CGSize)aSize
{
[_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0];
[_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0];
}
- (CPString)currentSize
@@ -365,7 +295,7 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
- (void)setCurrentFont:(CPFont)aFont
{
[_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0];
[_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0];
}
- (CPString)currentFont
@@ -390,10 +320,9 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
case kTypefaceIndex_BoldItalic:
row = 3;
break;
}
[_traitBrowser selectRow:row inColumn:0];
[_traitBrowser selectRow:row inColumn:0];
}
// FIXME<!> Locale support
@@ -441,8 +370,6 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if ([self currentTrait] != typefaceIndex)
[self setCurrentTrait:typefaceIndex ];
[_previewView setPreviewFont:font];
_fontChanges = kNothingChanged;
}
@@ -455,30 +382,23 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
////////////////////////////////////////////////////////////////////
// TODO: ask CPFontManager for traits //
- (void)browserClicked:(id)aBrowser
{
if (aBrowser === _fontBrowser)
{
_fontChanges = kFontNameChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
else if (aBrowser === _traitBrowser)
{
_fontChanges = kTypefaceChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
else if (aBrowser === _sizeBrowser)
{
_fontChanges = kSizeChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
// Apply change immediately to manager (standard behavior)
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
// Update our preview manually because convertFont: calls rely on selected rows
// We construct a temporary font to update the preview view immediately
var updatedFont = [self panelConvertFont:[_previewView font]];
if (updatedFont)
[_previewView setPreviewFont:updatedFont];
}
- (void)dblClicked:(id)sender
@@ -494,7 +414,7 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if (aBrowser === _traitBrowser)
return [_availableTraits count];
return [_availableSizes count];
return [_availableSizes count]
}
- (id)browser:(id)aBrowser child:(int)index ofItem:(id)anItem
@@ -520,116 +440,4 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
@end
// -----------------------------------------------------------------------------
// _CPFontPanelPreviewView
// A helper class to display a font sample with metrics grid
// -----------------------------------------------------------------------------
@implementation _CPFontPanelPreviewView : CPView
{
CPTextField _sampleText;
CPColor _gridColor;
float _gridSize;
}
- (id)initWithFrame:(CGRect)aRect
{
self = [super initWithFrame:aRect];
if (self)
{
[self setBackgroundColor:[CPColor whiteColor]];
_gridColor = [CPColor colorWithHexString:@"e4f4ff"];
_gridSize = 10.0;
_sampleText = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(aRect), CGRectGetHeight(aRect))];
[_sampleText setStringValue:@"AaYy-0123"];
[_sampleText setAlignment:CPCenterTextAlignment];
[_sampleText setVerticalAlignment:CPCenterVerticalTextAlignment];
[_sampleText setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_sampleText setTextColor:[CPColor blackColor]];
[self addSubview:_sampleText];
}
return self;
}
- (void)setPreviewFont:(CPFont)aFont
{
[_sampleText setFont:aFont];
[self setNeedsDisplay:YES];
}
- (CPFont)font
{
return [_sampleText font];
}
- (void)drawRect:(CGRect)dirtyRect
{
// Draw Grid (from MetricsView inspiration)
var context = [[CPGraphicsContext currentContext] graphicsPort],
bounds = [self bounds],
maxX = CGRectGetMaxX(bounds),
maxY = CGRectGetMaxY(bounds);
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColor(context, _gridColor);
CGContextBeginPath(context);
for (var y = 0.5; y <= maxY; y += _gridSize)
{
CGContextMoveToPoint(context, 0.0, y);
CGContextAddLineToPoint(context, maxX, y);
}
for (var x = 0.5; x <= maxX; x += _gridSize)
{
CGContextMoveToPoint(context, x, 0.0);
CGContextAddLineToPoint(context, x, maxY);
}
CGContextStrokePath(context);
// Draw Baseline/Ascender/Descender (from BaselineView inspiration)
var font = [_sampleText font];
if (!font) return;
var ascender = [font ascender],
descender = [font descender],
lineHeight = [font defaultLineHeightForFont];
// Calculate the baseline.
// CPTextField with CPCenterVerticalTextAlignment usually centers the line height.
// Top of line = midY - (lineHeight / 2.0)
// Baseline = Top of line + ascender
var midY = maxY / 2.0,
baselineY = midY - (lineHeight / 2.0) + ascender;
CGContextSetStrokeColor(context, [CPColor redColor]);
CGContextBeginPath(context);
// Baseline
CGContextMoveToPoint(context, 0, baselineY);
CGContextAddLineToPoint(context, maxX, baselineY);
// Ascender Line
CGContextMoveToPoint(context, 0, baselineY - ascender);
CGContextAddLineToPoint(context, maxX, baselineY - ascender);
// Descender Line
CGContextMoveToPoint(context, 0, baselineY - descender);
CGContextAddLineToPoint(context, maxX, baselineY - descender);
CGContextStrokePath(context);
}
- (void)mouseDown:(CPEvent)anEvent
{
var text = prompt("Enter sample text:", [_sampleText stringValue]);
if (text)
[_sampleText setStringValue:text];
}
@end
[CPFontManager setFontPanelFactory:[CPFontPanel class]];
+107 -320
View File
@@ -2,6 +2,9 @@
* CPLayoutManager.j
* AppKit
*
* FIXME remove from DOM when scrolled out of visible area? (as done in CPTableView)
*
*
* Created by Daniel Boehringer on 27/12/2013.
* All modifications copyright Daniel Boehringer 2013.
* Extensive code formatting and review by Andrew Hankinson
@@ -31,9 +34,6 @@
@import "CPFont.j"
@global _MakeRangeFromAbs
@global document
@global CPBaselineOffsetAttributeName
@global CPSuperscriptAttributeName
@class CPTextContainer
@class CPTextView
@@ -72,12 +72,11 @@ _oncontextmenuhandler = function () { return false; };
BOOL _isValidatingLayoutAndGlyphs;
CPRange _removeInvalidLineFragmentsRange;
CPRange _lastEditedRange;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)init
{
@@ -97,14 +96,13 @@ _oncontextmenuhandler = function () { return false; };
_textContainers = [[CPMutableArray alloc] init];
_textStorage = [[CPTextStorage alloc] init];
_typesetter = [CPTypesetter sharedSystemTypesetter];
_lastEditedRange = nil;
[_textStorage addLayoutManager:self];
}
// MARK: -
// MARK: Text containes method
#pragma mark -
#pragma mark Text containes method
- (void)insertTextContainer:(CPTextContainer)aContainer atIndex:(int)index
{
@@ -261,9 +259,6 @@ _oncontextmenuhandler = function () { return false; };
{
if (_lineFragments[i]._isInvalid)
{
while (i > 0 && !_lineFragments[i - 1]._isLast)
i--;
startIndex = _lineFragments[i]._range.location;
removeRange.location = i;
removeRange.length = l - i;
@@ -289,12 +284,11 @@ _oncontextmenuhandler = function () { return false; };
if (removeRange.length)
_removeInvalidLineFragmentsRange = CPMakeRangeCopy(removeRange);
else
_removeInvalidLineFragmentsRange = nil;
// We erased all lines
if (!startIndex)
[self setExtraLineFragmentRect:CGRectMake(0, 0) usedRect:CGRectMake(0, 0) textContainer:nil];
// document.title=startIndex;
[_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil];
@@ -307,17 +301,13 @@ _oncontextmenuhandler = function () { return false; };
- (BOOL)_rescuingInvalidFragmentsWasPossibleForGlyphRange:(CPRange)aRange
{
// 1. EARLY EXIT: If there are no fragments to rescue (e.g. setting new text), do nothing.
if (!_lineFragmentsForRescue || _lineFragmentsForRescue.length === 0)
return NO;
var l = _lineFragments.length,
location = aRange.location,
found = NO,
targetLine = l - 1; // Start from the END of the array
location = aRange.location,
found = NO,
targetLine = 0;
// 2. REVERSE SEARCH: The fragment we want is almost always at the end.
for (; targetLine >= 0; targetLine--)
// try to find the first linefragment of the desired range
for (; targetLine < l; targetLine++)
{
if (CPLocationInRange(location, _lineFragments[targetLine]._range))
{
@@ -340,6 +330,9 @@ _oncontextmenuhandler = function () { return false; };
newLength = [[_textStorage string].length],
removalSkip = 1;
// if (ABS(newLength - oldLength) > 1)
// return NO;
if (![oldLineFragment isVisuallyIdenticalToFragment:newLineFragment])
{
isIdentical = NO;
@@ -368,20 +361,10 @@ _oncontextmenuhandler = function () { return false; };
if (ABS(rangeOffset) !== ABS(newLength - oldLength))
return NO;
var verticalOffset = CGRectGetMaxY(_lineFragments[targetLine]._fragmentRect) - CGRectGetMaxY(_lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect),
var verticalOffset = _lineFragments[targetLine]._fragmentRect.origin.y - _lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect.origin.y,
l = _lineFragmentsForRescue.length,
newTargetLine = startLineForDOMRemoval + removalSkip;
// Ensure that the remaining lines we are attempting to rescue
// start after the end of the edited region.
if (newTargetLine < l && _lastEditedRange)
{
var firstRescuedLineNewLocation = _lineFragmentsForRescue[newTargetLine]._range.location + rangeOffset;
if (firstRescuedLineNewLocation < CPMaxRange(_lastEditedRange))
return NO;
}
for (; newTargetLine < l; newTargetLine++)
{
_lineFragmentsForRescue[newTargetLine]._isInvalid = NO; // protect them from final removal
@@ -453,8 +436,6 @@ _oncontextmenuhandler = function () { return false; };
{
var actualRange = CPMakeRange(CPNotFound,0);
_lastEditedRange = CPMakeRangeCopy(charRange);
[self invalidateLayoutForCharacterRange:invalidatedRange isSoft:NO actualCharacterRange:actualRange];
[self invalidateDisplayForGlyphRange:actualRange];
[self _validateLayoutAndGlyphs];
@@ -514,6 +495,16 @@ _oncontextmenuhandler = function () { return false; };
}
- (void)drawUnderlineForGlyphRange:(CPRange)glyphRange
underlineType:(int)underlineVal
baselineOffset:(float)baselineOffset
lineFragmentRect:(CGRect)lineFragmentRect
lineFragmentGlyphRange:(CPRange)lineGlyphRange
containerOrigin:(CGPoint)containerOrigin
{
// FIXME
}
- (void)drawGlyphsForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint
{
var lineFragments = _objectsInRange(_lineFragments, aRange);
@@ -546,26 +537,20 @@ _oncontextmenuhandler = function () { return false; };
var frames = [fragment glyphFrames],
len = fragment._range.length;
if (frames)
for (var j = 0; j < len; j++)
{
var maxLen = MIN(len, frames.length);
for (var j = 0; j < maxLen; j++)
if (CGRectContainsPoint(frames[j], point))
{
var frame = frames[j];
if (partialFraction)
partialFraction[0] = (point.x - frames[j].origin.x) / frames[j].size.width;
if (frame && CGRectContainsPoint(frame, point))
{
if (partialFraction)
partialFraction[0] = (point.x - frame.origin.x) / frame.size.width;
return fragment._range.location + j;
}
return fragment._range.location + j;
}
}
}
}
// Not found, maybe a point left to the last character was clicked -> search again with broader constraints
if ([[_textStorage string] length])
{
for (var i = 0; i < c; i++)
@@ -574,33 +559,30 @@ _oncontextmenuhandler = function () { return false; };
if (fragment._textContainer === container)
{
if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y &&
point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height)
{
if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y)
continue;
var nlLoc = CPMaxRange(fragment._range),
frames = [fragment glyphFrames];
if (frames && frames.length > 0)
// Within the horizontal territory of the current (not-empty) line?
if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y &&
point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height)
{
var lastFrame = frames[frames.length - 1],
firstFrame = frames[0];
// Skip tabs and move on the last fragment in this line
if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y)
continue;
if (lastFrame && firstFrame)
{
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0]))
nlLoc--;
var nlLoc = CPMaxRange(fragment._range),
lastFrame = [fragment glyphFrames][fragment._range.length - 1],
firstFrame = [fragment glyphFrames][0];
if (point.x > CGRectGetMaxX(lastFrame))
return nlLoc;
else if (point.x <= CGRectGetMinX(firstFrame))
return fragment._range.location;
else
return nlLoc;
}
}
// stay on the line the newline character belongs to
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0]))
nlLoc--;
// Clicked right to the last character
if (point.x > CGRectGetMaxX(lastFrame))
return nlLoc;
// Clicked left to the last character
else if (point.x <= CGRectGetMinX(firstFrame))
return fragment._range.location;
else
return nlLoc;
}
}
}
@@ -689,10 +671,12 @@ _oncontextmenuhandler = function () { return false; };
{
var j = i;
while (j > 0 && !_lineFragments[j - 1]._isLast)
j--;
while (--j > 0 && !_lineFragments[j]._isLast)
{
// body intentionally left empty
}
return _lineFragments[j];
return _lineFragments[j + 1];
}
}
@@ -702,16 +686,13 @@ _oncontextmenuhandler = function () { return false; };
{
var l = _lineFragments.length;
if (location >= CPMaxRange(_lineFragments[l - 1]._range))
return _lineFragments[l - 1];
for (var i = 0; i < l; i++)
{
if (CPLocationInRange(location, _lineFragments[i]._range))
{
var j = i;
while (j < l && !_lineFragments[j]._isLast)
while (!_lineFragments[j]._isLast)
j++;
return _lineFragments[j];
@@ -730,11 +711,7 @@ _oncontextmenuhandler = function () { return false; };
var index = location - lineFragment._range.location;
if (index < 0 || !lineFragment._glyphsOffsets || index >= lineFragment._glyphsOffsets.length)
return 0.0;
var offset = lineFragment._glyphsOffsets[index];
return (offset === undefined) ? 0.0 : offset;
return lineFragment._glyphsOffsets[index];
}
- (double)_descentAtLocation:(unsigned)location
@@ -746,11 +723,7 @@ _oncontextmenuhandler = function () { return false; };
var index = location - lineFragment._range.location;
if (index < 0 || !lineFragment._glyphsFrames || index >= lineFragment._glyphsFrames.length)
return 0.0;
var frame = lineFragment._glyphsFrames[index];
return (frame && frame._descent !== undefined) ? frame._descent : 0.0;
return lineFragment._glyphsFrames[index]._descent;
}
- (void)setLineFragmentRect:(CGRect)fragmentRect forGlyphRange:(CPRange)glyphRange usedRect:(CGRect)usedRect
@@ -874,16 +847,11 @@ _oncontextmenuhandler = function () { return false; };
{
if (_lineFragments.length > 0 && index >= [self numberOfGlyphs] - 1)
{
var lineFragment = _lineFragments[_lineFragments.length - 1],
var lineFragment= _lineFragments[_lineFragments.length - 1],
glyphFrames = [lineFragment glyphFrames];
if (glyphFrames && glyphFrames.length > 0)
{
var frame = glyphFrames[glyphFrames.length - 1];
if (frame)
return CGPointCreateCopy(frame.origin);
}
if (glyphFrames.length > 0)
return CGPointCreateCopy(glyphFrames[glyphFrames.length - 1].origin);
}
var lineFragment = _objectWithLocationInRange(_lineFragments, index);
@@ -894,17 +862,8 @@ _oncontextmenuhandler = function () { return false; };
return CGPointCreateCopy(lineFragment._location);
var glyphFrames = [lineFragment glyphFrames];
var relativeIndex = index - lineFragment._range.location;
if (glyphFrames && relativeIndex >= 0 && relativeIndex < glyphFrames.length)
{
var frame = glyphFrames[relativeIndex];
if (frame)
return CGPointCreateCopy(frame.origin);
}
return CGPointCreateCopy(lineFragment._location);
return CGPointCreateCopy(glyphFrames[index - lineFragment._range.location].origin);
}
return CGPointMakeZero();
@@ -950,6 +909,7 @@ _oncontextmenuhandler = function () { return false; };
inTextContainer:(CPTextContainer)container
rectCount:(CGRectPointer)rectCount
{
var rectArray = [],
lineFragments = _objectsInRange(_lineFragments, selectedCharRange);
@@ -968,23 +928,21 @@ _oncontextmenuhandler = function () { return false; };
rect = nil,
len = fragment._range.length;
if (frames)
for (var j = 0; j < len; j++)
{
for (var j = 0; j < len; j++)
if (CPLocationInRange(fragment._range.location + j, selectedCharRange))
{
if (j < frames.length && CPLocationInRange(fragment._range.location + j, selectedCharRange))
var correctedRect = CGRectCreateCopy(frames[j]);
correctedRect.size.height -= frames[j]._descent;
correctedRect.origin.y -= frames[j]._descent;
if (!rect)
rect = CGRectCreateCopy(correctedRect);
else
rect = CGRectUnion(rect, correctedRect);
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)]))
{
var frame = frames[j];
if (frame)
{
var correctedRect = CGRectCreateCopy(frame);
if (!rect)
rect = CGRectCreateCopy(correctedRect);
else
rect = CGRectUnion(rect, correctedRect);
}
rect.size.width = containerSize.width - rect.origin.x;
}
}
}
@@ -996,7 +954,7 @@ _oncontextmenuhandler = function () { return false; };
var len = rectArray.length;
for (var i = 0; i < len - 1; i++)
for (var i = 0; i < len - 1; i++) // extend the width of all but the last one
{
if (FLOOR(CGRectGetMaxY(rectArray[i])) == FLOOR(CGRectGetMaxY(rectArray[i + 1])))
continue;
@@ -1132,25 +1090,17 @@ var _objectsInRange = function(aList, aRange)
CPMutableArray _runs;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)aColor
{
return [self createDOMElementWithText:aString andFont:aFont andColor:aColor andBackgroundColor:nil andUnderline:nil ];
}
- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)fgColor andBackgroundColor:(CPColor)bgColor andUnderline:(CPUnderlineStyle)aUnderline
{
if (!aString || aString.length === 0)
return nil;
#if PLATFORM(DOM)
var style,
span = document.createElement("span");
span.oncontextmenu = span.onmousedown = span.onselectstart = _oncontextmenuhandler;
// span.contentEditable = true; // this unfortunately does not work to make native pasting work on safari
style = span.style;
style.position = "absolute";
@@ -1160,42 +1110,16 @@ var _objectsInRange = function(aList, aRange)
style.whiteSpace = "pre";
style.backgroundColor = "transparent";
style.font = [aFont cssString];
if (aUnderline)
{
style.textDecoration = "underline";
switch (aUnderline)
{
case CPUnderlineStyleSingle:
style.textDecorationStyle = "solid";
break;
case CPUnderlineStyleDouble:
style.textDecorationStyle = "double";
break;
case CPUnderlineStylePatternDot:
style.textDecorationStyle = "dotted";
break;
case CPUnderlineStylePatternDash:
style.textDecorationStyle = "dashed";
break;
}
}
if (fgColor)
style.color = [fgColor cssString];
if (bgColor)
style.backgroundColor = [bgColor cssString];
if (aColor)
style.color = [aColor cssString];
if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature))
span.innerText = aString;
else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature))
span.textContent = aString;
//<!> FIXME aString.replace(/&/g,'&amp;')
return span;
#else
return nil;
@@ -1215,9 +1139,7 @@ var _objectsInRange = function(aList, aRange)
_range = CPMakeRangeCopy(aRange);
_textContainer = aContainer;
_isInvalid = NO;
_runs = [];
_glyphsFrames = [];
_glyphsOffsets = [];
_runs = [[CPMutableArray alloc] init];
for (location = aRange.location; location < CPMaxRange(aRange); location = CPMaxRange(effectiveRange))
{
@@ -1226,115 +1148,16 @@ var _objectsInRange = function(aList, aRange)
effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange;
var string = [textStorage._string substringWithRange:effectiveRange],
underline = [attributes objectForKey:CPUnderlineStyleAttributeName] || CPUnderlineStyleNone,
paragraphStyle = [attributes objectForKey:CPParagraphStyleAttributeName] || [CPParagraphStyle defaultParagraphStyle];
font = [textStorage font] || [CPFont systemFontOfSize:12.0];
// this is an attachment -> create a run for it
if (string === _CPAttachmentCharacterAsString)
{
if (![attributes objectForKey:_CPAttachmentInvisible])
{
var view = [attributes objectForKey:_CPAttachmentView];
var run = {_range:CPMakeRangeCopy(effectiveRange), color:nil, font:nil, elem:nil, string:nil, view:view, paragraphStyle:paragraphStyle, underline:underline, baselineOffset:0.0};
_runs.push(run);
}
}
else
{
var color = [attributes objectForKey:CPForegroundColorAttributeName],
bgcolor = [attributes objectForKey:CPBackgroundColorAttributeName],
font = [attributes objectForKey:CPFontAttributeName] || [textStorage font] || [CPFont systemFontOfSize:12.0];
if ([attributes containsKey:CPFontAttributeName])
font = [attributes objectForKey:CPFontAttributeName];
var baselineOffset = [attributes objectForKey:CPBaselineOffsetAttributeName],
superscript = [attributes objectForKey:CPSuperscriptAttributeName];
var color = [attributes objectForKey:CPForegroundColorAttributeName],
elem = [self createDOMElementWithText:string andFont:font andColor:color],
run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string};
if (baselineOffset === nil || baselineOffset === undefined || typeof baselineOffset !== "number")
baselineOffset = 0.0;
if (superscript === nil || superscript === undefined || typeof superscript !== "number")
superscript = 0;
if (superscript !== 0)
{
var size = [font size],
scaledSize = size * 0.65,
fontName = [font familyName],
isBold = [font isBold],
isItalic = [font isItalic];
font = [CPFont _fontWithName:fontName size:scaledSize bold:isBold italic:isItalic];
if (baselineOffset === 0.0)
{
if (superscript > 0)
baselineOffset = size * 0.35;
else
baselineOffset = -size * 0.15;
}
}
var currentLoc = effectiveRange.location,
strLen = string.length,
startIdx = 0;
for (var i = 0; i < strLen; i++)
{
if (string.charCodeAt(i) === 9) // Tabulator-Zeichen '\t'
{
if (i > startIdx)
{
var subString = string.substring(startIdx, i),
subRange = CPMakeRange(currentLoc + startIdx, i - startIdx),
run = {
_range: subRange,
color: color,
font: font,
elem: nil,
string: subString,
bgcolor: bgcolor,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: baselineOffset
};
_runs.push(run);
}
var tabRange = CPMakeRange(currentLoc + i, 1),
tabRun = {
_range: tabRange,
color: nil,
font: nil,
elem: nil,
string: nil,
bgcolor: nil,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: 0.0
};
_runs.push(tabRun);
startIdx = i + 1;
}
}
if (startIdx < strLen)
{
var subString = string.substring(startIdx, strLen),
subRange = CPMakeRange(currentLoc + startIdx, strLen - startIdx),
run = {
_range: subRange,
color: color,
font: font,
elem: nil,
string: subString,
bgcolor: bgcolor,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: baselineOffset
};
_runs.push(run);
}
}
_runs.push(run);
if (!CPMaxRange(effectiveRange))
break;
@@ -1357,10 +1180,7 @@ var _objectsInRange = function(aList, aRange)
{
_glyphsFrames[i] = CGRectMake(origin.x, origin.y, someAdvancements[i].width, height);
_glyphsFrames[i]._descent = someAdvancements[i].descent;
// Align the run's baseline with the common line baseline (_location.y)
_glyphsOffsets[i] = _location.y - someAdvancements[i].height;
_glyphsOffsets[i] = height - someAdvancements[i].height;
origin.x += someAdvancements[i].width;
}
}
@@ -1408,9 +1228,6 @@ var _objectsInRange = function(aList, aRange)
for (var i = 0; i < l; i++)
{
if (_runs[i].view && _runs[i].DOMactive)
[_runs[i].view removeFromSuperview];
if (_runs[i].elem && _runs[i].DOMactive)
_textContainer._textView._DOMElement.removeChild(_runs[i].elem);
@@ -1425,15 +1242,14 @@ var _objectsInRange = function(aList, aRange)
c = runs.length,
orig = CGPointMake(_fragmentRect.origin.x, _fragmentRect.origin.y);
if (_runs.length === 0)
return;
for (var i = 0; i < c; i++)
{
var run = runs[i];
if (!run.elem && CPRectIntersectsRect([_textContainer._textView exposedRect], _fragmentRect))
run.elem = [self createDOMElementWithText:run.string andFont:run.font andColor:run.color andBackgroundColor:run.bgcolor andUnderline:run.underline];
{
run.elem=[self createDOMElementWithText:run.string andFont:run.font andColor:run.color];
}
if (run.DOMactive && !run.DOMpatched)
continue;
@@ -1442,42 +1258,26 @@ var _objectsInRange = function(aList, aRange)
continue;
var loc = run._range.location - _runs[0]._range.location;
// Safety bounds check to protect against uninitialized/empty glyph frames or offsets
if (loc < 0 || loc >= _glyphsFrames.length || !_glyphsFrames[loc] || !_glyphsOffsets || loc >= _glyphsOffsets.length)
continue;
orig.x = _glyphsFrames[loc].origin.x + aPoint.x;
orig.y = _glyphsFrames[loc].origin.y + aPoint.y + _glyphsOffsets[loc];
if(run.elem || run.view)
if(run.elem)
{
if (run.elem)
{
run.elem.style.left = (orig.x) + "px";
run.elem.style.top = (orig.y) + "px";
}
if (run.view)
[run.view setFrameOrigin:orig];
run.elem.style.left = (orig.x) + "px";
run.elem.style.top = (orig.y) + "px";
if (!run.DOMactive)
{
if (run.view)
[_textContainer._textView addSubview:run.view];
if (run.elem)
_textContainer._textView._DOMElement.appendChild(run.elem);
}
_textContainer._textView._DOMElement.appendChild(run.elem);
run.DOMactive = YES;
}
run.DOMpatched = NO;
}
}
- (CPColor)backgroundColorForGlyphAtIndex:(unsigned)index
- (void)backgroundColorForGlyphAtIndex:(unsigned)index
{
var run = _objectWithLocationInRange(_runs, index);
@@ -1505,17 +1305,9 @@ var _objectsInRange = function(aList, aRange)
if (!_RectEqualToRectHorizontally(newLineFragment._fragmentRect, _fragmentRect))
return NO;
if (newFragmentRuns[i].color !== oldFragmentRuns[i].color ||
newFragmentRuns[i].bgcolor !== oldFragmentRuns[i].bgcolor ||
newFragmentRuns[i].font !== oldFragmentRuns[i].font ||
newFragmentRuns[i].baselineOffset !== oldFragmentRuns[i].baselineOffset)
if (newFragmentRuns[i].color !== oldFragmentRuns[i].color || newFragmentRuns[i].font !== oldFragmentRuns[i].font)
return NO;
var oldStyle = oldFragmentRuns[i].paragraphStyle || [CPParagraphStyle defaultParagraphStyle],
newStyle = newFragmentRuns[i].paragraphStyle || [CPParagraphStyle defaultParagraphStyle];
if (![oldStyle isEqual:newStyle])
return NO;
}
return YES;
@@ -1531,14 +1323,9 @@ var _objectsInRange = function(aList, aRange)
{
_runs[i]._range.location += rangeOffset;
if (verticalOffset)
if (verticalOffset && _runs[i].elem)
{
if (_runs[i].view)
_runs[i].view._frame.origin.y += verticalOffset;
if (_runs[i].elem)
_runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
_runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
_runs[i].DOMpatched = YES;
}
}
+150 -313
View File
@@ -2,6 +2,10 @@
* CPParagraphStyle.j
* AppKit
*
* FIXME
* This is basically a stub.
* We need to store all the spacing informations as well as writing direction (among others)
*
* Created by Daniel Boehringer on 11/01/2014
* Copyright Daniel Boehringer 2014.
*
@@ -22,365 +26,198 @@
@import <Foundation/CPObject.j>
@import <Foundation/CPArray.j>
@import <Foundation/CPDictionary.j>
@import "CPText.j"
CPLeftTabStopType = 0;
CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName";
// Define missing global tab stop type constants
CPLeftTabStopType = 0;
CPRightTabStopType = 1;
CPCenterTabStopType = 2;
CPDecimalTabStopType = 3;
// Standard Tab Interval (28pts is roughly 4 spaces in standard fonts)
var kDefaultTabInterval = 28.0;
// MARK: - CPTextTab Implementation
@implementation CPTextTab : CPObject
{
CPTextAlignment _alignment @accessors(readonly, property=alignment);
float _location @accessors(readonly, property=location);
CPDictionary _options @accessors(readonly, property=options);
}
- (id)initWithTextAlignment:(CPTextAlignment)anAlignment location:(float)aLocation options:(CPDictionary)options
{
if (self = [super init])
{
_alignment = anAlignment;
_location = aLocation;
_options = [options copy];
}
return self;
}
// Convenience initializer matching AppKit behavior
- (id)initWithType:(CPTabStopType)aType location:(float)aLocation
{
// Map old TabStopType to TextAlignment for modern compatibility
return [self initWithTextAlignment:aType location:aLocation options:nil];
}
// Added to resolve the unrecognized selector exception in the RTF producer
- (CPTabStopType)tabStopType
{
return _alignment;
}
- (BOOL)isEqual:(id)other
{
if (self === other) return YES;
if (![other isKindOfClass:[CPTextTab class]]) return NO;
return _location === [other location] &&
_alignment === [other alignment] &&
((_options == nil && [other options] == nil) || [_options isEqualToDictionary:[other options]]);
}
- (id)copy
{
return [[CPTextTab alloc] initWithTextAlignment:_alignment location:_location options:_options];
}
- (id)initWithCoder:(CPCoder)aCoder
{
if (self = [super init])
{
_alignment = [aCoder decodeIntForKey:@"CPTextTabAlignment"];
_location = [aCoder decodeFloatForKey:@"CPTextTabLocation"];
_options = [aCoder decodeObjectForKey:@"CPTextTabOptions"];
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeInt:_alignment forKey:@"CPTextTabAlignment"];
[aCoder encodeFloat:_location forKey:@"CPTextTabLocation"];
[aCoder encodeObject:_options forKey:@"CPTextTabOptions"];
}
@end
// MARK: - CPParagraphStyle Implementation
var _sharedDefaultParagraphStyle = nil;
var _sharedDefaultParagraphStyle,
_defaultTabStopArray;
@implementation CPParagraphStyle : CPObject
{
float _lineSpacing @accessors(readonly, property=lineSpacing);
float _paragraphSpacing @accessors(readonly, property=paragraphSpacing);
CPTextAlignment _alignment @accessors(readonly, property=alignment);
float _headIndent @accessors(readonly, property=headIndent);
float _tailIndent @accessors(readonly, property=tailIndent);
float _firstLineHeadIndent @accessors(readonly, property=firstLineHeadIndent);
float _minimumLineHeight @accessors(readonly, property=minimumLineHeight);
float _maximumLineHeight @accessors(readonly, property=maximumLineHeight);
CPLineBreakMode _lineBreakMode @accessors(readonly, property=lineBreakMode);
CPWritingDirection _baseWritingDirection @accessors(readonly, property=baseWritingDirection);
float _lineHeightMultiple @accessors(readonly, property=lineHeightMultiple);
float _paragraphSpacingBefore @accessors(readonly, property=paragraphSpacingBefore);
float _defaultTabInterval @accessors(readonly, property=defaultTabInterval);
CPArray _tabStops @accessors(readonly, property=tabStops);
CPArray _tabStops @accessors(property=tabStops);
CPTextAlignment _alignment @accessors(property=alignment);
unsigned _firstLineHeadIndent @accessors(property=firstLineHeadIndent);
unsigned _headIndent @accessors(property=headIndent);
unsigned _tailIndent @accessors(property=tailIndent);
unsigned _paragraphSpacing @accessors(property=paragraphSpacing);
unsigned _minimumLineHeight @accessors(property=minimumLineHeight);
unsigned _maximumLineHeight @accessors(property=maximumLineHeight);
unsigned _lineSpacing @accessors(property=lineSpacing);
}
#pragma mark -
#pragma mark Class methods
+ (CPParagraphStyle)defaultParagraphStyle
{
if (!_sharedDefaultParagraphStyle)
{
_sharedDefaultParagraphStyle = [[CPParagraphStyle alloc] init];
// Ensure defaults are set on the shared instance internal vars
// Since it's immutable, we rely on the init to set these.
}
_sharedDefaultParagraphStyle = [self new];
return _sharedDefaultParagraphStyle;
}
+ (CPWritingDirection)defaultWritingDirectionForLanguage:(CPString)languageName
+ (CPArray)_defaultTabStops
{
// Simplified: Cappuccino usually assumes LTR unless specified otherwise.
return CPWritingDirectionLeftToRight;
if (!_defaultTabStopArray)
{
var i;
_defaultTabStopArray = [];
// <!> FIXME: Define constants for these magic numbers: 13, 28
for (i = 1; i < 16 ; i++)
{
_defaultTabStopArray.push([[CPTextTab alloc] initWithType:CPLeftTabStopType location:i * 28]);
}
}
return _defaultTabStopArray;
}
#pragma mark -
#pragma mark Init methods
- (id)init
{
if (self = [super init])
{
_lineSpacing = 0.0;
_paragraphSpacing = 0.0;
_alignment = CPLeftTextAlignment;
_headIndent = 0.0;
_tailIndent = 0.0;
_firstLineHeadIndent = 0.0;
_minimumLineHeight = 0.0;
_maximumLineHeight = 0.0;
_lineBreakMode = CPLineBreakByWordWrapping;
_baseWritingDirection = CPWritingDirectionNatural;
_lineHeightMultiple = 0.0;
_paragraphSpacingBefore = 0.0;
_defaultTabInterval = kDefaultTabInterval;
// Generate default tab stops
_tabStops = [];
for (var i = 1; i <= 12; i++)
{
[_tabStops addObject:[[CPTextTab alloc] initWithType:CPLeftTextAlignment
location:i * kDefaultTabInterval]];
}
}
[self _initWithDefaults];
return self;
}
- (id)initWithParagraphStyle:(CPParagraphStyle)other
- (CPParagraphStyle)initWithParagraphStyle:(CPParagraphStyle)other
{
if (self = [super init])
{
_lineSpacing = [other lineSpacing];
_paragraphSpacing = [other paragraphSpacing];
_alignment = [other alignment];
_headIndent = [other headIndent];
_tailIndent = [other tailIndent];
_firstLineHeadIndent = [other firstLineHeadIndent];
_minimumLineHeight = [other minimumLineHeight];
_maximumLineHeight = [other maximumLineHeight];
_lineBreakMode = [other lineBreakMode];
_baseWritingDirection = [other baseWritingDirection];
_lineHeightMultiple = [other lineHeightMultiple];
_paragraphSpacingBefore = [other paragraphSpacingBefore];
_defaultTabInterval = [other defaultTabInterval];
_tabStops = [[other tabStops] copy];
}
self = [super init];
_tabStops = [other._tabStops copy];
_alignment = other._alignment;
_firstLineHeadIndent = other._firstLineHeadIndent;
_headIndent = other._headIndent;
_tailIndent = other._tailIndent;
_paragraphSpacing = other._paragraphSpacing;
_minimumLineHeight = other._minimumLineHeight;
_maximumLineHeight = other._maximumLineHeight;
_lineSpacing = other._lineSpacing;
return self;
}
- (void)_initWithDefaults
{
_alignment = CPLeftTextAlignment;
_tabStops = [[[self class] _defaultTabStops] copy];
}
- (void)addTabStop:(CPTextTab)aStop
{
_tabStops.push(aStop);
}
- (id)copy
{
// Since this class is immutable, return self.
// Subclasses (Mutable) will override.
if ([self class] === [CPParagraphStyle class])
return self;
return [[CPParagraphStyle alloc] initWithParagraphStyle:self];
}
var other = [[self class] alloc];
- (id)mutableCopy
{
return [[CPMutableParagraphStyle alloc] initWithParagraphStyle:self];
}
// MARK: - Coding Support
- (id)initWithCoder:(CPCoder)aCoder
{
if (self = [super init])
{
_lineSpacing = [aCoder decodeFloatForKey:@"CPParagraphStyleLineSpacing"];
_paragraphSpacing = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacing"];
_alignment = [aCoder decodeIntForKey:@"CPParagraphStyleAlignment"];
_headIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleHeadIndent"];
_tailIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleTailIndent"];
_firstLineHeadIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleFirstLineHeadIndent"];
_minimumLineHeight = [aCoder decodeFloatForKey:@"CPParagraphStyleMinimumLineHeight"];
_maximumLineHeight = [aCoder decodeFloatForKey:@"CPParagraphStyleMaximumLineHeight"];
_lineBreakMode = [aCoder decodeIntForKey:@"CPParagraphStyleLineBreakMode"];
_baseWritingDirection = [aCoder decodeIntForKey:@"CPParagraphStyleBaseWritingDirection"];
_lineHeightMultiple = [aCoder decodeFloatForKey:@"CPParagraphStyleLineHeightMultiple"];
_paragraphSpacingBefore = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacingBefore"];
_defaultTabInterval = [aCoder decodeFloatForKey:@"CPParagraphStyleDefaultTabInterval"];
_tabStops = [aCoder decodeObjectForKey:@"CPParagraphStyleTabStops"];
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeFloat:_lineSpacing forKey:@"CPParagraphStyleLineSpacing"];
[aCoder encodeFloat:_paragraphSpacing forKey:@"CPParagraphStyleParagraphSpacing"];
[aCoder encodeInt:_alignment forKey:@"CPParagraphStyleAlignment"];
[aCoder encodeFloat:_headIndent forKey:@"CPParagraphStyleHeadIndent"];
[aCoder encodeFloat:_tailIndent forKey:@"CPParagraphStyleTailIndent"];
[aCoder encodeFloat:_firstLineHeadIndent forKey:@"CPParagraphStyleFirstLineHeadIndent"];
[aCoder encodeFloat:_minimumLineHeight forKey:@"CPParagraphStyleMinimumLineHeight"];
[aCoder encodeFloat:_maximumLineHeight forKey:@"CPParagraphStyleMaximumLineHeight"];
[aCoder encodeInt:_lineBreakMode forKey:@"CPParagraphStyleLineBreakMode"];
[aCoder encodeInt:_baseWritingDirection forKey:@"CPParagraphStyleBaseWritingDirection"];
[aCoder encodeFloat:_lineHeightMultiple forKey:@"CPParagraphStyleLineHeightMultiple"];
[aCoder encodeFloat:_paragraphSpacingBefore forKey:@"CPParagraphStyleParagraphSpacingBefore"];
[aCoder encodeFloat:_defaultTabInterval forKey:@"CPParagraphStyleDefaultTabInterval"];
[aCoder encodeObject:_tabStops forKey:@"CPParagraphStyleTabStops"];
}
// MARK: - Equality
- (BOOL)isEqual:(id)other
{
if (self === other) return YES;
if (![other isKindOfClass:[CPParagraphStyle class]]) return NO;
return _lineSpacing === [other lineSpacing] &&
_paragraphSpacing === [other paragraphSpacing] &&
_alignment === [other alignment] &&
_headIndent === [other headIndent] &&
_tailIndent === [other tailIndent] &&
_firstLineHeadIndent === [other firstLineHeadIndent] &&
_lineBreakMode === [other lineBreakMode] &&
[_tabStops isEqualToArray:[other tabStops]];
return [other initWithParagraphStyle:self];
}
@end
// MARK: - CPMutableParagraphStyle Implementation
var CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey",
CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey",
CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey",
CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey",
CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey",
CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey",
CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey",
CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey",
CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey";
@implementation CPMutableParagraphStyle : CPParagraphStyle
{
}
@implementation CPParagraphStyle (CPCoding)
- (id)initWithParagraphStyle:(CPParagraphStyle)other
- (id)initWithCoder:(id)aCoder
{
if (self = [super initWithParagraphStyle:other])
self = [self init];
if (self)
{
// Ensure our tab stops array is mutable in the mutable subclass
_tabStops = [[other tabStops] mutableCopy];
_tabStops = [aCoder decodeObjectForKey:"CPParagraphStyleTabStopsKey"];
_alignment = [aCoder decodeIntForKey:"CPParagraphStyleAlignmentKey"];
_firstLineHeadIndent = [aCoder decodeIntForKey:"CPParagraphStyleFirstLineHeadIndentKey"];
_headIndent = [aCoder decodeIntForKey:"CPParagraphStyleHeadIndentKey"];
_tailIndent = [aCoder decodeIntForKey:"CPParagraphStyleTailIndentKey"];
_paragraphSpacing = [aCoder decodeIntForKey:"CPParagraphStyleParagraphSpacingKey"];
_minimumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMinimumLineHeightKey"];
_maximumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMaximumLineHeightKey"];
_lineSpacing = [aCoder decodeIntForKey:"CPParagraphStyleLineSpacingKey"];
}
return self;
}
- (id)initWithCoder:(CPCoder)aCoder
- (void)encodeWithCoder:(id)aCoder
{
if (self = [super initWithCoder:aCoder])
[aCoder encodeInt:_alignment forKey:"CPParagraphStyleAlignmentKey"];
[aCoder encodeObject:_tabStops forKey:"CPParagraphStyleTabStopsKey"];
[aCoder encodeInt:_firstLineHeadIndent forKey:"CPParagraphStyleFirstLineHeadIndentKey"];
[aCoder encodeInt:_headIndent forKey:"CPParagraphStyleHeadIndentKey"];
[aCoder encodeInt:_tailIndent forKey:"CPParagraphStyleTailIndentKey"];
[aCoder encodeInt:_paragraphSpacing forKey:"CPParagraphStyleParagraphSpacingKey"];
[aCoder encodeInt:_minimumLineHeight forKey:"CPParagraphStyleMinimumLineHeightKey"];
[aCoder encodeInt:_maximumLineHeight forKey:"CPParagraphStyleMaximumLineHeightKey"];
[aCoder encodeInt:_lineSpacing forKey:"CPParagraphStyleLineSpacingKey"];
}
@end
@implementation CPTextTab : CPObject
{
int _type @accessors(property = tabStopType);
double _location @accessors(property = location);
}
- (id)initWithType:(CPTabStopType) aType location:(double) aLocation
{
if ([self = [super init]])
{
_tabStops = [_tabStops mutableCopy];
_type = aType;
_location = aLocation;
}
return self;
}
- (void)setLineSpacing:(float)aLineSpacing
{
_lineSpacing = aLineSpacing;
}
- (void)setParagraphSpacing:(float)aParagraphSpacing
{
_paragraphSpacing = aParagraphSpacing;
}
- (void)setAlignment:(CPTextAlignment)anAlignment
{
_alignment = anAlignment;
}
- (void)setHeadIndent:(float)aHeadIndent
{
_headIndent = aHeadIndent;
}
- (void)setTailIndent:(float)aTailIndent
{
_tailIndent = aTailIndent;
}
- (void)setFirstLineHeadIndent:(float)aFirstLineHeadIndent
{
_firstLineHeadIndent = aFirstLineHeadIndent;
}
- (void)setMinimumLineHeight:(float)aMinimumLineHeight
{
_minimumLineHeight = aMinimumLineHeight;
}
- (void)setMaximumLineHeight:(float)aMaximumLineHeight
{
_maximumLineHeight = aMaximumLineHeight;
}
- (void)setLineBreakMode:(CPLineBreakMode)aLineBreakMode
{
_lineBreakMode = aLineBreakMode;
}
- (void)setBaseWritingDirection:(CPWritingDirection)aBaseWritingDirection
{
_baseWritingDirection = aBaseWritingDirection;
}
- (void)setLineHeightMultiple:(float)aLineHeightMultiple
{
_lineHeightMultiple = aLineHeightMultiple;
}
- (void)setParagraphSpacingBefore:(float)aParagraphSpacingBefore
{
_paragraphSpacingBefore = aParagraphSpacingBefore;
}
- (void)setDefaultTabInterval:(float)aDefaultTabInterval
{
_defaultTabInterval = aDefaultTabInterval;
}
- (void)addTabStop:(CPTextTab)aTabStop
{
[_tabStops addObject:aTabStop];
}
- (void)removeTabStop:(CPTextTab)aTabStop
{
[_tabStops removeObject:aTabStop];
}
- (void)setTabStops:(CPArray)newTabStops
{
if (_tabStops === newTabStops) return;
_tabStops = [newTabStops mutableCopy];
}
- (id)copyWithZone:(CPZone)aZone
{
// Return an immutable copy
return [[CPParagraphStyle alloc] initWithParagraphStyle:self];
}
@end
var CPTextTabTypeKey = @"CPTextTabTypeKey",
CPTextTabLocationKey = @"CPTextTabLocationKey";
@implementation CPTextTab (CPCoding)
- (id)initWithCoder:(id)aCoder
{
self = [self init];
if (self)
{
_type = [aCoder decodeIntForKey:"CPTextTabTypeKey"];
_location = [aCoder decodeDoubleForKey:"CPTextTabLocationKey"];
}
return self;
}
- (void)encodeWithCoder:(id)aCoder
{
[aCoder encodeInt:_type forKey:"CPTextTabTypeKey"];
[aCoder encodeDouble:_location forKey:"CPTextTabLocationKey"];
}
@end
-670
View File
@@ -1,670 +0,0 @@
/*
* CPRulerView.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 "CPView.j"
@import "CPTextField.j"
@import "CPColor.j"
@import "CPFont.j"
@import "CPMenu.j"
@import "CPMenuItem.j"
// Orientations matching AppKit standards
// typedef enum CPRulerOrientation
CPHorizontalRuler = 0,
CPVerticalRuler = 1,
CPRulerOrientationHorizontal = 0,
CPRulerOrientationVertical = 1
@class CPRulerView;
// MARK: - CPRulerMarker (Interactive Handles with Dynamic Alignment Icons)
@implementation CPRulerMarker : CPView
{
CPRulerView _rulerView @accessors(property=rulerView);
float _imageValue @accessors(property=imageValue);
id _representedObject @accessors(property=representedObject);
CPTextField _label;
CPView _customHandleView;
}
- (id)initWithRulerView:(CPRulerView)aRulerView markerLocation:(float)aLocation imageValue:(float)anImageValue representedObject:(id)anObject
{
if (self = [super initWithFrame:CGRectMake(0, 0, 12, 12)])
{
_rulerView = aRulerView;
_imageValue = anImageValue;
_representedObject = anObject;
_label = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 12, 12)];
[_label setFont:[CPFont systemFontOfSize:10.0]];
[_label setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
[_label setAlignment:CPCenterTextAlignment];
[self addSubview:_label];
_customHandleView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[self addSubview:_customHandleView];
[self updateMarkerIcon];
}
return self;
}
- (CPTextField)label
{
return _label;
}
- (void)setRepresentedObject:(id)anObject
{
_representedObject = anObject;
[self updateMarkerIcon];
}
- (void)setFrame:(CGRect)aFrame
{
[super setFrame:aFrame];
[self updateMarkerIcon];
}
// Dynamically sets the Unicode triangle direction based on the alignment or indent type,
// or draws custom split-height grab handles for indentation controls.
- (void)updateMarkerIcon
{
var isIndentMarker = (_representedObject === @"CPFirstLineIndent" || _representedObject === @"CPHeadIndent");
if (isIndentMarker)
{
[_label setHidden:YES];
[_customHandleView setHidden:NO];
var frame = [self bounds];
[_customHandleView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
// Remove old internal rendering to update cleanly
[[_customHandleView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
var isFirstLine = (_representedObject === @"CPFirstLineIndent");
// Dark outline/border representation
[_customHandleView setBackgroundColor:[CPColor colorWithWhite:0.5 alpha:1.0]];
// Inner fill (top handle is lighter, bottom is slightly darker)
var innerView = [[CPView alloc] initWithFrame:CGRectMake(1.0, 1.0, frame.size.width - 2.0, frame.size.height - 2.0)];
if (isFirstLine)
[innerView setBackgroundColor:[CPColor colorWithWhite:0.92 alpha:1.0]];
else
[innerView setBackgroundColor:[CPColor colorWithWhite:0.80 alpha:1.0]];
[_customHandleView addSubview:innerView];
// Horizontal indicator line to visually guide drag interactions
var gripLine = [[CPView alloc] initWithFrame:CGRectMake(Math.floor(frame.size.width / 2.0) - 1.0, 2.0, 1.0, frame.size.height - 4.0)];
[gripLine setBackgroundColor:[CPColor colorWithWhite:0.6 alpha:1.0]];
[innerView addSubview:gripLine];
}
else
{
[_label setHidden:NO];
[_customHandleView setHidden:YES];
[_label setFrame:[self bounds]];
if ([_representedObject isKindOfClass:[CPTextTab class]])
{
var align = [_representedObject alignment];
if (align === CPLeftTextAlignment)
[_label setStringValue:@"▶"]; // Left-aligned points Right
else if (align === CPCenterTextAlignment)
[_label setStringValue:@"▼"]; // Center-aligned points Down
else if (align === CPRightTextAlignment)
[_label setStringValue:@"◀"]; // Right-aligned points Left
}
else if ([_representedObject isKindOfClass:[CPString class]])
{
if (_representedObject === @"CPTailIndent")
[_label setStringValue:@"⥘"]; // Solid downward triangle for tail indent
else
[_label setStringValue:@"⇡"]; // Fallback standard up marker
}
else
{
[_label setStringValue:@"⇡"]; // Fallback standard up marker
}
}
}
// MARK: -
// MARK: Context Menu Support
- (CPMenu)menuForEvent:(CPEvent)anEvent
{
var menu = [[CPMenu alloc] initWithTitle:@"Marker Context Menu"];
// If the marker represents a standard tab stop, allow changing its type
if ([_representedObject isKindOfClass:[CPTextTab class]])
{
var itemLeft = [menu addItemWithTitle:@"Left Tab Stop" action:@selector(changeTypeToLeft:) keyEquivalent:@""],
itemCenter = [menu addItemWithTitle:@"Center Tab Stop" action:@selector(changeTypeToCenter:) keyEquivalent:@""],
itemRight = [menu addItemWithTitle:@"Right Tab Stop" action:@selector(changeTypeToRight:) keyEquivalent:@""];
[itemLeft setTarget:self];
[itemCenter setTarget:self];
[itemRight setTarget:self];
var align = [_representedObject alignment];
if (align === CPLeftTextAlignment) [itemLeft setState:CPOnState];
else if (align === CPCenterTextAlignment) [itemCenter setState:CPOnState];
else if (align === CPRightTextAlignment) [itemRight setState:CPOnState];
[menu addItem:[CPMenuItem separatorItem]];
}
// Determine the context-specific delete title
var deleteTitle = @"Delete Tab Stop";
if ([_representedObject isKindOfClass:[CPString class]])
{
if (_representedObject === @"CPFirstLineIndent")
deleteTitle = @"Delete 1st line indentation marker";
else if (_representedObject === @"CPHeadIndent")
deleteTitle = @"Delete head indentation marker";
else if (_representedObject === @"CPTailIndent")
deleteTitle = @"Delete tail indentation marker";
}
var itemDelete = [menu addItemWithTitle:deleteTitle action:@selector(deleteMarker:) keyEquivalent:@""];
[itemDelete setTarget:self];
return menu;
}
- (void)changeTypeToLeft:(id)sender
{
[self _changeAlignment:CPLeftTextAlignment];
}
- (void)changeTypeToCenter:(id)sender
{
[self _changeAlignment:CPCenterTextAlignment];
}
- (void)changeTypeToRight:(id)sender
{
[self _changeAlignment:CPRightTextAlignment];
}
- (void)_changeAlignment:(CPTextAlignment)alignment
{
if (![_representedObject isKindOfClass:[CPTextTab class]])
return;
var oldTab = _representedObject;
var newTab = [[CPTextTab alloc] initWithType:alignment location:_imageValue];
// Using setRepresentedObject: automatically updates the marker triangle direction
[self setRepresentedObject:newTab];
var client = [_rulerView clientView];
if (client && [client respondsToSelector:@selector(rulerView:didUpdateMarker:oldTab:)])
{
[client rulerView:_rulerView didUpdateMarker:self oldTab:oldTab];
}
}
- (void)deleteMarker:(id)sender
{
var client = [_rulerView clientView];
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
{
[client rulerView:_rulerView didRemoveMarker:self];
}
[_rulerView removeMarker:self];
}
@end
// MARK: - CPRulerView (Pure DOM + Interactive Engine)
@implementation CPRulerView : CPView
{
CPScrollView _scrollView @accessors(property=scrollView);
CPRulerOrientation _orientation @accessors(property=orientation);
CPView _clientView @accessors(property=clientView);
float _ruleThickness @accessors(property=ruleThickness);
float _reservedThicknessForMarkers;
CPArray _markers;
// Dragger variables
CPRulerMarker _draggingMarker;
CGPoint _dragStartPoint;
float _dragStartLocation;
}
- (id)initWithScrollView:(CPScrollView)aScrollView orientation:(CPRulerOrientation)anOrientation
{
if (self = [super initWithFrame:CGRectMakeZero()])
{
_scrollView = aScrollView;
_orientation = anOrientation;
_clientView = [aScrollView documentView];
_ruleThickness = (anOrientation === CPHorizontalRuler) ? 16.0 : 24.0;
_reservedThicknessForMarkers = 0.0;
_markers = [];
[self setBackgroundColor:[CPColor colorWithWhite:0.96 alpha:1.0]];
}
return self;
}
- (void)setFrame:(CGRect)aFrame
{
[super setFrame:aFrame];
[self updateRuler];
}
// Markers registration
- (void)addMarker:(CPRulerMarker)aMarker
{
if ([_markers containsObject:aMarker])
return;
[_markers addObject:aMarker];
[self addSubview:aMarker];
[self _positionMarker:aMarker];
}
- (void)removeMarker:(CPRulerMarker)aMarker
{
[_markers removeObject:aMarker];
[aMarker removeFromSuperview];
}
- (void)setMarkers:(CPArray)newMarkers
{
for (var i = 0; i < [_markers count]; i++)
[[_markers objectAtIndex:i] removeFromSuperview];
_markers = [newMarkers mutableCopy];
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
[self addSubview:marker];
[self _positionMarker:marker];
}
}
- (CPRulerMarker)_markerAtPoint:(CGPoint)aPoint
{
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
if (CGRectContainsPoint([marker frame], aPoint))
return marker;
}
return nil;
}
- (void)_positionMarker:(CPRulerMarker)aMarker
{
if (!_scrollView)
return;
var clipView = [_scrollView contentView],
scrollPoint = [clipView bounds].origin,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]),
markerLocation = [aMarker imageValue];
if (isHorizontal)
{
var x = markerLocation - scrollPoint.x - 6.0, // Center the 12px wide marker
y = rulerHeight - 11.0,
w = 12.0,
h = 12.0;
// Align the First Line Indent (upper half) and Head Indent (lower half) controls
if ([aMarker representedObject] === @"CPFirstLineIndent")
{
y = 0.0;
h = Math.floor(rulerHeight / 2.0);
}
else if ([aMarker representedObject] === @"CPHeadIndent")
{
y = Math.floor(rulerHeight / 2.0);
h = rulerHeight - y - 1.0; // Subtract 1px to stay cleanly above bottom border
}
else
{
// Keep normal horizontal markers within the bounds of the ruler to prevent clipping
if (x < 0.0)
x = 0.0;
else if (x + 12.0 > rulerWidth)
x = rulerWidth - 12.0;
}
[aMarker setFrame:CGRectMake(x, y, w, h)];
}
else
{
var x = rulerWidth - 11.0,
y = markerLocation - scrollPoint.y - 6.0;
// Keep vertical marker within the bounds of the ruler to prevent clipping
if (y < 0.0)
y = 0.0;
else if (y + 12.0 > rulerHeight)
y = rulerHeight - 12.0;
[aMarker setFrame:CGRectMake(x, y, 12.0, 12.0)];
}
}
// MARK: -
// MARK: Interaction Handlers
- (void)mouseDown:(CPEvent)anEvent
{
var locationInWindow = [anEvent locationInWindow],
localPoint = [self convertPoint:locationInWindow fromView:nil],
clipView = [_scrollView contentView],
scrollPoint = [clipView bounds].origin,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
var rulerLocation = isHorizontal ? (localPoint.x + scrollPoint.x) : (localPoint.y + scrollPoint.y);
// 1. Check if clicked an existing marker
var clickedMarker = [self _markerAtPoint:localPoint];
if (clickedMarker)
{
_draggingMarker = clickedMarker;
_dragStartPoint = localPoint;
_dragStartLocation = [_draggingMarker imageValue];
}
// 2. Otherwise, create a new marker dynamically where the user clicked
else
{
var newMarker = [[CPRulerMarker alloc] initWithRulerView:self
markerLocation:rulerLocation
imageValue:rulerLocation
representedObject:nil];
[self addMarker:newMarker];
// Notify the client view (e.g., CPTextView) that a new marker was added
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didAddMarker:)])
[client rulerView:self didAddMarker:newMarker];
_draggingMarker = newMarker;
_dragStartPoint = localPoint;
_dragStartLocation = rulerLocation;
}
}
- (void)mouseDragged:(CPEvent)anEvent
{
if (!_draggingMarker)
return;
var locationInWindow = [anEvent locationInWindow],
localPoint = [self convertPoint:locationInWindow fromView:nil],
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
var delta = isHorizontal ? (localPoint.x - _dragStartPoint.x) : (localPoint.y - _dragStartPoint.y),
newLocation = _dragStartLocation + delta;
if (newLocation < 0) newLocation = 0;
[_draggingMarker setImageValue:newLocation];
// Smoothly redraw ruler and margin bounds on every drag step
[self updateRuler];
// Check if dragged off the ruler (more than 15px off the boundary)
var draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15);
if (draggedOff)
{
// Visual feedback: Dim the handle to 40% and turn the triangle icon gray
[_draggingMarker setAlphaValue:0.4];
[[_draggingMarker label] setTextColor:[CPColor grayColor]];
}
else
{
// Restore standard styling when dragged back into the active strip
[_draggingMarker setAlphaValue:1.0];
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
}
// Notify the CPTextView that the marker coordinates shifted
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didMoveMarker:)])
[client rulerView:self didMoveMarker:_draggingMarker];
}
- (void)mouseUp:(CPEvent)anEvent
{
if (!_draggingMarker)
return;
var localPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil],
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
// If dragged more than 15 pixels off the ruler, delete the marker
draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15);
if (draggedOff)
{
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
[client rulerView:self didRemoveMarker:_draggingMarker];
[self removeMarker:_draggingMarker];
}
else
{
// Ensure marker style is fully restored if not deleted
[_draggingMarker setAlphaValue:1.0];
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
}
_draggingMarker = nil;
[self updateRuler];
}
// MARK: -
// MARK: DOM Layout Builder
- (void)updateRuler
{
// Wipe subviews to redraw the dynamic visible tick lines/numbers
[self setSubviews:@[]];
if (!_scrollView)
return;
var clipView = [_scrollView contentView],
scrollBounds = [clipView bounds],
scrollPoint = scrollBounds.origin,
visibleSize = scrollBounds.size,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
if (isHorizontal)
{
var start = Math.floor(scrollPoint.x / 10) * 10,
end = scrollPoint.x + visibleSize.width,
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]);
// Draw solid horizontal bottom border (pure, razor-sharp CSS DOM view)
var bottomBorder = [[CPView alloc] initWithFrame:CGRectMake(0, rulerHeight - 1, rulerWidth, 1)];
[bottomBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
[self addSubview:bottomBorder];
// Find indent markers to determine background highlight boundaries
var firstLineMarker = nil,
headMarker = nil;
for (var i = 0; i < [_markers count]; i++)
{
var m = [_markers objectAtIndex:i];
if ([m representedObject] === @"CPFirstLineIndent")
firstLineMarker = m;
else if ([m representedObject] === @"CPHeadIndent")
headMarker = m;
}
var halfHeight = Math.floor(rulerHeight / 2.0);
// Draw First Line Indent background - top half (lighter gray)
if (firstLineMarker)
{
var firstLineX = [firstLineMarker imageValue] - scrollPoint.x;
if (firstLineX > 0)
{
var firstLineBg = [[CPView alloc] initWithFrame:CGRectMake(0, 0, firstLineX, halfHeight)];
[firstLineBg setBackgroundColor:[CPColor colorWithWhite:0.93 alpha:1.0]];
[self addSubview:firstLineBg];
}
}
// Draw Head Indent background - bottom half (slightly darker gray)
if (headMarker)
{
var headX = [headMarker imageValue] - scrollPoint.x;
if (headX > 0)
{
var headBg = [[CPView alloc] initWithFrame:CGRectMake(0, halfHeight, headX, rulerHeight - halfHeight - 1.0)];
[headBg setBackgroundColor:[CPColor colorWithWhite:0.86 alpha:1.0]];
[self addSubview:headBg];
}
}
// Render ruler tick lines and labels on top of shaded areas
for (var val = start; val <= end; val += 10)
{
if (val < 0) continue;
var screenX = val - scrollPoint.x,
isMajor = (val % 50 === 0),
tickHeight = isMajor ? 8.0 : 4.0,
tickY = rulerHeight - tickHeight - 1.0;
// Tick mark CSS line view
var tick = [[CPView alloc] initWithFrame:CGRectMake(screenX, tickY, 1.0, tickHeight)];
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
[self addSubview:tick];
// Unit label
if (isMajor)
{
var labelX = screenX - 20.0,
alignment = CPCenterTextAlignment;
// Adjust label frame and alignment if it lands near left/right bounds
if (labelX < 0.0)
{
labelX = Math.max(0.0, screenX);
alignment = CPLeftTextAlignment;
}
else if (labelX + 40.0 > rulerWidth)
{
labelX = rulerWidth - 40.0;
alignment = CPRightTextAlignment;
}
var label = [[CPTextField alloc] initWithFrame:CGRectMake(labelX, 1.0, 40.0, 12.0)];
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
[label setFont:[CPFont systemFontOfSize:8.0]];
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
[label setAlignment:alignment];
[self addSubview:label];
}
}
}
else
{
// Vertical Ruler
var start = Math.floor(scrollPoint.y / 10) * 10,
end = scrollPoint.y + visibleSize.height,
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]);
// Draw solid vertical right border (pure DOM)
var rightBorder = [[CPView alloc] initWithFrame:CGRectMake(rulerWidth - 1, 0, 1, rulerHeight)];
[rightBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
[self addSubview:rightBorder];
for (var val = start; val <= end; val += 10)
{
if (val < 0) continue;
var screenY = val - scrollPoint.y,
isMajor = (val % 50 === 0),
tickWidth = isMajor ? 8.0 : 4.0,
tickX = rulerWidth - tickWidth - 1.0;
// Tick mark CSS line view
var tick = [[CPView alloc] initWithFrame:CGRectMake(tickX, screenY, tickWidth, 1.0)];
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
[self addSubview:tick];
// Unit label
if (isMajor)
{
var labelY = screenY - 6.0;
// Adjust label frame if it lands near top/bottom bounds
if (labelY < 0.0)
labelY = 0.0;
else if (labelY + 12.0 > rulerHeight)
labelY = rulerHeight - 12.0;
var label = [[CPTextField alloc] initWithFrame:CGRectMake(1.0, labelY, rulerWidth - 12.0, 12.0)];
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
[label setFont:[CPFont systemFontOfSize:8.0]];
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
[label setAlignment:CPRightTextAlignment];
[self addSubview:label];
}
}
}
// Reposition and display active markers
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
if ([marker superview] !== self)
[self addSubview:marker];
[self _positionMarker:marker];
}
}
@end
+15 -69
View File
@@ -84,13 +84,11 @@ CPLineMovesUp = 4;
CPLayoutManager _layoutManager @accessors(property=layoutManager);
CPTextView _textView @accessors(property=textView);
BOOL _inResizing;
BOOL _widthTracksTextView;
BOOL _heightTracksTextView;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)initWithContainerSize:(CGSize)aSize
{
@@ -118,8 +116,8 @@ CPLineMovesUp = 4;
[_layoutManager addTextContainer:self];
}
// MARK: -
// MARK: Setter methods
#pragma mark -
#pragma mark Setter methods
- (void)setContainerSize:(CGSize)someSize
{
@@ -141,65 +139,28 @@ CPLineMovesUp = 4;
}
// Controls whether the receiver adjusts the width of its bounding rectangle when its text view is resized.
- (BOOL)widthTracksTextView
{
return _widthTracksTextView;
}
- (void)setWidthTracksTextView:(BOOL)flag
{
if (_widthTracksTextView === flag)
return;
[_textView setPostsFrameChangedNotifications:flag];
_widthTracksTextView = flag;
[self _updateFrameObserver];
}
// Controls whether the receiver adjusts the height of its bounding rectangle when its text view is resized.
- (BOOL)heightTracksTextView
{
return _heightTracksTextView;
}
- (void)setHeightTracksTextView:(BOOL)flag
{
if (_heightTracksTextView === flag)
return;
_heightTracksTextView = flag;
[self _updateFrameObserver];
}
- (void)_updateFrameObserver
{
if (_textView)
if (flag)
{
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewFrameChanged:)
name:CPViewFrameDidChangeNotification
object:_textView];
}
else
{
[[CPNotificationCenter defaultCenter] removeObserver:self
name:CPViewFrameDidChangeNotification
object:_textView];
var flag = _widthTracksTextView || _heightTracksTextView;
[_textView setPostsFrameChangedNotifications:flag];
if (flag)
{
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewFrameChanged:)
name:CPViewFrameDidChangeNotification
object:_textView];
}
}
}
- (void)textViewFrameChanged:(CPNotification)aNotification
{
var newSize = CGSizeMake(_size.width, _size.height);
if (_widthTracksTextView)
newSize.width = [_textView frame].size.width;
if (_heightTracksTextView)
newSize.height = [_textView frame].size.height;
var newSize = CGSizeMake([_textView frame].size.width, _size.height);
[self setContainerSize:newSize];
}
@@ -207,20 +168,12 @@ CPLineMovesUp = 4;
- (void)setTextView:(CPTextView)aTextView
{
if (_textView)
{
[[CPNotificationCenter defaultCenter] removeObserver:self
name:CPViewFrameDidChangeNotification
object:_textView];
[_textView setTextContainer:nil];
}
_textView = aTextView;
if (_textView)
{
[self _updateFrameObserver];
[_textView setTextContainer:self];
}
[_layoutManager textContainerChangedTextView:self];
}
@@ -269,9 +222,7 @@ CPLineMovesUp = 4;
var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey",
CPTextContainerWidthTracksTextViewKey = @"CPTextContainerWidthTracksTextViewKey",
CPTextContainerHeightTracksTextViewKey = @"CPTextContainerHeightTracksTextViewKey";
CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey";
@implementation CPTextContainer (CPCoding)
@@ -287,9 +238,6 @@ var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
_layoutManager = [aCoder decodeObjectForKey:CPTextContainerLayoutManagerKey];
[_layoutManager addTextContainer:self];
_widthTracksTextView = [aCoder decodeBoolForKey:CPTextContainerWidthTracksTextViewKey];
_heightTracksTextView = [aCoder decodeBoolForKey:CPTextContainerHeightTracksTextViewKey];
}
return self;
@@ -299,8 +247,6 @@ var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
{
[aCoder encodeSize:_size forKey:CPTextContainerSizeKey];
[aCoder encodeObject:_layoutManager forKey:CPTextContainerLayoutManagerKey];
[aCoder encodeBool:_widthTracksTextView forKey:CPTextContainerWidthTracksTextViewKey];
[aCoder encodeBool:_heightTracksTextView forKey:CPTextContainerHeightTracksTextViewKey];
}
@end
+32 -30
View File
@@ -42,11 +42,6 @@ CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNot
@end
CPAttachmentCharacter = 65532; // "\ufffc";
_CPAttachmentCharacterAsString = String.fromCharCode(CPAttachmentCharacter);
_CPAttachmentView = "_CPAttachmentView";
_CPAttachmentInvisible = "_CPAttachmentInvisible";
var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
CPTextStorageDelegate_textStorageDidProcessEditing_ = 1 << 2;
@@ -70,8 +65,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
{
@@ -99,8 +94,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
// MARK: -
// MARK: Delegate methods
#pragma mark -
#pragma mark Delegate methods
- (void)setDelegate:(id <CPTextStorageDelegate>)aDelegate
{
@@ -121,13 +116,13 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
// MARK: -
// MARK: Layout manager methods
#pragma mark -
#pragma mark Layout manager methods
- (void)addLayoutManager:(CPLayoutManager)aManager
{
if ([_layoutManagers containsObject:aManager])
return;
return
[aManager setTextStorage:self];
[_layoutManagers addObject:aManager];
@@ -136,7 +131,7 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
- (void)removeLayoutManager:(CPLayoutManager)aManager
{
if (![_layoutManagers containsObject:aManager])
return;
return
[aManager setTextStorage:nil];
[_layoutManagers removeObject:aManager];
@@ -148,8 +143,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
// MARK: -
// MARK: Editing methods
#pragma mark -
#pragma mark Editing methods
- (void)processEditing
{
@@ -262,21 +257,6 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
return [super attributedSubstringFromRange:aRange];
}
/*!
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:(CPView)someView
{
var result = [[self alloc] initWithString:_CPAttachmentCharacterAsString];
[result setAttributes:@{_CPAttachmentView:someView} range:CPMakeRange(0, 1)];
return result;
}
@end
@@ -299,3 +279,25 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
@end
@implementation CPTextStorage (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
}
@end
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More