Compare commits

...
Author SHA1 Message Date
daboe01andGitHub 03195b66bc removed: unused local variable charCode 2024-09-21 19:12:18 +02:00
daboe01andGitHub 2349aacc3e Update README.markdown 2024-07-21 21:34:53 +02:00
daboe01andGitHub 1be7bbe0dc Update README.markdown 2024-07-21 21:32:22 +02:00
daboe01andGitHub ea332d5492 Update README.markdown 2024-07-21 21:30:54 +02:00
daboe01andGitHub aedede7381 Update README.markdown 2024-07-21 21:22:52 +02:00
daboe01andGitHub d4cd46c331 Update README.markdown
stringWithFormat returns autoreleased instances iirc
2024-07-21 21:22:05 +02:00
daboe01andGitHub 62450c3bb1 Update README.markdown 2024-07-21 21:19:16 +02:00
daboe01andGitHub bafba81f56 Update README.markdown 2024-07-21 21:18:07 +02:00
daboe01andGitHub 651b445c75 Update README.markdown 2024-07-21 21:17:38 +02:00
daboe01andGitHub 5adc3c6a99 Update README.markdown 2024-07-21 21:17:04 +02:00
daboe01andGitHub 4914ff8205 Update README.markdown 2024-07-21 21:14:49 +02:00
daboe01andGitHub 783205c731 Update README.markdown 2024-07-21 19:32:24 +02:00
daboe01andGitHub 00ab2d1f3a Update README.markdown 2024-07-21 19:28:21 +02:00
daboe01andGitHub 98dcbdd87a Update README.markdown 2024-07-21 19:24:58 +02:00
daboe01andGitHub 4a936a6eef README.markdown: reduced redundant introduction 2024-07-21 19:10:41 +02:00
daboe01andGitHub ef7ee1b699 Merge pull request #3089 from enquora/README-refinements
Readme refinements
2024-07-21 19:06:48 +02:00
daboe01andGitHub 4e70c1fb10 Merge branch 'main' into README-refinements 2024-07-21 19:02:52 +02:00
daboe01andGitHub 21d4fa5176 Update README.markdown 2024-07-21 13:35:07 +02:00
daboe01andGitHub f16b2e8243 Update README.markdown 2024-07-21 13:32:34 +02:00
daboe01andGitHub 4e550a22b0 fixed: markdown syntax errors 2024-07-21 13:30:34 +02:00
David Richardson c2404d3c67 Reduce references to Cocoa, annotate remaining ones to indicate it is a registered trademark 2024-07-20 09:50:18 -06:00
David Richardson f1a5467d04 Refine and re-order benefits section 2024-07-20 09:19:56 -06:00
David Richardson 512486f2e8 Increase priority/visibility of toolchain stability as a benefit 2024-07-20 08:49:52 -06:00
daboe01andGitHub f1d95fa883 Merge pull request #3087 from enquora/README-refinements
README refinements
2024-07-20 16:48:52 +02:00
David Richardson 31cf9b8527 Add missing statement terminator in code example 2024-07-20 08:43:10 -06:00
David Richardson e0e8e8c6cc Clean up porting example gone wrong, further edits to increase brevity 2024-07-20 08:39:03 -06:00
daboe01andGitHub 6653690d0c Merge pull request #3086 from daboe01/segmentedcontrol-theme-fix
fixed: 1xp glitch with CPSegmentedControl in safari
2024-07-20 13:42:08 +02:00
David Richardson e63f59d33e Update source code installation to reflex new jake dist task 2024-07-19 16:08:20 -06:00
David Richardson 3702c18df5 README refinements
General refinements.
Move npm quickstart instructions to a more prominent position.
Add licensing FAQ.
Add FAQ for porting code from Objective-C to Objective-J
2024-07-19 16:05:05 -06:00
daboe01 dfe215f34c fixed: 1xp glitch with CPSegmentedControl in safari 2024-07-19 06:36:58 +02:00
daboe01andGitHub 621a559585 Update README.markdown 2024-07-18 10:07:25 +02:00
daboe01andGitHub 61317eff71 Update README.markdown (swift vs. objective j) 2024-07-18 10:06:37 +02:00
daboe01andGitHub b314a951da Update README.markdown 2024-07-17 12:31:51 +02:00
daboe01andGitHub 1a1c105c71 Update README.markdown 2024-07-17 07:38:24 +02:00
daboe01andGitHub 3f8ee35ddb Update README.markdown 2024-07-17 07:27:03 +02:00
daboe01andGitHub d2ea74ea14 Update README.markdown 2024-07-17 06:41:35 +02:00
daboe01andGitHub 8e1fb771e0 Update README.markdown 2024-07-17 06:35:36 +02:00
daboe01andGitHub bbe5c82700 Merge pull request #3083 from michaelbach/patch-2
fixied: bindings for case  'only stop at tick marks'
2024-07-16 19:13:32 +02:00
daboe01andGitHub 95e3ff3349 wrap fix in if (_allowsTickMarkValuesOnly) 2024-07-16 19:12:04 +02:00
daboe01andGitHub 00c3b2083b formatting 2024-07-16 19:08:58 +02:00
daboe01andGitHub 8549858951 Merge pull request #3084 from cappuccino/narwhal
fixed: selection and backspace are recently broken in CPTokenField
2024-07-16 19:07:18 +02:00
daboe01andGitHub 4730c3f4b5 Update README.markdown 2024-07-16 19:06:38 +02:00
daboe01andGitHub 19ba66fd9d Merge branch 'main' into narwhal 2024-07-16 19:04:08 +02:00
daboe01andGitHub 589c26192d Merge pull request #2894 from daboe01/tokenfield-fr-fix
fixed: selection and backspace are recently broken in CPTokenField
2024-07-16 19:01:25 +02:00
David RichardsonandGitHub 8471aeea1b Merge pull request #3082 from michaelbach/patch-1
Update README.markdown
2024-07-16 09:43:32 -06:00
Michael BachandGitHub 58c39c14a5 Update CPSlider.j (fixing bindings for case 'only stop at tick marks'
This fixes bindings to also work when 'only stop at tick marks' is selected; formerly the code would crash silently with `setObjectValue`.
2024-07-16 16:15:57 +02:00
Michael BachandGitHub 2cfe45950b Update README.markdown
mistype (LTS → Node.js), typography (- → –) and slight presentation change (bullet list).
2024-07-16 10:22:18 +02:00
daboe01andGitHub 688671db6a Merge pull request #3081 from enquora/update-README
Modernize project README
2024-07-16 07:23:38 +02:00
David Richardson 1d2e50dfa2 FAQ section added 2024-07-15 13:20:39 -06:00
David Richardson 7a70036c8f Further content edits
Link to description of message passing languages.
Style and content edits.
2024-07-15 09:54:11 -06:00
David Richardson ab88a3b236 Add comment about loose-coupled nature of message-passing architectures 2024-07-14 15:05:16 -06:00
David Richardson 266e14063e Modernize project README 2024-07-14 14:00:07 -06:00
David RichardsonandGitHub 3af0296779 Merge pull request #3080 from enquora/re-enable-capp_lint
Re-enable installation of capp_lint
2024-07-05 15:22:06 -06:00
David Richardson 5ff05503a4 Re-enable installation of capp_lint
Requires Python 2, but this is true for XcodeCapp also.
Installs to /usr/bin/local, but must be manually installed using install script in source directory.
2024-07-05 15:14:43 -06:00
David RichardsonandGitHub 13e6782cf1 Merge pull request #3077 from enquora/ibtool-fix-node
Update nib2cib's Converter.j to handle xib files with deployment targ…
2024-07-03 13:32:46 -06:00
David Richardson 24052e6369 Improve inline documentation, code formatting 2024-07-03 13:05:07 -06:00
David RichardsonandGitHub 9b17ce5524 Merge pull request #3078 from enquora/XcodeCapp-fix-deprecations
Xcode capp fix deprecations
2024-07-03 12:20:21 -06:00
David Richardson e4c2d62eb0 Clean up deprecation notices
Reduce debugging noise by removing deprecation notices.
2024-07-03 09:05:32 -06:00
David Richardson 27fa37a7e1 Update dataFromPropertyList usage to remove deprecation warning 2024-07-03 09:05:32 -06:00
David Richardson 987da2b812 Remove deprecation warning when reading settings plist 2024-07-03 09:05:32 -06:00
David Richardson d1611bae12 Update deprecated NSCenterTextAlignment usage to NSTextAlignmentCenter 2024-07-03 09:05:32 -06:00
David Richardson 96a3c4b9dd Remove extraneous 'SettingsView.cib' file
Not a part of XcodeCapp project, unclear why it was included.
2024-07-03 09:05:32 -06:00
David Richardson 8ef40dced5 Update 'About' dialog
Use property values in Info.plist rather than hard-coded values in dialog view.
2024-07-03 09:05:32 -06:00
David Richardson 4a6ddb901f Clean up alignment of assignment statements 2024-07-03 09:05:32 -06:00
David Richardson a5094e0acc Update nib2cib's Converter.j to handle xib files with deployment targets of Xcode 11 or later 2024-07-02 16:39:17 -06:00
daboe01andGitHub fffcca592d Merge pull request #3047 from daboe01/patch-8
Prepare README.markdown for the upcoming release
2024-06-02 21:19:22 +02:00
Martin CarlbergandGitHub 341584c032 Change reference to branch main instead of node 2024-05-24 12:49:10 +02:00
Martin Carlberg a1e178d0d7 Merge remote-tracking branch 'origin/narwhal' 2024-05-24 12:18:28 +02:00
Martin CarlbergandGitHub c59687b6e2 Fixed: Use latest version of actions as the old ones are deprecated 2024-04-10 11:34:50 +02:00
Martin CarlbergandGitHub c2bd9f26a7 Fixed: Github Action build with Node 20 and 21 too 2024-04-10 11:19:10 +02:00
David RichardsonandGitHub 6d78b9b6df Merge pull request #3065 from enquora/update-XcodeCapp-project-structure
Update xcode capp project structure
2024-03-20 10:15:51 -06:00
David Richardson 0669ce06f2 Update version check in XcodeCapp Jakefile
Match deployment version set in XcodeCapp project file - macOS 10.13.
2024-03-20 10:07:25 -06:00
David Richardson 767d9378e3 Move deployment target back from macOS 10.15 to 10.13,
the earliest version on-hand for testing.
2024-03-20 09:48:09 -06:00
David Richardson c2d62b7a13 Update Info.plist
Set Display Name property to 'XcodeCapp'.
Set App Category to 'Developer Tools'.
2024-03-20 09:25:16 -06:00
David Richardson 10e33298a9 Fixes to allow successful building under Xcode 15
Manually remove DSTROOT occurrences from project.pbxproj.
Their presence causes Xcode build process to believe a reference cycle exists - preventing compilation.

Update minimum installation target to macOS 10.15
Earlier targets require presence of 'libarclite' - no longer included with Xcode 15.
2024-03-20 09:23:11 -06:00
David RichardsonandGitHub ecc88112d5 Merge pull request #3063 from enquora/narwhal-jsc-sonoma 2024-03-19 08:11:08 -06:00
daboe01andGitHub b190063264 Merge pull request #3060 from daboe01/new-releaseWhenClosed-support
new: support for releasedWhenClosed in CPWindow
2024-03-19 15:04:44 +01:00
David RichardsonandGitHub 0c9c0c0239 Merge pull request #3064 from enquora/fix-host-os-version-check
Allow building XcodeCapp under any version greater than Sierra
2024-03-19 08:03:18 -06:00
David Richardson ae333ecb77 Allow building XcodeCapp under any version greater than Sierra
Individual new OS releases had support added individually until now.
2024-03-18 10:09:46 -06:00
David Richardson 72a85d11df Fix narwhal-jsc build under Sonoma
Remove newly-unsupported linker flag
2024-03-18 09:57:59 -06:00
Martin Carlberg f33ba954f5 Fixed: critical severity vulnerability in package xmldom 2024-01-09 14:28:15 +01:00
daboe01 f642330306 new: support for releasedWhenClosed in CPWindow 2023-12-23 19:08:38 +01:00
daboe01andGitHub ae1a39322e Merge pull request #3053 from daboe01/CPDatePickerUsabilityImprovement
fixed: poor user experience when entering dates that are too high for…
2023-12-21 20:00:53 +01:00
daboe01 29502d10eb formatting 2023-12-21 19:59:38 +01:00
daboe01andGitHub 37550b9fb0 Merge pull request #3048 from michaelbach/fix-typo-in-CPView-(_ephereralSubviews)
CPView.j, fix typo: _ephereralSubviews → _ephemeralSubviews
2023-12-21 19:24:11 +01:00
daboe01andGitHub efd2ed1960 Merge pull request #3055 from daboe01/cpstring+-containsString
fixed: CPString did not have containsString:
2023-12-21 19:19:49 +01:00
Martin Carlberg 150e44dcd5 Fixed: The objj2objcskeleton tool stop generating output if a SelectorLiteralExpression was used in the source file 2023-05-31 14:32:38 +02:00
daboe01 3a164c8080 fixed: CPString did not have containsString: 2023-03-05 21:37:50 +01:00
daboe01 fb9223c6c3 fixed: poor user experience when entering dates that are too high for the current month 2023-02-25 21:30:42 +01:00
Martin Carlberg 511ce4b07d Merge branch 'master' into node 2022-12-09 15:13:17 +01:00
Martin Carlberg aea3fb5f07 Merge remote-tracking branch 'origin/es2022' into node 2022-12-09 13:59:48 +01:00
Martin CarlbergandGitHub 5932a07eb2 Fixed: Make sure Jakefile both work for async and sync calls
The callback variable was not set if the call to initilize was synchronous.
It is now set before so both sync and async will work
2022-12-09 10:15:38 +01:00
Martin CarlbergandGitHub 2b07c4b662 Fixed: Make sure Jakefile both work for async and sync calls
The callback variable was not set if the call to initilize was synchronous.
It is now set before so both sync and async will work
2022-12-09 10:14:52 +01:00
Martin CarlbergandGitHub bc689b91dc Fixed: Added 'npm update' to use latest modules 2022-12-08 17:26:57 +01:00
Martin CarlbergandGitHub c6a42781e7 Fixed: Github Action build with Node 18 and 19 too 2022-12-08 14:25:01 +01:00
Martin Carlberg c92aedf433 Fixed: Quick fix for XcodeCapp so it will run without python2 scripts 2022-11-21 16:41:16 +01:00
cacaodevandMartin Carlberg 7857f92f19 FIXED: CPLikePredicateOperatorType was matching substrings
Fixes #2955
2022-11-21 16:33:56 +01:00
cacaodevandGitHub 9361e5d401 FIXED: CPLikePredicateOperatorType was matching substrings
Fixes #2955
2022-11-10 10:49:01 +01:00
Michael Bach 6c6f69d4e5 CPView.j, fix typo: _ephereralSubviews → _ephemeralSubviews
This typo occurs only once, line #3462 references the correct spelling.
2022-11-01 18:04:21 +01:00
daboe01andGitHub e836226174 Prepare README.markdown for the upcoming release 2022-11-01 17:32:37 +01:00
Martin CarlbergandGitHub ad1862a253 Update CPURLConnection.j
New: Async method for requesting data
2022-10-06 11:24:56 +02:00
Martin Carlberg 4af137894a Fixed: Npm lock file with latest versions 2022-09-01 14:02:40 +02:00
Martin Carlberg 55aca1fde9 Fixed: Make sure we compile with es2022 as ecmaVersion in the parser 2022-09-01 13:53:52 +02:00
Alfred Wärnsäter 53e1e936d9 Reordered includes 2022-08-26 15:08:58 +02:00
Alfred Wärnsäter ff1448fb54 Fixed Objective-J framework using latest transpiler and parser 2022-08-26 12:25:40 +02:00
Alfred Wärnsäter 95fb19f311 New version 2022-08-25 17:57:57 +02:00
Alfred Wärnsäter 46d869a62f Updated lock file 2022-08-25 15:46:09 +02:00
Alfred Wärnsäter 03140dbf64 Updated Terser and added acorn walk dependency 2022-08-25 15:45:23 +02:00
Alfred Wärnsäter 052c96e05a Changes for compatibility with ES2022 transpiler 2022-08-25 15:34:19 +02:00
Alfred Wärnsäter 9a88f9c324 Added ES2022 as compile option 2022-08-25 15:32:43 +02:00
Alfred Wärnsäter 747ea627c9 Removed debugger statement 2022-08-25 15:30:02 +02:00
daboe01andMartin Carlberg 89c0ab405a Fixed: å and Å did not work well on scandinavian keyboards in CPTextView (#3031) 2022-04-04 10:23:17 +02:00
daboe01andGitHub 04c2b98ae4 Fixed: å and Å did not work well on scandinavian keyboards in CPTextView (#3031) 2022-04-04 10:16:37 +02:00
Martin Carlberg 24b36cdd95 Fixed: Allow space in path to .xib/.cib file when running nib2cib 2022-03-29 10:39:04 +02:00
Martin Carlberg 3e83bd2363 Revert "Fixed: Before this PR, nib2cib failed if a color well or a scrollable text view was added in a CPBox (#2994)"
This reverts commit 6544aff88e.
2022-03-21 16:25:24 +01:00
Martin Carlberg 5c91a08eaf Revert "Fixed: Before this PR, nib2cib failed if a color well or a scrollable text view was added in a CPBox (#2994)"
This reverts commit 6544aff88e.
2022-03-21 16:24:17 +01:00
Martin Carlberg edfba15d6f Merge branch 'master' into node 2022-03-21 15:55:08 +01:00
Didier KorthoudtandGitHub 48e786e8b5 Fixed: This PR adapts views and custom views decoding to fix "not ready view" bugs (#2917) 2022-03-21 15:50:44 +01:00
daboe01andGitHub 1282f1ac0e Removed: Flash support (#2903) 2022-03-21 15:46:54 +01:00
KrisandGitHub ee2fb61fdd Fixed: Changed Cappuccino.doxygen EXCLUDE_PATTERNS to exclude nothing. (#2680)
Classes such as CPArray are no longer shown in the generated documentation

This should fix #1956
2022-03-21 15:42:46 +01:00
Martin CarlbergandGitHub 68a113e7ee Fixed: New Cocoa api that replace old deprecated for calculating content and frame size of scroll view (#3023)
Old deprecated api did not work as it was accessing ivars _horizontalScroller and _verticalScroller from class method.
New Cocoa api is added as described in Apple Developer Documentation.

Also added type CPScrollerStyle
2022-03-21 15:25:09 +01:00
Martin CarlbergandGitHub 3cf3b76446 Fixed: Correct init of CPTextView when running nib2cib (#3014) 2022-03-21 15:24:34 +01:00
Didier KorthoudtandGitHub d7d561d3a9 Fixed: Aristo3 - New _CPImageAndTextView & theme parameter (#2951) 2022-03-21 15:23:16 +01:00
David SiawandGitHub 73eeda3c09 Fixed: strange lines that appear in Google Chrome (#2863) 2022-03-21 15:14:14 +01:00
Didier KorthoudtandGitHub 3d514b5010 Fixed: Aristo3 - Various _CPImageAndTextView bugs (#2914) 2022-03-21 14:28:21 +01:00
Didier KorthoudtandGitHub 93fc8d29cc Fixed: Before this PR, imagesize would provide image size in points (that is, taking care of the image resolution) instead of pixels (#2965) 2022-03-21 14:12:51 +01:00
Didier KorthoudtandGitHub 145bf65542 Fixed: Before this PR, outlet connection decoding was done before views are ready (#2959) 2022-03-21 14:10:53 +01:00
daboe01andGitHub f21555bb6c Fixed: Width of text sizing was slightly off in chrome due to a Chrome bug (#2975)
This is a real bug in chrome. see https://bugs.chromium.org/p/chromium/issues/detail?id=1192834

Also fixed: ellipsis clipping in segmented controls
and
fixed: tooltip wrapping issues from fractional pixels
2022-03-21 14:08:45 +01:00
Martin CarlbergandGitHub be072a283f Fixed: Basic support for on/off/mixed state images for menuitems from the Interface Builder (#2991) 2022-03-21 13:38:39 +01:00
David RichardsonandGitHub 9e8d51c94d New: Add additional manual test for scaleUnitSquareToSize (#2979)
Currently failing to match behaviour of Objective-C equivalent.
2022-03-21 13:37:24 +01:00
Didier KorthoudtandGitHub b3051a5bd0 Fixed: Before this PR, CPBox followed the autoresizesSubviews flags while Cocoa NSBox always autoresizes its subviews, whatever the flag is set to. (#2992) 2022-03-21 13:35:22 +01:00
Didier KorthoudtandGitHub a523cfc381 Fixed: Before this PR, CGContextSetShadow wasn't initializing the shadow color (#2966) 2022-03-21 13:33:00 +01:00
Didier KorthoudtandGitHub 6544aff88e Fixed: Before this PR, nib2cib failed if a color well or a scrollable text view was added in a CPBox (#2994) 2022-03-21 13:29:31 +01:00
Michael BachandGitHub 7c1fdbc720 Fixed: Modernise "document.write" in «index.html» to "innerHTML" (#2998) 2022-03-21 13:26:11 +01:00
daboe01andGitHub bd84804e32 Fixed: Usage of eval instead of JSON.parse for parsing JSON (#3016)
Using eval for parsing JSON is considered bad practice and is also slower
2022-03-21 13:07:44 +01:00
David RichardsonandGitHub 1f2a257bab Fixed: CPBox - Suppress superfluous warnings and expand documentation (#2996)
Warnings about borderColor, borderWidth, cornerRadius and fillColor are suppressed when boxType is CPBoxSeparator.
They are not relevant and needlessly clutter verbose output when debugging and should be suppressed.
2022-03-21 13:06:18 +01:00
daboe01andGitHub b0684929e2 Fixed: key event issues of handling escape in recent firefox … (#3020) 2022-03-21 13:02:37 +01:00
daboe01andGitHub 22dc587a8f Fixed: caret was blinking in non-editable text in CPTextView (#3022) 2022-03-21 13:00:40 +01:00
daboe01andMartin Carlberg 622fcf740e Update README.markdown
make wording of system requirements more concise
2022-03-21 12:58:55 +01:00
cacaodevandGitHub 77b59c8a18 Merge pull request #3024 from cappuccino/revert-2963-compile_deep_structured_project_in_browser
Revert "New: Support to compile deep structured project in the web browser"
2022-03-17 21:10:03 +01:00
cacaodevandGitHub 462a18930b Revert "New: Support to compile deep structured project in the web browser" 2022-03-17 21:07:16 +01:00
cacaodevandGitHub fb07def5fa Merge pull request #3017 from daboe01/fixed-sheet-animation-click-issue
fixed: freeze when clicking during sheet animation
2022-03-05 18:12:27 +01:00
cacaodevandGitHub a03ee6726e Merge pull request #2963 from mrcarlberg/compile_deep_structured_project_in_browser
New: Support to compile deep structured project in the web browser
2022-03-05 18:11:08 +01:00
daboe01 1ef49ef290 formatting 2022-01-23 13:42:12 +01:00
daboe01 76d52e07d4 fixed: freeze when clicking during sheet animation
previously, the app froze when clicking somewhere during sheet open/close animations.
these adversarial clicks are simply ignored by this PR
2022-01-23 13:39:33 +01:00
Martin Carlberg 044b0bb0d2 Fixed: NibApplication template for 'capp' to use correct build dir 2022-01-04 14:51:43 +01:00
Martin Carlberg 472b59ef80 Fixed: Allow space in path to .xib/.cib file 2022-01-04 14:51:43 +01:00
Martin Carlberg 6b559ce283 1.2.2 2021-12-03 14:02:00 +01:00
Martin Carlberg c17b806fd5 1.2.1 2021-12-03 14:00:14 +01:00
Martin Carlberg 1dcea8fe4c Added: .npmignore for easier "npm publish" 2021-12-03 14:00:02 +01:00
Martin Carlberg d72a52b9ce Merge branch 'master' into node 2021-12-03 12:36:27 +01:00
Martin Carlberg 61e01b08e5 Fixed: Rename the README back to the original name 2021-12-03 12:29:37 +01:00
Didier KorthoudtandGitHub 37756a5853 Fixed: nib2cib: checks if there's a defined superview in resizeWithOldSuperviewSize (#2995) 2021-11-23 13:47:54 +01:00
David RichardsonandGitHub b66690e304 New: Implement missing indexPath method for CPTreeNode (#2999) 2021-11-23 13:45:10 +01:00
Martin CarlbergandGitHub ccc6007bab Fixed: Bezel state instance variable _bezelState must remove current state when changing to a new state (#3001)
If the bezel state was changed and some attribute was set like an "image". The image would not be found as there were two states active and it would not match.
2021-11-23 13:42:36 +01:00
David RichardsonandGitHub 3179d56879 Fixed: Include commented code to hide mainMenu (#3008)
Add commented-out code to capp gen NibApplication template.
The technique itself is easy to forget and not obvious to newcomers.
It is included in the standard capp gen application template
and added here for completeness.
2021-11-23 13:41:30 +01:00
Martin CarlbergandGitHub b631032857 Fixed: Node build badge can be clicked to actions 2021-11-09 12:43:32 +01:00
Martin CarlbergandGitHub 49574d1c54 Fixed: Node build badge can be clicked to actions 2021-11-09 12:41:01 +01:00
Martin CarlbergandGitHub b7d7368ce6 Fixed: Include the Node build status badge at top 2021-11-09 11:49:30 +01:00
Martin CarlbergandGitHub e221156c11 Fixed: Changed name on Build & Test GitHub action 2021-11-09 11:43:39 +01:00
Martin CarlbergandGitHub 8e346acdce Fixed: Changed travis build badge to Github 2021-11-09 11:34:14 +01:00
Martin CarlbergandGitHub 7c36fa3f56 Fixed: "capp gen" template for NApplication uses environment variable CAPP_BUILD 2021-11-09 10:14:37 +01:00
Martin CarlbergandGitHub 642e254522 Fixed: Use upper case ENV variable 2021-11-09 10:07:35 +01:00
Martin CarlbergandGitHub 72aa1b8416 Fixed: Better name on the Build and Test Action 2021-11-08 17:11:43 +01:00
Martin CarlbergandGitHub edb1ef7f98 Fixed: Remove action on Node version 12 and 14
We use npm package-lock file version 2 and it is not supported by version 12 and 14
2021-11-08 13:21:40 +01:00
Martin CarlbergandGitHub 92beaba1d6 Fixed: Add Node version 12, 14 and 17 in action 2021-11-08 13:12:37 +01:00
Martin CarlbergandGitHub 3b61b1c85d Fixed: Use node branch 2021-11-08 13:07:40 +01:00
Martin CarlbergandGitHub a529e752b3 Create BuildAndTest.yml 2021-11-08 13:05:26 +01:00
Martin Carlberg d9791673df Fixed: Make sure we can build on Linux. Tested on Ubuntu 20.04 2021-11-07 13:41:01 +00:00
David RichardsonandGitHub a49a20e2c2 Fixed: nib2cib under Xcode 13 (#3006)
Backwards-compatible changes to accommodate different ibtool output from Xcode 13 onwards
2021-10-19 08:31:54 +02:00
Martin CarlbergandGitHub 8570476cff New: Added link to install the new Node version 2021-10-06 12:18:51 +02:00
Martin Carlberg d0923efa1a Fixed: Updated package-lock file. 2021-10-06 09:26:15 +02:00
Martin Carlberg eab8fe2d64 Fixed: Divided 'jake install' into 'jake dist' and 'jake install'
'jake dist' will do everything that 'jake install' does but creating the global symlinks
for the 'dist' binaries at the 'npm prefix' path.
2021-10-05 11:34:25 +02:00
Martin Carlberg c9e8b2970b Fixed: Moved out nib2cib functions to correct scope so they can be accessed by catch 2021-10-04 12:51:12 +02:00
Martin Carlberg 1440c156b2 Fixed: Make the path to the Frameworks directory for each package 2021-10-01 19:32:53 +02:00
Martin Carlberg ddfa4445ae Fixed: Move the 'objj' tool to the right place 2021-10-01 13:11:57 +02:00
Martin Carlberg 1f501e6429 Fixed: Make sure environment variables are included when running test-only 2021-10-01 13:10:46 +02:00
Martin Carlberg 7969f118de Fixed: Build in project home directory if no environment variables are set
Earlier the build directory was placed in the parent directory
2021-10-01 08:45:58 +02:00
Martin Carlberg 706166dc5c Fixed: Make sure we use the built parts of Cappuccino when building parts that is dependent of former.
This is done by passing the environment variable 'OBJJ_OPT' when spawning a new jake task. The content
of OBJJ_OPT will be parsed as arguments when starting an Objective-J tool
2021-09-29 09:18:12 +02:00
Martin Carlberg 4a8667e0f3 Fixed: Exit with error code when test cases did fail 2021-09-28 15:31:01 +02:00
Martin Carlberg 094fbcc67c Fixed: Move Terser from a development dependency to a regular dependency 2021-09-22 09:37:22 +02:00
Martin Carlberg d34c47c167 Fixed: Use preset variable from common.jake for build directory 2021-09-21 20:49:05 +02:00
Martin Carlberg 7226330514 Fixed: Create soft link to node_modules in a simpler way 2021-09-21 20:31:29 +02:00
Martin Carlberg 852b2bd5d9 Fixed: Missing enquote of path when creating build directory and soft link to node_modules 2021-09-21 17:56:14 +02:00
Martin Carlberg 3d8ecfad47 Fixed: Use unique temp directory for each run with nib2cib 2021-09-21 16:04:10 +02:00
Martin Carlberg 9e6f1e0dc3 Fixed: Timezone abbreviation was not working with the latest changes on all setups.
This is still not 100% but looks like it works a little bit better
2021-09-21 15:38:51 +02:00
Martin Carlberg 4deb7a3f51 Merge branch 'node' of github.com:cappuccino/cappuccino into node 2021-09-21 12:54:00 +02:00
Martin Carlberg 5b0b030435 Fixed: Exclude the dist folder except cappuccino/bin and objective-j/bin 2021-09-21 12:52:48 +02:00
Martin Carlberg 4edec2df2e 1.2.0 2021-09-21 12:34:10 +02:00
Martin Carlberg e0596bdf75 Fixed: Added latest package.json files 2021-09-21 12:34:04 +02:00
Martin Carlberg 6430463b5d Fixed: Included the latest binaries into the dist folder. 2021-09-21 12:31:45 +02:00
Martin Carlberg d9e27e549f Fixed: Removed compiler warnings in test case 2021-09-21 11:11:57 +02:00
Martin Carlberg 9f14dac5d2 Fixed: Cleanup of the output from our test cases 2021-09-21 11:11:25 +02:00
Martin Carlberg d70199ca58 Fixed: Asynchronous and output changes from the new Terser compressor 2021-09-21 11:09:09 +02:00
Martin Carlberg 6b1cdbd19c Fixed: Removed compiler warnings from test cases 2021-09-21 10:58:03 +02:00
Martin Carlberg 5b99757a0d Fixed: The compiler might inline the 'message send' call so we do it manually in these test cases. 2021-09-21 10:56:10 +02:00
Martin Carlberg 6c49116c68 Fixed: Use date string that is not as old as the previous one.
JavaScript now handles time a lot better. Times from before around 1900 is different in different
parts of the world so we just changed this test case to be a bit more modern.
2021-09-21 10:55:16 +02:00
Martin Carlberg 68f0cf27df Fixed: Bundle test cases must run asynchronous as a bundle will also load asynchronous in Node.
Also removed some compilation warnings from the test cases.
2021-09-21 10:51:17 +02:00
Martin Carlberg 89757e35e6 Fixed: Class Swapper should convert NSView -> CPView.
This is needed when a plain NSView is use without a Custom class name as it will otherwise try to create a NSView.
Also the Class Swapper is only used when the 'Instantiation' is check for 'Prefer code' under
the 'Runtime Behavior' header in the Document tab in Xcode. If this is not checked the '_CPCibCustomView'
is used instead of the Class Swapper.
2021-09-21 10:46:18 +02:00
Martin Carlberg 24bf09b70c Fixed: Abbreviation for a timezone is not handled in JavaScript. This changes makes it better but not perfect. 2021-09-21 10:30:26 +02:00
Martin Carlberg 0ed2709124 Fixed: Better message when loading a cib file and the class swapper doesn't find a class 2021-09-21 10:26:33 +02:00
Martin Carlberg a835202db1 Fixed: Adjusted test cases for Node. XMLHttpRequest now demands a protocol. 2021-09-20 11:00:37 +02:00
Martin Carlberg 830fbbab26 Fixed: Removed old Rhino adjustments in test cases 2021-09-20 10:51:14 +02:00
Martin Carlberg 119e11cfe2 Fixed: Allow 'await' for loading a bundle with the method 'loadWithDelegate:' 2021-09-20 09:19:05 +02:00
Martin Carlberg 511e18b44f Fixed: Wait for the bundle load to finish with the new async bundle load function 2021-09-17 21:55:48 +02:00
Martin Carlberg 2f00d93b22 Fixed: Must check in Node if path exists when doing 'fs.lstatSync' as it will throw if not. 2021-09-17 10:39:11 +02:00
Martin Carlberg 973fa1b978 Fixed: Misspelled function name 2021-09-17 10:37:03 +02:00
Martin Carlberg 79f40d24ae Fixed: Replace soft link to the directory 'node_modules' when it is already there. 2021-09-17 09:39:37 +02:00
Martin Carlberg fdff92e5c8 Fixed: Ask Node for a temporary directory instead of using '/tmp' 2021-09-17 09:38:29 +02:00
Martin Carlberg 653dd07162 Fixed: Include the tool 'objj2objcskeleton' in the installed binaries 2021-09-15 17:38:24 +02:00
Martin Carlberg b7968ca4a7 Fixed: Make sure output from the tool 'ojtest' gets to the terminal.
Also suspend stack trace etc. when the tests fails with a try ... catch.
2021-09-15 17:37:04 +02:00
Martin Carlberg 2a0a6449e5 Fixed: Converting the test cases in ToolTest.j to Node 2021-09-15 17:33:21 +02:00
Martin Carlberg b7c04d7d16 Fixed: Converted the tool 'objj2objcskeleton' for Node 2021-09-15 17:32:31 +02:00
Martin Carlberg ab4c87e69f Fixed: Removed warnings and cleanup for test cases when converting to Node 2021-09-15 17:31:20 +02:00
Martin Carlberg 8257632706 Fixed: When creating a method the function name can't start with '0_' 2021-09-15 17:28:07 +02:00
Martin Carlberg 3f8821b43b Fixed: Adjust arguments for the tool objj
This has changed now when running on Node.
2021-09-15 17:26:40 +02:00
Martin Carlberg f6b8bc0c45 Fixed: Include source files when compiling ojtest 2021-09-15 17:25:36 +02:00
Martin Carlberg 0618fa9bb2 Fixed: Added protocol to data source and delegate for outline view 2021-09-15 17:24:31 +02:00
Martin Carlberg 2e5a483dce Fixed: Added examples how to add macros in the 'capp gen' templates index[-debug].html files 2021-09-13 09:38:02 +02:00
Martin Carlberg e608e0dbf3 Fixed: Partly adapted Tooltest for Node 2021-09-10 15:56:12 +02:00
Martin Carlberg a88e316a94 Fixed: Compile OJTest with source map for easier debugging 2021-09-10 15:55:40 +02:00
Martin Carlberg 51ffa256ca Fixed: Preprocessor Output test adapted for Node 2021-09-10 15:55:01 +02:00
Martin Carlberg 52bd654850 Fixed: The new script for the ojtest tool must be in the dist/cappuccino/bin directory 2021-09-04 09:15:24 +02:00
Martin Carlberg 0518d479d3 Fixed: Included ojtest as binary in the package.json file 2021-09-03 16:28:23 +02:00
Martin Carlberg a893f3f5c1 Fixed: The tool ojtest now works with the new Node version. 2021-09-03 14:31:11 +02:00
Martin Carlberg 9c2f37b43f Fixed: The Tool nib2cib will give information of used Resource directory. 2021-09-02 12:44:48 +02:00
Martin Carlberg 0289ba17f1 Fixed: Add test task for Framework template used by the tool capp gen
Plus some minor bugfixes
2021-09-02 11:23:32 +02:00
Martin Carlberg 5db94e1e52 Fixed: Cleanup of the Jakefile template for Application and Framework used by the tool "capp gen" 2021-09-01 16:28:55 +02:00
Martin Carlberg a4bc08c29e Fixed: Moved enquote function to utils package.
This function was originally in the Narwhal package OS
2021-09-01 16:27:16 +02:00
Martin Carlberg b638d8e904 Fixed: From around Xcode 12.5.1 ibtool starts to create a folder with two files.
We have to check for this and take action. We don't know of this is a temporary thing as Apple has no
information about this change.
2021-08-30 16:29:43 +02:00
Martin Carlberg 251ce7fdad Fixed: Info.plist file with just whitespaces is not treated as an empty file in Node. Trim it and check length. 2021-08-27 09:08:19 +02:00
Martin Carlberg a2337a2969 Fixed: Can't use generic array of numbers when writing to file with Node 2021-08-27 09:04:51 +02:00
Martin Carlberg 0c803e26ca Fixed: Must expand path to absolute to be able to get application name. Was translated to wrong Node function when converting from Narwhal. 2021-08-27 09:03:58 +02:00
Martin Carlberg ab25fadb1e Fixed: Function for imagesize used wrong and non declared argument 2021-08-27 09:02:26 +02:00
Martin Carlberg e697f4eca7 Fixed: Better debugging experience 2021-08-27 09:01:19 +02:00
Martin Carlberg 80158a8982 Fixed: Cleanup of fontinfo and imagesize Jakefile 2021-08-27 08:59:45 +02:00
Martin Carlberg 55bcfacf3b Fixed: Moved common function used in Jakefiles that was declared all over the place to a file.js in @objj/utils. Also some general cleanup. 2021-08-25 09:32:58 +02:00
Martin Carlberg 52b7a82b4d Fixed: Cleanup of Jakefiles for better Node adaption 2021-08-24 10:58:12 +02:00
Martin Carlberg 471e605871 Fixed: removed debug log message 2021-08-23 11:14:12 +02:00
Alfred Wärnsäter 5c635f17a7 require node 14 as minimum 2021-08-20 14:22:11 +02:00
Alfred Wärnsäter c27d604780 removed print statement 2021-08-20 09:29:59 +02:00
Alfred Wärnsäter ddaf575719 fixed --version option in nib2cib 2021-08-20 09:29:34 +02:00
Alfred Wärnsäter 0757f2ea80 corrected instructions for Node alpha 2021-08-20 09:28:11 +02:00
Alfred Wärnsäter 3f8dfb6516 removed debugger statement 2021-08-20 09:27:03 +02:00
Alfred Wärnsäter 45ee25adc2 removed print statement 2021-08-20 09:26:13 +02:00
Alfred Wärnsäter fcfb3d4083 added bin dir from dist 2021-08-19 17:31:21 +02:00
Alfred Wärnsäter 357f8bd684 1.1.1 2021-08-19 16:36:38 +02:00
Alfred Wärnsäter 173cd8b4ad bumped npm version 2021-08-19 16:36:27 +02:00
Alfred Wärnsäter d2ca5994c8 1.2.0 2021-08-19 16:35:39 +02:00
Alfred Wärnsäter fe04d177af added more instructions 2021-08-19 16:35:19 +02:00
Alfred Wärnsäter 2a6b18bc1b added more instructions 2021-08-19 16:33:15 +02:00
Alfred Wärnsäter 152cd272f6 updated readme for Node alpha version 2021-08-19 16:07:26 +02:00
Alfred Wärnsäter abbdc139e8 now using sudo only when necessary 2021-08-19 13:59:43 +02:00
Alfred Wärnsäter 7226019b6a use npm prefix for install path 2021-08-19 13:30:40 +02:00
Alfred Wärnsäter 78ea29bd48 added dist to to gitignore 2021-08-19 11:15:27 +02:00
Alfred Wärnsäter 5c90d1f769 added binary part of dist dir 2021-08-19 11:14:38 +02:00
Alfred Wärnsäter 2ecea40fdc lots of Node fixes 2021-08-19 10:56:13 +02:00
Martin Carlberg 8d9c11b6f2 Fixed: Make sure we return the result from an executed file in the callback 2021-08-18 15:56:36 +02:00
Martin Carlberg afd23e3457 Fixed: Added file translation dictionary for the Foundation framework
This allows the Foundation framework to be compiled while loading the Application in the Browser
2021-08-17 16:47:06 +02:00
Alfred Wärnsäter ddeaad74d0 added jake stuff to bundletask 2021-08-10 12:31:48 +02:00
Alfred Wärnsäter a82472f509 removed unused dependancies 2021-08-10 12:29:43 +02:00
Alfred Wärnsäter 4a43e376ed modified capp to work with node 2021-08-10 12:27:50 +02:00
Alfred Wärnsäter 9ab82e1e5e removed console.log statement 2021-08-10 12:24:20 +02:00
Didier KorthoudtandGitHub 4b28d0c38b Fixed: setTextColor on a CPTextField would not take visual effect before an event (#2993) 2021-08-06 12:17:07 +02:00
Alfred Wärnsäter 1924cd1822 removed fs-extra dependancy 2021-08-06 11:02:55 +02:00
Alfred Wärnsäter 730b2dd874 removed glob dependancy 2021-08-06 10:36:04 +02:00
Alfred Wärnsäter 1c2b383838 removed uuid dependancy 2021-08-06 09:43:49 +02:00
Alfred Wärnsäter 0b99f83d6f fixed bug that broke the build process when building twice 2021-08-05 17:53:45 +02:00
Alfred Wärnsäter dc5b3f48e8 fixed watch functionality in nib2cib 2021-08-05 16:22:36 +02:00
Alfred Wärnsäter d17671c99e added parsing of command line arguments 2021-08-04 15:19:10 +02:00
Alfred Wärnsäter b69259e83b swapped print to console.log 2021-08-04 15:16:41 +02:00
Alfred Wärnsäter f2c80dc815 added node_modules to .gitignore 2021-08-04 15:15:12 +02:00
Alfred Wärnsäter cbc281b173 added terser as compressor 2021-08-04 09:25:36 +02:00
Alfred Wärnsäter 472f859bd8 basic support for nib2cib 2021-08-03 13:39:54 +02:00
Alfred Wärnsäter e71f650650 removal of print and debugger statements + added some more color 2021-07-30 15:35:57 +02:00
Alfred Wärnsäter e64c9d9cd4 starting to port the capp tool to node 2021-07-30 14:09:11 +02:00
Alfred Wärnsäter 87a1c6621c ported nib2cib from narwhal to node, it now build using objj-jake 2021-07-29 13:20:30 +02:00
Didier KorthoudtandGitHub 09f49c4afa Fixed: CPButton should not have the same label text size whatever the control size (#2990)
Adding font size definitions to Aristo2.
2021-07-29 12:38:25 +02:00
eba1b2f999 Fixed: CPSearchField's bezel did not resize (#2987)
Co-authored-by: Didier Korthoudt <didier.korthoudt@icloud.com>
2021-07-29 11:39:42 +02:00
daboe01andMartin Carlberg 8e026279ac fixed: CPButton's images were lost when becoming disabled 2021-07-29 11:30:55 +02:00
Alfred Wärnsäter 3605e99914 minor bugfixes and clean up 2021-07-28 10:12:24 +02:00
Alfred Wärnsäter 5e9d479cef AppKit and Themes are now buildable 2021-07-27 08:59:05 +02:00
David RichardsonandMartin Carlberg fb20463449 Fix Java version check to cleanly identify both Oracle and OpenJDK variants
Set minimum supported version to 8 to simplify this check.
Java 8 is the last variant from Oracle to be free and unencumbered, and is classified LTS.
It is also available from OpenJDK.
2021-07-05 11:07:53 +02:00
Alfred Wärnsäter f5db73342f changed some code in blendtask.j to older javascript 2021-07-01 16:45:39 +02:00
Alfred Wärnsäter 9b2dc23197 finished porting blendtask.j 2021-07-01 16:30:30 +02:00
Alfred Wärnsäter 067052e1a8 ported blendtask.j to node api 2021-07-01 16:17:43 +02:00
Alfred Wärnsäter 441e5fbc05 ported nib2cib to node the node api 2021-07-01 16:00:32 +02:00
Alfred Wärnsäter 0a2aae3bc2 some restructuring of the repo, trying to fix problems when building with node 2021-06-30 10:13:52 +02:00
Alfred Wärnsäter a76c798500 Merge remote-tracking branch 'mrcarlberg/compile_deep_structured_project_in_browser' into tmp 2021-06-23 10:45:34 +02:00
Alfred Wärnsäter c238ebeef4 major changes, very much WIP, experimenting with async 2021-06-22 14:04:31 +02:00
Alfred Wärnsäter 206eddbdbd further removal of narwhal APIs 2021-06-16 15:43:19 +02:00
Alfred Wärnsäter 45549cdbcf removing narwhal api from jakefiles 2021-06-14 13:17:14 +02:00
cacaodevandGitHub e043b6a8c5 Merge pull request #2978 from daboe01/nib2cib-update
fixed: some cib files have been outdated
2021-05-22 10:48:30 +02:00
daboe01 677076b89d fixed: some cib files have been outdated 2021-04-01 18:04:06 +02:00
cacaodev 77c9b99f74 Manual test exposing issue #2970 2021-03-07 20:46:44 +01:00
cacaodevandGitHub c8cdda0c18 Merge pull request #2962 from mrcarlberg/cptextview_more_ib_options
Fixed: CPTextView now handles more options from Interface Builder
2021-03-07 08:48:50 +01:00
daboe01andGitHub 41cd73703e Fixed: sharedApplication was instantiating the wrong class in subclasses of CPApplication (#2968)
Previously, CPApplication was hard-coded. This PR makes sharedApplication
instantiating the CPApp of the correct class.
2021-01-19 09:03:37 +01:00
cacaodevandGitHub 607e84c052 Merge pull request #2964 from mrcarlberg/compiler_remove_unused
Fixed: Removed unused variables and statements in compiler
2020-12-23 07:36:29 +01:00
Martin Carlberg 8416390631 Fixed: Removed unused variables and statements in compiler 2020-12-22 20:16:30 +01:00
Martin Carlberg 6094d25cfb New: Support to compile deep structured project in the web browser
A Cappuccino project has always needed to have a flat file structure to
be able to compile from source in the web browser.

A new dictionary can be added to the project's Info.plist with the key
'CPFileTranslationDictionary'. It should contain all the path
translations in the project to allow the Browser to find out where in
the file structure the source files are. Check in the AppKit framework
for an example.

Also, a include list can be added in the Info.plist for the key
'CPCompileIncludeFileArray'. The file in the list will be included
by a '#include' statement for each file that is compiled in this project

A last new feature is to add macros in the 'OBJJ_COMPILER_FLAGS' global
variable. It is usually declared in the index.html file. It is done
with the format '-Dmacroname[=macrodefinition]'.

An updated Info.plist file in the AppKit framework will allow the AppKit
to compile from the source in the web browser. The following things is
needed to get this to work:

1. Replace the AppKit folder in your project's Frameworks folder with a
copy of the AppKit folder from the Cappuccino project.
2. Add a copy of the built Aristo2.blend folder to the AppKit's Resource
folder.
3. Add "-DPLATFORM_DOM", "-DPLATFORM_BROWSER" and optionally "-DDEBUG"
to the global variable 'OBJJ_COMPILER_FLAGS' in the project's index.html
file.

The app load time will increase (about 3 seconds on a 2018 MacBook Pro)
as the AppKit framework is compiled from source.

The Foundation framework is not yet tested but it might be working by
adding info about the file structure in the Info.plist file.
2020-12-21 11:14:36 +01:00
cacaodevandGitHub c80504efae Merge pull request #2960 from mrcarlberg/cptextview_exposedrect
Fixed: Prevent crash in CPTextView when _exposedRect is nil
2020-12-19 15:19:19 +01:00
cacaodevandGitHub 2589033523 Merge pull request #2961 from mrcarlberg/radio_multiple_value_bindings_test
New: Unit test to illustrate multiple value bindings on radio buttons
2020-12-19 15:18:27 +01:00
Martin Carlberg f74fed5ee0 Fixed: CPTextView now handles more options from Interface Builder
The new options are:
1. Resizable Horizontally
2. Resizable Vertically
3. Max Size

Also two options from the Text Container is recognized:
1. Tracks text view width
2. Tracks text view height

The last two are needed to allow the text view to recalculate
the size of its content if the frame size is changed.
2020-12-16 11:29:11 +01:00
Martin Carlberg 01e295084c New: Unit test to illustrate multiple value bindings on radio buttons 2020-12-14 13:11:24 +01:00
Martin Carlberg 9f923941a8 Fixed: Prevent crash when _exposedRect is nil 2020-12-14 12:47:34 +01:00
Didier KorthoudtandGitHub 3aba03eb28 Fixed: Missing call to super in NSTabView.j (#2956) 2020-11-22 11:01:14 +01:00
Didier KorthoudtandGitHub 1107195ab6 Fixed: Modern Cocoa behavior, new theme parameters, Aristo3 CSS theme ready for CPTabView (#2936) 2020-11-20 08:33:56 +01:00
Didier KorthoudtandGitHub b67384cdd1 Fixed: Running condition where the value is set before the radio button is associated in a radio group (#2952) 2020-11-20 08:25:03 +01:00
David RichardsonandGitHub 59603b550e Fixed: "Loading…" animation using modern layout (#2953)
Correct vertical alignment in all modern browsers. IE no longer a target.
2020-11-20 08:22:45 +01:00
David RichardsonandGitHub c52f714149 Fixed: Version check for XcodeCapp compatibility with Big Sur (#2954) 2020-11-20 08:20:19 +01:00
Didier KorthoudtandGitHub 35420732d1 Fixed: Modern Cocoa behavior and Aristo3 CSS theme ready for CPDatePicker (#2935)
Also, clock hands can be manipulated
2020-11-06 16:27:07 +01:00
David RichardsonandGitHub 7f48b84292 Fixed: Update template for NibApplication's xib and cib to modern format (#2946) 2020-11-06 08:31:22 +01:00
Didier KorthoudtandGitHub 5110c5706f Fixed: Compilation warnings (#2950) 2020-11-06 07:25:21 +01:00
Didier KorthoudtandGitHub 633511acda Fixed: Before this PR, placing a CPTableView in a CPBox would break nib2cib (#2949) 2020-10-31 17:56:13 +01:00
Didier KorthoudtandGitHub 75d0a69c02 Fixed: uncorrect CPStepper rendering (#2948) 2020-10-30 10:58:34 +01:00
Didier KorthoudtandGitHub e6fe9396f8 Fixed: Change default bezel style to CPRoundRectBezelStyle for CPButton (#2947)
Also removes unwanted comments
2020-10-27 11:01:18 +01:00
Didier KorthoudtandGitHub 5c4107835b New: Aristo3 improvements for CPSearchField (#2934)
New theme parameters
Animations
Modern Cocoa behavior
Aristo3 CSS theme ready
2020-10-23 09:05:33 +02:00
Didier KorthoudtandGitHub 57cc90f89f New: Provides more information on an error message in CPScroller (#2931) 2020-10-23 08:43:59 +02:00
Didier KorthoudtandGitHub 7e08e48a8a New: Adds a new theme parameter "menu-offset" to CPPopUpButton instead of hard coded value (#2923) 2020-10-23 08:42:00 +02:00
Didier KorthoudtandGitHub 87719f1161 Fixed: Adds the missing isCSSBased method for CPView (#2945) 2020-10-12 08:51:49 +02:00
Didier KorthoudtandGitHub 99d7c8d824 New: Modern Cocoa behavior for Menus (#2929)
More theme parameters
Modern Cocoa behavior
First menubar item can be displayed specifically
All 3 control sizes are now managed
2020-10-09 15:52:54 +02:00
Didier KorthoudtandGitHub 1caf4f99cf Fixed: Correct / complete theme states for themed values on CPStepper (#2930) 2020-10-09 14:39:38 +02:00
Didier KorthoudtandGitHub 78b3887e4a New: CSS shadow, new theme parameters and Aristo3 CSS theme ready for CPWindow (#2927) 2020-10-09 14:29:10 +02:00
Didier KorthoudtandGitHub bb064aacd5 New: The ability to control CPPopoverWindow background color (#2928)
This introduces a Cappuccino specific behavior that permits to control the background color of the popover by setting a background color on the content view.
2020-10-09 13:37:47 +02:00
Didier KorthoudtandGitHub d9e8321d9e Fixed: CPButton's now set their font using [self font] (#2943)
The corresponding adaptation in CPControl was missing in the previous PR
2020-10-09 13:05:51 +02:00
Didier KorthoudtandGitHub 5f6df95e30 New: Modern Cocoa behavior and methods, ticked sliders and Aristo3 CSS theme ready (#2926) 2020-10-09 11:47:18 +02:00
Didier KorthoudtandGitHub ef779c346e New: Support modern Cocoa behavior regarding radio buttons groups. (#2941) 2020-10-09 09:35:56 +02:00
Martin CarlbergandGitHub c1543d4a2f Fixed: Loading cached theme attributes will miss previous cached values if superclass is done before subclass (#2892) 2020-10-02 16:01:30 +02:00
Didier KorthoudtandGitHub 9145e27009 Fixed: Aristo3 CSS theme ready and replaces setTextFieldBackgroundColor by setBackgroundColor for CPTextField (#2925) 2020-10-02 14:47:50 +02:00
Didier KorthoudtandGitHub 299f1a9102 Fixed: Aristo3 ready and compatible with new button state management for CPRadio (#2922) 2020-10-02 14:03:25 +02:00
bedaltonandGitHub 6eab438778 Fixed: CPKeyedArchiver method call does not match selector tested (#2904)
Delegate methods described on lines 76 and 85 are not the selectors
checked on lines 305 and line 302, but are the methods actually called
on the delegate on lines 195 and 225. This fix updates the method
selectors checked on the delegate, to match those defined and called
within the CPKeyedArchiver class.

Fixes #2679
2020-10-02 13:52:42 +02:00
Didier KorthoudtandGitHub 2a623fbb8c Fixed: correct mask values used to determine image position for NSButton.j (#2906) 2020-10-02 13:49:11 +02:00
Didier KorthoudtandGitHub 7bde091f1b FIXED: when using a custom view with subviews, subviews were presented in the reversed order (#2913) 2020-10-02 13:31:45 +02:00
Didier KorthoudtandGitHub 412baf7872 Fixed: Aristo3 ready and modern Cocoa behavior for CPBox (#2924) 2020-10-02 13:28:06 +02:00
Didier KorthoudtandGitHub 010057d9e1 New: CPCheckBox Aristo3 ready and compatible with new button state management (#2921) 2020-10-02 13:05:59 +02:00
Didier KorthoudtandGitHub 6c7d8df565 Aristo3: CPButton (#2920) 2020-10-02 12:59:32 +02:00
Didier KorthoudtandGitHub 92598da06f New: themable-system-font (#2919) 2020-10-02 12:55:32 +02:00
Didier KorthoudtandGitHub 601d6f7c6c New: class method to BKThemeDescriptor that enables a view to inherit theme values of another view (#2916) 2020-10-02 12:31:22 +02:00
Didier KorthoudtandGitHub e13cebaa3a NEW: Material icon image (#2909) 2020-10-02 12:13:00 +02:00
Didier KorthoudtandGitHub 5724868c5a Fixed: restorePreviousCSSState method for CPColor must be a class method for internal reasons (#2912) 2020-10-02 10:58:20 +02:00
Didier KorthoudtandGitHub 03972f0aec Fixed: The classname for CSS theming on CPImage was not specified in the correct DOM element (#2908) 2020-10-02 10:55:55 +02:00
Didier KorthoudtandGitHub 6143ec4180 Fixed: Theme compilation problem because of lack of rgba capability of the compiler environment.(#2910) 2020-09-11 09:01:33 +02:00
Didier KorthoudtandGitHub c30464ee77 New: This adds the CSS dictionaries to the description of the color (#2911) 2020-09-11 08:55:10 +02:00
cacaodevandGitHub 1e1e6456cd Merge pull request #2902 from daboe01/test-attributedString-replacing
new: unittest for replaceOccurrencesOfString:withString:options:range:
2020-09-02 18:26:14 +02:00
cacaodevandGitHub 6c46bb4756 Merge pull request #2940 from didierkorthoudt/031-CPViewAnimator-fix
FIXED: This PR fixes correct parameter type in CPViewAnimator -setAlphaValue
2020-09-02 18:21:45 +02:00
Didier Korthoudt 5ac6b1d1f1 Update CPViewAnimator.j
This fixes parameter type.
2020-08-30 10:01:11 +02:00
daboe01 9c95c9c6e9 formatting 2020-07-13 20:37:52 +02:00
daboe01 4b052e74f1 test whether range restriction works 2020-07-05 16:06:04 +02:00
daboe01 bd6b8e4b7f fixed: comparison between wrong objects 2020-07-03 21:27:48 +02:00
daboe01 26e8432bb0 debugging 2020-07-03 19:55:51 +02:00
daboe01 05f9fd22fe formatting 2020-07-03 19:36:06 +02:00
daboe01 8f5bde2797 new: unittest for replaceOccurrencesOfString:withString:options:range: 2020-07-02 19:38:34 +02:00
daboe01andGitHub 77fe16faec Fixed: background color attribute was ignored by CPTextView (#2899) 2020-07-01 16:33:07 +02:00
Martin CarlbergandGitHub 38dd776049 Fixed: JavaScriptCore (WebKit) has a limit at 65536 on number of arguments in a function. (#2891)
The compiler will crash for really large Objective-J files as the array with code segments will hit this limit.
The solution is to append the array segment by segment.
2020-06-30 20:53:28 +02:00
daboe01andGitHub 7e3c4074e4 New: replaceOccurrencesOfString:(CPString)target withString:(CPString… (#2900)
New: replaceOccurrencesOfString:(CPString)target withString:(CPString)replacement options:(int)options range:(CSRange)searchRange
2020-06-30 20:52:34 +02:00
cacaodevandGitHub 89c8f8e80f Merge pull request #2895 from mrcarlberg/bad_merge
Fixed: Removed old unused line from a bad manual merge
2020-05-05 08:38:05 +02:00
Martin Carlberg 7397e681f4 Fixed: Removed old unused line from a bad manual merge 2020-05-04 13:42:37 +02:00
Didier KorthoudtandGitHub e7d5afe90e New: Added dynamic attributes set for CSS based themes (#2701) 2020-04-06 13:10:34 +02:00
Didier KorthoudtandGitHub 2d9d354a05 Fixed: When a CPSearchField predicate was bound specifying a predicate format using more than one time the value of the search field ($value), only the first occurence was correctly set, others were set to null (#2777) 2020-04-06 13:03:57 +02:00
Didier KorthoudtandGitHub 60a686d75c Fixed: _CPMenuItemStandardView didn't take in account the left-margin theme attribute (#2689) 2020-04-06 09:18:31 +02:00
Didier KorthoudtandGitHub 89b70843c7 Fixed: Nib2cib was using hard coded frame adjustment for CPBox (#2761) 2020-04-06 09:17:05 +02:00
daboe01 9f9044b164 fixed: selection and backspace are recently broken in CPTokenField
this bug was introduced by commit 26aab29
 26aab29 was meant to improve iOS experience but makes the tokenfield loose FR status prematurely
2020-03-22 11:59:24 +01:00
Didier KorthoudtandGitHub 617987f4a5 Fixed: CPTextField forced height to 29 instead of using theme value min-size (#2688) 2020-03-17 09:14:25 +01:00
daboe01andGitHub 5bb338d6ec New: CPTextField now supports the CPEditorRegistrationProtocol (#2169) 2020-03-11 10:30:00 +01:00
Didier KorthoudtandGitHub 8003d91c4a Fixed: [CPControl setTextColor:] was missing theme state attribute (#2828) 2020-03-11 10:20:04 +01:00
Didier KorthoudtandGitHub a4ef18ddab FIXED: Before this PR, only CPControl could use nib2cib-adjustment-frame. (#2831) 2020-03-11 10:17:14 +01:00
daboe01andGitHub 82e55b4761 Fixed: _glyphsFrames and _glyphsOffsets could be used uninitialized in CPTextView (#2886) 2020-03-03 17:04:52 +01:00
Didier KorthoudtandGitHub 3a51c386ca Fixed: Before this PR, a click in a not first key responder CPTextField would misplace the caret if text alignment was not CPLeftTextAlignment #2887
Adapts the calculation of where in the textfield text the mouse is clicked to take in account the text alignment
2020-03-03 17:02:42 +01:00
Didier KorthoudtandGitHub 9868519cc0 Fix: Before this PR, the method attributeBySettingValue ignored existing values #2888 2020-03-03 16:43:29 +01:00
Didier KorthoudtandGitHub 038c007915 FIXED: Before this PR, CPButton never declares that its label should be single line (#2889) 2020-03-03 13:44:07 +01:00
Didier KorthoudtandGitHub ba6350e24d NEW: CPSplitView modern Cocoa behavior implementation (#2876) 2020-02-21 14:32:53 +01:00
Martin CarlbergandGitHub 1c866931f3 New: The compiler will now create warnings for variables that are never read (#2871)
This new warning adds a lot of warnings in the Cappuccino frameworks. I have turned off
the warning in the Jakefiles when compiling most of the Cappuccino frameworks. But it is
on as default for any user projects.

So there is also a new feature to turn off/on warnings as a compiler flag.

These flags are (all flags are on as default):
-Wunused-but-set-variable                    Warning when a local variable is never read.
-Wshadow-ivar                                Warning when a local variable is shadowing an instance variable for the class.
-Wcreate-global-inside-function-or-method    Warning when creating a global variable inside a function or method.
-Wunknown-class-or-global                    Warning when a class or global variable is not known.
-Wunknown-ivar-type                          Warning when the type for an instance variable is not known.

To turn off a flag add a 'no-' prefix. Example: -Wno-unused-but-set-variable

For an example how to use it in a Jakefile please look at the Jakefiles in this commit.
2020-02-21 14:29:48 +01:00
cacaodevandGitHub b7aff28b5e Merge pull request #2884 from daboe01/documentation-for-attributedStringWithAttachment
fixed: documentation for attributedStringWithAttachment: was missing
2020-02-04 13:14:45 +01:00
cacaodevandGitHub f0897ffdf8 Merge pull request #2885 from mrcarlberg/predicate_not_equal_nil
Fixed: CPPredicate does never evaluate to true for 'something != nil'
2020-02-04 13:06:37 +01:00
Martin Carlberg f5937a6fad Fixed: CPComparisonPredicate does never evaluate to true for a predicate like 'something != nil'
An example:

var array = @[ @{@"Name": @"1"},
               @{@"Name": @"2", @"approveTime": @"Now"}
             ];

var predicate = [CPPredicate predicateWithFormat:@"approveTime != nil"];

CPLog(@"result: %@", [array filteredArrayUsingPredicate:predicate]); // result: <empty array>

If I run this in Cappuccino I get an empty array

If I run it in Cocoa on a Mac I get:

result: (
        {
        Name = 2;
        approveTime = Now;
    }
2020-01-31 14:24:29 +01:00
Martin CarlbergandGitHub 7dc77ed609 Fixed: Make sure we handle undefined when testing for nil values (#2862) 2020-01-31 13:43:58 +01:00
daboe01 d1bfe62a65 removed: obsolete fixme comment 2020-01-12 17:08:23 +01:00
daboe01 f7c8f24bff removed dead code 2020-01-11 15:13:30 +01:00
daboe01 055d63eeb5 formatting 2020-01-11 15:13:04 +01:00
daboe01 ef7f92ed15 fixed: documentation for attributedStringWithAttachment: was missing 2020-01-11 15:09:41 +01:00
daboe01andMartin Carlberg 8daa53dd3e Fixed: CPTextView keyboard issue in recent Firefox versions (#2874)
Wrong characters were sometimes emitted in keyboard events on Firefox
2020-01-07 12:34:39 +01:00
Martin CarlbergandGitHub 109003ae12 Fixed: Performance improvement on visibleRect when updating tracking areas (#2881)
Calculations of the visible rect can be a heavy operation as it depends of the superviews
visible rect. They have to be transformed and intersected together to create the result.
This calculation is a recursive operation that travels up the view hierarchy all the way
to the top.

Updating the tracking areas is also a recursive operation. But it travels down the view hierarchy
instead. It will calculate the new tracking area using the visible rect for the current view. For
each view it updates it will get the visible rect that has to travel all the way up the view
hierarchy to calculate it by transform and intersect them together. This results in that the visible
rect will be calculated for each view multible times. This makes it a very slow operation, specially
for deeper view hierarchies.

This fix will send along the visible rect for the superview when traveling down the
view hierarchy. This means that the visible rect does not need to travel up the view hierarchy
to be calculated. It will just need transform and intersect the provided rect for its superview
with the one from itself.
2020-01-06 12:48:40 +01:00
Martin CarlbergandGitHub 36f1abdbe6 Fixed: CPAppearance has much better performance now when views needs layout. (#2880)
This makes updating views much faster when they need layout. This will make resizing browser window and changing split views sizes much smoother.

The effectiveAppearance is inherited from the superview. The superviews appearance is passed along as it is recomputed down the view hierarchy.
Also the current appearance is saved on each view to make it possible to only change the theme states when the appearance is changed.
2020-01-06 12:35:38 +01:00
Martin CarlbergandGitHub 50519776e0 Fixed: Update Tracking areas only once when doing setFrame: (#2879)
This speed improvement is very notable when resizing a browser window of altering the size of split views.
The fix uses a variable on the window to control when to update the tracking areas.
If the view is not connected to a window the tracking areas will not be updated as they will
be when added to a window.
2020-01-06 12:32:35 +01:00
daboe01andMartin Carlberg 9e289574ff Fixed: attachment support was missing in CPTextView (#2872) 2019-12-19 09:08:33 +01:00
cacaodevandGitHub 2d96b06d4c Merge pull request #2878 from didierkorthoudt/CPButton-continous-bug
FIXED: Before this PR, disabled buttons that were continuous responded to mouse clicks
2019-12-17 13:27:06 +01:00
cacaodevandGitHub 0c706cfed2 Merge pull request #2866 from mrcarlberg/cpset_enumerator_use_stop_variable
Fixed: CPSet enumerateObjectsUsingBlock: will honor the stop variable
2019-12-16 11:27:37 +01:00
Didier Korthoudt 3f9e1c6c47 Continuous fix 2019-12-15 10:48:21 +01:00
jasperdebandMartin Carlberg 58c08cebd7 Fixed: Added underline support in CPTextView (#2875) 2019-12-06 09:12:31 +01:00
Martin CarlbergandGitHub fed0703ecf New: Allow lazy loading control for combined observer key paths (#2854)
This pull request adds a kvoValueForKey: method used by _CPKVOForwardingObserver for getting values.
The default action is to just return the valueForKey value.
Override this method if you want any other behavior.
It can be used to just return nil if you are implementing a lazy load behavior on a class.
It can allow a combined binding to observe this object without trigger any lazy loading.
2019-11-28 09:38:55 +01:00
Martin CarlbergandGitHub aaed1a934e Fixed: When in modal mode make sure a focus event will make the modal window the key window (#2867)
The default was to make the first window in the most background layer the key window if nothing else was used.
This does not work well if there is a modal window that should have all the attention.

This solution will first choose the modal window if in modal mode. If not the old variant is used.
2019-11-27 19:16:03 +01:00
Martin CarlbergandGitHub 11639af26e Fixed: Let the text field handle key commands when it is selectable. (#2868)
There was a problem introduced by pull request #2600 when trying to select and copy text in a selectable text field.
The problem only occurs when the text field is in a modal window.

It was caused by the modal run loop that is present as an eventListener on the CPApplication when in modal mode. It will
force a bail out from the sendEvent: method in CPApplication when trying to handle the event. As the check for key
equivalent is moved down by the #2600 pull request it will never be allowed to handle a copy command from the browser
menu or key equivalent.

The solution is to allow the text field to handle the event if it is selectable. This will also allow the text selection
to be altered from the keyboard with keys like shift-left/right/up/down arrow. Combinations like shift-option arrow also
work for whole word selection.
2019-11-26 09:20:47 +01:00
cacaodevandMartin Carlberg 623ad2b1dc Fixed: Previoulsly, clearsFilterPredicateOnInsertion was applied (removes filter predicate) when setting content or on content binding. (#2860)
With Tests: Foundation unit test.
Solves #2791
2019-11-12 14:02:35 +01:00
Brian M. PalmaandMartin Carlberg ac68fec6b1 FIXED: Add Dark Mode to XcodeCapp (#2856)
FIXED: Add Dark Mode to XcodeCapp

This commit also fixes an issue where two NSBox instances had a borderRadius of 100 (boxStatus and boxImport) in IB. The borderRadius values are now updated to be half the size of the height and width values, restoring the circle visual.

Update installation directory for XcodeCapp

- Replace $LOCAL_APPS_DIR with $USER_APPS_DIR
- Installation directory updated from /Applications to ~/Applications
2019-10-18 13:37:22 +02:00
Martin Carlberg dad49354aa Fixed: CPSet enumerateObjectsUsingBlock: will honor the stop variable
Test case also added.

Deprecated the very old behaviour that the block could return YES to abort the enumeration.
2019-10-18 13:01:04 +02:00
daboe01andMartin Carlberg ce4fa3c027 Fixed: Pressing capslock inserted whitespaces under certain conditions (#2857)
This was present in a CPTextView.

A press on caps lock should also result in a flagsChanged event.
An event should have the corresponding flag for caps lock in modifierFlags.
2019-09-10 08:34:07 +02:00
meelashandMartin Carlberg 02d1a12bf5 Fixed: Update nonworking link (#2859) 2019-09-02 09:23:17 +02:00
cacaodevandGitHub 78cbc3871e Merge pull request #2852 from mrcarlberg/msg_send_return_undefined_when_receiver_is_undefined
New: msgSend will return undefined instead of nil when the receiver is undefined.
2019-08-31 11:28:17 +02:00
daboe01andMartin Carlberg f9bb882ceb Formatting: make readme a litte more concise (#2858)
removed the wrong attribute 'demo' for our tutorials link
2019-08-26 09:24:58 +02:00
cacaodevandGitHub 87fc6bde1d Merge pull request #2853 from mrcarlberg/optimize_table_column_bindings_with_long_key_paths
Fixed: Optimization for bindings with longer key paths in table views.
2019-08-17 18:33:33 +02:00
Martin Carlberg f666367b5d Fixed: Optimization for bindings with longer key paths in table views.
This prevents a buildup of large arrays with a lot of values and then choose one row and throw away the rest. This is great for speed and a must for lazy loading.

Previous there was a optimization for this but it was only effective for short combined key path like 'a.b'. This pull request makes it effective for any kind of length of a combined key path like 'a.b.c.d...'.

This is done by returning the object at a row in the first found CPArray in the key path
that is divided in a first and second part.
The first part is never a combined key path. The second part can be a combined key path.
If this optimization is not done we will create an array with the valueForKeyPath value on each row and then pick
the wanted value for the row and throw away all the other rows. It is much more effective to first
pick the row and then do the valueForKeyPath on the rest of the key path.
2019-06-26 09:37:40 +02:00
Martin Carlberg 81c02f24c6 New: msgSend will return undefined instead of nil when the receiver is undefined.
The msgSend function has always returned nil if the receiver has been nil or undefined.
Example:

var a = undefined;
var b = [a someSelector]; // b = nil

The variable b is now nil.

This pull request adds the ability for the msgSend function to return undefined if the receiver is undefined. If the receiver is nil it still return nil.
The above example again:

var a = undefined;
var b = [a someSelector]; // b = undefined

The variable b is now undefined.

Background.
The use of nil in Objective-C corresponds to the use of null in C and C++ and stands for "no value". A fundamental function in Objective-C is when you send a message to a receiver that is nil the result is again a nil value. This can be very convenient as you don't need the check if a receiver is nil before sending a message to it.

Objective-J adds the ability the use class structures and message send functionality for Javascript in the same way as Objective-C adds this to C and C++. Javascript also has the value null and is handled in the same way for Objective-J. Javascript also has the value undefined that stands for "not yet defined". This can sometimes be confusing and many try to handle null and undefined as the same thing. This is almost what Objective-J and the Cappuccino frameworks always has been doing. They try to always check for both null and ``undefined and always return nil but never undefined. We can say that Objective-J understands undefined but will try to translate it to nil.

Why do we need this change.
Javascript has both null and undefined and Objective-J is a superset of Javascript. It is now very hard to use undefined in Objective-J code as it will always try to translate it to nil. There are some Javascript libraries that use both null and undefined values and they are very hard to use from Objective-J programs today.  Same if you decide to use nil and undefined as a value in your own Objective-J code. This small change will make undefined a full member of the Objective-J language. It should be as it is a superset of Javascript. The Cappuccino frameworks will still handle undefined but only return nil.

How will it impact.
Today most methods will never return undefined so this is a very limited problem. If any does this the main concern is if some code will only check for nil and not undefined.
Example:

var a = [someObject someSelector];
If (a === nil) a = 42;
...

If the variable someObject contains undefined the variable a will now be set to undefined instead of nil.  But the variable a will then never be set to 42.
The correct way is to check for both nil and undefined like this:

var a = [someObject someSelector];
If (a == nil) a = 42;
...
2019-06-24 17:36:15 +02:00
Didier KorthoudtandMartin Carlberg 1d01ed92d8 Fixed: CPSearchField, when no recent searches were done, the menu wasn’t showing, even if search categories were specified (#2846) 2019-06-13 16:30:53 +02:00
daboe01andMartin Carlberg 1a7ced34a7 Fixed: smart copy/paste cycle was leaving an additional whitespace on the LHS (#2776) 2019-06-13 16:17:52 +02:00
Martin CarlbergandGitHub 47ab94b5bc Fixed: If sourcemap is available include it when building with a nondebug version of the tools. (#2850)
The sourcemap was not included when building from command line with the release (the default) version of the tools.
2019-06-13 16:15:48 +02:00
Didier KorthoudtandMartin Carlberg bea0778acc Fixed: Before this PR, cell based table views that use specific font, reverted to standard font when selected/editing (#2845) 2019-06-13 16:14:48 +02:00
Didier KorthoudtandMartin Carlberg eeba5087b1 Fixed: Before this, under certain circumstances, CPControl didn't declare its tracking areas soon enough (#2827) 2019-06-13 16:12:52 +02:00
daboe01andMartin Carlberg 7bd8bf02f6 Fixed: Typo in function call to _CPDecimalSetZero and made the test case work properly (#2820) 2019-06-13 16:09:17 +02:00
Martin CarlbergandGitHub 378f87a634 Fixed: Temporary fix so finding the compiler can work both in the Narwhal and Node world (#2793) 2019-06-13 16:06:15 +02:00
Didier KorthoudtandMartin Carlberg 76dd5383ab Fixed: A «feature» of Xcode where IB sometimes stores NSFont values in the alternate image property. (#2844) 2019-06-13 16:04:59 +02:00
daboe01andMartin Carlberg 129ca185e4 Fixed: backspace triggered page back in FireFox (#2849) 2019-06-13 15:59:35 +02:00
daboe01andMartin Carlberg 1f4ef42625 Fixed: index.html and index-debug.html were outdated in the manual tests (#2848) 2019-06-13 15:58:34 +02:00
daboe01andMartin Carlberg eb55e4def6 Fixed: keyboard issues from FF66 alignmets to the other major browsers (#2847) 2019-05-24 12:57:57 +02:00
Alexandre WilhelmandGitHub acda834ad9 Merge pull request #2819 from daboe01/CPFontManager-duplicate-methods-removal
fixed: removed outdated duplicate method definitions in CPFontManager.j
2019-04-14 20:02:22 -07:00
Alexandre WilhelmandGitHub aba3e30d9c Merge pull request #2824 from daboe01/cpdocumentcontroller-return-type-fix
fixed: return type of openUntitledDocumentOfType:display:
2019-04-14 20:01:36 -07:00
Alexandre WilhelmandGitHub c9e55ff1dc Merge pull request #2825 from daboe01/CPTableHeaderView-return-type-fix
fixed: wrong return type of textField method in
2019-04-14 20:01:21 -07:00
Alexandre WilhelmandGitHub 013c5e9e58 Merge pull request #2826 from daboe01/cpproxy-return-type-fix
fixed: wrong return type of forward::
2019-04-14 20:01:07 -07:00
Didier Korthoudt 5e7f3dc24a Merge remote-tracking branch 'cappuccino/master' 2019-04-13 10:25:08 +02:00
daboe01 2ffa174900 fixed: wrong return type of forward:: 2019-04-08 14:42:23 +02:00
daboe01 26cbe17218 fixed: wrong return type of textField method in 2019-04-08 14:39:02 +02:00
daboe01 fc8f1dca4e fixed: return type of openUntitledDocumentOfType:display: 2019-04-08 14:32:06 +02:00
Alexandre WilhelmandGitHub 6b20a4f8f7 Merge pull request #2786 from didierkorthoudt/CPStepper-enabled-from-IB
FIXED: CPStepper was not receiving the enabled status from IB
2019-04-03 15:15:22 -07:00
Alexandre WilhelmandGitHub 091025a304 Merge pull request #2794 from mrcarlberg/initialize_missing_on_custom_root_class
Fixed: the class method +initialize was always sent even if the root class did not implement it.
2019-04-03 15:07:59 -07:00
Alexandre WilhelmandGitHub 1180789c0c Merge pull request #2823 from daboe01/CPBrowserDelegateMethodFix
fixed: CPBrowserDelegate_browser_imageValueForItem_ was defined twice
2019-04-03 14:37:33 -07:00
Alexandre WilhelmandGitHub ac056d4a06 Merge pull request #2822 from daboe01/cpcursor-fix
fixed: accidental creation of a global variable in CPCursor.j
2019-04-03 14:36:44 -07:00
Alexandre WilhelmandGitHub 7c196e098a Merge pull request #2821 from daboe01/CPGraphicsContext-typo-fix
fixed: typo in initWithGraphicsPort: declaration
2019-04-03 14:36:23 -07:00
daboe01 c612b13529 fixed: CPBrowserDelegate_browser_imageValueForItem_ was defined twice 2019-03-31 19:42:54 +02:00
daboe01 5f4c79f6c4 fixed: accidental creation of a global variable 2019-03-31 17:44:07 +02:00
daboe01 302cbf641e fixed: typo in initWithGraphicsPort: declaration 2019-03-31 17:37:44 +02:00
daboe01 4bc2e9ad44 fixed: removed outdated duplicate method definitions in CPFontManager.j 2019-03-31 17:24:03 +02:00
Alexandre WilhelmandGitHub 6853c4c212 Merge pull request #2779 from daboe01/textview-contextmenu
New: support contextmenus for CPTextView
2019-03-29 15:06:39 -07:00
Alexandre WilhelmandGitHub 800e15cfd0 Merge pull request #2803 from bedalton/fixed-return-type-CPPlatformPasteboard
Fixed: Return type CPPlatformPasteboard.j should return BOOL
2019-03-29 15:04:14 -07:00
Alexandre WilhelmandGitHub 873f184117 Merge pull request #2798 from bedalton/formatting-add-semi-colons-to-CPTextStorage
Formatting: Add semi-colons to CPTextStorage.j
2019-03-29 15:03:47 -07:00
Alexandre WilhelmandGitHub 3bae5cd5cb Merge pull request #2807 from bedalton/fixed-duplicate-method-in-cparraycontroller
Fixed: Duplicate method in CPArrayController
2019-03-29 15:03:26 -07:00
Alexandre WilhelmandGitHub 19300332e8 Merge pull request #2813 from bedalton/fixed-cpcontrol-misspelled-parameter-type
Fixed: CPControl misspelled parameter type
2019-03-29 15:02:12 -07:00
Alexandre WilhelmandGitHub 89d88c9644 Merge pull request #2811 from bedalton/formatting-remove-redundant-semi-colon-in-cptextview
Formatting: Remove redundant semi-colon in CPTextView
2019-03-29 15:01:52 -07:00
Alexandre WilhelmandGitHub 472014b1b1 Merge pull request #2812 from bedalton/fixed-cpwindowcontroller-returns-cpview
Fixed: Changed CPWindowController method return to CPView
2019-03-29 15:01:29 -07:00
Alexandre WilhelmandGitHub c094420428 Merge pull request #2810 from bedalton/fixed-cpscroller-return-style
Fixed: Change CPScroller's style method return type to int
2019-03-29 15:01:13 -07:00
Alexandre WilhelmandGitHub e948064063 Merge pull request #2809 from bedalton/fixed-cocolor-parameter-to-cpstring
Fixed: Changed CPColor parameter to CPString
2019-03-29 15:00:59 -07:00
Alexandre WilhelmandGitHub bdd3b7d2a5 Merge pull request #2808 from bedalton/fixed-cpbrowserdelegate-duplicate-method
Fixed: CPBrowserDelegate duplicate protocol method
2019-03-29 15:00:46 -07:00
Alexandre WilhelmandGitHub 404c37b54c Merge pull request #2800 from bedalton/fixed-mismatched-parameters-in-CPTextView
Fixed: mismatched parameters in CPTextView
2019-03-29 15:00:18 -07:00
Alexandre WilhelmandGitHub ac686a17fa Merge pull request #2799 from bedalton/fixed-mispelled-parameter-in-CPTextView
Fixed: Misspelled parameter in CPTextView.j
2019-03-29 14:59:55 -07:00
Alexandre WilhelmandGitHub bfc5068b5e Merge pull request #2796 from bedalton/formatting-remove-redundant-semi-colon-CPDatePicker
Formatting: Remove redundant semi-colon form CPDatePicker
2019-03-29 14:59:39 -07:00
Alexandre WilhelmandGitHub 649bee2330 Merge pull request #2806 from bedalton/fixed-cpbezier-path-nsinteger
Fixed: CPBezierPath using parameter type NSInteger
2019-03-29 14:59:18 -07:00
Alexandre WilhelmandGitHub 593587ea60 Merge pull request #2805 from bedalton/fixed-cpapplication-isactive-return-type
Fixed: CPApplication's isActive method return type
2019-03-29 14:59:02 -07:00
Alexandre WilhelmandGitHub 0ab81a7b32 Merge pull request #2804 from bedalton/fixed-_cpautocompletemenu-return-type
Fixed:  Changed void return to id in CPAutocompleteMenu.j
2019-03-29 14:58:50 -07:00
Alexandre WilhelmandGitHub 8bf937357c Merge pull request #2801 from bedalton/formatting-remove-semi-colon-_CPStandardWindowView
Formatting: Remove extraneous semi-colon _CPStandardWindowView.j
2019-03-29 14:58:16 -07:00
Alexandre WilhelmandGitHub 1e8b1fda81 Merge pull request #2797 from bedalton/fixed-return-type-_CPMenuBarWindow
Fixed: Return type in _CPMenuBarWindow
2019-03-29 14:57:43 -07:00
daniel cba9212150 Fixed: CPControl parameter type to CPVerticalTextAlignment
Parameter was set to type CPTextVerticalAlignment, but typedef was declared as CPVerticalTextAlignment. This commit fixes this mismatch
2019-03-29 11:58:51 -07:00
daniel a0a6c42227 Fixed: Change return in CPWindowController to CPView
In CPWindowController, method viewControllerContainerView, implies that it returns something, and it does. It returns the _viewControllerContainerView instance object, and return type should be changed from void to CPView
2019-03-28 15:19:59 -07:00
daniel c245d9de80 Formatting: Removed redundant semi-colon in CPTextField 2019-03-28 14:55:44 -07:00
daniel ca5cd8e09b Fixed: CPScroller's style method should have returned int
Despite returning an int and a comment stating it returns a value, CPScroller's style method had a return type of void.
2019-03-28 14:51:15 -07:00
daniel 34ac4cd23b Fixed: Changed CPColor parameter from string to CPString
Parameter type in CPColor was string, changed here to CPString, to make it more inline with other string parameter types.
2019-03-28 14:33:00 -07:00
daniel 8598fe8ad9 Revert "Fixed: Changed CPColor parameter type from string to CPString"
This reverts commit 06498672ab.
2019-03-28 14:32:00 -07:00
daniel 3699a31f28 Revert "Formatting: Removed extra semi-colon ThemeDescriptors.j"
This reverts commit 75cd5a75e1.
2019-03-28 14:31:17 -07:00
daniel 06498672ab Fixed: Changed CPColor parameter type from string to CPString
Parameter type in CPColor was string, changed here to CPString, to make it more inline with other string parameter types.
2019-03-28 14:29:50 -07:00
daniel cd3b92af7a Fixed:CPBrowserDelegate duplicate method
Method browser:imageValueForItem was defined twice in CPBrowserDelegate. Once on line 43, and the other on line 45. This removes the commit removed the second declaration
2019-03-28 14:26:08 -07:00
daniel e15cc29e2f Fixed: CPBezierPath used parameter types NSInteger
CPBezierPath has parameter type NSInteger. Assumed meant to be CPInteger
2019-03-28 14:19:09 -07:00
daniel dabeb066de Fixed: Duplicate method in CPArrayController.j
Method alwaysUsesMultipleValuesMarker is duplicated in class CPArrayController.j. Both methods have the same body and same selector, the only difference being their comments are slightly different. Both comments however communicate the same thing.
2019-03-28 14:11:28 -07:00
daniel c59e7cc456 Fixed: Changed CPApplication's isActive method return type to BOOL
Method has declared return type of void, despite the method returning a BOOL, and the name implying that it returns a BOOL
2019-03-28 14:08:00 -07:00
daniel 3c16bfef58 Fixed: Changed void return type to id in _CPAutocompleteMenu.j
tableView:objectValueForTableColumn:row: has return type set to void, despite returning an method call returning id. Similar methods also return id.
2019-03-28 14:01:31 -07:00
daniel ec24977d4e Revert "Fixed: changed void return type in _CPAutocompleMenu.j to id"
This reverts commit 10b4f57a0e.
2019-03-28 13:58:15 -07:00
daniel 10b4f57a0e Fixed: changed void return type in _CPAutocompleMenu.j to id
tableView:objectValueForTableColumn:row: has return type set to void, despite returning an method call returning id. Similar methods also return id.
2019-03-28 13:54:20 -07:00
daniel 75cd5a75e1 Formatting: Removed extra semi-colon ThemeDescriptors.j 2019-03-28 13:46:25 -07:00
daniel fb2d0ed170 Fixed: Return types in CPPlatformPasteboard.j should return BOOL
Method _mayRequireDOMPasteboardElementHack:flags had return type of void, despite returning a BOOL value, and also being annotated with a doc comment stating that it should return a BOOL value

Also some methods had return type "boolean", should possibly be BOOL
2019-03-28 13:42:39 -07:00
daniel d4e139eaf5 Fixed: Return type for method in CPPlatformPasteboard.j
Method return type is labeled as void despite returning a BOOL and the DOC comment stating that it should return a BOOL
2019-03-28 13:38:33 -07:00
daniel 49ef1276f6 Formatting: Removed extra semi-colon in _CPStandardWindowView.j 2019-03-28 13:32:56 -07:00
daniel 828788b643 Fixed: Mismatched parameter types in CPTextView
isBlinking had return type void, but returned a BOOL, and the method name implies that it returns a BOOL.

Some parameters were marked as integer, so were changed to int to coincide with most other names for integers.

setString had parameter type id, despite implying that parameter was a string, and the super class parameter being a string.
2019-03-28 13:21:34 -07:00
daniel 0ca8d7a258 Fixed: Mispelled DOMElement parameter in CPTextView.j 2019-03-28 13:12:30 -07:00
daniel 75de49d151 Formatting: Added potentially missing semi-colons in CPTextStorage 2019-03-28 13:08:19 -07:00
daniel d39e1c0c6e Fixed: Return type for font method should have been CPFont 2019-03-28 12:51:04 -07:00
daniel 8e7f201ca8 Formatting: Removed redundant semi-colon in CPDatePicker 2019-03-28 12:49:13 -07:00
daniel 85b2a9b0a0 Merge remote-tracking branch 'upstream/master' 2019-03-28 12:41:13 -07:00
Martin Carlberg d4e40faecc Fixed: the class method +initialize was always sent even if the root class did not implement it. Forwarding and Invocation worked also on the method.
To mimic Objective-C correctly the +initialize method should not be sent on a class hierarchy if it does not implement it.
Also the +initialize method should not allowed to be forwarded to another target with Invocation or any other forwarding mechanism.
Added test cases for this and cleaned up some test cases that succeeded for the wrong reason.

This change will not affect many projects as all classes inherit from CPObject.
2019-02-20 23:44:00 +01:00
Alexandre WilhelmandGitHub 0be913ab18 Merge pull request #2780 from daboe01/fixed-wrong-return-type-in-_CPCibClassSwapper
fixed: wrong return type in initializer of _CPCibClassSwapper
2019-02-06 16:58:44 -08:00
Alexandre WilhelmandGitHub bab30f3b61 Merge pull request #2781 from daboe01/fixed-wrong-return-type-in-shouldArchiveValueForKey-
fixed: wrong return type in shouldArchiveValueForKey:
2019-02-06 16:58:25 -08:00
Alexandre WilhelmandGitHub abb599c556 Merge pull request #2782 from daboe01/fixed-wrong-return-type-of-_menuWithName-
fixed: wrong return type of _menuWithName:
2019-02-06 16:57:59 -08:00
Alexandre WilhelmandGitHub 2b6c6e9ab3 Merge pull request #2783 from daboe01/fixed-wrong-return-type-of-backgroundColorForGlyphAtIndex-
fixed: wrong return type of backgroundColorForGlyphAtIndex:
2019-02-06 16:57:42 -08:00
Alexandre WilhelmandGitHub 0db0d744da Merge pull request #2784 from daboe01/fixed-wrong-return-type-in-isMovable
fixed: wrong return type in isMovable
2019-02-06 16:57:21 -08:00
Alexandre WilhelmandGitHub 6b9f2d29fc Merge pull request #2785 from daboe01/fixed-wrong-return-type-of-isMiniaturized
fixed: wrong return type of isMiniaturized
2019-02-06 16:56:49 -08:00
bedalton 2c141a5255 Revert "Fixed: CPMenuBarWindow did not return a value when expected"
This reverts commit 4f69a32700.
2019-01-04 17:24:14 -08:00
bedalton 4f69a32700 Fixed: CPMenuBarWindow did not return a value when expected
CPMenuBarWindows's font: method did not return a value when a CPFont was expected.
2019-01-04 17:24:05 -08:00
Didier Korthoudt 3d4c08ad20 CPStepper-enabled-from-IB 2018-10-27 12:01:57 +02:00
Didier Korthoudt 594d415976 Merge remote-tracking branch 'cappuccino/master' 2018-10-27 11:59:52 +02:00
daboe01 ae50fde4e1 fixed: wrong return type of isMiniaturized 2018-10-14 16:49:49 +02:00
daboe01 401461f1f0 fixed: wrong return type in isMovable 2018-10-14 13:36:53 +02:00
daboe01 676c6e106d fixed: wrong return type of backgroundColorForGlyphAtIndex: 2018-10-14 13:34:33 +02:00
daboe01 d0a066ccd3 fixed: wrong return type of _menuWithName: 2018-10-14 13:31:20 +02:00
daboe01 340e286277 fixed: wrong return type in shouldArchiveValueForKey: 2018-10-14 13:27:38 +02:00
daboe01 9211d41984 fixed: wrong return type in initializer of _CPCibClassSwapper 2018-10-14 13:24:03 +02:00
daboe01 686aa457d6 fixed: cut/copy did not autoenable 2018-10-07 21:22:37 +02:00
daboe01 1b1431f0d0 menu cleanup 2018-10-07 21:08:31 +02:00
daboe01 cbcb46597d fixed: syntax errors 2018-10-06 23:19:50 +02:00
daboe01 6644c85978 new: context menu support for CPTextView 2018-10-06 23:16:46 +02:00
Didier KorthoudtandMartin Carlberg dd781fd86b Fixed: Enabled state for CPSlider was not converted by nib2cib (#2775) 2018-09-17 09:11:36 +02:00
Didier Korthoudt 97209846ad Merge remote-tracking branch 'cappuccino/master' 2018-09-16 10:55:10 +02:00
Alexander Ljungberg 9744aa79b3 Fixed: doc generation crash due to bad UTF.
Not clear what's causing the problem, but the ± in CPDate.j get prefixed with some garbage bytes in the Doxygen output even that the input encoding looks fine.

The work-around for now is to just discard the bad bytes in the Python step. The output comes out correct (with the ± preserved).
2018-09-10 12:14:35 +01:00
Didier Korthoudt 801648fcfe Merge remote-tracking branch 'cappuccino/master' 2018-09-10 11:45:02 +02:00
Alexander Ljungberg 181fb9edb6 New: Dockerfile which helps test bootstrap itself. 2018-09-10 10:39:07 +01:00
Alexander Ljungberg 9ddbdfb80a Fixed: new rsync-base option always on.
Whether enabled or not, this new feature would try to rsync from the current folder.

Refs #2769.
2018-09-10 10:21:57 +01:00
Alexander Ljungberg 1cab88181d Fixed: XcodeCapp good build reporting failure.
After a recent change, a successful build of XcodeCapp actually resulted in the message "Unable to build  XcodeCapp."
2018-09-07 23:52:08 +01:00
Alexander Ljungberg 803cc6e402 New: bump XcodeCapp copyright year. 2018-09-07 23:49:20 +01:00
Alexander Ljungberg 2e0b9f5ea5 New: tweak flash view warning. 2018-09-07 23:16:31 +01:00
Alexander Ljungberg fe60311072 This is Cappuccino 1.0. 2018-09-06 18:38:52 +01:00
Alexander Ljungberg de4a1d1e93 Fixed: building the source link. 2018-09-03 14:54:48 +02:00
David RichardsonandMartin Carlberg de36a2f4d1 Fixed: Update XcodeCapp code and Jakefile to install properly and launch without crashing when built with Xcode 10/ macOSSDK10.14 (#2773)
Disable YRKSpinningProgressIndicator threaded animation (causing immediate crash on launch since Xcode 10 beta 6).
Simplify Jakefile build process to 'xcodebuild install' to avoid permissions problems when building (still requires sudo when using Cappuccino defaults).
Increment version number to 4.0.1 to reflect minor nature of change.
Update About window version number field to fully display and properly align longer version numbers.
2018-09-03 10:29:43 +02:00
David RichardsonandMartin Carlberg 57f6f3b318 New: Add --rsync-base option to bootstrap.sh (#2769)
Existing --copy-local option to use local cappuccino-base has following limitations which are cumbersome when updating bootstrap process
* local source must be a zip archive - which requires an extra step after making changes to source
* local zip archive is erased after install - which makes repetitive testing more difficult

This commit intended to allow testing of updates to cappuccino-base narwhal-jsc.
With macOS 10.14 and later, narwhal-jsc must use modern c++ standard library.
libedit must do the same, so the dependency reference to it is updated to version which is current at this time.
2018-08-31 14:51:20 +02:00
Didier KorthoudtandMartin Carlberg 8a3eb0d83b FIXED: This PR fixes a side effect of #2744 on table data views (#2767)
When a textField is used in a tableView, setValue forThemeAttribute
should refer to state CPThemeStateTableDataView instead of
CPThemeStateNormal as #2744 is now more accurate on states.
2018-08-31 09:14:54 +02:00
Didier KorthoudtandMartin Carlberg dd68b37a2a Fixed: CPButtonBar with actionPopupButton was outside the visible rect. Fixes #2770 (#2771) 2018-08-31 09:10:44 +02:00
cacaodev 96793b39ba Revert "Fixed: Define header-view-height attribute in ThemeDescriptors. (#2764)"
This reverts commit 9ad44e6dc2.
2018-08-30 16:02:48 +02:00
Didier Korthoudt aa4f91b3b0 Merge remote-tracking branch 'cappuccino/master' 2018-08-30 09:13:16 +02:00
cacaodevandMartin Carlberg 9ad44e6dc2 Fixed: Define header-view-height attribute in ThemeDescriptors. (#2764)
This is a better solution for problem introduced by  #2758
2018-08-29 14:28:02 +02:00
Didier KorthoudtandMartin Carlberg 988b3d27b8 Fixed: allowFadingOut side effect introduced by #2718 (#2768) 2018-08-29 11:27:50 +02:00
Didier Korthoudt 043cc13bb3 Merge remote-tracking branch 'cappuccino/master' 2018-08-27 23:24:52 +02:00
Didier KorthoudtandMartin Carlberg c0ee5f6563 Fixed: Problem with nib2cib introduced by #2758 (#2763) 2018-08-22 09:41:37 +02:00
cacaodevandMartin Carlberg f02d0ab1dd STYLE: refactoring using CPCharacterSet (#2762) 2018-08-22 09:18:21 +02:00
Didier Korthoudtandcacaodev ff6d17990f FIXED: Before this PR, starting a column drag within a scrolled down table would scroll the table content to the bottom (#2753)
Before this commit, in the method [view convertPoint:aPoint toView:aView] : aPoint was not in the coordinate system of view.
2018-08-21 19:26:42 +02:00
Didier Korthoudt 9be52f639d Merge remote-tracking branch 'cappuccino/master' 2018-08-21 11:22:22 +02:00
Didier KorthoudtandMartin Carlberg 586c419c46 Fixed: Before this PR, Nib2Cib wasn't using theme attribute "header-view-height" (#2758) 2018-08-21 09:26:40 +02:00
Didier KorthoudtandMartin Carlberg 73f02cc1bf NEW: Add first responder management based on content view to CPScrollView (#2757) 2018-08-21 09:14:21 +02:00
Didier KorthoudtandMartin Carlberg eb27d24923 Fixed: CPTableView-visual-problem(#2755) 2018-08-21 09:08:16 +02:00
Didier KorthoudtandMartin Carlberg eeb056d509 Fixed: CPCursor-fix-and-update, some cursors were badly or not displayed (#2752) 2018-08-20 13:35:18 +02:00
Didier KorthoudtandMartin Carlberg 3615912ce0 Fixed: Before this PR, loading of a CPTabView from a CIB file could crash the app (#2686) 2018-08-20 13:30:02 +02:00
Didier KorthoudtandMartin Carlberg 4518dfc0ca FIXED: CPSearchField was positionning its menu with hard coded offset (#2691) 2018-08-20 13:12:22 +02:00
Didier KorthoudtandMartin Carlberg d5a7e8938d CPTrackingArea-manual-test-fix (#2759) 2018-08-20 13:01:52 +02:00
Didier KorthoudtandMartin Carlberg 3d15844f7c CPTableView-bad-selection-color (#2756) 2018-08-20 12:55:35 +02:00
Didier KorthoudtandMartin Carlberg 62ed21b316 Fixed: Before this PR, when dragging a partially masked column, the dragged view was truncated (#2754) 2018-08-20 12:54:17 +02:00
daboe01andMartin Carlberg a178d43b01 Fixed: return key was not swallowed by textview but triggered the default button. (#2598) 2018-08-20 12:52:53 +02:00
daboe01andMartin Carlberg ef2152beea FIXED: click to key window in the background did not bring it forward (take 2) (#2705) 2018-08-20 12:48:58 +02:00
daboe01andMartin Carlberg e6475c1a9e Fixed: setMinSize: had no effect in platform windows (#2609) 2018-08-20 12:47:29 +02:00
daboe01andMartin Carlberg 091debd2a7 Fixed: deviation from cocoa when calling setTitle:'' (#2607) 2018-08-20 11:40:00 +02:00
daboe01andMartin Carlberg e7eff3e585 Fixed: attached sheets were not put to front after calls to makeKeyAndOrderFront: in code (#2606) 2018-08-20 11:34:21 +02:00
daboe01andMartin Carlberg 86a90e39d2 Fixed: key equivalent takes precedence over event tracking which is not the case in cocoa (#2600) 2018-08-20 11:31:36 +02:00
Didier KorthoudtandMartin Carlberg 2887fbb251 Edge-browser-detection (#2751) 2018-08-20 11:29:28 +02:00
daboe01andMartin Carlberg fa6177b56d Fixed: touch scrolling events to scrollviews also scrolled the document body (#2750) 2018-08-20 11:05:13 +02:00
Alexandre WilhelmandMartin Carlberg 78524cdde7 Fixed: deprecate FlashView, this old technology is now almost banned for all browser (#2748) 2018-08-20 10:59:27 +02:00
Didier KorthoudtandMartin Carlberg 6077d3f4c3 FIXED: _CPThemeAttribute valueForState was, under some circumstances, ignoring theme settings (#2744) 2018-08-20 10:50:08 +02:00
cacaodevandMartin Carlberg fd3acc456d FIXED: Before this commit, CPScrollerView was not correctly differentiating the scroller-width attribute (#2740)
between the vertical scroller and the horizontal scroller.
The scroller width was picked from a common CPScroller instance cached in CPScroller.
The bug was not visible because the method -_adjustScrollerSize was correcting the size repeatidly in the layout pass.
This was causing unnecessary calls to setFrame: in a performance sensitive method (triggered by scroll events).
The ScrollView is now picking the right width from the theme and a setFrame: call is skipped.

Tests: Manual/CPScrollView2/

TODO : The _adjustScrollerSize method should be enabled with a flag only when a width change is explicitely requested.
2018-08-20 10:36:59 +02:00
Didier Korthoudt 257fe6dd3f Merge remote-tracking branch 'cappuccino/master' 2018-08-16 14:54:27 +02:00
daniel 8c72e77e0d Merge remote-tracking branch 'cappuccino/master' 2018-04-08 12:07:30 -07:00
865 changed files with 66310 additions and 30207 deletions
+34
View File
@@ -0,0 +1,34 @@
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node build
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [17.x, 18.x, 19.x, 20.x, 21.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: echo "${PWD}/dist/cappuccino/bin" >> $GITHUB_PATH
- run: echo "${PWD}/dist/objective-j/bin" >> $GITHUB_PATH
- run: npm install
- run: npm update
- run: jake dist
- run: jake test-only
+9 -1
View File
@@ -17,4 +17,12 @@ Tests/Manual/**/*.xcodeproj
*.sublime-project *.sublime-project
*.sublime-workspace *.sublime-workspace
*.tm_properties *.tm_properties
*.idea *.idea
node_modules
*.vscode
/dist/objective-j/objj-executable
/dist/objective-j/package.json
/dist/objective-j/lib
/dist/cappuccino/package.json
/dist/cappuccino/lib
+28
View File
@@ -0,0 +1,28 @@
.DS_Store
Frameworks
!/dist/objective-j/Frameworks
!/dist/cappuccino-j/Frameworks
Build
Demos
./Aristo
WebSite
.push-package
*.xcodeproj*
*.xcodeproj/*.pbxuser
*.xcodeproj/*.perspectivev3
xcuserdata/
!*.xcodeproj/project.pbxproj
*.xCodeSupport/
*.XcodeSupport/
*XcodeSupport/
Tests/Manual/**/*.xcodeproj
*.sublime-project
*.sublime-workspace
*.tm_properties
*.idea
node_modules
*.vscode
/dist/objective-j/objj-executable
/dist/objective-j/package.json
/dist/cappuccino/package.json
+1 -3
View File
@@ -62,8 +62,6 @@
@import "CPDocument.j" @import "CPDocument.j"
@import "CPDocumentController.j" @import "CPDocumentController.j"
@import "CPEvent.j" @import "CPEvent.j"
@import "CPFlashMovie.j"
@import "CPFlashView.j"
@import "CPFont.j" @import "CPFont.j"
@import "CPFontManager.j" @import "CPFontManager.j"
@import "CPGradient.j" @import "CPGradient.j"
@@ -116,4 +114,4 @@
@import "CPWebView.j" @import "CPWebView.j"
@import "CPWindow.j" @import "CPWindow.j"
@import "CPWindowController.j" @import "CPWindowController.j"
@import "CPWorkspace.j" @import "CPWorkspace.j"
+7 -7
View File
@@ -132,7 +132,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
+ (CPApplication)sharedApplication + (CPApplication)sharedApplication
{ {
if (!CPApp) if (!CPApp)
CPApp = [[CPApplication alloc] init]; CPApp = [[self alloc] init];
return CPApp; return CPApp;
} }
@@ -479,7 +479,7 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
[self _didResignActive]; [self _didResignActive];
} }
- (void)isActive - (BOOL)isActive
{ {
return _isActive; return _isActive;
} }
@@ -628,11 +628,6 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
var theWindow = [anEvent window]; var theWindow = [anEvent window];
// Check if this is a candidate for key equivalent...
if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
// The key equivalent was handled.
return;
if ([anEvent type] == CPMouseMoved) if ([anEvent type] == CPMouseMoved)
{ {
if (theWindow !== _lastMouseMoveWindow) if (theWindow !== _lastMouseMoveWindow)
@@ -669,6 +664,11 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
_eventListenerInsertionIndex = _eventListeners.length; _eventListenerInsertionIndex = _eventListeners.length;
// Check if this is a candidate for key equivalent...
if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
// The key equivalent was handled.
return;
if (theWindow) if (theWindow)
[theWindow sendEvent:anEvent]; [theWindow sendEvent:anEvent];
} }
+24 -25
View File
@@ -219,17 +219,6 @@
_clearsFilterPredicateOnInsertion = aFlag; _clearsFilterPredicateOnInsertion = aFlag;
} }
/*!
Whether the receiver will always return the multiple values marker when multiple
items are selected, even if the items have the same value.
@return BOOL YES if the receiver always uses the multiple values marker
*/
- (BOOL)alwaysUsesMultipleValuesMarker
{
return _alwaysUsesMultipleValuesMarker;
}
/*! /*!
Sets whether the receiver should always return the multiple values marker when multiple Sets whether the receiver should always return the multiple values marker when multiple
items are selected, even if the items have the same value. items are selected, even if the items have the same value.
@@ -282,7 +271,7 @@
if (_disableSetContent) if (_disableSetContent)
return; return;
if (value === nil) if (value == nil)
value = []; value = [];
if (![value isKindOfClass:[CPArray class]]) if (![value isKindOfClass:[CPArray class]])
@@ -308,27 +297,18 @@
class. class.
*/ */
if (_clearsFilterPredicateOnInsertion)
[self willChangeValueForKey:@"filterPredicate"];
// Don't use [super setContent:] as that would fire the contentObject change. // Don't use [super setContent:] as that would fire the contentObject change.
// We need to be in control of when notifications fire. // We need to be in control of when notifications fire.
// Note that if we have a contentArray binding, setting the content does /not/ // Note that if we have a contentArray binding, setting the content does /not/
// cause a reverse binding set. // cause a reverse binding set.
_contentObject = value; _contentObject = value;
if (_clearsFilterPredicateOnInsertion && _filterPredicate != nil) [self _rearrangeObjects];
[self __setFilterPredicate:nil]; // Causes a _rearrangeObjects.
else
[self _rearrangeObjects];
if ([self preservesSelection]) if ([self preservesSelection])
[self __setSelectedObjects:oldSelectedObjects]; [self __setSelectedObjects:oldSelectedObjects];
else else
[self __setSelectionIndexes:oldSelectionIndexes]; [self __setSelectionIndexes:oldSelectionIndexes];
if (_clearsFilterPredicateOnInsertion)
[self didChangeValueForKey:@"filterPredicate"];
} }
/*! /*!
@@ -629,6 +609,25 @@
return YES; return YES;
} }
- (void)_selectionWillChange
{
// Push back all data from the dirty editors before it is too late.
var editorsCount = [_editors count];
while (editorsCount--)
{
var allBindings = [CPBinder allBindingsForObject:_editors[editorsCount]],
allKeys = [allBindings allKeys],
keysCount = allKeys.length;
while (keysCount--)
[[allBindings objectForKey:allKeys[keysCount]] reverseSetValueFor:allKeys[keysCount]];
}
[super _selectionWillChange];
}
/*! /*!
Returns an array of the selected objects. Returns an array of the selected objects.
@@ -774,7 +773,7 @@
_filterPredicate = nil; _filterPredicate = nil;
[self _rearrangeObjects]; [self _rearrangeObjects];
} }
else if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object]) else if (_filterPredicate == nil || [_filterPredicate evaluateWithObject:object])
{ {
// Insert directly into the array. // Insert directly into the array.
var pos = [_arrangedObjects insertObject:object inArraySortedByDescriptors:_sortDescriptors]; var pos = [_arrangedObjects insertObject:object inArraySortedByDescriptors:_sortDescriptors];
@@ -787,7 +786,7 @@
[_selectionIndexes shiftIndexesStartingAtIndex:pos by:1]; [_selectionIndexes shiftIndexesStartingAtIndex:pos by:1];
} }
/* /*
else if (_filterPredicate !== nil) else if (_filterPredicate != nil)
... ...
// Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does // Implies _filterPredicate && ![_filterPredicate evaluateWithObject:object], so the new object does
// not appear in arrangedObjects and we do not have to update at all. // not appear in arrangedObjects and we do not have to update at all.
@@ -887,7 +886,7 @@
_disableSetContent = NO; _disableSetContent = NO;
if (_filterPredicate === nil || [_filterPredicate evaluateWithObject:object]) if (_filterPredicate == nil || [_filterPredicate evaluateWithObject:object])
{ {
// selectionIndexes change notification will be fired as a result of the // selectionIndexes change notification will be fired as a result of the
// content change. Don't fire manually. // content change. Don't fire manually.
+2 -2
View File
@@ -231,7 +231,7 @@ var DefaultLineWidth = 1.0;
/*! /*!
Cocoa compatibility. Cocoa compatibility.
*/ */
- (void)getLineDash:(CPArrayRef)patternRef count:(NSInteger)count phase:(CGFloatRef)phaseRef - (void)getLineDash:(CPArrayRef)patternRef count:(CPInteger)count phase:(CGFloatRef)phaseRef
{ {
return [self getLineDash:patternRef phase:phaseRef]; return [self getLineDash:patternRef phase:phaseRef];
} }
@@ -250,7 +250,7 @@ var DefaultLineWidth = 1.0;
/*! /*!
Cocoa compatibility. Cocoa compatibility.
*/ */
- (void)setLineDash:(CPArray)aPattern count:(NSInteger)count phase:(CGFloat)aPhase - (void)setLineDash:(CPArray)aPattern count:(CPInteger)count phase:(CGFloat)aPhase
{ {
[self setLineDash:aPattern phase:aPhase]; [self setLineDash:aPattern phase:aPhase];
} }
+366 -72
View File
@@ -22,13 +22,14 @@
@import "CPTextField.j" @import "CPTextField.j"
@import "CPView.j" @import "CPView.j"
@import <Foundation/CPGeometry.j>
// CPBoxType // CPBoxType
@typedef CPBoxType @typedef CPBoxType
CPBoxPrimary = 0; CPBoxPrimary = 0;
CPBoxSecondary = 1; CPBoxSecondary = 1; // Deprecated
CPBoxSeparator = 2; CPBoxSeparator = 2;
CPBoxOldStyle = 3; CPBoxOldStyle = 3; // Deprecated
CPBoxCustom = 4; CPBoxCustom = 4;
// CPBorderType // CPBorderType
@@ -58,12 +59,16 @@ CPBelowBottom = 6;
@implementation CPBox : CPView @implementation CPBox : CPView
{ {
CPBoxType _boxType; CPBoxType _boxType;
CPBorderType _borderType; CPBorderType _borderType; // deprecated
CPView _contentView; CPView _contentView;
CPView _boxView; // needed for CSS theming, will be transparent for non CSS themes
BOOL _transparent @accessors(getter=isTransparent);
CPString _title @accessors(getter=title); CPString _title @accessors(getter=title);
int _titlePosition @accessors(getter=titlePosition); int _titlePosition @accessors(getter=titlePosition);
CPTextField _titleView; CPTextField _titleView;
BOOL _cachedAutoresizesSubviews;
} }
+ (Class)_binderClassForBinding:(CPString)aBinding + (Class)_binderClassForBinding:(CPString)aBinding
@@ -90,6 +95,14 @@ CPBelowBottom = 6;
@"inner-shadow-size": 6.0, @"inner-shadow-size": 6.0,
@"inner-shadow-color": [CPNull null], @"inner-shadow-color": [CPNull null],
@"content-margin": CGSizeMakeZero(), @"content-margin": CGSizeMakeZero(),
@"title-font": [CPNull null],
@"title-left-offset": 5.0,
@"title-top-offset": 0.0,
@"title-color": [CPNull null],
@"nib2cib-adjustment-primary-frame": CGRectMake(4, -4, -8, -6),
@"content-adjustment": CGRectMakeZero(),
@"min-y-correction-no-title": 0,
@"min-y-correction-title": 0
}; };
} }
@@ -114,16 +127,26 @@ CPBelowBottom = 6;
if (self) if (self)
{ {
_borderType = CPBezelBorder; _borderType = CPGrooveBorder; // Was CPBezelBorder but Cocoa default is CPGrooveBorder
_boxType = CPBoxPrimary;
_titlePosition = CPNoTitle; _titlePosition = CPNoTitle;
_titleView = [CPTextField labelWithTitle:@""]; _titleView = [CPTextField labelWithTitle:@""];
[_titleView setFont:[self titleFont]];
[_titleView setTextColor:[self titleColor]];
_boxView = [[CPView alloc] initWithFrame:[self bounds]];
[_boxView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
_contentView = [[CPView alloc] initWithFrame:[self bounds]]; _contentView = [[CPView alloc] initWithFrame:[self bounds]];
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; [_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[self setAutoresizesSubviews:YES]; [self setAutoresizesSubviews:YES];
[self addSubview:_contentView]; [self addSubview:_boxView];
[_boxView setAutoresizesSubviews:YES];
[_boxView addSubview:_contentView];
[self sizeToFit];
} }
return self; return self;
@@ -155,6 +178,8 @@ CPBelowBottom = 6;
*/ */
- (CPBorderType)borderType - (CPBorderType)borderType
{ {
CPLog.warn("CPBox borderType is deprecated.");
return _borderType; return _borderType;
} }
@@ -177,7 +202,8 @@ CPBelowBottom = 6;
return; return;
_borderType = aBorderType; _borderType = aBorderType;
[self setNeedsDisplay:YES];
[self refreshDisplay];
} }
/*! /*!
@@ -217,13 +243,46 @@ CPBelowBottom = 6;
*/ */
- (void)setBoxType:(CPBoxType)aBoxType - (void)setBoxType:(CPBoxType)aBoxType
{ {
if ((aBoxType == CPBoxSecondary) || (aBoxType == CPBoxOldStyle))
CPLog.warn("CPBox setBoxType: CPBoxSecondary and CPBoxOldStyle are deprecated.");
if (_boxType === aBoxType) if (_boxType === aBoxType)
return; return;
_boxType = aBoxType; _boxType = aBoxType;
[self setNeedsDisplay:YES];
[self refreshDisplay];
} }
- (void)setTransparent:(BOOL)shouldBeTransparent
{
if (_transparent == shouldBeTransparent)
return;
_transparent = shouldBeTransparent;
[self _manageTitlePositioning];
}
#pragma mark -
#pragma mark Style properties which override theme values
/*!
The borderColor, borderWidth, cornerRadius and fillColor properties for the receiver
are only supported for boxes with boxType === CPBoxCustom and borderType === CPLineBorder.
Boxes with the Primary boxType have fixed values which are defined by the system theme.
Apple does support lineTypes of Groove and Bezel for boxes of type CPBoxCustom, CPBoxSecondary and CPBoxOldStyle,
but they are deprecated as of macOS 10.15.
Cappuccino has supported these in the past but no longer does so - both to simplify CSS-based theming and
to avoid the effort needed for supporting something which will be very short-lived.
These styles can be recreated as custom theme elements by developers, as needed.
Additionally, boxes with boxType === CPBoxSeparator (horizontal and vertical lines) have never allowed changing these values.
No warnings are generated for separator boxes.
*/
// See discussion above.
#pragma mark borderColor
- (CPColor)borderColor - (CPColor)borderColor
{ {
return [self valueForThemeAttribute:@"border-color"]; return [self valueForThemeAttribute:@"border-color"];
@@ -231,12 +290,25 @@ CPBelowBottom = 6;
- (void)setBorderColor:(CPColor)color - (void)setBorderColor:(CPColor)color
{ {
if (_boxType === CPBoxSeparator)
{
return;
}
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setBorderColor: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setBorderColor. Property is ignored.");
return;
}
if ([color isEqual:[self borderColor]]) if ([color isEqual:[self borderColor]])
return; return;
[self setValue:color forThemeAttribute:@"border-color"]; [self setValue:color forThemeAttribute:@"border-color"];
} }
// See discussion above.
#pragma mark borderWidth
- (float)borderWidth - (float)borderWidth
{ {
return [self valueForThemeAttribute:@"border-width"]; return [self valueForThemeAttribute:@"border-width"];
@@ -244,12 +316,25 @@ CPBelowBottom = 6;
- (void)setBorderWidth:(float)width - (void)setBorderWidth:(float)width
{ {
if (_boxType === CPBoxSeparator)
{
return;
}
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setBorderWidth: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setBorderWidth. Property is ignored.");
return;
}
if (width === [self borderWidth]) if (width === [self borderWidth])
return; return;
[self setValue:width forThemeAttribute:@"border-width"]; [self setValue:width forThemeAttribute:@"border-width"];
} }
// See discussion above.
#pragma mark cornerRadius
- (float)cornerRadius - (float)cornerRadius
{ {
return [self valueForThemeAttribute:@"corner-radius"]; return [self valueForThemeAttribute:@"corner-radius"];
@@ -257,12 +342,25 @@ CPBelowBottom = 6;
- (void)setCornerRadius:(float)radius - (void)setCornerRadius:(float)radius
{ {
if (_boxType === CPBoxSeparator)
{
return;
}
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setCornerRadius: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setCornerRadius. Property is ignored.");
return;
}
if (radius === [self cornerRadius]) if (radius === [self cornerRadius])
return; return;
[self setValue:radius forThemeAttribute:@"corner-radius"]; [self setValue:radius forThemeAttribute:@"corner-radius"];
} }
// See discussion above.
#pragma mark fillColor
- (CPColor)fillColor - (CPColor)fillColor
{ {
return [self valueForThemeAttribute:@"background-color"]; return [self valueForThemeAttribute:@"background-color"];
@@ -270,6 +368,17 @@ CPBelowBottom = 6;
- (void)setFillColor:(CPColor)color - (void)setFillColor:(CPColor)color
{ {
if (_boxType === CPBoxSeparator)
{
return;
}
if ((_boxType !== CPBoxCustom) || (_borderType !== CPLineBorder))
{
CPLog.warn("CPBox setFillColor: the box must be of type CPBoxCustom AND border of type CPLineBorder in order to use setFillColor. Property is ignored.");
return;
}
if ([color isEqual:[self fillColor]]) if ([color isEqual:[self fillColor]])
return; return;
@@ -286,21 +395,20 @@ CPBelowBottom = 6;
if (aView === _contentView) if (aView === _contentView)
return; return;
var borderWidth = [self borderWidth],
contentMargin = [self valueForThemeAttribute:@"content-margin"];
[aView setFrame:CGRectInset([self bounds], contentMargin.width + borderWidth, contentMargin.height + borderWidth)];
[aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; [aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
// A nil contentView is allowed (tested in Cocoa 2013-02-22). // A nil contentView is allowed (tested in Cocoa 2013-02-22).
if (!aView) if (!aView)
[_contentView removeFromSuperview]; [_contentView removeFromSuperview];
else if (_contentView) else if (_contentView)
[self replaceSubview:_contentView with:aView]; [_boxView replaceSubview:_contentView with:aView];
else else
[self addSubview:aView]; [_boxView addSubview:aView];
_contentView = aView; _contentView = aView;
[self sizeToFit];
[self refreshDisplay];
} }
- (CGSize)contentViewMargins - (CGSize)contentViewMargins
@@ -320,9 +428,14 @@ CPBelowBottom = 6;
{ {
var offset = [self _titleHeightOffset], var offset = [self _titleHeightOffset],
borderWidth = [self borderWidth], borderWidth = [self borderWidth],
contentMargin = [self valueForThemeAttribute:@"content-margin"]; contentMargin = [self valueForThemeAttribute:@"content-margin"],
contentAdjustment = [self valueForThemeAttribute:@"content-adjustment"],
minYCorrection = [self valueForThemeAttribute:(_titlePosition === CPNoTitle ? @"min-y-correction-no-title" : @"min-y-correction-title")];
[self setFrame:CGRectInset(aRect, -(contentMargin.width + borderWidth), -(contentMargin.height + offset[0] + borderWidth))]; [self setFrame:CGRectMake(aRect.origin.x - contentAdjustment.origin.x - contentMargin.width + borderWidth,
aRect.origin.y - contentAdjustment.origin.y - contentMargin.height + borderWidth - minYCorrection,
aRect.size.width + 2 * contentMargin.width - contentAdjustment.size.width,
aRect.size.height + 2 * contentMargin.height - contentAdjustment.size.height)];
} }
- (void)setTitle:(CPString)aTitle - (void)setTitle:(CPString)aTitle
@@ -347,14 +460,42 @@ CPBelowBottom = 6;
- (CPFont)titleFont - (CPFont)titleFont
{ {
return [_titleView font]; if ([self hasThemeAttribute:@"title-font"])
return [self valueForThemeAttribute:@"title-font"];
else
return [_titleView font];
} }
- (void)setTitleFont:(CPFont)aFont - (void)setTitleFont:(CPFont)aFont
{ {
if ([aFont isEqual:[self titleFont]])
return;
if ([self hasThemeAttribute:@"title-font"])
[self setValue:aFont forThemeAttribute:@"title-font"];
[_titleView setFont:aFont]; [_titleView setFont:aFont];
} }
- (CPColor)titleColor
{
if ([self hasThemeAttribute:@"title-color"])
return [self valueForThemeAttribute:@"title-color"];
else
return [_titleView textColor];
}
- (void)setTitleColor:(CPColor)aColor
{
if ([aColor isEqual:[self titleColor]])
return;
if ([self hasThemeAttribute:@"title-color"])
[self setValue:aColor forThemeAttribute:@"title-color"];
[_titleView setTextColor:aColor];
}
/*! /*!
Return the text field used to display the receiver's title. Return the text field used to display the receiver's title.
@@ -365,25 +506,39 @@ CPBelowBottom = 6;
return _titleView; return _titleView;
} }
/*!
Return the rectangle in which the receivers title is drawn.
*/
- (CGRect)titleRect
{
return [_titleView frame];
}
- (void)_manageTitlePositioning - (void)_manageTitlePositioning
{ {
if (_titlePosition == CPNoTitle) if ((_titlePosition == CPNoTitle) || _transparent)
{ {
[_titleView removeFromSuperview]; [_titleView removeFromSuperview];
[self setNeedsDisplay:YES];
if (_boxType !== CPBoxSeparator)
[self sizeToFit];
[self refreshDisplay];
return; return;
} }
[_titleView setStringValue:_title]; [_titleView setStringValue:_title];
[_titleView sizeToFit]; [_titleView sizeToFit];
[self addSubview:_titleView];
var titleLeftOffset = [self valueForThemeAttribute:@"title-left-offset"],
titleTopOffset = [self valueForThemeAttribute:@"title-top-offset"];
switch (_titlePosition) switch (_titlePosition)
{ {
case CPAtTop: case CPAtTop:
case CPAboveTop: case CPAboveTop:
case CPBelowTop: case CPBelowTop:
[_titleView setFrameOrigin:CGPointMake(5.0, 0.0)]; [_titleView setFrameOrigin:CGPointMake(titleLeftOffset, titleTopOffset)]; // FIXME: was 0.0
[_titleView setAutoresizingMask:CPViewNotSizable]; [_titleView setAutoresizingMask:CPViewNotSizable];
break; break;
@@ -391,39 +546,51 @@ CPBelowBottom = 6;
case CPAtBottom: case CPAtBottom:
case CPBelowBottom: case CPBelowBottom:
var h = [_titleView frameSize].height; var h = [_titleView frameSize].height;
[_titleView setFrameOrigin:CGPointMake(5.0, [self frameSize].height - h)]; [_titleView setFrameOrigin:CGPointMake(titleLeftOffset, [self frameSize].height - h - titleTopOffset)];
[_titleView setAutoresizingMask:CPViewMinYMargin]; [_titleView setAutoresizingMask:CPViewMinYMargin];
break; break;
} }
if (!_transparent)
[self addSubview:_titleView];
[self sizeToFit]; [self sizeToFit];
[self setNeedsDisplay:YES]; [self refreshDisplay];
} }
- (void)sizeToFit - (void)sizeToFit
{ {
var contentFrame = [_contentView frame], var offset = [self _titleHeightOffset],
offset = [self _titleHeightOffset], size = [self frameSize];
contentMargin = [self valueForThemeAttribute:@"content-margin"];
if (!contentFrame) [_boxView setFrame:CGRectMake(0, offset[1], size.width, size.height - offset[0])];
if (!_contentView)
return; return;
[_contentView setFrameOrigin:CGPointMake(contentMargin.width, contentMargin.height + offset[1])]; var boxSize = [_boxView frameSize],
contentMargin = [self valueForThemeAttribute:@"content-margin"],
contentAdjustment = [self valueForThemeAttribute:@"content-adjustment"],
borderWidth = [self valueForThemeAttribute:@"border-width"],
minYCorrection = [self valueForThemeAttribute:(_titlePosition === CPNoTitle ? @"min-y-correction-no-title" : @"min-y-correction-title")];
[_contentView setFrame:CGRectMake(contentAdjustment.origin.x + contentMargin.width - borderWidth,
contentAdjustment.origin.y + contentMargin.height - borderWidth + minYCorrection,
boxSize.width - 2 * contentMargin.width + contentAdjustment.size.width,
boxSize.height - 2 * contentMargin.height + contentAdjustment.size.height)];
} }
- (float)_titleHeightOffset - (CPArray)_titleHeightOffset
{ {
if (_titlePosition == CPNoTitle) var titleTopOffset = [self valueForThemeAttribute:@"title-top-offset"];
return [0.0, 0.0];
switch (_titlePosition) switch (_titlePosition)
{ {
case CPAtTop: case CPAtTop:
return [[_titleView frameSize].height, [_titleView frameSize].height]; return [[_titleView frameSize].height + titleTopOffset, [_titleView frameSize].height + titleTopOffset];
case CPAtBottom: case CPAtBottom:
return [[_titleView frameSize].height, 0.0]; return [[_titleView frameSize].height + titleTopOffset, 0.0];
default: default:
return [0.0, 0.0]; return [0.0, 0.0];
@@ -440,21 +607,24 @@ CPBelowBottom = 6;
- (void)drawRect:(CGRect)rect - (void)drawRect:(CGRect)rect
{ {
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
return;
var bounds = [self bounds]; var bounds = [self bounds];
switch (_boxType) if (_boxType === CPBoxSeparator)
{ {
case CPBoxSeparator: // NSBox does not include a horizontal flag for the separator type. We have to determine
// NSBox does not include a horizontal flag for the separator type. We have to determine // the type of separator to draw by the width and height of the frame.
// the type of separator to draw by the width and height of the frame. if (CGRectGetWidth(bounds) === 5.0)
if (CGRectGetWidth(bounds) === 5.0) return [self _drawVerticalSeparatorInRect:bounds];
return [self _drawVerticalSeparatorInRect:bounds]; else if (CGRectGetHeight(bounds) === 5.0)
else if (CGRectGetHeight(bounds) === 5.0) return [self _drawHorizontalSeparatorInRect:bounds];
return [self _drawHorizontalSeparatorInRect:bounds];
break;
} }
if (_transparent)
return;
if (_titlePosition == CPAtTop) if (_titlePosition == CPAtTop)
{ {
bounds.origin.y += [_titleView frameSize].height; bounds.origin.y += [_titleView frameSize].height;
@@ -475,9 +645,6 @@ CPBelowBottom = 6;
switch (_borderType) switch (_borderType)
{ {
case CPBezelBorder: case CPBezelBorder:
[self _drawBezelBorderInRect:bounds];
break;
case CPGrooveBorder: case CPGrooveBorder:
case CPLineBorder: case CPLineBorder:
[self _drawLineBorderInRect:bounds]; [self _drawLineBorderInRect:bounds];
@@ -594,12 +761,83 @@ CPBelowBottom = 6;
@end @end
#pragma mark -
@implementation CPBox (CSSTheming)
- (void)layoutSubviews
{
if (![self isCSSBased] || (_boxType === CPBoxCustom))
return;
var bounds = [self bounds];
if (_boxType === CPBoxSeparator)
{
if (bounds.size.width === 5.0)
{
// Vertical separator
[_boxView setFrame:CGRectMake(2,0,1,bounds.size.height)];
}
else
{
// Horizontal separator
[_boxView setFrame:CGRectMake(0,2,bounds.size.width,1)];
}
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"border-color"]];
return;
}
// All types of boxes (beside custom which is not covered here) always draw the same way, unless they are CPNoBorder.
if ((_borderType !== CPNoBorder) && !_transparent)
{
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"background-color"]];
return;
}
// No border or transparent
[_boxView setBackgroundColor:nil];
}
- (BOOL)isCSSBased
{
return [[self theme] isCSSBased];
}
- (void)refreshDisplay
{
if ([self isCSSBased] && (_boxType !== CPBoxCustom))
[self setNeedsLayout:YES];
else
[self setNeedsDisplay:YES];
}
- (void)setAutoresizesSubviews:(BOOL)flag
{
// CPBox should always resize its subviews, like in Cocoa, whatever is the corresponding flag set.
// We have to keep the flag value as we could have to return it in -autoresizesSubview method.
_cachedAutoresizesSubviews = !!flag;
[super setAutoresizesSubviews:YES];
}
- (BOOL)autoresizesSubview
{
return _cachedAutoresizesSubviews;
}
@end
#pragma mark -
var CPBoxTypeKey = @"CPBoxTypeKey", var CPBoxTypeKey = @"CPBoxTypeKey",
CPBoxBorderTypeKey = @"CPBoxBorderTypeKey", CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
CPBoxTitle = @"CPBoxTitle", CPBoxTitleKey = @"CPBoxTitleKey",
CPBoxTitlePosition = @"CPBoxTitlePosition", CPBoxTitlePositionKey = @"CPBoxTitlePositionKey",
CPBoxTitleView = @"CPBoxTitleView", CPBoxTitleViewKey = @"CPBoxTitleViewKey",
CPBoxContentView = @"CPBoxContentView"; CPBoxContentViewKey = @"CPBoxContentViewKey",
CPBoxBoxViewKey = @"CPBoxBoxViewKey";
@implementation CPBox (CPCoding) @implementation CPBox (CPCoding)
@@ -612,33 +850,56 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
_boxType = [aCoder decodeIntForKey:CPBoxTypeKey]; _boxType = [aCoder decodeIntForKey:CPBoxTypeKey];
_borderType = [aCoder decodeIntForKey:CPBoxBorderTypeKey]; _borderType = [aCoder decodeIntForKey:CPBoxBorderTypeKey];
_title = [aCoder decodeObjectForKey:CPBoxTitle]; _title = [aCoder decodeObjectForKey:CPBoxTitleKey];
_titlePosition = [aCoder decodeIntForKey:CPBoxTitlePosition]; _titlePosition = [aCoder decodeIntForKey:CPBoxTitlePositionKey];
_titleView = [aCoder decodeObjectForKey:CPBoxTitleView] || [CPTextField labelWithTitle:_title];
if (_boxType != CPBoxSeparator) // Important : see comment on encodeWithCoder below
_boxView = [aCoder decodeObjectForKey:CPBoxBoxViewKey];
if (!_boxView)
{ {
// FIXME: we have a problem with CIB decoding here. // We're coming from nib2cib.
// We should be able to simply add : _contentView = [self subviews][0]
// but first box subview seems to be malformed (badly decoded).
// For example, when deployed, this view doesn't have its _trackingAreas array initialized.
// As a (temporary) workaround, we encode/decode the _contentView property. We then transfer the subview hierarchy
// and replace the first (and only) box subview with this _contentView
_contentView = [aCoder decodeObjectForKey:CPBoxContentView] || [[CPView alloc] initWithFrame:[self bounds]]; _boxView = [[CPView alloc] initWithFrame:[self bounds]];
var malformedContentView = [self subviews][0]; _titleView = [CPTextField labelWithTitle:_title];
[_contentView setSubviews:[malformedContentView subviews]];
[self replaceSubview:malformedContentView with:_contentView];
} }
else else
{ {
_titlePosition = CPNoTitle; // We're coming from elsewhere
_titleView = [aCoder decodeObjectForKey:CPBoxTitleViewKey];
} }
[self setAutoresizesSubviews:YES]; _contentView = [aCoder decodeObjectForKey:CPBoxContentViewKey];
// FIXME: super-mega-hyper-trick : _contentView has a superview which is not normal !
// FIXME: (see encodeWithCoder to understand why this is not possible)
// FIXME: we fix this by hand. This is horrible so please find a structural solution !
if (_contentView)
_contentView._superview = nil;
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; [_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_boxView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_boxView setAutoresizesSubviews:YES];
[self setAutoresizesSubviews:YES];
if (_contentView)
[_boxView setSubviews:@[_contentView]];
[self addSubview:_boxView];
[self addSubview:_titleView];
if (_boxType === CPBoxSeparator)
_titlePosition = CPNoTitle;
[_titleView setFont:[self titleFont]];
[_titleView setTextColor:[self titleColor]];
[self _manageTitlePositioning]; [self _manageTitlePositioning];
[self refreshDisplay];
} }
return self; return self;
@@ -646,14 +907,47 @@ var CPBoxTypeKey = @"CPBoxTypeKey",
- (void)encodeWithCoder:(CPCoder)aCoder - (void)encodeWithCoder:(CPCoder)aCoder
{ {
// We have to distinguish between 2 cases :
// - we come from nib2cib
// - we come from elsewhere
//
// When coming from nib2cib, we have no _boxView, _contentView, _titleView.
// We fix _contentView to be the first (and only) subview.
// They will have to be added on decoding.
//
// When coming from elsewhere, we remove _boxView (and thus _contentView) and _titleView
// from the view hierarchy as we'll already encode them via variables.
// They will be putted back during decoding. This way, we reduce the space and speed needed for coding.
var subviews = [self subviews];
if (!_boxView)
{
// We're coming from nib2cib.
_contentView = subviews[0];
[_contentView removeFromSuperview];
}
else
{
// We're coming from elsewhere.
[_boxView removeFromSuperview];
[_titleView removeFromSuperview];
}
[super encodeWithCoder:aCoder]; [super encodeWithCoder:aCoder];
[self setSubviews:subviews];
[aCoder encodeInt:_boxType forKey:CPBoxTypeKey]; [aCoder encodeInt:_boxType forKey:CPBoxTypeKey];
[aCoder encodeInt:_borderType forKey:CPBoxBorderTypeKey]; [aCoder encodeInt:_borderType forKey:CPBoxBorderTypeKey];
[aCoder encodeObject:_title forKey:CPBoxTitle]; [aCoder encodeObject:_title forKey:CPBoxTitleKey];
[aCoder encodeInt:_titlePosition forKey:CPBoxTitlePosition]; [aCoder encodeInt:_titlePosition forKey:CPBoxTitlePositionKey];
[aCoder encodeObject:_titleView forKey:CPBoxTitleView]; [aCoder encodeConditionalObject:_contentView forKey:CPBoxContentViewKey];
[aCoder encodeObject:_contentView forKey:CPBoxContentView]; [aCoder encodeConditionalObject:_titleView forKey:CPBoxTitleViewKey];
[aCoder encodeConditionalObject:_boxView forKey:CPBoxBoxViewKey];
} }
@end @end
+14 -19
View File
@@ -42,7 +42,6 @@
- (CPDragOperation)browser:(CPBrowser)browser validateDrop:(id)info proposedRow:(CPInteger)row column:(CPInteger)column dropOperation:(CPTableViewDropOperation)dropOperation; - (CPDragOperation)browser:(CPBrowser)browser validateDrop:(id)info proposedRow:(CPInteger)row column:(CPInteger)column dropOperation:(CPTableViewDropOperation)dropOperation;
- (CPImage)browser:(CPBrowser)browser imageValueForItem:(id)anItem; - (CPImage)browser:(CPBrowser)browser imageValueForItem:(id)anItem;
- (CPImage)browser:(CPBrowser)browser draggingImageForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset; - (CPImage)browser:(CPBrowser)browser draggingImageForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
- (CPImage)browser:(CPBrowser)browser imageValueForItem:(id)item;
- (CPIndexSet)browser:(CPBrowser)browser selectionIndexesForProposedSelection:(CPIndexSet)proposedSelectionIndexes inColumn:(CPInteger)column; - (CPIndexSet)browser:(CPBrowser)browser selectionIndexesForProposedSelection:(CPIndexSet)proposedSelectionIndexes inColumn:(CPInteger)column;
- (CPInteger)browser:(CPBrowser)browser numberOfChildrenOfItem:(id)item; - (CPInteger)browser:(CPBrowser)browser numberOfChildrenOfItem:(id)item;
- (CPView)browser:(CPBrowser)browser draggingViewForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset; - (CPView)browser:(CPBrowser)browser draggingViewForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(CPInteger)column withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
@@ -62,19 +61,18 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
CPBrowserDelegate_browser_shouldSelectRowIndexes_inColumn_ = 1 << 4, CPBrowserDelegate_browser_shouldSelectRowIndexes_inColumn_ = 1 << 4,
CPBrowserDelegate_browser_writeRowsWithIndexes_inColumn_toPasteboard_ = 1 << 5, CPBrowserDelegate_browser_writeRowsWithIndexes_inColumn_toPasteboard_ = 1 << 5,
CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_ = 1 << 6, CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_ = 1 << 6,
CPBrowserDelegate_browser_imageValueForItem_ = 1 << 7, CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 7,
CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 8, CPBrowserDelegate_browser_imageValueForItem_ = 1 << 8,
CPBrowserDelegate_browser_imageValueForItem_ = 1 << 9, CPBrowserDelegate_browser_selectionIndexesForProposedSelection_inColumn_ = 1 << 9,
CPBrowserDelegate_browser_selectionIndexesForProposedSelection_inColumn_ = 1 << 10, CPBrowserDelegate_browser_numberOfChildrenOfItem_ = 1 << 10,
CPBrowserDelegate_browser_numberOfChildrenOfItem_ = 1 << 11, CPBrowserDelegate_browser_draggingViewForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 11,
CPBrowserDelegate_browser_draggingViewForRowsWithIndexes_inColumn_withEvent_offset_ = 1 << 12, CPBrowserDelegate_browser_child_ofItem_ = 1 << 12,
CPBrowserDelegate_browser_child_ofItem_ = 1 << 13, CPBrowserDelegate_browser_objectValueForItem_ = 1 << 13,
CPBrowserDelegate_browser_objectValueForItem_ = 1 << 14, CPBrowserDelegate_rootItemForBrowser_ = 1 << 14,
CPBrowserDelegate_rootItemForBrowser_ = 1 << 15, CPBrowserDelegate_browser_didChangeLastColumn_toColumn_ = 1 << 15,
CPBrowserDelegate_browser_didChangeLastColumn_toColumn_ = 1 << 16, CPBrowserDelegate_browser_didResizeColumn_ = 1 << 16,
CPBrowserDelegate_browser_didResizeColumn_ = 1 << 17, CPBrowserDelegate_browserSelectionIsChanging_ = 1 << 17,
CPBrowserDelegate_browserSelectionIsChanging_ = 1 << 18, CPBrowserDelegate_browserSelectionDidChange_ = 1 << 18;
CPBrowserDelegate_browserSelectionDidChange_ = 1 << 19;
/*! /*!
@ingroup appkit @ingroup appkit
@@ -211,9 +209,6 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
if ([_delegate respondsToSelector:@selector(browser:validateDrop:proposedRow:column:dropOperation:)]) if ([_delegate respondsToSelector:@selector(browser:validateDrop:proposedRow:column:dropOperation:)])
_implementedDelegateMethods |= CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_; _implementedDelegateMethods |= CPBrowserDelegate_browser_validateDrop_proposedRow_column_dropOperation_;
if ([_delegate respondsToSelector:@selector(browser:imageValueForItem:)])
_implementedDelegateMethods |= CPBrowserDelegate_browser_imageValueForItem_;
if ([_delegate respondsToSelector:@selector(browser:draggingImageForRowsWithIndexes:inColumn:withEvent:offset:)]) if ([_delegate respondsToSelector:@selector(browser:draggingImageForRowsWithIndexes:inColumn:withEvent:offset:)])
_implementedDelegateMethods |= CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_; _implementedDelegateMethods |= CPBrowserDelegate_browser_draggingImageForRowsWithIndexes_inColumn_withEvent_offset_;
@@ -468,7 +463,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
- (id)itemAtRow:(CPInteger)row inColumn:(CPInteger)column - (id)itemAtRow:(CPInteger)row inColumn:(CPInteger)column
{ {
return [_tableDelegates[column] childAtIndex:row]; return [_tableDelegates[column] childAtIndex:row] || nil;
} }
- (BOOL)isLeafItem:(id)item - (BOOL)isLeafItem:(id)item
@@ -478,7 +473,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
- (id)parentForItemsInColumn:(CPInteger)column - (id)parentForItemsInColumn:(CPInteger)column
{ {
return [_tableDelegates[column] _item]; return [_tableDelegates[column] _item] || nil;
} }
- (CPSet)selectedItems - (CPSet)selectedItems
+247 -167
View File
@@ -44,6 +44,7 @@ CPTexturedRoundedBezelStyle = 11; // Round Textured
CPRoundRectBezelStyle = 12; // Round Rect CPRoundRectBezelStyle = 12; // Round Rect
CPRecessedBezelStyle = 13; // Recessed CPRecessedBezelStyle = 13; // Recessed
CPRoundedDisclosureBezelStyle = 14; // Disclosure CPRoundedDisclosureBezelStyle = 14; // Disclosure
CPInlineBezelStyle = 15; // Inline
CPHUDBezelStyle = -1; CPHUDBezelStyle = -1;
@@ -72,14 +73,32 @@ CPPushInCellMask = CPPushInButtonMask;
CPChangeGrayCellMask = CPGrayButtonMask; CPChangeGrayCellMask = CPGrayButtonMask;
CPChangeBackgroundCellMask = CPBackgroundButtonMask; CPChangeBackgroundCellMask = CPBackgroundButtonMask;
CPButtonStateMixed = CPThemeState("mixed"); CPButtonStateMixed = CPThemeState("mixed");
CPButtonStateBezelStyleRounded = CPThemeState("rounded"); CPButtonStateBezelStyleRounded = CPThemeState("rounded"); // IB style : Push
CPButtonStateBezelStyleRoundRect = CPThemeState("roundRect"); CPButtonStateBezelStyleShadowlessSquare = CPThemeState("square"); // IB style : Square
CPButtonStateBezelStyleSmallSquare = CPThemeState("gradient"); // IB style : Gradient
CPButtonStateBezelStyleTexturedRounded = CPThemeState("textured-rounded"); // IB style : Textured rounded
CPButtonStateBezelStyleRoundRect = CPThemeState("roundRect"); // IB style : Round rect
CPButtonStateBezelStyleRecessed = CPThemeState("recessed"); // IB style : Recessed
CPButtonStateBezelStyleInline = CPThemeState("inline"); // IB style : Inline
CPButtonStateBezelStyleRegularSquare = CPThemeState("bevel"); // IB style : Bevel
CPButtonStateBezelStyleTextured = CPThemeState("textured"); // IB style : Textured
CPButtonStateBezelStyleDisclosure = CPThemeState("disclosure"); // IB style : Disclosure triangle
CPButtonStateBezelStyleRoundedDisclosure = CPThemeState("rounded-disclosure"); // IB style : Rounded disclosure
// add all future correspondance between bezel styles and theme state here. // add all future correspondance between bezel styles and theme state here.
var CPButtonBezelStyleStateMap = @{ var CPButtonBezelStyleStateMap = @{
CPRoundedBezelStyle: CPButtonStateBezelStyleRounded, CPRoundedBezelStyle: CPButtonStateBezelStyleRounded,
CPRoundRectBezelStyle: CPButtonStateBezelStyleRoundRect, CPShadowlessSquareBezelStyle: CPButtonStateBezelStyleShadowlessSquare,
CPSmallSquareBezelStyle: CPButtonStateBezelStyleSmallSquare,
CPTexturedRoundedBezelStyle: CPButtonStateBezelStyleTexturedRounded,
CPRoundRectBezelStyle: CPButtonStateBezelStyleRoundRect,
CPRecessedBezelStyle: CPButtonStateBezelStyleRecessed,
CPInlineBezelStyle: CPButtonStateBezelStyleInline,
CPRegularSquareBezelStyle: CPButtonStateBezelStyleRegularSquare,
CPTexturedSquareBezelStyle: CPButtonStateBezelStyleTextured,
CPDisclosureBezelStyle: CPButtonStateBezelStyleDisclosure,
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure
}; };
/// @cond IGNORE /// @cond IGNORE
@@ -108,6 +127,7 @@ CPButtonImageOffset = 3.0;
// NS-style Display Properties // NS-style Display Properties
CPBezelStyle _bezelStyle; CPBezelStyle _bezelStyle;
ThemeState _bezelState;
CPString _keyEquivalent; CPString _keyEquivalent;
unsigned _keyEquivalentModifierMask; unsigned _keyEquivalentModifierMask;
@@ -117,7 +137,7 @@ CPButtonImageOffset = 3.0;
float _periodicDelay; float _periodicDelay;
float _periodicInterval; float _periodicInterval;
BOOL _isTracking; BOOL _isHighlighted;
} }
+ (Class)_binderClassForBinding:(CPString)aBinding + (Class)_binderClassForBinding:(CPString)aBinding
@@ -154,9 +174,17 @@ CPButtonImageOffset = 3.0;
return @{ return @{
@"image": [CPNull null], @"image": [CPNull null],
@"image-offset": 0.0, @"image-offset": 0.0,
@"image-vertical-offset": 0.0,
@"bezel-inset": CGInsetMakeZero(), @"bezel-inset": CGInsetMakeZero(),
@"content-inset": CGInsetMakeZero(), @"content-inset": CGInsetMakeZero(),
@"bezel-color": [CPNull null], @"bezel-color": [CPNull null],
@"image-position": CPImageLeft,
@"vertical-alignment": CPCenterVerticalTextAlignment,
@"alignment": CPCenterTextAlignment,
@"image-scaling": CPImageScaleNone,
@"invert-image": NO,
@"invert-image-on-push": NO,
@"image-color": [CPNull null] // If null, image color follows text color
}; };
} }
@@ -171,12 +199,6 @@ CPButtonImageOffset = 3.0;
if (self) if (self)
{ {
// Should we instead override the defaults?
[self setValue:CPCenterTextAlignment forThemeAttribute:@"alignment"];
[self setValue:CPCenterVerticalTextAlignment forThemeAttribute:@"vertical-alignment"];
[self setValue:CPImageLeft forThemeAttribute:@"image-position"];
[self setValue:CPImageScaleNone forThemeAttribute:@"image-scaling"];
[self setBezelStyle:CPRoundRectBezelStyle]; [self setBezelStyle:CPRoundRectBezelStyle];
[self setBordered:YES]; [self setBordered:YES];
@@ -258,30 +280,6 @@ CPButtonImageOffset = 3.0;
anObjectValue = CPOnState; anObjectValue = CPOnState;
[super setObjectValue:anObjectValue]; [super setObjectValue:anObjectValue];
switch ([self objectValue])
{
case CPMixedState:
[self unsetThemeState:CPThemeStateSelected];
[self setThemeState:CPButtonStateMixed];
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self setThemeState:CPThemeStateHighlighted];
else
[self unsetThemeState:CPThemeStateHighlighted];
break;
case CPOnState:
[self unsetThemeState:CPButtonStateMixed];
[self setThemeState:CPThemeStateSelected];
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self setThemeState:CPThemeStateHighlighted];
else
[self unsetThemeState:CPThemeStateHighlighted];
break;
case CPOffState:
[self unsetThemeStates:[CPThemeStateSelected, CPButtonStateMixed, CPThemeStateHighlighted]];
}
} }
/*! /*!
@@ -378,12 +376,21 @@ CPButtonImageOffset = 3.0;
- (void)setImage:(CPImage)anImage - (void)setImage:(CPImage)anImage
{ {
[self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateNormal]; // This is needed when compiling themes
if (!_bezelState)
_bezelState = CPThemeStateNormal;
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState];
// if we omit this, images will disappear as soon as the button becomes disabled
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateDisabled)];
} }
- (CPImage)image - (CPImage)image
{ {
return [self valueForThemeAttribute:@"image" inState:CPThemeStateNormal]; if (!_bezelState)
_bezelState = CPThemeStateNormal;
return [self valueForThemeAttribute:@"image" inState:_bezelState];
} }
/*! /*!
@@ -392,7 +399,8 @@ CPButtonImageOffset = 3.0;
*/ */
- (void)setAlternateImage:(CPImage)anImage - (void)setAlternateImage:(CPImage)anImage
{ {
[self setValue:anImage forThemeAttribute:@"image" inState:CPThemeStateHighlighted]; [self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHighlighted)];
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateSelected)];
} }
/*! /*!
@@ -400,7 +408,17 @@ CPButtonImageOffset = 3.0;
*/ */
- (CPImage)alternateImage - (CPImage)alternateImage
{ {
return [self valueForThemeAttribute:@"image" inState:CPThemeStateHighlighted]; return [self valueForThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateSelected)];
}
- (void)setHoveredImage:(CPImage)anImage
{
[self setValue:anImage forThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHovered)];
}
- (CPImage)hoveredImage
{
return [self valueForThemeAttribute:@"image" inState:_bezelState.and(CPThemeStateHovered)];
} }
- (void)setImageOffset:(float)theImageOffset - (void)setImageOffset:(float)theImageOffset
@@ -423,11 +441,6 @@ CPButtonImageOffset = 3.0;
_showsStateBy = aMask; _showsStateBy = aMask;
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask) && [self state] != CPOffState)
[self setThemeState:CPThemeStateHighlighted];
else
[self unsetThemeState:CPThemeStateHighlighted];
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
[self setNeedsLayout]; [self setNeedsLayout];
} }
@@ -444,11 +457,8 @@ CPButtonImageOffset = 3.0;
_highlightsBy = aMask; _highlightsBy = aMask;
if ([self hasThemeState:CPThemeStateHighlighted]) [self setNeedsDisplay:YES];
{ [self setNeedsLayout];
[self setNeedsDisplay:YES];
[self setNeedsLayout];
}
} }
- (CPInteger)highlightsBy - (CPInteger)highlightsBy
@@ -533,9 +543,20 @@ CPButtonImageOffset = 3.0;
_periodicInterval = anInterval; _periodicInterval = anInterval;
} }
- (void)highlight:(BOOL)shouldHighlight
{
if (_isHighlighted == shouldHighlight)
return;
_isHighlighted = shouldHighlight;
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
- (void)mouseDown:(CPEvent)anEvent - (void)mouseDown:(CPEvent)anEvent
{ {
if ([self isContinuous]) if ([self isEnabled] && [self isContinuous])
{ {
_continuousDelayTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicDelay callback: function() _continuousDelayTimer = [CPTimer scheduledTimerWithTimeInterval:_periodicDelay callback: function()
{ {
@@ -555,46 +576,12 @@ CPButtonImageOffset = 3.0;
[_target performSelector:_action withObject:self]; [_target performSelector:_action withObject:self];
} }
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
_isTracking = YES;
var startedTracking = [super startTrackingAt:aPoint];
if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
{
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self highlight:[self state] == CPOffState];
else
[self highlight:YES];
}
else
{
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self highlight:[self state] != CPOffState];
else
[self highlight:NO];
}
return startedTracking;
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
{ {
_isTracking = NO;
if (mouseIsUp && CGRectContainsPoint([self bounds], aPoint)) if (mouseIsUp && CGRectContainsPoint([self bounds], aPoint))
[self setNextState]; [self setNextState];
else
{
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
[self highlight:[self state] != CPOffState];
else
[self highlight:NO];
}
[self setNeedsLayout]; [self highlight:NO];
[self setNeedsDisplay:YES];
[self invalidateTimers]; [self invalidateTimers];
} }
@@ -615,7 +602,7 @@ CPButtonImageOffset = 3.0;
- (CGRect)contentRectForBounds:(CGRect)bounds - (CGRect)contentRectForBounds:(CGRect)bounds
{ {
var contentInset = [self currentValueForThemeAttribute:@"content-inset"]; var contentInset = [self valueForThemeAttribute:@"content-inset" inState:[self _contentVisualState]];
return CGRectInsetByInset(bounds, contentInset); return CGRectInsetByInset(bounds, contentInset);
} }
@@ -642,7 +629,7 @@ CPButtonImageOffset = 3.0;
size = [contentView frameSize]; size = [contentView frameSize];
} }
else else
size = [([self title] || " ") sizeWithFont:[self currentValueForThemeAttribute:@"font"]]; size = [([self title] || " ") sizeWithFont:[self font]];
var contentInset = [self currentValueForThemeAttribute:@"content-inset"], var contentInset = [self currentValueForThemeAttribute:@"content-inset"],
minSize = [self currentValueForThemeAttribute:@"min-size"], minSize = [self currentValueForThemeAttribute:@"min-size"],
@@ -698,86 +685,164 @@ CPButtonImageOffset = 3.0;
return [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()]; return [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
} }
- (CPThemeState)_backgroundVisualState
{
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state],
buttonIsOn = (currentState !== CPOffState);
if (_isHighlighted && (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask)))
visualState = visualState.and(CPThemeStateHighlighted);
else
visualState = visualState.without(CPThemeStateHighlighted);
if (buttonIsOn && (_showsStateBy & (CPPushInCellMask | CPChangeGrayCellMask)))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
else
visualState = visualState.without(CPThemeStateSelected);
return visualState;
}
// Note : We have to split content and image visual states as, for example, radio buttons don't follow push buttons behavior
- (CPThemeState)_contentVisualState
{
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state],
buttonIsOn = (currentState !== CPOffState);
// If the button is pushed (_isHighlighted), always add the highlighted state
if (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
visualState = visualState.and(CPThemeStateHighlighted);
else
visualState = visualState.without(CPThemeStateHighlighted);
if (buttonIsOn && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
else
visualState = visualState.without(CPThemeStateSelected);
return visualState;
}
- (CPThemeState)_imageVisualState
{
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state],
buttonIsOn = (currentState !== CPOffState);
// Remove highlighted & selected theme states
visualState = visualState.without(CPThemeStateHighlighted);
visualState = visualState.without(CPThemeStateSelected);
// Note : We have to deal with special case where button is ON, highlightsBy and showsStateBy use content, and button is pushed
// BUT this should not be used for disclosure buttons !
if (_isHighlighted && buttonIsOn && (_highlightsBy & CPContentsCellMask) && (_showsStateBy & CPContentsCellMask) && (_bezelStyle !== CPDisclosureBezelStyle))
return visualState;
if (_isHighlighted && ((_highlightsBy & CPContentsCellMask) || (_highlightsBy & CPChangeGrayCellMask)))
visualState = visualState.and(CPThemeStateHighlighted);
if (buttonIsOn && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
}
- (CPString)_currentTitle
{
var buttonIsOn = ([self state] !== CPOffState);
// Note : We have to deal with special case where button is ON, highlightsBy and showsStateBy use content, and button is pushed
if (_isHighlighted && buttonIsOn && (_highlightsBy & CPContentsCellMask) && (_showsStateBy & CPContentsCellMask))
return _title;
else if (_alternateTitle && ((_isHighlighted && (_highlightsBy & CPContentsCellMask)) || (buttonIsOn && (_showsStateBy & CPContentsCellMask))))
return _alternateTitle;
else
return _title;
}
- (CPImage)_currentImage
{
var visualState = [self _imageVisualState],
currentImage = [self valueForThemeAttribute:@"image" inState:visualState],
imageColor = [self valueForThemeAttribute:@"image-color" inState:visualState],
buttonIsOn = ([self state] !== CPOffState);
if ([currentImage isMaterialIconImage])
{
if (([self valueForThemeAttribute:@"invert-image" inState:visualState] || ([self valueForThemeAttribute:@"invert-image-on-push" inState:visualState] && (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn)))))
currentImage = [currentImage invertedImage];
else if (imageColor && [imageColor isKindOfClass:CPColor])
// In some buttons, image color doesn't follow text color !
currentImage = [currentImage imageVersionWithColor:imageColor];
else
// By default, image color follows text color
currentImage = [currentImage imageVersionWithColor:[self valueForThemeAttribute:@"text-color" inState:[self _contentVisualState]]];
}
return currentImage;
}
- (void)layoutSubviews - (void)layoutSubviews
{ {
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view" var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"]; relativeToEphemeralSubviewNamed:@"content-view"],
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]]; contentView = [self layoutEphemeralSubviewNamed:@"content-view"
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"]; relativeToEphemeralSubviewNamed:@"bezel-view"],
if (contentView) image = [self _currentImage],
{ contentVisualState = [self _contentVisualState];
var title = nil,
image = nil;
if (_isTracking) [bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color" inState:[self _backgroundVisualState]]];
{ [contentView setText:[self _currentTitle]];
if (_highlightsBy & CPContentsCellMask) [contentView setImage:image];
{
if (_showsStateBy & CPContentsCellMask)
{
title = ([self state] == CPOffState && _alternateTitle) ? _alternateTitle : _title;
image = ([self state] == CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
}
else
{
title = [self alternateTitle];
image = [self alternateImage];
}
}
else if (_showsStateBy & CPContentsCellMask)
{
title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
}
else
{
title = _title;
image = [self image];
}
}
else
{
if (_showsStateBy & CPContentsCellMask)
{
title = ([self state] != CPOffState && _alternateTitle) ? _alternateTitle : _title;
image = ([self state] != CPOffState && [self alternateImage]) ? [self alternateImage] : [self image];
}
else
{
title = _title;
image = [self image];
}
}
[contentView setText:title]; [contentView setImageOffset:[self valueForThemeAttribute:@"image-offset" inState:contentVisualState]];
[contentView setImage:image]; [contentView setImageVerticalOffset:[self valueForThemeAttribute:@"image-vertical-offset" inState:contentVisualState]];
[contentView setImageOffset:[self currentValueForThemeAttribute:@"image-offset"]];
[contentView setFont:[self currentValueForThemeAttribute:@"font"]]; [contentView setFont:[self font]];
[contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]]; [contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:contentVisualState]];
[contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]]; [contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:contentVisualState]];
[contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]]; [contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:contentVisualState]];
[contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]]; [contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:contentVisualState]];
[contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]]; [contentView _setUsesSingleLineMode:YES];
[contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]]; [contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:contentVisualState]];
[contentView setImagePosition:[self currentValueForThemeAttribute:@"image-position"]]; [contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:contentVisualState]];
[contentView setImageScaling:[self currentValueForThemeAttribute:@"image-scaling"]]; [contentView setImagePosition:[self valueForThemeAttribute:@"image-position"]];
[contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled]; [contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling"]];
}
// We don't automatically dim material icon images as the color is driven by the theme
[contentView setDimsImage:[self hasThemeState:CPThemeStateDisabled] && _imageDimsWhenDisabled && ![image isMaterialIconImage]];
} }
- (void)setBordered:(BOOL)shouldBeBordered - (void)setBordered:(BOOL)shouldBeBordered
{ {
if (shouldBeBordered) if (shouldBeBordered)
{
[self setThemeState:CPThemeStateBordered]; [self setThemeState:CPThemeStateBordered];
if (_bezelState)
_bezelState = _bezelState.and(CPThemeStateBordered);
else
_bezelState = CPThemeStateBordered;
}
else else
{
[self unsetThemeState:CPThemeStateBordered]; [self unsetThemeState:CPThemeStateBordered];
if (_bezelState)
_bezelState = _bezelState.without(CPThemeStateBordered);
else
_bezelState = CPThemeStateNormal;
}
} }
- (BOOL)isBordered - (BOOL)isBordered
@@ -807,7 +872,7 @@ CPButtonImageOffset = 3.0;
{ {
var selfWindow = [self window]; var selfWindow = [self window];
if (selfWindow === aWindow || aWindow === nil) if (selfWindow === aWindow || aWindow == nil)
return; return;
if ([selfWindow defaultButton] === self) if ([selfWindow defaultButton] === self)
@@ -873,17 +938,7 @@ CPButtonImageOffset = 3.0;
[self setState:[self nextState]]; [self setState:[self nextState]];
var shouldHighlight = NO; [self highlight:YES];
if (_highlightsBy & (CPPushInCellMask | CPChangeGrayCellMask))
{
if (_showsStateBy & (CPChangeGrayCellMask | CPChangeBackgroundCellMask))
shouldHighlight = [self state] == CPOffState;
else
shouldHighlight = YES;
}
[self highlight:shouldHighlight];
try try
{ {
@@ -895,8 +950,7 @@ CPButtonImageOffset = 3.0;
} }
finally finally
{ {
if (shouldHighlight) [CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(unhighlightButtonTimerDidFinish:) userInfo:nil repeats:NO];
[CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(unhighlightButtonTimerDidFinish:) userInfo:nil repeats:NO];
} }
} }
@@ -919,6 +973,22 @@ CPButtonImageOffset = 3.0;
[self setThemeState:newState]; [self setThemeState:newState];
_bezelStyle = aBezelStyle; _bezelStyle = aBezelStyle;
if (_bezelState && newState)
{
if (currentState)
_bezelState =_bezelState.without(currentState);
_bezelState = _bezelState.and(newState);
}
else
_bezelState = newState || CPThemeStateNormal;
// For disclosure triangle and rounded, we have to move away from
// what Xcode tells us as we implement visual behavior with images (so content)
// and not background
if ((_bezelStyle === CPDisclosureBezelStyle) || (_bezelStyle === CPRoundedDisclosureBezelStyle))
[self setShowsStateBy:CPContentsCellMask];
} }
- (unsigned)bezelStyle - (unsigned)bezelStyle
@@ -942,7 +1012,8 @@ var CPButtonImageKey = @"CPButtonImageKey",
CPButtonPeriodicDelayKey = @"CPButtonPeriodicDelayKey", CPButtonPeriodicDelayKey = @"CPButtonPeriodicDelayKey",
CPButtonPeriodicIntervalKey = @"CPButtonPeriodicIntervalKey", CPButtonPeriodicIntervalKey = @"CPButtonPeriodicIntervalKey",
CPButtonHighlightsByKey = @"CPButtonHighlightsByKey", CPButtonHighlightsByKey = @"CPButtonHighlightsByKey",
CPButtonShowsStateByKey = @"CPButtonShowsStateByKey"; CPButtonShowsStateByKey = @"CPButtonShowsStateByKey",
CPButtonBezelStyleKey = @"CPButtonBezelStyleKey";
@implementation CPButton (CPCoding) @implementation CPButton (CPCoding)
@@ -991,6 +1062,12 @@ var CPButtonImageKey = @"CPButtonImageKey",
_keyEquivalentModifierMask = [aCoder decodeIntForKey:CPButtonKeyEquivalentMaskKey]; _keyEquivalentModifierMask = [aCoder decodeIntForKey:CPButtonKeyEquivalentMaskKey];
if ([aCoder containsValueForKey:CPButtonIsBorderedKey])
[self setBordered:[aCoder decodeBoolForKey:CPButtonIsBorderedKey]];
if ([aCoder containsValueForKey:CPButtonBezelStyleKey])
[self setBezelStyle:[aCoder decodeIntForKey:CPButtonBezelStyleKey]];
[self setNeedsLayout]; [self setNeedsLayout];
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
} }
@@ -1025,6 +1102,9 @@ var CPButtonImageKey = @"CPButtonImageKey",
[aCoder encodeObject:_periodicDelay forKey:CPButtonPeriodicDelayKey]; [aCoder encodeObject:_periodicDelay forKey:CPButtonPeriodicDelayKey];
[aCoder encodeObject:_periodicInterval forKey:CPButtonPeriodicIntervalKey]; [aCoder encodeObject:_periodicInterval forKey:CPButtonPeriodicIntervalKey];
[aCoder encodeBool:[self isBordered] forKey:CPButtonIsBorderedKey];
[aCoder encodeInt: [self bezelStyle] forKey:CPButtonBezelStyleKey];
} }
@end @end
+1 -1
View File
@@ -70,7 +70,7 @@
[button addItemWithTitle:nil]; [button addItemWithTitle:nil];
[[button lastItem] setImage:image]; [[button lastItem] setImage:image];
[button setImagePosition:CPImageOnly]; [button setImagePosition:CPImageOnly];
[button setValue:CGInsetMake(0, 0, 0, 0) forThemeAttribute:"content-inset"]; [button setValue:CGInsetMake(0, 0, 0, 0) forThemeAttribute:"content-inset" inState:CPPopUpButtonStatePullsDown];
[button setPullsDown:YES]; [button setPullsDown:YES];
+47 -17
View File
@@ -94,23 +94,6 @@ CPCheckBoxImageOffset = 4.0;
[self takeStateFromKeyPath:aKeyPath ofObjects:objects]; [self takeStateFromKeyPath:aKeyPath ofObjects:objects];
} }
- (CPImage)image
{
return [self currentValueForThemeAttribute:@"image"];
}
- (CPImage)alternateImage
{
return [self currentValueForThemeAttribute:@"image"];
}
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
var startedTracking = [super startTrackingAt:aPoint];
[self highlight:YES];
return startedTracking;
}
#pragma mark - #pragma mark -
#pragma mark Override methods from CPButton #pragma mark Override methods from CPButton
@@ -135,6 +118,36 @@ CPCheckBoxImageOffset = 4.0;
return size; return size;
} }
- (CPThemeState)_contentVisualState
{
// Note : Behavior differs from CPButton as title doesn't follow the highlightsBy content flag
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state];
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
}
- (CPThemeState)_imageVisualState
{
// Note : Behavior differs from CPButton as we don't force "not selected" theme state
// when button is highglighted and selected
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state];
if (_isHighlighted && (_highlightsBy & CPContentsCellMask))
visualState = visualState.and(CPThemeStateHighlighted);
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
}
@end @end
@implementation _CPCheckBoxValueBinder : CPBinder @implementation _CPCheckBoxValueBinder : CPBinder
@@ -163,3 +176,20 @@ CPCheckBoxImageOffset = 4.0;
} }
@end @end
#pragma mark -
@implementation CPCheckBox (TableDataView)
// We overide here _CPObject+Theme setValue:forThemeAttribute as CPCheckBox can be used as tableView data view
// So, when outside a table data view, setValue:forThemeAttribute should store the value with the CPThemeStateNormal (default behavior)
// When inside a table data view, it should store the value with the CPThemeStateTableDataView. If not, the value won't be used if the
// theme defined a value for this attribute for state CPThemeStateTableDataView
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
{
[super setValue:aValue forThemeAttribute:aName];
[super setValue:aValue forThemeAttribute:aName inState:CPThemeStateTableDataView];
}
@end
+1 -1
View File
@@ -739,7 +739,7 @@ var HORIZONTAL_MARGIN = 2;
*/ */
- (void)setMinItemSize:(CGSize)aSize - (void)setMinItemSize:(CGSize)aSize
{ {
if (aSize === nil || aSize === undefined) if (aSize == nil)
[CPException raise:CPInvalidArgumentException reason:"Invalid value provided for minimum size"]; [CPException raise:CPInvalidArgumentException reason:"Invalid value provided for minimum size"];
if (CGSizeEqualToSize(_minItemSize, aSize)) if (CGSizeEqualToSize(_minItemSize, aSize))
+12 -5
View File
@@ -87,7 +87,7 @@ var cachedBlackColor,
CPImage _patternImage; CPImage _patternImage;
CPString _cssString; CPString _cssString;
} }
@global document
#pragma mark - #pragma mark -
#pragma mark Theming #pragma mark Theming
@@ -267,7 +267,7 @@ var cachedBlackColor,
@return an initialized RGB color @return an initialized RGB color
*/ */
+ (CPColor)colorWithHexString:(string)hex + (CPColor)colorWithHexString:(CPString)hex
{ {
var rgba = hexToRGB(hex); var rgba = hexToRGB(hex);
return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null; return rgba ? [[CPColor alloc] _initWithRGBA: rgba] : null;
@@ -561,7 +561,11 @@ var cachedBlackColor,
- (void)_initCSSStringFromComponents - (void)_initCSSStringFromComponents
{ {
var hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0; // Fix to avoid a problem when compiling a theme (missing the alpha component as theme compiler doesn't have CSS rgba capability)
var hasAlpha = YES;
#if PLATFORM(DOM)
hasAlpha = CPFeatureIsCompatible(CPCSSRGBAFeature) && _components[3] != 1.0;
#endif
_cssString = (hasAlpha ? "rgba(" : "rgb(") + _cssString = (hasAlpha ? "rgba(" : "rgb(") +
parseInt(_components[0] * 255.0) + ", " + parseInt(_components[0] * 255.0) + ", " +
@@ -800,6 +804,9 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
var description = [super description], var description = [super description],
patternImage = [self patternImage]; patternImage = [self patternImage];
if ([self isCSSBased])
return description + "\n" + [self cssDictionary]+ "\nBefore:\n" + [self cssBeforeDictionary] + "\nAfter:\n" + [self cssAfterDictionary];
if (!patternImage) if (!patternImage)
return description + " " + [self cssString]; return description + " " + [self cssString];
@@ -933,7 +940,7 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
// You can use -(BOOL)isCSSBased to determine how to cope with it in your code. // You can use -(BOOL)isCSSBased to determine how to cope with it in your code.
// -(BOOL)hasCSSDictionary, -(BOOL)hasCSSBeforeDictionary and -(BOOL)hasCSSAfterDictionary are convience methods you can use. // -(BOOL)hasCSSDictionary, -(BOOL)hasCSSBeforeDictionary and -(BOOL)hasCSSAfterDictionary are convience methods you can use.
// //
// Remark : -(void)restorePreviousCSSState and -(DOMElement)applyCSSColorForView are meant to be used by low level UI widgets (like CPView) to implement // Remark : +(void)restorePreviousCSSState and -(DOMElement)applyCSSColorForView are meant to be used by low level UI widgets (like CPView) to implement
// CSS theme support. // CSS theme support.
@implementation CPColor (CSSTheming) @implementation CPColor (CSSTheming)
@@ -992,7 +999,7 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
return ([_cssAfterDictionary count] > 0); return ([_cssAfterDictionary count] > 0);
} }
- (void)restorePreviousCSSState:(CPArrayRef)aPreviousStateRef forDOMElement:(DOMElement)aDOMElement + (void)restorePreviousCSSState:(CPArrayRef)aPreviousStateRef forDOMElement:(DOMElement)aDOMElement
{ {
#if PLATFORM(DOM) #if PLATFORM(DOM)
var aPreviousState = @deref(aPreviousStateRef); var aPreviousState = @deref(aPreviousStateRef);
+1 -1
View File
@@ -483,7 +483,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
]; ];
} }
var cookieValue = eval(cookieValue); var cookieValue = JSON.parse(cookieValue);
return [cookieValue arrayByApplyingBlock:function(value) return [cookieValue arrayByApplyingBlock:function(value)
{ {
+1 -1
View File
@@ -564,7 +564,7 @@ var CPComboBoxTextSubview = @"text",
var selectedStringValue = [_listDelegate selectedStringValue]; var selectedStringValue = [_listDelegate selectedStringValue];
if (selectedStringValue === nil) if (selectedStringValue == nil)
return NO; return NO;
else else
_selectedStringValue = selectedStringValue; _selectedStringValue = selectedStringValue;
+28
View File
@@ -32,6 +32,7 @@ CPKHTMLBrowserEngine = 1 << 2;
CPOperaBrowserEngine = 1 << 3; CPOperaBrowserEngine = 1 << 3;
CPWebKitBrowserEngine = 1 << 4; // Safari + Chrome CPWebKitBrowserEngine = 1 << 4; // Safari + Chrome
CPBlinkBrowserEngine = 1 << 5; // Recent Chrome CPBlinkBrowserEngine = 1 << 5; // Recent Chrome
CPEdgeBrowserEngine = 1 << 6;
// Operating Systems // Operating Systems
CPMacOperatingSystem = 0; CPMacOperatingSystem = 0;
@@ -83,6 +84,7 @@ CPAltEnterTextAreaFeature = 32;
CPCSSAnimationFeature = 33; CPCSSAnimationFeature = 33;
CPBackspaceTriggersPageBack = 34;
/* /*
When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen. This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203 When an absolutely positioned div (CPView) with an absolutely positioned canvas in it (CPView with drawRect:) moves things on top of the canvas (subviews) don't redraw correctly. E.g. if you have a bunch of text fields in a CPBox in a sheet which animates in, some of the text fields might not be visible because the CPBox has a canvas at the bottom and the box moved form offscreen to onscreen. This bug is probably very related: https://bugs.webkit.org/show_bug.cgi?id=67203
*/ */
@@ -146,6 +148,28 @@ else if (typeof window !== "undefined" && (window.attachEvent || (!(window.Activ
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES; PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = YES;
} }
// Edge
else if (USER_AGENT.indexOf("Edge/") != -1)
{
PLATFORM_ENGINE |= CPEdgeBrowserEngine;
PLATFORM_FEATURES[CPCSSRGBAFeature] = YES;
PLATFORM_FEATURES[CPHTMLContentEditableFeature] = YES;
PLATFORM_FEATURES[CPJavaScriptClipboardEventsFeature] = YES;
PLATFORM_FEATURES[CPJavaScriptClipboardAccessFeature] = NO;
PLATFORM_FEATURES[CPJavaScriptShadowFeature] = YES;
var versionStart = USER_AGENT.indexOf("Edge/") + "Edge/".length,
versionEnd = USER_AGENT.indexOf(" ", versionStart),
versionString = USER_AGENT.substring(versionStart, versionEnd),
versionDivision = versionString.indexOf('.'),
majorVersion = parseInt(versionString.substring(0, versionDivision)),
minorVersion = parseInt(versionString.substr(versionDivision + 1));
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
}
// Safari + Chrome (WebKit and Blink) // Safari + Chrome (WebKit and Blink)
else if (USER_AGENT.indexOf("AppleWebKit/") != -1) else if (USER_AGENT.indexOf("AppleWebKit/") != -1)
{ {
@@ -211,6 +235,7 @@ else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
PLATFORM_ENGINE |= CPGeckoBrowserEngine; PLATFORM_ENGINE |= CPGeckoBrowserEngine;
PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES; PLATFORM_FEATURES[CPJavaScriptCanvasDrawFeature] = YES;
PLATFORM_FEATURES[CPBackspaceTriggersPageBack] = YES;
var index = USER_AGENT.indexOf("Firefox"), var index = USER_AGENT.indexOf("Firefox"),
version = (index === -1) ? 2.0 : parseFloat(USER_AGENT.substring(index + "Firefox".length + 1)); version = (index === -1) ? 2.0 : parseFloat(USER_AGENT.substring(index + "Firefox".length + 1));
@@ -221,6 +246,9 @@ else if (USER_AGENT.indexOf("Gecko") !== -1) // Must follow KHTML check.
if (version < 3.0) if (version < 3.0)
PLATFORM_FEATURES[CPJavaScriptMouseWheelValues_8_15] = YES; PLATFORM_FEATURES[CPJavaScriptMouseWheelValues_8_15] = YES;
if (version >= 66)
PLATFORM_FEATURES[CPJavaScriptRemedialKeySupport] = YES;
// Some day this might be fixed and should be version prefixed. No known fixed version yet. // Some day this might be fixed and should be version prefixed. No known fixed version yet.
PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES; PLATFORM_FEATURES[CPInput1PxLeftPadding] = YES;
+27 -15
View File
@@ -23,12 +23,13 @@
@import <Foundation/CPFormatter.j> @import <Foundation/CPFormatter.j>
@import <Foundation/CPTimer.j> @import <Foundation/CPTimer.j>
@import "CPFont.j"
@import "CPShadow.j" @import "CPShadow.j"
@import "CPText.j" @import "CPText.j"
@import "CPKeyValueBinding.j" @import "CPKeyValueBinding.j"
@import "CPTrackingArea.j" @import "CPTrackingArea.j"
@class CPFont
@global CPApp @global CPApp
@protocol CPControlTextEditingDelegate <CPObject> @protocol CPControlTextEditingDelegate <CPObject>
@@ -48,6 +49,9 @@ CPRegularControlSize = 0;
CPSmallControlSize = 1; CPSmallControlSize = 1;
CPMiniControlSize = 2; CPMiniControlSize = 2;
// To get the theme state corresponding to a control size, use CPControlSizeThemeStates[controlSize]
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini];
@typedef CPLineBreakMode @typedef CPLineBreakMode
CPLineBreakByWordWrapping = 0; CPLineBreakByWordWrapping = 0;
CPLineBreakByCharWrapping = 1; CPLineBreakByCharWrapping = 1;
@@ -131,14 +135,13 @@ var CPControlBlackColor = [CPColor blackColor];
@"vertical-alignment": CPTopVerticalTextAlignment, @"vertical-alignment": CPTopVerticalTextAlignment,
@"line-break-mode": CPLineBreakByClipping, @"line-break-mode": CPLineBreakByClipping,
@"text-color": [CPColor blackColor], @"text-color": [CPColor blackColor],
@"font": [CPFont systemFontOfSize:CPFontCurrentSystemSize], @"font": [CPNull null],
@"text-shadow-color": [CPNull null], @"text-shadow-color": [CPNull null],
@"text-shadow-offset": CGSizeMakeZero(), @"text-shadow-offset": CGSizeMakeZero(),
@"image-position": CPImageLeft, @"image-position": CPImageLeft,
@"image-scaling": CPScaleToFit, @"image-scaling": CPScaleToFit,
@"min-size": CGSizeMakeZero(), @"min-size": CGSizeMakeZero(),
@"max-size": CGSizeMake(-1.0, -1.0), @"max-size": CGSizeMake(-1.0, -1.0)
@"nib2cib-adjustment-frame": CGRectMakeZero()
}; };
} }
@@ -196,6 +199,9 @@ var CPControlBlackColor = [CPColor blackColor];
{ {
_sendActionOn = CPLeftMouseUpMask; _sendActionOn = CPLeftMouseUpMask;
_trackingMouseDownFlags = 0; _trackingMouseDownFlags = 0;
[self setControlSize:CPThemeStateControlSizeRegular];
[self updateTrackingAreas];
} }
return self; return self;
@@ -620,15 +626,15 @@ var CPControlBlackColor = [CPColor blackColor];
*/ */
- (CPString)stringValue - (CPString)stringValue
{ {
if (_formatter && _value !== undefined) if (_formatter && _value != nil)
{ {
var formattedValue = [self hasThemeState:CPThemeStateEditing] ? [_formatter editingStringForObjectValue:_value] : [_formatter stringForObjectValue:_value]; var formattedValue = [self hasThemeState:CPThemeStateEditing] ? [_formatter editingStringForObjectValue:_value] : [_formatter stringForObjectValue:_value];
if (formattedValue !== nil && formattedValue !== undefined) if (formattedValue != nil)
return formattedValue; return formattedValue;
} }
return (_value === undefined || _value === nil) ? @"" : String(_value); return _value == nil ? @"" : String(_value);
} }
/*! /*!
@@ -637,7 +643,7 @@ var CPControlBlackColor = [CPColor blackColor];
- (void)setStringValue:(CPString)aString - (void)setStringValue:(CPString)aString
{ {
// Cocoa raises an invalid parameter assertion and returns if you pass nil. // Cocoa raises an invalid parameter assertion and returns if you pass nil.
if (aString === nil || aString === undefined) if (aString == nil)
{ {
CPLog.warn("nil or undefined sent to CPControl -setStringValue"); CPLog.warn("nil or undefined sent to CPControl -setStringValue");
return; return;
@@ -803,7 +809,7 @@ var CPControlBlackColor = [CPColor blackColor];
CPBottomVerticalTextAlignment CPBottomVerticalTextAlignment
</pre> </pre>
*/ */
- (void)setVerticalAlignment:(CPTextVerticalAlignment)alignment - (void)setVerticalAlignment:(CPVerticalTextAlignment)alignment
{ {
[self setValue:alignment forThemeAttribute:@"vertical-alignment"]; [self setValue:alignment forThemeAttribute:@"vertical-alignment"];
} }
@@ -848,7 +854,12 @@ var CPControlBlackColor = [CPColor blackColor];
*/ */
- (void)setTextColor:(CPColor)aColor - (void)setTextColor:(CPColor)aColor
{ {
[self setValue:aColor forThemeAttribute:@"text-color"]; [self setValue:aColor forThemeAttribute:@"text-color" inState:[self themeState]];
}
- (void)setTextColor:(CPColor)aColor inThemeStates:(CPArray)themeStates
{
[self setValue:aColor forThemeAttribute:@"text-color" inStates:themeStates];
} }
/*! /*!
@@ -906,7 +917,7 @@ var CPControlBlackColor = [CPColor blackColor];
*/ */
- (CPFont)font - (CPFont)font
{ {
return [self valueForThemeAttribute:@"font"]; return [self currentValueForThemeAttribute:@"font"] || [CPFont systemFontForControlSize:_controlSize];
} }
/*! /*!
@@ -1116,6 +1127,7 @@ var CPControlActionKey = @"CPControlActionKey",
[self setControlSize:[aCoder decodeIntForKey:CPControlControlSizeKey]]; [self setControlSize:[aCoder decodeIntForKey:CPControlControlSizeKey]];
[self setBaseWritingDirection:[aCoder decodeIntForKey:CPControlBaseWrittingDirectionKey]]; [self setBaseWritingDirection:[aCoder decodeIntForKey:CPControlBaseWrittingDirectionKey]];
[self updateTrackingAreas];
} }
return self; return self;
@@ -1135,18 +1147,18 @@ var CPControlActionKey = @"CPControlActionKey",
var objectValue = [self objectValue]; var objectValue = [self objectValue];
if (objectValue !== nil) if (objectValue != nil)
[aCoder encodeObject:objectValue forKey:CPControlValueKey]; [aCoder encodeObject:objectValue forKey:CPControlValueKey];
if (_target !== nil) if (_target != nil)
[aCoder encodeConditionalObject:_target forKey:CPControlTargetKey]; [aCoder encodeConditionalObject:_target forKey:CPControlTargetKey];
if (_action !== nil) if (_action != nil)
[aCoder encodeObject:_action forKey:CPControlActionKey]; [aCoder encodeObject:_action forKey:CPControlActionKey];
[aCoder encodeInt:_sendActionOn forKey:CPControlSendActionOnKey]; [aCoder encodeInt:_sendActionOn forKey:CPControlSendActionOnKey];
if (_formatter !== nil) if (_formatter != nil)
[aCoder encodeObject:_formatter forKey:CPControlFormatterKey]; [aCoder encodeObject:_formatter forKey:CPControlFormatterKey];
[aCoder encodeInt:_controlSize forKey:CPControlControlSizeKey]; [aCoder encodeInt:_controlSize forKey:CPControlControlSizeKey];
+2
View File
@@ -37,6 +37,8 @@
CPString _expires; CPString _expires;
} }
@global document
/*! /*!
Initializes a cookie with a given name \c aName. Initializes a cookie with a given name \c aName.
@param the name for the cookie @param the name for the cookie
Executable → Regular
+112 -44
View File
@@ -23,6 +23,7 @@ Cursor support by browser:
@import <Foundation/CPObject.j> @import <Foundation/CPObject.j>
@import "CPImage.j" @import "CPImage.j"
@import "CPCompatibility.j"
@global CPApp @global CPApp
@@ -31,6 +32,12 @@ var currentCursor = nil,
cursors = {}, cursors = {},
ieCursorMap = {}; ieCursorMap = {};
@typedef CPCursorPlatform
CPCursorPlatformNone = 0;
CPCursorPlatformMac = 1;
CPCursorPlatformWindows = 2;
CPCursorPlatformBoth = 3;
@implementation CPCursor : CPObject @implementation CPCursor : CPObject
{ {
CPString _cssString @accessors(readonly); CPString _cssString @accessors(readonly);
@@ -157,7 +164,7 @@ var currentCursor = nil,
} }
// Internal method that is used to return the system cursors. Caches the system cursors for performance. // Internal method that is used to return the system cursors. Caches the system cursors for performance.
+ (CPCursor)_systemCursorWithName:(CPString)cursorName cssString:(CPString)aString hasImage:(BOOL)doesHaveImage + (CPCursor)_nativeSystemCursorWithName:(CPString)cursorName cssString:(CPString)aString
{ {
var cursor = cursors[cursorName]; var cursor = cursors[cursorName];
@@ -165,155 +172,216 @@ var currentCursor = nil,
{ {
var cssString; var cssString;
if (doesHaveImage) // IE <= 8 does not support some cursors, map them to supported cursors
{ var ieLessThan9 = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPFeatureIsCompatible(CPHTMLCanvasFeature);
var themeResourcePath = [[[CPApp themeBlend] bundle] resourcePath],
extension = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) ? @"cur" : @"png";
cssString = [CPString stringWithFormat:@"url(%@cursors/%@.%@), %@", themeResourcePath, cursorName, extension, aString];
}
if (ieLessThan9)
cssString = ieCursorMap[aString] || aString;
else else
{ cssString = aString;
// IE <= 8 does not support some cursors, map them to supported cursors
var ieLessThan9 = CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPFeatureIsCompatible(CPHTMLCanvasFeature);
if (ieLessThan9) cursors[cursorName] = cursor = [[CPCursor alloc] initWithCSSString:cssString];
cssString = ieCursorMap[aString] || aString;
else
cssString = aString;
}
cursor = [[CPCursor alloc] initWithCSSString:cssString];
cursors[cursorName] = cursor;
} }
return cursor; return cursor;
} }
+ (CPCursor)_imageCursorWithName:(CPString)cursorName cssString:(CPString)aString
{
var cursor = cursors[cursorName];
if (typeof cursor === "undefined")
{
var themeResourcePath = [[[CPApp themeBlend] bundle] resourcePath],
extension = CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) ? @"cur" : @"png",
cssString = [CPString stringWithFormat:@"url(%@cursors/%@.%@), %@", themeResourcePath, cursorName, extension, aString];
cursors[cursorName] = cursor = [[CPCursor alloc] initWithCSSString:cssString];
}
return cursor;
}
+ (CPCursor)_tryUsingNativeSystemCursorWithName:(CPString)cursorName cssString:(CPString)cssName onPlatform:(CPCursorPlatform)shouldUseNativeCursorOn fallingBackWithImageAndCSSPointer:(CPString)aString
{
var useNativeSystemCursor = (((shouldUseNativeCursorOn == CPCursorPlatformBoth) ||
((shouldUseNativeCursorOn == CPCursorPlatformMac) && CPBrowserIsOperatingSystem(CPMacOperatingSystem)) ||
((shouldUseNativeCursorOn == CPCursorPlatformWindows) && CPBrowserIsOperatingSystem(CPWindowsOperatingSystem)))
&& [CPCursor _nativeCursorExists:cssName]);
if (useNativeSystemCursor)
return [CPCursor _nativeSystemCursorWithName:cursorName cssString:cssName];
else
return [CPCursor _imageCursorWithName:cursorName cssString:aString];
}
+ (BOOL)_nativeCursorExists:(CPString)cursorCSSName
{
#if PLATFORM(DOM)
// FIXME: Trick until FF/Win & Chrome/Win correctly implement context-menu cursor
// They will answer that they implement it but they actually don't
if ([cursorCSSName isEqualToString:@"context-menu"] && CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) && !CPBrowserIsEngine(CPInternetExplorerBrowserEngine) && !CPBrowserIsEngine(CPEdgeBrowserEngine))
return NO;
// Normal usage : try to set the cursor and check if resulting cursor is the one we tried to set.
// If yes, then the browser implements the cursor. If no (and usually we get "default"), then it doesn't.
var platformWindows = [[CPPlatformWindow visiblePlatformWindows] allObjects],
count = [platformWindows count];
if (count > 0)
{
var currentPlatformCursor = platformWindows[0]._DOMBodyElement.style.cursor;
platformWindows[0]._DOMBodyElement.style.cursor = cursorCSSName;
var doesExist = (platformWindows[0]._DOMBodyElement.style.cursor == cursorCSSName);
platformWindows[0]._DOMBodyElement.style.cursor = currentPlatformCursor;
return doesExist;
}
#endif
return NO;
}
+ (CPCursor)arrowCursor + (CPCursor)arrowCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"default" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"default"];
} }
+ (CPCursor)crosshairCursor + (CPCursor)crosshairCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"crosshair" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"crosshair"];
} }
+ (CPCursor)IBeamCursor + (CPCursor)IBeamCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"text" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"text"];
} }
+ (CPCursor)pointingHandCursor + (CPCursor)pointingHandCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"pointer" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"pointer"];
} }
+ (CPCursor)resizeNorthwestCursor + (CPCursor)resizeNorthwestCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nw-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nw-resize"];
} }
+ (CPCursor)resizeNorthwestSoutheastCursor + (CPCursor)resizeNorthwestSoutheastCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nwse-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nwse-resize"];
} }
+ (CPCursor)resizeNortheastCursor + (CPCursor)resizeNortheastCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ne-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ne-resize"];
} }
+ (CPCursor)resizeNortheastSouthwestCursor + (CPCursor)resizeNortheastSouthwestCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nesw-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"nesw-resize"];
} }
+ (CPCursor)resizeSouthwestCursor + (CPCursor)resizeSouthwestCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"sw-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"sw-resize"];
} }
+ (CPCursor)resizeSoutheastCursor + (CPCursor)resizeSoutheastCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"se-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"se-resize"];
} }
+ (CPCursor)resizeDownCursor + (CPCursor)resizeDownCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"s-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"s-resize"];
} }
+ (CPCursor)resizeUpCursor + (CPCursor)resizeUpCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"n-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"n-resize"];
} }
+ (CPCursor)resizeLeftCursor + (CPCursor)resizeLeftCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"w-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"w-resize"];
} }
+ (CPCursor)resizeRightCursor + (CPCursor)resizeRightCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"e-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"e-resize"];
} }
+ (CPCursor)resizeLeftRightCursor + (CPCursor)resizeLeftRightCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"col-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"col-resize"];
} }
+ (CPCursor)resizeEastWestCursor + (CPCursor)resizeEastWestCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ew-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ew-resize"];
} }
+ (CPCursor)resizeUpDownCursor + (CPCursor)resizeUpDownCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"row-resize"];
} }
+ (CPCursor)resizeNorthSouthCursor + (CPCursor)resizeNorthSouthCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ns-resize" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"ns-resize"];
} }
+ (CPCursor)operationNotAllowedCursor + (CPCursor)operationNotAllowedCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"not-allowed" hasImage:NO]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"not-allowed"];
} }
+ (CPCursor)dragCopyCursor + (CPCursor)dragCopyCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"copy" hasImage:YES]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"copy"];
} }
+ (CPCursor)dragLinkCursor + (CPCursor)dragLinkCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"alias" hasImage:YES]; return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"alias"];
} }
+ (CPCursor)contextualMenuCursor + (CPCursor)contextualMenuCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"context-menu" hasImage:YES]; return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
cssString:@"context-menu"
onPlatform:CPCursorPlatformBoth
fallingBackWithImageAndCSSPointer:@"default"];
} }
+ (CPCursor)openHandCursor + (CPCursor)openHandCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"move" hasImage:YES]; return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
cssString:@"grab"
onPlatform:CPCursorPlatformMac
fallingBackWithImageAndCSSPointer:@"default"];
} }
+ (CPCursor)closedHandCursor + (CPCursor)closedHandCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"-moz-grabbing" hasImage:YES]; return [CPCursor _tryUsingNativeSystemCursorWithName:CPStringFromSelector(_cmd)
cssString:@"grabbing"
onPlatform:CPCursorPlatformMac
fallingBackWithImageAndCSSPointer:@"default"];
} }
+ (CPCursor)disappearingItemCursor + (CPCursor)disappearingItemCursor
{ {
return [CPCursor _systemCursorWithName:CPStringFromSelector(_cmd) cssString:@"auto" hasImage:YES]; return [CPCursor _imageCursorWithName:CPStringFromSelector(_cmd) cssString:@"default"];
}
+ (CPCursor)IBeamCursorForVerticalLayout
{
return [CPCursor _nativeSystemCursorWithName:CPStringFromSelector(_cmd) cssString:@"vertical-text"];
} }
@end @end
+184 -85
View File
@@ -1,7 +1,7 @@
/* CPDatePicker.j /* CPDatePicker.j
* AppKit * AppKit
* *
* Created by Alexandre Wilhelm * Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com> * Copyright 2012 <alexandre.wilhelmfr@gmail.com>
* *
* This library is free software; you can redistribute it and/or * This library is free software; you can redistribute it and/or
@@ -59,26 +59,26 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
@implementation CPDatePicker : CPControl @implementation CPDatePicker : CPControl
{ {
BOOL _isBordered @accessors(getter=isBordered, setter=setBordered:); BOOL _isBordered @accessors(getter=isBordered, setter=setBordered:);
BOOL _isBezeled @accessors(getter=isBezeled, setter=setBezeled:); BOOL _isBezeled @accessors(getter=isBezeled, setter=setBezeled:);
BOOL _drawsBackground @accessors(property=drawsBackground); BOOL _drawsBackground @accessors(property=drawsBackground);
CPDate _dateValue @accessors(property=dateValue); CPDate _dateValue @accessors(property=dateValue);
CPDate _minDate @accessors(property=minDate); CPDate _minDate @accessors(property=minDate);
CPDate _maxDate @accessors(property=maxDate); CPDate _maxDate @accessors(property=maxDate);
CPFont _textFont @accessors(property=textFont); CPFont _textFont @accessors(property=textFont);
CPLocale _locale @accessors(property=locale); CPLocale _locale @accessors(property=locale);
//CPCalendar _calendar @accessors(property=calendar); //CPCalendar _calendar @accessors(property=calendar);
CPTimeZone _timeZone @accessors(property=timeZone); CPTimeZone _timeZone @accessors(property=timeZone);
id _delegate @accessors(property=delegate); id _delegate @accessors(property=delegate);
CPInteger _datePickerElements @accessors(property=datePickerElements); CPInteger _datePickerElements @accessors(property=datePickerElements);
CPInteger _datePickerMode @accessors(property=datePickerMode); CPInteger _datePickerMode @accessors(property=datePickerMode);
CPInteger _datePickerStyle @accessors(property=datePickerStyle); CPInteger _datePickerStyle @accessors(property=datePickerStyle);
CPInteger _timeInterval @accessors(property=timeInterval); CPInteger _timeInterval @accessors(property=timeInterval);
BOOL _invokedByUserEvent; BOOL _invokedByUserEvent;
_CPDatePickerTextField _datePickerTextfield;
_CPDatePickerCalendar _datePickerCalendar;
unsigned _implementedCDatePickerDelegateMethods; unsigned _implementedCDatePickerDelegateMethods;
BOOL _isTextual;
id _datePickerComponent;
} }
@@ -144,6 +144,32 @@ CPEraDatePickerElementFlag = 0x0100;
@"hour-hand-size": CGSizeMakeZero(), @"hour-hand-size": CGSizeMakeZero(),
@"middle-hand-size": CGSizeMakeZero(), @"middle-hand-size": CGSizeMakeZero(),
@"minute-hand-size": CGSizeMakeZero(), @"minute-hand-size": CGSizeMakeZero(),
@"previous-button-size": CGSizeMakeZero(),
@"current-button-size": CGSizeMakeZero(),
@"next-button-size": CGSizeMakeZero(),
@"title-inset": [CPNull null],
@"day-label-inset": [CPNull null],
@"tile-content-inset": CGInsetMakeZero(),
@"tile-margin": [CPNull null],
@"tile-inset": CGInsetMakeZero(),
@"separator-color": [CPNull null],
@"separator-margin-width": 0,
@"separator-height": 0,
@"bezel-color-calendar-left": [CPNull null],
@"bezel-color-calendar-middle": [CPNull null],
@"bezel-color-calendar-right": [CPNull null],
@"tile-vertical-alignment": CPCenterVerticalTextAlignment,
@"tile-text-alignment": CPCenterTextAlignment,
@"hour-ampm-margin": 2,
@"time-separator-content-inset": CGInsetMakeZero(),
@"clock-second-hand-over": NO,
@"clock-draws-hours": NO,
@"clock-hours-font": [CPNull null],
@"clock-hours-text-color": [CPColor clearColor],
@"clock-hours-radius": 0,
@"calendar-clock-margin": 10,
@"clock-only-nib2cib-adjustment-frame": CPRectMakeZero(),
@"uses-focus-ring": NO
}; };
} }
@@ -206,19 +232,44 @@ CPEraDatePickerElementFlag = 0x0100;
if (!_locale) if (!_locale)
_locale = [CPLocale currentLocale]; _locale = [CPLocale currentLocale];
_datePickerTextfield = [[_CPDatePickerTextField alloc] initWithFrame:[self bounds] withDatePicker:self]; _datePickerComponent = nil;
[_datePickerTextfield setDateValue:_dateValue];
_datePickerCalendar = [[_CPDatePickerCalendar alloc] initWithFrame:[self bounds] withDatePicker:self]; [self _createComponents];
[_datePickerCalendar setDateValue:_dateValue];
// We might have been unarchived in a disabled state.
[_datePickerTextfield setEnabled:[self isEnabled]];
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
[self setNeedsLayout]; [self setNeedsLayout];
} }
- (void)_createComponents
{
_isTextual = (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle) || (_datePickerStyle == CPTextFieldDatePickerStyle);
if (_datePickerComponent)
{
[_datePickerComponent removeFromSuperview];
_datePickerComponent = nil;
}
_datePickerComponent = [[(_isTextual ? _CPDatePickerTextField : _CPDatePickerCalendar) alloc] initWithFrame:[self bounds] withDatePicker:self];
[_datePickerComponent setDateValue:_dateValue];
[_datePickerComponent setControlSize:[self controlSize]];
[_datePickerComponent setDatePickerElements:_datePickerElements];
// FIXME: Don't know why but next line will cause theme compilation to fail...
// Workaround: added "if PLATFORM(DOM)"
#if PLATFORM(DOM)
[_datePickerComponent setEnabled:[self isEnabled]];
#endif
if (_isTextual)
// We need to transmit text color to the text field version (Cocoa doesn't permit adapting the calendar view text color)
[_datePickerComponent setTextColor:[self textColor]];
[self addSubview:_datePickerComponent];
}
#pragma mark - #pragma mark -
#pragma mark Control Size #pragma mark Control Size
@@ -227,7 +278,9 @@ CPEraDatePickerElementFlag = 0x0100;
{ {
[super setControlSize:aControlSize]; [super setControlSize:aControlSize];
if ([self datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [self datePickerStyle] == CPTextFieldDatePickerStyle) [_datePickerComponent setControlSize:aControlSize];
if (_isTextual)
[self _sizeToControlSize]; [self _sizeToControlSize];
} }
@@ -256,31 +309,8 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)layoutSubviews - (void)layoutSubviews
{ {
[super layoutSubviews]; [_datePickerComponent setNeedsLayout];
[_datePickerComponent setNeedsDisplay:YES];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle)
{
if (![_datePickerTextfield superview])
[self addSubview:_datePickerTextfield];
if ([_datePickerCalendar superview])
[_datePickerCalendar removeFromSuperview];
[_datePickerTextfield setControlSize:[self controlSize]];
[_datePickerTextfield setNeedsLayout];
[_datePickerTextfield setNeedsDisplay:YES];
}
else
{
if (![_datePickerCalendar superview])
[self addSubview:_datePickerCalendar];
if ([_datePickerTextfield superview])
[_datePickerTextfield removeFromSuperview];
[_datePickerCalendar setNeedsLayout];
[_datePickerCalendar setNeedsDisplay:YES];
}
} }
#pragma mark - #pragma mark -
@@ -341,10 +371,7 @@ CPEraDatePickerElementFlag = 0x0100;
if ([aDateValue isEqualToDate:_dateValue] && aTimeInterval == _timeInterval) if ([aDateValue isEqualToDate:_dateValue] && aTimeInterval == _timeInterval)
{ {
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) [_datePickerComponent setDateValue:_dateValue];
[_datePickerTextfield setDateValue:_dateValue];
else
[_datePickerCalendar setDateValue:_dateValue];
return; return;
} }
@@ -372,10 +399,7 @@ CPEraDatePickerElementFlag = 0x0100;
if (_invokedByUserEvent) if (_invokedByUserEvent)
[self sendAction:[self action] to:[self target]]; [self sendAction:[self action] to:[self target]];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) [_datePickerComponent setDateValue:_dateValue];
[_datePickerTextfield setDateValue:_dateValue];
else
[_datePickerCalendar setDateValue:_dateValue];
} }
/*! Set the minDate of the datePicker /*! Set the minDate of the datePicker
@@ -383,6 +407,9 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setMinDate:(CPDate)aMinDate - (void)setMinDate:(CPDate)aMinDate
{ {
if (_minDate === aMinDate)
return;
[self willChangeValueForKey:@"minDate"]; [self willChangeValueForKey:@"minDate"];
_minDate = aMinDate; _minDate = aMinDate;
[self didChangeValueForKey:@"minDate"]; [self didChangeValueForKey:@"minDate"];
@@ -395,6 +422,9 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setMaxDate:(CPDate)aMaxDate - (void)setMaxDate:(CPDate)aMaxDate
{ {
if (_maxDate === aMaxDate)
return;
[self willChangeValueForKey:@"maxDate"]; [self willChangeValueForKey:@"maxDate"];
_maxDate = aMaxDate; _maxDate = aMaxDate;
[self didChangeValueForKey:@"maxDate"]; [self didChangeValueForKey:@"maxDate"];
@@ -407,10 +437,32 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setDatePickerStyle:(CPInteger)aDatePickerStyle - (void)setDatePickerStyle:(CPInteger)aDatePickerStyle
{ {
if (_datePickerStyle === aDatePickerStyle)
return;
_datePickerStyle = aDatePickerStyle; _datePickerStyle = aDatePickerStyle;
// This is needed in order to specify different theme attributes values for textual / graphical date picker
if (_datePickerStyle === CPClockAndCalendarDatePickerStyle)
[self setThemeState:CPThemeStateAlternateState];
else
[self unsetThemeState:CPThemeStateAlternateState];
// This is needed in order to specify different theme attributes values for with / without stepper textual date picker
if (_datePickerStyle === CPTextFieldAndStepperDatePickerStyle)
[self setThemeState:CPThemeStateComposedControl];
else
[self unsetThemeState:CPThemeStateComposedControl];
[self setControlSize:[self controlSize]]; [self setControlSize:[self controlSize]];
if (_datePickerComponent)
{
// We already have a component so we need to update it
[_datePickerComponent resignFirstResponder];
[self _createComponents];
}
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
[self setNeedsLayout]; [self setNeedsLayout];
} }
@@ -420,8 +472,14 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setDatePickerElements:(CPInteger)aDatePickerElements - (void)setDatePickerElements:(CPInteger)aDatePickerElements
{ {
if (_datePickerElements === aDatePickerElements)
return;
_datePickerElements = aDatePickerElements; _datePickerElements = aDatePickerElements;
// Notify the component of the new value
[_datePickerComponent setDatePickerElements:_datePickerElements];
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
[self setNeedsLayout]; [self setNeedsLayout];
} }
@@ -431,6 +489,9 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setDatePickerMode:(CPInteger)aDatePickerMode - (void)setDatePickerMode:(CPInteger)aDatePickerMode
{ {
if (_datePickerMode === aDatePickerMode)
return;
_datePickerMode = aDatePickerMode; _datePickerMode = aDatePickerMode;
if (_datePickerMode == CPSingleDateMode) if (_datePickerMode == CPSingleDateMode)
@@ -456,6 +517,9 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setLocale:(CPLocale)aLocale - (void)setLocale:(CPLocale)aLocale
{ {
if (_locale === aLocale)
return;
_locale = aLocale; _locale = aLocale;
if (_formatter) if (_formatter)
@@ -466,7 +530,9 @@ CPEraDatePickerElementFlag = 0x0100;
} }
// This will update the textFields (usefull when changing with a date with pm and am) // This will update the textFields (usefull when changing with a date with pm and am)
[_datePickerTextfield setDateValue:_dateValue]; if (_isTextual)
[_datePickerComponent setDateValue:_dateValue];
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
[self setNeedsLayout]; [self setNeedsLayout];
} }
@@ -477,6 +543,9 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setBezeled:(BOOL)shouldBeBezeled - (void)setBezeled:(BOOL)shouldBeBezeled
{ {
if (_isBezeled === shouldBeBezeled)
return;
_isBezeled = shouldBeBezeled; _isBezeled = shouldBeBezeled;
if (shouldBeBezeled) if (shouldBeBezeled)
@@ -491,6 +560,9 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setBordered:(BOOL)shouldBeBordered - (void)setBordered:(BOOL)shouldBeBordered
{ {
if (_isBordered === shouldBeBordered)
return;
_isBordered = shouldBeBordered; _isBordered = shouldBeBordered;
if (shouldBeBordered) if (shouldBeBordered)
@@ -506,6 +578,22 @@ CPEraDatePickerElementFlag = 0x0100;
- (void)setTextFont:(CPFont)aFont - (void)setTextFont:(CPFont)aFont
{ {
[self setFont:aFont]; [self setFont:aFont];
if (_isTextual)
[_datePickerComponent setTextFont:aFont];
}
/*!
Sets the color of the control.
@param aColor
*/
- (void)setTextColor:(CPColor)aColor
{
[super setTextColor:aColor];
if (_isTextual)
[_datePickerComponent setTextColor:aColor];
// REM: in Cocoa, setTextColor has no effect on calendar view
} }
/*! Sets the enabled status of the control. Controls that are not enabled can not be used by the user and obtain the CPThemeStateDisabled theme state. /*! Sets the enabled status of the control. Controls that are not enabled can not be used by the user and obtain the CPThemeStateDisabled theme state.
@@ -515,8 +603,10 @@ CPEraDatePickerElementFlag = 0x0100;
{ {
[super setEnabled:aBoolean]; [super setEnabled:aBoolean];
[_datePickerTextfield setEnabled:aBoolean]; [_datePickerComponent setEnabled:aBoolean];
[_datePickerCalendar setEnabled:aBoolean];
if (!aBoolean)
[self resignFirstResponder];
} }
/*! Set the background color of the datePicker /*! Set the background color of the datePicker
@@ -533,6 +623,9 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setDrawsBackground:(BOOL)aBoolean - (void)setDrawsBackground:(BOOL)aBoolean
{ {
if (_drawsBackground === aBoolean)
return;
[self willChangeValueForKey:@"drawsBackground"]; [self willChangeValueForKey:@"drawsBackground"];
_drawsBackground = aBoolean; _drawsBackground = aBoolean;
[self didChangeValueForKey:@"drawsBackground"]; [self didChangeValueForKey:@"drawsBackground"];
@@ -545,16 +638,16 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)setTimeZone:(CPTimeZone)aTimeZone - (void)setTimeZone:(CPTimeZone)aTimeZone
{ {
if (_timeZone === aTimeZone)
return;
[self willChangeValueForKey:@"timeZone"]; [self willChangeValueForKey:@"timeZone"];
_timeZone = aTimeZone; _timeZone = aTimeZone;
[self didChangeValueForKey:@"timeZone"]; [self didChangeValueForKey:@"timeZone"];
[self setNeedsLayout]; [self setNeedsLayout];
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) [_datePickerComponent setDateValue:_dateValue];
[_datePickerTextfield setDateValue:_dateValue];
else
[_datePickerCalendar setDateValue:_dateValue];
} }
@@ -565,12 +658,12 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (BOOL)becomeFirstResponder - (BOOL)becomeFirstResponder
{ {
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) if (_isTextual)
{ {
if (![super becomeFirstResponder]) if (![super becomeFirstResponder])
return NO; return NO;
[_datePickerTextfield _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]]; [_datePickerComponent _selectTextFieldWithFlags:[[CPApp currentEvent] modifierFlags]];
return YES; return YES;
} }
@@ -589,8 +682,8 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (BOOL)resignFirstResponder - (BOOL)resignFirstResponder
{ {
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) if (_isTextual)
[_datePickerTextfield resignFirstResponder]; [_datePickerComponent resignFirstResponder];
return YES; return YES;
} }
@@ -638,8 +731,8 @@ CPEraDatePickerElementFlag = 0x0100;
*/ */
- (void)keyDown:(CPEvent)anEvent - (void)keyDown:(CPEvent)anEvent
{ {
if (_datePickerStyle == CPTextFieldAndStepperDatePickerStyle || _datePickerStyle == CPTextFieldDatePickerStyle) if (_isTextual)
[_datePickerTextfield keyDown:anEvent]; [_datePickerComponent keyDown:anEvent];
} }
@end @end
@@ -665,20 +758,22 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
if (self) if (self)
{ {
_textFont = [aCoder decodeObjectForKey:CPTextFontKey];
_minDate = [aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast];
_maxDate = [aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture];
_timeInterval = [aCoder decodeDoubleForKey:CPIntervalKey];
_datePickerMode = [aCoder decodeIntForKey:CPDatePickerModeKey];
_datePickerElements = [aCoder decodeIntForKey:CPDatePickerElementsKey];
[self setDatePickerStyle:[aCoder decodeIntForKey:CPDatePickerStyleKey]];
_locale = [aCoder decodeObjectForKey:CPLocaleKey];
_dateValue = [aCoder decodeObjectForKey:CPDateValueKey];
_backgroundColor = [aCoder decodeObjectForKey:CPBackgroundColorKey]; _backgroundColor = [aCoder decodeObjectForKey:CPBackgroundColorKey];
_drawsBackground = [aCoder decodeBoolForKey:CPDrawsBackgroundKey]; [self setBordered:[aCoder decodeBoolForKey:CPBorderedKey]];
_isBordered = [aCoder decodeBoolForKey:CPBorderedKey]; [self setDrawsBackground:[aCoder decodeBoolForKey:CPDrawsBackgroundKey]];
[self setDatePickerElements:[aCoder decodeIntForKey:CPDatePickerElementsKey]];
[self setDatePickerMode:[aCoder decodeIntForKey:CPDatePickerModeKey]];
[self setDatePickerStyle:[aCoder decodeIntForKey:CPDatePickerStyleKey]];
[self setMinDate:[aCoder decodeObjectForKey:CPMinDateKey] || [CPDate distantPast]];
[self setMaxDate:[aCoder decodeObjectForKey:CPMaxDateKey] || [CPDate distantFuture]];
[self setLocale:[aCoder decodeObjectForKey:CPLocaleKey]];
[self _init]; [self _init];
[self setTextFont:[aCoder decodeObjectForKey:CPTextFontKey]];
[self setTimeInterval:[aCoder decodeDoubleForKey:CPIntervalKey]];
[self setDateValue:[aCoder decodeObjectForKey:CPDateValueKey]];
} }
return self return self
@@ -686,7 +781,10 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
- (void)encodeWithCoder:(CPCoder)aCoder - (void)encodeWithCoder:(CPCoder)aCoder
{ {
// FIXME Do we need to encode _datePickerTextfield and _datePickerCalendar? As subviews they'll be encoded, but when we decode we recreate them anyhow. // Before encoding, we remove all subviews as we'll recreate them at loading
while ([[self subviews] count] > 0)
[[[self subviews] lastObject] removeFromSuperview];
[super encodeWithCoder:aCoder]; [super encodeWithCoder:aCoder];
[aCoder encodeDouble:_timeInterval forKey:CPIntervalKey]; [aCoder encodeDouble:_timeInterval forKey:CPIntervalKey];
@@ -695,7 +793,7 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
[aCoder encodeInt:_datePickerElements forKey:CPDatePickerElementsKey]; [aCoder encodeInt:_datePickerElements forKey:CPDatePickerElementsKey];
[aCoder encodeObject:_minDate forKey:CPMinDateKey]; [aCoder encodeObject:_minDate forKey:CPMinDateKey];
[aCoder encodeObject:_maxDate forKey:CPMaxDateKey]; [aCoder encodeObject:_maxDate forKey:CPMaxDateKey];
[aCoder encodeObject:_dateValue forKey:CPDateValueKey];; [aCoder encodeObject:_dateValue forKey:CPDateValueKey];
[aCoder encodeObject:_textFont forKey:CPTextFontKey]; [aCoder encodeObject:_textFont forKey:CPTextFontKey];
[aCoder encodeObject:_locale forKey:CPLocaleKey]; [aCoder encodeObject:_locale forKey:CPLocaleKey];
[aCoder encodeObject:_backgroundColor forKey:CPBackgroundColorKey]; [aCoder encodeObject:_backgroundColor forKey:CPBackgroundColorKey];
@@ -706,6 +804,7 @@ var CPDatePickerModeKey = @"CPDatePickerModeKey",
@end @end
// FIXME: add support for CPEditorRegistrationProtocol as implemented for CPTextField
@implementation _CPDatePickerValueBinder : CPBinder @implementation _CPDatePickerValueBinder : CPBinder
{ {
} }
+98
View File
@@ -0,0 +1,98 @@
/* _CPDatePickerBox.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/Foundation.j>
@import "CPView.j"
@class CPDatePicker
@implementation _CPDatePickerBox : CPView
{
CPDatePicker _datePicker @accessors(property=datePicker);
}
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if ([_datePicker isCSSBased])
return;
if ([_datePicker isBordered])
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"] / 2;
CGContextBeginPath(context);
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]);
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
CGContextMoveToPoint(context, borderWidth, borderWidth);
CGContextAddLineToPoint(context, aRect.size.width - borderWidth, borderWidth);
CGContextAddLineToPoint(context, aRect.size.width - borderWidth, aRect.size.height - borderWidth);
CGContextAddLineToPoint(context, borderWidth, aRect.size.height - borderWidth);
CGContextAddLineToPoint(context, borderWidth,borderWidth);
CGContextStrokePath(context);
CGContextClosePath(context);
}
}
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
{
if (aName === "bezel-view")
return [self bounds];
return [super rectForEphemeralSubviewNamed:aName];
}
- (CPView)createEphemeralSubviewNamed:(CPString)aName
{
if (aName === "bezel-view")
{
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[view setHitTests:NO];
return view;
}
return [super createEphemeralSubviewNamed:aName];
}
- (void)layoutSubviews
{
if ([_datePicker isCSSBased])
{
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color"]];
}
if ([_datePicker drawsBackground])
[self setBackgroundColor:[_datePicker backgroundColor]];
else
[self setBackgroundColor:[CPColor clearColor]];
}
@end
File diff suppressed because it is too large Load Diff
+408 -110
View File
@@ -20,13 +20,18 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
@import <Foundation/CPKeyedArchiver.j>
@import <Foundation/CPKeyedUnarchiver.j>
@import "CPView.j" @import "CPView.j"
@import "CPTextField.j" @import "CPTextField.j"
@import "CPImageView.j" @import "CPImage.j"
@import "CALayer.j" @import "CALayer.j"
@class _CPCibCustomResource @class _CPCibCustomResource
@class CPDatePicker @class CPDatePicker
@class HandImageLayer
@class HoursLayer
@class HandLayer
@global CPHourMinuteSecondDatePickerElementFlag @global CPHourMinuteSecondDatePickerElementFlag
@global CPTextFieldAndStepperDatePickerStyle @global CPTextFieldAndStepperDatePickerStyle
@@ -34,17 +39,35 @@
var RADIANS = Math.PI / 180; var RADIANS = Math.PI / 180;
@typedef _CPDatePickerClockHand
_CPDatePickerClockHours = 1;
_CPDatePickerClockMinutes = 2;
_CPDatePickerClockSeconds = 3;
@implementation _CPDatePickerClock : CPView @implementation _CPDatePickerClock : CPControl
{ {
BOOL _isEnabled; BOOL _isEnabled;
CALayer _rootLayer; HoursLayer _rootLayer;
CALayer _hourHandLayer; HandLayer _hourHandLayer;
CALayer _minuteHandLayer; HandLayer _minuteHandLayer;
CALayer _secondHandLayer; HandLayer _secondHandLayer;
CALayer _middleHandLayer; CALayer _middleHandLayer;
CPDatePicker _datePicker; CPDatePicker _datePicker;
CPTextField _PMAMTextField; CPTextField _PMAMTextField;
CALayer _currentHandLayer;
_CPDatePickerClockHand _currentHand;
CPInteger _currentRepresentedValue;
float _currentValueShift;
CPInteger _numberOfUnits;
BOOL _trackingHand;
CPInteger _representedHours;
CPInteger _representedMinutes;
CPInteger _representedSeconds;
BOOL _representedHourIsPM;
CPInteger _datePickerElements @accessors(getter=datePickerElements);
} }
@@ -55,7 +78,9 @@ var RADIANS = Math.PI / 180;
{ {
if (self = [super initWithFrame:aFrame]) if (self = [super initWithFrame:aFrame])
{ {
_datePicker = aDatePicker; _datePicker = aDatePicker;
_datePickerElements = [_datePicker datePickerElements];
_trackingHand = NO;
_PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()]; _PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
@@ -73,7 +98,7 @@ var RADIANS = Math.PI / 180;
var middleHandSize = [_datePicker valueForThemeAttribute:@"middle-hand-size"], var middleHandSize = [_datePicker valueForThemeAttribute:@"middle-hand-size"],
minuteHandSize = [_datePicker valueForThemeAttribute:@"minute-hand-size"], minuteHandSize = [_datePicker valueForThemeAttribute:@"minute-hand-size"],
hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"], hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"],
secondHandSize = [_datePicker valueForThemeAttribute:@"second-hand-size"]; secondHandSize = [_datePicker valueForThemeAttribute:@"second-hand-size"];
// We use layer to make the rotation possible // We use layer to make the rotation possible
@@ -97,106 +122,132 @@ var RADIANS = Math.PI / 180;
[_middleHandLayer setAnchorPoint:CGPointMakeZero()]; [_middleHandLayer setAnchorPoint:CGPointMakeZero()];
[_middleHandLayer setPosition:CGPointMake(0.0, 0.0)]; [_middleHandLayer setPosition:CGPointMake(0.0, 0.0)];
_rootLayer = [CALayer layer]; _rootLayer = [[HoursLayer alloc] init];
[self setWantsLayer:YES]; [self setWantsLayer:YES];
[self setLayer:_rootLayer]; [self setLayer:_rootLayer];
[_hourHandLayer setNeedsDisplay]; [self _initHands];
[_middleHandLayer setNeedsDisplay];
[_secondHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_rootLayer addSublayer:_hourHandLayer]; [_rootLayer addSublayer:_hourHandLayer];
[_rootLayer addSublayer:_minuteHandLayer]; [_rootLayer addSublayer:_minuteHandLayer];
[_rootLayer addSublayer:_secondHandLayer];
[_rootLayer addSublayer:_middleHandLayer];
if ([_datePicker valueForThemeAttribute:@"clock-second-hand-over"])
{
[_rootLayer addSublayer:_middleHandLayer];
[_rootLayer addSublayer:_secondHandLayer];
}
else
{
[_rootLayer addSublayer:_secondHandLayer];
[_rootLayer addSublayer:_middleHandLayer];
}
[_rootLayer setDrawsHours:[_datePicker valueForThemeAttribute:@"clock-draws-hours"]];
[_rootLayer setNeedsDisplay]; [_rootLayer setNeedsDisplay];
} }
return self; return self;
} }
- (void)_initHands
#pragma mark -
#pragma mark Layout methods
- (void)layoutSubviews
{ {
if ([_datePicker datePickerStyle] == CPTextFieldAndStepperDatePickerStyle || [_datePicker datePickerStyle] == CPTextFieldDatePickerStyle) var middleHandImage = [_datePicker currentValueForThemeAttribute:@"middle-hand-image"],
return; hourHandImage = [_datePicker currentValueForThemeAttribute:@"hour-hand-image"],
minuteHandImage = [_datePicker currentValueForThemeAttribute:@"minute-hand-image"],
secondHandImage = [_datePicker currentValueForThemeAttribute:@"second-hand-image"];
[super layoutSubviews]; // If hand images are true CPImage, we have to duplicate them to avoid
// the multiple delegates bug when multiple clocks are displayed
var bounds = [self bounds], if ([middleHandImage isKindOfClass:[CPImage class]])
dateValue = [[_datePicker dateValue] copy]; middleHandImage = [middleHandImage duplicate];
[dateValue _dateWithTimeZone:[_datePicker timeZone]]; if ([hourHandImage isKindOfClass:[CPImage class]])
hourHandImage = [hourHandImage duplicate];
[self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]]; if ([minuteHandImage isKindOfClass:[CPImage class]])
[_middleHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"middle-hand-image"]]; minuteHandImage = [minuteHandImage duplicate];
[_hourHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"hour-hand-image"]];
[_minuteHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"minute-hand-image"]];
[_secondHandLayer setImage:[_datePicker currentValueForThemeAttribute:@"second-hand-image"]];
if ([_datePicker _isAmericanFormat]) if ([secondHandImage isKindOfClass:[CPImage class]])
{ secondHandImage = [secondHandImage duplicate];
if (dateValue.getHours() > 11)
[_PMAMTextField setStringValue:@"PM"];
else
[_PMAMTextField setStringValue:@"AM"];
[_PMAMTextField sizeToFit]; [_middleHandLayer setImage:middleHandImage];
[_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)]; [_hourHandLayer setImage:hourHandImage];
[_PMAMTextField setHidden:NO]; [_minuteHandLayer setImage:minuteHandImage];
} [_secondHandLayer setImage:secondHandImage];
else
{
[_PMAMTextField setHidden:YES];
}
[_hourHandLayer setRotationRadians:[self _hourPositionRadianForDate:dateValue]]; [_hourHandLayer setNeedsDisplay];
[_minuteHandLayer setRotationRadians:[self _minutePositionRadianForDate:dateValue]]; [_middleHandLayer setNeedsDisplay];
[_secondHandLayer setRotationRadians:[self _secondPositionRadianForDate:dateValue]]; [_secondHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_PMAMTextField setEnabled:_isEnabled]; [_rootLayer setFont:[_datePicker currentValueForThemeAttribute:@"clock-hours-font"]];
[_hourHandLayer setEnabled:_isEnabled]; [_rootLayer setTextColor:[_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"]];
[_middleHandLayer setEnabled:_isEnabled]; [_rootLayer setRadius:[_datePicker currentValueForThemeAttribute:@"clock-hours-radius"]];
[_secondHandLayer setEnabled:_isEnabled];
[_minuteHandLayer setEnabled:_isEnabled];
// Check if we have to display the hand second
if (([_datePicker datePickerElements] & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)
[_secondHandLayer setHidden:NO];
else
[_secondHandLayer setHidden:YES];
[_rootLayer setNeedsDisplay]; [_rootLayer setNeedsDisplay];
} }
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
{
_datePickerElements = aDatePickerElements;
// Check if we have to display the hand second
// FIXME: Don't know why but next line will cause theme compilation to fail...
// Workaround: added "if PLATFORM(DOM)"
#if PLATFORM(DOM)
[_secondHandLayer setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
#endif
}
#pragma mark Layout methods
- (void)layoutSubviews
{
// While tracking a hand, we don't want the whole thing to be relayouted at each mouse movement
if (_trackingHand)
return;
[self setBackgroundColor:[_datePicker currentValueForThemeAttribute:@"bezel-color-clock"]];
var dateValue = [[_datePicker dateValue] copy];
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
_representedHours = dateValue.getHours();
_representedMinutes = dateValue.getMinutes();
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
// Hours are expressed in 24 hours format, we need 12 hours format
_representedHours -= (_representedHourIsPM ? 12 : 0);
[self _updateHands];
// FIXME: Workaround. Seems that CALayer doesn't redraw without an event
[CALayer runLoopUpdateLayers];
}
- (void)_updateHands
{
var bounds = [self bounds];
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
[_PMAMTextField sizeToFit];
[_PMAMTextField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_PMAMTextField frameSize].width / 2, bounds.size.height / 2 + 15)];
[_hourHandLayer setRotationRadians:(360 * (_representedHours + _representedMinutes / 60) / 12) * RADIANS];
[_minuteHandLayer setRotationRadians:(360 * (_representedMinutes + _representedSeconds / 60) / 60) * RADIANS];
[_secondHandLayer setRotationRadians:(360 * _representedSeconds / 60) * RADIANS];
[_hourHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_secondHandLayer setNeedsDisplay];
// [_middleHandLayer setNeedsDisplay];
}
#pragma mark -
#pragma mark Accessors #pragma mark Accessors
- (float)_hourPositionRadianForDate:(CPDate)aDate
{
var hours = aDate.getHours() + aDate.getMinutes() / 60;
return (360 * hours / 12) * RADIANS;
}
- (float)_secondPositionRadianForDate:(CPDate)aDate
{
return (360 * aDate.getSeconds() / 60) * RADIANS;
}
- (float)_minutePositionRadianForDate:(CPDate)aDate
{
var minutes = aDate.getMinutes() + aDate.getSeconds() / 60;
return (360 * minutes / 60) * RADIANS;
}
- (void)setEnabled:(BOOL)shouldEnable - (void)setEnabled:(BOOL)shouldEnable
{ {
shouldEnable = !!shouldEnable; shouldEnable = !!shouldEnable;
@@ -205,36 +256,191 @@ var RADIANS = Math.PI / 180;
return; return;
_isEnabled = shouldEnable; _isEnabled = shouldEnable;
[self setNeedsLayout];
// FIXME: This is a workaround for an apparent bug in CALayer. [self _initHands];
// Without pumping the event loop, the sublayers of _rootLayer [self setNeedsLayout];
// (the hands) are not redrawn until an event occurs. }
[[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
#pragma mark Mouse actions
- (void)mouseDown:(CPEvent)anEvent
{
if (!_isEnabled)
return;
var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil];
if ([_secondHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandLayer = _secondHandLayer;
_currentHand = _CPDatePickerClockSeconds;
_currentRepresentedValue = _representedSeconds;
_currentValueShift = 0;
_numberOfUnits = 60;
}
else if ([_minuteHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandLayer = _minuteHandLayer;
_currentHand = _CPDatePickerClockMinutes;
_currentRepresentedValue = _representedMinutes;
_currentValueShift = _representedSeconds / 60;
_numberOfUnits = 60;
}
else if ([_hourHandLayer handIsHitAtPoint:currentLocation])
{
_currentHandLayer = _hourHandLayer;
_currentHand = _CPDatePickerClockHours;
_currentRepresentedValue = _representedHours;
_currentValueShift = _representedMinutes / 60;
_numberOfUnits = 12;
}
else
{
_currentHandLayer = nil;
_currentHand = CPNotFound;
_currentRepresentedValue = CPNotFound;
_currentValueShift = CPNotFound;
_numberOfUnits = CPNotFound;
}
if (_currentHandLayer)
[self trackMouse:anEvent];
}
- (BOOL)tracksMouseOutsideOfFrame
{
return YES;
}
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
_trackingHand = YES;
return YES;
}
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
{
var dx = aPoint.x -_bounds.size.width / 2,
dy = _bounds.size.height / 2 - aPoint.y,
angle = (PI_2 - ATAN2(dy,dx) + PI2) % PI2,
value = ROUND(angle * _numberOfUnits / PI2 - _currentValueShift) % _numberOfUnits;
if (value !== _currentRepresentedValue)
{
var movedForward = (_currentRepresentedValue > _numberOfUnits * 3/4) && (value < _numberOfUnits / 4),
movedBackward = (_currentRepresentedValue < _numberOfUnits / 4) && (value > _numberOfUnits * 3/4),
dateValue = [[_datePicker dateValue] copy];
[dateValue _dateWithTimeZone:[_datePicker timeZone]];
switch (_currentHand)
{
case _CPDatePickerClockHours:
if (movedForward || movedBackward)
{
_representedHourIsPM = !_representedHourIsPM;
if (movedForward && !_representedHourIsPM)
// Day++
dateValue.setDate(dateValue.getDate() + 1);
else if (movedBackward && _representedHourIsPM)
// Day--
dateValue.setDate(dateValue.getDate() - 1);
}
dateValue.setHours(value + (_representedHourIsPM ? 12 : 0));
break;
case _CPDatePickerClockMinutes:
if (movedForward)
// Hours++
dateValue.setHours(dateValue.getHours() + 1);
else if (movedBackward)
// Hours--
dateValue.setHours(dateValue.getHours() - 1);
dateValue.setMinutes(value);
break;
case _CPDatePickerClockSeconds:
if (movedForward)
// Minutes++
dateValue.setMinutes(dateValue.getMinutes() + 1);
else if (movedBackward)
// Minutes--
dateValue.setMinutes(dateValue.getMinutes() - 1);
dateValue.setSeconds(value);
break;
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
// We have to adapt represented values
_representedHours = dateValue.getHours();
_representedMinutes = dateValue.getMinutes();
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
// Hours are expressed in 24 hours format, we need 12 hours format
_representedHours -= (_representedHourIsPM ? 12 : 0);
switch (_currentHand) {
case _CPDatePickerClockHours:
_currentRepresentedValue = _representedHours;
break;
case _CPDatePickerClockMinutes:
_currentRepresentedValue = _representedMinutes;
break;
case _CPDatePickerClockSeconds:
_currentRepresentedValue = _representedSeconds;
break;
}
[self _updateHands];
}
return YES;
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
{
_trackingHand = NO;
} }
@end @end
#pragma mark -
@implementation HandLayer : CALayer @implementation HandLayer : CALayer
{ {
BOOL _isEnabled @accessors(setter=setEnabled:, getter=isEnabled); CPImage _image;
HandImageLayer _imageLayer;
CPImage _image; float _rotationRadians;
CALayer _imageLayer;
float _rotationRadians;
} }
#pragma mark -
#pragma mark Init methods #pragma mark Init methods
- (id)initWithSize:(CGSize)aSize - (id)initWithSize:(CGSize)aSize
{ {
if (self = [super init]) if (self = [super init])
{ {
_isEnabled = YES; _imageLayer = [HandImageLayer layer];
_imageLayer = [CALayer layer];
_rotationRadians = 0; _rotationRadians = 0;
[_imageLayer setDelegate:self]; [_imageLayer setDelegate:self];
@@ -247,7 +453,6 @@ var RADIANS = Math.PI / 180;
} }
#pragma mark -
#pragma mark Setter Getter methods #pragma mark Setter Getter methods
/*! /*!
@@ -285,18 +490,6 @@ var RADIANS = Math.PI / 180;
1.0, 1.0)]; 1.0, 1.0)];
} }
- (void)setEnabled:(BOOL)shouldEnable
{
shouldEnable = !!shouldEnable;
if (_isEnabled === shouldEnable)
return;
_isEnabled = shouldEnable;
[self setNeedsDisplay];
[_imageLayer setNeedsDisplay];
}
- (void)imageDidLoad:(CPImage)anImage - (void)imageDidLoad:(CPImage)anImage
{ {
[_imageLayer setNeedsDisplay]; [_imageLayer setNeedsDisplay];
@@ -310,4 +503,109 @@ var RADIANS = Math.PI / 180;
CGContextDrawImage(aContext, [aLayer bounds], _image); CGContextDrawImage(aContext, [aLayer bounds], _image);
} }
- (BOOL)handIsHitAtPoint:(CGPoint)aPoint
{
return (!_isHidden && [_imageLayer hitTest:aPoint] === _imageLayer);
}
- (void)setNeedsDisplay
{
[super setNeedsDisplay];
[_imageLayer setNeedsDisplay];
}
@end
#pragma mark -
@implementation HandImageLayer : CALayer
{
CGRect _handBounds;
}
// We have to adapt hitTest so it only takes the hand into account (that's the top half of the image layer)
// We are also sure there's no sublayers
- (CALayer)hitTest:(CGPoint)aPoint
{
if (_isHidden)
return nil;
var point = CGPointApplyAffineTransform(aPoint, _transformToLayer);
return CGRectContainsPoint(_handBounds, point) ? self : nil;
}
- (void)setBounds:(CGRect)aBounds
{
if (CGRectEqualToRect(_bounds, aBounds))
return;
_handBounds = CGRectMakeCopy(aBounds);
_handBounds.size.height = _handBounds.size.height / 2;
[super setBounds:aBounds];
}
@end
#pragma mark -
@implementation HoursLayer : CALayer
{
BOOL _drawsHours;
CPFont _font @accessors(property=font);
CPColor _textColor @accessors(property=textColor);
float _radius @accessors(property=radius);
}
- (id)init
{
if (self = [super init])
{
_drawsHours = NO;
}
return self;
}
- (void)setDrawsHours:(BOOL)shouldDrawHours
{
shouldDrawHours = !!shouldDrawHours;
if (_drawsHours === shouldDrawHours)
return;
_drawsHours = shouldDrawHours;
[self setNeedsDisplay];
}
- (void)drawInContext:(CGContext)aContext
{
[super drawInContext:aContext];
if (_drawsHours)
{
var bounds = [self bounds],
centerX = bounds.size.width / 2,
centerY = bounds.size.height / 2;
CGContextSelectFont(aContext, _font);
CGContextSetFillColor(aContext, _textColor);
aContext.textBaseline = @"middle";
aContext.textAlign = @"center";
for (var i = 1, angle = 60.0, x, y; i < 13; i++, angle -= 30.0)
{
x = centerX + _radius * COS(angle * RADIANS);
y = centerY - _radius * SIN(angle * RADIANS);
aContext.fillText(i, x, y);
}
}
}
@end @end
+280
View File
@@ -0,0 +1,280 @@
/* _CPDatePickerDayView.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/Foundation.j>
@import "_CPDatePickerDayViewTextField.j"
@class CPDatePicker
@implementation _CPDatePickerDayView : CPControl
{
CPDate _date @accessors(property=date);
BOOL _isDisabled;
BOOL _isHighlighted;
BOOL _isSelected;
CPDatePicker _datePicker;
CPTextField _textField;
CPInteger _dayInWeek @accessors(property=dayInWeek);
BOOL _firstSelected @accessors(property=firstSelected);
BOOL _lastSelected @accessors(property=lastSelected);
}
#pragma mark Init methods
/*! Create a new instance of _CPDatePickerDayView
@param aFrame
@param aDatePicker
@return a new instance of _CPDatePickerDayView
*/
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
{
if (self = [super initWithFrame:aFrame])
{
[self setHitTests:NO];
_datePicker = aDatePicker;
// FIXME: Beginning with Aristo3, the text field is directly themed based on a new class _CPDatePickerDayViewTextField
if ([self isCSSBased])
{
_textField = [[_CPDatePickerDayViewTextField alloc] initWithFrame:aFrame];
}
else
{
_textField = [[CPTextField alloc] initWithFrame:aFrame];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-alignment"] forThemeAttribute:@"alignment"];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-vertical-alignment"] forThemeAttribute:@"vertical-alignment"];
var contentInset = [_datePicker valueForThemeAttribute:@"tile-content-inset"];
if (contentInset)
[_textField setValue:contentInset forThemeAttribute:@"content-inset"];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateSelected] forThemeAttribute:@"font" inState:CPThemeStateSelected];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-color" inState:CPThemeStateSelected];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateSelected];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateSelected] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateSelected];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]]forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inState:CPThemeStateHighlighted] forThemeAttribute:@"font" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-color" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inState:CPThemeStateHighlighted] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateHighlighted];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted, CPThemeStateSelected]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"font" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-color" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
[_textField setValue:[_datePicker valueForThemeAttribute:@"tile-text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]] forThemeAttribute:@"text-shadow-offset" inStates:[CPThemeStateDisabled, CPThemeStateHighlighted]];
}
[self addSubview:_textField];
[self setNeedsLayout];
}
return self;
}
#pragma mark -
#pragma mark Theme methods
/*! Set a theme
*/
- (BOOL)setThemeState:(ThemeState)aState
{
[_textField setThemeState:aState];
[super setThemeState:aState];
}
/*! Unset a theme
*/
- (BOOL)unsetThemeState:(ThemeState)aState
{
[_textField unsetThemeState:aState];
[super unsetThemeState:aState];
}
#pragma mark -
#pragma mark Getter methods
/*! Select the tile
*/
- (void)setSelected:(BOOL)shouldBeSelected
{
if (_isSelected === shouldBeSelected)
return;
_isSelected = shouldBeSelected;
if (_isSelected)
[self setThemeState:CPThemeStateSelected];
else
[self unsetThemeState:CPThemeStateSelected];
}
/*! Disabled the tile (used for previous and next month tile)
*/
- (void)setDisabled:(BOOL)shouldBeDisabled
{
if (_isDisabled === shouldBeDisabled)
return;
_isDisabled = shouldBeDisabled;
if (_isDisabled)
[self setThemeState:CPThemeStateDisabled];
else
[self unsetThemeState:CPThemeStateDisabled];
}
- (BOOL)isDisabled
{
return _isDisabled;
}
/*! Highlight the tile (used for current day)
*/
- (void)setHighlighted:(BOOL)shouldBeHighlighted
{
if (_isHighlighted === shouldBeHighlighted)
return;
_isHighlighted = shouldBeHighlighted;
if (_isHighlighted)
[self setThemeState:CPThemeStateHighlighted];
else
[self unsetThemeState:CPThemeStateHighlighted];
}
/*! Set the stringValue of the tile
@param aStringValue
*/
- (void)setStringValue:(CPString)aStringValue
{
[_textField setStringValue:aStringValue];
}
#pragma mark -
#pragma mark Layout methods
/*! Layout the subviews
*/
- (void)layoutSubviews
{
if ([_datePicker isCSSBased])
{
var attributeName = @"bezel-color-calendar";
if (_isSelected)
{
if (_firstSelected && !_lastSelected)
attributeName = @"bezel-color-calendar-left";
else if (_lastSelected && !_firstSelected)
attributeName = @"bezel-color-calendar-right";
else if (!_firstSelected && !_lastSelected)
{
if (_dayInWeek == 0)
attributeName = @"bezel-color-calendar-left";
else if (_dayInWeek == 6)
attributeName = @"bezel-color-calendar-right";
else
attributeName = @"bezel-color-calendar-middle";
}
}
[self setBackgroundColor:[_datePicker valueForThemeAttribute:attributeName inState:[self themeState]]];
return;
}
var bounds = [self bounds];
[_textField sizeToFit];
[_textField setFrameOrigin:CGPointMake(bounds.size.width / 2 - [_textField frameSize].width / 2 + [_datePicker valueForThemeAttribute:@"border-width"], bounds.size.height / 2 - [_textField frameSize].height / 2)];
}
- (void)setFrame:(CGRect)aFrame
{
[super setFrame:aFrame];
[_textField setFrame:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
}
/*! Drawrect
*/
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if ([_datePicker isCSSBased])
return;
var themeState = [self themeState],
context = [[CPGraphicsContext currentContext] graphicsPort];
if (themeState.hasThemeState(CPThemeStateSelected))
{
[self setBackgroundColor:[_datePicker valueForThemeAttribute:@"bezel-color-calendar" inState:themeState]];
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:themeState]);
CGContextAddRect(context, [self bounds]);
CGContextStrokeRect(context, [self bounds]);
}
else
{
// Clear color, because the original color of a tile is handle by his superview
[self setBackgroundColor:[CPColor clearColor]];
}
}
@end
@@ -0,0 +1,48 @@
/* _CPDatePickerDayViewTextField.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/Foundation.j>
@import "CPTextField.j"
@implementation _CPDatePickerDayViewTextField : CPTextField
+ (CPString)defaultThemeClass
{
return @"datePickerDayViewTextField";
}
+ (CPDictionary)themeAttributes
{
return @{
@"min-size": CGSizeMakeZero(),
@"content-inset": CGInsetMakeZero(),
@"text-color": [CPColor blackColor],
@"text-shadow-color": [CPColor clearColor],
@"text-shadow-offset": CGSizeMakeZero(),
@"font": [CPNull null],
@"vertical-alignment": CPCenterVerticalTextAlignment,
@"alignment": CPCenterTextAlignment,
};
}
@end
@@ -0,0 +1,589 @@
/* _CPDatePickerElementTextField.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPTextField.j"
@class CPDatePicker
@class _CPDatePickerElementView
CPDatePickerElementTextFieldBecomeFirstResponder = @"CPDatePickerElementTextFieldBecomeFirstResponder";
CPDatePickerElementTextFieldAMPMChangedNotification = @"CPDatePickerElementTextFieldAMPMChangedNotification";
var CPZeroKeyCode = 48,
CPNineKeyCode = 57,
CPMajAKeyCode = 65,
CPMajPKeyCode = 80,
CPAKeyCode = 97,
CPPKeyCode = 112;
CPMonthDateType = 0;
CPDayDateType = 1;
CPYearDateType = 2;
CPHourDateType = 3;
CPSecondDateType = 4;
CPMinuteDateType = 5;
CPAMPMDateType = 6;
/*! An element textField
*/
@implementation _CPDatePickerElementTextField : CPTextField
{
_CPDatePickerElementTextField _nextTextField @accessors(property=nextTextField);
_CPDatePickerElementTextField _previousTextField @accessors(property=previousTextField);
_CPDatePickerElementView _datePickerElementView @accessors(property=datePickerElementView);
CPDatePicker _datePicker @accessors(setter=setDatePicker:);
int _dateType @accessors(getter=dateType);
int _maxNumber @accessors(getter=maxNumber);
int _minNumber @accessors(getter=minNumber);
BOOL _firstEvent;
CPTimer _timerEdition;
}
+ (CPString)defaultThemeClass
{
return @"datePickerElementTextField";
}
+ (CPDictionary)themeAttributes
{
return @{
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
@"bezel-color": [CPNull null],
@"min-size": CGSizeMakeZero()
};
}
- (id)init
{
if (self = [super init])
{
_firstEvent = YES;
}
return self;
}
/*! @ignore */
- (BOOL)acceptsFirstResponder
{
return [_datePicker isEnabled];
}
/*! Set the dateType of the textField
*/
- (void)setDateType:(int)aDateType
{
_dateType = aDateType;
switch (aDateType)
{
case CPMonthDateType:
_minNumber = 1;
_maxNumber = 12;
break;
case CPDayDateType:
_minNumber = 1;
_maxNumber = 31;
break;
case CPYearDateType:
_minNumber = 0;
_maxNumber = 9999;
break;
case CPHourDateType:
_minNumber = 0;
_maxNumber = 23;
break;
case CPSecondDateType:
_minNumber = 0;
_maxNumber = 59;
break;
case CPMinuteDateType:
_minNumber = 0;
_maxNumber = 59;
break;
}
}
/*! Return the maxNumber of the textField
*/
- (int)maxNumber
{
if (_dateType == CPDayDateType)
return [[_datePicker dateValue] _daysInMonth];
return _maxNumber;
}
/*! Return the maxNumber of the textField depending of the maxDate
*/
- (int)_maxNumberWithMaxDate
{
var maxDate = [_datePicker maxDate],
date = [_datePicker dateValue];
if (maxDate)
{
switch (_dateType)
{
case CPMonthDateType:
if (maxDate.getFullYear() == date.getFullYear())
return maxDate.getMonth();
break;
case CPDayDateType:
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth())
return maxDate.getDate();
break;
case CPYearDateType:
return maxDate.getFullYear();
case CPHourDateType:
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate())
return maxDate.getHours();
break;
case CPSecondDateType:
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours() && maxDate.getMinutes() == date.getMinutes())
return maxDate.getSeconds();
break;
case CPMinuteDateType:
if (maxDate.getFullYear() == date.getFullYear() && maxDate.getMonth() == date.getMonth() && maxDate.getDate() == date.getDate() && maxDate.getHours() == date.getHours())
return maxDate.getMinutes();
break;
}
}
return _maxNumber;
}
/*! Set the stringValue of the textField. This is going to check if there is 2 or 4 letters. If not it adds a space. Check also the maxDate
It's called when the user is editing with the keyboard
@param aStringValue a CPString
*/
- (void)setValueForKeyEvent:(CPEvent)anEvent
{
var keyCode = [anEvent keyCode];
if (keyCode != CPDeleteKeyCode && keyCode != CPDeleteForwardKeyCode && keyCode < CPZeroKeyCode || keyCode > CPNineKeyCode)
return;
var newValue = [self stringValue].replace(/\s/g, ''),
length = [newValue length],
eventKeyValue = parseInt([anEvent characters]).toString();
if (keyCode == CPDeleteKeyCode || keyCode == CPDeleteForwardKeyCode)
{
[_timerEdition invalidate];
_timerEdition = nil;
newValue = [newValue substringToIndex:(length - 1)];
}
else
{
if (!_timerEdition)
{
_timerEdition = [CPTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(_timerKeyEvent:) userInfo:nil repeats:NO];
if (_firstEvent || !length)
newValue = eventKeyValue;
else
newValue = parseInt(newValue).toString() + eventKeyValue;
}
else
{
var newFireDate = [CPDate date];
newFireDate.setSeconds(newFireDate.getSeconds() + 2);
[_timerEdition setFireDate:newFireDate];
newValue = parseInt(newValue).toString() + eventKeyValue;
}
}
if (parseInt(newValue) > [self _maxNumberWithMaxDate] || ([_datePicker _isAmericanFormat] && _dateType == CPHourDateType && parseInt(newValue) > 12))
return;
_firstEvent = NO;
[super setObjectValue:newValue];
}
/*!
End of the timer
*/
- (void)_timerKeyEvent:(id)sender
{
var stringValue = [self stringValue];
_timerEdition = nil;
if ([stringValue length])
{
if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType)
{
var isAMHour = [[self superview] _isAMHour];
if (!isAMHour && stringValue != 12)
stringValue = parseInt(stringValue) + 12;
if (stringValue == 12 && !isAMHour)
stringValue = 12;
else if (stringValue == 12)
stringValue = 0;
}
[self setObjectValue:stringValue];
}
}
/*!
We force to end the timer
*/
- (void)_invalidTimer
{
if (_timerEdition)
{
[_timerEdition invalidate];
_timerEdition = nil;
}
}
/*!
We force to end the timer and to update the objectValue of the datePicker
*/
- (void)_endEditing
{
if (_timerEdition)
[_timerEdition invalidate];
_timerEdition = nil;
var objectValue = [self stringValue];
if (![objectValue length])
objectValue = [self objectValue];
if ([_datePicker _isAmericanFormat] && [self dateType] == CPHourDateType)
{
var isAMHour = [[self superview] _isAMHour];
if (!isAMHour && objectValue != 12)
objectValue = parseInt(objectValue) + 12;
if (objectValue == 12 && !isAMHour)
objectValue = 12;
else if (objectValue == 12)
objectValue = 0;
}
[self setObjectValue:objectValue];
}
/*! Set the stringValue of the TextField. Add some zeros of there isn't 2/4 letters in the value. It's called at the end of the editing process
@param aStringValue a CPString
*/
- (void)setStringValue:(CPString)aStringValue
{
if (_dateType == CPYearDateType)
{
while ([aStringValue length] < 4)
aStringValue = "0" + aStringValue;
}
else if (_dateType != CPAMPMDateType)
{
if (_dateType == CPHourDateType && [_datePicker _isAmericanFormat])
{
var value = parseInt(aStringValue);
if (value == 0)
value = 12;
else if (value > 12)
value = value - 12;
aStringValue = value.toString();
}
while ([aStringValue length] < 2)
{
if (_dateType == CPSecondDateType || _dateType == CPMinuteDateType)
aStringValue = @"0" + aStringValue;
else
aStringValue = @" " + aStringValue;
}
}
[super setObjectValue:aStringValue];
}
/*! Set the objectValue. This will update the dateValue of the datePicker also. It's called with the binding of the stepper or arrows
This is not going to update the objectValue of the control !!! It updates the dateValue of the datePicker who's going to update the datePickerTextField if necessary
It's a bit tricky
@param aObjectValue
*/
- (void)setObjectValue:(id)anObjectValue
{
var dateValue = [[_datePicker dateValue] copy],
lengthString = [[self stringValue] length],
objectValue = parseInt(anObjectValue);
switch (_dateType)
{
case CPMonthDateType:
if (objectValue == 0 || !lengthString)
{
[self setStringValue:(dateValue.getMonth() + 1).toString()];
return;
}
var dateNextMonth = [dateValue copy];
dateNextMonth.setDate(1);
dateNextMonth.setMonth(parseInt(anObjectValue) - 1);
var numberDayNextMonth = [dateNextMonth _daysInMonth];
if (numberDayNextMonth < [dateValue _daysInMonth] && dateValue.getDate() > numberDayNextMonth)
[_datePickerElementView setDayDateValue:numberDayNextMonth.toString()];
[super setObjectValue:objectValue];
break;
case CPDayDateType:
if (objectValue == 0 || !lengthString)
{
[self setStringValue:dateValue.getDate().toString()];
return;
}
// if we enter a day that is too high for the current month
// we need to increase the month by one
// if we do not do this, the user input would be silently reset
// very poor user experience
if (parseInt(anObjectValue, 10) > [dateValue _daysInMonth])
{
[_datePickerElementView._textFieldMonth setIntValue:(dateValue.getMonth() + 2)];
[super setObjectValue:objectValue];
return;
}
[super setObjectValue:objectValue];
break;
case CPYearDateType:
if (objectValue == 0 || !lengthString)
{
[self setStringValue:dateValue.getFullYear().toString()];
return;
}
[super setObjectValue:objectValue];
break;
case CPHourDateType:
if (!lengthString)
{
[self setStringValue:dateValue.getHours().toString()];
return;
}
[super setObjectValue:objectValue];
break;
case CPSecondDateType:
if (!lengthString)
{
[self setStringValue:dateValue.getSeconds().toString()];
return;
}
[super setObjectValue:objectValue];
break;
case CPMinuteDateType:
if (!lengthString)
{
[self setStringValue:dateValue.getMinutes().toString()];
return;
}
[super setObjectValue:objectValue];
break;
}
var newDateValue = [_datePickerElementView dateValue],
timeZone = [_datePicker timeZone];
if (timeZone)
{
var secondsFromGMT = [timeZone secondsFromGMTForDate:newDateValue],
secondsFromGMTTimeZone = [timeZone secondsFromGMT];
newDateValue.setSeconds(newDateValue.getSeconds() + secondsFromGMT - secondsFromGMTTimeZone);
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:newDateValue timeInterval:[_datePicker timeInterval]];
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
#pragma mark -
#pragma mark Mouse event
/*! Mouse down event. Launch a notification to notif the new first responder textField
*/
- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEnabled])
return;
[super mouseDown:anEvent];
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldBecomeFirstResponder object:[[self superview] superview] userInfo:[CPDictionary dictionaryWithObject:self forKey:@"textField"]];
}
#pragma mark -
#pragma mark Theme functions
/*! Set the theme CPThemeStateSelected
*/
- (void)makeSelectable
{
[self setThemeState:CPThemeStateSelected];
[_datePicker setThemeState:CPThemeStateEditing];
}
/*! Unsert the theme CPThemeStateSelected
*/
- (void)makeDeselectable
{
_firstEvent = YES;
[self unsetThemeState:CPThemeStateSelected];
[_datePicker unsetThemeState:CPThemeStateEditing];
}
#pragma mark -
#pragma mark Override
/*!
We override this method to get all the time the good width
*/
- (CGSize)_minimumFrameSize
{
var frameSize = [self frameSize],
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
minSize = [self currentValueForThemeAttribute:@"min-size"],
maxSize = [self currentValueForThemeAttribute:@"max-size"],
lineBreakMode = [self lineBreakMode],
text = (_dateType == CPYearDateType) ? @"0000" : (_dateType == CPMonthDateType) ? @"10" : @"00",
textSize = CGSizeMakeCopy(frameSize),
font = [self currentValueForThemeAttribute:@"font"];
textSize.width -= contentInset.left + contentInset.right;
textSize.height -= contentInset.top + contentInset.bottom;
if (_dateType == CPAMPMDateType)
text = [self stringValue];
if (frameSize.width !== 0 &&
![self isBezeled] &&
(lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
{
textSize = [text sizeWithFont:font inWidth:textSize.width];
}
else
{
textSize = [text sizeWithFont:font];
// Account for possible fractional pixels at right edge
textSize.width += 1;
}
// Account for possible fractional pixels at bottom edge
textSize.height += 1;
frameSize.height = textSize.height + contentInset.top + contentInset.bottom;
if ([self isBezeled])
{
frameSize.height = MAX(frameSize.height, minSize.height);
if (maxSize.width > 0.0)
frameSize.width = MIN(frameSize.width, maxSize.width);
if (maxSize.height > 0.0)
frameSize.height = MIN(frameSize.height, maxSize.height);
}
else
frameSize.width = textSize.width + contentInset.left + contentInset.right;
frameSize.width = MAX(frameSize.width, minSize.width);
return frameSize;
}
- (CGRect)bezelRectForBounds:(CGRect)bounds
{
return CGRectMakeCopy(bounds);
}
@end
#pragma mark -
@implementation _CPDatePickerElementSeparator : CPTextField
+ (CPString)defaultThemeClass
{
return @"datePickerElementSeparator";
}
+ (CPDictionary)themeAttributes
{
return @{
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
@"min-size": CGSizeMakeZero()
};
}
@end
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,306 @@
/* _CPDatePickerHeaderView.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/Foundation.j>
@import "CPControl.j"
@import "CPTextField.j"
@import "CPButton.j"
@class CPDatePicker
var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"],
CPShortWeekDayNameArrayUS = [@"Su", @"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa"],
CPShortWeekDayNameArrayFr = [@"L", @"M", @"M", @"J", @"V", @"S", @"D"],
CPShortWeekDayNameArrayDe = [@"M", @"D", @"M", @"D", @"F", @"S", @"S"],
CPShortWeekDayNameArrayEs = [@"L", @"M", @"X", @"J", @"V", @"S", @"D"],
CPShortMonthNameArrayEn = [@"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec"],
CPShortMonthNameArrayFr = [@"janv.", String.fromCharCode(102, 233, 118, 46), @"mars", @"apr.", @"mai", @"juin", @"juil.", String.fromCharCode(97, 111, 251, 116), @"sept.", @"oct.", @"nov.", String.fromCharCode(100, 233, 99, 46)],
CPShortMonthNameArrayDe = [@"Jan", @"Feb", String.fromCharCode(77, 228, 114), @"Apr", @"Mai", @"Jun", @"Jul", @"Aug", @"Sep", @"Okt", @"Nov", @"Dez"],
CPShortMonthNameArrayEs = [@"ene", @"feb", @"mar", @"abr", @"may", @"jun", @"jul", @"ago", @"sep", @"oct", @"nov", @"dic"];
@implementation _CPDatePickerHeaderView : CPControl
{
CPArray _dayLabels;
CPArray _monthNames;
CPButton _nextButton;
CPButton _previousButton;
CPButton _currentButton;
CPDatePicker _datePicker;
CPDate _date;
CPTextField _title;
}
#pragma mark Init methods
/*! Init a new instance of _CPDatePickerHeaderView
@param aFrame
@param aDatePicker
@return a new instance of _CPDatePickerHeaderView
*/
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate
{
self = [super initWithFrame:aFrame];
if (self)
{
_datePicker = aDatePicker;
// Title
_title = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_title setValue:[_datePicker valueForThemeAttribute:@"title-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[self addSubview:_title];
_dayLabels = [CPArray array];
// Days
for (var i = 0, count = [[self _dayNames] count]; i < count; i++)
{
var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[label setValue:[_datePicker valueForThemeAttribute:@"tile-text-alignment"] forThemeAttribute:@"alignment"];
var contentInset = [_datePicker valueForThemeAttribute:@"tile-content-inset"];
if (contentInset)
[label setValue:contentInset forThemeAttribute:@"content-inset"];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[label setValue:[_datePicker valueForThemeAttribute:@"weekday-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[_dayLabels addObject:label];
[self addSubview:label];
}
// Arrows
var size = [_datePicker valueForThemeAttribute:@"previous-button-size"];
_previousButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[_previousButton setButtonType:CPMomentaryChangeButton];
[_previousButton setBordered:NO];
[_previousButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-left"]];
[_previousButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-left-highlighted"]];
[self addSubview:_previousButton];
size = [_datePicker valueForThemeAttribute:@"next-button-size"];
_nextButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[_nextButton setButtonType:CPMomentaryChangeButton];
[_nextButton setBordered:NO];
[_nextButton setImage:[_datePicker valueForThemeAttribute:@"arrow-image-right"]];
[_nextButton setAlternateImage:[_datePicker valueForThemeAttribute:@"arrow-image-right-highlighted"]];
[self addSubview:_nextButton];
size = [_datePicker valueForThemeAttribute:@"current-button-size"];
_currentButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)];
[_currentButton setButtonType:CPMomentaryChangeButton];
[_currentButton setBordered:NO];
[_currentButton setImage:[_datePicker valueForThemeAttribute:@"circle-image"]];
[_currentButton setAlternateImage:[_datePicker valueForThemeAttribute:@"circle-image-highlighted"]];
[self addSubview:_currentButton];
[_previousButton setTarget:aDelegate];
[_previousButton setAction:@selector(_clickArrowPrevious:)];
[_previousButton setContinuous:YES];
[_nextButton setTarget:aDelegate];
[_nextButton setAction:@selector(_clickArrowNext:)];
[_nextButton setContinuous:YES];
[_currentButton setTarget:aDelegate];
[_currentButton setAction:@selector(_currentMonth:)];
[self setNeedsLayout];
}
return self;
}
#pragma mark -
#pragma mark Getter Setter methods
/*! Return the day names depending on the CPLocale of the datePicker
@return an array
*/
- (CPArray)_dayNames
{
switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode])
{
case @"en":
// Check if it's in the american format. If yes the week will begin the sunday
if ([_datePicker _isAmericanFormat])
return CPShortWeekDayNameArrayUS;
else
return CPShortWeekDayNameArrayEn;
break;
case @"es":
return CPShortWeekDayNameArrayEs;
break;
case @"de":
return CPShortWeekDayNameArrayDe;
break;
case @"fr":
return CPShortWeekDayNameArrayFr;
break;
default:
return CPShortWeekDayNameArrayEn;
break;
}
}
/*! Return the month names depending on the CPLocale of the datePicker
@return an array
*/
- (CPArray)_monthNames
{
switch ([[_datePicker locale] objectForKey:CPLocaleLanguageCode])
{
case @"en":
return CPShortMonthNameArrayEn;
break;
case @"es":
return CPShortMonthNameArrayEs;
break;
case @"de":
return CPShortMonthNameArrayDe;
break;
case @"fr":
return CPShortMonthNameArrayFr;
break;
default:
return CPShortMonthNameArrayEn;
break;
}
}
/*! Set the monthDate of the header
@aMonthDate the new monthDate
*/
- (void)setMonthForDate:(CPDate)aMonthDate
{
_date = aMonthDate;
[self setNeedsLayout];
}
/*! Set enabled
@param aBoolean
*/
- (void)setEnabled:(BOOL)aBoolean
{
[_previousButton setEnabled:aBoolean];
[_nextButton setEnabled:aBoolean];
[_currentButton setEnabled:aBoolean];
[_dayLabels makeObjectsPerformSelector:@selector(setEnabled:) withObject:aBoolean];
[_title setEnabled:aBoolean];
}
#pragma mark -
#pragma mark Layout methods
/*! Layout the subviews
*/
- (void)layoutSubviews
{
var bounds = [self bounds],
dayNames = [self _dayNames],
width = CGRectGetWidth(bounds),
buttonInset = [_datePicker valueForThemeAttribute:@"arrow-inset"],
numberOfLabels = [_dayLabels count],
labelWidth = width / numberOfLabels,
sizeButtonLeft = [[_datePicker valueForThemeAttribute:@"arrow-image-left"] size],
sizeButtonRight = [[_datePicker valueForThemeAttribute:@"arrow-image-right"] size],
sizeButtonCircle = [[_datePicker valueForThemeAttribute:@"circle-image"] size],
sizeTileWidth = [_datePicker valueForThemeAttribute:@"size-tile"].width,
titleInset = [_datePicker valueForThemeAttribute:@"title-inset"],
dayLabelInset = [_datePicker valueForThemeAttribute:@"day-label-inset"];
// Arrows
[_nextButton setFrame:CGRectMake(width - [_nextButton frameSize].width - buttonInset.right, buttonInset.top, sizeButtonRight.width, sizeButtonRight.height)];
[_currentButton setFrame:CGRectMake(CGRectGetMinX([_nextButton frame]) - sizeButtonCircle.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonCircle.width, sizeButtonCircle.height)];
[_previousButton setFrame:CGRectMake(CGRectGetMinX([_currentButton frame]) - sizeButtonLeft.width - buttonInset.left - buttonInset.right, buttonInset.top, sizeButtonLeft.width, sizeButtonLeft.height)];
var firstDayTileX;
// Weekday label
for (var i = 0; i < numberOfLabels; i++)
{
var dayLabel = _dayLabels[i];
[dayLabel setStringValue:dayNames[i]];
if (dayLabelInset) // Beginning with Aristo3
{
var thisWidth = ROUND((i+1) * sizeTileWidth) - ROUND(i * sizeTileWidth);
[dayLabel sizeToFit];
[dayLabel setFrame:CGRectMake(dayLabelInset.left + ROUND(i * sizeTileWidth), dayLabelInset.top, thisWidth, [dayLabel frameSize].height)];
}
else
{
[dayLabel sizeToFit];
[dayLabel setFrameOrigin:CGPointMake(sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2, 23)];
if (i == 0)
firstDayTileX = sizeTileWidth * (i + 1) - sizeTileWidth / 2 - [dayLabel frameSize].width / 2;
}
}
// Title
[_title setStringValue:[CPString stringWithFormat:@"%s %i", [self _monthNames][_date.getMonth()], _date.getFullYear()]];
[_title sizeToFit];
if (titleInset) // Beginning with Aristo3
[_title setFrameOrigin:CGPointMake(titleInset.left, titleInset.top)];
else
[_title setFrameOrigin:CGPointMake(firstDayTileX, 6)];
}
@end
@@ -0,0 +1,636 @@
/* _CPDatePickerMonthView.j
* AppKit
*
* Created by Alexandre Wilhelm
* Copyright 2012 <alexandre.wilhelmfr@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/Foundation.j>
@import "CPControl.j"
@import "_CPDatePickerDayView.j"
@class CPDatePicker
@global CPSingleDateMode
@global CPRangeDateMode
@implementation _CPDatePickerMonthView : CPControl
{
BOOL _isMonthJustChanged;
CPArray _dayTiles;
CPDate _clickDate;
CPDate _dragDate;
CPDate _date;
CPDate _previousMonth @accessors(property=previousMonth);
CPDate _nextMonth @accessors(property=nextMonth);
CPDatePicker _datePicker;
CPEvent _eventDragged;
CPTimer _timerMonth;
id _delegate;
int _indexDayTile;
}
#pragma mark Init methods
/*! Init a _CPDatePickerMonthView
@param aFrame
@param aDatePicker
@return a new _CPDatePickerMonthView
*/
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker delegate:(id)aDelegate
{
if (self = [super initWithFrame:aFrame])
{
_delegate = aDelegate;
_isMonthJustChanged = NO;
_indexDayTile = -1;
_datePicker = aDatePicker;
_dayTiles = [CPArray array];
// Create tiles
for (var i = 0; i < 42; i++)
{
var dayView = [[_CPDatePickerDayView alloc] initWithFrame:CGRectMakeZero() withDatePicker:_datePicker];
[self addSubview:dayView];
[_dayTiles addObject:dayView];
}
[self setNeedsLayout];
}
return self;
}
#pragma mark -
#pragma mark Getter Setter methods
/*! Set the monthDate of the component
@param aDate
*/
- (void)setMonthForDate:(CPDate)aDate
{
if (_dragDate)
{
if (_dragDate.getMonth() != _date.getMonth())
_isMonthJustChanged = YES;
_date = [_dragDate copy];
}
else
{
_date = [aDate copy];
}
if (![aDate isEqualToDate:[CPDate distantFuture]])
{
// Reset the date to the first day of the month & midnight
_date.setDate(1);
[_date _resetToMidnight];
// There must be a better way to do this.
var firstDay = [_date copy];
firstDay.setDate(1);
// Set the previous and next month date. This is usefull for the tile of the next/previous month
_previousMonth = new Date(firstDay.getTime() - 86400000);
_previousMonth.setDate(1);
_nextMonth = new Date(firstDay.getTime() + (([_date _daysInMonth] + 1) * 86400000));
_nextMonth.setDate(1);
}
[self reloadData];
if (_isMonthJustChanged)
{
var dayTile = [_dayTiles objectAtIndex:_indexDayTile];
if ([dayTile date].getMonth() == _date.getMonth())
{
[self mouseDragged:_eventDragged];
}
else
{
if ([dayTile date].getMonth() - _date.getMonth() == 1 || [dayTile date].getFullYear() - _date.getFullYear() == 1)
_timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerNextMonthEvent:) userInfo:nil repeats:NO];
else
_timerMonth = [CPTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(_timerPreviousMonthEvent:) userInfo:nil repeats:NO];
}
}
}
- (void)monthDate
{
return _date;
}
/*! Return the size of a tile
*/
- (CGSize)tileSize
{
return [_datePicker valueForThemeAttribute:@"size-tile"];
}
/*! Return the first index day of the month
*/
- (int)startOfWeekForDate:(CPDate)aDate
{
var day = aDate.getDay();
// American people begins the week the sunday
if (![_datePicker _isAmericanFormat])
return (day + 6) % 7;
return day;
}
/*! Set enabled
@param aBoolean
*/
- (void)setEnabled:(BOOL)aBoolean
{
[super setEnabled:aBoolean];
[self reloadData];
}
/*! Return the index of tile depending of the giving event
@param anEvent
@return an index
*/
- (CPInteger)indexOfTileForEvent:(CPEvent)anEvent
{
var locationInView = [self convertPoint:[anEvent locationInWindow] fromView:nil],
tileSize = [self tileSize],
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"],
margin = [_datePicker valueForThemeAttribute:@"tile-margin"] || CGSizeMakeZero(),
tileInset = [_datePicker valueForThemeAttribute:@"tile-inset"] || CGInsetMakeZero();
// Get the week row
var rowIndex = FLOOR((locationInView.y - tileInset.top - margin.height) / (tileSize.height + 2 * margin.height + borderWidth)),
columnIndex = FLOOR((locationInView.x - tileInset.left) / (tileSize.width + 2 * margin.width + borderWidth));
columnIndex = MIN(MAX(columnIndex, 0), 6);
rowIndex = MIN(MAX(rowIndex, 0), 5);
var tileIndex = (rowIndex * 7) + columnIndex;
return tileIndex;
}
#pragma mark -
#pragma mark Reload data
/*! Reload the data
*/
- (void)reloadData
{
if (!_date)
return;
var currentMonth = _date,
startOfMonthDay = [self startOfWeekForDate:currentMonth],
daysInPreviousMonth = [_previousMonth _daysInMonth],
firstDayToShowInPreviousMonth = daysInPreviousMonth - startOfMonthDay,
currentDate = new Date(_previousMonth.getFullYear(), _previousMonth.getMonth(), firstDayToShowInPreviousMonth),
now = [CPDate date],
dateValue = [_datePicker dateValue];
// Update the tiles
for (var i = 0; i < [_dayTiles count]; i++)
{
var dayTile = _dayTiles[i];
// Increment to next day
currentDate.setTime(currentDate.getTime() + 90000000);
[currentDate _resetToMidnight];
var isPresentMonth = (now.getMonth() == currentDate.getMonth()
&& now.getFullYear() == currentDate.getFullYear());
[dayTile setDate:[currentDate copy]];
[dayTile setStringValue:currentDate.getDate()];
[dayTile setDisabled:/*![self isEnabled] ||*/ currentDate.getMonth() !== currentMonth.getMonth() || currentDate < [_datePicker minDate] || currentDate > [_datePicker maxDate]];
[dayTile setHighlighted:isPresentMonth && currentDate.getDate() == now.getDate()];
}
// Select the dates
[self _selectDate:[_datePicker dateValue] timeInterval:[_datePicker timeInterval]];
}
#pragma mark -
#pragma mark Select methods
/*! Select one date or several date depending of the giving interval
@param aStartDate
@param anInterval;
*/
- (void)_selectDate:(CPDate)aStartDate timeInterval:(CPInteger)anInterval
{
var endDate = [[CPDate alloc] initWithTimeInterval:anInterval sinceDate:aStartDate],
tilesCount = [_dayTiles count];
aStartDate = [aStartDate copy];
[aStartDate _resetToMidnight];
[endDate _resetToMidnight];
var firstSelected = NO;
for (var i = 0; i < tilesCount; i++)
{
var tile = _dayTiles[i],
tileDate = [[tile date] copy],
selected = NO;
[tileDate _resetToMidnight];
if (aStartDate)
selected = tileDate >= aStartDate && tileDate <= endDate;
// Select a tile
[tile setSelected:selected];
// If we are disabled, we have to disable selected tiles so they will appear disabled
[tile setDisabled:[tile isDisabled] || (selected && ![self isEnabled])];
if (selected)
{
if (!firstSelected)
{
firstSelected = YES;
[tile setFirstSelected:YES];
}
else
[tile setFirstSelected:NO];
[tile setLastSelected:NO];
}
else
{
if (firstSelected)
{
firstSelected = NO;
// As there was a first selected and we are now on an unselected tile,
// we are sure that i > 0
[_dayTiles[i-1] setLastSelected:YES];
}
}
}
}
#pragma mark -
#pragma mark Layout methods
/*! Tile the view
*/
- (void)tile
{
var tileSize = [self tileSize],
width = tileSize.width,
height = tileSize.height,
tilesCount = [_dayTiles count],
borderWidth = [_datePicker valueForThemeAttribute:@"border-width"],
margin = [_datePicker valueForThemeAttribute:@"tile-margin"],
tileInset = [_datePicker valueForThemeAttribute:@"tile-inset"],
thisWidth,
thisX,
dayInWeek,
weekInMonth,
tileFrame,
tileIndex;
// Set the frame of the tiles
for (tileIndex = 0; tileIndex < tilesCount; tileIndex++)
{
dayInWeek = tileIndex % 7;
weekInMonth = (tileIndex - dayInWeek) / 7;
tileFrame;
if (margin) // Beginning with Aristo3
{
thisX = ROUND(dayInWeek * (width + 2 * margin.width));
thisWidth = ROUND((dayInWeek+1) * (width + 2 * margin.width)) - thisX;
tileFrame = CGRectMake(tileInset.left + thisX, tileInset.top + margin.height + weekInMonth * (height + 2 * margin.height), thisWidth + borderWidth, height + borderWidth);
}
else
tileFrame = CGRectMake(dayInWeek * width, weekInMonth * height, width + borderWidth, height + borderWidth);
[_dayTiles[tileIndex] setFrame:tileFrame];
[_dayTiles[tileIndex] setDayInWeek:dayInWeek];
}
[self reloadData];
}
/*! Layout the subviews
*/
- (void)layoutSubviews
{
[super layoutSubviews];
[self tile];
[_dayTiles makeObjectsPerformSelector:@selector(setNeedsLayout)];
}
/*! Draw the component. This draws the border of the tile.
The selected tile are drawed in the drawRect method of the tile. But the unselected tile here.
It avoids some problems with tiles over other tiles (otherwise the color of the tile border would be different).
Rememeber that the first pixel of a tile are over the last pixel of the last tile (because the border)
*/
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if ([_datePicker isCSSBased])
{
// We just have to draw the separator (if any)
// No separator color means no separator
var separatorColor = [_datePicker valueForThemeAttribute:@"separator-color"],
separatorHeight = [_datePicker valueForThemeAttribute:@"separator-height"],
separatorMarginWidth = [_datePicker valueForThemeAttribute:@"separator-margin-width"];
if (separatorColor)
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
bounds = [self bounds];
CGContextBeginPath(context);
CGContextSetStrokeColor(context, separatorColor);
CGContextSetLineWidth(context, separatorHeight);
CGContextMoveToPoint(context, separatorMarginWidth, 0.5);
CGContextAddLineToPoint(context, bounds.size.width - separatorMarginWidth, 0.5);
CGContextStrokePath(context);
CGContextClosePath(context);
}
return;
}
var context = [[CPGraphicsContext currentContext] graphicsPort],
width = [self tileSize].width,
height = [self tileSize].height,
isBorderPair = ([_datePicker valueForThemeAttribute:@"border-width"] % 2) == 0;
CGContextBeginPath(context);
CGContextSetStrokeColor(context, [_datePicker valueForThemeAttribute:@"border-color" inState:[_datePicker themeState]]);
CGContextSetLineWidth(context, [_datePicker valueForThemeAttribute:@"border-width"]);
if ([_datePicker isBordered])
{
for (var i = 0; i < 6; i++)
{
var y = i * height;
// Very usefull to avoid to have a line of two pixels instead one
if (!isBorderPair)
y += 0.5;
CGContextMoveToPoint(context, 0, y);
CGContextAddLineToPoint(context, [self bounds].size.width, y);
}
for (var i = 0; i < 7; i++)
{
var x = i * width;
// Very usefull to avoid to have a line of two pixels instead one
if (!isBorderPair)
x += 0.5;
CGContextMoveToPoint(context, x, 0);
CGContextAddLineToPoint(context, x, [self bounds].size.height);
}
}
else
{
var y = 0;
// Very usefull to avoid to have a line of two pixels instead one
if (!isBorderPair)
y += 0.5;
CGContextMoveToPoint(context, 0, y);
CGContextAddLineToPoint(context, [self bounds].size.width, y);
}
CGContextStrokePath(context);
CGContextClosePath(context);
}
#pragma mark -
#pragma mark Mouse event
/*! Mouse down event
*/
- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEnabled])
return;
var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]],
dateTile = [[dayTile date] copy],
dateValue = [_datePicker dateValue];
_clickDate = [dateTile copy];
_dragDate = nil;
_indexDayTile = -1;
_eventDragged = nil;
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
// Check if we have to change or not the month of the component
if ([dayTile date].getMonth() == _date.getMonth())
{
if ([_datePicker datePickerMode] == CPRangeDateMode && [anEvent modifierFlags] & CPShiftKeyMask)
{
var dateValueAtMidnight = [[_datePicker dateValue] copy];
[dateValueAtMidnight _resetToMidnight];
if (dateTile < dateValueAtMidnight)
{
var interval;
if (dateTile == dateValueAtMidnight)
interval = [_datePicker timeInterval];
else
interval = ([dateValueAtMidnight timeIntervalSinceDate:dateTile] + [_datePicker timeInterval]);
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:interval];
}
else if ([[dayTile date] isEqualToDate:dateValueAtMidnight])
{
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
}
else
{
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:[dateValueAtMidnight copy]] timeInterval:([dateTile timeIntervalSinceDate:dateValueAtMidnight])];
}
// Be sure to display the good month
[_delegate setDateValue:dateTile];
}
else
{
var minDate = [[_datePicker minDate] copy],
maxDate = [[_datePicker maxDate] copy];
[minDate _resetToMidnight];
[maxDate _resetToLastSeconds];
if (dateTile >= minDate && dateTile <= maxDate)
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
}
}
else
{
// Check the year and the month. The year is usefull when changing from Jan to Dec.
if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1)
[_delegate _displayPreviousMonth];
else
[_delegate _displayNextMonth];
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
/*! Mouse dragged event
*/
- (void)mouseDragged:(CPEvent)anEvent
{
if (![self isEnabled] || !CGRectContainsPoint([self bounds],[self convertPoint:[anEvent locationInWindow] fromView:nil]))
return;
var dayTile = [_dayTiles objectAtIndex:[self indexOfTileForEvent:anEvent]],
dateTile = [[dayTile date] copy],
dateValue = [_datePicker dateValue];
_dragDate = [dateTile copy];
_indexDayTile = [self indexOfTileForEvent:anEvent];
_eventDragged = anEvent;
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
if ([_datePicker datePickerMode] == CPSingleDateMode)
{
// Check if we have to change or not the month of the component
if ([dayTile date].getMonth() == _date.getMonth())
{
[_timerMonth invalidate];
_isMonthJustChanged = NO;
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
}
else if (!_isMonthJustChanged)
{
[_timerMonth invalidate];
_isMonthJustChanged = NO;
// Check the year and the month. The year is usefull when changing from Jan to Dec.
if (_date.getMonth() - [dayTile date].getMonth() == 1 || _date.getFullYear() - [dayTile date].getFullYear() == 1)
[_delegate _displayPreviousMonth];
else
[_delegate _displayNextMonth];
}
}
else
{
if (dateTile.getMonth() == _date.getMonth() || !_isMonthJustChanged)
{
[_timerMonth invalidate];
_isMonthJustChanged = NO;
var dateValueAtMidnight = [[_datePicker dateValue] copy];
[dateValueAtMidnight _resetToMidnight];
if (dateTile < _clickDate)
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:[_clickDate timeIntervalSinceDate:dateTile]];
else if ([[dayTile date] isEqualToDate:_clickDate])
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:dateTile] timeInterval:0];
else
[_datePicker _setDateValue:[self _hoursMinutesSecondsFromDatePickerForDate:_clickDate] timeInterval:[dateTile timeIntervalSinceDate:dateValueAtMidnight]];
}
}
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = NO;
#endif
}
- (void)mouseUp:(CPEvent)anEvent
{
[_timerMonth invalidate];
_dragDate = nil;
_clickDate = nil;
_isMonthJustChanged = NO;
_indexDayTile = -1;
_eventDragged = nil;
}
#pragma mark -
#pragma mark Timer
- (void)_timerNextMonthEvent:(CPEvent)anEvent
{
if (_isMonthJustChanged)
{
_dragDate.setMonth(_date.getMonth() + 1);
[_delegate _displayNextMonth];
}
}
- (void)_timerPreviousMonthEvent:(CPEvent)anEvent
{
if (_isMonthJustChanged)
{
_dragDate.setMonth(_date.getMonth() - 1);
[_delegate _displayPreviousMonth];
}
}
#pragma mark -
#pragma mark Date methods
- (CPDate)_hoursMinutesSecondsFromDatePickerForDate:(CPDate)aDate
{
var dateValue = [_datePicker dateValue];
aDate.setHours(dateValue.getHours());
aDate.setMinutes(dateValue.getMinutes());
aDate.setSeconds(dateValue.getSeconds());
return aDate;
}
@end
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -69,7 +69,7 @@
aNewObject._controller = self; aNewObject._controller = self;
aNewObject._key = aKey; aNewObject._key = aKey;
if (aValue !== nil) if (aValue != nil)
[aNewObject setValue:aValue]; [aNewObject setValue:aValue];
return aNewObject; return aNewObject;
@@ -98,7 +98,7 @@
var iter = [[CPSet setWithArray:allKeys] objectEnumerator], var iter = [[CPSet setWithArray:allKeys] objectEnumerator],
obj; obj;
while ((obj = [iter nextObject]) !== nil) while ((obj = [iter nextObject]) != nil)
if (![_excludedKeys containsObject:obj]) if (![_excludedKeys containsObject:obj])
[array addObject:[self _newObjectWithKey:obj value:nil]]; [array addObject:[self _newObjectWithKey:obj value:nil]];
+2 -2
View File
@@ -106,7 +106,7 @@ var CPSharedDocumentController = nil;
@param aType the type of the new document @param aType the type of the new document
@param shouldDisplay whether to display the document on screen @param shouldDisplay whether to display the document on screen
*/ */
- (void)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay - (CPDocument)openUntitledDocumentOfType:(CPString)aType display:(BOOL)shouldDisplay
{ {
var theDocument = [self makeUntitledDocumentOfType:aType error:nil]; var theDocument = [self makeUntitledDocumentOfType:aType error:nil];
@@ -297,7 +297,7 @@ var CPSharedDocumentController = nil;
var iter = [_documents objectEnumerator], var iter = [_documents objectEnumerator],
obj; obj;
while ((obj = [iter nextObject]) !== nil) while ((obj = [iter nextObject]) != nil)
{ {
if ([obj isDocumentEdited]) if ([obj isDocumentEdited])
return YES; return YES;
+1 -1
View File
@@ -608,7 +608,7 @@ var _CPEventPeriodicEventPeriod = 0,
*/ */
+ (void)stopPeriodicEvents + (void)stopPeriodicEvents
{ {
if (_CPEventPeriodicEventTimer === nil) if (_CPEventPeriodicEventTimer == nil)
return; return;
window.clearTimeout(_CPEventPeriodicEventTimer); window.clearTimeout(_CPEventPeriodicEventTimer);
-85
View File
@@ -1,85 +0,0 @@
/*
* CPFlashMovie.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
/*!
@ingroup appkit
@class CPFlashMovie
CPFlashMovie is used to represent a Flash movie in the Cappuccino framework.
*/
@implementation CPFlashMovie : CPObject
{
CPString _filename;
}
/*!
Creates a new Flash movie with the swf at \c aFileName.
@param aFilename the swf to load
@return the initialized CPFlashMovie
*/
+ (id)flashMovieWithFile:(CPString)aFilename
{
return [[self alloc] initWithFile:aFilename];
}
/*!
Initializes the Flash movie.
@param aFilename the swf to load
@return the initialized CPFlashMovie
*/
- (id)initWithFile:(CPString)aFilename
{
self = [super init];
if (self)
_filename = aFilename;
return self;
}
- (CPString)filename
{
return _filename;
}
@end
var CPFlashMovieFilenameKey = "CPFlashMovieFilenameKey";
@implementation CPFlashMovie (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
_filename = [aCoder decodeObjectForKey:CPFlashMovieFilenameKey];
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeObject:_filename forKey:CPFlashMovieFilenameKey];
}
@end
-212
View File
@@ -1,212 +0,0 @@
/*
* CPFlashView.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2008, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPFlashMovie.j"
@import "CPView.j"
var IEFlashCLSID = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
/*!
@ingroup appkit
*/
@implementation CPFlashView : CPView
{
CPFlashMovie _flashMovie;
CPDictionary _params;
CPDictionary _paramElements;
#if PLATFORM(DOM)
DOMElement _DOMParamElement;
DOMElement _DOMObjectElement;
#endif
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
#if PLATFORM(DOM)
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
{
_DOMObjectElement = document.createElement(@"object");
_DOMObjectElement.id = [self elementID];
_DOMObjectElement.width = @"100%";
_DOMObjectElement.height = @"100%";
_DOMObjectElement.style.top = @"0px";
_DOMObjectElement.style.left = @"0px";
_DOMObjectElement.type = @"application/x-shockwave-flash";
_DOMParamElement = document.createElement(@"param");
_DOMParamElement.name = @"movie";
_DOMObjectElement.appendChild(_DOMParamElement);
_DOMElement.appendChild(_DOMObjectElement);
}
else
[self _rebuildIEObjects];
#endif
}
return self;
}
- (void)setFlashMovie:(CPFlashMovie)aFlashMovie
{
if (_flashMovie == aFlashMovie)
return;
_flashMovie = aFlashMovie;
#if PLATFORM(DOM)
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
{
_DOMParamElement.value = [aFlashMovie filename];
_DOMObjectElement.data = [aFlashMovie filename];
}
else
[self _rebuildIEObjects];
#endif
}
- (CPFlashMovie)flashMovie
{
return _flashMovie;
}
- (void)setFlashVars:(CPDictionary)aDictionary
{
var varString = @"",
enumerator = [aDictionary keyEnumerator],
key;
if (key = [enumerator nextObject])
varString = [varString stringByAppendingFormat:@"%@=%@", key, [aDictionary objectForKey:key]];
while (key = [enumerator nextObject])
varString = [varString stringByAppendingFormat:@"&%@=%@", key, [aDictionary objectForKey:key]];
if (!_params)
_params = @{};
[_params setObject:varString forKey:@"flashvars"];
[self setParameters:_params];
}
- (CPDictionary)flashVars
{
return [_params objectForKey:@"flashvars"];
}
- (void)setParameters:(CPDictionary)aDictionary
{
#if PLATFORM(DOM)
if (_paramElements && !CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
{
var elements = [_paramElements allValues],
count = [elements count];
for (var i = 0; i < count; i++)
_DOMObjectElement.removeChild([elements objectAtIndex:i]);
}
#endif
if (!_params)
_params = aDictionary;
else
[_params addEntriesFromDictionary:aDictionary];
#if PLATFORM(DOM)
if (!CPBrowserIsEngine(CPInternetExplorerBrowserEngine))
{
_paramElements = @{};
var enumerator = [_params keyEnumerator],
key;
while (_DOMObjectElement && (key = [enumerator nextObject]) !== nil)
{
var param = document.createElement(@"param");
param.name = key;
param.value = [_params objectForKey:key];
_DOMObjectElement.appendChild(param);
[_paramElements setObject:param forKey:key];
}
}
else
[self _rebuildIEObjects];
#endif
}
- (CPDictionary)parameters
{
return _params;
}
#if PLATFORM(DOM)
- (void)_rebuildIEObjects
{
_DOMElement.innerHTML = @"";
if (![_flashMovie filename])
return;
var paramString = [CPString stringWithFormat:@"<param name='movie' value='%@' />", [_flashMovie filename]],
paramEnumerator = [_params keyEnumerator],
key;
while ((key = [paramEnumerator nextObject]) !== nil)
paramString = [paramString stringByAppendingFormat:@"<param name='%@' value='%@' />", key, [_params objectForKey:key]];
_DOMObjectElement = document.createElement(@"object");
_DOMElement.appendChild(_DOMObjectElement);
_DOMObjectElement.outerHTML = [CPString stringWithFormat:@"<object id=%@ classid=%@ width=%@ height=%@>%@</object>", [self elementID], IEFlashCLSID, CGRectGetWidth([self bounds]), CGRectGetHeight([self bounds]), paramString];
}
#endif
- (CPString)elementID
{
return @"CPFV_" + [self UID];
}
- (void)mouseMoved:(CPEvent)sommit
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
- (void)mouseDragged:(CPEvent)anEvent
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
- (void)mouseDown:(CPEvent)anEvent
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
- (void)mouseUp:(CPEvent)anEvent
{
[[[self window] platformWindow] _propagateCurrentDOMEvent:YES];
}
@end
+115 -11
View File
@@ -25,6 +25,8 @@
@import "CPView.j" @import "CPView.j"
@import "CPFontDescriptor.j" @import "CPFontDescriptor.j"
@import "_CPObject+Theme.j"
@import "CPControl.j"
CPFontDefaultSystemFontFace = @"Arial, sans-serif"; CPFontDefaultSystemFontFace = @"Arial, sans-serif";
CPFontDefaultSystemFontSize = 12; CPFontDefaultSystemFontSize = 12;
@@ -38,12 +40,15 @@ CPFontCurrentSystemSize = -1;
// For internal use only by this class and subclasses // For internal use only by this class and subclasses
_CPFontSystemFacePlaceholder = "_CPFontSystemFacePlaceholder"; _CPFontSystemFacePlaceholder = "_CPFontSystemFacePlaceholder";
var _CPFontCache = {}, var _CPFontCache = {},
_CPSystemFontCache = {}, _CPSystemFontCache = {},
_CPFontSystemFontFace = CPFontDefaultSystemFontFace, _CPFontSystemFontFace = CPFontDefaultSystemFontFace,
_CPFontSystemFontSize = 12, _CPFontSystemFontSize = CPFontDefaultSystemFontSize,
_CPFontFallbackFaces = CPFontDefaultSystemFontFace.split(", "), _CPFontSystemFontSizeSmall = CPFontDefaultSystemFontSize - 1,
_CPFontStripRegExp = new RegExp("(^\\s*[\"']?|[\"']?\\s*$)", "g"); _CPFontSystemFontSizeMini = CPFontDefaultSystemFontSize - 2,
_CPFontFallbackFaces = CPFontDefaultSystemFontFace.split(", "),
_CPFontStripRegExp = new RegExp("(^\\s*[\"']?|[\"']?\\s*$)", "g"),
_CPFontSystemFontFaceSpecified = NO;
#define _CPRealFontSize(aSize) (aSize <= 0 ? _CPFontSystemFontSize : aSize) #define _CPRealFontSize(aSize) (aSize <= 0 ? _CPFontSystemFontSize : aSize)
@@ -109,7 +114,7 @@ following:
<string>Asap</string> <string>Asap</string>
@endcode @endcode
*/ */
@implementation CPFont : CPObject @implementation CPFont : CPObject <CPTheme>
{ {
CPString _name; CPString _name;
float _size; float _size;
@@ -123,6 +128,22 @@ following:
CPString _cssString; CPString _cssString;
} }
+ (CPString)defaultThemeClass
{
return @"font";
}
+ (CPDictionary)themeAttributes
{
return @{
@"system-font-face": [CPNull null],
@"system-font-style": [CPNull null],
@"system-font-size-regular": [CPNull null],
@"system-font-size-small": [CPNull null],
@"system-font-size-mini": [CPNull null]
};
}
+ (void)initialize + (void)initialize
{ {
if (self !== [CPFont class]) if (self !== [CPFont class])
@@ -134,7 +155,10 @@ following:
systemFontFace = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontFace"]; systemFontFace = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontFace"];
if (systemFontFace) if (systemFontFace)
{
_CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace); _CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
_CPFontSystemFontFaceSpecified = YES;
}
var systemFontSize = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontSize"]; var systemFontSize = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPSystemFontSize"];
@@ -142,7 +166,62 @@ following:
systemFontSize = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontSize"]; systemFontSize = [[CPBundle bundleForClass:[CPView class]] objectForInfoDictionaryKey:@"CPSystemFontSize"];
if (systemFontSize) if (systemFontSize)
{
_CPFontSystemFontSize = systemFontSize; _CPFontSystemFontSize = systemFontSize;
_CPFontSystemFontFaceSpecified = YES;
}
}
+ (void)initializeSystemFontFromTheme:(CPTheme)aTheme
{
// If something was specified via +initialize (from an Info.plist file), don't do anything
if (_CPFontSystemFontFaceSpecified)
return;
// Reset all system font caches
_CPSystemFontCache = {};
// Now, try to get information from the theme
var systemFontFace = [aTheme valueForAttributeWithName:@"system-font-face" forClass:[CPFont class]];
if (systemFontFace)
{
[self _invalidateSystemFontCache];
_CPFontSystemFontFace = _CPFontNormalizedNames(systemFontFace);
}
var systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-regular" forClass:[CPFont class]];
if (systemFontSize)
{
[self _invalidateSystemFontCache];
_CPFontSystemFontSize = systemFontSize;
}
systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-small" forClass:[CPFont class]];
if (systemFontSize)
{
[self _invalidateSystemFontCache];
_CPFontSystemFontSizeSmall = systemFontSize;
}
systemFontSize = [aTheme valueForAttributeWithName:@"system-font-size-mini" forClass:[CPFont class]];
if (systemFontSize)
{
[self _invalidateSystemFontCache];
_CPFontSystemFontSizeMini = systemFontSize;
}
// Is there something to add to the global syle definition ?
var systemFontStyle = [aTheme valueForAttributeWithName:@"system-font-style" forClass:[CPFont class]];
if (systemFontStyle)
{
// Yes, so install it in the DOM Style element
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPathInString:systemFontStyle];
}
} }
/*! /*!
@@ -177,14 +256,13 @@ following:
+ (CPFont)systemFontForControlSize:(CPControlSize)aSize + (CPFont)systemFontForControlSize:(CPControlSize)aSize
{ {
// TODO These sizes should be themable or made less arbitrary in some other way.
switch (aSize) switch (aSize)
{ {
case CPSmallControlSize: case CPSmallControlSize:
return [self systemFontOfSize:_CPFontSystemFontSize - 1]; return [self systemFontOfSize:_CPFontSystemFontSizeSmall];
case CPMiniControlSize: case CPMiniControlSize:
return [self systemFontOfSize:_CPFontSystemFontSize - 2]; return [self systemFontOfSize:_CPFontSystemFontSizeMini];
case CPRegularControlSize: case CPRegularControlSize:
default: default:
@@ -315,6 +393,10 @@ following:
_isItalic = isItalic; _isItalic = isItalic;
_isSystem = isSystem; _isSystem = isSystem;
_theme = [CPTheme defaultTheme];
_themeState = CPThemeStateNormal;
[self _loadThemeAttributes];
if (isSystem) if (isSystem)
{ {
_name = aName; _name = aName;
@@ -432,6 +514,22 @@ following:
_lineHeight = [metrics objectForKey:@"lineHeight"]; _lineHeight = [metrics objectForKey:@"lineHeight"];
} }
- (CPControlSize)controlSizeCorrespondingToFontSize
{
switch (_size)
{
case _CPFontSystemFontSizeSmall:
return CPSmallControlSize;
case _CPFontSystemFontSizeMini:
return CPMiniControlSize;
default:
// If we can't find a corresponding size, return regular control size
return CPRegularControlSize;
}
}
@end @end
@implementation CPFont(DescriptorAdditions) @implementation CPFont(DescriptorAdditions)
@@ -492,7 +590,11 @@ var CPFontNameKey = @"CPFontNameKey",
isItalic = [aCoder decodeBoolForKey:CPFontIsItalicKey], isItalic = [aCoder decodeBoolForKey:CPFontIsItalicKey],
isSystem = [aCoder decodeBoolForKey:CPFontIsSystemKey]; isSystem = [aCoder decodeBoolForKey:CPFontIsSystemKey];
return [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem]; self = [self _initWithName:fontName size:size bold:isBold italic:isItalic system:isSystem];
[self _decodeThemeObjectsWithCoder:aCoder];
return self;
} }
/*! /*!
@@ -506,6 +608,8 @@ var CPFontNameKey = @"CPFontNameKey",
[aCoder encodeBool:_isBold forKey:CPFontIsBoldKey]; [aCoder encodeBool:_isBold forKey:CPFontIsBoldKey];
[aCoder encodeBool:_isItalic forKey:CPFontIsItalicKey]; [aCoder encodeBool:_isItalic forKey:CPFontIsItalicKey];
[aCoder encodeBool:_isSystem forKey:CPFontIsSystemKey]; [aCoder encodeBool:_isSystem forKey:CPFontIsSystemKey];
[self _encodeThemeObjectsWithCoder:aCoder];
} }
@end @end
+5 -30
View File
@@ -29,6 +29,8 @@
@global CPApp @global CPApp
@class CPFontPanel @class CPFontPanel
@global document
CPItalicFontMask = 1 << 0; CPItalicFontMask = 1 << 0;
CPBoldFontMask = 1 << 1; CPBoldFontMask = 1 << 1;
CPUnboldFontMask = 1 << 2; CPUnboldFontMask = 1 << 2;
@@ -190,35 +192,6 @@ CPRemoveTraitFontAction = 7;
return ([aFont isBold] ? CPBoldFontMask : 0) | ([aFont isItalic] ? CPItalicFontMask : 0); return ([aFont isBold] ? CPBoldFontMask : 0) | ([aFont isItalic] ? CPItalicFontMask : 0);
} }
- (CPFont)convertFont:(CPFont)aFont
{
if (!_activeChange)
return aFont;
var addTraits = [_activeChange valueForKey:@"addTraits"];
if (addTraits)
aFont = [self convertFont:aFont toHaveTrait:addTraits];
return aFont;
}
- (CPFont)convertFont:(CPFont)aFont toHaveTrait:(CPFontTraitMask)addTraits
{
if (!aFont)
return nil;
var shouldBeBold = ([aFont isBold] || (addTraits & CPBoldFontMask)) && !(addTraits & CPUnboldFontMask),
shouldBeItalic = ([aFont isItalic] || (addTraits & CPItalicFontMask)) && !(addTraits & CPUnitalicFontMask),
shouldBeSize = [aFont size];
// XXX On the current platform there will always be a bold/italic version of each font, but still leave
// || aFont in here for future platforms.
aFont = [CPFont _fontWithName:[aFont familyName] size:shouldBeSize bold:shouldBeBold italic:shouldBeItalic] || aFont;
return aFont;
}
- (CPFont)convertFont:(CPFont)aFont toFace:(CPString)aTypeface - (CPFont)convertFont:(CPFont)aFont toFace:(CPString)aTypeface
{ {
if (!aFont) if (!aFont)
@@ -236,7 +209,7 @@ CPRemoveTraitFontAction = 7;
- (@action)addFontTrait:(id)sender - (@action)addFontTrait:(id)sender
{ {
var tag = [sender tag]; var tag = [sender tag];
_activeChange = tag === nil ? @{} : @{ @"addTraits": tag }; _activeChange = tag == nil ? @{} : @{ @"addTraits": tag };
_fontAction = CPAddTraitFontAction; _fontAction = CPAddTraitFontAction;
[self sendAction]; [self sendAction];
@@ -386,6 +359,7 @@ CPRemoveTraitFontAction = 7;
- (CPFont)convertFont:(CPFont)aFont - (CPFont)convertFont:(CPFont)aFont
{ {
var newFont = nil; var newFont = nil;
switch (_fontAction) switch (_fontAction)
{ {
case CPNoFontChangeAction: case CPNoFontChangeAction:
@@ -398,6 +372,7 @@ CPRemoveTraitFontAction = 7;
case CPAddTraitFontAction: case CPAddTraitFontAction:
newFont = aFont; newFont = aFont;
if (!_activeChange) if (!_activeChange)
break; break;
+1 -1
View File
@@ -92,7 +92,7 @@ var CPGraphicsContextCurrent = nil,
@param aGraphicsPort the graphics port to initialize with @param aGraphicsPort the graphics port to initialize with
@return the initialized context @return the initialized context
*/ */
- (id)initWithGraphicsPort:(CPContext)aGraphicsPort - (id)initWithGraphicsPort:(CGContext)aGraphicsPort
{ {
self = [super init]; self = [super init];
+202 -5
View File
@@ -26,10 +26,14 @@
@import <Foundation/CPRunLoop.j> @import <Foundation/CPRunLoop.j>
@import <Foundation/CPString.j> @import <Foundation/CPString.j>
@import <Foundation/CPData.j> @import <Foundation/CPData.j>
@import <Foundation/CPKeyedArchiver.j>
@import <Foundation/CPKeyedUnarchiver.j>
@import "CGGeometry.j" @import "CGGeometry.j"
@import "CPCompatibility.j" @import "CPCompatibility.j"
@class CPColor
@global document
@protocol CPImageDelegate <CPObject> @protocol CPImageDelegate <CPObject>
@@ -85,9 +89,7 @@ function CPImageInBundle()
if (typeof(arguments[1]) === "number") if (typeof(arguments[1]) === "number")
{ {
if (arguments[1] !== nil && arguments[1] !== undefined) size = CGSizeMake(arguments[1], arguments[2]);
size = CGSizeMake(arguments[1], arguments[2]);
bundle = arguments[3]; bundle = arguments[3];
} }
else if (typeof(arguments[1]) === "object") else if (typeof(arguments[1]) === "object")
@@ -161,7 +163,7 @@ function CPAppKitImage(aFilename, aSize)
- (id)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize - (id)initByReferencingFile:(CPString)aFilename size:(CGSize)aSize
{ {
// Quietly return nil like in Cocoa, rather than crashing later. // Quietly return nil like in Cocoa, rather than crashing later.
if (aFilename === undefined || aFilename === nil) if (aFilename == nil)
return nil; return nil;
self = [super init]; self = [super init];
@@ -446,6 +448,11 @@ function CPAppKitImage(aFilename, aSize)
return NO; return NO;
} }
- (BOOL)isMaterialIconImage
{
return NO;
}
- (CPString)description - (CPString)description
{ {
var filename = [self filename], var filename = [self filename],
@@ -564,6 +571,7 @@ function CPAppKitImage(aFilename, aSize)
CPDictionary _cssDictionary @accessors(property=cssDictionary); CPDictionary _cssDictionary @accessors(property=cssDictionary);
CPDictionary _cssBeforeDictionary @accessors(property=cssBeforeDictionary); CPDictionary _cssBeforeDictionary @accessors(property=cssBeforeDictionary);
CPDictionary _cssAfterDictionary @accessors(property=cssAfterDictionary); CPDictionary _cssAfterDictionary @accessors(property=cssAfterDictionary);
CGSize _displaySize;
} }
+ (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary size:(CGSize)aSize + (CPImage)imageWithCSSDictionary:(CPDictionary)aDictionary size:(CGSize)aSize
@@ -582,6 +590,16 @@ function CPAppKitImage(aFilename, aSize)
return [[CPImage alloc] initWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:nil size:aSize]; return [[CPImage alloc] initWithCSSDictionary:@{} beforeDictionary:nil afterDictionary:nil size:aSize];
} }
+ (CPImage)imageWithMaterialIconNamed:(CPString)iconName size:(CGSize)size
{
return [_CPMaterialIconImage imageWithIconNamed:iconName size:size];
}
+ (CPImage)imageWithMaterialIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color
{
return [_CPMaterialIconImage imageWithIconNamed:iconName size:size color:color];
}
- (id)initWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary size:(CGSize)aSize - (id)initWithCSSDictionary:(CPDictionary)aDictionary beforeDictionary:(CPDictionary)beforeDictionary afterDictionary:(CPDictionary)afterDictionary size:(CGSize)aSize
{ {
self = [super init]; self = [super init];
@@ -594,6 +612,7 @@ function CPAppKitImage(aFilename, aSize)
_cssDictionary = aDictionary; _cssDictionary = aDictionary;
_cssBeforeDictionary = beforeDictionary; _cssBeforeDictionary = beforeDictionary;
_cssAfterDictionary = afterDictionary; _cssAfterDictionary = afterDictionary;
_displaySize = CGSizeMakeCopy(aSize);
} }
return self; return self;
@@ -685,7 +704,7 @@ function CPAppKitImage(aFilename, aSize)
aStyleNode.replaceChild(styleDescription, aStyleNode.firstChild); aStyleNode.replaceChild(styleDescription, aStyleNode.firstChild);
} }
[aView setDOMClassName:@"CP"+[aView UID]]; aDOMElement.className = @"CP"+[aView UID];
} }
else else
{ {
@@ -707,6 +726,11 @@ function CPAppKitImage(aFilename, aSize)
#endif #endif
} }
- (BOOL)_shouldBeResized
{
return NO;
}
@end @end
var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey", var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
@@ -750,6 +774,168 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
@end @end
#pragma mark -
@implementation _CPMaterialIconImage : CPImage
{
CPMutableDictionary _cachedColorVersions;
CPColor _cachedInvertedColor;
}
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size
{
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size];
}
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color
{
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size color:color];
}
+ (_CPMaterialIconImage)imageWithIconNamed:(CPString)iconName size:(CGSize)size color:(CPColor)color additionalCSSDictionary:(CPDictionary)additionalCSSDictionary
{
return [[_CPMaterialIconImage alloc] initWithIconName:iconName size:size color:color additionalCSSDictionary:additionalCSSDictionary];
}
- (CPDictionary)_baseMaterialIconCSSDictionaryForIconName:(CPString)iconName size:(CGSize)size
{
return @{
@"width": size.width + @"px",
@"height": size.height + @"px",
@"top": @"0px",
@"left": @"0px",
@"content": @"'" + iconName + @"'",
@"position": @"absolute",
@"z-index": @"300",
@"font-family": @"'Material Icons'",
@"font-weight": @"normal",
@"font-style": @"normal",
@"font-size": MIN(size.width, size.height) + @"px",
@"display": @"inline-block",
@"line-height": @"1",
@"text-transform": @"none",
@"letter-spacing": @"normal",
@"word-wrap": @"normal",
@"white-space": @"nowrap",
@"direction": @"ltr",
@"-webkit-font-smoothing": @"antialiased",
@"text-rendering": @"optimizeLegibility",
@"-moz-osx-font-smoothing": @"grayscale",
@"font-feature-settings": @"'liga'"
};
}
- (_CPMaterialIconImage)initWithIconName:(CPString)iconName size:(CGSize)size
{
return [super initWithCSSDictionary:@{}
beforeDictionary:@{}
afterDictionary:[self _baseMaterialIconCSSDictionaryForIconName:iconName size:size]
size:size];
}
- (_CPMaterialIconImage)initWithIconName:(CPString)iconName size:(CGSize)size color:(CPColor)color
{
var materialIconCSSDictionary = [self _baseMaterialIconCSSDictionaryForIconName:iconName size:size];
[materialIconCSSDictionary setObject:[color cssString] forKey:@"color"];
return [super initWithCSSDictionary:@{}
beforeDictionary:@{}
afterDictionary:materialIconCSSDictionary
size:size];
}
- (void)addRotationEffectWithAngle:(float)angle
{
[self addCSSDictionary:@{
@"transform": @"rotate("+angle+"deg)",
@"transition": @"transform 0.35s ease"
}];
}
- (void)addCSSDictionary:(CPDictionary)additionalCSSDictionary
{
[_cssAfterDictionary addEntriesFromDictionary:additionalCSSDictionary];
}
- (void)setSize:(CGSize)aSize
{
[self _setDisplaySize:aSize];
[super setSize:aSize];
}
- (void)_setDisplaySize:(CGSize)aSize
{
if (CGSizeEqualToSize(_displaySize, aSize))
return;
_displaySize = CGSizeMakeCopy(aSize);
[_cssAfterDictionary setObject:(aSize.width + @"px") forKey:@"width"];
[_cssAfterDictionary setObject:(aSize.height + @"px") forKey:@"height"];
[_cssAfterDictionary setObject:(MIN(aSize.width, aSize.height) + @"px") forKey:@"font-size"];
}
- (BOOL)_shouldBeResized
{
return YES;
}
- (BOOL)isMaterialIconImage
{
return YES;
}
- (_CPMaterialIconImage)invertedImage
{
if (!_cachedInvertedColor)
{
var sourceCSSColor = [_cssAfterDictionary objectForKey:@"color"] || @"rgba(0,0,0,1)",
sourceColor = [CPColor colorWithCSSString:sourceCSSColor];
_cachedInvertedColor = [CPColor colorWithRed:(1-[sourceColor redComponent])
green:(1-[sourceColor greenComponent])
blue:(1-[sourceColor blueComponent])
alpha:[sourceColor alphaComponent]];
}
return [self imageVersionWithColor:_cachedInvertedColor];
}
- (_CPMaterialIconImage)imageVersionWithColor:(CPColor)aColor
{
// We can't just set the color in the cssAfterDictionary as this would not be noticed as a new image,
// so -setImage won't do anything, so no visual refresh won't occur.
// The trick here is to keep in cache a clone of this image for each needed color.
var colorCSSString = [aColor cssString];
if (!_cachedColorVersions)
_cachedColorVersions = @{};
var cachedColorVersion = [_cachedColorVersions objectForKey:colorCSSString];
if (!cachedColorVersion)
{
cachedColorVersion = [self duplicate];
[cachedColorVersion _setCSSColor:colorCSSString];
[_cachedColorVersions setObject:cachedColorVersion forKey:colorCSSString];
}
return cachedColorVersion;
}
- (void)_setCSSColor:(CPString)aCSSColor
{
[_cssAfterDictionary setObject:aCSSColor forKey:@"color"];
}
@end
#pragma mark -
@implementation CPThreePartImage : CPObject @implementation CPThreePartImage : CPObject
{ {
CPArray _imageSlices; CPArray _imageSlices;
@@ -890,3 +1076,14 @@ var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
} }
@end @end
#pragma mark -
@implementation CPImage (Duplication)
- (CPImage)duplicate
{
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:self]];
}
@end
+16 -2
View File
@@ -29,6 +29,8 @@
@global CPImagesPboardType @global CPImagesPboardType
@global appkit_tag_dom_elements @global appkit_tag_dom_elements
@global document
@typedef CPImageAlignment @typedef CPImageAlignment
CPImageAlignCenter = 0; CPImageAlignCenter = 0;
CPImageAlignTop = 1; CPImageAlignTop = 1;
@@ -99,8 +101,8 @@ var CPImageViewEmptyPlaceholderImage = nil;
{ {
#if PLATFORM(DOM) #if PLATFORM(DOM)
var image = [self objectValue], var image = [self objectValue],
isCSSBasedImage = [image isCSSBased], isCSSBasedImage = [image isCSSBased],
isIMGImageElement = _DOMImageElement && (_DOMImageElement.nodeName == "IMG"); isIMGImageElement = _DOMImageElement && (_DOMImageElement.nodeName == "IMG");
// First, check if we need to destroy a current DOM image element. This is the case if : // First, check if we need to destroy a current DOM image element. This is the case if :
// - we have one but not the right one (that is a DIV but needing an IMG, and vice versa) // - we have one but not the right one (that is a DIV but needing an IMG, and vice versa)
@@ -444,6 +446,18 @@ var CPImageViewEmptyPlaceholderImage = nil;
#endif #endif
} }
#if PLATFORM(DOM)
if ([image isCSSBased] && [image _shouldBeResized])
{
[image _setDisplaySize:CGSizeMake(ROUND(width), ROUND(height))];
_cssStyleNode = [image applyCSSImageForView:self
onDOMElement:_DOMImageElement
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
}
#endif
_imageRect = CGRectMake(x, y, width, height); _imageRect = CGRectMake(x, y, width, height);
if (_hasShadow) if (_hasShadow)
+3 -3
View File
@@ -293,7 +293,7 @@ var CPBindingOperationAnd = 0,
// If the value is nil AND the source doesn't respond to setPlaceholderString: then // If the value is nil AND the source doesn't respond to setPlaceholderString: then
// we set the value to the placeholder. Otherwise, we do not want to short cut the process // we set the value to the placeholder. Otherwise, we do not want to short cut the process
// of setting the placeholder that is based on the fact that the value is nil. // of setting the placeholder that is based on the fact that the value is nil.
if ((aValue === undefined || aValue === nil || aValue === [CPNull null]) if ((aValue == nil || aValue === [CPNull null])
&& ![_source respondsToSelector:@selector(setPlaceholderString:)]) && ![_source respondsToSelector:@selector(setPlaceholderString:)])
aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil; aValue = [options objectForKey:CPNullPlaceholderBindingOption] || nil;
@@ -653,7 +653,7 @@ var CPBindingOperationAnd = 0,
keyPath = [info objectForKey:CPObservedKeyPathKey], keyPath = [info objectForKey:CPObservedKeyPathKey],
value = [object valueForKeyPath:keyPath]; value = [object valueForKeyPath:keyPath];
if (value === nil || value === undefined) if (value == nil)
{ {
[_source setEnabled:NO]; [_source setEnabled:NO];
return; return;
@@ -796,7 +796,7 @@ var CPBindingOperationAnd = 0,
else else
value = [theBinding transformValue:value withOptions:options]; value = [theBinding transformValue:value withOptions:options];
if (value === nil || value === undefined) if (value == nil)
value = @""; value = @"";
result.value = result.value.replace("%{" + _patternPlaceholder + count + "}@", [value description]); result.value = result.value.replace("%{" + _patternPlaceholder + count + "}@", [value description]);
+1 -6
View File
@@ -22,15 +22,10 @@
@import "CPControl.j" @import "CPControl.j"
@import "CPWindow_Constants.j" @import "CPWindow_Constants.j"
@import "CPSlider.j"
@global CPApp @global CPApp
@typedef CPTickMarkPosition
CPTickMarkBelow = 0;
CPTickMarkAbove = 1;
CPTickMarkLeft = CPTickMarkAbove;
CPTickMarkRight = CPTickMarkBelow;
@typedef CPLevelIndicatorStyle @typedef CPLevelIndicatorStyle
CPRelevancyLevelIndicatorStyle = 0; CPRelevancyLevelIndicatorStyle = 0;
CPContinuousCapacityLevelIndicatorStyle = 1; CPContinuousCapacityLevelIndicatorStyle = 1;
+33 -2
View File
@@ -164,7 +164,7 @@ var _CPMenuBarVisible = NO,
+ (void)_setOrRemoveMenuBarAttribute:(id)aValue forKey:(id)aKey + (void)_setOrRemoveMenuBarAttribute:(id)aValue forKey:(id)aKey
{ {
if (aValue === nil) if (aValue == nil)
[_CPMenuBarAttributes removeObjectForKey:aKey]; [_CPMenuBarAttributes removeObjectForKey:aKey];
else else
[_CPMenuBarAttributes setObject:aValue forKey:aKey]; [_CPMenuBarAttributes setObject:aValue forKey:aKey];
@@ -1131,7 +1131,7 @@ var _CPMenuBarVisible = NO,
} }
} }
- (void)_menuWithName:(CPString)aName - (CPMenu)_menuWithName:(CPString)aName
{ {
if (aName === _name) if (aName === _name)
return self; return self;
@@ -1293,6 +1293,37 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
@end @end
#pragma mark -
@implementation CPMenu (CSSTheming)
+ (void)setNamedValue:(CPString)aName forKey:(CPString)aKey inAttributes:(CPDictionary)aDictionary forTheme:(CPTheme)aTheme
{
var value = [aTheme valueForAttributeWithName:aName forClass:_CPMenuView];
if (value)
[aDictionary setObject:value forKey:aKey];
else
[aDictionary removeObjectForKey:aKey];
}
+ (void)updateMenuBarAttributesWithTheme:(CPTheme)aTheme
{
var newAttributes = @{};
[CPMenu setNamedValue:@"menu-bar-text-color" forKey:@"CPMenuBarTextColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-title-color" forKey:@"CPMenuBarTitleColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-text-shadow-color" forKey:@"CPMenuBarTextShadowColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-title-shadow-color" forKey:@"CPMenuBarTitleShadowColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-highlight-color" forKey:@"CPMenuBarHighlightColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-highlight-text-color" forKey:@"CPMenuBarHighlightTextColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setNamedValue:@"menu-bar-highlight-text-shadow-color" forKey:@"CPMenuBarHighlightTextShadowColor" inAttributes:newAttributes forTheme:aTheme];
[CPMenu setMenuBarAttributes:newAttributes];
}
@end
@import "_CPMenuBarWindow.j" @import "_CPMenuBarWindow.j"
@import "_CPMenuWindow.j" @import "_CPMenuWindow.j"
+18 -3
View File
@@ -25,11 +25,14 @@
@class _CPMenuView @class _CPMenuView
@class CPMenu @class CPMenu
@class CPMenuItem
@global CPMenuDidAddItemNotification @global CPMenuDidAddItemNotification
@global CPMenuDidChangeItemNotification @global CPMenuDidChangeItemNotification
@global CPMenuDidRemoveItemNotification @global CPMenuDidRemoveItemNotification
@global document
@implementation _CPMenuBarWindow : CPPanel @implementation _CPMenuBarWindow : CPPanel
{ {
CPView _highlightView; CPView _highlightView;
@@ -129,10 +132,12 @@
- (void)setColor:(CPColor)aColor - (void)setColor:(CPColor)aColor
{ {
var targetView = [[CPTheme defaultTheme] valueForAttributeWithName:@"css-based" forClass:CPView] ? [[self contentView] superview] : [self contentView];
if (!aColor) if (!aColor)
[[self contentView] setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-background-color" forClass:_CPMenuView]]; [targetView setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-background-color" forClass:_CPMenuView]];
else else
[[self contentView] setBackgroundColor:aColor]; [targetView setBackgroundColor:aColor];
} }
- (void)setTextColor:(CPColor)aColor - (void)setTextColor:(CPColor)aColor
@@ -277,6 +282,16 @@
[menuItemView setTextColor:_textColor]; [menuItemView setTextColor:_textColor];
[menuItemView setHidden:[item isHidden]]; [menuItemView setHidden:[item isHidden]];
// If first menu item has tag -1 and if there is a special theme value menu-bar-window-first-item-font,
// set the corresponding font. This is used to set bold on the first item of the menubar (à la Cocoa)
if ((index == 0) && ([item tag] == -1))
{
var firstItemFont = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-first-item-font" forClass:_CPMenuView];
if (firstItemFont)
[item setFont:firstItemFont];
}
[menuItemView synchronizeWithMenuItem]; [menuItemView synchronizeWithMenuItem];
[contentView addSubview:menuItemView]; [contentView addSubview:menuItemView];
@@ -350,7 +365,7 @@
- (CPFont)font - (CPFont)font
{ {
[CPFont systemFontOfSize:[CPFont systemFontSize]]; return [CPFont systemFontOfSize:[CPFont systemFontSize]];
} }
- (void)tile - (void)tile
+39 -26
View File
@@ -28,6 +28,8 @@
@class CPWindow @class CPWindow
@class _CPMenuWindow @class _CPMenuWindow
@class _CPMenuView
@class CPMenuItem
@global CPApp @global CPApp
@@ -223,7 +225,7 @@ var STICKY_TIME_INTERVAL = 0.4,
_lastGlobalLocation = globalLocation; _lastGlobalLocation = globalLocation;
// If the item isn't enabled its as if we clicked on nothing. // If the item isn't enabled its as if we clicked on nothing.
if (![activeItem isEnabled] || [activeItem _isMenuBarButton]) if ([activeItem _isMenuBarButton])
{ {
activeItemIndex = CPNotFound; activeItemIndex = CPNotFound;
activeItem = nil; activeItem = nil;
@@ -352,7 +354,23 @@ var STICKY_TIME_INTERVAL = 0.4,
if ([activeMenuContainer isMenuBar]) if ([activeMenuContainer isMenuBar])
newMenuOrigin = CGPointMake(CGRectGetMinX(activeItemRect), CGRectGetMaxY(activeItemRect)); newMenuOrigin = CGPointMake(CGRectGetMinX(activeItemRect), CGRectGetMaxY(activeItemRect));
else else
newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect), CGRectGetMinY(activeItemRect)); {
// New theme attributes to have more precise submenus positioning
var defaultTheme = [CPTheme defaultTheme],
themeDeltaX = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-delta-x" forClass:_CPMenuView],
themeDeltaY = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-delta-y" forClass:_CPMenuView],
themeFirstDeltaY = [defaultTheme valueForAttributeWithName:@"menu-window-submenu-first-level-delta-y" forClass:_CPMenuView],
activeMenuIndex = [_menuContainerStack indexOfObject:activeMenuContainer],
deltaX = themeDeltaX ? themeDeltaX : 0,
deltaY = themeDeltaY ? themeDeltaY : 0;
if (themeFirstDeltaY && (activeMenuIndex == 1) && [_menuContainerStack[0] isMenuBar])
deltaY += themeFirstDeltaY;
newMenuOrigin = CGPointMake(CGRectGetMaxX(activeItemRect)+deltaX, CGRectGetMinY(activeItemRect)+deltaY);
}
newMenuOrigin = [activeMenuContainer convertBaseToGlobal:newMenuOrigin]; newMenuOrigin = [activeMenuContainer convertBaseToGlobal:newMenuOrigin];
@@ -553,7 +571,7 @@ var STICKY_TIME_INTERVAL = 0.4,
var iter = [selectorNames objectEnumerator], var iter = [selectorNames objectEnumerator],
obj; obj;
while ((obj = [iter nextObject]) !== nil) while ((obj = [iter nextObject]) != nil)
{ {
var aSelector = CPSelectorFromString(obj); var aSelector = CPSelectorFromString(obj);
@@ -580,6 +598,8 @@ var STICKY_TIME_INTERVAL = 0.4,
[self selectNextItemBeginningWith:_keyBuffer inMenu:menu]; [self selectNextItemBeginningWith:_keyBuffer inMenu:menu];
_lastGlobalLocation = nil; _lastGlobalLocation = nil;
} }
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO];
} }
- (void)selectNextItemBeginningWith:(CPString)characters inMenu:(CPMenu)menu - (void)selectNextItemBeginningWith:(CPString)characters inMenu:(CPMenu)menu
@@ -587,7 +607,7 @@ var STICKY_TIME_INTERVAL = 0.4,
var iter = [[menu itemArray] objectEnumerator], var iter = [[menu itemArray] objectEnumerator],
obj; obj;
while ((obj = [iter nextObject]) !== nil) while ((obj = [iter nextObject]) != nil)
{ {
if ([obj isHidden] || ![obj isEnabled]) if ([obj isHidden] || ![obj isEnabled])
continue; continue;
@@ -725,39 +745,32 @@ var STICKY_TIME_INTERVAL = 0.4,
- (void)moveDown:(CPMenu)menu - (void)moveDown:(CPMenu)menu
{ {
var index = menu._highlightedIndex + 1; var index = menu._highlightedIndex + 1,
item;
// Search for the next enabled item
while ((index < [menu numberOfItems]) && (item = [menu itemAtIndex:index]) && ([item isSeparatorItem] || [item isHidden] || ![item isEnabled]))
index++;
if (index < [menu numberOfItems]) if (index < [menu numberOfItems])
{
[menu _highlightItemAtIndex:index]; [menu _highlightItemAtIndex:index];
var item = [menu highlightedItem];
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
[self moveDown:menu];
}
else if (menu == [CPApp mainMenu]) else if (menu == [CPApp mainMenu])
[menu _highlightItemAtIndex:0]; [menu _highlightItemAtIndex:0];
} }
- (void)moveUp:(CPMenu)menu - (void)moveUp:(CPMenu)menu
{ {
var index = menu._highlightedIndex - 1; var index = menu._highlightedIndex - 1,
item;
if (index < 0) // Search for the previous enabled item
{ while ((index >= 0) && (item = [menu itemAtIndex:index]) && ([item isSeparatorItem] || [item isHidden] || ![item isEnabled]))
if (index != CPNotFound || menu == [CPApp mainMenu]) index--;
[menu _highlightItemAtIndex:[menu numberOfItems] - 1];
return; if (index >= 0)
} [menu _highlightItemAtIndex:index];
else if (menu == [CPApp mainMenu])
[menu _highlightItemAtIndex:index]; [menu _highlightItemAtIndex:[menu numberOfItems] - 1];
var item = [menu highlightedItem];
if ([item isSeparatorItem] || [item isHidden] || ![item isEnabled])
[self moveUp:menu];
} }
- (void)insertNewline:(CPMenu)menu - (void)insertNewline:(CPMenu)menu
+36
View File
@@ -26,6 +26,7 @@
@import "_CPMenuManager.j" @import "_CPMenuManager.j"
@class _CPMenuView @class _CPMenuView
@class CPMenuItem
var _CPMenuWindowPool = [], var _CPMenuWindowPool = [],
_CPMenuWindowPoolCapacity = 5, _CPMenuWindowPoolCapacity = 5,
@@ -419,6 +420,21 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end @end
#pragma mark -
@implementation _CPMenuWindow (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[[self contentView] _setThemeIncludingDescendants:aTheme];
[_menuView _setThemeIncludingDescendants:aTheme];
[_menuView tile];
}
@end
#pragma mark -
/* /*
@ignore @ignore
*/ */
@@ -449,6 +465,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@"menu-bar-window-background-color": [CPNull null], @"menu-bar-window-background-color": [CPNull null],
@"menu-bar-window-background-selected-color": [CPNull null], @"menu-bar-window-background-selected-color": [CPNull null],
@"menu-bar-window-font": [CPNull null], @"menu-bar-window-font": [CPNull null],
@"menu-bar-window-first-item-font": [CPNull null],
@"menu-bar-window-height": 30.0, @"menu-bar-window-height": 30.0,
@"menu-bar-window-margin": 10.0, @"menu-bar-window-margin": 10.0,
@"menu-bar-window-left-margin": 10.0, @"menu-bar-window-left-margin": 10.0,
@@ -466,6 +483,9 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@"menu-general-icon-new": [CPNull null], @"menu-general-icon-new": [CPNull null],
@"menu-general-icon-save": [CPNull null], @"menu-general-icon-save": [CPNull null],
@"menu-general-icon-open": [CPNull null], @"menu-general-icon-open": [CPNull null],
@"menu-window-submenu-delta-x": 0.0,
@"menu-window-submenu-delta-y": 0.0,
@"menu-window-submenu-first-level-delta-y": 0.0
}; };
} }
@@ -552,6 +572,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[view setFrameOrigin:CGPointMake(0.0, y)]; [view setFrameOrigin:CGPointMake(0.0, y)];
[view _setThemeIncludingDescendants:[CPTheme defaultTheme]];
[self addSubview:view]; [self addSubview:view];
var size = [view minSize], var size = [view minSize],
@@ -582,3 +603,18 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
} }
@end @end
#pragma mark -
@implementation _CPMenuView (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[_menuItemViews makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
for (var i = 0, items = [[self menu] itemArray], count = [items count]; i < count; i++)
[[items[i] _menuItemView] _setThemeIncludingDescendants:aTheme];
}
@end
+23 -3
View File
@@ -897,6 +897,20 @@ CPControlKeyMask
@end @end
#pragma mark -
@implementation CPMenuItem (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[_view _setThemeIncludingDescendants:aTheme];
[_menuItemView _setThemeIncludingDescendants:aTheme];
}
@end
#pragma mark -
var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey", var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
CPMenuItemTitleKey = @"CPMenuItemTitleKey", CPMenuItemTitleKey = @"CPMenuItemTitleKey",
@@ -911,6 +925,9 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
CPMenuItemImageKey = @"CPMenuItemImageKey", CPMenuItemImageKey = @"CPMenuItemImageKey",
CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey", CPMenuItemAlternateImageKey = @"CPMenuItemAlternateImageKey",
CPMenuItemOnStateImageKey = @"CPMenuItemOnStateImageKey",
CPMenuItemOffStateImageKey = @"CPMenuItemOffStateImageKey",
CPMenuItemMixedStateImageKey = @"CPMenuItemMixedStateImageKey",
CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey", CPMenuItemSubmenuKey = @"CPMenuItemSubmenuKey",
CPMenuItemMenuKey = @"CPMenuItemMenuKey", CPMenuItemMenuKey = @"CPMenuItemMenuKey",
@@ -955,9 +972,9 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
_image = [aCoder decodeObjectForKey:CPMenuItemImageKey]; _image = [aCoder decodeObjectForKey:CPMenuItemImageKey];
_alternateImage = [aCoder decodeObjectForKey:CPMenuItemAlternateImageKey]; _alternateImage = [aCoder decodeObjectForKey:CPMenuItemAlternateImageKey];
// CPImage _onStateImage; _onStateImage = [aCoder decodeObjectForKey:CPMenuItemOnStateImageKey];
// CPImage _offStateImage; _offStateImage = [aCoder decodeObjectForKey:CPMenuItemOffStateImageKey];
// CPImage _mixedStateImage; _mixedStateImage = [aCoder decodeObjectForKey:CPMenuItemMixedStateImageKey];
// This order matters because setSubmenu: needs _menu to be around. // This order matters because setSubmenu: needs _menu to be around.
_menu = [aCoder decodeObjectForKey:CPMenuItemMenuKey]; _menu = [aCoder decodeObjectForKey:CPMenuItemMenuKey];
@@ -1003,6 +1020,9 @@ var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
ENCODE_IFNOT(CPMenuItemImageKey, _image, nil); ENCODE_IFNOT(CPMenuItemImageKey, _image, nil);
ENCODE_IFNOT(CPMenuItemAlternateImageKey, _alternateImage, nil); ENCODE_IFNOT(CPMenuItemAlternateImageKey, _alternateImage, nil);
ENCODE_IFNOT(CPMenuItemOnStateImageKey, _onStateImage, nil);
ENCODE_IFNOT(CPMenuItemOffStateImageKey, _offStateImage, nil);
ENCODE_IFNOT(CPMenuItemMixedStateImageKey, _mixedStateImage, nil);
ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil); ENCODE_IFNOT(CPMenuItemSubmenuKey, _submenu, nil);
ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil); ENCODE_IFNOT(CPMenuItemMenuKey, _menu, nil);
+19 -4
View File
@@ -31,7 +31,9 @@
+ (id)view + (id)view
{ {
return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 10.0)]; var themedHeight = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-view-height" forClass:_CPMenuItemStandardView];
return [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, (themedHeight ? themedHeight : 10.0))];
} }
- (id)initWithFrame:(CGRect)aFrame - (id)initWithFrame:(CGRect)aFrame
@@ -47,12 +49,25 @@
- (void)drawRect:(CGRect)aRect - (void)drawRect:(CGRect)aRect
{ {
var context = [[CPGraphicsContext currentContext] graphicsPort], var context = [[CPGraphicsContext currentContext] graphicsPort],
bounds = [self bounds]; bounds = [self bounds],
height = CGRectGetMaxY(bounds),
themedHeight = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-height" forClass:_CPMenuItemStandardView],
lineHeight = themedHeight ? themedHeight : 1.0;
CGContextBeginPath(context); CGContextBeginPath(context);
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5); CGContextSetLineWidth(context, lineHeight);
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
if (!!((height - lineHeight) % 2))
{
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)) - 0.5);
}
else
{
CGContextMoveToPoint(context, CGRectGetMinX(bounds), FLOOR(CGRectGetMidY(bounds)));
CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), FLOOR(CGRectGetMidY(bounds)));
}
CGContextSetStrokeColor(context, [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-color" forClass:_CPMenuItemStandardView]); CGContextSetStrokeColor(context, [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-item-separator-color" forClass:_CPMenuItemStandardView]);
CGContextStrokePath(context); CGContextStrokePath(context);
+96 -21
View File
@@ -41,6 +41,8 @@
_CPImageAndTextView _imageAndTextView; _CPImageAndTextView _imageAndTextView;
_CPImageAndTextView _keyEquivalentView; _CPImageAndTextView _keyEquivalentView;
CPView _submenuIndicatorView; CPView _submenuIndicatorView;
BOOL _hasSubmenuIndicatorImage;
} }
+ (CPString)defaultThemeClass + (CPString)defaultThemeClass
@@ -63,12 +65,17 @@
@"menu-item-default-mixed-state-image": [CPNull null], @"menu-item-default-mixed-state-image": [CPNull null],
@"menu-item-default-mixed-state-highlighted-image": [CPNull null], @"menu-item-default-mixed-state-highlighted-image": [CPNull null],
@"menu-item-separator-color": [CPNull null], @"menu-item-separator-color": [CPNull null],
@"menu-item-separator-height": 1.0,
@"menu-item-separator-view-height": 10.0,
@"left-margin": 3.0, @"left-margin": 3.0,
@"right-margin": 17.0, @"right-margin": 17.0,
@"state-column-width": 14.0, @"state-column-width": 14.0,
@"indentation-width": 17.0, @"indentation-width": 17.0,
@"vertical-margin": 4.0, @"vertical-margin": 4.0,
@"vertical-offset": 0.0,
@"right-columns-margin": 30.0, @"right-columns-margin": 30.0,
@"submenu-indicator-image": [CPNull null],
@"submenu-indicator-highlighted-image": [CPNull null]
}; };
} }
@@ -91,6 +98,7 @@
_stateView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)]; _stateView = [[CPImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 0.0, 0.0)];
[_stateView setImageScaling:CPImageScaleNone]; [_stateView setImageScaling:CPImageScaleNone];
[_stateView setImageAlignment:CPImageAlignCenter];
[self addSubview:_stateView]; [self addSubview:_stateView];
@@ -109,9 +117,24 @@
[self addSubview:_keyEquivalentView]; [self addSubview:_keyEquivalentView];
_submenuIndicatorView = [[_CPMenuItemSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 8.0, 10.0)]; // Do we have a submenu indicator image specified in the theme ?
_hasSubmenuIndicatorImage = !![self valueForThemeAttribute:@"submenu-indicator-image"];
if (_hasSubmenuIndicatorImage)
{
// Yes, then use an imageView
_submenuIndicatorView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[_submenuIndicatorView setImageAlignment:CPImageAlignCenter];
}
else
{
// No, then use self drawing _CPMenuItemSubmenuIndicatorView
_submenuIndicatorView = [[_CPMenuItemSubmenuIndicatorView alloc] initWithFrame:CGRectMake(0.0, 0.0, 8.0, 10.0)];
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
}
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
[_submenuIndicatorView setAutoresizingMask:CPViewMinXMargin]; [_submenuIndicatorView setAutoresizingMask:CPViewMinXMargin];
[self addSubview:_submenuIndicatorView]; [self addSubview:_submenuIndicatorView];
@@ -149,11 +172,24 @@
_font = aFont; _font = aFont;
} }
- (CPFont)font
{
// Menu item font is forced local font or _menuItem font or system font
return _font || [_menuItem font] || [CPFont systemFontOfSize:CPFontCurrentSystemSize];
}
// FIXME: update is called 2 times at each display. Find why and fix.
- (void)update - (void)update
{ {
var x = [self valueForThemeAttribute:@"left-margin"] + [_menuItem indentationLevel] * [self valueForThemeAttribute:@"indentation-width"], var x = [self valueForThemeAttribute:@"left-margin"] + [_menuItem indentationLevel] * [self valueForThemeAttribute:@"indentation-width"],
height = 0.0, height = 0.0,
hasStateColumn = [[_menuItem menu] showsStateColumn]; hasStateColumn = [[_menuItem menu] showsStateColumn],
myFont = [self font],
// When possible, use specific vertical margin/offset value based on font size (which could have been set by control size)
correspondingControlSize = [myFont controlSizeCorrespondingToFontSize],
verticalMargin = [self valueForThemeAttribute:@"vertical-margin" inState:CPControlSizeThemeStates[correspondingControlSize]],
verticalOffset = [self valueForThemeAttribute:@"vertical-offset" inState:CPControlSizeThemeStates[correspondingControlSize]];
if (hasStateColumn) if (hasStateColumn)
{ {
@@ -163,27 +199,32 @@
switch ([_menuItem state]) switch ([_menuItem state])
{ {
case CPOnState: case CPOnState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image"]]; [_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
case CPOffState: case CPOffState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image"]]; [_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
case CPMixedState: case CPMixedState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image"]]; [_stateView setImage:[_menuItem mixedStateImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
default: default:
break; break;
} }
var stateViewFrameOrigin = [_stateView frameOrigin];
stateViewFrameOrigin.x = x;
[_stateView setFrameOrigin:stateViewFrameOrigin];
x += [self valueForThemeAttribute:@"state-column-width"]; x += [self valueForThemeAttribute:@"state-column-width"];
} }
else else
[_stateView setHidden:YES]; [_stateView setHidden:YES];
[_imageAndTextView setFont:[_menuItem font] || _font]; [_imageAndTextView setFont:myFont];
[_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment]; [_imageAndTextView setVerticalAlignment:CPCenterVerticalTextAlignment];
[_imageAndTextView setImage:[_menuItem image]]; [_imageAndTextView setImage:[_menuItem image]];
[_imageAndTextView setText:[_menuItem title]]; [_imageAndTextView setText:[_menuItem title]];
@@ -196,7 +237,7 @@
imageAndTextViewFrame.origin.x = x; imageAndTextViewFrame.origin.x = x;
x += CGRectGetWidth(imageAndTextViewFrame); x += CGRectGetWidth(imageAndTextViewFrame);
height = MAX(height, CGRectGetHeight(imageAndTextViewFrame)); height = MAX(height, CGRectGetHeight(imageAndTextViewFrame)); // FIXME: here, height = 0 -> MAX useless
var hasKeyEquivalent = !![_menuItem keyEquivalent], var hasKeyEquivalent = !![_menuItem keyEquivalent],
hasSubmenu = [_menuItem hasSubmenu]; hasSubmenu = [_menuItem hasSubmenu];
@@ -206,14 +247,14 @@
if (hasKeyEquivalent) if (hasKeyEquivalent)
{ {
[_keyEquivalentView setFont:[_menuItem font] || _font]; [_keyEquivalentView setFont:myFont];
[_keyEquivalentView setVerticalAlignment:CPCenterVerticalTextAlignment]; [_keyEquivalentView setVerticalAlignment:CPCenterVerticalTextAlignment];
[_keyEquivalentView setImage:[_menuItem image]]; [_keyEquivalentView setImage:[_menuItem image]];
[_keyEquivalentView setText:[_menuItem keyEquivalentStringRepresentation]]; [_keyEquivalentView setText:[_menuItem keyEquivalentStringRepresentation]];
[_keyEquivalentView setTextColor:[self textColor]]; [_keyEquivalentView setTextColor:[self textColor]];
[_keyEquivalentView setTextShadowColor:[self textShadowColor]]; [_keyEquivalentView setTextShadowColor:[self textShadowColor]];
[_keyEquivalentView setTextShadowOffset:CGSizeMake(0, 1)]; [_keyEquivalentView setTextShadowOffset:CGSizeMake(0, 1)];
[_keyEquivalentView setFrameOrigin:CGPointMake(x, [self valueForThemeAttribute:@"vertical-margin"])]; [_keyEquivalentView setFrameOrigin:CGPointMake(x, verticalMargin)];
[_keyEquivalentView sizeToFit]; [_keyEquivalentView sizeToFit];
var keyEquivalentViewFrame = [_keyEquivalentView frame]; var keyEquivalentViewFrame = [_keyEquivalentView frame];
@@ -230,6 +271,14 @@
if (hasSubmenu) if (hasSubmenu)
{ {
if (_hasSubmenuIndicatorImage)
{
var submenuIndicatorImage = [self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]];
[_submenuIndicatorView setImage:submenuIndicatorImage];
[_submenuIndicatorView setFrameSize:[submenuIndicatorImage size]];
}
[_submenuIndicatorView setHidden:NO]; [_submenuIndicatorView setHidden:NO];
var submenuViewFrame = [_submenuIndicatorView frame]; var submenuViewFrame = [_submenuIndicatorView frame];
@@ -242,9 +291,9 @@
else else
[_submenuIndicatorView setHidden:YES]; [_submenuIndicatorView setHidden:YES];
height += 2.0 * [self valueForThemeAttribute:@"vertical-margin"]; height += 2.0 * verticalMargin;
imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0); imageAndTextViewFrame.origin.y = FLOOR((height - CGRectGetHeight(imageAndTextViewFrame)) / 2.0) + verticalOffset;
[_imageAndTextView setFrame:imageAndTextViewFrame]; [_imageAndTextView setFrame:imageAndTextViewFrame];
if (hasStateColumn) if (hasStateColumn)
@@ -252,7 +301,7 @@
if (hasKeyEquivalent) if (hasKeyEquivalent)
{ {
keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0); keyEquivalentViewFrame.origin.y = FLOOR((height - CGRectGetHeight(keyEquivalentViewFrame)) / 2.0) + verticalOffset;
[_keyEquivalentView setFrame:keyEquivalentViewFrame]; [_keyEquivalentView setFrame:keyEquivalentViewFrame];
} }
@@ -277,6 +326,8 @@
_highlighted = shouldHighlight; _highlighted = shouldHighlight;
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize];
[_imageAndTextView setTextColor:[self textColor]]; [_imageAndTextView setTextColor:[self textColor]];
[_keyEquivalentView setTextColor:[self textColor]]; [_keyEquivalentView setTextColor:[self textColor]];
[_imageAndTextView setTextShadowColor:[self textShadowColor]]; [_imageAndTextView setTextShadowColor:[self textShadowColor]];
@@ -286,13 +337,21 @@
{ {
[self setBackgroundColor:[self valueForThemeAttribute:@"menu-item-selection-color"]]; [self setBackgroundColor:[self valueForThemeAttribute:@"menu-item-selection-color"]];
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]]; [_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
[_submenuIndicatorView setColor:[self textColor]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
else
[_submenuIndicatorView setColor:[self textColor]];
} }
else else
{ {
[self setBackgroundColor:nil]; [self setBackgroundColor:nil];
[_imageAndTextView setImage:[_menuItem image]]; [_imageAndTextView setImage:[_menuItem image]];
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
else
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
} }
if ([[_menuItem menu] showsStateColumn]) if ([[_menuItem menu] showsStateColumn])
@@ -302,15 +361,15 @@
switch ([_menuItem state]) switch ([_menuItem state])
{ {
case CPOnState: case CPOnState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image"]]; [_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
case CPOffState: case CPOffState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image"]]; [_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
case CPMixedState: case CPMixedState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image"]]; [_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
default: default:
@@ -322,15 +381,15 @@
switch ([_menuItem state]) switch ([_menuItem state])
{ {
case CPOnState: case CPOnState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image"]]; [_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
case CPOffState: case CPOffState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image"]]; [_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
case CPMixedState: case CPMixedState:
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image"]]; [_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
break; break;
default: default:
@@ -347,6 +406,22 @@
@end @end
#pragma mark -
@implementation _CPMenuItemStandardView (CSSTheming)
#pragma mark Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[[self subviews] makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
}
@end
#pragma mark -
@implementation _CPMenuItemSubmenuIndicatorView : CPView @implementation _CPMenuItemSubmenuIndicatorView : CPView
{ {
CPColor _color; CPColor _color;
+25
View File
@@ -252,6 +252,31 @@
@end @end
#pragma mark -
@implementation _CPMenuItemView (CSSTheming)
#pragma mark Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[_view _setThemeIncludingDescendants:aTheme];
// Items must also perform this (without this, only the selected item does)
[_imageAndTextView _setThemeIncludingDescendants:aTheme];
[_submenuView _setThemeIncludingDescendants:aTheme];
[[_menuItem view] _setThemeIncludingDescendants:aTheme];
if ([_view respondsToSelector:@selector(update)])
[_view update];
}
@end
#pragma mark -
@implementation _CPMenuItemArrowView : CPView @implementation _CPMenuItemArrowView : CPView
{ {
CPColor _color; CPColor _color;
+24 -2
View File
@@ -384,13 +384,35 @@
*/ */
- (void)_selectionDidChange - (void)_selectionDidChange
{ {
if (_selection === undefined || _selection === nil) if (_selection == nil)
_selection = [[CPControllerSelectionProxy alloc] initWithController:self]; _selection = [[CPControllerSelectionProxy alloc] initWithController:self];
[_selection controllerDidChange]; [_selection controllerDidChange];
[self didChangeValueForKey:@"selection"]; [self didChangeValueForKey:@"selection"];
} }
/*!
@ignore
These two private methods map CPTextField notifications to the CPEditorRegistration protocol
This should be generalized in the future:
The CPEditorRegistrationProtocol can be implemented in all controls that support editing, not just CPTextField.
In CPArrayController there are other cases than selection change when we need to review all editor pending changes. They should be covered, including the selection change, by the wider concept described by the methods commitEditing: (forces to end editing) and discardEditing: (pending changes are lost).
*/
- (void)_objectDidBeginEditing:(CPNotification)notification
{
[self objectDidBeginEditing:[notification object]];
}
/*!
@ignore
*/
- (void) _objectDidEndEditing:(CPNotification)notification
{
[self objectDidEndEditing:[notification object]];
}
/*! /*!
@return id - Returns the keys which are being observed. @return id - Returns the keys which are being observed.
*/ */
@@ -770,7 +792,7 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
} }
} }
if (value === nil || value.isa && [value isEqual:[CPNull null]]) if (value == nil || value.isa && [value isEqual:[CPNull null]])
value = CPNullMarker; value = CPNullMarker;
return value; return value;
+4 -4
View File
@@ -1438,7 +1438,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
_shouldRetargetChildIndex = YES; _shouldRetargetChildIndex = YES;
// set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex // set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex
var retargetedItemInfo = (_retargetedItem !== nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo; var retargetedItemInfo = (_retargetedItem != nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo;
if (_retargedChildIndex === [retargetedItemInfo.children count]) if (_retargedChildIndex === [retargetedItemInfo.children count])
{ {
@@ -1881,7 +1881,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
@end @end
@implementation _CPOutlineViewTableViewDataSource : CPObject @implementation _CPOutlineViewTableViewDataSource : CPObject <CPTableViewDataSource>
{ {
CPObject _outlineView; CPObject _outlineView;
} }
@@ -1940,7 +1940,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
if (theDropOperation === CPTableViewDropAbove) if (theDropOperation === CPTableViewDropAbove)
{ {
var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset], var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset],
itemInfo = (parentItem !== nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo, itemInfo = (parentItem != nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo,
children = itemInfo.children; children = itemInfo.children;
childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]]; childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]];
@@ -2011,7 +2011,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0,
@end @end
@implementation _CPOutlineViewTableViewDelegate : CPObject @implementation _CPOutlineViewTableViewDelegate : CPObject <CPTableViewDelegate>
{ {
CPOutlineView _outlineView; CPOutlineView _outlineView;
} }
+1
View File
@@ -46,6 +46,7 @@ CPImagesPboardType = @"CPImagesPboardType";
CPVideosPboardType = @"CPVideosPboardType"; CPVideosPboardType = @"CPVideosPboardType";
CPRTFPboardType = @"CPRTFPboardType"; CPRTFPboardType = @"CPRTFPboardType";
_CPSmartPboardType = @"_CPSmartPboardType"; _CPSmartPboardType = @"_CPSmartPboardType";
_CPASPboardType = @"_CPASPboardType";
UTF8PboardType = @"public.utf8-plain-text"; UTF8PboardType = @"public.utf8-plain-text";
+21 -4
View File
@@ -45,6 +45,13 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
return "popup-button"; return "popup-button";
} }
+ (CPDictionary)themeAttributes
{
return @{
@"menu-offset": CGSizeMake(0, 0)
};
}
+ (CPSet)keyPathsForValuesAffectingSelectedIndex + (CPSet)keyPathsForValuesAffectingSelectedIndex
{ {
return [CPSet setWithObject:@"objectValue"]; return [CPSet setWithObject:@"objectValue"];
@@ -491,9 +498,17 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
if (index < 0) if (index < 0)
{ {
[self addItemWithTitle:aTitle];
index = [self numberOfItems] - 1; // this ist to match cocoa where setting an empty string does not add it but simply clears the title
// and sets objectValue to -1
if (aTitle === '')
[self selectItemAtIndex:-1];
else
{
[self addItemWithTitle:aTitle];
index = [self numberOfItems] - 1;
}
} }
[self selectItemAtIndex:index]; [self selectItemAtIndex:index];
@@ -677,14 +692,16 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
if ([self pullsDown]) if ([self pullsDown])
{ {
var positionedItem = nil, var positionedItem = nil,
location = CGPointMake(0.0, CGRectGetMaxY(bounds) - 1); menuOffset = [self currentValueForThemeAttribute:@"menu-offset"],
location = CGPointMake(menuOffset.width, CGRectGetMaxY(bounds) + menuOffset.height);
} }
else else
{ {
var contentRect = [self contentRectForBounds:bounds], var contentRect = [self contentRectForBounds:bounds],
positionedItem = [self selectedItem], positionedItem = [self selectedItem],
standardLeftMargin = [_CPMenuWindow _standardLeftMargin] + [_CPMenuItemStandardView _standardLeftMargin], standardLeftMargin = [_CPMenuWindow _standardLeftMargin] + [_CPMenuItemStandardView _standardLeftMargin],
location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin, 0.0); menuOffset = [self currentValueForThemeAttribute:@"menu-offset"],
location = CGPointMake(CGRectGetMinX(contentRect) - standardLeftMargin + menuOffset.width, menuOffset.height);
minimumWidth += standardLeftMargin; minimumWidth += standardLeftMargin;
+108 -10
View File
@@ -67,6 +67,18 @@ CPRadioImageOffset = 4.0;
[[button1 radioGroup] selectedRadio] returns the currently selected [[button1 radioGroup] selectedRadio] returns the currently selected
option. option.
UPDATE 09/2020 : Implementation of modern Cocoa behavior :
As in Cocoa, radio buttons grouping is now automatic.
To be associated in a common group (and so being mutually exclusive),
radio buttons must combine 2 criteria :
- same superview (enclosing view)
- same action
TODO: This first implementation uses "as is" CPRadioGroup. This could
be simplified (no more need for radio group action, for example)
*/ */
@implementation CPRadio : CPButton @implementation CPRadio : CPButton
{ {
@@ -160,6 +172,71 @@ CPRadioImageOffset = 4.0;
[CPApp sendAction:[_radioGroup action] to:[_radioGroup target] from:_radioGroup]; [CPApp sendAction:[_radioGroup action] to:[_radioGroup target] from:_radioGroup];
} }
- (void)viewDidMoveToSuperview
{
[self _setRadioGroup];
[super viewDidMoveToSuperview];
}
- (void)setAction:(SEL)anAction
{
if (anAction === _action)
return;
[super setAction:anAction];
[self _setRadioGroup];
}
#pragma mark Private methods
- (void)_setRadioGroup
{
// Implementation of modern Cocoa behavior : automatic radio group
// If no action is set or no superview, no grouping can be done.
if (![self action] || ![self superview])
{
// If this radio is in a group (size > 1), remove it.
if ([[self radioGroup] size] > 1)
{
[self setRadioGroup:[CPRadioGroup new]];
if ([self state] === CPOnState)
[_radioGroup _setSelectedRadio:self];
}
return;
}
// Search in superview subviews for other radio buttons having the same action.
// Take the one with the radio group having the greatest number of members.
var radioGroup;
for (var i = 0, superviewSubviews = [[self superview] subviews], count = [superviewSubviews count], aSubview, myAction = [self action], radioGroupSize = -1; (i < count); i++)
{
aSubview = superviewSubviews[i];
if ([aSubview isKindOfClass:CPRadio] && (aSubview !== self) && ([aSubview action] === myAction) && ([[aSubview radioGroup] size] > radioGroupSize))
{
radioGroup = [aSubview radioGroup];
radioGroupSize = [radioGroup size];
}
}
if (radioGroup)
[self setRadioGroup:radioGroup];
else
// No other radio buttons to group with found.
// It may be because this radio button was in a radio group and its action was changed.
// If this is the case, we must reisolate it in a new radio group.
if ([_radioGroup size] > 1)
[self setRadioGroup:[CPRadioGroup new]];
if ([self state] === CPOnState)
[_radioGroup _setSelectedRadio:self];
}
@end @end
var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey"; var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
@@ -183,21 +260,37 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
[aCoder encodeObject:_radioGroup forKey:CPRadioRadioGroupKey]; [aCoder encodeObject:_radioGroup forKey:CPRadioRadioGroupKey];
} }
- (CPImage)image #pragma mark -
#pragma mark Override methods from CPButton
- (CPThemeState)_contentVisualState
{ {
return [self currentValueForThemeAttribute:@"image"]; // Note : Behavior differs from CPButton as title doesn't follow the highlightsBy content flag
var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
currentState = [self state];
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
} }
- (CPImage)alternateImage - (CPThemeState)_imageVisualState
{ {
return [self currentValueForThemeAttribute:@"image"]; // Note : Behavior differs from CPButton as we don't force "not selected" theme state
} // when button is highglighted and selected
- (BOOL)startTrackingAt:(CGPoint)aPoint var visualState = [self themeState] || CPThemeStateNormal, // Needed during theme compilation
{ currentState = [self state];
var startedTracking = [super startTrackingAt:aPoint];
[self highlight:YES]; if (_isHighlighted && (_highlightsBy & CPContentsCellMask))
return startedTracking; visualState = visualState.and(CPThemeStateHighlighted);
if ((currentState !== CPOffState) && (_showsStateBy & CPContentsCellMask))
visualState = visualState.and((currentState === CPOnState) ? CPThemeStateSelected : CPButtonStateMixed);
return visualState;
} }
@end @end
@@ -304,6 +397,11 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
return _radios; return _radios;
} }
- (int)size
{
return [_radios count];
}
- (void)setEnabled:(BOOL)enabled - (void)setEnabled:(BOOL)enabled
{ {
[_radios makeObjectsPerformSelector:@selector(setEnabled:) withObject:enabled]; [_radios makeObjectsPerformSelector:@selector(setEnabled:) withObject:enabled];
+1 -1
View File
@@ -397,7 +397,7 @@ var CPResponderNextResponderKey = @"CPResponderNextResponderKey",
- (void)encodeWithCoder:(CPCoder)aCoder - (void)encodeWithCoder:(CPCoder)aCoder
{ {
// This will come out nil on the other side with decodeObjectForKey: // This will come out nil on the other side with decodeObjectForKey:
if (_nextResponder !== nil) if (_nextResponder != nil)
[aCoder encodeConditionalObject:_nextResponder forKey:CPResponderNextResponderKey]; [aCoder encodeConditionalObject:_nextResponder forKey:CPResponderNextResponderKey];
[aCoder encodeObject:_menu forKey:CPResponderMenuKey]; [aCoder encodeObject:_menu forKey:CPResponderMenuKey];
+20 -20
View File
@@ -157,7 +157,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
- (id)initWithFrame:(CGRect)frame - (id)initWithFrame:(CGRect)frame
{ {
self = [super initWithFrame:frame]; self = [super initWithFrame:frame];
if (self !== nil) if (self)
{ {
_slices = [[CPMutableArray alloc] init]; _slices = [[CPMutableArray alloc] init];
@@ -419,7 +419,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/ */
- (void)setFormattingStringsFilename:(CPString)stringsFilename - (void)setFormattingStringsFilename:(CPString)stringsFilename
{ {
if (_standardLocalizer === nil) if (_standardLocalizer == nil)
_standardLocalizer = [_CPRuleEditorLocalizer new]; _standardLocalizer = [_CPRuleEditorLocalizer new];
if (_stringsFilename !== stringsFilename) if (_stringsFilename !== stringsFilename)
@@ -427,14 +427,14 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
// Convert an empty string to nil // Convert an empty string to nil
_stringsFilename = stringsFilename || nil; _stringsFilename = stringsFilename || nil;
if (stringsFilename !== nil) if (stringsFilename != nil)
{ {
if (![stringsFilename hasSuffix:@".strings"]) if (![stringsFilename hasSuffix:@".strings"])
stringsFilename = stringsFilename + @".strings"; stringsFilename = stringsFilename + @".strings";
var path = [[CPBundle mainBundle] pathForResource:stringsFilename]; var path = [[CPBundle mainBundle] pathForResource:stringsFilename];
if (path !== nil) if (path != nil)
[_standardLocalizer loadContentOfURL:[CPURL URLWithString:path]]; [_standardLocalizer loadContentOfURL:[CPURL URLWithString:path]];
} }
} }
@@ -465,7 +465,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/ */
- (void)setCriteria:(CPArray)criteria andDisplayValues:(CPArray)values forRowAtIndex:(int)rowIndex - (void)setCriteria:(CPArray)criteria andDisplayValues:(CPArray)values forRowAtIndex:(int)rowIndex
{ {
if (criteria === nil || values === nil) if (criteria == nil || values == nil)
[CPException raise:CPInvalidArgumentException reason:_cmd + @". criteria and values parameters must not be nil."]; [CPException raise:CPInvalidArgumentException reason:_cmd + @". criteria and values parameters must not be nil."];
if (rowIndex < 0 || rowIndex >= [self numberOfRows]) if (rowIndex < 0 || rowIndex >= [self numberOfRows])
@@ -852,7 +852,7 @@ TODO: implement
while (current_index !== CPNotFound) while (current_index !== CPNotFound)
{ {
var subpredicate = [self predicateForRow:current_index]; var subpredicate = [self predicateForRow:current_index];
if (subpredicate !== nil) if (subpredicate != nil)
[subpredicates addObject:subpredicate]; [subpredicates addObject:subpredicate];
current_index = [subrowsIndexes indexGreaterThanIndex:current_index]; current_index = [subrowsIndexes indexGreaterThanIndex:current_index];
@@ -888,33 +888,33 @@ TODO: implement
modifier = [predicateParts objectForKey:CPRuleEditorPredicateComparisonModifier], modifier = [predicateParts objectForKey:CPRuleEditorPredicateComparisonModifier],
selector = CPSelectorFromString([predicateParts objectForKey:CPRuleEditorPredicateCustomSelector]); selector = CPSelectorFromString([predicateParts objectForKey:CPRuleEditorPredicateCustomSelector]);
if (lhs === nil) if (lhs == nil)
{ {
CPLogConsole(@"missing left expression in predicate parts dictionary"); CPLogConsole(@"missing left expression in predicate parts dictionary");
return NULL; return NULL;
} }
if (rhs === nil) if (rhs == nil)
{ {
CPLogConsole(@"missing right expression in predicate parts dictionary"); CPLogConsole(@"missing right expression in predicate parts dictionary");
return NULL; return NULL;
} }
if (selector === nil && operator === nil) if (selector == nil && operator == nil)
{ {
CPLogConsole(@"missing operator and selector in predicate parts dictionary"); CPLogConsole(@"missing operator and selector in predicate parts dictionary");
return NULL; return NULL;
} }
if (modifier === nil) if (modifier == nil)
CPLogConsole(@"missing modifier in predicate parts dictionary. Setting default: CPDirectPredicateModifier"); CPLogConsole(@"missing modifier in predicate parts dictionary. Setting default: CPDirectPredicateModifier");
if (options === nil) if (options == nil)
CPLogConsole(@"missing options in predicate parts dictionary. Setting default: CPCaseInsensitivePredicateOption"); CPLogConsole(@"missing options in predicate parts dictionary. Setting default: CPCaseInsensitivePredicateOption");
try try
{ {
if (selector !== nil) if (selector != nil)
predicate = [CPComparisonPredicate predicateWithLeftExpression:lhs predicate = [CPComparisonPredicate predicateWithLeftExpression:lhs
rightExpression:rhs rightExpression:rhs
customSelector:selector customSelector:selector
@@ -1167,7 +1167,7 @@ TODO: implement
- (BOOL)_wantsRowAnimations - (BOOL)_wantsRowAnimations
{ {
return (_currentAnimation !== nil); return (_currentAnimation != nil);
} }
- (void)_updateButtonVisibilities - (void)_updateButtonVisibilities
@@ -1753,7 +1753,7 @@ TODO: implement
{ {
var subpredicate = [self predicateForRow:current_index]; var subpredicate = [self predicateForRow:current_index];
if (subpredicate !== nil) if (subpredicate != nil)
[subpredicates addObject:subpredicate]; [subpredicates addObject:subpredicate];
current_index = [subindexes indexGreaterThanIndex:current_index]; current_index = [subindexes indexGreaterThanIndex:current_index];
@@ -1795,7 +1795,7 @@ TODO: implement
startRect = [aslice frame], startRect = [aslice frame],
startIndex = [aslice rowIndex] - 1; startIndex = [aslice rowIndex] - 1;
if ([aslice superview] === nil) if ([aslice superview] == nil)
{ {
startRect = CGRectMake(0, startIndex * _sliceHeight, CGRectGetWidth(startRect), _sliceHeight); startRect = CGRectMake(0, startIndex * _sliceHeight, CGRectGetWidth(startRect), _sliceHeight);
[aslice _reconfigureSubviews]; [aslice _reconfigureSubviews];
@@ -2129,7 +2129,7 @@ TODO: implement
- (BOOL)_dragShouldBeginFromMouseDown:(CPView)view - (BOOL)_dragShouldBeginFromMouseDown:(CPView)view
{ {
return (([self nestingMode] === CPRuleEditorNestingModeList || [view rowIndex] !== 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows === nil); return (([self nestingMode] === CPRuleEditorNestingModeList || [view rowIndex] !== 0) && _editable && [view isKindOfClass:[_CPRuleEditorViewSliceRow class]] && _draggingRows == nil);
} }
- (BOOL)_performDragForSlice:(id)slice withEvent:(CPEvent)event - (BOOL)_performDragForSlice:(id)slice withEvent:(CPEvent)event
@@ -2321,7 +2321,7 @@ TODO: implement
- (void)_postRowCountChangedNotificationOfType:(CPString)notificationName indexes:indexes - (void)_postRowCountChangedNotificationOfType:(CPString)notificationName indexes:indexes
{ {
var userInfo = indexes === nil ? @{} : @{ "indexes": indexes }; var userInfo = indexes == nil ? @{} : @{ "indexes": indexes };
[[CPNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:userInfo]; [[CPNotificationCenter defaultCenter] postNotificationName:notificationName object:self userInfo:userInfo];
} }
@@ -2386,7 +2386,7 @@ TODO: implement
var criteria = [self criteriaForRow:aRow]; var criteria = [self criteriaForRow:aRow];
indexofCriterion = [criteria indexOfObject:criterion]; indexofCriterion = [criteria indexOfObject:criterion];
if (parentItem !== nil if (parentItem != nil
&& indexofCriterion !== CPNotFound && indexofCriterion !== CPNotFound
&& indexofCriterion < [criteria count] - 1) && indexofCriterion < [criteria count] - 1)
{ {
@@ -2469,7 +2469,7 @@ var CPRuleEditorAlignmentGridWidthKey = @"CPRuleEditorAlignmentGridWidth",
- (id)initWithCoder:(CPCoder)coder - (id)initWithCoder:(CPCoder)coder
{ {
self = [super initWithCoder:coder]; self = [super initWithCoder:coder];
if (self !== nil) if (self)
{ {
[self setFormattingStringsFilename:[coder decodeObjectForKey:CPRuleEditorStringsFilenameKey]]; [self setFormattingStringsFilename:[coder decodeObjectForKey:CPRuleEditorStringsFilenameKey]];
_alignmentGridWidth = [coder decodeFloatForKey:CPRuleEditorAlignmentGridWidthKey]; _alignmentGridWidth = [coder decodeFloatForKey:CPRuleEditorAlignmentGridWidthKey];
@@ -2553,7 +2553,7 @@ var CriteriaKey = @"criteria",
- (id)initWithCoder:(CPCoder)coder - (id)initWithCoder:(CPCoder)coder
{ {
self = [super init]; self = [super init];
if (self !== nil) if (self)
{ {
subrows = [coder decodeObjectForKey:SubrowsKey]; subrows = [coder decodeObjectForKey:SubrowsKey];
criteria = [coder decodeObjectForKey:CriteriaKey]; criteria = [coder decodeObjectForKey:CriteriaKey];
@@ -131,7 +131,7 @@
{ {
var title = [self title]; var title = [self title];
if (title !== nil) if (title != nil)
return title; return title;
return [self templateView]; return [self templateView];
+4 -4
View File
@@ -40,7 +40,7 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
- (void)reloadIfNeeded - (void)reloadIfNeeded
{ {
if (connection !== nil) // Connection waiting if (connection != nil) // Connection waiting
{ {
connection = nil; connection = nil;
@@ -51,7 +51,7 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
- (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)rawString - (void)connection:(CPURLConnection)aConnection didReceiveData:(CPString)rawString
{ {
if (connection !== nil && rawString !== nil) if (connection != nil && rawString != nil)
[self loadContent:rawString]; [self loadContent:rawString];
connection = nil; connection = nil;
@@ -83,11 +83,11 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
{ {
[self reloadIfNeeded]; [self reloadIfNeeded];
if (_dictionary !== nil && aString !== nil) if (_dictionary != nil && aString != nil)
{ {
var localized = [_dictionary objectForKey:aString]; var localized = [_dictionary objectForKey:aString];
if (localized !== nil) if (localized != nil)
return localized; return localized;
} }
+103 -24
View File
@@ -30,6 +30,8 @@
@import "CPScroller.j" @import "CPScroller.j"
@import "CPView.j" @import "CPView.j"
@class CPTableView
#define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden]) #define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden])
@@ -147,7 +149,7 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
var globalValue = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPScrollersGlobalStyle"]; var globalValue = [[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPScrollersGlobalStyle"];
if (globalValue === nil || globalValue === -1) if (globalValue == nil || globalValue === -1)
CPScrollerStyleGlobal = _isBrowserUsingOverlayScrollers() ? CPScrollerStyleOverlay : CPScrollerStyleLegacy CPScrollerStyleGlobal = _isBrowserUsingOverlayScrollers() ? CPScrollerStyleOverlay : CPScrollerStyleLegacy
else else
CPScrollerStyleGlobal = globalValue; CPScrollerStyleGlobal = globalValue;
@@ -166,33 +168,65 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
}; };
} }
/*! Deprecated
*/
+ (CGSize)contentSizeForFrameSize:(CGSize)frameSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType + (CGSize)contentSizeForFrameSize:(CGSize)frameSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType
{ {
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, frameSize.width, frameSize.height) borderType:borderType], return [self contentSizeForFrameSize:frameSize
scrollerWidth = [CPScroller scrollerWidth]; horizontalScrollerClass:hFlag ? [CPScroller class] : nil
verticalScrollerClass:vFlag ? [CPScroller class] : nil
borderType:borderType
controlSize:CPRegularControlSize
scrollerStyle:CPScrollerStyleGlobal];
}
if (hFlag) + (CGSize)contentSizeForFrameSize:(CGSize)frameSize
bounds.size.height -= scrollerWidth; horizontalScrollerClass:(Class)horizontalScrollerClass
verticalScrollerClass:(Class)verticalScrollerClass
borderType:(CPBorderType)borderType
controlSize:(CPControlSize)controlSize
scrollerStyle:(CPScrollerStyle)scrollerStyle
{
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, frameSize.width, frameSize.height) borderType:borderType];
if (vFlag) if (horizontalScrollerClass)
bounds.size.width -= scrollerWidth; bounds.size.height -= [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
if (verticalScrollerClass)
bounds.size.width -= [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
return bounds.size; return bounds.size;
} }
/*! Deprecated
*/
+ (CGSize)frameSizeForContentSize:(CGSize)contentSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType + (CGSize)frameSizeForContentSize:(CGSize)contentSize hasHorizontalScroller:(BOOL)hFlag hasVerticalScroller:(BOOL)vFlag borderType:(CPBorderType)borderType
{
return [self frameSizeForContentSize:contentSize
horizontalScrollerClass:hFlag ? [CPScroller class] : nil
verticalScrollerClass:vFlag ? [CPScroller class] : nil
borderType:borderType
controlSize:CPRegularControlSize
scrollerStyle:CPScrollerStyleGlobal];
}
+ (CGSize)frameSizeForContentSize:(CGSize)contentSize
horizontalScrollerClass:(Class)horizontalScrollerClass
verticalScrollerClass:(Class)verticalScrollerClass
borderType:(CPBorderType)borderType
controlSize:(CPControlSize)controlSize
scrollerStyle:(CPScrollerStyle)scrollerStyle
{ {
var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, contentSize.width, contentSize.height) borderType:borderType], var bounds = [self _insetBounds:CGRectMake(0.0, 0.0, contentSize.width, contentSize.height) borderType:borderType],
widthInset = contentSize.width - bounds.size.width, widthInset = contentSize.width - bounds.size.width,
heightInset = contentSize.height - bounds.size.height, heightInset = contentSize.height - bounds.size.height,
frameSize = CGSizeMake(contentSize.width + widthInset, contentSize.height + heightInset), frameSize = CGSizeMake(contentSize.width + widthInset, contentSize.height + heightInset);
scrollerWidth = [CPScroller scrollerWidth];
if (hFlag) if (hFlag)
frameSize.height += scrollerWidth; frameSize.height += [horizontalScrollerClass scrollerWidthInStyle:scrollerStyle];
if (vFlag) if (vFlag)
frameSize.width += scrollerWidth; frameSize.width += [verticalScrollerClass scrollerWidthForControlSize:scrollerStyle];
return frameSize; return frameSize;
} }
@@ -220,7 +254,7 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
/*! /*!
Get the system wide scroller style. Get the system wide scroller style.
*/ */
+ (int)globalScrollerStyle + (CPScrollerStyle)globalScrollerStyle
{ {
return CPScrollerStyleGlobal; return CPScrollerStyleGlobal;
} }
@@ -230,7 +264,7 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
@param aStyle the scroller style to set all scroller views to use (CPScrollerStyleLegacy or CPScrollerStyleOverlay) @param aStyle the scroller style to set all scroller views to use (CPScrollerStyleLegacy or CPScrollerStyleOverlay)
*/ */
+ (void)setGlobalScrollerStyle:(int)aStyle + (void)setGlobalScrollerStyle:(CPScrollerStyle)aStyle
{ {
CPScrollerStyleGlobal = aStyle; CPScrollerStyleGlobal = aStyle;
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollerStyleGlobalChangeNotification object:nil]; [[CPNotificationCenter defaultCenter] postNotificationName:CPScrollerStyleGlobalChangeNotification object:nil];
@@ -311,7 +345,7 @@ Notifies the delegate when the scroll view has finished scrolling.
_delegate = aDelegate; _delegate = aDelegate;
_implementedDelegateMethods = 0; _implementedDelegateMethods = 0;
if (_delegate === nil) if (_delegate == nil)
return; return;
if ([_delegate respondsToSelector:@selector(scrollViewWillScroll:)]) if ([_delegate respondsToSelector:@selector(scrollViewWillScroll:)])
@@ -321,7 +355,7 @@ Notifies the delegate when the scroll view has finished scrolling.
_implementedDelegateMethods |= CPScrollViewDelegate_scrollViewDidScroll_; _implementedDelegateMethods |= CPScrollViewDelegate_scrollViewDidScroll_;
} }
- (int)scrollerStyle - (CPScrollerStyle)scrollerStyle
{ {
return _scrollerStyle; return _scrollerStyle;
} }
@@ -332,7 +366,7 @@ Notifies the delegate when the scroll view has finished scrolling.
- CPScrollerStyleLegacy: Standard scrollers like Windows or Mac OS X prior to 10.7 - CPScrollerStyleLegacy: Standard scrollers like Windows or Mac OS X prior to 10.7
- CPScrollerStyleOverlay: scrollers like those in Mac OS X 10.7+ - CPScrollerStyleOverlay: scrollers like those in Mac OS X 10.7+
*/ */
- (void)setScrollerStyle:(int)aStyle - (void)setScrollerStyle:(CPScrollerStyle)aStyle
{ {
if (_scrollerStyle === aStyle) if (_scrollerStyle === aStyle)
return; return;
@@ -529,8 +563,8 @@ Notifies the delegate when the scroll view has finished scrolling.
{ {
var bounds = [self _insetBounds]; var bounds = [self _insetBounds];
[self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, MAX(CGRectGetWidth(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle] + 1), [CPScroller scrollerWidthInStyle:_scrollerStyle])]]; [self setHorizontalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, MAX(CGRectGetWidth(bounds), [_horizontalScroller scrollerWidth] + 1), [_horizontalScroller scrollerWidth])]];
[[self horizontalScroller] setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle])]; [[self horizontalScroller] setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [_horizontalScroller scrollerWidth])];
} }
[self reflectScrolledClipView:_contentView]; [self reflectScrolledClipView:_contentView];
@@ -594,8 +628,8 @@ Notifies the delegate when the scroll view has finished scrolling.
{ {
var bounds = [self _insetBounds]; var bounds = [self _insetBounds];
[self setVerticalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, [CPScroller scrollerWidthInStyle:_scrollerStyle], MAX(CGRectGetHeight(bounds), [CPScroller scrollerWidthInStyle:_scrollerStyle] + 1))]]; [self setVerticalScroller:[[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, [_verticalScroller scrollerWidth], MAX(CGRectGetHeight(bounds), [_verticalScroller scrollerWidth] + 1))]];
[[self verticalScroller] setFrameSize:CGSizeMake([CPScroller scrollerWidthInStyle:_scrollerStyle], CGRectGetHeight(bounds))]; [[self verticalScroller] setFrameSize:CGSizeMake([_verticalScroller scrollerWidth], CGRectGetHeight(bounds))];
} }
[self reflectScrolledClipView:_contentView]; [self reflectScrolledClipView:_contentView];
@@ -911,8 +945,8 @@ Notifies the delegate when the scroll view has finished scrolling.
bottomCornerFrame.origin.x = CGRectGetMinX(verticalFrame); bottomCornerFrame.origin.x = CGRectGetMinX(verticalFrame);
bottomCornerFrame.origin.y = CGRectGetMaxY(verticalFrame); bottomCornerFrame.origin.y = CGRectGetMaxY(verticalFrame);
bottomCornerFrame.size.width = [CPScroller scrollerWidthInStyle:_scrollerStyle]; bottomCornerFrame.size.width = [_verticalScroller scrollerWidth];
bottomCornerFrame.size.height = [CPScroller scrollerWidthInStyle:_scrollerStyle]; bottomCornerFrame.size.height = [_horizontalScroller scrollerWidth];
return bottomCornerFrame; return bottomCornerFrame;
} }
@@ -1124,8 +1158,8 @@ Notifies the delegate when the scroll view has finished scrolling.
contentFrame.size.height -= headerClipViewHeight; contentFrame.size.height -= headerClipViewHeight;
var difference = CGSizeMake(CGRectGetWidth(documentFrame) - CGRectGetWidth(contentFrame), CGRectGetHeight(documentFrame) - CGRectGetHeight(contentFrame)), var difference = CGSizeMake(CGRectGetWidth(documentFrame) - CGRectGetWidth(contentFrame), CGRectGetHeight(documentFrame) - CGRectGetHeight(contentFrame)),
verticalScrollerWidth = [CPScroller scrollerWidthInStyle:[_verticalScroller style]], verticalScrollerWidth = [_verticalScroller scrollerWidth],
horizontalScrollerHeight = [CPScroller scrollerWidthInStyle:[_horizontalScroller style]], horizontalScrollerHeight = [_horizontalScroller scrollerWidth],
hasVerticalScroll = difference.height > 0.0, hasVerticalScroll = difference.height > 0.0,
hasHorizontalScroll = difference.width > 0.0, hasHorizontalScroll = difference.width > 0.0,
shouldShowVerticalScroller = _hasVerticalScroller && (!_autohidesScrollers || hasVerticalScroll), shouldShowVerticalScroller = _hasVerticalScroller && (!_autohidesScrollers || hasVerticalScroll),
@@ -1510,6 +1544,51 @@ Notifies the delegate when the scroll view has finished scrolling.
@end @end
#pragma mark -
@implementation CPScrollView (FirstResponder)
// Those 4 next methods are needed to (un)set CPThemeStateFirstResponder based on content view
- (void)viewWillMoveToWindow:(CPWindow)aWindow
{
[super viewWillMoveToWindow:aWindow];
[self _stopObservingFirstResponderForWindow:[self window]];
if (aWindow)
[self _startObservingFirstResponderForWindow:aWindow];
}
- (void)_startObservingFirstResponderForWindow:(CPWindow)aWindow
{
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_firstResponderDidChange:) name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
}
- (void)_stopObservingFirstResponderForWindow:(CPWindow)aWindow
{
[[CPNotificationCenter defaultCenter] removeObserver:self name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
}
- (void)_firstResponderDidChange:(CPNotification)aNotification
{
var responder = [[self window] firstResponder],
// FIXME: We add focus ring only on table views right now. When focus ring management will be added, this must be adapted.
shouldAddFocusRing = [responder isKindOfClass:[CPTableView class]],
found;
while (!(found = (responder === self)) && responder)
responder = [responder superview];
if (found && shouldAddFocusRing)
[self setThemeState:CPThemeStateFirstResponder];
else
[self unsetThemeState:CPThemeStateFirstResponder];
}
@end
#pragma mark -
var CPScrollViewContentViewKey = @"CPScrollViewContentView", var CPScrollViewContentViewKey = @"CPScrollViewContentView",
CPScrollViewHeaderClipViewKey = @"CPScrollViewHeaderClipViewKey", CPScrollViewHeaderClipViewKey = @"CPScrollViewHeaderClipViewKey",
+14 -5
View File
@@ -62,6 +62,7 @@ NAMES_FOR_PARTS[CPScrollerKnobSlot] = @"knob-slot";
NAMES_FOR_PARTS[CPScrollerKnob] = @"knob"; NAMES_FOR_PARTS[CPScrollerKnob] = @"knob";
@typedef CPScrollerStyle
CPScrollerStyleLegacy = 0; CPScrollerStyleLegacy = 0;
CPScrollerStyleOverlay = 1; CPScrollerStyleOverlay = 1;
@@ -134,7 +135,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
/*! /*!
Returns the CPScroller's width for a CPRegularControlSize. Returns the CPScroller's width for a CPRegularControlSize.
*/ */
+ (float)scrollerWidthInStyle:(int)aStyle + (float)scrollerWidthInStyle:(CPScrollerStyle)aStyle
{ {
if (!_CACHED_THEME_SCROLLER) if (!_CACHED_THEME_SCROLLER)
_CACHED_THEME_SCROLLER = [[self alloc] init]; _CACHED_THEME_SCROLLER = [[self alloc] init];
@@ -208,7 +209,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
/*! /*!
Returns the scroller's style Returns the scroller's style
*/ */
- (void)style - (int)style
{ {
return _style; return _style;
} }
@@ -217,7 +218,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
Set the scroller's control size Set the scroller's control size
@param aStyle the scroller style: CPScrollerStyleLegacy or CPScrollerStyleOverlay @param aStyle the scroller style: CPScrollerStyleLegacy or CPScrollerStyleOverlay
*/ */
- (void)setStyle:(id)aStyle - (void)setStyle:(CPScrollerStyle)aStyle
{ {
if (_style != nil && _style === aStyle) if (_style != nil && _style === aStyle)
return; return;
@@ -226,6 +227,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
if (_style === CPScrollerStyleLegacy) if (_style === CPScrollerStyleLegacy)
{ {
_allowFadingOut = NO;
[self fadeIn]; [self fadeIn];
[self setThemeState:CPThemeStateScrollViewLegacy]; [self setThemeState:CPThemeStateScrollViewLegacy];
} }
@@ -258,7 +260,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
- (void)setKnobProportion:(float)aProportion - (void)setKnobProportion:(float)aProportion
{ {
if (!_IS_NUMERIC(aProportion)) if (!_IS_NUMERIC(aProportion))
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric"]; [CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric, was: "+aProportion];
_knobProportion = MIN(1.0, MAX(0.0001, aProportion)); _knobProportion = MIN(1.0, MAX(0.0001, aProportion));
@@ -758,7 +760,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
if ([self isHidden] || ![self isEnabled] || !_isMouseOver) if ([self isHidden] || ![self isEnabled] || !_isMouseOver)
return; return;
_allowFadingOut = YES; _allowFadingOut = (_style !== CPScrollerStyleLegacy);
_isMouseOver = NO; _isMouseOver = NO;
if (_timerFadeOut) if (_timerFadeOut)
@@ -770,6 +772,13 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
_timerFadeOut = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_performFadeOut:) userInfo:nil repeats:NO]; _timerFadeOut = [CPTimer scheduledTimerWithTimeInterval:1.2 target:self selector:@selector(_performFadeOut:) userInfo:nil repeats:NO];
} }
- (float)scrollerWidth
{
if (_style == CPScrollerStyleLegacy)
return [self valueForThemeAttribute:@"scroller-width" inState:CPThemeStateScrollViewLegacy];
return [self currentValueForThemeAttribute:@"scroller-width"];
}
#pragma mark - #pragma mark -
#pragma mark Delegates #pragma mark Delegates
+238 -33
View File
@@ -24,8 +24,12 @@
@import "CPMenu.j" @import "CPMenu.j"
@import "CPMenuItem.j" @import "CPMenuItem.j"
@import "CPTextField.j" @import "CPTextField.j"
@import "CPAnimationContext.j"
@import "CPViewAnimator.j"
@import "CPArrayController.j"
@class CPUserDefaults @class CPUserDefaults
@class CALayer
@global CPApp @global CPApp
@@ -58,6 +62,9 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
BOOL _sendsSearchStringImmediately; BOOL _sendsSearchStringImmediately;
BOOL _canResignFirstResponder; BOOL _canResignFirstResponder;
CPTimer _partialStringTimer; CPTimer _partialStringTimer;
CPView _contentView;
BOOL _isBecomingFirstResponder;
} }
+ (CPString)defaultThemeClass + (CPString)defaultThemeClass
@@ -74,7 +81,10 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@"image-cancel-pressed": [CPNull null], @"image-cancel-pressed": [CPNull null],
@"image-search-inset" : CGInsetMake(0, 0, 0, 5), @"image-search-inset" : CGInsetMake(0, 0, 0, 5),
@"image-cancel-inset" : CGInsetMake(0, 5, 0, 0), @"image-cancel-inset" : CGInsetMake(0, 5, 0, 0),
@"search-right-margin": 2 @"search-button-rect-function": [CPNull null],
@"layout-function": [CPNull null],
@"search-right-margin": 2,
@"search-menu-offset": CGPointMake(10, -4)
}; };
} }
@@ -122,6 +132,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[self resetSearchButton]; [self resetSearchButton];
_canResignFirstResponder = YES; _canResignFirstResponder = YES;
_isBecomingFirstResponder = NO;
} }
@@ -182,7 +193,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)resetSearchButton - (void)resetSearchButton
{ {
var button = [self searchButton], var button = [self searchButton],
searchButtonImage = (_searchMenuTemplate === nil) ? [self currentValueForThemeAttribute:@"image-search"] : [self currentValueForThemeAttribute:@"image-find"]; searchButtonImage = (_searchMenuTemplate == nil) ? [self currentValueForThemeAttribute:@"image-search"] : [self currentValueForThemeAttribute:@"image-find"];
[button setBordered:NO]; [button setBordered:NO];
[button setImageScaling:CPImageScaleAxesIndependently]; [button setImageScaling:CPImageScaleAxesIndependently];
@@ -229,8 +240,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
var button = [self cancelButton]; var button = [self cancelButton];
[button setBordered:NO]; [button setBordered:NO];
[button setImageScaling:CPImageScaleAxesIndependently]; [button setImageScaling:CPImageScaleAxesIndependently];
[button setImage:[self valueForThemeAttribute:@"image-cancel"]]; [button setImage:[self currentValueForThemeAttribute:@"image-cancel"]];
[button setAlternateImage:[self valueForThemeAttribute:@"image-cancel-pressed"]]; [button setAlternateImage:[self currentValueForThemeAttribute:@"image-cancel-pressed"]];
[button setAutoresizingMask:CPViewMinXMargin]; [button setAutoresizingMask:CPViewMinXMargin];
[button setTarget:self]; [button setTarget:self];
[button setAction:@selector(cancelOperation:)]; [button setAction:@selector(cancelOperation:)];
@@ -252,7 +263,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
if (_searchButton) if (_searchButton)
{ {
var searchBounds = [self searchButtonRectForBounds:bounds], var searchBounds = [self searchButtonRectForBounds:bounds],
rightMargin = [self currentValueForThemeAttribute:@"search-right-margin"]; rightMargin = [self _potentialCurrentValueForThemeAttribute:@"search-right-margin"];
leftOffset = CGRectGetMaxX(searchBounds) + rightMargin; leftOffset = CGRectGetMaxX(searchBounds) + rightMargin;
} }
@@ -272,10 +283,18 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
*/ */
- (CGRect)searchButtonRectForBounds:(CGRect)rect - (CGRect)searchButtonRectForBounds:(CGRect)rect
{ {
var size = [[self currentValueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero(), var themedRectFunction = [self _potentialCurrentValueForThemeAttribute:@"search-button-rect-function"];
inset = [self currentValueForThemeAttribute:@"image-search-inset"];
return CGRectMake(inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height); if (themedRectFunction)
// There's a theme defined positioning function, just use it
return objj_eval("("+themedRectFunction+")")(self, rect);
else
{
var size = [[self _potentialCurrentValueForThemeAttribute:@"image-search"] size] || CGSizeMakeZero(),
inset = [self _potentialCurrentValueForThemeAttribute:@"image-search-inset"];
return CGRectMake(inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.height) / 2, size.width, size.height);
}
} }
/*! /*!
@@ -285,8 +304,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
*/ */
- (CGRect)cancelButtonRectForBounds:(CGRect)rect - (CGRect)cancelButtonRectForBounds:(CGRect)rect
{ {
var size = [[self currentValueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero(), var size = [[self _potentialCurrentValueForThemeAttribute:@"image-cancel"] size] || CGSizeMakeZero(),
inset = [self currentValueForThemeAttribute:@"image-cancel-inset"]; inset = [self _potentialCurrentValueForThemeAttribute:@"image-cancel-inset"];
return CGRectMake(CGRectGetWidth(rect) - size.width + inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.width) / 2, size.height, size.height); return CGRectMake(CGRectGetWidth(rect) - size.width + inset.left - inset.right, inset.top - inset.bottom + (CGRectGetHeight(rect) - size.width) / 2, size.height, size.height);
} }
@@ -394,8 +413,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
{ {
var max = MIN([self maximumRecents], [searches count]); var max = MIN([self maximumRecents], [searches count]);
searches = [searches subarrayWithRange:CPMakeRange(0, max)]; _recentSearches = [searches subarrayWithRange:CPMakeRange(0, max)];
_recentSearches = searches;
[self _autosaveRecentSearchList]; [self _autosaveRecentSearchList];
} }
@@ -480,6 +498,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (BOOL)sendAction:(SEL)anAction to:(id)anObject - (BOOL)sendAction:(SEL)anAction to:(id)anObject
{ {
[self selectAll:nil];
[super sendAction:anAction to:anObject]; [super sendAction:anAction to:anObject];
[_partialStringTimer invalidate]; [_partialStringTimer invalidate];
@@ -490,7 +509,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)_addStringToRecentSearches:(CPString)string - (void)_addStringToRecentSearches:(CPString)string
{ {
if (string === nil || string === @"" || [_recentSearches containsObject:string]) if (string == nil || string === @"" || [_recentSearches containsObject:string])
return; return;
var searches = [CPMutableArray arrayWithArray:_recentSearches]; var searches = [CPMutableArray arrayWithArray:_recentSearches];
@@ -578,6 +597,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[item setTarget:self]; [item setTarget:self];
[template addItem:item]; [template addItem:item];
[self _addSeparatorToMenu:template];
item = [[CPMenuItem alloc] initWithTitle:@"Recent Searches" item = [[CPMenuItem alloc] initWithTitle:@"Recent Searches"
action:nil action:nil
keyEquivalent:@""]; keyEquivalent:@""];
@@ -597,7 +618,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)_updateSearchMenu - (void)_updateSearchMenu
{ {
if (_searchMenuTemplate === nil) if (_searchMenuTemplate == nil)
return; return;
var menu = [[CPMenu alloc] init], var menu = [[CPMenu alloc] init],
@@ -615,9 +636,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
case CPSearchFieldRecentsTitleMenuItemTag: case CPSearchFieldRecentsTitleMenuItemTag:
if (countOfRecents === 0) if (countOfRecents === 0)
continue; continue;
if ([menu numberOfItems] > 0)
[self _addSeparatorToMenu:menu];
break; break;
case CPSearchFieldRecentsMenuItemTag: case CPSearchFieldRecentsMenuItemTag:
@@ -640,9 +658,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
if (countOfRecents === 0) if (countOfRecents === 0)
continue; continue;
if ([menu numberOfItems] > 0)
[self _addSeparatorToMenu:menu];
[item setAction:@selector(_searchFieldClearRecents:)]; [item setAction:@selector(_searchFieldClearRecents:)];
[item setTarget:self]; [item setTarget:self];
break; break;
@@ -650,9 +665,6 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
case CPSearchFieldNoRecentsMenuItemTag: case CPSearchFieldNoRecentsMenuItemTag:
if (countOfRecents !== 0) if (countOfRecents !== 0)
continue; continue;
if ([menu numberOfItems] > 0)
[self _addSeparatorToMenu:menu];
break; break;
} }
@@ -669,6 +681,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
{ {
var separator = [CPMenuItem separatorItem]; var separator = [CPMenuItem separatorItem];
[separator setEnabled:NO]; [separator setEnabled:NO];
[separator setTag:CPSearchFieldRecentsTitleMenuItemTag];
[aMenu addItem:separator]; [aMenu addItem:separator];
} }
@@ -686,11 +699,12 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)_showMenu - (void)_showMenu
{ {
if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![self isEnabled] || ([_recentSearches count] === 0)) if (_searchMenu === nil || [_searchMenu numberOfItems] === 0 || ![self isEnabled])
return; return;
var aFrame = [[self superview] convertRect:[self frame] toView:nil], var aFrame = [[self superview] convertRect:[self frame] toView:nil],
location = CGPointMake(aFrame.origin.x + 10, aFrame.origin.y + aFrame.size.height - 4); offset = [self currentValueForThemeAttribute:@"search-menu-offset"],
location = CGPointMake(aFrame.origin.x + offset.x, aFrame.origin.y + aFrame.size.height + offset.y);
var anEvent = [CPEvent mouseEventWithType:CPRightMouseDown location:location modifierFlags:0 timestamp:[[CPApp currentEvent] timestamp] windowNumber:[[self window] windowNumber] context:nil eventNumber:1 clickCount:1 pressure:0]; var anEvent = [CPEvent mouseEventWithType:CPRightMouseDown location:location modifierFlags:0 timestamp:[[CPApp currentEvent] timestamp] windowNumber:[[self window] windowNumber] context:nil eventNumber:1 clickCount:1 pressure:0];
@@ -706,10 +720,14 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)cancelOperation:(id)sender - (void)cancelOperation:(id)sender
{ {
[self setObjectValue:@""]; // If something was entered, the search field must deactivate itself (else, do nothing)
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]]; if ([[self stringValue] length] > 0)
{
[self setObjectValue:@""];
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
[self _updateCancelButtonVisibility]; [[self window] makeFirstResponder:[self window]];
}
} }
- (void)_searchFieldSearch:(id)sender - (void)_searchFieldSearch:(id)sender
@@ -759,15 +777,197 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
- (void)_loadRecentSearchList - (void)_loadRecentSearchList
{ {
var name = [self recentsAutosaveName]; var name = [self recentsAutosaveName];
if (name === nil) if (name == nil)
return; return;
var list = [[CPUserDefaults standardUserDefaults] objectForKey:name]; var list = [[CPUserDefaults standardUserDefaults] objectForKey:name];
if (list !== nil) if (list != nil)
_recentSearches = list; _recentSearches = list;
} }
@end
#pragma mark -
@implementation CPSearchField (ThemingAdditions)
// Overwrite CPTextField method to permit themed layout functions
- (void)layoutSubviews
{
if (!_contentView)
{
// Search for the CPImageAndTextView subview of mine
for (var i = 0, subviews = [self subviews], nb = [subviews count]; (!_contentView && (i < nb)); i++)
if ([subviews[i] isKindOfClass:_CPImageAndTextView])
_contentView = subviews[i];
}
var bezelColor = [self currentValueForThemeAttribute:@"bezel-color"];
if ([bezelColor isCSSBased])
{
// CSS Styling
// We don't need bezelView as we apply CSS styling directly on the search view itself
[self _setBackgroundColor:bezelColor];
if (!_contentView)
_contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:nil];
}
else
{
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
[bezelView setBackgroundColor:bezelColor];
if (!_contentView)
_contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
}
if (_contentView)
{
[_contentView setHidden:(_stringValue && _stringValue.length > 0) && [self hasThemeState:CPThemeStateEditing]];
[_contentView setText:[self hasThemeState:CPTextFieldStatePlaceholder] ? [self placeholderString] : _stringValue];
[_contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
[_contentView setFont:[self font]]; //[self currentValueForThemeAttribute:@"font"]];
[_contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
[_contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
[_contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
[_contentView setTextShadowColor:[self currentValueForThemeAttribute:@"text-shadow-color"]];
[_contentView setTextShadowOffset:[self currentValueForThemeAttribute:@"text-shadow-offset"]];
}
if (_isEditing)
[self _setCSSStyleForInputElement];
[self updateSearchButton];
[self updateTrackingAreas];
}
// As CPTextField redefines setBackgroundColor, we have to bypass it
- (void)_setBackgroundColor:(CPColor)aColor
{
var grannyMethod = class_getInstanceMethod([[self superclass] superclass], @selector(setBackgroundColor:)),
grannyImplementation = method_getImplementation(grannyMethod);
grannyImplementation(self, @selector(setBackgroundColor:), aColor);
}
- (void)_layoutSubviews
{
var themedLayoutFunction = [self currentValueForThemeAttribute:@"layout-function"];
if (themedLayoutFunction)
// There's a theme defined layout function, just use it
objj_eval("("+themedLayoutFunction+")")(self);
else
// Call directly the completion handler
[self themedLayoutFunctionCompletionHandler];
}
- (void)updateSearchButton
{
[_searchButton setImage:(_searchMenuTemplate ? [self currentValueForThemeAttribute:@"image-find"] : [self currentValueForThemeAttribute:@"image-search"])];
[self updateTrackingAreas];
}
- (void)themedLayoutFunctionCompletionHandler
{
if (_isBecomingFirstResponder)
{
_isBecomingFirstResponder = NO;
// Call the real _becomeFirstKeyResponder to finish setting the input element
[super _becomeFirstKeyResponder];
// For some (unknown yet) reason, input field doesn't focus and text field visual state is not updated
var element = [self _inputElement];
element.focus();
[self layoutSubviews];
[CALayer runLoopUpdateLayers]; // Thank you @daboe01 for suggesting adding this
}
else
[self layoutSubviews];
}
// We override CPTextField method in order to delay the display of the input element until the end of the animation
- (BOOL)_becomeFirstKeyResponder
{
// As we have to return a result, we check if response could be NO
if (![self _isWithinUsablePlatformRect] || ![self isEditable])
return NO;
// We are now sure that response will be YES
_isBecomingFirstResponder = YES;
// We have to do this now in order to avoid running conditions messing things among multiple textfields
_stringValue = [self stringValue];
var element = [self _inputElement];
element.value = _stringValue;
[self _layoutSubviews];
return YES;
}
- (void)textDidEndEditing:(CPNotification)note
{
if ([note object] != self)
return;
[self _layoutSubviews];
[super textDidEndEditing:note];
}
- (void)_windowDidResignKey:(CPNotification)aNotification
{
// When the window resigns key, if the search field is empty, it must cancel first responder
if ([[self stringValue] length] == 0)
[[self window] makeFirstResponder:[self window]];
[super _windowDidResignKey:aNotification];
[self _layoutSubviews];
}
- (void)setPlaceholderString:(CPString)aStringValue
{
[super setPlaceholderString:aStringValue];
[self _layoutContent];
}
- (void)_layoutContent
{
[_searchButton setFrame:[self searchButtonRectForBounds:[self bounds]]];
[_contentView setFrame:[self contentRectForBounds:[self bounds]]];
[_cancelButton setFrame:[self cancelButtonRectForBounds:[self bounds]]];
[self updateTrackingAreas];
}
- (void)setFrameSize:(CGSize)aSize
{
[super setFrameSize:aSize];
[self _layoutContent];
}
- (id)_potentialCurrentValueForThemeAttribute:(CPString)aName
{
if (_isBecomingFirstResponder)
return [self valueForThemeAttribute:aName inState:[self themeState].and(CPThemeStateEditing)];
else
return [self currentValueForThemeAttribute:aName];
}
- (void)unbind:(CPString)aBinding - (void)unbind:(CPString)aBinding
{ {
[super unbind:aBinding]; [super unbind:aBinding];
@@ -812,7 +1012,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[self addTrackingArea:_searchButtonTrackingArea]; [self addTrackingArea:_searchButtonTrackingArea];
} }
if (_cancelButton) if (_cancelButton && ![_cancelButton isHidden])
{ {
_cancelButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:[_cancelButton frame] _cancelButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:[_cancelButton frame]
options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow options:CPTrackingCursorUpdate | CPTrackingActiveInKeyWindow
@@ -913,16 +1113,21 @@ var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
var destination = [_info objectForKey:CPObservedObjectKey], var destination = [_info objectForKey:CPObservedObjectKey],
keyPath = [_info objectForKey:CPObservedKeyPathKey]; keyPath = [_info objectForKey:CPObservedKeyPathKey];
var formatString = _predicateFormat.replace(/\$value/g, "%@");
[self suppressSpecificNotificationFromObject:destination keyPath:keyPath]; [self suppressSpecificNotificationFromObject:destination keyPath:keyPath];
if (aValue) if (aValue)
[_controller setFilterPredicate:[CPPredicate predicateWithFormat:formatString, aValue]]; {
var values = @[],
formatString = _predicateFormat.replace(/\$value/g, function(x) {[values addObject:aValue]; return "%@";});
[_controller setFilterPredicate:[CPPredicate predicateWithFormat:formatString argumentArray:values]];
}
else else
[_controller setFilterPredicate:nil]; [_controller setFilterPredicate:nil];
[self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath]; [self unsuppressSpecificNotificationFromObject:destination keyPath:keyPath];
} }
- (CPString)searchFieldValue - (CPString)searchFieldValue
{ {
return [_source stringValue]; return [_source stringValue];
+7 -5
View File
@@ -561,10 +561,11 @@ CPSegmentSwitchTrackingMomentary = 2;
} }
else if (aName === "right-segment-bezel") else if (aName === "right-segment-bezel")
{ {
return CGRectMake(CGRectGetWidth([self bounds]) - contentInset.right, // we have to FLOOR the coordinates to prevent a 1px glitch in Safari
bezelInset.top, return CGRectMake(FLOOR(CGRectGetWidth([self bounds]) - contentInset.right),
contentInset.right, FLOOR(bezelInset.top),
height); FLOOR(contentInset.right),
FLOOR(height));
} }
else if (aName.indexOf("segment-bezel") === 0) else if (aName.indexOf("segment-bezel") === 0)
{ {
@@ -802,7 +803,8 @@ CPSegmentSwitchTrackingMomentary = 2;
label = [segment label], label = [segment label],
image = [segment image]; image = [segment image];
width = (label ? [label sizeWithFont:[self font]].width : 4.0) + (image ? [image size].width : 0) + contentInsetWidth; // add 1 pixel to account for possible fractional pixels at right edge
width = (label ? [label sizeWithFont:[self font]].width + 1 : 4.0) + (image ? [image size].width : 0) + contentInsetWidth;
} }
return CGRectMake(left, top, width, height); return CGRectMake(left, top, width, height);
+808 -150
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -32,6 +32,7 @@
@end @end
@global document
var CPSoundDelegate_sound_didFinishPlaying_ = 1 << 1; var CPSoundDelegate_sound_didFinishPlaying_ = 1 << 1;
+903 -515
View File
File diff suppressed because it is too large Load Diff
+7 -6
View File
@@ -188,12 +188,12 @@
[_buttonUp setFrame:upFrame]; [_buttonUp setFrame:upFrame];
[_buttonDown setFrame:downFrame]; [_buttonDown setFrame:downFrame];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered]; [_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled]]; [_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect]];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted]]; [_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inState:CPThemeStateBordered]; [_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled]]; [_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect]];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted]]; [_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:[controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted]] forThemeAttribute:@"bezel-color" inStates:[CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect]];
} }
- (void)_sizeToFit - (void)_sizeToFit
@@ -323,6 +323,7 @@ var CPStepperMinValue = @"CPStepperMinValue",
_autorepeat = [aCoder decodeBoolForKey:CPStepperAutorepeat]; _autorepeat = [aCoder decodeBoolForKey:CPStepperAutorepeat];
[self _init]; [self _init];
[self setEnabled:[self isEnabled]];
} }
return self; return self;
+19 -19
View File
@@ -32,7 +32,7 @@ var CPStringSizeWithFontInWidthCache = [],
CPStringSizeWithFontHeightCache = [], CPStringSizeWithFontHeightCache = [],
CPStringSizeMeasuringContext; CPStringSizeMeasuringContext;
CPStringSizeCachingEnabled = YES; CPCanvasStringSizingIsFunctional = NO;
@implementation CPString (CPStringDrawing) @implementation CPString (CPStringDrawing)
@@ -63,36 +63,42 @@ CPStringSizeCachingEnabled = YES;
return; return;
#if PLATFORM(DOM) #if PLATFORM(DOM)
if (CPFeatureIsCompatible(CPHTMLCanvasFeature) && !CPStringSizeMeasuringContext) if (CPFeatureIsCompatible(CPHTMLCanvasFeature))
CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate(); {
if (!CPStringSizeMeasuringContext)
CPStringSizeMeasuringContext = CGBitmapGraphicsContextCreate();
// This is to make sure that Canvas based string sizing is functional before we use it.
// Currently, Chrome has issues with certain strings, FF had issues in the past.
// Unfortunately, this test does fit in CPCompatibility.j where things are not sufficiently initialized.
var testingFont = [CPFont systemFontOfSize:12];
var testingText = 'A A A A A A A A';
CPStringSizeMeasuringContext.font = [testingFont cssString];
CPCanvasStringSizingIsFunctional = ROUND(CPStringSizeMeasuringContext.measureText(testingText).width) == ROUND([CPPlatformString sizeOfString:testingText withFont:testingFont forWidth:NULL].width);
}
#endif #endif
} }
- (CGSize)_sizeWithFont:(CPFont)aFont inWidth:(float)aWidth - (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
{ {
var size; var size;
#if PLATFORM(DOM) #if PLATFORM(DOM)
if (!CPStringSizeCachingEnabled)
return [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self]; var sizeCacheForFont = CPStringSizeWithFontInWidthCache[self];
if (sizeCacheForFont === undefined) if (sizeCacheForFont === undefined)
sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = []; sizeCacheForFont = CPStringSizeWithFontInWidthCache[self] = [];
if (!aWidth)
aWidth = '0';
var cssString = [aFont cssString], var cssString = [aFont cssString],
cacheKey = cssString + '_' + aWidth; cacheKey = cssString + '_' + (aWidth ? aWidth : '0');
size = sizeCacheForFont[cacheKey]; size = sizeCacheForFont[cacheKey];
if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey)) if (size !== undefined && sizeCacheForFont.hasOwnProperty(cacheKey))
return CGSizeMakeCopy(size); return CGSizeMakeCopy(size);
if (!CPFeatureIsCompatible(CPHTMLCanvasFeature) || aWidth > 0) if (!CPCanvasStringSizingIsFunctional || aWidth)
size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth]; size = [CPPlatformString sizeOfString:self withFont:aFont forWidth:aWidth];
else else
{ {
@@ -109,15 +115,9 @@ CPStringSizeCachingEnabled = YES;
sizeCacheForFont[cacheKey] = size; sizeCacheForFont[cacheKey] = size;
#else #else
size = CGSizeMake(0, 0); size = CGSizeMake(0, 0);
#endif #endif
return CGSizeMakeCopy(size); return CGSizeMakeCopy(size);
} }
- (CGSize)sizeWithFont:(CPFont)aFont inWidth:(float)aWidth
{
var size = [self _sizeWithFont:aFont inWidth:aWidth];
return CGSizeMake(CEIL(size.width), size.height);
}
@end @end
+117 -16
View File
@@ -79,6 +79,20 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
unsigned _delegateSelectors; unsigned _delegateSelectors;
} }
+ (CPString)defaultThemeClass
{
return @"tab-view";
}
+ (CPDictionary)themeAttributes
{
return @{
@"nib2cib-adjustment-frame": [CPNull null],
@"should-center-on-border": NO,
@"box-content-inset": CGInsetMakeZero()
};
}
- (id)initWithFrame:(CGRect)aFrame - (id)initWithFrame:(CGRect)aFrame
{ {
if (self = [super initWithFrame:aFrame]) if (self = [super initWithFrame:aFrame])
@@ -95,14 +109,18 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
{ {
_tabs = [[_CPSegmentedControl alloc] initWithFrame:CGRectMakeZero()]; _tabs = [[_CPSegmentedControl alloc] initWithFrame:CGRectMakeZero()];
[_tabs setTabView:self]; [_tabs setTabView:self];
[_tabs setHitTests:NO];
[_tabs setSegments:[CPArray array]]; [_tabs setSegments:[CPArray array]];
[_tabs setAction:@selector(_reflectSelectedTab:)];
[_tabs setTarget:self];
var height = [_tabs valueForThemeAttribute:@"min-size"].height; var height = [_tabs valueForThemeAttribute:@"min-size"].height;
[_tabs setFrameSize:CGSizeMake(0, height)]; [_tabs setFrameSize:CGSizeMake(0, height)];
_box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]]; _box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
[_box setTabView:self]; [_box setTabView:self];
[self setBackgroundColor:[CPColor colorWithCalibratedWhite:0.95 alpha:1.0]]; [_box setContentInset:[self currentValueForThemeAttribute:@"box-content-inset"]];
[_box setContentViewMargins:CGSizeMakeZero()];
[self addSubview:_box]; [self addSubview:_box];
[self addSubview:_tabs]; [self addSubview:_tabs];
@@ -132,10 +150,10 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
*/ */
- (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPUInteger)anIndex - (void)insertTabViewItem:(CPTabViewItem)aTabViewItem atIndex:(CPUInteger)anIndex
{ {
[self _insertTabViewItems:[aTabViewItem] atIndexes:[CPIndexSet indexSetWithIndex:anIndex]]; [self _insertTabViewItems:[aTabViewItem] atIndexes:[CPIndexSet indexSetWithIndex:anIndex] canUpdateSelectedTab:YES];
} }
- (void)_insertTabViewItems:(CPArray)tabViewItems atIndexes:(CPIndexSet)indexes - (void)_insertTabViewItems:(CPArray)tabViewItems atIndexes:(CPIndexSet)indexes canUpdateSelectedTab:(BOOL)canUpdateSelectedTab
{ {
var prevItemsCount = [self numberOfTabViewItems]; var prevItemsCount = [self numberOfTabViewItems];
@@ -148,7 +166,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
[self _sendDelegateTabViewDidChangeNumberOfTabViewItems]; [self _sendDelegateTabViewDidChangeNumberOfTabViewItems];
// Do not allow empty selection if selection bindings are not enabled. // Do not allow empty selection if selection bindings are not enabled.
if (prevItemsCount == 0 && [self numberOfTabViewItems] > 0 && ![self _isSelectionBinded]) if (prevItemsCount == 0 && [self numberOfTabViewItems] > 0 && ![self _isSelectionBinded] && canUpdateSelectedTab)
[self _selectTabViewItemAtIndex:0]; [self _selectTabViewItemAtIndex:0];
} }
@@ -275,7 +293,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
*/ */
- (void)selectNextTabViewItem:(id)aSender - (void)selectNextTabViewItem:(id)aSender
{ {
if (_selectedTabViewItem === nil) if (_selectedTabViewItem == nil)
return; return;
var nextIndex = [self indexOfTabViewItem:_selectedTabViewItem] + 1; var nextIndex = [self indexOfTabViewItem:_selectedTabViewItem] + 1;
@@ -293,7 +311,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
*/ */
- (void)selectPreviousTabViewItem:(id)aSender - (void)selectPreviousTabViewItem:(id)aSender
{ {
if (_selectedTabViewItem === nil) if (_selectedTabViewItem == nil)
return; return;
var previousIndex = [self indexOfTabViewItem:_selectedTabViewItem] - 1; var previousIndex = [self indexOfTabViewItem:_selectedTabViewItem] - 1;
@@ -353,15 +371,15 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
{ {
var controller = [aTabViewItem viewController]; var controller = [aTabViewItem viewController];
if (controller !== nil && ![controller isViewLoaded]) if (controller != nil && ![controller isViewLoaded])
{ {
[controller loadViewWithCompletionHandler:function(view, error) [controller loadViewWithCompletionHandler:function(view, error)
{ {
if (error !== nil) if (error != nil)
{ {
CPLog.warn("Could not load the view for item " + aTabViewItem + ". " + error); CPLog.warn("Could not load the view for item " + aTabViewItem + ". " + error);
} }
else if (view !== nil) else if (view != nil)
{ {
[aTabViewItem setView:view]; [aTabViewItem setView:view];
@@ -463,10 +481,10 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
{ {
var aFrame = [self frame], var aFrame = [self frame],
segmentedHeight = CGRectGetHeight([_tabs frame]), segmentedHeight = CGRectGetHeight([_tabs frame]),
origin = _type === CPTopTabsBezelBorder ? segmentedHeight / 2 : 0; borderWidth = [self currentValueForThemeAttribute:@"should-center-on-border"] ? [_box borderWidth] : 0,
origin = _type === CPTopTabsBezelBorder ? (segmentedHeight - borderWidth) / 2 : 0;
[_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame), [_box setFrame:CGRectMake(0, origin, CGRectGetWidth(aFrame), CGRectGetHeight(aFrame) - (segmentedHeight - borderWidth) / 2)];
CGRectGetHeight(aFrame) - segmentedHeight / 2)];
[self _repositionTabs]; [self _repositionTabs];
} }
@@ -526,6 +544,11 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
return [_box backgroundColor]; return [_box backgroundColor];
} }
- (void)mouseDown:(CPEvent)anEvent
{
[_tabs trackSegment:anEvent];
}
- (void)_repositionTabs - (void)_repositionTabs
{ {
var horizontalCenterOfSelf = CGRectGetWidth([self bounds]) / 2, var horizontalCenterOfSelf = CGRectGetWidth([self bounds]) / 2,
@@ -542,6 +565,11 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
[_box setContentView:aView]; [_box setContentView:aView];
} }
- (void)_reflectSelectedTab:(id)aSender
{
[self selectTabViewItemAtIndex:[_tabs selectedSegment]];
}
// DELEGATE METHODS // DELEGATE METHODS
- (BOOL)_sendDelegateShouldSelectTabViewItem:(CPTabViewItem)aTabViewItem - (BOOL)_sendDelegateShouldSelectTabViewItem:(CPTabViewItem)aTabViewItem
@@ -599,7 +627,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
{ {
var theBinder = [self binderForBinding:CPSelectionIndexesBinding]; var theBinder = [self binderForBinding:CPSelectionIndexesBinding];
if (theBinder !== nil) if (theBinder != nil)
[theBinder reverseSetValueFor:@"selectionIndexes"]; [theBinder reverseSetValueFor:@"selectionIndexes"];
else else
{ {
@@ -796,7 +824,7 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
[_tabs setFont:_font]; [_tabs setFont:_font];
var items = [aCoder decodeObjectForKey:CPTabViewItemsKey] || [CPArray array]; var items = [aCoder decodeObjectForKey:CPTabViewItemsKey] || [CPArray array];
[self _insertTabViewItems:items atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [items count])]]; [self _insertTabViewItems:items atIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [items count])] canUpdateSelectedTab:NO];
[self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]]; [self setDelegate:[aCoder decodeObjectForKey:CPTabViewDelegateKey]];
@@ -858,15 +886,48 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
@end @end
#pragma mark -
@implementation CPTabView (CSSTheming)
#pragma mark Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[self setTheme:aTheme];
[[self subviews] makeObjectsPerformSelector:@selector(_setThemeIncludingDescendants:) withObject:aTheme];
// Items must also perform this (without this, only the selected item does)
for (var i = 0, allItems = [self items], count = allItems.length; i < count; i++)
if (allItems[i] != _selectedTabViewItem)
[[allItems[i] view] _setThemeIncludingDescendants:aTheme];
}
@end
#pragma mark -
@implementation _CPTabViewBox : CPBox @implementation _CPTabViewBox : CPBox
{ {
CPTabView _tabView @accessors(property=tabView); CPTabView _tabView @accessors(property=tabView);
CGInset _contentInset @accessors(property=contentInset);
} }
#pragma mark - #pragma mark -
#pragma mark Override #pragma mark Override
- (id)initWithFrame:(CGRect)aFrame
{
if (self = [super initWithFrame:aFrame])
{
[self setBoxType:CPBoxPrimary];
}
return self;
}
- (CPView)hitTest:(CGPoint)aPoint - (CPView)hitTest:(CGPoint)aPoint
{ {
// Here we check if we have clicked on the segmentedControl of the tabView or not // Here we check if we have clicked on the segmentedControl of the tabView or not
@@ -879,6 +940,46 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
return [super hitTest:aPoint]; return [super hitTest:aPoint];
} }
// Some CPBox overrides because CPTabView use of CPBox differs from legacy CPBox regarding layout.
- (void)setContentView:(CPView)aView
{
if (aView === _contentView)
return;
if (_contentInset)
[aView setFrame:CGRectInsetByInset([self bounds], _contentInset)];
[aView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
// A nil contentView is allowed (tested in Cocoa 2013-02-22).
if (!aView)
[_contentView removeFromSuperview];
else if (_contentView)
[_boxView replaceSubview:_contentView with:aView];
else
[_boxView addSubview:aView];
_contentView = aView;
[self refreshDisplay];
}
- (void)sizeToFit
{
var offset = [self _titleHeightOffset],
size = [self frameSize];
[_boxView setFrame:CGRectMake(0, offset[1], size.width, size.height - offset[0])];
if (!_contentView)
return;
var boxSize = [_boxView frameSize];
if (_contentInset)
[_contentView setFrame:CGRectMake(_contentInset.left, _contentInset.top, boxSize.width - _contentInset.left - _contentInset.right, boxSize.height - _contentInset.top - _contentInset.bottom)];
}
@end @end
# pragma mark - # pragma mark -
@@ -914,7 +1015,7 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
{ {
[self setSelected:YES forSegment:_trackingSegment]; [self setSelected:YES forSegment:_trackingSegment];
_selectedSegment = _trackingSegment; _selectedSegment = _trackingSegment;
[_tabView selectTabViewItemAtIndex:_selectedSegment]; [self sendAction:[self action] to:[self target]];
} }
[self drawSegmentBezel:_trackingSegment highlight:NO]; [self drawSegmentBezel:_trackingSegment highlight:NO];
+55 -9
View File
@@ -593,7 +593,7 @@ CPTableColumnUserResizingMask = 1 << 1;
{ {
var options = [_info objectForKey:CPOptionsKey], var options = [_info objectForKey:CPOptionsKey],
optionValue = [options objectForKey:CPCreatesSortDescriptorBindingOption]; optionValue = [options objectForKey:CPCreatesSortDescriptorBindingOption];
return optionValue === nil ? YES : [optionValue boolValue]; return optionValue == nil ? YES : [optionValue boolValue];
} }
@end @end
@@ -639,6 +639,53 @@ CPTableColumnUserResizingMask = 1 << 1;
} }
} }
/*!
@ignore
This method will return the object at a row in the first found CPArray in the key path
that is divided in a first and second part.
The first part is never a combined key path. The second part can be a combined key path.
If this optimization is not done we will create an array with the valueForKeyPath value on each row and then pick
the wanted value for the row and throw away all the other rows. It is much more effective to first
pick the row and then do the valueForKeyPath on the rest of the key path.
When the second part is depleated it will stop the search for a CPArray and return the
current object for the first part. The second part will then be nil.
The secondPartRef will always be updated with the rest of the key path that can be applied
to the returned object.
It will stop the search if the object is nil
*/
- (CPValueCoding)_firstObjectInArrayUsingKeyPathFirstPart:(CPString)firstPart secondPart:(CPStringRef)secondPartRef sourceObject:(CPValueCoding)source forRow:(unsigned)aRow
{
var firstValue = [source valueForKeyPath:firstPart];
if (firstValue == nil)
return firstValue;
if ([firstValue isKindOfClass:CPArray])
return [firstValue objectAtIndex:aRow];
var secondPart = @deref(secondPartRef);
if (secondPart == nil)
return firstValue;
var dotIndex = secondPart.indexOf(".");
if (dotIndex === CPNotFound)
{
firstPart = secondPart;
@deref(secondPartRef) = nil;
}
else
{
firstPart = secondPart.substring(0, dotIndex);
@deref(secondPartRef) = secondPart.substring(dotIndex + 1);
}
return [self _firstObjectInArrayUsingKeyPathFirstPart:firstPart secondPart:secondPartRef sourceObject:firstValue forRow:aRow];
}
/*! /*!
@ignore @ignore
*/ */
@@ -655,7 +702,7 @@ CPTableColumnUserResizingMask = 1 << 1;
bindingInfo = binding._info, bindingInfo = binding._info,
destination = [bindingInfo objectForKey:CPObservedObjectKey], destination = [bindingInfo objectForKey:CPObservedObjectKey],
keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey], keyPath = [bindingInfo objectForKey:CPObservedKeyPathKey],
dotIndex = keyPath.lastIndexOf("."), dotIndex = keyPath.indexOf("."),
value; value;
if (dotIndex === CPNotFound) if (dotIndex === CPNotFound)
@@ -670,17 +717,16 @@ CPTableColumnUserResizingMask = 1 << 1;
The optimization is to get the array and access the value directly. This The optimization is to get the array and access the value directly. This
turns the operation into a single access regardless of how long the model turns the operation into a single access regardless of how long the model
array is. array is or how long the key path is.
*/ */
var firstPart = keyPath.substring(0, dotIndex), var firstPart = keyPath.substring(0, dotIndex),
secondPart = keyPath.substring(dotIndex + 1), secondPart = keyPath.substring(dotIndex + 1);
firstValue = [destination valueForKeyPath:firstPart];
if ([firstValue isKindOfClass:CPArray]) value = [self _firstObjectInArrayUsingKeyPathFirstPart:firstPart secondPart:@ref(secondPart) sourceObject:destination forRow:aRow];
value = [[firstValue objectAtIndex:aRow] valueForKeyPath:secondPart];
else if (secondPart != nil)
value = [[firstValue valueForKeyPath:secondPart] objectAtIndex:aRow]; value = [value valueForKeyPath:secondPart];
} }
value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]]; value = [binding transformValue:value withOptions:[bindingInfo objectForKey:CPOptionsKey]];
+2 -2
View File
@@ -108,7 +108,7 @@
return [_textField text]; return [_textField text];
} }
- (void)textField - (CPTextField)textField
{ {
return _textField; return _textField;
} }
@@ -550,7 +550,7 @@ var CPTableHeaderViewResizeZone = 3.0,
- (void)_autoscroll:(CPEvent)theEvent localLocation:(CGPoint)theLocation - (void)_autoscroll:(CPEvent)theEvent localLocation:(CGPoint)theLocation
{ {
// Constrain the y coordinate so we don't autoscroll vertically // Constrain the y coordinate so we don't autoscroll vertically
var constrainedLocation = CGPointMake(theLocation.x, CGRectGetMinY([_tableView visibleRect])), var constrainedLocation = CGPointMake(theLocation.x, CGRectGetMaxY([self frame])),
constrainedEvent = [CPEvent mouseEventWithType:CPLeftMouseDragged constrainedEvent = [CPEvent mouseEventWithType:CPLeftMouseDragged
location:[self convertPoint:constrainedLocation toView:nil] location:[self convertPoint:constrainedLocation toView:nil]
modifierFlags:[theEvent modifierFlags] modifierFlags:[theEvent modifierFlags]
+12 -11
View File
@@ -2977,7 +2977,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
oldMainSortDescriptor = [[self sortDescriptors] objectAtIndex: 0]; oldMainSortDescriptor = [[self sortDescriptors] objectAtIndex: 0];
// Remove every main descriptor equivalents (normally only one) // Remove every main descriptor equivalents (normally only one)
while ((descriptor = [e nextObject]) !== nil) while ((descriptor = [e nextObject]) != nil)
{ {
if ([[descriptor key] isEqual: [newMainSortDescriptor key]]) if ([[descriptor key] isEqual: [newMainSortDescriptor key]])
[outdatedDescriptors addObject:descriptor]; [outdatedDescriptors addObject:descriptor];
@@ -3154,7 +3154,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
// Now a view that clips the column data views, which itself is clipped to the content view // Now a view that clips the column data views, which itself is clipped to the content view
var columnVisRect = CGRectIntersection(columnRect, visibleRect); var columnVisRect = CGRectIntersection(columnRect, visibleRect);
frame = CGRectMake(0.0, CGRectGetHeight(headerFrame), CGRectGetWidth(columnVisRect), CGRectGetHeight(columnVisRect)); frame = CGRectMake(0.0, CGRectGetHeight(headerFrame), CGRectGetWidth(columnRect), CGRectGetHeight(columnVisRect));
var columnClipView = [[CPView alloc] initWithFrame:frame]; var columnClipView = [[CPView alloc] initWithFrame:frame];
@@ -3189,6 +3189,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
// While dragging, the column is deselected in the table view // While dragging, the column is deselected in the table view
[_selectedColumnIndexes removeIndex:columnIndex]; [_selectedColumnIndexes removeIndex:columnIndex];
[self setNeedsDisplay:YES];
return dragView; return dragView;
} }
@@ -3318,7 +3319,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
var oldSortDescriptors = [[self sortDescriptors] copy], var oldSortDescriptors = [[self sortDescriptors] copy],
newSortDescriptors = [CPArray array]; newSortDescriptors = [CPArray array];
if (sortDescriptors !== nil) if (sortDescriptors != nil)
[newSortDescriptors addObjectsFromArray:sortDescriptors]; [newSortDescriptors addObjectsFromArray:sortDescriptors];
if ([newSortDescriptors isEqual:oldSortDescriptors]) if ([newSortDescriptors isEqual:oldSortDescriptors])
@@ -3379,7 +3380,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
objectValue = tableColumnObjectValues[aRowIndex]; objectValue = tableColumnObjectValues[aRowIndex];
// tableView:objectValueForTableColumn:row: is optional if content bindings are in place. // tableView:objectValueForTableColumn:row: is optional if content bindings are in place.
if (objectValue === undefined) if (objectValue == nil)
{ {
if ([self _dataSourceRespondsToObjectValueForTableColumn]) if ([self _dataSourceRespondsToObjectValueForTableColumn])
{ {
@@ -3834,7 +3835,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
else if ([self _delegateRespondsToDataViewForTableColumn]) else if ([self _delegateRespondsToDataViewForTableColumn])
_viewForTableColumnRowSelector = @selector(_sendDelegateDataViewForTableColumn:row:); _viewForTableColumnRowSelector = @selector(_sendDelegateDataViewForTableColumn:row:);
_isViewBased = (_viewForTableColumnRowSelector !== nil || _archivedDataViews !== nil); _isViewBased = (_viewForTableColumnRowSelector != nil || _archivedDataViews != nil);
} }
/*! /*!
@@ -4723,7 +4724,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
dropOperation = [self _proposedDropOperationAtPoint:location], dropOperation = [self _proposedDropOperationAtPoint:location],
row = [self _proposedRowAtPoint:location]; row = [self _proposedRowAtPoint:location];
if (_retargetedDropRow !== nil) if (_retargetedDropRow != nil)
row = _retargetedDropRow; row = _retargetedDropRow;
var draggedTypes = [self registeredDraggedTypes], var draggedTypes = [self registeredDraggedTypes],
@@ -4779,7 +4780,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
*/ */
- (CPTableViewDropOperation)_proposedDropOperationAtPoint:(CGPoint)theDragPoint - (CPTableViewDropOperation)_proposedDropOperationAtPoint:(CGPoint)theDragPoint
{ {
if (_retargetedDropOperation !== nil) if (_retargetedDropOperation != nil)
return _retargetedDropOperation; return _retargetedDropOperation;
var row = [self _proposedRowAtPoint:theDragPoint], var row = [self _proposedRowAtPoint:theDragPoint],
@@ -4858,10 +4859,10 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
row = [self _proposedRowAtPoint:location], row = [self _proposedRowAtPoint:location],
dragOperation = [self _sendDataSourceValidateDrop:sender proposedRow:row proposedDropOperation:dropOperation]; dragOperation = [self _sendDataSourceValidateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
if (_retargetedDropRow !== nil) if (_retargetedDropRow != nil)
row = _retargetedDropRow; row = _retargetedDropRow;
if (_retargetedDropOperation !== nil) if (_retargetedDropOperation != nil)
dropOperation = _retargetedDropOperation; dropOperation = _retargetedDropOperation;
if (dropOperation === CPTableViewDropOn && row >= numberOfRows) if (dropOperation === CPTableViewDropOn && row >= numberOfRows)
@@ -4906,7 +4907,7 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
operation = [self _proposedDropOperationAtPoint:location], operation = [self _proposedDropOperationAtPoint:location],
row = _retargetedDropRow; row = _retargetedDropRow;
if (row === nil) if (row == nil)
row = [self _proposedRowAtPoint:location]; row = [self _proposedRowAtPoint:location];
return [self _sendDataSourceAcceptDrop:sender row:row dropOperation:operation]; return [self _sendDataSourceAcceptDrop:sender row:row dropOperation:operation];
@@ -6368,7 +6369,7 @@ var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
if (tableView._draggedColumnIsSelected) if (tableView._draggedColumnIsSelected)
{ {
CGContextSetFillColor(context, [tableView selectionHighlightColor]); CGContextSetFillColor(context, [tableView _isFocused] ? [tableView selectionHighlightColor] : [tableView unfocusedSelectionHighlightColor]);
CGContextFillRect(context, bounds); CGContextFillRect(context, bounds);
} }
else else
+17 -1
View File
@@ -84,6 +84,18 @@ CPCenterTextAlignment = 2;
CPJustifiedTextAlignment = 3; CPJustifiedTextAlignment = 3;
CPNaturalTextAlignment = 4; CPNaturalTextAlignment = 4;
@typedef CPUnderlineStyle
CPUnderlineStyleNone = 0;
CPUnderlineStyleSingle = 1;
CPUnderlineStyleThick = 2;
CPUnderlineStyleDouble = 3;
CPUnderlineStylePatternSolid = 4;
CPUnderlineStylePatternDot = 5;
CPUnderlineStylePatternDash = 6;
CPUnderlineStylePatternDashDot = 7;
CPUnderlineStylePatternDashDotDot = 8;
CPUnderlineStyleByWord = 9;
/* /*
CPText notifications CPText notifications
*/ */
@@ -167,7 +179,11 @@ CPKernAttributeName = @"CPKernAttributeName";
{ {
var pasteboard = [CPPasteboard generalPasteboard], var pasteboard = [CPPasteboard generalPasteboard],
dataForPasting = [pasteboard stringForType:CPRTFPboardType], dataForPasting = [pasteboard stringForType:CPRTFPboardType],
stringForPasting = [pasteboard stringForType:CPStringPboardType]; stringForPasting = [pasteboard stringForType:CPStringPboardType],
attributedStringData = [pasteboard stringForType:_CPASPboardType];
if ([self isRichText] && attributedStringData)
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPData dataWithRawString:attributedStringData]];
if (dataForPasting || [stringForPasting hasPrefix:"{\\rtf1\\ansi"]) if (dataForPasting || [stringForPasting hasPrefix:"{\\rtf1\\ansi"])
stringForPasting = [[_CPRTFParser new] parseRTF:dataForPasting ? dataForPasting : stringForPasting]; stringForPasting = [[_CPRTFParser new] parseRTF:dataForPasting ? dataForPasting : stringForPasting];
+208 -31
View File
@@ -34,6 +34,8 @@
@global CPStringPboardType @global CPStringPboardType
@global CPCursor @global CPCursor
@global document
@protocol CPTextFieldDelegate <CPControlTextEditingDelegate> @protocol CPTextFieldDelegate <CPControlTextEditingDelegate>
@end @end
@@ -169,7 +171,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
+ (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme + (CPTextField)textFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
{ {
var textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)]; var minSize = aTheme ? [aTheme valueForAttributeWithName:@"min-size" forClass:CPTextField] : CGSizeMake(0,0),
textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, minSize.height)];
[textField setTheme:aTheme]; [textField setTheme:aTheme];
[textField setStringValue:aStringValue]; [textField setStringValue:aStringValue];
@@ -190,7 +193,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
+ (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme + (CPTextField)roundedTextFieldWithStringValue:(CPString)aStringValue placeholder:(CPString)aPlaceholder width:(float)aWidth theme:(CPTheme)aTheme
{ {
var textField = [[CPTextField alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, 29.0)]; var minSize = aTheme ? [aTheme valueForAttributeWithName:@"min-size" forClass:CPTextField] : CGSizeMake(0,0),
textField = [[self alloc] initWithFrame:CGRectMake(0.0, 0.0, aWidth, minSize.height)];
[textField setTheme:aTheme]; [textField setTheme:aTheme];
[textField setStringValue:aStringValue]; [textField setStringValue:aStringValue];
@@ -231,6 +235,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
@"bezel-inset": CGInsetMakeZero(), @"bezel-inset": CGInsetMakeZero(),
@"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0), @"content-inset": CGInsetMake(1.0, 0.0, 0.0, 0.0),
@"bezel-color": [CPNull null], @"bezel-color": [CPNull null],
@"min-size": CGSizeMake(0, 29),
@"background-inset": CGInsetMakeZero()
}; };
} }
@@ -365,8 +371,6 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self setPlaceholderString:@""]; [self setPlaceholderString:@""];
_sendActionOn = CPKeyUpMask | CPKeyDownMask; _sendActionOn = CPKeyUpMask | CPKeyDownMask;
[self setValue:CPNaturalTextAlignment forThemeAttribute:@"alignment"];
} }
return self; return self;
@@ -557,9 +561,9 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
Sets the background color, which is shown for non-bezeled text fields with drawsBackground set to YES Sets the background color, which is shown for non-bezeled text fields with drawsBackground set to YES
@param aColor The background color @param aColor The background color
*/ */
- (void)setTextFieldBackgroundColor:(CPColor)aColor - (void)setBackgroundColor:(CPColor)aColor
{ {
if (_textFieldBackgroundColor == aColor) if (_backgroundColor == aColor)
return; return;
_textFieldBackgroundColor = aColor; _textFieldBackgroundColor = aColor;
@@ -571,7 +575,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
/*! /*!
Returns the background color. Returns the background color.
*/ */
- (CPColor)textFieldBackgroundColor - (CPColor)backgroundColor
{ {
return _textFieldBackgroundColor; return _textFieldBackgroundColor;
} }
@@ -686,8 +690,38 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
} }
else else
{ {
var point = CGPointMake([self convertPointFromBase:[[CPApp currentEvent] locationInWindow]].x - [self currentValueForThemeAttribute:@"content-inset"].left, 0), var x = [self convertPointFromBase:[[CPApp currentEvent] locationInWindow]].x,
position = [CPPlatformString charPositionOfString:[self stringValue] withFont:[self font] forPoint:point]; contentInset = [self currentValueForThemeAttribute:@"content-inset"],
text = [self stringValue],
font = [self font];
switch ([self alignment]) {
case CPCenterTextAlignment:
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
x -= (contentWidth - textWidth) / 2 + contentInset.left;
break;
case CPRightTextAlignment:
var contentWidth = [self bounds].size.width - contentInset.left - contentInset.right,
textWidth = [text sizeWithFont:font].width;
x -= (contentWidth - textWidth) + contentInset.left;
break;
default: // CPLeftTextAlignment, CPJustifiedTextAlignment, CPNaturalTextAlignment
x -= contentInset.left;
break;
}
var position = [CPPlatformString charPositionOfString:text withFont:font forPoint:CGPointMake(x, 0)];
[self setSelectedRange:CPMakeRange(position, 0)]; [self setSelectedRange:CPMakeRange(position, 0)];
} }
@@ -726,7 +760,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
#if PLATFORM(DOM) #if PLATFORM(DOM)
var element = [self _inputElement], var element = [self _inputElement],
font = [self currentValueForThemeAttribute:@"font"], font = [self font],
lineHeight = [font defaultLineHeightForFont], lineHeight = [font defaultLineHeightForFont],
contentRect = [self contentRectForBounds:[self bounds]], contentRect = [self contentRectForBounds:[self bounds]],
verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"], verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"],
@@ -783,7 +817,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
element.style.zIndex = 1000; element.style.zIndex = 1000;
element.style.top = topPoint; element.style.top = topPoint;
element.style.lineHeight = ROUND(lineHeight) + "px"; element.style.lineHeight = ROUND(lineHeight) + "px";
element.style.height = isTextArea ? CGRectGetHeight(contentRect) + "px" : ROUND(lineHeight) + "px";; element.style.height = isTextArea ? CGRectGetHeight(contentRect) + "px" : ROUND(lineHeight) + "px";
element.style.width = CGRectGetWidth(contentRect) + "px"; element.style.width = CGRectGetWidth(contentRect) + "px";
element.style.left = left + "px"; element.style.left = left + "px";
element.style.verticalAlign = "top"; element.style.verticalAlign = "top";
@@ -1092,7 +1126,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
- (void)keyDown:(CPEvent)anEvent - (void)keyDown:(CPEvent)anEvent
{ {
if (!([self isEnabled] && [self isEditable])) // Has to be enabled, and it also has to be editable or selectable.
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
return; return;
// CPTextField uses an HTML input element to take the input so we need to // CPTextField uses an HTML input element to take the input so we need to
@@ -1331,7 +1366,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
// If there is a formatter, make sure the object value can be formatted successfully // If there is a formatter, make sure the object value can be formatted successfully
var formattedString = [self hasThemeState:CPThemeStateEditing] ? [formatter editingStringForObjectValue:aValue] : [formatter stringForObjectValue:aValue]; var formattedString = [self hasThemeState:CPThemeStateEditing] ? [formatter editingStringForObjectValue:aValue] : [formatter stringForObjectValue:aValue];
if (formattedString === nil) if (formattedString == nil)
{ {
var value = nil; var value = nil;
@@ -1341,7 +1376,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
value = undefined; value = undefined;
[super setObjectValue:value]; [super setObjectValue:value];
_stringValue = (value === nil || value === undefined) ? @"" : String(value); _stringValue = (value == nil) ? @"" : String(value);
} }
else else
_stringValue = formattedString; _stringValue = formattedString;
@@ -1428,7 +1463,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
lineBreakMode = [self lineBreakMode], lineBreakMode = [self lineBreakMode],
text = (_stringValue || @" "), text = (_stringValue || @" "),
textSize = CGSizeMakeCopy(frameSize), textSize = CGSizeMakeCopy(frameSize),
font = [self currentValueForThemeAttribute:@"font"]; font = [self font];
textSize.width -= contentInset.left + contentInset.right; textSize.width -= contentInset.left + contentInset.right;
textSize.height -= contentInset.top + contentInset.bottom; textSize.height -= contentInset.top + contentInset.bottom;
@@ -1437,7 +1472,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
![self isBezeled] && ![self isBezeled] &&
(lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping)) (lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
{ {
textSize = [text sizeWithFont:font inWidth:textSize.width]; textSize = [text sizeWithFont:font inWidth:textSize.width] + 1;
} }
else else
{ {
@@ -1497,7 +1532,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
else else
[[CPRunLoop mainRunLoop] performBlock:function(){ element.select(); } argument:nil order:0 modes:[CPDefaultRunLoopMode]]; [[CPRunLoop mainRunLoop] performBlock:function(){ element.select(); } argument:nil order:0 modes:[CPDefaultRunLoopMode]];
} }
else if (wind !== nil && [wind makeFirstResponder:self]) else if (wind != nil && [wind makeFirstResponder:self])
[self _selectText:sender immediately:immediately]; [self _selectText:sender immediately:immediately];
} }
else else
@@ -1507,7 +1542,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
#else #else
// Even if we can't actually select the text we need to preserve the first // Even if we can't actually select the text we need to preserve the first
// responder side effect. // responder side effect.
if (wind !== nil && [wind firstResponder] !== self) if (wind != nil && [wind firstResponder] !== self)
[wind makeFirstResponder:self]; [wind makeFirstResponder:self];
#endif #endif
} }
@@ -1832,6 +1867,12 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (aName === "bezel-view") if (aName === "bezel-view")
return [self bezelRectForBounds:[self bounds]]; return [self bezelRectForBounds:[self bounds]];
else if (aName === "background-view")
{
var backgroundInset = [self currentValueForThemeAttribute:@"background-inset"];
return CGRectInsetByInset([self bounds], backgroundInset);
}
else if (aName === "content-view") else if (aName === "content-view")
return [self contentRectForBounds:[self bounds]]; return [self contentRectForBounds:[self bounds]];
@@ -1848,6 +1889,14 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return view; return view;
} }
else if (aName === "background-view")
{
var view = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[view setHitTests:NO];
return view;
}
else else
{ {
var view = [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()]; var view = [[_CPImageAndTextView alloc] initWithFrame:CGRectMakeZero()];
@@ -1862,16 +1911,37 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
- (void)layoutSubviews - (void)layoutSubviews
{ {
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view" var bezelColor = [self currentValueForThemeAttribute:@"bezel-color"];
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"]; if ([bezelColor isCSSBased])
{
// CSS Styling
// We don't need bezelView as we apply CSS styling directly on the text field view itself
if (bezelView) // We need to call [super setBackgroundColor:] as we have redefined it here
[bezelView setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]]; [super setBackgroundColor:bezelColor];
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:nil],
backgroundView = [self layoutEphemeralSubviewNamed:@"background-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
var contentView = [self layoutEphemeralSubviewNamed:@"content-view" [backgroundView setBackgroundColor:(_drawsBackground ? _textFieldBackgroundColor : [CPColor clearColor])];
positioned:CPWindowAbove }
relativeToEphemeralSubviewNamed:@"bezel-view"]; else
{
var bezelView = [self layoutEphemeralSubviewNamed:@"bezel-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:@"content-view"];
[bezelView setBackgroundColor:bezelColor];
var contentView = [self layoutEphemeralSubviewNamed:@"content-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
}
if (contentView) if (contentView)
{ {
@@ -1892,7 +1962,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[contentView setText:string]; [contentView setText:string];
[contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]]; [contentView setTextColor:[self currentValueForThemeAttribute:@"text-color"]];
[contentView setFont:[self currentValueForThemeAttribute:@"font"]]; [contentView setFont:[self font]];
[contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]]; [contentView setAlignment:[self currentValueForThemeAttribute:@"alignment"]];
[contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]]; [contentView setVerticalAlignment:[self currentValueForThemeAttribute:@"vertical-alignment"]];
[contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]]; [contentView setLineBreakMode:[self currentValueForThemeAttribute:@"line-break-mode"]];
@@ -1932,8 +2002,28 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
// We don't want to change the text-color of the placeHolder of the textField // We don't want to change the text-color of the placeHolder of the textField
var placeholderColor = [self valueForThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder]; var placeholderColor = [self valueForThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder];
[super setTextColor:aColor]; // If the text field is a cell based table data view, we need to fix the color for all possible states
if ([self hasThemeState:CPThemeStateTableDataView])
{
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView]];
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView, CPThemeStateSelectedDataView]];
[self setTextColor:aColor inThemeStates:[CPThemeStateTableDataView, CPThemeStateSelectedDataView, CPThemeStateFirstResponder, CPThemeStateKeyWindow]];
}
else
{
if ([self hasThemeState:CPTextFieldStateRounded])
{
[self setTextColor:aColor inThemeStates:[CPTextFieldStateRounded]];
[self setTextColor:aColor inThemeStates:[CPTextFieldStateRounded, CPThemeStateEditing]];
}
[self setTextColor:aColor inThemeStates:[CPThemeStateNormal]];
[self setTextColor:aColor inThemeStates:[CPThemeStateEditing]];
}
[self setValue:placeholderColor forThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder]; [self setValue:placeholderColor forThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder];
[self layoutSubviews];
} }
- (void)viewDidHide - (void)viewDidHide
@@ -2075,8 +2165,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
[self setSelectable:[aCoder decodeBoolForKey:CPTextFieldIsSelectableKey]]; [self setSelectable:[aCoder decodeBoolForKey:CPTextFieldIsSelectableKey]];
[self setDrawsBackground:[aCoder decodeBoolForKey:CPTextFieldDrawsBackgroundKey]]; [self setDrawsBackground:[aCoder decodeBoolForKey:CPTextFieldDrawsBackgroundKey]];
[self setBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
[self setTextFieldBackgroundColor:[aCoder decodeObjectForKey:CPTextFieldBackgroundColorKey]];
[self setLineBreakMode:[aCoder decodeIntForKey:CPTextFieldLineBreakModeKey]]; [self setLineBreakMode:[aCoder decodeIntForKey:CPTextFieldLineBreakModeKey]];
[self setAlignment:[aCoder decodeIntForKey:CPTextFieldAlignmentKey]]; [self setAlignment:[aCoder decodeIntForKey:CPTextFieldAlignmentKey]];
@@ -2086,6 +2175,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
[self _setUsesSingleLineMode:[aCoder decodeBoolForKey:CPTextFieldUsesSingleLineMode]]; [self _setUsesSingleLineMode:[aCoder decodeBoolForKey:CPTextFieldUsesSingleLineMode]];
[self _setWraps:[aCoder decodeBoolForKey:CPTextFieldWraps]]; [self _setWraps:[aCoder decodeBoolForKey:CPTextFieldWraps]];
[self _setScrolls:[aCoder decodeBoolForKey:CPTextFieldScrolls]]; [self _setScrolls:[aCoder decodeBoolForKey:CPTextFieldScrolls]];
[self updateTrackingAreas];
} }
return self; return self;
@@ -2120,6 +2210,56 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
@implementation _CPTextFieldValueBinder : CPBinder @implementation _CPTextFieldValueBinder : CPBinder
+ (void)unbind:(CPString)aBinding forObject:(id)anObject
{
var theBinding = [self getBinding:aBinding forObject:anObject],
notificationCenter = [CPNotificationCenter defaultCenter];
if (theBinding)
{
[notificationCenter removeObserver:[theBinding._info objectForKey:CPObservedObjectKey]
name:CPControlTextDidBeginEditingNotification
object:anObject];
[notificationCenter removeObserver:[theBinding._info objectForKey:CPObservedObjectKey]
name:CPControlTextDidEndEditingNotification
object:anObject];
[super unbind:aBinding forObject:anObject];
}
}
- (id)initWithBinding:(CPString)aBinding name:(CPString)aName to:(id)aDestination keyPath:(CPString)aKeyPath options:(CPDictionary)options from:(id)aSource
{
self = [super initWithBinding:aBinding
name:aName
to:aDestination
keyPath:aKeyPath
options:options
from:aSource];
var notificationCenter = [CPNotificationCenter defaultCenter];
// This gives us support for the CPEditorRegistration informal protocol
if ([aDestination respondsToSelector:@selector(_objectDidBeginEditing:)])
{
[notificationCenter addObserver:aDestination
selector:@selector(_objectDidBeginEditing:)
name:CPControlTextDidBeginEditingNotification
object:aSource];
}
if ([aDestination respondsToSelector:@selector(_objectDidEndEditing:)])
{
[notificationCenter addObserver:aDestination
selector:@selector(_objectDidEndEditing:)
name:CPControlTextDidEndEditingNotification
object:aSource];
}
return self;
}
- (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding - (void)_updatePlaceholdersWithOptions:(CPDictionary)options forBinding:(CPString)aBinding
{ {
[super _updatePlaceholdersWithOptions:options]; [super _updatePlaceholdersWithOptions:options];
@@ -2152,7 +2292,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
newValue = [self valueForBinding:aBinding], newValue = [self valueForBinding:aBinding],
value = [destination valueForKeyPath:keyPath]; value = [destination valueForKeyPath:keyPath];
if (CPIsControllerMarker(value) && newValue === nil) if (CPIsControllerMarker(value) && newValue == nil)
return; return;
newValue = [self reverseTransformValue:newValue withOptions:options]; newValue = [self reverseTransformValue:newValue withOptions:options];
@@ -2217,3 +2357,40 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
} }
@end @end
#pragma mark -
@implementation CPTextField (TableDataView)
// We overide here _CPObject+Theme setValue:forThemeAttribute as CPTextField can be used as tableView data view
// So, when outside a table data view, setValue:forThemeAttribute should store the value with the CPThemeStateNormal (default behavior)
// When inside a table data view, it should store the value with the CPThemeStateTableDataView. If not, the value won't be used if the
// theme defined a value for this attribute for state CPThemeStateTableDataView
- (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
{
[super setValue:aValue forThemeAttribute:aName];
[super setValue:aValue forThemeAttribute:aName inState:CPThemeStateTableDataView];
}
@end
#pragma mark -
@implementation CPTextField (Deprecated)
- (void)setTextFieldBackgroundColor:(CPColor)aColor
{
CPLog.error("[CPTextField setTextFieldBackgroundColor:] is deprecated, use [CPTextField setBackgroundColor:] instead.");
[self setBackgroundColor:aColor];
}
- (CPColor)textFieldBackgroundColor
{
CPLog.info("[CPTextField textFieldBackgroundColor] is deprecated, use [CPTextField backgroundColor] instead.");
return [self backgroundColor];
}
@end
+1 -1
View File
@@ -94,7 +94,7 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
*/ */
+ (BOOL)sharedFontPanelExists + (BOOL)sharedFontPanelExists
{ {
return _sharedFontPanel !== nil; return _sharedFontPanel != nil;
} }
/*! /*!
+92 -41
View File
@@ -2,9 +2,6 @@
* CPLayoutManager.j * CPLayoutManager.j
* AppKit * AppKit
* *
* FIXME remove from DOM when scrolled out of visible area? (as done in CPTableView)
*
*
* Created by Daniel Boehringer on 27/12/2013. * Created by Daniel Boehringer on 27/12/2013.
* All modifications copyright Daniel Boehringer 2013. * All modifications copyright Daniel Boehringer 2013.
* Extensive code formatting and review by Andrew Hankinson * Extensive code formatting and review by Andrew Hankinson
@@ -35,6 +32,8 @@
@global _MakeRangeFromAbs @global _MakeRangeFromAbs
@global document
@class CPTextContainer @class CPTextContainer
@class CPTextView @class CPTextView
@@ -259,6 +258,9 @@ _oncontextmenuhandler = function () { return false; };
{ {
if (_lineFragments[i]._isInvalid) if (_lineFragments[i]._isInvalid)
{ {
while (i > 0 && !_lineFragments[i - 1]._isLast)
i--;
startIndex = _lineFragments[i]._range.location; startIndex = _lineFragments[i]._range.location;
removeRange.location = i; removeRange.location = i;
removeRange.length = l - i; removeRange.length = l - i;
@@ -361,7 +363,7 @@ _oncontextmenuhandler = function () { return false; };
if (ABS(rangeOffset) !== ABS(newLength - oldLength)) if (ABS(rangeOffset) !== ABS(newLength - oldLength))
return NO; return NO;
var verticalOffset = _lineFragments[targetLine]._fragmentRect.origin.y - _lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect.origin.y, var verticalOffset = CGRectGetMaxY(_lineFragments[targetLine]._fragmentRect) - CGRectGetMaxY(_lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect),
l = _lineFragmentsForRescue.length, l = _lineFragmentsForRescue.length,
newTargetLine = startLineForDOMRemoval + removalSkip; newTargetLine = startLineForDOMRemoval + removalSkip;
@@ -495,16 +497,6 @@ _oncontextmenuhandler = function () { return false; };
} }
- (void)drawUnderlineForGlyphRange:(CPRange)glyphRange
underlineType:(int)underlineVal
baselineOffset:(float)baselineOffset
lineFragmentRect:(CGRect)lineFragmentRect
lineFragmentGlyphRange:(CPRange)lineGlyphRange
containerOrigin:(CGPoint)containerOrigin
{
// FIXME
}
- (void)drawGlyphsForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint - (void)drawGlyphsForGlyphRange:(CPRange)aRange atPoint:(CGPoint)aPoint
{ {
var lineFragments = _objectsInRange(_lineFragments, aRange); var lineFragments = _objectsInRange(_lineFragments, aRange);
@@ -671,12 +663,10 @@ _oncontextmenuhandler = function () { return false; };
{ {
var j = i; var j = i;
while (--j > 0 && !_lineFragments[j]._isLast) while (j > 0 && !_lineFragments[j - 1]._isLast)
{ j--;
// body intentionally left empty
}
return _lineFragments[j + 1]; return _lineFragments[j];
} }
} }
@@ -686,13 +676,16 @@ _oncontextmenuhandler = function () { return false; };
{ {
var l = _lineFragments.length; var l = _lineFragments.length;
if (location >= CPMaxRange(_lineFragments[l - 1]._range))
return _lineFragments[l - 1];
for (var i = 0; i < l; i++) for (var i = 0; i < l; i++)
{ {
if (CPLocationInRange(location, _lineFragments[i]._range)) if (CPLocationInRange(location, _lineFragments[i]._range))
{ {
var j = i; var j = i;
while (!_lineFragments[j]._isLast) while (j < l && !_lineFragments[j]._isLast)
j++; j++;
return _lineFragments[j]; return _lineFragments[j];
@@ -935,15 +928,14 @@ _oncontextmenuhandler = function () { return false; };
var correctedRect = CGRectCreateCopy(frames[j]); var correctedRect = CGRectCreateCopy(frames[j]);
correctedRect.size.height -= frames[j]._descent; correctedRect.size.height -= frames[j]._descent;
correctedRect.origin.y -= frames[j]._descent; correctedRect.origin.y -= frames[j]._descent;
if (!rect) if (!rect)
rect = CGRectCreateCopy(correctedRect); rect = CGRectCreateCopy(correctedRect);
else else
rect = CGRectUnion(rect, correctedRect); rect = CGRectUnion(rect, correctedRect);
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)])) if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)]))
{
rect.size.width = containerSize.width - rect.origin.x; rect.size.width = containerSize.width - rect.origin.x;
}
} }
} }
@@ -1094,13 +1086,17 @@ var _objectsInRange = function(aList, aRange)
#pragma mark Init methods #pragma mark Init methods
- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)aColor - (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)aColor
{
return [self createDOMElementWithText:aString andFont:aFont andColor:aColor andBackgroundColor:nil andUnderline:nil ];
}
- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)fgColor andBackgroundColor:(CPColor)bgColor andUnderline:(CPUnderlineStyle)aUnderline
{ {
#if PLATFORM(DOM) #if PLATFORM(DOM)
var style, var style,
span = document.createElement("span"); span = document.createElement("span");
span.oncontextmenu = span.onmousedown = span.onselectstart = _oncontextmenuhandler; span.oncontextmenu = span.onmousedown = span.onselectstart = _oncontextmenuhandler;
// span.contentEditable = true; // this unfortunately does not work to make native pasting work on safari
style = span.style; style = span.style;
style.position = "absolute"; style.position = "absolute";
@@ -1110,16 +1106,42 @@ var _objectsInRange = function(aList, aRange)
style.whiteSpace = "pre"; style.whiteSpace = "pre";
style.backgroundColor = "transparent"; style.backgroundColor = "transparent";
style.font = [aFont cssString]; style.font = [aFont cssString];
if (aUnderline)
{
style.textDecoration = "underline";
switch (aUnderline)
{
case CPUnderlineStyleSingle:
style.textDecorationStyle = "solid";
break;
if (aColor) case CPUnderlineStyleDouble:
style.color = [aColor cssString]; style.textDecorationStyle = "double";
break;
case CPUnderlineStylePatternDot:
style.textDecorationStyle = "dotted";
break;
case CPUnderlineStylePatternDash:
style.textDecorationStyle = "dashed";
break;
}
}
if (fgColor)
style.color = [fgColor cssString];
if (bgColor)
style.backgroundColor = [bgColor cssString];
if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature)) if (CPFeatureIsCompatible(CPJavaScriptInnerTextFeature))
span.innerText = aString; span.innerText = aString;
else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature)) else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature))
span.textContent = aString; span.textContent = aString;
//<!> FIXME aString.replace(/&/g,'&amp;')
return span; return span;
#else #else
return nil; return nil;
@@ -1139,7 +1161,9 @@ var _objectsInRange = function(aList, aRange)
_range = CPMakeRangeCopy(aRange); _range = CPMakeRangeCopy(aRange);
_textContainer = aContainer; _textContainer = aContainer;
_isInvalid = NO; _isInvalid = NO;
_runs = [[CPMutableArray alloc] init]; _runs = [];
_glyphsFrames = [];
_glyphsOffsets = [];
for (location = aRange.location; location < CPMaxRange(aRange); location = CPMaxRange(effectiveRange)) for (location = aRange.location; location < CPMaxRange(aRange); location = CPMaxRange(effectiveRange))
{ {
@@ -1148,16 +1172,30 @@ var _objectsInRange = function(aList, aRange)
effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange; effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange;
var string = [textStorage._string substringWithRange:effectiveRange], var string = [textStorage._string substringWithRange:effectiveRange],
font = [textStorage font] || [CPFont systemFontOfSize:12.0]; underline = [attributes objectForKey:CPUnderlineStyleAttributeName] || CPUnderlineStyleNone;
if ([attributes containsKey:CPFontAttributeName]) // this is an attachment -> create a run for it
font = [attributes objectForKey:CPFontAttributeName]; if (string === _CPAttachmentCharacterAsString)
{
if (![attributes objectForKey:_CPAttachmentInvisible])
{
var view = [attributes objectForKey:_CPAttachmentView],
viewCopy = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:view]],
elem = viewCopy._DOMElement,
run = {_range:CPMakeRangeCopy(effectiveRange), color:nil, font:nil, elem:elem, string:nil, view:viewCopy};
var color = [attributes objectForKey:CPForegroundColorAttributeName], _runs.push(run);
elem = [self createDOMElementWithText:string andFont:font andColor:color], }
run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string}; }
else
{
var color = [attributes objectForKey:CPForegroundColorAttributeName],
bgcolor = [attributes objectForKey:CPBackgroundColorAttributeName],
font = [attributes objectForKey:CPFontAttributeName] || [textStorage font] || [CPFont systemFontOfSize:12.0],
run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string, bgcolor:bgcolor};
_runs.push(run); _runs.push(run);
}
if (!CPMaxRange(effectiveRange)) if (!CPMaxRange(effectiveRange))
break; break;
@@ -1229,7 +1267,12 @@ var _objectsInRange = function(aList, aRange)
for (var i = 0; i < l; i++) for (var i = 0; i < l; i++)
{ {
if (_runs[i].elem && _runs[i].DOMactive) if (_runs[i].elem && _runs[i].DOMactive)
_textContainer._textView._DOMElement.removeChild(_runs[i].elem); {
if (_runs[i].view)
[_runs[i].view removeFromSuperview];
else
_textContainer._textView._DOMElement.removeChild(_runs[i].elem);
}
_runs[i].elem = nil; _runs[i].elem = nil;
_runs[i].DOMactive = NO; _runs[i].DOMactive = NO;
@@ -1247,9 +1290,7 @@ var _objectsInRange = function(aList, aRange)
var run = runs[i]; var run = runs[i];
if (!run.elem && CPRectIntersectsRect([_textContainer._textView exposedRect], _fragmentRect)) if (!run.elem && CPRectIntersectsRect([_textContainer._textView exposedRect], _fragmentRect))
{ run.elem = [self createDOMElementWithText:run.string andFont:run.font andColor:run.color andBackgroundColor:run.bgcolor andUnderline:run.underline];
run.elem=[self createDOMElementWithText:run.string andFont:run.font andColor:run.color];
}
if (run.DOMactive && !run.DOMpatched) if (run.DOMactive && !run.DOMpatched)
continue; continue;
@@ -1266,18 +1307,25 @@ var _objectsInRange = function(aList, aRange)
run.elem.style.left = (orig.x) + "px"; run.elem.style.left = (orig.x) + "px";
run.elem.style.top = (orig.y) + "px"; run.elem.style.top = (orig.y) + "px";
if (run.view)
[run.view setFrameOrigin:orig];
if (!run.DOMactive) if (!run.DOMactive)
_textContainer._textView._DOMElement.appendChild(run.elem); {
if (run.view)
[self._textContainer._textView addSubview:run.view];
else
_textContainer._textView._DOMElement.appendChild(run.elem);
}
run.DOMactive = YES; run.DOMactive = YES;
} }
run.DOMpatched = NO; run.DOMpatched = NO;
} }
} }
- (void)backgroundColorForGlyphAtIndex:(unsigned)index - (CPColor)backgroundColorForGlyphAtIndex:(unsigned)index
{ {
var run = _objectWithLocationInRange(_runs, index); var run = _objectWithLocationInRange(_runs, index);
@@ -1325,6 +1373,9 @@ var _objectsInRange = function(aList, aRange)
if (verticalOffset && _runs[i].elem) if (verticalOffset && _runs[i].elem)
{ {
if (_runs[i].view)
_runs[i].view._frame.origin.y += verticalOffset;
_runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px'; _runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
_runs[i].DOMpatched = YES; _runs[i].DOMpatched = YES;
} }
+23 -2
View File
@@ -84,6 +84,8 @@ CPLineMovesUp = 4;
CPLayoutManager _layoutManager @accessors(property=layoutManager); CPLayoutManager _layoutManager @accessors(property=layoutManager);
CPTextView _textView @accessors(property=textView); CPTextView _textView @accessors(property=textView);
BOOL _inResizing; BOOL _inResizing;
BOOL _widthTracksTextView;
BOOL _heightTracksTextView;
} }
@@ -139,11 +141,17 @@ CPLineMovesUp = 4;
} }
// Controls whether the receiver adjusts the width of its bounding rectangle when its text view is resized. // Controls whether the receiver adjusts the width of its bounding rectangle when its text view is resized.
- (BOOL)widthTracksTextView
{
return _widthTracksTextView;
}
- (void)setWidthTracksTextView:(BOOL)flag - (void)setWidthTracksTextView:(BOOL)flag
{ {
_widthTracksTextView = flag;
[_textView setPostsFrameChangedNotifications:flag]; [_textView setPostsFrameChangedNotifications:flag];
if (flag) if (flag && _textView)
{ {
[[CPNotificationCenter defaultCenter] addObserver:self [[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewFrameChanged:) selector:@selector(textViewFrameChanged:)
@@ -168,12 +176,18 @@ CPLineMovesUp = 4;
- (void)setTextView:(CPTextView)aTextView - (void)setTextView:(CPTextView)aTextView
{ {
if (_textView) if (_textView)
{
[self setWidthTracksTextView:NO]; // We only support width
[_textView setTextContainer:nil]; [_textView setTextContainer:nil];
}
_textView = aTextView; _textView = aTextView;
if (_textView) if (_textView)
{
[self setWidthTracksTextView:_widthTracksTextView]; // We only support width
[_textView setTextContainer:self]; [_textView setTextContainer:self];
}
[_layoutManager textContainerChangedTextView:self]; [_layoutManager textContainerChangedTextView:self];
} }
@@ -222,7 +236,9 @@ CPLineMovesUp = 4;
var CPTextContainerSizeKey = @"CPTextContainerSizeKey", var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey"; CPTextContainerLayoutManagerKey = @"CPTextContainerLayoutManagerKey",
CPTextContainerWidthTracksTextViewKey = @"CPTextContainerWidthTracksTextViewKey",
CPTextContainerHeightTracksTextViewKey = @"CPTextContainerHeightTracksTextViewKey";
@implementation CPTextContainer (CPCoding) @implementation CPTextContainer (CPCoding)
@@ -238,6 +254,9 @@ var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
_layoutManager = [aCoder decodeObjectForKey:CPTextContainerLayoutManagerKey]; _layoutManager = [aCoder decodeObjectForKey:CPTextContainerLayoutManagerKey];
[_layoutManager addTextContainer:self]; [_layoutManager addTextContainer:self];
_widthTracksTextView = [aCoder decodeBoolForKey:CPTextContainerWidthTracksTextViewKey];
_heightTracksTextView = [aCoder decodeBoolForKey:CPTextContainerHeightTracksTextViewKey];
} }
return self; return self;
@@ -247,6 +266,8 @@ var CPTextContainerSizeKey = @"CPTextContainerSizeKey",
{ {
[aCoder encodeSize:_size forKey:CPTextContainerSizeKey]; [aCoder encodeSize:_size forKey:CPTextContainerSizeKey];
[aCoder encodeObject:_layoutManager forKey:CPTextContainerLayoutManagerKey]; [aCoder encodeObject:_layoutManager forKey:CPTextContainerLayoutManagerKey];
[aCoder encodeBool:_widthTracksTextView forKey:CPTextContainerWidthTracksTextViewKey];
[aCoder encodeBool:_heightTracksTextView forKey:CPTextContainerHeightTracksTextViewKey];
} }
@end @end
+22 -24
View File
@@ -42,6 +42,11 @@ CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNot
@end @end
CPAttachmentCharacter = 65532; // "\ufffc";
_CPAttachmentCharacterAsString = String.fromCharCode(CPAttachmentCharacter);
_CPAttachmentView = "_CPAttachmentView";
_CPAttachmentInvisible = "_CPAttachmentInvisible";
var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1, var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
CPTextStorageDelegate_textStorageDidProcessEditing_ = 1 << 2; CPTextStorageDelegate_textStorageDidProcessEditing_ = 1 << 2;
@@ -122,7 +127,7 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
- (void)addLayoutManager:(CPLayoutManager)aManager - (void)addLayoutManager:(CPLayoutManager)aManager
{ {
if ([_layoutManagers containsObject:aManager]) if ([_layoutManagers containsObject:aManager])
return return;
[aManager setTextStorage:self]; [aManager setTextStorage:self];
[_layoutManagers addObject:aManager]; [_layoutManagers addObject:aManager];
@@ -131,7 +136,7 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
- (void)removeLayoutManager:(CPLayoutManager)aManager - (void)removeLayoutManager:(CPLayoutManager)aManager
{ {
if (![_layoutManagers containsObject:aManager]) if (![_layoutManagers containsObject:aManager])
return return;
[aManager setTextStorage:nil]; [aManager setTextStorage:nil];
[_layoutManagers removeObject:aManager]; [_layoutManagers removeObject:aManager];
@@ -257,6 +262,21 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
return [super attributedSubstringFromRange:aRange]; return [super attributedSubstringFromRange:aRange];
} }
/*!
Returns an instance of CPTextStorage that contains the provided instance of CPView.
This can be used to insert arbitrary views into the text. These views are treated as individual characters during editing.
This works only with views that conform to the CPCoding protocol
*/
+ (id)attributedStringWithAttachment:(CPView)someView
{
var result = [[self alloc] initWithString:_CPAttachmentCharacterAsString];
[result setAttributes:@{_CPAttachmentView:someView} range:CPMakeRange(0, 1)];
return result;
}
@end @end
@@ -279,25 +299,3 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
} }
@end @end
@implementation CPTextStorage (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
}
@end
+181 -110
View File
@@ -37,6 +37,7 @@
@class CPClipView; @class CPClipView;
@class CPMenu;
@class _CPSelectionBox; @class _CPSelectionBox;
@class _CPCaret; @class _CPCaret;
@class _CPNativeInputManager; @class _CPNativeInputManager;
@@ -50,9 +51,12 @@
- (CPRange)textView:(CPTextView)aTextView willChangeSelectionFromCharacterRange:(CPRange)oldSelectedCharRange toCharacterRange:(CPRange)newSelectedCharRange; - (CPRange)textView:(CPTextView)aTextView willChangeSelectionFromCharacterRange:(CPRange)oldSelectedCharRange toCharacterRange:(CPRange)newSelectedCharRange;
- (void)textViewDidChangeSelection:(CPNotification)aNotification; - (void)textViewDidChangeSelection:(CPNotification)aNotification;
- (void)textViewDidChangeTypingAttributes:(CPNotification)aNotification; - (void)textViewDidChangeTypingAttributes:(CPNotification)aNotification;
- (CPMenu)textView:(CPTextView)view menu:(CPMenu)menu forEvent:(CPEvent)event atIndex:(unsigned)charIndex;
@end @end
@global document
_MakeRangeFromAbs = function(a1, a2) _MakeRangeFromAbs = function(a1, a2)
{ {
return (a1 < a2) ? CPMakeRange(a1, a2 - a1) : CPMakeRange(a2, a1 - a2); return (a1 < a2) ? CPMakeRange(a1, a2 - a1) : CPMakeRange(a2, a1 - a2);
@@ -105,6 +109,7 @@ var kDelegateRespondsTo_textShouldBeginEditing
kDelegateRespondsTo_textView_didChangeTypingAttributes = 1 << 8, kDelegateRespondsTo_textView_didChangeTypingAttributes = 1 << 8,
kDelegateRespondsTo_textView_textDidBeginEditing = 1 << 9, kDelegateRespondsTo_textView_textDidBeginEditing = 1 << 9,
kDelegateRespondsTo_textView_textDidEndEditing = 1 << 10; kDelegateRespondsTo_textView_textDidEndEditing = 1 << 10;
kDelegateRespondsTo_textView_menu_forEvent_atIndex = 1 << 11;
@class _CPCaret; @class _CPCaret;
@@ -170,6 +175,20 @@ var kDelegateRespondsTo_textShouldBeginEditing
return [super _binderClassForBinding:aBinding]; return [super _binderClassForBinding:aBinding];
} }
+ (CPMenu)defaultMenu
{
var editMenu = [CPMenu new];
// FIXME I8N
[editMenu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"];
[editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
[editMenu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"];
[editMenu setAutoenablesItems:NO]
return editMenu;
}
#pragma mark - #pragma mark -
#pragma mark Class methods #pragma mark Class methods
@@ -437,10 +456,11 @@ var kDelegateRespondsTo_textShouldBeginEditing
stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)], stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)],
richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}]; richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}];
[pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType] owner:nil]; [pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType, _CPSmartPboardType, _CPASPboardType] owner:nil];
[pasteboard setString:stringForPasting._string forType:CPStringPboardType]; [pasteboard setString:[stringForPasting._string stringByReplacingOccurrencesOfString:_CPAttachmentCharacterAsString withString:''] forType:CPStringPboardType];
[pasteboard setString:richData forType:CPRTFPboardType]; [pasteboard setString:richData forType:CPRTFPboardType];
[pasteboard setString:_previousSelectionGranularity + '' forType:_CPSmartPboardType]; [pasteboard setString:_previousSelectionGranularity + '' forType:_CPSmartPboardType];
[pasteboard setString:[[CPKeyedArchiver archivedDataWithRootObject:stringForPasting] rawString] forType:_CPASPboardType];
} }
- (void)_pasteString:(id)stringForPasting - (void)_pasteString:(id)stringForPasting
@@ -450,12 +470,20 @@ var kDelegateRespondsTo_textShouldBeginEditing
var shouldUseSmartPasting = [self _shouldUseSmartPasting]; var shouldUseSmartPasting = [self _shouldUseSmartPasting];
if (shouldUseSmartPasting && _selectionRange.location > 0) if (shouldUseSmartPasting)
{ {
if (!_isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]) && if (_isWhitespaceCharacter([[stringForPasting string] characterAtIndex:0]))
_selectionRange.location != [_layoutManager numberOfCharacters])
{ {
[self insertText:" "]; if (_selectionRange.location == 0 ||
_isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]) &&
_selectionRange.location != [_layoutManager numberOfCharacters])
[stringForPasting deleteCharactersInRange:CPMakeRange(0, 1)];
}
else
{
if (_selectionRange.location > 0 &
!_isWhitespaceCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]))
[self insertText:" "];
} }
} }
@@ -590,6 +618,9 @@ var kDelegateRespondsTo_textShouldBeginEditing
if ([_delegate respondsToSelector:@selector(textView:shouldChangeTypingAttributes:toAttributes:)]) if ([_delegate respondsToSelector:@selector(textView:shouldChangeTypingAttributes:toAttributes:)])
_delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes; _delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_shouldChangeTypingAttributes_toAttributes;
if ([_delegate respondsToSelector:@selector(textView:menu:forEvent:atIndex:)])
_delegateRespondsToSelectorMask |= kDelegateRespondsTo_textView_menu_forEvent_atIndex;
if (_superview) if (_superview)
[self _addDelegateObservers]; [self _addDelegateObservers];
} }
@@ -668,7 +699,7 @@ var kDelegateRespondsTo_textShouldBeginEditing
[self setString:aValue]; [self setString:aValue];
} }
- (void)setString:(id)aString - (void)setString:(CPString)aString
{ {
if ([aString isKindOfClass:[CPAttributedString class]]) if ([aString isKindOfClass:[CPAttributedString class]])
{ {
@@ -951,7 +982,7 @@ Sets the selection to a range of characters in response to user action.
if (!isNewSelection && _mouseDownOldSelection) if (!isNewSelection && _mouseDownOldSelection)
isNewSelection = !CPEqualRanges(newSelectionRange, _mouseDownOldSelection); isNewSelection = !CPEqualRanges(newSelectionRange, _mouseDownOldSelection);
if (doOverwrite && _placeholderString === nil && isNewSelection) if (doOverwrite && _placeholderString == nil && isNewSelection)
[self setTypingAttributes:[_textStorage attributesAtIndex:CPMaxRange(range) effectiveRange:nil]]; [self setTypingAttributes:[_textStorage attributesAtIndex:CPMaxRange(range) effectiveRange:nil]];
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeSelectionNotification object:self]; [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeSelectionNotification object:self];
@@ -982,7 +1013,7 @@ Sets the selection to a range of characters in response to user action.
// interface to the _CPNativeInputManager // interface to the _CPNativeInputManager
- (void)_activateNativeInputElement:(DOMElemet)aNativeField - (void)_activateNativeInputElement:(DOMElement)aNativeField
{ {
var attributes = [[self typingAttributes] copy]; var attributes = [[self typingAttributes] copy];
@@ -1022,7 +1053,7 @@ Sets the selection to a range of characters in response to user action.
[[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome) [[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome)
if (![_CPNativeInputManager isNativeInputFieldActive] && [event charactersIgnoringModifiers].charCodeAt(0) != 229) // filter out 229 because this would be inserted in chrome on each deadkey if (![_CPNativeInputManager isNativeInputFieldActive] && ![_CPNativeInputManager isDeadKey:event])
[self interpretKeyEvents:[event]]; [self interpretKeyEvents:[event]];
[_caret setPermanentlyVisible:YES]; [_caret setPermanentlyVisible:YES];
@@ -1065,6 +1096,17 @@ Sets the selection to a range of characters in response to user action.
return YES; return YES;
} }
- (void)_hideRange:(CPRange)rangeToHide inDragPlaceholderString:(CPTextView)placeholderString
{
if (!rangeToHide.length)
return;
[placeholderString addAttribute:CPForegroundColorAttributeName
value:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] // invisibleInk
range:rangeToHide];
[placeholderString addAttribute:_CPAttachmentInvisible value:YES range:rangeToHide];
}
#pragma mark - #pragma mark -
#pragma mark Mouse Events #pragma mark Mouse Events
@@ -1084,31 +1126,35 @@ Sets the selection to a range of characters in response to user action.
// dragging the selection // dragging the selection
if ([self selectionGranularity] == CPSelectByCharacter && CPLocationInRange(_startTrackingLocation, _selectionRange)) if ([self selectionGranularity] == CPSelectByCharacter && CPLocationInRange(_startTrackingLocation, _selectionRange))
{ {
var lineBeginningIndex = [_layoutManager _firstLineFragmentForLineFromLocation:_selectionRange.location]._range.location, var visibleRange = [_layoutManager glyphRangeForBoundingRect:[self exposedRect] inTextContainer:_textContainer],
placeholderRange = _MakeRangeFromAbs(lineBeginningIndex, CPMaxRange(_selectionRange)), firstFragment = [_layoutManager _firstLineFragmentForLineFromLocation:_selectionRange.location],
lastFragment = [_layoutManager _lastLineFragmentForLineFromLocation:CPMaxRange(_selectionRange)],
lineBeginningIndex = firstFragment._range.location,
lineEndIndex = CPMaxRange(lastFragment._range),
placeholderRange = CPIntersectionRange(_MakeRangeFromAbs(lineBeginningIndex, lineEndIndex), visibleRange),
placeholderString = [_textStorage attributedSubstringFromRange:placeholderRange], placeholderString = [_textStorage attributedSubstringFromRange:placeholderRange],
placeholderFrame = CGRectIntersection([_layoutManager boundingRectForGlyphRange:placeholderRange inTextContainer:_textContainer], _frame), placeholderFrame = CGRectIntersection([_layoutManager boundingRectForGlyphRange:placeholderRange inTextContainer:_textContainer], _frame),
rangeToHide = CPMakeRange(0, _selectionRange.location - lineBeginningIndex), rangeToHideLHS = CPMakeRange(0, _selectionRange.location - lineBeginningIndex),
rangeToHideRHS = _MakeRangeFromAbs(CPMaxRange(_selectionRange) - lineBeginningIndex, lineEndIndex - lineBeginningIndex),
dragPlaceholder; dragPlaceholder;
// hide the left part of the first line of the selection that is not included // hide the left/right parts of the first/last lines of the selection that are not included
[placeholderString addAttribute:CPForegroundColorAttributeName [self _hideRange:rangeToHideLHS inDragPlaceholderString:placeholderString];
value:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] [self _hideRange:rangeToHideRHS inDragPlaceholderString:placeholderString];
range:rangeToHide];
_movingSelection = CPMakeRange(_startTrackingLocation, 0); _movingSelection = CPMakeRange(_startTrackingLocation, 0);
dragPlaceholder = [[CPTextView alloc] initWithFrame:placeholderFrame]; dragPlaceholder = [[CPTextView alloc] initWithFrame:_frame];
[dragPlaceholder._textStorage replaceCharactersInRange:CPMakeRange(0, 0) withAttributedString:placeholderString]; [dragPlaceholder._textStorage replaceCharactersInRange:CPMakeRange(0, 0) withAttributedString:placeholderString];
[dragPlaceholder setBackgroundColor:[CPColor colorWithRed:1 green:1 blue:1 alpha:0]]; [dragPlaceholder setBackgroundColor:[CPColor colorWithRed:1 green:1 blue:1 alpha:0]];
[dragPlaceholder setAlphaValue:0.5]; [dragPlaceholder setAlphaValue:0.5];
var stringForPasting = [_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)], var stringForPasting = [_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)],
richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}],
draggingPasteboard = [CPPasteboard pasteboardWithName:CPDragPboard]; draggingPasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[draggingPasteboard declareTypes:[CPRTFPboardType, CPStringPboardType] owner:nil]; [draggingPasteboard declareTypes:[_CPASPboardType, CPStringPboardType] owner:nil];
[draggingPasteboard setString:richData forType:CPRTFPboardType]; [draggingPasteboard setString:[[CPKeyedArchiver archivedDataWithRootObject:stringForPasting] rawString] forType:_CPASPboardType];
// this is necessary because the drag will not work without data of kind CPStringPboardType
[draggingPasteboard setString:stringForPasting._string forType:CPStringPboardType]; [draggingPasteboard setString:stringForPasting._string forType:CPStringPboardType];
[self dragView:dragPlaceholder [self dragView:dragPlaceholder
@@ -1134,6 +1180,21 @@ Sets the selection to a range of characters in response to user action.
[self setSelectedRange:setRange affinity:0 stillSelecting:YES]; [self setSelectedRange:setRange affinity:0 stillSelecting:YES];
} }
- (CPMenu)menuForEvent:(CPEvent)anEvent
{
var myMenu = [super menuForEvent:anEvent];
if (_selectionRange.length === 0)
{
[[myMenu itemAtIndex:0] setEnabled:NO];
[[myMenu itemAtIndex:1] setEnabled:NO];
}
if (_delegateRespondsToSelectorMask & kDelegateRespondsTo_textView_menu_forEvent_atIndex)
myMenu = [_delegate textView:self menu:myMenu forEvent:anEvent atIndex:_selectionRange.location];
return myMenu;
}
- (void)_supportScrolling:(CPTimer)aTimer - (void)_supportScrolling:(CPTimer)aTimer
{ {
[self mouseDragged:[CPApp currentEvent]]; [self mouseDragged:[CPApp currentEvent]];
@@ -1297,7 +1358,7 @@ Sets the selection to a range of characters in response to user action.
_startTrackingLocation = _selectionRange.location; _startTrackingLocation = _selectionRange.location;
} }
- (unsigned)_calculateMoveSelectionFromRange:(CPRange)aRange intoDirection:(integer)move granularity:(CPSelectionGranularity)granularity - (unsigned)_calculateMoveSelectionFromRange:(CPRange)aRange intoDirection:(int)move granularity:(CPSelectionGranularity)granularity
{ {
var inWord = [self _isCharacterAtIndex:(move > 0 ? CPMaxRange(aRange) : aRange.location) + move granularity:granularity], var inWord = [self _isCharacterAtIndex:(move > 0 ? CPMaxRange(aRange) : aRange.location) + move granularity:granularity],
aSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aRange) : aRange.location) + move, 0) granularity:granularity], aSel = [self selectionRangeForProposedRange:CPMakeRange((move > 0 ? CPMaxRange(aRange) : aRange.location) + move, 0) granularity:granularity],
@@ -1306,7 +1367,7 @@ Sets the selection to a range of characters in response to user action.
return move > 0 ? CPMaxRange(inWord? aSel:bSel) : (inWord? aSel:bSel).location; return move > 0 ? CPMaxRange(inWord? aSel:bSel) : (inWord? aSel:bSel).location;
} }
- (void)_moveSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity - (void)_moveSelectionIntoDirection:(int)move granularity:(CPSelectionGranularity)granularity
{ {
var pos = [self _calculateMoveSelectionFromRange:_selectionRange intoDirection:move granularity:granularity]; var pos = [self _calculateMoveSelectionFromRange:_selectionRange intoDirection:move granularity:granularity];
@@ -1314,7 +1375,7 @@ Sets the selection to a range of characters in response to user action.
_startTrackingLocation = _selectionRange.location; _startTrackingLocation = _selectionRange.location;
} }
- (void)_extendSelectionIntoDirection:(integer)move granularity:(CPSelectionGranularity)granularity - (void)_extendSelectionIntoDirection:(int)move granularity:(CPSelectionGranularity)granularity
{ {
var aSel = CPMakeRangeCopy(_selectionRange); var aSel = CPMakeRangeCopy(_selectionRange);
@@ -1673,6 +1734,8 @@ Sets the selection to a range of characters in response to user action.
- (void)insertLineBreak:(id)sender - (void)insertLineBreak:(id)sender
{ {
[self insertText:@"\n"]; [self insertText:@"\n"];
// make sure that the return key is "swallowed" and the default button not triggered as is the case in cocoa
[[self window] _temporarilyDisableKeyEquivalentForDefaultButton];
} }
- (void)insertTab:(id)sender - (void)insertTab:(id)sender
@@ -1877,14 +1940,14 @@ Sets the selection to a range of characters in response to user action.
if ([attrib containsKey:CPUnderlineStyleAttributeName] && [[attrib objectForKey:CPUnderlineStyleAttributeName] intValue]) if ([attrib containsKey:CPUnderlineStyleAttributeName] && [[attrib objectForKey:CPUnderlineStyleAttributeName] intValue])
[_textStorage removeAttribute:CPUnderlineStyleAttributeName range:_selectionRange]; [_textStorage removeAttribute:CPUnderlineStyleAttributeName range:_selectionRange];
else else
[_textStorage addAttribute:CPUnderlineStyleAttributeName value:[CPNumber numberWithInt:1] range:CPMakeRangeCopy(_selectionRange)]; [_textStorage addAttribute:CPUnderlineStyleAttributeName value:CPUnderlineStyleSingle range:CPMakeRangeCopy(_selectionRange)];
} }
else else
{ {
if ([_typingAttributes containsKey:CPUnderlineStyleAttributeName] && [[_typingAttributes objectForKey:CPUnderlineStyleAttributeName] intValue]) if ([_typingAttributes containsKey:CPUnderlineStyleAttributeName] && [[_typingAttributes objectForKey:CPUnderlineStyleAttributeName] intValue])
[_typingAttributes setObject:[CPNumber numberWithInt:0] forKey:CPUnderlineStyleAttributeName]; [_typingAttributes setObject:CPUnderlineStyleNone forKey:CPUnderlineStyleAttributeName];
else else
[_typingAttributes setObject:[CPNumber numberWithInt:1] forKey:CPUnderlineStyleAttributeName]; [_typingAttributes setObject:CPUnderlineStyleSingle forKey:CPUnderlineStyleAttributeName];
} }
[_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(_selectionRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(_selectionRange)]; [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(_selectionRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(_selectionRange)];
@@ -1917,43 +1980,45 @@ Sets the selection to a range of characters in response to user action.
- (void)setFrameSize:(CGSize)aSize - (void)setFrameSize:(CGSize)aSize
{ {
var minSize = [self minSize], var desiredSize = CGSizeCreateCopy(aSize);
maxSize = [self maxSize],
desiredSize = CGSizeCreateCopy(aSize), if (_isHorizontallyResizable || _isVerticallyResizable)
rect = CGRectUnion([_layoutManager boundingRectForGlyphRange:CPMakeRange(0, 1) inTextContainer:_textContainer], {
[_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, [_layoutManager numberOfCharacters] - 2), 1) inTextContainer:_textContainer]), var minSize = [self minSize],
myClipviewSize = nil; maxSize = [self maxSize],
rect = CGRectUnion([_layoutManager boundingRectForGlyphRange:CPMakeRange(0, 1) inTextContainer:_textContainer],
[_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, [_layoutManager numberOfCharacters] - 2), 1) inTextContainer:_textContainer]);
if ([_layoutManager extraLineFragmentTextContainer] === _textContainer)
rect = CGRectUnion(rect, [_layoutManager extraLineFragmentRect]);
if (_isHorizontallyResizable)
{
rect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(0, MAX(0, [_layoutManager numberOfCharacters] - 1)) inTextContainer:_textContainer]; // needs expensive "deep" recalculation
desiredSize.width = rect.size.width + 2 * _textContainerInset.width;
if (desiredSize.width < minSize.width)
desiredSize.width = minSize.width;
else if (desiredSize.width > maxSize.width)
desiredSize.width = maxSize.width;
}
if (_isVerticallyResizable)
{
desiredSize.height = rect.size.height + 2 * _textContainerInset.height;
if (desiredSize.height < minSize.height)
desiredSize.height = minSize.height;
else if (desiredSize.height > maxSize.height)
desiredSize.height = maxSize.height;
}
}
if ([[self superview] isKindOfClass:[CPClipView class]]) if ([[self superview] isKindOfClass:[CPClipView class]])
myClipviewSize = [[self superview] frame].size;
if ([_layoutManager extraLineFragmentTextContainer] === _textContainer)
rect = CGRectUnion(rect, [_layoutManager extraLineFragmentRect]);
if (_isHorizontallyResizable)
{ {
rect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(0, MAX(0, [_layoutManager numberOfCharacters] - 1)) inTextContainer:_textContainer]; // needs expensive "deep" recalculation var myClipviewSize = [[self superview] frame].size;
desiredSize.width = rect.size.width + 2 * _textContainerInset.width;
if (desiredSize.width < minSize.width)
desiredSize.width = minSize.width;
else if (desiredSize.width > maxSize.width)
desiredSize.width = maxSize.width;
}
if (_isVerticallyResizable)
{
desiredSize.height = rect.size.height + 2 * _textContainerInset.height;
if (desiredSize.height < minSize.height)
desiredSize.height = minSize.height;
else if (desiredSize.height > maxSize.height)
desiredSize.height = maxSize.height;
}
if (myClipviewSize)
{
if (desiredSize.width < myClipviewSize.width) if (desiredSize.width < myClipviewSize.width)
desiredSize.width = myClipviewSize.width; desiredSize.width = myClipviewSize.width;
if (desiredSize.height < myClipviewSize.height) if (desiredSize.height < myClipviewSize.height)
@@ -2107,41 +2172,29 @@ Sets the selection to a range of characters in response to user action.
- (BOOL)shouldDrawInsertionPoint - (BOOL)shouldDrawInsertionPoint
{ {
if (![self isEditable])
return NO;
return (_selectionRange.length === 0 && [self _isFocused] && !_placeholderString); return (_selectionRange.length === 0 && [self _isFocused] && !_placeholderString);
} }
- (void)updateInsertionPointStateAndRestartTimer:(BOOL)flag - (CPRect)_getCaretRect
{ {
var caretRect, var numberOfGlyphs = [_layoutManager numberOfCharacters];
numberOfGlyphs = [_layoutManager numberOfCharacters];
if (_selectionRange.length) if (!numberOfGlyphs)
[_caret setVisibility:NO];
if (_selectionRange.location >= numberOfGlyphs) // cursor is "behind" the last chacacter
{ {
caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0,_selectionRange.location - 1), 1) inTextContainer:_textContainer]; var font = [_typingAttributes objectForKey:CPFontAttributeName] || [self font];
return CGRectMake(1, ([font ascender] - [font descender]) * 0.5 + _textContainerOrigin.y, 1, [font size]);
if (!numberOfGlyphs)
{
var font = [_typingAttributes objectForKey:CPFontAttributeName] || [self font];
caretRect.size.height = [font size];
caretRect.origin.y = ([font ascender] - [font descender]) * 0.5 + _textContainerOrigin.y;
}
caretRect.origin.x += caretRect.size.width;
if (_selectionRange.location > 0 && [[_textStorage string] characterAtIndex:_selectionRange.location - 1] === '\n')
{
caretRect.origin.y += caretRect.size.height;
caretRect.origin.x = 0;
}
} }
else
caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(_selectionRange.location, 1) inTextContainer:_textContainer];
var loc = (_selectionRange.location === numberOfGlyphs && numberOfGlyphs > 0) ? _selectionRange.location - 1 : _selectionRange.location, // cursor is at a newline character -> jump to next line
if (_selectionRange.location == numberOfGlyphs && _isNewlineCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1]))
return CGRectCreateCopy([_layoutManager extraLineFragmentRect]);
var caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(_selectionRange.location, 1) inTextContainer:_textContainer];
var loc = (_selectionRange.location == numberOfGlyphs) ? _selectionRange.location - 1 : _selectionRange.location,
caretOffset = [_layoutManager _characterOffsetAtLocation:loc], caretOffset = [_layoutManager _characterOffsetAtLocation:loc],
oldYPosition = CGRectGetMaxY(caretRect), oldYPosition = CGRectGetMaxY(caretRect),
caretDescend = [_layoutManager _descentAtLocation:loc]; caretDescend = [_layoutManager _descentAtLocation:loc];
@@ -2151,14 +2204,24 @@ Sets the selection to a range of characters in response to user action.
caretRect.origin.y += caretOffset; caretRect.origin.y += caretOffset;
caretRect.size.height = oldYPosition - caretRect.origin.y; caretRect.size.height = oldYPosition - caretRect.origin.y;
} }
if (caretDescend < 0) if (caretDescend < 0)
caretRect.size.height -= caretDescend; caretRect.size.height -= caretDescend;
if (_selectionRange.location == numberOfGlyphs)
caretRect.origin.x += caretRect.size.width;
caretRect.origin.x += _textContainerOrigin.x; caretRect.origin.x += _textContainerOrigin.x;
caretRect.origin.y += _textContainerOrigin.y; caretRect.origin.y += _textContainerOrigin.y;
caretRect.size.width = 1;
[_caret setRect:caretRect]; return caretRect;
}
- (void)updateInsertionPointStateAndRestartTimer:(BOOL)flag
{
if (_selectionRange.length)
[_caret setVisibility:NO];
[_caret setRect:[self _getCaretRect]];
if (flag) if (flag)
[_caret startBlinking]; [_caret startBlinking];
@@ -2170,6 +2233,10 @@ Sets the selection to a range of characters in response to user action.
location = [self _characterIndexFromRawPoint:CGPointCreateCopy(point)]; location = [self _characterIndexFromRawPoint:CGPointCreateCopy(point)];
_movingSelection = CPMakeRange(location, 0); _movingSelection = CPMakeRange(location, 0);
if (CPLocationInRange(location, _selectionRange))
return;
[_caret _drawCaretAtLocation:_movingSelection.location]; [_caret _drawCaretAtLocation:_movingSelection.location];
[_caret setVisibility:YES]; [_caret setVisibility:YES];
} }
@@ -2182,7 +2249,7 @@ Sets the selection to a range of characters in response to user action.
var location = [self convertPoint:[aSender draggingLocation] fromView:nil], var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
pasteboard = [aSender draggingPasteboard]; pasteboard = [aSender draggingPasteboard];
if ([pasteboard availableTypeFromArray:[CPRTFPboardType, CPStringPboardType]]) if ([pasteboard availableTypeFromArray:[_CPASPboardType]])
{ {
[_caret setVisibility:NO]; [_caret setVisibility:NO];
@@ -2199,15 +2266,10 @@ Sets the selection to a range of characters in response to user action.
[self _deleteForRange:_selectionRange]; [self _deleteForRange:_selectionRange];
[self setSelectedRange:_movingSelection]; [self setSelectedRange:_movingSelection];
var dataForPasting = [pasteboard stringForType:CPRTFPboardType] || [pasteboard stringForType:CPStringPboardType]; var stringForPasting = [CPKeyedUnarchiver unarchiveObjectWithData:[CPData dataWithRawString:[pasteboard stringForType:_CPASPboardType]]];
// setTimeout is to a work around a transaction issue with the undomanager // setTimeout is to a work around a transaction issue with the undomanager
setTimeout(function(){ setTimeout(function(){
[self insertText:stringForPasting];
if ([dataForPasting hasPrefix:"{\\rtf"])
[self insertText:[[_CPRTFParser new] parseRTF:dataForPasting]];
else
[self insertText:dataForPasting];
}, 0); }, 0);
} }
@@ -2327,7 +2389,11 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
CPTextViewTextStorageKey = @"CPTextViewTextStorageKey", CPTextViewTextStorageKey = @"CPTextViewTextStorageKey",
CPTextViewInsertionPointColorKey = @"CPTextViewInsertionPointColorKey", CPTextViewInsertionPointColorKey = @"CPTextViewInsertionPointColorKey",
CPTextViewSelectedTextAttributesKey = @"CPTextViewSelectedTextAttributesKey", CPTextViewSelectedTextAttributesKey = @"CPTextViewSelectedTextAttributesKey",
CPTextViewDelegateKey = @"CPTextViewDelegateKey"; CPTextViewDelegateKey = @"CPTextViewDelegateKey",
CPTextViewHorizontallyResizableKey = @"CPTextViewHorizontallyResizableKey",
CPTextViewVerticallyResizableKey = @"CPTextViewVerticallyResizableKey",
CPMaxSize = @"CPMaxSize";
@implementation CPTextView (CPCoding) @implementation CPTextView (CPCoding)
@@ -2368,6 +2434,10 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
[self setFont:[_typingAttributes valueForKey:CPFontAttributeName]]; [self setFont:[_typingAttributes valueForKey:CPFontAttributeName]];
[self setString:[_textStorage string]]; [self setString:[_textStorage string]];
[self setMaxSize:[aCoder decodeSizeForKey:CPMaxSize]];
[self setHorizontallyResizable:[aCoder decodeBoolForKey:CPTextViewHorizontallyResizableKey]];
[self setVerticallyResizable:[aCoder decodeBoolForKey:CPTextViewVerticallyResizableKey]]
} }
return self; return self;
@@ -2383,6 +2453,9 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
[aCoder encodeObject:_selectedTextAttributes forKey:CPTextViewSelectedTextAttributesKey]; [aCoder encodeObject:_selectedTextAttributes forKey:CPTextViewSelectedTextAttributesKey];
[aCoder encodeBool:_allowsUndo forKey:CPTextViewAllowsUndoKey]; [aCoder encodeBool:_allowsUndo forKey:CPTextViewAllowsUndoKey];
[aCoder encodeBool:_usesFontPanel forKey:CPTextViewUsesFontPanelKey]; [aCoder encodeBool:_usesFontPanel forKey:CPTextViewUsesFontPanelKey];
[aCoder encodeBool:_isHorizontallyResizable forKey:CPTextViewHorizontallyResizableKey];
[aCoder encodeBool:_isVerticallyResizable forKey:CPTextViewVerticallyResizableKey];
[aCoder encodeSize:_maxSize forKey:CPMaxSize];
} }
@end @end
@@ -2480,6 +2553,7 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
style.whiteSpace = "pre"; style.whiteSpace = "pre";
style.backgroundColor = "black"; style.backgroundColor = "black";
_caretDOM.style.width = "1px"; _caretDOM.style.width = "1px";
_caretDOM.style.zIndex = 10001;
_textView = aView; _textView = aView;
_textView._DOMElement.appendChild(_caretDOM); _textView._DOMElement.appendChild(_caretDOM);
} }
@@ -2521,7 +2595,7 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey",
_caretTimer = [CPTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES]; _caretTimer = [CPTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(_blinkCaret:) userInfo:nil repeats:YES];
} }
- (void)isBlinking - (BOOL)isBlinking
{ {
return [_caretTimer isValid]; return [_caretTimer isValid];
} }
@@ -2564,7 +2638,14 @@ var _CPCopyPlaceholder = '-';
{ {
return _CPNativeInputFieldActive; return _CPNativeInputFieldActive;
} }
+ (void)isDeadKey:(CPEvent)event
{
#if PLATFORM(DOM)
return event._DOMEvent && (event._DOMEvent.key == 'Dead' || event._DOMEvent.key == 'Process');
#endif
return NO;
}
+ (void)cancelCurrentNativeInputSession + (void)cancelCurrentNativeInputSession
{ {
@@ -2637,16 +2718,6 @@ var _CPCopyPlaceholder = '-';
if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)])
return false; // prevent the default behaviour return false; // prevent the default behaviour
var charCode = _CPNativeInputField.innerHTML.charCodeAt(0);
// å and Å need to be filtered out in keyDown: due to chrome inserting 229 on a deadkey
if (charCode == 229 || charCode == 197)
{
[currentFirstResponder insertText:_CPNativeInputField.innerHTML];
_CPNativeInputField.innerHTML = '';
return;
}
// chrome-trigger: keypressed is omitted for deadkeys // chrome-trigger: keypressed is omitted for deadkeys
if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyPressedCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3) if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyPressedCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3)
{ {
@@ -2860,7 +2931,7 @@ var _CPCopyPlaceholder = '-';
- (void)setValue:(id)aValue forBinding:(CPString)aBinding - (void)setValue:(id)aValue forBinding:(CPString)aBinding
{ {
if (aValue === nil || (aValue.isa && [aValue isMemberOfClass:CPNull])) if (aValue == nil || (aValue.isa && [aValue isMemberOfClass:CPNull]))
[_source _setPlaceholderString:[self _placeholderForMarker:CPNullMarker]]; [_source _setPlaceholderString:[self _placeholderForMarker:CPNullMarker]];
else else
[_source _setPlaceholderString:nil]; [_source _setPlaceholderString:nil];
+45 -17
View File
@@ -199,13 +199,11 @@ var CPSystemTypesetterFactory,
[_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange]; [_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange];
[_layoutManager _setAdvancements:advancements forGlyphRange:lineRange]; [_layoutManager _setAdvancements:advancements forGlyphRange:lineRange];
if (!sameLine) //fix the _lineFragments when fontsizes differ //fix the _lineFragments when fontsizes differ
{ var l = _lineFragments.length;
var l = _lineFragments.length;
for (var i = 0 ; i < l ; i++) for (var i = 0 ; i < l ; i++)
[_lineFragments[i] _adjustForHeight:_lineHeight]; [_lineFragments[i] _adjustForHeight:_lineHeight];
}
if (!lineCount) // do not rescue on first line if (!lineCount) // do not rescue on first line
return NO; return NO;
@@ -245,6 +243,7 @@ var CPSystemTypesetterFactory,
wrapWidth = 0, wrapWidth = 0,
isNewline = NO, isNewline = NO,
isTabStop = NO, isTabStop = NO,
isAttachment = NO,
isWordWrapped = NO, isWordWrapped = NO,
numberOfGlyphs= [_textStorage length], numberOfGlyphs= [_textStorage length],
leading, leading,
@@ -318,10 +317,38 @@ var CPSystemTypesetterFactory,
measuringRange.length++; measuringRange.length++;
var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons
rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) _sizeWithFont:currentFont inWidth:NULL].width + currentAnchor; rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor;
switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char. switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char.
{ {
case CPAttachmentCharacter:
{
var attributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:nil],
view = [attributes objectForKey:_CPAttachmentView],
viewSize = view ? view._frame.size : CGSizeMake(0, 0);
rangeWidth = prevRangeWidth + viewSize.width; // undo sizing of dummy character
isAttachment = YES;
wrapRange = CPMakeRange(lineRange.location, lineRange.length - 1); // wrap before image
// prevent crash when image is larger than text container
if (viewSize.width > containerSizeWidth)
wrapRange.length++;
wrapWidth = rangeWidth;
wrapRange._height = _lineHeight;
wrapRange._base = _lineBase;
if (viewSize.height > _lineBase)
_lineBase = viewSize.height;
if (viewSize.height > _lineHeight)
_lineHeight = viewSize.height - descent + leading;
ascent = viewSize.height;
break;
}
case 9: // '\t' case 9: // '\t'
{ {
var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0]; var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0];
@@ -346,7 +373,6 @@ var CPSystemTypesetterFactory,
} }
advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent}); advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent});
prevRangeWidth = _lineWidth = rangeWidth; prevRangeWidth = _lineWidth = rangeWidth;
if (lineOrigin.x + rangeWidth > containerSizeWidth) if (lineOrigin.x + rangeWidth > containerSizeWidth)
@@ -364,16 +390,13 @@ var CPSystemTypesetterFactory,
glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character
} }
if (isNewline || isTabStop) if (isNewline || isTabStop || isAttachment)
{ {
if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline]) if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline])
return; return;
if (isTabStop) if (isTabStop || isAttachment)
{
lineOrigin.x += rangeWidth; lineOrigin.x += rangeWidth;
isTabStop = NO;
}
if (isNewline) if (isNewline)
{ {
@@ -404,6 +427,9 @@ var CPSystemTypesetterFactory,
_lineBase = ascent; _lineBase = ascent;
} }
isTabStop = NO;
isAttachment = NO;
isWordWrapped = NO;
_lineWidth = 0; _lineWidth = 0;
advancements = []; advancements = [];
currentAnchor = 0; currentAnchor = 0;
@@ -412,18 +438,20 @@ var CPSystemTypesetterFactory,
measuringRange = CPMakeRange(glyphIndex + 1, 0); measuringRange = CPMakeRange(glyphIndex + 1, 0);
wrapRange = CPMakeRange(0, 0); wrapRange = CPMakeRange(0, 0);
wrapWidth = 0; wrapWidth = 0;
isWordWrapped = NO;
} }
} }
// this is to "flush" the remaining characters // this is to "flush" the remaining characters
if (lineRange.length) if (lineRange.length)
{
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO]; [self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO];
}
var rect = CGRectMake(0, lineOrigin.y, containerSizeWidth, [_layoutManager._lineFragments lastObject]._usedRect.size.height - descent); var rect = CGRectMake(1, lineOrigin.y - descent, containerSizeWidth, currentFontLineHeight);
[_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer]; [_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer];
var fragment = [_layoutManager._lineFragments lastObject];
if (fragment)
fragment._isLast = YES;
} }
@end @end
+2 -2
View File
@@ -113,7 +113,7 @@ function _points2twips(a) { return (a) * 20.0; }
keyArray = [keyArray sortedArrayUsingSelector:@selector(compare:)]; keyArray = [keyArray sortedArrayUsingSelector:@selector(compare:)];
fontEnum = [keyArray objectEnumerator]; fontEnum = [keyArray objectEnumerator];
while ((currFont = [fontEnum nextObject]) !== nil) while ((currFont = [fontEnum nextObject]) != nil)
{ {
var fontFamily, var fontFamily,
detail; detail;
@@ -149,7 +149,7 @@ function _points2twips(a) { return (a) * 20.0; }
next, next,
i; i;
while ((next = [keyEnum nextObject]) !== nil) while ((next = [keyEnum nextObject]) != nil)
{ {
var cn = [colorDict objectForKey:next]; var cn = [colorDict objectForKey:next];
[list insertObject:[CPColor colorWithCSSString:next] atIndex:[cn intValue]-1]; [list insertObject:[CPColor colorWithCSSString:next] atIndex:[cn intValue]-1];
+242 -41
View File
@@ -30,6 +30,7 @@
@class _CPThemeAttribute @class _CPThemeAttribute
@class CPImage @class CPImage
@class CPColor @class CPColor
@class CPApplication
var CPThemesByName = { }, var CPThemesByName = { },
CPThemeDefaultTheme = nil, CPThemeDefaultTheme = nil,
@@ -48,6 +49,8 @@ var CPThemesByName = { },
+ (void)setDefaultTheme:(CPTheme)aTheme + (void)setDefaultTheme:(CPTheme)aTheme
{ {
CPThemeDefaultTheme = aTheme; CPThemeDefaultTheme = aTheme;
[CPFont initializeSystemFontFromTheme:aTheme];
} }
+ (CPTheme)defaultTheme + (CPTheme)defaultTheme
@@ -259,7 +262,7 @@ var CPThemesByName = { },
attributeNames = [attributes keyEnumerator], attributeNames = [attributes keyEnumerator],
objectThemeClass = [anObject themeClass]; objectThemeClass = [anObject themeClass];
while ((attributeName = [attributeNames nextObject]) !== nil) while ((attributeName = [attributeNames nextObject]) != nil)
[self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass]; [self _recordAttribute:[attributes objectForKey:attributeName] forClass:objectThemeClass];
} }
@@ -339,9 +342,143 @@ var CPThemeNameKey = @"CPThemeNameKey",
// //
// The method -(void)setCSSResourcesPath is meant to be used only by CPThemeBlend during theme loading in order to // The method -(void)setCSSResourcesPath is meant to be used only by CPThemeBlend during theme loading in order to
// replace the special path "%%" in CSS components (like in url(%%packed.png) ) with the path to the theme blend resources folder. // replace the special path "%%" in CSS components (like in url(%%packed.png) ) with the path to the theme blend resources folder.
//
// DYNAMIC ATTRIBUTES SET:
//
// If the default theme is CSS based and exposes some dynamic attributes (like Aristo3 does for color schema), you can switch "live"
// to another set of values by calling [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:aDynamicSet] where aDynamicSet is a
// dictionary with keys being the names of exposed dynamic attributes (see the themeDescriptor.j of the CSS based theme you use)
// and values, accordingly to the type of the corresponding default values provided by the theme (CPColor, a CSS color string, ...).
// You can provide key-value pairs for a subset of the exposed dynamic attributes. If no key-value pair is specified for a dynamic
// attribute, the theme default value will be used.
//
// To restore default theme values, simply pass nil as dynamic set : [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:nil]
//
// During theme loading, if your application responds to -(CPDictionary)themeDynamicAttributesSet, the returned values set is directly
// applied to the default set. This avoids displaying the theme with its default values and then redisplaying it with your values.
// The -(CPDictionary)themeDynamicAttributesSet must be declared in a CPApplication category (and not in your application delegate) as
// it will be called during application initialisation, before the delegate has been defined.
//
// To use default theme values, simply return nil.
//
//
// TYPICAL USAGE:
//
// One can imagine that a user can choose between some predefined color schemas. You can store this user preference in standard user defaults:
//
// [[CPUserDefaults standardUserDefaults] setObject:ConstantRepresentingAColorSchema forKey:@"userTheme"];
// [[CPUserDefaults standardUserDefaults] synchronize];
//
// As you want to use the choosen color schema at run time, you've implemented -(CPDictionary)themeDynamicAttributesSet (see below), so updating
// the UI as soon as the user has made his choice is easy :
//
// [CPTheme updateDefaultThemeDynamicAttributesSetWithSet:[[CPApplication sharedApplication] themeDynamicAttributesSet]];
//
// If the user chooses the theme default color schema, you can then simply remove the user default:
//
// [[CPUserDefaults standardUserDefaults] removeObjectForKey:@"userTheme"];
// [[CPUserDefaults standardUserDefaults] synchronize];
//
// Your CPApplication category could then be something like:
//
// @implementation CPApplication (themeColorSchema)
//
// - (CPDictionary)themeDynamicAttributesSet
// {
// var userTheme = [[CPUserDefaults standardUserDefaults] objectForKey:@"userTheme"];
//
// if (!userTheme)
// // Return nil to have the theme default color schema
// return nil;
//
// switch (userTheme)
// {
// case ConstantRepresentingAColorSchema:
// return @{
// @"ExposedDynamicAttribute1": [CPColor colorWithRed:245.0/255.0 green:164.0/255.0 blue:9.0/255.0 alpha:0.85],
// @"ExposedDynamicAttribute2": [CPColor colorWithRed:245.0/255.0 green:164.0/255.0 blue:9.0/255.0 alpha:0.85],
// @"ExposedDynamicAttribute3": @"rgb(245,164,9)",
// @"ExposedDynamicAttribute4": @"url(./Resources/mypacked.png)"
// };
// break;
//
// case AnotherConstantRepresentingAColorSchema:
// return @{
// @"ExposedDynamicAttribute1": [CPColor colorWithRed:39.0/255.0 green:216.0/255.0 blue:93.0/255.0 alpha:0.85],
// @"ExposedDynamicAttribute3": @"rgb(39,216,93)"
// };
// break;
//
// ...
// }
// }
//
// @end
var _savedThemesByName = { };
@implementation CPTheme (CSSTheming) @implementation CPTheme (CSSTheming)
- (void)_initializeDynamicAttributesAndResourcesPathWithBundle:(CPBundle)aBundle
{
// First, save a template of the theme so dynamic attributes and images placeholders are preserved
_savedThemesByName[_name] = [CPKeyedArchiver archivedDataWithRootObject:self];
// Then, compute the dynamic attributes set that will be applied to the theme
// Does the application delegate provide a dynamic attributes set ?
var dynamicSet = ([[CPApplication sharedApplication] respondsToSelector:@selector(themeDynamicAttributesSet)]) ? [[CPApplication sharedApplication] themeDynamicAttributesSet] : nil;
// Take the one defined in the theme
var defaultDynamicSet = [self valueForAttributeWithName:@"dynamic-set" forClass:[CPView class]];
// Update the default dynamic attributes set with the one provided by the application
[dynamicSet enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
[defaultDynamicSet setObject:anObject forKey:aKey];
}];
// Apply the resulting set
[self applyDynamicSet:defaultDynamicSet];
// Then fix resources path in CSS directives
[self setCSSResourcesPath:[aBundle resourcePath]];
}
+ (void)updateDefaultThemeDynamicAttributesSetWithSet:(CPDictionary)dynamicSet
{
// First, get a copy of the theme template
var newTheme = [CPKeyedUnarchiver unarchiveObjectWithData:_savedThemesByName[[CPThemeDefaultTheme name]]];
// Take the dynamic set defined in the theme
var defaultDynamicSet = [newTheme valueForAttributeWithName:@"dynamic-set" forClass:[CPView class]];
// Update the default dynamic attributes set with the one provided by the application
[dynamicSet enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
[defaultDynamicSet setObject:anObject forKey:aKey];
}];
// Apply the resulting set to the theme
[newTheme applyDynamicSet:defaultDynamicSet];
// Then, fix resources path in CSS directives
[newTheme setCSSResourcesPath:[[[[CPApplication sharedApplication] themeBlend] bundle] resourcePath]];
// Now apply the new theme variation
[CPTheme setDefaultTheme:newTheme];
var windows = [[CPApplication sharedApplication] windows];
for (var i = 0, count = windows.length; i < count; i++)
{
[windows[i] _setThemeIncludingDescendants:newTheme];
}
// Update the menu bar
[CPMenu updateMenuBarAttributesWithTheme:newTheme];
}
- (void)setCSSResourcesPath:(CPString)pathToResources - (void)setCSSResourcesPath:(CPString)pathToResources
{ {
[_attributes enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop) [_attributes enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
@@ -372,6 +509,46 @@ var CPThemeNameKey = @"CPThemeNameKey",
}]; }];
} }
- (void)applyDynamicSet:(CPDictionary)dynamicSet
{
[_attributes enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
[anObject enumerateKeysAndObjectsUsingBlock:function(aKey2, anObject2, stop2)
{
var anObject2Values = [anObject2 values];
[anObject2Values enumerateKeysAndObjectsUsingBlock:function(aKey3, anObject3, stop3)
{
if (anObject3.isa)
{
if (([anObject3 isKindOfClass:CPImage] || [anObject3 isKindOfClass:CPColor]) && [anObject3 cssDictionary])
{
// We have a CSS defined image or color
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssDictionary]];
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssBeforeDictionary]];
[self _applyDynamicSet:dynamicSet toCSSDictionary:[anObject3 cssAfterDictionary]];
}
else if ([anObject3 isKindOfClass:CPString])
{
// We have a direct string value
if ([dynamicSet containsKey:anObject3])
[anObject2Values setObject:[dynamicSet objectForKey:anObject3] forKey:aKey3];
}
}
}];
}];
}];
}
- (void)_applyDynamicSet:(CPDictionary)dynamicSet toCSSDictionary:(CPDictionary)aDictionary
{
[aDictionary enumerateKeysAndObjectsUsingBlock:function(aKey, anObject, stop)
{
if ([dynamicSet containsKey:anObject])
[aDictionary setObject:[dynamicSet objectForKey:anObject] forKey:aKey];
}];
}
- (BOOL)isCSSBased - (BOOL)isCSSBased
{ {
return !![self valueForAttributeWithName:@"css-based" forClass:[CPView class]]; return !![self valueForAttributeWithName:@"css-based" forClass:[CPView class]];
@@ -637,6 +814,8 @@ CPThemeStateKeyWindow = CPThemeState("keyWindow");
CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular"); CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular");
CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall"); CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall");
CPThemeStateControlSizeMini = CPThemeState("controlSizeMini"); CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
CPThemeStateAlternateState = CPThemeState("alternate");
CPThemeStateComposedControl = CPThemeState("composed");
CPThemeStateNormalString = String(CPThemeStateNormal); CPThemeStateNormalString = String(CPThemeStateNormal);
@@ -685,17 +864,12 @@ CPThemeStateNormalString = String(CPThemeStateNormal);
- (_CPThemeAttribute)attributeBySettingValue:(id)aValue - (_CPThemeAttribute)attributeBySettingValue:(id)aValue
{ {
var attribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute]; return [self attributeBySettingValue:aValue forState:CPThemeStateNormal];
if (aValue !== undefined && aValue !== nil)
attribute._values = @{ CPThemeStateNormalString: aValue };
return attribute;
} }
- (_CPThemeAttribute)attributeBySettingValue:(id)aValue forState:(ThemeState)aState - (_CPThemeAttribute)attributeBySettingValue:(id)aValue forState:(ThemeState)aState
{ {
var shouldRemoveValue = aValue === undefined || aValue === nil, var shouldRemoveValue = aValue == nil,
attribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute], attribute = [[_CPThemeAttribute alloc] initWithName:_name defaultValue:_defaultValue defaultAttribute:_themeDefaultAttribute],
values = _values; values = _values;
@@ -727,6 +901,7 @@ CPThemeStateNormalString = String(CPThemeStateNormal);
- (id)valueForState:(ThemeState)aState - (id)valueForState:(ThemeState)aState
{ {
// First, search in cache.
var stateName = String(aState), var stateName = String(aState),
value = _cache[stateName]; value = _cache[stateName];
@@ -734,50 +909,76 @@ CPThemeStateNormalString = String(CPThemeStateNormal);
if (value !== undefined) if (value !== undefined)
return value; return value;
// Not in cache. OK, search in values.
value = [_values objectForKey:stateName]; value = [_values objectForKey:stateName];
if (value === undefined || value === nil) if (value != nil)
return _cache[stateName] = value;
// No direct match in values.
// If this is a composite state, find the closest partial subset match.
if (aState._stateNameCount > 1)
{ {
// If this is a composite state, find the closest partial subset match. var largestThemeState = [self largestThemeStateMatchForState:aState returnedValue:@ref(value)];
if (aState._stateNameCount > 1)
if ((value !== undefined) && (value !== nil))
return _cache[stateName] = value;
}
// Still don't have a value? OK, let's use the normal value.
value = [_values objectForKey:String(CPThemeStateNormal)];
if (value != nil)
return _cache[stateName] = value;
// No normal value, try asking _themeDefaultAttribute
value = [_themeDefaultAttribute valueForState:aState];
if (value != nil)
return _cache[stateName] = value;
// Well, last option, use default value
value = _defaultValue;
// Class theme attributes cannot use nil because it's a dictionary.
// So transform CPNull into nil.
if (value === [CPNull null])
value = nil;
return _cache[stateName] = value;
}
- (CPInteger)largestThemeStateMatchForState:(ThemeState)aState returnedValue:(id)valueRef
{
var stateName = String(aState),
value,
states = [_values allKeys],
count = states ? states.length : 0,
largestThemeState = 0;
while (count--)
{
var stateObject = CPThemeState(states[count]);
if (stateObject.isSubsetOf(aState) && stateObject._stateNameCount > largestThemeState)
{ {
var states = [_values allKeys], value = [_values objectForKey:states[count]];
count = states ? states.length : 0, largestThemeState = stateObject._stateNameCount;
largestThemeState = 0;
while (count--)
{
var stateObject = CPThemeState(states[count]);
if (stateObject.isSubsetOf(aState) && stateObject._stateNameCount > largestThemeState)
{
value = [_values objectForKey:states[count]];
largestThemeState = stateObject._stateNameCount;
}
}
} }
// Still don't have a value? OK, let's use the normal value.
if (value === undefined || value === nil)
value = [_values objectForKey:String(CPThemeStateNormal)];
} }
if (value === undefined || value === nil) // _themeDefaultAttribute may have a larger theme state match. If so, we have to take it. If not, we take our closest match.
value = [_themeDefaultAttribute valueForState:aState]; var defaultAttributeFoundValue,
defaultAttributeMatchLength = [_themeDefaultAttribute largestThemeStateMatchForState:aState returnedValue:@ref(defaultAttributeFoundValue)];
if (value === undefined || value === nil) if (defaultAttributeMatchLength > largestThemeState)
{ {
value = _defaultValue; value = defaultAttributeFoundValue;
largestThemeState = defaultAttributeMatchLength;
// Class theme attributes cannot use nil because it's a dictionary.
// So transform CPNull into nil.
if (value === [CPNull null])
value = nil;
} }
_cache[stateName] = value; @deref(valueRef) = value;
return largestThemeState;
return value;
} }
- (_CPThemeAttribute)attributeBySettingParentAttribute:(_CPThemeAttribute)anAttribute - (_CPThemeAttribute)attributeBySettingParentAttribute:(_CPThemeAttribute)anAttribute
+1 -1
View File
@@ -103,7 +103,7 @@
aTheme = [CPTheme themeNamed:aThemeName]; aTheme = [CPTheme themeNamed:aThemeName];
if ([aTheme isCSSBased]) if ([aTheme isCSSBased])
[aTheme setCSSResourcesPath:[aBundle resourcePath]]; [aTheme _initializeDynamicAttributesAndResourcesPathWithBundle:aBundle];
} }
[_loadDelegate blendDidFinishLoading:self]; [_loadDelegate blendDidFinishLoading:self];
+15 -8
View File
@@ -40,6 +40,7 @@
@global CPTextFieldDidFocusNotification @global CPTextFieldDidFocusNotification
@global CPTextFieldDidBlurNotification @global CPTextFieldDidBlurNotification
@global document
// TODO: should be conform to protocol CPTextFieldDelegate // TODO: should be conform to protocol CPTextFieldDelegate
@protocol CPTokenFieldDelegate <CPObject> @protocol CPTokenFieldDelegate <CPObject>
@@ -584,6 +585,12 @@ CPTokenFieldDeleteButtonType = 1;
// Snap to the token if it's only half visible due to mouse wheel scrolling. // Snap to the token if it's only half visible due to mouse wheel scrolling.
_shouldScrollTo = aToken; _shouldScrollTo = aToken;
} }
// this is a hack to compensate for a recent adoption in FR management as introduced by commit #26aab29
// this PR makes the tokenfield loose FR status prematurely, so we have to regain here in order to make deleteForward: and friends work
setTimeout(function(){
[[self window] makeFirstResponder:self];
}, 50);
} }
// =========== // ===========
@@ -630,7 +637,7 @@ CPTokenFieldDeleteButtonType = 1;
- (void)setObjectValue:(id)aValue - (void)setObjectValue:(id)aValue
{ {
if (aValue !== nil && ![aValue isKindOfClass:[CPArray class]]) if (aValue != nil && ![aValue isKindOfClass:[CPArray class]])
{ {
[super setObjectValue:nil]; [super setObjectValue:nil];
return; return;
@@ -645,7 +652,7 @@ CPTokenFieldDeleteButtonType = 1;
newTokens = []; newTokens = [];
// Preserve as many existing tokens as possible to reduce redraw flickering. // Preserve as many existing tokens as possible to reduce redraw flickering.
if (aValue !== nil) if (aValue != nil)
{ {
for (var i = 0, count = [aValue count]; i < count; i++) for (var i = 0, count = [aValue count]; i < count; i++)
{ {
@@ -666,7 +673,7 @@ CPTokenFieldDeleteButtonType = 1;
} }
} }
if (newToken === nil) if (newToken == nil)
{ {
newToken = [_CPTokenFieldToken new]; newToken = [_CPTokenFieldToken new];
[newToken setTokenField:self]; [newToken setTokenField:self];
@@ -964,8 +971,8 @@ CPTokenFieldDeleteButtonType = 1;
[self _selectToken:tokenView byExtendingSelection:NO]; [self _selectToken:tokenView byExtendingSelection:NO];
} }
} }
else // we have to remove unconditionally because the backspace is not propagated anymore starting from commit #26aab29
[self _removeSelectedTokens:nil]; [self _removeSelectedTokens:nil];
} }
else else
{ {
@@ -1310,7 +1317,7 @@ CPTokenFieldDeleteButtonType = 1;
{ {
var stringForRepresentedObject = [_tokenFieldDelegate tokenField:self displayStringForRepresentedObject:representedObject]; var stringForRepresentedObject = [_tokenFieldDelegate tokenField:self displayStringForRepresentedObject:representedObject];
if (stringForRepresentedObject !== nil) if (stringForRepresentedObject != nil)
return stringForRepresentedObject; return stringForRepresentedObject;
} }
@@ -1332,7 +1339,7 @@ CPTokenFieldDeleteButtonType = 1;
{ {
var approvedObjects = [_tokenFieldDelegate tokenField:self shouldAddObjects:tokens atIndex:index]; var approvedObjects = [_tokenFieldDelegate tokenField:self shouldAddObjects:tokens atIndex:index];
if (approvedObjects !== nil) if (approvedObjects != nil)
return approvedObjects; return approvedObjects;
} }
@@ -1354,7 +1361,7 @@ CPTokenFieldDeleteButtonType = 1;
{ {
var token = [_tokenFieldDelegate tokenField:self representedObjectForEditingString:aString]; var token = [_tokenFieldDelegate tokenField:self representedObjectForEditingString:aString];
if (token !== nil && token !== undefined) if (token != nil)
return token; return token;
// If nil was returned, assume the string is the represented object. The alternative would have been // If nil was returned, assume the string is the represented object. The alternative would have been
// to not add anything to the object value array for a nil response. // to not add anything to the object value array for a nil response.
+1 -1
View File
@@ -363,7 +363,7 @@ var CPToolbarsByIdentifier = nil,
item = [item copy]; item = [item copy];
if (item === nil) if (item == nil)
[CPException raise:CPInvalidArgumentException [CPException raise:CPInvalidArgumentException
reason:@"Toolbar delegate " + _delegate + " returned nil toolbar item for identifier \"" + identifier + "\""]; reason:@"Toolbar delegate " + _delegate + " returned nil toolbar item for identifier \"" + identifier + "\""];
+17 -8
View File
@@ -51,7 +51,7 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
/*! /*!
@ingroup appkit @ingroup appkit
A CPTrackingArea defines a region of view that generates mouse-tracking and A CPTrackingArea defines a region of view that generates mouse-tracking and
cursor-update events when the mouse is over that region. cursor-update events when the mouse is over that region.
*/ */
@@ -61,7 +61,7 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
CPTrackingAreaOptions _options @accessors(getter=options); CPTrackingAreaOptions _options @accessors(getter=options);
id _owner @accessors(getter=owner); id _owner @accessors(getter=owner);
CPDictionary _userInfo @accessors(getter=userInfo); CPDictionary _userInfo @accessors(getter=userInfo);
CPView _referencingView @accessors(property=view); CPView _referencingView @accessors(property=view);
CGRect _windowRect @accessors(getter=windowRect); CGRect _windowRect @accessors(getter=windowRect);
@@ -72,13 +72,13 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
#pragma mark - #pragma mark -
#pragma mark Initialization #pragma mark Initialization
/*! /*!
Initializes and returns an object defining a region of a view to receive mouse-tracking events, mouse-moved events, cursor-update events, or possibly Initializes and returns an object defining a region of a view to receive mouse-tracking events, mouse-moved events, cursor-update events, or possibly
all these events. all these events.
*/ */
- (CPTrackingArea)initWithRect:(CGRect)aRect options:(CPTrackingAreaOptions)options owner:(id)owner userInfo:(CPDictionary)userInfo - (CPTrackingArea)initWithRect:(CGRect)aRect options:(CPTrackingAreaOptions)options owner:(id)owner userInfo:(CPDictionary)userInfo
{ {
if (owner === nil) if (owner == nil)
[CPException raise:CPInternalInconsistencyException reason:"No owner specified"]; [CPException raise:CPInternalInconsistencyException reason:"No owner specified"];
if (options === 0) if (options === 0)
@@ -116,7 +116,7 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
if ([_owner respondsToSelector:@selector(cursorUpdate:)]) if ([_owner respondsToSelector:@selector(cursorUpdate:)])
_implementedOwnerMethods |= CPTrackingOwnerImplementsCursorUpdate; _implementedOwnerMethods |= CPTrackingOwnerImplementsCursorUpdate;
} }
return self; return self;
} }
@@ -126,7 +126,16 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
- (void)_updateWindowRect - (void)_updateWindowRect
{ {
_windowRect = [_referencingView convertRect:((_options & CPTrackingInVisibleRect) ? [_referencingView visibleRect] : _viewRect) toView:[[_referencingView window] _windowView]]; [self _updateWindowRectWithReferencingSuperViewVisibleRect:nil];
}
/*!
To speed up things we allow the caller to pass the visible rect of the referencing view's superview.
This allows the visible rect calculations to be optimized when traveling down the view hierarchy.
*/
- (void)_updateWindowRectWithReferencingSuperViewVisibleRect:(CGRect)referencingSuperviewVisibleRect
{
_windowRect = [_referencingView convertRect:((_options & CPTrackingInVisibleRect) ? [_referencingView _visibleRectWithSuperviewVisibleRect:referencingSuperviewVisibleRect] : _viewRect) toView:[[_referencingView window] _windowView]];
} }
@end @end
@@ -147,7 +156,7 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
_referencingView = [aCoder decodeObjectForKey:CPTrackingAreaReferencingViewKey]; _referencingView = [aCoder decodeObjectForKey:CPTrackingAreaReferencingViewKey];
_windowRect = [aCoder decodeObjectForKey:CPTrackingAreaWindowRect]; _windowRect = [aCoder decodeObjectForKey:CPTrackingAreaWindowRect];
} }
return self; return self;
} }
+25
View File
@@ -50,6 +50,31 @@
return self; return self;
} }
- (CPIndexPath)indexPath
{
if (_parentNode != nil)
{
var path;
var index;
index = [[_parentNode childNodes] indexOfObject:self];
path = [_parentNode indexPath];
if (path != nil)
{
return [path indexPathByAddingIndex:index];
}
else
{
return [CPIndexPath indexPathWithIndex:index];
}
}
else
{
return nil;
}
}
- (BOOL)isLeaf - (BOOL)isLeaf
{ {
return [_childNodes count] <= 0; return [_childNodes count] <= 0;
+4 -4
View File
@@ -160,13 +160,13 @@ var CPUserDefaultsControllerSharedKey = "CPUserDefaultsControllerSharedKey";
- (id)valueForKey:(CPString)aKey - (id)valueForKey:(CPString)aKey
{ {
var value = [_cachedValues objectForKey:aKey]; var value = [_cachedValues objectForKey:aKey];
if (value === nil) if (value == nil)
{ {
value = [[_controller defaults] objectForKey:aKey]; value = [[_controller defaults] objectForKey:aKey];
if (value === nil) if (value == nil)
value = [[_controller initialValues] objectForKey:aKey]; value = [[_controller initialValues] objectForKey:aKey];
if (value !== nil) if (value != nil)
[_cachedValues setObject:value forKey:aKey]; [_cachedValues setObject:value forKey:aKey];
} }
return value; return value;
@@ -220,7 +220,7 @@ var CPUserDefaultsControllerSharedKey = "CPUserDefaultsControllerSharedKey";
[self willChangeValueForKey:key]; [self willChangeValueForKey:key];
var initialValue = [initial objectForKey:key]; var initialValue = [initial objectForKey:key];
if (initialValue !== nil) if (initialValue != nil)
[_cachedValues setObject:initialValue forKey:key]; [_cachedValues setObject:initialValue forKey:key];
else else
[_cachedValues removeObjectForKey:key]; [_cachedValues removeObjectForKey:key];
+137 -77
View File
@@ -44,6 +44,7 @@
@class CPClipView @class CPClipView
@class CPScrollView @class CPScrollView
@class CALayer @class CALayer
@class CPBinder
@global appkit_tag_dom_elements @global appkit_tag_dom_elements
@@ -232,7 +233,7 @@ var CPViewHighDPIDrawingEnabled = YES;
JSObject _ephemeralSubviews; JSObject _ephemeralSubviews;
JSObject _ephemeralSubviewsForNames; JSObject _ephemeralSubviewsForNames;
CPSet _ephereralSubviews; CPSet _ephemeralSubviews;
// Key View Support // Key View Support
CPView _nextKeyView; CPView _nextKeyView;
@@ -250,10 +251,9 @@ var CPViewHighDPIDrawingEnabled = YES;
BOOL _allowsVibrancy @accessors(property=allowsVibrancy); BOOL _allowsVibrancy @accessors(property=allowsVibrancy);
CPAppearance _appearance @accessors(getter=appearance); CPAppearance _appearance @accessors(getter=appearance);
CPAppearance _effectiveAppearance; CPAppearance _currentAppearance;
CPMutableArray _trackingAreas @accessors(getter=trackingAreas, copy); CPMutableArray _trackingAreas @accessors(getter=trackingAreas, copy);
BOOL _inhibitUpdateTrackingAreas;
id _animator; id _animator;
CPDictionary _animationsDictionary; CPDictionary _animationsDictionary;
@@ -333,7 +333,9 @@ var CPViewHighDPIDrawingEnabled = YES;
+ (CPDictionary)themeAttributes + (CPDictionary)themeAttributes
{ {
return @{ return @{
@"css-based": NO @"css-based": NO,
@"dynamic-set": [CPNull null],
@"nib2cib-adjustment-frame": CGRectMakeZero()
}; };
} }
@@ -423,6 +425,9 @@ var CPViewHighDPIDrawingEnabled = YES;
_animator = nil; _animator = nil;
_animationsDictionary = @{}; _animationsDictionary = @{};
// Set the current appearance to something that can't be the correct one so it will recalculate it at the first layout.
_currentAppearance = aFrame;
[self _setupViewFlags]; [self _setupViewFlags];
[self _loadThemeAttributes]; [self _loadThemeAttributes];
@@ -798,7 +803,7 @@ var CPViewHighDPIDrawingEnabled = YES;
var addedSubview = nil, var addedSubview = nil,
addedSubviewEnumerator = [addedSubviews objectEnumerator]; addedSubviewEnumerator = [addedSubviews objectEnumerator];
while ((addedSubview = [addedSubviewEnumerator nextObject]) !== nil) while ((addedSubview = [addedSubviewEnumerator nextObject]) != nil)
[self addSubview:addedSubview]; [self addSubview:addedSubview];
// If the order is fine, no need to reorder. // If the order is fine, no need to reorder.
@@ -1045,11 +1050,23 @@ var CPViewHighDPIDrawingEnabled = YES;
if (CGRectEqualToRect(_frame, aFrame) && !_forceUpdates) if (CGRectEqualToRect(_frame, aFrame) && !_forceUpdates)
return; return;
// Save the previous value as we can be traveling down the view hierarchy.
// If view is not connected with a window don't update tracking areas.
var saveInhibitUpdateTrackingAreas = _window ? _window._inhibitUpdateTrackingAreas : YES;
// We only want to update tracking areas once so turn it off for all views.
if (_window)
_window._inhibitUpdateTrackingAreas = YES;
// Turn off change notifications for only this view
_inhibitFrameAndBoundsChangedNotifications = YES; _inhibitFrameAndBoundsChangedNotifications = YES;
[self setFrameOrigin:aFrame.origin]; [self setFrameOrigin:aFrame.origin];
[self setFrameSize:aFrame.size]; [self setFrameSize:aFrame.size];
if (_window)
_window._inhibitUpdateTrackingAreas = saveInhibitUpdateTrackingAreas;
_inhibitFrameAndBoundsChangedNotifications = NO; _inhibitFrameAndBoundsChangedNotifications = NO;
if (_postsFrameChangedNotifications) if (_postsFrameChangedNotifications)
@@ -1058,7 +1075,7 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_isSuperviewAClipView) if (_isSuperviewAClipView)
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]]; [[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
if (!_inhibitUpdateTrackingAreas) if (!saveInhibitUpdateTrackingAreas)
[self _updateTrackingAreasWithRecursion:YES]; [self _updateTrackingAreasWithRecursion:YES];
} }
@@ -1134,7 +1151,7 @@ var CPViewHighDPIDrawingEnabled = YES;
} }
#endif #endif
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications) if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
[self _updateTrackingAreasWithRecursion:YES]; [self _updateTrackingAreasWithRecursion:YES];
} }
@@ -1288,7 +1305,7 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications) if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications)
[[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]]; [[self superview] viewFrameChanged:[[CPNotification alloc] initWithName:CPViewFrameDidChangeNotification object:self userInfo:nil]];
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications) if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
[self _updateTrackingAreasWithRecursion:!_autoresizesSubviews]; [self _updateTrackingAreasWithRecursion:!_autoresizesSubviews];
} }
@@ -1338,7 +1355,7 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_isSuperviewAClipView) if (_isSuperviewAClipView)
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]]; [[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
if (!_inhibitUpdateTrackingAreas) if (_window && !_window._inhibitUpdateTrackingAreas)
[self _updateTrackingAreasWithRecursion:YES]; [self _updateTrackingAreasWithRecursion:YES];
} }
@@ -1406,7 +1423,7 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications) if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications)
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]]; [[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications) if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
[self _updateTrackingAreasWithRecursion:YES]; [self _updateTrackingAreasWithRecursion:YES];
} }
@@ -1450,7 +1467,7 @@ var CPViewHighDPIDrawingEnabled = YES;
if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications) if (_isSuperviewAClipView && !_inhibitFrameAndBoundsChangedNotifications)
[[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]]; [[self superview] viewBoundsChanged:[[CPNotification alloc] initWithName:CPViewBoundsDidChangeNotification object:self userInfo:nil]];
if (!_inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications) if (_window && !_window._inhibitUpdateTrackingAreas && !_inhibitFrameAndBoundsChangedNotifications)
[self _updateTrackingAreasWithRecursion:YES]; [self _updateTrackingAreasWithRecursion:YES];
} }
@@ -1463,7 +1480,7 @@ var CPViewHighDPIDrawingEnabled = YES;
{ {
var mask = [self autoresizingMask]; var mask = [self autoresizingMask];
if (mask === CPViewNotSizable) if ((mask === CPViewNotSizable) || !_superview)
return; return;
var frame = _superview._frame, var frame = _superview._frame,
@@ -1689,8 +1706,8 @@ var CPViewHighDPIDrawingEnabled = YES;
- (void)_setSuperview:(CPView)aSuperview - (void)_setSuperview:(CPView)aSuperview
{ {
var hasOldSuperview = (_superview !== nil), var hasOldSuperview = (_superview != nil),
hasNewSuperview = (aSuperview !== nil), hasNewSuperview = (aSuperview != nil),
oldSuperviewIsHidden = hasOldSuperview && [_superview isHiddenOrHasHiddenAncestor], oldSuperviewIsHidden = hasOldSuperview && [_superview isHiddenOrHasHiddenAncestor],
newSuperviewIsHidden = hasNewSuperview && [aSuperview isHiddenOrHasHiddenAncestor]; newSuperviewIsHidden = hasNewSuperview && [aSuperview isHiddenOrHasHiddenAncestor];
@@ -1979,7 +1996,7 @@ var CPViewHighDPIDrawingEnabled = YES;
#if PLATFORM(DOM) #if PLATFORM(DOM)
if (_backgroundType === BackgroundCSSStyling) if (_backgroundType === BackgroundCSSStyling)
[_backgroundColor restorePreviousCSSState:@ref(_cssStylePreviousState) forDOMElement:_DOMElement]; [CPColor restorePreviousCSSState:@ref(_cssStylePreviousState) forDOMElement:_DOMElement];
var patternImage = [_backgroundColor patternImage], var patternImage = [_backgroundColor patternImage],
colorExists = _backgroundColor && ([_backgroundColor patternImage] || [_backgroundColor alphaComponent] > 0.0), colorExists = _backgroundColor && ([_backgroundColor patternImage] || [_backgroundColor alphaComponent] > 0.0),
@@ -2146,6 +2163,7 @@ var CPViewHighDPIDrawingEnabled = YES;
{ {
CPDOMDisplayServerSetStyleLeftTop(_DOMImageParts[partIndex], NULL, left, 0.0); CPDOMDisplayServerSetStyleLeftTop(_DOMImageParts[partIndex], NULL, left, 0.0);
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], width, _DOMImageSizes[1].height); CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], width, _DOMImageSizes[1].height);
_DOMImageParts[partIndex].style.backgroundRepeat = "repeat-x";
partIndex++; partIndex++;
} }
if (_DOMImageSizes[2]) if (_DOMImageSizes[2])
@@ -2157,6 +2175,7 @@ var CPViewHighDPIDrawingEnabled = YES;
{ {
CPDOMDisplayServerSetStyleLeftTop(_DOMImageParts[partIndex], NULL, 0.0, top); CPDOMDisplayServerSetStyleLeftTop(_DOMImageParts[partIndex], NULL, 0.0, top);
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], _DOMImageSizes[3].width, height); CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], _DOMImageSizes[3].width, height);
_DOMImageParts[partIndex].style.backgroundRepeat = "repeat-y";
partIndex++; partIndex++;
} }
if (_DOMImageSizes[4]) if (_DOMImageSizes[4])
@@ -2169,6 +2188,7 @@ var CPViewHighDPIDrawingEnabled = YES;
{ {
CPDOMDisplayServerSetStyleRightTop(_DOMImageParts[partIndex], NULL, 0.0, top); CPDOMDisplayServerSetStyleRightTop(_DOMImageParts[partIndex], NULL, 0.0, top);
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], _DOMImageSizes[5].width, height); CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], _DOMImageSizes[5].width, height);
_DOMImageParts[partIndex].style.backgroundRepeat = "repeat-y";
partIndex++; partIndex++;
} }
if (_DOMImageSizes[6]) if (_DOMImageSizes[6])
@@ -2180,6 +2200,7 @@ var CPViewHighDPIDrawingEnabled = YES;
{ {
CPDOMDisplayServerSetStyleLeftBottom(_DOMImageParts[partIndex], NULL, left, 0.0); CPDOMDisplayServerSetStyleLeftBottom(_DOMImageParts[partIndex], NULL, left, 0.0);
CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], width, _DOMImageSizes[7].height); CPDOMDisplayServerSetStyleSize(_DOMImageParts[partIndex], width, _DOMImageSizes[7].height);
_DOMImageParts[partIndex].style.backgroundRepeat = "repeat-x";
partIndex++; partIndex++;
} }
if (_DOMImageSizes[8]) if (_DOMImageSizes[8])
@@ -2823,7 +2844,7 @@ setBoundsOrigin:
*/ */
- (void)viewDidLayout - (void)viewDidLayout
{ {
[self _recomputeAppearance]; [self _recomputeAppearanceWithSuperviewEffectiveAppearance:[self effectiveAppearance]];
} }
- (void)layoutSubviews - (void)layoutSubviews
@@ -2843,11 +2864,16 @@ setBoundsOrigin:
Returns the rectangle of the receiver not clipped by its superview. Returns the rectangle of the receiver not clipped by its superview.
*/ */
- (CGRect)visibleRect - (CGRect)visibleRect
{
return [self _visibleRectWithSuperviewVisibleRect:nil];
}
- (CGRect)_visibleRectWithSuperviewVisibleRect:(CGRect)superviewVisibleRect
{ {
if (!_superview) if (!_superview)
return _bounds; return _bounds;
return CGRectIntersection([self convertRect:[_superview visibleRect] fromView:_superview], _bounds); return CGRectIntersection([self convertRect:superviewVisibleRect || [_superview _visibleRectWithSuperviewVisibleRect:nil] fromView:_superview], _bounds);
} }
// Scrolling // Scrolling
@@ -3484,6 +3510,11 @@ setBoundsOrigin:
#endif #endif
} }
- (BOOL)isCSSBased
{
return [[CPTheme defaultTheme] valueForAttributeWithName:@"css-based" forClass:CPView];
}
@end @end
@@ -3511,53 +3542,59 @@ setBoundsOrigin:
[self setNeedsLayout:YES]; [self setNeedsLayout:YES];
} }
var CPAppearanceAqua = [CPAppearance appearanceNamed:CPAppearanceNameAqua];
var CPAppearanceLightContent = [CPAppearance appearanceNamed:CPAppearanceNameLightContent];
var CPAppearanceVibrantLight = [CPAppearance appearanceNamed:CPAppearanceNameVibrantLight];
var CPAppearanceVibrantDark = [CPAppearance appearanceNamed:CPAppearanceNameVibrantDark];
/*! @ignore /*! @ignore
*/ */
- (void)_recomputeAppearance - (void)_recomputeAppearanceWithSuperviewEffectiveAppearance:(CPAppearance)superviewEffectiveAppearance
{ {
var effectiveAppearance = [self effectiveAppearance]; var effectiveAppearance = _appearance || superviewEffectiveAppearance;
if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameAqua]]) // Only reset the themestates if it is different from the current one so we get good performance.
{ if (effectiveAppearance != _currentAppearance) {
[self setThemeState:CPThemeStateAppearanceAqua]; switch (effectiveAppearance) {
[self unsetThemeState:CPThemeStateAppearanceLightContent]; case CPAppearanceAqua:
[self unsetThemeState:CPThemeStateAppearanceVibrantLight]; [self setThemeState:CPThemeStateAppearanceAqua];
[self unsetThemeState:CPThemeStateAppearanceVibrantDark]; [self unsetThemeState:CPThemeStateAppearanceLightContent];
} [self unsetThemeState:CPThemeStateAppearanceVibrantLight];
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameLightContent]]) [self unsetThemeState:CPThemeStateAppearanceVibrantDark];
{ break;
[self unsetThemeState:CPThemeStateAppearanceAqua]; case CPAppearanceLightContent:
[self setThemeState:CPThemeStateAppearanceLightContent]; [self unsetThemeState:CPThemeStateAppearanceAqua];
[self unsetThemeState:CPThemeStateAppearanceVibrantLight]; [self setThemeState:CPThemeStateAppearanceLightContent];
[self unsetThemeState:CPThemeStateAppearanceVibrantDark]; [self unsetThemeState:CPThemeStateAppearanceVibrantLight];
} [self unsetThemeState:CPThemeStateAppearanceVibrantDark];
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]]) break;
{ case CPAppearanceVibrantLight:
[self unsetThemeState:CPThemeStateAppearanceAqua]; [self unsetThemeState:CPThemeStateAppearanceAqua];
[self unsetThemeState:CPThemeStateAppearanceLightContent]; [self unsetThemeState:CPThemeStateAppearanceLightContent];
[self setThemeState:CPThemeStateAppearanceVibrantLight]; [self setThemeState:CPThemeStateAppearanceVibrantLight];
[self unsetThemeState:CPThemeStateAppearanceVibrantDark]; [self unsetThemeState:CPThemeStateAppearanceVibrantDark];
} break;
else if ([effectiveAppearance isEqual:[CPAppearance appearanceNamed:CPAppearanceNameVibrantDark]]) case CPAppearanceVibrantDark:
{ [self unsetThemeState:CPThemeStateAppearanceAqua];
[self unsetThemeState:CPThemeStateAppearanceAqua]; [self unsetThemeState:CPThemeStateAppearanceLightContent];
[self unsetThemeState:CPThemeStateAppearanceLightContent]; [self unsetThemeState:CPThemeStateAppearanceVibrantLight];
[self unsetThemeState:CPThemeStateAppearanceVibrantLight]; [self setThemeState:CPThemeStateAppearanceVibrantDark];
[self setThemeState:CPThemeStateAppearanceVibrantDark]; break;
} default:
else [self unsetThemeState:CPThemeStateAppearanceAqua];
{ [self unsetThemeState:CPThemeStateAppearanceLightContent];
[self unsetThemeState:CPThemeStateAppearanceAqua]; [self unsetThemeState:CPThemeStateAppearanceVibrantLight];
[self unsetThemeState:CPThemeStateAppearanceLightContent]; [self unsetThemeState:CPThemeStateAppearanceVibrantDark];
[self unsetThemeState:CPThemeStateAppearanceVibrantLight]; }
[self unsetThemeState:CPThemeStateAppearanceVibrantDark];
_currentAppearance = effectiveAppearance;
} }
// var start = [CPDate new]; // var start = [CPDate new];
for (var i = 0, size = [_subviews count]; i < size; i++) for (var i = 0, size = [_subviews count]; i < size; i++)
{ {
[[_subviews objectAtIndex:i] _recomputeAppearance]; [[_subviews objectAtIndex:i] _recomputeAppearanceWithSuperviewEffectiveAppearance:effectiveAppearance];
} }
// [_subviews makeObjectsPerformSelector:@selector(_recomputeAppearance)]; // [_subviews makeObjectsPerformSelector:@selector(_recomputeAppearance)];
@@ -3637,22 +3674,33 @@ setBoundsOrigin:
- (void)_updateTrackingAreasWithRecursion:(BOOL)shouldCallRecursively - (void)_updateTrackingAreasWithRecursion:(BOOL)shouldCallRecursively
{ {
_inhibitUpdateTrackingAreas = YES; [self _updateTrackingAreasWithRecursion:shouldCallRecursively withReferencingSuperViewVisibleRect:[_superview visibleRect]];
}
[self _updateTrackingAreasForOwners:[self _calcTrackingAreaOwners]]; /*!
The referencingSuperViewVisibleRect is used to speed up the execution of this as the visibleRect method is a heavy operation.
It has to go up the view hierarchy and get every superviews visibleRect to transform and intersect them together.
Here we keep the superviews visible rect when we are going down the view hierarchy to make the operation much faster.
*/
- (void)_updateTrackingAreasWithRecursion:(BOOL)shouldCallRecursively withReferencingSuperViewVisibleRect:(CGRect)referencingSuperViewVisibleRect
{
[self _updateTrackingAreasForOwners:[self _calcTrackingAreaOwnersWithReferencingSuperViewVisibleRect:referencingSuperViewVisibleRect]];
if (shouldCallRecursively) if (shouldCallRecursively)
{ {
// Now, call _updateTrackingAreasWithRecursion on subviews // Now, call _updateTrackingAreasWithRecursion on subviews
for (var i = 0; i < _subviews.length; i++) for (var i = 0; i < _subviews.length; i++)
[_subviews[i] _updateTrackingAreasWithRecursion:YES]; [_subviews[i] _updateTrackingAreasWithRecursion:YES withReferencingSuperViewVisibleRect:[self _visibleRectWithSuperviewVisibleRect:referencingSuperViewVisibleRect]];
} }
_inhibitUpdateTrackingAreas = NO;
} }
- (CPArray)_calcTrackingAreaOwners - (CPArray)_calcTrackingAreaOwners
{
return [self _calcTrackingAreaOwnersWithReferencingSuperViewVisibleRect:nil];
}
- (CPArray)_calcTrackingAreaOwnersWithReferencingSuperViewVisibleRect:(CGRect)referencingSuperViewVisibleRect
{ {
// First search all owners that must be notified // First search all owners that must be notified
// Remark: 99.99% of time, the only owner will be the view itself // Remark: 99.99% of time, the only owner will be the view itself
@@ -3665,7 +3713,7 @@ setBoundsOrigin:
var trackingArea = _trackingAreas[i]; var trackingArea = _trackingAreas[i];
if ([trackingArea options] & CPTrackingInVisibleRect) if ([trackingArea options] & CPTrackingInVisibleRect)
[trackingArea _updateWindowRect]; [trackingArea _updateWindowRectWithReferencingSuperViewVisibleRect:referencingSuperViewVisibleRect];
else else
{ {
@@ -3685,6 +3733,16 @@ setBoundsOrigin:
[owners[i] updateTrackingAreas]; [owners[i] updateTrackingAreas];
} }
// needed by CPWindow's releasedWhenClosed property
- (void)_releaseRecursively
{
[_subviews makeObjectsPerformSelector:@selector(_releaseRecursively)];
[self _removeObservers];
[CPBinder unbindAllForObject:self];
[self removeFromSuperview];
}
@end @end
var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask", var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
@@ -3730,16 +3788,21 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
// Also decode these "early". // Also decode these "early".
_frame = [aCoder decodeRectForKey:CPViewFrameKey]; _frame = [aCoder decodeRectForKey:CPViewFrameKey];
_bounds = [aCoder decodeRectForKey:CPViewBoundsKey]; _bounds = [aCoder decodeRectForKey:CPViewBoundsKey];
_scaleSize = [aCoder containsValueForKey:CPViewScaleKey] ? [aCoder decodeSizeForKey:CPViewScaleKey] : CGSizeMake(1.0, 1.0);
_hierarchyScaleSize = [aCoder containsValueForKey:CPViewSizeScaleKey] ? [aCoder decodeSizeForKey:CPViewSizeScaleKey] : CGSizeMake(1.0, 1.0);
_isScaled = [aCoder containsValueForKey:CPViewIsScaledKey] ? [aCoder decodeBoolForKey:CPViewIsScaledKey] : NO;
_subviews = @[];
// Trying to fix "not ready" views
_trackingAreas = [aCoder decodeObjectForKey:CPViewTrackingAreasKey] || @[];
[self _decodeThemeObjectsWithCoder:aCoder];
[self setAppearance:[aCoder decodeObjectForKey:CPViewAppearanceKey]];
self = [super initWithCoder:aCoder]; self = [super initWithCoder:aCoder];
if (self) if (self)
{ {
_trackingAreas = [aCoder decodeObjectForKey:CPViewTrackingAreasKey];
if (!_trackingAreas)
_trackingAreas = [];
// We have to manually check because it may be 0, so we can't use || // We have to manually check because it may be 0, so we can't use ||
_tag = [aCoder containsValueForKey:CPViewTagKey] ? [aCoder decodeIntForKey:CPViewTagKey] : -1; _tag = [aCoder containsValueForKey:CPViewTagKey] ? [aCoder decodeIntForKey:CPViewTagKey] : -1;
_identifier = [aCoder decodeObjectForKey:CPReuseIdentifierKey]; _identifier = [aCoder decodeObjectForKey:CPReuseIdentifierKey];
@@ -3748,7 +3811,6 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
_superview = [aCoder decodeObjectForKey:CPViewSuperviewKey]; _superview = [aCoder decodeObjectForKey:CPViewSuperviewKey];
// We have to manually add the subviews so that they will receive // We have to manually add the subviews so that they will receive
// viewWillMoveToSuperview: and viewDidMoveToSuperview: // viewWillMoveToSuperview: and viewDidMoveToSuperview:
_subviews = [];
var subviews = [aCoder decodeObjectForKey:CPViewSubviewsKey] || []; var subviews = [aCoder decodeObjectForKey:CPViewSubviewsKey] || [];
@@ -3765,7 +3827,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
// Other views (CPBox) might set an autoresizes mask on their subviews before it is actually decoded. // Other views (CPBox) might set an autoresizes mask on their subviews before it is actually decoded.
// We make sure we don't override the value by checking if it was already set. // We make sure we don't override the value by checking if it was already set.
if (_autoresizingMask === nil) if (_autoresizingMask == nil)
_autoresizingMask = [aCoder decodeIntForKey:CPViewAutoresizingMaskKey] || CPViewNotSizable; _autoresizingMask = [aCoder decodeIntForKey:CPViewAutoresizingMaskKey] || CPViewNotSizable;
_autoresizesSubviews = ![aCoder containsValueForKey:CPViewAutoresizesSubviewsKey] || [aCoder decodeBoolForKey:CPViewAutoresizesSubviewsKey]; _autoresizesSubviews = ![aCoder containsValueForKey:CPViewAutoresizesSubviewsKey] || [aCoder decodeBoolForKey:CPViewAutoresizesSubviewsKey];
@@ -3777,10 +3839,6 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
if (_toolTip) if (_toolTip)
[self _installToolTipEventHandlers]; [self _installToolTipEventHandlers];
_scaleSize = [aCoder containsValueForKey:CPViewScaleKey] ? [aCoder decodeSizeForKey:CPViewScaleKey] : CGSizeMake(1.0, 1.0);
_hierarchyScaleSize = [aCoder containsValueForKey:CPViewSizeScaleKey] ? [aCoder decodeSizeForKey:CPViewSizeScaleKey] : CGSizeMake(1.0, 1.0);
_isScaled = [aCoder containsValueForKey:CPViewIsScaledKey] ? [aCoder decodeBoolForKey:CPViewIsScaledKey] : NO;
// DOM SETUP // DOM SETUP
#if PLATFORM(DOM) #if PLATFORM(DOM)
_cssStylePreviousState = @[]; _cssStylePreviousState = @[];
@@ -3811,10 +3869,12 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
[self setBackgroundColor:[aCoder decodeObjectForKey:CPViewBackgroundColorKey]]; [self setBackgroundColor:[aCoder decodeObjectForKey:CPViewBackgroundColorKey]];
[self _setupViewFlags]; [self _setupViewFlags];
[self _decodeThemeObjectsWithCoder:aCoder];
[self setAppearance:[aCoder decodeObjectForKey:CPViewAppearanceKey]]; [self setAppearance:[aCoder decodeObjectForKey:CPViewAppearanceKey]];
// Set the current appearance to something that can't be the correct one so it will recalculate it at the first layout.
_currentAppearance = _frame;
[self updateTrackingAreas];
[self setNeedsDisplay:YES]; [self setNeedsDisplay:YES];
[self setNeedsLayout]; [self setNeedsLayout];
} }
@@ -3837,7 +3897,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
[aCoder encodeRect:_bounds forKey:CPViewBoundsKey]; [aCoder encodeRect:_bounds forKey:CPViewBoundsKey];
// This will come out nil on the other side with decodeObjectForKey: // This will come out nil on the other side with decodeObjectForKey:
if (_window !== nil) if (_window != nil)
[aCoder encodeConditionalObject:_window forKey:CPViewWindowKey]; [aCoder encodeConditionalObject:_window forKey:CPViewWindowKey];
var count = [_subviews count], var count = [_subviews count],
@@ -3856,7 +3916,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
[aCoder encodeObject:encodedSubviews forKey:CPViewSubviewsKey]; [aCoder encodeObject:encodedSubviews forKey:CPViewSubviewsKey];
// This will come out nil on the other side with decodeObjectForKey: // This will come out nil on the other side with decodeObjectForKey:
if (_superview !== nil) if (_superview != nil)
[aCoder encodeConditionalObject:_superview forKey:CPViewSuperviewKey]; [aCoder encodeConditionalObject:_superview forKey:CPViewSuperviewKey];
if (_autoresizingMask !== CPViewNotSizable) if (_autoresizingMask !== CPViewNotSizable)
@@ -3865,7 +3925,7 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
if (!_autoresizesSubviews) if (!_autoresizesSubviews)
[aCoder encodeBool:_autoresizesSubviews forKey:CPViewAutoresizesSubviewsKey]; [aCoder encodeBool:_autoresizesSubviews forKey:CPViewAutoresizesSubviewsKey];
if (_backgroundColor !== nil) if (_backgroundColor != nil)
[aCoder encodeObject:_backgroundColor forKey:CPViewBackgroundColorKey]; [aCoder encodeObject:_backgroundColor forKey:CPViewBackgroundColorKey];
if (_hitTests !== YES) if (_hitTests !== YES)
@@ -3882,12 +3942,12 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
var nextKeyView = [self nextKeyView]; var nextKeyView = [self nextKeyView];
if (nextKeyView !== nil && ![nextKeyView isEqual:self]) if (nextKeyView != nil && ![nextKeyView isEqual:self])
[aCoder encodeConditionalObject:nextKeyView forKey:CPViewNextKeyViewKey]; [aCoder encodeConditionalObject:nextKeyView forKey:CPViewNextKeyViewKey];
var previousKeyView = [self previousKeyView]; var previousKeyView = [self previousKeyView];
if (previousKeyView !== nil && ![previousKeyView isEqual:self]) if (previousKeyView != nil && ![previousKeyView isEqual:self])
[aCoder encodeConditionalObject:previousKeyView forKey:CPViewPreviousKeyViewKey]; [aCoder encodeConditionalObject:previousKeyView forKey:CPViewPreviousKeyViewKey];
[self _encodeThemeObjectsWithCoder:aCoder]; [self _encodeThemeObjectsWithCoder:aCoder];
+3 -3
View File
@@ -258,7 +258,7 @@ var CPViewControllerCachedCibs;
[self loadView]; [self loadView];
if (_view === nil && [cibOwner isKindOfClass:[CPDocument class]]) if (_view == nil && [cibOwner isKindOfClass:[CPDocument class]])
[self setView:[cibOwner valueForKey:@"view"]]; [self setView:[cibOwner valueForKey:@"view"]];
if (!_view) if (!_view)
@@ -408,7 +408,7 @@ var CPViewControllerCachedCibs;
[self willChangeValueForKey:"isViewLoaded"]; [self willChangeValueForKey:"isViewLoaded"];
_view = aView; _view = aView;
_isViewLoaded = aView !== nil; _isViewLoaded = aView != nil;
if (willChangeIsViewLoaded) if (willChangeIsViewLoaded)
[self didChangeValueForKey:"isViewLoaded"]; [self didChangeValueForKey:"isViewLoaded"];
@@ -421,7 +421,7 @@ var CPViewControllerCachedCibs;
- (void)_registerOrUnregister:(BOOL)shouldRegister notificationsForView:(CPView)aView - (void)_registerOrUnregister:(BOOL)shouldRegister notificationsForView:(CPView)aView
{ {
if (aView === nil) if (aView == nil)
return; return;
var center = [CPNotificationCenter defaultCenter], var center = [CPNotificationCenter defaultCenter],
+5 -3
View File
@@ -39,6 +39,8 @@ CPWebViewProgressEstimateChangedNotification = "CPWebViewProgressEstimateChan
CPWebViewProgressStartedNotification = "CPWebViewProgressStartedNotification"; CPWebViewProgressStartedNotification = "CPWebViewProgressStartedNotification";
CPWebViewProgressFinishedNotification = "CPWebViewProgressFinishedNotification"; CPWebViewProgressFinishedNotification = "CPWebViewProgressFinishedNotification";
@global document
/*! /*!
Automatically choose between AppKit (Cappuccino style) scrollbars and Automatically choose between AppKit (Cappuccino style) scrollbars and
native scrollbars. In this mode AppKit scrollbars are always used except native scrollbars. In this mode AppKit scrollbars are always used except
@@ -470,7 +472,7 @@ CPWebViewAppKitScrollMaxPollCount = 3;
_iframe.src = _url; _iframe.src = _url;
} }
else if (_html !== nil) else if (_html != nil)
{ {
// clear the iframe // clear the iframe
_iframe.src = ""; _iframe.src = "";
@@ -480,7 +482,7 @@ CPWebViewAppKitScrollMaxPollCount = 3;
_ignoreLoadEnd = NO; _ignoreLoadEnd = NO;
if (_loadHTMLStringTimer !== nil) if (_loadHTMLStringTimer != nil)
{ {
window.clearTimeout(_loadHTMLStringTimer); window.clearTimeout(_loadHTMLStringTimer);
_loadHTMLStringTimer = nil; _loadHTMLStringTimer = nil;
@@ -848,7 +850,7 @@ CPWebViewAppKitScrollMaxPollCount = 3;
- (@action)reload:(id)sender - (@action)reload:(id)sender
{ {
// If we're displaying pure HTML, redisplay it. // If we're displaying pure HTML, redisplay it.
if (!_url && (_html !== nil)) if (!_url && (_html != nil))
[self loadHTMLString:_html]; [self loadHTMLString:_html];
else else
[self _loadMainFrameURL]; [self _loadMainFrameURL];
+154 -29
View File
@@ -186,6 +186,7 @@ var CPWindowActionMessageKeys = [
BOOL _constrainsToUsableScreen; BOOL _constrainsToUsableScreen;
unsigned _shadowStyle; unsigned _shadowStyle;
BOOL _showsResizeIndicator; BOOL _showsResizeIndicator;
BOOL _releasedWhenClosed @accessors(property=releasedWhenClosed);
int _positioningMask; int _positioningMask;
CGRect _positioningScreenRect; CGRect _positioningScreenRect;
@@ -193,7 +194,7 @@ var CPWindowActionMessageKeys = [
BOOL _isDocumentEdited; BOOL _isDocumentEdited;
BOOL _isDocumentSaving; BOOL _isDocumentSaving;
CPImageView _shadowView; _CPShadowWindowView _shadowView;
CPView _windowView; CPView _windowView;
CPView _contentView; CPView _contentView;
@@ -238,6 +239,7 @@ var CPWindowActionMessageKeys = [
CPButton _defaultButton; CPButton _defaultButton;
BOOL _defaultButtonEnabled; BOOL _defaultButtonEnabled;
BOOL _defaultButtonDisabledTemporarily;
BOOL _autorecalculatesKeyViewLoop; BOOL _autorecalculatesKeyViewLoop;
BOOL _keyViewLoopIsDirty; BOOL _keyViewLoopIsDirty;
@@ -263,8 +265,12 @@ var CPWindowActionMessageKeys = [
BOOL _isSheet; BOOL _isSheet;
_CPWindowFrameAnimation _frameAnimation; _CPWindowFrameAnimation _frameAnimation;
_CPWindowFrameAnimationDelegate _frameAnimationDelegate; _CPWindowFrameAnimationDelegate _frameAnimationDelegate;
BOOL _inhibitUpdateTrackingAreas; // Used by the CPView when updating tracking areas
} }
@global document
+ (Class)_binderClassForBinding:(CPString)aBinding + (Class)_binderClassForBinding:(CPString)aBinding
{ {
if ([aBinding hasPrefix:CPDisplayPatternTitleBinding]) if ([aBinding hasPrefix:CPDisplayPatternTitleBinding])
@@ -366,6 +372,23 @@ CPTexturedBackgroundWindowMask
[_windowView _setWindow:self]; [_windowView _setWindow:self];
[_windowView setNextResponder:self]; [_windowView setNextResponder:self];
// CSS Styling
#if PLATFORM(DOM)
var radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-top-left-radius"])
_windowView._DOMElement.style.borderTopLeftRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-top-right-radius"])
_windowView._DOMElement.style.borderTopRightRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-bottom-left-radius"])
_windowView._DOMElement.style.borderBottomLeftRadius = radius;
if (radius = [_windowView actualValueForThemeAttribute:@"border-bottom-right-radius"])
_windowView._DOMElement.style.borderBottomRightRadius = radius;
#endif
// Size calculation needs _windowView // Size calculation needs _windowView
_minSize = [self _calculateMinSizeForProposedSize:CGSizeMake(0.0, 0.0)]; _minSize = [self _calculateMinSizeForProposedSize:CGSizeMake(0.0, 0.0)];
_maxSize = CGSizeMake(1000000.0, 1000000.0); _maxSize = CGSizeMake(1000000.0, 1000000.0);
@@ -402,6 +425,7 @@ CPTexturedBackgroundWindowMask
_autorecalculatesKeyViewLoop = NO; _autorecalculatesKeyViewLoop = NO;
_defaultButtonEnabled = YES; _defaultButtonEnabled = YES;
_defaultButtonDisabledTemporarily = NO;
_keyViewLoopIsDirty = NO; _keyViewLoopIsDirty = NO;
_hasBecomeKeyWindow = NO; _hasBecomeKeyWindow = NO;
@@ -990,6 +1014,9 @@ CPTexturedBackgroundWindowMask
[self makeMainWindow]; [self makeMainWindow];
[_platformWindow _setShouldUpdateContentRect:YES]; [_platformWindow _setShouldUpdateContentRect:YES];
if ([self attachedSheet])
[_platformWindow order:CPWindowAbove window:[self attachedSheet] relativeTo:nil];
} }
/* /*
@@ -1403,25 +1430,51 @@ CPTexturedBackgroundWindowMask
return; return;
} }
if (_hasShadow && !_shadowView) if ([[self actualTheme] isCSSBased])
{ {
_shadowView = [[_CPShadowWindowView alloc] initWithFrame:CGRectMakeZero()]; // When using CSS theming, we get rid of _shadowView and add CSS shadowing directly
// on the _windowView
[_shadowView setWindowView:_windowView];
[_shadowView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_shadowView setNeedsLayout];
#if PLATFORM(DOM) #if PLATFORM(DOM)
CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement); // We must check that _windowView exists as _updateShadow can be called by
// _setSharesChromeWithPlatformWindow whereas _windowView is not yet created
if (_windowView && _windowView._DOMElement)
{
var currentBoxShadow = _windowView._DOMElement.style.boxShadow;
if (_hasShadow && (currentBoxShadow.length == 0))
{
_windowView._DOMElement.style.boxShadow = [_windowView actualValueForThemeAttribute:@"window-shadow-color"];
}
else if (!_hasShadow && (currentBoxShadow.length > 0))
{
_windowView._DOMElement.style.boxShadow = "";
}
}
#endif #endif
} }
else if (!_hasShadow && _shadowView) else
{ {
if (_hasShadow && !_shadowView)
{
_shadowView = [[_CPShadowWindowView alloc] initWithFrame:CGRectMakeZero()];
[_shadowView setWindowView:_windowView];
[_shadowView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_shadowView setNeedsLayout];
#if PLATFORM(DOM) #if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement); CPDOMDisplayServerInsertBefore(_DOMElement, _shadowView._DOMElement, _windowView._DOMElement);
#endif #endif
_shadowView = nil; }
else if (!_hasShadow && _shadowView)
{
#if PLATFORM(DOM)
CPDOMDisplayServerRemoveChild(_DOMElement, _shadowView._DOMElement);
#endif
_shadowView = nil;
}
} }
} }
@@ -1810,7 +1863,7 @@ CPTexturedBackgroundWindowMask
/*! /*!
Returns \c YES if the window can be moved. Returns \c YES if the window can be moved.
*/ */
- (void)isMovable - (BOOL)isMovable
{ {
return _isMovable; return _isMovable;
} }
@@ -1851,6 +1904,11 @@ CPTexturedBackgroundWindowMask
// CPLeftMouseDown is needed for window moving and resizing to work. // CPLeftMouseDown is needed for window moving and resizing to work.
// CPMouseMoved is needed for rollover effects on title bar buttons. // CPMouseMoved is needed for rollover effects on title bar buttons.
// ignore events that happen during open / close animations
// not ignoring these would cause the app to freeze thereafter
if (sheet && (_sheetContext["isClosing"] || _sheetContext["isOpening"]))
return;
if (sheet && _sheetContext["isAttached"]) if (sheet && _sheetContext["isAttached"])
{ {
switch (type) switch (type)
@@ -1927,8 +1985,7 @@ CPTexturedBackgroundWindowMask
[[self firstResponder] keyDown:anEvent]; [[self firstResponder] keyDown:anEvent];
// Trigger the default button if needed // Trigger the default button if needed
// FIXME: Is this only applicable in a sheet? See isse: #722. if (_defaultButtonEnabled && !_defaultButtonDisabledTemporarily)
if (![self disableKeyEquivalentForDefaultButton])
{ {
var defaultButton = [self defaultButton], var defaultButton = [self defaultButton],
keyEquivalent = [defaultButton keyEquivalent], keyEquivalent = [defaultButton keyEquivalent],
@@ -1938,6 +1995,8 @@ CPTexturedBackgroundWindowMask
[[self defaultButton] performClick:self]; [[self defaultButton] performClick:self];
} }
_defaultButtonDisabledTemporarily = NO;
return; return;
case CPScrollWheel: case CPScrollWheel:
@@ -1979,7 +2038,7 @@ CPTexturedBackgroundWindowMask
var theWindow = [anEvent window], var theWindow = [anEvent window],
selector = type == CPRightMouseDown ? @selector(rightMouseDown:) : @selector(mouseDown:); selector = type == CPRightMouseDown ? @selector(rightMouseDown:) : @selector(mouseDown:);
if ([theWindow isKeyWindow] || ([theWindow becomesKeyOnlyIfNeeded] && ![_leftMouseDownView needsPanelToBecomeKey])) if (([theWindow _isFrontmostWindow] && [theWindow isKeyWindow]) || ([theWindow becomesKeyOnlyIfNeeded] && ![_leftMouseDownView needsPanelToBecomeKey]))
return [_leftMouseDownView performSelector:selector withObject:anEvent]; return [_leftMouseDownView performSelector:selector withObject:anEvent];
else else
{ {
@@ -2102,6 +2161,28 @@ CPTexturedBackgroundWindowMask
return (_styleMask & CPTitledWindowMask) || [self isFullPlatformWindow] || _isSheet; return (_styleMask & CPTitledWindowMask) || [self isFullPlatformWindow] || _isSheet;
} }
/* @ignore */
- (BOOL)_isFrontmostWindow
{
if ([self isFullBridge])
return YES;
var orderedWindows = [CPApp orderedWindows];
if ([orderedWindows count] == 0)
return YES;
if ([orderedWindows objectAtIndex:0] === self)
return YES;
// this is necessary, because the CPMainMenuWindow is always the first object in orderedWindows, even if another window is in front of it
if ([[orderedWindows objectAtIndex:0] level] === CPMainMenuWindowLevel &&
[orderedWindows count] > 1 && [orderedWindows objectAtIndex:1] === self)
return YES;
return NO;
}
/*! /*!
Returns \c YES if the window is the key window. Returns \c YES if the window is the key window.
*/ */
@@ -2362,7 +2443,7 @@ CPTexturedBackgroundWindowMask
/*! /*!
Returns YES if the window is minimized. Returns YES if the window is minimized.
*/ */
- (void)isMiniaturized - (BOOL)isMiniaturized
{ {
return _isMiniaturized; return _isMiniaturized;
} }
@@ -2456,6 +2537,9 @@ CPTexturedBackgroundWindowMask
[_parentWindow removeChildWindow:self]; [_parentWindow removeChildWindow:self];
[self _orderOutRecursively:NO]; [self _orderOutRecursively:NO];
[self _detachFromChildrenClosing:!_parentWindow]; [self _detachFromChildrenClosing:!_parentWindow];
if (_releasedWhenClosed)
[_contentView _releaseRecursively];
} }
- (void)_detachFromChildrenClosing:(BOOL)shouldCloseChildren - (void)_detachFromChildrenClosing:(BOOL)shouldCloseChildren
@@ -2780,7 +2864,7 @@ CPTexturedBackgroundWindowMask
contentRect = [_contentView frame], contentRect = [_contentView frame],
sheetFrame = CGRectMakeCopy([attachedSheet frame]); sheetFrame = CGRectMakeCopy([attachedSheet frame]);
sheetFrame.origin.y = CGRectGetMinY(_frame) + CGRectGetMinY(contentRect); sheetFrame.origin.y = [[attachedSheet _windowView] _sheetVerticalOffset]+ CGRectGetMinY(_frame) + CGRectGetMinY(contentRect);
sheetFrame.origin.x = CGRectGetMinX(_frame) + FLOOR((CGRectGetWidth(_frame) - CGRectGetWidth(sheetFrame)) / 2.0); sheetFrame.origin.x = CGRectGetMinX(_frame) + FLOOR((CGRectGetWidth(_frame) - CGRectGetWidth(sheetFrame)) / 2.0);
[attachedSheet setFrame:sheetFrame display:YES animate:NO]; [attachedSheet setFrame:sheetFrame display:YES animate:NO];
@@ -2979,13 +3063,12 @@ CPTexturedBackgroundWindowMask
} }
// The sheet starts hidden just above the top of a clip rect // The sheet starts hidden just above the top of a clip rect
// TODO : Make properly for the -1 in endY
var sheetFrame = [sheet frame], var sheetFrame = [sheet frame],
sheetShadowFrame = sheet._hasShadow ? [sheet._shadowView frame] : sheetFrame, sheetShadowFrame = [sheet _shadowFrame],
frame = [self frame], frame = [self frame],
originX = frame.origin.x + FLOOR((frame.size.width - sheetFrame.size.width) / 2), originX = frame.origin.x + FLOOR((frame.size.width - sheetFrame.size.width) / 2),
startFrame = CGRectMake(originX, -sheetShadowFrame.size.height, sheetFrame.size.width, sheetFrame.size.height), startFrame = CGRectMake(originX, -sheetShadowFrame.size.height, sheetFrame.size.width, sheetFrame.size.height),
endY = -1 + [_windowView bodyOffset] - [[self contentView] frame].origin.y, endY = [[sheet _windowView] _sheetVerticalOffset] + [_windowView bodyOffset] - [[self contentView] frame].origin.y,
endFrame = CGRectMake(originX, endY, sheetFrame.size.width, sheetFrame.size.height); endFrame = CGRectMake(originX, endY, sheetFrame.size.width, sheetFrame.size.height);
if (_toolbar && [_windowView showsToolbar] && [self isFullPlatformWindow]) if (_toolbar && [_windowView showsToolbar] && [self isFullPlatformWindow])
@@ -3024,7 +3107,7 @@ CPTexturedBackgroundWindowMask
{ {
var sheet = _sheetContext["sheet"], var sheet = _sheetContext["sheet"],
sheetFrame = [sheet frame], sheetFrame = [sheet frame],
fullHeight = sheet._hasShadow ? [sheet._shadowView frame].size.height : sheetFrame.size.height, fullHeight = [sheet _shadowFrame].size.height,
endFrame = CGRectMakeCopy(sheetFrame), endFrame = CGRectMakeCopy(sheetFrame),
contentOrigin = [self convertBaseToGlobal:[[self contentView] frame].origin]; contentOrigin = [self convertBaseToGlobal:[[self contentView] frame].origin];
@@ -3056,7 +3139,7 @@ CPTexturedBackgroundWindowMask
sheetOrigin = CGPointMakeCopy(sheetFrame.origin); sheetOrigin = CGPointMakeCopy(sheetFrame.origin);
[self _removeClipForSheet:sheet]; [self _removeClipForSheet:sheet];
[sheet setFrameOrigin:CGPointMake(sheetOrigin.x, [sheet frame].origin.y + sheetOrigin.y)]; [self _setAttachedSheetFrameOrigin];
// we wanted to close the sheet while it animated in, do that now // we wanted to close the sheet while it animated in, do that now
if (_sheetContext["shouldClose"] === YES) if (_sheetContext["shouldClose"] === YES)
@@ -3089,7 +3172,7 @@ CPTexturedBackgroundWindowMask
*/ */
- (CPWindow)attachedSheet - (CPWindow)attachedSheet
{ {
if (_sheetContext === nil) if (_sheetContext == nil)
return nil; return nil;
return _sheetContext["sheet"]; return _sheetContext["sheet"];
@@ -3103,6 +3186,14 @@ CPTexturedBackgroundWindowMask
return _isSheet; return _isSheet;
} }
- (CGRect)_shadowFrame
{
if (_hasShadow)
return _shadowView ? [_shadowView frame] : [self frame];
return [self frame];
}
// //
/* /*
Used privately. Used privately.
@@ -3414,6 +3505,11 @@ CPTexturedBackgroundWindowMask
_defaultButtonEnabled = NO; _defaultButtonEnabled = NO;
} }
- (void)_temporarilyDisableKeyEquivalentForDefaultButton
{
_defaultButtonDisabledTemporarily = YES;
}
/*! /*!
Removes the key equivalent for the default button. Removes the key equivalent for the default button.
Note: this method is deprecated. Use disableKeyEquivalentForDefaultButton instead. Note: this method is deprecated. Use disableKeyEquivalentForDefaultButton instead.
@@ -3604,6 +3700,20 @@ var keyViewComparator = function(lhs, rhs, context)
return [CPPlatform isBrowser] ? [self convertBaseToPlatformWindow:aPoint] : [self convertBaseToScreen:aPoint]; return [CPPlatform isBrowser] ? [self convertBaseToPlatformWindow:aPoint] : [self convertBaseToScreen:aPoint];
} }
/*!
@ignore
get the scroll offset (if any) from native scroll bars (can happen when the platform window shrinks below minSize)
*/
- (CGPoint)_nativeScrollOffset
{
#if PLATFORM(DOM)
return CGPointMake(_windowView._DOMElement.scrollLeft, _windowView._DOMElement.scrollTop);
#else
CGPointMake(0, 0);
#endif
}
/*! /*!
Converts aPoint from the global coordinate system to the window coordinate system. Converts aPoint from the global coordinate system to the window coordinate system.
*/ */
@@ -3620,9 +3730,10 @@ var keyViewComparator = function(lhs, rhs, context)
if ([self _sharesChromeWithPlatformWindow]) if ([self _sharesChromeWithPlatformWindow])
return CGPointMakeCopy(aPoint); return CGPointMakeCopy(aPoint);
var origin = [self frame].origin; var origin = [self frame].origin,
scrollOffset = [self _nativeScrollOffset];
return CGPointMake(aPoint.x + origin.x, aPoint.y + origin.y); return CGPointMake(aPoint.x + origin.x - scrollOffset.x, aPoint.y + origin.y - scrollOffset.y);
} }
/*! /*!
@@ -3633,9 +3744,10 @@ var keyViewComparator = function(lhs, rhs, context)
if ([self _sharesChromeWithPlatformWindow]) if ([self _sharesChromeWithPlatformWindow])
return CGPointMakeCopy(aPoint); return CGPointMakeCopy(aPoint);
var origin = [self frame].origin; var origin = [self frame].origin,
scrollOffset = [self _nativeScrollOffset];
return CGPointMake(aPoint.x - origin.x, aPoint.y - origin.y); return CGPointMake(aPoint.x - origin.x + scrollOffset.x, aPoint.y - origin.y + scrollOffset.y);
} }
- (CGPoint)convertScreenToBase:(CGPoint)aPoint - (CGPoint)convertScreenToBase:(CGPoint)aPoint
@@ -4338,6 +4450,19 @@ var interpolate = function(fromValue, toValue, progress)
@end @end
#pragma mark -
@implementation CPWindow (CSSTheming)
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
[[self contentView] _setThemeIncludingDescendants:aTheme];
}
@end
#pragma mark -
function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel) function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel)
{ {
return { windowView:aWindowView, contentRect:aContentRect, hasShadow:hasShadow, level:aLevel }; return { windowView:aWindowView, contentRect:aContentRect, hasShadow:hasShadow, level:aLevel };
@@ -81,4 +81,19 @@
[_toolbarBackgroundView setFrame:frame]; [_toolbarBackgroundView setFrame:frame];
} }
- (void)setFrameSize:(CGSize)aFrameSize
{
[super setFrameSize:aFrameSize];
var theWindow = [self window];
if (_frame.size.width < theWindow._minSize.width || _frame.size.height < theWindow._minSize.height)
[theWindow._contentView setFrameSize:CGSizeMake(MAX(_frame.size.width, theWindow._minSize.width), MAX(_frame.size.height, theWindow._minSize.height))];
#if PLATFORM(DOM)
_DOMElement.style.overflowX = (_frame.size.width < theWindow._minSize.width)? "scroll":"hidden";
_DOMElement.style.overflowY = (_frame.size.height < theWindow._minSize.height)? "scroll":"hidden";
#endif
}
@end @end
+42 -1
View File
@@ -41,6 +41,7 @@ var _CPPopoverWindowViewDefaultCursorSize = CGSizeMake(16, 10);
unsigned _preferredEdge @accessors(property=preferredEdge); unsigned _preferredEdge @accessors(property=preferredEdge);
CGSize _cursorSize; CGSize _cursorSize;
CPColor _contentViewSavedBackgroundColor;
} }
+ (CPString)defaultThemeClass + (CPString)defaultThemeClass
@@ -123,6 +124,7 @@ var _CPPopoverWindowViewDefaultCursorSize = CGSizeMake(16, 10);
_arrowOffsetY = 0.0; _arrowOffsetY = 0.0;
_appearance = [CPAppearance appearanceNamed:CPAppearanceNameVibrantLight]; _appearance = [CPAppearance appearanceNamed:CPAppearanceNameVibrantLight];
_cursorSize = CGSizeMakeCopy(_CPPopoverWindowViewDefaultCursorSize); _cursorSize = CGSizeMakeCopy(_CPPopoverWindowViewDefaultCursorSize);
_contentViewSavedBackgroundColor = nil;
} }
return self; return self;
@@ -177,6 +179,24 @@ var _CPPopoverWindowViewDefaultCursorSize = CGSizeMake(16, 10);
strokeColor = [self valueForThemeAttribute:@"stroke-color-hud"]; strokeColor = [self valueForThemeAttribute:@"stroke-color-hud"];
} }
// Cappuccino special behavior :
// If the content view has a background color, we use it as gradient color
var subviews = [self subviews],
contentView = [subviews count] > 0 ? subviews[0] : nil,
contentViewBackgroundColor = [contentView backgroundColor];
if (_contentViewSavedBackgroundColor)
{
gradient = _contentViewSavedBackgroundColor;
}
else if (contentViewBackgroundColor)
{
gradient = _contentViewSavedBackgroundColor = contentViewBackgroundColor;
// We remove the content view background color (it will be restored when the popover closes)
[contentView setBackgroundColor:nil];
}
// fix rect to take care of stroke and shadow // fix rect to take care of stroke and shadow
frame.origin.x += halfStrokeWidth + shadowBlur; frame.origin.x += halfStrokeWidth + shadowBlur;
frame.origin.y += halfStrokeWidth + (shadowBlur + shadowSize.height / 2); frame.origin.y += halfStrokeWidth + (shadowBlur + shadowSize.height / 2);
@@ -188,7 +208,14 @@ var _CPPopoverWindowViewDefaultCursorSize = CGSizeMake(16, 10);
CGContextBeginPath(context); CGContextBeginPath(context);
CGContextSetShadowWithColor(context, shadowSize, shadowBlur, shadowColor); CGContextSetShadowWithColor(context, shadowSize, shadowBlur, shadowColor);
CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), 0.0), CGPointMake(CGRectGetMidX(frame), frame.size.height), 0);
// Is gradient a CGGradient or a CPColor ?
if ('locations' in gradient)
// CGGradient
CGContextDrawLinearGradient(context, gradient, CGPointMake(CGRectGetMidX(frame), 0.0), CGPointMake(CGRectGetMidX(frame), frame.size.height), 0);
else
// CPColor or CGColor
CGContextSetFillColor(context, gradient);
var xMin = CGRectGetMinX(frame), var xMin = CGRectGetMinX(frame),
xMax = CGRectGetMaxX(frame), xMax = CGRectGetMaxX(frame),
@@ -401,4 +428,18 @@ var _CPPopoverWindowViewDefaultCursorSize = CGSizeMake(16, 10);
CGContextFillPath(context); CGContextFillPath(context);
} }
- (void)_restoreViewBackgroundColor
{
if (_contentViewSavedBackgroundColor)
{
// We restore the saved background color in the content view
var subviews = [self subviews],
contentView = [subviews count] > 0 ? subviews[0] : nil;
[contentView setBackgroundColor:_contentViewSavedBackgroundColor];
_contentViewSavedBackgroundColor = nil;
}
}
@end @end
+1 -1
View File
@@ -156,7 +156,7 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
_headView = [[_CPTexturedWindowHeadView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), [self valueForThemeAttribute:@"title-bar-height"]) windowView:self]; _headView = [[_CPTexturedWindowHeadView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), [self valueForThemeAttribute:@"title-bar-height"]) windowView:self];
[_headView setAutoresizingMask:CPViewWidthSizable];; [_headView setAutoresizingMask:CPViewWidthSizable];
[_headView setHitTests:NO]; [_headView setHitTests:NO];
[self addSubview:_headView positioned:CPWindowBelow relativeTo:_titleField]; [self addSubview:_headView positioned:CPWindowBelow relativeTo:_titleField];
+50 -5
View File
@@ -70,12 +70,40 @@ _CPWindowViewResizeSlop = 3;
+ (CGRect)contentRectForFrameRect:(CGRect)aFrameRect + (CGRect)contentRectForFrameRect:(CGRect)aFrameRect
{ {
return CGRectMakeCopy(aFrameRect); // First, we have to check if we are compiling a theme or running an application because if working on a theme,
// we can't use theme attributes to determine the inset ! This would be a kind of circular reference...
var compilingATheme = [[[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPApplicationDelegateClass"] isEqualToString:@"BKShowcaseController"],
frameOutset = compilingATheme ? CGInsetMakeZero() : [[CPTheme defaultTheme] valueForAttributeWithName:@"frame-outset" forClass:_CPWindowView];
if (!frameOutset)
// No value found in theme. Get default value.
frameOutset = [[self themeAttributes] objectForKey:@"frame-outset"];
if (!frameOutset)
// No value found in defaults. Get default value for _CPWindowView (we know here that the window class is not _CPWindowView)
frameOutset = [[_CPWindowView themeAttributes] objectForKey:@"frame-outset"];
return CGRectMake(aFrameRect.origin.x - frameOutset.left, aFrameRect.origin.y - frameOutset.top, aFrameRect.size.width + frameOutset.left + frameOutset.right, aFrameRect.size.height + frameOutset.top + frameOutset.bottom);
} }
+ (CGRect)frameRectForContentRect:(CGRect)aContentRect + (CGRect)frameRectForContentRect:(CGRect)aContentRect
{ {
return CGRectMakeCopy(aContentRect); // First, we have to check if we are compiling a theme or running an application because if working on a theme,
// we can't use theme attributes to determine the inset ! This would be a kind of circular reference...
var compilingATheme = [[[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPApplicationDelegateClass"] isEqualToString:@"BKShowcaseController"],
frameOutset = compilingATheme ? CGInsetMakeZero() : [[CPTheme defaultTheme] valueForAttributeWithName:@"frame-outset" forClass:[self class]]; // _CPWindowView - [self class]
if (!frameOutset)
// No value found in theme. Get default value.
frameOutset = [[self themeAttributes] objectForKey:@"frame-outset"];
if (!frameOutset)
// No value found in theme. Get default value for _CPWindowView
frameOutset = [[_CPWindowView themeAttributes] objectForKey:@"frame-outset"];
return CGRectMake(aContentRect.origin.x - frameOutset.left, aContentRect.origin.y - frameOutset.top, aContentRect.size.width + frameOutset.left + frameOutset.right, aContentRect.size.height + frameOutset.top + frameOutset.bottom);
} }
+ (CPString)defaultThemeClass + (CPString)defaultThemeClass
@@ -94,6 +122,7 @@ _CPWindowViewResizeSlop = 3;
@"resize-indicator": [CPNull null], @"resize-indicator": [CPNull null],
@"attached-sheet-shadow-color": [CPColor blackColor], @"attached-sheet-shadow-color": [CPColor blackColor],
@"shadow-height": 8, @"shadow-height": 8,
@"shadown-horizontal-offset": 0,
@"close-image-origin": [CPNull null], @"close-image-origin": [CPNull null],
@"close-image-size": [CPNull null], @"close-image-size": [CPNull null],
@"close-image": [CPNull null], @"close-image": [CPNull null],
@@ -106,6 +135,17 @@ _CPWindowViewResizeSlop = 3;
@"title-alignment": CPCenterTextAlignment, @"title-alignment": CPCenterTextAlignment,
@"title-line-break-mode": CPLineBreakByTruncatingTail, @"title-line-break-mode": CPLineBreakByTruncatingTail,
@"title-vertical-alignment": CPTopVerticalTextAlignment, @"title-vertical-alignment": CPTopVerticalTextAlignment,
@"title-margin": 20,
@"border-top-left-radius": @"0px",
@"border-top-right-radius": @"0px",
@"border-bottom-left-radius": @"0px",
@"border-bottom-right-radius": @"0px",
@"minimize-image-origin": [CPNull null],
@"minimize-image-size": [CPNull null],
@"zoom-image-origin": [CPNull null],
@"zoom-image-size": [CPNull null],
@"frame-outset": CGInsetMakeZero(),
@"sheet-vertical-offset": -1
}; };
} }
@@ -752,7 +792,7 @@ _CPWindowViewResizeSlop = 3;
- (BOOL)showsResizeIndicator - (BOOL)showsResizeIndicator
{ {
return _resizeIndicator !== nil; return _resizeIndicator != nil;
} }
- (void)setResizeIndicatorOffset:(CGSize)anOffset - (void)setResizeIndicatorOffset:(CGSize)anOffset
@@ -932,9 +972,14 @@ _CPWindowViewResizeSlop = 3;
[self setNeedsLayout]; [self setNeedsLayout];
} }
- (CGRect)_shadowViewWidthForParentWindow:(CPWindow)parentWindow - (int)_sheetVerticalOffset
{ {
var myWidth = [self bounds].size.width, return [self currentValueForThemeAttribute:@"sheet-vertical-offset"];
}
- (int)_shadowViewWidthForParentWindow:(CPWindow)parentWindow
{
var myWidth = [self bounds].size.width - [self currentValueForThemeAttribute:@"shadown-horizontal-offset"],
parentWidth = [[parentWindow contentView] bounds].size.width; parentWidth = [[parentWindow contentView] bounds].size.width;
return MIN(myWidth, parentWidth); return MIN(myWidth, parentWidth);
+3 -3
View File
@@ -168,7 +168,7 @@
*/ */
- (BOOL)isWindowLoaded - (BOOL)isWindowLoaded
{ {
return _window !== nil; return _window != nil;
} }
/*! /*!
@@ -184,7 +184,7 @@
[self loadWindow]; [self loadWindow];
if (_window === nil && [_cibOwner isKindOfClass:[CPDocument class]]) if (_window == nil && [_cibOwner isKindOfClass:[CPDocument class]])
[self setWindow:[_cibOwner valueForKey:@"window"]]; [self setWindow:[_cibOwner valueForKey:@"window"]];
if (!_window) if (!_window)
@@ -378,7 +378,7 @@
_viewControllerContainerView = aView; _viewControllerContainerView = aView;
} }
- (void)viewControllerContainerView - (CPView)viewControllerContainerView
{ {
return _viewControllerContainerView; return _viewControllerContainerView;
} }
+1 -1
View File
@@ -152,7 +152,7 @@ var CPCibObjectDataKey = @"CPCibObjectDataKey";
var key = nil, var key = nil,
keyEnumerator = [replacementClasses keyEnumerator]; keyEnumerator = [replacementClasses keyEnumerator];
while ((key = [keyEnumerator nextObject]) !== nil) while ((key = [keyEnumerator nextObject]) != nil)
[unarchiver setClass:[replacementClasses objectForKey:key] forClassName:key]; [unarchiver setClass:[replacementClasses objectForKey:key] forClassName:key];
} }
+4 -3
View File
@@ -31,7 +31,7 @@ var _CPCibClassSwapperClassNameKey = @"_CPCibClassSwapperClassNameKey",
{ {
} }
+ (void)allocObjectWithCoder:(CPCoder)aCoder className:(CPString)aClassName + (id)allocObjectWithCoder:(CPCoder)aCoder className:(CPString)aClassName
{ {
// FIXME: Also check class classForClassName: // FIXME: Also check class classForClassName:
var theClass = [aCoder classForClassName:aClassName]; var theClass = [aCoder classForClassName:aClassName];
@@ -61,9 +61,10 @@ var _CPCibClassSwapperClassNameKey = @"_CPCibClassSwapperClassNameKey",
if (!object) if (!object)
{ {
CPLog.error("Unable to find class " + theClassName + " referenced in cib file."); var originalClassName = [aCoder decodeObjectForKey:_CPCibClassSwapperOriginalClassNameKey];
CPLog.warn(@"Unable to find class " + theClassName + @" referenced in cib file. Will try to use original class " + originalClassName + @".");
object = [self allocObjectWithCoder:aCoder className:[aCoder decodeObjectForKey:_CPCibClassSwapperOriginalClassNameKey]]; object = [self allocObjectWithCoder:aCoder className:originalClassName];
} }
} }

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