mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-09 12:17:13 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f1cb388827 | ||
|
|
2e1a0da0e4 | ||
|
|
47d0537ae4 | ||
|
|
81c2f219ee | ||
|
|
5e53a2326c | ||
|
|
0394731fdf | ||
|
|
a426aec2c9 | ||
|
|
dbad10f060 | ||
|
|
3985ef24e3 | ||
|
|
ff4a79d6cd | ||
|
|
9b017de39c |
@@ -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
|
||||
@@ -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
|
||||
});
|
||||
-18
@@ -5,26 +5,8 @@ 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/objective-j/lib
|
||||
/dist/cappuccino/package.json
|
||||
/dist/cappuccino/lib
|
||||
/dist/cappuccino/bin
|
||||
Tests/Manual/.Frameworks
|
||||
/Tests/Manual/index.html
|
||||
|
||||
-28
@@ -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
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.8
|
||||
install: ./bootstrap.sh --noprompt --directory ./narwhal
|
||||
script: jake test
|
||||
env: PATH="$TRAVIS_BUILD_DIR/narwhal/bin:$PATH" CAPP_BUILD="$TRAVIS_BUILD_DIR/Build" NARWHAL_ENGINE=rhino
|
||||
+2
-11
@@ -20,15 +20,12 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "_CPObject+Theme.j"
|
||||
@import "_CPToolTip.j"
|
||||
@import "CALayer.j"
|
||||
@import "CGGeometry.j"
|
||||
@import "CPAccordionView.j"
|
||||
@import "CPAlert.j"
|
||||
@import "CPAnimation.j"
|
||||
@import "CPAnimationContext.j"
|
||||
@import "CPAppearance.j"
|
||||
@import "CPApplication.j"
|
||||
@import "CPArrayController.j"
|
||||
@import "CPBezierPath.j"
|
||||
@@ -58,10 +55,11 @@
|
||||
@import "CPController.j"
|
||||
@import "CPCookie.j"
|
||||
@import "CPCursor.j"
|
||||
@import "CPDatePicker.j"
|
||||
@import "CPDocument.j"
|
||||
@import "CPDocumentController.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPFlashMovie.j"
|
||||
@import "CPFlashView.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPFontManager.j"
|
||||
@import "CPGradient.j"
|
||||
@@ -93,28 +91,21 @@
|
||||
@import "CPSlider.j"
|
||||
@import "CPSound.j"
|
||||
@import "CPSplitView.j"
|
||||
@import "CPStackView.j"
|
||||
@import "CPStepper.j"
|
||||
@import "CPTableColumn.j"
|
||||
@import "CPTableView.j"
|
||||
@import "CPTabView.j"
|
||||
@import "CPText.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPTextView.j"
|
||||
@import "CPTokenField.j"
|
||||
@import "CPToolbar.j"
|
||||
@import "CPToolbarItem.j"
|
||||
@import "CPTrackingArea.j"
|
||||
@import "CPTreeNode.j"
|
||||
@import "CPUserDefaultsController.j"
|
||||
@import "CPView.j"
|
||||
@import "CPViewAnimator.j"
|
||||
@import "CPViewAnimation.j"
|
||||
@import "CPViewController.j"
|
||||
@import "CPVisualEffectView.j"
|
||||
@import "CPWebView.j"
|
||||
@import "CPWindow.j"
|
||||
@import "CPWindowController.j"
|
||||
@import "CPWorkspace.j"
|
||||
@import "CPFontPanel.j"
|
||||
@import "CPTreeController.j"
|
||||
|
||||
+15
-15
@@ -106,7 +106,7 @@ var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection
|
||||
_itemViews = [];
|
||||
_expandedItemIndexes = [CPIndexSet indexSet];
|
||||
|
||||
[self setItemHeaderPrototype:[[CPButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 24.0)]];
|
||||
[self setItemHeaderPrototype:[[CPButton alloc] initWithFrame:_CGRectMake(0.0, 0.0, 100.0, 24.0)]];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -227,7 +227,7 @@ var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection
|
||||
{
|
||||
var indexSet = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _items.length)];
|
||||
|
||||
[indexSet removeIndexes:_expandedItemIndexes];
|
||||
[indexSet removeIndexes:_expandedIndexes];
|
||||
|
||||
return indexSet;
|
||||
}
|
||||
@@ -258,18 +258,18 @@ var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
var width = CGRectGetWidth([self frame]);
|
||||
var width = _CGRectGetWidth([self frame]);
|
||||
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
if (width !== CGRectGetWidth([self frame]))
|
||||
if (width !== _CGRectGetWidth([self frame]))
|
||||
[self _invalidateItemsStartingAtIndex:0];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_items.length <= 0)
|
||||
return [self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), 0.0)];
|
||||
return [self setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), 0.0)];
|
||||
|
||||
if (_dirtyItemIndex === CPNotFound)
|
||||
return;
|
||||
@@ -278,7 +278,7 @@ var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection
|
||||
|
||||
var index = _dirtyItemIndex,
|
||||
count = _itemViews.length,
|
||||
width = CGRectGetWidth([self bounds]),
|
||||
width = _CGRectGetWidth([self bounds]),
|
||||
y = index > 0 ? CGRectGetMaxY([_itemViews[index - 1] frame]) : 0.0;
|
||||
|
||||
// Do this now (instead of after looping), so that if we are made dirty again in the middle we don't blow this value away.
|
||||
@@ -293,7 +293,7 @@ var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection
|
||||
y = CGRectGetMaxY([itemView frame]);
|
||||
}
|
||||
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), y)];
|
||||
[self setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), y)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -310,7 +310,7 @@ var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection
|
||||
|
||||
- (id)initWithAccordionView:(CPAccordionView)anAccordionView
|
||||
{
|
||||
self = [super initWithFrame:CGRectMakeZero()];
|
||||
self = [super initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
if (self)
|
||||
{
|
||||
@@ -376,21 +376,21 @@ var secondItem = [[CPAccordionViewItem alloc] initWithIdentifier:@"secondSection
|
||||
|
||||
- (void)setFrameY:(float)aY width:(float)aWidth
|
||||
{
|
||||
var headerHeight = CGRectGetHeight([_headerView frame]);
|
||||
var headerHeight = _CGRectGetHeight([_headerView frame]);
|
||||
|
||||
// Size to fit or something?
|
||||
[_headerView setFrameSize:CGSizeMake(aWidth, headerHeight)];
|
||||
[_contentView setFrameOrigin:CGPointMake(0.0, headerHeight)];
|
||||
[_headerView setFrameSize:_CGSizeMake(aWidth, headerHeight)];
|
||||
[_contentView setFrameOrigin:_CGPointMake(0.0, headerHeight)];
|
||||
|
||||
if ([self isCollapsed])
|
||||
[self setFrame:CGRectMake(0.0, aY, aWidth, headerHeight)];
|
||||
[self setFrame:_CGRectMake(0.0, aY, aWidth, headerHeight)];
|
||||
|
||||
else
|
||||
{
|
||||
var contentHeight = CGRectGetHeight([_contentView frame]);
|
||||
var contentHeight = _CGRectGetHeight([_contentView frame]);
|
||||
|
||||
[_contentView setFrameSize:CGSizeMake(aWidth, contentHeight)];
|
||||
[self setFrame:CGRectMake(0.0, aY, aWidth, contentHeight + headerHeight)];
|
||||
[_contentView setFrameSize:_CGSizeMake(aWidth, contentHeight)];
|
||||
[self setFrame:_CGRectMake(0.0, aY, aWidth, contentHeight + headerHeight)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+138
-293
@@ -28,23 +28,15 @@
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPApplication.j"
|
||||
@import "CPButton.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPText.j"
|
||||
@import "CPTextField.j"
|
||||
|
||||
@class CPCheckBox
|
||||
|
||||
@global CPApp
|
||||
|
||||
var CPAlertDelegate_alertShowHelp_ = 1 << 0,
|
||||
CPAlertDelegate_alertDidEnd_returnCode_ = 1 << 1;
|
||||
|
||||
@typedef CPAlertStyle
|
||||
/*
|
||||
@global
|
||||
@group CPAlertStyle
|
||||
@@ -61,64 +53,6 @@ CPInformationalAlertStyle = 1;
|
||||
*/
|
||||
CPCriticalAlertStyle = 2;
|
||||
|
||||
var bottomHeight = 71;
|
||||
|
||||
@protocol CPAlertDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (BOOL)alertShowHelp:(CPAlert)alert;
|
||||
- (void)alertDidEnd:(CPAlert)theAlert returnCode:(int)returnCode;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation _CPAlertThemeView : CPView
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"alert";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"size": CGSizeMake(400.0, 110.0),
|
||||
@"content-inset": CGInsetMake(15, 15, 15, 50),
|
||||
@"informative-offset": 6,
|
||||
@"button-offset": 10,
|
||||
@"message-text-alignment": CPJustifiedTextAlignment,
|
||||
@"message-text-color": [CPColor blackColor],
|
||||
@"message-text-font": [CPFont boldSystemFontOfSize:13.0],
|
||||
@"message-text-shadow-color": [CPNull null],
|
||||
@"message-text-shadow-offset": CGSizeMakeZero(),
|
||||
@"informative-text-alignment": CPJustifiedTextAlignment,
|
||||
@"informative-text-color": [CPColor blackColor],
|
||||
@"informative-text-font": [CPFont systemFontOfSize:12.0],
|
||||
@"informative-text-shadow-color": [CPNull null],
|
||||
@"informative-text-shadow-offset": CGSizeMakeZero(),
|
||||
@"image-offset": CGPointMake(15, 12),
|
||||
@"information-image": [CPNull null],
|
||||
@"warning-image": [CPNull null],
|
||||
@"error-image": [CPNull null],
|
||||
@"help-image": [CPNull null],
|
||||
@"help-image-left-offset": 15,
|
||||
@"help-image-pressed": [CPNull null],
|
||||
@"suppression-button-y-offset": 0.0,
|
||||
@"suppression-button-x-offset": 0.0,
|
||||
@"default-elements-margin": 3.0,
|
||||
@"suppression-button-text-color": [CPColor blackColor],
|
||||
@"suppression-button-text-font": [CPFont systemFontOfSize:12.0],
|
||||
@"suppression-button-text-shadow-color": [CPNull null],
|
||||
@"suppression-button-text-shadow-offset": 0.0,
|
||||
@"modal-window-button-margin-y": 0.0,
|
||||
@"modal-window-button-margin-x": 0.0,
|
||||
@"standard-window-button-margin-y": 0.0,
|
||||
@"standard-window-button-margin-x": 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
|
||||
@@ -141,38 +75,35 @@ var bottomHeight = 71;
|
||||
representing the first button added to the alert which appears on the
|
||||
right, 1 representing the next button to the left and so on)
|
||||
*/
|
||||
@implementation CPAlert : CPObject
|
||||
@implementation CPAlert : CPView
|
||||
{
|
||||
BOOL _showHelp @accessors(property=showsHelp);
|
||||
BOOL _showSuppressionButton @accessors(property=showsSuppressionButton);
|
||||
BOOL _showHelp @accessors(property=showsHelp);
|
||||
BOOL _showSuppressionButton @accessors(property=showsSuppressionButton);
|
||||
|
||||
CPAlertStyle _alertStyle @accessors(property=alertStyle);
|
||||
CPString _title @accessors(property=title);
|
||||
CPView _accessoryView @accessors(property=accessoryView);
|
||||
CPImage _icon @accessors(property=icon);
|
||||
CPAlertStyle _alertStyle @accessors(property=alertStyle);
|
||||
CPString _title @accessors(property=title);
|
||||
CPView _accessoryView @accessors(property=accessoryView);
|
||||
CPImage _icon @accessors(property=icon);
|
||||
|
||||
CPArray _buttons @accessors(property=buttons, readonly);
|
||||
CPCheckBox _suppressionButton @accessors(property=suppressionButton, readonly);
|
||||
CPArray _buttons @accessors(property=buttons,readonly);
|
||||
CPCheckBox _suppressionButton @accessors(property=suppressionButton,readonly);
|
||||
|
||||
id <CPAlertDelegate> _delegate @accessors(property=delegate);
|
||||
id _modalDelegate;
|
||||
SEL _didEndSelector @accessors(property=didEndSelector);
|
||||
Function _didEndBlock;
|
||||
unsigned _implementedDelegateMethods;
|
||||
id _delegate @accessors(property=delegate);
|
||||
id _modalDelegate;
|
||||
SEL _didEndSelector;
|
||||
|
||||
_CPAlertThemeView _themeView @accessors(property=themeView, readonly);
|
||||
CPWindow _window @accessors(property=window, readonly);
|
||||
int _defaultWindowStyle;
|
||||
CPWindow _window @accessors(property=window,readonly);
|
||||
int _defaultWindowStyle;
|
||||
|
||||
CPImageView _alertImageView;
|
||||
CPTextField _informativeLabel;
|
||||
CPTextField _messageLabel;
|
||||
CPButton _alertHelpButton;
|
||||
CPImageView _alertImageView;
|
||||
CPTextField _informativeLabel;
|
||||
CPTextField _messageLabel;
|
||||
CPButton _alertHelpButton;
|
||||
|
||||
BOOL _needsLayout;
|
||||
BOOL _needsLayout;
|
||||
}
|
||||
|
||||
// MARK: Creating Alerts
|
||||
#pragma mark Creating Alerts
|
||||
|
||||
/*!
|
||||
Returns a CPAlert object with the provided info
|
||||
@@ -232,8 +163,7 @@ var bottomHeight = 71;
|
||||
_alertStyle = CPWarningAlertStyle;
|
||||
_showHelp = NO;
|
||||
_needsLayout = YES;
|
||||
_defaultWindowStyle = _CPModalWindowMask;
|
||||
_themeView = [_CPAlertThemeView new];
|
||||
_defaultWindowStyle = CPTitledWindowMask;
|
||||
|
||||
_messageLabel = [CPTextField labelWithTitle:@"Alert"];
|
||||
_alertImageView = [[CPImageView alloc] init];
|
||||
@@ -248,36 +178,7 @@ var bottomHeight = 71;
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Delegate
|
||||
|
||||
/*!
|
||||
Set the delegate of the receiver
|
||||
@param aDelegate the delegate object for the alert.
|
||||
*/
|
||||
- (void)setDelegate:(id <CPAlertDelegate>)aDelegate
|
||||
{
|
||||
if (_delegate === aDelegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(alertShowHelp:)])
|
||||
_implementedDelegateMethods |= CPAlertDelegate_alertShowHelp_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(alertDidEnd:returnCode:)])
|
||||
_implementedDelegateMethods |= CPAlertDelegate_alertDidEnd_returnCode_;
|
||||
}
|
||||
|
||||
|
||||
// MARK: Accessors
|
||||
|
||||
- (CPTheme)theme
|
||||
{
|
||||
return [_themeView theme];
|
||||
}
|
||||
#pragma mark Accessors
|
||||
|
||||
/*!
|
||||
set the theme to use
|
||||
@@ -296,29 +197,19 @@ var bottomHeight = 71;
|
||||
|
||||
_window = nil; // will be regenerated at next layout
|
||||
_needsLayout = YES;
|
||||
[_themeView setTheme:aTheme];
|
||||
[super setTheme:aTheme];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
|
||||
{
|
||||
[_themeView setValue:aValue forThemeAttribute:aName];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName inState:(ThemeState)aState
|
||||
{
|
||||
[_themeView setValue:aValue forThemeAttribute:aName inState:aState];
|
||||
}
|
||||
|
||||
|
||||
/*! @deprecated */
|
||||
- (void)setWindowStyle:(int)style
|
||||
/*! @deprecated
|
||||
*/
|
||||
- (void)setWindowStyle:(int)aStyle
|
||||
{
|
||||
CPLog.warn("DEPRECATED: setWindowStyle: is deprecated. use setTheme: instead");
|
||||
|
||||
[self setTheme:(style === CPHUDBackgroundWindowMask) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
|
||||
[self setTheme:(aStyle === CPHUDBackgroundWindowMask) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
|
||||
}
|
||||
|
||||
/*! @deprecated */
|
||||
/*! @deprecated
|
||||
*/
|
||||
- (int)windowStyle
|
||||
{
|
||||
CPLog.warn("DEPRECATED: windowStyle: is deprecated. use theme instead");
|
||||
@@ -327,19 +218,18 @@ var bottomHeight = 71;
|
||||
|
||||
|
||||
/*!
|
||||
Set the text of the alert's message.
|
||||
set the text of the alert's message
|
||||
|
||||
@param aText CPString containing the text
|
||||
*/
|
||||
- (void)setMessageText:(CPString)text
|
||||
- (void)setMessageText:(CPString)aText
|
||||
{
|
||||
[_messageLabel setStringValue:text];
|
||||
[_messageLabel setStringValue:aText];
|
||||
_needsLayout = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the content of the message text.
|
||||
|
||||
return the content of the message text
|
||||
@return CPString containing the message text
|
||||
*/
|
||||
- (CPString)messageText
|
||||
@@ -348,13 +238,13 @@ var bottomHeight = 71;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the text of the alert's informative text.
|
||||
set the text of the alert's informative text
|
||||
|
||||
@param aText CPString containing the informative text
|
||||
*/
|
||||
- (void)setInformativeText:(CPString)text
|
||||
- (void)setInformativeText:(CPString)aText
|
||||
{
|
||||
[_informativeLabel setStringValue:text];
|
||||
[_informativeLabel setStringValue:aText];
|
||||
_needsLayout = YES;
|
||||
}
|
||||
|
||||
@@ -371,7 +261,6 @@ var bottomHeight = 71;
|
||||
/*!
|
||||
Sets the title of the alert window.
|
||||
This API is not present in Cocoa.
|
||||
|
||||
@param aTitle CPString containing the window title
|
||||
*/
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
@@ -381,7 +270,7 @@ var bottomHeight = 71;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set the accessory view.
|
||||
set the accessory view
|
||||
|
||||
@param aView the accessory view
|
||||
*/
|
||||
@@ -392,7 +281,7 @@ var bottomHeight = 71;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set if the alert shows the suppression button.
|
||||
set if alert shows the suppression button
|
||||
|
||||
@param shouldShowSuppressionButton YES or NO
|
||||
*/
|
||||
@@ -402,7 +291,7 @@ var bottomHeight = 71;
|
||||
_needsLayout = YES;
|
||||
}
|
||||
|
||||
// MARK: Accessing Buttons
|
||||
#pragma mark Accessing Buttons
|
||||
|
||||
/*!
|
||||
Adds a button with a given title to the receiver.
|
||||
@@ -438,23 +327,23 @@ var bottomHeight = 71;
|
||||
[_buttons insertObject:button atIndex:0];
|
||||
}
|
||||
|
||||
// MARK: Layout
|
||||
#pragma mark Layout
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)_layoutMessageView
|
||||
{
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
var inset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
sizeWithFontCorrection = 6.0,
|
||||
messageLabelWidth,
|
||||
messageLabelTextSize;
|
||||
|
||||
[_messageLabel setTextColor:[_themeView currentValueForThemeAttribute:@"message-text-color"]];
|
||||
[_messageLabel setFont:[_themeView currentValueForThemeAttribute:@"message-text-font"]];
|
||||
[_messageLabel setTextShadowColor:[_themeView currentValueForThemeAttribute:@"message-text-shadow-color"]];
|
||||
[_messageLabel setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"message-text-shadow-offset"]];
|
||||
[_messageLabel setAlignment:[_themeView currentValueForThemeAttribute:@"message-text-alignment"]];
|
||||
[_messageLabel setTextColor:[self currentValueForThemeAttribute:@"message-text-color"]];
|
||||
[_messageLabel setFont:[self currentValueForThemeAttribute:@"message-text-font"]];
|
||||
[_messageLabel setTextShadowColor:[self currentValueForThemeAttribute:@"message-text-shadow-color"]];
|
||||
[_messageLabel setTextShadowOffset:[self currentValueForThemeAttribute:@"message-text-shadow-offset"]];
|
||||
[_messageLabel setAlignment:[self currentValueForThemeAttribute:@"message-text-alignment"]];
|
||||
[_messageLabel setLineBreakMode:CPLineBreakByWordWrapping];
|
||||
|
||||
messageLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right;
|
||||
@@ -468,18 +357,18 @@ var bottomHeight = 71;
|
||||
*/
|
||||
- (void)_layoutInformativeView
|
||||
{
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
var inset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
sizeWithFontCorrection = 6.0,
|
||||
informativeLabelWidth,
|
||||
informativeLabelOriginY,
|
||||
informativeLabelTextSize;
|
||||
|
||||
[_informativeLabel setTextColor:[_themeView currentValueForThemeAttribute:@"informative-text-color"]];
|
||||
[_informativeLabel setFont:[_themeView currentValueForThemeAttribute:@"informative-text-font"]];
|
||||
[_informativeLabel setTextShadowColor:[_themeView currentValueForThemeAttribute:@"informative-text-shadow-color"]];
|
||||
[_informativeLabel setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"informative-text-shadow-offset"]];
|
||||
[_informativeLabel setAlignment:[_themeView currentValueForThemeAttribute:@"informative-text-alignment"]];
|
||||
[_informativeLabel setTextColor:[self currentValueForThemeAttribute:@"informative-text-color"]];
|
||||
[_informativeLabel setFont:[self currentValueForThemeAttribute:@"informative-text-font"]];
|
||||
[_informativeLabel setTextShadowColor:[self currentValueForThemeAttribute:@"informative-text-shadow-color"]];
|
||||
[_informativeLabel setTextShadowOffset:[self currentValueForThemeAttribute:@"informative-text-shadow-offset"]];
|
||||
[_informativeLabel setAlignment:[self currentValueForThemeAttribute:@"informative-text-alignment"]];
|
||||
[_informativeLabel setLineBreakMode:CPLineBreakByWordWrapping];
|
||||
|
||||
informativeLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right;
|
||||
@@ -497,8 +386,8 @@ var bottomHeight = 71;
|
||||
if (!_accessoryView)
|
||||
return;
|
||||
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
var inset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
accessoryViewWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right,
|
||||
accessoryViewOriginY = CGRectGetMaxY([_informativeLabel frame]) + defaultElementsMargin;
|
||||
|
||||
@@ -514,16 +403,16 @@ var bottomHeight = 71;
|
||||
if (!_showSuppressionButton)
|
||||
return;
|
||||
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
suppressionViewXOffset = [_themeView currentValueForThemeAttribute:@"suppression-button-x-offset"],
|
||||
suppressionViewYOffset = [_themeView currentValueForThemeAttribute:@"suppression-button-y-offset"],
|
||||
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
var inset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
suppressionViewXOffset = [self currentValueForThemeAttribute:@"suppression-button-x-offset"],
|
||||
suppressionViewYOffset = [self currentValueForThemeAttribute:@"suppression-button-y-offset"],
|
||||
defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
suppressionButtonViewOriginY = CGRectGetMaxY([(_accessoryView || _informativeLabel) frame]) + defaultElementsMargin + suppressionViewYOffset;
|
||||
|
||||
[_suppressionButton setTextColor:[_themeView currentValueForThemeAttribute:@"suppression-button-text-color"]];
|
||||
[_suppressionButton setFont:[_themeView currentValueForThemeAttribute:@"suppression-button-text-font"]];
|
||||
[_suppressionButton setTextShadowColor:[_themeView currentValueForThemeAttribute:@"suppression-button-text-shadow-color"]];
|
||||
[_suppressionButton setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"suppression-button-text-shadow-offset"]];
|
||||
[_suppressionButton setTextColor:[self currentValueForThemeAttribute:@"suppression-button-text-color"]];
|
||||
[_suppressionButton setFont:[self currentValueForThemeAttribute:@"suppression-button-text-font"]];
|
||||
[_suppressionButton setTextShadowColor:[self currentValueForThemeAttribute:@"suppression-button-text-shadow-color"]];
|
||||
[_suppressionButton setTextShadowOffset:[self currentValueForThemeAttribute:@"suppression-button-text-shadow-offset"]];
|
||||
[_suppressionButton sizeToFit];
|
||||
|
||||
[_suppressionButton setFrameOrigin:CGPointMake(inset.left + suppressionViewXOffset, suppressionButtonViewOriginY)];
|
||||
@@ -535,17 +424,14 @@ var bottomHeight = 71;
|
||||
*/
|
||||
- (CGSize)_layoutButtonsFromView:(CPView)lastView
|
||||
{
|
||||
var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
|
||||
minimumSize = [_themeView currentValueForThemeAttribute:@"size"],
|
||||
buttonOffset = [_themeView currentValueForThemeAttribute:@"button-offset"],
|
||||
helpLeftOffset = [_themeView currentValueForThemeAttribute:@"help-image-left-offset"],
|
||||
var inset = [self currentValueForThemeAttribute:@"content-inset"],
|
||||
minimumSize = [self currentValueForThemeAttribute:@"size"],
|
||||
buttonOffset = [self currentValueForThemeAttribute:@"button-offset"],
|
||||
helpLeftOffset = [self currentValueForThemeAttribute:@"help-image-left-offset"],
|
||||
aRepresentativeButton = [_buttons objectAtIndex:0],
|
||||
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"],
|
||||
panelSize = [[_window contentView] frame].size,
|
||||
buttonsOriginY,
|
||||
buttonMarginY,
|
||||
buttonMarginX,
|
||||
theme = [self theme],
|
||||
offsetX;
|
||||
|
||||
[aRepresentativeButton setTheme:[self theme]];
|
||||
@@ -558,19 +444,6 @@ var bottomHeight = 71;
|
||||
buttonsOriginY = panelSize.height - [aRepresentativeButton frameSize].height + buttonOffset;
|
||||
offsetX = panelSize.width - inset.right;
|
||||
|
||||
switch ([_window styleMask])
|
||||
{
|
||||
case _CPModalWindowMask:
|
||||
buttonMarginY = [_themeView currentValueForThemeAttribute:@"modal-window-button-margin-y"];
|
||||
buttonMarginX = [_themeView currentValueForThemeAttribute:@"modal-window-button-margin-x"];
|
||||
break;
|
||||
|
||||
default:
|
||||
buttonMarginY = [_themeView currentValueForThemeAttribute:@"standard-window-button-margin-y"];
|
||||
buttonMarginX = [_themeView currentValueForThemeAttribute:@"standard-window-button-margin-x"];
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i = [_buttons count] - 1; i >= 0 ; i--)
|
||||
{
|
||||
var button = _buttons[i];
|
||||
@@ -582,14 +455,14 @@ var bottomHeight = 71;
|
||||
height = CGRectGetHeight(buttonFrame);
|
||||
|
||||
offsetX -= width;
|
||||
[button setFrame:CGRectMake(offsetX + buttonMarginX, buttonsOriginY + buttonMarginY, width, height)];
|
||||
[button setFrame:CGRectMake(offsetX, buttonsOriginY, width, height)];
|
||||
offsetX -= 10;
|
||||
}
|
||||
|
||||
if (_showHelp)
|
||||
{
|
||||
var helpImage = [_themeView currentValueForThemeAttribute:@"help-image"],
|
||||
helpImagePressed = [_themeView currentValueForThemeAttribute:@"help-image-pressed"],
|
||||
var helpImage = [self currentValueForThemeAttribute:@"help-image"],
|
||||
helpImagePressed = [self currentValueForThemeAttribute:@"help-image-pressed"],
|
||||
helpImageSize = helpImage ? [helpImage size] : CGSizeMakeZero(),
|
||||
helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY, helpImageSize.width, helpImageSize.height);
|
||||
|
||||
@@ -614,7 +487,7 @@ var bottomHeight = 71;
|
||||
if (!_window)
|
||||
[self _createWindowWithStyle:nil];
|
||||
|
||||
var iconOffset = [_themeView currentValueForThemeAttribute:@"image-offset"],
|
||||
var iconOffset = [self currentValueForThemeAttribute:@"image-offset"],
|
||||
theImage = _icon,
|
||||
finalSize;
|
||||
|
||||
@@ -622,13 +495,13 @@ var bottomHeight = 71;
|
||||
switch (_alertStyle)
|
||||
{
|
||||
case CPWarningAlertStyle:
|
||||
theImage = [_themeView currentValueForThemeAttribute:@"warning-image"];
|
||||
theImage = [self currentValueForThemeAttribute:@"warning-image"];
|
||||
break;
|
||||
case CPInformationalAlertStyle:
|
||||
theImage = [_themeView currentValueForThemeAttribute:@"information-image"];
|
||||
theImage = [self currentValueForThemeAttribute:@"information-image"];
|
||||
break;
|
||||
case CPCriticalAlertStyle:
|
||||
theImage = [_themeView currentValueForThemeAttribute:@"error-image"];
|
||||
theImage = [self currentValueForThemeAttribute:@"error-image"];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -646,7 +519,7 @@ var bottomHeight = 71;
|
||||
if (_showSuppressionButton)
|
||||
lastView = _suppressionButton;
|
||||
else if (_accessoryView)
|
||||
lastView = _accessoryView;
|
||||
lastView = _accessoryView
|
||||
|
||||
finalSize = [self _layoutButtonsFromView:lastView];
|
||||
if ([_window styleMask] & CPDocModalWindowMask)
|
||||
@@ -655,16 +528,10 @@ var bottomHeight = 71;
|
||||
[_window setFrameSize:finalSize];
|
||||
[_window center];
|
||||
|
||||
if ([_window styleMask] & _CPModalWindowMask || [_window styleMask] & CPHUDBackgroundWindowMask)
|
||||
{
|
||||
[_window setMovable:YES];
|
||||
[_window setMovableByWindowBackground:YES];
|
||||
}
|
||||
|
||||
_needsLayout = NO;
|
||||
}
|
||||
|
||||
// MARK: Displaying Alerts
|
||||
#pragma mark Displaying Alerts
|
||||
|
||||
/*!
|
||||
Displays the \c CPAlert panel as a modal dialog. The user will not be
|
||||
@@ -683,17 +550,6 @@ var bottomHeight = 71;
|
||||
[CPApp runModalForWindow:_window];
|
||||
}
|
||||
|
||||
/*!
|
||||
The same as \c runModal, but executes the code in \c block when the
|
||||
alert is dismissed.
|
||||
*/
|
||||
- (void)runModalWithDidEndBlock:(Function /*(CPAlert alert, int returnCode)*/)block
|
||||
{
|
||||
_didEndBlock = block;
|
||||
|
||||
[self runModal];
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs the receiver modally as an alert sheet attached to a specified window.
|
||||
|
||||
@@ -728,21 +584,7 @@ var bottomHeight = 71;
|
||||
[self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs the receiver modally as an alert sheet attached to a specified window.
|
||||
Executes the code in \c block when the alert is dismissed.
|
||||
|
||||
@param window The parent window for the sheet.
|
||||
@param block Code block to execute on dismissal
|
||||
*/
|
||||
- (void)beginSheetModalForWindow:(CPWindow)aWindow didEndBlock:(Function /*(CPAlert alert, int returnCode)*/)block
|
||||
{
|
||||
_didEndBlock = block;
|
||||
|
||||
[self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
#pragma mark Private
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
@@ -750,11 +592,9 @@ var bottomHeight = 71;
|
||||
- (void)_createWindowWithStyle:(int)forceStyle
|
||||
{
|
||||
var frame = CGRectMakeZero();
|
||||
frame.size = [_themeView currentValueForThemeAttribute:@"size"];
|
||||
frame.size = [self currentValueForThemeAttribute:@"size"];
|
||||
|
||||
_window = [[CPPanel alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle];
|
||||
[_window setLevel:CPStatusWindowLevel];
|
||||
[_window setPlatformWindow:[[CPApp keyWindow] platformWindow]];
|
||||
_window = [[CPWindow alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle];
|
||||
|
||||
if (_title)
|
||||
[_window setTitle:_title];
|
||||
@@ -781,7 +621,8 @@ var bottomHeight = 71;
|
||||
*/
|
||||
- (@action)_showHelp:(id)aSender
|
||||
{
|
||||
[self _sendDelegateAlertShowHelp];
|
||||
if ([_delegate respondsToSelector:@selector(alertShowHelp:)])
|
||||
[_delegate alertShowHelp:self];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -791,8 +632,8 @@ var bottomHeight = 71;
|
||||
{
|
||||
if ([_window isSheet])
|
||||
{
|
||||
[_window orderOut:nil];
|
||||
[CPApp endSheet:_window returnCode:[aSender tag]];
|
||||
[_window orderOut:nil];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -808,57 +649,61 @@ var bottomHeight = 71;
|
||||
*/
|
||||
- (void)_alertDidEnd:(CPWindow)aWindow returnCode:(int)returnCode contextInfo:(id)contextInfo
|
||||
{
|
||||
if (_didEndBlock)
|
||||
{
|
||||
if (typeof(_didEndBlock) === "function")
|
||||
_didEndBlock(self, returnCode);
|
||||
else
|
||||
CPLog.warn("%s: didEnd block is not a function", [self description]);
|
||||
if ([_delegate respondsToSelector:@selector(alertDidEnd:returnCode:)])
|
||||
[_delegate alertDidEnd:self returnCode:returnCode];
|
||||
|
||||
// didEnd blocks are transient
|
||||
_didEndBlock = nil;
|
||||
}
|
||||
else if (_modalDelegate)
|
||||
{
|
||||
if (_didEndSelector)
|
||||
_modalDelegate.isa.objj_msgSend3(_modalDelegate, _didEndSelector, self, returnCode, contextInfo);
|
||||
}
|
||||
else if (_delegate)
|
||||
{
|
||||
if (_didEndSelector)
|
||||
_delegate.isa.objj_msgSend2(_delegate, _didEndSelector, self, returnCode);
|
||||
else
|
||||
[self _sendDelegateAlertDidEndReturnCode:returnCode];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPAlert (CPAlertDelegate)
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call the delegate alertDidEnd:returnCode
|
||||
*/
|
||||
- (void)_sendDelegateAlertDidEndReturnCode:(int)returnCode
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPAlertDelegate_alertDidEnd_returnCode_))
|
||||
return;
|
||||
|
||||
[_delegate alertDidEnd:self returnCode:returnCode];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call the delegate alertShowHelp:
|
||||
*/
|
||||
- (BOOL)_sendDelegateAlertShowHelp
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPAlertDelegate_alertShowHelp_))
|
||||
return YES;
|
||||
|
||||
return [_delegate alertShowHelp:self];
|
||||
if (_didEndSelector)
|
||||
objj_msgSend(_modalDelegate, _didEndSelector, self, returnCode, contextInfo);
|
||||
|
||||
_modalDelegate = nil;
|
||||
_didEndSelector = nil;
|
||||
}
|
||||
|
||||
#pragma mark Theme Attributes
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"alert";
|
||||
}
|
||||
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return [CPDictionary dictionaryWithObjects:[CGSizeMake(400.0, 110.0), CGInsetMake(15, 15, 15, 50), 6, 10,
|
||||
CPJustifiedTextAlignment, [CPColor blackColor], [CPFont boldSystemFontOfSize:13.0], [CPNull null], CGSizeMakeZero(),
|
||||
CPJustifiedTextAlignment, [CPColor blackColor], [CPFont systemFontOfSize:12.0], [CPNull null], CGSizeMakeZero(),
|
||||
CGPointMake(15, 12),
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
[CPNull null],
|
||||
0.0,
|
||||
0.0,
|
||||
3.0,
|
||||
[CPColor blackColor],
|
||||
[CPFont systemFontOfSize:12.0],
|
||||
[CPNull null],
|
||||
0.0
|
||||
]
|
||||
forKeys:[@"size", @"content-inset", @"informative-offset", @"button-offset",
|
||||
@"message-text-alignment", @"message-text-color", @"message-text-font", @"message-text-shadow-color", @"message-text-shadow-offset",
|
||||
@"informative-text-alignment", @"informative-text-color", @"informative-text-font", @"informative-text-shadow-color", @"informative-text-shadow-offset",
|
||||
@"image-offset",
|
||||
@"information-image",
|
||||
@"warning-image",
|
||||
@"error-image",
|
||||
@"help-image",
|
||||
@"help-image-left-offset",
|
||||
@"help-image-pressed",
|
||||
@"suppression-button-y-offset",
|
||||
@"suppression-button-x-offset",
|
||||
@"default-elements-margin",
|
||||
@"suppression-button-text-color",
|
||||
@"suppression-button-text-font",
|
||||
@"suppression-button-text-shadow-color",
|
||||
@"suppression-button-text-shadow-offset"
|
||||
]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+45
-131
@@ -26,26 +26,25 @@
|
||||
@import "CAMediaTimingFunction.j"
|
||||
|
||||
|
||||
@protocol CPAnimationDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (BOOL)animationShouldStart:(CPAnimation)animation;
|
||||
- (float)animation:(CPAnimation)animation valueForProgress:(float)progress;
|
||||
- (void)animationDidEnd:(CPAnimation)animation;
|
||||
- (void)animationDidStop:(CPAnimation)animation;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPAnimationDelegate_animationShouldStart_ = 1 << 1,
|
||||
CPAnimationDelegate_animation_valueForProgress_ = 1 << 2,
|
||||
CPAnimationDelegate_animationDidEnd_ = 1 << 3,
|
||||
CPAnimationDelegate_animationDidStop_ = 1 << 4;
|
||||
|
||||
@typedef CPAnimationCurve
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseInOut = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseIn = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseOut = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationLinear = 3;
|
||||
|
||||
ACTUAL_FRAME_RATE = 0;
|
||||
@@ -81,18 +80,17 @@ ACTUAL_FRAME_RATE = 0;
|
||||
*/
|
||||
@implementation CPAnimation : CPObject
|
||||
{
|
||||
CPTimeInterval _lastTime;
|
||||
CPTimeInterval _duration;
|
||||
CPTimeInterval _lastTime;
|
||||
CPTimeInterval _duration;
|
||||
|
||||
CPAnimationCurve _animationCurve;
|
||||
CAMediaTimingFunction _timingFunction;
|
||||
CPAnimationCurve _animationCurve;
|
||||
CAMediaTimingFunction _timingFunction;
|
||||
|
||||
float _frameRate;
|
||||
float _progress;
|
||||
float _frameRate;
|
||||
float _progress;
|
||||
|
||||
id <CPAnimationDelegate> _delegate;
|
||||
CPTimer _timer;
|
||||
unsigned _implementedDelegateMethods;
|
||||
id _delegate;
|
||||
CPTimer _timer;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -125,29 +123,23 @@ ACTUAL_FRAME_RATE = 0;
|
||||
- (void)setAnimationCurve:(CPAnimationCurve)anAnimationCurve
|
||||
{
|
||||
var timingFunctionName;
|
||||
|
||||
switch (anAnimationCurve)
|
||||
{
|
||||
case CPAnimationEaseInOut:
|
||||
timingFunctionName = kCAMediaTimingFunctionEaseInEaseOut;
|
||||
break;
|
||||
case CPAnimationEaseInOut: timingFunctionName = kCAMediaTimingFunctionEaseInEaseOut;
|
||||
break;
|
||||
|
||||
case CPAnimationEaseIn:
|
||||
timingFunctionName = kCAMediaTimingFunctionEaseIn;
|
||||
break;
|
||||
case CPAnimationEaseIn: timingFunctionName = kCAMediaTimingFunctionEaseIn;
|
||||
break;
|
||||
|
||||
case CPAnimationEaseOut:
|
||||
timingFunctionName = kCAMediaTimingFunctionEaseOut;
|
||||
break;
|
||||
case CPAnimationEaseOut: timingFunctionName = kCAMediaTimingFunctionEaseOut;
|
||||
break;
|
||||
|
||||
case CPAnimationLinear:
|
||||
timingFunctionName = kCAMediaTimingFunctionLinear;
|
||||
break;
|
||||
case CPAnimationLinear: timingFunctionName = kCAMediaTimingFunctionLinear;
|
||||
break;
|
||||
|
||||
default:
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:@"Invalid value provided for animation curve"];
|
||||
break;
|
||||
default: [CPException raise:CPInvalidArgumentException
|
||||
reason:"Invalid value provided for animation curve"];
|
||||
break;
|
||||
}
|
||||
|
||||
_animationCurve = anAnimationCurve;
|
||||
@@ -216,25 +208,9 @@ ACTUAL_FRAME_RATE = 0;
|
||||
Sets the animation's delegate.
|
||||
@param aDelegate the new delegate
|
||||
*/
|
||||
- (void)setDelegate:(id <CPAnimationDelegate>)aDelegate
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate === aDelegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animationShouldStart:)])
|
||||
_implementedDelegateMethods |= CPAnimationDelegate_animationShouldStart_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animationDidEnd:)])
|
||||
_implementedDelegateMethods |= CPAnimationDelegate_animationDidEnd_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animationDidStop:)])
|
||||
_implementedDelegateMethods |= CPAnimationDelegate_animationDidStop_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
|
||||
_implementedDelegateMethods |= CPAnimationDelegate_animation_valueForProgress_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -245,7 +221,7 @@ ACTUAL_FRAME_RATE = 0;
|
||||
- (void)startAnimation
|
||||
{
|
||||
// If we're already animating, or our delegate stops us, animate.
|
||||
if (_timer || ![self _sendDelegateAnimationShouldStart])
|
||||
if (_timer || _delegate && [_delegate respondsToSelector:@selector(animationShouldStart:)] && ![_delegate animationShouldStart:self])
|
||||
return;
|
||||
|
||||
if (_progress === 1.0)
|
||||
@@ -254,9 +230,7 @@ ACTUAL_FRAME_RATE = 0;
|
||||
ACTUAL_FRAME_RATE = 0;
|
||||
_lastTime = new Date();
|
||||
|
||||
var timerInterval = _frameRate <= 0.0 ? 0.0001 : 1.0 / _frameRate;
|
||||
|
||||
_timer = [CPTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:@selector(animationTimerDidFire:) userInfo:nil repeats:YES];
|
||||
_timer = [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(animationTimerDidFire:) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -278,7 +252,8 @@ ACTUAL_FRAME_RATE = 0;
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
|
||||
[self _sendDelegateAnimationDidEnd];
|
||||
if ([_delegate respondsToSelector:@selector(animationDidEnd:)])
|
||||
[_delegate animationDidEnd:self];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +268,8 @@ ACTUAL_FRAME_RATE = 0;
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
|
||||
[self _sendDelegateAnimationDidStop];
|
||||
if ([_delegate respondsToSelector:@selector(animationDidStop:)])
|
||||
[_delegate animationDidStop:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -329,8 +305,8 @@ ACTUAL_FRAME_RATE = 0;
|
||||
{
|
||||
var t = [self currentProgress];
|
||||
|
||||
if ([self _delegateRespondsToAnimationValueForProgress])
|
||||
return [self _sendDelegateAnimationValueForProgress:t];
|
||||
if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
|
||||
return [_delegate animation:self valueForProgress:t];
|
||||
|
||||
if (_animationCurve == CPAnimationLinear)
|
||||
return t;
|
||||
@@ -346,72 +322,10 @@ ACTUAL_FRAME_RATE = 0;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPAnimation (CPAnimationDelegate)
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Check if the delegate responds to animation:valueForProgress:
|
||||
*/
|
||||
- (BOOL)_delegateRespondsToAnimationValueForProgress
|
||||
{
|
||||
return _implementedDelegateMethods & CPAnimationDelegate_animation_valueForProgress_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate animationShouldStart:
|
||||
*/
|
||||
- (BOOL)_sendDelegateAnimationShouldStart
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPAnimationDelegate_animationShouldStart_))
|
||||
return YES;
|
||||
|
||||
return [_delegate animationShouldStart:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate animation:valueForProgress:
|
||||
*/
|
||||
- (float)_sendDelegateAnimationValueForProgress:(float)aProgress
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPAnimationDelegate_animation_valueForProgress_))
|
||||
return aProgress;
|
||||
|
||||
return [_delegate animation:self valueForProgress:aProgress];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate animationDidEnd:
|
||||
*/
|
||||
- (void)_sendDelegateAnimationDidEnd
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPAnimationDelegate_animationDidEnd_))
|
||||
return;
|
||||
|
||||
[_delegate animationDidEnd:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate animationDidStop:
|
||||
*/
|
||||
- (void)_sendDelegateAnimationDidStop
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPAnimationDelegate_animationDidStop_))
|
||||
return;
|
||||
|
||||
[_delegate animationDidStop:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// currently used function to determine time
|
||||
// 1:1 conversion to js from webkit source files
|
||||
// UnitBezier.h, WebCore_animation_AnimationBase.cpp
|
||||
var CubicBezierAtTime = function(t, p1x, p1y, p2x, p2y, duration)
|
||||
var CubicBezierAtTime = function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration)
|
||||
{
|
||||
var ax = 0,
|
||||
bx = 0,
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* CPAppearance.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Antoine Mercadal.
|
||||
* Copyright 2015, 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 <Foundation/Foundation.j>
|
||||
@import "CPTheme.j"
|
||||
|
||||
CPAppearanceNameAqua = @"CPAppearanceNameAqua";
|
||||
CPAppearanceNameLightContent = @"CPAppearanceNameLightContent";
|
||||
CPAppearanceNameVibrantDark = @"CPAppearanceNameVibrantDark";
|
||||
CPAppearanceNameVibrantLight = @"CPAppearanceNameVibrantLight";
|
||||
|
||||
var _CPAppearanceCurrent = nil,
|
||||
_CPAppearancesRegistry = @{};
|
||||
|
||||
|
||||
@protocol CPAppearanceCustomization <CPObject>
|
||||
|
||||
@required
|
||||
- (CPAppearance)appearance;
|
||||
- (void)setAppearance:(CPAppearance)appearance;
|
||||
- (CPAppearance)effectiveAppearance;
|
||||
- (void)setEffectiveAppearance:(CPAppearance)appearance;
|
||||
|
||||
@end
|
||||
|
||||
CPThemeStateAppearanceAqua = CPThemeState("appearance-aqua");
|
||||
CPThemeStateAppearanceLightContent = CPThemeState("appearance-light-content");
|
||||
CPThemeStateAppearanceVibrantLight = CPThemeState("appearance-vibrant-light");
|
||||
CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark");
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
|
||||
A CPAppareance represents the appearance of an to a subset of UI elements.
|
||||
This is a very lightweight implementation of the NSAppearance system, but
|
||||
We are using it for compliance, and especially for the CPVisualEffectView
|
||||
*/
|
||||
@implementation CPAppearance : CPObject
|
||||
{
|
||||
BOOL _allowsVibrancy @accessors(property=allowsVibrancy);
|
||||
|
||||
CPString _name;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Class Methods
|
||||
|
||||
/*! Returns the current default CPAppearance
|
||||
*/
|
||||
+ (CPAppearance)currentAppearance
|
||||
{
|
||||
if (!_CPAppearanceCurrent)
|
||||
_CPAppearanceCurrent = [CPAppearance appearanceNamed:CPAppearanceNameAqua];
|
||||
|
||||
return _CPAppearanceCurrent;
|
||||
}
|
||||
|
||||
/*! Sets the current default CPAppearance
|
||||
@param appearance the new current appearance
|
||||
*/
|
||||
+ (void)setCurrentAppearance:(CPAppearance)anAppearance
|
||||
{
|
||||
_CPAppearanceCurrent = anAppearance;
|
||||
}
|
||||
|
||||
/*! Returns the CPAppearance object with the given name
|
||||
@param name the name of the appearance
|
||||
*/
|
||||
+ (CPAppearance)appearanceNamed:(CPString)aName
|
||||
{
|
||||
if (![_CPAppearancesRegistry containsKey:aName])
|
||||
{
|
||||
[_CPAppearancesRegistry setObject:[[CPAppearance alloc] initWithAppearanceNamed:aName bundle:nil]
|
||||
forKey:aName];
|
||||
}
|
||||
|
||||
return [_CPAppearancesRegistry objectForKey:aName];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// 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
|
||||
*/
|
||||
- (id)initWithAppearanceNamed:(CPString)aName bundle:(CPBundle)bundle
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_name = aName;
|
||||
_allowsVibrancy = YES;
|
||||
|
||||
if ([_CPAppearancesRegistry containsKey:aName])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Appearance with name '" + aName + "' is already declared."];
|
||||
|
||||
[_CPAppearancesRegistry setObject:self forKey:aName];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Implementation
|
||||
|
||||
- (BOOL)isEqual:(id)anObject
|
||||
{
|
||||
if (![anObject isKindOfClass:CPAppearance])
|
||||
return NO;
|
||||
|
||||
return self._name == anObject._name;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return @"<CPAppearance @" + [self UID] + @" name: " + _name + ">";
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: CPCoding
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_name = [aCoder decodeObjectForKey:@"_name"];
|
||||
_allowsVibrancy = [aCoder decodeBoolForKey:@"_allowsVibrancy"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_name forKey:@"_name"];
|
||||
[aCoder encodeBool:_allowsVibrancy forKey:@"_allowsVibrancy"];
|
||||
}
|
||||
|
||||
@end
|
||||
+151
-250
@@ -22,46 +22,37 @@
|
||||
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@import "CPApplication_Constants.j"
|
||||
@import "CPCompatibility.j"
|
||||
@import "CPColorPanel.j"
|
||||
@import "CPCursor.j"
|
||||
@import "CPDocumentController.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPResponder.j"
|
||||
@import "CPDocumentController.j"
|
||||
@import "CPThemeBlend.j"
|
||||
@import "CPCibLoading.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPPlatform.j"
|
||||
@import "CPWindowController.j"
|
||||
@import "_CPPopoverWindow.j"
|
||||
|
||||
@typedef CPModalSession
|
||||
|
||||
var CPMainCibFile = @"CPMainCibFile",
|
||||
CPMainCibFileHumanFriendly = @"Main cib file base name",
|
||||
CPEventModifierFlags = 0;
|
||||
|
||||
CPApp = nil;
|
||||
|
||||
@protocol CPApplicationDelegate <CPObject>
|
||||
CPApplicationWillFinishLaunchingNotification = @"CPApplicationWillFinishLaunchingNotification";
|
||||
CPApplicationDidFinishLaunchingNotification = @"CPApplicationDidFinishLaunchingNotification";
|
||||
CPApplicationWillTerminateNotification = @"CPApplicationWillTerminateNotification";
|
||||
CPApplicationWillBecomeActiveNotification = @"CPApplicationWillBecomeActiveNotification";
|
||||
CPApplicationDidBecomeActiveNotification = @"CPApplicationDidBecomeActiveNotification";
|
||||
CPApplicationWillResignActiveNotification = @"CPApplicationWillResignActiveNotification";
|
||||
CPApplicationDidResignActiveNotification = @"CPApplicationDidResignActiveNotification";
|
||||
|
||||
@optional
|
||||
- (CPApplicationTerminateReply)applicationShouldTerminate:(CPApplication)sender;
|
||||
- (CPString)applicationShouldTerminateMessage:(CPApplication)sender;
|
||||
- (void)applicationDidBecomeActive:(CPNotification)aNotification;
|
||||
- (void)applicationDidChangeScreenParameters:(CPNotification)aNotification;
|
||||
- (void)applicationDidFinishLaunching:(CPNotification)aNotification;
|
||||
- (void)applicationDidResignActive:(CPNotification)aNotification;
|
||||
- (void)applicationWillBecomeActive:(CPNotification)aNotification;
|
||||
- (void)applicationWillFinishLaunching:(CPNotification)aNotification;
|
||||
- (void)applicationWillResignActive:(CPNotification)aNotification;
|
||||
- (void)applicationWillTerminate:(CPNotification)aNotification;
|
||||
CPTerminateNow = YES;
|
||||
CPTerminateCancel = NO;
|
||||
CPTerminateLater = -1; // not currently supported
|
||||
|
||||
@end
|
||||
|
||||
var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
CPApplicationDelegate_applicationShouldTerminateMessage_ = 1 << 1;
|
||||
CPRunStoppedResponse = -1000;
|
||||
CPRunAbortedResponse = -1001;
|
||||
CPRunContinuesResponse = -1002;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -90,38 +81,34 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
*/
|
||||
@implementation CPApplication : CPResponder
|
||||
{
|
||||
CPArray _eventListeners;
|
||||
int _eventListenerInsertionIndex;
|
||||
CPArray _eventListeners;
|
||||
|
||||
CPEvent _currentEvent;
|
||||
CPWindow _lastMouseMoveWindow;
|
||||
CPEvent _currentEvent;
|
||||
|
||||
CPArray _windows;
|
||||
CPWindow _keyWindow;
|
||||
CPWindow _mainWindow;
|
||||
CPWindow _previousKeyWindow;
|
||||
CPWindow _previousMainWindow;
|
||||
CPArray _windows;
|
||||
CPWindow _keyWindow;
|
||||
CPWindow _mainWindow;
|
||||
CPWindow _previousKeyWindow;
|
||||
CPWindow _previousMainWindow;
|
||||
|
||||
CPDocumentController _documentController;
|
||||
CPDocumentController _documentController;
|
||||
|
||||
CPModalSession _currentSession;
|
||||
CPModalSession _currentSession;
|
||||
|
||||
//
|
||||
id <CPApplicationDelegate> _delegate;
|
||||
CPInteger _implementedDelegateMethods;
|
||||
id _delegate;
|
||||
BOOL _finishedLaunching;
|
||||
BOOL _isActive;
|
||||
|
||||
BOOL _finishedLaunching;
|
||||
BOOL _isActive;
|
||||
CPDictionary _namedArgs;
|
||||
CPArray _args;
|
||||
CPString _fullArgsString;
|
||||
|
||||
CPDictionary _namedArgs;
|
||||
CPArray _args;
|
||||
CPString _fullArgsString;
|
||||
CPImage _applicationIconImage;
|
||||
|
||||
CPImage _applicationIconImage;
|
||||
CPPanel _aboutPanel;
|
||||
|
||||
CPPanel _aboutPanel;
|
||||
|
||||
CPThemeBlend _themeBlend @accessors(property=themeBlend);
|
||||
CPThemeBlend _themeBlend @accessors(property=themeBlend);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -132,7 +119,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
+ (CPApplication)sharedApplication
|
||||
{
|
||||
if (!CPApp)
|
||||
CPApp = [[self alloc] init];
|
||||
CPApp = [[CPApplication alloc] init];
|
||||
|
||||
return CPApp;
|
||||
}
|
||||
@@ -151,9 +138,10 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
if (self)
|
||||
{
|
||||
_eventListeners = [];
|
||||
_eventListenerInsertionIndex = 0;
|
||||
|
||||
_windows = [[CPNull null]];
|
||||
_windows = [];
|
||||
|
||||
[_windows addObject:nil];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -167,13 +155,11 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
react to these events.
|
||||
@param aDelegate the delegate object
|
||||
*/
|
||||
- (void)setDelegate:(id <CPApplicationDelegate>)aDelegate
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate == aDelegate)
|
||||
return;
|
||||
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter],
|
||||
delegateNotifications =
|
||||
[
|
||||
@@ -183,8 +169,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
CPApplicationDidBecomeActiveNotification, @selector(applicationDidBecomeActive:),
|
||||
CPApplicationWillResignActiveNotification, @selector(applicationWillResignActive:),
|
||||
CPApplicationDidResignActiveNotification, @selector(applicationDidResignActive:),
|
||||
CPApplicationWillTerminateNotification, @selector(applicationWillTerminate:),
|
||||
CPApplicationDidChangeScreenParametersNotification, @selector(applicationDidChangeScreenParameters:)
|
||||
CPApplicationWillTerminateNotification, @selector(applicationWillTerminate:)
|
||||
],
|
||||
count = [delegateNotifications count];
|
||||
|
||||
@@ -214,12 +199,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
if ([_delegate respondsToSelector:selector])
|
||||
[defaultCenter addObserver:_delegate selector:selector name:notificationName object:self];
|
||||
}
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(applicationShouldTerminate:)])
|
||||
_implementedDelegateMethods |= CPApplicationDelegate_applicationShouldTerminate_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(applicationShouldTerminateMessage:)])
|
||||
_implementedDelegateMethods |= CPApplicationDelegate_applicationShouldTerminateMessage_
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -271,13 +250,8 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
_documentController = [CPDocumentController sharedDocumentController];
|
||||
|
||||
var needsUntitled = !!_documentController,
|
||||
URLStrings = nil;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
URLStrings = window.cpOpeningURLStrings && window.cpOpeningURLStrings();
|
||||
#endif
|
||||
|
||||
var index = 0,
|
||||
URLStrings = window.cpOpeningURLStrings && window.cpOpeningURLStrings(),
|
||||
index = 0,
|
||||
count = [URLStrings count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
@@ -388,32 +362,22 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
applicationVersion = [options objectForKey:@"ApplicationVersion"] || [mainInfo objectForKey:@"CPBundleShortVersionString"],
|
||||
copyright = [options objectForKey:@"Copyright"] || [mainInfo objectForKey:@"CPHumanReadableCopyright"];
|
||||
|
||||
var windowWidth = 275,
|
||||
windowHeight = 223,
|
||||
imgWidth = 100,
|
||||
imgHeight = 100,
|
||||
interField = 8,
|
||||
aboutPanel = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, windowWidth, windowHeight) styleMask:CPClosableWindowMask],
|
||||
imageView = [[CPImageView alloc] initWithFrame:CGRectMake((windowWidth / 2) - (imgWidth / 2), interField, imgWidth, imgHeight)],
|
||||
applicationLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 16, windowWidth - 34, 24)],
|
||||
versionLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 48, windowWidth - 34, 16)],
|
||||
copyrightLabel = [[CPTextField alloc] initWithFrame:CGRectMake(17, imgHeight + 72, windowWidth - 34, 32)],
|
||||
contentView = [aboutPanel contentView];
|
||||
var aboutPanelPath = [[CPBundle bundleForClass:[CPWindowController class]] pathForResource:@"AboutPanel.cib"],
|
||||
aboutPanelController = [CPWindowController alloc],
|
||||
aboutPanelController = [aboutPanelController initWithWindowCibPath:aboutPanelPath owner:aboutPanelController],
|
||||
aboutPanel = [aboutPanelController window],
|
||||
contentView = [aboutPanel contentView],
|
||||
imageView = [contentView viewWithTag:1],
|
||||
applicationLabel = [contentView viewWithTag:2],
|
||||
versionLabel = [contentView viewWithTag:3],
|
||||
copyrightLabel = [contentView viewWithTag:4],
|
||||
standardPath = [[CPBundle bundleForClass:[self class]] pathForResource:@"standardApplicationIcon.png"];
|
||||
|
||||
// FIXME move this into the CIB eventually
|
||||
[applicationLabel setFont:[CPFont boldSystemFontOfSize:[CPFont systemFontSize] + 2]];
|
||||
[applicationLabel setAlignment:CPCenterTextAlignment];
|
||||
[versionLabel setFont:[CPFont systemFontOfSize:[CPFont systemFontSize] - 1]];
|
||||
[versionLabel setAlignment:CPCenterTextAlignment];
|
||||
[copyrightLabel setFont:[CPFont systemFontOfSize:[CPFont systemFontSize] - 1]];
|
||||
[copyrightLabel setAlignment:CPCenterTextAlignment];
|
||||
[copyrightLabel setLineBreakMode:CPLineBreakByWordWrapping];
|
||||
|
||||
[contentView addSubview:imageView];
|
||||
[contentView addSubview:applicationLabel];
|
||||
[contentView addSubview:versionLabel];
|
||||
[contentView addSubview:copyrightLabel];
|
||||
|
||||
var standardPath = [[CPBundle bundleForClass:[self class]] pathForResource:@"standardApplicationIcon.png"];
|
||||
|
||||
[imageView setImage:applicationIcon || [[CPImage alloc] initWithContentsOfFile:standardPath
|
||||
size:CGSizeMake(256, 256)]];
|
||||
@@ -427,7 +391,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
else
|
||||
[versionLabel setStringValue:@""];
|
||||
|
||||
[copyrightLabel setStringValue:copyright || @""];
|
||||
[copyrightLabel setStringValue:copyright || ""];
|
||||
[aboutPanel center];
|
||||
|
||||
_aboutPanel = aboutPanel;
|
||||
@@ -437,11 +401,16 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
}
|
||||
|
||||
|
||||
- (void)_documentController:(CPDocumentController)docController didCloseAll:(BOOL)didCloseAll context:(Object)info
|
||||
- (void)_documentController:(NSDocumentController *)docController didCloseAll:(BOOL)didCloseAll context:(Object)info
|
||||
{
|
||||
// callback method for terminate:
|
||||
if (didCloseAll)
|
||||
[self replyToApplicationShouldTerminate:[self _sendDelegateApplicationShouldTerminate]];
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(applicationShouldTerminate:)])
|
||||
[self replyToApplicationShouldTerminate:[_delegate applicationShouldTerminate:self]];
|
||||
else
|
||||
[self replyToApplicationShouldTerminate:YES];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)replyToApplicationShouldTerminate:(BOOL)terminate
|
||||
@@ -455,22 +424,16 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
|
||||
- (void)activateIgnoringOtherApps:(BOOL)shouldIgnoreOtherApps
|
||||
{
|
||||
if (_isActive)
|
||||
return;
|
||||
|
||||
[self _willBecomeActive];
|
||||
|
||||
[CPPlatform activateIgnoringOtherApps:shouldIgnoreOtherApps];
|
||||
_isActive = YES;
|
||||
|
||||
[self _didBecomeActive];
|
||||
[self _willResignActive];
|
||||
}
|
||||
|
||||
- (void)deactivate
|
||||
{
|
||||
if (!_isActive)
|
||||
return;
|
||||
|
||||
[self _willResignActive];
|
||||
|
||||
[CPPlatform deactivate];
|
||||
@@ -479,7 +442,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
[self _didResignActive];
|
||||
}
|
||||
|
||||
- (BOOL)isActive
|
||||
- (void)isActive
|
||||
{
|
||||
return _isActive;
|
||||
}
|
||||
@@ -496,15 +459,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
- (void)run
|
||||
{
|
||||
[self finishLaunching];
|
||||
[self sendEvent:[CPEvent otherEventWithType:CPAppKitDefined
|
||||
location:CGPointMakeZero()
|
||||
modifierFlags:0
|
||||
timestamp:[CPEvent currentTimestamp]
|
||||
windowNumber:[_keyWindow windowNumber]
|
||||
context:nil
|
||||
subtype:nil
|
||||
data1:nil
|
||||
data2:nil]];
|
||||
}
|
||||
|
||||
// Managing the Event Loop
|
||||
@@ -546,8 +500,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
if (_eventListeners[count]._callback === _CPRunModalLoop)
|
||||
{
|
||||
_eventListeners.splice(count, 1);
|
||||
if (count <= _eventListenerInsertionIndex)
|
||||
_eventListenerInsertionIndex--;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -595,7 +547,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
|
||||
// [theWindow._bridge _obscureWindowsBelowModalWindow];
|
||||
|
||||
[CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:YES];
|
||||
[CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -613,8 +565,8 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
/* @ignore */
|
||||
- (BOOL)_handleKeyEquivalent:(CPEvent)anEvent
|
||||
{
|
||||
return [[self keyWindow] performKeyEquivalent:anEvent] ||
|
||||
[[self mainMenu] performKeyEquivalent:anEvent];
|
||||
return [[self keyWindow] performKeyEquivalent:anEvent] ||
|
||||
[[self mainMenu] performKeyEquivalent:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -626,51 +578,43 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
_currentEvent = anEvent;
|
||||
CPEventModifierFlags = [anEvent modifierFlags];
|
||||
|
||||
var theWindow = [anEvent window];
|
||||
#if PLATFORM(DOM)
|
||||
var willPropagate = [[[anEvent window] platformWindow] _willPropagateCurrentDOMEvent];
|
||||
|
||||
if ([anEvent type] == CPMouseMoved)
|
||||
{
|
||||
if (theWindow !== _lastMouseMoveWindow)
|
||||
[_lastMouseMoveWindow _mouseExitedResizeRect];
|
||||
|
||||
_lastMouseMoveWindow = theWindow;
|
||||
}
|
||||
|
||||
/*
|
||||
Event listeners are processed from back to front so that newer event listeners normally take
|
||||
precedence. If during the execution of a callback a new event listener is added, it should
|
||||
be inserted after the current callback but before any higher priority callbacks. This makes
|
||||
repeating event listeners (those that reinsert themselves) stable relative to each other.
|
||||
*/
|
||||
for (var i = _eventListeners.length - 1; i >= 0; i--)
|
||||
{
|
||||
var listener = _eventListeners[i];
|
||||
|
||||
if (listener._mask & (1 << [anEvent type]))
|
||||
{
|
||||
_eventListeners.splice(i, 1);
|
||||
// In case the callback wants to add more listeners.
|
||||
_eventListenerInsertionIndex = i;
|
||||
listener._callback(anEvent);
|
||||
|
||||
if (listener._dequeue)
|
||||
{
|
||||
// Don't process the event normally and don't send it to any other listener.
|
||||
_eventListenerInsertionIndex = _eventListeners.length;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_eventListenerInsertionIndex = _eventListeners.length;
|
||||
// temporarily pretend we won't propagate the event. we'll restore the saved value later
|
||||
// we do this outside the if so that changes user code might make in _handleKeyEquiv. are preserved
|
||||
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
|
||||
#endif
|
||||
|
||||
// Check if this is a candidate for key equivalent...
|
||||
if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
|
||||
// The key equivalent was handled.
|
||||
return;
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var characters = [anEvent characters],
|
||||
modifierFlags = [anEvent modifierFlags];
|
||||
|
||||
if (theWindow)
|
||||
[theWindow sendEvent:anEvent];
|
||||
// Unconditionally propagate on these keys to solve browser copy paste bugs
|
||||
if ((characters == "c" || characters == "x" || characters == "v") && (modifierFlags & CPPlatformActionKeyMask))
|
||||
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:YES];
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
// if we make it this far, then restore the original willPropagate value
|
||||
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:willPropagate];
|
||||
#endif
|
||||
|
||||
if (_eventListeners.length)
|
||||
{
|
||||
if (_eventListeners[_eventListeners.length - 1]._mask & (1 << [anEvent type]))
|
||||
_eventListeners.pop()._callback(anEvent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[[anEvent window] sendEvent:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -706,10 +650,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
*/
|
||||
- (CPWindow)windowWithWindowNumber:(int)aWindowNumber
|
||||
{
|
||||
// Never allow _windows[0] to be returned - it's an internal CPNull placeholder.
|
||||
if (!aWindowNumber)
|
||||
return nil;
|
||||
|
||||
return _windows[aWindowNumber];
|
||||
}
|
||||
|
||||
@@ -718,8 +658,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
*/
|
||||
- (CPArray)windows
|
||||
{
|
||||
// Return all windows, but not the CPNull placeholder in _windows[0].
|
||||
return [_windows subarrayWithRange:CPMakeRange(1, [_windows count] - 1)];
|
||||
return _windows;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -947,44 +886,31 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
|
||||
/*!
|
||||
Fires a callback function when an event matching a given mask occurs.
|
||||
|
||||
If multiple callbacks are set which match the same event, later callbacks
|
||||
take priority over earlier callbacks, unless a new callback is set while
|
||||
an existing callback is being processed in which case it's given the same
|
||||
priority as the currently processing callback.
|
||||
|
||||
@param aCallback A js function to be fired.
|
||||
@prarm aMask An event mask for the next event.
|
||||
@param anExpiration The date for which this callback expires (not implemented).
|
||||
@param aCallback - A js function to be fired.
|
||||
@prarm aMask - An event mask for the next event.
|
||||
@param anExpiration - The date for which this callback expires (not implemented).
|
||||
@param inMode (not implemented).
|
||||
@param shouldDequeue YES to remove the event from the queue after calling the callback,
|
||||
NO to deliver it normally.
|
||||
@param shouldDequeue (not implemented).
|
||||
*/
|
||||
- (void)setCallback:(Function)aCallback forNextEventMatchingMask:(unsigned int)aMask untilDate:(CPDate)anExpiration inMode:(CPString)aMode dequeue:(BOOL)shouldDequeue
|
||||
{
|
||||
_eventListeners.splice(_eventListenerInsertionIndex++, 0, _CPEventListenerMake(aMask, aCallback, shouldDequeue));
|
||||
_eventListeners.push(_CPEventListenerMake(aMask, aCallback));
|
||||
}
|
||||
|
||||
/*!
|
||||
Assigns a target and action for the next event matching a given event mask.
|
||||
The callback method called will be passed the CPEvent when it fires.
|
||||
|
||||
If multiple callbacks are set which match the same event, later callbacks
|
||||
take priority over earlier callbacks, unless a new callback is set while
|
||||
an existing callback is being processed in which case it's given the same
|
||||
priority as the currently processing callback.
|
||||
|
||||
@param aTarget The target object for the callback.
|
||||
@param aSelector The selector which should be called on the target object.
|
||||
@param aMask The mask for a given event which should trigger the callback.
|
||||
@param anExpiration The date for which the callback expires (not implemented).
|
||||
@param aTarget - The target object for the callback.
|
||||
@param aSelector - The selector which should be called on the target object.
|
||||
@param aMask - The mask for a given event which should trigger the callback.
|
||||
@param anExpiration - The date for which the callback expires (not implemented).
|
||||
@param aMode (not implemented).
|
||||
@param shouldDequeue YES to remove the event from the queue after calling the callback,
|
||||
NO to deliver it normally.
|
||||
@param shouldDequeue (not implemented).
|
||||
*/
|
||||
- (void)setTarget:(id)aTarget selector:(SEL)aSelector forNextEventMatchingMask:(unsigned int)aMask untilDate:(CPDate)anExpiration inMode:(CPString)aMode dequeue:(BOOL)shouldDequeue
|
||||
{
|
||||
_eventListeners.splice(_eventListenerInsertionIndex++, 0, _CPEventListenerMake(aMask, function (anEvent) { if (aTarget != null) aTarget.isa.objj_msgSend1(aTarget, aSelector, anEvent); }, shouldDequeue));
|
||||
_eventListeners.push(_CPEventListenerMake(aMask, function (anEvent) { objj_msgSend(aTarget, aSelector, anEvent); }));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1002,10 +928,10 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
@param aSheet the window to display as a sheet
|
||||
@param aWindow the window that will hold the sheet as a child
|
||||
@param aModalDelegate
|
||||
@param didEndSelector
|
||||
@param contextInfo
|
||||
@param aDidEndSelector
|
||||
@param aContextInfo
|
||||
*/
|
||||
- (void)beginSheet:(CPWindow)aSheet modalForWindow:(CPWindow)aWindow modalDelegate:(id)aModalDelegate didEndSelector:(SEL)didEndSelector contextInfo:(id)contextInfo
|
||||
- (void)beginSheet:(CPWindow)aSheet modalForWindow:(CPWindow)aWindow modalDelegate:(id)aModalDelegate didEndSelector:(SEL)aDidEndSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
if ([aWindow isSheet])
|
||||
{
|
||||
@@ -1013,10 +939,16 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
return;
|
||||
}
|
||||
|
||||
if (![aWindow attachedSheet])
|
||||
[aSheet._windowView _enableSheet:YES inWindow:aWindow];
|
||||
[aSheet._windowView _enableSheet:YES];
|
||||
|
||||
[aWindow _attachSheet:aSheet modalDelegate:aModalDelegate didEndSelector:didEndSelector contextInfo:contextInfo];
|
||||
// -dw- if a sheet is already visible, we skip this since it serves no purpose and causes
|
||||
// orderOut: to be called on the sheet, which is not what we want.
|
||||
if (![aWindow isVisible])
|
||||
{
|
||||
[aWindow orderFront:self];
|
||||
[aSheet setPlatformWindow:[aWindow platformWindow]];
|
||||
}
|
||||
[aWindow _attachSheet:aSheet modalDelegate:aModalDelegate didEndSelector:aDidEndSelector contextInfo:aContextInfo];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1041,7 +973,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
var aWindow = [_windows objectAtIndex:count],
|
||||
context = aWindow._sheetContext;
|
||||
|
||||
if (context && context["sheet"] === sheet)
|
||||
if (context != nil && context["sheet"] === sheet)
|
||||
{
|
||||
context["returnCode"] = returnCode;
|
||||
[aWindow _endSheet];
|
||||
@@ -1077,8 +1009,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
*/
|
||||
- (CPArray)arguments
|
||||
{
|
||||
// FIXME This should probably not access the window object #if !PLATFORM(DOM), but the unit tests rely on it.
|
||||
if (window && window.location && _fullArgsString !== window.location.hash)
|
||||
if (_fullArgsString !== window.location.hash)
|
||||
[self _reloadArguments];
|
||||
|
||||
return _args;
|
||||
@@ -1105,9 +1036,8 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
if (!args || args.length == 0)
|
||||
{
|
||||
_args = [];
|
||||
// Don't use if PLATFORM(DOM) here - the unit test fakes window.location so we should play along.
|
||||
if (window && window.location)
|
||||
window.location.hash = @"#";
|
||||
window.location.hash = @"#";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1122,15 +1052,12 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
|
||||
var hash = [toEncode componentsJoinedByString:@"/"];
|
||||
|
||||
// Don't use if PLATFORM(DOM) here - the unit test fakes window.location so we should play along.
|
||||
if (window && window.location)
|
||||
window.location.hash = @"#" + hash;
|
||||
window.location.hash = @"#" + hash;
|
||||
}
|
||||
|
||||
- (void)_reloadArguments
|
||||
{
|
||||
// FIXME This should probably not access the window object #if !PLATFORM(DOM), but the unit tests rely on it.
|
||||
_fullArgsString = (window && window.location) ? window.location.hash : "";
|
||||
_fullArgsString = window.location.hash;
|
||||
|
||||
if (_fullArgsString.length)
|
||||
{
|
||||
@@ -1202,7 +1129,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
[[self keyWindow] orderFront:self];
|
||||
else if ([self mainWindow])
|
||||
[[self mainWindow] makeKeyAndOrderFront:self];
|
||||
else if ([self mainMenu])
|
||||
else
|
||||
[[self mainMenu]._menuWindow makeKeyWindow]; //FIXME this may not actually work
|
||||
|
||||
_previousKeyWindow = nil;
|
||||
@@ -1220,22 +1147,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
userInfo:nil];
|
||||
}
|
||||
|
||||
- (BOOL)_sendDelegateApplicationShouldTerminate
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPApplicationDelegate_applicationShouldTerminate_))
|
||||
return YES;
|
||||
|
||||
return [_delegate applicationShouldTerminate:self];
|
||||
}
|
||||
|
||||
- (CPString)_sendDelegateApplicationShouldTerminateMessage
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPApplicationDelegate_applicationShouldTerminateMessage_))
|
||||
return @"You have attempted to leave this page. Are you sure you want to exit this page?";
|
||||
|
||||
return [_delegate applicationShouldTerminateMessage:self];
|
||||
}
|
||||
|
||||
- (void)_didResignActive
|
||||
{
|
||||
if (self._activeMenu)
|
||||
@@ -1260,7 +1171,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
|
||||
|
||||
+ (CPString)defaultThemeName
|
||||
{
|
||||
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo2");
|
||||
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo");
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1270,29 +1181,28 @@ var _CPModalSessionMake = function(aWindow, aStopCode)
|
||||
return { _window:aWindow, _state:CPRunContinuesResponse , _previous:nil };
|
||||
};
|
||||
|
||||
var _CPEventListenerMake = function(anEventMask, aCallback, shouldDequeue)
|
||||
var _CPEventListenerMake = function(anEventMask, aCallback)
|
||||
{
|
||||
return { _mask:anEventMask, _callback:aCallback, _dequeue:shouldDequeue };
|
||||
return { _mask:anEventMask, _callback:aCallback };
|
||||
};
|
||||
|
||||
// Make this a global for use in CPPlatformWindow+DOM.j.
|
||||
_CPRunModalLoop = function(anEvent)
|
||||
{
|
||||
[CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:YES];
|
||||
[CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:NO];
|
||||
|
||||
var theWindow = [anEvent window],
|
||||
modalSession = CPApp._currentSession;
|
||||
|
||||
/*
|
||||
The special case for popovers here is not clear. In Cocoa the popover window does not respond YES to worksWhenModal, yet it works when there is a modal window. Maybe it starts its own modal session, but interaction with the original modal window seems to continue working as well. Regardless of correctness, this solution beats popovers not working at all from sheets.
|
||||
*/
|
||||
// The special case for popovers here is not clear. In Cocoa the popover window does not respond YES to worksWhenModal,
|
||||
// yet it works when there is a modal window. Maybe it starts its own modal session, but interaction with the original
|
||||
// modal window seems to continue working as well. Regardless of correctness, this solution beats popovers not working
|
||||
// at all from sheets.
|
||||
if (theWindow == modalSession._window ||
|
||||
[theWindow worksWhenModal] ||
|
||||
[theWindow attachedSheet] == modalSession._window || // -dw- allow modal parent of sheet to be repositioned
|
||||
([theWindow isKindOfClass:_CPPopoverWindow] && [[theWindow targetView] window] === modalSession._window))
|
||||
{
|
||||
([theWindow isKindOfClass:_CPAttachedWindow] && [[theWindow targetView] window] === modalSession._window))
|
||||
[theWindow sendEvent:anEvent];
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -1308,17 +1218,7 @@ function CPApplicationMain(args, namedArgs)
|
||||
#if PLATFORM(DOM)
|
||||
// hook to allow recorder, etc to manipulate things before starting AppKit
|
||||
if (window.parent !== window && typeof window.parent._childAppIsStarting === "function")
|
||||
{
|
||||
try
|
||||
{
|
||||
window.parent._childAppIsStarting(window);
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
// This could happen if we're in an iframe without access to the parent frame.
|
||||
CPLog.warn("Failed to call parent frame's _childAppIsStarting().");
|
||||
}
|
||||
}
|
||||
window.parent._childAppIsStarting(window);
|
||||
#endif
|
||||
|
||||
var mainBundle = [CPBundle mainBundle],
|
||||
@@ -1358,7 +1258,7 @@ var _CPAppBootstrapperActions = nil;
|
||||
{
|
||||
var action = _CPAppBootstrapperActions.shift();
|
||||
|
||||
if (self.isa.objj_msgSend0(self, action))
|
||||
if (objj_msgSend(self, action))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1375,7 +1275,7 @@ var _CPAppBootstrapperActions = nil;
|
||||
var defaultThemeName = [CPApplication defaultThemeName],
|
||||
themeURL = nil;
|
||||
|
||||
if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2")
|
||||
if (defaultThemeName === @"Aristo")
|
||||
themeURL = [[CPBundle bundleForClass:[CPApplication class]] pathForResource:defaultThemeName + @".blend"];
|
||||
else
|
||||
themeURL = [[CPBundle mainBundle] pathForResource:defaultThemeName + @".blend"];
|
||||
@@ -1402,8 +1302,8 @@ var _CPAppBootstrapperActions = nil;
|
||||
if (mainCibFile)
|
||||
{
|
||||
[mainBundle loadCibFile:mainCibFile
|
||||
externalNameTable:@{ CPCibOwner: CPApp }
|
||||
loadDelegate:self];
|
||||
externalNameTable:[CPDictionary dictionaryWithObject:CPApp forKey:CPCibOwner]
|
||||
loadDelegate:self];
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -1420,25 +1320,26 @@ var _CPAppBootstrapperActions = nil;
|
||||
// FIXME: We should implement autoenabling.
|
||||
[mainMenu setAutoenablesItems:NO];
|
||||
|
||||
var newMenuItem = [[CPMenuItem alloc] initWithTitle:@"New" action:@selector(newDocument:) keyEquivalent:@"n"];
|
||||
var bundle = [CPBundle bundleForClass:[CPApplication class]],
|
||||
newMenuItem = [[CPMenuItem alloc] initWithTitle:@"New" action:@selector(newDocument:) keyEquivalent:@"n"];
|
||||
|
||||
[newMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-new" forClass:_CPMenuView]];
|
||||
[newMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-new" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
|
||||
[newMenuItem setImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/New.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
[newMenuItem setAlternateImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/NewHighlighted.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
|
||||
[mainMenu addItem:newMenuItem];
|
||||
|
||||
var openMenuItem = [[CPMenuItem alloc] initWithTitle:@"Open" action:@selector(openDocument:) keyEquivalent:@"o"];
|
||||
|
||||
[openMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-open" forClass:_CPMenuView]];
|
||||
[openMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-open" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
|
||||
[openMenuItem setImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/Open.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
[openMenuItem setAlternateImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/OpenHighlighted.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
|
||||
[mainMenu addItem:openMenuItem];
|
||||
|
||||
var saveMenu = [[CPMenu alloc] initWithTitle:@"Save"],
|
||||
saveMenuItem = [[CPMenuItem alloc] initWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:nil];
|
||||
|
||||
[saveMenuItem setImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-save" forClass:_CPMenuView]];
|
||||
[saveMenuItem setAlternateImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-general-icon-save" inState:CPThemeStateHighlighted forClass:_CPMenuView]];
|
||||
[saveMenuItem setImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/Save.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
[saveMenuItem setAlternateImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/SaveHighlighted.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
|
||||
[saveMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:@"s"]];
|
||||
[saveMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Save As" action:@selector(saveDocumentAs:) keyEquivalent:nil]];
|
||||
@@ -1480,7 +1381,7 @@ var _CPAppBootstrapperActions = nil;
|
||||
|
||||
+ (void)cibDidFailToLoad:(CPCib)aCib
|
||||
{
|
||||
throw new Error("Could not load main cib file. Did you forget to nib2cib it?");
|
||||
throw new Error("Could not load main cib file (Did you forget to nib2cib it?).");
|
||||
}
|
||||
|
||||
+ (void)reset
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* CPApplication_Constants.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Aparajita Fishman.
|
||||
* Copyright 2013 The 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
|
||||
*/
|
||||
|
||||
CPApp = nil;
|
||||
|
||||
CPApplicationWillFinishLaunchingNotification = @"CPApplicationWillFinishLaunchingNotification";
|
||||
CPApplicationDidFinishLaunchingNotification = @"CPApplicationDidFinishLaunchingNotification";
|
||||
CPApplicationWillTerminateNotification = @"CPApplicationWillTerminateNotification";
|
||||
CPApplicationWillBecomeActiveNotification = @"CPApplicationWillBecomeActiveNotification";
|
||||
CPApplicationDidBecomeActiveNotification = @"CPApplicationDidBecomeActiveNotification";
|
||||
CPApplicationWillResignActiveNotification = @"CPApplicationWillResignActiveNotification";
|
||||
CPApplicationDidResignActiveNotification = @"CPApplicationDidResignActiveNotification";
|
||||
CPApplicationDidChangeScreenParametersNotification = @"CPApplicationDidChangeScreenParametersNotification";
|
||||
|
||||
@typedef CPApplicationTerminateReply
|
||||
CPTerminateNow = YES;
|
||||
CPTerminateCancel = NO;
|
||||
CPTerminateLater = -1; // not currently supported
|
||||
|
||||
CPRunStoppedResponse = -1000;
|
||||
CPRunAbortedResponse = -1001;
|
||||
CPRunContinuesResponse = -1002;
|
||||
+45
-68
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
@import <Foundation/CPIndexSet.j>
|
||||
@import <Foundation/CPPredicate.j>
|
||||
|
||||
@import "CPObjectController.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
|
||||
@@ -65,51 +65,51 @@
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingContentArray
|
||||
{
|
||||
return [CPSet setWithObjects:@"content"];
|
||||
return [CPSet setWithObjects:"content"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingArrangedObjects
|
||||
{
|
||||
// Also depends on "filterPredicate" but we'll handle that manually.
|
||||
return [CPSet setWithObjects:@"content", @"sortDescriptors"];
|
||||
return [CPSet setWithObjects:"content", "sortDescriptors"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingSelection
|
||||
{
|
||||
return [CPSet setWithObjects:@"selectionIndexes"];
|
||||
return [CPSet setWithObjects:"selectionIndexes"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingSelectionIndex
|
||||
{
|
||||
return [CPSet setWithObjects:@"selectionIndexes"];
|
||||
return [CPSet setWithObjects:"selectionIndexes"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingSelectionIndexes
|
||||
{
|
||||
// When the arranged objects change, selection preservation may cause the indexes
|
||||
// to change.
|
||||
return [CPSet setWithObjects:@"arrangedObjects"];
|
||||
return [CPSet setWithObjects:"arrangedObjects"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingSelectedObjects
|
||||
{
|
||||
// Don't need to depend on arrangedObjects here because selectionIndexes already does.
|
||||
return [CPSet setWithObjects:@"selectionIndexes"];
|
||||
return [CPSet setWithObjects:"selectionIndexes"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingCanRemove
|
||||
{
|
||||
return [CPSet setWithObjects:@"selectionIndexes"];
|
||||
return [CPSet setWithObjects:"selectionIndexes"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingCanSelectNext
|
||||
{
|
||||
return [CPSet setWithObjects:@"selectionIndexes"];
|
||||
return [CPSet setWithObjects:"selectionIndexes"];
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingCanSelectPrevious
|
||||
{
|
||||
return [CPSet setWithObjects:@"selectionIndexes"];
|
||||
return [CPSet setWithObjects:"selectionIndexes"];
|
||||
}
|
||||
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -543,7 +563,7 @@
|
||||
*/
|
||||
- (BOOL)setSelectionIndexes:(CPIndexSet)indexes
|
||||
{
|
||||
[self _selectionWillChange];
|
||||
[self _selectionWillChange]
|
||||
var r = [self __setSelectionIndexes:indexes avoidEmpty:NO];
|
||||
[self _selectionDidChange];
|
||||
return r;
|
||||
@@ -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.
|
||||
@@ -743,7 +744,6 @@
|
||||
return;
|
||||
|
||||
var willClearPredicate = NO;
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion && _filterPredicate)
|
||||
{
|
||||
[self willChangeValueForKey:@"filterPredicate"];
|
||||
@@ -773,7 +773,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,24 +786,16 @@
|
||||
[_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.
|
||||
*/
|
||||
|
||||
// TODO: Remove these lines when granular notifications are implemented
|
||||
var proxy = [_CPKVOProxy proxyForObject:self];
|
||||
[proxy setAdding:YES];
|
||||
|
||||
// This will also send notifications for arrangedObjects.
|
||||
// This will also send notificaitons for arrangedObjects.
|
||||
[self didChangeValueForKey:@"content"];
|
||||
|
||||
if (willClearPredicate)
|
||||
[self didChangeValueForKey:@"filterPredicate"];
|
||||
|
||||
// TODO: Remove this line when granular notifications are implemented
|
||||
[proxy setAdding:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -819,7 +811,6 @@
|
||||
return;
|
||||
|
||||
var willClearPredicate = NO;
|
||||
|
||||
if (_clearsFilterPredicateOnInsertion && _filterPredicate)
|
||||
{
|
||||
[self willChangeValueForKey:@"filterPredicate"];
|
||||
@@ -857,15 +848,9 @@
|
||||
if ([self avoidsEmptySelection] && [[self selectionIndexes] count] <= 0 && [_contentObject count] > 0)
|
||||
[self __setSelectionIndexes:[CPIndexSet indexSetWithIndex:0]];
|
||||
|
||||
var proxy = [_CPKVOProxy proxyForObject:self];
|
||||
[proxy setAdding:YES];
|
||||
|
||||
[self didChangeValueForKey:@"content"];
|
||||
|
||||
if (willClearPredicate)
|
||||
[self didChangeValueForKey:@"filterPredicate"];
|
||||
|
||||
[proxy setAdding:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -886,7 +871,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.
|
||||
@@ -912,9 +897,7 @@
|
||||
if (![self canAdd])
|
||||
return;
|
||||
|
||||
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject];
|
||||
|
||||
[self addObject:newObject];
|
||||
[self insert:sender];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -926,13 +909,9 @@
|
||||
if (![self canInsert])
|
||||
return;
|
||||
|
||||
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
|
||||
lastSelectedIndex = [_selectionIndexes lastIndex];
|
||||
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject];
|
||||
|
||||
if (lastSelectedIndex !== CPNotFound)
|
||||
[self insertObject:newObject atArrangedObjectIndex:lastSelectedIndex];
|
||||
else
|
||||
[self addObject:newObject];
|
||||
[self addObject:newObject];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -985,7 +964,7 @@
|
||||
// be the 'wrong' one - as in not the one the user selected - but the wrong
|
||||
// one is still just another pointer to the same object, so the user will not
|
||||
// be able to see any difference.
|
||||
var contentIndex = [_contentObject indexOfObjectIdenticalTo:object];
|
||||
contentIndex = [_contentObject indexOfObjectIdenticalTo:object];
|
||||
[_contentObject removeObjectAtIndex:contentIndex];
|
||||
}
|
||||
[arrangedObjects removeObjectAtIndex:anIndex];
|
||||
@@ -1094,12 +1073,12 @@
|
||||
|
||||
@implementation CPArrayController (CPBinder)
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (aBinding == @"contentArray")
|
||||
if (theBinding == @"contentArray")
|
||||
return [_CPArrayControllerContentBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1114,8 +1093,7 @@
|
||||
isCompound = [self handlesContentAsCompoundValue],
|
||||
dotIndex = keyPath.lastIndexOf("."),
|
||||
firstPart = dotIndex !== CPNotFound ? keyPath.substring(0, dotIndex) : nil,
|
||||
isSelectionProxy = firstPart && [[destination valueForKeyPath:firstPart] isKindOfClass:CPControllerSelectionProxy],
|
||||
newValue;
|
||||
isSelectionProxy = firstPart && [[destination valueForKeyPath:firstPart] isKindOfClass:CPControllerSelectionProxy];
|
||||
|
||||
if (!isCompound && !isSelectionProxy)
|
||||
{
|
||||
@@ -1135,7 +1113,6 @@
|
||||
}
|
||||
|
||||
var isPlaceholder = CPIsControllerMarker(newValue);
|
||||
|
||||
if (isPlaceholder)
|
||||
{
|
||||
if (newValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
|
||||
|
||||
+5
-103
@@ -48,8 +48,6 @@ var DefaultLineWidth = 1.0;
|
||||
{
|
||||
CGPath _path;
|
||||
float _lineWidth;
|
||||
CPArray _lineDashes;
|
||||
float _lineDashesPhase;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -63,11 +61,11 @@ var DefaultLineWidth = 1.0;
|
||||
/*!
|
||||
Create a new CPBezierPath object initialized with an oval path drawn within a rectangular path.
|
||||
*/
|
||||
+ (CPBezierPath)bezierPathWithOvalInRect:(CGRect)aRect
|
||||
+ (CPBezierPath)bezierPathWithOvalInRect:(CGRect)rect
|
||||
{
|
||||
var path = [self bezierPath];
|
||||
|
||||
[path appendBezierPathWithOvalInRect:aRect];
|
||||
[path appendBezierPathWithOvalInRect:rect];
|
||||
|
||||
return path;
|
||||
}
|
||||
@@ -75,20 +73,11 @@ var DefaultLineWidth = 1.0;
|
||||
/*!
|
||||
Create a new CPBezierPath object initialized with a rectangular path.
|
||||
*/
|
||||
+ (CPBezierPath)bezierPathWithRect:(CGRect)aRect
|
||||
+ (CPBezierPath)bezierPathWithRect:(CGRect)rect
|
||||
{
|
||||
var path = [self bezierPath];
|
||||
|
||||
[path appendBezierPathWithRect:aRect];
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
+ (CPBezierPath)bezierPathWithRoundedRect:(CGRect)aRect xRadius:(float)xRadius yRadius:(float)yRadius
|
||||
{
|
||||
var path = [self bezierPath];
|
||||
|
||||
[path appendBezierPathWithRoundedRect:aRect xRadius:xRadius yRadius:yRadius];
|
||||
[path appendBezierPathWithRect:rect];
|
||||
|
||||
return path;
|
||||
}
|
||||
@@ -147,8 +136,6 @@ var DefaultLineWidth = 1.0;
|
||||
{
|
||||
_path = CGPathCreateMutable();
|
||||
_lineWidth = [[self class] defaultLineWidth];
|
||||
_lineDashesPhase = 0;
|
||||
_lineDashes = [];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -181,7 +168,7 @@ var DefaultLineWidth = 1.0;
|
||||
- (CGRect)bounds
|
||||
{
|
||||
// TODO: this should return this. The controlPointBounds is not a tight fit.
|
||||
// return CGPathGetBoundingBox(_path);
|
||||
// return CGPathGetPathBoundingBox(_path);
|
||||
|
||||
return [self controlPointBounds];
|
||||
}
|
||||
@@ -209,7 +196,6 @@ var DefaultLineWidth = 1.0;
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextAddPath(ctx, _path);
|
||||
CGContextSetLineWidth(ctx, [self lineWidth]);
|
||||
CGContextSetLineDash(ctx, _lineDashesPhase, _lineDashes);
|
||||
CGContextStrokePath(ctx);
|
||||
}
|
||||
|
||||
@@ -223,50 +209,10 @@ var DefaultLineWidth = 1.0;
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextAddPath(ctx, _path);
|
||||
CGContextSetLineWidth(ctx, [self lineWidth]);
|
||||
CGContextSetLineDash(ctx, _lineDashesPhase, _lineDashes);
|
||||
CGContextClosePath(ctx);
|
||||
CGContextFillPath(ctx);
|
||||
}
|
||||
|
||||
/*!
|
||||
Cocoa compatibility.
|
||||
*/
|
||||
- (void)getLineDash:(CPArrayRef)patternRef count:(CPInteger)count phase:(CGFloatRef)phaseRef
|
||||
{
|
||||
return [self getLineDash:patternRef phase:phaseRef];
|
||||
}
|
||||
|
||||
/*!
|
||||
Retrieve the line dash pattern and phase and write them into the provided references.
|
||||
*/
|
||||
- (void)getLineDash:(CPArrayRef)patternRef phase:(CGFloatRef)phaseRef
|
||||
{
|
||||
if (patternRef)
|
||||
@deref(patternRef) = [_lineDashes copy];
|
||||
if (phaseRef)
|
||||
@deref(phaseRef) = _lineDashesPhase;
|
||||
}
|
||||
|
||||
/*!
|
||||
Cocoa compatibility.
|
||||
*/
|
||||
- (void)setLineDash:(CPArray)aPattern count:(CPInteger)count phase:(CGFloat)aPhase
|
||||
{
|
||||
[self setLineDash:aPattern phase:aPhase];
|
||||
}
|
||||
|
||||
/*!
|
||||
Set stroke line dash pattern.
|
||||
|
||||
@param aPattern an array of stroke-skip lengths such as [2, 2, 4, 4]
|
||||
@param aPhase amount of shift for the starting position of the first stroke
|
||||
*/
|
||||
- (void)setLineDash:(CPArray)aPattern phase:(CGFloat)aPhase
|
||||
{
|
||||
_lineDashes = aPattern;
|
||||
_lineDashesPhase = aPhase;
|
||||
}
|
||||
|
||||
/*!
|
||||
Get the line width.
|
||||
*/
|
||||
@@ -378,47 +324,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
|
||||
|
||||
|
||||
+139
-525
@@ -20,27 +20,22 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@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
|
||||
@typedef CPBorderType
|
||||
CPNoBorder = 0;
|
||||
CPLineBorder = 1;
|
||||
CPBezelBorder = 2;
|
||||
CPGrooveBorder = 3;
|
||||
|
||||
// CPTitlePosition
|
||||
@typedef CPTitlePosition
|
||||
CPNoTitle = 0;
|
||||
CPAboveTop = 1;
|
||||
CPAtTop = 2;
|
||||
@@ -59,51 +54,20 @@ CPBelowBottom = 6;
|
||||
@implementation CPBox : CPView
|
||||
{
|
||||
CPBoxType _boxType;
|
||||
CPBorderType _borderType; // deprecated
|
||||
CPBorderType _borderType;
|
||||
|
||||
CPColor _borderColor;
|
||||
CPColor _fillColor;
|
||||
|
||||
float _cornerRadius;
|
||||
float _borderWidth;
|
||||
|
||||
CPSize _contentMargin;
|
||||
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
|
||||
{
|
||||
if ([aBinding hasPrefix:CPDisplayPatternTitleBinding])
|
||||
return [CPTitleWithPatternBinding class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"box";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"background-color": [CPNull null],
|
||||
@"border-color": [CPNull null],
|
||||
@"border-width": 1.0,
|
||||
@"corner-radius": 3.0,
|
||||
@"inner-shadow-offset": CGSizeMakeZero(),
|
||||
@"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
|
||||
};
|
||||
}
|
||||
|
||||
+ (id)boxEnclosingView:(CPView)aView
|
||||
@@ -121,32 +85,27 @@ CPBelowBottom = 6;
|
||||
return box;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frameRect
|
||||
- (id)initWithFrame:(CPRect)frameRect
|
||||
{
|
||||
self = [super initWithFrame:frameRect];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_borderType = CPGrooveBorder; // Was CPBezelBorder but Cocoa default is CPGrooveBorder
|
||||
_boxType = CPBoxPrimary;
|
||||
_borderType = CPBezelBorder;
|
||||
_fillColor = [CPColor clearColor];
|
||||
_borderColor = [CPColor blackColor];
|
||||
|
||||
_borderWidth = 1.0;
|
||||
_contentMargin = CGSizeMake(0.0, 0.0);
|
||||
|
||||
_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;
|
||||
@@ -159,7 +118,7 @@ CPBelowBottom = 6;
|
||||
|
||||
@return the border rectangle of the box
|
||||
*/
|
||||
- (CGRect)borderRect
|
||||
- (CPRect)borderRect
|
||||
{
|
||||
return [self bounds];
|
||||
}
|
||||
@@ -178,8 +137,6 @@ CPBelowBottom = 6;
|
||||
*/
|
||||
- (CPBorderType)borderType
|
||||
{
|
||||
CPLog.warn("CPBox borderType is deprecated.");
|
||||
|
||||
return _borderType;
|
||||
}
|
||||
|
||||
@@ -202,8 +159,7 @@ CPBelowBottom = 6;
|
||||
return;
|
||||
|
||||
_borderType = aBorderType;
|
||||
|
||||
[self refreshDisplay];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -243,146 +199,67 @@ 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 valueForThemeAttribute:@"border-color"];
|
||||
return _borderColor;
|
||||
}
|
||||
|
||||
- (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]])
|
||||
if ([color isEqual:_borderColor])
|
||||
return;
|
||||
|
||||
[self setValue:color forThemeAttribute:@"border-color"];
|
||||
_borderColor = color;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
// See discussion above.
|
||||
// MARK: borderWidth
|
||||
- (float)borderWidth
|
||||
{
|
||||
return [self valueForThemeAttribute:@"border-width"];
|
||||
return _borderWidth;
|
||||
}
|
||||
|
||||
- (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])
|
||||
if (width === _borderWidth)
|
||||
return;
|
||||
|
||||
[self setValue:width forThemeAttribute:@"border-width"];
|
||||
_borderWidth = width;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
// See discussion above.
|
||||
// MARK: cornerRadius
|
||||
- (float)cornerRadius
|
||||
{
|
||||
return [self valueForThemeAttribute:@"corner-radius"];
|
||||
return _cornerRadius;
|
||||
}
|
||||
|
||||
- (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])
|
||||
if (radius === _cornerRadius)
|
||||
return;
|
||||
|
||||
[self setValue:radius forThemeAttribute:@"corner-radius"];
|
||||
_cornerRadius = radius;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
// See discussion above.
|
||||
// MARK: fillColor
|
||||
- (CPColor)fillColor
|
||||
{
|
||||
return [self valueForThemeAttribute:@"background-color"];
|
||||
return _fillColor;
|
||||
}
|
||||
|
||||
- (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]])
|
||||
if ([color isEqual:_fillColor])
|
||||
return;
|
||||
|
||||
[self setValue:color forThemeAttribute:@"background-color"];
|
||||
_fillColor = color;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (CPView)contentView
|
||||
@@ -395,47 +272,34 @@ CPBelowBottom = 6;
|
||||
if (aView === _contentView)
|
||||
return;
|
||||
|
||||
[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];
|
||||
else
|
||||
[_boxView addSubview:aView];
|
||||
[self replaceSubview:_contentView with:aView];
|
||||
|
||||
_contentView = aView;
|
||||
|
||||
[self sizeToFit];
|
||||
[self refreshDisplay];
|
||||
}
|
||||
|
||||
- (CGSize)contentViewMargins
|
||||
- (CPSize)contentViewMargins
|
||||
{
|
||||
return [self valueForThemeAttribute:@"content-margin"];
|
||||
return _contentMargin;
|
||||
}
|
||||
|
||||
- (void)setContentViewMargins:(CGSize)size
|
||||
- (void)setContentViewMargins:(CPSize)size
|
||||
{
|
||||
if (size.width < 0 || size.height < 0)
|
||||
[CPException raise:CPGenericException reason:@"Margins must be positive"];
|
||||
|
||||
[self setValue:CGSizeMakeCopy(size) forThemeAttribute:@"content-margin"];
|
||||
_contentMargin = CGSizeMakeCopy(size);
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setFrameFromContentFrame:(CGRect)aRect
|
||||
- (void)setFrameFromContentFrame:(CPRect)aRect
|
||||
{
|
||||
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")];
|
||||
var offset = [self _titleHeightOffset];
|
||||
|
||||
[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))];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
@@ -460,85 +324,33 @@ 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.
|
||||
|
||||
This is the Cappuccino equivalent to the `titleCell` method.
|
||||
*/
|
||||
- (CPTextField)titleView
|
||||
{
|
||||
return _titleView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the rectangle in which the receiver’s 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:CPPointMake(5.0, 0.0)];
|
||||
[_titleView setAutoresizingMask:CPViewNotSizable];
|
||||
break;
|
||||
|
||||
@@ -546,84 +358,68 @@ CPBelowBottom = 6;
|
||||
case CPAtBottom:
|
||||
case CPBelowBottom:
|
||||
var h = [_titleView frameSize].height;
|
||||
[_titleView setFrameOrigin:CGPointMake(titleLeftOffset, [self frameSize].height - h - titleTopOffset)];
|
||||
[_titleView setFrameOrigin:CPPointMake(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];
|
||||
|
||||
[_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 setAutoresizingMask:CPViewNotSizable];
|
||||
[self setFrameSize:CGSizeMake(contentFrame.size.width + _contentMargin.width * 2,
|
||||
contentFrame.size.height + _contentMargin.height * 2 + offset[0])];
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[_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];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forKey:(CPString)aKey
|
||||
- (void)drawRect:(CPRect)rect
|
||||
{
|
||||
if (aKey === CPDisplayPatternTitleBinding)
|
||||
[self setTitle:aValue || @""];
|
||||
else
|
||||
[super setValue:aValue forKey:aKey];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
|
||||
if (_borderType === CPNoBorder)
|
||||
return;
|
||||
|
||||
var bounds = [self bounds];
|
||||
var bounds = CGRectMakeCopy([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 _drawVerticalSeperatorInRect:bounds];
|
||||
else if (CGRectGetHeight(bounds) === 5.0)
|
||||
return [self _drawHorizontalSeperatorInRect:bounds];
|
||||
|
||||
if (_transparent)
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_titlePosition == CPAtTop)
|
||||
{
|
||||
@@ -635,209 +431,84 @@ CPBelowBottom = 6;
|
||||
bounds.size.height -= [_titleView frameSize].height;
|
||||
}
|
||||
|
||||
// Primary or secondary type boxes always draw the same way, unless they are CPNoBorder.
|
||||
if ((_boxType === CPBoxPrimary || _boxType === CPBoxSecondary) && _borderType !== CPNoBorder)
|
||||
{
|
||||
[self _drawPrimaryBorderInRect:bounds];
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_borderType)
|
||||
{
|
||||
case CPBezelBorder:
|
||||
case CPGrooveBorder:
|
||||
case CPLineBorder:
|
||||
[self _drawLineBorderInRect:bounds];
|
||||
[self _drawBezelBorderInRect:bounds];
|
||||
break;
|
||||
|
||||
case CPNoBorder:
|
||||
[self _drawNoBorderInRect:bounds];
|
||||
default:
|
||||
case CPLineBorder:
|
||||
[self _drawLineBorderInRect:bounds];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_drawHorizontalSeparatorInRect:(CGRect)aRect
|
||||
- (void)_drawHorizontalSeperatorInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
CGContextSetLineWidth(context, 1.0);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(aRect), CGRectGetMidY(aRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetWidth(aRect), CGRectGetMidY(aRect));
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(aRect), CGRectGetMinY(aRect) + 0.5);
|
||||
CGContextAddLineToPoint(context, CGRectGetWidth(aRect), CGRectGetMinY(aRect) + 0.5);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void)_drawVerticalSeparatorInRect:(CGRect)aRect
|
||||
- (void)_drawVerticalSeperatorInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
CGContextSetLineWidth(context, 1.0);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMidX(aRect), CGRectGetMinY(aRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMidX(aRect), CGRectGetHeight(aRect));
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(aRect) + 0.5, CGRectGetMinY(aRect));
|
||||
CGContextAddLineToPoint(context, CGRectGetMinX(aRect) + 0.5, CGRectGetHeight(aRect));
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void)_drawLineBorderInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
cornerRadius = [self cornerRadius],
|
||||
borderWidth = [self borderWidth];
|
||||
|
||||
aRect = CGRectInset(aRect, borderWidth / 2.0, borderWidth / 2.0);
|
||||
|
||||
CGContextSetFillColor(context, [self fillColor]);
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
|
||||
CGContextSetLineWidth(context, borderWidth);
|
||||
CGContextFillRoundedRectangleInRect(context, aRect, cornerRadius, YES, YES, YES, YES);
|
||||
CGContextStrokeRoundedRectangleInRect(context, aRect, cornerRadius, YES, YES, YES, YES);
|
||||
}
|
||||
|
||||
- (void)_drawBezelBorderInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
cornerRadius = [self cornerRadius],
|
||||
borderWidth = [self borderWidth],
|
||||
shadowOffset = [self valueForThemeAttribute:@"inner-shadow-offset"],
|
||||
shadowSize = [self valueForThemeAttribute:@"inner-shadow-size"],
|
||||
shadowColor = [self valueForThemeAttribute:@"inner-shadow-color"];
|
||||
sides = [CPMinYEdge, CPMaxXEdge, CPMaxYEdge, CPMinXEdge],
|
||||
sideGray = 190.0 / 255.0,
|
||||
grays = [142.0 / 255.0, sideGray, sideGray, sideGray],
|
||||
borderWidth = _borderWidth;
|
||||
|
||||
var baseRect = aRect;
|
||||
aRect = CGRectInset(aRect, borderWidth / 2.0, borderWidth / 2.0);
|
||||
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
CGContextSetLineWidth(context, borderWidth);
|
||||
CGContextSetFillColor(context, [self fillColor]);
|
||||
CGContextFillRoundedRectangleInRect(context, aRect, cornerRadius, YES, YES, YES, YES);
|
||||
CGContextStrokeRoundedRectangleInRect(context, aRect, cornerRadius, YES, YES, YES, YES);
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)_drawPrimaryBorderInRect:(CGRect)aRect
|
||||
{
|
||||
// Draw the "primary" style CPBox.
|
||||
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
cornerRadius = [self cornerRadius],
|
||||
borderWidth = [self borderWidth],
|
||||
shadowOffset = [self valueForThemeAttribute:@"inner-shadow-offset"],
|
||||
shadowSize = [self valueForThemeAttribute:@"inner-shadow-size"],
|
||||
shadowColor = [self valueForThemeAttribute:@"inner-shadow-color"],
|
||||
baseRect = aRect;
|
||||
|
||||
aRect = CGRectInset(aRect, borderWidth / 2.0, borderWidth / 2.0);
|
||||
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
CGContextSetLineWidth(context, borderWidth);
|
||||
CGContextSetFillColor(context, [self fillColor]);
|
||||
CGContextFillRoundedRectangleInRect(context, aRect, cornerRadius, YES, YES, YES, YES);
|
||||
|
||||
CGContextBeginPath(context);
|
||||
// Note we can't use the 0.5 inset rectangle when setting up clipping. The clipping has to be
|
||||
// on integer coordinates for this to look right in Chrome.
|
||||
CGContextAddPath(context, CGPathWithRoundedRectangleInRect(baseRect, cornerRadius, cornerRadius, YES, YES, YES, YES));
|
||||
CGContextClip(context);
|
||||
CGContextSetShadowWithColor(context, shadowOffset, shadowSize, shadowColor);
|
||||
CGContextStrokeRoundedRectangleInRect(context, aRect, cornerRadius, YES, YES, YES, YES);
|
||||
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)_drawNoBorderInRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
while (borderWidth--)
|
||||
aRect = CPDrawTiledRects(aRect, aRect, sides, grays);
|
||||
|
||||
CGContextSetFillColor(context, [self fillColor]);
|
||||
CGContextFillRect(context, aRect);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
@implementation CPBox (CSSTheming)
|
||||
|
||||
- (void)layoutSubviews
|
||||
- (void)_drawLineBorderInRect:(CGRect)aRect
|
||||
{
|
||||
if (![self isCSSBased] || (_boxType === CPBoxCustom))
|
||||
return;
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
var bounds = [self bounds];
|
||||
aRect = CGRectInset(aRect, _borderWidth / 2.0, _borderWidth / 2.0);
|
||||
|
||||
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)];
|
||||
}
|
||||
CGContextSetFillColor(context, [self fillColor]);
|
||||
CGContextSetStrokeColor(context, [self borderColor]);
|
||||
|
||||
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"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;
|
||||
CGContextSetLineWidth(context, _borderWidth);
|
||||
CGContextFillRoundedRectangleInRect(context, aRect, _cornerRadius, YES, YES, YES, YES);
|
||||
CGContextStrokeRoundedRectangleInRect(context, aRect, _cornerRadius, YES, YES, YES, YES);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
var CPBoxTypeKey = @"CPBoxTypeKey",
|
||||
CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
|
||||
CPBoxTitleKey = @"CPBoxTitleKey",
|
||||
CPBoxTitlePositionKey = @"CPBoxTitlePositionKey",
|
||||
CPBoxTitleViewKey = @"CPBoxTitleViewKey",
|
||||
CPBoxContentViewKey = @"CPBoxContentViewKey",
|
||||
CPBoxBoxViewKey = @"CPBoxBoxViewKey";
|
||||
CPBoxBorderColorKey = @"CPBoxBorderColorKey",
|
||||
CPBoxFillColorKey = @"CPBoxFillColorKey",
|
||||
CPBoxCornerRadiusKey = @"CPBoxCornerRadiusKey",
|
||||
CPBoxBorderWidthKey = @"CPBoxBorderWidthKey",
|
||||
CPBoxContentMarginKey = @"CPBoxContentMarginKey",
|
||||
CPBoxTitle = @"CPBoxTitle",
|
||||
CPBoxTitlePosition = @"CPBoxTitlePosition",
|
||||
CPBoxTitleView = @"CPBoxTitleView";
|
||||
|
||||
@implementation CPBox (CPCoding)
|
||||
|
||||
@@ -850,56 +521,24 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
|
||||
_boxType = [aCoder decodeIntForKey:CPBoxTypeKey];
|
||||
_borderType = [aCoder decodeIntForKey:CPBoxBorderTypeKey];
|
||||
|
||||
_title = [aCoder decodeObjectForKey:CPBoxTitleKey];
|
||||
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePositionKey];
|
||||
_borderColor = [aCoder decodeObjectForKey:CPBoxBorderColorKey];
|
||||
_fillColor = [aCoder decodeObjectForKey:CPBoxFillColorKey];
|
||||
|
||||
// Important : see comment on encodeWithCoder below
|
||||
_cornerRadius = [aCoder decodeFloatForKey:CPBoxCornerRadiusKey];
|
||||
_borderWidth = [aCoder decodeFloatForKey:CPBoxBorderWidthKey];
|
||||
|
||||
_boxView = [aCoder decodeObjectForKey:CPBoxBoxViewKey];
|
||||
_contentMargin = [aCoder decodeSizeForKey:CPBoxContentMarginKey];
|
||||
|
||||
if (!_boxView)
|
||||
{
|
||||
// We're coming from nib2cib.
|
||||
_title = [aCoder decodeObjectForKey:CPBoxTitle];
|
||||
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePosition];
|
||||
_titleView = [aCoder decodeObjectForKey:CPBoxTitleView] || [CPTextField labelWithTitle:_title];
|
||||
|
||||
_boxView = [[CPView alloc] initWithFrame:[self bounds]];
|
||||
_titleView = [CPTextField labelWithTitle:_title];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We're coming from elsewhere
|
||||
|
||||
_titleView = [aCoder decodeObjectForKey:CPBoxTitleViewKey];
|
||||
}
|
||||
|
||||
_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;
|
||||
_contentView = [self subviews][0];
|
||||
|
||||
[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 +546,22 @@ 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:_borderColor forKey:CPBoxBorderColorKey];
|
||||
[aCoder encodeObject:_fillColor forKey:CPBoxFillColorKey];
|
||||
|
||||
[aCoder encodeFloat:_cornerRadius forKey:CPBoxCornerRadiusKey];
|
||||
[aCoder encodeFloat:_borderWidth forKey:CPBoxBorderWidthKey];
|
||||
|
||||
[aCoder encodeObject:_title forKey:CPBoxTitle];
|
||||
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePosition];
|
||||
[aCoder encodeObject:_titleView forKey:CPBoxTitleView];
|
||||
|
||||
[aCoder encodeSize:_contentMargin forKey:CPBoxContentMarginKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+136
-313
@@ -20,109 +20,63 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPIndexSet.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPScrollView.j"
|
||||
@import "CPTableView.j"
|
||||
@import "CPTextField.j"
|
||||
|
||||
@global CPApp
|
||||
|
||||
@protocol CPBrowserDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (BOOL)browser:(CPBrowser)browser acceptDrop:(id)info atRow:(CPInteger)row column:(CPInteger)column dropOperation:(CPTableViewDropOperation)dropOperation;
|
||||
- (BOOL)browser:(CPBrowser)browser canDragRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent )event;
|
||||
- (BOOL)browser:(CPBrowser)browser isLeafItem:(id)item;
|
||||
- (BOOL)browser:(CPBrowser)browser shouldSelectRowIndexes:(CPIndexSet)anIndexSet inColumn:(CPInteger)column;
|
||||
- (BOOL)browser:(CPBrowser)browser writeRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column toPasteboard:(CPPasteboard)pasteboard;
|
||||
- (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;
|
||||
- (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;
|
||||
- (id)browser:(CPBrowser)browser child:(CPInteger)index ofItem:(id)item;
|
||||
- (id)browser:(CPBrowser)browser objectValueForItem:(id)item;
|
||||
- (id)rootItemForBrowser:(CPBrowser)browser;
|
||||
- (void)browser:(CPBrowser)browser didChangeLastColumn:(CPInteger)oldLastColumn toColumn:(CPInteger)column;
|
||||
- (void)browser:(CPBrowser)browser didResizeColumn:(CPInteger)column;
|
||||
- (void)browserSelectionIsChanging:(CPBrowser)browser;
|
||||
- (void)browserSelectionDidChange:(CPBrowser)browser;
|
||||
|
||||
@end
|
||||
|
||||
var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_ = 1 << 1,
|
||||
CPBrowserDelegate_browser_canDragRowsWithIndexes_inColumn_withEvent_ = 1 << 2,
|
||||
CPBrowserDelegate_browser_isLeafItem_ = 1 << 3,
|
||||
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;
|
||||
@import "CPScrollView.j"
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPBrowser
|
||||
*/
|
||||
|
||||
@implementation CPBrowser : CPControl
|
||||
{
|
||||
id <CPBrowserDelegate> _delegate;
|
||||
CPString _pathSeparator;
|
||||
unsigned _implementedDelegateMethods;
|
||||
id _delegate;
|
||||
CPString _pathSeparator;
|
||||
|
||||
CPView _contentView;
|
||||
CPScrollView _horizontalScrollView;
|
||||
CPView _prototypeView;
|
||||
CPView _contentView;
|
||||
CPScrollView _horizontalScrollView;
|
||||
CPView _prototypeView;
|
||||
|
||||
CPArray _tableViews;
|
||||
CPArray _tableDelegates;
|
||||
CPArray _tableViews;
|
||||
CPArray _tableDelegates;
|
||||
|
||||
id _rootItem;
|
||||
id _rootItem;
|
||||
|
||||
BOOL _delegateSupportsImages;
|
||||
BOOL _delegateSupportsImages;
|
||||
|
||||
SEL _doubleAction @accessors(property=doubleAction);
|
||||
SEL _doubleAction @accessors(property=doubleAction);
|
||||
|
||||
BOOL _allowsMultipleSelection;
|
||||
BOOL _allowsEmptySelection;
|
||||
BOOL _allowsMultipleSelection;
|
||||
BOOL _allowsEmptySelection;
|
||||
|
||||
Class _tableViewClass @accessors(property=tableViewClass);
|
||||
Class _tableViewClass @accessors(property=tableViewClass);
|
||||
|
||||
float _rowHeight;
|
||||
float _imageWidth;
|
||||
float _leafWidth;
|
||||
float _minColumnWidth;
|
||||
float _defaultColumnWidth @accessors(property=defaultColumnWidth);
|
||||
float _rowHeight;
|
||||
float _imageWidth;
|
||||
float _leafWidth;
|
||||
float _minColumnWidth;
|
||||
float _defaultColumnWidth @accessors(property=defaultColumnWidth);
|
||||
|
||||
CPArray _columnWidths;
|
||||
CPArray _columnWidths;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
+ (CPImage)branchImage
|
||||
{
|
||||
return "browser";
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPBrowser class]]
|
||||
pathForResource:"browser-leaf.png"]
|
||||
size:CGSizeMake(9,9)];
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
+ (CPImage)highlightedBranchImage
|
||||
{
|
||||
return @{
|
||||
@"image-control-resize": [CPNull null],
|
||||
@"image-control-leaf": [CPNull null],
|
||||
@"image-control-leaf-pressed": [CPNull null]
|
||||
};
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPBrowser class]]
|
||||
pathForResource:"browser-leaf-highlighted.png"]
|
||||
size:CGSizeMake(9,9)];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -182,71 +136,10 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
[CPKeyedArchiver archivedDataWithRootObject:_prototypeView]];
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id <CPBrowserDelegate>)anObject
|
||||
- (void)setDelegate:(id)anObject
|
||||
{
|
||||
if (_delegate === anObject)
|
||||
return;
|
||||
|
||||
_delegate = anObject;
|
||||
_implementedDelegateMethods = 0;
|
||||
_delegateSupportsImages = NO;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:acceptDrop:atRow:column:dropOperation:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:canDragRowsWithIndexes:inColumn:withEvent:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_canDragRowsWithIndexes_inColumn_withEvent_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:isLeafItem:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_isLeafItem_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:shouldSelectRowIndexes:inColumn:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_shouldSelectRowIndexes_inColumn_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:writeRowsWithIndexes:inColumn:toPasteboard:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_writeRowsWithIndexes_inColumn_toPasteboard_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:validateDrop:proposedRow:column:dropOperation:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:draggingImageForRowsWithIndexes:inColumn:withEvent:offset:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:imageValueForItem:)])
|
||||
{
|
||||
_delegateSupportsImages = YES;
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_imageValueForItem_;
|
||||
}
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:selectionIndexesForProposedSelection:inColumn:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_selectionIndexesForProposedSelection_inColumn_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:numberOfChildrenOfItem:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_numberOfChildrenOfItem_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:draggingViewForRowsWithIndexes:inColumn:withEvent:offset:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_draggingViewForRowsWithIndexes_inColumn_withEvent_offset_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:child:ofItem:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_child_ofItem_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:objectValueForItem:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_objectValueForItem_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(rootItemForBrowser:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_rootItemForBrowser_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:didChangeLastColumn:toColumn:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_didChangeLastColumn_toColumn_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browser:didChangeLastColumn:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browser_didResizeColumn_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browserSelectionIsChanging:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browserSelectionIsChanging_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(browserSelectionDidChange:)])
|
||||
_implementedDelegateMethods |= CPBrowserDelegate_browserSelectionDidChange_;
|
||||
_delegateSupportsImages = [_delegate respondsToSelector:@selector(browser:imageValueForItem:)];
|
||||
|
||||
[self loadColumnZero];
|
||||
}
|
||||
@@ -268,13 +161,16 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
|
||||
- (void)loadColumnZero
|
||||
{
|
||||
_rootItem = [self _sendDelegateRootItemForBrowser];
|
||||
if ([_delegate respondsToSelector:@selector(rootItemForBrowser:)])
|
||||
_rootItem = [_delegate rootItemForBrowser:self];
|
||||
else
|
||||
_rootItem = nil;
|
||||
|
||||
[self setLastColumn:-1];
|
||||
[self addColumn];
|
||||
}
|
||||
|
||||
- (void)setLastColumn:(CPInteger)columnIndex
|
||||
- (void)setLastColumn:(int)columnIndex
|
||||
{
|
||||
if (columnIndex >= _tableViews.length)
|
||||
return;
|
||||
@@ -282,18 +178,14 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
var oldValue = _tableViews.length - 1,
|
||||
indexPlusOne = columnIndex + 1; // unloads all later columns.
|
||||
|
||||
if (columnIndex > 0)
|
||||
[_tableViews[columnIndex - 1] setNeedsDisplay:YES];
|
||||
|
||||
[_tableViews[columnIndex] setNeedsDisplay:YES];
|
||||
|
||||
[[_tableViews.slice(indexPlusOne) valueForKey:"enclosingScrollView"]
|
||||
makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
|
||||
_tableViews = _tableViews.slice(0, indexPlusOne);
|
||||
_tableDelegates = _tableDelegates.slice(0, indexPlusOne);
|
||||
|
||||
[self _sendDelegateBrowserDidChangeLastColumn:oldValue toColumn:columnIndex];
|
||||
if ([_delegate respondsToSelector:@selector(browser:didChangeLastColumn:toColumn:)])
|
||||
[_delegate browser:self didChangeLastColumn:oldValue toColumn:columnIndex];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
@@ -384,8 +276,8 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
var column = [[CPTableColumn alloc] initWithIdentifier:@"Leaf"],
|
||||
view = [[_CPBrowserLeafView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[view setBranchImage:[self valueForThemeAttribute:@"image-control-leaf"]];
|
||||
[view setHighlightedBranchImage:[self valueForThemeAttribute:@"image-control-leaf-pressed"]];
|
||||
[view setBranchImage:[[self class] branchImage]];
|
||||
[view setHighlightedBranchImage:[[self class] highlightedBranchImage]];
|
||||
|
||||
[column setDataView:view];
|
||||
[column setResizingMask:CPTableColumnNoResizing];
|
||||
@@ -393,7 +285,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
[aTableView addTableColumn:column];
|
||||
}
|
||||
|
||||
- (void)reloadColumn:(CPInteger)column
|
||||
- (void)reloadColumn:(int)column
|
||||
{
|
||||
[[self tableViewInColumn:column] reloadData];
|
||||
}
|
||||
@@ -461,19 +353,19 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
|
||||
// ITEMS
|
||||
|
||||
- (id)itemAtRow:(CPInteger)row inColumn:(CPInteger)column
|
||||
- (id)itemAtRow:(int)row inColumn:(int)column
|
||||
{
|
||||
return [_tableDelegates[column] childAtIndex:row] || nil;
|
||||
return [_tableDelegates[column] childAtIndex:row];
|
||||
}
|
||||
|
||||
- (BOOL)isLeafItem:(id)item
|
||||
{
|
||||
return (_implementedDelegateMethods & CPBrowserDelegate_browser_isLeafItem_) && [_delegate browser:self isLeafItem:item];
|
||||
return [_delegate respondsToSelector:@selector(browser:isLeafItem:)] && [_delegate browser:self isLeafItem:item];
|
||||
}
|
||||
|
||||
- (id)parentForItemsInColumn:(CPInteger)column
|
||||
- (id)parentForItemsInColumn:(int)column
|
||||
{
|
||||
return [_tableDelegates[column] _item] || nil;
|
||||
return [_tableDelegates[column] _item];
|
||||
}
|
||||
|
||||
- (CPSet)selectedItems
|
||||
@@ -533,26 +425,11 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
var key = [anEvent charactersIgnoringModifiers],
|
||||
column = [self selectedColumn];
|
||||
|
||||
if (column === CPNotFound)
|
||||
var column = [self selectedColumn];
|
||||
if (column === -1)
|
||||
return;
|
||||
|
||||
if (key === CPLeftArrowFunctionKey || key === CPRightArrowFunctionKey)
|
||||
{
|
||||
if (key === CPLeftArrowFunctionKey)
|
||||
{
|
||||
var previousColumn = column - 1,
|
||||
selectedRow = [self selectedRowInColumn:previousColumn];
|
||||
|
||||
[self selectRow:selectedRow inColumn:previousColumn];
|
||||
}
|
||||
else
|
||||
[self selectRow:0 inColumn:column + 1];
|
||||
}
|
||||
else
|
||||
[_tableViews[column] keyDown:anEvent];
|
||||
[_tableViews[column] keyDown:anEvent];
|
||||
}
|
||||
|
||||
// SIZING
|
||||
@@ -595,7 +472,9 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
{
|
||||
_columnWidths[column] = aWidth;
|
||||
|
||||
[self _sendDelegateBrowserDidResizeColumn:column];
|
||||
if ([_delegate respondsToSelector:@selector(browser:didResizeColumn:)])
|
||||
[_delegate browser:self didResizeColumn:column];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
@@ -710,12 +589,15 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
if (column < 0 || column > [self lastColumn] + 1)
|
||||
return;
|
||||
|
||||
indexSet = [self _sendDelegateBrowserSelectionIndexesForProposedSelection:indexSet inColumn:column];
|
||||
if ([_delegate respondsToSelector:@selector(browser:selectionIndexesForProposedSelection:inColumn:)])
|
||||
indexSet = [_delegate browser:self selectionIndexesForProposedSelection:indexSet inColumn:column];
|
||||
|
||||
if (![self _sendDelegateBrowserShouldSelectRowIndexes:indexSet inColumn:column])
|
||||
if ([_delegate respondsToSelector:@selector(browser:shouldSelectRowIndexes:inColumn:)] &&
|
||||
![_delegate browser:self shouldSelectRowIndexes:indexSet inColumn:column])
|
||||
return;
|
||||
|
||||
[self _sendDelegateBrowserSelectionIsChanging];
|
||||
if ([_delegate respondsToSelector:@selector(browserSelectionIsChanging:)])
|
||||
[_delegate browserSelectionIsChanging:self];
|
||||
|
||||
if (column > [self lastColumn])
|
||||
[self addColumn];
|
||||
@@ -726,7 +608,8 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
|
||||
[self scrollColumnToVisible:column];
|
||||
|
||||
[self _sendDelegateBrowserSelectionDidChange];
|
||||
if ([_delegate respondsToSelector:@selector(browserSelectionDidChange:)])
|
||||
[_delegate browserSelectionDidChange:self];
|
||||
}
|
||||
|
||||
- (void)setBackgroundColor:(CPColor)aColor
|
||||
@@ -748,6 +631,30 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
[_tableViews makeObjectsPerformSelector:@selector(registerForDraggedTypes:) withObject:types];
|
||||
}
|
||||
|
||||
- (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(browser:canDragRowsWithIndexes:inColumn:withEvent:)])
|
||||
return [_delegate browser:self canDragRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPImage)draggingImageForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(browser:draggingImageForRowsWithIndexes:inColumn:withEvent:offset:)])
|
||||
return [_delegate browser:self draggingImageForRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent offset:dragImageOffset];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CPView)draggingViewForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(browser:draggingViewForRowsWithIndexes:inColumn:withEvent:offset:)])
|
||||
return [_delegate browser:self draggingViewForRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent offset:dragImageOffset];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPBrowser (CPCoding)
|
||||
@@ -797,6 +704,8 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
@end
|
||||
|
||||
|
||||
var _CPBrowserResizeControlBackgroundImage = nil;
|
||||
|
||||
@implementation _CPBrowserResizeControl : CPView
|
||||
{
|
||||
CGPoint _mouseDownX;
|
||||
@@ -805,14 +714,22 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
unsigned _width;
|
||||
}
|
||||
|
||||
+ (CPImage)backgroundImage
|
||||
{
|
||||
if (!_CPBrowserResizeControlBackgroundImage)
|
||||
{
|
||||
var path = [[CPBundle bundleForClass:[self class]] pathForResource:"browser-resize-control.png"];
|
||||
_CPBrowserResizeControlBackgroundImage = [[CPImage alloc] initWithContentsOfFile:path
|
||||
size:CGSizeMake(15, 14)];
|
||||
}
|
||||
|
||||
return _CPBrowserResizeControlBackgroundImage;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
var browser = [[CPBrowser alloc] init];
|
||||
[self setBackgroundColor:[CPColor colorWithPatternImage:[browser valueForThemeAttribute:@"image-control-resize"]]];
|
||||
}
|
||||
|
||||
[self setBackgroundColor:[CPColor colorWithPatternImage:[[self class] backgroundImage]]];
|
||||
|
||||
return self;
|
||||
}
|
||||
@@ -843,7 +760,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
CPBrowser _browser @accessors;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
- (void)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
@@ -897,26 +814,18 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
return _browser;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (BOOL)_isFocused
|
||||
{
|
||||
return ([super _isFocused] || [_browser tableViewInColumn:[_browser selectedColumn]] === self);
|
||||
}
|
||||
|
||||
- (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes atPoint:(CGPoint)mouseDownPoint
|
||||
{
|
||||
return [_browser canDragRowsWithIndexes:rowIndexes inColumn:[_browser columnOfTableView:self] withEvent:[CPApp currentEvent]];
|
||||
}
|
||||
|
||||
- (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
|
||||
- (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CPPointPointer)dragImageOffset
|
||||
{
|
||||
return [_browser draggingImageForRowsWithIndexes:dragRows inColumn:[_browser columnOfTableView:self] withEvent:dragEvent offset:dragImageOffset] ||
|
||||
[super dragImageForRowsWithIndexes:dragRows tableColumns:theTableColumns event:dragEvent offset:dragImageOffset];
|
||||
}
|
||||
|
||||
- (CPView)dragViewForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CGPoint)dragViewOffset
|
||||
- (CPView)dragViewForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CPPoint)dragViewOffset
|
||||
{
|
||||
var count = theTableColumns.length;
|
||||
while (count--)
|
||||
@@ -929,9 +838,35 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
[super dragViewForRowsWithIndexes:dragRows tableColumns:theTableColumns event:dragEvent offset:dragViewOffset];
|
||||
}
|
||||
|
||||
- (void)moveUp:(id)sender
|
||||
{
|
||||
[super moveUp:sender];
|
||||
[_browser selectRow:[self selectedRow] inColumn:[_browser selectedColumn]];
|
||||
}
|
||||
|
||||
- (void)moveDown:(id)sender
|
||||
{
|
||||
[super moveDown:sender];
|
||||
[_browser selectRow:[self selectedRow] inColumn:[_browser selectedColumn]];
|
||||
}
|
||||
|
||||
- (void)moveLeft:(id)sender
|
||||
{
|
||||
var previousColumn = [_browser selectedColumn] - 1,
|
||||
selectedRow = [_browser selectedRowInColumn:previousColumn];
|
||||
|
||||
[_browser selectRow:selectedRow inColumn:previousColumn];
|
||||
}
|
||||
|
||||
- (void)moveRight:(id)sender
|
||||
{
|
||||
[_browser selectRow:0 inColumn:[_browser selectedColumn] + 1];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@implementation _CPBrowserTableDelegate : CPObject
|
||||
{
|
||||
CPBrowser _browser @accessors;
|
||||
@@ -972,22 +907,22 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
[_browser selectRowIndexes:selectedIndexes inColumn:_index];
|
||||
}
|
||||
|
||||
- (id)childAtIndex:(CPUInteger)index
|
||||
- (id)childAtIndex:(unsigned)index
|
||||
{
|
||||
return [_delegate browser:_browser child:index ofItem:_item];
|
||||
}
|
||||
|
||||
- (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id)info row:(CPInteger)row dropOperation:(CPTableViewDropOperation)operation
|
||||
- (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id)info row:(int)row dropOperation:(CPTableViewDropOperation)operation
|
||||
{
|
||||
if (_browser._implementedDelegateMethods & CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_)
|
||||
if ([_delegate respondsToSelector:@selector(browser:acceptDrop:atRow:column:dropOperation:)])
|
||||
return [_delegate browser:_browser acceptDrop:info atRow:row column:_index dropOperation:operation];
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id)info proposedRow:(CPInteger)row proposedDropOperation:(CPTableViewDropOperation)operation
|
||||
- (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id)info proposedRow:(int)row proposedDropOperation:(CPTableViewDropOperation)operation
|
||||
{
|
||||
if (_browser._implementedDelegateMethods & CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_)
|
||||
if ([_delegate respondsToSelector:@selector(browser:validateDrop:proposedRow:column:dropOperation:)])
|
||||
return [_delegate browser:_browser validateDrop:info proposedRow:row column:_index dropOperation:operation];
|
||||
else
|
||||
return CPDragOperationNone;
|
||||
@@ -995,7 +930,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
|
||||
- (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard
|
||||
{
|
||||
if (_browser._implementedDelegateMethods & CPBrowserDelegate_browser_writeRowsWithIndexes_inColumn_toPasteboard_)
|
||||
if ([_delegate respondsToSelector:@selector(browser:writeRowsWithIndexes:inColumn:toPasteboard:)])
|
||||
return [_delegate browser:_browser writeRowsWithIndexes:rowIndexes inColumn:_index toPasteboard:pboard];
|
||||
else
|
||||
return NO;
|
||||
@@ -1050,7 +985,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
var imageView = [self layoutEphemeralSubviewNamed:@"image-view"
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:nil],
|
||||
isHighlighted = [self hasThemeState:CPThemeStateSelectedDataView];
|
||||
isHighlighted = [self themeState] & CPThemeStateSelectedDataView;
|
||||
|
||||
[imageView setImage: _isLeaf ? (isHighlighted ? _highlightedBranchImage : _branchImage) : nil];
|
||||
[imageView setImageScaling:CPImageScaleNone];
|
||||
@@ -1065,7 +1000,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
[aCoder encodeObject:_highlightedBranchImage forKey:"_CPBrowserLeafViewHighlightedBranchImageKey"];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
- (void)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
@@ -1078,115 +1013,3 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPBrowser (CPBrowserDelegate)
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate rootItemForBrowser:
|
||||
*/
|
||||
- (id)_sendDelegateRootItemForBrowser
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPBrowserDelegate_rootItemForBrowser_))
|
||||
return nil;
|
||||
|
||||
return [_delegate rootItemForBrowser:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate browser:didChangeLastColumn:toColumn:
|
||||
*/
|
||||
- (void)_sendDelegateBrowserDidChangeLastColumn:(CPInteger)lastColumn toColumn:(CPInteger)newColumn
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPBrowserDelegate_browser_didChangeLastColumn_toColumn_))
|
||||
return;
|
||||
|
||||
[_delegate browser:self didChangeLastColumn:lastColumn toColumn:newColumn];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate browser:didResizeColumn:
|
||||
*/
|
||||
- (void)_sendDelegateBrowserDidResizeColumn:(CPInteger)column
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPBrowserDelegate_browser_didResizeColumn_))
|
||||
return;
|
||||
|
||||
[_delegate browser:self didResizeColumn:column];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate browserSelectionIsChanging:
|
||||
*/
|
||||
- (void)_sendDelegateBrowserSelectionIsChanging
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPBrowserDelegate_browserSelectionIsChanging_))
|
||||
return;
|
||||
|
||||
[_delegate browserSelectionIsChanging:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate browser:shouldSelectRowIndexes:inColumn:
|
||||
*/
|
||||
- (BOOL)_sendDelegateBrowserShouldSelectRowIndexes:(CPIndexSet)anIndexSet inColumn:(CPInteger)aColumn
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPBrowserDelegate_browser_shouldSelectRowIndexes_inColumn_))
|
||||
return YES;
|
||||
|
||||
return [_delegate browser:self shouldSelectRowIndexes:anIndexSet inColumn:aColumn];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate browser:selectionIndexesForProposedSelection:inColumn:
|
||||
*/
|
||||
- (CPIndexSet)_sendDelegateBrowserSelectionIndexesForProposedSelection:(CPIndexSet)anIndexSet inColumn:(CPInteger)aColumn
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPBrowserDelegate_browser_selectionIndexesForProposedSelection_inColumn_))
|
||||
return anIndexSet;
|
||||
|
||||
return [_delegate browser:self selectionIndexesForProposedSelection:anIndexSet inColumn:aColumn];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate browserSelectionDidChange
|
||||
*/
|
||||
- (void)_sendDelegateBrowserSelectionDidChange
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPBrowserDelegate_browserSelectionDidChange_))
|
||||
return;
|
||||
|
||||
[_delegate browserSelectionDidChange:self];
|
||||
}
|
||||
|
||||
- (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)columnIndex withEvent:(CPEvent)dragEvent
|
||||
{
|
||||
if (_implementedDelegateMethods & CPBrowserDelegate_browser_canDragRowsWithIndexes_inColumn_withEvent_)
|
||||
return [_delegate browser:self canDragRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPImage)draggingImageForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)columnIndex withEvent:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
|
||||
{
|
||||
if (_implementedDelegateMethods & CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_)
|
||||
return [_delegate browser:self draggingImageForRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent offset:dragImageOffset];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (CPView)draggingViewForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)columnIndex withEvent:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
|
||||
{
|
||||
if (_implementedDelegateMethods & CPBrowserDelegate_browser_draggingViewForRowsWithIndexes_inColumn_withEvent_offset_)
|
||||
return [_delegate browser:self draggingViewForRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent offset:dragImageOffset];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+213
-330
@@ -25,11 +25,11 @@
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPStringDrawing.j"
|
||||
@import "CPText.j"
|
||||
@import "CPWindow_Constants.j"
|
||||
|
||||
|
||||
/* @group CPBezelStyle */
|
||||
@typedef CPBezelStyle
|
||||
|
||||
// IB style
|
||||
CPRoundedBezelStyle = 1; // Push
|
||||
CPRegularSquareBezelStyle = 2; // Bevel
|
||||
CPThickSquareBezelStyle = 3;
|
||||
@@ -44,12 +44,10 @@ CPTexturedRoundedBezelStyle = 11; // Round Textured
|
||||
CPRoundRectBezelStyle = 12; // Round Rect
|
||||
CPRecessedBezelStyle = 13; // Recessed
|
||||
CPRoundedDisclosureBezelStyle = 14; // Disclosure
|
||||
CPInlineBezelStyle = 15; // Inline
|
||||
CPHUDBezelStyle = -1;
|
||||
|
||||
|
||||
/* @group CPButtonType */
|
||||
@typedef CPButtonType
|
||||
CPMomentaryLightButton = 0;
|
||||
CPPushOnPushOffButton = 1;
|
||||
CPToggleButton = 2;
|
||||
@@ -61,11 +59,11 @@ CPMomentaryPushInButton = 7;
|
||||
CPMomentaryPushButton = 0;
|
||||
CPMomentaryLight = 7;
|
||||
|
||||
CPNoButtonMask = 0;
|
||||
CPContentsButtonMask = 1;
|
||||
CPPushInButtonMask = 2;
|
||||
CPGrayButtonMask = 4;
|
||||
CPBackgroundButtonMask = 8;
|
||||
CPNoButtonMask = 0;
|
||||
CPContentsButtonMask = 1;
|
||||
CPPushInButtonMask = 2;
|
||||
CPGrayButtonMask = 4;
|
||||
CPBackgroundButtonMask = 8;
|
||||
|
||||
CPNoCellMask = CPNoButtonMask;
|
||||
CPContentsCellMask = CPContentsButtonMask;
|
||||
@@ -73,38 +71,16 @@ 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");
|
||||
|
||||
// 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
|
||||
};
|
||||
var CPButtonBezelStyleStateMap = [CPDictionary dictionaryWithObjects:[CPButtonStateBezelStyleRounded, nil]
|
||||
forKeys:[CPRoundedBezelStyle, CPRoundRectBezelStyle]];
|
||||
|
||||
/// @cond IGNORE
|
||||
CPButtonDefaultHeight = 25.0;
|
||||
|
||||
CPButtonDefaultHeight = 24.0;
|
||||
CPButtonImageOffset = 3.0;
|
||||
/// @endcond
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -127,7 +103,7 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
// NS-style Display Properties
|
||||
CPBezelStyle _bezelStyle;
|
||||
ThemeState _bezelState;
|
||||
CPControlSize _controlSize;
|
||||
|
||||
CPString _keyEquivalent;
|
||||
unsigned _keyEquivalentModifierMask;
|
||||
@@ -137,15 +113,7 @@ CPButtonImageOffset = 3.0;
|
||||
float _periodicDelay;
|
||||
float _periodicInterval;
|
||||
|
||||
BOOL _isHighlighted;
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPTargetBinding || [aBinding hasPrefix:CPArgumentBinding])
|
||||
return [CPActionBinding class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
BOOL _isTracking;
|
||||
}
|
||||
|
||||
+ (id)buttonWithTitle:(CPString)aTitle
|
||||
@@ -169,23 +137,10 @@ CPButtonImageOffset = 3.0;
|
||||
return @"button";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
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
|
||||
};
|
||||
return [CPDictionary dictionaryWithObjects:[[CPNull null], 0.0, _CGInsetMakeZero(), _CGInsetMakeZero(), [CPNull null]]
|
||||
forKeys:[@"image", @"image-offset", @"bezel-inset", @"content-inset", @"bezel-color"]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -199,6 +154,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];
|
||||
|
||||
@@ -210,6 +171,8 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
_keyEquivalent = @"";
|
||||
_keyEquivalentModifierMask = 0;
|
||||
|
||||
@@ -220,20 +183,6 @@ CPButtonImageOffset = 3.0;
|
||||
[self setButtonType:CPMomentaryPushInButton];
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: Control Size
|
||||
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
[super setControlSize:aControlSize];
|
||||
|
||||
if ([self isBordered])
|
||||
[self _sizeToControlSize];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
|
||||
// Setting the state
|
||||
/*!
|
||||
Returns a Boolean value indicating whether the button allows a mixed state.
|
||||
@@ -272,7 +221,7 @@ CPButtonImageOffset = 3.0;
|
||||
else if (![anObjectValue isKindOfClass:[CPNumber class]])
|
||||
anObjectValue = CPOnState;
|
||||
else if (anObjectValue >= CPOnState)
|
||||
anObjectValue = CPOnState;
|
||||
anObjectValue = CPOnState
|
||||
else if (anObjectValue < CPOffState)
|
||||
if ([self allowsMixedState])
|
||||
anObjectValue = CPMixedState;
|
||||
@@ -280,6 +229,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 unsetThemeState:CPThemeStateSelected | CPButtonStateMixed | CPThemeStateHighlighted];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -302,7 +275,7 @@ CPButtonImageOffset = 3.0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's state to the next available state.
|
||||
Sets the button's next state to \c aState.
|
||||
@param aState Possible states are any of the CPButton globals:
|
||||
\c CPOffState, \c CPOnState, \c CPMixedState
|
||||
*/
|
||||
@@ -376,21 +349,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"];
|
||||
}
|
||||
|
||||
- (CPImage)image
|
||||
{
|
||||
if (!_bezelState)
|
||||
_bezelState = CPThemeStateNormal;
|
||||
|
||||
return [self valueForThemeAttribute:@"image" inState:_bezelState];
|
||||
return [self valueForThemeAttribute:@"image" inState:CPThemeStateNormal];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -399,8 +363,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];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -408,17 +371,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
|
||||
@@ -441,6 +394,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];
|
||||
}
|
||||
@@ -457,8 +415,11 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
_highlightsBy = aMask;
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
if ([self hasThemeState:CPThemeStateHighlighted])
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPInteger)highlightsBy
|
||||
@@ -470,47 +431,38 @@ CPButtonImageOffset = 3.0;
|
||||
{
|
||||
switch (aButtonType)
|
||||
{
|
||||
case CPMomentaryLightButton:
|
||||
[self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
case CPMomentaryLightButton: [self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
|
||||
case CPMomentaryPushInButton:
|
||||
[self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
case CPMomentaryPushInButton: [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
|
||||
case CPMomentaryChangeButton:
|
||||
[self setHighlightsBy:CPContentsCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
case CPMomentaryChangeButton: [self setHighlightsBy:CPContentsCellMask];
|
||||
[self setShowsStateBy:CPNoCellMask];
|
||||
break;
|
||||
|
||||
case CPPushOnPushOffButton:
|
||||
[self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPChangeBackgroundCellMask | CPChangeGrayCellMask];
|
||||
break;
|
||||
case CPPushOnPushOffButton: [self setHighlightsBy:CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPChangeBackgroundCellMask | CPChangeGrayCellMask];
|
||||
break;
|
||||
|
||||
case CPOnOffButton:
|
||||
[self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
break;
|
||||
case CPOnOffButton: [self setHighlightsBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
[self setShowsStateBy:CPChangeGrayCellMask | CPChangeBackgroundCellMask];
|
||||
break;
|
||||
|
||||
case CPToggleButton:
|
||||
[self setHighlightsBy:CPPushInCellMask | CPContentsCellMask];
|
||||
[self setShowsStateBy:CPContentsCellMask];
|
||||
break;
|
||||
case CPToggleButton: [self setHighlightsBy:CPPushInCellMask | CPContentsCellMask];
|
||||
[self setShowsStateBy:CPContentsCellMask];
|
||||
break;
|
||||
|
||||
case CPSwitchButton:
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:"The CPSwitchButton type is not supported in Cappuccino, use the CPCheckBox class instead."];
|
||||
case CPSwitchButton: [CPException raise:CPInvalidArgumentException
|
||||
reason:"The CPSwitchButton type is not supported in Cappuccino, use the CPCheckBox class instead."];
|
||||
|
||||
case CPRadioButton:
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:"The CPRadioButton type is not supported in Cappuccino, use the CPRadio class instead."];
|
||||
case CPRadioButton: [CPException raise:CPInvalidArgumentException
|
||||
reason:"The CPRadioButton type is not supported in Cappuccino, use the CPRadio class instead."];
|
||||
|
||||
default:
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:"Unknown button type."];
|
||||
default: [CPException raise:CPInvalidArgumentException
|
||||
reason:"Unknown button type."];
|
||||
}
|
||||
|
||||
[self setImageDimsWhenDisabled:YES];
|
||||
@@ -543,20 +495,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()
|
||||
{
|
||||
@@ -576,12 +517,44 @@ 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];
|
||||
}
|
||||
|
||||
@@ -602,9 +575,9 @@ 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);
|
||||
return _CGRectInsetByInset(bounds, contentInset);
|
||||
}
|
||||
|
||||
- (CGRect)bezelRectForBounds:(CGRect)bounds
|
||||
@@ -615,7 +588,7 @@ CPButtonImageOffset = 3.0;
|
||||
|
||||
var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
|
||||
|
||||
return CGRectInsetByInset(bounds, bezelInset);
|
||||
return _CGRectInsetByInset(bounds, bezelInset);
|
||||
}
|
||||
|
||||
- (CGSize)_minimumFrameSize
|
||||
@@ -629,7 +602,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"],
|
||||
@@ -675,174 +648,96 @@ CPButtonImageOffset = 3.0;
|
||||
{
|
||||
if (aName === "bezel-view")
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
return view;
|
||||
}
|
||||
else
|
||||
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, // Needed during theme compilation
|
||||
currentState = [self state],
|
||||
buttonIsOn = (currentState !== CPOffState);
|
||||
|
||||
// If the button is pushed (_isHighlighted), always add the highlighted state
|
||||
if (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
|
||||
visualState = visualState.and(CPThemeStateHighlighted);
|
||||
else
|
||||
visualState = visualState.without(CPThemeStateHighlighted);
|
||||
|
||||
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;
|
||||
return [[_CPImageAndTextView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
}
|
||||
|
||||
- (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
|
||||
@@ -872,7 +767,7 @@ CPButtonImageOffset = 3.0;
|
||||
{
|
||||
var selfWindow = [self window];
|
||||
|
||||
if (selfWindow === aWindow || aWindow == nil)
|
||||
if (selfWindow === aWindow || aWindow === nil)
|
||||
return;
|
||||
|
||||
if ([selfWindow defaultButton] === self)
|
||||
@@ -938,7 +833,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
|
||||
{
|
||||
@@ -950,7 +855,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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -973,22 +879,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
|
||||
@@ -1012,8 +902,7 @@ var CPButtonImageKey = @"CPButtonImageKey",
|
||||
CPButtonPeriodicDelayKey = @"CPButtonPeriodicDelayKey",
|
||||
CPButtonPeriodicIntervalKey = @"CPButtonPeriodicIntervalKey",
|
||||
CPButtonHighlightsByKey = @"CPButtonHighlightsByKey",
|
||||
CPButtonShowsStateByKey = @"CPButtonShowsStateByKey",
|
||||
CPButtonBezelStyleKey = @"CPButtonBezelStyleKey";
|
||||
CPButtonShowsStateByKey = @"CPButtonShowsStateByKey";
|
||||
|
||||
@implementation CPButton (CPCoding)
|
||||
|
||||
@@ -1062,12 +951,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];
|
||||
}
|
||||
@@ -1102,9 +985,9 @@ 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
|
||||
|
||||
@import "CPCheckBox.j"
|
||||
@import "CPRadio.j"
|
||||
|
||||
+30
-151
@@ -1,48 +1,18 @@
|
||||
/*
|
||||
* CPButtonBar.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPView.j"
|
||||
@import "CPWindow_Constants.j"
|
||||
|
||||
@class CPSplitView
|
||||
|
||||
@global CPPopUpButtonStatePullsDown
|
||||
@global CPKeyValueChangeOldKey
|
||||
@global CPKeyValueChangeNewKey
|
||||
@global CPKeyValueObservingOptionNew
|
||||
@global CPKeyValueObservingOptionOld
|
||||
|
||||
@implementation CPButtonBar : CPView
|
||||
{
|
||||
BOOL _hasResizeControl;
|
||||
BOOL _resizeControlIsLeftAligned;
|
||||
CPArray _buttons;
|
||||
CPArray _rightButtons;
|
||||
}
|
||||
|
||||
+ (id)plusButton
|
||||
{
|
||||
var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
|
||||
image = [[CPTheme defaultTheme] valueForAttributeWithName:@"button-image-plus" forClass:[CPButtonBar class]];
|
||||
image = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPButtonBar class]] pathForResource:@"plus_button.png"] size:CGSizeMake(11, 12)];
|
||||
|
||||
[button setBordered:NO];
|
||||
[button setImage:image];
|
||||
@@ -54,7 +24,7 @@
|
||||
+ (id)minusButton
|
||||
{
|
||||
var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
|
||||
image = [[CPTheme defaultTheme] valueForAttributeWithName:@"button-image-minus" forClass:[CPButtonBar class]];
|
||||
image = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPButtonBar class]] pathForResource:@"minus_button.png"] size:CGSizeMake(11, 4)];
|
||||
|
||||
[button setBordered:NO];
|
||||
[button setImage:image];
|
||||
@@ -66,12 +36,12 @@
|
||||
+ (id)actionPopupButton
|
||||
{
|
||||
var button = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0, 0, 35, 25)],
|
||||
image = [[CPTheme defaultTheme] valueForAttributeWithName:@"button-image-action" forClass:[CPButtonBar class]];
|
||||
image = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPButtonBar class]] pathForResource:@"action_button.png"] size:CGSizeMake(22, 14)];
|
||||
|
||||
[button addItemWithTitle:nil];
|
||||
[[button lastItem] setImage:image];
|
||||
[button setImagePosition:CPImageOnly];
|
||||
[button setValue:CGInsetMake(0, 0, 0, 0) forThemeAttribute:"content-inset" inState:CPPopUpButtonStatePullsDown];
|
||||
[button setValue:CGInsetMake(0, 0, 0, 0) forThemeAttribute:"content-inset"];
|
||||
|
||||
[button setPullsDown:YES];
|
||||
|
||||
@@ -83,19 +53,10 @@
|
||||
return @"button-bar";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"resize-control-inset": CGInsetMake(0.0, 0.0, 0.0, 0.0),
|
||||
@"resize-control-size": CGSizeMakeZero(),
|
||||
@"resize-control-color": [CPNull null],
|
||||
@"bezel-color": [CPNull null],
|
||||
@"button-bezel-color": [CPNull null],
|
||||
@"button-text-color": [CPNull null],
|
||||
@"button-image-plus": [CPNull null],
|
||||
@"button-image-minus": [CPNull null],
|
||||
@"button-image-action": [CPNull null],
|
||||
};
|
||||
return [CPDictionary dictionaryWithObjects:[CGInsetMake(0.0, 0.0, 0.0, 0.0), CGSizeMakeZero(), [CPNull null], [CPNull null], [CPNull null], [CPNull null]]
|
||||
forKeys:[@"resize-control-inset", @"resize-control-size", @"resize-control-color", @"bezel-color", @"button-bezel-color", @"button-text-color"]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -105,7 +66,6 @@
|
||||
if (self)
|
||||
{
|
||||
_buttons = [];
|
||||
_rightButtons = [];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
@@ -114,8 +74,6 @@
|
||||
|
||||
- (void)awakeFromCib
|
||||
{
|
||||
[super awakeFromCib];
|
||||
|
||||
var view = [self superview],
|
||||
subview = self;
|
||||
|
||||
@@ -136,20 +94,10 @@
|
||||
|
||||
- (void)setButtons:(CPArray)buttons
|
||||
{
|
||||
for (var i = [_buttons count] - 1; i >= 0; i--)
|
||||
{
|
||||
[_buttons[i] removeFromSuperview];
|
||||
[_buttons[i] removeObserver:self forKeyPath:@"hidden"];
|
||||
}
|
||||
|
||||
|
||||
_buttons = [CPArray arrayWithArray:buttons];
|
||||
|
||||
for (var i = [_buttons count] - 1; i >= 0; i--)
|
||||
{
|
||||
[_buttons[i] addObserver:self forKeyPath:@"hidden" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:nil];
|
||||
for (var i = 0, count = [_buttons count]; i < count; i++)
|
||||
[_buttons[i] setBordered:YES];
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
@@ -159,30 +107,6 @@
|
||||
return [CPArray arrayWithArray:_buttons];
|
||||
}
|
||||
|
||||
- (void)setRightButtons:(CPArray)buttons
|
||||
{
|
||||
for (var i = [_rightButtons count] - 1; i >= 0; i--)
|
||||
{
|
||||
[_rightButtons[i] removeFromSuperview];
|
||||
[_rightButtons[i] removeObserver:self forKeyPath:@"hidden"];
|
||||
}
|
||||
|
||||
_rightButtons = [CPArray arrayWithArray:buttons];
|
||||
|
||||
for (var i = [_rightButtons count] - 1; i >= 0; i--)
|
||||
{
|
||||
[_rightButtons[i] addObserver:self forKeyPath:@"hidden" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:nil];
|
||||
[_rightButtons[i] setBordered:YES];
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (CPArray)rightButtons
|
||||
{
|
||||
return [CPArray arrayWithArray:_rightButtons];
|
||||
}
|
||||
|
||||
- (void)setHasResizeControl:(BOOL)shouldHaveResizeControl
|
||||
{
|
||||
if (_hasResizeControl === shouldHaveResizeControl)
|
||||
@@ -259,83 +183,49 @@
|
||||
count = [buttonsNotHidden count];
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var button = buttonsNotHidden[count];
|
||||
if ([buttonsNotHidden[count] isHidden])
|
||||
[buttonsNotHidden removeObject:buttonsNotHidden[count]];
|
||||
|
||||
if ([button isHidden])
|
||||
{
|
||||
[button removeFromSuperview];
|
||||
[buttonsNotHidden removeObject:button];
|
||||
}
|
||||
}
|
||||
|
||||
var rightButtonsNotHidden = [CPArray arrayWithArray:_rightButtons],
|
||||
rightCount = [rightButtonsNotHidden count];
|
||||
|
||||
while (rightCount--)
|
||||
{
|
||||
var button = rightButtonsNotHidden[rightCount];
|
||||
|
||||
if ([button isHidden])
|
||||
{
|
||||
[button removeFromSuperview];
|
||||
[rightButtonsNotHidden removeObject:button];
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = [self bounds],
|
||||
var currentButtonOffset = _resizeControlIsLeftAligned ? CGRectGetMaxX([self bounds]) + 1 : -1,
|
||||
bounds = [self bounds],
|
||||
height = CGRectGetHeight(bounds) - 1,
|
||||
frameWidth = CGRectGetWidth(bounds),
|
||||
resizeRect = _hasResizeControl ? [self rectForEphemeralSubviewNamed:"resize-control-view"] : CGRectMakeZero(),
|
||||
resizeWidth = CGRectGetWidth(resizeRect),
|
||||
availableWidth = frameWidth - resizeWidth - 1;
|
||||
|
||||
var currentLeftOffset = _resizeControlIsLeftAligned ? resizeWidth - 1 : -1,
|
||||
currentRightOffset = _resizeControlIsLeftAligned ? CGRectGetMaxX(bounds) + 1 : CGRectGetMaxX(bounds) - resizeWidth + 1;
|
||||
|
||||
var setupButton = function(button, isRightAligned)
|
||||
for (var i = 0, count = [buttonsNotHidden count]; i < count; i++)
|
||||
{
|
||||
var width = CGRectGetWidth([button frame]);
|
||||
var button = buttonsNotHidden[i],
|
||||
width = CGRectGetWidth([button frame]);
|
||||
|
||||
if (availableWidth > width)
|
||||
availableWidth -= width;
|
||||
else
|
||||
return NO;
|
||||
break;
|
||||
|
||||
if (isRightAligned)
|
||||
if (_resizeControlIsLeftAligned)
|
||||
{
|
||||
[button setFrame:CGRectMake(currentRightOffset - width, 1, width, height)];
|
||||
currentRightOffset -= width - 1;
|
||||
[button setFrame:CGRectMake(currentButtonOffset - width, 1, width, height)];
|
||||
currentButtonOffset -= width - 1;
|
||||
}
|
||||
else
|
||||
{[button setFrame:CGRectMake(currentLeftOffset, 1, width, height)];
|
||||
currentLeftOffset += width - 1;
|
||||
{
|
||||
[button setFrame:CGRectMake(currentButtonOffset, 1, width, height)];
|
||||
currentButtonOffset += width - 1;
|
||||
}
|
||||
|
||||
[button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered]];
|
||||
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered]];
|
||||
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateDisabled, CPThemeStateBordered]];
|
||||
[button setValue:normalColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal | CPThemeStateBordered];
|
||||
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inState:CPThemeStateHighlighted | CPThemeStateBordered];
|
||||
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled | CPThemeStateBordered];
|
||||
[button setValue:textColor forThemeAttribute:@"text-color" inState:CPThemeStateBordered];
|
||||
|
||||
// FIXME shouldn't need this[button setValue:normalColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateNormal, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
|
||||
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
|
||||
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateDisabled, CPThemeStateBordered, CPPopUpButtonStatePullsDown]];
|
||||
// FIXME shouldn't need this
|
||||
[button setValue:normalColor forThemeAttribute:@"bezel-color" inState:CPThemeStateNormal | CPThemeStateBordered | CPPopUpButtonStatePullsDown];
|
||||
[button setValue:highlightedColor forThemeAttribute:@"bezel-color" inState:CPThemeStateHighlighted | CPThemeStateBordered | CPPopUpButtonStatePullsDown];
|
||||
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inState:CPThemeStateDisabled | CPThemeStateBordered | CPPopUpButtonStatePullsDown];
|
||||
|
||||
[self addSubview:button];
|
||||
|
||||
return YES;
|
||||
};
|
||||
|
||||
for (var i = 0, count = [buttonsNotHidden count]; i < count; i++)
|
||||
{
|
||||
if (!setupButton(buttonsNotHidden[i], _resizeControlIsLeftAligned))
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i = 0, count = [rightButtonsNotHidden count]; i < count; i++)
|
||||
{
|
||||
if (!setupButton(rightButtonsNotHidden[i], YES))
|
||||
break;
|
||||
}
|
||||
|
||||
if (_hasResizeControl)
|
||||
@@ -349,14 +239,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context
|
||||
{
|
||||
if ([change objectForKey:CPKeyValueChangeOldKey] == [change objectForKey:CPKeyValueChangeNewKey])
|
||||
return;
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
@@ -367,8 +249,7 @@
|
||||
|
||||
var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey",
|
||||
CPButtonBarResizeControlIsLeftAlignedKey = @"CPButtonBarResizeControlIsLeftAlignedKey",
|
||||
CPButtonBarButtonsKey = @"CPButtonBarButtonsKey",
|
||||
CPButtonBarRightButtonsKey = @"CPButtonBarRightButtonsKey";
|
||||
CPButtonBarButtonsKey = @"CPButtonBarButtonsKey";
|
||||
|
||||
@implementation CPButtonBar (CPCoding)
|
||||
|
||||
@@ -379,7 +260,6 @@ var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey",
|
||||
[aCoder encodeBool:_hasResizeControl forKey:CPButtonBarHasResizeControlKey];
|
||||
[aCoder encodeBool:_resizeControlIsLeftAligned forKey:CPButtonBarResizeControlIsLeftAlignedKey];
|
||||
[aCoder encodeObject:_buttons forKey:CPButtonBarButtonsKey];
|
||||
[aCoder encodeObject:_rightButtons forKey:CPButtonBarRightButtonsKey];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
@@ -387,7 +267,6 @@ var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey",
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_buttons = [aCoder decodeObjectForKey:CPButtonBarButtonsKey] || [];
|
||||
_rightButtons = [aCoder decodeObjectForKey:CPButtonBarRightButtonsKey] || [];
|
||||
_hasResizeControl = [aCoder decodeBoolForKey:CPButtonBarHasResizeControlKey];
|
||||
_resizeControlIsLeftAligned = [aCoder decodeBoolForKey:CPButtonBarResizeControlIsLeftAlignedKey];
|
||||
}
|
||||
|
||||
+11
-65
@@ -44,12 +44,12 @@ CPCheckBoxImageOffset = 4.0;
|
||||
return @"check-box";
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (aBinding === CPValueBinding)
|
||||
if (theBinding === CPValueBinding)
|
||||
return [_CPCheckBoxValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -94,58 +94,21 @@ CPCheckBoxImageOffset = 4.0;
|
||||
[self takeStateFromKeyPath:aKeyPath ofObjects:objects];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Override methods from CPButton
|
||||
|
||||
- (CGSize)_minimumFrameSize
|
||||
- (CPImage)image
|
||||
{
|
||||
var size = [super _minimumFrameSize],
|
||||
contentView = [self ephemeralSubviewNamed:@"content-view"];
|
||||
|
||||
if (!contentView && [[self title] length])
|
||||
{
|
||||
var minSize = [self currentValueForThemeAttribute:@"min-size"],
|
||||
maxSize = [self currentValueForThemeAttribute:@"max-size"];
|
||||
|
||||
// Here we always add the min size to the control which is the size of the view of the checkBox
|
||||
size.width += minSize.width + CPCheckBoxImageOffset;
|
||||
|
||||
if (maxSize.width >= 0.0)
|
||||
size.width = MIN(size.width, maxSize.width);
|
||||
}
|
||||
|
||||
return size;
|
||||
return [self currentValueForThemeAttribute:@"image"];
|
||||
}
|
||||
|
||||
- (CPThemeState)_contentVisualState
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
// 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
|
||||
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
||||
{
|
||||
// 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;
|
||||
var startedTracking = [super startTrackingAt:aPoint];
|
||||
[self highlight:YES];
|
||||
return startedTracking;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -176,20 +139,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
|
||||
|
||||
+36
-15
@@ -22,7 +22,6 @@
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
@class CPScrollView
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -45,13 +44,44 @@
|
||||
if (_documentView == aView)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_documentView)
|
||||
{
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPViewFrameDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPViewBoundsDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[_documentView removeFromSuperview];
|
||||
}
|
||||
|
||||
_documentView = aView;
|
||||
|
||||
if (_documentView)
|
||||
{
|
||||
[self addSubview:_documentView];
|
||||
|
||||
[_documentView setPostsFrameChangedNotifications:YES];
|
||||
[_documentView setPostsBoundsChangedNotifications:YES];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(viewFrameChanged:)
|
||||
name:CPViewFrameDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(viewBoundsChanged:)
|
||||
name:CPViewBoundsDidChangeNotification
|
||||
object:_documentView];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -71,19 +101,19 @@
|
||||
- (CGPoint)constrainScrollPoint:(CGPoint)aPoint
|
||||
{
|
||||
if (!_documentView)
|
||||
return CGPointMakeZero();
|
||||
return _CGPointMakeZero();
|
||||
|
||||
var documentFrame = [_documentView frame];
|
||||
|
||||
aPoint.x = MAX(0.0, MIN(aPoint.x, MAX(CGRectGetWidth(documentFrame) - CGRectGetWidth(_bounds), 0.0)));
|
||||
aPoint.y = MAX(0.0, MIN(aPoint.y, MAX(CGRectGetHeight(documentFrame) - CGRectGetHeight(_bounds), 0.0)));
|
||||
aPoint.x = MAX(0.0, MIN(aPoint.x, MAX(_CGRectGetWidth(documentFrame) - _CGRectGetWidth(_bounds), 0.0)));
|
||||
aPoint.y = MAX(0.0, MIN(aPoint.y, MAX(_CGRectGetHeight(documentFrame) - _CGRectGetHeight(_bounds), 0.0)));
|
||||
|
||||
return aPoint;
|
||||
}
|
||||
|
||||
- (void)setBoundsOrigin:(CGPoint)aPoint
|
||||
{
|
||||
if (CGPointEqualToPoint(_bounds.origin, aPoint))
|
||||
if (_CGPointEqualToPoint(_bounds.origin, aPoint))
|
||||
return;
|
||||
|
||||
[super setBoundsOrigin:aPoint];
|
||||
@@ -193,11 +223,6 @@
|
||||
return [self scrollToPoint:CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY)];
|
||||
}
|
||||
|
||||
- (CGRect)documentVisibleRect
|
||||
{
|
||||
return [_documentView visibleRect];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -208,11 +233,7 @@ var CPClipViewDocumentViewKey = @"CPScrollViewDocumentView";
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
// Don't call setDocumentView: here. It calls addSubview:, but it's A) not necessary since the
|
||||
// view hierarchy is fully encoded and B) dangerous if the subview is not fully decoded.
|
||||
_documentView = [aCoder decodeObjectForKey:CPClipViewDocumentViewKey];
|
||||
}
|
||||
[self setDocumentView:[aCoder decodeObjectForKey:CPClipViewDocumentViewKey]];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+266
-821
File diff suppressed because it is too large
Load Diff
@@ -22,38 +22,13 @@
|
||||
|
||||
@import "CPViewController.j"
|
||||
|
||||
|
||||
/*!
|
||||
Represents an object inside a CPCollectionView.
|
||||
*/
|
||||
@implementation CPCollectionViewItem : CPViewController
|
||||
{
|
||||
BOOL _isSelected;
|
||||
CPData _cachedArchive;
|
||||
}
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var cibName = [self cibName],
|
||||
copy;
|
||||
|
||||
if (cibName)
|
||||
{
|
||||
copy = [[[self class] alloc] initWithCibName:cibName bundle:[self cibBundle]];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_cachedArchive)
|
||||
_cachedArchive = [CPKeyedArchiver archivedDataWithRootObject:self];
|
||||
|
||||
copy = [CPKeyedUnarchiver unarchiveObjectWithData:_cachedArchive];
|
||||
|
||||
// copy connections
|
||||
}
|
||||
|
||||
[copy setRepresentedObject:[self representedObject]];
|
||||
[copy setSelected:_isSelected];
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
// Setting the Represented Object
|
||||
|
||||
+90
-956
File diff suppressed because it is too large
Load Diff
+64
-117
@@ -20,38 +20,14 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPApplication.j"
|
||||
@import "CPButton.j"
|
||||
@import "CPCookie.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPView.j"
|
||||
|
||||
@class CPSlider
|
||||
@class _CPColorPanelToolbar
|
||||
@class _CPColorPanelSwatches
|
||||
@class _CPColorPanelPreview
|
||||
|
||||
@global CPApp
|
||||
|
||||
/*
|
||||
A color wheel
|
||||
@global
|
||||
@group CPColorPanelMode
|
||||
*/
|
||||
CPWheelColorPickerMode = 1;
|
||||
|
||||
/*
|
||||
Slider based picker
|
||||
@global
|
||||
@group CPColorPanelMode
|
||||
*/
|
||||
CPSliderColorPickerMode = 2;
|
||||
|
||||
CPColorPickerViewWidth = 265;
|
||||
CPColorPickerViewHeight = 370;
|
||||
|
||||
CPColorPanelColorDidChangeNotification = @"CPColorPanelColorDidChangeNotification";
|
||||
CPColorDragType = CPColorPboardType;
|
||||
|
||||
var PREVIEW_HEIGHT = 20.0,
|
||||
TOOLBAR_HEIGHT = 32.0,
|
||||
@@ -62,6 +38,22 @@ var PREVIEW_HEIGHT = 20.0,
|
||||
var SharedColorPanel = nil,
|
||||
ColorPickerClasses = [];
|
||||
|
||||
/*
|
||||
A color wheel
|
||||
@global
|
||||
@group CPColorPanelMode
|
||||
*/
|
||||
CPWheelColorPickerMode = 1;
|
||||
/*
|
||||
Slider based picker
|
||||
@global
|
||||
@group CPColorPanelMode
|
||||
*/
|
||||
CPSliderColorPickerMode = 2;
|
||||
|
||||
CPColorPickerViewWidth = 265;
|
||||
CPColorPickerViewHeight = 370;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPColorPanel
|
||||
@@ -72,6 +64,7 @@ var SharedColorPanel = nil,
|
||||
*/
|
||||
@implementation CPColorPanel : CPPanel
|
||||
{
|
||||
_CPColorPanelToolbar _toolbar;
|
||||
_CPColorPanelSwatches _swatchView;
|
||||
_CPColorPanelPreview _previewView;
|
||||
|
||||
@@ -130,6 +123,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 +143,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 +168,7 @@ var SharedColorPanel = nil,
|
||||
{
|
||||
[self setColor:aColor];
|
||||
|
||||
if (bool && _activePicker)
|
||||
if (bool)
|
||||
[_activePicker setColor:_color];
|
||||
}
|
||||
|
||||
@@ -270,8 +248,8 @@ var SharedColorPanel = nil,
|
||||
var height = (TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5 + SWATCH_HEIGHT + 32),
|
||||
bounds = [[self contentView] bounds];
|
||||
|
||||
[view setFrameSize:CGSizeMake(bounds.size.width - 10, bounds.size.height - height)];
|
||||
[view setFrameOrigin:CGPointMake(5, height)];
|
||||
[view setFrameSize:CPSizeMake(bounds.size.width - 10, bounds.size.height - height)];
|
||||
[view setFrameOrigin:CPPointMake(5, height)];
|
||||
}
|
||||
|
||||
[_currentView removeFromSuperview];
|
||||
@@ -348,7 +326,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 +344,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 +385,8 @@ var SharedColorPanel = nil,
|
||||
[contentView addSubview:opacityLabel];
|
||||
[contentView addSubview:_opacitySlider];
|
||||
|
||||
_target = nil;
|
||||
_action = nil;
|
||||
_activePicker = nil;
|
||||
|
||||
[_previewView setBackgroundColor:_color];
|
||||
@@ -426,6 +406,8 @@ var SharedColorPanel = nil,
|
||||
@end
|
||||
|
||||
|
||||
CPColorDragType = "CPColorDragType";
|
||||
|
||||
var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
|
||||
/* @ignore */
|
||||
@@ -435,14 +417,12 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
CPColor _dragColor;
|
||||
CPColorPanel _colorPanel;
|
||||
CPCookie _swatchCookie;
|
||||
CGPoint _mouseDownPoint;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
_mouseDownPoint = CGPointMake(0, 0);
|
||||
[self setBackgroundColor:[CPColor grayColor]];
|
||||
|
||||
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
|
||||
@@ -456,7 +436,8 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
|
||||
for (var i = 0; i < 50; i++)
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:CGRectMake(13 * i + 1, 1, 12, 12)],
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
var view = [[CPView alloc] initWithFrame:CPRectMake(13 * i + 1, 1, 12, 12)],
|
||||
fillView = [[CPView alloc] initWithFrame:CGRectInset([view bounds], 1.0, 1.0)];
|
||||
|
||||
[view setBackgroundColor:whiteColor];
|
||||
@@ -496,17 +477,19 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
];
|
||||
}
|
||||
|
||||
var cookieValue = JSON.parse(cookieValue);
|
||||
var cookieValue = eval(cookieValue),
|
||||
result = [];
|
||||
|
||||
return [cookieValue arrayByApplyingBlock:function(value)
|
||||
{
|
||||
return [CPColor colorWithHexString:value];
|
||||
}];
|
||||
for (var i = 0; i < cookieValue.length; i++)
|
||||
result.push([CPColor colorWithHexString:cookieValue[i]]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (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 +516,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 +557,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 +572,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 +582,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 +590,11 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
@implementation _CPColorPanelPreview : CPView
|
||||
{
|
||||
CPColorPanel _colorPanel;
|
||||
CGPoint _mouseDownPoint;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
_mouseDownPoint = CGPointMake(0, 0);
|
||||
|
||||
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
|
||||
|
||||
@@ -646,7 +611,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
return _colorPanel;
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
|
||||
- (void)performDragOperation:(id <CPDraggingInfo>)aSender
|
||||
{
|
||||
var pasteboard = [aSender draggingPasteboard];
|
||||
|
||||
@@ -655,8 +620,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
|
||||
var color = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]];
|
||||
[_colorPanel setColor:color updatePicker:YES];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
@@ -664,22 +627,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);
|
||||
var bounds = CPRectMake(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 +644,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)
|
||||
at:CPPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
|
||||
offset:CPPointMake(0.0, 0.0)
|
||||
event:anEvent
|
||||
pasteboard:pasteboard
|
||||
pasteboard:nil
|
||||
source:self
|
||||
slideBack:YES];
|
||||
}
|
||||
@@ -707,11 +659,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:aType];
|
||||
}
|
||||
|
||||
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
|
||||
{
|
||||
return CPDragOperationCopy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@import "CPColorPicker.j"
|
||||
|
||||
+16
-25
@@ -24,14 +24,6 @@
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
@class CPSlider
|
||||
@class CPColorPanel
|
||||
@class __CPColorWheel
|
||||
|
||||
@global CPColorPickerViewWidth
|
||||
@global CPColorPickerViewHeight
|
||||
@global CPWheelColorPickerMode
|
||||
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -117,16 +109,15 @@
|
||||
|
||||
- (id)initView
|
||||
{
|
||||
var aFrame = CGRectMake(0, 0, CPColorPickerViewWidth, CPColorPickerViewHeight);
|
||||
var aFrame = _CGRectMake(0, 0, CPColorPickerViewWidth, CPColorPickerViewHeight);
|
||||
|
||||
_pickerView = [[CPView alloc] initWithFrame:aFrame];
|
||||
[_pickerView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
_brightnessSlider = [[CPSlider alloc] initWithFrame:CGRectMake(0, (aFrame.size.height - 34), aFrame.size.width, 15)];
|
||||
_brightnessSlider = [[CPSlider alloc] initWithFrame:_CGRectMake(0, (aFrame.size.height - 34), aFrame.size.width, 15)];
|
||||
|
||||
[_brightnessSlider setValue:15.0 forThemeAttribute:@"track-width"];
|
||||
var brightnessImage = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPColorPicker class]] pathForResource:@"brightness_bar.png"] size:CGSizeMake(272, 20)];
|
||||
[_brightnessSlider setValue:[CPColor colorWithPatternImage:brightnessImage] forThemeAttribute:@"track-color"];
|
||||
[_brightnessSlider setValue:[CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPColorPicker class]] pathForResource:@"brightness_bar.png"]]] forThemeAttribute:@"track-color"];
|
||||
|
||||
[_brightnessSlider setMinValue:0.0];
|
||||
[_brightnessSlider setMaxValue:100.0];
|
||||
@@ -136,7 +127,7 @@
|
||||
[_brightnessSlider setAction:@selector(brightnessSliderDidChange:)];
|
||||
[_brightnessSlider setAutoresizingMask:CPViewWidthSizable | CPViewMinYMargin];
|
||||
|
||||
_hueSaturationView = [[__CPColorWheel alloc] initWithFrame:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height - 38)];
|
||||
_hueSaturationView = [[__CPColorWheel alloc] initWithFrame:_CGRectMake(0, 0, aFrame.size.width, aFrame.size.height - 38)];
|
||||
[_hueSaturationView setDelegate:self];
|
||||
[_hueSaturationView setAutoresizingMask:(CPViewWidthSizable | CPViewHeightSizable)];
|
||||
|
||||
@@ -161,12 +152,12 @@
|
||||
brightness = [_brightnessSlider floatValue];
|
||||
|
||||
[_hueSaturationView setWheelBrightness:brightness / 100.0];
|
||||
[_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hue / 360.0 saturation:saturation / 100.0 brightness:1]];
|
||||
[_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hue saturation:saturation brightness:100]];
|
||||
|
||||
var colorPanel = [self colorPanel],
|
||||
opacity = [colorPanel opacity];
|
||||
|
||||
_cachedColor = [CPColor colorWithHue:hue / 360.0 saturation:saturation / 100.0 brightness:brightness / 100.0 alpha:opacity];
|
||||
_cachedColor = [CPColor colorWithHue:hue saturation:saturation brightness:brightness alpha:opacity];
|
||||
|
||||
[[self colorPanel] setColor:_cachedColor];
|
||||
}
|
||||
@@ -197,10 +188,10 @@
|
||||
var hsb = [newColor hsbComponents];
|
||||
|
||||
[_hueSaturationView setPositionToColor:newColor];
|
||||
[_brightnessSlider setFloatValue:hsb[2] * 100.0];
|
||||
[_hueSaturationView setWheelBrightness:hsb[2]];
|
||||
[_brightnessSlider setFloatValue:hsb[2]];
|
||||
[_hueSaturationView setWheelBrightness:hsb[2] / 100.0];
|
||||
|
||||
[_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hsb[0] saturation:hsb[1] brightness:1]];
|
||||
[_brightnessSlider setBackgroundColor:[CPColor colorWithHue:hsb[0] saturation:hsb[1] brightness:100]];
|
||||
}
|
||||
|
||||
- (CPImage)provideNewButtonImage
|
||||
@@ -231,7 +222,7 @@
|
||||
float _radius;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
@@ -247,7 +238,7 @@
|
||||
_blackWheelImage = new Image();
|
||||
_blackWheelImage.src = path;
|
||||
_blackWheelImage.style.opacity = "0";
|
||||
_blackWheelImage.style.filter = "alpha(opacity=0)";
|
||||
_blackWheelImage.style.filter = "alpha(opacity=0)"
|
||||
_blackWheelImage.style.position = "absolute";
|
||||
|
||||
_DOMElement.appendChild(_wheelImage);
|
||||
@@ -278,13 +269,13 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
- (void)setFrameSize:(CPSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
[self setWheelSize:aSize];
|
||||
}
|
||||
|
||||
- (void)setWheelSize:(CGSize)aSize
|
||||
- (void)setWheelSize:(CPSize)aSize
|
||||
{
|
||||
var min = MIN(aSize.width, aSize.height);
|
||||
|
||||
@@ -362,15 +353,15 @@
|
||||
_angle = [self radiansToDegrees:angle];
|
||||
_distance = (distance / _radius) * 100.0;
|
||||
|
||||
[_crosshair setFrameOrigin:CGPointMake(COS(angle) * distance + midX - 2.0, SIN(angle) * distance + midY - 2.0)];
|
||||
[_crosshair setFrameOrigin:CPPointMake(COS(angle) * distance + midX - 2.0, SIN(angle) * distance + midY - 2.0)];
|
||||
}
|
||||
|
||||
- (void)setPositionToColor:(CPColor)aColor
|
||||
{
|
||||
var hsb = [aColor hsbComponents],
|
||||
bounds = [self bounds],
|
||||
angle = [self degreesToRadians:hsb[0] * 360.0],
|
||||
distance = hsb[1] * _radius;
|
||||
angle = [self degreesToRadians:hsb[0]],
|
||||
distance = (hsb[1] / 100.0) * _radius;
|
||||
|
||||
[self setAngle:angle distance:distance];
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
var sRGBColorSpace = nil;
|
||||
|
||||
/*!
|
||||
|
||||
+132
-226
@@ -25,7 +25,7 @@
|
||||
@import "CPView.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPColorPanel.j"
|
||||
@import "CPPasteboard.j"
|
||||
|
||||
|
||||
var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiveNotification";
|
||||
|
||||
@@ -39,18 +39,18 @@ 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
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (aBinding == CPValueBinding)
|
||||
if (theBinding == CPValueBinding)
|
||||
return [CPColorWellValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
@@ -58,15 +58,10 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
|
||||
return @"colorwell";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"bezel-inset": CGInsetMakeZero(),
|
||||
@"bezel-color": [CPNull null],
|
||||
@"content-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0),
|
||||
@"content-border-inset": CGInsetMakeZero(),
|
||||
@"content-border-color": [CPNull null],
|
||||
};
|
||||
return [CPDictionary dictionaryWithObjects:[_CGInsetMakeZero(), [CPNull null], _CGInsetMake(3.0, 3.0, 3.0, 3.0), _CGInsetMakeZero(), [CPNull null]]
|
||||
forKeys:[@"bezel-inset", @"bezel-color", @"content-inset", @"content-border-inset", @"content-border-color"]];
|
||||
}
|
||||
|
||||
- (void)_reverseSetBinding
|
||||
@@ -77,54 +72,38 @@ 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]];
|
||||
|
||||
[self _registerForNotifications];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: Draw
|
||||
- (void)_registerForNotifications
|
||||
{
|
||||
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]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the color well is bordered.
|
||||
@@ -145,8 +124,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 +139,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,190 +156,109 @@ 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 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);
|
||||
|
||||
return _CGRectInsetByInset(bounds, contentInset);
|
||||
}
|
||||
|
||||
- (CGRect)bezelRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var bezelInset = [self currentValueForThemeAttribute:@"bezel-inset"];
|
||||
|
||||
return CGRectInsetByInset(bounds, bezelInset);
|
||||
return _CGRectInsetByInset(bounds, bezelInset);
|
||||
}
|
||||
|
||||
- (CGRect)contentBorderRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var contentBorderInset = [self currentValueForThemeAttribute:@"content-border-inset"];
|
||||
|
||||
return CGRectInsetByInset(bounds, contentBorderInset);
|
||||
return _CGRectInsetByInset(bounds, contentBorderInset);
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
|
||||
@@ -386,7 +278,7 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
@@ -405,6 +297,7 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:@"bezel-view"];
|
||||
|
||||
|
||||
[contentView setBackgroundColor:_color];
|
||||
|
||||
var contentBorderView = [self layoutEphemeralSubviewNamed:@"content-border-view"
|
||||
@@ -422,7 +315,7 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options
|
||||
{
|
||||
var placeholderColor = [CPColor blackColor];
|
||||
var placeholderColor = [CPColor blueColor];
|
||||
|
||||
[self _setPlaceholder:placeholderColor forMarker:CPMultipleValuesMarker isDefault:YES];
|
||||
[self _setPlaceholder:placeholderColor forMarker:CPNoSelectionMarker isDefault:YES];
|
||||
@@ -430,19 +323,30 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
|
||||
[self _setPlaceholder:placeholderColor forMarker:CPNullMarker isDefault:YES];
|
||||
}
|
||||
|
||||
- (id)valueForBinding:(CPString)aBinding
|
||||
- (void)setValueFor:(CPString)theBinding
|
||||
{
|
||||
return [_source color];
|
||||
}
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
newValue = [destination valueForKeyPath:keyPath],
|
||||
isPlaceholder = CPIsControllerMarker(newValue);
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)theBinding
|
||||
{
|
||||
[_source setColor:aValue];
|
||||
}
|
||||
if (isPlaceholder)
|
||||
{
|
||||
if (newValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
|
||||
{
|
||||
[CPException raise:CPGenericException
|
||||
reason:@"can't transform non applicable key on: " + _source + " value: " + newValue];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source setColor:aValue];
|
||||
newValue = [self _placeholderForMarker:newValue];
|
||||
}
|
||||
else
|
||||
{
|
||||
newValue = [self transformValue:newValue withOptions:options];
|
||||
}
|
||||
|
||||
[_source setColor:newValue];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -462,9 +366,11 @@ var CPColorWellColorKey = "CPColorWellColorKey",
|
||||
|
||||
if (self)
|
||||
{
|
||||
_active = NO;
|
||||
_color = [aCoder decodeObjectForKey:CPColorWellColorKey];
|
||||
[self setBordered:[aCoder decodeBoolForKey:CPColorWellBorderedKey]];
|
||||
[self registerForDraggedTypes:[CPArray arrayWithObject:CPColorPboardType]];
|
||||
|
||||
[self _registerForNotifications];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
||||
+104
-211
@@ -20,32 +20,10 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPText.j"
|
||||
@import "CPTextField.j"
|
||||
@import "_CPPopUpList.j"
|
||||
|
||||
|
||||
// TODO : should conform to protocol CPTextFieldDelegate
|
||||
@protocol CPComboBoxDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (void)comboBoxSelectionIsChanging:(CPNotification)aNotification;
|
||||
- (void)comboBoxSelectionDidChange:(CPNotification)aNotification;
|
||||
- (void)comboBoxWillPopUp:(CPNotification)aNotification;
|
||||
- (void)comboBoxWillDismiss:(CPNotification)aNotification;
|
||||
|
||||
@end
|
||||
|
||||
@protocol CPComboBoxDataSource <CPObject>
|
||||
|
||||
@optional
|
||||
- (CPString)comboBox:(CPComboBox)aComboBox completedString:(CPString)uncompletedString;
|
||||
- (id)comboBox:(CPComboBox)aComboBox objectValueForItemAtIndex:(int)index;
|
||||
- (int)comboBox:(CPComboBox)aComboBox indexOfItemWithStringValue:(CPString)stringValue;
|
||||
- (int)numberOfItemsInComboBox:(CPComboBox)aComboBox;
|
||||
|
||||
@end
|
||||
|
||||
CPComboBoxSelectionDidChangeNotification = @"CPComboBoxSelectionDidChangeNotification";
|
||||
CPComboBoxSelectionIsChangingNotification = @"CPComboBoxSelectionIsChangingNotification";
|
||||
CPComboBoxWillDismissNotification = @"CPComboBoxWillDismissNotification";
|
||||
@@ -53,11 +31,6 @@ CPComboBoxWillPopUpNotification = @"CPComboBoxWillPopUpNotification";
|
||||
|
||||
CPComboBoxStateButtonBordered = CPThemeState("button-bordered");
|
||||
|
||||
var CPComboBoxDelegate_comboBoxSelectionIsChanging_ = 1 << 0,
|
||||
CPComboBoxDelegate_comboBoxSelectionDidChange_ = 1 << 1,
|
||||
CPComboBoxDelegate_comboBoxWillPopUp_ = 1 << 2,
|
||||
CPComboBoxDelegate_comboBoxWillDismiss_ = 1 << 3;
|
||||
|
||||
var CPComboBoxTextSubview = @"text",
|
||||
CPComboBoxButtonSubview = @"button",
|
||||
CPComboBoxDefaultNumberOfVisibleItems = 5,
|
||||
@@ -66,20 +39,17 @@ var CPComboBoxTextSubview = @"text",
|
||||
|
||||
@implementation CPComboBox : CPTextField
|
||||
{
|
||||
BOOL _canComplete;
|
||||
BOOL _completes;
|
||||
BOOL _forceSelection;
|
||||
BOOL _hasVerticalScroller;
|
||||
BOOL _popUpButtonCausedResign;
|
||||
BOOL _usesDataSource;
|
||||
CGSize _intercellSpacing;
|
||||
CPArray _items;
|
||||
id <CPComboBoxDataSource> _dataSource;
|
||||
CPInteger _implementedDelegateComboBoxMethods;
|
||||
CPString _selectedStringValue;
|
||||
float _itemHeight;
|
||||
int _numberOfVisibleItems;
|
||||
_CPPopUpList _listDelegate;
|
||||
CPArray _items;
|
||||
_CPPopUpList _listDelegate;
|
||||
CPComboBoxDataSource _dataSource;
|
||||
BOOL _usesDataSource;
|
||||
BOOL _completes;
|
||||
BOOL _canComplete;
|
||||
int _numberOfVisibleItems;
|
||||
BOOL _forceSelection;
|
||||
BOOL _hasVerticalScroller;
|
||||
CPString _selectedStringValue;
|
||||
BOOL _popUpButtonCausedResign;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
@@ -87,20 +57,17 @@ var CPComboBoxTextSubview = @"text",
|
||||
return "combobox";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"popup-button-size": CGSizeMake(21.0, 29.0),
|
||||
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0),
|
||||
};
|
||||
return [CPDictionary dictionaryWithObjectsAndKeys:_CGSizeMake(21.0, 29.0), @"popup-button-size", _CGInsetMake(3.0, 3.0, 3.0, 3.0), @"border-inset"];
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (aBinding === CPContentBinding || aBinding === CPContentValuesBinding)
|
||||
if (theBinding === CPContentBinding || theBinding === CPContentValuesBinding)
|
||||
return [_CPComboBoxContentBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -116,7 +83,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
- (void)_initComboBox
|
||||
{
|
||||
_items = [CPArray array];
|
||||
// _listClass = [_CPPopUpList class];
|
||||
_listClass = [_CPPopUpList class];
|
||||
_usesDataSource = NO;
|
||||
_completes = NO;
|
||||
_canComplete = NO;
|
||||
@@ -133,7 +100,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
[self setThemeState:CPComboBoxStateButtonBordered];
|
||||
}
|
||||
|
||||
// MARK: Setting Display Attributes
|
||||
#pragma mark Setting Display Attributes
|
||||
|
||||
- (BOOL)hasVerticalScroller
|
||||
{
|
||||
@@ -148,9 +115,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
return;
|
||||
|
||||
_hasVerticalScroller = flag;
|
||||
|
||||
if (_listDelegate)
|
||||
[[_listDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
|
||||
[[_listDelegate scrollView] setHasVerticalScroller:flag];
|
||||
}
|
||||
|
||||
- (CGSize)intercellSpacing
|
||||
@@ -160,13 +125,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
|
||||
- (void)setIntercellSpacing:(CGSize)aSize
|
||||
{
|
||||
if (_intercellSpacing && CGSizeEqualToSize(aSize, _intercellSpacing))
|
||||
return;
|
||||
|
||||
_intercellSpacing = aSize;
|
||||
|
||||
if (_listDelegate)
|
||||
[[_listDelegate tableView] setIntercellSpacing:_intercellSpacing];
|
||||
[[_listDelegate tableView] setIntercellSpacing:aSize];
|
||||
}
|
||||
|
||||
- (BOOL)isButtonBordered
|
||||
@@ -189,18 +148,10 @@ var CPComboBoxTextSubview = @"text",
|
||||
|
||||
- (void)setItemHeight:(float)itemHeight
|
||||
{
|
||||
if (itemHeight === _itemHeight)
|
||||
return;
|
||||
[[_listDelegate tableView] setRowHeight:itemHeight];
|
||||
|
||||
_itemHeight = itemHeight;
|
||||
|
||||
if (_listDelegate)
|
||||
{
|
||||
[[_listDelegate tableView] setRowHeight:_itemHeight];
|
||||
|
||||
// FIXME: This shouldn't be necessary, but CPTableView does not tile after setRowHeight
|
||||
[[_listDelegate tableView] reloadData];
|
||||
}
|
||||
// FIXME: This shouldn't be necessary, but CPTableView does not tile after setRowHeight
|
||||
[[_listDelegate tableView] reloadData];
|
||||
}
|
||||
|
||||
- (int)numberOfVisibleItems
|
||||
@@ -214,9 +165,9 @@ var CPComboBoxTextSubview = @"text",
|
||||
_numberOfVisibleItems = MAX(visibleItems, 1);
|
||||
}
|
||||
|
||||
// MARK: Setting a Delegate
|
||||
#pragma mark Setting a Delegate
|
||||
|
||||
- (id <CPComboBoxDelegate>)delegate
|
||||
- (id < CPComboBoxDelegate >)delegate
|
||||
{
|
||||
return [super delegate];
|
||||
}
|
||||
@@ -227,36 +178,56 @@ var CPComboBoxTextSubview = @"text",
|
||||
protocol, in actual fact it doesn't. Also note that the same
|
||||
delegate may conform to the NSTextFieldDelegate protocol.
|
||||
*/
|
||||
- (void)setDelegate:(id <CPComboBoxDelegate>)aDelegate
|
||||
- (void)setDelegate:(id < CPComboBoxDelegate >)aDelegate
|
||||
{
|
||||
var delegate = [self delegate];
|
||||
|
||||
if (aDelegate === delegate)
|
||||
return;
|
||||
|
||||
_implementedDelegateComboBoxMethods = 0;
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (delegate)
|
||||
{
|
||||
[defaultCenter removeObserver:delegate name:CPComboBoxSelectionIsChangingNotification object:self];
|
||||
[defaultCenter removeObserver:delegate name:CPComboBoxSelectionDidChangeNotification object:self];
|
||||
[defaultCenter removeObserver:delegate name:CPComboBoxWillDismissNotification object:self];
|
||||
[defaultCenter removeObserver:delegate name:CPComboBoxWillPopUpNotification object:self];
|
||||
}
|
||||
|
||||
if (aDelegate)
|
||||
{
|
||||
if ([aDelegate respondsToSelector:@selector(comboBoxSelectionIsChanging:)])
|
||||
_implementedDelegateComboBoxMethods |= CPComboBoxDelegate_comboBoxSelectionIsChanging_;
|
||||
[defaultCenter addObserver:delegate
|
||||
selector:@selector(comboBoxSelectionIsChanging:)
|
||||
name:CPComboBoxSelectionIsChangingNotification
|
||||
object:self];
|
||||
|
||||
if ([aDelegate respondsToSelector:@selector(comboBoxSelectionDidChange:)])
|
||||
_implementedDelegateComboBoxMethods |= CPComboBoxDelegate_comboBoxSelectionDidChange_;
|
||||
[defaultCenter addObserver:delegate
|
||||
selector:@selector(comboBoxSelectionDidChange:)
|
||||
name:CPComboBoxSelectionDidChangeNotification
|
||||
object:self];
|
||||
|
||||
if ([aDelegate respondsToSelector:@selector(comboBoxWillPopUp:)])
|
||||
_implementedDelegateComboBoxMethods |= CPComboBoxDelegate_comboBoxWillPopUp_;
|
||||
[defaultCenter addObserver:delegate
|
||||
selector:@selector(comboBoxWillPopUp:)
|
||||
name:CPComboBoxWillPopUpNotification
|
||||
object:self];
|
||||
|
||||
if ([aDelegate respondsToSelector:@selector(comboBoxWillDismiss:)])
|
||||
_implementedDelegateComboBoxMethods |= CPComboBoxDelegate_comboBoxWillDismiss_;
|
||||
[defaultCenter addObserver:delegate
|
||||
selector:@selector(comboBoxWillDissmis:)
|
||||
name:CPComboBoxWillDismissNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
[super setDelegate:aDelegate];
|
||||
}
|
||||
|
||||
// MARK: Setting a Data Source
|
||||
#pragma mark Setting a Data Source
|
||||
|
||||
- (id <CPComboBoxDataSource>)dataSource
|
||||
- (id < CPComboBoxDataSource >)dataSource
|
||||
{
|
||||
if (!_usesDataSource)
|
||||
[self _dataSourceWarningForMethod:_cmd condition:NO];
|
||||
@@ -264,12 +235,10 @@ var CPComboBoxTextSubview = @"text",
|
||||
return _dataSource;
|
||||
}
|
||||
|
||||
- (void)setDataSource:(id <CPComboBoxDataSource>)aSource
|
||||
- (void)setDataSource:(id < CPComboBoxDataSource >)aSource
|
||||
{
|
||||
if (!_usesDataSource)
|
||||
{
|
||||
[self _dataSourceWarningForMethod:_cmd condition:NO];
|
||||
}
|
||||
else if (_dataSource !== aSource)
|
||||
{
|
||||
if (![aSource respondsToSelector:@selector(numberOfItemsInComboBox:)] ||
|
||||
@@ -278,9 +247,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
CPLog.warn("Illegal %s data source (%s). Must implement numberOfItemsInComboBox: and comboBox:objectValueForItemAtIndex:", [self className], [aSource description]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_dataSource = aSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +272,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 +345,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.
|
||||
@@ -398,57 +365,49 @@ var CPComboBoxTextSubview = @"text",
|
||||
if (_listDelegate === aDelegate)
|
||||
return;
|
||||
|
||||
[self _removeObserversForListDelegate:_listDelegate];
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_listDelegate)
|
||||
{
|
||||
[defaultCenter removeObserver:self name:_CPPopUpListWillPopUpNotification object:_listDelegate];
|
||||
[defaultCenter removeObserver:self name:_CPPopUpListWillDismissNotification object:_listDelegate];
|
||||
[defaultCenter removeObserver:self name:_CPPopUpListDidDismissNotification object:_listDelegate];
|
||||
[defaultCenter removeObserver:self name:_CPPopUpListItemWasClickedNotification object:_listDelegate];
|
||||
|
||||
var oldTableView = [_listDelegate tableView];
|
||||
|
||||
if (oldTableView)
|
||||
{
|
||||
[defaultCenter removeObserver:self name:CPTableViewSelectionIsChangingNotification object:oldTableView];
|
||||
[defaultCenter removeObserver:self name:CPTableViewSelectionDidChangeNotification object:oldTableView];
|
||||
}
|
||||
}
|
||||
|
||||
_listDelegate = aDelegate;
|
||||
|
||||
// We only add the observers if the CPComboBox is displayed
|
||||
if ([self window])
|
||||
[self _addObserversForListDelegate:_listDelegate]
|
||||
|
||||
// Apply our text style to the list
|
||||
[_listDelegate setFont:[self font]];
|
||||
[_listDelegate setAlignment:[self alignment]];
|
||||
|
||||
[[_listDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
|
||||
|
||||
if (_intercellSpacing)
|
||||
[[_listDelegate tableView] setIntercellSpacing:_intercellSpacing];
|
||||
|
||||
if (_itemHeight)
|
||||
[[_listDelegate tableView] setRowHeight:_itemHeight];
|
||||
}
|
||||
|
||||
- (void)_addObserversForListDelegate:(_CPPopUpList)aDelegate
|
||||
{
|
||||
if (!aDelegate)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(comboBoxWillPopUp:)
|
||||
name:_CPPopUpListWillPopUpNotification
|
||||
object:aDelegate];
|
||||
object:_listDelegate];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(comboBoxWillDismiss:)
|
||||
name:_CPPopUpListWillDismissNotification
|
||||
object:aDelegate];
|
||||
object:_listDelegate];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(listDidDismiss:)
|
||||
name:_CPPopUpListDidDismissNotification
|
||||
object:aDelegate];
|
||||
object:_listDelegate];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(itemWasClicked:)
|
||||
name:_CPPopUpListItemWasClickedNotification
|
||||
object:aDelegate];
|
||||
object:_listDelegate];
|
||||
|
||||
[[aDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
|
||||
[[_listDelegate scrollView] setHasVerticalScroller:_hasVerticalScroller];
|
||||
|
||||
var tableView = [aDelegate tableView];
|
||||
var tableView = [_listDelegate tableView];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(comboBoxSelectionIsChanging:)
|
||||
@@ -459,27 +418,10 @@ var CPComboBoxTextSubview = @"text",
|
||||
selector:@selector(comboBoxSelectionDidChange:)
|
||||
name:CPTableViewSelectionDidChangeNotification
|
||||
object:tableView];
|
||||
}
|
||||
|
||||
- (void)_removeObserversForListDelegate:(_CPPopUpList)aDelegate
|
||||
{
|
||||
if (!aDelegate)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter removeObserver:self name:_CPPopUpListWillPopUpNotification object:aDelegate];
|
||||
[defaultCenter removeObserver:self name:_CPPopUpListWillDismissNotification object:aDelegate];
|
||||
[defaultCenter removeObserver:self name:_CPPopUpListDidDismissNotification object:aDelegate];
|
||||
[defaultCenter removeObserver:self name:_CPPopUpListItemWasClickedNotification object:aDelegate];
|
||||
|
||||
var oldTableView = [aDelegate tableView];
|
||||
|
||||
if (oldTableView)
|
||||
{
|
||||
[defaultCenter removeObserver:self name:CPTableViewSelectionIsChangingNotification object:oldTableView];
|
||||
[defaultCenter removeObserver:self name:CPTableViewSelectionDidChangeNotification object:oldTableView];
|
||||
}
|
||||
// Apply our text style to the list
|
||||
[_listDelegate setFont:[self font]];
|
||||
[_listDelegate setAlignment:[self alignment]];
|
||||
}
|
||||
|
||||
- (int)indexOfItemWithObjectValue:(id)anObject
|
||||
@@ -524,6 +466,8 @@ var CPComboBoxTextSubview = @"text",
|
||||
if (!_listDelegate)
|
||||
[self setListDelegate:[[_CPPopUpList alloc] initWithDataSource:self]];
|
||||
|
||||
[self _selectMatchingItem];
|
||||
|
||||
// Note the offset here is 1 less than the focus ring width because the outer edge
|
||||
// of the focus ring is very transparent and it looks better if the list is closer.
|
||||
if (CPComboBoxFocusRingWidth < 0)
|
||||
@@ -534,7 +478,6 @@ var CPComboBoxTextSubview = @"text",
|
||||
}
|
||||
|
||||
[_listDelegate popUpRelativeToRect:[self _borderFrame] view:self offset:CPComboBoxFocusRingWidth - 1];
|
||||
[self _selectMatchingItem];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
@@ -564,7 +507,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
|
||||
var selectedStringValue = [_listDelegate selectedStringValue];
|
||||
|
||||
if (selectedStringValue == nil)
|
||||
if (selectedStringValue === nil)
|
||||
return NO;
|
||||
else
|
||||
_selectedStringValue = selectedStringValue;
|
||||
@@ -594,7 +537,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
// MARK: Manipulating the Selection
|
||||
#pragma mark Manipulating the Selection
|
||||
|
||||
- (void)deselectItemAtIndex:(int)index
|
||||
{
|
||||
@@ -646,7 +589,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
[self selectItemAtIndex:index];
|
||||
}
|
||||
|
||||
// MARK: Completing the Text Field
|
||||
#pragma mark Completing the Text Field
|
||||
|
||||
- (BOOL)completes
|
||||
{
|
||||
@@ -693,7 +636,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
|
||||
@@ -843,17 +786,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
// In FireFox this needs to be done in setTimeout, otherwise there is no caret
|
||||
// We have to save the input element now, when we lose focus it will change.
|
||||
var element = [self _inputElement];
|
||||
|
||||
[[CPRunLoop mainRunLoop] performBlock:function()
|
||||
{
|
||||
// This will prevent to jump to the focused element
|
||||
var previousScrollingOrigin = [self _scrollToVisibleRectAndReturnPreviousOrigin];
|
||||
|
||||
element.focus();
|
||||
|
||||
[self _restorePreviousScrollingOrigin:previousScrollingOrigin];
|
||||
|
||||
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
|
||||
window.setTimeout(function() { element.focus(); }, 0);
|
||||
#endif
|
||||
|
||||
return NO;
|
||||
@@ -880,20 +813,16 @@ var CPComboBoxTextSubview = @"text",
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
[super setFont:aFont];
|
||||
|
||||
if (_listDelegate)
|
||||
[_listDelegate setFont:aFont];
|
||||
[_listDelegate setFont:aFont];
|
||||
}
|
||||
|
||||
- (void)setAlignment:(CPTextAlignment)alignment
|
||||
{
|
||||
[super setAlignment:alignment];
|
||||
|
||||
if (_listDelegate)
|
||||
[_listDelegate setAlignment:alignment];
|
||||
[_listDelegate setAlignment:alignment];
|
||||
}
|
||||
|
||||
// MARK: Pop Up Button Layout
|
||||
#pragma mark Pop Up Button Layout
|
||||
|
||||
- (CGRect)popupButtonRectForBounds:(CGRect)bounds
|
||||
{
|
||||
@@ -921,7 +850,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
{
|
||||
if (aName === "popup-button-view")
|
||||
{
|
||||
var view = [[_CPComboBoxPopUpButton alloc] initWithFrame:CGRectMakeZero() comboBox:self];
|
||||
var view = [[_CPComboBoxPopUpButton alloc] initWithFrame:_CGRectMakeZero() comboBox:self];
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -938,7 +867,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
relativeToEphemeralSubviewNamed:@"content-view"];
|
||||
}
|
||||
|
||||
// MARK: Internal Helpers
|
||||
#pragma mark Internal Helpers
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_dataSourceWarningForMethod:(SEL)cmd condition:(CPString)flag
|
||||
@@ -961,9 +890,7 @@ var CPComboBoxTextSubview = @"text",
|
||||
index = [_dataSource comboBox:self indexOfItemWithStringValue:stringValue]
|
||||
}
|
||||
else
|
||||
{
|
||||
index = [self indexOfItemWithObjectValue:stringValue];
|
||||
}
|
||||
|
||||
[_listDelegate selectRow:index];
|
||||
|
||||
@@ -1015,28 +942,6 @@ var CPComboBoxTextSubview = @"text",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Observers method
|
||||
|
||||
- (void)_addObservers
|
||||
{
|
||||
if (_isObserving)
|
||||
return;
|
||||
|
||||
[super _addObservers];
|
||||
[self _addObserversForListDelegate:_listDelegate];
|
||||
}
|
||||
|
||||
- (void)_removeObservers
|
||||
{
|
||||
if (!_isObserving)
|
||||
return;
|
||||
|
||||
[super _removeObservers];
|
||||
[self _removeObserversForListDelegate:_listDelegate];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPComboBox (CPComboBoxDelegate)
|
||||
@@ -1044,36 +949,24 @@ var CPComboBoxTextSubview = @"text",
|
||||
/*! @ignore */
|
||||
- (void)comboBoxSelectionIsChanging:(CPNotification)aNotification
|
||||
{
|
||||
if (_implementedDelegateComboBoxMethods & CPComboBoxDelegate_comboBoxSelectionIsChanging_)
|
||||
[_delegate comboBoxSelectionIsChanging:[[CPNotification alloc] initWithName:CPComboBoxSelectionIsChangingNotification object:self userInfo:nil]];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPComboBoxSelectionIsChangingNotification object:self];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)comboBoxSelectionDidChange:(CPNotification)aNotification
|
||||
{
|
||||
if (_implementedDelegateComboBoxMethods & CPComboBoxDelegate_comboBoxSelectionDidChange_)
|
||||
[_delegate comboBoxSelectionDidChange:[[CPNotification alloc] initWithName:CPComboBoxSelectionDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPComboBoxSelectionDidChangeNotification object:self];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)comboBoxWillPopUp:(CPNotification)aNotification
|
||||
{
|
||||
if (_implementedDelegateComboBoxMethods & CPComboBoxDelegate_comboBoxWillPopUp_)
|
||||
[_delegate comboBoxWillPopUp:[[CPNotification alloc] initWithName:CPComboBoxWillPopUpNotification object:self userInfo:nil]];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPComboBoxWillPopUpNotification object:self];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)comboBoxWillDismiss:(CPNotification)aNotification
|
||||
{
|
||||
if (_implementedDelegateComboBoxMethods & CPComboBoxDelegate_comboBoxWillDismiss_)
|
||||
[_delegate comboBoxWillDismiss:[[CPNotification alloc] initWithName:CPComboBoxWillDismissNotification object:self userInfo:nil]];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPComboBoxWillDismissNotification object:self];
|
||||
}
|
||||
|
||||
@@ -1142,9 +1035,11 @@ var CPComboBoxTextSubview = @"text",
|
||||
// Directly nuke _items, [_items removeAll] will trigger an extra call to setContent
|
||||
_items = [];
|
||||
|
||||
var values = [anArray arrayByApplyingBlock:function(object)
|
||||
var values = [];
|
||||
|
||||
[anArray enumerateObjectsUsingBlock:function(object)
|
||||
{
|
||||
return [object description];
|
||||
values.push([object description]);
|
||||
}];
|
||||
|
||||
[self addItemsWithObjectValues:values];
|
||||
@@ -1215,7 +1110,7 @@ var CPComboBoxCompletionTest = function(object, index, context)
|
||||
*/
|
||||
@implementation _CPComboBoxContentBinder : CPBinder
|
||||
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
- (void)setValueFor:(CPString)theBinding
|
||||
{
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
@@ -1257,15 +1152,13 @@ var CPComboBoxCompletionTest = function(object, index, context)
|
||||
else
|
||||
newValue = [self transformValue:newValue withOptions:options];
|
||||
|
||||
switch (aBinding)
|
||||
switch (theBinding)
|
||||
{
|
||||
case CPContentBinding:
|
||||
[_source setContent:newValue];
|
||||
break;
|
||||
case CPContentBinding: [_source setContent:newValue];
|
||||
break;
|
||||
|
||||
case CPContentValuesBinding:
|
||||
[_source setContentValues:newValue];
|
||||
break;
|
||||
case CPContentValuesBinding: [_source setContentValues:newValue];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+80
-315
@@ -20,19 +20,17 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPEvent_Constants.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPPlatform.j"
|
||||
|
||||
|
||||
// Browser Engines
|
||||
CPUnknownBrowserEngine = 0;
|
||||
CPGeckoBrowserEngine = 1 << 0;
|
||||
CPInternetExplorerBrowserEngine = 1 << 1;
|
||||
CPKHTMLBrowserEngine = 1 << 2;
|
||||
CPOperaBrowserEngine = 1 << 3;
|
||||
CPWebKitBrowserEngine = 1 << 4; // Safari + Chrome
|
||||
CPBlinkBrowserEngine = 1 << 5; // Recent Chrome
|
||||
CPEdgeBrowserEngine = 1 << 6;
|
||||
CPGeckoBrowserEngine = 1;
|
||||
CPInternetExplorerBrowserEngine = 2;
|
||||
CPKHTMLBrowserEngine = 3;
|
||||
CPOperaBrowserEngine = 4;
|
||||
CPWebKitBrowserEngine = 5;
|
||||
|
||||
// Operating Systems
|
||||
CPMacOperatingSystem = 0;
|
||||
@@ -40,81 +38,57 @@ CPWindowsOperatingSystem = 1;
|
||||
CPOtherOperatingSystem = 2;
|
||||
|
||||
// Features
|
||||
CPCSSRGBAFeature = 5;
|
||||
CPCSSRGBAFeature = 1 << 5;
|
||||
|
||||
CPHTMLCanvasFeature = 6;
|
||||
CPHTMLContentEditableFeature = 7;
|
||||
CPHTMLDragAndDropFeature = 8;
|
||||
CPHTMLCanvasFeature = 1 << 6;
|
||||
CPHTMLContentEditableFeature = 1 << 7;
|
||||
CPHTMLDragAndDropFeature = 1 << 8;
|
||||
|
||||
CPJavaScriptInnerTextFeature = 9;
|
||||
CPJavaScriptTextContentFeature = 10;
|
||||
// In onpaste, oncopy and oncut events, the event has an event.clipboardData from which the current pasteboard contents can be read with event.clipboardData.getData.
|
||||
CPJavaScriptClipboardEventsFeature = 11;
|
||||
// window.clipboardData exists and can be read and written to at any time using window.clipboardData.getData/setData.
|
||||
CPJavaScriptClipboardAccessFeature = 12;
|
||||
CPJavaScriptCanvasDrawFeature = 13;
|
||||
CPJavaScriptCanvasTransformFeature = 14;
|
||||
CPJavaScriptInnerTextFeature = 1 << 9;
|
||||
CPJavaScriptTextContentFeature = 1 << 10;
|
||||
CPJavaScriptClipboardEventsFeature = 1 << 11;
|
||||
CPJavaScriptClipboardAccessFeature = 1 << 12;
|
||||
CPJavaScriptCanvasDrawFeature = 1 << 13;
|
||||
CPJavaScriptCanvasTransformFeature = 1 << 14;
|
||||
|
||||
CPVMLFeature = 15;
|
||||
CPVMLFeature = 1 << 15;
|
||||
|
||||
CPJavaScriptRemedialKeySupport = 16;
|
||||
CPJavaScriptShadowFeature = 20;
|
||||
CPJavaScriptRemedialKeySupport = 1 << 16;
|
||||
CPJavaScriptShadowFeature = 1 << 20;
|
||||
|
||||
CPJavaScriptNegativeMouseWheelValues = 22;
|
||||
CPJavaScriptMouseWheelValues_8_15 = 23;
|
||||
CPJavaScriptNegativeMouseWheelValues = 1 << 22;
|
||||
CPJavaScriptMouseWheelValues_8_15 = 1 << 23;
|
||||
|
||||
CPOpacityRequiresFilterFeature = 24;
|
||||
CPOpacityRequiresFilterFeature = 1 << 24;
|
||||
|
||||
// Internet explorer does not allow dynamically changing the type of an input element
|
||||
CPInputTypeCanBeChangedFeature = 25;
|
||||
CPHTML5DragAndDropSourceYOffBy1 = 26;
|
||||
//Internet explorer does not allow dynamically changing the type of an input element
|
||||
CPInputTypeCanBeChangedFeature = 1 << 25;
|
||||
CPHTML5DragAndDropSourceYOffBy1 = 1 << 26;
|
||||
|
||||
CPSOPDisabledFromFileURLs = 27;
|
||||
CPSOPDisabledFromFileURLs = 1 << 27;
|
||||
|
||||
// element.style.font can be set for an element not in the DOM.
|
||||
CPInputSetFontOutsideOfDOM = 28;
|
||||
CPInputSetFontOutsideOfDOM = 1 << 28;
|
||||
|
||||
// Input elements have 1 px of extra padding on the left regardless of padding setting.
|
||||
CPInput1PxLeftPadding = 29;
|
||||
CPInputOnInputEventFeature = 30;
|
||||
CPInput1PxLeftPadding = 1 << 29;
|
||||
CPInputOnInputEventFeature = 1 << 30;
|
||||
|
||||
CPFileAPIFeature = 31;
|
||||
|
||||
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
|
||||
*/
|
||||
// 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
|
||||
CPCanvasParentDrawErrorsOnMovementBug = 1 << 0;
|
||||
|
||||
// The paste event is only sent if an input or textarea has focus.
|
||||
CPJavaScriptPasteRequiresEditableTarget = 1 << 1;
|
||||
// Redirecting the focus of the browser on keydown to an input for Cmd-V or Ctrl-V makes the paste fail.
|
||||
CPJavaScriptPasteCantRefocus = 1 << 2;
|
||||
|
||||
/*
|
||||
Safari calculates incorrect text size unless you set the canvas font even if it is already set
|
||||
You can see the bug after disabling the workaround and opening any panel while typing.
|
||||
You can use the font panel in the manual test for CPTextView.
|
||||
Look out for a displaced cursor, i.e. after typing letters of small width, such as the 'i'.
|
||||
https://bugs.webkit.org/show_bug.cgi?id=150224
|
||||
*/
|
||||
CPTextSizingAlwaysNeedsSetFontBug = 1 << 3;
|
||||
|
||||
|
||||
var USER_AGENT = "",
|
||||
PLATFORM_ENGINE = CPUnknownBrowserEngine,
|
||||
PLATFORM_FEATURES = [],
|
||||
PLATFORM_BUGS = 0,
|
||||
PLATFORM_STYLE_JS_PROPERTIES = {};
|
||||
PLATFORM_FEATURES = 0,
|
||||
PLATFORM_BUGS = 0;
|
||||
|
||||
// default these features to true
|
||||
PLATFORM_FEATURES[CPInputTypeCanBeChangedFeature] = YES;
|
||||
PLATFORM_FEATURES[CPInputSetFontOutsideOfDOM] = YES;
|
||||
PLATFORM_FEATURES[CPAltEnterTextAreaFeature] = YES;
|
||||
|
||||
PLATFORM_FEATURES |= CPInputTypeCanBeChangedFeature;
|
||||
PLATFORM_FEATURES |= CPInputSetFontOutsideOfDOM;
|
||||
|
||||
if (typeof window !== "undefined" && typeof window.navigator !== "undefined")
|
||||
USER_AGENT = window.navigator.userAgent;
|
||||
@@ -122,66 +96,44 @@ if (typeof window !== "undefined" && typeof window.navigator !== "undefined")
|
||||
// Opera
|
||||
if (typeof window !== "undefined" && window.opera)
|
||||
{
|
||||
PLATFORM_ENGINE |= CPOperaBrowserEngine;
|
||||
PLATFORM_ENGINE = CPOperaBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasDrawFeature;
|
||||
}
|
||||
|
||||
// Internet Explorer
|
||||
else if (typeof window !== "undefined" && (window.attachEvent || (!(window.ActiveXObject) && "ActiveXObject" in window))) // Must follow Opera check.
|
||||
else if (typeof window !== "undefined" && window.attachEvent) // Must follow Opera check.
|
||||
{
|
||||
PLATFORM_ENGINE |= CPInternetExplorerBrowserEngine;
|
||||
PLATFORM_ENGINE = CPInternetExplorerBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with IE (no known independent tests)
|
||||
PLATFORM_FEATURES[CPVMLFeature] = YES;
|
||||
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
|
||||
PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPVMLFeature;
|
||||
PLATFORM_FEATURES |= CPJavaScriptRemedialKeySupport;
|
||||
PLATFORM_FEATURES |= CPJavaScriptShadowFeature;
|
||||
|
||||
PLATFORM_FEATURES[CPOpacityRequiresFilterFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPOpacityRequiresFilterFeature;
|
||||
|
||||
PLATFORM_FEATURES[CPInputTypeCanBeChangedFeature] = NO;
|
||||
PLATFORM_FEATURES &= ~CPInputTypeCanBeChangedFeature;
|
||||
|
||||
// Tested in Internet Explore 8 and 9.
|
||||
PLATFORM_FEATURES[CPInputSetFontOutsideOfDOM] = NO;
|
||||
|
||||
// IE allows free clipboard access.
|
||||
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES;
|
||||
PLATFORM_FEATURES &= ~CPInputSetFontOutsideOfDOM;
|
||||
}
|
||||
|
||||
// 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)
|
||||
// WebKit
|
||||
else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
{
|
||||
PLATFORM_ENGINE |= CPWebKitBrowserEngine;
|
||||
PLATFORM_ENGINE = CPWebKitBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with WebKit (no known independent tests)
|
||||
PLATFORM_FEATURES[CPCSSRGBAFeature] = YES;
|
||||
PLATFORM_FEATURES[CPHTMLContentEditableFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPCSSRGBAFeature;
|
||||
PLATFORM_FEATURES |= CPHTMLContentEditableFeature;
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES;
|
||||
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = NO;
|
||||
PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES;
|
||||
if (USER_AGENT.indexOf("Chrome") === -1)
|
||||
PLATFORM_FEATURES |= CPHTMLDragAndDropFeature;
|
||||
|
||||
PLATFORM_FEATURES |= CPJavaScriptClipboardEventsFeature;
|
||||
PLATFORM_FEATURES |= CPJavaScriptClipboardAccessFeature;
|
||||
PLATFORM_FEATURES |= CPJavaScriptShadowFeature;
|
||||
|
||||
var versionStart = USER_AGENT.indexOf("AppleWebKit/") + "AppleWebKit/".length,
|
||||
versionEnd = USER_AGENT.indexOf(" ", versionStart),
|
||||
@@ -191,32 +143,22 @@ else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
minorVersion = parseInt(versionString.substr(versionDivision + 1));
|
||||
|
||||
if ((USER_AGENT.indexOf("Safari") !== CPNotFound && (majorVersion > 525 || (majorVersion === 525 && minorVersion > 14))) || USER_AGENT.indexOf("Chrome") !== CPNotFound)
|
||||
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
|
||||
PLATFORM_FEATURES |= CPJavaScriptRemedialKeySupport;
|
||||
|
||||
// FIXME this is a terrible hack to get around this bug:
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=21548
|
||||
if (![CPPlatform isBrowser])
|
||||
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
|
||||
PLATFORM_FEATURES |= CPJavaScriptRemedialKeySupport;
|
||||
|
||||
if (majorVersion < 532 || (majorVersion === 532 && minorVersion < 6))
|
||||
PLATFORM_FEATURES[CPHTML5DragAndDropSourceYOffBy1] = YES;
|
||||
PLATFORM_FEATURES |= CPHTML5DragAndDropSourceYOffBy1;
|
||||
|
||||
// This is supposedly fixed in webkit r123603. Seems to work in Chrome 21 but not Safari 6.0.
|
||||
if (majorVersion < 537)
|
||||
PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES;
|
||||
PLATFORM_FEATURES |= CPInput1PxLeftPadding;
|
||||
|
||||
if (USER_AGENT.indexOf("Chrome") === CPNotFound)
|
||||
{
|
||||
PLATFORM_FEATURES[CPSOPDisabledFromFileURLs] = YES;
|
||||
PLATFORM_FEATURES[CPHTMLDragAndDropFeature] = YES;
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=75891
|
||||
PLATFORM_BUGS |= CPJavaScriptPasteRequiresEditableTarget;
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=39689
|
||||
PLATFORM_BUGS |= CPJavaScriptPasteCantRefocus;
|
||||
PLATFORM_BUGS |= CPTextSizingAlwaysNeedsSetFontBug;
|
||||
}
|
||||
else if ((window.chrome || (window.Intl && Intl.v8BreakIterator)) && 'CSS' in window)
|
||||
PLATFORM_ENGINE |= CPBlinkBrowserEngine;
|
||||
PLATFORM_FEATURES |= CPSOPDisabledFromFileURLs;
|
||||
|
||||
// Assume this bug was introduced around Safari 5.1/Chrome 16. This could probably be tighter.
|
||||
if (majorVersion > 533)
|
||||
@@ -226,98 +168,67 @@ else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
// KHTML
|
||||
else if (USER_AGENT.indexOf("KHTML") != -1) // Must follow WebKit check.
|
||||
{
|
||||
PLATFORM_ENGINE |= CPKHTMLBrowserEngine;
|
||||
PLATFORM_ENGINE = CPKHTMLBrowserEngine;
|
||||
}
|
||||
|
||||
// Gecko
|
||||
else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
|
||||
{
|
||||
PLATFORM_ENGINE |= CPGeckoBrowserEngine;
|
||||
PLATFORM_ENGINE = CPGeckoBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES;
|
||||
PLATFORM_FEATURES[CPBackspaceTriggersPageBack] = YES;
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasDrawFeature;
|
||||
|
||||
var index = USER_AGENT.indexOf("Firefox"),
|
||||
version = (index === -1) ? 2.0 : parseFloat(USER_AGENT.substring(index + "Firefox".length + 1));
|
||||
|
||||
if (version >= 3.0)
|
||||
PLATFORM_FEATURES[CPCSSRGBAFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPCSSRGBAFeature;
|
||||
|
||||
if (version < 3.0)
|
||||
PLATFORM_FEATURES[CPJavaScriptMouseWheelValues_8_15] = YES;
|
||||
|
||||
if (version >= 66)
|
||||
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
|
||||
PLATFORM_FEATURES |= CPJavaScriptMouseWheelValues_8_15;
|
||||
|
||||
// Some day this might be fixed and should be version prefixed. No known fixed version yet.
|
||||
PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES;
|
||||
|
||||
PLATFORM_FEATURES[CPAltEnterTextAreaFeature] = NO;
|
||||
// This was supposed to be added in Firefox 22, but when testing with the latest beta as of 2013-06-14
|
||||
// it does not seem to work. It seems to exhibit the CPJavaScriptPasteRequiresEditableTarget problem,
|
||||
// and in addition doesn't seem to work with our native copy code either.
|
||||
/*if (version >= 22.0)
|
||||
{
|
||||
PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES;
|
||||
// TODO File a bug at https://bugzilla.mozilla.org/. In other browsers, one can return "false" from the
|
||||
// beforepaste event to indicate a paste should be enabled even that the DOMEvent.target is not editable.
|
||||
PLATFORM_BUGS |= CPJavaScriptPasteRequiresEditableTarget;
|
||||
}*/
|
||||
PLATFORM_FEATURES |= CPInput1PxLeftPadding;
|
||||
}
|
||||
|
||||
// Feature-specific checks
|
||||
// Feature Specific Checks
|
||||
if (typeof document != "undefined")
|
||||
{
|
||||
var canvasElement = document.createElement("canvas");
|
||||
|
||||
// Detect canvas support
|
||||
// Detect Canvas Support
|
||||
if (canvasElement && canvasElement.getContext)
|
||||
{
|
||||
PLATFORM_FEATURES[CPHTMLCanvasFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPHTMLCanvasFeature;
|
||||
|
||||
// Any browser that supports canvas supports CSS opacity
|
||||
PLATFORM_FEATURES[CPOpacityRequiresFilterFeature] = NO;
|
||||
|
||||
// Detect canvas setTransform/transform support
|
||||
// Detect Canvas setTransform/transform support
|
||||
var context = document.createElement("canvas").getContext("2d");
|
||||
|
||||
if (context && context.setTransform && context.transform)
|
||||
PLATFORM_FEATURES[CPJavaScriptCanvasTransformFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasTransformFeature;
|
||||
}
|
||||
|
||||
var DOMElement = document.createElement("div");
|
||||
|
||||
// Detect whether we have innerText or textContent (or neither)
|
||||
if (DOMElement.innerText != undefined)
|
||||
PLATFORM_FEATURES[CPJavaScriptInnerTextFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPJavaScriptInnerTextFeature;
|
||||
else if (DOMElement.textContent != undefined)
|
||||
PLATFORM_FEATURES[CPJavaScriptTextContentFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPJavaScriptTextContentFeature;
|
||||
|
||||
var DOMInputElement = document.createElement("input");
|
||||
|
||||
if ("oninput" in DOMInputElement)
|
||||
PLATFORM_FEATURES[CPInputOnInputEventFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPInputOnInputEventFeature;
|
||||
else if (typeof DOMInputElement.setAttribute === "function")
|
||||
{
|
||||
DOMInputElement.setAttribute("oninput", "return;");
|
||||
|
||||
if (typeof DOMInputElement.oninput === "function")
|
||||
PLATFORM_FEATURES[CPInputOnInputEventFeature] = YES;
|
||||
PLATFORM_FEATURES |= CPInputOnInputEventFeature;
|
||||
}
|
||||
|
||||
// Detect FileAPI support
|
||||
if (typeof DOMInputElement.setAttribute === "function")
|
||||
{
|
||||
DOMInputElement.setAttribute("type", "file");
|
||||
PLATFORM_FEATURES[CPFileAPIFeature] = !!DOMInputElement["files"];
|
||||
}
|
||||
else
|
||||
PLATFORM_FEATURES[CPFileAPIFeature] = NO;
|
||||
}
|
||||
|
||||
function CPFeatureIsCompatible(aFeature)
|
||||
{
|
||||
return !!PLATFORM_FEATURES[aFeature];
|
||||
return PLATFORM_FEATURES & aFeature;
|
||||
}
|
||||
|
||||
function CPPlatformHasBug(aBug)
|
||||
@@ -327,7 +238,7 @@ function CPPlatformHasBug(aBug)
|
||||
|
||||
function CPBrowserIsEngine(anEngine)
|
||||
{
|
||||
return PLATFORM_ENGINE & anEngine;
|
||||
return PLATFORM_ENGINE === anEngine;
|
||||
}
|
||||
|
||||
function CPBrowserIsOperatingSystem(anOperatingSystem)
|
||||
@@ -362,149 +273,3 @@ else
|
||||
CPUndoKeyEquivalentModifierMask = CPControlKeyMask;
|
||||
CPRedoKeyEquivalentModifierMask = CPControlKeyMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a feature with the given value.
|
||||
*/
|
||||
function CPSetPlatformFeature(aFeature, aBool)
|
||||
{
|
||||
PLATFORM_FEATURES[aFeature] = aBool;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the properly prefixed JS property for the given name. E.g. in a webkit browser,
|
||||
CPBrowserStyleProperty('transition') -> WebkitTransition
|
||||
|
||||
While technically not a style property, style related event handler names are also supported.
|
||||
CPBrowserStyleProperty('transitionend') -> 'webkitTransitionEnd'
|
||||
|
||||
CSS is only available in platform(dom), so don't rely too heavily on it.
|
||||
*/
|
||||
function CPBrowserStyleProperty(aProperty)
|
||||
{
|
||||
var lowerProperty = aProperty.toLowerCase();
|
||||
|
||||
if (PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] === undefined)
|
||||
{
|
||||
var r = nil;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
var testElement = document.createElement('div');
|
||||
|
||||
switch (lowerProperty)
|
||||
{
|
||||
case 'transitionend':
|
||||
var candidates = {
|
||||
'WebkitTransition' : 'webkitTransitionEnd',
|
||||
'MozTransition' : 'transitionend',
|
||||
'OTransition' : 'oTransitionEnd',
|
||||
'msTransition' : 'MSTransitionEnd',
|
||||
'transition' : 'transitionend'
|
||||
};
|
||||
|
||||
r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transition']] || nil;
|
||||
break;
|
||||
|
||||
case 'transformorigin':
|
||||
|
||||
var candidates = {
|
||||
'WebkitTransform' : 'WebkitTransformOrigin',
|
||||
'MozTransform' : 'MozTransformOrigin',
|
||||
'OTransform' : 'OTransformOrigin',
|
||||
'msTransform' : 'MSTransformOrigin',
|
||||
'transform' : 'transformOrigin'
|
||||
};
|
||||
|
||||
r = candidates[PLATFORM_STYLE_JS_PROPERTIES['transform']] || nil;
|
||||
break;
|
||||
|
||||
case 'animationend':
|
||||
var candidates = {
|
||||
'WebkitAnimation' : 'webkitAnimationEnd',
|
||||
'MozAnimation' : 'animationend',
|
||||
'OAnimation' : 'oAnimationEnd',
|
||||
'msAnimation' : 'MSAnimationEnd',
|
||||
'animation' : 'animationend'
|
||||
};
|
||||
|
||||
r = candidates[PLATFORM_STYLE_JS_PROPERTIES['animation']] || nil;
|
||||
break;
|
||||
|
||||
default:
|
||||
var prefixes = ["Webkit", "Moz", "O", "ms"],
|
||||
strippedProperty = aProperty.split('-').join(' '),
|
||||
capProperty = [strippedProperty capitalizedString].split(' ').join('');
|
||||
|
||||
for (var i = 0; i < prefixes.length; i++)
|
||||
{
|
||||
// First check if the property is already valid without being formatted, otherwise try the capitalized property
|
||||
if (prefixes[i] + aProperty in testElement.style)
|
||||
{
|
||||
r = prefixes[i] + aProperty;
|
||||
break;
|
||||
}
|
||||
else if (prefixes[i] + capProperty in testElement.style)
|
||||
{
|
||||
r = prefixes[i] + capProperty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!r && lowerProperty in testElement.style)
|
||||
r = lowerProperty;
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
PLATFORM_STYLE_JS_PROPERTIES[lowerProperty] = r;
|
||||
}
|
||||
|
||||
return PLATFORM_STYLE_JS_PROPERTIES[lowerProperty];
|
||||
}
|
||||
|
||||
function CPBrowserCSSProperty(aProperty)
|
||||
{
|
||||
var browserProperty = CPBrowserStyleProperty(aProperty);
|
||||
|
||||
if (!browserProperty)
|
||||
return nil;
|
||||
|
||||
var prefixes = {
|
||||
'Webkit': '-webkit-',
|
||||
'Moz': '-moz-',
|
||||
'O': '-o-',
|
||||
'ms': '-ms-'
|
||||
};
|
||||
|
||||
for (var prefix in prefixes)
|
||||
{
|
||||
if (browserProperty.substring(0, prefix.length) == prefix)
|
||||
{
|
||||
var browserPropertyWithoutPrefix = browserProperty.substring(prefix.length),
|
||||
parts = browserPropertyWithoutPrefix.match(/[A-Z][a-z]+/g);
|
||||
|
||||
// If there were any capitalized words in the browserProperty, insert a "-" between each one
|
||||
if (parts && parts.length > 0)
|
||||
browserPropertyWithoutPrefix = parts.join("-");
|
||||
|
||||
return prefixes[prefix] + browserPropertyWithoutPrefix.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
var parts = browserProperty.match(/[A-Z][a-z]+/g);
|
||||
|
||||
if (parts && parts.length > 0)
|
||||
browserProperty = parts.join("-");
|
||||
|
||||
return browserProperty.toLowerCase();
|
||||
}
|
||||
|
||||
function CPBrowserBackingStorePixelRatio(context)
|
||||
{
|
||||
return context.webkitBackingStorePixelRatio ||
|
||||
context.mozBackingStorePixelRatio ||
|
||||
context.msBackingStorePixelRatio ||
|
||||
context.oBackingStorePixelRatio ||
|
||||
context.backingStorePixelRatio || 1;
|
||||
}
|
||||
|
||||
+60
-292
@@ -20,39 +20,24 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPFormatter.j>
|
||||
@import <Foundation/CPTimer.j>
|
||||
#import "../Foundation/Ref.h"
|
||||
|
||||
@import "../Foundation/CPFormatter.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPShadow.j"
|
||||
@import "CPText.j"
|
||||
@import "CPView.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
@import "CPTrackingArea.j"
|
||||
|
||||
@class CPFont
|
||||
CPLeftTextAlignment = 0;
|
||||
CPRightTextAlignment = 1;
|
||||
CPCenterTextAlignment = 2;
|
||||
CPJustifiedTextAlignment = 3;
|
||||
CPNaturalTextAlignment = 4;
|
||||
|
||||
@global CPApp
|
||||
|
||||
@protocol CPControlTextEditingDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (void)controlTextDidBeginEditing:(CPNotification)aNotification;
|
||||
- (void)controlTextDidChange:(CPNotification)aNotification;
|
||||
- (void)controlTextDidEndEditing:(CPNotification)aNotification;
|
||||
- (void)controlTextDidFocus:(CPNotification)aNotification;
|
||||
- (void)controlTextDidBlur:(CPNotification)aNotification;
|
||||
- (BOOL)control:(CPControl)control didFailToFormatString:(CPString)string errorDescription:(CPString)error;
|
||||
|
||||
@end
|
||||
|
||||
@typedef CPControlSize
|
||||
CPRegularControlSize = 0;
|
||||
CPSmallControlSize = 1;
|
||||
CPMiniControlSize = 2;
|
||||
|
||||
// To get the theme state corresponding to a control size, use CPControlSizeThemeStates[controlSize]
|
||||
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini];
|
||||
|
||||
@typedef CPLineBreakMode
|
||||
CPLineBreakByWordWrapping = 0;
|
||||
CPLineBreakByCharWrapping = 1;
|
||||
CPLineBreakByClipping = 2;
|
||||
@@ -60,13 +45,11 @@ CPLineBreakByTruncatingHead = 3;
|
||||
CPLineBreakByTruncatingTail = 4;
|
||||
CPLineBreakByTruncatingMiddle = 5;
|
||||
|
||||
@typedef CPVerticalTextAlignment
|
||||
CPTopVerticalTextAlignment = 1;
|
||||
CPCenterVerticalTextAlignment = 2;
|
||||
CPBottomVerticalTextAlignment = 3;
|
||||
|
||||
// Deprecated for use with images, use the CPImageScale constants
|
||||
@typedef CPImageScaling
|
||||
CPScaleProportionally = 0;
|
||||
CPScaleToFit = 1;
|
||||
CPScaleNone = 2;
|
||||
@@ -76,7 +59,6 @@ CPImageScaleAxesIndependently = 1;
|
||||
CPImageScaleNone = 2;
|
||||
CPImageScaleProportionallyUpOrDown = 3;
|
||||
|
||||
@typedef CPCellImagePosition
|
||||
CPNoImage = 0;
|
||||
CPImageOnly = 1;
|
||||
CPImageLeft = 2;
|
||||
@@ -122,27 +104,32 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
BOOL _trackingWasWithinFrame;
|
||||
unsigned _trackingMouseDownFlags;
|
||||
CGPoint _previousTrackingLocation;
|
||||
|
||||
CPControlSize _controlSize;
|
||||
|
||||
CPWritingDirection _baseWritingDirection @accessors(property=baseWritingDirection);
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"alignment": CPLeftTextAlignment,
|
||||
@"vertical-alignment": CPTopVerticalTextAlignment,
|
||||
@"line-break-mode": CPLineBreakByClipping,
|
||||
@"text-color": [CPColor blackColor],
|
||||
@"font": [CPNull null],
|
||||
@"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)
|
||||
};
|
||||
return [CPDictionary dictionaryWithObjects:[CPLeftTextAlignment,
|
||||
CPTopVerticalTextAlignment,
|
||||
CPLineBreakByClipping,
|
||||
[CPColor blackColor],
|
||||
[CPFont systemFontOfSize:CPFontCurrentSystemSize],
|
||||
[CPNull null],
|
||||
_CGSizeMakeZero(),
|
||||
CPImageLeft,
|
||||
CPScaleToFit,
|
||||
_CGSizeMakeZero(),
|
||||
_CGSizeMake(-1.0, -1.0)]
|
||||
forKeys:[@"alignment",
|
||||
@"vertical-alignment",
|
||||
@"line-break-mode",
|
||||
@"text-color",
|
||||
@"font",
|
||||
@"text-shadow-color",
|
||||
@"text-shadow-offset",
|
||||
@"image-position",
|
||||
@"image-scaling",
|
||||
@"min-size",
|
||||
@"max-size"]];
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
@@ -161,18 +148,16 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
[self exposeBinding:@"enabled"];
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (aBinding === CPValueBinding)
|
||||
if (theBinding === CPValueBinding)
|
||||
return [_CPValueBinder class];
|
||||
else if ([aBinding hasPrefix:CPEnabledBinding])
|
||||
return [CPMultipleValueAndBinding class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
/*!
|
||||
Reverse set the binding if the CPContinuouslyUpdatesValueBindingOption is set.
|
||||
Reverse set the binding iff the CPContinuouslyUpdatesValueBindingOption is set.
|
||||
*/
|
||||
- (void)_continuouslyReverseSetBinding
|
||||
{
|
||||
@@ -197,97 +182,13 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_sendActionOn = CPLeftMouseUpMask;
|
||||
_sendActionOn = CPLeftMouseUpMask;
|
||||
_trackingMouseDownFlags = 0;
|
||||
|
||||
[self setControlSize:CPThemeStateControlSizeRegular];
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: Control Size
|
||||
|
||||
/*!
|
||||
Returns the control's control size
|
||||
*/
|
||||
- (CPControlSize)controlSize
|
||||
{
|
||||
return _controlSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the control's size.
|
||||
@param aControlSize the control's size
|
||||
*/
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
if (_controlSize === aControlSize)
|
||||
return;
|
||||
|
||||
[self unsetThemeState:[self _controlSizeThemeState]];
|
||||
_controlSize = aControlSize;
|
||||
[self setThemeState:[self _controlSizeThemeState]];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets the current theme state according to the current controlSize.
|
||||
@return a CPThemeState
|
||||
*/
|
||||
- (ThemeState)_controlSizeThemeState
|
||||
{
|
||||
switch (_controlSize)
|
||||
{
|
||||
case CPSmallControlSize:
|
||||
return CPThemeStateControlSizeSmall;
|
||||
|
||||
case CPMiniControlSize:
|
||||
return CPThemeStateControlSizeMini;
|
||||
|
||||
case CPRegularControlSize:
|
||||
default:
|
||||
return CPThemeStateControlSizeRegular;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Change frame size according to the theme control size theme constraints
|
||||
Basically for height to min-size.
|
||||
*/
|
||||
- (void)_sizeToControlSize
|
||||
{
|
||||
var frameSize = [self frameSize],
|
||||
minSize = [self currentValueForThemeAttribute:@"min-size"],
|
||||
maxSize = [self currentValueForThemeAttribute:@"max-size"];
|
||||
|
||||
if (minSize.width > 0)
|
||||
{
|
||||
frameSize.width = MAX(minSize.width, frameSize.width);
|
||||
|
||||
if (maxSize.width > 0)
|
||||
frameSize.width = MIN(maxSize.width, frameSize.width);
|
||||
}
|
||||
|
||||
if (minSize.height > 0)
|
||||
{
|
||||
frameSize.height = MAX(minSize.height, frameSize.height);
|
||||
|
||||
if (maxSize.height > 0)
|
||||
frameSize.height = MIN(maxSize.height, frameSize.height);
|
||||
}
|
||||
|
||||
[self setFrameSize:frameSize];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
|
||||
/*!
|
||||
Sets the receiver's target action.
|
||||
|
||||
@@ -334,9 +235,6 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
{
|
||||
[self _reverseSetBinding];
|
||||
|
||||
var binding = [CPBinder getBinding:CPTargetBinding forObject:self];
|
||||
[binding invokeAction];
|
||||
|
||||
return [CPApp sendAction:anAction to:anObject from:self];
|
||||
}
|
||||
|
||||
@@ -426,11 +324,11 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
_previousTrackingLocation = currentLocation;
|
||||
}
|
||||
|
||||
- (void)setState:(CPInteger)state
|
||||
- (void)setState:(int)state
|
||||
{
|
||||
}
|
||||
|
||||
- (CPInteger)nextState
|
||||
- (int)nextState
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -499,14 +397,6 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
[self highlight:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Enabled controls accept first mouse by default.
|
||||
*/
|
||||
- (BOOL)acceptsFirstMouse:(CPEvent)anEvent
|
||||
{
|
||||
return [self isEnabled];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
@@ -626,15 +516,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);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -643,7 +533,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;
|
||||
@@ -655,10 +545,10 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
{
|
||||
value = nil;
|
||||
|
||||
if ([_formatter getObjectValue:@ref(value) forString:aString errorDescription:nil] === NO)
|
||||
if ([_formatter getObjectValue:AT_REF(value) forString:aString errorDescription:nil] === NO)
|
||||
{
|
||||
// If the given string is non-empty and doesn't work, Cocoa tries an empty string.
|
||||
if (!aString || [_formatter getObjectValue:@ref(value) forString:@"" errorDescription:nil] === NO)
|
||||
if (!aString || [_formatter getObjectValue:AT_REF(value) forString:@"" errorDescription:nil] === NO)
|
||||
value = undefined; // Means the value is invalid
|
||||
}
|
||||
}
|
||||
@@ -711,7 +601,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
if ([note object] != self)
|
||||
return;
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidBeginEditingNotification object:self userInfo:@{"CPFieldEditor": [note object]}];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidBeginEditingNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
|
||||
}
|
||||
|
||||
- (void)textDidChange:(CPNotification)note
|
||||
@@ -720,7 +610,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
if ([note object] != self)
|
||||
return;
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidChangeNotification object:self userInfo:@{"CPFieldEditor": [note object]}];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidChangeNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
|
||||
}
|
||||
|
||||
- (void)textDidEndEditing:(CPNotification)note
|
||||
@@ -731,49 +621,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
|
||||
[self _reverseSetBinding];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidEndEditingNotification object:self userInfo:[note userInfo]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Return the currentTextMovement needed by the delegate textDidEndEditing
|
||||
This is going to check the currentEvent of the CPApp
|
||||
*/
|
||||
- (unsigned)_currentTextMovement
|
||||
{
|
||||
var currentEvent = [CPApp currentEvent],
|
||||
keyCode = [currentEvent keyCode],
|
||||
modifierFlags = [currentEvent modifierFlags];
|
||||
|
||||
switch (keyCode)
|
||||
{
|
||||
case CPEscapeKeyCode:
|
||||
return CPCancelTextMovement;
|
||||
|
||||
case CPLeftArrowKeyCode:
|
||||
return CPLeftTextMovement;
|
||||
|
||||
case CPRightArrowKeyCode:
|
||||
return CPRightTextMovement;
|
||||
|
||||
case CPUpArrowKeyCode:
|
||||
return CPUpTextMovement;
|
||||
|
||||
case CPDownArrowKeyCode:
|
||||
return CPDownTextMovement;
|
||||
|
||||
case CPReturnKeyCode:
|
||||
return CPReturnTextMovement;
|
||||
|
||||
case CPTabKeyCode:
|
||||
if (modifierFlags & CPShiftKeyMask)
|
||||
return CPBacktabTextMovement;
|
||||
|
||||
return CPTabTextMovement;
|
||||
|
||||
default:
|
||||
return CPOtherTextMovement;
|
||||
}
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidEndEditingNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -809,7 +657,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
CPBottomVerticalTextAlignment
|
||||
</pre>
|
||||
*/
|
||||
- (void)setVerticalAlignment:(CPVerticalTextAlignment)alignment
|
||||
- (void)setVerticalAlignment:(CPTextVerticalAlignment)alignment
|
||||
{
|
||||
[self setValue:alignment forThemeAttribute:@"vertical-alignment"];
|
||||
}
|
||||
@@ -854,12 +702,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"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -917,7 +760,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
*/
|
||||
- (CPFont)font
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"font"] || [CPFont systemFontForControlSize:_controlSize];
|
||||
return [self valueForThemeAttribute:@"font"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -964,7 +807,7 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
/*!
|
||||
Returns the image scaling of the control.
|
||||
*/
|
||||
- (CPUInteger)imageScaling
|
||||
- (CPImageScaling)imageScaling
|
||||
{
|
||||
return [self valueForThemeAttribute:@"image-scaling"];
|
||||
}
|
||||
@@ -1022,81 +865,16 @@ var CPControlBlackColor = [CPColor blackColor];
|
||||
return [self hasThemeState:CPThemeStateHighlighted];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Base writing direction
|
||||
|
||||
/*!
|
||||
Sets the initial writing direction of the receiver
|
||||
@param writingDirection - It could be CPWritingDirectionNatural, CPWritingDirectionLeftToRight, CPWritingDirectionRightToLeft
|
||||
*/
|
||||
- (void)setBaseWritingDirection:(CPWritingDirection)writingDirection
|
||||
{
|
||||
if (writingDirection == _baseWritingDirection)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"baseWritingDirection"];
|
||||
_baseWritingDirection = writingDirection;
|
||||
[self didChangeValueForKey:@"baseWritingDirection"];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
|
||||
var style;
|
||||
|
||||
switch (_baseWritingDirection)
|
||||
{
|
||||
case CPWritingDirectionNatural:
|
||||
style = "initial";
|
||||
break;
|
||||
|
||||
case CPWritingDirectionLeftToRight:
|
||||
style = "ltr";
|
||||
break;
|
||||
|
||||
case CPWritingDirectionRightToLeft:
|
||||
style = "rtl";
|
||||
break;
|
||||
|
||||
default:
|
||||
style = "initial";
|
||||
}
|
||||
|
||||
_DOMElement.style.direction = style;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPControl (CPTrackingArea)
|
||||
{
|
||||
CPTrackingArea _controlTrackingArea;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (_controlTrackingArea)
|
||||
[self removeTrackingArea:_controlTrackingArea];
|
||||
|
||||
_controlTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
|
||||
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:_controlTrackingArea];
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPControlActionKey = @"CPControlActionKey",
|
||||
CPControlControlSizeKey = @"CPControlControlSizeKey",
|
||||
var CPControlValueKey = @"CPControlValueKey",
|
||||
CPControlControlStateKey = @"CPControlControlStateKey",
|
||||
CPControlFormatterKey = @"CPControlFormatterKey",
|
||||
CPControlIsEnabledKey = @"CPControlIsEnabledKey",
|
||||
CPControlSendActionOnKey = @"CPControlSendActionOnKey",
|
||||
CPControlSendsActionOnEndEditingKey = @"CPControlSendsActionOnEndEditingKey",
|
||||
CPControlTargetKey = @"CPControlTargetKey",
|
||||
CPControlValueKey = @"CPControlValueKey",
|
||||
CPControlBaseWrittingDirectionKey = @"CPControlBaseWrittingDirectionKey";
|
||||
CPControlActionKey = @"CPControlActionKey",
|
||||
CPControlSendActionOnKey = @"CPControlSendActionOnKey",
|
||||
CPControlFormatterKey = @"CPControlFormatterKey",
|
||||
CPControlSendsActionOnEndEditingKey = @"CPControlSendsActionOnEndEditingKey",
|
||||
|
||||
__Deprecated__CPImageViewImageKey = @"CPImageViewImageKey";
|
||||
|
||||
@@ -1123,11 +901,6 @@ var CPControlActionKey = @"CPControlActionKey",
|
||||
[self setSendsActionOnEndEditing:[aCoder decodeBoolForKey:CPControlSendsActionOnEndEditingKey]];
|
||||
|
||||
[self setFormatter:[aCoder decodeObjectForKey:CPControlFormatterKey]];
|
||||
|
||||
[self setControlSize:[aCoder decodeIntForKey:CPControlControlSizeKey]];
|
||||
|
||||
[self setBaseWritingDirection:[aCoder decodeIntForKey:CPControlBaseWrittingDirectionKey]];
|
||||
[self updateTrackingAreas];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -1147,24 +920,19 @@ 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];
|
||||
|
||||
[aCoder encodeInt:_baseWritingDirection forKey:CPControlBaseWrittingDirectionKey];
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -64,7 +64,7 @@ var CPControllerDeclaredKeysKey = @"CPControllerDeclaredKeysKey";
|
||||
count = _editors.length;
|
||||
|
||||
for (; index < count; ++index)
|
||||
if (![[_editors objectAtIndex:index] commitEditing])
|
||||
if (![[_editors objectAtIndex:i] commitEditing])
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
|
||||
+1
-3
@@ -37,8 +37,6 @@
|
||||
CPString _expires;
|
||||
}
|
||||
|
||||
@global document
|
||||
|
||||
/*!
|
||||
Initializes a cookie with a given name \c aName.
|
||||
@param the name for the cookie
|
||||
@@ -96,7 +94,7 @@
|
||||
domain = "";
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
document.cookie = _cookieName + "=" + value + expires + "; path=/" + domain;
|
||||
document.cookie = _cookieName+"="+value+expires+"; path=/"+domain;
|
||||
#else
|
||||
_cookieValue = value;
|
||||
_expires = expires;
|
||||
|
||||
Regular → Executable
+32
-188
@@ -22,21 +22,10 @@ Cursor support by browser:
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import "CPImage.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
@global CPApp
|
||||
|
||||
var currentCursor = nil,
|
||||
cursorStack = [],
|
||||
cursors = {},
|
||||
ieCursorMap = {};
|
||||
|
||||
@typedef CPCursorPlatform
|
||||
CPCursorPlatformNone = 0;
|
||||
CPCursorPlatformMac = 1;
|
||||
CPCursorPlatformWindows = 2;
|
||||
CPCursorPlatformBoth = 3;
|
||||
cursors = {};
|
||||
|
||||
@implementation CPCursor : CPObject
|
||||
{
|
||||
@@ -47,20 +36,6 @@ CPCursorPlatformBoth = 3;
|
||||
BOOL _isSetOnMouseExited @accessors(readwrite, getter=isSetOnMouseExited, setter=setOnMouseExited:);
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== CPCursor)
|
||||
return;
|
||||
|
||||
// IE < 9 does not support some CSS cursors, we map them to supported ones
|
||||
ieCursorMap = {
|
||||
"ew-resize": "e-resize",
|
||||
"ns-resize": "n-resize",
|
||||
"nesw-resize": "ne-resize",
|
||||
"nwse-resize": "nw-resize"
|
||||
};
|
||||
}
|
||||
|
||||
- (id)initWithCSSString:(CPString)aString
|
||||
{
|
||||
if (self = [super init])
|
||||
@@ -69,30 +44,23 @@ CPCursorPlatformBoth = 3;
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Init a cursor with the given image and hotspot.
|
||||
hotspot is supported in CSS3 (but not IE).
|
||||
*/
|
||||
- (id)initWithImage:(CPImage)image hotSpot:(CGPoint)hotSpot
|
||||
// hotspot is supported in CSS3 (but not IE).
|
||||
- (id)initWithImage:(CPImage)image hotSpot:(CPPoint)hotSpot
|
||||
{
|
||||
_hotSpot = hotSpot;
|
||||
_image = image;
|
||||
return [self initWithCSSString:"url(" + [_image filename] + ")" + hotSpot.x + " " + hotSpot.y + ", auto"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Init a cursor with the given image and hotspot. This is provided
|
||||
for compliance with Cocoa. Note that foregroundColor and backgroundColor are ignored
|
||||
(as they are in Cocoa). See http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSCursor_Class/Reference/Reference.html
|
||||
*/
|
||||
- (id)initWithImage:(CPImage)image foregroundColorHint:(CPColor)foregroundColor backgroundColorHint:(CPColor)backgroundColor hotSpot:(CGPoint)aHotSpot
|
||||
// foregroundColor and backgroundColor are ignored in Cocoa as well. See http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSCursor_Class/Reference/Reference.html
|
||||
- (id)initWithImage:(CPImage)image foregroundColorHint:(CPColor)foregroundColor backgroundColorHint:(CPColor)backgroundColor hotSpot:(CPPoint)aHotSpot
|
||||
{
|
||||
return [self initWithImage:image hotSpot:aHotSpot];
|
||||
return [self initWithImage:image hotSpot:hotSpot];
|
||||
}
|
||||
|
||||
+ (void)hide
|
||||
{
|
||||
[self _setCursorCSS:@"none"]; // Not supported in IE < 9
|
||||
[self _setCursorCSS:"none"]; // Not supported in IE
|
||||
}
|
||||
|
||||
+ (void)unhide
|
||||
@@ -124,15 +92,11 @@ CPCursorPlatformBoth = 3;
|
||||
|
||||
- (void)push
|
||||
{
|
||||
cursorStack.push(self);
|
||||
currentCursor = self;
|
||||
currentCursor = cursorStack.push(self);
|
||||
}
|
||||
|
||||
- (void)set
|
||||
{
|
||||
if (currentCursor === self)
|
||||
return;
|
||||
|
||||
currentCursor = self;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
@@ -157,231 +121,111 @@ CPCursorPlatformBoth = 3;
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var platformWindows = [[CPPlatformWindow visiblePlatformWindows] allObjects];
|
||||
|
||||
for (var i = 0, count = [platformWindows count]; i < count; i++)
|
||||
platformWindows[i]._DOMBodyElement.style.cursor = aString;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Internal method that is used to return the system cursors. Caches the system cursors for performance.
|
||||
+ (CPCursor)_nativeSystemCursorWithName:(CPString)cursorName cssString:(CPString)aString
|
||||
+ (CPCursor)_systemCursorWithName:(CPString)cursorName cssString:(CPString)aString hasImage:(BOOL)doesHaveImage
|
||||
{
|
||||
var cursor = cursors[cursorName];
|
||||
|
||||
if (typeof cursor === "undefined")
|
||||
if (typeof cursor === 'undefined')
|
||||
{
|
||||
var cssString;
|
||||
|
||||
// IE <= 8 does not support some cursors, map them to supported cursors
|
||||
var ieLessThan9 = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPFeatureIsCompatible(CPHTMLCanvasFeature);
|
||||
|
||||
if (ieLessThan9)
|
||||
cssString = ieCursorMap[aString] || aString;
|
||||
if (doesHaveImage)
|
||||
cssString = @"url(" + [[CPBundle bundleForClass:self] resourcePath] + @"/CPCursor/" + cursorName + ".cur), " + aString;
|
||||
else
|
||||
cssString = aString;
|
||||
|
||||
cursors[cursorName] = cursor = [[CPCursor alloc] initWithCSSString:cssString];
|
||||
cssString = aString
|
||||
cursor = [[CPCursor alloc] initWithCSSString:cssString];
|
||||
cursors[cursorName] = cursor;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthwestCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nw-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthwestSoutheastCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nwse-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNortheastCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ne-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNortheastSouthwestCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nesw-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeSouthwestCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"sw-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeSoutheastCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"se-resize"];
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"pointer" 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"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeEastWestCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ew-resize"];
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"col-resize" hasImage:NO];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeUpDownCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-resize"];
|
||||
}
|
||||
|
||||
+ (CPCursor)resizeNorthSouthCursor
|
||||
{
|
||||
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ns-resize"];
|
||||
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-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
|
||||
|
||||
@@ -1,837 +0,0 @@
|
||||
/* CPDatePicker.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 "CPControl.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPTextField.j"
|
||||
@import "_CPDatePickerTextField.j"
|
||||
@import "_CPDatePickerCalendar.j"
|
||||
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPDate.j>
|
||||
@import <Foundation/CPLocale.j>
|
||||
@import <Foundation/CPTimeZone.j>
|
||||
|
||||
@class CPStepper
|
||||
@class CPApp
|
||||
|
||||
@global CPLocaleLanguageCode
|
||||
@global CPDateFormatterShortStyle
|
||||
|
||||
var CPDatePicker_validateProposedDateValue_timeInterval = 1 << 1;
|
||||
|
||||
CPSingleDateMode = 0;
|
||||
CPRangeDateMode = 1;
|
||||
|
||||
CPTextFieldAndStepperDatePickerStyle = 0;
|
||||
CPClockAndCalendarDatePickerStyle = 1;
|
||||
CPTextFieldDatePickerStyle = 2;
|
||||
|
||||
CPHourMinuteDatePickerElementFlag = 0x000c;
|
||||
CPHourMinuteSecondDatePickerElementFlag = 0x000e;
|
||||
CPTimeZoneDatePickerElementFlag = 0x0010;
|
||||
CPYearMonthDatePickerElementFlag = 0x00c0;
|
||||
CPYearMonthDayDatePickerElementFlag = 0x00e0;
|
||||
CPEraDatePickerElementFlag = 0x0100;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
This control displays a datepicker in a Cappuccino application
|
||||
*/
|
||||
@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 _invokedByUserEvent;
|
||||
unsigned _implementedCDatePickerDelegateMethods;
|
||||
BOOL _isTextual;
|
||||
id _datePickerComponent;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Theme methods
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return @"datePicker";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"bezel-color": [CPColor clearColor],
|
||||
@"border-width" : 1.0,
|
||||
@"border-color": [CPColor clearColor],
|
||||
@"content-inset": CGInsetMakeZero(),
|
||||
@"bezel-inset": CGInsetMakeZero(),
|
||||
@"datepicker-textfield-bezel-color": [CPColor clearColor],
|
||||
@"min-size-datepicker-textfield": CGSizeMakeZero(),
|
||||
@"content-inset-datepicker-textfield": CGInsetMakeZero(),
|
||||
@"content-inset-datepicker-textfield-separator": CGInsetMakeZero(),
|
||||
@"separator-content-inset": CGInsetMakeZero(),
|
||||
@"date-hour-margin": 5.0,
|
||||
@"stepper-margin": 5.0,
|
||||
@"bezel-color-calendar": [CPColor clearColor],
|
||||
@"title-text-color": [CPColor blackColor],
|
||||
@"title-text-shadow-color": [CPColor clearColor],
|
||||
@"title-text-shadow-offset": CGSizeMakeZero(),
|
||||
@"title-font": [CPNull null],
|
||||
@"weekday-text-color": [CPColor blackColor],
|
||||
@"weekday-text-shadow-color": [CPColor clearColor],
|
||||
@"weekday-text-shadow-offset": CGSizeMakeZero(),
|
||||
@"weekday-font": [CPNull null],
|
||||
@"arrow-image-left": [CPNull null],
|
||||
@"arrow-image-right": [CPNull null],
|
||||
@"arrow-image-left-highlighted": [CPNull null],
|
||||
@"arrow-image-right-highlighted": [CPNull null],
|
||||
@"arrow-inset": CGInsetMakeZero(),
|
||||
@"circle-image": [CPNull null],
|
||||
@"circle-image-highlighted": [CPNull null],
|
||||
@"tile-text-color": [CPColor blackColor],
|
||||
@"tile-text-shadow-color": [CPColor clearColor],
|
||||
@"tile-text-shadow-offset": CGSizeMakeZero(),
|
||||
@"tile-font": [CPNull null],
|
||||
@"size-tile": CGSizeMakeZero(),
|
||||
@"size-calendar": CGSizeMakeZero(),
|
||||
@"size-header": CGSizeMakeZero(),
|
||||
@"min-size-calendar": CGSizeMakeZero(),
|
||||
@"max-size-calendar": CGSizeMakeZero(),
|
||||
@"bezel-color-clock": [CPColor clearColor],
|
||||
@"clock-text-color": [CPColor blackColor],
|
||||
@"clock-text-shadow-color": [CPColor clearColor],
|
||||
@"clock-text-shadow-offset": CGSizeMakeZero(),
|
||||
@"clock-font": [CPNull null],
|
||||
@"second-hand-image": [CPNull null],
|
||||
@"hour-hand-image": [CPNull null],
|
||||
@"middle-hand-image": [CPNull null],
|
||||
@"minute-hand-image": [CPNull null],
|
||||
@"size-clock": CGSizeMakeZero(),
|
||||
@"second-hand-size": CGSizeMakeZero(),
|
||||
@"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
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (theBinding == CPValueBinding || theBinding == CPMinValueBinding || theBinding == CPMaxValueBinding)
|
||||
return [_CPDatePickerValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (CPString)_replacementKeyPathForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding == CPValueBinding)
|
||||
return @"dateValue";
|
||||
|
||||
if (aBinding == CPMinValueBinding)
|
||||
return @"minDate";
|
||||
|
||||
if (aBinding == CPMaxValueBinding)
|
||||
return @"maxDate";
|
||||
|
||||
return [super _replacementKeyPathForBinding:aBinding];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Init methods
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_drawsBackground = YES;
|
||||
_datePickerStyle = CPTextFieldAndStepperDatePickerStyle;
|
||||
_datePickerMode = CPSingleDateMode;
|
||||
_datePickerElements = CPYearMonthDayDatePickerElementFlag | CPHourMinuteSecondDatePickerElementFlag;
|
||||
_timeInterval = 0;
|
||||
_implementedCDatePickerDelegateMethods = 0;
|
||||
|
||||
[self setObjectValue:[CPDate date]];
|
||||
_minDate = [CPDate distantPast];
|
||||
_maxDate = [CPDate distantFuture];
|
||||
|
||||
[self setBezeled:YES];
|
||||
[self setBordered:YES];
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
if (!_locale)
|
||||
_locale = [CPLocale currentLocale];
|
||||
|
||||
_datePickerComponent = nil;
|
||||
|
||||
[self _createComponents];
|
||||
|
||||
[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
|
||||
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
[super setControlSize:aControlSize];
|
||||
|
||||
[_datePickerComponent setControlSize:aControlSize];
|
||||
|
||||
if (_isTextual)
|
||||
[self _sizeToControlSize];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Delegate methods
|
||||
|
||||
/*! Set the delegate of the datePicker
|
||||
@param aDelegate delegate of the datePicker
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
_implementedCDatePickerDelegateMethods = 0;
|
||||
|
||||
// Look if the delegate implements or not the delegate methods
|
||||
if ([_delegate respondsToSelector:@selector(datePicker:validateProposedDateValue:timeInterval:)])
|
||||
_implementedCDatePickerDelegateMethods |= CPDatePicker_validateProposedDateValue_timeInterval;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Layout method
|
||||
|
||||
/*! Layout the subviews
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[_datePickerComponent setNeedsLayout];
|
||||
[_datePickerComponent setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: Setter
|
||||
|
||||
/*! Return the objectValue of the datePicker. The objectValue should take the timeZoneEffect
|
||||
*/
|
||||
- (id)objectValue
|
||||
{
|
||||
// TODO : add timeZone effect. How to do it because js ???
|
||||
return _dateValue
|
||||
}
|
||||
|
||||
/*! Set the objectValue of the datePicker. It has to be a CPDate
|
||||
@param aDateValue the dateValue
|
||||
*/
|
||||
- (void)setObjectValue:(CPDate)aValue
|
||||
{
|
||||
if (![aValue isKindOfClass:[CPDate class]])
|
||||
return;
|
||||
|
||||
[self setDateValue:aValue];
|
||||
}
|
||||
|
||||
/* Set the dateValue of the datePicker
|
||||
@param aDateValue the dateValue
|
||||
*/
|
||||
- (void)setDateValue:(CPDate)aDateValue
|
||||
{
|
||||
if (aDateValue == nil)
|
||||
return;
|
||||
|
||||
_invokedByUserEvent = NO;
|
||||
[self _setDateValue:aDateValue timeInterval:_timeInterval];
|
||||
}
|
||||
|
||||
/*! Set the dateValue and the timeInterval. This method checks the min and max date of the datePicker also. It will call the delegate if possible.
|
||||
@param aDateValue the dateValue
|
||||
@param aTimeInterval the timeInterval
|
||||
*/
|
||||
- (void)_setDateValue:(CPDate)aDateValue timeInterval:(CPTimeInterval)aTimeInterval
|
||||
{
|
||||
// Make sure to have a valid date and avoid NaN values
|
||||
if (!isFinite(aDateValue))
|
||||
{
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:@"aDateValue is not valid"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (_minDate)
|
||||
aDateValue = new Date (MAX(aDateValue, _minDate));
|
||||
|
||||
if (_maxDate)
|
||||
aDateValue = new Date (MIN(aDateValue, _maxDate));
|
||||
|
||||
aTimeInterval = MAX(MIN(aTimeInterval, [_maxDate timeIntervalSinceDate:aDateValue]), [_minDate timeIntervalSinceDate:aDateValue]);
|
||||
|
||||
if ([aDateValue isEqualToDate:_dateValue] && aTimeInterval == _timeInterval)
|
||||
{
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_implementedCDatePickerDelegateMethods & CPDatePicker_validateProposedDateValue_timeInterval)
|
||||
{
|
||||
// constrain timeInterval also
|
||||
var aStartDateRef = function(x){if (typeof x == 'undefined') return aDateValue; aDateValue = x;};
|
||||
var aTimeIntervalRef = function(x){if (typeof x == 'undefined') return aTimeInterval; aTimeInterval = x;};
|
||||
|
||||
[_delegate datePicker:self validateProposedDateValue:aStartDateRef timeInterval:aTimeIntervalRef];
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"objectValue"];
|
||||
[self willChangeValueForKey:@"dateValue"];
|
||||
_dateValue = aDateValue;
|
||||
[super setObjectValue:_dateValue];
|
||||
[self didChangeValueForKey:@"objectValue"];
|
||||
[self didChangeValueForKey:@"dateValue"];
|
||||
|
||||
[self willChangeValueForKey:@"timeInterval"];
|
||||
_timeInterval = (_datePickerMode == CPSingleDateMode)? 0 : aTimeInterval;
|
||||
[self didChangeValueForKey:@"timeInterval"];
|
||||
|
||||
if (_invokedByUserEvent)
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
}
|
||||
|
||||
/*! Set the minDate of the datePicker
|
||||
@param aMinDate the minDate
|
||||
*/
|
||||
- (void)setMinDate:(CPDate)aMinDate
|
||||
{
|
||||
if (_minDate === aMinDate)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"minDate"];
|
||||
_minDate = aMinDate;
|
||||
[self didChangeValueForKey:@"minDate"];
|
||||
|
||||
[self _setDateValue:_dateValue timeInterval:_timeInterval];
|
||||
}
|
||||
|
||||
/*! Set the maxDate of the datePicker
|
||||
@param aMaxDate the maxDate
|
||||
*/
|
||||
- (void)setMaxDate:(CPDate)aMaxDate
|
||||
{
|
||||
if (_maxDate === aMaxDate)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"maxDate"];
|
||||
_maxDate = aMaxDate;
|
||||
[self didChangeValueForKey:@"maxDate"];
|
||||
|
||||
[self _setDateValue:_dateValue timeInterval:_timeInterval];
|
||||
}
|
||||
|
||||
/*! Set the syle of the datePicker
|
||||
@param aDatePickerStyle the datePicker style
|
||||
*/
|
||||
- (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];
|
||||
}
|
||||
|
||||
/*! Set the elements of the datePicker
|
||||
@param aDatePickerElements the datePicker elements
|
||||
*/
|
||||
- (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];
|
||||
}
|
||||
|
||||
/*! Set the mode of the datePicker
|
||||
@param aDatePickerMode the datePicker mode
|
||||
*/
|
||||
- (void)setDatePickerMode:(CPInteger)aDatePickerMode
|
||||
{
|
||||
if (_datePickerMode === aDatePickerMode)
|
||||
return;
|
||||
|
||||
_datePickerMode = aDatePickerMode;
|
||||
|
||||
if (_datePickerMode == CPSingleDateMode)
|
||||
[self _setDateValue:[self dateValue] timeInterval:0];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
/*! Set the timeInterval of the datePicker
|
||||
@param aTimeInterval the timeInterval of the datePicker
|
||||
*/
|
||||
- (void)setTimeInterval:(CPInteger)aTimeInterval
|
||||
{
|
||||
if (_datePickerMode == CPSingleDateMode)
|
||||
return;
|
||||
|
||||
[self _setDateValue:[self dateValue] timeInterval:aTimeInterval];
|
||||
}
|
||||
|
||||
/*! Set the locale of the datePicker. This update laso the locale of the formatter.
|
||||
@param aLocale the locale
|
||||
*/
|
||||
- (void)setLocale:(CPLocale)aLocale
|
||||
{
|
||||
if (_locale === aLocale)
|
||||
return;
|
||||
|
||||
_locale = aLocale;
|
||||
|
||||
if (_formatter)
|
||||
{
|
||||
[self willChangeValueForKey:@"locale"];
|
||||
[_formatter setLocale:_locale];
|
||||
[self didChangeValueForKey:@"locale"];
|
||||
}
|
||||
|
||||
// This will update the textFields (usefull when changing with a date with pm and am)
|
||||
if (_isTextual)
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the datepicker will have a bezeled border.
|
||||
@param shouldBeBezeled \c YES means the datepicker will draw a bezeled border
|
||||
*/
|
||||
- (void)setBezeled:(BOOL)shouldBeBezeled
|
||||
{
|
||||
if (_isBezeled === shouldBeBezeled)
|
||||
return;
|
||||
|
||||
_isBezeled = shouldBeBezeled;
|
||||
|
||||
if (shouldBeBezeled)
|
||||
[self setThemeState:CPThemeStateBezeled];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateBezeled];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the datepicker will have a border drawn. (actually it does nothing)
|
||||
@param shouldBeBordered \c YES makes the datepicker draw a border
|
||||
*/
|
||||
- (void)setBordered:(BOOL)shouldBeBordered
|
||||
{
|
||||
if (_isBordered === shouldBeBordered)
|
||||
return;
|
||||
|
||||
_isBordered = shouldBeBordered;
|
||||
|
||||
if (shouldBeBordered)
|
||||
[self setThemeState:CPThemeStateBordered];
|
||||
else
|
||||
[self unsetThemeState:CPThemeStateBordered];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the font of the control.
|
||||
@param aFont
|
||||
*/
|
||||
- (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.
|
||||
@param a boolean. YES if the control should be enabled, otherwise NO.
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)aBoolean
|
||||
{
|
||||
[super setEnabled:aBoolean];
|
||||
|
||||
[_datePickerComponent setEnabled:aBoolean];
|
||||
|
||||
if (!aBoolean)
|
||||
[self resignFirstResponder];
|
||||
}
|
||||
|
||||
/*! Set the background color of the datePicker
|
||||
@param aColor
|
||||
*/
|
||||
- (void)setBackgroundColor:(CPColor)aColor
|
||||
{
|
||||
_backgroundColor = aColor;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
/*! Set the boolean drawsBackgroundColor
|
||||
@param aBoolean
|
||||
*/
|
||||
- (void)setDrawsBackground:(BOOL)aBoolean
|
||||
{
|
||||
if (_drawsBackground === aBoolean)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"drawsBackground"];
|
||||
_drawsBackground = aBoolean;
|
||||
[self didChangeValueForKey:@"drawsBackground"];
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
/*! Set the timeZone
|
||||
@param aTimeZone
|
||||
*/
|
||||
- (void)setTimeZone:(CPTimeZone)aTimeZone
|
||||
{
|
||||
if (_timeZone === aTimeZone)
|
||||
return;
|
||||
|
||||
[self willChangeValueForKey:@"timeZone"];
|
||||
_timeZone = aTimeZone;
|
||||
[self didChangeValueForKey:@"timeZone"];
|
||||
|
||||
[self setNeedsLayout];
|
||||
|
||||
[_datePickerComponent setDateValue:_dateValue];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: First responder methods
|
||||
|
||||
/*! Return YES if style is set to CPTextFieldAndStepperDatePickerStyle or CPTextFieldDatePickerStyle
|
||||
*/
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
if (_isTextual)
|
||||
{
|
||||
if (![super becomeFirstResponder])
|
||||
return NO;
|
||||
|
||||
[_datePickerComponent _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*! Return YES
|
||||
*/
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*! Return YES
|
||||
*/
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
if (_isTextual)
|
||||
[_datePickerComponent resignFirstResponder];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: getter
|
||||
|
||||
/*!
|
||||
Returns \c YES if the textfield is bezeled.
|
||||
*/
|
||||
- (BOOL)isBezeled
|
||||
{
|
||||
return [self hasThemeState:CPThemeStateBezeled];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c YES if the textfield has a border.
|
||||
*/
|
||||
- (BOOL)isBordered
|
||||
{
|
||||
return [self hasThemeState:CPThemeStateBordered];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the font of the control.
|
||||
*/
|
||||
- (CPFont)textFont
|
||||
{
|
||||
return [self font];
|
||||
}
|
||||
|
||||
/*! Check if we are in the american format or not. Depending on the locale
|
||||
*/
|
||||
- (BOOL)_isAmericanFormat
|
||||
{
|
||||
return [[_locale objectForKey:CPLocaleCountryCode] isEqualToString:@"US"];
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: Key event
|
||||
|
||||
/*! Key down event
|
||||
@param anEvent
|
||||
*/
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
if (_isTextual)
|
||||
[_datePickerComponent keyDown:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPDatePickerModeKey = @"CPDatePickerModeKey",
|
||||
CPIntervalKey = @"CPIntervalKey",
|
||||
CPMinDateKey = @"CPMinDateKey",
|
||||
CPMaxDateKey = @"CPMaxDateKey",
|
||||
CPBackgroundColorKey = @"CPBackgroundColorKey",
|
||||
CPDrawsBackgroundKey = @"CPDrawsBackgroundKey",
|
||||
CPTextFontKey = @"CPTextFontKey",
|
||||
CPDatePickerElementsKey = @"CPDatePickerElementsKey",
|
||||
CPDatePickerStyleKey = @"CPDatePickerStyleKey",
|
||||
CPLocaleKey = @"CPLocaleKey",
|
||||
CPBorderedKey = @"CPBorderedKey",
|
||||
CPDateValueKey = @"CPDateValueKey";
|
||||
|
||||
@implementation CPDatePicker (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
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]];
|
||||
[self setDatePickerStyle:[aCoder decodeIntForKey:CPDatePickerStyleKey]];
|
||||
[self setMinDate:[aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast]];
|
||||
[self setMaxDate:[aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture]];
|
||||
[self setLocale:[aCoder decodeObjectForKey:CPLocaleKey]];
|
||||
|
||||
[self _init];
|
||||
|
||||
[self setTextFont:[aCoder decodeObjectForKey:CPTextFontKey]];
|
||||
[self setTimeInterval:[aCoder decodeDoubleForKey:CPIntervalKey]];
|
||||
[self setDateValue:[aCoder decodeObjectForKey:CPDateValueKey]];
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
- (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];
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeDouble:_timeInterval forKey:CPIntervalKey];
|
||||
[aCoder encodeInt:_datePickerMode forKey:CPDatePickerModeKey];
|
||||
[aCoder encodeInt:_datePickerStyle forKey:CPDatePickerStyleKey];
|
||||
[aCoder encodeInt:_datePickerElements forKey:CPDatePickerElementsKey];
|
||||
[aCoder encodeObject:_minDate forKey:CPMinDateKey];
|
||||
[aCoder encodeObject:_maxDate forKey:CPMaxDateKey];
|
||||
[aCoder encodeObject:_dateValue forKey:CPDateValueKey];
|
||||
[aCoder encodeObject:_textFont forKey:CPTextFontKey];
|
||||
[aCoder encodeObject:_locale forKey:CPLocaleKey];
|
||||
[aCoder encodeObject:_backgroundColor forKey:CPBackgroundColorKey];
|
||||
[aCoder encodeObject:_drawsBackground forKey:CPDrawsBackgroundKey];
|
||||
[aCoder encodeObject:_isBordered forKey:CPBorderedKey];
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// FIXME: add support for CPEditorRegistrationProtocol as implemented for CPTextField
|
||||
@implementation _CPDatePickerValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPDate (CPDatePickerAdditions)
|
||||
|
||||
- (int)_daysInMonth
|
||||
{
|
||||
return 32 - new Date(self.getFullYear(), self.getMonth(), 32).getDate();
|
||||
}
|
||||
|
||||
- (void)_resetToMidnight
|
||||
{
|
||||
self.setHours(0);
|
||||
self.setMinutes(0);
|
||||
self.setSeconds(0);
|
||||
self.setMilliseconds(0);
|
||||
}
|
||||
|
||||
- (void)_resetToLastSeconds
|
||||
{
|
||||
self.setHours(23);
|
||||
self.setMinutes(59);
|
||||
self.setSeconds(59);
|
||||
self.setMilliseconds(99);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
@@ -1,302 +0,0 @@
|
||||
/* _CPDatePickerCalendar.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 "_CPDatePickerClock.j"
|
||||
@import "_CPDatePickerBox.j"
|
||||
@import "_CPDatePickerMonthView.j"
|
||||
@import "_CPDatePickerHeaderView.j"
|
||||
|
||||
@class CPDatePicker
|
||||
|
||||
@global CPApp
|
||||
@global CPHourMinuteDatePickerElementFlag
|
||||
@global CPHourMinuteSecondDatePickerElementFlag
|
||||
@global CPTimeZoneDatePickerElementFlag
|
||||
@global CPYearMonthDatePickerElementFlag
|
||||
@global CPYearMonthDayDatePickerElementFlag
|
||||
@global CPEraDatePickerElementFlag
|
||||
|
||||
@implementation _CPDatePickerCalendar : CPControl
|
||||
{
|
||||
_CPDatePickerMonthView _monthView;
|
||||
_CPDatePickerHeaderView _headerView;
|
||||
_CPDatePickerClock _datePickerClock;
|
||||
_CPDatePickerBox _box;
|
||||
CPDatePicker _datePicker;
|
||||
CPInteger _startSelectionIndex;
|
||||
CPInteger _currentSelectionIndex;
|
||||
BOOL _hasClock;
|
||||
BOOL _hasCalendar;
|
||||
BOOL _isClockOnly;
|
||||
CPInteger _datePickerElements @accessors(getter=datePickerElements);
|
||||
}
|
||||
|
||||
|
||||
// MARK: Init method
|
||||
|
||||
/*! Init a _CPDatePickerCalendar
|
||||
@param aFrame
|
||||
@param aDatePicker
|
||||
@return a new instance of _CPDatePickerCalendar
|
||||
*/
|
||||
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_datePicker = aDatePicker;
|
||||
_datePickerElements = [_datePicker datePickerElements];
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*! Init the object
|
||||
*/
|
||||
- (void)_init
|
||||
{
|
||||
var sizeHeader = [_datePicker valueForThemeAttribute:@"size-header"],
|
||||
sizeCalendar = [_datePicker valueForThemeAttribute:@"size-calendar"],
|
||||
sizeClock = [_datePicker valueForThemeAttribute:@"size-clock"],
|
||||
calendarClockMargin = [_datePicker valueForThemeAttribute:@"calendar-clock-margin"];
|
||||
|
||||
_hasClock = (_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) || (_datePickerElements & CPHourMinuteDatePickerElementFlag);
|
||||
_hasCalendar = (_datePickerElements & CPYearMonthDayDatePickerElementFlag) || (_datePickerElements & CPYearMonthDatePickerElementFlag);
|
||||
_isClockOnly = _hasClock && !_hasCalendar;
|
||||
|
||||
if (_hasCalendar && !_box)
|
||||
{
|
||||
_box = [[_CPDatePickerBox alloc] initWithFrame:CGRectMake(0, 0, sizeCalendar.width, sizeHeader.height + sizeCalendar.height)];
|
||||
[_box setDatePicker:_datePicker];
|
||||
[self addSubview:_box];
|
||||
|
||||
_headerView = [[_CPDatePickerHeaderView alloc] initWithFrame:CGRectMake(0, 0, sizeHeader.width, sizeHeader.height) datePicker:_datePicker delegate:self];
|
||||
[_box addSubview:_headerView];
|
||||
|
||||
_monthView = [[_CPDatePickerMonthView alloc] initWithFrame:CGRectMake(0, sizeHeader.height, sizeCalendar.width, sizeCalendar.height) datePicker:_datePicker delegate:self];
|
||||
[_box addSubview:_monthView];
|
||||
}
|
||||
|
||||
if (_hasClock && !_datePickerClock)
|
||||
{
|
||||
_datePickerClock = [[_CPDatePickerClock alloc] initWithFrame:CGRectMake(0, 0, sizeClock.width, sizeClock.height) datePicker:_datePicker];
|
||||
[self addSubview:_datePickerClock];
|
||||
}
|
||||
|
||||
if (_hasClock)
|
||||
[_datePickerClock setDatePickerElements:_datePickerElements];
|
||||
|
||||
[self setDateValue:[_datePicker dateValue]];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Responder methods
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Getter Setter methods
|
||||
|
||||
/*! Set the date value of the component. It sets the dateValue of the header and the monthView also
|
||||
@param aDateValue
|
||||
*/
|
||||
- (void)setDateValue:(CPDate)aDateValue
|
||||
{
|
||||
var dateValue = [aDateValue copy];
|
||||
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
|
||||
|
||||
[_monthView setMonthForDate:dateValue];
|
||||
[_headerView setMonthForDate:[_monthView monthDate]];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*! Set enabled
|
||||
@param aBoolean
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)aBoolean
|
||||
{
|
||||
[super setEnabled:aBoolean];
|
||||
|
||||
[_datePickerClock setEnabled:aBoolean];
|
||||
[_headerView setEnabled:aBoolean];
|
||||
[_monthView setEnabled:aBoolean];
|
||||
}
|
||||
|
||||
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
|
||||
{
|
||||
if (_datePickerElements === aDatePickerElements)
|
||||
return;
|
||||
|
||||
_datePickerElements = aDatePickerElements;
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: Layout methods
|
||||
|
||||
/*! Manager the subviews. It hides or not the clock.
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var minSize = [_datePicker valueForThemeAttribute:@"min-size-calendar"],
|
||||
sizeHeader = [_datePicker valueForThemeAttribute:@"size-header"],
|
||||
sizeCalendar = [_datePicker valueForThemeAttribute:@"size-calendar"],
|
||||
sizeClock = [_datePicker valueForThemeAttribute:@"size-clock"],
|
||||
calendarClockMargin = [_datePicker valueForThemeAttribute:@"calendar-clock-margin"];
|
||||
|
||||
if (_hasClock)
|
||||
{
|
||||
if (!_isClockOnly)
|
||||
{
|
||||
var frameSize = CGSizeMakeCopy(minSize);
|
||||
frameSize.width += sizeClock.width + calendarClockMargin;
|
||||
|
||||
[_datePicker setFrameSize:frameSize];
|
||||
[_datePickerClock setFrameOrigin:CGPointMake(sizeCalendar.width + calendarClockMargin, [self bounds].size.height / 2 - sizeClock.height / 2)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_datePicker setFrameSize:sizeClock];
|
||||
[_datePickerClock setFrameOrigin:CGPointMake(0, 0)];
|
||||
}
|
||||
|
||||
[_datePickerClock setHidden:NO];
|
||||
[_datePickerClock setFrameSize:sizeClock];
|
||||
[_datePickerClock setNeedsLayout];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_datePicker setFrameSize:minSize];
|
||||
[_datePickerClock setHidden:YES];
|
||||
}
|
||||
|
||||
if (_hasCalendar)
|
||||
{
|
||||
[_box setHidden:NO];
|
||||
[_headerView setHidden:NO];
|
||||
[_monthView setHidden:NO];
|
||||
[_box setNeedsLayout];
|
||||
[_box setNeedsDisplay:YES];
|
||||
[_headerView setNeedsLayout];
|
||||
[_monthView setNeedsLayout];
|
||||
[_monthView setNeedsDisplay:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_box setHidden:YES];
|
||||
[_headerView setHidden:YES];
|
||||
[_monthView setHidden:YES];
|
||||
}
|
||||
|
||||
[self setFrameSize:[_datePicker frameSize]];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Action methods
|
||||
|
||||
/*! Move to the nextMonth without changing the dateValue of the datePicker
|
||||
*/
|
||||
- (void)_clickArrowNext:(id)sender
|
||||
{
|
||||
var currentEvent = [CPApp currentEvent],
|
||||
modifierFlags = [currentEvent modifierFlags];
|
||||
|
||||
if (modifierFlags & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask))
|
||||
{
|
||||
var date = [[_monthView monthDate] copy];
|
||||
date.setDate(1);
|
||||
|
||||
if (modifierFlags & CPAlternateKeyMask)
|
||||
date.setUTCFullYear(date.getUTCFullYear() + 10);
|
||||
else
|
||||
date.setUTCFullYear(date.getUTCFullYear() + 1);
|
||||
|
||||
[self setDateValue:date];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self _displayNextMonth];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_displayNextMonth
|
||||
{
|
||||
// Copy the date so we don't modify the view's state directly
|
||||
var nextDate = [[_monthView nextMonth] copy];
|
||||
|
||||
// Set to the middle of the month (15th).
|
||||
// This prevents [setDateValue:]'s timezone adjustment from
|
||||
// shifting the date back into the previous month (e.g., Nov 1 -> Oct 31).
|
||||
nextDate.setDate(15);
|
||||
|
||||
[self setDateValue:nextDate];
|
||||
}
|
||||
|
||||
- (void)_displayPreviousMonth
|
||||
{
|
||||
[self setDateValue:[_monthView previousMonth]];
|
||||
}
|
||||
|
||||
/*! Move to the previous month without changing the dateValue of the datePicker
|
||||
*/
|
||||
- (void)_clickArrowPrevious:(id)sender
|
||||
{
|
||||
var currentEvent = [CPApp currentEvent],
|
||||
modifierFlags = [currentEvent modifierFlags];
|
||||
|
||||
if (modifierFlags & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask))
|
||||
{
|
||||
var date = [[_monthView monthDate] copy];
|
||||
date.setDate(1);
|
||||
|
||||
if (modifierFlags & CPAlternateKeyMask)
|
||||
date.setUTCFullYear(date.getUTCFullYear() - 10);
|
||||
else
|
||||
date.setUTCFullYear(date.getUTCFullYear() - 1);
|
||||
|
||||
[self setDateValue:date];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self _displayPreviousMonth];
|
||||
}
|
||||
}
|
||||
|
||||
/*! Move to the current selected day
|
||||
*/
|
||||
- (void)_currentMonth:(id)sender
|
||||
{
|
||||
[self setDateValue:[_datePicker dateValue]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,611 +0,0 @@
|
||||
/*
|
||||
* _CPDatePickerClock.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/CPKeyedArchiver.j>
|
||||
@import <Foundation/CPKeyedUnarchiver.j>
|
||||
@import "CPView.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPImage.j"
|
||||
@import "CALayer.j"
|
||||
|
||||
@class _CPCibCustomResource
|
||||
@class CPDatePicker
|
||||
@class HandImageLayer
|
||||
@class HoursLayer
|
||||
@class HandLayer
|
||||
|
||||
@global CPHourMinuteSecondDatePickerElementFlag
|
||||
@global CPTextFieldAndStepperDatePickerStyle
|
||||
@global CPTextFieldDatePickerStyle
|
||||
|
||||
var RADIANS = Math.PI / 180;
|
||||
|
||||
@typedef _CPDatePickerClockHand
|
||||
_CPDatePickerClockHours = 1;
|
||||
_CPDatePickerClockMinutes = 2;
|
||||
_CPDatePickerClockSeconds = 3;
|
||||
|
||||
@implementation _CPDatePickerClock : CPControl
|
||||
{
|
||||
BOOL _isEnabled;
|
||||
HoursLayer _rootLayer;
|
||||
HandLayer _hourHandLayer;
|
||||
HandLayer _minuteHandLayer;
|
||||
HandLayer _secondHandLayer;
|
||||
CALayer _middleHandLayer;
|
||||
CPDatePicker _datePicker;
|
||||
CPTextField _PMAMTextField;
|
||||
|
||||
CALayer _currentHandLayer;
|
||||
_CPDatePickerClockHand _currentHand;
|
||||
CPInteger _currentRepresentedValue;
|
||||
float _currentValueShift;
|
||||
CPInteger _numberOfUnits;
|
||||
BOOL _trackingHand;
|
||||
|
||||
CPInteger _representedHours;
|
||||
CPInteger _representedMinutes;
|
||||
CPInteger _representedSeconds;
|
||||
BOOL _representedHourIsPM;
|
||||
|
||||
CPInteger _datePickerElements @accessors(getter=datePickerElements);
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Init methods
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_datePicker = aDatePicker;
|
||||
_datePickerElements = [_datePicker datePickerElements];
|
||||
_trackingHand = NO;
|
||||
|
||||
_PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_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];
|
||||
|
||||
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"];
|
||||
|
||||
// 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)];
|
||||
|
||||
_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)];
|
||||
|
||||
_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)];
|
||||
|
||||
_middleHandLayer = [[HandLayer alloc] initWithSize:middleHandSize];
|
||||
[_middleHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
|
||||
[_middleHandLayer setAnchorPoint:CGPointMakeZero()];
|
||||
[_middleHandLayer setPosition:CGPointMake(0.0, 0.0)];
|
||||
|
||||
_rootLayer = [[HoursLayer alloc] init];
|
||||
[self setWantsLayer:YES];
|
||||
[self setLayer:_rootLayer];
|
||||
|
||||
[self _initHands];
|
||||
|
||||
[_rootLayer addSublayer:_hourHandLayer];
|
||||
[_rootLayer addSublayer:_minuteHandLayer];
|
||||
|
||||
if ([_datePicker valueForThemeAttribute:@"clock-second-hand-over"])
|
||||
{
|
||||
[_rootLayer addSublayer:_middleHandLayer];
|
||||
[_rootLayer addSublayer:_secondHandLayer];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_rootLayer addSublayer:_secondHandLayer];
|
||||
[_rootLayer addSublayer:_middleHandLayer];
|
||||
}
|
||||
|
||||
[_rootLayer setDrawsHours:[_datePicker valueForThemeAttribute:@"clock-draws-hours"]];
|
||||
[_rootLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_initHands
|
||||
{
|
||||
var middleHandImage = [_datePicker currentValueForThemeAttribute:@"middle-hand-image"],
|
||||
hourHandImage = [_datePicker currentValueForThemeAttribute:@"hour-hand-image"],
|
||||
minuteHandImage = [_datePicker currentValueForThemeAttribute:@"minute-hand-image"],
|
||||
secondHandImage = [_datePicker currentValueForThemeAttribute:@"second-hand-image"];
|
||||
|
||||
// If hand images are true CPImage, we have to duplicate them to avoid
|
||||
// the multiple delegates bug when multiple clocks are displayed
|
||||
|
||||
if ([middleHandImage isKindOfClass:[CPImage class]])
|
||||
middleHandImage = [middleHandImage duplicate];
|
||||
|
||||
if ([hourHandImage isKindOfClass:[CPImage class]])
|
||||
hourHandImage = [hourHandImage duplicate];
|
||||
|
||||
if ([minuteHandImage isKindOfClass:[CPImage class]])
|
||||
minuteHandImage = [minuteHandImage duplicate];
|
||||
|
||||
if ([secondHandImage isKindOfClass:[CPImage class]])
|
||||
secondHandImage = [secondHandImage duplicate];
|
||||
|
||||
[_middleHandLayer setImage:middleHandImage];
|
||||
[_hourHandLayer setImage:hourHandImage];
|
||||
[_minuteHandLayer setImage:minuteHandImage];
|
||||
[_secondHandLayer setImage:secondHandImage];
|
||||
|
||||
[_hourHandLayer setNeedsDisplay];
|
||||
[_middleHandLayer setNeedsDisplay];
|
||||
[_secondHandLayer setNeedsDisplay];
|
||||
[_minuteHandLayer setNeedsDisplay];
|
||||
|
||||
[_rootLayer setFont:[_datePicker currentValueForThemeAttribute:@"clock-hours-font"]];
|
||||
[_rootLayer setTextColor:[_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"]];
|
||||
[_rootLayer setRadius:[_datePicker currentValueForThemeAttribute:@"clock-hours-radius"]];
|
||||
|
||||
[_rootLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
|
||||
{
|
||||
_datePickerElements = aDatePickerElements;
|
||||
|
||||
// Check if we have to display the hand second
|
||||
// FIXME: Don't know why but next line will cause theme compilation to fail...
|
||||
// Workaround: added "if PLATFORM(DOM)"
|
||||
#if PLATFORM(DOM)
|
||||
[_secondHandLayer setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
|
||||
#endif
|
||||
}
|
||||
|
||||
// MARK: Layout methods
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
// While tracking a hand, we don't want the whole thing to be relayouted at each mouse movement
|
||||
if (_trackingHand)
|
||||
return;
|
||||
|
||||
[self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]];
|
||||
|
||||
var dateValue = [[_datePicker dateValue] copy];
|
||||
|
||||
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
|
||||
|
||||
_representedHours = dateValue.getHours();
|
||||
_representedMinutes = dateValue.getMinutes();
|
||||
_representedSeconds = dateValue.getSeconds();
|
||||
_representedHourIsPM = (_representedHours > 11);
|
||||
|
||||
// Hours are expressed in 24 hours format, we need 12 hours format
|
||||
_representedHours -= (_representedHourIsPM ? 12 : 0);
|
||||
|
||||
[self _updateHands];
|
||||
|
||||
// FIXME: Workaround. Seems that CALayer doesn't redraw without an event
|
||||
[CALayer runLoopUpdateLayers];
|
||||
}
|
||||
|
||||
- (void)_updateHands
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
|
||||
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
|
||||
[_PMAMTextField sizeToFit];
|
||||
[_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)];
|
||||
|
||||
[_hourHandLayer setRotationRadians:(360 * (_representedHours + _representedMinutes / 60) / 12) * RADIANS];
|
||||
[_minuteHandLayer setRotationRadians:(360 * (_representedMinutes + _representedSeconds / 60) / 60) * RADIANS];
|
||||
[_secondHandLayer setRotationRadians:(360 * _representedSeconds / 60) * RADIANS];
|
||||
|
||||
[_hourHandLayer setNeedsDisplay];
|
||||
[_minuteHandLayer setNeedsDisplay];
|
||||
[_secondHandLayer setNeedsDisplay];
|
||||
// [_middleHandLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
// MARK: Accessors
|
||||
|
||||
- (void)setEnabled:(BOOL)shouldEnable
|
||||
{
|
||||
shouldEnable = !!shouldEnable;
|
||||
|
||||
if (shouldEnable === _isEnabled)
|
||||
return;
|
||||
|
||||
_isEnabled = shouldEnable;
|
||||
|
||||
[self _initHands];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
// MARK: Mouse actions
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (!_isEnabled)
|
||||
return;
|
||||
|
||||
var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if ([_secondHandLayer handIsHitAtPoint:currentLocation])
|
||||
{
|
||||
_currentHandLayer = _secondHandLayer;
|
||||
_currentHand = _CPDatePickerClockSeconds;
|
||||
_currentRepresentedValue = _representedSeconds;
|
||||
_currentValueShift = 0;
|
||||
_numberOfUnits = 60;
|
||||
}
|
||||
else if ([_minuteHandLayer handIsHitAtPoint:currentLocation])
|
||||
{
|
||||
_currentHandLayer = _minuteHandLayer;
|
||||
_currentHand = _CPDatePickerClockMinutes;
|
||||
_currentRepresentedValue = _representedMinutes;
|
||||
_currentValueShift = _representedSeconds / 60;
|
||||
_numberOfUnits = 60;
|
||||
}
|
||||
else if ([_hourHandLayer handIsHitAtPoint:currentLocation])
|
||||
{
|
||||
_currentHandLayer = _hourHandLayer;
|
||||
_currentHand = _CPDatePickerClockHours;
|
||||
_currentRepresentedValue = _representedHours;
|
||||
_currentValueShift = _representedMinutes / 60;
|
||||
_numberOfUnits = 12;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentHandLayer = nil;
|
||||
_currentHand = CPNotFound;
|
||||
_currentRepresentedValue = CPNotFound;
|
||||
_currentValueShift = CPNotFound;
|
||||
_numberOfUnits = CPNotFound;
|
||||
}
|
||||
|
||||
if (_currentHandLayer)
|
||||
[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)
|
||||
// Day++
|
||||
dateValue.setDate(dateValue.getDate() + 1);
|
||||
|
||||
else if (movedBackward && _representedHourIsPM)
|
||||
// Day--
|
||||
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
|
||||
|
||||
// We have to adapt represented values
|
||||
_representedHours = dateValue.getHours();
|
||||
_representedMinutes = dateValue.getMinutes();
|
||||
_representedSeconds = dateValue.getSeconds();
|
||||
_representedHourIsPM = (_representedHours > 11);
|
||||
|
||||
// Hours are expressed in 24 hours format, we need 12 hours format
|
||||
_representedHours -= (_representedHourIsPM ? 12 : 0);
|
||||
|
||||
switch (_currentHand) {
|
||||
case _CPDatePickerClockHours:
|
||||
_currentRepresentedValue = _representedHours;
|
||||
break;
|
||||
|
||||
case _CPDatePickerClockMinutes:
|
||||
_currentRepresentedValue = _representedMinutes;
|
||||
break;
|
||||
|
||||
case _CPDatePickerClockSeconds:
|
||||
_currentRepresentedValue = _representedSeconds;
|
||||
break;
|
||||
}
|
||||
|
||||
[self _updateHands];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
|
||||
{
|
||||
_trackingHand = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
@implementation HandLayer : CALayer
|
||||
{
|
||||
CPImage _image;
|
||||
HandImageLayer _imageLayer;
|
||||
float _rotationRadians;
|
||||
}
|
||||
|
||||
// MARK: Init methods
|
||||
|
||||
- (id)initWithSize:(CGSize)aSize
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_imageLayer = [HandImageLayer layer];
|
||||
_rotationRadians = 0;
|
||||
|
||||
[_imageLayer setDelegate:self];
|
||||
[_imageLayer setBounds:CGRectMake(0.0, 0.0, aSize.width, aSize.height)];
|
||||
|
||||
[self addSublayer:_imageLayer];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
// MARK: Setter Getter methods
|
||||
|
||||
/*!
|
||||
Set the bounds of the layer. The imageLayer will be at the center of this bounds.
|
||||
*/
|
||||
- (void)setBounds:(CGRect)aRect
|
||||
{
|
||||
[super setBounds:aRect];
|
||||
|
||||
[_imageLayer setPosition:CGPointMake(CGRectGetMidX(aRect), CGRectGetMidY(aRect))];
|
||||
}
|
||||
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
if (_image === anImage)
|
||||
return;
|
||||
|
||||
if ([anImage isKindOfClass:[_CPCibCustomResource class]])
|
||||
_image = [anImage imageFromCoder:nil];
|
||||
else
|
||||
_image = anImage;
|
||||
|
||||
[_imageLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)setRotationRadians:(float)radians
|
||||
{
|
||||
if (_rotationRadians === radians)
|
||||
return;
|
||||
|
||||
_rotationRadians = radians;
|
||||
|
||||
[_imageLayer setAffineTransform:CGAffineTransformScale(
|
||||
CGAffineTransformMakeRotation(_rotationRadians),
|
||||
1.0, 1.0)];
|
||||
}
|
||||
|
||||
- (void)imageDidLoad:(CPImage)anImage
|
||||
{
|
||||
[_imageLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawLayer:(CALayer)aLayer inContext:(CGContext)aContext
|
||||
{
|
||||
if ([_image loadStatus] != CPImageLoadStatusCompleted)
|
||||
[_image setDelegate:self];
|
||||
else
|
||||
CGContextDrawImage(aContext, [aLayer bounds], _image);
|
||||
}
|
||||
|
||||
- (BOOL)handIsHitAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
return (!_isHidden && [_imageLayer hitTest:aPoint] === _imageLayer);
|
||||
}
|
||||
|
||||
- (void)setNeedsDisplay
|
||||
{
|
||||
[super setNeedsDisplay];
|
||||
[_imageLayer setNeedsDisplay];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
@implementation HandImageLayer : CALayer
|
||||
{
|
||||
CGRect _handBounds;
|
||||
}
|
||||
|
||||
// We have to adapt hitTest so it only takes the hand into account (that's the top half of the image layer)
|
||||
// We are also sure there's no sublayers
|
||||
- (CALayer)hitTest:(CGPoint)aPoint
|
||||
{
|
||||
if (_isHidden)
|
||||
return nil;
|
||||
|
||||
var point = CGPointApplyAffineTransform(aPoint, _transformToLayer);
|
||||
|
||||
return CGRectContainsPoint(_handBounds, point) ? self : nil;
|
||||
}
|
||||
|
||||
- (void)setBounds:(CGRect)aBounds
|
||||
{
|
||||
if (CGRectEqualToRect(_bounds, aBounds))
|
||||
return;
|
||||
|
||||
_handBounds = CGRectMakeCopy(aBounds);
|
||||
|
||||
_handBounds.size.height = _handBounds.size.height / 2;
|
||||
|
||||
[super setBounds:aBounds];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
@implementation HoursLayer : CALayer
|
||||
{
|
||||
BOOL _drawsHours;
|
||||
|
||||
CPFont _font @accessors(property=font);
|
||||
CPColor _textColor @accessors(property=textColor);
|
||||
float _radius @accessors(property=radius);
|
||||
}
|
||||
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_drawsHours = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDrawsHours:(BOOL)shouldDrawHours
|
||||
{
|
||||
shouldDrawHours = !!shouldDrawHours;
|
||||
|
||||
if (_drawsHours === shouldDrawHours)
|
||||
return;
|
||||
|
||||
_drawsHours = shouldDrawHours;
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawInContext:(CGContext)aContext
|
||||
{
|
||||
[super drawInContext:aContext];
|
||||
|
||||
if (_drawsHours)
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
centerX = bounds.size.width / 2,
|
||||
centerY = bounds.size.height / 2;
|
||||
|
||||
CGContextSelectFont(aContext, _font);
|
||||
CGContextSetFillColor(aContext, _textColor);
|
||||
aContext.textBaseline = @"middle";
|
||||
aContext.textAlign = @"center";
|
||||
|
||||
for (var i = 1, angle = 60.0, x, y; i < 13; i++, angle -= 30.0)
|
||||
{
|
||||
x = centerX + _radius * COS(angle * RADIANS);
|
||||
y = centerY - _radius * SIN(angle * RADIANS);
|
||||
|
||||
aContext.fillText(i, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -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
|
||||
|
||||
@@ -1,633 +0,0 @@
|
||||
/* _CPDatePickerTextField.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 "CPControl.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPStepper.j"
|
||||
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPDate.j>
|
||||
@import <Foundation/CPDateFormatter.j>
|
||||
@import <Foundation/CPLocale.j>
|
||||
|
||||
@class CPDatePicker
|
||||
@class _CPDatePickerElementView
|
||||
|
||||
@import "_CPDatePickerElementView.j"
|
||||
|
||||
@global CPSingleDateMode
|
||||
@global CPRangeDateMode
|
||||
|
||||
@global CPTextFieldAndStepperDatePickerStyle
|
||||
@global CPTextFieldDatePickerStyle
|
||||
|
||||
@global CPHourMinuteDatePickerElementFlag
|
||||
@global CPHourMinuteSecondDatePickerElementFlag
|
||||
@global CPTimeZoneDatePickerElementFlag
|
||||
@global CPYearMonthDatePickerElementFlag
|
||||
@global CPYearMonthDayDatePickerElementFlag
|
||||
@global CPEraDatePickerElementFlag
|
||||
|
||||
// This class is used to represente the datePicker with the CPTextFieldAndStepperDatePickerStyle/CPTextFieldDatePickerStyle mode
|
||||
@implementation _CPDatePickerTextField : CPControl
|
||||
{
|
||||
_CPDatePickerElementTextField _firstTextField @accessors(property=firstTextField);
|
||||
_CPDatePickerElementTextField _lastTextField @accessors(property=lastTextField);
|
||||
|
||||
_CPDatePickerElementTextField _currentTextField;
|
||||
_CPDatePickerElementView _datePickerElementView;
|
||||
CPDatePicker _datePicker;
|
||||
CPStepper _stepper;
|
||||
CPInteger _datePickerElements @accessors(getter=datePickerElements);
|
||||
}
|
||||
|
||||
|
||||
// MARK: Init
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_datePicker = aDatePicker;
|
||||
[self _init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
_datePickerElements = [_datePicker datePickerElements];
|
||||
|
||||
_datePickerElementView = [[_CPDatePickerElementView alloc] initWithFrame:CGRectMakeZero() withDatePicker:_datePicker];
|
||||
[self addSubview:_datePickerElementView];
|
||||
|
||||
_stepper = [CPStepper stepper];
|
||||
[_stepper setTarget:self];
|
||||
[_stepper setAction:@selector(_clickStepper:)];
|
||||
[self addSubview:_stepper];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if ([_datePicker currentValueForThemeAttribute:@"uses-focus-ring"])
|
||||
{
|
||||
// As with overflow:hidden, views are clipping their content, in order
|
||||
// to show a focus ring (which is external to a view), we need to let
|
||||
// content extend outside the view.
|
||||
_datePicker._DOMElement.style.overflow = "visible";
|
||||
_DOMElement.style.overflow = "visible";
|
||||
_datePickerElementView._DOMElement.style.overflow = "visible";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Override responder methods
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
// End the timer of editing
|
||||
[_currentTextField _endEditing];
|
||||
|
||||
// Don't forget to unbind, otherwise several steppers will increase or decrease
|
||||
[_currentTextField unbind:@"objectValue"];
|
||||
[_currentTextField makeDeselectable];
|
||||
_currentTextField = nil;
|
||||
|
||||
// This is usefull when clicking on the stepper when the datePicker is not selected
|
||||
[_stepper setObjectValue:0];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyView
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Setter Getter methods
|
||||
|
||||
/*! Set the value of the control
|
||||
@param aDateValue
|
||||
*/
|
||||
- (void)setDateValue:(CPDate)aDateValue
|
||||
{
|
||||
var dateValue = [aDateValue copy];
|
||||
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
|
||||
[_datePickerElementView setDateValue:dateValue];
|
||||
|
||||
// Be sure to update the stepper value. We don't use -setObjectValue to avoid a binding update.
|
||||
if (_currentTextField)
|
||||
_stepper._value = [_currentTextField intValue];
|
||||
}
|
||||
|
||||
/*! Set the widget enabled or not
|
||||
@param aBoolean
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)aBoolean
|
||||
{
|
||||
[super setEnabled:aBoolean];
|
||||
[_stepper setEnabled:aBoolean];
|
||||
[_datePickerElementView setEnabled:aBoolean];
|
||||
}
|
||||
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
[super setTextColor:aColor];
|
||||
[_datePickerElementView setTextColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setTextFont:(CPFont)aFont
|
||||
{
|
||||
[self setFont:aFont];
|
||||
[_datePickerElementView setTextFont:aFont];
|
||||
}
|
||||
|
||||
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
|
||||
{
|
||||
if (_datePickerElements === aDatePickerElements)
|
||||
return;
|
||||
|
||||
_datePickerElements = aDatePickerElements;
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: Notification methods
|
||||
|
||||
/*! This is called to when the user just changed the selected textField
|
||||
@param aNotification
|
||||
*/
|
||||
- (void)_datePickerElementTextFieldBecomeFirstResponder:(CPNotification)aNotification
|
||||
{
|
||||
if ([[aNotification userInfo] objectForKey:@"textField"] == _currentTextField)
|
||||
return;
|
||||
|
||||
[self _selectTextField:[[aNotification userInfo] objectForKey:@"textField"]];
|
||||
|
||||
// This is tricky, its to avoid to have the cursor
|
||||
if ([[self window] firstResponder] != _datePicker)
|
||||
[[self window] makeFirstResponder:_datePicker];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: SelectTextField action
|
||||
|
||||
- (void)_selectTextFieldWithFlags:(unsigned)flags
|
||||
{
|
||||
[_datePickerElementView _updateResponderTextField];
|
||||
|
||||
if (!_currentTextField)
|
||||
{
|
||||
var targetField = nil;
|
||||
|
||||
if (flags & CPShiftKeyMask)
|
||||
{
|
||||
// Try last field; if hidden, find previous visible
|
||||
if ([_lastTextField isHidden])
|
||||
targetField = [self _previousVisibleTextFieldFrom:_lastTextField];
|
||||
else
|
||||
targetField = _lastTextField;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try first field; if hidden, find next visible
|
||||
if ([_firstTextField isHidden])
|
||||
targetField = [self _nextVisibleTextFieldFrom:_firstTextField];
|
||||
else
|
||||
targetField = _firstTextField;
|
||||
}
|
||||
|
||||
// Only select if we actually found a valid visible field
|
||||
if (targetField)
|
||||
[self _selectTextField:targetField];
|
||||
}
|
||||
}
|
||||
|
||||
/*! Select a textField
|
||||
@param aDatePickerElementTextField the textField
|
||||
*/
|
||||
- (void)_selectTextField:(_CPDatePickerElementTextField)aDatePickerElementTextField
|
||||
{
|
||||
if (_currentTextField == aDatePickerElementTextField)
|
||||
return;
|
||||
|
||||
// End the timer of editing
|
||||
[_currentTextField _endEditing];
|
||||
|
||||
// Don't forget to unbind, otherwise several steppers will increase or decrease
|
||||
[_currentTextField unbind:@"objectValue"];
|
||||
[_currentTextField makeDeselectable];
|
||||
|
||||
_currentTextField = aDatePickerElementTextField;
|
||||
[_currentTextField makeSelectable];
|
||||
|
||||
// We cannot assign a value with the textField AM/PM
|
||||
if ([_currentTextField dateType] != CPAMPMDateType)
|
||||
{
|
||||
// We update the value of the stepper dependind on the textField
|
||||
[_stepper setObjectValue:[_currentTextField intValue]];
|
||||
[_stepper setMaxValue:[_currentTextField maxNumber]];
|
||||
[_stepper setMinValue:[_currentTextField minNumber]];
|
||||
|
||||
// We bind the stepper with textField
|
||||
[_currentTextField bind:@"objectValue" toObject:_stepper withKeyPath:@"objectValue" options:nil];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Events
|
||||
|
||||
/*! Called when the user click on the stepper
|
||||
*/
|
||||
- (void)_clickStepper:(id)sender
|
||||
{
|
||||
// Success when clicking on the stepper if the datePicker is not selected
|
||||
if ([[self window] firstResponder] != _datePicker || !_currentTextField)
|
||||
{
|
||||
var isUp = NO;
|
||||
|
||||
if ([sender objectValue] == 1)
|
||||
isUp = YES;
|
||||
|
||||
[self _selectTextField:_firstTextField];
|
||||
[[self window] makeFirstResponder:_datePicker];
|
||||
|
||||
// Update the dateValue with the binding.
|
||||
if (isUp)
|
||||
[_stepper setDoubleValue:[_currentTextField intValue] + 1];
|
||||
else
|
||||
[_stepper setDoubleValue:[_currentTextField intValue] - 1];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ([_currentTextField dateType] != CPAMPMDateType)
|
||||
{
|
||||
// Make sure to get the good value, especially when we reach the maxDate or minDate
|
||||
[sender setDoubleValue:[_currentTextField intValue]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// AM/PM behavior
|
||||
if ([[_currentTextField stringValue] isEqualToString:@"PM"])
|
||||
[_currentTextField setStringValue:@"AM"];
|
||||
else
|
||||
[_currentTextField setStringValue:@"PM"];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldAMPMChangedNotification object:_currentTextField userInfo:nil];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
PerformKeyEquivalent event
|
||||
We need to override that to handle the tab key
|
||||
*/
|
||||
- (BOOL)performKeyEquivalent:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled] || !_currentTextField || [[self window] firstResponder] != _datePicker)
|
||||
return NO;
|
||||
|
||||
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter)
|
||||
{
|
||||
if ([anEvent modifierFlags] & CPShiftKeyMask)
|
||||
[self insertBacktab:self];
|
||||
else
|
||||
[self insertTab:self];
|
||||
|
||||
return YES;
|
||||
}
|
||||
else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter)
|
||||
{
|
||||
[self insertBacktab:self];
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [super performKeyEquivalent:anEvent];
|
||||
}
|
||||
|
||||
- (_CPDatePickerElementTextField)_nextVisibleTextFieldFrom:(_CPDatePickerElementTextField)aTextField
|
||||
{
|
||||
var runner = [aTextField nextTextField];
|
||||
|
||||
// If we wrapped back to the start immediately, or runner is nil, we are done.
|
||||
if (!runner || runner == _firstTextField)
|
||||
return nil;
|
||||
|
||||
// Traverse hidden fields
|
||||
while (runner && [runner isHidden])
|
||||
{
|
||||
// If we hit the absolute last field and it is hidden, we've reached the end.
|
||||
if (runner == _lastTextField)
|
||||
return nil;
|
||||
|
||||
runner = [runner nextTextField];
|
||||
|
||||
// Safety: if we wrapped back to the start inside the loop
|
||||
if (runner == _firstTextField)
|
||||
return nil;
|
||||
}
|
||||
|
||||
return runner;
|
||||
}
|
||||
|
||||
- (_CPDatePickerElementTextField)_previousVisibleTextFieldFrom:(_CPDatePickerElementTextField)aTextField
|
||||
{
|
||||
var runner = [aTextField previousTextField];
|
||||
|
||||
// If we wrapped back to the end immediately, or runner is nil, we are done.
|
||||
if (!runner || runner == _lastTextField)
|
||||
return nil;
|
||||
|
||||
// Traverse hidden fields
|
||||
while (runner && [runner isHidden])
|
||||
{
|
||||
// If we hit the absolute first field and it is hidden, we've reached the start.
|
||||
if (runner == _firstTextField)
|
||||
return nil;
|
||||
|
||||
runner = [runner previousTextField];
|
||||
|
||||
// Safety: if we wrapped back to the end inside the loop
|
||||
if (runner == _lastTextField)
|
||||
return nil;
|
||||
}
|
||||
|
||||
return runner;
|
||||
}
|
||||
|
||||
- (void)insertTab:(id)sender
|
||||
{
|
||||
if (!_currentTextField)
|
||||
return;
|
||||
|
||||
// Ensure boundaries are up to date
|
||||
[_datePickerElementView _updateResponderTextField];
|
||||
|
||||
var nextField = [self _nextVisibleTextFieldFrom:_currentTextField];
|
||||
|
||||
if (nextField)
|
||||
{
|
||||
[self _selectTextField:nextField];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We reached the visual end. Manually find the next external view.
|
||||
// We cannot rely on [[self window] selectNextKeyView:self] because it might
|
||||
// loop back into our own internal fields or select 'self' which refuses focus.
|
||||
var nextView = [_currentTextField nextValidKeyView];
|
||||
|
||||
// Skip any view that is part of this control (descendant)
|
||||
while (nextView && [nextView isDescendantOf:self])
|
||||
{
|
||||
// If we looped back to the current field, we are trapped in a closed loop with no exit.
|
||||
if (nextView == _currentTextField)
|
||||
{
|
||||
nextView = nil;
|
||||
break;
|
||||
}
|
||||
nextView = [nextView nextValidKeyView];
|
||||
}
|
||||
|
||||
if (nextView)
|
||||
[[self window] makeFirstResponder:nextView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)insertBacktab:(id)sender
|
||||
{
|
||||
if (!_currentTextField)
|
||||
return;
|
||||
|
||||
[_datePickerElementView _updateResponderTextField];
|
||||
|
||||
var prevField = [self _previousVisibleTextFieldFrom:_currentTextField];
|
||||
|
||||
if (prevField)
|
||||
{
|
||||
[self _selectTextField:prevField];
|
||||
}
|
||||
else
|
||||
{
|
||||
// We reached the visual start. Manually find the previous external view.
|
||||
var prevView = [_currentTextField previousValidKeyView];
|
||||
|
||||
// Skip any view that is part of this control
|
||||
while (prevView && [prevView isDescendantOf:self])
|
||||
{
|
||||
if (prevView == _currentTextField)
|
||||
{
|
||||
prevView = nil;
|
||||
break;
|
||||
}
|
||||
prevView = [prevView previousValidKeyView];
|
||||
}
|
||||
|
||||
if (prevView)
|
||||
[[self window] makeFirstResponder:prevView];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)moveRight:(id)sender
|
||||
{
|
||||
if (!_currentTextField)
|
||||
return;
|
||||
|
||||
[_datePickerElementView _updateResponderTextField];
|
||||
|
||||
// Use the helper to skip hidden fields
|
||||
var nextField = [self _nextVisibleTextFieldFrom:_currentTextField];
|
||||
|
||||
if (nextField)
|
||||
[self _selectTextField:nextField];
|
||||
}
|
||||
|
||||
- (void)moveLeft:(id)sender
|
||||
{
|
||||
if (!_currentTextField)
|
||||
return;
|
||||
|
||||
[_datePickerElementView _updateResponderTextField];
|
||||
|
||||
// Use the helper to skip hidden fields to be safe
|
||||
var prevField = [self _previousVisibleTextFieldFrom:_currentTextField];
|
||||
|
||||
if (prevField)
|
||||
[self _selectTextField:prevField];
|
||||
}
|
||||
|
||||
- (void)moveDown:(id)sender
|
||||
{
|
||||
if (!_currentTextField)
|
||||
return;
|
||||
|
||||
[_currentTextField _invalidTimer];
|
||||
[_stepper setDoubleValue:[_currentTextField intValue]];
|
||||
[_stepper performClickDown:self];
|
||||
}
|
||||
|
||||
- (void)moveUp:(id)sender
|
||||
{
|
||||
if (!_currentTextField)
|
||||
return;
|
||||
|
||||
[_currentTextField _invalidTimer];
|
||||
[_stepper setDoubleValue:[_currentTextField intValue]];
|
||||
[_stepper performClickUp:self];
|
||||
}
|
||||
|
||||
- (void)insertNewline:(id)sender
|
||||
{
|
||||
if (!_currentTextField)
|
||||
return;
|
||||
|
||||
[_currentTextField _endEditing];
|
||||
}
|
||||
|
||||
/*! KeyDown event
|
||||
We just care about the event A/P and every numbers
|
||||
*/
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
[self interpretKeyEvents:[anEvent]];
|
||||
|
||||
var characters = [anEvent characters];
|
||||
|
||||
if ([_datePicker _isAmericanFormat] && [_currentTextField dateType] == CPAMPMDateType && [characters length] > 0)
|
||||
{
|
||||
var charUpper = [characters uppercaseString];
|
||||
|
||||
if (charUpper === "A")
|
||||
{
|
||||
[_currentTextField setStringValue:@"AM"];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldAMPMChangedNotification object:_currentTextField userInfo:nil];
|
||||
return;
|
||||
}
|
||||
else if (charUpper === "P")
|
||||
{
|
||||
[_currentTextField setStringValue:@"PM"];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldAMPMChangedNotification object:_currentTextField userInfo:nil];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass the event down to the specific field (which handles numeric input validation via regex)
|
||||
[_currentTextField setValueForKeyEvent:anEvent];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Layout methods
|
||||
|
||||
/*! Layout the subviews
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[_datePicker _sizeToControlSize];
|
||||
[self setFrameSize:[_datePicker frameSize]];
|
||||
|
||||
[super layoutSubviews];
|
||||
|
||||
var frameSize,
|
||||
bezelInset = [_datePicker valueForThemeAttribute:@"bezel-inset" inState:[_datePicker themeState]];
|
||||
|
||||
// Check the mode to display or not the stepper
|
||||
if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle)
|
||||
{
|
||||
[_stepper setHidden:NO];
|
||||
[_stepper setControlSize:[_datePicker controlSize]];
|
||||
|
||||
frameSize = CGSizeMake(CGRectGetWidth([_datePicker frame]) - CGRectGetWidth([_stepper frame]) - [_datePicker currentValueForThemeAttribute:@"stepper-margin"], CGRectGetHeight([_datePicker frame]));
|
||||
|
||||
frameSize.width -= bezelInset.left;
|
||||
frameSize.height -= bezelInset.top + bezelInset.bottom;
|
||||
|
||||
[_datePickerElementView setFrameSize:frameSize];
|
||||
[_datePickerElementView setFrameOrigin:CGPointMake(bezelInset.left, bezelInset.top)];
|
||||
|
||||
[_stepper setFrameOrigin:CGPointMake(CGRectGetMaxX([_datePickerElementView frame]) + [_datePicker currentValueForThemeAttribute:@"stepper-margin"], bezelInset.top + CGRectGetHeight([_datePickerElementView frame]) / 2 - CGRectGetHeight([_stepper frame]) / 2)];
|
||||
}
|
||||
else if ([_datePicker datePickerStyle] == CPTextFieldDatePickerStyle)
|
||||
{
|
||||
frameSize = CGSizeMake(CGRectGetWidth([_datePicker frame]), CGRectGetHeight([_datePicker frame]));
|
||||
|
||||
frameSize.width -= bezelInset.left + bezelInset.right;
|
||||
frameSize.height -= bezelInset.top + bezelInset.bottom;
|
||||
|
||||
[_datePickerElementView setFrameSize:frameSize];
|
||||
[_datePickerElementView setFrameOrigin:CGPointMake(bezelInset.left, bezelInset.top)];
|
||||
[_stepper setHidden:YES];
|
||||
}
|
||||
|
||||
// FIXME: should be done in a self setControlSize
|
||||
[_datePickerElementView setControlSize:[_datePicker controlSize]];
|
||||
|
||||
[_datePickerElementView setNeedsLayout];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Override observers
|
||||
|
||||
- (void)_removeObservers
|
||||
{
|
||||
if (!_isObserving)
|
||||
return;
|
||||
|
||||
[super _removeObservers];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPDatePickerElementTextFieldBecomeFirstResponder object:self];
|
||||
}
|
||||
|
||||
- (void)_addObservers
|
||||
{
|
||||
if (_isObserving)
|
||||
return;
|
||||
|
||||
[super _addObservers];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_datePickerElementTextFieldBecomeFirstResponder:) name:CPDatePickerElementTextFieldBecomeFirstResponder object:self];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* CPDictionaryController.j
|
||||
* AppKit
|
||||
*
|
||||
* Adapted from Cocotron, by Johannes Fortmann
|
||||
*
|
||||
* Created by Blair Duncan
|
||||
* Copyright 2013, SGL Studio, BBDO Toronto 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 "CPArrayController.j"
|
||||
|
||||
@implementation CPDictionaryController : CPArrayController
|
||||
{
|
||||
CPDictionary _contentDictionary;
|
||||
|
||||
CPArray _includedKeys @accessors(property=includedKeys);
|
||||
CPArray _excludedKeys @accessors(property=excludedKeys);
|
||||
|
||||
CPString _initialKey @accessors(property=initialKey);
|
||||
id _initialValue @accessors(property=initialValue);
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_initialKey = @"key";
|
||||
_initialValue = @"value";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)newObject
|
||||
{
|
||||
var keys = [_contentDictionary allKeys],
|
||||
newKey = _initialKey,
|
||||
count = 0;
|
||||
|
||||
if ([keys containsObject:newKey])
|
||||
while ([keys containsObject:newKey])
|
||||
newKey = [CPString stringWithFormat:@"%@%i", _initialKey, ++count];
|
||||
|
||||
return [self _newObjectWithKey:newKey value:_initialValue];
|
||||
}
|
||||
|
||||
- (id)_newObjectWithKey:(CPString)aKey value:(id)aValue
|
||||
{
|
||||
var aNewObject = [_CPDictionaryControllerKeyValuePair new];
|
||||
|
||||
aNewObject._dictionary = _contentDictionary;
|
||||
aNewObject._controller = self;
|
||||
aNewObject._key = aKey;
|
||||
|
||||
if (aValue != nil)
|
||||
[aNewObject setValue:aValue];
|
||||
|
||||
return aNewObject;
|
||||
}
|
||||
|
||||
- (CPDictionary)contentDictionary
|
||||
{
|
||||
return _contentDictionary;
|
||||
}
|
||||
|
||||
- (void)setContentDictionary:(CPDictionary)aDictionary
|
||||
{
|
||||
if (aDictionary == _contentDictionary)
|
||||
return;
|
||||
|
||||
if ([aDictionary isKindOfClass:[CPDictionary class]])
|
||||
_contentDictionary = aDictionary;
|
||||
else
|
||||
_contentDictionary = nil;
|
||||
|
||||
var array = [CPArray array],
|
||||
allKeys = [_contentDictionary allKeys];
|
||||
|
||||
[allKeys addObjectsFromArray:_includedKeys];
|
||||
|
||||
var iter = [[CPSet setWithArray:allKeys] objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) != nil)
|
||||
if (![_excludedKeys containsObject:obj])
|
||||
[array addObject:[self _newObjectWithKey:obj value:nil]];
|
||||
|
||||
[super setContent:array];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPIncludedKeys = @"CPIncludedKeys",
|
||||
CPExcludedKeys = @"CPExcludedKeys";
|
||||
|
||||
@implementation CPDictionaryController (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_includedKeys = [aCoder decodeObjectForKey:CPIncludedKeys];
|
||||
_excludedKeys = [aCoder decodeObjectForKey:CPExcludedKeys];
|
||||
_initialKey = @"key";
|
||||
_initialValue = @"value";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_includedKeys forKey:CPIncludedKeys];
|
||||
[aCoder encodeObject:_excludedKeys forKey:CPExcludedKeys];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
|
||||
|
||||
@implementation _CPDictionaryControllerKeyValuePair : CPObject
|
||||
{
|
||||
CPString _key @accessors(property=key);
|
||||
CPDictionary _dictionary @accessors(property=dictionary);
|
||||
CPDictionaryController _controller @accessors(property=controller);
|
||||
}
|
||||
|
||||
- (id)value
|
||||
{
|
||||
return [_dictionary objectForKey:_key];
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue
|
||||
{
|
||||
[_dictionary setObject:aValue forKey:_key];
|
||||
}
|
||||
|
||||
- (BOOL)isExplicitlyIncluded
|
||||
{
|
||||
return [[_controller _includedKeys] containsObject:_key];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
+43
-45
@@ -23,16 +23,12 @@
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@import "CPAlert.j"
|
||||
@import "CPApplication.j"
|
||||
@import "CPResponder.j"
|
||||
@import "CPSavePanel.j"
|
||||
@import "CPViewController.j"
|
||||
@import "CPWindowController.j"
|
||||
|
||||
@class CPDocumentController
|
||||
|
||||
@global CPApp
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@@ -129,7 +125,7 @@ var CPDocumentUntitledCount = 0;
|
||||
if (self)
|
||||
{
|
||||
_windowControllers = [];
|
||||
_viewControllersForWindowControllers = @{};
|
||||
_viewControllersForWindowControllers = [CPDictionary dictionary];
|
||||
|
||||
_hasUndoManager = YES;
|
||||
_changeCount = 0;
|
||||
@@ -146,7 +142,7 @@ var CPDocumentUntitledCount = 0;
|
||||
@param anError not used
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)initWithType:(CPString)aType error:(/*{*/CPError/*}*/)anError
|
||||
- (id)initWithType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
@@ -215,7 +211,7 @@ var CPDocumentUntitledCount = 0;
|
||||
@throws CPUnsupportedMethodException if this method hasn't been overridden by the subclass
|
||||
@return the document data
|
||||
*/
|
||||
- (CPData)dataOfType:(CPString)aType error:(/*{*/CPError/*}*/)anError
|
||||
- (CPData)dataOfType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:"dataOfType:error: must be overridden by the document subclass."];
|
||||
@@ -244,6 +240,11 @@ var CPDocumentUntitledCount = 0;
|
||||
{
|
||||
}
|
||||
|
||||
- (CPWindowController)firstEligibleExistingWindowController
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Creating and managing window controllers
|
||||
/*!
|
||||
Creates the window controller for this document.
|
||||
@@ -256,7 +257,6 @@ var CPDocumentUntitledCount = 0;
|
||||
- (void)makeViewAndWindowControllers
|
||||
{
|
||||
var viewCibName = [self viewCibName],
|
||||
windowCibName = [self windowCibName],
|
||||
viewController = nil,
|
||||
windowController = nil;
|
||||
|
||||
@@ -264,21 +264,31 @@ var CPDocumentUntitledCount = 0;
|
||||
if ([viewCibName length])
|
||||
viewController = [[CPViewController alloc] initWithCibName:viewCibName bundle:nil owner:self];
|
||||
|
||||
// From a cib if we have one.
|
||||
if ([windowCibName length])
|
||||
windowController = [[CPWindowController alloc] initWithWindowCibName:windowCibName owner:self];
|
||||
// If we have a view controller, check if we have a free window for it.
|
||||
if (viewController)
|
||||
windowController = [self firstEligibleExistingWindowController];
|
||||
|
||||
// If not you get a standard window capable of displaying multiple documents and view
|
||||
else if (viewController)
|
||||
// If not, create one.
|
||||
if (!windowController)
|
||||
{
|
||||
var view = [viewController view],
|
||||
viewFrame = [view frame];
|
||||
var windowCibName = [self windowCibName];
|
||||
|
||||
viewFrame.origin = CGPointMake(50, 50);
|
||||
// From a cib if we have one.
|
||||
if ([windowCibName length])
|
||||
windowController = [[CPWindowController alloc] initWithWindowCibName:windowCibName owner:self];
|
||||
|
||||
var theWindow = [[CPWindow alloc] initWithContentRect:viewFrame styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask];
|
||||
// If not you get a standard window capable of displaying multiple documents and view
|
||||
else if (viewController)
|
||||
{
|
||||
var view = [viewController view],
|
||||
viewFrame = [view frame];
|
||||
|
||||
windowController = [[CPWindowController alloc] initWithWindow:theWindow];
|
||||
viewFrame.origin = CGPointMake(50, 50);
|
||||
|
||||
var theWindow = [[CPWindow alloc] initWithContentRect:viewFrame styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask];
|
||||
|
||||
windowController = [[CPWindowController alloc] initWithWindow:theWindow];
|
||||
}
|
||||
}
|
||||
|
||||
if (windowController && viewController)
|
||||
@@ -515,9 +525,7 @@ var CPDocumentUntitledCount = 0;
|
||||
|
||||
alert("There was an error retrieving the document.");
|
||||
|
||||
var theDelegate = session.delegate;
|
||||
|
||||
theDelegate.isa.objj_msgSend3(theDelegate, session.didReadSelector, self, NO, session.contextInfo);
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -542,9 +550,7 @@ var CPDocumentUntitledCount = 0;
|
||||
|
||||
_writeRequest = nil;
|
||||
|
||||
var theDelegate = session.delegate;
|
||||
|
||||
theDelegate.isa.objj_msgSend3(theDelegate, session.didSaveSelector, self, NO, session.contextInfo);
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
|
||||
[self _sendDocumentSavedNotification:NO];
|
||||
}
|
||||
}
|
||||
@@ -557,15 +563,14 @@ var CPDocumentUntitledCount = 0;
|
||||
*/
|
||||
- (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)aData
|
||||
{
|
||||
var session = aConnection.session,
|
||||
theDelegate = session.delegate;
|
||||
var session = aConnection.session;
|
||||
|
||||
// READ
|
||||
if (aConnection == _readConnection)
|
||||
{
|
||||
[self readFromData:[CPData dataWithRawString:aData] ofType:session.fileType error:nil];
|
||||
|
||||
theDelegate.isa.objj_msgSend3(theDelegate, session.didReadSelector, self, YES, session.contextInfo);
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, YES, session.contextInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -574,7 +579,7 @@ var CPDocumentUntitledCount = 0;
|
||||
|
||||
_writeRequest = nil;
|
||||
|
||||
theDelegate.isa.objj_msgSend3(theDelegate, session.didSaveSelector, self, YES, session.contextInfo);
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, YES, session.contextInfo);
|
||||
[self _sendDocumentSavedNotification:YES];
|
||||
}
|
||||
}
|
||||
@@ -585,11 +590,10 @@ var CPDocumentUntitledCount = 0;
|
||||
*/
|
||||
- (void)connection:(CPURLConnection)aConnection didFailWithError:(CPError)anError
|
||||
{
|
||||
var session = aConnection.session,
|
||||
theDelegate = session.delegate;
|
||||
var session = aConnection.session;
|
||||
|
||||
if (_readConnection == aConnection)
|
||||
theDelegate.isa.objj_msgSend3(theDelegate, session.didReadSelector, self, NO, session.contextInfo);
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
|
||||
|
||||
else
|
||||
{
|
||||
@@ -603,7 +607,7 @@ var CPDocumentUntitledCount = 0;
|
||||
|
||||
alert("There was an error saving the document.");
|
||||
|
||||
theDelegate.isa.objj_msgSend3(theDelegate, session.didSaveSelector, self, NO, session.contextInfo);
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
|
||||
[self _sendDocumentSavedNotification:NO];
|
||||
}
|
||||
}
|
||||
@@ -864,27 +868,21 @@ var CPDocumentUntitledCount = 0;
|
||||
[self canCloseDocumentWithDelegate:self shouldCloseSelector:@selector(_document:shouldClose:context:) contextInfo:{delegate:delegate, selector:selector, context:info}];
|
||||
|
||||
else if ([delegate respondsToSelector:selector])
|
||||
delegate.isa.objj_msgSend3(delegate, selector, self, YES, info);
|
||||
objj_msgSend(delegate, selector, self, YES, info);
|
||||
}
|
||||
|
||||
- (void)_document:(CPDocument)aDocument shouldClose:(BOOL)shouldClose context:(Object)context
|
||||
{
|
||||
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)
|
||||
theDelegate.isa.objj_msgSend3(theDelegate, context.selector, aDocument, shouldClose, context.context);
|
||||
objj_msgSend(context.delegate, context.selector, aDocument, shouldClose, context.context);
|
||||
}
|
||||
|
||||
- (void)canCloseDocumentWithDelegate:(id)aDelegate shouldCloseSelector:(SEL)aSelector contextInfo:(Object)context
|
||||
{
|
||||
if (![self isDocumentEdited])
|
||||
return [aDelegate respondsToSelector:aSelector] && aDelegate.isa.objj_msgSend3(aDelegate, aSelector, self, YES, context);
|
||||
return [aDelegate respondsToSelector:aSelector] && objj_msgSend(aDelegate, aSelector, self, YES, context);
|
||||
|
||||
_canCloseAlert = [[CPAlert alloc] init];
|
||||
|
||||
@@ -913,8 +911,8 @@ var CPDocumentUntitledCount = 0;
|
||||
|
||||
if (returnCode === 0)
|
||||
[self saveDocumentWithDelegate:delegate didSaveSelector:selector contextInfo:context];
|
||||
else if (delegate != null)
|
||||
delegate.isa.objj_msgSend3(delegate, selector, self, returnCode === 2, context);
|
||||
else
|
||||
objj_msgSend(delegate, selector, self, returnCode === 2, context);
|
||||
|
||||
_canCloseAlert = nil;
|
||||
}
|
||||
|
||||
@@ -25,10 +25,6 @@
|
||||
|
||||
@import "CPDocument.j"
|
||||
@import "CPOpenPanel.j"
|
||||
@import "CPMenuItem.j"
|
||||
@import "CPWindowController.j"
|
||||
|
||||
@global CPApp
|
||||
|
||||
|
||||
var CPSharedDocumentController = nil;
|
||||
@@ -106,7 +102,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];
|
||||
|
||||
@@ -128,7 +124,7 @@ var CPSharedDocumentController = nil;
|
||||
@param anError not used
|
||||
@return the created document
|
||||
*/
|
||||
- (CPDocument)makeUntitledDocumentOfType:(CPString)aType error:(/*{*/CPError/*}*/)anError
|
||||
- (CPDocument)makeUntitledDocumentOfType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
return [[[self documentClassForType:aType] alloc] initWithType:aType error:anError];
|
||||
}
|
||||
@@ -148,7 +144,7 @@ var CPSharedDocumentController = nil;
|
||||
{
|
||||
var type = [self typeForContentsOfURL:anAbsoluteURL error:anError];
|
||||
|
||||
result = [self makeDocumentWithContentsOfURL:anAbsoluteURL ofType:type delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:@{ @"shouldDisplay": shouldDisplay }];
|
||||
result = [self makeDocumentWithContentsOfURL:anAbsoluteURL ofType:type delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:[CPDictionary dictionaryWithObject:shouldDisplay forKey:@"shouldDisplay"]];
|
||||
|
||||
[self addDocument:result];
|
||||
|
||||
@@ -253,14 +249,6 @@ var CPSharedDocumentController = nil;
|
||||
return _documents;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPDocument object associated with the main window.
|
||||
*/
|
||||
- (CPDocument)currentDocument
|
||||
{
|
||||
return [[[CPApp mainWindow] windowController] document];
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds \c aDocument under the control of the receiver.
|
||||
@param aDocument the document to add
|
||||
@@ -279,33 +267,6 @@ var CPSharedDocumentController = nil;
|
||||
[_documents removeObjectIdenticalTo:aDocument];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document object whose window controller
|
||||
owns a specified window.
|
||||
*/
|
||||
- (CPDocument)documentForWindow:(CPWindow)aWindow
|
||||
{
|
||||
return [[aWindow windowController] document];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a Boolean value that indicates whether the receiver
|
||||
has any documents with unsaved changes.
|
||||
*/
|
||||
- (BOOL)hasEditedDocuments
|
||||
{
|
||||
var iter = [_documents objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) != nil)
|
||||
{
|
||||
if ([obj isDocumentEdited])
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CPString)defaultType
|
||||
{
|
||||
return [_documentTypes[0] objectForKey:@"CPBundleTypeName"];
|
||||
@@ -378,10 +339,10 @@ var CPSharedDocumentController = nil;
|
||||
- (void)closeAllDocumentsWithDelegate:(id)aDelegate didCloseAllSelector:(SEL)didCloseSelector contextInfo:(Object)info
|
||||
{
|
||||
var context = {
|
||||
delegate: aDelegate,
|
||||
selector: didCloseSelector,
|
||||
context: info
|
||||
};
|
||||
delegate: aDelegate,
|
||||
selector: didCloseSelector,
|
||||
context: info
|
||||
};
|
||||
|
||||
[self _closeDocumentsStartingWith:nil shouldClose:YES context:context];
|
||||
}
|
||||
@@ -402,10 +363,8 @@ var CPSharedDocumentController = nil;
|
||||
}
|
||||
}
|
||||
|
||||
var theDelegate = context.delegate;
|
||||
|
||||
if ([theDelegate respondsToSelector:context.selector])
|
||||
theDelegate.isa.objj_msgSend3(theDelegate, context.selector, self, [[self documents] count] === 0, context.context);
|
||||
if ([context.delegate respondsToSelector:context.selector])
|
||||
objj_msgSend(context.delegate, context.selector, self, [[self documents] count] === 0, context.context);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+66
-93
@@ -20,18 +20,22 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPDragServer_Constants.j"
|
||||
@import "CPApplication.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPView.j"
|
||||
@import "CPWindow_Constants.j"
|
||||
@import "CPViewAnimation.j"
|
||||
@import "CPWindow.j"
|
||||
|
||||
@class CPWindow // This file is imported by CPWindow.j
|
||||
@class _CPDOMDataTransferPasteboard
|
||||
|
||||
@global CPApp
|
||||
CPDragOperationNone = 0;
|
||||
CPDragOperationCopy = 1 << 1;
|
||||
CPDragOperationLink = 1 << 1;
|
||||
CPDragOperationGeneric = 1 << 2;
|
||||
CPDragOperationPrivate = 1 << 3;
|
||||
CPDragOperationMove = 1 << 4;
|
||||
CPDragOperationDelete = 1 << 5;
|
||||
CPDragOperationEvery = -1;
|
||||
|
||||
#define DRAGGING_WINDOW(anObject) ([anObject isKindOfClass:[CPWindow class]] ? anObject : [anObject window])
|
||||
|
||||
@@ -68,7 +72,7 @@ var CPDragServerSource = nil,
|
||||
- (unsigned)draggingSourceOperationMask
|
||||
*/
|
||||
|
||||
- (CGPoint)draggingLocation
|
||||
- (CPPoint)draggingLocation
|
||||
{
|
||||
return [[CPDragServer sharedDragServer] draggingLocation];
|
||||
}
|
||||
@@ -133,10 +137,6 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
unsigned _dragOperation;
|
||||
|
||||
CPTimer _draggingUpdateTimer;
|
||||
|
||||
// Animation State
|
||||
CGPoint _pendingEndLocation;
|
||||
CPDragOperation _pendingEndOperation;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -168,7 +168,7 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
|
||||
if (self)
|
||||
{
|
||||
_draggedWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessWindowMask];
|
||||
_draggedWindow = [[CPWindow alloc] initWithContentRect:_CGRectMakeZero() styleMask:CPBorderlessWindowMask];
|
||||
|
||||
[_draggedWindow setLevel:CPDraggingWindowLevel];
|
||||
}
|
||||
@@ -206,12 +206,7 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
- (void)draggingSourceUpdatedWithGlobalLocation:(CGPoint)aGlobalLocation
|
||||
{
|
||||
if (![CPPlatform supportsDragAndDrop])
|
||||
{
|
||||
var frame = [_draggedWindow frame];
|
||||
frame.origin.x = aGlobalLocation.x - _draggingOffset.width;
|
||||
frame.origin.y = aGlobalLocation.y - _draggingOffset.height;
|
||||
[_draggedWindow _setFrame:frame display:YES animate:NO constrainWidth:NO constrainHeight:NO];
|
||||
}
|
||||
[_draggedWindow setFrameOrigin:_CGPointMake(aGlobalLocation.x - _draggingOffset.width, aGlobalLocation.y - _draggingOffset.height)];
|
||||
|
||||
if (_implementedDraggingSourceMethods & CPDraggingSource_draggedImage_movedTo_)
|
||||
[_draggingSource draggedImage:[_draggedView image] movedTo:aGlobalLocation];
|
||||
@@ -258,7 +253,7 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
_draggingUpdateTimer = [CPTimer scheduledTimerWithTimeInterval:CPDragServerPeriodicUpdateInterval
|
||||
target:self
|
||||
selector:@selector(_sendPeriodicDraggingUpdate:)
|
||||
userInfo:@{ "platformWindow":aPlatformWindow, "location":aLocation }
|
||||
userInfo:[CPDictionary dictionaryWithJSObject:{platformWindow:aPlatformWindow, location:aLocation}]
|
||||
repeats:NO];
|
||||
|
||||
var scrollView = [_draggingDestination isKindOfClass:[CPView class]] ? [_draggingDestination enclosingScrollView] : nil;
|
||||
@@ -266,28 +261,19 @@ 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))
|
||||
{
|
||||
if ([scrollView hasVerticalScroller])
|
||||
{
|
||||
if (eventLocation.y < CGRectGetMinY(insetBounds))
|
||||
deltaY = CGRectGetMinY(insetBounds) - eventLocation.y;
|
||||
else if (eventLocation.y > CGRectGetMaxY(insetBounds))
|
||||
deltaY = CGRectGetMaxY(insetBounds) - eventLocation.y;
|
||||
if (eventLocation.y < _CGRectGetMinY(insetBounds))
|
||||
deltaY = _CGRectGetMinY(insetBounds) - eventLocation.y;
|
||||
else if (eventLocation.y > _CGRectGetMaxY(insetBounds))
|
||||
deltaY = _CGRectGetMaxY(insetBounds) - eventLocation.y;
|
||||
if (deltaY < -insetBounds.size.height)
|
||||
deltaY = -insetBounds.size.height;
|
||||
if (deltaY > insetBounds.size.height)
|
||||
@@ -296,17 +282,17 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
|
||||
if ([scrollView hasHorizontalScroller])
|
||||
{
|
||||
if (eventLocation.x < CGRectGetMinX(insetBounds))
|
||||
deltaX = CGRectGetMinX(insetBounds) - eventLocation.x;
|
||||
else if (eventLocation.x > CGRectGetMaxX(insetBounds))
|
||||
deltaX = CGRectGetMaxX(insetBounds) - eventLocation.x;
|
||||
if (eventLocation.x < _CGRectGetMinX(insetBounds))
|
||||
deltaX = _CGRectGetMinX(insetBounds) - eventLocation.x;
|
||||
else if (eventLocation.x > _CGRectGetMaxX(insetBounds))
|
||||
deltaX = _CGRectGetMaxX(insetBounds) - eventLocation.x;
|
||||
if (deltaX < -insetBounds.size.width)
|
||||
deltaX = -insetBounds.size.width;
|
||||
if (deltaX > insetBounds.size.width)
|
||||
deltaX = insetBounds.size.width;
|
||||
}
|
||||
|
||||
var scrollPoint = CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY);
|
||||
var scrollPoint = _CGPointMake(bounds.origin.x - deltaX, bounds.origin.y - deltaY);
|
||||
|
||||
[contentView scrollToPoint:scrollPoint];
|
||||
[[scrollView _headerView] scrollPoint:scrollPoint];
|
||||
@@ -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])
|
||||
@@ -424,26 +370,22 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
if (mouseDownWindow)
|
||||
mouseDownEventLocation = [mouseDownWindow convertBaseToGlobal:mouseDownEventLocation];
|
||||
|
||||
_draggingOffset = CGSizeMake(mouseDownEventLocation.x - viewLocation.x, mouseDownEventLocation.y - viewLocation.y);
|
||||
_draggingOffset = _CGSizeMake(mouseDownEventLocation.x - viewLocation.x, mouseDownEventLocation.y - viewLocation.y);
|
||||
}
|
||||
else
|
||||
_draggingOffset = CGSizeMakeZero();
|
||||
_draggingOffset = _CGSizeMakeZero();
|
||||
|
||||
if ([CPPlatform isBrowser])
|
||||
[_draggedWindow setPlatformWindow:[aWindow platformWindow]];
|
||||
|
||||
[aView setFrameOrigin:CGPointMakeZero()];
|
||||
[aView setFrameOrigin:_CGPointMakeZero()];
|
||||
|
||||
var mouseLocation = [CPEvent mouseLocation],
|
||||
viewSize = [aView frameSize],
|
||||
startDragLocationX = mouseLocation.x - _draggingOffset.width,
|
||||
startDragLocationY = mouseLocation.y - _draggingOffset.height,
|
||||
draggedWindowFrame = CGRectMake(startDragLocationX, startDragLocationY, viewSize.width, viewSize.height);
|
||||
var mouseLocation = [CPEvent mouseLocation];
|
||||
|
||||
// Place it where the mouse pointer is.
|
||||
_startDragLocation = CGPointMake(startDragLocationX, startDragLocationY);
|
||||
|
||||
[_draggedWindow _setFrame:draggedWindowFrame display:YES animate:NO constrainWidth:NO constrainHeight:NO];
|
||||
_startDragLocation = _CGPointMake(mouseLocation.x - _draggingOffset.width, mouseLocation.y - _draggingOffset.height);
|
||||
[_draggedWindow setFrameOrigin:_startDragLocation];
|
||||
[_draggedWindow setFrameSize:[aView frame].size];
|
||||
|
||||
[[_draggedWindow contentView] addSubview:aView];
|
||||
|
||||
@@ -492,7 +434,7 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
var imageSize = [anImage size];
|
||||
|
||||
if (!_imageView)
|
||||
_imageView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, imageSize.width, imageSize.height)];
|
||||
_imageView = [[CPImageView alloc] initWithFrame:_CGRectMake(0.0, 0.0, imageSize.width, imageSize.height)];
|
||||
|
||||
[_imageView setImage:anImage];
|
||||
|
||||
@@ -519,7 +461,6 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
else if (type === CPKeyDown)
|
||||
{
|
||||
var characters = [anEvent characters];
|
||||
|
||||
if (characters === CPEscapeFunctionKey)
|
||||
{
|
||||
_dragOperation = CPDragOperationNone;
|
||||
@@ -536,7 +477,39 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
|
||||
// If we're not a mouse up, then we're going to want to grab the next event.
|
||||
[CPApp setTarget:self selector:@selector(trackDragging:)
|
||||
forNextEventMatchingMask:CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPKeyDownMask
|
||||
untilDate:nil inMode:0 dequeue:YES];
|
||||
untilDate:nil inMode:0 dequeue:NO];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPWindow (CPDraggingAdditions)
|
||||
|
||||
/* @ignore */
|
||||
- (id)_dragHitTest:(CGPoint)aPoint pasteboard:(CPPasteboard)aPasteboard
|
||||
{
|
||||
// If none of our views or ourselves has registered for drag events...
|
||||
if (!_inclusiveRegisteredDraggedTypes)
|
||||
return nil;
|
||||
|
||||
// We don't need to do this because the only place this gets called
|
||||
// -_dragHitTest: in CPPlatformWindow does this already. Perhaps to
|
||||
// be safe?
|
||||
// if (![self containsPoint:aPoint])
|
||||
// return nil;
|
||||
|
||||
var adjustedPoint = [self convertPlatformWindowToBase:aPoint],
|
||||
hitView = [_windowView hitTest:adjustedPoint];
|
||||
|
||||
while (hitView && ![aPasteboard availableTypeFromArray:[hitView registeredDraggedTypes]])
|
||||
hitView = [hitView superview];
|
||||
|
||||
if (hitView)
|
||||
return hitView;
|
||||
|
||||
if ([aPasteboard availableTypeFromArray:[self registeredDraggedTypes]])
|
||||
return self;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* CPDragServer_Constants.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
|
||||
*/
|
||||
|
||||
@typedef CPDragOperation
|
||||
CPDragOperationNone = 0;
|
||||
CPDragOperationCopy = 1 << 1;
|
||||
CPDragOperationLink = 1 << 1;
|
||||
CPDragOperationGeneric = 1 << 2;
|
||||
CPDragOperationPrivate = 1 << 3;
|
||||
CPDragOperationMove = 1 << 4;
|
||||
CPDragOperationDelete = 1 << 5;
|
||||
CPDragOperationEvery = -1;
|
||||
+166
-176
@@ -20,28 +20,162 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPEvent_Constants.j"
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPObjJRuntime.j>
|
||||
@import <Foundation/CPString.j>
|
||||
@import "CPText.j"
|
||||
|
||||
@import "CPCompatibility.j"
|
||||
@import "CGGeometry.j"
|
||||
@import "CPTrackingArea.j"
|
||||
|
||||
@class CPTextField
|
||||
@class CPWindow
|
||||
@class CPGraphicsContext
|
||||
CPLeftMouseDown = 1;
|
||||
CPLeftMouseUp = 2;
|
||||
CPRightMouseDown = 3;
|
||||
CPRightMouseUp = 4;
|
||||
CPMouseMoved = 5;
|
||||
CPLeftMouseDragged = 6;
|
||||
CPRightMouseDragged = 7;
|
||||
CPMouseEntered = 8;
|
||||
CPMouseExited = 9;
|
||||
CPKeyDown = 10;
|
||||
CPKeyUp = 11;
|
||||
CPFlagsChanged = 12;
|
||||
CPAppKitDefined = 13;
|
||||
CPSystemDefined = 14;
|
||||
CPApplicationDefined = 15;
|
||||
CPPeriodic = 16;
|
||||
CPCursorUpdate = 17;
|
||||
CPScrollWheel = 22;
|
||||
CPOtherMouseDown = 25;
|
||||
CPOtherMouseUp = 26;
|
||||
CPOtherMouseDragged = 27;
|
||||
|
||||
@global CPApp
|
||||
@global CPNewlineCharacter
|
||||
@global CPCarriageReturnCharacter
|
||||
@global CPEnterCharacter
|
||||
// iPhone Event Types
|
||||
CPTouchStart = 28;
|
||||
CPTouchMove = 29;
|
||||
CPTouchEnd = 30;
|
||||
CPTouchCancel = 31;
|
||||
|
||||
@typedef DOMEvent
|
||||
@typedef CPEventType
|
||||
CPAlphaShiftKeyMask = 1 << 16;
|
||||
CPShiftKeyMask = 1 << 17;
|
||||
CPControlKeyMask = 1 << 18;
|
||||
CPAlternateKeyMask = 1 << 19;
|
||||
CPCommandKeyMask = 1 << 20;
|
||||
CPNumericPadKeyMask = 1 << 21;
|
||||
CPHelpKeyMask = 1 << 22;
|
||||
CPFunctionKeyMask = 1 << 23;
|
||||
CPDeviceIndependentModifierFlagsMask = 0xffff0000;
|
||||
|
||||
CPLeftMouseDownMask = 1 << CPLeftMouseDown;
|
||||
CPLeftMouseUpMask = 1 << CPLeftMouseUp;
|
||||
CPRightMouseDownMask = 1 << CPRightMouseDown;
|
||||
CPRightMouseUpMask = 1 << CPRightMouseUp;
|
||||
CPOtherMouseDownMask = 1 << CPOtherMouseDown;
|
||||
CPOtherMouseUpMask = 1 << CPOtherMouseUp;
|
||||
CPMouseMovedMask = 1 << CPMouseMoved;
|
||||
CPLeftMouseDraggedMask = 1 << CPLeftMouseDragged;
|
||||
CPRightMouseDraggedMask = 1 << CPRightMouseDragged;
|
||||
CPOtherMouseDragged = 1 << CPOtherMouseDragged;
|
||||
CPMouseEnteredMask = 1 << CPMouseEntered;
|
||||
CPMouseExitedMask = 1 << CPMouseExited;
|
||||
CPCursorUpdateMask = 1 << CPCursorUpdate;
|
||||
CPKeyDownMask = 1 << CPKeyDown;
|
||||
CPKeyUpMask = 1 << CPKeyUp;
|
||||
CPFlagsChangedMask = 1 << CPFlagsChanged;
|
||||
CPAppKitDefinedMask = 1 << CPAppKitDefined;
|
||||
CPSystemDefinedMask = 1 << CPSystemDefined;
|
||||
CPApplicationDefinedMask = 1 << CPApplicationDefined;
|
||||
CPPeriodicMask = 1 << CPPeriodic;
|
||||
CPScrollWheelMask = 1 << CPScrollWheel;
|
||||
CPAnyEventMask = 0xffffffff;
|
||||
|
||||
CPUpArrowFunctionKey = "\uF700";
|
||||
CPDownArrowFunctionKey = "\uF701";
|
||||
CPLeftArrowFunctionKey = "\uF702";
|
||||
CPRightArrowFunctionKey = "\uF703";
|
||||
CPF1FunctionKey = "\uF704";
|
||||
CPF2FunctionKey = "\uF705";
|
||||
CPF3FunctionKey = "\uF706";
|
||||
CPF4FunctionKey = "\uF707";
|
||||
CPF5FunctionKey = "\uF708";
|
||||
CPF6FunctionKey = "\uF709";
|
||||
CPF7FunctionKey = "\uF70A";
|
||||
CPF8FunctionKey = "\uF70B";
|
||||
CPF9FunctionKey = "\uF70C";
|
||||
CPF10FunctionKey = "\uF70D";
|
||||
CPF11FunctionKey = "\uF70E";
|
||||
CPF12FunctionKey = "\uF70F";
|
||||
CPF13FunctionKey = "\uF710";
|
||||
CPF14FunctionKey = "\uF711";
|
||||
CPF15FunctionKey = "\uF712";
|
||||
CPF16FunctionKey = "\uF713";
|
||||
CPF17FunctionKey = "\uF714";
|
||||
CPF18FunctionKey = "\uF715";
|
||||
CPF19FunctionKey = "\uF716";
|
||||
CPF20FunctionKey = "\uF717";
|
||||
CPF21FunctionKey = "\uF718";
|
||||
CPF22FunctionKey = "\uF719";
|
||||
CPF23FunctionKey = "\uF71A";
|
||||
CPF24FunctionKey = "\uF71B";
|
||||
CPF25FunctionKey = "\uF71C";
|
||||
CPF26FunctionKey = "\uF71D";
|
||||
CPF27FunctionKey = "\uF71E";
|
||||
CPF28FunctionKey = "\uF71F";
|
||||
CPF29FunctionKey = "\uF720";
|
||||
CPF30FunctionKey = "\uF721";
|
||||
CPF31FunctionKey = "\uF722";
|
||||
CPF32FunctionKey = "\uF723";
|
||||
CPF33FunctionKey = "\uF724";
|
||||
CPF34FunctionKey = "\uF725";
|
||||
CPF35FunctionKey = "\uF726";
|
||||
CPInsertFunctionKey = "\uF727";
|
||||
CPDeleteFunctionKey = "\uF728";
|
||||
CPHomeFunctionKey = "\uF729";
|
||||
CPBeginFunctionKey = "\uF72A";
|
||||
CPEndFunctionKey = "\uF72B";
|
||||
CPPageUpFunctionKey = "\uF72C";
|
||||
CPPageDownFunctionKey = "\uF72D";
|
||||
CPPrintScreenFunctionKey = "\uF72E";
|
||||
CPScrollLockFunctionKey = "\uF72F";
|
||||
CPPauseFunctionKey = "\uF730";
|
||||
CPSysReqFunctionKey = "\uF731";
|
||||
CPBreakFunctionKey = "\uF732";
|
||||
CPResetFunctionKey = "\uF733";
|
||||
CPStopFunctionKey = "\uF734";
|
||||
CPMenuFunctionKey = "\uF735";
|
||||
CPUserFunctionKey = "\uF736";
|
||||
CPSystemFunctionKey = "\uF737";
|
||||
CPPrintFunctionKey = "\uF738";
|
||||
CPClearLineFunctionKey = "\uF739";
|
||||
CPClearDisplayFunctionKey = "\uF73A";
|
||||
CPInsertLineFunctionKey = "\uF73B";
|
||||
CPDeleteLineFunctionKey = "\uF73C";
|
||||
CPInsertCharFunctionKey = "\uF73D";
|
||||
CPDeleteCharFunctionKey = "\uF73E";
|
||||
CPPrevFunctionKey = "\uF73F";
|
||||
CPNextFunctionKey = "\uF740";
|
||||
CPSelectFunctionKey = "\uF741";
|
||||
CPExecuteFunctionKey = "\uF742";
|
||||
CPUndoFunctionKey = "\uF743";
|
||||
CPRedoFunctionKey = "\uF744";
|
||||
CPFindFunctionKey = "\uF745";
|
||||
CPHelpFunctionKey = "\uF746";
|
||||
CPModeSwitchFunctionKey = "\uF747";
|
||||
CPEscapeFunctionKey = "\u001B";
|
||||
CPSpaceFunctionKey = "\u0020";
|
||||
|
||||
|
||||
CPDOMEventDoubleClick = "dblclick";
|
||||
CPDOMEventMouseDown = "mousedown";
|
||||
CPDOMEventMouseUp = "mouseup";
|
||||
CPDOMEventMouseMoved = "mousemove";
|
||||
CPDOMEventMouseDragged = "mousedrag";
|
||||
CPDOMEventKeyUp = "keyup";
|
||||
CPDOMEventKeyDown = "keydown";
|
||||
CPDOMEventKeyPress = "keypress";
|
||||
CPDOMEventCopy = "copy";
|
||||
CPDOMEventPaste = "paste";
|
||||
CPDOMEventScrollWheel = "mousewheel";
|
||||
CPDOMEventTouchStart = "touchstart";
|
||||
CPDOMEventTouchMove = "touchmove";
|
||||
CPDOMEventTouchEnd = "touchend";
|
||||
CPDOMEventTouchCancel = "touchcancel";
|
||||
|
||||
var _CPEventPeriodicEventPeriod = 0,
|
||||
_CPEventPeriodicEventTimer = nil,
|
||||
@@ -56,7 +190,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
@implementation CPEvent : CPObject
|
||||
{
|
||||
CPEventType _type;
|
||||
CGPoint _location;
|
||||
CPPoint _location;
|
||||
unsigned _modifierFlags;
|
||||
CPTimeInterval _timestamp;
|
||||
CPGraphicsContext _context;
|
||||
@@ -66,28 +200,16 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
CPWindow _window;
|
||||
Number _windowNumber;
|
||||
CPString _characters;
|
||||
CPString _charactersIgnoringModifiers;
|
||||
CPString _charactersIgnoringModifiers
|
||||
BOOL _isARepeat;
|
||||
unsigned _keyCode;
|
||||
DOMEvent _DOMEvent;
|
||||
BOOL _isActionKey;
|
||||
int _data1;
|
||||
int _data2;
|
||||
short _subtype;
|
||||
|
||||
float _deltaX;
|
||||
float _deltaY;
|
||||
float _deltaZ;
|
||||
float _scrollingDeltaX;
|
||||
float _scrollingDeltaY;
|
||||
BOOL _hasPreciseScrollingDeltas;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
BOOL _suppressCappuccinoCut;
|
||||
BOOL _suppressCappuccinoPaste;
|
||||
#endif
|
||||
|
||||
CPTrackingArea _trackingArea;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -111,28 +233,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];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -158,27 +269,6 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext eventNumber:anEventNumber clickCount:aClickCount pressure:aPressure];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new mouse tracking event.
|
||||
|
||||
@param anEventType the event type
|
||||
@param aPoint the location of the cursor in the window specified by \c aWindowNumber
|
||||
@param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals
|
||||
@param aTimestamp the time the event occurred
|
||||
@param aWindowNumber the number of the CPWindow where the event occurred
|
||||
@param aGraphicsContext the graphics context where the event occurred
|
||||
@param anEventNumber a number for this event
|
||||
@param aTrackingArea the tracking area that triggered the event
|
||||
@throws CPInternalInconsistencyException if an invalid event type is provided
|
||||
@return the new mouse event
|
||||
*/
|
||||
+ (id)enterExitEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
eventNumber:(int)anEventNumber trackingArea:(CPTrackingArea)aTrackingArea
|
||||
{
|
||||
return [[self alloc] _initEnterExitEventWithType:anEventType location:aPoint modifierFlags:modifierFlags timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext eventNumber:anEventNumber trackingArea:aTrackingArea];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new custom event.
|
||||
|
||||
@@ -210,9 +300,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
|
||||
// Make sure these are 0 rather than nil.
|
||||
_deltaX = 0;
|
||||
_scrollingDeltaX = 0;
|
||||
_deltaY = 0;
|
||||
_scrollingDeltaY = 0;
|
||||
_deltaZ = 0;
|
||||
}
|
||||
|
||||
@@ -220,13 +308,13 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initMouseEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
- (id)_initMouseEventWithType:(CPEventType)anEventType location:(CPPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
eventNumber:(int)anEventNumber clickCount:(int)aClickCount pressure:(float)aPressure
|
||||
{
|
||||
if (self = [self _initWithType:anEventType])
|
||||
{
|
||||
_location = CGPointCreateCopy(aPoint);
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
@@ -240,35 +328,13 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initEnterExitEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
eventNumber:(int)anEventNumber trackingArea:(CPTrackingArea)aTrackingArea
|
||||
{
|
||||
if ((anEventType != CPMouseEntered) && (anEventType != CPMouseExited) && (anEventType != CPCursorUpdate))
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Invalid event type"];
|
||||
|
||||
if (self = [self _initWithType:anEventType])
|
||||
{
|
||||
_location = CGPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
_eventNumber = anEventNumber;
|
||||
_trackingArea = aTrackingArea;
|
||||
_window = [CPApp windowWithWindowNumber:aWindowNumber];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
|
||||
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CPPoint)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])
|
||||
{
|
||||
_location = CGPointCreateCopy(aPoint);
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
@@ -276,7 +342,6 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
_charactersIgnoringModifiers = unmodCharacters;
|
||||
_isARepeat = isARepeat;
|
||||
_keyCode = code;
|
||||
_isActionKey = isAnActionKey;
|
||||
_windowNumber = aWindowNumber;
|
||||
}
|
||||
|
||||
@@ -290,14 +355,13 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
{
|
||||
if (self = [self _initWithType:anEventType])
|
||||
{
|
||||
_location = CGPointCreateCopy(aPoint);
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
_subtype = aSubtype;
|
||||
_data1 = aData1;
|
||||
_data2 = aData2;
|
||||
_windowNumber = aWindowNumber;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -313,7 +377,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
*/
|
||||
- (CGPoint)locationInWindow
|
||||
{
|
||||
return CGPointMakeCopy(_location);
|
||||
return _CGPointMakeCopy(_location);
|
||||
}
|
||||
|
||||
- (CGPoint)globalLocation
|
||||
@@ -351,14 +415,6 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
return _type;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the subtype of the event.
|
||||
*/
|
||||
- (CPEventType)subtype
|
||||
{
|
||||
return _subtype;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the event's associated window.
|
||||
*/
|
||||
@@ -498,31 +554,6 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
return _deltaZ;
|
||||
}
|
||||
|
||||
- (BOOL)hasPreciseScrollingDeltas
|
||||
{
|
||||
return !!_hasPreciseScrollingDeltas;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the change in points on the x-axis for a mouse movement, unless hasPreciseScrollingDeltas is NO,
|
||||
in which case the change is measured in number of "lines" or "rows" and needs to be multiplied as appropriate
|
||||
to get change in pixels.
|
||||
*/
|
||||
- (float)scrollingDeltaX
|
||||
{
|
||||
return _scrollingDeltaX;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the change in points on the y-axis for a mouse movement, unless hasPreciseScrollingDeltas is NO,
|
||||
in which case the change is measured in number of "lines" or "columns" and needs to be multiplied as appropriate
|
||||
to get change in pixels.
|
||||
*/
|
||||
- (float)scrollingDeltaY
|
||||
{
|
||||
return _scrollingDeltaY;
|
||||
}
|
||||
|
||||
- (BOOL)_triggersKeyEquivalent:(CPString)aKeyEquivalent withModifierMask:aKeyEquivalentModifierMask
|
||||
{
|
||||
if (!aKeyEquivalent)
|
||||
@@ -584,39 +615,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
|
||||
element after processing of this event. The implication is that it should not be done by
|
||||
the CPTextField (or whatever else is controlling the input) since this would result in
|
||||
nothing being cut (because the field already cut the text out), or a double paste
|
||||
(because the field pasted as well as the browser).
|
||||
*/
|
||||
- (BOOL)_platformIsEffectingCutOrPaste
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
return _suppressCappuccinoCut || _suppressCappuccinoPaste;
|
||||
#else
|
||||
return NO;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Generates periodic events every \c aPeriod seconds.
|
||||
|
||||
@@ -636,7 +634,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
*/
|
||||
+ (void)stopPeriodicEvents
|
||||
{
|
||||
if (_CPEventPeriodicEventTimer == nil)
|
||||
if (_CPEventPeriodicEventTimer === nil)
|
||||
return;
|
||||
|
||||
window.clearTimeout(_CPEventPeriodicEventTimer);
|
||||
@@ -651,7 +649,7 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
case CPKeyDown:
|
||||
case CPKeyUp:
|
||||
case CPFlagsChanged:
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ chars=\"%@\" unmodchars=\"%@\" repeat=%d keyCode=%d", _type, CGStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _characters, _charactersIgnoringModifiers, _isARepeat, _keyCode];
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ chars=\"%@\" unmodchars=\"%@\" repeat=%d keyCode=%d", _type, CPStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _characters, _charactersIgnoringModifiers, _isARepeat, _keyCode];
|
||||
case CPLeftMouseDown:
|
||||
case CPLeftMouseUp:
|
||||
case CPRightMouseDown:
|
||||
@@ -661,25 +659,17 @@ var _CPEventPeriodicEventPeriod = 0,
|
||||
case CPRightMouseDragged:
|
||||
case CPMouseEntered:
|
||||
case CPMouseExited:
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ evNum=%d click=%d buttonNumber=%d pressure=%f", _type, CGStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _eventNumber, _clickCount, [self buttonNumber], _pressure];
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ evNum=%d click=%d buttonNumber=%d pressure=%f", _type, CPStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _eventNumber, _clickCount, [self buttonNumber], _pressure];
|
||||
default:
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ subtype=%d data1=%d data2=%d", _type, CGStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _subtype, _data1, _data2];
|
||||
return [CPString stringWithFormat:@"CPEvent: type=%d loc=%@ time=%.1f flags=0x%X win=%@ winNum=%d ctxt=%@ subtype=%d data1=%d data2=%d", _type, CPStringFromPoint(_location), _timestamp, _modifierFlags, _window, _windowNumber, _context, _subtype, _data1, _data2];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPTrackingArea)trackingArea
|
||||
{
|
||||
if ((_type !== CPMouseEntered) && (_type !== CPMouseExited) && (_type !== CPCursorUpdate))
|
||||
[CPException raise:CPInternalInconsistencyException format:@"You can't call trackingArea for events of type %#x", _type];
|
||||
|
||||
return _trackingArea;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
function _CPEventFirePeriodEvent()
|
||||
{
|
||||
[CPApp sendEvent:[CPEvent otherEventWithType:CPPeriodic location:CGPointMakeZero() modifierFlags:0 timestamp:[CPEvent currentTimestamp] windowNumber:0 context:nil subtype:0 data1:0 data2:0]];
|
||||
[CPApp sendEvent:[CPEvent otherEventWithType:CPPeriodic location:_CGPointMakeZero() modifierFlags:0 timestamp:[CPEvent currentTimestamp] windowNumber:0 context:nil subtype:0 data1:0 data2:0]];
|
||||
}
|
||||
|
||||
var CPEventClass = [CPEvent class];
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* CPEvent_Constants.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
|
||||
*/
|
||||
|
||||
CPLeftMouseDown = 1;
|
||||
CPLeftMouseUp = 2;
|
||||
CPRightMouseDown = 3;
|
||||
CPRightMouseUp = 4;
|
||||
CPMouseMoved = 5;
|
||||
CPLeftMouseDragged = 6;
|
||||
CPRightMouseDragged = 7;
|
||||
CPMouseEntered = 8;
|
||||
CPMouseExited = 9;
|
||||
CPKeyDown = 10;
|
||||
CPKeyUp = 11;
|
||||
CPFlagsChanged = 12;
|
||||
CPAppKitDefined = 13;
|
||||
CPSystemDefined = 14;
|
||||
CPApplicationDefined = 15;
|
||||
CPPeriodic = 16;
|
||||
CPCursorUpdate = 17;
|
||||
CPScrollWheel = 22;
|
||||
CPOtherMouseDown = 25;
|
||||
CPOtherMouseUp = 26;
|
||||
CPOtherMouseDragged = 27;
|
||||
|
||||
// iPhone Event Types
|
||||
CPTouchStart = 28;
|
||||
CPTouchMove = 29;
|
||||
CPTouchEnd = 30;
|
||||
CPTouchCancel = 31;
|
||||
|
||||
CPAlphaShiftKeyMask = 1 << 16;
|
||||
CPShiftKeyMask = 1 << 17;
|
||||
CPControlKeyMask = 1 << 18;
|
||||
CPAlternateKeyMask = 1 << 19;
|
||||
CPCommandKeyMask = 1 << 20;
|
||||
CPNumericPadKeyMask = 1 << 21;
|
||||
CPHelpKeyMask = 1 << 22;
|
||||
CPFunctionKeyMask = 1 << 23;
|
||||
CPDeviceIndependentModifierFlagsMask = 0xffff0000;
|
||||
|
||||
CPLeftMouseDownMask = 1 << CPLeftMouseDown;
|
||||
CPLeftMouseUpMask = 1 << CPLeftMouseUp;
|
||||
CPRightMouseDownMask = 1 << CPRightMouseDown;
|
||||
CPRightMouseUpMask = 1 << CPRightMouseUp;
|
||||
CPOtherMouseDownMask = 1 << CPOtherMouseDown;
|
||||
CPOtherMouseUpMask = 1 << CPOtherMouseUp;
|
||||
CPMouseMovedMask = 1 << CPMouseMoved;
|
||||
CPLeftMouseDraggedMask = 1 << CPLeftMouseDragged;
|
||||
CPRightMouseDraggedMask = 1 << CPRightMouseDragged;
|
||||
CPOtherMouseDragged = 1 << CPOtherMouseDragged;
|
||||
CPMouseEnteredMask = 1 << CPMouseEntered;
|
||||
CPMouseExitedMask = 1 << CPMouseExited;
|
||||
CPCursorUpdateMask = 1 << CPCursorUpdate;
|
||||
CPKeyDownMask = 1 << CPKeyDown;
|
||||
CPKeyUpMask = 1 << CPKeyUp;
|
||||
CPFlagsChangedMask = 1 << CPFlagsChanged;
|
||||
CPAppKitDefinedMask = 1 << CPAppKitDefined;
|
||||
CPSystemDefinedMask = 1 << CPSystemDefined;
|
||||
CPApplicationDefinedMask = 1 << CPApplicationDefined;
|
||||
CPPeriodicMask = 1 << CPPeriodic;
|
||||
CPScrollWheelMask = 1 << CPScrollWheel;
|
||||
CPAnyEventMask = 0xffffffff;
|
||||
|
||||
CPUpArrowFunctionKey = "\uF700";
|
||||
CPDownArrowFunctionKey = "\uF701";
|
||||
CPLeftArrowFunctionKey = "\uF702";
|
||||
CPRightArrowFunctionKey = "\uF703";
|
||||
CPF1FunctionKey = "\uF704";
|
||||
CPF2FunctionKey = "\uF705";
|
||||
CPF3FunctionKey = "\uF706";
|
||||
CPF4FunctionKey = "\uF707";
|
||||
CPF5FunctionKey = "\uF708";
|
||||
CPF6FunctionKey = "\uF709";
|
||||
CPF7FunctionKey = "\uF70A";
|
||||
CPF8FunctionKey = "\uF70B";
|
||||
CPF9FunctionKey = "\uF70C";
|
||||
CPF10FunctionKey = "\uF70D";
|
||||
CPF11FunctionKey = "\uF70E";
|
||||
CPF12FunctionKey = "\uF70F";
|
||||
CPF13FunctionKey = "\uF710";
|
||||
CPF14FunctionKey = "\uF711";
|
||||
CPF15FunctionKey = "\uF712";
|
||||
CPF16FunctionKey = "\uF713";
|
||||
CPF17FunctionKey = "\uF714";
|
||||
CPF18FunctionKey = "\uF715";
|
||||
CPF19FunctionKey = "\uF716";
|
||||
CPF20FunctionKey = "\uF717";
|
||||
CPF21FunctionKey = "\uF718";
|
||||
CPF22FunctionKey = "\uF719";
|
||||
CPF23FunctionKey = "\uF71A";
|
||||
CPF24FunctionKey = "\uF71B";
|
||||
CPF25FunctionKey = "\uF71C";
|
||||
CPF26FunctionKey = "\uF71D";
|
||||
CPF27FunctionKey = "\uF71E";
|
||||
CPF28FunctionKey = "\uF71F";
|
||||
CPF29FunctionKey = "\uF720";
|
||||
CPF30FunctionKey = "\uF721";
|
||||
CPF31FunctionKey = "\uF722";
|
||||
CPF32FunctionKey = "\uF723";
|
||||
CPF33FunctionKey = "\uF724";
|
||||
CPF34FunctionKey = "\uF725";
|
||||
CPF35FunctionKey = "\uF726";
|
||||
CPInsertFunctionKey = "\uF727";
|
||||
CPDeleteFunctionKey = "\uF728";
|
||||
CPHomeFunctionKey = "\uF729";
|
||||
CPBeginFunctionKey = "\uF72A";
|
||||
CPEndFunctionKey = "\uF72B";
|
||||
CPPageUpFunctionKey = "\uF72C";
|
||||
CPPageDownFunctionKey = "\uF72D";
|
||||
CPPrintScreenFunctionKey = "\uF72E";
|
||||
CPScrollLockFunctionKey = "\uF72F";
|
||||
CPPauseFunctionKey = "\uF730";
|
||||
CPSysReqFunctionKey = "\uF731";
|
||||
CPBreakFunctionKey = "\uF732";
|
||||
CPResetFunctionKey = "\uF733";
|
||||
CPStopFunctionKey = "\uF734";
|
||||
CPMenuFunctionKey = "\uF735";
|
||||
CPUserFunctionKey = "\uF736";
|
||||
CPSystemFunctionKey = "\uF737";
|
||||
CPPrintFunctionKey = "\uF738";
|
||||
CPClearLineFunctionKey = "\uF739";
|
||||
CPClearDisplayFunctionKey = "\uF73A";
|
||||
CPInsertLineFunctionKey = "\uF73B";
|
||||
CPDeleteLineFunctionKey = "\uF73C";
|
||||
CPInsertCharFunctionKey = "\uF73D";
|
||||
CPDeleteCharFunctionKey = "\uF73E";
|
||||
CPPrevFunctionKey = "\uF73F";
|
||||
CPNextFunctionKey = "\uF740";
|
||||
CPSelectFunctionKey = "\uF741";
|
||||
CPExecuteFunctionKey = "\uF742";
|
||||
CPUndoFunctionKey = "\uF743";
|
||||
CPRedoFunctionKey = "\uF744";
|
||||
CPFindFunctionKey = "\uF745";
|
||||
CPHelpFunctionKey = "\uF746";
|
||||
CPModeSwitchFunctionKey = "\uF747";
|
||||
CPEscapeFunctionKey = "\u001B";
|
||||
CPSpaceFunctionKey = "\u0020";
|
||||
|
||||
|
||||
CPDOMEventDoubleClick = "dblclick";
|
||||
CPDOMEventMouseDown = "mousedown";
|
||||
CPDOMEventMouseUp = "mouseup";
|
||||
CPDOMEventMouseMoved = "mousemove";
|
||||
CPDOMEventMouseDragged = "mousedrag";
|
||||
CPDOMEventKeyUp = "keyup";
|
||||
CPDOMEventKeyDown = "keydown";
|
||||
CPDOMEventKeyPress = "keypress";
|
||||
CPDOMEventCopy = "copy";
|
||||
CPDOMEventPaste = "paste";
|
||||
CPDOMEventScrollWheel = "mousewheel";
|
||||
CPDOMEventTouchStart = "touchstart";
|
||||
CPDOMEventTouchMove = "touchmove";
|
||||
CPDOMEventTouchEnd = "touchend";
|
||||
CPDOMEventTouchCancel = "touchcancel";
|
||||
@@ -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
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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;
|
||||
DOMElement _DOMInnerObjectElement;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
_DOMObjectElement = document.createElement(@"object");
|
||||
_DOMObjectElement.width = @"100%";
|
||||
_DOMObjectElement.height = @"100%";
|
||||
_DOMObjectElement.style.top = @"0px";
|
||||
_DOMObjectElement.style.left = @"0px";
|
||||
_DOMObjectElement.type = @"application/x-shockwave-flash";
|
||||
_DOMObjectElement.setAttribute(@"classid", IEFlashCLSID);
|
||||
|
||||
_DOMParamElement = document.createElement(@"param");
|
||||
_DOMParamElement.name = @"movie";
|
||||
|
||||
_DOMInnerObjectElement = document.createElement(@"object");
|
||||
_DOMInnerObjectElement.width = @"100%";
|
||||
_DOMInnerObjectElement.height = @"100%";
|
||||
|
||||
_DOMObjectElement.appendChild(_DOMParamElement);
|
||||
_DOMObjectElement.appendChild(_DOMInnerObjectElement);
|
||||
|
||||
_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];
|
||||
_DOMInnerObjectElement.data = [aFlashMovie filename];
|
||||
}
|
||||
else
|
||||
[self _rebuildIEObjects];
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CPFlashMovie)flashMovie
|
||||
{
|
||||
return _flashMovie;
|
||||
}
|
||||
|
||||
- (void)setFlashVars:(CPDictionary)aDictionary
|
||||
{
|
||||
var varString = @"",
|
||||
enumerator = [aDictionary keyEnumerator],
|
||||
key;
|
||||
|
||||
while ((key = [enumerator nextObject]) !== nil)
|
||||
varString = [varString stringByAppendingFormat:@"&%@=%@", key, [aDictionary objectForKey:key]];
|
||||
|
||||
if (!_params)
|
||||
_params = [CPDictionary dictionary];
|
||||
|
||||
[_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 = [CPDictionary dictionary];
|
||||
|
||||
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 classid=%@ width=%@ height=%@>%@</object>", IEFlashCLSID, CGRectGetWidth([self bounds]), CGRectGetHeight([self bounds]), paramString];
|
||||
}
|
||||
#endif
|
||||
|
||||
- (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
|
||||
+17
-177
@@ -24,9 +24,6 @@
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@import "CPView.j"
|
||||
@import "CPFontDescriptor.j"
|
||||
@import "_CPObject+Theme.j"
|
||||
@import "CPControl.j"
|
||||
|
||||
CPFontDefaultSystemFontFace = @"Arial, sans-serif";
|
||||
CPFontDefaultSystemFontSize = 12;
|
||||
@@ -40,15 +37,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 +108,7 @@ following:
|
||||
<string>Asap</string>
|
||||
@endcode
|
||||
*/
|
||||
@implementation CPFont : CPObject <CPTheme>
|
||||
@implementation CPFont : CPObject
|
||||
{
|
||||
CPString _name;
|
||||
float _size;
|
||||
@@ -128,22 +122,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 +133,7 @@ following:
|
||||
systemFontFace = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontFace"];
|
||||
|
||||
if (systemFontFace)
|
||||
{
|
||||
_CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
|
||||
_CPFontSystemFontFaceSpecified = YES;
|
||||
}
|
||||
|
||||
var systemFontSize = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontSize"];
|
||||
|
||||
@@ -166,62 +141,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 setCSSResourcesPathInString:systemFontStyle];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -242,7 +162,7 @@ following:
|
||||
if (normalizedFaces === _CPFontSystemFontFace)
|
||||
return;
|
||||
|
||||
[self _invalidateSystemFontCache];
|
||||
[self _invalidateSystemFontCache]
|
||||
_CPFontSystemFontFace = aFace;
|
||||
}
|
||||
|
||||
@@ -254,19 +174,18 @@ following:
|
||||
return _CPFontSystemFontSize;
|
||||
}
|
||||
|
||||
+ (CPFont)systemFontForControlSize:(CPControlSize)aSize
|
||||
+ (float)systemFontSizeForControlSize:(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 _CPFontSystemFontSize - 1;
|
||||
case CPMiniControlSize:
|
||||
return [self systemFontOfSize:_CPFontSystemFontSizeMini];
|
||||
|
||||
return _CPFontSystemFontSize - 2;
|
||||
case CPRegularControlSize:
|
||||
default:
|
||||
return [self systemFontOfSize:_CPFontSystemFontSize];
|
||||
return _CPFontSystemFontSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +206,7 @@ following:
|
||||
var systemSize = String(_CPFontSystemFontSize),
|
||||
currentSize = String(CPFontCurrentSystemSize);
|
||||
|
||||
for (var key in _CPSystemFontCache)
|
||||
for (key in _CPSystemFontCache)
|
||||
{
|
||||
if (_CPSystemFontCache.hasOwnProperty(key) &&
|
||||
(key.indexOf(systemSize) === 0 || key.indexOf(currentSize) === 0))
|
||||
@@ -362,7 +281,7 @@ following:
|
||||
that dynamically tracks the current system font size.
|
||||
@return the requested system font
|
||||
*/
|
||||
+ (CPFont)systemFontOfSize:(CGSize)aSize
|
||||
+ (CPFont)systemFontOfSize:(CPSize)aSize
|
||||
{
|
||||
return _CPSystemFont(aSize === 0 ? _CPFontSystemFontSize : aSize, NO);
|
||||
}
|
||||
@@ -374,7 +293,7 @@ following:
|
||||
that dynamically tracks the current system font size.
|
||||
@return the requested bold system font
|
||||
*/
|
||||
+ (CPFont)boldSystemFontOfSize:(CGSize)aSize
|
||||
+ (CPFont)boldSystemFontOfSize:(CPSize)aSize
|
||||
{
|
||||
return _CPSystemFont(aSize === 0 ? _CPFontSystemFontSize : aSize, YES);
|
||||
}
|
||||
@@ -393,10 +312,6 @@ following:
|
||||
_isItalic = isItalic;
|
||||
_isSystem = isSystem;
|
||||
|
||||
_theme = [CPTheme defaultTheme];
|
||||
_themeState = CPThemeStateNormal;
|
||||
[self _loadThemeAttributes];
|
||||
|
||||
if (isSystem)
|
||||
{
|
||||
_name = aName;
|
||||
@@ -464,22 +379,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,61 +429,8 @@ 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)
|
||||
|
||||
- (id)_initWithFontDescriptor:(CPFontDescriptor)fontDescriptor
|
||||
{
|
||||
var aName = [fontDescriptor objectForKey: CPFontNameAttribute] ,
|
||||
aSize = [fontDescriptor pointSize],
|
||||
isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait,
|
||||
isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait;
|
||||
|
||||
return [self _initWithName:aName size:aSize bold:isBold italic:isItalic system:NO];
|
||||
}
|
||||
|
||||
+ (CPFont)fontWithDescriptor:(CPFontDescriptor)fontDescriptor size:(float)aSize
|
||||
{
|
||||
var aName = [fontDescriptor objectForKey: CPFontNameAttribute],
|
||||
isBold = [fontDescriptor symbolicTraits] & CPFontBoldTrait,
|
||||
isItalic = [fontDescriptor symbolicTraits] & CPFontItalicTrait;
|
||||
|
||||
return [self _fontWithName:aName size:aSize || [fontDescriptor pointSize] bold:isBold italic:isItalic];
|
||||
}
|
||||
|
||||
- (CPFontDescriptor)fontDescriptor
|
||||
{
|
||||
var traits = 0;
|
||||
|
||||
if ([self isBold])
|
||||
traits |= CPFontBoldTrait;
|
||||
|
||||
if ([self isItalic])
|
||||
traits |= CPFontItalicTrait;
|
||||
|
||||
return [[CPFontDescriptor fontDescriptorWithName:_name size:_size] fontDescriptorWithSymbolicTraits:traits];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPFontNameKey = @"CPFontNameKey",
|
||||
CPFontSizeKey = @"CPFontSizeKey",
|
||||
CPFontIsBoldKey = @"CPFontIsBoldKey",
|
||||
@@ -606,11 +452,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 +466,6 @@ var CPFontNameKey = @"CPFontNameKey",
|
||||
[aCoder encodeBool:_isBold forKey:CPFontIsBoldKey];
|
||||
[aCoder encodeBool:_isItalic forKey:CPFontIsItalicKey];
|
||||
[aCoder encodeBool:_isSystem forKey:CPFontIsSystemKey];
|
||||
|
||||
[self _encodeThemeObjectsWithCoder:aCoder];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+39
-229
@@ -22,14 +22,7 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPFontDescriptor.j"
|
||||
|
||||
@global CPApp
|
||||
@class CPFontPanel
|
||||
|
||||
@global document
|
||||
|
||||
CPItalicFontMask = 1 << 0;
|
||||
CPBoldFontMask = 1 << 1;
|
||||
@@ -46,20 +39,7 @@ CPUnitalicFontMask = 1 << 24;
|
||||
|
||||
|
||||
var CPSharedFontManager = nil,
|
||||
CPFontManagerFactory = nil,
|
||||
CPFontPanelFactory = nil;
|
||||
|
||||
/*
|
||||
modifyFont: sender's tag
|
||||
*/
|
||||
CPNoFontChangeAction = 0;
|
||||
CPViaPanelFontAction = 1;
|
||||
CPAddTraitFontAction = 2;
|
||||
CPSizeUpFontAction = 3;
|
||||
CPSizeDownFontAction = 4;
|
||||
CPHeavierFontAction = 5;
|
||||
CPLighterFontAction = 6;
|
||||
CPRemoveTraitFontAction = 7;
|
||||
CPFontManagerFactory = Nil;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -74,11 +54,9 @@ CPRemoveTraitFontAction = 7;
|
||||
id _delegate @accessors(property=delegate);
|
||||
|
||||
CPFont _selectedFont;
|
||||
BOOL _multiple @accessors(getter=isMultiple, setter=setMultiple:);
|
||||
BOOL _multiple @accessors;
|
||||
|
||||
CPDictionary _activeChange;
|
||||
|
||||
unsigned _fontAction;
|
||||
}
|
||||
|
||||
// Getting the Shared Font Manager
|
||||
@@ -103,15 +81,6 @@ CPRemoveTraitFontAction = 7;
|
||||
{
|
||||
CPFontManagerFactory = aClass;
|
||||
}
|
||||
/*!
|
||||
Sets the class that will be used to create the application's
|
||||
Font panel.
|
||||
*/
|
||||
+ (void)setFontPanelFactory:(Class)aClass
|
||||
{
|
||||
CPFontPanelFactory = aClass;
|
||||
}
|
||||
|
||||
|
||||
- (id)init
|
||||
{
|
||||
@@ -130,9 +99,6 @@ CPRemoveTraitFontAction = 7;
|
||||
{
|
||||
if (!_availableFonts)
|
||||
{
|
||||
_availableFonts = [];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_CPFontDetectSpan = document.createElement("span");
|
||||
_CPFontDetectSpan.fontSize = "24px";
|
||||
_CPFontDetectSpan.appendChild(document.createTextNode("mmmmmmmmmml"));
|
||||
@@ -144,16 +110,13 @@ CPRemoveTraitFontAction = 7;
|
||||
|
||||
_CPFontDetectReferenceFonts = _CPFontDetectPickTwoDifferentFonts(["monospace", "serif", "sans-serif", "cursive"]);
|
||||
|
||||
_availableFonts = [];
|
||||
for (var i = 0; i < _CPFontDetectAllFonts.length; i++)
|
||||
{
|
||||
var available = _CPFontDetectFontAvailable(_CPFontDetectAllFonts[i]);
|
||||
if (available)
|
||||
_availableFonts.push(_CPFontDetectAllFonts[i]);
|
||||
}
|
||||
#else
|
||||
// If there's no font detection, just assume all fonts are available.
|
||||
_availableFonts = _CPFontDetectAllFonts;
|
||||
#endif
|
||||
}
|
||||
return _availableFonts;
|
||||
}
|
||||
@@ -170,7 +133,7 @@ CPRemoveTraitFontAction = 7;
|
||||
- (void)setSelectedFont:(CPFont)aFont isMultiple:(BOOL)aFlag
|
||||
{
|
||||
_selectedFont = aFont;
|
||||
_multiple = aFlag;
|
||||
_isMultiple = aFlag;
|
||||
|
||||
// TODO Notify CPFontPanel when it exists.
|
||||
}
|
||||
@@ -180,6 +143,11 @@ CPRemoveTraitFontAction = 7;
|
||||
return _selectedFont;
|
||||
}
|
||||
|
||||
- (BOOL)isMultiple
|
||||
{
|
||||
return _isMultiple;
|
||||
}
|
||||
|
||||
- (int)weightOfFont:(CPFont)aFont
|
||||
{
|
||||
// TODO Weight 5 is a normal of book weight and 9 and above is bold, but it would be nice to be more
|
||||
@@ -192,6 +160,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,13 +205,7 @@ CPRemoveTraitFontAction = 7;
|
||||
|
||||
- (@action)addFontTrait:(id)sender
|
||||
{
|
||||
var tag = sender;
|
||||
|
||||
if ([sender respondsToSelector:@selector(tag)])
|
||||
tag = [sender tag];
|
||||
|
||||
_activeChange = tag == nil ? @{} : @{ @"addTraits": tag };
|
||||
_fontAction = CPAddTraitFontAction;
|
||||
_activeChange = [CPDictionary dictionaryWithObject:[sender tag] forKey:@"addTraits"];
|
||||
|
||||
[self sendAction];
|
||||
}
|
||||
@@ -224,187 +215,6 @@ CPRemoveTraitFontAction = 7;
|
||||
return [CPApp sendAction:_action to:_target from:self];
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
This method open the font panel, create it if necessary.
|
||||
@param sender The object that sent the message.
|
||||
*/
|
||||
- (CPFontPanel)fontPanel:(BOOL)createIt
|
||||
{
|
||||
var panel = nil,
|
||||
panelExists = [CPFontPanelFactory sharedFontPanelExists];
|
||||
|
||||
if ((panelExists) || (!panelExists && createIt))
|
||||
panel = [CPFontPanelFactory sharedFontPanel];
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Convert a font to have the specified Font traits. The font is unchanged expect for the specified Font traits.
|
||||
Using CPUnboldFontMask or CPUnitalicFontMask will respectively remove Bold and Italic traits.
|
||||
@param aFont The font to convert.
|
||||
@param fontTrait The new font traits mask.
|
||||
@result The converted font or \c aFont if the conversion failed.
|
||||
*/
|
||||
- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)fontTrait
|
||||
{
|
||||
var attributes = [[[aFont fontDescriptor] fontAttributes] copy],
|
||||
symbolicTrait = [[aFont fontDescriptor] symbolicTraits];
|
||||
|
||||
if (fontTrait & CPBoldFontMask)
|
||||
symbolicTrait |= CPFontBoldTrait;
|
||||
|
||||
if (fontTrait & CPItalicFontMask)
|
||||
symbolicTrait |= CPFontItalicTrait;
|
||||
|
||||
if (fontTrait & CPUnboldFontMask) /* FIXME: this only change CPFontSymbolicTrait what about CPFontWeightTrait */
|
||||
symbolicTrait &= ~CPFontBoldTrait;
|
||||
|
||||
if (fontTrait & CPUnitalicFontMask)
|
||||
symbolicTrait &= ~CPFontItalicTrait;
|
||||
|
||||
if (fontTrait & CPExpandedFontMask)
|
||||
symbolicTrait |= CPFontExpandedTrait;
|
||||
|
||||
if (fontTrait & CPSmallCapsFontMask)
|
||||
symbolicTrait |= CPFontSmallCapsTrait;
|
||||
|
||||
if (![attributes containsKey:CPFontTraitsAttribute])
|
||||
[attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
|
||||
forKey:CPFontSymbolicTrait]
|
||||
forKey:CPFontTraitsAttribute];
|
||||
else
|
||||
[[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
|
||||
forKey:CPFontSymbolicTrait];
|
||||
|
||||
return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Convert a font to not have the specified Font traits. The font is unchanged expect for the specified Font traits.
|
||||
@param aFont The font to convert.
|
||||
@param fontTrait The font traits mask to remove.
|
||||
@result The converted font or \c aFont if the conversion failed.
|
||||
*/
|
||||
- (CPFont)convertFont:(CPFont)aFont toNotHaveTrait:(CPFontTraitMask)fontTrait
|
||||
{
|
||||
var attributes = [[[aFont fontDescriptor] fontAttributes] copy],
|
||||
symbolicTrait = [[aFont fontDescriptor] symbolicTraits];
|
||||
|
||||
if ((fontTrait & CPBoldFontMask) || (fontTrait & CPUnboldFontMask)) /* FIXME: see convertFont:toHaveTrait: about CPFontWeightTrait */
|
||||
symbolicTrait &= ~CPFontBoldTrait;
|
||||
|
||||
if ((fontTrait & CPItalicFontMask) || (fontTrait & CPUnitalicFontMask))
|
||||
symbolicTrait &= ~CPFontItalicTrait;
|
||||
|
||||
if (fontTrait & CPExpandedFontMask)
|
||||
symbolicTrait &= ~CPFontExpandedTrait;
|
||||
|
||||
if (fontTrait & CPSmallCapsFontMask)
|
||||
symbolicTrait &= ~CPFontSmallCapsTrait;
|
||||
|
||||
if (![attributes containsKey:CPFontTraitsAttribute])
|
||||
[attributes setObject:[CPDictionary dictionaryWithObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
|
||||
forKey:CPFontSymbolicTrait]
|
||||
forKey:CPFontTraitsAttribute];
|
||||
else
|
||||
[[attributes objectForKey:CPFontTraitsAttribute] setObject:[CPNumber numberWithUnsignedInt:symbolicTrait]
|
||||
forKey:CPFontSymbolicTrait];
|
||||
|
||||
return [[aFont class] fontWithDescriptor:[CPFontDescriptor fontDescriptorWithFontAttributes:attributes] size:0.0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Convert a font to have specified size. The font is unchanged expect for the specified size.
|
||||
@param aFont The font to convert.
|
||||
@param aSize The new font size.
|
||||
@result The converted font or \c aFont if the conversion failed.
|
||||
*/
|
||||
- (CPFont)convertFont:(CPFont)aFont toSize:(float)aSize
|
||||
{
|
||||
var descriptor = [aFont fontDescriptor];
|
||||
|
||||
return [[aFont class] fontWithDescriptor: descriptor size:aSize]
|
||||
}
|
||||
|
||||
- (void)orderFrontFontPanel:(id)sender
|
||||
{
|
||||
[[self fontPanel:YES] orderFront:sender];
|
||||
}
|
||||
|
||||
- (void)modifyFont:(id)sender
|
||||
{
|
||||
_fontAction = [sender tag];
|
||||
[self sendAction];
|
||||
|
||||
if (_selectedFont)
|
||||
[self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
This method causes the receiver to send its action message.
|
||||
@param sender The object that sent the message. (a Font panel)
|
||||
*/
|
||||
- (void)modifyFontViaPanel:(id)sender
|
||||
{
|
||||
_fontAction = CPViaPanelFontAction;
|
||||
if (_selectedFont)
|
||||
[self setSelectedFont:[self convertFont:_selectedFont] isMultiple:NO];
|
||||
|
||||
[self sendAction];
|
||||
}
|
||||
|
||||
/*!
|
||||
Convert a font according to current font changes, provided by the object that initiated the font change.
|
||||
@param aFont The font to convert.
|
||||
@result The converted font or \c aFont if the conversion failed.
|
||||
*/
|
||||
- (CPFont)convertFont:(CPFont)aFont
|
||||
{
|
||||
var newFont = nil;
|
||||
|
||||
switch (_fontAction)
|
||||
{
|
||||
case CPNoFontChangeAction:
|
||||
newFont = aFont;
|
||||
break;
|
||||
|
||||
case CPViaPanelFontAction:
|
||||
newFont = [[self fontPanel:NO] panelConvertFont:aFont];
|
||||
break;
|
||||
|
||||
case CPAddTraitFontAction:
|
||||
newFont = aFont;
|
||||
|
||||
if (!_activeChange)
|
||||
break;
|
||||
|
||||
var addTraits = [_activeChange valueForKey:@"addTraits"];
|
||||
|
||||
if (addTraits)
|
||||
newFont = [self convertFont:aFont toHaveTrait:addTraits];
|
||||
break;
|
||||
|
||||
case CPSizeUpFontAction:
|
||||
newFont = [self convertFont:aFont toSize:[aFont size] + 1.0]; /* any limit ? */
|
||||
break;
|
||||
|
||||
case CPSizeDownFontAction:
|
||||
if ([aFont size] > 1)
|
||||
newFont = [self convertFont:aFont toSize:[aFont size] - 1.0];
|
||||
/* else CPBeep() :-p */
|
||||
break;
|
||||
|
||||
default:
|
||||
CPLog.trace(@"-[" + [self className] + " " + _cmd + "] unsupported font action: " + _fontAction + " aFont unchanged");
|
||||
newFont = aFont;
|
||||
break;
|
||||
}
|
||||
|
||||
return newFont;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPFontDetectSpan,
|
||||
|
||||
+13
-59
@@ -20,16 +20,6 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPException.j>
|
||||
@import <Foundation/CPMutableArray.j>
|
||||
|
||||
@import "CGColor.j"
|
||||
@import "CGColorSpace.j"
|
||||
@import "CGGradient.j"
|
||||
@import "CPBezierPath.j"
|
||||
@import "CPGraphicsContext.j"
|
||||
|
||||
CPGradientDrawsBeforeStartingLocation = kCGGradientDrawsBeforeStartLocation;
|
||||
CPGradientDrawsAfterEndingLocation = kCGGradientDrawsAfterEndLocation;
|
||||
|
||||
@@ -41,11 +31,6 @@ CPGradientDrawsAfterEndingLocation = kCGGradientDrawsAfterEndLocation;
|
||||
CGGradient _gradient;
|
||||
}
|
||||
|
||||
- (id)initWithStartingColor:(CPColor)startingColor endingColor:(CPColor)endingColor
|
||||
{
|
||||
return [self initWithColors:[startingColor, endingColor]];
|
||||
}
|
||||
|
||||
- (id)initWithColors:(CPArray)someColors
|
||||
{
|
||||
var count = [someColors count];
|
||||
@@ -70,12 +55,11 @@ CPGradientDrawsAfterEndingLocation = kCGGradientDrawsAfterEndLocation;
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
var colorSpace = [aColorSpace CGColorSpace] || CGColorSpaceCreateDeviceRGB,
|
||||
cgColors = [someColors arrayByApplyingBlock:function(color)
|
||||
{
|
||||
return CGColorCreate(colorSpace, [color components])
|
||||
}];
|
||||
|
||||
var cgColors = [],
|
||||
count = [someColors count],
|
||||
colorSpace = [aColorSpace CGColorSpace] || CGColorSpaceCreateDeviceRGB;
|
||||
for (var i = 0; i < count; i++)
|
||||
cgColors.push(CGColorCreate(colorSpace, [someColors[i] components]));
|
||||
_gradient = CGGradientCreateWithColors(colorSpace, cgColors, someLocations);
|
||||
}
|
||||
|
||||
@@ -90,32 +74,20 @@ CPGradientDrawsAfterEndingLocation = kCGGradientDrawsAfterEndLocation;
|
||||
CGContextClipToRect(ctx, rect);
|
||||
CGContextAddRect(ctx, rect);
|
||||
|
||||
[self _drawInRect:rect atAngle:angle];
|
||||
|
||||
CGContextRestoreGState(ctx);
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore like draw in rect but apply no clipping.
|
||||
*/
|
||||
- (void)_drawInRect:(CGRect)rect atAngle:(float)angle
|
||||
{
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
|
||||
startPoint,
|
||||
var startPoint,
|
||||
endPoint;
|
||||
|
||||
// Modulo of negative values doesn't work as expected in JS.
|
||||
angle = ((angle % 360.0) + 360.0) % 360.0;
|
||||
|
||||
if (angle < 90.0)
|
||||
startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
|
||||
startPoint = _CGPointMake(_CGRectGetMinX(rect), _CGRectGetMinY(rect));
|
||||
else if (angle < 180.0)
|
||||
startPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
|
||||
startPoint = _CGPointMake(_CGRectGetMaxX(rect), _CGRectGetMinY(rect));
|
||||
else if (angle < 270.0)
|
||||
startPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect));
|
||||
startPoint = _CGPointMake(_CGRectGetMaxX(rect), _CGRectGetMaxY(rect));
|
||||
else
|
||||
startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect));
|
||||
startPoint = _CGPointMake(_CGRectGetMinX(rect), _CGRectGetMaxY(rect));
|
||||
|
||||
// A line segment comes out of the starting point at the given angle, with the first colour
|
||||
// at the starting point and the last at the end. To do what drawInRect: is supposed to do
|
||||
@@ -130,31 +102,13 @@ CPGradientDrawsAfterEndingLocation = kCGGradientDrawsAfterEndLocation;
|
||||
// This simplifies down to (in the first quadrant) rectWidth * cos(a) + rectHeight * sin(a).
|
||||
|
||||
var radians = PI * angle / 180.0,
|
||||
length = ABS(CGRectGetWidth(rect) * COS(radians)) + ABS(CGRectGetHeight(rect) * SIN(radians));
|
||||
length = ABS(_CGRectGetWidth(rect) * COS(radians)) + ABS(_CGRectGetHeight(rect) * SIN(radians));
|
||||
|
||||
endPoint = CGPointMake(startPoint.x + length * COS(radians),
|
||||
endPoint = _CGPointMake(startPoint.x + length * COS(radians),
|
||||
startPoint.y + length * SIN(radians));
|
||||
|
||||
[self drawFromPoint:startPoint toPoint:endPoint options:CPGradientDrawsBeforeStartingLocation | CPGradientDrawsAfterEndingLocation];
|
||||
}
|
||||
|
||||
- (void)drawInBezierPath:(CPBezierPath)aPath angle:(float)anAngle
|
||||
{
|
||||
[CPGraphicsContext saveGraphicsState];
|
||||
|
||||
var ctx = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
// Nail down the path which CGContextDrawLinearGradient will cause to be filled.
|
||||
// Note we don't do this by clipping to the path and then calling drawInRect:atAngle:
|
||||
// as this would cut off any antialias of the drawing, plus any active context shadow.
|
||||
CGContextBeginPath(ctx);
|
||||
CGContextAddPath(ctx, aPath._path);
|
||||
CGContextSetLineWidth(ctx, [aPath lineWidth]);
|
||||
CGContextClosePath(ctx);
|
||||
|
||||
[self _drawInRect:[aPath bounds] atAngle:anAngle];
|
||||
|
||||
[CPGraphicsContext restoreGraphicsState];
|
||||
CGContextRestoreGState(ctx);
|
||||
}
|
||||
|
||||
- (void)drawFromPoint:(NSPoint)startingPoint toPoint:(NSPoint)endingPoint options:(NSGradientDrawingOptions)options
|
||||
|
||||
+16
-16
@@ -43,10 +43,10 @@ function CPDrawTiledRects(
|
||||
if (sides.length != grays.length)
|
||||
[CPException raise:CPInvalidArgumentException reason:@"sides (length: " + sides.length + ") and grays (length: " + grays.length + ") must have the same length."];
|
||||
|
||||
var colors = [grays arrayByApplyingBlock:function(gray)
|
||||
{
|
||||
return [CPColor colorWithCalibratedWhite:gray alpha:1.0];
|
||||
}];
|
||||
var colors = [];
|
||||
|
||||
for (var i = 0; i < grays.length; ++i)
|
||||
colors.push([CPColor colorWithCalibratedWhite:grays[i] alpha:1.0]);
|
||||
|
||||
return CPDrawColorTiledRects(boundsRect, clipRect, sides, colors);
|
||||
}
|
||||
@@ -60,9 +60,9 @@ function CPDrawColorTiledRects(
|
||||
if (sides.length != colors.length)
|
||||
[CPException raise:CPInvalidArgumentException reason:@"sides (length: " + sides.length + ") and colors (length: " + colors.length + ") must have the same length."];
|
||||
|
||||
var resultRect = CGRectMakeCopy(boundsRect),
|
||||
slice = CGRectMakeZero(),
|
||||
remainder = CGRectMakeZero(),
|
||||
var resultRect = _CGRectMakeCopy(boundsRect),
|
||||
slice = _CGRectMakeZero(),
|
||||
remainder = _CGRectMakeZero(),
|
||||
context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
|
||||
CGContextSaveGState(context);
|
||||
@@ -77,7 +77,7 @@ function CPDrawColorTiledRects(
|
||||
slice = CGRectIntersection(slice, clipRect);
|
||||
|
||||
// Cocoa docs say that only slices that are within the clipRect are actually drawn
|
||||
if (CGRectIsEmpty(slice))
|
||||
if (_CGRectIsEmpty(slice))
|
||||
continue;
|
||||
|
||||
var minX,
|
||||
@@ -88,23 +88,23 @@ function CPDrawColorTiledRects(
|
||||
if (side == CPMinXEdge || side == CPMaxXEdge)
|
||||
{
|
||||
// Make sure we have at least 1 pixel to draw a line
|
||||
if (CGRectGetWidth(slice) < 1.0)
|
||||
if (_CGRectGetWidth(slice) < 1.0)
|
||||
continue;
|
||||
|
||||
minX = CGRectGetMinX(slice) + 0.5;
|
||||
minX = _CGRectGetMinX(slice) + 0.5;
|
||||
maxX = minX;
|
||||
minY = CGRectGetMinY(slice);
|
||||
maxY = CGRectGetMaxY(slice);
|
||||
minY = _CGRectGetMinY(slice);
|
||||
maxY = _CGRectGetMaxY(slice);
|
||||
}
|
||||
else // CPMinYEdge || CPMaxYEdge
|
||||
{
|
||||
// Make sure we have at least 1 pixel to draw a line
|
||||
if (CGRectGetHeight(slice) < 1.0)
|
||||
if (_CGRectGetHeight(slice) < 1.0)
|
||||
continue;
|
||||
|
||||
minX = CGRectGetMinX(slice);
|
||||
maxX = CGRectGetMaxX(slice);
|
||||
minY = CGRectGetMinY(slice) + 0.5;
|
||||
minX = _CGRectGetMinX(slice);
|
||||
maxX = _CGRectGetMaxX(slice);
|
||||
minY = _CGRectGetMinY(slice) + 0.5;
|
||||
maxY = minY;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ var CPGraphicsContextCurrent = nil,
|
||||
+ (void)restoreGraphicsState
|
||||
{
|
||||
var lastContext = [CPGraphicsContextThreadStack lastObject];
|
||||
|
||||
if (lastContext)
|
||||
{
|
||||
[lastContext restoreGraphicsState];
|
||||
@@ -92,7 +91,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];
|
||||
|
||||
|
||||
+23
-563
@@ -21,33 +21,12 @@
|
||||
*/
|
||||
|
||||
@import <Foundation/CPBundle.j>
|
||||
@import <Foundation/CPGeometry.j>
|
||||
@import <Foundation/CPNotificationCenter.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
@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>
|
||||
|
||||
@optional
|
||||
- (void)imageDidLoad:(CPImage)anImage;
|
||||
- (void)imageDidError:(CPImage)anImage;
|
||||
- (void)imageDidAbort:(CPImage)anImage;
|
||||
|
||||
@end
|
||||
|
||||
var CPImageDelegate_imageDidLoad_ = 1 << 1,
|
||||
CPImageDelegate_imageDidError_ = 1 << 2,
|
||||
CPImageDelegate_imageDidAbort_ = 1 << 3;
|
||||
|
||||
CPImageLoadStatusInitialized = 0;
|
||||
CPImageLoadStatusLoading = 1;
|
||||
@@ -70,42 +49,15 @@ var imagesForNames = { },
|
||||
AppKitImageForNames[CPImageNameColorPanel] = CGSizeMake(26.0, 29.0);
|
||||
AppKitImageForNames[CPImageNameColorPanelHighlighted] = CGSizeMake(26.0, 29.0);
|
||||
|
||||
/*!
|
||||
Returns a resource image with a relative path and size
|
||||
in a bundle.
|
||||
|
||||
@param filename A filename or relative path to a resource image.
|
||||
@param width Width of the image. May be omitted.
|
||||
@param height Height of the image. May be omitted if width is omitted.
|
||||
@param size Instead of passing width/height, a CGSize may be passed.
|
||||
@param bundle Bundle in which the image resource can be found.
|
||||
If omitted, defaults to the main bundle.
|
||||
@return CPImage
|
||||
*/
|
||||
function CPImageInBundle()
|
||||
function CPImageInBundle(aFilename, aSize, aBundle)
|
||||
{
|
||||
var filename = arguments[0],
|
||||
size = nil,
|
||||
bundle = nil;
|
||||
if (!aBundle)
|
||||
aBundle = [CPBundle mainBundle];
|
||||
|
||||
if (typeof(arguments[1]) === "number")
|
||||
{
|
||||
size = CGSizeMake(arguments[1], arguments[2]);
|
||||
bundle = arguments[3];
|
||||
}
|
||||
else if (typeof(arguments[1]) === "object")
|
||||
{
|
||||
size = arguments[1];
|
||||
bundle = arguments[2];
|
||||
}
|
||||
if (aSize)
|
||||
return [[CPImage alloc] initWithContentsOfFile:[aBundle pathForResource:aFilename] size:aSize];
|
||||
|
||||
if (!bundle)
|
||||
bundle = [CPBundle mainBundle];
|
||||
|
||||
if (size)
|
||||
return [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:filename] size:size];
|
||||
|
||||
return [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:filename]];
|
||||
return [[CPImage alloc] initWithContentsOfFile:[aBundle pathForResource:aFilename]];
|
||||
}
|
||||
|
||||
function CPAppKitImage(aFilename, aSize)
|
||||
@@ -136,15 +88,14 @@ function CPAppKitImage(aFilename, aSize)
|
||||
*/
|
||||
@implementation CPImage : CPObject
|
||||
{
|
||||
CGSize _size;
|
||||
CPString _filename;
|
||||
CPString _name;
|
||||
CGSize _size;
|
||||
CPString _filename;
|
||||
CPString _name;
|
||||
|
||||
id <CPImageDelegate> _delegate;
|
||||
unsigned _loadStatus;
|
||||
unsigned _implementedDelegateMethods;
|
||||
id _delegate;
|
||||
unsigned _loadStatus;
|
||||
|
||||
Image _image;
|
||||
Image _image;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
@@ -154,24 +105,19 @@ function CPAppKitImage(aFilename, aSize)
|
||||
|
||||
/*!
|
||||
Initializes the image, by associating it with a filename. The image
|
||||
denoted in \c aFilename is not actually loaded. It will be loaded
|
||||
once needed.
|
||||
|
||||
denoted in \c aFilename is not actually loaded. It will
|
||||
be loaded once needed.
|
||||
@param aFilename the file containing the image
|
||||
@param aSize the image's size
|
||||
@return the initialized image
|
||||
*/
|
||||
- (id)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize
|
||||
{
|
||||
// Quietly return nil like in Cocoa, rather than crashing later.
|
||||
if (aFilename == nil)
|
||||
return nil;
|
||||
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_size = CGSizeMakeCopy(aSize);
|
||||
_size = CPSizeCreateCopy(aSize);
|
||||
_filename = aFilename;
|
||||
_loadStatus = CPImageLoadStatusInitialized;
|
||||
}
|
||||
@@ -249,7 +195,7 @@ function CPAppKitImage(aFilename, aSize)
|
||||
var canvas = document.createElement("canvas"),
|
||||
ctx = canvas.getContext("2d");
|
||||
|
||||
canvas.width = _image.width;
|
||||
canvas.width = _image.width,
|
||||
canvas.height = _image.height;
|
||||
|
||||
ctx.drawImage(_image, 0, 0);
|
||||
@@ -278,7 +224,7 @@ function CPAppKitImage(aFilename, aSize)
|
||||
*/
|
||||
- (CGSize)size
|
||||
{
|
||||
return CGSizeMakeCopy(_size);
|
||||
return _size;
|
||||
}
|
||||
|
||||
+ (id)imageNamed:(CPString)aName
|
||||
@@ -325,34 +271,13 @@ function CPAppKitImage(aFilename, aSize)
|
||||
return _name;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the underlying Image element for a single image.
|
||||
*/
|
||||
- (Image)image
|
||||
{
|
||||
return _image;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's delegate.
|
||||
@param the delegate
|
||||
*/
|
||||
- (void)setDelegate:(id <CPImageDelegate>)aDelegate
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate === aDelegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidLoad:)])
|
||||
_implementedDelegateMethods |= CPImageDelegate_imageDidLoad_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidError:)])
|
||||
_implementedDelegateMethods |= CPImageDelegate_imageDidError_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidAbort:)])
|
||||
_implementedDelegateMethods |= CPImageDelegate_imageDidAbort_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -434,11 +359,6 @@ function CPAppKitImage(aFilename, aSize)
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)isSingleImage
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return NO;
|
||||
@@ -449,11 +369,6 @@ function CPAppKitImage(aFilename, aSize)
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isMaterialIconImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
var filename = [self filename],
|
||||
@@ -493,7 +408,7 @@ function CPAppKitImage(aFilename, aSize)
|
||||
postNotificationName:CPImageDidLoadNotification
|
||||
object:self];
|
||||
|
||||
if (_implementedDelegateMethods & CPImageDelegate_imageDidLoad_)
|
||||
if ([_delegate respondsToSelector:@selector(imageDidLoad:)])
|
||||
[_delegate imageDidLoad:self];
|
||||
}
|
||||
|
||||
@@ -502,7 +417,7 @@ function CPAppKitImage(aFilename, aSize)
|
||||
{
|
||||
_loadStatus = CPImageLoadStatusReadError;
|
||||
|
||||
if (_implementedDelegateMethods & CPImageDelegate_imageDidError_)
|
||||
if ([_delegate respondsToSelector:@selector(imageDidError:)])
|
||||
[_delegate imageDidError:self];
|
||||
}
|
||||
|
||||
@@ -511,235 +426,12 @@ function CPAppKitImage(aFilename, aSize)
|
||||
{
|
||||
_loadStatus = CPImageLoadStatusCancelled;
|
||||
|
||||
if (_implementedDelegateMethods & CPImageDelegate_imageDidAbort_)
|
||||
if ([_delegate respondsToSelector:@selector(imageDidAbort:)])
|
||||
[_delegate imageDidAbort:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
// 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
|
||||
// support this new kind of CPColor/CPImage. See CPImageView, CPView and _CPImageAndTextView.
|
||||
//
|
||||
// To create a CPImage that uses CSS, simply use the new class method :
|
||||
// + (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary size:(CGSize)aSize
|
||||
// where beforeDictionary & afterDictionary are related to ::before & ::after pseudo-elements.
|
||||
// If you don't need them, just use the simplified class method :
|
||||
// + (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary size:(CGSize)aSize
|
||||
//
|
||||
// Examples :
|
||||
// regularImageNormal = [CPImage imageWithCSSDictionary:@{
|
||||
// @"border-color": A3ColorActiveBorder,
|
||||
// @"border-style": @"solid",
|
||||
// @"border-width": @"1px",
|
||||
// @"border-radius": @"50%",
|
||||
// @"box-sizing": @"border-box",
|
||||
// @"background-color": A3ColorBackgroundWhite,
|
||||
// @"transition-duration": @"0.35s",
|
||||
// @"transition-property": @"all",
|
||||
// @"transition-timing-function": @"ease"
|
||||
// }
|
||||
// size:CGSizeMake(16,16)];
|
||||
//
|
||||
// imageSearch = [CPImage imageWithCSSDictionary:@{
|
||||
// @"background-image": @"url(%%packed.png)",
|
||||
// @"background-position": @"-16px -32px",
|
||||
// @"background-repeat": @"no-repeat",
|
||||
// @"background-size": @"100px 400px"
|
||||
// }
|
||||
// size:CGSizeMake(16,16)];
|
||||
//
|
||||
// Remark : Please note the special URL of the background image used in this example : url(%%packed.png)
|
||||
// During theme loading, "%%" will be replaced by the path to the theme blend resources folder.
|
||||
// Typically, a CSS theme will use some (rare) images all packed together in a single image resource (see packed.png in Aristo3 theme)
|
||||
//
|
||||
// Also, please note that if you don't use one of the CSS components, you can either set it to nil (best solution) or to an empty dictionary, like :
|
||||
// aCssImage = [CPImage imageWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:@{ ... }];
|
||||
//
|
||||
// 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 : -(DOMElement)applyCSSImageForView is meant to be used by low level UI widgets (like CPImageView and _CPImageAndTextView) to implement CSS theme support.
|
||||
//
|
||||
// In some circumstances, you may have to clear a CSS image. You can do this easily by replacing your current image with the special dummy empty CSS image :
|
||||
// [CPImage dummyCSSImageOfSize:CGSizeMake(someWidth, someHeight)]
|
||||
|
||||
@implementation CPImage (CSSTheming)
|
||||
{
|
||||
CPDictionary _cssDictionary @accessors(property=cssDictionary);
|
||||
CPDictionary _cssBeforeDictionary @accessors(property=cssBeforeDictionary);
|
||||
CPDictionary _cssAfterDictionary @accessors(property=cssAfterDictionary);
|
||||
CGSize _displaySize;
|
||||
}
|
||||
|
||||
+ (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary size:(CGSize)aSize
|
||||
{
|
||||
return [[CPImage alloc] initWithCSSDictionary:aDictionary beforeDictionary:nil afterDictionary:nil size:aSize];
|
||||
}
|
||||
|
||||
+ (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary size:(CGSize)aSize
|
||||
{
|
||||
return [[CPImage alloc] initWithCSSDictionary:aDictionary beforeDictionary:beforeDictionary afterDictionary:afterDictionary size:aSize];
|
||||
}
|
||||
|
||||
+ (CPImage)dummyCSSImageOfSize:(CGSize)aSize
|
||||
{
|
||||
// This is used to clear a previous CSS image
|
||||
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];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_size = CGSizeMakeCopy(aSize);
|
||||
_filename = @"CSS image";
|
||||
_loadStatus = CPImageLoadStatusCompleted;
|
||||
_cssDictionary = aDictionary;
|
||||
_cssBeforeDictionary = beforeDictionary;
|
||||
_cssAfterDictionary = afterDictionary;
|
||||
_displaySize = CGSizeMakeCopy(aSize);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isCSSBased
|
||||
{
|
||||
return !!(_cssDictionary || _cssBeforeDictionary || _cssAfterDictionary);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSDictionary
|
||||
{
|
||||
return ([_cssDictionary count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSBeforeDictionary
|
||||
{
|
||||
return ([_cssBeforeDictionary count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL)hasCSSAfterDictionary
|
||||
{
|
||||
return ([_cssAfterDictionary count] > 0);
|
||||
}
|
||||
|
||||
- (DOMElement)applyCSSImageForView:(CPView)aView onDOMElement:(DOMElement)aDOMElement styleNode:(DOMElement)aStyleNode previousState:(CPArrayRef)aPreviousStateRef
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
// First, restore previous CSS styling before applying the new one
|
||||
|
||||
var aPreviousState = @deref(aPreviousStateRef);
|
||||
|
||||
for (var i = 0, count = aPreviousState.length; i < count; i++)
|
||||
aDOMElement.style[aPreviousState[i][0]] = aPreviousState[i][1];
|
||||
|
||||
aPreviousState = @[];
|
||||
|
||||
// Then apply new CSS styling
|
||||
|
||||
[_cssDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
[aPreviousState addObject:@[aKey, aDOMElement.style[aKey]]];
|
||||
aDOMElement.style[aKey] = anObject;
|
||||
}];
|
||||
|
||||
if (_cssBeforeDictionary || _cssAfterDictionary)
|
||||
{
|
||||
// We need to create a unique class name
|
||||
|
||||
var styleClassName = @".CP" + [aView UID],
|
||||
styleContent = @"";
|
||||
|
||||
if (_cssBeforeDictionary)
|
||||
{
|
||||
styleContent += styleClassName + @"::before { ";
|
||||
|
||||
[_cssBeforeDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
styleContent += aKey + ": " + anObject + "; ";
|
||||
}];
|
||||
|
||||
styleContent += "} ";
|
||||
}
|
||||
|
||||
if (_cssAfterDictionary)
|
||||
{
|
||||
styleContent += styleClassName + @"::after { ";
|
||||
|
||||
[_cssAfterDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
|
||||
{
|
||||
styleContent += aKey + ": " + anObject + "; ";
|
||||
}];
|
||||
|
||||
styleContent += "} ";
|
||||
}
|
||||
|
||||
var styleDescription = document.createTextNode(styleContent);
|
||||
|
||||
if (!aStyleNode)
|
||||
{
|
||||
aStyleNode = document.createElement("style");
|
||||
|
||||
aView._DOMElement.insertBefore(aStyleNode, aView._DOMElement.firstChild);
|
||||
|
||||
aStyleNode.appendChild(styleDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
aStyleNode.replaceChild(styleDescription, aStyleNode.firstChild);
|
||||
}
|
||||
|
||||
aDOMElement.className = @"CP"+[aView UID];
|
||||
}
|
||||
else
|
||||
{
|
||||
// no before/after so remove aStyleNode if existing
|
||||
|
||||
if (aStyleNode)
|
||||
{
|
||||
aView._DOMElement.removeChild(aStyleNode);
|
||||
aStyleNode = nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return actualised values
|
||||
|
||||
@deref(aPreviousStateRef) = aPreviousState;
|
||||
|
||||
return aStyleNode;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (BOOL)_shouldBeResized
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
|
||||
CPImageCSSBeforeDictionaryKey = @"CPImageCSSBeforeDictionaryKey",
|
||||
CPImageCSSAfterDictionaryKey = @"CPImageCSSAfterDictionaryKey";
|
||||
|
||||
// MARK: -
|
||||
|
||||
@implementation CPImage (CPCoding)
|
||||
|
||||
/*!
|
||||
@@ -749,10 +441,7 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if ([aCoder containsValueForKey:CPImageCSSDictionaryKey])
|
||||
return [self initWithCSSDictionary:[aCoder decodeObjectForKey:CPImageCSSDictionaryKey] beforeDictionary:[aCoder decodeObjectForKey:CPImageCSSBeforeDictionaryKey] afterDictionary:[aCoder decodeObjectForKey:CPImageCSSAfterDictionaryKey] size:[aCoder decodeSizeForKey:@"CPSize"]];
|
||||
else
|
||||
return [self initWithContentsOfFile:[aCoder decodeObjectForKey:@"CPFilename"] size:[aCoder decodeSizeForKey:@"CPSize"]];
|
||||
return [self initWithContentsOfFile:[aCoder decodeObjectForKey:@"CPFilename"] size:[aCoder decodeSizeForKey:@"CPSize"]];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -763,218 +452,10 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
|
||||
{
|
||||
[aCoder encodeObject:_filename forKey:@"CPFilename"];
|
||||
[aCoder encodeSize:_size forKey:@"CPSize"];
|
||||
|
||||
// CSS Styling
|
||||
if ([self isCSSBased])
|
||||
{
|
||||
[aCoder encodeObject:_cssDictionary forKey:CPImageCSSDictionaryKey];
|
||||
[aCoder encodeObject:_cssBeforeDictionary forKey:CPImageCSSBeforeDictionaryKey];
|
||||
[aCoder encodeObject:_cssAfterDictionary forKey:CPImageCSSAfterDictionaryKey];
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -1009,11 +490,6 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
|
||||
return _isVertical;
|
||||
}
|
||||
|
||||
- (BOOL)isSingleImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return YES;
|
||||
@@ -1078,11 +554,6 @@ var CPThreePartImageImageSlicesKey = @"CPThreePartImageImageSlicesKey",
|
||||
return _imageSlices;
|
||||
}
|
||||
|
||||
- (BOOL)isSingleImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return NO;
|
||||
@@ -1115,14 +586,3 @@ var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
@implementation CPImage (Duplication)
|
||||
|
||||
- (CPImage)duplicate
|
||||
{
|
||||
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:self]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+49
-122
@@ -26,12 +26,6 @@
|
||||
@import "CPImage.j"
|
||||
@import "CPShadowView.j"
|
||||
|
||||
@global CPImagesPboardType
|
||||
@global appkit_tag_dom_elements
|
||||
|
||||
@global document
|
||||
|
||||
@typedef CPImageAlignment
|
||||
CPImageAlignCenter = 0;
|
||||
CPImageAlignTop = 1;
|
||||
CPImageAlignTopLeft = 2;
|
||||
@@ -73,14 +67,12 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
CPImageViewEmptyPlaceholderImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"empty.png"]];
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (aBinding === CPValueBinding || aBinding === CPValueURLBinding || aBinding === CPValuePathBinding || aBinding === CPDataBinding)
|
||||
if (theBinding === CPValueBinding || theBinding === CPValueURLBinding || theBinding === CPValuePathBinding || theBinding === CPDataBinding)
|
||||
return [CPImageViewValueBinder class];
|
||||
else if ([aBinding hasPrefix:CPEditableBinding])
|
||||
return [CPMultipleValueAndBinding class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -90,59 +82,26 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
[self _createDOMImageElement];
|
||||
_DOMImageElement = document.createElement("img");
|
||||
_DOMImageElement.style.position = "absolute";
|
||||
_DOMImageElement.style.left = "0px";
|
||||
_DOMImageElement.style.top = "0px";
|
||||
|
||||
if ([CPPlatform supportsDragAndDrop])
|
||||
{
|
||||
_DOMImageElement.setAttribute("draggable", "true");
|
||||
_DOMImageElement.style["-khtml-user-drag"] = "element";
|
||||
}
|
||||
|
||||
CPDOMDisplayServerAppendChild(_DOMElement, _DOMImageElement);
|
||||
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_createDOMImageElement
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var image = [self objectValue],
|
||||
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)
|
||||
|
||||
if (_DOMImageElement)
|
||||
{
|
||||
if ((isIMGImageElement && isCSSBasedImage) || (!isIMGImageElement && !isCSSBasedImage))
|
||||
{
|
||||
// OK, destroy it
|
||||
|
||||
_DOMElement.removeChild(_DOMImageElement);
|
||||
|
||||
_DOMImageElement = nil;
|
||||
|
||||
// CSS styling cleaning
|
||||
_cssStylePreviousState = @[];
|
||||
_cssStyleNode = nil;
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
_DOMImageElement = document.createElement(isCSSBasedImage ? "div" : "img");
|
||||
_DOMImageElement.style.position = "absolute";
|
||||
_DOMImageElement.style.left = "0px";
|
||||
_DOMImageElement.style.top = "0px";
|
||||
|
||||
if ([CPPlatform supportsDragAndDrop])
|
||||
{
|
||||
_DOMImageElement.setAttribute("draggable", "true");
|
||||
_DOMImageElement.style["-khtml-user-drag"] = "element";
|
||||
}
|
||||
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
AppKitTagDOMElement(self, _DOMImageElement);
|
||||
|
||||
CPDOMDisplayServerAppendChild(_DOMElement, _DOMImageElement);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the view's image.
|
||||
*/
|
||||
@@ -174,15 +133,7 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
var newImage = [self objectValue];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
[self _createDOMImageElement];
|
||||
|
||||
if ([newImage isCSSBased])
|
||||
_cssStyleNode = [newImage applyCSSImageForView:self
|
||||
onDOMElement:_DOMImageElement
|
||||
styleNode:_cssStyleNode
|
||||
previousState:@ref(_cssStylePreviousState)];
|
||||
else
|
||||
_DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
|
||||
_DOMImageElement.src = newImage ? [newImage filename] : [CPImageViewEmptyPlaceholderImage filename];
|
||||
#endif
|
||||
|
||||
var size = [newImage size];
|
||||
@@ -208,7 +159,6 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
|
||||
- (void)imageDidLoad:(CPNotification)aNotification
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPImageDidLoadNotification object:[self objectValue]];
|
||||
[self hideOrDisplayContents];
|
||||
|
||||
[self setNeedsLayout];
|
||||
@@ -298,7 +248,7 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (CPUInteger)imageScaling
|
||||
- (unsigned)imageScaling
|
||||
{
|
||||
return [self currentValueForThemeAttribute:@"image-scaling"];
|
||||
}
|
||||
@@ -347,8 +297,8 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
y = 0.0,
|
||||
insetWidth = (_hasShadow ? [_shadowView horizontalInset] : 0.0),
|
||||
insetHeight = (_hasShadow ? [_shadowView verticalInset] : 0.0),
|
||||
boundsWidth = CGRectGetWidth(bounds),
|
||||
boundsHeight = CGRectGetHeight(bounds),
|
||||
boundsWidth = _CGRectGetWidth(bounds),
|
||||
boundsHeight = _CGRectGetHeight(bounds),
|
||||
width = boundsWidth - insetWidth,
|
||||
height = boundsHeight - insetHeight;
|
||||
|
||||
@@ -446,22 +396,10 @@ 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)];
|
||||
}
|
||||
#endif
|
||||
|
||||
_imageRect = CGRectMake(x, y, width, height);
|
||||
_imageRect = _CGRectMake(x, y, width, height);
|
||||
|
||||
if (_hasShadow)
|
||||
[_shadowView setFrame:CGRectMake(x - [_shadowView leftInset], y - [_shadowView topInset], width + insetWidth, height + insetHeight)];
|
||||
[_shadowView setFrame:_CGRectMake(x - [_shadowView leftInset], y - [_shadowView topInset], width + insetWidth, height + insetHeight)];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
@@ -533,30 +471,16 @@ var CPImageViewEmptyPlaceholderImage = nil;
|
||||
{
|
||||
var image;
|
||||
|
||||
if (aValue == nil)
|
||||
image = nil;
|
||||
else if (aBinding === CPDataBinding)
|
||||
if (aBinding === CPDataBinding)
|
||||
image = [[CPImage alloc] initWithData:aValue];
|
||||
else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
|
||||
image = [CPImage cachedImageWithContentsOfFile:aValue];
|
||||
image = [[CPImage alloc] initWithContentsOfFile:aValue];
|
||||
else if (aBinding === CPValueBinding)
|
||||
image = aValue;
|
||||
|
||||
[_source setImage:image];
|
||||
}
|
||||
|
||||
- (id)valueForBinding:(CPString)aBinding
|
||||
{
|
||||
var image = [_source image];
|
||||
|
||||
if (aBinding === CPDataBinding)
|
||||
return [image data];
|
||||
else if (aBinding === CPValueURLBinding || aBinding === CPValuePathBinding)
|
||||
return [image filename];
|
||||
else if (aBinding === CPValueBinding)
|
||||
return image;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPImageViewImageKey = @"CPImageViewImageKey",
|
||||
@@ -574,12 +498,28 @@ var CPImageViewImageKey = @"CPImageViewImageKey",
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement = document.createElement("img");
|
||||
_DOMImageElement.style.position = "absolute";
|
||||
_DOMImageElement.style.left = "0px";
|
||||
_DOMImageElement.style.top = "0px";
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
if ([CPPlatform supportsDragAndDrop])
|
||||
{
|
||||
_DOMImageElement.setAttribute("draggable", "true");
|
||||
_DOMImageElement.style["-khtml-user-drag"] = "element";
|
||||
}
|
||||
|
||||
if (typeof(appkit_tag_dom_elements) !== "undefined" && !!appkit_tag_dom_elements)
|
||||
_DOMImageElement.setAttribute("data-cappuccino-view", [self className]);
|
||||
#endif
|
||||
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
[self _createDOMImageElement];
|
||||
_DOMElement.appendChild(_DOMImageElement);
|
||||
#endif
|
||||
|
||||
[self setHasShadow:[aCoder decodeBoolForKey:CPImageViewHasShadowKey]];
|
||||
@@ -605,12 +545,17 @@ var CPImageViewImageKey = @"CPImageViewImageKey",
|
||||
// We do this in order to avoid encoding the _shadowView, which
|
||||
// should just automatically be created programmatically as needed.
|
||||
if (_shadowView)
|
||||
[_shadowView removeFromSuperview];
|
||||
{
|
||||
var actualSubviews = _subviews;
|
||||
|
||||
_subviews = [_subviews copy];
|
||||
[_subviews removeObjectIdenticalTo:_shadowView];
|
||||
}
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
if (_shadowView)
|
||||
[self addSubview:_shadowView];
|
||||
_subviews = actualSubviews;
|
||||
|
||||
[aCoder encodeBool:_hasShadow forKey:CPImageViewHasShadowKey];
|
||||
[aCoder encodeInt:_imageAlignment forKey:CPImageViewImageAlignmentKey];
|
||||
@@ -620,21 +565,3 @@ var CPImageViewImageKey = @"CPImageViewImageKey",
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPImage (CachedImage)
|
||||
|
||||
+ (CPImage)cachedImageWithContentsOfFile:(CPString)aFile
|
||||
{
|
||||
var cached_name = [CPString stringWithFormat:@"%@_%d", [self class], [aFile hash]],
|
||||
image = [CPImage imageNamed:cached_name];
|
||||
|
||||
if (!image)
|
||||
{
|
||||
image = [[CPImage alloc] initWithContentsOfFile:aFile];
|
||||
[image setName:cached_name];
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -21,16 +21,14 @@
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@import "CPEvent_Constants.j"
|
||||
@import "CPText.j"
|
||||
@import "CPEvent.j"
|
||||
|
||||
|
||||
CPStandardKeyBindings = {
|
||||
@"@.": @"cancelOperation:",
|
||||
|
||||
@"@a": @"selectAll:",
|
||||
@"@~$v": @"pasteAsPlainText:",
|
||||
@"^a": @"moveToBeginningOfParagraph:",
|
||||
@"^$a": @"moveToBeginningOfParagraphAndModifySelection:",
|
||||
@"^b": @"moveBackward:",
|
||||
|
||||
+96
-442
@@ -26,23 +26,19 @@
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPInvocation.j>
|
||||
@import <Foundation/CPValueTransformer.j>
|
||||
@import <Foundation/CPKeyValueObserving.j>
|
||||
|
||||
@class CPButton
|
||||
|
||||
var exposedBindingsMap = @{},
|
||||
bindingsMap = @{};
|
||||
var exposedBindingsMap = [CPDictionary new],
|
||||
bindingsMap = [CPDictionary new];
|
||||
|
||||
@typedef CPBindingOperationKind
|
||||
var CPBindingOperationAnd = 0,
|
||||
CPBindingOperationOr = 1;
|
||||
|
||||
@implementation CPBinder : CPObject
|
||||
{
|
||||
CPDictionary _info;
|
||||
id _source @accessors(getter=source);
|
||||
id _source;
|
||||
|
||||
JSObject _suppressedNotifications;
|
||||
JSObject _placeholderForMarker;
|
||||
@@ -71,20 +67,6 @@ var CPBindingOperationAnd = 0,
|
||||
return [[bindingsMap objectForKey:[anObject UID]] objectForKey:aBinding];
|
||||
}
|
||||
|
||||
+ (void)_reverseSetValueFromExclusiveBinderForObject:(id)anObject
|
||||
{
|
||||
var bindersByBindingName = [bindingsMap objectForKey:[anObject UID]];
|
||||
|
||||
[bindersByBindingName enumerateKeysAndObjectsUsingBlock:function(binding, binder, stop)
|
||||
{
|
||||
if ([binder isKindOfClass:[self class]])
|
||||
{
|
||||
[binder reverseSetValueFor:binding];
|
||||
stop(YES);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (CPDictionary)infoForBinding:(CPString)aBinding forObject:(id)anObject
|
||||
{
|
||||
var theBinding = [self getBinding:aBinding forObject:anObject];
|
||||
@@ -112,9 +94,9 @@ var CPBindingOperationAnd = 0,
|
||||
if (!theBinding)
|
||||
return;
|
||||
|
||||
var info = theBinding._info,
|
||||
observedObject = [info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [info objectForKey:CPObservedKeyPathKey];
|
||||
var infoDictionary = theBinding._info,
|
||||
observedObject = [infoDictionary objectForKey:CPObservedObjectKey],
|
||||
keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey];
|
||||
|
||||
[observedObject removeObserver:theBinding forKeyPath:keyPath];
|
||||
[bindings removeObjectForKey:aBinding];
|
||||
@@ -123,7 +105,6 @@ var CPBindingOperationAnd = 0,
|
||||
+ (void)unbindAllForObject:(id)anObject
|
||||
{
|
||||
var bindings = [bindingsMap objectForKey:[anObject UID]];
|
||||
|
||||
if (!bindings)
|
||||
return;
|
||||
|
||||
@@ -131,39 +112,33 @@ var CPBindingOperationAnd = 0,
|
||||
count = allKeys.length;
|
||||
|
||||
while (count--)
|
||||
[anObject unbind:allKeys[count]];
|
||||
[anObject unbind:[bindings objectForKey:allKeys[count]]];
|
||||
|
||||
[bindingsMap removeObjectForKey:[anObject UID]];
|
||||
}
|
||||
|
||||
- (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
|
||||
{
|
||||
// We use [self init] here because subclasses override init. We can't override this method
|
||||
// because their initialization has to occur before this method in this class is executed.
|
||||
self = [self init];
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_source = aSource;
|
||||
_info = @{
|
||||
CPObservedObjectKey: aDestination,
|
||||
CPObservedKeyPathKey: aKeyPath,
|
||||
};
|
||||
_info = [CPDictionary dictionaryWithObjects:[aDestination, aKeyPath] forKeys:[CPObservedObjectKey, CPObservedKeyPathKey]];
|
||||
_suppressedNotifications = {};
|
||||
_placeholderForMarker = {};
|
||||
|
||||
if (options)
|
||||
[_info setObject:options forKey:CPOptionsKey];
|
||||
|
||||
[self _updatePlaceholdersWithOptions:options forBinding:aName];
|
||||
[self _updatePlaceholdersWithOptions:options];
|
||||
|
||||
[aDestination addObserver:self forKeyPath:aKeyPath options:0 context:aBinding];
|
||||
[aDestination addObserver:self forKeyPath:aKeyPath options:CPKeyValueObservingOptionNew context:aBinding];
|
||||
|
||||
var bindings = [bindingsMap objectForKey:[_source UID]];
|
||||
|
||||
if (!bindings)
|
||||
{
|
||||
bindings = @{};
|
||||
bindings = [CPDictionary new];
|
||||
[bindingsMap setObject:bindings forKey:[_source UID]];
|
||||
}
|
||||
|
||||
@@ -174,47 +149,21 @@ var CPBindingOperationAnd = 0,
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)isBindingAllowed:(CPString)aBinding forObject:(id)anObject
|
||||
{
|
||||
if ([[anObject class] isBindingExclusive:aBinding])
|
||||
{
|
||||
var bindingsForObject = [bindingsMap objectForKey:[anObject UID]],
|
||||
allBindings = [bindingsForObject allKeys],
|
||||
count = [allBindings count];
|
||||
|
||||
while (count--)
|
||||
{
|
||||
if ([[anObject class] isBindingExclusive:allBindings[count]])
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)raiseIfNotApplicable:(id)aValue forKeyPath:(CPString)keyPath options:(CPDictionary)options
|
||||
{
|
||||
if (aValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
|
||||
{
|
||||
[CPException raise:CPGenericException
|
||||
reason:@"Cannot transform non-applicable key on: " + _source + " key path: " + keyPath + " value: " + aValue];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)theBinding
|
||||
{
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
newValue = [destination valueForKeyPath:keyPath];
|
||||
newValue = [destination valueForKeyPath:keyPath],
|
||||
isPlaceholder = CPIsControllerMarker(newValue);
|
||||
|
||||
// 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 (isPlaceholder)
|
||||
{
|
||||
[self raiseIfNotApplicable:newValue forKeyPath:keyPath options:options];
|
||||
if (newValue === CPNotApplicableMarker && [options objectForKey:CPRaisesForNotApplicableKeysBindingOption])
|
||||
{
|
||||
[CPException raise:CPGenericException
|
||||
reason:@"Cannot transform non-applicable key on: " + _source + " key path: " + keyPath + " value: " + newValue];
|
||||
}
|
||||
|
||||
var value = [self _placeholderForMarker:newValue];
|
||||
[self setPlaceholderValue:value withMarker:newValue forBinding:theBinding];
|
||||
@@ -261,7 +210,6 @@ var CPBindingOperationAnd = 0,
|
||||
return;
|
||||
|
||||
var objectSuppressions = _suppressedNotifications[[anObject UID]];
|
||||
|
||||
if (objectSuppressions && objectSuppressions[aKeyPath])
|
||||
return;
|
||||
|
||||
@@ -280,7 +228,6 @@ var CPBindingOperationAnd = 0,
|
||||
if (!valueTransformer)
|
||||
{
|
||||
var valueTransformerClass = CPClassFromString(valueTransformerName);
|
||||
|
||||
if (valueTransformerClass)
|
||||
{
|
||||
valueTransformer = [[valueTransformerClass alloc] init];
|
||||
@@ -294,11 +241,7 @@ var CPBindingOperationAnd = 0,
|
||||
if (valueTransformer)
|
||||
aValue = [valueTransformer transformedValue:aValue];
|
||||
|
||||
// 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])
|
||||
&& ![_source respondsToSelector:@selector(setPlaceholderString:)])
|
||||
if (aValue === undefined || aValue === nil || aValue === [CPNull null])
|
||||
aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
|
||||
|
||||
return aValue;
|
||||
@@ -342,7 +285,6 @@ var CPBindingOperationAnd = 0,
|
||||
|
||||
var uid = [anObject UID],
|
||||
objectSuppressions = _suppressedNotifications[uid];
|
||||
|
||||
if (!objectSuppressions)
|
||||
_suppressedNotifications[uid] = objectSuppressions = {};
|
||||
|
||||
@@ -359,7 +301,6 @@ var CPBindingOperationAnd = 0,
|
||||
|
||||
var uid = [anObject UID],
|
||||
objectSuppressions = _suppressedNotifications[uid];
|
||||
|
||||
if (!objectSuppressions)
|
||||
return;
|
||||
|
||||
@@ -376,23 +317,15 @@ var CPBindingOperationAnd = 0,
|
||||
optionName = CPBinderPlaceholderOptions[count],
|
||||
isExplicit = [options containsKey:optionName],
|
||||
placeholder = isExplicit ? [options objectForKey:optionName] : nil;
|
||||
|
||||
[self _setPlaceholder:placeholder forMarker:marker isDefault:!isExplicit];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
|
||||
- (void)_placeholderForMarker:aMarker
|
||||
{
|
||||
[self _updatePlaceholdersWithOptions:options];
|
||||
}
|
||||
|
||||
- (JSObject)_placeholderForMarker:(id)aMarker
|
||||
{
|
||||
var placeholder = _placeholderForMarker[[aMarker UID]];
|
||||
|
||||
var placeholder = _placeholderForMarker[aMarker];
|
||||
if (placeholder)
|
||||
return placeholder.value;
|
||||
|
||||
return placeholder['value'];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -400,14 +333,14 @@ var CPBindingOperationAnd = 0,
|
||||
{
|
||||
if (isDefault)
|
||||
{
|
||||
var existingPlaceholder = _placeholderForMarker[[aMarker UID]];
|
||||
var existingPlaceholder = _placeholderForMarker[aMarker];
|
||||
|
||||
// Don't overwrite an explicitly set placeholder with a default.
|
||||
if (existingPlaceholder && !existingPlaceholder.isDefault)
|
||||
if (existingPlaceholder && !existingPlaceholder['isDefault'])
|
||||
return;
|
||||
}
|
||||
|
||||
_placeholderForMarker[[aMarker UID]] = { 'isDefault': isDefault, 'value': aPlaceholder };
|
||||
_placeholderForMarker[aMarker] = { 'isDefault': isDefault, 'value': aPlaceholder };
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -419,16 +352,11 @@ var CPBindingOperationAnd = 0,
|
||||
[CPBinder exposeBinding:aBinding forClass:[self class]];
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
return [CPBinder class];
|
||||
}
|
||||
|
||||
+ (BOOL)isBindingExclusive:(CPString)aBinding
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (CPArray)exposedBindings
|
||||
{
|
||||
var exposedBindings = [],
|
||||
@@ -457,11 +385,6 @@ var CPBindingOperationAnd = 0,
|
||||
if (!anObject || !aKeyPath)
|
||||
return CPLog.error("Invalid object or path on " + self + " for " + aBinding);
|
||||
|
||||
if (![CPBinder isBindingAllowed:aBinding forObject:self])
|
||||
{
|
||||
CPLog.warn([self description] + " : cannot bind " + aBinding + " because another binding with the same functionality is already in use.");
|
||||
return;
|
||||
}
|
||||
//if (![[self exposedBindings] containsObject:aBinding])
|
||||
// CPLog.warn("No binding exposed on " + self + " for " + aBinding);
|
||||
|
||||
@@ -482,7 +405,7 @@ var CPBindingOperationAnd = 0,
|
||||
[binderClass unbind:aBinding forObject:self];
|
||||
}
|
||||
|
||||
- (CPString)_replacementKeyPathForBinding:(CPString)binding
|
||||
- (id)_replacementKeyPathForBinding:(CPString)binding
|
||||
{
|
||||
return binding;
|
||||
}
|
||||
@@ -491,13 +414,13 @@ var CPBindingOperationAnd = 0,
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Provides stub implementations that simply calls super for the "objectValue" binding.
|
||||
This class should not be necessary but assures backwards compliance with our old way of doing bindings.
|
||||
|
||||
IMPORTANT!
|
||||
Every class with a value binding should implement a subclass to handle its specific value binding logic.
|
||||
Provides stub implementations that simply call super for the "objectValue" binding
|
||||
This class should not be necessary but assures backwards compliance with our old way of doing bindings
|
||||
Every class with a value binding should implement a subclass to handle it's specific value binding logic
|
||||
*/
|
||||
@implementation _CPValueBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)theBinding
|
||||
{
|
||||
@@ -511,9 +434,8 @@ var CPBindingOperationAnd = 0,
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMultipleValueBooleanBinding : CPBinder
|
||||
@implementation _CPKeyValueOrBinding : CPBinder
|
||||
{
|
||||
CPBindingOperationKind _operation;
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
@@ -523,14 +445,7 @@ var CPBindingOperationAnd = 0,
|
||||
if (!bindings)
|
||||
return;
|
||||
|
||||
var baseBinding = aBinding.replace(/\d$/, "");
|
||||
|
||||
[_source setValue:[self resolveMultipleValuesForBinding:baseBinding bindings:bindings booleanOperation:_operation] forKey:baseBinding];
|
||||
}
|
||||
|
||||
- (void)reverseSetValueFor:(CPString)theBinding
|
||||
{
|
||||
// read-only
|
||||
[_source setValue:resolveMultipleValues(aBinding, bindings, CPBindingOperationOr) forKey:aBinding];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
||||
@@ -538,146 +453,61 @@ var CPBindingOperationAnd = 0,
|
||||
[self setValueFor:context];
|
||||
}
|
||||
|
||||
- (BOOL)resolveMultipleValuesForBinding:(CPString)aBinding bindings:(CPDictionary)bindings booleanOperation:(CPBindingOperationKind)operation
|
||||
@end
|
||||
|
||||
@implementation _CPKeyValueAndBinding : CPBinder
|
||||
{
|
||||
var bindingName = aBinding,
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
{
|
||||
var bindings = [bindingsMap objectForKey:[_source UID]];
|
||||
|
||||
if (!bindings)
|
||||
return;
|
||||
|
||||
[_source setValue:resolveMultipleValues(aBinding, bindings, CPBindingOperationAnd) forKey:aBinding];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObejct change:(CPDictionary)changes context:(id)context
|
||||
{
|
||||
[self setValueFor:context];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var resolveMultipleValues = function(/*CPString*/key, /*CPDictionary*/bindings, /*GSBindingOperationKind*/operation)
|
||||
{
|
||||
var bindingName = key,
|
||||
theBinding,
|
||||
count = 2;
|
||||
count = 1;
|
||||
|
||||
while (theBinding = [bindings objectForKey:bindingName])
|
||||
{
|
||||
var info = theBinding._info,
|
||||
object = [info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
||||
options = [info objectForKey:CPOptionsKey],
|
||||
value = [object valueForKeyPath:keyPath];
|
||||
var infoDictionary = theBinding._info,
|
||||
object = [infoDictionary objectForKey:CPObservedObjectKey],
|
||||
keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey],
|
||||
options = [infoDictionary objectForKey:CPOptionsKey];
|
||||
|
||||
if (CPIsControllerMarker(value))
|
||||
{
|
||||
[self raiseIfNotApplicable:value forKeyPath:keyPath options:options];
|
||||
value = [theBinding _placeholderForMarker:value];
|
||||
}
|
||||
else
|
||||
value = [theBinding transformValue:value withOptions:options];
|
||||
var value = [theBinding transformValue:[object valueForKeyPath:keyPath] withOptions:options];
|
||||
|
||||
if (operation === CPBindingOperationOr)
|
||||
{
|
||||
// Any true condition means true for OR
|
||||
if (value)
|
||||
return YES;
|
||||
}
|
||||
if (value == operation)
|
||||
return operation;
|
||||
|
||||
// Any false condition means false for AND
|
||||
else if (!value)
|
||||
return NO;
|
||||
|
||||
bindingName = aBinding + (count++);
|
||||
bindingName = [CPString stringWithFormat:@"%@%i", key, ++count];
|
||||
}
|
||||
|
||||
// If we get here, all OR conditions were false or all AND conditions were true
|
||||
return operation === CPBindingOperationOr ? NO : YES;
|
||||
}
|
||||
return !operation;
|
||||
};
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPMultipleValueAndBinding : _CPMultipleValueBooleanBinding
|
||||
|
||||
- (id)init
|
||||
var invokeAction = function(/*CPString*/targetKey, /*CPString*/argumentKey, /*CPDictionary*/bindings)
|
||||
{
|
||||
if (self = [super init])
|
||||
_operation = CPBindingOperationAnd;
|
||||
var theBinding = [bindings objectForKey:targetKey],
|
||||
infoDictionary = theBinding._info,
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPMultipleValueOrBinding : _CPMultipleValueBooleanBinding
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
_operation = CPBindingOperationOr;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMultipleValueActionBinding : CPBinder
|
||||
{
|
||||
CPString _argumentBinding;
|
||||
CPString _targetBinding;
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)theBinding
|
||||
{
|
||||
// Called when the binding is first created
|
||||
[self checkForNullBinding:theBinding initializing:YES];
|
||||
}
|
||||
|
||||
- (void)reverseSetValueFor:(CPString)theBinding
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
||||
{
|
||||
// context is the binding name
|
||||
[self checkForNullBinding:context initializing:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
|
||||
When the value of a multiple-value argument binding changes,
|
||||
if the binding is marked not to allow null arguments, we have to check
|
||||
if the binding's value is nil and disable the button if it is.
|
||||
Otherwise the button is enabled.
|
||||
*/
|
||||
- (void)checkForNullBinding:(CPString)theBinding initializing:(BOOL)isInitializing
|
||||
{
|
||||
// Only done for buttons
|
||||
if (![_source isKindOfClass:CPButton])
|
||||
return;
|
||||
|
||||
// We start with the button enabled for the first argument during init,
|
||||
// and subsequent checks can disable it.
|
||||
if (isInitializing && theBinding === CPArgumentBinding)
|
||||
[_source setEnabled:YES];
|
||||
|
||||
var bindings = [bindingsMap valueForKey:[_source UID]],
|
||||
binding = [bindings objectForKey:theBinding],
|
||||
info = binding._info,
|
||||
options = [info objectForKey:CPOptionsKey];
|
||||
|
||||
if (![options valueForKey:CPAllowsNullArgumentBindingOption])
|
||||
{
|
||||
var object = [info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
||||
value = [object valueForKeyPath:keyPath];
|
||||
|
||||
if (value == nil)
|
||||
{
|
||||
[_source setEnabled:NO];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If a binding value changed and did not fail the null test, enable the button
|
||||
if (!isInitializing)
|
||||
[_source setEnabled:YES];
|
||||
}
|
||||
|
||||
- (void)invokeAction
|
||||
{
|
||||
var bindings = [bindingsMap valueForKey:[_source UID]],
|
||||
theBinding = [bindings objectForKey:CPTargetBinding],
|
||||
|
||||
info = theBinding._info,
|
||||
object = [info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
||||
options = [info objectForKey:CPOptionsKey],
|
||||
object = [infoDictionary objectForKey:CPObservedObjectKey],
|
||||
keyPath = [infoDictionary objectForKey:CPObservedKeyPathKey],
|
||||
options = [infoDictionary objectForKey:CPOptionsKey],
|
||||
|
||||
target = [object valueForKeyPath:keyPath],
|
||||
selector = [options objectForKey:CPSelectorNameBindingOption];
|
||||
@@ -685,194 +515,27 @@ var CPBindingOperationAnd = 0,
|
||||
if (!target || !selector)
|
||||
return;
|
||||
|
||||
var invocation = [CPInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]],
|
||||
bindingName = CPArgumentBinding,
|
||||
var invocation = [CPInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
|
||||
[invocation setSelector:selector];
|
||||
|
||||
var bindingName = argumentKey,
|
||||
count = 1;
|
||||
|
||||
while (theBinding = [bindings objectForKey:bindingName])
|
||||
{
|
||||
info = theBinding._info;
|
||||
object = [info objectForKey:CPObservedObjectKey];
|
||||
keyPath = [info objectForKey:CPObservedKeyPathKey];
|
||||
infoDictionary = theBinding._info;
|
||||
|
||||
[invocation setArgument:[object valueForKeyPath:keyPath] atIndex:++count];
|
||||
keyPath = [infoDictionary objectForKey:CPObserverKeyPathKey];
|
||||
object = [[infoDictionary objectForKey:CPObservedObjectKey] valueForKeyPath:keyPath];
|
||||
|
||||
bindingName = CPArgumentBinding + count;
|
||||
if (object)
|
||||
[invocation setArgument:object atIndex:++count];
|
||||
|
||||
bindingName = [CPString stringWithFormat:@"%@%i", argumentKey, count];
|
||||
}
|
||||
|
||||
[invocation setSelector:selector];
|
||||
[invocation invokeWithTarget:target];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPActionBinding : _CPMultipleValueActionBinding
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_argumentBinding = CPArgumentBinding;
|
||||
_targetBinding = CPTargetBinding;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPDoubleClickActionBinding : _CPMultipleValueActionBinding
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_argumentBinding = CPArgumentBinding;
|
||||
_targetBinding = CPTargetBinding;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
Abstract superclass for CPValueWithPatternBinding and CPTitleWithPatternBinding.
|
||||
*/
|
||||
@implementation _CPPatternBinding : CPBinder
|
||||
{
|
||||
CPString _bindingKey;
|
||||
CPString _patternPlaceholder;
|
||||
}
|
||||
|
||||
- (void)setValueFor:(CPString)aBinding
|
||||
{
|
||||
var bindings = [bindingsMap valueForKey:[_source UID]];
|
||||
|
||||
if (!bindings)
|
||||
return;
|
||||
|
||||
// Strip off any trailing number from the binding name
|
||||
var baseBinding = aBinding.replace(/\d$/, ""),
|
||||
result = [self resolveMultipleValuesForBindings:bindings];
|
||||
|
||||
if (result.isPlaceholder)
|
||||
[self setPlaceholderValue:result.value withMarker:result.marker forBinding:baseBinding];
|
||||
else
|
||||
[self setValue:result.value forBinding:baseBinding];
|
||||
}
|
||||
|
||||
- (void)reverseSetValueFor:(CPString)theBinding
|
||||
{
|
||||
// read-only
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)changes context:(id)context
|
||||
{
|
||||
[self setValueFor:context];
|
||||
}
|
||||
|
||||
- (JSObject)resolveMultipleValuesForBindings:(CPDictionary)bindings
|
||||
{
|
||||
var theBinding,
|
||||
result = { value:@"", isPlaceholder:NO, marker:nil };
|
||||
|
||||
for (var count = 1; theBinding = [bindings objectForKey:_bindingKey + count]; ++count)
|
||||
{
|
||||
var info = theBinding._info,
|
||||
object = [info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
||||
options = [info objectForKey:CPOptionsKey],
|
||||
value = [object valueForKeyPath:keyPath];
|
||||
|
||||
if (count === 1)
|
||||
result.value = [options objectForKey:CPDisplayPatternBindingOption];
|
||||
|
||||
if (CPIsControllerMarker(value))
|
||||
{
|
||||
[self raiseIfNotApplicable:value forKeyPath:keyPath options:options];
|
||||
|
||||
result.isPlaceholder = YES;
|
||||
result.marker = value;
|
||||
|
||||
value = [theBinding _placeholderForMarker:value];
|
||||
}
|
||||
else
|
||||
value = [theBinding transformValue:value withOptions:options];
|
||||
|
||||
if (value == nil)
|
||||
value = @"";
|
||||
|
||||
result.value = result.value.replace("%{" + _patternPlaceholder + count + "}@", [value description]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/*!
|
||||
Users of this class must override setValue:forKey: and if the
|
||||
key is CPDisplayPatternValueBinding, set the appropriate value
|
||||
for the control class. For example, CPTextField uses setObjectValue.
|
||||
*/
|
||||
@implementation CPValueWithPatternBinding : _CPPatternBinding
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_bindingKey = CPDisplayPatternValueBinding;
|
||||
_patternPlaceholder = @"value";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/*!
|
||||
Users of this class must override setValue:forKey: and if the
|
||||
key is CPDisplayPatternTitleBinding, set the appropriate value
|
||||
for the control class. For example, CPBox uses setTitle.
|
||||
*/
|
||||
@implementation CPTitleWithPatternBinding : _CPPatternBinding
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_bindingKey = CPDisplayPatternTitleBinding;
|
||||
_patternPlaceholder = @"title";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPStateMarker : CPObject
|
||||
{
|
||||
CPString _name;
|
||||
}
|
||||
|
||||
- (id)initWithName:(CPString)aName
|
||||
{
|
||||
if (self = [super init])
|
||||
_name = aName;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPString)description
|
||||
{
|
||||
return "<" + _name + ">";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
[invocation invoke];
|
||||
};
|
||||
|
||||
// Keys in options dictionary
|
||||
|
||||
@@ -882,23 +545,18 @@ CPObservedKeyPathKey = @"CPObservedKeyPathKey";
|
||||
CPOptionsKey = @"CPOptionsKey";
|
||||
|
||||
// special markers
|
||||
CPNoSelectionMarker = [[_CPStateMarker alloc] initWithName:@"NO SELECTION MARKER"];
|
||||
CPMultipleValuesMarker = [[_CPStateMarker alloc] initWithName:@"MULTIPLE VALUES MARKER"];
|
||||
CPNotApplicableMarker = [[_CPStateMarker alloc] initWithName:@"NOT APPLICABLE MARKER"];
|
||||
CPNullMarker = [[_CPStateMarker alloc] initWithName:@"NULL MARKER"];
|
||||
CPMultipleValuesMarker = @"CPMultipleValuesMarker";
|
||||
CPNoSelectionMarker = @"CPNoSelectionMarker";
|
||||
CPNotApplicableMarker = @"CPNotApplicableMarker";
|
||||
CPNullMarker = @"CPNullMarker";
|
||||
|
||||
// Binding name constants
|
||||
CPAlignmentBinding = @"alignment";
|
||||
CPArgumentBinding = @"argument";
|
||||
CPContentArrayBinding = @"contentArray";
|
||||
CPContentBinding = @"content";
|
||||
CPContentObjectBinding = @"contentObject";
|
||||
CPContentObjectsBinding = @"contentObjects";
|
||||
CPContentValuesBinding = @"contentValues";
|
||||
CPDisplayPatternTitleBinding = @"displayPatternTitle";
|
||||
CPDisplayPatternValueBinding = @"displayPatternValue";
|
||||
CPDoubleClickArgumentBinding = @"doubleClickArgument";
|
||||
CPDoubleClickTargetBinding = @"doubleClickTarget";
|
||||
CPEditableBinding = @"editable";
|
||||
CPEnabledBinding = @"enabled";
|
||||
CPFontBinding = @"font";
|
||||
@@ -906,8 +564,6 @@ CPFontNameBinding = @"fontName";
|
||||
CPFontBoldBinding = @"fontBold";
|
||||
CPHiddenBinding = @"hidden";
|
||||
CPFilterPredicateBinding = @"filterPredicate";
|
||||
CPMaxValueBinding = @"maxValue";
|
||||
CPMinValueBinding = @"minValue";
|
||||
CPPredicateBinding = @"predicate";
|
||||
CPSelectedIndexBinding = @"selectedIndex";
|
||||
CPSelectedLabelBinding = @"selectedLabel";
|
||||
@@ -917,17 +573,15 @@ CPSelectedTagBinding = @"selectedTag";
|
||||
CPSelectedValueBinding = @"selectedValue";
|
||||
CPSelectedValuesBinding = @"selectedValues";
|
||||
CPSelectionIndexesBinding = @"selectionIndexes";
|
||||
CPTargetBinding = @"target";
|
||||
CPTextColorBinding = @"textColor";
|
||||
CPTitleBinding = @"title";
|
||||
CPToolTipBinding = @"toolTip";
|
||||
CPValueBinding = @"value";
|
||||
CPAttributedStringBinding = @"attributedString";
|
||||
CPValueURLBinding = @"valueURL";
|
||||
CPValuePathBinding = @"valuePath";
|
||||
CPDataBinding = @"data";
|
||||
|
||||
// Binding options constants
|
||||
//Binding options constants
|
||||
CPAllowsEditingMultipleValuesSelectionBindingOption = @"CPAllowsEditingMultipleValuesSelection";
|
||||
CPAllowsNullArgumentBindingOption = @"CPAllowsNullArgument";
|
||||
CPConditionallySetsEditableBindingOption = @"CPConditionallySetsEditable";
|
||||
|
||||
+87
-47
@@ -21,17 +21,25 @@
|
||||
*/
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPWindow_Constants.j"
|
||||
@import "CPSlider.j"
|
||||
|
||||
@global CPApp
|
||||
CPTickMarkBelow = 0;
|
||||
CPTickMarkAbove = 1;
|
||||
CPTickMarkLeft = CPTickMarkAbove;
|
||||
CPTickMarkRight = CPTickMarkBelow;
|
||||
|
||||
@typedef CPLevelIndicatorStyle
|
||||
CPRelevancyLevelIndicatorStyle = 0;
|
||||
CPContinuousCapacityLevelIndicatorStyle = 1;
|
||||
CPDiscreteCapacityLevelIndicatorStyle = 2;
|
||||
CPRatingLevelIndicatorStyle = 3;
|
||||
|
||||
var _CPLevelIndicatorBezelColor = nil,
|
||||
_CPLevelIndicatorSegmentEmptyColor = nil,
|
||||
_CPLevelIndicatorSegmentNormalColor = nil,
|
||||
_CPLevelIndicatorSegmentWarningColor = nil,
|
||||
_CPLevelIndicatorSegmentCriticalColor = nil,
|
||||
|
||||
_CPLevelIndicatorSpacing = 1;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPLevelIndicator
|
||||
@@ -54,21 +62,57 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
BOOL _isTracking;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
+ (void)initialize
|
||||
{
|
||||
return "level-indicator";
|
||||
}
|
||||
if (self !== [CPLevelIndicator class])
|
||||
return;
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"bezel-color": [CPNull null],
|
||||
@"color-empty": [CPNull null],
|
||||
@"color-normal": [CPNull null],
|
||||
@"color-warning": [CPNull null],
|
||||
@"color-critical": [CPNull null],
|
||||
@"spacing": 1.0,
|
||||
};
|
||||
var bundle = [CPBundle bundleForClass:CPLevelIndicator];
|
||||
|
||||
_CPLevelIndicatorBezelColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-bezel-left.png"] size:CGSizeMake(3.0, 18.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-bezel-center.png"] size:CGSizeMake(1.0, 18.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-bezel-right.png"] size:CGSizeMake(3.0, 18.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
|
||||
_CPLevelIndicatorSegmentEmptyColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-empty-left.png"] size:CGSizeMake(3.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-empty-center.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-empty-right.png"] size:CGSizeMake(3.0, 17.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
|
||||
_CPLevelIndicatorSegmentNormalColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-normal-left.png"] size:CGSizeMake(3.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-normal-center.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-normal-right.png"] size:CGSizeMake(3.0, 17.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
|
||||
_CPLevelIndicatorSegmentWarningColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-warning-left.png"] size:CGSizeMake(3.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-warning-center.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-warning-right.png"] size:CGSizeMake(3.0, 17.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
|
||||
_CPLevelIndicatorSegmentCriticalColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-critical-left.png"] size:CGSizeMake(3.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-critical-center.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPLevelIndicator/level-indicator-segment-critical-right.png"] size:CGSizeMake(3.0, 17.0)]
|
||||
]
|
||||
isVertical:NO
|
||||
]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -81,42 +125,37 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
_maxValue = 2;
|
||||
_warningValue = 2;
|
||||
_criticalValue = 2;
|
||||
|
||||
[self _init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_init
|
||||
{
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var bezelView = [self layoutEphemeralSubviewNamed:"bezel"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
// TODO Make themable.
|
||||
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color"]];
|
||||
[bezelView setBackgroundColor:_CPLevelIndicatorBezelColor];
|
||||
|
||||
var segmentCount = _maxValue - _minValue;
|
||||
|
||||
if (segmentCount <= 0)
|
||||
return;
|
||||
|
||||
var filledColor = [self valueForThemeAttribute:@"color-normal"],
|
||||
var filledColor = _CPLevelIndicatorSegmentNormalColor,
|
||||
value = [self doubleValue];
|
||||
|
||||
if (_warningValue < _criticalValue)
|
||||
{
|
||||
if (value >= _criticalValue)
|
||||
filledColor = [self valueForThemeAttribute:@"color-critical"];
|
||||
else if (value >= _warningValue)
|
||||
filledColor = [self valueForThemeAttribute:@"color-warning"];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value <= _criticalValue)
|
||||
filledColor = [self valueForThemeAttribute:@"color-critical"];
|
||||
else if (value <= _warningValue)
|
||||
filledColor = [self valueForThemeAttribute:@"color-warning"];
|
||||
}
|
||||
|
||||
if (value <= _criticalValue)
|
||||
filledColor = _CPLevelIndicatorSegmentCriticalColor;
|
||||
else if (value <= _warningValue)
|
||||
filledColor = _CPLevelIndicatorSegmentWarningColor;
|
||||
|
||||
for (var i = 0; i < segmentCount; i++)
|
||||
{
|
||||
@@ -124,13 +163,13 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
positioned:CPWindowAbove
|
||||
relativeToEphemeralSubviewNamed:bezelView];
|
||||
|
||||
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : [self valueForThemeAttribute:@"color-empty"]];
|
||||
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : _CPLevelIndicatorSegmentEmptyColor];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
return [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
return [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
|
||||
@@ -138,11 +177,11 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
// TODO Put into theme attributes.
|
||||
var bezelHeight = 18,
|
||||
segmentHeight = 17,
|
||||
bounds = CGRectCreateCopy([self bounds]);
|
||||
bounds = _CGRectCreateCopy([self bounds]);
|
||||
|
||||
if (aViewName == "bezel")
|
||||
{
|
||||
bounds.origin.y = (CGRectGetHeight(bounds) - bezelHeight) / 2.0;
|
||||
bounds.origin.y = (_CGRectGetHeight(bounds) - bezelHeight) / 2.0;
|
||||
bounds.size.height = bezelHeight;
|
||||
return bounds;
|
||||
}
|
||||
@@ -152,21 +191,20 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
segmentCount = _maxValue - _minValue;
|
||||
|
||||
if (segment >= segmentCount)
|
||||
return CGRectMakeZero();
|
||||
return _CGRectMakeZero();
|
||||
|
||||
var basicSegmentWidth = bounds.size.width / segmentCount,
|
||||
segmentFrame = CGRectCreateCopy([self bounds]),
|
||||
spacing = [self valueForThemeAttribute:@"spacing"];
|
||||
segmentFrame = CGRectCreateCopy([self bounds]);
|
||||
|
||||
segmentFrame.origin.y = (CGRectGetHeight(bounds) - bezelHeight) / 2.0;
|
||||
segmentFrame.origin.y = (_CGRectGetHeight(bounds) - bezelHeight) / 2.0;
|
||||
segmentFrame.origin.x = FLOOR(segment * basicSegmentWidth);
|
||||
segmentFrame.size.width = (segment == segmentCount - 1) ? bounds.size.width - segmentFrame.origin.x : FLOOR(((segment + 1) * basicSegmentWidth)) - FLOOR((segment * basicSegmentWidth)) - spacing;
|
||||
segmentFrame.size.width = (segment == segmentCount - 1) ? bounds.size.width - segmentFrame.origin.x : FLOOR(((segment + 1) * basicSegmentWidth)) - FLOOR((segment * basicSegmentWidth)) - _CPLevelIndicatorSpacing;
|
||||
segmentFrame.size.height = segmentHeight;
|
||||
|
||||
return segmentFrame;
|
||||
}
|
||||
|
||||
return CGRectMakeZero();
|
||||
return _CGRectMakeZero();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -188,7 +226,7 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
- (CPView)hitTest:(CGPoint)aPoint
|
||||
- (CPView)hitTest:(CPPoint)aPoint
|
||||
{
|
||||
// Don't swallow clicks when displayed in a table.
|
||||
if (![self isEditable])
|
||||
@@ -287,7 +325,7 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setWarningValue:(double)warningValue
|
||||
- (void)setWarningValue:(double)warningValue;
|
||||
{
|
||||
if (_warningValue === warningValue)
|
||||
return;
|
||||
@@ -296,7 +334,7 @@ CPRatingLevelIndicatorStyle = 3;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setCriticalValue:(double)criticalValue
|
||||
- (void)setCriticalValue:(double)criticalValue;
|
||||
{
|
||||
if (_criticalValue === criticalValue)
|
||||
return;
|
||||
@@ -350,6 +388,8 @@ var CPLevelIndicatorStyleKey = "CPLevelIndicatorStyleKey",
|
||||
|
||||
_isEditable = [aCoder decodeBoolForKey:CPLevelIndicatorIsEditableKey];
|
||||
|
||||
[self _init];
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
+62
-267
@@ -25,22 +25,12 @@
|
||||
@import <Foundation/CPNotificationCenter.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPKeyValueBinding.j"
|
||||
@import "_CPMenuManager.j"
|
||||
@import "CPApplication.j"
|
||||
@import "CPClipView.j"
|
||||
@import "CPMenuItem.j"
|
||||
@import "CALayer.j"
|
||||
@import "CPPanel.j"
|
||||
|
||||
@global CPApp
|
||||
|
||||
@protocol CPMenuDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (void)menuWillOpen:(CPMenu)aMenu;
|
||||
- (void)menuDidClose:(CPMenu)aMenu;
|
||||
|
||||
@end
|
||||
|
||||
var CPMenuDelegate_menuWillOpen_ = 1 << 1,
|
||||
CPMenuDelegate_menuDidClose_ = 1 << 2;
|
||||
|
||||
CPMenuDidAddItemNotification = @"CPMenuDidAddItemNotification";
|
||||
CPMenuDidChangeItemNotification = @"CPMenuDidChangeItemNotification";
|
||||
@@ -48,8 +38,12 @@ CPMenuDidRemoveItemNotification = @"CPMenuDidRemoveItemNotification";
|
||||
|
||||
CPMenuDidEndTrackingNotification = @"CPMenuDidEndTrackingNotification";
|
||||
|
||||
var MENUBAR_HEIGHT = 28.0;
|
||||
|
||||
var _CPMenuBarVisible = NO,
|
||||
_CPMenuBarTitle = @"",
|
||||
_CPMenuBarIconImage = nil,
|
||||
_CPMenuBarIconImageAlphaValue = 1.0,
|
||||
_CPMenuBarAttributes = nil,
|
||||
_CPMenuBarSharedWindow = nil;
|
||||
|
||||
@@ -62,27 +56,24 @@ var _CPMenuBarVisible = NO,
|
||||
*/
|
||||
@implementation CPMenu : CPObject
|
||||
{
|
||||
CPMenu _supermenu;
|
||||
CPMenu _supermenu;
|
||||
|
||||
CPString _title;
|
||||
CPString _name;
|
||||
CPString _title;
|
||||
CPString _name;
|
||||
|
||||
CPFont _font;
|
||||
CPFont _font;
|
||||
|
||||
float _minimumWidth;
|
||||
float _minimumWidth;
|
||||
|
||||
CPMutableArray _items;
|
||||
CPMutableArray _items;
|
||||
|
||||
BOOL _autoenablesItems;
|
||||
BOOL _showsStateColumn;
|
||||
BOOL _autoenablesItems;
|
||||
BOOL _showsStateColumn;
|
||||
|
||||
id <CPMenuDelegate> _delegate;
|
||||
unsigned _implementedDelegateMethods;
|
||||
id _delegate;
|
||||
|
||||
int _highlightedIndex;
|
||||
_CPMenuWindow _menuWindow;
|
||||
|
||||
CPEvent _lastCloseEvent;
|
||||
int _highlightedIndex;
|
||||
_CPMenuWindow _menuWindow;
|
||||
}
|
||||
|
||||
// Managing the Menu Bar
|
||||
@@ -92,7 +83,7 @@ var _CPMenuBarVisible = NO,
|
||||
if (self !== [CPMenu class])
|
||||
return;
|
||||
|
||||
[[self class] setMenuBarAttributes:@{}];
|
||||
[[self class] setMenuBarAttributes:[CPDictionary dictionary]];
|
||||
}
|
||||
|
||||
+ (BOOL)menuBarVisible
|
||||
@@ -118,8 +109,8 @@ var _CPMenuBarVisible = NO,
|
||||
[_CPMenuBarSharedWindow setMenu:[CPApp mainMenu]];
|
||||
|
||||
[_CPMenuBarSharedWindow setTitle:_CPMenuBarTitle];
|
||||
[_CPMenuBarSharedWindow setIconImage:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-icon-image" forClass:_CPMenuView]];
|
||||
[_CPMenuBarSharedWindow setIconImageAlphaValue:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-icon-image-alpha-value" forClass:_CPMenuView]];
|
||||
[_CPMenuBarSharedWindow setIconImage:_CPMenuBarIconImage];
|
||||
[_CPMenuBarSharedWindow setIconImageAlphaValue:_CPMenuBarIconImageAlphaValue];
|
||||
|
||||
[_CPMenuBarSharedWindow setColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarBackgroundColor"]];
|
||||
[_CPMenuBarSharedWindow setTextColor:[_CPMenuBarAttributes objectForKey:@"CPMenuBarTextColor"]];
|
||||
@@ -163,13 +154,6 @@ var _CPMenuBarVisible = NO,
|
||||
return _CPMenuBarImage;
|
||||
}
|
||||
|
||||
+ (void)_setOrRemoveMenuBarAttribute:(id)aValue forKey:(id)aKey
|
||||
{
|
||||
if (aValue == nil)
|
||||
[_CPMenuBarAttributes removeObjectForKey:aKey];
|
||||
else
|
||||
[_CPMenuBarAttributes setObject:aValue forKey:aKey];
|
||||
}
|
||||
|
||||
+ (void)setMenuBarAttributes:(CPDictionary)attributes
|
||||
{
|
||||
@@ -194,8 +178,8 @@ var _CPMenuBarVisible = NO,
|
||||
|
||||
else if (!textColor && !titleColor)
|
||||
{
|
||||
[self _setOrRemoveMenuBarAttribute:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-text-color" forClass:_CPMenuView] forKey:@"CPMenuBarTextColor"];
|
||||
[self _setOrRemoveMenuBarAttribute:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-title-color" forClass:_CPMenuView] forKey:@"CPMenuBarTitleColor"];
|
||||
[_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTextColor"];
|
||||
[_CPMenuBarAttributes setObject:[CPColor colorWithRed:0.051 green:0.2 blue:0.275 alpha:1.0] forKey:@"CPMenuBarTitleColor"];
|
||||
}
|
||||
|
||||
if (!textShadowColor && titleShadowColor)
|
||||
@@ -206,18 +190,18 @@ var _CPMenuBarVisible = NO,
|
||||
|
||||
else if (!textShadowColor && !titleShadowColor)
|
||||
{
|
||||
[self _setOrRemoveMenuBarAttribute:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-text-shadow-color" forClass:_CPMenuView] forKey:@"CPMenuBarTextShadowColor"];
|
||||
[self _setOrRemoveMenuBarAttribute:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-title-shadow-color" forClass:_CPMenuView] forKey:@"CPMenuBarTitleShadowColor"];
|
||||
[_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTextShadowColor"];
|
||||
[_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarTitleShadowColor"];
|
||||
}
|
||||
|
||||
if (!highlightColor)
|
||||
[self _setOrRemoveMenuBarAttribute:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-highlight-color" forClass:_CPMenuView] forKey:@"CPMenuBarHighlightColor"];
|
||||
[_CPMenuBarAttributes setObject:[CPColor colorWithCalibratedRed:94.0 / 255.0 green:130.0 / 255.0 blue:186.0 / 255.0 alpha:1.0] forKey:@"CPMenuBarHighlightColor"];
|
||||
|
||||
if (!highlightTextColor)
|
||||
[self _setOrRemoveMenuBarAttribute:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-highlight-text-color" forClass:_CPMenuView] forKey:@"CPMenuBarHighlightTextColor"];
|
||||
[_CPMenuBarAttributes setObject:[CPColor whiteColor] forKey:@"CPMenuBarHighlightTextColor"];
|
||||
|
||||
if (!highlightTextShadowColor)
|
||||
[self _setOrRemoveMenuBarAttribute:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-highlight-text-shadow-color" forClass:_CPMenuView] forKey:@"CPMenuBarHighlightTextShadowColor"];
|
||||
[_CPMenuBarAttributes setObject:[CPColor blackColor] forKey:@"CPMenuBarHighlightTextShadowColor"];
|
||||
|
||||
if (_CPMenuBarSharedWindow)
|
||||
{
|
||||
@@ -246,14 +230,14 @@ var _CPMenuBarVisible = NO,
|
||||
- (float)menuBarHeight
|
||||
{
|
||||
if (self === [CPApp mainMenu])
|
||||
return [CPMenu menuBarHeight];
|
||||
return MENUBAR_HEIGHT;
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
+ (float)menuBarHeight
|
||||
{
|
||||
return [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-height" forClass:_CPMenuView];
|
||||
return MENUBAR_HEIGHT;
|
||||
}
|
||||
|
||||
// Creating a CPMenu Object
|
||||
@@ -269,8 +253,7 @@ 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;
|
||||
@@ -292,7 +275,7 @@ var _CPMenuBarVisible = NO,
|
||||
@param aMenuItem the item to insert
|
||||
@param anIndex the index in the menu to insert the item.
|
||||
*/
|
||||
- (void)insertItem:(CPMenuItem)aMenuItem atIndex:(CPUInteger)anIndex
|
||||
- (void)insertItem:(CPMenuItem)aMenuItem atIndex:(unsigned)anIndex
|
||||
{
|
||||
[self insertObject:aMenuItem inItemsAtIndex:anIndex];
|
||||
}
|
||||
@@ -305,7 +288,7 @@ var _CPMenuBarVisible = NO,
|
||||
@param anIndex the index location in the menu for the new item
|
||||
@return the new menu item
|
||||
*/
|
||||
- (CPMenuItem)insertItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent atIndex:(CPUInteger)anIndex
|
||||
- (CPMenuItem)insertItemWithTitle:(CPString)aTitle action:(SEL)anAction keyEquivalent:(CPString)aKeyEquivalent atIndex:(unsigned)anIndex
|
||||
{
|
||||
var item = [[CPMenuItem alloc] initWithTitle:aTitle action:anAction keyEquivalent:aKeyEquivalent];
|
||||
|
||||
@@ -349,7 +332,7 @@ var _CPMenuBarVisible = NO,
|
||||
Removes the item at the specified index from the menu
|
||||
@param anIndex the index of the item to remove
|
||||
*/
|
||||
- (void)removeItemAtIndex:(CPUInteger)anIndex
|
||||
- (void)removeItemAtIndex:(unsigned)anIndex
|
||||
{
|
||||
[self removeObjectFromItemsAtIndex:anIndex];
|
||||
}
|
||||
@@ -369,16 +352,12 @@ var _CPMenuBarVisible = NO,
|
||||
while (count--)
|
||||
[_items[count] setMenu:nil];
|
||||
|
||||
[self _highlightItemAtIndex:CPNotFound];
|
||||
_highlightedIndex = CPNotFound;
|
||||
|
||||
// Because we are changing _items directly, be sure to notify KVO
|
||||
[self willChangeValueForKey:@"items"];
|
||||
_items = [CPMutableArray array];
|
||||
[self didChangeValueForKey:@"items"];
|
||||
|
||||
// Ensure the main menu updates if cleared
|
||||
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
|
||||
[_CPMenuBarSharedWindow setMenu:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -387,23 +366,15 @@ var _CPMenuBarVisible = NO,
|
||||
*/
|
||||
- (void)itemChanged:(CPMenuItem)aMenuItem
|
||||
{
|
||||
/*
|
||||
During cib unarchiving, menu items will have a reference to their menu,
|
||||
but of course the items are still being unarchived and the menu's _items
|
||||
have not yet been instantiated. In that case we will not do anything here.
|
||||
*/
|
||||
if ([aMenuItem menu] !== self || !_items)
|
||||
if ([aMenuItem menu] !== self)
|
||||
return;
|
||||
|
||||
if (_menuWindow)
|
||||
[[_menuWindow _menuView] tile];
|
||||
|
||||
[aMenuItem setValue:[aMenuItem valueForKey:@"changeCount"] + 1 forKey:@"changeCount"];
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPMenuDidChangeItemNotification
|
||||
object:self
|
||||
userInfo:@{ @"CPMenuItemIndex": [_items indexOfObjectIdenticalTo:aMenuItem] }];
|
||||
userInfo:[CPDictionary dictionaryWithObject:[_items indexOfObjectIdenticalTo:aMenuItem] forKey:@"CPMenuItemIndex"]];
|
||||
}
|
||||
|
||||
// Finding Menu Items
|
||||
@@ -636,17 +607,15 @@ var _CPMenuBarVisible = NO,
|
||||
|
||||
/*!
|
||||
Enables or disables the receiver’s menu items.
|
||||
If the item has no target, an action binding is checked. If the target does not implement
|
||||
the menu item's action method or the binding's selector, the item is disabled.
|
||||
If the target responds to selector validateMenuItem: or validateUserInterfaceItem: (in that order) the return value is used.
|
||||
If the target does not implement the menu item's action method the item is disabled.
|
||||
If the target responsds to selector validateMenuItem: or validateUserInterfaceItem: (in that order) the return value is used.
|
||||
*/
|
||||
- (void)update
|
||||
{
|
||||
if (!_autoenablesItems)
|
||||
if (![self autoenablesItems])
|
||||
return;
|
||||
|
||||
var items = [self itemArray];
|
||||
|
||||
for (var i = 0; i < [items count]; i++)
|
||||
{
|
||||
var item = [items objectAtIndex:i];
|
||||
@@ -654,51 +623,14 @@ var _CPMenuBarVisible = NO,
|
||||
if ([item hasSubmenu])
|
||||
continue;
|
||||
|
||||
// If there are enabled bindings for the item, they override anything else
|
||||
var binder = [CPBinder getBinding:CPEnabledBinding forObject:item];
|
||||
var validator = [CPApp targetForAction:[item action] to:[item target] from:item];
|
||||
|
||||
if (binder)
|
||||
{
|
||||
[binder setValueFor:CPEnabledBinding];
|
||||
[[_menuWindow _menuView] tile];
|
||||
return;
|
||||
}
|
||||
|
||||
var validator = [CPApp targetForAction:[item action] to:[item target] from:item],
|
||||
shouldBeEnabled = YES;
|
||||
|
||||
if (!validator)
|
||||
{
|
||||
// If targetForAction: returns nil, it could be that there is no action.
|
||||
// If there is an action and nil is returned, no valid target could be found.
|
||||
if ([item action] || [item target])
|
||||
shouldBeEnabled = NO;
|
||||
else
|
||||
{
|
||||
// Check to see if there is a target binding with an invalid selector
|
||||
var info = [CPBinder infoForBinding:CPTargetBinding forObject:item];
|
||||
|
||||
if (info)
|
||||
{
|
||||
var object = [info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [info objectForKey:CPObservedKeyPathKey],
|
||||
options = [info objectForKey:CPOptionsKey],
|
||||
target = [object valueForKeyPath:keyPath],
|
||||
selector = [options valueForKey:CPSelectorNameBindingOption];
|
||||
|
||||
if (target && selector && ![target respondsToSelector:CPSelectorFromString(selector)])
|
||||
shouldBeEnabled = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (![validator respondsToSelector:[item action]])
|
||||
shouldBeEnabled = NO;
|
||||
if (!validator || ![validator respondsToSelector:[item action]])
|
||||
[item setEnabled:NO];
|
||||
else if ([validator respondsToSelector:@selector(validateMenuItem:)])
|
||||
shouldBeEnabled = [validator validateMenuItem:item];
|
||||
[item setEnabled:[validator validateMenuItem:item]];
|
||||
else if ([validator respondsToSelector:@selector(validateUserInterfaceItem:)])
|
||||
shouldBeEnabled = [validator validateUserInterfaceItem:item];
|
||||
|
||||
[item setEnabled:shouldBeEnabled];
|
||||
[item setEnabled:[validator validateUserInterfaceItem:item]];
|
||||
}
|
||||
|
||||
[[_menuWindow _menuView] tile];
|
||||
@@ -743,13 +675,7 @@ var _CPMenuBarVisible = NO,
|
||||
// highlightedItem is always enabled. Do there exist edge cases: disabling on closing a menu,
|
||||
// etc.? Requires further investigation and tests.
|
||||
if (highlightedItem && [highlightedItem isEnabled])
|
||||
{
|
||||
// Perform any action binding
|
||||
var binding = [CPBinder getBinding:CPTargetBinding forObject:highlightedItem];
|
||||
[binding invokeAction];
|
||||
|
||||
[CPApp sendAction:[highlightedItem action] to:[highlightedItem target] from:highlightedItem];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -877,9 +803,6 @@ var _CPMenuBarVisible = NO,
|
||||
|
||||
+ (void)popUpContextMenu:(CPMenu)aMenu withEvent:(CPEvent)anEvent forView:(CPView)aView withFont:(CPFont)aFont
|
||||
{
|
||||
// This is needed when we are making several rights click
|
||||
[[_CPMenuManager sharedMenuManager] cancelActiveMenu];
|
||||
|
||||
[aMenu _menuWillOpen];
|
||||
|
||||
if (!aFont)
|
||||
@@ -963,19 +886,9 @@ var _CPMenuBarVisible = NO,
|
||||
|
||||
// Managing the Delegate
|
||||
|
||||
- (void)setDelegate:(id <CPMenuDelegate>)aDelegate
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate === aDelegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
_implementedDelegateMethods = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(menuWillOpen:)])
|
||||
_implementedDelegateMethods |= CPMenuDelegate_menuWillOpen_;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(menuDidClose:)])
|
||||
_implementedDelegateMethods |= CPMenuDelegate_menuDidClose_;
|
||||
}
|
||||
|
||||
- (id)delegate
|
||||
@@ -985,16 +898,18 @@ var _CPMenuBarVisible = NO,
|
||||
|
||||
- (void)_menuWillOpen
|
||||
{
|
||||
[self _sendDelegateMenuWillOpen];
|
||||
var delegate = [self delegate];
|
||||
|
||||
if ([delegate respondsToSelector:@selector(menuWillOpen:)])
|
||||
[delegate menuWillOpen:self];
|
||||
}
|
||||
|
||||
- (void)_menuDidClose
|
||||
{
|
||||
// Remember which event caused this menu to close, if any. CPPopUpButton uses this to detect
|
||||
// when a click on the button itself caused the menu to close.
|
||||
_lastCloseEvent = [CPApp currentEvent];
|
||||
var delegate = [self delegate];
|
||||
|
||||
[self _sendDelegateMenuDidClose];
|
||||
if ([delegate respondsToSelector:@selector(menuDidClose:)])
|
||||
[delegate menuDidClose:self];
|
||||
}
|
||||
|
||||
// Handling Tracking
|
||||
@@ -1010,7 +925,7 @@ var _CPMenuBarVisible = NO,
|
||||
{
|
||||
[CPApp sendEvent:[CPEvent
|
||||
otherEventWithType:CPAppKitDefined
|
||||
location:CGPointMakeZero()
|
||||
location:_CGPointMakeZero()
|
||||
modifierFlags:0
|
||||
timestamp:0
|
||||
windowNumber:0
|
||||
@@ -1025,10 +940,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 */
|
||||
@@ -1070,23 +981,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?
|
||||
@@ -1096,13 +991,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;
|
||||
@@ -1113,7 +1002,7 @@ var _CPMenuBarVisible = NO,
|
||||
Sends the action of the menu item at the specified index.
|
||||
@param anIndex the index of the item
|
||||
*/
|
||||
- (void)performActionForItemAtIndex:(CPUInteger)anIndex
|
||||
- (void)performActionForItemAtIndex:(unsigned)anIndex
|
||||
{
|
||||
var item = _items[anIndex];
|
||||
|
||||
@@ -1166,7 +1055,7 @@ var _CPMenuBarVisible = NO,
|
||||
}
|
||||
}
|
||||
|
||||
- (CPMenu)_menuWithName:(CPString)aName
|
||||
- (void)_menuWithName:(CPString)aName
|
||||
{
|
||||
if (aName === _name)
|
||||
return self;
|
||||
@@ -1182,57 +1071,8 @@ 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
|
||||
|
||||
|
||||
@implementation CPMenu (CPMenuDelegate)
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate menuWillOpen
|
||||
*/
|
||||
- (void)_sendDelegateMenuWillOpen
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPMenuDelegate_menuWillOpen_))
|
||||
return;
|
||||
|
||||
[_delegate menuWillOpen:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Call delegate menuDidClose
|
||||
*/
|
||||
- (void)_sendDelegateMenuDidClose
|
||||
{
|
||||
if (!(_implementedDelegateMethods & CPMenuDelegate_menuDidClose_))
|
||||
return;
|
||||
|
||||
[_delegate menuDidClose:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPMenu (CPKeyValueCoding)
|
||||
|
||||
- (CPUInteger)countOfItems
|
||||
@@ -1265,18 +1105,12 @@ var _CPMenuBarVisible = NO,
|
||||
return;
|
||||
|
||||
[aMenuItem setMenu:self];
|
||||
[self _highlightItemAtIndex:CPNotFound];
|
||||
[_items insertObject:aMenuItem atIndex:anIndex];
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
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];
|
||||
userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]];
|
||||
}
|
||||
|
||||
- (void)removeObjectFromItemsAtIndex:(CPUInteger)anIndex
|
||||
@@ -1285,17 +1119,12 @@ var _CPMenuBarVisible = NO,
|
||||
return;
|
||||
|
||||
[[_items objectAtIndex:anIndex] setMenu:nil];
|
||||
[self _highlightItemAtIndex:CPNotFound];
|
||||
[_items removeObjectAtIndex:anIndex];
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
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];
|
||||
userInfo:[CPDictionary dictionaryWithObject:anIndex forKey:@"CPMenuItemIndex"]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1303,8 +1132,7 @@ var _CPMenuBarVisible = NO,
|
||||
var CPMenuTitleKey = @"CPMenuTitleKey",
|
||||
CPMenuNameKey = @"CPMenuNameKey",
|
||||
CPMenuItemsKey = @"CPMenuItemsKey",
|
||||
CPMenuShowsStateColumnKey = @"CPMenuShowsStateColumnKey",
|
||||
CPMenuAutoEnablesItemsKey = @"CPMenuAutoEnablesItemsKey";
|
||||
CPMenuShowsStateColumnKey = @"CPMenuShowsStateColumnKey";
|
||||
|
||||
@implementation CPMenu (CPCoding)
|
||||
|
||||
@@ -1326,7 +1154,7 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
|
||||
|
||||
_showsStateColumn = ![aCoder containsValueForKey:CPMenuShowsStateColumnKey] || [aCoder decodeBoolForKey:CPMenuShowsStateColumnKey];
|
||||
|
||||
_autoenablesItems = ![aCoder containsValueForKey:CPMenuAutoEnablesItemsKey] || [aCoder decodeBoolForKey:CPMenuAutoEnablesItemsKey];
|
||||
_autoenablesItems = YES;
|
||||
|
||||
[self setMinimumWidth:0];
|
||||
}
|
||||
@@ -1349,43 +1177,10 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
|
||||
|
||||
if (!_showsStateColumn)
|
||||
[aCoder encodeBool:_showsStateColumn forKey:CPMenuShowsStateColumnKey];
|
||||
|
||||
if (!_autoenablesItems)
|
||||
[aCoder encodeBool:_autoenablesItems forKey:CPMenuAutoEnablesItemsKey];
|
||||
}
|
||||
|
||||
@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"
|
||||
|
||||
|
||||
+156
-113
@@ -1,40 +1,18 @@
|
||||
/*
|
||||
* _CPMenuBarWindow.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPPanel.j"
|
||||
@import "_CPMenuManager.j"
|
||||
@import "_CPMenuWindow.j"
|
||||
|
||||
@class _CPMenuView
|
||||
@class CPMenu
|
||||
@class CPMenuItem
|
||||
|
||||
@global CPMenuDidAddItemNotification
|
||||
@global CPMenuDidChangeItemNotification
|
||||
@global CPMenuDidRemoveItemNotification
|
||||
var MENUBAR_HEIGHT = 28.0,
|
||||
MENUBAR_MARGIN = 10.0,
|
||||
MENUBAR_LEFT_MARGIN = 10.0,
|
||||
MENUBAR_RIGHT_MARGIN = 10.0;
|
||||
|
||||
@global document
|
||||
var _CPMenuBarWindowBackgroundColor = nil,
|
||||
_CPMenuBarWindowFont = nil;
|
||||
|
||||
@implementation _CPMenuBarWindow : CPPanel
|
||||
{
|
||||
CPMenu _menu;
|
||||
CPView _highlightView;
|
||||
CPArray _menuItemViews;
|
||||
|
||||
@@ -54,9 +32,19 @@
|
||||
CPColor _highlightTextShadowColor;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [_CPMenuBarWindow class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPMenuBarWindowFont = [CPFont boldSystemFontOfSize:[CPFont systemFontSize]];
|
||||
}
|
||||
|
||||
+ (CPFont)font
|
||||
{
|
||||
return [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-font" forClass:_CPMenuView];
|
||||
return _CPMenuBarWindowFont;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
@@ -64,14 +52,12 @@
|
||||
// This only shows up in browser land, so don't bother calculating metrics in desktop.
|
||||
var contentRect = [[CPPlatformWindow primaryPlatformWindow] contentBounds];
|
||||
|
||||
contentRect.size.height = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-height" forClass:_CPMenuView];
|
||||
contentRect.size.height = MENUBAR_HEIGHT;
|
||||
|
||||
self = [super initWithContentRect:contentRect styleMask:CPBorderlessWindowMask];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_constrainsToUsableScreen = NO;
|
||||
|
||||
[self setLevel:CPMainMenuWindowLevel];
|
||||
[self setAutoresizingMask:CPWindowWidthSizable];
|
||||
|
||||
@@ -132,12 +118,15 @@
|
||||
|
||||
- (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]];
|
||||
{
|
||||
if (!_CPMenuBarWindowBackgroundColor)
|
||||
_CPMenuBarWindowBackgroundColor = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[_CPMenuBarWindow class]] pathForResource:@"_CPMenuBarWindow/_CPMenuBarWindowBackground.png"] size:CGSizeMake(1.0, 28.0)]];
|
||||
|
||||
[[self contentView] setBackgroundColor:_CPMenuBarWindowBackgroundColor];
|
||||
}
|
||||
else
|
||||
[targetView setBackgroundColor:aColor];
|
||||
[[self contentView] setBackgroundColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
@@ -148,7 +137,6 @@
|
||||
_textColor = aColor;
|
||||
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setTextColor:) withObject:_textColor];
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setParentMenuTextColor:) withObject:_textColor];
|
||||
}
|
||||
|
||||
- (void)setTitleColor:(CPColor)aColor
|
||||
@@ -169,7 +157,6 @@
|
||||
_textShadowColor = aColor;
|
||||
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setTextShadowColor:) withObject:_textShadowColor];
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setParentMenuTextShadowColor:) withObject:_textShadowColor];
|
||||
}
|
||||
|
||||
- (void)setTitleShadowColor:(CPColor)aColor
|
||||
@@ -188,8 +175,6 @@
|
||||
return;
|
||||
|
||||
_highlightColor = aColor;
|
||||
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setParentMenuHighlightColor:) withObject:_highlightColor];
|
||||
}
|
||||
|
||||
- (void)setHighlightTextColor:(CPColor)aColor
|
||||
@@ -199,7 +184,7 @@
|
||||
|
||||
_highlightTextColor = aColor;
|
||||
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setParentMenuHighlightTextColor:) withObject:_highlightTextColor];
|
||||
// [_menuItemViews makeObjectsPerformSelector:@selector(setActivateColor:) withObject:_highlightTextColor];
|
||||
}
|
||||
|
||||
- (void)setHighlightTextShadowColor:(CPColor)aColor
|
||||
@@ -209,7 +194,7 @@
|
||||
|
||||
_highlightTextShadowColor = aColor;
|
||||
|
||||
[_menuItemViews makeObjectsPerformSelector:@selector(setParentMenuHighlightTextShadowColor:) withObject:_highlightTextShadowColor];
|
||||
// [_menuItemViews makeObjectsPerformSelector:@selector(setActivateShadowColor:) withObject:_highlightTextShadowColor];
|
||||
}
|
||||
|
||||
- (void)setMenu:(CPMenu)aMenu
|
||||
@@ -282,16 +267,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];
|
||||
@@ -341,11 +316,6 @@
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstMouse:(CPEvent)anEvent
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
var constraintRect = CGRectInset([[self platformWindow] visibleFrame], 5.0, 0.0);
|
||||
@@ -365,46 +335,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 = MENUBAR_LEFT_MARGIN,
|
||||
y = 0.0,
|
||||
isLeftAligned = YES;
|
||||
|
||||
for (; index < count; ++index)
|
||||
@@ -413,38 +354,46 @@
|
||||
|
||||
if ([item isSeparatorItem])
|
||||
{
|
||||
x = CGRectGetWidth([self frame]) - [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-right-margin" forClass:_CPMenuView];
|
||||
x = CGRectGetWidth([self frame]) - MENUBAR_RIGHT_MARGIN;
|
||||
isLeftAligned = NO;
|
||||
|
||||
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)
|
||||
{
|
||||
[menuItemView setFrame:CGRectMake(x, 0.0, CGRectGetWidth(frame), [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-height" forClass:_CPMenuView])];
|
||||
[menuItemView setFrame:CGRectMake(x, 0.0, CGRectGetWidth(frame), MENUBAR_HEIGHT)];
|
||||
|
||||
x += CGRectGetWidth([menuItemView frame]);
|
||||
}
|
||||
else
|
||||
{
|
||||
[menuItemView setFrame:CGRectMake(x - CGRectGetWidth(frame), 0.0, CGRectGetWidth(frame), [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-height" forClass:_CPMenuView])];
|
||||
[menuItemView setFrame:CGRectMake(x - CGRectGetWidth(frame), 0.0, CGRectGetWidth(frame), MENUBAR_HEIGHT)];
|
||||
|
||||
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
|
||||
@@ -453,7 +402,7 @@
|
||||
|
||||
[super setFrame:aRect display:shouldDisplay animate:shouldAnimate];
|
||||
|
||||
if (!CGSizeEqualToSize(size, aRect.size))
|
||||
if (!_CGSizeEqualToSize(size, aRect.size))
|
||||
[self tile];
|
||||
}
|
||||
|
||||
@@ -485,18 +434,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;
|
||||
}
|
||||
|
||||
@@ -508,3 +452,102 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMenuBarView : _CPMenuView
|
||||
{
|
||||
}
|
||||
|
||||
- (CGRect)rectForItemAtIndex:(int)anIndex
|
||||
{
|
||||
return [_menuItemViews[anIndex === CPNotFound ? 0 : anIndex] frame];
|
||||
}
|
||||
|
||||
- (int)itemIndexAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
|
||||
if (!CGRectContainsPoint(bounds, aPoint))
|
||||
return CPNotFound;
|
||||
|
||||
var x = aPoint.x,
|
||||
low = 0,
|
||||
high = _visibleMenuItemInfos.length - 1;
|
||||
|
||||
while (low <= high)
|
||||
{
|
||||
var middle = FLOOR(low + (high - low) / 2),
|
||||
info = _visibleMenuItemInfos[middle],
|
||||
frame = [info.view frame];
|
||||
|
||||
if (x < CGRectGetMinX(frame))
|
||||
high = middle - 1;
|
||||
|
||||
else if (x > CGRectGetMaxX(frame))
|
||||
low = middle + 1;
|
||||
|
||||
else
|
||||
return info.index;
|
||||
}
|
||||
|
||||
return CPNotFound;
|
||||
}
|
||||
|
||||
- (void)tile
|
||||
{
|
||||
var items = [_menu itemArray],
|
||||
index = 0,
|
||||
count = items.length,
|
||||
|
||||
x = MENUBAR_LEFT_MARGIN,
|
||||
y = 0.0,
|
||||
isLeftAligned = YES;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var item = items[index];
|
||||
|
||||
if ([item isSeparatorItem])
|
||||
{
|
||||
x = CGRectGetWidth([self frame]) - MENUBAR_RIGHT_MARGIN;
|
||||
isLeftAligned = NO;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([item isHidden])
|
||||
continue;
|
||||
|
||||
var menuItemView = [item _menuItemView],
|
||||
frame = [menuItemView frame];
|
||||
|
||||
if (isLeftAligned)
|
||||
{
|
||||
[menuItemView setFrameOrigin:CGPointMake(x, (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)];
|
||||
|
||||
x += CGRectGetWidth([menuItemView frame]) + MENUBAR_MARGIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
[menuItemView setFrameOrigin:CGPointMake(x - CGRectGetWidth(frame), (MENUBAR_HEIGHT - 1.0 - CGRectGetHeight(frame)) / 2.0)];
|
||||
|
||||
x = CGRectGetMinX([menuItemView frame]) - MENUBAR_MARGIN;
|
||||
}
|
||||
}
|
||||
|
||||
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)];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+54
-142
@@ -1,53 +1,16 @@
|
||||
/*
|
||||
* _CPMenuManager.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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>
|
||||
@import <Foundation/CPNotificationCenter.j>
|
||||
|
||||
@import "CPEvent.j"
|
||||
@import "CPKeyBinding.j"
|
||||
|
||||
@class CPWindow
|
||||
@class _CPMenuWindow
|
||||
@class _CPMenuView
|
||||
@class CPMenuItem
|
||||
|
||||
|
||||
@global CPApp
|
||||
@global CPMenuDidEndTrackingNotification
|
||||
@global _CPMenuWindowMenuBarBackgroundStyle
|
||||
@global _CPMenuWindowPopUpBackgroundStyle
|
||||
|
||||
_CPMenuManagerScrollingStateUp = -1;
|
||||
_CPMenuManagerScrollingStateDown = 1;
|
||||
_CPMenuManagerScrollingStateNone = 0;
|
||||
|
||||
var STICKY_TIME_INTERVAL = 0.4,
|
||||
var STICKY_TIME_INTERVAL = 0.5,
|
||||
SharedMenuManager = nil;
|
||||
|
||||
@implementation _CPMenuManager: CPObject
|
||||
{
|
||||
CPTimeInterval _startTime;
|
||||
CPEvent _openEvent;
|
||||
BOOL _mouseWasDragged;
|
||||
int _scrollingState;
|
||||
CGPoint _lastGlobalLocation;
|
||||
@@ -63,8 +26,6 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
|
||||
CPMenuItem _previousActiveItem;
|
||||
int _showTimerID;
|
||||
|
||||
int _menuBarButtonItemIndex;
|
||||
}
|
||||
|
||||
+ (_CPMenuManager)sharedMenuManager
|
||||
@@ -106,7 +67,6 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
CPApp._activeMenu = menu;
|
||||
|
||||
_startTime = [anEvent timestamp];
|
||||
_openEvent = anEvent;
|
||||
_scrollingState = _CPMenuManagerScrollingStateNone;
|
||||
|
||||
_constraintRect = aRect;
|
||||
@@ -125,7 +85,7 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
activeItem = activeItemIndex !== CPNotFound ? [menu itemAtIndex:activeItemIndex] : nil;
|
||||
|
||||
_menuBarButtonItemIndex = activeItemIndex;
|
||||
// _menuBarButtonMenuContainer = aMenuContainer;
|
||||
_menuBarButtonMenuContainer = aMenuContainer;
|
||||
|
||||
if ([activeItem _isMenuBarButton])
|
||||
return [self trackMenuBarButtonEvent:anEvent];
|
||||
@@ -136,11 +96,6 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
[self trackEvent:anEvent];
|
||||
}
|
||||
|
||||
- (void)_trackAgain
|
||||
{
|
||||
[CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPKeyDownMask | CPPeriodicMask | CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseDownMask | CPLeftMouseUpMask | CPRightMouseUpMask | CPAppKitDefinedMask | CPScrollWheelMask untilDate:nil inMode:nil dequeue:YES];
|
||||
}
|
||||
|
||||
- (void)trackEvent:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type],
|
||||
@@ -150,6 +105,8 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
if (type === CPAppKitDefined)
|
||||
return [self completeTracking];
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackEvent:) forNextEventMatchingMask:CPKeyDownMask | CPPeriodicMask | CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask | CPRightMouseUpMask | CPAppKitDefinedMask | CPScrollWheelMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
if (type === CPKeyDown)
|
||||
{
|
||||
var menu = trackingMenu,
|
||||
@@ -165,18 +122,23 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
if ([menu numberOfItems])
|
||||
[self interpretKeyEvent:anEvent forMenu:menu];
|
||||
|
||||
[self _trackAgain];
|
||||
return;
|
||||
}
|
||||
|
||||
if (_keyBuffer)
|
||||
{
|
||||
if (([anEvent timestamp] - _startTime) > (STICKY_TIME_INTERVAL + [activeMenu numberOfItems] / 2))
|
||||
[self selectNextItemBeginningWith:_keyBuffer inMenu:menu clearBuffer:YES];
|
||||
|
||||
if (type === CPPeriodic)
|
||||
return;
|
||||
}
|
||||
|
||||
// Periodic events don't have a valid location.
|
||||
var globalLocation = type === CPPeriodic ? _lastGlobalLocation : [anEvent globalLocation];
|
||||
|
||||
if (!globalLocation)
|
||||
{
|
||||
[self _trackAgain];
|
||||
return;
|
||||
}
|
||||
|
||||
// Find which menu window the mouse is currently on top of
|
||||
var activeMenuContainer = [self menuContainerForPoint:globalLocation],
|
||||
@@ -187,35 +149,6 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
activeItemIndex = activeMenuContainer ? [activeMenuContainer itemIndexAtPoint:menuLocation] : CPNotFound,
|
||||
activeItem = activeItemIndex !== CPNotFound ? [activeMenu itemAtIndex:activeItemIndex] : nil;
|
||||
|
||||
/*
|
||||
Click outside the menu structure?
|
||||
|
||||
The event which caused the menu to open is not considered even if it's
|
||||
technically a click outside of an open menu, since this would mean the same
|
||||
click which opened the menu could also close it. This is actually common.
|
||||
E.g. you click on a button and it opens a menu below itself.
|
||||
*/
|
||||
if (type === CPLeftMouseDown && _openEvent !== anEvent && (!activeMenuContainer || !CGRectContainsPoint([activeMenuContainer globalFrame], globalLocation)))
|
||||
{
|
||||
[self completeTracking];
|
||||
|
||||
/*
|
||||
You can close and interact with a control in a single click. E.g. you can have a menu open,
|
||||
click on a button outside of it and have the menu immediately close and the button activate,
|
||||
without having to click once to close the menu and once to activate the button.
|
||||
|
||||
Since we normally dequeue the event after tracking it, we'll have to put it back on the stack
|
||||
in this special case. Note that it's important the event is executed /right now/, since certain
|
||||
controls such as HTML upload buttons need a native click event at the top of the stack trace
|
||||
to activate - it's not something we can fake later.
|
||||
*/
|
||||
[CPApp sendEvent:anEvent];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[self _trackAgain];
|
||||
|
||||
// unhighlight when mouse is moved off the menu
|
||||
if (_lastGlobalLocation && CGRectContainsPoint([activeMenuContainer globalFrame], _lastGlobalLocation)
|
||||
&& !CGRectContainsPoint([activeMenuContainer globalFrame], globalLocation))
|
||||
@@ -225,7 +158,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;
|
||||
@@ -252,9 +185,9 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
|
||||
if (_lastMouseOverMenuView != mouseOverMenuView)
|
||||
{
|
||||
[_lastMouseOverMenuView mouseExited:anEvent];
|
||||
[mouseOverMenuView mouseExited:anEvent];
|
||||
// FIXME: Possibly multiple of these?
|
||||
[mouseOverMenuView mouseEntered:anEvent];
|
||||
[_lastMouseOverMenuView mouseEntered:anEvent];
|
||||
|
||||
_lastMouseOverMenuView = mouseOverMenuView;
|
||||
}
|
||||
@@ -283,7 +216,7 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
_lastMouseOverMenuView = nil;
|
||||
}
|
||||
|
||||
if (activeItemIndex !== CPNotFound)
|
||||
if (activeItemIndex != CPNotFound)
|
||||
[activeMenu _highlightItemAtIndex:activeItemIndex];
|
||||
|
||||
if (type === CPMouseMoved || type === CPLeftMouseDragged || type === CPLeftMouseDown || type === CPPeriodic)
|
||||
@@ -354,23 +287,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];
|
||||
|
||||
@@ -425,15 +342,6 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
[menu cancelTracking];
|
||||
}
|
||||
|
||||
- (void)cancelActiveMenu
|
||||
{
|
||||
if (CPApp._activeMenu)
|
||||
{
|
||||
[self completeTracking];
|
||||
_menuContainerStack = [];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)completeTracking
|
||||
{
|
||||
var trackingMenu = [self trackingMenu];
|
||||
@@ -484,8 +392,8 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
{
|
||||
var menuContainer = _menuContainerStack[index],
|
||||
menuContainerFrame = [menuContainer globalFrame],
|
||||
menuContainerMinX = CGRectGetMinX(menuContainerFrame),
|
||||
menuContainerMaxX = CGRectGetMaxX(menuContainerFrame);
|
||||
menuContainerMinX = _CGRectGetMinX(menuContainerFrame),
|
||||
menuContainerMaxX = _CGRectGetMaxX(menuContainerFrame);
|
||||
|
||||
// If within the x bounds of this menu container, return it.
|
||||
if (x < menuContainerMaxX && x >= menuContainerMinX)
|
||||
@@ -571,7 +479,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);
|
||||
|
||||
@@ -581,9 +489,6 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
}
|
||||
else if (!(modifierFlags & (CPCommandKeyMask | CPControlKeyMask)))
|
||||
{
|
||||
if (([anEvent timestamp] - _startTime) > STICKY_TIME_INTERVAL)
|
||||
_keyBuffer = nil;
|
||||
|
||||
if (!_keyBuffer)
|
||||
{
|
||||
_startTime = [anEvent timestamp];
|
||||
@@ -595,19 +500,17 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
else
|
||||
_keyBuffer += character;
|
||||
|
||||
[self selectNextItemBeginningWith:_keyBuffer inMenu:menu];
|
||||
_lastGlobalLocation = nil;
|
||||
[self selectNextItemBeginningWith:_keyBuffer inMenu:menu clearBuffer:NO];
|
||||
_lastGlobalLocation = Nil;
|
||||
}
|
||||
|
||||
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
|
||||
}
|
||||
|
||||
- (void)selectNextItemBeginningWith:(CPString)characters inMenu:(CPMenu)menu
|
||||
- (void)selectNextItemBeginningWith:(CPString)characters inMenu:(CPMenu)menu clearBuffer:(BOOL)shouldClear
|
||||
{
|
||||
var iter = [[menu itemArray] objectEnumerator],
|
||||
obj;
|
||||
|
||||
while ((obj = [iter nextObject]) != nil)
|
||||
while ((obj = [iter nextObject]) !== nil)
|
||||
{
|
||||
if ([obj isHidden] || ![obj isEnabled])
|
||||
continue;
|
||||
@@ -619,7 +522,13 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
}
|
||||
}
|
||||
|
||||
_startTime = [CPEvent currentTimestamp];
|
||||
if (shouldClear)
|
||||
{
|
||||
[CPEvent stopPeriodicEvents];
|
||||
_keyBuffer = Nil;
|
||||
}
|
||||
else
|
||||
_startTime = [CPEvent currentTimestamp];
|
||||
}
|
||||
|
||||
- (void)scrollToBeginningOfDocument:(CPMenu)menu
|
||||
@@ -647,7 +556,7 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
return;
|
||||
}
|
||||
|
||||
var next = current + (last - first);
|
||||
next = current + (last - first);
|
||||
|
||||
if (next < [menu numberOfItems])
|
||||
[menu _highlightItemAtIndex:next];
|
||||
@@ -675,7 +584,7 @@ var STICKY_TIME_INTERVAL = 0.4,
|
||||
return;
|
||||
}
|
||||
|
||||
var next = current - (last - first);
|
||||
next = current - (last - first);
|
||||
|
||||
if (next < 0)
|
||||
[menu _highlightItemAtIndex:0];
|
||||
@@ -745,32 +654,35 @@ 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];
|
||||
else if (menu == [CPApp mainMenu])
|
||||
[menu _highlightItemAtIndex:0];
|
||||
|
||||
var item = [menu highlightedItem];
|
||||
|
||||
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
|
||||
[self moveDown:menu];
|
||||
}
|
||||
}
|
||||
|
||||
- (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 _highlightItemAtIndex:[menu numberOfItems] - 1];
|
||||
return;
|
||||
}
|
||||
[menu _highlightItemAtIndex:index];
|
||||
|
||||
if (index >= 0)
|
||||
[menu _highlightItemAtIndex:index];
|
||||
else if (menu == [CPApp mainMenu])
|
||||
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
|
||||
var item = [menu highlightedItem];
|
||||
|
||||
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
|
||||
[self moveUp:menu];
|
||||
}
|
||||
|
||||
- (void)insertNewline:(CPMenu)menu
|
||||
|
||||
+81
-154
@@ -1,32 +1,5 @@
|
||||
/*
|
||||
* _CPMenuWindow.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPClipView.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPWindow.j"
|
||||
@import "_CPMenuManager.j"
|
||||
|
||||
@class _CPMenuView
|
||||
@class CPMenuItem
|
||||
|
||||
var _CPMenuWindowPool = [],
|
||||
_CPMenuWindowPoolCapacity = 5,
|
||||
@@ -37,6 +10,15 @@ _CPMenuWindowMenuBarBackgroundStyle = 0;
|
||||
_CPMenuWindowPopUpBackgroundStyle = 1;
|
||||
_CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
var STICKY_TIME_INTERVAL = 500,
|
||||
|
||||
TOP_MARGIN = 5.0,
|
||||
LEFT_MARGIN = 1.0,
|
||||
RIGHT_MARGIN = 1.0,
|
||||
BOTTOM_MARGIN = 5.0,
|
||||
|
||||
SCROLL_INDICATOR_HEIGHT = 16.0;
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
@@ -75,24 +57,32 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
+ (void)poolMenuWindow:(_CPMenuWindow)aMenuWindow
|
||||
{
|
||||
// FIXME :the poolMenuWindow is called too many times somewhere....
|
||||
if (!aMenuWindow || _CPMenuWindowPool.length >= _CPMenuWindowPoolCapacity || [_CPMenuWindowPool containsObject:aMenuWindow])
|
||||
if (!aMenuWindow || _CPMenuWindowPool.length >= _CPMenuWindowPoolCapacity)
|
||||
return;
|
||||
|
||||
_CPMenuWindowPool.push(aMenuWindow);
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [_CPMenuWindow class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPMenuWindowMoreAboveImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowMoreAbove.png"] size:CGSizeMake(38.0, 18.0)];
|
||||
_CPMenuWindowMoreBelowImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowMoreBelow.png"] size:CGSizeMake(38.0, 18.0)];
|
||||
}
|
||||
|
||||
- (id)initWithContentRect:(CGRect)aRect styleMask:(unsigned)aStyleMask
|
||||
{
|
||||
_constraintRect = CGRectMakeZero();
|
||||
_unconstrainedFrame = CGRectMakeZero();
|
||||
_constraintRect = _CGRectMakeZero();
|
||||
_unconstrainedFrame = _CGRectMakeZero();
|
||||
|
||||
self = [super initWithContentRect:aRect styleMask:CPBorderlessWindowMask];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_constrainsToUsableScreen = NO;
|
||||
|
||||
[self setLevel:CPPopUpMenuWindowLevel];
|
||||
[self setHasShadow:YES];
|
||||
[self setShadowStyle:CPMenuWindowShadowStyle];
|
||||
@@ -102,22 +92,22 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
_menuView = [[_CPMenuView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
_menuClipView = [[CPClipView alloc] initWithFrame:CGRectMake([_menuView valueForThemeAttribute:@"menu-window-margin-inset"].left, [_menuView valueForThemeAttribute:@"menu-window-margin-inset"].top, 0.0, 0.0)];
|
||||
_menuClipView = [[CPClipView alloc] initWithFrame:CGRectMake(LEFT_MARGIN, TOP_MARGIN, 0.0, 0.0)];
|
||||
[_menuClipView setDocumentView:_menuView];
|
||||
|
||||
[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]];
|
||||
[_moreAboveView setImage:_CPMenuWindowMoreAboveImage];
|
||||
[_moreAboveView setFrameSize:[_CPMenuWindowMoreAboveImage size]];
|
||||
|
||||
[contentView addSubview:_moreAboveView];
|
||||
|
||||
_moreBelowView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_moreBelowView setImage:[_menuView valueForThemeAttribute:@"menu-window-more-below-image"]];
|
||||
[_moreBelowView setFrameSize:[[_menuView valueForThemeAttribute:@"menu-window-more-below-image"] size]];
|
||||
[_moreBelowView setImage:_CPMenuWindowMoreBelowImage];
|
||||
[_moreBelowView setFrameSize:[_CPMenuWindowMoreBelowImage size]];
|
||||
|
||||
[contentView addSubview:_moreBelowView];
|
||||
}
|
||||
@@ -127,7 +117,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
+ (float)_standardLeftMargin
|
||||
{
|
||||
return [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-window-margin-inset" forClass:_CPMenuView].left;
|
||||
return LEFT_MARGIN;
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
@@ -149,10 +139,36 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
var bundle = [CPBundle bundleForClass:[self class]];
|
||||
|
||||
if (aBackgroundStyle == _CPMenuWindowPopUpBackgroundStyle)
|
||||
color = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-window-pop-up-background-style-color" forClass:_CPMenuView];
|
||||
color = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded0.png"] size:CGSizeMake(4.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow1.png"] size:CGSizeMake(1.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded2.png"] size:CGSizeMake(4.0, 4.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 1.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded6.png"] size:CGSizeMake(4.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow7.png"] size:CGSizeMake(1.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded8.png"] size:CGSizeMake(4.0, 4.0)]
|
||||
]]];
|
||||
|
||||
else if (aBackgroundStyle == _CPMenuWindowMenuBarBackgroundStyle)
|
||||
color = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-window-menu-bar-background-style-color" forClass:_CPMenuView];
|
||||
color = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 0.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 0.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 0.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow3.png"] size:CGSizeMake(4.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow4.png"] size:CGSizeMake(1.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow5.png"] size:CGSizeMake(4.0, 1.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded6.png"] size:CGSizeMake(4.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindow7.png"] size:CGSizeMake(1.0, 4.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuWindow/_CPMenuWindowRounded8.png"] size:CGSizeMake(4.0, 4.0)]
|
||||
]]];
|
||||
|
||||
_CPMenuWindowBackgroundColors[aBackgroundStyle] = color;
|
||||
}
|
||||
@@ -170,13 +186,12 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
[aMenu _setMenuWindow:self];
|
||||
[_menuView setMenu:aMenu];
|
||||
|
||||
var menuViewSize = [_menuView frame].size,
|
||||
marginInset = [_menuView valueForThemeAttribute:@"menu-window-margin-inset"];
|
||||
var menuViewSize = [_menuView frame].size;
|
||||
|
||||
[self setFrameSize:CGSizeMake(marginInset.left + menuViewSize.width + marginInset.right, marginInset.top + menuViewSize.height + marginInset.bottom)];
|
||||
[self setFrameSize:CGSizeMake(LEFT_MARGIN + menuViewSize.width + RIGHT_MARGIN, TOP_MARGIN + menuViewSize.height + BOTTOM_MARGIN)];
|
||||
|
||||
[_menuView scrollPoint:CGPointMake(0.0, 0.0)];
|
||||
[_menuClipView setFrame:CGRectMake(marginInset.left, marginInset.top, menuViewSize.width, menuViewSize.height)];
|
||||
[_menuClipView setFrame:CGRectMake(LEFT_MARGIN, TOP_MARGIN, menuViewSize.width, menuViewSize.height)];
|
||||
}
|
||||
|
||||
- (void)setMinWidth:(float)aWidth
|
||||
@@ -213,43 +228,28 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
- (CGRect)unconstrainedFrame
|
||||
{
|
||||
return CGRectMakeCopy(_unconstrainedFrame);
|
||||
return _CGRectMakeCopy(_unconstrainedFrame);
|
||||
}
|
||||
|
||||
// We need this because if not this will call setFrame: with -frame instead of -unconstrainedFrame, turning
|
||||
// the constrained frame into the unconstrained frame.
|
||||
- (void)setFrameOrigin:(CGPoint)aPoint
|
||||
{
|
||||
[super setFrame:CGRectMake(aPoint.x, aPoint.y, CGRectGetWidth(_unconstrainedFrame), CGRectGetHeight(_unconstrainedFrame))];
|
||||
[super setFrame:_CGRectMake(aPoint.x, aPoint.y, _CGRectGetWidth(_unconstrainedFrame), _CGRectGetHeight(_unconstrainedFrame))];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrame:CGRectMake(CGRectGetMinX(_unconstrainedFrame), CGRectGetMinY(_unconstrainedFrame), aSize.width, aSize.height)];
|
||||
[super setFrame:_CGRectMake(_CGRectGetMinX(_unconstrainedFrame), _CGRectGetMinY(_unconstrainedFrame), aSize.width, aSize.height)];
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)aFrame display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate
|
||||
{
|
||||
// FIXME: There are integral window issues with platform windows.
|
||||
// FIXME: This gets called far too often.
|
||||
_unconstrainedFrame = CGRectMakeCopy(aFrame);
|
||||
_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 && (CGRectGetMaxX(_unconstrainedFrame) > CGRectGetMaxX(_constraintRect)))
|
||||
{
|
||||
var supermenuWindow = supermenu._menuWindow;
|
||||
if (supermenuWindow)
|
||||
{
|
||||
var supermenuFrame = [supermenuWindow frame];
|
||||
_unconstrainedFrame.origin.x = CGRectGetMinX(supermenuFrame) - CGRectGetWidth(_unconstrainedFrame);
|
||||
}
|
||||
}
|
||||
|
||||
var constrainedFrame = CGRectIntersection(_unconstrainedFrame, _constraintRect),
|
||||
marginInset = [_menuView valueForThemeAttribute:@"menu-window-margin-inset"],
|
||||
scrollIndicatorHeight = [_menuView valueForThemeAttribute:@"menu-window-scroll-indicator-height"];
|
||||
var constrainedFrame = CGRectIntersection(_unconstrainedFrame, _constraintRect);
|
||||
|
||||
// We don't want to simply intersect the visible frame and the unconstrained frame.
|
||||
// We should be allowing as much of the width to fit as possible (pushing back and forward).
|
||||
@@ -268,41 +268,41 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
[super setFrame:constrainedFrame display:shouldDisplay animate:shouldAnimate];
|
||||
|
||||
// This needs to happen before changing the frame.
|
||||
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,
|
||||
var menuViewOrigin = CGPointMake(CGRectGetMinX(aFrame) + LEFT_MARGIN, CGRectGetMinY(aFrame) + TOP_MARGIN),
|
||||
moreAbove = menuViewOrigin.y < CGRectGetMinY(constrainedFrame) + TOP_MARGIN,
|
||||
moreBelow = menuViewOrigin.y + CGRectGetHeight([_menuView frame]) > CGRectGetMaxY(constrainedFrame) - BOTTOM_MARGIN,
|
||||
|
||||
topMargin = marginInset.top,
|
||||
bottomMargin = marginInset.bottom,
|
||||
topMargin = TOP_MARGIN,
|
||||
bottomMargin = BOTTOM_MARGIN,
|
||||
|
||||
contentView = [self contentView],
|
||||
bounds = [contentView bounds];
|
||||
|
||||
if (moreAbove)
|
||||
{
|
||||
topMargin += scrollIndicatorHeight;
|
||||
topMargin += SCROLL_INDICATOR_HEIGHT;
|
||||
|
||||
var frame = [_moreAboveView frame];
|
||||
|
||||
[_moreAboveView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(frame)) / 2.0, (marginInset.top + scrollIndicatorHeight - CGRectGetHeight(frame)) / 2.0)];
|
||||
[_moreAboveView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(frame)) / 2.0, (TOP_MARGIN + SCROLL_INDICATOR_HEIGHT - CGRectGetHeight(frame)) / 2.0)];
|
||||
}
|
||||
|
||||
[_moreAboveView setHidden:!moreAbove];
|
||||
|
||||
if (moreBelow)
|
||||
{
|
||||
bottomMargin += scrollIndicatorHeight;
|
||||
bottomMargin += SCROLL_INDICATOR_HEIGHT;
|
||||
|
||||
[_moreBelowView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth([_moreBelowView frame])) / 2.0, CGRectGetHeight(bounds) - scrollIndicatorHeight - marginInset.bottom)];
|
||||
[_moreBelowView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth([_moreBelowView frame])) / 2.0, CGRectGetHeight(bounds) - SCROLL_INDICATOR_HEIGHT - BOTTOM_MARGIN)];
|
||||
}
|
||||
|
||||
[_moreBelowView setHidden:!moreBelow];
|
||||
|
||||
var clipFrame = CGRectMakeZero();
|
||||
|
||||
clipFrame.origin.x = marginInset.left;
|
||||
clipFrame.origin.x = LEFT_MARGIN;
|
||||
clipFrame.origin.y = topMargin;
|
||||
clipFrame.size.width = CGRectGetWidth(constrainedFrame) - marginInset.left - marginInset.right;
|
||||
clipFrame.size.width = CGRectGetWidth(constrainedFrame) - LEFT_MARGIN - RIGHT_MARGIN;
|
||||
clipFrame.size.height = CGRectGetHeight(constrainedFrame) - topMargin - bottomMargin;
|
||||
|
||||
[_menuClipView setFrame:clipFrame];
|
||||
@@ -398,15 +398,15 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
- (_CPManagerScrollingState)scrollingStateForPoint:(CGPoint)aGlobalLocation
|
||||
{
|
||||
var frame = [self frame];
|
||||
if (!CGRectContainsPoint(frame,aGlobalLocation) || ![self canScroll])
|
||||
if (!CPRectContainsPoint(frame,aGlobalLocation) || ![self canScroll])
|
||||
return _CPMenuManagerScrollingStateNone;
|
||||
|
||||
// If we're at or above of the top scroll indicator...
|
||||
if (aGlobalLocation.y < CGRectGetMinY(frame) + [_menuView valueForThemeAttribute:@"menu-window-margin-inset"].top + [_menuView valueForThemeAttribute:@"menu-window-scroll-indicator-height"] && ![_moreAboveView isHidden])
|
||||
if (aGlobalLocation.y < CGRectGetMinY(frame) + TOP_MARGIN + SCROLL_INDICATOR_HEIGHT && ![_moreAboveView isHidden])
|
||||
return _CPMenuManagerScrollingStateUp;
|
||||
|
||||
// If we're at or below the bottom scroll indicator...
|
||||
if (aGlobalLocation.y > CGRectGetMaxY(frame) - [_menuView valueForThemeAttribute:@"menu-window-margin-inset"].bottom - [_menuView valueForThemeAttribute:@"menu-window-scroll-indicator-height"] && ![_moreBelowView isHidden])
|
||||
if (aGlobalLocation.y > CGRectGetMaxY(frame) - BOTTOM_MARGIN - SCROLL_INDICATOR_HEIGHT && ![_moreBelowView isHidden])
|
||||
return _CPMenuManagerScrollingStateDown;
|
||||
|
||||
return _CPMenuManagerScrollingStateNone;
|
||||
@@ -414,7 +414,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
- (float)deltaYForItemAtIndex:(int)anIndex
|
||||
{
|
||||
return [_menuView valueForThemeAttribute:@"menu-window-margin-inset"].top + CGRectGetMinY([_menuView rectForItemAtIndex:anIndex]);
|
||||
return TOP_MARGIN + CGRectGetMinY([_menuView rectForItemAtIndex:anIndex]);
|
||||
}
|
||||
|
||||
- (CGPoint)rectForItemAtIndex:(int)anIndex
|
||||
@@ -426,28 +426,13 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
{
|
||||
// Don't return indexes of items that aren't visible.
|
||||
if (!CGRectContainsPoint([_menuClipView bounds], [_menuClipView convertPoint:aPoint fromView:nil]))
|
||||
return CPNotFound;
|
||||
return NO;
|
||||
|
||||
return [_menuView itemIndexAtPoint:[_menuView convertPoint:aPoint fromView:nil]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
@implementation _CPMenuWindow (CSSTheming)
|
||||
|
||||
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
|
||||
{
|
||||
[[self contentView] _setThemeIncludingDescendants:aTheme];
|
||||
[_menuView _setThemeIncludingDescendants:aTheme];
|
||||
[_menuView tile];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
@@ -460,48 +445,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
CPFont _font @accessors(property=font);
|
||||
}
|
||||
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return "menu-view";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"menu-window-more-above-image": [CPNull null],
|
||||
@"menu-window-more-below-image": [CPNull null],
|
||||
@"menu-window-pop-up-background-style-color": [CPNull null],
|
||||
@"menu-window-menu-bar-background-style-color": [CPNull null],
|
||||
@"menu-window-margin-inset": CGInsetMake(5.0, 1.0, 1.0, 5.0),
|
||||
@"menu-window-scroll-indicator-height": 16.0,
|
||||
@"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,
|
||||
@"menu-bar-window-right-margin": 10.0,
|
||||
@"menu-bar-text-color": [CPNull null],
|
||||
@"menu-bar-title-color": [CPNull null],
|
||||
@"menu-bar-text-shadow-color": [CPNull null],
|
||||
@"menu-bar-title-shadow-color": [CPNull null],
|
||||
@"menu-bar-highlight-color": [CPNull null],
|
||||
@"menu-bar-highlight-text-color": [CPNull null],
|
||||
@"menu-bar-highlight-text-shadow-color": [CPNull null],
|
||||
@"menu-bar-height": 28.0,
|
||||
@"menu-bar-icon-image": [CPNull null],
|
||||
@"menu-bar-icon-image-alpha-value": 1.0,
|
||||
@"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
|
||||
};
|
||||
}
|
||||
|
||||
- (unsigned)numberOfUnhiddenItems
|
||||
{
|
||||
return _visibleMenuItemInfos.length;
|
||||
@@ -585,7 +528,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
|
||||
|
||||
[view setFrameOrigin:CGPointMake(0.0, y)];
|
||||
|
||||
[view _setThemeIncludingDescendants:[CPTheme defaultTheme]];
|
||||
[self addSubview:view];
|
||||
|
||||
var size = [view minSize],
|
||||
@@ -616,18 +558,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
|
||||
|
||||
@@ -25,33 +25,29 @@
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPImage.j"
|
||||
@import "CPText.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPView.j"
|
||||
@import "_CPMenuItemView.j"
|
||||
|
||||
@class CPMenu
|
||||
|
||||
@global CPApp
|
||||
|
||||
var CPMenuItemStringRepresentationDictionary = @{
|
||||
CPEscapeFunctionKey: "\u238B",
|
||||
CPTabCharacter: "\u21E5",
|
||||
CPBackTabCharacter: "\u21E4",
|
||||
CPSpaceFunctionKey: "\u2423",
|
||||
CPCarriageReturnCharacter: "\u23CE",
|
||||
CPBackspaceCharacter: "\u232B",
|
||||
CPDeleteFunctionKey: "\u232B",
|
||||
CPDeleteCharacter: "\u2326",
|
||||
CPHomeFunctionKey: "\u21F1",
|
||||
CPEndFunctionKey: "\u21F2",
|
||||
CPPageUpFunctionKey: "\u21DE",
|
||||
CPPageDownFunctionKey: "\u21DF",
|
||||
CPUpArrowFunctionKey: "\u2191",
|
||||
CPDownArrowFunctionKey: "\u2193",
|
||||
CPLeftArrowFunctionKey: "\u2190",
|
||||
CPRightArrowFunctionKey: "\u2192",
|
||||
CPClearDisplayFunctionKey: "\u2327",
|
||||
};
|
||||
var CPMenuItemStringRepresentationDictionary = [CPDictionary dictionary];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u238B" forKey:CPEscapeFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u21E5" forKey:CPTabCharacter];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u21E4" forKey:CPBackTabCharacter];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u2423" forKey:CPSpaceFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u23CE" forKey:CPCarriageReturnCharacter];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u232B" forKey:CPBackspaceCharacter];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u232B" forKey:CPDeleteFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u2326" forKey:CPDeleteCharacter];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u21F1" forKey:CPHomeFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u21F2" forKey:CPEndFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u21DE" forKey:CPPageUpFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u21DF" forKey:CPPageDownFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u2191" forKey:CPUpArrowFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u2193" forKey:CPDownArrowFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u2190" forKey:CPLeftArrowFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u2192" forKey:CPRightArrowFunctionKey];
|
||||
[CPMenuItemStringRepresentationDictionary setObject:"\u2327" forKey:CPClearDisplayFunctionKey];
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -105,16 +101,6 @@ var CPMenuItemStringRepresentationDictionary = @{
|
||||
_CPMenuItemView _menuItemView;
|
||||
}
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if ([aBinding hasPrefix:CPEnabledBinding])
|
||||
return [CPMultipleValueAndBinding class];
|
||||
else if (aBinding === CPTargetBinding || [aBinding hasPrefix:CPArgumentBinding])
|
||||
return [CPActionBinding class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
return [self initWithTitle:@"" action:nil keyEquivalent:nil];
|
||||
@@ -166,9 +152,6 @@ var CPMenuItemStringRepresentationDictionary = @{
|
||||
if (_isEnabled === isEnabled)
|
||||
return;
|
||||
|
||||
if (!isEnabled && [self isHighlighted])
|
||||
[_menu _highlightItemAtIndex:CPNotFound];
|
||||
|
||||
_isEnabled = !!isEnabled;
|
||||
|
||||
[_menuItemView setDirty];
|
||||
@@ -505,7 +488,7 @@ CPOffState
|
||||
if (_submenu)
|
||||
{
|
||||
[_submenu setSupermenu:_menu];
|
||||
[_submenu setTitle:[self title]];
|
||||
[_submenu setTitle:[self title]]
|
||||
|
||||
[self setTarget:_menu];
|
||||
[self setAction:@selector(submenuAction:)];
|
||||
@@ -826,7 +809,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.
|
||||
@@ -843,7 +826,7 @@ CPControlKeyMask
|
||||
[item setTarget:_target];
|
||||
[item setAction:_action];
|
||||
[item setEnabled:_isEnabled];
|
||||
[item setHidden:_isHidden];
|
||||
[item setHidden:_isHidden]
|
||||
[item setTag:_tag];
|
||||
[item setState:_state];
|
||||
[item setImage:_image];
|
||||
@@ -867,7 +850,7 @@ CPControlKeyMask
|
||||
return [self copy];
|
||||
}
|
||||
|
||||
// MARK: Internal
|
||||
#pragma mark Internal
|
||||
|
||||
/*
|
||||
@ignore
|
||||
@@ -897,20 +880,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 +894,6 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
|
||||
|
||||
CPMenuItemImageKey = @"CPMenuItemImageKey",
|
||||
CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey",
|
||||
CPMenuItemOnStateImageKey = @"CPMenuItemOnStateImageKey",
|
||||
CPMenuItemOffStateImageKey = @"CPMenuItemOffStateImageKey",
|
||||
CPMenuItemMixedStateImageKey = @"CPMenuItemMixedStateImageKey",
|
||||
|
||||
CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey",
|
||||
CPMenuItemMenuKey = @"CPMenuItemMenuKey",
|
||||
@@ -972,9 +938,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 +986,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);
|
||||
|
||||
@@ -1,62 +1,54 @@
|
||||
/*
|
||||
* _CPMenuItemMenuBarView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPView.j"
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "_CPImageAndTextView.j"
|
||||
|
||||
@class _CPMenuBarWindow
|
||||
@class _CPMenuView
|
||||
@class CPMenuItem
|
||||
var HORIZONTAL_MARGIN = 8.0,
|
||||
SUBMENU_INDICATOR_MARGIN = 3.0,
|
||||
VERTICAL_MARGIN = 4.0;
|
||||
|
||||
var SelectionColor = nil,
|
||||
SUBMENU_INDICATOR_COLOR = nil,
|
||||
_CPMenuItemSelectionColor = nil,
|
||||
_CPMenuItemTextShadowColor = nil,
|
||||
|
||||
_CPMenuItemDefaultStateImages = [],
|
||||
_CPMenuItemDefaultStateHighlightedImages = [];
|
||||
|
||||
@implementation _CPMenuItemMenuBarView : CPView
|
||||
{
|
||||
CPColor _highlightColor @accessors(property=highlightColor);
|
||||
CPColor _textColor @accessors(property=textColor);
|
||||
CPColor _textShadowColor @accessors(property=textShadowColor);
|
||||
CPColor _highlightTextColor @accessors(property=highlightTextColor);
|
||||
CPColor _highlightTextShadowColor @accessors(property=highlightTextShadowColor);
|
||||
|
||||
CPMenuItem _menuItem @accessors(property=menuItem);
|
||||
|
||||
CPFont _font;
|
||||
CPColor _textColor;
|
||||
CPColor _textShadowColor;
|
||||
|
||||
BOOL _isDirty;
|
||||
BOOL _shouldHighlight;
|
||||
|
||||
_CPImageAndTextView _imageAndTextView;
|
||||
CPView _submenuIndicatorView;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
+ (void)initialize
|
||||
{
|
||||
return "menu-item-bar-view";
|
||||
}
|
||||
if (self !== [_CPMenuItemMenuBarView class])
|
||||
return;
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"horizontal-margin": 9.0,
|
||||
@"submenu-indicator-margin": 3.0,
|
||||
@"vertical-margin": 4.0,
|
||||
};
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
SelectionColor = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPMenuBarWindow/_CPMenuBarWindowBackgroundSelected.png"] size:CGSizeMake(1.0, 28.0)]];
|
||||
|
||||
SUBMENU_INDICATOR_COLOR = [CPColor grayColor];
|
||||
|
||||
_CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0];
|
||||
_CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOffState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil;
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPMixedState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil;
|
||||
}
|
||||
|
||||
+ (id)view
|
||||
@@ -70,7 +62,7 @@
|
||||
|
||||
if (self)
|
||||
{
|
||||
_imageAndTextView = [[_CPImageAndTextView alloc] initWithFrame:CGRectMake([self valueForThemeAttribute:@"horizontal-margin"], 0.0, 0.0, 0.0)];
|
||||
_imageAndTextView = [[_CPImageAndTextView alloc] initWithFrame:CGRectMake(HORIZONTAL_MARGIN, 0.0, 0.0, 0.0)];
|
||||
|
||||
[_imageAndTextView setImagePosition:CPImageLeft];
|
||||
[_imageAndTextView setImageOffset:3.0];
|
||||
@@ -79,98 +71,86 @@
|
||||
|
||||
[self addSubview:_imageAndTextView];
|
||||
|
||||
_submenuIndicatorView = [[_CPMenuItemMenuBarSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 9.0, 6.0)];
|
||||
|
||||
[_submenuIndicatorView setAutoresizingMask:CPViewMinYMargin | CPViewMaxYMargin];
|
||||
|
||||
[self addSubview:_submenuIndicatorView];
|
||||
|
||||
[self setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
_textColor = aColor;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setTextShadowColor:(CPColor)aColor
|
||||
{
|
||||
_textShadowColor = aColor;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setHighlightTextColor:(CPColor)aColor
|
||||
{
|
||||
_highlightTextColor = aColor;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)setHighlightTextShadowColor:(CPColor)aColor
|
||||
{
|
||||
_highlightTextShadowColor = aColor;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (CPColor)textColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [CPColor lightGrayColor];
|
||||
|
||||
return _textColor || [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-text-color" forClass:_CPMenuView];
|
||||
return _textColor || [CPColor colorWithCalibratedRed:70.0 / 255.0 green:69.0 / 255.0 blue:69.0 / 255.0 alpha:1.0];
|
||||
}
|
||||
|
||||
- (CPColor)textShadowColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [CPColor clearColor];
|
||||
return [CPColor colorWithWhite:0.8 alpha:0.8];
|
||||
|
||||
return _textShadowColor || [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-text-shadow-color" forClass:_CPMenuView];
|
||||
}
|
||||
|
||||
- (CPColor)highlightTextColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [CPColor lightGrayColor];
|
||||
|
||||
return _highlightTextColor || [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-highlight-text-color" forClass:_CPMenuView];
|
||||
}
|
||||
|
||||
- (CPColor)highlightTextShadowColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [CPColor clearColor];
|
||||
|
||||
return _highlightTextShadowColor || [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-highlight-text-shadow-color" forClass:_CPMenuView];
|
||||
}
|
||||
|
||||
- (CPColor)highlightColor
|
||||
{
|
||||
return _highlightColor || [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-background-selected-color" forClass:_CPMenuView];
|
||||
return _textShadowColor || [CPColor colorWithWhite:1.0 alpha:0.8];
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
var x = [self valueForThemeAttribute:@"horizontal-margin"],
|
||||
var x = HORIZONTAL_MARGIN,
|
||||
height = 0.0;
|
||||
|
||||
[_imageAndTextView setText:[_menuItem title]];
|
||||
[_imageAndTextView setFont:[_menuItem font] || [_CPMenuBarWindow font]];
|
||||
[_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment];
|
||||
[_imageAndTextView setTextShadowOffset:CGSizeMake(0.0, 1.0)];
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_imageAndTextView setText:[_menuItem title]];
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
[_imageAndTextView setTextShadowOffset:CGSizeMake(0.0, 1.0)];
|
||||
[_imageAndTextView sizeToFit];
|
||||
|
||||
var imageAndTextViewFrame = [_imageAndTextView frame];
|
||||
|
||||
imageAndTextViewFrame.origin.x = x;
|
||||
x += CGRectGetWidth(imageAndTextViewFrame);
|
||||
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame)) + 2.0 * [self valueForThemeAttribute:@"vertical-margin"];
|
||||
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame));
|
||||
|
||||
var hasSubmenuIndicator = [_menuItem hasSubmenu] && [_menuItem action];
|
||||
|
||||
if (hasSubmenuIndicator)
|
||||
{
|
||||
[_submenuIndicatorView setHidden:NO];
|
||||
[_submenuIndicatorView setColor:[self textColor]];
|
||||
[_submenuIndicatorView setShadowColor:[self textShadowColor]];
|
||||
|
||||
var submenuViewFrame = [_submenuIndicatorView frame];
|
||||
|
||||
submenuViewFrame.origin.x = x + SUBMENU_INDICATOR_MARGIN;
|
||||
|
||||
x = CGRectGetMaxX(submenuViewFrame);
|
||||
height = MAX(height, CGRectGetHeight(submenuViewFrame));
|
||||
}
|
||||
else
|
||||
[_submenuIndicatorView setHidden:YES];
|
||||
|
||||
height += 2.0 * VERTICAL_MARGIN;
|
||||
|
||||
imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0);
|
||||
[_imageAndTextView setFrame:imageAndTextViewFrame];
|
||||
|
||||
if (hasSubmenuIndicator)
|
||||
{
|
||||
submenuViewFrame.origin.y = FLOOR((height - CGRectGetHeight(submenuViewFrame)) / 2.0) + 1.0;
|
||||
[_submenuIndicatorView setFrame:submenuViewFrame];
|
||||
}
|
||||
|
||||
[self setAutoresizesSubviews:NO];
|
||||
[self setFrameSize:CGSizeMake(x + [self valueForThemeAttribute:@"horizontal-margin"], height)];
|
||||
[self setFrameSize:CGSizeMake(x + HORIZONTAL_MARGIN, height)];
|
||||
[self setAutoresizesSubviews:YES];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)highlight:(BOOL)shouldHighlight
|
||||
@@ -179,20 +159,17 @@
|
||||
if (![_menuItem isEnabled])
|
||||
shouldHighlight = NO;
|
||||
|
||||
_shouldHighlight = shouldHighlight;
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_shouldHighlight)
|
||||
if (shouldHighlight)
|
||||
{
|
||||
if (![_menuItem _isMenuBarButton])
|
||||
[self setBackgroundColor:[self highlightColor]];
|
||||
[self setBackgroundColor:SelectionColor];
|
||||
|
||||
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
|
||||
[_imageAndTextView setTextColor:[self highlightTextColor]];
|
||||
[_imageAndTextView setTextShadowColor:[self highlightTextShadowColor]];
|
||||
[_imageAndTextView setTextColor:[CPColor whiteColor]];
|
||||
[_imageAndTextView setTextShadowColor:_CPMenuItemTextShadowColor];
|
||||
|
||||
[_submenuIndicatorView setColor:[CPColor whiteColor]];
|
||||
[_submenuIndicatorView setShadowColor:[CPColor colorWithWhite:0.1 alpha:0.7]];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -201,7 +178,60 @@
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
|
||||
[_submenuIndicatorView setColor:[self textColor]];
|
||||
[_submenuIndicatorView setShadowColor:[self textShadowColor]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPMenuItemMenuBarSubmenuIndicatorView : CPView
|
||||
{
|
||||
CPColor _color;
|
||||
CPColor _shadowColor;
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
if (_color === aColor)
|
||||
return;
|
||||
|
||||
_color = aColor;
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setShadowColor:(CPColor)aColor
|
||||
{
|
||||
if (_shadowColor === aColor)
|
||||
return;
|
||||
|
||||
_shadowColor = aColor;
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds];
|
||||
|
||||
bounds.size.height -= 1.0;
|
||||
bounds.size.width -= 2.0;
|
||||
bounds.origin.x += 1.0;
|
||||
|
||||
CGContextBeginPath(context);
|
||||
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(bounds), CGRectGetMinY(bounds));
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), CGRectGetMinY(bounds));
|
||||
CGContextAddLineToPoint(context, CGRectGetMidX(bounds), CGRectGetMaxY(bounds));
|
||||
|
||||
CGContextClosePath(context);
|
||||
|
||||
CGContextSetShadowWithColor(context, CGSizeMake(0.0, 1.0), 1.1, _shadowColor || [CPColor whiteColor]);
|
||||
CGContextSetFillColor(context, _color || [CPColor blackColor]);
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,29 +1,6 @@
|
||||
/*
|
||||
* _CPMenuItemSeparatorView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPView.j"
|
||||
|
||||
@class _CPMenuItemStandardView
|
||||
|
||||
|
||||
@implementation _CPMenuItemSeparatorView : CPView
|
||||
{
|
||||
@@ -31,9 +8,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,28 +24,15 @@
|
||||
- (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);
|
||||
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
|
||||
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
CGContextSetStrokeColor(context, [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-color" forClass:_CPMenuItemStandardView]);
|
||||
CGContextSetStrokeColor(context, [CPColor lightGrayColor]);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
@@ -1,82 +1,59 @@
|
||||
/*
|
||||
* _CPMenuItemStandardView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPControl.j"
|
||||
@import "CPImageView.j"
|
||||
@import "_CPImageAndTextView.j"
|
||||
|
||||
@class CPMenuItem
|
||||
|
||||
var LEFT_MARGIN = 3.0,
|
||||
RIGHT_MARGIN = 14.0 + 3.0,
|
||||
STATE_COLUMN_WIDTH = 14.0,
|
||||
INDENTATION_WIDTH = 17.0,
|
||||
VERTICAL_MARGIN = 4.0,
|
||||
|
||||
RIGHT_COLUMNS_MARGIN = 30.0,
|
||||
KEY_EQUIVALENT_MARGIN = 10.0;
|
||||
|
||||
var SUBMENU_INDICATOR_COLOR = nil,
|
||||
_CPMenuItemSelectionColor = nil,
|
||||
_CPMenuItemTextShadowColor = nil,
|
||||
|
||||
_CPMenuItemDefaultStateImages = [],
|
||||
_CPMenuItemDefaultStateHighlightedImages = [];
|
||||
|
||||
@implementation _CPMenuItemStandardView : CPView
|
||||
{
|
||||
CPMenuItem _menuItem @accessors(property=menuItem);
|
||||
|
||||
CPFont _font;
|
||||
CPColor _textColor;
|
||||
CPColor _textShadowColor;
|
||||
|
||||
CGSize _minSize @accessors(readonly, property=minSize);
|
||||
BOOL _isDirty;
|
||||
BOOL _highlighted;
|
||||
|
||||
CPImageView _stateView;
|
||||
_CPImageAndTextView _imageAndTextView;
|
||||
_CPImageAndTextView _keyEquivalentView;
|
||||
CPView _submenuIndicatorView;
|
||||
|
||||
BOOL _hasSubmenuIndicatorImage;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
+ (void)initialize
|
||||
{
|
||||
return "menu-item-standard-view";
|
||||
}
|
||||
if (self !== [_CPMenuItemStandardView class])
|
||||
return;
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"submenu-indicator-color": [CPNull null],
|
||||
@"menu-item-selection-color": [CPNull null],
|
||||
@"menu-item-text-shadow-color": [CPNull null],
|
||||
@"menu-item-text-color": [CPNull null],
|
||||
@"menu-item-disabled-text-color": [CPColor lightGrayColor],
|
||||
@"menu-item-default-off-state-image": [CPNull null],
|
||||
@"menu-item-default-off-state-highlighted-image": [CPNull null],
|
||||
@"menu-item-default-on-state-image": [CPNull null],
|
||||
@"menu-item-default-on-state-highlighted-image": [CPNull null],
|
||||
@"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]
|
||||
};
|
||||
SUBMENU_INDICATOR_COLOR = [CPColor grayColor];
|
||||
|
||||
_CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0];
|
||||
_CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0];
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOffState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil;
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPMixedState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil;
|
||||
}
|
||||
|
||||
+ (id)view
|
||||
@@ -86,7 +63,7 @@
|
||||
|
||||
+ (float)_standardLeftMargin
|
||||
{
|
||||
return [[CPTheme defaultTheme] valueForAttributeWithName:@"left-margin" forClass:[self class]] + [[CPTheme defaultTheme] valueForAttributeWithName:@"state-column-width" forClass:[self class]];
|
||||
return LEFT_MARGIN + STATE_COLUMN_WIDTH;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -98,7 +75,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 +93,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:SUBMENU_INDICATOR_COLOR];
|
||||
[_submenuIndicatorView setAutoresizingMask:CPViewMinXMargin];
|
||||
|
||||
[self addSubview:_submenuIndicatorView];
|
||||
@@ -148,12 +109,9 @@
|
||||
- (CPColor)textColor
|
||||
{
|
||||
if (![_menuItem isEnabled])
|
||||
return [self valueForThemeAttribute:@"menu-item-disabled-text-color"];
|
||||
return [CPColor lightGrayColor];
|
||||
|
||||
if (_highlighted)
|
||||
return [CPColor whiteColor];
|
||||
|
||||
return [self valueForThemeAttribute:@"menu-item-text-color"];
|
||||
return _textColor || [CPColor colorWithCalibratedRed:70.0 / 255.0 green:69.0 / 255.0 blue:69.0 / 255.0 alpha:1.0];
|
||||
}
|
||||
|
||||
- (CPColor)textShadowColor
|
||||
@@ -161,10 +119,7 @@
|
||||
if (![_menuItem isEnabled])
|
||||
return nil;
|
||||
|
||||
if (_highlighted)
|
||||
return nil;
|
||||
|
||||
return [self valueForThemeAttribute:@"menu-item-text-shadow-color"];
|
||||
return _textShadowColor || [CPColor colorWithWhite:1.0 alpha:0.8];
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
@@ -172,59 +127,23 @@
|
||||
_font = aFont;
|
||||
}
|
||||
|
||||
- (CPFont)font
|
||||
{
|
||||
// Menu item font is forced local font or _menuItem font or system font
|
||||
return _font || [_menuItem font] || [CPFont systemFontOfSize:CPFontCurrentSystemSize];
|
||||
}
|
||||
|
||||
// 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"],
|
||||
var x = LEFT_MARGIN + [_menuItem indentationLevel] * 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],
|
||||
verticalMargin = [self valueForThemeAttribute:@"vertical-margin" inState:CPControlSizeThemeStates[correspondingControlSize]],
|
||||
verticalOffset = [self valueForThemeAttribute:@"vertical-offset" inState:CPControlSizeThemeStates[correspondingControlSize]];
|
||||
hasStateColumn = [[_menuItem menu] showsStateColumn];
|
||||
|
||||
if (hasStateColumn)
|
||||
{
|
||||
[_stateView setHidden:NO];
|
||||
//[_stateView setImage:_CPMenuItemDefaultStateImages[[_menuItem state]] || nil];
|
||||
[_stateView setImage:_CPMenuItemDefaultStateImages[[_menuItem state]] || nil];
|
||||
|
||||
switch ([_menuItem state])
|
||||
{
|
||||
case CPOnState:
|
||||
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPOffState:
|
||||
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPMixedState:
|
||||
[_stateView setImage:[_menuItem mixedStateImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var stateViewFrameOrigin = [_stateView frameOrigin];
|
||||
|
||||
stateViewFrameOrigin.x = x;
|
||||
[_stateView setFrameOrigin:stateViewFrameOrigin];
|
||||
|
||||
x += [self valueForThemeAttribute:@"state-column-width"];
|
||||
x += 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]];
|
||||
@@ -237,24 +156,24 @@
|
||||
|
||||
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];
|
||||
|
||||
if (hasKeyEquivalent || hasSubmenu)
|
||||
x += [self valueForThemeAttribute:@"right-columns-margin"];
|
||||
x += RIGHT_COLUMNS_MARGIN;
|
||||
|
||||
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, VERTICAL_MARGIN)];
|
||||
[_keyEquivalentView sizeToFit];
|
||||
|
||||
var keyEquivalentViewFrame = [_keyEquivalentView frame];
|
||||
@@ -264,21 +183,13 @@
|
||||
height = MAX(height, CGRectGetHeight(keyEquivalentViewFrame));
|
||||
|
||||
if (hasSubmenu)
|
||||
x += [self valueForThemeAttribute:@"right-columns-margin"];
|
||||
x += RIGHT_COLUMNS_MARGIN;
|
||||
}
|
||||
else
|
||||
[_keyEquivalentView setHidden:YES];
|
||||
|
||||
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];
|
||||
@@ -291,17 +202,17 @@
|
||||
else
|
||||
[_submenuIndicatorView setHidden:YES];
|
||||
|
||||
height += 2.0 * verticalMargin;
|
||||
height += 2.0 * 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)
|
||||
[_stateView setFrameSize:CGSizeMake([self valueForThemeAttribute:@"state-column-width"], height)];
|
||||
[_stateView setFrameSize:CGSizeMake(STATE_COLUMN_WIDTH, height)];
|
||||
|
||||
if (hasKeyEquivalent)
|
||||
{
|
||||
keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0) + verticalOffset;
|
||||
keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0);
|
||||
[_keyEquivalentView setFrame:keyEquivalentViewFrame];
|
||||
}
|
||||
|
||||
@@ -311,7 +222,7 @@
|
||||
[_submenuIndicatorView setFrame:submenuViewFrame];
|
||||
}
|
||||
|
||||
_minSize = CGSizeMake(x + [self valueForThemeAttribute:@"right-margin"], height);
|
||||
_minSize = CGSizeMake(x + RIGHT_MARGIN, height);
|
||||
|
||||
[self setAutoresizesSubviews:NO];
|
||||
[self setFrameSize:_minSize];
|
||||
@@ -324,104 +235,42 @@
|
||||
if (![_menuItem isEnabled])
|
||||
return;
|
||||
|
||||
_highlighted = shouldHighlight;
|
||||
|
||||
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize];
|
||||
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_keyEquivalentView setTextColor:[self textColor]];
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
[_keyEquivalentView setTextShadowColor:[self textShadowColor]];
|
||||
|
||||
if (shouldHighlight)
|
||||
{
|
||||
[self setBackgroundColor:[self valueForThemeAttribute:@"menu-item-selection-color"]];
|
||||
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
|
||||
[self setBackgroundColor:_CPMenuItemSelectionColor];
|
||||
|
||||
if (_hasSubmenuIndicatorImage)
|
||||
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
else
|
||||
[_submenuIndicatorView setColor:[self textColor]];
|
||||
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
|
||||
[_imageAndTextView setTextColor:[CPColor whiteColor]];
|
||||
[_keyEquivalentView setTextColor:[CPColor whiteColor]];
|
||||
[_submenuIndicatorView setColor:[CPColor whiteColor]];
|
||||
|
||||
[_imageAndTextView setTextShadowColor:_CPMenuItemTextShadowColor];
|
||||
[_keyEquivalentView setTextShadowColor:_CPMenuItemTextShadowColor];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setBackgroundColor:nil];
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
|
||||
if (_hasSubmenuIndicatorImage)
|
||||
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
else
|
||||
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
|
||||
[_imageAndTextView setImage:[_menuItem image]];
|
||||
[_imageAndTextView setTextColor:[self textColor]];
|
||||
[_keyEquivalentView setTextColor:[self textColor]];
|
||||
[_submenuIndicatorView setColor:SUBMENU_INDICATOR_COLOR];
|
||||
|
||||
[_imageAndTextView setTextShadowColor:[self textShadowColor]];
|
||||
[_keyEquivalentView setTextShadowColor:[self textShadowColor]];
|
||||
}
|
||||
|
||||
if ([[_menuItem menu] showsStateColumn])
|
||||
{
|
||||
if (shouldHighlight)
|
||||
{
|
||||
switch ([_menuItem state])
|
||||
{
|
||||
case CPOnState:
|
||||
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPOffState:
|
||||
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPMixedState:
|
||||
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
[_stateView setImage:_CPMenuItemDefaultStateHighlightedImages[[_menuItem state]] || nil];
|
||||
else
|
||||
{
|
||||
switch ([_menuItem state])
|
||||
{
|
||||
case CPOnState:
|
||||
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPOffState:
|
||||
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
case CPMixedState:
|
||||
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
[_stateView setImage:_CPMenuItemDefaultStateImages[[_menuItem state]] || nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isHighlighted
|
||||
{
|
||||
return _highlighted;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
@@ -1,33 +1,22 @@
|
||||
/*
|
||||
* _CPMenuItemView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2009, 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 "CPControl.j"
|
||||
@import <AppKit/CPControl.j>
|
||||
|
||||
@import "_CPMenuItemSeparatorView.j"
|
||||
@import "_CPMenuItemStandardView.j"
|
||||
@import "_CPMenuItemMenuBarView.j"
|
||||
|
||||
@class CPMenuItem
|
||||
@global CPApp
|
||||
|
||||
var LEFT_MARGIN = 3.0,
|
||||
RIGHT_MARGIN = 16.0,
|
||||
STATE_COLUMN_WIDTH = 14.0,
|
||||
INDENTATION_WIDTH = 17.0,
|
||||
VERTICAL_MARGIN = 4.0;
|
||||
|
||||
var _CPMenuItemSelectionColor = nil,
|
||||
_CPMenuItemTextShadowColor = nil,
|
||||
|
||||
_CPMenuItemDefaultStateImages = [],
|
||||
_CPMenuItemDefaultStateHighlightedImages = [];
|
||||
|
||||
/*
|
||||
@ignore
|
||||
@@ -35,7 +24,7 @@
|
||||
@implementation _CPMenuItemView : CPView
|
||||
{
|
||||
CPMenuItem _menuItem;
|
||||
CPView _view @accessors(property=view, readonly);
|
||||
CPView _view;
|
||||
|
||||
CPFont _font;
|
||||
CPColor _textColor;
|
||||
@@ -49,22 +38,31 @@
|
||||
CPView _submenuView;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
+ (void)initialize
|
||||
{
|
||||
return "menu-item-view";
|
||||
if (self !== [_CPMenuItemView class])
|
||||
return;
|
||||
|
||||
_CPMenuItemSelectionColor = [CPColor colorWithCalibratedRed:95.0 / 255.0 green:131.0 / 255.0 blue:185.0 / 255.0 alpha:1.0];
|
||||
_CPMenuItemTextShadowColor = [CPColor colorWithCalibratedRed:26.0 / 255.0 green: 73.0 / 255.0 blue:109.0 / 255.0 alpha:1.0];
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOffState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOffState] = nil;
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnState.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPOnState] = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPMenuItem/CPMenuItemOnStateHighlighted.png"] size:CGSizeMake(14.0, 14.0)];
|
||||
|
||||
_CPMenuItemDefaultStateImages[CPMixedState] = nil;
|
||||
_CPMenuItemDefaultStateHighlightedImages[CPMixedState] = nil;
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
+ (float)leftMargin
|
||||
{
|
||||
return @{};
|
||||
return LEFT_MARGIN + STATE_COLUMN_WIDTH;
|
||||
}
|
||||
|
||||
// Not used in the Appkit
|
||||
// + (float)leftMargin
|
||||
// {
|
||||
// return LEFT_MARGIN + STATE_COLUMN_WIDTH;
|
||||
// }
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame forMenuItem:(CPMenuItem)aMenuItem
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
@@ -113,6 +111,7 @@
|
||||
_view = menuItemView;
|
||||
}
|
||||
}
|
||||
|
||||
else if ([_menuItem menu] == [CPApp mainMenu])
|
||||
{
|
||||
if (![_view isKindOfClass:[_CPMenuItemMenuBarView class]])
|
||||
@@ -220,63 +219,8 @@
|
||||
return [_menuItem isEnabled] ? (_textShadowColor ? _textShadowColor : [CPColor colorWithWhite:1.0 alpha:0.8]) : [CPColor colorWithWhite:0.8 alpha:0.8];
|
||||
}
|
||||
|
||||
- (void)setParentMenuHighlightColor:(CPColor)aColor
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setHighlightColor:)])
|
||||
[_view setHighlightColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setParentMenuHighlightTextColor:(CPColor)aColor
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setHighlightTextColor:)])
|
||||
[_view setHighlightTextColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setParentMenuHighlightTextShadowColor:(CPColor)aColor
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setHighlightTextShadowColor:)])
|
||||
[_view setHighlightTextShadowColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setParentMenuTextColor:(CPColor)aColor
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setTextColor:)])
|
||||
[_view setTextColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setParentMenuTextShadowColor:(CPColor)aColor
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setTextShadowColor:)])
|
||||
[_view setTextShadowColor:aColor];
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -301,6 +245,8 @@
|
||||
CGContextMoveToPoint(context, 1.0, 4.0);
|
||||
CGContextAddLineToPoint(context, 9.0, 4.0);
|
||||
CGContextAddLineToPoint(context, 5.0, 8.0);
|
||||
CGContextAddLineToPoint(context, 1.0, 4.0);
|
||||
|
||||
CGContextClosePath(context);
|
||||
|
||||
CGContextSetFillColor(context, _color);
|
||||
|
||||
+34
-182
@@ -22,13 +22,8 @@
|
||||
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPCountedSet.j>
|
||||
@import <Foundation/_CPCollectionKVCOperators.j>
|
||||
|
||||
@import "CPController.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
|
||||
@class _CPManagedProxy
|
||||
@class CPPredicate;
|
||||
|
||||
/*!
|
||||
@class
|
||||
@@ -50,14 +45,11 @@
|
||||
|
||||
BOOL _isEditable;
|
||||
BOOL _automaticallyPreparesContent;
|
||||
BOOL _usesLazyFetching @accessors(getter=usesLazyFetching, setter=setUsesLazyFetching:);
|
||||
BOOL _isUsingManagedProxy;
|
||||
_CPManagedProxy _managedProxy;
|
||||
|
||||
CPCountedSet _observedKeys;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
+ (id)initialize
|
||||
{
|
||||
if (self !== [CPObjectController class])
|
||||
return;
|
||||
@@ -112,13 +104,11 @@
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
[self setContent:aContent];
|
||||
[self setEditable:YES];
|
||||
[self setObjectClass:[CPMutableDictionary class]];
|
||||
|
||||
_observedKeys = [[CPCountedSet alloc] init];
|
||||
_selection = [[CPControllerSelectionProxy alloc] initWithController:self];
|
||||
|
||||
[self setContent:aContent];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -185,52 +175,6 @@
|
||||
return _automaticallyPreparesContent;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the entity name the controller handles.
|
||||
|
||||
@param CPString newEntityName - The new entity name.
|
||||
*/
|
||||
- (void)setEntityName:(CPString)newEntityName
|
||||
{
|
||||
if (!_managedProxy)
|
||||
{
|
||||
_managedProxy = [[_CPManagedProxy alloc] init];
|
||||
_isUsingManagedProxy = YES;
|
||||
}
|
||||
|
||||
[_managedProxy setEntityName:newEntityName];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the entity name.
|
||||
|
||||
@return CPString - The name of the entity.
|
||||
*/
|
||||
- (CPString)entityName
|
||||
{
|
||||
return [_managedProxy entityName];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the predicate used to fetch content.
|
||||
|
||||
@param CPPredicate newPredicate - The fetch predicate.
|
||||
*/
|
||||
- (void)setFetchPredicate:(CPPredicate)newPredicate
|
||||
{
|
||||
[_managedProxy setFetchPredicate:newPredicate];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the fetch predicate.
|
||||
|
||||
@return CPPredicate - The predicate used to fetch content.
|
||||
*/
|
||||
- (CPPredicate)fetchPredicate
|
||||
{
|
||||
return [_managedProxy fetchPredicate];
|
||||
}
|
||||
|
||||
/*!
|
||||
Overridden by a subclass that require control over the creation of new objects.
|
||||
*/
|
||||
@@ -241,7 +185,6 @@
|
||||
|
||||
/*!
|
||||
Sets the object class when creating new objects.
|
||||
|
||||
@param Class - the class of new objects that will be created.
|
||||
*/
|
||||
- (void)setObjectClass:(Class)aClass
|
||||
@@ -384,35 +327,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.
|
||||
*/
|
||||
@@ -438,10 +359,7 @@
|
||||
var CPObjectControllerContentKey = @"CPObjectControllerContentKey",
|
||||
CPObjectControllerObjectClassNameKey = @"CPObjectControllerObjectClassNameKey",
|
||||
CPObjectControllerIsEditableKey = @"CPObjectControllerIsEditableKey",
|
||||
CPObjectControllerAutomaticallyPreparesContentKey = @"CPObjectControllerAutomaticallyPreparesContentKey",
|
||||
CPObjectControllerUsesLazyFetchingKey = @"CPObjectControllerUsesLazyFetchingKey",
|
||||
CPObjectControllerIsUsingManagedProxyKey = @"CPObjectControllerIsUsingManagedProxyKey",
|
||||
CPObjectControllerManagedProxyKey = @"CPObjectControllerManagedProxyKey";
|
||||
CPObjectControllerAutomaticallyPreparesContentKey = @"CPObjectControllerAutomaticallyPreparesContentKey";
|
||||
|
||||
@implementation CPObjectController (CPCoding)
|
||||
|
||||
@@ -452,18 +370,12 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
if (self)
|
||||
{
|
||||
var objectClassName = [aCoder decodeObjectForKey:CPObjectControllerObjectClassNameKey],
|
||||
objectClass = CPClassFromString(objectClassName),
|
||||
content = [aCoder decodeObjectForKey:CPObjectControllerContentKey];
|
||||
objectClass = CPClassFromString(objectClassName);
|
||||
|
||||
[self setObjectClass:objectClass || [CPMutableDictionary class]];
|
||||
[self setEditable:[aCoder decodeBoolForKey:CPObjectControllerIsEditableKey]];
|
||||
[self setAutomaticallyPreparesContent:[aCoder decodeBoolForKey:CPObjectControllerAutomaticallyPreparesContentKey]];
|
||||
[self setUsesLazyFetching:[aCoder decodeBoolForKey:CPObjectControllerUsesLazyFetchingKey]];
|
||||
_isUsingManagedProxy = [aCoder decodeBoolForKey:CPObjectControllerIsUsingManagedProxyKey];
|
||||
_managedProxy = [aCoder decodeObjectForKey:CPObjectControllerManagedProxyKey];
|
||||
|
||||
if (content != nil)
|
||||
[self setContent:content];
|
||||
[self setContent:[aCoder decodeObjectForKey:CPObjectControllerContentKey]];
|
||||
|
||||
_observedKeys = [[CPCountedSet alloc] init];
|
||||
}
|
||||
@@ -482,11 +394,6 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
[aCoder encodeBool:[self isEditable] forKey:CPObjectControllerIsEditableKey];
|
||||
[aCoder encodeBool:[self automaticallyPreparesContent] forKey:CPObjectControllerAutomaticallyPreparesContentKey];
|
||||
[aCoder encodeBool:[self usesLazyFetching] forKey:CPObjectControllerUsesLazyFetchingKey];
|
||||
[aCoder encodeBool:_isUsingManagedProxy forKey:CPObjectControllerIsUsingManagedProxyKey];
|
||||
|
||||
if (_managedProxy)
|
||||
[aCoder encodeObject:_managedProxy forKey:CPObjectControllerManagedProxyKey];
|
||||
}
|
||||
|
||||
- (void)awakeFromCib
|
||||
@@ -548,13 +455,13 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (BOOL)isEqual:(id)anObject
|
||||
{
|
||||
if (self === anObject)
|
||||
return YES;
|
||||
if ([anObject class] === [self class])
|
||||
{
|
||||
if (anObject._observer === _observer && [anObject._keyPath isEqual:_keyPath] && [anObject._object isEqual:_object])
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (!anObject || [anObject class] !== [self class] || anObject._observer !== _observer || anObject._keyPath !== _keyPath || anObject._object !== _object)
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)change context:(id)context
|
||||
@@ -609,12 +516,8 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (void)addObserver:(id)anObserver forKeyPath:(CPString)aKeyPath options:(CPKeyValueObservingOptions)options context:(id)context
|
||||
{
|
||||
if (aKeyPath.charAt(0) === "@")
|
||||
if (aKeyPath.indexOf("@") === 0)
|
||||
{
|
||||
// Simple collection operators are scalar and can't be proxied
|
||||
if ([_CPCollectionKVCOperator isSimpleCollectionOperator:aKeyPath])
|
||||
return;
|
||||
|
||||
var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObserver object:self];
|
||||
|
||||
proxy._options = options;
|
||||
@@ -637,12 +540,8 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (void)removeObserver:(id)anObserver forKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
if (aKeyPath.charAt(0) === "@")
|
||||
if (aKeyPath.indexOf("@") === 0)
|
||||
{
|
||||
// Simple collection operators are scalar and can't be proxied
|
||||
if ([_CPCollectionKVCOperator isSimpleCollectionOperator:aKeyPath])
|
||||
return;
|
||||
|
||||
var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObserver object:self],
|
||||
index = [_observationProxies indexOfObject:proxy];
|
||||
|
||||
@@ -661,13 +560,13 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
}
|
||||
}
|
||||
|
||||
- (void)insertObject:(id)anObject atIndex:(CPUInteger)anIndex
|
||||
- (void)insertObject:(id)anObject atIndex:(unsigned)anIndex
|
||||
{
|
||||
for (var i = 0, count = [_observationProxies count]; i < count; i++)
|
||||
{
|
||||
var proxy = [_observationProxies objectAtIndex:i],
|
||||
keyPath = [proxy keyPath],
|
||||
operator = keyPath.charAt(0) === ".";
|
||||
operator = keyPath.indexOf(".") === 0;
|
||||
|
||||
if (operator)
|
||||
[self willChangeValueForKey:keyPath];
|
||||
@@ -681,7 +580,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
[super insertObject:anObject atIndex:anIndex];
|
||||
}
|
||||
|
||||
- (void)removeObjectAtIndex:(CPUInteger)anIndex
|
||||
- (void)removeObjectAtIndex:(unsigned)anIndex
|
||||
{
|
||||
var currentObject = [self objectAtIndex:anIndex];
|
||||
|
||||
@@ -689,7 +588,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
{
|
||||
var proxy = [_observationProxies objectAtIndex:i],
|
||||
keyPath = [proxy keyPath],
|
||||
operator = keyPath.charAt(0) === ".";
|
||||
operator = keyPath.indexOf(".") === 0;
|
||||
|
||||
if (operator)
|
||||
[self willChangeValueForKey:keyPath];
|
||||
@@ -703,7 +602,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
[super removeObjectAtIndex:anIndex];
|
||||
}
|
||||
|
||||
- (CPArray)objectsAtIndexes:(CPIndexSet)theIndexes
|
||||
- (_CPObservableArray)objectsAtIndexes:(CPIndexSet)theIndexes
|
||||
{
|
||||
return [_CPObservableArray arrayWithArray:[super objectsAtIndexes:theIndexes]];
|
||||
}
|
||||
@@ -718,7 +617,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
[self removeObjectAtIndex:[self count]];
|
||||
}
|
||||
|
||||
- (void)replaceObjectAtIndex:(CPUInteger)anIndex withObject:(id)anObject
|
||||
- (void)replaceObjectAtIndex:(unsigned)anIndex withObject:(id)anObject
|
||||
{
|
||||
var currentObject = [self objectAtIndex:anIndex];
|
||||
|
||||
@@ -726,7 +625,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
{
|
||||
var proxy = [_observationProxies objectAtIndex:i],
|
||||
keyPath = [proxy keyPath],
|
||||
operator = keyPath.charAt(0) === ".";
|
||||
operator = keyPath.indexOf(".") === 0;
|
||||
|
||||
if (operator)
|
||||
[self willChangeValueForKey:keyPath];
|
||||
@@ -758,7 +657,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_cachedValues = @{};
|
||||
_cachedValues = [CPDictionary dictionary];
|
||||
_observationProxies = [CPArray array];
|
||||
_controller = aController;
|
||||
_observedObjectsByKeyPath = {};
|
||||
@@ -792,7 +691,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;
|
||||
@@ -800,19 +699,12 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (id)valueForKeyPath:(CPString)theKeyPath
|
||||
{
|
||||
var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath];
|
||||
var values = [[_controller selectedObjects] valueForKeyPath:theKeyPath],
|
||||
value = [self _controllerMarkerForValues:values];
|
||||
|
||||
// Simple collection operators like @count return a scalar value, not an array or set
|
||||
if ([values isKindOfClass:CPArray] || [values isKindOfClass:CPSet])
|
||||
{
|
||||
var value = [self _controllerMarkerForValues:values];
|
||||
[_cachedValues setObject:value forKey:theKeyPath];
|
||||
[_cachedValues setObject:value forKey:theKeyPath];
|
||||
|
||||
// Apple's implementation returns nil instead of CPNullMarker
|
||||
return value === CPNullMarker ? nil : value;
|
||||
}
|
||||
else
|
||||
return values;
|
||||
return value;
|
||||
}
|
||||
|
||||
- (id)valueForKey:(CPString)theKeyPath
|
||||
@@ -839,7 +731,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (void)setValue:(id)theValue forKey:(CPString)theKeyPath
|
||||
{
|
||||
[self setValue:theValue forKeyPath:theKeyPath];
|
||||
[self setValue:theKeyPath forKeyPath:theKeyPath];
|
||||
}
|
||||
|
||||
- (unsigned)count
|
||||
@@ -898,55 +790,15 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
|
||||
|
||||
- (void)removeObserver:(id)anObject forKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
[_observationProxies enumerateObjectsUsingBlock:function(aProxy, idx, stop)
|
||||
{
|
||||
if (aProxy._object === self && aProxy._keyPath == aKeyPath && aProxy._observer === anObject)
|
||||
{
|
||||
var observedObjects = _observedObjectsByKeyPath[aKeyPath];
|
||||
var proxy = [[_CPObservationProxy alloc] initWithKeyPath:aKeyPath observer:anObject object:self],
|
||||
index = [_observationProxies indexOfObject:proxy];
|
||||
|
||||
[observedObjects removeObserver:aProxy forKeyPath:aKeyPath];
|
||||
[_observationProxies removeObjectAtIndex:idx];
|
||||
var observedObjects = _observedObjectsByKeyPath[aKeyPath];
|
||||
[observedObjects removeObserver:[_observationProxies objectAtIndex:index] forKeyPath:aKeyPath];
|
||||
|
||||
_observedObjectsByKeyPath[aKeyPath] = nil;
|
||||
[_observationProxies removeObjectAtIndex:index];
|
||||
|
||||
stop(YES);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation _CPManagedProxy : CPObject
|
||||
{
|
||||
CPString _entityName @accessors(getter=entityName, setter=setEntityName:);
|
||||
CPPredicate _fetchPredicate @accessors(getter=fetchPredicate, setter=setFetchPredicate:);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPManagedProxyEntityNameKey = @"CPManagedProxyEntityNameKey",
|
||||
CPManagedProxyFetchPredicateKey = @"CPManagedProxyFetchPredicateKey";
|
||||
|
||||
@implementation _CPManagedProxy (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setEntityName:[aCoder decodeObjectForKey:CPManagedProxyEntityNameKey]];
|
||||
[self setFetchPredicate:[aCoder decodeObjectForKey:CPManagedProxyFetchPredicateKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:[self entityName] forKey:CPManagedProxyEntityNameKey];
|
||||
[aCoder encodeObject:[self fetchPredicate] forKey:CPManagedProxyFetchPredicateKey];
|
||||
_observedObjects = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -47,9 +47,9 @@
|
||||
var options = { directoryURL: [self directoryURL],
|
||||
canChooseFiles: [self canChooseFiles],
|
||||
canChooseDirectories: [self canChooseDirectories],
|
||||
allowsMultipleSelection: [self allowsMultipleSelection] },
|
||||
allowsMultipleSelection: [self allowsMultipleSelection] };
|
||||
|
||||
result = window.cpOpenPanel(options);
|
||||
var result = window.cpOpenPanel(options);
|
||||
|
||||
_URLs = result.URLs;
|
||||
|
||||
|
||||
+460
-1034
File diff suppressed because it is too large
Load Diff
+2
-28
@@ -22,13 +22,10 @@
|
||||
|
||||
@import "CPWindow.j"
|
||||
|
||||
@global CPApp
|
||||
|
||||
CPOKButton = 1;
|
||||
CPCancelButton = 0;
|
||||
|
||||
CPDocModalWindowMask = 1 << 6;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPPanel
|
||||
@@ -54,6 +51,8 @@ CPDocModalWindowMask = 1 << 6;
|
||||
@global
|
||||
@class CPWindow
|
||||
*/
|
||||
CPDocModalWindowMask = 1 << 6;
|
||||
|
||||
@implementation CPPanel : CPWindow
|
||||
{
|
||||
BOOL _becomesKeyOnlyIfNeeded;
|
||||
@@ -115,29 +114,4 @@ CPDocModalWindowMask = 1 << 6;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeKeyWindow
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Overrides
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
*/
|
||||
- (void)cancelOperation:(id)sender
|
||||
{
|
||||
if ([[CPApp currentEvent] _couldBeKeyEquivalent] && [self performKeyEquivalent:[CPApp currentEvent]])
|
||||
return;
|
||||
|
||||
[[self firstResponder] tryToPerform:@selector(cancel:) with:self];
|
||||
}
|
||||
|
||||
- (void)cancel:(id)sender
|
||||
{
|
||||
[self performClose:sender];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+79
-25
@@ -22,13 +22,9 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPData.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPPropertyListSerialization.j>
|
||||
|
||||
@class CPWebScriptObject
|
||||
|
||||
@typedef DataTransfer
|
||||
|
||||
CPGeneralPboard = @"CPGeneralPboard";
|
||||
CPFontPboard = @"CPFontPboard";
|
||||
@@ -44,9 +40,6 @@ CPStringPboardType = @"CPStringPboardType";
|
||||
CPURLPboardType = @"CPURLPboardType";
|
||||
CPImagesPboardType = @"CPImagesPboardType";
|
||||
CPVideosPboardType = @"CPVideosPboardType";
|
||||
CPRTFPboardType = @"CPRTFPboardType";
|
||||
_CPSmartPboardType = @"_CPSmartPboardType";
|
||||
_CPASPboardType = @"_CPASPboardType";
|
||||
|
||||
UTF8PboardType = @"public.utf8-plain-text";
|
||||
|
||||
@@ -54,7 +47,8 @@ UTF8PboardType = @"public.utf8-plain-text";
|
||||
CPImagePboardType = @"CPImagePboardType";
|
||||
|
||||
|
||||
var CPPasteboards = nil;
|
||||
var CPPasteboards = nil,
|
||||
supportsNativePasteboard = NO;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -70,6 +64,8 @@ var CPPasteboards = nil;
|
||||
|
||||
unsigned _changeCount;
|
||||
CPString _stateUID;
|
||||
|
||||
WebScriptObject _nativePasteboard;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -82,7 +78,10 @@ var CPPasteboards = nil;
|
||||
|
||||
[self setVersion:1.0];
|
||||
|
||||
CPPasteboards = @{};
|
||||
CPPasteboards = [CPDictionary dictionary];
|
||||
|
||||
if (typeof window.cpPasteboardWithName !== "undefined")
|
||||
supportsNativePasteboard = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -118,13 +117,19 @@ var CPPasteboards = nil;
|
||||
|
||||
if (self)
|
||||
{
|
||||
// _name = aName;
|
||||
_name = aName;
|
||||
_types = [];
|
||||
|
||||
_owners = @{};
|
||||
_provided = @{};
|
||||
_owners = [CPDictionary dictionary];
|
||||
_provided = [CPDictionary dictionary];
|
||||
|
||||
_changeCount = 0;
|
||||
|
||||
if (supportsNativePasteboard)
|
||||
{
|
||||
_nativePasteboard = window.cpPasteboardWithName(aName);
|
||||
[self _synchronizePasteboard];
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -154,6 +159,15 @@ var CPPasteboards = nil;
|
||||
[_owners setObject:anOwner forKey:type];
|
||||
}
|
||||
|
||||
if (_nativePasteboard)
|
||||
{
|
||||
var nativeTypes = [types copy];
|
||||
if ([types containsObject:CPStringPboardType])
|
||||
nativeTypes.push(UTF8PboardType);
|
||||
|
||||
_nativePasteboard.addTypes_(nativeTypes);
|
||||
}
|
||||
|
||||
return ++_changeCount;
|
||||
}
|
||||
|
||||
@@ -164,19 +178,32 @@ var CPPasteboards = nil;
|
||||
@return the pasteboard's change count
|
||||
*/
|
||||
- (unsigned)declareTypes:(CPArray)types owner:(id)anOwner
|
||||
{
|
||||
[self _declareTypes:types owner:anOwner updateNativePasteboard:YES];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (unsigned)_declareTypes:(CPArray)types owner:(id)anOwner updateNativePasteboard:(BOOL)shouldUpdate
|
||||
{
|
||||
[_types setArray:types];
|
||||
|
||||
_owners = @{};
|
||||
_provided = @{};
|
||||
_owners = [CPDictionary dictionary];
|
||||
_provided = [CPDictionary dictionary];
|
||||
|
||||
if (anOwner)
|
||||
var count = _types.length;
|
||||
|
||||
while (count--)
|
||||
[_owners setObject:anOwner forKey:_types[count]];
|
||||
|
||||
if (_nativePasteboard && shouldUpdate)
|
||||
{
|
||||
var count = _types.length;
|
||||
while (count--)
|
||||
[_owners setObject:anOwner forKey:_types[count]];
|
||||
}
|
||||
var nativeTypes = [types copy];
|
||||
if ([types containsObject:CPStringPboardType])
|
||||
nativeTypes.push(UTF8PboardType);
|
||||
|
||||
_nativePasteboard.declareTypes_(nativeTypes);
|
||||
_changeCount = _nativePasteboard.changeCount();
|
||||
}
|
||||
return ++_changeCount;
|
||||
}
|
||||
|
||||
@@ -215,23 +242,19 @@ var CPPasteboards = nil;
|
||||
*/
|
||||
- (void)setString:(CPString)aString forType:(CPString)aType
|
||||
{
|
||||
// Putting a non-string on the string pasteboard can lead to strange crashes.
|
||||
if (aString && aString.isa && ![aString isKindOfClass:CPString])
|
||||
[CPException raise:CPInvalidArgumentException reason:"CPPasteboard setString:forType: must be called with a string."];
|
||||
|
||||
[self setPropertyList:aString forType:aType];
|
||||
}
|
||||
|
||||
// Determining Types
|
||||
/*!
|
||||
Checks the pasteboard's types for a match with the types listed in the specified array. The array should
|
||||
Checks the pasteboard's types for a match with the types listen in the specified array. The array should
|
||||
be ordered by the requestor's most preferred data type first.
|
||||
@param anArray an array of requested types ordered by preference
|
||||
@return the highest match with the pasteboard's supported types or \c nil if no match was found
|
||||
*/
|
||||
- (CPString)availableTypeFromArray:(CPArray)anArray
|
||||
{
|
||||
return [anArray firstObjectCommonWithArray:[self types]];
|
||||
return [[self types] firstObjectCommonWithArray:anArray];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -239,6 +262,7 @@ var CPPasteboards = nil;
|
||||
*/
|
||||
- (CPArray)types
|
||||
{
|
||||
[self _synchronizePasteboard];
|
||||
return _types;
|
||||
}
|
||||
|
||||
@@ -277,6 +301,36 @@ var CPPasteboards = nil;
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)_synchronizePasteboard
|
||||
{
|
||||
if (_nativePasteboard && _nativePasteboard.changeCount() > _changeCount)
|
||||
{
|
||||
var nativeTypes = [_nativePasteboard.types() copy];
|
||||
if ([nativeTypes containsObject:UTF8PboardType])
|
||||
nativeTypes.push(CPStringPboardType);
|
||||
|
||||
[self _declareTypes:nativeTypes owner:self updateNativePasteboard:NO];
|
||||
|
||||
_changeCount = _nativePasteboard.changeCount();
|
||||
}
|
||||
}
|
||||
|
||||
/*! @ignore
|
||||
method provided for integration with native pasteboard
|
||||
*/
|
||||
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
|
||||
{
|
||||
if (aType === CPStringPboardType)
|
||||
{
|
||||
var string = _nativePasteboard.stringForType_(UTF8PboardType);
|
||||
|
||||
[self setString:string forType:CPStringPboardType];
|
||||
[self setString:string forType:UTF8PboardType];
|
||||
}
|
||||
else
|
||||
[self setString:_nativePasteboard.stringForType_(aType) forType:aType];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the property list for the specified data type
|
||||
@param aType the requested data type
|
||||
|
||||
+56
-75
@@ -20,12 +20,15 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import <Foundation/CPGeometry.j>
|
||||
|
||||
@import "CPButton.j"
|
||||
@import "CPKeyValueBinding.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPMenuItem.j"
|
||||
|
||||
var VISIBLE_MARGIN = 7.0;
|
||||
|
||||
CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
|
||||
/*!
|
||||
@@ -45,13 +48,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
return "popup-button";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"menu-offset": CGSizeMake(0, 0)
|
||||
};
|
||||
}
|
||||
|
||||
+ (CPSet)keyPathsForValuesAffectingSelectedIndex
|
||||
{
|
||||
return [CPSet setWithObject:@"objectValue"];
|
||||
@@ -284,7 +280,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
Selects the item at the specified index
|
||||
@param anIndex the index of the item to select
|
||||
*/
|
||||
- (void)setObjectValue:(id)anIndex
|
||||
- (void)setObjectValue:(int)anIndex
|
||||
{
|
||||
var indexOfSelectedItem = [self objectValue];
|
||||
|
||||
@@ -349,7 +345,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
Returns the item at the specified index or \c nil if the item does not exist.
|
||||
@param anIndex the index of the item to obtain
|
||||
*/
|
||||
- (CPMenuItem)itemAtIndex:(CPUInteger)anIndex
|
||||
- (CPMenuItem)itemAtIndex:(unsigned)anIndex
|
||||
{
|
||||
return [[self menu] itemAtIndex:anIndex];
|
||||
}
|
||||
@@ -358,7 +354,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
Returns the title of the item at the specified index or \c nil if no item exists.
|
||||
@param anIndex the index of the item
|
||||
*/
|
||||
- (CPString)itemTitleAtIndex:(CPUInteger)anIndex
|
||||
- (CPString)itemTitleAtIndex:(unsigned)anIndex
|
||||
{
|
||||
return [[[self menu] itemAtIndex:anIndex] title];
|
||||
}
|
||||
@@ -368,10 +364,15 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
*/
|
||||
- (CPArray)itemTitles
|
||||
{
|
||||
return [[self itemArray] arrayByApplyingBlock:function(item)
|
||||
{
|
||||
return [item title];
|
||||
}];
|
||||
var titles = [],
|
||||
items = [self itemArray],
|
||||
index = 0,
|
||||
count = [items count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
titles.push([items[index] title]);
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -498,17 +499,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];
|
||||
@@ -675,15 +668,10 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
if (![self isEnabled] || ![self numberOfItems])
|
||||
return;
|
||||
|
||||
var menu = [self menu];
|
||||
|
||||
// Don't reopen the menu based on the same click which caused it to close, e.g. a click on this button.
|
||||
if (menu._lastCloseEvent === anEvent)
|
||||
return;
|
||||
|
||||
[self highlight:YES];
|
||||
|
||||
var bounds = [self bounds],
|
||||
var menu = [self menu],
|
||||
bounds = [self bounds],
|
||||
minimumWidth = CGRectGetWidth(bounds);
|
||||
|
||||
// FIXME: setFont: should set the font on the menu.
|
||||
@@ -692,16 +680,14 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
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));
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -783,6 +769,13 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
[[self selectedItem] setState:CPOffState];
|
||||
}
|
||||
|
||||
- (void)_reverseSetBinding
|
||||
{
|
||||
[_CPPopUpButtonSelectionBinder reverseSetValueForObject:self];
|
||||
|
||||
[super _reverseSetBinding];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPPopUpButton (BindingSupport)
|
||||
@@ -805,20 +798,6 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
}
|
||||
|
||||
+ (BOOL)isBindingExclusive:(CPString)aBinding
|
||||
{
|
||||
return (aBinding == CPSelectedIndexBinding ||
|
||||
aBinding == CPSelectedTagBinding ||
|
||||
aBinding == CPSelectedValueBinding);
|
||||
}
|
||||
|
||||
- (void)_reverseSetBinding
|
||||
{
|
||||
[_CPPopUpButtonSelectionBinder _reverseSetValueFromExclusiveBinderForObject:self];
|
||||
|
||||
[super _reverseSetBinding];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPPopUpButtonContentBinder : CPBinder
|
||||
@@ -855,7 +834,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
[self _setContentValuesIfNeeded:contentArray];
|
||||
}
|
||||
|
||||
- (id)valueForBinding:(CPString)aBinding
|
||||
- (void)valueForBinding:(CPString)aBinding
|
||||
{
|
||||
return [self _content];
|
||||
}
|
||||
@@ -864,17 +843,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
{
|
||||
var count = [aValue count],
|
||||
options = [_info objectForKey:CPOptionsKey],
|
||||
offset = [self _getInsertNullOffset],
|
||||
selectedBindingInfo = [_source infoForBinding:CPSelectedObjectBinding],
|
||||
selectedObject = nil;
|
||||
|
||||
if (selectedBindingInfo)
|
||||
{
|
||||
var destination = [selectedBindingInfo objectForKey:CPObservedObjectKey],
|
||||
keyPath = [selectedBindingInfo objectForKey:CPObservedKeyPathKey];
|
||||
|
||||
selectedObject = [destination valueForKeyPath:keyPath];
|
||||
}
|
||||
offset = [self _getInsertNullOffset];
|
||||
|
||||
if (count + offset != [_source numberOfItems])
|
||||
{
|
||||
@@ -885,19 +854,9 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var item = [[CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil],
|
||||
itemValue = [aValue objectAtIndex:i];
|
||||
|
||||
[self _setValue:itemValue forItem:item withOptions:options];
|
||||
var item = [[CPMenuItem alloc] initWithTitle:@"" action:NULL keyEquivalent:nil];
|
||||
[self _setValue:[aValue objectAtIndex:i] forItem:item withOptions:options];
|
||||
[_source addItem:item];
|
||||
|
||||
// Select this item if it is the one selected by the selected object binding
|
||||
// This is needed if the selected object binding is set before the items
|
||||
// from the content binding
|
||||
if (itemValue === selectedObject)
|
||||
{
|
||||
[_source setSelectedIndex:[_source numberOfItems] - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -947,7 +906,7 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setValue:(CPArray)aValue forBinding:(CPString)aBinding
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
[super _setContent:aValue];
|
||||
}
|
||||
@@ -968,8 +927,30 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
|
||||
|
||||
@end
|
||||
|
||||
var binderForObject = {};
|
||||
|
||||
@implementation _CPPopUpButtonSelectionBinder : CPBinder
|
||||
{
|
||||
CPString _selectionBinding @accessors;
|
||||
}
|
||||
|
||||
- (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];
|
||||
|
||||
if (self)
|
||||
{
|
||||
binderForObject[[aSource UID]] = self;
|
||||
_selectionBinding = aName;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (void)reverseSetValueForObject:(id)aSource
|
||||
{
|
||||
var binder = binderForObject[[aSource UID]];
|
||||
[binder reverseSetValueFor:[binder _selectionBinding]];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
||||
|
||||
+76
-118
@@ -28,19 +28,8 @@
|
||||
@import "CPImageView.j"
|
||||
@import "CPResponder.j"
|
||||
@import "CPView.j"
|
||||
@import "CPViewController.j"
|
||||
@import "_CPPopoverWindow.j"
|
||||
@import "_CPAttachedWindow.j"
|
||||
|
||||
@protocol CPPopoverDelegate <CPObject>
|
||||
|
||||
@optional
|
||||
- (BOOL)popoverShouldClose:(CPPopover)aPopover;
|
||||
- (void)popoverWillClose:(CPPopover)aPopover;
|
||||
- (void)popoverDidClose:(CPPopover)aPopover;
|
||||
- (void)popoverWillShow:(CPPopover)aPopover;
|
||||
- (void)popoverDidShow:(CPPopover)aPopover;
|
||||
|
||||
@end
|
||||
|
||||
CPPopoverBehaviorApplicationDefined = 0;
|
||||
CPPopoverBehaviorTransient = 1;
|
||||
@@ -56,7 +45,7 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
/*! @ingroup appkit
|
||||
@class CPPopover
|
||||
|
||||
This class represent a widget that displays a popover
|
||||
This class represent a widget that displays a attached
|
||||
view relative to another one.
|
||||
|
||||
Delegate can implement:
|
||||
@@ -71,21 +60,20 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
*/
|
||||
@implementation CPPopover : CPResponder
|
||||
{
|
||||
@outlet CPViewController _contentViewController @accessors(property=contentViewController);
|
||||
@outlet id <CPPopoverDelegate> _delegate @accessors(getter=delegate);
|
||||
@outlet CPViewController _contentViewController @accessors(property=contentViewController);
|
||||
@outlet id _delegate @accessors(getter=delegate);
|
||||
|
||||
BOOL _animates @accessors(getter=animates);
|
||||
int _appearance @accessors(property=appearance);
|
||||
int _behavior @accessors(getter=behavior);
|
||||
BOOL _animates @accessors(property=animates);
|
||||
int _appearance @accessors(property=appearance);
|
||||
int _behavior @accessors(getter=behavior);
|
||||
|
||||
_CPPopoverWindow _popoverWindow;
|
||||
CPView _positioningView;
|
||||
int _implementedDelegateMethods;
|
||||
_CPAttachedWindow _attachedWindow;
|
||||
int _implementedDelegateMethods;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Initialization
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
/*!
|
||||
Initialize the CPPopover witn default values
|
||||
@@ -105,8 +93,8 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Getters / Setters
|
||||
#pragma mark -
|
||||
#pragma mark Getters / Setters
|
||||
|
||||
/*!
|
||||
Returns the current rect of the popover
|
||||
@@ -115,10 +103,9 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
*/
|
||||
- (CGRect)positioningRect
|
||||
{
|
||||
if (![_popoverWindow isVisible])
|
||||
if (![_attachedWindow isVisible])
|
||||
return CGRectMakeZero();
|
||||
|
||||
return [_popoverWindow frame];
|
||||
return [_attachedWindow frame];
|
||||
}
|
||||
|
||||
/*! Sets the frame of the popover
|
||||
@@ -126,10 +113,9 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
*/
|
||||
- (void)setPositioningRect:(CGRect)aRect
|
||||
{
|
||||
if (![_popoverWindow isVisible])
|
||||
if (![_attachedWindow isVisible])
|
||||
return;
|
||||
|
||||
[_popoverWindow setFrame:aRect];
|
||||
[_attachedWindow setFrame:aRect];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -139,6 +125,8 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
*/
|
||||
- (CGSize)contentSize
|
||||
{
|
||||
if (![_attachedWindow isVisible])
|
||||
return CGRectMakeZero();
|
||||
return [[_contentViewController view] frameSize];
|
||||
}
|
||||
|
||||
@@ -147,12 +135,9 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
|
||||
@param aSize the desired size
|
||||
*/
|
||||
- (void)setContentSize:(CGSize)aSize
|
||||
- (void)setContentSize:(CPSize)aSize
|
||||
{
|
||||
if (!_popoverWindow)
|
||||
[[_contentViewController view] setFrameSize:aSize];
|
||||
else
|
||||
[_popoverWindow updateFrameWithSize:aSize];
|
||||
[[_contentViewController view] setFrameSize:aSize];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -162,21 +147,7 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
|
||||
*/
|
||||
- (BOOL)isShown
|
||||
{
|
||||
return [_popoverWindow isVisible];
|
||||
}
|
||||
|
||||
/*!
|
||||
Set if the popover should animate for open/close actions.
|
||||
|
||||
@param shouldAnimate if YES, the popover will be animated.
|
||||
*/
|
||||
- (void)setAnimates:(BOOL)shouldAnimate
|
||||
{
|
||||
if (_animates == shouldAnimate)
|
||||
return;
|
||||
|
||||
_animates = shouldAnimate;
|
||||
[_popoverWindow setAnimates:_animates];
|
||||
return [_attachedWindow isVisible];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -193,10 +164,10 @@ Set the behavior of the CPPopover. It can be:
|
||||
return;
|
||||
|
||||
_behavior = aBehavior;
|
||||
[_popoverWindow setStyleMask:[self _styleMaskForBehavior]];
|
||||
[_attachedWindow setStyleMask:[self styleMaskForBehavior]];
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id <CPPopoverDelegate>)aDelegate
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate === aDelegate)
|
||||
return;
|
||||
@@ -220,8 +191,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
|
||||
@@ -238,38 +209,38 @@ Set the behavior of the CPPopover. It can be:
|
||||
if (!_contentViewController)
|
||||
[CPException raise:CPInternalInconsistencyException reason:@"contentViewController must not be nil"];
|
||||
|
||||
// If the popover is currently closing or opening, do nothing. That is what Cocoa does.
|
||||
if ([_popoverWindow isClosing] || [_popoverWindow isOpening])
|
||||
// If the popover is currently closing, do nothing. That is what Cocoa does.
|
||||
if ([_attachedWindow isClosing])
|
||||
return;
|
||||
|
||||
_positioningView = positioningView;
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_willShow_)
|
||||
[_delegate popoverWillShow:self];
|
||||
|
||||
if (!_popoverWindow)
|
||||
_popoverWindow = [[_CPPopoverWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:[self _styleMaskForBehavior]];
|
||||
if (!_attachedWindow)
|
||||
{
|
||||
_attachedWindow = [[_CPAttachedWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:[self styleMaskForBehavior]];
|
||||
|
||||
[_popoverWindow setPlatformWindow:[[positioningView window] platformWindow]];
|
||||
[_popoverWindow setAppearance:_appearance];
|
||||
[_popoverWindow setAnimates:_animates];
|
||||
[_popoverWindow setDelegate:self];
|
||||
[_popoverWindow setMovableByWindowBackground:NO];
|
||||
[_popoverWindow setFrame:[_popoverWindow frameRectForContentRect:[[_contentViewController view] frame]]];
|
||||
[_popoverWindow setContentView:[_contentViewController view]];
|
||||
var parentWindow = [positioningView window];
|
||||
|
||||
if (![self isShown])
|
||||
[self _popoverWillShow];
|
||||
if (![parentWindow isKindOfClass:_CPAttachedWindow])
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(parentWindowWillClose:) name:CPWindowWillCloseNotification object:parentWindow];
|
||||
}
|
||||
|
||||
[_popoverWindow positionRelativeToRect:positioningRect ofView:positioningView preferredEdge:preferredEdge];
|
||||
[_attachedWindow setAppearance:_appearance];
|
||||
[_attachedWindow setAnimates:_animates];
|
||||
[_attachedWindow setDelegate:self];
|
||||
[_attachedWindow setMovableByWindowBackground:NO];
|
||||
[_attachedWindow setFrame:[_attachedWindow frameRectForContentRect:[[_contentViewController view] frame]]];
|
||||
[_attachedWindow setContentView:[_contentViewController view]];
|
||||
[_attachedWindow positionRelativeToRect:positioningRect ofView:positioningView preferredEdge:preferredEdge];
|
||||
|
||||
if (![self isShown])
|
||||
[self _popoverWindowDidShow];
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_didShow_)
|
||||
[_delegate popoverDidShow:self];
|
||||
}
|
||||
|
||||
- (unsigned)_styleMaskForBehavior
|
||||
- (unsigned)styleMaskForBehavior
|
||||
{
|
||||
if (_behavior == CPPopoverBehaviorApplicationDefined)
|
||||
return 0;
|
||||
|
||||
return CPClosableOnBlurWindowMask
|
||||
return (_behavior == CPPopoverBehaviorTransient) ? CPClosableOnBlurWindowMask : 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -285,21 +256,21 @@ Set the behavior of the CPPopover. It can be:
|
||||
*/
|
||||
- (void)_close
|
||||
{
|
||||
if ([_popoverWindow isClosing] || ![self isShown])
|
||||
if ([_attachedWindow isClosing] || ![self isShown])
|
||||
return;
|
||||
|
||||
[self _popoverWillClose];
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_willClose_)
|
||||
[_delegate popoverWillClose:self];
|
||||
|
||||
_positioningView = nil;
|
||||
[_popoverWindow close];
|
||||
[_attachedWindow close];
|
||||
|
||||
// popoverDidClose will be sent from popoverWindowDidClose, since
|
||||
// the popover window will close asynchronously when animating.
|
||||
// popoverDidClose will be sent from attachedWindowDidClose, since
|
||||
// the attached window will close asynchronously when animating.
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Action
|
||||
#pragma mark -
|
||||
#pragma mark Action
|
||||
|
||||
/*!
|
||||
Close the popover
|
||||
@@ -308,65 +279,52 @@ Set the behavior of the CPPopover. It can be:
|
||||
*/
|
||||
- (IBAction)performClose:(id)sender
|
||||
{
|
||||
if ([_popoverWindow isClosing])
|
||||
if ([_attachedWindow isClosing])
|
||||
return;
|
||||
|
||||
if (![self _popoverShouldClose])
|
||||
return;
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_shouldClose_)
|
||||
if (![_delegate popoverShouldClose:self])
|
||||
return;
|
||||
|
||||
[self _close];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Delegates
|
||||
#pragma mark -
|
||||
#pragma mark Delegates
|
||||
|
||||
/*! @ignore */
|
||||
- (BOOL)_popoverWindowShouldClose
|
||||
- (BOOL)attachedWindowShouldClose:(_CPAttachedWindow)anAttachedWindow
|
||||
{
|
||||
[self performClose:self];
|
||||
|
||||
// We return NO, because we want the CPPopover to determine
|
||||
// if the popover window can be closed and to give us a chance
|
||||
// if the attached window can be closed and to give us a chance
|
||||
// to send delegate messages.
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_popoverWindowDidClose
|
||||
- (void)attachedWindowDidClose:(_CPAttachedWindow)anAttachedWindow
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_didClose_)
|
||||
[_delegate popoverDidClose:self];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_popoverWindowDidShow
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_didShow_)
|
||||
[_delegate popoverDidShow:self];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (BOOL)_popoverShouldClose
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_shouldClose_)
|
||||
return [_delegate popoverShouldClose:self];
|
||||
#pragma mark -
|
||||
#pragma mark Notifications
|
||||
|
||||
return YES;
|
||||
}
|
||||
/*!
|
||||
@ignore
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_popoverWillClose
|
||||
This method is called only when a non-popover parent window
|
||||
is closing. In that case popovers should order out.
|
||||
*/
|
||||
- (void)parentWindowWillClose:(CPNotification)aNotification
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_willClose_)
|
||||
[_delegate popoverWillClose:self];
|
||||
}
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_popoverWillShow
|
||||
{
|
||||
if (_implementedDelegateMethods & CPPopoverDelegate_popover_willShow_)
|
||||
[_delegate popoverWillShow:self];
|
||||
[_attachedWindow orderOut:nil];
|
||||
[self performClose:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -382,7 +340,7 @@ Set the behavior of the CPPopover. It can be:
|
||||
|
||||
@end
|
||||
|
||||
var CPPopoverNeedsNewPopoverWindowKey = @"CPPopoverNeedsNewPopoverWindowKey",
|
||||
var CPPopoverNeedsNewAttachedWindowKey = @"CPPopoverNeedsNewAttachedWindowKey",
|
||||
CPPopoverAppearanceKey = @"CPPopoverAppearanceKey",
|
||||
CPPopoverAnimatesKey = @"CPPopoverAnimatesKey",
|
||||
CPPopoverContentViewControllerKey = @"CPPopoverContentViewControllerKey",
|
||||
|
||||
+101
-170
@@ -23,27 +23,29 @@
|
||||
@import "CGGeometry.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPView.j"
|
||||
@import "CPWindow_Constants.j"
|
||||
|
||||
|
||||
@typedef CPProgressIndicatorStyle
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorBarStyle = 0;
|
||||
CPProgressIndicatorBarStyle = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorSpinningStyle = 1;
|
||||
CPProgressIndicatorSpinningStyle = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorHUDBarStyle = 2;
|
||||
CPProgressIndicatorHUDBarStyle = 2;
|
||||
|
||||
var CPProgressIndicatorSpinningStyleColors = [];
|
||||
var CPProgressIndicatorSpinningStyleColors = nil,
|
||||
|
||||
CPProgressIndicatorClassName = nil,
|
||||
CPProgressIndicatorStyleIdentifiers = nil,
|
||||
CPProgressIndicatorStyleSizes = nil;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -62,7 +64,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
|
||||
CPControlSize _controlSize;
|
||||
|
||||
BOOL _indeterminate;
|
||||
BOOL _isIndeterminate;
|
||||
CPProgressIndicatorStyle _style;
|
||||
|
||||
BOOL _isAnimating;
|
||||
@@ -71,33 +73,66 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
BOOL _isDisplayedWhenStopped;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
return @"progress-indicator";
|
||||
}
|
||||
if (self !== [CPProgressIndicator class])
|
||||
return;
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"indeterminate-bar-color": [CPNull null],
|
||||
@"bar-color": [CPNull null],
|
||||
@"default-height": 20,
|
||||
@"bezel-color": [CPNull null],
|
||||
@"spinning-mini-gif": [CPNull null],
|
||||
@"spinning-small-gif": [CPNull null],
|
||||
@"spinning-regular-gif": [CPNull null],
|
||||
@"circular-border-color": [CPNull null],
|
||||
@"circular-border-size": 1,
|
||||
@"circular-color": [CPNull null]
|
||||
};
|
||||
}
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPValueBinding || aBinding === @"isIndeterminate")
|
||||
return [_CPProgressIndicatorBinder class];
|
||||
CPProgressIndicatorSpinningStyleColors = [];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleMini.gif"] size:CGSizeMake(16.0, 16.0)]];
|
||||
CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleSmall.gif"] size:CGSizeMake(32.0, 32.0)]];
|
||||
CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleRegular.gif"] size:CGSizeMake(64.0, 64.0)]];
|
||||
|
||||
CPProgressIndicatorBezelBorderViewPool = [];
|
||||
|
||||
var start = CPProgressIndicatorBarStyle,
|
||||
end = CPProgressIndicatorHUDBarStyle;
|
||||
|
||||
for (; start <= end; ++start)
|
||||
{
|
||||
CPProgressIndicatorBezelBorderViewPool[start] = [];
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPMiniControlSize] = [];
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPSmallControlSize] = [];
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPRegularControlSize] = [];
|
||||
}
|
||||
|
||||
CPProgressIndicatorClassName = [self className];
|
||||
CPProgressIndicatorStyleIdentifiers = [];
|
||||
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle] = @"Bar";
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorSpinningStyle] = @"Spinny";
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle] = @"HUDBar";
|
||||
|
||||
var regularIdentifier = _CPControlIdentifierForControlSize(CPRegularControlSize),
|
||||
smallIdentifier = _CPControlIdentifierForControlSize(CPSmallControlSize),
|
||||
miniIdentifier = _CPControlIdentifierForControlSize(CPMiniControlSize);
|
||||
|
||||
CPProgressIndicatorStyleSizes = [];
|
||||
|
||||
// Bar Style
|
||||
var prefixes = [
|
||||
CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle],
|
||||
CPProgressIndicatorClassName + @"Bar" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle],
|
||||
CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle],
|
||||
CPProgressIndicatorClassName + @"Bar" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle]
|
||||
];
|
||||
|
||||
for (var i = 0, count = prefixes.length; i < count; i++)
|
||||
{
|
||||
var prefix = prefixes[i];
|
||||
CPProgressIndicatorStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(3.0, 16.0), _CGSizeMake(1.0, 16.0), _CGSizeMake(3.0, 16.0)];
|
||||
CPProgressIndicatorStyleSizes[prefix + smallIdentifier] = [_CGSizeMake(3.0, 16.0), _CGSizeMake(1.0, 16.0), _CGSizeMake(3.0, 16.0)];
|
||||
CPProgressIndicatorStyleSizes[prefix + miniIdentifier] = [_CGSizeMake(3.0, 16.0), _CGSizeMake(1.0, 16.0), _CGSizeMake(3.0, 16.0)];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
@@ -116,7 +151,8 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self updateBackgroundColor];
|
||||
[self drawBar];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -278,12 +314,12 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
Specifies whether this progress indicator should be indeterminate or display progress based on it's max and min.
|
||||
@param isDeterminate \c YES makes the indicator indeterminate
|
||||
*/
|
||||
- (void)setIndeterminate:(BOOL)indeterminate
|
||||
- (void)setIndeterminate:(BOOL)isIndeterminate
|
||||
{
|
||||
if (_indeterminate == indeterminate)
|
||||
if (_isIndeterminate == isIndeterminate)
|
||||
return;
|
||||
|
||||
_indeterminate = indeterminate;
|
||||
_isIndeterminate = isIndeterminate;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
@@ -293,7 +329,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
*/
|
||||
- (BOOL)isIndeterminate
|
||||
{
|
||||
return _indeterminate;
|
||||
return _isIndeterminate;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -307,8 +343,6 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
|
||||
_style = aStyle;
|
||||
|
||||
[self setTheme:(_style === CPProgressIndicatorHUDBarStyle) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
@@ -320,7 +354,8 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
[self setFrameSize:[[CPProgressIndicatorSpinningStyleColors[_controlSize] patternImage] size]];
|
||||
else
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), [self valueForThemeAttribute:@"default-height"])];
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), CPProgressIndicatorStyleSizes[
|
||||
CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle] + _CPControlIdentifierForControlSize(_controlSize)][0].height)];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -370,13 +405,25 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
/* @ignore */
|
||||
- (void)drawBar
|
||||
{
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
return;
|
||||
|
||||
var barView = [self layoutEphemeralSubviewNamed:"bar-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
[barView setBackgroundColor:_CPControlThreePartImagePattern(
|
||||
NO,
|
||||
CPProgressIndicatorStyleSizes,
|
||||
CPProgressIndicatorClassName,
|
||||
@"Bar",
|
||||
CPProgressIndicatorStyleIdentifiers[_style],
|
||||
_CPControlIdentifierForControlSize(_controlSize))];
|
||||
}
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
return [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
return [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
}
|
||||
|
||||
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
|
||||
@@ -389,10 +436,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
if (barWidth > 0.0 && barWidth < 4.0)
|
||||
barWidth = 4.0;
|
||||
|
||||
if (_indeterminate)
|
||||
barWidth = width;
|
||||
|
||||
return CGRectMake(0, 0, barWidth, [self valueForThemeAttribute:@"default-height"]);
|
||||
return _CGRectMake(0, 0, barWidth, 16.0);
|
||||
}
|
||||
|
||||
return nil;
|
||||
@@ -400,26 +444,11 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
|
||||
/* @ignore */
|
||||
- (void)updateBackgroundColor
|
||||
{
|
||||
if ([CPProgressIndicatorSpinningStyleColors count] === 0)
|
||||
{
|
||||
CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [self valueForThemeAttribute:@"spinning-mini-gif"];
|
||||
CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [self valueForThemeAttribute:@"spinning-small-gif"];
|
||||
CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [self valueForThemeAttribute:@"spinning-regular-gif"];
|
||||
}
|
||||
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (YES)//_isBezeled)
|
||||
{
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
{
|
||||
if (!_indeterminate)
|
||||
return;
|
||||
|
||||
// This will cause the bar view to go away due to having a nil rect when _style == CPProgressIndicatorSpinningStyle.
|
||||
[self layoutEphemeralSubviewNamed:"bar-view"
|
||||
positioned:CPWindowBelow
|
||||
@@ -429,70 +458,21 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
|
||||
[self setBackgroundColor:_CPControlThreePartImagePattern(
|
||||
NO,
|
||||
CPProgressIndicatorStyleSizes,
|
||||
CPProgressIndicatorClassName,
|
||||
@"BezelBorder",
|
||||
CPProgressIndicatorStyleIdentifiers[_style],
|
||||
_CPControlIdentifierForControlSize(_controlSize))];
|
||||
|
||||
var barView = [self layoutEphemeralSubviewNamed:"bar-view"
|
||||
positioned:CPWindowBelow
|
||||
relativeToEphemeralSubviewNamed:nil];
|
||||
|
||||
if (_indeterminate)
|
||||
[barView setBackgroundColor:[self currentValueForThemeAttribute:@"indeterminate-bar-color"]];
|
||||
else
|
||||
[barView setBackgroundColor:[self currentValueForThemeAttribute:@"bar-color"]];
|
||||
[self drawBar];
|
||||
}
|
||||
}
|
||||
else
|
||||
[self setBackgroundColor:nil];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
if (_style == CPProgressIndicatorSpinningStyle && !_indeterminate)
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
rect = CGRectMakeCopy(aRect),
|
||||
borderSize = [self currentValueForThemeAttribute:@"circular-border-size"];
|
||||
|
||||
rect.origin.x += borderSize;
|
||||
rect.origin.y += borderSize;
|
||||
rect.size.width = rect.size.width - borderSize * 2;
|
||||
rect.size.height = rect.size.height - borderSize * 2;
|
||||
|
||||
if ([self doubleValue] > [self minValue] && [self doubleValue] < [self maxValue])
|
||||
{
|
||||
var midX = CGRectGetMidX(rect),
|
||||
midY = CGRectGetMidY(rect),
|
||||
endAngle = Math.PI * 2 * (([self doubleValue] - [self minValue]) / ([self maxValue] - [self minValue])) - Math.PI / 2,
|
||||
radius = MIN(rect.size.width / 2, rect.size.height / 2);
|
||||
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetLineWidth(context, borderSize);
|
||||
CGContextSetFillColor(context, [self currentValueForThemeAttribute:@"circular-color"]);
|
||||
CGContextMoveToPoint(context, midX, midY);
|
||||
CGContextAddArc(context, midX, midY, radius, 3 * Math.PI / 2, endAngle, YES);
|
||||
CGContextAddLineToPoint(context, midX, midY);
|
||||
CGContextClosePath(context);
|
||||
CGContextFillPath(context);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
else if ([self doubleValue] == [self maxValue])
|
||||
{
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetFillColor(context, [self currentValueForThemeAttribute:@"circular-color"]);
|
||||
CGContextAddEllipseInRect(context, rect);
|
||||
CGContextClosePath(context);
|
||||
CGContextFillPath(context);
|
||||
}
|
||||
|
||||
CGContextBeginPath(context);
|
||||
CGContextSetStrokeColor(context , [self currentValueForThemeAttribute:@"circular-border-color"]);
|
||||
CGContextSetLineWidth(context, borderSize);
|
||||
CGContextAddEllipseInRect(context, rect);
|
||||
CGContextClosePath(context);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -506,7 +486,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
_maxValue = [aCoder decodeObjectForKey:@"_maxValue"];
|
||||
_doubleValue = [aCoder decodeObjectForKey:@"_doubleValue"];
|
||||
_controlSize = [aCoder decodeObjectForKey:@"_controlSize"];
|
||||
_indeterminate = [aCoder decodeObjectForKey:@"_indeterminate"];
|
||||
_isIndeterminate = [aCoder decodeObjectForKey:@"_isIndeterminate"];
|
||||
_style = [aCoder decodeIntForKey:@"_style"];
|
||||
_isAnimating = [aCoder decodeObjectForKey:@"_isAnimating"];
|
||||
_isDisplayedWhenStoppedSet = [aCoder decodeObjectForKey:@"_isDisplayedWhenStoppedSet"];
|
||||
@@ -520,8 +500,8 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
|
||||
- (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.
|
||||
// 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];
|
||||
@@ -531,7 +511,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
[aCoder encodeObject:_maxValue forKey:@"_maxValue"];
|
||||
[aCoder encodeObject:_doubleValue forKey:@"_doubleValue"];
|
||||
[aCoder encodeObject:_controlSize forKey:@"_controlSize"];
|
||||
[aCoder encodeObject:_indeterminate forKey:@"_indeterminate"];
|
||||
[aCoder encodeObject:_isIndeterminate forKey:@"_isIndeterminate"];
|
||||
[aCoder encodeInt:_style forKey:@"_style"];
|
||||
[aCoder encodeObject:_isAnimating forKey:@"_isAnimating"];
|
||||
[aCoder encodeObject:_isDisplayedWhenStoppedSet forKey:@"_isDisplayedWhenStoppedSet"];
|
||||
@@ -539,52 +519,3 @@ var CPProgressIndicatorSpinningStyleColors = [];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation _CPProgressIndicatorBinder : CPBinder
|
||||
|
||||
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
|
||||
{
|
||||
var value = aBinding === CPValueBinding ? 0.0 : YES;
|
||||
|
||||
[self _setPlaceholder:value forMarker:CPMultipleValuesMarker isDefault:YES];
|
||||
[self _setPlaceholder:value forMarker:CPNoSelectionMarker isDefault:YES];
|
||||
[self _setPlaceholder:value forMarker:CPNotApplicableMarker isDefault:YES];
|
||||
[self _setPlaceholder:value forMarker:CPNullMarker isDefault:YES];
|
||||
}
|
||||
|
||||
- (id)valueForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPValueBinding)
|
||||
return [_source doubleValue];
|
||||
else if (aBinding === @"isIndeterminate")
|
||||
[_source isIndeterminate];
|
||||
else
|
||||
return [super valueForBinding:aBinding];
|
||||
}
|
||||
|
||||
- (BOOL)_setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPValueBinding)
|
||||
[_source setDoubleValue:aValue];
|
||||
else if (aBinding === @"isIndeterminate")
|
||||
[_source setIndeterminate:aValue];
|
||||
else
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
if (![self _setValue:aValue forBinding:aBinding])
|
||||
[super setValue:aValue forBinding:aBinding];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
||||
{
|
||||
if (![self _setValue:aValue forBinding:aBinding])
|
||||
[super setPlaceholderValue:aValue withMarker:aMarker forBinding:aBinding];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+27
-309
@@ -25,11 +25,6 @@
|
||||
|
||||
@import "CPButton.j"
|
||||
|
||||
@class CPRadioGroup
|
||||
|
||||
@global CPApp
|
||||
|
||||
CPRadioImageOffset = 4.0;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@@ -67,19 +62,10 @@ 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)
|
||||
*/
|
||||
|
||||
CPRadioImageOffset = 4.0;
|
||||
|
||||
@implementation CPRadio : CPButton
|
||||
{
|
||||
CPRadioGroup _radioGroup;
|
||||
@@ -164,7 +150,7 @@ CPRadioImageOffset = 4.0;
|
||||
[_radioGroup _setSelectedRadio:self];
|
||||
}
|
||||
|
||||
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
|
||||
- (void)sendAction:(SEL)anAction to:(id)anObject
|
||||
{
|
||||
[super sendAction:anAction to:anObject];
|
||||
|
||||
@@ -172,71 +158,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,164 +181,50 @@ 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
|
||||
|
||||
@implementation CPRadioGroup : CPObject
|
||||
{
|
||||
CPArray _radios;
|
||||
CPSet _radios;
|
||||
CPRadio _selectedRadio;
|
||||
|
||||
BOOL _enabled @accessors(getter=enabled);
|
||||
BOOL _hidden @accessors(getter=hidden);
|
||||
|
||||
id _target @accessors(property=target);
|
||||
SEL _action @accessors(property=action);
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self !== [CPRadioGroup class])
|
||||
return;
|
||||
|
||||
[self exposeBinding:CPSelectedValueBinding];
|
||||
[self exposeBinding:CPSelectedTagBinding];
|
||||
[self exposeBinding:CPSelectedIndexBinding];
|
||||
|
||||
[self exposeBinding:CPEnabledBinding];
|
||||
[self exposeBinding:CPHiddenBinding];
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_radios = [];
|
||||
_radios = [CPSet set];
|
||||
_selectedRadio = nil;
|
||||
_enabled = YES;
|
||||
_hidden = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the radio button at the given index within the group.
|
||||
If index is -1, all of the radio buttons are turned off.
|
||||
*/
|
||||
- (void)selectRadioAtIndex:(int)index
|
||||
{
|
||||
if (index === -1)
|
||||
[self _setSelectedRadio:nil];
|
||||
else
|
||||
{
|
||||
var radio = [_radios objectAtIndex:index];
|
||||
|
||||
[self _setSelectedRadio:radio];
|
||||
[radio setState:CPOnState];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the first radio button within the group with the given tag.
|
||||
If a radio button with the given tag is found, selects it
|
||||
and returns YES. Otherwise returns NO.
|
||||
*/
|
||||
- (BOOL)selectRadioWithTag:(int)tag
|
||||
{
|
||||
var index = [_radios indexOfObjectPassingTest:function(radio)
|
||||
{
|
||||
return [radio tag] === tag;
|
||||
}];
|
||||
|
||||
if (index !== CPNotFound)
|
||||
{
|
||||
[self selectRadioAtIndex:index];
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPRadio that is currently selected within the group,
|
||||
or nil if none are selected.
|
||||
*/
|
||||
- (CPRadio)selectedRadio
|
||||
{
|
||||
return _selectedRadio;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the index of the selected radio within the array of radio buttons.
|
||||
Buttons are numbered going left to right and top to bottom. Returns -1
|
||||
if no radio within the group is selected.
|
||||
*/
|
||||
- (int)selectedRadioIndex
|
||||
{
|
||||
return [_radios indexOfObject:_selectedRadio];
|
||||
}
|
||||
|
||||
- (CPArray)radios
|
||||
{
|
||||
return _radios;
|
||||
}
|
||||
|
||||
- (int)size
|
||||
{
|
||||
return [_radios count];
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)enabled
|
||||
{
|
||||
[_radios makeObjectsPerformSelector:@selector(setEnabled:) withObject:enabled];
|
||||
}
|
||||
|
||||
- (void)setHidden:(BOOL)hidden
|
||||
{
|
||||
[_radios makeObjectsPerformSelector:@selector(setHidden:) withObject:hidden];
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
- (void)_addRadio:(CPRadio)aRadio
|
||||
{
|
||||
if ([_radios indexOfObject:aRadio] === CPNotFound)
|
||||
[_radios addObject:aRadio];
|
||||
[_radios addObject:aRadio];
|
||||
|
||||
if ([aRadio state] === CPOnState)
|
||||
[self _setSelectedRadio:aRadio];
|
||||
@@ -431,30 +238,23 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
|
||||
[_radios removeObject:aRadio];
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the first radio button within the group with the given tag.
|
||||
If a radio button with the given tag is found, selects it
|
||||
and returns YES. Otherwise returns NO.
|
||||
*/
|
||||
- (void)_selectRadioWithTitle:(CPString)aTitle
|
||||
{
|
||||
var index = [_radios indexOfObjectPassingTest:function(radio)
|
||||
{
|
||||
return [radio title] === aTitle;
|
||||
}];
|
||||
|
||||
[self selectRadioAtIndex:index];
|
||||
}
|
||||
|
||||
- (void)_setSelectedRadio:(CPRadio)aRadio
|
||||
{
|
||||
if (_selectedRadio === aRadio)
|
||||
return;
|
||||
|
||||
[_selectedRadio setState:CPOffState];
|
||||
|
||||
_selectedRadio = aRadio;
|
||||
[_CPRadioGroupSelectionBinder _reverseSetValueFromExclusiveBinderForObject:self];
|
||||
}
|
||||
|
||||
- (CPRadio)selectedRadio
|
||||
{
|
||||
return _selectedRadio;
|
||||
}
|
||||
|
||||
- (CPArray)radios
|
||||
{
|
||||
return [_radios allObjects];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -484,85 +284,3 @@ var CPRadioGroupRadiosKey = @"CPRadioGroupRadiosKey",
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPRadioGroup (BindingSupport)
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPSelectedValueBinding ||
|
||||
aBinding === CPSelectedTagBinding ||
|
||||
aBinding === CPSelectedIndexBinding)
|
||||
{
|
||||
var capitalizedBinding = aBinding.charAt(0).toUpperCase() + aBinding.substr(1);
|
||||
|
||||
return [CPClassFromString(@"_CPRadioGroup" + capitalizedBinding + "Binder") class];
|
||||
}
|
||||
else if ([aBinding hasPrefix:CPEnabledBinding])
|
||||
return [CPMultipleValueAndBinding class];
|
||||
else if ([aBinding hasPrefix:CPHiddenBinding])
|
||||
return [CPMultipleValueOrBinding class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
}
|
||||
|
||||
+ (BOOL)isBindingExclusive:(CPString)aBinding
|
||||
{
|
||||
return (aBinding == CPSelectedIndexBinding ||
|
||||
aBinding == CPSelectedTagBinding ||
|
||||
aBinding == CPSelectedValueBinding);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPRadioGroupSelectionBinder : CPBinder
|
||||
{
|
||||
}
|
||||
|
||||
- (void)setPlaceholderValue:(id)aValue withMarker:(CPString)aMarker forBinding:(CPString)aBinding
|
||||
{
|
||||
[self setValue:aValue forBinding:aBinding];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPRadioGroupSelectedIndexBinder : _CPRadioGroupSelectionBinder
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source selectRadioAtIndex:aValue];
|
||||
}
|
||||
|
||||
- (id)valueForBinding:(CPString)aBinding
|
||||
{
|
||||
return [_source selectedRadioIndex];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPRadioGroupSelectedTagBinder : _CPRadioGroupSelectionBinder
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source selectRadioWithTag:aValue];
|
||||
}
|
||||
|
||||
- (id)valueForBinding:(CPString)aBinding
|
||||
{
|
||||
return [[_source selectedRadio] tag];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPRadioGroupSelectedValueBinder : _CPRadioGroupSelectionBinder
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
[_source _selectRadioWithTitle:aValue];
|
||||
}
|
||||
|
||||
- (id)valueForBinding:(CPString)aBinding
|
||||
{
|
||||
return [[_source selectedRadio] title];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+2
-20
@@ -21,13 +21,7 @@
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPObjJRuntime.j>
|
||||
|
||||
@import "CPEvent.j"
|
||||
@import "CPCursor.j"
|
||||
|
||||
@class CPKeyBinding
|
||||
@class CPMenu
|
||||
|
||||
CPDeleteKeyCode = 8;
|
||||
CPTabKeyCode = 9;
|
||||
@@ -201,18 +195,6 @@ CPDeleteForwardKeyCode = 46;
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the mouse entered the receiver's area and that it can adapt the cursor.
|
||||
@param anEvent contains information about the exit
|
||||
*/
|
||||
- (void)cursorUpdate:(CPEvent)anEvent
|
||||
{
|
||||
if (_nextResponder)
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
else
|
||||
[[CPCursor arrowCursor] set];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the mouse scroll wheel has moved.
|
||||
@param anEvent information about the scroll
|
||||
@@ -370,7 +352,7 @@ CPDeleteForwardKeyCode = 46;
|
||||
var CPResponderNextResponderKey = @"CPResponderNextResponderKey",
|
||||
CPResponderMenuKey = @"CPResponderMenuKey";
|
||||
|
||||
@implementation CPResponder (CPCoding) <CPCoding>
|
||||
@implementation CPResponder (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the responder with data from a coder.
|
||||
@@ -397,7 +379,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];
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
SEL _predicateAction @accessors(property=action);
|
||||
}
|
||||
|
||||
// MARK: public methods
|
||||
#pragma mark public methods
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPPredicateEditor
|
||||
@@ -82,15 +82,15 @@
|
||||
}
|
||||
|
||||
/*! @cond */
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
+ (Class)_binderClassForBinding:(CPString)theBinding
|
||||
{
|
||||
if (aBinding == CPValueBinding)
|
||||
if (theBinding == CPValueBinding)
|
||||
return [CPPredicateEditorValueBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
return [super _binderClassForBinding:theBinding];
|
||||
}
|
||||
|
||||
- (CPString)_replacementKeyPathForBinding:(CPString)aBinding
|
||||
- (id)_replacementKeyPathForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding == CPValueBinding)
|
||||
return @"predicate";
|
||||
@@ -220,7 +220,7 @@
|
||||
var children = [CPArray array],
|
||||
itemsCount = 0,
|
||||
menuIndex = -1,
|
||||
itemArray,
|
||||
itemsArray,
|
||||
|
||||
templateView = [templateViews objectAtIndex:count],
|
||||
isPopup = [templateView isKindOfClass:[CPPopUpButton class]];
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -365,16 +360,12 @@
|
||||
rootItems = [treeChild children];
|
||||
}
|
||||
|
||||
var row = @{
|
||||
@"criteria": criteria,
|
||||
@"displayValues": values,
|
||||
@"rowType": rowType,
|
||||
};
|
||||
var row = [CPDictionary dictionaryWithObjectsAndKeys:criteria, @"criteria", values, @"displayValues", rowType, @"rowType"];
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
// MARK: Get the predicate
|
||||
#pragma mark Get the predicate
|
||||
|
||||
- (void)_updatePredicate
|
||||
{
|
||||
@@ -452,7 +443,7 @@
|
||||
return CPAndPredicateType;
|
||||
}
|
||||
|
||||
// MARK: Control delegate
|
||||
#pragma mark Control delegate
|
||||
|
||||
- (void)_sendRuleAction
|
||||
{
|
||||
@@ -487,9 +478,9 @@
|
||||
}
|
||||
*/
|
||||
|
||||
// MARK: RuleEditor delegate methods
|
||||
#pragma mark RuleEditor delegate methods
|
||||
|
||||
- (int)_queryNumberOfChildrenOfItem:(id)rowItem withRowType:(CPRuleEditorRowType)type
|
||||
- (int)_queryNumberOfChildrenOfItem:(id)rowItem withRowType:(int)type
|
||||
{
|
||||
if (rowItem == nil)
|
||||
{
|
||||
@@ -499,7 +490,7 @@
|
||||
return [[rowItem children] count];
|
||||
}
|
||||
|
||||
- (id)_queryChild:(int)childIndex ofItem:(id)rowItem withRowType:(CPRuleEditorRowType)type
|
||||
- (id)_queryChild:(int)childIndex ofItem:(id)rowItem withRowType:(int)type
|
||||
{
|
||||
if (rowItem == nil)
|
||||
{
|
||||
@@ -510,7 +501,7 @@
|
||||
return [[rowItem children] objectAtIndex:childIndex];
|
||||
}
|
||||
|
||||
- (id)_queryValueForItem:(id)rowItem inRow:(CPInteger)rowIndex
|
||||
- (id)_queryValueForItem:(id)rowItem inRow:(int)rowIndex
|
||||
{
|
||||
return [rowItem displayValue];
|
||||
}
|
||||
@@ -521,7 +512,7 @@ var CPPredicateTemplatesKey = @"CPPredicateTemplates";
|
||||
|
||||
@implementation CPPredicateEditor (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
- (id)initWithCoder:(id)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
@@ -536,7 +527,7 @@ var CPPredicateTemplatesKey = @"CPPredicateTemplates";
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
- (void)encodeWithCoder:(id)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
[aCoder encodeObject:_allTemplates forKey:CPPredicateTemplatesKey];
|
||||
|
||||
@@ -20,16 +20,6 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPPredicate.j>
|
||||
|
||||
@import "CPCheckBox.j"
|
||||
@import "CPPopUpButton.j"
|
||||
@import "CPMenuItem.j"
|
||||
@import "CPTextField.j"
|
||||
|
||||
@import "CPDatePicker.j"
|
||||
|
||||
|
||||
CPUndefinedAttributeType = 0;
|
||||
CPInteger16AttributeType = 100;
|
||||
CPInteger32AttributeType = 200;
|
||||
@@ -471,11 +461,9 @@ CPTransformableAttributeType = 1800;
|
||||
|
||||
var value;
|
||||
if (attributeType >= CPInteger16AttributeType && attributeType <= CPFloatAttributeType)
|
||||
value = [aView floatValue];
|
||||
value = [aView intValue];
|
||||
else if (attributeType == CPBooleanAttributeType)
|
||||
value = [aView state];
|
||||
else if (attributeType == CPDateAttributeType)
|
||||
value = [aView dateValue];
|
||||
else
|
||||
value = [aView stringValue];
|
||||
|
||||
@@ -489,36 +477,7 @@ CPTransformableAttributeType = 1800;
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var views = [CPArray array],
|
||||
copy = [[[self class] alloc] init];
|
||||
|
||||
[copy _setTemplateType:_templateType];
|
||||
[copy _setOptions:_predicateOptions];
|
||||
[copy _setModifier:_predicateModifier];
|
||||
[copy _setLeftAttributeType:_leftAttributeType];
|
||||
[copy _setRightAttributeType:_rightAttributeType];
|
||||
[copy setLeftIsWildcard:_leftIsWildcard];
|
||||
[copy setRightIsWildcard:_rightIsWildcard];
|
||||
|
||||
[_views enumerateObjectsUsingBlock:function(aView, idx, stop)
|
||||
{
|
||||
var vcopy;
|
||||
|
||||
if ([aView implementsSelector:@selector(copy)])
|
||||
{
|
||||
vcopy = [aView copy];
|
||||
}
|
||||
else
|
||||
{
|
||||
vcopy = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:aView]];
|
||||
}
|
||||
|
||||
[views addObject:vcopy];
|
||||
}];
|
||||
|
||||
[copy setTemplateViews:views];
|
||||
|
||||
return copy;
|
||||
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:self]];
|
||||
}
|
||||
|
||||
+ (id)_operatorsForAttributeType:(CPAttributeType)attributeType
|
||||
@@ -624,7 +583,7 @@ CPTransformableAttributeType = 1800;
|
||||
|
||||
- (CPPopUpButton)_viewFromExpressions:(CPArray)expressions
|
||||
{
|
||||
var popup = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0, 0, 100, 18)],
|
||||
var popup = [[CPPopUpButton alloc] initWithFrame:CPMakeRect(0, 0, 100, 18)],
|
||||
count = [expressions count];
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
@@ -710,10 +669,7 @@ CPTransformableAttributeType = 1800;
|
||||
view = [[CPCheckBox alloc] initWithFrame:CGRectMake(0, 0, 50, 26)];
|
||||
}
|
||||
else if (attributeType == CPDateAttributeType)
|
||||
{
|
||||
view = [[CPDatePicker alloc] initWithFrame:CGRectMake(0, 0, 180, 26)];
|
||||
[view setDatePickerElements:CPYearMonthDayDatePickerElementFlag];
|
||||
}
|
||||
view = [[CPDatePicker alloc] initWithFrame:CGRectMake(0, 0, 150, 26)];
|
||||
else
|
||||
return nil;
|
||||
|
||||
@@ -735,12 +691,12 @@ CPTransformableAttributeType = 1800;
|
||||
return textField;
|
||||
}
|
||||
|
||||
- (void)_setOptions:(unsigned)options
|
||||
- (void)_setOptions:(unsigned int)options
|
||||
{
|
||||
_predicateOptions = options;
|
||||
}
|
||||
|
||||
- (void)_setModifier:(unsigned)modifier
|
||||
- (void)_setModifier:(unsigned int)modifier
|
||||
{
|
||||
_predicateModifier = modifier;
|
||||
}
|
||||
@@ -830,31 +786,5 @@ var CPPredicateTemplateTypeKey = @"CPPredicateTemplateType",
|
||||
[coder encodeObject:_views forKey:CPPredicateTemplateViewsKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Copy support for built-in types
|
||||
@implementation CPDatePicker (CPCopying)
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var ret = [[[self class] alloc] initWithFrame:[self frame]];
|
||||
|
||||
[ret setTextFont:[self textFont]];
|
||||
[ret setMinDate:[self minDate]];
|
||||
[ret setMaxDate:[self maxDate]];
|
||||
[ret setTimeInterval:[self timeInterval]];
|
||||
[ret setDatePickerMode:[self datePickerMode]];
|
||||
[ret setDatePickerElements:[self datePickerElements]];
|
||||
[ret setDatePickerStyle:[self datePickerStyle]];
|
||||
[ret setLocale:[self locale]];
|
||||
[ret setDateValue:[self dateValue]];
|
||||
[ret setBackgroundColor:[self backgroundColor]];
|
||||
[ret setDrawsBackground:[self drawsBackground]];
|
||||
[ret setBordered:[self isBordered]];
|
||||
[ret _init];
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@end
|
||||
/*! @endcond */
|
||||
|
||||
+169
-334
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* CPRuleEditor_Constants.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by cacaodev.
|
||||
* Copyright 2011, cacaodev.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
|
||||
CPRuleEditorPredicateLeftExpression = "CPRuleEditorPredicateLeftExpression";
|
||||
CPRuleEditorPredicateRightExpression = "CPRuleEditorPredicateRightExpression";
|
||||
CPRuleEditorPredicateComparisonModifier = "CPRuleEditorPredicateComparisonModifier";
|
||||
CPRuleEditorPredicateOptions = "CPRuleEditorPredicateOptions";
|
||||
CPRuleEditorPredicateOperatorType = "CPRuleEditorPredicateOperatorType";
|
||||
CPRuleEditorPredicateCustomSelector = "CPRuleEditorPredicateCustomSelector";
|
||||
CPRuleEditorPredicateCompoundType = "CPRuleEditorPredicateCompoundType";
|
||||
|
||||
CPRuleEditorRowsDidChangeNotification = "CPRuleEditorRowsDidChangeNotification";
|
||||
CPRuleEditorRulesDidChangeNotification = "CPRuleEditorRulesDidChangeNotification";
|
||||
|
||||
CPRuleEditorNestingModeSingle = 0; // Only a single row is allowed. Plus/minus buttons will not be shown
|
||||
CPRuleEditorNestingModeList = 1; // Allows a single list, with no nesting and no compound rows
|
||||
CPRuleEditorNestingModeCompound = 2; // Unlimited nesting and compound rows; this is the default
|
||||
CPRuleEditorNestingModeSimple = 3; // One compound row at the top with subrows beneath it, and no further nesting allowed
|
||||
|
||||
@typedef CPRuleEditorRowType
|
||||
CPRuleEditorRowTypeSimple = 0;
|
||||
CPRuleEditorRowTypeCompound = 1;
|
||||
@@ -1,25 +1,9 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. 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
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@class _CPPredicateEditorTree;
|
||||
@implementation _CPPredicateEditorRowNode : CPObject
|
||||
{
|
||||
_CPPredicateEditorTree tree @accessors;
|
||||
@@ -131,7 +115,7 @@
|
||||
{
|
||||
var title = [self title];
|
||||
|
||||
if (title != nil)
|
||||
if (title !== nil)
|
||||
return title;
|
||||
|
||||
return [self templateView];
|
||||
|
||||
@@ -1,27 +1,8 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. 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
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@class CPPredicateEditorRowTemplate
|
||||
|
||||
@implementation _CPPredicateEditorTree : CPObject
|
||||
{
|
||||
CPPredicateEditorRowTemplate template @accessors;
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. 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
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPURLConnection.j>
|
||||
@import <Foundation/CPURLRequest.j>
|
||||
|
||||
var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)?");
|
||||
|
||||
@@ -29,7 +13,7 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
|
||||
{
|
||||
CPDictionary _dictionary @accessors(property=dictionary);
|
||||
CPURLConnection connection;
|
||||
CPURLRequest request;
|
||||
CPURLRequest resquest;
|
||||
}
|
||||
|
||||
- (void)loadContentOfURL:(CPURL)aURL
|
||||
@@ -40,7 +24,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 +35,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;
|
||||
@@ -59,7 +43,7 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
|
||||
|
||||
- (void)loadContent:(CPString)aContent
|
||||
{
|
||||
var dict = @{},
|
||||
var dict = [CPDictionary dictionary],
|
||||
lines = [aContent componentsSeparatedByString:"\n"],
|
||||
count = [lines count];
|
||||
|
||||
@@ -77,251 +61,19 @@ 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
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
var GRADIENT_START_COLOR = "#fcfcfc",
|
||||
GRADIENT_END_COLOR = "#dfdfdf",
|
||||
BORDER_COLOR = "#BDBDBD";
|
||||
|
||||
var GRADIENT_NORMAL,
|
||||
GRADIENT_HIGHLIGHTED,
|
||||
GRADIENT_PROPERTY;
|
||||
|
||||
if (CPBrowserIsEngine(CPWebKitBrowserEngine))
|
||||
{
|
||||
GRADIENT_NORMAL = "-webkit-gradient(linear, left top, left bottom, from(" + GRADIENT_START_COLOR + "), to(" + GRADIENT_END_COLOR + "))",
|
||||
GRADIENT_HIGHLIGHTED = "-webkit-gradient(linear, left top, left bottom, from(" + GRADIENT_END_COLOR + "), to(" + GRADIENT_START_COLOR + "))";
|
||||
GRADIENT_PROPERTY = "background";
|
||||
}
|
||||
else if (CPBrowserIsEngine(CPGeckoBrowserEngine))
|
||||
{
|
||||
GRADIENT_NORMAL = "-moz-linear-gradient(top, " + GRADIENT_START_COLOR + ", " + GRADIENT_END_COLOR + ")",
|
||||
GRADIENT_HIGHLIGHTED = "-moz-linear-gradient(top, " + GRADIENT_END_COLOR + ", " + GRADIENT_START_COLOR + ")";
|
||||
GRADIENT_PROPERTY = "background";
|
||||
}
|
||||
else if (CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
|
||||
{
|
||||
GRADIENT_NORMAL = "progid:DXImageTransform.Microsoft.gradient(startColorstr='" + GRADIENT_START_COLOR + "', endColorstr='" + GRADIENT_END_COLOR + "')";
|
||||
GRADIENT_HIGHLIGHTED = "progid:DXImageTransform.Microsoft.gradient(startColorstr='" + GRADIENT_END_COLOR + "', endColorstr='" + GRADIENT_START_COLOR + "')";
|
||||
GRADIENT_PROPERTY = "filter";
|
||||
}else
|
||||
{
|
||||
GRADIENT_NORMAL = GRADIENT_START_COLOR;
|
||||
GRADIENT_HIGHLIGHTED = GRADIENT_END_COLOR;
|
||||
GRADIENT_PROPERTY = "background";
|
||||
}
|
||||
|
||||
@implementation _CPRuleEditorPopUpButton : CPPopUpButton
|
||||
{
|
||||
CPInteger radius;
|
||||
}
|
||||
|
||||
- (void)_sharedInit
|
||||
{
|
||||
[self setBordered:NO];
|
||||
|
||||
var style = _DOMElement.style;
|
||||
style.border = "1px solid " + BORDER_COLOR;
|
||||
style[GRADIENT_PROPERTY] = GRADIENT_NORMAL;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
[self _sharedInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
[self _sharedInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)hitTest:(CPPoint)point
|
||||
{
|
||||
if (!CPRectContainsPoint([self frame], point) || ![self sliceIsEditable])
|
||||
return nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)sliceIsEditable
|
||||
{
|
||||
var superview = [self superview];
|
||||
return ![superview isKindOfClass:[_CPRuleEditorViewSlice]] || [superview isEditable];
|
||||
}
|
||||
|
||||
- (BOOL)trackMouse:(CPEvent)theEvent
|
||||
{
|
||||
if (![self sliceIsEditable])
|
||||
return NO;
|
||||
|
||||
return [super trackMouse:theEvent];
|
||||
}
|
||||
|
||||
- (CGRect)contentRectForBounds:(CGRect)bounds
|
||||
{
|
||||
var contentRect = [super contentRectForBounds:bounds];
|
||||
contentRect.origin.x += radius;
|
||||
contentRect.size.width -= 2 * radius;
|
||||
|
||||
return contentRect;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
radius = FLOOR(CGRectGetHeight([self bounds]) / 2);
|
||||
var style = _DOMElement.style,
|
||||
radiusCSS = radius + "px";
|
||||
|
||||
style.borderRadius = radiusCSS;
|
||||
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)aRect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds],
|
||||
arrow_width = FLOOR(CGRectGetHeight(bounds) / 3.5);
|
||||
|
||||
CGContextTranslateCTM(context, CGRectGetWidth(bounds) - radius - arrow_width, CGRectGetHeight(bounds) / 2);
|
||||
|
||||
var arrowsPath = [CPBezierPath bezierPath];
|
||||
[arrowsPath moveToPoint:CGPointMake(0, 1)];
|
||||
[arrowsPath lineToPoint:CGPointMake(arrow_width, 1)];
|
||||
[arrowsPath lineToPoint:CGPointMake(arrow_width / 2, arrow_width + 1)];
|
||||
[arrowsPath closePath];
|
||||
|
||||
CGContextSetFillColor(context, [CPColor colorWithWhite:101 / 255 alpha:1]);
|
||||
[arrowsPath fill];
|
||||
|
||||
CGContextScaleCTM(context, 1 , -1);
|
||||
[arrowsPath fill];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPRuleEditorButton : CPButton
|
||||
{
|
||||
CPInteger radius;
|
||||
}
|
||||
|
||||
- (void)_sharedInit
|
||||
{
|
||||
[self setBordered:NO];
|
||||
|
||||
var style = _DOMElement.style;
|
||||
style.border = "1px solid " + BORDER_COLOR;
|
||||
style[GRADIENT_PROPERTY] = GRADIENT_NORMAL;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
[self _sharedInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
[self _sharedInit];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
radius = FLOOR(CGRectGetHeight([self bounds]) / 2);
|
||||
|
||||
var style = _DOMElement.style,
|
||||
radiusCSS = radius + "px";
|
||||
|
||||
style.borderRadius = radiusCSS;
|
||||
style[GRADIENT_PROPERTY] = ([self isHighlighted]) ? GRADIENT_HIGHLIGHTED : GRADIENT_NORMAL;
|
||||
|
||||
[super layoutSubviews];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,35 +1,17 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. 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
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
@class CPRuleEditor
|
||||
|
||||
@implementation _CPRuleEditorViewSlice : CPView
|
||||
{
|
||||
CPRuleEditor _ruleEditor;
|
||||
int _indentation @accessors(property=indentation);
|
||||
int _rowIndex @accessors(property=rowIndex);
|
||||
CGRect _animationTargetRect @accessors(property=_animationTargetRect);
|
||||
CPRect _animationTargetRect @accessors(property=_animationTargetRect);
|
||||
BOOL _selected @accessors(getter=_isSelected, setter=_setSelected:);
|
||||
BOOL _lastSelected @accessors(getter=_isLastSelected, setter=_setLastSelected:);
|
||||
BOOL _editable @accessors(getter=isEditable, setter=setEditable:);
|
||||
CPColor _backgroundColor @accessors(property=backgroundColor);
|
||||
}
|
||||
|
||||
- (void)removeFromSuperview
|
||||
@@ -54,19 +36,19 @@
|
||||
if (select == _selected)
|
||||
return;
|
||||
|
||||
var selector = select ? @selector(setThemeState:) : @selector(unsetThemeState:);
|
||||
[[self subviews] makeObjectsPerformSelector:selector withObject:CPThemeStateSelectedDataView];
|
||||
var selector = select ? @"setThemeState:" : @"unsetThemeState:";
|
||||
[[self subviews] makeObjectsPerformSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelectedDataView];
|
||||
_selected = select;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
- (void)drawRect:(CPRect)rect
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
||||
bounds = [self bounds],
|
||||
maxX = CGRectGetWidth(bounds),
|
||||
maxY = CGRectGetHeight(bounds);
|
||||
bounds = [self bounds],
|
||||
maxX = CGRectGetWidth(bounds),
|
||||
maxY = CGRectGetHeight(bounds);
|
||||
|
||||
// Draw background
|
||||
// Draw background
|
||||
if ([self _isSelected])
|
||||
_backgroundColor = [_ruleEditor _selectedRowColor];
|
||||
else
|
||||
@@ -79,33 +61,33 @@
|
||||
CGContextSetFillColor(context, _backgroundColor);
|
||||
CGContextFillRect(context, rect);
|
||||
|
||||
// Draw Top Border
|
||||
// Draw Top Border
|
||||
CGContextBeginPath(context);
|
||||
CGContextMoveToPoint(context, 0, 0);
|
||||
CGContextAddLineToPoint(context, maxX, 0);
|
||||
CGContextClosePath(context);
|
||||
CGContextSetStrokeColor(context, [_ruleEditor _sliceTopBorderColor]);
|
||||
CGContextStrokePath(context);
|
||||
|
||||
// Draw Bottom Border
|
||||
// Draw Bottom Border
|
||||
CGContextBeginPath(context);
|
||||
CGContextMoveToPoint(context, 0, maxY);
|
||||
CGContextAddLineToPoint(context, maxX, maxY);
|
||||
|
||||
CGContextClosePath(context);
|
||||
var bottomColor = (_rowIndex == [_ruleEditor _lastRow]) ? [_ruleEditor _sliceLastBottomBorderColor] : [_ruleEditor _sliceBottomBorderColor];
|
||||
|
||||
CGContextSetStrokeColor(context, bottomColor);
|
||||
CGContextStrokePath(context);
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)theEvent
|
||||
{
|
||||
if (_editable)
|
||||
if (editable)
|
||||
[_ruleEditor _mouseDownOnSlice:self withEvent:theEvent];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)theEvent
|
||||
{
|
||||
if (_editable)
|
||||
if (editable)
|
||||
[_ruleEditor _mouseUpOnSlice:self withEvent:theEvent];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +1,26 @@
|
||||
/*
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. 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
|
||||
* Created by cacaodev@gmail.com.
|
||||
* Copyright (c) 2011 Pear, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
@import "CPRuleEditor_Constants.j"
|
||||
@import "_CPRuleEditorViewSlice.j"
|
||||
@import "_CPRuleEditorPopUpButton.j"
|
||||
@import "CPRuleEditor.j"
|
||||
|
||||
@import "CPButton.j"
|
||||
@import "CPDatePicker.j"
|
||||
@import "CPPopUpButton.j"
|
||||
|
||||
@global CPApp
|
||||
@global CPMiniControlSize
|
||||
@global CPSmallControlSize
|
||||
var CONTROL_HEIGHT = 16.,
|
||||
BUTTON_HEIGHT = 16.;
|
||||
|
||||
@implementation _CPRuleEditorViewSliceRow : _CPRuleEditorViewSlice
|
||||
{
|
||||
CPButton _addButton;
|
||||
CPButton _subtractButton;
|
||||
CPMutableArray _correspondingRuleItems;
|
||||
CPMutableArray _ruleOptionFrames;
|
||||
CPMutableArray _ruleOptionInitialViewFrames;
|
||||
CPMutableArray _ruleOptionViews;
|
||||
|
||||
|
||||
CPRuleEditorRowType _rowType @accessors;
|
||||
CPRuleEditorRowType _plusButtonRowType;
|
||||
CPMutableArray _ruleOptionViews;
|
||||
CPMutableArray _ruleOptionFrames;
|
||||
CPMutableArray _correspondingRuleItems;
|
||||
CPMutableArray _ruleOptionInitialViewFrames;
|
||||
CPButton _addButton;
|
||||
CPButton _subtractButton;
|
||||
BOOL editable;
|
||||
CPRuleEditorRowType _rowType @accessors;
|
||||
CPRuleEditorRowType _plusButtonRowType;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame ruleEditorView:(id)editor
|
||||
@@ -52,33 +33,34 @@
|
||||
|
||||
- (void)_initShared
|
||||
{
|
||||
_correspondingRuleItems = [[CPMutableArray alloc] init];
|
||||
_ruleOptionFrames = [[CPMutableArray alloc] init];
|
||||
_ruleOptionInitialViewFrames = [[CPMutableArray alloc] init];
|
||||
_ruleOptionViews = [[CPMutableArray alloc] init];
|
||||
_editable = [_ruleEditor isEditable];
|
||||
|
||||
_addButton = [self _createAddRowButton];
|
||||
_subtractButton = [self _createDeleteRowButton];
|
||||
_correspondingRuleItems = [[CPMutableArray alloc] init];
|
||||
_ruleOptionFrames = [[CPMutableArray alloc] init];
|
||||
_ruleOptionInitialViewFrames = [[CPMutableArray alloc] init];
|
||||
_ruleOptionViews = [[CPMutableArray alloc] init];
|
||||
editable = [_ruleEditor isEditable];
|
||||
|
||||
_addButton = [self _createAddRowButton];
|
||||
_subtractButton = [self _createDeleteRowButton];
|
||||
[_addButton setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
|
||||
[_subtractButton setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
|
||||
[_addButton setHidden:!editable];
|
||||
[_subtractButton setHidden:!editable];
|
||||
[self addSubview:_addButton];
|
||||
[self addSubview:_subtractButton];
|
||||
|
||||
[self setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
var center = [CPNotificationCenter defaultCenter];
|
||||
[center addObserver:self selector:@selector(_textDidChange:) name:CPControlTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (CPButton)_createRowButton
|
||||
{
|
||||
var button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[button setFont:[_ruleEditor font]];
|
||||
[button setTextColor:[_ruleEditor _fontColor]];
|
||||
|
||||
var button = [[_CPRuleEditorButton alloc] initWithFrame:CGRectMakeZero()];
|
||||
[button setFont:[CPFont boldFontWithName:@"Apple Symbol" size:12.0]];
|
||||
[button setTextColor:[CPColor colorWithWhite:150 / 255 alpha:1]];
|
||||
[button setAlignment:CPCenterTextAlignment];
|
||||
[button setAutoresizingMask:CPViewMinXMargin];
|
||||
[button setButtonType:CPMomentaryChangeButton];
|
||||
[button setBordered:NO];
|
||||
[button setHidden:!_editable];
|
||||
[button setImagePosition:CPImageOnly];
|
||||
|
||||
return button;
|
||||
@@ -88,10 +70,7 @@
|
||||
{
|
||||
var button = [self _createRowButton];
|
||||
|
||||
[button setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
|
||||
[button setValue:[_ruleEditor _imageAdd] forThemeAttribute:@"image" inState:CPThemeStateNormal];
|
||||
[button setValue:[_ruleEditor _imageAddHighlighted] forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
|
||||
|
||||
[button setImage:[_ruleEditor _addImage]];
|
||||
[button setAction:@selector(_addOption:)];
|
||||
[button setTarget:self];
|
||||
|
||||
@@ -102,10 +81,7 @@
|
||||
{
|
||||
var button = [self _createRowButton];
|
||||
|
||||
[button setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
|
||||
[button setValue:[_ruleEditor _imageRemove] forThemeAttribute:@"image" inState:CPThemeStateNormal];
|
||||
[button setValue:[_ruleEditor _imageRemoveHighlighted] forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
|
||||
|
||||
[button setImage:[_ruleEditor _removeImage]];
|
||||
[button setAction:@selector(_deleteOption:)];
|
||||
[button setTarget:self];
|
||||
|
||||
@@ -114,42 +90,21 @@
|
||||
|
||||
- (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];
|
||||
var mItem = [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
|
||||
return mItem;
|
||||
}
|
||||
|
||||
- (CPPopUpButton)_createPopUpButtonWithItems:(CPArray)itemsArray selectedItemIndex:(int)index
|
||||
{
|
||||
var title = [[itemsArray objectAtIndex:index] title],
|
||||
font = [_ruleEditor font],
|
||||
width = [title sizeWithFont:font].width + 35, // 35 for right arrows + margins
|
||||
rect = CGRectMake(0, 0, width, [_ruleEditor rowHeight]),
|
||||
popup = [[CPPopUpButton alloc] initWithFrame:rect];
|
||||
var title = [[itemsArray objectAtIndex:index] title],
|
||||
font = [_ruleEditor font],
|
||||
width = [title sizeWithFont:font].width + 20,
|
||||
rect = CGRectMake(0, 0, (width - width % 40) + 80, CONTROL_HEIGHT),
|
||||
|
||||
popup = [[_CPRuleEditorPopUpButton alloc] initWithFrame:rect];
|
||||
|
||||
[popup setTextColor:[CPColor colorWithWhite:101 / 255 alpha:1]];
|
||||
[popup setValue:font forThemeAttribute:@"font"];
|
||||
|
||||
var count = [itemsArray count];
|
||||
@@ -166,13 +121,27 @@
|
||||
return [CPMenuItem separatorItem];
|
||||
}
|
||||
|
||||
- (_CPRuleEditorTextField)_createStaticTextFieldWithStringValue:(CPString)text
|
||||
{
|
||||
var textField = [[_CPRuleEditorTextField alloc] initWithFrame:CPMakeRect(0, 0, 200, CONTROL_HEIGHT)],
|
||||
refont = [_ruleEditor font],
|
||||
font = [CPFont fontWithName:[refont familyName] size:[refont size] + 2],
|
||||
|
||||
localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text];
|
||||
|
||||
[textField setValue:font forThemeAttribute:@"font"];
|
||||
[textField setStringValue:localizedText];
|
||||
[textField sizeToFit];
|
||||
|
||||
return textField;
|
||||
}
|
||||
|
||||
- (void)_addOption:(id)sender
|
||||
{
|
||||
if (_rowIndex == [_ruleEditor numberOfRows] - 1)
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
var type = _plusButtonRowType;
|
||||
|
||||
if ([_ruleEditor nestingMode] == CPRuleEditorNestingModeCompound && ([[CPApp currentEvent] modifierFlags] & CPAlternateKeyMask))
|
||||
type = CPRuleEditorRowTypeCompound;
|
||||
|
||||
@@ -184,12 +153,12 @@
|
||||
[_ruleEditor _deleteSlice:self];
|
||||
}
|
||||
|
||||
- (void)_ruleOptionPopupChangedAction:(CPMenuItem)sender
|
||||
- (void)_ruleOptionPopupChangedAction:(CPMenuItem )sender
|
||||
{
|
||||
var layoutdict = [sender representedObject],
|
||||
newItem = [layoutdict objectForKey:@"item"],
|
||||
var layoutdict = [sender representedObject],
|
||||
newItem = [layoutdict objectForKey:@"item"],
|
||||
indexInCriteria = [layoutdict objectForKey:@"indexInCriteria"],
|
||||
oldItem = [_correspondingRuleItems objectAtIndex:indexInCriteria];
|
||||
oldItem = [_correspondingRuleItems objectAtIndex:indexInCriteria];
|
||||
|
||||
if (![newItem isEqual:oldItem])
|
||||
{
|
||||
@@ -217,7 +186,8 @@
|
||||
|
||||
- (void)_reconfigureSubviews
|
||||
{
|
||||
var criteria,
|
||||
var ruleItems,
|
||||
criteria,
|
||||
repObject,
|
||||
menuItem,
|
||||
ruleView,
|
||||
@@ -284,11 +254,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
repObject = @{
|
||||
@"item": childItem,
|
||||
@"value": childValue,
|
||||
@"indexInCriteria": i
|
||||
};
|
||||
repObject = [CPDictionary dictionaryWithObjectsAndKeys:childItem, @"item", childValue, @"value", i, @"indexInCriteria"];
|
||||
[menuItem setRepresentedObject:repObject];
|
||||
[menuItems addObject:menuItem];
|
||||
}
|
||||
@@ -320,16 +286,8 @@
|
||||
|
||||
if (ruleView != nil)
|
||||
{
|
||||
[ruleView setControlSize:CPSmallControlSize];
|
||||
|
||||
var minSize = [ruleView currentValueForThemeAttribute:@"min-size"],
|
||||
frame = [ruleView frame];
|
||||
|
||||
// Force controls to their minimum size
|
||||
frame.size.height = minSize.height;
|
||||
[ruleView setFrame:frame];
|
||||
|
||||
[_ruleOptionViews addObject:ruleView];
|
||||
var frame = [ruleView frame];
|
||||
[_ruleOptionInitialViewFrames addObject:frame];
|
||||
[_ruleOptionFrames addObject:frame];
|
||||
|
||||
@@ -342,29 +300,7 @@
|
||||
|
||||
[_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)
|
||||
if (!editable)
|
||||
[self _updateEnabledStateForSubviews];
|
||||
|
||||
[self _relayoutSubviewsWidthChanged:YES];
|
||||
@@ -374,6 +310,8 @@
|
||||
var aView = [_ruleOptionViews objectAtIndex:firstResponderIndex];
|
||||
[[self window] makeFirstResponder:aView]; // This is not working. bug in CPPopUpButton firstResponder ?
|
||||
}
|
||||
|
||||
//[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)_updateEnabledStateForSubviews
|
||||
@@ -391,16 +329,14 @@
|
||||
var optionViewOriginX,
|
||||
leftHorizontalPadding,
|
||||
leftButtonMinX,
|
||||
rowHeight = [_ruleEditor rowHeight],
|
||||
count = [_ruleOptionViews count],
|
||||
sliceFrame = [self frame],
|
||||
image = [_ruleEditor _imageAdd],
|
||||
imageSize = image ? [image size] : CGSizeMake(0,0),
|
||||
rowHeight = [_ruleEditor rowHeight],
|
||||
count = [_ruleOptionViews count],
|
||||
sliceFrame = [self frame],
|
||||
|
||||
buttonFrame = CGRectMake(CGRectGetWidth(sliceFrame) - imageSize.width - [self _rowButtonsRightHorizontalPadding], ([_ruleEditor rowHeight] - imageSize.height) / 2 - 1, imageSize.width, imageSize.height);
|
||||
buttonFrame = CGRectMake(CGRectGetWidth(sliceFrame) - BUTTON_HEIGHT - [self _rowButtonsRightHorizontalPadding], ([_ruleEditor rowHeight] - BUTTON_HEIGHT) / 2 - 2, BUTTON_HEIGHT, BUTTON_HEIGHT);
|
||||
|
||||
[_addButton setFrame:buttonFrame];
|
||||
buttonFrame.origin.x -= imageSize.width + [self _rowButtonsInterviewHorizontalPadding];
|
||||
buttonFrame.origin.x -= BUTTON_HEIGHT + [self _rowButtonsInterviewHorizontalPadding];
|
||||
[_subtractButton setFrame:buttonFrame];
|
||||
|
||||
if (widthChanged)
|
||||
@@ -415,8 +351,15 @@
|
||||
var ruleOptionView = _ruleOptionViews[i],
|
||||
optionFrame = _ruleOptionFrames[i];
|
||||
|
||||
// Use a pixel correction to align controls
|
||||
optionFrame.origin.y = (rowHeight - CGRectGetHeight(optionFrame)) / 2;
|
||||
optionFrame.origin.y = (rowHeight - CGRectGetHeight(optionFrame)) / 2 - 2;
|
||||
|
||||
// small positioning fix
|
||||
if ([ruleOptionView isKindOfClass:CPTextField])
|
||||
{
|
||||
optionFrame.origin.y += 2;
|
||||
[_ruleOptionViews[i] setValue:CGInsetMake(7, 7, 7, 8) forThemeAttribute:@"content-inset"];
|
||||
}
|
||||
|
||||
|
||||
if (widthChanged)
|
||||
{
|
||||
@@ -440,9 +383,6 @@
|
||||
{
|
||||
[_addButton setHidden:[_ruleEditor _shouldHideAddButtonForSlice:self]];
|
||||
[_subtractButton setHidden:[_ruleEditor _shouldHideSubtractButtonForSlice:self]];
|
||||
|
||||
[_addButton setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
|
||||
[_subtractButton setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
|
||||
}
|
||||
|
||||
- (void)_configurePlusButtonByRowType:(CPRuleEditorRowType)type
|
||||
@@ -450,9 +390,14 @@
|
||||
[self _setRowTypeToAddFromPlusButton:type];
|
||||
}
|
||||
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return editable;
|
||||
}
|
||||
|
||||
- (void)setEditable:(BOOL)value
|
||||
{
|
||||
[super setEditable:value]
|
||||
editable = value;
|
||||
// [self _updateEnabledStateForSubviews];
|
||||
[self _updateButtonVisibilities];
|
||||
}
|
||||
@@ -528,36 +473,22 @@
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)_addObservers
|
||||
{
|
||||
if (_isObserving)
|
||||
return;
|
||||
|
||||
[super _addObservers];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_textDidChange:) name:CPControlTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)_removeObservers
|
||||
{
|
||||
if (!_isObserving)
|
||||
return;
|
||||
|
||||
[super _removeObservers];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPControlTextDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
- (void)drawRect:(CPRect)rect
|
||||
{
|
||||
[super drawRect:rect];
|
||||
}
|
||||
|
||||
- (BOOL)_isRulePopup:(CPView)view
|
||||
{
|
||||
if ([view isKindOfClass:[CPPopUpButton class]])
|
||||
if ([view isKindOfClass:[_CPRuleEditorPopUpButton class]])
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)_isRuleStaticTextField:(CPView)view
|
||||
{
|
||||
if ([view isKindOfClass:[_CPRuleEditorTextField class]])
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -574,3 +505,30 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPRuleEditorTextField : CPTextField
|
||||
{
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self != nil)
|
||||
{
|
||||
[self setBordered:NO];
|
||||
[self setEditable:NO];
|
||||
[self setDrawsBackground:NO];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)hitTest:(CPPoint)point
|
||||
{
|
||||
if (!CPRectContainsPoint([self frame], point))
|
||||
return nil;
|
||||
|
||||
return [self superview];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -35,11 +35,6 @@
|
||||
CPArray _allowedFileTypes @accessors(property=allowedFileTypes);
|
||||
}
|
||||
|
||||
+ (CPURL)proposedFileURLWithDocumentName:(CPString)aDocumentName
|
||||
{
|
||||
return [CPURL URLWithString:aDocumentName];
|
||||
}
|
||||
|
||||
+ (id)savePanel
|
||||
{
|
||||
return [[CPSavePanel alloc] init];
|
||||
|
||||
+2
-2
@@ -43,9 +43,9 @@
|
||||
- (CGRect)visibleFrame
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
return CGRectMake(window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight);
|
||||
return _CGRectMake(window.screen.availLeft, window.screen.availTop, window.screen.availWidth, window.screen.availHeight);
|
||||
#else
|
||||
return CGRectMakeZero();
|
||||
return _CGRectMakeZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+148
-496
File diff suppressed because it is too large
Load Diff
+142
-142
@@ -23,16 +23,11 @@
|
||||
* 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"
|
||||
|
||||
@global CPApp
|
||||
|
||||
// CPScroller Constants
|
||||
@typedef CPScrollerPart
|
||||
CPScrollerNoPart = 0;
|
||||
CPScrollerDecrementPage = 1;
|
||||
CPScrollerKnob = 2;
|
||||
@@ -44,24 +39,25 @@ CPScrollerKnobSlot = 6;
|
||||
CPScrollerIncrementArrow = 0;
|
||||
CPScrollerDecrementArrow = 1;
|
||||
|
||||
@typedef CPUsableScrollerParts
|
||||
CPNoScrollerParts = 0;
|
||||
CPOnlyScrollerArrows = 1;
|
||||
CPAllScrollerParts = 2;
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPScroller
|
||||
*/
|
||||
|
||||
var PARTS_ARRANGEMENT = [CPScrollerKnobSlot, CPScrollerDecrementLine, CPScrollerIncrementLine, CPScrollerKnob],
|
||||
NAMES_FOR_PARTS = {},
|
||||
PARTS_FOR_NAMES = {};
|
||||
|
||||
var _CACHED_THEME_SCROLLER = nil; // This is used by the class methods to pull the theme attributes.
|
||||
|
||||
NAMES_FOR_PARTS[CPScrollerDecrementLine] = @"decrement-line";
|
||||
NAMES_FOR_PARTS[CPScrollerIncrementLine] = @"increment-line";
|
||||
NAMES_FOR_PARTS[CPScrollerKnobSlot] = @"knob-slot";
|
||||
NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
|
||||
|
||||
|
||||
@typedef CPScrollerStyle
|
||||
CPScrollerStyleLegacy = 0;
|
||||
CPScrollerStyleOverlay = 1;
|
||||
|
||||
@@ -73,13 +69,9 @@ CPThemeStateScrollViewLegacy = CPThemeState("scroller-style-legacy");
|
||||
CPThemeStateScrollerKnobLight = CPThemeState("scroller-knob-light");
|
||||
CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPScroller
|
||||
*/
|
||||
|
||||
@implementation CPScroller : CPControl
|
||||
{
|
||||
CPControlSize _controlSize;
|
||||
CPUsableScrollerParts _usableParts;
|
||||
CPArray _partRects;
|
||||
|
||||
@@ -101,29 +93,29 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Class methods
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
{
|
||||
return "scroller";
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
+ (id)themeAttributes
|
||||
{
|
||||
return @{
|
||||
@"scroller-width": 7.0,
|
||||
@"knob-slot-color": [CPNull null],
|
||||
@"decrement-line-color": [CPNull null],
|
||||
@"increment-line-color": [CPNull null],
|
||||
@"knob-color": [CPNull null],
|
||||
@"decrement-line-size": CGSizeMakeZero(),
|
||||
@"increment-line-size": CGSizeMakeZero(),
|
||||
@"track-inset": CGInsetMakeZero(),
|
||||
@"knob-inset": CGInsetMakeZero(),
|
||||
@"minimum-knob-length": 21.0,
|
||||
@"track-border-overlay": 9.0
|
||||
};
|
||||
return [CPDictionary dictionaryWithJSObject:{
|
||||
@"scroller-width": 7.0,
|
||||
@"knob-slot-color": [CPNull null],
|
||||
@"decrement-line-color": [CPNull null],
|
||||
@"increment-line-color": [CPNull null],
|
||||
@"knob-color": [CPNull null],
|
||||
@"decrement-line-size":_CGSizeMakeZero(),
|
||||
@"increment-line-size":_CGSizeMakeZero(),
|
||||
@"track-inset":_CGInsetMakeZero(),
|
||||
@"knob-inset": _CGInsetMakeZero(),
|
||||
@"minimum-knob-length":21.0,
|
||||
@"track-border-overlay": 9.0
|
||||
}];
|
||||
}
|
||||
|
||||
+ (float)scrollerWidth
|
||||
@@ -134,15 +126,13 @@ 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];
|
||||
var scroller = [[self alloc] init];
|
||||
|
||||
if (aStyle == CPScrollerStyleLegacy)
|
||||
return [_CACHED_THEME_SCROLLER valueForThemeAttribute:@"scroller-width" inState:CPThemeStateScrollViewLegacy];
|
||||
|
||||
return [_CACHED_THEME_SCROLLER currentValueForThemeAttribute:@"scroller-width"];
|
||||
return [scroller valueForThemeAttribute:@"scroller-width" inState:CPThemeStateScrollViewLegacy];
|
||||
return [scroller currentValueForThemeAttribute:@"scroller-width"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -150,10 +140,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
*/
|
||||
+ (float)scrollerOverlay
|
||||
{
|
||||
if (!_CACHED_THEME_SCROLLER)
|
||||
_CACHED_THEME_SCROLLER = [[self alloc] init];
|
||||
|
||||
return [_CACHED_THEME_SCROLLER currentValueForThemeAttribute:@"track-border-overlay"];
|
||||
return [[[self alloc] init] currentValueForThemeAttribute:@"track-border-overlay"];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -166,13 +153,14 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Initialization
|
||||
#pragma mark -
|
||||
#pragma mark Initialization
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_controlSize = CPRegularControlSize;
|
||||
_partRects = [];
|
||||
|
||||
[self setFloatValue:0.0];
|
||||
@@ -182,33 +170,27 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
_allowFadingOut = YES;
|
||||
_isMouseOver = NO;
|
||||
_style = CPScrollerStyleOverlay;
|
||||
|
||||
var paramAnimFadeOut = @{
|
||||
CPViewAnimationTargetKey: self,
|
||||
CPViewAnimationEffectKey: CPViewAnimationFadeOutEffect,
|
||||
};
|
||||
var paramAnimFadeOut = [CPDictionary dictionaryWithObjects:[self, CPViewAnimationFadeOutEffect]
|
||||
forKeys:[CPViewAnimationTargetKey, CPViewAnimationEffectKey]];
|
||||
|
||||
_animationScroller = [[CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut];
|
||||
[_animationScroller setViewAnimations:[paramAnimFadeOut]];
|
||||
[_animationScroller setDelegate:self];
|
||||
[self setAlphaValue:0.0];
|
||||
|
||||
// We have to choose an orientation. If for some bizarre reason width === height,
|
||||
// punt and choose vertical.
|
||||
[self _setIsVertical:CGRectGetHeight(aFrame) >= CGRectGetWidth(aFrame)];
|
||||
[self _calculateIsVertical];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Getters / Setters
|
||||
#pragma mark -
|
||||
#pragma mark Getters / Setters
|
||||
|
||||
/*!
|
||||
Returns the scroller's style
|
||||
*/
|
||||
- (int)style
|
||||
- (void)style
|
||||
{
|
||||
return _style;
|
||||
}
|
||||
@@ -217,7 +199,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 +208,6 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
if (_style === CPScrollerStyleLegacy)
|
||||
{
|
||||
_allowFadingOut = NO;
|
||||
[self fadeIn];
|
||||
[self setThemeState:CPThemeStateScrollViewLegacy];
|
||||
}
|
||||
@@ -236,7 +217,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
[self unsetThemeState:CPThemeStateScrollViewLegacy];
|
||||
}
|
||||
|
||||
//[self _adjustScrollerSize];
|
||||
[self _adjustScrollerSize];
|
||||
}
|
||||
|
||||
- (void)setObjectValue:(id)aValue
|
||||
@@ -244,6 +225,29 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
[super setObjectValue:MIN(1.0, MAX(0.0, +aValue))];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the scroller's control size
|
||||
*/
|
||||
- (CPControlSize)controlSize
|
||||
{
|
||||
return _controlSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the scroller's size.
|
||||
@param aControlSize the scroller's size
|
||||
*/
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
if (_controlSize == aControlSize)
|
||||
return;
|
||||
|
||||
_controlSize = aControlSize;
|
||||
|
||||
[self setNeedsLayout];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return's the knob's proportion
|
||||
*/
|
||||
@@ -258,8 +262,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 +272,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Privates
|
||||
#pragma mark -
|
||||
#pragma mark Privates
|
||||
|
||||
/*! @ignore */
|
||||
- (void)_adjustScrollerSize
|
||||
@@ -294,13 +298,13 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Utilities
|
||||
#pragma mark -
|
||||
#pragma mark Utilities
|
||||
|
||||
- (CGRect)rectForPart:(CPScrollerPart)aPart
|
||||
{
|
||||
if (aPart == CPScrollerNoPart)
|
||||
return CGRectMakeZero();
|
||||
return _CGRectMakeZero();
|
||||
|
||||
return _partRects[aPart];
|
||||
}
|
||||
@@ -317,7 +321,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
// The ordering of these tests is important. We check the knob and
|
||||
// page rects first since they may overlap with the arrows.
|
||||
|
||||
if (![self hasThemeState:CPThemeStateSelected] && ![self hasThemeState:CPThemeStateScrollViewLegacy])
|
||||
if (![self hasThemeState:CPThemeStateSelected])
|
||||
return CPScrollerNoPart;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
|
||||
@@ -370,8 +374,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
var knobInset = [self currentValueForThemeAttribute:@"knob-inset"],
|
||||
trackInset = [self currentValueForThemeAttribute:@"track-inset"],
|
||||
width = CGRectGetWidth(bounds),
|
||||
height = CGRectGetHeight(bounds);
|
||||
width = _CGRectGetWidth(bounds),
|
||||
height = _CGRectGetHeight(bounds);
|
||||
|
||||
if ([self isVertical])
|
||||
{
|
||||
@@ -381,26 +385,25 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
effectiveIncrementLineHeight = incrementLineSize.height + trackInset.bottom,
|
||||
slotSize = height - effectiveDecrementLineHeight - effectiveIncrementLineHeight,
|
||||
minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
|
||||
knobVerticalInset = knobInset.top + knobInset.bottom,
|
||||
knobWidth = width - knobInset.left - knobInset.right,
|
||||
knobHeight = MAX(minimumKnobLength, ((slotSize - knobVerticalInset) * _knobProportion)),
|
||||
knobLocation = effectiveDecrementLineHeight + (slotSize - knobHeight - knobVerticalInset) * [self floatValue] + knobInset.top;
|
||||
knobHeight = MAX(minimumKnobLength, (slotSize * _knobProportion)),
|
||||
knobLocation = effectiveDecrementLineHeight + (slotSize - knobHeight) * [self floatValue];
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = CGRectMake(0.0, effectiveDecrementLineHeight, width, knobLocation - effectiveDecrementLineHeight);
|
||||
_partRects[CPScrollerKnob] = CGRectMake(knobInset.left, knobLocation, knobWidth, knobHeight);
|
||||
_partRects[CPScrollerIncrementPage] = CGRectMake(0.0, knobLocation + knobHeight, width, height - (knobLocation + knobHeight) - effectiveIncrementLineHeight);
|
||||
_partRects[CPScrollerKnobSlot] = CGRectMake(trackInset.left, effectiveDecrementLineHeight, width - trackInset.left - trackInset.right, slotSize);
|
||||
_partRects[CPScrollerDecrementLine] = CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
|
||||
_partRects[CPScrollerIncrementLine] = CGRectMake(0.0, height - incrementLineSize.height, incrementLineSize.width, incrementLineSize.height);
|
||||
_partRects[CPScrollerDecrementPage] = _CGRectMake(0.0, effectiveDecrementLineHeight, width, knobLocation - effectiveDecrementLineHeight);
|
||||
_partRects[CPScrollerKnob] = _CGRectMake(knobInset.left, knobLocation, knobWidth, knobHeight);
|
||||
_partRects[CPScrollerIncrementPage] = _CGRectMake(0.0, knobLocation + knobHeight, width, height - (knobLocation + knobHeight) - effectiveIncrementLineHeight);
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(trackInset.left, effectiveDecrementLineHeight, width - trackInset.left - trackInset.right, slotSize);
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMake(0.0, height - incrementLineSize.height, incrementLineSize.width, incrementLineSize.height);
|
||||
|
||||
if (height < knobHeight + decrementLineSize.height + incrementLineSize.height + trackInset.top + trackInset.bottom)
|
||||
_partRects[CPScrollerKnob] = CGRectMakeZero();
|
||||
_partRects[CPScrollerKnob] = _CGRectMakeZero();
|
||||
|
||||
if (height < decrementLineSize.height + incrementLineSize.height - 2)
|
||||
{
|
||||
_partRects[CPScrollerIncrementLine] = CGRectMakeZero();
|
||||
_partRects[CPScrollerDecrementLine] = CGRectMakeZero();
|
||||
_partRects[CPScrollerKnobSlot] = CGRectMake(trackInset.left, 0, width - trackInset.left - trackInset.right, height);
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(trackInset.left, 0, width - trackInset.left - trackInset.right, height);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -411,26 +414,25 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
effectiveIncrementLineWidth = incrementLineSize.width + trackInset.right,
|
||||
slotSize = width - effectiveDecrementLineWidth - effectiveIncrementLineWidth,
|
||||
minimumKnobLength = [self currentValueForThemeAttribute:"minimum-knob-length"],
|
||||
knobHorizontalInset = knobInset.left + knobInset.right,
|
||||
knobWidth = MAX(minimumKnobLength, ((slotSize - knobHorizontalInset) * _knobProportion)),
|
||||
knobWidth = MAX(minimumKnobLength, (slotSize * _knobProportion)),
|
||||
knobHeight = height - knobInset.top - knobInset.bottom,
|
||||
knobLocation = effectiveDecrementLineWidth + (slotSize - knobWidth - knobHorizontalInset) * [self floatValue] + knobInset.left;
|
||||
knobLocation = effectiveDecrementLineWidth + (slotSize - knobWidth) * [self floatValue];
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = CGRectMake(effectiveDecrementLineWidth, 0.0, knobLocation - effectiveDecrementLineWidth, height);
|
||||
_partRects[CPScrollerKnob] = CGRectMake(knobLocation, knobInset.top, knobWidth, knobHeight);
|
||||
_partRects[CPScrollerIncrementPage] = CGRectMake(knobLocation + knobWidth, 0.0, width - (knobLocation + knobWidth) - effectiveIncrementLineWidth, height);
|
||||
_partRects[CPScrollerKnobSlot] = CGRectMake(effectiveDecrementLineWidth, trackInset.top, slotSize, height - trackInset.top - trackInset.bottom);
|
||||
_partRects[CPScrollerDecrementLine] = CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
|
||||
_partRects[CPScrollerIncrementLine] = CGRectMake(width - incrementLineSize.width, 0.0, incrementLineSize.width, incrementLineSize.height);
|
||||
_partRects[CPScrollerDecrementPage] = _CGRectMake(effectiveDecrementLineWidth, 0.0, knobLocation - effectiveDecrementLineWidth, height);
|
||||
_partRects[CPScrollerKnob] = _CGRectMake(knobLocation, knobInset.top, knobWidth, knobHeight);
|
||||
_partRects[CPScrollerIncrementPage] = _CGRectMake(knobLocation + knobWidth, 0.0, width - (knobLocation + knobWidth) - effectiveIncrementLineWidth, height);
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(effectiveDecrementLineWidth, trackInset.top, slotSize, height - trackInset.top - trackInset.bottom);
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, decrementLineSize.width, decrementLineSize.height);
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMake(width - incrementLineSize.width, 0.0, incrementLineSize.width, incrementLineSize.height);
|
||||
|
||||
if (width < knobWidth + decrementLineSize.width + incrementLineSize.width + trackInset.left + trackInset.right)
|
||||
_partRects[CPScrollerKnob] = CGRectMakeZero();
|
||||
_partRects[CPScrollerKnob] = _CGRectMakeZero();
|
||||
|
||||
if (width < decrementLineSize.width + incrementLineSize.width - 2)
|
||||
{
|
||||
_partRects[CPScrollerIncrementLine] = CGRectMakeZero();
|
||||
_partRects[CPScrollerDecrementLine] = CGRectMakeZero();
|
||||
_partRects[CPScrollerKnobSlot] = CGRectMake(0.0, 0.0, width, slotSize);
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(0.0, 0.0, width, slotSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -463,12 +465,15 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
*/
|
||||
- (void)fadeOut
|
||||
{
|
||||
if ([self hasThemeState:CPThemeStateScrollViewLegacy])
|
||||
return;
|
||||
|
||||
[_animationScroller startAnimation];
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Drawing
|
||||
#pragma mark -
|
||||
#pragma mark Drawing
|
||||
|
||||
/*!
|
||||
Draws the specified arrow and sets the highlight.
|
||||
@@ -495,7 +500,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
- (CPView)createViewForPart:(CPScrollerPart)aPart
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
@@ -509,7 +514,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
- (CPView)createEphemeralSubviewNamed:(CPString)aName
|
||||
{
|
||||
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
var view = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[view setHitTests:NO];
|
||||
|
||||
@@ -518,7 +523,6 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[self _adjustScrollerSize];
|
||||
[self checkSpaceForParts];
|
||||
|
||||
var index = 0,
|
||||
@@ -584,7 +588,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
{
|
||||
var knobRect = [self rectForPart:CPScrollerKnob],
|
||||
knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
|
||||
remainder = ![self isVertical] ? (CGRectGetWidth(knobSlotRect) - CGRectGetWidth(knobRect)) : (CGRectGetHeight(knobSlotRect) - CGRectGetHeight(knobRect));
|
||||
remainder = ![self isVertical] ? (_CGRectGetWidth(knobSlotRect) - _CGRectGetWidth(knobRect)) : (_CGRectGetHeight(knobSlotRect) - _CGRectGetHeight(knobRect));
|
||||
|
||||
if (remainder <= 0)
|
||||
[self setFloatValue:0.0];
|
||||
@@ -629,20 +633,20 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
if ([anEvent modifierFlags] & CPAlternateKeyMask)
|
||||
{
|
||||
if (_trackingPart === CPScrollerDecrementLine)
|
||||
if (_trackingPart == CPScrollerDecrementLine)
|
||||
_hitPart = CPScrollerDecrementPage;
|
||||
|
||||
else if (_trackingPart === CPScrollerIncrementLine)
|
||||
else if (_trackingPart == CPScrollerIncrementLine)
|
||||
_hitPart = CPScrollerIncrementPage;
|
||||
|
||||
else if (_trackingPart === CPScrollerDecrementPage || _trackingPart === CPScrollerIncrementPage)
|
||||
else if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
|
||||
{
|
||||
var knobRect = [self rectForPart:CPScrollerKnob],
|
||||
knobWidth = ![self isVertical] ? CGRectGetWidth(knobRect) : CGRectGetHeight(knobRect),
|
||||
knobWidth = ![self isVertical] ? _CGRectGetWidth(knobRect) : _CGRectGetHeight(knobRect),
|
||||
knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
|
||||
remainder = (![self isVertical] ? CGRectGetWidth(knobSlotRect) : CGRectGetHeight(knobSlotRect)) - knobWidth;
|
||||
remainder = (![self isVertical] ? _CGRectGetWidth(knobSlotRect) : _CGRectGetHeight(knobSlotRect)) - knobWidth;
|
||||
|
||||
[self setFloatValue:((![self isVertical] ? _trackingStartPoint.x - CGRectGetMinX(knobSlotRect) : _trackingStartPoint.y - CGRectGetMinY(knobSlotRect)) - knobWidth / 2.0) / remainder];
|
||||
[self setFloatValue:((![self isVertical] ? _trackingStartPoint.x - _CGRectGetMinX(knobSlotRect) : _trackingStartPoint.y - _CGRectGetMinY(knobSlotRect)) - knobWidth / 2.0) / remainder];
|
||||
|
||||
_hitPart = CPScrollerKnob;
|
||||
|
||||
@@ -663,11 +667,11 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
{
|
||||
_trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if (_trackingPart === CPScrollerDecrementPage || _trackingPart === CPScrollerIncrementPage)
|
||||
if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
|
||||
{
|
||||
var hitPart = [self testPart:[anEvent locationInWindow]];
|
||||
|
||||
if (hitPart === CPScrollerDecrementPage || hitPart === CPScrollerIncrementPage)
|
||||
if (hitPart == CPScrollerDecrementPage || hitPart == CPScrollerIncrementPage)
|
||||
{
|
||||
_trackingPart = hitPart;
|
||||
_hitPart = hitPart;
|
||||
@@ -683,13 +687,18 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
}
|
||||
|
||||
- (void)_setIsVertical:(BOOL)isVertical
|
||||
- (void)_calculateIsVertical
|
||||
{
|
||||
_isVertical = isVertical;
|
||||
// Recalculate isVertical.
|
||||
var bounds = [self bounds],
|
||||
width = _CGRectGetWidth(bounds),
|
||||
height = _CGRectGetHeight(bounds);
|
||||
|
||||
if (_isVertical)
|
||||
_isVertical = width < height ? 1 : (width > height ? 0 : -1);
|
||||
|
||||
if (_isVertical === 1)
|
||||
[self setThemeState:CPThemeStateVertical];
|
||||
else
|
||||
else if (_isVertical === 0)
|
||||
[self unsetThemeState:CPThemeStateVertical];
|
||||
}
|
||||
|
||||
@@ -702,15 +711,15 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Overrides
|
||||
#pragma mark -
|
||||
#pragma mark Overrides
|
||||
|
||||
- (id)currentValueForThemeAttribute:(CPString)anAttributeName
|
||||
{
|
||||
var themeState = _themeState;
|
||||
|
||||
if (NAMES_FOR_PARTS[_hitPart] + "-color" !== anAttributeName)
|
||||
themeState = themeState.without(CPThemeStateHighlighted);
|
||||
themeState &= ~CPThemeStateHighlighted;
|
||||
|
||||
return [self valueForThemeAttribute:anAttributeName inState:themeState];
|
||||
}
|
||||
@@ -724,14 +733,12 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
switch (_hitPart)
|
||||
{
|
||||
case CPScrollerKnob:
|
||||
return [self trackKnob:anEvent];
|
||||
case CPScrollerKnob: return [self trackKnob:anEvent];
|
||||
|
||||
case CPScrollerDecrementLine:
|
||||
case CPScrollerIncrementLine:
|
||||
case CPScrollerDecrementPage:
|
||||
case CPScrollerIncrementPage:
|
||||
return [self trackScrollButtons:anEvent];
|
||||
case CPScrollerIncrementPage: return [self trackScrollButtons:anEvent];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,28 +766,18 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
if ([self isHidden] || ![self isEnabled] || !_isMouseOver)
|
||||
return;
|
||||
|
||||
_allowFadingOut = (_style !== CPScrollerStyleLegacy);
|
||||
_allowFadingOut = YES;
|
||||
_isMouseOver = NO;
|
||||
|
||||
if (_timerFadeOut)
|
||||
[_timerFadeOut invalidate];
|
||||
|
||||
if ([self hasThemeState:CPThemeStateScrollViewLegacy])
|
||||
[self unsetThemeState:CPThemeStateSelected];
|
||||
else
|
||||
_timerFadeOut = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_performFadeOut:) userInfo:nil repeats:NO];
|
||||
_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
|
||||
{
|
||||
@@ -789,9 +786,9 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
|
||||
|
||||
@end
|
||||
|
||||
var CPScrollerIsVerticalKey = @"CPScrollerIsVerticalKey",
|
||||
var CPScrollerControlSizeKey = @"CPScrollerControlSize",
|
||||
CPScrollerKnobProportionKey = @"CPScrollerKnobProportion",
|
||||
CPScrollerStyleKey = @"CPScrollerStyleKey";
|
||||
CPScrollerStyleKey = @"CPScrollerStyleKey";
|
||||
|
||||
@implementation CPScroller (CPCoding)
|
||||
|
||||
@@ -799,6 +796,11 @@ var CPScrollerIsVerticalKey = @"CPScrollerIsVerticalKey",
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
if ([aCoder containsValueForKey:CPScrollerControlSizeKey])
|
||||
_controlSize = [aCoder decodeIntForKey:CPScrollerControlSizeKey];
|
||||
|
||||
_knobProportion = 1.0;
|
||||
|
||||
if ([aCoder containsValueForKey:CPScrollerKnobProportionKey])
|
||||
@@ -811,19 +813,17 @@ var CPScrollerIsVerticalKey = @"CPScrollerIsVerticalKey",
|
||||
_allowFadingOut = YES;
|
||||
_isMouseOver = NO;
|
||||
|
||||
var paramAnimFadeOut = @{
|
||||
CPViewAnimationTargetKey: self,
|
||||
CPViewAnimationEffectKey: CPViewAnimationFadeOutEffect,
|
||||
};
|
||||
var paramAnimFadeOut = [CPDictionary dictionaryWithObjects:[self, CPViewAnimationFadeOutEffect]
|
||||
forKeys:[CPViewAnimationTargetKey, CPViewAnimationEffectKey]];
|
||||
|
||||
_animationScroller = [[CPViewAnimation alloc] initWithDuration:0.2 animationCurve:CPAnimationEaseInOut];
|
||||
[_animationScroller setViewAnimations:[paramAnimFadeOut]];
|
||||
[_animationScroller setDelegate:self];
|
||||
[self setAlphaValue:0.0];
|
||||
|
||||
[self setStyle:[aCoder decodeIntForKey:CPScrollerStyleKey]];
|
||||
[self _calculateIsVertical];
|
||||
|
||||
[self _setIsVertical:[aCoder decodeBoolForKey:CPScrollerIsVerticalKey]];
|
||||
[self setStyle:[aCoder decodeIntForKey:CPScrollerStyleKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -833,7 +833,7 @@ var CPScrollerIsVerticalKey = @"CPScrollerIsVerticalKey",
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeInt:_isVertical forKey:CPScrollerIsVerticalKey];
|
||||
[aCoder encodeInt:_controlSize forKey:CPScrollerControlSizeKey];
|
||||
[aCoder encodeFloat:_knobProportion forKey:CPScrollerKnobProportionKey];
|
||||
[aCoder encodeInt:_style forKey:CPScrollerStyleKey];
|
||||
}
|
||||
|
||||
+89
-413
@@ -21,25 +21,26 @@
|
||||
*/
|
||||
|
||||
@import "CPButton.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPMenuItem.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPAnimationContext.j"
|
||||
@import "CPViewAnimator.j"
|
||||
@import "CPArrayController.j"
|
||||
|
||||
@class CPUserDefaults
|
||||
@class CALayer
|
||||
|
||||
@global CPApp
|
||||
|
||||
CPSearchFieldRecentsTitleMenuItemTag = 1000;
|
||||
CPSearchFieldRecentsMenuItemTag = 1001;
|
||||
CPSearchFieldClearRecentsMenuItemTag = 1002;
|
||||
CPSearchFieldNoRecentsMenuItemTag = 1003;
|
||||
|
||||
var CPSearchFieldSearchImage = nil,
|
||||
CPSearchFieldFindImage = nil,
|
||||
CPSearchFieldCancelImage = nil,
|
||||
CPSearchFieldCancelPressedImage = nil;
|
||||
|
||||
var SEARCH_BUTTON_DEFAULT_WIDTH = 25.0,
|
||||
CANCEL_BUTTON_DEFAULT_WIDTH = 22.0,
|
||||
BUTTON_DEFAULT_HEIGHT = 22.0;
|
||||
|
||||
var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotification";
|
||||
|
||||
var RECENT_SEARCH_PREFIX = @" ";
|
||||
|
||||
/*!
|
||||
@ingroup appkit
|
||||
@class CPSearchField
|
||||
@@ -62,9 +63,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
BOOL _sendsSearchStringImmediately;
|
||||
BOOL _canResignFirstResponder;
|
||||
CPTimer _partialStringTimer;
|
||||
|
||||
CPView _contentView;
|
||||
BOOL _isBecomingFirstResponder;
|
||||
}
|
||||
|
||||
+ (CPString)defaultThemeClass
|
||||
@@ -72,28 +70,16 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
return @"searchfield"
|
||||
}
|
||||
|
||||
+ (CPDictionary)themeAttributes
|
||||
+ (void)initialize
|
||||
{
|
||||
return @{
|
||||
@"image-search": [CPNull null],
|
||||
@"image-find": [CPNull null],
|
||||
@"image-cancel": [CPNull null],
|
||||
@"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)
|
||||
};
|
||||
}
|
||||
if (self !== [CPSearchField class])
|
||||
return;
|
||||
|
||||
+ (Class)_binderClassForBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPPredicateBinding)
|
||||
return [_CPSearchFieldPredicateBinder class];
|
||||
|
||||
return [super _binderClassForBinding:aBinding];
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
CPSearchFieldSearchImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldSearch.png"] size:_CGSizeMake(SEARCH_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT)];
|
||||
CPSearchFieldFindImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldFind.png"] size:_CGSizeMake(SEARCH_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT)];
|
||||
CPSearchFieldCancelImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldCancel.png"] size:_CGSizeMake(CANCEL_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT)];
|
||||
CPSearchFieldCancelPressedImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSearchField/CPSearchFieldCancelPressed.png"] size:_CGSizeMake(CANCEL_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT)];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
@@ -106,6 +92,10 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
_recentsAutosaveName = nil;
|
||||
|
||||
[self _init];
|
||||
#if PLATFORM(DOM)
|
||||
_cancelButton._DOMElement.style.cursor = "default";
|
||||
_searchButton._DOMElement.style.cursor = "default";
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -132,31 +122,18 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
[self resetSearchButton];
|
||||
|
||||
_canResignFirstResponder = YES;
|
||||
_isBecomingFirstResponder = NO;
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
// MARK: Override observers
|
||||
|
||||
- (void)_removeObservers
|
||||
- (void)viewWillMoveToSuperview:(CPView)aView
|
||||
{
|
||||
if (!_isObserving)
|
||||
return;
|
||||
|
||||
[super _removeObservers];
|
||||
[super viewWillMoveToSuperview:aView];
|
||||
|
||||
// First we remove any observer that may have been in place to avoid memory leakage.
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPControlTextDidChangeNotification object:self];
|
||||
}
|
||||
|
||||
- (void)_addObservers
|
||||
{
|
||||
if (_isObserving)
|
||||
return;
|
||||
|
||||
[super _addObservers];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_searchFieldTextDidChange:) name:CPControlTextDidChangeNotification object:self];
|
||||
// Register the observe here if we need to.
|
||||
if (aView)
|
||||
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_searchFieldTextDidChange:) name:CPControlTextDidChangeNotification object:self];
|
||||
}
|
||||
|
||||
// Managing Buttons
|
||||
@@ -193,7 +170,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
- (void)resetSearchButton
|
||||
{
|
||||
var button = [self searchButton],
|
||||
searchButtonImage = (_searchMenuTemplate == nil) ? [self currentValueForThemeAttribute:@"image-search"] : [self currentValueForThemeAttribute:@"image-find"];
|
||||
searchButtonImage = (_searchMenuTemplate === nil) ? CPSearchFieldSearchImage : CPSearchFieldFindImage;
|
||||
|
||||
[button setBordered:NO];
|
||||
[button setImageScaling:CPImageScaleAxesIndependently];
|
||||
@@ -216,7 +193,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
[_cancelButton setAutoresizingMask:CPViewMinXMargin];
|
||||
[_cancelButton setTarget:self];
|
||||
[_cancelButton setAction:@selector(cancelOperation:)];
|
||||
[_cancelButton setButtonType:CPMomentaryChangeButton];
|
||||
[self _updateCancelButtonVisibility];
|
||||
[self addSubview:_cancelButton];
|
||||
}
|
||||
@@ -240,8 +216,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:CPSearchFieldCancelImage];
|
||||
[button setAlternateImage:CPSearchFieldCancelPressedImage];
|
||||
[button setAutoresizingMask:CPViewMinXMargin];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(cancelOperation:)];
|
||||
@@ -257,23 +233,22 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
- (CGRect)searchTextRectForBounds:(CGRect)rect
|
||||
{
|
||||
var leftOffset = 0,
|
||||
width = CGRectGetWidth(rect),
|
||||
width = _CGRectGetWidth(rect),
|
||||
bounds = [self bounds];
|
||||
|
||||
if (_searchButton)
|
||||
{
|
||||
var searchBounds = [self searchButtonRectForBounds:bounds],
|
||||
rightMargin = [self _potentialCurrentValueForThemeAttribute:@"search-right-margin"];
|
||||
leftOffset = CGRectGetMaxX(searchBounds) + rightMargin;
|
||||
var searchBounds = [self searchButtonRectForBounds:bounds];
|
||||
leftOffset = _CGRectGetMaxX(searchBounds) + 2;
|
||||
}
|
||||
|
||||
if (_cancelButton)
|
||||
{
|
||||
var cancelRect = [self cancelButtonRectForBounds:bounds];
|
||||
width = CGRectGetMinX(cancelRect) - leftOffset;
|
||||
width = _CGRectGetMinX(cancelRect) - leftOffset;
|
||||
}
|
||||
|
||||
return CGRectMake(leftOffset, CGRectGetMinY(rect), width, CGRectGetHeight(rect));
|
||||
return _CGRectMake(leftOffset, _CGRectGetMinY(rect), width, _CGRectGetHeight(rect));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -283,18 +258,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
*/
|
||||
- (CGRect)searchButtonRectForBounds:(CGRect)rect
|
||||
{
|
||||
var themedRectFunction = [self _potentialCurrentValueForThemeAttribute:@"search-button-rect-function"];
|
||||
|
||||
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(5, (_CGRectGetHeight(rect) - BUTTON_DEFAULT_HEIGHT) / 2, SEARCH_BUTTON_DEFAULT_WIDTH, BUTTON_DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -304,10 +268,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
*/
|
||||
- (CGRect)cancelButtonRectForBounds:(CGRect)rect
|
||||
{
|
||||
var size = [[self _potentialCurrentValueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero(),
|
||||
inset = [self _potentialCurrentValueForThemeAttribute:@"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);
|
||||
return _CGRectMake(_CGRectGetWidth(rect) - CANCEL_BUTTON_DEFAULT_WIDTH - 5, (_CGRectGetHeight(rect) - CANCEL_BUTTON_DEFAULT_WIDTH) / 2, BUTTON_DEFAULT_HEIGHT, BUTTON_DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
// Managing Menu Templates
|
||||
@@ -411,9 +372,10 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
*/
|
||||
- (void)setRecentSearches:(CPArray)searches
|
||||
{
|
||||
var max = MIN([self maximumRecents], [searches count]);
|
||||
var max = MIN([self maximumRecents], [searches count]),
|
||||
searches = [searches subarrayWithRange:CPMakeRange(0, max)];
|
||||
|
||||
_recentSearches = [searches subarrayWithRange:CPMakeRange(0, max)];
|
||||
_recentSearches = searches;
|
||||
[self _autosaveRecentSearchList];
|
||||
}
|
||||
|
||||
@@ -496,9 +458,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
|
||||
- (void)sendAction:(SEL)anAction to:(id)anObject
|
||||
{
|
||||
[self selectAll:nil];
|
||||
[super sendAction:anAction to:anObject];
|
||||
|
||||
[_partialStringTimer invalidate];
|
||||
@@ -509,7 +470,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];
|
||||
@@ -521,7 +482,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
- (CPView)hitTest:(CGPoint)aPoint
|
||||
{
|
||||
// Make sure a hit anywhere within the search field returns the search field itself
|
||||
if (CGRectContainsPoint([self frame], aPoint))
|
||||
if (_CGRectContainsPoint([self frame], aPoint))
|
||||
return self;
|
||||
else
|
||||
return nil;
|
||||
@@ -537,7 +498,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
var location = [anEvent locationInWindow],
|
||||
point = [self convertPoint:location fromView:nil];
|
||||
|
||||
if (CGRectContainsPoint([self searchButtonRectForBounds:[self bounds]], point))
|
||||
if (_CGRectContainsPoint([self searchButtonRectForBounds:[self bounds]], point))
|
||||
{
|
||||
if (_searchMenuTemplate == nil)
|
||||
{
|
||||
@@ -549,23 +510,26 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
else
|
||||
[self _showMenu];
|
||||
}
|
||||
else if (CGRectContainsPoint([self cancelButtonRectForBounds:[self bounds]], point))
|
||||
else if (_CGRectContainsPoint([self cancelButtonRectForBounds:[self bounds]], point))
|
||||
[_cancelButton mouseDown:anEvent];
|
||||
else
|
||||
[super mouseDown:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides the common case items for a recent searches menu.
|
||||
Provides the common case items for a recent searches menu. If there are not recent searches,
|
||||
displays a single disabled item:
|
||||
|
||||
No Recent Searches
|
||||
|
||||
If there are 1 more recent searches, it displays:
|
||||
|
||||
Clear
|
||||
---------------------
|
||||
Recent Searches
|
||||
recent search 1
|
||||
recent search 2
|
||||
etc.
|
||||
recent search 1
|
||||
recent search 2
|
||||
etc.
|
||||
---------------------
|
||||
Clear Recent Searches
|
||||
|
||||
If you wish to add items before or after the template, you can. If you put items
|
||||
before, a separator will automatically be placed before the default template item.
|
||||
@@ -590,15 +554,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
var template = [[CPMenu alloc] init],
|
||||
item;
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Clear"
|
||||
action:@selector(_searchFieldClearRecents:)
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldClearRecentsMenuItemTag];
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
[self _addSeparatorToMenu:template];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Recent Searches"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
@@ -613,20 +568,32 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"Clear Recent Searches"
|
||||
action:@selector(_searchFieldClearRecents:)
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldClearRecentsMenuItemTag];
|
||||
[item setTarget:self];
|
||||
[template addItem:item];
|
||||
|
||||
item = [[CPMenuItem alloc] initWithTitle:@"No Recent Searches"
|
||||
action:nil
|
||||
keyEquivalent:@""];
|
||||
[item setTag:CPSearchFieldNoRecentsMenuItemTag];
|
||||
[item setEnabled:NO];
|
||||
[template addItem:item];
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
- (void)_updateSearchMenu
|
||||
{
|
||||
if (_searchMenuTemplate == nil)
|
||||
if (_searchMenuTemplate === nil)
|
||||
return;
|
||||
|
||||
var menu = [[CPMenu alloc] init],
|
||||
countOfRecents = [_recentSearches count],
|
||||
numberOfItems = [_searchMenuTemplate numberOfItems];
|
||||
|
||||
[menu setAutoenablesItems:NO];
|
||||
|
||||
for (var i = 0; i < numberOfItems; i++)
|
||||
{
|
||||
var item = [[_searchMenuTemplate itemAtIndex:i] copy];
|
||||
@@ -636,6 +603,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
case CPSearchFieldRecentsTitleMenuItemTag:
|
||||
if (countOfRecents === 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
break;
|
||||
|
||||
case CPSearchFieldRecentsMenuItemTag:
|
||||
@@ -644,7 +614,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
|
||||
for (var recentIndex = 0; recentIndex < countOfRecents; ++recentIndex)
|
||||
{
|
||||
var recentItem = [[CPMenuItem alloc] initWithTitle:[_recentSearches objectAtIndex:recentIndex]
|
||||
// RECENT_SEARCH_PREFIX is a hack until CPMenuItem -setIndentationLevel works
|
||||
var recentItem = [[CPMenuItem alloc] initWithTitle:RECENT_SEARCH_PREFIX + [_recentSearches objectAtIndex:recentIndex]
|
||||
action:itemAction
|
||||
keyEquivalent:[item keyEquivalent]];
|
||||
[item setTarget:self];
|
||||
@@ -658,6 +629,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
if (countOfRecents === 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
|
||||
[item setAction:@selector(_searchFieldClearRecents:)];
|
||||
[item setTarget:self];
|
||||
break;
|
||||
@@ -665,6 +639,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
case CPSearchFieldNoRecentsMenuItemTag:
|
||||
if (countOfRecents !== 0)
|
||||
continue;
|
||||
|
||||
if ([menu numberOfItems] > 0)
|
||||
[self _addSeparatorToMenu:menu];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -681,7 +658,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
{
|
||||
var separator = [CPMenuItem separatorItem];
|
||||
[separator setEnabled:NO];
|
||||
[separator setTag:CPSearchFieldRecentsTitleMenuItemTag];
|
||||
[aMenu addItem:separator];
|
||||
}
|
||||
|
||||
@@ -703,8 +679,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
|
||||
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 = CPMakePoint(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];
|
||||
|
||||
@@ -720,19 +695,15 @@ 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
|
||||
{
|
||||
var searchString = [sender title];
|
||||
var searchString = [[sender title] substringFromIndex:[RECENT_SEARCH_PREFIX length]];
|
||||
|
||||
if ([sender tag] != CPSearchFieldRecentsMenuItemTag)
|
||||
[self _addStringToRecentSearches:searchString];
|
||||
@@ -777,266 +748,17 @@ 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];
|
||||
|
||||
// our CPPredicateBinding binder adds a binding to the value.
|
||||
// this private binding has to be also removed
|
||||
if (aBinding === CPPredicateBinding)
|
||||
[[[self class] _binderClassForBinding:aBinding] unbind:CPValueBinding forObject:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
@implementation CPSearchField (CPTrackingArea)
|
||||
{
|
||||
CPTrackingArea _searchButtonTrackingArea;
|
||||
CPTrackingArea _cancelButtonTrackingArea;
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (_searchButtonTrackingArea)
|
||||
{
|
||||
[self removeTrackingArea:_searchButtonTrackingArea];
|
||||
_searchButtonTrackingArea = nil;
|
||||
}
|
||||
|
||||
if (_cancelButtonTrackingArea)
|
||||
{
|
||||
[self removeTrackingArea:_cancelButtonTrackingArea];
|
||||
_cancelButtonTrackingArea = nil;
|
||||
}
|
||||
|
||||
if (_searchButton)
|
||||
{
|
||||
_searchButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:[_searchButton frame]
|
||||
options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
|
||||
owner:self
|
||||
userInfo:@{ @"isButton": YES }];
|
||||
|
||||
[self addTrackingArea:_searchButtonTrackingArea];
|
||||
}
|
||||
|
||||
if (_cancelButton && ![_cancelButton isHidden])
|
||||
{
|
||||
_cancelButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:[_cancelButton frame]
|
||||
options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
|
||||
owner:self
|
||||
userInfo:@{ @"isButton": YES }];
|
||||
|
||||
[self addTrackingArea:_cancelButtonTrackingArea];
|
||||
}
|
||||
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
- (void)cursorUpdate:(CPEvent)anEvent
|
||||
{
|
||||
if ([[[anEvent trackingArea] userInfo] objectForKey:@"isButton"])
|
||||
[[CPCursor arrowCursor] set];
|
||||
else
|
||||
[super cursorUpdate:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// MARK: -
|
||||
|
||||
var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
|
||||
CPSendsWholeSearchStringKey = @"CPSendsWholeSearchStringKey",
|
||||
CPSendsSearchStringImmediatelyKey = @"CPSendsSearchStringImmediatelyKey",
|
||||
@@ -1089,49 +811,3 @@ var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPSearchFieldPredicateBinder : CPBinder
|
||||
{
|
||||
CPArrayController _controller;
|
||||
CPString _predicateFormat;
|
||||
}
|
||||
|
||||
- (void)setValue:(id)aValue forBinding:(CPString)aBinding
|
||||
{
|
||||
if (aBinding === CPPredicateBinding)
|
||||
{
|
||||
var options = [_info objectForKey:CPOptionsKey];
|
||||
|
||||
_controller = [_info objectForKey:CPObservedObjectKey];
|
||||
_predicateFormat = [options objectForKey:"CPPredicateFormat"];
|
||||
[_source bind:CPValueBinding toObject:self withKeyPath:"searchFieldValue" options:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setSearchFieldValue:(CPString)aValue
|
||||
{
|
||||
var destination = [_info objectForKey:CPObservedObjectKey],
|
||||
keyPath = [_info objectForKey:CPObservedKeyPathKey];
|
||||
|
||||
[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]];
|
||||
}
|
||||
else
|
||||
[_controller setFilterPredicate:nil];
|
||||
|
||||
[self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
|
||||
}
|
||||
|
||||
- (CPString)searchFieldValue
|
||||
{
|
||||
return [_source stringValue];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
+203
-440
File diff suppressed because it is too large
Load Diff
+30
-12
@@ -22,9 +22,6 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPGraphicsContext.j"
|
||||
|
||||
/*!
|
||||
@deprecated
|
||||
@@ -34,9 +31,11 @@
|
||||
*/
|
||||
@implementation CPShadow : CPObject
|
||||
{
|
||||
CGSize _offset @accessors(property=shadowOffset);
|
||||
float _blurRadius @accessors(property=shadowBlurRadius);
|
||||
CPColor _color @accessors(property=shadowColor);
|
||||
CPSize _offset;
|
||||
float _blurRadius;
|
||||
CPColor _color;
|
||||
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -52,7 +51,7 @@
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithOffset:(CGSize)anOffset blurRadius:(float)aBlurRadius color:(CPColor)aColor
|
||||
- (id)_initWithOffset:(CPSize)anOffset blurRadius:(float)aBlurRadius color:(CPColor)aColor
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
@@ -61,16 +60,35 @@
|
||||
_offset = anOffset;
|
||||
_blurRadius = aBlurRadius;
|
||||
_color = aColor;
|
||||
|
||||
_cssString = [_color cssString] + " " + ROUND(anOffset.width) + @"px " + ROUND(anOffset.height) + @"px " + ROUND(_blurRadius) + @"px";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)set
|
||||
/*!
|
||||
Returns the shadow's offset.
|
||||
*/
|
||||
- (CGSize)shadowOffset
|
||||
{
|
||||
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
||||
return _offset;
|
||||
}
|
||||
|
||||
CGContextSetShadowWithColor(context, _offset, _blurRadius, _color);
|
||||
/*!
|
||||
Returns the shadow's blur radius
|
||||
*/
|
||||
- (float)shadowBlurRadius
|
||||
{
|
||||
return _blurRadius;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the shadow's color.
|
||||
*/
|
||||
- (CPColor)shadowColor
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -78,7 +96,7 @@
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
return [_color cssString] + " " + ROUND(_offset.width) + @"px " + ROUND(_offset.height) + @"px " + ROUND(_blurRadius) + @"px";
|
||||
return _cssString;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user