Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2aacd3e807 | ||
|
|
1d2d37334c | ||
|
|
fbf4f426db | ||
|
|
5ecb83aafa | ||
|
|
77315d0268 | ||
|
|
f29be2f69c | ||
|
|
4a9f09570e | ||
|
|
fd46487c82 | ||
|
|
d92fc8c4d0 | ||
|
|
b87493bd5f | ||
|
|
6925bfd416 | ||
|
|
2d9d9f3e93 | ||
|
|
87f644e5ba | ||
|
|
5c57d241ca | ||
|
|
a966e7520f | ||
|
|
f6b3afa75c | ||
|
|
cfbdb1ce76 | ||
|
|
39ff2b46ab | ||
|
|
29d33193cd | ||
|
|
5babf3708e | ||
|
|
ab40761a6c | ||
|
|
cf5fa034b9 | ||
|
|
9d6ebf79cd | ||
|
|
c992b61ca2 | ||
|
|
3ba2f208d4 | ||
|
|
462bed7d67 | ||
|
|
d938d39929 | ||
|
|
abe2e14af4 | ||
|
|
3516e026d0 | ||
|
|
10db0dab7e | ||
|
|
e0967272e7 | ||
|
|
c0aa78eb76 | ||
|
|
09d03bf270 | ||
|
|
6399ac0c95 | ||
|
|
2639b7beeb | ||
|
|
062ff28a5c | ||
|
|
d59403cc5b | ||
|
|
9565ad2cb2 | ||
|
|
72375fe1cd | ||
|
|
ce696a50f1 | ||
|
|
a7a4740c60 | ||
|
|
d0b52f7118 | ||
|
|
a92243314a | ||
|
|
538adfeb61 | ||
|
|
2dc5458e63 | ||
|
|
043bcc5729 | ||
|
|
2ff51a3dbb | ||
|
|
5d8cea9ed6 | ||
|
|
f3dcfbac52 | ||
|
|
952fe0091b | ||
|
|
6ea22a96ad | ||
|
|
b4b28cda92 | ||
|
|
db542fa905 | ||
|
|
30f905a253 | ||
|
|
cc8e6e5820 | ||
|
|
67ee22c6d9 | ||
|
|
20830ae349 | ||
|
|
44b6f8cfc5 | ||
|
|
1be4ba0075 | ||
|
|
bbeba1e735 | ||
|
|
3b65f460d6 | ||
|
|
fbb3959e66 | ||
|
|
663f28e541 | ||
|
|
9321084d84 | ||
|
|
7e352676c9 | ||
|
|
4c8d7b0f1c | ||
|
|
827e56b23f | ||
|
|
04294cf8b9 | ||
|
|
7f0aa75ba1 | ||
|
|
cb7c8046e4 | ||
|
|
ad2bf51fd9 | ||
|
|
26333c3d2b | ||
|
|
cbf51a6127 | ||
|
|
4dbfe1f23e | ||
|
|
ea5221bfda | ||
|
|
ea0c9af0f8 | ||
|
|
ba43925f22 | ||
|
|
e51b1cb7e1 | ||
|
|
d64e55226d | ||
|
|
311939ecb9 | ||
|
|
e1d57aac3b | ||
|
|
102c741ca3 | ||
|
|
1a8093c314 | ||
|
|
7d8b02dd96 | ||
|
|
6e15ce0c00 | ||
|
|
9e8eff4543 | ||
|
|
eee0338b63 | ||
|
|
75bedef465 | ||
|
|
8adaa2d3c8 | ||
|
|
581a004ed0 | ||
|
|
88fbecb4d3 | ||
|
|
ac5c59f8ba | ||
|
|
add1776125 | ||
|
|
937865ef83 | ||
|
|
bf72b5cd13 | ||
|
|
88f12bad93 | ||
|
|
9b764caf79 | ||
|
|
56c2b9d755 | ||
|
|
814696070d | ||
|
|
0907f287af | ||
|
|
08c1973f11 | ||
|
|
3156de3a5c | ||
|
|
16209b3e3f | ||
|
|
9f9cc4c22c | ||
|
|
9def3ba765 | ||
|
|
5d5aa0a4d2 | ||
|
|
edf463ab31 | ||
|
|
7702fd9929 | ||
|
|
eae1dc2568 | ||
|
|
204739e220 | ||
|
|
41b3d7987a | ||
|
|
f5b0542d35 | ||
|
|
b754d60752 | ||
|
|
edd49f222e | ||
|
|
6cff27c7bb | ||
|
|
54d48e9292 | ||
|
|
07507eac1e | ||
|
|
912895d1ad | ||
|
|
5823db8319 | ||
|
|
b9b109daf0 | ||
|
|
ed5aa5b54c | ||
|
|
1457f5d1c8 | ||
|
|
e8febcda15 | ||
|
|
5c04acf840 | ||
|
|
a3ae869f1d | ||
|
|
7a3fab1d6d | ||
|
|
745df353c4 | ||
|
|
f7dd436b81 | ||
|
|
80f95e4d58 | ||
|
|
8fcf7581e6 | ||
|
|
12b58cd938 | ||
|
|
5144a5a579 | ||
|
|
b5ad11323c | ||
|
|
748d5b84bd | ||
|
|
b7bba7dc7f | ||
|
|
3e6fa883cf | ||
|
|
6f8a6bde62 | ||
|
|
75aa1e591a | ||
|
|
7568d91d28 | ||
|
|
5023588af4 | ||
|
|
98e5c0ed3e | ||
|
|
2e9e38ebaf | ||
|
|
f34265a864 | ||
|
|
cb53db05dc | ||
|
|
88c64c5071 | ||
|
|
abebe2e081 | ||
|
|
fabd1df4dd | ||
|
|
abbbc6e8f0 | ||
|
|
a6e4712524 | ||
|
|
0798e47459 | ||
|
|
5c6b82c172 | ||
|
|
2c7c53084f | ||
|
|
04695597b2 | ||
|
|
956bc7fe14 | ||
|
|
75117d77a9 | ||
|
|
3190b57ad3 | ||
|
|
94ea70f4d4 | ||
|
|
d3a0d90ce7 | ||
|
|
010fe01265 | ||
|
|
f8ee950339 | ||
|
|
f7aa5c15c9 | ||
|
|
17ec9a0cae | ||
|
|
42a68812a9 | ||
|
|
82f26fbdc0 | ||
|
|
f19793f8a3 | ||
|
|
13940ad38f | ||
|
|
abe4dd7abc | ||
|
|
c6a2d55821 | ||
|
|
dd56b75bd6 | ||
|
|
cd2a66a644 | ||
|
|
32fd299d1d | ||
|
|
fc9e5b3a4c | ||
|
|
0ac0a84b18 | ||
|
|
dad6d613e6 | ||
|
|
fd23867c93 | ||
|
|
59b7c55ba2 | ||
|
|
0997042dcd | ||
|
|
847118c35b | ||
|
|
5a086243f5 | ||
|
|
f228934e54 | ||
|
|
5404b0bbfa | ||
|
|
c2b1130e23 | ||
|
|
816d0d3636 | ||
|
|
7e28bbeee9 | ||
|
|
4d39466f14 | ||
|
|
358251ec44 | ||
|
|
1fc253d943 | ||
|
|
b1c8265f4c | ||
|
|
02b4187742 | ||
|
|
ba189afaf5 | ||
|
|
bf28b8bbc6 | ||
|
|
6b6a6a6f1d | ||
|
|
a85e1b8db2 | ||
|
|
529c3807e4 | ||
|
|
ac4faf3a4c | ||
|
|
89461fd7e7 | ||
|
|
9da107fc45 | ||
|
|
05b922dc9c | ||
|
|
a0c594a86a | ||
|
|
344d2eb78c | ||
|
|
5f7514bb6f | ||
|
|
cbcd3bbbb6 | ||
|
|
d99c583d79 | ||
|
|
fcc1be118e | ||
|
|
964cb66fa8 | ||
|
|
ecc39ff11f | ||
|
|
ff1f5bdcdf | ||
|
|
ac6c3b556e | ||
|
|
7a69d6e711 | ||
|
|
3a50d3562f | ||
|
|
77dae63cb9 | ||
|
|
f636491e75 | ||
|
|
c1e882ace1 | ||
|
|
a789ccb431 | ||
|
|
3892ac7885 | ||
|
|
01177a7e02 | ||
|
|
bbc6ef7602 | ||
|
|
e86e276a14 | ||
|
|
e9befe9084 | ||
|
|
8c1b217c7a | ||
|
|
caf26373c7 | ||
|
|
642b58659e | ||
|
|
970cd5d1b0 | ||
|
|
40a647f80c | ||
|
|
461c8ef9d5 | ||
|
|
c68a680af0 | ||
|
|
7f942e54f0 | ||
|
|
d6c0f8802a | ||
|
|
c943eefec3 | ||
|
|
ff140b8c4c | ||
|
|
530774d52c | ||
|
|
3834751aa3 | ||
|
|
fbd7950b87 | ||
|
|
0e26f27540 | ||
|
|
3b39b64e1b | ||
|
|
dbedbfddfc | ||
|
|
df3264de94 | ||
|
|
1c0a450b36 | ||
|
|
9d5e772d0a | ||
|
|
f4cedf5acc | ||
|
|
89516d4a38 | ||
|
|
2aa5b0a1ad | ||
|
|
dff3b249d9 | ||
|
|
59e16f9e61 | ||
|
|
2830feed60 | ||
|
|
e4588cf235 | ||
|
|
3f55ee6284 | ||
|
|
bef9c8c6f9 | ||
|
|
ce04c97053 | ||
|
|
7f59ce782b | ||
|
|
464ddf269d | ||
|
|
13cf238655 | ||
|
|
a10058fc7f | ||
|
|
d183f6c434 | ||
|
|
a5cc45051e | ||
|
|
5456bb597b | ||
|
|
f39dfef168 | ||
|
|
96b1dbdb60 | ||
|
|
ebcee1154e | ||
|
|
ae6e3f2c47 | ||
|
|
d90b603572 | ||
|
|
f9e6cf3da9 | ||
|
|
07d4ebdf9f | ||
|
|
08d0dfd190 | ||
|
|
d38eac1458 | ||
|
|
2b60c67cca | ||
|
|
0ee572c094 | ||
|
|
7ebb457ff9 | ||
|
|
92eccd36a8 | ||
|
|
edcbb02a21 | ||
|
|
d6b4ebaa30 | ||
|
|
334776df12 | ||
|
|
8bae7048b1 | ||
|
|
e95c2b9efc | ||
|
|
dbc0999f66 | ||
|
|
53d4a714e2 | ||
|
|
a64d1a1ed6 | ||
|
|
088fdf4e05 | ||
|
|
06e56eb37a | ||
|
|
3dd3a8733b | ||
|
|
0b81dd718c | ||
|
|
fcc4d34098 | ||
|
|
323c6342d8 | ||
|
|
6a6348e841 | ||
|
|
9305b93506 | ||
|
|
9a397bf03f | ||
|
|
6c2a50f767 | ||
|
|
7274bc5ac2 | ||
|
|
b6f6102970 | ||
|
|
05773d745c | ||
|
|
34f58026df | ||
|
|
525493574c | ||
|
|
33c0dcd482 | ||
|
|
ec6c08e9ea | ||
|
|
ffa91e0f59 | ||
|
|
d030ef15b5 | ||
|
|
a9e9dab7e6 | ||
|
|
6a5545b316 | ||
|
|
36ec2d3027 | ||
|
|
bf9ffb8a95 | ||
|
|
1b2aadc6f0 | ||
|
|
2eae5373bc | ||
|
|
b173c0978d | ||
|
|
0ffbca776f | ||
|
|
b6aec87551 | ||
|
|
538987e73e | ||
|
|
95e5a7430a | ||
|
|
bae8e1c6b0 | ||
|
|
f9b78273ad | ||
|
|
8aed3787fd | ||
|
|
39afdd57cd | ||
|
|
6c7ee733fc | ||
|
|
1a7baf0b0d | ||
|
|
62d87917a8 | ||
|
|
bf5381e1a3 | ||
|
|
25f22d8fc8 | ||
|
|
398b284910 | ||
|
|
43cc45b1f9 | ||
|
|
8c237c5f8e | ||
|
|
e1fefd1018 | ||
|
|
5618b628cf | ||
|
|
62e44d3d12 | ||
|
|
a40fbd7961 | ||
|
|
05811ca965 | ||
|
|
558bcc1fa3 | ||
|
|
ac025c9f38 | ||
|
|
130113a99d | ||
|
|
41f349534f | ||
|
|
905deb8f3a | ||
|
|
2e361b447d | ||
|
|
4a858cda3e | ||
|
|
ca85476d75 | ||
|
|
c25005de43 | ||
|
|
1d5b9e6b7d | ||
|
|
d31ddcbe2e | ||
|
|
00140ac04a | ||
|
|
80dffdec14 | ||
|
|
2202597b8f | ||
|
|
1edcae005d | ||
|
|
c5415eae05 | ||
|
|
b1c99a5f0a | ||
|
|
2c3ac4c1c3 | ||
|
|
71a468f932 | ||
|
|
a1920c55c3 | ||
|
|
e3679c1f5c | ||
|
|
e99efff0c6 | ||
|
|
5e982e64dc | ||
|
|
d797b2fe80 | ||
|
|
36b4508afc | ||
|
|
ee457792c7 | ||
|
|
ac227569fd | ||
|
|
3d1b2f9560 | ||
|
|
a97bb72800 |
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* AppKit.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CALayer.j"
|
||||
@import "CPAlert.j"
|
||||
@import "CPAnimation.j"
|
||||
@import "CPApplication.j"
|
||||
@import "CPButton.j"
|
||||
@import "CPClipView.j"
|
||||
@import "CPCollectionView.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPColorPanel.j"
|
||||
@import "CPColorWell.j"
|
||||
@import "CPCompatibility.j"
|
||||
@import "CPControl.j"
|
||||
@import "CPCookie.j"
|
||||
@import "CPDocument.j"
|
||||
@import "CPDocumentController.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPFlashMovie.j"
|
||||
@import "CPFlashView.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPFontManager.j"
|
||||
@import "CPGeometry.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPMenuItem.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPPasteboard.j"
|
||||
@import "CPPopUpButton.j"
|
||||
@import "CPProgressIndicator.j"
|
||||
@import "CPResponder.j"
|
||||
@import "CPScrollView.j"
|
||||
@import "CPScroller.j"
|
||||
@import "CPSecureTextField.j"
|
||||
@import "CPSegmentedControl.j"
|
||||
@import "CPShadow.j"
|
||||
@import "CPSlider.j"
|
||||
@import "CPSplitView.j"
|
||||
@import "CPTabView.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPToolbar.j"
|
||||
@import "CPToolbarItem.j"
|
||||
@import "CPView.j"
|
||||
@import "CPWebView.j"
|
||||
@import "CPWindow.j"
|
||||
@import "CPWindowController.j"
|
||||
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>AppKit</string>
|
||||
<key>Targets</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Name</key>
|
||||
<string>AppKit</string>
|
||||
<key>Excluded</key>
|
||||
<array>
|
||||
<string>CPOutlineView.j</string>
|
||||
<string>CPTableColumn.j</string>
|
||||
<string>CPTableView.j</string>
|
||||
<!-- These get compiled into CGContext -->
|
||||
<string>CoreGraphics/CGContextCanvas.j</string>
|
||||
<string>CoreGraphics/CGContextVML.j</string>
|
||||
</array>
|
||||
<key>Flags</key>
|
||||
<string>-DPLATFORM_DOM</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Name</key>
|
||||
<string>AppKit-Rhino</string>
|
||||
<key>Excluded</key>
|
||||
<array>
|
||||
<string>CPOutlineView.j</string>
|
||||
<string>CPTableColumn.j</string>
|
||||
<string>CPTableView.j</string>
|
||||
<!-- These get compiled into CGContext -->
|
||||
<string>CoreGraphics/CGContextCanvas.j</string>
|
||||
<string>CoreGraphics/CGContextVML.j</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>Configurations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>Name</key>
|
||||
<string>Debug</string>
|
||||
<key>Flags</key>
|
||||
<string>-DDEBUG -g</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>Name</key>
|
||||
<string>Release</string>
|
||||
<key>Flags</key>
|
||||
<string>-O</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* CPAlert.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Jake MacMullin.
|
||||
* Copyright 2008, Jake MacMullin.
|
||||
*
|
||||
* 11/10/2008 Ross Boucher
|
||||
* - Make it conform to style guidelines, general cleanup and ehancements
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import <AppKit/CPApplication.j>
|
||||
@import <AppKit/CPButton.j>
|
||||
@import <AppKit/CPColor.j>
|
||||
@import <AppKit/CPFont.j>
|
||||
@import <AppKit/CPImage.j>
|
||||
@import <AppKit/CPImageView.j>
|
||||
@import <AppKit/CPPanel.j>
|
||||
@import <AppKit/CPTextField.j>
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPAlertStyle
|
||||
*/
|
||||
CPWarningAlertStyle = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPAlertStyle
|
||||
*/
|
||||
CPInformationalAlertStyle = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPAlertStyle
|
||||
*/
|
||||
CPCriticalAlertStyle = 2;
|
||||
|
||||
|
||||
var CPAlertWarningImage,
|
||||
CPAlertInformationImage,
|
||||
CPAlertErrorImage;
|
||||
|
||||
/*
|
||||
CPAlert is an alert panel that can be displayed modally to present the
|
||||
user with a message and one or more options.
|
||||
|
||||
It can be used to display an information message (<pre>CPInformationalAlertStyle</pre>),
|
||||
a warning message (<pre>CPWarningAlertStyle</pre> - which is the default), or a critical
|
||||
alert (<pre>CPCriticalAlertStyle</pre>). In each case the user can be presented with one
|
||||
or more options by adding buttons using the <pre>addButtonWithTitle:</pre> method.
|
||||
|
||||
The panel is displayed modally by calling <pre>runModal</pre> and once the user has
|
||||
dismissed the panel, a message will be sent to the panel's delegate (if set), informing
|
||||
it which button was clicked (see delegate methods).
|
||||
|
||||
@delegate -(void)alertDidEnd:(CPAlert)theAlert returnCode:(int)returnCode;
|
||||
Called when the user dismisses the alert by clicking one of the buttons.
|
||||
@param theAlert the alert panel that the user dismissed
|
||||
@param returnCode the index of the button that the user clicked (starting from 0,
|
||||
representing the first button added to the alert which appears on the
|
||||
right, 1 representing the next button to the left and so on)
|
||||
*/
|
||||
@implementation CPAlert : CPObject
|
||||
{
|
||||
CPPanel _alertPanel;
|
||||
|
||||
CPTextField _messageLabel;
|
||||
CPImageView _alertImageView;
|
||||
|
||||
CPAlertStyle _alertStyle;
|
||||
int _buttonCount;
|
||||
|
||||
id _delegate;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != CPAlert)
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:[self class]];
|
||||
|
||||
CPAlertWarningImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPAlert/dialog-warning.png"]
|
||||
size:CGSizeMake(32,32)];
|
||||
|
||||
CPAlertInformationImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPAlert/dialog-information.png"]
|
||||
size:CGSizeMake(32,32)];
|
||||
|
||||
CPAlertErrorImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPAlert/dialog-error.png"]
|
||||
size:CGSizeMake(32,32)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a <pre>CPAlert</pre> panel with the default alert style (<pre>CPWarningAlertStyle</pre>).
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_buttonCount = 0;
|
||||
_alertStyle = CPWarningAlertStyle;
|
||||
|
||||
_alertPanel = [[CPPanel alloc] initWithContentRect:CGRectMake(0, 0, 300, 150) styleMask:CPHUDBackgroundWindowMask|CPTitledWindowMask];
|
||||
[_alertPanel setFloatingPanel:YES];
|
||||
[_alertPanel center];
|
||||
|
||||
_messageLabel = [[CPTextField alloc] initWithFrame: CGRectMake(70,10, 200, 100)];
|
||||
[_messageLabel setFont: [CPFont fontWithName: "Helvetica Neue" size: 12.0]];
|
||||
[_messageLabel setTextColor: [CPColor whiteColor]];
|
||||
[_messageLabel setLineBreakMode:CPLineBreakByWordWrapping];
|
||||
|
||||
[[_alertPanel contentView] addSubview: _messageLabel];
|
||||
|
||||
_alertImageView = [[CPImageView alloc] initWithFrame:CGRectMake(25,12,32,32)];
|
||||
|
||||
[[_alertPanel contentView] addSubview: _alertImageView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver’s delegate.
|
||||
@param delegate - Delegate for the alert. nil removes the delegate.
|
||||
*/
|
||||
- (void)setDelegate:(id)delegate
|
||||
{
|
||||
_delegate = delegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets the receiver's delegate.
|
||||
*/
|
||||
- (void)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the alert style of the receiver.
|
||||
@param style - Alert style for the alert.
|
||||
*/
|
||||
- (void)setAlertStyle:(CPAlertStyle)style
|
||||
{
|
||||
_alertStyle = style;
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets the alert style.
|
||||
*/
|
||||
- (CPAlertStyle)alertStyle
|
||||
{
|
||||
return _alertStyle;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set’s the receiver’s message text, or title, to a given text.
|
||||
@param messageText - Message text for the alert.
|
||||
*/
|
||||
- (void)setMessageText:(CPString)messageText
|
||||
{
|
||||
[_messageLabel setStringValue:messageText];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return's the receiver's message text body.
|
||||
*/
|
||||
- (CPString)messageText
|
||||
{
|
||||
return [_messageLabel stringValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds a button with a given title to the receiver.
|
||||
Buttons will be added starting from the right hand side of the <pre>CPAlert</pre> panel.
|
||||
The first button will have the index 0, the second button 1 and so on.
|
||||
|
||||
You really shouldn't need more than 3 buttons.
|
||||
*/
|
||||
- (void)addButtonWithTitle:(CPString)title
|
||||
{
|
||||
var button = [[CPButton alloc] initWithFrame:CGRectMake(190 - (_buttonCount * 90),80,80,18)];
|
||||
|
||||
[button setTitle:title];
|
||||
[button setTarget:self];
|
||||
[button setTag:_buttonCount];
|
||||
[button setBezelStyle:CPHUDBezelStyle];
|
||||
[button setAction:@selector(_notifyDelegate:)];
|
||||
|
||||
[[_alertPanel contentView] addSubview:button];
|
||||
|
||||
_buttonCount++;
|
||||
}
|
||||
|
||||
/*!
|
||||
Displays the <pre>CPAlert</pre> panel as a modal dialog. The user will not be
|
||||
able to interact with any other controls until s/he has dismissed the alert
|
||||
by clicking on one of the buttons.
|
||||
*/
|
||||
- (void)runModal
|
||||
{
|
||||
switch (_alertStyle)
|
||||
{
|
||||
case CPWarningAlertStyle: [_alertImageView setImage:CPAlertWarningImage];
|
||||
break;
|
||||
case CPInformationalAlertStyle: [_alertImageView setImage:CPAlertInformationImage];
|
||||
break;
|
||||
case CPCriticalAlertStyle: [_alertImageView setImage:CPAlertErrorImage];
|
||||
break;
|
||||
}
|
||||
|
||||
[CPApp runModalForWindow:_alertPanel];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_notifyDelegate:(id)button
|
||||
{
|
||||
if (_delegate && [_delegate respondsToSelector:@selector(alertDidEnd:returnCode:)])
|
||||
[_delegate alertDidEnd:self returnCode:[button tag]];
|
||||
|
||||
[CPApp abortModal];
|
||||
[_alertPanel close];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* CPAnimation.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CAMediaTimingFunction.j"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseInOut = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseIn = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationEaseOut = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPAnimationCurve
|
||||
*/
|
||||
CPAnimationLinear = 3;
|
||||
|
||||
ACTUAL_FRAME_RATE = 0;
|
||||
|
||||
/*! @class CPAnimation
|
||||
Manages an animation. Contains timing and progress information.
|
||||
|
||||
@par Delegate Methods
|
||||
|
||||
@delegate -(BOOL)animationShouldStart:(CPAnimation)animation;
|
||||
Called at the beginning of <code>startAnimation</code>.
|
||||
@param animation the animation that will start
|
||||
@return <code>YES</code> allows the animation to start.
|
||||
<code>NO</code> stops the animation.
|
||||
|
||||
@delegate -(void)animationDidEnd:(CPAnimation)animation;
|
||||
Called when an animation has completed.
|
||||
@param animation the animation that completed
|
||||
|
||||
@delegate -(void)animationDidStop:(CPAnimation)animation;
|
||||
Called when the animation was stopped (before completing).
|
||||
@param animation the animation that was stopped
|
||||
|
||||
@delegate - (float)animation:(CPAnimation)animation valueForProgress:(float)progress;
|
||||
The value from this method will be returned when CPAnimation's
|
||||
<code>currentValue</code> method is called.
|
||||
@param animation the animation to obtain the curve value for
|
||||
@param progress the current animation progress
|
||||
@return the curve value
|
||||
*/
|
||||
@implementation CPAnimation : CPObject
|
||||
{
|
||||
CPTimeInterval _startTime;
|
||||
CPTimeInterval _duration;
|
||||
|
||||
CPAnimationCurve _animationCurve;
|
||||
CAMediaTimingFunction _timingFunction;
|
||||
|
||||
float _frameRate;
|
||||
float _progress;
|
||||
|
||||
id _delegate;
|
||||
CPTimer _timer;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the animation with a duration and animation curve.
|
||||
@param aDuration the length of the animation
|
||||
@param anAnimationCurve defines the animation's pace
|
||||
@throws CPInvalidArgumentException if an invalid animation curve is specified
|
||||
*/
|
||||
- (id)initWithDuration:(float)aDuration animationCurve:(CPAnimationCurve)anAnimationCurve
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_duration = MAX(0.0, aDuration);
|
||||
_animationCurve = anAnimationCurve;
|
||||
_frameRate = 60.0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation's pace.
|
||||
@param anAnimationCurve the animation's pace
|
||||
@throws CPInvalidArgumentException if an invalid animation curve is specified
|
||||
*/
|
||||
- (void)setAnimationCurve:(CPAnimationCurve)anAnimationCurve
|
||||
{
|
||||
_animationCurve = anAnimationCurve;
|
||||
|
||||
var timingFunctionName = kCAMediaTimingFunctionLinear;
|
||||
|
||||
switch (_animationCurve)
|
||||
{
|
||||
case CPAnimationEaseInOut: timingFunctionName = kCAMediaTimingFunctionEaseInEaseOut;
|
||||
break;
|
||||
|
||||
case CPAnimationEaseIn: timingFunctionName = kCAMediaTimingFunctionEaseIn;
|
||||
break;
|
||||
|
||||
case CPAnimationEaseOut: timingFunctionName = kCAMediaTimingFunctionEaseOut;
|
||||
break;
|
||||
|
||||
default: [CPException raise:CPInvalidArgumentException
|
||||
reason:"Invalid value provided for animation curve"];
|
||||
break;
|
||||
}
|
||||
|
||||
_timingFunction = [CAMediaTimingFunction functionWithName:timingFunctionName];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's pace
|
||||
*/
|
||||
- (CPAnimationCurve)animationCurve
|
||||
{
|
||||
return _animationCurve;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation's length.
|
||||
@param aDuration the new animation length
|
||||
@throws CPInvalidArgumentException if <code>aDuration</code> is negative
|
||||
*/
|
||||
- (void)setDuration:(CPTimeInterval)aDuration
|
||||
{
|
||||
if (aDuration < 0)
|
||||
[CPException raise:CPInvalidArgumentException reason:"aDuration can't be negative"];
|
||||
|
||||
_duration = aDuration;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the length of the animation.
|
||||
*/
|
||||
- (CPTimeInterval)duration
|
||||
{
|
||||
return _duration;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation frame rate. This is not a guaranteed frame rate. 0 means to go as fast as possible.
|
||||
@param frameRate the new desired frame rate
|
||||
@throws CPInvalidArgumentException if <code>frameRate</code> is negative
|
||||
*/
|
||||
- (void)setFrameRate:(float)frameRate
|
||||
{
|
||||
if (frameRate < 0)
|
||||
[CPException raise:CPInvalidArgumentException reason:"frameRate can't be negative"];
|
||||
|
||||
_frameRate = frameRate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the desired frame rate.
|
||||
*/
|
||||
- (float)frameRate
|
||||
{
|
||||
return _frameRate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's delegate
|
||||
*/
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation's delegate.
|
||||
@param aDelegate the new delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Starts the animation. The method calls <code>animationShouldStart:</code>
|
||||
on the delegate (if it implements it) to see if the animation
|
||||
should begin.
|
||||
*/
|
||||
- (void)startAnimation
|
||||
{
|
||||
// If we're already animating, or our delegate stops us, animate.
|
||||
if (_timer || _delegate && [_delegate respondsToSelector:@selector(animationShouldStart)] && ![_delegate animationShouldStart:self])
|
||||
return;
|
||||
|
||||
_progress = 0.0;
|
||||
ACTUAL_FRAME_RATE = 0;
|
||||
_startTime = new Date();
|
||||
|
||||
_timer = [CPTimer scheduledTimerWithTimeInterval:0.0 target:self selector:@selector(animationTimerDidFire:) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (void)animationTimerDidFire:(CPTimer)aTimer
|
||||
{
|
||||
var elapsed = new Date() - _startTime,
|
||||
progress = MIN(1.0, 1.0 - (_duration - elapsed / 1000.0) / _duration);
|
||||
|
||||
++ACTUAL_FRAME_RATE;
|
||||
|
||||
[self setCurrentProgress:progress];
|
||||
|
||||
if (progress === 1.0)
|
||||
{
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animationDidEnd:)])
|
||||
[_delegate animationDidEnd:self];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops the animation before it has completed.
|
||||
*/
|
||||
- (void)stopAnimation
|
||||
{
|
||||
if (!_timer)
|
||||
return;
|
||||
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(animationDidStop:)])
|
||||
[_delegate animationDidStop:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the animation
|
||||
is running.
|
||||
*/
|
||||
- (BOOL)isAnimating
|
||||
{
|
||||
return _timer;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation's progress.
|
||||
@param aProgress the animation's progress
|
||||
*/
|
||||
- (void)setCurrentProgress:(float)aProgress
|
||||
{
|
||||
_progress = aProgress;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's progress
|
||||
*/
|
||||
- (float)currentProgress
|
||||
{
|
||||
return _progress;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's timing progress.
|
||||
*/
|
||||
- (float)currentValue
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(animation:valueForProgress:)])
|
||||
return [_delegate animation:self valueForProgress:_progress];
|
||||
|
||||
if (_animationCurve == CPAnimationLinear)
|
||||
return _progress;
|
||||
|
||||
alert("IMPLEMENT ANIMATION CURVES!!!");
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,748 @@
|
||||
/*
|
||||
* CPApplication.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@import "CPCompatibility.j"
|
||||
@import "CPEvent.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPResponder.j"
|
||||
@import "CPDocumentController.j"
|
||||
|
||||
|
||||
CPApp = nil;
|
||||
|
||||
CPApplicationWillFinishLaunchingNotification = @"CPApplicationWillFinishLaunchingNotification";
|
||||
CPApplicationDidFinishLaunchingNotification = @"CPApplicationDidFinishLaunchingNotification";
|
||||
|
||||
CPRunStoppedResponse = -1000;
|
||||
CPRunAbortedResponse = -1001;
|
||||
CPRunContinuesResponse = -1002;
|
||||
|
||||
/*! @class CPApplication
|
||||
|
||||
CPApplication is THE way to start up the Cappucino framework for your application to use.
|
||||
Every GUI application has exactly one instance of CPApplication (or of a custom subclass of
|
||||
CPApplication). Your program's main() function can create that instance by calling the
|
||||
<code>CPApplicationMain</code> function. A simple example looks like this:
|
||||
|
||||
<pre>
|
||||
function main(args, namedArgs)
|
||||
{
|
||||
CPApplicationMain(args, namedArgs);
|
||||
}
|
||||
</pre>
|
||||
|
||||
@delegate -(void)applicationDidFinishLaunching:(CPNotification)aNotification;
|
||||
Sent from the notification center after the app initializes, but before
|
||||
receiving events.
|
||||
@param aNotification contains information about the event
|
||||
|
||||
@delegate -(void)applicationWillFinishLaunching:(CPNotification)aNotification;
|
||||
Sent from the notification center before the app is initialized.
|
||||
@param aNotification contains information about the event
|
||||
*/
|
||||
@implementation CPApplication : CPResponder
|
||||
{
|
||||
CPArray _eventListeners;
|
||||
|
||||
CPEvent _currentEvent;
|
||||
|
||||
CPArray _windows;
|
||||
CPWindow _keyWindow;
|
||||
CPWindow _mainWindow;
|
||||
|
||||
CPMenu _mainMenu;
|
||||
CPDocumentController _documentController;
|
||||
|
||||
CPModalSession _currentSession;
|
||||
|
||||
//
|
||||
id _delegate;
|
||||
|
||||
CPDictionary _namedArgs;
|
||||
CPArray _args;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the singleton instance of the running application. If it
|
||||
doesn't exist, it will be created, and then returned.
|
||||
@return the application singleton
|
||||
*/
|
||||
+ (CPApplication)sharedApplication
|
||||
{
|
||||
if (!CPApp)
|
||||
CPApp = [[CPApplication alloc] init];
|
||||
|
||||
return CPApp;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the Document based application with basic menu functions.
|
||||
Functions are <code>New, Open, Undo, Redo, Save, Cut, Copy, Paste</code>.
|
||||
@return the initialized application
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_eventListeners = [];
|
||||
|
||||
_windows = [];
|
||||
|
||||
[_windows addObject:nil];
|
||||
|
||||
// FIXME: This should be read from the cib.
|
||||
_mainMenu = [[CPMenu alloc] initWithTitle:@"MainMenu"];
|
||||
|
||||
// FIXME: We should implement autoenabling.
|
||||
[_mainMenu setAutoenablesItems:NO];
|
||||
|
||||
var bundle = [CPBundle bundleForClass:[CPApplication class]],
|
||||
newMenuItem = [[CPMenuItem alloc] initWithTitle:@"New" action:@selector(newDocument:) keyEquivalent:@"N"];
|
||||
|
||||
[newMenuItem setImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/New.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
[newMenuItem setAlternateImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/NewHighlighted.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
|
||||
[_mainMenu addItem:newMenuItem];
|
||||
|
||||
var openMenuItem = [[CPMenuItem alloc] initWithTitle:@"Open" action:@selector(openDocument:) keyEquivalent:@"O"];
|
||||
|
||||
[openMenuItem setImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/Open.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
[openMenuItem setAlternateImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/OpenHighlighted.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
|
||||
[_mainMenu addItem:openMenuItem];
|
||||
|
||||
var saveMenu = [[CPMenu alloc] initWithTitle:@"Save"],
|
||||
saveMenuItem = [[CPMenuItem alloc] initWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:@""];
|
||||
|
||||
[saveMenuItem setImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/Save.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
[saveMenuItem setAlternateImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPApplication/SaveHighlighted.png"] size:CGSizeMake(16.0, 16.0)]];
|
||||
|
||||
[saveMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Save" action:@selector(saveDocument:) keyEquivalent:@"S"]];
|
||||
[saveMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Save As" action:@selector(saveDocumentAs:) keyEquivalent:@""]];
|
||||
|
||||
[saveMenuItem setSubmenu:saveMenu];
|
||||
|
||||
[_mainMenu addItem:saveMenuItem];
|
||||
|
||||
var editMenuItem = [[CPMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""],
|
||||
editMenu = [[CPMenu alloc] initWithTitle:@"Edit"],
|
||||
|
||||
undoMenuItem = [[CPMenuItem alloc] initWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:CPUndoKeyEquivalent],
|
||||
redoMenuItem = [[CPMenuItem alloc] initWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:CPRedoKeyEquivalent];
|
||||
|
||||
[undoMenuItem setKeyEquivalentModifierMask:CPUndoKeyEquivalentModifierMask];
|
||||
[redoMenuItem setKeyEquivalentModifierMask:CPRedoKeyEquivalentModifierMask];
|
||||
|
||||
[editMenu addItem:undoMenuItem];
|
||||
[editMenu addItem:redoMenuItem];
|
||||
|
||||
[editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"X"]],
|
||||
[editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"C"]],
|
||||
[editMenu addItem:[[CPMenuItem alloc] initWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"V"]];
|
||||
|
||||
[editMenuItem setSubmenu:editMenu];
|
||||
[editMenuItem setHidden:YES];
|
||||
|
||||
[_mainMenu addItem:editMenuItem];
|
||||
|
||||
[_mainMenu addItem:[CPMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Configuring Applications
|
||||
|
||||
/*!
|
||||
Sets the delegate for this application. The delegate will receive various notifications
|
||||
caused by user interactions during the application's run. The delegate can choose to
|
||||
react to these events.
|
||||
@param aDelegate the delegate object
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate == aDelegate)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_delegate)
|
||||
{
|
||||
[defaultCenter
|
||||
removeObserver:_delegate
|
||||
name:CPApplicationWillFinishLaunchingNotification
|
||||
object:self];
|
||||
|
||||
[defaultCenter
|
||||
removeObserver:_delegate
|
||||
name:CPApplicationDidFinishLaunchingNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
_delegate = aDelegate;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(applicationWillFinishLaunching:)])
|
||||
[defaultCenter
|
||||
addObserver:_delegate
|
||||
selector:@selector(applicationWillFinishLaunching:)
|
||||
name:CPApplicationWillFinishLaunchingNotification
|
||||
object:self];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(applicationDidFinishLaunching:)])
|
||||
[defaultCenter
|
||||
addObserver:_delegate
|
||||
selector:@selector(applicationDidFinishLaunching:)
|
||||
name:CPApplicationDidFinishLaunchingNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the application's delegate. The app can only have one delegate at a time.
|
||||
*/
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
This method is called by <code>run</code> before the event loop begins.
|
||||
When it successfully completes, it posts the notification
|
||||
CPApplicationDidFinishLaunchingNotification. If you override
|
||||
<code>finishLaunching</code>, the subclass method should invoke the superclass method.
|
||||
*/
|
||||
- (void)finishLaunching
|
||||
{
|
||||
var bundle = [CPBundle mainBundle],
|
||||
types = [bundle objectForInfoDictionaryKey:@"CPBundleDocumentTypes"];
|
||||
|
||||
if ([types count] > 0)
|
||||
_documentController = [CPDocumentController sharedDocumentController];
|
||||
|
||||
var delegateClassName = [bundle objectForInfoDictionaryKey:@"CPApplicationDelegateClass"];
|
||||
|
||||
if (delegateClassName)
|
||||
{
|
||||
var delegateClass = objj_getClass(delegateClassName);
|
||||
|
||||
if (delegateClass)
|
||||
if ([_documentController class] == delegateClass)
|
||||
[self setDelegate:_documentController];
|
||||
else
|
||||
[self setDelegate:[[delegateClass alloc] init]];
|
||||
}
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter
|
||||
postNotificationName:CPApplicationWillFinishLaunchingNotification
|
||||
object:self];
|
||||
|
||||
if (_documentController)
|
||||
[_documentController newDocument:self];
|
||||
|
||||
[defaultCenter
|
||||
postNotificationName:CPApplicationDidFinishLaunchingNotification
|
||||
object:self];
|
||||
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
|
||||
/*!
|
||||
Calls <code>finishLaunching</code> method which results in starting
|
||||
the main event loop.
|
||||
*/
|
||||
- (void)run
|
||||
{
|
||||
[self finishLaunching];
|
||||
}
|
||||
|
||||
// Managing the Event Loop
|
||||
/*!
|
||||
Starts a modal event loop for <code>aWindow</code>
|
||||
@param aWindow the window to start the event loop for
|
||||
*/
|
||||
- (void)runModalForWindow:(CPWindow)aWindow
|
||||
{
|
||||
[self runModalSession:[self beginModalSessionForWindow:aWindow]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops the event loop started by <code>runModalForWindow:</code> and
|
||||
sets the code that <code>runModalForWindow:</code> will return.
|
||||
@param aCode the return code for the modal event
|
||||
*/
|
||||
- (void)stopModalWithCode:(int)aCode
|
||||
{
|
||||
if (!_currentSession)
|
||||
{
|
||||
return;
|
||||
// raise exception;
|
||||
}
|
||||
|
||||
_currentSession._state = aCode;
|
||||
_currentSession = _currentSession._previous;
|
||||
|
||||
if (aCode == CPRunAbortedResponse)
|
||||
[self _removeRunModalLoop];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_removeRunModalLoop
|
||||
{
|
||||
var count = _eventListeners.length;
|
||||
|
||||
while (count--)
|
||||
if (_eventListeners[count]._callback == _CPRunModalLoop)
|
||||
{
|
||||
_eventListeners.splice(count, 1);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops the modal event loop
|
||||
*/
|
||||
- (void)stopModal
|
||||
{
|
||||
[self stopModalWithCode:CPRunStoppedResponse]
|
||||
}
|
||||
|
||||
/*!
|
||||
Aborts the event loop started by <code>runModalForWindow:</code>
|
||||
*/
|
||||
- (void)abortModal
|
||||
{
|
||||
[self stopModalWithCode:CPRunAbortedResponse];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets up a modal session with <code>theWindow</code>.
|
||||
@param aWindow the window to set up the modal session for
|
||||
*/
|
||||
- (CPModalSession)beginModalSessionForWindow:(CPWindow)aWindow
|
||||
{
|
||||
return _CPModalSessionMake(aWindow, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Runs a modal session
|
||||
@param CPModalSession the session to run
|
||||
*/
|
||||
- (void)runModalSession:(CPModalSession)aModalSession
|
||||
{
|
||||
aModalSession._previous = _currentSession;
|
||||
_currentSession = aModalSession;
|
||||
|
||||
var theWindow = aModalSession._window;
|
||||
|
||||
[theWindow center];
|
||||
[theWindow makeKeyAndOrderFront:self];
|
||||
|
||||
// [theWindow._bridge _obscureWindowsBelowModalWindow];
|
||||
|
||||
[CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the window for the current modal session. If there is no
|
||||
modal session, it returns <code>nil</code>.
|
||||
*/
|
||||
- (CPWindow)modalWindow
|
||||
{
|
||||
if (!_currentSession)
|
||||
return nil;
|
||||
|
||||
return _currentSession._window;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (BOOL)_handleKeyEquivalent:(CPEvent)anEvent
|
||||
{
|
||||
if ([_mainMenu performKeyEquivalent:anEvent])
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Dispatches events to other objects.
|
||||
@param anEvent the event to dispatch
|
||||
*/
|
||||
- (void)sendEvent:(CPEvent)anEvent
|
||||
{
|
||||
// Check if this is a candidate for key equivalent...
|
||||
if ([anEvent type] == CPKeyDown &&
|
||||
[anEvent modifierFlags] & (CPCommandKeyMask | CPControlKeyMask) &&
|
||||
[[anEvent characters] length] > 0 &&
|
||||
[self _handleKeyEquivalent:anEvent])
|
||||
return;
|
||||
|
||||
if (_eventListeners.length)
|
||||
{
|
||||
if (_eventListeners[_eventListeners.length - 1]._mask & (1 << [anEvent type]))
|
||||
_eventListeners.pop()._callback(anEvent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[[anEvent window] sendEvent:anEvent];
|
||||
}
|
||||
|
||||
- (void)doCommandBySelector:(SEL)aSelector
|
||||
{
|
||||
if ([_delegate respondsToSelector:aSelector])
|
||||
[_delegate performSelector:aSelector];
|
||||
else
|
||||
[super doCommandBySelector:aSelector];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the key window.
|
||||
*/
|
||||
- (CPWindow)keyWindow
|
||||
{
|
||||
return _keyWindow;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the main window.
|
||||
*/
|
||||
- (CPWindow)mainWindow
|
||||
{
|
||||
return _mainWindow;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPWindow object corresponding to <code>aWindowNumber</code>.
|
||||
*/
|
||||
- (CPWindow)windowWithWindowNumber:(int)aWindowNumber
|
||||
{
|
||||
return _windows[aWindowNumber];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of the application's CPWindows
|
||||
*/
|
||||
- (CPArray)windows
|
||||
{
|
||||
return _windows;
|
||||
}
|
||||
|
||||
// Accessing the Main Menu
|
||||
/*!
|
||||
Returns the application's main menu
|
||||
*/
|
||||
- (CPMenu)mainMenu
|
||||
{
|
||||
return _mainMenu;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the main menu for the application
|
||||
@param aMenu the menu to set for the application
|
||||
*/
|
||||
- (void)setMainMenu:(CPMenu)aMenu
|
||||
{
|
||||
_mainMenu = aMenu;
|
||||
}
|
||||
|
||||
// Posting Actions
|
||||
/*!
|
||||
Tries to perform the action with an argument. Performs
|
||||
the action on itself (if it responds to it), then
|
||||
tries to perform the action on the delegate.
|
||||
@param anAction the action to perform.
|
||||
@param anObject the argument for the action
|
||||
method
|
||||
@return <code>YES</code> if the action was performed
|
||||
*/
|
||||
- (BOOL)tryToPerform:(SEL)anAction with:(id)anObject
|
||||
{
|
||||
if (!anAction)
|
||||
return NO;
|
||||
|
||||
if ([self tryToPerform:anAction with:anObject])
|
||||
return YES;
|
||||
|
||||
if([_delegate respondsToSelector:aSelector])
|
||||
{
|
||||
[_delegate performSelector:aSelector withObject:anObject];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sends an action to a target.
|
||||
@param anAction the action to send
|
||||
@param aTarget the target for the action
|
||||
@param aSender the action sender
|
||||
@return <code>YES</code>
|
||||
*/
|
||||
- (BOOL)sendAction:(SEL)anAction to:(id)aTarget from:(id)aSender
|
||||
{
|
||||
var target = [self targetForAction:anAction to:aTarget from:aSender];
|
||||
|
||||
if (!target)
|
||||
return NO;
|
||||
|
||||
[target performSelector:anAction withObject:aSender];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Finds a target for the specified action. If the
|
||||
action is <code>nil</code>, returns <code>nil</code>.
|
||||
If the target is not <code>nil</code>, <code>aTarget</code> is
|
||||
returned. Otherwise, it calls <code>targetForAction:</code>
|
||||
to search for a target.
|
||||
@param anAction the action to find a target for
|
||||
@param aTarget if not <code>nil</code>, this will be returned
|
||||
@aSender not used
|
||||
@return a target for the action
|
||||
*/
|
||||
- (id)targetForAction:(SEL)anAction to:(id)aTarget from:(id)aSender
|
||||
{
|
||||
if (!anAction)
|
||||
return nil;
|
||||
|
||||
if (aTarget)
|
||||
return aTarget;
|
||||
|
||||
return [self targetForAction:anAction];
|
||||
}
|
||||
|
||||
/*!
|
||||
Finds an action-target for a specified window.
|
||||
It finds a matching target in the following order:
|
||||
<ol>
|
||||
<li>the window's first responder</li>
|
||||
<li>all the next responders after first responder</li>
|
||||
<li>the window</li>
|
||||
<li>the window's delegate</li>
|
||||
<li>the window's controller</li>
|
||||
<li>the window's associated document</li>
|
||||
</ol>
|
||||
@param aWindow the window to search for a target
|
||||
@param anAction the action to find a responder to
|
||||
@return the object that responds to the action, or <code>nil</code>
|
||||
if no matching target was found
|
||||
@ignore
|
||||
*/
|
||||
- (id)_targetForWindow:(CPWindow)aWindow action:(SEL)anAction
|
||||
{
|
||||
var responder = [aWindow firstResponder],
|
||||
checkWindow = YES;
|
||||
|
||||
while (responder)
|
||||
{
|
||||
if ([responder respondsToSelector:anAction])
|
||||
return responder;
|
||||
|
||||
if (responder == aWindow)
|
||||
checkWindow = NO;
|
||||
|
||||
responder = [responder nextResponder];
|
||||
}
|
||||
|
||||
if (checkWindow && [aWindow respondsToSelector:anAction])
|
||||
return aWindow;
|
||||
|
||||
var delegate = [aWindow delegate];
|
||||
|
||||
if ([delegate respondsToSelector:anAction])
|
||||
return delegate;
|
||||
|
||||
var windowController = [aWindow windowController];
|
||||
|
||||
if ([windowController respondsToSelector:anAction])
|
||||
return windowController;
|
||||
|
||||
var theDocument = [windowController document];
|
||||
|
||||
if (theDocument != delegate && [theDocument respondsToSelector:anAction])
|
||||
return theDocument;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Looks for a target that can handle the specified action.
|
||||
Checks for a target in the following order:
|
||||
<ol>
|
||||
<li>a responder from the key window</li>
|
||||
<li>a responder frmo the main window</li>
|
||||
<li>the CPApplication instance</li>
|
||||
<li>the application delegate</li>
|
||||
<li>the document controller</li>
|
||||
</ol>
|
||||
@param anAction the action to handle
|
||||
@return a target that can respond, or <code>nil</code>
|
||||
if no match could be found
|
||||
*/
|
||||
- (id)targetForAction:(SEL)anAction
|
||||
{
|
||||
if (!anAction)
|
||||
return nil;
|
||||
|
||||
var target = [self _targetForWindow:[self keyWindow] action:anAction];
|
||||
|
||||
if (target)
|
||||
return target;
|
||||
|
||||
target = [self _targetForWindow:[self mainWindow] action:anAction];
|
||||
|
||||
if (target)
|
||||
return target;
|
||||
|
||||
if ([self respondsToSelector:anAction])
|
||||
return self;
|
||||
|
||||
if ([_delegate respondsToSelector:anAction])
|
||||
return _delegate;
|
||||
|
||||
if ([_documentController respondsToSelector:anAction])
|
||||
return _documentController;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setCallback:(Function)aCallback forNextEventMatchingMask:(unsigned int)aMask untilDate:(CPDate)anExpiration inMode:(CPString)aMode dequeue:(BOOL)shouldDequeue
|
||||
{
|
||||
_eventListeners.push(_CPEventListenerMake(aMask, aCallback));
|
||||
|
||||
if (_eventListeners.length == 3) objj_debug_print_backtrace();
|
||||
}
|
||||
|
||||
- (CPEvent)setTarget:(id)aTarget selector:(SEL)aSelector forNextEventMatchingMask:(unsigned int)aMask untilDate:(CPDate)anExpiration inMode:(CPString)aMode dequeue:(BOOL)shouldDequeue
|
||||
{
|
||||
_eventListeners.push(_CPEventListenerMake(aMask, function (anEvent) { objj_msgSend(aTarget, aSelector, anEvent); }));
|
||||
}
|
||||
|
||||
// Managing Sheets
|
||||
|
||||
/*!
|
||||
Displays a window as a sheet.
|
||||
@param aSheet the window to display as a sheet
|
||||
@param aWindow the window that will hold the sheet as a child
|
||||
@param aModalDelegate
|
||||
@param aDidEndSelector
|
||||
@param aContextInfo
|
||||
*/
|
||||
- (void)beginSheet:(CPWindow)aSheet modalForWindow:(CPWindow)aWindow modalDelegate:(id)aModalDelegate didEndSelector:(SEL)aDidEndSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
[aWindow _attachSheet:aSheet modalDelegate:aModalDelegate didEndSelector:aDidEndSelector contextInfo:aContextInfo];
|
||||
}
|
||||
|
||||
- (CPArray)arguments
|
||||
{
|
||||
return _args;
|
||||
}
|
||||
|
||||
- (CPDictionary)namedArguments
|
||||
{
|
||||
return _namedArgs;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPModalSessionMake = function(aWindow, aStopCode)
|
||||
{
|
||||
return { _window:aWindow, _state:CPRunContinuesResponse , _previous:nil };
|
||||
}
|
||||
|
||||
var _CPEventListenerMake = function(anEventMask, aCallback)
|
||||
{
|
||||
return { _mask:anEventMask, _callback:aCallback };
|
||||
}
|
||||
|
||||
var _CPRunModalLoop = function(anEvent)
|
||||
{
|
||||
[CPApp setCallback:_CPRunModalLoop forNextEventMatchingMask:CPAnyEventMask untilDate:nil inMode:0 dequeue:NO];
|
||||
|
||||
// FIXME: abortModal from event loop?
|
||||
var theWindow = [anEvent window],
|
||||
modalSession = CPApp._currentSession;
|
||||
|
||||
if (theWindow == modalSession._window || [theWindow worksWhenModal])
|
||||
[theWindow sendEvent:anEvent];
|
||||
/*else
|
||||
[[session modalWindow] makeKeyAndOrderFront:]*/
|
||||
|
||||
if (modalSession._state != CPRunContinuesResponse)
|
||||
[CPApp _removeRunModalLoop];
|
||||
}
|
||||
|
||||
/*!
|
||||
Starts the GUI and Cappuccino frameworks. This function should be
|
||||
called from the <code>main()</code> function of your program.
|
||||
@class CPApplication
|
||||
@return void
|
||||
*/
|
||||
function CPApplicationMain(args, namedArgs)
|
||||
{
|
||||
var mainBundle = [CPBundle mainBundle],
|
||||
principalClass = [mainBundle principalClass];
|
||||
|
||||
if (!principalClass)
|
||||
principalClass = [CPApplication class];
|
||||
|
||||
[principalClass sharedApplication];
|
||||
|
||||
//[NSBundle loadNibNamed:@"myMain" owner:NSApp];
|
||||
|
||||
//FIXME?
|
||||
if (!args && !namedArgs)
|
||||
{
|
||||
var args = window.location.hash.replace("#", "").split("/").slice(0),
|
||||
searchParams = window.location.search.substring(1).split("&");
|
||||
namedArgs = [CPDictionary dictionary];
|
||||
|
||||
for(var i=0, count = args.length; i<count; i++)
|
||||
args[i] = decodeURIComponent(args[i]);
|
||||
|
||||
if([args containsObject:"debug"])
|
||||
CPLogRegister(CPLogPopup);
|
||||
|
||||
for(var i=0; i<searchParams.length; i++)
|
||||
{
|
||||
var index = searchParams[i].indexOf('=');
|
||||
if(index == -1)
|
||||
[namedArgs setObject: "" forKey:searchParams[i]];
|
||||
else
|
||||
[namedArgs setObject: searchParams[i].substring(index+1) forKey: searchParams[i].substring(0, index)];
|
||||
}
|
||||
}
|
||||
|
||||
CPApp._args = args;
|
||||
CPApp._namedArgs = namedArgs;
|
||||
|
||||
[CPApp run];
|
||||
}
|
||||
@@ -0,0 +1,887 @@
|
||||
/*
|
||||
* CPButton.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "_CPImageAndTitleView.j"
|
||||
@import "CGGeometry.j"
|
||||
|
||||
@import "CPControl.j"
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
CPScaleProportionally = 0;
|
||||
CPScaleToFit = 1;
|
||||
CPScaleNone = 2;
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPCellImagePosition
|
||||
*/
|
||||
CPNoImage = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPCellImagePosition
|
||||
*/
|
||||
CPImageOnly = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPCellImagePosition
|
||||
*/
|
||||
CPImageLeft = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPCellImagePosition
|
||||
*/
|
||||
CPImageRight = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPCellImagePosition
|
||||
*/
|
||||
CPImageBelow = 4;
|
||||
/*
|
||||
@global
|
||||
@group CPCellImagePosition
|
||||
*/
|
||||
CPImageAbove = 5;
|
||||
/*
|
||||
@global
|
||||
@group CPCellImagePosition
|
||||
*/
|
||||
CPImageOverlaps = 6;
|
||||
|
||||
/*
|
||||
@global
|
||||
@class CPButton
|
||||
*/
|
||||
CPOnState = 1;
|
||||
/*
|
||||
@global
|
||||
@class CPButton
|
||||
*/
|
||||
CPOffState = 0;
|
||||
/*
|
||||
@global
|
||||
@class CPButton
|
||||
*/
|
||||
CPMixedState = -1;
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPRoundedBezelStyle = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPRegularSquareBezelStyle = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPThickSquareBezelStyle = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPThickerSquareBezelStyle = 4;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPDisclosureBezelStyle = 5;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPShadowlessSquareBezelStyle = 6;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPCircularBezelStyle = 7;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPTexturedSquareBezelStyle = 8;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPHelpButtonBezelStyle = 9;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPSmallSquareBezelStyle = 10;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPTexturedRoundedBezelStyle = 11;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPRoundRectBezelStyle = 12;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPRecessedBezelStyle = 13;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPRoundedDisclosureBezelStyle = 14;
|
||||
/*
|
||||
@global
|
||||
@group CPBezelStyle
|
||||
*/
|
||||
CPHUDBezelStyle = -1;
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPMomentaryLightButton = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPPushOnPushOffButton = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPToggleButton = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPSwitchButton = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPRadioButton = 4;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPMomentaryChangeButton = 5;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPOnOffButton = 6;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPMomentaryPushInButton = 7;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPMomentaryPushButton = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPButtonType
|
||||
*/
|
||||
CPMomentaryLight = 7;
|
||||
|
||||
|
||||
var CPHUDBezelStyleTextColor = nil;
|
||||
|
||||
var _CPButtonClassName = nil,
|
||||
_CPButtonBezelStyleSizes = {},
|
||||
_CPButtonBezelStyleIdentifiers = {},
|
||||
_CPButtonBezelStyleHighlightedIdentifier = @"Highlighted";
|
||||
|
||||
/*! @class CPButton
|
||||
|
||||
CPButton is a subclass of CPControl that
|
||||
intercepts mouse-down events and sends an action message to a
|
||||
target object when it's clicked or pressed.
|
||||
*/
|
||||
@implementation CPButton : CPControl
|
||||
{
|
||||
int _tag;
|
||||
int _state;
|
||||
BOOL _allowsMixedState;
|
||||
BOOL _isHighlighted;
|
||||
|
||||
CPImage _image;
|
||||
CPImage _alternateImage;
|
||||
|
||||
CPCellImagePosition _imagePosition;
|
||||
CPImageScaling _imageScaling;
|
||||
|
||||
CPString _title;
|
||||
CPString _alternateTitle;
|
||||
|
||||
CPBezelStyle _bezelStyle;
|
||||
BOOL _isBordered;
|
||||
CPControlSize _controlSize;
|
||||
|
||||
BOOL _bezelBorderNeedsUpdate;
|
||||
|
||||
_CPImageAndTitleView _imageAndTitleView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the CPButton class.
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPButton class])
|
||||
return;
|
||||
|
||||
_CPButtonClassName = [CPButton className];
|
||||
|
||||
// Textured Rounded
|
||||
_CPButtonBezelStyleIdentifiers[CPRoundedBezelStyle] = @"Rounded";
|
||||
_CPButtonBezelStyleIdentifiers[CPRegularSquareBezelStyle] = @"RegularSquare";
|
||||
_CPButtonBezelStyleIdentifiers[CPThickSquareBezelStyle] = @"ThickSquare";
|
||||
_CPButtonBezelStyleIdentifiers[CPThickerSquareBezelStyle] = @"ThickerSquare";
|
||||
_CPButtonBezelStyleIdentifiers[CPDisclosureBezelStyle] = @"Disclosure";
|
||||
_CPButtonBezelStyleIdentifiers[CPShadowlessSquareBezelStyle] = @"ShadowlessSquare";
|
||||
_CPButtonBezelStyleIdentifiers[CPCircularBezelStyle] = @"Circular";
|
||||
_CPButtonBezelStyleIdentifiers[CPTexturedSquareBezelStyle] = @"TexturedSquare";
|
||||
_CPButtonBezelStyleIdentifiers[CPHelpButtonBezelStyle] = @"HelpButton";
|
||||
_CPButtonBezelStyleIdentifiers[CPSmallSquareBezelStyle] = @"SmallSquare";
|
||||
_CPButtonBezelStyleIdentifiers[CPTexturedRoundedBezelStyle] = @"TexturedRounded";
|
||||
_CPButtonBezelStyleIdentifiers[CPRoundRectBezelStyle] = @"RoundRect";
|
||||
_CPButtonBezelStyleIdentifiers[CPRecessedBezelStyle] = @"Recessed";
|
||||
_CPButtonBezelStyleIdentifiers[CPRoundedDisclosureBezelStyle] = @"RoundedDisclosure";
|
||||
_CPButtonBezelStyleIdentifiers[CPHUDBezelStyle] = @"HUD";
|
||||
|
||||
var regularIdentifier = _CPControlIdentifierForControlSize(CPRegularControlSize),
|
||||
smallIdentifier = _CPControlIdentifierForControlSize(CPSmallControlSize),
|
||||
miniIdentifier = _CPControlIdentifierForControlSize(CPMiniControlSize);
|
||||
|
||||
// Rounded Rect
|
||||
var prefix = _CPButtonClassName + _CPButtonBezelStyleIdentifiers[CPRoundRectBezelStyle];
|
||||
|
||||
_CPButtonBezelStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(10.0, 18.0), _CGSizeMake(1.0, 18.0), _CGSizeMake(10.0, 18.0)];
|
||||
_CPButtonBezelStyleSizes[prefix + regularIdentifier + _CPButtonBezelStyleHighlightedIdentifier] = [_CGSizeMake(10.0, 18.0), _CGSizeMake(1.0, 18.0), _CGSizeMake(10.0, 18.0)];
|
||||
|
||||
// HUD
|
||||
var prefix = _CPButtonClassName + _CPButtonBezelStyleIdentifiers[CPHUDBezelStyle];
|
||||
|
||||
_CPButtonBezelStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(13.0, 20.0), _CGSizeMake(1.0, 20.0), _CGSizeMake(13.0, 20.0)];
|
||||
_CPButtonBezelStyleSizes[prefix + regularIdentifier + _CPButtonBezelStyleHighlightedIdentifier] = [_CGSizeMake(13.0, 20.0), _CGSizeMake(1.0, 20.0), _CGSizeMake(13.0, 20.0)];
|
||||
|
||||
CPHUDBezelStyleTextColor = [CPColor whiteColor];
|
||||
|
||||
// Textured Rounded
|
||||
var prefix = _CPButtonClassName + _CPButtonBezelStyleIdentifiers[CPTexturedRoundedBezelStyle];
|
||||
|
||||
_CPButtonBezelStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(7.0, 20.0), _CGSizeMake(1.0, 20.0), _CGSizeMake(7.0, 20.0)];
|
||||
_CPButtonBezelStyleSizes[prefix + regularIdentifier + _CPButtonBezelStyleHighlightedIdentifier] = [_CGSizeMake(7.0, 20.0), _CGSizeMake(1.0, 20.0), _CGSizeMake(7.0, 20.0)];
|
||||
}
|
||||
|
||||
// Configuring Buttons
|
||||
/*!
|
||||
Sets how the button highlights and shows its state.
|
||||
@param aButtonType Defines the behavior of the button.
|
||||
*/
|
||||
- (void)setButtonType:(CPButtonType)aButtonType
|
||||
{
|
||||
if (aButtonType == CPSwitchButton)
|
||||
{
|
||||
[self setBordered:NO];
|
||||
[self setImage:nil];
|
||||
[self setAlternateImage:nil];
|
||||
[self setAlignment:CPLeftTextAlignment];
|
||||
}
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_imagePosition = CPNoImage;
|
||||
_imageScaling = CPScaleNone;
|
||||
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
[self setBezelStyle:CPRoundRectBezelStyle];
|
||||
[self setBordered:YES];
|
||||
|
||||
[self setAlignment:CPCenterTextAlignment];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the position of the button's image to <code>anImagePosition</code>.
|
||||
@param anImagePosition the position for the button's image
|
||||
*/
|
||||
- (void)setImagePosition:(CPCellImagePosition)anImagePosition
|
||||
{
|
||||
if (_imagePosition == anImagePosition)
|
||||
return;
|
||||
|
||||
_imagePosition = anImagePosition;
|
||||
|
||||
[self drawContentsWithHighlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the buton's image position
|
||||
*/
|
||||
- (CPCellImagePosition)imagePosition
|
||||
{
|
||||
return _imagePosition;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's images scaling method
|
||||
@param anImageScaling the image scaling method
|
||||
*/
|
||||
- (void)setImageScaling:(CPImageScaling)anImageScaling
|
||||
{
|
||||
if (_imageScaling == anImageScaling)
|
||||
return;
|
||||
|
||||
_imageScaling = anImageScaling;
|
||||
|
||||
[self drawContentsWithHighlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button's image scaling method
|
||||
*/
|
||||
- (CPImageScaling)imageScaling
|
||||
{
|
||||
return _imageScaling;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color of the button's text
|
||||
@param aColor the color to use for drawing the button text
|
||||
*/
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
[super setTextColor:aColor];
|
||||
|
||||
[self drawContentsWithHighlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the font that will be used to draw the button text
|
||||
@param aFont the font used to draw the button text
|
||||
*/
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
[super setFont:aFont];
|
||||
|
||||
[self drawContentsWithHighlight:_isHighlighted];
|
||||
}
|
||||
|
||||
// Setting the state
|
||||
/*!
|
||||
Returns <code>YES</code> if the button has a 'mixed' state in addition to on and off.
|
||||
*/
|
||||
- (BOOL)allowsMixedState
|
||||
{
|
||||
return _allowsMixedState;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the button can have a 'mixed' state.
|
||||
@param aFlag specifies whether a 'mixed' state is allowed or not
|
||||
*/
|
||||
- (void)setAllowsMixedState:(BOOL)aFlag
|
||||
{
|
||||
_allowsMixedState = aFlag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button to its next state.
|
||||
*/
|
||||
- (void)setNextState
|
||||
{
|
||||
if (_state == CPOffState)
|
||||
_state = CPOnState;
|
||||
else
|
||||
_state = (_state >= CPOnState && _allowsMixedState) ? CPMixedState : CPOffState;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's state to <code>aState</code>.
|
||||
@param aState Possible states are any of the CPButton globals:
|
||||
<code>CPOffState, CPOnState, CPMixedState</code>
|
||||
*/
|
||||
- (void)setState:(int)aState
|
||||
{
|
||||
_state = aState;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button's current state
|
||||
*/
|
||||
- (int)state
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the alignment of the text on the button.
|
||||
@param anAlignment an alignment object
|
||||
*/
|
||||
- (void)setAlignment:(CPTextAlignment)anAlignment
|
||||
{
|
||||
[super setAlignment:anAlignment];
|
||||
|
||||
[self drawContentsWithHighlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the image that will be drawn on the button.
|
||||
@param anImage the image that will be drawn
|
||||
*/
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
if (_image == anImage)
|
||||
return;
|
||||
|
||||
_image = anImage;
|
||||
|
||||
[self drawContentsWithHighlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the image that will be drawn on the button
|
||||
*/
|
||||
- (CPImage)image
|
||||
{
|
||||
return _image;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's image which is used in its alternate state.
|
||||
@param anImage the image to be used while the button is in an alternate state
|
||||
*/
|
||||
- (void)setAlternateImage:(CPImage)anImage
|
||||
{
|
||||
_alternateImage = anImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the image used when the button is in an alternate state.
|
||||
*/
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
return _alternateImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's title.
|
||||
@param aTitle the new title for the button
|
||||
*/
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
{
|
||||
if (_title == aTitle)
|
||||
return;
|
||||
|
||||
_title = aTitle;
|
||||
|
||||
[self drawContentsWithHighlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button's title string
|
||||
*/
|
||||
- (CPString)title
|
||||
{
|
||||
return _title;
|
||||
}
|
||||
|
||||
/*!
|
||||
Lays out the button.
|
||||
*/
|
||||
- (void)tile
|
||||
{
|
||||
var size = [self bounds].size;
|
||||
|
||||
if (_isBordered)
|
||||
{
|
||||
var imageAndTitleSize = CGSizeMakeCopy(size);
|
||||
|
||||
if (_bezelStyle == CPHUDBezelStyle)
|
||||
imageAndTitleSize.height -= 4.0;
|
||||
else if (_bezelStyle == CPRoundRectBezelStyle)
|
||||
imageAndTitleSize.height -= 2.0;
|
||||
else if (_bezelStyle == CPTexturedRoundedBezelStyle)
|
||||
imageAndTitleSize.height -= 2.0;
|
||||
|
||||
[_imageAndTitleView setFrameSize:imageAndTitleSize];
|
||||
}
|
||||
else
|
||||
[_imageAndTitleView setFrameSize:size];
|
||||
}
|
||||
|
||||
/*!
|
||||
Compacts the button's frame to fit its contents.
|
||||
*/
|
||||
- (void)sizeToFit
|
||||
{
|
||||
[_imageAndTitleView sizeToFit];
|
||||
|
||||
var frame = [_imageAndTitleView frame],
|
||||
height = CGRectGetHeight(frame);
|
||||
/*
|
||||
if (_isBordered)
|
||||
if (_bezelStyle == CPHUDBezelStyle)
|
||||
height += 2.0;
|
||||
else if (_bezelStyle == CPRoundRectBezelStyle)
|
||||
height += 1.0;
|
||||
else if (_bezelStyle == CPTexturedRoundedBezelStyle)
|
||||
height += 2.0;
|
||||
*/
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth(frame), height)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the frame size for the button.
|
||||
@param the new frame size
|
||||
*/
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Highlights the receiver based on <code>aFlag</code>.
|
||||
@param If <code>YES</code> the button will highlight, <code>NO</code> the button will unhighlight.
|
||||
*/
|
||||
- (void)highlight:(BOOL)aFlag
|
||||
{
|
||||
[self drawBezelWithHighlight:aFlag];
|
||||
[self drawContentsWithHighlight:aFlag];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets button's tag.
|
||||
@param aTag the button's new tag
|
||||
*/
|
||||
- (void)setTag:(int)aTag
|
||||
{
|
||||
_tag = aTag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button's tag.
|
||||
*/
|
||||
- (int)tag
|
||||
{
|
||||
return _tag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when the user clicks on this button.
|
||||
@param anEvent the event with information about the click
|
||||
*/
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
_isHighlighted = YES;
|
||||
|
||||
[self highlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when the user drags on the button.
|
||||
@param anEvent the event with information about the drag
|
||||
*/
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
_isHighlighted = CGRectContainsPoint([self bounds], [self convertPoint:[anEvent locationInWindow] fromView:nil]);
|
||||
|
||||
[self highlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when the user releases the mouse button
|
||||
@param anEvent the event with information about the mouse release
|
||||
*/
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
_isHighlighted = NO;
|
||||
|
||||
[self highlight:_isHighlighted];
|
||||
|
||||
[super mouseUp:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's control size.
|
||||
@param aControlSize the button's new control size
|
||||
*/
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
if (_controlSize == aControlSize)
|
||||
return;
|
||||
|
||||
_controlSize = aControlSize;
|
||||
|
||||
[self drawBezelWithHighlight:_isHighlighted];
|
||||
[self _updateTextAttributes];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the button's control size.
|
||||
*/
|
||||
- (CPControlSize)controlSize
|
||||
{
|
||||
return _controlSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the button has a bezeled border.
|
||||
@param If <code>YES</code>, the the button will have a bezeled border.
|
||||
*/
|
||||
- (void)setBordered:(BOOL)isBordered
|
||||
{
|
||||
if (_isBordered == isBordered)
|
||||
return;
|
||||
|
||||
_isBordered = isBordered;
|
||||
|
||||
[self updateBackgroundColors];
|
||||
[self drawBezelWithHighlight:_isHighlighted];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the border is bezeled.
|
||||
*/
|
||||
- (BOOL)isBordered
|
||||
{
|
||||
return _isBordered;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the button's bezel style.
|
||||
@param aBezelStye one of the predefined bezel styles
|
||||
*/
|
||||
- (void)setBezelStyle:(CPBezelStyle)aBezelStyle
|
||||
{
|
||||
// FIXME: We need real support for these:
|
||||
if (aBezelStyle == CPRoundedBezelStyle ||
|
||||
aBezelStyle == CPRoundedBezelStyle ||
|
||||
aBezelStyle == CPRegularSquareBezelStyle ||
|
||||
aBezelStyle == CPThickSquareBezelStyle ||
|
||||
aBezelStyle == CPThickerSquareBezelStyle ||
|
||||
aBezelStyle == CPDisclosureBezelStyle ||
|
||||
aBezelStyle == CPShadowlessSquareBezelStyle ||
|
||||
aBezelStyle == CPCircularBezelStyle ||
|
||||
aBezelStyle == CPTexturedSquareBezelStyle ||
|
||||
aBezelStyle == CPHelpButtonBezelStyle ||
|
||||
aBezelStyle == CPSmallSquareBezelStyle ||
|
||||
aBezelStyle == CPRecessedBezelStyle ||
|
||||
aBezelStyle == CPRoundedDisclosureBezelStyle)
|
||||
aBezelStyle = CPRoundRectBezelStyle;
|
||||
|
||||
if (_bezelStyle == aBezelStyle)
|
||||
return;
|
||||
|
||||
_bezelStyle = aBezelStyle;
|
||||
|
||||
[self updateBackgroundColors];
|
||||
[self drawBezelWithHighlight:_isHighlighted];
|
||||
|
||||
[self _updateTextAttributes];
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current bezel style
|
||||
*/
|
||||
- (int)bezelStyle
|
||||
{
|
||||
return _bezelStyle;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)updateBackgroundColors
|
||||
{
|
||||
if (_isBordered)
|
||||
{
|
||||
[self setBackgroundColor:_CPControlThreePartImagePattern(
|
||||
NO,
|
||||
_CPButtonBezelStyleSizes,
|
||||
_CPButtonClassName,
|
||||
_CPButtonBezelStyleIdentifiers[_bezelStyle],
|
||||
_CPControlIdentifierForControlSize(_controlSize)) forName:CPControlNormalBackgroundColor];
|
||||
|
||||
[self setBackgroundColor:_CPControlThreePartImagePattern(
|
||||
NO,
|
||||
_CPButtonBezelStyleSizes,
|
||||
_CPButtonClassName,
|
||||
_CPButtonBezelStyleIdentifiers[_bezelStyle],
|
||||
_CPControlIdentifierForControlSize(_controlSize),
|
||||
_CPButtonBezelStyleHighlightedIdentifier) forName:CPControlHighlightedBackgroundColor];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setBackgroundColor:nil forName:CPControlNormalBackgroundColor];
|
||||
[self setBackgroundColor:nil forName:CPControlHighlightedBackgroundColor];
|
||||
}
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)drawBezelWithHighlight:(BOOL)shouldHighlight
|
||||
{
|
||||
_bezelBorderNeedsUpdate = ![self window];
|
||||
|
||||
if (_bezelBorderNeedsUpdate)
|
||||
return;
|
||||
|
||||
[self setBackgroundColorWithName:shouldHighlight ? CPControlHighlightedBackgroundColor : CPControlNormalBackgroundColor];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)drawContentsWithHighlight:(BOOL)isHighlighted
|
||||
{
|
||||
if (!_title && !_image && !_alternateTitle && !_alternateImage && !_imageAndTitleView)
|
||||
return;
|
||||
|
||||
if (!_imageAndTitleView)
|
||||
{
|
||||
_imageAndTitleView = [[_CPImageAndTitleView alloc] initWithFrame:[self bounds]];
|
||||
|
||||
[self addSubview:_imageAndTitleView];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
[_imageAndTitleView setFont:[self font]];
|
||||
[_imageAndTitleView setTextColor:[self textColor]];
|
||||
[_imageAndTitleView setAlignment:[self alignment]];
|
||||
[_imageAndTitleView setImagePosition:_imagePosition];
|
||||
[_imageAndTitleView setImageScaling:_imageScaling];
|
||||
|
||||
[_imageAndTitleView setTitle:isHighlighted && _alternateTitle ? _alternateTitle : _title];
|
||||
[_imageAndTitleView setImage:isHighlighted && _alternateImage ? _alternateImage : _image];
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
if (_bezelBorderNeedsUpdate)
|
||||
[self drawBezelWithHighlight:_isHighlighted];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_updateTextAttributes
|
||||
{
|
||||
if (_bezelStyle == CPHUDBezelStyle)
|
||||
[self setTextColor:CPHUDBezelStyleTextColor];
|
||||
|
||||
if (_controlSize == CPRegularControlSize)
|
||||
[self setFont:[CPFont systemFontOfSize:11.0]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPButtonImageKey = @"CPButtonImageKey",
|
||||
CPButtonAlternateImageKey = @"CPButtonAlternateImageKey",
|
||||
CPButtonTitleKey = @"CPButtonTitleKey",
|
||||
CPButtonAlteranteTitleKey = @"CPButtonAlternateTitleKey",
|
||||
CPButtonImageAndTitleViewKey = @"CPButtonImageAndTitleViewKey",
|
||||
CPButtonImagePositionKey = @"CPButtonImagePositionKey",
|
||||
CPButtonImageScalingKey = @"CPButtonImageScalingKey",
|
||||
CPButtonIsBorderedKey = @"CPButtonIsBorderedKey",
|
||||
CPButtonBezelStyleKey = @"CPButtonBezelStyleKey",
|
||||
CPButtonImageAndTitleViewKey = @"CPButtonImageAndTitleViewKey";
|
||||
|
||||
@implementation CPButton (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the button by unarchiving data from <code>aCoder</code>.
|
||||
@param aCoder the coder containing the archived CPButton.
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
[self setImage:[aCoder decodeObjectForKey:CPButtonImageKey]];
|
||||
[self setAlternateImage:[aCoder decodeObjectForKey:CPButtonAlternateImageKey]];
|
||||
|
||||
[self setTitle:[aCoder decodeObjectForKey:CPButtonTitleKey]];
|
||||
|
||||
[self setImagePosition:[aCoder decodeIntForKey:CPButtonImagePositionKey]];
|
||||
[self setImageScaling:[aCoder decodeIntForKey:CPButtonImageScalingKey]];
|
||||
|
||||
[self setBezelStyle:[aCoder decodeIntForKey:CPButtonBezelStyleKey]];
|
||||
[self setBordered:[aCoder decodeBoolForKey:CPButtonIsBorderedKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives this button into the provided coder.
|
||||
@param aCoder the coder to which the button's instance data will be written.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// We do this in order to avoid encoding the _imageAndTitleView, which
|
||||
// should just automatically be created programmatically as needed.
|
||||
var actualSubviews = _subviews;
|
||||
|
||||
_subviews = [_subviews copy];
|
||||
[_subviews removeObjectIdenticalTo:_imageAndTitleView];
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
_subviews = actualSubviews;
|
||||
|
||||
[aCoder encodeObject:_image forKey:CPButtonImageKey];
|
||||
[aCoder encodeObject:_alternateImage forKey:CPButtonAlternateImageKey];
|
||||
|
||||
[aCoder encodeObject:_title forKey:CPButtonTitleKey];
|
||||
|
||||
[aCoder encodeInt:_imagePosition forKey:CPButtonImagePositionKey];
|
||||
[aCoder encodeInt:_imageScaling forKey:CPButtonImageScalingKey];
|
||||
|
||||
[aCoder encodeBool:_isBordered forKey:CPButtonIsBorderedKey];
|
||||
[aCoder encodeInt:_bezelStyle forKey:CPButtonBezelStyleKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* CPClipView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
/*! @class CPClipView
|
||||
|
||||
CPClipView allows you to define a clip rect and display only that portion of its containing view.
|
||||
It is used to hold the document view in a CPScrollView.
|
||||
*/
|
||||
@implementation CPClipView : CPView
|
||||
{
|
||||
CPView _documentView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the document view to be <code>aView</code>.
|
||||
@param aView the new document view. It's frame origin will be changed to <code>(0,0)</code> after calling this method.
|
||||
*/
|
||||
- (void)setDocumentView:(CPView)aView
|
||||
{
|
||||
if (_documentView == aView)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_documentView)
|
||||
{
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPViewFrameDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPViewBoundsDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[_documentView removeFromSuperview];
|
||||
}
|
||||
|
||||
_documentView = aView;
|
||||
|
||||
if (_documentView)
|
||||
{
|
||||
// FIXME: remove when bounds.
|
||||
[_documentView setFrameOrigin:CGPointMake(0.0, 0.0)];
|
||||
|
||||
[self addSubview:_documentView];
|
||||
|
||||
[_documentView setPostsFrameChangedNotifications:YES];
|
||||
[_documentView setPostsBoundsChangedNotifications:YES];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(viewFrameChanged:)
|
||||
name:CPViewFrameDidChangeNotification
|
||||
object:_documentView];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(viewBoundsChanged:)
|
||||
name:CPViewBoundsDidChangeNotification
|
||||
object:_documentView];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document view.
|
||||
*/
|
||||
- (id)documentView
|
||||
{
|
||||
return _documentView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new point that may be adjusted from <code>aPoint</code>
|
||||
to make sure it lies within the document view.
|
||||
@param aPoint
|
||||
@return the adjusted point
|
||||
*/
|
||||
- (CGPoint)constrainScrollPoint:(CGPoint)aPoint
|
||||
{
|
||||
var documentFrame = [_documentView frame];
|
||||
|
||||
aPoint.x = MAX(0.0, MIN(aPoint.x, MAX(_CGRectGetWidth(documentFrame) - _CGRectGetWidth(_bounds), 0.0)));
|
||||
aPoint.y = MAX(0.0, MIN(aPoint.y, MAX(_CGRectGetHeight(documentFrame) - _CGRectGetHeight(_bounds), 0.0)));
|
||||
|
||||
return aPoint;
|
||||
}
|
||||
|
||||
- (void)setBoundsOrigin:(CGPoint)aPoint
|
||||
{
|
||||
if (_CGPointEqualToPoint(_bounds.origin, aPoint))
|
||||
return;
|
||||
|
||||
[super setBoundsOrigin:aPoint];
|
||||
|
||||
var superview = [self superview];
|
||||
|
||||
if([superview isKindOfClass:[CPScrollView class]])
|
||||
[superview reflectScrolledClipView:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Scrolls the clip view to the specified point. The method
|
||||
sets its bounds origin to <code>aPoint</code>.
|
||||
*/
|
||||
- (void)scrollToPoint:(CGPoint)aPoint
|
||||
{
|
||||
[self setBoundsOrigin:[self constrainScrollPoint:aPoint]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles a CPViewBoundsDidChangeNotification.
|
||||
@param aNotification the notification event
|
||||
*/
|
||||
- (void)viewBoundsChanged:(CPNotification)aNotification
|
||||
{
|
||||
[self viewFrameChanged:aNotification];
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles a CPViewFrameDidChangeNotification.
|
||||
@param aNotification the notification event
|
||||
*/
|
||||
- (void)viewFrameChanged:(CPNotification)aNotification
|
||||
{
|
||||
var oldScrollPoint = [self bounds].origin;
|
||||
|
||||
// Call scrollToPoint: because the current scroll point may no longer make
|
||||
// sense given the new frame of the document view.
|
||||
[self scrollToPoint:oldScrollPoint];
|
||||
|
||||
// scrollToPoint: takes care of reflectScrollClipView: for us, so bail if
|
||||
// the scroll points are not equal (meaning scrollToPoint: didn't early bail).
|
||||
if (!CGPointEqualToPoint(oldScrollPoint, [self bounds].origin))
|
||||
return;
|
||||
|
||||
// ... and we're in a scroll view of course.
|
||||
var superview = [self superview];
|
||||
|
||||
if ([superview isKindOfClass:[CPScrollView class]])
|
||||
[superview reflectScrolledClipView:self];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,700 @@
|
||||
/*
|
||||
* CPCollectionView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPData.j>
|
||||
@import <Foundation/CPIndexSet.j>
|
||||
@import <Foundation/CPKeyedArchiver.j>
|
||||
@import <Foundation/CPKeyedUnarchiver.j>
|
||||
|
||||
@import <AppKit/CPView.j>
|
||||
|
||||
|
||||
/*! @class CPCollectionView
|
||||
|
||||
This class displays an array as a grid of objects, where each object is represented by a view.
|
||||
The view is controlled by creating a CPCollectionViewItem and specifying its view, then
|
||||
setting that item as the collection view prototype.
|
||||
|
||||
@par Delegate Methods
|
||||
|
||||
@delegate -(void)collectionViewDidChangeSelection:(CPCollectionView)collectionView;
|
||||
Called when the selection in the collection view has changed.
|
||||
@param collectionView the collection view who's selection changed
|
||||
|
||||
@delegate -(void)collectionView:(CPCollectionView)collectionView didDoubleClickOnItemAtIndex:(int)index;
|
||||
Called when the user double-clicks on an item in the collection view.
|
||||
@param collectionView the collection view that received the double-click
|
||||
@param index the index of the item that received the double-click
|
||||
|
||||
@delegate -(CPData)collectionView:(CPCollectionView)collectionView dataForItemsAtIndexes:(CPIndexSet)indices forType:(CPString)aType;
|
||||
Invoked to obtain data for a set of indices.
|
||||
@param collectionView the collection view to obtain data for
|
||||
@param indices the indices to return data for
|
||||
@param aType the data type
|
||||
@return a data object containing the index items
|
||||
|
||||
@delegate -(CPArray)collectionView:(CPCollectionView)collectionView dragTypesForItemsAtIndexes:(CPIndexSet)indices;
|
||||
Invoked to obtain the data types supported by the specified indices for placement on the pasteboard.
|
||||
@param collectionView the collection view the items reside in
|
||||
@param indices the indices to obtain drag types
|
||||
@return an array of drag types (CPString)
|
||||
*/
|
||||
@implementation CPCollectionView : CPView
|
||||
{
|
||||
CPArray _content;
|
||||
CPArray _items;
|
||||
|
||||
CPData _itemData;
|
||||
CPCollectionViewItem _itemPrototype;
|
||||
CPCollectionViewItem _itemForDragging;
|
||||
CPMutableArray _cachedItems;
|
||||
|
||||
unsigned _maxNumberOfRows;
|
||||
unsigned _maxNumberOfColumns;
|
||||
|
||||
CGSize _minItemSize;
|
||||
CGSize _maxItemSize;
|
||||
|
||||
float _tileWidth;
|
||||
|
||||
BOOL _isSelectable;
|
||||
BOOL _allowsMultipleSelection;
|
||||
CPIndexSet _selectionIndexes;
|
||||
|
||||
CGSize _itemSize;
|
||||
|
||||
float _horizontalMargin;
|
||||
float _verticalMargin;
|
||||
|
||||
unsigned _numberOfRows;
|
||||
unsigned _numberOfColumns;
|
||||
|
||||
id _delegate;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_items = [];
|
||||
_content = [];
|
||||
|
||||
_cachedItems = [];
|
||||
|
||||
_itemSize = CGSizeMakeZero();
|
||||
_minItemSize = CGSizeMakeZero();
|
||||
_maxItemSize = CGSizeMakeZero();
|
||||
|
||||
_verticalMargin = 5.0;
|
||||
_tileWidth = -1.0;
|
||||
|
||||
_selectionIndexes = [CPIndexSet indexSet];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item prototype to <code>anItem</code>
|
||||
@param anItem the new item prototype
|
||||
*/
|
||||
- (void)setItemPrototype:(CPCollectionViewItem)anItem
|
||||
{
|
||||
_itemData = [CPKeyedArchiver archivedDataWithRootObject:anItem];
|
||||
_itemForDragging = anItem//[CPKeyedUnarchiver unarchiveObjectWithData:_itemData];
|
||||
|
||||
[self reloadContent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current item prototype
|
||||
*/
|
||||
- (CPCollectionViewItem)itemPrototype
|
||||
{
|
||||
return _itemPrototype;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a collection view item for <code>anObject</code>.
|
||||
@param anObject the object to be represented.
|
||||
*/
|
||||
- (CPCollectionViewItem)newItemForRepresentedObject:(id)anObject
|
||||
{
|
||||
var item = nil;
|
||||
|
||||
if (_cachedItems.length)
|
||||
item = _cachedItems.pop();
|
||||
else
|
||||
item = [CPKeyedUnarchiver unarchiveObjectWithData:_itemData];
|
||||
|
||||
[item setRepresentedObject:anObject];
|
||||
[[item view] setFrameSize:_itemSize];
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
// Working with the Responder Chain
|
||||
/*!
|
||||
Returns <code>YES</code> by default.
|
||||
*/
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the receiver is currently the first responder.
|
||||
*/
|
||||
- (BOOL)isFirstResponder
|
||||
{
|
||||
return [[self window] firstResponder] == self;
|
||||
}
|
||||
|
||||
// Setting the Content
|
||||
/*!
|
||||
Sets the content of the collection view to the content in <code>anArray</code>.
|
||||
This array can be of any type, and each element will be passed to the <code>setRepresentedObject:</code> method.
|
||||
It's the responsibility of your custom collection view item to interpret the object.
|
||||
@param anArray the content array
|
||||
*/
|
||||
- (void)setContent:(CPArray)anArray
|
||||
{
|
||||
if (_content == anArray)
|
||||
return;
|
||||
|
||||
_content = anArray;
|
||||
|
||||
[self reloadContent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the collection view content array
|
||||
*/
|
||||
- (CPArray)content
|
||||
{
|
||||
return _content;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the collection view items.
|
||||
*/
|
||||
- (CPArray)items
|
||||
{
|
||||
return _items;
|
||||
}
|
||||
|
||||
// Setting the Selection Mode
|
||||
/*!
|
||||
Sets whether the user is allowed to select items
|
||||
@param isSelectable <code>YES</code> allows the user to select items.
|
||||
*/
|
||||
- (void)setSelectable:(BOOL)isSelectable
|
||||
{
|
||||
if (_isSelectable == isSelectable)
|
||||
return;
|
||||
|
||||
_isSelectable = isSelectable;
|
||||
|
||||
if (!_isSelectable)
|
||||
{
|
||||
var index = CPNotFound;
|
||||
|
||||
while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
|
||||
[_items[index] setSelected:NO];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the collection view is
|
||||
selected, and <code>NO</code> otherwise.
|
||||
*/
|
||||
- (BOOL)isSelected
|
||||
{
|
||||
return _isSelected;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the user can select multiple items.
|
||||
@param shouldAllowMultipleSelection <code>YES</code> allows the user to select multiple items
|
||||
*/
|
||||
- (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
|
||||
{
|
||||
_allowsMultipleSelection = shouldAllowMultipleSelection;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the user can select multiple items, <code>NO</code> otherwise.
|
||||
*/
|
||||
- (BOOL)allowsMultipleSelection
|
||||
{
|
||||
return _allowsMultipleSelection;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the selected items based on the provided indices.
|
||||
@param anIndexSet the set of items to be selected
|
||||
*/
|
||||
- (void)setSelectionIndexes:(CPIndexSet)anIndexSet
|
||||
{
|
||||
if (_selectionIndexes == anIndexSet)
|
||||
return;
|
||||
|
||||
var index = CPNotFound;
|
||||
|
||||
while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
|
||||
[_items[index] setSelected:NO];
|
||||
|
||||
_selectionIndexes = anIndexSet;
|
||||
|
||||
var index = CPNotFound;
|
||||
|
||||
while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
|
||||
[_items[index] setSelected:YES];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(collectionViewDidChangeSelection:)])
|
||||
[_delegate collectionViewDidChangeSelection:self]
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a set of the selected indices.
|
||||
*/
|
||||
- (CPIndexSet)selectionIndexes
|
||||
{
|
||||
return _selectionIndexes;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)reloadContent
|
||||
{
|
||||
// Remove current views
|
||||
var count = _items.length;
|
||||
|
||||
while (count--)
|
||||
{
|
||||
[[_items[count] view] removeFromSuperview];
|
||||
_cachedItems.push(_items[count]);
|
||||
}
|
||||
|
||||
_items = [];
|
||||
|
||||
if (!_itemData || !_content)
|
||||
return;
|
||||
|
||||
var index = 0;
|
||||
|
||||
count = _content.length;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
_items.push([self newItemForRepresentedObject:_content[index]]);
|
||||
|
||||
[self addSubview:[_items[index] view]];
|
||||
}
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)tile
|
||||
{
|
||||
var width = CGRectGetWidth([self bounds]);
|
||||
|
||||
if (![_content count] || width == _tileWidth)
|
||||
return;
|
||||
|
||||
// We try to fit as many views per row as possible. Any remaining space is then
|
||||
// either proportioned out to the views (if their minSize != maxSize) or used as
|
||||
// margin
|
||||
var itemSize = CGSizeMakeCopy(_minItemSize);
|
||||
|
||||
_numberOfColumns = MAX(1.0, FLOOR(width / itemSize.width));
|
||||
|
||||
if (_maxNumberOfColumns > 0)
|
||||
_numberOfColumns = MIN(_maxNumberOfColumns, _numberOfColumns);
|
||||
|
||||
var remaining = width - _numberOfColumns * itemSize.width,
|
||||
itemsNeedSizeUpdate = NO;
|
||||
|
||||
if (remaining > 0 && itemSize.width < _maxItemSize.width)
|
||||
itemSize.width = MIN(_maxItemSize.width, itemSize.width + FLOOR(remaining / _numberOfColumns));
|
||||
|
||||
if (!CGSizeEqualToSize(_itemSize, itemSize))
|
||||
{
|
||||
_itemSize = itemSize;
|
||||
itemsNeedSizeUpdate = YES;
|
||||
}
|
||||
|
||||
var index = 0,
|
||||
count = _items.length;
|
||||
|
||||
if (_maxNumberOfColumns > 0 && _maxNumberOfRows > 0)
|
||||
count = MIN(count, _maxNumberOfColumns * _maxNumberOfRows);
|
||||
|
||||
_numberOfRows = CEIL(count / _numberOfColumns);
|
||||
|
||||
_horizontalMargin = FLOOR((width - _numberOfColumns * itemSize.width) / (_numberOfColumns + 1));
|
||||
|
||||
var x = _horizontalMargin,
|
||||
y = -itemSize.height;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
if (index % _numberOfColumns == 0)
|
||||
{
|
||||
x = _horizontalMargin;
|
||||
y += _verticalMargin + itemSize.height;
|
||||
}
|
||||
|
||||
var view = [_items[index] view];
|
||||
|
||||
[view setFrameOrigin:CGPointMake(x, y)];
|
||||
|
||||
if (itemsNeedSizeUpdate)
|
||||
[view setFrameSize:_itemSize];
|
||||
|
||||
x += itemSize.width + _horizontalMargin;
|
||||
}
|
||||
|
||||
_tileWidth = width;
|
||||
[self setFrameSize:CGSizeMake(width, y + itemSize.height + _verticalMargin)];
|
||||
_tileWidth = -1.0;
|
||||
}
|
||||
|
||||
- (void)resizeSubviewsWithOldSize:(CGSize)aSize
|
||||
{
|
||||
[self tile];
|
||||
}
|
||||
|
||||
// Laying Out the Collection View
|
||||
/*!
|
||||
Sets the maximum number of rows.
|
||||
@param aMaxNumberOfRows the new maximum number of rows
|
||||
*/
|
||||
- (void)setMaxNumberOfRows:(unsigned)aMaxNumberOfRows
|
||||
{
|
||||
if (_maxNumberOfRows == aMaxNumberOfRows)
|
||||
return;
|
||||
|
||||
_maxNumberOfRows = aMaxNumberOfRows;
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the maximum number of rows.
|
||||
*/
|
||||
- (unsigned)maxNumberOfRows
|
||||
{
|
||||
return _maxNumberOfRows;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the maximum number of columns.
|
||||
@param aMaxNumberOfColumns the new maximum number of columns
|
||||
*/
|
||||
- (void)setMaxNumberOfColumns:(unsigned)aMaxNumberOfColumns
|
||||
{
|
||||
if (_maxNumberOfColumns == aMaxNumberOfColumns)
|
||||
return;
|
||||
|
||||
_maxNumberOfColumns = aMaxNumberOfColumns;
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the maximum number of columns
|
||||
*/
|
||||
- (unsigned)maxNumberOfColumns
|
||||
{
|
||||
return _maxNumberOfColumns;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the minimum size for an item
|
||||
@param aSize the new minimum item size
|
||||
*/
|
||||
- (void)setMinItemSize:(CGSize)aSize
|
||||
{
|
||||
if (CGSizeEqualToSize(_minItemSize, aSize))
|
||||
return;
|
||||
|
||||
_minItemSize = CGSizeMakeCopy(aSize);
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current minimum item size
|
||||
*/
|
||||
- (CGSize)minItemSize
|
||||
{
|
||||
return _minItemSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the maximum item size.
|
||||
@param aSize the new maximum item size
|
||||
*/
|
||||
- (void)setMaxItemSize:(CGSize)aSize
|
||||
{
|
||||
if (CGSizeEqualToSize(_maxItemSize, aSize))
|
||||
return;
|
||||
|
||||
_maxItemSize = CGSizeMakeCopy(aSize);
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current maximum item size.
|
||||
*/
|
||||
- (CGSize)maxItemSize
|
||||
{
|
||||
return _maxItemSize;
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
if ([_selectionIndexes count] && [anEvent clickCount] == 2 && [_delegate respondsToSelector:@selector(collectionView:didDoubleClickOnItemAtIndex:)])
|
||||
[_delegate collectionView:self didDoubleClickOnItemAtIndex:[_selectionIndexes firstIndex]];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
row = FLOOR(location.y / (_itemSize.height + _verticalMargin)),
|
||||
column = FLOOR(location.x / (_itemSize.width + _horizontalMargin)),
|
||||
index = row * _numberOfColumns + column;
|
||||
|
||||
if (index >= 0 && index < _items.length)
|
||||
[self setSelectionIndexes:[CPIndexSet indexSetWithIndex:index]];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
if (![_delegate respondsToSelector:@selector(collectionView:dragTypesForItemsAtIndexes:)])
|
||||
return;
|
||||
|
||||
// If we don't have any selected items, we've clicked away, and thus the drag is meaningless.
|
||||
if (![_selectionIndexes count])
|
||||
return;
|
||||
|
||||
// Set up the pasteboard
|
||||
var dragTypes = [_delegate collectionView:self dragTypesForItemsAtIndexes:_selectionIndexes];
|
||||
|
||||
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:dragTypes owner:self];
|
||||
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
[_itemForDragging setRepresentedObject:_content[[_selectionIndexes firstIndex]]];
|
||||
|
||||
var view = [_itemForDragging view],
|
||||
frame = [view frame];
|
||||
|
||||
[view setFrameSize:_itemSize];
|
||||
|
||||
[self dragView:view
|
||||
at:[[_items[[_selectionIndexes firstIndex]] view] frame].origin
|
||||
offset:CGPointMakeZero()
|
||||
event:anEvent
|
||||
pasteboard:nil
|
||||
source:self
|
||||
slideBack:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Places the selected items on the specified pasteboard. The items are requested from the collection's delegate.
|
||||
@param aPasteboard the pasteboard to put the items on
|
||||
@param aType the format the pasteboard data
|
||||
*/
|
||||
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
|
||||
{
|
||||
[aPasteboard setData:[_delegate collectionView:self dataForItemsAtIndexes:_selectionIndexes forType:aType] forType:aType];
|
||||
}
|
||||
|
||||
// Cappuccino Additions
|
||||
|
||||
- (void)setVerticalMargin:(float)aVerticalMargin
|
||||
{
|
||||
if (_verticalMargin == aVerticalMargin)
|
||||
return;
|
||||
|
||||
_verticalMargin = aVerticalMargin;
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the collection view's delegate
|
||||
@param aDelegate the new delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the collection view's delegate
|
||||
*/
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
Represents an object inside a CPCollectionView.
|
||||
*/
|
||||
@implementation CPCollectionViewItem : CPObject
|
||||
{
|
||||
id _representedObject;
|
||||
|
||||
CPView _view;
|
||||
|
||||
BOOL _isSelected;
|
||||
}
|
||||
|
||||
// Setting the Represented Object
|
||||
/*!
|
||||
Sets the object to be represented by this item.
|
||||
@param anObject the object to be represented
|
||||
*/
|
||||
- (void)setRepresentedObject:(id)anObject
|
||||
{
|
||||
if (_representedObject == anObject)
|
||||
return;
|
||||
|
||||
_representedObject = anObject;
|
||||
|
||||
// FIXME: This should be set up by bindings
|
||||
[_view setRepresentedObject:anObject];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the object represented by this view item
|
||||
*/
|
||||
- (id)representedObject
|
||||
{
|
||||
return _representedObject;
|
||||
}
|
||||
|
||||
// Modifying the View
|
||||
/*!
|
||||
Sets the view that is used represent this object.
|
||||
@param aView the view used to represent this object
|
||||
*/
|
||||
- (void)setView:(CPView)aView
|
||||
{
|
||||
_view = aView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the view that represents this object.
|
||||
*/
|
||||
- (CPView)view
|
||||
{
|
||||
return _view;
|
||||
}
|
||||
|
||||
// Modifying the Selection
|
||||
/*!
|
||||
Sets whether this view item should be selected.
|
||||
@param shouldBeSelected <code>YES</code> makes the item selected. <code>NO</code> deselects it.
|
||||
*/
|
||||
- (void)setSelected:(BOOL)shouldBeSelected
|
||||
{
|
||||
if (_isSelected == shouldBeSelected)
|
||||
return;
|
||||
|
||||
_isSelected = shouldBeSelected;
|
||||
|
||||
// FIXME: This should be set up by bindings
|
||||
[_view setSelected:_isSelected];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the item is currently selected. <code>NO</code> if the item is not selected.
|
||||
*/
|
||||
- (BOOL)isSelected
|
||||
{
|
||||
return _isSelected;
|
||||
}
|
||||
|
||||
// Parent Collection View
|
||||
/*!
|
||||
Returns the collection view of which this item is a part.
|
||||
*/
|
||||
- (CPCollectionView)collectionView
|
||||
{
|
||||
return [_view superview];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPCollectionViewItemViewKey = @"CPCollectionViewItemViewKey";
|
||||
|
||||
@implementation CPCollectionViewItem (CPCoding)
|
||||
|
||||
/*
|
||||
FIXME Not yet implemented
|
||||
*/
|
||||
- (id)copy
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPCollectionViewItemViewKey = @"CPCollectionViewItemViewKey";
|
||||
|
||||
@implementation CPCollectionViewItem (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the view item by unarchiving data from a coder.
|
||||
@param aCoder the coder from which the data will be unarchived
|
||||
@return the initialized collection view item
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_view = [aCoder decodeObjectForKey:CPCollectionViewItemViewKey];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives the colletion view item to the provided coder.
|
||||
@param aCoder the coder to which the view item should be archived
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_view forKey:CPCollectionViewItemViewKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,564 @@
|
||||
/*
|
||||
* CPColor.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CGColor.j"
|
||||
|
||||
@import "CPCompatibility.j"
|
||||
@import "CPImage.j"
|
||||
|
||||
|
||||
var _redComponent = 0,
|
||||
_greenComponent = 1,
|
||||
_blueComponent = 2,
|
||||
_alphaCompnent = 3;
|
||||
|
||||
var _hueComponent = 0,
|
||||
_saturationComponent = 1,
|
||||
_brightnessComponent = 2;
|
||||
|
||||
/*! @code CPColor
|
||||
|
||||
<code>CPColor</code> can be used to represent color
|
||||
in an RGB or HSB model with an optional transparency value.</p>
|
||||
|
||||
<p>It also provides some class helper methods that
|
||||
returns instances of commonly used colors.</p>
|
||||
|
||||
<p>The class does not have a <code>set:</code> method
|
||||
like NextStep based frameworks to change the color of
|
||||
the current context. To change the color of the current
|
||||
context, use CGContextSetFillColor().
|
||||
*/
|
||||
@implementation CPColor : CPObject
|
||||
{
|
||||
CPArray _components;
|
||||
|
||||
CPImage _patternImage;
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a color in the RGB color space, with an alpha value.
|
||||
Each component should be between the range of 0.0 to 1.0. For
|
||||
the alpha component, a value of 1.0 is opaque, and 0.0 means
|
||||
completely transparent.
|
||||
|
||||
@param red the red component of the color
|
||||
@param green the green component of the color
|
||||
@param blue the blue component of the color
|
||||
@param alpha the alpha component
|
||||
|
||||
@return a color initialized to the values specified
|
||||
*/
|
||||
+ (CPColor)colorWithCalibratedRed:(float)red green:(float)green blue:(float)blue alpha:(float)alpha
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[red, green, blue, alpha]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new color object with <code>white</code> for the RGB components.
|
||||
For the alpha component, a value of 1.0 is opaque, and 0.0 means completely transparent.
|
||||
|
||||
@param white a float between 0.0 and 1.0
|
||||
@param alpha the alpha component between 0.0 and 1.0
|
||||
|
||||
@return a color initialized to the values specified
|
||||
*/
|
||||
+ (CPColor)colorWithCalibratedWhite:(float)white alpha:(float)alpha
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[white, white, white, alpha]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new color in HSB space.
|
||||
|
||||
@param hue the hue value
|
||||
@param saturation the saturation value
|
||||
@param brightness the brightness value
|
||||
|
||||
@return the initialized color
|
||||
*/
|
||||
+ (CPColor)colorWithHue:(float)hue saturation:(float)saturation brightness:(float)brightness
|
||||
{
|
||||
if(saturation == 0.0)
|
||||
return [CPColor colorWithCalibratedWhite: brightness/100.0 alpha: 1.0];
|
||||
|
||||
var f = hue % 60,
|
||||
p = (brightness * (100 - saturation)) / 10000,
|
||||
q = (brightness * (6000 - saturation * f)) / 600000,
|
||||
t = (brightness * (6000 - saturation * (60 -f))) / 600000,
|
||||
b = brightness / 100.0;
|
||||
|
||||
switch(FLOOR(hue / 60))
|
||||
{
|
||||
case 0: return [CPColor colorWithCalibratedRed: b green: t blue: p alpha: 1.0];
|
||||
case 1: return [CPColor colorWithCalibratedRed: q green: b blue: p alpha: 1.0];
|
||||
case 2: return [CPColor colorWithCalibratedRed: p green: b blue: t alpha: 1.0];
|
||||
case 3: return [CPColor colorWithCalibratedRed: p green: q blue: b alpha: 1.0];
|
||||
case 4: return [CPColor colorWithCalibratedRed: t green: p blue: b alpha: 1.0];
|
||||
case 5: return [CPColor colorWithCalibratedRed: b green: p blue: q alpha: 1.0];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates an RGB color from a hexadecimal string. For example,
|
||||
the a string of "FFFFFF" would return a white CPColor.
|
||||
"FF0000" would return a pure red, "00FF00" would return a
|
||||
pure blue, and "0000FF" would return a pure green.
|
||||
|
||||
@param hex a 6 character long string of hex
|
||||
|
||||
@return an initialized RGB color
|
||||
*/
|
||||
+ (CPColor)colorWithHexString:(string)hex
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA: hexToRGB(hex)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a black color object. (RGBA=[0.0, 0.0, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)blackColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a blue color object. (RGBA=[0.0, 0.0, 1.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)blueColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 1.0, 1.0]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a dark gray color object. (RGBA=[0.33 ,0.33, 0.33, 1.0])
|
||||
*/
|
||||
+ (CPColor)darkGrayColor
|
||||
{
|
||||
return [CPColor colorWithCalibratedWhite:1.0 / 3.0 alpha:1.0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a gray color object. (RGBA=[0.5, 0.5, 0.5, 1.0])
|
||||
*/
|
||||
+ (CPColor)grayColor
|
||||
{
|
||||
return [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a green color object. (RGBA=[0.0, 1.0, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)greenColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[0.0, 1.0, 0.0, 1.0]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a light gray color object (RGBA=[0.66, 0.66, 0.66, 1.0])
|
||||
*/
|
||||
+ (CPColor)lightGrayColor
|
||||
{
|
||||
return [CPColor colorWithCalibratedWhite:2.0 / 3.0 alpha:1.0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a red color object (RGBA=[1.0, 0.0, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)redColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[1.0, 0.0, 0.0, 1.0]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a white color object (RGBA=[1.0, 1.0, 1.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)whiteColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 1.0, 1.0]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a yellow color object (RGBA=[1.0, 1.0, 0.0, 1.0])
|
||||
*/
|
||||
+ (CPColor)yellowColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[1.0, 1.0, 0.0, 1.0]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a shadow looking color (RGBA=[0.0, 0.0, 0.0, 0.33])
|
||||
*/
|
||||
+ (CPColor)shadowColor
|
||||
{
|
||||
return [[CPColor alloc] _initWithRGBA:[0.0, 0.0, 0.0, 1.0 / 3.0]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a color using a tile pattern with <code>anImage</code>
|
||||
@param the image to tile
|
||||
@return a tiled image color object
|
||||
*/
|
||||
+ (CPColor)colorWithPatternImage:(CPImage)anImage
|
||||
{
|
||||
return [[CPColor alloc] _initWithPatternImage:anImage];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a CPColor from a valid CSS RGB string. Example, "rgb(32,64,129)".
|
||||
|
||||
@param aString a CSS color string
|
||||
@return a color initialized to the value in the css string
|
||||
*/
|
||||
+ (CPColor)colorWithCSSString:(CPString)aString
|
||||
{
|
||||
return [[CPColor alloc] _initWithCSSString: aString];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithCSSString:(CPString)aString
|
||||
{
|
||||
if(aString.indexOf("rgb") == CPNotFound)
|
||||
return nil;
|
||||
|
||||
self = [super init];
|
||||
|
||||
var startingIndex = aString.indexOf("(");
|
||||
var parts = aString.substring(startingIndex+1).split(',');
|
||||
|
||||
_components = [
|
||||
parseInt(parts[0], 10) / 255.0,
|
||||
parseInt(parts[1], 10) / 255.0,
|
||||
parseInt(parts[2], 10) / 255.0,
|
||||
parts[3] ? parseInt(parts[3], 10) / 255.0 : 1.0
|
||||
]
|
||||
|
||||
_cssString = aString;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithRGBA:(CPArray)components
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_components = components;
|
||||
|
||||
if (!CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0 && window.Base64 && window.CRC32)
|
||||
{
|
||||
var bytes = [0x89,0x50,0x4e,0x47,0xd,0xa,0x1a,0xa,0x0,0x0,0x0,0xd,0x49,0x48,0x44,0x52,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1,0x8,0x3,0x0,0x0,0x0,0x28,0xcb,0x34,0xbb,0x0,0x0,0x3,0x0,0x50,0x4c,0x54,0x45,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x17,0x89,0x99,0x55,0x0,0x0,0x0,0x1,0x74,0x52,0x4e,0x53,0x0,0x40,0xe6,0xd8,0x66,0x0,0x0,0x0,0x10,0x49,0x44,0x41,0x54,0x78,0xda,0x62,0x60,0x0,0x0,0x0,0x0,0xff,0xff,0x3,0x0,0x0,0x2,0x0,0x1,0x24,0x7f,0x24,0xf1,0x0,0x0,0x0,0x0,0x49,0x45,0x4e,0x44,0xae,0x42,0x60,0x82,0xff];
|
||||
var r_off = 41;
|
||||
var g_off = 42;
|
||||
var b_off = 43;
|
||||
var a_off = 821;
|
||||
var plte_crc_off = 809;
|
||||
var trns_crc_off = 822;
|
||||
var plte_type_off = 37;
|
||||
var trns_type_off = 817;
|
||||
|
||||
bytes[r_off] = Math.round(_components[0]*255);
|
||||
bytes[g_off] = Math.round(_components[1]*255);
|
||||
bytes[b_off] = Math.round(_components[2]*255);
|
||||
bytes[a_off] = Math.round(_components[3]*255);
|
||||
|
||||
// calculate new CRCs
|
||||
var new_plte_crc = integerToBytes(CRC32.getCRC(bytes, plte_type_off, 4+768), 4);
|
||||
var new_trns_crc = integerToBytes(CRC32.getCRC(bytes, trns_type_off, 4+1), 4);
|
||||
|
||||
// overwrite old CRCs with new ones
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
bytes[plte_crc_off+i] = new_plte_crc[i];
|
||||
bytes[trns_crc_off+i] = new_trns_crc[i];
|
||||
}
|
||||
|
||||
// Base64 encode, strip whitespace and build data URL
|
||||
var base64image = Base64.encode(bytes); //.replace(/[\s]/g, "");
|
||||
|
||||
_cssString = "url(\"data:image/png;base64," + base64image + "\")";
|
||||
}
|
||||
else
|
||||
{
|
||||
var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
|
||||
|
||||
_cssString = (hasAlpha ? "rgba(" : "rgb(") +
|
||||
parseInt(_components[0] * 255.0) + ", " +
|
||||
parseInt(_components[1] * 255.0) + ", " +
|
||||
parseInt(_components[2] * 255.0) +
|
||||
(hasAlpha ? (", " + _components[3]) : "") + ")";
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithPatternImage:(CPImage)anImage
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_patternImage = anImage;
|
||||
_cssString = "url(\"" + _patternImage._filename + "\")";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the image being used as the pattern for the tile in this color.
|
||||
*/
|
||||
- (CPImage)patternImage
|
||||
{
|
||||
return _patternImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the alpha component of this color.
|
||||
*/
|
||||
- (float)alphaComponent
|
||||
{
|
||||
return _components[3];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the blue component of this color.
|
||||
*/
|
||||
- (float)blueComponent
|
||||
{
|
||||
return _components[2];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the green component of this color.
|
||||
*/
|
||||
- (float)greenComponent
|
||||
{
|
||||
return _components[1];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the red component of this color.
|
||||
*/
|
||||
- (float)redComponent
|
||||
{
|
||||
return _components[0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the RGBA components of this color in an array.
|
||||
The index values are ordered as:
|
||||
<pre>
|
||||
<b>Index</b> <b>Component</b>
|
||||
0 Red
|
||||
1 Green
|
||||
2 Blue
|
||||
3 Alpha
|
||||
</pre>
|
||||
*/
|
||||
- (CPArray)components
|
||||
{
|
||||
return _components;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new color with the same RGB as the receiver but a new alpha component.
|
||||
|
||||
@param anAlphaComponent the alpha component for the new color
|
||||
|
||||
@return a new color object
|
||||
*/
|
||||
- (CPColor)colorWithAlphaComponent:(float)anAlphaComponent
|
||||
{
|
||||
var components = _components.slice();
|
||||
|
||||
components[components.length - 1] = anAlphaComponent;
|
||||
|
||||
return [[[self class] alloc] _initWithRGBA:components];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array with the HSB values for this color.
|
||||
The index values are ordered as:
|
||||
<pre>
|
||||
<b>Index</b> <b>Component</b>
|
||||
0 Hue
|
||||
1 Saturation
|
||||
2 Brightness
|
||||
</pre>
|
||||
*/
|
||||
- (CPArray)hsbComponents
|
||||
{
|
||||
var red = ROUND(_components[_redComponent] * 255.0),
|
||||
green = ROUND(_components[_greenComponent] * 255.0),
|
||||
blue = ROUND(_components[_blueComponent] * 255.0);
|
||||
|
||||
var max = MAX(red, green, blue),
|
||||
min = MIN(red, green, blue),
|
||||
delta = max - min;
|
||||
|
||||
var brightness = max / 255.0,
|
||||
saturation = (max != 0) ? delta / max : 0;
|
||||
|
||||
var hue;
|
||||
if(saturation == 0)
|
||||
hue = 0;
|
||||
else
|
||||
{
|
||||
var rr = (max - red) / delta;
|
||||
var gr = (max - green) / delta;
|
||||
var br = (max - blue) / delta;
|
||||
|
||||
if (red == max)
|
||||
hue = br - gr;
|
||||
else if (green == max)
|
||||
hue = 2 + rr - br;
|
||||
else
|
||||
hue = 4 + gr - rr;
|
||||
|
||||
hue /= 6;
|
||||
if (hue < 0)
|
||||
hue++;
|
||||
}
|
||||
|
||||
return [
|
||||
ROUND(hue * 360.0),
|
||||
ROUND(saturation * 100.0),
|
||||
ROUND(brightness * 100.0)
|
||||
];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CSS representation of this color. The color will
|
||||
be in one of the following forms:
|
||||
<pre>
|
||||
rgb(22,44,88)
|
||||
rgba(22,44,88,0.5) // if there is an alpha
|
||||
url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
|
||||
</pre>
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
return _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a 6 character long hex string of this color.
|
||||
*/
|
||||
- (CPString)hexString
|
||||
{
|
||||
return rgbToHex([self redComponent], [self greenComponent], [self blueComponent])
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPColorComponentsKey = @"CPColorComponentsKey",
|
||||
CPColorPatternImageKey = @"CPColorPatternImageKey";
|
||||
|
||||
@implementation CPColor (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes this color from the data archived in a coder.
|
||||
@param aCoder the coder from which the color will be loaded
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if ([aCoder containsValueForKey:CPColorPatternImageKey])
|
||||
return [self _initWithPatternImage:[aCoder decodeObjectForKey:CPColorPatternImageKey]];
|
||||
|
||||
return [self _initWithRGBA:[aCoder decodeObjectForKey:CPColorComponentsKey]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives this color into a coder.
|
||||
@param aCoder the coder into which the color will be archived.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (_patternImage)
|
||||
[aCoder encodeObject:_patternImage forKey:CPColorPatternImageKey];
|
||||
else
|
||||
[aCoder encodeObject:_components forKey:CPColorComponentsKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var hexCharacters = "0123456789ABCDEF";
|
||||
|
||||
/*!
|
||||
Used for the CPColor <code>colorWithHexString:</code> implementation
|
||||
@ignore
|
||||
@class CPColor
|
||||
@return an array of rgb components
|
||||
*/
|
||||
function hexToRGB(hex)
|
||||
{
|
||||
if(hex.length != 6)
|
||||
return null;
|
||||
|
||||
hex = hex.toUpperCase();
|
||||
|
||||
for(var i=0; i<hex.length; i++)
|
||||
if(hexCharacters.indexOf(hex.charAt(i)) == -1)
|
||||
return null;
|
||||
|
||||
var red = (hexCharacters.indexOf(hex.charAt(0)) * 16 + hexCharacters.indexOf(hex.charAt(1))) / 255.0;
|
||||
var green = (hexCharacters.indexOf(hex.charAt(2)) * 16 + hexCharacters.indexOf(hex.charAt(3))) / 255.0;
|
||||
var blue = (hexCharacters.indexOf(hex.charAt(4)) * 16 + hexCharacters.indexOf(hex.charAt(5))) / 255.0;
|
||||
|
||||
return [red, green, blue, 1.0];
|
||||
}
|
||||
|
||||
function integerToBytes(integer, length) {
|
||||
if (!length)
|
||||
length = (integer == 0) ? 1 : Math.round((Math.log(integer)/Math.log(2))/8+0.5);
|
||||
|
||||
var bytes = new Array(length);
|
||||
for (var i = length-1; i >= 0; i--) {
|
||||
bytes[i] = integer & 255;
|
||||
integer = integer >> 8
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function rgbToHex(r,g,b) {
|
||||
return byteToHex(r) + byteToHex(g) + byteToHex(b);
|
||||
}
|
||||
|
||||
function byteToHex(n) {
|
||||
if (!n || isNaN(n)) return "00";
|
||||
n = ROUND(MIN(255,MAX(0,256*n)));
|
||||
return hexCharacters.charAt((n - n % 16) / 16) +
|
||||
hexCharacters.charAt(n % 16);
|
||||
}
|
||||
|
||||
// Toll-Free bridge CPColor to CGColor.
|
||||
//CGColor.prototype.isa = CPColor;
|
||||
//[CPColor initialize];
|
||||
|
||||
//http://dev.mootools.net/browser/trunk/Source/Utilities/Color.js?rev=1184
|
||||
@@ -0,0 +1,624 @@
|
||||
/*
|
||||
* CPColorPanel.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPButton.j"
|
||||
@import "CPCookie.j"
|
||||
@import "CPPanel.j"
|
||||
@import "CPView.j"
|
||||
|
||||
CPColorPanelColorDidChangeNotification = @"CPColorPanelColorDidChangeNotification";
|
||||
|
||||
var PREVIEW_HEIGHT = 20.0,
|
||||
TOOLBAR_HEIGHT = 32.0,
|
||||
SWATCH_HEIGHT = 14.0,
|
||||
ICON_WIDTH = 32.0,
|
||||
ICON_PADDING = 12.0;
|
||||
|
||||
var SharedColorPanel = nil,
|
||||
ColorPickerClasses = [];
|
||||
|
||||
/*
|
||||
A color wheel
|
||||
@global
|
||||
@group CPColorPanelMode
|
||||
*/
|
||||
CPWheelColorPickerMode = 1;
|
||||
/*
|
||||
Slider based picker
|
||||
@global
|
||||
@group CPColorPanelMode
|
||||
*/
|
||||
CPSliderColorPickerMode = 2;
|
||||
|
||||
CPColorPickerViewWidth = 265,
|
||||
CPColorPickerViewHeight = 370;
|
||||
|
||||
/*! @class CPColorPanel
|
||||
|
||||
CPColorPanel provides a reusable panel that can be used
|
||||
displayed on screen to prompt the user for a color selection. To
|
||||
obtain the panel, call the <code>sharedColorPanel</code> method.
|
||||
*/
|
||||
@implementation CPColorPanel : CPPanel
|
||||
{
|
||||
_CPColorPanelToolbar _toolbar;
|
||||
_CPColorPanelSwatches _swatchView;
|
||||
_CPColorPanelPreview _previewView;
|
||||
|
||||
CPTextField _previewLabel;
|
||||
CPTextField _swatchLabel;
|
||||
|
||||
CPArray _colorPickers;
|
||||
CPView _currentView;
|
||||
|
||||
CPColor _color;
|
||||
|
||||
id _target;
|
||||
SEL _action;
|
||||
|
||||
int _mode;
|
||||
}
|
||||
|
||||
/*!
|
||||
A list of color pickers is collected here, and any color panel created will contain
|
||||
any picker in this list up to this point. In other words, call before creating a color panel.
|
||||
*/
|
||||
+ (void)provideColorPickerClass:(Class)aColorPickerSubclass
|
||||
{
|
||||
ColorPickerClasses.push(aColorPickerSubclass);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns (and if necessary, creates) the shared color panel.
|
||||
*/
|
||||
+ (CPColorPanel)sharedColorPanel
|
||||
{
|
||||
if (!SharedColorPanel)
|
||||
SharedColorPanel = [[CPColorPanel alloc] init];
|
||||
|
||||
return SharedColorPanel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the mode for the shared color panel.
|
||||
@param mode the mode to which the color panel will be set
|
||||
*/
|
||||
+ (void)setPickerMode:(CPColorPanelMode)mode
|
||||
{
|
||||
var panel = [CPColorPanel sharedColorPanel];
|
||||
[panel setMode: mode];
|
||||
}
|
||||
|
||||
/*
|
||||
To obtain the color panel, use <code>sharedColorPanel</code>.
|
||||
@ignore
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super initWithContentRect:CGRectMake(500.0, 50.0, 218.0, 360.0)
|
||||
styleMask:(CPHUDBackgroundWindowMask | CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask)];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setTitle:@"Color Panel"];
|
||||
[self setLevel:CPFloatingWindowLevel];
|
||||
|
||||
[self setFloatingPanel:YES];
|
||||
[self setBecomesKeyOnlyIfNeeded:YES];
|
||||
|
||||
[self setMinSize:CGSizeMake(218.0, 360.0)];
|
||||
[self setMaxSize:CGSizeMake(327.0, 540.0)];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color of the panel, and updates the picker. Also posts a <code>CPColorPanelDidChangeNotification</code>.
|
||||
*/
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
_color = aColor;
|
||||
[_previewView setBackgroundColor: _color];
|
||||
|
||||
[CPApp sendAction:@selector(changeColor:) to:nil from:self];
|
||||
|
||||
if (_target && _action)
|
||||
[CPApp sendAction:_action to:_target from:self];
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPColorPanelColorDidChangeNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the selected color of the panel and optionally updates the picker.
|
||||
@param bool whether or not to update the picker
|
||||
@ignore
|
||||
*/
|
||||
- (void)setColor:(CPColor)aColor updatePicker:(BOOL)bool
|
||||
{
|
||||
[self setColor:aColor];
|
||||
|
||||
if (bool)
|
||||
[_activePicker setColor:_color];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the panel's currently selected color.
|
||||
*/
|
||||
- (CPColor)color
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the target for the color panel. Messages are sent
|
||||
to the target when colors are selected in the panel.
|
||||
*/
|
||||
- (void)setTarget:(id)aTarget
|
||||
{
|
||||
_target = aTarget;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current target. The target receives messages
|
||||
when colors are selected in the panel.
|
||||
*/
|
||||
- (id)target
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the action that gets sent to the target.
|
||||
This action is sent whenever a color is selected in the panel.
|
||||
@param anAction the action that will be sent
|
||||
*/
|
||||
- (void)setAction:(selector)anAction
|
||||
{
|
||||
_action = anAction;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current target action.
|
||||
*/
|
||||
- (selector)action
|
||||
{
|
||||
return _action;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the mode (look) of the color panel.
|
||||
@param mode the mode in which to display the color panel
|
||||
*/
|
||||
- (void)setMode:(CPColorPanelMode)mode
|
||||
{
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
- (void)_setPicker:(id)sender
|
||||
{
|
||||
var picker = _colorPickers[[sender tag]],
|
||||
view = [picker provideNewView:NO];
|
||||
|
||||
if (!view)
|
||||
view = [picker provideNewView:YES];
|
||||
|
||||
if (view == _currentView)
|
||||
return;
|
||||
|
||||
if (_currentView)
|
||||
[view setFrame:[_currentView frame]];
|
||||
else
|
||||
{
|
||||
var height = (TOOLBAR_HEIGHT+10+PREVIEW_HEIGHT+5+SWATCH_HEIGHT+10),
|
||||
bounds = [[self contentView] bounds];
|
||||
|
||||
[view setFrameSize: CPSizeMake(bounds.size.width - 10, bounds.size.height - height)];
|
||||
[view setFrameOrigin: CPPointMake(5, height)];
|
||||
}
|
||||
|
||||
[_currentView removeFromSuperview];
|
||||
[[self contentView] addSubview:view];
|
||||
|
||||
_currentView = view;
|
||||
_activePicker = picker;
|
||||
|
||||
[picker setColor:[self color]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the color panel's current display mode.
|
||||
*/
|
||||
- (CPColorPanelMode)mode
|
||||
{
|
||||
return _mode;
|
||||
}
|
||||
|
||||
- (void)orderFront:(id)aSender
|
||||
{
|
||||
[self _loadContentsIfNecessary];
|
||||
[super orderFront:aSender];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_loadContentsIfNecessary
|
||||
{
|
||||
if (_toolbar)
|
||||
return;
|
||||
|
||||
_colorPickers = [];
|
||||
|
||||
var count = [ColorPickerClasses count];
|
||||
for (var i=0; i<count; i++)
|
||||
{
|
||||
var currentPickerClass = ColorPickerClasses[i],
|
||||
currentPicker = [[currentPickerClass alloc] initWithPickerMask:0 colorPanel:self];
|
||||
|
||||
_colorPickers.push(currentPicker);
|
||||
}
|
||||
|
||||
var contentView = [self contentView],
|
||||
bounds = [contentView bounds];
|
||||
|
||||
_toolbar = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(bounds), TOOLBAR_HEIGHT)];
|
||||
[_toolbar setAutoresizingMask: CPViewWidthSizable];
|
||||
|
||||
var totalToolbarWidth = count * ICON_WIDTH + (count - 1) * ICON_PADDING,
|
||||
leftOffset = (CGRectGetWidth(bounds) - totalToolbarWidth) / 2.0,
|
||||
buttonForLater = nil;
|
||||
|
||||
for (var i=0; i<count; i++)
|
||||
{
|
||||
var image = [_colorPickers[i] provideNewButtonImage],
|
||||
highlightImage = [_colorPickers[i] provideNewAlternateButtonImage],
|
||||
button = [[CPButton alloc] initWithFrame:CGRectMake(leftOffset + i*(ICON_WIDTH+ICON_PADDING), 0, ICON_WIDTH, ICON_WIDTH)];
|
||||
|
||||
[button setTag:i];
|
||||
[button setTarget:self];
|
||||
[button setAction:@selector(_setPicker:)];
|
||||
[button setBordered:NO];
|
||||
[button setAutoresizingMask:CPViewMinXMargin|CPViewMaxXMargin];
|
||||
|
||||
[button setImage:image];
|
||||
[button setAlternateImage:highlightImage];
|
||||
|
||||
[_toolbar addSubview:button];
|
||||
|
||||
if (!buttonForLater)
|
||||
buttonForLater = button;
|
||||
}
|
||||
|
||||
// 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)];
|
||||
|
||||
[_previewView setColorPanel:self];
|
||||
[_previewView setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[previewBox setBackgroundColor:[CPColor grayColor]];
|
||||
[previewBox setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[previewBox addSubview:_previewView];
|
||||
|
||||
_previewLabel = [[CPTextField alloc] initWithFrame: CPRectMake(10, TOOLBAR_HEIGHT + 14, 60, 15)];
|
||||
[_previewLabel setStringValue: "Preview:"];
|
||||
[_previewLabel setTextColor:[CPColor whiteColor]];
|
||||
[_previewLabel setAlignment:CPRightTextAlignment];
|
||||
|
||||
// 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 grayColor]];
|
||||
[swatchBox setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
_swatchView = [[_CPColorPanelSwatches alloc] initWithFrame:CGRectInset([swatchBox bounds], 1.0, 1.0)];
|
||||
|
||||
[_swatchView setColorPanel: self];
|
||||
[_swatchView setAutoresizingMask: CPViewWidthSizable];
|
||||
|
||||
[swatchBox addSubview:_swatchView];
|
||||
|
||||
_swatchLabel = [[CPTextField alloc] initWithFrame: CPRectMake(10, TOOLBAR_HEIGHT + 8 + PREVIEW_HEIGHT + 5, 60, 15)];
|
||||
[_swatchLabel setStringValue: "Swatches:"];
|
||||
[_swatchLabel setTextColor:[CPColor whiteColor]];
|
||||
[_swatchLabel setAlignment:CPRightTextAlignment];
|
||||
|
||||
|
||||
[contentView addSubview: _toolbar];
|
||||
[contentView addSubview: previewBox];
|
||||
[contentView addSubview: _previewLabel];
|
||||
[contentView addSubview: swatchBox];
|
||||
[contentView addSubview: _swatchLabel];
|
||||
|
||||
_target = nil;
|
||||
_action = nil;
|
||||
_activePicker = nil;
|
||||
|
||||
[self setColor:[CPColor whiteColor]];
|
||||
|
||||
if (buttonForLater)
|
||||
[self _setPicker:buttonForLater];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
CPColorDragType = "CPColorDragType";
|
||||
var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
|
||||
|
||||
/* @ignore */
|
||||
@implementation _CPColorPanelSwatches : CPView
|
||||
{
|
||||
CPView[] _swatches;
|
||||
CPColor _dragColor;
|
||||
CPColorPanel _colorPanel;
|
||||
CPCookie _swatchCookie;
|
||||
}
|
||||
|
||||
-(id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
[self setBackgroundColor: [CPColor grayColor]];
|
||||
|
||||
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
|
||||
|
||||
var whiteColor = [CPColor whiteColor];
|
||||
|
||||
_swatchCookie = [[CPCookie alloc] initWithName: CPColorPanelSwatchesCookie];
|
||||
var colorList = [self startingColorList];
|
||||
|
||||
_swatches = [];
|
||||
|
||||
for(var i=0; i < 50; i++)
|
||||
{
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
var view = [[CPView alloc] initWithFrame: CPRectMake(13*i+1, 1, 12, 12)],
|
||||
fillView = [[CPView alloc] initWithFrame:CGRectInset([view bounds], 1.0, 1.0)];
|
||||
|
||||
[view setBackgroundColor:whiteColor];
|
||||
[fillView setBackgroundColor: (i < colorList.length) ? colorList[i] : whiteColor];
|
||||
|
||||
[view addSubview:fillView];
|
||||
|
||||
[self addSubview: view];
|
||||
|
||||
_swatches.push(view);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (CPArray)startingColorList
|
||||
{
|
||||
var cookieValue = [_swatchCookie value];
|
||||
if(cookieValue == "")
|
||||
{
|
||||
return [
|
||||
[CPColor blackColor],
|
||||
[CPColor darkGrayColor],
|
||||
[CPColor grayColor],
|
||||
[CPColor lightGrayColor],
|
||||
[CPColor whiteColor],
|
||||
[CPColor redColor],
|
||||
[CPColor greenColor],
|
||||
[CPColor blueColor],
|
||||
[CPColor yellowColor]
|
||||
];
|
||||
}
|
||||
|
||||
var cookieValue = eval(cookieValue);
|
||||
var result = [];
|
||||
|
||||
for(var i=0; i<cookieValue.length; i++)
|
||||
result.push([CPColor colorWithHexString: cookieValue[i]]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (CPArray)saveColorList
|
||||
{
|
||||
var result = [];
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
for(var i=0; i<_swatches.length; i++)
|
||||
result.push([[[_swatches[i] subviews][0] backgroundColor] hexString]);
|
||||
|
||||
var future = new Date();
|
||||
future.setYear(2019);
|
||||
|
||||
[_swatchCookie setValue: CPJSObjectCreateJSON(result) expires:future domain: nil];
|
||||
}
|
||||
|
||||
- (void)setColorPanel:(CPColorPanel)panel
|
||||
{
|
||||
_colorPanel = panel;
|
||||
}
|
||||
|
||||
- (CPColorPanel)colorPanel
|
||||
{
|
||||
return _colorPanel;
|
||||
}
|
||||
|
||||
- (CPColor)colorAtIndex:(int)index
|
||||
{
|
||||
return [[_swatches[index] subviews][0] backgroundColor];
|
||||
}
|
||||
|
||||
- (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)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if(point.x > [self bounds].size.width - 1 || point.x < 1)
|
||||
return NO;
|
||||
|
||||
[_colorPanel setColor: [self colorAtIndex:FLOOR(point.x / 13)] updatePicker: YES];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if(point.x > [self bounds].size.width - 1 || point.x < 1)
|
||||
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 = CPRectCreateCopy([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)];
|
||||
|
||||
[dragView setBackgroundColor:[CPColor blackColor]];
|
||||
[dragFillView setBackgroundColor:_dragColor];
|
||||
|
||||
[dragView addSubview:dragFillView];
|
||||
|
||||
[self dragView: dragView
|
||||
at: CPPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
|
||||
offset: CPPointMake(0.0, 0.0)
|
||||
event: anEvent
|
||||
pasteboard: nil
|
||||
source: self
|
||||
slideBack: YES];
|
||||
}
|
||||
|
||||
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
|
||||
{
|
||||
if(aType == CPColorDragType)
|
||||
[aPasteboard setData:_dragColor forType:aType];
|
||||
}
|
||||
|
||||
- (void)performDragOperation:(id <CPDraggingInfo>)aSender
|
||||
{
|
||||
var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
|
||||
pasteboard = [aSender draggingPasteboard],
|
||||
swatch = nil;
|
||||
|
||||
if(![pasteboard availableTypeFromArray:[CPColorDragType]] || location.x > [self bounds].size.width - 1 || location.x < 1)
|
||||
return NO;
|
||||
|
||||
[self setColor:[pasteboard dataForType:CPColorDragType] atIndex: FLOOR(location.x / 13)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* @ignore */
|
||||
@implementation _CPColorPanelPreview : CPView
|
||||
{
|
||||
CPColorPanel _colorPanel;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setColorPanel:(CPColorPanel)aPanel
|
||||
{
|
||||
_colorPanel = aPanel;
|
||||
}
|
||||
|
||||
- (CPColorPanel)colorPanel
|
||||
{
|
||||
return _colorPanel;
|
||||
}
|
||||
|
||||
- (void)performDragOperation:(id <CPDraggingInfo>)aSender
|
||||
{
|
||||
var pasteboard = [aSender draggingPasteboard];
|
||||
|
||||
if(![pasteboard availableTypeFromArray:[CPColorDragType]])
|
||||
return NO;
|
||||
|
||||
var color = [pasteboard dataForType:CPColorDragType];
|
||||
[_colorPanel setColor: color updatePicker: YES];
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
|
||||
|
||||
var bounds = CPRectMake(0, 0, 15, 15);
|
||||
|
||||
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
|
||||
var dragView = [[CPView alloc] initWithFrame: bounds];
|
||||
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
|
||||
|
||||
[dragView setBackgroundColor:[CPColor blackColor]];
|
||||
[dragFillView setBackgroundColor:[self backgroundColor]];
|
||||
|
||||
[dragView addSubview:dragFillView];
|
||||
|
||||
[self dragView: dragView
|
||||
at: CPPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
|
||||
offset: CPPointMake(0.0, 0.0)
|
||||
event: anEvent
|
||||
pasteboard: nil
|
||||
source: self
|
||||
slideBack: YES];
|
||||
}
|
||||
|
||||
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
|
||||
{
|
||||
if(aType == CPColorDragType)
|
||||
[aPasteboard setData:[self backgroundColor] forType:aType];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@import "CPColorPicker.j"
|
||||
@import "CPSliderColorPicker.j"
|
||||
|
||||
[CPColorPanel provideColorPickerClass:CPColorWheelColorPicker];
|
||||
[CPColorPanel provideColorPickerClass:CPSliderColorPicker];
|
||||
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* CPColorPicker.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import "CPColorPanel.j"
|
||||
|
||||
/*! @class CPColorPicker
|
||||
|
||||
CPColorPicker is an abstract superclass for all color picker subclasses. If you want a particular color picker, use CPColorPanel's <code>setPickerMode:</code> method. The simplest way to implement your own color picker is to create a subclass of CPColorPicker.
|
||||
*/
|
||||
@implementation CPColorPicker : CPObject
|
||||
{
|
||||
CPColorPanel _panel;
|
||||
int _mask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the color picker.
|
||||
@param aMask a unique unsigned int identifying your color picker
|
||||
@param aPanel the color panel that owns this picker
|
||||
*/
|
||||
- (id)initWithPickerMask:(int)aMask colorPanel:(CPColorPanel)aPanel
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_panel = aPanel;
|
||||
_mask = aMask;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the color panel that owns this picker
|
||||
*/
|
||||
- (CPColorPanel)colorPanel
|
||||
{
|
||||
return _panel;
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME Not implemented.
|
||||
@return <code>nil</code>
|
||||
@ignore
|
||||
*/
|
||||
- (CPImage)provideNewButtonImage
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color picker's mode.
|
||||
@param mode the color panel mode
|
||||
*/
|
||||
- (void)setMode:(CPColorPanelMode)mode
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the picker's color.
|
||||
@param aColor the new color for the picker
|
||||
*/
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
The wheel mode color picker.
|
||||
@ignore
|
||||
*/
|
||||
@implementation CPColorWheelColorPicker : CPColorPicker
|
||||
{
|
||||
CPView _pickerView;
|
||||
CPView _brightnessSlider;
|
||||
DOMElement _brightnessBarImage;
|
||||
__CPColorWheel _hueSaturationView;
|
||||
}
|
||||
|
||||
- (id)initWithPickerMask:(int)mask colorPanel:(CPColorPanel)owningColorPanel
|
||||
{
|
||||
return [super initWithPickerMask:mask colorPanel: owningColorPanel];
|
||||
}
|
||||
|
||||
-(id)initView
|
||||
{
|
||||
aFrame = CPRectMake(0, 0, CPColorPickerViewWidth, CPColorPickerViewHeight);
|
||||
|
||||
_pickerView = [[CPView alloc] initWithFrame:aFrame];
|
||||
[_pickerView setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable];
|
||||
|
||||
var path = [[CPBundle bundleForClass: CPColorPicker] pathForResource:@"brightness_bar.png"];
|
||||
|
||||
_brightnessBarImage = new Image();
|
||||
_brightnessBarImage.src = path;
|
||||
_brightnessBarImage.style.width = "100%";
|
||||
_brightnessBarImage.style.height = "100%";
|
||||
_brightnessBarImage.style.position = "absolute";
|
||||
_brightnessBarImage.style.top = "0px";
|
||||
_brightnessBarImage.style.left = "0px";
|
||||
|
||||
var brightnessBarView = [[CPView alloc] initWithFrame: CPRectMake(0, (aFrame.size.height - 34), aFrame.size.width, 15)];
|
||||
[brightnessBarView setAutoresizingMask: (CPViewWidthSizable | CPViewMinYMargin)];
|
||||
brightnessBarView._DOMElement.appendChild(_brightnessBarImage);
|
||||
|
||||
_brightnessSlider = [[CPSlider alloc] initWithFrame: CPRectMake(0, aFrame.size.height - 22, aFrame.size.width, 12)];
|
||||
[_brightnessSlider setMaxValue: 100.0];
|
||||
[_brightnessSlider setMinValue: 0.0];
|
||||
[_brightnessSlider setFloatValue:100.0];
|
||||
|
||||
[_brightnessSlider setTarget: self];
|
||||
[_brightnessSlider setAction: @selector(brightnessSliderDidChange:)];
|
||||
[_brightnessSlider setAutoresizingMask: (CPViewWidthSizable | CPViewMinYMargin)];
|
||||
|
||||
_hueSaturationView = [[__CPColorWheel alloc] initWithFrame: CPRectMake(0, 0, aFrame.size.width, aFrame.size.height - 38)];
|
||||
[_hueSaturationView setDelegate: self];
|
||||
[_hueSaturationView setAutoresizingMask: (CPViewWidthSizable | CPViewHeightSizable)];
|
||||
|
||||
[_pickerView addSubview: brightnessBarView];
|
||||
[_pickerView addSubview: _hueSaturationView];
|
||||
[_pickerView addSubview: _brightnessSlider];
|
||||
}
|
||||
|
||||
-(void)brightnessSliderDidChange:(id)sender
|
||||
{
|
||||
[self updateColor];
|
||||
}
|
||||
|
||||
-(void)colorWheelDidChange:(id)sender
|
||||
{
|
||||
[self updateColor];
|
||||
}
|
||||
|
||||
-(void)updateColor
|
||||
{
|
||||
var hue = [_hueSaturationView angle],
|
||||
saturation = [_hueSaturationView distance],
|
||||
brightness = [_brightnessSlider floatValue];
|
||||
|
||||
[_hueSaturationView setWheelBrightness: brightness / 100.0];
|
||||
_brightnessBarImage.style.backgroundColor = "#"+[[CPColor colorWithHue: hue saturation: saturation brightness: 100] hexString];
|
||||
|
||||
[[self colorPanel] setColor:[CPColor colorWithHue: hue saturation: saturation brightness: brightness]];
|
||||
}
|
||||
|
||||
- (BOOL)supportsMode:(int)mode
|
||||
{
|
||||
return (mode == CPWheelColorPickerMode) ? YES : NO;
|
||||
}
|
||||
|
||||
- (int)currentMode
|
||||
{
|
||||
return CPWheelColorPickerMode;
|
||||
}
|
||||
|
||||
- (CPView)provideNewView:(BOOL)initialRequest
|
||||
{
|
||||
if (initialRequest)
|
||||
[self initView];
|
||||
|
||||
return _pickerView;
|
||||
}
|
||||
|
||||
- (void)setColor:(CPColor)newColor
|
||||
{
|
||||
var hsb = [newColor hsbComponents];
|
||||
|
||||
[_hueSaturationView setPositionToColor:newColor];
|
||||
[_brightnessSlider setFloatValue:hsb[2]];
|
||||
[_hueSaturationView setWheelBrightness:hsb[2] / 100.0];
|
||||
|
||||
_brightnessBarImage.style.backgroundColor = "#"+[[CPColor colorWithHue:hsb[0] saturation:hsb[1] brightness:100] hexString];
|
||||
}
|
||||
|
||||
- (CPImage)provideNewButtonImage
|
||||
{
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:CPColorPicker] pathForResource:"wheel_button.png"] size:CGSizeMake(32, 32)];
|
||||
}
|
||||
|
||||
- (CPImage)provideNewAlternateButtonImage
|
||||
{
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:CPColorPicker] pathForResource:"wheel_button_h.png"] size:CGSizeMake(32, 32)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* @ignore */
|
||||
@implementation __CPColorWheel : CPView
|
||||
{
|
||||
DOMElement _wheelImage;
|
||||
DOMElement _blackWheelImage;
|
||||
|
||||
CPView _crosshair;
|
||||
|
||||
id _delegate;
|
||||
|
||||
float _angle;
|
||||
float _distance;
|
||||
|
||||
float _radius;
|
||||
}
|
||||
|
||||
-(id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame: aFrame];
|
||||
|
||||
var path = [[CPBundle bundleForClass: CPColorPicker] pathForResource:@"wheel.png"];
|
||||
|
||||
_wheelImage = new Image();
|
||||
_wheelImage.src = path;
|
||||
_wheelImage.style.position = "absolute";
|
||||
|
||||
path = [[CPBundle bundleForClass: CPColorPicker] pathForResource:@"wheel_black.png"];
|
||||
|
||||
_blackWheelImage = new Image();
|
||||
_blackWheelImage.src = path;
|
||||
_blackWheelImage.style.opacity = "0";
|
||||
_blackWheelImage.style.filter = "alpha(opacity=0)"
|
||||
_blackWheelImage.style.position = "absolute";
|
||||
|
||||
_DOMElement.appendChild(_wheelImage);
|
||||
_DOMElement.appendChild(_blackWheelImage);
|
||||
|
||||
[self setWheelSize: aFrame.size];
|
||||
|
||||
_crosshair = [[CPView alloc] initWithFrame:CPRectMake(_radius - 2, _radius - 2, 4, 4)];
|
||||
[_crosshair setBackgroundColor:[CPColor blackColor]];
|
||||
|
||||
var view = [[CPView alloc] initWithFrame:CGRectInset([_crosshair bounds], 1.0, 1.0)];
|
||||
[view setBackgroundColor:[CPColor whiteColor]];
|
||||
|
||||
[_crosshair addSubview:view];
|
||||
|
||||
[self addSubview: _crosshair];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)setWheelBrightness:(float)brightness
|
||||
{
|
||||
_blackWheelImage.style.opacity = 1.0 - brightness;
|
||||
_blackWheelImage.style.filter = "alpha(opacity=" + (1.0 - brightness)*100 + ")"
|
||||
}
|
||||
|
||||
-(void)setFrameSize:(CPSize)aSize
|
||||
{
|
||||
[super setFrameSize: aSize];
|
||||
[self setWheelSize: aSize];
|
||||
}
|
||||
|
||||
-(void)setWheelSize:(CPSize)aSize
|
||||
{
|
||||
var min = MIN(aSize.width, aSize.height);
|
||||
|
||||
_blackWheelImage.style.width = min;
|
||||
_blackWheelImage.style.height = min;
|
||||
_blackWheelImage.width = min;
|
||||
_blackWheelImage.height = min;
|
||||
_blackWheelImage.style.top = (aSize.height - min) / 2.0 + "px";
|
||||
_blackWheelImage.style.left = (aSize.width - min) / 2.0 + "px";
|
||||
|
||||
_wheelImage.style.width = min;
|
||||
_wheelImage.style.height = min;
|
||||
_wheelImage.width = min;
|
||||
_wheelImage.height = min;
|
||||
_wheelImage.style.top = (aSize.height - min) / 2.0 + "px";
|
||||
_wheelImage.style.left = (aSize.width - min) / 2.0 + "px";
|
||||
|
||||
_radius = min / 2.0;
|
||||
|
||||
[self setAngle: [self degreesToRadians: _angle] distance: (_distance / 100.0) * _radius];
|
||||
}
|
||||
|
||||
-(void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
}
|
||||
|
||||
-(id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
-(float)angle
|
||||
{
|
||||
return _angle;
|
||||
}
|
||||
|
||||
-(float)distance
|
||||
{
|
||||
return _distance;
|
||||
}
|
||||
|
||||
-(void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[self reposition: anEvent];
|
||||
}
|
||||
|
||||
-(void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[self reposition: anEvent];
|
||||
}
|
||||
|
||||
-(void)reposition:(CPEvent)anEvent
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
location = [self convertPoint: [anEvent locationInWindow] fromView: nil];
|
||||
|
||||
var midX = CGRectGetMidX(bounds);
|
||||
var midY = CGRectGetMidY(bounds);
|
||||
|
||||
var distance = MIN(SQRT((location.x - midX)*(location.x - midX) + (location.y - midY)*(location.y - midY)), _radius);
|
||||
var angle = ATAN2(location.y - midY, location.x - midX);
|
||||
|
||||
[self setAngle: angle distance: distance];
|
||||
|
||||
if(_delegate)
|
||||
[_delegate colorWheelDidChange: self];
|
||||
}
|
||||
|
||||
-(void)setAngle:(int)angle distance:(float)distance
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
var midX = CGRectGetMidX(bounds);
|
||||
var midY = CGRectGetMidY(bounds);
|
||||
|
||||
_angle = [self radiansToDegrees: angle];
|
||||
_distance = (distance / _radius) * 100.0;
|
||||
|
||||
[_crosshair setFrameOrigin:CPPointMake(COS(angle) * distance + midX - 2.0, SIN(angle) * distance + midY - 2.0)];
|
||||
}
|
||||
|
||||
-(void)setPositionToColor:(CPColor)aColor
|
||||
{
|
||||
var hsb = [aColor hsbComponents],
|
||||
bounds = [self bounds];
|
||||
|
||||
var angle = [self degreesToRadians: hsb[0]],
|
||||
distance = (hsb[1] / 100.0) * _radius;
|
||||
|
||||
[self setAngle: angle distance: distance];
|
||||
}
|
||||
|
||||
-(int)radiansToDegrees:(float)radians
|
||||
{
|
||||
return ((-radians / PI) * 180 + 360) % 360;
|
||||
}
|
||||
|
||||
-(float)degreesToRadians:(float)degrees
|
||||
{
|
||||
return -(((degrees - 360) / 180) * PI);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* CPColorWell.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPView.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPColorPanel.j"
|
||||
|
||||
|
||||
var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiveNotification";
|
||||
|
||||
/*! @class CPColorWell
|
||||
|
||||
CPColorWell is a CPControl for selecting and displaying a single color value. An example of a CPColorWell object (or simply color well) is found in CPColorPanel, which uses a color well to display the current color selection.</p>
|
||||
|
||||
<p>An application can have one or more active CPColorWells. You can activate multiple CPColorWells by invoking the <code>activate:</code> method with <code>NO</code> as its argument. When a mouse-down event occurs on an CPColorWell's border, it becomes the only active color well. When a color well becomes active, it brings up the color panel also.
|
||||
*/
|
||||
@implementation CPColorWell : CPControl
|
||||
{
|
||||
BOOL _active;
|
||||
BOOL _bordered;
|
||||
|
||||
CPColor _color;
|
||||
CPView _wellView;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_active = NO;
|
||||
_bordered = YES;
|
||||
_color = [CPColor whiteColor];
|
||||
|
||||
[self drawBezelWithHighlight:NO];
|
||||
[self drawWellInside:CGRectInset([self bounds], 3.0, 3.0)];
|
||||
|
||||
[self _registerForNotifications];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)_registerForNotifications
|
||||
{
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(colorWellDidBecomeExclusive:)
|
||||
name:_CPColorWellDidBecomeExclusiveNotification
|
||||
object:nil];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(colorPanelWillClose:)
|
||||
name:CPWindowWillCloseNotification
|
||||
object:[CPColorPanel sharedColorPanel]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the color well is bordered
|
||||
*/
|
||||
- (BOOL)isBordered
|
||||
{
|
||||
return _bordered;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color well's current color.
|
||||
*/
|
||||
- (void)setBordered:(BOOL)bordered
|
||||
{
|
||||
if (_bordered == bordered)
|
||||
return;
|
||||
|
||||
_bordered = bordered;
|
||||
|
||||
[self drawWellInside:CGRectInset([self bounds], 3.0, 3.0)];
|
||||
}
|
||||
|
||||
// Managing Color From Color Wells
|
||||
|
||||
/*!
|
||||
Returns the color well's current color.
|
||||
*/
|
||||
- (CPColor)color
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the color well's current color.
|
||||
*/
|
||||
- (void)setColor:(CPColor)aColor
|
||||
{
|
||||
if (_color == aColor)
|
||||
return;
|
||||
|
||||
_color = aColor;
|
||||
|
||||
[self drawWellInside:CGRectInset([self bounds], 3.0, 3.0)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Changes the color of the well to that of <code>aSender</code>.
|
||||
@param aSender the object from which to retrieve the color
|
||||
*/
|
||||
- (void)takeColorFrom:(id)aSender
|
||||
{
|
||||
[self setColor:[aSender color]];
|
||||
}
|
||||
|
||||
// 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 <code>YES</code>, deactivates any other CPColorWells. <code>NO</code>, keeps them active.
|
||||
@param shouldBeExclusive whether other color wells should be deactivated.
|
||||
*/
|
||||
- (void)activate:(BOOL)shouldBeExclusive
|
||||
{
|
||||
if (shouldBeExclusive)
|
||||
// FIXME: make this queue!
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:_CPColorWellDidBecomeExclusiveNotification
|
||||
object:self];
|
||||
|
||||
|
||||
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 <code>YES</code> if the color well is active.
|
||||
*/
|
||||
- (BOOL)isActive
|
||||
{
|
||||
return _active;
|
||||
}
|
||||
|
||||
// Drawing a Color Well
|
||||
|
||||
- (void)drawBezelWithHighlight:(BOOL)shouldHighlight
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Draws the colored area inside the color well without borders.
|
||||
@param aRect the location at which to draw
|
||||
*/
|
||||
- (void)drawWellInside:(CGRect)aRect
|
||||
{
|
||||
if (!_wellView)
|
||||
{
|
||||
_wellView = [[CPView alloc] initWithFrame:aRect];
|
||||
[_wellView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[self addSubview:_wellView];
|
||||
}
|
||||
else
|
||||
[_wellView setFrame:aRect];
|
||||
|
||||
[_wellView setBackgroundColor:_color];
|
||||
}
|
||||
|
||||
- (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)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[self drawBezelWithHighlight:YES];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[self drawBezelWithHighlight:CGRectContainsPoint([self bounds], [self convertPoint:[anEvent locationInWindow] fromView:nil])];
|
||||
}
|
||||
|
||||
-(void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[self drawBezelWithHighlight:NO];
|
||||
|
||||
if (!CGRectContainsPoint([self bounds], [self convertPoint:[anEvent locationInWindow] fromView:nil]))
|
||||
return;
|
||||
|
||||
[self activate:YES];
|
||||
|
||||
var colorPanel = [CPColorPanel sharedColorPanel];
|
||||
|
||||
[colorPanel setColor:_color];
|
||||
|
||||
[colorPanel orderFront:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPColorWellColorKey = "CPColorWellColorKey",
|
||||
CPColorWellBorderedKey = "CPColorWellBorderedKey";
|
||||
|
||||
@implementation CPColorWell (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the color well by unarchiving data from <code>aCoder</code>.
|
||||
@param aCoder the coder containing the archived CPColorWell.
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_active = NO;
|
||||
_bordered = [aCoder decodeObjectForKey:CPColorWellBorderedKey];
|
||||
_color = [aCoder decodeObjectForKey:CPColorWellColorKey];
|
||||
|
||||
[self drawBezelWithHighlight:NO];
|
||||
[self drawWellInside:CGRectInset([self bounds], 3.0, 3.0)];
|
||||
|
||||
[self _registerForNotifications];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives this button into the provided coder.
|
||||
@param aCoder the coder to which the color well's instance data will be written.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// We do this in order to avoid encoding the _wellView, which
|
||||
// should just automatically be created programmatically as needed.
|
||||
var actualSubviews = _subviews;
|
||||
|
||||
_subviews = [_subviews copy];
|
||||
[_subviews removeObjectIdenticalTo:_wellView];
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
_subviews = actualSubviews;
|
||||
|
||||
[aCoder encodeObject:_color forKey:CPColorWellColorKey];
|
||||
[aCoder encodeObject:_bordered forKey:CPColorWellBorderedKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* CPCompatibility.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPEvent.j"
|
||||
|
||||
// Browser Engines
|
||||
CPUnknownBrowserEngine = 0;
|
||||
CPGeckoBrowserEngine = 1;
|
||||
CPInternetExplorerBrowserEngine = 2;
|
||||
CPKHTMLBrowserEngine = 3;
|
||||
CPOperaBrowserEngine = 4;
|
||||
CPWebKitBrowserEngine = 5;
|
||||
|
||||
// Features
|
||||
CPCSSRGBAFeature = 1 << 5;
|
||||
|
||||
CPHTMLCanvasFeature = 1 << 6;
|
||||
CPHTMLContentEditableFeature = 1 << 7;
|
||||
|
||||
CPJavascriptInnerTextFeature = 1 << 8;
|
||||
CPJavascriptTextContentFeature = 1 << 9;
|
||||
CPJavascriptClipboardEventsFeature = 1 << 10;
|
||||
CPJavascriptClipboardAccessFeature = 1 << 11;
|
||||
CPJavaScriptCanvasDrawFeature = 1 << 12;
|
||||
CPJavaScriptCanvasTransformFeature = 1 << 13;
|
||||
|
||||
CPVMLFeature = 1 << 14;
|
||||
|
||||
CPJavascriptRemedialKeySupport = 1 << 15;
|
||||
CPJavaScriptShadowFeature = 1 << 20;
|
||||
|
||||
CPJavaScriptNegativeMouseWheelValues = 1 << 22;
|
||||
|
||||
CPOpacityRequiresFilterFeature = 1 << 23;
|
||||
|
||||
var USER_AGENT = "";
|
||||
|
||||
PLATFORM_ENGINE = CPUnknownBrowserEngine,
|
||||
PLATFORM_FEATURES = 0;
|
||||
|
||||
if (typeof window != "undfined" && typeof window.navigator != "undefined")
|
||||
USER_AGENT = window.navigator.userAgent;
|
||||
|
||||
// Opera
|
||||
if (window.opera)
|
||||
{
|
||||
PLATFORM_ENGINE = CPOperaBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasDrawFeature;
|
||||
}
|
||||
|
||||
// Internet Explorer
|
||||
else if (window.attachEvent) // Must follow Opera check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPInternetExplorerBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with IE (no known independent tests)
|
||||
PLATFORM_FEATURES |= CPVMLFeature;
|
||||
PLATFORM_FEATURES |= CPJavascriptRemedialKeySupport;
|
||||
PLATFORM_FEATURES |= CPJavaScriptShadowFeature;
|
||||
|
||||
PLATFORM_FEATURES |= CPOpacityRequiresFilterFeature;
|
||||
}
|
||||
|
||||
// WebKit
|
||||
else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
|
||||
{
|
||||
PLATFORM_ENGINE = CPWebKitBrowserEngine;
|
||||
|
||||
// Features we can only be sure of with WebKit (no known independent tests)
|
||||
PLATFORM_FEATURES |= CPCSSRGBAFeature;
|
||||
PLATFORM_FEATURES |= CPHTMLContentEditableFeature;
|
||||
PLATFORM_FEATURES |= CPJavascriptClipboardEventsFeature;
|
||||
PLATFORM_FEATURES |= CPJavascriptClipboardAccessFeature;
|
||||
PLATFORM_FEATURES |= CPJavaScriptShadowFeature;
|
||||
|
||||
var versionStart = USER_AGENT.indexOf("AppleWebKit/") + "AppleWebKit/".length,
|
||||
versionEnd = USER_AGENT.indexOf(" ", versionStart),
|
||||
version = parseFloat(USER_AGENT.substring(versionStart, versionEnd), 10);
|
||||
|
||||
if(USER_AGENT.indexOf("Plainview") == -1 && version >= 525.14 || USER_AGENT.indexOf("Chrome") != -1)
|
||||
PLATFORM_FEATURES |= CPJavascriptRemedialKeySupport;
|
||||
}
|
||||
|
||||
// KHTML
|
||||
else if (USER_AGENT.indexOf("KHTML") != -1) // Must follow WebKit check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPKHTMLBrowserEngine;
|
||||
}
|
||||
|
||||
// Gecko
|
||||
else if (USER_AGENT.indexOf("Gecko") != -1) // Must follow KHTML check.
|
||||
{
|
||||
PLATFORM_ENGINE = CPGeckoBrowserEngine;
|
||||
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasDrawFeature;
|
||||
|
||||
var index = USER_AGENT.indexOf("Firefox"),
|
||||
version = (index == -1) ? 2.0 : parseFloat(USER_AGENT.substring(index+"Firefox".length+1));
|
||||
|
||||
if (version >= 3.0)
|
||||
PLATFORM_FEATURES |= CPCSSRGBAFeature;
|
||||
}
|
||||
|
||||
// Feature Specific Checks
|
||||
if (typeof document != "undefined")
|
||||
{
|
||||
var canvasElement = document.createElement("canvas");
|
||||
// Detect Canvas Support
|
||||
if (canvasElement && canvasElement.getContext)
|
||||
{
|
||||
PLATFORM_FEATURES |= CPHTMLCanvasFeature;
|
||||
|
||||
// Detect Canvas setTransform/transform support
|
||||
var context = document.createElement("canvas").getContext("2d");
|
||||
|
||||
if (context && context.setTransform && context.transform)
|
||||
PLATFORM_FEATURES |= CPJavaScriptCanvasTransformFeature;
|
||||
}
|
||||
|
||||
var DOMElement = document.createElement("div");
|
||||
|
||||
// Detect whether we have innerText or textContent (or neither)
|
||||
if (DOMElement.innerText != undefined)
|
||||
PLATFORM_FEATURES |= CPJavascriptInnerTextFeature;
|
||||
else if (DOMElement.textContent != undefined)
|
||||
PLATFORM_FEATURES |= CPJavascriptTextContentFeature;
|
||||
}
|
||||
|
||||
function CPFeatureIsCompatible(aFeature)
|
||||
{
|
||||
return PLATFORM_FEATURES & aFeature;
|
||||
}
|
||||
|
||||
function CPBrowserIsEngine(anEngine)
|
||||
{
|
||||
return PLATFORM_ENGINE == anEngine;
|
||||
}
|
||||
|
||||
if (USER_AGENT.indexOf("Mac") != -1)
|
||||
{
|
||||
CPPlatformActionKeyMask = CPCommandKeyMask;
|
||||
|
||||
CPUndoKeyEquivalent = @"Z";
|
||||
CPRedoKeyEquivalent = @"Z";
|
||||
|
||||
CPUndoKeyEquivalentModifierMask = CPCommandKeyMask;
|
||||
CPRedoKeyEquivalentModifierMask = CPCommandKeyMask | CPShiftKeyMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
CPPlatformActionKeyMask = CPControlKeyMask;
|
||||
|
||||
CPUndoKeyEquivalent = @"Z";
|
||||
CPRedoKeyEquivalent = @"Y";
|
||||
|
||||
CPUndoKeyEquivalentModifierMask = CPControlKeyMask;
|
||||
CPRedoKeyEquivalentModifierMask = CPControlKeyMask;
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
/*
|
||||
* CPControl.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPFont.j"
|
||||
@import "CPShadow.j"
|
||||
@import "CPView.j"
|
||||
|
||||
#include "Platform/Platform.h"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPTextAlignment
|
||||
*/
|
||||
CPLeftTextAlignment = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPTextAlignment
|
||||
*/
|
||||
CPRightTextAlignment = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPTextAlignment
|
||||
*/
|
||||
CPCenterTextAlignment = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPTextAlignment
|
||||
*/
|
||||
CPJustifiedTextAlignment = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPTextAlignment
|
||||
*/
|
||||
CPNaturalTextAlignment = 4;
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPControlSize
|
||||
*/
|
||||
CPRegularControlSize = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPControlSize
|
||||
*/
|
||||
CPSmallControlSize = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPControlSize
|
||||
*/
|
||||
CPMiniControlSize = 2;
|
||||
|
||||
CPControlNormalBackgroundColor = "CPControlNormalBackgroundColor";
|
||||
CPControlSelectedBackgroundColor = "CPControlSelectedBackgroundColor";
|
||||
CPControlHighlightedBackgroundColor = "CPControlHighlightedBackgroundColor";
|
||||
CPControlDisabledBackgroundColor = "CPControlDisabledBackgroundColor";
|
||||
|
||||
CPControlTextDidBeginEditingNotification = "CPControlTextDidBeginEditingNotification";
|
||||
CPControlTextDidChangeNotification = "CPControlTextDidChangeNotification";
|
||||
CPControlTextDidEndEditingNotification = "CPControlTextDidEndEditingNotification";
|
||||
|
||||
var CPControlBlackColor = [CPColor blackColor];
|
||||
|
||||
/*! @class CPControl
|
||||
|
||||
CPControl is an abstract superclass used to implement user interface elements. As a subclass of CPView and CPResponder it has the ability to handle screen drawing and handling user input.
|
||||
*/
|
||||
@implementation CPControl : CPView
|
||||
{
|
||||
id _value;
|
||||
|
||||
BOOL _isEnabled;
|
||||
|
||||
int _alignment;
|
||||
CPFont _font;
|
||||
CPColor _textColor;
|
||||
CPShadow _textShadow;
|
||||
|
||||
id _target;
|
||||
SEL _action;
|
||||
int _sendActionOn;
|
||||
|
||||
CPDictionary _backgroundColors;
|
||||
CPString _currentBackgroundColorName;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_sendActionOn = CPLeftMouseUpMask;
|
||||
_isEnabled = YES;
|
||||
|
||||
[self setFont:[CPFont systemFontOfSize:12.0]];
|
||||
[self setTextColor:CPControlBlackColor];
|
||||
|
||||
_backgroundColors = [CPDictionary dictionary];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the receiver responds to mouse events.
|
||||
@param isEnabled whether the receiver will respond to mouse events
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)isEnabled
|
||||
{
|
||||
[self setAlphaValue:(_isEnabled = isEnabled) ? 1.0 : 0.3];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the receiver responds to mouse events.
|
||||
*/
|
||||
- (BOOL)isEnabled
|
||||
{
|
||||
return _isEnabled;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Sets the color of the receiver's text.
|
||||
*/
|
||||
- (void)setTextColor:(CPColor)aColor
|
||||
{
|
||||
if (_textColor == aColor)
|
||||
return;
|
||||
|
||||
_textColor = aColor;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMElement.style.color = [aColor cssString];
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the color of the receiver's text
|
||||
*/
|
||||
- (CPColor)textColor
|
||||
{
|
||||
return _textColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's alignment
|
||||
*/
|
||||
- (CPTextAlignment)alignment
|
||||
{
|
||||
return _alignment;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's alignment
|
||||
@param anAlignment the receiver's alignment
|
||||
*/
|
||||
- (void)setAlignment:(CPTextAlignment)anAlignment
|
||||
{
|
||||
_alignment = anAlignment;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's font
|
||||
@param aFont the font for the receiver
|
||||
*/
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
if (_font == aFont)
|
||||
return;
|
||||
|
||||
_font = aFont;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMElement.style.font = [_font ? _font : [CPFont systemFontOfSize:12.0] cssString];
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's font
|
||||
*/
|
||||
- (CPFont)font
|
||||
{
|
||||
return _font;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the shadow for the receiver's text.
|
||||
@param aTextShadow the text shadow
|
||||
*/
|
||||
- (void)setTextShadow:(CPShadow)aTextShadow
|
||||
{
|
||||
_DOMElement.style.textShadow = [_textShadow = aTextShadow cssString];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's text shadow
|
||||
*/
|
||||
- (CPShadow)textShadow
|
||||
{
|
||||
return _textShadow;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's target action
|
||||
*/
|
||||
- (SEL)action
|
||||
{
|
||||
return _action;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's target action
|
||||
@param anAction Sets the action message that gets sent to the target.
|
||||
*/
|
||||
- (void)setAction:(SEL)anAction
|
||||
{
|
||||
_action = anAction;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's target. The target receives action messages from the receiver.
|
||||
*/
|
||||
- (id)target
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's target. The target receives action messages from the receiver.
|
||||
@param aTarget the object that will receive the message specified by action
|
||||
*/
|
||||
- (void)setTarget:(id)aTarget
|
||||
{
|
||||
_target = aTarget;
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
if (_sendActionOn & CPLeftMouseUpMask && CPRectContainsPoint([self bounds], [self convertPoint:[anEvent locationInWindow] fromView:nil]))
|
||||
[self sendAction:_action to:_target];
|
||||
|
||||
[super mouseUp:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Causes <code>anAction</code> to be sent to <code>anObject</code>.
|
||||
@param anAction the action to send
|
||||
@param anObject the object to which the action will be sent
|
||||
*/
|
||||
- (void)sendAction:(SEL)anAction to:(id)anObject
|
||||
{
|
||||
[CPApp sendAction:anAction to:anObject from:self];
|
||||
}
|
||||
|
||||
- (int)sendActionOn:(int)mask
|
||||
{
|
||||
var previousMask = _sendActionOn;
|
||||
|
||||
_sendActionOn = mask;
|
||||
|
||||
return previousMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the control can continuously send its action messages.
|
||||
*/
|
||||
- (BOOL)isContinuous
|
||||
{
|
||||
// Some subclasses should redefine this with CPLeftMouseDraggedMask
|
||||
return (_sendActionOn & CPPeriodicMask) != 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the cell can continuously send its action messages.
|
||||
*/
|
||||
- (void)setContinuous:(BOOL)flag
|
||||
{
|
||||
// Some subclasses should redefine this with CPLeftMouseDraggedMask
|
||||
if (flag)
|
||||
_sendActionOn |= CPPeriodicMask;
|
||||
else
|
||||
_sendActionOn &= ~CPPeriodicMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's object value
|
||||
*/
|
||||
- (id)objectValue
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set's the receiver's object value
|
||||
*/
|
||||
- (void)setObjectValue:(id)anObject
|
||||
{
|
||||
_value = anObject;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's float value
|
||||
*/
|
||||
- (float)floatValue
|
||||
{
|
||||
var floatValue = parseFloat(_value, 10);
|
||||
return isNaN(floatValue) ? 0.0 : floatValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's float value
|
||||
*/
|
||||
- (void)setFloatValue:(float)aValue
|
||||
{
|
||||
[self setObjectValue:aValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's double value
|
||||
*/
|
||||
- (double)doubleValue
|
||||
{
|
||||
var doubleValue = parseFloat(_value, 10);
|
||||
return isNaN(doubleValue) ? 0.0 : doubleValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set's the receiver's double value
|
||||
*/
|
||||
- (void)setDoubleValue:(double)anObject
|
||||
{
|
||||
[self setObjectValue:anObject];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's int value
|
||||
*/
|
||||
- (int)intValue
|
||||
{
|
||||
var intValue = parseInt(_value, 10);
|
||||
return isNaN(intValue) ? 0.0 : intValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set's the receiver's int value
|
||||
*/
|
||||
- (void)setIntValue:(int)anObject
|
||||
{
|
||||
[self setObjectValue:anObject];
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Returns the receiver's int value
|
||||
*/
|
||||
- (int)integerValue
|
||||
{
|
||||
var intValue = parseInt(_value, 10);
|
||||
return isNaN(intValue) ? 0.0 : intValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Set's the receiver's int value
|
||||
*/
|
||||
- (void)setIntegerValue:(int)anObject
|
||||
{
|
||||
[self setObjectValue:anObject];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's int value
|
||||
*/
|
||||
- (CPString)stringValue
|
||||
{
|
||||
return _value ? String(_value) : "";
|
||||
}
|
||||
|
||||
/*!
|
||||
Set's the receiver's int value
|
||||
*/
|
||||
- (void)setStringValue:(CPString)anObject
|
||||
{
|
||||
[self setObjectValue:anObject];
|
||||
}
|
||||
|
||||
|
||||
- (void)takeDoubleValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(doubleValue)])
|
||||
[self setDoubleValue:[sender doubleValue]];
|
||||
}
|
||||
|
||||
|
||||
- (void)takeFloatValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(floatValue)])
|
||||
[self setFloatValue:[sender floatValue]];
|
||||
}
|
||||
|
||||
|
||||
- (void)takeIntegerValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(integerValue)])
|
||||
[self setIntegerValue:[sender integerValue]];
|
||||
}
|
||||
|
||||
|
||||
- (void)takeIntValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(intValue)])
|
||||
[self setIntValue:[sender intValue]];
|
||||
}
|
||||
|
||||
|
||||
- (void)takeObjectValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(objectValue)])
|
||||
[self setObjectValue:[sender objectValue]];
|
||||
}
|
||||
|
||||
- (void)takeStringValueFrom:(id)sender
|
||||
{
|
||||
if ([sender respondsToSelector:@selector(stringValue)])
|
||||
[self setStringValue:[sender stringValue]];
|
||||
}
|
||||
|
||||
|
||||
- (void)setBackgroundColor:(CPColor)aColor
|
||||
{
|
||||
_backgroundColors = [CPDictionary dictionary];
|
||||
|
||||
[self setBackgroundColor:aColor forName:CPControlNormalBackgroundColor];
|
||||
|
||||
[super setBackgroundColor:aColor];
|
||||
}
|
||||
|
||||
- (void)setBackgroundColor:(CPColor)aColor forName:(CPString)aName
|
||||
{
|
||||
if (!aColor)
|
||||
[_backgroundColors removeObjectForKey:aName];
|
||||
else
|
||||
[_backgroundColors setObject:aColor forKey:aName];
|
||||
|
||||
if (_currentBackgroundColorName == aName)
|
||||
[self setBackgroundColorWithName:_currentBackgroundColorName];
|
||||
}
|
||||
|
||||
- (CPColor)backgroundColorForName:(CPString)aName
|
||||
{
|
||||
var backgroundColor = [_backgroundColors objectForKey:aName];
|
||||
|
||||
if (!backgroundColor && aName != CPControlNormalBackgroundColor)
|
||||
return [_backgroundColors objectForKey:CPControlNormalBackgroundColor];
|
||||
|
||||
return backgroundColor;
|
||||
}
|
||||
|
||||
- (void)setBackgroundColorWithName:(CPString)aName
|
||||
{
|
||||
_currentBackgroundColorName = aName;
|
||||
|
||||
[super setBackgroundColor:[self backgroundColorForName:aName]];
|
||||
}
|
||||
|
||||
- (void)textDidBeginEditing:(CPNotification)note
|
||||
{
|
||||
//this looks to prevent false propagation of notifications for other objects
|
||||
if([note object] != self)
|
||||
return;
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidBeginEditingNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
|
||||
}
|
||||
|
||||
- (void)textDidChange:(CPNotification)note
|
||||
{
|
||||
//this looks to prevent false propagation of notifications for other objects
|
||||
if([note object] != self)
|
||||
return;
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidChangeNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
|
||||
}
|
||||
|
||||
- (void)textDidEndEditing:(CPNotification)note
|
||||
{
|
||||
//this looks to prevent false propagation of notifications for other objects
|
||||
if([note object] != self)
|
||||
return;
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPControlTextDidEndEditingNotification object:self userInfo:[CPDictionary dictionaryWithObject:[note object] forKey:"CPFieldEditor"]];
|
||||
}
|
||||
|
||||
/*
|
||||
Ð doubleValue
|
||||
Ð setDoubleValue:
|
||||
Ð intValue
|
||||
Ð setIntValue:
|
||||
Ð objectValue
|
||||
Ð setObjectValue:
|
||||
Ð stringValue
|
||||
Ð setStringValue:
|
||||
Ð setNeedsDisplay
|
||||
Ð attributedStringValue
|
||||
Ð setAttributedStringValue:
|
||||
*/
|
||||
|
||||
@end
|
||||
|
||||
var CPControlValueKey = "CPControlValueKey",
|
||||
CPControlIsEnabledKey = "CPControlIsEnabledKey",
|
||||
CPControlAlignmentKey = "CPControlAlignmentKey",
|
||||
CPControlFontKey = "CPControlFontKey",
|
||||
CPControlTextColorKey = "CPControlTextColorKey",
|
||||
CPControlTargetKey = "CPControlTargetKey",
|
||||
CPControlActionKey = "CPControlActionKey",
|
||||
CPControlSendActionOnKey = "CPControlSendActionOnKey";
|
||||
|
||||
@implementation CPControl (CPCoding)
|
||||
|
||||
/*
|
||||
Initializes the control by unarchiving it from a coder.
|
||||
@param aCoder the coder from which to unarchive the control
|
||||
@return the initialized control
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setObjectValue:[aCoder decodeObjectForKey:CPControlValueKey]];
|
||||
|
||||
[self setEnabled:[aCoder decodeBoolForKey:CPControlIsEnabledKey]];
|
||||
|
||||
[self setAlignment:[aCoder decodeIntForKey:CPControlAlignmentKey]];
|
||||
[self setFont:[aCoder decodeObjectForKey:CPControlFontKey]];
|
||||
[self setTextColor:[aCoder decodeObjectForKey:CPControlTextColorKey]];
|
||||
|
||||
[self setTarget:[aCoder decodeObjectForKey:CPControlTargetKey]];
|
||||
[self setAction:[aCoder decodeObjectForKey:CPControlActionKey]];
|
||||
[self sendActionOn:[aCoder decodeIntForKey:CPControlSendActionOnKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
Archives the control to the provided coder.
|
||||
@param aCoder the coder to which the control will be archived.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_value forKey:CPControlValueKey];
|
||||
|
||||
[aCoder encodeBool:_isEnabled forKey:CPControlIsEnabledKey];
|
||||
|
||||
[aCoder encodeInt:_alignment forKey:CPControlAlignmentKey];
|
||||
[aCoder encodeObject:_font forKey:CPControlFontKey];
|
||||
[aCoder encodeObject:_textColor forKey:CPControlTextColorKey];
|
||||
|
||||
[aCoder encodeConditionalObject:_target forKey:CPControlTargetKey];
|
||||
[aCoder encodeObject:_action forKey:CPControlActionKey];
|
||||
|
||||
[aCoder encodeInt:_sendActionOn forKey:CPControlSendActionOnKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPControlSizeIdentifiers = [],
|
||||
_CPControlCachedThreePartImages = {},
|
||||
_CPControlCachedColorWithPatternImages = {},
|
||||
_CPControlCachedThreePartImagePattern = {};
|
||||
|
||||
_CPControlSizeIdentifiers[CPRegularControlSize] = "Regular";
|
||||
_CPControlSizeIdentifiers[CPSmallControlSize] = "Small";
|
||||
_CPControlSizeIdentifiers[CPMiniControlSize] = "Mini";
|
||||
|
||||
function _CPControlIdentifierForControlSize(aControlSize)
|
||||
{
|
||||
return _CPControlSizeIdentifiers[aControlSize];
|
||||
}
|
||||
|
||||
function _CPControlColorWithPatternImage(sizes, aClassName)
|
||||
{
|
||||
var index = 1,
|
||||
count = arguments.length,
|
||||
identifier = "";
|
||||
|
||||
for (; index < count; ++index)
|
||||
identifier += arguments[index];
|
||||
|
||||
var color = _CPControlCachedColorWithPatternImages[identifier];
|
||||
|
||||
if (!color)
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[CPControl class]];
|
||||
|
||||
color = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:aClassName + "/" + identifier + ".png"] size:sizes[identifier]]];
|
||||
|
||||
_CPControlCachedColorWithPatternImages[identifier] = color;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
function _CPControlThreePartImages(sizes, aClassName)
|
||||
{
|
||||
var index = 1,
|
||||
count = arguments.length,
|
||||
identifier = "";
|
||||
|
||||
for (; index < count; ++index)
|
||||
identifier += arguments[index];
|
||||
|
||||
var images = _CPControlCachedThreePartImages[identifier];
|
||||
|
||||
if (!images)
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[CPControl class]],
|
||||
path = aClassName + "/" + identifier;
|
||||
|
||||
sizes = sizes[identifier];
|
||||
|
||||
images = [
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "0.png"] size:sizes[0]],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "1.png"] size:sizes[1]],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "2.png"] size:sizes[2]]
|
||||
];
|
||||
|
||||
_CPControlCachedThreePartImages[identifier] = images;
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
function _CPControlThreePartImagePattern(isVertical, sizes, aClassName)
|
||||
{
|
||||
var index = 2,
|
||||
count = arguments.length,
|
||||
identifier = "";
|
||||
|
||||
for (; index < count; ++index)
|
||||
identifier += arguments[index];
|
||||
|
||||
var color = _CPControlCachedThreePartImagePattern[identifier];
|
||||
|
||||
if (!color)
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[CPControl class]],
|
||||
path = aClassName + "/" + identifier;
|
||||
|
||||
sizes = sizes[identifier];
|
||||
|
||||
color = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "0.png"] size:sizes[0]],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "1.png"] size:sizes[1]],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:path + "2.png"] size:sizes[2]]
|
||||
] isVertical:isVertical]];
|
||||
|
||||
_CPControlCachedThreePartImagePattern[identifier] = color;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* CPCookie.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
|
||||
/*! @class CPCookie
|
||||
CPCookie is the Cappuccino interface to a web browser cookie. You can set the name
|
||||
*/
|
||||
@implementation CPCookie : CPObject
|
||||
{
|
||||
CPString _cookieName;
|
||||
CPString _cookieValue;
|
||||
|
||||
CPString _expires;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a cookie with a given name <code>aName</code>.
|
||||
@param the name for the cookie
|
||||
*/
|
||||
- (id)initWithName:(CPString)aName
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_cookieName = aName;
|
||||
_cookieValue = [self _readCookieValue];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the cookie's data value
|
||||
*/
|
||||
- (CPString)value
|
||||
{
|
||||
return _cookieValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the cookie's name
|
||||
*/
|
||||
- (CPString)name
|
||||
{
|
||||
return _cookieName;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the cookie's expiration date
|
||||
*/
|
||||
- (CPString)expires
|
||||
{
|
||||
return _expires;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a value, expiration date, and domain for the cookie
|
||||
@param value the cookie's value
|
||||
@param date the cookie's expiration date
|
||||
@param domain the cookie's domain
|
||||
*/
|
||||
- (void)setValue:(CPString)value expires:(CPDate)date domain:(CPString)domain
|
||||
{
|
||||
if(date)
|
||||
var expires = "; expires="+date.toGMTString();
|
||||
else
|
||||
var expires = "";
|
||||
|
||||
if(domain)
|
||||
domain = "; domain="+domain;
|
||||
else
|
||||
domain = "";
|
||||
|
||||
document.cookie = _cookieName+"="+value+expires+"; path=/"+domain;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (CPString)_readCookieValue
|
||||
{
|
||||
var nameEQ = _cookieName + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0;i < ca.length;i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
//http://www.quirksmode.org/js/cookies.html
|
||||
@@ -0,0 +1,724 @@
|
||||
/*
|
||||
* CPDocument.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPString.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@import "CPResponder.j"
|
||||
@import "CPWindowController.j"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPSaveOperationType
|
||||
*/
|
||||
CPSaveOperation = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPSaveOperationType
|
||||
*/
|
||||
CPSaveAsOperation = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPSaveOperationType
|
||||
*/
|
||||
CPSaveToOperation = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPSaveOperationType
|
||||
*/
|
||||
CPAutosaveOperation = 3;
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeDone = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeUndone = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeCleared = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeReadOtherContents = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPDocumentChangeType
|
||||
*/
|
||||
CPChangeAutosaved = 4;
|
||||
|
||||
CPDocumentWillSaveNotification = @"CPDocumentWillSaveNotification";
|
||||
CPDocumentDidSaveNotification = @"CPDocumentDidSaveNotification";
|
||||
CPDocumentDidFailToSaveNotification = @"CPDocumentDidFailToSaveNotification";
|
||||
|
||||
var CPDocumentUntitledCount = 0;
|
||||
|
||||
/*! @class CPDocument
|
||||
|
||||
CPDocument is used to represent a document/file in a Cappuccino application.
|
||||
In a document-based application, generally multiple documents are open simutaneously
|
||||
(multiple text documents, slide presentations, spreadsheets, etc.), and multiple
|
||||
CPDocuments should be used to represent this.
|
||||
*/
|
||||
@implementation CPDocument : CPResponder
|
||||
{
|
||||
CPURL _fileURL;
|
||||
CPString _fileType;
|
||||
CPArray _windowControllers;
|
||||
unsigned _untitledDocumentIndex;
|
||||
|
||||
BOOL _hasUndoManager;
|
||||
CPUndoManager _undoManager;
|
||||
|
||||
int _changeCount;
|
||||
|
||||
CPURLConnection _readConnection;
|
||||
CPURLRequest _writeRequest;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes an empty document.
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_windowControllers = [];
|
||||
|
||||
_hasUndoManager = YES;
|
||||
_changeCount = 0;
|
||||
|
||||
[self setNextResponder:CPApp];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the document with a specific data type.
|
||||
@param aType the type of document to initialize
|
||||
@param anError not used
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)initWithType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
[self setFileType:aType];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes a document of a specific type located at a URL. Notifies
|
||||
the provided delegate after initialization.
|
||||
@param anAbsoluteURL the url of the document content
|
||||
@param aType the type of document located at the URL
|
||||
@param aDelegate the delegate to notify
|
||||
@param aDidReadSelector the selector used to notify the delegate
|
||||
@param aContextInfo context information passed to the delegate
|
||||
after initialization
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)initWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self readFromURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
|
||||
|
||||
[self setFileURL:anAbsoluteURL];
|
||||
[self setFileType:aType];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the document from a URL.
|
||||
@param anAbsoluteURL the document location
|
||||
@param absoluteContentsURL the location of the document's contents
|
||||
@param aType the type of the contents
|
||||
@param aDelegate this object will receive a callback after the document's contents are loaded
|
||||
@param aDidReadSelector the message selector that will be sent to <code>aDelegate</code>
|
||||
@param aContextInfo passed as the argument to the message sent to the <code>aDelegate</code>
|
||||
@return the initialized document
|
||||
*/
|
||||
- (id)initForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self readFromURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aDidReadSelector contextInfo:aContextInfo];
|
||||
|
||||
[self setFileURL:anAbsoluteURL];
|
||||
[self setFileType:aType];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's data in a specified type. The default implementation just
|
||||
throws an exception.
|
||||
@param aType the format of the data
|
||||
@param anError not used
|
||||
@throws CPUnsupportedMethodException if this method hasn't been overriden by the subclass
|
||||
@return the document data
|
||||
*/
|
||||
- (CPData)dataOfType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:"dataOfType:error: must be overridden by the document subclass."];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the content of the document by reading the provided
|
||||
data. The default implementation just throws an exception.
|
||||
@param aData the document's data
|
||||
@param aType the document type
|
||||
@param anError not used
|
||||
@throws CPUnsupportedMethodException if this method hasn't been
|
||||
overridden by the subclass
|
||||
*/
|
||||
- (void)readFromData:(CPData)aData ofType:(CPString)aType error:(CPError)anError
|
||||
{
|
||||
[CPException raise:CPUnsupportedMethodException
|
||||
reason:"readFromData:ofType: must be overridden by the document subclass."];
|
||||
}
|
||||
|
||||
// Creating and managing window controllers
|
||||
/*!
|
||||
Creates the window controller for this document.
|
||||
*/
|
||||
- (void)makeWindowControllers
|
||||
{
|
||||
var controller = [[CPWindowController alloc] initWithWindowCibName:nil];
|
||||
|
||||
[self addWindowController:controller];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document's window controllers
|
||||
*/
|
||||
- (CPArray)windowControllers
|
||||
{
|
||||
return _windowControllers;
|
||||
}
|
||||
|
||||
/*!
|
||||
Add a controller to the document's list of controllers. This should
|
||||
be called after making a new window controller.
|
||||
@param aWindowController the controller to add
|
||||
*/
|
||||
- (void)addWindowController:(CPWindowController)aWindowController
|
||||
{
|
||||
[_windowControllers addObject:aWindowController];
|
||||
|
||||
if ([aWindowController document] != self)
|
||||
{
|
||||
[aWindowController setNextResponder:self];
|
||||
[aWindowController setDocument:self];
|
||||
}
|
||||
}
|
||||
|
||||
// Managing Document Windows
|
||||
/*!
|
||||
Shows all the document's windows.
|
||||
*/
|
||||
- (void)showWindows
|
||||
{
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(showWindow:) withObject:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the name of the document as displayed in the title bar.
|
||||
*/
|
||||
- (CPString)displayName
|
||||
{
|
||||
if (_fileURL)
|
||||
return [_fileURL lastPathComponent];
|
||||
|
||||
if (!_untitledDocumentIndex)
|
||||
_untitledDocumentIndex = ++CPDocumentUntitledCount;
|
||||
|
||||
if (_untitledDocumentIndex == 1)
|
||||
return @"Untitled";
|
||||
|
||||
return @"Untitled " + _untitledDocumentIndex;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document's Cib name
|
||||
*/
|
||||
- (CPString)windowCibName
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Called after <code>aWindowController<code> loads the document's Nib file.
|
||||
@param aWindowController the controller that loaded the Nib file
|
||||
*/
|
||||
- (void)windowControllerDidLoadNib:(CPWindowController)aWindowController
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Called before <code>aWindowController</code> will load the document's Nib file.
|
||||
@param aWindowController the controller that will load the Nib file
|
||||
*/
|
||||
- (void)windowControllerWillLoadNib:(CPWindowController)aWindowController
|
||||
{
|
||||
}
|
||||
|
||||
// Reading from and Writing to URLs
|
||||
/*!
|
||||
Set the document's data from a URL. Notifies the provided delegate afterwards.
|
||||
@param anAbsoluteURL the URL to the document's content
|
||||
@param aType the document type
|
||||
@param aDelegate delegate to notify after reading the data
|
||||
@param aDidReadSelector message that will be sent to the delegate
|
||||
@param aContextInfo context information that gets sent to the delegate
|
||||
*/
|
||||
- (void)readFromURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aDidReadSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
[_readConnection cancel];
|
||||
|
||||
// FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
|
||||
_readConnection = [CPURLConnection connectionWithRequest:[CPURLRequest requestWithURL:anAbsoluteURL] delegate:self];
|
||||
|
||||
_readConnection.session = _CPReadSessionMake(aType, aDelegate, aDidReadSelector, aContextInfo);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the path to the document's file.
|
||||
*/
|
||||
- (CPURL)fileURL
|
||||
{
|
||||
return _fileURL;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the path to the document's file.
|
||||
@param aFileURL the path to the document's file
|
||||
*/
|
||||
- (void)setFileURL:(CPURL)aFileURL
|
||||
{
|
||||
if (_fileURL == aFileURL)
|
||||
return;
|
||||
|
||||
_fileURL = aFileURL;
|
||||
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(synchronizeWindowTitleWithDocumentName)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Saves the document to the specified URL. Notifies the provided delegate
|
||||
with the provided selector and context info afterwards.
|
||||
@param anAbsoluteURL the url to write the document data to
|
||||
@param aTypeName the document type
|
||||
@param aSaveOperation the type of save operation
|
||||
@param aDelegate the delegate to notify after saving
|
||||
@param aDidSaveSelector the selector to send the delegate
|
||||
@param aContextInfo context info that gets passed to the delegate
|
||||
*/
|
||||
- (void)saveToURL:(CPURL)anAbsoluteURL ofType:(CPString)aTypeName forSaveOperation:(CPSaveOperationType)aSaveOperation delegate:(id)aDelegate didSaveSelector:(SEL)aDidSaveSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
var data = [self dataOfType:[self fileType] error:nil],
|
||||
oldChangeCount = _changeCount;
|
||||
|
||||
_writeRequest = [CPURLRequest requestWithURL:anAbsoluteURL];
|
||||
|
||||
[_writeRequest setHTTPMethod:@"POST"];
|
||||
[_writeRequest setHTTPBody:[data string]];
|
||||
|
||||
[_writeRequest setValue:@"close" forHTTPHeaderField:@"Connection"];
|
||||
|
||||
if (aSaveOperation == CPSaveOperation)
|
||||
[_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
|
||||
|
||||
if (aSaveOperation != CPSaveToOperation)
|
||||
[self updateChangeCount:CPChangeCleared];
|
||||
|
||||
// FIXME: Oh man is this every looking for trouble, we need to handle login at the Cappuccino level, with HTTP Errors.
|
||||
var connection = [CPURLConnection connectionWithRequest:_writeRequest delegate:self];
|
||||
|
||||
connection.session = _CPSaveSessionMake(anAbsoluteURL, aSaveOperation, oldChangeCount, aDelegate, aDidSaveSelector, aContextInfo, connection);
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate method for CPURLConnection
|
||||
@ignore
|
||||
*/
|
||||
- (void)connection:(CPURLConnection)aConnection didReceiveResponse:(CPURLResponse)aResponse
|
||||
{
|
||||
var statusCode = [aResponse statusCode];
|
||||
|
||||
// Nothing to do if everything is hunky dory.
|
||||
if (statusCode == 200)
|
||||
return;
|
||||
|
||||
var session = aConnection.session;
|
||||
|
||||
if (aConnection == _readConnection)
|
||||
{
|
||||
[aConnection cancel];
|
||||
|
||||
alert("There was an error retrieving the document.");
|
||||
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 409: Conflict, in Cappuccino, overwrite protection for documents.
|
||||
if (statusCode == 409)
|
||||
{
|
||||
[aConnection cancel];
|
||||
|
||||
if (confirm("There already exists a file with that name, would you like to overwrite it?"))
|
||||
{
|
||||
[_writeRequest setValue:@"true" forHTTPHeaderField:@"x-cappuccino-overwrite"];
|
||||
|
||||
[aConnection start];
|
||||
}
|
||||
else
|
||||
{
|
||||
if (session.saveOperation != CPSaveToOperation)
|
||||
{
|
||||
_changeCount += session.changeCount;
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
||||
}
|
||||
|
||||
_writeRequest = nil;
|
||||
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate method for CPURLConnection
|
||||
@ignore
|
||||
*/
|
||||
- (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)aData
|
||||
{
|
||||
var session = aConnection.session;
|
||||
|
||||
// READ
|
||||
if (aConnection == _readConnection)
|
||||
{
|
||||
[self readFromData:[CPData dataWithString:aData] ofType:session.fileType error:nil];
|
||||
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, YES, session.contextInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (session.saveOperation != CPSaveToOperation)
|
||||
[self setFileURL:session.absoluteURL];
|
||||
|
||||
_writeRequest = nil;
|
||||
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, YES, session.contextInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate method for CPURLConnection
|
||||
@ignore
|
||||
*/
|
||||
- (void)connection:(CPURLConnection)aConnection didFailWithError:(CPError)anError
|
||||
{
|
||||
var session = aConnection.session;
|
||||
|
||||
if (_readConnection == aConnection)
|
||||
objj_msgSend(session.delegate, session.didReadSelector, self, NO, session.contextInfo);
|
||||
|
||||
else
|
||||
{
|
||||
if (session.saveOperation != CPSaveToOperation)
|
||||
{
|
||||
_changeCount += session.changeCount;
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
||||
}
|
||||
|
||||
_writeRequest = nil;
|
||||
|
||||
alert("There was an error saving the document.");
|
||||
|
||||
objj_msgSend(session.delegate, session.didSaveSelector, self, NO, session.contextInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate method for CPURLConnection
|
||||
@ignore
|
||||
*/
|
||||
- (void)connectionDidFinishLoading:(CPURLConnection)aConnection
|
||||
{
|
||||
if (_readConnection == aConnection)
|
||||
_readConnection = nil;
|
||||
}
|
||||
|
||||
// Managing Document Status
|
||||
/*!
|
||||
Returns <code>YES</code> if there are any unsaved changes.
|
||||
*/
|
||||
- (BOOL)isDocumentEdited
|
||||
{
|
||||
return _changeCount != 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Updates the number of unsaved changes to the document.
|
||||
@param aChangeType a new document change to apply
|
||||
*/
|
||||
- (void)updateChangeCount:(CPDocumentChangeType)aChangeType
|
||||
{
|
||||
if (aChangeType == CPChangeDone)
|
||||
++_changeCount;
|
||||
else if (aChangeType == CPChangeUndone)
|
||||
--_changeCount;
|
||||
else if (aChangeType == CPChangeCleared)
|
||||
_changeCount = 0;
|
||||
/*else if (aChangeType == CPCHangeReadOtherContents)
|
||||
|
||||
else if (aChangeType == CPChangeAutosaved)*/
|
||||
|
||||
[_windowControllers makeObjectsPerformSelector:@selector(setDocumentEdited:) withObject:[self isDocumentEdited]];
|
||||
}
|
||||
|
||||
// Managing File Types
|
||||
/*!
|
||||
Sets the document's file type
|
||||
@param aType the document's type
|
||||
*/
|
||||
- (void)setFileType:(CPString)aType
|
||||
{
|
||||
_fileType = aType;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document's file type
|
||||
*/
|
||||
- (CPString)fileType
|
||||
{
|
||||
return _fileType;
|
||||
}
|
||||
|
||||
// Working with Undo Manager
|
||||
/*!
|
||||
Returns <code>YES</code> if the document has a
|
||||
CPUndoManager.
|
||||
*/
|
||||
- (BOOL)hasUndoManager
|
||||
{
|
||||
return _hasUndoManager;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the document should have a CPUndoManager.
|
||||
@param aFlag <code>YES</code> makes the document have an undo manager
|
||||
*/
|
||||
- (void)setHasUndoManager:(BOOL)aFlag
|
||||
{
|
||||
if (_hasUndoManager == aFlag)
|
||||
return;
|
||||
|
||||
_hasUndoManager = aFlag;
|
||||
|
||||
if (!_hasUndoManager)
|
||||
[self setUndoManager:nil];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_undoManagerWillCloseGroup:(CPNotification)aNotification
|
||||
{
|
||||
var undoManager = [aNotification object];
|
||||
|
||||
if ([undoManager isUndoing] || [undoManager isRedoing])
|
||||
return;
|
||||
|
||||
[self updateChangeCount:CPChangeDone];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_undoManagerDidUndoChange:(CPNotification)aNotification
|
||||
{
|
||||
[self updateChangeCount:CPChangeUndone];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_undoManagerDidRedoChange:(CPNotification)aNotification
|
||||
{
|
||||
[self updateChangeCount:CPChangeDone];
|
||||
}
|
||||
|
||||
/*
|
||||
Sets the document's undo manager. This method will add the
|
||||
undo manager as an observer to the notification center.
|
||||
@param anUndoManager the new undo manager for the document
|
||||
*/
|
||||
- (void)setUndoManager:(CPUndoManager)anUndoManager
|
||||
{
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_undoManager)
|
||||
{
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPUndoManagerDidUndoChangeNotification
|
||||
object:_undoManager];
|
||||
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPUndoManagerDidRedoChangeNotification
|
||||
object:_undoManager];
|
||||
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPUndoManagerWillCloseUndoGroupNotification
|
||||
object:_undoManager];
|
||||
}
|
||||
|
||||
_undoManager = anUndoManager;
|
||||
|
||||
if (_undoManager)
|
||||
{
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_undoManagerDidUndoChange:)
|
||||
name:CPUndoManagerDidUndoChangeNotification
|
||||
object:_undoManager];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_undoManagerDidRedoChange:)
|
||||
name:CPUndoManagerDidRedoChangeNotification
|
||||
object:_undoManager];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_undoManagerWillCloseGroup:)
|
||||
name:CPUndoManagerWillCloseUndoGroupNotification
|
||||
object:_undoManager];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document's undo manager. If the document
|
||||
should have one, but the manager is <code>nil</code>, it
|
||||
will be created and then returned.
|
||||
@return the document's undo manager
|
||||
*/
|
||||
- (CPUndoManager)undoManager
|
||||
{
|
||||
if (_hasUndoManager && !_undoManager)
|
||||
[self setUndoManager:[[CPUndoManager alloc] init]];
|
||||
|
||||
return _undoManager;
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented as a delegate of a CPWindow
|
||||
@ignore
|
||||
*/
|
||||
- (CPUndoManager)windowWillReturnUndoManager:(CPWindow)aWindow
|
||||
{
|
||||
return [self undoManager];
|
||||
}
|
||||
|
||||
// Handling User Actions
|
||||
/*!
|
||||
Saves the document. If the document does not
|
||||
have a file path to save to (<code>fileURL</code>)
|
||||
then <code>saveDocumentAs:</code> will be called.
|
||||
@param aSender the object requesting the save
|
||||
*/
|
||||
- (void)saveDocument:(id)aSender
|
||||
{
|
||||
if (_fileURL)
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPDocumentWillSaveNotification
|
||||
object:self];
|
||||
|
||||
[self saveToURL:_fileURL ofType:[self fileType] forSaveOperation:CPSaveOperation delegate:self didSaveSelector:@selector(document:didSave:contextInfo:) contextInfo:NULL];
|
||||
}
|
||||
else
|
||||
[self saveDocumentAs:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Saves the document to a user specified path.
|
||||
@param aSender the object requesting the operation
|
||||
*/
|
||||
- (void)saveDocumentAs:(id)aSender
|
||||
{
|
||||
_documentName = window.prompt("Document Name:");
|
||||
|
||||
if (!_documentName)
|
||||
return;
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPDocumentWillSaveNotification
|
||||
object:self];
|
||||
|
||||
[self saveToURL:[self proposedFileURL] ofType:[self fileType] forSaveOperation:CPSaveAsOperation delegate:self didSaveSelector:@selector(document:didSave:contextInfo:) contextInfo:NULL];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (void)document:(id)aDocument didSave:(BOOL)didSave contextInfo:(id)aContextInfo
|
||||
{
|
||||
if (didSave)
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPDocumentDidSaveNotification
|
||||
object:self];
|
||||
else
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPDocumentDidFailToSaveNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPReadSessionMake = function(aType, aDelegate, aDidReadSelector, aContextInfo)
|
||||
{
|
||||
return { fileType:aType, delegate:aDelegate, didReadSelector:aDidReadSelector, contextInfo:aContextInfo };
|
||||
}
|
||||
|
||||
var _CPSaveSessionMake = function(anAbsoluteURL, aSaveOperation, aChangeCount, aDelegate, aDidSaveSelector, aContextInfo, aConnection)
|
||||
{
|
||||
return { absoluteURL:anAbsoluteURL, saveOperation:aSaveOperation, changeCount:aChangeCount, delegate:aDelegate, didSaveSelector:aDidSaveSelector, contextInfo:aContextInfo, connection:aConnection };
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* CPDocumentController.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@import "CPDocument.j"
|
||||
|
||||
|
||||
var CPSharedDocumentController = nil;
|
||||
|
||||
/*! @class CPDocumentController
|
||||
This class is responsible for managing an application's open documents.
|
||||
*/
|
||||
@implementation CPDocumentController : CPObject
|
||||
{
|
||||
CPArray _documents;
|
||||
CPArray _documentTypes;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the singleton instance of the application's document controller. If it has not
|
||||
been created yet, it will be created then returned.
|
||||
@return a CPDocumentController
|
||||
*/
|
||||
+ (id)sharedDocumentController
|
||||
{
|
||||
if (!CPSharedDocumentController)
|
||||
[[self alloc] init];
|
||||
|
||||
return CPSharedDocumentController;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_documents = [[CPArray alloc] init];
|
||||
|
||||
if (!CPSharedDocumentController)
|
||||
CPSharedDocumentController = self;
|
||||
|
||||
_documentTypes = [[[CPBundle mainBundle] infoDictionary] objectForKey:@"CPBundleDocumentTypes"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// Creating and Opening Documents
|
||||
|
||||
/*!
|
||||
Returns the document matching the specified URL. This
|
||||
method searches documents already open. It does not
|
||||
open the document at the URL if it is not already open.
|
||||
@param aURL the url of the document
|
||||
@return the document, or <code>nil</code> if such a document is not open
|
||||
*/
|
||||
- (CPDocument)documentForURL:(CPURL)aURL
|
||||
{
|
||||
var index = 0,
|
||||
count = [_documents count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var theDocument = _documents[index];
|
||||
|
||||
if ([[theDocument fileURL] isEqual:aURL])
|
||||
return theDocument;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new document of the specified type.
|
||||
@param aType the type of the new document
|
||||
@param shouldDisplay whether to display the document on screen
|
||||
*/
|
||||
- (void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
|
||||
{
|
||||
var theDocument = [self makeUntitledDocumentOfType:aType error:nil];
|
||||
|
||||
if (theDocument)
|
||||
[self addDocument:theDocument];
|
||||
|
||||
if (shouldDisplay)
|
||||
{
|
||||
[theDocument makeWindowControllers];
|
||||
[theDocument showWindows];
|
||||
}
|
||||
|
||||
return theDocument;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a document of the specified type.
|
||||
@param aType the document type
|
||||
@param anError not used
|
||||
@return the created document
|
||||
*/
|
||||
- (CPDocument)makeUntitledDocumentOfType:(CPString)aType error:({CPError})anError
|
||||
{
|
||||
return [[[self documentClassForType:aType] alloc] initWithType:aType error:anError];
|
||||
}
|
||||
|
||||
/*!
|
||||
Opens the document at the specified URL.
|
||||
@param anAbsoluteURL the path to the document's file
|
||||
@param shouldDisplay whether to display the document on screen
|
||||
@param anError not used
|
||||
@return the opened document
|
||||
*/
|
||||
- (CPDocument)openDocumentWithContentsOfURL:(CPURL)anAbsoluteURL display:(BOOL)shouldDisplay error:(CPError)anError
|
||||
{
|
||||
var result = [self documentForURL:anAbsoluteURL];
|
||||
|
||||
if (!result)
|
||||
{
|
||||
var type = [self typeForContentsOfURL:anAbsoluteURL error:anError];
|
||||
|
||||
result = [self makeDocumentWithContentsOfURL:anAbsoluteURL ofType:type delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:[CPDictionary dictionaryWithObject:shouldDisplay forKey:@"shouldDisplay"]];
|
||||
}
|
||||
else if (shouldDisplay)
|
||||
[result showWindows];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Loads a document for a specified URL with it's content
|
||||
retrieved from another URL.
|
||||
@param anAbsoluteURL the document URL
|
||||
@param absoluteContentsURL the location of the document's contents
|
||||
@param anError not used
|
||||
@return the loaded document or <code>nil</code> if there was an error
|
||||
*/
|
||||
- (CPDocument)reopenDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL error:(CPError)anError
|
||||
{
|
||||
return [self makeDocumentForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] delegate:self didReadSelector:@selector(document:didRead:contextInfo:) contextInfo:nil];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a document from the contents at the specified URL.
|
||||
Notifies the provided delegate with the provided selector afterwards.
|
||||
@param anAbsoluteURL the location of the document data
|
||||
@param aType the document type
|
||||
@param aDelegate the delegate to notify
|
||||
@param aSelector the selector to notify with
|
||||
@param aContextInfo the context infomration passed to the delegate
|
||||
*/
|
||||
- (CPDocument)makeDocumentWithContentsOfURL:(CPURL)anAbsoluteURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
return [[[self documentClassForType:aType] alloc] initWithContentsOfURL:anAbsoluteURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a document from the contents of a URL, and sets
|
||||
the document's URL location as another URL.
|
||||
@param anAbsoluteURL the document's location
|
||||
@param absoluteContentsURL the location of the document's contents
|
||||
@param aType the document's data type
|
||||
@param aDelegate receives a callback after the load has completed
|
||||
@param aSelector the selector to invoke for the callback
|
||||
@param aContextInfo an object passed as an argument for the callback
|
||||
@return a new document or <code>nil</code> if there was an error
|
||||
*/
|
||||
- (CPDocument)makeDocumentForURL:(CPURL)anAbsoluteURL withContentsOfURL:(CPURL)absoluteContentsURL ofType:(CPString)aType delegate:(id)aDelegate didReadSelector:(SEL)aSelector contextInfo:(id)aContextInfo
|
||||
{
|
||||
return [[[self documentClassForType:aType] alloc] initForURL:anAbsoluteURL withContentsOfURL:absoluteContentsURL ofType:aType delegate:aDelegate didReadSelector:aSelector contextInfo:aContextInfo];
|
||||
}
|
||||
|
||||
/*
|
||||
Implemented delegate method
|
||||
@ignore
|
||||
*/
|
||||
- (void)document:(CPDocument)aDocument didRead:(BOOL)didRead contextInfo:(id)aContextInfo
|
||||
{
|
||||
if (!didRead)
|
||||
return;
|
||||
|
||||
[self addDocument:aDocument];
|
||||
[aDocument makeWindowControllers];
|
||||
|
||||
if ([aContextInfo objectForKey:@"shouldDisplay"])
|
||||
[aDocument showWindows];
|
||||
}
|
||||
|
||||
/*!
|
||||
Opens a new document in the application.
|
||||
@param aSender the requesting object
|
||||
*/
|
||||
- (CFAction)newDocument:(id)aSender
|
||||
{
|
||||
[self openUntitledDocumentOfType:[[_documentTypes objectAtIndex:0] objectForKey:@"CPBundleTypeName"] display:YES];
|
||||
}
|
||||
|
||||
// Managing Documents
|
||||
|
||||
/*!
|
||||
Returns the array of all documents being managed. This is
|
||||
the same as all open documents in the application.
|
||||
*/
|
||||
- (CPArray)documents
|
||||
{
|
||||
return _documents;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds <code>aDocument</code> under the control of the receiver.
|
||||
@param aDocument the document to add
|
||||
*/
|
||||
- (void)addDocument:(CPDocument)aDocument
|
||||
{
|
||||
[_documents addObject:aDocument];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes <code>aDocument</code> from the control of the receiver.
|
||||
@param aDocument the document to remove
|
||||
*/
|
||||
- (void)removeDocument:(CPDocument)aDocument
|
||||
{
|
||||
[_documents removeObjectIdenticalTo:aDocument];
|
||||
}
|
||||
|
||||
- (CPString)defaultType
|
||||
{
|
||||
return [_documentTypes[0] objectForKey:@"CPBundleTypeName"];
|
||||
}
|
||||
|
||||
- (CPString)typeForContentsOfURL:(CPURL)anAbsoluteURL error:(CPError)outError
|
||||
{
|
||||
var index = 0,
|
||||
count = _documentTypes.length,
|
||||
|
||||
extension = [[anAbsoluteURL pathExtension] lowercaseString];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var documentType = _documentTypes[index],
|
||||
extensions = [documentType objectForKey:@"CFBundleTypeExtensions"],
|
||||
extensionIndex = 0,
|
||||
extensionCount = extensions.length;
|
||||
|
||||
for (; extensionIndex < extensionCount; ++extensionIndex)
|
||||
if ([extensions[extensionIndex] lowercaseString] == extension)
|
||||
return [documentType objectForKey:@"CPBundleTypeName"];
|
||||
}
|
||||
|
||||
// FIXME?
|
||||
return [self defaultType];//nil;
|
||||
}
|
||||
|
||||
// Managing Document Types
|
||||
|
||||
/* @ignore */
|
||||
- (CPDictionary)_infoForType:(CPString)aType
|
||||
{
|
||||
var i = 0,
|
||||
count = [_documentTypes count];
|
||||
|
||||
for (;i < count; ++i)
|
||||
{
|
||||
var documentType = _documentTypes[i];
|
||||
|
||||
if ([documentType objectForKey:@"CPBundleTypeName"] == aType)
|
||||
return documentType;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPDocument subclass associated with <code>aType</code>.
|
||||
@param aType the type of document
|
||||
@return a Cappuccino Class object, or <code>nil</code> if no match was found
|
||||
*/
|
||||
- (Class)documentClassForType:(CPString)aType
|
||||
{
|
||||
var className = [[self _infoForType:aType] objectForKey:@"CPDocumentClass"];
|
||||
|
||||
return className ? CPClassFromString(className) : nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* CPDragServer.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <AppKit/CPView.j>
|
||||
@import <AppKit/CPEvent.j>
|
||||
@import <AppKit/CPPasteboard.j>
|
||||
@import <AppKit/CPImageView.j>
|
||||
|
||||
|
||||
var CPSharedDragServer = nil;
|
||||
|
||||
var CPDragServerView = nil,
|
||||
CPDragServerSource = nil,
|
||||
CPDragServerWindow = nil,
|
||||
CPDragServerOffset = nil,
|
||||
CPDragServerLocation = nil,
|
||||
CPDragServerPasteboard = nil,
|
||||
CPDragServerDestination = nil,
|
||||
CPDragServerDraggingInfo = nil;
|
||||
|
||||
var CPDragServerIsDraggingImage = NO,
|
||||
|
||||
CPDragServerShouldSendDraggedViewMovedTo = NO,
|
||||
CPDragServerShouldSendDraggedImageMovedTo = NO,
|
||||
|
||||
CPDragServerShouldSendDraggedViewEndedAtOperation = NO,
|
||||
CPDragServerShouldSendDraggedImageEndedAtOperation = NO;
|
||||
|
||||
var CPDragServerStartDragging = function(anEvent)
|
||||
{
|
||||
CPDragServerUpdateDragging(anEvent);
|
||||
}
|
||||
|
||||
var CPDragServerUpdateDragging = function(anEvent)
|
||||
{
|
||||
// If this is a mouse up, then complete the drag.
|
||||
if([anEvent type] == CPLeftMouseUp)
|
||||
{
|
||||
CPDragServerLocation = [[CPDragServerDestination window] convertBridgeToBase:[[anEvent window] convertBaseToBridge:[anEvent locationInWindow]]];
|
||||
|
||||
[CPDragServerView removeFromSuperview];
|
||||
[CPSharedDragServer._dragWindow orderOut:nil];
|
||||
|
||||
if (CPDragServerDestination &&
|
||||
(![CPDragServerDestination respondsToSelector:@selector(prepareForDragOperation:)] || [CPDragServerDestination prepareForDragOperation:CPDragServerDraggingInfo]) &&
|
||||
(![CPDragServerDestination respondsToSelector:@selector(performDragOperation:)] || [CPDragServerDestination performDragOperation:CPDragServerDraggingInfo]) &&
|
||||
[CPDragServerDestination respondsToSelector:@selector(concludeDragOperation:)])
|
||||
[CPDragServerDestination concludeDragOperation:CPDragServerDraggingInfo];
|
||||
|
||||
if (CPDragServerShouldSendDraggedImageEndedAtOperation)
|
||||
[CPDragServerSource draggedImage:[CPDragServerView image] endedAt:CPDragServerLocation operation:NO];
|
||||
else if (CPDragServerShouldSendDraggedViewEndedAtOperation)
|
||||
[CPDragServerSource draggedView:CPDragServerView endedAt:CPDragServerLocation operation:NO];
|
||||
|
||||
CPDragServerIsDraggingImage = NO;
|
||||
CPDragServerDestination = nil;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not a mouse up, then we're going to want to grab the next event.
|
||||
[CPApp setCallback:CPDragServerUpdateDragging
|
||||
forNextEventMatchingMask:CPMouseMovedMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask
|
||||
untilDate:nil inMode:0 dequeue:NO];
|
||||
|
||||
var location = [anEvent locationInWindow],
|
||||
operation =
|
||||
bridgeLocation = [[anEvent window] convertBaseToBridge:location];
|
||||
|
||||
// We have to convert base to bridge since the drag event comes from the source window, not the drag window.
|
||||
var draggingDestination = [[CPDOMWindowBridge sharedDOMWindowBridge] _dragHitTest:bridgeLocation pasteboard:CPDragServerPasteboard];
|
||||
|
||||
CPDragServerLocation = [[CPDragServerDestination window] convertBridgeToBase:bridgeLocation];
|
||||
|
||||
if(draggingDestination != CPDragServerDestination)
|
||||
{
|
||||
if (CPDragServerDestination && [CPDragServerDestination respondsToSelector:@selector(draggingExited:)])
|
||||
[CPDragServerDestination draggingExited:CPDragServerDraggingInfo];
|
||||
|
||||
CPDragServerDestination = draggingDestination;
|
||||
|
||||
if (CPDragServerDestination && [CPDragServerDestination respondsToSelector:@selector(draggingEntered:)])
|
||||
[CPDragServerDestination draggingEntered:CPDragServerDraggingInfo];
|
||||
}
|
||||
else if (CPDragServerDestination && [CPDragServerDestination respondsToSelector:@selector(draggingUpdated:)])
|
||||
[CPDragServerDestination draggingUpdated:CPDragServerDraggingInfo];
|
||||
|
||||
location.x -= CPDragServerOffset.x;
|
||||
location.y -= CPDragServerOffset.y;
|
||||
|
||||
[CPDragServerView setFrameOrigin:location];
|
||||
|
||||
if (CPDragServerShouldSendDraggedImageMovedTo)
|
||||
[CPDragServerSource draggedImage:[CPDragServerView image] movedTo:location];
|
||||
else if (CPDragServerShouldSendDraggedViewMovedTo)
|
||||
[CPDragServerSource draggedView:CPDragServerView movedTo:location];
|
||||
}
|
||||
|
||||
/*
|
||||
CPDraggingInfo is a container of information about a specific dragging session.
|
||||
@ignore
|
||||
*/
|
||||
@implementation CPDraggingInfo : CPObject
|
||||
{
|
||||
CPWindow _window;
|
||||
}
|
||||
|
||||
/*
|
||||
Initializes the receiver with the window
|
||||
*/
|
||||
- (id)initWithWindow:(CPWindow)aWindow
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_window = aWindow;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
- (id)draggingSource
|
||||
{
|
||||
return CPDragServerSource;
|
||||
}
|
||||
|
||||
- (CPPoint)draggingLocation
|
||||
{
|
||||
return CPDragServerLocation;
|
||||
}
|
||||
|
||||
- (CPPasteboard)draggingPasteboard
|
||||
{
|
||||
return CPDragServerPasteboard;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPDragServer : CPObject
|
||||
{
|
||||
CPWindow _dragWindow;
|
||||
CPImageView _imageView;
|
||||
}
|
||||
|
||||
/*
|
||||
Private Objective-J/Cappuccino method
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPDragServer class])
|
||||
return;
|
||||
|
||||
CPDragServerDraggingInfo = [[CPDraggingInfo alloc] init];
|
||||
}
|
||||
|
||||
+ (CPDragServer)sharedDragServer
|
||||
{
|
||||
if (!CPSharedDragServer)
|
||||
CPSharedDragServer = [[CPDragServer alloc] init];
|
||||
|
||||
return CPSharedDragServer;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_dragWindow = [[CPWindow alloc] initWithContentRect:CPRectMakeZero() styleMask:CPBorderlessWindowMask];
|
||||
[_dragWindow setLevel:CPDraggingWindowLevel];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initiates a drag session.
|
||||
@param aView the view being dragged
|
||||
@param aWindow the window where the drag source is
|
||||
@param viewLocation
|
||||
@param mouseOffset
|
||||
@param anEvent
|
||||
@param aPasteboard the pasteboard that contains the drag data
|
||||
@param aSourceObject the object where the drag started
|
||||
@param slideBack if <code>YES</code>, <code>aView</code> slides back to
|
||||
its origin on a failed drop
|
||||
*/
|
||||
- (void)dragView:(CPView)aView fromWindow:(CPWindow)aWindow at:(CGPoint)viewLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
|
||||
{
|
||||
var eventLocation = [anEvent locationInWindow];
|
||||
|
||||
CPDragServerView = aView;
|
||||
CPDragServerSource = aSourceObject;
|
||||
CPDragServerWindow = aWindow;
|
||||
CPDragServerOffset = CPPointMake(eventLocation.x - viewLocation.x, eventLocation.y - viewLocation.y);
|
||||
CPDragServerPasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];//aPasteboard;
|
||||
|
||||
[_dragWindow setFrameSize:CGSizeMakeCopy([[CPDOMWindowBridge sharedDOMWindowBridge] frame].size)];
|
||||
[_dragWindow orderFront:self];
|
||||
|
||||
[aView setFrameOrigin:viewLocation];
|
||||
[[_dragWindow contentView] addSubview:aView];
|
||||
|
||||
if (CPDragServerIsDraggingImage)
|
||||
{
|
||||
if ([CPDragServerSource respondsToSelector:@selector(draggedImage:beganAt:)])
|
||||
[CPDragServerSource draggedImage:[aView image] beganAt:viewLocation];
|
||||
|
||||
CPDragServerShouldSendDraggedImageMovedTo = [CPDragServerSource respondsToSelector:@selector(draggedImage:movedTo:)];
|
||||
CPDragServerShouldSendDraggedImageEndedAtOperation = [CPDragServerSource respondsToSelector:@selector(draggedImage:endAt:operation:)];
|
||||
|
||||
CPDragServerShouldSendDraggedViewMovedTo = NO;
|
||||
CPDragServerShouldSendDraggedViewEndedAtOperation = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([CPDragServerSource respondsToSelector:@selector(draggedView:beganAt:)])
|
||||
[CPDragServerSource draggedView:aView beganAt:viewLocation];
|
||||
|
||||
CPDragServerShouldSendDraggedViewMovedTo = [CPDragServerSource respondsToSelector:@selector(draggedView:movedTo:)];
|
||||
CPDragServerShouldSendDraggedViewEndedAtOperation = [CPDragServerSource respondsToSelector:@selector(draggedView:endedAt:operation:)];
|
||||
|
||||
|
||||
CPDragServerShouldSendDraggedImageMovedTo = NO;
|
||||
CPDragServerShouldSendDraggedImageEndedAtOperation = NO;
|
||||
}
|
||||
|
||||
CPDragServerStartDragging(anEvent);
|
||||
}
|
||||
|
||||
/*!
|
||||
Initiates a drag session.
|
||||
@param anImage the image to be dragged
|
||||
@param aWindow the source window of the drag session
|
||||
@param imageLocation
|
||||
@param mouseOffset
|
||||
@param anEvent
|
||||
@param aPasteboard the pasteboard where the drag data is located
|
||||
@param aSourceObject the object where the drag started
|
||||
@param slideBack if <code>YES</code>, <code>aView</code> slides back to
|
||||
its origin on a failed drop
|
||||
*/
|
||||
- (void)dragImage:(CPImage)anImage fromWindow:(CPWindow)aWindow at:(CGPoint)imageLocation offset:(CGSize)mouseOffset event:(CPEvent)anEvent pasteboard:(CPPasteboard)aPasteboard source:(id)aSourceObject slideBack:(BOOL)slideBack
|
||||
{
|
||||
CPDragServerIsDraggingImage = YES;
|
||||
|
||||
if (!_imageView)
|
||||
_imageView = [[CPImageView alloc] initWithFrame:CPRectMakeZero()];
|
||||
|
||||
[_imageView setImage:anImage];
|
||||
[_imageView setFrameSize:CGSizeMakeCopy([anImage size])];
|
||||
|
||||
[self dragView:_imageView fromWindow:aWindow at:imageLocation offset:mouseOffset event:anEvent pasteboard:aPasteboard source:aSourceObject slideBack:slideBack];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPView (CPDraggingAdditions)
|
||||
|
||||
/* @ignore */
|
||||
- (CPView)_dragHitTest:(CPPoint)aPoint pasteboard:(CPPasteboard)aPasteboard
|
||||
{
|
||||
if(!CPRectContainsPoint(_frame, aPoint) || self == CPDragServerView)
|
||||
return nil;
|
||||
|
||||
var view = nil,
|
||||
i = [_subviews count],
|
||||
adjustedPoint = CPPointMake(aPoint.x - CPRectGetMinX(_frame), aPoint.y - CPRectGetMinY(_frame));
|
||||
|
||||
while (i--)
|
||||
if (view = [_subviews[i] _dragHitTest:adjustedPoint pasteboard:aPasteboard])
|
||||
return view;
|
||||
|
||||
if ([aPasteboard availableTypeFromArray:_registeredDraggedTypes])
|
||||
return self;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,521 @@
|
||||
/*
|
||||
* CPEvent.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPLeftMouseDown = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPLeftMouseUp = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPRightMouseDown = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPRightMouseUp = 4;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPMouseMoved = 5;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPLeftMouseDragged = 6;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPRightMouseDragged = 7;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPMouseEntered = 8;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPMouseExited = 9;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPKeyDown = 10;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPKeyUp = 11;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPFlagsChanged = 12;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPAppKitDefined = 13;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPSystemDefined = 14;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPApplicationDefined = 15;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPPeriodic = 16;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPCursorUpdate = 17;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPScrollWheel = 22;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPOtherMouseDown = 25;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPOtherMouseUp = 26;
|
||||
/*
|
||||
@global
|
||||
@group CPEventType
|
||||
*/
|
||||
CPOtherMouseDragged = 27;
|
||||
|
||||
CPAlphaShiftKeyMask = 1 << 16;
|
||||
CPShiftKeyMask = 1 << 17;
|
||||
CPControlKeyMask = 1 << 18;
|
||||
CPAlternateKeyMask = 1 << 19;
|
||||
CPCommandKeyMask = 1 << 20;
|
||||
CPNumericPadKeyMask = 1 << 21;
|
||||
CPHelpKeyMask = 1 << 22;
|
||||
CPFunctionKeyMask = 1 << 23;
|
||||
CPDeviceIndependentModifierFlagsMask = 0xffff0000;
|
||||
|
||||
CPLeftMouseDownMask = 1 << CPLeftMouseDown;
|
||||
CPLeftMouseUpMask = 1 << CPLeftMouseUp;
|
||||
CPRightMouseDownMask = 1 << CPRightMouseDown;
|
||||
CPRightMouseUpMask = 1 << CPRightMouseUp;
|
||||
CPOtherMouseDownMask = 1 << CPOtherMouseDown;
|
||||
CPOtherMouseUpMask = 1 << CPOtherMouseUp;
|
||||
CPMouseMovedMask = 1 << CPMouseMoved;
|
||||
CPLeftMouseDraggedMask = 1 << CPLeftMouseDragged;
|
||||
CPRightMouseDraggedMask = 1 << CPRightMouseDragged;
|
||||
CPOtherMouseDragged = 1 << CPOtherMouseDragged;
|
||||
CPMouseEnteredMask = 1 << CPMouseEntered;
|
||||
CPMouseExitedMask = 1 << CPMouseExited;
|
||||
CPCursorUpdateMask = 1 << CPCursorUpdate;
|
||||
CPKeyDownMask = 1 << CPKeyDown;
|
||||
CPKeyUpMask = 1 << CPKeyUp;
|
||||
CPFlagsChangedMask = 1 << CPFlagsChanged;
|
||||
CPAppKitDefinedMask = 1 << CPAppKitDefined;
|
||||
CPSystemDefinedMask = 1 << CPSystemDefined;
|
||||
CPApplicationDefinedMask = 1 << CPApplicationDefined;
|
||||
CPPeriodicMask = 1 << CPPeriodic;
|
||||
CPScrollWheelMask = 1 << CPScrollWheel;
|
||||
CPAnyEventMask = 0xffffffff;
|
||||
|
||||
CPDOMEventDoubleClick = "dblclick",
|
||||
CPDOMEventMouseDown = "mousedown",
|
||||
CPDOMEventMouseUp = "mouseup",
|
||||
CPDOMEventMouseMoved = "mousemove",
|
||||
CPDOMEventMouseDragged = "mousedrag",
|
||||
CPDOMEventKeyUp = "keyup",
|
||||
CPDOMEventKeyDown = "keydown",
|
||||
CPDOMEventKeyPress = "keypress";
|
||||
CPDOMEventCopy = "copy";
|
||||
CPDOMEventPaste = "paste";
|
||||
CPDOMEventScrollWheel = "mousewheel";
|
||||
|
||||
var _CPEventPeriodicEventPeriod = 0,
|
||||
_CPEventPeriodicEventTimer = nil;
|
||||
|
||||
/*! @class CPEvent
|
||||
CPEvent encapsulates the details of a Cappuccino keyboard or mouse event.
|
||||
*/
|
||||
@implementation CPEvent : CPObject
|
||||
{
|
||||
CPEventType _type;
|
||||
CPPoint _location;
|
||||
unsigned _modifierFlags;
|
||||
CPTimeInterval _timestamp;
|
||||
CPGraphicsContext _context;
|
||||
int _eventNumber;
|
||||
unsigned _clickCount;
|
||||
float _pressure;
|
||||
CPWindow _window;
|
||||
CPString _characters;
|
||||
CPString _charactersIgnoringModifiers
|
||||
BOOL _isARepeat;
|
||||
unsigned _keyCode;
|
||||
DOMEvent _DOMEvent;
|
||||
|
||||
float _deltaX;
|
||||
float _deltaY;
|
||||
float _deltaZ;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new keyboard event.
|
||||
@param anEventType the event type. Must be one of CPKeyDown, CPKeyUp or CPFlagsChanged
|
||||
@param aPoint the location of the cursor in the window specified by <code>aWindowNumber</code>
|
||||
@param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals
|
||||
@param aTimestamp the time the event occurred
|
||||
@param aWindowNumber the number of the CPWindow where the event occurred
|
||||
@param aGraphicsContext the graphics context where the event occurred
|
||||
@param characters the characters associated with the event
|
||||
@param unmodCharacters the string of keys pressed without the presence of any modifiers other than Shift
|
||||
@param repeatKey <code>YES</code> 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
|
||||
@throws CPInternalInconsistencyException if <code>anEventType</code> 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
|
||||
{
|
||||
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
|
||||
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
|
||||
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new mouse event
|
||||
@param anEventType the event type
|
||||
@param aPoint the location of the cursor in the window specified by <code>aWindowNumber</code>
|
||||
@param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals
|
||||
@param aTimestamp the time the event occurred
|
||||
@param aWindowNumber the number of the CPWindow where the event occurred
|
||||
@param aGraphicsContext the graphics context where the event occurred
|
||||
@param anEventNumber a number for this event
|
||||
@param aClickCount the number of clicks that caused this event
|
||||
@param aPressure the amount of pressure applied to the input device (ranges from 0.0 to 1.0)
|
||||
@throws CPInternalInconsistencyException if an invalid event type is provided
|
||||
@return the new mouse event
|
||||
*/
|
||||
+ (id)mouseEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
eventNumber:(int)anEventNumber clickCount:(int)aClickCount pressure:(float)aPressure
|
||||
{
|
||||
return [[self alloc] _initMouseEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
|
||||
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext eventNumber:anEventNumber clickCount:aClickCount pressure:aPressure];
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new custom event
|
||||
@param anEventType the event type. Must be one of CPAppKitDefined, CPSystemDefined, CPApplicationDefined or CPPeriodic
|
||||
@param aLocation the location of the cursor in the window specified by <code>aWindowNumber</code>
|
||||
@param modifierFlags a bitwise combination of the modifiers specified in the CPEvent globals
|
||||
@param aTimestamp the time the event occurred
|
||||
@param aWindowNumber the number of the CPWindow where the event occurred
|
||||
@param aGraphicsContext the graphics context where the event occurred
|
||||
@param aSubtype a numeric identifier to differentiate this event from other custom events
|
||||
@param aData1 more data that describes the event
|
||||
@param aData2 even more data that describes the event
|
||||
@throws CPInternalInconsistencyException if an invalid event type is provided
|
||||
@return the new custom event
|
||||
*/
|
||||
+ (CPEvent)otherEventWithType:(CPEventType)anEventType location:(CGPoint)aLocation modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
subtype:(short)aSubtype data1:(int)aData1 data2:(int)aData2
|
||||
{
|
||||
return [[self alloc] _initOtherEventWithType:anEventType location:aLocation modifierFlags:modifierFlags
|
||||
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext subtype:aSubtype data1:aData1 data2:aData2];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initMouseEventWithType:(CPEventType)anEventType location:(CPPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
eventNumber:(int)anEventNumber clickCount:(int)aClickCount pressure:(float)aPressure
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_type = anEventType;
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
_eventNumber = anEventNumber;
|
||||
_clickCount = aClickCount;
|
||||
_pressure = aPressure;
|
||||
_window = [CPApp windowWithWindowNumber:aWindowNumber];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CPPoint)aPoint modifierFlags:(unsigned int)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_type = anEventType;
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
_characters = characters;
|
||||
_charactersIgnoringModifiers = unmodCharacters;
|
||||
_isARepeat = isARepeat;
|
||||
_keyCode = code;
|
||||
_window = [CPApp windowWithWindowNumber:aWindowNumber];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initOtherEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned)modifierFlags
|
||||
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
|
||||
subtype:(short)aSubtype data1:(int)aData1 data2:(int)aData2
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_type = anEventType;
|
||||
_location = CPPointCreateCopy(aPoint);
|
||||
_modifierFlags = modifierFlags;
|
||||
_timestamp = aTimestamp;
|
||||
_context = aGraphicsContext;
|
||||
_subtype = aSubtype;
|
||||
_data1 = aData1;
|
||||
_data2 = aData2;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the location of the mouse (for mouse events).
|
||||
If this is not a mouse event, it returns <code>nil</code>.
|
||||
If <code>window</code> returns <code>nil</code>, then
|
||||
the mouse coordinates will be based on the screen coordinates.
|
||||
Otherwise, the coordinates are relative to the window's coordinates.
|
||||
@return the location of the mouse, or <code>nil</code> for non-mouse events.
|
||||
*/
|
||||
- (CGPoint)locationInWindow
|
||||
{
|
||||
return _location;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns event information as a bit mask
|
||||
*/
|
||||
- (unsigned)modifierFlags
|
||||
{
|
||||
return _modifierFlags;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the time the event occurred
|
||||
*/
|
||||
- (CPTimeInterval)timestamp
|
||||
{
|
||||
return _timestamp;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the type of event.
|
||||
*/
|
||||
- (CPEventType)type
|
||||
{
|
||||
return _type;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the event's associated window
|
||||
*/
|
||||
- (CPWindow)window
|
||||
{
|
||||
return _window;
|
||||
}
|
||||
|
||||
/*!
|
||||
The number of the window associated with the event.
|
||||
*/
|
||||
- (int)windowNumber
|
||||
{
|
||||
return _windowNumber;
|
||||
}
|
||||
|
||||
// Mouse Event Information
|
||||
/*!
|
||||
Returns the button number for the mouse that generated the event.
|
||||
*/
|
||||
- (int)buttonNumber
|
||||
{
|
||||
return _buttonNumber;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of clicks that caused this event. (mouse only)
|
||||
*/
|
||||
- (int)clickCount
|
||||
{
|
||||
return _clickCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the characters associated with this event (keyboard only)
|
||||
@throws CPInternalInconsistencyException if this method is called on a non-key event
|
||||
*/
|
||||
- (CPString)characters
|
||||
{
|
||||
return _characters;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the character ignoring any modifiers (except shift).
|
||||
@throws CPInternalInconsistencyException if this method is called on a non-key event
|
||||
*/
|
||||
- (CPString)charactersIgnoringModifiers
|
||||
{
|
||||
return _charactersIgnoringModifiers;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the keyboard event was caused by the key being held down.
|
||||
@throws CPInternalInconsistencyException if this method is called on a non-key event
|
||||
*/
|
||||
- (BOOL)isARepeat
|
||||
{
|
||||
return _isARepeat;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the key's key code.
|
||||
@throws CPInternalInconsistencyException if this method is called on a non-key event
|
||||
*/
|
||||
- (unsigned short)keyCode
|
||||
{
|
||||
return _keyCode;
|
||||
}
|
||||
|
||||
- (float)pressure
|
||||
{
|
||||
return _pressure;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (DOMEvent)_DOMEvent
|
||||
{
|
||||
return _DOMEvent;
|
||||
}
|
||||
|
||||
// Getting Scroll Wheel Event Infomration
|
||||
/*!
|
||||
Returns the change in the x-axis for a mouse event.
|
||||
*/
|
||||
- (float)deltaX
|
||||
{
|
||||
return _deltaX;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the change in the y-axis for a mouse event.
|
||||
*/
|
||||
- (float)deltaY
|
||||
{
|
||||
return _deltaY;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the change in the x-axis for a mouse event.
|
||||
*/
|
||||
- (float)deltaZ
|
||||
{
|
||||
return _deltaZ;
|
||||
}
|
||||
|
||||
/*!
|
||||
Generates periodic events every <code>aPeriod</code> seconds.
|
||||
@param aDelay the number of seconds before the first event
|
||||
@param aPeriod the length of time in seconds between successive events
|
||||
*/
|
||||
+ (void)startPeriodicEventsAfterDelay:(CPTimeInterval)aDelay withPeriod:(CPTimeInterval)aPeriod
|
||||
{
|
||||
_CPEventPeriodicEventPeriod = aPeriod;
|
||||
|
||||
// FIXME: OH TIMERS!!!
|
||||
_CPEventPeriodicEventTimer = window.setTimeout(function() { _CPEventPeriodicEventTimer = window.setInterval(_CPEventFirePeriodEvent, aPeriod * 1000.0); }, aDelay * 1000.0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops the periodic events from being generated
|
||||
*/
|
||||
+ (void)stopPeriodicEvents
|
||||
{
|
||||
if (_CPEventPeriodicEventTimer === nil)
|
||||
return;
|
||||
|
||||
window.clearTimeout(_CPEventPeriodicEventTimer);
|
||||
|
||||
_CPEventPeriodicEventTimer = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
function _CPEventFirePeriodEvent()
|
||||
{
|
||||
[CPApp sendEvent:[CPEvent otherEventWithType:CPPeriodic location:_CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil subtype:0 data1:0 data2:0]];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* CPFlashMovie.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
|
||||
@implementation CPFlashMovie : CPObject
|
||||
{
|
||||
CPString _fileName;
|
||||
}
|
||||
|
||||
+ (id)flashMovieWithFile:(CPString)aFileName
|
||||
{
|
||||
return [[self alloc] initWithFile:aFileName];
|
||||
}
|
||||
|
||||
- (id)initWithFile:(CPString)aFileName
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_fileName = aFileName;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* CPFlashView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPDOMWindowBridge.j"
|
||||
@import "CPFlashMovie.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
@implementation CPFlashView : CPView
|
||||
{
|
||||
CPFlashMovie _flashMovie;
|
||||
|
||||
DOMElement _DOMEmbedElement;
|
||||
DOMElement _DOMMParamElement;
|
||||
DOMElement _DOMObjectElement;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_DOMObjectElement = document.createElement("object");
|
||||
_DOMObjectElement.width = "100%";
|
||||
_DOMObjectElement.height = "100%";
|
||||
_DOMObjectElement.style.top = "0px";
|
||||
_DOMObjectElement.style.left = "0px";
|
||||
|
||||
_DOMParamElement = document.createElement("param");
|
||||
_DOMParamElement.name = "movie";
|
||||
|
||||
_DOMObjectElement.appendChild(_DOMParamElement);
|
||||
|
||||
var param = document.createElement("param");
|
||||
|
||||
param.name = "wmode";
|
||||
param.value = "transparent";
|
||||
|
||||
_DOMObjectElement.appendChild(param);
|
||||
|
||||
_DOMEmbedElement = document.createElement("embed");
|
||||
|
||||
_DOMEmbedElement.type = "application/x-shockwave-flash";
|
||||
_DOMEmbedElement.wmode = "transparent";
|
||||
_DOMEmbedElement.width = "100%";
|
||||
_DOMEmbedElement.height = "100%";
|
||||
|
||||
// IE requires this thing to be in the _DOMElement and not the _DOMObjectElement.
|
||||
_DOMElement.appendChild(_DOMEmbedElement);
|
||||
|
||||
_DOMElement.appendChild(_DOMObjectElement);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFlashMovie:(CPFlashMovie)aFlashMovie
|
||||
{
|
||||
if (_flashMovie == aFlashMovie)
|
||||
return;
|
||||
|
||||
_flashMovie = aFlashMovie;
|
||||
|
||||
_DOMParamElement.value = aFlashMovie._fileName;
|
||||
|
||||
if (_DOMEmbedElement)
|
||||
_DOMEmbedElement.src = aFlashMovie._fileName;
|
||||
}
|
||||
|
||||
- (CPFlashMovie)flashMovie
|
||||
{
|
||||
return _flashMovie;
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[[CPDOMWindowBridge sharedDOMWindowBridge] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[[CPDOMWindowBridge sharedDOMWindowBridge] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[[CPDOMWindowBridge sharedDOMWindowBridge] _propagateCurrentDOMEvent:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* CPFont.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
var _CPFonts = {};
|
||||
_CPFontSystemFontFace = @"Arial";
|
||||
|
||||
#define _CPCachedFont(aName, aSize, isBold) _CPFonts[(isBold ? @"bold " : @"") + ROUND(aSize) + @"px '" + aName + @"'"]
|
||||
|
||||
/*! @class CPFont
|
||||
|
||||
The CPFont class allows control of the fonts used for displaying text anywhere on the screen. The primary method for getting a particular font is through one of the class methods that take a name and/or size as arguments, and return the appropriate CPFont.
|
||||
*/
|
||||
@implementation CPFont : CPObject
|
||||
{
|
||||
CPString _name;
|
||||
float _size;
|
||||
BOOL _isBold;
|
||||
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a font with the specified name and size.
|
||||
@param aName the name of the font
|
||||
@param aSize the size of the font (in points)
|
||||
@return the requested font
|
||||
*/
|
||||
+ (CPFont)fontWithName:(CPString)aName size:(float)aSize
|
||||
{
|
||||
return _CPCachedFont(aName, aSize, NO) || [[CPFont alloc] _initWithName:aName size:aSize bold:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a bold font with the specified name and size.
|
||||
@param aName the name of the font
|
||||
@param aSize the size of the font (in points)
|
||||
@return the requested bold font
|
||||
*/
|
||||
+ (CPFont)boldFontWithName:(CPString)aName size:(float)aSize
|
||||
{
|
||||
return _CPCachedFont(aName, aSize, YES) || [[CPFont alloc] _initWithName:aName size:aSize bold:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the system font scaled to the specified size
|
||||
@param aSize the size of the font (in points)
|
||||
@return the requested system font
|
||||
*/
|
||||
+ (CPFont)systemFontOfSize:(CPSize)aSize
|
||||
{
|
||||
return _CPCachedFont(_CPFontSystemFontFace, aSize, NO) || [[CPFont alloc] _initWithName:_CPFontSystemFontFace size:aSize bold:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the bold system font scaled to the specified size
|
||||
@param aSize the size of the font (in points)
|
||||
@return the requested bold system font
|
||||
*/
|
||||
+ (CPFont)boldSystemFontOfSize:(CPSize)aSize
|
||||
{
|
||||
return _CPCachedFont(_CPFontSystemFontFace, aSize, YES) || [[CPFont alloc] _initWithName:_CPFontSystemFontFace size:aSize bold:YES];
|
||||
}
|
||||
|
||||
/* FIXME Font Descriptor
|
||||
@ignore
|
||||
*/
|
||||
- (id)_initWithName:(CPString)aName size:(float)aSize bold:(BOOL)isBold
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_name = aName;
|
||||
_size = aSize;
|
||||
_isBold = isBold;
|
||||
|
||||
_cssString = (_isBold ? @"bold " : @"") + ROUND(aSize) + @"px '" + aName + @"'";
|
||||
|
||||
_CPFonts[_cssString] = self;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the font size (in points)
|
||||
*/
|
||||
- (float)size
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the font as a CSS string
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
return _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the font's family name
|
||||
*/
|
||||
- (CPString)familyName
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPFontNameKey = @"CPFontNameKey",
|
||||
CPFontSizeKey = @"CPFontSizeKey",
|
||||
CPFontIsBoldKey = @"CPFontIsBoldKey";
|
||||
|
||||
@implementation CPFont (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the font from a coder.
|
||||
@param aCoder the coder from which to read the font data
|
||||
@return the initialized font
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
return [self _initWithName:[aCoder decodeObjectForKey:CPFontNameKey]
|
||||
size:[aCoder decodeFloatForKey:CPFontSizeKey]
|
||||
bold:[aCoder decodeBoolForKey:CPFontIsBoldKey]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the font information out to a coder.
|
||||
@param aCoder the coder to which the data will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_name forKey:CPFontNameKey];
|
||||
[aCoder encodeFloat:_size forKey:CPFontSizeKey];
|
||||
[aCoder encodeBool:_isBold forKey:CPFontIsBoldKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* CPFontManager.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Tom Robinson.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import <AppKit/CPFont.j>
|
||||
|
||||
|
||||
var CPSharedFontManager = nil,
|
||||
CPFontManagerFactory = Nil;
|
||||
|
||||
@implementation CPFontManager : CPObject
|
||||
{
|
||||
CPArray _availableFonts;
|
||||
}
|
||||
|
||||
// Getting the Shared Font Manager
|
||||
/*!
|
||||
Returns the application's font manager. If the font
|
||||
manager does not exist yet, it will be created.
|
||||
*/
|
||||
+ (CPFontManager)sharedFontManager
|
||||
{
|
||||
if (!CPSharedFontManager)
|
||||
CPSharedFontManager = [[CPFontManagerFactory alloc] init];
|
||||
|
||||
return CPSharedFontManager;
|
||||
}
|
||||
|
||||
// Changing the Default Font Conversion Classes
|
||||
/*!
|
||||
Sets the class that will be used to create the applcation's
|
||||
font manager.
|
||||
*/
|
||||
+ (void)setFontManagerFactory:(Class)aClass
|
||||
{
|
||||
CPFontManagerFactory = aClass;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of the available fonts
|
||||
*/
|
||||
- (CPArray)availableFonts
|
||||
{
|
||||
if (!_availableFonts)
|
||||
{
|
||||
_CPFontDetectSpan = document.createElement("span");
|
||||
_CPFontDetectSpan.fontSize = "24px";
|
||||
_CPFontDetectSpan.appendChild(document.createTextNode("mmmmmmmmmml"));
|
||||
var div = document.createElement("div");
|
||||
div.style.position = "absolute";
|
||||
div.style.top = "-1000px";
|
||||
div.appendChild(_CPFontDetectSpan);
|
||||
document.getElementsByTagName("body")[0].appendChild(div);
|
||||
|
||||
_CPFontDetectReferenceFonts = _CPFontDetectPickTwoDifferentFonts(["monospace", "serif", "sans-serif", "cursive"]);
|
||||
|
||||
_availableFonts = [];
|
||||
for (var i = 0; i < _CPFontDetectAllFonts.length; i++) {
|
||||
var available = _CPFontDetectFontAvailable(_CPFontDetectAllFonts[i]);
|
||||
if (available)
|
||||
_availableFonts.push(_CPFontDetectAllFonts[i]);
|
||||
}
|
||||
}
|
||||
return _availableFonts;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the available fonts matching the provided name.
|
||||
@param aFontName the name of the font
|
||||
*/
|
||||
- (CPArray)fontWithNameIsAvailable:(CPString)aFontName
|
||||
{
|
||||
return _CPFontDetectFontAvailable(aFontName);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPFontDetectSpan,
|
||||
_CPFontDetectReferenceFonts,
|
||||
_CPFontDetectAllFonts = [
|
||||
/* "04b_21","A Charming Font","Abadi MT Condensed","Abadi MT Condensed Extra Bold","Abadi MT Condensed Light","Academy Engraved LET","Agency FB","Alba","Alba Matter","Alba Super","Algerian",*/
|
||||
"American Typewriter",
|
||||
/* "Andale Mono","Andale Mono IPA","Andy", */
|
||||
"Apple Chancery","Arial","Arial Black","Arial Narrow","Arial Rounded MT Bold","Arial Unicode MS",
|
||||
/* "Avant Garde","Avantgarde","Baby Kruffy","Base 02","Baskerville","Baskerville Old Face","Bauhaus 93","Beesknees ITC","Bell MT","Berlin Sans FB","Berlin Sans FB Demi","Bernard MT Condensed","Bickley Script",*/
|
||||
"Big Caslon","Bitstream Vera Sans","Bitstream Vera Sans Mono","Bitstream Vera Serif",
|
||||
/* "Blackadder ITC","Blackletter686 BT","Bodoni MT","Bodoni MT Black","Bodoni MT Condensed","Bodoni MT Poster Compressed","Book Antiqua","Bookman","Bookman Old Style","Bradley Hand ITC","Braggadocio","Britannic Bold","Broadway","Broadway BT",*/
|
||||
"Brush Script MT",
|
||||
/* "BudHand","CAMPBELL","Calibri","Californian FB","Calisto MT","Calligraph421 BT",*/
|
||||
"Cambria",
|
||||
/* "Candara","Capitals",*/
|
||||
"Caslon","Castellar","Cataneo BT","Centaur","Century Gothic","Century Schoolbook","Century Schoolbook L",
|
||||
/* "Champignon","Charcoal","Charter","Charter BT","Chicago","Chick","Chiller","ClearlyU","Colonna MT",*/
|
||||
"Comic Sans", "Comic Sans MS","Consolas","Constantia","Cooper Black","Copperplate","Copperplate Gothic Bold","Copperplate Gothic Light","Corbel","Courier","Courier New",
|
||||
/* "Croobie","Curlz MT","Desdemona","Didot","DomBold BT","Edwardian Script ITC","Engravers MT","Eras Bold ITC","Eras Demi ITC","Eras Light ITC","Eras Medium ITC","Eurostile","FIRSTHOME","Fat","Felix Titling","Fine Hand","Fixed","Footlight MT Light","Forte","Franklin Gothic Book","Franklin Gothic Demi","Franklin Gothic Demi Cond","Franklin Gothic Heavy","Franklin Gothic Medium","Franklin Gothic Medium Cond","Freestyle Script","French Script MT","Freshbot","Frosty",*/
|
||||
"Futura",
|
||||
/* "GENUINE","Gadget","Garamond",*/
|
||||
"Geneva","Georgia","Georgia Ref", "Geeza Pro", "Gigi","Gill Sans","Gill Sans MT","Gill Sans MT Condensed","Gill Sans MT Ext Condensed Bold","Gill Sans Ultra Bold","Gill Sans Ultra Bold Condensed",
|
||||
/* "GlooGun","Gloucester MT Extra Condensed","Goudy Old Style","Goudy Stout","Haettenschweiler","Harlow Solid Italic","Harrington",*/
|
||||
"Helvetica","Helvetica Narrow","Helvetica Neue","Herculanum","High Tower Text","Highlight LET","Hoefler Text","Impact","Imprint MT Shadow",
|
||||
/* "Informal Roman","Jenkins v2.0","John Handy LET","Jokerman","Jokerman LET","Jokewood","Juice ITC","Kabel Ult BT","Kartika","Kino MT","Kristen ITC","Kunstler Script","La Bamba LET", */
|
||||
"Lucida","Lucida Bright","Lucida Calligraphy","Lucida Console","Lucida Fax","Lucida Grande","Lucida Handwriting","Lucida Sans","Lucida Sans Typewriter","Lucida Sans Unicode",
|
||||
/* "Luxi Mono","Luxi Sans","Luxi Serif","MARKETPRO","MS Reference Sans Serif","MS Reference Serif","Magneto","Maiandra GD", */
|
||||
"Marker Felt",
|
||||
/* "Matisse ITC","Matura MT Script Capitals","Mead Bold","Mekanik LET","Mercurius Script MT Bold", */
|
||||
"Microsoft Sans Serif","Milano LET","Minion Web","MisterEarl BT","Mistral","Monaco","Monotype Corsiva","Monotype.com","New Century Schoolbook","New York","News Gothic MT",
|
||||
/* "Niagara Engraved","Niagara Solid","Nimbus Mono L","Nimbus Roman No9 L","OCR A Extended","OCRB","Odessa LET","Old English Text MT","OldDreadfulNo7 BT","One Stroke Script LET","Onyx","Optima","Orange LET","Palace Script MT","Palatino","Palatino Linotype", */
|
||||
"Papyrus",
|
||||
/* "ParkAvenue BT","Pepita MT","Perpetua","Perpetua Titling MT","Placard Condensed","Playbill","Poornut","Pristina","Pump Demi Bold LET","Pussycat","Quixley LET","Rage Italic","Rage Italic LET","Ravie","Rockwell","Rockwell Condensed","Rockwell Extra Bold","Ruach LET","Runic MT Condensed","Sand","Script MT Bold","Scruff LET","Segoe UI","Showcard Gothic","Skia","Smudger LET","Snap ITC","Square721 BT","Staccato222 BT","Stencil","Sylfaen", */
|
||||
"Tahoma","Techno","Tempus Sans ITC","Terminal","Textile","Times","Times New Roman","Tiranti Solid LET","Trebuchet MS",
|
||||
/* "Tw Cen MT","Tw Cen MT Condensed","Tw Cen MT Condensed Extra Bold","URW Antiqua T","URW Bookman L","URW Chancery L","URW Gothic L","URW Palladio L","Univers","University Roman LET","Utopia", */
|
||||
"Verdana","Verdana Ref", /* "Victorian LET","Viner Hand ITC","Vivaldi","Vladimir Script","Vrinda","Weltron Urban","Westwood LET","Wide Latin","Zapf Chancery", */
|
||||
"Zapfino"];
|
||||
|
||||
// Compare against the reference fonts. Return true if it produces a different size than at least one of them.
|
||||
var _CPFontDetectFontAvailable = function(font) {
|
||||
for (var i = 0; i < _CPFontDetectReferenceFonts.length; i++)
|
||||
if (_CPFontDetectCompareFonts(_CPFontDetectReferenceFonts[i], font))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
var _CPFontDetectCache = {};
|
||||
|
||||
// Compares two given fonts. Returns true if they produce different sizes (i.e. fontA didn't fallback to fontB)
|
||||
var _CPFontDetectCompareFonts = function(fontA, fontB) {
|
||||
var a;
|
||||
if (_CPFontDetectCache[fontA]) {
|
||||
a = _CPFontDetectCache[fontA];
|
||||
} else {
|
||||
_CPFontDetectSpan.style.fontFamily = '"' + fontA + '"';
|
||||
_CPFontDetectCache[fontA] = a = { w: _CPFontDetectSpan.offsetWidth, h: _CPFontDetectSpan.offsetHeight };
|
||||
}
|
||||
|
||||
_CPFontDetectSpan.style.fontFamily= '"' + fontB + '", "' + fontA + '"';
|
||||
var bWidth = _CPFontDetectSpan.offsetWidth;
|
||||
var bHeight = _CPFontDetectSpan.offsetHeight;
|
||||
|
||||
return (a.w != bWidth || a.h != bHeight);
|
||||
}
|
||||
|
||||
// Test the candidate fonts pairwise until we find two that are different. Otherwise return the first.
|
||||
var _CPFontDetectPickTwoDifferentFonts = function(candidates) {
|
||||
for (var i = 0; i < candidates.length; i++)
|
||||
for (var j = 0; j < i; j++)
|
||||
if (_CPFontDetectCompareFonts(candidates[i], candidates[j]))
|
||||
return [candidates[i], candidates[j]];
|
||||
return [candidates[0]];
|
||||
}
|
||||
|
||||
[CPFontManager setFontManagerFactory:[CPFontManager class]];
|
||||
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
* CPGeometry.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CGGeometry.j"
|
||||
|
||||
|
||||
CPMinXEdge = 0;
|
||||
CPMinYEdge = 1;
|
||||
CPMaxXEdge = 2;
|
||||
CPMaxYEdge = 3;
|
||||
|
||||
// FIXME: the rest!
|
||||
CPMakePoint = CGPointMake;
|
||||
CPMakeSize = CGSizeMake;
|
||||
CPMakeRect = CGRectMake;
|
||||
|
||||
/*!
|
||||
Creates a copy of a specified point and returns the copy
|
||||
@group CGPoint
|
||||
@param the point to be copied
|
||||
@return CGPoint the copy of the provided CGPoint
|
||||
*/
|
||||
function CPPointCreateCopy(aPoint)
|
||||
{
|
||||
return { x: aPoint.x, y: aPoint.y };
|
||||
}
|
||||
|
||||
/*!
|
||||
Makes a CGPoint object out of two numbers provided as arguments
|
||||
@group CGPoint
|
||||
@param x the x-coordinate of the CGPoint
|
||||
@param y the y-coordinate of the CGPoint
|
||||
@return CGPoint a CGPoint with an X and Y coordinate equal to the function arguments
|
||||
*/
|
||||
function CPPointMake(x, y)
|
||||
{
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
/*!
|
||||
Makes a CGRect with an origin and size equal to <code>aRect</code> less the <code>dX/dY</code> insets specified.
|
||||
@param dX the size of the inset in the x-axis
|
||||
@param dY the size of the inset in the y-axis
|
||||
@group CGRect
|
||||
@return CGRect a rectangle like <code>aRect</code> with an inset
|
||||
*/
|
||||
function CPRectInset(aRect, dX, dY)
|
||||
{
|
||||
return CPRectMake( aRect.origin.x + dX, aRect.origin.y + dY,
|
||||
aRect.size.width - 2 * dX, aRect.size.height - 2*dY);
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@ignore
|
||||
@return void
|
||||
@deprecated
|
||||
*/
|
||||
function CPRectIntegral(aRect)
|
||||
{
|
||||
// FIXME!!!
|
||||
alert("CPRectIntegral unimplemented");
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the intersection of the two provided rectangles as a new rectangle
|
||||
@group CGRect
|
||||
@param lhsRect the first rectangle used for calculation
|
||||
@param rhsRect the second rectangle used for calculation
|
||||
@return CGRect the intersection of the two rectangles
|
||||
*/
|
||||
function CPRectIntersection(lhsRect, rhsRect)
|
||||
{
|
||||
var intersection = CPRectMake(
|
||||
Math.max(CPRectGetMinX(lhsRect), CPRectGetMinX(rhsRect)),
|
||||
Math.max(CPRectGetMinY(lhsRect), CPRectGetMinY(rhsRect)),
|
||||
0, 0);
|
||||
|
||||
intersection.size.width = Math.min(CPRectGetMaxX(lhsRect), CPRectGetMaxX(rhsRect)) - CPRectGetMinX(intersection);
|
||||
intersection.size.height = Math.min(CPRectGetMaxY(lhsRect), CPRectGetMaxY(rhsRect)) - CPRectGetMinY(intersection);
|
||||
|
||||
return CPRectIsEmpty(intersection) ? CPRectMakeZero() : intersection;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a copy of the provided rectangle
|
||||
@group CGRect
|
||||
@param aRect the CGRect that will be copied
|
||||
@return CGRect the rectangle copy
|
||||
*/
|
||||
function CPRectCreateCopy(aRect)
|
||||
{
|
||||
return { origin: CPPointCreateCopy(aRect.origin), size: CPSizeCreateCopy(aRect.size) };
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a CGRect made of the specified arguments
|
||||
@group CGRect
|
||||
@param x the x-coordinate of the rectangle's origin
|
||||
@param y the y-coordinate of the rectangle's origin
|
||||
@param width the width of the new rectangle
|
||||
@param height the height of the new rectangle
|
||||
@return CGRect the new rectangle
|
||||
*/
|
||||
function CPRectMake(x, y, width, height)
|
||||
{
|
||||
return { origin: CPPointMake(x, y), size: CPSizeMake(width, height) };
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new rectangle with its origin offset by <code>dX</code> and <code>dY</code>.
|
||||
@group CGRect
|
||||
@param aRect the rectangle to copy the origin and size from
|
||||
@param dX the amount added to the x-size of the new rectangle
|
||||
@param dY the amount added to the y-size of the new rectangle
|
||||
@return CGRect the new rectangle with modified size
|
||||
*/
|
||||
function CPRectOffset(aRect, dX, dY)
|
||||
{
|
||||
return CPRectMake(aRect.origin.x + dX, aRect.origin.y + dY, aRect.size.width, aRect.size.height);
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return CGRect
|
||||
*/
|
||||
function CPRectStandardize(aRect)
|
||||
{
|
||||
var width = CPRectGetWidth(aRect),
|
||||
height = CPRectGetHeight(aRect),
|
||||
standardized = CPRectCreateCopy(aRect);
|
||||
|
||||
if (width < 0.0)
|
||||
{
|
||||
standardized.origin.x += width;
|
||||
standardized.size.width = -width;
|
||||
}
|
||||
|
||||
if (height < 0.0)
|
||||
{
|
||||
standardized.origin.y += height;
|
||||
standardized.size.height = -height;
|
||||
}
|
||||
|
||||
return standardized;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the smallest rectangle that can contain the two argument <code>CGRect</code>s.
|
||||
@group CGRect
|
||||
@param lhsRect the first CGRect to use for the union calculation
|
||||
@param rhsRect the second CGRect to use for the union calculation
|
||||
@return CGRect the union rectangle
|
||||
*/
|
||||
function CPRectUnion(lhsRect, rhsRect)
|
||||
{
|
||||
var minX = Math.min(CPRectGetMinX(lhsRect), CPRectGetMinX(rhsRect)),
|
||||
minY = Math.min(CPRectGetMinY(lhsRect), CPRectGetMinY(rhsRect)),
|
||||
maxX = Math.max(CPRectGetMaxX(lhsRect), CPRectGetMaxX(rhsRect)),
|
||||
maxY = Math.max(CPRectGetMaxY(lhsRect), CPRectGetMaxY(rhsRect));
|
||||
|
||||
return CPRectMake(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a copy of the provided CGSize
|
||||
@group CGSize
|
||||
@param aSize the CGSize to copy
|
||||
@return CGSize the copy of the CGSize
|
||||
*/
|
||||
function CPSizeCreateCopy(aSize)
|
||||
{
|
||||
return { width: aSize.width, height: aSize.height };
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a new CGSize object from the provided dimensions.
|
||||
@group CGSize
|
||||
@param width the width for the new CGSize
|
||||
@param height the height for the new CGSize
|
||||
@return CGSize the new CGSize
|
||||
*/
|
||||
function CPSizeMake(width, height)
|
||||
{
|
||||
return { width: width, height: height };
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the CGRect, <code>aRect</code>, contains
|
||||
the CGPoint, <code>aPoint</code>.
|
||||
@param aRect the rectangle to test with
|
||||
@param aPoint the point to test with
|
||||
@group CGRect
|
||||
@return BOOL <code>YES</code> if the rectangle contains the point, <code>NO</code> otherwise.
|
||||
*/
|
||||
function CPRectContainsPoint(aRect, aPoint)
|
||||
{
|
||||
return aPoint.x >= CPRectGetMinX(aRect) &&
|
||||
aPoint.y >= CPRectGetMinY(aRect) &&
|
||||
aPoint.x < CPRectGetMaxX(aRect) &&
|
||||
aPoint.y < CPRectGetMaxY(aRect);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a <code>BOOL</code> indicating whether CGRect <code>possibleOuter</code>
|
||||
contains CGRect <code>possibleInner</code>.
|
||||
@group CGRect
|
||||
@param possibleOuter the CGRect to test if <code>possibleInner</code> is inside of
|
||||
@param possibleInner the CGRect to test if it fits inside <code>possibleOuter</code>.
|
||||
@return BOOL <code>YES</code> if <code>possibleInner</code> fits inside <code>possibleOuter</code>.
|
||||
*/
|
||||
function CPRectContainsRect(lhsRect, rhsRect)
|
||||
{
|
||||
return CPRectEqualToRect(CPUnionRect(lhsRect, rhsRect), rhsRect);
|
||||
}
|
||||
|
||||
/*!
|
||||
Tests whether the two CGPoints are equal to each other by comparing their
|
||||
<code>x</code> and <code>y</code> members.
|
||||
@group @CGPoint
|
||||
@param lhsPoint the first CGPoint to check
|
||||
@param rhsPoint the second CGPoint to check
|
||||
@return BOOL <code>YES</code> if the two points have the same x's, and the same y's.
|
||||
*/
|
||||
function CPPointEqualToPoint(lhsPoint, rhsPoint)
|
||||
{
|
||||
return lhsPoint.x == rhsPoint.x && lhsPoint.y == rhsPoint.y;
|
||||
}
|
||||
|
||||
/*!
|
||||
Test whether the two CGRects have the same origin and size
|
||||
@group CGRect
|
||||
@param lhsRect the first CGRect to compare
|
||||
@param rhsRect the second CGRect to compare
|
||||
@return BOOL <code>YES</code> if the two rectangles have the same origin and size. <code>NO</code>, otherwise.
|
||||
*/
|
||||
function CPRectEqualToRect(lhsRect, rhsRect)
|
||||
{
|
||||
return CPPointEqualToPoint(lhsRect.origin, rhsRect.origin) &&
|
||||
CPSizeEqualToSize(lhsRect.size, rhsRect.size);
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return int
|
||||
*/
|
||||
function CPRectGetHeight(aRect)
|
||||
{
|
||||
return aRect.size.height;
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return int
|
||||
*/
|
||||
function CPRectGetMaxX(aRect)
|
||||
{
|
||||
return aRect.origin.x + aRect.size.width;
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return int
|
||||
*/
|
||||
function CPRectGetMaxY(aRect)
|
||||
{
|
||||
return aRect.origin.y + aRect.size.height;
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return float
|
||||
*/
|
||||
function CPRectGetMidX(aRect)
|
||||
{
|
||||
return aRect.origin.x + (aRect.size.width) / 2.0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return float
|
||||
*/
|
||||
function CPRectGetMidY(aRect)
|
||||
{
|
||||
return aRect.origin.y + (aRect.size.height) / 2.0;
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return int
|
||||
*/
|
||||
function CPRectGetMinX(aRect)
|
||||
{
|
||||
return aRect.origin.x;
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return int
|
||||
*/
|
||||
function CPRectGetMinY(aRect)
|
||||
{
|
||||
return aRect.origin.y;
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGRect
|
||||
@param aRect a CGRect
|
||||
@return int
|
||||
*/
|
||||
function CPRectGetWidth(aRect)
|
||||
{
|
||||
return aRect.size.width;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the two rectangles intersect
|
||||
@group CGRect
|
||||
@param lhsRect the first CGRect
|
||||
@param rhsRect the second CGRect
|
||||
@return BOOL <code>YES</code> if the two rectangles have any common spaces, and <code>NO</code>, otherwise.
|
||||
*/
|
||||
function CPRectIntersectsRect(lhsRect, rhsRect)
|
||||
{
|
||||
return !CPRectIsEmpty(CPRectIntersection(lhsRect, rhsRect));
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the CGRect has no area.
|
||||
The test is performed by checking if the width and height are both zero.
|
||||
@group CGRect
|
||||
@param aRect the CGRect to test
|
||||
@return BOOL <code>YES</code> if the CGRect has no area, and <code>NO</code>, otherwise.
|
||||
*/
|
||||
function CPRectIsEmpty(aRect)
|
||||
{
|
||||
return aRect.size.width <= 0.0 || aRect.size.height <= 0.0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the CGRect has no area.
|
||||
The test is performed by checking if the width and height are both zero.
|
||||
@group CGRect
|
||||
@return BOOL <code>YES</code> if the CGRect has no area, and <code>NO</code>, otherwise.
|
||||
*/
|
||||
function CPRectIsNull(aRect)
|
||||
{
|
||||
return aRect.size.width <= 0.0 || aRect.size.height <= 0.0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the two CGSizes are identical.
|
||||
@group CGSize
|
||||
@param lhsSize the first CGSize to compare
|
||||
@param rhsSize the second CGSize to compare
|
||||
@return BOOL <code>YES</code> if the two sizes are identical. <code>NO</code>, otherwise.
|
||||
*/
|
||||
function CPSizeEqualToSize(lhsSize, rhsSize)
|
||||
{
|
||||
return lhsSize.width == rhsSize.width && lhsSize.height == rhsSize.height;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a human readable string of the provided CGPoint.
|
||||
@group CGPoint
|
||||
@param aPoint the point to represent
|
||||
@return CGPoint a string representation of the CGPoint
|
||||
*/
|
||||
function CPStringFromPoint(aPoint)
|
||||
{
|
||||
return "{" + aPoint.x + ", " + aPoint.y + "}";
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a human readable string of the provided CGSize.
|
||||
@group CGSize
|
||||
@param aSize the size to represent
|
||||
@return CGSize a string representation of the CGSize
|
||||
*/
|
||||
function CPStringFromSize(aSize)
|
||||
{
|
||||
return "{" + aSize.width + ", " + aSize.height + "}";
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a human readable string of the provided CGRect.
|
||||
@group CGRect
|
||||
@param aRect the rectangle to represent
|
||||
@return CGString the string representation of the rectangle
|
||||
*/
|
||||
function CPStringFromRect(aRect)
|
||||
{
|
||||
return "{" + CPStringFromPoint(aRect.origin) + ", " + CPStringFromSize(aRect.size) + "}";
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a CGPoint from a string with a comma separated pair of integers.
|
||||
@group CGPoint
|
||||
@param aString a string containing two comma separated integers
|
||||
@return CGPoint the point object created from the string
|
||||
*/
|
||||
function CPPointFromString(aString)
|
||||
{
|
||||
var comma = aString.indexOf(',');
|
||||
|
||||
return { x:parseFloat(aString.substr(1, comma - 1), 10), y:parseFloat(aString.substring(comma + 1, aString.length), 10) };
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a CGSize from a string containing a pair of comma separated integers.
|
||||
@group CGSize
|
||||
@param aString a string containing two comma separated integers
|
||||
@return CGSize the size object created from the string
|
||||
*/
|
||||
function CPSizeFromString(aString)
|
||||
{
|
||||
var comma = aString.indexOf(',');
|
||||
|
||||
return { width:parseFloat(aString.substr(1, comma - 1), 10), height:parseFloat(aString.substring(comma + 1, aString.length), 10) };
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a CGRect created from a string.
|
||||
@group CGRect
|
||||
@param aString a string in the form generated by <code>CPStringFromRect</code>
|
||||
@return CGRect the rectangle created from the string
|
||||
*/
|
||||
function CPRectFromString(aString)
|
||||
{
|
||||
var comma = aString.indexOf(',', aString.indexOf(',') + 1);
|
||||
|
||||
return { origin:CPPointFromString(aString.substr(1, comma - 1)), size:CPSizeFromString(aString.substring(comma + 2, aString.length)) };
|
||||
}
|
||||
|
||||
/*!
|
||||
@group CGPoint
|
||||
@param anEvent
|
||||
@return CGPoint
|
||||
*/
|
||||
function CPPointFromEvent(anEvent)
|
||||
{
|
||||
return CPPointMake(anEvent.clientX, anEvent.clientY, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a zero sized CGSize.
|
||||
@group CGSize
|
||||
@return CGSize a size object with zeros for <code>width</code> and <code>height</code>
|
||||
*/
|
||||
function CPSizeMakeZero()
|
||||
{
|
||||
return CPSizeMake(0, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a rectangle at origin <code>(0,0)</code> and size of <code>(0,0)</code>.
|
||||
@group CGRect
|
||||
@return CGRect a zeroed out CGRect
|
||||
*/
|
||||
function CPRectMakeZero()
|
||||
{
|
||||
return CPRectMake(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a point located at <code>(0, 0)</code>.
|
||||
@group CGPoint
|
||||
@return CGPoint a point located at <code>(0, 0)</code>
|
||||
*/
|
||||
function CPPointMakeZero()
|
||||
{
|
||||
return CPPointMake(0, 0, 0);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* CPGraphicsContext.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
var CPGraphicsContextCurrent = nil;
|
||||
|
||||
/*!
|
||||
Provides an interface for drawing to the screen.
|
||||
*/
|
||||
@implementation CPGraphicsContext : CPObject
|
||||
{
|
||||
CPContext _graphicsPort;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current graphics context.
|
||||
*/
|
||||
+ (CPGraphicsContext)currentContext
|
||||
{
|
||||
return CPGraphicsContextCurrent;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current graphics context
|
||||
*/
|
||||
+ (void)setCurrentContext:(CPGraphicsContext)aGraphicsContext
|
||||
{
|
||||
CPGraphicsContextCurrent = aGraphicsContext;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a graphics context with a provided port.
|
||||
@param aContext the context to initialize with
|
||||
@param aFlag whether the context should be flipped
|
||||
@return the initialized graphics context
|
||||
*/
|
||||
+ (CPGraphicsContext)graphicsContextWithGraphicsPort:(CGContext)aContext flipped:(BOOL)aFlag
|
||||
{
|
||||
return [[self alloc] initWithGraphicsPort:aContext];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the context with a graphics port.
|
||||
@param aGraphicsPort the graphics port to initialize with
|
||||
@return the initialized context
|
||||
*/
|
||||
- (id)initWithGraphicsPort:(CPContext)aGraphicsPort
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_graphicsPort = aGraphicsPort;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the graphics context's port.
|
||||
*/
|
||||
- (CGContext)graphicsPort
|
||||
{
|
||||
return _graphicsPort;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
* CPImage.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPBundle.j>
|
||||
@import <Foundation/CPNotificationCenter.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPRunLoop.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPGeometry.j"
|
||||
|
||||
CPImageLoadStatusInitialized = 0;
|
||||
CPImageLoadStatusLoading = 1;
|
||||
CPImageLoadStatusCompleted = 2;
|
||||
CPImageLoadStatusCancelled = 3;
|
||||
CPImageLoadStatusInvalidData = 4;
|
||||
CPImageLoadStatusUnexpectedEOF = 5;
|
||||
CPImageLoadStatusReadError = 6;
|
||||
|
||||
CPImageDidLoadNotification = @"CPImageDidLoadNotification";
|
||||
|
||||
@implementation CPBundle (CPImageAdditions)
|
||||
|
||||
- (CPString)pathForResource:(CPString)aFilename
|
||||
{
|
||||
return [self resourcePath] + '/' + aFilename;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*! @class CPImage
|
||||
|
||||
CPImage is used to represent images in the Cappuccino framework. It supports loading
|
||||
all image types supported by the browser.
|
||||
|
||||
@par Delegate Methods
|
||||
|
||||
@delegate -(void)imageDidLoad:(CPImage)image;
|
||||
Called when the specified image has finished loading.
|
||||
@param image the image that loaded
|
||||
|
||||
@delegate -(void)imageDidError:(CPImage)image;
|
||||
Called when the specified image had an error loading.
|
||||
@param image the image with the loading error
|
||||
|
||||
@delegate -(void)imageDidAbort:(CPImage)image;
|
||||
Called when the image loading was aborted.
|
||||
@param image the image that was aborted
|
||||
*/
|
||||
@implementation CPImage : CPObject
|
||||
{
|
||||
CGSize _size;
|
||||
CPString _filename;
|
||||
|
||||
id _delegate;
|
||||
unsigned _loadStatus;
|
||||
|
||||
Image _image;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the image, by associating it with a filename. The image
|
||||
denoted in <code>aFilename</code> is not actually loaded. It will
|
||||
be loaded once needed.
|
||||
@param aFilename the file containing the image
|
||||
@param aSize the image's size
|
||||
@return the initialized image
|
||||
*/
|
||||
- (CPImage)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_size = CPSizeCreateCopy(aSize);
|
||||
_filename = aFilename;
|
||||
_loadStatus = CPImageLoadStatusInitialized;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the image. Loads the specified image into memory.
|
||||
@param aFilename the image to load
|
||||
@param aSize the size of the image
|
||||
@return the initialized image.
|
||||
*/
|
||||
- (CPImage)initWithContentsOfFile:(CPString)aFilename size:(CGSize)aSize
|
||||
{
|
||||
self = [self initByReferencingFile:aFilename size:aSize];
|
||||
|
||||
if (self)
|
||||
[self load];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the receiver with the contents of the specified
|
||||
image file. The method loads the data into memory.
|
||||
@param aFilename the file name of the image
|
||||
@return the initialized image
|
||||
*/
|
||||
- (CPImage)initWithContentsOfFile:(CPString)aFilename
|
||||
{
|
||||
self = [self initByReferencingFile:aFilename size: CGSizeMake(-1, -1)];
|
||||
|
||||
if (self)
|
||||
[self load];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the path of the file associated with this image.
|
||||
*/
|
||||
- (CPString)filename
|
||||
{
|
||||
return _filename;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the size of the image.
|
||||
@param the size of the image
|
||||
*/
|
||||
- (void)setSize:(CGSize)aSize
|
||||
{
|
||||
_size = CGSizeMakeCopy(aSize);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the size of the image
|
||||
*/
|
||||
- (CGSize)size
|
||||
{
|
||||
return _size;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver's delegate.
|
||||
@param the delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's delegate
|
||||
*/
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the image data has already been loaded.
|
||||
*/
|
||||
- (BOOL)loadStatus
|
||||
{
|
||||
return _loadStatus;
|
||||
}
|
||||
|
||||
/*!
|
||||
Loads the image data from the file into memory. You
|
||||
should not call this method directly. Instead use
|
||||
one of the initializers.
|
||||
*/
|
||||
|
||||
- (void)load
|
||||
{
|
||||
if (_loadStatus == CPImageLoadStatusLoading || _loadStatus == CPImageLoadStatusCompleted)
|
||||
return;
|
||||
|
||||
_loadStatus = CPImageLoadStatusLoading;
|
||||
|
||||
_image = new Image();
|
||||
|
||||
var isSynchronous = YES;
|
||||
|
||||
// FIXME: We need a better/performance way of doing this.
|
||||
_image.onload = function ()
|
||||
{
|
||||
if (isSynchronous)
|
||||
window.setTimeout(function() { [self _imageDidLoad]; }, 0);
|
||||
else
|
||||
{
|
||||
[self _imageDidLoad];
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
}
|
||||
|
||||
_image.onerror = function ()
|
||||
{
|
||||
if (isSynchronous)
|
||||
window.setTimeout(function() { [self _imageDidError]; }, 0);
|
||||
else
|
||||
{
|
||||
[self _imageDidError];
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
}
|
||||
|
||||
_image.onabort = function ()
|
||||
{
|
||||
if (isSynchronous)
|
||||
window.setTimeout(function() { [self _imageDidAbort]; }, 0);
|
||||
else
|
||||
{
|
||||
[self _imageDidAbort];
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
}
|
||||
|
||||
_image.src = _filename;
|
||||
|
||||
isSynchronous = NO;
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isNinePartImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_imageDidLoad
|
||||
{
|
||||
_loadStatus = CPImageLoadStatusCompleted;
|
||||
|
||||
// FIXME: IE is wrong on image sizes????
|
||||
if (!_size || (_size.width == -1 && _size.height == -1))
|
||||
_size = CGSizeMake(_image.width, _image.height);
|
||||
|
||||
[[CPNotificationCenter defaultCenter]
|
||||
postNotificationName:CPImageDidLoadNotification
|
||||
object:self];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidLoad:)])
|
||||
[_delegate imageDidLoad:self];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_imageDidError
|
||||
{
|
||||
_loadStatus = CPImageLoadStatusReadError;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidError:)])
|
||||
[_delegate imageDidError:self];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_imageDidAbort
|
||||
{
|
||||
_loadStatus = CPImageLoadStatusCancelled;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(imageDidAbort:)])
|
||||
[_delegate imageDidAbort:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPImage (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the image with data from a coder.
|
||||
@param aCoder the coder from which to read the image data
|
||||
@return the initialized image
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
return [self initWithContentsOfFile:[aCoder decodeObjectForKey:@"CPFilename"] size:[aCoder decodeSizeForKey:@"CPSize"]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the image data from memory into the coder.
|
||||
@param aCoder the coder to which the data will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_filename forKey:@"CPFilename"];
|
||||
[aCoder encodeSize:_size forKey:@"CPSize"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPThreePartImage : CPObject
|
||||
{
|
||||
CPArray _imageSlices;
|
||||
BOOL _isVertical;
|
||||
}
|
||||
|
||||
- (id)initWithImageSlices:(CPArray)imageSlices isVertical:(BOOL)isVertical
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_imageSlices = imageSlices;
|
||||
_isVertical = isVertical;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPArray)imageSlices
|
||||
{
|
||||
return _imageSlices;
|
||||
}
|
||||
|
||||
- (BOOL)isVertical
|
||||
{
|
||||
return _isVertical;
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)isNinePartImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPThreePartImageImageSlicesKey = @"CPThreePartImageImageSlicesKey",
|
||||
CPThreePartImageIsVerticalKey = @"CPThreePartImageIsVerticalKey";
|
||||
|
||||
@implementation CPThreePartImage (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_imageSlices = [aCoder decodeObjectForKey:CPThreePartImageImageSlicesKey];
|
||||
_isVertical = [aCoder decodeBoolForKey:CPThreePartImageIsVerticalKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_imageSlices forKey:CPThreePartImageImageSlicesKey];
|
||||
[aCoder encodeBool:_isVertical forKey:CPThreePartImageIsVerticalKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPNinePartImage : CPObject
|
||||
{
|
||||
CPArray _imageSlices;
|
||||
}
|
||||
|
||||
- (id)initWithImageSlices:(CPArray)imageSlices
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_imageSlices = imageSlices;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPArray)imageSlices
|
||||
{
|
||||
return _imageSlices;
|
||||
}
|
||||
|
||||
- (BOOL)isThreePartImage
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isNinePartImage
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
|
||||
|
||||
@implementation CPNinePartImage (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_imageSlices = [aCoder decodeObjectForKey:CPNinePartImageImageSlicesKey];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_imageSlices forKey:CPNinePartImageImageSlicesKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* CPImageView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPNotificationCenter.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPShadowView.j"
|
||||
|
||||
#include "Platform/Platform.h"
|
||||
#include "Platform/DOM/CPDOMDisplayServer.h"
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPImageScaling
|
||||
*/
|
||||
CPScaleProportionally = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPImageScaling
|
||||
*/
|
||||
CPScaleToFit = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPImageScaling
|
||||
*/
|
||||
CPScaleNone = 2;
|
||||
|
||||
|
||||
var CPImageViewShadowBackgroundColor = nil;
|
||||
|
||||
var LEFT_SHADOW_INSET = 3.0,
|
||||
RIGHT_SHADOW_INSET = 3.0,
|
||||
TOP_SHADOW_INSET = 3.0,
|
||||
BOTTOM_SHADOW_INSET = 5.0,
|
||||
VERTICAL_SHADOW_INSET = TOP_SHADOW_INSET + BOTTOM_SHADOW_INSET,
|
||||
HORIZONTAL_SHADOW_INSET = LEFT_SHADOW_INSET + RIGHT_SHADOW_INSET;
|
||||
|
||||
/*! @class CPImageView
|
||||
|
||||
This class is a control that displays an image.
|
||||
*/
|
||||
@implementation CPImageView : CPControl
|
||||
{
|
||||
CPImage _image;
|
||||
DOMElement _DOMImageElement;
|
||||
|
||||
CPImageScaling _imageScaling;
|
||||
|
||||
BOOL _hasShadow;
|
||||
CPView _shadowView;
|
||||
|
||||
CGRect _imageRect;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMImageElement = document.createElement("img");
|
||||
_DOMImageElement.style.position = "absolute";
|
||||
_DOMImageElement.style.left = "0px";
|
||||
_DOMImageElement.style.top = "0px";
|
||||
|
||||
CPDOMDisplayServerAppendChild(_DOMElement, _DOMImageElement);
|
||||
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the view's image.
|
||||
*/
|
||||
- (CPImage)image
|
||||
{
|
||||
return _image;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the image for the view.
|
||||
@param anImage the view's image
|
||||
*/
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
if (_image == anImage)
|
||||
return;
|
||||
|
||||
var center = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_image)
|
||||
[center removeObserver:self name:CPImageDidLoadNotification object:_image];
|
||||
|
||||
_image = anImage;
|
||||
_DOMImageElement.src = [anImage filename];
|
||||
|
||||
var size = [_image size];
|
||||
|
||||
if (size && size.width == -1 && size.height == -1)
|
||||
{
|
||||
[center addObserver:self selector:@selector(imageDidLoad:) name:CPImageDidLoadNotification object:_image];
|
||||
|
||||
_DOMImageElement.width = 0;
|
||||
_DOMImageElement.height = 0;
|
||||
|
||||
[_shadowView setHidden:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self hideOrDisplayContents];
|
||||
[self tile];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)imageDidLoad:(CPNotification)aNotification
|
||||
{
|
||||
[self hideOrDisplayContents];
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the image view draws with
|
||||
a drop shadow. The default is <code>NO</code>.
|
||||
*/
|
||||
- (BOOL)hasShadow
|
||||
{
|
||||
return _hasShadow;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the image view should draw with a drop shadow.
|
||||
@param shouldHaveShadow whether the image view should have a shadow
|
||||
*/
|
||||
- (void)setHasShadow:(BOOL)shouldHaveShadow
|
||||
{
|
||||
if (_hasShadow == shouldHaveShadow)
|
||||
return;
|
||||
|
||||
_hasShadow = shouldHaveShadow;
|
||||
|
||||
if (_hasShadow)
|
||||
{
|
||||
_shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
|
||||
|
||||
[self addSubview:_shadowView];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_shadowView removeFromSuperview];
|
||||
|
||||
_shadowView = nil;
|
||||
}
|
||||
|
||||
[self hideOrDisplayContents];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the type of image scaling that should be used to
|
||||
render the image.
|
||||
@param anImageScaling the type of scaling to use
|
||||
*/
|
||||
- (void)setImageScaling:(CPImageScaling)anImageScaling
|
||||
{
|
||||
if (_imageScaling == anImageScaling)
|
||||
return;
|
||||
|
||||
_imageScaling = anImageScaling;
|
||||
|
||||
if (_imageScaling == CPScaleToFit)
|
||||
{
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, 0.0, 0.0);
|
||||
}
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the image scaling method used to
|
||||
render this image.
|
||||
*/
|
||||
- (CPImageScaling)imageScaling
|
||||
{
|
||||
return _imageScaling;
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*!
|
||||
Toggles the display of the image view.
|
||||
*/
|
||||
- (void)hideOrDisplayContents
|
||||
{
|
||||
if (!_image)
|
||||
{
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
[_shadowView setHidden:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
_DOMImageElement.style.visibility = "visible";
|
||||
[_shadowView setHidden:NO];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the view's image rectangle
|
||||
*/
|
||||
- (CGRect)imageRect
|
||||
{
|
||||
return _imageRect;
|
||||
}
|
||||
|
||||
/*!
|
||||
Add a description
|
||||
*/
|
||||
- (void)tile
|
||||
{
|
||||
if (!_image)
|
||||
return;
|
||||
|
||||
var bounds = [self bounds],
|
||||
x = 0.0,
|
||||
y = 0.0,
|
||||
insetWidth = (_hasShadow ? HORIZONTAL_SHADOW_INSET : 0.0),
|
||||
insetHeight = (_hasShadow ? VERTICAL_SHADOW_INSET : 0.0),
|
||||
boundsWidth = _CGRectGetWidth(bounds),
|
||||
boundsHeight = _CGRectGetHeight(bounds),
|
||||
width = boundsWidth - insetWidth,
|
||||
height = boundsHeight - insetHeight;
|
||||
|
||||
if (_imageScaling == CPScaleToFit)
|
||||
{
|
||||
_DOMImageElement.width = ROUND(width);
|
||||
_DOMImageElement.height = ROUND(height);
|
||||
}
|
||||
else
|
||||
{
|
||||
var size = [_image size];
|
||||
|
||||
if (size.width == -1 && size.height == -1)
|
||||
return;
|
||||
|
||||
if (_imageScaling == CPScaleProportionally)
|
||||
{
|
||||
// The max size it can be is size.width x size.height, so only
|
||||
// only proportion otherwise.
|
||||
if (width >= size.width && height >= size.height)
|
||||
{
|
||||
width = size.width;
|
||||
height = size.height;
|
||||
}
|
||||
else
|
||||
{
|
||||
var imageRatio = size.width / size.height,
|
||||
viewRatio = width / height;
|
||||
|
||||
if (viewRatio > imageRatio)
|
||||
width = height * imageRatio;
|
||||
else
|
||||
height = width / imageRatio;
|
||||
}
|
||||
|
||||
_DOMImageElement.width = ROUND(width);
|
||||
_DOMImageElement.height = ROUND(height);
|
||||
}
|
||||
else
|
||||
{
|
||||
width = size.width;
|
||||
height = size.height;
|
||||
}
|
||||
|
||||
if (_imageScaling == CPScaleNone)
|
||||
{
|
||||
_DOMImageElement.width = ROUND(size.width);
|
||||
_DOMImageElement.height = ROUND(size.height);
|
||||
}
|
||||
|
||||
var x = (boundsWidth - width) / 2.0,
|
||||
y = (boundsHeight - height) / 2.0;
|
||||
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMImageElement, NULL, x, y);
|
||||
}
|
||||
|
||||
_imageRect = _CGRectMake(x, y, width, height);
|
||||
|
||||
if (_hasShadow)
|
||||
[_shadowView setFrame:_CGRectMake(x - LEFT_SHADOW_INSET, y - TOP_SHADOW_INSET, width + insetWidth, height + insetHeight)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPImageViewImageKey = @"CPImageViewImageKey",
|
||||
CPImageViewImageScalingKey = @"CPImageViewImageScalingKey",
|
||||
CPImageViewHasShadowKey = @"CPImageViewHasShadowKey";
|
||||
|
||||
@implementation CPImageView (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the image view with the provided coder.
|
||||
@param aCoder the coder from which data will be read.
|
||||
@return the initialized image view
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_DOMImageElement = document.createElement("img");
|
||||
_DOMImageElement.style.position = "absolute";
|
||||
_DOMImageElement.style.left = "0px";
|
||||
_DOMImageElement.style.top = "0px";
|
||||
|
||||
_DOMElement.appendChild(_DOMImageElement);
|
||||
_DOMImageElement.style.visibility = "hidden";
|
||||
|
||||
[self setImage:[aCoder decodeObjectForKey:CPImageViewImageKey]];
|
||||
|
||||
[self setImageScaling:[aCoder decodeIntForKey:CPImageViewImageScalingKey]];
|
||||
[self setHasShadow:[aCoder decodeBoolForKey:CPImageViewHasShadowKey]];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the image view out to the coder.
|
||||
@param aCoder the coder to which the image
|
||||
view will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
// We do this in order to avoid encoding the _shadowView, which
|
||||
// should just automatically be created programmatically as needed.
|
||||
if (_shadowView)
|
||||
{
|
||||
var actualSubviews = _subviews;
|
||||
|
||||
_subviews = [_subviews copy];
|
||||
[_subviews removeObjectIdenticalTo:_shadowView];
|
||||
}
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
if (_shadowView)
|
||||
_subviews = actualSubviews;
|
||||
|
||||
[aCoder encodeObject:_image forKey:CPImageViewImageKey];
|
||||
|
||||
[aCoder encodeInt:_imageScaling forKey:CPImageViewImageScalingKey];
|
||||
[aCoder encodeBool:_hasShadow forKey:CPImageViewHasShadowKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* CPOutlineView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPTableView.j"
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
This class is a subclass of CPTableView which provides the user with a way to display
|
||||
tree structured data in an outline format. It is particularly useful for displaying hierarchical data
|
||||
such as a class inheritance tree or any other set of relationships.
|
||||
*/
|
||||
|
||||
@implementation CPOutlineView : CPTableView
|
||||
{
|
||||
id _outlineDataSource;
|
||||
CPArray _itemsByRow;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[super setDataSource:self];
|
||||
_itemsByRow = [[CPArray alloc] init];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
@ignore
|
||||
Sets the outline's data source. The data source must implement the following methods:
|
||||
<pre>
|
||||
- (id)outlineView:(CPOutlineView)outlineView child:(int)index ofItem:(id)item
|
||||
- (BOOL)outlineView:(CPOutlineView)outlineView isItemExpandable:(id)item
|
||||
- (int)outlineView:(CPOutlineView)outlineView numberOfChildrenOfItem:(id)item
|
||||
- (id)outlineView:(CPOutlineView)outlineView objectValueForTableColumn:(CPTableColumn)tableColumn byItem:(id)item
|
||||
</pre>
|
||||
@param aDataSource the outline's data source
|
||||
@throws CPInternalInconsistencyException if the data source does not implement all the required methods
|
||||
*/
|
||||
- (void)setDataSource:(id)aDataSource
|
||||
{
|
||||
if (![aDataSource respondsToSelector:@selector(outlineView:child:ofItem)])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:child:ofItem'"];
|
||||
if (![aDataSource respondsToSelector:@selector(outlineView:isItemExpandable)])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:isItemExpandable'"];
|
||||
if (![aDataSource respondsToSelector:@selector(outlineView:numberOfChildrenOfItem)])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:numberOfChildrenOfItem'"];
|
||||
if (![aDataSource respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem)])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:objectValueForTableColumn:byItem'"];
|
||||
|
||||
_outlineDataSource = aDataSource;
|
||||
|
||||
[self reloadData];
|
||||
}
|
||||
/* @ignore */
|
||||
- (void)reloadData
|
||||
{
|
||||
_numberOfVisibleItems = [_outlineDataSource outlineView:self numberOfChildrenOfItem:nil];
|
||||
_numberOfRows = _numberOfVisibleItems;
|
||||
|
||||
var i = 0;
|
||||
|
||||
for (; i < _numberOfVisibleItems; ++i)
|
||||
_itemsByRow[i] = [_outlineDataSource outlineView:self child:i ofItem:nil];
|
||||
|
||||
[self loadTableCellsInRect:[self bounds]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* @ignore */
|
||||
@implementation CPOutlineView (CPTableDataSource)
|
||||
|
||||
/*
|
||||
FIXME
|
||||
*/
|
||||
/* @ignore */
|
||||
- (void)numberOfRowsInTableView:(CPTableView)aTableView
|
||||
{
|
||||
return _numberOfVisibleItems;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(int)aRowIndex
|
||||
{
|
||||
return [_outlineDataSource outlineView:self objectValueForTableColumn:aTableColumn byItem:_itemsByRow[aRowIndex]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* CPPanel.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPWindow.j"
|
||||
|
||||
|
||||
CPOKButton = 1;
|
||||
CPCancelButton = 0;
|
||||
|
||||
/*! @class CPPanel
|
||||
|
||||
The CPPanel class defines objects that manage the panels of the Cappuccino user interface. A panel is a window that serves an auxiliary function within an application. It generally displays controls that the user can act on to give instructions to the application or to modify the contents of a standard window.</p>
|
||||
|
||||
<p>Panels behave differently from standard windows in only a small number of ways, but the ways are important to the user interface:</p>
|
||||
|
||||
<ul>
|
||||
<li>Panels can assume key window status but not main window status. (The key window receives keyboard events. The main window is the primary focus of user actions; it might contain the document the user is working on, for example.)</li>
|
||||
</ul>
|
||||
|
||||
<p>To aid in their auxiliary role, panels can be assigned special behaviors:</p>
|
||||
|
||||
<ul>
|
||||
<li>A panel can be precluded from becoming the key window until the user makes a selection (makes some view in the panel the first responder) indicating an intention to begin typing. This prevents key window status from shifting to the panel unnecessarily.</li>
|
||||
<li>Palettes and similar panels can be made to float above standard windows and other panels. This prevents them from being covered and keeps them readily available to the user.</li>
|
||||
<li>A panel can be made to work even when there's an attention panel on-screen. This permits actions within the panel to affect the attention panel.</li>
|
||||
</ul>
|
||||
*/
|
||||
@implementation CPPanel : CPWindow
|
||||
{
|
||||
BOOL _becomesKeyOnlyIfNeeded;
|
||||
BOOL _worksWhenModal;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the receiver is a floating panel (like a palette).
|
||||
*/
|
||||
- (BOOL)isFloatingPanel
|
||||
{
|
||||
return [self level] == CPFloatingWindowLevel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the receiver to be a floating panel. <code>YES</code>
|
||||
makes the window a floating panel. <code>NO</code> makes it a normal window.
|
||||
@param isFloatingPanel specifies whether to make it floating
|
||||
*/
|
||||
- (void)setFloatingPanel:(BOOL)isFloatingPanel
|
||||
{
|
||||
[self setLevel:isFloatingPanel ? CPFloatingWindowLevel : CPNormalWindowLevel];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the window only becomes key
|
||||
if needed. <code>NO</code> means it behaves just like other windows.
|
||||
*/
|
||||
- (BOOL)becomesKeyOnlyIfNeeded
|
||||
{
|
||||
return _becomesKeyOnlyIfNeeded;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the the window becomes key only if needed.
|
||||
@param shouldBecomeKeyOnlyIfNeeded <code>YES</code> makes the window become key only if needed
|
||||
*/
|
||||
- (void)setBecomesKeyOnlyIfNeeded:(BOOL)shouldBecomeKeyOnlyIfNeeded
|
||||
{
|
||||
_becomesKeyOnlyIfNeeded = shouldBecomeKeyOnlyIfNeeded
|
||||
}
|
||||
|
||||
- (BOOL)worksWhenModal
|
||||
{
|
||||
return _worksWhenModal;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether this window can receive input while another window is running modally.
|
||||
@param shouldWorkWhenModal whether to receive input while another window is modal
|
||||
*/
|
||||
- (void)setWorksWhenModal:(BOOL)shouldWorkWhenModal
|
||||
{
|
||||
_worksWhenModal = shouldWorkWhenModal;
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeMainWindow
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* CPPasteboard.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPPropertyListSerialization.j>
|
||||
|
||||
|
||||
CPGeneralPboard = @"CPGeneralPboard";
|
||||
CPFontPboard = @"CPFontPboard";
|
||||
CPRulerPboard = @"CPRulerPboard";
|
||||
CPFindPboard = @"CPFindPboard";
|
||||
CPDragPboard = @"CPDragPboard";
|
||||
|
||||
CPColorPboardType = @"CPColorPboardType";
|
||||
CPFilenamesPboardType = @"CPFilenamesPboardType";
|
||||
CPFontPboardType = @"CPFontPboardType";
|
||||
CPHTMLPboardType = @"CPHTMLPboardType";
|
||||
CPStringPboardType = @"CPStringPboardType";
|
||||
CPURLPboardType = @"CPURLPboardType";
|
||||
CPImagePboardType = @"CPImagePboardType";
|
||||
|
||||
var CPPasteboards = nil;
|
||||
|
||||
/*! @class CPPasteboard
|
||||
|
||||
CPPasteBoard is the object responsible for cut/copy/paste and drag&drop operations.
|
||||
*/
|
||||
@implementation CPPasteboard : CPObject
|
||||
{
|
||||
CPArray _types;
|
||||
CPDictionary _owners;
|
||||
CPDictionary _provided;
|
||||
|
||||
unsigned _changeCount;
|
||||
CPString _stateUID;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPPasteboard class])
|
||||
return;
|
||||
|
||||
[self setVersion:1.0];
|
||||
|
||||
CPPasteboards = [CPDictionary dictionary];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a new instance of a pasteboard
|
||||
*/
|
||||
+ (id)generalPasteboard
|
||||
{
|
||||
return [CPPasteboard pasteboardWithName:CPGeneralPboard];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a pasteboard with the specified name. If the pasteboard doesn't exist, it will be created.
|
||||
@param aName the name of the pasteboard
|
||||
@return the requested pasteboard
|
||||
*/
|
||||
+ (id)pasteboardWithName:(CPString)aName
|
||||
{
|
||||
var pasteboard = [CPPasteboards objectForKey:aName];
|
||||
|
||||
if (pasteboard)
|
||||
return pasteboard;
|
||||
|
||||
pasteboard = [[CPPasteboard alloc] _initWithName:aName];
|
||||
[CPPasteboards setObject:pasteboard forKey:aName];
|
||||
|
||||
return pasteboard;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithName:(CPString)aName
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_name = aName;
|
||||
_types = [];
|
||||
|
||||
_owners = [CPDictionary dictionary];
|
||||
_provided = [CPDictionary dictionary];
|
||||
|
||||
_changeCount = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds supported data types to the pasteboard
|
||||
@param types the data types
|
||||
@param anOwner the object that contains the data types
|
||||
@return the pasteboard's change count
|
||||
*/
|
||||
- (unsigned)addTypes:(CPArray)types owner:(id)anOwner
|
||||
{
|
||||
var i = 0,
|
||||
count = types.length;
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var type = types[i];
|
||||
|
||||
if(![_owners objectForKey:type])
|
||||
{
|
||||
[_types addObject:type];
|
||||
[_provided removeObjectForKey:type];
|
||||
}
|
||||
|
||||
[_owners setObject:anOwner forKey:type];
|
||||
}
|
||||
|
||||
return ++_changeCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the data types that this pasteboard will contain.
|
||||
@param type the data types it will support
|
||||
@param anOwner the object that contains the the data
|
||||
@return the pasteboard's change count
|
||||
*/
|
||||
- (unsigned)declareTypes:(CPArray)types owner:(id)anOwner
|
||||
{
|
||||
[_types setArray:types];
|
||||
|
||||
_owners = [CPDictionary dictionary];
|
||||
_provided = [CPDictionary dictionary];
|
||||
|
||||
var count = _types.length;
|
||||
|
||||
while (count--)
|
||||
[_owners setObject:anOwner forKey:_types[count]];
|
||||
|
||||
return ++_changeCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the pasteboard data for the specified type
|
||||
@param aData the data
|
||||
@param aType the data type being set
|
||||
@return <code>YES</code> if the data was successfully written to the pasteboard
|
||||
*/
|
||||
- (BOOL)setData:(CPData)aData forType:(CPString)aType
|
||||
{
|
||||
[_provided setObject:aData forKey:aType];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes the specified property list as data for the specified type
|
||||
@param aPropertyList the property list to write
|
||||
@param aType the data type
|
||||
@return <code>YES</code> if the property list was successfully written to the pasteboard
|
||||
*/
|
||||
- (BOOL)setPropertyList:(id)aPropertyList forType:(CPString)aType
|
||||
{
|
||||
return [self setData:[CPPropertyListSerialization dataFromPropertyList:aPropertyList format:CPPropertyListXMLFormat_v1_0 errorDescription:nil] forType:aType];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the specified string as data for the specified type
|
||||
@param aString the string to write
|
||||
@param aType the data type
|
||||
@return <code>YES</code> if the string was successfully written to the pasteboard
|
||||
*/
|
||||
- (void)setString:(CPString)aString forType:(CPString)aType
|
||||
{
|
||||
return [self setPropertyList:aString forType:aType];
|
||||
}
|
||||
|
||||
// Determining Types
|
||||
/*!
|
||||
Checks the pasteboard's types for a match with the types listen in the specified array. The array should
|
||||
be ordered by the requestor's most preferred data type first.
|
||||
@param anArray an array of requested types ordered by preference
|
||||
@return the highest match with the pasteboard's supported types or <code>nil</code> if no match was found
|
||||
*/
|
||||
- (CPString)availableTypeFromArray:(CPArray)anArray
|
||||
{
|
||||
return [_types firstObjectCommonWithArray:anArray];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the pasteboards supported types
|
||||
*/
|
||||
- (CPArray)types
|
||||
{
|
||||
return _types;
|
||||
}
|
||||
|
||||
// Reading data
|
||||
/*!
|
||||
Returns the number of changes that have occurred to this pasteboard
|
||||
*/
|
||||
- (unsigned)changeCount
|
||||
{
|
||||
return _changeCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the pasteboard data for the specified data type
|
||||
@param aType the requested data type
|
||||
@return the requested data or <code>nil</code> if the data doesn't exist
|
||||
*/
|
||||
- (CPData)dataForType:(CPString)aType
|
||||
{
|
||||
var data = [_provided objectForKey:aType];
|
||||
|
||||
if (data)
|
||||
return data;
|
||||
|
||||
var owner = [_owners objectForKey:aType];
|
||||
|
||||
if (owner)
|
||||
{
|
||||
[owner pasteboard:self provideDataForType:aType];
|
||||
|
||||
++_changeCount;
|
||||
|
||||
return [_provided objectForKey:aType];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the property list for the specified data type
|
||||
@param aType the requested data type
|
||||
@return the property list or <code>nil</code> if the list was not found
|
||||
*/
|
||||
- (id)propertyListForType:(CPString)aType
|
||||
{
|
||||
var data = [self dataForType:aType];
|
||||
|
||||
if (data)
|
||||
return [CPPropertyListSerialization propertyListFromData:data format:CPPropertyListXMLFormat_v1_0 errorDescription:nil];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the string for the specified data type
|
||||
@param aType the requested data type
|
||||
@return the string or <code>nil</code> if the string was not found
|
||||
*/
|
||||
- (CPString)stringForType:(CPString)aType
|
||||
{
|
||||
return [self propertyListForType:aType];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (CPString)_generateStateUID
|
||||
{
|
||||
var bits = 32;
|
||||
|
||||
_stateUID = @"";
|
||||
|
||||
while (bits--)
|
||||
_stateUID += FLOOR(RAND() * 16.0).toString(16).toUpperCase();
|
||||
|
||||
return _stateUID;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (CPString)_stateUID
|
||||
{
|
||||
return _stateUID;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,742 @@
|
||||
/*
|
||||
* CPPopUpButton.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPButton.j"
|
||||
@import "CPGeometry.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPMenuItem.j"
|
||||
|
||||
|
||||
var VISIBLE_MARGIN = 7.0;
|
||||
|
||||
var CPPopUpButtonArrowsImage = nil;
|
||||
|
||||
/*! @class CPPopUpButton
|
||||
|
||||
A CPPopUpButton contains a pop-up menu of items that a user can select from.
|
||||
*/
|
||||
@implementation CPPopUpButton : CPButton
|
||||
{
|
||||
BOOL _pullsDown;
|
||||
int _selectedIndex;
|
||||
CPRectEdge _preferredEdge;
|
||||
|
||||
CPImageView _arrowsView;
|
||||
|
||||
CPMenu _menu;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the pop-up button to the specified size.
|
||||
@param aFrame the size for the button
|
||||
@param shouldPullDown <code>YES</code> makes this a pull-down menu, <code>NO</code> makes it a pop-up menu.
|
||||
@return the initialized pop-up button
|
||||
*/
|
||||
- (id)initWithFrame:(CGRect)aFrame pullsDown:(BOOL)shouldPullDown
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_pullsDown = shouldPullDown;
|
||||
_selectedIndex = CPNotFound;
|
||||
_preferredEdge = CPMaxYEdge;
|
||||
|
||||
[self setBezelStyle:CPTexturedRoundedBezelStyle];
|
||||
|
||||
[self setImagePosition:CPImageLeft];
|
||||
[self setAlignment:CPLeftTextAlignment];
|
||||
|
||||
[self setMenu:[[CPMenu alloc] initWithTitle:@""]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
return [self initWithFrame:aFrame pullsDown:NO];
|
||||
}
|
||||
|
||||
- (void)setBordered:(BOOL)shouldBeBordered
|
||||
{
|
||||
if (shouldBeBordered)
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
|
||||
_arrowsView = [[CPImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth(bounds) - 10.0, (CGRectGetHeight(bounds) - 8.0) / 2.0, 5.0, 8.0)];
|
||||
|
||||
if (!CPPopUpButtonArrowsImage)
|
||||
CPPopUpButtonArrowsImage = [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPPopUpButton class]] pathForResource:@"CPPopUpButton/CPPopUpButtonArrows.png"] size:CGSizeMake(5.0, 8.0)];
|
||||
|
||||
[_arrowsView setImage:CPPopUpButtonArrowsImage];
|
||||
[_arrowsView setAutoresizingMask:CPViewMaxXMargin | CPViewMinYMargin | CPViewMaxYMargin];
|
||||
|
||||
|
||||
[self addSubview:_arrowsView];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_arrowsView removeFromSuperview];
|
||||
|
||||
_arrowsView = nil;
|
||||
}
|
||||
|
||||
[super setBordered:shouldBeBordered];
|
||||
}
|
||||
|
||||
// Setting the Type of Menu
|
||||
|
||||
/*!
|
||||
Specifies whether the object is a pull-down or a pop-up menu.
|
||||
@param shouldPullDown <code>YES</code> makes the pop-up button
|
||||
a pull-down menu. <code>NO</code> makes it a pop-up menu.
|
||||
*/
|
||||
- (void)setPullsDown:(BOOL)shouldPullDown
|
||||
{
|
||||
if (_pullsDown == shouldPullDown)
|
||||
return;
|
||||
|
||||
_pullsDown = shouldPullDown;
|
||||
|
||||
var items = [_menu itemArray];
|
||||
|
||||
if (items.length <= 0)
|
||||
return;
|
||||
|
||||
[items[0] setHidden:_pullsDown];
|
||||
|
||||
[self synchronizeTitleAndSelectedItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the button is a pull-down menu. <code>NO</code> if the button is a pop-up menu.
|
||||
*/
|
||||
- (BOOL)pullsDown
|
||||
{
|
||||
return _pullsDown;
|
||||
}
|
||||
|
||||
// Inserting and Deleting Items
|
||||
/*!
|
||||
Adds a new menu item with the specified title.
|
||||
@param the new menu item's tite
|
||||
*/
|
||||
- (void)addItemWithTitle:(CPString)aTitle
|
||||
{
|
||||
[_menu addItemWithTitle:aTitle action:NULL keyEquivalent:NULL];
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds multiple new menu items with the titles specified in the provided array.
|
||||
@param titles an arry of names for the new items
|
||||
*/
|
||||
- (void)addItemsWithTitles:(CPArray)titles
|
||||
{
|
||||
var index = 0,
|
||||
count = [titles count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
[self addItemWithTitle:titles[index]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Inserts a new item with the specified title and index location.
|
||||
@param aTitle the new itme's title
|
||||
@param anIndex the item's index in the menu
|
||||
*/
|
||||
- (void)insertItemWithTitle:(CPString)aTitle atIndex:(int)anIndex
|
||||
{
|
||||
var items = [self itemArray],
|
||||
count = [items count];
|
||||
|
||||
while (count--)
|
||||
if ([items[count] title] == aTitle)
|
||||
[self removeItemAtIndex:count];
|
||||
|
||||
[_menu insertItemWithTitle:aTitle action:NULL keyEquivalent:NULL atIndex:anIndex];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes all menu items from the pop-up button's menu
|
||||
*/
|
||||
- (void)removeAllItems
|
||||
{
|
||||
var count = [_menu numberOfItems];
|
||||
|
||||
while (count--)
|
||||
[_menu removeItemAtIndex:0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes a menu item with the specified title from the button.
|
||||
@param aTitle the title of the item to remove
|
||||
*/
|
||||
- (void)removeItemWithTitle:(CPString)aTitle
|
||||
{
|
||||
[self removeItemAtIndex:[self indexOfItemWithTitle:aTitle]];
|
||||
[self synchronizeTitleAndSelectedItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes the menu item at the specified index
|
||||
@param anIndex the index of the item to remove
|
||||
*/
|
||||
- (void)removeItemAtIndex:(int)anIndex
|
||||
{
|
||||
[_menu removeItemAtIndex:anIndex];
|
||||
[self synchronizeTitleAndSelectedItem];
|
||||
}
|
||||
|
||||
// Getting the User's Selection
|
||||
/*!
|
||||
Returns the selected item or <code>nil</code> if no item is selected.
|
||||
*/
|
||||
- (CPMenuItem)selectedItem
|
||||
{
|
||||
if (_selectedIndex < 0)
|
||||
return nil;
|
||||
|
||||
return [_menu itemAtIndex:_selectedIndex];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the title of the selected item or <code>nil</code> if no item is selected.
|
||||
*/
|
||||
- (CPString)titleOfSelectedItem
|
||||
{
|
||||
return [[self selectedItem] title];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the index of the selected item. If no item is selected, it returns CPNotFound.
|
||||
*/
|
||||
- (int)indexOfSelectedItem
|
||||
{
|
||||
return _selectedIndex;
|
||||
}
|
||||
|
||||
// For us, CPNumber is toll-free bridged to Number, so just return the selected index.
|
||||
/*!
|
||||
Returns the selected item's index. If no item is selected, it returns CPNotFound.
|
||||
*/
|
||||
- (id)objectValue
|
||||
{
|
||||
return _selectedIndex;
|
||||
}
|
||||
|
||||
// Setting the Current Selection
|
||||
/*!
|
||||
Selects the specified menu item.
|
||||
@param aMenuItem the item to select
|
||||
*/
|
||||
- (void)selectItem:(CPMenuItem)aMenuItem
|
||||
{
|
||||
[self selectItemAtIndex:[self indexOfItem:aMenuItem]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the item at the specified index
|
||||
@param anIndex the index of the item to select
|
||||
*/
|
||||
- (void)selectItemAtIndex:(int)anIndex
|
||||
{
|
||||
if (_selectedIndex == anIndex)
|
||||
return;
|
||||
|
||||
if (_selectedIndex >= 0 && !_pullsDown)
|
||||
[[self selectedItem] setState:CPOffState];
|
||||
|
||||
_selectedIndex = anIndex;
|
||||
|
||||
if (_selectedIndex >= 0 && !_pullsDown)
|
||||
[[self selectedItem] setState:CPOnState];
|
||||
|
||||
[self synchronizeTitleAndSelectedItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the menu item with the specified tag
|
||||
@param the tag of the item to select
|
||||
*/
|
||||
- (void)selectItemWithTag:(int)aTag
|
||||
{
|
||||
[self selectItemAtIndex:[self indexOfItemWithTag:aTag]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the item with the specified title
|
||||
@param the title of the item to select
|
||||
*/
|
||||
- (void)selectItemWithTitle:(CPString)aTitle
|
||||
{
|
||||
[self selectItemAtIndex:[self indexOfItemWithTitle:aTitle]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the object for the selected item. If no item is selected, then this method has no effect.
|
||||
@param the object set for the selected item
|
||||
*/
|
||||
- (void)setObjectValue:(id)aValue
|
||||
{
|
||||
[self selectItemAtIndex:[aValue intValue]];
|
||||
}
|
||||
|
||||
// Getting Menu Items
|
||||
/*!
|
||||
Returns the button's menu of items.
|
||||
*/
|
||||
- (CPMenu)menu
|
||||
{
|
||||
return _menu;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the menu for the button
|
||||
*/
|
||||
- (void)setMenu:(CPMenu)aMenu
|
||||
{
|
||||
if (_menu == aMenu)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_menu)
|
||||
{
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPMenuDidAddItemNotification
|
||||
object:_menu];
|
||||
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPMenuDidChangeItemNotification
|
||||
object:_menu];
|
||||
|
||||
[defaultCenter
|
||||
removeObserver:self
|
||||
name:CPMenuDidRemoveItemNotification
|
||||
object:_menu];
|
||||
}
|
||||
|
||||
_menu = aMenu;
|
||||
|
||||
if (_menu)
|
||||
{
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(menuDidAddItem:)
|
||||
name:CPMenuDidAddItemNotification
|
||||
object:_menu];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(menuDidChangeItem:)
|
||||
name:CPMenuDidChangeItemNotification
|
||||
object:_menu];
|
||||
|
||||
[defaultCenter
|
||||
addObserver:self
|
||||
selector:@selector(menuDidRemoveItem:)
|
||||
name:CPMenuDidRemoveItemNotification
|
||||
object:_menu];
|
||||
}
|
||||
|
||||
[self synchronizeTitleAndSelectedItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a count of the number of items in the button's menu.
|
||||
*/
|
||||
- (int)numberOfItems
|
||||
{
|
||||
return [_menu numberOfItems];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of the items in the menu
|
||||
*/
|
||||
- (CPArray)itemArray
|
||||
{
|
||||
return [_menu itemArray];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item at the specified index or <code>nil</code> if the item does not exist.
|
||||
@param anIndex the index of the item to obtain
|
||||
*/
|
||||
- (CPMenuItem)itemAtIndex:(unsigned)anIndex
|
||||
{
|
||||
return [_menu itemAtIndex:anIndex];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the title of the item at the specified index or <code>nil</code> if no item exists.
|
||||
@param anIndex the index of the item
|
||||
*/
|
||||
- (CPString)itemTitleAtIndex:(unsigned)anIndex
|
||||
{
|
||||
return [[_menu itemAtIndex:anIndex] title];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns an array of all the menu item titles.
|
||||
*/
|
||||
- (CPArray)itemTitles
|
||||
{
|
||||
var titles = [],
|
||||
items = [self itemArray],
|
||||
|
||||
index = 0,
|
||||
count = [items count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
items.push([items[index] title]);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the menu item with the specified title.
|
||||
@param aTitle the title of the desired menu item
|
||||
*/
|
||||
- (CPMenuItem)itemWithTitle:(CPString)aTitle
|
||||
{
|
||||
return [_menu itemAtIndex:[_menu indexOfItemWithTitle:aTitle]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the last menu item
|
||||
*/
|
||||
- (CPMenuItem)lastItem
|
||||
{
|
||||
return [[_menu itemArray] lastObject];
|
||||
}
|
||||
|
||||
// Getting the Indices of Menu Items
|
||||
/*!
|
||||
Returns the index of the specified item or CPNotFound if the item is not in the list.
|
||||
@param aMenuItem the item to obtain the index for
|
||||
*/
|
||||
- (int)indexOfItem:(CPMenuItem)aMenuItem
|
||||
{
|
||||
return [_menu indexOfItem:aMenuItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the index of the item with the specified tag or CPNotFound if the item is not in the list.
|
||||
@param aTag the item's tag
|
||||
*/
|
||||
- (int)indexOfItemWithTag:(int)aTag
|
||||
{
|
||||
return [_menu indexOfItemWithTag:aMenuItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the index of the item with the specified title or CPNotFound.
|
||||
@param aTitle the item's titel
|
||||
*/
|
||||
- (int)indexOfItemWithTitle:(CPString)aTitle
|
||||
{
|
||||
return [_menu indexOfItemWithTitle:aTitle];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the index of the item with the specified
|
||||
represented object or CPNotFound
|
||||
if a match does not exist.
|
||||
@param anObject the item's represented object
|
||||
*/
|
||||
- (int)indexOfItemWithRepresentedObject:(id)anObject
|
||||
{
|
||||
return [_menu indexOfItemWithRepresentedObejct:anObject];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the index of the item with the specified target
|
||||
and action. Returns CPNotFound if the no
|
||||
such item is in the list.
|
||||
@param aTarget the item's target
|
||||
@param anAction the item's action
|
||||
*/
|
||||
- (int)indexOfItemWithTarget:(id)aTarget action:(SEL)anAction
|
||||
{
|
||||
return [_menu indexOfItemWithTarget:aTarget action:anAction];
|
||||
}
|
||||
|
||||
// Setting the Cell Edge to Pop out in Restricted Situations
|
||||
/*!
|
||||
Returns the button's edge where the pop-up menu will be
|
||||
displayed when there is not enough room to display directly
|
||||
above the button.
|
||||
*/
|
||||
- (CPRectEdge)preferredEdge
|
||||
{
|
||||
return _preferredEdge;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the preffered edge of the button to display the
|
||||
pop-up when there is a limited amount of screen space.
|
||||
By default, the pop-up should draw on top of the button.
|
||||
*/
|
||||
- (void)setPreferredEdge:(CPRectEdge)aRectEdge
|
||||
{
|
||||
_preferredEdge = aRectEdge;
|
||||
}
|
||||
|
||||
// Setting the Title
|
||||
/*!
|
||||
Sets the pop-up button's title.
|
||||
@param aTitle the new title
|
||||
*/
|
||||
- (void)setTitle:(CPString)aTitle
|
||||
{
|
||||
if ([self title] == aTitle)
|
||||
return;
|
||||
|
||||
if (_pullsDown)
|
||||
{
|
||||
[_items[0] setTitle:aTitle];
|
||||
[self synchronizeTitleAndSelectedItem];
|
||||
}
|
||||
else
|
||||
{
|
||||
var index = [self indexOfItemWithTitle:aTitle];
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
[self addItemWithTitle:aTitle];
|
||||
|
||||
index = [self numberOfItems] - 1;
|
||||
}
|
||||
|
||||
[self selectItemAtIndex:index];
|
||||
}
|
||||
}
|
||||
|
||||
// Setting the Image
|
||||
/*!
|
||||
This method has no effect. Because the image is taken
|
||||
from the currently selected item, this method serves
|
||||
no purpose.
|
||||
*/
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
// The Image is set by the currently selected item.
|
||||
}
|
||||
|
||||
// Setting the State
|
||||
/*!
|
||||
Makes sure the selected item and the item
|
||||
being displayed are one and the same.
|
||||
*/
|
||||
- (void)synchronizeTitleAndSelectedItem
|
||||
{
|
||||
var item = nil;
|
||||
|
||||
if (_pullsDown)
|
||||
{
|
||||
var items = [_menu itemArray];
|
||||
|
||||
if (items.length > 0)
|
||||
item = items[0];
|
||||
}
|
||||
else
|
||||
item = [self selectedItem];
|
||||
|
||||
[super setImage:[item image]];
|
||||
[super setTitle:[item title]];
|
||||
}
|
||||
|
||||
//
|
||||
/*!
|
||||
Called when the menu has a new item added to it.
|
||||
@param aNotification information about the event
|
||||
*/
|
||||
- (void)menuDidAddItem:(CPNotification)aNotification
|
||||
{
|
||||
var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"];
|
||||
|
||||
if (_selectedIndex < 0)
|
||||
[self selectItemAtIndex:0];
|
||||
|
||||
else if (index == _selectedIndex)
|
||||
[self synchronizeTitleAndSelectedItem];
|
||||
|
||||
else if (index < _selectedIndex)
|
||||
++_selectedIndex;
|
||||
|
||||
if (index == 0 && _pullsDown)
|
||||
{
|
||||
var items = [_menu itemArray];
|
||||
|
||||
[items[0] setHidden:YES];
|
||||
|
||||
if (items.length > 0)
|
||||
[items[1] setHidden:NO];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when a menu item has changed.
|
||||
@param aNotification information about the event
|
||||
*/
|
||||
- (void)menuDidChangeItem:(CPNotification)aNotification
|
||||
{
|
||||
var index = [[aNotification userInfo] objectForKey:@"CPMenuItemIndex"];
|
||||
|
||||
if (_pullsDown && index != 0)
|
||||
return;
|
||||
|
||||
if (!_pullsDown && index != _selectedIndex)
|
||||
return;
|
||||
|
||||
[self synchronizeTitleAndSelectedItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Called when an item was removed from the menu.
|
||||
@param aNotification information about the event
|
||||
*/
|
||||
- (void)menuDidRemoveItem:(CPNotification)aNotification
|
||||
{
|
||||
var numberOfItems = [self numberOfItems];
|
||||
|
||||
if (numberOfItems <= _selectedIndex)
|
||||
[self selectItemAtIndex:numberOfItems - 1];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
[self highlight:YES];
|
||||
|
||||
var theWindow = [self window],
|
||||
menuWindow = [_CPMenuWindow menuWindowWithMenu:[self menu] font:[self font]];
|
||||
|
||||
[menuWindow setDelegate:self];
|
||||
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
|
||||
|
||||
var menuOrigin = [theWindow convertBaseToBridge:[self convertPoint:CGPointMakeZero() toView:nil]];
|
||||
|
||||
// Pull Down Menus show up directly below their buttons.
|
||||
if (_pullsDown)
|
||||
menuOrigin.y += CGRectGetHeight([self frame]);
|
||||
|
||||
// Pop Up Menus attempt to show up "on top" of the selected item.
|
||||
else
|
||||
{
|
||||
var contentRect = [menuWindow rectForItemAtIndex:_selectedIndex];
|
||||
|
||||
menuOrigin.x -= CGRectGetMinX(contentRect) + [_CPMenuItemView leftMargin];
|
||||
menuOrigin.y -= CGRectGetMinY(contentRect);
|
||||
}
|
||||
|
||||
[menuWindow setFrameOrigin:menuOrigin];
|
||||
|
||||
var menuMaxX = CGRectGetMaxX([menuWindow frame]),
|
||||
buttonMaxX = CGRectGetMaxX([self convertRect:[self bounds] toView:nil]);
|
||||
|
||||
if (menuMaxX < buttonMaxX)
|
||||
[menuWindow setMinWidth:CGRectGetWidth([menuWindow frame]) + buttonMaxX - menuMaxX - VISIBLE_MARGIN];
|
||||
|
||||
[menuWindow orderFront:self];
|
||||
[menuWindow beginTrackingWithEvent:anEvent sessionDelegate:self didEndSelector:@selector(menuWindowDidFinishTracking:highlightedItem:)];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (void)menuWindowDidFinishTracking:(_CPMenuWindow)aMenuWindow highlightedItem:(CPMenuItem)aMenuItem
|
||||
{
|
||||
[_CPMenuWindow poolMenuWindow:aMenuWindow];
|
||||
|
||||
[self highlight:NO];
|
||||
|
||||
var index = [_menu indexOfItem:aMenuItem];
|
||||
|
||||
if (index == CPNotFound)
|
||||
return;
|
||||
|
||||
[self selectItemAtIndex:index];
|
||||
|
||||
var selectedItem = [self selectedItem],
|
||||
target = nil,
|
||||
action = [selectedItem action];
|
||||
|
||||
if (!action)
|
||||
{
|
||||
target = [self target];
|
||||
action = [self action];
|
||||
}
|
||||
|
||||
// FIXME: If [selectedItem target] == nil do we use our own target?
|
||||
else
|
||||
target = [selectedItem target];
|
||||
|
||||
[self sendAction:action to:target];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPPopUpButtonMenuKey = @"CPPopUpButtonMenuKey",
|
||||
CPPopUpButtonSelectedIndexKey = @"CPPopUpButtonSelectedIndexKey",
|
||||
CPPopUpButtonPullsDownKey = @"CPPopUpButtonPullsDownKey";
|
||||
|
||||
@implementation CPPopUpButton (CPCoding)
|
||||
/*!
|
||||
Initializes the pop-up button with data from the
|
||||
specified coder.
|
||||
@param aCoder the coder from which to read
|
||||
the data
|
||||
@return the initialized pop-up button
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setMenu:[aCoder decodeObjectForKey:CPPopUpButtonMenuKey]];
|
||||
[self selectItemAtIndex:[aCoder decodeObjectForKey:CPPopUpButtonSelectedIndexKey]];
|
||||
[self setPullsDown:[aCoder decodeBoolForKey:CPPopUpButtonPullsDownKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Encodes the data of the pop-up button into a coder
|
||||
@param aCoder the coder to which the data
|
||||
will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_menu forKey:CPPopUpButtonMenuKey];
|
||||
[aCoder encodeInt:_selectedIndex forKey:CPPopUpButtonSelectedIndexKey];
|
||||
[aCoder encodeBool:_pullsDown forKey:CPPopUpButtonPullsDownKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,476 @@
|
||||
/*
|
||||
* CPProgressIndicator.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <AppKit/CGGeometry.j>
|
||||
@import <AppKit/CPImageView.j>
|
||||
@import <AppKit/CPView.j>
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorBarStyle = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorSpinningStyle = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPProgressIndicatorStyle
|
||||
*/
|
||||
CPProgressIndicatorHUDBarStyle = 2;
|
||||
|
||||
var CPProgressIndicatorSpinningStyleColors = nil,
|
||||
|
||||
CPProgressIndicatorClassName = nil,
|
||||
CPProgressIndicatorStyleIdentifiers = nil,
|
||||
CPProgressIndicatorStyleSizes = nil;
|
||||
|
||||
/*! @class CPProgressIndicator
|
||||
|
||||
This class is used in a Cappuccino GUI to display the progress of a
|
||||
function or task. If the duration of the task is unknown, there is
|
||||
also an indeterminate mode for the indicator.
|
||||
*/
|
||||
@implementation CPProgressIndicator : CPView
|
||||
{
|
||||
double _minValue;
|
||||
double _maxValue;
|
||||
|
||||
double _doubleValue;
|
||||
|
||||
CPControlSize _controlSize;
|
||||
|
||||
BOOL _isIndeterminate;
|
||||
CPProgressIndicatorStyle _style;
|
||||
|
||||
BOOL _isAnimating;
|
||||
|
||||
BOOL _isDisplayedWhenStoppedSet;
|
||||
BOOL _isDisplayedWhenStopped;
|
||||
|
||||
CPView _barView;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPProgressIndicator class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
CPProgressIndicatorSpinningStyleColors = [];
|
||||
|
||||
CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleRegular.gif"] size:CGSizeMake(64.0, 64.0)]];
|
||||
CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleRegular.gif"] size:CGSizeMake(64.0, 64.0)]];
|
||||
CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:
|
||||
[bundle pathForResource:@"CPProgressIndicator/CPProgressIndicatorSpinningStyleRegular.gif"] size:CGSizeMake(64.0, 64.0)]];
|
||||
|
||||
CPProgressIndicatorBezelBorderViewPool = [];
|
||||
|
||||
var start = CPProgressIndicatorBarStyle,
|
||||
end = CPProgressIndicatorHUDBarStyle;
|
||||
|
||||
for (; start <= end; ++start)
|
||||
{
|
||||
CPProgressIndicatorBezelBorderViewPool[start] = [];
|
||||
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPMiniControlSize] = [];
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPSmallControlSize] = [];
|
||||
CPProgressIndicatorBezelBorderViewPool[start][CPRegularControlSize] = [];
|
||||
}
|
||||
|
||||
CPProgressIndicatorClassName = [self className];
|
||||
|
||||
|
||||
CPProgressIndicatorStyleIdentifiers = [];
|
||||
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle] = @"Bar";
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorSpinningStyle] = @"Spinny";
|
||||
CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle] = @"HUDBar";
|
||||
|
||||
var regularIdentifier = _CPControlIdentifierForControlSize(CPRegularControlSize),
|
||||
smallIdentifier = _CPControlIdentifierForControlSize(CPSmallControlSize),
|
||||
miniIdentifier = _CPControlIdentifierForControlSize(CPMiniControlSize);
|
||||
|
||||
CPProgressIndicatorStyleSizes = [];
|
||||
|
||||
// Bar Sttyle
|
||||
var prefix = CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle];
|
||||
|
||||
CPProgressIndicatorStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(3.0, 15.0), _CGSizeMake(1.0, 15.0), _CGSizeMake(3.0, 15.0)];
|
||||
CPProgressIndicatorStyleSizes[prefix + smallIdentifier] = [_CGSizeMake(3.0, 15.0), _CGSizeMake(1.0, 15.0), _CGSizeMake(3.0, 15.0)];
|
||||
CPProgressIndicatorStyleSizes[prefix + miniIdentifier] = [_CGSizeMake(3.0, 15.0), _CGSizeMake(1.0, 15.0), _CGSizeMake(3.0, 15.0)];
|
||||
|
||||
prefix = CPProgressIndicatorClassName + @"Bar" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle];
|
||||
|
||||
CPProgressIndicatorStyleSizes[prefix + regularIdentifier] = _CGSizeMake(1.0, 9.0);
|
||||
CPProgressIndicatorStyleSizes[prefix + smallIdentifier] = _CGSizeMake(1.0, 9.0);
|
||||
CPProgressIndicatorStyleSizes[prefix + miniIdentifier] = _CGSizeMake(1.0, 9.0);
|
||||
|
||||
// HUD Bar Style
|
||||
prefix = CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle];
|
||||
|
||||
CPProgressIndicatorStyleSizes[prefix + regularIdentifier] = [_CGSizeMake(3.0, 15.0), _CGSizeMake(1.0, 15.0), _CGSizeMake(3.0, 15.0)];
|
||||
CPProgressIndicatorStyleSizes[prefix + smallIdentifier] = [_CGSizeMake(3.0, 15.0), _CGSizeMake(1.0, 15.0), _CGSizeMake(3.0, 15.0)];
|
||||
CPProgressIndicatorStyleSizes[prefix + miniIdentifier] = [_CGSizeMake(3.0, 15.0), _CGSizeMake(1.0, 15.0), _CGSizeMake(3.0, 15.0)];
|
||||
|
||||
prefix = CPProgressIndicatorClassName + @"Bar" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorHUDBarStyle];
|
||||
|
||||
CPProgressIndicatorStyleSizes[prefix + regularIdentifier] = _CGSizeMake(1.0, 9.0);
|
||||
CPProgressIndicatorStyleSizes[prefix + smallIdentifier] = _CGSizeMake(1.0, 9.0);
|
||||
CPProgressIndicatorStyleSizes[prefix + miniIdentifier] = _CGSizeMake(1.0, 9.0);
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_minValue = 0.0;
|
||||
_maxValue = 100.0;
|
||||
|
||||
_doubleValue = 0.0;
|
||||
|
||||
_style = CPProgressIndicatorBarStyle;
|
||||
_isDisplayedWhenStoppedSet = NO;
|
||||
|
||||
_controlSize = CPRegularControlSize;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
[self drawBar];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (void)setUsesThreadedAnimation:(BOOL)aFlag
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Starts the animation of the progress indicator in indeterminate mode.
|
||||
@param the requesting object
|
||||
*/
|
||||
- (void)startAnimation:(id)aSender
|
||||
{
|
||||
_isAnimating = YES;
|
||||
|
||||
[self _hideOrDisplay];
|
||||
}
|
||||
|
||||
/*!
|
||||
Stops the animation of the progress indicator in indeterminate mode.
|
||||
@param the requesting object
|
||||
*/
|
||||
- (void)stopAnimation:(id)aSender
|
||||
{
|
||||
_isAnimating = NO;
|
||||
|
||||
[self _hideOrDisplay];
|
||||
}
|
||||
|
||||
/*!
|
||||
Always returns <code>NO</code>. Cappuccino does not have multiple threads.
|
||||
*/
|
||||
- (BOOL)usesThreadedAnimation
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Advancing the Progress Bar
|
||||
/*!
|
||||
Increases the progress of the bar by the specified value.
|
||||
@param aValue the amount to increase the progress value
|
||||
*/
|
||||
- (void)incrementBy:(double)aValue
|
||||
{
|
||||
[self setDoubleValue:_doubleValue + aValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the progress value of the indicator.
|
||||
*/
|
||||
- (void)setDoubleValue:(double)aValue
|
||||
{
|
||||
_doubleValue = MIN(MAX(aValue, _minValue), _maxValue);
|
||||
|
||||
[self drawBar];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the value of the progress indicator.
|
||||
*/
|
||||
- (double)doubleValue
|
||||
{
|
||||
return _doubleValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the minimum value of the progress indicator. The default is 0.0.
|
||||
@param aValue the new minimum value
|
||||
*/
|
||||
- (void)setMinValue:(double)aValue
|
||||
{
|
||||
_minValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the minimum value of the progress indicator.
|
||||
*/
|
||||
- (double)minValue
|
||||
{
|
||||
return _minValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the maximum value of the progress indicator. The default is 100.0.
|
||||
@param aValue the new maximum value.
|
||||
*/
|
||||
- (void)setMaxValue:(double)aValue
|
||||
{
|
||||
_maxValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the maximum value of the progress indicator.
|
||||
*/
|
||||
- (double)maxValue
|
||||
{
|
||||
return _maxValue;
|
||||
}
|
||||
|
||||
// Setting the Appearance
|
||||
/*!
|
||||
Sets the progress indicator's size.
|
||||
@param aControlSize the new size
|
||||
*/
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
if (_controlSize == aControlSize)
|
||||
return;
|
||||
|
||||
_controlSize = aControlSize;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the progress indicator's size
|
||||
*/
|
||||
- (CPControlSize)controlSize
|
||||
{
|
||||
return _controlSize;
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet implemented
|
||||
*/
|
||||
- (void)setControlTint:(CPControlTint)aControlTint
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet impemented.
|
||||
*/
|
||||
- (CPControlTint)controlTint
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet implemented.
|
||||
*/
|
||||
- (void)setBezeled:(BOOL)isBezeled
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet implemented.
|
||||
*/
|
||||
- (BOOL)isBezeled
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Specifies whether this progress indicator should be indeterminate or display progress based on it's max and min.
|
||||
@param isDeterminate <code>YES</code> makes the indicator indeterminate
|
||||
*/
|
||||
- (void)setIndeterminate:(BOOL)isIndeterminate
|
||||
{
|
||||
if (_isIndeterminate == isIndeterminate)
|
||||
return;
|
||||
|
||||
_isIndeterminate = isIndeterminate;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the progress bar is indeterminate.
|
||||
*/
|
||||
- (BOOL)isIndeterminate
|
||||
{
|
||||
return _isIndeterminate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the progress indicator's style
|
||||
@param aStyle the style to set it to
|
||||
*/
|
||||
- (void)setStyle:(CPProgressIndicatorStyle)aStyle
|
||||
{
|
||||
if (_style == aStyle)
|
||||
return;
|
||||
|
||||
_style = aStyle;
|
||||
|
||||
[self updateBackgroundColor];
|
||||
}
|
||||
|
||||
/*!
|
||||
Resizes the indicator based on it's style.
|
||||
*/
|
||||
- (void)sizeToFit
|
||||
{
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
[self setFrameSize:[[CPProgressIndicatorSpinningStyleColors[_controlSize] patternImage] size]];
|
||||
else
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), CPProgressIndicatorStyleSizes[
|
||||
CPProgressIndicatorClassName + @"BezelBorder" + CPProgressIndicatorStyleIdentifiers[CPProgressIndicatorBarStyle] +
|
||||
_CPControlIdentifierForControlSize(_controlSize)][0].height)];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the indicator should be displayed when it isn't animating. By default this is <code>YES</code> if the style
|
||||
is CPProgressIndicatorBarStyle, and <code>NO</code> if it's CPProgressIndicatorSpinningStyle.
|
||||
@param isDisplayedWhenStopped <code>YES</code> means the indicator will be displayed when it's not animating.
|
||||
*/
|
||||
- (void)setDisplayedWhenStopped:(BOOL)isDisplayedWhenStopped
|
||||
{
|
||||
if (_isDisplayedWhenStoppedSet && _isDisplayedWhenStopped == isDisplayedWhenStopped)
|
||||
return;
|
||||
|
||||
_isDisplayedWhenStoppedSet = YES;
|
||||
|
||||
_isDisplayedWhenStopped = isDisplayedWhenStopped;
|
||||
|
||||
[self _hideOrDisplay];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the progress bar is displayed when not animating.
|
||||
*/
|
||||
- (BOOL)isDisplayedWhenStopped
|
||||
{
|
||||
if (_isDisplayedWhenStoppedSet)
|
||||
return _isDisplayedWhenStopped;
|
||||
|
||||
if (_style == CPProgressIndicatorBarStyle || _style == CPProgressIndicatorHUDBarStyle)
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_hideOrDisplay
|
||||
{
|
||||
[self setHidden:!_isAnimating && ![self isDisplayedWhenStopped]];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self drawBar];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)drawBar
|
||||
{
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
return;
|
||||
|
||||
if (!_barView)
|
||||
{
|
||||
_barView = [[CPView alloc] initWithFrame:CGRectMake(2.0, 2.0, 0.0, 9.0)];
|
||||
|
||||
[_barView setBackgroundColor:[CPColor redColor]];
|
||||
|
||||
[self addSubview:_barView];
|
||||
}
|
||||
|
||||
[_barView setBackgroundColor:_CPControlColorWithPatternImage(
|
||||
CPProgressIndicatorStyleSizes,
|
||||
CPProgressIndicatorClassName,
|
||||
@"Bar",
|
||||
CPProgressIndicatorStyleIdentifiers[_style],
|
||||
_CPControlIdentifierForControlSize(_controlSize))];
|
||||
|
||||
[_barView setFrameSize:CGSizeMake(CGRectGetWidth([self bounds]) * (_doubleValue - _minValue) / (_maxValue - _minValue) - 4.0, 9.0)];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)updateBackgroundColor
|
||||
{
|
||||
if (YES)//_isBezeled)
|
||||
{
|
||||
if (_style == CPProgressIndicatorSpinningStyle)
|
||||
{
|
||||
[_barView removeFromSuperview];
|
||||
|
||||
_barView = nil;
|
||||
|
||||
[self setBackgroundColor:CPProgressIndicatorSpinningStyleColors[_controlSize]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self setBackgroundColor:_CPControlThreePartImagePattern(
|
||||
NO,
|
||||
CPProgressIndicatorStyleSizes,
|
||||
CPProgressIndicatorClassName,
|
||||
@"BezelBorder",
|
||||
CPProgressIndicatorStyleIdentifiers[_style],
|
||||
_CPControlIdentifierForControlSize(_controlSize))];
|
||||
|
||||
[self drawBar];
|
||||
}
|
||||
}
|
||||
else
|
||||
[self setBackgroundColor:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* CPResponder.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
|
||||
CPDeleteKeyCode = 8;
|
||||
CPUpArrowKeyCode = 63232;
|
||||
CPDownArrowKeyCode = 63233;
|
||||
CPLeftArrowKeyCode = 63234;
|
||||
CPRightArrowKeyCode = 63235;
|
||||
|
||||
/*!
|
||||
@class CPResponder
|
||||
|
||||
Subclasses of CPResonder can be part of the responder chain.
|
||||
*/
|
||||
@implementation CPResponder : CPObject
|
||||
{
|
||||
CPMenu _menu;
|
||||
CPResponder _nextResponder;
|
||||
}
|
||||
|
||||
// Changing the first responder
|
||||
/*!
|
||||
Returns <code>YES</code> if the receiver is able to become the first responder. <code>NO</code> otherwise.
|
||||
*/
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that it will become the first responder. The receiver can reject first
|
||||
responder if it returns <code>NO</code>. The default implementation always returns <code>YES</code>.
|
||||
@return <code>YES</code> if the receiver accepts first responder status.
|
||||
*/
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that it has been asked to give up first responder status.
|
||||
@return <code>YES</code> if the receiver is willing to give up first responder status.
|
||||
*/
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
// Setting the next responder
|
||||
/*!
|
||||
Sets the receiver's next responder.
|
||||
@param aResponder the responder after the receiver
|
||||
*/
|
||||
- (void)setNextResponder:(CPResponder)aResponder
|
||||
{
|
||||
_nextResponder = aResponder;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the responder after the receiver.
|
||||
*/
|
||||
- (CPResponder)nextResponder
|
||||
{
|
||||
return _nextResponder;
|
||||
}
|
||||
|
||||
/*!
|
||||
Called to interpret a series of key events.
|
||||
@param events an array of key CPEvents
|
||||
*/
|
||||
- (void)interpretKeyEvents:(CPArray)events
|
||||
{
|
||||
var event,
|
||||
index = 0;
|
||||
|
||||
while(event = events[index++])
|
||||
{
|
||||
switch([event keyCode])
|
||||
{
|
||||
case CPLeftArrowKeyCode: [self moveBackward:self];
|
||||
break;
|
||||
case CPRightArrowKeyCode: [self moveForward:self];
|
||||
break;
|
||||
case CPUpArrowKeyCode: [self moveUp:self];
|
||||
break;
|
||||
case CPDownArrowKeyCode: [self moveDown:self];
|
||||
break;
|
||||
case CPDeleteKeyCode: [self deleteBackward:self];
|
||||
break;
|
||||
case 3:
|
||||
case 13: [self insertLineBreak:self];
|
||||
break;
|
||||
default: [self insertText:[event characters]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has clicked the mouse down in its area.
|
||||
@param anEvent contains information about the click
|
||||
*/
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has initiated a drag
|
||||
over it. A drag is a mouse movement while the left button is down.
|
||||
@param anEvent contains information about the drag
|
||||
*/
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has released the left mouse button.
|
||||
@param anEvent contains information about the release
|
||||
*/
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has moved the mouse (with no buttons down).
|
||||
@param anEvent contains information about the movement
|
||||
*/
|
||||
- (void)mouseMoved:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseEntered:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the mouse exited the receiver's area.
|
||||
@param anEvent contains information about the exit
|
||||
*/
|
||||
- (void)mouseExited:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the mouse scroll wheel has moved.
|
||||
@param anEvent information about the scroll
|
||||
*/
|
||||
- (void)scrollWheel:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has pressed a key.
|
||||
@param anEvent information about the key press
|
||||
*/
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Notifies the receiver that the user has released a key.
|
||||
@param anEvent information about the key press
|
||||
*/
|
||||
- (void)keyUp:(CPEvent)anEvent
|
||||
{
|
||||
[_nextResponder performSelector:_cmd withObject:anEvent];
|
||||
}
|
||||
|
||||
/*
|
||||
FIXME This description is bad.
|
||||
Based on <code>anEvent</code>, the receiver should simulate the event.
|
||||
@param anEvent the event to simulate
|
||||
@return <code>YES</code> if the event receiver simulated the event
|
||||
*/
|
||||
- (BOOL)performKeyEquivalent:(CPEvent)anEvent
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Action Methods
|
||||
/*!
|
||||
Deletes one character backward, or the selection if anything is selected.
|
||||
@param aSender the object requesting this
|
||||
*/
|
||||
- (void)deleteBackward:(id)aSender
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Insert a line break at the caret position or selection.
|
||||
@param aSender the object requesting this
|
||||
*/
|
||||
- (void)insertLineBreak:(id)aSender
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Inserts some text at the caret position or selection.
|
||||
@param aString the string to insert
|
||||
*/
|
||||
- (void)insertText:(CPString)aString
|
||||
{
|
||||
}
|
||||
|
||||
// Dispatch methods
|
||||
/*!
|
||||
The receiver will attempt to perform the command,
|
||||
if it responds to it. If not, the <code>nextResponder</code> will be called to do it.
|
||||
@param aSelector the command to attempt
|
||||
*/
|
||||
- (void)doCommandBySelector:(SEL)aSelector
|
||||
{
|
||||
if([self respondsToSelector:aSelector])
|
||||
[self performSelector:aSelector];
|
||||
else
|
||||
[_nextResponder doCommandBySelector:aSelector];
|
||||
}
|
||||
|
||||
/*!
|
||||
The receiver will attempt to perform the command, or pass it on to the next responder if it doesn't respond to it.
|
||||
@param aSelector the command to perform
|
||||
@param anObject the argument to the method
|
||||
@return <code>YES</code> if the receiver was able to perform the command, or a responder down the chain was
|
||||
able to perform the command.
|
||||
*/
|
||||
- (BOOL)tryToPerform:(SEL)aSelector with:(id)anObject
|
||||
{
|
||||
if([self respondsToSelector:aSelector])
|
||||
{
|
||||
[self performSelector:aSelector withObject:anObject];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [_nextResponder tryToPerform:aSelector with:anObject];
|
||||
}
|
||||
|
||||
// Managing a Responder's menu
|
||||
|
||||
- (void)setMenu:(CPMenu)aMenu
|
||||
{
|
||||
_menu = aMenu;
|
||||
}
|
||||
|
||||
- (CPMenu)menu
|
||||
{
|
||||
return _menu;
|
||||
}
|
||||
|
||||
// Getting the Undo Manager
|
||||
/*!
|
||||
Returns the undo manager for the receiver.
|
||||
*/
|
||||
- (CPUndoManager)undoManager
|
||||
{
|
||||
return [_nextResponder performSelector:_cmd];
|
||||
}
|
||||
|
||||
// Terminating the responder chain
|
||||
/*!
|
||||
Called when an event finds no suitable responder.
|
||||
@param anEventSelector the command that failed
|
||||
*/
|
||||
- (void)noResponderFor:(SEL)anEventSelector
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPResponderNextResponderKey = @"CPResponderNextResponderKey";
|
||||
|
||||
@implementation CPResponder (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the responder with data from a coder.
|
||||
@param aCoder the coder from which data will be read
|
||||
@return the initialized responder
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_nextResponder = [aCoder decodeObjectForKey:CPResponderNextResponderKey];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Archives the responder to a coder.
|
||||
@param aCoder the coder to which the responder will be archived
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeConditionalObject:_nextResponder forKey:CPResponderNextResponderKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,628 @@
|
||||
/*
|
||||
* CPScrollView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPView.j"
|
||||
@import "CPClipView.j"
|
||||
@import "CPScroller.j"
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
/*! @class CPScrollView
|
||||
|
||||
Used to display views that are too large for the viewing area. the CPScrollView
|
||||
places scroll bars on the side of the view to allow the user to scroll and see the entire
|
||||
contents of the view.
|
||||
*/
|
||||
@implementation CPScrollView : CPView
|
||||
{
|
||||
CPClipView _contentView;
|
||||
|
||||
BOOL _hasVerticalScroller;
|
||||
BOOL _hasHorizontalScroller;
|
||||
BOOL _autohidesScrollers;
|
||||
|
||||
CPScroller _verticalScroller;
|
||||
CPScroller _horizontalScroller;
|
||||
|
||||
int _recursionCount;
|
||||
|
||||
float _verticalLineScroll;
|
||||
float _verticalPageScroll;
|
||||
float _horizontalLineScroll;
|
||||
float _horizontalPageScroll;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_verticalLineScroll = 10.0;
|
||||
_verticalPageScroll = 10.0;
|
||||
|
||||
_horizontalLineScroll = 10.0;
|
||||
_horizontalPageScroll = 10.0;
|
||||
|
||||
_contentView = [[CPClipView alloc] initWithFrame:[self bounds]];
|
||||
|
||||
[self addSubview:_contentView];
|
||||
|
||||
[self setHasVerticalScroller:YES];
|
||||
[self setHasHorizontalScroller:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Determining component sizes
|
||||
/*!
|
||||
Returns the size of the scroll view's content view.
|
||||
*/
|
||||
- (CGRect)contentSize
|
||||
{
|
||||
return [_contentView frame].size;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the view that is scrolled for the user.
|
||||
*/
|
||||
- (id)documentView
|
||||
{
|
||||
return [_contentView documentView];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the content view that clips the document
|
||||
@param aContentView the content view
|
||||
*/
|
||||
- (void)setContentView:(CPClipView)aContentView
|
||||
{
|
||||
if (!aContentView)
|
||||
return;
|
||||
|
||||
var documentView = [aContentView documentView];
|
||||
|
||||
if (documentView)
|
||||
[documentView removeFromSuperview];
|
||||
|
||||
[_contentView removeFromSuperview];
|
||||
|
||||
var size = [self contentSize];
|
||||
|
||||
_contentView = aContentView;
|
||||
|
||||
[_contentView setFrame:CGRectMake(0.0, 0.0, size.width, size.height)];
|
||||
[_contentView setDocumentView:documentView];
|
||||
|
||||
[self addSubview:_contentView];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the content view that clips the document.
|
||||
*/
|
||||
- (CPClipView)contentView
|
||||
{
|
||||
return _contentView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the view that is scrolled for the user.
|
||||
@param aView the view that will be scrolled
|
||||
*/
|
||||
- (void)setDocumentView:(CPView)aView
|
||||
{
|
||||
[_contentView setDocumentView:aView];
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}
|
||||
|
||||
/*!
|
||||
Resizes the scroll view to contain the specified clip view.
|
||||
@param aClipView the clip view to resize to
|
||||
*/
|
||||
- (void)reflectScrolledClipView:(CPClipView)aClipView
|
||||
{
|
||||
if(_contentView != aClipView)
|
||||
return;
|
||||
|
||||
if (_recursionCount > 5)
|
||||
return;
|
||||
|
||||
++_recursionCount;
|
||||
|
||||
var documentView = [self documentView];
|
||||
|
||||
if (!documentView)
|
||||
{
|
||||
if (_autohidesScrollers)
|
||||
{
|
||||
[_verticalScroller setHidden:YES];
|
||||
[_horizontalScroller setHidden:YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_verticalScroller setEnabled:NO];
|
||||
[_horizontalScroller setEnabled:NO];
|
||||
}
|
||||
|
||||
[_contentView setFrame:[self bounds]];
|
||||
|
||||
--_recursionCount;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var documentFrame = [documentView frame],
|
||||
contentViewFrame = [self bounds],
|
||||
scrollPoint = [_contentView bounds].origin,
|
||||
difference = _CGSizeMake(CPRectGetWidth(documentFrame) - CPRectGetWidth(contentViewFrame), CPRectGetHeight(documentFrame) - CPRectGetHeight(contentViewFrame)),
|
||||
shouldShowVerticalScroller = (!_autohidesScrollers || difference.height > 0.0) && _hasVerticalScroller,
|
||||
shouldShowHorizontalScroller = (!_autohidesScrollers || difference.width > 0.0) && _hasHorizontalScroller,
|
||||
wasShowingVerticalScroller = ![_verticalScroller isHidden],
|
||||
wasShowingHorizontalScroller = ![_horizontalScroller isHidden],
|
||||
verticalScrollerWidth = [CPScroller scrollerWidthForControlSize:[_verticalScroller controlSize]],
|
||||
horizontalScrollerHeight = [CPScroller scrollerWidthForControlSize:[_horizontalScroller controlSize]];
|
||||
|
||||
if (_autohidesScrollers)
|
||||
{
|
||||
if (shouldShowVerticalScroller)
|
||||
shouldShowHorizontalScroller = (!_autohidesScrollers || difference.width > -verticalScrollerWidth) && _hasHorizontalScroller;
|
||||
if (shouldShowHorizontalScroller)
|
||||
shouldShowVerticalScroller = (!_autohidesScrollers || difference.height > -horizontalScrollerHeight) && _hasVerticalScroller;
|
||||
}
|
||||
|
||||
[_verticalScroller setHidden:!shouldShowVerticalScroller];
|
||||
[_verticalScroller setEnabled:!_autohidesScrollers && difference.height < 0];
|
||||
|
||||
[_horizontalScroller setHidden:!shouldShowHorizontalScroller];
|
||||
[_horizontalScroller setEnabled:!_autohidesScrollers && difference.width < 0];
|
||||
|
||||
if (shouldShowVerticalScroller)
|
||||
{
|
||||
var verticalScrollerHeight = CPRectGetHeight(contentViewFrame);
|
||||
|
||||
if (shouldShowHorizontalScroller)
|
||||
verticalScrollerHeight -= horizontalScrollerHeight;
|
||||
|
||||
difference.width += verticalScrollerWidth;
|
||||
contentViewFrame.size.width -= verticalScrollerWidth;
|
||||
|
||||
[_verticalScroller setFloatValue:(difference.height <= 0.0) ? 0.0 : scrollPoint.y / difference.height
|
||||
knobProportion:CPRectGetHeight(contentViewFrame) / CPRectGetHeight(documentFrame)];
|
||||
[_verticalScroller setFrame:CPRectMake(CPRectGetMaxX(contentViewFrame), 0.0, verticalScrollerWidth, verticalScrollerHeight)];
|
||||
}
|
||||
else if (wasShowingVerticalScroller)
|
||||
[_verticalScroller setFloatValue:0.0 knobProportion:1.0];
|
||||
|
||||
if (shouldShowHorizontalScroller)
|
||||
{
|
||||
difference.height += horizontalScrollerHeight;
|
||||
contentViewFrame.size.height -= horizontalScrollerHeight;
|
||||
|
||||
[_horizontalScroller setFloatValue:(difference.width <= 0.0) ? 0.0 : scrollPoint.x / difference.width
|
||||
knobProportion:CPRectGetWidth(contentViewFrame) / CPRectGetWidth(documentFrame)];
|
||||
[_horizontalScroller setFrame:CPRectMake(0.0, CPRectGetMaxY(contentViewFrame), CPRectGetWidth(contentViewFrame), horizontalScrollerHeight)];
|
||||
}
|
||||
else if (wasShowingHorizontalScroller)
|
||||
[_horizontalScroller setFloatValue:0.0 knobProportion:1.0];
|
||||
|
||||
[_contentView setFrame:contentViewFrame];
|
||||
|
||||
--_recursionCount;
|
||||
}
|
||||
|
||||
// Managing Scrollers
|
||||
/*!
|
||||
Sets the scroll view's horizontal scroller.
|
||||
@param aScroller the horizontal scroller for the scroll view
|
||||
*/
|
||||
- (void)setHorizontalScroller:(CPScroller)aScroller
|
||||
{
|
||||
if (_horizontalScroller == aScroller)
|
||||
return;
|
||||
|
||||
[_horizontalScroller removeFromSuperview];
|
||||
[_horizontalScroller setTarget:nil];
|
||||
[_horizontalScroller setAction:nil];
|
||||
|
||||
_horizontalScroller = aScroller;
|
||||
|
||||
[_horizontalScroller setTarget:self];
|
||||
[_horizontalScroller setAction:@selector(_horizontalScrollerDidScroll:)];
|
||||
|
||||
[self addSubview:_horizontalScroller];
|
||||
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the scroll view's horizontal scroller
|
||||
*/
|
||||
- (CPScroller)horizontalScroller
|
||||
{
|
||||
return _horizontalScroller;
|
||||
}
|
||||
|
||||
/*!
|
||||
Specifies whether the scroll view can have a horizontal scroller.
|
||||
@param hasHorizontalScroller <code>YES</code> lets the scroll view
|
||||
allocate a horizontal scroller if necessary.
|
||||
*/
|
||||
- (void)setHasHorizontalScroller:(BOOL)shouldHaveHorizontalScroller
|
||||
{
|
||||
if (_hasHorizontalScroller == shouldHaveHorizontalScroller)
|
||||
return;
|
||||
|
||||
_hasHorizontalScroller = shouldHaveHorizontalScroller;
|
||||
|
||||
if (_hasHorizontalScroller && !_horizontalScroller)
|
||||
[self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CPRectMake(0.0, 0.0, CPRectGetWidth([self bounds]), [CPScroller scrollerWidth])]];
|
||||
|
||||
else if (!_hasHorizontalScroller && _horizontalScroller)
|
||||
{
|
||||
[_horizontalScroller setHidden:YES];
|
||||
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the scroll view can have a horizontal scroller.
|
||||
*/
|
||||
- (BOOL)hasHorizontalScroller
|
||||
{
|
||||
return _hasHorizontalScroller;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the scroll view's vertical scroller.
|
||||
@param aScroller the vertical scroller
|
||||
*/
|
||||
- (void)setVerticalScroller:(CPScroller)aScroller
|
||||
{
|
||||
if (_verticalScroller == aScroller)
|
||||
return;
|
||||
|
||||
[_verticalScroller removeFromSuperview];
|
||||
[_verticalScroller setTarget:nil];
|
||||
[_verticalScroller setAction:nil];
|
||||
|
||||
_verticalScroller = aScroller;
|
||||
|
||||
[_verticalScroller setTarget:self];
|
||||
[_verticalScroller setAction:@selector(_verticalScrollerDidScroll:)];
|
||||
|
||||
[self addSubview:_verticalScroller];
|
||||
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return's the scroll view's vertical scroller
|
||||
*/
|
||||
- (CPScroller)verticalScroller
|
||||
{
|
||||
return _verticalScroller;
|
||||
}
|
||||
|
||||
/*!
|
||||
Specifies whether the scroll view has can have
|
||||
a vertical scroller. It allocates it if necessary.
|
||||
@param hasVerticalScroller <code>YES</code> allows
|
||||
the scroll view to display a vertical scroller
|
||||
*/
|
||||
- (void)setHasVerticalScroller:(BOOL)shouldHaveVerticalScroller
|
||||
{
|
||||
if (_hasVerticalScroller == shouldHaveVerticalScroller)
|
||||
return;
|
||||
|
||||
_hasVerticalScroller = shouldHaveVerticalScroller;
|
||||
|
||||
if (_hasVerticalScroller && !_verticalScroller)
|
||||
[self setVerticalScroller:[[CPScroller alloc] initWithFrame:CPRectMake(0.0, 0.0, [CPScroller scrollerWidth], CPRectGetHeight([self bounds]))]];
|
||||
|
||||
else if (!_hasVerticalScroller && _verticalScroller)
|
||||
{
|
||||
[_verticalScroller setHidden:YES];
|
||||
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the scroll view can have a vertical scroller.
|
||||
*/
|
||||
- (BOOL)hasHorizontalScroller
|
||||
{
|
||||
return _hasHorizontalScroller;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the scroll view hides its scoll bars when not needed.
|
||||
@param autohidesScrollers <code>YES</code> causes the scroll bars
|
||||
to be hidden when not needed.
|
||||
*/
|
||||
- (void)setAutohidesScrollers:(BOOL)autohidesScrollers
|
||||
{
|
||||
if (_autohidesScrollers == autohidesScrollers)
|
||||
return;
|
||||
|
||||
_autohidesScrollers = autohidesScrollers;
|
||||
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the scroll view hides its scroll
|
||||
bars when not necessary.
|
||||
*/
|
||||
- (BOOL)autohidesScrollers
|
||||
{
|
||||
return _autohidesScrollers;
|
||||
}
|
||||
/*
|
||||
- (void)setFrameSize:(CPRect)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}*/
|
||||
|
||||
/* @ignore */
|
||||
- (void)_verticalScrollerDidScroll:(CPScroller)aScroller
|
||||
{
|
||||
var value = [aScroller floatValue],
|
||||
documentFrame = [[_contentView documentView] frame];
|
||||
contentBounds = [_contentView bounds];
|
||||
|
||||
switch ([_verticalScroller hitPart])
|
||||
{
|
||||
case CPScrollerDecrementLine: contentBounds.origin.y -= _verticalLineScroll;
|
||||
break;
|
||||
|
||||
case CPScrollerIncrementLine: contentBounds.origin.y += _verticalLineScroll;
|
||||
break;
|
||||
|
||||
case CPScrollerDecrementPage: contentBounds.origin.y -= _CGRectGetHeight(contentBounds) - _verticalPageScroll;
|
||||
break;
|
||||
|
||||
case CPScrollerIncrementPage: contentBounds.origin.y += _CGRectGetHeight(contentBounds) - _verticalPageScroll;
|
||||
break;
|
||||
|
||||
case CPScrollerKnobSlot:
|
||||
case CPScrollerKnob:
|
||||
default: contentBounds.origin.y = value * (_CGRectGetHeight(documentFrame) - _CGRectGetHeight(contentBounds));
|
||||
}
|
||||
|
||||
[_contentView scrollToPoint:contentBounds.origin];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_horizontalScrollerDidScroll:(CPScroller)aScroller
|
||||
{
|
||||
var value = [aScroller floatValue],
|
||||
documentFrame = [[self documentView] frame],
|
||||
contentBounds = [_contentView bounds];
|
||||
|
||||
switch ([_horizontalScroller hitPart])
|
||||
{
|
||||
case CPScrollerDecrementLine: contentBounds.origin.x -= _horizontalLineScroll;
|
||||
break;
|
||||
|
||||
case CPScrollerIncrementLine: contentBounds.origin.x += _horizontalLineScroll;
|
||||
break;
|
||||
|
||||
case CPScrollerDecrementPage: contentBounds.origin.x -= _CGRectGetWidth(contentBounds) - _horizontalPageScroll;
|
||||
break;
|
||||
|
||||
case CPScrollerIncrementPage: contentBounds.origin.x += _CGRectGetWidth(contentBounds) - _horizontalPageScroll;
|
||||
break;
|
||||
|
||||
case CPScrollerKnobSlot:
|
||||
case CPScrollerKnob:
|
||||
default: contentBounds.origin.x = value * (_CGRectGetWidth(documentFrame) - _CGRectGetWidth(contentBounds));
|
||||
}
|
||||
|
||||
[_contentView scrollToPoint:contentBounds.origin];
|
||||
}
|
||||
|
||||
/*!
|
||||
Lays out the scroll view's components.
|
||||
*/
|
||||
- (void)tile
|
||||
{
|
||||
// yuck.
|
||||
// RESIZE: tile->setHidden AND refl
|
||||
// Outside Change: refl->tile->setHidden AND refl
|
||||
// scroll: refl.
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
-(void)resizeSubviewsWithOldSize:(CGSize)aSize
|
||||
{
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}
|
||||
|
||||
// Setting Scrolling Behavior
|
||||
/*!
|
||||
Sets how much the document moves when scrolled. Sets the vertical and horizontal scroll.
|
||||
@param aLineScroll the amount to move the document when scrolled
|
||||
*/
|
||||
- (void)setLineScroll:(float)aLineScroll
|
||||
{
|
||||
[self setHorizonalLineScroll:aLineScroll];
|
||||
[self setVerticalLineScroll:aLineScroll];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns how much the document moves when scrolled
|
||||
*/
|
||||
- (float)lineScroll
|
||||
{
|
||||
return [self horizontalLineScroll];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets how much the document moves when scrolled horizontally.
|
||||
@param aLineScroll the amount to move horizontally when scrolled.
|
||||
*/
|
||||
- (void)setHorizontalLineScroll:(float)aLineScroll
|
||||
{
|
||||
_horizontalLineScroll = aLineScroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns how much the document moves horizontally when scrolled.
|
||||
*/
|
||||
- (float)horizontalLineScroll
|
||||
{
|
||||
return _horizontalLineScroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets how much the document moves when scrolled vertically.
|
||||
@param aLineScroll the new amount to move vertically when scrolled.
|
||||
*/
|
||||
- (void)setVerticalLineScroll:(float)aLineScroll
|
||||
{
|
||||
_verticalLineScroll = aLineScroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns how much the document moves vertically when scrolled.
|
||||
*/
|
||||
- (float)verticalLineScroll
|
||||
{
|
||||
return _verticalLineScroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the horizontal and vertical page scroll amount.
|
||||
@param aPageScroll the new horizontal and vertical page scroll amount
|
||||
*/
|
||||
- (void)setPageScroll:(float)aPageScroll
|
||||
{
|
||||
[self setHorizontalPageScroll:aPageScroll];
|
||||
[self setVerticalPageScroll:aPageScroll];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the vertical and horizontal page scroll amount.
|
||||
*/
|
||||
- (float)pageScroll
|
||||
{
|
||||
return [self horizontalPageScroll];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the horizontal page scroll amount.
|
||||
@param aPageScroll the new horizontal page scroll amount
|
||||
*/
|
||||
- (void)setHorizontalPageScroll:(float)aPageScroll
|
||||
{
|
||||
_horizontalPageScroll = aPageScroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the horizontal page scroll amount.
|
||||
*/
|
||||
- (float)horizontalPageScroll
|
||||
{
|
||||
return _horizontalPageScroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the vertical page scroll amount.
|
||||
@param aPageScroll the new vertcal page scroll amount
|
||||
*/
|
||||
- (void)setVerticalPageScroll:(float)aPageScroll
|
||||
{
|
||||
_verticalPageScroll = aPageScroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the vertical page scroll amount.
|
||||
*/
|
||||
- (float)verticalPageScroll
|
||||
{
|
||||
return _verticalPageScroll;
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles a scroll wheel event from the user.
|
||||
@param anEvent the scroll wheel event
|
||||
*/
|
||||
- (void)scrollWheel:(CPEvent)anEvent
|
||||
{
|
||||
var value = [_verticalScroller floatValue],
|
||||
documentFrame = [[self documentView] frame],
|
||||
contentBounds = [_contentView bounds];
|
||||
|
||||
contentBounds.origin.x += [anEvent deltaX] * _horizontalLineScroll;
|
||||
contentBounds.origin.y += [anEvent deltaY] * _verticalLineScroll;
|
||||
|
||||
[_contentView scrollToPoint:contentBounds.origin];
|
||||
}
|
||||
|
||||
- (void)keyDown:(CPEvent)anEvent
|
||||
{
|
||||
var keyCode = [anEvent keyCode],
|
||||
value = [_verticalScroller floatValue],
|
||||
documentFrame = [[self documentView] frame],
|
||||
contentBounds = [_contentView bounds];
|
||||
|
||||
switch (keyCode)
|
||||
{
|
||||
case 33: /*pageup*/
|
||||
contentBounds.origin.y -= _CGRectGetHeight(contentBounds) - _verticalPageScroll;
|
||||
break;
|
||||
|
||||
case 34: /*pagedown*/
|
||||
contentBounds.origin.y += _CGRectGetHeight(contentBounds) - _verticalPageScroll;
|
||||
break;
|
||||
|
||||
case 38: /*up arrow*/
|
||||
contentBounds.origin.y -= _verticalLineScroll;
|
||||
break;
|
||||
|
||||
case 40: /*down arrow*/
|
||||
contentBounds.origin.y += _verticalLineScroll;
|
||||
break;
|
||||
|
||||
case 37: /*left arrow*/
|
||||
contentBounds.origin.x -= _horizontalLineScroll;
|
||||
break;
|
||||
|
||||
case 49: /*right arrow*/
|
||||
contentBounds.origin.x += _horizontalLineScroll;
|
||||
break;
|
||||
|
||||
default: return [super keyDown:anEvent];
|
||||
}
|
||||
|
||||
[_contentView scrollToPoint:contentBounds.origin];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,696 @@
|
||||
/*
|
||||
* CPScroller.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPControl.j"
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
// CPScroller Constants
|
||||
CPScrollerNoPart = 0;
|
||||
CPScrollerDecrementPage = 1;
|
||||
CPScrollerKnob = 2;
|
||||
CPScrollerIncrementPage = 3;
|
||||
CPScrollerDecrementLine = 4;
|
||||
CPScrollerIncrementLine = 5;
|
||||
CPScrollerKnobSlot = 6;
|
||||
|
||||
CPScrollerIncrementArrow = 0;
|
||||
CPScrollerDecrementArrow = 1;
|
||||
|
||||
CPNoScrollerParts = 0;
|
||||
CPOnlyScrollerArrows = 1;
|
||||
CPAllScrollerParts = 2;
|
||||
|
||||
var _CPScrollerWidths = [],
|
||||
_CPScrollerKnobMinimumHeights = [],
|
||||
_CPScrollerArrowHeights = [],
|
||||
_CPScrollerArrowUsableHeights = [];
|
||||
|
||||
_CPScrollerWidths[CPRegularControlSize] = 14.0;
|
||||
_CPScrollerWidths[CPSmallControlSize] = 11.0;
|
||||
_CPScrollerWidths[CPMiniControlSize] = 11.0; // FIXME
|
||||
|
||||
_CPScrollerKnobMinimumHeights[CPRegularControlSize] = 18.0;
|
||||
_CPScrollerKnobMinimumHeights[CPSmallControlSize] = 12.0;
|
||||
_CPScrollerKnobMinimumHeights[CPMiniControlSize] = 12.0; // FIXME
|
||||
|
||||
_CPScrollerArrowHeights[CPRegularControlSize] = 21.0;
|
||||
_CPScrollerArrowHeights[CPSmallControlSize] = 16.0;
|
||||
_CPScrollerArrowHeights[CPMiniControlSize] = 16.0; // FIXME
|
||||
|
||||
_CPScrollerArrowUsableHeights[CPRegularControlSize] = 16.0
|
||||
_CPScrollerArrowUsableHeights[CPSmallControlSize] = 10.0;
|
||||
_CPScrollerArrowUsableHeights[CPMiniControlSize] = 10.0; // FIXME
|
||||
|
||||
var _CPScrollerKnobIdentifier = @"Knob",
|
||||
_CPScrollerKnobSlotIdentifier = @"KnobSlot",
|
||||
_CPScrollerDecrementArrowIdentifier = @"DecrementArrow",
|
||||
_CPScrollerIncrementArrowIdentifier = @"IncrementArrow",
|
||||
_CPScrollerHorizontalIdentifier = @"Horizontal",
|
||||
_CPScrollerVerticalIdentifier = @"Vertical",
|
||||
_CPScrollerHighlightedIdentifier = @"Highlighted",
|
||||
_CPScrollerDisabledIdentifier = @"Disabled";
|
||||
|
||||
var _CPScrollerClassName = nil,
|
||||
_CPScrollerPartSizes = {};
|
||||
|
||||
/*! @class CPScroller
|
||||
|
||||
*/
|
||||
|
||||
@implementation CPScroller : CPControl
|
||||
{
|
||||
CPControlSize _controlSize;
|
||||
CPUsableScrollerParts _usableParts;
|
||||
CPArray _partRects;
|
||||
|
||||
BOOL _isHorizontal;
|
||||
float _knobProportion;
|
||||
|
||||
CPScrollerPart _hitPart;
|
||||
|
||||
CPScrollerPart _trackingPart;
|
||||
float _trackingFloatValue;
|
||||
CGPoint _trackingStartPoint;
|
||||
|
||||
CPView _knobView;
|
||||
CPView _knobSlotView;
|
||||
|
||||
CPView _decrementArrowView;
|
||||
CPView _incrementArrowView;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPScroller class])
|
||||
return;
|
||||
|
||||
_CPScrollerClassName = [self className];
|
||||
|
||||
var regularIdentifier = _CPControlIdentifierForControlSize(CPRegularControlSize),
|
||||
smallIdentifier = _CPControlIdentifierForControlSize(CPSmallControlSize),
|
||||
miniIdentifier = _CPControlIdentifierForControlSize(CPMiniControlSize);
|
||||
|
||||
// Horizontal Knob Sizes
|
||||
var prefix = _CPScrollerClassName + _CPScrollerKnobIdentifier + _CPScrollerHorizontalIdentifier;
|
||||
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier] = [_CGSizeMake(9.0, _CPScrollerWidths[CPRegularControlSize]), _CGSizeMake(1.0, _CPScrollerWidths[CPRegularControlSize]), _CGSizeMake(9.0, _CPScrollerWidths[CPRegularControlSize])];
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier] = [_CGSizeMake(6.0, _CPScrollerWidths[CPSmallControlSize]), _CGSizeMake(1.0, _CPScrollerWidths[CPSmallControlSize]), _CGSizeMake(6.0, _CPScrollerWidths[CPSmallControlSize])];
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier] = [_CGSizeMake(6.0, _CPScrollerWidths[CPMiniControlSize]), _CGSizeMake(1.0, _CPScrollerWidths[CPMiniControlSize]), _CGSizeMake(6.0, _CPScrollerWidths[CPMiniControlSize])];
|
||||
|
||||
// Vertical Knob Sizes
|
||||
var prefix = _CPScrollerClassName + _CPScrollerKnobIdentifier + _CPScrollerVerticalIdentifier;
|
||||
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier] = [_CGSizeMake(_CPScrollerWidths[CPRegularControlSize], 9.0), _CGSizeMake(_CPScrollerWidths[CPRegularControlSize], 1.0), _CGSizeMake(_CPScrollerWidths[CPRegularControlSize], 9.0)];
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier] = [_CGSizeMake(_CPScrollerWidths[CPSmallControlSize], 6.0), _CGSizeMake(_CPScrollerWidths[CPSmallControlSize], 1.0), _CGSizeMake(_CPScrollerWidths[CPSmallControlSize], 6.0)];
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier] = [_CGSizeMake(_CPScrollerWidths[CPMiniControlSize], 6.0), _CGSizeMake(_CPScrollerWidths[CPMiniControlSize], 1.0), _CGSizeMake(_CPScrollerWidths[CPMiniControlSize], 6.0)];
|
||||
|
||||
// Horizontal Knob Slot Sizes
|
||||
var prefix = _CPScrollerClassName + _CPScrollerKnobSlotIdentifier + _CPScrollerHorizontalIdentifier;
|
||||
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier] = _CGSizeMake(1.0, _CPScrollerWidths[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier] = _CGSizeMake(1.0, _CPScrollerWidths[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier] = _CGSizeMake(1.0, _CPScrollerWidths[CPMiniControlSize]);
|
||||
|
||||
// Vertical Knob Slot Sizes
|
||||
var prefix = _CPScrollerClassName + _CPScrollerKnobSlotIdentifier + _CPScrollerVerticalIdentifier;
|
||||
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier] = _CGSizeMake(_CPScrollerWidths[CPRegularControlSize], 1.0);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier] = _CGSizeMake(_CPScrollerWidths[CPSmallControlSize], 1.0);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier] = _CGSizeMake(_CPScrollerWidths[CPMiniControlSize], 1.0);
|
||||
|
||||
// Horizontal Decrement Arrows Sizes
|
||||
var prefix = _CPScrollerClassName + _CPScrollerDecrementArrowIdentifier + _CPScrollerHorizontalIdentifier;
|
||||
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPRegularControlSize], _CPScrollerWidths[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPRegularControlSize], _CPScrollerWidths[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPSmallControlSize] , _CPScrollerWidths[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPSmallControlSize] , _CPScrollerWidths[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPMiniControlSize], _CPScrollerWidths[CPMiniControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPMiniControlSize], _CPScrollerWidths[CPMiniControlSize]);
|
||||
|
||||
// Vertical Decrement Arrows Sizes
|
||||
var prefix = _CPScrollerClassName + _CPScrollerDecrementArrowIdentifier + _CPScrollerVerticalIdentifier;
|
||||
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier] = _CGSizeMake(_CPScrollerWidths[CPRegularControlSize], _CPScrollerArrowHeights[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerWidths[CPRegularControlSize], _CPScrollerArrowHeights[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier] = _CGSizeMake(_CPScrollerWidths[CPSmallControlSize], _CPScrollerArrowHeights[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerWidths[CPSmallControlSize], _CPScrollerArrowHeights[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier] = _CGSizeMake(_CPScrollerWidths[CPMiniControlSize], _CPScrollerArrowHeights[CPMiniControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerWidths[CPMiniControlSize], _CPScrollerArrowHeights[CPMiniControlSize]);
|
||||
|
||||
// Horizontal Increment Arrows Sizes
|
||||
var prefix = _CPScrollerClassName + _CPScrollerIncrementArrowIdentifier + _CPScrollerHorizontalIdentifier;
|
||||
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPRegularControlSize], _CPScrollerWidths[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPRegularControlSize], _CPScrollerWidths[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPSmallControlSize], _CPScrollerWidths[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPSmallControlSize], _CPScrollerWidths[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPMiniControlSize], _CPScrollerWidths[CPMiniControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerArrowHeights[CPMiniControlSize], _CPScrollerWidths[CPMiniControlSize]);
|
||||
|
||||
// Vertical Increment Arrows Sizes
|
||||
var prefix = _CPScrollerClassName + _CPScrollerIncrementArrowIdentifier + _CPScrollerVerticalIdentifier;
|
||||
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier] = _CGSizeMake(_CPScrollerWidths[CPRegularControlSize], _CPScrollerArrowHeights[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + regularIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerWidths[CPRegularControlSize], _CPScrollerArrowHeights[CPRegularControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier] = _CGSizeMake(_CPScrollerWidths[CPSmallControlSize], _CPScrollerArrowHeights[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + smallIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerWidths[CPSmallControlSize], _CPScrollerArrowHeights[CPSmallControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier] = _CGSizeMake(_CPScrollerWidths[CPMiniControlSize], _CPScrollerArrowHeights[CPMiniControlSize]);
|
||||
_CPScrollerPartSizes[prefix + miniIdentifier + _CPScrollerHighlightedIdentifier] = _CGSizeMake(_CPScrollerWidths[CPMiniControlSize], _CPScrollerArrowHeights[CPMiniControlSize]);
|
||||
}
|
||||
|
||||
// Calculating Layout
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_controlSize = CPRegularControlSize;
|
||||
_partRects = [];
|
||||
|
||||
[self setFloatValue:0.0 knobProportion:1.0];
|
||||
|
||||
_isHorizontal = CPRectGetWidth(aFrame) > CPRectGetHeight(aFrame);
|
||||
|
||||
_hitPart = CPScrollerNoPart;
|
||||
|
||||
[self checkSpaceForParts];
|
||||
[self drawParts];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Determining CPScroller Size
|
||||
/*!
|
||||
Returns the CPScroller's width for a CPRegularControlSize.
|
||||
*/
|
||||
+ (float)scrollerWidth
|
||||
{
|
||||
return [self scrollerWidthForControlSize:CPRegularControlSize];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the width of a CPScroller for the specified CPControlSize.
|
||||
@param aControlSize the size of a controller to return the width for
|
||||
*/
|
||||
+ (float)scrollerWidthForControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
return _CPScrollerWidths[aControlSize];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the scroller's size.
|
||||
@param aControlSize the scroller's size
|
||||
*/
|
||||
- (void)setControlSize:(CPControlSize)aControlSize
|
||||
{
|
||||
if (_controlSize == aControlSize)
|
||||
return;
|
||||
|
||||
_controlSize = aControlSize;
|
||||
|
||||
[self drawKnobSlot];
|
||||
[self drawKnob];
|
||||
[self drawArrow:CPScrollerDecrementArrow highlight:NO];
|
||||
[self drawArrow:CPScrollerIncrementArrow highlight:NO];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the scroller's control size
|
||||
*/
|
||||
- (CPControlSize)controlSize
|
||||
{
|
||||
return _controlSize;
|
||||
}
|
||||
|
||||
// Setting the Knob Position
|
||||
/*!
|
||||
Sets the scroller's knob position (ranges from 0.0 to 1.0).
|
||||
@param aValue the knob position (ranges from 0.0 to 1.0)
|
||||
*/
|
||||
- (void)setFloatValue:(float)aValue
|
||||
{
|
||||
[super setFloatValue:MIN(1.0, MAX(0.0, aValue))];
|
||||
|
||||
[self checkSpaceForParts];
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the position and proportion of the knob.
|
||||
@param aValue the knob position (ranges from 0.0 to 1.0)
|
||||
@param aProportion the knob's proportion (ranges from 0.0 to 1.0)
|
||||
*/
|
||||
- (void)setFloatValue:(float)aValue knobProportion:(float)aProportion
|
||||
{
|
||||
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
|
||||
|
||||
[self setFloatValue:aValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Return's the knob's proportion
|
||||
*/
|
||||
- (float)knobProportion
|
||||
{
|
||||
return _knobProportion;
|
||||
}
|
||||
|
||||
// Calculating Layout
|
||||
|
||||
- (CGRect)rectForPart:(CPScrollerPart)aPart
|
||||
{
|
||||
if (aPart == CPScrollerNoPart)
|
||||
return _CGRectMakeZero();
|
||||
|
||||
return _partRects[aPart];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the part of the scroller that would be hit by <code>aPoint</code>.
|
||||
@param aPoint the simulated point hit
|
||||
@return the part of the scroller that intersects the point
|
||||
*/
|
||||
- (CPScrollerPart)testPart:(CGPoint)aPoint
|
||||
{
|
||||
aPoint = [self convertPoint:aPoint fromView:nil];
|
||||
|
||||
// The ordering of these tests is important. We check the knob and
|
||||
// page rects first since they may overlap with the arrows.
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
|
||||
return CPScrollerKnob;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementPage], aPoint))
|
||||
return CPScrollerDecrementPage;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementPage], aPoint))
|
||||
return CPScrollerIncrementPage;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementLine], aPoint))
|
||||
return CPScrollerDecrementLine;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerIncrementLine], aPoint))
|
||||
return CPScrollerIncrementLine;
|
||||
|
||||
if (CGRectContainsPoint([self rectForPart:CPScrollerKnobSlot], aPoint))
|
||||
return CPScrollerKnobSlot;
|
||||
|
||||
return CPScrollerNoPart;
|
||||
}
|
||||
|
||||
/*!
|
||||
Check if there's enough space in the scroller to display the knob
|
||||
*/
|
||||
- (void)checkSpaceForParts
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
|
||||
// Assume we won't be needing the arrows.
|
||||
if (_knobProportion == 1.0)
|
||||
{
|
||||
_usableParts = CPNoScrollerParts;
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerKnob] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerIncrementPage] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMakeZero();
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMakeZero();
|
||||
|
||||
// In this case, the slot is the entirety of the scroller.
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMakeCopy(bounds);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var width = _CGRectGetWidth(bounds),
|
||||
height = _CGRectGetHeight(bounds),
|
||||
usableArrowHeight = _CPScrollerArrowUsableHeights[_controlSize],
|
||||
slotWidth = (_isHorizontal ? width : height) - 2.0 * usableArrowHeight,
|
||||
knobWidth = MAX(_CPScrollerKnobMinimumHeights[_controlSize], (slotWidth * _knobProportion));
|
||||
|
||||
_usableParts = CPAllScrollerParts;
|
||||
|
||||
var arrowHeight = _CPScrollerArrowHeights[_controlSize],
|
||||
knobLocation = usableArrowHeight + (slotWidth - knobWidth) * [self floatValue];
|
||||
|
||||
// At this point we know we're going to need arrows.
|
||||
if (_isHorizontal)
|
||||
{
|
||||
// ASSERT(_CPScrollerWidths[_controlSize] == height)
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = _CGRectMake(usableArrowHeight, 0.0, knobLocation - usableArrowHeight, height);
|
||||
_partRects[CPScrollerKnob] = _CGRectMake(knobLocation, 0.0, knobWidth, _CPScrollerWidths[_controlSize]);
|
||||
_partRects[CPScrollerIncrementPage] = _CGRectMake(knobLocation + knobWidth, 0.0, width - (knobLocation + knobWidth) - usableArrowHeight, height);
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(usableArrowHeight, 0.0, slotWidth, height);
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, arrowHeight, height);
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMake(width - _CPScrollerArrowHeights[_controlSize], 0.0, arrowHeight, height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ASSERT(_CPScrollerWidths[_controlSize] == width)
|
||||
|
||||
_partRects[CPScrollerDecrementPage] = _CGRectMake(0.0, usableArrowHeight, width, knobLocation - usableArrowHeight);
|
||||
_partRects[CPScrollerKnob] = _CGRectMake(0.0, knobLocation, _CPScrollerWidths[_controlSize], knobWidth);
|
||||
_partRects[CPScrollerIncrementPage] = _CGRectMake(0.0, knobLocation + knobWidth, width, height - (knobLocation + knobWidth) - usableArrowHeight);
|
||||
_partRects[CPScrollerKnobSlot] = _CGRectMake(0.0, usableArrowHeight, width, slotWidth);
|
||||
_partRects[CPScrollerDecrementLine] = _CGRectMake(0.0, 0.0, width, arrowHeight);
|
||||
_partRects[CPScrollerIncrementLine] = _CGRectMake(0.0, height - _CPScrollerArrowHeights[_controlSize], width, arrowHeight);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns all the parts of the scroller that
|
||||
are usable for displaying.
|
||||
*/
|
||||
- (CPUsableScrollerParts)usableParts
|
||||
{
|
||||
return _usableParts;
|
||||
}
|
||||
|
||||
// Drawing the Parts
|
||||
/*!
|
||||
Draws the specified arrow and sets the highlight.
|
||||
@param anArrow the arrow to draw
|
||||
@param shouldHighlight sets whether the arrow should be highlighted
|
||||
*/
|
||||
- (void)drawArrow:(CPScrollerArrow)anArrow highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
var identifier = (anArrow == CPScrollerDecrementArrow ? _CPScrollerDecrementArrowIdentifier : _CPScrollerIncrementArrowIdentifier),
|
||||
arrowView = (anArrow == CPScrollerDecrementArrow ? _decrementArrowView : _incrementArrowView);
|
||||
|
||||
[arrowView setBackgroundColor:_CPControlColorWithPatternImage(
|
||||
_CPScrollerPartSizes,
|
||||
_CPScrollerClassName,
|
||||
identifier,
|
||||
_isHorizontal ? _CPScrollerHorizontalIdentifier : _CPScrollerVerticalIdentifier,
|
||||
_CPControlIdentifierForControlSize(_controlSize),
|
||||
shouldHighlight ? _CPScrollerHighlightedIdentifier : @"")];
|
||||
}
|
||||
|
||||
/*!
|
||||
Draws the knob
|
||||
*/
|
||||
- (void)drawKnob
|
||||
{
|
||||
[_knobView setBackgroundColor:_CPControlThreePartImagePattern(
|
||||
!_isHorizontal,
|
||||
_CPScrollerPartSizes,
|
||||
_CPScrollerClassName,
|
||||
_CPScrollerKnobIdentifier,
|
||||
_isHorizontal ? _CPScrollerHorizontalIdentifier : _CPScrollerVerticalIdentifier,
|
||||
_CPControlIdentifierForControlSize(_controlSize))];
|
||||
}
|
||||
|
||||
/*!
|
||||
Draws the knob's slot
|
||||
*/
|
||||
- (void)drawKnobSlot
|
||||
{
|
||||
[_knobSlotView setBackgroundColor:_CPControlColorWithPatternImage(
|
||||
_CPScrollerPartSizes,
|
||||
_CPScrollerClassName,
|
||||
_CPScrollerKnobSlotIdentifier,
|
||||
_isHorizontal ? _CPScrollerHorizontalIdentifier : _CPScrollerVerticalIdentifier,
|
||||
_CPControlIdentifierForControlSize(_controlSize))];
|
||||
}
|
||||
|
||||
/*!
|
||||
Caches images for the scroll arrow and knob.
|
||||
*/
|
||||
- (void)drawParts
|
||||
{
|
||||
_knobSlotView = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[_knobSlotView setHitTests:NO];
|
||||
|
||||
[self addSubview:_knobSlotView];
|
||||
|
||||
[self drawKnobSlot];
|
||||
|
||||
_knobView = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[_knobView setHitTests:NO];
|
||||
|
||||
[self addSubview:_knobView];
|
||||
|
||||
[self drawKnob];
|
||||
|
||||
_decrementArrowView = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[_decrementArrowView setHitTests:NO];
|
||||
|
||||
[self addSubview:_decrementArrowView];
|
||||
|
||||
[self drawArrow:CPScrollerDecrementArrow highlight:NO];
|
||||
|
||||
_incrementArrowView = [[CPView alloc] initWithFrame:_CGRectMakeZero()];
|
||||
|
||||
[_incrementArrowView setHitTests:NO];
|
||||
|
||||
[self addSubview:_incrementArrowView];
|
||||
|
||||
[self drawArrow:CPScrollerIncrementArrow highlight:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Draws the scroller's arrow with a possible highlight,
|
||||
if the user's mouse is over it.
|
||||
@param shouldHighlight <code>YES</code> will draw the
|
||||
arrow highlighted if the mouse is hovering over it.
|
||||
*/
|
||||
- (void)highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
if (_trackingPart == CPScrollerDecrementLine)
|
||||
[self drawArrow:CPScrollerDecrementArrow highlight:shouldHighlight];
|
||||
|
||||
else if (_trackingPart == CPScrollerIncrementLine)
|
||||
[self drawArrow:CPScrollerIncrementArrow highlight:shouldHighlight];
|
||||
}
|
||||
|
||||
// Event Handling
|
||||
/*!
|
||||
Returns the part of the scroller that was hit.
|
||||
*/
|
||||
- (CPScrollerPart)hitPart
|
||||
{
|
||||
return _hitPart;
|
||||
}
|
||||
|
||||
/*!
|
||||
Tracks the knob.
|
||||
@param anEvent the input event
|
||||
*/
|
||||
- (void)trackKnob:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type];
|
||||
|
||||
if (type == CPLeftMouseUp)
|
||||
{
|
||||
_hitPart = CPScrollerNoPart;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == CPLeftMouseDown)
|
||||
{
|
||||
_trackingFloatValue = [self floatValue];
|
||||
_trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
}
|
||||
|
||||
else if (type == CPLeftMouseDragged)
|
||||
{
|
||||
var knobRect = [self rectForPart:CPScrollerKnob],
|
||||
knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
|
||||
remainder = _isHorizontal ? (_CGRectGetWidth(knobSlotRect) - _CGRectGetWidth(knobRect)) : (_CGRectGetHeight(knobSlotRect) - _CGRectGetHeight(knobRect));
|
||||
|
||||
if (remainder <= 0)
|
||||
[self setFloatValue:0.0];
|
||||
else
|
||||
{
|
||||
var location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
delta = _isHorizontal ? location.x - _trackingStartPoint.x : location.y - _trackingStartPoint.y;
|
||||
|
||||
[self setFloatValue:_trackingFloatValue + delta / remainder];
|
||||
}
|
||||
}
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackKnob:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Tracks the scroll button.
|
||||
@param anEvent the input event
|
||||
*/
|
||||
- (void)trackScrollButtons:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type];
|
||||
|
||||
if (type == CPLeftMouseUp)
|
||||
{
|
||||
[self highlight:NO];
|
||||
[CPEvent stopPeriodicEvents];
|
||||
|
||||
_hitPart = CPScrollerNoPart;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == CPLeftMouseDown)
|
||||
{
|
||||
_trackingPart = [self hitPart];
|
||||
|
||||
_trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if ([anEvent modifierFlags] & CPAlternateKeyMask)
|
||||
{
|
||||
if (_trackingPart == CPScrollerDecrementLine)
|
||||
_hitPart = CPScrollerDecrementPage;
|
||||
else if (_trackingPart == CPScrollerIncrementLine)
|
||||
_hitPart = CPScrollerIncrementPage;
|
||||
else if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
|
||||
{
|
||||
var knobRect = [self rectForPart:CPScrollerKnob],
|
||||
knobWidth = _isHorizontal ? _CGRectGetWidth(knobRect) : _CGRectGetHeight(knobRect),
|
||||
knobSlotRect = [self rectForPart:CPScrollerKnobSlot],
|
||||
remainder = (_isHorizontal ? _CGRectGetWidth(knobSlotRect) : _CGRectGetHeight(knobSlotRect)) - knobWidth;
|
||||
|
||||
[self setFloatValue:((_isHorizontal ? _trackingStartPoint.x - _CGRectGetMinX(knobSlotRect) : _trackingStartPoint.y - _CGRectGetMinY(knobSlotRect)) - knobWidth / 2.0) / remainder];
|
||||
|
||||
_hitPart = CPScrollerKnob;
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
// Now just track the knob.
|
||||
return [self trackKnob:anEvent];
|
||||
}
|
||||
}
|
||||
|
||||
[self highlight:YES];
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
[CPEvent startPeriodicEventsAfterDelay:0.5 withPeriod:0.04];
|
||||
}
|
||||
|
||||
else if (type == CPLeftMouseDragged)
|
||||
{
|
||||
_trackingStartPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if (_trackingPart == CPScrollerDecrementPage || _trackingPart == CPScrollerIncrementPage)
|
||||
{
|
||||
var hitPart = [self testPart:[anEvent locationInWindow]];
|
||||
|
||||
if (hitPart == CPScrollerDecrementPage || hitPart == CPScrollerIncrementPage)
|
||||
{
|
||||
_trackingPart = hitPart;
|
||||
_hitPart = hitPart;
|
||||
}
|
||||
}
|
||||
|
||||
[self highlight:CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint)];
|
||||
}
|
||||
else if (type == CPPeriodic && CGRectContainsPoint([self rectForPart:_trackingPart], _trackingStartPoint))
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackScrollButtons:) forNextEventMatchingMask:CPPeriodicMask | CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self checkSpaceForParts];
|
||||
|
||||
var frame = [self frame],
|
||||
isHorizontal = CPRectGetWidth(frame) > CPRectGetHeight(frame);
|
||||
|
||||
if (_isHorizontal != isHorizontal)
|
||||
{
|
||||
_isHorizontal = isHorizontal;
|
||||
|
||||
[self drawParts];
|
||||
}
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
/*!
|
||||
Lays out the scrollers subviews
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[_knobSlotView setFrame:[self rectForPart:CPScrollerKnobSlot]];
|
||||
|
||||
var usableParts = [self usableParts],
|
||||
hidden = !(usableParts == CPAllScrollerParts);
|
||||
|
||||
if (hidden != [_knobView isHidden])
|
||||
{
|
||||
[_knobView setHidden:hidden];
|
||||
[_decrementArrowView setHidden:hidden];
|
||||
[_incrementArrowView setHidden:hidden];
|
||||
}
|
||||
|
||||
if (!hidden)
|
||||
{
|
||||
[_knobView setFrame:[self rectForPart:CPScrollerKnob]];
|
||||
[_decrementArrowView setFrame:[self rectForPart:CPScrollerDecrementLine]];
|
||||
[_incrementArrowView setFrame:[self rectForPart:CPScrollerIncrementLine]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
_hitPart = [self testPart:[anEvent locationInWindow]];
|
||||
|
||||
switch (_hitPart)
|
||||
{
|
||||
case CPScrollerKnob: return [self trackKnob:anEvent];
|
||||
|
||||
case CPScrollerDecrementLine:
|
||||
case CPScrollerIncrementLine:
|
||||
case CPScrollerDecrementPage:
|
||||
case CPScrollerIncrementPage: return [self trackScrollButtons:anEvent];
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: This is a result of the "dumb" code that just makes things transparent when disabled.
|
||||
- (void)setEnabled:(BOOL)shouldBeEnabled
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,103 @@
|
||||
@import "CPTextField.j"
|
||||
|
||||
#include "Platform/Platform.h"
|
||||
|
||||
|
||||
var TOP_PADDING = 4.0,
|
||||
BOTTOM_PADDING = 3.0;
|
||||
HORIZONTAL_PADDING = 3.0;
|
||||
|
||||
var CPSecureTextFieldDOMInputElement = nil;
|
||||
|
||||
@implementation CPSecureTextField : CPTextField
|
||||
{
|
||||
}
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
+ (DOMElement)_inputElement
|
||||
{
|
||||
if (!CPSecureTextFieldDOMInputElement)
|
||||
{
|
||||
CPSecureTextFieldDOMInputElement = document.createElement("input");
|
||||
CPSecureTextFieldDOMInputElement.type = "password";
|
||||
CPSecureTextFieldDOMInputElement.style.position = "absolute";
|
||||
CPSecureTextFieldDOMInputElement.style.top = "0px";
|
||||
CPSecureTextFieldDOMInputElement.style.left = "0px";
|
||||
CPSecureTextFieldDOMInputElement.style.width = "100%"
|
||||
CPSecureTextFieldDOMInputElement.style.height = "100%";
|
||||
CPSecureTextFieldDOMInputElement.style.border = "0px";
|
||||
CPSecureTextFieldDOMInputElement.style.padding = "0px";
|
||||
CPSecureTextFieldDOMInputElement.style.whiteSpace = "pre";
|
||||
CPSecureTextFieldDOMInputElement.style.background = "transparent";
|
||||
CPSecureTextFieldDOMInputElement.style.outline = "none";
|
||||
CPSecureTextFieldDOMInputElement.style.paddingLeft = HORIZONTAL_PADDING + "px";
|
||||
CPSecureTextFieldDOMInputElement.style.paddingTop = TOP_PADDING - 2.0 + "px";
|
||||
CPSecureTextFieldDOMInputElement.style.margin = "0px";
|
||||
}
|
||||
|
||||
return CPSecureTextFieldDOMInputElement;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMElement.removeChild(_DOMTextElement);
|
||||
|
||||
_DOMTextElement = document.createElement("input");
|
||||
_DOMTextElement.type = "password";
|
||||
_DOMTextElement.style.position = "absolute";
|
||||
_DOMTextElement.style.top = TOP_PADDING + "px";
|
||||
_DOMTextElement.style.left = HORIZONTAL_PADDING + "px";
|
||||
_DOMTextElement.style.width = MAX(0.0, CGRectGetWidth(aFrame) - 2.0 * HORIZONTAL_PADDING) + "px";
|
||||
_DOMTextElement.style.height = MAX(0.0, CGRectGetHeight(aFrame) - TOP_PADDING - BOTTOM_PADDING) + "px";
|
||||
_DOMTextElement.style.whiteSpace = "pre";
|
||||
_DOMTextElement.style.cursor = "default";
|
||||
_DOMTextElement.style.zIndex = 100;
|
||||
_DOMTextElement.style.border = "0";
|
||||
_DOMTextElement.style.font = _DOMElement.style.font;
|
||||
_DOMTextElement.style.padding = "0px";
|
||||
_DOMTextElement.style.margin = "0px";
|
||||
|
||||
_DOMElement.appendChild(_DOMTextElement);
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
[super setFont:aFont];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
if (_DOMTextElement)
|
||||
_DOMTextElement.style.font = _DOMElement.style.font;
|
||||
#endif
|
||||
}
|
||||
|
||||
- (CPString)stringValue
|
||||
{
|
||||
// All of this needs to be better.
|
||||
#if PLATFORM(DOM)
|
||||
if ([[self window] firstResponder] == self)
|
||||
return [[self class] _inputElement].value;
|
||||
#endif
|
||||
|
||||
return _DOMTextElement.value;
|
||||
}
|
||||
|
||||
- (void)setStringValue:(CPString)aStringValue
|
||||
{
|
||||
_value = aStringValue;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMTextElement.value = _value;
|
||||
#endif
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
* CPSegmentedControl.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@import "CPControl.j"
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPSegmentSwitchTracking
|
||||
*/
|
||||
CPSegmentSwitchTrackingSelectOne = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPSegmentSwitchTracking
|
||||
*/
|
||||
CPSegmentSwitchTrackingSelectAny = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPSegmentSwitchTracking
|
||||
*/
|
||||
CPSegmentSwitchTrackingMomentary = 2;
|
||||
|
||||
/*! @class CPSegmentedControl
|
||||
|
||||
This class is a horizontal button with multiple segments.
|
||||
*/
|
||||
@implementation CPSegmentedControl : CPControl
|
||||
{
|
||||
unsigned _segmentCount;
|
||||
|
||||
CPArray _segments;
|
||||
CPArray _selectedSegment;
|
||||
|
||||
CPSegmentSwitchTracking _trackingMode;
|
||||
unsigned _trackingSegment;
|
||||
BOOL _trackingHighlighted;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aRect
|
||||
{
|
||||
self = [super initWithFrame:aRect];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_segments = [];
|
||||
_selectedSegment = -1;
|
||||
|
||||
_segmentCount = 0;
|
||||
|
||||
_trackingMode = CPSegmentSwitchTrackingSelectOne;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tag of the selected segment.
|
||||
*/
|
||||
- (int)selectedTag
|
||||
{
|
||||
return _segments[_selectedSegment].tag;
|
||||
}
|
||||
|
||||
// Specifying the number of segments
|
||||
/*!
|
||||
Sets the number of segments in the button.
|
||||
@param aCount the number of segments on the button
|
||||
*/
|
||||
- (void)setSegmentCount:(unsigned)aCount
|
||||
{
|
||||
if (_segmentCount == aCount)
|
||||
return;
|
||||
|
||||
var height = CGRectGetHeight([self bounds]);
|
||||
|
||||
if (_segmentCount < aCount)
|
||||
{
|
||||
var index = _segmentCount;
|
||||
|
||||
for (; index < aCount; ++index)
|
||||
{
|
||||
_segments[index] = _CPSegmentMake();
|
||||
_segments[index].frame.size.height = height;
|
||||
}
|
||||
}
|
||||
else if (aCount < _segmentCount)
|
||||
{
|
||||
var index = aCount;
|
||||
|
||||
for (; index < _segmentCount; ++index)
|
||||
{
|
||||
[_segments[index].imageView removeFromSuperview];
|
||||
[_segments[index].labelView removeFromSuperview];
|
||||
|
||||
_segments[index] = nil;
|
||||
}
|
||||
}
|
||||
|
||||
_segmentCount = aCount;
|
||||
|
||||
if (_selectedSegment < _segmentCount)
|
||||
_selectedSegment = -1;
|
||||
|
||||
[self tileWithChangedSegment:0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of segments in the button.
|
||||
*/
|
||||
- (unsigned)segmentCount
|
||||
{
|
||||
return _segmentCount;
|
||||
}
|
||||
|
||||
// Specifying Selected Segment
|
||||
/*!
|
||||
Selects a segment.
|
||||
@param aSegment the segment to select
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (void)setSelectedSegment:(unsigned)aSegment
|
||||
{
|
||||
// setSelected:forSegment throws the exception for us (if necessary)
|
||||
[self setSelected:YES forSegment:aSegment];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the selected segment.
|
||||
*/
|
||||
- (unsigned)selectedSegment
|
||||
{
|
||||
return _selectedSegment;
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the button segment with the specified tag.
|
||||
*/
|
||||
- (BOOL)selectSegmentWithTag:(int)aTag
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
for (; index < _segmentCount; ++index)
|
||||
if (_segments[index].tag == aTag)
|
||||
{
|
||||
[self setSelectedSegment:index];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Specifying Tracking Mode
|
||||
|
||||
- (BOOL)isTracking
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (void)setTrackingMode:(CPSegmentSwitchTracking)aTrackingMode
|
||||
{
|
||||
if (_trackingMode == aTrackingMode)
|
||||
return;
|
||||
|
||||
_trackingMode = aTrackingMode;
|
||||
|
||||
if (_trackingMode == CPSegmentSwitchTrackingSelectOne)
|
||||
{
|
||||
var index = 0,
|
||||
selected = NO;
|
||||
|
||||
for (; index < _segmentCount; ++index)
|
||||
if (_segments[index].selected)
|
||||
if (selected)
|
||||
[self setSelected:NO forSegment:index];
|
||||
else
|
||||
selected = YES;
|
||||
}
|
||||
|
||||
else if (_trackingMode == CPSegmentSwitchTrackingMomentary)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
for (; index < _segmentCount; ++index)
|
||||
if (_segments[index].selected)
|
||||
[self setSelected:NO forSegment:index];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the control's tracking mode.
|
||||
*/
|
||||
- (CPSegmentSwitchTracking)trackingMode
|
||||
{
|
||||
return _trackingMode;
|
||||
}
|
||||
|
||||
// Working with Individual Segments
|
||||
/*!
|
||||
Sets the width of the specified segment.
|
||||
@param aWidth the new width for the segment
|
||||
@param aSegment the segment to set the width for
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (void)setWidth:(float)aWidth forSegment:(unsigned)aSegment
|
||||
{
|
||||
_segments[aSegment].width = aWidth;
|
||||
|
||||
[self tileWithChangedSegment:aSegment];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the width for the specified segment.
|
||||
@param aSegment the segment to get the width for
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (float)widthForSegment:(unsigned)aSegment
|
||||
{
|
||||
return _segments[aSegment].width;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the image for the specified segment.
|
||||
@param anImage the image for the segment
|
||||
@param aSegment the segment to set the image on
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (void)setImage:(CPImage)anImage forSegment:(unsigned)aSegment
|
||||
{
|
||||
var segment = _segments[aSegment];
|
||||
|
||||
if (!anImage)
|
||||
{
|
||||
[segment.imageView removeFromSuperview];
|
||||
|
||||
segment.imageView = nil;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (!segment.imageView)
|
||||
{
|
||||
segment.imageView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[self addSubview:segment.imageView];
|
||||
}
|
||||
|
||||
[segment.imageView setImage:anImage];
|
||||
[segment.imageView setFrameSize:CGSizeMakeCopy([anImage size])];
|
||||
}
|
||||
|
||||
segment.image = anImage;
|
||||
|
||||
if (segment.width)
|
||||
[self drawSegment:aSegment highlight:NO];
|
||||
else
|
||||
[self tileWithChangedSegment:aSegment];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the image for the specified segment
|
||||
@param aSegment the segment to obtain the image for
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (CPImage)imageForSegment:(unsigned)aSegment
|
||||
{
|
||||
return _segments[aSegment].image;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the label for the specified segment
|
||||
@param aLabel the label for the segment
|
||||
@param aSegment the segment to label
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (void)setLabel:(CPString)aLabel forSegment:(unsigned)aSegment
|
||||
{
|
||||
var segment = _segments[aSegment];
|
||||
|
||||
if (!aLabel || !aLabel.length)
|
||||
{
|
||||
[segment.labelView removeFromSuperview];
|
||||
|
||||
segment.labelView = nil;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
if (!segment.labelView)
|
||||
{
|
||||
segment.labelView = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[segment.labelView setFont:[self font]];
|
||||
|
||||
[self addSubview:segment.labelView];
|
||||
}
|
||||
|
||||
[segment.labelView setStringValue:aLabel];
|
||||
[segment.labelView sizeToFit];
|
||||
}
|
||||
|
||||
_segments[aSegment].label = aLabel;
|
||||
|
||||
if (segment.width)
|
||||
[self drawSegment:aSegment highlight:NO];
|
||||
else
|
||||
[self tileWithChangedSegment:aSegment];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the label for the specified segment
|
||||
@param the segment to obtain the label for
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (CPString)labelForSegment:(unsigned)aSegment
|
||||
{
|
||||
return _segments[aSegment].label;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the menu for the specified segment
|
||||
@param aMenu the menu to set
|
||||
@param aSegment the segment to set the menu on
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (void)setMenu:(CPMenu)aMenu forSegment:(unsigned)aSegment
|
||||
{
|
||||
_segments[aSegment].menu = aMenu;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the menu for the specified segment.
|
||||
@param aSegment the segment to obtain the menu for
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (CPMenu)menuForSegment:(unsigned)aSegment
|
||||
{
|
||||
return _segments[aSegment].menu;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the selection for the specified segment. If only one segment
|
||||
can be selected at a time, any other segment will be deselected.
|
||||
@param isSelected <code>YES</code> selects the segment. <code>NO</code> deselects it.
|
||||
@param aSegment the segment to set the selection for
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (void)setSelected:(BOOL)isSelected forSegment:(unsigned)aSegment
|
||||
{
|
||||
var segment = _segments[aSegment];
|
||||
|
||||
// If we're already in this state, bail.
|
||||
if (segment.selected == isSelected)
|
||||
return;
|
||||
|
||||
segment.selected = isSelected;
|
||||
|
||||
// We need to do some cleanup if we only allow one selection.
|
||||
if (isSelected)
|
||||
{
|
||||
var oldSelectedSegment = _selectedSegment;
|
||||
|
||||
_selectedSegment = aSegment;
|
||||
|
||||
if (_trackingMode == CPSegmentSwitchTrackingSelectOne && oldSelectedSegment != aSegment && oldSelectedSegment != -1)
|
||||
{
|
||||
_segments[oldSelectedSegment].selected = NO;
|
||||
|
||||
[self drawSegmentBezel:oldSelectedSegment highlight:NO];
|
||||
}
|
||||
}
|
||||
|
||||
if (_trackingMode != CPSegmentSwitchTrackingMomentary)
|
||||
[self drawSegmentBezel:aSegment highlight:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the specified segment is selected.
|
||||
@param aSegment the segment to check for selection
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (BOOL)isSelectedForSegment:(unsigned)aSegment
|
||||
{
|
||||
return _segments[aSegment].selected;
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables/diables the specified segment.
|
||||
@param isEnabled <code>YES</code> enables the segment
|
||||
@param aSegment the segment to enable/disble
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)isEnabled forSegment:(unsigned)aSegment
|
||||
{
|
||||
_segments[aSegment].enabled = isEnabled;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the specified segment is enabled.
|
||||
@param aSegment the segment to check
|
||||
@throws CPRangeException if <code>aSegment</code> is out of bounds
|
||||
*/
|
||||
- (BOOL)isEnabledForSegment:(unsigned)aSegment
|
||||
{
|
||||
return _segments[aSegment].enabled;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a tag for the specified segment.
|
||||
@param aTag the tag to set
|
||||
@param aSegment the segment to set the tag on
|
||||
*/
|
||||
- (void)setTag:(int)aTag forSegment:(unsigned)aSegment
|
||||
{
|
||||
_segments[aSegment].tag = aTag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tag for the specified segment.
|
||||
@param aSegment the segment to obtain the tag for
|
||||
*/
|
||||
- (int)tagForSegment:(unsigned)aSegment
|
||||
{
|
||||
return _segments[aSegment].tag;
|
||||
}
|
||||
|
||||
// Drawings
|
||||
/*!
|
||||
Draws the specified segment bezel
|
||||
@param aSegment the segment to draw the bezel for
|
||||
@param shouldHighlight <code>YES</code> highlights the bezel
|
||||
*/
|
||||
- (void)drawSegmentBezel:(int)aSegment highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Draws the specified segment
|
||||
@param aSegment the segment to draw
|
||||
@param shouldHighlight <code>YES</code> highlights the bezel
|
||||
*/
|
||||
- (void)drawSegment:(int)aSegment highlight:(BOOL)shouldHighlight
|
||||
{
|
||||
var segment = _segments[aSegment],
|
||||
|
||||
imageView = segment.imageView,
|
||||
labelView = segment.labelView,
|
||||
|
||||
frame = segment.frame,
|
||||
|
||||
segmentX = CGRectGetMinX(frame),
|
||||
segmentWidth = CGRectGetWidth(frame),
|
||||
segmentHeight = CGRectGetHeight(frame) - 1.0;
|
||||
|
||||
if (imageView && labelView)
|
||||
{
|
||||
var imageViewSize = [imageView frame].size,
|
||||
labelViewSize = [labelView frame].size,
|
||||
totalHeight = imageViewSize.height + labelViewSize.height,
|
||||
labelWidth = MIN(labelViewSize.width, width),
|
||||
y = (segmentHeight - totalHeight) / 2.0;
|
||||
|
||||
[imageView setFrameOrigin:CGPointMake(segmentX + (segmentWidth - imageViewSize.width) / 2.0, y)];
|
||||
|
||||
if (labelWidth < labelViewSize.width)
|
||||
[labelView setFrameSize:CGSizeMake(labelWidth, labelViewSize.height)];
|
||||
|
||||
[labelView setFrameOrigin:CGPointMake(segmentX + (segmentWidth - labelWidth) / 2.0, y + imageViewSize.height)];
|
||||
}
|
||||
else if (imageView)
|
||||
{
|
||||
var imageViewSize = [imageView frame].size;
|
||||
|
||||
[imageView setFrameOrigin:CGPointMake(segmentX + (segmentWidth - imageViewSize.width) / 2.0, (segmentHeight - imageViewSize.height) / 2.0)];
|
||||
}
|
||||
else if (labelView)
|
||||
{
|
||||
var labelViewSize = [labelView frame].size,
|
||||
labelWidth = MIN(labelViewSize.width, segmentWidth);
|
||||
|
||||
if (labelWidth < labelViewSize.width)
|
||||
[labelView setFrameSize:CGSizeMake(labelWidth, labelViewSize.height)];
|
||||
|
||||
[labelView setFrameOrigin:CGPointMake(segmentX + (segmentWidth - labelWidth) / 2.0, (segmentHeight - labelViewSize.height) / 2.0)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tileWithChangedSegment:(unsigned)aSegment
|
||||
{
|
||||
var segment = _segments[aSegment],
|
||||
segmentWidth = segment.width;
|
||||
|
||||
if (!segmentWidth)
|
||||
{
|
||||
if (segment.labelView && segment.imageView)
|
||||
segmentWidth = MAX(CGRectGetWidth([segment.labelView frame]) , CGRectGetWidth([segment.imageView frame]));
|
||||
else if (segment.labelView)
|
||||
segmentWidth = CGRectGetWidth([segment.labelView frame]);
|
||||
else if (segment.imageView)
|
||||
segmentWidth = CGRectGetWidth([segment.imageView frame]);
|
||||
}
|
||||
|
||||
var delta = segmentWidth - CGRectGetWidth(segment.frame);
|
||||
|
||||
if (!delta)
|
||||
return;
|
||||
|
||||
// Update Contorl Size
|
||||
var frame = [self frame];
|
||||
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth(frame) + delta, CGRectGetHeight(frame))];
|
||||
|
||||
// Update Segment Width
|
||||
segment.frame.size.width = segmentWidth;
|
||||
|
||||
// Update Following Segments Widths
|
||||
var index = aSegment + 1;
|
||||
|
||||
for (; index < _segmentCount; ++index)
|
||||
{
|
||||
_segments[index].frame.origin.x += delta;
|
||||
|
||||
[self drawSegmentBezel:index highlight:NO];
|
||||
[self drawSegment:index highlight:NO];
|
||||
}
|
||||
|
||||
[self drawSegmentBezel:aSegment highlight:NO];
|
||||
[self drawSegment:aSegment highlight:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the bounding rectangle for the specified segment.
|
||||
@param aSegment the segment to get the rectangle for
|
||||
*/
|
||||
- (CGRect)frameForSegment:(unsigned)aSegment
|
||||
{
|
||||
return _segments[aSegment].frame;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the segment that is hit by the specified point.
|
||||
@param aPoint the point to test for a segment hit
|
||||
@return the intersecting segment
|
||||
*/
|
||||
- (unsigned)testSegment:(CGPoint)aPoint
|
||||
{
|
||||
var location = [self convertPoint:aPoint fromView:nil],
|
||||
count = _segments.length;
|
||||
|
||||
while (count--)
|
||||
if (CGRectContainsPoint(_segments[count].frame, aPoint))
|
||||
return count;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
if (![self isEnabled])
|
||||
return;
|
||||
|
||||
[self trackSegment:anEvent];
|
||||
}
|
||||
|
||||
// FIXME: this should be fixed way up in cpbutton/cpcontrol.
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Handles events for the segment
|
||||
@param anEvent the event to handle
|
||||
*/
|
||||
- (void)trackSegment:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type],
|
||||
location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
if (type == CPLeftMouseUp)
|
||||
{
|
||||
if (CGRectContainsPoint(_segments[_trackingSegment].frame, location))
|
||||
{
|
||||
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
|
||||
{
|
||||
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
|
||||
|
||||
// With ANY, _selectedSegment means last pressed.
|
||||
_selectedSegment = _trackingSegment;
|
||||
}
|
||||
else
|
||||
[self setSelected:YES forSegment:_trackingSegment];
|
||||
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
|
||||
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
|
||||
{
|
||||
[self setSelected:NO forSegment:_trackingSegment];
|
||||
|
||||
_selectedSegment = -1;
|
||||
}
|
||||
}
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:NO];
|
||||
|
||||
_trackingSegment = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == CPLeftMouseDown)
|
||||
{
|
||||
_trackingHighlighted = YES;
|
||||
_trackingSegment = [self testSegment:location];
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:YES];
|
||||
}
|
||||
|
||||
else if (type == CPLeftMouseDragged)
|
||||
{
|
||||
var highlighted = CGRectContainsPoint(_segments[_trackingSegment].frame, location);
|
||||
|
||||
if (highlighted != _trackingHighlighted)
|
||||
{
|
||||
_trackingHighlighted = highlighted;
|
||||
|
||||
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
|
||||
}
|
||||
}
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
}
|
||||
|
||||
- (void)setFont:(CPFont)aFont
|
||||
{
|
||||
[super setFont:aFont];
|
||||
|
||||
var count = _segmentCount;
|
||||
|
||||
if (!count)
|
||||
return;
|
||||
|
||||
while (count--)
|
||||
[_segments[count].labelView setFont:aFont];
|
||||
|
||||
[self tileWithChangedSegment:0];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPSegmentMake = function()
|
||||
{
|
||||
return { width:0, image:nil, label:@"", menu:nil, selected:NO, enabled:NO, tag:0, labelView:nil, imageView:nil, frame:CGRectMakeZero() }
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* CPShadow.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
|
||||
/*! @class CPShadow
|
||||
|
||||
Instances of this class contain the attributes of a drop shadow used in Cappuccino.
|
||||
*/
|
||||
@implementation CPShadow : CPObject
|
||||
{
|
||||
CPSize _offset;
|
||||
float _blurRadius;
|
||||
CPColor _color;
|
||||
|
||||
CPString _cssString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a shadow with the specified attributes.
|
||||
@param anOffset the shadow's offset
|
||||
@param aBlurRadius the shadow's blur radius
|
||||
@param aColor the shadow's color
|
||||
@return the new shadow
|
||||
*/
|
||||
+ (id)shadowWithOffset:(CGSize)anOffset blurRadius:(float)aBlurRadius color:(CPColor)aColor
|
||||
{
|
||||
return [[CPShadow alloc] _initWithOffset:anOffset blurRadius:aBlurRadius color:aColor];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithOffset:(CPSize)anOffset blurRadius:(float)aBlurRadius color:(CPColor)aColor
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_offset = anOffset;
|
||||
_blurRadius = aBlurRadius;
|
||||
_color = aColor;
|
||||
|
||||
_cssString = [_color cssString] + " " + Math.round(anOffset.width) + @"px " + Math.round(anOffset.height) + @"px " + Math.round(_blurRadius) + @"px";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the shadow's offset.
|
||||
*/
|
||||
- (CGSize)shadowOffset
|
||||
{
|
||||
return _offset;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the shadow's blur radius
|
||||
*/
|
||||
- (float)shadowBlurRadius
|
||||
{
|
||||
return _blurRadius;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the shadow's color.
|
||||
*/
|
||||
- (CPColor)shadowColor
|
||||
{
|
||||
return _color;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a CSS string representation of the shadow.
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
return _cssString;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* CPShadowView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPBundle.j>
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CPImage.j"
|
||||
@import "CPImageView.j"
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
CPLightShadow = 0;
|
||||
CPHeavyShadow = 1;
|
||||
|
||||
var CPShadowViewLightBackgroundColor = nil,
|
||||
CPShadowViewHeavyBackgroundColor = nil;
|
||||
|
||||
var LIGHT_LEFT_INSET = 3.0,
|
||||
LIGHT_RIGHT_INSET = 3.0,
|
||||
LIGHT_TOP_INSET = 3.0,
|
||||
LIGHT_BOTTOM_INSET = 5.0,
|
||||
|
||||
HEAVY_LEFT_INSET = 7.0,
|
||||
HEAVY_RIGHT_INSET = 7.0,
|
||||
HEAVY_TOP_INSET = 5.0,
|
||||
HEAVY_BOTTOM_INSET = 5.0;
|
||||
|
||||
@implementation CPShadowView : CPView
|
||||
{
|
||||
CPShadowWeight _weight;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPShadowView class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:[self class]];
|
||||
|
||||
CPShadowViewLightBackgroundColor = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewLightTopLeft.png"] size:CGSizeMake(9.0, 9.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewLightTop.png"] size:CGSizeMake(1.0, 9.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewLightTopRight.png"] size:CGSizeMake(9.0, 9.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewLightLeft.png"] size:CGSizeMake(9.0, 1.0)],
|
||||
nil,
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewLightRight.png"] size:CGSizeMake(9.0, 1.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewLightBottomLeft.png"] size:CGSizeMake(9.0, 9.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewLightBottom.png"] size:CGSizeMake(1.0, 9.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewLightBottomRight.png"] size:CGSizeMake(9.0, 9.0)]
|
||||
]]];
|
||||
|
||||
CPShadowViewHeavyBackgroundColor = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewHeavyTopLeft.png"] size:CGSizeMake(17.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewHeavyTop.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewHeavyTopRight.png"] size:CGSizeMake(17.0, 17.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewHeavyLeft.png"] size:CGSizeMake(17.0, 1.0)],
|
||||
nil,
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewHeavyRight.png"] size:CGSizeMake(17.0, 1.0)],
|
||||
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewHeavyBottomLeft.png"] size:CGSizeMake(17.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewHeavyBottom.png"] size:CGSizeMake(1.0, 17.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPShadowView/CPShadowViewHeavyBottomRight.png"] size:CGSizeMake(17.0, 17.0)]
|
||||
]]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_weight = CPLightShadow;
|
||||
|
||||
[self setBackgroundColor:CPShadowViewLightBackgroundColor];
|
||||
|
||||
[self setHitTests:NO];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setWeight:(CPShadowWeight)aWeight
|
||||
{
|
||||
if (_weight == aWeight)
|
||||
return;
|
||||
|
||||
_weight = aWeight;
|
||||
|
||||
if (_weight == CPLightShadow)
|
||||
[self setBackgroundColor:CPShadowViewLightBackgroundColor];
|
||||
|
||||
else
|
||||
[self setBackgroundColor:CPShadowViewHeavyBackgroundColor];
|
||||
}
|
||||
|
||||
- (float)leftInset
|
||||
{
|
||||
return _weight == CPLightShadow ? LIGHT_LEFT_INSET : HEAVY_LEFT_INSET;
|
||||
}
|
||||
|
||||
- (float)rightInset
|
||||
{
|
||||
return _weight == CPLightShadow ? LIGHT_RIGHT_INSET : HEAVY_RIGHT_INSET;
|
||||
}
|
||||
|
||||
- (float)topInset
|
||||
{
|
||||
return _weight == CPLightShadow ? LIGHT_TOP_INSET : HEAVY_TOP_INSET;
|
||||
}
|
||||
|
||||
- (float)bottomInset
|
||||
{
|
||||
return _weight == CPLightShadow ? LIGHT_BOTTOM_INSET : HEAVY_BOTTOM_INSET;
|
||||
}
|
||||
|
||||
- (float)horizontalInset
|
||||
{
|
||||
if (_weight == CPLightShadow)
|
||||
return LIGHT_LEFT_INSET + LIGHT_RIGHT_INSET;
|
||||
|
||||
return HEAVY_LEFT_INSET + HEAVY_RIGHT_INSET;
|
||||
}
|
||||
|
||||
- (float)verticalInset
|
||||
{
|
||||
if (_weight == CPLightShadow)
|
||||
return LIGHT_TOP_INSET + LIGHT_BOTTOM_INSET;
|
||||
|
||||
return HEAVY_TOP_INSET + HEAVY_BOTTOM_INSET;
|
||||
}
|
||||
|
||||
+ (CGRect)frameForContentFrame:(CGRect)aFrame withWeight:(CPShadowWeight)aWeight
|
||||
{
|
||||
if (aWeight == CPLightShadow)
|
||||
return CGRectMake(_CGRectGetMinX(aFrame) - LIGHT_LEFT_INSET, _CGRectGetMinY(aFrame) - LIGHT_TOP_INSET, _CGRectGetWidth(aFrame) + LIGHT_LEFT_INSET + LIGHT_RIGHT_INSET, _CGRectGetHeight(aFrame) + LIGHT_TOP_INSET + LIGHT_BOTTOM_INSET);
|
||||
else
|
||||
return CGRectMake(_CGRectGetMinX(aFrame) - HEAVY_LEFT_INSET, _CGRectGetMinY(aFrame) - HEAVY_TOP_INSET, _CGRectGetWidth(aFrame) + HEAVY_LEFT_INSET + HEAVY_RIGHT_INSET, _CGRectGetHeight(aFrame) + HEAVY_TOP_INSET + HEAVY_BOTTOM_INSET);
|
||||
}
|
||||
|
||||
- (CGRect)frameForContentFrame:(CGRect)aFrame
|
||||
{
|
||||
return [[self class] frameForContentFrame:aFrame withWeight:_weight];
|
||||
}
|
||||
|
||||
- (void)setFrameForContentFrame:(CGRect)aFrame
|
||||
{
|
||||
[self setFrame:[self frameForContentFrame:aFrame]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,454 @@
|
||||
/*
|
||||
* CPSlider.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPControl.j"
|
||||
|
||||
|
||||
var CPSliderHorizontalKnobImage = nil,
|
||||
CPSliderHorizontalBarLeftImage = nil,
|
||||
CPSliderHorizontalBarRightImage = nil,
|
||||
CPSliderHorizontalBarCenterImage = nil;
|
||||
|
||||
/*! @class CPSlider
|
||||
|
||||
An CPSlider displays, and allows control of, some value in the application. It represents a continuous stream of values of type <code>float</code>, which can be retrieved by the method <code>floatValue</code> and set by the method <code>setFloatValue:</code>.
|
||||
*/
|
||||
@implementation CPSlider : CPControl
|
||||
{
|
||||
double _minValue;
|
||||
double _maxValue;
|
||||
double _altIncrementValue;
|
||||
BOOL _isVertical;
|
||||
|
||||
CPView _bar;
|
||||
CPView _knob;
|
||||
|
||||
CPImageView _standardKnob;
|
||||
CPView _standardVerticalBar;
|
||||
CPView _standardHorizontalBar;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPSlider class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
CPSliderKnobImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPSlider/CPSliderKnobRegular.png"] size:CPSizeMake(11.0, 11.0)],
|
||||
CPSliderKnobPushedImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPSlider/CPSliderKnobRegularPushed.png"] size:CPSizeMake(11.0, 11.0)],
|
||||
CPSliderHorizontalBarLeftImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPSlider/CPSliderTrackHorizontalLeft.png"] size:CPSizeMake(2.0, 4.0)],
|
||||
CPSliderHorizontalBarRightImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPSlider/CPSliderTrackHorizontalRight.png"] size:CPSizeMake(2.0, 4.0)],
|
||||
CPSliderHorizontalBarCenterImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPSlider/CPSliderTrackHorizontalCenter.png"] size:CPSizeMake(1.0, 4.0)];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_value = 50.0;
|
||||
_minValue = 0.0;
|
||||
_maxValue = 100.0;
|
||||
|
||||
_bar = [self bar];
|
||||
_knob = [self knob];
|
||||
_knobSize = [[self knobImage] size];
|
||||
_isVertical = [self isVertical];
|
||||
|
||||
[self setContinuous:YES];
|
||||
|
||||
[_knob setFrameOrigin:[self knobPosition]];
|
||||
|
||||
[self addSubview:_bar];
|
||||
[self addSubview:_knob];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
if (aSize.height > 21.0)
|
||||
aSize.height = 21.0;
|
||||
|
||||
if (_isVertical != [self isVertical])
|
||||
{
|
||||
_isVertical = [self isVertical];
|
||||
|
||||
var bar = [self bar],
|
||||
knob = [self knob];
|
||||
|
||||
if (_bar != bar)
|
||||
[self replaceSubview:_bar = bar withView:_bar];
|
||||
|
||||
if (_knob != knob)
|
||||
{
|
||||
[self replaceSubview:knob withView:_knob];
|
||||
|
||||
_knob = knob;
|
||||
[_knob setFrameOrigin:[self knobPosition]];
|
||||
}
|
||||
}
|
||||
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[_knob setFrameOrigin:[self knobPosition]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the value by which the slider will be
|
||||
incremented if the user holds down the <code>ALT</code>s key.
|
||||
*/
|
||||
- (double)altIncrementValue
|
||||
{
|
||||
return _altIncrementValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the value the slider will be incremented if the user holds the <code>ALT</code> key.
|
||||
*/
|
||||
- (void)setAltIncrementValue:(double)anIncrementValue
|
||||
{
|
||||
_altIncrementValue = anIncrementValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the control can continuously send its action messages.
|
||||
*/
|
||||
- (BOOL)isContinuous
|
||||
{
|
||||
return (_sendActionOn & CPLeftMouseDraggedMask) != 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the cell can continuously send its action messages.
|
||||
*/
|
||||
- (void)setContinuous:(BOOL)flag
|
||||
{
|
||||
if (flag)
|
||||
_sendActionOn |= CPLeftMouseDraggedMask;
|
||||
else
|
||||
_sendActionOn &= ~CPLeftMouseDraggedMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the thickness of the slider's knob. This value is in pixels,
|
||||
and is the size of the knob along the slider's track.
|
||||
*/
|
||||
- (float)knobThickness
|
||||
{
|
||||
return CPRectGetWidth([_knob frame]);
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (CPImage)leftTrackImage
|
||||
{
|
||||
return CPSliderHorizontalBarLeftImage;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (CPImage)rightTrackImage
|
||||
{
|
||||
return CPSliderHorizontalBarRightImage;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (CPImage)centerTrackImage
|
||||
{
|
||||
return CPSliderHorizontalBarCenterImage
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (CPImage)knobImage
|
||||
{
|
||||
return CPSliderKnobImage;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (CPImage)pushedKnobImage
|
||||
{
|
||||
return CPSliderKnobPushedImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the slider's knob.
|
||||
*/
|
||||
- (CPView)knob
|
||||
{
|
||||
if (!_standardKnob)
|
||||
{
|
||||
var knobImage = [self knobImage],
|
||||
knobSize = [knobImage size];
|
||||
|
||||
_standardKnob = [[CPImageView alloc] initWithFrame:CPRectMake(0.0, 0.0, knobSize.width, knobSize.height)];
|
||||
|
||||
[_standardKnob setImage:knobImage];
|
||||
}
|
||||
|
||||
return _standardKnob;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the slider's bar.
|
||||
*/
|
||||
- (CPView)bar
|
||||
{
|
||||
// FIXME: veritcal.
|
||||
if ([self isVertical])
|
||||
return nil;
|
||||
else
|
||||
{
|
||||
if (!_standardHorizontalBar)
|
||||
{
|
||||
var frame = [self frame],
|
||||
barFrame = CPRectMake(0.0, 0.0, CPRectGetWidth(frame), 4.0);
|
||||
|
||||
_standardHorizontalBar = [[CPView alloc] initWithFrame:barFrame];
|
||||
|
||||
[_standardHorizontalBar setBackgroundColor:[CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[[self leftTrackImage], [self centerTrackImage], [self rightTrackImage]] isVertical:NO]]];
|
||||
|
||||
[_standardHorizontalBar setFrame:CPRectMake(0.0, (CPRectGetHeight(frame) - CPRectGetHeight(barFrame)) / 2.0, CPRectGetWidth(_isVertical ? barFrame : frame), CPRectGetHeight(_isVertical ? frame : barFrame))];
|
||||
[_standardHorizontalBar setAutoresizingMask:_isVertical ? CPViewHeightSizable : CPViewWidthSizable];
|
||||
}
|
||||
|
||||
return _standardHorizontalBar;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the slider is vertical.
|
||||
*/
|
||||
- (BOOL)isVertical
|
||||
{
|
||||
var frame = [self frame];
|
||||
|
||||
if (CPRectGetWidth(frame) == CPRectGetHeight(frame))
|
||||
return -1;
|
||||
|
||||
return CPRectGetWidth(frame) < CPRectGetHeight(frame);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the slider's maximum value
|
||||
*/
|
||||
- (double)maxValue
|
||||
{
|
||||
return _maxValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the slider's minimum value
|
||||
*/
|
||||
- (double)minValue
|
||||
{
|
||||
return _minValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the slider's maximum value
|
||||
@param aMaxValue the new maximum value
|
||||
*/
|
||||
- (void)setMaxValue:(double)aMaxValue
|
||||
{
|
||||
_maxValue = aMaxValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the slider's minimum value
|
||||
@param aMinValue the new minimum value
|
||||
*/
|
||||
- (void)setMinValue:(double)aMinValue
|
||||
{
|
||||
_minValue = aMinValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the slider's value
|
||||
@param aValue the new slider value
|
||||
@deprecated Use setFloatValue, setObjectValue, etc
|
||||
*/
|
||||
- (void)setValue:(double)aValue
|
||||
{
|
||||
[self setObjectValue:aValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the slider's value
|
||||
@deprecated Use floatValue, objectValue, etc
|
||||
*/
|
||||
- (double)value
|
||||
{
|
||||
return [self floatValue];
|
||||
}
|
||||
|
||||
- (void)setObjectValue:(id)anObject
|
||||
{
|
||||
[super setObjectValue:anObject];
|
||||
|
||||
if (_knob)
|
||||
[_knob setFrameOrigin:[self knobPosition]];
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the knob's position
|
||||
@ignore
|
||||
*/
|
||||
- (CGPoint)knobPosition
|
||||
{
|
||||
if ([self isVertical])
|
||||
return CPPointMake(0.0, 0.0);
|
||||
else
|
||||
return CPPointMake(
|
||||
(([self floatValue] - _minValue) / (_maxValue - _minValue)) * (CPRectGetWidth([self frame]) - CPRectGetWidth([_knob frame])),
|
||||
(CPRectGetHeight([self frame]) - CPRectGetHeight([_knob frame])) / 2.0);
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (float)valueForKnobPosition:(CGPoint)aPoint
|
||||
{
|
||||
if ([self isVertical])
|
||||
return 0.0;
|
||||
else
|
||||
return MAX(MIN((aPoint.x) * (_maxValue - _minValue) / ( CPRectGetWidth([self frame]) - CPRectGetWidth([_knob frame]) ) + _minValue, _maxValue), _minValue);
|
||||
}
|
||||
|
||||
- (CGPoint)constrainKnobPosition:(CGPoint)aPoint
|
||||
{
|
||||
//FIXME
|
||||
aPoint.x -= _knobSize.width / 2.0;
|
||||
return CPPointMake(MAX(MIN(CPRectGetWidth([self bounds]) - _knobSize.width, aPoint.x), 0.0), (CPRectGetHeight([self bounds]) - CPRectGetHeight([_knob frame])) / 2.0);
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
[[self knob] setImage:[self knobImage]];
|
||||
|
||||
if ([_target respondsToSelector:@selector(sliderDidFinish:)])
|
||||
[_target sliderDidFinish:self];
|
||||
|
||||
if (_sendActionOn & CPLeftMouseUpMask)
|
||||
[self sendAction:_action to:_target];
|
||||
//[super mouseUp:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[_knob setFrameOrigin:[self constrainKnobPosition:[self convertPoint:[anEvent locationInWindow] fromView:nil]]];
|
||||
|
||||
[super setObjectValue:[self valueForKnobPosition:[_knob frame].origin]];
|
||||
|
||||
[[self knob] setImage:[self pushedKnobImage]];
|
||||
|
||||
if (_sendActionOn & CPLeftMouseDraggedMask)
|
||||
[self sendAction:_action to:_target];
|
||||
//[super mouseDown:anEvent];
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
[_knob setFrameOrigin:[self constrainKnobPosition:[self convertPoint:[anEvent locationInWindow] fromView:nil]]];
|
||||
|
||||
[super setObjectValue:[self valueForKnobPosition:[_knob frame].origin]];
|
||||
|
||||
if (_sendActionOn & CPLeftMouseDraggedMask)
|
||||
[self sendAction:_action to:_target];
|
||||
//[super mouseDown:anEvent];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPSliderMinValueKey = "CPSliderMinValueKey",
|
||||
CPSliderMaxValueKey = "CPSliderMaxValueKey",
|
||||
CPSliderAltIncrValueKey = "CPSliderAltIncrValueKey",
|
||||
CPSliderIsVerticalKey = "CPSliderIsVerticalKey";
|
||||
|
||||
@implementation CPSlider (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the slider from the data in a coder.
|
||||
@param aCoder the coder from which to read the data
|
||||
@return the initialized slider
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_minValue = [aCoder decodeDoubleForKey:CPSliderMinValueKey];
|
||||
_maxValue = [aCoder decodeDoubleForKey:CPSliderMaxValueKey];
|
||||
_altIncrementValue = [aCoder decodeDoubleForKey:CPSliderAltIncrValueKey];
|
||||
_isVertical = [aCoder decodeDoubleForKey:CPSliderIsVerticalKey];
|
||||
|
||||
_bar = [self bar];
|
||||
_knob = [self knob];
|
||||
_knobSize = [[self knobImage] size];
|
||||
_isVertical = [self isVertical];
|
||||
|
||||
[_knob setFrameOrigin:[self knobPosition]];
|
||||
|
||||
[self addSubview:_bar];
|
||||
[self addSubview:_knob];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes out the slider's instance information to a coder.
|
||||
@param aCoder the coder to which to write the data
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
var subviews = _subviews;
|
||||
|
||||
_subviews = [];
|
||||
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
_subviews = subviews;
|
||||
|
||||
[aCoder encodeDouble:_minValue forKey:CPSliderMinValueKey];
|
||||
[aCoder encodeDouble:_maxValue forKey:CPSliderMaxValueKey];
|
||||
[aCoder encodeDouble:_altIncrementValue forKey:CPSliderAltIncrValueKey];
|
||||
[aCoder encodeBool:_isVertical forKey:CPSliderIsVerticalKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* CPApplication.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Ross Boucher.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPColorPicker.j"
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
@implementation CPSliderColorPicker : CPColorPicker
|
||||
{
|
||||
CPView _contentView;
|
||||
|
||||
CPSlider _redSlider;
|
||||
CPSlider _greenSlider;
|
||||
CPSlider _blueSlider;
|
||||
CPSlider _hueSlider;
|
||||
CPSlider _saturationSlider;
|
||||
CPSlider _brightnessSlider;
|
||||
|
||||
CPTextField _rgbLabel;
|
||||
CPTextField _hsbLabel;
|
||||
CPTextField _redLabel;
|
||||
CPTextField _greenLabel;
|
||||
CPTextField _blueLabel;
|
||||
CPTextField _hueLabel;
|
||||
CPTextField _saturationLabel;
|
||||
CPTextField _brightnessLabel;
|
||||
CPTextField _hexLabel;
|
||||
|
||||
DOMElement _redValue;
|
||||
DOMElement _greenValue;
|
||||
DOMElement _blueValue;
|
||||
DOMElement _hueValue;
|
||||
DOMElement _saturationValue;
|
||||
DOMElement _brightnessValue;
|
||||
DOMElement _hexValue;
|
||||
}
|
||||
|
||||
- (id)initWithPickerMask:(int)mask colorPanel:(CPColorPanel)owningColorPanel
|
||||
{
|
||||
return [super initWithPickerMask:mask colorPanel: owningColorPanel];
|
||||
}
|
||||
|
||||
-(id)initView
|
||||
{
|
||||
aFrame = CPRectMake(0, 0, CPColorPickerViewWidth, CPColorPickerViewHeight);
|
||||
|
||||
_contentView = [[CPView alloc] initWithFrame:aFrame];
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable];
|
||||
|
||||
_rgbLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 10, 100, 20)];
|
||||
[_rgbLabel setStringValue: "RGB"];
|
||||
[_rgbLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
_redLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 35, 15, 20)];
|
||||
[_redLabel setStringValue: "R"];
|
||||
[_redLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
_redSlider = [[CPSlider alloc] initWithFrame: CPRectMake(15, 35, aFrame.size.width - 70, 20)];
|
||||
[_redSlider setMaxValue: 1.0];
|
||||
[_redSlider setMinValue: 0.0];
|
||||
[_redSlider setTarget: self];
|
||||
[_redSlider setAction: @selector(sliderChanged:)];
|
||||
[_redSlider setAutoresizingMask: CPViewWidthSizable];
|
||||
|
||||
var updateFunction = function(aDOMEvent)
|
||||
{
|
||||
if(isNaN(this.value))
|
||||
return;
|
||||
|
||||
switch(this)
|
||||
{
|
||||
case _redValue: [_redSlider setFloatValue:MAX(MIN(ROUND(this.value), 255) / 255.0, 0)];
|
||||
//[self sliderChanged: _redSlider];
|
||||
break;
|
||||
|
||||
case _greenValue: [_greenSlider setFloatValue:MAX(MIN(ROUND(this.value), 255) / 255.0, 0)];
|
||||
//[self sliderChanged: _greenSlider];
|
||||
break;
|
||||
|
||||
case _blueValue: [_blueSlider setFloatValue:MAX(MIN(ROUND(this.value), 255) / 255.0, 0)];
|
||||
//[self sliderChanged: _blueSlider];
|
||||
break;
|
||||
|
||||
case _hueValue: [_hueSlider setFloatValue:MAX(MIN(ROUND(this.value), 360), 0)];
|
||||
//[self sliderChanged: _hueSlider];
|
||||
break;
|
||||
|
||||
case _saturationValue: [_saturationSlider setFloatValue:MAX(MIN(ROUND(this.value), 100), 0)];
|
||||
//[self sliderChanged: _saturationSlider];
|
||||
break;
|
||||
|
||||
case _brightnessValue: [_brightnessSlider setFloatValue:MAX(MIN(ROUND(this.value), 100), 0)];
|
||||
//[self sliderChanged: _brightnessSlider];
|
||||
break;
|
||||
}
|
||||
|
||||
this.blur();
|
||||
};
|
||||
|
||||
var keypressFunction = function(aDOMEvent)
|
||||
{
|
||||
aDOMEvent = aDOMEvent || window.event;
|
||||
if (aDOMEvent.keyCode == 13)
|
||||
{
|
||||
updateFunction(aDOMEvent);
|
||||
|
||||
if(aDOMEvent.preventDefault)
|
||||
aDOMEvent.preventDefault();
|
||||
else if(aDOMEvent.stopPropagation)
|
||||
aDOMEvent.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
//red value input box
|
||||
var redValue = [[CPView alloc] initWithFrame: CPRectMake(aFrame.size.width - 45, 35, 45, 20)];
|
||||
[redValue setAutoresizingMask: CPViewMinXMargin];
|
||||
|
||||
_redValue = document.createElement("input");
|
||||
_redValue.style.width = "40px";
|
||||
_redValue.style.backgroundColor = "transparent";
|
||||
_redValue.style.border = "1px solid white";
|
||||
_redValue.style.color = "white";
|
||||
_redValue.style.position = "absolute";
|
||||
_redValue.style.top = "0px";
|
||||
_redValue.style.left = "0px";
|
||||
_redValue.onchange = updateFunction;
|
||||
|
||||
redValue._DOMElement.appendChild(_redValue);
|
||||
[_contentView addSubview: redValue];
|
||||
|
||||
_greenLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 58, 15, 20)];
|
||||
[_greenLabel setStringValue: "G"];
|
||||
[_greenLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
_greenSlider = [[CPSlider alloc] initWithFrame: CPRectMake(15, 58, aFrame.size.width - 70, 20)];
|
||||
[_greenSlider setMaxValue: 1.0];
|
||||
[_greenSlider setMinValue: 0.0];
|
||||
[_greenSlider setTarget: self];
|
||||
[_greenSlider setAction: @selector(sliderChanged:)];
|
||||
[_greenSlider setAutoresizingMask: CPViewWidthSizable];
|
||||
|
||||
//green value input box
|
||||
var greenValue = [[CPView alloc] initWithFrame: CPRectMake(aFrame.size.width - 45, 58, 45, 20)];
|
||||
[greenValue setAutoresizingMask: CPViewMinXMargin];
|
||||
|
||||
_greenValue = _redValue.cloneNode(false);
|
||||
_greenValue.onchange = updateFunction;
|
||||
|
||||
greenValue._DOMElement.appendChild(_greenValue);
|
||||
[_contentView addSubview: greenValue];
|
||||
|
||||
_blueLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 81, 15, 20)];
|
||||
[_blueLabel setStringValue: "B"];
|
||||
[_blueLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
_blueSlider = [[CPSlider alloc] initWithFrame: CPRectMake(15, 81, aFrame.size.width - 70, 20)];
|
||||
[_blueSlider setMaxValue: 1.0];
|
||||
[_blueSlider setMinValue: 0.0];
|
||||
[_blueSlider setTarget: self];
|
||||
[_blueSlider setAction: @selector(sliderChanged:)];
|
||||
[_blueSlider setAutoresizingMask: CPViewWidthSizable];
|
||||
|
||||
//blue value input box
|
||||
var blueValue = [[CPView alloc] initWithFrame: CPRectMake(aFrame.size.width - 45, 81, 45, 20)];
|
||||
[blueValue setAutoresizingMask: CPViewMinXMargin];
|
||||
|
||||
_blueValue = _redValue.cloneNode(false);
|
||||
_blueValue.onchange = updateFunction;
|
||||
|
||||
blueValue._DOMElement.appendChild(_blueValue);
|
||||
[_contentView addSubview: blueValue];
|
||||
|
||||
_hsbLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 120, 100, 20)];
|
||||
[_hsbLabel setStringValue: "HSB"];
|
||||
[_hsbLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
_hueLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 145, 15, 20)];
|
||||
[_hueLabel setStringValue: "H"];
|
||||
[_hueLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
_hueSlider = [[CPSlider alloc] initWithFrame: CPRectMake(15, 145, aFrame.size.width - 70, 20)];
|
||||
[_hueSlider setMaxValue: 359.0];
|
||||
[_hueSlider setMinValue: 0.0];
|
||||
[_hueSlider setTarget: self];
|
||||
[_hueSlider setAction: @selector(sliderChanged:)];
|
||||
[_hueSlider setAutoresizingMask: CPViewWidthSizable];
|
||||
|
||||
//red value input box
|
||||
var hueValue = [[CPView alloc] initWithFrame: CPRectMake(aFrame.size.width - 45, 145, 45, 20)];
|
||||
[hueValue setAutoresizingMask: CPViewMinXMargin];
|
||||
|
||||
_hueValue = _redValue.cloneNode(false);
|
||||
_hueValue.onchange = updateFunction;
|
||||
|
||||
hueValue._DOMElement.appendChild(_hueValue);
|
||||
[_contentView addSubview: hueValue];
|
||||
|
||||
_saturationLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 168, 15, 20)];
|
||||
[_saturationLabel setStringValue: "S"];
|
||||
[_saturationLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
_saturationSlider = [[CPSlider alloc] initWithFrame: CPRectMake(15, 168, aFrame.size.width - 70, 20)];
|
||||
[_saturationSlider setMaxValue: 100.0];
|
||||
[_saturationSlider setMinValue: 0.0];
|
||||
[_saturationSlider setTarget: self];
|
||||
[_saturationSlider setAction: @selector(sliderChanged:)];
|
||||
[_saturationSlider setAutoresizingMask: CPViewWidthSizable];
|
||||
|
||||
//green value input box
|
||||
var saturationValue = [[CPView alloc] initWithFrame: CPRectMake(aFrame.size.width - 45, 168, 45, 20)];
|
||||
[saturationValue setAutoresizingMask: CPViewMinXMargin];
|
||||
|
||||
_saturationValue = _redValue.cloneNode(false);
|
||||
_saturationValue.onchange = updateFunction;
|
||||
|
||||
saturationValue._DOMElement.appendChild(_saturationValue);
|
||||
[_contentView addSubview: saturationValue];
|
||||
|
||||
_brightnessLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 191, 15, 20)];
|
||||
[_brightnessLabel setStringValue: "B"];
|
||||
[_brightnessLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
_brightnessSlider = [[CPSlider alloc] initWithFrame: CPRectMake(15, 191, aFrame.size.width - 70, 20)];
|
||||
[_brightnessSlider setMaxValue: 100.0];
|
||||
[_brightnessSlider setMinValue: 0.0];
|
||||
[_brightnessSlider setTarget: self];
|
||||
[_brightnessSlider setAction: @selector(sliderChanged:)];
|
||||
[_brightnessSlider setAutoresizingMask: CPViewWidthSizable];
|
||||
|
||||
//blue value input box
|
||||
var brightnessValue = [[CPView alloc] initWithFrame: CPRectMake(aFrame.size.width - 45, 191, 45, 20)];
|
||||
[brightnessValue setAutoresizingMask: CPViewMinXMargin];
|
||||
|
||||
_brightnessValue = _redValue.cloneNode(false);
|
||||
_brightnessValue.onchange = updateFunction;
|
||||
|
||||
brightnessValue._DOMElement.appendChild(_brightnessValue);
|
||||
[_contentView addSubview: brightnessValue];
|
||||
|
||||
_hexLabel = [[CPTextField alloc] initWithFrame: CPRectMake(0, 230, 100, 20)];
|
||||
[_hexLabel setStringValue: "Hex"];
|
||||
[_hexLabel setTextColor:[CPColor whiteColor]];
|
||||
|
||||
//hex input box
|
||||
_hexValue = _redValue.cloneNode(false);
|
||||
_hexValue.style.top = "255px";
|
||||
_hexValue.style.width = "80px";
|
||||
_hexValue.style.left = "0px";
|
||||
_hexValue.onkeypress = function(aDOMEvent)
|
||||
{
|
||||
aDOMEvent = aDOMEvent || window.event;
|
||||
if (aDOMEvent.keyCode == 13)
|
||||
{
|
||||
var newColor = [CPColor colorWithHexString: this.value];
|
||||
|
||||
if(newColor)
|
||||
{
|
||||
[self setColor: newColor];
|
||||
[[self colorPanel] setColor: newColor];
|
||||
}
|
||||
|
||||
if(aDOMEvent.preventDefault)
|
||||
aDOMEvent.preventDefault();
|
||||
else if(aDOMEvent.stopPropagation)
|
||||
aDOMEvent.stopPropagation();
|
||||
|
||||
this.blur();
|
||||
}
|
||||
};
|
||||
|
||||
_contentView._DOMElement.appendChild(_hexValue);
|
||||
|
||||
[_contentView addSubview: _rgbLabel];
|
||||
[_contentView addSubview: _redLabel];
|
||||
[_contentView addSubview: _greenLabel];
|
||||
[_contentView addSubview: _blueLabel];
|
||||
[_contentView addSubview: _redSlider];
|
||||
[_contentView addSubview: _greenSlider];
|
||||
[_contentView addSubview: _blueSlider];
|
||||
|
||||
[_contentView addSubview: _hsbLabel];
|
||||
[_contentView addSubview: _hueLabel];
|
||||
[_contentView addSubview: _saturationLabel];
|
||||
[_contentView addSubview: _brightnessLabel];
|
||||
[_contentView addSubview: _hueSlider];
|
||||
[_contentView addSubview: _saturationSlider];
|
||||
[_contentView addSubview: _brightnessSlider];
|
||||
|
||||
[_contentView addSubview: _hexLabel];
|
||||
}
|
||||
|
||||
- (CPView)provideNewView:(BOOL)initialRequest
|
||||
{
|
||||
if (initialRequest)
|
||||
[self initView];
|
||||
|
||||
return _contentView;
|
||||
}
|
||||
|
||||
- (int)currentMode
|
||||
{
|
||||
return CPSliderColorPickerMode;
|
||||
}
|
||||
|
||||
- (BOOL)supportsMode:(int)mode
|
||||
{
|
||||
return (mode == CPSliderColorPickerMode) ? YES : NO;
|
||||
}
|
||||
|
||||
-(void)sliderChanged:(id)sender
|
||||
{
|
||||
var newColor;
|
||||
|
||||
switch(sender)
|
||||
{
|
||||
case _hueSlider:
|
||||
case _saturationSlider:
|
||||
case _brightnessSlider: newColor = [CPColor colorWithHue: [_hueSlider floatValue]
|
||||
saturation: [_saturationSlider floatValue]
|
||||
brightness: [_brightnessSlider floatValue]];
|
||||
|
||||
[self updateRGBSliders: newColor];
|
||||
break;
|
||||
|
||||
case _redSlider:
|
||||
case _greenSlider:
|
||||
case _blueSlider: newColor = [CPColor colorWithCalibratedRed: [_redSlider floatValue]
|
||||
green: [_greenSlider floatValue]
|
||||
blue: [_blueSlider floatValue]
|
||||
alpha: 1.0];
|
||||
|
||||
[self updateHSBSliders: newColor];
|
||||
break;
|
||||
}
|
||||
|
||||
[self updateLabels];
|
||||
[self updateHex: newColor];
|
||||
[[self colorPanel] setColor: newColor];
|
||||
}
|
||||
|
||||
-(void)setColor:(CPColor)aColor
|
||||
{
|
||||
[self updateRGBSliders: aColor];
|
||||
[self updateHSBSliders: aColor];
|
||||
[self updateHex: aColor];
|
||||
[self updateLabels];
|
||||
}
|
||||
|
||||
-(void)updateHSBSliders:(CPColor)aColor
|
||||
{
|
||||
var hsb = [aColor hsbComponents];
|
||||
|
||||
[_hueSlider setFloatValue:hsb[0]];
|
||||
[_saturationSlider setFloatValue:hsb[1]];
|
||||
[_brightnessSlider setFloatValue:hsb[2]];
|
||||
}
|
||||
|
||||
- (void)updateHex:(CPColor)aColor
|
||||
{
|
||||
_hexValue.value = [aColor hexString];
|
||||
}
|
||||
|
||||
- (void)updateRGBSliders:(CPColor)aColor
|
||||
{
|
||||
var rgb = [aColor components];
|
||||
|
||||
[_redSlider setFloatValue:rgb[0]];
|
||||
[_greenSlider setFloatValue:rgb[1]];
|
||||
[_blueSlider setFloatValue:rgb[2]];
|
||||
}
|
||||
|
||||
- (void)updateLabels
|
||||
{
|
||||
_hueValue.value = ROUND([_hueSlider floatValue]);
|
||||
_saturationValue.value = ROUND([_saturationSlider floatValue]);
|
||||
_brightnessValue.value = ROUND([_brightnessSlider floatValue]);
|
||||
|
||||
_redValue.value = ROUND([_redSlider floatValue] * 255);
|
||||
_greenValue.value = ROUND([_greenSlider floatValue] * 255);
|
||||
_blueValue.value = ROUND([_blueSlider floatValue] * 255);
|
||||
}
|
||||
|
||||
- (CPImage)provideNewButtonImage
|
||||
{
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:CPColorPicker] pathForResource:"slider_button.png"] size:CGSizeMake(32, 32)];
|
||||
}
|
||||
|
||||
- (CPImage)provideNewAlternateButtonImage
|
||||
{
|
||||
return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:CPColorPicker] pathForResource:"slider_button_h.png"] size:CGSizeMake(32, 32)];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* CPSplitView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Thomas Robinson.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPImage.j"
|
||||
@import "CPView.j"
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
#include "Platform/Platform.h"
|
||||
#include "Platform/DOM/CPDOMDisplayServer.h"
|
||||
|
||||
|
||||
CPSplitViewDidResizeSubviewsNotification = @"CPSplitViewDidResizeSubviewsNotification";
|
||||
CPSplitViewWillResizeSubviewsNotification = @"CPSplitViewWillResizeSubviewsNotification";
|
||||
|
||||
var CPSplitViewHorizontalImage = nil,
|
||||
CPSplitViewVerticalImage = nil;
|
||||
|
||||
@implementation CPSplitView : CPView
|
||||
{
|
||||
id _delegate;
|
||||
BOOL _isVertical;
|
||||
BOOL _isPaneSplitter;
|
||||
|
||||
int _currentDivider;
|
||||
float _initialOffset;
|
||||
|
||||
CPString _originComponent;
|
||||
CPString _sizeComponent;
|
||||
|
||||
CPArray _DOMDividerElements;
|
||||
CPString _dividerImagePath;
|
||||
int _drawingDivider;
|
||||
|
||||
BOOL _needsResizeSubviews;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPSplitView class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
CPSplitViewHorizontalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewHorizontal.png"] size:CPSizeMake(5.0, 10.0)];
|
||||
CPSplitViewVerticalImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPSplitView/CPSplitViewVertical.png"] size:CPSizeMake(10.0, 5.0)];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_currentDivider = CPNotFound;
|
||||
|
||||
_DOMDividerElements = [];
|
||||
|
||||
[self _setVertical:YES];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (float)dividerThickness
|
||||
{
|
||||
return _isPaneSplitter ? 1.0 : 10.0;
|
||||
}
|
||||
|
||||
- (BOOL)isVertical
|
||||
{
|
||||
return _isVertical;
|
||||
}
|
||||
|
||||
- (void)setVertical:(BOOL)shouldBeVertical
|
||||
{
|
||||
if (![self _setVertical:shouldBeVertical])
|
||||
return;
|
||||
|
||||
// Just re-adjust evenly.
|
||||
var frame = [self frame],
|
||||
dividerThickness = [self dividerThickness];
|
||||
|
||||
[self _postNotificationWillResize];
|
||||
|
||||
var eachSize = ROUND((frame.size[_sizeComponent] - dividerThickness * (_subviews.length - 1)) / _subviews.length),
|
||||
index = 0,
|
||||
count = _subviews.length;
|
||||
|
||||
if ([self isVertical])
|
||||
for (; index < count; ++index)
|
||||
[_subviews[index] setFrame:CGRectMake(ROUND((eachSize + dividerThickness) * index), 0, eachSize, frame.size.height)];
|
||||
else
|
||||
for (; index < count; ++index)
|
||||
[_subviews[index] setFrame:CGRectMake(0, ROUND((eachSize + dividerThickness) * index), frame.size.width, eachSize)];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
|
||||
[self _postNotificationDidResize];
|
||||
|
||||
}
|
||||
|
||||
- (BOOL)_setVertical:(BOOL)shouldBeVertical
|
||||
{
|
||||
var changed = (_isVertical != shouldBeVertical);
|
||||
|
||||
_isVertical = shouldBeVertical;
|
||||
|
||||
_originComponent = [self isVertical] ? "x" : "y";
|
||||
_sizeComponent = [self isVertical] ? "width" : "height";
|
||||
_dividerImagePath = [self isVertical] ? [CPSplitViewVerticalImage filename] : [CPSplitViewHorizontalImage filename];
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
- (BOOL)isPaneSplitter
|
||||
{
|
||||
return _isPaneSplitter;
|
||||
}
|
||||
|
||||
- (void)setIsPaneSplitter:(BOOL)shouldBePaneSplitter
|
||||
{
|
||||
if (_isPaneSplitter == shouldBePaneSplitter)
|
||||
return;
|
||||
|
||||
_isPaneSplitter = shouldBePaneSplitter;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMDividerElements = [];
|
||||
#endif
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)didAddSubview:(CPView)aSubview
|
||||
{
|
||||
_needsResizeSubviews = YES;
|
||||
// [self adjustSubviews];
|
||||
}
|
||||
|
||||
- (BOOL)isSubviewCollapsed:(CPView)subview
|
||||
{
|
||||
return [subview frame].size[_sizeComponent] < 1 ? YES : NO;
|
||||
}
|
||||
|
||||
- (CGRect)rectOfDividerAtIndex:(int)aDivider
|
||||
{
|
||||
var frame = [_subviews[aDivider] frame],
|
||||
rect = CGRectMakeZero();
|
||||
|
||||
rect.size = [self frame].size;
|
||||
|
||||
rect.size[_sizeComponent] = [self dividerThickness];
|
||||
rect.origin[_originComponent] = frame.origin[_originComponent] + frame.size[_sizeComponent];
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
- (CGRect)effectiveRectOfDividerAtIndex:(int)aDivider
|
||||
{
|
||||
var realRect = [self rectOfDividerAtIndex:aDivider];
|
||||
|
||||
var padding = 2;
|
||||
|
||||
realRect.size[_sizeComponent] += padding * 2;
|
||||
realRect.origin[_originComponent] -= padding;
|
||||
|
||||
return realRect;
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
var count = [_subviews count] - 1;
|
||||
|
||||
if (count > 0)
|
||||
while (count--)
|
||||
[self drawDividerInRect:[self rectOfDividerAtIndex:count]];
|
||||
}
|
||||
|
||||
- (void)drawDividerInRect:(CGRect)aRect
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
if (!_DOMDividerElements[_drawingDivider])
|
||||
{
|
||||
_DOMDividerElements[_drawingDivider] = document.createElement("div");
|
||||
_DOMDividerElements[_drawingDivider].style.cursor = "move";
|
||||
_DOMDividerElements[_drawingDivider].style.position = "absolute";
|
||||
_DOMDividerElements[_drawingDivider].style.backgroundRepeat = "repeat";
|
||||
|
||||
CPDOMDisplayServerAppendChild(_DOMElement, _DOMDividerElements[_drawingDivider]);
|
||||
|
||||
if (_isPaneSplitter)
|
||||
{
|
||||
_DOMDividerElements[_drawingDivider].style.backgroundColor = "#A5A5A5";
|
||||
_DOMDividerElements[_drawingDivider].style.backgroundImage = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
_DOMDividerElements[_drawingDivider].style.backgroundColor = "";
|
||||
_DOMDividerElements[_drawingDivider].style.backgroundImage = "url('"+_dividerImagePath+"')";
|
||||
}
|
||||
}
|
||||
|
||||
CPDOMDisplayServerSetStyleLeftTop(_DOMDividerElements[_drawingDivider], NULL, _CGRectGetMinX(aRect), _CGRectGetMinY(aRect));
|
||||
CPDOMDisplayServerSetStyleSize(_DOMDividerElements[_drawingDivider], _CGRectGetWidth(aRect), _CGRectGetHeight(aRect));
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)viewWillDraw
|
||||
{
|
||||
[self _adjustSubviewsWithCalculatedSize];
|
||||
}
|
||||
|
||||
- (void)_adjustSubviewsWithCalculatedSize
|
||||
{
|
||||
if (!_needsResizeSubviews)
|
||||
return;
|
||||
|
||||
_needsResizeSubviews = NO;
|
||||
|
||||
var subviews = [self subviews],
|
||||
count = subviews.length,
|
||||
oldSize = CGSizeMakeZero();
|
||||
|
||||
if ([self isVertical])
|
||||
{
|
||||
oldSize.width += [self dividerThickness] * (count - 1);
|
||||
oldSize.height = CGRectGetHeight([self frame]);
|
||||
}
|
||||
else
|
||||
{
|
||||
oldSize.width = CGRectGetWidth([self frame]);
|
||||
oldSize.height += [self dividerThickness] * (count - 1);
|
||||
}
|
||||
|
||||
while (count--)
|
||||
oldSize[_sizeComponent] += [subviews[count] frame].size[_sizeComponent];
|
||||
|
||||
[self resizeSubviewsWithOldSize:oldSize];
|
||||
}
|
||||
|
||||
- (BOOL)cursorAtPoint:(CPPoint)aPoint hitDividerAtIndex:(int)anIndex
|
||||
{
|
||||
var frame = [_subviews[anIndex] frame],
|
||||
startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent],
|
||||
effectiveRect = [self effectiveRectOfDividerAtIndex:anIndex],
|
||||
additionalRect = null;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(splitView:effectiveRect:forDrawnRect:ofDividerAtIndex:)])
|
||||
effectiveRect = [_delegate splitView:self effectiveRect:effectiveRect forDrawnRect:effectiveRect ofDividerAtIndex:anIndex];
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(splitView:additionalEffectiveRectOfDividerAtIndex:)])
|
||||
additionalRect = [_delegate splitView:self additionalEffectiveRectOfDividerAtIndex:anIndex];
|
||||
|
||||
return CGRectContainsPoint(effectiveRect, aPoint) || (additionalRect && CGRectContainsPoint(additionalRect, aPoint));
|
||||
}
|
||||
|
||||
- (CPView)hitTest:(CGPoint)aPoint
|
||||
{
|
||||
if ([self isHidden] || ![self hitTests] || !CGRectContainsPoint([self frame], aPoint))
|
||||
return nil;
|
||||
|
||||
var point = [self convertPoint:aPoint fromView:[self superview]];
|
||||
|
||||
var count = [_subviews count] - 1;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if ([self cursorAtPoint:point hitDividerAtIndex:i])
|
||||
return self;
|
||||
}
|
||||
|
||||
return [super hitTest:aPoint];
|
||||
}
|
||||
|
||||
/*
|
||||
Tracks the divider.
|
||||
@param anEvent the input event
|
||||
*/
|
||||
- (void)trackDivider:(CPEvent)anEvent
|
||||
{
|
||||
var type = [anEvent type];
|
||||
|
||||
if (type == CPLeftMouseUp)
|
||||
{
|
||||
if (_currentDivider != CPNotFound)
|
||||
{
|
||||
_currentDivider = CPNotFound;
|
||||
[self _postNotificationDidResize];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == CPLeftMouseDown)
|
||||
{
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
_currentDivider = CPNotFound;
|
||||
var count = [_subviews count] - 1;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var frame = [_subviews[i] frame],
|
||||
startPosition = frame.origin[_originComponent] + frame.size[_sizeComponent];
|
||||
|
||||
if ([self cursorAtPoint:point hitDividerAtIndex:i])
|
||||
{
|
||||
if ([anEvent clickCount] == 2 &&
|
||||
[_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)] &&
|
||||
[_delegate respondsToSelector:@selector(splitView:shouldCollapseSubview:forDoubleClickOnDividerAtIndex:)])
|
||||
{
|
||||
var minPosition = [self minPossiblePositionOfDividerAtIndex:i],
|
||||
maxPosition = [self maxPossiblePositionOfDividerAtIndex:i];
|
||||
|
||||
if ([_delegate splitView:self canCollapseSubview:_subviews[i]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i] forDoubleClickOnDividerAtIndex:i])
|
||||
{
|
||||
if ([self isSubviewCollapsed:_subviews[i]])
|
||||
[self setPosition:(minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i];
|
||||
else
|
||||
[self setPosition:minPosition ofDividerAtIndex:i];
|
||||
}
|
||||
else if ([_delegate splitView:self canCollapseSubview:_subviews[i+1]] && [_delegate splitView:self shouldCollapseSubview:_subviews[i+1] forDoubleClickOnDividerAtIndex:i])
|
||||
{
|
||||
if ([self isSubviewCollapsed:_subviews[i+1]])
|
||||
[self setPosition:(minPosition + (maxPosition - minPosition) / 2) ofDividerAtIndex:i];
|
||||
else
|
||||
[self setPosition:maxPosition ofDividerAtIndex:i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentDivider = i;
|
||||
_initialOffset = startPosition - point[_originComponent];
|
||||
|
||||
[self _postNotificationWillResize];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (type == CPLeftMouseDragged && _currentDivider != CPNotFound)
|
||||
{
|
||||
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
|
||||
|
||||
[self setPosition:(point[_originComponent] + _initialOffset) ofDividerAtIndex:_currentDivider];
|
||||
}
|
||||
|
||||
[CPApp setTarget:self selector:@selector(trackDivider:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
|
||||
}
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
[self trackDivider:anEvent];
|
||||
}
|
||||
|
||||
- (float)maxPossiblePositionOfDividerAtIndex:(int)dividerIndex
|
||||
{
|
||||
var frame = [_subviews[dividerIndex + 1] frame];
|
||||
|
||||
if (dividerIndex + 1 < [_subviews count] - 1)
|
||||
return frame.origin[_originComponent] + frame.size[_sizeComponent] - [self dividerThickness];
|
||||
else
|
||||
return [self frame].size[_sizeComponent] - [self dividerThickness];
|
||||
}
|
||||
|
||||
- (float)minPossiblePositionOfDividerAtIndex:(int)dividerIndex
|
||||
{
|
||||
var frame = [_subviews[dividerIndex - 1] frame];
|
||||
|
||||
if (dividerIndex > 0)
|
||||
return frame.origin[_originComponent] + frame.size[_sizeComponent] + [self dividerThickness];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)setPosition:(float)position ofDividerAtIndex:(int)dividerIndex
|
||||
{
|
||||
[self _adjustSubviewsWithCalculatedSize];
|
||||
|
||||
// not sure where this should override other positions?
|
||||
if ([_delegate respondsToSelector:@selector(splitView:constrainSplitPosition:ofSubviewAt:)])
|
||||
position = [_delegate splitView:self constrainSplitPosition:position ofSubviewAt:dividerIndex];
|
||||
|
||||
var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex],
|
||||
proposedMin = [self minPossiblePositionOfDividerAtIndex:dividerIndex],
|
||||
actualMax = proposedMax,
|
||||
actualMin = proposedMin;
|
||||
|
||||
if([_delegate respondsToSelector:@selector(splitView:constrainMinCoordinate:ofSubviewAt:)])
|
||||
actualMin = [_delegate splitView:self constrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex];
|
||||
|
||||
if([_delegate respondsToSelector:@selector(splitView:constrainMaxCoordinate:ofSubviewAt:)])
|
||||
actualMax = [_delegate splitView:self constrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex];
|
||||
|
||||
var frame = [self frame],
|
||||
viewA = _subviews[dividerIndex],
|
||||
frameA = [viewA frame],
|
||||
viewB = _subviews[dividerIndex+1],
|
||||
frameB = [viewB frame];
|
||||
|
||||
var realPosition = MAX(MIN(position, actualMax), actualMin);
|
||||
|
||||
if (position < proposedMin + (actualMin - proposedMin) / 2)
|
||||
if ([_delegate respondsToSelector:@selector(splitView:canCollapseSubview:)])
|
||||
if ([_delegate splitView:self canCollapseSubview:viewA])
|
||||
realPosition = proposedMin;
|
||||
|
||||
frameA.size[_sizeComponent] = realPosition - frameA.origin[_originComponent];
|
||||
[_subviews[dividerIndex] setFrame:frameA];
|
||||
|
||||
frameB.size[_sizeComponent] = frameB.origin[_originComponent] + frameB.size[_sizeComponent] - realPosition - [self dividerThickness];
|
||||
frameB.origin[_originComponent] = realPosition + [self dividerThickness];
|
||||
[_subviews[dividerIndex + 1] setFrame:frameB];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[self _adjustSubviewsWithCalculatedSize];
|
||||
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)resizeSubviewsWithOldSize:(CPSize)oldSize
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(splitView:resizeSubviewsWithOldSize:)])
|
||||
{
|
||||
[_delegate splitView:self resizeSubviewsWithOldSize:oldSize];
|
||||
return;
|
||||
}
|
||||
|
||||
[self _postNotificationWillResize];
|
||||
|
||||
var index = 0,
|
||||
count = [_subviews count],
|
||||
bounds = [self bounds],
|
||||
dividerThickness = [self dividerThickness];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var view = _subviews[index],
|
||||
viewFrame = CGRectMakeCopy(bounds);
|
||||
|
||||
if (index + 1 == count)
|
||||
viewFrame.size[_sizeComponent] = bounds.size[_sizeComponent] - viewFrame.origin[_originComponent];
|
||||
else
|
||||
viewFrame.size[_sizeComponent] = bounds.size[_sizeComponent] * ([view frame].size[_sizeComponent] / oldSize[_sizeComponent]);
|
||||
|
||||
bounds.origin[_originComponent] += viewFrame.size[_sizeComponent] + dividerThickness;
|
||||
|
||||
[view setFrame:viewFrame];
|
||||
}
|
||||
|
||||
[self _postNotificationDidResize];
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)delegate
|
||||
{
|
||||
if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)])
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewDidResizeSubviewsNotification object:self];
|
||||
if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)])
|
||||
[[CPNotificationCenter defaultCenter] removeObserver:_delegate name:CPSplitViewWillResizeSubviewsNotification object:self];
|
||||
|
||||
_delegate = delegate;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(splitViewDidResizeSubviews:)])
|
||||
[[CPNotificationCenter defaultCenter] addObserver:_delegate
|
||||
selector:@selector(splitViewDidResizeSubviews:)
|
||||
name:CPSplitViewDidResizeSubviewsNotification
|
||||
object:self];
|
||||
if ([_delegate respondsToSelector:@selector(splitViewWillResizeSubviews:)])
|
||||
[[CPNotificationCenter defaultCenter] addObserver:_delegate
|
||||
selector:@selector(splitViewWillResizeSubviews:)
|
||||
name:CPSplitViewWillResizeSubviewsNotification
|
||||
object:self];
|
||||
}
|
||||
|
||||
- (void)_postNotificationWillResize
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewWillResizeSubviewsNotification object:self];
|
||||
}
|
||||
|
||||
- (void)_postNotificationDidResize
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPSplitViewDidResizeSubviewsNotification object:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPSplitViewDelegateKey = "CPSplitViewDelegateKey",
|
||||
CPSplitViewIsVerticalKey = "CPSplitViewIsVerticalKey",
|
||||
CPSplitViewIsPaneSplitterKey = "CPSplitViewIsPaneSplitterKey";
|
||||
|
||||
@implementation CPSplitView (CPCoding)
|
||||
|
||||
/*
|
||||
Initializes the split view by unarchiving data from <code>aCoder</code>.
|
||||
@param aCoder the coder containing the archived CPSplitView.
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_currentDivider = CPNotFound;
|
||||
|
||||
_DOMDividerElements = [];
|
||||
|
||||
_delegate = [aCoder decodeObjectForKey:CPSplitViewDelegateKey];;
|
||||
|
||||
_isPaneSplitter = [aCoder decodeBoolForKey:CPSplitViewIsPaneSplitterKey];
|
||||
[self _setVertical:[aCoder decodeBoolForKey:CPSplitViewIsVerticalKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
Archives this split view into the provided coder.
|
||||
@param aCoder the coder to which the button's instance data will be written.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeConditionalObject:_delegate forKey:CPSplitViewDelegateKey];
|
||||
|
||||
[aCoder encodeBool:_isVertical forKey:CPSplitViewIsVerticalKey];
|
||||
[aCoder encodeBool:_isPaneSplitter forKey:CPSplitViewIsPaneSplitterKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* CPStringDrawing.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
|
||||
var CPStringReferenceElement = nil;
|
||||
|
||||
@implementation CPString (CPStringDrawing)
|
||||
|
||||
/*!
|
||||
Returns the string
|
||||
*/
|
||||
- (CPString)cssString
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CGSize)sizeWithFont:(CPFont)aFont
|
||||
{
|
||||
if (!CPStringReferenceElement)
|
||||
{
|
||||
CPStringReferenceElement = document.createElement("span");
|
||||
|
||||
var style = CPStringReferenceElement.style;
|
||||
|
||||
style.position = "absolute";
|
||||
style.whiteSpace = "pre";
|
||||
style.visibility = "visible";
|
||||
style.padding = "0px";
|
||||
style.margin = "0px";
|
||||
|
||||
style.left = "-100000px";
|
||||
style.top = "-100000px";
|
||||
style.zIndex = "10000";
|
||||
style.background = "red";
|
||||
|
||||
document.getElementsByTagName("body")[0].appendChild(CPStringReferenceElement);
|
||||
}
|
||||
|
||||
CPStringReferenceElement.style.font = [aFont ? aFont : [CPFont systemFontOfSize:12.0] cssString];
|
||||
|
||||
if (CPFeatureIsCompatible(CPJavascriptInnerTextFeature))
|
||||
CPStringReferenceElement.innerText = self;
|
||||
else if (CPFeatureIsCompatible(CPJavascriptTextContentFeature))
|
||||
CPStringReferenceElement.textContent = self;
|
||||
|
||||
return CGSizeMake(CPStringReferenceElement.clientWidth, CPStringReferenceElement.clientHeight);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,784 @@
|
||||
/*
|
||||
* CPTabView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPImageView.j"
|
||||
@import "CPTabViewItem.j"
|
||||
@import "CPView.j"
|
||||
|
||||
#include "CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
/*
|
||||
Places tabs on top with a bezeled border.
|
||||
@global
|
||||
@group CPTabViewType
|
||||
*/
|
||||
CPTopTabsBezelBorder = 0;
|
||||
//CPLeftTabsBezelBorder = 1;
|
||||
//CPBottomTabsBezelBorder = 2;
|
||||
//CPRightTabsBezelBorder = 3;
|
||||
/*
|
||||
Displays no tabs and has a bezeled border.
|
||||
@global
|
||||
@group CPTabViewType
|
||||
*/
|
||||
CPNoTabsBezelBorder = 4;
|
||||
/*
|
||||
Has no tabs and displays a line border.
|
||||
@global
|
||||
@group CPTabViewType
|
||||
*/
|
||||
CPNoTabsLineBorder = 5;
|
||||
/*
|
||||
Displays no tabs and no border.
|
||||
@global
|
||||
@group CPTabViewType
|
||||
*/
|
||||
CPNoTabsNoBorder = 6;
|
||||
|
||||
var CPTabViewBezelBorderLeftImage = nil,
|
||||
CPTabViewBackgroundCenterImage = nil,
|
||||
CPTabViewBezelBorderRightImage = nil,
|
||||
CPTabViewBezelBorderColor = nil,
|
||||
CPTabViewBezelBorderBackgroundColor = nil;
|
||||
|
||||
var LEFT_INSET = 7.0,
|
||||
RIGHT_INSET = 7.0;
|
||||
|
||||
var CPTabViewDidSelectTabViewItemSelector = 1,
|
||||
CPTabViewShouldSelectTabViewItemSelector = 2,
|
||||
CPTabViewWillSelectTabViewItemSelector = 4,
|
||||
CPTabViewDidChangeNumberOfTabViewItemsSelector = 8;
|
||||
|
||||
/*! @class CPTabView
|
||||
|
||||
This class represents a view that has multiple subviews (CPTabViewItem) presented as individual tabs.
|
||||
Only one CPTabViewItem is shown at a time, and other CPTabViewItems can be made visible
|
||||
(one at a time) by clicking on the CPTabViewItem's tab at the top of the tab view.
|
||||
|
||||
THe currently selected CPTabViewItem is the view that is displayed.
|
||||
*/
|
||||
@implementation CPTabView : CPView
|
||||
{
|
||||
CPView _labelsView;
|
||||
CPView _backgroundView;
|
||||
CPView _separatorView;
|
||||
|
||||
CPView _auxiliaryView;
|
||||
CPView _contentView;
|
||||
|
||||
CPArray _tabViewItems;
|
||||
CPTabViewItem _selectedTabViewItem;
|
||||
|
||||
CPTabViewType _tabViewType;
|
||||
|
||||
id _delegate;
|
||||
unsigned _delegateSelectors;
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != CPTabView)
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self],
|
||||
|
||||
emptyImage = [[CPImage alloc] initByReferencingFile:@"" size:CGSizeMake(7.0, 0.0)],
|
||||
backgroundImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/CPTabViewBezelBackgroundCenter.png"] size:CGSizeMake(1.0, 1.0)],
|
||||
|
||||
bezelBorderLeftImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/CPTabViewBezelBorderLeft.png"] size:CGSizeMake(7.0, 1.0)],
|
||||
bezerBorderImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/CPTabViewBezelBorder.png"] size:CGSizeMake(1.0, 1.0)],
|
||||
bezelBorderRightImage = [[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/CPTabViewBezelBorderRight.png"] size:CGSizeMake(7.0, 1.0)];
|
||||
|
||||
CPTabViewBezelBorderBackgroundColor = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
emptyImage,
|
||||
emptyImage,
|
||||
emptyImage,
|
||||
|
||||
bezelBorderLeftImage,
|
||||
backgroundImage,
|
||||
bezelBorderRightImage,
|
||||
|
||||
bezelBorderLeftImage,
|
||||
bezerBorderImage,
|
||||
bezelBorderRightImage
|
||||
]]];
|
||||
|
||||
CPTabViewBezelBorderColor = [CPColor colorWithPatternImage:bezerBorderImage];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
+ (CPColor)bezelBorderColor
|
||||
{
|
||||
return CPTabViewBezelBorderColor;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_tabViewType = CPTopTabsBezelBorder;
|
||||
_tabViewItems = [];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidMoveToWindow
|
||||
{
|
||||
if (_tabViewType != CPTopTabsBezelBorder || _labelsView)
|
||||
return;
|
||||
|
||||
[self _createBezelBorder];
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_createBezelBorder
|
||||
{
|
||||
var bounds = [self bounds];
|
||||
|
||||
_labelsView = [[_CPTabLabelsView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), 0.0)];
|
||||
|
||||
[_labelsView setTabView:self];
|
||||
[_labelsView setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[self addSubview:_labelsView];
|
||||
|
||||
_backgroundView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_backgroundView setBackgroundColor:CPTabViewBezelBorderBackgroundColor];
|
||||
|
||||
[_backgroundView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[self addSubview:_backgroundView];
|
||||
|
||||
_separatorView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_separatorView setBackgroundColor:[[self class] bezelBorderColor]];
|
||||
[_separatorView setAutoresizingMask:CPViewWidthSizable | CPViewMaxYMargin];
|
||||
|
||||
[self addSubview:_separatorView];
|
||||
}
|
||||
|
||||
/*
|
||||
Lays out the subviews
|
||||
@ignore
|
||||
*/
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
if (_tabViewType == CPTopTabsBezelBorder)
|
||||
{
|
||||
var backgroundRect = [self bounds],
|
||||
labelsViewHeight = [_CPTabLabelsView height];
|
||||
|
||||
backgroundRect.origin.y += labelsViewHeight;
|
||||
backgroundRect.size.height -= labelsViewHeight;
|
||||
|
||||
[_backgroundView setFrame:backgroundRect];
|
||||
|
||||
var auxiliaryViewHeight = 5.0;
|
||||
|
||||
if (_auxiliaryView)
|
||||
{
|
||||
auxiliaryViewHeight = CGRectGetHeight([_auxiliaryView frame]);
|
||||
|
||||
[_auxiliaryView setFrame:CGRectMake(LEFT_INSET, labelsViewHeight, CGRectGetWidth(backgroundRect) - LEFT_INSET - RIGHT_INSET, auxiliaryViewHeight)];
|
||||
}
|
||||
|
||||
[_separatorView setFrame:CGRectMake(LEFT_INSET, labelsViewHeight + auxiliaryViewHeight, CGRectGetWidth(backgroundRect) - LEFT_INSET - RIGHT_INSET, 1.0)];
|
||||
}
|
||||
|
||||
// CPNoTabsNoBorder
|
||||
[_contentView setFrame:[self contentRect]];
|
||||
}
|
||||
|
||||
// Adding and Removing Tabs
|
||||
/*!
|
||||
Adds a CPTabViewItem to the tab view.
|
||||
@param aTabViewItem the item to add
|
||||
*/
|
||||
- (void)addTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
[self insertTabViewItem:aTabViewItem atIndex:[_tabViewItems count]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Inserts a CPTabViewItem into the tab view
|
||||
at the specified index.
|
||||
@param aTabViewItem the item to insert
|
||||
@param anIndex the index for the item
|
||||
*/
|
||||
- (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(unsigned)anIndex
|
||||
{
|
||||
[_tabViewItems insertObject:aTabViewItem atIndex:anIndex];
|
||||
|
||||
[_labelsView tabView:self didAddTabViewItem:aTabViewItem];
|
||||
|
||||
if ([_tabViewItems count] == 1)
|
||||
[self selectFirstTabViewItem:self];
|
||||
|
||||
if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
|
||||
[_delegate tabViewDidChangeNumberOfTabViewItems:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Removes the specified tab view item from the tab view.
|
||||
@param aTabViewItem the item to remove
|
||||
*/
|
||||
- (void)removeTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
[_tabViewItems removeObjectIdenticalTo:aTabViewItem];
|
||||
|
||||
[_labelsView tabView:self didRemoveTabViewItem:aTabViewItem];
|
||||
|
||||
if (_delegateSelectors & CPTabViewDidChangeNumberOfTabViewItemsSelector)
|
||||
[_delegate tabViewDidChangeNumberOfTabViewItems:self];
|
||||
}
|
||||
|
||||
// Accessing Tabs
|
||||
/*!
|
||||
Returns the index of the specified item
|
||||
@param aTabViewItem the item to find the index for
|
||||
*/
|
||||
- (int)indexOfTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
return [_tabViewItems indexOfObjectIdenticalTo:aTabViewItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the index of the CPTabViewItem with the specified identifier.
|
||||
@param anIdentifier the identifier of the item
|
||||
*/
|
||||
- (int)indexOfTabViewItemWithIdentifier:(CPString)anIdentifier
|
||||
{
|
||||
var index = 0,
|
||||
count = [_tabViewItems count];
|
||||
|
||||
for (; index < count; ++index)
|
||||
if ([[_tabViewItems[index] identifier] isEqualTo:anIdentifier])
|
||||
return index;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of items in the tab view.
|
||||
*/
|
||||
- (unsigned)numberOfTabViewItems
|
||||
{
|
||||
return [_tabViewItems count];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPTabViewItem at the specified index.
|
||||
*/
|
||||
- (CPTabViewItem)tabViewItemAtIndex:(unsigned)anIndex
|
||||
{
|
||||
return _tabViewItems[anIndex];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the array of items that backs this tab view.
|
||||
*/
|
||||
- (CPArray)tabViewItems
|
||||
{
|
||||
return _tabViewItems;
|
||||
}
|
||||
|
||||
// Selecting a Tab
|
||||
/*!
|
||||
Sets the first tab view item in the array to be displayed to the user.
|
||||
@param aSender the object making this request
|
||||
*/
|
||||
- (void)selectFirstTabViewItem:(id)aSender
|
||||
{
|
||||
var count = [_tabViewItems count];
|
||||
|
||||
if (count)
|
||||
[self selectTabViewItemAtIndex:0];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the last tab view item in the array to be displayed to the user.
|
||||
@param aSender the object making this request
|
||||
*/
|
||||
- (void)selectLastTabViewItem:(id)aSender
|
||||
{
|
||||
var count = [_tabViewItems count];
|
||||
|
||||
if (count)
|
||||
[self selectTabViewItemAtIndex:count - 1];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the next tab item in the array to be displayed.
|
||||
@param aSender the object making this request
|
||||
*/
|
||||
- (void)selectNextTabViewItem:(id)aSender
|
||||
{
|
||||
if (!_selectedTabViewItem)
|
||||
return;
|
||||
|
||||
var index = [self indexOfTabViewItem:_selectedTabViewItem],
|
||||
count = [_tabViewItems count];
|
||||
|
||||
[self selectTabViewItemAtIndex:index + 1 % count];
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the previous item in the array for display.
|
||||
@param aSender the object making this request
|
||||
*/
|
||||
- (void)selectPreviousTabViewItem:(id)aSender
|
||||
{
|
||||
if (!_selectedTabViewItem)
|
||||
return;
|
||||
|
||||
var index = [self indexOfTabViewItem:_selectedTabViewItem],
|
||||
count = [_tabViewItems count];
|
||||
|
||||
[self selectTabViewItemAtIndex:index == 0 ? count : index - 1];
|
||||
}
|
||||
|
||||
/*!
|
||||
Displays the specified item in the tab view.
|
||||
@param aTabViewItem the item to display
|
||||
*/
|
||||
- (void)selectTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
if ((_delegateSelectors & CPTabViewShouldSelectTabViewItemSelector) && ![_delegate tabView:self shouldSelectTabViewItem:aTabViewItem])
|
||||
return;
|
||||
|
||||
if (_delegateSelectors & CPTabViewWillSelectTabViewItemSelector)
|
||||
[_delegate tabView:self willSelectTabViewItem:aTabViewItem];
|
||||
|
||||
if (_selectedTabViewItem)
|
||||
{
|
||||
_selectedTabViewItem._tabState = CPBackgroundTab;
|
||||
[_labelsView tabView:self didChangeStateOfTabViewItem:_selectedTabViewItem];
|
||||
|
||||
[_contentView removeFromSuperview];
|
||||
[_auxiliaryView removeFromSuperview];
|
||||
}
|
||||
_selectedTabViewItem = aTabViewItem;
|
||||
|
||||
_selectedTabViewItem._tabState = CPSelectedTab;
|
||||
|
||||
_contentView = [_selectedTabViewItem view];
|
||||
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
_auxiliaryView = [_selectedTabViewItem auxiliaryView];
|
||||
[_auxiliaryView setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[self addSubview:_contentView];
|
||||
|
||||
if (_auxiliaryView)
|
||||
[self addSubview:_auxiliaryView];
|
||||
|
||||
[_labelsView tabView:self didChangeStateOfTabViewItem:_selectedTabViewItem];
|
||||
|
||||
[self layoutSubviews];
|
||||
|
||||
if (_delegateSelectors & CPTabViewDidSelectTabViewItemSelector)
|
||||
[_delegate tabView:self didSelectTabViewItem:aTabViewItem];
|
||||
}
|
||||
|
||||
/*!
|
||||
Selects the item at the specified index.
|
||||
@param anIndex the index of the item to display.
|
||||
*/
|
||||
- (void)selectTabViewItemAtIndex:(unsigned)anIndex
|
||||
{
|
||||
[self selectTabViewItem:_tabViewItems[anIndex]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the current item being displayed.
|
||||
*/
|
||||
- (CPTabViewItem)selectedTabViewItem
|
||||
{
|
||||
return _selectedTabViewItem;
|
||||
}
|
||||
|
||||
//
|
||||
/*!
|
||||
Sets the tab view type.
|
||||
@param aTabViewType the view type
|
||||
*/
|
||||
- (void)setTabViewType:(CPTabViewType)aTabViewType
|
||||
{
|
||||
if (_tabViewType == aTabViewType)
|
||||
return;
|
||||
|
||||
_tabViewType = aTabViewType;
|
||||
|
||||
if (_tabViewType == CPNoTabsBezelBorder || _tabViewType == CPNoTabsLineBorder || _tabViewType == CPNoTabsNoBorder)
|
||||
[_labelsView removeFromSuperview];
|
||||
else if (![_labelsView superview])
|
||||
[self addSubview:_lablesView];
|
||||
|
||||
if (_tabViewType == CPNoTabsLineBorder || _tabViewType == CPNoTabsNoBorder)
|
||||
[_backgroundView removeFromSuperview];
|
||||
else if (![_backgroundView superview])
|
||||
[self addSubview:_backgroundView];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tab view type.
|
||||
*/
|
||||
- (CPTabViewType)tabViewType
|
||||
{
|
||||
return _tabViewType;
|
||||
}
|
||||
|
||||
// Determining the Size
|
||||
/*!
|
||||
Returns the content rectangle.
|
||||
*/
|
||||
- (CGRect)contentRect
|
||||
{
|
||||
var contentRect = CGRectMakeCopy([self bounds]);
|
||||
|
||||
if (_tabViewType == CPTopTabsBezelBorder)
|
||||
{
|
||||
var labelsViewHeight = [_CPTabLabelsView height],
|
||||
auxiliaryViewHeight = _auxiliaryView ? CGRectGetHeight([_auxiliaryView frame]) : 5.0,
|
||||
separatorViewHeight = 1.0;
|
||||
|
||||
contentRect.origin.y += labelsViewHeight + auxiliaryViewHeight + separatorViewHeight;
|
||||
contentRect.size.height -= labelsViewHeight + auxiliaryViewHeight + separatorViewHeight * 2.0; // 2 for the bottom border as well.
|
||||
|
||||
contentRect.origin.x += LEFT_INSET;
|
||||
contentRect.size.width -= LEFT_INSET + RIGHT_INSET;
|
||||
}
|
||||
|
||||
return contentRect;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the delegate for this tab view.
|
||||
@param aDelegate the tab view's delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate == aDelegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
|
||||
_delegateSelectors = 0;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)])
|
||||
_delegateSelectors |= CPTabViewShouldSelectTabViewItemSelector;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(tabView:willSelectTabViewItem:)])
|
||||
_delegateSelectors |= CPTabViewWillSelectTabViewItemSelector;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(tabView:didSelectTabViewItem:)])
|
||||
_delegateSelectors |= CPTabViewDidSelectTabViewItemSelector;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)])
|
||||
_delegateSelectors |= CPTabViewDidChangeNumberOfTabViewItemsSelector;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
var location = [_labelsView convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
tabViewItem = [_labelsView representedTabViewItemAtPoint:location];
|
||||
|
||||
if (tabViewItem)
|
||||
[self selectTabViewItem:tabViewItem];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPTabViewItemsKey = "CPTabViewItemsKey",
|
||||
CPTabViewSelectedItemKey = "CPTabViewSelectedItemKey",
|
||||
CPTabViewTypeKey = "CPTabViewTypeKey",
|
||||
CPTabViewDelegateKey = "CPTabViewDelegateKey";
|
||||
|
||||
@implementation CPTabView (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_tabViewType = [aCoder decodeIntForKey:CPTabViewTypeKey];
|
||||
_tabViewItems = [];
|
||||
|
||||
// FIXME: this is somewhat hacky
|
||||
[self _createBezelBorder];
|
||||
|
||||
var items = [aCoder decodeObjectForKey:CPTabViewItemsKey];
|
||||
for (var i = 0; items && i < items.length; i++)
|
||||
[self insertTabViewItem:items[i] atIndex:i];
|
||||
|
||||
var selected = [aCoder decodeObjectForKey:CPTabViewSelectedItemKey];
|
||||
if (selected)
|
||||
[self selectTabViewItem:selected];
|
||||
|
||||
[self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
var actualSubviews = _subviews;
|
||||
_subviews = [];
|
||||
[super encodeWithCoder:aCoder];
|
||||
_subviews = actualSubviews;
|
||||
|
||||
[aCoder encodeObject:_tabViewItems forKey:CPTabViewItemsKey];;
|
||||
[aCoder encodeObject:_selectedTabViewItem forKey:CPTabViewSelectedItemKey];
|
||||
|
||||
[aCoder encodeInt:_tabViewType forKey:CPTabViewTypeKey];
|
||||
|
||||
[aCoder encodeConditionalObject:_delegate forKey:CPTabViewDelegateKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var _CPTabLabelsViewBackgroundColor = nil,
|
||||
_CPTabLabelsViewInsideMargin = 10.0,
|
||||
_CPTabLabelsViewOutsideMargin = 15.0;
|
||||
|
||||
/* @ignore */
|
||||
@implementation _CPTabLabelsView : CPView
|
||||
{
|
||||
CPTabView _tabView;
|
||||
CPDictionary _tabLabels;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [_CPTabLabelsView class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPTabLabelsViewBackgroundColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelsViewLeft.png"] size:CGSizeMake(12.0, 26.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelsViewCenter.png"] size:CGSizeMake(1.0, 26.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelsViewRight.png"] size:CGSizeMake(12.0, 26.0)]
|
||||
] isVertical:NO]];
|
||||
}
|
||||
|
||||
+ (float)height
|
||||
{
|
||||
return 26.0;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_tabLabels = [];
|
||||
|
||||
[self setBackgroundColor:_CPTabLabelsViewBackgroundColor];
|
||||
|
||||
[self setFrameSize:CGSizeMake(CGRectGetWidth(aFrame), 26.0)];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setTabView:(CPTabView)aTabView
|
||||
{
|
||||
_tabView = aTabView;
|
||||
}
|
||||
|
||||
- (CPTabView)tabView
|
||||
{
|
||||
return _tabView;
|
||||
}
|
||||
|
||||
- (void)tabView:(CPTabView)aTabView didAddTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
var label = [[_CPTabLabel alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[label setTabViewItem:aTabViewItem];
|
||||
|
||||
_tabLabels.push(label);
|
||||
|
||||
[self addSubview:label];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)tabView:(CPTabView)aTabView didRemoveTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
var index = [aTabView indexOfTabViewItem:aTabViewItem],
|
||||
label = _tabLabels[index];
|
||||
|
||||
[_tabLabels removeObjectAtIndex:index];
|
||||
|
||||
[label removeFromSuperview];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
-(void)tabView:(CPTabView)aTabView didChangeStateOfTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
[_tabLabels[[aTabView indexOfTabViewItem:aTabViewItem]] setTabState:[aTabViewItem tabState]];
|
||||
}
|
||||
|
||||
- (CPTabViewItem)representedTabViewItemAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
var index = 0,
|
||||
count = _tabLabels.length;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var label = _tabLabels[index];
|
||||
|
||||
if (CGRectContainsPoint([label frame], aPoint))
|
||||
return [label tabViewItem];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
var index = 0,
|
||||
count = _tabLabels.length,
|
||||
width = (_CGRectGetWidth([self bounds]) - (count - 1) * _CPTabLabelsViewInsideMargin - 2 * _CPTabLabelsViewOutsideMargin) / count,
|
||||
x = _CPTabLabelsViewOutsideMargin;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var label = _tabLabels[index],
|
||||
frame = _CGRectMake(x, 8.0, width, 18.0);
|
||||
|
||||
[label setFrame:frame];
|
||||
|
||||
x = _CGRectGetMaxX(frame) + _CPTabLabelsViewInsideMargin;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
if (CGSizeEqualToSize([self frame].size, aSize))
|
||||
return;
|
||||
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPTabLabelBackgroundColor = nil,
|
||||
_CPTabLabelSelectedBackgroundColor = nil;
|
||||
|
||||
/* @ignore */
|
||||
@implementation _CPTabLabel : CPView
|
||||
{
|
||||
CPTabViewItem _tabViewItem;
|
||||
CPTextField _labelField;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [_CPTabLabel class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPTabLabelBackgroundColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelBackgroundLeft.png"] size:CGSizeMake(6.0, 18.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelBackgroundCenter.png"] size:CGSizeMake(1.0, 18.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelBackgroundRight.png"] size:CGSizeMake(6.0, 18.0)]
|
||||
] isVertical:NO]];
|
||||
|
||||
_CPTabLabelSelectedBackgroundColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelSelectedLeft.png"] size:CGSizeMake(3.0, 18.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelSelectedCenter.png"] size:CGSizeMake(1.0, 18.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"CPTabView/_CPTabLabelSelectedRight.png"] size:CGSizeMake(3.0, 18.0)]
|
||||
] isVertical:NO]];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_labelField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[_labelField setAlignment:CPCenterTextAlignment];
|
||||
[_labelField setFrame:CGRectMake(5.0, 0.0, CGRectGetWidth(aFrame) - 10.0, 20.0)];
|
||||
[_labelField setAutoresizingMask:CPViewWidthSizable];
|
||||
[_labelField setFont:[CPFont boldSystemFontOfSize:11.0]];
|
||||
|
||||
[self addSubview:_labelField];
|
||||
|
||||
[self setTabState:CPBackgroundTab];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setTabState:(CPTabState)aTabState
|
||||
{
|
||||
[self setBackgroundColor:aTabState == CPSelectedTab ? _CPTabLabelSelectedBackgroundColor : _CPTabLabelBackgroundColor];
|
||||
}
|
||||
|
||||
- (void)setTabViewItem:(CPTabViewItem)aTabViewItem
|
||||
{
|
||||
_tabViewItem = aTabViewItem;
|
||||
|
||||
[self update];
|
||||
}
|
||||
|
||||
- (CPTabViewItem)tabViewItem
|
||||
{
|
||||
return _tabViewItem;
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
[_labelField setStringValue:[_tabViewItem label]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* CPTabViewItem.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import <AppKit/CPView.j>
|
||||
|
||||
|
||||
/*
|
||||
The tab is currently selected.
|
||||
@global
|
||||
@group CPTabState
|
||||
*/
|
||||
CPSelectedTab = 0;
|
||||
/*
|
||||
The tab is currently in the background (not selected).
|
||||
@global
|
||||
@group CPTabState
|
||||
*/
|
||||
CPBackgroundTab = 1;
|
||||
/*
|
||||
The tab of this item is currently being pressed by the user.
|
||||
@global
|
||||
@group CPTabState
|
||||
*/
|
||||
CPPressedTab = 2;
|
||||
|
||||
/*! @class CPTabView
|
||||
|
||||
The class representation of an item in a CPTabView. One tab view item
|
||||
can be shown at a time in a CPTabView.
|
||||
*/
|
||||
@implementation CPTabViewItem : CPObject
|
||||
{
|
||||
id _identifier;
|
||||
CPString _label;
|
||||
|
||||
CPView _view;
|
||||
CPView _auxiliaryView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the tab view item with the specified identifier.
|
||||
@return the initialized CPTabViewItem
|
||||
*/
|
||||
- (id)initWithIdentifier:(id)anIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_identifier = anIdentifier;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Working With Labels
|
||||
/*!
|
||||
Sets the CPTabViewItem's label.
|
||||
@param aLabel the label for the item
|
||||
*/
|
||||
- (void)setLabel:(CPString)aLabel
|
||||
{
|
||||
_label = aLabel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CPTabViewItem's label
|
||||
*/
|
||||
- (CPString)label
|
||||
{
|
||||
return _label;
|
||||
}
|
||||
|
||||
// Checking the Tab Display State
|
||||
/*!
|
||||
Returns the tab's current state.
|
||||
*/
|
||||
- (CPTabState)tabState
|
||||
{
|
||||
return _tabState;
|
||||
}
|
||||
|
||||
// Assigning an Identifier Object
|
||||
/*!
|
||||
Sets the item's identifier.
|
||||
@param anIdentifier the new identifier for the item
|
||||
*/
|
||||
- (void)setIdentifier:(id)anIdentifier
|
||||
{
|
||||
_identifier = anIdentifier;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tab's identifier.
|
||||
*/
|
||||
- (id)identifier
|
||||
{
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
// Assigning a View
|
||||
/*!
|
||||
Sets the view that gets displayed in this tab.
|
||||
*/
|
||||
- (void)setView:(CPView)aView
|
||||
{
|
||||
_view = aView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tab's view.
|
||||
*/
|
||||
- (CPView)view
|
||||
{
|
||||
return _view;
|
||||
}
|
||||
|
||||
// Assigning an Auxiliary View
|
||||
/*!
|
||||
Sets the tab's auxillary view.
|
||||
@param anAuxillaryView the new auxillary view
|
||||
*/
|
||||
- (void)setAuxiliaryView:(CPView)anAuxiliaryView
|
||||
{
|
||||
_auxiliaryView = anAuxiliaryView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the tab's auxillary view
|
||||
*/
|
||||
- (CPView)auxiliaryView
|
||||
{
|
||||
return _auxiliaryView;
|
||||
}
|
||||
|
||||
// Accessing the Parent Tab View
|
||||
/*!
|
||||
Returns the tab view that contains this item.
|
||||
*/
|
||||
- (CPTabView)tabView
|
||||
{
|
||||
return _tabView;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPTabViewItemIdentifierKey = "CPTabViewItemIdentifierKey",
|
||||
CPTabViewItemLabelKey = "CPTabViewItemLabelKey",
|
||||
CPTabViewItemViewKey = "CPTabViewItemViewKey",
|
||||
CPTabViewItemAuxViewKey = "CPTabViewItemAuxViewKey";
|
||||
|
||||
|
||||
@implementation CPTabViewItem (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_identifier = [aCoder decodeObjectForKey:CPTabViewItemIdentifierKey];
|
||||
_label = [aCoder decodeObjectForKey:CPTabViewItemLabelKey];
|
||||
|
||||
_view = [aCoder decodeObjectForKey:CPTabViewItemViewKey];
|
||||
_auxiliaryView = [aCoder decodeObjectForKey:CPTabViewItemAuxViewKey];
|
||||
|
||||
CPLog.warn("_identifier="+_identifier+" _label="+_label+" _view="+_view+" _auxiliaryView="+_auxiliaryView);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_identifier forKey:CPTabViewItemIdentifierKey];
|
||||
[aCoder encodeObject:_label forKey:CPTabViewItemLabelKey];
|
||||
|
||||
[aCoder encodeObject:_view forKey:CPTabViewItemViewKey];
|
||||
[aCoder encodeObject:_auxiliaryView forKey:CPTabViewItemAuxViewKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* CPTableColumn.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/Foundation.j>
|
||||
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
|
||||
/*
|
||||
@global
|
||||
@class CPTableColumn
|
||||
*/
|
||||
CPTableColumnNoResizing = 0;
|
||||
/*
|
||||
@global
|
||||
@class CPTableColumn
|
||||
*/
|
||||
CPTableColumnAutoresizingMask = 1;
|
||||
/*
|
||||
@global
|
||||
@class CPTableColumn
|
||||
*/
|
||||
CPTableColumnUserResizingMask = 2;
|
||||
|
||||
/*! @class CPTableColumn
|
||||
|
||||
An CPTableColumn object mainly keeps information about the width of the column, its minimum and maximum width; whether the column can be edited or resized; and the cells used to draw the column header and the data in the column. You can change all these attributes of the column by calling the appropriate methods. Please note that the table column does not hold nor has access to the data to be displayed in the column; this data is maintained in the table view's data source.</p>
|
||||
|
||||
<p>Each CPTableColumn object is identified by a CPString, called the column identifier. The reason is that, after a column has been added to a table view, the user might move the columns around, so there is a need to identify the columns regardless of its position in the table.
|
||||
|
||||
@ignore
|
||||
*/
|
||||
@implementation CPTableColumn : CPObject
|
||||
{
|
||||
CPString _identifier;
|
||||
|
||||
CPTableView _tableView;
|
||||
|
||||
float _width;
|
||||
float _minWidth;
|
||||
float _maxWidth;
|
||||
|
||||
unsigned _resizingMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the table column with the specified identifier.
|
||||
@param anIdentifier the identifier
|
||||
@return the initialized table column
|
||||
*/
|
||||
- (id)initWithIdentifier:(CPString)anIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_identifier = anIdentifier;
|
||||
|
||||
_width = 40.0;
|
||||
_minWidth = 8.0;
|
||||
_maxWidth = 1000.0;
|
||||
|
||||
// FIXME
|
||||
_dataCell = [[CPTextField alloc] initWithFrame:CPRectMakeZero()];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the table column's identifier
|
||||
@param anIdentifier the new identifier
|
||||
*/
|
||||
- (void)setIdentifier:(CPString)anIdentifier
|
||||
{
|
||||
_identifier = anIdentifier;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the table column's identifier
|
||||
*/
|
||||
- (CPString)identifier
|
||||
{
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
// Setting the CPTableView
|
||||
/*!
|
||||
Sets the table's view. This is called automatically by Cappuccino.
|
||||
@param aTableView the new table view
|
||||
*/
|
||||
- (void)setTableView:(CPTableView)aTableView
|
||||
{
|
||||
_tableView = aTableView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the column's table view.
|
||||
*/
|
||||
- (CPTableView)tableView
|
||||
{
|
||||
return _tableView;
|
||||
}
|
||||
|
||||
// Controlling size
|
||||
/*!
|
||||
Sets the column's width.
|
||||
@param aWidth the new column width
|
||||
*/
|
||||
- (void)setWidth:(float)aWidth
|
||||
{
|
||||
_width = aWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the column's width
|
||||
*/
|
||||
- (float)width
|
||||
{
|
||||
return _width;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets column's minimum width.
|
||||
@param aWidth the new minimum column width
|
||||
*/
|
||||
- (void)setMinWidth:(float)aWidth
|
||||
{
|
||||
if (_width < (_minWidth = aWidth))
|
||||
[self setWidth:_minWidth];
|
||||
}
|
||||
|
||||
/*!
|
||||
The column's minimum width
|
||||
*/
|
||||
- (float)minWidth
|
||||
{
|
||||
return _minWidth;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the column's maximum width.
|
||||
@param aWidth the new maximum width
|
||||
*/
|
||||
- (void)setMaxWidth:(float)aWidth
|
||||
{
|
||||
if (_width > (_maxmimumWidth = aWidth))
|
||||
[self setWidth:_maxWidth];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the resizing mask. The mask is one of:
|
||||
<pre>
|
||||
CPTableColumnNoResizing;
|
||||
CPTableColumnAutoresizingMask;
|
||||
CPTableColumnUserResizingMask;
|
||||
</pre>
|
||||
@param aMask the new resizing mask
|
||||
*/
|
||||
- (void)setResizingMask:(unsigned)aMask
|
||||
{
|
||||
_resizingMask = aMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the column's resizing mask. One of:
|
||||
<pre>
|
||||
CPTableColumnNoResizing;
|
||||
CPTableColumnAutoresizingMask;
|
||||
CPTableColumnUserResizingMask;
|
||||
</pre>
|
||||
*/
|
||||
- (unsigned)resizingMask
|
||||
{
|
||||
return _resizingMask;
|
||||
}
|
||||
|
||||
/*!
|
||||
Resizes the column according to the min, max and set width.
|
||||
*/
|
||||
- (void)sizeToFit
|
||||
{
|
||||
var width = CPRectGetWidth([_headerView frame]);
|
||||
|
||||
if (width < _minWidth)
|
||||
[self setMinWidth:width];
|
||||
else if (width > _maxWidth)
|
||||
[self setMaxWidth:width]
|
||||
|
||||
if (_width != width)
|
||||
[self setWidth:width];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the column in this data is editable.
|
||||
@param aFlag <code>YES</code> means the column data is editable
|
||||
*/
|
||||
- (void)setEditable:(BOOL)aFlag
|
||||
{
|
||||
_isEditable = aFlag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the column data is editable.
|
||||
*/
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the view that draws the column's header.
|
||||
@param aHeaderView the view that will draws the column header
|
||||
*/
|
||||
- (void)setHeaderView:(CPView)aHeaderView
|
||||
{
|
||||
_headerView = aHeaderView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Return the view that draws the column's header
|
||||
*/
|
||||
- (CPView)headerView
|
||||
{
|
||||
return _headerView;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the data cell that draws rows in this column.
|
||||
*/
|
||||
- (void)setDataCell:(CPCell)aDataCell
|
||||
{
|
||||
_dataCell = aDataCell;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the data cell that draws rows in this column
|
||||
*/
|
||||
- (CPCell)dataCell
|
||||
{
|
||||
return _dataCell;
|
||||
}
|
||||
|
||||
/*!
|
||||
By default returns the value from <code>dataCell</code>. This can
|
||||
be overridden by a subclass to return different cells for different
|
||||
rows.
|
||||
@param aRowIndex the index of the row to obtain the cell for
|
||||
*/
|
||||
- (CPCell)dataCellForRow:(int)aRowIndex
|
||||
{
|
||||
return [self dataCell];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,425 @@
|
||||
/*
|
||||
* CPTableView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
import "CPControl.j"
|
||||
import "CPTableColumn.j"
|
||||
|
||||
|
||||
var CPTableViewCellPlaceholder = nil;
|
||||
|
||||
/*
|
||||
CPTableView is located within the AppKit framework and is used to display tables. It uses a delegate model for getting its data i.e. you give it an object that provides it with the data it should display.
|
||||
|
||||
@ignore
|
||||
*/
|
||||
@implementation CPTableView : CPControl
|
||||
{
|
||||
id _dataSource;
|
||||
|
||||
CPScrollView _scrollView;
|
||||
CPTableHeaderView _headerView;
|
||||
|
||||
CPArray _tableColumns;
|
||||
|
||||
unsigned _numberOfRows;
|
||||
unsigned _numberOfColumns;
|
||||
|
||||
CPArray _tableCells;
|
||||
CPArray _tableColumnViews;
|
||||
|
||||
CGSize _intercellSpacing;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPTableView class])
|
||||
return;
|
||||
|
||||
CPTableViewCellPlaceholder = [[CPObject alloc] init];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
/*_scrollView = [[CPView alloc] initWithFrame:CPRectMakeZero()];
|
||||
_headerView = [[CPView alloc] initWithFrame:CPRectMakeZero()];
|
||||
|
||||
[_scrollView setBackgroundColor:[CPColor redColor]];
|
||||
[_headerView setBackgroundColor:[CPColor blueColor]];
|
||||
|
||||
[self addSubview:_scrollView];
|
||||
[self addSubview:_headerView];
|
||||
|
||||
[self tile];*/
|
||||
|
||||
//[self setBackgroundColor:[CPColor redColor]];
|
||||
|
||||
_rowHeight = 17.0;
|
||||
|
||||
_tableCells = [[CPArray alloc] init];
|
||||
_tableColumns = [[CPArray alloc] init];
|
||||
_tableColumnViews = [[CPArray alloc] init];
|
||||
|
||||
_intercellSpacing = CPSizeMake(3.0, 2.0);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the table's column height
|
||||
*/
|
||||
- (float)columnHeight
|
||||
{
|
||||
var bounds = [self bounds],
|
||||
height = _numberOfRows * (_rowHeight + _intercellSpacing.height);
|
||||
|
||||
return CPRectGetHeight(bounds) > height ? CPRectGetHeight(bounds) : height;
|
||||
}
|
||||
|
||||
- (void)loadTableCellsInRect:(CGRect)aRect
|
||||
{
|
||||
if (!_dataSource)
|
||||
return;
|
||||
|
||||
// Use a ambitious estimate for our starting row.
|
||||
var rows = CPMakeRange(MAX(Math.floor((CPRectGetMinY(aRect) + _intercellSpacing.height) / (_rowHeight + _intercellSpacing.height)), 0), 1);
|
||||
|
||||
// Use a conservative estimate for the final row.
|
||||
rows.length = MIN(_numberOfRows, Math.ceil(CPRectGetMaxY(aRect) / (_rowHeight + _intercellSpacing.height))) - rows.location;
|
||||
|
||||
var columns = CPMakeRange(0, 1);
|
||||
|
||||
// Iterate through all our columns until we find one that intersects the rect.
|
||||
while (columns.location < _numberOfColumns && !CPRectIntersectsRect([_tableColumnViews[columns.location] frame], aRect))
|
||||
++columns.location;
|
||||
|
||||
// Now iterate through our columns until we find one that doesn't intersect our rect.
|
||||
while (CPMaxRange(columns) < _numberOfColumns && CPRectIntersectsRect([_tableColumnViews[CPMaxRange(columns)] frame], aRect))
|
||||
++columns.length;
|
||||
|
||||
var row = rows.location,
|
||||
column = 0;
|
||||
|
||||
for (; row < CPMaxRange(rows); ++row)
|
||||
for (column = columns.location; column < CPMaxRange(columns); ++column)
|
||||
{
|
||||
if (!_tableCells[column][row] || _tableCells[column][row] == CPTableViewCellPlaceholder)
|
||||
{
|
||||
_tableCells[column][row] = [[_tableColumns[column] dataCellForRow:row] copy];
|
||||
|
||||
[_tableCells[column][row] setFrame:CPRectMake(0.0, row * (_rowHeight + _intercellSpacing.height), [_tableColumns[column] width], _rowHeight)];
|
||||
//[_tableCells[column][row] setBackgroundColor:[CPColor blueColor]];
|
||||
[_tableColumnViews[column] addSubview:_tableCells[column][row]];
|
||||
}
|
||||
|
||||
[_tableCells[column][row] setObjectValue:[_dataSource tableView:self objectValueForTableColumn:_tableColumns[column] row:row]];
|
||||
}
|
||||
}
|
||||
|
||||
// Setting display attributes
|
||||
/*
|
||||
Sets the width and height between cell. The default is (3.0, 2.0) (width, height).
|
||||
@param the width and height spacing
|
||||
*/
|
||||
- (void)setIntercellSpacing:(CGSize)aSize
|
||||
{
|
||||
if (_intercellSpacing.width != aSize.width)
|
||||
{
|
||||
var i = 1,
|
||||
delta = aSize.width - _intercellSpacing.width;
|
||||
total = delta;
|
||||
|
||||
for (; i < _numberOfColumns; ++i, total += delta)
|
||||
{
|
||||
var origin = [_tableColumnViews[i] frame].origin;
|
||||
[_tableColumnViews[i] setFrameOrigin:CPPointMake(origin.x + total, origin.y)];
|
||||
}
|
||||
}
|
||||
|
||||
if (_intercellSpacing.height != aSize.height)
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
for (; i < _numberOfColumns; ++i, total += delta)
|
||||
{
|
||||
[_tableColumnViews[i] setFrameSize:CPSizeMake([_tableColumnViews[i] width], _numberOfRows * (_rowHeight + _intercellSpacing.height))];
|
||||
|
||||
var j = 1,
|
||||
y = _rowHeight + _intercellSpacing.height;
|
||||
|
||||
for (; j < _numberOfRows; ++i, y += _rowHeight + _intercellSpacing.height)
|
||||
{
|
||||
if (_tableCells[i][j] == CPTableViewCellPlaceholder)
|
||||
continue;
|
||||
|
||||
[_tableCells[i][j] setFrameOrigin:CPPointMake(0.0, y)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_intercellSpacing = CPSizeCreateCopy(aSize);
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the width and height of the space between
|
||||
cells.
|
||||
*/
|
||||
- (CGSize)intercellSpacing
|
||||
{
|
||||
return _intercellSpacing;
|
||||
}
|
||||
|
||||
/*
|
||||
Sets the height of table rows
|
||||
@param aRowHeight the new height of the table rows
|
||||
*/
|
||||
- (void)setRowHeight:(unsigned)aRowHeight
|
||||
{
|
||||
if (_rowHeight == aRowHeight)
|
||||
return;
|
||||
|
||||
_rowHeight = aRowHeight;
|
||||
|
||||
var row = 0,
|
||||
column = 0;
|
||||
|
||||
for (; row < _numberOfRows; ++row)
|
||||
for (column = 0; column < _numberOfColumns; ++column)
|
||||
[_tableCells[column][row] setFrameOrigin:CPPointMake(0.0, row * (_rowHeight + _intercellSpacing.height))];
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the height of a table row
|
||||
*/
|
||||
- (unsigned)rowHeight
|
||||
{
|
||||
return _rowHeight;
|
||||
}
|
||||
|
||||
/*
|
||||
Adds a column to the table
|
||||
@param aTableColumn the column to be added
|
||||
*/
|
||||
- (void)addTableColumn:(CPTableColumn)aTableColumn
|
||||
{
|
||||
var i = 0,
|
||||
x = _numberOfColumns ? CPRectGetMaxX([self rectOfColumn:_numberOfColumns - 1]) + _intercellSpacing.width : 0.0,
|
||||
tableColumnView = [[CPView alloc] initWithFrame:CPRectMake(x, 0.0, [aTableColumn width], [self columnHeight])],
|
||||
tableColumnCells = [[CPArray alloc] init];
|
||||
|
||||
[_tableColumns addObject:aTableColumn];
|
||||
[_tableColumnViews addObject:tableColumnView];
|
||||
|
||||
// [tableColumnView setBackgroundColor:[CPColor greenColor]];
|
||||
|
||||
[self addSubview:tableColumnView];
|
||||
|
||||
[_tableCells addObject:tableColumnCells];
|
||||
|
||||
for (; i < _numberOfRows; ++i)
|
||||
_tableCells[_numberOfColumns][i] = CPTableViewCellPlaceholder;
|
||||
|
||||
++_numberOfColumns;
|
||||
}
|
||||
|
||||
/*
|
||||
Removes a column from the table
|
||||
@param aTableColumn the column to remove
|
||||
*/
|
||||
- (void)removeTableColumn:(CPTableColumn)aTableColumn
|
||||
{
|
||||
var frame = [self frame],
|
||||
width = [aTableColumn width] + _intercellSpacing.width,
|
||||
index = [_tableColumns indexOfObjectIdenticalTo:aTableColumn];
|
||||
|
||||
// Remove the column view and all the cell views from the view hierarchy.
|
||||
[_tableColumnViews[i] removeFromSuperview];
|
||||
|
||||
[_tableCells removeObjectAtIndex:index];
|
||||
[_tableColumns removeObjectAtIndex:index];
|
||||
[_tabelColumnViews removeObjectAtIndex:index];
|
||||
|
||||
// Shift remaining column views to the left.
|
||||
for (; index < _numberOfColumns; ++ index)
|
||||
[_tableColumnViews[index] setFrameOrigin:CPPointMake(CPRectGetMinX([_tableColumnViews[index] frame]) - width, 0.0)]
|
||||
|
||||
// Resize ourself.
|
||||
[self setFrameSize:CPSizeMake(CPRectGetWidth(frame) - width, CPRectGetHeight(frame))];
|
||||
}
|
||||
|
||||
/*
|
||||
Not yet implemented. Changes the position of the column in
|
||||
the table.
|
||||
@param fromIndex the original index of the column
|
||||
@param toIndex the desired index of the column
|
||||
*/
|
||||
- (void)moveColumn:(unsigned)fromIndex toColumn:(unsinged)toIndex
|
||||
{
|
||||
if (fromIndex == toIndex)
|
||||
return;
|
||||
// FIXME: IMPLEMENT
|
||||
}
|
||||
|
||||
/*
|
||||
Returns an array containing the table's CPTableColumns.
|
||||
*/
|
||||
- (CPArray)tableColumns
|
||||
{
|
||||
return _tableColumns;
|
||||
}
|
||||
|
||||
// Getting the dimensions of the table
|
||||
/*
|
||||
Returns the number of columns in the table
|
||||
*/
|
||||
- (int)numberOfColumns
|
||||
{
|
||||
return _numberOfColumns;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the number of rows in the table
|
||||
*/
|
||||
- (int)numberOfRows
|
||||
{
|
||||
return _numberOfRows;
|
||||
}
|
||||
|
||||
/*
|
||||
Adjusts the size of the table and it's header view.
|
||||
*/
|
||||
- (void)tile
|
||||
{
|
||||
var HEIGHT = 10.0;
|
||||
|
||||
[_headerView setFrame:CPRectMake(0.0, 0.0, CPRectGetWidth([self bounds]), HEIGHT)];
|
||||
[_scrollView setFrame:CPRectMake(0.0, HEIGHT, CPRectGetWidth([self bounds]), CPRectGetHeight([self bounds]) - HEIGHT)];
|
||||
}
|
||||
|
||||
/*
|
||||
Sets the object that is used to obtain the table data. The data source must implement the following
|
||||
methods:
|
||||
<pre>
|
||||
- (int)numberOfRowsInTableView:(CPTableView)aTableView
|
||||
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex
|
||||
</pre>
|
||||
@param aDataSource the object with the table data
|
||||
@throws CPInternalInconsistencyException if <code>aDataSource</code> doesn't implement all the required methods
|
||||
*/
|
||||
- (void)setDataSource:(id)aDataSource
|
||||
{
|
||||
if (![aDataSource respondsToSelector:@selector(numberOfRowsInTableView:)])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Data source doesn't support 'numberOfRowsInTableView:'"];
|
||||
if (![aDataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)])
|
||||
[CPException raise:CPInternalInconsistencyException reason:"Data source doesn't support 'tableView:objectValueForTableColumn:row:'"];
|
||||
|
||||
_dataSource = aDataSource;
|
||||
|
||||
[self reloadData];
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the object that has access to the table data
|
||||
*/
|
||||
- (id)dataSource
|
||||
{
|
||||
return _dataSource;
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
var oldColumnHeight = [self columnHeight];
|
||||
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
var columnHeight = [self columnHeight];
|
||||
|
||||
if (columnHeight != oldColumnHeight)
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
for (; i < _numberOfColumns; ++i)
|
||||
[_tableColumnViews[i] setFrameSize:CPSizeMake([_tableColumns[i] width], columnHeight)];
|
||||
}
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*
|
||||
Tells the table view that the number of rows in the table
|
||||
has changed.
|
||||
*/
|
||||
- (void)noteNumberOfRowsChanged
|
||||
{
|
||||
var numberOfRows = [_dataSource numberOfRowsInTableView:self];
|
||||
|
||||
if (_numberOfRows != numberOfRows)
|
||||
{
|
||||
_numberOfRows = numberOfRows;
|
||||
[self sizeToFit];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the rectangle bounding the specified row.
|
||||
@param aRowIndex the row to obtain a rectangle for
|
||||
@return the bounding rectangle
|
||||
*/
|
||||
- (CGRect)rectOfRow:(int)aRowIndex
|
||||
{
|
||||
return CPRectMake(0.0, aRowIndex * (_rowHeight + _intercellSpacing.height), CPRectGetWidth([self bounds]), _rowHeight);
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the rectangle bounding the specified column
|
||||
@param aColumnIndex the column to obtain a rectangle for
|
||||
@return the bounding column
|
||||
*/
|
||||
- (CGRect)rectOfColumn:(int)aColumnIndex
|
||||
{
|
||||
return CPRectCreateCopy([_tableColumnViews[aColumnIndex] frame]);
|
||||
}
|
||||
|
||||
/*
|
||||
Adjusts column widths to make them all visible at once. Same as <code>tile</code>.
|
||||
*/
|
||||
- (void)sizeToFit
|
||||
{
|
||||
[self tile];
|
||||
}
|
||||
|
||||
/*
|
||||
Reloads the data from the <code>dataSource</code>. This is an
|
||||
expensive method, so use it lightly.
|
||||
*/
|
||||
- (void)reloadData
|
||||
{
|
||||
_numberOfRows = [_dataSource numberOfRowsInTableView:self];
|
||||
|
||||
[self loadTableCellsInRect:[self bounds]];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,780 @@
|
||||
/*
|
||||
* CPTextField.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPControl.j"
|
||||
@import "CPStringDrawing.j"
|
||||
|
||||
#include "Platform/Platform.h"
|
||||
#include "Platform/DOM/CPDOMDisplayServer.h"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPLineBreakMode
|
||||
*/
|
||||
CPLineBreakByWordWrapping = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPLineBreakMode
|
||||
*/
|
||||
CPLineBreakByCharWrapping = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPLineBreakMode
|
||||
*/
|
||||
CPLineBreakByClipping = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPLineBreakMode
|
||||
*/
|
||||
CPLineBreakByTruncatingHead = 3;
|
||||
/*
|
||||
@global
|
||||
@group CPLineBreakMode
|
||||
*/
|
||||
CPLineBreakByTruncatingTail = 4;
|
||||
/*
|
||||
@global
|
||||
@group CPLineBreakMode
|
||||
*/
|
||||
CPLineBreakByTruncatingMiddle = 5;
|
||||
|
||||
/*
|
||||
A textfield bezel with a squared corners.
|
||||
@global
|
||||
@group CPTextFieldBezelStyle
|
||||
*/
|
||||
CPTextFieldSquareBezel = 0;
|
||||
/*
|
||||
A textfield bezel with rounded corners.
|
||||
@global
|
||||
@group CPTextFieldBezelStyle
|
||||
*/
|
||||
CPTextFieldRoundedBezel = 1;
|
||||
|
||||
var TOP_PADDING = 4.0,
|
||||
BOTTOM_PADDING = 3.0;
|
||||
HORIZONTAL_PADDING = 3.0;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
var CPTextFieldDOMInputElement = nil;
|
||||
#endif
|
||||
|
||||
var _CPTextFieldSquareBezelColor = nil,
|
||||
_CPTextFieldRoundedBezelColor = nil;
|
||||
|
||||
@implementation CPString (CPTextFieldAdditions)
|
||||
|
||||
/*!
|
||||
Returns the string (<code>self</code>).
|
||||
*/
|
||||
- (CPString)string
|
||||
{
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
This control displays editable text in a Cappuccino application.
|
||||
*/
|
||||
@implementation CPTextField : CPControl
|
||||
{
|
||||
BOOL _isEditable;
|
||||
BOOL _isSelectable;
|
||||
|
||||
BOOL _isBordered;
|
||||
BOOL _isBezeled;
|
||||
CPTextFieldBezelStyle _bezelStyle;
|
||||
BOOL _drawsBackground;
|
||||
|
||||
CPLineBreakMode _lineBreakMode;
|
||||
CPColor _textFieldBackgroundColor;
|
||||
|
||||
id _placeholderString;
|
||||
|
||||
id _delegate;
|
||||
|
||||
CPString _textDidChangeValue;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
DOMElement _DOMTextElement;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
#if PLATFORM(DOM)
|
||||
+ (DOMElement)_inputElement
|
||||
{
|
||||
if (!CPTextFieldDOMInputElement)
|
||||
{
|
||||
CPTextFieldDOMInputElement = document.createElement("input");
|
||||
CPTextFieldDOMInputElement.style.position = "absolute";
|
||||
CPTextFieldDOMInputElement.style.top = "0px";
|
||||
CPTextFieldDOMInputElement.style.left = "0px";
|
||||
CPTextFieldDOMInputElement.style.width = "100%"
|
||||
CPTextFieldDOMInputElement.style.height = "100%";
|
||||
CPTextFieldDOMInputElement.style.border = "0px";
|
||||
CPTextFieldDOMInputElement.style.padding = "0px";
|
||||
CPTextFieldDOMInputElement.style.whiteSpace = "pre";
|
||||
CPTextFieldDOMInputElement.style.background = "transparent";
|
||||
CPTextFieldDOMInputElement.style.outline = "none";
|
||||
CPTextFieldDOMInputElement.style.paddingLeft = HORIZONTAL_PADDING - 1.0 + "px";
|
||||
CPTextFieldDOMInputElement.style.paddingTop = TOP_PADDING - 2.0 + "px";
|
||||
}
|
||||
|
||||
return CPTextFieldDOMInputElement;
|
||||
}
|
||||
#endif
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_value = "";
|
||||
_placeholderString = "";
|
||||
|
||||
_sendActionOn = CPKeyUpMask | CPKeyDownMask;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
_DOMTextElement = document.createElement("div");
|
||||
_DOMTextElement.style.position = "absolute";
|
||||
_DOMTextElement.style.top = TOP_PADDING + "px";
|
||||
_DOMTextElement.style.left = HORIZONTAL_PADDING + "px";
|
||||
_DOMTextElement.style.width = MAX(0.0, CGRectGetWidth(aFrame) - 2.0 * HORIZONTAL_PADDING) + "px";
|
||||
_DOMTextElement.style.height = MAX(0.0, CGRectGetHeight(aFrame) - TOP_PADDING - BOTTOM_PADDING) + "px";
|
||||
_DOMTextElement.style.whiteSpace = "pre";
|
||||
_DOMTextElement.style.cursor = "default";
|
||||
_DOMTextElement.style.zIndex = 100;
|
||||
|
||||
_DOMElement.appendChild(_DOMTextElement);
|
||||
#endif
|
||||
[self setAlignment:CPLeftTextAlignment];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
var center = [CPNotificationCenter defaultCenter];
|
||||
|
||||
//unsubscribe the existing delegate if it exists
|
||||
if (_delegate)
|
||||
{
|
||||
[center removeObserver:_delegate name:CPControlTextDidBeginEditingNotification object:self];
|
||||
[center removeObserver:_delegate name:CPControlTextDidChangeNotification object:self];
|
||||
[center removeObserver:_delegate name:CPControlTextDidEndEditingNotification object:self];
|
||||
}
|
||||
|
||||
_delegate = aDelegate;
|
||||
|
||||
if ([_delegate respondsToSelector:@selector(controlTextDidBeginEditing:)])
|
||||
[center addObserver:_delegate selector:@selector(controlTextDidBeginEditing:) name:CPControlTextDidBeginEditingNotification object:self];
|
||||
if ([_delegate respondsToSelector:@selector(controlTextDidChange:)])
|
||||
[center addObserver:_delegate selector:@selector(controlTextDidChange:) name:CPControlTextDidChangeNotification object:self];
|
||||
if ([_delegate respondsToSelector:@selector(controlTextDidEndEditing:)])
|
||||
[center addObserver:_delegate selector:@selector(controlTextDidEndEditing:) name:CPControlTextDidEndEditingNotification object:self];
|
||||
|
||||
}
|
||||
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
// Setting the Bezel Style
|
||||
/*!
|
||||
Sets whether the textfield will have a bezeled border.
|
||||
@param shouldBeBezeled <code>YES</code> means the textfield will draw a bezeled border
|
||||
*/
|
||||
- (void)setBezeled:(BOOL)shouldBeBezeled
|
||||
{
|
||||
if (_isBezeled == shouldBeBezeled)
|
||||
return;
|
||||
|
||||
_isBezeled = shouldBeBezeled;
|
||||
|
||||
[self _updateBackground];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the textfield draws a bezeled border.
|
||||
*/
|
||||
- (BOOL)isBezeled
|
||||
{
|
||||
return _isBezeled;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the textfield's bezel style.
|
||||
@param aBezelStyle the constant for the desired bezel style
|
||||
*/
|
||||
- (void)setBezelStyle:(CPTextFieldBezelStyle)aBezelStyle
|
||||
{
|
||||
if (_bezelStyle == aBezelStyle)
|
||||
return;
|
||||
|
||||
_bezelStyle = aBezelStyle;
|
||||
|
||||
[self _updateBackground];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the textfield's bezel style.
|
||||
*/
|
||||
- (CPTextFieldBezelStyle)bezelStyle
|
||||
{
|
||||
return _bezelStyle;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the textfield will have a border drawn.
|
||||
@param shouldBeBordered <code>YES</code> makes the textfield draw a border
|
||||
*/
|
||||
- (void)setBordered:(BOOL)shouldBeBordered
|
||||
{
|
||||
if (_isBordered == shouldBeBordered)
|
||||
return;
|
||||
|
||||
_isBordered = shouldBeBordered;
|
||||
|
||||
[self _updateBackground];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the textfield has a border.
|
||||
*/
|
||||
- (BOOL)isBordered
|
||||
{
|
||||
return _isBordered;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the textfield will have a background drawn.
|
||||
@param shouldDrawBackground <code>YES</code> makes the textfield draw a background
|
||||
*/
|
||||
- (void)setDrawsBackground:(BOOL)shouldDrawBackground
|
||||
{
|
||||
if (_drawsBackground == shouldDrawBackground)
|
||||
return;
|
||||
|
||||
_drawsBackground = shouldDrawBackground;
|
||||
|
||||
[self _updateBackground];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the textfield draws a background.
|
||||
*/
|
||||
- (BOOL)drawsBackground
|
||||
{
|
||||
return _drawsBackground;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the background color, which is shown for non-bezeled text fields with drawsBackground set to YES
|
||||
@param aColor The background color
|
||||
*/
|
||||
- (void)setTextFieldBackgroundColor:(BOOL)aColor
|
||||
{
|
||||
if (_textFieldBackgroundColor == aColor)
|
||||
return;
|
||||
|
||||
_textFieldBackgroundColor = aColor;
|
||||
|
||||
[self _updateBackground];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the background color.
|
||||
*/
|
||||
- (BOOL)textFieldBackgroundColor
|
||||
{
|
||||
return _textFieldBackgroundColor;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_updateBackground
|
||||
{
|
||||
if (_isBezeled)
|
||||
{
|
||||
if (_bezelStyle == CPTextFieldSquareBezel)
|
||||
{
|
||||
if (!_CPTextFieldSquareBezelColor)
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[CPTextField class]];
|
||||
|
||||
_CPTextFieldSquareBezelColor = [CPColor colorWithPatternImage:[[CPNinePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare0.png"] size:CGSizeMake(2.0, 3.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare1.png"] size:CGSizeMake(1.0, 3.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare2.png"] size:CGSizeMake(2.0, 3.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare3.png"] size:CGSizeMake(2.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare4.png"] size:CGSizeMake(1.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare5.png"] size:CGSizeMake(2.0, 1.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare6.png"] size:CGSizeMake(2.0, 2.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare7.png"] size:CGSizeMake(1.0, 2.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelSquare8.png"] size:CGSizeMake(2.0, 2.0)]
|
||||
]]];
|
||||
}
|
||||
[self setBackgroundColor:_CPTextFieldSquareBezelColor];
|
||||
}
|
||||
else if (_bezelStyle == CPTextFieldRoundedBezel)
|
||||
{
|
||||
if (!_CPTextFieldRoundedBezelColor)
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[CPTextField class]];
|
||||
|
||||
_CPTextFieldRoundedBezelColor = [CPColor colorWithPatternImage:[[CPThreePartImage alloc] initWithImageSlices:
|
||||
[
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelRounded0.png"] size:CGSizeMake(12.0, 22.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelRounded1.png"] size:CGSizeMake(16.0, 22.0)],
|
||||
[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:"CPTextField/CPTextFieldBezelRounded2.png"] size:CGSizeMake(12.0, 22.0)]
|
||||
] isVertical:NO]];
|
||||
}
|
||||
[self setBackgroundColor:_CPTextFieldRoundedBezelColor];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_drawsBackground)
|
||||
[self setBackgroundColor:_textFieldBackgroundColor];
|
||||
else
|
||||
[self setBackgroundColor:nil];
|
||||
|
||||
// FIXME: do something for bordered textfields
|
||||
//if (_isBordered)
|
||||
}
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
var string = [self stringValue];
|
||||
|
||||
[self setStringValue:""];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
var element = [[self class] _inputElement];
|
||||
|
||||
element.value = string;
|
||||
element.style.color = _DOMElement.style.color;
|
||||
element.style.font = _DOMElement.style.font;
|
||||
element.style.zIndex = 1000;
|
||||
element.style.width = CGRectGetWidth([self bounds]) - 3.0 + "px";
|
||||
element.style.marginTop = "0px";
|
||||
|
||||
//element.style.left = _DOMTextElement.style.left;
|
||||
//element.style.top = _DOMTextElement.style.top;
|
||||
|
||||
_DOMElement.appendChild(element);
|
||||
window.setTimeout(function() { element.focus(); }, 0.0);
|
||||
|
||||
element.onblur = function ()
|
||||
{
|
||||
[self setObjectValue:element.value];
|
||||
[self sendAction:[self action] to:[self target]];
|
||||
[[self window] makeFirstResponder:nil];
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
};
|
||||
|
||||
//element.onblur = function() { objj_debug_print_backtrace(); }
|
||||
//element.select();
|
||||
|
||||
element.onkeydown = function(aDOMEvent)
|
||||
{
|
||||
//all key presses might trigger the delegate method controlTextDidChange:
|
||||
//record the current string value before we allow this keydown to propagate
|
||||
_textDidChangeValue = [self stringValue];
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
return true;
|
||||
}
|
||||
|
||||
element.onkeypress = function(aDOMEvent)
|
||||
{
|
||||
aDOMEvent = aDOMEvent || window.event;
|
||||
|
||||
if (aDOMEvent.keyCode == 13)
|
||||
{
|
||||
if (aDOMEvent.preventDefault)
|
||||
aDOMEvent.preventDefault();
|
||||
if (aDOMEvent.stopPropagation)
|
||||
aDOMEvent.stopPropagation();
|
||||
aDOMEvent.cancelBubble = true;
|
||||
|
||||
element.blur();
|
||||
}
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
};
|
||||
|
||||
//inspect keyup to detect changes in order to trigger controlTextDidChange: delegate method
|
||||
element.onkeyup = function(aDOMEvent)
|
||||
{
|
||||
//check if we should fire a notification for CPControlTextDidChange
|
||||
if ([self stringValue] != _textDidChangeValue)
|
||||
{
|
||||
_textDidChangeValue = [self stringValue];
|
||||
|
||||
//call to CPControls methods for posting the notification
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
}
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
};
|
||||
|
||||
// If current value is the placeholder value, remove it to allow user to update.
|
||||
if ([string lowercaseString] == [[self placeholderString] lowercaseString])
|
||||
[self setStringValue:""];
|
||||
|
||||
//post CPControlTextDidBeginEditingNotification
|
||||
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
||||
|
||||
[[CPDOMWindowBridge sharedDOMWindowBridge] _propagateCurrentDOMEvent:YES];
|
||||
#endif
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var element = [[self class] _inputElement];
|
||||
|
||||
//nil out dom handlers
|
||||
element.onkeyup = nil;
|
||||
element.onkeydown = nil;
|
||||
element.onkeypress = nil;
|
||||
|
||||
_DOMElement.removeChild(element);
|
||||
[self setStringValue:element.value]; // redundant?
|
||||
|
||||
// If textfield has no value, then display the placeholderValue
|
||||
if (!_value)
|
||||
[self setStringValue:[self placeholderString]];
|
||||
|
||||
#endif
|
||||
//post CPControlTextDidEndEditingNotification
|
||||
[self textDidEndEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
if (_isEditable && [[self window] firstResponder] == self)
|
||||
return;
|
||||
|
||||
[super mouseUp:anEvent];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether or not the receiver text field can be edited
|
||||
*/
|
||||
- (void)setEditable:(BOOL)shouldBeEditable
|
||||
{
|
||||
_isEditable = shouldBeEditable;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the textfield is currently editable by the user.
|
||||
*/
|
||||
- (BOOL)isEditable
|
||||
{
|
||||
return _isEditable;
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(CGSize)aSize
|
||||
{
|
||||
[super setFrameSize:aSize];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
CPDOMDisplayServerSetStyleSize(_DOMTextElement, _frame.size.width - 2.0 * HORIZONTAL_PADDING, _frame.size.height - TOP_PADDING - BOTTOM_PADDING);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the field's text is selectable by the user.
|
||||
*/
|
||||
- (BOOL)isSelectable
|
||||
{
|
||||
return _isSelectable;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the field's text is selectable by the user.
|
||||
@param aFlag <code>YES</code> makes the text selectable
|
||||
*/
|
||||
- (void)setSelectable:(BOOL)aFlag
|
||||
{
|
||||
_isSelectable = aFlag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the alignment of the text in the field.
|
||||
@param anAlignment
|
||||
*/
|
||||
- (void)setAlignment:(CPTextAlignment)anAlignment
|
||||
{
|
||||
if ([self alignment] == anAlignment)
|
||||
return;
|
||||
|
||||
[super setAlignment:anAlignment];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
switch ([self alignment])
|
||||
{
|
||||
case CPLeftTextAlignment: _DOMTextElement.style.textAlign = "left";
|
||||
break;
|
||||
case CPRightTextAlignment: _DOMTextElement.style.textAlign = "right";
|
||||
break;
|
||||
case CPCenterTextAlignment: _DOMTextElement.style.textAlign = "center";
|
||||
break;
|
||||
case CPJustifiedTextAlignment: _DOMTextElement.style.textAlign = "justify";
|
||||
break;
|
||||
case CPNaturalTextAlignment: _DOMTextElement.style.textAlign = "";
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the way line breaks occur in the text field.
|
||||
@param aLineBreakMode the line break style
|
||||
*/
|
||||
- (void)setLineBreakMode:(CPLineBreakMode)aLineBreakMode
|
||||
{
|
||||
_lineBreakMode = aLineBreakMode;
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
switch (aLineBreakMode)
|
||||
{
|
||||
case CPLineBreakByTruncatingTail: _DOMTextElement.style.textOverflow = "ellipsis";
|
||||
_DOMTextElement.style.whiteSpace = "nowrap";
|
||||
_DOMTextElement.style.overflow = "hidden";
|
||||
|
||||
if (document.attachEvent)
|
||||
_DOMTextElement.style.wordWrap = "normal";
|
||||
|
||||
break;
|
||||
|
||||
case CPLineBreakByWordWrapping: if (document.attachEvent)
|
||||
{
|
||||
_DOMTextElement.style.whiteSpace = "pre";
|
||||
_DOMTextElement.style.wordWrap = "break-word";
|
||||
}
|
||||
else
|
||||
{
|
||||
_DOMTextElement.style.whiteSpace = "-o-pre-wrap";
|
||||
_DOMTextElement.style.whiteSpace = "-pre-wrap";
|
||||
_DOMTextElement.style.whiteSpace = "-moz-pre-wrap";
|
||||
_DOMTextElement.style.whiteSpace = "pre-wrap";
|
||||
}
|
||||
|
||||
_DOMTextElement.style.overflow = "hidden";
|
||||
_DOMTextElement.style.textOverflow = "clip";
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the string the text field.
|
||||
*/
|
||||
- (id)objectValue
|
||||
{
|
||||
// All of this needs to be better.
|
||||
#if PLATFORM(DOM)
|
||||
if ([[self window] firstResponder] == self)
|
||||
return [[self class] _inputElement].value;
|
||||
#endif
|
||||
//if the content is the same as the placeholder value, return "" instead
|
||||
if ([super objectValue] == [self placeholderString])
|
||||
return "";
|
||||
|
||||
return [super objectValue];
|
||||
}
|
||||
|
||||
/*
|
||||
@ignore
|
||||
*/
|
||||
- (void)setObjectValue:(id)aValue
|
||||
{
|
||||
[super setObjectValue:aValue];
|
||||
|
||||
#if PLATFORM(DOM)
|
||||
var displayString = "";
|
||||
|
||||
if (aValue && [aValue respondsToSelector:@selector(string)])
|
||||
displayString = [aValue string];
|
||||
else if (aValue)
|
||||
displayString += aValue;
|
||||
|
||||
if ([[self window] firstResponder] == self)
|
||||
[[self class] _inputElement].value = displayString;
|
||||
|
||||
if (CPFeatureIsCompatible(CPJavascriptInnerTextFeature))
|
||||
_DOMTextElement.innerText = displayString;
|
||||
else if (CPFeatureIsCompatible(CPJavascriptTextContentFeature))
|
||||
_DOMTextElement.textContent = displayString;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the receiver's placeholder string
|
||||
*/
|
||||
- (CPString)placeholderString
|
||||
{
|
||||
return _placeholderString;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets a placeholder string for the receiver. The placeholder is displayed until editing begins,
|
||||
and after editing ends, if the text field has an empty string value
|
||||
*/
|
||||
-(void)setPlaceholderString:(CPString)aStringValue
|
||||
{
|
||||
_placeholderString = aStringValue;
|
||||
|
||||
//if there is no set value, automatically display the placeholder
|
||||
if (!_value)
|
||||
[self setStringValue:aStringValue];
|
||||
}
|
||||
|
||||
/*!
|
||||
Adjusts the text field's size in the application.
|
||||
*/
|
||||
- (void)sizeToFit
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var size = [(_value || " ") sizeWithFont:[self font]];
|
||||
|
||||
[self setFrameSize:CGSizeMake(size.width + 2 * HORIZONTAL_PADDING, size.height + TOP_PADDING + BOTTOM_PADDING)];
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
Select all the text in the CPTextField.
|
||||
*/
|
||||
- (void)selectText:(id)sender
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var element = [[self class] _inputElement];
|
||||
|
||||
if (element.parentNode == _DOMElement && ([self isEditable] || [self isSelectable]))
|
||||
element.select();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
|
||||
var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
|
||||
CPTextFieldIsSelectableKey = "CPTextFieldIsSelectableKey",
|
||||
CPTextFieldIsBorderedKey = "CPTextFieldIsBorderedKey",
|
||||
CPTextFieldIsBezeledKey = "CPTextFieldIsBezeledKey",
|
||||
CPTextFieldBezelStyleKey = "CPTextFieldBezelStyleKey",
|
||||
CPTextFieldDrawsBackgroundKey = "CPTextFieldDrawsBackgroundKey",
|
||||
CPTextFieldLineBreakModeKey = "CPTextFieldLineBreakModeKey",
|
||||
CPTextFieldBackgroundColorKey = "CPTextFieldBackgroundColorKey",
|
||||
CPTextFieldPlaceholderStringKey = "CPTextFieldPlaceholderStringKey";
|
||||
|
||||
@implementation CPTextField (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the textfield with data from a coder.
|
||||
@param aCoder the coder from which to read the textfield data
|
||||
@return the initialized textfield
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
_DOMTextElement = document.createElement("div");
|
||||
#endif
|
||||
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
#if PLATFORM(DOM)
|
||||
var bounds = [self bounds];
|
||||
_DOMTextElement.style.position = "absolute";
|
||||
_DOMTextElement.style.top = TOP_PADDING + "px";
|
||||
_DOMTextElement.style.left = HORIZONTAL_PADDING + "px";
|
||||
_DOMTextElement.style.width = MAX(0.0, CGRectGetWidth(bounds) - 2.0 * HORIZONTAL_PADDING) + "px";
|
||||
_DOMTextElement.style.height = MAX(0.0, CGRectGetHeight(bounds) - TOP_PADDING - BOTTOM_PADDING) + "px";
|
||||
_DOMTextElement.style.whiteSpace = "pre";
|
||||
_DOMTextElement.style.cursor = "default";
|
||||
|
||||
_DOMElement.appendChild(_DOMTextElement);
|
||||
#endif
|
||||
|
||||
[self setEditable:[aCoder decodeBoolForKey:CPTextFieldIsEditableKey]];
|
||||
[self setSelectable:[aCoder decodeBoolForKey:CPTextFieldIsSelectableKey]];
|
||||
|
||||
[self setBordered:[aCoder decodeBoolForKey:CPTextFieldIsBorderedKey]];
|
||||
[self setBezeled:[aCoder decodeBoolForKey:CPTextFieldIsBezeledKey]];
|
||||
[self setBezelStyle:[aCoder decodeIntForKey:CPTextFieldBezelStyleKey]];
|
||||
[self setDrawsBackground:[aCoder decodeBoolForKey:CPTextFieldDrawsBackgroundKey]];
|
||||
|
||||
[self setLineBreakMode:[aCoder decodeIntForKey:CPTextFieldLineBreakModeKey]];
|
||||
[self setTextFieldBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
|
||||
|
||||
[self setPlaceholderString:[aCoder decodeObjectForKey:CPTextFieldPlaceholderStringKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Encodes the data of this textfield into the provided coder.
|
||||
@param aCoder the coder into which the data will be written
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeBool:_isEditable forKey:CPTextFieldIsEditableKey];
|
||||
[aCoder encodeBool:_isSelectable forKey:CPTextFieldIsSelectableKey];
|
||||
|
||||
[aCoder encodeBool:_isBordered forKey:CPTextFieldIsBorderedKey];
|
||||
[aCoder encodeBool:_isBezeled forKey:CPTextFieldIsBezeledKey];
|
||||
[aCoder encodeInt:_bezelStyle forKey:CPTextFieldBezelStyleKey];
|
||||
[aCoder encodeBool:_drawsBackground forKey:CPTextFieldDrawsBackgroundKey];
|
||||
|
||||
[aCoder encodeInt:_lineBreakMode forKey:CPTextFieldLineBreakModeKey];
|
||||
[aCoder encodeObject:_textFieldBackgroundColor forKey:CPTextFieldBackgroundColorKey];
|
||||
|
||||
[aCoder encodeObject:_placeholderString forKey:CPTextFieldPlaceholderStringKey];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,796 @@
|
||||
/*
|
||||
* CPToolbar.j
|
||||
* AppKit
|
||||
*
|
||||
* Portions based on NSToolbar.m (11/10/2008) in Cocotron (http://www.cocotron.org/)
|
||||
* Copyright (c) 2006-2007 Christopher J. W. Lloyd
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
@import "CPPopUpButton.j"
|
||||
@import "CPToolbarItem.j"
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CPToolbarDisplayMode
|
||||
*/
|
||||
CPToolbarDisplayModeDefault = 0;
|
||||
/*
|
||||
@global
|
||||
@group CPToolbarDisplayMode
|
||||
*/
|
||||
CPToolbarDisplayModeIconAndLabel = 1;
|
||||
/*
|
||||
@global
|
||||
@group CPToolbarDisplayMode
|
||||
*/
|
||||
CPToolbarDisplayModeIconOnly = 2;
|
||||
/*
|
||||
@global
|
||||
@group CPToolbarDisplayMode
|
||||
*/
|
||||
CPToolbarDisplayModeLabelOnly = 3;
|
||||
|
||||
var CPToolbarsByIdentifier = nil;
|
||||
var CPToolbarConfigurationsByIdentifier = nil;
|
||||
|
||||
/*!
|
||||
@class CPToolbar
|
||||
|
||||
A CPToolbar is displayed at the top of a window with multiple
|
||||
buttons (tools) that offer the user quick access to features.
|
||||
|
||||
@par Delegate Methods
|
||||
|
||||
@delegate -(CPArray)toolbarDefaultItemIdentifiers:(CPToolbar)toolbar;
|
||||
Called to obtain the toolbar's default item identifiers. Required.
|
||||
@param toolbar the toolbar to obtain identifiers for
|
||||
@return an array of default item identifiers in the order on the toolbar
|
||||
|
||||
@delegate -(CPArray)toolbarAllowedItemIdentifiers:(CPToolbar)toolbar;
|
||||
Called to obtain the toolbar's default item identifiers. Required.
|
||||
@param toolbar the toolbar to obtain identifiers for
|
||||
@return an array of default item identifiers in the order on the toolbar
|
||||
|
||||
@delegate - (CPToolbarItem)toolbar:(CPToolbar)toolbar itemForItemIdentifier:(CPString)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag;
|
||||
Called to obtain a toolbar item. Required.
|
||||
@param toolbar the toolbar the item belongs to
|
||||
@param itemIdentifier the identifier of the toolbar item
|
||||
@param flag <code>YES</code> means the item will be placed in the toolbar. <code>NO</code> means the item will be displayed for
|
||||
some other purpose (non-functional)
|
||||
@return the toolbar item or <code>nil</code> if no such item belongs in the toolbar
|
||||
*/
|
||||
@implementation CPToolbar : CPObject
|
||||
{
|
||||
CPString _identifier;
|
||||
CPToolbarDisplayMode _displayMode;
|
||||
BOOL _showsBaselineSeparator;
|
||||
BOOL _allowsUserCustomization;
|
||||
BOOL _isVisible;
|
||||
|
||||
id _delegate;
|
||||
|
||||
CPArray _itemIdentifiers;
|
||||
|
||||
CPDictionary _identifiedItems;
|
||||
CPArray _defaultItems;
|
||||
CPArray _allowedItems;
|
||||
CPArray _selectableItems;
|
||||
|
||||
CPArray _items;
|
||||
CPArray _itemsSortedByVisibilityPriority;
|
||||
|
||||
CPView _toolbarView;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [CPToolbar class])
|
||||
return;
|
||||
|
||||
CPToolbarsByIdentifier = [CPDictionary dictionary];
|
||||
CPToolbarConfigurationsByIdentifier = [CPDictionary dictionary];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
+ (void)_addToolbar:(CPToolbar)toolbar forIdentifier:(CPString)identifier
|
||||
{
|
||||
var toolbarsSharingIdentifier = [CPToolbarsByIdentifier objectForKey:identifier];
|
||||
|
||||
if (!toolbarsSharingIdentifier)
|
||||
{
|
||||
toolbarsSharingIdentifier = []
|
||||
[CPToolbarsByIdentifier setObject:toolbarsSharingIdentifier forKey:identifier];
|
||||
}
|
||||
|
||||
[toolbarsSharingIdentifier addObject:toolbar];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the toolbar with the specified identifier.
|
||||
@param anIdentifier the identifier for the toolbar
|
||||
@return the initialized toolbar
|
||||
*/
|
||||
- (id)initWithIdentifier:(CPString)anIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_items = [];
|
||||
|
||||
_identifier = anIdentifier;
|
||||
_isVisible = YES;
|
||||
|
||||
[CPToolbar _addToolbar:self forIdentifier:_identifier];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
Sets the toolbar's display mode. NOT YET IMPLEMENTED.
|
||||
*/
|
||||
- (void)setDisplayMode:(CPToolbarDisplayMode)aDisplayMode
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the toolbar's identifier
|
||||
*/
|
||||
- (CPString)identifier
|
||||
{
|
||||
return _identifier;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the toolbar's delegate
|
||||
*/
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the toolbar is currently visible
|
||||
*/
|
||||
- (BOOL)isVisible
|
||||
{
|
||||
return _isVisible;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the toolbar should be visible.
|
||||
@param aFlag <code>YES</code> makes the toolbar visible
|
||||
*/
|
||||
- (void)setVisible:(BOOL)aFlag
|
||||
{
|
||||
if (_isVisible == aFlag)
|
||||
return;
|
||||
|
||||
_isVisible = aFlag;
|
||||
|
||||
[_window _setToolbarVisible:_isVisible];
|
||||
[self _reloadToolbarItems];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the delegate for the toolbar.
|
||||
@param aDelegate the new toolbar delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
if (_delegate == aDelegate)
|
||||
return;
|
||||
|
||||
_delegate = aDelegate;
|
||||
|
||||
[self _reloadToolbarItems];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_loadConfiguration
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (CPView)_toolbarView
|
||||
{
|
||||
if (!_toolbarView)
|
||||
{
|
||||
_toolbarView = [[_CPToolbarView alloc] initWithFrame:CPRectMake(0.0, 0.0, 1200.0, 59.0)];
|
||||
[_toolbarView setAutoresizingMask:CPViewWidthSizable];
|
||||
|
||||
[_toolbarView setToolbar:self];
|
||||
}
|
||||
|
||||
return _toolbarView;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_reloadToolbarItems
|
||||
{
|
||||
if (![_toolbarView superview] || !_delegate)
|
||||
return;
|
||||
|
||||
var count = [_itemIdentifiers count];
|
||||
|
||||
if (!count)
|
||||
{
|
||||
_itemIdentifiers = [[_delegate toolbarDefaultItemIdentifiers:self] mutableCopy];
|
||||
count = [_itemIdentifiers count];
|
||||
}
|
||||
|
||||
[[_toolbarView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
|
||||
_items = [];
|
||||
|
||||
var index = 0;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var identifier = _itemIdentifiers[index],
|
||||
item = [CPToolbarItem _standardItemWithItemIdentifier:identifier];
|
||||
|
||||
if (!item)
|
||||
item = [[_delegate toolbar:self itemForItemIdentifier:identifier willBeInsertedIntoToolbar:YES] copy];
|
||||
|
||||
if (item == nil)
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:sprintf(@"_delegate %s returned nil toolbar item returned for identifier %s", _delegate, identifier)];
|
||||
|
||||
[_items addObject:item];
|
||||
}
|
||||
|
||||
// _items = [[self _defaultToolbarItems] mutableCopy];
|
||||
|
||||
// Store items sorted by priority. We want items to be removed first at the end of the array,
|
||||
// items to be removed last at the front.
|
||||
|
||||
_itemsSortedByVisibilityPriority = [_items sortedArrayUsingFunction:_CPToolbarItemVisibilityPriorityCompare context:NULL];
|
||||
|
||||
[_toolbarView reloadToolbarItems];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns all the items in this toolbar.
|
||||
*/
|
||||
- (CPArray)items
|
||||
{
|
||||
return _items;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns all the visible items in this toolbar
|
||||
*/
|
||||
- (CPArray)visibleItems
|
||||
{
|
||||
return [_toolbarView visibleItems];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the toolbar items sorted by their <code>visibilityPriority</code>(ies).
|
||||
*/
|
||||
- (CPArray)itemsSortedByVisibilityPriority
|
||||
{
|
||||
return _itemsSortedByVisibilityPriority;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_itemForItemIdentifier:(CPString)identifier willBeInsertedIntoToolbar:(BOOL)toolbar
|
||||
{
|
||||
var item = [_identifiedItems objectForKey:identifier];
|
||||
if (!item)
|
||||
{
|
||||
item = [CPToolbarItem _standardItemWithItemIdentifier:identifier];
|
||||
if (_delegate && !item)
|
||||
{
|
||||
item = [[_delegate toolbar:self itemForItemIdentifier:identifier willBeInsertedIntoToolbar:toolbar] copy];
|
||||
if (!item)
|
||||
[CPException raise:CPInvalidArgumentException
|
||||
reason:sprintf(@"_delegate %s returned nil toolbar item returned for identifier %s", _delegate, identifier)];
|
||||
}
|
||||
|
||||
[_identifiedItems setObject:item forKey:identifier];
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_itemsWithIdentifiers:(CPArray)identifiers
|
||||
{
|
||||
var items = [];
|
||||
for (var i = 0; i < identifiers.length; i++)
|
||||
[items addObject:[self _itemForItemIdentifier:identifiers[i] willBeInsertedIntoToolbar:NO]];
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
-(id)_defaultToolbarItems
|
||||
{
|
||||
if (!_defaultItems)
|
||||
if ([_delegate respondsToSelector:@selector(toolbarDefaultItemIdentifiers:)])
|
||||
_defaultItems = [self _itemsWithIdentifiers:[_delegate toolbarDefaultItemIdentifiers:self]];
|
||||
|
||||
return _defaultItems;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPToolbarIdentifierKey = "CPToolbarIdentifierKey",
|
||||
CPToolbarDisplayModeKey = "CPToolbarDisplayModeKey",
|
||||
CPToolbarShowsBaselineSeparatorKey = "CPToolbarShowsBaselineSeparatorKey",
|
||||
CPToolbarAllowsUserCustomizationKey = "CPToolbarAllowsUserCustomizationKey",
|
||||
CPToolbarIsVisibleKey = "CPToolbarIsVisibleKey",
|
||||
CPToolbarDelegateKey = "CPToolbarDelegateKey",
|
||||
CPToolbarIdentifiedItemsKey = "CPToolbarIdentifiedItemsKey",
|
||||
CPToolbarDefaultItemsKey = "CPToolbarDefaultItemsKey",
|
||||
CPToolbarAllowedItemsKey = "CPToolbarAllowedItemsKey",
|
||||
CPToolbarSelectableItemsKey = "CPToolbarSelectableItemsKey";
|
||||
|
||||
@implementation CPToolbar (CPCoding)
|
||||
|
||||
/*
|
||||
Initializes the toolbar by unarchiving data from <code>aCoder</code>.
|
||||
@param aCoder the coder containing the archived CPToolbar.
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_identifier = [aCoder decodeObjectForKey:CPToolbarIdentifierKey];
|
||||
_displayMode = [aCoder decodeIntForKey:CPToolbarDisplayModeKey];
|
||||
_showsBaselineSeparator = [aCoder decodeBoolForKey:CPToolbarShowsBaselineSeparatorKey];
|
||||
_allowsUserCustomization = [aCoder decodeBoolForKey:CPToolbarAllowsUserCustomizationKey];
|
||||
_isVisible = [aCoder decodeBoolForKey:CPToolbarIsVisibleKey];
|
||||
|
||||
_identifiedItems = [aCoder decodeObjectForKey:CPToolbarIdentifiedItemsKey];
|
||||
_defaultItems = [aCoder decodeObjectForKey:CPToolbarDefaultItemsKey];
|
||||
_allowedItems = [aCoder decodeObjectForKey:CPToolbarAllowedItemsKey];
|
||||
_selectableItems = [aCoder decodeObjectForKey:CPToolbarSelectableItemsKey];
|
||||
|
||||
_items = [];
|
||||
[CPToolbar _addToolbar:self forIdentifier:_identifier];
|
||||
|
||||
[self setDelegate:[aCoder decodeObjectForKey:CPToolbarDelegateKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*
|
||||
Archives this toolbar into the provided coder.
|
||||
@param aCoder the coder to which the toolbar's instance data will be written.
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_identifier forKey:CPToolbarIdentifierKey];
|
||||
[aCoder encodeInt:_displayMode forKey:CPToolbarDisplayModeKey];
|
||||
[aCoder encodeBool:_showsBaselineSeparator forKey:CPToolbarShowsBaselineSeparatorKey];
|
||||
[aCoder encodeBool:_allowsUserCustomization forKey:CPToolbarAllowsUserCustomizationKey];
|
||||
[aCoder encodeBool:_isVisible forKey:CPToolbarIsVisibleKey];
|
||||
|
||||
[aCoder encodeObject:_identifiedItems forKey:CPToolbarIdentifiedItemsKey];
|
||||
[aCoder encodeObject:_defaultItems forKey:CPToolbarDefaultItemsKey];
|
||||
[aCoder encodeObject:_allowedItems forKey:CPToolbarAllowedItemsKey];
|
||||
[aCoder encodeObject:_selectableItems forKey:CPToolbarSelectableItemsKey];
|
||||
|
||||
[aCoder encodeConditionalObject:_delegate forKey:CPToolbarDelegateKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var _CPToolbarViewBackgroundColor = nil,
|
||||
_CPToolbarViewExtraItemsImage = nil,
|
||||
_CPToolbarViewExtraItemsAlternateImage = nil;
|
||||
|
||||
var TOOLBAR_TOP_MARGIN = 5.0,
|
||||
TOOLBAR_ITEM_MARGIN = 10.0,
|
||||
TOOLBAR_EXTRA_ITEMS_WIDTH = 20.0;
|
||||
|
||||
var _CPToolbarItemInfoMake = function(anIndex, aView, aLabel, aMinWidth)
|
||||
{
|
||||
return { index:anIndex, view:aView, label:aLabel, minWidth:aMinWidth };
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
@implementation _CPToolbarView : CPView
|
||||
{
|
||||
CPToolbar _toolbar;
|
||||
|
||||
CPIndexSet _flexibleWidthIndexes;
|
||||
CPIndexSet _visibleFlexibleWidthIndexes;
|
||||
|
||||
CPDictionary _itemInfos;
|
||||
|
||||
CPArray _visibleItems;
|
||||
CPArray _invisibleItems;
|
||||
|
||||
CPPopUpButton _additionalItemsButton;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self != [_CPToolbarView class])
|
||||
return;
|
||||
|
||||
var bundle = [CPBundle bundleForClass:self];
|
||||
|
||||
_CPToolbarViewBackgroundColor = [CPColor colorWithPatternImage:[[CPImage alloc] initWithContentsOfFile:[bundle pathForResource:@"_CPToolbarView/_CPToolbarViewBackground.png"] size:CGSizeMake(1.0, 59.0)]];
|
||||
|
||||
_CPToolbarViewExtraItemsImage = [[CPImage alloc] initWithContentsOfFile: [bundle pathForResource:"_CPToolbarView/_CPToolbarViewExtraItemsImage.png"] size: CPSizeMake(10.0, 15.0)];
|
||||
|
||||
_CPToolbarViewExtraItemsAlternateImage = [[CPImage alloc] initWithContentsOfFile: [bundle pathForResource:"_CPToolbarView/_CPToolbarViewExtraItemsAlternateImage.png"] size:CGSizeMake(10.0, 15.0)];
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)aFrame
|
||||
{
|
||||
self = [super initWithFrame:aFrame];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_minWidth = 0;
|
||||
|
||||
[self setBackgroundColor:_CPToolbarViewBackgroundColor];
|
||||
|
||||
_additionalItemsButton = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 15.0) pullsDown:YES];
|
||||
[_additionalItemsButton setBordered:NO];
|
||||
|
||||
[_additionalItemsButton setImagePosition:CPImageOnly];
|
||||
[[_additionalItemsButton menu] setShowsStateColumn:NO];
|
||||
|
||||
[_additionalItemsButton setAlternateImage:_CPToolbarViewExtraItemsAlternateImage];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setToolbar:(CPToolbar)aToolbar
|
||||
{
|
||||
_toolbar = aToolbar;
|
||||
}
|
||||
|
||||
- (CPToolbar)toolbar
|
||||
{
|
||||
return _toolbar;
|
||||
}
|
||||
|
||||
// This *should* be roughly O(3N) = O(N)
|
||||
- (void)resizeSubviewsWithOldSize:(CGSize)aSize
|
||||
{
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
// We begin by recalculating the visible items.
|
||||
var items = [_toolbar items],
|
||||
width = CGRectGetWidth([self bounds]),
|
||||
minWidth = _minWidth,
|
||||
flexibleItemIndexes = [CPIndexSet indexSet],
|
||||
// FIXME: This should be a CPSet.
|
||||
invisibleItemsSortedByPriority = [];
|
||||
|
||||
_visibleItems = items;
|
||||
|
||||
// We only have hidden items if our actual width is smaller than our
|
||||
// minimum width for hiding items.
|
||||
if (width < minWidth)
|
||||
{
|
||||
width -= TOOLBAR_EXTRA_ITEMS_WIDTH;
|
||||
|
||||
_visibleItems = [_visibleItems copy];
|
||||
|
||||
var itemsSortedByVisibilityPriority = [_toolbar itemsSortedByVisibilityPriority],
|
||||
count = itemsSortedByVisibilityPriority.length;
|
||||
|
||||
// While our items take up too much space...
|
||||
while (minWidth > width)
|
||||
{
|
||||
var item = itemsSortedByVisibilityPriority[count--];
|
||||
|
||||
minWidth -= [self minWidthForItem:item] + TOOLBAR_ITEM_MARGIN;
|
||||
|
||||
[_visibleItems removeObjectIdenticalTo:item];
|
||||
[invisibleItemsSortedByPriority addObject:item];
|
||||
|
||||
[[self viewForItem:item] setHidden:YES];
|
||||
[[self labelForItem:item] setHidden:YES];
|
||||
}
|
||||
}
|
||||
|
||||
// Determine all the items that have flexible width.
|
||||
// Also determine the height of the toolbar.
|
||||
// NOTE: height is height without top margin, and bottom margin/label.
|
||||
var index = _visibleItems.length,
|
||||
height = 0.0;
|
||||
|
||||
while (index--)
|
||||
{
|
||||
var item = _visibleItems[index],
|
||||
minSize = [item minSize],
|
||||
view = [self viewForItem:item];
|
||||
|
||||
if (minSize.width != [item maxSize].width)
|
||||
[flexibleItemIndexes addIndex:index];
|
||||
|
||||
// If the item doesn't have flexible width, then make sure it's set to the static width (min==max)
|
||||
// This handles the case where the user did setView: with a view of a different size than minSize/maxSize
|
||||
else
|
||||
[view setFrameSize:CGSizeMake([item minSize].width, CGRectGetHeight([view frame]))];
|
||||
|
||||
// FIXME: minHeight?
|
||||
|
||||
[view setHidden:NO];
|
||||
[[self labelForItem:item] setHidden:NO];
|
||||
|
||||
if (height < minSize.height)
|
||||
height = minSize.height;
|
||||
}
|
||||
|
||||
var remainingSpace = width - minWidth,
|
||||
proportionate = 0.0;
|
||||
|
||||
// Continue to distribute space proportionately while we have it,
|
||||
// and there are flexible items left that want it. (Those with max
|
||||
// widths may eventually not want it anymore).
|
||||
while (remainingSpace && [flexibleItemIndexes count])
|
||||
{
|
||||
// Divy out the space.
|
||||
proportionate += remainingSpace / [flexibleItemIndexes count];
|
||||
|
||||
// Reset the remaining space to 0
|
||||
remainingSpace = 0.0;
|
||||
|
||||
var index = CPNotFound;
|
||||
|
||||
while ((index = [flexibleItemIndexes indexGreaterThanIndex:index]) != CPNotFound)
|
||||
{
|
||||
var item = _visibleItems[index];
|
||||
view = [self viewForItem:item],
|
||||
viewFrame = [view frame],
|
||||
// FIXME: Should this be minWidthForItem: ?
|
||||
proposedWidth = [item minSize].width + proportionate,
|
||||
constrainedWidth = MIN(proposedWidth, [item maxSize].width);
|
||||
|
||||
if (constrainedWidth < proposedWidth)
|
||||
{
|
||||
[flexibleItemIndexes removeIndex:index];
|
||||
|
||||
remainingSpace += proposedWidth - constrainedWidth;
|
||||
}
|
||||
|
||||
[view setFrameSize:CGSizeMake(constrainedWidth, CGRectGetHeight(viewFrame))];
|
||||
}
|
||||
}
|
||||
|
||||
// Now that all the visible items are the correct width, position them accordingly.
|
||||
var count = _visibleItems.length,
|
||||
x = TOOLBAR_ITEM_MARGIN,
|
||||
fullHeightItems = [];
|
||||
|
||||
for (index = 0; index < count; ++index)
|
||||
{
|
||||
var item = _visibleItems[index],
|
||||
view = [self viewForItem:item],
|
||||
|
||||
viewFrame = [view frame],
|
||||
viewWidth = CGRectGetWidth(viewFrame),
|
||||
|
||||
label = [self labelForItem:item],
|
||||
labelFrame = [label frame],
|
||||
labelWidth = CGRectGetWidth(labelFrame),
|
||||
|
||||
itemWidth = MAX([self minWidthForItem:item], viewWidth),
|
||||
|
||||
viewHeight = CGRectGetHeight(viewFrame);
|
||||
|
||||
// itemWidth != viewWidth. itemWidth is MAX(size of view, size of label). If the label is larger,
|
||||
// *center* the view, don't resize it.
|
||||
[view setFrame:CGRectMake(x + (itemWidth - viewWidth) / 2.0, TOOLBAR_TOP_MARGIN + (height - viewHeight) / 2.0, viewWidth, viewHeight)];
|
||||
[label setFrameOrigin:CGPointMake(x + (itemWidth - labelWidth) / 2.0, TOOLBAR_TOP_MARGIN + height + 2.0)];
|
||||
|
||||
x += itemWidth + TOOLBAR_ITEM_MARGIN;
|
||||
|
||||
if ([item itemIdentifier] == CPToolbarSeparatorItemIdentifier)
|
||||
fullHeightItems.push(item);
|
||||
}
|
||||
|
||||
for (index = 0, count = fullHeightItems.length; index < count; ++index)
|
||||
{
|
||||
var view = [self viewForItem:fullHeightItems[index]],
|
||||
viewHeight = 53.0;
|
||||
|
||||
// FIXME: Variable Height
|
||||
[view setFrame:CGRectMake(CGRectGetMinX([view frame]), (59.0 - viewHeight) / 2.0, CGRectGetWidth([view frame]), viewHeight)];
|
||||
}
|
||||
|
||||
if ([invisibleItemsSortedByPriority count])
|
||||
{
|
||||
var index = 0,
|
||||
count = [items count];
|
||||
|
||||
_invisibleItems = [];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var item = items[index];
|
||||
|
||||
if ([invisibleItemsSortedByPriority indexOfObjectIdenticalTo:item] != CPNotFound)
|
||||
[_invisibleItems addObject:item];
|
||||
}
|
||||
|
||||
[_additionalItemsButton setFrameOrigin:CGPointMake(width + 5.0, (CGRectGetHeight([self bounds]) - CGRectGetHeight([_additionalItemsButton frame])) / 2.0)];
|
||||
|
||||
[self addSubview:_additionalItemsButton];
|
||||
|
||||
[_additionalItemsButton removeAllItems];
|
||||
|
||||
var index = 0,
|
||||
count = [_invisibleItems count];
|
||||
|
||||
[_additionalItemsButton addItemWithTitle:@"Additional Items"];
|
||||
[[_additionalItemsButton itemArray][0] setImage:_CPToolbarViewExtraItemsImage];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var item = _invisibleItems[index];
|
||||
|
||||
[_additionalItemsButton addItemWithTitle:[item label]];
|
||||
|
||||
var menuItem = [_additionalItemsButton itemArray][index + 1];
|
||||
|
||||
[menuItem setImage:[item image]];
|
||||
|
||||
[menuItem setTarget:[item target]];
|
||||
[menuItem setAction:[item action]];
|
||||
}
|
||||
}
|
||||
else
|
||||
[_additionalItemsButton removeFromSuperview];
|
||||
|
||||
}
|
||||
|
||||
- (CPView)viewForItem:(CPToolbarItem)anItem
|
||||
{
|
||||
var info = [_itemInfos objectForKey:[anItem hash]];
|
||||
|
||||
if (!info)
|
||||
return nil;
|
||||
|
||||
return info.view;
|
||||
}
|
||||
|
||||
- (CPTextField)labelForItem:(CPToolbarItem)anItem
|
||||
{
|
||||
var info = [_itemInfos objectForKey:[anItem hash]];
|
||||
|
||||
if (!info)
|
||||
return nil;
|
||||
|
||||
return info.label;
|
||||
}
|
||||
|
||||
- (float)minWidthForItem:(CPToolbarItem)anItem
|
||||
{
|
||||
var info = [_itemInfos objectForKey:[anItem hash]];
|
||||
|
||||
if (!info)
|
||||
return 0;
|
||||
|
||||
return info.minWidth;
|
||||
}
|
||||
|
||||
- (void)reloadToolbarItems
|
||||
{
|
||||
// Get rid of all our current subviews.
|
||||
var subviews = [self subviews],
|
||||
count = subviews.length;
|
||||
|
||||
while (count--)
|
||||
[subviews removeObjectAtIndex:count];
|
||||
|
||||
// Populate with new subviews.
|
||||
var items = [_toolbar items],
|
||||
index = 0;
|
||||
|
||||
count = items.length;
|
||||
|
||||
_itemInfos = [CPDictionary dictionary];
|
||||
_minWidth = TOOLBAR_ITEM_MARGIN;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var item = items[index],
|
||||
view = [item view];
|
||||
|
||||
// If this item doesn't have a custom view, create a standard one.
|
||||
if (!view)
|
||||
{
|
||||
view = [[CPButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 32.0, 32.0)];
|
||||
|
||||
[view setBordered:NO];
|
||||
|
||||
[view setImage:[item image]];
|
||||
[view setAlternateImage:[item alternateImage]];
|
||||
|
||||
[view setTarget:[item target]];
|
||||
[view setAction:[item action]];
|
||||
|
||||
[view setImagePosition:CPImageOnly];
|
||||
}
|
||||
|
||||
[self addSubview:view];
|
||||
|
||||
// Create a lable for this item.
|
||||
var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
|
||||
|
||||
[label setStringValue:[item label]];
|
||||
[label setFont:[CPFont systemFontOfSize:11.0]];
|
||||
[label sizeToFit];
|
||||
|
||||
[label setTarget:[item target]];
|
||||
[label setAction:[item action]];
|
||||
|
||||
[self addSubview:label];
|
||||
|
||||
var minSize = [item minSize],
|
||||
minWidth = MAX(minSize.width, CGRectGetWidth([label frame]));
|
||||
|
||||
[_itemInfos setObject:_CPToolbarItemInfoMake(index, view, label, minWidth) forKey:[item hash]];
|
||||
|
||||
_minWidth += minWidth + TOOLBAR_ITEM_MARGIN;
|
||||
|
||||
// If the minSize is different than the maxSize, then this item has flexible width.
|
||||
//if (minSize.width != [item maxSize].width)
|
||||
// [_flexibleWidthIndexes addIndex:index];
|
||||
}
|
||||
|
||||
[self layoutSubviews];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/* @ignore */
|
||||
var _CPToolbarItemVisibilityPriorityCompare = function(lhs, rhs)
|
||||
{
|
||||
var lhsVisibilityPriority = [lhs visibilityPriority],
|
||||
rhsVisibilityPriority = [rhs visibilityPriority];
|
||||
|
||||
if (lhsVisibilityPriority == rhsVisibilityPriority)
|
||||
return CPOrderedSame;
|
||||
|
||||
if (lhsVisibilityPriority > rhsVisibilityPriority)
|
||||
return CPOrderedAscending;
|
||||
|
||||
return CPOrderedDescending;
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
* CPToolbarItem.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import <AppKit/CPImage.j>
|
||||
@import <AppKit/CPView.j>
|
||||
|
||||
|
||||
/*
|
||||
@global
|
||||
@class CPToolbarItem
|
||||
*/
|
||||
CPToolbarItemVisibilityPriorityStandard = 0;
|
||||
/*
|
||||
@global
|
||||
@class CPToolbarItem
|
||||
*/
|
||||
CPToolbarItemVisibilityPriorityLow = -1000;
|
||||
/*
|
||||
@global
|
||||
@class CPToolbarItem
|
||||
*/
|
||||
CPToolbarItemVisibilityPriorityHigh = 1000;
|
||||
/*
|
||||
@global
|
||||
@class CPToolbarItem
|
||||
*/
|
||||
CPToolbarItemVisibilityPriorityUser = 2000;
|
||||
|
||||
CPToolbarSeparatorItemIdentifier = @"CPToolbarSeparatorItemIdentifier";
|
||||
CPToolbarSpaceItemIdentifier = @"CPToolbarSpaceItemIdentifier";
|
||||
CPToolbarFlexibleSpaceItemIdentifier = @"CPToolbarFlexibleSpaceItemIdentifier";
|
||||
CPToolbarShowColorsItemIdentifier = @"CPToolbarShowColorsItemIdentifier";
|
||||
CPToolbarShowFontsItemIdentifier = @"CPToolbarShowFontsItemIdentifier";
|
||||
CPToolbarCustomizeToolbarItemIdentifier = @"CPToolbarCustomizeToolbarItemIdentifier";
|
||||
CPToolbarPrintItemIdentifier = @"CPToolbarPrintItemIdentifier";
|
||||
|
||||
var _CPToolbarSeparatorItemView = nil;
|
||||
|
||||
/*! @class CPToolbarItem
|
||||
|
||||
A representation of an item in a CPToolbar.
|
||||
*/
|
||||
@implementation CPToolbarItem : CPObject
|
||||
{
|
||||
CPString _itemIdentifier;
|
||||
|
||||
CPToolbar _toolbar;
|
||||
|
||||
CPString _label;
|
||||
CPString _paletteLabel;
|
||||
CPString _toolTip;
|
||||
int _tag;
|
||||
id _target;
|
||||
SEL _action;
|
||||
BOOL _isEnabled;
|
||||
CPImage _image;
|
||||
CPImage _alternateImage;
|
||||
|
||||
CPView _view;
|
||||
|
||||
CGSize _minSize;
|
||||
CGSize _maxSize;
|
||||
|
||||
int _visibilityPriority;
|
||||
}
|
||||
|
||||
// Creating a Toolbar Item
|
||||
/*!
|
||||
Initializes the toolbar item with a specified identifier.
|
||||
@param anItemIdentifier the item's identifier
|
||||
@return the initialized toolbar item
|
||||
*/
|
||||
- (id)initWithItemIdentifier:(CPString)anItemIdentifier
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_itemIdentifier = anItemIdentifier;
|
||||
|
||||
_tag = 0;
|
||||
_isEnabled = YES;
|
||||
|
||||
_minSize = CGSizeMakeZero();
|
||||
_maxSize = CGSizeMakeZero();
|
||||
|
||||
_visibilityPriority = CPToolbarItemVisibilityPriorityStandard;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
// Managing Attributes
|
||||
/*!
|
||||
Returns the item's identifier.
|
||||
*/
|
||||
- (CPString)itemIdentifier
|
||||
{
|
||||
return _itemIdentifier;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the toolbar of which this item is a part.
|
||||
*/
|
||||
- (CPToolbar)toolbar
|
||||
{
|
||||
return _toolbar;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's label
|
||||
*/
|
||||
- (CPString)label
|
||||
{
|
||||
return _label;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's label.
|
||||
@param aLabel the new label for the item
|
||||
*/
|
||||
- (void)setLabel:(CPString)aLabel
|
||||
{
|
||||
_label = aLabel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the palette label.
|
||||
*/
|
||||
- (CPString)paletteLabel
|
||||
{
|
||||
return _paletteLabel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the palette label
|
||||
@param aPaletteLabel the new palette label
|
||||
*/
|
||||
- (void)setPaletteLabel:(CPString)aPaletteLabel
|
||||
{
|
||||
_paletteLabel = aPaletteLabel;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's tooltip. A tooltip pops up
|
||||
next to the cursor when the user hovers over
|
||||
the item with the mouse.
|
||||
*/
|
||||
- (CPString)toolTip
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(toolTip)])
|
||||
return [_view toolTip];
|
||||
|
||||
return _toolTip;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's tooltip. A tooltip pops up next to the cursor when the user hovers over the item with the mouse.
|
||||
@param aToolTip the new item tool tip
|
||||
*/
|
||||
- (void)setToolTip:(CPString)aToolTip
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setToolTip:)])
|
||||
[view setToolTip:aToolTip];
|
||||
|
||||
_toolTip = aToolTip;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's tag.
|
||||
*/
|
||||
- (int)tag
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(tag)])
|
||||
return [_view tag];
|
||||
|
||||
return _tag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's tag.
|
||||
@param aTag the new tag for the item
|
||||
*/
|
||||
- (void)setTag:(int)aTag
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setTag:)])
|
||||
[_view setTag:aTag];
|
||||
|
||||
_tag = aTag;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's action target.
|
||||
*/
|
||||
- (id)target
|
||||
{
|
||||
if (_view)
|
||||
return [_view respondsToSelector:@selector(target)] ? [_view target] : nil;
|
||||
|
||||
return _target;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the target of the action that is triggered when the user clicks this item. <code>nil</code> will cause
|
||||
the action to be passed on to the first responder.
|
||||
@param aTarget the new target
|
||||
*/
|
||||
- (void)setTarget:(id)aTarget
|
||||
{
|
||||
if (!_view)
|
||||
_target = aTarget;
|
||||
|
||||
else if ([_view respondsToSelector:@selector(setTarget:)])
|
||||
[_view setTarget:aTarget];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the action that is triggered when the user clicks this item.
|
||||
*/
|
||||
- (SEL)action
|
||||
{
|
||||
if (_view)
|
||||
return [_view respondsToSelector:@selector(action)] ? [_view action] : nil;
|
||||
|
||||
return _action;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the action that is triggered when the user clicks this item.
|
||||
@param anAction the new action
|
||||
*/
|
||||
- (void)setAction:(SEL)anAction
|
||||
{
|
||||
if (!_view)
|
||||
_action = anAction;
|
||||
|
||||
else if ([_view respondsToSelector:@selector(setAction:)])
|
||||
[_view setAction:anAction];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the item is enabled.
|
||||
*/
|
||||
- (BOOL)isEnabled
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(isEnabled)])
|
||||
return [_view isEnabled];
|
||||
|
||||
return _isEnabled;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the item is enabled.
|
||||
@param aFlag <code>YES</code> enables the item
|
||||
*/
|
||||
- (void)setEnabled:(BOOL)shouldBeEnabled
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setEnabled:)])
|
||||
[_view setEnabled:shouldBeEnabled];
|
||||
|
||||
_isEnabled = shouldBeEnabled;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's image
|
||||
*/
|
||||
- (CPImage)image
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(image)])
|
||||
return [_view image];
|
||||
|
||||
return _image;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's image.
|
||||
@param anImage the new item image
|
||||
*/
|
||||
- (void)setImage:(CPImage)anImage
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setImage:)])
|
||||
[_view setImage:anImage];
|
||||
|
||||
_image = anImage;
|
||||
|
||||
if (!_image)
|
||||
return;
|
||||
|
||||
if (_minSize.width == 0 && _minSize.height == 0 &&
|
||||
_maxSize.width == 0 && _maxSize.height == 0)
|
||||
{
|
||||
var imageSize = [_image size];
|
||||
|
||||
if (imageSize.width > 0 || imageSize.height > 0)
|
||||
{
|
||||
[self setMinSize:imageSize];
|
||||
[self setMaxSize:imageSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the alternate image. This image is displayed on the item when the user is clicking it.
|
||||
@param anImage the new alternate image
|
||||
*/
|
||||
- (void)setAlternateImage:(CPImage)anImage
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(setAlternateImage:)])
|
||||
[_view setAlternateImage:anImage];
|
||||
|
||||
_alternateImage = anImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the alternate image. This image is displayed on the item when the user is clicking it.
|
||||
*/
|
||||
- (CPImage)alternateImage
|
||||
{
|
||||
if ([_view respondsToSelector:@selector(alternateIamge)])
|
||||
return [_view alternateImage];
|
||||
|
||||
return _alternateImage;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's view.
|
||||
*/
|
||||
- (CPView)view
|
||||
{
|
||||
return _view;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's view
|
||||
@param aView the item's new view
|
||||
*/
|
||||
- (void)setView:(CPView)aView
|
||||
{
|
||||
if (_view == aView)
|
||||
return;
|
||||
|
||||
_view = aView;
|
||||
|
||||
if (_view)
|
||||
{
|
||||
// Tags get forwarded.
|
||||
if (_tag !== 0 && [_view respondsToSelector:@selector(setTag:)])
|
||||
[_view setTag:_tag];
|
||||
|
||||
_target = nil;
|
||||
_action = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's minimum size.
|
||||
*/
|
||||
- (CGSize)minSize
|
||||
{
|
||||
return _minSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's minimum size.
|
||||
@param aMinSize the new minimum size
|
||||
*/
|
||||
- (void)setMinSize:(CGSize)aMinSize
|
||||
{
|
||||
_minSize = CGSizeMakeCopy(aMinSize);
|
||||
|
||||
// Try to provide some sanity: Make maxSize >= minSize
|
||||
_maxSize = CGSizeMake(MAX(_minSize.width, _maxSize.width), MAX(_minSize.height, _maxSize.height));
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the item's maximum size.
|
||||
*/
|
||||
- (CGSize)maxSize
|
||||
{
|
||||
return _maxSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's new maximum size.
|
||||
@param aMaxSize the new maximum size
|
||||
*/
|
||||
- (void)setMaxSize:(CGSize)aMaxSize
|
||||
{
|
||||
_maxSize = CGSizeMakeCopy(aMaxSize);
|
||||
|
||||
// Try to provide some sanity: Make minSize <= maxSize
|
||||
_minSize = CGSizeMake(MIN(_minSize.width, _maxSize.width), MIN(_minSize.height, _maxSize.height));
|
||||
}
|
||||
|
||||
// Visibility Priority
|
||||
/*!
|
||||
Returns the item's visibility priority. The value will be one of:
|
||||
<pre>
|
||||
CPToolbarItemVisibilityPriorityStandard
|
||||
CPToolbarItemVisibilityPriorityLow
|
||||
CPToolbarItemVisibilityPriorityHigh
|
||||
CPToolbarItemVisibilityPriorityUser
|
||||
</pre>
|
||||
*/
|
||||
- (int)visibilityPriority
|
||||
{
|
||||
return _visibilityPriority;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the item's visibility priority. The value must be one of:
|
||||
<pre>
|
||||
CPToolbarItemVisibilityPriorityStandard
|
||||
CPToolbarItemVisibilityPriorityLow
|
||||
CPToolbarItemVisibilityPriorityHigh
|
||||
CPToolbarItemVisibilityPriorityUser
|
||||
</pre>
|
||||
@param aVisiblityPriority the priority
|
||||
*/
|
||||
- (void)setVisibilityPriority:(int)aVisibilityPriority
|
||||
{
|
||||
_visibilityPriority = aVisibilityPriority;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPToolbarItem (CPCopying)
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var copy = [[[self class] alloc] initWithItemIdentifier:_itemIdentifier];
|
||||
|
||||
if (_view)
|
||||
[copy setView:[CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:_view]]];
|
||||
|
||||
[copy setLabel:_label];
|
||||
[copy setPaletteLabel:_paletteLabel];
|
||||
[copy setToolTip:[self toolTip]];
|
||||
|
||||
[copy setTag:[self tag]];
|
||||
[copy setTarget:[self target]];
|
||||
[copy setAction:[self action]];
|
||||
|
||||
[copy setEnabled:[self isEnabled]];
|
||||
[copy setImage:_image];
|
||||
[copy setAlternateImage:_alternateImage];
|
||||
|
||||
[copy setMinSize:_minSize];
|
||||
[copy setMaxSize:_maxSize];
|
||||
|
||||
[copy setVisibilityPriority:_visibilityPriority];
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Standard toolbar identifiers
|
||||
|
||||
@implementation CPToolbarItem (Standard)
|
||||
|
||||
/* @ignore */
|
||||
+ (CPToolbarItem)_standardItemWithItemIdentifier:(CPString)anItemIdentifier
|
||||
{
|
||||
var item = [[CPToolbarItem alloc] initWithItemIdentifier:anItemIdentifier];
|
||||
|
||||
switch (anItemIdentifier)
|
||||
{
|
||||
case CPToolbarSeparatorItemIdentifier: [item setMinSize:CGSizeMake(2.0, 0.0)];
|
||||
[item setMaxSize:CGSizeMake(2.0, 100000.0)];
|
||||
|
||||
if (!_CPToolbarSeparatorItemView)
|
||||
{
|
||||
_CPToolbarSeparatorItemView = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 2.0, 32.0)];
|
||||
|
||||
sizes = {};
|
||||
sizes[@"CPToolbarItemSeparator"] = [CGSizeMake(2.0, 26.0), CGSizeMake(2.0, 1.0), CGSizeMake(2.0, 26.0)];
|
||||
[_CPToolbarSeparatorItemView setBackgroundColor:_CPControlThreePartImagePattern(YES, sizes, @"CPToolbarItem", @"Separator")];
|
||||
}
|
||||
|
||||
[item setView:_CPToolbarSeparatorItemView];
|
||||
|
||||
|
||||
return item;
|
||||
|
||||
case CPToolbarSpaceItemIdentifier: [item setMinSize:CGSizeMake(32.0, 32.0)];
|
||||
[item setMaxSize:CGSizeMake(32.0, 32.0)];
|
||||
|
||||
return item;
|
||||
|
||||
case CPToolbarFlexibleSpaceItemIdentifier: [item setMinSize:CGSizeMake(32.0, 32.0)];
|
||||
[item setMaxSize:CGSizeMake(10000.0, 32.0)];
|
||||
|
||||
return item;
|
||||
|
||||
case CPToolbarShowColorsItemIdentifier: return nil;
|
||||
case CPToolbarShowFontsItemIdentifier: return nil;
|
||||
case CPToolbarCustomizeToolbarItemIdentifier: return nil;
|
||||
case CPToolbarPrintItemIdentifier: return nil;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
* CPWebView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Thomas Robinson.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
// FIXME: implement these where possible:
|
||||
/*
|
||||
CPWebViewDidBeginEditingNotification = "CPWebViewDidBeginEditingNotification";
|
||||
CPWebViewDidChangeNotification = "CPWebViewDidChangeNotification";
|
||||
CPWebViewDidChangeSelectionNotification = "CPWebViewDidChangeSelectionNotification";
|
||||
CPWebViewDidChangeTypingStyleNotification = "CPWebViewDidChangeTypingStyleNotification";
|
||||
CPWebViewDidEndEditingNotification = "CPWebViewDidEndEditingNotification";
|
||||
CPWebViewProgressEstimateChangedNotification = "CPWebViewProgressEstimateChangedNotification";
|
||||
*/
|
||||
CPWebViewProgressStartedNotification = "CPWebViewProgressStartedNotification";
|
||||
CPWebViewProgressFinishedNotification = "CPWebViewProgressFinishedNotification";
|
||||
|
||||
// FIXME: somehow make CPWebView work with CPScrollView instead of native scrollbars (is this even possible?)
|
||||
|
||||
@implementation CPWebView : CPView
|
||||
{
|
||||
IFrame _iframe;
|
||||
CPString _mainFrameURL;
|
||||
CPArray _backwardStack;
|
||||
CPArray _forwardStack;
|
||||
|
||||
BOOL _ignoreLoadEvent;
|
||||
|
||||
id _downloadDelegate;
|
||||
id _frameLoadDelegate;
|
||||
id _policyDelegate;
|
||||
id _resourceLoadDelegate;
|
||||
id _UIDelegate;
|
||||
|
||||
CPWebScriptObject _wso;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)frameRect frameName:(CPString)frameName groupName:(CPString)groupName
|
||||
{
|
||||
if (self = [self initWithFrame:frameRect])
|
||||
{
|
||||
_iframe.name = frameName;
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
if (self = [super initWithFrame:aFrame])
|
||||
{
|
||||
_mainFrameURL = nil;
|
||||
_backwardStack = [];
|
||||
_forwardStack = [];
|
||||
[self _initDOMWithFrame:aFrame];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)_initDOMWithFrame:(CPRect)aFrame
|
||||
{
|
||||
_ignoreLoadEvent = NO;
|
||||
|
||||
_iframe = document.createElement("iframe");
|
||||
_iframe.name = "iframe_" + Math.floor(Math.random()*10000);
|
||||
_iframe.style.width = "100%";
|
||||
_iframe.style.height = "100%";
|
||||
_iframe.style.borderWidth = "0px";
|
||||
[self setDrawsBackground:YES];
|
||||
|
||||
var loadCallback = function() {
|
||||
// HACK: this block handles the case where we don't know about loads initiated by the user clicking a link
|
||||
if (!_ignoreLoadEvent)
|
||||
{
|
||||
// post the start load notification
|
||||
[self _startedLoading];
|
||||
|
||||
if (_mainFrameURL)
|
||||
[_backwardStack addObject:_mainFrameURL];
|
||||
|
||||
// FIXME: this doesn't actually get the right URL for different domains. Probably not be possible due to browser security restrictions.
|
||||
_mainFrameURL = _iframe.src;
|
||||
|
||||
[_forwardStack removeAllObjects];
|
||||
}
|
||||
_ignoreLoadEvent = NO;
|
||||
|
||||
[self _finishedLoading]
|
||||
}
|
||||
|
||||
if (_iframe.addEventListener)
|
||||
_iframe.addEventListener("load", loadCallback, false);
|
||||
else if (_iframe.attachEvent)
|
||||
_iframe.attachEvent("onload", loadCallback);
|
||||
|
||||
_DOMElement.appendChild(_iframe);
|
||||
}
|
||||
|
||||
// IBActions
|
||||
|
||||
- (IBAction)takeStringURLFrom:(id)sender
|
||||
{
|
||||
[self setMainFrameURL:[sender stringValue]];
|
||||
}
|
||||
|
||||
- (IBAction)goBack:(id)sender
|
||||
{
|
||||
[self goBack];
|
||||
}
|
||||
|
||||
- (IBAction)goForward:(id)sender
|
||||
{
|
||||
[self goForward];
|
||||
}
|
||||
|
||||
- (IBAction)stopLoading:(id)sender
|
||||
{
|
||||
// FIXME: what to do?
|
||||
}
|
||||
|
||||
- (IBAction)reload:(id)sender
|
||||
{
|
||||
[self _loadMainFrameURL];
|
||||
}
|
||||
|
||||
- (IBAction)print:(id)sender
|
||||
{
|
||||
try
|
||||
{
|
||||
[self window].print();
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
alert('Please click the webpage and select "Print" from the "File" menu');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)drawsBackground
|
||||
{
|
||||
return _iframe.style.backgroundColor != "";
|
||||
}
|
||||
|
||||
- (void)setDrawsBackground:(BOOL)drawsBackround
|
||||
{
|
||||
_iframe.style.backgroundColor = drawsBackround ? "white" : "";
|
||||
}
|
||||
|
||||
- (void)_loadMainFrameURL
|
||||
{
|
||||
[self _startedLoading];
|
||||
|
||||
_ignoreLoadEvent = YES;
|
||||
_iframe.src = _mainFrameURL;
|
||||
}
|
||||
|
||||
- (void)_startedLoading
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPWebViewProgressStartedNotification object:self];
|
||||
|
||||
if ([_frameLoadDelegate respondsToSelector:@selector(webView:didStartProvisionalLoadForFrame:)])
|
||||
[_frameLoadDelegate webView:self didStartProvisionalLoadForFrame:nil]; // FIXME: give this a frame somehow?
|
||||
}
|
||||
|
||||
- (void)_finishedLoading
|
||||
{
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPWebViewProgressFinishedNotification object:self];
|
||||
|
||||
if ([_frameLoadDelegate respondsToSelector:@selector(webView:didFinishLoadForFrame:)])
|
||||
[_frameLoadDelegate webView:self didFinishLoadForFrame:nil]; // FIXME: give this a frame somehow?
|
||||
}
|
||||
|
||||
- (CPString)mainFrameURL
|
||||
{
|
||||
return _mainFrameURL;
|
||||
}
|
||||
|
||||
- (void)setMainFrameURL:(CPString)URLString
|
||||
{
|
||||
if (_mainFrameURL)
|
||||
[_backwardStack addObject:_mainFrameURL];
|
||||
_mainFrameURL = URLString;
|
||||
[_forwardStack removeAllObjects];
|
||||
|
||||
[self _loadMainFrameURL];
|
||||
}
|
||||
|
||||
- (BOOL)goBack
|
||||
{
|
||||
if (_backwardStack.length > 0)
|
||||
{
|
||||
if (_mainFrameURL)
|
||||
[_forwardStack addObject:_mainFrameURL];
|
||||
_mainFrameURL = [_backwardStack lastObject];
|
||||
[_backwardStack removeLastObject];
|
||||
|
||||
[self _loadMainFrameURL];
|
||||
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)goForward
|
||||
{
|
||||
if (_forwardStack.length > 0)
|
||||
{
|
||||
if (_mainFrameURL)
|
||||
[_backwardStack addObject:_mainFrameURL];
|
||||
_mainFrameURL = [_forwardStack lastObject];
|
||||
[_forwardStack removeLastObject];
|
||||
|
||||
[self _loadMainFrameURL];
|
||||
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canGoBack
|
||||
{
|
||||
return (_backwardStack.length > 0);
|
||||
}
|
||||
|
||||
- (BOOL)canGoForward
|
||||
{
|
||||
return (_forwardStack.length > 0);
|
||||
}
|
||||
|
||||
- (WebBackForwardList)backForwardList
|
||||
{
|
||||
// FIXME: return a real WebBackForwardList?
|
||||
return { back: _backwardStack, forward: _forwardStack };
|
||||
}
|
||||
|
||||
- (void)close
|
||||
{
|
||||
_DOMElement.removeChild(_iframe);
|
||||
}
|
||||
|
||||
- (Window)window
|
||||
{
|
||||
return (_iframe.contentDocument && _iframe.contentDocument.defaultView) || _iframe.contentWindow;
|
||||
}
|
||||
|
||||
- (CPWebScriptObject)windowScriptObject
|
||||
{
|
||||
var win = [self window];
|
||||
if (!_wso || win != [_wso window])
|
||||
{
|
||||
if (win)
|
||||
_wso = [[CPWebScriptObject alloc] initWithWindow:win];
|
||||
else
|
||||
_wso = nil;
|
||||
}
|
||||
return _wso;
|
||||
}
|
||||
|
||||
- (CPString)stringByEvaluatingJavaScriptFromString:(CPString)script
|
||||
{
|
||||
var result = [self objectByEvaluatingJavaScriptFromString:script];
|
||||
return result ? String(result) : nil;
|
||||
}
|
||||
|
||||
- (JSObject)objectByEvaluatingJavaScriptFromString:(CPString)script
|
||||
{
|
||||
return [[self windowScriptObject] evaluateWebScript:script];
|
||||
}
|
||||
|
||||
- (DOMCSSStyleDeclaration)computedStyleForElement:(DOMElement)element pseudoElement:(CPString)pseudoElement
|
||||
{
|
||||
var win = [[self windowScriptObject] window];
|
||||
if (win)
|
||||
{
|
||||
// FIXME: IE version?
|
||||
return win.document.defaultView.getComputedStyle(element, pseudoElement);
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Delegates:
|
||||
|
||||
// FIXME: implement more delegates, though most of these will likely never work with the iframe implementation
|
||||
|
||||
- (id)downloadDelegate
|
||||
{
|
||||
return _downloadDelegate;
|
||||
}
|
||||
- (void)setDownloadDelegate:(id)anObject
|
||||
{
|
||||
_downloadDelegate = anObject;
|
||||
}
|
||||
- (id)frameLoadDelegate
|
||||
{
|
||||
return _frameLoadDelegate;
|
||||
}
|
||||
- (void)setFrameLoadDelegate:(id)anObject
|
||||
{
|
||||
_frameLoadDelegate = anObject;
|
||||
}
|
||||
- (id)policyDelegate
|
||||
{
|
||||
return _policyDelegate;
|
||||
}
|
||||
- (void)setPolicyDelegate:(id)anObject
|
||||
{
|
||||
_policyDelegate = anObject;
|
||||
}
|
||||
- (id)resourceLoadDelegate
|
||||
{
|
||||
return _resourceLoadDelegate;
|
||||
}
|
||||
- (void)setResourceLoadDelegate:(id)anObject
|
||||
{
|
||||
_resourceLoadDelegate = anObject;
|
||||
}
|
||||
- (id)UIDelegate
|
||||
{
|
||||
return _UIDelegate;
|
||||
}
|
||||
- (void)setUIDelegate:(id)anObject
|
||||
{
|
||||
_UIDelegate = anObject;
|
||||
}
|
||||
|
||||
|
||||
- (void)loadHTMLString:(CPString)aString
|
||||
{
|
||||
[self loadHTMLString:aString baseURL:nil];
|
||||
}
|
||||
|
||||
- (void)loadHTMLString:(CPString)aString baseURL:(CPURL)URL
|
||||
{
|
||||
// FIXME: do something with baseURL?
|
||||
|
||||
// clear the iframe
|
||||
_iframe.src = "";
|
||||
|
||||
// need to give the browser a chance to reset iframe, otherwise we'll be document.write()-ing the previous document
|
||||
window.setTimeout(function() {
|
||||
var win = [self window];
|
||||
win.document.write(aString);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPWebScriptObject : CPObject
|
||||
{
|
||||
Window _window;
|
||||
}
|
||||
|
||||
- (id)initWithWindow:(Window)aWindow
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_window = aWindow
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)callWebScriptMethod:(CPString)methodName withArguments:(CPArray)args
|
||||
{
|
||||
// Would using "with" be better here?
|
||||
if (typeof _window[methodName] == "function")
|
||||
{
|
||||
try {
|
||||
return _window[methodName].apply(args);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
- (id)evaluateWebScript:(CPString)script
|
||||
{
|
||||
try {
|
||||
return _window.eval(script);
|
||||
} catch (e) {
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
- (Window)window
|
||||
{
|
||||
return _window;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPWebView (CPCoding)
|
||||
|
||||
/*!
|
||||
Initializes the web view from the data in a coder.
|
||||
@param aCoder the coder from which to read the data
|
||||
@return the initialized web view
|
||||
*/
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
{
|
||||
// FIXME: encode/decode these?
|
||||
_mainFrameURL = nil;
|
||||
_backwardStack = [];
|
||||
_forwardStack = [];
|
||||
|
||||
[self _initDOMWithFrame:[self frame]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Writes out the web view's instance information to a coder.
|
||||
@param aCoder the coder to which to write the data
|
||||
*/
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* CPWindowController.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPResponder.j"
|
||||
@import "CPWindow.j"
|
||||
@import "CPDocument.j"
|
||||
|
||||
#include "Platform/Platform.h"
|
||||
|
||||
|
||||
/*! @class CPWindowController
|
||||
|
||||
An instance of a CPWindowController manages a CPWindow. It has methods
|
||||
that get called when the window is loading, and after the window has loaded. In the
|
||||
Model-View-Controller method of program design, the CPWindowController would be
|
||||
considered the 'Controller' and the CPWindow the 'Model.'
|
||||
*/
|
||||
@implementation CPWindowController : CPResponder
|
||||
{
|
||||
id _owner;
|
||||
CPWindow _window;
|
||||
CPDocument _document;
|
||||
CPString _windowCibName;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the controller with a window.
|
||||
@param aWindow the window to control
|
||||
@return the initialzed window controller
|
||||
*/
|
||||
- (id)initWithWindow:(CPWindow)aWindow
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
[self setWindow:aWindow];
|
||||
|
||||
[self setNextResponder:CPApp];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the controller with a Capppuccino Interface Builder name.
|
||||
@param aWindowCibName the cib name of the window to control
|
||||
@return the initialized window controller
|
||||
*/
|
||||
- (id)initWithWindowCibName:(CPString)aWindowCibName
|
||||
{
|
||||
return [self initWithWindowCibName:aWindowCibName owner:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Initializes the controller with a cafe name.
|
||||
@param aWindowCibName the cib name of the window to control
|
||||
@param anOwner the owner of the cib file
|
||||
@return the initialized window controller
|
||||
*/
|
||||
- (id)initWithWindowCibName:(CPString)aWindowCibName owner:(id)anOwner
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_owner = anOwner;
|
||||
_windowCibName = aWindowCibName;
|
||||
|
||||
[self setNextResponder:CPApp];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Loads the window
|
||||
*/
|
||||
- (void)loadWindow
|
||||
{
|
||||
[self windowWillLoad];
|
||||
//FIXME: ACTUALLY LOAD WINDOW!!!
|
||||
[self setWindow:CPApp._keyWindow = [[CPWindow alloc] initWithContentRect:CPRectMakeZero() styleMask:CPBorderlessBridgeWindowMask|CPTitledWindowMask|CPClosableWindowMask|CPResizableWindowMask]];
|
||||
|
||||
[self windowDidLoad];
|
||||
}
|
||||
|
||||
/*!
|
||||
Shows the window.
|
||||
@param aSender the object requesting the show
|
||||
*/
|
||||
- (CFAction)showWindow:(id)aSender
|
||||
{
|
||||
var theWindow = [self window];
|
||||
|
||||
if ([theWindow respondsToSelector:@selector(becomesKeyOnlyIfNeeded)] && [theWindow becomesKeyOnlyIfNeeded])
|
||||
[theWindow orderFront:aSender];
|
||||
else
|
||||
[theWindow makeKeyAndOrderFront:aSender];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the window has been loaded. Specifically,
|
||||
if loadWindow has been called.
|
||||
*/
|
||||
- (BOOL)isWindowLoaded
|
||||
{
|
||||
return _window;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the window this object controls.
|
||||
*/
|
||||
- (CPWindow)window
|
||||
{
|
||||
if (!_window)
|
||||
[self loadWindow];
|
||||
|
||||
return _window;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the window to be controlled.
|
||||
@param aWindow the new window to control
|
||||
*/
|
||||
- (void)setWindow:(CPWindow)aWindow
|
||||
{
|
||||
_window = aWindow;
|
||||
|
||||
[_window setWindowController:self];
|
||||
[_window setNextResponder:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
The method notifies the controller that it's window has loaded.
|
||||
*/
|
||||
- (void)windowDidLoad
|
||||
{
|
||||
[_document windowControllerDidLoadNib:self];
|
||||
|
||||
[self synchronizeWindowTitleWithDocumentName];
|
||||
}
|
||||
|
||||
/*!
|
||||
The method notifies the controller that it's window is about to load.
|
||||
*/
|
||||
- (void)windowWillLoad
|
||||
{
|
||||
[_document windowControllerWillLoadNib:self];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the document that is inside the controlled window.
|
||||
@param aDocument the document in the controlled window
|
||||
*/
|
||||
- (void)setDocument:(CPDocument)aDocument
|
||||
{
|
||||
if (_document == aDocument)
|
||||
return;
|
||||
|
||||
var defaultCenter = [CPNotificationCenter defaultCenter];
|
||||
|
||||
if (_document)
|
||||
{
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPDocumentWillSaveNotification
|
||||
object:_document];
|
||||
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPDocumentDidSaveNotification
|
||||
object:_document];
|
||||
|
||||
[defaultCenter removeObserver:self
|
||||
name:CPDocumentDidFailToSaveNotification
|
||||
object:_document];
|
||||
}
|
||||
|
||||
_document = aDocument;
|
||||
|
||||
if (_document)
|
||||
{
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_documentWillSave:)
|
||||
name:CPDocumentWillSaveNotification
|
||||
object:_document];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_documentDidSave:)
|
||||
name:CPDocumentDidSaveNotification
|
||||
object:_document];
|
||||
|
||||
[defaultCenter addObserver:self
|
||||
selector:@selector(_documentDidFailToSave:)
|
||||
name:CPDocumentDidFailToSaveNotification
|
||||
object:_document];
|
||||
|
||||
[self setDocumentEdited:[_document isDocumentEdited]];
|
||||
}
|
||||
|
||||
[self synchronizeWindowTitleWithDocumentName];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_documentWillSave:(CPNotification)aNotification
|
||||
{
|
||||
[[self window] setDocumentSaving:YES];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_documentDidSave:(CPNotification)aNotification
|
||||
{
|
||||
[[self window] setDocumentSaving:NO];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_documentDidFailToSave:(CPNotification)aNotification
|
||||
{
|
||||
[[self window] setDocumentSaving:NO];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the document in the controlled window.
|
||||
*/
|
||||
- (CPDocument)document
|
||||
{
|
||||
return _document;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets whether the document has unsaved changes. The window can use this as a hint to
|
||||
@param isEdited <code>YES</code> means the document has unsaved changes.
|
||||
*/
|
||||
- (void)setDocumentEdited:(BOOL)isEdited
|
||||
{
|
||||
[[self window] setDocumentEdited:isEdited];
|
||||
}
|
||||
|
||||
// Setting and Getting Window Attributes
|
||||
|
||||
/*!
|
||||
Sets the title of the window as the name of the document.
|
||||
*/
|
||||
- (void)synchronizeWindowTitleWithDocumentName
|
||||
{
|
||||
if (!_document || !_window)
|
||||
return;
|
||||
|
||||
// [_window setRepresentedFilename:];
|
||||
[_window setTitle:[self windowTitleForDocumentDisplayName:[_document displayName]]];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the window title based on the document's name.
|
||||
@param aDisplayName the document's filename
|
||||
*/
|
||||
- (CPString)windowTitleForDocumentDisplayName:(CPString)aDisplayName
|
||||
{
|
||||
return aDisplayName;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* CPCib.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPURLConnection.j>
|
||||
@import <Foundation/CPURLRequest.j>
|
||||
|
||||
@import "_CPCibCustomObject.j"
|
||||
@import "_CPCibCustomView.j"
|
||||
@import "_CPCibKeyedUnarchiver.j"
|
||||
@import "_CPCibObjectData.j"
|
||||
@import "_CPCibWindowTemplate.j"
|
||||
|
||||
|
||||
CPCibOwner = @"CPCibOwner",
|
||||
CPCibTopLevelObjects = @"CPCibTopLevelObjects";
|
||||
|
||||
var CPCibObjectDataKey = @"CPCibObjectDataKey";
|
||||
|
||||
@implementation CPCib : CPObject
|
||||
{
|
||||
CPData _data;
|
||||
}
|
||||
|
||||
- (id)initWithContentsOfURL:(CPURL)aURL
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_data = [CPURLConnection sendSynchronousRequest:[CPURLRequest requestWithURL:aURL] returningResponse:nil error:nil];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)instantiateCibWithExternalNameTable:(CPDictionary)anExternalNameTable
|
||||
{
|
||||
var unarchiver = [[_CPCibKeyedUnarchiver alloc] initForReadingWithData:_data],
|
||||
objectData = [unarchiver decodeObjectForKey:CPCibObjectDataKey];
|
||||
|
||||
if (!objectData || ![objectData isKindOfClass:[_CPCibObjectData class]])
|
||||
return NO;
|
||||
|
||||
[objectData establishConnectionsWithExternalNameTable:anExternalNameTable];
|
||||
|
||||
var owner = [anExternalNameTable objectForKey:CPCibOwner],
|
||||
topLevelObjects = [anExternalNameTable objectForKey:CPCibTopLevelObjects];
|
||||
|
||||
var menu;
|
||||
if ((menu = [objectData mainMenu]) != nil)
|
||||
{
|
||||
[CPApp setMainMenu:menu];
|
||||
[CPMenu setMenuBarVisible:YES];
|
||||
}
|
||||
|
||||
/*
|
||||
// [objectData establishConnectionsWithOwner:owner topLevelObjects:topLevelObjects];
|
||||
// [objectData cibInstantiateWithOwner:owner topLevelObjects:topLevelObjects];
|
||||
// alert([objectData description]);
|
||||
// alert([unarchiver._archive description]);
|
||||
|
||||
NSMutableArray *t;
|
||||
NSEnumerator *e;
|
||||
id o;
|
||||
id owner;
|
||||
id rootObject;
|
||||
#if 0
|
||||
NSLog(@"instantiateCibWithExternalNameTable=%@", table);
|
||||
#endif
|
||||
if(![decoded isKindOfClass:[NSIBObjectData class]])
|
||||
return NO;
|
||||
owner=[table objectForKey:NSCibOwner];
|
||||
rootObject=[decoded rootObject];
|
||||
#if 0
|
||||
NSLog(@"establishConnections");
|
||||
#endif
|
||||
[decoded establishConnectionsWithExternalNameTable:table];
|
||||
#if 0
|
||||
NSLog(@"awakeFromCib %d objects", [decodedObjects count]);
|
||||
#endif
|
||||
#if 0
|
||||
NSLog(@"objects 2=%@", decodedObjects);
|
||||
#endif
|
||||
|
||||
// FIXME: shouldn't be accessing private variables.
|
||||
var objects = unarchiver._objects,
|
||||
count = [objects count];
|
||||
|
||||
// The order in which objects receive the awakeFromCib message is not guaranteed.
|
||||
while (count--)
|
||||
{
|
||||
var object = objects[count];
|
||||
|
||||
if ([object respondsToSelector:@selector(awakeFromCib)])
|
||||
[object awakeFromCib];
|
||||
}
|
||||
|
||||
// if ([owner respondsToSelector:@selector(awakeFromCib)])
|
||||
// [owner awakeFromCib];
|
||||
// Display visible windows.
|
||||
|
||||
return YES;*/
|
||||
}
|
||||
|
||||
- (BOOL)instantiateCibWithOwner:(id)anOwner topLevelObjects:(CPArray)topLevelObjects
|
||||
{
|
||||
[CPDictionary dictionaryWithObjectsAndKeys:anOwner, CPCibOwner, topLevelObjects, CPCibTopLevelObjects];
|
||||
return [self instantiate];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,112 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPKeyValueCoding.j>
|
||||
|
||||
|
||||
var _CPCibConnectorSourceKey = @"_CPCibConnectorSourceKey",
|
||||
_CPCibConnectorDestinationKey = @"_CPCibConnectorDestinationKey",
|
||||
_CPCibConnectorLabelKey = @"_CPCibConnectorLabelKey";
|
||||
|
||||
@implementation _CPCibConnector : CPObject
|
||||
{
|
||||
id _source;
|
||||
id _destination;
|
||||
CPString _label;
|
||||
}
|
||||
|
||||
- (void)replaceObject:(id)anObject withObject:(id)anotherObject
|
||||
{
|
||||
if (_source == anObject)
|
||||
_source = anotherObject;
|
||||
|
||||
else if (_destination == anObject)
|
||||
_destination = anotherObject;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPCibConnector (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_source = [aCoder decodeObjectForKey:_CPCibConnectorSourceKey];
|
||||
_destination = [aCoder decodeObjectForKey:_CPCibConnectorDestinationKey];
|
||||
_label = [aCoder decodeObjectForKey:_CPCibConnectorLabelKey];
|
||||
|
||||
#if DEBUG
|
||||
CPLog("Connection from " + [_source description] + " to " + [_destination description] + " and label " + _label + " decoded.");
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_source forKey:_CPCibConnectorSourceKey];
|
||||
[aCoder encodeObject:_destination forKey:_CPCibConnectorDestinationKey];
|
||||
[aCoder encodeObject:_label forKey:_CPCibConnectorLabelKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPCibControlConnector : _CPCibConnector
|
||||
{
|
||||
}
|
||||
|
||||
- (void)establishConnection
|
||||
{
|
||||
#if DEBUG
|
||||
CPLog('[' + [_source description] + " setTarget: " + [_destination description] + ']');
|
||||
CPLog('[' + [_source description] + " setAction: " + _label + ']');
|
||||
#endif
|
||||
|
||||
var selectorName = _label;
|
||||
|
||||
if (![selectorName hasSuffix:@":"])
|
||||
selectorName += ':';
|
||||
|
||||
var selector = CPSelectorFromString(selectorName);
|
||||
|
||||
if (!selector)
|
||||
[CPException
|
||||
raise:CPInvalidArgumentException
|
||||
reason:@"-[" + [self className] + ' ' + _cmd + @"] selector " + selectorName + @" does not exist."];
|
||||
|
||||
if ([_source respondsToSelector:@selector(setAction:)])
|
||||
objj_msgSend(_source, @selector(setAction:), selector);
|
||||
|
||||
else
|
||||
[CPException
|
||||
raise:CPInvalidArgumentException
|
||||
reason:@"-[" + [self className] + ' ' + _cmd + @"] " + [_source description] + " does not respond to setAction:"];
|
||||
|
||||
if ([_source respondsToSelector:@selector(setTarget:)])
|
||||
objj_msgSend(_source, @selector(setTarget:), _destination);
|
||||
|
||||
else
|
||||
[CPException
|
||||
raise:CPInvalidArgumentException
|
||||
reason:@"-[" + [self className] + ' ' + _cmd + @"] " + [_source description] + " does not respond to setTarget:"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPCibOutletConnector : _CPCibConnector
|
||||
{
|
||||
}
|
||||
|
||||
- (void)establishConnection
|
||||
{
|
||||
#if DEBUG
|
||||
CPLog([_source description] + " setValue: " + [_destination description] + " forKey: " + _label);
|
||||
#endif
|
||||
|
||||
[_source setValue:_destination forKey:_label];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,41 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
|
||||
var _CPCibCustomObjectClassName = @"_CPCibCustomObjectClassName";
|
||||
|
||||
@implementation _CPCibCustomObject : CPObject
|
||||
{
|
||||
CPString _className;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation _CPCibCustomObject (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_className = [aCoder decodeObjectForKey:_CPCibCustomObjectClassName];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_className forKey:_CPCibCustomObjectClassName];
|
||||
}
|
||||
|
||||
- (id)_cibInstantiate
|
||||
{
|
||||
var theClass = CPClassFromString(_className);
|
||||
|
||||
if (!theClass)
|
||||
CPLog("Unknown class \"" + _className + "\" in cib file");
|
||||
|
||||
return [[theClass alloc] init];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* _CPCibCustomView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CPView.j"
|
||||
|
||||
|
||||
/* @ignore */
|
||||
|
||||
@implementation _CPCibCustomView : CPView
|
||||
{
|
||||
CPString _className;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPCibCustomViewClassNameKey = @"_CPCibCustomViewClassNameKey";
|
||||
|
||||
@implementation _CPCibCustomView (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super initWithCoder:aCoder];
|
||||
|
||||
if (self)
|
||||
_className = [aCoder decodeObjectForKey:_CPCibCustomViewClassNameKey];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[super encodeWithCoder:aCoder];
|
||||
|
||||
[aCoder encodeObject:_className forKey:_CPCibCustomViewClassNameKey];
|
||||
}
|
||||
|
||||
- (id)_cibInstantiate
|
||||
{
|
||||
var theClass = CPClassFromString(_className);
|
||||
|
||||
// If we don't have this class, just use CPView.
|
||||
// FIXME: Should we instead throw an exception?
|
||||
if (!theClass)
|
||||
{
|
||||
CPLog("Unknown class \"" + _className + "\" in cib file, using CPView instead.");
|
||||
|
||||
theClass = [CPView class];
|
||||
}
|
||||
|
||||
var view = [[theClass alloc] initWithFrame:[self frame]];
|
||||
|
||||
if (view)
|
||||
{
|
||||
[view setBounds:[self bounds]];
|
||||
|
||||
// Since the object replacement logic hasn't had a chance to kick in yet, we need to do it manually:
|
||||
|
||||
// we need to copy subviews since each time we add a subview to a different view its removed from the original subviews array
|
||||
var subviews = [[self subviews] copy],
|
||||
index = 0,
|
||||
count = subviews.length;
|
||||
|
||||
for (; index < count; ++index)
|
||||
[view addSubview:subviews[index]];
|
||||
|
||||
[view setAutoresizingMask:[self autoresizingMask]];
|
||||
[view setAutoresizesSubviews:[self autoresizesSubviews]];
|
||||
|
||||
[view setHitTests:[self hitTests]];
|
||||
[view setHidden:[self isHidden]];
|
||||
[view setAlphaValue:[self alphaValue]];
|
||||
|
||||
[view setBackgroundColor:[self backgroundColor]];
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
@import <Foundation/CPKeyedUnarchiver.j>
|
||||
|
||||
|
||||
@implementation _CPCibKeyedUnarchiver : CPKeyedUnarchiver
|
||||
{
|
||||
}
|
||||
|
||||
- (id)initForReadingWithData:(CPData)data
|
||||
{
|
||||
self = [super initForReadingWithData:data];
|
||||
|
||||
if (self)
|
||||
[self setDelegate:self];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)unarchiver:(CPKeyedUnarchiver)aKeyedUnarchiver didDecodeObject:(id)anObject
|
||||
{
|
||||
if ([anObject respondsToSelector:@selector(_cibInstantiate)])
|
||||
return [anObject _cibInstantiate];
|
||||
|
||||
return anObject;
|
||||
}
|
||||
|
||||
- (void)replaceObjectAtUID:(int)aUID withObject:(id)anObject
|
||||
{
|
||||
_objects[aUID] = anObject;
|
||||
}
|
||||
|
||||
- (id)objectAtUID:(int)aUID
|
||||
{
|
||||
return _objects[aUID];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* _CPCibLoading.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPBundle.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
|
||||
var CPCibOwner = @"CPCibOwner";
|
||||
|
||||
@implementation CPObject (CPCibLoading)
|
||||
|
||||
-(void)awakeFromCib
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPBundle (CPCibLoading)
|
||||
|
||||
+ (BOOL)loadCibFile:(CPString)aPath externalNameTable:(CPDictionary)aNameTable
|
||||
{
|
||||
return [[[CPCib alloc] initWithContentsOfFile:aPath] instantiateCibWithExternalNameTable:aNameTable];
|
||||
}
|
||||
|
||||
+ (BOOL)loadCibNamed:(CPString)aName owner:(id)anOwner
|
||||
{
|
||||
var bundle = [CPBundle bundleForClass:[anOwner class]];
|
||||
|
||||
/*NSString *path;
|
||||
|
||||
path=[bundle pathForResource:name ofType:@"cib"];
|
||||
if(path==nil)
|
||||
path=[[NSBundle mainBundle] pathForResource:name ofType:@"cib"];
|
||||
|
||||
if(path==nil)
|
||||
return NO;
|
||||
*/
|
||||
var path = [bundle pathForResource:aName];
|
||||
|
||||
return [CPBundle loadCibFile:aPath externalNameTable:[CPDictionary dictionaryWithObject:anOwner forKey:CPCibOwner]];
|
||||
}
|
||||
|
||||
- (BOOL)loadCibFile:(CPString)aPath externalNameTable:(CPDictionary)aNameTable
|
||||
{
|
||||
return [[[CPCib alloc] initWithContentsOfFile:aPath] instantiateCibWithExternalNameTable:aNameTable];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* _CPCibObjectData.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CPCib.j"
|
||||
@import "_CPCibConnector.j"
|
||||
|
||||
|
||||
@implementation _CPCibObjectData : CPObject
|
||||
{
|
||||
CPArray _namesKeys;
|
||||
CPArray _namesValues;
|
||||
|
||||
CPArray _accessibilityConnectors;
|
||||
CPArray _accessibilityOidsKeys;
|
||||
CPArray _accessibilityOidsValues;
|
||||
|
||||
CPArray _classesKeys;
|
||||
CPArray _classesValues;
|
||||
|
||||
CPArray _connections;
|
||||
|
||||
id _fontManager;
|
||||
|
||||
CPString _framework;
|
||||
|
||||
int _nextOid;
|
||||
|
||||
CPArray _objectsKeys;
|
||||
CPArray _objectsValues;
|
||||
|
||||
CPArray _oidKeys;
|
||||
CPArray _oidValues;
|
||||
|
||||
_CPCibCustomObject _fileOwner;
|
||||
|
||||
CPSet _visibleWindows;
|
||||
}
|
||||
|
||||
- (CPArray)topLevelObjects
|
||||
{
|
||||
var count = [_objectsValues count],
|
||||
topLevelObjects = [];
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var eachObject = _objectsValues[count];
|
||||
|
||||
if (eachObject == _fileOwner)
|
||||
{
|
||||
var anObject = _objectsKeys[count];
|
||||
|
||||
if (anObject != _fileOwner)
|
||||
topLevelObjects.push(anObject);
|
||||
}
|
||||
}
|
||||
|
||||
return topLevelObjects;
|
||||
}
|
||||
|
||||
- (CPMenu)mainMenu
|
||||
{
|
||||
var index = [_namesValues indexOfObjectIdenticalTo:"MainMenu"];
|
||||
if (index === CPNotFound)
|
||||
return nil;
|
||||
|
||||
return _namesKeys[index];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var _CPCibObjectDataNamesKeysKey = @"_CPCibObjectDataNamesKeysKey",
|
||||
_CPCibObjectDataNamesValuesKey = @"_CPCibObjectDataNamesValuesKey",
|
||||
|
||||
_CPCibObjectDataAccessibilityConnectorsKey = @"_CPCibObjectDataAccessibilityConnectors",
|
||||
_CPCibObjectDataAccessibilityOidsKeysKey = @"_CPCibObjectDataAccessibilityOidsKeys",
|
||||
_CPCibObjectDataAccessibilityOidsValuesKey = @"_CPCibObjectDataAccessibilityOidsValues",
|
||||
|
||||
_CPCibObjectDataClassesKeysKey = @"_CPCibObjectDataClassesKeysKey",
|
||||
_CPCibObjectDataClassesValuesKey = @"_CPCibObjectDataClassesValuesKey",
|
||||
|
||||
_CPCibObjectDataConnectionsKey = @"_CPCibObjectDataConnectionsKey",
|
||||
|
||||
_CPCibObjectDataFontManagerKey = @"_CPCibObjectDataFontManagerKey",
|
||||
|
||||
_CPCibObjectDataFrameworkKey = @"_CPCibObjectDataFrameworkKey",
|
||||
|
||||
_CPCibObjectDataNextOidKey = @"_CPCibObjectDataNextOidKey",
|
||||
|
||||
_CPCibObjectDataObjectsKeysKey = @"_CPCibObjectDataObjectsKeysKey",
|
||||
_CPCibObjectDataObjectsValuesKey = @"_CPCibObjectDataObjectsValuesKey",
|
||||
|
||||
_CPCibObjectDataOidKeysKey = @"_CPCibObjectDataOidKeysKey",
|
||||
_CPCibObjectDataOidValuesKey = @"_CPCibObjectDataOidValuesKey",
|
||||
|
||||
_CPCibObjectDataFileOwnerKey = @"_CPCibObjectDataFileOwnerKey",
|
||||
_CPCibObjectDataVisibleWindowsKey = @"_CPCibObjectDataVisibleWindowsKey";
|
||||
|
||||
@implementation _CPCibObjectData (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_namesKeys = [aCoder decodeObjectForKey:_CPCibObjectDataNamesKeysKey];
|
||||
_namesValues = [aCoder decodeObjectForKey:_CPCibObjectDataNamesValuesKey];
|
||||
|
||||
//CPArray _accessibilityConnectors;
|
||||
//CPArray _accessibilityOidsKeys;
|
||||
//CPArray _accessibilityOidsValues;
|
||||
|
||||
_classesKeys = [aCoder decodeObjectForKey:_CPCibObjectDataClassesKeysKey];
|
||||
_classesValues = [aCoder decodeObjectForKey:_CPCibObjectDataClassesValuesKey];
|
||||
|
||||
_connections = [aCoder decodeObjectForKey:_CPCibObjectDataConnectionsKey];
|
||||
//id _fontManager;
|
||||
|
||||
_framework = [aCoder decodeObjectForKey:_CPCibObjectDataFrameworkKey];
|
||||
|
||||
_nextOid = [aCoder decodeIntForKey:_CPCibObjectDataNextOidKey];
|
||||
|
||||
_objectsKeys = [aCoder decodeObjectForKey:_CPCibObjectDataObjectsKeysKey];
|
||||
_objectsValues = [aCoder decodeObjectForKey:_CPCibObjectDataObjectsValuesKey];
|
||||
|
||||
_oidKeys = [aCoder decodeObjectForKey:_CPCibObjectDataOidKeysKey];
|
||||
_oidValues = [aCoder decodeObjectForKey:_CPCibObjectDataOidValuesKey];
|
||||
|
||||
_fileOwner = [aCoder decodeObjectForKey:_CPCibObjectDataFileOwnerKey];
|
||||
|
||||
// CPSet _visibleWindows;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeObject:_namesKeys forKey:_CPCibObjectDataNamesKeysKey];
|
||||
[aCoder encodeObject:_namesValues forKey:_CPCibObjectDataNamesValuesKey];
|
||||
|
||||
//CPArray _accessibilityConnectors;
|
||||
//CPArray _accessibilityOidsKeys;
|
||||
//CPArray _accessibilityOidsValues;
|
||||
|
||||
[aCoder encodeObject:_classesKeys forKey:_CPCibObjectDataClassesKeysKey];
|
||||
[aCoder encodeObject:_classesValues forKey:_CPCibObjectDataClassesValuesKey];
|
||||
|
||||
[aCoder encodeObject:_connections forKey:_CPCibObjectDataConnectionsKey];
|
||||
|
||||
//id _fontManager;
|
||||
|
||||
[aCoder encodeObject:_framework forKey:_CPCibObjectDataFrameworkKey];
|
||||
|
||||
[aCoder encodeInt:_nextOid forKey:_CPCibObjectDataNextOidKey];
|
||||
|
||||
[aCoder encodeObject:_objectsKeys forKey:_CPCibObjectDataObjectsKeysKey];
|
||||
[aCoder encodeObject:_objectsValues forKey:_CPCibObjectDataObjectsValuesKey];
|
||||
|
||||
[aCoder encodeObject:_oidKeys forKey:_CPCibObjectDataOidKeysKey];
|
||||
[aCoder encodeObject:_oidValues forKey:_CPCibObjectDataOidValuesKey];
|
||||
|
||||
[aCoder encodeObject:_fileOwner forKey:_CPCibObjectDataFileOwnerKey];
|
||||
// CPCustomObject _fileOwner;
|
||||
|
||||
// CPSet _visibleWindows;
|
||||
}
|
||||
|
||||
- (void)establishConnectionsWithExternalNameTable:(CPDictionary)anExternalNameTable
|
||||
{
|
||||
var index = 0,
|
||||
count = _connections.length,
|
||||
cibOwner = [anExternalNameTable objectForKey:CPCibOwner];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var connection = _connections[index];
|
||||
|
||||
[connection replaceObject:_fileOwner withObject:cibOwner];
|
||||
[connection establishConnection];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
|
||||
var _CPCibWindowTemplateMinSizeKey = @"_CPCibWindowTemplateMinSizeKey",
|
||||
_CPCibWindowTemplateMaxSizeKey = @"_CPCibWindowTemplateMaxSizeKey",
|
||||
|
||||
_CPCibWindowTemplateViewClassKey = @"_CPCibWindowTemplateViewClassKey",
|
||||
_CPCibWindowTemplateWindowClassKey = @"_CPCibWindowTemplateWindowClassKey",
|
||||
|
||||
_CPCibWindowTemplateWindowRectKey = @"_CPCibWindowTemplateWindowRectKey",
|
||||
_CPCibWindowTemplateWindowStyleMaskKey = @"_CPCibWindowTempatStyleMaskKey",
|
||||
_CPCibWindowTemplateWindowTitleKey = @"_CPCibWindowTemplateWindowTitleKey",
|
||||
_CPCibWindowTemplateWindowViewKey = @"_CPCibWindowTemplateWindowViewKey";
|
||||
|
||||
@implementation _CPCibWindowTemplate : CPObject
|
||||
{
|
||||
CGSize _minSize;
|
||||
CGSize _maxSize;
|
||||
//CGSize _screenRect;
|
||||
|
||||
id _viewClass;
|
||||
//unsigned _wtFlags;
|
||||
CPString _windowClass;
|
||||
CGRect _windowRect;
|
||||
unsigned _windowStyleMask;
|
||||
|
||||
CPString _windowTitle;
|
||||
CPView _windowView;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
if ([aCoder containsValueForKey:_CPCibWindowTemplateMinSizeKey])
|
||||
_minSize = [aCoder decodeSizeForKey:_CPCibWindowTemplateMinSizeKey];
|
||||
if ([aCoder containsValueForKey:_CPCibWindowTemplateMaxSizeKey])
|
||||
_maxSize = [aCoder decodeSizeForKey:_CPCibWindowTemplateMaxSizeKey];
|
||||
|
||||
_viewClass = [aCoder decodeObjectForKey:_CPCibWindowTemplateViewClassKey];
|
||||
|
||||
_windowClass = [aCoder decodeObjectForKey:_CPCibWindowTemplateWindowClassKey];
|
||||
_windowRect = [aCoder decodeRectForKey:_CPCibWindowTemplateWindowRectKey];
|
||||
_windowStyleMask = [aCoder decodeIntForKey:_CPCibWindowTemplateWindowStyleMaskKey];
|
||||
|
||||
_windowTitle = [aCoder decodeObjectForKey:_CPCibWindowTemplateWindowTitleKey];
|
||||
_windowView = [aCoder decodeObjectForKey:_CPCibWindowTemplateWindowViewKey];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (_minSize)
|
||||
[aCoder encodeSize:_minSize forKey:_CPCibWindowTemplateMinSizeKey];
|
||||
if (_maxSize)
|
||||
[aCoder encodeSize:_maxSize forKey:_CPCibWindowTemplateMaxSizeKey];
|
||||
|
||||
[aCoder encodeObject:_viewClass forKey:_CPCibWindowTemplateViewClassKey];
|
||||
|
||||
[aCoder encodeObject:_windowClass forKey:_CPCibWindowTemplateWindowClassKey];
|
||||
[aCoder encodeRect:_windowRect forKey:_CPCibWindowTemplateWindowRectKey];
|
||||
[aCoder encodeInt:_windowStyleMask forKey:_CPCibWindowTemplateWindowStyleMaskKey];
|
||||
|
||||
[aCoder encodeObject:_windowTitle forKey:_CPCibWindowTemplateWindowTitleKey];
|
||||
[aCoder encodeObject:_windowView forKey:_CPCibWindowTemplateWindowViewKey];
|
||||
}
|
||||
|
||||
- (id)_cibInstantiate
|
||||
{
|
||||
var windowClass = CPClassFromString(_windowClass);
|
||||
|
||||
/* if (!windowClass)
|
||||
[NSException raise:NSInvalidArgumentException format:@"Unable to locate NSWindow class %@, using NSWindow",_windowClass];
|
||||
class=[NSWindow class];*/
|
||||
|
||||
var theWindow = [[windowClass alloc] initWithContentRect:_windowRect styleMask:_windowStyleMask];
|
||||
|
||||
if (_minSize)
|
||||
[theWindow setMinSize:_minSize];
|
||||
if (_maxSize)
|
||||
[theWindow setMaxSize:_maxSize];
|
||||
[theWindow setLevel:CPFloatingWindowLevel];
|
||||
|
||||
//[result setHidesOnDeactivate:(_wtFlags&0x80000000)?YES:NO];
|
||||
[theWindow setTitle:_windowTitle];
|
||||
|
||||
// FIXME: we can't autoresize yet...
|
||||
[_windowView setAutoresizesSubviews:NO];
|
||||
|
||||
[theWindow setContentView:_windowView];
|
||||
|
||||
[_windowView setAutoresizesSubviews:YES];
|
||||
|
||||
if ([_viewClass isKindOfClass:[CPToolbar class]])
|
||||
{
|
||||
[theWindow setToolbar:_viewClass];
|
||||
}
|
||||
|
||||
return theWindow;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* CAAnimation.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
@import "CAMediaTimingFunction.j"
|
||||
|
||||
/*
|
||||
This is an animation class.
|
||||
*/
|
||||
@implementation CAAnimation : CPObject
|
||||
{
|
||||
BOOL _isRemovedOnCompletion;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new CAAnimation instance
|
||||
@return a new CAAnimation instance
|
||||
*/
|
||||
+ (id)animation
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
_isRemovedOnCompletion = YES;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code>
|
||||
@return <code>YES</code>
|
||||
*/
|
||||
- (void)shouldArchiveValueForKey:(CPString)aKey
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>nil</code>
|
||||
@return <code>nil</code>
|
||||
*/
|
||||
+ (id)defaultValueForKey:(CPString)aKey
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Specifies whether this animation should be removed after it has completed.
|
||||
@param <code>YES</code> means the animation should be removed
|
||||
*/
|
||||
- (void)setRemovedOnCompletion:(BOOL)isRemovedOnCompletion
|
||||
{
|
||||
_isRemovedOnCompletion = isRemovedOnCompletion;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the animation is removed after completion
|
||||
*/
|
||||
- (BOOL)removedOnCompletion
|
||||
{
|
||||
return _isRemovedOnCompletion;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the animation is removed after completion
|
||||
*/
|
||||
- (BOOL)isRemovedOnCompletion
|
||||
{
|
||||
return _isRemovedOnCompletion;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's timing function. If <code>nil</code>, then it has a linear pacing.
|
||||
*/
|
||||
- (CAMediaTimingFunction)timingFunction
|
||||
{
|
||||
// Linear Pacing
|
||||
return nil;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the animation delegate
|
||||
@param aDelegate the new delegate
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
_delegate = aDelegate;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's delegate
|
||||
*/
|
||||
- (id)delegate
|
||||
{
|
||||
return _delegate;
|
||||
}
|
||||
|
||||
- (void)runActionForKey:(CPString)aKey object:(id)anObject arguments:(CPDictionary)arguments
|
||||
{
|
||||
[anObject addAnimation:self forKey:aKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
@implementation CAPropertyAnimation : CAAnimation
|
||||
{
|
||||
CPString _keyPath;
|
||||
|
||||
BOOL _isCumulative;
|
||||
BOOL _isAdditive;
|
||||
}
|
||||
|
||||
+ (id)animationWithKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
var animation = [self animation];
|
||||
|
||||
[animation setKeypath:aKeyPath];
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
- (void)setKeyPath:(CPString)aKeyPath
|
||||
{
|
||||
_keyPath = aKeyPath;
|
||||
}
|
||||
|
||||
- (CPString)keyPath
|
||||
{
|
||||
return _keyPath;
|
||||
}
|
||||
|
||||
- (void)setCumulative:(BOOL)isCumulative
|
||||
{
|
||||
_isCumulative = isCumulative;
|
||||
}
|
||||
|
||||
- (BOOL)cumulative
|
||||
{
|
||||
return _isCumulative;
|
||||
}
|
||||
|
||||
- (BOOL)isCumulative
|
||||
{
|
||||
return _isCumulative;
|
||||
}
|
||||
|
||||
- (void)setAdditive:(BOOL)isAdditive
|
||||
{
|
||||
_isAdditive = isAdditive;
|
||||
}
|
||||
|
||||
- (BOOL)additive
|
||||
{
|
||||
return _isAdditive;
|
||||
}
|
||||
|
||||
- (BOOL)isAdditive
|
||||
{
|
||||
return _isAdditive;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
/*!
|
||||
A CABasicAnimation is a simple animation that moves a
|
||||
CALayer from one point to another over a specified
|
||||
period of time.
|
||||
*/
|
||||
@implementation CABasicAnimation : CAPropertyAnimation
|
||||
{
|
||||
id _fromValue;
|
||||
id _toValue;
|
||||
id _byValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the starting position for the animation.
|
||||
@param aValue the animation starting position
|
||||
*/
|
||||
- (void)setFromValue:(id)aValue
|
||||
{
|
||||
_fromValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's starting position.
|
||||
*/
|
||||
- (id)fromValue
|
||||
{
|
||||
return _fromValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the ending position for the animation.
|
||||
@param aValue the animation ending position
|
||||
*/
|
||||
- (void)setToValue:(id)aValue
|
||||
{
|
||||
_toValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's ending position.
|
||||
*/
|
||||
- (id)toValue
|
||||
{
|
||||
return _toValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the optional byValue for animation interpolation.
|
||||
@param aValue the byValue
|
||||
*/
|
||||
- (void)setByValue:(id)aValue
|
||||
{
|
||||
_byValue = aValue;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the animation's byValue.
|
||||
*/
|
||||
- (id)byValue
|
||||
{
|
||||
return _byValue;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* CABackingStore.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
#define PIXEL(pixels) pixels + "px";
|
||||
|
||||
|
||||
function CABackingStoreGetContext(aBackingStore)
|
||||
{
|
||||
return aBackingStore.context;
|
||||
}
|
||||
|
||||
if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
|
||||
{
|
||||
|
||||
CABackingStoreCreate = function()
|
||||
{
|
||||
var DOMElement = document.createElement("canvas");
|
||||
|
||||
DOMElement.style.position = "absolute";
|
||||
|
||||
// FIXME: Consolidate drawImage to support this.
|
||||
return { context:DOMElement.getContext("2d"), buffer:DOMElement, _image:DOMElement };
|
||||
}
|
||||
|
||||
CABackingStoreSetSize = function(aBackingStore, aSize)
|
||||
{
|
||||
var buffer = aBackingStore.buffer;
|
||||
|
||||
buffer.width = aSize.width;
|
||||
buffer.height = aSize.height;
|
||||
buffer.style.width = PIXEL(aSize.width);
|
||||
buffer.style.height = PIXEL(aSize.height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
CABackingStoreCreate = function()
|
||||
{
|
||||
var context = CGBitmapGraphicsContextCreate();
|
||||
|
||||
context.buffer = "";
|
||||
|
||||
return { context:context };
|
||||
}
|
||||
|
||||
CABackingStoreSetSize = function(aBackingStore, aSize)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* CAFlashLayer.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CALayer.j"
|
||||
@import "CPFlashMovie.j"
|
||||
|
||||
|
||||
@implementation CAFlashLayer : CALayer
|
||||
{
|
||||
CPFlashMovie _flashMovie;
|
||||
}
|
||||
|
||||
- (void)setFlashMovie:(CPFlashMovie)aFlashMovie
|
||||
{
|
||||
if (_flashMovie == aFlashMovie)
|
||||
return;
|
||||
|
||||
_flashMovie = aFlashMovie;
|
||||
|
||||
_DOMElement.innerHTML = "<object width = \"100%\" height = \"100%\"><param name = \"movie\" value = \"" +
|
||||
aFlashMovie._fileName +
|
||||
"\"></param><param name = \"wmode\" value = \"transparent\"></param><embed src = \"" +
|
||||
aFlashMovie._fileName + "\" type = \"application/x-shockwave-flash\" wmode = \"transparent\" width = \"100%\" height = \"100%\"></embed></object>";
|
||||
}
|
||||
|
||||
- (CPFlashMovie)flashMovie
|
||||
{
|
||||
return _flashMovie;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* CAMediaTimingFunction.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPDictionary.j>
|
||||
@import <Foundation/CPString.j>
|
||||
|
||||
|
||||
kCAMediaTimingFunctionLinear = @"kCAMediaTimingFunctionLinear";
|
||||
kCAMediaTimingFunctionEaseIn = @"kCAMediaTimingFunctionEaseIn";
|
||||
kCAMediaTimingFunctionEaseOut = @"kCAMediaTimingFunctionEaseOut";
|
||||
kCAMediaTimingFunctionEaseInEaseOut = @"kCAMediaTimingFunctionEaseInEaseOut";
|
||||
|
||||
var CAMediaNamedTimingFunctions = nil;
|
||||
|
||||
@implementation CAMediaTimingFunction : CPObject
|
||||
{
|
||||
float _c1x;
|
||||
float _c1y;
|
||||
float _c2x;
|
||||
float _c2y;
|
||||
}
|
||||
|
||||
+ (id)functionWithName:(CPString)aName
|
||||
{
|
||||
if (!CAMediaNamedTimingFunctions)
|
||||
{
|
||||
CAMediaNamedTimingFunctions = [CPDictionary dictionary];
|
||||
|
||||
[CAMediaNamedTimingFunctions setObject:[CAMediaTimingFunction functionWithControlPoints:0.0 :0.0 :1.0 :1.0] forKey:kCAMediaTimingFunctionLinear];
|
||||
[CAMediaNamedTimingFunctions setObject:[CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :1.0 :1.0] forKey:kCAMediaTimingFunctionEaseIn];
|
||||
[CAMediaNamedTimingFunctions setObject:[CAMediaTimingFunction functionWithControlPoints:0.0 :0.0 :0.58 :1.0] forKey:kCAMediaTimingFunctionEaseOut];
|
||||
[CAMediaNamedTimingFunctions setObject:[CAMediaTimingFunction functionWithControlPoints:0.42 :0.0 :0.58 :1.0] forKey:kCAMediaTimingFunctionEaseInEaseOut];
|
||||
}
|
||||
|
||||
return [CAMediaNamedTimingFunctions objectForKey:aName];
|
||||
}
|
||||
|
||||
+ (id)functionWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y
|
||||
{
|
||||
return [[self alloc] initWithControlPoints:c1x :c1y :c2x :c2y];
|
||||
}
|
||||
|
||||
- (id)initWithControlPoints:(float)c1x :(float)c1y :(float)c2x :(float)c2y
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_c1x = c1x;
|
||||
_c1y = c1y;
|
||||
_c2x = c2x;
|
||||
_c2y = c2y;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)getControlPointAtIndex:(unsigned)anIndex values:(float[2])reference
|
||||
{
|
||||
if (anIndex == 0)
|
||||
{
|
||||
reference[0] = 0;
|
||||
reference[1] = 0;
|
||||
}
|
||||
else if (anIndex == 1)
|
||||
{
|
||||
reference[0] = _c1x;
|
||||
reference[1] = _c1y;
|
||||
}
|
||||
else if (anIndex == 2)
|
||||
{
|
||||
reference[0] = _c2x;
|
||||
reference[1] = _c2y;
|
||||
}
|
||||
else
|
||||
{
|
||||
reference[0] = 1.0;
|
||||
reference[0] = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* CGAffineTransform.h
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "CGGeometry.h"
|
||||
|
||||
#define _CGAffineTransformMake(a_, b_, c_, d_, tx_, ty_) { a:a_, b:b_, c:c_, d:d_, tx:tx_, ty:ty_ }
|
||||
#define _CGAffineTransformMakeIdentity() _CGAffineTransformMake(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
#define _CGAffineTransformMakeCopy(aTransform) _CGAffineTransformMake(aTransform.a, aTransform.b, aTransform.c, aTransform.d, aTransform.tx, aTransform.ty)
|
||||
|
||||
#define _CGAffineTransformMakeScale(sx, sy) _CGAffineTransformMake(sx, 0.0, 0.0, sy, 0.0, 0.0)
|
||||
#define _CGAffineTransformMakeTranslation(tx, ty) _CGAffineTransformMake(1.0, 0.0, 0.0, 1.0, tx, ty)
|
||||
|
||||
#define _CGAffineTransformTranslate(aTransform, tx, ty) _CGAffineTransformMake(aTransform.a, aTransform.b, aTransform.c, aTransform.d, aTransform.tx + aTransform.a * tx + aTransform.c * ty, aTransform.ty + aTransform.b * tx + aTransform.d * ty)
|
||||
#define _CGAffineTransformScale(aTransform, sx, sy) _CGAffineTransformMake(aTransform.a * sx, aTransform.b * sx, aTransform.c * sy, aTransform.d * sy, aTransform.tx, aTransform.ty)
|
||||
|
||||
#define _CGAffineTransformConcat(lhs, rhs) _CGAffineTransformMake(lhs.a * rhs.a + lhs.b * rhs.c, lhs.a * rhs.b + lhs.b * rhs.d, lhs.c * rhs.a + lhs.d * rhs.c, lhs.c * rhs.b + lhs.d * rhs.d, lhs.tx * rhs.a + lhs.ty * rhs.c + rhs.tx, lhs.tx * rhs.b + lhs.ty * rhs.d + rhs.ty)
|
||||
|
||||
#define _CGPointApplyAffineTransform(aPoint, aTransform) _CGPointMake(aPoint.x * aTransform.a + aPoint.y * aTransform.c + aTransform.tx, aPoint.x * aTransform.b + aPoint.y * aTransform.d + aTransform.ty)
|
||||
#define _CGSizeApplyAffineTransform(aSize, aTransform) _CGSizeMake(aSize.width * aTransform.a + aSize.height * aTransform.c, aSize.width * aTransform.b + aSize.height * aTransform.d)
|
||||
#define _CGRectApplyAffineTransform(aRect, aTransform) { origin:_CGPointApplyAffineTransform(aRect.origin, aTransform), size:_CGSizeApplyAffineTransform(aRect.size, aTransform) }
|
||||
|
||||
#define _CGAffineTransformIsIdentity(aTransform) (aTransform.a == 1 && aTransform.b == 0 && aTransform.c == 0 && aTransform.d == 1 && aTransform.tx == 0 && aTransform.ty == 0)
|
||||
#define _CGAffineTransformEqualToTransform(lhs, rhs) (lhs.a == rhs.a && lhs.b == rhs.b && lhs.c == rhs.c && lhs.d == rhs.d && lhs.tx == rhs.tx && lhs.ty == rhs.ty)
|
||||
|
||||
#define _CGStringCreateWithCGAffineTransform(aTransform) (" [[ " + aTransform.a + ", " + aTransform.b + ", 0 ], [ " + aTransform.c + ", " + aTransform.d + ", 0 ], [ " + aTransform.tx + ", " + aTransform.ty + ", 1]]")
|
||||
|
||||
#define _CGAffineTransformConcatTo(lhs, rhs, to) \
|
||||
var tx = lhs.tx * rhs.a + lhs.ty * rhs.c + rhs.tx;\
|
||||
to.ty = lhs.tx * rhs.b + lhs.ty * rhs.d + rhs.ty;\
|
||||
to.tx = tx;\
|
||||
var a = lhs.a * rhs.a + lhs.b * rhs.c,\
|
||||
b = lhs.a * rhs.b + lhs.b * rhs.d,\
|
||||
c = lhs.c * rhs.a + lhs.d * rhs.c;\
|
||||
to.d = lhs.c * rhs.b + lhs.d * rhs.d;\
|
||||
to.a = a;\
|
||||
to.b = b;\
|
||||
to.c = c;\
|
||||
|
||||
/*
|
||||
a b 0 cos sin 0
|
||||
c d 0 -sin cos 0
|
||||
tx ty 1 0 0 1
|
||||
*/
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* CGAffineTransform.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "CGGeometry.h"
|
||||
#include "CGAffineTransform.h"
|
||||
|
||||
#define _function(inline) function inline { return _##inline; }
|
||||
|
||||
@import "CGGeometry.j"
|
||||
|
||||
|
||||
_function(CGAffineTransformMake(a, b, c, d, tx, ty))
|
||||
_function(CGAffineTransformMakeIdentity())
|
||||
_function(CGAffineTransformMakeCopy(anAffineTransform))
|
||||
|
||||
_function(CGAffineTransformMakeScale(sx, sy))
|
||||
_function(CGAffineTransformMakeTranslation(tx, ty))
|
||||
_function(CGAffineTransformTranslate(aTransform, tx, ty))
|
||||
_function(CGAffineTransformScale(aTransform, sx, sy))
|
||||
|
||||
_function(CGAffineTransformConcat(lhs, rhs))
|
||||
_function(CGPointApplyAffineTransform(aPoint, aTransform))
|
||||
_function(CGSizeApplyAffineTransform(aSize, aTransform))
|
||||
|
||||
_function(CGAffineTransformIsIdentity(aTransform))
|
||||
_function(CGAffineTransformEqualToTransform(lhs, rhs))
|
||||
|
||||
_function(CGStringCreateWithCGAffineTransform(aTransform))
|
||||
|
||||
/*
|
||||
FIXME: !!!!
|
||||
@return void
|
||||
@group CGAffineTransform
|
||||
*/
|
||||
function CGAffineTransformCreateCopy(aTransform)
|
||||
{
|
||||
return _CGAffineTransformMakeCopy(aTransform);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns a transform that rotates a coordinate system.
|
||||
@param anAngle the amount in radians for the transform
|
||||
to rotate a coordinate system
|
||||
@return CGAffineTransform the transform with a specified
|
||||
rotation
|
||||
@group CGAffineTransform
|
||||
*/
|
||||
function CGAffineTransformMakeRotation(anAngle)
|
||||
{
|
||||
var sin = SIN(anAngle),
|
||||
cos = COS(anAngle);
|
||||
|
||||
return _CGAffineTransformMake(cos, sin, -sin, cos, 0.0, 0.0);
|
||||
}
|
||||
|
||||
/*!
|
||||
Rotates a transform.
|
||||
@param aTransform the transform to rotate
|
||||
@param anAngle the amount to rotate in radians
|
||||
@return void
|
||||
@group CGAffineTransform
|
||||
*/
|
||||
function CGAffineTransformRotate(aTransform, anAngle)
|
||||
{
|
||||
var sin = SIN(anAngle),
|
||||
cos = COS(anAngle);
|
||||
|
||||
return {
|
||||
a:aTransform.a * cos + aTransform.c * sin,
|
||||
b:aTransform.b * cos + aTransform.d * sin,
|
||||
c:aTransform.c * cos - aTransform.a * sin,
|
||||
d:aTransform.d * cos - aTransform.b * sin,
|
||||
tx:aTransform.tx,
|
||||
ty:aTransform.ty
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
Inverts a transform.
|
||||
@param aTransform the transform to invert
|
||||
@return CGAffineTransform an inverted transform
|
||||
@group CGAffineTransform
|
||||
*/
|
||||
function CGAffineTransformInvert(aTransform)
|
||||
{
|
||||
var determinant = 1 / (aTransform.a * aTransform.d - aTransform.b * aTransform.c);
|
||||
|
||||
return {
|
||||
a:determinant * aTransform.d,
|
||||
b:-determinant * aTransform.b,
|
||||
c:-determinant * aTransform.c,
|
||||
d:determinant * aTransform.a,
|
||||
tx:determinant * (aTransform.c * aTransform.ty - aTransform.d * aTransform.tx),
|
||||
ty:determinant * (aTransform.b * aTransform.tx - aTransform.a * aTransform.ty)
|
||||
};
|
||||
}
|
||||
|
||||
/*!
|
||||
Applies a transform to the rectangle's points. The transformed rectangle
|
||||
will be the smallest box that contains the transformed points.
|
||||
@param aRect the rectangle to transform
|
||||
@param anAffineTransform the transform to apply
|
||||
@return CGRect the new transformed rectangle
|
||||
@group CGAffineTransform
|
||||
*/
|
||||
function CGRectApplyAffineTransform(aRect, anAffineTransform)
|
||||
{
|
||||
var top = _CGRectGetMinY(aRect),
|
||||
left = _CGRectGetMinX(aRect),
|
||||
right = _CGRectGetMaxX(aRect),
|
||||
bottom = _CGRectGetMaxY(aRect),
|
||||
topLeft = CGPointApplyAffineTransform(_CGPointMake(left, top), anAffineTransform),
|
||||
topRight = CGPointApplyAffineTransform(_CGPointMake(right, top), anAffineTransform),
|
||||
bottomLeft = CGPointApplyAffineTransform(_CGPointMake(left, bottom), anAffineTransform),
|
||||
bottomRight = CGPointApplyAffineTransform(_CGPointMake(right, bottom), anAffineTransform),
|
||||
minX = MIN(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x),
|
||||
maxX = MAX(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x),
|
||||
minY = MIN(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y),
|
||||
maxY = MAX(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y);
|
||||
|
||||
return _CGRectMake(minX, minY, (maxX - minX), (maxY - minY));
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates and returns a string representation of an affine transform.
|
||||
@param anAffineTransform the transform to represent as a string
|
||||
@return CPString a string describing the transform
|
||||
@group CGAffineTransform
|
||||
*/
|
||||
function CPStringFromCGAffineTransform(anAffineTransform)
|
||||
{
|
||||
return '{' + anAffineTransform.a + ", " + anAffineTransform.b + ", " + anAffineTransform.c + ", " + anAffineTransform.d + ", " + anAffineTransform.tx + ", " + anAffineTransform.ty + '}';
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* CGColor.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
//import "CGPattern.j"
|
||||
@import "CGColorSpace.j"
|
||||
|
||||
|
||||
// FIXME: Move this to Objective-J.js!!!
|
||||
var CFTypeGlobalCount = 0;
|
||||
|
||||
function CFHashCode(aCFObject)
|
||||
{
|
||||
if (!aCFObject.hash)
|
||||
aCFObject.hash = ++CFTypeGlobalCount;
|
||||
|
||||
return aCFObject;
|
||||
}
|
||||
|
||||
kCGColorWhite = "kCGColorWhite";
|
||||
kCGColorBlack = "kCGColorBlack";
|
||||
kCGColorClear = "kCGColorClear";
|
||||
|
||||
var _CGColorMap = { };
|
||||
|
||||
function CGColorGetConstantColor(aColorName)
|
||||
{
|
||||
alert("FIX ME");
|
||||
}
|
||||
|
||||
/*!
|
||||
This function is for source compatability.
|
||||
*/
|
||||
function CGColorRetain(aColor)
|
||||
{
|
||||
return aColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
This function is for source compatability.
|
||||
*/
|
||||
function CGColorRelease()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a new CGColor.
|
||||
@param aColorSpace the CGColorSpace of the color
|
||||
@param components the color's intensity values plus alpha
|
||||
@return CGColor the new color object
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorCreate(aColorSpace, components)
|
||||
{
|
||||
if (!aColorSpace || !components)
|
||||
return NULL;
|
||||
|
||||
var components = components.slice();
|
||||
|
||||
CGColorSpaceStandardizeComponents(aColorSpace, components);
|
||||
|
||||
var UID = CFHashCode(aColorSpace) + components.join("");
|
||||
|
||||
if (_CGColorMap[UID])
|
||||
return _CGColorMap[UID];
|
||||
|
||||
return _CGColorMap[UID] = { colorspace:aColorSpace, pattern:NULL, components:components };
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a copy of a color... but not really. CGColors
|
||||
are immutable, so to be efficient, this function will just
|
||||
return the same object that was passed in.
|
||||
@param aColor the CGColor to 'copy'
|
||||
@return CGColor the color copy
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorCreateCopy(aColor)
|
||||
{
|
||||
// Colors should be treated as immutable, so don't mutate it!
|
||||
return aColor;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a gray color object.
|
||||
@param gray the value to use for the color intensities (<code>0.0-1.0</code>)
|
||||
@param alpha the gray's alpha value (<code>0.0-1.0</code>)
|
||||
@return CGColor the new gray color object
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorCreateGenericGray(gray, alpha)
|
||||
{
|
||||
return CGColorCreate(0, [gray, alpha]);
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates an RGB color.
|
||||
@param red the red component (<code>0.0-1.0</code>)
|
||||
@param green the green component (<code>0.0-1.0</code>)
|
||||
@param blue the blue component (<code>0.0-1.0</code>)
|
||||
@param alpha the alpha component (<code>0.0-1.0</code>)
|
||||
@return CGColor the RGB based color
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorCreateGenericRGB(red, green, blue, alpha)
|
||||
{
|
||||
return CGColorCreate(0, [red, green, blue, alpha]);
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a CMYK color.
|
||||
@param cyan the cyan component (<code>0.0-1.0</code>)
|
||||
@param magenta the magenta component (<code>0.0-1.0</code>)
|
||||
@param yellow the yellow component (<code>0.0-1.0</code>)
|
||||
@param black the black component (<code>0.0-1.0</code>)
|
||||
@param alpha the alpha component (<code>0.0-1.0</code>)
|
||||
@return CGColor the CMYK based color
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorCreateGenericCMYK(cyan, magenta, yellow, black, alpha)
|
||||
{
|
||||
return CGColorCreate(0, [cyan, magenta, yellow, black, alpha]);
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a copy of the color with a specified alpha.
|
||||
@param aColor the color object to copy
|
||||
@param anAlpha the new alpha component for the copy (<code>0.0-1.0</code>)
|
||||
@return CGColor the new copy
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorCreateCopyWithAlpha(aColor, anAlpha)
|
||||
{
|
||||
var components = aColor.components;
|
||||
|
||||
if (!aColor || anAlpha == components[components.length - 1])
|
||||
return aColor;
|
||||
|
||||
if (aColor.pattern)
|
||||
var copy = CGColorCreateWithPattern(aColor.colorspace, aColor.pattern, components);
|
||||
else
|
||||
var copy = CGColorCreate(aColor.colorspace, components);
|
||||
|
||||
copy.components[components.length - 1] = anAlpha;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates a color using the specified pattern.
|
||||
@param aColorSpace the CGColorSpace
|
||||
@param aPattern the pattern image
|
||||
@param components the color components plus the alpha component
|
||||
@return CGColor the patterned color
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorCreateWithPattern(aColorSpace, aPattern, components)
|
||||
{
|
||||
if (!aColorSpace || !aPattern || !components)
|
||||
return NULL;
|
||||
|
||||
return { colorspace:aColorSpace, pattern:aPattern, components:components.slice() };
|
||||
}
|
||||
|
||||
/*!
|
||||
Determines if two colors are the same.
|
||||
@param lhs the first CGColor
|
||||
@param rhs the second CGColor
|
||||
@return <code>YES</code> if the two colors are equal.
|
||||
<code>NO</code> otherwise.
|
||||
*/
|
||||
function CGColorEqualToColor(lhs, rhs)
|
||||
{
|
||||
if (lhs == rhs)
|
||||
return true;
|
||||
|
||||
if (!lhs || !rhs)
|
||||
return false;
|
||||
|
||||
var lhsComponents = lhs.components,
|
||||
rhsComponents = rhs.components,
|
||||
lhsComponentCount = lhsComponents.length;
|
||||
|
||||
if (lhsComponentCount != rhsComponents.length)
|
||||
return false;
|
||||
|
||||
while (lhsComponentCount--)
|
||||
if (lhsComponents[lhsComponentCount] != rhsComponents[lhsComponentCount])
|
||||
return false;
|
||||
|
||||
if (lhs.pattern != rhs.pattern)
|
||||
return false;
|
||||
|
||||
if (CGColorSpaceEqualToColorSpace(lhs.colorspace, rhs.colorspace))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the color's alpha component.
|
||||
@param aColor the color
|
||||
@return float the alpha component (<code>0.0-1.0</code>)
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorGetAlpha(aColor)
|
||||
{
|
||||
var components = aColor.components;
|
||||
|
||||
return components[components.length - 1];
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CGColor's color space.
|
||||
@return CGColorSpace
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorGetColorSpace(aColor)
|
||||
{
|
||||
return aColor.colorspace;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the CGColor's components
|
||||
including the alpha in an array.
|
||||
@param aColor the color
|
||||
@return CPArray the color's components
|
||||
*/
|
||||
function CGColorGetComponents(aColor)
|
||||
{
|
||||
return aColor.components;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the number of color components
|
||||
(including alpha) in the specified color.
|
||||
@param aColor the CGColor
|
||||
@return CPNumber the number of components
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorGetNumberOfComponents(aColor)
|
||||
{
|
||||
return aColor.components.length;
|
||||
}
|
||||
|
||||
/*!
|
||||
Gets the CGColor's pattern.
|
||||
@param a CGColor
|
||||
@return CGPatternFIXME the pattern image
|
||||
@group CGColor
|
||||
*/
|
||||
function CGColorGetPattern(aColor)
|
||||
{
|
||||
return aColor.pattern;
|
||||
}
|
||||
|
||||
/* var components = aColor.components;
|
||||
|
||||
case : _CGCSSForColor[CFGetHash(aColor)] = "rgba(" + ROUND(components[0] * 255.0) + ',' + ROUND(components[0] * 255.0) + ',' ROUND(components[0] * 255.0) + ',' + ROUND(components[0] * 255.0);
|
||||
_cssString = (hasAlpha ? "rgba(" : "rgb(") +
|
||||
parseInt(_components[0] * 255.0) + ", " +
|
||||
parseInt(_components[1] * 255.0) + ", " +
|
||||
parseInt(_components[2] * 255.0) +
|
||||
(hasAlpha ? (", " + _components[3]) : "") + ")";
|
||||
|
||||
function CFStringFromColor()
|
||||
{
|
||||
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* CGColorSpace.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
kCGColorSpaceModelUnknown = -1;
|
||||
kCGColorSpaceModelMonochrome = 0;
|
||||
kCGColorSpaceModelRGB = 1;
|
||||
kCGColorSpaceModelCMYK = 2;
|
||||
kCGColorSpaceModelLab = 3;
|
||||
kCGColorSpaceModelDeviceN = 4;
|
||||
kCGColorSpaceModelIndexed = 5;
|
||||
kCGColorSpaceModelPattern = 6;
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CGColorSpace
|
||||
*/
|
||||
kCGColorSpaceGenericGray = "CGColorSpaceGenericGray";
|
||||
/*
|
||||
@global
|
||||
@group CGColorSpace
|
||||
*/
|
||||
kCGColorSpaceGenericRGB = "CGColorSpaceGenericRGB";
|
||||
/*
|
||||
@global
|
||||
@group CGColorSpace
|
||||
*/
|
||||
kCGColorSpaceGenericCMYK = "CGColorSpaceGenericCMYK";
|
||||
/*
|
||||
@global
|
||||
@group CGColorSpace
|
||||
*/
|
||||
kCGColorSpaceGenericRGBLinear = "CGColorSpaceGenericRGBLinear";
|
||||
/*
|
||||
@global
|
||||
@group CGColorSpace
|
||||
*/
|
||||
kCGColorSpaceGenericRGBHDR = "CGColorSpaceGenericRGBHDR";
|
||||
/*
|
||||
@global
|
||||
@group CGColorSpace
|
||||
*/
|
||||
kCGColorSpaceAdobeRGB1998 = "CGColorSpaceAdobeRGB1998";
|
||||
/*
|
||||
@global
|
||||
@group CGColorSpace
|
||||
*/
|
||||
kCGColorSpaceSRGB = "CGColorSpaceSRGB";
|
||||
|
||||
var _CGNamedColorSpaces = {};
|
||||
|
||||
#define _CGColorSpaceCreateWithModel(aModel, aComponentCount, aBaseColorSpace) \
|
||||
{ model:aModel, count:aComponentCount, base:aBaseColorSpace }
|
||||
|
||||
function CGColorSpaceCreateCalibratedGray(aWhitePoint, aBlackPoint, gamma)
|
||||
{
|
||||
return _CGColorSpaceCreateWithModel(kCGColorSpaceModelMonochrome, 1, NULL);
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateCalibratedRGB(aWhitePoint, aBlackPoint, gamma)
|
||||
{
|
||||
return _CGColorSpaceCreateWithModel(kCGColorSpaceModelRGB, 1, NULL);
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateICCBased(aComponentCount, range, profile, alternate)
|
||||
{
|
||||
// FIXME: Do we need to support this?
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateLab(aWhitePoint, aBlackPoint, aRange)
|
||||
{
|
||||
// FIXME: Do we need to support this?
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateDeviceCMYK()
|
||||
{
|
||||
return CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK);
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateDeviceGray()
|
||||
{
|
||||
return CGColorSpaceCreateWithName(kCGColorSpaceGenericGray);
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateDeviceRGB()
|
||||
{
|
||||
return CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateWithPlatformColorSpace()
|
||||
{
|
||||
// FIXME: This for sure we don't need.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateIndexed(aBaseColorSpace, lastIndex, colorTable)
|
||||
{
|
||||
// FIXME: Do we need to support this?
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function CGColorSpaceCreatePattern(aBaseColorSpace)
|
||||
{
|
||||
if (aBaseColorSpace)
|
||||
return _CGColorSpaceCreateWithModel(kCGColorSpaceModelPattern, aBaseColorSpace.count, aBaseColorSpace);
|
||||
|
||||
return _CGColorSpaceCreateWithModel(kCGColorSpaceModelPattern, 0, NULL);
|
||||
}
|
||||
|
||||
function CGColorSpaceCreateWithName(aName)
|
||||
{
|
||||
var colorSpace = _CGNamedColorSpaces[aName];
|
||||
|
||||
if (colorSpace)
|
||||
return colorSpace;
|
||||
|
||||
switch (aName)
|
||||
{
|
||||
case kCGColorSpaceGenericGray: return _CGNamedColorSpaces[aName] = _CGColorSpaceCreateWithModel(kCGColorSpaceModelMonochrome, 1, NULL);
|
||||
case kCGColorSpaceGenericRGB: return _CGNamedColorSpaces[aName] = _CGColorSpaceCreateWithModel(kCGColorSpaceModelRGB, 3, NULL);
|
||||
case kCGColorSpaceGenericCMYK: return _CGNamedColorSpaces[aName] = _CGColorSpaceCreateWithModel(kCGColorSpaceModelCMYK, 4, NULL);
|
||||
case kCGColorSpaceGenericRGBLinear: return _CGNamedColorSpaces[aName] = _CGColorSpaceCreateWithModel(kCGColorSpaceModelRGB, 3, NULL);
|
||||
case kCGColorSpaceGenericRGBHDR: return _CGNamedColorSpaces[aName] = _CGColorSpaceCreateWithModel(kCGColorSpaceModelRGB, 3, NULL);
|
||||
case kCGColorSpaceAdobeRGB1998: return _CGNamedColorSpaces[aName] = _CGColorSpaceCreateWithModel(kCGColorSpaceModelRGB, 3, NULL);
|
||||
case kCGColorSpaceSRGB: return _CGNamedColorSpaces[aName] = _CGColorSpaceCreateWithModel(kCGColorSpaceModelRGB, 3, NULL);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Getting Information About Color Spaces
|
||||
|
||||
function CGColorSpaceCopyICCProfile(aColorSpace)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function CGColorSpaceGetNumberOfComponents(aColorSpace)
|
||||
{
|
||||
return aColorSpace.count;
|
||||
}
|
||||
|
||||
function CGColorSpaceGetTypeID(aColorSpace)
|
||||
{
|
||||
}
|
||||
|
||||
function CGColorSpaceGetModel(aColorSpace)
|
||||
{
|
||||
return aColorSpace.model;
|
||||
}
|
||||
|
||||
function CGColorSpaceGetBaseColorSpace(aColorSpace)
|
||||
{
|
||||
}
|
||||
|
||||
function CGColorSpaceGetColorTableCount(aColorSpace)
|
||||
{
|
||||
}
|
||||
|
||||
function CGColorSpaceGetColorTable(aColorSpace)
|
||||
{
|
||||
}
|
||||
|
||||
// Retaining and Releasing Color Spaces
|
||||
|
||||
function CGColorSpaceRelease(aColorSpace)
|
||||
{
|
||||
}
|
||||
|
||||
function CGColorSpaceRetain(aColorSpace)
|
||||
{
|
||||
return aColorSpace;
|
||||
}
|
||||
|
||||
// FIXME: We should refer to some default values.
|
||||
#define STANDARDIZE(components, index, minimum, maximum, multiplier) \
|
||||
{ \
|
||||
if (index > components.length) \
|
||||
{ \
|
||||
components[index] = maximum; \
|
||||
return; \
|
||||
} \
|
||||
\
|
||||
var component = components[index]; \
|
||||
\
|
||||
if (component < minimum) \
|
||||
components[index] = minimum; \
|
||||
else if (component > maximum) \
|
||||
components[index] = maximum; \
|
||||
else \
|
||||
components[index] = ROUND(component * multiplier) / multiplier; \
|
||||
}
|
||||
|
||||
function CGColorSpaceStandardizeComponents(aColorSpace, components)
|
||||
{
|
||||
var count = aColorSpace.count;
|
||||
|
||||
// Standardize the alpha value. We allow the alpha value to have a
|
||||
// higher precision than other components since it is not ultimately
|
||||
// bound to 256 bits like RGB.
|
||||
STANDARDIZE(components, count, 0, 1, 1000);
|
||||
|
||||
if (aColorSpace.base)
|
||||
aColorSpace = aColorSpace.base;
|
||||
|
||||
switch (aColorSpace.model)
|
||||
{
|
||||
case kCGColorSpaceModelMonochrome:
|
||||
case kCGColorSpaceModelRGB:
|
||||
case kCGColorSpaceModelCMYK:
|
||||
case kCGColorSpaceModelDeviceN: while (count--)
|
||||
STANDARDIZE(components, count, 0, 1, 255);
|
||||
break;
|
||||
|
||||
// We don't currently support these color spaces.
|
||||
case kCGColorSpaceModelIndexed:
|
||||
case kCGColorSpaceModelLab:
|
||||
case kCGColorSpaceModelPattern: break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
/*
|
||||
* CGContext.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "CGGeometry.h"
|
||||
#include "CGAffineTransform.h"
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CGAffineTransform.j"
|
||||
@import "CGPath.j"
|
||||
|
||||
kCGLineCapButt = 0;
|
||||
kCGLineCapRound = 1;
|
||||
kCGLineCapSquare = 2;
|
||||
|
||||
kCGLineJoinMiter = 0;
|
||||
kCGLineJoinRound = 1;
|
||||
kCGLineJoinBevel = 2;
|
||||
|
||||
kCGPathFill = 0;
|
||||
kCGPathEOFill = 1;
|
||||
kCGPathStroke = 2;
|
||||
kCGPathFillStroke = 3;
|
||||
kCGPathEOFillStroke = 4;
|
||||
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeNormal = 0;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeMultiply = 1;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeScreen = 2;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeOverlay = 3;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeDarken = 4;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeLighten = 5;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeColorDodge = 6;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeColorBurn = 7;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeSoftLight = 8;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeHardLight = 9;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeDifference = 10;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeExclusion = 11;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeHue = 12;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeSaturation = 13;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeColor = 14;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeLuminosity = 15;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeClear = 16;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeCopy = 17;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeSourceIn = 18;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeSourceOut = 19;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeSourceAtop = 20;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeDestinationOver = 21;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeDestinationIn = 22;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeDestinationOut = 23;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeDestinationAtop = 24;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModeXOR = 25;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModePlusDarker = 26;
|
||||
/*
|
||||
@global
|
||||
@group CGBlendMode
|
||||
*/
|
||||
kCGBlendModePlusLighter = 27;
|
||||
|
||||
/*!
|
||||
This function is just here for source compatability.
|
||||
It does nothing.
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextRelease()
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
This function is just here for source compatability.
|
||||
It does nothing.
|
||||
@param aContext a CGContext
|
||||
@return CGContext the context
|
||||
*/
|
||||
function CGContextRetain(aContext)
|
||||
{
|
||||
return aContext;
|
||||
}
|
||||
|
||||
// BEGIN CANVAS IF
|
||||
if (!CPFeatureIsCompatible(CPHTMLCanvasFeature))
|
||||
{
|
||||
|
||||
function CGGStateCreate()
|
||||
{
|
||||
return { alpha:1.0, strokeStyle:"#000", fillStyle:"#ccc", lineWidth:1.0, lineJoin:kCGLineJoinMiter, lineCap:kCGLineCapButt, miterLimit:10.0, globalAlpha:1.0,
|
||||
blendMode:kCGBlendModeNormal,
|
||||
shadowOffset:_CGSizeMakeZero(), shadowBlur:0.0, shadowColor:NULL, CTM:_CGAffineTransformMakeIdentity() };
|
||||
}
|
||||
|
||||
function CGGStateCreateCopy(aGState)
|
||||
{
|
||||
return { alpha:aGState.alpha, strokeStyle:aGState.strokeStyle, fillStyle:aGState.fillStyle, lineWidth:aGState.lineWidth,
|
||||
lineJoin:aGState.lineJoin, lineCap:aGState.lineCap, miterLimit:aGState.miterLimit, globalAlpha:aGState.globalAlpha,
|
||||
blendMode:aGState.blendMode,
|
||||
shadowOffset:aGState.shadowOffset, shadowBlur:aGState.shadowBlur, shadowColor:aGState.shadowColor, CTM:_CGAffineTransformMakeCopy(aGState.CTM) };
|
||||
}
|
||||
|
||||
function CGBitmapGraphicsContextCreate()
|
||||
{
|
||||
return { DOMElement:document.createElement("div"), path:NULL, gState:CGGStateCreate(), gStateStack:[] };
|
||||
}
|
||||
|
||||
function CGContextSaveGState(aContext)
|
||||
{
|
||||
aContext.gStateStack.push(CGGStateCreateCopy(aContext.gState));
|
||||
}
|
||||
|
||||
function CGContextRestoreGState(aContext)
|
||||
{
|
||||
aContext.gState = aContext.gStateStack.pop();
|
||||
}
|
||||
|
||||
function CGContextSetLineCap(aContext, aLineCap)
|
||||
{
|
||||
aContext.gState.lineCap = aLineCap;
|
||||
}
|
||||
|
||||
function CGContextSetLineJoin(aContext, aLineJoin)
|
||||
{
|
||||
aContext.gState.lineJoin = aLineJoin;
|
||||
}
|
||||
|
||||
function CGContextSetLineWidth(aContext, aLineWidth)
|
||||
{
|
||||
aContext.gState.lineWidth = aLineWidth;
|
||||
}
|
||||
|
||||
function CGContextSetMiterLimit(aContext, aMiterLimit)
|
||||
{
|
||||
aContext.gState.miterLimit = aMiterLimit;
|
||||
}
|
||||
|
||||
function CGContextSetBlendMode(aContext, aBlendMode)
|
||||
{
|
||||
aContext.gState.blendMode = aBlendMode;
|
||||
}
|
||||
|
||||
function CGContextAddArc(aContext, x, y, radius, startAngle, endAngle, clockwise)
|
||||
{
|
||||
CGPathAddArc(aContext.path, aContext.gState.CTM, x, y, radius, startAngle, endAngle, clockwise);
|
||||
}
|
||||
|
||||
function CGContextAddArcToPoint(aContext, x1, y1, x2, y2, radius)
|
||||
{
|
||||
CGPathAddArcToPoint(aContext.path, aContext.gState.CTM, x1, y1, x2, y2, radius);
|
||||
}
|
||||
|
||||
function CGContextAddCurveToPoint(aContext, cp1x, cp1y, cp2x, cp2y, x, y)
|
||||
{
|
||||
CGPathAddCurveToPoint(aContext.path, aContext.gState.CTM, cp1x, cp1y, cp2x, cp2y, x, y);
|
||||
}
|
||||
|
||||
function CGContextAddLines(aContext, points, count)
|
||||
{
|
||||
CGPathAddLines(aContext.path, aContext.gState.CTM, points, count);
|
||||
}
|
||||
|
||||
function CGContextAddLineToPoint(aContext, x, y)
|
||||
{
|
||||
CGPathAddLineToPoint(aContext.path, aContext.gState.CTM, x, y);
|
||||
}
|
||||
|
||||
function CGContextAddPath(aContext, aPath)
|
||||
{
|
||||
if (!aContext || CGPathIsEmpty(aPath))
|
||||
return;
|
||||
|
||||
if (!aContext.path)
|
||||
aContext.path = CGPathCreateMutable();
|
||||
|
||||
CGPathAddPath(aContext.path, aContext.gState.CTM, aPath);
|
||||
}
|
||||
|
||||
function CGContextAddQuadCurveToPoint(aContext, cpx, cpy, x, y)
|
||||
{
|
||||
CGPathAddQuadCurveToPoint(aContext.path, aContext.gState.CTM, cpx, cpy, x, y);
|
||||
}
|
||||
|
||||
function CGContextAddRect(aContext, aRect)
|
||||
{
|
||||
CGPathAddRect(aContext.path, aContext.gState.CTM, aRect);
|
||||
}
|
||||
|
||||
function CGContextAddRects(aContext, rects, count)
|
||||
{
|
||||
CGPathAddRects(aContext.path, aContext.gState.CTM, rects, count);
|
||||
}
|
||||
|
||||
function CGContextBeginPath(aContext)
|
||||
{
|
||||
// This clears any previous path.
|
||||
aContext.path = CGPathCreateMutable();
|
||||
}
|
||||
|
||||
function CGContextClosePath(aContext)
|
||||
{
|
||||
CGPathCloseSubpath(aContext.path);
|
||||
}
|
||||
|
||||
function CGContextMoveToPoint(aContext, x, y)
|
||||
{
|
||||
if (!aContext.path)
|
||||
aContext.path = CGPathCreateMutable();
|
||||
|
||||
CGPathMoveToPoint(aContext.path, aContext.gState.CTM, x, y);
|
||||
}
|
||||
|
||||
function CGContextFillRect(aContext, aRect)
|
||||
{
|
||||
CGContextFillRects(aContext, [aRect], 1);
|
||||
}
|
||||
|
||||
function CGContextFillRects(aContext, rects, count)
|
||||
{
|
||||
if (arguments["count"] == NULL)
|
||||
var count = rects.length;
|
||||
|
||||
CGContextBeginPath(aContext);
|
||||
CGContextAddRects(aContext, rects, count);
|
||||
CGContextClosePath(aContext);
|
||||
|
||||
CGContextDrawPath(aContext, kCGPathFill);
|
||||
}
|
||||
|
||||
function CGContextStrokeRect(aContext, aRect)
|
||||
{
|
||||
CGContextBeginPath(aContext);
|
||||
CGContextAddRect(aContext, aRect);
|
||||
CGContextClosePath(aContext);
|
||||
|
||||
CGContextDrawPath(aContext, kCGPathStroke);
|
||||
}
|
||||
|
||||
function CGContextStrokeRectWithWidth(aContext, aRect, aWidth)
|
||||
{
|
||||
CGContextSaveGState(aContext);
|
||||
|
||||
CGContextSetLineWidth(aContext, aWidth);
|
||||
CGContextStrokeRect(aContext, aRect);
|
||||
|
||||
CGContextRestoreGState(aContext);
|
||||
}
|
||||
|
||||
function CGContextConcatCTM(aContext, aTransform)
|
||||
{
|
||||
var CTM = aContext.gState.CTM;
|
||||
|
||||
_CGAffineTransformConcatTo(CTM, aTransform, CTM);
|
||||
}
|
||||
|
||||
function CGContextGetCTM(aContext)
|
||||
{
|
||||
return aContext.gState.CTM;
|
||||
}
|
||||
|
||||
function CGContextRotateCTM(aContext, anAngle)
|
||||
{
|
||||
var gState = aContext.gState;
|
||||
|
||||
gState.CTM = CGAffineTransformRotate(gState.CTM, anAngle);
|
||||
}
|
||||
|
||||
function CGContextScaleCTM(aContext, sx, sy)
|
||||
{
|
||||
var gState = aContext.gState;
|
||||
|
||||
gState.CTM = _CGAffineTransformScale(gState.CTM, sx, sy);
|
||||
}
|
||||
|
||||
function CGContextTranslateCTM(aContext, tx, ty)
|
||||
{
|
||||
var gState = aContext.gState;
|
||||
|
||||
gState.CTM = _CGAffineTransformTranslate(gState.CTM, tx, ty);
|
||||
}
|
||||
|
||||
function CGContextSetShadow(aContext, aSize, aBlur)
|
||||
{
|
||||
var gState = aContext.gState;
|
||||
|
||||
gState.shadowOffset = _CGSizeMakeCopy(aSize);
|
||||
gState.shadowBlur = aBlur;
|
||||
gState.shadowColor = [CPColor shadowColor];
|
||||
}
|
||||
|
||||
function CGContextSetShadowWithColor(aContext, aSize, aBlur, aColor)
|
||||
{
|
||||
var gState = aContext.gState;
|
||||
|
||||
gState.shadowOffset = _CGSizeMakeCopy(aSize);
|
||||
gState.shadowBlur = aBlur;
|
||||
gState.shadowColor = aColor;
|
||||
}
|
||||
|
||||
function CGContextSetAlpha(aContext, anAlpha)
|
||||
{
|
||||
aContext.gState.alpha = MAX(MIN(anAlpha, 1.0), 0.0);
|
||||
}
|
||||
|
||||
} // END CANVAS IF
|
||||
|
||||
// GOOD.
|
||||
/*!
|
||||
Fills in the area of the current path, using the even-odd fill rule.
|
||||
@param aContext the CGContext of the path
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextEOFillPath(aContext)
|
||||
{
|
||||
CGContextDrawPath(aContext, kCGPathEOFill);
|
||||
}
|
||||
|
||||
/*!
|
||||
Fills in the area of the current path, using the non-zero winding number rule.
|
||||
@param aContext the CGContext of the path
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextFillPath(aContext)
|
||||
{
|
||||
CGContextDrawPath(aContext, kCGPathFill);
|
||||
}
|
||||
|
||||
var KAPPA = 4.0 * ((SQRT2 - 1.0) / 3.0);
|
||||
|
||||
/*!
|
||||
Draws the outline of an ellipse bounded by a rectangle.
|
||||
@param aContext CGContext to draw on
|
||||
@param aRect the rectangle bounding the ellipse
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextAddEllipseInRect(aContext, aRect)
|
||||
{
|
||||
CGContextBeginPath(aContext);
|
||||
|
||||
if (_CGRectGetWidth(aRect) == _CGRectGetHeight(aRect))
|
||||
CGContextAddArc(aContext, _CGRectGetMidX(aRect), _CGRectGetMidY(aRect), _CGRectGetWidth(aRect) / 2.0, 0.0, 2 * PI, YES);
|
||||
else
|
||||
{
|
||||
var axis = _CGSizeMake(_CGRectGetWidth(aRect) / 2.0, _CGRectGetHeight(aRect) / 2.0),
|
||||
center = _CGPointMake(_CGRectGetMinX(aRect) + axis.width, _CGRectGetMinY(aRect) + axis.height);
|
||||
|
||||
CGContextMoveToPoint(aContext, center.x, center.y - axis.height);
|
||||
|
||||
CGContextAddCurveToPoint(aContext, center.x + (KAPPA * axis.width), center.y - axis.height, center.x + axis.width, center.y - (KAPPA * axis.height), center.x + axis.width, center.y);
|
||||
CGContextAddCurveToPoint(aContext, center.x + axis.width, center.y + (KAPPA * axis.height), center.x + (KAPPA * axis.width), center.y + axis.height, center.x, center.y + axis.height);
|
||||
CGContextAddCurveToPoint(aContext, center.x - (KAPPA * axis.width), center.y + axis.height, center.x - axis.width, center.y + (KAPPA * axis.height), center.x - axis.width, center.y);
|
||||
CGContextAddCurveToPoint(aContext, center.x - axis.width, center.y - (KAPPA * axis.height), center.x - (KAPPA * axis.width), center.y - axis.height, center.x, center.y - axis.height);
|
||||
}
|
||||
|
||||
CGContextClosePath(aContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
Fills an ellipse bounded by a rectangle.
|
||||
@param aContext CGContext to draw on
|
||||
@param aRect the rectangle bounding the ellipse
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextFillEllipseInRect(aContext, aRect)
|
||||
{
|
||||
CGContextAddEllipseInRect(aContext, aRect);
|
||||
CGContextFillPath(aContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
Strokes an ellipse bounded by the specified rectangle.
|
||||
@param aContext CGContext to draw on
|
||||
@param aRect the rectangle bounding the ellipse
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextStrokeEllipseInRect(aContext, aRect)
|
||||
{
|
||||
CGContextAddEllipseInRect(aContext, aRect);
|
||||
CGContextStrokePath(aContext);
|
||||
}
|
||||
|
||||
/*!
|
||||
Paints a line in the current path of the current context.
|
||||
@param aContext CGContext to draw on
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextStrokePath(aContext)
|
||||
{
|
||||
CGContextDrawPath(aContext, kCGPathStroke);
|
||||
}
|
||||
|
||||
/*!
|
||||
Strokes multiple line segments.
|
||||
@param aContext CGContext to draw on
|
||||
@param points an array with an even number of points. The
|
||||
first point is the beginning of the first line segment, the second
|
||||
is the end of the first line segment. The third point is
|
||||
the beginning of second line segment, etc.
|
||||
@param count the number of points in the array
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextStrokeLineSegments(aContext, points, count)
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
if (arguments["count"] == NULL)
|
||||
var count = points.length;
|
||||
|
||||
CGContextBeginPath(aContext);
|
||||
|
||||
for (; i < count; i += 2)
|
||||
{
|
||||
CGContextMoveToPoint(aContext, points[i].x, points[i].y);
|
||||
CGContextAddLineToPoint(aContext, points[i + 1].x, points[i + 1].y);
|
||||
}
|
||||
|
||||
CGContextStrokePath(aContext);
|
||||
}
|
||||
|
||||
|
||||
//FIXME: THIS IS WRONG!!!
|
||||
|
||||
/*!
|
||||
Sets the current fill color.
|
||||
@param aContext the CGContext
|
||||
@param aColor the new color for the fill
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
|
||||
function CGContextSetFillColor(aContext, aColor)
|
||||
{
|
||||
if (aColor)
|
||||
aContext.gState.fillStyle = [aColor cssString];
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current stroke color.
|
||||
@param aContext the CGContext
|
||||
@param aColor the new color for the stroke
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextSetStrokeColor(aContext, aColor)
|
||||
{
|
||||
if (aColor)
|
||||
aContext.gState.strokeStyle = [aColor cssString];
|
||||
}
|
||||
|
||||
/*!
|
||||
Fills a rounded rectangle.
|
||||
@param aContext the CGContext to draw into
|
||||
@param aRect the base rectangle
|
||||
@param aRadius the distance from the rectange corner to the rounded corner
|
||||
@param ne set it to <code>YES</code> for a rounded northeast corner
|
||||
@param se set it to <code>YES</code> for a rounded southeast corner
|
||||
@param sw set it to <code>YES</code> for a rounded southwest corner
|
||||
@param nw set it to <code>YES</code> for a rounded northwest corner
|
||||
@return void
|
||||
@group CGContext
|
||||
*/
|
||||
function CGContextFillRoundedRectangleInRect(aContext, aRect, aRadius, ne, se, sw, nw)
|
||||
{
|
||||
var xMin = _CGRectGetMinX(aRect),
|
||||
xMax = _CGRectGetMaxX(aRect),
|
||||
yMin = _CGRectGetMinY(aRect),
|
||||
yMax = _CGRectGetMaxY(aRect);
|
||||
|
||||
CGContextBeginPath(aContext);
|
||||
CGContextMoveToPoint(aContext, xMin + aRadius, yMin);
|
||||
|
||||
if (ne)
|
||||
{
|
||||
CGContextAddLineToPoint(aContext, xMax - aRadius, yMin);
|
||||
CGContextAddCurveToPoint(aContext, xMax - aRadius, yMin, xMax, yMin, xMax, yMin + aRadius);
|
||||
}
|
||||
else
|
||||
CGContextAddLineToPoint(aContext, xMax, yMin);
|
||||
|
||||
if (se)
|
||||
{
|
||||
CGContextAddLineToPoint(aContext, xMax, yMax - aRadius);
|
||||
CGContextAddCurveToPoint(aContext, xMax, yMax - aRadius, xMax, yMax, xMax - aRadius, yMax);
|
||||
}
|
||||
else
|
||||
CGContextAddLineToPoint(aContext, xMax, yMax);
|
||||
|
||||
if (sw)
|
||||
{
|
||||
CGContextAddLineToPoint(aContext, xMin + aRadius, yMax);
|
||||
CGContextAddCurveToPoint(aContext, xMin + aRadius, yMax, xMin, yMax, xMin, yMax - aRadius);
|
||||
}
|
||||
else
|
||||
CGContextAddLineToPoint(aContext, xMin, yMax);
|
||||
|
||||
if (nw)
|
||||
{
|
||||
CGContextAddLineToPoint(aContext, xMin, yMin + aRadius);
|
||||
CGContextAddCurveToPoint(aContext, xMin, yMin + aRadius, xMin, yMin, xMin + aRadius, yMin);
|
||||
} else
|
||||
CGContextAddLineToPoint(aContext, xMin, yMin);
|
||||
|
||||
CGContextClosePath(aContext);
|
||||
|
||||
CGContextFillPath(aContext);
|
||||
}
|
||||
|
||||
if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
|
||||
{
|
||||
#include "CGContextCanvas.j"
|
||||
}
|
||||
else if (CPFeatureIsCompatible(CPVMLFeature))
|
||||
{
|
||||
#include "CGContextVML.j"
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* CGContextCanvas.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
var CANVAS_LINECAP_TABLE = [ "butt", "round", "square" ],
|
||||
CANVAS_LINEJOIN_TABLE = [ "miter", "round", "bevel" ],
|
||||
CANVAS_COMPOSITE_TABLE = [ "source-over", "source-over", "source-over", "source-over", "darker",
|
||||
"lighter", "source-over", "source-over", "source-over", "source-over",
|
||||
"source-over", "source-over", "source-over", "source-over", "source-over",
|
||||
"source-over", "source-over",
|
||||
"copy", "source-in", "source-out", "source-atop",
|
||||
"destination-over", "destination-in", "destination-out", "destination-atop",
|
||||
"xor", "source-over", "source-over" ];
|
||||
|
||||
#define _CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, anticlockwise) aContext.arc(x, y, radius, startAngle, endAngle, anticlockwise)
|
||||
#define _CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius) aContext.arcTo(x1, y1, x2, y2, radius)
|
||||
#define _CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y) aContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
|
||||
#define _CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y) aContext.quadraticCurveTo(cpx, cpy, x, y)
|
||||
#define _CGContextAddLineToPointCanvas(aContext, x, y) aContext.lineTo(x, y)
|
||||
#define _CGContextClosePathCanvas(aContext) aContext.closePath()
|
||||
#define _CGContextMoveToPointCanvas(aContext, x, y) aContext.moveTo(x, y)
|
||||
|
||||
#define _CGContextAddRectCanvas(aContext, aRect) aContext.rect(_CGRectGetMinX(aRect), _CGRectGetMinY(aRect), _CGRectGetWidth(aRect), _CGRectGetHeight(aRect))
|
||||
#define _CGContextBeginPathCanvas(aContext) aContext.beginPath()
|
||||
#define _CGContextFillRectCanvas(aContext, aRect) aContext.fillRect(_CGRectGetMinX(aRect), _CGRectGetMinY(aRect), _CGRectGetWidth(aRect), _CGRectGetHeight(aRect))
|
||||
#define _CGContextClipCanvas(aContext) aContext.clip()
|
||||
|
||||
function CGContextSaveGState(aContext)
|
||||
{
|
||||
aContext.save();
|
||||
}
|
||||
|
||||
function CGContextRestoreGState(aContext)
|
||||
{
|
||||
aContext.restore();
|
||||
}
|
||||
|
||||
function CGContextSetLineCap(aContext, aLineCap)
|
||||
{
|
||||
aContext.lineCap = CANVAS_LINECAP_TABLE[aLineCap];
|
||||
}
|
||||
|
||||
function CGContextSetLineJoin(aContext, aLineJoin)
|
||||
{
|
||||
aContext.lineJoin = CANVAS_LINEJOIN_TABLE[aLineJoin];
|
||||
}
|
||||
|
||||
function CGContextSetLineWidth(aContext, aLineWidth)
|
||||
{
|
||||
aContext.lineWidth = aLineWidth;
|
||||
}
|
||||
|
||||
function CGContextSetMiterLimit(aContext, aMiterLimit)
|
||||
{
|
||||
aContext.miterLimit = aMiterLimit;
|
||||
}
|
||||
|
||||
function CGContextSetBlendMode(aContext, aBlendMode)
|
||||
{
|
||||
aContext.globalCompositeOperation = CANVAS_COMPOSITE_TABLE[aBlendMode];
|
||||
}
|
||||
|
||||
function CGContextAddArc(aContext, x, y, radius, startAngle, endAngle, clockwise)
|
||||
{
|
||||
// Despite the documentation saying otherwise, the last parameter is anti-clockwise not clockwise.
|
||||
// http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes#Arcs
|
||||
_CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, !clockwise);
|
||||
}
|
||||
|
||||
function CGContextAddArcToPoint(aContext, x1, y1, x2, y2, radius)
|
||||
{
|
||||
_CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius);
|
||||
}
|
||||
|
||||
function CGContextAddCurveToPoint(aContext, cp1x, cp1y, cp2x, cp2y, x, y)
|
||||
{
|
||||
_CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y);
|
||||
}
|
||||
|
||||
function CGContextAddLineToPoint(aContext, x, y)
|
||||
{
|
||||
_CGContextAddLineToPointCanvas(aContext, x, y);
|
||||
}
|
||||
|
||||
function CGContextAddPath(aContext, aPath)
|
||||
{
|
||||
if (!aContext || CGPathIsEmpty(aPath))
|
||||
return;
|
||||
|
||||
var elements = aPath.elements,
|
||||
|
||||
i = 0,
|
||||
count = aPath.count;
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var element = elements[i],
|
||||
type = element.type;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case kCGPathElementMoveToPoint: _CGContextMoveToPointCanvas(aContext, element.x, element.y);
|
||||
break;
|
||||
case kCGPathElementAddLineToPoint: _CGContextAddLineToPointCanvas(aContext, element.x, element.y);
|
||||
break;
|
||||
case kCGPathElementAddQuadCurveToPoint: _CGContextAddQuadCurveToPointCanvas(aContext, element.cpx, element.cpy, element.x, element.y);
|
||||
break;
|
||||
case kCGPathElementAddCurveToPoint: _CGContextAddCurveToPointCanvas(aContext, element.cp1x, element.cp1y, element.cp2x, element.cp2y, element.x, element.y);
|
||||
break;
|
||||
case kCGPathElementCloseSubpath: _CGContextClosePathCanvas(aContext);
|
||||
break;
|
||||
case kCGPathElementAddArc: _CGContextAddArcCanvas(aContext, element.x, element.y, element.radius, element.startAngle, element.endAngle, element.clockwise);
|
||||
break;
|
||||
case kCGPathElementAddArcTo: //_CGContextAddArcToPointCanvas(aContext, element.cp1x, element.cp1.y, element.cp2.x, element.cp2y, element.radius);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function CGContextAddRect(aContext, aRect)
|
||||
{
|
||||
_CGContextAddRectCanvas(aContext, aRect);
|
||||
}
|
||||
|
||||
function CGContextAddRects(aContext, rects, count)
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
if (arguments["count"] == NULL)
|
||||
var count = rects.length;
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var rect = rects[i];
|
||||
_CGContextAddRectCanvas(aContext, rect);
|
||||
}
|
||||
}
|
||||
|
||||
function CGContextBeginPath(aContext)
|
||||
{
|
||||
_CGContextBeginPathCanvas(aContext);
|
||||
}
|
||||
|
||||
function CGContextClosePath(aContext)
|
||||
{
|
||||
_CGContextClosePathCanvas(aContext);
|
||||
}
|
||||
|
||||
function CGContextMoveToPoint(aContext, x, y)
|
||||
{
|
||||
_CGContextMoveToPointCanvas(aContext, x, y);
|
||||
}
|
||||
|
||||
function CGContextClearRect(aContext, aRect)
|
||||
{
|
||||
aContext.clearRect(_CGRectGetMinX(aRect), _CGRectGetMinY(aRect), _CGRectGetWidth(aRect), _CGRectGetHeight(aRect));
|
||||
}
|
||||
|
||||
function CGContextDrawPath(aContext, aMode)
|
||||
{
|
||||
if (aMode == kCGPathFill || aMode == kCGPathFillStroke)
|
||||
aContext.fill();
|
||||
else if (aMode == kCGPathEOFill || aMode == kCGPathEOFillStroke)
|
||||
alert("not implemented!!!");
|
||||
|
||||
if (aMode == kCGPathStroke || aMode == kCGPathFillStroke || aMode == kCGPathEOFillStroke)
|
||||
aContext.stroke();
|
||||
}
|
||||
|
||||
function CGContextFillRect(aContext, aRect)
|
||||
{
|
||||
_CGContextFillRectCanvas(aContext, aRect);
|
||||
}
|
||||
|
||||
function CGContextFillRects(aContext, rects, count)
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
if (arguments["count"] == NULL)
|
||||
var count = rects.length;
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var rect = rects[i];
|
||||
_CGContextFillRectCanvas(aContext, rect);
|
||||
}
|
||||
}
|
||||
|
||||
function CGContextStrokeRect(aContext, aRect)
|
||||
{
|
||||
aContext.strokeRect(_CGRectGetMinX(aRect), _CGRectGetMinY(aRect), _CGRectGetWidth(aRect), _CGRectGetHeight(aRect));
|
||||
}
|
||||
|
||||
function CGContextClip(aContext)
|
||||
{
|
||||
_CGContextClipCanvas(aContext);
|
||||
}
|
||||
|
||||
function CGContextClipToRect(aContext, aRect)
|
||||
{
|
||||
_CGContextBeginPathCanvas(aContext);
|
||||
_CGContextAddRectCanvas(aContext, aRect);
|
||||
_CGContextClosePathCanvas(aContext);
|
||||
|
||||
_CGContextClipCanvas(aContext);
|
||||
}
|
||||
|
||||
function CGContextClipToRects(aContext, rects, count)
|
||||
{
|
||||
if (arguments["count"] == NULL)
|
||||
var count = rects.length;
|
||||
|
||||
_CGContextBeginPathCanvas(aContext);
|
||||
CGContextAddRects(aContext, rects, count);
|
||||
_CGContextClipCanvas(aContext);
|
||||
}
|
||||
|
||||
function CGContextSetAlpha(aContext, anAlpha)
|
||||
{
|
||||
aContext.globalAlpha = anAlpha;
|
||||
}
|
||||
|
||||
function CGContextSetFillColor(aContext, aColor)
|
||||
{
|
||||
aContext.fillStyle = [aColor cssString];
|
||||
}
|
||||
|
||||
function CGContextSetStrokeColor(aContext, aColor)
|
||||
{
|
||||
aContext.strokeStyle = [aColor cssString];
|
||||
}
|
||||
|
||||
function CGContextSetShadow(aContext, aSize, aBlur)
|
||||
{
|
||||
aContext.shadowOffsetX = aSize.width;
|
||||
aContext.shadowOffsetY = aSize.height;
|
||||
aContext.shadowBlur = aBlur;
|
||||
}
|
||||
|
||||
function CGContextSetShadowWithColor(aContext, aSize, aBlur, aColor)
|
||||
{
|
||||
aContext.shadowOffsetX = aSize.width;
|
||||
aContext.shadowOffsetY = aSize.height;
|
||||
aContext.shadowBlur = aBlur;
|
||||
aContext.shadowColor = [aColor cssString];
|
||||
}
|
||||
|
||||
function CGContextRotateCTM(aContext, anAngle)
|
||||
{
|
||||
aContext.rotate(anAngle);
|
||||
}
|
||||
|
||||
function CGContextScaleCTM(aContext, sx, sy)
|
||||
{
|
||||
aContext.scale(sx, sy);
|
||||
}
|
||||
|
||||
function CGContextTranslateCTM(aContext, tx, ty)
|
||||
{
|
||||
aContext.translate(tx, ty);
|
||||
}
|
||||
|
||||
#define scale_rotate(a, b, c, d) \
|
||||
var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0, \
|
||||
a2 = (ATAN2(b, d) + ATAN2(-sign * c, sign * a)) / 2.0, \
|
||||
cos = COS(a2),\
|
||||
sin = SIN(a2);\
|
||||
\
|
||||
if (cos == 0)\
|
||||
{\
|
||||
sx = -c / sin;\
|
||||
sy = b / sin;\
|
||||
}\
|
||||
else if (sin == 0)\
|
||||
{\
|
||||
sx = a / cos;\
|
||||
sy = d / cos;\
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
abs_cos = ABS(cos);\
|
||||
abs_sin = ABS(sin);\
|
||||
\
|
||||
sx = (abs_cos * a / cos + abs_sin * -c / sin) / (abs_cos + abs_sin);\
|
||||
sy = (abs_cos * d / cos + abs_sin * b / sin) / (abs_cos + abs_sin);\
|
||||
}\
|
||||
|
||||
#define rotate_scale(a, b, c, d) \
|
||||
var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0;\
|
||||
a1 = (Math.atan2(sign * b, sign * a) + Math.atan2(-c, d)) / 2.0,\
|
||||
cos = COS(a1),\
|
||||
sin = SIN(a1);\
|
||||
\
|
||||
if (cos == 0)\
|
||||
{\
|
||||
sx = b / sin;\
|
||||
sy = -c / sin;\
|
||||
}\
|
||||
else if (sin == 0)\
|
||||
{\
|
||||
sx = a / cos;\
|
||||
sy = d / cos;\
|
||||
}\
|
||||
else\
|
||||
{\
|
||||
abs_cos = ABS(cos);\
|
||||
abs_sin = ABS(sin);\
|
||||
\
|
||||
sx = (abs_cos * a / cos + abs_sin * b / sin) / (abs_cos + abs_sin);\
|
||||
sy = (abs_cos * d / cos + abs_sin * -c / sin) / (abs_cos + abs_sin);\
|
||||
}\
|
||||
|
||||
function eigen(anAffineTransform)
|
||||
{
|
||||
alert("IMPLEMENT ME!");
|
||||
}
|
||||
|
||||
|
||||
if (CPFeatureIsCompatible(CPJavaScriptCanvasTransformFeature))
|
||||
{
|
||||
|
||||
CGContextConcatCTM = function(aContext, anAffineTransform)
|
||||
{
|
||||
aContext.transform(anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
CGContextConcatCTM = function(aContext, anAffineTransform)
|
||||
{
|
||||
var a = anAffineTransform.a,
|
||||
b = anAffineTransform.b,
|
||||
c = anAffineTransform.c,
|
||||
d = anAffineTransform.d,
|
||||
tx = anAffineTransform.tx,
|
||||
ty = anAffineTransform.ty,
|
||||
sx = 1.0,
|
||||
sy = 1.0,
|
||||
a1 = 0.0,
|
||||
a2 = 0.0;
|
||||
|
||||
// Detect the simple case of just scaling.
|
||||
if (b == 0.0 && c == 0.0)
|
||||
{
|
||||
sx = a;
|
||||
sy = d;
|
||||
}
|
||||
|
||||
// a scale followed by a rotate
|
||||
else if (a * b == -c * d)
|
||||
{
|
||||
scale_rotate(a, b, c, d)
|
||||
}
|
||||
|
||||
// rotate, then scale.
|
||||
else if (a * c == -b * d)
|
||||
{
|
||||
rotate_scale(a, b, c, d)
|
||||
}
|
||||
else
|
||||
{
|
||||
var transpose = CGAffineTransformMake(a, c, b, d, 0.0, 0.0), // inline
|
||||
u = eigen(CGAffineTransformConcat(anAffineTransform, transpose)),
|
||||
v = eigen(CGAffineTransformConcat(transpose, anAffineTransform)),
|
||||
U = CGAffineTransformMake(u.vector_1.x, u.vector_2.x, u.vector_1.y, u.vector_2.y, 0.0, 0.0), // inline
|
||||
VT = CGAffineTransformMake(v.vector_1.x, v.vector_1.y, v.vector_2.x, v.vector_2.y, 0.0, 0.0),
|
||||
S = CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformInvert(U), anAffineTransform), CGAffineTransformInvert(VT));
|
||||
|
||||
a = VT.a;
|
||||
b = VT.b;
|
||||
c = VT.c;
|
||||
d = VT.d;
|
||||
scale_rotate(a, b, c, d)
|
||||
S.a *= sx;
|
||||
S.d *= sy;
|
||||
a = U.a;
|
||||
b = U.b;
|
||||
c = U.c;
|
||||
d = U.d;
|
||||
rotate_scale(a, b, c, d)
|
||||
sx = S.a * sx;
|
||||
sy = S.d * sy;
|
||||
}
|
||||
|
||||
if (tx != 0 || ty != 0)
|
||||
CGContextTranslateCTM(aContext, tx, ty);
|
||||
if (a1 != 0.0)
|
||||
CGContextRotateCTM(aContext, a1);
|
||||
if (sx != 1.0 || sy != 1.0)
|
||||
CGContextScaleCTM(aContext, sx, sy);
|
||||
if (a2 != 0.0)
|
||||
CGContextRotateCTM(aContext, a2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function CGContextDrawImage(aContext, aRect, anImage)
|
||||
{
|
||||
aContext.drawImage(anImage._image, _CGRectGetMinX(aRect), _CGRectGetMinY(aRect), _CGRectGetWidth(aRect), _CGRectGetHeight(aRect));
|
||||
}
|
||||
|
||||
function to_string(aColor)
|
||||
{
|
||||
return "rgba(" + ROUND(aColor.components[0] * 255) + ", " + ROUND(aColor.components[1] * 255) + ", " + ROUND(255 * aColor.components[2]) + ", " + aColor.components[3] + ")";
|
||||
}
|
||||
|
||||
function CGContextDrawLinearGradient(aContext, aGradient, aStartPoint, anEndPoint, options)
|
||||
{
|
||||
var colors = aGradient.colors,
|
||||
count = colors.length,
|
||||
|
||||
linearGradient = aContext.createLinearGradient(aStartPoint.x, aStartPoint.y, anEndPoint.x, anEndPoint.y);
|
||||
|
||||
while (count--)
|
||||
linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count]));
|
||||
|
||||
aContext.fillStyle = linearGradient;
|
||||
aContext.fill();
|
||||
}
|
||||
|
||||
function CGBitmapGraphicsContextCreate()
|
||||
{
|
||||
var DOMElement = document.createElement("canvas"),
|
||||
context = DOMElement.getContext("2d");
|
||||
|
||||
context.DOMElement = DOMElement;
|
||||
|
||||
return context;
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* CGContextVML.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
var VML_TRUTH_TABLE = [ "f", "t"],
|
||||
VML_LINECAP_TABLE = [ "flat", "round", "square" ],
|
||||
VML_LINEJOIN_TABLE = [ "miter", "round", "bevel" ],
|
||||
VML_ELEMENT_TABLE = [ " m ", " l ", "qb", " c ", " x ", [" at ", " wa "]];
|
||||
|
||||
var _CGBitmapGraphicsContextCreate = CGBitmapGraphicsContextCreate;
|
||||
|
||||
function CGBitmapGraphicsContextCreate()
|
||||
{
|
||||
// The first time around, we have to set up our environment to support vml.
|
||||
document.namespaces.add("cg_vml_", "urn:schemas-microsoft-com:vml");
|
||||
document.createStyleSheet().cssText = "cg_vml_\\:*{behavior:url(#default#VML)}";
|
||||
|
||||
CGBitmapGraphicsContextCreate = _CGBitmapGraphicsContextCreate;
|
||||
|
||||
return _CGBitmapGraphicsContextCreate();
|
||||
}
|
||||
|
||||
// FIXME: aRect is ignored.
|
||||
function CGContextClearRect(aContext, aRect)
|
||||
{
|
||||
if (aContext.buffer != nil)
|
||||
aContext.buffer = "";
|
||||
else
|
||||
aContext.DOMElement.innerHTML = "";
|
||||
|
||||
aContext.path = NULL;
|
||||
}
|
||||
|
||||
var W = 10.0,
|
||||
H = 10.0,
|
||||
Z = 10.0,
|
||||
Z_2 = Z / 2.0;
|
||||
|
||||
#define COORD(aCoordinate) (ROUND(Z * (aCoordinate) - Z_2))
|
||||
|
||||
function CGContextDrawImage(aContext, aRect, anImage)
|
||||
{
|
||||
var string = "";
|
||||
|
||||
if (anImage.buffer != nil)
|
||||
string = anImage.buffer;
|
||||
else
|
||||
{
|
||||
var ctm = aContext.gState.CTM,
|
||||
origin = CGPointApplyAffineTransform(aRect.origin, ctm),
|
||||
similarity = ctm.a == ctm.d && ctm.b == -ctm.c,
|
||||
vml = ["<cg_vml_:group coordsize=\"1,1\" coordorigin=\"0,0\" style=\"width:1;height:1;position:absolute"];
|
||||
|
||||
/*if (similarity)
|
||||
{
|
||||
var angle = CGPointMake(1.0, 0.0);
|
||||
|
||||
angle = _CGPointApplyAffineTransform(angle, ctm);
|
||||
|
||||
vml.push(";rotation:", ATAN2(angle.y - ctm.ty, angle.x - ctm.tx) * 180 / PI);
|
||||
}*/
|
||||
|
||||
// Only create a filter if absolutely necessary. This actually
|
||||
// turns out to only be the case if our transform matrix is not
|
||||
// a similarity matrix, that is to say, the transform actually
|
||||
// morphs the shape beyond scaling and rotation.
|
||||
//if (!similarity && (ctm.a != 1.0 || ctm.b || ctm.c || ctm.d != 1.0))
|
||||
{
|
||||
var transformedRect = CGRectApplyAffineTransform(aRect, ctm);
|
||||
|
||||
vml.push( ";padding:0 ", ROUND(_CGRectGetMaxX(transformedRect)), "px ", ROUND(_CGRectGetMaxY(transformedRect)),
|
||||
"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
|
||||
"M11='", ctm.a, "',M12='", ctm.c, "',M21='", ctm.b, "',M22='", ctm.d, "',",
|
||||
"Dx='", ROUND(origin.x), "', Dy='", ROUND(origin.y), "', sizingmethod='clip');");
|
||||
}
|
||||
//else
|
||||
// vml.push(";top:", ROUND(origin.y - 0.5), "px;left:", ROUND(origin.x - 0.5), "px;");
|
||||
|
||||
vml.push( "\"><cg_vml_:image src=\"", anImage._image.src,
|
||||
"\" style=\"width:", _CGRectGetWidth(aRect), "px;height:", _CGRectGetHeight(aRect),
|
||||
"px;\"/></g_vml_:group>");
|
||||
|
||||
string = vml.join("");
|
||||
}
|
||||
|
||||
if (aContext.buffer != nil)
|
||||
aContext.buffer += string;
|
||||
else
|
||||
aContext.DOMElement.insertAdjacentHTML("BeforeEnd", string);
|
||||
}
|
||||
|
||||
function CGContextDrawPath(aContext, aMode)
|
||||
{
|
||||
if (!aContext || CGPathIsEmpty(aContext.path))
|
||||
return;
|
||||
|
||||
var elements = aContext.path.elements,
|
||||
|
||||
i = 0,
|
||||
count = aContext.path.count,
|
||||
|
||||
gState = aContext.gState,
|
||||
fill = (aMode == kCGPathFill || aMode == kCGPathFillStroke) ? 1 : 0,
|
||||
stroke = (aMode == kCGPathStroke || aMode == kCGPathFillStroke) ? 1 : 0,
|
||||
opacity = gState.alpha,
|
||||
vml = [ "<cg_vml_:shape",
|
||||
" fillcolor=\"", gState.fillStyle,
|
||||
"\" filled=\"", VML_TRUTH_TABLE[fill],
|
||||
"\" style=\"position:absolute;width:", W, ";height:", H,
|
||||
";\" coordorigin=\"0 0\" coordsize=\"", Z * W, " ", Z * H,
|
||||
"\" stroked=\"", VML_TRUTH_TABLE[stroke],
|
||||
"\" strokeweight=\"", gState.lineWidth,
|
||||
"\" strokecolor=\"", gState.strokeStyle,
|
||||
"\" path=\""];
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var element = elements[i],
|
||||
type = element.type;
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case kCGPathElementMoveToPoint:
|
||||
case kCGPathElementAddLineToPoint: vml.push(VML_ELEMENT_TABLE[type], COORD(element.x), ',', COORD(element.y));
|
||||
break;
|
||||
|
||||
case kCGPathElementAddQuadCurveToPoint: vml.push(VML_ELEMENT_TABLE[type],
|
||||
COORD(element.cpx), ',', COORD(element.cpy), ',',
|
||||
COORD(element.x), ',', COORD(element.y));
|
||||
break;
|
||||
|
||||
case kCGPathElementAddCurveToPoint: vml.push(VML_ELEMENT_TABLE[type],
|
||||
COORD(element.cp1x), ',', COORD(element.cp1y), ',',
|
||||
COORD(element.cp2x), ',', COORD(element.cp2y), ',',
|
||||
COORD(element.x), ',', COORD(element.y));
|
||||
break;
|
||||
|
||||
case kCGPathElementCloseSubpath: vml.push(VML_ELEMENT_TABLE[type]);
|
||||
break;
|
||||
|
||||
case kCGPathElementAddArc: var x = element.x,
|
||||
y = element.y,
|
||||
radius = element.radius,
|
||||
clockwise = element.clockwise ? 1 : 0,
|
||||
endAngle = element.endAngle,
|
||||
startAngle = element.startAngle,
|
||||
|
||||
start = _CGPointMake(x + radius * COS(startAngle), y + radius * SIN(startAngle));
|
||||
|
||||
// If the angle's are equal, then we won't actually draw an arc, but instead
|
||||
// simply move to its start/end to get the proper fill.
|
||||
// We only need this special case for anti-clockwise because start == end is
|
||||
// interpreted as a full circle with anti-clockwise, but empty for clockwise.
|
||||
if (startAngle == endAngle && !clockwise)
|
||||
{
|
||||
vml.push(VML_ELEMENT_TABLE[kCGPathElementMoveToPoint], COORD(start.x), ',', COORD(start.y));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var end = _CGPointMake(x + radius * COS(endAngle), y + radius * SIN(endAngle));
|
||||
|
||||
// Only do the start correction if the angles aren't equal. If they are, then
|
||||
// let the circle be empty.
|
||||
// FIXME: Should this be |star.x - end.x| < 0.125 ?
|
||||
if (clockwise && startAngle != endAngle && _CGPointEqualToPoint(start, end))
|
||||
if (start.x >= x)
|
||||
{
|
||||
if (start.y < y)
|
||||
start.x += 0.125;
|
||||
else
|
||||
start.y += 0.125;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (end.y <= y)
|
||||
end.x += 0.125;
|
||||
else
|
||||
end.y += 0.125;
|
||||
}
|
||||
|
||||
vml.push(VML_ELEMENT_TABLE[type][clockwise],
|
||||
COORD(x - radius), ',', COORD(y - radius), " ",
|
||||
COORD(x + radius), ',', COORD(y + radius), " ",
|
||||
COORD(start.x), ',', COORD(start.y), " ",
|
||||
COORD(end.x), ',', COORD(end.y));
|
||||
break;
|
||||
case kCGPathElementAddArcTo: break;
|
||||
}
|
||||
|
||||
// TODO: Following is broken for curves due to
|
||||
// move to proper paths.
|
||||
|
||||
// Figure out dimensions so we can do gradient fills
|
||||
// properly
|
||||
/*if(c) {
|
||||
if (min.x == null || c.x < min.x) {
|
||||
min.x = c.x;
|
||||
}
|
||||
if (max.x == null || c.x > max.x) {
|
||||
max.x = c.x;
|
||||
}
|
||||
if (min.y == null || c.y < min.y) {
|
||||
min.y = c.y;
|
||||
}
|
||||
if (max.y == null || c.y > max.y) {
|
||||
max.y = c.y;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
vml.push("\">");
|
||||
|
||||
if (fill)
|
||||
vml.push("<cg_vml_:fill color=\"", gState.fillStyle, "\" opacity=\"", opacity, "\" />");
|
||||
|
||||
if (stroke)
|
||||
vml.push( "<cg_vml_:stroke opacity=\"", opacity,
|
||||
"\" joinstyle=\"", VML_LINEJOIN_TABLE[gState.lineJoin],
|
||||
"\" miterlimit=\"", gState.miterLimit,
|
||||
"\" endcap=\"", VML_LINECAP_TABLE[gState.lineCap],
|
||||
"\" weight=\"", gState.lineWidth, "",
|
||||
"px\" color=\"", gState.strokeStyle,"\" />");
|
||||
|
||||
var shadowColor = gState.shadowColor;
|
||||
//\"", [shadowColor cssString], "\"
|
||||
if (shadowColor)
|
||||
{
|
||||
var shadowOffset = gState.shadowOffset;
|
||||
|
||||
vml.push("<cg_vml_:shadow on=\"t\" offset=\"",
|
||||
shadowOffset.width, "pt ", shadowOffset.height, "pt\" opacity=\"", [shadowColor alphaComponent], "\" color=black />");
|
||||
}
|
||||
|
||||
vml.push("</cg_vml_:shape>");
|
||||
|
||||
aContext.path = NULL;
|
||||
|
||||
if (aContext.buffer != nil)
|
||||
aContext.buffer += vml.join("");
|
||||
else
|
||||
aContext.DOMElement.insertAdjacentHTML("BeforeEnd", vml.join(""));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* CGGeometry.h
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define _CGPointMake(x_, y_) { x:x_, y:y_ }
|
||||
#define _CGPointMakeCopy(aPoint) _CGPointMake(aPoint.x, aPoint.y)
|
||||
#define _CGPointMakeZero() _CGPointMake(0.0, 0.0)
|
||||
|
||||
#define _CGPointEqualToPoint(lhsPoint, rhsPoint) (lhsPoint.x == rhsPoint.x && lhsPoint.y == rhsPoint.y)
|
||||
#define _CGStringFromPoint(aPoint) ("{" + aPoint.x + ", " + aPoint.y + "}")
|
||||
|
||||
#define _CGSizeMake(width_, height_) { width:width_, height:height_ }
|
||||
#define _CGSizeMakeCopy(aSize) _CGSizeMake(aSize.width, aSize.height)
|
||||
#define _CGSizeMakeZero() _CGSizeMake(0.0, 0.0)
|
||||
|
||||
#define _CGSizeEqualToSize(lhsSize, rhsSize) (lhsSize.width == rhsSize.width && lhsSize.height == rhsSize.height)
|
||||
#define _CGStringFromSize(aSize) ("{" + aSize.width + ", " + aSize.height + "}")
|
||||
|
||||
#define _CGRectMake(x, y, width, height) { origin: _CGPointMake(x, y), size: _CGSizeMake(width, height) }
|
||||
#define _CGRectMakeZero() _CGRectMake(0.0, 0.0, 0.0, 0.0)
|
||||
#define _CGRectMakeCopy(aRect) _CGRectMake(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height)
|
||||
#define _CGRectCreateCopy(aRect) _CGRectMake(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height)
|
||||
|
||||
#define _CGRectEqualToRect(lhsRect, rhsRect) (_CGPointEqualToPoint(lhsRect.origin, rhsRect.origin) && _CGSizeEqualToSize(lhsRect.size, rhsRect.size))
|
||||
#define _CGStringFromRect(aRect) ("{" + _CGStringFromPoint(aRect.origin) + ", " + _CGStringFromSize(aRect.size) + "}")
|
||||
|
||||
#define _CGRectOffset(aRect, dX, dY) _CGRectMake(aRect.origin.x + dX, aRect.origin.y + dY, aRect.size.width, aRect.size.height)
|
||||
#define _CGRectInset(aRect, dX, dY) _CGRectMake(aRect.origin.x + dX, aRect.origin.y + dY, aRect.size.width - 2 * dX, aRect.size.height - 2 * dY)
|
||||
|
||||
#define _CGRectGetHeight(aRect) (aRect.size.height)
|
||||
#define _CGRectGetMaxX(aRect) (aRect.origin.x + aRect.size.width)
|
||||
#define _CGRectGetMaxY(aRect) (aRect.origin.y + aRect.size.height)
|
||||
#define _CGRectGetMidX(aRect) (aRect.origin.x + (aRect.size.width) / 2.0)
|
||||
#define _CGRectGetMidY(aRect) (aRect.origin.y + (aRect.size.height) / 2.0)
|
||||
#define _CGRectGetMinX(aRect) (aRect.origin.x)
|
||||
#define _CGRectGetMinY(aRect) (aRect.origin.y)
|
||||
#define _CGRectGetWidth(aRect) (aRect.size.width)
|
||||
|
||||
#define _CGRectIsEmpty(aRect) (aRect.size.width <= 0.0 || aRect.size.height <= 0.0)
|
||||
#define _CGRectIsNull(aRect) (aRect.size.width <= 0.0 || aRect.size.height <= 0.0)
|
||||
|
||||
#define _CGRectContainsPoint(aRect, aPoint) (aPoint.x >= _CGRectGetMinX(aRect) && aPoint.y >= _CGRectGetMinY(aRect) && aPoint.x < _CGRectGetMaxX(aRect) && aPoint.y < _CGRectGetMaxY(aRect))
|
||||
|
||||
// DEPRECATED
|
||||
#define _CGPointCreateCopy(aPoint) _CGPointMake(aPoint.x, aPoint.y)
|
||||
#define _CGSizeCreateCopy(aSize) _CGSizeMake(aSize.width, aSize.height)
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* CGGeometry.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "CGGeometry.h"
|
||||
|
||||
#define _function(inline) function inline { return _##inline; }
|
||||
|
||||
_function(CGPointMake(x, y))
|
||||
_function(CGPointMakeZero())
|
||||
_function(CGPointMakeCopy(aPoint))
|
||||
_function(CGPointCreateCopy(aPoint))
|
||||
|
||||
_function(CGPointEqualToPoint(lhsPoint, rhsPoint))
|
||||
_function(CGStringFromPoint(aPoint))
|
||||
|
||||
_function(CGSizeMake(width, height))
|
||||
_function(CGSizeMakeZero())
|
||||
_function(CGSizeMakeCopy(aSize))
|
||||
_function(CGSizeCreateCopy(aSize))
|
||||
|
||||
_function(CGSizeEqualToSize(lhsSize, rhsSize))
|
||||
_function(CGStringFromSize(aSize))
|
||||
|
||||
_function(CGRectMake(x, y, width, height))
|
||||
_function(CGRectMakeZero())
|
||||
_function(CGRectMakeCopy(aRect))
|
||||
_function(CGRectCreateCopy(aRect))
|
||||
|
||||
_function(CGRectEqualToRect(lhsRect, rhsRect))
|
||||
_function(CGStringFromRect(aRect))
|
||||
|
||||
_function(CGRectOffset(aRect, dX, dY))
|
||||
_function(CGRectInset(aRect, dX, dY))
|
||||
|
||||
_function(CGRectGetHeight(aRect))
|
||||
_function(CGRectGetMaxX(aRect))
|
||||
_function(CGRectGetMaxY(aRect))
|
||||
_function(CGRectGetMidX(aRect))
|
||||
_function(CGRectGetMidY(aRect))
|
||||
_function(CGRectGetMinX(aRect))
|
||||
_function(CGRectGetMinY(aRect))
|
||||
_function(CGRectGetWidth(aRect))
|
||||
|
||||
_function(CGRectIsEmpty(aRect))
|
||||
_function(CGRectIsNull(aRect))
|
||||
|
||||
_function(CGRectContainsPoint(aRect, aPoint))
|
||||
|
||||
/*!
|
||||
Returns a <code>BOOL</code> indicating whether CGRect <code>lhsRect</code>
|
||||
contains CGRect <code>rhsRect</code>.
|
||||
@group CGRect
|
||||
@param lhsRect the CGRect to test if <code>rhsRect</code> is inside of
|
||||
@param rhsRect the CGRect to test if it fits inside <code>lhsRect</code>.
|
||||
@return BOOL <code>YES</code> if <code>rhsRect</code> fits inside <code>lhsRect</code>.
|
||||
*/
|
||||
function CGRectContainsRect(lhsRect, rhsRect)
|
||||
{
|
||||
var union = CGRectUnion(lhsRect, rhsRect);
|
||||
|
||||
return _CGRectEqualToRect(union, lhsRect);
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns <code>YES</code> if the two rectangles intersect
|
||||
@group CGRect
|
||||
@param lhsRect the first CGRect
|
||||
@param rhsRect the second CGRect
|
||||
@return BOOL <code>YES</code> if the two rectangles have any common spaces, and <code>NO</code>, otherwise.
|
||||
*/
|
||||
function CGRectIntersectsRect(lhsRect, rhsRect)
|
||||
{
|
||||
var intersection = CGRectIntersection(lhsRect, rhsRect);
|
||||
|
||||
return !_CGRectIsEmpty(intersection);
|
||||
}
|
||||
|
||||
/*!
|
||||
Makes the origin and size of a CGRect all integers. Specifically, by making
|
||||
the southwest corner the origin (rounded down), and the northeast corner a CGSize (rounded up).
|
||||
@param aRect the rectangle to operate on
|
||||
@return CGRect the modified rectangle (same as the input)
|
||||
@group CGRect
|
||||
*/
|
||||
function CGRectIntegral(aRect)
|
||||
{
|
||||
aRect = CGRectStandardize(aRect);
|
||||
|
||||
// Store these out separately, if not the GetMaxes will return incorrect values.
|
||||
var x = FLOOR(_CGRectGetMinX(aRect)),
|
||||
y = FLOOR(_CGRectGetMinY(aRect));
|
||||
|
||||
aRect.size.width = CEIL(_CGRectGetMaxX(aRect)) - x;
|
||||
aRect.size.height = CEIL(_CGRectGetMaxY(aRect)) - y;
|
||||
|
||||
aRect.origin.x = x;
|
||||
aRect.origin.y = y;
|
||||
|
||||
return aRect;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the intersection of the two provided rectangles as a new rectangle.
|
||||
@param lhsRect the first rectangle used for calculation
|
||||
@param rhsRect the second rectangle used for calculation
|
||||
@return CGRect the intersection of the two rectangles
|
||||
@group CGRect
|
||||
*/
|
||||
function CGRectIntersection(lhsRect, rhsRect)
|
||||
{
|
||||
var intersection = _CGRectMake(
|
||||
MAX(_CGRectGetMinX(lhsRect), _CGRectGetMinX(rhsRect)),
|
||||
MAX(_CGRectGetMinY(lhsRect), _CGRectGetMinY(rhsRect)),
|
||||
0, 0);
|
||||
|
||||
intersection.size.width = MIN(_CGRectGetMaxX(lhsRect), _CGRectGetMaxX(rhsRect)) - _CGRectGetMinX(intersection);
|
||||
intersection.size.height = MIN(_CGRectGetMaxY(lhsRect), _CGRectGetMaxY(rhsRect)) - _CGRectGetMinY(intersection);
|
||||
|
||||
return _CGRectIsEmpty(intersection) ? _CGRectMakeZero() : intersection;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
function CGRectStandardize(aRect)
|
||||
{
|
||||
var width = _CGRectGetWidth(aRect),
|
||||
height = _CGRectGetHeight(aRect),
|
||||
standardized = aRect;
|
||||
|
||||
if (width < 0.0)
|
||||
{
|
||||
if (standardized == aRect)
|
||||
standardized = _CGRectMakeCopy(aRect);
|
||||
|
||||
standardized.origin.x += width;
|
||||
standardized.size.width = -width;
|
||||
}
|
||||
|
||||
if (height < 0.0)
|
||||
{
|
||||
if (standardized == aRect)
|
||||
standardized = _CGRectMakeCopy(aRect);
|
||||
|
||||
standardized.origin.y += height;
|
||||
standardized.size.height = -height;
|
||||
}
|
||||
|
||||
return standardized;
|
||||
}
|
||||
|
||||
function CGRectUnion(lhsRect, rhsRect)
|
||||
{
|
||||
var minX = MIN(_CGRectGetMinX(lhsRect), _CGRectGetMinX(rhsRect)),
|
||||
minY = MIN(_CGRectGetMinY(lhsRect), _CGRectGetMinY(rhsRect)),
|
||||
maxX = MAX(_CGRectGetMaxX(lhsRect), _CGRectGetMaxX(rhsRect)),
|
||||
maxY = MAX(_CGRectGetMaxY(lhsRect), _CGRectGetMaxY(rhsRect));
|
||||
|
||||
return _CGRectMake(minX, minY, maxX - minX, maxY - minY);
|
||||
}
|
||||
|
||||
function CGPointFromString(aString)
|
||||
{
|
||||
var comma = aString.indexOf(',');
|
||||
|
||||
return { x:parseInt(aString.substr(1, comma - 1)), y:parseInt(aString.substring(comma + 1, aString.length)) };
|
||||
}
|
||||
|
||||
function CGSizeFromString(aString)
|
||||
{
|
||||
var comma = aString.indexOf(',');
|
||||
|
||||
return { width:parseInt(aString.substr(1, comma - 1)), height:parseInt(aString.substring(comma + 1, aString.length)) };
|
||||
}
|
||||
|
||||
function CGRectFromString(aString)
|
||||
{
|
||||
var comma = aString.indexOf(',', aString.indexOf(',') + 1);
|
||||
|
||||
return { origin:CGPointFromString(aString.substr(1, comma - 1)), size:CGSizeFromString(aString.substring(comma + 2, aString.length)) };
|
||||
}
|
||||
|
||||
function CGPointFromEvent(anEvent)
|
||||
{
|
||||
return _CGPointMake(anEvent.clientX, anEvent.clientY);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* CGGradient.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import "CGColor.j"
|
||||
@import "CGColorSpace.j"
|
||||
|
||||
|
||||
kCGGradientDrawsBeforeStartLocation = 1 << 0;
|
||||
kCGGradientDrawsAfterEndLocation = 1 << 1;
|
||||
|
||||
function CGGradientCreateWithColorComponents(aColorSpace, components, locations, count)
|
||||
{
|
||||
if (arguments["count"] == NULL)
|
||||
var count = locations.length;
|
||||
|
||||
var colors = [];
|
||||
|
||||
while (count--)
|
||||
{
|
||||
var offset = count * 4;
|
||||
colors[count] = CGColorCreate(aColorSpace, components.slice(offset, offset + 4));
|
||||
}
|
||||
|
||||
return CGGradientCreateWithColors(aColorSpace, colors, locations);
|
||||
}
|
||||
|
||||
function CGGradientCreateWithColors(aColorSpace, colors, locations)
|
||||
{
|
||||
return { colorspace:aColorSpace, colors:colors, locations:locations };
|
||||
}
|
||||
|
||||
function CGGradientRelease()
|
||||
{
|
||||
}
|
||||
|
||||
function CGGradientRetain(aGradient)
|
||||
{
|
||||
return aGradient;
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* CGPath.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "CGGeometry.h"
|
||||
#include "CGAffineTransform.h"
|
||||
|
||||
@import "CGGeometry.j"
|
||||
@import "CGAffineTransform.j"
|
||||
|
||||
|
||||
kCGPathElementMoveToPoint = 0;
|
||||
kCGPathElementAddLineToPoint = 1;
|
||||
kCGPathElementAddQuadCurveToPoint = 2;
|
||||
kCGPathElementAddCurveToPoint = 3;
|
||||
kCGPathElementCloseSubpath = 4;
|
||||
|
||||
kCGPathElementAddArc = 5;
|
||||
kCGPathElementAddArcToPoint = 6;
|
||||
|
||||
function CGPathCreateMutable()
|
||||
{
|
||||
return { count:0, start:NULL, current:NULL, elements:[] };
|
||||
}
|
||||
|
||||
function CGPathCreateMutableCopy(aPath)
|
||||
{
|
||||
var path = CGPathCreateMutable();
|
||||
|
||||
CGPathAddPath(path, aPath);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
function CGPathCreateCopy(aPath)
|
||||
{
|
||||
return CGPathCreateMutableCopy(aPath);
|
||||
}
|
||||
|
||||
function CGPathRelease(aPath)
|
||||
{
|
||||
}
|
||||
|
||||
function CGPathRetain(aPath)
|
||||
{
|
||||
return aPath;
|
||||
}
|
||||
|
||||
function CGPathAddArc(aPath, aTransform, x, y, aRadius, aStartAngle, anEndAngle, isClockwise)
|
||||
{
|
||||
if (aTransform && !_CGAffineTransformIsIdentity(aTransform))
|
||||
{
|
||||
var center = _CGPointMake(x, y),
|
||||
end = _CGPointMake(COS(anEndAngle), SIN(anEndAngle)),
|
||||
start = _CGPointMake(COS(aStartAngle), SIN(aStartAngle));
|
||||
|
||||
end = _CGPointApplyAffineTransform(end, aTransform);
|
||||
start = _CGPointApplyAffineTransform(start, aTransform);
|
||||
center = _CGPointApplyAffineTransform(center, aTransform);
|
||||
|
||||
x = center.x;
|
||||
y = center.y;
|
||||
|
||||
var oldEndAngle = anEndAngle,
|
||||
oldStartAngle = aStartAngle;
|
||||
|
||||
anEndAngle = ATAN2(end.y - aTransform.ty, end.x - aTransform.tx);
|
||||
aStartAngle = ATAN2(start.y - aTransform.ty, start.x - aTransform.tx);
|
||||
|
||||
// Angles that equal "modulo" 2 pi return as equal after transforming them,
|
||||
// so we have to make sure to make them different again if they were different
|
||||
// to start out with. It's the difference between no circle and a full circle.
|
||||
if (anEndAngle == aStartAngle && oldEndAngle != oldStartAngle)
|
||||
if (oldStartAngle > oldEndAngle)
|
||||
anEndAngle = anEndAngle - PI2;
|
||||
else
|
||||
aStartAngle = aStartAngle - PI2;
|
||||
|
||||
aRadius = _CGSizeMake(aRadius, 0);
|
||||
aRadius = _CGSizeApplyAffineTransform(aRadius, aTransform);
|
||||
aRadius = SQRT(aRadius.width * aRadius.width + aRadius.height * aRadius.height);
|
||||
}
|
||||
|
||||
aPath.current = _CGPointMake(x + aRadius * COS(anEndAngle), y + aRadius * SIN(anEndAngle));
|
||||
aPath.elements[aPath.count++] = { type:kCGPathElementAddArc, x:x, y:y, radius:aRadius, startAngle:aStartAngle, endAngle:anEndAngle };
|
||||
}
|
||||
|
||||
function CGPathAddArcToPoint(aPath, aTransform, x1, y1, x2, y2, aRadius)
|
||||
{
|
||||
}
|
||||
|
||||
function CGPathAddCurveToPoint(aPath, aTransform, cp1x, cp1y, cp2x, cp2y, x, y)
|
||||
{
|
||||
var cp1 = _CGPointMake(cp1x, cp1y),
|
||||
cp2 = _CGPointMake(cp2x, cp2y),
|
||||
end = _CGPointMake(x, y);
|
||||
|
||||
if (aTransform)
|
||||
{
|
||||
cp1 = _CGPointApplyAffineTransform(cp1, aTransform);
|
||||
cp2 = _CGPointApplyAffineTransform(cp2, aTransform);
|
||||
end = _CGPointApplyAffineTransform(end, aTransform);
|
||||
}
|
||||
|
||||
aPath.current = end;
|
||||
aPath.elements[aPath.count++] = { type:kCGPathElementAddCurveToPoint, cp1x:cp1.x, cp1y:cp1.y, cp2x:cp2.x, cp2y:cp2.y, x:end.x, y:end.y };
|
||||
}
|
||||
|
||||
function CGPathAddLines(aPath, aTransform, points, count)
|
||||
{
|
||||
var i = 1;
|
||||
|
||||
if (arguments["count"] == NULL)
|
||||
var count = points.length;
|
||||
|
||||
if (!aPath || count < 2)
|
||||
return;
|
||||
|
||||
CGPathMoveToPoint(aPath, aTransform, points[0].x, points[0].y);
|
||||
|
||||
for (; i < count; ++i)
|
||||
CGPathAddLineToPoint(aPath, aTransform, points[i].x, points[i].y);
|
||||
}
|
||||
|
||||
function CGPathAddLineToPoint(aPath, aTransform, x, y)
|
||||
{
|
||||
var point = _CGPointMake(x, y);
|
||||
|
||||
if (aTransform != NULL)
|
||||
point = _CGPointApplyAffineTransform(point, aTransform);
|
||||
|
||||
aPath.elements[aPath.count++] = { type: kCGPathElementAddLineToPoint, x:point.x, y:point.y };
|
||||
aPath.current = point;
|
||||
}
|
||||
|
||||
function CGPathAddPath(aPath, aTransform, anotherPath)
|
||||
{
|
||||
var i = 0,
|
||||
count = anotherPath.count;
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var element = anotherPath[i];
|
||||
|
||||
aPath.elements[aPath.count++] = { type:element.type, points:element.point.slice() };
|
||||
|
||||
if (element.type == kCGPathElementAddArc || element.type == kCGPathElementAddArcToPoint)
|
||||
aPath.elements[aPath.count - 1].radius = element.radius;
|
||||
}
|
||||
|
||||
aPath.current = anotherPath.current;
|
||||
}
|
||||
|
||||
function CGPathAddQuadCurveToPoint(aPath, aTransform, cpx, cpy, x, y)
|
||||
{
|
||||
var cp = _CGPointMake(cpx, cpy),
|
||||
end = _CGPointMake(x, y);
|
||||
|
||||
if (aTransform)
|
||||
{
|
||||
cp = _CGPointApplyAffineTransform(control, aTransform);
|
||||
end = _CGPointApplyAffineTransform(end, aTransform);
|
||||
}
|
||||
|
||||
aPath.elements[aPath.count++] = { type:kCGPathElementAddQuadCurveToPoint, cpx:cp.x, cpy:cp.y, x:end.x, y:end.y }
|
||||
aPath.current = end;
|
||||
}
|
||||
|
||||
function CGPathAddRect(aPath, aTransform, aRect)
|
||||
{
|
||||
CGPathAddRects(aPath, aTransform, [aRect], 1);
|
||||
}
|
||||
|
||||
function CGPathAddRects(aPath, aTransform, rects, count)
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
if (arguments["count"] == NULL)
|
||||
var count = rects.length;
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var rect = rects[i];
|
||||
|
||||
CGPathMoveToPoint(aPath, aTransform, _CGRectGetMinX(rect), _CGRectGetMinY(rect));
|
||||
CGPathAddLineToPoint(aPath, aTransform, _CGRectGetMaxX(rect), _CGRectGetMinY(rect));
|
||||
CGPathAddLineToPoint(aPath, aTransform, _CGRectGetMaxX(rect), _CGRectGetMaxY(rect));
|
||||
CGPathAddLineToPoint(aPath, aTransform, _CGRectGetMinX(rect), _CGRectGetMaxY(rect));
|
||||
|
||||
CGPathCloseSubpath(aPath);
|
||||
}
|
||||
}
|
||||
|
||||
function CGPathMoveToPoint(aPath, aTransform, x, y)
|
||||
{
|
||||
var point = _CGPointMake(x, y),
|
||||
count = aPath.count;
|
||||
|
||||
if (aTransform != NULL)
|
||||
point = _CGPointApplyAffineTransform(point, aTransform);
|
||||
|
||||
aPath.start = point;
|
||||
aPath.current = point;
|
||||
|
||||
var previous = aPath.elements[count - 1];
|
||||
|
||||
if (count != 0 && previous.type == kCGPathElementMoveToPoint)
|
||||
{
|
||||
previous.x = point.x;
|
||||
previous.y = point.y;
|
||||
}
|
||||
else
|
||||
aPath.elements[aPath.count++] = { type:kCGPathElementMoveToPoint, x:point.x, y:point.y };
|
||||
}
|
||||
|
||||
function CGPathCloseSubpath(aPath)
|
||||
{
|
||||
var count = aPath.count;
|
||||
|
||||
// Don't bother closing this subpath if there aren't any current elements, or the last element already closed the subpath.
|
||||
if (count == 0 || aPath.elements[count - 1].type == kCGPathElementCloseSubpath)
|
||||
return;
|
||||
|
||||
aPath.elements[aPath.count++] = { type:kCGPathElementCloseSubpath, points:[aPath.start] };
|
||||
}
|
||||
|
||||
function CGPathEqualToPath(aPath, anotherPath)
|
||||
{
|
||||
if (aPath == anotherPath)
|
||||
return YES;
|
||||
|
||||
if (aPath.count != anotherPath.count || !_CGPointEqualToPoint(aPath.start, anotherPath.start) || !_CGPointEqualToPoint(aPath.current, anotherPath.current))
|
||||
return NO;
|
||||
|
||||
var i = 0,
|
||||
count = aPath.count;
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
var element = aPath[i],
|
||||
anotherElement = anotherPath[i];
|
||||
|
||||
if (element.type != anotherElement.type)
|
||||
return NO;
|
||||
|
||||
if ((element.type == kCGPathElementAddArc || element.type == kCGPathElementAddArcToPoint) &&
|
||||
element.radius != anotherElement.radius)
|
||||
return NO;
|
||||
|
||||
var j = element.points.length;
|
||||
|
||||
while (j--)
|
||||
if (!_CGPointEqualToPoint(element.points[j], anotherElement.points[j]))
|
||||
return NO;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
function CGPathGetCurrentPoint(aPath)
|
||||
{
|
||||
return _CGPointCreateCopy(aPath.current);
|
||||
}
|
||||
|
||||
function CGPathIsEmpty(aPath)
|
||||
{
|
||||
return !aPath || aPath.count == 0;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!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>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* CPDOMDisplayServer.h
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define SetStyleOrigin 0
|
||||
#define SetStyleLeftTop 0
|
||||
#define SetStyleRightTop 1
|
||||
#define SetStyleLeftBottom 2
|
||||
#define SetStyleRightBottom 3
|
||||
#define SetStyleSize 4
|
||||
#define SetSize 5
|
||||
#define AppendChild 6
|
||||
#define InsertBefore 7
|
||||
#define RemoveChild 8
|
||||
|
||||
#define CPDOMDisplayServerSetStyleOrigin(anInstruction, aDOMElement, aTransform, x, y)\
|
||||
if (!aDOMElement.CPDOMDisplayContext)\
|
||||
aDOMElement.CPDOMDisplayContext = [];\
|
||||
var __index = aDOMElement.CPDOMDisplayContext[SetStyleOrigin];\
|
||||
if (!(__index >= 0))\
|
||||
{\
|
||||
__index = aDOMElement.CPDOMDisplayContext[SetStyleOrigin] = CPDOMDisplayServerInstructionCount;\
|
||||
CPDOMDisplayServerInstructionCount += 5;\
|
||||
}\
|
||||
CPDOMDisplayServerInstructions[__index] = anInstruction;\
|
||||
CPDOMDisplayServerInstructions[__index + 1] = aDOMElement;\
|
||||
CPDOMDisplayServerInstructions[__index + 2] = aTransform;\
|
||||
CPDOMDisplayServerInstructions[__index + 3] = x;\
|
||||
CPDOMDisplayServerInstructions[__index + 4] = y;
|
||||
|
||||
#define CPDOMDisplayServerSetStyleLeftTop(aDOMElement, aTransform, aLeft, aTop) CPDOMDisplayServerSetStyleOrigin(SetStyleLeftTop, aDOMElement, aTransform, aLeft, aTop)
|
||||
|
||||
#define CPDOMDisplayServerSetStyleRightTop(aDOMElement, aTransform, aRight, aTop) CPDOMDisplayServerSetStyleOrigin(SetStyleRightTop, aDOMElement, aTransform, aRight, aTop)
|
||||
|
||||
#define CPDOMDisplayServerSetStyleLeftBottom(aDOMElement, aTransform, aLeft, aBottom) CPDOMDisplayServerSetStyleOrigin(SetStyleLeftBottom, aDOMElement, aTransform, aLeft, aBottom)
|
||||
|
||||
#define CPDOMDisplayServerSetStyleRightBottom(aDOMElement, aTransform, aRight, aBottom) CPDOMDisplayServerSetStyleOrigin(SetStyleRightBottom, aDOMElement, aTransform, aRight, aBottom)
|
||||
|
||||
#define CPDOMDisplayServerSetStyleSize(aDOMElement, aWidth, aHeight)\
|
||||
if (!aDOMElement.CPDOMDisplayContext)\
|
||||
aDOMElement.CPDOMDisplayContext = [];\
|
||||
var __index = aDOMElement.CPDOMDisplayContext[SetStyleSize];\
|
||||
if (!(__index >= 0))\
|
||||
{\
|
||||
__index = aDOMElement.CPDOMDisplayContext[SetStyleSize] = CPDOMDisplayServerInstructionCount;\
|
||||
CPDOMDisplayServerInstructionCount += 4;\
|
||||
}\
|
||||
CPDOMDisplayServerInstructions[__index] = SetStyleSize;\
|
||||
CPDOMDisplayServerInstructions[__index + 1] = aDOMElement;\
|
||||
CPDOMDisplayServerInstructions[__index + 2] = aWidth;\
|
||||
CPDOMDisplayServerInstructions[__index + 3] = aHeight;
|
||||
|
||||
#define CPDOMDisplayServerSetSize(aDOMElement, aWidth, aHeight)\
|
||||
if (!aDOMElement.CPDOMDisplayContext)\
|
||||
aDOMElement.CPDOMDisplayContext = [];\
|
||||
var __index = aDOMElement.CPDOMDisplayContext[SetSize];\
|
||||
if (!(__index >= 0))\
|
||||
{\
|
||||
__index = aDOMElement.CPDOMDisplayContext[SetSize] = CPDOMDisplayServerInstructionCount;\
|
||||
CPDOMDisplayServerInstructionCount += 4;\
|
||||
}\
|
||||
CPDOMDisplayServerInstructions[__index] = SetSize;\
|
||||
CPDOMDisplayServerInstructions[__index + 1] = aDOMElement;\
|
||||
CPDOMDisplayServerInstructions[__index + 2] = aWidth;\
|
||||
CPDOMDisplayServerInstructions[__index + 3] = aHeight;
|
||||
|
||||
#define CPDOMDisplayServerAppendChild(aParentElement, aChildElement)\
|
||||
if (aChildElement.CPDOMDisplayContext) aChildElement.CPDOMDisplayContext[SetStyleOrigin] = -1;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = AppendChild;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = aParentElement;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = aChildElement;
|
||||
|
||||
#define CPDOMDisplayServerInsertBefore(aParentElement, aChildElement, aBeforeElement)\
|
||||
if (aChildElement.CPDOMDisplayContext) aChildElement.CPDOMDisplayContext[SetStyleOrigin] = -1;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = InsertBefore;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = aParentElement;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = aChildElement;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = aBeforeElement;
|
||||
|
||||
#define CPDOMDisplayServerRemoveChild(aParentElement, aChildElement)\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = RemoveChild;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = aParentElement;\
|
||||
CPDOMDisplayServerInstructions[CPDOMDisplayServerInstructionCount++] = aChildElement;
|
||||
|
||||
//#dfeine CPDOMDisplayServerCustomAction()
|
||||
|
||||
#define CPDOMDisplayServerAddView(aView)\
|
||||
{\
|
||||
var ___hash = [aView hash];\
|
||||
if (typeof (CPDOMDisplayServerViewsContext[___hash]) == "undefined")\
|
||||
{\
|
||||
CPDOMDisplayServerViews[CPDOMDisplayServerViewsCount++] = aView;\
|
||||
CPDOMDisplayServerViewsContext[___hash] = aView;\
|
||||
}\
|
||||
}\
|
||||
|
||||
#define CPDOMDisplayServerRemoveView(aView)\
|
||||
{\
|
||||
var index = CPDOMDisplayServerViewsContext[[aView hash]];\
|
||||
if (typeof index != "undefined") \
|
||||
{\
|
||||
CPDOMDisplayServerViewsContext[[aView hash]];\
|
||||
CPDOMDisplayServerViews[index] = NULL;\
|
||||
}\
|
||||
}\
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* CPDOMDisplayServer.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPRunLoop.j>
|
||||
|
||||
#include "../../CoreGraphics/CGAffineTransform.h"
|
||||
#include "CPDOMDisplayServer.h"
|
||||
|
||||
|
||||
var CPDOMDisplayRunLoop = nil;
|
||||
|
||||
CPDOMDisplayServerInstructions = [];
|
||||
CPDOMDisplayServerInstructionCount = 0;
|
||||
|
||||
CPDOMDisplayServerViews = [];
|
||||
CPDOMDisplayServerViewsCount = 0;
|
||||
CPDOMDisplayServerViewsContext = {};
|
||||
|
||||
@implementation CPDOMDisplayServer : CPObject
|
||||
{
|
||||
}
|
||||
|
||||
+ (void)start
|
||||
{
|
||||
CPDOMDisplayRunLoop = [CPRunLoop currentRunLoop];
|
||||
|
||||
[CPDOMDisplayRunLoop performSelector:@selector(run) target:CPDOMDisplayServer argument:nil order:0 modes:[CPDefaultRunLoopMode]];
|
||||
}
|
||||
|
||||
+ (void)run
|
||||
{
|
||||
while (CPDOMDisplayServerInstructionCount || CPDOMDisplayServerViewsCount)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
while (index < CPDOMDisplayServerInstructionCount)
|
||||
{
|
||||
var instruction = CPDOMDisplayServerInstructions[index++];
|
||||
try{
|
||||
switch (instruction)
|
||||
{
|
||||
case SetStyleLeftTop:
|
||||
case SetStyleRightTop:
|
||||
case SetStyleLeftBottom:
|
||||
case SetStyleRightBottom: var element = CPDOMDisplayServerInstructions[index],
|
||||
style = element.style,
|
||||
x = (instruction == SetStyleLeftTop || instruction == SetStyleLeftBottom) ? "left" : "right",
|
||||
y = (instruction == SetStyleLeftTop || instruction == SetStyleRightTop) ? "top" : "bottom";
|
||||
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
|
||||
var transform = CPDOMDisplayServerInstructions[index++];
|
||||
|
||||
if (transform)
|
||||
{
|
||||
var point = _CGPointMake(CPDOMDisplayServerInstructions[index++], CPDOMDisplayServerInstructions[index++]),
|
||||
transformed = _CGPointApplyAffineTransform(point, transform);
|
||||
|
||||
style[x] = ROUND(transformed.x) + "px";
|
||||
style[y] = ROUND(transformed.y) + "px";
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
style[x] = ROUND(CPDOMDisplayServerInstructions[index++]) + "px";
|
||||
style[y] = ROUND(CPDOMDisplayServerInstructions[index++]) + "px";
|
||||
}
|
||||
|
||||
element.CPDOMDisplayContext[SetStyleOrigin] = -1;
|
||||
|
||||
break;
|
||||
|
||||
case SetStyleSize: var element = CPDOMDisplayServerInstructions[index],
|
||||
style = element.style;
|
||||
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
|
||||
element.CPDOMDisplayContext[SetStyleSize] = -1;
|
||||
|
||||
style.width = MAX(0.0, ROUND(CPDOMDisplayServerInstructions[index++])) + "px";
|
||||
style.height = MAX(0.0, ROUND(CPDOMDisplayServerInstructions[index++])) + "px";
|
||||
|
||||
break;
|
||||
|
||||
case SetSize: var element = CPDOMDisplayServerInstructions[index];
|
||||
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
|
||||
element.CPDOMDisplayContext[SetSize] = -1;
|
||||
|
||||
element.width = MAX(0.0, ROUND(CPDOMDisplayServerInstructions[index++]));
|
||||
element.height = MAX(0.0, ROUND(CPDOMDisplayServerInstructions[index++]));
|
||||
|
||||
break;
|
||||
|
||||
case AppendChild: CPDOMDisplayServerInstructions[index].appendChild(CPDOMDisplayServerInstructions[index + 1]);
|
||||
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
|
||||
break;
|
||||
|
||||
case InsertBefore: CPDOMDisplayServerInstructions[index].insertBefore(CPDOMDisplayServerInstructions[index + 1], CPDOMDisplayServerInstructions[index + 2]);
|
||||
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
|
||||
break;
|
||||
|
||||
case RemoveChild: CPDOMDisplayServerInstructions[index].removeChild(CPDOMDisplayServerInstructions[index + 1]);
|
||||
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
CPDOMDisplayServerInstructions[index++] = nil;
|
||||
|
||||
break;
|
||||
}}catch(e) { CPLog("here?" + instruction) }
|
||||
}
|
||||
|
||||
CPDOMDisplayServerInstructionCount = 0;
|
||||
|
||||
var views = CPDOMDisplayServerViews,
|
||||
index = 0,
|
||||
count = CPDOMDisplayServerViewsCount;
|
||||
|
||||
// We don't reset CPDOMDisplayServerViewsContext because it can serve for displays that are coming...
|
||||
CPDOMDisplayServerViews = [];
|
||||
CPDOMDisplayServerViewsCount = 0;
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
var view = views[index];
|
||||
|
||||
delete CPDOMDisplayServerViewsContext[[view hash]];
|
||||
|
||||
[view displayIfNeeded];
|
||||
}
|
||||
}
|
||||
|
||||
[CPDOMDisplayRunLoop performSelector:@selector(run) target:CPDOMDisplayServer argument:nil order:0 modes:[CPDefaultRunLoopMode]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
[CPDOMDisplayServer start];
|
||||
@@ -0,0 +1,904 @@
|
||||
/*
|
||||
* CPDOMWindowBridge.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPRunLoop.j>
|
||||
|
||||
@import "CPEvent.j"
|
||||
@import "CPCompatibility.j"
|
||||
|
||||
@import "CPDOMWindowLayer.j"
|
||||
|
||||
#import "../../CoreGraphics/CGGeometry.h"
|
||||
|
||||
|
||||
CPSharedDOMWindowBridge = nil;
|
||||
|
||||
var ExcludedDOMElements = [];
|
||||
|
||||
// Define up here so compressor knows about em.
|
||||
var CPDOMWindowGetFrame,
|
||||
CPDOMEventGetClickCount,
|
||||
CPDOMEventStop;
|
||||
|
||||
@implementation CPDOMWindowBridge : CPObject
|
||||
{
|
||||
CPArray _orderedWindows;
|
||||
CPWindow _mouseDownWindow;
|
||||
|
||||
DOMWindow _DOMWindow;
|
||||
DOMElement _DOMBodyElement;
|
||||
DOMElement _DOMFocusElement;
|
||||
|
||||
CPArray _windowLevels;
|
||||
CPDictionary _windowLayers;
|
||||
|
||||
CPRect _frame;
|
||||
CPRect _contentBounds;
|
||||
|
||||
BOOL _mouseIsDown;
|
||||
CPTimeInterval _lastMouseUp;
|
||||
CPTimeInterval _lastMouseDown;
|
||||
|
||||
CPPoint _currentMousePosition;
|
||||
|
||||
JSObject _charCodes;
|
||||
unsigned _keyCode;
|
||||
|
||||
BOOL _DOMEventMode;
|
||||
|
||||
// Native Pasteboard Support
|
||||
DOMElement _DOMPasteboardElement;
|
||||
CPEvent _pasteboardKeyDownEvent;
|
||||
|
||||
CPString _overriddenEventType;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the shared DOMWindowBridge.
|
||||
*/
|
||||
+ (id)sharedDOMWindowBridge
|
||||
{
|
||||
if (!CPSharedDOMWindowBridge)
|
||||
CPSharedDOMWindowBridge = [[CPDOMWindowBridge alloc] _initWithDOMWindow:window];
|
||||
|
||||
return CPSharedDOMWindowBridge;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CPRect)aFrame
|
||||
{
|
||||
alert("unimplemented");
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (id)_initWithDOMWindow:(DOMWindow)aDOMWindow
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_DOMWindow = aDOMWindow;
|
||||
|
||||
_windowLevels = [];
|
||||
_windowLayers = [CPDictionary dictionary];
|
||||
|
||||
// Do this before getting the frame of the window, because if not it will be wrong in IE.
|
||||
_DOMBodyElement = document.getElementsByTagName("body")[0];
|
||||
_DOMBodyElement.innerHTML = ""; // Get rid of anything that might be lingering in the body element.
|
||||
_DOMBodyElement.style.overflow = "hidden";
|
||||
|
||||
if (document.documentElement)
|
||||
document.documentElement.style.overflow = "hidden";
|
||||
|
||||
_frame = CPDOMWindowGetFrame(_DOMWindow);
|
||||
_contentBounds = CGRectMake(0.0, 0.0, CPRectGetWidth(_frame), CPRectGetHeight(_frame));
|
||||
|
||||
_DOMFocusElement = document.createElement("input");
|
||||
_DOMFocusElement.style.position = "absolute";
|
||||
_DOMFocusElement.style.zIndex = "-1000";
|
||||
_DOMFocusElement.style.opacity = "0";
|
||||
_DOMFocusElement.style.filter = "alpha(opacity=0)";
|
||||
_DOMBodyElement.appendChild(_DOMFocusElement);
|
||||
|
||||
// Create Native Pasteboard handler.
|
||||
_DOMPasteboardElement = document.createElement("input");
|
||||
_DOMPasteboardElement.style.position = "absolute";
|
||||
_DOMPasteboardElement.style.top = "-10000px";
|
||||
_DOMPasteboardElement.style.zIndex = "99";
|
||||
|
||||
_DOMBodyElement.appendChild(_DOMPasteboardElement);
|
||||
|
||||
// Make sure the pastboard element is blurred.
|
||||
_DOMPasteboardElement.blur();
|
||||
|
||||
_charCodes = {};
|
||||
|
||||
//
|
||||
var theClass = [self class],
|
||||
|
||||
keyEventSelector = @selector(_bridgeKeyEvent:),
|
||||
keyEventImplementation = class_getMethodImplementation(theClass, keyEventSelector),
|
||||
keyEventCallback = function (anEvent) { keyEventImplementation(self, nil, anEvent); },
|
||||
|
||||
mouseEventSelector = @selector(_bridgeMouseEvent:),
|
||||
mouseEventImplementation = class_getMethodImplementation(theClass, mouseEventSelector),
|
||||
mouseEventCallback = function (anEvent) { mouseEventImplementation(self, nil, anEvent); },
|
||||
|
||||
scrollEventSelector = @selector(_bridgeScrollEvent:),
|
||||
scrollEventImplementation = class_getMethodImplementation(theClass, scrollEventSelector),
|
||||
scrollEventCallback = function (anEvent) { scrollEventImplementation(self, nil, anEvent); },
|
||||
|
||||
resizeEventSelector = @selector(_bridgeResizeEvent:),
|
||||
resizeEventImplementation = class_getMethodImplementation(theClass, resizeEventSelector),
|
||||
resizeEventCallback = function (anEvent) { resizeEventImplementation(self, nil, anEvent); },
|
||||
|
||||
theDocument = _DOMWindow.document;
|
||||
|
||||
if (document.addEventListener)
|
||||
{
|
||||
_DOMWindow.addEventListener("resize", resizeEventCallback, NO);
|
||||
|
||||
theDocument.addEventListener(CPDOMEventMouseUp, mouseEventCallback, NO);
|
||||
theDocument.addEventListener(CPDOMEventMouseDown, mouseEventCallback, NO);
|
||||
theDocument.addEventListener(CPDOMEventMouseMoved, mouseEventCallback, NO);
|
||||
|
||||
theDocument.addEventListener(CPDOMEventKeyUp, keyEventCallback, NO);
|
||||
theDocument.addEventListener(CPDOMEventKeyDown, keyEventCallback, NO);
|
||||
theDocument.addEventListener(CPDOMEventKeyPress, keyEventCallback, NO);
|
||||
|
||||
//FIXME: does firefox really need a different value?
|
||||
_DOMWindow.addEventListener("DOMMouseScroll", scrollEventCallback, NO);
|
||||
_DOMWindow.addEventListener(CPDOMEventScrollWheel, scrollEventCallback, NO);
|
||||
}
|
||||
else if(document.attachEvent)
|
||||
{
|
||||
_DOMWindow.attachEvent("onresize", resizeEventCallback);
|
||||
|
||||
theDocument.attachEvent("on" + CPDOMEventMouseUp, mouseEventCallback);
|
||||
theDocument.attachEvent("on" + CPDOMEventMouseDown, mouseEventCallback);
|
||||
theDocument.attachEvent("on" + CPDOMEventMouseMoved, mouseEventCallback);
|
||||
theDocument.attachEvent("on" + CPDOMEventDoubleClick, mouseEventCallback);
|
||||
|
||||
theDocument.attachEvent("on" + CPDOMEventKeyUp, keyEventCallback);
|
||||
theDocument.attachEvent("on" + CPDOMEventKeyDown, keyEventCallback);
|
||||
theDocument.attachEvent("on" + CPDOMEventKeyPress, keyEventCallback);
|
||||
|
||||
_DOMWindow.onmousewheel = scrollEventCallback;
|
||||
theDocument.onmousewheel = scrollEventCallback;
|
||||
|
||||
theDocument.body.ondrag = function () { return NO; };
|
||||
theDocument.body.onselectstart = function () { return window.event.srcElement == _DOMPasteboardElement; };
|
||||
}
|
||||
|
||||
ExcludedDOMElements["INPUT"] = YES;
|
||||
ExcludedDOMElements["SELECT"] = YES;
|
||||
ExcludedDOMElements["TEXTAREA"] = YES;
|
||||
ExcludedDOMElements["OPTION"] = YES;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPRect)frame
|
||||
{
|
||||
return CGRectMakeCopy(_frame);
|
||||
}
|
||||
|
||||
- (CGRect)visibleFrame
|
||||
{
|
||||
var frame = [self frame];
|
||||
|
||||
frame.origin = CGPointMakeZero();
|
||||
|
||||
if ([CPMenu menuBarVisible])
|
||||
{
|
||||
var menuBarHeight = [[CPApp mainMenu] menuBarHeight];
|
||||
|
||||
frame.origin.y += menuBarHeight;
|
||||
frame.size.height -= menuBarHeight;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
- (CPRect)contentBounds
|
||||
{
|
||||
return CPRectCreateCopy(_contentBounds);
|
||||
}
|
||||
|
||||
- (CPDOMWindowLayer)layerAtLevel:(int)aLevel create:(BOOL)aFlag
|
||||
{
|
||||
var layer = [_windowLayers objectForKey:aLevel];
|
||||
|
||||
// If the layer doesn't currently exist, and the create flag is true,
|
||||
// create the layer.
|
||||
if (!layer && aFlag)
|
||||
{
|
||||
layer = [[CPDOMWindowLayer alloc] initWithLevel:aLevel];
|
||||
|
||||
[_windowLayers setObject:layer forKey:aLevel];
|
||||
|
||||
// Find the nearest layer. This is similar to a binary search,
|
||||
// only we know we won't find the value.
|
||||
var low = 0,
|
||||
high = _windowLevels.length - 1,
|
||||
middle;
|
||||
|
||||
while (low <= high)
|
||||
{
|
||||
middle = FLOOR((low + high) / 2);
|
||||
|
||||
if (_windowLevels[middle] > aLevel)
|
||||
high = middle - 1;
|
||||
else
|
||||
low = middle + 1;
|
||||
}
|
||||
|
||||
[_windowLevels insertObject:aLevel atIndex:_windowLevels[middle] > aLevel ? middle : middle + 1];
|
||||
layer._DOMElement.style.zIndex = aLevel;
|
||||
_DOMBodyElement.appendChild(layer._DOMElement);
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
- (void)order:(CPWindowOrderingMode)aPlace window:(CPWindow)aWindow relativeTo:(CPWindow)otherWindow
|
||||
{
|
||||
// Grab the appropriate level for the layer, and create it if
|
||||
// necessary (if we are not simply removing the window).
|
||||
var layer = [self layerAtLevel:[aWindow level] create:aPlace != CPWindowOut];
|
||||
|
||||
// Ignore otherWindow, simply remove this window from it's level.
|
||||
// If layer is nil, this will be a no-op.
|
||||
if (aPlace == CPWindowOut)
|
||||
return [layer removeWindow:aWindow];
|
||||
|
||||
// Place the window at the appropriate index.
|
||||
[layer insertWindow:aWindow atIndex:(otherWindow ? (aPlace == CPWindowAbove ? otherWindow._index + 1 : otherWindow._index) : CPNotFound)];
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (CPView)_dragHitTest:(CPPoint)aPoint pasteboard:(CPPasteboard)aPasteboard
|
||||
{
|
||||
var view = nil,
|
||||
levels = _windowLevels,
|
||||
layers = _windowLayers,
|
||||
levelCount = levels.length;
|
||||
|
||||
while (levelCount-- && !view)
|
||||
{
|
||||
// Skip any windows above or at the dragging level.
|
||||
if (levels[levelCount] >= CPDraggingWindowLevel)
|
||||
continue;
|
||||
|
||||
var windows = [layers objectForKey:levels[levelCount]]._windows,
|
||||
windowCount = windows.length;
|
||||
|
||||
while (windowCount--)
|
||||
{
|
||||
var theWindow = windows[windowCount],
|
||||
frame = theWindow._frame;
|
||||
|
||||
if (CPRectContainsPoint(frame, aPoint))
|
||||
if (view = [theWindow._windowView _dragHitTest:CGPointMake(aPoint.x - frame.origin.x, aPoint.y - frame.origin.y) pasteboard:aPasteboard])
|
||||
return view;
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_propagateCurrentDOMEvent:(BOOL)aFlag
|
||||
{
|
||||
StopDOMEventPropagation = !aFlag;
|
||||
}
|
||||
|
||||
- (CPWindow)hitTest:(CPPoint)location
|
||||
{
|
||||
var levels = _windowLevels,
|
||||
layers = _windowLayers,
|
||||
levelCount = levels.length,
|
||||
theWindow = nil;
|
||||
|
||||
while (levelCount-- && !theWindow)
|
||||
{
|
||||
var windows = [layers objectForKey:levels[levelCount]]._windows,
|
||||
windowCount = windows.length;
|
||||
|
||||
while (windowCount-- && !theWindow)
|
||||
if (CPRectContainsPoint(windows[windowCount]._frame, location))
|
||||
theWindow = windows[windowCount];
|
||||
}
|
||||
|
||||
return theWindow;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CPDOMWindowGetFrame = function(_DOMWindow)
|
||||
{
|
||||
var frame = nil;//CGRectMakeZero();
|
||||
|
||||
// We will rarely be able to get all this information, but we do the best we can:
|
||||
if (_DOMWindow.outerWidth)
|
||||
frame = CGRectMake(0, 0, _DOMWindow.outerWidth, _DOMWindow.outerHeight);
|
||||
|
||||
else /*if(self.outerWidth)*/
|
||||
frame = CGRectMake(0, 0, -1, -1);
|
||||
|
||||
if (window.screenTop)
|
||||
frame.origin = CGPointMake(_DOMWindow.screenLeft, _DOMWindow.screenTop, 0);
|
||||
|
||||
else if (window.screenX)
|
||||
frame.origin = CGPointMake(_DOMWindow.screenX, _DOMWindow.screenY, 0);
|
||||
|
||||
// Safari, Mozilla, Firefox, and Opera
|
||||
if (_DOMWindow.innerWidth)
|
||||
frame.size = CGSizeMake(_DOMWindow.innerWidth, _DOMWindow.innerHeight);
|
||||
|
||||
// Internet Explorer 6 in Strict Mode
|
||||
else if (document.documentElement && document.documentElement.clientWidth)
|
||||
frame.size = CGSizeMake(_DOMWindow.document.documentElement.clientWidth, _DOMWindow.document.documentElement.clientHeight);
|
||||
|
||||
// Internet Explorer X
|
||||
else
|
||||
frame.size = CGSizeMake(_DOMWindow.document.body.clientWidth, _DOMWindow.document.body.clientHeight);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
//right now we hard code q, w, r and t as keys to propogate
|
||||
//these aren't normal keycodes, they are with modifier key codes
|
||||
//might be mac only, we should investigate futher later.
|
||||
var KeyCodesToPrevent = {},
|
||||
CharacterKeysToPrevent = {},
|
||||
KeyCodesWithoutKeyPressEvents = { '8':1, '9':1, '37':1, '38':1, '39':1, '40':1, '46':1, '33':1, '34':1 };
|
||||
|
||||
var CTRL_KEY_CODE = 17;
|
||||
|
||||
@implementation CPDOMWindowBridge (Events)
|
||||
|
||||
/*!
|
||||
When using command (mac) or control (windows), keys are propagated to the browser by default.
|
||||
To prevent a character key from propagating (to prevent its default action, and instead use it
|
||||
in your own application), use these methods. These methods are additive -- the list builds until you clear it.
|
||||
|
||||
@param characters a list of characters to stop propagating keypresses to the browser.
|
||||
*/
|
||||
- (void)preventCharacterKeysFromPropagating:(CPArray)characters
|
||||
{
|
||||
for(var i=characters.length; i>0; i--)
|
||||
CharacterKeysToPrevent[""+characters[i-1].toLowerCase()] = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
@param character a character to stop propagating keypresses to the browser.
|
||||
*/
|
||||
- (void)preventCharacterKeyFromPropagating:(CPString)character
|
||||
{
|
||||
CharacterKeysToPrevent[character.toLowerCase()] = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Clear the list of characters for which we are not sending keypresses to the browser.
|
||||
*/
|
||||
- (void)clearCharacterKeysToPreventFromPropagating
|
||||
{
|
||||
CharacterKeysToPrevent = {};
|
||||
}
|
||||
|
||||
/*!
|
||||
Prevent these keyCodes from sending their keypresses to the browser.
|
||||
@param keyCodes an array of keycodes to prevent propagation.
|
||||
*/
|
||||
- (void)preventKeyCodesFromPropagating:(CPArray)keyCodes
|
||||
{
|
||||
for(var i=keyCodes.length; i>0; i--)
|
||||
KeyCodesToPrevent[keyCodes[i-1]] = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Prevent this keyCode from sending its key events to the browser.
|
||||
@param keyCode a keycode to prevent propagation.
|
||||
*/
|
||||
- (void)preventKeyCodeFromPropagating:(CPString)keyCode
|
||||
{
|
||||
KeyCodesToPrevent[keyCode] = YES;
|
||||
}
|
||||
|
||||
/*!
|
||||
Clear the list of keyCodes for which we are not sending keypresses to the browser.
|
||||
*/
|
||||
- (void)clearKeyCodesToPreventFromPropagating
|
||||
{
|
||||
KeyCodesToPrevent = {};
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_bridgeMouseEvent:(DOMEvent)aDOMEvent
|
||||
{
|
||||
var theType = _overriddenEventType || aDOMEvent.type;
|
||||
|
||||
// IE's event order is down, up, up, dblclick, so we have create these events artificially.
|
||||
if (theType == CPDOMEventDoubleClick)
|
||||
{
|
||||
_overriddenEventType = CPDOMEventMouseDown;
|
||||
[self _bridgeMouseEvent:aDOMEvent];
|
||||
|
||||
_overriddenEventType = CPDOMEventMouseUp;
|
||||
[self _bridgeMouseEvent:aDOMEvent];
|
||||
|
||||
_overriddenEventType = nil;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var event,
|
||||
location = _CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY),
|
||||
timestamp = aDOMEvent.timeStamp ? aDOMEvent.timeStamp : new Date(),
|
||||
sourceElement = (aDOMEvent.target || aDOMEvent.srcElement),
|
||||
windowNumber = 0,
|
||||
modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
|
||||
(aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
|
||||
(aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
|
||||
(aDOMEvent.metaKey ? CPCommandKeyMask : 0);
|
||||
|
||||
StopDOMEventPropagation = YES;
|
||||
|
||||
if (_mouseDownWindow)
|
||||
windowNumber = [_mouseDownWindow windowNumber];
|
||||
else
|
||||
{
|
||||
var theWindow = [self hitTest:location];
|
||||
|
||||
if (aDOMEvent.type == CPDOMEventMouseDown && theWindow)
|
||||
_mouseDownWindow = theWindow;
|
||||
|
||||
windowNumber = [theWindow windowNumber];
|
||||
}
|
||||
|
||||
if (windowNumber)
|
||||
{
|
||||
var windowFrame = CPApp._windows[windowNumber]._frame;
|
||||
|
||||
location.x -= _CGRectGetMinX(windowFrame);
|
||||
location.y -= _CGRectGetMinY(windowFrame);
|
||||
}
|
||||
|
||||
switch (theType)
|
||||
{
|
||||
case CPDOMEventMouseUp: if(_mouseIsDown)
|
||||
{
|
||||
event = [CPEvent mouseEventWithType:CPLeftMouseUp location:location modifierFlags:modifierFlags
|
||||
timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1
|
||||
clickCount:CPDOMEventGetClickCount(_lastMouseUp, timestamp, location) pressure:0];
|
||||
|
||||
_mouseIsDown = NO;
|
||||
_lastMouseUp = event;
|
||||
_mouseDownWindow = nil;
|
||||
}
|
||||
|
||||
if(_DOMEventMode)
|
||||
{
|
||||
_DOMEventMode = NO;
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CPDOMEventMouseDown: if (ExcludedDOMElements[sourceElement.tagName] && sourceElement != _DOMFocusElement)
|
||||
{
|
||||
_DOMEventMode = YES;
|
||||
_mouseIsDown = YES;
|
||||
|
||||
//fake a down and up event so that event tracking mode will work correctly
|
||||
[CPApp sendEvent:[CPEvent mouseEventWithType:CPLeftMouseDown location:location modifierFlags:modifierFlags
|
||||
timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1
|
||||
clickCount:CPDOMEventGetClickCount(_lastMouseDown, timestamp, location) pressure:0]];
|
||||
|
||||
[CPApp sendEvent:[CPEvent mouseEventWithType:CPLeftMouseUp location:location modifierFlags:modifierFlags
|
||||
timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1
|
||||
clickCount:CPDOMEventGetClickCount(_lastMouseDown, timestamp, location) pressure:0]];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
event = [CPEvent mouseEventWithType:CPLeftMouseDown location:location modifierFlags:modifierFlags
|
||||
timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1
|
||||
clickCount:CPDOMEventGetClickCount(_lastMouseDown, timestamp, location) pressure:0];
|
||||
|
||||
_mouseIsDown = YES;
|
||||
_lastMouseDown = event;
|
||||
|
||||
break;
|
||||
|
||||
case CPDOMEventMouseMoved: if (_DOMEventMode)
|
||||
return;
|
||||
|
||||
event = [CPEvent mouseEventWithType:_mouseIsDown ? CPLeftMouseDragged : CPMouseMoved
|
||||
location:location modifierFlags:modifierFlags timestamp:timestamp
|
||||
windowNumber:windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0];
|
||||
|
||||
_currentMousePosition = _CGPointMake(aDOMEvent.clientX, aDOMEvent.clientY);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (event)
|
||||
{
|
||||
event._DOMEvent = aDOMEvent;
|
||||
|
||||
[CPApp sendEvent:event];
|
||||
}
|
||||
|
||||
if (StopDOMEventPropagation)
|
||||
CPDOMEventStop(aDOMEvent);
|
||||
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
objj_exception_report(anException, {path:@"CPDOMWindowBridge.j"});
|
||||
}
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_bridgeKeyEvent:(DOMEvent)aDOMEvent
|
||||
{
|
||||
try
|
||||
{
|
||||
var event,
|
||||
timestamp = aDOMEvent.timeStamp ? aDOMEvent.timeStamp : new Date(),
|
||||
sourceElement = (aDOMEvent.target || aDOMEvent.srcElement),
|
||||
windowNumber = [[CPApp keyWindow] windowNumber],
|
||||
modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
|
||||
(aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
|
||||
(aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
|
||||
(aDOMEvent.metaKey ? CPCommandKeyMask : 0);
|
||||
|
||||
if (ExcludedDOMElements[sourceElement.tagName] && sourceElement != _DOMFocusElement && sourceElement != _DOMPasteboardElement)
|
||||
return;
|
||||
|
||||
//We want to stop propagation if this is a command key AND this character or keycode has been added to our blacklist
|
||||
StopDOMEventPropagation = !(modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) ||
|
||||
CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] ||
|
||||
KeyCodesToPrevent[aDOMEvent.keyCode];
|
||||
|
||||
var isNativePasteEvent = NO,
|
||||
isNativeCopyOrCutEvent = NO;
|
||||
|
||||
switch (aDOMEvent.type)
|
||||
{
|
||||
case CPDOMEventKeyDown: // Grab and store the keycode now since it is correct and consistent at this point.
|
||||
_keyCode = aDOMEvent.keyCode;
|
||||
|
||||
var characters = String.fromCharCode(_keyCode).toLowerCase();
|
||||
|
||||
// If this could be a native PASTE event, then we need to further examine it before
|
||||
// sending a CPEvent. Select our element to see if anything gets pasted in it.
|
||||
if (characters == "v" && (modifierFlags & CPPlatformActionKeyMask))
|
||||
{
|
||||
_DOMPasteboardElement.select();
|
||||
_DOMPasteboardElement.value = "";
|
||||
|
||||
isNativePasteEvent = YES;
|
||||
}
|
||||
|
||||
// Normally we return now because we let keypress send the actual CPEvent keyDown event, since we don't have
|
||||
// a complete set of information yet.
|
||||
|
||||
// However, of this could be a native COPY event, we need to let the normal event-process take place so it
|
||||
// can capture our internal Cappuccino pasteboard.
|
||||
else if ((characters == "c" || characters == "x") && (modifierFlags & CPPlatformActionKeyMask))
|
||||
isNativeCopyOrCutEvent = YES;
|
||||
|
||||
// Also, certain browsers (IE and Safari), have broken keyboard supportwhere they don't send keypresses for certain events.
|
||||
// So, allow the keypress event to handle the event if we are not a browser with broken (remedial) key support...
|
||||
else if (!CPFeatureIsCompatible(CPJavascriptRemedialKeySupport))
|
||||
return;
|
||||
|
||||
// Or, if this is not one of those special keycodes, and also not a ctrl+event
|
||||
else if (!KeyCodesWithoutKeyPressEvents[_keyCode] && (_keyCode == CTRL_KEY_CODE || !(modifierFlags & CPControlKeyMask)))
|
||||
return;
|
||||
|
||||
// If this is in fact our broke state, continue to keypress and send the keydown.
|
||||
case CPDOMEventKeyPress:
|
||||
// If the source of this event is our pasteboard element, then simply let it continue
|
||||
// as normal, so that the paste event can successfully complete.
|
||||
if ((aDOMEvent.target || aDOMEvent.srcElement) == _DOMPasteboardElement)
|
||||
return;
|
||||
|
||||
var keyCode = _keyCode,
|
||||
charCode = aDOMEvent.keyCode || aDOMEvent.charCode,
|
||||
isARepeat = (_charCodes[keyCode] != nil);
|
||||
|
||||
_charCodes[keyCode] = charCode;
|
||||
|
||||
var characters = String.fromCharCode(charCode),
|
||||
charactersIgnoringModifiers = characters.toLowerCase();
|
||||
|
||||
event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags
|
||||
timestamp:timestamp windowNumber:windowNumber context:nil
|
||||
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:keyCode];
|
||||
|
||||
if (isNativePasteEvent)
|
||||
{
|
||||
_pasteboardKeyDownEvent = event;
|
||||
|
||||
window.setTimeout(function () { [self _checkPasteboardElement] }, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CPDOMEventKeyUp: var keyCode = aDOMEvent.keyCode,
|
||||
charCode = _charCodes[keyCode];
|
||||
|
||||
_charCodes[keyCode] = nil;
|
||||
|
||||
var characters = String.fromCharCode(charCode),
|
||||
charactersIgnoringModifiers = characters.toLowerCase();
|
||||
|
||||
if (!(modifierFlags & CPShiftKeyMask))
|
||||
characters = charactersIgnoringModifiers;
|
||||
|
||||
event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags
|
||||
timestamp: timestamp windowNumber:windowNumber context:nil
|
||||
characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode];
|
||||
break;
|
||||
}
|
||||
|
||||
if (event)
|
||||
{
|
||||
event._DOMEvent = aDOMEvent;
|
||||
|
||||
[CPApp sendEvent:event];
|
||||
|
||||
if (isNativeCopyOrCutEvent)
|
||||
{
|
||||
var pasteboard = [CPPasteboard generalPasteboard],
|
||||
types = [pasteboard types];
|
||||
|
||||
// If this is a native copy event, then check if the pasteboard has anything in it.
|
||||
if (types.length)
|
||||
{
|
||||
if ([types indexOfObjectIdenticalTo:CPStringPboardType] != CPNotFound)
|
||||
_DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType];
|
||||
else
|
||||
_DOMPasteboardElement.value = [pasteboard _generateStateUID];
|
||||
|
||||
_DOMPasteboardElement.select();
|
||||
|
||||
window.setTimeout(function() { [self _clearPasteboardElement]; }, 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (StopDOMEventPropagation)
|
||||
CPDOMEventStop(aDOMEvent);
|
||||
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
objj_exception_report(anException, {path:@"CPDOMWindowBridge.j"});
|
||||
}
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_bridgeScrollEvent:(DOMEvent)aDOMEvent
|
||||
{
|
||||
if(!aDOMEvent)
|
||||
aDOMEvent = window.event;
|
||||
|
||||
try
|
||||
{
|
||||
var deltaX = 0.0,
|
||||
deltaY = 0.0,
|
||||
windowNumber = 0,
|
||||
location = CGPointMake(_currentMousePosition.x, _currentMousePosition.y),
|
||||
timestamp = aDOMEvent.timeStamp ? aDOMEvent.timeStamp : new Date(),
|
||||
modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) |
|
||||
(aDOMEvent.ctrlKey ? CPControlKeyMask : 0) |
|
||||
(aDOMEvent.altKey ? CPAlternateKeyMask : 0) |
|
||||
(aDOMEvent.metaKey ? CPCommandKeyMask : 0);
|
||||
|
||||
StopDOMEventPropagation = YES;
|
||||
|
||||
windowNumber = [[self hitTest:location] windowNumber];
|
||||
|
||||
if (!windowNumber)
|
||||
return;
|
||||
|
||||
var windowFrame = CPApp._windows[windowNumber]._frame;
|
||||
|
||||
location.x -= CGRectGetMinX(windowFrame);
|
||||
location.y -= CGRectGetMinY(windowFrame);
|
||||
|
||||
if(typeof aDOMEvent.wheelDeltaX != "undefined")
|
||||
{
|
||||
deltaX = aDOMEvent.wheelDeltaX / 120.0;
|
||||
deltaY = aDOMEvent.wheelDeltaY / 120.0;
|
||||
}
|
||||
|
||||
else if (aDOMEvent.wheelDelta)
|
||||
deltaY = aDOMEvent.wheelDelta / 120.0;
|
||||
|
||||
else if (aDOMEvent.detail)
|
||||
deltaY = -aDOMEvent.detail / 3.0;
|
||||
|
||||
else
|
||||
return;
|
||||
|
||||
if(!CPFeatureIsCompatible(CPJavaScriptNegativeMouseWheelValues))
|
||||
{
|
||||
deltaX = -deltaX;
|
||||
deltaY = -deltaY;
|
||||
}
|
||||
|
||||
var event = [CPEvent mouseEventWithType:CPScrollWheel location:location modifierFlags:modifierFlags
|
||||
timestamp:timestamp windowNumber:windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0 ];
|
||||
|
||||
event._DOMEvent = aDOMEvent;
|
||||
event._deltaX = ROUND(deltaX * 1.5);
|
||||
event._deltaY = ROUND(deltaY * 1.5);
|
||||
|
||||
[CPApp sendEvent:event];
|
||||
|
||||
if (StopDOMEventPropagation)
|
||||
CPDOMEventStop(aDOMEvent);
|
||||
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
objj_exception_report(anException, {path:@"CPDOMWindowBridge.j"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_bridgeResizeEvent:(DOMEvent)aDOMEvent
|
||||
{
|
||||
try
|
||||
{
|
||||
// FIXME: This is not the right way to do this.
|
||||
// We should pay attention to mouse down and mouse up in conjunction with this.
|
||||
//window.liveResize = YES;
|
||||
|
||||
var oldSize = _frame.size;
|
||||
|
||||
// window.liveResize = YES?
|
||||
_frame = CPDOMWindowGetFrame(_DOMWindow);
|
||||
_contentBounds.size = CGSizeCreateCopy(_frame.size);
|
||||
|
||||
var levels = _windowLevels,
|
||||
layers = _windowLayers,
|
||||
levelCount = levels.length;
|
||||
|
||||
while (levelCount--)
|
||||
{
|
||||
var windows = [layers objectForKey:levels[levelCount]]._windows,
|
||||
windowCount = windows.length;
|
||||
|
||||
while (windowCount--)
|
||||
[windows[windowCount] resizeWithOldBridgeSize:oldSize];
|
||||
}
|
||||
|
||||
//window.liveResize = NO;
|
||||
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
objj_exception_report(anException, {path:@"CPDOMWindowBridge.j"});
|
||||
}
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_checkPasteboardElement
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = _DOMPasteboardElement.value;
|
||||
|
||||
if ([value length])
|
||||
{
|
||||
var pasteboard = [CPPasteboard generalPasteboard];
|
||||
|
||||
if ([pasteboard _stateUID] != value)
|
||||
{
|
||||
[pasteboard declareTypes:[CPStringPboardType] owner:self];
|
||||
|
||||
[pasteboard setString:value forType:CPStringPboardType];
|
||||
}
|
||||
}
|
||||
|
||||
[self _clearPasteboardElement];
|
||||
|
||||
[CPApp sendEvent:_pasteboardKeyDownEvent];
|
||||
|
||||
_pasteboardKeyDownEvent = nil;
|
||||
|
||||
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
|
||||
}
|
||||
catch (anException)
|
||||
{
|
||||
objj_exception_report(anException, {path:@"CPDOMWindowBridge.j"});
|
||||
}
|
||||
}
|
||||
|
||||
/* @ignore */
|
||||
- (void)_clearPasteboardElement
|
||||
{
|
||||
_DOMPasteboardElement.value = "";
|
||||
_DOMPasteboardElement.blur();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
var CLICK_SPACE_DELTA = 5.0,
|
||||
CLICK_TIME_DELTA = (typeof document != "undefined" && document.addEventListener) ? 350.0 : 1000.0;
|
||||
|
||||
var CPDOMEventGetClickCount = function(aComparisonEvent, aTimestamp, aLocation)
|
||||
{
|
||||
if (!aComparisonEvent)
|
||||
return 1;
|
||||
|
||||
var comparisonLocation = [aComparisonEvent locationInWindow];
|
||||
|
||||
return (aTimestamp - [aComparisonEvent timestamp] < CLICK_TIME_DELTA &&
|
||||
ABS(comparisonLocation.x - aLocation.x) < CLICK_SPACE_DELTA &&
|
||||
ABS(comparisonLocation.y - aLocation.y) < CLICK_SPACE_DELTA) ? [aComparisonEvent clickCount] + 1 : 1;
|
||||
}
|
||||
|
||||
var CPDOMEventStop = function(aDOMEvent)
|
||||
{
|
||||
// IE Model
|
||||
aDOMEvent.cancelBubble = true;
|
||||
aDOMEvent.returnValue = false;
|
||||
|
||||
// W3C Model
|
||||
if (aDOMEvent.preventDefault)
|
||||
aDOMEvent.preventDefault();
|
||||
|
||||
if (aDOMEvent.stopPropagation)
|
||||
aDOMEvent.stopPropagation();
|
||||
|
||||
if (aDOMEvent.type == CPDOMEventMouseDown)
|
||||
{
|
||||
CPSharedDOMWindowBridge._DOMFocusElement.focus();
|
||||
CPSharedDOMWindowBridge._DOMFocusElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* CPDOMWindowLayer.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPArray.j>
|
||||
@import <Foundation/CPObject.j>
|
||||
|
||||
#include "CPDOMDisplayServer.h"
|
||||
|
||||
|
||||
@implementation CPDOMWindowLayer : CPObject
|
||||
{
|
||||
int _level;
|
||||
CPArray _windows;
|
||||
DOMElement _DOMElement;
|
||||
}
|
||||
|
||||
- (id)initWithLevel:(int)aLevel
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
if (self)
|
||||
{
|
||||
_level = aLevel;
|
||||
|
||||
_windows = [];
|
||||
|
||||
_DOMElement = document.createElement("div");
|
||||
_DOMElement.style.position = "absolute";
|
||||
_DOMElement.style.top = "0px";
|
||||
_DOMElement.style.left = "0px";
|
||||
_DOMElement.style.width = "1px";
|
||||
_DOMElement.style.height = "1px";
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (int)level
|
||||
{
|
||||
return _level;
|
||||
}
|
||||
|
||||
- (void)removeWindow:(CPWindow)aWindow
|
||||
{
|
||||
var index = aWindow._index,
|
||||
count = _windows.length - 1;
|
||||
|
||||
CPDOMDisplayServerRemoveChild(_DOMElement, aWindow._DOMElement);
|
||||
|
||||
[_windows removeObjectAtIndex:aWindow._index];
|
||||
|
||||
for (; index < count; ++index)
|
||||
{
|
||||
_windows[index]._index = index;
|
||||
_windows[index]._DOMElement.style.zIndex = index;
|
||||
}
|
||||
|
||||
aWindow._isVisible = NO;
|
||||
}
|
||||
|
||||
- (void)insertWindow:(CPWindow)aWindow atIndex:(unsigned)anIndex
|
||||
{
|
||||
// We will have to adjust the z-index of all windows starting at this index.
|
||||
var count = [_windows count],
|
||||
zIndex = (anIndex == CPNotFound ? count : anIndex),
|
||||
isVisible = aWindow._isVisible;
|
||||
|
||||
// If the window is already a resident of this layer, remove it.
|
||||
if (isVisible)
|
||||
{
|
||||
zIndex = MIN(zIndex, aWindow._index);
|
||||
[_windows removeObjectAtIndex:aWindow._index];
|
||||
}
|
||||
else
|
||||
++count;
|
||||
|
||||
if (anIndex == CPNotFound || anIndex >= count)
|
||||
[_windows addObject:aWindow];
|
||||
else
|
||||
[_windows insertObject:aWindow atIndex:anIndex];
|
||||
|
||||
// Adjust all the affected z-indexes.
|
||||
for (; zIndex < count; ++zIndex)
|
||||
{
|
||||
_windows[zIndex]._index = zIndex;
|
||||
_windows[zIndex]._DOMElement.style.zIndex = zIndex;
|
||||
}
|
||||
// If the window is not already a resident of this layer, add it.
|
||||
if (!isVisible)
|
||||
{
|
||||
CPDOMDisplayServerAppendChild(_DOMElement, aWindow._DOMElement);
|
||||
|
||||
aWindow._isVisible = YES;
|
||||
|
||||
if ([aWindow styleMask] & CPBorderlessBridgeWindowMask)
|
||||
[aWindow setFrame:[aWindow._bridge contentBounds]];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Platform.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Francisco Tolmasky.
|
||||
* Copyright 2008, 280 North, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#define PLATFORM(FEATURE) (defined( PLATFORM_##FEATURE ) && PLATFORM_##FEATURE)
|
||||
@@ -0,0 +1,58 @@
|
||||
These graphics come from the Tango Desktop Project (http://tango.freedesktop.org/Tango_Desktop_Project).
|
||||
They are released under the Creative Commons Attribution Share-Alike license, full text below.
|
||||
|
||||
|
||||
(license available from CC here: http://creativecommons.org/licenses/by-sa/2.5/)
|
||||
|
||||
License
|
||||
|
||||
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||
|
||||
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
|
||||
|
||||
1. Definitions
|
||||
|
||||
"Collective Work" means a work, such as a periodical issue, anthology or encyclopedia, in which the Work in its entirety in unmodified form, along with a number of other contributions, constituting separate and independent works in themselves, are assembled into a collective whole. A work that constitutes a Collective Work will not be considered a Derivative Work (as defined below) for the purposes of this License.
|
||||
"Derivative Work" means a work based upon the Work or upon the Work and other pre-existing works, such as a translation, musical arrangement, dramatization, fictionalization, motion picture version, sound recording, art reproduction, abridgment, condensation, or any other form in which the Work may be recast, transformed, or adapted, except that a work that constitutes a Collective Work will not be considered a Derivative Work for the purpose of this License. For the avoidance of doubt, where the Work is a musical composition or sound recording, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered a Derivative Work for the purpose of this License.
|
||||
"Licensor" means the individual or entity that offers the Work under the terms of this License.
|
||||
"Original Author" means the individual or entity who created the Work.
|
||||
"Work" means the copyrightable work of authorship offered under the terms of this License.
|
||||
"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
|
||||
"License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike.
|
||||
2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws.
|
||||
|
||||
3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
|
||||
|
||||
to reproduce the Work, to incorporate the Work into one or more Collective Works, and to reproduce the Work as incorporated in the Collective Works;
|
||||
to create and reproduce Derivative Works;
|
||||
to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission the Work including as incorporated in Collective Works;
|
||||
to distribute copies or phonorecords of, display publicly, perform publicly, and perform publicly by means of a digital audio transmission Derivative Works.
|
||||
For the avoidance of doubt, where the work is a musical composition:
|
||||
|
||||
Performance Royalties Under Blanket Licenses. Licensor waives the exclusive right to collect, whether individually or via a performance rights society (e.g. ASCAP, BMI, SESAC), royalties for the public performance or public digital performance (e.g. webcast) of the Work.
|
||||
Mechanical Rights and Statutory Royalties. Licensor waives the exclusive right to collect, whether individually or via a music rights society or designated agent (e.g. Harry Fox Agency), royalties for any phonorecord You create from the Work ("cover version") and distribute, subject to the compulsory license created by 17 USC Section 115 of the US Copyright Act (or the equivalent in other jurisdictions).
|
||||
Webcasting Rights and Statutory Royalties. For the avoidance of doubt, where the Work is a sound recording, Licensor waives the exclusive right to collect, whether individually or via a performance-rights society (e.g. SoundExchange), royalties for the public digital performance (e.g. webcast) of the Work, subject to the compulsory license created by 17 USC Section 114 of the US Copyright Act (or the equivalent in other jurisdictions).
|
||||
The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. All rights not expressly granted by Licensor are hereby reserved.
|
||||
|
||||
4. Restrictions.The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
|
||||
|
||||
You may distribute, publicly display, publicly perform, or publicly digitally perform the Work only under the terms of this License, and You must include a copy of, or the Uniform Resource Identifier for, this License with every copy or phonorecord of the Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Work that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Work itself to be made subject to the terms of this License. If You create a Collective Work, upon notice from any Licensor You must, to the extent practicable, remove from the Collective Work any credit as required by clause 4(c), as requested. If You create a Derivative Work, upon notice from any Licensor You must, to the extent practicable, remove from the Derivative Work any credit as required by clause 4(c), as requested.
|
||||
You may distribute, publicly display, publicly perform, or publicly digitally perform a Derivative Work only under the terms of this License, a later version of this License with the same License Elements as this License, or a Creative Commons iCommons license that contains the same License Elements as this License (e.g. Attribution-ShareAlike 2.5 Japan). You must include a copy of, or the Uniform Resource Identifier for, this License or other license specified in the previous sentence with every copy or phonorecord of each Derivative Work You distribute, publicly display, publicly perform, or publicly digitally perform. You may not offer or impose any terms on the Derivative Works that alter or restrict the terms of this License or the recipients' exercise of the rights granted hereunder, and You must keep intact all notices that refer to this License and to the disclaimer of warranties. You may not distribute, publicly display, publicly perform, or publicly digitally perform the Derivative Work with any technological measures that control access or use of the Work in a manner inconsistent with the terms of this License Agreement. The above applies to the Derivative Work as incorporated in a Collective Work, but this does not require the Collective Work apart from the Derivative Work itself to be made subject to the terms of this License.
|
||||
If you distribute, publicly display, publicly perform, or publicly digitally perform the Work or any Derivative Works or Collective Works, You must keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or (ii) if the Original Author and/or Licensor designate another party or parties (e.g. a sponsor institute, publishing entity, journal) for attribution in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; the title of the Work if supplied; to the extent reasonably practicable, the Uniform Resource Identifier, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and in the case of a Derivative Work, a credit identifying the use of the Work in the Derivative Work (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). Such credit may be implemented in any reasonable manner; provided, however, that in the case of a Derivative Work or Collective Work, at a minimum such credit will appear where any other comparable authorship credit appears and in a manner at least as prominent as such other comparable authorship credit.
|
||||
5. Representations, Warranties and Disclaimer
|
||||
|
||||
UNLESS OTHERWISE AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE MATERIALS, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||
|
||||
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
7. Termination
|
||||
|
||||
This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works or Collective Works from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
|
||||
Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
|
||||
8. Miscellaneous
|
||||
|
||||
Each time You distribute or publicly digitally perform the Work or a Collective Work, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
|
||||
Each time You distribute or publicly digitally perform a Derivative Work, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
|
||||
If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
|
||||
No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
|
||||
This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 437 B |
|
After Width: | Height: | Size: 471 B |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |