Compare commits

..
1263 changed files with 169077 additions and 63460 deletions
-12
View File
@@ -1,12 +0,0 @@
# EditorConfig reference: https://editorconfig.org
# https://editorconfig.org/#file-format-details
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
-34
View File
@@ -1,34 +0,0 @@
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node build
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [24.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: echo "${PWD}/dist/cappuccino/bin" >> $GITHUB_PATH
- run: echo "${PWD}/dist/objective-j/bin" >> $GITHUB_PATH
- run: npm install
- run: npm update
- run: jake dist
- run: jake test-only
@@ -1,180 +0,0 @@
name: Main branch - Build Testbook with fresh frameworks and manual tests & deploy to GitHub Pages
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: write
pages: write # required by deploy-pages
id-token: write # required by deploy-pages
pull-requests: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout cappuccino (this repo)
uses: actions/checkout@v5
- name: Checkout Cappuccino-Testbook into ./testbook
uses: actions/checkout@v5
with:
repository: ArgosOz/Cappuccino-Testbook
path: testbook
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 24.x
- run: echo "${PWD}/dist/cappuccino/bin" >> $GITHUB_PATH
- run: echo "${PWD}/dist/objective-j/bin" >> $GITHUB_PATH
- run: npm install
- run: npm update
- run: jake dist
- run: jake test-only
# Refresh Frameworks from dist → testbook/Frameworks
- name: Refresh Frameworks
run: |
set -euo pipefail
if [ ! -d dist ]; then
echo "❌ dist/ not found at repo root" >&2
exit 1
fi
mkdir -p testbook/Frameworks
rm -rf testbook/Frameworks/*
cp -a dist/cappuccino/Frameworks/{Foundation,AppKit} testbook/Frameworks/
cp -a dist/objective-j/Frameworks/Objective-J testbook/Frameworks/
echo "Frameworks populated:" && ls -la testbook/Frameworks || true
- name: Generate manifest of manual test directories
if: ${{ hashFiles('Tests/Manual/**') != '' }}
run: |
set -euo pipefail
mkdir -p artifacts
OUT="testbook/Resources/recipes.txt"
: > "$OUT"
if [ ! -d "Tests/Manual" ]; then
echo "❌ Tests/Manual not found at repo root" >&2
exit 1
fi
# Iterate over immediate subdirectories of Tests/Manual (null-safe for spaces)
while IFS= read -r -d '' d; do
name=$(basename "$d")
ts=$(date +%s%3N) # milliseconds since epoch
printf '%s|%s|\n' "$ts" "$name" >> "$OUT"
# Optional tiny sleep to help keep timestamps distinct:
sleep 0.01
done < <(find "Tests/Manual" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
echo "Wrote $OUT:"
cat "$OUT"
# Replace subdirectories inside testbook/Resources with Tests/Manual subdirectories
- name: Sync Resources from Tests/Manual
run: |
set -euo pipefail
if [ ! -d "Tests/Manual" ]; then
echo "❌ Tests/Manual not found at repo root" >&2
exit 1
fi
mkdir -p testbook/Resources
# Remove only immediate subdirectories (keep stray files if any)
find testbook/Resources -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -r rm -rf
# Copy each immediate subdirectory from Tests/Manual → testbook/Resources
while IFS= read -r -d '' d; do
cp -a "$d" testbook/Resources/
done < <(find Tests/Manual -mindepth 1 -maxdepth 1 -type d -print0)
echo "Resources now contains:" && ls -la testbook/Resources || true
# Ensure each test Index.html includes the include path line before OBJJ_MAIN_FILE
- name: Inject OBJJ_INCLUDE_PATHS into Index.html files
run: |
set -euo pipefail
found=0
while IFS= read -r -d '' f; do
found=1
if grep -q 'OBJJ_INCLUDE_PATHS' "$f"; then
echo "Already updated: $f"
continue
fi
perl -0777 -i -pe 's/OBJJ_MAIN_FILE\s*=\s*"main\.j";/OBJJ_INCLUDE_PATHS = ["..\/..\/Frameworks"];\nOBJJ_MAIN_FILE = "main.j";/i' "$f"
perl -0777 -i -pe 's/Frameworks\/Objective-J\/Objective-J.js/\.\.\/\.\.\/Frameworks\/Objective-J\/Objective-J.js/' "$f"
echo "Updated: $f"
done < <(find testbook/Resources -mindepth 2 -maxdepth 2 -type f -iname "index.html" -print0)
if [ "$found" -eq 0 ]; then
echo "⚠️ No index.html files found under testbook/Resources/*/" >&2
fi
# Optional: prune VCS/CI metadata from the served content
- name: Clean testbook for Pages (optional)
run: |
set -euo pipefail
test -f testbook/index.html
test -d testbook/Resources
test -d testbook/Frameworks
rm -rf testbook/.git testbook/.github || true
find testbook -maxdepth 1 -type f -name ".git*" -delete || true
- name: Publish to gh-pages (preserve PR previews)
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: testbook
clean-exclude: pr-preview/ # keep PR preview folders
force: false # avoid force-push so previews survive
- name: Checkout gh-pages ./gh-pages
uses: actions/checkout@v5
with:
path: gh-pages
ref: gh-pages
- name: Clean gh-pages for Pages
run: |
set -euo pipefail
test -d gh-pages
rm -rf gh-pages/.git gh-pages/.github || true
find gh-pages -maxdepth 1 -type f -name ".git*" -delete || true
find gh-pages -type f -name "*.xib" -delete || true
find gh-pages -type f -name "*xcode*" -delete || true
find gh-pages -name "Jakefile" -delete || true
- name: Upload Pages artifact (testbook/)
uses: actions/upload-pages-artifact@v4
with:
path: gh-pages
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
concurrency:
group: pages
cancel-in-progress: true
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- name: Comment Pages URL on PR
if: ${{ github.event_name == 'pull_request' }}
uses: actions/github-script@v7
with:
script: |
const url = `${{ toJSON(steps.deployment.outputs.page_url) }}`;
const body = `📄 GitHub Pages preview for this PR:\n\n${url}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
+1 -11
View File
@@ -17,14 +17,4 @@ Tests/Manual/**/*.xcodeproj
*.sublime-project
*.sublime-workspace
*.tm_properties
*.idea
node_modules
*.vscode
/dist/objective-j/objj-executable
/dist/objective-j/package.json
/dist/objective-j/lib
/dist/cappuccino/package.json
/dist/cappuccino/lib
/dist/cappuccino/bin
Tests/Manual/.Frameworks
/Tests/Manual/index.html
*.idea
-28
View File
@@ -1,28 +0,0 @@
.DS_Store
Frameworks
!/dist/objective-j/Frameworks
!/dist/cappuccino-j/Frameworks
Build
Demos
./Aristo
WebSite
.push-package
*.xcodeproj*
*.xcodeproj/*.pbxuser
*.xcodeproj/*.perspectivev3
xcuserdata/
!*.xcodeproj/project.pbxproj
*.xCodeSupport/
*.XcodeSupport/
*XcodeSupport/
Tests/Manual/**/*.xcodeproj
*.sublime-project
*.sublime-workspace
*.tm_properties
*.idea
node_modules
*.vscode
/dist/objective-j/objj-executable
/dist/objective-j/package.json
/dist/cappuccino/package.json
-3
View File
@@ -93,7 +93,6 @@
@import "CPSlider.j"
@import "CPSound.j"
@import "CPSplitView.j"
@import "CPStackView.j"
@import "CPStepper.j"
@import "CPTableColumn.j"
@import "CPTableView.j"
@@ -116,5 +115,3 @@
@import "CPWindow.j"
@import "CPWindowController.j"
@import "CPWorkspace.j"
@import "CPFontPanel.j"
@import "CPTreeController.j"
+8 -8
View File
@@ -172,7 +172,7 @@ var bottomHeight = 71;
BOOL _needsLayout;
}
// MARK: Creating Alerts
#pragma mark Creating Alerts
/*!
Returns a CPAlert object with the provided info
@@ -249,8 +249,8 @@ var bottomHeight = 71;
}
// MARK: -
// MARK: Delegate
#pragma mark -
#pragma mark Delegate
/*!
Set the delegate of the receiver
@@ -272,7 +272,7 @@ var bottomHeight = 71;
}
// MARK: Accessors
#pragma mark Accessors
- (CPTheme)theme
{
@@ -402,7 +402,7 @@ var bottomHeight = 71;
_needsLayout = YES;
}
// MARK: Accessing Buttons
#pragma mark Accessing Buttons
/*!
Adds a button with a given title to the receiver.
@@ -438,7 +438,7 @@ var bottomHeight = 71;
[_buttons insertObject:button atIndex:0];
}
// MARK: Layout
#pragma mark Layout
/*!
@ignore
@@ -664,7 +664,7 @@ var bottomHeight = 71;
_needsLayout = NO;
}
// MARK: Displaying Alerts
#pragma mark Displaying Alerts
/*!
Displays the \c CPAlert panel as a modal dialog. The user will not be
@@ -742,7 +742,7 @@ var bottomHeight = 71;
[self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
}
// MARK: Private
#pragma mark Private
/*!
@ignore
+9 -9
View File
@@ -63,8 +63,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
// MARK: -
// MARK: Class Methods
#pragma mark -
#pragma mark Class Methods
/*! Returns the current default CPAppearance
*/
@@ -99,8 +99,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
/*! Creates a CPAppearance object initialized to the specified appearance file in the specified bundle
This method does actually nothing special. It just creates a default appearance object
@@ -122,8 +122,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
// MARK: -
// MARK: Implementation
#pragma mark -
#pragma mark Implementation
- (BOOL)isEqual:(id)anObject
{
@@ -139,8 +139,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
// MARK: -
// MARK: CPCoding
#pragma mark -
#pragma mark CPCoding
- (id)initWithCoder:(CPCoder)aCoder
{
@@ -159,4 +159,4 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
[aCoder encodeBool:_allowsVibrancy forKey:@"_allowsVibrancy"];
}
@end
@end
-44
View File
@@ -378,47 +378,3 @@ var DefaultLineWidth = 1.0;
}
@end
@implementation CPBezierPath (AnimationAdditions)
- (CPString)SVGString
{
var pathString = "";
var elements = _path.elements;
var count = _path.count;
for (var i = 0; i < count; i++)
{
var element = elements[i];
// Use the kCGPathElement* constants defined in CGPath.j
switch (element.type)
{
case kCGPathElementMoveToPoint:
pathString += "M " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddLineToPoint:
pathString += "L " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddQuadCurveToPoint:
pathString += "Q " + element.cpx + " " + element.cpy + " " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddCurveToPoint:
pathString += "C " + element.cp1x + " " + element.cp1y + " " + element.cp2x + " " + element.cp2y + " " + element.x + " " + element.y + " ";
break;
case kCGPathElementCloseSubpath:
pathString += "Z ";
break;
}
}
return pathString.trim();
}
@end
+8 -8
View File
@@ -264,8 +264,8 @@ CPBelowBottom = 6;
[self _manageTitlePositioning];
}
// MARK: -
// MARK: Style properties which override theme values
#pragma mark -
#pragma 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.
@@ -282,7 +282,7 @@ CPBelowBottom = 6;
*/
// See discussion above.
// MARK: borderColor
#pragma mark borderColor
- (CPColor)borderColor
{
return [self valueForThemeAttribute:@"border-color"];
@@ -308,7 +308,7 @@ CPBelowBottom = 6;
}
// See discussion above.
// MARK: borderWidth
#pragma mark borderWidth
- (float)borderWidth
{
return [self valueForThemeAttribute:@"border-width"];
@@ -334,7 +334,7 @@ CPBelowBottom = 6;
}
// See discussion above.
// MARK: cornerRadius
#pragma mark cornerRadius
- (float)cornerRadius
{
return [self valueForThemeAttribute:@"corner-radius"];
@@ -360,7 +360,7 @@ CPBelowBottom = 6;
}
// See discussion above.
// MARK: fillColor
#pragma mark fillColor
- (CPColor)fillColor
{
return [self valueForThemeAttribute:@"background-color"];
@@ -761,7 +761,7 @@ CPBelowBottom = 6;
@end
// MARK: -
#pragma mark -
@implementation CPBox (CSSTheming)
@@ -829,7 +829,7 @@ CPBelowBottom = 6;
@end
// MARK: -
#pragma mark -
var CPBoxTypeKey = @"CPBoxTypeKey",
CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
+3 -3
View File
@@ -220,8 +220,8 @@ CPButtonImageOffset = 3.0;
[self setButtonType:CPMomentaryPushInButton];
}
// MARK: -
// MARK: Control Size
#pragma mark -
#pragma mark Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
@@ -232,7 +232,7 @@ CPButtonImageOffset = 3.0;
}
// MARK: -
#pragma mark -
// Setting the state
/*!
+16 -72
View File
@@ -36,7 +36,6 @@
BOOL _hasResizeControl;
BOOL _resizeControlIsLeftAligned;
CPArray _buttons;
CPArray _rightButtons;
}
+ (id)plusButton
@@ -105,7 +104,6 @@
if (self)
{
_buttons = [];
_rightButtons = [];
[self setNeedsLayout];
}
@@ -159,30 +157,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)
@@ -269,73 +243,46 @@
}
}
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:highlightedColor forThemeAttribute:@"bezel-color" inStates:[CPThemeStateHighlighted, CPThemeStateBordered, ]];
[button setValue:disabledColor forThemeAttribute:@"bezel-color" inStates:[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]];
// 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]];
[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)
@@ -367,8 +314,7 @@
var CPButtonBarHasResizeControlKey = @"CPButtonBarHasResizeControlKey",
CPButtonBarResizeControlIsLeftAlignedKey = @"CPButtonBarResizeControlIsLeftAlignedKey",
CPButtonBarButtonsKey = @"CPButtonBarButtonsKey",
CPButtonBarRightButtonsKey = @"CPButtonBarRightButtonsKey";
CPButtonBarButtonsKey = @"CPButtonBarButtonsKey";
@implementation CPButtonBar (CPCoding)
@@ -379,7 +325,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 +332,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];
}
+3 -3
View File
@@ -95,8 +95,8 @@ CPCheckBoxImageOffset = 4.0;
}
// MARK: -
// MARK: Override methods from CPButton
#pragma mark -
#pragma mark Override methods from CPButton
- (CGSize)_minimumFrameSize
{
@@ -177,7 +177,7 @@ CPCheckBoxImageOffset = 4.0;
@end
// MARK: -
#pragma mark -
@implementation CPCheckBox (TableDataView)
+6 -13
View File
@@ -202,8 +202,8 @@ var HORIZONTAL_MARGIN = 2;
// MARK: -
// MARK: Delegate
#pragma mark -
#pragma mark Delegate
/*!
Set the delegate of the receiver
@@ -600,12 +600,9 @@ var HORIZONTAL_MARGIN = 2;
if (_maxNumberOfRows > 0)
numberOfRows = MIN(numberOfRows, _maxNumberOfRows);
// calculate the required height: (sum of item heights) + (sum of margins between items).
var requiredHeight = (numberOfRows * _minItemSize.height) + (MAX(0, numberOfRows - 1) * _verticalMargin);
height = MAX(height, requiredHeight);
height = MAX(height, numberOfRows * (_minItemSize.height + _verticalMargin));
// calculate individual item height based on the total available height.
var itemSizeHeight = (numberOfRows > 0) ? FLOOR((height - (MAX(0, numberOfRows - 1) * _verticalMargin)) / numberOfRows) : 0;
var itemSizeHeight = FLOOR(height / numberOfRows) - _verticalMargin;
if (maxItemSizeHeight > 0)
itemSizeHeight = MIN(itemSizeHeight, maxItemSizeHeight);
@@ -624,7 +621,7 @@ var HORIZONTAL_MARGIN = 2;
_horizontalMargin = _uniformSubviewsResizing ? FLOOR((aFrameSize.width - numberOfColumns * anItemSize.width) / (numberOfColumns + 1)) : HORIZONTAL_MARGIN;
var x = _horizontalMargin,
y = -anItemSize.height;
y = -anItemSize.height;
[displayItems enumerateObjectsUsingBlock:function(item, idx, stop)
{
@@ -639,11 +636,7 @@ var HORIZONTAL_MARGIN = 2;
if (idx % numberOfColumns == 0)
{
x = _horizontalMargin;
// For the first row, don't add a margin. For all subsequent rows, add the margin.
if (idx === 0)
y += anItemSize.height;
else
y += _verticalMargin + anItemSize.height;
y += _verticalMargin + anItemSize.height;
}
[view setFrameOrigin:CGPointMake(x, y)];
+8 -8
View File
@@ -87,10 +87,10 @@ var cachedBlackColor,
CPImage _patternImage;
CPString _cssString;
}
@global document
// MARK: -
// MARK: Theming
#pragma mark -
#pragma mark Theming
+ (CPString)defaultThemeClass
{
@@ -109,8 +109,8 @@ var cachedBlackColor,
}
// MARK: -
// MARK: Static methods
#pragma mark -
#pragma mark Static methods
/*!
Creates a color in the RGB colorspace, with an alpha value.
@@ -884,8 +884,8 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
@end
// MARK: -
// MARK: CSS Theming
#pragma mark -
#pragma mark CSS Theming
// The code below adds support for CSS theming with 100% compatibility with current theming system.
// The idea is to extend CPColor (and CPImage) with CSS components and adapt low level UI components to
@@ -1091,7 +1091,7 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
@end
// MARK: -
#pragma mark -
/// @cond IGNORE
var CPColorComponentsKey = @"CPColorComponentsKey",
+32 -80
View File
@@ -51,7 +51,6 @@ CPColorPickerViewWidth = 265;
CPColorPickerViewHeight = 370;
CPColorPanelColorDidChangeNotification = @"CPColorPanelColorDidChangeNotification";
CPColorDragType = CPColorPboardType;
var PREVIEW_HEIGHT = 20.0,
TOOLBAR_HEIGHT = 32.0,
@@ -130,6 +129,8 @@ var SharedColorPanel = nil,
if (self)
{
//[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
[self setTitle:@"Color Panel"];
[self setLevel:CPFloatingWindowLevel];
@@ -148,37 +149,20 @@ var SharedColorPanel = nil,
*/
- (void)setColor:(CPColor)aColor
{
if ([_color isEqual:aColor])
return;
_color = aColor;
[_previewView setBackgroundColor:_color];
// Check if the color change originated from user interaction inside the panel itself.
// We only broadcast `changeColor:` if the user picked a color via the panel's UI.
// If an external CPColorWell called `setColor:` programmatically, broadcasting it
// back down the responder chain would incorrectly change the previous First Responder.
var currentEvent = [CPApp currentEvent],
isFromPanel = currentEvent && ([currentEvent window] === self);
[CPApp sendAction:@selector(changeColor:) to:nil from:self];
if (isFromPanel)
{
// Push color via Responder Chain (targets First Responder, i.e., the active CPColorWell)
[CPApp sendAction:@selector(changeColor:) to:nil from:self];
if (_target && _action)
[CPApp sendAction:_action to:_target from:self];
}
if (_target && _action)
[CPApp sendAction:_action to:_target from:self];
[[CPNotificationCenter defaultCenter]
postNotificationName:CPColorPanelColorDidChangeNotification
object:self];
if (_activePicker)
[_activePicker setColor:_color];
if (_opacitySlider)
[_opacitySlider setFloatValue:[_color alphaComponent]];
[_activePicker setColor:_color];
[_opacitySlider setFloatValue:[_color alphaComponent]];
}
/*!
@@ -190,7 +174,7 @@ var SharedColorPanel = nil,
{
[self setColor:aColor];
if (bool && _activePicker)
if (bool)
[_activePicker setColor:_color];
}
@@ -348,7 +332,7 @@ var SharedColorPanel = nil,
buttonForLater = button;
}
// Preview
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var previewBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10, CGRectGetWidth(bounds) - 86, PREVIEW_HEIGHT)];
_previewView = [[_CPColorPanelPreview alloc] initWithFrame:CGRectInset([previewBox bounds], 2.0, 2.0)];
@@ -366,7 +350,7 @@ var SharedColorPanel = nil,
[_previewLabel setTextColor:[CPColor blackColor]];
[_previewLabel setAlignment:CPRightTextAlignment];
// Swatches
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var swatchBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5, CGRectGetWidth(bounds) - 86, SWATCH_HEIGHT + 2.0)];
[swatchBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
@@ -407,6 +391,8 @@ var SharedColorPanel = nil,
[contentView addSubview:opacityLabel];
[contentView addSubview:_opacitySlider];
_target = nil;
_action = nil;
_activePicker = nil;
[_previewView setBackgroundColor:_color];
@@ -426,6 +412,8 @@ var SharedColorPanel = nil,
@end
CPColorDragType = "CPColorDragType";
var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
/* @ignore */
@@ -435,14 +423,12 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
CPColor _dragColor;
CPColorPanel _colorPanel;
CPCookie _swatchCookie;
CGPoint _mouseDownPoint;
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
_mouseDownPoint = CGPointMake(0, 0);
[self setBackgroundColor:[CPColor grayColor]];
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
@@ -456,6 +442,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
for (var i = 0; i < 50; i++)
{
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var view = [[CPView alloc] initWithFrame:CGRectMake(13 * i + 1, 1, 12, 12)],
fillView = [[CPView alloc] initWithFrame:CGRectInset([view bounds], 1.0, 1.0)];
@@ -507,6 +494,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
- (CPArray)saveColorList
{
var result = [];
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
for (var i = 0; i < _swatches.length; i++)
result.push([[[_swatches[i] subviews][0] backgroundColor] hexString]);
@@ -533,44 +521,39 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
- (void)setColor:(CPColor)aColor atIndex:(int)index
{
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
[[_swatches[index] subviews][0] setBackgroundColor:aColor];
[self saveColorList];
}
- (void)mouseDown:(CPEvent)anEvent
{
_mouseDownPoint = [anEvent locationInWindow];
}
- (void)mouseUp:(CPEvent)anEvent
{
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil],
bounds = [self bounds];
if (!CGRectContainsPoint(bounds, point) || point.x > [self bounds].size.width - 1 || point.x < 1)
return;
return NO;
[_colorPanel setColor:[self colorAtIndex:FLOOR(point.x / 13)] updatePicker:YES];
}
- (void)mouseDragged:(CPEvent)anEvent
{
var windowPoint = [anEvent locationInWindow];
// Prevent accidental drags from rapid clicking causing small micro-movements
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
var point = [self convertPoint:windowPoint fromView:nil];
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
if (point.x > [self bounds].size.width - 1 || point.x < 1)
return;
return NO;
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
var swatch = _swatches[FLOOR(point.x / 13)];
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
_dragColor = [[swatch subviews][0] backgroundColor];
var bounds = CGRectMakeCopy([swatch bounds]);
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
@@ -579,15 +562,11 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:CPColorDragType];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:pasteboard
pasteboard:nil
source:self
slideBack:YES];
}
@@ -598,7 +577,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:aType];
}
- (BOOL)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
- (void)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
{
var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
pasteboard = [aSender draggingPasteboard],
@@ -608,13 +587,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return NO;
[self setColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] atIndex:FLOOR(location.x / 13)];
return YES;
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
@end
@@ -623,13 +595,11 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
@implementation _CPColorPanelPreview : CPView
{
CPColorPanel _colorPanel;
CGPoint _mouseDownPoint;
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
_mouseDownPoint = CGPointMake(0, 0);
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
@@ -646,7 +616,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return _colorPanel;
}
- (BOOL)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
- (void)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
{
var pasteboard = [aSender draggingPasteboard];
@@ -655,8 +625,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
var color = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]];
[_colorPanel setColor:color updatePicker:YES];
return YES;
}
- (BOOL)isOpaque
@@ -664,22 +632,15 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return YES;
}
- (void)mouseDown:(CPEvent)anEvent
{
_mouseDownPoint = [anEvent locationInWindow];
}
- (void)mouseDragged:(CPEvent)anEvent
{
var windowPoint = [anEvent locationInWindow];
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
var point = [self convertPoint:windowPoint fromView:nil];
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPColorDragType] owner:self];
var bounds = CGRectMake(0, 0, 15, 15);
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
@@ -688,15 +649,11 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:CPColorDragType];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:pasteboard
pasteboard:nil
source:self
slideBack:YES];
}
@@ -707,11 +664,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:aType];
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
@end
@import "CPColorPicker.j"
+136 -200
View File
@@ -25,7 +25,7 @@
@import "CPView.j"
@import "CPColor.j"
@import "CPColorPanel.j"
@import "CPPasteboard.j"
var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiveNotification";
@@ -39,10 +39,10 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
*/
@implementation CPColorWell : CPControl
{
BOOL _active;
BOOL _bordered;
CPColor _color;
BOOL _isChangingColorFromPanel; // Guard flag to prevent recursion
CGPoint _mouseDownPoint;
}
+ (Class)_binderClassForBinding:(CPString)aBinding
@@ -77,54 +77,52 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[theBinding reverseSetValueFor:@"color"];
}
- (BOOL)isFirstResponder
{
return [[self window] firstResponder] === self;
}
- (BOOL)acceptsFirstResponder
{
return [self isEnabled];
}
- (void)activate:(BOOL)shouldBeExclusive
{
[[self window] makeFirstResponder:self];
[[CPColorPanel sharedColorPanel] orderFront:self];
}
- (BOOL)isActive
{
return [self isFirstResponder] && [self isEnabled];
}
/*!
Deactivates the color well.
*/
- (void)deactivate
{
if ([self isFirstResponder])
[[self window] makeFirstResponder:nil];
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
_active = NO;
_color = [CPColor whiteColor];
_mouseDownPoint = CGPointMakeZero();
[self setBordered:YES];
[self registerForDraggedTypes:[CPArray arrayWithObject:CPColorPboardType]];
}
return self;
}
// MARK: -
// MARK: Draw
- (void)_registerNotifications
{
var defaultCenter = [CPNotificationCenter defaultCenter];
[defaultCenter
addObserver:self
selector:@selector(colorWellDidBecomeExclusive:)
name:_CPColorWellDidBecomeExclusiveNotification
object:nil];
[defaultCenter
addObserver:self
selector:@selector(colorPanelWillClose:)
name:CPWindowWillCloseNotification
object:[CPColorPanel sharedColorPanel]];
}
- (void)_removeNotifications
{
var defaultCenter = [CPNotificationCenter defaultCenter];
[defaultCenter
removeObserver:self
name:_CPColorWellDidBecomeExclusiveNotification
object:nil];
[defaultCenter
removeObserver:self
name:CPWindowWillCloseNotification
object:[CPColorPanel sharedColorPanel]];
}
/*!
Sets whether the color well is bordered.
@@ -145,8 +143,7 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
return [self hasThemeState:CPThemeStateBordered];
}
// MARK: -
// MARK: Managing Color
// Managing Color From Color Wells
/*!
Returns the color well's current color.
@@ -161,17 +158,12 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
*/
- (void)setColor:(CPColor)aColor
{
if ([_color isEqual:aColor])
if (_color == aColor)
return;
_color = aColor;
[self setNeedsLayout];
// Only push back to the panel if we initiated the change (not if the panel pushed it to us)
// AND if we are the current focus.
if (!_isChangingColorFromPanel && [self isFirstResponder])
[[CPColorPanel sharedColorPanel] setColor:_color];
}
/*!
@@ -183,175 +175,96 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[self setColor:[aSender color]];
}
/*!
Standard action method sent by CPColorPanel via the Responder Chain.
*/
- (void)changeColor:(id)aSender
{
if ([aSender isKindOfClass:[CPColorPanel class]])
{
_isChangingColorFromPanel = YES;
[self setColor:[aSender color]];
_isChangingColorFromPanel = NO;
// Forward the action to our target (e.g. controller)
[self sendAction:[self action] to:[self target]];
}
}
// MARK: -
// MARK: Activating and Deactivating
// Activating and Deactivating Color Wells
/*!
Activates the color well, displays the color panel, and makes the panel's current color the same as its own.
If exclusive is \c YES, deactivates any other CPColorWells. \c NO, keeps them active.
@param shouldBeExclusive whether other color wells should be deactivated.
*/
- (BOOL)becomeFirstResponder
- (void)activate:(BOOL)shouldBeExclusive
{
[self setThemeState:CPThemeStateFirstResponder];
var panel = [CPColorPanel sharedColorPanel];
// EXPLICITLY set ourselves as the target.
// This ensures that when the user clicks the panel (making Panel key),
// the panel still knows to send messages back to us.
[panel setTarget:self];
[panel setAction:@selector(changeColor:)];
// Sync panel to our current color
[panel setColor:_color];
if (shouldBeExclusive)
// FIXME: make this queue!
[[CPNotificationCenter defaultCenter]
postNotificationName:_CPColorWellDidBecomeExclusiveNotification
object:self];
[[CPNotificationCenter defaultCenter] postNotificationName:_CPColorWellDidBecomeExclusiveNotification object:self];
return YES;
}
- (BOOL)resignFirstResponder
{
[self unsetThemeState:CPThemeStateFirstResponder];
var panel = [CPColorPanel sharedColorPanel];
// Clean up if we were the target
if ([panel target] == self)
[panel setTarget:nil];
return YES;
}
// MARK: -
// MARK: Event Handling
- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEnabled])
if ([self isActive])
return;
_active = YES;
[[CPNotificationCenter defaultCenter]
addObserver:self
selector:@selector(colorPanelDidChangeColor:)
name:CPColorPanelColorDidChangeNotification
object:[CPColorPanel sharedColorPanel]];
}
/*!
Deactivates the color well.
*/
- (void)deactivate
{
if (![self isActive])
return;
_active = NO;
[[CPNotificationCenter defaultCenter]
removeObserver:self
name:CPColorPanelColorDidChangeNotification
object:[CPColorPanel sharedColorPanel]];
}
/*!
Returns \c YES if the color well is active.
*/
- (BOOL)isActive
{
return _active;
}
- (void)colorPanelDidChangeColor:(CPNotification)aNotification
{
[self takeColorFrom:[aNotification object]];
[self sendAction:[self action] to:[self target]];
}
- (void)colorWellDidBecomeExclusive:(CPNotification)aNotification
{
if (self != [aNotification object])
[self deactivate];
}
- (void)colorPanelWillClose:(CPNotification)aNotification
{
[self deactivate];
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
{
[self highlight:NO];
if (!mouseIsUp || !CGRectContainsPoint([self bounds], aPoint) || ![self isEnabled])
return;
_mouseDownPoint = [anEvent locationInWindow];
[self activate:YES];
var colorPanel = [CPColorPanel sharedColorPanel];
[colorPanel setPlatformWindow:[[self window] platformWindow]];
[colorPanel setColor:_color];
[colorPanel orderFront:self];
}
- (void)mouseDragged:(CPEvent)anEvent
{
if (![self isEnabled])
return;
var windowPoint = [anEvent locationInWindow];
// Prevent accidental drags from rapid clicking causing small micro-movements
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
var bounds = CGRectMake(0, 0, 15, 15);
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
[dragView setBackgroundColor:[CPColor blackColor]];
[dragFillView setBackgroundColor:_color];
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorPboardType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_color] forType:CPColorPboardType];
var point = [self convertPoint:windowPoint fromView:nil];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:pasteboard
source:self
slideBack:YES];
}
// MARK: -
// MARK: Drag and Drop
- (void)draggingEntered:(id)sender
{
var pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:CPColorPboardType])
{
[self setThemeState:CPThemeStateHighlighted];
return CPDragOperationCopy;
}
return CPDragOperationNone;
}
- (void)draggingExited:(id)sender
{
[self unsetThemeState:CPThemeStateHighlighted];
}
- (BOOL)performDragOperation:(id)sender
{
var pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:CPColorPboardType])
{
var data = [pasteboard dataForType:CPColorPboardType],
newColor = [CPKeyedUnarchiver unarchiveObjectWithData:data];
if (newColor && [newColor isKindOfClass:[CPColor class]])
{
[self setColor:newColor];
[self sendAction:[self action] to:[self target]];
// Activate nicely after drop
[self activate:YES];
[self unsetThemeState:CPThemeStateHighlighted];
return YES;
}
}
return NO;
}
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
{
if (aType == CPColorPboardType)
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_color] forType:aType];
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
// MARK: -
// MARK: Layout
- (CGRect)contentRectForBounds:(CGRect)bounds
{
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
return CGRectInsetByInset(bounds, contentInset);
}
@@ -405,6 +318,7 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
[contentView setBackgroundColor:_color];
var contentBorderView = [self layoutEphemeralSubviewNamed:@"content-border-view"
@@ -414,6 +328,28 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[contentBorderView setBackgroundColor:[self currentValueForThemeAttribute:@"content-border-color"]];
}
#pragma mark -
#pragma mark Observers method
- (void)_addObservers
{
if (_isObserving)
return;
[super _addObservers];
[self _registerNotifications];
}
- (void)_removeObservers
{
if (!_isObserving)
return;
[super _removeObservers];
[self _removeNotifications];
}
@end
@implementation CPColorWellValueBinder : CPBinder
@@ -462,9 +398,9 @@ var CPColorWellColorKey = "CPColorWellColorKey",
if (self)
{
_active = NO;
_color = [aCoder decodeObjectForKey:CPColorWellColorKey];
[self setBordered:[aCoder decodeBoolForKey:CPColorWellBorderedKey]];
[self registerForDraggedTypes:[CPArray arrayWithObject:CPColorPboardType]];
}
return self;
+12 -12
View File
@@ -133,7 +133,7 @@ var CPComboBoxTextSubview = @"text",
[self setThemeState:CPComboBoxStateButtonBordered];
}
// MARK: Setting Display Attributes
#pragma mark Setting Display Attributes
- (BOOL)hasVerticalScroller
{
@@ -214,7 +214,7 @@ var CPComboBoxTextSubview = @"text",
_numberOfVisibleItems = MAX(visibleItems, 1);
}
// MARK: Setting a Delegate
#pragma mark Setting a Delegate
- (id <CPComboBoxDelegate>)delegate
{
@@ -254,7 +254,7 @@ var CPComboBoxTextSubview = @"text",
[super setDelegate:aDelegate];
}
// MARK: Setting a Data Source
#pragma mark Setting a Data Source
- (id <CPComboBoxDataSource>)dataSource
{
@@ -305,7 +305,7 @@ var CPComboBoxTextSubview = @"text",
[self reloadData];
}
// MARK: Working with an Internal List
#pragma mark Working with an Internal List
- (void)addItemsWithObjectValues:(CPArray)objects
{
@@ -378,7 +378,7 @@ var CPComboBoxTextSubview = @"text",
return _items.length;
}
// MARK: Manipulating the Displayed List
#pragma mark Manipulating the Displayed List
/*!
Returns the delegate to be used when creating the pop up list.
@@ -594,7 +594,7 @@ var CPComboBoxTextSubview = @"text",
[self sendAction:[self action] to:[self target]];
}
// MARK: Manipulating the Selection
#pragma mark Manipulating the Selection
- (void)deselectItemAtIndex:(int)index
{
@@ -646,7 +646,7 @@ var CPComboBoxTextSubview = @"text",
[self selectItemAtIndex:index];
}
// MARK: Completing the Text Field
#pragma mark Completing the Text Field
- (BOOL)completes
{
@@ -693,7 +693,7 @@ var CPComboBoxTextSubview = @"text",
_forceSelection = !!flag;
}
// MARK: CPTextField Delegate Methods and Overrides
#pragma mark CPTextField Delegate Methods and Overrides
/*! @ignore */
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
@@ -893,7 +893,7 @@ var CPComboBoxTextSubview = @"text",
[_listDelegate setAlignment:alignment];
}
// MARK: Pop Up Button Layout
#pragma mark Pop Up Button Layout
- (CGRect)popupButtonRectForBounds:(CGRect)bounds
{
@@ -938,7 +938,7 @@ var CPComboBoxTextSubview = @"text",
relativeToEphemeralSubviewNamed:@"content-view"];
}
// MARK: Internal Helpers
#pragma mark Internal Helpers
/*! @ignore */
- (void)_dataSourceWarningForMethod:(SEL)cmd condition:(CPString)flag
@@ -1016,8 +1016,8 @@ var CPComboBoxTextSubview = @"text",
}
// MARK: -
// MARK: Observers method
#pragma mark -
#pragma mark Observers method
- (void)_addObservers
{
+5 -5
View File
@@ -207,8 +207,8 @@ var CPControlBlackColor = [CPColor blackColor];
return self;
}
// MARK: -
// MARK: Control Size
#pragma mark -
#pragma mark Control Size
/*!
Returns the control's control size
@@ -286,7 +286,7 @@ var CPControlBlackColor = [CPColor blackColor];
}
// MARK: -
#pragma mark -
/*!
Sets the receiver's target action.
@@ -1023,8 +1023,8 @@ var CPControlBlackColor = [CPColor blackColor];
}
// MARK: -
// MARK: Base writing direction
#pragma mark -
#pragma mark Base writing direction
/*!
Sets the initial writing direction of the receiver
-2
View File
@@ -37,8 +37,6 @@
CPString _expires;
}
@global document
/*!
Initializes a cookie with a given name \c aName.
@param the name for the cookie
+20 -20
View File
@@ -82,8 +82,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: Theme methods
#pragma mark -
#pragma mark Theme methods
+ (CPString)defaultThemeClass
{
@@ -174,8 +174,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: Binding methods
#pragma mark -
#pragma mark Binding methods
+ (Class)_binderClassForBinding:(CPString)theBinding
{
@@ -200,8 +200,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)initWithFrame:(CGRect)aFrame
{
@@ -271,8 +271,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: Control Size
#pragma mark -
#pragma mark Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
@@ -285,8 +285,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: Delegate methods
#pragma mark -
#pragma mark Delegate methods
/*! Set the delegate of the datePicker
@param aDelegate delegate of the datePicker
@@ -302,8 +302,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: Layout method
#pragma mark -
#pragma mark Layout method
/*! Layout the subviews
*/
@@ -313,8 +313,8 @@ CPEraDatePickerElementFlag = 0x0100;
[_datePickerComponent setNeedsDisplay:YES];
}
// MARK: -
// MARK: Setter
#pragma mark -
#pragma mark Setter
/*! Return the objectValue of the datePicker. The objectValue should take the timeZoneEffect
*/
@@ -651,8 +651,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: First responder methods
#pragma mark -
#pragma mark First responder methods
/*! Return YES if style is set to CPTextFieldAndStepperDatePickerStyle or CPTextFieldDatePickerStyle
*/
@@ -689,8 +689,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
// MARK: -
// MARK: getter
#pragma mark -
#pragma mark getter
/*!
Returns \c YES if the textfield is bezeled.
@@ -723,8 +723,8 @@ CPEraDatePickerElementFlag = 0x0100;
return [[_locale objectForKey:CPLocaleCountryCode] isEqualToString:@"US"];
}
// MARK: -
// MARK: Key event
#pragma mark -
#pragma mark Key event
/*! Key down event
@param anEvent
+10 -18
View File
@@ -52,7 +52,7 @@
}
// MARK: Init method
#pragma mark Init method
/*! Init a _CPDatePickerCalendar
@param aFrame
@@ -111,8 +111,8 @@
}
// MARK: -
// MARK: Responder methods
#pragma mark -
#pragma mark Responder methods
- (BOOL)acceptsFirstResponder
{
@@ -120,8 +120,8 @@
}
// MARK: -
// MARK: Getter Setter methods
#pragma mark -
#pragma mark Getter Setter methods
/*! Set the date value of the component. It sets the dateValue of the header and the monthView also
@param aDateValue
@@ -160,8 +160,8 @@
[self _init];
}
// MARK: -
// MARK: Layout methods
#pragma mark -
#pragma mark Layout methods
/*! Manager the subviews. It hides or not the clock.
*/
@@ -221,8 +221,8 @@
}
// MARK: -
// MARK: Action methods
#pragma mark -
#pragma mark Action methods
/*! Move to the nextMonth without changing the dateValue of the datePicker
*/
@@ -251,15 +251,7 @@
- (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];
[self setDateValue:[_monthView nextMonth]];
}
- (void)_displayPreviousMonth
+10 -10
View File
@@ -71,8 +71,8 @@ _CPDatePickerClockSeconds = 3;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker
{
@@ -200,7 +200,7 @@ _CPDatePickerClockSeconds = 3;
#endif
}
// MARK: Layout methods
#pragma mark Layout methods
- (void)layoutSubviews
{
@@ -246,7 +246,7 @@ _CPDatePickerClockSeconds = 3;
// [_middleHandLayer setNeedsDisplay];
}
// MARK: Accessors
#pragma mark Accessors
- (void)setEnabled:(BOOL)shouldEnable
{
@@ -261,7 +261,7 @@ _CPDatePickerClockSeconds = 3;
[self setNeedsLayout];
}
// MARK: Mouse actions
#pragma mark Mouse actions
- (void)mouseDown:(CPEvent)anEvent
{
@@ -425,7 +425,7 @@ _CPDatePickerClockSeconds = 3;
@end
// MARK: -
#pragma mark -
@implementation HandLayer : CALayer
{
@@ -434,7 +434,7 @@ _CPDatePickerClockSeconds = 3;
float _rotationRadians;
}
// MARK: Init methods
#pragma mark Init methods
- (id)initWithSize:(CGSize)aSize
{
@@ -453,7 +453,7 @@ _CPDatePickerClockSeconds = 3;
}
// MARK: Setter Getter methods
#pragma mark Setter Getter methods
/*!
Set the bounds of the layer. The imageLayer will be at the center of this bounds.
@@ -516,7 +516,7 @@ _CPDatePickerClockSeconds = 3;
@end
// MARK: -
#pragma mark -
@implementation HandImageLayer : CALayer
{
@@ -550,7 +550,7 @@ _CPDatePickerClockSeconds = 3;
@end
// MARK: -
#pragma mark -
@implementation HoursLayer : CALayer
{
+7 -7
View File
@@ -41,7 +41,7 @@
}
// MARK: Init methods
#pragma mark Init methods
/*! Create a new instance of _CPDatePickerDayView
@param aFrame
@@ -123,8 +123,8 @@
}
// MARK: -
// MARK: Theme methods
#pragma mark -
#pragma mark Theme methods
/*! Set a theme
*/
@@ -143,8 +143,8 @@
}
// MARK: -
// MARK: Getter methods
#pragma mark -
#pragma mark Getter methods
/*! Select the tile
*/
@@ -205,8 +205,8 @@
}
// MARK: -
// MARK: Layout methods
#pragma mark -
#pragma mark Layout methods
/*! Layout the subviews
*/
@@ -27,7 +27,12 @@
CPDatePickerElementTextFieldBecomeFirstResponder = @"CPDatePickerElementTextFieldBecomeFirstResponder";
CPDatePickerElementTextFieldAMPMChangedNotification = @"CPDatePickerElementTextFieldAMPMChangedNotification";
// Removed hardcoded KeyCodes (CPZeroKeyCode, etc) as they are unreliable across browsers/layouts.
var CPZeroKeyCode = 48,
CPNineKeyCode = 57,
CPMajAKeyCode = 65,
CPMajPKeyCode = 80,
CPAKeyCode = 97,
CPPKeyCode = 112;
CPMonthDateType = 0;
CPDayDateType = 1;
@@ -185,38 +190,23 @@ CPAMPMDateType = 6;
*/
- (void)setValueForKeyEvent:(CPEvent)anEvent
{
var keyCode = [anEvent keyCode],
characters = [anEvent characters];
var keyCode = [anEvent keyCode];
// 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)
if (keyCode != CPDeleteKeyCode && keyCode != CPDeleteForwardKeyCode && keyCode < CPZeroKeyCode || keyCode > CPNineKeyCode)
return;
var newValue = [self stringValue].replace(/\s/g, ''),
length = [newValue length];
length = [newValue length],
eventKeyValue = parseInt([anEvent characters]).toString();
if (isDelete)
if (keyCode == CPDeleteKeyCode || keyCode == CPDeleteForwardKeyCode)
{
[_timerEdition invalidate];
_timerEdition = nil;
// Ensure we don't substring if length is 0
if (length > 0)
newValue = [newValue substringToIndex:(length - 1)];
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];
@@ -237,12 +227,7 @@ CPAMPMDateType = 6;
}
}
// 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))
if (parseInt(newValue) > [self _maxNumberWithMaxDate] || ([_datePicker _isAmericanFormat] && _dateType == CPHourDateType && parseInt(newValue) > 12))
return;
_firstEvent = NO;
@@ -400,18 +385,6 @@ CPAMPMDateType = 6;
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;
@@ -481,8 +454,8 @@ CPAMPMDateType = 6;
}
// MARK: -
// MARK: Mouse event
#pragma mark -
#pragma mark Mouse event
/*! Mouse down event. Launch a notification to notif the new first responder textField
*/
@@ -496,8 +469,8 @@ CPAMPMDateType = 6;
}
// MARK: -
// MARK: Theme functions
#pragma mark -
#pragma mark Theme functions
/*! Set the theme CPThemeStateSelected
*/
@@ -517,8 +490,8 @@ CPAMPMDateType = 6;
}
// MARK: -
// MARK: Override
#pragma mark -
#pragma mark Override
/*!
We override this method to get all the time the good width
@@ -584,7 +557,7 @@ CPAMPMDateType = 6;
@end
// MARK: -
#pragma mark -
@implementation _CPDatePickerElementSeparator : CPTextField
+15 -15
View File
@@ -50,7 +50,7 @@
}
// MARK: Init
#pragma mark Init
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
{
@@ -141,8 +141,8 @@
}
// MARK: -
// MARK: Responder methods
#pragma mark -
#pragma mark Responder methods
/*! @ignore */
- (BOOL)acceptsFirstResponder
@@ -154,8 +154,8 @@
}
// MARK: -
// MARK: Override observers
#pragma mark -
#pragma mark Override observers
- (void)_removeObservers
{
@@ -177,8 +177,8 @@
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_datePickerElementTextFieldAMPMChangedNotification:) name:CPDatePickerElementTextFieldAMPMChangedNotification object:_textFieldPMAM];
}
// MARK: -
// MARK: Mouse event
#pragma mark -
#pragma mark Mouse event
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
{
@@ -229,8 +229,8 @@
return nil;
}
// MARK: -
// MARK: Setter Getter methods
#pragma mark -
#pragma mark Setter Getter methods
/*! Set the value of the textFields
@param aDateValue the value
@@ -378,8 +378,8 @@
}
// MARK: -
// MARK: Notification methods
#pragma mark -
#pragma mark Notification methods
/*! Called when changing AM or PM
@param aNotification
@@ -413,8 +413,8 @@
}
// MARK: -
// MARK: Layout methods
#pragma mark -
#pragma mark Layout methods
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
{
@@ -886,8 +886,8 @@
}
// MARK: -
// MARK: Responder methods
#pragma mark -
#pragma mark Responder methods
- (void)_updateResponderTextField
{
@@ -50,7 +50,7 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"
}
// MARK: Init methods
#pragma mark Init methods
/*! Init a new instance of _CPDatePickerHeaderView
@param aFrame
@@ -153,8 +153,8 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"
}
// MARK: -
// MARK: Getter Setter methods
#pragma mark -
#pragma mark Getter Setter methods
/*! Return the day names depending on the CPLocale of the datePicker
@return an array
@@ -242,8 +242,8 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"
}
// MARK: -
// MARK: Layout methods
#pragma mark -
#pragma mark Layout methods
/*! Layout the subviews
*/
+15 -15
View File
@@ -46,7 +46,7 @@
}
// MARK: Init methods
#pragma mark Init methods
/*! Init a _CPDatePickerMonthView
@param aFrame
@@ -77,8 +77,8 @@
}
// MARK: -
// MARK: Getter Setter methods
#pragma mark -
#pragma mark Getter Setter methods
/*! Set the monthDate of the component
@param aDate
@@ -194,8 +194,8 @@
return tileIndex;
}
// MARK: -
// MARK: Reload data
#pragma mark -
#pragma mark Reload data
/*! Reload the data
*/
@@ -235,8 +235,8 @@
}
// MARK: -
// MARK: Select methods
#pragma mark -
#pragma mark Select methods
/*! Select one date or several date depending of the giving interval
@param aStartDate
@@ -297,8 +297,8 @@
}
}
// MARK: -
// MARK: Layout methods
#pragma mark -
#pragma mark Layout methods
/*! Tile the view
*/
@@ -438,8 +438,8 @@
}
// MARK: -
// MARK: Mouse event
#pragma mark -
#pragma mark Mouse event
/*! Mouse down event
*/
@@ -596,8 +596,8 @@
}
// MARK: -
// MARK: Timer
#pragma mark -
#pragma mark Timer
- (void)_timerNextMonthEvent:(CPEvent)anEvent
{
@@ -618,8 +618,8 @@
}
// MARK: -
// MARK: Date methods
#pragma mark -
#pragma mark Date methods
- (CPDate)_hoursMinutesSecondsFromDatePickerForDate:(CPDate)aDate
{
+49 -180
View File
@@ -48,6 +48,13 @@
@global CPYearMonthDayDatePickerElementFlag
@global CPEraDatePickerElementFlag
var CPZeroKeyCode = 48,
CPNineKeyCode = 57,
CPMajAKeyCode = 65,
CPMajPKeyCode = 80,
CPAKeyCode = 97,
CPPKeyCode = 112;
// This class is used to represente the datePicker with the CPTextFieldAndStepperDatePickerStyle/CPTextFieldDatePickerStyle mode
@implementation _CPDatePickerTextField : CPControl
{
@@ -62,7 +69,7 @@
}
// MARK: Init
#pragma mark Init
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
{
@@ -103,8 +110,8 @@
}
// MARK: -
// MARK: Override responder methods
#pragma mark -
#pragma mark Override responder methods
- (BOOL)becomeFirstResponder
{
@@ -124,7 +131,7 @@
// Don't forget to unbind, otherwise several steppers will increase or decrease
[_currentTextField unbind:@"objectValue"];
[_currentTextField makeDeselectable];
_currentTextField = nil;
_currentTextField = nil
// This is usefull when clicking on the stepper when the datePicker is not selected
[_stepper setObjectValue:0];
@@ -138,8 +145,8 @@
}
// MARK: -
// MARK: Setter Getter methods
#pragma mark -
#pragma mark Setter Getter methods
/*! Set the value of the control
@param aDateValue
@@ -188,8 +195,8 @@
[self setNeedsLayout];
}
// MARK: -
// MARK: Notification methods
#pragma mark -
#pragma mark Notification methods
/*! This is called to when the user just changed the selected textField
@param aNotification
@@ -207,37 +214,20 @@
}
// MARK: -
// MARK: SelectTextField action
#pragma mark -
#pragma mark SelectTextField action
- (void)_selectTextFieldWithFlags:(unsigned)flags
{
[_datePickerElementView _updateResponderTextField];
// We select the firstTextField when the datePicker becomes firstResponder if _currentTextField is null. It can be null just when using tab
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;
}
[self _selectTextField:_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];
[self _selectTextField:_firstTextField];
}
}
@@ -273,8 +263,8 @@
}
// MARK: -
// MARK: Events
#pragma mark -
#pragma mark Events
/*! Called when the user click on the stepper
*/
@@ -344,126 +334,15 @@
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];
}
if (_currentTextField == _lastTextField)
[[self window] selectNextKeyView:self];
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];
}
[self moveRight:sender];
}
- (void)moveRight:(id)sender
@@ -471,13 +350,18 @@
if (!_currentTextField)
return;
[_datePickerElementView _updateResponderTextField];
[self _selectTextField:[_currentTextField nextTextField]];
}
// Use the helper to skip hidden fields
var nextField = [self _nextVisibleTextFieldFrom:_currentTextField];
if (nextField)
[self _selectTextField:nextField];
- (void)insertBacktab:(id)sender
{
if (!_currentTextField)
return;
if (_currentTextField == _firstTextField)
[[self window] selectPreviousKeyView:self];
else
[self moveLeft:sender];
}
- (void)moveLeft:(id)sender
@@ -485,13 +369,7 @@
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];
[self _selectTextField:[_currentTextField previousTextField]];
}
- (void)moveDown:(id)sender
@@ -523,7 +401,7 @@
}
/*! KeyDown event
We just care about the event A/P and every numbers
We just care care about the event A/P and every numbers
*/
- (void)keyDown:(CPEvent)anEvent
{
@@ -532,33 +410,24 @@
[self interpretKeyEvents:[anEvent]];
var characters = [anEvent characters];
if ([_datePicker _isAmericanFormat] && [_currentTextField dateType] == CPAMPMDateType && [characters length] > 0)
if ([_datePicker _isAmericanFormat] && [_currentTextField dateType] == CPAMPMDateType && ([anEvent keyCode] == CPAKeyCode || [anEvent keyCode] == CPPKeyCode || [anEvent keyCode] == CPMajAKeyCode || [anEvent keyCode] == CPMajPKeyCode))
{
var charUpper = [characters uppercaseString];
if (charUpper === "A")
{
if ([anEvent keyCode] == CPAKeyCode || [anEvent keyCode] == CPMajAKeyCode)
[_currentTextField setStringValue:@"AM"];
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldAMPMChangedNotification object:_currentTextField userInfo:nil];
return;
}
else if (charUpper === "P")
{
else
[_currentTextField setStringValue:@"PM"];
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldAMPMChangedNotification object:_currentTextField userInfo:nil];
return;
}
[[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
#pragma mark -
#pragma mark Layout methods
/*! Layout the subviews
*/
@@ -607,8 +476,8 @@
}
// MARK: -
// MARK: Override observers
#pragma mark -
#pragma mark Override observers
- (void)_removeObservers
{
+2 -5
View File
@@ -871,13 +871,10 @@ var CPDocumentUntitledCount = 0;
{
var theDelegate = context.delegate;
// Only close the document explicitly if there is NO delegate to handle the action.
// If a delegate exists (e.g., the CPWindow), it is responsible for performing the close
// upon receiving the callback below. Calling [self close] here would cause a double-close.
if (aDocument === self && shouldClose && theDelegate == nil)
if (aDocument === self && shouldClose)
[self close];
if (theDelegate)
if (theDelegate != null)
theDelegate.isa.objj_msgSend3(theDelegate, context.selector, aDocument, shouldClose, context.context);
}
+2 -56
View File
@@ -26,7 +26,6 @@
@import "CPPasteboard.j"
@import "CPView.j"
@import "CPWindow_Constants.j"
@import "CPViewAnimation.j"
@class CPWindow // This file is imported by CPWindow.j
@class _CPDOMDataTransferPasteboard
@@ -133,10 +132,6 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
unsigned _dragOperation;
CPTimer _draggingUpdateTimer;
// Animation State
CGPoint _pendingEndLocation;
CPDragOperation _pendingEndOperation;
}
/*
@@ -266,19 +261,10 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
{
var contentView = [scrollView contentView],
bounds = [contentView bounds],
insetBounds = CGRectInset(bounds, 30, 30),
eventLocation = [contentView convertPoint:_draggingLocation fromView:nil],
deltaX = 0,
deltaY = 0,
insetSize = 30;
if ([contentView respondsToSelector:@selector(documentView)] &&
[[contentView documentView] respondsToSelector:@selector(rowHeight)])
{
// Adjust inset bounds based on CPTableView row height
insetSize = MAX(insetSize, [[contentView documentView] rowHeight]);
}
var insetBounds = CGRectInset(bounds, insetSize, insetSize);
deltaY = 0;
if (!CGRectContainsPoint(insetBounds, eventLocation))
{
@@ -330,46 +316,6 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
[_draggingUpdateTimer invalidate];
_draggingUpdateTimer = nil;
// Check if we should slide back (drag failed + slideBack requested)
if (![CPPlatform supportsDragAndDrop] && _shouldSlideBack && anOperation === CPDragOperationNone)
{
// Store state to finalize drag after animation completes
_pendingEndLocation = aLocation;
_pendingEndOperation = anOperation;
var currentFrame = [_draggedWindow frame],
targetFrame = CGRectMake(_startDragLocation.x, _startDragLocation.y, currentFrame.size.width, currentFrame.size.height);
// We use CPViewAnimation. Even though _draggedWindow is a CPWindow,
// CPViewAnimation supports targets that respond to setFrame: (like NSViewAnimation does for NSWindow).
var animation = [[CPViewAnimation alloc] initWithViewAnimations:[
[CPDictionary dictionaryWithObjects:[_draggedWindow, currentFrame, targetFrame]
forKeys:[CPViewAnimationTargetKey, CPViewAnimationStartFrameKey, CPViewAnimationEndFrameKey]]
]];
[animation setAnimationCurve:CPAnimationEaseOut];
[animation setDuration:0.25];
[animation setDelegate:self];
[animation startAnimation];
return;
}
[self _performFinalCleanupWithLocation:aLocation operation:anOperation];
}
- (void)animationDidEnd:(CPAnimation)anAnimation
{
[self _performFinalCleanupWithLocation:_pendingEndLocation operation:_pendingEndOperation];
}
- (void)animationDidStop:(CPAnimation)anAnimation
{
[self _performFinalCleanupWithLocation:_pendingEndLocation operation:_pendingEndOperation];
}
- (void)_performFinalCleanupWithLocation:(CGPoint)aLocation operation:(CPDragOperation)anOperation
{
[_draggedView removeFromSuperview];
if (![CPPlatform supportsDragAndDrop])
+3 -31
View File
@@ -70,7 +70,6 @@ var _CPEventPeriodicEventPeriod = 0,
BOOL _isARepeat;
unsigned _keyCode;
DOMEvent _DOMEvent;
BOOL _isActionKey;
int _data1;
int _data2;
short _subtype;
@@ -111,28 +110,17 @@ var _CPEventPeriodicEventPeriod = 0,
@param unmodCharacters the string of keys pressed without the presence of any modifiers other than Shift
@param repeatKey \c YES if this is caused by the system repeat as opposed to the user pressing the key again
@param code a number associated with the keyboard key of this event
@param isAnActionKey a BOOL indicating whether this key is an action key (e.g. a function key)
@throws CPInternalInconsistencyException if \c anEventType is not a CPKeyDown,
CPKeyUp or CPFlagsChanged
@return the keyboard event
*/
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:isAnActionKey];
}
// for backwards compatibility only
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:NO];
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code];
}
/*!
@@ -264,7 +252,7 @@ var _CPEventPeriodicEventPeriod = 0,
/* @ignore */
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code
{
if (self = [self _initWithType:anEventType])
{
@@ -276,7 +264,6 @@ var _CPEventPeriodicEventPeriod = 0,
_charactersIgnoringModifiers = unmodCharacters;
_isARepeat = isARepeat;
_keyCode = code;
_isActionKey = isAnActionKey;
_windowNumber = aWindowNumber;
}
@@ -584,21 +571,6 @@ var _CPEventPeriodicEventPeriod = 0,
return !firstResponderIsText;
}
- (BOOL)_isActionOrCommandEvent
{
// This method is now platform-agnostic. It checks for abstract properties
// of the event, including the _isActionKey flag that was set at creation time.
return (
// Is it a command shortcut?
(_modifierFlags & (CPCommandKeyMask | CPControlKeyMask)) ||
// Is it a key that doesn't produce a character?
([_characters length] === 0) ||
// Was it identified as an action key by the platform-specific layer?
_isActionKey
);
}
/*!
Return YES if this event is a part of processing a browser controlled cut or paste event
where the browser will go ahead and do the work of cutting or pasting within the input
-16
View File
@@ -464,22 +464,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
*/
+1 -7
View File
@@ -29,8 +29,6 @@
@global CPApp
@class CPFontPanel
@global document
CPItalicFontMask = 1 << 0;
CPBoldFontMask = 1 << 1;
CPUnboldFontMask = 1 << 2;
@@ -208,11 +206,7 @@ CPRemoveTraitFontAction = 7;
- (@action)addFontTrait:(id)sender
{
var tag = sender;
if ([sender respondsToSelector:@selector(tag)])
tag = [sender tag];
var tag = [sender tag];
_activeChange = tag == nil ? @{} : @{ @"addTraits": tag };
_fontAction = CPAddTraitFontAction;
+6 -46
View File
@@ -31,10 +31,8 @@
@import "CGGeometry.j"
@import "CPCompatibility.j"
@import "CPGraphicsContext.j"
@class CPColor
@global document
@protocol CPImageDelegate <CPObject>
@@ -517,8 +515,8 @@ function CPAppKitImage(aFilename, aSize)
@end
// MARK: -
// MARK: CSS Theming
#pragma mark -
#pragma mark CSS Theming
// The code below adds support for CSS theming with 100% compatibility with current theming system.
// The idea is to extend CPImage (and CPColor) with CSS components and adapt low level UI components to
@@ -738,7 +736,7 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
CPImageCSSBeforeDictionaryKey = @"CPImageCSSBeforeDictionaryKey",
CPImageCSSAfterDictionaryKey = @"CPImageCSSAfterDictionaryKey";
// MARK: -
#pragma mark -
@implementation CPImage (CPCoding)
@@ -775,45 +773,7 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
@end
// MARK: -
// MARK: Drawing
@implementation CPImage (Drawing)
- (void)drawAtPoint:(CGPoint)point fromRect:(CPRect)fromRect operation:(CGBlendMode)op fraction:(float)delta
{
if (_loadStatus !== CPImageLoadStatusCompleted)
return;
var context = [CPGraphicsContext currentContext].graphicsPort;
if (!context)
return;
CGContextSaveGState(context);
CGContextSetBlendMode(context, op);
CGContextSetAlpha(context, delta);
context.drawImage(
_image,
fromRect.origin.x,
fromRect.origin.y,
fromRect.size.width,
fromRect.size.height,
point.x,
point.y,
fromRect.size.width,
fromRect.size.height
);
CGContextRestoreGState(context);
}
@end
// MARK: -
#pragma mark -
@implementation _CPMaterialIconImage : CPImage
{
@@ -973,7 +933,7 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
@end
// MARK: -
#pragma mark -
@implementation CPThreePartImage : CPObject
{
@@ -1116,7 +1076,7 @@ var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
@end
// MARK: -
#pragma mark -
@implementation CPImage (Duplication)
-2
View File
@@ -29,8 +29,6 @@
@global CPImagesPboardType
@global appkit_tag_dom_elements
@global document
@typedef CPImageAlignment
CPImageAlignCenter = 0;
CPImageAlignTop = 1;
-1
View File
@@ -30,7 +30,6 @@ CPStandardKeyBindings = {
@"@.": @"cancelOperation:",
@"@a": @"selectAll:",
@"@~$v": @"pasteAsPlainText:",
@"^a": @"moveToBeginningOfParagraph:",
@"^$a": @"moveToBeginningOfParagraphAndModifySelection:",
@"^b": @"moveBackward:",
+1 -5
View File
@@ -208,11 +208,7 @@ var CPBindingOperationAnd = 0,
options = [_info objectForKey:CPOptionsKey],
newValue = [destination valueForKeyPath:keyPath];
// give nil values the chance to be transformed to true via CPNegateBoolean (issue #1986)
if ((newValue == nil || CPIsControllerMarker(newValue)) && [options objectForKey:CPValueTransformerNameBindingOption] === CPNegateBooleanTransformerName)
[self setValue:[self transformValue:NO withOptions:options] forBinding:theBinding];
else if (CPIsControllerMarker(newValue))
if (CPIsControllerMarker(newValue))
{
[self raiseIfNotApplicable:newValue forKeyPath:keyPath options:options];
+3 -65
View File
@@ -27,7 +27,6 @@
@import "CPKeyValueBinding.j"
@import "CPMenuItem.j"
@import "CALayer.j"
@global CPApp
@@ -269,8 +268,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;
@@ -375,10 +373,6 @@ var _CPMenuBarVisible = NO,
[self willChangeValueForKey:@"items"];
_items = [CPMutableArray array];
[self didChangeValueForKey:@"items"];
// Ensure the main menu updates if cleared
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
/*!
@@ -395,9 +389,6 @@ var _CPMenuBarVisible = NO,
if ([aMenuItem menu] !== self || !_items)
return;
if (_menuWindow)
[[_menuWindow _menuView] tile];
[aMenuItem setValue:[aMenuItem valueForKey:@"changeCount"] + 1 forKey:@"changeCount"];
[[CPNotificationCenter defaultCenter]
@@ -1025,10 +1016,6 @@ var _CPMenuBarVisible = NO,
// an additional mouse move after all this, but not in other browsers.
// This will be fixed correctly with the coming run loop changes.
[_CPDisplayServer run];
// If layers are updated within the menu callback, the re-draw & re-layout doesn't occur until a mouse move.
// We force the update here.
[CALayer runLoopUpdateLayers];
}
/* @ignore */
@@ -1070,23 +1057,7 @@ var _CPMenuBarVisible = NO,
if ([anEvent _triggersKeyEquivalent:[item keyEquivalent] withModifierMask:[item keyEquivalentModifierMask]])
{
if ([item isEnabled])
{
// Flash the top-level item if this is the Main Menu
if (self === [CPApp mainMenu])
[self _flashItemAtIndex:index];
anEvent._isKeyEquivalent = YES; // prevent the menu keystroke from beeing inserted into textview
[self performActionForItemAtIndex:index];
#if PLATFORM(DOM)
// we are done with this event in cappuccino space. do not let the browser do something weird additionally (e.g. command-o).
// but we must not stop copy/paste events as these can only be handled by the browser at this time even if they are in our menu
// (until we move to CPTextView as the fieleditor)
if (characters != "c" && characters != "x" && characters != "v")
_CPDOMEventStop(anEvent._DOMEvent);
#endif
}
else
{
//beep?
@@ -1096,13 +1067,7 @@ var _CPMenuBarVisible = NO,
}
if ([[item submenu] performKeyEquivalent:anEvent])
{
// Flash the top-level item if a submenu handled the event
if (self === [CPApp mainMenu])
[self _flashItemAtIndex:index];
return YES;
}
}
return NO;
@@ -1182,25 +1147,6 @@ var _CPMenuBarVisible = NO,
return nil;
}
//
/*
@ignore
*/
- (void)_flashItemAtIndex:(int)anIndex
{
// If we are using a native bridge (like a desktop wrapper), let the OS handle the visual feedback.
if ([CPPlatform supportsNativeMainMenu])
return;
[self _highlightItemAtIndex:anIndex];
[self performSelector:@selector(_stopFlashingItem) withObject:nil afterDelay:0.2];
}
- (void)_stopFlashingItem
{
[self _highlightItemAtIndex:CPNotFound];
}
@end
@@ -1272,11 +1218,6 @@ var _CPMenuBarVisible = NO,
postNotificationName:CPMenuDidAddItemNotification
object:self
userInfo:@{ @"CPMenuItemIndex": anIndex }];
// FIX #1222: If this is the main menu, force the shared menu bar window to refresh its layout.
// This ensures new items are positioned correctly (e.g. not pushed to the far right by previous layout states).
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
- (void)removeObjectFromItemsAtIndex:(CPUInteger)anIndex
@@ -1292,10 +1233,6 @@ var _CPMenuBarVisible = NO,
postNotificationName:CPMenuDidRemoveItemNotification
object:self
userInfo:@{ @"CPMenuItemIndex": anIndex }];
// FIX #1222: Ensure the shared menu bar updates layout when items are removed.
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
@end
@@ -1356,7 +1293,7 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
@end
// MARK: -
#pragma mark -
@implementation CPMenu (CSSTheming)
@@ -1389,3 +1326,4 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
@import "_CPMenuBarWindow.j"
@import "_CPMenuWindow.j"
+23 -51
View File
@@ -31,8 +31,6 @@
@global CPMenuDidChangeItemNotification
@global CPMenuDidRemoveItemNotification
@global document
@implementation _CPMenuBarWindow : CPPanel
{
CPView _highlightView;
@@ -370,41 +368,12 @@
- (void)tile
{
var bounds = [[self contentView] bounds],
height = CGRectGetHeight(bounds),
x = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-left-margin" forClass:_CPMenuView];
// 1. Layout the Icon (if present)
if (_iconImageView && ![_iconImageView isHidden])
{
var iconFrame = [_iconImageView frame];
iconFrame.origin.x = x;
// Vertically center
iconFrame.origin.y = (height - CGRectGetHeight(iconFrame)) / 2.0;
[_iconImageView setFrame:iconFrame];
x = CGRectGetMaxX(iconFrame) + 6.0; // Spacing between icon and title
}
// 2. Layout the Title (if present)
if (_titleField && [_titleField stringValue] && [[_titleField stringValue] length] > 0)
{
var titleFrame = [_titleField frame];
titleFrame.origin.x = x;
titleFrame.origin.y = (height - CGRectGetHeight(titleFrame)) / 2.0;
[_titleField setFrame:titleFrame];
x = CGRectGetMaxX(titleFrame) + 12.0; // Spacing between title and menu items
}
// 3. Layout the Menu Items
var items = [_menu itemArray],
index = 0,
count = items ? items.length : 0,
count = items.length,
x = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-left-margin" forClass:_CPMenuView],
y = 0.0,
isLeftAligned = YES;
for (; index < count; ++index)
@@ -419,18 +388,11 @@
continue;
}
// Fix for #1742: If a main menu item does not have a submenu, it should not appear in the menu bar.
if ([item isHidden] || ![item submenu])
{
[[item _menuItemView] setHidden:YES];
if ([item isHidden])
continue;
}
var menuItemView = [item _menuItemView];
[menuItemView setHidden:NO];
var frame = [menuItemView frame];
var menuItemView = [item _menuItemView],
frame = [menuItemView frame];
if (isLeftAligned)
{
@@ -445,6 +407,21 @@
x = CGRectGetMinX([menuItemView frame]);
}
}
var bounds = [[self contentView] bounds],
titleFrame = [_titleField frame];
if ([_iconImageView isHidden])
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(titleFrame)) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
else
{
var iconFrame = [_iconImageView frame],
iconWidth = CGRectGetWidth(iconFrame),
totalWidth = iconWidth + CGRectGetWidth(titleFrame);
[_iconImageView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(iconFrame)) / 2.0)];
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0 + iconWidth, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
}
}
- (void)setFrame:(CGRect)aRect display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate
@@ -485,18 +462,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;
}
+3 -16
View File
@@ -234,19 +234,6 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
// FIXME: This gets called far too often.
_unconstrainedFrame = CGRectMakeCopy(aFrame);
// If we are a submenu and we are being displayed off the right of the screen,
// we should try and display on the left of our supermenu.
var supermenu = [[self menu] supermenu];
if (supermenu && (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"];
@@ -433,7 +420,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end
// MARK: -
#pragma mark -
@implementation _CPMenuWindow (CSSTheming)
@@ -446,7 +433,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end
// MARK: -
#pragma mark -
/*
@ignore
@@ -617,7 +604,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end
// MARK: -
#pragma mark -
@implementation _CPMenuView (CSSTheming)
+4 -4
View File
@@ -826,7 +826,7 @@ CPControlKeyMask
return [[self menu] highlightedItem] == self;
}
// MARK: CPObject Overrides
#pragma mark CPObject Overrides
/*!
Returns a copy of the item. The copy does not belong If the item has a submenu, it is NOT copied.
@@ -867,7 +867,7 @@ CPControlKeyMask
return [self copy];
}
// MARK: Internal
#pragma mark Internal
/*
@ignore
@@ -897,7 +897,7 @@ CPControlKeyMask
@end
// MARK: -
#pragma mark -
@implementation CPMenuItem (CSSTheming)
@@ -909,7 +909,7 @@ CPControlKeyMask
@end
// MARK: -
#pragma mark -
var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
+3 -3
View File
@@ -406,11 +406,11 @@
@end
// MARK: -
#pragma mark -
@implementation _CPMenuItemStandardView (CSSTheming)
// MARK: Override
#pragma mark Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
@@ -420,7 +420,7 @@
@end
// MARK: -
#pragma mark -
@implementation _CPMenuItemSubmenuIndicatorView : CPView
{
+3 -3
View File
@@ -252,11 +252,11 @@
@end
// MARK: -
#pragma mark -
@implementation _CPMenuItemView (CSSTheming)
// MARK: Override
#pragma mark Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
@@ -275,7 +275,7 @@
@end
// MARK: -
#pragma mark -
@implementation _CPMenuItemArrowView : CPView
{
+2 -3
View File
@@ -808,8 +808,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
var value = [self _controllerMarkerForValues:values];
[_cachedValues setObject:value forKey:theKeyPath];
// Apple's implementation returns nil instead of CPNullMarker
return value === CPNullMarker ? nil : value;
return value;
}
else
return values;
@@ -949,4 +948,4 @@ var CPManagedProxyEntityNameKey = @"CPManagedProxyEntity
[aCoder encodeObject:[self fetchPredicate] forKey:CPManagedProxyFetchPredicateKey];
}
@end
@end
+91 -451
View File
@@ -23,7 +23,6 @@
@import "CPButton.j"
@import "CPTableColumn.j"
@import "CPTableView.j"
@import "CPTreeNode.j"
@global CPApp
@@ -128,112 +127,16 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
@protocol CPOutlineViewDataSource <CPObject>
@optional
/*!
@abstract Invoked when a drag operation concludes over the outline view.
@discussion The data source should incorporate the data from the dragging pasteboard and update its data model.
@param anOutlineView The outline view that is the destination of the drop.
@param info An object that contains information about the dragging session.
@param anItem The item that is the proposed parent for the dropped data. If anItem is nil, the data is to be dropped at the root level.
@param anIndex The index at which to drop the data among the item's children. If you want to drop on anItem, this will be CPOutlineViewDropOnItemIndex (-1).
@return YES if the drop was successful; otherwise, NO.
*/
- (BOOL)outlineView:(CPOutlineView)anOutlineView acceptDrop:(id /*<CPDraggingInfo>*/)info item:(id)anItem childIndex:(CPInteger)anIndex;
/*!
@abstract Asks the data source whether to defer displaying the children of a given item.
@discussion This method is useful for implementing lazy loading of outline view data. Returning NO prevents the outline view from querying for children of anItem, even if it is expandable.
@param anOutlineView The outline view that sent the message.
@param anItem The item being considered for expansion.
@return YES to allow the outline view to query for children of anItem; otherwise, NO. The default is YES.
*/
- (BOOL)outlineView:(CPOutlineView)anOutlineView shouldDeferDisplayingChildrenOfItem:(id)anItem;
/*!
@abstract Invoked when a drag should begin.
@discussion The data source should write the representation of the specified items to the pasteboard.
@param anOutlineView The outline view that is the source of the drag.
@param items An array of items to be dragged.
@param pboard The pasteboard to which the data for the dragged items should be written.
@return YES if the drag should begin; NO to prevent the drag.
*/
- (BOOL)outlineView:(CPOutlineView)anOutlineView writeItems:(CPArray)items toPasteboard:(CPPasteboard)pboard;
/*!
@abstract Used for promised-file dragging.
@discussion When a promised-file drag is dropped, this method is invoked to ask the data source to create the files at the specified destination and return their names.
@param anOutlineView The outline view that was the source of the drag.
@param dropDestination The URL of the directory where the files should be created.
@param items The items that were dragged, representing the promised files.
@return An array of strings containing the names of the files that were created.
*/
- (CPArray)outlineView:(CPOutlineView)anOutlineView namesOfPromisedFilesDroppedAtDestination:(CPURL)dropDestination forDraggedItems:(CPArray)items;
/*!
@abstract Invoked to determine if a drop is allowed at a specified location.
@discussion This method is called repeatedly while the user drags over the outline view. It should return the drag operation that should be performed.
@param anOutlineView The outline view that is the destination of the drag.
@param info An object that contains information about the dragging session.
@param anItem The item that is the proposed parent for the dropped data.
@param anIndex The index at which to drop the data among the item's children. If you want to drop on anItem, this will be CPOutlineViewDropOnItemIndex (-1).
@return A CPDragOperation value that indicates the type of operation to perform.
*/
- (CPDragOperation)outlineView:(CPOutlineView)anOutlineView validateDrop:(id /*<CPDraggingInfo>*/)info proposedItem:(id)anItem proposedChildIndex:(CPInteger)anIndex;
/*!
@abstract Invoked to determine if a drop is allowed at a specified row.
@discussion This is a legacy method from CPTableView. It is recommended to implement outlineView:validateDrop:proposedItem:proposedChildIndex: instead for more precise control in an outline view.
@param anOutlineView The outline view that is the destination of the drag.
@param info An object that contains information about the dragging session.
@param theRow The proposed row for the drop.
@param theOperation The proposed drop operation (CPTableViewDropOn or CPTableViewDropAbove).
@return A CPDragOperation value that indicates the type of operation to perform.
*/
- (CPDragOperation)outlineView:(CPOutlineView)anOutlineView validateDrop:(id /*<CPDraggingInfo>*/)info proposedRow:(int)theRow proposedDropOperation:(CPTableViewDropOperation)theOperation;
/*!
@abstract Used for state preservation.
@discussion This method is called to convert a persistent, serializable object back into a model item.
@param anOutlineView The outline view requesting the item.
@param anObject The persistent object used to identify the model item.
@return The model item corresponding to anObject, or nil if it cannot be found.
*/
- (id)outlineView:(CPOutlineView)anOutlineView itemForPersistentObject:(id)anObject;
/*!
@abstract Returns the data object to be displayed for a given item and column.
@discussion This method is called by the outline view to get the value for each cell. It is required for cell-based outline views.
@param anOutlineView The outline view that sent the message.
@param aTableColumn The column for which the value is requested.
@param anItem The item for the row being displayed.
@return The data object (e.g., a CPString) for the specified item and column.
*/
- (id)outlineView:(CPOutlineView)anOutlineView objectValueforTableColumn:(CPTableColumn)aTableColumn byItem:(id)anItem;
/*!
@abstract Used for state preservation.
@discussion This method is called to convert a model item into a persistent, serializable object (e.g., a string identifier) that can be saved.
@param anOutlineView The outline view requesting the persistent object.
@param anItem The item to be converted.
@return A serializable object that persistently identifies anItem.
*/
- (id)outlineView:(CPOutlineView)anOutlineView persistentObjectForItem:(id)anItem;
/*!
@abstract Sets the data object for a given item and column.
@discussion This method is called when the user edits a cell's value. The data source should update its model with the new value.
@param anOutlineView The outline view that sent the message.
@param anObject The new value.
@param aTableColumn The column that was edited.
@param anItem The item whose value was edited.
*/
- (void)outlineView:(CPOutlineView)anOutlineView setObjectValue:(id)anObject forTableColumn:(CPTableColumn)aTableColumn byItem:(id)anItem;
/*!
@abstract Notifies the data source that the sort descriptors have changed.
@discussion This method is called after the user clicks a column header to change the sort order. The data source should re-sort its data based on the outline view's new 'sortDescriptors' property and then call `reloadData`.
@param anOutlineView The outline view that sent the message.
@param oldDescriptors The previous sort descriptors.
*/
- (void)outlineView:(CPOutlineView)anOutlineView sortDescriptorsDidChange:(CPArray)oldDescriptors;
@end
@@ -312,8 +215,8 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
[self setIndentationPerLevel:16.0];
[self setIndentationMarkerFollowsDataView:YES];
[super setDataSource:self];
[super setDelegate:self];
[super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
[super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
[self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
}
@@ -774,11 +677,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
[self reloadItem:anItem reloadChildren:NO];
}
- (int)_numberOfRows
{
return _itemsForRows ? _itemsForRows.length : 0;
}
/*!
Reloads the data for a given item and optionally the children.
@@ -787,12 +685,9 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
*/
- (void)reloadItem:(id)anItem reloadChildren:(BOOL)shouldReloadChildren
{
_pendingItemToClean = [];
_itemAddedDuringLastLoading = [];
var previousRowCount = _itemsForRows.length;
if (!!shouldReloadChildren || !anItem)
[self _loadItemInfoForItem:anItem intermediate:NO];
else
@@ -800,11 +695,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
[self _cleanPendingItem];
// Safely update the table size and force a synchronous layout recalculation
// BEFORE the views are reloaded, avoiding the clipping issue.
if (_itemsForRows.length !== previousRowCount)
[self noteNumberOfRowsChanged];
[super _reloadDataViews];
}
@@ -851,20 +741,9 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
for (var i = [previousItems count] - 1; i >= 0; i--)
{
var item = previousItems[i],
found = NO;
var item = previousItems[i];
// Use strict identity (===) instead of containsObject: (which triggers isEqual:)
for (var j = 0, count = children.length; j < count; j++)
{
if (children[j] === item)
{
found = YES;
break;
}
}
if (!found)
if (![children containsObject:item])
[self _addPendingItem:item];
}
}
@@ -878,8 +757,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
var children = itemInfo.children;
// Fixed out-of-bounds index (was previously [children count])
for (var i = children.length - 1; i >= 0; i--)
for (var i = [children count]; i >= 0; i--)
{
var child = children[i];
[self _addPendingItem:child];
@@ -890,7 +768,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
- (void)_cleanPendingItem
{
for (var i = [_pendingItemToClean count] - 1; i >= 0; i--)
for (var i = [_pendingItemToClean count]; i >= 0; i--)
{
var item = _pendingItemToClean[i];
@@ -934,8 +812,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
var weight = itemInfo.weight,
descendants = anItem ? [anItem] : [];
if (anItem)
[_itemAddedDuringLastLoading addObject:anItem];
[_itemAddedDuringLastLoading addObject:anItem];
if (itemInfo.isExpanded && [self _sendDataSourceShouldDeferDisplayingChildrenOfItem:anItem])
{
@@ -1112,7 +989,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
var parent = itemInfo.parent;
// Check if the parent is the root item because we never return the actual root item
if (parent && _itemInfosForItems[[parent UID]] === _rootItemInfo)
if (itemInfo[[parent UID]] === _rootItemInfo)
parent = nil;
return parent;
@@ -1513,21 +1390,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
return [super frameOfDataViewAtColumn:aColumn row:aRow];
}
- (void)_applyToolTipToDataView:(CPView)aDataView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
{
if (_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_)
{
var item = [self itemAtRow:aRow],
tooltip = [self _sendDelegateToolTipForView:aDataView rect:[aDataView frame] tableColumn:aTableColumn item:item mouseLocation:CGPointMakeZero()];
[aDataView setToolTip:tooltip];
}
else
{
[super _applyToolTipToDataView:aDataView forTableColumn:aTableColumn row:aRow];
}
}
/*!
Retargets the drop item for the outlineview.
@@ -2017,28 +1879,43 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_;
}
/*** CPTableViewDataSource methods ***/
@end
@implementation _CPOutlineViewTableViewDataSource : CPObject
{
CPObject _outlineView;
}
- (id)initWithOutlineView:(CPOutlineView)anOutlineView
{
self = [super init];
if (self)
_outlineView = anOutlineView;
return self;
}
- (CPInteger)numberOfRowsInTableView:(CPTableView)anOutlineView
{
return _itemsForRows.length;
return _outlineView._itemsForRows.length;
}
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
{
return [_outlineViewDataSource outlineView:self objectValueForTableColumn:aTableColumn byItem:_itemsForRows[aRow]];
return [_outlineView._outlineViewDataSource outlineView:_outlineView objectValueForTableColumn:aTableColumn byItem:_outlineView._itemsForRows[aRow]];
}
- (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
{
if (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_))
if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_))
return;
[_outlineViewDataSource outlineView:self setObjectValue:aValue forTableColumn:aColumn byItem:_itemsForRows[aRow]];
[_outlineView._outlineViewDataSource outlineView:_outlineView setObjectValue:aValue forTableColumn:aColumn byItem:_outlineView._itemsForRows[aRow]];
}
- (BOOL)tableView:(CPTableView)aTableColumn writeRowsWithIndexes:(CPIndexSet)theIndexes toPasteboard:(CPPasteboard)thePasteboard
{
if (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_))
if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_))
return NO;
var items = [],
@@ -2046,27 +1923,27 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
while (index !== CPNotFound)
{
[items addObject:[self itemAtRow:index]];
[items addObject:[_outlineView itemAtRow:index]];
index = [theIndexes indexGreaterThanIndex:index];
}
return [_outlineViewDataSource outlineView:self writeItems:items toPasteboard:thePasteboard];
return [_outlineView._outlineViewDataSource outlineView:_outlineView writeItems:items toPasteboard:thePasteboard];
}
- (int)_childIndexForDropOperation:(CPTableViewDropOperation)theDropOperation row:(CPInteger)theRow offset:(CGPoint)theOffset
{
if (_shouldRetargetChildIndex)
return _retargedChildIndex;
if (_outlineView._shouldRetargetChildIndex)
return _outlineView._retargedChildIndex;
var childIndex = CPNotFound;
if (theDropOperation === CPTableViewDropAbove)
{
var parentItem = [self _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset],
itemInfo = (parentItem != nil) ? _itemInfosForItems[[parentItem UID]] : _rootItemInfo,
var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset],
itemInfo = (parentItem != nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo,
children = itemInfo.children;
childIndex = [children indexOfObject:[self itemAtRow:theRow]];
childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]];
if (childIndex === CPNotFound)
childIndex = children.length;
@@ -2077,151 +1954,155 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
return childIndex;
}
- (id)_parentItemForDropOperation:(CPTableViewDropOperation)theDropOperation row:(CPInteger)theRow offset:(CGPoint)theOffset
- (void)_parentItemForDropOperation:(CPTableViewDropOperation)theDropOperation row:(CPInteger)theRow offset:(CGPoint)theOffset
{
if (theDropOperation === CPTableViewDropAbove)
return [self _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset];
return [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset];
return [self itemAtRow:theRow];
return [_outlineView itemAtRow:theRow];
}
- (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id /*< CPDraggingInfo >*/)theInfo
proposedRow:(CPInteger)theRow proposedDropOperation:(CPTableViewDropOperation)theOperation
{
if (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_))
if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_))
return CPDragOperationNone;
// Make sure the retargeted item and index are reset
_retargetedItem = nil;
_shouldRetargetItem = NO;
_outlineView._retargetedItem = nil;
_outlineView._shouldRetargetItem = NO;
_retargedChildIndex = nil;
_shouldRetargetChildIndex = NO;
_outlineView._retargedChildIndex = nil;
_outlineView._shouldRetargetChildIndex = NO;
var location = [self convertPoint:[theInfo draggingLocation] fromView:nil],
var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
return [_outlineViewDataSource outlineView:self validateDrop:theInfo proposedItem:parentItem proposedChildIndex:childIndex];
return [_outlineView._outlineViewDataSource outlineView:_outlineView validateDrop:theInfo proposedItem:parentItem proposedChildIndex:childIndex];
}
- (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id /*<CPDraggingInfo>*/)theInfo row:(CPInteger)theRow dropOperation:(CPTableViewDropOperation)theOperation
{
if (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_))
if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_))
return NO;
var location = [self convertPoint:[theInfo draggingLocation] fromView:nil],
var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
_retargetedItem = nil;
_shouldRetargetItem = NO;
_outlineView._retargetedItem = nil;
_outlineView._shouldRetargetItem = NO;
_retargedChildIndex = nil;
_shouldRetargetChildIndex = NO;
_outlineView._retargedChildIndex = nil;
_outlineView._shouldRetargetChildIndex = NO;
return [_outlineViewDataSource outlineView:self acceptDrop:theInfo item:parentItem childIndex:childIndex];
return [_outlineView._outlineViewDataSource outlineView:_outlineView acceptDrop:theInfo item:parentItem childIndex:childIndex];
}
- (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldSortDescriptors
{
if ((_implementedOutlineViewDataSourceMethods &
if ((_outlineView._implementedOutlineViewDataSourceMethods &
CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_))
{
[[self dataSource] outlineView:self sortDescriptorsDidChange:oldSortDescriptors];
[[_outlineView dataSource] outlineView:_outlineView sortDescriptorsDidChange:oldSortDescriptors];
}
}
/*** CPTableViewDelegate methods ***/
@end
/*!
@ignore
*/
- (CPString)_sendDelegateToolTipForView:(id)aView rect:(CGRect)aRect tableColumn:(CPTableColumn)aTableColumn item:(id)anItem mouseLocation:(CGPoint)aPoint
@implementation _CPOutlineViewTableViewDelegate : CPObject
{
if (!(_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_))
return nil;
CPOutlineView _outlineView;
}
return [_outlineViewDelegate outlineView:self toolTipForView:aView rect:aRect tableColumn:aTableColumn item:anItem mouseLocation:aPoint];
- (id)initWithOutlineView:(CPOutlineView)anOutlineView
{
self = [super init];
if (self)
_outlineView = anOutlineView;
return self;
}
- (BOOL)tableView:(CPTableView)theTableView shouldSelectRow:(CPInteger)theRow
{
return SHOULD_SELECT_ITEM(self, [self itemAtRow:theRow]);
return SHOULD_SELECT_ITEM(_outlineView, [_outlineView itemAtRow:theRow]);
}
- (BOOL)selectionShouldChangeInTableView:(CPTableView)theTableView
{
return SELECTION_SHOULD_CHANGE(self);
return SELECTION_SHOULD_CHANGE(_outlineView);
}
- (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
{
if ((_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_))
return [_outlineViewDelegate outlineView:self shouldEditTableColumn:aColumn item:[self itemAtRow:aRow]];
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_))
return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldEditTableColumn:aColumn item:[_outlineView itemAtRow:aRow]];
return NO;
}
- (float)tableView:(CPTableView)theTableView heightOfRow:(CPInteger)theRow
{
if ((_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_heightOfRowByItem_))
return [_outlineViewDelegate outlineView:self heightOfRowByItem:[self itemAtRow:theRow]];
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_heightOfRowByItem_))
return [_outlineView._outlineViewDelegate outlineView:_outlineView heightOfRowByItem:[_outlineView itemAtRow:theRow]];
return [theTableView rowHeight];
}
- (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
{
if ((_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_))
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_))
{
var item = [self itemAtRow:aRowIndex];
[_outlineViewDelegate outlineView:self willDisplayView:aView forTableColumn:aTableColumn item:item];
var item = [_outlineView itemAtRow:aRowIndex];
[_outlineView._outlineViewDelegate outlineView:_outlineView willDisplayView:aView forTableColumn:aTableColumn item:item];
}
}
- (void)tableView:(CPTableView)aTableView willRemoveView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
{
if ((_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willRemoveView_forTableColumn_item_))
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willRemoveView_forTableColumn_item_))
{
var item = [self itemAtRow:aRowIndex];
[_outlineViewDelegate outlineView:self willRemoveView:aView forTableColumn:aTableColumn item:item];
var item = [_outlineView itemAtRow:aRowIndex];
[_outlineView._outlineViewDelegate outlineView:_outlineView willRemoveView:aView forTableColumn:aTableColumn item:item];
}
}
- (BOOL)tableView:(CPTableView)aTableView isGroupRow:(CPInteger)aRow
{
if ((_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_isGroupItem_))
return [_outlineViewDelegate outlineView:self isGroupItem:[self itemAtRow:aRow]];
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_isGroupItem_))
return [_outlineView._outlineViewDelegate outlineView:_outlineView isGroupItem:[_outlineView itemAtRow:aRow]];
return NO;
}
- (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
{
if ((_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_menuForTableColumn_item_))
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_menuForTableColumn_item_))
{
var item = [self itemAtRow:aRow];
return [_outlineViewDelegate outlineView:self menuForTableColumn:aTableColumn item:item]
var item = [_outlineView itemAtRow:aRow];
return [_outlineView._outlineViewDelegate outlineView:_outlineView menuForTableColumn:aTableColumn item:item]
}
// We reimplement CPView menuForEvent: because we can't call it directly. CPTableView implements menuForEvent:
// to call this delegate method.
return [self menu] || [[self class] defaultMenu];
return [_outlineView menu] || [[_outlineView class] defaultMenu];
}
- (CPIndexSet)tableView:(CPTableView)aTableView selectionIndexesForProposedSelection:(CPIndexSet)anIndexSet
{
if ((_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_))
return [_outlineViewDelegate outlineView:self selectionIndexesForProposedSelection:anIndexSet];
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_))
return [_outlineView._outlineViewDelegate outlineView:_outlineView selectionIndexesForProposedSelection:anIndexSet];
return anIndexSet;
}
- (BOOL)tableView:(CPTableView)aTableView shouldSelectTableColumn:(CPTableColumn)aTableColumn
{
if ((_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_))
return [_outlineViewDelegate outlineView:self shouldSelectTableColumn:aTableColumn];
if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_))
return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldSelectTableColumn:aTableColumn];
return YES;
}
@@ -2327,8 +2208,8 @@ var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey"
_outlineViewDataSource = [aCoder decodeObjectForKey:CPOutlineViewDataSourceKey];
_outlineViewDelegate = [aCoder decodeObjectForKey:CPOutlineViewDelegateKey];
[super setDataSource:self];
[super setDelegate:self];
[super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
[super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
[self _updateIsViewBased];
}
@@ -2367,244 +2248,3 @@ var colorForDisclosureTriangle = function(isSelected, isHighlighted)
? [CPColor colorWithCalibratedWhite:0.4 alpha: 1.0]
: [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0]);
};
@implementation CPOutlineView (CPBindings)
+ (void)initialize
{
if (self !== [CPOutlineView class])
return;
[self exposeBinding:@"content"];
[self exposeBinding:@"selectionIndexPaths"];
[self exposeBinding:@"sortDescriptors"];
}
/*!
Returns the currently selected index paths.
This allows the outline view to be KVC-compliant for `selectionIndexPaths`.
*/
- (CPArray)selectionIndexPaths
{
var indexes = [self selectedRowIndexes],
paths = [CPMutableArray array],
index = [indexes firstIndex];
while (index !== CPNotFound)
{
var item = [self itemAtRow:index];
// Check if the item is a CPTreeNode proxy (which it will be when bound to CPTreeController)
if ([item respondsToSelector:@selector(indexPath)])
[paths addObject:[item indexPath]];
index = [indexes indexGreaterThanIndex:index];
}
return paths;
}
@end
@implementation CPOutlineView (CPBinder)
- (id)content { return nil; }
- (void)setContent:(id)aContent { }
- (void)setSelectionIndexPaths:(CPArray)paths { }
+ (Class)_binderClassForBinding:(CPString)aBinding
{
if (aBinding === @"content")
return [_CPOutlineViewContentBinder class];
if (aBinding === @"selectionIndexPaths")
return [_CPOutlineViewSelectionIndexPathsBinder class];
return [super _binderClassForBinding:aBinding];
}
@end
// --- Content Binder ---
/*!
_CPOutlineViewContentBinder acts as the CPOutlineViewDataSource when the outline view
is bound to a CPTreeController's arrangedObjects.
*/
@implementation _CPOutlineViewContentBinder : CPBinder
{
CPTreeNode _rootNode;
}
- (void)setValueFor:(CPString)aBinding
{
var destination = [_info objectForKey:CPObservedObjectKey],
keyPath = [_info objectForKey:CPObservedKeyPathKey],
value = [destination valueForKeyPath:keyPath];
if (!value || ![value isKindOfClass:[CPTreeNode class]])
_rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil];
else
_rootNode = value;
if ([_source dataSource] !== self)
[_source setDataSource:self];
else
[_source reloadData];
}
- (CPTreeNode)rootNode
{
return _rootNode;
}
// -- CPOutlineViewDataSource implementation --
- (id)outlineView:(CPOutlineView)outlineView child:(CPInteger)index ofItem:(id)item
{
var node = item || _rootNode;
return [[node childNodes] objectAtIndex:index];
}
- (BOOL)outlineView:(CPOutlineView)outlineView isItemExpandable:(id)item
{
var node = item || _rootNode;
return ![node isLeaf];
}
- (int)outlineView:(CPOutlineView)outlineView numberOfChildrenOfItem:(id)item
{
var node = item || _rootNode;
return [[node childNodes] count];
}
- (id)outlineView:(CPOutlineView)outlineView objectValueForTableColumn:(CPTableColumn)tableColumn byItem:(id)item
{
var rep = [item respondsToSelector:@selector(representedObject)] ? [item representedObject] : item;
// Dynamically fetch the value using the column's identifier (e.g., "name")
if (rep && [tableColumn identifier] && [tableColumn identifier] !== @"")
return [rep valueForKey:[tableColumn identifier]];
return rep;
}
- (void)outlineView:(CPOutlineView)outlineView setObjectValue:(id)value forTableColumn:(CPTableColumn)tableColumn byItem:(id)item
{
var rep = [item respondsToSelector:@selector(representedObject)] ?[item representedObject] : item;
// Push the inline edit back to the model using the column's identifier
if (rep && [tableColumn identifier] && [tableColumn identifier] !== @"")
[rep setValue:value forKey:[tableColumn identifier]];
}
- (id)content
{
// CPTableView internals probe the binder for its flat content to draw rows.
if (_source && _source._itemsForRows)
return _source._itemsForRows;
return [];
}
@end
// --- Selection Index Paths Binder ---
/*!
_CPOutlineViewSelectionIndexPathsBinder listens for selection changes on the CPOutlineView
and translates the selected rows into CPIndexPaths to push to the CPTreeController.
It also intercepts changes from the CPTreeController and auto-expands the tree to highlight them.
*/
@implementation _CPOutlineViewSelectionIndexPathsBinder : CPBinder
{
BOOL _isSyncingFromModel;
}
- (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];
[[CPNotificationCenter defaultCenter]
addObserver:self
selector:@selector(outlineViewSelectionDidChange:)
name:CPOutlineViewSelectionDidChangeNotification
object:aSource];
return self;
}
+ (void)unbind:(CPString)aBinding forObject:(id)anObject
{
if (aBinding === "selectionIndexPaths")
[[CPNotificationCenter defaultCenter]
removeObserver:self
name:CPOutlineViewSelectionDidChangeNotification
object:anObject];
[super unbind:aBinding forObject:anObject];
}
- (void)setValueFor:(CPString)aBinding
{
// 1. SUPPRESS KVO AT THE VERY TOP to avoid circular updates when expanding parents
_isSyncingFromModel = YES;
var destination = [_info objectForKey:CPObservedObjectKey],
keyPath = [_info objectForKey:CPObservedKeyPathKey],
indexPaths = [destination valueForKeyPath:keyPath] || [],
indexes = [CPMutableIndexSet indexSet];
// 2. Fetch the root node directly from the CPTreeController (destination)
var rootNode = [destination respondsToSelector:@selector(arrangedObjects)] ? [destination arrangedObjects] : nil;
if (rootNode)
{
for (var i = 0, count = [indexPaths count]; i < count; i++)
{
var item = [rootNode descendantNodeAtIndexPath:[indexPaths objectAtIndex:i]];
if (item)
{
var parentsToExpand = [CPMutableArray array],
parent = [item parentNode];
while (parent && parent !== rootNode)
{
[parentsToExpand insertObject:parent atIndex:0];
parent = [parent parentNode];
}
for (var j = 0; j < [parentsToExpand count]; j++)
[_source expandItem:parentsToExpand[j]];
var row = [_source rowForItem:item];
if (row !== CPNotFound && row >= 0)
[indexes addIndex:row];
}
}
}
// Adjust the CPOutlineView selection
[_source selectRowIndexes:indexes byExtendingSelection:NO];
// 3. Re-enable KVO after adjustments are done
_isSyncingFromModel = NO;
}
- (void)outlineViewSelectionDidChange:(CPNotification)note
{
// We only want to push the change back if we aren't currently syncing down from the model
if (_isSyncingFromModel)
return;
// In CPBinder, reverseSetValueFor: takes the name of the property on _source
// it should fetch the updated value from. Since CPOutlineView has the selectionIndexPaths method:
[self reverseSetValueFor:@"selectionIndexPaths"];
}
@end
+2 -2
View File
@@ -121,8 +121,8 @@ CPDocModalWindowMask = 1 << 6;
}
// MARK: -
// MARK: Overrides
#pragma mark -
#pragma mark Overrides
/*!
@ignore
+10 -10
View File
@@ -84,8 +84,8 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
/*!
Initialize the CPPopover witn default values
@@ -105,8 +105,8 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
}
// MARK: -
// MARK: Getters / Setters
#pragma mark -
#pragma mark Getters / Setters
/*!
Returns the current rect of the popover
@@ -220,8 +220,8 @@ Set the behavior of the CPPopover. It can be:
_implementedDelegateMethods |= CPPopoverDelegate_popover_didClose_;
}
// MARK: -
// MARK: Positioning
#pragma mark -
#pragma mark Positioning
/*!
Show the popover
@@ -298,8 +298,8 @@ Set the behavior of the CPPopover. It can be:
}
// MARK: -
// MARK: Action
#pragma mark -
#pragma mark Action
/*!
Close the popover
@@ -318,8 +318,8 @@ Set the behavior of the CPPopover. It can be:
}
// MARK: -
// MARK: Delegates
#pragma mark -
#pragma mark Delegates
/*! @ignore */
- (BOOL)_popoverWindowShouldClose
+4 -4
View File
@@ -187,7 +187,7 @@ CPRadioImageOffset = 4.0;
[self _setRadioGroup];
}
// MARK: Private methods
#pragma mark Private methods
- (void)_setRadioGroup
{
@@ -260,8 +260,8 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
[aCoder encodeObject:_radioGroup forKey:CPRadioRadioGroupKey];
}
// MARK: -
// MARK: Override methods from CPButton
#pragma mark -
#pragma mark Override methods from CPButton
- (CPThemeState)_contentVisualState
{
@@ -412,7 +412,7 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
[_radios makeObjectsPerformSelector:@selector(setHidden:) withObject:hidden];
}
// MARK: Private
#pragma mark Private
- (void)_addRadio:(CPRadio)aRadio
{
+6 -11
View File
@@ -34,7 +34,7 @@
SEL _predicateAction @accessors(property=action);
}
// MARK: public methods
#pragma mark public methods
/*!
@ingroup appkit
@class CPPredicateEditor
@@ -251,7 +251,7 @@
return tree;
}
// MARK: Set the Predicate
#pragma mark Set the Predicate
- (void)setObjectValue:(id)objectValue
{
@@ -269,8 +269,6 @@
_currentAnimation = nil;
_sendAction = NO;
var rows = [];
if (predicate != nil)
{
if ((_nestingMode == CPRuleEditorNestingModeSimple || _nestingMode == CPRuleEditorNestingModeCompound)
@@ -278,13 +276,10 @@
predicate = [[CPCompoundPredicate alloc] initWithType:[self _compoundPredicateTypeForRootRows] subpredicates:[CPArray arrayWithObject:predicate]];
var row = [self _rowObjectFromPredicate:predicate];
if (row != nil)
[rows addObject:row];
[_boundArrayOwner setValue:[CPArray arrayWithObject:row] forKey:_boundArrayKeyPath];
}
[_boundArrayOwner setValue:rows forKey:_boundArrayKeyPath];
[self setAnimation:animation];
}
@@ -374,7 +369,7 @@
return row;
}
// MARK: Get the predicate
#pragma mark Get the predicate
- (void)_updatePredicate
{
@@ -452,7 +447,7 @@
return CPAndPredicateType;
}
// MARK: Control delegate
#pragma mark Control delegate
- (void)_sendRuleAction
{
@@ -487,7 +482,7 @@
}
*/
// MARK: RuleEditor delegate methods
#pragma mark RuleEditor delegate methods
- (int)_queryNumberOfChildrenOfItem:(id)rowItem withRowType:(CPRuleEditorRowType)type
{
+38 -145
View File
@@ -80,35 +80,6 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
@n An ordered to-many relation containing the display values for the row.
@n@n @c @@"criteria"
@n An ordered to-many relation containing the criteria for the row.
@n@n
Localization & Positional Reordering
@n
CPRuleEditor supports complete localization of menu items and grammatical positional reordering (sentence structure layout adjustment) via strings resource files (.strings) or custom programmatic CPDictionary tables.
@n@n
Since sentence structures vary significantly across languages, the editor can dynamically reposition views (such as popups, static labels, and text fields) from left to right to form grammatically correct sentences.
@n@n
Formatting Keys (English representation):
@n @c %[%]@@
@n Represents a popup button displaying its selected value (e.g. @c %[firstName]@@).
@n @c %@@
@n Represents an editable text input field.
@n Static text represents a literal label placed directly inside the formatting key.
@n@n
Example English format key:
@n @c "%[firstName]@ %[is equal to]@ %@"
@n@n
Translation Patterns (Target language):
@n Positional specifiers such as @c %1$@@, @c %2$@@, @c %3$@@ dictate the visual order of views from left to right.
@n Bracketed values inside positional specifiers (e.g. @c %1$[Nombre]@@) define the localized title for popup selection items.
@n Literal text outside the specifiers (such as @c "y" or @c "und") is automatically instantiated as static text labels positioned between controls.
@n@n
Example translations:
@n@n
Spanish (Reorders to: [1: Name] y [3: Value] [2: are equal]):
@n @c "%[firstName]@ %[is equal to]@ %@" = "%1$[Nombre]@ y %3$@ %2$[son iguales]@";
@n@n
German (Reorders to: [1: First Name] und [3: Value] [2: are equal]):
@n @c "%[firstName]@ %[is equal to]@ %@" = "%1$[Vorname]@ und %3$@ %2$[sind gleich]@";
*/
@implementation CPRuleEditor : CPControl
@@ -156,7 +127,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
BOOL _isKeyDown;
BOOL _nestingModeDidChange;
_CPRuleEditorLocalizer _standardLocalizer;
_CPRuleEditorLocalizer _standardLocalizer @accessors(property=standardLocalizer);
CPDictionary _itemsAndValuesToAddForRowType;
}
@@ -236,34 +207,8 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType,nil]];
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_ruleEditorLocalizerDidLoad:)
name:@"_CPRuleEditorLocalizerDidLoadNotification"
object:nil];
}
- (void)_ruleEditorLocalizerDidLoad:(CPNotification)aNotification
{
if ([aNotification object] === [self standardLocalizer])
{
// Defer execution to the next run loop cycle so that any active slice
// insertions have fully completed and are present in the `_slices` array.
[[CPRunLoop mainRunLoop] performBlock:function() {
var count = [_slices count];
for (var i = 0; i < count; i++)
{
var slice = [_slices objectAtIndex:i];
[slice _reconfigureSubviews];
[slice _updateButtonVisibilities]; // Force updates on row button tooltips
}
[self _updatePredicate];
[self _sendRuleAction];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
}
}
/*! @endcond */
/*!
@@ -439,7 +384,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/
- (CPDictionary)formattingDictionary
{
return [[self standardLocalizer] dictionary];
return [_standardLocalizer dictionary];
}
/*!
@@ -451,9 +396,6 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/
- (void)setFormattingDictionary:(CPDictionary)dictionary
{
if (_standardLocalizer == nil)
_standardLocalizer = [_CPRuleEditorLocalizer new];
[_standardLocalizer setDictionary:dictionary];
_stringsFilename = nil;
}
@@ -498,19 +440,6 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
}
}
- (_CPRuleEditorLocalizer)standardLocalizer
{
if (_standardLocalizer == nil)
_standardLocalizer = [_CPRuleEditorLocalizer new];
return _standardLocalizer;
}
- (void)setStandardLocalizer:(_CPRuleEditorLocalizer)aLocalizer
{
_standardLocalizer = aLocalizer;
}
/*!
@name Providing Data
*/
@@ -609,7 +538,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
if ([self rowTypeForRow:current_index] === CPRuleEditorRowTypeCompound)
{
var candidate = [[self _rowCacheForIndex:current_index] rowObject],
subObjects = [self _subrowObjectsOfObject:candidate]; // Standard direct array query
subObjects = [[self _subrowObjectsOfObject:candidate] _representedObject];
if ([subObjects indexOfObjectIdenticalTo:targetObject] !== CPNotFound)
return current_index;
@@ -676,7 +605,7 @@ TODO: implement
for (var i = rowIndex + 1; i < count; i++)
{
var candidate = [[self _rowCacheForIndex:i] rowObject],
indexInSubrows = [subobjects indexOfObjectIdenticalTo:candidate]; // Standard direct array query
indexInSubrows = [[subobjects _representedObject] indexOfObjectIdenticalTo:candidate];
if (indexInSubrows !== CPNotFound)
{
@@ -845,7 +774,7 @@ TODO: implement
while (current_index !== CPNotFound)
{
var rowObject = [[self _rowCacheForIndex:current_index] rowObject],
relativeChildIndex = [subrows indexOfObjectIdenticalTo:rowObject]; // Standard direct array query
relativeChildIndex = [[subrows _representedObject] indexOfObjectIdenticalTo:rowObject];
if (relativeChildIndex !== CPNotFound)
[childsIndexes addIndex:relativeChildIndex];
@@ -902,6 +831,7 @@ TODO: implement
for (i = 0; i < count; i++)
{
var item = [items objectAtIndex:i],
//var displayValue = [self _queryValueForItem:item inRow:aRow]; Ask the delegate or get cached value ?.
displayValue = [[self displayValuesForRow:aRow] objectAtIndex:i],
predpart = [self _sendDelegateRuleEditorPredicatePartsForCriterion:item withDisplayValue:displayValue inRow:aRow];
@@ -919,7 +849,6 @@ TODO: implement
return nil;
var current_index = [subrowsIndexes firstIndex];
while (current_index !== CPNotFound)
{
var subpredicate = [self predicateForRow:current_index];
@@ -1304,7 +1233,7 @@ TODO: implement
return shouldHide;
}
// MARK: Rows management
#pragma mark Rows management
- (id)_rowCacheForIndex:(int)index
{
@@ -1349,28 +1278,23 @@ TODO: implement
while (current_index !== CPNotFound)
{
var parentIndex = [self parentRowForRow:current_index];
// If the row has a valid parent in the editor (i.e. not a root row)
if (parentIndex !== -1)
var parentIndex = [self parentRowForRow:current_index],
subrowsIndexes = [self subrowIndexesForRow:parentIndex];
if ([subrowsIndexes count] === 1)
{
var subrowsIndexes = [self subrowIndexesForRow:parentIndex];
if (parentIndex !== -1)
return [CPIndexSet indexSetWithIndex:0];
// If deleting this row leaves the parent with no remaining child rows
if ([subrowsIndexes count] === 1)
{
[childlessParents addIndex:parentIndex];
// Recursively check if deleting this parent row leaves the grandparent childless
var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]];
[childlessParents addIndexes:childlessGranPa];
}
var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]];
[childlessParents addIndexes:childlessGranPa];
}
current_index = [indexes indexGreaterThanIndex:current_index];
}
return childlessParents;
// (id)-[RuleEditor _includeSubslicesForSlicesAtIndexes:]
}
- (CPIndexSet)_includeSubslicesForSlicesAtIndexes:(CPIndexSet)indexes
@@ -1432,25 +1356,8 @@ TODO: implement
if ([self rowTypeForRow:row] === type && itemIndex < [aCriteria count])
{
// Verify that this row's parent path matches the path currently being built
var pathMatches = true;
for (var p = 0; p < itemIndex; p++)
{
var criterionA = [aCriteria objectAtIndex:p],
criterionB = [items objectAtIndex:p];
if (criterionA !== criterionB && (typeof criterionA.isEqual !== "function" || ![criterionA isEqual:criterionB]))
{
pathMatches = false;
break;
}
}
if (pathMatches)
{
var crit = [aCriteria objectAtIndex:itemIndex];
[current_criterions addObject:crit];
}
var crit = [aCriteria objectAtIndex:itemIndex];
[current_criterions addObject:crit];
}
}
@@ -1566,7 +1473,7 @@ TODO: implement
return row;
}
// MARK: Key value observing
#pragma mark Key value observing
- (void)_startObservingRowObjectsRecursively:(CPArray)rowObjects
{
@@ -1867,12 +1774,7 @@ TODO: implement
- (_CPRuleEditorViewSliceRow)_createNewSliceWithFrame:(CGRect)frame ruleEditorView:(CPRuleEditor)editor
{
var slice = [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor];
// Ensure the slice resizes with the editor
[slice setAutoresizingMask:CPViewWidthSizable];
return slice;
return [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor];
}
- (void)_reconfigureSubviewsAnimate:(BOOL)animate
@@ -2017,17 +1919,17 @@ TODO: implement
- (CPString)_toolTipForAddCompoundRowButton
{
return [[self standardLocalizer] localizedStringForString:@"Add compound row"];
return [_standardLocalizer localizedStringForString:@"Add compound row"];
}
- (CPString)_toolTipForAddSimpleRowButton
{
return [[self standardLocalizer] localizedStringForString:@"Add row"];
return [_standardLocalizer localizedStringForString:@"Add row"];
}
- (CPString)_toolTipForDeleteRowButton
{
return [[self standardLocalizer] localizedStringForString:@"Delete row"];
return [_standardLocalizer localizedStringForString:@"Delete row"];
}
- (void)_updateSliceIndentations
@@ -2342,41 +2244,33 @@ TODO: implement
- (BOOL)performDragOperation:(id /*< CPDraggingInfo >*/)info
{
var object;
var aboveInsertIndexCount = 0,
object,
removeIndex;
var rowObjects = [_rowCache valueForKey:@"rowObject"],
index = [_draggingRows lastIndex];
var firstDraggingIndex = [_draggingRows firstIndex],
parentRowIndex = [self parentRowForRow:firstDraggingIndex],
var parentRowIndex = [self parentRowForRow:index], // first index of draggingrows
parentRowObject = (parentRowIndex === -1) ? _boundArrayOwner : [[self _rowCacheForIndex:parentRowIndex] rowObject],
insertIndex = _subviewIndexOfDropLine;
while (index !== CPNotFound)
{
// Identify if this row's parent is also inside the dragging set.
// If it is, we skip it because it will move automatically with its parent.
var parentOfCurrent = [self parentRowForRow:index];
if (parentOfCurrent !== -1 && [_draggingRows containsIndex:parentOfCurrent])
if (index >= insertIndex)
{
index = [_draggingRows indexLessThanIndex:index];
continue;
removeIndex = index + aboveInsertIndexCount;
aboveInsertIndexCount += 1;
}
else
{
removeIndex = index;
insertIndex -= 1;
}
object = [rowObjects objectAtIndex:index];
// Find the current live index of the object inside the editor
var cache = [self _searchCacheForRowObject:object];
var liveIndex = [_rowCache indexOfObjectIdenticalTo:cache];
if (liveIndex !== CPNotFound)
{
if (liveIndex < insertIndex)
insertIndex -= 1;
[self removeRowAtIndex:liveIndex];
[[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1];
}
object = [rowObjects objectAtIndex:removeIndex];
[self removeRowAtIndex:removeIndex];
[[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1];
index = [_draggingRows indexLessThanIndex:index];
}
@@ -2386,7 +2280,6 @@ TODO: implement
return YES;
}
- (CPIndexSet)_draggingTypes
{
return [CPIndexSet indexSetWithIndex:CPDragOperationMove];
@@ -77,9 +77,6 @@ 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
@@ -97,231 +94,4 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
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
+18 -51
View File
@@ -114,32 +114,8 @@
- (CPMenuItem)_createMenuItemWithTitle:(CPString )title
{
var originalTitle = title;
var localizedTitle = [[_ruleEditor standardLocalizer] localizedStringForString:title];
var item = [[CPMenuItem alloc] initWithTitle:localizedTitle action:nil keyEquivalent:@""];
// Cache the raw English title for pattern-matching
item._originalTitle = originalTitle;
return item;
}
- (CPTextField)_createStaticTextFieldWithStringValue:(CPString)text
{
var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()],
ruleEditorFont = [_ruleEditor font],
font = [CPFont fontWithName:[ruleEditorFont familyName] size:[ruleEditorFont size] + 2],
localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text],
size = [localizedText sizeWithFont:font];
[textField setFrameSize:CGSizeMake(size.width + 4, [_ruleEditor rowHeight])];
[textField setValue:font forThemeAttribute:@"font"];
[textField setValue:[_ruleEditor _verticalAlignment] forThemeAttribute:@"vertical-alignment"];
[textField setStringValue:localizedText];
// Cache the raw English text for pattern-matching
textField._originalText = text;
return textField;
title = [[_ruleEditor standardLocalizer] localizedStringForString:title];
return [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
}
- (CPPopUpButton)_createPopUpButtonWithItems:(CPArray)itemsArray selectedItemIndex:(int)index
@@ -166,6 +142,22 @@
return [CPMenuItem separatorItem];
}
- (CPTextField)_createStaticTextFieldWithStringValue:(CPString)text
{
var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()],
ruleEditorFont = [_ruleEditor font],
font = [CPFont fontWithName:[ruleEditorFont familyName] size:[ruleEditorFont size] + 2],
localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text],
size = [localizedText sizeWithFont:font];
[textField setFrameSize:CGSizeMake(size.width + 4, [_ruleEditor rowHeight])];
[textField setValue:font forThemeAttribute:@"font"];
[textField setValue:[_ruleEditor _verticalAlignment] forThemeAttribute:@"vertical-alignment"];
[textField setStringValue:localizedText];
return textField;
}
- (void)_addOption:(id)sender
{
if (_rowIndex == [_ruleEditor numberOfRows] - 1)
@@ -342,28 +334,6 @@
[_correspondingRuleItems setArray:ruleItems];
// Localize drop-down options in context and reorder/insert intermediate labels natively
var localizer = [_ruleEditor standardLocalizer];
if (localizer)
{
[localizer localizeMenuItemsForViews:_ruleOptionViews];
_ruleOptionViews = [localizer localizeAndReorderViews:_ruleOptionViews];
}
// Rebuild frame configurations to match the new localized layout order
[_ruleOptionFrames removeAllObjects];
[_ruleOptionInitialViewFrames removeAllObjects];
var newCount = [_ruleOptionViews count];
for (var i = 0; i < newCount; i++)
{
var view = [_ruleOptionViews objectAtIndex:i],
frame = [view frame];
[_ruleOptionFrames addObject:frame];
[_ruleOptionInitialViewFrames addObject:frame];
}
if (!_editable)
[self _updateEnabledStateForSubviews];
@@ -440,9 +410,6 @@
{
[_addButton setHidden:[_ruleEditor _shouldHideAddButtonForSlice:self]];
[_subtractButton setHidden:[_ruleEditor _shouldHideSubtractButtonForSlice:self]];
[_addButton setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
[_subtractButton setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
}
- (void)_configurePlusButtonByRowType:(CPRuleEditorRowType)type
+21 -232
View File
@@ -29,10 +29,8 @@
@import "CPClipView.j"
@import "CPScroller.j"
@import "CPView.j"
@import "CPRulerView.j"
@class CPTableView
@class CPRulerView
#define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden])
@@ -96,10 +94,6 @@ var TIMER_INTERVAL = 0.2,
CPScrollViewFadeOutTime = 1.3;
var CPScrollViewWillStartLiveScrollNotification = @"CPScrollViewWillStartLiveScrollNotification",
CPScrollViewDidLiveScrollNotification = @"CPScrollViewDidLiveScrollNotification",
CPScrollViewDidEndLiveScrollNotification = @"CPScrollViewDidEndLiveScrollNotification";
var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
CPScrollerStyleGlobalChangeNotification = @"CPScrollerStyleGlobalChangeNotification";
@@ -142,19 +136,11 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
int _scrollerStyle;
int _scrollerKnobStyle;
// Ruler Support
BOOL _hasVerticalRuler;
BOOL _hasHorizontalRuler;
BOOL _rulersVisible;
CPRulerView _verticalRuler;
CPRulerView _horizontalRuler;
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
+ (void)initialize
{
@@ -285,8 +271,8 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
- (id)initWithFrame:(CGRect)aFrame
{
@@ -316,10 +302,6 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
_scrollerKnobStyle = CPScrollerKnobStyleDefault;
[self setScrollerStyle:CPScrollerStyleGlobal];
_hasVerticalRuler = NO;
_hasHorizontalRuler = NO;
_rulersVisible = NO;
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
@@ -329,8 +311,8 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
}
// MARK: -
// MARK: Getters / Setters
#pragma mark -
#pragma mark Getters / Setters
/*!
The delegate of the scroll view
@@ -808,105 +790,8 @@ Notifies the delegate when the scroll view has finished scrolling.
}
// MARK: -
// MARK: Rulers
- (BOOL)hasHorizontalRuler
{
return _hasHorizontalRuler;
}
- (void)setHasHorizontalRuler:(BOOL)shouldHaveHorizontalRuler
{
if (_hasHorizontalRuler === shouldHaveHorizontalRuler)
return;
_hasHorizontalRuler = shouldHaveHorizontalRuler;
if (_hasHorizontalRuler && !_horizontalRuler)
{
_horizontalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationHorizontal];
}
[self tile];
}
- (BOOL)hasVerticalRuler
{
return _hasVerticalRuler;
}
- (void)setHasVerticalRuler:(BOOL)shouldHaveVerticalRuler
{
if (_hasVerticalRuler === shouldHaveVerticalRuler)
return;
_hasVerticalRuler = shouldHaveVerticalRuler;
if (_hasVerticalRuler && !_verticalRuler)
{
_verticalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationVertical];
}
[self tile];
}
- (BOOL)rulersVisible
{
return _rulersVisible;
}
- (void)setRulersVisible:(BOOL)areRulersVisible
{
if (_rulersVisible === areRulersVisible)
return;
_rulersVisible = areRulersVisible;
[self tile];
}
- (CPRulerView)horizontalRulerView
{
return _horizontalRuler;
}
- (void)setHorizontalRulerView:(CPRulerView)aRulerView
{
if (_horizontalRuler === aRulerView)
return;
[_horizontalRuler removeFromSuperview];
_horizontalRuler = aRulerView;
if (_horizontalRuler)
[self addSubview:_horizontalRuler];
[self tile];
}
- (CPRulerView)verticalRulerView
{
return _verticalRuler;
}
- (void)setVerticalRulerView:(CPRulerView)aRulerView
{
if (_verticalRuler === aRulerView)
return;
[_verticalRuler removeFromSuperview];
_verticalRuler = aRulerView;
if (_verticalRuler)
[self addSubview:_verticalRuler];
[self tile];
}
// MARK: -
// MARK: Privates
#pragma mark -
#pragma mark Privates
/* @ignore */
- (void)_updateScrollerStyle
@@ -1102,8 +987,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[self _sendDelegateMessages];
[_contentView scrollToPoint:contentBounds.origin];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
}
/* @ignore */
@@ -1142,8 +1025,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView scrollToPoint:contentBounds.origin];
[_headerClipView scrollToPoint:CGPointMake(contentBounds.origin.x, 0.0)];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
}
/* @ignore */
@@ -1154,7 +1035,6 @@ Notifies the delegate when the scroll view has finished scrolling.
if (!_scrollTimer)
{
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewWillStartLiveScrollNotification object:self];
[self _scrollViewWillScroll];
_scrollTimer = [CPTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(_scrollViewDidScroll) userInfo:nil repeats:YES];
}
@@ -1193,8 +1073,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView scrollToPoint:constrainedOrigin];
[_headerClipView scrollToPoint:CGPointMake(constrainedOrigin.x, 0.0)];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
if (extraX || extraY)
[enclosingScrollView _respondToScrollWheelEventWithDeltaX:extraX deltaY:extraY];
}
@@ -1214,8 +1092,6 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_implementedDelegateMethods & CPScrollViewDelegate_scrollViewDidScroll_)
[_delegate scrollViewDidScroll:self];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidEndLiveScrollNotification object:self];
}
/*! @ignore*/
@@ -1226,15 +1102,18 @@ Notifies the delegate when the scroll view has finished scrolling.
// MARK: -
// MARK: Utilities
#pragma mark -
#pragma mark Utilities
/*!
Lays out the scroll view's components.
*/
- (void)tile
{
[self reflectScrolledClipView:_contentView];
// yuck.
// RESIZE: tile->setHidden AND refl
// Outside Change: refl->tile->setHidden AND refl
// scroll: refl.
}
/*!
@@ -1278,41 +1157,6 @@ Notifies the delegate when the scroll view has finished scrolling.
contentFrame.origin.y += headerClipViewHeight;
contentFrame.size.height -= headerClipViewHeight;
// Adjust content view based on horizontal / vertical ruler presence
var showHorizontalRuler = _rulersVisible && _hasHorizontalRuler && _horizontalRuler,
showVerticalRuler = _rulersVisible && _hasVerticalRuler && _verticalRuler;
var horizRulerThickness = showHorizontalRuler ? ([_horizontalRuler respondsToSelector:@selector(ruleThickness)] ? [_horizontalRuler ruleThickness] : 16.0) : 0.0,
vertRulerThickness = showVerticalRuler ? ([_verticalRuler respondsToSelector:@selector(ruleThickness)] ? [_verticalRuler ruleThickness] : 24.0) : 0.0;
if (showHorizontalRuler)
{
if ([_horizontalRuler superview] !== self)
[self addSubview:_horizontalRuler];
[_horizontalRuler setHidden:NO];
}
else if (_horizontalRuler)
{
[_horizontalRuler setHidden:YES];
}
if (showVerticalRuler)
{
if ([_verticalRuler superview] !== self)
[self addSubview:_verticalRuler];
[_verticalRuler setHidden:NO];
}
else if (_verticalRuler)
{
[_verticalRuler setHidden:YES];
}
contentFrame.origin.y += horizRulerThickness;
contentFrame.size.height -= horizRulerThickness;
contentFrame.origin.x += vertRulerThickness;
contentFrame.size.width -= vertRulerThickness;
var difference = CGSizeMake(CGRectGetWidth(documentFrame) - CGRectGetWidth(contentFrame), CGRectGetHeight(documentFrame) - CGRectGetHeight(contentFrame)),
verticalScrollerWidth = [_verticalScroller scrollerWidth],
horizontalScrollerHeight = [_horizontalScroller scrollerWidth],
@@ -1405,7 +1249,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView setFrame:contentFrame];
[_headerClipView setFrame:[self _headerClipViewFrame]];
[[_headerClipView documentView] setNeedsDisplay:YES];
if (SHOULD_SHOW_CORNER_VIEW())
{
[_cornerView setFrame:[self _cornerViewFrame]];
@@ -1420,37 +1263,6 @@ Notifies the delegate when the scroll view has finished scrolling.
[[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]];
}
// Position and redraw rulers to track viewport updates
if (showHorizontalRuler)
{
[_horizontalRuler setFrame:CGRectMake(
CGRectGetMinX(contentFrame),
CGRectGetMinY(contentFrame) - horizRulerThickness,
CGRectGetWidth(contentFrame),
horizRulerThickness
)];
if ([_horizontalRuler respondsToSelector:@selector(updateRuler)])
[_horizontalRuler updateRuler];
else
[_horizontalRuler setNeedsDisplay:YES];
}
if (showVerticalRuler)
{
[_verticalRuler setFrame:CGRectMake(
CGRectGetMinX(contentFrame) - vertRulerThickness,
CGRectGetMinY(contentFrame),
vertRulerThickness,
CGRectGetHeight(contentFrame)
)];
if ([_verticalRuler respondsToSelector:@selector(updateRuler)])
[_verticalRuler updateRuler];
else
[_verticalRuler setNeedsDisplay:YES];
}
--_recursionCount;
}
@@ -1491,8 +1303,8 @@ Notifies the delegate when the scroll view has finished scrolling.
return [_contentView documentVisibleRect];
}
// MARK: -
// MARK: Overrides
#pragma mark -
#pragma mark Overrides
- (void)_removeObservers
@@ -1613,8 +1425,8 @@ Notifies the delegate when the scroll view has finished scrolling.
y = maxY - 1.5;
CGContextMoveToPoint(maxX - 1.0, y);
CGContextAddLineToPoint(minX + 2.0, y);
CGContextMoveToPoint(context, maxX - 1.0, y);
CGContextAddLineToPoint(context, minX + 2.0, y);
x = minX + 0.5;
@@ -1732,7 +1544,7 @@ Notifies the delegate when the scroll view has finished scrolling.
@end
// MARK: -
#pragma mark -
@implementation CPScrollView (FirstResponder)
@@ -1776,7 +1588,7 @@ Notifies the delegate when the scroll view has finished scrolling.
@end
// MARK: -
#pragma mark -
var CPScrollViewContentViewKey = @"CPScrollViewContentView",
CPScrollViewHeaderClipViewKey = @"CPScrollViewHeaderClipViewKey",
@@ -1793,14 +1605,7 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
CPScrollViewBottomCornerViewKey = @"CPScrollViewBottomCornerViewKey",
CPScrollViewBorderTypeKey = @"CPScrollViewBorderTypeKey",
CPScrollViewScrollerStyleKey = @"CPScrollViewScrollerStyleKey",
CPScrollViewScrollerKnobStyleKey = @"CPScrollViewScrollerKnobStyleKey",
// Ruler Coding Keys
CPScrollViewHasVRulerKey = @"CPScrollViewHasVRuler",
CPScrollViewHasHRulerKey = @"CPScrollViewHasHRuler",
CPScrollViewRulersVisibleKey = @"CPScrollViewRulersVisible",
CPScrollViewVRulerKey = @"CPScrollViewVRuler",
CPScrollViewHRulerKey = @"CPScrollViewHRuler";
CPScrollViewScrollerKnobStyleKey = @"CPScrollViewScrollerKnobStyleKey";
@implementation CPScrollView (CPCoding)
@@ -1835,14 +1640,6 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
_cornerView = [aCoder decodeObjectForKey:CPScrollViewCornerViewKey];
_bottomCornerView = [aCoder decodeObjectForKey:CPScrollViewBottomCornerViewKey];
// Ruler decoding
_hasVerticalRuler = [aCoder decodeBoolForKey:CPScrollViewHasVRulerKey];
_hasHorizontalRuler = [aCoder decodeBoolForKey:CPScrollViewHasHRulerKey];
_rulersVisible = [aCoder decodeBoolForKey:CPScrollViewRulersVisibleKey];
_verticalRuler = [aCoder decodeObjectForKey:CPScrollViewVRulerKey];
_horizontalRuler = [aCoder decodeObjectForKey:CPScrollViewHRulerKey];
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
@@ -1896,14 +1693,6 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
[aCoder encodeInt:_scrollerStyle forKey:CPScrollViewScrollerStyleKey];
[aCoder encodeInt:_scrollerKnobStyle forKey:CPScrollViewScrollerKnobStyleKey];
// Ruler encoding
[aCoder encodeBool:_hasVerticalRuler forKey:CPScrollViewHasVRulerKey];
[aCoder encodeBool:_hasHorizontalRuler forKey:CPScrollViewHasHRulerKey];
[aCoder encodeBool:_rulersVisible forKey:CPScrollViewRulersVisibleKey];
[aCoder encodeObject:_verticalRuler forKey:CPScrollViewVRulerKey];
[aCoder encodeObject:_horizontalRuler forKey:CPScrollViewHRulerKey];
}
@end
+19 -18
View File
@@ -23,11 +23,12 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPAnimation.j"
@import "CPControl.j"
@import "CPViewAnimation.j"
@import "CPWindow_Constants.j"
@import "CPViewAnimation.j"
@global CPApp
@@ -101,8 +102,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
+ (CPString)defaultThemeClass
{
@@ -166,8 +167,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
- (id)initWithFrame:(CGRect)aFrame
{
@@ -202,8 +203,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Getters / Setters
#pragma mark -
#pragma mark Getters / Setters
/*!
Returns the scroller's style
@@ -258,7 +259,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
*/
- (void)setKnobProportion:(float)aProportion
{
if (!CPIsNumeric(aProportion))
if (!_IS_NUMERIC(aProportion))
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric, was: "+aProportion];
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
@@ -268,8 +269,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Privates
#pragma mark -
#pragma mark Privates
/*! @ignore */
- (void)_adjustScrollerSize
@@ -294,8 +295,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Utilities
#pragma mark -
#pragma mark Utilities
- (CGRect)rectForPart:(CPScrollerPart)aPart
{
@@ -467,8 +468,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Drawing
#pragma mark -
#pragma mark Drawing
/*!
Draws the specified arrow and sets the highlight.
@@ -702,8 +703,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
// MARK: -
// MARK: Overrides
#pragma mark -
#pragma mark Overrides
- (id)currentValueForThemeAttribute:(CPString)anAttributeName
{
@@ -779,8 +780,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
return [self currentValueForThemeAttribute:@"scroller-width"];
}
// MARK: -
// MARK: Delegates
#pragma mark -
#pragma mark Delegates
- (void)animationDidEnd:(CPAnimation)animation
{
+5 -5
View File
@@ -136,8 +136,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
}
// MARK: -
// MARK: Override observers
#pragma mark -
#pragma mark Override observers
- (void)_removeObservers
{
@@ -788,7 +788,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@end
// MARK: -
#pragma mark -
@implementation CPSearchField (ThemingAdditions)
@@ -980,7 +980,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@end
// MARK: -
#pragma mark -
@implementation CPSearchField (CPTrackingArea)
{
@@ -1035,7 +1035,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@end
// MARK: -
#pragma mark -
var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
CPSendsWholeSearchStringKey = @"CPSendsWholeSearchStringKey",
+4 -5
View File
@@ -561,11 +561,10 @@ CPSegmentSwitchTrackingMomentary = 2;
}
else if (aName === "right-segment-bezel")
{
// we have to FLOOR the coordinates to prevent a 1px glitch in Safari
return CGRectMake(FLOOR(CGRectGetWidth([self bounds]) - contentInset.right),
FLOOR(bezelInset.top),
FLOOR(contentInset.right),
FLOOR(height));
return CGRectMake(CGRectGetWidth([self bounds]) - contentInset.right,
bezelInset.top,
contentInset.right,
height);
}
else if (aName.indexOf("segment-bezel") === 0)
{
+4 -10
View File
@@ -312,12 +312,6 @@ var AFFINITY = 5;
if (!trackRect || CGRectIsEmpty(trackRect))
trackRect = bounds;
if (_allowsTickMarkValuesOnly)
{
[self closestTickMarkValueToValue:[self doubleValue]]; // we only need the side effect …
_currentTickMarkSegment = _closestTickMarkIndex;
}
if (_isCircular)
{
var angle = 3 * PI_2 - (1.0 - [self doubleValue] - _minValue) / (_maxValue - _minValue) * PI2,
@@ -586,7 +580,7 @@ var AFFINITY = 5;
return [self setFloatValue:1.0];
}
// MARK: - New methods as in High Sierra (10.13)
#pragma mark - New methods as in High Sierra (10.13)
/*!
Creates and returns a continuous horizontal slider whose values range from 0.0 to 1.0.
@@ -812,7 +806,7 @@ var AFFINITY = 5;
return [self valueForThemeAttribute:@"left-track-color" inStates:normalState];
}
// MARK: - Private methods
#pragma mark - Private methods
- (void)_refreshCachesAndStates
{
@@ -1050,7 +1044,7 @@ var AFFINITY = 5;
return value;
}
// MARK: - Overrides
#pragma mark - Overrides
- (void)setFrameSize:(CGSize)aSize
{
@@ -1060,7 +1054,7 @@ var AFFINITY = 5;
[self setNeedsDisplay:YES];
}
// MARK: -
#pragma mark -
@end
+11 -12
View File
@@ -32,7 +32,6 @@
@end
@global document
var CPSoundDelegate_sound_didFinishPlaying_ = 1 << 1;
@@ -66,8 +65,8 @@ CPSoundPlayBackStatePause = 2;
unsigned _implementedDelegateMethods;
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
- (id)init
{
@@ -147,8 +146,8 @@ CPSoundPlayBackStatePause = 2;
}
// MARK: -
// MARK: Delegate methods
#pragma mark -
#pragma mark Delegate methods
/*!
Sets the sound's delegate.
@@ -166,8 +165,8 @@ CPSoundPlayBackStatePause = 2;
_implementedDelegateMethods |= CPSoundDelegate_sound_didFinishPlaying_;
}
// MARK: -
// MARK: Events listener
#pragma mark -
#pragma mark Events listener
/*! @ignore
*/
@@ -199,8 +198,8 @@ CPSoundPlayBackStatePause = 2;
}
// MARK: -
// MARK: Media controls
#pragma mark -
#pragma mark Media controls
/*!
Play the sound.
@@ -324,8 +323,8 @@ CPSoundPlayBackStatePause = 2;
_audioTag.volume = aVolume;
}
// MARK: -
// MARK: Accessors
#pragma mark -
#pragma mark Accessors
/*!
Returns the duration in seconds of the sound.
@@ -364,4 +363,4 @@ CPSoundPlayBackStatePause = 2;
[_delegate sound:self didFinishPlaying:finishedPlaying];
}
@end
@end
+16 -14
View File
@@ -23,11 +23,13 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPButtonBar.j"
@import "CPCursor.j"
@import "CPImage.j"
@import "CPTrackingArea.j"
@import "CPView.j"
@import "CPCursor.j"
@import "CPTrackingArea.j"
@class CPUserDefaults
@global CPApp
@@ -214,7 +216,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[self _setVertical:YES];
}
// MARK: - Properties
#pragma mark - Properties
- (CPSplitViewDividerStyle)dividerStyle
{
@@ -477,7 +479,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
return _delegate;
}
// MARK: - Subviews management
#pragma mark - Subviews management
// FIXME: il faut également tenir compte des button bars quand on ajouter / insert une vue.
// Par exemple, si une button bar est placée sur la dernière vue, pas de resize à droite mais
@@ -650,7 +652,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
_subviewsManagementDisabled = NO;
}
// MARK: - Layout subviews
#pragma mark - Layout subviews
- (CGRect)rectOfDividerAtIndex:(int)aDivider
{
@@ -843,7 +845,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[self updateTrackingAreas];
}
// MARK: - Private layout utilities
#pragma mark - Private layout utilities
- (void)_distribute:(CPInteger)remainingSpace amoung:(CPInteger)count onFlexible:(BOOL)onFlexible fromIndex:(CPInteger)fromIndex toIndex:(CPInteger)toIndex
{
@@ -898,7 +900,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[_ratios addObject:(_initialSizes[i] / fixedSpace)];
}
// MARK: -
#pragma mark -
/*!
Returns YES if the supplied subview is collapsed, otherwise NO.
@@ -1169,7 +1171,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
// Silently ignore bad positions which could result from odd delegate responses. We don't want these
// bad results to go into the system and cause havoc with frame sizes as the split view tries to resize
// its subviews.
if (CPIsNumeric(proposedPosition))
if (_IS_NUMERIC(proposedPosition))
position = proposedPosition;
var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex],
@@ -1179,10 +1181,10 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
proposedActualMin = [self _sendDelegateSplitViewConstrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex],
proposedActualMax = [self _sendDelegateSplitViewConstrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex];
if (CPIsNumeric(proposedActualMin))
if (_IS_NUMERIC(proposedActualMin))
actualMin = proposedActualMin;
if (CPIsNumeric(proposedActualMax))
if (_IS_NUMERIC(proposedActualMax))
actualMax = proposedActualMax;
var viewA = _arrangedSubviews[dividerIndex],
@@ -1485,7 +1487,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
@end
// MARK: -
#pragma mark -
@implementation CPSplitView (CPTrackingArea)
{
@@ -1565,7 +1567,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
@end
// MARK: -
#pragma mark -
@implementation CPSplitView (CPSplitViewDelegate)
@@ -1743,7 +1745,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
@end
// MARK: -
#pragma mark -
var CPSplitViewDelegateKey = @"CPSplitViewDelegateKey",
CPSplitViewIsVerticalKey = @"CPSplitViewIsVerticalKey",
@@ -1868,7 +1870,7 @@ var CPSplitViewDelegateKey = @"CPSplitViewDelegateKey",
@end
// MARK: -
#pragma mark -
@implementation CPSplitView (Deprecated)
-388
View File
@@ -1,388 +0,0 @@
/*
* CPSplitViewController.j
*
* Created by Daniel Boehringer on September 2, 2025.
* Copyright (c) 2025 Daniel Boehringer. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPViewController.j"
@import "CPSplitView.j"
var CPSplitViewControllerAutomaticDimension = -1.0;
/**
* A container view controller that manages two or more child view
* controllers in a split view interface.
*
* This class provides a controller-level abstraction for CPSplitView,
* managing the addition, removal, and arrangement of view controllers
* through CPSplitViewItem instances.
*
* This implementation was synthesized with the assistance of an LLM,
* directed by the author.
*/
@implementation CPSplitViewController : CPViewController <CPSplitViewDelegate>
{
/** @private The underlying split view that arranges the child views. */
CPSplitView _splitView;
/** @private An array of CPSplitViewItem objects managed by this controller. */
CPMutableArray _splitViewItems;
/** @private The minimum thickness for sidebars to be displayed inline. */
CPNumber _minimumThicknessForInlineSidebars;
}
// MARK: - Initialization
- (id)init
{
if (self = [super init])
{
_splitViewItems = [CPMutableArray array];
_minimumThicknessForInlineSidebars = 20.0;
}
return self;
}
// MARK: - View Lifecycle
- (void)loadView
{
if (!_splitView)
{
_splitView = [[CPSplitView alloc] initWithFrame:CGRectMake(0,0,400,400)];
[_splitView setDelegate:self];
}
[self setView:_splitView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
for (var i = 0; i < [_splitViewItems count]; i++)
{
var viewController = [[_splitViewItems objectAtIndex:i] viewController];
[[self splitView] addArrangedSubview:[viewController view]];
}
}
// MARK: - Accessors
/**
* Returns the CPSplitView instance managed by the controller.
*
* @returns {CPSplitView} The split view.
*/
- (CPSplitView)splitView
{
return _splitView;
}
/**
* Sets a custom split view for the controller.
*
* @param {CPSplitView} splitView The custom split view to use.
*/
- (void)setSplitView:(CPSplitView)splitView
{
if (_splitView !== splitView)
{
_splitView = splitView;
[_splitView setDelegate:self];
if ([self isViewLoaded])
[self setView:_splitView];
}
}
/**
* Returns the array of split view items.
*
* @returns {CPArray} The array of CPSplitViewItem objects.
*/
- (CPArray)splitViewItems
{
return _splitViewItems;
}
/**
* Sets the array of split view items, replacing any existing items.
*
* @param {CPArray} splitViewItems An array of CPSplitViewItem objects.
*/
- (void)setSplitViewItems:(CPArray)splitViewItems
{
// Remove all existing items
while ([_splitViewItems count] > 0)
{
[self removeSplitViewItem:[_splitViewItems lastObject]];
}
// Add new items
for (var i = 0; i < [splitViewItems count]; i++)
{
[self addSplitViewItem:[splitViewItems objectAtIndex:i]];
}
}
// MARK: - Managing Split View Items
/**
* Adds a split view item to the end of the split view.
*
* @param {CPSplitViewItem} splitViewItem The split view item to add.
*/
- (void)addSplitViewItem:(CPSplitViewItem)splitViewItem
{
[self insertSplitViewItem:splitViewItem atIndex:[_splitViewItems count]];
}
/**
* Inserts a split view item at a specific index.
*
* @param {CPSplitViewItem} splitViewItem The split view item to insert.
* @param {CPInteger} index The zero-based index at which to insert the item.
*/
- (void)insertSplitViewItem:(CPSplitViewItem)splitViewItem atIndex:(CPInteger)index
{
[splitViewItem _setSplitViewController:self];
[_splitViewItems insertObject:splitViewItem atIndex:index];
[self addChildViewController:[splitViewItem viewController]];
if ([self isViewLoaded])
[[self splitView] insertArrangedSubview:[[splitViewItem viewController] view] atIndex:index];
}
/**
* Removes the specified split view item.
*
* @param {CPSplitViewItem} splitViewItem The split view item to remove.
*/
- (void)removeSplitViewItem:(CPSplitViewItem)splitViewItem
{
var viewController = [splitViewItem viewController];
if ([self isViewLoaded])
[[viewController view] removeFromSuperview];
[viewController removeFromParentViewController];
[splitViewItem _setSplitViewController:nil];
[_splitViewItems removeObject:splitViewItem];
}
/**
* Retrieves the split view item associated with a given view controller.
*
* @param {CPViewController} viewController The view controller to find.
* @returns {CPSplitViewItem | null} The corresponding split view item, or nil if not found.
*/
- (CPSplitViewItem)splitViewItemForViewController:(CPViewController)viewController
{
for (var i = 0; i < [_splitViewItems count]; i++)
{
var item = [_splitViewItems objectAtIndex:i];
if ([item viewController] === viewController)
return item;
}
return nil;
}
// MARK: - Managing Sidebars and Inspectors
/**
* Toggles the collapsed state of the first split view item, typically a sidebar.
*
* @param {id} sender The object that initiated the action.
*/
- (void)toggleSidebar:(id)sender
{
if ([_splitViewItems count] > 0)
{
var sidebarItem = [_splitViewItems objectAtIndex:0];
[sidebarItem setCollapsed:![sidebarItem isCollapsed]];
}
}
/**
* Toggles the collapsed state of the last split view item, typically an inspector.
*
* @param {id} sender The object that initiated the action.
*/
- (void)toggleInspector:(id)sender
{
if ([_splitViewItems count] > 1)
{
var inspectorItem = [_splitViewItems lastObject];
[inspectorItem setCollapsed:![inspectorItem isCollapsed]];
}
}
/**
* Returns the minimum thickness for sidebars to be displayed inline.
*
* @returns {CPNumber} The minimum thickness.
*/
- (CPNumber)minimumThicknessForInlineSidebars
{
return _minimumThicknessForInlineSidebars;
}
// MARK: - CPSplitViewDelegate Methods
// Note: A more complete implementation would forward these delegate methods
// to a separate delegate property on the CPSplitViewController itself.
// For now, these are stubbed to demonstrate where they would be handled.
- (BOOL)splitView:(CPSplitView)splitView canCollapseSubview:(CPView)subview
{
// Default behavior: allow all subviews to be collapsed.
return YES;
}
- (CGFloat)splitView:(CPSplitView)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(CPInteger)dividerIndex
{
return proposedMinimumPosition;
}
- (CGFloat)splitView:(CPSplitView)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(CPInteger)dividerIndex
{
return proposedMaximumPosition;
}
- (CGRect)splitView:(CPSplitView)splitView effectiveRect:(CGRect)proposedEffectiveRect forDrawnRect:(CGRect)drawnRect ofDividerAtIndex:(CPInteger)dividerIndex
{
// Default behavior: return the proposed rectangle.
// This can be overridden to provide a larger or custom hit area for the divider.
return proposedEffectiveRect;
}
- (void)splitViewDidResizeSubviews:(CPNotification)notification
{
// Can be used to respond to user-initiated resizing.
}
@end
/**
* An object that manages a view controller within a CPSplitViewController.
*
* A CPSplitViewItem acts as a wrapper around a CPViewController,
* maintaining properties like its collapsed state within the parent
* split view controller.
*/
@implementation CPSplitViewItem : CPObject
{
/** @private The view controller managed by this item. */
CPViewController _viewController;
/** @private A boolean indicating whether the item is collapsed. */
BOOL _isCollapsed;
/** @private A weak reference to the owning split view controller. */
CPSplitViewController _splitViewController;
}
// MARK: - Class Methods
/**
* Creates and returns a new split view item with the specified view controller.
*
* @param {CPViewController} viewController The view controller for the new item.
* @returns {instancetype} A new CPSplitViewItem instance.
*/
+ (instancetype)splitViewItemWithViewController:(CPViewController)viewController
{
return [[self alloc] initWithViewController:viewController];
}
// MARK: - Initialization
/**
* Initializes a new split view item with the specified view controller.
*
* @param {CPViewController} viewController The view controller for the new item.
* @returns {id} The initialized CPSplitViewItem instance.
*/
- (id)initWithViewController:(CPViewController)viewController
{
if (self = [super init])
{
_viewController = viewController;
_isCollapsed = NO;
}
return self;
}
// MARK: - Accessors
/**
* Returns the view controller associated with the item.
*
* @returns {CPViewController} The associated view controller.
*/
- (CPViewController)viewController
{
return _viewController;
}
/**
* Returns a boolean value indicating whether the item is collapsed.
*
* @returns {BOOL} YES if the item is collapsed, otherwise NO.
*/
- (BOOL)isCollapsed
{
return _isCollapsed;
}
/**
* Sets the collapsed state of the item.
* When collapsed, the view controller's view is hidden.
*
* @param {BOOL} shouldCollapse YES to collapse the item, NO to expand it.
*/
- (void)setCollapsed:(BOOL)shouldCollapse
{
if (_isCollapsed === shouldCollapse)
return;
_isCollapsed = shouldCollapse;
[[_viewController view] setHidden:shouldCollapse];
}
/**
* Returns the split view controller that owns this item.
*
* @returns {CPSplitViewController | null} The parent split view controller.
*/
- (CPSplitViewController)splitViewController
{
return _splitViewController;
}
/**
* @private
* Sets the owning split view controller. This method is for internal use by
* CPSplitViewController.
*
* @param {CPSplitViewController} splitViewController The parent controller.
*/
- (void)_setSplitViewController:(CPSplitViewController)splitViewController
{
_splitViewController = splitViewController;
}
@end
-815
View File
@@ -1,815 +0,0 @@
/*
* CPStackView.j
* AppKit
*
* Created by Daniel Boehringer.
* Copyright 2025, Cappuccino Project.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* PLACEHOLDER IMPLEMENTATION — READ BEFORE USE OR MODIFICATION.
*
* This class lays out views by direct, procedural arithmetic. It has no
* constraint solver. It cannot compress, expand, or negotiate space among
* views the way NSStackView does; it only places views at their existing
* frame size, in order, separated by fixed spacing.
*
* Known, accepted limitations:
* - `distribution` is stored but has no effect on layout. Fill,
* FillEqually, FillProportionally, and EqualSpacing are unimplemented.
* - Center-gravity views are clamped against the Leading edge only; if
* Leading + Center + Trailing content overflows the container, Center
* views can overlap Trailing views instead of compressing.
* - `visibilityPriority:forView:` only supports the two extreme values
* (MustHold / NotVisible). Intermediate priorities are accepted but
* have no defined effect.
* - No guarantee is made of correctness beyond what a single manual
* test (Tests/Manual/CPStackViewTest) exercises: orientation, the
* three gravity areas, alignment switching, spacing, and hidden-view
* detachment. Insertion, removal, custom spacing, and the CPCoding
* archive path are implemented but not verified by that test.
*
* This exists to give AppKit a working CPStackView symbol now, not to be
* a durable design. It is expected to be replaced by a constraint-solver
* based implementation (Kiwi.js) when time permits. Do not build on its
* internal layout algorithm as if it were a stable foundation.
*/
@import "CPView.j"
@import <Foundation/CPMapTable.j>
// MARK: -
// MARK: Minimal local type definitions
//
// These types support this file only. They are not shared with the rest
// of AppKit. A future constraint-solver based Auto Layout engine will
// replace them. Numeric values match the equivalent Cocoa constants
// (NSUserInterfaceLayoutOrientation, NSLayoutAttribute) so that a later,
// solver-based CPLayoutAttribute can reuse these numbers without a
// renumbering pass.
@typedef CPUserInterfaceLayoutOrientation
CPUserInterfaceLayoutOrientationHorizontal = 0;
CPUserInterfaceLayoutOrientationVertical = 1;
@typedef CPLayoutAttribute
CPLayoutAttributeLeft = 1;
CPLayoutAttributeRight = 2;
CPLayoutAttributeTop = 3;
CPLayoutAttributeBottom = 4;
CPLayoutAttributeLeading = 5;
CPLayoutAttributeTrailing = 6;
CPLayoutAttributeWidth = 7;
CPLayoutAttributeHeight = 8;
CPLayoutAttributeCenterX = 9;
CPLayoutAttributeCenterY = 10;
@typedef CPEdgeInsets
/*!
Creates a CPEdgeInsets. Argument order matches Cocoa's NSEdgeInsetsMake
(top, left, bottom, right). Storage reuses the existing CGInset struct,
whose field order is (top, right, bottom, left).
*/
function CPEdgeInsetsMake(top, left, bottom, right)
{
return CGInsetMake(top, right, bottom, left);
}
function CPEdgeInsetsEqualToEdgeInsets(lhsInsets, rhsInsets)
{
return CGInsetEqualToInset(lhsInsets, rhsInsets);
}
// Gravity Areas
@typedef CPStackViewGravity
CPStackViewGravityTop = 1;
CPStackViewGravityLeading = 1;
CPStackViewGravityCenter = 2;
CPStackViewGravityBottom = 3;
CPStackViewGravityTrailing = 3;
// Distribution (Deprecated in modern macOS, but kept for compatibility/logic)
@typedef CPStackViewDistribution
CPStackViewDistributionGravityAreas = 0;
CPStackViewDistributionFill = 1;
CPStackViewDistributionFillEqually = 2;
CPStackViewDistributionFillProportionally = 3;
CPStackViewDistributionEqualSpacing = 4;
CPStackViewDistributionEqualCentering = 5;
// Visibility Priority
@typedef CPStackViewVisibilityPriority
CPStackViewVisibilityPriorityMustHold = 1000.0;
CPStackViewVisibilityPriorityNotVisible = 0.0;
var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
/*!
@ingroup appkit
@class CPStackView
CPStackView arranges an array of views horizontally or vertically and updates
their placement and sizing when the window size changes.
Unlike a simple list, CPStackView supports "Gravity Areas" (Leading, Center, Trailing),
allowing you to pin groups of views to specific sections of the layout.
*/
@implementation CPStackView : CPView
{
CPUserInterfaceLayoutOrientation _orientation;
CPLayoutAttribute _alignment;
CPStackViewDistribution _distribution;
float _spacing;
CPEdgeInsets _edgeInsets;
BOOL _detachesHiddenViews;
// View Storage by Gravity
CPMutableArray _viewsLeading;
CPMutableArray _viewsCenter;
CPMutableArray _viewsTrailing;
// Internal cache of all arranged subviews to maintain order for hittesting/iterating
CPMutableArray _arrangedSubviews;
// Custom Spacing storage
CPMapTable _customSpacings;
// Visibility Priorities
CPMapTable _visibilityPriorities;
}
// MARK: -
// MARK: Initialization
+ (CPStackView)stackViewWithViews:(CPArray)views
{
var stackView = [[CPStackView alloc] initWithFrame:CGRectMakeZero()];
for (var i = 0, count = [views count]; i < count; i++)
[stackView addView:views[i] inGravity:CPStackViewGravityLeading];
return stackView;
}
- (id)initWithFrame:(CGRect)aFrame
{
if (self = [super initWithFrame:aFrame])
{
_orientation = CPUserInterfaceLayoutOrientationHorizontal;
_alignment = CPLayoutAttributeCenterY; // Default alignment
_distribution = CPStackViewDistributionGravityAreas;
_spacing = 8.0; // Default Cocoa spacing
_edgeInsets = CPEdgeInsetsMake(0, 0, 0, 0);
_detachesHiddenViews = YES;
_viewsLeading = [[CPMutableArray alloc] init];
_viewsCenter = [[CPMutableArray alloc] init];
_viewsTrailing = [[CPMutableArray alloc] init];
_arrangedSubviews = [[CPMutableArray alloc] init];
_customSpacings = [[CPMapTable alloc] init];
_visibilityPriorities = [[CPMapTable alloc] init];
}
return self;
}
// MARK: -
// MARK: Configuration
/*!
The horizontal or vertical layout direction of the stack view.
*/
- (CPUserInterfaceLayoutOrientation)orientation
{
return _orientation;
}
- (void)setOrientation:(CPUserInterfaceLayoutOrientation)anOrientation
{
if (_orientation === anOrientation)
return;
_orientation = anOrientation;
// Reset default alignment based on new orientation if needed,
// though usually developer sets alignment explicitly.
// If switching to Vertical, CenterY makes less sense, usually CenterX.
if (_orientation === CPUserInterfaceLayoutOrientationVertical)
{
if (_alignment === CPLayoutAttributeCenterY)
_alignment = CPLayoutAttributeCenterX;
}
else
{
if (_alignment === CPLayoutAttributeCenterX)
_alignment = CPLayoutAttributeCenterY;
}
[self setNeedsLayout:YES];
}
/*!
The view alignment within the stack view.
Common values:
Horizontal: CPLayoutAttributeTop, CPLayoutAttributeBottom, CPLayoutAttributeCenterY, CPLayoutAttributeHeight (fill)
Vertical: CPLayoutAttributeLeading, CPLayoutAttributeTrailing, CPLayoutAttributeCenterX, CPLayoutAttributeWidth (fill)
*/
- (CPLayoutAttribute)alignment
{
return _alignment;
}
- (void)setAlignment:(CPLayoutAttribute)anAlignment
{
if (_alignment === anAlignment)
return;
_alignment = anAlignment;
[self setNeedsLayout:YES];
}
/*!
The distribution mode for the stack view.
@note Not yet applied to layout. All views are laid out at their
existing frame size regardless of this value, pending the
constraint-solver based layout engine. The value is stored and
returned so client code can read back what was set.
*/
- (CPStackViewDistribution)distribution
{
return _distribution;
}
- (void)setDistribution:(CPStackViewDistribution)aDistribution
{
if (_distribution === aDistribution)
return;
_distribution = aDistribution;
[self setNeedsLayout:YES];
}
/*!
The minimum spacing, in points, between adjacent views in the stack view.
*/
- (float)spacing
{
return _spacing;
}
- (void)setSpacing:(float)aSpacing
{
if (_spacing === aSpacing)
return;
_spacing = aSpacing;
[self setNeedsLayout:YES];
}
/*!
The geometric padding, in points, inside the stack view, surrounding its views.
*/
- (CPEdgeInsets)edgeInsets
{
return _edgeInsets;
}
- (void)setEdgeInsets:(CPEdgeInsets)insets
{
if (CPEdgeInsetsEqualToEdgeInsets(_edgeInsets, insets))
return;
_edgeInsets = insets;
[self setNeedsLayout:YES];
}
/*!
A Boolean value that indicates whether the stack view removes hidden views from its view hierarchy.
*/
- (BOOL)detachesHiddenViews
{
return _detachesHiddenViews;
}
- (void)setDetachesHiddenViews:(BOOL)shouldDetach
{
if (_detachesHiddenViews === shouldDetach)
return;
_detachesHiddenViews = shouldDetach;
[self setNeedsLayout:YES];
}
// MARK: -
// MARK: Managing Views in Gravity Areas
- (CPArray)_containerForGravity:(CPStackViewGravity)gravity
{
if (gravity === CPStackViewGravityCenter)
return _viewsCenter;
else if (gravity === CPStackViewGravityTrailing) // or Bottom
return _viewsTrailing;
return _viewsLeading; // Leading or Top
}
/*!
Rebuilds _arrangedSubviews from the three gravity containers, in
Leading, Center, Trailing order. Call after any change to a gravity
container so _arrangedSubviews stays a correct, single source of truth
for ordering, rather than an incrementally and separately maintained
(and error-prone) copy.
*/
- (void)_rebuildArrangedSubviews
{
_arrangedSubviews = [[CPMutableArray alloc] init];
[_arrangedSubviews addObjectsFromArray:_viewsLeading];
[_arrangedSubviews addObjectsFromArray:_viewsCenter];
[_arrangedSubviews addObjectsFromArray:_viewsTrailing];
}
/*!
Adds a view to the end of the stack view gravity area.
*/
- (void)addView:(CPView)aView inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
// Check if view is already in a container
if ([_arrangedSubviews containsObject:aView])
[self removeView:aView];
[container addObject:aView];
[self _rebuildArrangedSubviews];
// Add as actual subview
if ([aView superview] !== self)
[self addSubview:aView];
[self setNeedsLayout:YES];
}
/*!
Adds a view to a stack view gravity area at a specified index position.
*/
- (void)insertView:(CPView)aView atIndex:(CPInteger)index inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
if ([_arrangedSubviews containsObject:aView])
[self removeView:aView];
if (index >= [container count])
[container addObject:aView];
else
[container insertObject:aView atIndex:index];
[self _rebuildArrangedSubviews];
if ([aView superview] !== self)
[self addSubview:aView];
[self setNeedsLayout:YES];
}
/*!
Specifies an array of views for a specified gravity area in the stack view, replacing any previous views in that area.
*/
- (void)setViews:(CPArray)views inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
// Remove old views from superview
for (var i = 0; i < [container count]; i++)
[container[i] removeFromSuperview];
[container removeAllObjects];
for (var i = 0; i < [views count]; i++)
{
var newView = views[i];
[container addObject:newView];
[self addSubview:newView];
}
[self _rebuildArrangedSubviews];
[self setNeedsLayout:YES];
}
/*!
Removes a specified view from the stack view.
*/
- (void)removeView:(CPView)aView
{
if (![_arrangedSubviews containsObject:aView])
return;
[_viewsLeading removeObject:aView];
[_viewsCenter removeObject:aView];
[_viewsTrailing removeObject:aView];
[self _rebuildArrangedSubviews];
[aView removeFromSuperview];
[self setNeedsLayout:YES];
}
/*!
Returns the array of views in the specified gravity area in the stack view.
*/
- (CPArray)viewsInGravity:(CPStackViewGravity)gravity
{
return [[self _containerForGravity:gravity] copy];
}
/*!
The array of views arranged by the stack view.
*/
- (CPArray)arrangedSubviews
{
return [_arrangedSubviews copy];
}
/*!
Adds the specified view to the end of the arranged subviews list.
(Defaults to Leading gravity if not specified).
*/
- (void)addArrangedSubview:(CPView)view
{
[self addView:view inGravity:CPStackViewGravityLeading];
}
/*!
Removes the provided view from the stacks array of arranged subviews.
*/
- (void)removeArrangedSubview:(CPView)view
{
[self removeView:view];
}
// MARK: -
// MARK: Custom Spacing
- (float)customSpacingAfterView:(CPView)aView
{
var val = [_customSpacings objectForKey:aView];
if (val)
return [val floatValue];
return CPStackViewSpacingUseDefault;
}
- (void)setCustomSpacing:(float)spacing afterView:(CPView)aView
{
if (spacing === CPStackViewSpacingUseDefault)
[_customSpacings removeObjectForKey:aView];
else
[_customSpacings setObject:spacing forKey:aView];
[self setNeedsLayout:YES];
}
- (float)_spacingAfterView:(CPView)aView
{
var custom = [self customSpacingAfterView:aView];
if (custom !== CPStackViewSpacingUseDefault)
return custom;
return _spacing;
}
// MARK: -
// MARK: Visibility Priority
- (void)setVisibilityPriority:(float)priority forView:(CPView)aView
{
[_visibilityPriorities setObject:priority forKey:aView];
if (priority === CPStackViewVisibilityPriorityNotVisible)
{
[aView setHidden:YES];
}
else if (priority === CPStackViewVisibilityPriorityMustHold)
{
[aView setHidden:NO];
}
// Note: Intermediate priorities require complex constraint logic
// or a multi-pass layout system to determine fitting, which is
// simplified here to basic Hidden/Visible states.
[self setNeedsLayout:YES];
}
- (float)visibilityPriorityForView:(CPView)aView
{
var val = [_visibilityPriorities objectForKey:aView];
if (val)
return [val floatValue];
return CPStackViewVisibilityPriorityMustHold;
}
// MARK: -
// MARK: Layout
- (void)resizeSubviewsWithOldSize:(CGSize)oldSize
{
[self layoutSubviews];
}
- (void)layoutSubviews
{
if (_orientation === CPUserInterfaceLayoutOrientationVertical)
[self _layoutVertical];
else
[self _layoutHorizontal];
}
- (void)_layoutHorizontal
{
var bounds = [self bounds],
availWidth = CGRectGetWidth(bounds) - _edgeInsets.left - _edgeInsets.right,
availHeight = CGRectGetHeight(bounds) - _edgeInsets.top - _edgeInsets.bottom,
currentX = _edgeInsets.left;
// 1. Layout Leading Views
currentX = [self _layoutViews:_viewsLeading startOffset:currentX availableOrthogonalSize:availHeight direction:1];
// 2. Layout Trailing Views
// We layout backwards from the right
var startRight = CGRectGetWidth(bounds) - _edgeInsets.right;
[self _layoutViews:_viewsTrailing startOffset:startRight availableOrthogonalSize:availHeight direction:-1];
// 3. Layout Center Views
if ([_viewsCenter count] > 0)
{
// Calculate total width of center stack
var centerStackWidth = 0.0;
for (var i = 0; i < [_viewsCenter count]; i++)
{
var view = _viewsCenter[i];
if (_detachesHiddenViews && [view isHidden]) continue;
centerStackWidth += CGRectGetWidth([view frame]);
if (i < [_viewsCenter count] - 1)
centerStackWidth += [self _spacingAfterView:view];
}
var centerStart = (CGRectGetWidth(bounds) / 2.0) - (centerStackWidth / 2.0);
// Clamp to prevent overlap with Leading (simplified collision logic)
// ideally stack view compresses views, but here we just shift/clip
if (centerStart < currentX)
centerStart = currentX;
[self _layoutViews:_viewsCenter startOffset:centerStart availableOrthogonalSize:availHeight direction:1];
}
}
- (void)_layoutVertical
{
var bounds = [self bounds],
availWidth = CGRectGetWidth(bounds) - _edgeInsets.left - _edgeInsets.right,
availHeight = CGRectGetHeight(bounds) - _edgeInsets.top - _edgeInsets.bottom,
currentY = _edgeInsets.top;
// 1. Layout Top (Leading) Views
currentY = [self _layoutViews:_viewsLeading startOffset:currentY availableOrthogonalSize:availWidth direction:1];
// 2. Layout Bottom (Trailing) Views
var startBottom = CGRectGetHeight(bounds) - _edgeInsets.bottom;
[self _layoutViews:_viewsTrailing startOffset:startBottom availableOrthogonalSize:availWidth direction:-1];
// 3. Layout Center Views
if ([_viewsCenter count] > 0)
{
var centerStackHeight = 0.0;
for (var i = 0; i < [_viewsCenter count]; i++)
{
var view = _viewsCenter[i];
if (_detachesHiddenViews && [view isHidden]) continue;
centerStackHeight += CGRectGetHeight([view frame]);
if (i < [_viewsCenter count] - 1)
centerStackHeight += [self _spacingAfterView:view];
}
var centerStart = (CGRectGetHeight(bounds) / 2.0) - (centerStackHeight / 2.0);
if (centerStart < currentY)
centerStart = currentY;
[self _layoutViews:_viewsCenter startOffset:centerStart availableOrthogonalSize:availWidth direction:1];
}
}
// Helper to layout a specific array of views in one direction
// Returns the ending offset
- (float)_layoutViews:(CPArray)views startOffset:(float)offset availableOrthogonalSize:(float)orthoSize direction:(int)dir
{
var cursor = offset;
var isVert = (_orientation === CPUserInterfaceLayoutOrientationVertical);
// If direction is -1 (Trailing/Bottom), we iterate backwards
// However, the standard behavior for trailing gravity is that the *last* view added is at the *end*.
// Leading: [A] [B] ->
// Trailing: -> [C] [D] (where D is rightmost)
// To support Trailing logic: We start at Right Edge, move left by Width(D), place D, move left by Spacing...
var count = [views count];
if (count === 0) return cursor;
// If direction is negative (Trailing), we process list in reverse order to stack them from edge inwards
var i = (dir === 1) ? 0 : count - 1;
var limit = (dir === 1) ? count : -1;
var step = (dir === 1) ? 1 : -1;
// Spacing is applied as a gap *before* placing an element (except the
// first placed one), rather than trailing off the end after the last
// element. This keeps the returned cursor at the true content edge,
// with no phantom spacing past the final view.
var hasPlacedAny = false;
var pendingSpacing = 0;
for (; i !== limit; i += step)
{
var view = views[i];
if (_detachesHiddenViews && [view isHidden])
continue;
if (hasPlacedAny)
cursor += (dir === 1) ? pendingSpacing : -pendingSpacing;
var viewFrame = [view frame];
var viewSizePrimary = isVert ? CGRectGetHeight(viewFrame) : CGRectGetWidth(viewFrame);
// Handle Alignment (Orthogonal Axis)
var orthoPos = 0.0;
var viewOrthoSize = isVert ? CGRectGetWidth(viewFrame) : CGRectGetHeight(viewFrame);
// Apply Stretch/Fill Alignment
if (isVert)
{
// Vertical Stack, dealing with Width
if (_alignment === CPLayoutAttributeWidth || _alignment === CPLayoutAttributeLeading || _alignment === CPLayoutAttributeTrailing)
{
// Note: CPLayoutAttributeLeading/Trailing in this context implies filling width usually,
// or aligning to edges. Let's assume Width/Fill for Leading/Trailing/Left/Right
// in this simplified implementation, or strictly left/right.
if (_alignment === CPLayoutAttributeWidth || _alignment === CPLayoutAttributeLeft || _alignment === CPLayoutAttributeLeading)
{
// Fill width if explicit, or just align left
if (_alignment === CPLayoutAttributeWidth) viewOrthoSize = orthoSize;
orthoPos = _edgeInsets.left;
}
else if (_alignment === CPLayoutAttributeRight || _alignment === CPLayoutAttributeTrailing)
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize);
}
else // CenterX
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize) / 2.0;
}
}
else // Default CenterX
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize) / 2.0;
}
}
else
{
// Horizontal Stack, dealing with Height
if (_alignment === CPLayoutAttributeHeight || _alignment === CPLayoutAttributeTop || _alignment === CPLayoutAttributeBottom)
{
if (_alignment === CPLayoutAttributeHeight)
{
viewOrthoSize = orthoSize;
orthoPos = _edgeInsets.top;
}
else if (_alignment === CPLayoutAttributeTop)
{
orthoPos = _edgeInsets.top;
}
else if (_alignment === CPLayoutAttributeBottom)
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize);
}
else // CenterY
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize) / 2.0;
}
}
else // Default CenterY
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize) / 2.0;
}
}
// Calculate Position
var originX = 0.0, originY = 0.0;
var sizeW = 0.0, sizeH = 0.0;
if (isVert)
{
// Vertical
sizeH = viewSizePrimary;
sizeW = viewOrthoSize;
originX = orthoPos;
if (dir === 1) {
originY = cursor;
cursor += sizeH;
} else {
cursor -= sizeH;
originY = cursor;
}
}
else
{
// Horizontal
sizeW = viewSizePrimary;
sizeH = viewOrthoSize;
originY = orthoPos;
if (dir === 1) {
originX = cursor;
cursor += sizeW;
} else {
cursor -= sizeW;
originX = cursor;
}
}
[view setFrame:CGRectMake(originX, originY, sizeW, sizeH)];
pendingSpacing = [self _spacingAfterView:view];
hasPlacedAny = true;
}
return cursor;
}
// MARK: -
// MARK: CPCoding
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
_orientation = [aCoder decodeIntForKey:@"CPStackViewOrientation"];
_alignment = [aCoder decodeIntForKey:@"CPStackViewAlignment"];
_distribution = [aCoder decodeIntForKey:@"CPStackViewDistribution"];
_spacing = [aCoder decodeFloatForKey:@"CPStackViewSpacing"];
_edgeInsets = [aCoder decodeObjectForKey:@"CPStackViewEdgeInsets"]; // Assuming CPEdgeInsets supports obj coding or manual decode
if (!_edgeInsets) _edgeInsets = CPEdgeInsetsMake(0,0,0,0);
_detachesHiddenViews = [aCoder decodeBoolForKey:@"CPStackViewDetachesHiddenViews"];
_viewsLeading = [aCoder decodeObjectForKey:@"CPStackViewViewsLeading"] || [];
_viewsCenter = [aCoder decodeObjectForKey:@"CPStackViewViewsCenter"] || [];
_viewsTrailing = [aCoder decodeObjectForKey:@"CPStackViewViewsTrailing"] || [];
// Rebuild arranged subviews cache
[self _rebuildArrangedSubviews];
_customSpacings = [aCoder decodeObjectForKey:@"CPStackViewCustomSpacings"] || [[CPMapTable alloc] init];
_visibilityPriorities = [[CPMapTable alloc] init]; // usually not persisted
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeInt:_orientation forKey:@"CPStackViewOrientation"];
[aCoder encodeInt:_alignment forKey:@"CPStackViewAlignment"];
[aCoder encodeInt:_distribution forKey:@"CPStackViewDistribution"];
[aCoder encodeFloat:_spacing forKey:@"CPStackViewSpacing"];
[aCoder encodeObject:_edgeInsets forKey:@"CPStackViewEdgeInsets"];
[aCoder encodeBool:_detachesHiddenViews forKey:@"CPStackViewDetachesHiddenViews"];
[aCoder encodeObject:_viewsLeading forKey:@"CPStackViewViewsLeading"];
[aCoder encodeObject:_viewsCenter forKey:@"CPStackViewViewsCenter"];
[aCoder encodeObject:_viewsTrailing forKey:@"CPStackViewViewsTrailing"];
[aCoder encodeObject:_customSpacings forKey:@"CPStackViewCustomSpacings"];
}
@end
+8 -8
View File
@@ -42,8 +42,8 @@
CPButton _buttonUp;
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
/*!
Initializes a CPStepper with given values.
@@ -147,8 +147,8 @@
[self _sizeToFit];
}
// MARK: -
// MARK: Superclass overrides
#pragma mark -
#pragma mark Superclass overrides
/*!
Set if the CPStepper is enabled or not.
@@ -230,8 +230,8 @@
[super setDoubleValue:aValue];
}
// MARK: -
// MARK: Actions
#pragma mark -
#pragma mark Actions
/*! @ignore */
- (IBAction)_buttonDidClick:(id)aSender
@@ -266,8 +266,8 @@
}
// MARK: -
// MARK: Theming
#pragma mark -
#pragma mark Theming
+ (CPString)defaultThemeClass
{
+7 -3
View File
@@ -69,9 +69,13 @@ CPCanvasStringSizingIsFunctional = NO;
CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate();
// This is to make sure that Canvas based string sizing is functional before we use it.
// Unfortunately, as of today canvas sizing is not funtional any more. Neither in Chrome nor in FF
CPCanvasStringSizingIsFunctional = NO;
// Currently, Chrome has issues with certain strings, FF had issues in the past.
// Unfortunately, this test does fit in CPCompatibility.j where things are not sufficiently initialized.
var testingFont = [CPFont systemFontOfSize:12];
var testingText = 'A A A A A A A A';
CPStringSizeMeasuringContext.font = [testingFont cssString];
CPCanvasStringSizingIsFunctional = ROUND(CPStringSizeMeasuringContext.measureText(testingText).width) == ROUND([CPPlatformString sizeOfString:testingText withFont:testingFont forWidth:NULL].width);
}
#endif
}
+7 -7
View File
@@ -696,8 +696,8 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
[self _displayItemView:_placeholderView];
}
// MARK: -
// MARK: Override
#pragma mark -
#pragma mark Override
/*!
Enabled controls accept first mouse by default.
@@ -886,11 +886,11 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
@end
// MARK: -
#pragma mark -
@implementation CPTabView (CSSTheming)
// MARK: Override
#pragma mark Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
@@ -906,7 +906,7 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
@end
// MARK: -
#pragma mark -
@implementation _CPTabViewBox : CPBox
{
@@ -915,8 +915,8 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
}
// MARK: -
// MARK: Override
#pragma mark -
#pragma mark Override
- (id)initWithFrame:(CGRect)aFrame
{
+17 -110
View File
@@ -296,8 +296,6 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
_CPTableDrawView _tableDrawView;
SEL _doubleAction;
id _doubleClickTarget @accessors(property=doubleClickTarget);
id _doubleClickArgument @accessors(property=doubleClickArgument);
CPInteger _clickedRow;
CPInteger _clickedColumn;
unsigned _columnAutoResizingStyle;
@@ -3488,73 +3486,13 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
[removeIndexes addIndex:columnIdx];
}
if ([removeIndexes count] > 0)
{
var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
[self _unloadDataViewsInRows:rowIndexes columns:removeIndexes];
var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
[self _unloadDataViewsInRows:rowIndexes columns:removeIndexes];
[_tableColumns removeObjectsAtIndexes:removeIndexes];
[_tableColumns removeObjectsAtIndexes:removeIndexes];
_dirtyTableColumnRangeIndex = 0;
[self _recalculateTableColumnRanges];
// Shift cached index sets downwards to account for the removed columns
var shiftIndexSet = function(indexSet)
{
var newSet = [CPIndexSet indexSet];
[indexSet enumerateIndexesUsingBlock:function(idx, stop)
{
if (![removeIndexes containsIndex:idx])
{
var shift = 0,
remIdx = [removeIndexes firstIndex];
while (remIdx !== CPNotFound && remIdx < idx)
{
shift++;
remIdx = [removeIndexes indexGreaterThanIndex:remIdx];
}
[newSet addIndex:idx - shift];
}
}];
return newSet;
};
_exposedColumns = shiftIndexSet(_exposedColumns);
_selectedColumnIndexes = shiftIndexSet(_selectedColumnIndexes);
// Shift individual index variables
var shiftIndex = function(idx)
{
if (idx === CPNotFound || idx === -1)
return idx;
if ([removeIndexes containsIndex:idx])
return CPNotFound;
var shift = 0,
remIdx = [removeIndexes firstIndex];
while (remIdx !== CPNotFound && remIdx < idx)
{
shift++;
remIdx = [removeIndexes indexGreaterThanIndex:remIdx];
}
return idx - shift;
};
_editingColumn = shiftIndex(_editingColumn);
_draggedColumnIndex = shiftIndex(_draggedColumnIndex);
if (_draggedColumnIndex === CPNotFound)
_draggedColumnIndex = -1;
_clickedColumn = shiftIndex(_clickedColumn);
if (_clickedColumn === CPNotFound)
_clickedColumn = -1;
}
_dirtyTableColumnRangeIndex = 0;
[self _recalculateTableColumnRanges];
[_differedColumnDataToRemove removeAllObjects];
_needsDifferedTableColumnRemove = NO;
@@ -3678,21 +3616,10 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
[self _setEditingState:NO forView:dataView];
[self _sendDelegateWillDisplayView:dataView forTableColumn:tableColumn row:row];
[self _applyToolTipToDataView:dataView forTableColumn:tableColumn row:row];
return dataView;
}
- (void)_applyToolTipToDataView:(CPView)aDataView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
{
var tooltip = nil;
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_toolTipForView_rect_tableColumn_row_mouseLocation_)
tooltip = [self _sendDelegateToolTipForView:aDataView rect:[aDataView frame] tableColumn:aTableColumn row:aRow mouseLocation:CGPointMakeZero()];
[aDataView setToolTip:tooltip];
}
- (void)_setObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow forView:(CPView)aDataView
{
[self _setObjectValueForTableColumn:aTableColumn row:aRow forView:aDataView useCache:!_invalidateObjectValuesCache];
@@ -3700,28 +3627,17 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
- (void)_setObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow forView:(CPView)aDataView useCache:(BOOL)useCache
{
var providedByDataSource = NO;
if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_)
{
var objectValue = [self _objectValueForTableColumn:aTableColumn row:aRow useCache:useCache];
[aDataView setObjectValue:objectValue];
providedByDataSource = YES;
}
[aDataView setObjectValue:[self _objectValueForTableColumn:aTableColumn row:aRow useCache:useCache]];
// This gives the table column an opportunity to apply its bindings.
// It will override the value set above if there is an explicit column binding.
var columnHasBindings = [[[CPBinder allBindingsForObject:aTableColumn] allKeys] count] > 0;
// It will override the value set above if there is a binding.
if (columnHasBindings)
{
[aTableColumn _prepareDataView:aDataView forRow:aRow];
}
// Only forcefully bind the raw content object if the data source didn't already provide a formatted value
else if (_contentBindingExplicitlySet && !providedByDataSource)
{
if (_contentBindingExplicitlySet)
[self _prepareContentBindedDataView:aDataView forRow:aRow];
}
else
// For both cell-based and view-based
[aTableColumn _prepareDataView:aDataView forRow:aRow];
}
- (void)_prepareContentBindedDataView:(CPView)dataView forRow:(CPInteger)aRow
@@ -4796,12 +4712,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
//double click actions
if ([[CPApp currentEvent] clickCount] === 2 && _doubleAction)
{
var target = _doubleClickTarget || _target,
argument = [self infoForBinding:@"doubleClickArgument"] ? _doubleClickArgument : self;
[CPApp sendAction:_doubleAction to:target from:argument];
}
[self sendAction:_doubleAction to:_target];
}
/*
@@ -5761,8 +5672,8 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
}
// MARK: -
// MARK: DataSource methods to implement
#pragma mark -
#pragma mark DataSource methods to implement
/*!
@ignore
@@ -6030,8 +5941,8 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
}
// MARK: -
// MARK: Delegate methods to implement
#pragma mark -
#pragma mark Delegate methods to implement
/*!
@ignore
@@ -6047,6 +5958,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
/*!
@ignore
Not yet implemented
*/
- (CPString)_sendDelegateToolTipForView:(id)aView rect:(CGRect)aRect tableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex mouseLocation:(CGPoint)aPoint
{
@@ -6165,11 +6077,6 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
{
if (aBinding == @"content")
_contentBindingExplicitlySet = YES;
else if (aBinding == @"doubleClickTarget")
{
if ([options objectForKey:CPSelectorNameBindingOption])
[self setDoubleAction:CPSelectorFromString([options objectForKey:CPSelectorNameBindingOption])];
}
[super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
}
+13 -82
View File
@@ -34,8 +34,6 @@
@global CPStringPboardType
@global CPCursor
@global document
@protocol CPTextFieldDelegate <CPControlTextEditingDelegate>
@end
@@ -68,11 +66,7 @@ var CPTextFieldDOMCurrentElement = nil,
CPTextFieldCachedDragFunction = nil,
CPTextFieldBlurHandler = nil,
CPTextFieldInputFunction = nil,
CPTexFieldCurrentCSSSelectableField = nil,
CPTextFieldLastValidationFailureEvent = nil,
CPTextFieldLastValidationFailureString = nil,
CPTextFieldLastValidationFailureField = nil,
CPTextFieldLastValidationFailureResult = NO;
CPTexFieldCurrentCSSSelectableField = nil;
var CPSecureTextFieldCharacter = "\u2022";
@@ -245,8 +239,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
// MARK: -
// MARK: Control Size
#pragma mark -
#pragma mark Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
@@ -257,7 +251,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
// MARK: -
#pragma mark -
#if PLATFORM(DOM)
- (DOMElement)_inputElement
@@ -336,8 +330,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
characters:nil
charactersIgnoringModifiers:nil
isARepeat:NO
keyCode:nil
isActionKey:NO];
keyCode:nil];
[CPTextFieldInputOwner keyUp:cappEvent];
@@ -381,7 +374,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return self;
}
// MARK: Controlling Editability and Selectability
#pragma mark Controlling Editability and Selectability
/*!
Sets whether or not the receiver text field can be edited. If NO, any
@@ -1001,26 +994,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
var acceptInvalidValue = NO;
if (_implementedDelegateMethods & CPTextFieldDelegate_control_didFailToFormatString_errorDescription_)
{
var currentEvent = [CPApp currentEvent];
if (currentEvent &&
CPTextFieldLastValidationFailureField === self &&
CPTextFieldLastValidationFailureString === aValue &&
CPTextFieldLastValidationFailureEvent === currentEvent)
{
acceptInvalidValue = CPTextFieldLastValidationFailureResult;
}
else
{
acceptInvalidValue = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
CPTextFieldLastValidationFailureField = self;
CPTextFieldLastValidationFailureString = aValue;
CPTextFieldLastValidationFailureEvent = currentEvent;
CPTextFieldLastValidationFailureResult = acceptInvalidValue;
}
}
acceptInvalidValue = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
if (acceptInvalidValue === NO)
return NO;
@@ -1154,12 +1128,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
return;
if ([self isEditable] && !_isEditing)
{
_isEditing = YES;
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
}
// CPTextField uses an HTML input element to take the input so we need to
// propagate the dom event so the element is updated. This has to be done
// before interpretKeyEvents: though so individual commands have a chance
@@ -1502,7 +1470,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
![self isBezeled] &&
(lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
{
textSize = [text sizeWithFont:font inWidth:textSize.width];
textSize = [text sizeWithFont:font inWidth:textSize.width] + 1;
}
else
{
@@ -1844,7 +1812,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _didEdit];
}
// MARK: Setting the Delegate
#pragma mark Setting the Delegate
- (void)setDelegate:(id <CPTextFieldDelegate>)aDelegate
{
@@ -2004,43 +1972,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _setCSSStyleForInputElement];
}
// MARK: Overrides
/*!
Sets the font of the receiver.
@param aFont - A CPFont object.
*/
- (void)setFont:(CPFont)aFont
{
if ([self currentValueForThemeAttribute:@"font"] === aFont)
return;
// Apply the font to the default/normal state
[self setValue:aFont forThemeAttribute:@"font"];
// Apply to standard editing and border states
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateEditing];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateBezeled];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateBordered];
[self setValue:aFont forThemeAttribute:@"font" inState:CPTextFieldStateRounded];
// Use CPThemeState() function to create composite states instead of array literals
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPTextFieldStateRounded, CPThemeStateEditing)];
// Apply across all standard control size states
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeRegular];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeSmall];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeMini];
// Apply to table data view states (ensuring Interface Builder-style lists respect the font)
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateTableDataView];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPThemeStateTableDataView, CPThemeStateSelectedDataView)];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPThemeStateTableDataView, CPThemeStateSelectedDataView, CPThemeStateFirstResponder, CPThemeStateKeyWindow)];
[self layoutSubviews];
}
- (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
{
var count = objects.length,
@@ -2057,7 +1988,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
}
// MARK: Overrides
#pragma mark Overrides
/*!
Sets the text color of the receiver.
@@ -2126,7 +2057,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return YES;
}
// MARK: Private
#pragma mark Private
- (BOOL)_isWithinUsablePlatformRect
{
@@ -2425,7 +2356,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
@end
// MARK: -
#pragma mark -
@implementation CPTextField (TableDataView)
@@ -2442,7 +2373,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
@end
// MARK: -
#pragma mark -
@implementation CPTextField (Deprecated)
+70 -263
View File
@@ -1,29 +1,34 @@
/*
CPFontPanel.j
AppKit
* CPFontPanel.j
* AppKit
*
* TODOs:
* 1. make browser-width for size smaller and fix columns
* 2. add all the missing features from the MacOS X counterpart (sampleview)
*
*
* Created by Daniel Boehringer on 2/JAN/2014.
* All modifications copyright Daniel Boehringer 2013.
* Extensive code formatting and review by Andrew Hankinson
* Based on original work by
* Created by Emmanuel Maillard on 06/03/2010.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
Created by Daniel Boehringer on 2/JAN/2014.
All modifications copyright Daniel Boehringer 2013.
Extensive code formatting and review by Andrew Hankinson
Based on original work by
Created by Emmanuel Maillard on 06/03/2010.
Copyright Emmanuel Maillard 2010.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPPanel.j"
@import "CPColorWell.j"
@@ -32,39 +37,36 @@
@import "CPText.j"
@import "CPFontManager.j"
@class CPTextStorage
@class CPLayoutManager
@class CPTextContainer
@class CPFontManager
@class _CPFontPanelPreviewView
/*
Collection indexes
*/
var kTypefaceIndex_Normal = 0,
kTypefaceIndex_Italic = 1,
kTypefaceIndex_Bold = 2,
var kTypefaceIndex_Normal = 0,
kTypefaceIndex_Italic = 1,
kTypefaceIndex_Bold = 2,
kTypefaceIndex_BoldItalic = 3,
kToolbarHeight = 32,
kPreviewHeight = 70,
kBorderSpacing = 6,
kInnerSpacing = 2,
kNothingChanged = 0,
kFontNameChanged = 1,
kTypefaceChanged = 2,
kSizeChanged = 3,
kTextColorChanged = 4,
kBackgroundColorChanged = 5,
kUnderlineChanged = 6,
kWeightChanged = 7,
kToolbarHeight = 32,
kBorderSpacing = 6,
kInnerSpacing = 2,
kNothingChanged = 0,
kFontNameChanged = 1,
kTypefaceChanged = 2,
kSizeChanged = 3,
kTextColorChanged = 4,
kBackgroundColorChanged = 5,
kUnderlineChanged = 6,
kWeightChanged = 7,
_sharedFontPanel;
// FIXME<!> Locale support
var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"64", @"72", @"96", @"144", @"288"];
var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"72", @"96"];
/*!
@ingroup appkit
@@ -75,10 +77,6 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
id _fontBrowser;
id _traitBrowser;
id _sizeBrowser;
// Preview
_CPFontPanelPreviewView _previewView;
CPArray _availableFonts;
id _textColorWell;
CPColor _textColor;
@@ -87,8 +85,9 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
int _fontChanges;
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
/*!
Check if the shared Font panel exists.
@@ -109,18 +108,14 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
return _sharedFontPanel;
}
- (BOOL)acceptsFirstResponder
{
return NO;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
/*! @ignore */
- (id)init
{
if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 420) styleMask:(CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask)])
if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 394) styleMask:(CPTitledWindowMask | CPClosableWindowMask /*| CPResizableWindowMask*/ )])
{
[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
[self setTitle:@"Font Panel"];
@@ -157,49 +152,10 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
[aBrowser setDoubleAction:@selector(dblClicked:)];
[aBrowser setAllowsEmptySelection:NO];
[aBrowser setAllowsMultipleSelection:NO];
// Config Scrollers
//[aBrowser setHasHorizontalScroller:NO];
//[aBrowser setHasVerticalScroller:YES];
//[aBrowser setAutohidesScrollers:YES];
//[aBrowser setMaxVisibleColumns:1];
[aBrowser setDelegate:self];
[[self contentView] addSubview:aBrowser];
}
- (void)_layoutBrowsers
{
var contentView = [self contentView],
contentBounds = [contentView bounds],
previewY = kBorderSpacing + kToolbarHeight + kInnerSpacing,
browserY = previewY + kPreviewHeight + 10,
browserHeight = CGRectGetHeight(contentBounds) - browserY - 10,
availableWidth = CGRectGetWidth(contentBounds) - 20; // 10px padding L/R
// Layout Calculations
// Increase sizeWidth slightly to 60 to allow space for the vertical scrollbar without clipping text
var sizeWidth = 90,
spacing = 5,
remainingWidth = availableWidth - sizeWidth - (spacing * 2),
// Split remaining roughly 60% font name, 40% trait
fontWidth = FLOOR(remainingWidth * 0.60),
traitWidth = remainingWidth - fontWidth;
// Apply frames and column constraints
[_fontBrowser setFrame:CGRectMake(10, browserY, fontWidth, browserHeight)];
[_fontBrowser setDefaultColumnWidth:fontWidth];
[_fontBrowser setLastColumn:0];
[_traitBrowser setFrame:CGRectMake(10 + fontWidth + spacing, browserY, traitWidth, browserHeight)];
[_traitBrowser setDefaultColumnWidth:traitWidth];
[_traitBrowser setLastColumn:0];
[_sizeBrowser setFrame:CGRectMake(10 + fontWidth + traitWidth + (spacing * 2), browserY, sizeWidth, browserHeight)];
[_sizeBrowser setDefaultColumnWidth:sizeWidth];
[_sizeBrowser setLastColumn:0];
}
- (void)_setupContents
{
if (_setupDone)
@@ -207,53 +163,33 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_setupDone = YES;
// We set ourselves as delegate to handle resizing layout manually
[self setDelegate:self];
[self _setupToolbarView];
var contentView = [self contentView],
contentBounds = [contentView bounds];
label = [CPTextField labelWithTitle:@"Font name"],
contentBounds = [contentView bounds],
upperView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(contentBounds), CGRectGetHeight(contentBounds) - (kBorderSpacing + kToolbarHeight + kInnerSpacing))];
[contentView addSubview:_toolbarView];
// Preview View
var previewY = kBorderSpacing + kToolbarHeight + kInnerSpacing;
_previewView = [[_CPFontPanelPreviewView alloc] initWithFrame:CGRectMake(10, previewY, CGRectGetWidth(contentBounds) - 20, kPreviewHeight)];
[_previewView setAutoresizingMask:CPViewWidthSizable];
[contentView addSubview:_previewView];
// Initialize Browsers with zero rect, _layoutBrowsers will size them
_fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
_traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
_sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
// Disable autoresizing masks because we are laying out manually in windowDidResize
[_fontBrowser setAutoresizingMask:CPViewNotSizable];
[_traitBrowser setAutoresizingMask:CPViewNotSizable];
[_sizeBrowser setAutoresizingMask:CPViewNotSizable];
_fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(10, 35, 150, 350)];
_traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(155, 35, 150, 350)];
_sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(300, 35, 140, 350)];
[self _setupBrowser:_fontBrowser];
[self _setupBrowser:_traitBrowser];
[self _setupBrowser:_sizeBrowser];
// Perform initial layout
[self _layoutBrowsers];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewDidChangeSelection:)
name:CPTextViewDidChangeSelectionNotification
object:nil];
}
- (void)windowDidResize:(CPNotification)aNotification
{
[self _layoutBrowsers];
}
- (void)textViewDidChangeSelection:(CPNotification)notification
{
[self _refreshWithTextView:[notification object]];
[self _refreshWithTextView:[notification object]];
}
- (void)_refreshWithTextView:(CPTextView)textView
@@ -261,9 +197,6 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if (![self isVisible])
return;
if (![textView respondsToSelector:@selector(_attributesForFontPanel)])
return;
var attribs = [textView _attributesForFontPanel],
font = [attribs objectForKey:CPFontAttributeName] || [[textView textStorage] font] || [CPFont systemFontOfSize:12.0],
color = [attribs objectForKey:CPForegroundColorAttributeName];
@@ -284,9 +217,6 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
[self setCurrentTrait:trait];
[self setCurrentSize:[font size] + ""]; //cast to string
// Update Preview
[_previewView setPreviewFont:font];
if (!color)
return;
@@ -323,7 +253,7 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
{
case kFontNameChanged:
newFont = [CPFont fontWithDescriptor:[[aFont fontDescriptor] fontDescriptorByAddingAttributes:
[CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0];
[CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0];
break;
case kTypefaceChanged:
@@ -342,13 +272,12 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toSize:[self currentSize]];
break;
case kNothingChanged:
case kNothingChanged:
break;
default:
CPLog.trace(@"FIXME: -[" + [self className] + " " + _cmd + "] unhandled _fontChanges: " + _fontChanges);
break;
}
return newFont;
@@ -356,7 +285,7 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
- (void)setCurrentSize:(CGSize)aSize
{
[_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0];
[_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0];
}
- (CPString)currentSize
@@ -366,7 +295,7 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
- (void)setCurrentFont:(CPFont)aFont
{
[_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0];
[_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0];
}
- (CPString)currentFont
@@ -391,10 +320,9 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
case kTypefaceIndex_BoldItalic:
row = 3;
break;
}
[_traitBrowser selectRow:row inColumn:0];
[_traitBrowser selectRow:row inColumn:0];
}
// FIXME<!> Locale support
@@ -442,8 +370,6 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if ([self currentTrait] != typefaceIndex)
[self setCurrentTrait:typefaceIndex ];
[_previewView setPreviewFont:font];
_fontChanges = kNothingChanged;
}
@@ -456,30 +382,23 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
////////////////////////////////////////////////////////////////////
// TODO: ask CPFontManager for traits //
- (void)browserClicked:(id)aBrowser
{
if (aBrowser === _fontBrowser)
{
_fontChanges = kFontNameChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
else if (aBrowser === _traitBrowser)
{
_fontChanges = kTypefaceChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
else if (aBrowser === _sizeBrowser)
{
_fontChanges = kSizeChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
// Apply change immediately to manager (standard behavior)
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
// Update our preview manually because convertFont: calls rely on selected rows
// We construct a temporary font to update the preview view immediately
var updatedFont = [self panelConvertFont:[_previewView font]];
if (updatedFont)
[_previewView setPreviewFont:updatedFont];
}
- (void)dblClicked:(id)sender
@@ -495,7 +414,7 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if (aBrowser === _traitBrowser)
return [_availableTraits count];
return [_availableSizes count];
return [_availableSizes count]
}
- (id)browser:(id)aBrowser child:(int)index ofItem:(id)anItem
@@ -521,116 +440,4 @@ var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
@end
// -----------------------------------------------------------------------------
// _CPFontPanelPreviewView
// A helper class to display a font sample with metrics grid
// -----------------------------------------------------------------------------
@implementation _CPFontPanelPreviewView : CPView
{
CPTextField _sampleText;
CPColor _gridColor;
float _gridSize;
}
- (id)initWithFrame:(CGRect)aRect
{
self = [super initWithFrame:aRect];
if (self)
{
[self setBackgroundColor:[CPColor whiteColor]];
_gridColor = [CPColor colorWithHexString:@"e4f4ff"];
_gridSize = 10.0;
_sampleText = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(aRect), CGRectGetHeight(aRect))];
[_sampleText setStringValue:@"AaYy-0123"];
[_sampleText setAlignment:CPCenterTextAlignment];
[_sampleText setVerticalAlignment:CPCenterVerticalTextAlignment];
[_sampleText setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_sampleText setTextColor:[CPColor blackColor]];
[self addSubview:_sampleText];
}
return self;
}
- (void)setPreviewFont:(CPFont)aFont
{
[_sampleText setFont:aFont];
[self setNeedsDisplay:YES];
}
- (CPFont)font
{
return [_sampleText font];
}
- (void)drawRect:(CGRect)dirtyRect
{
// Draw Grid (from MetricsView inspiration)
var context = [[CPGraphicsContext currentContext] graphicsPort],
bounds = [self bounds],
maxX = CGRectGetMaxX(bounds),
maxY = CGRectGetMaxY(bounds);
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColor(context, _gridColor);
CGContextBeginPath(context);
for (var y = 0.5; y <= maxY; y += _gridSize)
{
CGContextMoveToPoint(context, 0.0, y);
CGContextAddLineToPoint(context, maxX, y);
}
for (var x = 0.5; x <= maxX; x += _gridSize)
{
CGContextMoveToPoint(context, x, 0.0);
CGContextAddLineToPoint(context, x, maxY);
}
CGContextStrokePath(context);
// Draw Baseline/Ascender/Descender (from BaselineView inspiration)
var font = [_sampleText font];
if (!font) return;
var ascender = [font ascender],
descender = [font descender],
lineHeight = [font defaultLineHeightForFont];
// Calculate the baseline.
// CPTextField with CPCenterVerticalTextAlignment usually centers the line height.
// Top of line = midY - (lineHeight / 2.0)
// Baseline = Top of line + ascender
var midY = maxY / 2.0,
baselineY = midY - (lineHeight / 2.0) + ascender;
CGContextSetStrokeColor(context, [CPColor redColor]);
CGContextBeginPath(context);
// Baseline
CGContextMoveToPoint(context, 0, baselineY);
CGContextAddLineToPoint(context, maxX, baselineY);
// Ascender Line
CGContextMoveToPoint(context, 0, baselineY - ascender);
CGContextAddLineToPoint(context, maxX, baselineY - ascender);
// Descender Line
CGContextMoveToPoint(context, 0, baselineY - descender);
CGContextAddLineToPoint(context, maxX, baselineY - descender);
CGContextStrokePath(context);
}
- (void)mouseDown:(CPEvent)anEvent
{
var text = prompt("Enter sample text:", [_sampleText stringValue]);
if (text)
[_sampleText setStringValue:text];
}
@end
[CPFontManager setFontPanelFactory:[CPFontPanel class]];
+85 -249
View File
@@ -31,9 +31,6 @@
@import "CPFont.j"
@global _MakeRangeFromAbs
@global document
@global CPBaselineOffsetAttributeName
@global CPSuperscriptAttributeName
@class CPTextContainer
@class CPTextView
@@ -72,12 +69,11 @@ _oncontextmenuhandler = function () { return false; };
BOOL _isValidatingLayoutAndGlyphs;
CPRange _removeInvalidLineFragmentsRange;
CPRange _lastEditedRange;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)init
{
@@ -97,14 +93,13 @@ _oncontextmenuhandler = function () { return false; };
_textContainers = [[CPMutableArray alloc] init];
_textStorage = [[CPTextStorage alloc] init];
_typesetter = [CPTypesetter sharedSystemTypesetter];
_lastEditedRange = nil;
[_textStorage addLayoutManager:self];
}
// MARK: -
// MARK: Text containes method
#pragma mark -
#pragma mark Text containes method
- (void)insertTextContainer:(CPTextContainer)aContainer atIndex:(int)index
{
@@ -289,12 +284,11 @@ _oncontextmenuhandler = function () { return false; };
if (removeRange.length)
_removeInvalidLineFragmentsRange = CPMakeRangeCopy(removeRange);
else
_removeInvalidLineFragmentsRange = nil;
// We erased all lines
if (!startIndex)
[self setExtraLineFragmentRect:CGRectMake(0, 0) usedRect:CGRectMake(0, 0) textContainer:nil];
// document.title=startIndex;
[_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil];
@@ -307,17 +301,13 @@ _oncontextmenuhandler = function () { return false; };
- (BOOL)_rescuingInvalidFragmentsWasPossibleForGlyphRange:(CPRange)aRange
{
// 1. EARLY EXIT: If there are no fragments to rescue (e.g. setting new text), do nothing.
if (!_lineFragmentsForRescue || _lineFragmentsForRescue.length === 0)
return NO;
var l = _lineFragments.length,
location = aRange.location,
found = NO,
targetLine = l - 1; // Start from the END of the array
location = aRange.location,
found = NO,
targetLine = 0;
// 2. REVERSE SEARCH: The fragment we want is almost always at the end.
for (; targetLine >= 0; targetLine--)
// try to find the first linefragment of the desired range
for (; targetLine < l; targetLine++)
{
if (CPLocationInRange(location, _lineFragments[targetLine]._range))
{
@@ -340,6 +330,9 @@ _oncontextmenuhandler = function () { return false; };
newLength = [[_textStorage string].length],
removalSkip = 1;
// if (ABS(newLength - oldLength) > 1)
// return NO;
if (![oldLineFragment isVisuallyIdenticalToFragment:newLineFragment])
{
isIdentical = NO;
@@ -372,16 +365,6 @@ _oncontextmenuhandler = function () { return false; };
l = _lineFragmentsForRescue.length,
newTargetLine = startLineForDOMRemoval + removalSkip;
// Ensure that the remaining lines we are attempting to rescue
// start after the end of the edited region.
if (newTargetLine < l && _lastEditedRange)
{
var firstRescuedLineNewLocation = _lineFragmentsForRescue[newTargetLine]._range.location + rangeOffset;
if (firstRescuedLineNewLocation < CPMaxRange(_lastEditedRange))
return NO;
}
for (; newTargetLine < l; newTargetLine++)
{
_lineFragmentsForRescue[newTargetLine]._isInvalid = NO; // protect them from final removal
@@ -453,8 +436,6 @@ _oncontextmenuhandler = function () { return false; };
{
var actualRange = CPMakeRange(CPNotFound,0);
_lastEditedRange = CPMakeRangeCopy(charRange);
[self invalidateLayoutForCharacterRange:invalidatedRange isSoft:NO actualCharacterRange:actualRange];
[self invalidateDisplayForGlyphRange:actualRange];
[self _validateLayoutAndGlyphs];
@@ -546,26 +527,20 @@ _oncontextmenuhandler = function () { return false; };
var frames = [fragment glyphFrames],
len = fragment._range.length;
if (frames)
for (var j = 0; j < len; j++)
{
var maxLen = MIN(len, frames.length);
for (var j = 0; j < maxLen; j++)
if (CGRectContainsPoint(frames[j], point))
{
var frame = frames[j];
if (partialFraction)
partialFraction[0] = (point.x - frames[j].origin.x) / frames[j].size.width;
if (frame && CGRectContainsPoint(frame, point))
{
if (partialFraction)
partialFraction[0] = (point.x - frame.origin.x) / frame.size.width;
return fragment._range.location + j;
}
return fragment._range.location + j;
}
}
}
}
// Not found, maybe a point left to the last character was clicked -> search again with broader constraints
if ([[_textStorage string] length])
{
for (var i = 0; i < c; i++)
@@ -574,33 +549,30 @@ _oncontextmenuhandler = function () { return false; };
if (fragment._textContainer === container)
{
if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y &&
point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height)
{
if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y)
continue;
var nlLoc = CPMaxRange(fragment._range),
frames = [fragment glyphFrames];
if (frames && frames.length > 0)
// Within the horizontal territory of the current (not-empty) line?
if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y &&
point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height)
{
var lastFrame = frames[frames.length - 1],
firstFrame = frames[0];
// Skip tabs and move on the last fragment in this line
if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y)
continue;
if (lastFrame && firstFrame)
{
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0]))
nlLoc--;
var nlLoc = CPMaxRange(fragment._range),
lastFrame = [fragment glyphFrames][fragment._range.length - 1],
firstFrame = [fragment glyphFrames][0];
if (point.x > CGRectGetMaxX(lastFrame))
return nlLoc;
else if (point.x <= CGRectGetMinX(firstFrame))
return fragment._range.location;
else
return nlLoc;
}
}
// stay on the line the newline character belongs to
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0]))
nlLoc--;
// Clicked right to the last character
if (point.x > CGRectGetMaxX(lastFrame))
return nlLoc;
// Clicked left to the last character
else if (point.x <= CGRectGetMinX(firstFrame))
return fragment._range.location;
else
return nlLoc;
}
}
}
@@ -730,11 +702,7 @@ _oncontextmenuhandler = function () { return false; };
var index = location - lineFragment._range.location;
if (index < 0 || !lineFragment._glyphsOffsets || index >= lineFragment._glyphsOffsets.length)
return 0.0;
var offset = lineFragment._glyphsOffsets[index];
return (offset === undefined) ? 0.0 : offset;
return lineFragment._glyphsOffsets[index];
}
- (double)_descentAtLocation:(unsigned)location
@@ -746,11 +714,7 @@ _oncontextmenuhandler = function () { return false; };
var index = location - lineFragment._range.location;
if (index < 0 || !lineFragment._glyphsFrames || index >= lineFragment._glyphsFrames.length)
return 0.0;
var frame = lineFragment._glyphsFrames[index];
return (frame && frame._descent !== undefined) ? frame._descent : 0.0;
return lineFragment._glyphsFrames[index]._descent;
}
- (void)setLineFragmentRect:(CGRect)fragmentRect forGlyphRange:(CPRange)glyphRange usedRect:(CGRect)usedRect
@@ -874,16 +838,11 @@ _oncontextmenuhandler = function () { return false; };
{
if (_lineFragments.length > 0 && index >= [self numberOfGlyphs] - 1)
{
var lineFragment = _lineFragments[_lineFragments.length - 1],
var lineFragment= _lineFragments[_lineFragments.length - 1],
glyphFrames = [lineFragment glyphFrames];
if (glyphFrames && glyphFrames.length > 0)
{
var frame = glyphFrames[glyphFrames.length - 1];
if (frame)
return CGPointCreateCopy(frame.origin);
}
if (glyphFrames.length > 0)
return CGPointCreateCopy(glyphFrames[glyphFrames.length - 1].origin);
}
var lineFragment = _objectWithLocationInRange(_lineFragments, index);
@@ -894,17 +853,8 @@ _oncontextmenuhandler = function () { return false; };
return CGPointCreateCopy(lineFragment._location);
var glyphFrames = [lineFragment glyphFrames];
var relativeIndex = index - lineFragment._range.location;
if (glyphFrames && relativeIndex >= 0 && relativeIndex < glyphFrames.length)
{
var frame = glyphFrames[relativeIndex];
if (frame)
return CGPointCreateCopy(frame.origin);
}
return CGPointCreateCopy(lineFragment._location);
return CGPointCreateCopy(glyphFrames[index - lineFragment._range.location].origin);
}
return CGPointMakeZero();
@@ -950,6 +900,7 @@ _oncontextmenuhandler = function () { return false; };
inTextContainer:(CPTextContainer)container
rectCount:(CGRectPointer)rectCount
{
var rectArray = [],
lineFragments = _objectsInRange(_lineFragments, selectedCharRange);
@@ -968,24 +919,21 @@ _oncontextmenuhandler = function () { return false; };
rect = nil,
len = fragment._range.length;
if (frames)
for (var j = 0; j < len; j++)
{
for (var j = 0; j < len; j++)
if (CPLocationInRange(fragment._range.location + j, selectedCharRange))
{
if (j < frames.length && CPLocationInRange(fragment._range.location + j, selectedCharRange))
{
var frame = frames[j];
var correctedRect = CGRectCreateCopy(frames[j]);
correctedRect.size.height -= frames[j]._descent;
correctedRect.origin.y -= frames[j]._descent;
if (frame)
{
var correctedRect = CGRectCreateCopy(frame);
if (!rect)
rect = CGRectCreateCopy(correctedRect);
else
rect = CGRectUnion(rect, correctedRect);
if (!rect)
rect = CGRectCreateCopy(correctedRect);
else
rect = CGRectUnion(rect, correctedRect);
}
}
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)]))
rect.size.width = containerSize.width - rect.origin.x;
}
}
@@ -996,7 +944,7 @@ _oncontextmenuhandler = function () { return false; };
var len = rectArray.length;
for (var i = 0; i < len - 1; i++)
for (var i = 0; i < len - 1; i++) // extend the width of all but the last one
{
if (FLOOR(CGRectGetMaxY(rectArray[i])) == FLOOR(CGRectGetMaxY(rectArray[i + 1])))
continue;
@@ -1132,8 +1080,8 @@ var _objectsInRange = function(aList, aRange)
CPMutableArray _runs;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)aColor
{
@@ -1142,10 +1090,6 @@ var _objectsInRange = function(aList, aRange)
- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)fgColor andBackgroundColor:(CPColor)bgColor andUnderline:(CPUnderlineStyle)aUnderline
{
if (!aString || aString.length === 0)
return nil;
#if PLATFORM(DOM)
var style,
span = document.createElement("span");
@@ -1226,16 +1170,18 @@ var _objectsInRange = function(aList, aRange)
effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange;
var string = [textStorage._string substringWithRange:effectiveRange],
underline = [attributes objectForKey:CPUnderlineStyleAttributeName] || CPUnderlineStyleNone,
paragraphStyle = [attributes objectForKey:CPParagraphStyleAttributeName] || [CPParagraphStyle defaultParagraphStyle];
underline = [attributes objectForKey:CPUnderlineStyleAttributeName] || CPUnderlineStyleNone;
// this is an attachment -> create a run for it
if (string === _CPAttachmentCharacterAsString)
{
if (![attributes objectForKey:_CPAttachmentInvisible])
{
var view = [attributes objectForKey:_CPAttachmentView];
var run = {_range:CPMakeRangeCopy(effectiveRange), color:nil, font:nil, elem:nil, string:nil, view:view, paragraphStyle:paragraphStyle, underline:underline, baselineOffset:0.0};
var view = [attributes objectForKey:_CPAttachmentView],
viewCopy = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:view]],
elem = viewCopy._DOMElement,
run = {_range:CPMakeRangeCopy(effectiveRange), color:nil, font:nil, elem:elem, string:nil, view:viewCopy};
_runs.push(run);
}
}
@@ -1243,97 +1189,10 @@ var _objectsInRange = function(aList, aRange)
{
var color = [attributes objectForKey:CPForegroundColorAttributeName],
bgcolor = [attributes objectForKey:CPBackgroundColorAttributeName],
font = [attributes objectForKey:CPFontAttributeName] || [textStorage font] || [CPFont systemFontOfSize:12.0];
font = [attributes objectForKey:CPFontAttributeName] || [textStorage font] || [CPFont systemFontOfSize:12.0],
run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string, bgcolor:bgcolor};
var baselineOffset = [attributes objectForKey:CPBaselineOffsetAttributeName],
superscript = [attributes objectForKey:CPSuperscriptAttributeName];
if (baselineOffset === nil || baselineOffset === undefined || typeof baselineOffset !== "number")
baselineOffset = 0.0;
if (superscript === nil || superscript === undefined || typeof superscript !== "number")
superscript = 0;
if (superscript !== 0)
{
var size = [font size],
scaledSize = size * 0.65,
fontName = [font familyName],
isBold = [font isBold],
isItalic = [font isItalic];
font = [CPFont _fontWithName:fontName size:scaledSize bold:isBold italic:isItalic];
if (baselineOffset === 0.0)
{
if (superscript > 0)
baselineOffset = size * 0.35;
else
baselineOffset = -size * 0.15;
}
}
var currentLoc = effectiveRange.location,
strLen = string.length,
startIdx = 0;
for (var i = 0; i < strLen; i++)
{
if (string.charCodeAt(i) === 9) // Tabulator-Zeichen '\t'
{
if (i > startIdx)
{
var subString = string.substring(startIdx, i),
subRange = CPMakeRange(currentLoc + startIdx, i - startIdx),
run = {
_range: subRange,
color: color,
font: font,
elem: nil,
string: subString,
bgcolor: bgcolor,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: baselineOffset
};
_runs.push(run);
}
var tabRange = CPMakeRange(currentLoc + i, 1),
tabRun = {
_range: tabRange,
color: nil,
font: nil,
elem: nil,
string: nil,
bgcolor: nil,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: 0.0
};
_runs.push(tabRun);
startIdx = i + 1;
}
}
if (startIdx < strLen)
{
var subString = string.substring(startIdx, strLen),
subRange = CPMakeRange(currentLoc + startIdx, strLen - startIdx),
run = {
_range: subRange,
color: color,
font: font,
elem: nil,
string: subString,
bgcolor: bgcolor,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: baselineOffset
};
_runs.push(run);
}
_runs.push(run);
}
if (!CPMaxRange(effectiveRange))
@@ -1357,10 +1216,7 @@ var _objectsInRange = function(aList, aRange)
{
_glyphsFrames[i] = CGRectMake(origin.x, origin.y, someAdvancements[i].width, height);
_glyphsFrames[i]._descent = someAdvancements[i].descent;
// Align the run's baseline with the common line baseline (_location.y)
_glyphsOffsets[i] = _location.y - someAdvancements[i].height;
_glyphsOffsets[i] = height - someAdvancements[i].height;
origin.x += someAdvancements[i].width;
}
}
@@ -1408,11 +1264,13 @@ var _objectsInRange = function(aList, aRange)
for (var i = 0; i < l; i++)
{
if (_runs[i].view && _runs[i].DOMactive)
[_runs[i].view removeFromSuperview];
if (_runs[i].elem && _runs[i].DOMactive)
_textContainer._textView._DOMElement.removeChild(_runs[i].elem);
{
if (_runs[i].view)
[_runs[i].view removeFromSuperview];
else
_textContainer._textView._DOMElement.removeChild(_runs[i].elem);
}
_runs[i].elem = nil;
_runs[i].DOMactive = NO;
@@ -1425,9 +1283,6 @@ var _objectsInRange = function(aList, aRange)
c = runs.length,
orig = CGPointMake(_fragmentRect.origin.x, _fragmentRect.origin.y);
if (_runs.length === 0)
return;
for (var i = 0; i < c; i++)
{
var run = runs[i];
@@ -1442,21 +1297,13 @@ var _objectsInRange = function(aList, aRange)
continue;
var loc = run._range.location - _runs[0]._range.location;
// Safety bounds check to protect against uninitialized/empty glyph frames or offsets
if (loc < 0 || loc >= _glyphsFrames.length || !_glyphsFrames[loc] || !_glyphsOffsets || loc >= _glyphsOffsets.length)
continue;
orig.x = _glyphsFrames[loc].origin.x + aPoint.x;
orig.y = _glyphsFrames[loc].origin.y + aPoint.y + _glyphsOffsets[loc];
if(run.elem || run.view)
if(run.elem)
{
if (run.elem)
{
run.elem.style.left = (orig.x) + "px";
run.elem.style.top = (orig.y) + "px";
}
run.elem.style.left = (orig.x) + "px";
run.elem.style.top = (orig.y) + "px";
if (run.view)
[run.view setFrameOrigin:orig];
@@ -1464,9 +1311,8 @@ var _objectsInRange = function(aList, aRange)
if (!run.DOMactive)
{
if (run.view)
[_textContainer._textView addSubview:run.view];
if (run.elem)
[self._textContainer._textView addSubview:run.view];
else
_textContainer._textView._DOMElement.appendChild(run.elem);
}
@@ -1505,17 +1351,9 @@ var _objectsInRange = function(aList, aRange)
if (!_RectEqualToRectHorizontally(newLineFragment._fragmentRect, _fragmentRect))
return NO;
if (newFragmentRuns[i].color !== oldFragmentRuns[i].color ||
newFragmentRuns[i].bgcolor !== oldFragmentRuns[i].bgcolor ||
newFragmentRuns[i].font !== oldFragmentRuns[i].font ||
newFragmentRuns[i].baselineOffset !== oldFragmentRuns[i].baselineOffset)
if (newFragmentRuns[i].color !== oldFragmentRuns[i].color || newFragmentRuns[i].font !== oldFragmentRuns[i].font)
return NO;
var oldStyle = oldFragmentRuns[i].paragraphStyle || [CPParagraphStyle defaultParagraphStyle],
newStyle = newFragmentRuns[i].paragraphStyle || [CPParagraphStyle defaultParagraphStyle];
if (![oldStyle isEqual:newStyle])
return NO;
}
return YES;
@@ -1531,14 +1369,12 @@ var _objectsInRange = function(aList, aRange)
{
_runs[i]._range.location += rangeOffset;
if (verticalOffset)
if (verticalOffset && _runs[i].elem)
{
if (_runs[i].view)
_runs[i].view._frame.origin.y += verticalOffset;
if (_runs[i].elem)
_runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
_runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
_runs[i].DOMpatched = YES;
}
}
+150 -313
View File
@@ -2,6 +2,10 @@
* CPParagraphStyle.j
* AppKit
*
* FIXME
* This is basically a stub.
* We need to store all the spacing informations as well as writing direction (among others)
*
* Created by Daniel Boehringer on 11/01/2014
* Copyright Daniel Boehringer 2014.
*
@@ -22,365 +26,198 @@
@import <Foundation/CPObject.j>
@import <Foundation/CPArray.j>
@import <Foundation/CPDictionary.j>
@import "CPText.j"
CPLeftTabStopType = 0;
CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName";
@typedef CPTabStopType
CPLeftTabStopType = 0;
CPRightTabStopType = 1;
CPCenterTabStopType = 2;
CPDecimalTabStopType = 3;
// Standard Tab Interval (28pts is roughly 4 spaces in standard fonts)
var kDefaultTabInterval = 28.0;
// MARK: - CPTextTab Implementation
@implementation CPTextTab : CPObject
{
CPTextAlignment _alignment @accessors(readonly, property=alignment);
float _location @accessors(readonly, property=location);
CPDictionary _options @accessors(readonly, property=options);
}
- (id)initWithTextAlignment:(CPTextAlignment)anAlignment location:(float)aLocation options:(CPDictionary)options
{
if (self = [super init])
{
_alignment = anAlignment;
_location = aLocation;
_options = [options copy];
}
return self;
}
// Convenience initializer matching AppKit behavior
- (id)initWithType:(CPTabStopType)aType location:(float)aLocation
{
// Map old TabStopType to TextAlignment for modern compatibility
return [self initWithTextAlignment:aType location:aLocation options:nil];
}
// Added to resolve the unrecognized selector exception in the RTF producer
- (CPTabStopType)tabStopType
{
return _alignment;
}
- (BOOL)isEqual:(id)other
{
if (self === other) return YES;
if (![other isKindOfClass:[CPTextTab class]]) return NO;
return _location === [other location] &&
_alignment === [other alignment] &&
((_options == nil && [other options] == nil) || [_options isEqualToDictionary:[other options]]);
}
- (id)copy
{
return [[CPTextTab alloc] initWithTextAlignment:_alignment location:_location options:_options];
}
- (id)initWithCoder:(CPCoder)aCoder
{
if (self = [super init])
{
_alignment = [aCoder decodeIntForKey:@"CPTextTabAlignment"];
_location = [aCoder decodeFloatForKey:@"CPTextTabLocation"];
_options = [aCoder decodeObjectForKey:@"CPTextTabOptions"];
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeInt:_alignment forKey:@"CPTextTabAlignment"];
[aCoder encodeFloat:_location forKey:@"CPTextTabLocation"];
[aCoder encodeObject:_options forKey:@"CPTextTabOptions"];
}
@end
// MARK: - CPParagraphStyle Implementation
var _sharedDefaultParagraphStyle = nil;
var _sharedDefaultParagraphStyle,
_defaultTabStopArray;
@implementation CPParagraphStyle : CPObject
{
float _lineSpacing @accessors(readonly, property=lineSpacing);
float _paragraphSpacing @accessors(readonly, property=paragraphSpacing);
CPTextAlignment _alignment @accessors(readonly, property=alignment);
float _headIndent @accessors(readonly, property=headIndent);
float _tailIndent @accessors(readonly, property=tailIndent);
float _firstLineHeadIndent @accessors(readonly, property=firstLineHeadIndent);
float _minimumLineHeight @accessors(readonly, property=minimumLineHeight);
float _maximumLineHeight @accessors(readonly, property=maximumLineHeight);
CPLineBreakMode _lineBreakMode @accessors(readonly, property=lineBreakMode);
CPWritingDirection _baseWritingDirection @accessors(readonly, property=baseWritingDirection);
float _lineHeightMultiple @accessors(readonly, property=lineHeightMultiple);
float _paragraphSpacingBefore @accessors(readonly, property=paragraphSpacingBefore);
float _defaultTabInterval @accessors(readonly, property=defaultTabInterval);
CPArray _tabStops @accessors(readonly, property=tabStops);
CPArray _tabStops @accessors(property=tabStops);
CPTextAlignment _alignment @accessors(property=alignment);
unsigned _firstLineHeadIndent @accessors(property=firstLineHeadIndent);
unsigned _headIndent @accessors(property=headIndent);
unsigned _tailIndent @accessors(property=tailIndent);
unsigned _paragraphSpacing @accessors(property=paragraphSpacing);
unsigned _minimumLineHeight @accessors(property=minimumLineHeight);
unsigned _maximumLineHeight @accessors(property=maximumLineHeight);
unsigned _lineSpacing @accessors(property=lineSpacing);
}
#pragma mark -
#pragma mark Class methods
+ (CPParagraphStyle)defaultParagraphStyle
{
if (!_sharedDefaultParagraphStyle)
{
_sharedDefaultParagraphStyle = [[CPParagraphStyle alloc] init];
// Ensure defaults are set on the shared instance internal vars
// Since it's immutable, we rely on the init to set these.
}
_sharedDefaultParagraphStyle = [self new];
return _sharedDefaultParagraphStyle;
}
+ (CPWritingDirection)defaultWritingDirectionForLanguage:(CPString)languageName
+ (CPArray)_defaultTabStops
{
// Simplified: Cappuccino usually assumes LTR unless specified otherwise.
return CPWritingDirectionLeftToRight;
if (!_defaultTabStopArray)
{
var i;
_defaultTabStopArray = [];
// <!> FIXME: Define constants for these magic numbers: 13, 28
for (i = 1; i < 16 ; i++)
{
_defaultTabStopArray.push([[CPTextTab alloc] initWithType:CPLeftTabStopType location:i * 28]);
}
}
return _defaultTabStopArray;
}
#pragma mark -
#pragma mark Init methods
- (id)init
{
if (self = [super init])
{
_lineSpacing = 0.0;
_paragraphSpacing = 0.0;
_alignment = CPLeftTextAlignment;
_headIndent = 0.0;
_tailIndent = 0.0;
_firstLineHeadIndent = 0.0;
_minimumLineHeight = 0.0;
_maximumLineHeight = 0.0;
_lineBreakMode = CPLineBreakByWordWrapping;
_baseWritingDirection = CPWritingDirectionNatural;
_lineHeightMultiple = 0.0;
_paragraphSpacingBefore = 0.0;
_defaultTabInterval = kDefaultTabInterval;
// Generate default tab stops
_tabStops = [];
for (var i = 1; i <= 12; i++)
{
[_tabStops addObject:[[CPTextTab alloc] initWithType:CPLeftTextAlignment
location:i * kDefaultTabInterval]];
}
}
[self _initWithDefaults];
return self;
}
- (id)initWithParagraphStyle:(CPParagraphStyle)other
- (CPParagraphStyle)initWithParagraphStyle:(CPParagraphStyle)other
{
if (self = [super init])
{
_lineSpacing = [other lineSpacing];
_paragraphSpacing = [other paragraphSpacing];
_alignment = [other alignment];
_headIndent = [other headIndent];
_tailIndent = [other tailIndent];
_firstLineHeadIndent = [other firstLineHeadIndent];
_minimumLineHeight = [other minimumLineHeight];
_maximumLineHeight = [other maximumLineHeight];
_lineBreakMode = [other lineBreakMode];
_baseWritingDirection = [other baseWritingDirection];
_lineHeightMultiple = [other lineHeightMultiple];
_paragraphSpacingBefore = [other paragraphSpacingBefore];
_defaultTabInterval = [other defaultTabInterval];
_tabStops = [[other tabStops] copy];
}
self = [super init];
_tabStops = [other._tabStops copy];
_alignment = other._alignment;
_firstLineHeadIndent = other._firstLineHeadIndent;
_headIndent = other._headIndent;
_tailIndent = other._tailIndent;
_paragraphSpacing = other._paragraphSpacing;
_minimumLineHeight = other._minimumLineHeight;
_maximumLineHeight = other._maximumLineHeight;
_lineSpacing = other._lineSpacing;
return self;
}
- (void)_initWithDefaults
{
_alignment = CPLeftTextAlignment;
_tabStops = [[[self class] _defaultTabStops] copy];
}
- (void)addTabStop:(CPTextTab)aStop
{
_tabStops.push(aStop);
}
- (id)copy
{
// Since this class is immutable, return self.
// Subclasses (Mutable) will override.
if ([self class] === [CPParagraphStyle class])
return self;
return [[CPParagraphStyle alloc] initWithParagraphStyle:self];
}
var other = [[self class] alloc];
- (id)mutableCopy
{
return [[CPMutableParagraphStyle alloc] initWithParagraphStyle:self];
}
// MARK: - Coding Support
- (id)initWithCoder:(CPCoder)aCoder
{
if (self = [super init])
{
_lineSpacing = [aCoder decodeFloatForKey:@"CPParagraphStyleLineSpacing"];
_paragraphSpacing = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacing"];
_alignment = [aCoder decodeIntForKey:@"CPParagraphStyleAlignment"];
_headIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleHeadIndent"];
_tailIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleTailIndent"];
_firstLineHeadIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleFirstLineHeadIndent"];
_minimumLineHeight = [aCoder decodeFloatForKey:@"CPParagraphStyleMinimumLineHeight"];
_maximumLineHeight = [aCoder decodeFloatForKey:@"CPParagraphStyleMaximumLineHeight"];
_lineBreakMode = [aCoder decodeIntForKey:@"CPParagraphStyleLineBreakMode"];
_baseWritingDirection = [aCoder decodeIntForKey:@"CPParagraphStyleBaseWritingDirection"];
_lineHeightMultiple = [aCoder decodeFloatForKey:@"CPParagraphStyleLineHeightMultiple"];
_paragraphSpacingBefore = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacingBefore"];
_defaultTabInterval = [aCoder decodeFloatForKey:@"CPParagraphStyleDefaultTabInterval"];
_tabStops = [aCoder decodeObjectForKey:@"CPParagraphStyleTabStops"];
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeFloat:_lineSpacing forKey:@"CPParagraphStyleLineSpacing"];
[aCoder encodeFloat:_paragraphSpacing forKey:@"CPParagraphStyleParagraphSpacing"];
[aCoder encodeInt:_alignment forKey:@"CPParagraphStyleAlignment"];
[aCoder encodeFloat:_headIndent forKey:@"CPParagraphStyleHeadIndent"];
[aCoder encodeFloat:_tailIndent forKey:@"CPParagraphStyleTailIndent"];
[aCoder encodeFloat:_firstLineHeadIndent forKey:@"CPParagraphStyleFirstLineHeadIndent"];
[aCoder encodeFloat:_minimumLineHeight forKey:@"CPParagraphStyleMinimumLineHeight"];
[aCoder encodeFloat:_maximumLineHeight forKey:@"CPParagraphStyleMaximumLineHeight"];
[aCoder encodeInt:_lineBreakMode forKey:@"CPParagraphStyleLineBreakMode"];
[aCoder encodeInt:_baseWritingDirection forKey:@"CPParagraphStyleBaseWritingDirection"];
[aCoder encodeFloat:_lineHeightMultiple forKey:@"CPParagraphStyleLineHeightMultiple"];
[aCoder encodeFloat:_paragraphSpacingBefore forKey:@"CPParagraphStyleParagraphSpacingBefore"];
[aCoder encodeFloat:_defaultTabInterval forKey:@"CPParagraphStyleDefaultTabInterval"];
[aCoder encodeObject:_tabStops forKey:@"CPParagraphStyleTabStops"];
}
// MARK: - Equality
- (BOOL)isEqual:(id)other
{
if (self === other) return YES;
if (![other isKindOfClass:[CPParagraphStyle class]]) return NO;
return _lineSpacing === [other lineSpacing] &&
_paragraphSpacing === [other paragraphSpacing] &&
_alignment === [other alignment] &&
_headIndent === [other headIndent] &&
_tailIndent === [other tailIndent] &&
_firstLineHeadIndent === [other firstLineHeadIndent] &&
_lineBreakMode === [other lineBreakMode] &&
[_tabStops isEqualToArray:[other tabStops]];
return [other initWithParagraphStyle:self];
}
@end
// MARK: - CPMutableParagraphStyle Implementation
var CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey",
CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey",
CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey",
CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey",
CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey",
CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey",
CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey",
CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey",
CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey";
@implementation CPMutableParagraphStyle : CPParagraphStyle
{
}
@implementation CPParagraphStyle (CPCoding)
- (id)initWithParagraphStyle:(CPParagraphStyle)other
- (id)initWithCoder:(id)aCoder
{
if (self = [super initWithParagraphStyle:other])
self = [self init];
if (self)
{
// Ensure our tab stops array is mutable in the mutable subclass
_tabStops = [[other tabStops] mutableCopy];
_tabStops = [aCoder decodeObjectForKey:"CPParagraphStyleTabStopsKey"];
_alignment = [aCoder decodeIntForKey:"CPParagraphStyleAlignmentKey"];
_firstLineHeadIndent = [aCoder decodeIntForKey:"CPParagraphStyleFirstLineHeadIndentKey"];
_headIndent = [aCoder decodeIntForKey:"CPParagraphStyleHeadIndentKey"];
_tailIndent = [aCoder decodeIntForKey:"CPParagraphStyleTailIndentKey"];
_paragraphSpacing = [aCoder decodeIntForKey:"CPParagraphStyleParagraphSpacingKey"];
_minimumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMinimumLineHeightKey"];
_maximumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMaximumLineHeightKey"];
_lineSpacing = [aCoder decodeIntForKey:"CPParagraphStyleLineSpacingKey"];
}
return self;
}
- (id)initWithCoder:(CPCoder)aCoder
- (void)encodeWithCoder:(id)aCoder
{
if (self = [super initWithCoder:aCoder])
[aCoder encodeInt:_alignment forKey:"CPParagraphStyleAlignmentKey"];
[aCoder encodeObject:_tabStops forKey:"CPParagraphStyleTabStopsKey"];
[aCoder encodeInt:_firstLineHeadIndent forKey:"CPParagraphStyleFirstLineHeadIndentKey"];
[aCoder encodeInt:_headIndent forKey:"CPParagraphStyleHeadIndentKey"];
[aCoder encodeInt:_tailIndent forKey:"CPParagraphStyleTailIndentKey"];
[aCoder encodeInt:_paragraphSpacing forKey:"CPParagraphStyleParagraphSpacingKey"];
[aCoder encodeInt:_minimumLineHeight forKey:"CPParagraphStyleMinimumLineHeightKey"];
[aCoder encodeInt:_maximumLineHeight forKey:"CPParagraphStyleMaximumLineHeightKey"];
[aCoder encodeInt:_lineSpacing forKey:"CPParagraphStyleLineSpacingKey"];
}
@end
@implementation CPTextTab : CPObject
{
int _type @accessors(property = tabStopType);
double _location @accessors(property = location);
}
- (id)initWithType:(CPTabStopType) aType location:(double) aLocation
{
if ([self = [super init]])
{
_tabStops = [_tabStops mutableCopy];
_type = aType;
_location = aLocation;
}
return self;
}
- (void)setLineSpacing:(float)aLineSpacing
{
_lineSpacing = aLineSpacing;
}
- (void)setParagraphSpacing:(float)aParagraphSpacing
{
_paragraphSpacing = aParagraphSpacing;
}
- (void)setAlignment:(CPTextAlignment)anAlignment
{
_alignment = anAlignment;
}
- (void)setHeadIndent:(float)aHeadIndent
{
_headIndent = aHeadIndent;
}
- (void)setTailIndent:(float)aTailIndent
{
_tailIndent = aTailIndent;
}
- (void)setFirstLineHeadIndent:(float)aFirstLineHeadIndent
{
_firstLineHeadIndent = aFirstLineHeadIndent;
}
- (void)setMinimumLineHeight:(float)aMinimumLineHeight
{
_minimumLineHeight = aMinimumLineHeight;
}
- (void)setMaximumLineHeight:(float)aMaximumLineHeight
{
_maximumLineHeight = aMaximumLineHeight;
}
- (void)setLineBreakMode:(CPLineBreakMode)aLineBreakMode
{
_lineBreakMode = aLineBreakMode;
}
- (void)setBaseWritingDirection:(CPWritingDirection)aBaseWritingDirection
{
_baseWritingDirection = aBaseWritingDirection;
}
- (void)setLineHeightMultiple:(float)aLineHeightMultiple
{
_lineHeightMultiple = aLineHeightMultiple;
}
- (void)setParagraphSpacingBefore:(float)aParagraphSpacingBefore
{
_paragraphSpacingBefore = aParagraphSpacingBefore;
}
- (void)setDefaultTabInterval:(float)aDefaultTabInterval
{
_defaultTabInterval = aDefaultTabInterval;
}
- (void)addTabStop:(CPTextTab)aTabStop
{
[_tabStops addObject:aTabStop];
}
- (void)removeTabStop:(CPTextTab)aTabStop
{
[_tabStops removeObject:aTabStop];
}
- (void)setTabStops:(CPArray)newTabStops
{
if (_tabStops === newTabStops) return;
_tabStops = [newTabStops mutableCopy];
}
- (id)copyWithZone:(CPZone)aZone
{
// Return an immutable copy
return [[CPParagraphStyle alloc] initWithParagraphStyle:self];
}
@end
var CPTextTabTypeKey = @"CPTextTabTypeKey",
CPTextTabLocationKey = @"CPTextTabLocationKey";
@implementation CPTextTab (CPCoding)
- (id)initWithCoder:(id)aCoder
{
self = [self init];
if (self)
{
_type = [aCoder decodeIntForKey:"CPTextTabTypeKey"];
_location = [aCoder decodeDoubleForKey:"CPTextTabLocationKey"];
}
return self;
}
- (void)encodeWithCoder:(id)aCoder
{
[aCoder encodeInt:_type forKey:"CPTextTabTypeKey"];
[aCoder encodeDouble:_location forKey:"CPTextTabLocationKey"];
}
@end
-631
View File
@@ -1,631 +0,0 @@
/*
* CPRulerView.j
* AppKit
*
* Created by Daniel Boehringer on 11/01/2014
* Copyright Daniel Boehringer 2014.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import "CPView.j"
@import "CPTextField.j"
@import "CPColor.j"
@import "CPFont.j"
@import "CPMenu.j"
@import "CPMenuItem.j"
@typedef CPRulerOrientation
CPHorizontalRuler = 0;
CPVerticalRuler = 1;
CPRulerOrientationHorizontal = 0;
CPRulerOrientationVertical = 1;
@class CPRulerView;
@class CPScrollView;
@class CPTextTab;
// MARK: - CPRulerMarker
@implementation CPRulerMarker : CPView
{
CPRulerView _rulerView @accessors(property=rulerView);
float _imageValue @accessors(property=imageValue);
id _representedObject @accessors(property=representedObject);
CPTextField _label;
CPView _customHandleView;
}
- (id)initWithRulerView:(CPRulerView)aRulerView markerLocation:(float)aLocation imageValue:(float)anImageValue representedObject:(id)anObject
{
if (self = [super initWithFrame:CGRectMake(0, 0, 12, 12)])
{
_rulerView = aRulerView;
_imageValue = anImageValue;
_representedObject = anObject;
// CRITICAL: Subviews must not intercept mouse events so CPRulerView receives all mouseDragged: events
[self setHitTests:NO];
_label = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 12, 12)];
[_label setFont:[CPFont systemFontOfSize:10.0]];
[_label setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
[_label setAlignment:CPCenterTextAlignment];
[_label setHitTests:NO];
[self addSubview:_label];
_customHandleView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[_customHandleView setHitTests:NO];
[self addSubview:_customHandleView];
[self updateMarkerIcon];
}
return self;
}
- (CPTextField)label
{
return _label;
}
- (void)setRepresentedObject:(id)anObject
{
_representedObject = anObject;
[self updateMarkerIcon];
}
- (void)setFrame:(CGRect)aFrame
{
[super setFrame:aFrame];
[self updateMarkerIcon];
}
- (void)updateMarkerIcon
{
var isIndentMarker = (_representedObject === @"CPFirstLineIndent" || _representedObject === @"CPHeadIndent");
if (isIndentMarker)
{
[_label setHidden:YES];
[_customHandleView setHidden:NO];
var frame = [self bounds];
[_customHandleView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[[_customHandleView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
var isFirstLine = (_representedObject === @"CPFirstLineIndent");
[_customHandleView setBackgroundColor:[CPColor colorWithWhite:0.45 alpha:1.0]];
var innerView = [[CPView alloc] initWithFrame:CGRectMake(1.0, 1.0, Math.max(1.0, frame.size.width - 2.0), Math.max(1.0, frame.size.height - 2.0))];
[innerView setHitTests:NO];
[innerView setBackgroundColor:isFirstLine ? [CPColor colorWithWhite:0.95 alpha:1.0] : [CPColor colorWithWhite:0.80 alpha:1.0]];
[_customHandleView addSubview:innerView];
var gripLine = [[CPView alloc] initWithFrame:CGRectMake(Math.floor(frame.size.width / 2.0) - 1.0, 1.0, 1.0, Math.max(1.0, frame.size.height - 3.0))];
[gripLine setHitTests:NO];
[gripLine setBackgroundColor:[CPColor colorWithWhite:0.5 alpha:1.0]];
[innerView addSubview:gripLine];
}
else
{
[_label setHidden:NO];
[_customHandleView setHidden:YES];
[_label setFrame:[self bounds]];
if ([_representedObject isKindOfClass:[CPTextTab class]])
{
var align = [_representedObject alignment];
if (align === CPLeftTextAlignment)
[_label setStringValue:@"▶"];
else if (align === CPCenterTextAlignment)
[_label setStringValue:@"▼"];
else if (align === CPRightTextAlignment)
[_label setStringValue:@"◀"];
}
else
{
[_label setStringValue:@"▲"];
}
}
}
- (CPMenu)menuForEvent:(CPEvent)anEvent
{
var menu = [[CPMenu alloc] initWithTitle:@"Marker Context Menu"];
if ([_representedObject isKindOfClass:[CPTextTab class]])
{
var itemLeft = [menu addItemWithTitle:@"Left Tab Stop" action:@selector(changeTypeToLeft:) keyEquivalent:@""],
itemCenter = [menu addItemWithTitle:@"Center Tab Stop" action:@selector(changeTypeToCenter:) keyEquivalent:@""],
itemRight = [menu addItemWithTitle:@"Right Tab Stop" action:@selector(changeTypeToRight:) keyEquivalent:@""];
[itemLeft setTarget:self];
[itemCenter setTarget:self];
[itemRight setTarget:self];
var align = [_representedObject alignment];
if (align === CPLeftTextAlignment) [itemLeft setState:CPOnState];
else if (align === CPCenterTextAlignment) [itemCenter setState:CPOnState];
else if (align === CPRightTextAlignment) [itemRight setState:CPOnState];
[menu addItem:[CPMenuItem separatorItem]];
}
var deleteTitle = @"Delete Tab Stop";
if (_representedObject === @"CPFirstLineIndent")
deleteTitle = @"Reset 1st line indentation";
else if (_representedObject === @"CPHeadIndent")
deleteTitle = @"Reset head indentation";
var itemDelete = [menu addItemWithTitle:deleteTitle action:@selector(deleteMarker:) keyEquivalent:@""];
[itemDelete setTarget:self];
return menu;
}
- (void)changeTypeToLeft:(id)sender { [self _changeAlignment:CPLeftTextAlignment]; }
- (void)changeTypeToCenter:(id)sender { [self _changeAlignment:CPCenterTextAlignment]; }
- (void)changeTypeToRight:(id)sender { [self _changeAlignment:CPRightTextAlignment]; }
- (void)_changeAlignment:(CPTextAlignment)alignment
{
if (![_representedObject isKindOfClass:[CPTextTab class]])
return;
var oldTab = _representedObject,
newTab = [[CPTextTab alloc] initWithType:alignment location:_imageValue];
[self setRepresentedObject:newTab];
var client = [_rulerView clientView];
if (client && [client respondsToSelector:@selector(rulerView:didUpdateMarker:oldTab:)])
[client rulerView:_rulerView didUpdateMarker:self oldTab:oldTab];
}
- (void)deleteMarker:(id)sender
{
var client = [_rulerView clientView];
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
[client rulerView:_rulerView didRemoveMarker:self];
[_rulerView removeMarker:self];
}
@end
// MARK: - CPRulerView
@implementation CPRulerView : CPView
{
CPScrollView _scrollView @accessors(property=scrollView);
CPRulerOrientation _orientation @accessors(property=orientation);
CPView _clientView;
float _ruleThickness @accessors(property=ruleThickness);
float _reservedThicknessForMarkers;
CPArray _markers;
CPRulerMarker _draggingMarker @accessors(getter=draggingMarker);
CGPoint _dragStartPoint;
float _dragStartLocation;
}
- (id)initWithScrollView:(CPScrollView)aScrollView orientation:(CPRulerOrientation)anOrientation
{
if (self = [super initWithFrame:CGRectMakeZero()])
{
_scrollView = aScrollView;
_orientation = anOrientation;
_clientView = [aScrollView documentView];
_ruleThickness = (anOrientation === CPHorizontalRuler) ? 16.0 : 24.0;
_reservedThicknessForMarkers = 0.0;
_markers = [];
[self setBackgroundColor:[CPColor colorWithWhite:0.96 alpha:1.0]];
}
return self;
}
- (void)setFrame:(CGRect)aFrame
{
[super setFrame:aFrame];
[self updateRuler];
}
- (CPView)clientView
{
return _clientView || [_scrollView documentView];
}
- (void)setClientView:(CPView)aView
{
_clientView = aView;
}
- (void)addMarker:(CPRulerMarker)aMarker
{
if ([_markers containsObject:aMarker])
return;
[_markers addObject:aMarker];
[self addSubview:aMarker];
[self _positionMarker:aMarker];
}
- (void)removeMarker:(CPRulerMarker)aMarker
{
[_markers removeObject:aMarker];
[aMarker removeFromSuperview];
}
- (void)setMarkers:(CPArray)newMarkers
{
for (var i = 0; i < [_markers count]; i++)
[[_markers objectAtIndex:i] removeFromSuperview];
_markers = [newMarkers mutableCopy];
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
[self addSubview:marker];
[self _positionMarker:marker];
}
[self updateRuler];
}
- (CPRulerMarker)_markerAtPoint:(CGPoint)aPoint
{
// Search in reverse order so indent paddles (added last) are prioritized over overlapping 0-location tabs
for (var i = [_markers count] - 1; i >= 0; i--)
{
var marker = [_markers objectAtIndex:i],
frame = [marker frame];
// Expanded hit-box by 3px on all sides for easy grabbing
var hitFrame = CGRectMake(frame.origin.x - 3.0, frame.origin.y - 2.0, frame.size.width + 6.0, frame.size.height + 4.0);
if (CGRectContainsPoint(hitFrame, aPoint))
return marker;
}
return nil;
}
- (void)_positionMarker:(CPRulerMarker)aMarker
{
if (!_scrollView)
return;
var clipView = [_scrollView contentView],
scrollPoint = [clipView bounds].origin,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]),
markerLocation = [aMarker imageValue],
rep = [aMarker representedObject];
if (isHorizontal)
{
var x = markerLocation - scrollPoint.x - 6.0,
y = rulerHeight - 11.0,
w = 12.0,
h = 12.0;
if (rep === @"CPFirstLineIndent")
{
y = 0.0;
h = Math.floor(rulerHeight / 2.0);
}
else if (rep === @"CPHeadIndent")
{
y = Math.floor(rulerHeight / 2.0);
h = rulerHeight - y - 1.0;
}
else
{
if (x < -6.0) x = -6.0;
else if (x + 12.0 > rulerWidth) x = rulerWidth - 12.0;
}
[aMarker setFrame:CGRectMake(x, y, w, h)];
}
else
{
var x = rulerWidth - 11.0,
y = markerLocation - scrollPoint.y - 6.0;
if (y < 0.0) y = 0.0;
else if (y + 12.0 > rulerHeight) y = rulerHeight - 12.0;
[aMarker setFrame:CGRectMake(x, y, 12.0, 12.0)];
}
}
// MARK: - Interaction Handlers
- (void)mouseDown:(CPEvent)anEvent
{
var locationInWindow = [anEvent locationInWindow],
localPoint = [self convertPoint:locationInWindow fromView:nil],
clipView = [_scrollView contentView],
scrollPoint = [clipView bounds].origin,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
var rulerLocation = isHorizontal ? (localPoint.x + scrollPoint.x) : (localPoint.y + scrollPoint.y);
var clickedMarker = [self _markerAtPoint:localPoint];
if (clickedMarker)
{
_draggingMarker = clickedMarker;
_dragStartPoint = localPoint;
_dragStartLocation = [_draggingMarker imageValue];
}
else
{
// Allow client view to constrain initial placement
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:willAddMarker:atLocation:)])
rulerLocation = [client rulerView:self willAddMarker:nil atLocation:rulerLocation];
var newMarker = [[CPRulerMarker alloc] initWithRulerView:self
markerLocation:rulerLocation
imageValue:rulerLocation
representedObject:nil];
[self addMarker:newMarker];
if (client && [client respondsToSelector:@selector(rulerView:didAddMarker:)])
[client rulerView:self didAddMarker:newMarker];
_draggingMarker = newMarker;
_dragStartPoint = localPoint;
_dragStartLocation = rulerLocation;
}
}
- (void)mouseDragged:(CPEvent)anEvent
{
if (!_draggingMarker)
return;
var locationInWindow = [anEvent locationInWindow],
localPoint = [self convertPoint:locationInWindow fromView:nil],
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
var delta = isHorizontal ? (localPoint.x - _dragStartPoint.x) : (localPoint.y - _dragStartPoint.y),
newLocation = Math.max(0.0, _dragStartLocation + delta);
// Constrain marker location to last possible position
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:willMoveMarker:toLocation:)])
newLocation = [client rulerView:self willMoveMarker:_draggingMarker toLocation:newLocation];
[_draggingMarker setImageValue:newLocation];
[self _positionMarker:_draggingMarker];
var rep = [_draggingMarker representedObject],
isIndent = (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent");
var draggedOff = !isIndent && (isHorizontal
? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15));
if (draggedOff)
{
[_draggingMarker setAlphaValue:0.4];
[[_draggingMarker label] setTextColor:[CPColor grayColor]];
}
else
{
[_draggingMarker setAlphaValue:1.0];
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
}
if (client && [client respondsToSelector:@selector(rulerView:didMoveMarker:)])
[client rulerView:self didMoveMarker:_draggingMarker];
}
- (void)mouseUp:(CPEvent)anEvent
{
if (!_draggingMarker)
return;
var localPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil],
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
rep = [_draggingMarker representedObject],
isIndent = (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent");
var draggedOff = !isIndent && (isHorizontal
? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15));
if (draggedOff)
{
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
[client rulerView:self didRemoveMarker:_draggingMarker];
[self removeMarker:_draggingMarker];
}
else
{
[_draggingMarker setAlphaValue:1.0];
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
}
_draggingMarker = nil;
[self updateRuler];
}
// MARK: - Layout Builder
- (void)updateRuler
{
[self setSubviews:@[]];
if (!_scrollView)
return;
var clipView = [_scrollView contentView],
scrollBounds = [clipView bounds],
scrollPoint = scrollBounds.origin,
visibleSize = scrollBounds.size,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
if (isHorizontal)
{
var start = Math.floor(scrollPoint.x / 10) * 10,
end = scrollPoint.x + visibleSize.width,
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]);
var bottomBorder = [[CPView alloc] initWithFrame:CGRectMake(0, rulerHeight - 1, rulerWidth, 1)];
[bottomBorder setHitTests:NO];
[bottomBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
[self addSubview:bottomBorder];
var firstLineMarker = nil,
headMarker = nil;
for (var i = 0; i < [_markers count]; i++)
{
var m = [_markers objectAtIndex:i];
if ([m representedObject] === @"CPFirstLineIndent")
firstLineMarker = m;
else if ([m representedObject] === @"CPHeadIndent")
headMarker = m;
}
var halfHeight = Math.floor(rulerHeight / 2.0);
if (firstLineMarker)
{
var firstLineX = [firstLineMarker imageValue] - scrollPoint.x;
if (firstLineX > 0)
{
var firstLineBg = [[CPView alloc] initWithFrame:CGRectMake(0, 0, firstLineX, halfHeight)];
[firstLineBg setHitTests:NO];
[firstLineBg setBackgroundColor:[CPColor colorWithWhite:0.93 alpha:1.0]];
[self addSubview:firstLineBg];
}
}
if (headMarker)
{
var headX = [headMarker imageValue] - scrollPoint.x;
if (headX > 0)
{
var headBg = [[CPView alloc] initWithFrame:CGRectMake(0, halfHeight, headX, rulerHeight - halfHeight - 1.0)];
[headBg setHitTests:NO];
[headBg setBackgroundColor:[CPColor colorWithWhite:0.86 alpha:1.0]];
[self addSubview:headBg];
}
}
for (var val = start; val <= end; val += 10)
{
if (val < 0) continue;
var screenX = val - scrollPoint.x,
isMajor = (val % 50 === 0),
tickHeight = isMajor ? 8.0 : 4.0,
tickY = rulerHeight - tickHeight - 1.0;
var tick = [[CPView alloc] initWithFrame:CGRectMake(screenX, tickY, 1.0, tickHeight)];
[tick setHitTests:NO];
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
[self addSubview:tick];
if (isMajor)
{
var labelX = screenX - 20.0,
alignment = CPCenterTextAlignment;
if (labelX < 0.0) { labelX = Math.max(0.0, screenX); alignment = CPLeftTextAlignment; }
else if (labelX + 40.0 > rulerWidth) { labelX = rulerWidth - 40.0; alignment = CPRightTextAlignment; }
var label = [[CPTextField alloc] initWithFrame:CGRectMake(labelX, 1.0, 40.0, 12.0)];
[label setHitTests:NO];
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
[label setFont:[CPFont systemFontOfSize:8.0]];
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
[label setAlignment:alignment];
[self addSubview:label];
}
}
}
else
{
// Vertical Ruler
var start = Math.floor(scrollPoint.y / 10) * 10,
end = scrollPoint.y + visibleSize.height,
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]);
// Draw solid vertical right border (pure DOM)
var rightBorder = [[CPView alloc] initWithFrame:CGRectMake(rulerWidth - 1, 0, 1, rulerHeight)];
[rightBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
[self addSubview:rightBorder];
for (var val = start; val <= end; val += 10)
{
if (val < 0) continue;
var screenY = val - scrollPoint.y,
isMajor = (val % 50 === 0),
tickWidth = isMajor ? 8.0 : 4.0,
tickX = rulerWidth - tickWidth - 1.0;
// Tick mark CSS line view
var tick = [[CPView alloc] initWithFrame:CGRectMake(tickX, screenY, tickWidth, 1.0)];
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
[self addSubview:tick];
// Unit label
if (isMajor)
{
var labelY = screenY - 6.0;
// Adjust label frame if it lands near top/bottom bounds
if (labelY < 0.0)
labelY = 0.0;
else if (labelY + 12.0 > rulerHeight)
labelY = rulerHeight - 12.0;
var label = [[CPTextField alloc] initWithFrame:CGRectMake(1.0, labelY, rulerWidth - 12.0, 12.0)];
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
[label setFont:[CPFont systemFontOfSize:8.0]];
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
[label setAlignment:CPRightTextAlignment];
[self addSubview:label];
}
}
}
// Add back existing markers without re-creating them
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
[self addSubview:marker];
[self _positionMarker:marker];
}
}
@end
+16 -49
View File
@@ -89,8 +89,8 @@ CPLineMovesUp = 4;
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)initWithContainerSize:(CGSize)aSize
{
@@ -118,8 +118,8 @@ CPLineMovesUp = 4;
[_layoutManager addTextContainer:self];
}
// MARK: -
// MARK: Setter methods
#pragma mark -
#pragma mark Setter methods
- (void)setContainerSize:(CGSize)someSize
{
@@ -148,58 +148,27 @@ CPLineMovesUp = 4;
- (void)setWidthTracksTextView:(BOOL)flag
{
if (_widthTracksTextView === flag)
return;
_widthTracksTextView = flag;
[self _updateFrameObserver];
}
[_textView setPostsFrameChangedNotifications:flag];
// Controls whether the receiver adjusts the height of its bounding rectangle when its text view is resized.
- (BOOL)heightTracksTextView
{
return _heightTracksTextView;
}
- (void)setHeightTracksTextView:(BOOL)flag
{
if (_heightTracksTextView === flag)
return;
_heightTracksTextView = flag;
[self _updateFrameObserver];
}
- (void)_updateFrameObserver
{
if (_textView)
if (flag && _textView)
{
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewFrameChanged:)
name:CPViewFrameDidChangeNotification
object:_textView];
}
else
{
[[CPNotificationCenter defaultCenter] removeObserver:self
name:CPViewFrameDidChangeNotification
object:_textView];
var flag = _widthTracksTextView || _heightTracksTextView;
[_textView setPostsFrameChangedNotifications:flag];
if (flag)
{
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewFrameChanged:)
name:CPViewFrameDidChangeNotification
object:_textView];
}
}
}
- (void)textViewFrameChanged:(CPNotification)aNotification
{
var newSize = CGSizeMake(_size.width, _size.height);
if (_widthTracksTextView)
newSize.width = [_textView frame].size.width;
if (_heightTracksTextView)
newSize.height = [_textView frame].size.height;
var newSize = CGSizeMake([_textView frame].size.width, _size.height);
[self setContainerSize:newSize];
}
@@ -208,9 +177,7 @@ CPLineMovesUp = 4;
{
if (_textView)
{
[[CPNotificationCenter defaultCenter] removeObserver:self
name:CPViewFrameDidChangeNotification
object:_textView];
[self setWidthTracksTextView:NO]; // We only support width
[_textView setTextContainer:nil];
}
@@ -218,7 +185,7 @@ CPLineMovesUp = 4;
if (_textView)
{
[self _updateFrameObserver];
[self setWidthTracksTextView:_widthTracksTextView]; // We only support width
[_textView setTextContainer:self];
}
+8 -8
View File
@@ -70,8 +70,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
// MARK: -
// MARK: Init methods
#pragma mark -
#pragma mark Init methods
- (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
{
@@ -99,8 +99,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
// MARK: -
// MARK: Delegate methods
#pragma mark -
#pragma mark Delegate methods
- (void)setDelegate:(id <CPTextStorageDelegate>)aDelegate
{
@@ -121,8 +121,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
// MARK: -
// MARK: Layout manager methods
#pragma mark -
#pragma mark Layout manager methods
- (void)addLayoutManager:(CPLayoutManager)aManager
{
@@ -148,8 +148,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
// MARK: -
// MARK: Editing methods
#pragma mark -
#pragma mark Editing methods
- (void)processEditing
{
File diff suppressed because it is too large Load Diff
+41 -185
View File
@@ -6,7 +6,7 @@
* All modifications copyright Daniel Boehringer 2013.
* Extensive code formatting and review by Andrew Hankinson
* Based on original work by
* Created by Emmanuel Maillard on 27/02/2010.
* Emmanuel Maillard on 27/02/2010.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
@@ -30,9 +30,6 @@
@import "CPTextStorage.j"
@import "CPFont.j"
@global CPBaselineOffsetAttributeName
@global CPSuperscriptAttributeName
// forward declare these classes for type matching
@class CPLayoutManager
@class CPTextContainer
@@ -54,8 +51,8 @@ var CPSystemTypesetterFactory,
@implementation CPTypesetter : CPObject
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
+ (void)initialize
{
@@ -122,8 +119,8 @@ var CPSystemTypesetterFactory,
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
+ (id)sharedInstance
{
@@ -138,30 +135,31 @@ var CPSystemTypesetterFactory,
return [_layoutManager textContainers];
}
// Retrieves correct CPTextTab stop accounting for custom stops and default intervals
- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction
{
var tabStops = [_currentParagraph tabStops],
defaultInterval = [_currentParagraph defaultTabInterval] || 28.0;
var tabStops = [_currentParagraph tabStops];
var l = tabStops ? [tabStops count] : 0;
if (!tabStops)
tabStops = [CPParagraphStyle _defaultTabStops];
// 1. If custom tab stops exist ahead of current position, use the first one encountered
if (l > 0)
var l = tabStops.length;
if (aWidth > tabStops[l - 1]._location)
return nil;
for (var i = l - 1; i >= 0; i--)
{
for (var i = 0; i < l; i++)
if (aWidth > tabStops[i]._location)
{
var tab = [tabStops objectAtIndex:i];
if ([tab location] > aWidth)
return tab;
if (i + 1 < l)
return tabStops[i + 1];
}
}
// 2. Otherwise (or when all custom tab stops are behind the text), advance to the next default interval
var nextLocation = (Math.floor(aWidth / defaultInterval) + 1) * defaultInterval;
if (i === -1)
return tabStops[0];
return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextLocation];
return nil;
}
- (BOOL)_flushRange:(CPRange)lineRange
@@ -201,7 +199,7 @@ var CPSystemTypesetterFactory,
[_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange];
[_layoutManager _setAdvancements:advancements forGlyphRange:lineRange];
// fix the _lineFragments when fontsizes differ
//fix the _lineFragments when fontsizes differ
var l = _lineFragments.length;
for (var i = 0 ; i < l ; i++)
@@ -247,7 +245,7 @@ var CPSystemTypesetterFactory,
isTabStop = NO,
isAttachment = NO,
isWordWrapped = NO,
numberOfGlyphs = [_textStorage length],
numberOfGlyphs= [_textStorage length],
leading,
numLines = 0,
theString = [_textStorage string],
@@ -265,14 +263,6 @@ var CPSystemTypesetterFactory,
currentParagraphMaximumLineHeight,
currentParagraphLineSpacing;
// Track physical line starts to prevent overwriting lineOrigin.x in tab segments
var isStartOfPhysicalLine = YES;
// Track paragraph indents and margins
var isFirstLineOfLayout = YES,
isFirstLineOfParagraph = YES,
rightMargin = containerSizeWidth;
if (glyphIndex > 0)
lineOrigin = CGPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin);
else if ([_layoutManager extraLineFragmentTextContainer])
@@ -289,7 +279,7 @@ var CPSystemTypesetterFactory,
for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++)
{
// check whether there is any change in the attributes from here on
// check whether there any change in the attributes from here on
if (!CPLocationInRange(glyphIndex, _attributesRange))
{
_currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange];
@@ -299,85 +289,12 @@ var CPSystemTypesetterFactory,
currentParagraphMaximumLineHeight = [_currentParagraph maximumLineHeight];
currentParagraphLineSpacing = [_currentParagraph lineSpacing];
// Recalculate right margin on paragraph style change
var tailIndent = [_currentParagraph tailIndent];
if (tailIndent > 0.0)
rightMargin = tailIndent;
else if (tailIndent < 0.0)
rightMargin = containerSizeWidth + tailIndent;
else
rightMargin = containerSizeWidth;
// If we are at the start of a physical line, we update lineOrigin.x
if (isStartOfPhysicalLine)
{
if (glyphIndex > 0)
{
var prevChar = theString.charCodeAt(glyphIndex - 1);
isFirstLineOfParagraph = (prevChar === 10 || prevChar === 13);
}
else
{
isFirstLineOfParagraph = YES;
}
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
isFirstLineOfLayout = NO;
}
// Handle the layout's very first line indentation
if (isFirstLineOfLayout)
{
if (glyphIndex > 0)
{
var prevChar = theString.charCodeAt(glyphIndex - 1);
isFirstLineOfParagraph = (prevChar === 10 || prevChar === 13);
}
else
{
isFirstLineOfParagraph = YES;
}
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
isFirstLineOfLayout = NO;
}
if (!currentFont)
currentFont = [_textStorage font] || [CPFont systemFontOfSize:12.0];
// Safely retrieve and validate CPBaselineOffsetAttributeName
var baselineOffset = [_currentAttributes objectForKey:CPBaselineOffsetAttributeName];
if (baselineOffset === nil || baselineOffset === undefined || typeof baselineOffset !== "number")
baselineOffset = 0.0;
// Safely retrieve and validate CPSuperscriptAttributeName
var superscript = [_currentAttributes objectForKey:CPSuperscriptAttributeName];
if (superscript === nil || superscript === undefined || typeof superscript !== "number")
superscript = 0;
if (superscript !== 0)
{
var size = [currentFont size],
scaledSize = size * 0.65,
fontName = [currentFont familyName],
isBold = [currentFont isBold],
isItalic = [currentFont isItalic];
currentFont = [CPFont _fontWithName:fontName size:scaledSize bold:isBold italic:isItalic];
if (baselineOffset === 0.0)
{
if (superscript > 0)
baselineOffset = size * 0.35;
else
baselineOffset = -size * 0.15;
}
}
var fontAscent = [currentFont ascender] || 0.0,
fontDescent = [currentFont descender] || 0.0;
ascent = fontAscent + baselineOffset;
descent = fontDescent + baselineOffset;
leading = (fontAscent - fontDescent) * 0.2; // FAKE leading
ascent = [currentFont ascender];
descent = [currentFont descender];
leading = (ascent - descent) * 0.2; // FAKE leading
currentFontLineHeight = ascent - descent + leading;
@@ -390,26 +307,19 @@ var CPSystemTypesetterFactory,
}
// Clean bounds logic to prevent NaN and layout calculation overhead
var currentAscent = (ascent === undefined || isNaN(ascent)) ? 0.0 : ascent,
currentLineHeight = (currentFontLineHeight === undefined || isNaN(currentFontLineHeight)) ? 12.0 : currentFontLineHeight;
if (currentFontLineHeight > _lineHeight)
_lineHeight = currentFontLineHeight;
if (currentLineHeight > _lineHeight)
_lineHeight = currentLineHeight;
if (currentAscent > _lineBase)
_lineBase = currentAscent;
if (ascent > _lineBase)
_lineBase = ascent;
lineRange.length++;
measuringRange.length++;
// We are processing characters, so we are no longer at the start of a physical line
isStartOfPhysicalLine = NO;
var currentCharCode = theString.charCodeAt(glyphIndex),
var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons
rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor;
switch (currentCharCode)
switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char.
{
case CPAttachmentCharacter:
{
@@ -441,70 +351,20 @@ var CPSystemTypesetterFactory,
}
case 9: // '\t'
{
// Measure against the actual text position before the tab stop
var nextTab = [self textTabForWidth:prevRangeWidth + lineOrigin.x writingDirection:0];
var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0];
isTabStop = YES;
if (nextTab)
{
// Look-ahead to measure the width of the incoming text segment for alignment
var nextSegmentWidth = 0.0,
tempIndex = glyphIndex + 1,
segmentString = "";
while (tempIndex < numberOfGlyphs)
{
var nextCharCode = theString.charCodeAt(tempIndex);
if (nextCharCode === 9 || nextCharCode === 10 || nextCharCode === 13)
break;
segmentString += theString.charAt(tempIndex);
tempIndex++;
}
if (segmentString.length > 0)
nextSegmentWidth = [segmentString sizeWithFont:currentFont inWidth:NULL].width;
var tabLocation = [nextTab location],
tabAlignment = [nextTab alignment];
// Mathematically offset the tab character's right boundary
if (tabAlignment === CPCenterTextAlignment)
{
rangeWidth = (tabLocation - nextSegmentWidth / 2.0) - lineOrigin.x;
}
else if (tabAlignment === CPRightTextAlignment)
{
rangeWidth = (tabLocation - nextSegmentWidth) - lineOrigin.x;
}
else // Left align tab stop
{
rangeWidth = tabLocation - lineOrigin.x;
}
// Enforce a minimum safety spacer width to avoid character overlapping
var minRangeWidth = prevRangeWidth + 5.0;
if (rangeWidth < minRangeWidth)
rangeWidth = minRangeWidth;
}
rangeWidth = nextTab._location - lineOrigin.x;
else
{
rangeWidth = prevRangeWidth + 28.0; // standard fallback spacer
}
break;
}
rangeWidth += 28; //FIXME
} // fallthrough intentional
case 32: // ' '
wrapRange = CPMakeRangeCopy(lineRange);
wrapWidth = rangeWidth;
wrapRange._height = _lineHeight;
wrapRange._base = _lineBase;
if (theString.charCodeAt(glyphIndex + 1) !== 32)
{
currentAnchor = rangeWidth;
measuringRange = CPMakeRange(glyphIndex + 1, 0);
}
break;
case 10:
@@ -515,8 +375,7 @@ var CPSystemTypesetterFactory,
advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent});
prevRangeWidth = _lineWidth = rangeWidth;
// Wrap lines against the tail indent (rightMargin) instead of container boundaries
if (lineOrigin.x + rangeWidth > rightMargin)
if (lineOrigin.x + rangeWidth > containerSizeWidth)
{
if (wrapWidth)
{
@@ -528,7 +387,7 @@ var CPSystemTypesetterFactory,
isNewline = YES;
isWordWrapped = YES;
glyphIndex = CPMaxRange(lineRange) - 1;
glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character
}
if (isNewline || isTabStop || isAttachment)
@@ -560,15 +419,12 @@ var CPSystemTypesetterFactory,
containerSizeHeight = containerSize.height;
}
isFirstLineOfParagraph = !isWordWrapped;
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
lineOrigin.x = 0;
numLines++;
isNewline = NO;
_lineFragments = [];
_lineHeight = 0;
_lineBase = 0;
isStartOfPhysicalLine = YES;
_lineBase = ascent;
}
isTabStop = NO;
@@ -585,7 +441,7 @@ var CPSystemTypesetterFactory,
}
}
// Flush remaining characters
// this is to "flush" the remaining characters
if (lineRange.length)
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO];
File diff suppressed because it is too large Load Diff
+84 -266
View File
@@ -1,12 +1,12 @@
/*
* _CPRTFProducer.j
*
* Serialize CPAttributedString to a RTF String
*
* Copyright (C) 2014 Daniel Boehringer
* This file is based on the RTFProducer from GNUStep
* (which I co-authored with Fred Kiefer in 1999)
*
_CPRTFProducer.j
Serialize CPAttributedString to a RTF String
Copyright (C) 2014 Daniel Boehringer
This file is based on the RTFProducer from GNUStep
(which i co-authored with Fred Kiefer in 1999)
* 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
@@ -27,7 +27,6 @@
@import "CPColor.j"
@import "CPGraphics.j"
@import "CPFontManager.j"
@import "_CPTableTextAttachment.j"
@global CPForegroundColorAttributeName
@global CPBackgroundColorAttributeName
@@ -44,11 +43,6 @@
@global CPJustifiedTextAlignment
@global CPNaturalTextAlignment
@global CPLeftTabStopType
@global CPRightTabStopType
@global CPCenterTabStopType
@global CPDecimalTabStopType
var PAPERSIZE = @"PaperSize",
LEFTMARGIN = @"LeftMargin",
RIGHTMARGIN = @"RightMargin",
@@ -70,8 +64,8 @@ function _points2twips(a) { return (a) * 20.0; }
CPColor ulColor;
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
+ (CPString)produceRTF:(CPAttributedString)aText documentAttributes:(CPDictionary)dict
@@ -82,14 +76,19 @@ function _points2twips(a) { return (a) * 20.0; }
}
// MARK: -
// MARK: init methods
#pragma mark -
#pragma mark init methods
- (id)init
{
if (self = [super init])
{
// maintain a dictionary for the used colours
// (for rtf-header generation)
colorDict = [CPMutableDictionary new];
//maintain a dictionary for the used fonts
//(for rtf-header generation)
fontDict = [CPMutableDictionary new];
fgColor = [CPColor blackColor];
@@ -99,6 +98,7 @@ function _points2twips(a) { return (a) * 20.0; }
return self;
}
// private stuff follows
- (CPString)fontTable
{
if (![fontDict count])
@@ -277,7 +277,7 @@ function _points2twips(a) { return (a) * 20.0; }
if (!num)
[colorDict setObject:num = [CPNumber numberWithInt:[colorDict count] + 1]
forKey:[color cssString]];
forKey:[color cssString]];
return [num intValue];
}
@@ -313,6 +313,7 @@ function _points2twips(a) { return (a) * 20.0; }
break;
}
// write first line indent and left indent
var twips = _points2twips([paraStyle firstLineHeadIndent]);
if (twips != 0.0)
@@ -350,29 +351,26 @@ function _points2twips(a) { return (a) * 20.0; }
while ((tab = [enumerator nextObject]))
{
var tabType = [tab respondsToSelector:@selector(tabStopType)] ? [tab tabStopType] : nil;
if (tabType === nil && [tab respondsToSelector:@selector(alignment)])
tabType = [tab alignment];
switch (tabType)
switch ([tab tabStopType])
{
case CPLeftTabStopType:
case CPLeftTextAlignment:
// no tabkind emission needed
break;
case CPRightTabStopType:
case CPRightTextAlignment:
/* case NSRightTabStopType:
headerString += @"\\tqr";
break;
case CPCenterTabStopType:
case CPCenterTextAlignment:
headerString += @"\\tqc";
break;
case CPDecimalTabStopType:
break;
case NSCenterTabStopType:
headerString += @"\\tqc";
break;
case NSDecimalTabStopType:
headerString += @"\\tqdec";
break;
}
break;
default:
NSLog(@"Unknown tab stop type.");
*/
}
headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])];
headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])];
}
return headerString;
@@ -382,205 +380,6 @@ function _points2twips(a) { return (a) * 20.0; }
attributes:(CPDictionary) attributes
paragraphStart:(BOOL) first
{
var unwrap = function(obj) {
if (!obj) return null;
if ((typeof obj.respondsToSelector === "function" && ([obj respondsToSelector:@selector(headers)] || [obj respondsToSelector:@selector(rows)])) ||
obj.headers || obj._headers || obj.rows || obj._rows) {
return obj;
}
var unwrapped = null;
if (typeof obj.respondsToSelector === "function") {
if ([obj respondsToSelector:@selector(attachmentCell)]) {
unwrapped = [obj attachmentCell];
} else if ([obj respondsToSelector:@selector(content)]) {
unwrapped = [obj content];
} else if ([obj respondsToSelector:@selector(view)]) {
unwrapped = [obj view];
}
}
if (!unwrapped) {
unwrapped = obj._attachmentCell || obj._content || obj._view || obj.attachmentCell || obj.content || obj.view;
}
return unwrapped ? unwrapped : obj;
};
var tableAttachment = null;
if (typeof CPAttachmentAttributeName !== "undefined") {
tableAttachment = [attributes objectForKey:CPAttachmentAttributeName];
}
if (!tableAttachment) {
tableAttachment = [attributes objectForKey:@"CPAttachmentAttributeName"];
}
if (!tableAttachment) {
tableAttachment = [attributes objectForKey:@"TableAttachmentAttribute"];
}
if (!tableAttachment) {
tableAttachment = [attributes objectForKey:@"_CPAttachmentView"];
}
if (!tableAttachment && typeof _CPAttachmentView !== "undefined") {
tableAttachment = [attributes objectForKey:_CPAttachmentView];
}
tableAttachment = unwrap(tableAttachment);
if (!tableAttachment && (substring === "\uFFFC" || substring === ""))
{
var keys = [attributes allKeys],
count = [keys count];
for (var i = 0; i < count; i++)
{
var key = [keys objectAtIndex:i],
val = unwrap([attributes objectForKey:key]);
if (val && (
(typeof val.respondsToSelector === "function" && [val respondsToSelector:@selector(headers)]) ||
val._headers ||
val.headers ||
(typeof _CPTableTextAttachment !== "undefined" && [val isKindOfClass:[_CPTableTextAttachment class]])
)) {
tableAttachment = val;
break;
}
}
}
if (tableAttachment)
{
var headers = null,
rows = null;
// Try to fetch from the active live view of the attachment first to capture user edits
var activeView = null;
if (typeof tableAttachment.respondsToSelector === "function" && [tableAttachment respondsToSelector:@selector(view)]) {
activeView = [tableAttachment view];
}
if (!activeView) {
activeView = tableAttachment._view || tableAttachment.view;
}
if (activeView) {
if (typeof activeView.respondsToSelector === "function") {
if ([activeView respondsToSelector:@selector(headers)]) {
headers = [activeView headers];
}
if ([activeView respondsToSelector:@selector(rows)]) {
rows = [activeView rows];
}
}
if (!headers) {
headers = activeView._headers || activeView.headers;
}
if (!rows) {
rows = activeView._rows || activeView.rows;
}
}
// Fall back to the attachment's parsed properties if the view is nil or lacks the properties
if (!headers || !rows) {
if (typeof tableAttachment.respondsToSelector === "function") {
if ([tableAttachment respondsToSelector:@selector(headers)]) {
headers = [tableAttachment headers];
}
if ([tableAttachment respondsToSelector:@selector(rows)]) {
rows = [tableAttachment rows];
}
}
if (!headers) {
headers = tableAttachment._headers || tableAttachment.headers;
}
if (!rows) {
rows = tableAttachment._rows || tableAttachment.rows;
}
}
var getCount = function(arr) {
if (!arr) return 0;
if (typeof arr.count === "function") return [arr count];
return arr.length;
};
var getObjectAtIndex = function(arr, idx) {
if (!arr) return null;
if (typeof arr.objectAtIndex === "function") return [arr objectAtIndex:idx];
return arr[idx];
};
var numCols = getCount(headers);
if (numCols == 0 && getCount(rows) > 0) {
numCols = getCount(getObjectAtIndex(rows, 0));
}
if (numCols > 0)
{
var totalWidthTwips = 10000;
var colWidthTwips = Math.floor(totalWidthTwips / numCols);
var cellBoundaries = [];
var currentBoundary = 0;
for (var i = 0; i < numCols; i++)
{
currentBoundary += colWidthTwips;
cellBoundaries.push(currentBoundary);
}
var tableRTF = "";
var writeRow = function(rowData, isHeaderRow) {
var rowRTF = "\\trowd\\trgaph115\\trleft0";
for (var c = 0; c < numCols; c++) {
rowRTF += "\\clbrdrt\\brdrs\\brdrw10\\clbrdrb\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10";
rowRTF += "\\cellx" + cellBoundaries[c];
}
for (var c = 0; c < numCols; c++) {
var cellText = "";
if (c < getCount(rowData)) {
cellText = getObjectAtIndex(rowData, c);
}
if (cellText === null || cellText === undefined) {
cellText = "";
}
// Safely extract text representation from CPAttributedString / CPTextStorage if present
if (cellText && typeof cellText === "object") {
if (typeof cellText.string === "function") {
cellText = [cellText string];
} else if (cellText._string !== undefined) {
cellText = cellText._string;
} else if (cellText.string !== undefined) {
cellText = cellText.string;
}
}
cellText = String(cellText);
cellText = cellText.replace(/\\/g, '\\\\');
cellText = cellText.replace(/{/g, '\\{');
cellText = cellText.replace(/}/g, '\\}');
cellText = cellText.replace(/\n/g, '\\line ');
if (isHeaderRow) {
rowRTF += "{\\intbl\\b " + cellText + "\\b0\\cell}";
} else {
rowRTF += "{\\intbl " + cellText + "\\cell}";
}
}
rowRTF += "\\row\n";
return rowRTF;
};
if (getCount(headers) > 0) {
tableRTF += writeRow(headers, YES);
}
var rowCount = getCount(rows);
for (var r = 0; r < rowCount; r++) {
tableRTF += writeRow(getObjectAtIndex(rows, r), NO);
}
return tableRTF;
}
}
var result = "",
headerString = "",
trailerString = "",
@@ -593,12 +392,23 @@ function _points2twips(a) { return (a) * 20.0; }
headerString += [self paragraphStyle:paraStyle];
}
/*
* analyze attributes of current run
*
* FIXME: All the character attributes should be output relative to the font
* attributes of the paragraph. So if the paragraph has underline on it should
* still be possible to switch it off for some characters, which currently is
* not possible.
*/
attribEnum = [attributes keyEnumerator];
while ((currAttrib = [attribEnum nextObject]) != nil)
{
if ([currAttrib isEqualToString:CPFontAttributeName])
{
/*
* handle fonts
*/
var font,
fontName,
traits;
@@ -607,9 +417,15 @@ function _points2twips(a) { return (a) * 20.0; }
fontName = [font familyName];
traits = [[CPFontManager sharedFontManager] traitsOfFont:font];
/*
* font name
*/
if (currentFont == nil || ![fontName isEqualToString:[currentFont familyName]])
headerString += [self fontToken:fontName];
/*
* font size
*/
if (currentFont == nil || [font size] != [currentFont size])
{
var points = [font size] * 2,
@@ -618,7 +434,9 @@ function _points2twips(a) { return (a) * 20.0; }
pString = [CPString stringWithFormat:@"\\fs%d", points];
headerString += pString;
}
/*
* font attributes
*/
if (traits & CPItalicFontMask)
{
headerString += @"\\i";
@@ -657,29 +475,12 @@ function _points2twips(a) { return (a) * 20.0; }
else if ([currAttrib isEqualToString:CPUnderlineStyleAttributeName])
{
headerString += @"\\ul";
trailerString += @"\\ulnone "; // trailing space important!
trailerString += @"\\ulnone";
}
else if ([currAttrib isEqualToString:CPSuperscriptAttributeName])
{
var value = [attributes objectForKey:CPSuperscriptAttributeName],
ivalue = [value intValue];
if (ivalue > 0)
{
headerString += @"\\super";
trailerString += @"\\nosupersub "; // trailing space important!
}
else if (ivalue < 0)
{
headerString += @"\\sub";
trailerString += @"\\nosupersub "; // trailing space important!
}
}
else if ([currAttrib isEqualToString:CPBaselineOffsetAttributeName])
{
var value = [attributes objectForKey:CPBaselineOffsetAttributeName],
fvalue = [value floatValue],
svalue = Math.round(fvalue * 2.0); // Convert standard points to RTF half-points
svalue = [value intValue] * 6;
if (svalue > 0)
{
@@ -688,8 +489,23 @@ function _points2twips(a) { return (a) * 20.0; }
}
else if (svalue < 0)
{
// Correct negative formatting using safe positive boundary
headerString += [CPString stringWithFormat:@"\\dn%d", Math.abs(svalue)];
headerString += [CPString stringWithFormat:@"\\dn-%d", svalue];
trailerString += @"\\dn0";
}
}
else if ([currAttrib isEqualToString:CPBaselineOffsetAttributeName])
{
var value = [attributes objectForKey:CPBaselineOffsetAttributeName],
svalue = [value floatValue] * 2;
if (svalue > 0)
{
headerString += [CPString stringWithFormat:@"\\up%d", svalue];
trailerString += @"\\up0";
}
else if (svalue < 0)
{
headerString += [CPString stringWithFormat:@"\\dn-%d", svalue];
trailerString += @"\\dn0";
}
}
@@ -706,9 +522,11 @@ function _points2twips(a) { return (a) * 20.0; }
substring = substring.replace(/\\/g, '\\\\');
substring = substring.replace(/\n/g, '\\par\n');
substring = substring.replace(/\t/g, '\\tab ');
substring = substring.replace(/\t/g, '\\tab');
substring = substring.replace(/{/g, '\\{');
substring = substring.replace(/}/g, '\\}');
// FIXME: All characters not in the standard encoding must be
// replaced by \'xx
if (!first)
{
@@ -744,9 +562,10 @@ function _points2twips(a) { return (a) * 20.0; }
length = [string length],
currRange = CPMakeRange(loc, 0),
completeRange = CPMakeRange(0, length),
paragraphStart = YES;
first = YES;
while (CPMaxRange(currRange) < CPMaxRange(completeRange))
// FIXME <!> split along newline characters and run as outer loop
while (CPMaxRange(currRange) < CPMaxRange(completeRange)) // save all "runs"
{
var attributes,
substring,
@@ -758,14 +577,10 @@ function _points2twips(a) { return (a) * 20.0; }
substring = [string substringWithRange:currRange];
runString = [self runStringForString:substring
attributes:attributes
paragraphStart:paragraphStart];
paragraphStart:YES];
result += runString;
if (substring.length > 0 && substring.charAt(substring.length - 1) === '\n')
paragraphStart = YES;
else
paragraphStart = NO;
first = NO;
}
return result;
@@ -783,6 +598,9 @@ function _points2twips(a) { return (a) * 20.0; }
text = aText;
docDict = dict;
/*
* do not change order! (esp. body has to be generated first; builds context)
*/
bodyString = [self bodyString];
trailerString = [self trailerString];
headerString = [self headerString];
-558
View File
@@ -1,558 +0,0 @@
/* _CPTableTextAttachment.j
* A self-contained, renderable text attachment representation of a table.
*
* Copyright (C) 2026 Daniel Boehringer
*
* 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 CPTextView;
@class CPTextContainer;
@import "CPTextField.j"
@import <Foundation/CPAttributedString.j>
@implementation _CPTableTextAttachment : CPView
{
CPArray _headers;
CPArray _rows;
BOOL _isResizing;
BOOL _isEditable;
BOOL _acceptsRichText;
}
- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows
{
return [self initWithHeaders:headers rows:rows width:500.0 isEditable:YES acceptsRichText:YES];
}
- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows width:(float)totalWidth
{
return [self initWithHeaders:headers rows:rows width:totalWidth isEditable:YES acceptsRichText:YES];
}
- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows width:(float)totalWidth isEditable:(BOOL)isEditable acceptsRichText:(BOOL)acceptsRichText
{
self = [super initWithFrame:CGRectMake(0, 0, totalWidth, 20)];
if (self)
{
_headers = headers;
_rows = rows;
_isEditable = isEditable;
_acceptsRichText = acceptsRichText;
_isResizing = NO;
[self _rebuildTableWithWidth:totalWidth];
}
return self;
}
- (void)_rebuildTableWithWidth:(float)totalWidth
{
var numCols = _headers ? [_headers count] : 0;
if (numCols == 0 && _rows && [_rows count] > 0)
numCols = [[_rows objectAtIndex:0] count];
// Apply borders on the outer left and top container edges
if (self._DOMElement) {
self._DOMElement.style.borderTop = "1px solid #e0e0e0";
self._DOMElement.style.borderLeft = "1px solid #e0e0e0";
self._DOMElement.style.boxSizing = "border-box";
}
// Initialize header cells
if (_headers && [_headers count] > 0) {
for (var c = 0; c < numCols; c++) {
var headerText = [_headers objectAtIndex:c];
var cellView = [self createCellWithText:headerText frame:CGRectMakeZero() isHeader:YES];
[self addSubview:cellView];
}
}
// Initialize body rows
if (_rows) {
for (var r = 0; r < [_rows count]; r++) {
var rowData = [_rows objectAtIndex:r];
for (var c = 0; c < numCols; c++) {
var cellText = @"";
if (c < [rowData count])
cellText = [rowData objectAtIndex:c];
var cellView = [self createCellWithText:cellText frame:CGRectMakeZero() isHeader:NO];
[self addSubview:cellView];
}
}
}
[self resizeToWidth:totalWidth];
// Trigger parent layout engine re-calculation
var textView = [self superview];
if (textView && [textView isKindOfClass:[CPTextView class]])
{
var layoutManager = [textView layoutManager];
if (layoutManager)
{
var charRange = [self _findCharacterRangeInLayoutManager:layoutManager];
if (charRange && charRange.location !== CPNotFound)
{
[layoutManager invalidateLayoutForCharacterRange:charRange isSoft:NO actualCharacterRange:nil];
[layoutManager invalidateDisplayForGlyphRange:charRange];
[layoutManager _validateLayoutAndGlyphs];
[textView sizeToFit];
}
}
}
}
- (CPRange)_findCharacterRangeInLayoutManager:(CPLayoutManager)layoutManager
{
var lineFragments = layoutManager._lineFragments;
if (lineFragments)
{
var l = lineFragments.length;
for (var i = 0; i < l; i++)
{
var fragment = lineFragments[i];
var runs = fragment._runs;
if (runs)
{
var rc = runs.length;
for (var j = 0; j < rc; j++)
{
if (runs[j].view === self)
{
return runs[j]._range;
}
}
}
}
}
return CPMakeRange(CPNotFound, 0);
}
- (CPArray)headers
{
var numCols = _headers ? [_headers count] : 0;
if (numCols == 0 && _rows && [_rows count] > 0)
numCols = [[_rows objectAtIndex:0] count];
if (numCols == 0)
return _headers;
var subviews = [self subviews];
if ([subviews count] < numCols)
return _headers; // Subviews are not yet rendered, return cached fallback
var currentHeaders = [CPMutableArray array];
for (var c = 0; c < numCols; c++)
{
var cellView = [subviews objectAtIndex:c];
var textView = [self getTextViewFromCell:cellView];
var text = @"";
if (textView)
{
text = _acceptsRichText ? [[textView textStorage] copy] : [textView string];
}
[currentHeaders addObject:text];
}
_headers = currentHeaders;
return _headers;
}
- (CPArray)rows
{
var numCols = _headers ? [_headers count] : 0;
if (numCols == 0 && _rows && [_rows count] > 0)
numCols = [[_rows objectAtIndex:0] count];
if (numCols == 0 || !_rows)
return _rows;
var subviews = [self subviews];
var headerOffset = (_headers && [_headers count] > 0) ? numCols : 0;
var expectedCount = headerOffset + ([_rows count] * numCols);
if ([subviews count] < expectedCount)
return _rows; // Subviews are not yet rendered, return cached fallback
var currentRows = [CPMutableArray array];
var cellIndex = headerOffset;
for (var r = 0; r < [_rows count]; r++)
{
var rowData = [CPMutableArray array];
for (var c = 0; c < numCols; c++)
{
var cellView = [subviews objectAtIndex:cellIndex++];
var textView = [self getTextViewFromCell:cellView];
var text = @"";
if (textView)
{
text = _acceptsRichText ? [[textView textStorage] copy] : [textView string];
}
[rowData addObject:text];
}
[currentRows addObject:rowData];
}
_rows = currentRows;
return _rows;
}
- (CPView)viewForWidth:(float)width
{
[self resizeToWidth:width];
return self;
}
- (CPView)createCellWithText:(id)text frame:(CGRect)frame isHeader:(BOOL)isHeader
{
var initialWidth = (frame.size.width > 0) ? frame.size.width : 120.0;
var initialHeight = (frame.size.height > 0) ? frame.size.height : 28.0;
var cellContainer = [[CPView alloc] initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, initialWidth, initialHeight)];
[cellContainer setBackgroundColor:isHeader ? [CPColor colorWithWhite:0.92 alpha:1.0] : [CPColor whiteColor]];
// Bottom and right edge borders to construct the grid
var borderView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, initialWidth, initialHeight)];
[borderView setBackgroundColor:[CPColor clearColor]];
[borderView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
if (borderView._DOMElement)
{
borderView._DOMElement.style.borderBottom = "1px solid #e0e0e0";
borderView._DOMElement.style.borderRight = "1px solid #e0e0e0";
borderView._DOMElement.style.boxSizing = "border-box";
}
[cellContainer addSubview:borderView];
var textContainer = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(initialWidth - 8, 1e7)];
var textView = [[CPTextView alloc] initWithFrame:CGRectMake(4, 2, initialWidth - 8, initialHeight - 4) textContainer:textContainer];
// Set explicit zero margins inside the text view to keep height measurements aligned with usedRect
[textView setTextContainerInset:CGSizeMake(0, 0)];
[textView setEditable:_isEditable];
[textView setSelectable:YES];
[textView setBackgroundColor:[CPColor clearColor]];
[textView setVerticallyResizable:YES];
[textView setHorizontallyResizable:NO];
[[textView textContainer] setWidthTracksTextView:YES];
// Configure cell rich text mode
[textView setRichText:_acceptsRichText];
// Intercept changes within cell TextViews to notify the table
[textView setDelegate:self];
// Configure cell text style using standard CPTextView APIs
var cellFont = isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0];
[textView setFont:cellFont];
[textView setTextColor:[CPColor blackColor]];
if (text)
[textView insertText:text];
[cellContainer addSubview:textView];
return cellContainer;
}
- (void)textDidChange:(CPNotification)aNotification
{
// Live update cell layouts and heights when changes are typed
[self resizeToWidth:CGRectGetWidth([self frame])];
}
- (CPTextView)getTextViewFromCell:(CPView)cellView
{
var subviews = [cellView subviews];
for (var i = 0; i < [subviews count]; i++) {
var sub = [subviews objectAtIndex:i];
if ([sub isKindOfClass:[CPTextView class]]) {
return sub;
}
}
return nil;
}
- (void)resizeToWidth:(float)newWidth
{
if (_isResizing)
return;
_isResizing = YES;
// Use dynamic getters to fetch live edited strings
var currentHeaders = [self headers];
var currentRows = [self rows];
var numCols = currentHeaders ? [currentHeaders count] : 0;
if (numCols == 0 && currentRows && [currentRows count] > 0) {
numCols = [[currentRows objectAtIndex:0] count];
}
if (numCols == 0) {
_isResizing = NO;
return;
}
var subviews = [self subviews];
var colNaturalWidths = [];
var colMinWidths = [];
for (var c = 0; c < numCols; c++) {
colNaturalWidths[c] = 80.0;
colMinWidths[c] = 60.0;
}
var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)];
[measureTextField setFont:[CPFont systemFontOfSize:13.0]];
var measureCell = function(cellText, isHeader, colIndex) {
var cellFont = isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0];
[measureTextField setFont:cellFont];
var plainText = (cellText && typeof cellText.string === "function") ? [cellText string] : (cellText || @"");
plainText = String(plainText);
if (cellText && typeof cellText.string === "function") {
if ([measureTextField respondsToSelector:@selector(setAttributedStringValue:)]) {
[measureTextField setAttributedStringValue:cellText];
} else {
[measureTextField setStringValue:plainText];
}
} else {
[measureTextField setStringValue:plainText];
}
[measureTextField sizeToFit];
var naturalW = CGRectGetWidth([measureTextField frame]) + 24.0;
if (naturalW > colNaturalWidths[colIndex]) {
colNaturalWidths[colIndex] = naturalW;
}
var words = plainText.split(/[\s\-]/);
var maxWordW = 50.0;
for (var w = 0; w < words.length; w++) {
var word = words[w].trim();
if (word.length === 0) continue;
[measureTextField setStringValue:word];
[measureTextField sizeToFit];
var wordW = CGRectGetWidth([measureTextField frame]) + 30.0;
if (wordW > maxWordW) {
maxWordW = wordW;
}
}
if (maxWordW > colMinWidths[colIndex]) {
colMinWidths[colIndex] = maxWordW;
}
};
if (currentHeaders) {
for (var c = 0; c < [currentHeaders count]; c++) {
measureCell([currentHeaders objectAtIndex:c], YES, c);
}
}
if (currentRows) {
for (var r = 0; r < [currentRows count]; r++) {
var rowData = [currentRows objectAtIndex:r];
for (var c = 0; c < numCols; c++) {
var cellText = @"";
if (c < [rowData count]) {
cellText = [rowData objectAtIndex:c];
}
measureCell(cellText, NO, c);
}
}
}
var totalMinWidth = 0.0;
for (var c = 0; c < numCols; c++) {
totalMinWidth += colMinWidths[c];
}
var colWidths = [];
if (newWidth <= totalMinWidth) {
var remainingWidth = newWidth;
for (var c = 0; c < numCols; c++) {
var w = Math.floor((colMinWidths[c] / totalMinWidth) * newWidth);
colWidths[c] = w;
remainingWidth -= w;
}
if (numCols > 0) colWidths[numCols - 1] += remainingWidth;
} else {
for (var c = 0; c < numCols; c++) {
colWidths[c] = colMinWidths[c];
}
var totalGrowthCapacity = 0.0;
var growthCapacities = [];
for (var c = 0; c < numCols; c++) {
var capacity = Math.max(0.0, colNaturalWidths[c] - colMinWidths[c]);
growthCapacities[c] = capacity;
totalGrowthCapacity += capacity;
}
var extraWidth = newWidth - totalMinWidth;
var remainingExtra = extraWidth;
for (var c = 0; c < numCols; c++) {
if (totalGrowthCapacity > 0) {
var w = Math.floor((growthCapacities[c] / totalGrowthCapacity) * extraWidth);
colWidths[c] += w;
remainingExtra -= w;
}
}
if (numCols > 0) {
colWidths[numCols - 1] += remainingExtra;
}
}
var cellIndex = 0;
var currentY = 0;
var layoutRow = function(startIndex) {
var maxCellHeight = 28.0;
for (var c = 0; c < numCols; c++) {
var idx = startIndex + c;
if (idx < [subviews count]) {
var cellView = [subviews objectAtIndex:idx];
var textView = [self getTextViewFromCell:cellView];
if (textView)
{
var targetWidth = Math.max(10.0, colWidths[c] - 8);
// Update frame size directly so that textContainer auto-resizes.
// Set a large temporary height to allow accurate wrapping measurements.
[textView setFrameSize:CGSizeMake(targetWidth, 1e7)];
var layoutManager = [textView layoutManager];
if (layoutManager)
{
// FORCE LAYOUT RECALCULATION:
// Because the width changed, we must force the lazy layout manager
// to synchronously calculate glyphs and wraps at the new width.
[layoutManager glyphRangeForTextContainer:[textView textContainer]];
}
var usedRect = layoutManager ? [layoutManager usedRectForTextContainer:[textView textContainer]] : nil;
var textHeight = usedRect ? CGRectGetHeight(usedRect) : 0.0;
// Exact height + 8.0px padding (4.0px top, 4.0px bottom margin) inside the cell
var wrappedHeight = textHeight + 8.0;
if (wrappedHeight > maxCellHeight)
maxCellHeight = wrappedHeight;
}
}
}
var currentX = 0;
for (var c = 0; c < numCols; c++)
{
var idx = startIndex + c;
if (idx < [subviews count])
{
var cellView = [subviews objectAtIndex:idx];
[cellView setFrame:CGRectMake(currentX, currentY, colWidths[c], maxCellHeight)];
var textView = [self getTextViewFromCell:cellView];
if (textView)
{
var targetWidth = Math.max(10.0, colWidths[c] - 8);
var textY = 4.0;
var finalTextViewHeight = maxCellHeight - 8.0;
[textView setFrame:CGRectMake(4, textY, targetWidth, finalTextViewHeight)];
}
var cellSubviews = [cellView subviews];
if ([cellSubviews count] > 0)
[[cellSubviews objectAtIndex:0] setFrame:CGRectMake(0, 0, colWidths[c], maxCellHeight)];
}
currentX += colWidths[c];
}
return maxCellHeight;
};
if (currentHeaders && [currentHeaders count] > 0)
{
var headerHeight = layoutRow(cellIndex);
cellIndex += numCols;
currentY += headerHeight;
}
if (currentRows)
{
for (var r = 0; r < [currentRows count]; r++)
{
var rowHeight = layoutRow(cellIndex);
cellIndex += numCols;
currentY += rowHeight;
}
}
// GUARD FRAME SIZE MUTATIONS
var currentSize = [self frame].size;
if (ABS(currentSize.width - newWidth) > 0.1 || ABS(currentSize.height - currentY) > 0.1)
{
[self setFrameSize:CGSizeMake(newWidth, currentY)];
}
// we need to re-layout the textview here.
// but this is not easy as the layout engine is not re-entrant
// this does not work: (delay does not matter), layout is always off
// setTimeout(function() {
// var textView = [self superview];
//
// if (textView && [textView isKindOfClass:[CPTextView class]])
// {
// var layoutManager = [textView layoutManager];
//
// if (layoutManager)
// {
// var charRange = [self _findCharacterRangeInLayoutManager:layoutManager];
//
// if (charRange && charRange.location !== CPNotFound)
// {
// [layoutManager invalidateLayoutForCharacterRange:charRange isSoft:NO actualCharacterRange:nil];
// [layoutManager invalidateDisplayForGlyphRange:charRange];
// [layoutManager _validateLayoutAndGlyphs];
// [textView sizeToFit];
// }
// }
// }
// }, 0);
_isResizing = NO;
}
@end
+3 -3
View File
@@ -319,8 +319,8 @@ var CPThemeNameKey = @"CPThemeNameKey",
@end
// MARK: -
// MARK: CSS Theming
#pragma mark -
#pragma mark CSS Theming
// The code below adds support for CSS theming with 100% compatibility with current theming system.
// The idea is to extend CPColor and CPImage with CSS components and adapt low level UI components to
@@ -556,7 +556,7 @@ var _savedThemesByName = { };
@end
// MARK: -
#pragma mark -
/*!
* ThemeStates are immutable objects representing a particular ThemeState. Applications should never be creating
+7 -32
View File
@@ -40,7 +40,6 @@
@global CPTextFieldDidFocusNotification
@global CPTextFieldDidBlurNotification
@global document
// TODO: should be conform to protocol CPTextFieldDelegate
@protocol CPTokenFieldDelegate <CPObject>
@@ -169,8 +168,8 @@ CPTokenFieldDeleteButtonType = 1;
[self addSubview:_tokenScrollView];
}
// MARK: -
// MARK: Delegate methods
#pragma mark -
#pragma mark Delegate methods
/*!
Set the delegate of the receiver
@@ -349,12 +348,6 @@ CPTokenFieldDeleteButtonType = 1;
if (theBinding)
[theBinding reverseSetValueFor:@"objectValue"];
if (!_isEditing)
{
_isEditing = YES;
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
}
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
_shouldNotifyTarget = YES;
@@ -459,8 +452,8 @@ CPTokenFieldDeleteButtonType = 1;
{
[_tokenScrollView documentView]._DOMElement.appendChild(element);
// Removed so CPTokenField doesn't fire the notification the moment it becomes the first responder, but instead defers to the first keystroke, just like Cocoa (see keyDown: in CPTextField).
// [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
//post CPControlTextDidBeginEditingNotification
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
[[CPRunLoop mainRunLoop] performBlock:function()
{
@@ -504,8 +497,6 @@ CPTokenFieldDeleteButtonType = 1;
[self _resignFirstKeyResponder];
_isEditing = NO;
if (_shouldNotifyTarget)
{
_shouldNotifyTarget = NO;
@@ -559,7 +550,7 @@ CPTokenFieldDeleteButtonType = 1;
CPTokenFieldCachedDragFunction = nil;
document.body.ondrag = CPTokenFieldCachedDragFunction;
document.body.onselectstart = CPTokenFieldCachedSelectStartFunction;
document.body.onselectstart = CPTokenFieldCachedSelectStartFunction
}
#endif
@@ -593,12 +584,6 @@ CPTokenFieldDeleteButtonType = 1;
// Snap to the token if it's only half visible due to mouse wheel scrolling.
_shouldScrollTo = aToken;
}
// this is a hack to compensate for a recent adoption in FR management as introduced by commit #26aab29
// this PR makes the tokenfield loose FR status prematurely, so we have to regain here in order to make deleteForward: and friends work
setTimeout(function(){
[[self window] makeFirstResponder:self];
}, 50);
}
// ===========
@@ -979,8 +964,8 @@ CPTokenFieldDeleteButtonType = 1;
[self _selectToken:tokenView byExtendingSelection:NO];
}
}
// we have to remove unconditionally because the backspace is not propagated anymore starting from commit #26aab29
[self _removeSelectedTokens:nil];
else
[self _removeSelectedTokens:nil];
}
else
{
@@ -1040,16 +1025,6 @@ CPTokenFieldDeleteButtonType = 1;
CPTokenFieldTextDidChangeValue = [self stringValue];
#endif
// Has to be enabled, and it also has to be editable or selectable.
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
return;
if ([self isEditable] && !_isEditing)
{
_isEditing = YES;
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
}
// Leave the default _propagateCurrentDOMEvent setting in place. This might be YES or NO depending
// on if something that could be a browser shortcut was pressed or not, such as Cmd-R to reload.
// If it was NO we want to leave it at NO however and only enable it in insertText:. This is what
-31
View File
@@ -1014,29 +1014,12 @@ var LABEL_MARGIN = 2.0;
CPImageView _imageView;
CPView _view;
CPView _highlightView;
CPTextField _labelField;
BOOL _FIXME_isHUD;
}
- (BOOL)acceptsFirstResponder
{
if (_view && [_view acceptsFirstResponder])
return YES;
return NO;
}
- (BOOL)becomeFirstResponder
{
if (_view && [_view acceptsFirstResponder])
return [[self window] makeFirstResponder:_view];
return [super becomeFirstResponder];
}
- (id)initWithToolbarItem:(CPToolbarItem)aToolbarItem toolbar:(CPToolbar)aToolbar
{
self = [super init];
@@ -1260,18 +1243,6 @@ var LABEL_MARGIN = 2.0;
if (alternateImage)
[_imageView setImage:alternateImage];
else
{
if (!_highlightView)
{
_highlightView = [[CPView alloc] initWithFrame:[_imageView bounds]];
[_highlightView setBackgroundColor:[CPColor blackColor]];
[_highlightView setAlphaValue:0.3];
[_highlightView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
}
[_imageView addSubview:_highlightView];
}
[_labelField setTextShadowOffset:CGSizeMakeZero()];
}
@@ -1282,8 +1253,6 @@ var LABEL_MARGIN = 2.0;
if (image)
[_imageView setImage:image];
[_highlightView removeFromSuperview];
[_labelField setTextShadowOffset:CGSizeMake(0.0, 1.0)];
}
+6 -6
View File
@@ -69,8 +69,8 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
/*!
Initializes and returns an object defining a region of a view to receive mouse-tracking events, mouse-moved events, cursor-update events, or possibly
@@ -121,8 +121,8 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
}
// MARK: -
// MARK: Implementation
#pragma mark -
#pragma mark Implementation
- (void)_updateWindowRect
{
@@ -140,8 +140,8 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
@end
// MARK: -
// MARK: CPCoding
#pragma mark -
#pragma mark CPCoding
@implementation CPTrackingArea (CPCoding)
-716
View File
@@ -1,716 +0,0 @@
/*
* CPTreeController.j
* AppKit
*
* Daniel Boehringer Mar/2026
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPArray.j>
@import <Foundation/CPIndexPath.j>
@import "CPObjectController.j"
@import "CPKeyValueBinding.j"
@import "CPTreeNode.j"
@implementation CPTreeController : CPObjectController
{
BOOL _avoidsEmptySelection;
BOOL _preservesSelection;
BOOL _selectsInsertedObjects;
BOOL _alwaysUsesMultipleValuesMarker;
CPString _childrenKeyPath;
CPString _countKeyPath;
CPString _leafKeyPath;
CPArray _sortDescriptors;
id _arrangedObjects;
CPArray _selectionIndexPaths;
BOOL _disableSetContent;
}
+ (void)initialize
{
if (self !== [CPTreeController class])
return;
[self exposeBinding:@"contentArray"];
[self exposeBinding:@"sortDescriptors"];
[self exposeBinding:@"selectionIndexPaths"];
[self exposeBinding:@"selectionIndexPath"];
[self exposeBinding:@"selectedObjects"];
}
+ (CPSet)keyPathsForValuesAffectingContentArray
{
return [CPSet setWithObjects:@"content"];
}
+ (CPSet)keyPathsForValuesAffectingArrangedObjects
{
return [CPSet setWithObjects:@"content", @"contentArray", @"sortDescriptors", @"childrenKeyPath"];
}
+ (CPSet)keyPathsForValuesAffectingSelectionIndexPath
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingSelectedObjects
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"arrangedObjects"];
}
+ (CPSet)keyPathsForValuesAffectingSelectedNodes
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"arrangedObjects"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsert
{
return [CPSet setWithObjects:@"editable"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsertChild
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"editable"];
}
+ (CPSet)keyPathsForValuesAffectingCanAddChild
{
return [CPSet setWithObjects:@"selectionIndexPaths", @"editable"];
}
- (id)init
{
if (self = [super init])
{
_preservesSelection = YES;
_selectsInsertedObjects = YES;
_avoidsEmptySelection = YES;
_alwaysUsesMultipleValuesMarker = NO;
_childrenKeyPath = @"children";
[self _init];
}
return self;
}
- (void)_init
{
_sortDescriptors = [CPArray array];
_selectionIndexPaths = [CPArray array];
_arrangedObjects = [[CPTreeNode alloc] initWithRepresentedObject:nil];
}
- (void)prepareContent
{
[self _setContentArray:[CPArray arrayWithObject:[self newObject]]];
}
- (BOOL)preservesSelection { return _preservesSelection; }
- (void)setPreservesSelection:(BOOL)value { _preservesSelection = value; }
- (BOOL)selectsInsertedObjects { return _selectsInsertedObjects; }
- (void)setSelectsInsertedObjects:(BOOL)value { _selectsInsertedObjects = value; }
- (BOOL)avoidsEmptySelection { return _avoidsEmptySelection; }
- (void)setAvoidsEmptySelection:(BOOL)value { _avoidsEmptySelection = value; }
- (BOOL)alwaysUsesMultipleValuesMarker { return _alwaysUsesMultipleValuesMarker; }
- (void)setAlwaysUsesMultipleValuesMarker:(BOOL)aFlag { _alwaysUsesMultipleValuesMarker = aFlag; }
- (CPArray)sortDescriptors { return _sortDescriptors; }
- (void)setSortDescriptors:(CPArray)value
{
if (_sortDescriptors === value)
return;
_sortDescriptors = [value copy];
[self _rearrangeObjects];
}
- (CPString)childrenKeyPath { return _childrenKeyPath; }
- (void)setChildrenKeyPath:(CPString)aKeyPath
{
if (_childrenKeyPath === aKeyPath) return;
_childrenKeyPath = aKeyPath;
[self rearrangeObjects];
}
- (CPString)countKeyPath { return _countKeyPath; }
- (void)setCountKeyPath:(CPString)aKeyPath { _countKeyPath = aKeyPath; }
- (CPString)leafKeyPath { return _leafKeyPath; }
- (void)setLeafKeyPath:(CPString)aKeyPath { _leafKeyPath = aKeyPath; }
- (CPString)childrenKeyPathForNode:(CPTreeNode)node { return [self childrenKeyPath]; }
- (CPString)countKeyPathForNode:(CPTreeNode)node { return [self countKeyPath]; }
- (CPString)leafKeyPathForNode:(CPTreeNode)node { return [self leafKeyPath]; }
- (void)setContent:(id)value
{
if (_disableSetContent) return;
if (!value)
value = [CPArray array];
if (![value isKindOfClass:[CPArray class]])
value = [CPArray arrayWithObject:value];
if (_contentObject === value)
return;
var oldSelectedObjects = nil,
oldSelectionIndexPaths = nil;
if ([self preservesSelection])
oldSelectedObjects = [self selectedObjects];
else
oldSelectionIndexPaths = [self selectionIndexPaths];
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
[self willChangeValueForKey:@"contentArray"];
_contentObject = value;
[self _rearrangeObjects];
if ([self preservesSelection])
[self __setSelectedObjects:oldSelectedObjects];
else
[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
[self didChangeValueForKey:@"contentArray"];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)_setContentArray:(id)anArray { [self setContent:anArray]; }
- (id)contentArray { return _contentObject; }
- (id)arrangedObjects { return _arrangedObjects; }
- (void)rearrangeObjects
{
[self _selectionWillChange];
[self willChangeValueForKey:@"arrangedObjects"];
[self _rearrangeObjects];
[self didChangeValueForKey:@"arrangedObjects"];
[self _selectionDidChange];
}
- (void)_rearrangeObjects
{
var oldSelectedObjects = nil,
oldSelectionIndexPaths = nil;
if ([self preservesSelection])
oldSelectedObjects = [self selectedObjects];
else
oldSelectionIndexPaths = [self selectionIndexPaths];
[self __rebuildArrangedObjectsTree];
if ([self preservesSelection])
[self __setSelectedObjects:oldSelectedObjects];
else
[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
}
- (void)__rebuildArrangedObjectsTree
{
var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil],
contentArray = [self contentArray];
if (contentArray && [contentArray count] > 0)
{
var children = [self _buildTreeNodesForObjects:contentArray];
[[rootNode mutableChildNodes] addObjectsFromArray:children];
}
_arrangedObjects = rootNode;
}
- (CPArray)_buildTreeNodesForObjects:(CPArray)objects
{
var count = [objects count];
if (count === 0)
return [];
var sortedObjects = objects;
if (_sortDescriptors && [_sortDescriptors count] > 0)
sortedObjects = [objects sortedArrayUsingDescriptors:_sortDescriptors];
var nodes = [CPMutableArray arrayWithCapacity:count];
for (var i = 0; i < count; i++)
{
var obj = [sortedObjects objectAtIndex:i],
node = [[CPTreeNode alloc] initWithRepresentedObject:obj];
if (_childrenKeyPath)
{
var childObjects = [obj valueForKeyPath:_childrenKeyPath];
if (childObjects && [childObjects count] > 0)
{
var childNodes = [self _buildTreeNodesForObjects:childObjects];
[[node mutableChildNodes] addObjectsFromArray:childNodes];
}
}
[nodes addObject:node];
}
return nodes;
}
- (CPIndexPath)selectionIndexPath
{
return [_selectionIndexPaths count] > 0 ? [_selectionIndexPaths objectAtIndex:0] : nil;
}
- (BOOL)setSelectionIndexPath:(CPIndexPath)indexPath
{
var paths = indexPath ? [CPArray arrayWithObject:indexPath] : [CPArray array];
return [self setSelectionIndexPaths:paths];
}
- (CPArray)selectionIndexPaths { return _selectionIndexPaths; }
- (BOOL)setSelectionIndexPaths:(CPArray)indexPaths
{
return [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO];
}
- (void)_ensureTreeNodesExistForIndexPaths:(CPArray)indexPaths
{
if (!_childrenKeyPath)
return;
var count = [indexPaths count];
for (var i = 0; i < count; i++)
{
var path = [indexPaths objectAtIndex:i];
var length = [path length];
var currentNode = [self arrangedObjects];
for (var j = 0; j < length; j++)
{
var index = [path indexAtPosition:j];
var obj = [currentNode representedObject];
var expectedChildObjects = nil;
if (!obj && currentNode === [self arrangedObjects])
expectedChildObjects = [self contentArray];
else if (obj)
expectedChildObjects = [obj valueForKeyPath:_childrenKeyPath];
if (expectedChildObjects)
{
var childNodes = [currentNode mutableChildNodes];
var needsRebuild = NO;
if (!childNodes || [childNodes count] !== [expectedChildObjects count])
{
needsRebuild = YES;
}
else
{
for (var k = 0, kCount = [childNodes count]; k < kCount; k++)
{
if ([[childNodes objectAtIndex:k] representedObject] !== [expectedChildObjects objectAtIndex:k])
{
needsRebuild = YES;
break;
}
}
}
if (needsRebuild)
{
[childNodes removeAllObjects];
var newNodes = [self _buildTreeNodesForObjects:expectedChildObjects];
[childNodes addObjectsFromArray:newNodes];
}
}
var children = [currentNode childNodes];
if (children && index < [children count])
currentNode = [children objectAtIndex:index];
else
break;
}
}
}
- (BOOL)__setSelectionIndexPaths:(CPArray)indexPaths avoidEmpty:(BOOL)avoidEmpty
{
var newPaths = indexPaths;
if (!newPaths)
newPaths = [CPArray array];
if ([newPaths count] > 0)
[self _ensureTreeNodesExistForIndexPaths:newPaths];
if (![newPaths count] && avoidEmpty)
{
if ([[[self arrangedObjects] childNodes] count] > 0)
newPaths = [CPArray arrayWithObject:[CPIndexPath indexPathWithIndex:0]];
}
if ([_selectionIndexPaths isEqualToArray:newPaths])
return NO;
[self _selectionWillChange];
[self willChangeValueForKey:@"selectionIndexPaths"];
_selectionIndexPaths = [newPaths copy];
[self didChangeValueForKey:@"selectionIndexPaths"];
[self _selectionDidChange];
return YES;
}
- (BOOL)addSelectionIndexPaths:(CPArray)indexPaths
{
var newPaths = [_selectionIndexPaths mutableCopy];
[newPaths addObjectsFromArray:indexPaths];
return [self setSelectionIndexPaths:newPaths];
}
- (BOOL)removeSelectionIndexPaths:(CPArray)indexPaths
{
var newPaths = [_selectionIndexPaths mutableCopy];
[newPaths removeObjectsInArray:indexPaths];
return [self setSelectionIndexPaths:newPaths];
}
- (CPArray)selectedNodes
{
var nodes = [CPMutableArray array],
count = [_selectionIndexPaths count];
for (var i = 0; i < count; i++)
{
var node = [[self arrangedObjects] descendantNodeAtIndexPath:[_selectionIndexPaths objectAtIndex:i]];
if (node)
[nodes addObject:node];
}
return nodes;
}
- (CPArray)selectedObjects
{
var objects = [CPMutableArray array],
nodes = [self selectedNodes],
count = [nodes count];
for (var i = 0; i < count; i++)
{
var representedObject = [[nodes objectAtIndex:i] representedObject];
if (representedObject)
[objects addObject:representedObject];
}
return [_CPObservableArray arrayWithArray:objects];
}
- (BOOL)__setSelectedObjects:(CPArray)objects
{
if (!objects || [objects count] === 0)
return [self __setSelectionIndexPaths:[CPArray array] avoidEmpty:_avoidsEmptySelection];
var newPaths = [CPMutableArray array];
for (var i = 0, count = [objects count]; i < count; i++)
{
var path = [self _indexPathForObject:[objects objectAtIndex:i] inNode:[self arrangedObjects]];
if (path)
[newPaths addObject:path];
}
return [self __setSelectionIndexPaths:newPaths avoidEmpty:_avoidsEmptySelection];
}
- (CPIndexPath)_indexPathForObject:(id)anObject inNode:(CPTreeNode)node
{
if ([node representedObject] === anObject && [node parentNode] != nil)
return [node indexPath];
var children = [node childNodes];
if (children)
{
for (var i = 0, count = [children count]; i < count; i++)
{
var found = [self _indexPathForObject:anObject inNode:[children objectAtIndex:i]];
if (found)
return found;
}
}
return nil;
}
- (BOOL)canInsert { return [self isEditable]; }
- (BOOL)canInsertChild { return [self isEditable] && [_selectionIndexPaths count] > 0; }
- (BOOL)canAddChild { return [self canInsertChild]; }
- (void)add:(id)sender
{
if (![self canInsert]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
selectionPath = [self selectionIndexPath];
if (!selectionPath)
selectionPath = [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]];
var length = [selectionPath length],
lastIndex = [selectionPath indexAtPosition:length - 1],
insertPath = [selectionPath indexPathByRemovingLastIndex];
insertPath = [insertPath indexPathByAddingIndex:lastIndex + 1];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
- (void)addChild:(id)sender
{
if (![self canAddChild])
return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]],
childCount = [[parentNode childNodes] count],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
- (void)insert:(id)sender
{
if (![self canInsert]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0];
[self insertObject:newObject atArrangedObjectIndexPath:indexPath];
}
- (void)insertChild:(id)sender
{
if (![self canInsertChild]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
- (void)insertObject:(id)anObject atArrangedObjectIndexPath:(CPIndexPath)indexPath
{
[self insertObjects:[CPArray arrayWithObject:anObject] atArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]];
}
- (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths
{
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
_disableSetContent = YES;
var count = [objects count];
for (var i = 0; i < count; i++)
{
var object = [objects objectAtIndex:i],
path = [indexPaths objectAtIndex:i],
length = [path length];
if (length === 1)
{
[_contentObject insertObject:object atIndex:[path indexAtPosition:0]];
}
else
{
var parentPath = [path indexPathByRemovingLastIndex],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
if (parentNode)
{
var parentObj = [parentNode representedObject],
childIndex = [path indexAtPosition:length - 1];
var children = [parentObj valueForKeyPath:_childrenKeyPath];
if (!children)
{
children = [CPMutableArray array];
[parentObj setValue:children forKeyPath:_childrenKeyPath];
}
var mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
[mutableChildren insertObject:object atIndex:childIndex];
}
}
}
var binding = [[self class] _binderClassForBinding:@"contentArray"];
if (binding)
[[binding getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
[self _rearrangeObjects];
if ([self selectsInsertedObjects])
[self setSelectionIndexPaths:indexPaths];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)remove:(id)sender
{
[self removeObjectsAtArrangedObjectIndexPaths:_selectionIndexPaths];
}
- (void)removeObjectAtArrangedObjectIndexPath:(CPIndexPath)indexPath
{
[self removeObjectsAtArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]];
}
- (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths
{
[self _selectionWillChange];
[self willChangeValueForKey:@"content"];
_disableSetContent = YES;
var sortedPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)],
count = [sortedPaths count];
for (var i = count - 1; i >= 0; i--)
{
var path = [sortedPaths objectAtIndex:i],
length = [path length];
if (length === 1)
{
[_contentObject removeObjectAtIndex:[path indexAtPosition:0]];
}
else
{
var parentPath = [path indexPathByRemovingLastIndex],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
if (parentNode)
{
var parentObj = [parentNode representedObject],
childIndex = [path indexAtPosition:length - 1],
mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
if (mutableChildren && childIndex < [mutableChildren count])
[mutableChildren removeObjectAtIndex:childIndex];
}
}
}
var binding = [[self class] _binderClassForBinding:@"contentArray"];
if (binding)
[[binding getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
[self _rearrangeObjects];
[self didChangeValueForKey:@"content"];
[self _selectionDidChange];
}
- (void)moveNode:(CPTreeNode)node toIndexPath:(CPIndexPath)indexPath
{
[self moveNodes:[CPArray arrayWithObject:node] toIndexPath:indexPath];
}
- (void)moveNodes:(CPArray)nodes toIndexPath:(CPIndexPath)startingIndexPath
{
[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
}
@end
var CPTreeControllerAvoidsEmptySelection = @"CPTreeControllerAvoidsEmptySelection",
CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection",
CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects",
CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker",
CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath",
CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath",
CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath";
@implementation CPTreeController (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
_avoidsEmptySelection = [aCoder decodeBoolForKey:CPTreeControllerAvoidsEmptySelection];
_preservesSelection = [aCoder decodeBoolForKey:CPTreeControllerPreservesSelection];
_selectsInsertedObjects = [aCoder decodeBoolForKey:CPTreeControllerSelectsInsertedObjects];
_alwaysUsesMultipleValuesMarker = [aCoder decodeBoolForKey:CPTreeControllerAlwaysUsesMultipleValuesMarker];
_childrenKeyPath = [aCoder decodeObjectForKey:CPTreeControllerChildrenKeyPath] || @"children";
_countKeyPath = [aCoder decodeObjectForKey:CPTreeControllerCountKeyPath];
_leafKeyPath = [aCoder decodeObjectForKey:CPTreeControllerLeafKeyPath];
_sortDescriptors = [CPArray array];
_selectionIndexPaths = [CPArray array];
_arrangedObjects = [[CPTreeNode alloc] initWithRepresentedObject:nil];
if (![self content] && [self automaticallyPreparesContent])
[self prepareContent];
else if (![self content])
[self _setContentArray:[CPArray array]];
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeBool:_avoidsEmptySelection forKey:CPTreeControllerAvoidsEmptySelection];
[aCoder encodeBool:_preservesSelection forKey:CPTreeControllerPreservesSelection];
[aCoder encodeBool:_selectsInsertedObjects forKey:CPTreeControllerSelectsInsertedObjects];
[aCoder encodeBool:_alwaysUsesMultipleValuesMarker forKey:CPTreeControllerAlwaysUsesMultipleValuesMarker];
[aCoder encodeObject:_childrenKeyPath forKey:CPTreeControllerChildrenKeyPath];
[aCoder encodeObject:_countKeyPath forKey:CPTreeControllerCountKeyPath];
[aCoder encodeObject:_leafKeyPath forKey:CPTreeControllerLeafKeyPath];
}
- (void)awakeFromCib
{
[self _selectionWillChange];
[self _selectionDidChange];
}
@end
+38 -317
View File
@@ -17,41 +17,18 @@
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import <Foundation/CPIndexPath.j>
@import <Foundation/CPArray.j>
/*
* CPTreeNode implements the NSTreeNode contract.
* The _childNodes array contains only CPTreeNode instances.
* The representedObject property contains the application data.
* The _parentNode and _childNodes properties maintain a strict bidirectional relationship.
* The KVC mutation methods are the public mechanism to change the tree structure.
*/
@implementation CPTreeNode : CPObject
{
/*
* KVO notifications on parentNode are never delivered: there is no
* setParentNode: for the swizzling machinery to intercept, so there is
* no selector to instrument, in any code path.
*
* KVO notifications on childNodes are reliable for
* insertObject:inChildNodesAtIndex:, including same-parent and
* cross-parent moves: all detach paths for that method route through
* the KVC accessors. replaceObjectInChildNodesAtIndex:withObject:
* still detaches a same-parent replacement node by mutating
* _childNodes directly, bypassing the KVC proxy for that step; an
* observer of that node's former parent's childNodes can miss that
* specific removal, or see it reported as the wrong kind of change.
* Do not rely on childNodes observation during a same-parent replace;
* rely only on the state after the call returns.
*/
id _representedObject @accessors(readonly, property=representedObject);
CPTreeNode _parentNode @accessors(readonly, property=parentNode);
id _representedObject @accessors(readonly, property=representedObject);
CPTreeNode _parentNode @accessors(readonly, property=parentNode);
CPMutableArray _childNodes;
}
@@ -73,120 +50,38 @@
return self;
}
/*
* Route plain init through the designated initializer.
* Without this override, [[CPTreeNode alloc] init] leaves _childNodes unset.
* The first mutation call then fails against an undefined array.
*/
- (id)init
{
return [self initWithRepresentedObject:nil];
}
/*
* Return YES if adding aTreeNode below self makes a cycle.
* This method walks the parent chain.
* The operation time is proportional to the tree depth.
*/
- (BOOL)_wouldCreateCycleWithNode:(CPTreeNode)aTreeNode
{
for (var node = self; node; node = node._parentNode)
{
if (node === aTreeNode)
return YES;
}
return NO;
}
/*
* Enforce the NSTreeNode abstraction boundary.
* All children must be CPTreeNode instances.
*/
- (void)_validateChildNode:(id)aTreeNode
{
if (![aTreeNode isKindOfClass:[CPTreeNode class]])
{
[CPException raise:CPInvalidArgumentException
reason:"CPTreeNode children must be CPTreeNode instances."];
}
}
/*
* Remove a child node by delegating to the public KVC accessor.
* Use this method for internal structural changes across a parent
* boundary, so an observer of this node's childNodes sees the removal.
*/
- (void)_removeChildNode:(CPTreeNode)aNode
{
var index = [_childNodes indexOfObjectIdenticalTo:aNode];
/*
* A caller reaches this method only when aNode.parentNode already equals
* self (see the two call sites below). If self._childNodes does not
* actually contain aNode at that point, the parent/child relationship
* is already broken. indexPath raises for this identical class of
* inconsistency; silently returning here would hide the same problem
* instead of surfacing it.
*/
if (index === CPNotFound)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
}
[self removeObjectFromChildNodesAtIndex:index];
}
- (CPIndexPath)indexPath
{
if (!_parentNode)
return [CPIndexPath indexPathWithIndexes:[]];
var indexes = [],
node = self;
while (node._parentNode)
if (_parentNode != nil)
{
var parent = node._parentNode,
index = [parent._childNodes indexOfObjectIdenticalTo:node];
var path;
var index;
if (index === CPNotFound)
index = [[_parentNode childNodes] indexOfObject:self];
path = [_parentNode indexPath];
if (path != nil)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
return [path indexPathByAddingIndex:index];
}
else
{
return [CPIndexPath indexPathWithIndex:index];
}
[indexes addObject:index];
node = parent;
}
/*
* indexes was collected leaf-to-root. Build a second array in
* root-to-leaf order by walking indexes backward. CPArray has no
* -reverse selector; count/objectAtIndex:/addObject: are the verified,
* already-used-elsewhere primitives.
*/
var orderedIndexes = [],
count = [indexes count];
while (count--)
[orderedIndexes addObject:[indexes objectAtIndex:count]];
return [CPIndexPath indexPathWithIndexes:orderedIndexes];
else
{
return nil;
}
}
- (BOOL)isLeaf
{
return [_childNodes count] == 0;
return [_childNodes count] <= 0;
}
- (CPArray)childNodes
{
/*
* Return a copy.
* This prevents external changes that bypass the KVC methods.
*/
return [_childNodes copy];
}
@@ -195,141 +90,26 @@
return [self mutableArrayValueForKey:@"childNodes"];
}
/*
* KVC compliance methods.
* The mutableArrayValueForKey: method uses these names.
*/
- (void)insertObject:(CPTreeNode)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
- (void)insertObject:(id)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
{
var count = [_childNodes count];
if (anIndex < 0 || anIndex > count)
{
[CPException raise:CPRangeException
reason:"index (" + anIndex + ") beyond bounds (0 .. " + count + ") for insertObject:inChildNodesAtIndex:"];
}
[self _validateChildNode:aTreeNode];
if ([self _wouldCreateCycleWithNode:aTreeNode])
{
[CPException raise:CPInvalidArgumentException
reason:"Inserting a CPTreeNode beneath itself or one of its descendants makes a cycle."];
}
/*
* Detach the node from its old parent first.
* The code validated the index before this change.
*/
if (aTreeNode._parentNode)
{
if (aTreeNode._parentNode === self)
{
var originalIndex = [_childNodes indexOfObjectIdenticalTo:aTreeNode];
/*
* Route the detach through the KVC accessor, not direct array
* mutation, so an observer of childNodes sees the removal.
*/
[self removeObjectFromChildNodesAtIndex:originalIndex];
/*
* No index adjustment here. anIndex is the target position in
* the final array, per the KVC to-many contract. The array
* above is already one element short from the removal, so
* inserting at anIndex against it lands the node correctly.
*/
}
else
{
/*
* Detach the node from its old parent.
* Use the internal method to bypass KVO overhead.
*/
[aTreeNode._parentNode _removeChildNode:aTreeNode];
}
}
[[aTreeNode._parentNode mutableChildNodes] removeObjectIdenticalTo:aTreeNode];
aTreeNode._parentNode = self;
[_childNodes insertObject:aTreeNode atIndex:anIndex];
}
- (void)removeObjectFromChildNodesAtIndex:(CPInteger)anIndex
{
var node = [_childNodes objectAtIndex:anIndex];
[_childNodes objectAtIndex:anIndex]._parentNode = nil;
node._parentNode = nil;
[_childNodes removeObjectAtIndex:anIndex];
}
- (void)replaceObjectInChildNodesAtIndex:(CPInteger)anIndex withObject:(CPTreeNode)aTreeNode
- (void)replaceObjectFromChildNodesAtIndex:(CPInteger)anIndex withObject:(id)aTreeNode
{
var oldTreeNode = [_childNodes objectAtIndex:anIndex];
[self _validateChildNode:aTreeNode];
if (oldTreeNode === aTreeNode)
return;
if ([self _wouldCreateCycleWithNode:aTreeNode])
{
[CPException raise:CPInvalidArgumentException
reason:"Replacing a child with itself or one of its ancestors makes a cycle."];
}
/*
* If the replacement node is already a child of this parent, remove it first.
* The removal shifts the array elements.
* Adjust the target index before the replace operation.
* This matches the Cocoa KVC mutation semantics.
*/
var oldParent = aTreeNode._parentNode;
if (oldParent === self)
{
var replacementIndex = [_childNodes indexOfObjectIdenticalTo:aTreeNode];
/*
* aTreeNode.parentNode already equals self at this point. If
* self._childNodes does not actually contain aTreeNode, the
* parent/child relationship is already broken. indexPath raises
* for this identical class of inconsistency; proceeding here would
* silently tolerate the same problem instead of surfacing it.
*/
if (replacementIndex === CPNotFound)
{
[CPException raise:CPInternalInconsistencyException
reason:"CPTreeNode parent and child relationship is inconsistent."];
}
/*
* Bypass KVO for this internal structural adjustment.
*/
aTreeNode._parentNode = nil;
[_childNodes removeObjectAtIndex:replacementIndex];
/*
* Unlike insertObject:inChildNodesAtIndex:, anIndex here cannot be
* treated as a plain final-array position: replace requires an
* existing slot, it cannot append past the end. The removal above
* already took a slot out of the array ahead of the target
* whenever the replacement's original position was before it.
* Shift anIndex down by one in that case, to keep it pointing at
* the same physical slot the caller named.
*/
if (replacementIndex < anIndex)
--anIndex;
}
else if (oldParent)
{
/*
* Detach the node from its old parent.
* Use the internal method to bypass KVO overhead.
*/
[oldParent _removeChildNode:aTreeNode];
}
oldTreeNode._parentNode = nil;
aTreeNode._parentNode = self;
@@ -338,68 +118,30 @@
- (id)objectInChildNodesAtIndex:(CPInteger)anIndex
{
return [_childNodes objectAtIndex:anIndex];
}
- (CPInteger)countOfChildNodes
{
return [_childNodes count];
return _childNodes[anIndex];
}
- (void)sortWithSortDescriptors:(CPArray)sortDescriptors recursively:(BOOL)shouldSortRecursively
{
[_childNodes sortUsingDescriptors:sortDescriptors];
if (!shouldSortRecursively)
{
[_childNodes sortUsingDescriptors:sortDescriptors];
return;
}
/*
* Use an explicit stack, not recursion.
* The recursive form is not a tail call.
* The sibling loop continues after each child call returns.
* Only JavaScriptCore performs tail call optimization.
* The explicit stack prevents stack overflow on deep trees.
*/
var stack = [];
var count = [_childNodes count];
[stack addObject:self];
while ([stack count])
{
var node = [stack lastObject];
[stack removeLastObject];
[node._childNodes sortUsingDescriptors:sortDescriptors];
var count = [node._childNodes count];
while (count--)
{
[stack addObject:[node._childNodes objectAtIndex:count]];
}
}
while (count--)
[_childNodes[count] sortWithSortDescriptors:sortDescriptors recursively:YES];
}
- (CPTreeNode)descendantNodeAtIndexPath:(CPIndexPath)indexPath
{
if (!indexPath || [indexPath length] == 0)
return self;
var index = 0,
count = [indexPath length],
node = self;
var node = self,
length = [indexPath length];
for (var i = 0; i < length; i++)
{
var index = [indexPath indexAtPosition:i],
count = [node countOfChildNodes];
if (index < 0 || index >= count)
return nil;
node = [node objectInChildNodesAtIndex:index];
}
for (; index < count; ++index)
node = [node objectInChildNodesAtIndex:[indexPath indexAtPosition:index]];
return node;
}
@@ -421,27 +163,6 @@ var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
_representedObject = [aCoder decodeObjectForKey:CPTreeNodeRepresentedObjectKey];
_parentNode = [aCoder decodeObjectForKey:CPTreeNodeParentNodeKey];
_childNodes = [aCoder decodeObjectForKey:CPTreeNodeChildNodesKey];
if (!_childNodes)
_childNodes = [];
if (![_childNodes isKindOfClass:[CPMutableArray class]])
_childNodes = [_childNodes mutableCopy];
/*
* The child array is the authoritative structure.
* Re-establish the parent links.
* This makes the decoded tree match the tree built by the mutation methods.
*/
var count = [_childNodes count];
while (count--)
{
var child = [_childNodes objectAtIndex:count];
[self _validateChildNode:child];
child._parentNode = self;
}
}
return self;
+20 -124
View File
@@ -44,7 +44,6 @@
@class CPClipView
@class CPScrollView
@class CALayer
@class CPBinder
@global appkit_tag_dom_elements
@@ -233,7 +232,7 @@ var CPViewHighDPIDrawingEnabled = YES;
JSObject _ephemeralSubviews;
JSObject _ephemeralSubviewsForNames;
CPSet _ephemeralSubviews;
CPSet _ephereralSubviews;
// Key View Support
CPView _nextKeyView;
@@ -602,8 +601,7 @@ var CPViewHighDPIDrawingEnabled = YES;
// We will have to adjust the z-index of all views starting at this index.
var count = _subviews.length,
lastWindow,
isNewAddOrMove = aSubview._superview !== self;
lastWindow;
// Dirty the key view loop, in case the window wants to auto recalculate it
[[self window] _dirtyKeyViewLoop];
@@ -662,11 +660,6 @@ var CPViewHighDPIDrawingEnabled = YES;
#endif
}
#if PLATFORM(DOM)
var origin = aSubview._frame.origin;
CPDOMDisplayServerSetStyleLeftTop(aSubview._DOMElement, _boundsTransform, origin.x, origin.y);
#endif
[aSubview setNextResponder:self];
[aSubview _scaleSizeUnitSquareToSize:[self _hierarchyScaleSize]];
@@ -679,9 +672,6 @@ var CPViewHighDPIDrawingEnabled = YES;
if (!_window && lastWindow)
[aSubview _setWindow:nil];
if (isNewAddOrMove)
[aSubview _postViewDidAppearNotification];
// This method might be called before we are fully unarchived, in which case the theme state isn't set up yet
// and none of the below matters anyhow.
if (_themeState)
@@ -744,7 +734,6 @@ var CPViewHighDPIDrawingEnabled = YES;
// If the view is not hidden and one of its ancestors is hidden,
// notify the view that it is now unhidden.
[self _setSuperview:nil];
[self _postViewDidDisappearNotification];
[self _notifyWindowDidResignKey];
[self _notifyViewDidResignFirstResponder];
@@ -1415,9 +1404,6 @@ var CPViewHighDPIDrawingEnabled = YES;
_inverseBoundsTransform = nil;
}
if (_layer)
[_layer _owningViewBoundsChanged];
#if PLATFORM(DOM)
var index = _subviews.length;
@@ -1474,27 +1460,6 @@ var CPViewHighDPIDrawingEnabled = YES;
origin.y *= size.height / frameSize.height;
}
var newScaleSize;
if (size && size.width !== 0 && size.height !== 0 && frameSize)
newScaleSize = CGSizeMake(frameSize.width / size.width, frameSize.height / size.height);
else
newScaleSize = CGSizeMake(1.0, 1.0);
// Only update and propagate if the scale factor has actually changed
if (!CGSizeEqualToSize(_scaleSize, newScaleSize))
{
[self willChangeValueForKey:@"scaleSize"];
_scaleSize = newScaleSize;
_isScaled = (_scaleSize.width !== 1.0 || _scaleSize.height !== 1.0);
[self didChangeValueForKey:@"scaleSize"];
[self _scaleSizeUnitSquareToSize:CGSizeMake(1.0, 1.0)];
}
if (_layer)
[_layer _owningViewBoundsChanged];
if (_postsBoundsChangedNotifications && !_inhibitFrameAndBoundsChangedNotifications)
[CachedNotificationCenter postNotificationName:CPViewBoundsDidChangeNotification object:self];
@@ -1505,6 +1470,7 @@ var CPViewHighDPIDrawingEnabled = YES;
[self _updateTrackingAreasWithRecursion:YES];
}
/*!
Notifies subviews that the superview changed size.
@param aSize the size of the old superview
@@ -1752,7 +1718,11 @@ var CPViewHighDPIDrawingEnabled = YES;
_superview = aSuperview;
// Notifications are now posted manually from _insertSubview and _removeFromSuperview
if (hasOldSuperview)
[self _postViewDidDisappearNotification];
if (hasNewSuperview)
[self _postViewDidAppearNotification];
}
- (void)_recursiveLostHiddenAncestor
@@ -2623,7 +2593,7 @@ setBoundsOrigin:
*/
- (void)_scaleSizeUnitSquareToSize:(CGSize)aSize
{
_hierarchyScaleSize = _superview ? CGSizeMakeCopy([_superview _hierarchyScaleSize]) : CGSizeMake(1.0, 1.0);
_hierarchyScaleSize = CGSizeMakeCopy([_superview _hierarchyScaleSize]);
if (_isScaled)
{
@@ -3243,8 +3213,7 @@ setBoundsOrigin:
{
_layer._owningView = nil;
#if PLATFORM(DOM)
if (_layer._DOMElement && _layer._DOMElement.parentNode === _DOMElement)
_DOMElement.removeChild(_layer._DOMElement);
_DOMElement.removeChild(_layer._DOMElement);
#endif
}
@@ -3252,56 +3221,33 @@ setBoundsOrigin:
if (_layer)
{
var bounds = CGRectMakeCopy([self bounds]);
[_layer _setOwningView:self];
[_layer setFrame:[self bounds]]; // Sync layer frame with view bounds
#if PLATFORM(DOM)
_layer._DOMElement.style.zIndex = 100;
_DOMElement.appendChild(_layer._DOMElement);
#endif
}
}
/*!
Returns the core animation layer used by the receiver and creates one if necessary.
Returns the core animation layer used by the receiver.
*/
- (CALayer)layer
{
if (_wantsLayer && !_layer)
{
var layer = [[CALayer alloc] init];
[self setLayer:layer];
[self setNeedsLayout:YES];
[self setNeedsDisplay:YES];
}
return _layer;
}
/*!
Sets whether the receiver wants a core animation layer.
@param aFlag \c YES means the receiver wants a layer.
@param \c YES means the receiver wants a layer.
*/
- (void)setWantsLayer:(BOOL)aFlag
{
aFlag = !!aFlag;
if (_wantsLayer === aFlag)
return;
_wantsLayer = aFlag;
if (_wantsLayer)
{
// Accessing the layer will create it if it doesn't exist.
[self layer];
}
else
{
// Remove the layer if we no longer want it.
if (_layer)
[self setLayer:nil];
}
_wantsLayer = !!aFlag;
}
/*!
@@ -3313,38 +3259,6 @@ setBoundsOrigin:
return _wantsLayer;
}
/*!
Rotates the view's visual representation by a given angle (in degrees) around its center point.
This method achieves the rotation by applying a transform directly to the view's backing CALayer.
Because this is a direct layer manipulation, the view's own `frame` property is not updated to
reflect the new visual bounding box. Consequently, a `CPViewBoundsDidChangeNotification` is
**not** posted by this method. Note that this is a deviation from Cocoa's behavior.
This method requires the view to be layer-backed. If the view is not
already layer-backed, this method will automatically set wantsLayer to YES.
@param angle The angle in degrees to rotate the view.
*/
- (void)rotateByAngle:(CGFloat)angle
{
// Ensure the view is layer-backed
[self setWantsLayer:YES];
var layer = [self layer];
if (!layer)
return;
// Convert degrees to radians for the transform
var radians = angle * Math.PI / 180.0;
var rotationTransform = CGAffineTransformMakeRotation(radians);
var currentTransform = [layer affineTransform];
var newTransform = CGAffineTransformConcat(currentTransform, rotationTransform);
[layer setAffineTransform:newTransform];
}
@end
@@ -3384,7 +3298,7 @@ setBoundsOrigin:
@implementation CPView (Theming)
// MARK: Override
#pragma mark Override
- (BOOL)setThemeState:(ThemeState)aState
{
@@ -3421,7 +3335,7 @@ setBoundsOrigin:
}
// MARK: First responder
#pragma mark First responder
- (BOOL)becomeFirstResponder
{
@@ -3483,7 +3397,7 @@ setBoundsOrigin:
[_subviews[count] _notifyWindowDidResignKey];
}
// MARK: Theme Attributes
#pragma mark Theme Attributes
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
@@ -3818,16 +3732,6 @@ var CPAppearanceVibrantDark = [CPAppearance appearanceNamed:CPAppearanceNameVibr
[owners[i] updateTrackingAreas];
}
// needed by CPWindow's releasedWhenClosed property
- (void)_releaseRecursively
{
[_subviews makeObjectsPerformSelector:@selector(_releaseRecursively)];
[self _removeObservers];
[CPBinder unbindAllForObject:self];
[self removeFromSuperview];
}
@end
var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
@@ -3850,8 +3754,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
CPViewSizeScaleKey = @"CPViewSizeScaleKey",
CPViewIsScaledKey = @"CPViewIsScaledKey",
CPViewAppearanceKey = @"CPViewAppearanceKey",
CPViewTrackingAreasKey = @"CPViewTrackingAreasKey",
CPViewWantsLayerKey = @"CPViewWantsLayerKey";
CPViewTrackingAreasKey = @"CPViewTrackingAreasKey";
@implementation CPView (CPCoding)
@@ -3954,10 +3857,6 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
_opacity = 1.0;
[self setBackgroundColor:[aCoder decodeObjectForKey:CPViewBackgroundColorKey]];
if ([aCoder containsValueForKey:CPViewWantsLayerKey])
[self setWantsLayer:[aCoder decodeBoolForKey:CPViewWantsLayerKey]];
[self _setupViewFlags];
[self setAppearance:[aCoder decodeObjectForKey:CPViewAppearanceKey]];
@@ -4045,9 +3944,6 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
if (_identifier)
[aCoder encodeObject:_identifier forKey:CPReuseIdentifierKey];
if (_wantsLayer)
[aCoder encodeBool:_wantsLayer forKey:CPViewWantsLayerKey];
[aCoder encodeSize:[self scaleSize] forKey:CPViewScaleKey];
[aCoder encodeSize:[self _hierarchyScaleSize] forKey:CPViewSizeScaleKey];
[aCoder encodeBool:_isScaled forKey:CPViewIsScaledKey];
+1 -26
View File
@@ -166,7 +166,7 @@ var CPViewControllerCachedCibs;
if (!cib)
{
// if the cib isn't cached yet: fetch it and cache it
// if the cib isn't cached yet : fetch it and cache it
cib = [[CPCib alloc] initWithCibNamed:_cibName bundle:_cibBundle];
[CPViewControllerCachedCibs setObject:cib forKey:_cibName];
}
@@ -261,11 +261,6 @@ var CPViewControllerCachedCibs;
if (_view == nil && [cibOwner isKindOfClass:[CPDocument class]])
[self setView:[cibOwner valueForKey:@"view"]];
// If the view was just loaded, we must set its next responder.
// This is the first half of inserting the controller into the responder chain.
if (_view)
[_view setNextResponder:self];
if (!_view)
{
var reason = [CPString stringWithFormat:@"View for %@ could not be loaded from Cib or no view specified. Override loadView to load the view manually.", self];
@@ -413,28 +408,12 @@ var CPViewControllerCachedCibs;
[self willChangeValueForKey:"isViewLoaded"];
_view = aView;
// When the view is set manually, we must set its next responder.
if (_view)
[_view setNextResponder:self];
_isViewLoaded = aView != nil;
if (willChangeIsViewLoaded)
[self didChangeValueForKey:"isViewLoaded"];
}
/*!
@method nextResponder
@discussion The CPViewController implementation of this method returns the superview
of the view controller's view. This is the second half of the insertion,
completing the chain: view -> viewController -> superview.
*/
- (id)nextResponder
{
return [_view superview];
}
- (BOOL)automaticallyNotifiesObserversOfIsViewLoaded
{
return NO;
@@ -486,10 +465,6 @@ var CPViewControllerViewKey = @"CPViewControllerViewKey",
if (self)
{
_view = [aCoder decodeObjectForKey:CPViewControllerViewKey];
// When the view is unarchived, we must also set its next responder.
if (_view)
[_view setNextResponder:self];
_title = [aCoder decodeObjectForKey:CPViewControllerTitleKey];
_cibName = [aCoder decodeObjectForKey:CPViewControllerCibNameKey];
+9 -9
View File
@@ -57,8 +57,8 @@ CPVisualEffectStateInactive = 2;
}
// MARK: -
// MARK: Initialization
#pragma mark -
#pragma mark Initialization
- (id)initWithFrame:(CGRect)aFrame
{
@@ -74,8 +74,8 @@ CPVisualEffectStateInactive = 2;
}
// MARK: -
// MARK: CPVisualEffectView API
#pragma mark -
#pragma mark CPVisualEffectView API
/*! Sets the appearance of the CPVisualEffectView.
@@ -114,8 +114,8 @@ CPVisualEffectStateInactive = 2;
}
// MARK: -
// MARK: Utilities
#pragma mark -
#pragma mark Utilities
- (void)_setEffectEnabled:(BOOL)shouldEnable
{
@@ -156,8 +156,8 @@ CPVisualEffectStateInactive = 2;
}
// MARK: -
// MARK: CPCoding
#pragma mark -
#pragma mark CPCoding
- (id)initWithCoder:(CPCoder)aCoder
{
@@ -183,4 +183,4 @@ CPVisualEffectStateInactive = 2;
}
@end
@end
+1 -7
View File
@@ -39,8 +39,6 @@ CPWebViewProgressEstimateChangedNotification = "CPWebViewProgressEstimateChan
CPWebViewProgressStartedNotification = "CPWebViewProgressStartedNotification";
CPWebViewProgressFinishedNotification = "CPWebViewProgressFinishedNotification";
@global document
/*!
Automatically choose between AppKit (Cappuccino style) scrollbars and
native scrollbars. In this mode AppKit scrollbars are always used except
@@ -278,11 +276,7 @@ CPWebViewAppKitScrollMaxPollCount = 3;
if (_effectiveScrollMode === CPWebViewScrollAppKit)
{
// Use `[_scrollView documentVisibleRect]` instead of `[_frameView visibleRect]`.
// `visibleRect` accounts for window clipping, which collapses to 0x0 if the view is
// animated off-screen, resulting in an incorrectly shrunken web layout.
var visibleRect = [_scrollView documentVisibleRect];
var visibleRect = [_frameView visibleRect];
[_frameView setFrameSize:CGSizeMake(CGRectGetMaxX(visibleRect), CGRectGetMaxY(visibleRect))];
// try to get the document size so we can correctly set the frame
+3 -14
View File
@@ -186,7 +186,6 @@ var CPWindowActionMessageKeys = [
BOOL _constrainsToUsableScreen;
unsigned _shadowStyle;
BOOL _showsResizeIndicator;
BOOL _releasedWhenClosed @accessors(property=releasedWhenClosed);
int _positioningMask;
CGRect _positioningScreenRect;
@@ -269,8 +268,6 @@ var CPWindowActionMessageKeys = [
BOOL _inhibitUpdateTrackingAreas; // Used by the CPView when updating tracking areas
}
@global document
+ (Class)_binderClassForBinding:(CPString)aBinding
{
if ([aBinding hasPrefix:CPDisplayPatternTitleBinding])
@@ -1266,10 +1263,6 @@ CPTexturedBackgroundWindowMask
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_windowView addSubview:_contentView];
// The window view manages the exact layout of the content view (e.g. offsetting for the toolbar).
if ([_windowView respondsToSelector:@selector(tile)])
[_windowView tile];
/*
If the initial first responder has been set to something other than
the window, set it to the window because it will no longer be valid.
@@ -1951,8 +1944,7 @@ CPTexturedBackgroundWindowMask
return [[self firstResponder] keyUp:anEvent];
case CPKeyDown:
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter &&
!([anEvent modifierFlags] & (CPAlternateKeyMask | CPCommandKeyMask)))
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter)
{
if ([anEvent modifierFlags] & CPShiftKeyMask)
[self selectPreviousKeyView:self];
@@ -2542,9 +2534,6 @@ CPTexturedBackgroundWindowMask
[_parentWindow removeChildWindow:self];
[self _orderOutRecursively:NO];
[self _detachFromChildrenClosing:!_parentWindow];
if (_releasedWhenClosed)
[_contentView _releaseRecursively];
}
- (void)_detachFromChildrenClosing:(BOOL)shouldCloseChildren
@@ -4455,7 +4444,7 @@ var interpolate = function(fromValue, toValue, progress)
@end
// MARK: -
#pragma mark -
@implementation CPWindow (CSSTheming)
@@ -4466,7 +4455,7 @@ var interpolate = function(fromValue, toValue, progress)
@end
// MARK: -
#pragma mark -
function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel)
{
+4 -4
View File
@@ -30,8 +30,8 @@
}
// MARK: -
// MARK: Class methods
#pragma mark -
#pragma mark Class methods
+ (CPString)defaultThemeClass
{
@@ -80,8 +80,8 @@
}
// MARK: -
// MARK: drawing
#pragma mark -
#pragma mark drawing
- (void)drawRect:(CGRect)aRect
{
+2 -3
View File
@@ -61,10 +61,9 @@ var _CPCibClassSwapperClassNameKey = @"_CPCibClassSwapperClassNameKey",
if (!object)
{
var originalClassName = [aCoder decodeObjectForKey:_CPCibClassSwapperOriginalClassNameKey];
CPLog.warn(@"Unable to find class " + theClassName + @" referenced in cib file. Will try to use original class " + originalClassName + @".");
CPLog.error("Unable to find class " + theClassName + " referenced in cib file.");
object = [self allocObjectWithCoder:aCoder className:originalClassName];
object = [self allocObjectWithCoder:aCoder className:[aCoder decodeObjectForKey:_CPCibClassSwapperOriginalClassNameKey]];
}
}
-68
View File
@@ -1,68 +0,0 @@
/*
* CAAnimationGroup.j
* AppKit
* Created by Daniel Boehringer.
* Copyright 2025.
*
* Implements grouping for Core Animation.
*/
@import <Foundation/CPArray.j>
@import "CAAnimation.j"
@implementation CAAnimationGroup : CAAnimation
{
CPArray _animations;
}
+ (id)group
{
return [[self alloc] init];
}
- (id)init
{
if (self = [super init])
{
_animations = [];
}
return self;
}
- (void)setAnimations:(CPArray)anArray
{
if (_animations === anArray)
return;
_animations = anArray;
}
- (CPArray)animations
{
return _animations;
}
/*
Iterates through children and executes them recursively.
This effectively runs all grouped animations concurrently.
*/
- (void)runActionForKey:(CPString)aKey object:(id)anObject arguments:(CPDictionary)arguments
{
var count = [_animations count],
i = 0;
for (; i < count; i++)
{
var animation = [_animations objectAtIndex:i];
// Recursively call runActionForKey on the child.
// If the child is a CABasicAnimation, it will call [anObject addAnimation:...]
// If the child is another Group, it will recurse here.
if ([animation respondsToSelector:@selector(runActionForKey:object:arguments:)])
{
[animation runActionForKey:aKey object:anObject arguments:arguments];
}
}
}
@end
+2 -21
View File
@@ -1,28 +1,12 @@
@import <Foundation/CPObject.j>
@import "CAPropertyAnimation.j"
// Value calculation modes
kCAAnimationLinear = @"linear";
kCAAnimationDiscrete = @"discrete";
kCAAnimationPaced = @"paced";
kCAAnimationCubic = @"cubic";
kCAAnimationCubicPaced = @"cubicPaced";
// Rotation Mode Values
kCAAnimationRotateAuto = @"auto";
kCAAnimationRotateAutoReverse = @"autoReverse";
@implementation CAKeyframeAnimation : CAPropertyAnimation
{
CPArray _values @accessors(property=values);
CPArray _keyTimes @accessors(property=keyTimes);
CPArray _timingFunctions @accessors(property=timingFunctions);
id _path @accessors(property=path);
CPString _calculationMode @accessors(property=calculationMode);
CPString _rotationMode @accessors(property=rotationMode);
CPArray _tensionValues @accessors(property=tensionValues);
CPArray _continuityValues @accessors(property=continuityValues);
CPArray _biasValues @accessors(property=biasValues);
}
- (id)init
@@ -32,11 +16,8 @@ kCAAnimationRotateAutoReverse = @"autoReverse";
_values = [CPArray array];
_keyTimes = [CPArray array];
_timingFunctions = [CPArray array];
_tensionValues = [CPArray array];
_continuityValues = [CPArray array];
_biasValues = [CPArray array];
return self;
}
@end
@end
-246
View File
@@ -29,8 +29,6 @@
@import "CGGeometry.j"
@import "CPColor.j"
@import "CPView.j"
@import "CAMediaTimingFunction.j"
#define DOM(aLayer) aLayer._DOMElement
@@ -120,12 +118,8 @@ var CALayerRegisteredRunLoopUpdates = nil;
CGAffineTransform _transformToLayer;
CGAffineTransform _transformFromLayer;
CPMutableDictionary _activeAnimations;
}
@global document
/*!
Returns a new animation layer.
*/
@@ -164,8 +158,6 @@ var CALayerRegisteredRunLoopUpdates = nil;
_sublayers = [];
_activeAnimations = [CPMutableDictionary dictionary];
#if PLATFORM(DOM)
_DOMElement = document.createElement("div");
@@ -983,244 +975,6 @@ if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)
return _delegate;
}
/*
Adds an animation to the layer.
Supports CABasicAnimation for Numbers (opacity) and Points (position/anchorPoint).
The animation is exerted by means of periodically applying the keypath on the delegate
Only works if the delegate is set!
*/
- (void)addAnimation:(CAAnimation)anim forKey:(CPString)key
{
if (!anim) return;
// --- 1. Handle Animation Groups ---
// If it's a group, we simply schedule its children individually.
if ([anim respondsToSelector:@selector(animations)] && [anim animations])
{
var animations = [anim animations],
count = [animations count],
i = 0;
for (; i < count; i++)
{
var child = [animations objectAtIndex:i];
// Recurse: Add the child animation.
// We pass 'nil' for the key so the child's own 'keyPath'
// is used as the storage identifier in the dictionary.
[self addAnimation:child forKey:nil];
}
return;
}
// --- 2. Determine KeyPath ---
var keyPath = key;
// If the animation object has an explicit keyPath (like CABasicAnimation), use it.
if ([anim respondsToSelector:@selector(keyPath)] && [anim keyPath])
keyPath = [anim keyPath];
// If we can't determine a property to animate, we must abort.
if (!keyPath) return;
// --- 3. Determine Values ---
var startValue = ([anim respondsToSelector:@selector(fromValue)]) ? [anim fromValue] : nil;
// If startValue is missing, try to read it from the layer.
// We wrap this in a try-catch to prevent crashes if 'keyPath' is invalid.
if (startValue == nil)
{
try {
startValue = [[self delegate] valueForKey:keyPath];
}
catch (e) {
// The keyPath was likely invalid (not KVC compliant), abort.
return;
}
}
var endValue = ([anim respondsToSelector:@selector(toValue)]) ? [anim toValue] : nil;
if (endValue == nil)
return;
var duration = ([anim respondsToSelector:@selector(duration)]) ? [anim duration] : 0.25;
// Default to EaseInEaseOut if not specified
var timingFunction = ([anim respondsToSelector:@selector(timingFunction)]) ? [anim timingFunction] : [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// --- 4. Prepare Context ---
var context = {
"animation": anim,
"keyPath": keyPath,
"startValue": startValue,
"endValue": endValue,
"duration": duration * 1000.0, // ms
"timingFunction": timingFunction,
"startTime": null,
"requestId": null
};
// --- 5. Render Loop ---
var _self = self;
var renderLoop = function(timestamp) {
if ([_self _renderAnimationStep:context timestamp:timestamp])
context.requestId = window.requestAnimationFrame(renderLoop);
else
context.requestId = null;
};
// --- 6. Storage & Kickoff ---
// Use the keyPath as the identifier if no specific key was provided
var storageKey = (key && key.length > 0) ? key : keyPath;
// Remove any conflicting animation on this specific property/key
[self removeAnimationForKey:storageKey];
context.requestId = window.requestAnimationFrame(renderLoop);
[_activeAnimations setObject:context forKey:storageKey];
}
- (void)removeAnimationForKey:(CPString)key
{
var context = [_activeAnimations objectForKey:key];
if (context)
{
if (context.requestId !== null)
window.cancelAnimationFrame(context.requestId);
[_activeAnimations removeObjectForKey:key];
}
}
- (void)removeAllAnimations
{
var keys = [_activeAnimations allKeys],
count = [keys count];
while (count--)
[self removeAnimationForKey:[keys objectAtIndex:count]];
}
/*
Solves Cubic Bezier for t.
p1, p2 are the control points (x,y). p0 is 0,0, p3 is 1,1.
This is a simplified solver for standard Core Animation timing functions.
*/
- (float)_solveBezier:(float)t forTimingFunction:(CAMediaTimingFunction)tf
{
if (!tf) return t;
// Linear optimization
if (tf === [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear])
return t;
var points = [tf controlPoints]; // [c1x, c1y, c2x, c2y]
var p1x = points[0], p1y = points[1],
p2x = points[2], p2y = points[3];
// Simple polynomial evaluation (De Casteljau's algorithm/Cubic formula subset)
// Since we are usually dealing with standard easing, we can approximate 1D easing on the Y axis
// based on linear time X, or do a full solve.
// For brevity/speed in JS, we often approximate basic easing:
// 3t^2 * (1-t) + t^3 ... standard bezier blending functions
var cx = 3.0 * p1x;
var bx = 3.0 * (p2x - p1x) - cx;
var ax = 1.0 - cx - bx;
var cy = 3.0 * p1y;
var by = 3.0 * (p2y - p1y) - cy;
var ay = 1.0 - cy - by;
// Solve for X given t (time) using Newton-Raphson
var sampleT = t;
for (var i = 0; i < 5; i++) {
var x = ((ax * sampleT + bx) * sampleT + cx) * sampleT - t;
if (Math.abs(x) < 1e-3) break;
var d = (3.0 * ax * sampleT + 2.0 * bx) * sampleT + cx;
if (Math.abs(d) < 1e-6) break;
sampleT = sampleT - x / d;
}
// Solve for Y given derived T
return ((ay * sampleT + by) * sampleT + cy) * sampleT;
}
- (BOOL)_renderAnimationStep:(JSObject)context timestamp:(double)timestamp
{
if (context.startTime === null)
context.startTime = timestamp;
var elapsed = timestamp - context.startTime,
linearProgress = elapsed / context.duration;
if (linearProgress > 1.0) linearProgress = 1.0;
// Apply Timing Function
var progress = [self _solveBezier:linearProgress forTimingFunction:context.timingFunction];
var start = context.startValue,
end = context.endValue,
current = nil;
// Number
if (typeof start === "number")
{
current = start + (end - start) * progress;
}
// Point / Size / Rect
else if (start && start.x !== undefined && start.y !== undefined) // CGPoint
{
current = CGPointMake(start.x + (end.x - start.x) * progress,
start.y + (end.y - start.y) * progress);
}
else if (start && start.width !== undefined && start.height !== undefined) // CGSize
{
current = CGSizeMake(start.width + (end.width - start.width) * progress,
start.height + (end.height - start.height) * progress);
}
else if (start && start.origin !== undefined && start.size !== undefined) // CGRect
{
current = CGRectMake(
start.origin.x + (end.origin.x - start.origin.x) * progress,
start.origin.y + (end.origin.y - start.origin.y) * progress,
start.size.width + (end.size.width - start.size.width) * progress,
start.size.height + (end.size.height - start.size.height) * progress
);
}
if (current !== nil)
[[self delegate] setValue:current forKey:context.keyPath];
if (linearProgress >= 1.0)
{
var anim = context.animation;
// Cleanup
var shouldRemove = [anim respondsToSelector:@selector(isRemovedOnCompletion)] ? [anim isRemovedOnCompletion] : YES;
if (shouldRemove) {
// Find key by context identity to handle groups correctly
var keys = [_activeAnimations allKeys];
for (var i = 0; i < keys.length; i++) {
if ([_activeAnimations objectForKey:keys[i]] === context) {
[_activeAnimations removeObjectForKey:keys[i]];
break;
}
}
}
// Delegate
var delegate = [anim delegate];
if (delegate && [delegate respondsToSelector:@selector(animationDidStop:finished:)])
[delegate animationDidStop:anim finished:YES];
return NO;
}
return YES;
}
/* @ignore */
- (void)_setOwningView:(CPView)anOwningView
{
+11 -39
View File
@@ -164,36 +164,9 @@ var _CPAnimationContextStack = nil,
if ([animation isKindOfClass:[CAKeyframeAnimation class]])
{
values = [animation values];
keyTimes = [animation keyTimes];
timingFunctions = [animation timingFunctionsControlPoints];
var path = [animation path];
var calculationMode = [animation calculationMode] || kCAAnimationLinear; // Default to linear
var rotationMode = [animation rotationMode];
var tensionValues = [animation tensionValues];
var continuityValues = [animation continuityValues];
var biasValues = [animation biasValues];
return {
object: anObject,
root: anObject,
keypath: animatedKeyPath,
duration: duration,
completion: completionFunction,
// Keyframe-specific properties
values: values,
keytimes: keyTimes,
timingfunctions: timingFunctions,
path: path,
calculationMode: calculationMode,
rotationMode: rotationMode,
tensionValues: tensionValues,
continuityValues: continuityValues,
biasValues: biasValues
};
}
else
{
@@ -210,19 +183,18 @@ var _CPAnimationContextStack = nil,
values = [fromValue, toValue];
keyTimes = [0, 1];
timingFunctions = isBasicAnimation ? [animation timingFunctionControlPoints] : [_timingFunction controlPoints];
// Return a basic animation action
return {
object:anObject,
root:anObject,
keypath:animatedKeyPath,
values:values,
keytimes:keyTimes,
duration:duration,
timingfunctions:timingFunctions,
completion:completionFunction
};
}
return {
object:anObject,
root:anObject,
keypath:animatedKeyPath,
values:values,
keytimes:keyTimes,
duration:duration,
timingfunctions:timingFunctions,
completion:completionFunction
};
}
- (void)_flushAnimations
+2 -109
View File
@@ -1,3 +1,4 @@
@import "_CPObjectAnimator.j"
@import "CPView.j"
@import "CPCompatibility.j"
@@ -115,13 +116,6 @@ var DEFAULT_CSS_PROPERTIES = nil,
{
var target = anAction.object;
if (anAction.path)
{
// Path animations are handled differently. They don't map to standard properties like width/height.
// They use CSS offset-path.
return [self _addPathAnimation:animations forAction:anAction domElement:[target _DOMElement] identifier:[target UID]];
}
return [self _addAnimations:animations forAction:anAction domElement:[target _DOMElement] identifier:[target UID]];
}
@@ -138,20 +132,6 @@ var DEFAULT_CSS_PROPERTIES = nil,
[animations addObject:animation];
}
var calculationMode = anAction.calculationMode;
var timingFunctions = anAction.timingfunctions;
if (calculationMode === kCAAnimationDiscrete)
{
// For discrete animations, we use the steps() timing function.
// This makes the property jump between values.
timingFunctions = "steps(1, end)";
}
// For other calculation modes like 'paced' or 'cubic', more complex logic would be needed here.
// 'paced' would require pre-calculating keyTimes based on distance.
// 'cubic' would require generating many keyframes to simulate a spline.
// For now, we let them fall through to the default behavior.
var css_mapping = [self _cssPropertiesForKeyPath:anAction.keypath];
[css_mapping enumerateObjectsUsingBlock:function(aDict, anIndex, stop)
@@ -160,93 +140,10 @@ var DEFAULT_CSS_PROPERTIES = nil,
property = [aDict objectForKey:@"property"],
getter = [aDict objectForKey:@"value"];
animation.addPropertyAnimation(property, getter, anAction.duration, anAction.keytimes, anAction.values, timingFunctions, completionFunction);
animation.addPropertyAnimation(property, getter, anAction.duration, anAction.keytimes, anAction.values, anAction.timingfunctions, completionFunction);
}];
}
+ (void)_addPathAnimation:(CPArray)animations forAction:(id)anAction domElement:(Object)aDomElement identifier:(CPString)anIdentifier
{
var animation = [animations objectPassingTest:function(anim, idx, stop)
{
return anim.identifier == anIdentifier;
}];
if (animation == nil)
{
animation = new CSSAnimation(aDomElement, anIdentifier, [anAction.object debug_description]);
[animations addObject:animation];
}
// 1. Get the SVG path string.
var svgPath = [anAction.path SVGString];
// 2. Set the offset-path property.
aDomElement.style.offsetPath = "path('" + svgPath + "')";
// 3. Set the anchor point and neutralize the static position.
if (anAction.keypath === @"frameOrigin")
{
aDomElement.style.offsetAnchor = "0% 0%";
aDomElement.style.left = "0px";
aDomElement.style.top = "0px";
}
else
{
aDomElement.style.offsetAnchor = "50% 50%";
}
// 4. Set the rotation mode.
var rotationMode = anAction.rotationMode;
if (rotationMode === kCAAnimationRotateAuto)
{
aDomElement.style.offsetRotate = "auto";
}
else if (rotationMode === kCAAnimationRotateAutoReverse)
{
aDomElement.style.offsetRotate = "auto reverse";
}
else
{
aDomElement.style.offsetRotate = "0deg";
}
// Create a new completion handler that cleans up after the animation.
var originalCompletion = anAction.completion;
var cleanupCompletion = function()
{
// STEP 1: Run the original completion handler first. This is critical.
// It sets the final frameOrigin, which updates the static 'left' and 'top'
// styles to their final values, locking the view in the correct place.
if (originalCompletion)
originalCompletion();
// STEP 2: Now that the view's static position is correct, we can safely
// remove the animation-specific properties. This prevents state leakage.
aDomElement.style.offsetPath = null;
aDomElement.style.offsetAnchor = null;
aDomElement.style.offsetRotate = null;
};
// 5. Create the @keyframes rule, passing in our NEW cleanup handler.
var getter = function(start, current) { return current; };
var values = ["0%", "100%"];
var keytimes = [0, 1];
var timingfunctions;
if (anAction.calculationMode === kCAAnimationPaced) {
timingfunctions = "linear";
} else {
timingfunctions = [[CPAnimationContext currentContext] timingFunction];
}
animation.addPropertyAnimation("offset-distance", getter, anAction.duration, keytimes, values, timingfunctions, cleanupCompletion);
// 6. Keep fill-mode as 'forwards'. This is essential to prevent the view
// from jumping to (0,0) in the tiny gap between the animation ending
// and our completion handler running.
animation.setFillMode("forwards");
}
+ (CPArray)_cssPropertiesForKeyPath:(CPString)aKeyPath
{
return [[self _defaultCSSProperties] objectForKey:aKeyPath];
@@ -394,11 +291,7 @@ var frameToCSSTranslationTransformMatrix = function(start, current)
- (Object)_DOMElement
{
#if PLATFORM(DOM)
return _DOMElement;
#else
return nil;
#endif
}
- (CPString)debug_description
-5
View File
@@ -306,8 +306,3 @@ CSSAnimation.prototype.start = function()
return true;
};
CSSAnimation.prototype.setFillMode = function(aProperty)
{
this.target.style.setProperty(ANIMATION_FILL_MODE_PROPERTY, aProperty);
};
+208 -8
View File
@@ -2,13 +2,213 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CPBundleIdentifier</key>
<string>com.280n.AppKit</string>
<key>CPBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CPBundleName</key>
<string>AppKit</string>
<key>CPBundlePackageType</key>
<string>FMWK</string>
<key>CPBundleIdentifier</key>
<string>com.280n.AppKit</string>
<key>CPBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CPBundleName</key>
<string>AppKit</string>
<key>CPBundlePackageType</key>
<string>FMWK</string>
<key>CPCompileIncludeFileArray</key>
<array>
<string>Platform/Platform.h</string>
<string>Platform/DOM/CPDOMDisplayServer.h</string>
</array>
<key>CPFileTranslationDictionary</key>
<dict>
<key>_CPCibCustomObject.j</key>
<string>Cib/_CPCibCustomObject.j</string>
<key>_CPCibCustomView.j</key>
<string>Cib/_CPCibCustomView.j</string>
<key>CPCib.j</key>
<string>Cib/CPCib.j</string>
<key>_CPLocalizableString.j</key>
<string>Cib/_CPLocalizableString.j</string>
<key>CPCibConnector.j</key>
<string>Cib/CPCibConnector.j</string>
<key>CPCibHelpConnector.j</key>
<string>Cib/CPCibHelpConnector.j</string>
<key>_CPCibCustomResource.j</key>
<string>Cib/_CPCibCustomResource.j</string>
<key>_CPCibProxyObject.j</key>
<string>Cib/_CPCibProxyObject.j</string>
<key>_CPCibObjectData.j</key>
<string>Cib/_CPCibObjectData.j</string>
<key>CPCibLoading.j</key>
<string>Cib/CPCibLoading.j</string>
<key>_CPCibWindowTemplate.j</key>
<string>Cib/_CPCibWindowTemplate.j</string>
<key>CPCibRuntimeAttributesConnector.j</key>
<string>Cib/CPCibRuntimeAttributesConnector.j</string>
<key>_CPCibClassSwapper.j</key>
<string>Cib/_CPCibClassSwapper.j</string>
<key>CPCibControlConnector.j</key>
<string>Cib/CPCibControlConnector.j</string>
<key>CPCibOutletConnector.j</key>
<string>Cib/CPCibOutletConnector.j</string>
<key>_CPCibKeyedUnarchiver.j</key>
<string>Cib/_CPCibKeyedUnarchiver.j</string>
<key>CPCibBindingConnector.j</key>
<string>Cib/CPCibBindingConnector.j</string>
<key>_CPObjectAnimator.j</key>
<string>CoreAnimation/_CPObjectAnimator.j</string>
<key>CAAnimation.j</key>
<string>CoreAnimation/CAAnimation.j</string>
<key>CABackingStore.j</key>
<string>CoreAnimation/CABackingStore.j</string>
<key>CABasicAnimation.j</key>
<string>CoreAnimation/CABasicAnimation.j</string>
<key>CAFlashLayer.j</key>
<string>CoreAnimation/CAFlashLayer.j</string>
<key>CAKeyframeAnimation.j</key>
<string>CoreAnimation/CAKeyframeAnimation.j</string>
<key>CALayer.j</key>
<string>CoreAnimation/CALayer.j</string>
<key>CAMediaTimingFunction.j</key>
<string>CoreAnimation/CAMediaTimingFunction.j</string>
<key>CAPropertyAnimation.j</key>
<string>CoreAnimation/CAPropertyAnimation.j</string>
<key>CPAnimationContext.j</key>
<string>CoreAnimation/CPAnimationContext.j</string>
<key>CPViewAnimator.j</key>
<string>CoreAnimation/CPViewAnimator.j</string>
<key>CSSAnimation.j</key>
<string>CoreAnimation/CSSAnimation.j</string>
<key>CGGradient.j</key>
<string>CoreGraphics/CGGradient.j</string>
<key>CGContextText.j</key>
<string>CoreGraphics/CGContextText.j</string>
<key>CGAffineTransform.j</key>
<string>CoreGraphics/CGAffineTransform.j</string>
<key>CGColorSpace.j</key>
<string>CoreGraphics/CGColorSpace.j</string>
<key>CGContext.j</key>
<string>CoreGraphics/CGContext.j</string>
<key>CGGeometry.j</key>
<string>CoreGraphics/CGGeometry.j</string>
<key>CGContextVML.j</key>
<string>CoreGraphics/CGContextVML.j</string>
<key>CGPath.j</key>
<string>CoreGraphics/CGPath.j</string>
<key>CGContextCanvas.j</key>
<string>CoreGraphics/CGContextCanvas.j</string>
<key>CGColor.j</key>
<string>CoreGraphics/CGColor.j</string>
<key>_CPDatePickerCalendar.j</key>
<string>CPDatePicker/_CPDatePickerCalendar.j</string>
<key>_CPDatePickerTextField.j</key>
<string>CPDatePicker/_CPDatePickerTextField.j</string>
<key>_CPDatePickerClock.j</key>
<string>CPDatePicker/_CPDatePickerClock.j</string>
<key>CPDatePicker.j</key>
<string>CPDatePicker/CPDatePicker.j</string>
<key>_CPMenuWindow.j</key>
<string>CPMenu/_CPMenuWindow.j</string>
<key>CPMenu.j</key>
<string>CPMenu/CPMenu.j</string>
<key>_CPMenuManager.j</key>
<string>CPMenu/_CPMenuManager.j</string>
<key>_CPMenuBarWindow.j</key>
<string>CPMenu/_CPMenuBarWindow.j</string>
<key>CPMenuItem.j</key>
<string>CPMenuItem/CPMenuItem.j</string>
<key>_CPMenuItemView.j</key>
<string>CPMenuItem/_CPMenuItemView.j</string>
<key>_CPMenuItemStandardView.j</key>
<string>CPMenuItem/_CPMenuItemStandardView.j</string>
<key>_CPMenuItemSeparatorView.j</key>
<string>CPMenuItem/_CPMenuItemSeparatorView.j</string>
<key>_CPMenuItemMenuBarView.j</key>
<string>CPMenuItem/_CPMenuItemMenuBarView.j</string>
<key>_CPRuleEditorViewSliceRow.j</key>
<string>CPRuleEditor/_CPRuleEditorViewSliceRow.j</string>
<key>CPPredicateEditor.j</key>
<string>CPRuleEditor/CPPredicateEditor.j</string>
<key>CPRuleEditor.j</key>
<string>CPRuleEditor/CPRuleEditor.j</string>
<key>_CPRuleEditorLocalizer.j</key>
<string>CPRuleEditor/_CPRuleEditorLocalizer.j</string>
<key>CPPredicateEditorRowTemplate.j</key>
<string>CPRuleEditor/CPPredicateEditorRowTemplate.j</string>
<key>_CPRuleEditorViewSlice.j</key>
<string>CPRuleEditor/_CPRuleEditorViewSlice.j</string>
<key>CPRuleEditor_Constants.j</key>
<string>CPRuleEditor/CPRuleEditor_Constants.j</string>
<key>_CPPredicateEditorRowNode.j</key>
<string>CPRuleEditor/_CPPredicateEditorRowNode.j</string>
<key>_CPPredicateEditorTree.j</key>
<string>CPRuleEditor/_CPPredicateEditorTree.j</string>
<key>CPTextStorage.j</key>
<string>CPTextView/CPTextStorage.j</string>
<key>_CPRTFParser.j</key>
<string>CPTextView/_CPRTFParser.j</string>
<key>_CPRTFProducer.j</key>
<string>CPTextView/_CPRTFProducer.j</string>
<key>CPTextContainer.j</key>
<string>CPTextView/CPTextContainer.j</string>
<key>CPLayoutManager.j</key>
<string>CPTextView/CPLayoutManager.j</string>
<key>CPTextView.j</key>
<string>CPTextView/CPTextView.j</string>
<key>CPFontDescriptor.j</key>
<string>CPTextView/CPFontDescriptor.j</string>
<key>CPFontPanel.j</key>
<string>CPTextView/CPFontPanel.j</string>
<key>CPParagraphStyle.j</key>
<string>CPTextView/CPParagraphStyle.j</string>
<key>CPTypesetter.j</key>
<string>CPTextView/CPTypesetter.j</string>
<key>CPWindow_Constants.j</key>
<string>CPWindow/CPWindow_Constants.j</string>
<key>_CPStandardWindowView.j</key>
<string>CPWindow/_CPStandardWindowView.j</string>
<key>_CPShadowWindowView.j</key>
<string>CPWindow/_CPShadowWindowView.j</string>
<key>_CPToolTipWindowView.j</key>
<string>CPWindow/_CPToolTipWindowView.j</string>
<key>_CPBorderlessWindowView.j</key>
<string>CPWindow/_CPBorderlessWindowView.j</string>
<key>_CPBorderlessBridgeWindowView.j</key>
<string>CPWindow/_CPBorderlessBridgeWindowView.j</string>
<key>_CPDocModalWindowView.j</key>
<string>CPWindow/_CPDocModalWindowView.j</string>
<key>_CPModalWindowView.j</key>
<string>CPWindow/_CPModalWindowView.j</string>
<key>_CPPopoverWindowView.j</key>
<string>CPWindow/_CPPopoverWindowView.j</string>
<key>_CPWindowView.j</key>
<string>CPWindow/_CPWindowView.j</string>
<key>_CPTitleableWindowView.j</key>
<string>CPWindow/_CPTitleableWindowView.j</string>
<key>CPWindow.j</key>
<string>CPWindow/CPWindow.j</string>
<key>_CPHUDWindowView.j</key>
<string>CPWindow/_CPHUDWindowView.j</string>
<key>CPPlatform.j</key>
<string>Platform/CPPlatform.j</string>
<key>CPPlatformString.j</key>
<string>Platform/CPPlatformString.j</string>
<key>CPPlatformWindow.j</key>
<string>Platform/CPPlatformWindow.j</string>
<key>CPPlatformWindow+DOM.j</key>
<string>Platform/DOM/CPPlatformWindow+DOM.j</string>
<key>CPPlatformPasteboard.j</key>
<string>Platform/DOM/CPPlatformPasteboard.j</string>
<key>CPDOMWindowLayer.j</key>
<string>Platform/DOM/CPDOMWindowLayer.j</string>
<key>CPPlatformWindow+DOMKeys.j</key>
<string>Platform/DOM/CPPlatformWindow+DOMKeys.j</string>
</dict>
</dict>
</plist>
+12 -15
View File
@@ -1,22 +1,19 @@
require("../common.jake");
var framework = require("../Jake/frameworktask.js").framework,
BundleTask = require("../Jake/bundletask.js").BundleTask;
var framework = require("objective-j/jake").framework,
BundleTask = require("objective-j/jake").BundleTask;
const path = require("path");
const utilsFile = ObjectiveJ.utils.file;
$BUILD_PATH = path.join($BUILD_DIR, $CONFIGURATION, 'AppKit');
$BUILD_PATH = FILE.join($BUILD_DIR, $CONFIGURATION, 'AppKit');
AppKitFiles = new FileList("**/*.j").exclude('CoreGraphics/CGContextCanvas.j', 'CoreGraphics/CGContextVML.j', 'Themes/**/*', 'Tools/**/*', "Platform/DOM/CPPlatform.j", "Platform/DOM/CPPlatformString.j");
FIXME_fileDependency (path.join("Platform", "CPPlatform.j"), path.join("Platform", "DOM", "CPPlatform.j"));
FIXME_fileDependency (path.join("Platform", "CPPlatformString.j"), path.join("Platform", "DOM", "CPPlatformString.j"));
FIXME_fileDependency (FILE.join("Platform", "CPPlatform.j"), FILE.join("Platform", "DOM", "CPPlatform.j"));
FIXME_fileDependency (FILE.join("Platform", "CPPlatformString.j"), FILE.join("Platform", "DOM", "CPPlatformString.j"));
appKitTask = framework ("AppKit", function(appKitTask)
{
appKitTask.setBuildIntermediatesPath(path.join($BUILD_DIR, "AppKit.build", $CONFIGURATION))
appKitTask.setBuildIntermediatesPath(FILE.join($BUILD_DIR, "AppKit.build", $CONFIGURATION))
appKitTask.setBuildPath($BUILD_CONFIGURATION_DIR);
appKitTask.setAuthor("280 North, Inc.");
@@ -43,11 +40,11 @@ appKitTask = framework ("AppKit", function(appKitTask)
appKitTask.setCompilerFlags("-DDEBUG -g -S --inline-msg-send -Wno-unused-but-set-variable " + INCLUDES);
});
$BUILD_CJS_CAPPUCCINO_APPKIT = path.join($BUILD_CJS_CAPPUCCINO_FRAMEWORKS, "AppKit");
$BUILD_CJS_CAPPUCCINO_APPKIT = FILE.join($BUILD_CJS_CAPPUCCINO_FRAMEWORKS, "AppKit");
filedir ($BUILD_CJS_CAPPUCCINO_APPKIT, ["AppKit"], function()
{
utilsFile.cp_r(appKitTask.buildProductPath(), $BUILD_CJS_CAPPUCCINO_APPKIT);
cp_r(appKitTask.buildProductPath(), $BUILD_CJS_CAPPUCCINO_APPKIT);
});
subtasks (["Themes"], ["clean", "clobber"]);
@@ -56,11 +53,11 @@ task ("Theme", [$BUILD_CJS_CAPPUCCINO_APPKIT], function()
{
subjake(["Themes"], "build");
utilsFile.cp_r(path.join($BUILD_DIR, $CONFIGURATION, 'Aristo.blend'), path.join($BUILD_PATH, 'Resources', 'Aristo.blend'));
utilsFile.cp_r(path.join($BUILD_DIR, $CONFIGURATION, 'Aristo.blend'), path.join($BUILD_CJS_CAPPUCCINO_APPKIT, "Resources", "Aristo.blend"));
cp_r(FILE.join($BUILD_DIR, $CONFIGURATION, 'Aristo.blend'), FILE.join($BUILD_PATH, 'Resources', 'Aristo.blend'));
cp_r(FILE.join($BUILD_DIR, $CONFIGURATION, 'Aristo.blend'), FILE.join($BUILD_CJS_CAPPUCCINO_APPKIT, "Resources", "Aristo.blend"));
utilsFile.cp_r(path.join($BUILD_DIR, $CONFIGURATION, 'Aristo2.blend'), path.join($BUILD_PATH, 'Resources', 'Aristo2.blend'));
utilsFile.cp_r(path.join($BUILD_DIR, $CONFIGURATION, 'Aristo2.blend'), path.join($BUILD_CJS_CAPPUCCINO_APPKIT, "Resources", "Aristo2.blend"));
cp_r(FILE.join($BUILD_DIR, $CONFIGURATION, 'Aristo2.blend'), FILE.join($BUILD_PATH, 'Resources', 'Aristo2.blend'));
cp_r(FILE.join($BUILD_DIR, $CONFIGURATION, 'Aristo2.blend'), FILE.join($BUILD_CJS_CAPPUCCINO_APPKIT, "Resources", "Aristo2.blend"));
});
task ("build", ["AppKit", $BUILD_CJS_CAPPUCCINO_APPKIT, "Theme"]);
-9
View File
@@ -82,15 +82,6 @@ var PrimaryPlatformWindow = NULL;
CPWindow _currentMainWindow;
CPWindow _previousMainWindow;
// state of touch momentum scrolling
CPTimer _momentumScrollTimer;
float _touchVelocityX;
float _touchVelocityY;
float _lastTouchMoveTimestamp;
float _lastMomentumTimestamp;
BOOL _isTwoFingerScrolling;
#endif
}

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