Compare commits

...
Author SHA1 Message Date
David Richardson f53a922ed4 Merge branch 'main' into aristo3 2026-08-11 08:18:52 -06:00
David RichardsonandGitHub 60a143eb7c Merge pull request #3268 from cappuccino/readme-aristo3-testing
Update README to reflect testing branch for the Aristo3 theme
2026-08-10 20:27:02 -06:00
David Richardson aa961ff518 Update README to reflect testing branch for the Aristo3 theme 2026-08-10 20:17:30 -06:00
daboe01 40cdc9108a Merge remote-tracking branch 'upstream/main' into Aristo3 2026-08-10 18:21:37 +02:00
David RichardsonandGitHub bbdc7e1fe1 Merge pull request #3266 from enquora/modernize-isNumeric-macro
Remove _IS_NUMERIC macro in favour of concrete function CPIsNumeric()

- Create Foundation/_CPFoundationUtilities.j to house global low-level utilities.
- Implement CPIsNumeric as a concrete JavaScript function, preserving the exact legacy logic.
- Import _CPFoundationUtilities.j in the Foundation.j umbrella manifest.
- Replace _IS_NUMERIC macro calls at all call sites with CPIsNumeric().
2026-08-05 13:50:00 -06:00
David Richardson b62721d79a Remove _IS_NUMERIC macro in favor of concrete funtion CPIsNumeric()
- Create Foundation/_CPFoundationUtilities.j to house global low-level utilities.
- Implement CPIsNumeric as a concrete JavaScript function, preserving the exact legacy logic.
- Import _CPFoundationUtilities.j in the Foundation.j umbrella manifest.
- Replace _IS_NUMERIC macro calls at all call sites with CPIsNumeric().
2026-08-05 13:47:28 -06:00
David RichardsonandGitHub 450db2678a Merge pull request #3265 from enquora/modernize-CPNumberFormatter-remove-cpp-directives
Foundation: remove preprocessor macro from CPNumberFormatter.j
2026-08-04 19:48:17 -06:00
David Richardson 02a5434e4f Foundation: remove preprocessor macro from CPNumberFormatter.j
Replace SET_NEEDS_NUMBER_HANDLER_UPDATE() with inline statements.
Add a comment at each of the 6 call sites to state the reason.
No behavior change.
2026-08-04 19:46:09 -06:00
David RichardsonandGitHub 51b10e2bc4 Merge pull request #3258 from enquora/modernize-CPTimer
Refactor CPTimer: Remove CPP dependencies and mark legacy anti-patterns
2026-08-04 19:09:46 -06:00
David RichardsonandGitHub 8cb3d7771c Merge pull request #3264 from enquora/modernize-_CPArray-cpp
refactor(foundation): expand placeholder redirection macro in CPArray
2026-08-04 18:42:58 -06:00
David Richardson 4ef40ff151 refactor(foundation): expand placeholder redirection macro in CPArray
Remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS by  inlining the class cluster instantiation routing logic across all designated initializers to support the new Go-based toolchain architecture.
2026-08-04 18:40:57 -06:00
David RichardsonandGitHub d7e5c2ccac Merge pull request #3259 from enquora/modernize-CPNumber
Refactor CPNumber: Remove CPP macros and modernize syntax
2026-08-04 18:11:59 -06:00
David Richardson f414fae60a Replace tabs with spaces
Tab indentation inadvertenly applied previously.
2026-08-04 18:09:27 -06:00
David RichardsonandGitHub a6c57b7128 Merge pull request #3263 from enquora/modernize-CPException-inline-method-call-string
Refactor METHOD_CALL_STRING macro to Javascript function

Replace the unhygienic METHOD_CALL_STRING() C pre-processor macro 
in CPException.j with a standard JavaScript helper function, 
_CPMethodCallString().

Add a `// MARK: - Exception Utilities` navigation landmark. 

This removes a legacy text-substitution dependency, enabling 
single-pass AST generation for the native compilation pipeline.
2026-08-04 17:52:30 -06:00
David Richardson cbf1d35f24 Refactor METHOD_CALL_STRING macro to Javascript function
Replace the unhygienic METHOD_CALL_STRING() C pre-processor macro
in CPException.j with a standard JavaScript helper function,
_CPMethodCallString().

Add a `// MARK: - Exception Utilities` navigation landmark.

This removes a legacy text-substitution dependency, enabling
single-pass AST generation for the platform-native compilation pipeline.
2026-08-04 17:47:44 -06:00
David Richardson ecf36f12cb docs: Establish v1.5.0 baseline and direct legacy users to 1.4.0
Merge Aristo3 theme and make it default.
Update README to reflect transition to Cappuccino 2.
Change README extension from .markdown to .md for universal editor support.
2026-08-04 15:00:41 -06:00
daboe01 69c8c3d0a2 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-07-25 19:26:39 +02:00
daboe01 fa79df4309 Merge remote-tracking branch 'upstream/main' 2026-07-25 19:22:16 +02:00
David Richardson d9d3e5d613 Refactor CPNumber: Remove CPP macros and modernize syntax
Key changes:
- Removed the `CAST_TO_INT` macro and replaced it with native ES6
  `Math.trunc()` for float truncation.
- Replaced `var` with `let` for proper block scoping.
- Simplified boolean coercion in `boolValue` using the `!!` operator.
- Replaced the hostile `throw new Error` in `descriptionWithLocale:`
  with a safe fallback to `self.toString()`.
- Added `FIXME` comments to explicitly document architectural
  anti-patterns, including the global mutable state in `CPNumberUIDs`
  and the unimplemented `decimalValue` method.
2026-07-23 16:37:06 -06:00
David Richardson a745af8ead Refactor CPTimer: Remove CPP dependencies and mark legacy anti-patterns
As part of the ongoing technical debt cleanup, this commit modernizes
CPTimer.j by removing its reliance on the C-preprocessor and updating
syntax to modern JavaScript standards.

Key changes:
- Replaced `#define CPTimerDefaultTimeInterval` with a native `const`.
- Replaced `var` with `const`/`let` for proper block scoping and immutability.
- Replaced the build-time `PLATFORM(DOM)` guard with a runtime
  `typeof(window) !== 'undefined'` check, ensuring Foundation does not
  depend on build-time platform flags.
- Added `FIXME` comments to explicitly document architectural anti-patterns:
  - The invasive global override of `window.setTimeout` and `window.setInterval`.
  - The use of `new Function` for string evaluation (CSP violation).
  - The questionable exposure of `CPTimerDefaultTimeInterval` as a fallback.
2026-07-23 15:26:31 -06:00
David RichardsonandGitHub 87c1727ea0 Merge pull request #3255 from enquora/modernize-pragma-mark
Replace objc directive with native Javascript version // MARK: to facilitate elimination of pre-processing pass.
2026-07-21 16:16:00 -06:00
David Richardson 1a9a42ff54 Replace #pragma mark with // MARK:
Replace objc directive with native Javascript version // MARK: to facilitate elimination of preproccsing pass.
2026-07-21 15:15:53 -06:00
David RichardsonandGitHub 5592056351 Merge pull request #3254 from enquora/cleanup-formatting
Cleanup formatting in CPSet class cluster.
Align variable declarations.
Convert Xcode landmarks from c preprocessor pragmas to objj-native // MARK:
2026-07-21 11:11:53 -06:00
David Richardson 98bfd87446 Convert Xcode code landmarks from Objective-C to native Javascript
While objc `#pragma mark` works in objj files, it requires a preprocessor to handle the directive.

// MARK: is the fully functional and correct Objective-J variant.
2026-07-21 11:05:11 -06:00
David Richardson 3f63be7290 Align variable declarations 2026-07-21 10:56:38 -06:00
daboe01andGitHub b23d0cb8eb Merge pull request #3251 from daboe01/ruleeditor-drag-fix
fixed: CPRuleEditor: crash when dragging nested compound rows
2026-07-11 14:24:54 +02:00
daboe01andGitHub 0da009d1b9 Merge pull request #3246 from daboe01/markdown-manual-test
new: manual test for CPMarkdownParser
2026-07-11 13:51:37 +02:00
daboe01 b1fade86fe formatting 2026-07-11 13:51:04 +02:00
daboe01 04bad947e3 fixed: CPRuleEditor: crash when dragging nested compound rows 2026-07-10 16:14:29 +02:00
David RichardsonandGitHub f2cdb08e76 Merge pull request #3249 from enquora/resolve-duplicate-category-CPSet
Deconstruct _CPSet monolithic layout and relocate KVO proxies
2026-07-08 18:37:24 -06:00
David Richardson da3b0b5e21 fix(Foundation): implement valueForKey: for CPSet KVC compliance
Resolves testKVCCountSetOperators failure in CPSetTest. The previous implementation lacked an override for valueForKey:, causing the runtime to fall back to the default CPObject behavior, which failed to recognize KVC collection operators (e.g., @count).

Delegating valueForKey: to valueForKeyPath: ensures that CPSet correctly routes KVC operations through the existing collection operator logic.
2026-07-08 18:28:49 -06:00
David Richardson a240d312d0 Deconstruct _CPSet monolithic layout and relocate KVO proxies
•	Refactor @implementation _CPKVCSet and CPObject (CPSetKVO) from _CPSet.j to the tail of CPMutableSet.j.
•	Fix legacy compiler parse failures by ensuring the CPMutableSet superclass symbol is fully resolved within the translation unit prior to proxy evaluation.
•	Maintain strict DAG compliance across the modern toolchain pipeline topology.
•	Embed explicit #pragma mark navigation landmarks and enforce Allman brace layout to improve human structural ergonomics.
2026-07-08 17:35:33 -06:00
David RichardsonandGitHub bd48e2055e Merge pull request #3248 from enquora/foundation-resolve-duplicate-methods
Remove duplicate CPIndexSet -isEqual
2026-07-08 10:18:41 -06:00
David Richardson 42c212df64 Remove duplicate CPIndexSet -isEqual
Originally added in error, not realizing another author had just applied and committed the same fix.

Javascript-based tooling unable to catch this.
2026-07-08 10:16:25 -06:00
David RichardsonandGitHub 59e811328f Merge pull request #3247 from enquora/remove-import-cycles-foundation
Break Foundation import cycles for strict graph sorting
2026-07-08 09:16:16 -06:00
David Richardson 317c1a1475 Break Foundation import cycles for strict graph sorting
Disentangle circular dependencies Foundation framework primitives using
forward declarations.
2026-07-08 08:58:53 -06:00
daboe01 a93e67662b new: manual test for CPMarkdownParser 2026-06-26 19:42:42 +02:00
daboe01 a42f597463 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-24 16:54:37 +02:00
daboe01 14379b1be9 Merge remote-tracking branch 'upstream/main' 2026-06-24 16:53:48 +02:00
daboe01andGitHub 2e8767509e Merge pull request #3245 from daboe01/textview-selection-fix
new: Prefer e.data to capture the exact inserted character(s) from ke…
2026-06-24 16:53:22 +02:00
daboe01 429dfd7467 new: Prefer e.data to capture the exact inserted character(s) from key entry 2026-06-24 16:50:05 +02:00
daboe01 a1eddb9e50 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-21 17:36:16 +02:00
daboe01 71a7b54edd Merge remote-tracking branch 'upstream/main' 2026-06-21 17:36:04 +02:00
daboe01andGitHub ad05fbe97b Merge pull request #3244 from daboe01/textview-stalenes-fix
Fixed: CPLayoutManager false-positive layout rescue on zero-length-delta edits
2026-06-21 17:35:30 +02:00
daboe01 0fed962b37 Fixed: CPLayoutManager false-positive layout rescue on zero-length-delta edits 2026-06-21 17:30:18 +02:00
daboe01 29163884ce Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-20 17:54:23 +02:00
daboe01 ee36796180 Merge remote-tracking branch 'upstream/main' 2026-06-20 17:52:48 +02:00
daboe01andGitHub 535315485a Merge pull request #3235 from daboe01/paste-as-plain-text
New: standard key binding for pasteAsPlainText:
2026-06-20 14:10:49 +02:00
daboe01 aa592b8bc6 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-20 12:37:15 +02:00
daboe01andGitHub 88146a5389 Merge pull request #3240 from daboe01/baseline-fix
Fixed: baseline alignment in _CPLineFragment to prevent overlapping text descenders
2026-06-20 12:36:57 +02:00
daboe01 c8789c5450 formatting 2026-06-20 12:32:10 +02:00
daboe01 766035f4b7 Fixed: baseline alignment in _CPLineFragment to prevent overlapping text descenders 2026-06-20 12:06:24 +02:00
daboe01andGitHub a90b504dc7 Merge pull request #3239 from daboe01/more-cptextview-fixes
new: heightTracksTextView support in CPTextContainer + rtf fixes
2026-06-20 10:42:26 +02:00
daboe01 26b2280fc2 fixed: color spill issue 2026-06-20 10:40:46 +02:00
daboe01 15bbca8d77 fixed: whitespace issue 2026-06-20 10:36:07 +02:00
daboe01 319bcdaba9 fixed: table roundtrip issue 2026-06-19 22:56:38 +02:00
daboe01 89445f94a2 fixed: whitespace before tab 2026-06-19 22:26:52 +02:00
daboe01 beb4aef424 new: heightTracksTextView support in CPTextContainer 2026-06-19 09:47:39 +02:00
daboe01andGitHub ef680c080d Merge pull request #3238 from daboe01/baseline-fixes
New: implement baseline offsets and superscript/subscript support
2026-06-19 08:52:06 +02:00
daboe01 c187f5de1d fixed: rtf roundtrip with baseline stuff 2026-06-19 08:48:17 +02:00
daboe01 d40a151e05 new: colorpanel sync 2026-06-19 07:18:52 +02:00
daboe01 e67c141921 new: color panel 2026-06-18 22:21:50 +02:00
daboe01 de2ffd3aad New: implement baseline offsets and superscript/subscript support 2026-06-18 19:08:45 +02:00
daboe01 4c8fd58471 fixed: leaky abstraction 2026-06-17 19:22:36 +02:00
daboe01 873a102872 New: standard key binding for pasteAsPlainText: 2026-06-17 18:24:31 +02:00
daboe01 6dc11de431 Revert "New: standard key binding for pasteAsPlainText:"
This reverts commit 9731c4b394.
2026-06-17 18:21:26 +02:00
daboe01 672f25ce27 New: standard key binding for pasteAsPlainText:
This reverts commit 9731c4b394.
2026-06-17 18:20:39 +02:00
daboe01 9731c4b394 New: standard key binding for pasteAsPlainText: 2026-06-17 18:18:39 +02:00
daboe01 e0ce1603ff Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-17 16:21:09 +02:00
daboe01andGitHub bc0380e1cb Merge pull request #3233 from daboe01/fixed-Unable-to-preventDefault-inside-passive-event
new: register wheel event listeners as non-passive
2026-06-17 16:20:37 +02:00
daboe01 1223cfc965 new: register wheel event listeners as non-passive 2026-06-17 16:10:39 +02:00
daboe01andGitHub 361a4203da Merge pull request #3232 from daboe01/table-in-textview-fixes
fixed: table sizing
2026-06-17 08:09:17 +02:00
daboe01 8de928d905 formatting 2026-06-17 08:07:26 +02:00
daboe01 5a3d6e30cb fixed: table sizing 2026-06-17 07:58:33 +02:00
daboe01 0597e477ae Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-16 19:46:20 +02:00
daboe01andGitHub 50bac0a470 Merge pull request #3230 from daboe01/setfontfix
Fixed: CPInvalidArgumentException in CPTextField -setFont: when handling composite theme states
2026-06-16 19:45:49 +02:00
daboe01 cfff603508 Fixed: CPInvalidArgumentException in CPTextField -setFont: when handling composite theme states 2026-06-16 19:43:45 +02:00
daboe01 b9e734a21b Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-16 19:32:18 +02:00
daboe01 45bd822c28 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-16 19:31:19 +02:00
daboe01andGitHub 894a60ad45 Merge pull request #3229 from daboe01/rtf-producer-table-interface
NEW: add table support and other improvements to CPTextView
2026-06-16 19:28:45 +02:00
daboe01andGitHub acbe5885f1 Merge branch 'main' into rtf-producer-table-interface 2026-06-16 19:24:10 +02:00
daboe01 a981b8b04d improved: indentation markers 2026-06-16 19:17:44 +02:00
daboe01 d75c0ac452 improved: tab management 2026-06-16 17:28:03 +02:00
daboe01 da061aac1d improved: table generation 2026-06-16 10:30:00 +02:00
daboe01 77433a3a1c fixed: scrolling glitch 2026-06-16 07:16:14 +02:00
daboe01 4aaa32833a fixed: table roundtrip 2026-06-15 22:33:03 +02:00
daboe01 2a0cebb851 improved: rtf roundtrip 2026-06-15 19:42:53 +02:00
daboe01 1b23bb8a67 new: CPMarkdownParser 2026-06-15 16:43:58 +02:00
daboe01 5dc66c5b4e fixed: roundtrip 2026-06-14 22:56:23 +02:00
daboe01 49b1800027 improved: table generation 2026-06-14 22:42:33 +02:00
daboe01 1cad83aba8 initial display of the table 2026-06-14 21:06:51 +02:00
daboe01andGitHub e1f5238063 Merge pull request #3228 from daboe01/textview-dom-fix
removed: dead code
2026-06-13 22:47:30 +02:00
daboe01 5897d76c04 fixed: imports 2026-06-13 22:38:36 +02:00
daboe01 a0fc7723fc new: table support 2026-06-13 22:36:09 +02:00
daboe01andGitHub ec4a1d1724 Merge pull request #3225 from daboe01/textfield-setfontfix
fixed: ensure custom fonts are respected in all states, styles, and CIBs
2026-06-13 22:27:11 +02:00
daboe01 861c504c41 NEW: add native table support via dynamic protocol detection 2026-06-13 19:47:24 +02:00
daboe01 c9df6e4d25 removed: dead code 2026-06-13 19:18:12 +02:00
daboe01 c9d3ccdf0d fixed: top was not applied to style 2026-06-13 18:42:34 +02:00
daboe01andGitHub d88f2ef7c0 Merge pull request #3227 from daboe01/cptextsizefix
new: Clear the cached visible rect so it gets recalculated after size…
2026-06-13 15:46:28 +02:00
daboe01 14b5a67bf5 new: Clear the cached visible rect so it gets recalculated after size changes 2026-06-13 15:42:48 +02:00
daboe01 3467a60215 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-06-13 12:15:00 +02:00
daboe01andGitHub 210133d9e2 Merge pull request #3224 from daboe01/formatting-/-documentation-CPRuleEditor
new: Documentation for CPRuleEditor I8N stuff
2026-06-05 17:55:09 +02:00
daboe01 524b1df4c9 fix font issue 2026-06-05 17:38:04 +02:00
daboe01 9115949169 new: Documentation for CPRuleEditor I8N stuff 2026-06-04 21:39:02 +02:00
daboe01andGitHub 0b3aa6aac2 Merge pull request #3221 from daboe01/Fixed-CPRuleEditor-word-by-word-translation-issue
Fixed: CPRuleEditor word-by-word translation issue (#1834)
2026-06-04 14:12:08 +02:00
daboe01 e03e0c6fc4 fixed: exception after deleting the last row 2026-06-04 08:55:32 +02:00
daboe01 613a2722be new: dynamic mutilanguage demo 2026-06-04 08:15:52 +02:00
daboe01 e9b9d9702e fixed: localizer issues 2026-06-03 21:19:09 +02:00
daboe01 750c123d1e fixed: redraw issues 2026-06-03 19:19:36 +02:00
daboe01 5e0fc1cbb5 fixes + manual test 2026-06-02 22:20:44 +02:00
daboe01 831e0b4b85 new: rulerview demo 2026-06-01 20:40:52 +02:00
daboe01 d64966594d Merge remote-tracking branch 'upstream/main' into Aristo3 2026-05-30 15:12:50 +02:00
daboe01 015f027cf2 Merge remote-tracking branch 'upstream/main' 2026-05-30 15:12:31 +02:00
daboe01andGitHub c3d9dead19 Merge pull request #3192 from daboe01/fixed-CPControlTextDidBeginEditingNotification-is-not-correctly-triggered
Fixed: timing of CPControlTextDidBeginEditingNotification (#1941)
2026-05-30 15:09:34 +02:00
daboe01andGitHub b296f17d2b Merge pull request #3168 from daboe01/new-cprulerview
new: CPRulerview and friends
2026-05-30 15:08:56 +02:00
daboe01 22fdc971c7 improved: marker icons 2026-05-30 10:11:00 +02:00
daboe01 291be04dc3 improved: rtf parser 2026-05-30 09:40:25 +02:00
daboe01 5c7215c455 new: apply ruler to current paragraph in case of no selection 2026-05-30 09:25:26 +02:00
daboe01 2a83dcb64a fixed: adding new tabstops 2026-05-30 09:08:56 +02:00
daboe01 979118a86f restored: tabstop-fix 2026-05-30 08:55:25 +02:00
daboe01 4fbd50e81f revert: textTabForWidth-"fix" 2026-05-30 08:44:43 +02:00
daboe01 3b1c1c0886 fixed: tab could not be entered using the keyboard 2026-05-29 22:08:00 +02:00
daboe01 dbc8bfc7f3 fixed: pasting tab characters 2026-05-29 21:28:16 +02:00
daboe01 10bb6723f3 fixed: missing global symbols 2026-05-29 20:02:13 +02:00
daboe01 c58a5b520e fixed: tab copypaste 2026-05-29 18:19:03 +02:00
daboe01 7b6cb51f4d fixed: format menu 2026-05-29 11:45:17 +02:00
daboe01 c704880e13 improved: tabstop handles 2026-05-29 11:41:39 +02:00
daboe01 41c422ed03 new: visual feedback when "tearing off" 2026-05-29 10:55:22 +02:00
daboe01 c94ed00f41 fixed: basic ruler view support 2026-05-29 10:46:10 +02:00
daboe01 1d93cf2b1a new: cpscrollview fixes 2026-05-28 21:31:26 +02:00
daboe01 420f3a538d Update AppController.j 2026-05-28 21:23:01 +02:00
daboe01 453e8dbd2e Merge remote-tracking branch 'upstream/main' into new-cprulerview 2026-05-28 21:22:55 +02:00
daboe01 c6549867bd fixed: missing global variable 2026-05-28 21:13:42 +02:00
daboe01 3aecb3ea98 fixed: missing import 2026-05-28 21:11:51 +02:00
daboe01 2819546716 fixed: missing import 2026-05-28 21:07:43 +02:00
daboe01 7e0f03a6fc fixed: CI error 2026-05-28 21:03:31 +02:00
daboe01 6e36fc0b20 formatting 2026-05-28 17:51:10 +02:00
daboe01 e856605a09 _CPRuleEditorViewSliceRow integration 2026-05-28 17:38:14 +02:00
daboe01 d7315773e1 fixed: syntax issues 2026-05-28 17:15:41 +02:00
daboe01 a27aaf8bd0 Fixed: CPRuleEditor word-by-word translation issue (#1834) 2026-05-28 17:11:29 +02:00
daboe01 f2c10e9f4c Merge remote-tracking branch 'upstream/main' into Aristo3 2026-05-25 07:30:22 +02:00
daboe01andGitHub c0b875de5d Merge pull request #3220 from daboe01/rtf-parser-fix
Update _CPRTFParser.j
2026-05-25 06:59:44 +02:00
daboe01 141eb7b98e fixed: wrong characters in output 2026-05-25 06:56:32 +02:00
daboe01 6099311cbd Update _CPRTFParser.j 2026-05-25 06:48:27 +02:00
daboe01andGitHub dcb06fbc4f Fix formatting issues in README.markdown 2026-05-23 13:51:50 +02:00
daboe01andGitHub 8258c8ef0b Revise Cappuccino usage description in README 2026-05-23 13:51:25 +02:00
daboe01andGitHub 9312c96154 Clarify Cappuccino's purpose in README 2026-05-23 09:05:07 +02:00
daboe01andGitHub c27b48d27a Update documentation links in README 2026-05-23 08:46:29 +02:00
daboe01 a091a7c5d6 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-05-21 09:50:35 +02:00
daboe01andGitHub f3039fb77c Merge pull request #3219 from daboe01/cptextview-speed-fix
CPLayoutManager: Fix O(N^2) layout bottleneck when loading large text documents
2026-05-21 09:49:12 +02:00
daboe01 abe159eceb CPLayoutManager: Fix O(N^2) layout bottleneck when loading large text documents 2026-05-21 09:44:54 +02:00
daboe01 f06bdbb14f Merge remote-tracking branch 'upstream/main' into Aristo3 2026-05-19 18:53:15 +02:00
daboe01andGitHub ffb6b3e6fe Merge pull request #3218 from daboe01/cptextview-fixes
Fixed: rare layout issue after inserting multiple newlines and text layout performance improvement
2026-05-19 18:52:34 +02:00
daboe01 7f25de0fad improved: kerning safe optimization 2026-05-17 21:30:11 +02:00
daboe01 d6c5d8b4f2 formatting 2026-05-17 16:47:12 +02:00
daboe01 78e1ea43c8 new: layouting speedup in CPTextView 2026-05-17 15:58:16 +02:00
daboe01andGitHub 9d24c855e2 Update project status in README
Removed outdated information and clarified project status.
2026-05-06 19:55:15 +02:00
daboe01andGitHub 872af9ce2a Revise Cappuccino introduction and project status
Reorganized and condensed introductory information about Cappuccino, emphasizing its purpose and benefits for building desktop-class applications.
2026-05-06 19:54:43 +02:00
daboe01 76a3d0b6da improved: cancel button aesthetics in search field 2026-04-12 12:49:07 +02:00
daboe01andGitHub 18b2a668c5 Merge pull request #3213 from daboe01/double-action-tableview
new: doubleClickTarget and doubleClickArgument for CPTableView
2026-04-09 22:32:00 +02:00
daboe01andGitHub 72be296713 Merge pull request #3214 from daboe01/fixed-tableview-column-removal-issue
Fixed: CPTableView bug where removing a column breaks internal state
2026-04-09 22:30:59 +02:00
daboe01 078ab40d99 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-04-08 15:26:43 +02:00
daboe01 b620991770 Merge remote-tracking branch 'upstream/main' 2026-04-08 15:26:31 +02:00
daboe01andGitHub 732fc2d1e1 Merge pull request #3215 from daboe01/cptreecontroller-fixes
Fixed: CPTreeController selection bug for asynchronously loaded nodes
2026-04-08 15:26:17 +02:00
daboe01 79a9ae31d3 Fixed: CPTreeController selection bug for asynchronously loaded nodes 2026-04-08 15:23:45 +02:00
daboe01 e0e16eeca5 formatting 2026-04-08 15:18:46 +02:00
daboe01 a784435c2b formatting 2026-04-08 15:17:46 +02:00
daboe01 57987b6f67 Fixed: CPTableView bug where removing a column breaks internal state 2026-04-06 17:26:38 +02:00
daboe01 480917eeab new: doubleClickTarget and doubleClickArgument for CPTableView 2026-04-06 13:15:55 +02:00
daboe01andGitHub 013e7bdf7e Merge pull request #3210 from daboe01/cpwebview-resize-fix
Fixed: incorrect CPWebView sizing after animation
2026-04-06 12:08:35 +02:00
daboe01andGitHub c4032ecd1d Merge pull request #3212 from daboe01/tooltip-tableview-fix
New: toolTips for CPTableView and CPOutlineView
2026-04-06 11:59:14 +02:00
daboe01 2ea11eb5ab formatting 2026-04-06 11:58:00 +02:00
daboe01 7fad67484c new: manual tests 2026-04-06 11:56:23 +02:00
daboe01 f34cbbdf29 New: toolTips for CPTableView and CPOutlineView 2026-04-04 15:00:53 +02:00
daboe01 d44dd5da19 fixed: inconsistencies in focusring 2026-04-04 10:50:48 +02:00
daboe01 35132e7f46 fixed: drag-accepting style in colorwells was missing 2026-04-04 10:44:15 +02:00
daboe01 231b3052fc Merge remote-tracking branch 'upstream/main' into Aristo3 2026-04-03 19:01:37 +02:00
daboe01 1c0cc2acef Merge remote-tracking branch 'upstream/main' 2026-04-03 19:01:23 +02:00
daboe01andGitHub 14a4b04d9b Merge pull request #3211 from daboe01/fix-colorpanel-issues
fixed: colorpanel issue
2026-04-03 19:00:47 +02:00
daboe01 8aee2a511f fixed: colorpanel issue 2026-04-03 19:00:09 +02:00
daboe01 9bec13a72b new: color wells 2026-04-03 17:58:57 +02:00
daboe01 65cb048b1b Fixed: incorrect CPWebView sizing after animation 2026-04-03 17:40:45 +02:00
daboe01 fb348ed4f7 merge from main 2026-04-03 16:26:59 +02:00
daboe01 cf10b826d7 Merge remote-tracking branch 'upstream/main' 2026-04-03 16:01:18 +02:00
daboe01andGitHub b7ac5272d0 Merge pull request #3193 from daboe01/fixed-right-buttons-in-buttonbar
New: support for right-aligned buttons in CPButtonBar (#745)
2026-04-03 16:00:37 +02:00
daboe01 dc893f7ca3 Merge remote-tracking branch 'upstream/main' 2026-04-03 15:55:56 +02:00
daboe01andGitHub 206b1d4a05 Merge pull request #3209 from daboe01/cppredicateeditor-nil-value-fix
new: clear CPPredicateEditor rows when objectValue is set to nil
2026-04-03 15:33:10 +02:00
daboe01 0bd83b6635 new: clear CPPredicateEditor rows when objectValue is set to nil 2026-04-03 15:20:59 +02:00
daboe01 4dcfe992de Merge remote-tracking branch 'upstream/main' 2026-04-03 15:16:40 +02:00
daboe01andGitHub ef9d6662cb Merge pull request #3208 from daboe01/new-_CPKVOCollectionOperatorObserver
New:  support collection operators on child key paths
2026-04-03 14:56:29 +02:00
daboe01 0e5f3b1697 fixed caching 2026-04-03 13:49:33 +02:00
daboe01 7fbea7913a new: manual test 2026-04-03 13:39:42 +02:00
daboe01 4b81001330 new: testCollectionOperatorKeyPath 2026-04-03 13:33:06 +02:00
daboe01 043029cf41 new: _CPKVOCollectionOperatorObserver 2026-04-03 13:30:13 +02:00
daboe01 81988f1ec7 Merge remote-tracking branch 'upstream/main' 2026-03-25 19:34:03 +01:00
daboe01 e49144c80a Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-25 19:33:44 +01:00
daboe01andGitHub 9ecc739e8d Merge pull request #3186 from daboe01/colorpanel-fixes
Fix CPColorWell drag-and-drop and responder chain integration
2026-03-25 19:33:27 +01:00
daboe01 94799f19dc Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-24 21:36:21 +01:00
daboe01andGitHub 3d5f4eec18 Merge pull request #3207 from daboe01/cplocalefix
fixed: locale inference
2026-03-24 21:36:02 +01:00
daboe01 c7e89d3018 formatting 2026-03-24 21:34:39 +01:00
daboe01 63e3f6c3fc fixed single region of FF 2026-03-24 21:33:24 +01:00
daboe01 d39a583127 fixed: locale inference 2026-03-24 21:18:43 +01:00
daboe01 e1762fdcdd fixed: column dragging did not work when a panel is open 2026-03-21 11:26:42 +01:00
daboe01 f568427dc2 Merge remote-tracking branch 'upstream/main' 2026-03-15 17:44:59 +01:00
daboe01 fa077e4fc7 fixed: race condition during unfolding animation 2026-03-15 17:44:51 +01:00
daboe01 fd745d37a9 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-15 17:43:57 +01:00
daboe01 2da26b2de2 formatting 2026-03-15 17:41:51 +01:00
daboe01andGitHub 6e486bb30b Merge pull request #3205 from daboe01/fixed-missing-dom-protection
removed: erroneous code spillover
2026-03-15 17:40:54 +01:00
daboe01 5e297dd293 removed: erroneous code spillover 2026-03-15 17:39:33 +01:00
daboe01 17dc653149 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-15 17:09:21 +01:00
daboe01 63cbb04acc Merge remote-tracking branch 'upstream/main' 2026-03-15 17:09:10 +01:00
daboe01andGitHub 6f55c7bbba Merge pull request #3204 from daboe01/fixed-outlineview-animation-race-condition
fixed: racecondition in row expansion animation
2026-03-15 17:08:49 +01:00
daboe01 ea4adf1039 fixed: racecondition in row expansion animation 2026-03-15 17:08:21 +01:00
daboe01 3ff7818d58 Merge remote-tracking branch 'upstream/main' 2026-03-15 11:37:00 +01:00
daboe01 e779198a80 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-15 09:01:42 +01:00
daboe01andGitHub 3e4ebe8e47 Merge pull request #3203 from daboe01/fixed-treeviewbindings
fixed: treeview binding issues
2026-03-15 08:58:21 +01:00
daboe01 b77a374f48 formatting 2026-03-15 08:57:06 +01:00
daboe01 43936f640c formatting 2026-03-15 08:53:04 +01:00
daboe01 6671d371e9 fixed: treeview binding issues 2026-03-15 08:51:03 +01:00
daboe01 4fd99d2c52 fixed: tokenfield CPControlTextDidBeginEditingNotification notification 2026-03-14 19:25:50 +01:00
daboe01 092550e2a3 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-14 13:02:38 +01:00
daboe01andGitHub 6563e730a0 Merge pull request #3202 from daboe01/textview-caret-blinking-fix
fixed: caret is invisible at the end of the line
2026-03-14 13:02:28 +01:00
daboe01 b39c3e8d4b fixed: caret is invisible at the end of the line 2026-03-14 13:01:58 +01:00
daboe01 579129b891 fixed: usability issues with horizontal scroller 2026-03-14 12:51:46 +01:00
daboe01 ec8bb7e14d Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-13 16:04:26 +01:00
daboe01andGitHub 9f092a3c2e Merge pull request #3201 from daboe01/cptexctview-smartpaste-fix
fixed: smartpaste crash
2026-03-13 16:04:15 +01:00
daboe01 5b820b270c fixed: smartpaste crash 2026-03-13 16:03:30 +01:00
daboe01 4af4c6706f fixed: HUD scrollers 2026-03-12 19:53:41 +01:00
daboe01 da2c89daf3 Revert "stop tracking build artifacts"
This reverts commit ea425ae4c4.
2026-03-11 06:39:06 +01:00
daboe01 ea425ae4c4 stop tracking build artifacts 2026-03-11 06:35:24 +01:00
daboe01 fb9c0715b3 stop tracking generated imagesize 2026-03-11 06:34:34 +01:00
daboe01 8cedfebc07 stop tracking generated fontinfo 2026-03-11 06:34:11 +01:00
daboe01 93736b7ba9 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-11 06:29:57 +01:00
daboe01 37a6e8fde5 Merge remote-tracking branch 'upstream/main' 2026-03-11 06:29:40 +01:00
daboe01andGitHub 0274d9fee7 Merge pull request #3199 from daboe01/fixed-textsizing-via-canvas-not-functional-anymore
fixed: canvas text sizing is not functional anymore
2026-03-11 06:29:08 +01:00
daboe01 f223644640 fixed: canvas text sizing is not functional anymore 2026-03-11 06:26:10 +01:00
daboe01andGitHub 0aa4607d8e Merge pull request #3191 from daboe01/fixed-CPTextField-called-error-method-called-3-times
Fixed: multiple didFailToFormatString delegate calls in CPTextField (#1546)
2026-03-08 21:28:57 +01:00
daboe01andGitHub 37b68bb166 Merge pull request #3194 from daboe01/fixed-CPTokenField-autocomplete-does-not-work-when-placed-in-toolbar
fixed: CPTokenField autocomplete when placed in a CPToolbar
2026-03-08 21:27:59 +01:00
daboe01 e801064bd9 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-08 21:20:32 +01:00
daboe01andGitHub ae4eef5d16 Merge pull request #3156 from daboe01/cpoutlineview-delegate-fix
fixed: CPOutlineView should be its own tableView delegate and datasource
2026-03-08 21:20:09 +01:00
daboe01 fdba5f55c5 fixed> syntax error 2026-03-08 21:16:54 +01:00
daboe01 bfeaabc038 Merge remote-tracking branch 'upstream/main' into cpoutlineview-delegate-fix 2026-03-08 21:13:47 +01:00
daboe01 6b9bad37c2 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-08 21:09:57 +01:00
daboe01andGitHub a32b2d5f73 Merge pull request #3197 from daboe01/outlineview-fixes
removed: dead code
2026-03-08 21:09:20 +01:00
daboe01 9f4db09bec removed: dead code 2026-03-08 21:07:08 +01:00
daboe01 58fe3d3d27 Merge remote-tracking branch 'upstream/main' 2026-03-08 21:03:35 +01:00
daboe01andGitHub 28303a7893 Merge pull request #3196 from daboe01/new-CPTreeController
New: CPTreeController
2026-03-08 19:56:44 +01:00
daboe01 031e5e7ca3 fixed: selection issues 2026-03-08 19:52:58 +01:00
daboe01 c70079ae71 improved: bindings to CPOutlineView 2026-03-08 18:03:52 +01:00
daboe01 8ac2b94400 refac 2026-03-07 21:48:10 +01:00
daboe01 b1f2fb44a8 new: CPOutlineView bindings support 2026-03-07 21:43:41 +01:00
daboe01 cd4c3cffe0 fixed: test cases 2026-03-07 20:05:50 +01:00
daboe01 98c01b88e2 fixed various binding issues 2026-03-07 20:00:09 +01:00
daboe01 4970a2742d fixed: recursive tree building 2026-03-07 19:47:31 +01:00
daboe01 10d18e35ed formatting 2026-03-07 19:28:04 +01:00
daboe01 12960f8160 new: testcase 2026-03-07 19:27:01 +01:00
daboe01 622d738b44 new: CPTreeController 2026-03-07 19:25:32 +01:00
daboe01 4e5c0aed67 formatting 2026-03-07 17:29:49 +01:00
daboe01 96d3ad2c47 fixed: CPTokenField autocomplete when placed in a CPToolbar 2026-03-07 17:11:25 +01:00
daboe01 c519784ecc New: support for right-aligned buttons in CPButtonBar (#745) 2026-03-07 16:17:36 +01:00
daboe01 d0d7033f63 Fixed: timing of CPControlTextDidBeginEditingNotification (#1941) 2026-03-07 14:53:44 +01:00
daboe01 9aaa565657 fixed: accidental code spillover 2026-03-07 14:40:34 +01:00
daboe01 50e98d1335 Merge remote-tracking branch 'upstream/main' 2026-03-07 14:37:32 +01:00
daboe01 837df80b64 fixed: accidental code spill over 2026-03-07 14:33:31 +01:00
daboe01 f7a27cf905 fixed: delegate method was called more than once by textfield 2026-03-07 14:26:51 +01:00
daboe01 915047b996 fixed: scrollview white border view in HUD 2026-03-07 11:22:41 +01:00
daboe01 8231d8ddf1 new removal of canvas in _CPDatePickerClock 2026-03-07 10:28:47 +01:00
daboe01 125673880c fixed: cornerview in HUD 2026-03-06 19:45:55 +01:00
daboe01 b24cb2c48e fixed: disclosure triangle was canvas based 2026-03-06 19:21:55 +01:00
daboe01 a56458384c rework of tooltips to use only CSS + support for HUD 2026-03-05 08:31:01 +01:00
daboe01 063f2f0283 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-03-03 18:20:36 +01:00
daboe01andGitHub da35d6b6ed Merge pull request #3190 from daboe01/textview-copypaste-fix
Use setTimeout to prevent flickering on copy/paste
2026-03-03 18:19:59 +01:00
daboe01 fca30325e4 Use setTimeout to prevent flickering on copy/paste 2026-03-03 18:18:32 +01:00
daboe01 4aac83b50d removed: unnecessary triangle border 2026-03-02 20:57:10 +01:00
daboe01 48b4345e80 new: full animation support for CPOutlineView 2026-03-02 16:09:29 +01:00
daboe01 4636c0f3b8 fixed: double click error 2026-03-01 20:21:48 +01:00
daboe01 db5f5ba332 improved: view animation capabilities 2026-03-01 19:58:56 +01:00
daboe01 7c093e1a64 fixed: outline-view disclosure animation 2026-03-01 19:29:51 +01:00
daboe01 8f48d01c33 removed: enabled/disabled drawing emulation via alpha 2026-02-28 19:37:59 +01:00
daboe01 89d345dfdc fixed: added missing isActive 2026-02-28 19:35:51 +01:00
daboe01 1bbf87e2cb fixed: testcases 2026-02-28 18:26:47 +01:00
daboe01 ec9c658565 fixed: acceptsFirstResponder was uncoditionally true 2026-02-28 17:07:02 +01:00
daboe01 8db24fcaf6 new: testcases from enquora 2026-02-28 16:46:34 +01:00
daboe01 d9859af73c new: disabled state CPColorWell 2026-02-28 16:04:38 +01:00
daboe01 bce326de9d fixed: Colorwell did not reflect enabled state visually 2026-02-28 15:57:19 +01:00
daboe01 83bc94d1c3 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-02-23 14:25:34 +01:00
daboe01andGitHub 676ddc4601 Merge pull request #3189 from daboe01/fixed-key-duplication-on-alt
fixed: key duplication in textview on ALT
2026-02-23 14:25:19 +01:00
daboe01 19cceb4dd9 fixed: key duplication in textview on ALT 2026-02-23 14:24:09 +01:00
daboe01 3a4b8fe947 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-02-22 16:30:50 +01:00
daboe01andGitHub f31ac7cf21 Merge pull request #3188 from daboe01/windowcontenviewfix
Fixed: CPWindow setContentView: overlapping the toolbar
2026-02-22 16:30:25 +01:00
daboe01 802c644d94 Fixed: CPWindow setContentView: overlapping the toolbar 2026-02-22 14:54:46 +01:00
daboe01 5385e63fd2 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-02-22 11:33:31 +01:00
daboe01andGitHub 749909b23c Merge pull request #3187 from daboe01/fixed_keyequivalent_dom_bubbling_issue
fixed: menu key support conflicting with browser native menu keys
2026-02-22 11:32:33 +01:00
daboe01 df594ae48e fixed: copy/paste issue 2026-02-21 18:48:19 +01:00
daboe01 980e2f2c91 fixed: dom protection was missing 2026-02-21 12:08:47 +01:00
daboe01 e1a3f2b64d fixed: submenu positioning issue 2026-02-21 12:07:20 +01:00
daboe01 ea47767bb8 fixed: menu key support conflicting with browser native menu keys 2026-02-21 11:51:25 +01:00
daboe01 3d3674a7d8 fixed: out of bounds exception when dragging rule editor slices 2026-02-19 19:51:34 +01:00
daboe01 192b6ecfc4 fixed: rule editor selection 2026-02-19 19:44:11 +01:00
daboe01 79ff197cde fixed: HUD insertionpointcolor in demo app 2026-02-18 19:17:43 +01:00
daboe01 836698f8d2 fixed: caret color 2026-02-18 17:29:36 +01:00
daboe01 ac52d32c29 fixed: caret color in textview 2026-02-18 16:17:45 +01:00
daboe01 e66a0656c8 fixed: HUD style bridgewindow 2026-02-17 16:09:15 +01:00
daboe01 7dcf5629d7 fixed: combobox issue when selecting an element in placeholder state 2026-02-15 21:10:14 +01:00
daboe01 facb129183 fixed: HUD placeholder colors 2026-02-15 20:39:30 +01:00
daboe01 0b83c20648 fixed: life update 2026-02-15 19:53:17 +01:00
daboe01 0f9cadf8d3 fixed: life update 2026-02-15 19:53:00 +01:00
daboe01 c82e5750f0 fixed: the first version was not functional 2026-02-15 18:28:20 +01:00
daboe01 13557bd13f selection in colorwell 2026-02-15 18:27:01 +01:00
daboe01 2ff5eb39eb Fix CPColorWell drag-and-drop and responder chain integration 2026-02-15 12:32:13 +01:00
daboe01 dccdfb5635 fixed: popup HUD 2026-02-14 10:19:47 +01:00
daboe01 aba4c865e4 performance optimisation 2026-02-12 22:05:57 +01:00
daboe01 9709ca8b52 new: ThemingAdditions and HUD autosetter 2026-02-12 18:18:25 +01:00
daboe01 ea74a503a3 fixed: label truncation in segmented controls 2026-02-11 10:34:31 +01:00
daboe01 675d1ebd57 fixed: gap in segmented controls 2026-02-10 19:25:14 +01:00
daboe01 7eb042d985 fixed: missing dom protection 2026-02-09 19:53:01 +01:00
daboe01 dc4aa545f0 fixed: dom error 2026-02-09 19:48:46 +01:00
daboe01 b70abb730d fixed: unfocussed selection color in column 2026-02-09 19:36:44 +01:00
daboe01 36a2cc9a62 scroll out / -in fr fix 2026-02-09 18:27:20 +01:00
daboe01 113be1254e hud fix 2026-02-09 08:26:40 +01:00
daboe01 674a03dc23 fixed: table column dragging when selected 2026-02-08 22:36:22 +01:00
daboe01 9656985dbd fixed: animation header glitch 2026-02-08 20:46:07 +01:00
daboe01 0b4fe62f89 new: group row theming 2026-02-08 19:34:15 +01:00
daboe01 847a9c2b98 new: column-selection 2026-02-08 19:30:06 +01:00
daboe01 ce78d098ec group row fix 2026-02-08 19:03:09 +01:00
daboe01 18c1866db7 fixed: zebra fill 2026-02-08 17:11:17 +01:00
daboe01 1ae0566cf1 fixed: faulty test case 2026-02-08 16:00:58 +01:00
daboe01 383fb9d704 fixed: missing dom protection 2026-02-08 14:26:58 +01:00
daboe01 7f281d0442 restored: group row support 2026-02-08 12:20:48 +01:00
daboe01 2d76a03b01 new: dom based tableview background 2026-02-08 09:29:29 +01:00
daboe01 f96ffde1fb Merge remote-tracking branch 'upstream/main' into Aristo3 2026-02-07 10:03:31 +01:00
daboe01andGitHub e216f0a355 Merge pull request #3184 from daboe01/fixed-wrong-event-factory-cover-method
fixed: faulty event factory covermethod
2026-02-06 16:17:17 +01:00
daboe01 0e86264837 fixed: faulty event factory covermethod 2026-02-06 16:16:08 +01:00
daboe01 39fb7c6b5b formatting 2026-02-03 08:20:15 +01:00
daboe01 0fa2b62cc9 fixed: text jumping issues on FF 2026-02-03 07:14:20 +01:00
daboe01 ea69f8b9cc fixed: clock hands in FF 2026-02-02 15:55:49 +01:00
daboe01 b0e88390e5 new: tooltip 2026-02-01 21:23:46 +01:00
daboe01andGitHub 57d0750f3c Revise README for updated application demos and tutorials
Updated links and descriptions in the README to reflect current resources.
2026-02-01 21:02:42 +01:00
daboe01 4882f26142 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-02-01 19:44:07 +01:00
daboe01andGitHub 3be864e50f Merge pull request #3180 from daboe01/cptextview-changefont-fix
fixed: crash when changing typing attributes without selection
2026-02-01 19:43:50 +01:00
daboe01 d583403195 fixed: crash when changing typing attributes without selection 2026-02-01 19:43:17 +01:00
daboe01 373737cfd0 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-02-01 19:31:27 +01:00
daboe01andGitHub 946362f0d1 Merge pull request #3179 from daboe01/calendarstepper-fix
fixed: prevent timezone shift from reverting month navigation in _CPD…
2026-02-01 19:30:54 +01:00
daboe01 7ee4003840 fixed: prevent timezone shift from reverting month navigation in _CPDatePickerCalendar 2026-02-01 19:29:59 +01:00
daboe01 475945d329 improved: A3 showcase app 2026-02-01 19:28:19 +01:00
daboe01 5111a02672 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-31 21:18:09 +01:00
daboe01andGitHub f2bebcb2e6 Merge pull request #3178 from daboe01/improved-font-panel
new: font preview in fontpanel
2026-01-31 21:17:15 +01:00
daboe01 e413f3a9fd fixed: column sizing 2026-01-31 21:12:35 +01:00
daboe01 0c4d33dccc new: font preview in fontpanel 2026-01-31 18:21:41 +01:00
daboe01 12477179cb new: calendar in demo app 2026-01-31 18:05:15 +01:00
daboe01 ab81b1ad32 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-30 22:42:05 +01:00
daboe01andGitHub 4af8fcc008 Merge pull request #3177 from daboe01/menushortcut-textviewfix
fixed: keystrokes from menu shortcuts are inserted into textviews
2026-01-30 22:41:47 +01:00
daboe01 6800472725 fixed: keystrokes from menu shortcuts are inserted into textviews 2026-01-30 22:40:35 +01:00
daboe01 0af674ca74 fixed: textview sizing 2026-01-29 20:27:11 +01:00
daboe01 5b95490371 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-29 19:17:10 +01:00
daboe01andGitHub e6b9b9cfbd Merge pull request #3176 from daboe01/fixed-cpdatepicker-relied-on-deprecated-keycode-property
Fixed cpdatepicker relied on deprecated keycode property
2026-01-29 19:14:37 +01:00
daboe01 9dfaf293f2 Removed: hardcoded KeyCodes as they are unreliable 2026-01-29 19:12:17 +01:00
daboe01 fffcc85486 fixed: cpdatepicker relied on deprecated keycode property 2026-01-29 19:06:35 +01:00
daboe01 993e9a9e86 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-28 21:42:05 +01:00
daboe01andGitHub b349b23b81 Merge pull request #3175 from daboe01/fontmanager-font-trait-fix
fixed: addFontTrait
2026-01-28 21:41:36 +01:00
daboe01 886a4a422f fixed: addFontTrait 2026-01-28 21:41:15 +01:00
daboe01 2f32e5eaef Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-28 21:24:37 +01:00
daboe01andGitHub d8dd3d0660 Merge pull request #3174 from daboe01/new-texview-format-action-methods
new: CPTextView action methods for format menu
2026-01-28 21:24:12 +01:00
daboe01 f19901c214 new: action methods for format menu 2026-01-28 21:23:03 +01:00
daboe01 fcd1897883 new: format menu 2026-01-28 21:21:56 +01:00
daboe01 77063efe58 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-28 21:01:46 +01:00
daboe01andGitHub e198fbdc40 Merge pull request #3122 from daboe01/fixed-keycode-management-in-cptextview
fixed: Keyboard-event handling is based on the deprecated charCode
2026-01-28 20:57:07 +01:00
daboe01andGitHub 3dd97c0b49 Merge pull request #3135 from daboe01/ipad-double-tap-support
improved: double click emulation by double tapping
2026-01-28 20:56:34 +01:00
daboe01 57a53abbf7 fixed: textcolor in CPBrowser selected row 2026-01-28 19:59:59 +01:00
daboe01 e30380a8f9 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-28 19:39:50 +01:00
daboe01andGitHub b93aa732d2 Merge pull request #3173 from daboe01/fixed-datepicker-nextkeyview-loop
fixed: keyview loop trap
2026-01-28 19:39:04 +01:00
daboe01 16d8780812 fixed: keyview loop trap 2026-01-28 19:38:29 +01:00
daboe01 cdcaf1c4d4 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-28 19:28:42 +01:00
daboe01andGitHub febd1260cd Merge pull request #3172 from daboe01/cptext-fix
fixed: acceptsFirstMouse was missing
2026-01-28 19:24:57 +01:00
daboe01 45162a3fcb fixed: acceptsFirstMouse was missing 2026-01-28 19:24:29 +01:00
daboe01 e62b22da55 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-28 07:46:28 +01:00
daboe01andGitHub b598e80c58 Merge pull request #3171 from daboe01/textual-picker-key-navi-fix
Fixed: CPDatePicker tab navigation when elements are hidden
2026-01-28 07:28:01 +01:00
daboe01 41648b8605 Fixed: CPDatePicker tab navigation when elements are hidden 2026-01-28 07:26:02 +01:00
daboe01 7bd8660f89 browser hud 2026-01-28 07:22:58 +01:00
daboe01 d0c63345f2 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-27 20:04:29 +01:00
daboe01andGitHub 6b285cd7f0 Merge pull request #3170 from daboe01/fontpanel-fixes
fixed: fontpanel was crashing
2026-01-27 20:04:15 +01:00
daboe01 a5e9d65852 fixed: fontpanel was crashing 2026-01-27 20:03:05 +01:00
daboe01 1a7b7054fb improved: animation 2026-01-27 19:46:49 +01:00
daboe01 01fc279709 improved: animation 2026-01-27 19:41:18 +01:00
daboe01 5540ef8ca7 new: triangle animation in outline view 2026-01-26 22:38:49 +01:00
daboe01 ef3ee2e660 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-26 19:47:33 +01:00
daboe01 d661c83acc fixed disclosure colors in outlineview 2026-01-26 07:32:35 +01:00
daboe01 437bcc8078 revert: cpoutlineview 2026-01-26 07:26:22 +01:00
daboe01andGitHub 94195237d9 Merge pull request #3169 from daboe01/calayer-fixes
fixed: group animation semantics and test
2026-01-26 07:18:24 +01:00
daboe01 b8f63166cd fixed: group animation semantics and test 2026-01-26 07:18:02 +01:00
daboe01 68316732de improved: alert panels 2026-01-26 07:08:00 +01:00
daboe01 0a42b39a94 Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-25 19:35:20 +01:00
daboe01andGitHub fc91326a5f Merge pull request #3147 from daboe01/new-caanimationgroup
New: CAAnimationGroup and implement timer-based animations in CALayer
2026-01-25 19:34:34 +01:00
daboe01 0fc2ed7081 new: manual test 2026-01-25 19:33:33 +01:00
daboe01 8433d4a1d1 new: support for kCAMediaTimingFunctionEaseInEaseOut 2026-01-25 17:25:17 +01:00
daboe01 ac1407484a fixed: outlineview arrow colors 2026-01-25 17:19:07 +01:00
daboe01 a531dc6fc0 improved: demo app 2026-01-25 11:45:23 +01:00
daboe01 e42d5b860e fixed: A3 demo app layout issues 2026-01-24 19:25:01 +01:00
daboe01 57858db161 new: CPOutlineView 2026-01-24 19:15:37 +01:00
daboe01 f3392c7df1 fixed: RuleEditor was not functional 2026-01-24 16:55:26 +01:00
daboe01 18c2145138 fixed HUD rule editor 2026-01-24 12:14:08 +01:00
daboe01 ff6981bf10 fixed: HUD table view issues 2026-01-23 22:38:05 +01:00
daboe01 db82911b1f fixed: circular slider HUD 2026-01-23 19:52:36 +01:00
daboe01 768f5121cb fixed: column D&D animation issue when dragging outside 2026-01-23 19:47:53 +01:00
daboe01 c2d7a1dc74 cpstepper HUD fix 2026-01-23 19:39:41 +01:00
daboe01 61efb2fcdf ruleeditor fixes 2026-01-22 17:20:17 +01:00
daboe01 b6716f77e0 improved A3 app 2026-01-22 17:07:17 +01:00
daboe01 47c4bb957b fixed: datepicker in demoapp 2026-01-22 16:42:37 +01:00
daboe01 1a64819ab5 fixed: segmented control height 2026-01-22 16:08:09 +01:00
daboe01 ff7939df03 micro alignement fixes 2026-01-22 14:52:43 +01:00
daboe01 443ced0dce text baseline fixes 2026-01-22 08:15:15 +01:00
daboe01 baf505df52 HUD pulldown fix 2026-01-22 07:21:12 +01:00
daboe01 90331b075a hud popup fixes 2026-01-21 22:53:21 +01:00
daboe01 900e3b6ea1 fixed: pulldown HUD 2026-01-21 22:44:47 +01:00
daboe01 74d8463255 fixed: button types in A3 demo 2026-01-21 22:27:59 +01:00
daboe01 af6692d616 fixed: HUD stuff 2026-01-21 19:57:12 +01:00
daboe01 6611b74c19 HUD fixes 2026-01-21 19:53:01 +01:00
daboe01 37bff8b70b buttonbar HUD fixes 2026-01-21 17:16:28 +01:00
daboe01 d964b9f10a fixed: HUD combobox selection color 2026-01-21 09:57:44 +01:00
daboe01 189a2ddc3d fixed: combox textcolor in tableviews 2026-01-21 09:54:40 +01:00
daboe01 641b35d965 fixed: hud tableview header 2026-01-21 07:52:19 +01:00
daboe01 433e6606b6 new: CPBox in A3 test app 2026-01-21 07:36:00 +01:00
daboe01 bc88af047e fixed: hud for tableviews 2026-01-20 22:49:43 +01:00
daboe01 b93beb8730 improved: A3 demo 2026-01-20 07:28:35 +01:00
daboe01 24a176c736 improved: A3 kitchensink 2026-01-19 22:08:12 +01:00
daboe01 4d741040b6 fixed: CPSegmentedControl crash 2026-01-19 19:21:28 +01:00
daboe01 92a30edf6c fixed: textcolor in tableviews 2026-01-19 18:39:20 +01:00
daboe01andGitHub 2615577173 Fix Kitchen Sink demo link in README
Updated the link for the Kitchen Sink demo in the README.
2026-01-19 07:50:18 +01:00
daboe01 031c3acc57 new: hud split view 2026-01-18 22:39:14 +01:00
daboe01 500541a785 new: box hud 2026-01-18 22:33:57 +01:00
daboe01 60e01cdb33 fixed HUD recycling issue with popups 2026-01-18 19:48:40 +01:00
daboe01 c0c5325d8a fixed: circular slider disabled HUD 2026-01-18 15:27:03 +01:00
daboe01 fbe4b3d439 fixed: HUD combobox 2026-01-18 14:43:28 +01:00
daboe01 aa72c9e081 new: HUD button bar 2026-01-18 13:51:08 +01:00
daboe01 91db2f409e popup fix 2026-01-18 13:47:08 +01:00
daboe01 2c2211026f fixed: colors in levelindicator 2026-01-18 13:44:59 +01:00
daboe01 578f00172f improved: A3 demo app 2026-01-17 19:47:46 +01:00
daboe01 fb93d3dcf0 fixed: levelindicator 2026-01-17 19:42:46 +01:00
daboe01 10989ada31 fixed: segmented control hud 2026-01-17 19:29:48 +01:00
daboe01 28e9063c04 fixed: tokenfield HUD 2026-01-17 18:03:22 +01:00
daboe01 63ec5e78b6 fixed: stepper in HUD 2026-01-17 17:59:05 +01:00
daboe01 52f6503631 new: combobox HUD 2026-01-17 16:00:05 +01:00
daboe01 a5a91d7258 fixed: HUD issues 2026-01-17 11:22:08 +01:00
daboe01 a00b94ec33 new: KitchenSinkA3 (code only) 2026-01-17 09:13:32 +01:00
daboe01 78b1e292f9 fixed: comboboxes in tableviews 2026-01-16 23:34:08 +01:00
daboe01 d9d86df1bd formatting 2026-01-16 23:26:30 +01:00
daboe01 b5f9f05f9f formatting 2026-01-16 23:25:05 +01:00
daboe01 245561d40f fixed: nib2cib crashes with CPProgressIndicator 2026-01-16 23:22:23 +01:00
daboe01 9301ba7c25 fixed: button positions 2026-01-16 19:45:06 +01:00
daboe01 24e9eadfab fixed: button positions 2026-01-16 19:43:24 +01:00
daboe01 fe8f2deb6e shifted push buttons from IB 2px to the left 2026-01-16 19:00:06 +01:00
daboe01 5aa315e190 fixed: syntax errors 2026-01-16 17:17:00 +01:00
daboe01 d180a25f2e new: CPRulerview and friends 2026-01-16 17:08:50 +01:00
daboe01 1ae19f88b9 revert: nib2cib adjustment for push buttons 2026-01-16 17:02:38 +01:00
daboe01 50702a0560 fixed: combobox alignment issues 2026-01-16 09:39:07 +01:00
daboe01 fbee21dad3 removed: off-spec autostart of spinners 2026-01-16 08:56:20 +01:00
daboe01 13c3fe5771 fixed: missing visual feedback in buttobar gear icon 2026-01-15 22:44:28 +01:00
daboe01 fde9f56d4d fixed: nib2cib adjustment for push buttons 2026-01-15 19:32:46 +01:00
daboe01 f2ca8ab940 make control heights more consistent 2026-01-15 18:54:42 +01:00
daboe01 3d0b1b7680 fixed: window-close-button and friends did not highlight 2026-01-15 08:42:46 +01:00
daboe01 40172028e6 improved: progress indicators 2026-01-15 08:16:11 +01:00
daboe01 49923fa140 fixed: progress indicators 2026-01-15 07:47:10 +01:00
daboe01 b96a4e5b94 fixed textfield alignment issues 2026-01-14 19:44:40 +01:00
daboe01 9a50fc4789 fixed: pulldown glitch 2026-01-14 10:56:26 +01:00
daboe01 601daf9e72 fixed: docmodal black bar on top 2026-01-13 22:40:38 +01:00
daboe01 2658852ec2 Update ThemeDescriptors.j 2026-01-13 21:41:36 +01:00
daboe01 737fd115e4 fixed: button issues 2026-01-13 20:19:47 +01:00
daboe01 461f341abc fixed: button HUD styles 2026-01-12 20:27:04 +01:00
daboe01 c26feeb73e formatting 2026-01-11 19:49:47 +01:00
daboe01 29714ae18f default theme aristo3 in segmented control test 2026-01-11 19:22:07 +01:00
daboe01 496f934adb fixed: HUD menu scrolling indicator issues 2026-01-11 18:54:47 +01:00
daboe01 bf15c2bf11 fixed: scroll indicators in menus 2026-01-11 09:10:54 +01:00
daboe01 07cf8ac9be checkbox / radio / segmented control cleanup 2026-01-11 08:58:43 +01:00
daboe01 9708f80ad4 new: HUD animations 2026-01-11 08:28:58 +01:00
daboe01 0bd0dc6216 fixed: diverse HUD issues 2026-01-11 00:04:12 +01:00
daboe01 d3e5583070 new: HUD for scrollers 2026-01-10 17:25:40 +01:00
daboe01 10eedb2bb5 realign rounded textured with push buttons (IB correction) 2026-01-10 16:39:53 +01:00
daboe01 d381a602b7 move IB push buttons 6 pixels downward 2026-01-10 16:36:18 +01:00
daboe01 266251ffbf new: HUD support 2026-01-10 13:24:33 +01:00
daboe01 cd9bb7a86e make popup 0,5px larger 2026-01-10 12:46:33 +01:00
daboe01 97f98e3c93 Revert "fixed: popup-position from IB"
This reverts commit c72c761a60.
2026-01-10 12:41:27 +01:00
daboe01 c72c761a60 fixed: popup-position from IB 2026-01-10 10:19:08 +01:00
daboe01 2fe83853ac Revert "fixed: popup-position from IB"
This reverts commit cd940d64ed.
2026-01-10 10:12:51 +01:00
daboe01 cd940d64ed fixed: popup-position from IB 2026-01-10 10:08:44 +01:00
daboe01 15d4575b0a fixed: CPRuleEditor 2026-01-09 21:45:50 +01:00
daboe01 e21d23e911 fixed: popup-position from IB 2026-01-09 21:16:40 +01:00
daboe01 9e649ce96a fixed: sorting arrow was too small 2026-01-07 22:44:09 +01:00
daboe01 68526721b7 fixed: textured rounded button issues 2026-01-07 19:36:13 +01:00
daboe01 be3c5dac8a minor segmented control adjustment 2026-01-07 17:25:52 +01:00
daboe01 26cac2b84d removed: old artwork 2026-01-06 22:30:15 +01:00
daboe01 2f552abf94 fixed: disclosure button issue in outline view 2026-01-06 19:52:36 +01:00
daboe01 7129ebe884 removed: old artwork 2026-01-06 19:33:26 +01:00
daboe01 b9992215f8 new: make theme selectable in outline test 2026-01-06 19:32:49 +01:00
daboe01 0c55a5f372 removed: old artwork 2026-01-06 18:26:08 +01:00
daboe01 8cc33d1e37 fixed: datepicker 2026-01-06 18:15:40 +01:00
daboe01 73169f0be5 new: datepicker theme selectability 2026-01-06 11:30:27 +01:00
daboe01 7f8d3f289b fixed: splitview issues 2026-01-06 10:58:25 +01:00
daboe01 d8c74e81b8 fixed: button sizing issues 2026-01-06 10:29:46 +01:00
daboe01 3000254e67 formatting 2026-01-06 09:13:08 +01:00
daboe01 2356340f0b fixed: toolbar overplotting issue 2026-01-05 23:06:03 +01:00
daboe01 227d743666 fixed: pulldown menu 2026-01-05 22:25:53 +01:00
daboe01 b53feeb04c fixed: tokenfield issues 2026-01-05 19:53:30 +01:00
daboe01 3b1043a6b4 alternative image in button bar 2026-01-04 22:25:51 +01:00
daboe01 ba32f92e69 buttonbar fixes 2026-01-04 16:52:07 +01:00
daboe01 5c7d978245 fixed: #3059 2026-01-04 15:11:09 +01:00
daboe01 d96b642f4f fixed: #3051 2026-01-04 15:03:30 +01:00
daboe01 2366c5c1fd fixed: momentary push issue 2026-01-04 14:52:48 +01:00
daboe01 84a85a767c updated gear icon 2026-01-04 14:24:56 +01:00
daboe01 e0c9bd10a4 fixed: popupbutton were invisible when created in code 2026-01-04 11:33:07 +01:00
daboe01 eed6c93349 fixed: combo-box small and mini 2026-01-03 23:15:34 +01:00
daboe01 7023bb8b90 new: svg images for alert panel 2026-01-03 22:51:45 +01:00
daboe01 579814a5de fixed: images did not show in alert panels 2026-01-03 21:01:53 +01:00
daboe01 a2786a9aee fixed: image and shadow issues 2026-01-03 19:44:24 +01:00
daboe01 bcd0c725ce fixed: datepicker clipping issue 2026-01-02 23:25:29 +01:00
daboe01 3c1c01fd9f Merge remote-tracking branch 'upstream/main' into Aristo3 2026-01-02 21:53:59 +01:00
daboe01 3364d1c5cc code cleanup + refac to use svg instead of bitmap 2026-01-02 21:53:37 +01:00
daboe01andGitHub 8d61a5fbe1 Correct release year in project status
Update project status release year from 2025 to 2026.
2025-12-31 09:18:30 +01:00
daboe01 b2cd64b956 Revert "fixed: test-cases"
This reverts commit 53488c0adb.
2025-12-28 21:15:13 +01:00
daboe01 53488c0adb fixed: test-cases 2025-12-28 21:14:29 +01:00
daboe01andGitHub ca9b8a78b2 Merge pull request #3167 from michaelbach/main
Fix #3166: zero width/height for svg images
2025-12-27 22:15:15 +01:00
Michael BachandGitHub c05b900fe6 improve formatting of fox 2025-12-27 21:58:00 +01:00
Michael BachandGitHub 907450a63d Fix zero width/height for svg images 2025-12-27 21:38:14 +01:00
daboe01andGitHub 6b1e308dab Merge pull request #3157 from daboe01/fixed-does-not-properly-transform-the-DOMElements-bounds-if-the-view-has-a-non-zero-origin
Fixed: [CPView addSubview:] was ignoring bounds origin for DOM positioning
2025-12-26 12:34:21 +01:00
daboe01andGitHub e28df7d7d7 Merge pull request #3164 from daboe01/cpmenu-sticky-fix
Cpmenu top level item sticky selection fix
2025-12-26 12:21:47 +01:00
daboe01andGitHub 84d3a820f2 Merge pull request #3152 from daboe01/new-menu-icon-placement-issue-fix
new: Left-align menu bar icon and title
2025-12-26 12:16:01 +01:00
daboe01 dda325b51b fixed: wrong target for bounds 2025-12-26 12:09:15 +01:00
daboe01 f0e29069a9 fixed: sticky main menu selection issue 2025-12-26 12:06:18 +01:00
daboe01andGitHub 4730c23a97 Merge pull request #3154 from daboe01/Fixed-CPMenu-items-appearing-on-the-wrong-side-of-the-menu-bar
Fixed: CPMenu items appearing on the wrong side of the menu bar
2025-12-26 11:56:24 +01:00
daboe01andGitHub 593d098cc1 Merge pull request #3153 from daboe01/fixed-Main-menus-with-no-items-appear-in-the-menu-bar
new: Hide main menu items that do not have submenus
2025-12-26 11:51:52 +01:00
daboe01andGitHub 2e15f351a9 Merge branch 'main' into fixed-Main-menus-with-no-items-appear-in-the-menu-bar 2025-12-26 11:48:12 +01:00
daboe01 14a195592a new manual test 2025-12-26 11:43:54 +01:00
daboe01andGitHub 7c81471a3a Merge pull request #3149 from daboe01/new-CPMenu-Item-Change-Reflection
fixed: Update CPMenu immediately when a CPMenuItem changes
2025-12-26 11:36:42 +01:00
daboe01 42ea1a5972 new: manual test 2025-12-26 11:34:40 +01:00
daboe01andGitHub 1da107c404 Merge pull request #3160 from daboe01/new-CPStackView
new: CPStackView
2025-12-26 11:21:14 +01:00
daboe01 b2ada8a76f formatting 2025-12-26 11:16:46 +01:00
daboe01 5ac90c163a new: manual test 2025-12-26 11:10:44 +01:00
daboe01andGitHub 53e7d14e1f Merge pull request #3161 from daboe01/fixed-CPRuleEditor-did-not-resize-the-rhs-view-after-the-ruleeditor-has-been-shrunken
Fixed: CPRuleEditor rows did not resize after being shrunken
2025-12-26 11:01:50 +01:00
daboe01andGitHub 186e2e6130 Merge pull request #3150 from daboe01/fixed-when-performing-key-equivalent-for-a-MenuItem,-the-top-most-menu-item-should-highlight
new: Highlight top-level menu item when performing key equivalent
2025-12-26 10:53:18 +01:00
daboe01andGitHub fefa603b45 Merge pull request #3158 from daboe01/fixed-CPDocument-sends-windowWillClose-message-twice
fixed: CPDocument sends windowWillClose: message twice
2025-12-26 10:44:27 +01:00
daboe01 99cf8e6387 improved logic 2025-12-26 10:42:41 +01:00
daboe01andGitHub 53b4fe4fdc Merge pull request #3162 from daboe01/fixed-Toolbar-items-show-now-visual-feedback-when-clicked
Fixed: Toolbar items show now visual feedback when clicked
2025-12-26 10:21:34 +01:00
daboe01andGitHub 6773df15b9 Merge pull request #3163 from daboe01/new-slideback-animation-after-failed-drag
new: slideback animation after failed drag
2025-12-26 10:12:32 +01:00
daboe01 bc64d147a8 fixed: used wrong approach 2025-12-25 19:56:15 +01:00
daboe01 569f93549e new: slideback animation after failed drag
new: slideback animation after failed drag
2025-12-25 17:23:54 +01:00
daboe01andGitHub 2045d1eaeb Add cappuccino bin directory to .gitignore 2025-12-09 17:56:47 +01:00
daboe01 a2595093f8 Fixed: Toolbar items show now visual feedback when clicked 2025-12-07 21:23:07 +01:00
daboe01 9a9c9b8481 Fixed: CPRuleEditor rows did not resize after being shrunken 2025-12-07 19:46:11 +01:00
daboe01 99ec1af301 new: CPStackView 2025-12-07 18:14:52 +01:00
daboe01 f3fe384df1 fixed: CPDocument sends windowWillClose: message twice 2025-12-07 11:28:53 +01:00
daboe01 c7f3dd5b0f Fixed: [CPView addSubview:] was ignoring bounds origin for DOM positioning 2025-12-07 11:21:11 +01:00
daboe01 547be633b6 fixed: CPOutlineView should be its own tableView delegate and datasource 2025-12-07 09:18:36 +01:00
daboe01 a88002861b Fixed: CPMenu items appearing on the wrong side of the menu bar 2025-12-06 19:48:18 +01:00
daboe01 395022d652 new: Hide main menu items that do not have submenus 2025-12-06 19:39:53 +01:00
daboe01 dd24de5daf new: Left-align menu bar icon and title 2025-12-06 19:32:52 +01:00
daboe01 3c02cf5358 new: Highlight top-level menu item when performing key equivalent 2025-12-06 19:15:59 +01:00
daboe01 bf0f0ab509 fixed: Update CPMenu immediately when a CPMenuItem changes 2025-12-06 19:09:55 +01:00
daboe01 3d0639b6dc fixed: use requestAnimationFrame instead of setTimeout 2025-11-21 08:18:41 +01:00
daboe01 ed50f3b654 add test 2025-11-20 22:07:44 +01:00
daboe01 25d186bf5c New: CAAnimationGroup and implement timer-based animations in CALayer 2025-11-20 19:58:24 +01:00
daboe01andGitHub e02107102c Merge pull request #3142 from daboe01/new-CPSplitViewController
new: CPSplitViewController and CPSplitViewItem
2025-11-19 19:19:50 +01:00
daboe01andGitHub 1376466939 Merge pull request #3136 from daboe01/fixed-CAKeyframeAnimation-missing-properties-and-methods
fixed: CAKeyframeAnimation missing properties and methods
2025-11-19 19:18:05 +01:00
daboe01andGitHub f2c05c4841 Merge pull request #3141 from daboe01/fixed-scrollview-missing-notifications
fixed: CPScrollView did not send some notifications
2025-11-19 19:16:29 +01:00
daboe01andGitHub 17136d23f9 NEW: for..of iterator for CPDictionary (#3113) 2025-09-25 07:27:17 +02:00
daboe01 812f69f4a7 fixed: missing splitView:effectiveRect:forDrawnRect:ofDividerAtIndex: 2025-09-24 14:20:37 +02:00
Martin CarlbergandGitHub 7f286129de Create main-branch-publish-manual-tests.yaml 2025-09-17 16:21:12 +02:00
Martin CarlbergandGitHub b92459ba38 Merge pull request #3121 from daboe01/new-CPView-is-missing-rotateByAngle-method
Fixed: CPView was missing rotateByAngle: method
2025-09-12 12:56:13 +02:00
daboe01 51e9737114 improved: documentation 2025-09-11 18:26:53 +02:00
Martin CarlbergandGitHub 66051b066f Updated to use latest version of the runtime 2025-09-09 13:00:57 +02:00
daboe01 a3cb43bad8 fixed: missing api issues 2025-09-06 08:58:59 +02:00
Martin CarlbergandGitHub ba4f0f7e4d Merge pull request #3140 from daboe01/new-CPMapTable
New: CPMapTable.j
2025-09-05 11:42:10 +02:00
Martin CarlbergandGitHub ada5887deb Node version 25 and 26 are not yet available on GitHub 2025-09-05 11:39:01 +02:00
Martin CarlbergandGitHub af148b5008 Tests run with newer Node versions. Older is removed 2025-09-05 11:33:35 +02:00
daboe01 205b645de8 fixed: import statement for CPViewController 2025-09-04 12:14:06 +03:00
daboe01 f1f0703619 fixed: imports 2025-09-04 12:11:03 +03:00
daboe01 dc23fe49af new: unit tests 2025-09-04 11:54:06 +03:00
daboe01 44de0334b7 improved: documentation 2025-09-04 11:49:59 +03:00
daboe01 de96e0c22c fixed: missing import 2025-09-03 15:57:42 +03:00
daboe01 2f5593390e new: CPSplitViewController and CPSplitViewItem 2025-09-03 15:54:40 +03:00
daboe01 0ef1239283 fixed: CPScrollView did not send some notifications 2025-09-03 15:21:44 +03:00
daboe01 258dd7ba38 fixed: several css issues 2025-09-03 13:30:47 +03:00
daboe01 3e38560620 fixed: testcase 2025-09-03 11:09:22 +03:00
daboe01 1826a14afe improved: testcase 2025-09-03 10:39:11 +03:00
daboe01 e46fc1dc77 fixed: blue box was invisible 2025-09-03 08:24:19 +03:00
daboe01 debb43c1ca fixed: timing functions 2025-09-02 18:25:35 +03:00
daboe01 da8bf0abe1 fixed: property issues 2025-09-02 18:12:04 +03:00
daboe01 92b7583f9f fixed: SVGString 2025-09-02 17:38:10 +03:00
daboe01 8950c4f0e3 fixed: variable scope issues 2025-09-02 17:25:32 +03:00
daboe01 9442f6b7d7 new: manual tests 2025-09-02 17:04:51 +03:00
daboe01 c14e08633d fixed: testcase 2025-09-02 15:51:40 +03:00
daboe01 dfbf07b10c fixed: test suite 2025-09-02 15:44:39 +03:00
daboe01 b2b29c928f fixed: testcases 2025-09-02 15:41:34 +03:00
daboe01 c56448a29c fixed: keyEnumerator 2025-09-02 15:38:00 +03:00
daboe01 42a00edbd8 fixed: objectEnumerator 2025-09-02 15:35:53 +03:00
daboe01 18bb6daf10 fixed: nil and operator issues 2025-09-02 15:34:02 +03:00
daboe01 799cb189c9 new: test cases 2025-09-02 15:28:14 +03:00
daboe01 1112e658d4 removed: dealloc 2025-09-02 15:22:31 +03:00
daboe01 23cdee2f22 new: CPMapTable.j 2025-08-29 21:39:04 +03:00
daboe01 13688507dc new: restore old CPEvent factory method for backwards compatibility 2025-08-07 10:01:03 +02:00
daboe01 568d0faef6 fixed: missing parameter to CPEvent initializer 2025-08-06 17:19:14 +02:00
daboe01 41035a5b10 new: Implement missing CAKeyframeAnimation properties 2025-08-05 19:27:45 +02:00
daboe01 4cb0a87c68 fixed: syntax error 2025-08-03 22:26:09 +02:00
daboe01 cab5683cd9 fixed: CAKeyframeAnimation missing properties and methods 2025-08-03 22:22:42 +02:00
daboe01andGitHub 4d20007a9b Merge pull request #3133 from daboe01/collectionview-erroneous-offset-before-the-first-row
fixed: CPCollectionview displayed an erroneous offset before the first row of content
2025-08-02 16:55:25 +02:00
daboe01 56eb096903 fixed: accidental delete 2025-07-19 14:44:38 +02:00
daboe01 c077863e7a fixed: accidental changes 2025-07-19 14:29:46 +02:00
daboe01 49e5cb64ad Revert "removed: unwanted files"
This reverts commit 7e99e3ca20.
2025-07-19 14:23:52 +02:00
daboe01 7e99e3ca20 removed: unwanted files 2025-07-19 14:21:10 +02:00
daboe01 0b4d156b5b fixed: keyboard issues on ipad 2025-07-19 14:19:46 +02:00
daboe01 76ba6a4e64 fixed: ipad issue with backspace 2025-07-19 13:53:28 +02:00
daboe01 46bab97b49 fixed: backspace issue on ipad 2025-07-19 13:51:58 +02:00
daboe01 2a32934f86 fixed: ipad backspace issue 2025-07-19 09:40:14 +02:00
daboe01 bda5594f0c fixed: ipad softkeyboard trigger 2025-07-18 22:05:12 +02:00
daboe01 134688ef5c improved: double click emulation by double tapping 2025-07-18 21:48:09 +02:00
daboe01 e3fd7a295c fixed: ipad keyboard does not show 2025-07-17 19:41:48 +02:00
daboe01andGitHub c2d07b7b06 Merge pull request #3120 from daboe01/nil-in-cpviewcontroller-fix
fixed: subview's window setting did not happen BEFORE _setSuperview
2025-07-17 19:27:36 +02:00
daboe01 d9f31e8543 Revert "new: UIBuilder demo application"
This reverts commit 38f523b3c2.
2025-07-15 18:55:23 +02:00
daboe01 38f523b3c2 new: UIBuilder demo application 2025-07-15 18:54:09 +02:00
daboe01andGitHub 69a353d969 Update README.markdown 2025-07-15 07:21:35 +02:00
daboe01andGitHub 945e70c0ff Update README.markdown 2025-07-14 19:35:15 +02:00
daboe01andGitHub 277e9bee7b new: link to example app 2025-07-14 19:34:01 +02:00
daboe01andGitHub 95614d567c Update README.markdown 2025-07-14 19:14:55 +02:00
daboe01andGitHub 6a488af1ca Update README.markdown demo app 2025-07-14 19:14:17 +02:00
daboe01 a0f9cb5183 formatting 2025-07-13 19:55:34 +02:00
daboe01 1a74b4c692 fixed: CPCollectionview displayed an erroneous offset before the first row of content 2025-07-13 19:53:33 +02:00
daboe01andGitHub 746311c6cf Merge pull request #3124 from daboe01/fixed-touch-scrolling-had-no-momentum-support
fixed: touch scrolling did not support momentum
2025-07-09 22:23:42 +02:00
daboe01andGitHub 876aaa483e Merge pull request #3132 from daboe01/fixed-submenu-placement-issue
fixed: CPMenu Submenu Placement Issue
2025-07-09 22:22:28 +02:00
daboe01 570c31f248 fixed: richtext copypaste 2025-07-06 14:04:35 +02:00
daboe01 b3695ed710 formatting 2025-07-06 12:48:31 +02:00
daboe01 e9dddaad20 new: manual test 2025-07-06 10:58:19 +02:00
daboe01 2b254a6f16 fixed: CPMenu Submenu Placement Issue 2025-07-05 19:01:57 +02:00
daboe01andGitHub e9022d045c Merge pull request #3123 from daboe01/new-viewcontroller-responder-chain-issue
fixed: CPViewController doesn't participate fully in responder chain
2025-07-05 16:41:23 +02:00
daboe01 c70ea739ec fixed: unittest was overambitious 2025-07-05 16:39:10 +02:00
daboe01 85da0164ba fixed: designated initialiser was not used 2025-07-05 16:32:22 +02:00
daboe01 89b262103f Revert "fixed: _view was assigned bypassing the accessors"
This reverts commit cfcf8bc567.
2025-07-05 16:28:16 +02:00
daboe01 2c5744be57 formatting 2025-07-05 16:25:00 +02:00
daboe01 cfcf8bc567 fixed: _view was assigned bypassing the accessors 2025-07-05 16:21:19 +02:00
daboe01 d1acff5b44 improved setNextResponder assignment 2025-07-05 16:13:50 +02:00
daboe01 2add37983d new: unittest for responderchain 2025-07-05 15:56:44 +02:00
daboe01 ce00fdaae8 fixed: copy-paste-issues 2025-07-05 15:37:32 +02:00
daboe01 2fe29d4a26 Update .gitignore 2025-07-05 13:36:14 +02:00
daboe01andGitHub fc582d8125 Merge pull request #3131 from daboe01/fixed-CPTimeZone-initWithName-does-not-return-the-timeZone-with-the-good-daylight-saving-
fixed: CPTimeZone initWithName: does not return the timeZone with the good daylight saving
2025-07-05 13:16:56 +02:00
daboe01andGitHub c41307dc37 Merge branch 'main' into fixed-CPTimeZone-initWithName-does-not-return-the-timeZone-with-the-good-daylight-saving- 2025-07-05 12:58:01 +02:00
daboe01 27280f6da1 Update CPMenuTest.j 2025-07-05 12:35:43 +02:00
daboe01 ce7fec170a Update CPEventTest.j 2025-07-05 12:33:21 +02:00
daboe01 98ef6fd902 Update CPEventTest.j 2025-07-05 12:30:58 +02:00
daboe01 1c4d9b1d8f Update CPButtonTest.j 2025-07-05 12:29:11 +02:00
daboe01 b8b988c10b Update CPResponderTest.j 2025-07-05 09:04:26 +02:00
daboe01 0eb87f85b4 fixed: accidentally removed files 2025-07-05 09:01:12 +02:00
daboe01 a0136e2144 removed accidentally committed stuff 2025-07-05 08:58:57 +02:00
daboe01 d12770c65d fixed: testcase for keyevent 2025-07-05 08:57:29 +02:00
daboe01 b0186bf4f2 fixed: smart copy/paste 2025-07-05 08:49:08 +02:00
daboe01 9e15700f93 cleanup stuff 2025-07-03 22:10:57 +02:00
daboe01 6b2494ede4 fixed: arrow navigation 2025-07-02 19:54:52 +02:00
daboe01 266965f506 improved: native input manager 2025-07-02 07:50:46 +02:00
daboe01 1226f5120c improved: deadkey management 2025-07-01 22:55:53 +02:00
daboe01 f4ac0630b8 formatting 2025-07-01 22:25:20 +02:00
daboe01 b3cb22caef more modernisations 2025-07-01 22:16:33 +02:00
daboe01 b1d96dfdee improved reliance of key 2025-07-01 21:53:53 +02:00
daboe01 25732f2f52 fixed: second half of the fix was missing 2025-07-01 21:42:58 +02:00
daboe01 5ecdaafadf fixed: arrow keys did not work 2025-07-01 20:23:29 +02:00
daboe01 223bbc86e1 fixed: arrow keys did not work 2025-07-01 19:42:12 +02:00
daboe01 0867e718d1 Revert "fixed: arrow keys did not work"
This reverts commit c814b5daed.
2025-07-01 19:40:47 +02:00
daboe01 c814b5daed fixed: arrow keys did not work 2025-07-01 19:36:02 +02:00
daboe01andGitHub 0e8ee2f7d5 Merge pull request #3130 from daboe01/fixed-NSDateFormatter-unable-to-parse-dates-in-Javascript-Date.toISOString-format
fixed: NSDateFormatter was unable to parse dates in Javascript Date.toISOString format
2025-07-01 16:21:30 +02:00
daboe01andGitHub 4b22d67f42 Merge pull request #3117 from daboe01/null-marker-versus-nil-issue
fixed: CPObjectController returns 'null marker' instead of nil
2025-07-01 16:19:58 +02:00
daboe01andGitHub edf0a61653 Merge pull request #3119 from daboe01/CPTimeZone-fix
fixed: time zone methods return nil in Chrome and Firefox
2025-07-01 16:19:14 +02:00
daboe01andGitHub e50ede815c Merge pull request #3128 from daboe01/fixed-missing-documentation-of-CPOutlineView
fixed: missing documentation of CPOutlineViewDataSource protocol
2025-07-01 16:15:58 +02:00
daboe01andGitHub 6d2888e1c6 Merge pull request #3129 from daboe01/textfieldsizefix
fixed: nonsense addition accidentally turning the size into a string
2025-07-01 16:14:17 +02:00
daboe01 b6461ba4fb fixed: tests 2025-06-29 20:28:43 +02:00
daboe01 25708554fd fixed: typo 2025-06-29 20:22:38 +02:00
daboe01 74f204e6fa fixed: tests and implementation 2025-06-29 20:20:58 +02:00
daboe01 263ec36d80 fixed: test 2025-06-29 19:42:12 +02:00
daboe01 bb6b738fc7 fixed: CPTimeZone initWithName: does not return the timeZone with the good daylight saving 2025-06-29 19:34:56 +02:00
daboe01 fb1c2732ac fixed test 2025-06-29 18:00:19 +02:00
daboe01 1fb42fc119 fixed: NSDateFormatter was unable to parse dates in Javascript Date.toISOString format 2025-06-29 17:51:08 +02:00
daboe01 23f9170b90 fixed: nonsense addition accidentally turning the size into a string 2025-06-29 17:18:42 +02:00
daboe01 7be2005fae fixed: wrong documentation 2025-06-29 15:50:05 +02:00
daboe01 afadec517b fixed: missing documentation of CPOutlineViewDataSource protocol 2025-06-29 15:20:23 +02:00
daboe01 da5a12c938 formatting 2025-06-26 17:54:05 +02:00
daboe01 a9aa15d63f fixed CALayer class name 2025-06-26 17:52:11 +02:00
daboe01 e6d2d840e9 fixed: CALayer autocreate 2025-06-26 17:51:26 +02:00
daboe01 4963127c31 formatting 2025-06-26 17:49:36 +02:00
daboe01 62c7ac891f fixed: manual test 2025-06-26 17:43:51 +02:00
daboe01 e4b594f716 fixed: manual test 2025-06-26 17:42:03 +02:00
daboe01 0835f18721 new: manual test 2025-06-26 10:01:37 +02:00
daboe01 8231cbc55c fixed: wrong core animation symbol names 2025-06-26 08:01:45 +02:00
daboe01andGitHub b63cde48c6 Merge pull request #3111 from daboe01/CPImage_Drawing
new: CPImage method drawAtPoint:fromRect: operation: fraction:
2025-06-25 19:26:00 +02:00
daboe01andGitHub 30150178db Merge pull request #3116 from daboe01/doxygen-fix
fixed: make docs was not working in node version
2025-06-25 19:25:20 +02:00
daboe01andGitHub 61eebfcf46 Merge pull request #3118 from daboe01/fixed-capp-gen--l-issue
fixed: node fs issues in Generate.j
2025-06-25 19:24:22 +02:00
daboe01andGitHub 40154ed86e Update README.markdown 2025-06-25 10:58:27 +02:00
daboe01andGitHub 23605ff2b0 Merge pull request #3115 from daboe01/better-readme
Better readme
2025-06-25 10:56:42 +02:00
daboe01 964eb00ce1 formatting 2025-06-25 10:54:50 +02:00
daboe01 930c1e874c reordering faq 2025-06-25 10:54:08 +02:00
daboe01 1d96b09129 switch to passive voice 2025-06-25 10:53:41 +02:00
daboe01 9c6ce6d200 fixed: missing _momentumScrollStep 2025-06-23 09:40:27 +02:00
daboe01 a1aad41a6b fixed: missing ivars 2025-06-23 09:30:52 +02:00
daboe01 38cb3cb1a2 fixed: touch scrolling did not support momentum 2025-06-23 09:19:02 +02:00
daboe01 751e1d27a6 Update CPPlatformWindow+DOMKeys.j 2025-06-23 09:02:28 +02:00
daboe01 36d4bd32b5 Update CPPlatformWindow+DOM.j 2025-06-23 08:58:12 +02:00
daboe01 d835cf0c00 fixed: CPViewController doesn't participate fully in responder chain 2025-06-23 07:32:49 +02:00
daboe01 6ce85bae7e fixed: Keyboard-event handling is based on the deprecated charCode 2025-06-23 07:21:08 +02:00
daboe01 7d1df21b0e removed unused variable 2025-06-22 22:57:59 +02:00
daboe01 bbb87b4970 removed: wrong code 2025-06-22 22:56:35 +02:00
daboe01 14d9bb795d fixed: wrong code 2025-06-22 22:55:41 +02:00
daboe01 2eb1d1bfb9 fixed: CPView was missing rotateByAngle: method 2025-06-22 22:51:26 +02:00
daboe01 bae78b5055 formatting 2025-06-22 21:52:57 +02:00
daboe01 c0705448cd new: testcase 2025-06-22 21:47:26 +02:00
daboe01 e0a8fa3fde formatting 2025-06-22 21:34:19 +02:00
daboe01 64f4b0fc13 fixed: accidental removal of commented out code 2025-06-22 21:32:48 +02:00
daboe01 c28a05e968 fixed logic 2025-06-22 21:28:37 +02:00
daboe01 170d6ac2e7 fixed wrong logic 2025-06-22 21:19:03 +02:00
daboe01 64cc36a1ac new logic 2025-06-22 21:06:47 +02:00
daboe01 af61bcb93f fixed: wrong logic 2025-06-22 20:45:55 +02:00
daboe01 1f24e89c1b fixed wrong logic 2025-06-22 20:35:11 +02:00
daboe01 b12aee53d3 fixed: subview's window setting mus happen BEFORE _setSuperview 2025-06-22 19:47:19 +02:00
daboe01 feb8ea0316 new: test case for the new timezone abbreviation logic 2025-06-22 19:33:13 +02:00
daboe01 db51d5ca54 fixed: time zone methods return nil in Chrome and Firefox 2025-06-22 19:27:58 +02:00
daboe01 01e9f9181d fixed: node issues 2025-06-22 16:59:54 +02:00
daboe01 3a9db70426 fixed: CPObjectController returns 'null marker' instead of nil 2025-06-22 11:04:46 +02:00
daboe01 c7e850b95f fixed: accidentally removed markdown install instructions 2025-06-21 18:09:19 +02:00
daboe01 89b3fb003c formatting 2025-06-21 17:29:31 +02:00
daboe01 6abb8234fb fixed: docsetutil is nowadays missing from xcode 2025-06-21 17:26:46 +02:00
daboe01 4395663cc9 fixed: doc directories are spilled everywhere 2025-06-21 16:56:57 +02:00
daboe01 aeb982c616 formatting 2025-06-21 16:32:11 +02:00
daboe01 3fd26e681b fixed: make docs was not working in node version 2025-06-21 16:29:31 +02:00
daboe01 8bcf446187 fixed: cappuccino-cookbook was missing 2025-06-21 14:50:02 +02:00
daboe01 42d51dc808 Merge branch 'better-readme' of https://github.com/daboe01/cappuccino into better-readme 2025-06-21 13:16:12 +02:00
daboe01 b05aa3dd32 improved readme 2025-06-21 11:24:03 +02:00
daboe01 98a539657c fixed: type CGBlendMode missing 2025-06-19 09:15:08 +02:00
daboe01andGitHub 85f7125f22 New: for..of iteration for CPSet (#3114) 2025-06-18 10:22:41 +02:00
daboe01 2f8dd8c088 new: drawAtPoint:(CPPoint)point fromRect:(CPRect)fromRect operation:(CPCompositingOperation)op fraction:(CGFloat)delta 2025-06-11 14:14:30 +02:00
Martin CarlbergandGitHub 86d595b086 Fixed: Make the isa property on classes not enumerable. (#3100)
As we have a tool-free bridges between many classes and JavaScript types by setting the isa property on the type, for example CPArray and Array. Using the for...in enumeration the isa property will be included in the loop if the property is enumerable (this is the default behavior). This commit set the property as not enumerable.

Also added test case for check that the isa property on classes are not enumerable using a for...in loop.
2025-05-14 10:09:01 +02:00
daboe01andGitHub 2f563fb870 New: give nil values the chance to be transformed to true via CPNegateBoolean (#3103) 2025-04-28 15:40:57 +02:00
daboe01andGitHub c78a18dc3e Update README.markdown 2025-04-02 17:03:14 +02:00
daboe01andGitHub 0fa636dd79 Update README.markdown 2025-04-02 17:01:01 +02:00
Martin CarlbergandGitHub 7fc9fa70c1 Fixed: Github Action build with Node 22 and 23 too 2025-03-27 15:36:14 +01:00
daboe01andGitHub 9109cf3b54 Fixed: changing background color in CPTextView did not redraw properly (#3107) 2025-03-26 13:31:05 +01:00
David RichardsonandGitHub 50076efe1e Merge pull request #3106 from enquora/dark-mode-cleanup
Set default project details view (no project selected) to default colours
2025-01-27 09:45:48 -07:00
David Richardson 097785fb93 Set default project details view, when no project selected, to default colours
Remove hard-coded colours to allow dark mode support.
2025-01-27 09:41:58 -07:00
daboe01andGitHub 7e07deeb23 Update README for ECMA Script 2024 2025-01-14 15:46:42 +01:00
daboe01andGitHub e8c0c680ca Update README.markdown 2024-12-30 15:39:53 +01:00
daboe01andGitHub d9ee7b23c2 Update README.markdown 2024-12-30 15:36:34 +01:00
daboe01andGitHub 03339e3c5d Merge pull request #3102 from cappuccino/narwhal
merge of PR #1372 and #2018 into main
2024-12-30 14:05:09 +01:00
daboe01andGitHub c58c5554f9 Merge pull request #2018 from vidinoti/patch-1
Changing a CALayer in a menu callback doesn't trigger re-draw
2024-12-30 13:57:56 +01:00
daboe01andGitHub 16ae7b7e74 Merge pull request #1372 from kjamieson/scroll-fixes
If target view of a drag and drop is a CPTableView, set inset bounds based on the row height of the table
2024-12-30 13:51:00 +01:00
David RichardsonandGitHub 6d3fec88ec Merge pull request #3101 from enquora/dark-mode-cleanup
Dark mode cleanup
2024-11-13 10:48:25 -07:00
David Richardson 4b74e7da31 Increment patch component of version number to reflect dark mode support changes 2024-11-13 10:41:13 -07:00
David Richardson fa70ffc918 Clean up control colours for background and text to fully support dark mode 2024-11-13 10:34:04 -07:00
David RichardsonandGitHub fd177f4b9a Merge pull request #3072 from enquora/remove-narwhal-artifacts
Remove narwhal-specific artifacts
2024-10-17 10:01:08 -06:00
daboe01andGitHub 08c666a53f Merge pull request #3097 from cappuccino/remove-unused-local-variable-charCode
removed: unused local variable charCode
2024-10-07 19:44:13 +02:00
daboe01 639a0fe1c2 fixed: symbol size of combobox indicator in small and mini styles 2024-09-28 14:01:15 +02:00
daboe01andGitHub 668567d6e2 Merge pull request #3094 from enquora/editorconfig
Add editorconfig file
2024-09-27 13:36:07 +02:00
David Richardson 09f92407ec Cleanup
Make settings universal, across all file types.
2024-09-26 11:08:33 -06:00
daboe01andGitHub 9dc12c0142 Merge pull request #3091 from daboe01/cptextviewcopypastedocu
added documentation to the copy paste limitation
2024-09-22 13:03:52 +02:00
David RichardsonandGitHub 2fd821ca00 Merge pull request #3096 from enquora/fix-menubar-icon-visibility
Change rendering mode for menubar inactive icon from ‘Render As’ to ‘Template Image’
2024-09-21 12:58:57 -06:00
daboe01andGitHub 03195b66bc removed: unused local variable charCode 2024-09-21 19:12:18 +02:00
David Richardson a3d906165a Change menubar inactive icon ‘Render As’ mode to ‘Template Image’
Allows macOS to automatically manage blending mode and color of menubar icon.
Modern macOS renders the menubar with translucency, making icons with some fixed colors difficult to see with some wallpapers.
This was particularly pronounced with ‘status-icon-inactive’.
Changing render mode to ‘Template Image’ allows the OS to adjust icon color and blending mode dynamically — to suit current combination of Light/Dark mode and wallpaper color.
2024-09-15 16:03:23 -06:00
David Richardson 4c1ce50dd2 Add editorconfig file
Enforce/guide compliance with Cappuccino project coding standards.
Most modern editors supported directly or via plugins.
2024-08-29 19:20:16 -06:00
daboe01 b638242e6f added documentation to the copy paste limitation 2024-08-04 15:15:34 +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
David Richardson 966d9fe0d2 Remove bootstrap.sh and narwhal.local.conf
Used by/useful for Narwhal javascript engine - no longer needed.
2024-06-03 15:08:59 -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
daboe01andGitHub d3cc6c29c9 Merge branch 'main' into Aristo3 2024-05-24 16:07:32 +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
daboe01 89c00e193b fixed compatibility of theme jakefiles with node 2024-05-22 17:32:03 +02:00
daboe01 b38a5d19b1 update from node 2024-05-22 17:27:28 +02:00
daboe01 f7ae515d84 Update package-lock.json 2024-05-22 17:20:56 +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
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
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
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
daboe01 37fedd41e3 improved: sizing of combobox list 2022-09-30 19:22:16 +02:00
daboe01 3711461201 fixed: overlap between text and button in combobox 2022-09-30 11:15:26 +02:00
daboe01 4ddd8ac263 new: mini size combo box 2022-09-28 21:27:29 +02:00
daboe01 aa7a96cfb8 new: small size combo box 2022-09-28 19:04:08 +02:00
daboe01 2c2768a34f improved: A3 CSS for ComboBox 2022-09-19 19:36:35 +02:00
daboe01 91c2fd2085 new: focus ring for combobox 2022-06-10 09:09:36 +02:00
daboe01 9ab63ee854 fixed: handling combo-box over heights 2022-06-10 08:52:06 +02:00
daboe01 ef1dd16766 fixed: combobox text positioning issues 2022-06-09 22:57:20 +02:00
daboe01 39f4b573da proposed: CPComboBox style 2022-06-09 18:12:53 +02:00
Didier Korthoudt 2a0306b14d Update ThemeDescriptors.j
This commit fixes size and position of standard buttons in all sizes and in default button state.
2022-04-14 12:21:23 +02:00
Didier Korthoudt e0735a1d82 large-control-size
This commit adds compatibility with the new control size "Large" introduced in MacOS 11.
2022-04-14 12:00:13 +02:00
Didier Korthoudt 6f44fe9f93 Buttons-fix
This commit fixes buttons position and height. Compatibility with Xcode IB is also better.
2022-04-13 17:39:05 +02:00
daboe01 f390e5ed17 fixed: first item was hidden in buttonbar action menus 2022-04-13 08:24:27 +02:00
Didier Korthoudt ede3eb64ae Update CPTabView.j
Just a typo
2022-04-07 11:43:46 +02:00
Didier Korthoudt d0d4972681 Update CPSearchField.j
This commit adds "Search" as placeholder by default, as Cocoa does.
2022-04-07 11:40:32 +02:00
Didier Korthoudt 4be4a65c72 Update CPAlert.j
This fixes incorrect button bezel style in alerts, resulting in white text on white background, instead of default blue background.
2022-04-05 15:55:57 +02:00
Didier Korthoudt 3e009bbf26 Update CPSearchField.j
This fixes problems with search and cancel buttons, restoring correct behavior and animations.
2022-04-05 10:37:42 +02:00
daboe01 3db3f03e5d fixed: plus/minus buttons in buttonbar had been to small
->replaced with svg versions + added highlighted state
2022-04-03 19:51:44 +02:00
daboe01 698f58eaa0 fixed: action icon in button bar 2022-04-03 17:31:57 +02:00
daboe01 95cf933ab2 code cleanup 2022-04-03 14:07:18 +02:00
daboe01 692f365fed fixed: _CPButtonBarPopUpButton did not show icon 2022-04-02 23:41:37 +02:00
daboe01 96aa310839 replaced svg icons with the packed.png sprites 2022-04-02 17:52:28 +02:00
daboe01 8d3da8e4fa update xib in manual test 2022-03-27 16:11:35 +02:00
daboe01 885145d43d updated xib in manual tests 2022-03-27 15:17:18 +02:00
daboe01 c893e4a809 fixed: stepper icons 2022-03-27 14:29:26 +02:00
daboe01 1536c55b99 improved: gears icon 2022-03-27 13:13:02 +02:00
daboe01 f3f94eec66 fixed: use of outdated code in CPAnimationContext 2022-03-26 22:38:10 +01:00
daboe01 51b633f0d7 fixed: mixup of variable definitions 2022-03-26 19:48:29 +01:00
daboe01 5f1007dcbc fixed: mini and small segmentedControls + references to obsolete images 2022-03-26 19:18:09 +01:00
daboe01 ae7ce81b8c fixed: font support 2022-03-25 22:49:35 +01:00
daboe01 8b72e5db31 improved: gear icon 2022-03-25 22:43:13 +01:00
daboe01 0d35ceda31 replaced material icons with svg 2022-03-25 22:29:09 +01:00
daboe01 ed59003a95 fixed: 404 errors from missing obsolete background images 2022-03-25 21:42:56 +01:00
daboe01 94501c4618 POC for inline svg standard icons instead of material icons 2022-03-25 19:48:43 +01:00
daboe01 bf3847ce59 fixed: wrong method name 2022-03-24 19:17:42 +01:00
daboe01 c6ea894f10 fixed: old style Jakefile 2022-03-23 19:54:35 +01:00
daboe01 8df4e1ee6e fixed: aristo3 jakefile 2022-03-23 19:30:29 +01:00
daboe01 b608709014 resolved: missed conflict 2022-03-23 17:52:36 +01:00
daboe01 0450136af3 Merge branch 'pl_2719' into Aristo3 2022-03-23 06:59:28 +01:00
daboe01 29e4695503 Merge branch 'pl_2835' into Aristo3 2022-03-23 06:58:23 +01:00
daboe01 a7a080f416 Merge branch 'pl_2918' into Aristo3 2022-03-23 06:57:54 +01:00
daboe01 effb274410 Merge branch 'pl_2932' into Aristo3 2022-03-23 06:56:39 +01:00
daboe01 45f6f4bc90 Merge branch 'pl_2933' into Aristo3 2022-03-23 06:56:17 +01:00
daboe01 82b91b2f64 Merge branch 'pl_2937' into Aristo3 2022-03-23 06:54:49 +01:00
daboe01 59a9d8c093 Merge branch 'pl_2938' into Aristo3 2022-03-23 06:54:12 +01:00
daboe01 ae42acaef3 Merge branch 'pl_2939' into Aristo3 2022-03-23 06:53:00 +01:00
daboe01 531be2db49 fixed: missed conflict 2022-03-22 23:07:12 +01:00
daboe01 8089973b7b fixed: missed conflict 2022-03-22 23:06:24 +01:00
daboe01 1e8cc3101b Merge branch 'Aristo3' of https://github.com/daboe01/cappuccino into Aristo3 2022-03-22 23:00:49 +01:00
daboe01 a27a5fbc32 Merge branch 'pl_2507' into aristo3 2022-03-22 19:54:43 +01:00
Didier Korthoudt fccd76ec4c v02
This commit :
- removes global variables direct access
- better handles Aristo2 retro-compatibility
2020-10-25 11:44:03 +01:00
Didier KorthoudtandGitHub 39738b9ded Merge branch 'master' into 010-CPApplicationOSBehavior 2020-10-10 18:27:45 +02:00
Didier Korthoudt 3a18574838 Buttons fixes 2020-09-02 17:28:55 +02:00
Didier Korthoudt ec238f2cc3 000-Aristo3 2020-08-30 09:54:30 +02:00
Didier Korthoudt 8c08803fd0 030-CPTableView 2020-08-30 09:47:21 +02:00
Didier Korthoudt cc084692ae Update CPButtonBar.j 2020-08-29 23:42:21 +02:00
Didier Korthoudt 96a25dfcaa Update CPSegmentedControl.j 2020-08-29 22:14:02 +02:00
Didier Korthoudt 500237b2c8 Update CPScrollView.j
- new theme parameters
- Aristo3 CSS theme ready
2020-08-29 22:03:48 +02:00
Didier Korthoudt 67a0098c7a CPApplicationOSBehavior 2020-08-28 17:21:41 +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 Korthoudt 4fc80f4bbd A3-CPApplication 2019-04-16 10:25:52 +02:00
Didier KorthoudtandGitHub eca41e7474 Merge branch 'master' into CPScrollView-scrollerStyle-fix 2018-08-23 11:08:51 +02:00
Didier Korthoudt 9107322d28 setGlobalScrollerStyle-fixes 2018-06-16 17:51:52 +02:00
daboe01 88c1846e1a fixed: interpunctation 2018-06-12 13:23:56 +02:00
daboe01 be3d559d8a fixed: optical clutter, link duplications 2018-06-12 13:20:00 +02:00
Didier Korthoudt f5ca6c39f6 Revert "Revert "Revert "CPScrollView-scrollerStyle-fix"""
This reverts commit 653fdb999e.
2018-06-02 22:08:59 +02:00
Didier Korthoudt 653fdb999e Revert "Revert "CPScrollView-scrollerStyle-fix""
This reverts commit f25d76d0d6.
2018-05-29 12:19:03 +02:00
Didier Korthoudt f25d76d0d6 Revert "CPScrollView-scrollerStyle-fix"
This reverts commit 31fe918b3d.
2018-05-29 12:16:36 +02:00
Didier Korthoudt 41b0f80360 CPScrollViewTest-fix 2018-05-25 08:57:11 +02:00
Didier Korthoudt 31fe918b3d CPScrollView-scrollerStyle-fix 2018-05-23 23:58:48 +02:00
Didier Korthoudt 32495792dc Revert "NEW: Added automatic Xcode syntax coloring"
This reverts commit 0a497d8c3f.
2018-05-23 23:50:53 +02:00
Didier Korthoudt 30bdbb4544 Subviews-CSS-animations-removal 2016-11-08 15:25:58 +01:00
Mathieu Monney c528421a8e Added unit test for pull request #2018 2014-12-19 13:42:40 +01:00
Mathieu Monney 0cfb53cea4 Changing a CALayer in a menu callback doesn't trigger re-draw / re-layout 2013-11-18 15:29:41 +01:00
Kevin Jamieson b349f0a2fb If target view of a drag is a CPTableView, set inset bounds based on the row height of the table 2011-10-03 22:13:41 -07:00
312 changed files with 49400 additions and 13040 deletions
+12
View File
@@ -0,0 +1,12 @@
# EditorConfig reference: https://editorconfig.org
# https://editorconfig.org/#file-format-details
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
+3 -3
View File
@@ -5,9 +5,9 @@ name: Node build
on:
push:
branches: [ node ]
branches: [ main ]
pull_request:
branches: [ node ]
branches: [ main ]
jobs:
build:
@@ -16,7 +16,7 @@ jobs:
strategy:
matrix:
node-version: [17.x, 18.x, 19.x, 20.x, 21.x]
node-version: [20.x, 21.x, 22.x, 23.x, 24.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
@@ -0,0 +1,178 @@
name: Main branch - Build Testbook with fresh frameworks and manual tests & deploy to GitHub Pages
on:
push:
branches: [ main ]
workflow_dispatch:
permissions:
contents: write
pages: write # required by deploy-pages
id-token: write # required by deploy-pages
pull-requests: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout cappuccino (this repo)
uses: actions/checkout@v5
- name: Checkout Cappuccino-Testbook into ./testbook
uses: actions/checkout@v5
with:
repository: ArgosOz/Cappuccino-Testbook
path: testbook
- name: Use Node.js
uses: actions/setup-node@v4
- run: echo "${PWD}/dist/cappuccino/bin" >> $GITHUB_PATH
- run: echo "${PWD}/dist/objective-j/bin" >> $GITHUB_PATH
- run: npm install
- run: npm update
- run: jake dist
- run: jake test-only
# Refresh Frameworks from dist → testbook/Frameworks
- name: Refresh Frameworks
run: |
set -euo pipefail
if [ ! -d dist ]; then
echo "❌ dist/ not found at repo root" >&2
exit 1
fi
mkdir -p testbook/Frameworks
rm -rf testbook/Frameworks/*
cp -a dist/cappuccino/Frameworks/{Foundation,AppKit} testbook/Frameworks/
cp -a dist/objective-j/Frameworks/Objective-J testbook/Frameworks/
echo "Frameworks populated:" && ls -la testbook/Frameworks || true
- name: Generate manifest of manual test directories
if: ${{ hashFiles('Tests/Manual/**') != '' }}
run: |
set -euo pipefail
mkdir -p artifacts
OUT="testbook/Resources/recipes.txt"
: > "$OUT"
if [ ! -d "Tests/Manual" ]; then
echo "❌ Tests/Manual not found at repo root" >&2
exit 1
fi
# Iterate over immediate subdirectories of Tests/Manual (null-safe for spaces)
while IFS= read -r -d '' d; do
name=$(basename "$d")
ts=$(date +%s%3N) # milliseconds since epoch
printf '%s|%s|\n' "$ts" "$name" >> "$OUT"
# Optional tiny sleep to help keep timestamps distinct:
sleep 0.01
done < <(find "Tests/Manual" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
echo "Wrote $OUT:"
cat "$OUT"
# Replace subdirectories inside testbook/Resources with Tests/Manual subdirectories
- name: Sync Resources from Tests/Manual
run: |
set -euo pipefail
if [ ! -d "Tests/Manual" ]; then
echo "❌ Tests/Manual not found at repo root" >&2
exit 1
fi
mkdir -p testbook/Resources
# Remove only immediate subdirectories (keep stray files if any)
find testbook/Resources -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -r rm -rf
# Copy each immediate subdirectory from Tests/Manual → testbook/Resources
while IFS= read -r -d '' d; do
cp -a "$d" testbook/Resources/
done < <(find Tests/Manual -mindepth 1 -maxdepth 1 -type d -print0)
echo "Resources now contains:" && ls -la testbook/Resources || true
# Ensure each test Index.html includes the include path line before OBJJ_MAIN_FILE
- name: Inject OBJJ_INCLUDE_PATHS into Index.html files
run: |
set -euo pipefail
found=0
while IFS= read -r -d '' f; do
found=1
if grep -q 'OBJJ_INCLUDE_PATHS' "$f"; then
echo "Already updated: $f"
continue
fi
perl -0777 -i -pe 's/OBJJ_MAIN_FILE\s*=\s*"main\.j";/OBJJ_INCLUDE_PATHS = ["..\/..\/Frameworks"];\nOBJJ_MAIN_FILE = "main.j";/i' "$f"
perl -0777 -i -pe 's/Frameworks\/Objective-J\/Objective-J.js/\.\.\/\.\.\/Frameworks\/Objective-J\/Objective-J.js/' "$f"
echo "Updated: $f"
done < <(find testbook/Resources -mindepth 2 -maxdepth 2 -type f -iname "index.html" -print0)
if [ "$found" -eq 0 ]; then
echo "⚠️ No index.html files found under testbook/Resources/*/" >&2
fi
# Optional: prune VCS/CI metadata from the served content
- name: Clean testbook for Pages (optional)
run: |
set -euo pipefail
test -f testbook/index.html
test -d testbook/Resources
test -d testbook/Frameworks
rm -rf testbook/.git testbook/.github || true
find testbook -maxdepth 1 -type f -name ".git*" -delete || true
- name: Publish to gh-pages (preserve PR previews)
uses: JamesIves/github-pages-deploy-action@v4
with:
branch: gh-pages
folder: testbook
clean-exclude: pr-preview/ # keep PR preview folders
force: false # avoid force-push so previews survive
- name: Checkout gh-pages ./gh-pages
uses: actions/checkout@v5
with:
path: gh-pages
ref: gh-pages
- name: Clean gh-pages for Pages
run: |
set -euo pipefail
test -d gh-pages
rm -rf gh-pages/.git gh-pages/.github || true
find gh-pages -maxdepth 1 -type f -name ".git*" -delete || true
find gh-pages -type f -name "*.xib" -delete || true
find gh-pages -type f -name "*xcode*" -delete || true
find gh-pages -name "Jakefile" -delete || true
- name: Upload Pages artifact (testbook/)
uses: actions/upload-pages-artifact@v4
with:
path: gh-pages
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
concurrency:
group: pages
cancel-in-progress: true
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- name: Comment Pages URL on PR
if: ${{ github.event_name == 'pull_request' }}
uses: actions/github-script@v7
with:
script: |
const url = `${{ toJSON(steps.deployment.outputs.page_url) }}`;
const body = `📄 GitHub Pages preview for this PR:\n\n${url}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
+1 -1
View File
@@ -25,4 +25,4 @@ node_modules
/dist/objective-j/lib
/dist/cappuccino/package.json
/dist/cappuccino/lib
/dist/cappuccino/bin
+2
View File
@@ -115,3 +115,5 @@
@import "CPWindow.j"
@import "CPWindowController.j"
@import "CPWorkspace.j"
@import "CPFontPanel.j"
@import "CPTreeController.j"
+90 -40
View File
@@ -172,7 +172,7 @@ var bottomHeight = 71;
BOOL _needsLayout;
}
#pragma mark Creating Alerts
// MARK: Creating Alerts
/*!
Returns a CPAlert object with the provided info
@@ -249,8 +249,8 @@ var bottomHeight = 71;
}
#pragma mark -
#pragma mark Delegate
// MARK: -
// MARK: Delegate
/*!
Set the delegate of the receiver
@@ -272,7 +272,7 @@ var bottomHeight = 71;
}
#pragma mark Accessors
// MARK: Accessors
- (CPTheme)theme
{
@@ -402,7 +402,7 @@ var bottomHeight = 71;
_needsLayout = YES;
}
#pragma mark Accessing Buttons
// MARK: Accessing Buttons
/*!
Adds a button with a given title to the receiver.
@@ -418,27 +418,32 @@ var bottomHeight = 71;
*/
- (void)addButtonWithTitle:(CPString)aTitle
{
var bounds = [[_window contentView] bounds],
count = [_buttons count],
var count = [_buttons count],
button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
[button setTitle:aTitle];
[button setTag:count];
[button setTarget:self];
[button setAction:@selector(_takeReturnCodeFrom:)];
[button setBezelStyle:CPSmallSquareBezelStyle];
[[_window contentView] addSubview:button];
// Only add subview if the window has been created.
// Otherwise, _createWindowWithStyle will handle adding the buttons from the _buttons array later.
if (_window)
[[_window contentView] addSubview:button];
if (count == 0)
{
[button setKeyEquivalent:CPCarriageReturnCharacter];
[button setBezelStyle:CPRoundedBezelStyle];
}
else if ([aTitle lowercaseString] === @"cancel")
[button setKeyEquivalent:CPEscapeFunctionKey];
[_buttons insertObject:button atIndex:0];
}
#pragma mark Layout
// MARK: Layout
/*!
@ignore
@@ -530,6 +535,9 @@ var bottomHeight = 71;
[[_window contentView] addSubview:_suppressionButton];
}
/*!
@ignore
*/
/*!
@ignore
*/
@@ -539,25 +547,17 @@ var bottomHeight = 71;
minimumSize = [_themeView currentValueForThemeAttribute:@"size"],
buttonOffset = [_themeView currentValueForThemeAttribute:@"button-offset"],
helpLeftOffset = [_themeView currentValueForThemeAttribute:@"help-image-left-offset"],
aRepresentativeButton = [_buttons objectAtIndex:0],
defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
panelSize = [[_window contentView] frame].size,
buttonsOriginY,
buttonMarginY,
buttonMarginX,
theme = [self theme],
offsetX;
[aRepresentativeButton setTheme:[self theme]];
[aRepresentativeButton sizeToFit];
panelSize.height = CGRectGetMaxY([lastView frame]) + defaultElementsMargin + [aRepresentativeButton frameSize].height;
if (panelSize.height < minimumSize.height)
panelSize.height = minimumSize.height;
buttonsOriginY = panelSize.height - [aRepresentativeButton frameSize].height + buttonOffset;
offsetX = panelSize.width - inset.right;
var isHUD = (_defaultWindowStyle & CPHUDBackgroundWindowMask) || (theme === [CPTheme defaultHudTheme]);
// 1. Determine Margins (Moved up so we can use them in height calculation)
switch ([_window styleMask])
{
case _CPModalWindowMask:
@@ -571,27 +571,61 @@ var bottomHeight = 71;
break;
}
// 2. Prepare buttons and get row height
var maxButtonHeight = 0.0;
for (var i = 0; i < [_buttons count]; i++)
{
var btn = _buttons[i];
[btn sizeToFit];
if (isHUD)
[btn setThemeState:CPThemeStateHUD];
else
[btn unsetThemeState:CPThemeStateHUD];
maxButtonHeight = MAX(maxButtonHeight, CGRectGetHeight([btn frame]));
}
// 3. Calculate Content Height
var lastViewMaxY = CGRectGetMaxY([lastView frame]);
// Use bottomHeight (71) to reserve space for the footer
var requiredContentHeight = lastViewMaxY + bottomHeight;
var finalContentSize = CGSizeMake(
[[_window contentView] frame].size.width,
MAX(requiredContentHeight, minimumSize.height)
);
// 4. Position Buttons
// Calculate the top Y coordinate to vertically center the button row within the bottomHeight area
// Center Y of footer = Height - (bottomHeight / 2.0)
// Top Y of button = Center Y - (maxButtonHeight / 2.0)
buttonsOriginY = finalContentSize.height - ((bottomHeight + maxButtonHeight) / 2.0) - buttonMarginY;
offsetX = finalContentSize.width - inset.right;
// Loop and set frames
for (var i = [_buttons count] - 1; i >= 0 ; i--)
{
var button = _buttons[i];
[button setTheme:[self theme]];
[button sizeToFit];
var buttonFrame = [button frame],
var button = _buttons[i],
buttonFrame = [button frame],
width = MAX(80.0, CGRectGetWidth(buttonFrame)),
height = CGRectGetHeight(buttonFrame);
height = CGRectGetHeight(buttonFrame),
yOffset = FLOOR((maxButtonHeight - height) / 2.0);
offsetX -= width;
[button setFrame:CGRectMake(offsetX + buttonMarginX, buttonsOriginY + buttonMarginY, width, height)];
[button setFrame:CGRectMake(offsetX + buttonMarginX, buttonsOriginY + buttonMarginY + yOffset, width, height)];
offsetX -= 10;
}
// Position Help Button if needed
if (_showHelp)
{
var helpImage = [_themeView currentValueForThemeAttribute:@"help-image"],
helpImagePressed = [_themeView currentValueForThemeAttribute:@"help-image-pressed"],
helpImageSize = helpImage ? [helpImage size] : CGSizeMakeZero(),
helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY, helpImageSize.width, helpImageSize.height);
helpYOffset = FLOOR((maxButtonHeight - helpImageSize.height) / 2.0),
helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY + buttonMarginY + helpYOffset, helpImageSize.width, helpImageSize.height);
[_alertHelpButton setImage:helpImage];
[_alertHelpButton setAlternateImage:helpImagePressed];
@@ -599,8 +633,7 @@ var bottomHeight = 71;
[_alertHelpButton setFrame:helpFrame];
}
panelSize.height += [aRepresentativeButton frameSize].height + inset.bottom + buttonOffset;
return panelSize;
return finalContentSize;
}
/*!
@@ -614,9 +647,14 @@ var bottomHeight = 71;
if (!_window)
[self _createWindowWithStyle:nil];
// Ensure the theme view knows if we are in HUD mode so it picks up the right specificities
if ((_defaultWindowStyle & CPHUDBackgroundWindowMask) || ([self theme] === [CPTheme defaultHudTheme]))
[_themeView setThemeState:CPThemeStateHUD];
else
[_themeView unsetThemeState:CPThemeStateHUD];
var iconOffset = [_themeView currentValueForThemeAttribute:@"image-offset"],
theImage = _icon,
finalSize;
theImage = _icon;
if (!theImage)
switch (_alertStyle)
@@ -648,11 +686,17 @@ var bottomHeight = 71;
else if (_accessoryView)
lastView = _accessoryView;
finalSize = [self _layoutButtonsFromView:lastView];
if ([_window styleMask] & CPDocModalWindowMask)
finalSize.height -= 26; // adjust the absence of title bar
[_window setFrameSize:finalSize];
// 1. Get the size needed for the *content* (text, buttons, padding)
var finalContentSize = [self _layoutButtonsFromView:lastView];
// 2. Convert Content Size -> Frame Size
// This accounts for the Title Bar and borders automatically.
var contentRect = CGRectMake(0.0, 0.0, finalContentSize.width, finalContentSize.height);
var frameRect = [[_window class] frameRectForContentRect:contentRect styleMask:[_window styleMask]];
// 3. Apply the calculated Frame Size
[_window setFrameSize:frameRect.size];
[_window center];
if ([_window styleMask] & _CPModalWindowMask || [_window styleMask] & CPHUDBackgroundWindowMask)
@@ -664,7 +708,7 @@ var bottomHeight = 71;
_needsLayout = NO;
}
#pragma mark Displaying Alerts
// MARK: Displaying Alerts
/*!
Displays the \c CPAlert panel as a modal dialog. The user will not be
@@ -742,7 +786,7 @@ var bottomHeight = 71;
[self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
}
#pragma mark Private
// MARK: Private
/*!
@ignore
@@ -752,6 +796,12 @@ var bottomHeight = 71;
var frame = CGRectMakeZero();
frame.size = [_themeView currentValueForThemeAttribute:@"size"];
// Propagate CPHUDBackgroundWindowMask from _defaultWindowStyle to forceStyle.
// This ensures that even if we force CPDocModalWindowMask (for sheets),
// the window still knows it should be a HUD.
if (_defaultWindowStyle & CPHUDBackgroundWindowMask)
forceStyle |= CPHUDBackgroundWindowMask;
_window = [[CPPanel alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle];
[_window setLevel:CPStatusWindowLevel];
[_window setPlatformWindow:[[CPApp keyWindow] platformWindow]];
+9 -9
View File
@@ -63,8 +63,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
#pragma mark -
#pragma mark Class Methods
// MARK: -
// MARK: Class Methods
/*! Returns the current default CPAppearance
*/
@@ -99,8 +99,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
#pragma mark -
#pragma mark Initialization
// MARK: -
// MARK: Initialization
/*! Creates a CPAppearance object initialized to the specified appearance file in the specified bundle
This method does actually nothing special. It just creates a default appearance object
@@ -122,8 +122,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
#pragma mark -
#pragma mark Implementation
// MARK: -
// MARK: Implementation
- (BOOL)isEqual:(id)anObject
{
@@ -139,8 +139,8 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
}
#pragma mark -
#pragma mark CPCoding
// MARK: -
// MARK: CPCoding
- (id)initWithCoder:(CPCoder)aCoder
{
@@ -159,4 +159,4 @@ CPThemeStateAppearanceVibrantDark = CPThemeState("appearance-vibrant-dark")
[aCoder encodeBool:_allowsVibrancy forKey:@"_allowsVibrancy"];
}
@end
@end
+75 -2
View File
@@ -122,6 +122,10 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
CPPanel _aboutPanel;
CPThemeBlend _themeBlend @accessors(property=themeBlend);
// OS behavior
CPApplicationOSBehavior _OSBehavior;
BOOL _simulatesWindows;
}
/*!
@@ -154,6 +158,9 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
_eventListenerInsertionIndex = 0;
_windows = [[CPNull null]];
_OSBehavior = CPApplicationLegacyOSBehavior;
_simulatesWindows = NO;
}
return self;
@@ -1260,7 +1267,45 @@ var CPApplicationDelegate_applicationShouldTerminate_ = 1 << 0,
+ (CPString)defaultThemeName
{
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo2");
return ([[CPBundle mainBundle] objectForInfoDictionaryKey:"CPDefaultTheme"] || @"Aristo3");
}
// See CPApplication_Constants.j for comments
- (void)setOSBehavior:(CPApplicationOSBehavior)anOSBehavior
{
// Verify if provided OS behavior is valid
if ([[CPApplicationOSBehaviors allKeysForObject:anOSBehavior] count] == 0)
{
CPLog.warn("CPApplication setOSBehavior: invalid CPApplicationOSBehavior (received "+anOSBehavior+"). Ignored.");
return;
}
_OSBehavior = anOSBehavior;
[[CPNotificationCenter defaultCenter] postNotificationName:CPApplicationOSBehaviorDidChangeNotification object:CPApp userInfo:nil];
}
- (CPApplicationOSBehavior)OSBehavior
{
return _OSBehavior;
}
- (BOOL)shouldMimicWindows
{
return (_OSBehavior == CPApplicationFollowOSBehavior) && (CPBrowserIsOperatingSystem(CPWindowsOperatingSystem) || _simulatesWindows);
}
- (void)setSimulatesWindows:(BOOL)shouldSimulateWindows
{
if (_simulatesWindows === shouldSimulateWindows)
return;
_simulatesWindows = shouldSimulateWindows;
}
- (BOOL)simulatesWindows
{
return _simulatesWindows;
}
@end
@@ -1375,7 +1420,7 @@ var _CPAppBootstrapperActions = nil;
var defaultThemeName = [CPApplication defaultThemeName],
themeURL = nil;
if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2")
if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2" || defaultThemeName === @"Aristo3")
themeURL = [[CPBundle bundleForClass:[CPApplication class]] pathForResource:defaultThemeName + @".blend"];
else
themeURL = [[CPBundle mainBundle] pathForResource:defaultThemeName + @".blend"];
@@ -1391,6 +1436,34 @@ var _CPAppBootstrapperActions = nil;
[[CPApplication sharedApplication] setThemeBlend:aThemeBlend];
[CPTheme setDefaultTheme:[CPTheme themeNamed:[CPApplication defaultThemeName]]];
// Search in the Info.plist if the special CPApplicationSimulateWindowsOS flag is set (for testing)
[CPApp setSimulatesWindows:!![[CPBundle mainBundle] objectForInfoDictionaryKey:"CPApplicationSimulateWindowsOS"]];
// Before loading the main CIB, try to find if a CPApplicationOSBehavior is specified in the Info.plist or in the user defaults
// (with user defaults precedence). Value stored must be a string representing the name of the OS behavior.
var plistOSBehavior = [[CPBundle mainBundle] objectForInfoDictionaryKey:"CPApplicationOSBehavior"],
userOSBehavior = [[CPUserDefaults standardUserDefaults] objectForKey:@"CPApplicationOSBehavior"];
if (userOSBehavior)
{
var osBehavior = [CPApplicationOSBehaviors objectForKey:userOSBehavior];
if (osBehavior)
[CPApp setOSBehavior:osBehavior];
else
CPLog.warn("Invalid CPApplicationOSBehavior specified in user defaults (found:"+userOSBehavior+"). Ignored.");
}
else if (plistOSBehavior)
{
var osBehavior = [CPApplicationOSBehaviors objectForKey:plistOSBehavior];
if (osBehavior)
[CPApp setOSBehavior:osBehavior];
else
CPLog.warn("Invalid CPApplicationOSBehavior specified in Info.plist (found:"+plistOSBehavior+"). Ignored.");
}
[self performActions];
}
+12
View File
@@ -39,3 +39,15 @@ CPTerminateLater = -1; // not currently supported
CPRunStoppedResponse = -1000;
CPRunAbortedResponse = -1001;
CPRunContinuesResponse = -1002;
// Should the application follow Cappuccino UX-UI (which is OSX like) or OS UX-UI (mainly Windows) ?
// See explanation on https://github.com/cappuccino/cappuccino/wiki/CPApplicationSelectedOSBehavior
@typedef CPApplicationOSBehavior
CPApplicationLegacyOSBehavior = 1;
CPApplicationFollowOSBehavior = 2;
CPApplicationOSBehaviorDidChangeNotification = @"CPApplicationOSBehaviorDidChangeNotification";
CPApplicationOSBehaviors = @{
@"CPApplicationLegacyOSBehavior": CPApplicationLegacyOSBehavior,
@"CPApplicationFollowOSBehavior": CPApplicationFollowOSBehavior
};
+44
View File
@@ -378,3 +378,47 @@ var DefaultLineWidth = 1.0;
}
@end
@implementation CPBezierPath (AnimationAdditions)
- (CPString)SVGString
{
var pathString = "";
var elements = _path.elements;
var count = _path.count;
for (var i = 0; i < count; i++)
{
var element = elements[i];
// Use the kCGPathElement* constants defined in CGPath.j
switch (element.type)
{
case kCGPathElementMoveToPoint:
pathString += "M " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddLineToPoint:
pathString += "L " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddQuadCurveToPoint:
pathString += "Q " + element.cpx + " " + element.cpy + " " + element.x + " " + element.y + " ";
break;
case kCGPathElementAddCurveToPoint:
pathString += "C " + element.cp1x + " " + element.cp1y + " " + element.cp2x + " " + element.cp2y + " " + element.x + " " + element.y + " ";
break;
case kCGPathElementCloseSubpath:
pathString += "Z ";
break;
}
}
return pathString.trim();
}
@end
+10 -10
View File
@@ -264,8 +264,8 @@ CPBelowBottom = 6;
[self _manageTitlePositioning];
}
#pragma mark -
#pragma mark Style properties which override theme values
// MARK: -
// MARK: Style properties which override theme values
/*!
The borderColor, borderWidth, cornerRadius and fillColor properties for the receiver
are only supported for boxes with boxType === CPBoxCustom and borderType === CPLineBorder.
@@ -282,10 +282,10 @@ CPBelowBottom = 6;
*/
// See discussion above.
#pragma mark borderColor
// MARK: borderColor
- (CPColor)borderColor
{
return [self valueForThemeAttribute:@"border-color"];
return [self currentValueForThemeAttribute:@"border-color"];
}
- (void)setBorderColor:(CPColor)color
@@ -308,7 +308,7 @@ CPBelowBottom = 6;
}
// See discussion above.
#pragma mark borderWidth
// MARK: borderWidth
- (float)borderWidth
{
return [self valueForThemeAttribute:@"border-width"];
@@ -334,7 +334,7 @@ CPBelowBottom = 6;
}
// See discussion above.
#pragma mark cornerRadius
// MARK: cornerRadius
- (float)cornerRadius
{
return [self valueForThemeAttribute:@"corner-radius"];
@@ -360,7 +360,7 @@ CPBelowBottom = 6;
}
// See discussion above.
#pragma mark fillColor
// MARK: fillColor
- (CPColor)fillColor
{
return [self valueForThemeAttribute:@"background-color"];
@@ -761,7 +761,7 @@ CPBelowBottom = 6;
@end
#pragma mark -
// MARK: -
@implementation CPBox (CSSTheming)
@@ -785,7 +785,7 @@ CPBelowBottom = 6;
[_boxView setFrame:CGRectMake(0,2,bounds.size.width,1)];
}
[_boxView setBackgroundColor:[self valueForThemeAttribute:@"border-color"]];
[_boxView setBackgroundColor:[self currentValueForThemeAttribute:@"border-color"]];
return;
}
@@ -829,7 +829,7 @@ CPBelowBottom = 6;
@end
#pragma mark -
// MARK: -
var CPBoxTypeKey = @"CPBoxTypeKey",
CPBoxBorderTypeKey = @"CPBoxBorderTypeKey",
+1 -1
View File
@@ -153,7 +153,7 @@ var CPBrowserDelegate_browser_acceptDrop_atRow_column_dropOperation_
_prototypeView = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_prototypeView setVerticalAlignment:CPCenterVerticalTextAlignment];
[_prototypeView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateSelectedDataView];
[_prototypeView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateSelectedDataView.and(CPThemeStateTableDataView)];
[_prototypeView setLineBreakMode:CPLineBreakByTruncatingTail];
_horizontalScrollView = [[CPScrollView alloc] initWithFrame:[self bounds]];
+13 -7
View File
@@ -98,7 +98,8 @@ var CPButtonBezelStyleStateMap = @{
CPRegularSquareBezelStyle: CPButtonStateBezelStyleRegularSquare,
CPTexturedSquareBezelStyle: CPButtonStateBezelStyleTextured,
CPDisclosureBezelStyle: CPButtonStateBezelStyleDisclosure,
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure
CPRoundedDisclosureBezelStyle: CPButtonStateBezelStyleRoundedDisclosure,
CPHUDBezelStyle: CPThemeStateHUD
};
/// @cond IGNORE
@@ -220,8 +221,8 @@ CPButtonImageOffset = 3.0;
[self setButtonType:CPMomentaryPushInButton];
}
#pragma mark -
#pragma mark Control Size
// MARK: -
// MARK: Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
@@ -232,7 +233,7 @@ CPButtonImageOffset = 3.0;
}
#pragma mark -
// MARK: -
// Setting the state
/*!
@@ -707,12 +708,17 @@ CPButtonImageOffset = 3.0;
// 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
var visualState = [self themeState] || CPThemeStateNormal,
currentState = [self state],
buttonIsOn = (currentState !== CPOffState);
// If the button is pushed (_isHighlighted), always add the highlighted state
if (_isHighlighted || (((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
// Define masks that imply the background changes color (Blue/Gray)
// If the background changes, we usually want the text to turn White (Highlighted state).
var highlightMask = CPPushInCellMask | CPChangeGrayCellMask | CPChangeBackgroundCellMask;
// Only add CPThemeStateHighlighted if the button is configured to highlight visually (Background change)
if ((_isHighlighted && (_highlightsBy & highlightMask)) ||
(((_showsStateBy & CPChangeGrayCellMask) || (_showsStateBy & CPChangeBackgroundCellMask)) && buttonIsOn))
visualState = visualState.and(CPThemeStateHighlighted);
else
visualState = visualState.without(CPThemeStateHighlighted);
+1724 -148
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -95,8 +95,8 @@ CPCheckBoxImageOffset = 4.0;
}
#pragma mark -
#pragma mark Override methods from CPButton
// MARK: -
// MARK: Override methods from CPButton
- (CGSize)_minimumFrameSize
{
@@ -177,7 +177,7 @@ CPCheckBoxImageOffset = 4.0;
@end
#pragma mark -
// MARK: -
@implementation CPCheckBox (TableDataView)
+13 -6
View File
@@ -202,8 +202,8 @@ var HORIZONTAL_MARGIN = 2;
#pragma mark -
#pragma mark Delegate
// MARK: -
// MARK: Delegate
/*!
Set the delegate of the receiver
@@ -600,9 +600,12 @@ var HORIZONTAL_MARGIN = 2;
if (_maxNumberOfRows > 0)
numberOfRows = MIN(numberOfRows, _maxNumberOfRows);
height = MAX(height, numberOfRows * (_minItemSize.height + _verticalMargin));
// calculate the required height: (sum of item heights) + (sum of margins between items).
var requiredHeight = (numberOfRows * _minItemSize.height) + (MAX(0, numberOfRows - 1) * _verticalMargin);
height = MAX(height, requiredHeight);
var itemSizeHeight = FLOOR(height / numberOfRows) - _verticalMargin;
// calculate individual item height based on the total available height.
var itemSizeHeight = (numberOfRows > 0) ? FLOOR((height - (MAX(0, numberOfRows - 1) * _verticalMargin)) / numberOfRows) : 0;
if (maxItemSizeHeight > 0)
itemSizeHeight = MIN(itemSizeHeight, maxItemSizeHeight);
@@ -621,7 +624,7 @@ var HORIZONTAL_MARGIN = 2;
_horizontalMargin = _uniformSubviewsResizing ? FLOOR((aFrameSize.width - numberOfColumns * anItemSize.width) / (numberOfColumns + 1)) : HORIZONTAL_MARGIN;
var x = _horizontalMargin,
y = -anItemSize.height;
y = -anItemSize.height;
[displayItems enumerateObjectsUsingBlock:function(item, idx, stop)
{
@@ -636,7 +639,11 @@ var HORIZONTAL_MARGIN = 2;
if (idx % numberOfColumns == 0)
{
x = _horizontalMargin;
y += _verticalMargin + anItemSize.height;
// For the first row, don't add a margin. For all subsequent rows, add the margin.
if (idx === 0)
y += anItemSize.height;
else
y += _verticalMargin + anItemSize.height;
}
[view setFrameOrigin:CGPointMake(x, y)];
+7 -7
View File
@@ -89,8 +89,8 @@ var cachedBlackColor,
}
@global document
#pragma mark -
#pragma mark Theming
// MARK: -
// MARK: Theming
+ (CPString)defaultThemeClass
{
@@ -109,8 +109,8 @@ var cachedBlackColor,
}
#pragma mark -
#pragma mark Static methods
// MARK: -
// MARK: Static methods
/*!
Creates a color in the RGB colorspace, with an alpha value.
@@ -884,8 +884,8 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
@end
#pragma mark -
#pragma mark CSS Theming
// MARK: -
// MARK: CSS Theming
// The code below adds support for CSS theming with 100% compatibility with current theming system.
// The idea is to extend CPColor (and CPImage) with CSS components and adapt low level UI components to
@@ -1091,7 +1091,7 @@ url("data:image/png;base64,BASE64ENCODEDDATA") // if there is a pattern image
@end
#pragma mark -
// MARK: -
/// @cond IGNORE
var CPColorComponentsKey = @"CPColorComponentsKey",
+80 -32
View File
@@ -51,6 +51,7 @@ CPColorPickerViewWidth = 265;
CPColorPickerViewHeight = 370;
CPColorPanelColorDidChangeNotification = @"CPColorPanelColorDidChangeNotification";
CPColorDragType = CPColorPboardType;
var PREVIEW_HEIGHT = 20.0,
TOOLBAR_HEIGHT = 32.0,
@@ -129,8 +130,6 @@ var SharedColorPanel = nil,
if (self)
{
//[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
[self setTitle:@"Color Panel"];
[self setLevel:CPFloatingWindowLevel];
@@ -149,20 +148,37 @@ var SharedColorPanel = nil,
*/
- (void)setColor:(CPColor)aColor
{
if ([_color isEqual:aColor])
return;
_color = aColor;
[_previewView setBackgroundColor:_color];
[CPApp sendAction:@selector(changeColor:) to:nil from:self];
// Check if the color change originated from user interaction inside the panel itself.
// We only broadcast `changeColor:` if the user picked a color via the panel's UI.
// If an external CPColorWell called `setColor:` programmatically, broadcasting it
// back down the responder chain would incorrectly change the previous First Responder.
var currentEvent = [CPApp currentEvent],
isFromPanel = currentEvent && ([currentEvent window] === self);
if (_target && _action)
[CPApp sendAction:_action to:_target from:self];
if (isFromPanel)
{
// Push color via Responder Chain (targets First Responder, i.e., the active CPColorWell)
[CPApp sendAction:@selector(changeColor:) to:nil from:self];
if (_target && _action)
[CPApp sendAction:_action to:_target from:self];
}
[[CPNotificationCenter defaultCenter]
postNotificationName:CPColorPanelColorDidChangeNotification
object:self];
[_activePicker setColor:_color];
[_opacitySlider setFloatValue:[_color alphaComponent]];
if (_activePicker)
[_activePicker setColor:_color];
if (_opacitySlider)
[_opacitySlider setFloatValue:[_color alphaComponent]];
}
/*!
@@ -174,7 +190,7 @@ var SharedColorPanel = nil,
{
[self setColor:aColor];
if (bool)
if (bool && _activePicker)
[_activePicker setColor:_color];
}
@@ -332,7 +348,7 @@ var SharedColorPanel = nil,
buttonForLater = button;
}
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
// Preview
var previewBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10, CGRectGetWidth(bounds) - 86, PREVIEW_HEIGHT)];
_previewView = [[_CPColorPanelPreview alloc] initWithFrame:CGRectInset([previewBox bounds], 2.0, 2.0)];
@@ -350,7 +366,7 @@ var SharedColorPanel = nil,
[_previewLabel setTextColor:[CPColor blackColor]];
[_previewLabel setAlignment:CPRightTextAlignment];
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
// Swatches
var swatchBox = [[CPView alloc] initWithFrame:CGRectMake(76, TOOLBAR_HEIGHT + 10 + PREVIEW_HEIGHT + 5, CGRectGetWidth(bounds) - 86, SWATCH_HEIGHT + 2.0)];
[swatchBox setBackgroundColor:[CPColor colorWithWhite:0.8 alpha:1.0]];
@@ -391,8 +407,6 @@ var SharedColorPanel = nil,
[contentView addSubview:opacityLabel];
[contentView addSubview:_opacitySlider];
_target = nil;
_action = nil;
_activePicker = nil;
[_previewView setBackgroundColor:_color];
@@ -412,8 +426,6 @@ var SharedColorPanel = nil,
@end
CPColorDragType = "CPColorDragType";
var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
/* @ignore */
@@ -423,12 +435,14 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
CPColor _dragColor;
CPColorPanel _colorPanel;
CPCookie _swatchCookie;
CGPoint _mouseDownPoint;
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
_mouseDownPoint = CGPointMake(0, 0);
[self setBackgroundColor:[CPColor grayColor]];
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
@@ -442,7 +456,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
for (var i = 0; i < 50; i++)
{
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var view = [[CPView alloc] initWithFrame:CGRectMake(13 * i + 1, 1, 12, 12)],
fillView = [[CPView alloc] initWithFrame:CGRectInset([view bounds], 1.0, 1.0)];
@@ -494,7 +507,6 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
- (CPArray)saveColorList
{
var result = [];
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
for (var i = 0; i < _swatches.length; i++)
result.push([[[_swatches[i] subviews][0] backgroundColor] hexString]);
@@ -521,39 +533,44 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
- (void)setColor:(CPColor)aColor atIndex:(int)index
{
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
[[_swatches[index] subviews][0] setBackgroundColor:aColor];
[self saveColorList];
}
- (void)mouseDown:(CPEvent)anEvent
{
_mouseDownPoint = [anEvent locationInWindow];
}
- (void)mouseUp:(CPEvent)anEvent
{
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil],
bounds = [self bounds];
if (!CGRectContainsPoint(bounds, point) || point.x > [self bounds].size.width - 1 || point.x < 1)
return NO;
return;
[_colorPanel setColor:[self colorAtIndex:FLOOR(point.x / 13)] updatePicker:YES];
}
- (void)mouseDragged:(CPEvent)anEvent
{
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
var windowPoint = [anEvent locationInWindow];
// Prevent accidental drags from rapid clicking causing small micro-movements
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
var point = [self convertPoint:windowPoint fromView:nil];
if (point.x > [self bounds].size.width - 1 || point.x < 1)
return NO;
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
return;
var swatch = _swatches[FLOOR(point.x / 13)];
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
_dragColor = [[swatch subviews][0] backgroundColor];
var bounds = CGRectMakeCopy([swatch bounds]);
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
@@ -562,11 +579,15 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:CPColorDragType];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:nil
pasteboard:pasteboard
source:self
slideBack:YES];
}
@@ -577,7 +598,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_dragColor] forType:aType];
}
- (void)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
- (BOOL)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
{
var location = [self convertPoint:[aSender draggingLocation] fromView:nil],
pasteboard = [aSender draggingPasteboard],
@@ -587,6 +608,13 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return NO;
[self setColor:[CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]] atIndex:FLOOR(location.x / 13)];
return YES;
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
@end
@@ -595,11 +623,13 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
@implementation _CPColorPanelPreview : CPView
{
CPColorPanel _colorPanel;
CGPoint _mouseDownPoint;
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
_mouseDownPoint = CGPointMake(0, 0);
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPColorDragType]];
@@ -616,7 +646,7 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return _colorPanel;
}
- (void)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
- (BOOL)performDragOperation:(id /*<CPDraggingInfo>*/)aSender
{
var pasteboard = [aSender draggingPasteboard];
@@ -625,6 +655,8 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
var color = [CPKeyedUnarchiver unarchiveObjectWithData:[pasteboard dataForType:CPColorDragType]];
[_colorPanel setColor:color updatePicker:YES];
return YES;
}
- (BOOL)isOpaque
@@ -632,15 +664,22 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
return YES;
}
- (void)mouseDown:(CPEvent)anEvent
{
_mouseDownPoint = [anEvent locationInWindow];
}
- (void)mouseDragged:(CPEvent)anEvent
{
var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
var windowPoint = [anEvent locationInWindow];
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
[[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:[CPColorDragType] owner:self];
var point = [self convertPoint:windowPoint fromView:nil];
var bounds = CGRectMake(0, 0, 15, 15);
// FIXME: http://280north.lighthouseapp.com/projects/13294-cappuccino/tickets/25-implement-cpbox
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
@@ -649,11 +688,15 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorDragType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:CPColorDragType];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:nil
pasteboard:pasteboard
source:self
slideBack:YES];
}
@@ -664,6 +707,11 @@ var CPColorPanelSwatchesCookie = "CPColorPanelSwatchesCookie";
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self backgroundColor]] forType:aType];
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
@end
@import "CPColorPicker.j"
+200 -136
View File
@@ -25,7 +25,7 @@
@import "CPView.j"
@import "CPColor.j"
@import "CPColorPanel.j"
@import "CPPasteboard.j"
var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiveNotification";
@@ -39,10 +39,10 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
*/
@implementation CPColorWell : CPControl
{
BOOL _active;
BOOL _bordered;
CPColor _color;
BOOL _isChangingColorFromPanel; // Guard flag to prevent recursion
CGPoint _mouseDownPoint;
}
+ (Class)_binderClassForBinding:(CPString)aBinding
@@ -77,52 +77,54 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[theBinding reverseSetValueFor:@"color"];
}
- (BOOL)isFirstResponder
{
return [[self window] firstResponder] === self;
}
- (BOOL)acceptsFirstResponder
{
return [self isEnabled];
}
- (void)activate:(BOOL)shouldBeExclusive
{
[[self window] makeFirstResponder:self];
[[CPColorPanel sharedColorPanel] orderFront:self];
}
- (BOOL)isActive
{
return [self isFirstResponder] && [self isEnabled];
}
/*!
Deactivates the color well.
*/
- (void)deactivate
{
if ([self isFirstResponder])
[[self window] makeFirstResponder:nil];
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
_active = NO;
_color = [CPColor whiteColor];
_mouseDownPoint = CGPointMakeZero();
[self setBordered:YES];
[self registerForDraggedTypes:[CPArray arrayWithObject:CPColorPboardType]];
}
return self;
}
- (void)_registerNotifications
{
var defaultCenter = [CPNotificationCenter defaultCenter];
[defaultCenter
addObserver:self
selector:@selector(colorWellDidBecomeExclusive:)
name:_CPColorWellDidBecomeExclusiveNotification
object:nil];
[defaultCenter
addObserver:self
selector:@selector(colorPanelWillClose:)
name:CPWindowWillCloseNotification
object:[CPColorPanel sharedColorPanel]];
}
- (void)_removeNotifications
{
var defaultCenter = [CPNotificationCenter defaultCenter];
[defaultCenter
removeObserver:self
name:_CPColorWellDidBecomeExclusiveNotification
object:nil];
[defaultCenter
removeObserver:self
name:CPWindowWillCloseNotification
object:[CPColorPanel sharedColorPanel]];
}
// MARK: -
// MARK: Draw
/*!
Sets whether the color well is bordered.
@@ -143,7 +145,8 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
return [self hasThemeState:CPThemeStateBordered];
}
// Managing Color From Color Wells
// MARK: -
// MARK: Managing Color
/*!
Returns the color well's current color.
@@ -158,12 +161,17 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
*/
- (void)setColor:(CPColor)aColor
{
if (_color == aColor)
if ([_color isEqual:aColor])
return;
_color = aColor;
[self setNeedsLayout];
// Only push back to the panel if we initiated the change (not if the panel pushed it to us)
// AND if we are the current focus.
if (!_isChangingColorFromPanel && [self isFirstResponder])
[[CPColorPanel sharedColorPanel] setColor:_color];
}
/*!
@@ -175,96 +183,175 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[self setColor:[aSender color]];
}
// Activating and Deactivating Color Wells
/*!
Standard action method sent by CPColorPanel via the Responder Chain.
*/
- (void)changeColor:(id)aSender
{
if ([aSender isKindOfClass:[CPColorPanel class]])
{
_isChangingColorFromPanel = YES;
[self setColor:[aSender color]];
_isChangingColorFromPanel = NO;
// Forward the action to our target (e.g. controller)
[self sendAction:[self action] to:[self target]];
}
}
// MARK: -
// MARK: Activating and Deactivating
/*!
Activates the color well, displays the color panel, and makes the panel's current color the same as its own.
If exclusive is \c YES, deactivates any other CPColorWells. \c NO, keeps them active.
@param shouldBeExclusive whether other color wells should be deactivated.
*/
- (void)activate:(BOOL)shouldBeExclusive
- (BOOL)becomeFirstResponder
{
if (shouldBeExclusive)
// FIXME: make this queue!
[[CPNotificationCenter defaultCenter]
postNotificationName:_CPColorWellDidBecomeExclusiveNotification
object:self];
[self setThemeState:CPThemeStateFirstResponder];
var panel = [CPColorPanel sharedColorPanel];
// EXPLICITLY set ourselves as the target.
// This ensures that when the user clicks the panel (making Panel key),
// the panel still knows to send messages back to us.
[panel setTarget:self];
[panel setAction:@selector(changeColor:)];
// Sync panel to our current color
[panel setColor:_color];
if ([self isActive])
return;
_active = YES;
[[CPNotificationCenter defaultCenter]
addObserver:self
selector:@selector(colorPanelDidChangeColor:)
name:CPColorPanelColorDidChangeNotification
object:[CPColorPanel sharedColorPanel]];
}
/*!
Deactivates the color well.
*/
- (void)deactivate
{
if (![self isActive])
return;
_active = NO;
[[CPNotificationCenter defaultCenter]
removeObserver:self
name:CPColorPanelColorDidChangeNotification
object:[CPColorPanel sharedColorPanel]];
}
/*!
Returns \c YES if the color well is active.
*/
- (BOOL)isActive
{
return _active;
}
- (void)colorPanelDidChangeColor:(CPNotification)aNotification
{
[self takeColorFrom:[aNotification object]];
[self sendAction:[self action] to:[self target]];
}
- (void)colorWellDidBecomeExclusive:(CPNotification)aNotification
{
if (self != [aNotification object])
[self deactivate];
}
- (void)colorPanelWillClose:(CPNotification)aNotification
{
[self deactivate];
}
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
{
[self highlight:NO];
if (!mouseIsUp || !CGRectContainsPoint([self bounds], aPoint) || ![self isEnabled])
[[CPNotificationCenter defaultCenter] postNotificationName:_CPColorWellDidBecomeExclusiveNotification object:self];
return YES;
}
- (BOOL)resignFirstResponder
{
[self unsetThemeState:CPThemeStateFirstResponder];
var panel = [CPColorPanel sharedColorPanel];
// Clean up if we were the target
if ([panel target] == self)
[panel setTarget:nil];
return YES;
}
// MARK: -
// MARK: Event Handling
- (void)mouseDown:(CPEvent)anEvent
{
if (![self isEnabled])
return;
_mouseDownPoint = [anEvent locationInWindow];
[self activate:YES];
var colorPanel = [CPColorPanel sharedColorPanel];
[colorPanel setPlatformWindow:[[self window] platformWindow]];
[colorPanel setColor:_color];
[colorPanel orderFront:self];
}
- (void)mouseDragged:(CPEvent)anEvent
{
if (![self isEnabled])
return;
var windowPoint = [anEvent locationInWindow];
// Prevent accidental drags from rapid clicking causing small micro-movements
if (ABS(windowPoint.x - _mouseDownPoint.x) < 3 && ABS(windowPoint.y - _mouseDownPoint.y) < 3)
return;
var bounds = CGRectMake(0, 0, 15, 15);
var dragView = [[CPView alloc] initWithFrame:bounds],
dragFillView = [[CPView alloc] initWithFrame:CGRectInset(bounds, 1.0, 1.0)];
[dragView setBackgroundColor:[CPColor blackColor]];
[dragFillView setBackgroundColor:_color];
[dragView addSubview:dragFillView];
var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard];
[pasteboard declareTypes:[CPArray arrayWithObject:CPColorPboardType] owner:self];
[pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_color] forType:CPColorPboardType];
var point = [self convertPoint:windowPoint fromView:nil];
[self dragView:dragView
at:CGPointMake(point.x - bounds.size.width / 2.0, point.y - bounds.size.height / 2.0)
offset:CGPointMake(0.0, 0.0)
event:anEvent
pasteboard:pasteboard
source:self
slideBack:YES];
}
// MARK: -
// MARK: Drag and Drop
- (void)draggingEntered:(id)sender
{
var pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:CPColorPboardType])
{
[self setThemeState:CPThemeStateHighlighted];
return CPDragOperationCopy;
}
return CPDragOperationNone;
}
- (void)draggingExited:(id)sender
{
[self unsetThemeState:CPThemeStateHighlighted];
}
- (BOOL)performDragOperation:(id)sender
{
var pasteboard = [sender draggingPasteboard];
if ([[pasteboard types] containsObject:CPColorPboardType])
{
var data = [pasteboard dataForType:CPColorPboardType],
newColor = [CPKeyedUnarchiver unarchiveObjectWithData:data];
if (newColor && [newColor isKindOfClass:[CPColor class]])
{
[self setColor:newColor];
[self sendAction:[self action] to:[self target]];
// Activate nicely after drop
[self activate:YES];
[self unsetThemeState:CPThemeStateHighlighted];
return YES;
}
}
return NO;
}
- (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
{
if (aType == CPColorPboardType)
[aPasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:_color] forType:aType];
}
- (unsigned)draggingSourceOperationMaskForLocal:(BOOL)isLocal
{
return CPDragOperationCopy;
}
// MARK: -
// MARK: Layout
- (CGRect)contentRectForBounds:(CGRect)bounds
{
var contentInset = [self currentValueForThemeAttribute:@"content-inset"];
return CGRectInsetByInset(bounds, contentInset);
}
@@ -318,7 +405,6 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"bezel-view"];
[contentView setBackgroundColor:_color];
var contentBorderView = [self layoutEphemeralSubviewNamed:@"content-border-view"
@@ -328,28 +414,6 @@ var _CPColorWellDidBecomeExclusiveNotification = @"_CPColorWellDidBecomeExclusiv
[contentBorderView setBackgroundColor:[self currentValueForThemeAttribute:@"content-border-color"]];
}
#pragma mark -
#pragma mark Observers method
- (void)_addObservers
{
if (_isObserving)
return;
[super _addObservers];
[self _registerNotifications];
}
- (void)_removeObservers
{
if (!_isObserving)
return;
[super _removeObservers];
[self _removeNotifications];
}
@end
@implementation CPColorWellValueBinder : CPBinder
@@ -398,9 +462,9 @@ var CPColorWellColorKey = "CPColorWellColorKey",
if (self)
{
_active = NO;
_color = [aCoder decodeObjectForKey:CPColorWellColorKey];
[self setBordered:[aCoder decodeBoolForKey:CPColorWellBorderedKey]];
[self registerForDraggedTypes:[CPArray arrayWithObject:CPColorPboardType]];
}
return self;
+21 -31
View File
@@ -91,7 +91,7 @@ var CPComboBoxTextSubview = @"text",
{
return @{
@"popup-button-size": CGSizeMake(21.0, 29.0),
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0),
@"border-inset": CGInsetMake(3.0, 3.0, 3.0, 3.0)
};
}
@@ -133,7 +133,7 @@ var CPComboBoxTextSubview = @"text",
[self setThemeState:CPComboBoxStateButtonBordered];
}
#pragma mark Setting Display Attributes
// MARK: Setting Display Attributes
- (BOOL)hasVerticalScroller
{
@@ -214,7 +214,7 @@ var CPComboBoxTextSubview = @"text",
_numberOfVisibleItems = MAX(visibleItems, 1);
}
#pragma mark Setting a Delegate
// MARK: Setting a Delegate
- (id <CPComboBoxDelegate>)delegate
{
@@ -254,7 +254,7 @@ var CPComboBoxTextSubview = @"text",
[super setDelegate:aDelegate];
}
#pragma mark Setting a Data Source
// MARK: Setting a Data Source
- (id <CPComboBoxDataSource>)dataSource
{
@@ -305,7 +305,7 @@ var CPComboBoxTextSubview = @"text",
[self reloadData];
}
#pragma mark Working with an Internal List
// MARK: Working with an Internal List
- (void)addItemsWithObjectValues:(CPArray)objects
{
@@ -378,7 +378,7 @@ var CPComboBoxTextSubview = @"text",
return _items.length;
}
#pragma mark Manipulating the Displayed List
// MARK: Manipulating the Displayed List
/*!
Returns the delegate to be used when creating the pop up list.
@@ -533,7 +533,7 @@ var CPComboBoxTextSubview = @"text",
CPComboBoxFocusRingWidth = inset.bottom;
}
[_listDelegate popUpRelativeToRect:[self _borderFrame] view:self offset:CPComboBoxFocusRingWidth - 1];
[_listDelegate popUpRelativeToRect:[self bounds] view:self offset:CPComboBoxFocusRingWidth - 1];
[self _selectMatchingItem];
}
@@ -570,6 +570,13 @@ var CPComboBoxTextSubview = @"text",
_selectedStringValue = selectedStringValue;
[self setStringValue:_selectedStringValue];
[self _updatePlaceholderState];
#if PLATFORM(DOM)
[self _setCSSStyleForInputElement];
#endif
[self _reverseSetBinding];
return YES;
@@ -594,7 +601,7 @@ var CPComboBoxTextSubview = @"text",
[self sendAction:[self action] to:[self target]];
}
#pragma mark Manipulating the Selection
// MARK: Manipulating the Selection
- (void)deselectItemAtIndex:(int)index
{
@@ -646,7 +653,7 @@ var CPComboBoxTextSubview = @"text",
[self selectItemAtIndex:index];
}
#pragma mark Completing the Text Field
// MARK: Completing the Text Field
- (BOOL)completes
{
@@ -693,7 +700,7 @@ var CPComboBoxTextSubview = @"text",
_forceSelection = !!flag;
}
#pragma mark CPTextField Delegate Methods and Overrides
// MARK: CPTextField Delegate Methods and Overrides
/*! @ignore */
- (BOOL)sendAction:(SEL)anAction to:(id)anObject
@@ -893,7 +900,7 @@ var CPComboBoxTextSubview = @"text",
[_listDelegate setAlignment:alignment];
}
#pragma mark Pop Up Button Layout
// MARK: Pop Up Button Layout
- (CGRect)popupButtonRectForBounds:(CGRect)bounds
{
@@ -938,7 +945,7 @@ var CPComboBoxTextSubview = @"text",
relativeToEphemeralSubviewNamed:@"content-view"];
}
#pragma mark Internal Helpers
// MARK: Internal Helpers
/*! @ignore */
- (void)_dataSourceWarningForMethod:(SEL)cmd condition:(CPString)flag
@@ -975,23 +982,6 @@ var CPComboBoxTextSubview = @"text",
}
}
/*!
Calculate the frame in base coordinates that will nestle just below the visible border of the text field.
@ignore
*/
- (CGRect)_borderFrame
{
var inset = [self currentValueForThemeAttribute:@"border-inset"],
frame = [self bounds];
frame.origin.x += inset.left;
frame.origin.y += inset.top;
frame.size.width -= inset.left + inset.right;
frame.size.height -= inset.top + inset.bottom;
return frame;
}
/* @ignore */
- (void)_popUpButtonWasClicked
{
@@ -1016,8 +1006,8 @@ var CPComboBoxTextSubview = @"text",
}
#pragma mark -
#pragma mark Observers method
// MARK: -
// MARK: Observers method
- (void)_addObservers
{
+10 -6
View File
@@ -48,9 +48,10 @@
CPRegularControlSize = 0;
CPSmallControlSize = 1;
CPMiniControlSize = 2;
CPLargeControlSize = 3; // Since MacOS 11, there's a new control size "Large"
// To get the theme state corresponding to a control size, use CPControlSizeThemeStates[controlSize]
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini];
CPControlSizeThemeStates = @[CPThemeStateControlSizeRegular, CPThemeStateControlSizeSmall, CPThemeStateControlSizeMini, CPThemeStateControlSizeLarge];
@typedef CPLineBreakMode
CPLineBreakByWordWrapping = 0;
@@ -207,8 +208,8 @@ var CPControlBlackColor = [CPColor blackColor];
return self;
}
#pragma mark -
#pragma mark Control Size
// MARK: -
// MARK: Control Size
/*!
Returns the control's control size
@@ -248,6 +249,9 @@ var CPControlBlackColor = [CPColor blackColor];
case CPMiniControlSize:
return CPThemeStateControlSizeMini;
case CPLargeControlSize:
return CPThemeStateControlSizeLarge;
case CPRegularControlSize:
default:
@@ -286,7 +290,7 @@ var CPControlBlackColor = [CPColor blackColor];
}
#pragma mark -
// MARK: -
/*!
Sets the receiver's target action.
@@ -1023,8 +1027,8 @@ var CPControlBlackColor = [CPColor blackColor];
}
#pragma mark -
#pragma mark Base writing direction
// MARK: -
// MARK: Base writing direction
/*!
Sets the initial writing direction of the receiver
+20 -20
View File
@@ -82,8 +82,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
#pragma mark -
#pragma mark Theme methods
// MARK: -
// MARK: Theme methods
+ (CPString)defaultThemeClass
{
@@ -174,8 +174,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
#pragma mark -
#pragma mark Binding methods
// MARK: -
// MARK: Binding methods
+ (Class)_binderClassForBinding:(CPString)theBinding
{
@@ -200,8 +200,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
#pragma mark -
#pragma mark Init methods
// MARK: -
// MARK: Init methods
- (id)initWithFrame:(CGRect)aFrame
{
@@ -271,8 +271,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
#pragma mark -
#pragma mark Control Size
// MARK: -
// MARK: Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
@@ -285,8 +285,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
#pragma mark -
#pragma mark Delegate methods
// MARK: -
// MARK: Delegate methods
/*! Set the delegate of the datePicker
@param aDelegate delegate of the datePicker
@@ -302,8 +302,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
#pragma mark -
#pragma mark Layout method
// MARK: -
// MARK: Layout method
/*! Layout the subviews
*/
@@ -313,8 +313,8 @@ CPEraDatePickerElementFlag = 0x0100;
[_datePickerComponent setNeedsDisplay:YES];
}
#pragma mark -
#pragma mark Setter
// MARK: -
// MARK: Setter
/*! Return the objectValue of the datePicker. The objectValue should take the timeZoneEffect
*/
@@ -651,8 +651,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
#pragma mark -
#pragma mark First responder methods
// MARK: -
// MARK: First responder methods
/*! Return YES if style is set to CPTextFieldAndStepperDatePickerStyle or CPTextFieldDatePickerStyle
*/
@@ -689,8 +689,8 @@ CPEraDatePickerElementFlag = 0x0100;
}
#pragma mark -
#pragma mark getter
// MARK: -
// MARK: getter
/*!
Returns \c YES if the textfield is bezeled.
@@ -723,8 +723,8 @@ CPEraDatePickerElementFlag = 0x0100;
return [[_locale objectForKey:CPLocaleCountryCode] isEqualToString:@"US"];
}
#pragma mark -
#pragma mark Key event
// MARK: -
// MARK: Key event
/*! Key down event
@param anEvent
+18 -10
View File
@@ -52,7 +52,7 @@
}
#pragma mark Init method
// MARK: Init method
/*! Init a _CPDatePickerCalendar
@param aFrame
@@ -111,8 +111,8 @@
}
#pragma mark -
#pragma mark Responder methods
// MARK: -
// MARK: Responder methods
- (BOOL)acceptsFirstResponder
{
@@ -120,8 +120,8 @@
}
#pragma mark -
#pragma mark Getter Setter methods
// MARK: -
// MARK: Getter Setter methods
/*! Set the date value of the component. It sets the dateValue of the header and the monthView also
@param aDateValue
@@ -160,8 +160,8 @@
[self _init];
}
#pragma mark -
#pragma mark Layout methods
// MARK: -
// MARK: Layout methods
/*! Manager the subviews. It hides or not the clock.
*/
@@ -221,8 +221,8 @@
}
#pragma mark -
#pragma mark Action methods
// MARK: -
// MARK: Action methods
/*! Move to the nextMonth without changing the dateValue of the datePicker
*/
@@ -251,7 +251,15 @@
- (void)_displayNextMonth
{
[self setDateValue:[_monthView nextMonth]];
// Copy the date so we don't modify the view's state directly
var nextDate = [[_monthView nextMonth] copy];
// Set to the middle of the month (15th).
// This prevents [setDateValue:]'s timezone adjustment from
// shifting the date back into the previous month (e.g., Nov 1 -> Oct 31).
nextDate.setDate(15);
[self setDateValue:nextDate];
}
- (void)_displayPreviousMonth
+184 -324
View File
@@ -24,14 +24,11 @@
@import <Foundation/CPKeyedUnarchiver.j>
@import "CPView.j"
@import "CPTextField.j"
@import "CPImageView.j"
@import "CPImage.j"
@import "CALayer.j"
@class _CPCibCustomResource
@class CPDatePicker
@class HandImageLayer
@class HoursLayer
@class HandLayer
@global CPHourMinuteSecondDatePickerElementFlag
@global CPTextFieldAndStepperDatePickerStyle
@@ -47,15 +44,19 @@ _CPDatePickerClockSeconds = 3;
@implementation _CPDatePickerClock : CPControl
{
BOOL _isEnabled;
HoursLayer _rootLayer;
HandLayer _hourHandLayer;
HandLayer _minuteHandLayer;
HandLayer _secondHandLayer;
CALayer _middleHandLayer;
CPDatePicker _datePicker;
// Pure DOM Views
CPImageView _hourHandView;
CPImageView _minuteHandView;
CPImageView _secondHandView;
CPImageView _middleHandView;
CPArray _hourLabels;
CPTextField _PMAMTextField;
CALayer _currentHandLayer;
CPDatePicker _datePicker;
CPView _currentHandView;
_CPDatePickerClockHand _currentHand;
CPInteger _currentRepresentedValue;
float _currentValueShift;
@@ -67,12 +68,16 @@ _CPDatePickerClockSeconds = 3;
CPInteger _representedSeconds;
BOOL _representedHourIsPM;
CPInteger _datePickerElements @accessors(getter=datePickerElements);
// Angles for Hit-Testing
float _hourAngle;
float _minuteAngle;
float _secondAngle;
CPInteger _datePickerElements @accessors(getter=datePickerElements);
}
#pragma mark -
#pragma mark Init methods
// MARK: -
// MARK: Init methods
- (id)initWithFrame:(CGRect)aFrame datePicker:(CPDatePicker)aDatePicker
{
@@ -81,69 +86,53 @@ _CPDatePickerClockSeconds = 3;
_datePicker = aDatePicker;
_datePickerElements = [_datePicker datePickerElements];
_trackingHand = NO;
_isEnabled = YES;
// 1. Initialize AM/PM Label
_PMAMTextField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-font" inState:CPThemeStateNormal] forThemeAttribute:@"font" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-color" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-color" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-offset" inState:CPThemeStateNormal] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateNormal];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-font" inState:CPThemeStateDisabled] forThemeAttribute:@"font" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-color" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-color" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-color" inState:CPThemeStateDisabled];
[_PMAMTextField setValue:[_datePicker valueForThemeAttribute:@"clock-text-shadow-offset" inState:CPThemeStateDisabled] forThemeAttribute:@"text-shadow-offset" inState:CPThemeStateDisabled];
[_PMAMTextField setAlignment:CPCenterTextAlignment];
[_PMAMTextField setVerticalAlignment:CPCenterVerticalTextAlignment];
[self addSubview:_PMAMTextField];
var middleHandSize = [_datePicker valueForThemeAttribute:@"middle-hand-size"],
minuteHandSize = [_datePicker valueForThemeAttribute:@"minute-hand-size"],
hourHandSize = [_datePicker valueForThemeAttribute:@"hour-hand-size"],
secondHandSize = [_datePicker valueForThemeAttribute:@"second-hand-size"];
// 2. Initialize Number Labels (1 to 12)
_hourLabels = [CPArray array];
// We use layer to make the rotation possible
_hourHandLayer = [[HandLayer alloc] initWithSize:hourHandSize];
[_hourHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_hourHandLayer setAnchorPoint:CGPointMakeZero()];
[_hourHandLayer setPosition:CGPointMake(0.0, 0.0)];
for (var i = 1; i <= 12; i++)
{
var label = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[label setStringValue:String(i)];
[label setAlignment:CPCenterTextAlignment];
[label setVerticalAlignment:CPCenterVerticalTextAlignment];
[self addSubview:label];
[_hourLabels addObject:label];
}
_minuteHandLayer = [[HandLayer alloc] initWithSize:minuteHandSize];
[_minuteHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_minuteHandLayer setAnchorPoint:CGPointMakeZero()];
[_minuteHandLayer setPosition:CGPointMake(0.0, 0.0)];
// 3. Initialize Hand Views
_hourHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_minuteHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_secondHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_middleHandView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
_secondHandLayer = [[HandLayer alloc] initWithSize:secondHandSize];
[_secondHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_secondHandLayer setAnchorPoint:CGPointMakeZero()];
[_secondHandLayer setPosition:CGPointMake(0.0, 0.0)];
// Ensure images stretch accurately across the bounds of the image view
[_hourHandView setImageScaling:CPImageScaleAxesIndependently];
[_minuteHandView setImageScaling:CPImageScaleAxesIndependently];
[_secondHandView setImageScaling:CPImageScaleAxesIndependently];
[_middleHandView setImageScaling:CPImageScaleAxesIndependently];
_middleHandLayer = [[HandLayer alloc] initWithSize:middleHandSize];
[_middleHandLayer setBounds:CGRectMake(0, 0, aFrame.size.width, aFrame.size.height)];
[_middleHandLayer setAnchorPoint:CGPointMakeZero()];
[_middleHandLayer setPosition:CGPointMake(0.0, 0.0)];
_rootLayer = [[HoursLayer alloc] init];
[self setWantsLayer:YES];
[self setLayer:_rootLayer];
[self _initHands];
[_rootLayer addSublayer:_hourHandLayer];
[_rootLayer addSublayer:_minuteHandLayer];
// 4. Add subviews in correct Z-Order
[self addSubview:_hourHandView];
[self addSubview:_minuteHandView];
if ([_datePicker valueForThemeAttribute:@"clock-second-hand-over"])
{
[_rootLayer addSublayer:_middleHandLayer];
[_rootLayer addSublayer:_secondHandLayer];
[self addSubview:_middleHandView];
[self addSubview:_secondHandView];
}
else
{
[_rootLayer addSublayer:_secondHandLayer];
[_rootLayer addSublayer:_middleHandLayer];
[self addSubview:_secondHandView];
[self addSubview:_middleHandView];
}
[_rootLayer setDrawsHours:[_datePicker valueForThemeAttribute:@"clock-draws-hours"]];
[_rootLayer setNeedsDisplay];
}
return self;
@@ -151,60 +140,58 @@ _CPDatePickerClockSeconds = 3;
- (void)_initHands
{
var middleHandImage = [_datePicker currentValueForThemeAttribute:@"middle-hand-image"],
hourHandImage = [_datePicker currentValueForThemeAttribute:@"hour-hand-image"],
minuteHandImage = [_datePicker currentValueForThemeAttribute:@"minute-hand-image"],
secondHandImage = [_datePicker currentValueForThemeAttribute:@"second-hand-image"];
// FIX: Using 'duplicate' prevents CPImageViews from stealing the DOM element from each other!
[_middleHandView setImage:[[_datePicker currentValueForThemeAttribute:@"middle-hand-image"] duplicate]];
[_hourHandView setImage:[[_datePicker currentValueForThemeAttribute:@"hour-hand-image"] duplicate]];
[_minuteHandView setImage:[[_datePicker currentValueForThemeAttribute:@"minute-hand-image"] duplicate]];
[_secondHandView setImage:[[_datePicker currentValueForThemeAttribute:@"second-hand-image"] duplicate]];
// If hand images are true CPImage, we have to duplicate them to avoid
// the multiple delegates bug when multiple clocks are displayed
var font = [_datePicker currentValueForThemeAttribute:@"clock-font"],
textColor = [_datePicker currentValueForThemeAttribute:@"clock-text-color"],
shadowCol = [_datePicker currentValueForThemeAttribute:@"clock-text-shadow-color"],
shadowOff = [_datePicker currentValueForThemeAttribute:@"clock-text-shadow-offset"];
if ([middleHandImage isKindOfClass:[CPImage class]])
middleHandImage = [middleHandImage duplicate];
if (font)
[_PMAMTextField setFont:font];
if ([hourHandImage isKindOfClass:[CPImage class]])
hourHandImage = [hourHandImage duplicate];
if (textColor)
[_PMAMTextField setTextColor:textColor];
if ([minuteHandImage isKindOfClass:[CPImage class]])
minuteHandImage = [minuteHandImage duplicate];
if (shadowCol)
[_PMAMTextField setTextShadowColor:shadowCol];
if ([secondHandImage isKindOfClass:[CPImage class]])
secondHandImage = [secondHandImage duplicate];
if (shadowOff)
[_PMAMTextField setTextShadowOffset:shadowOff];
[_middleHandLayer setImage:middleHandImage];
[_hourHandLayer setImage:hourHandImage];
[_minuteHandLayer setImage:minuteHandImage];
[_secondHandLayer setImage:secondHandImage];
var hoursFont = [_datePicker currentValueForThemeAttribute:@"clock-hours-font"],
hoursColor = [_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"],
drawsHours = [_datePicker currentValueForThemeAttribute:@"clock-draws-hours"];
[_hourHandLayer setNeedsDisplay];
[_middleHandLayer setNeedsDisplay];
[_secondHandLayer setNeedsDisplay];
[_minuteHandLayer setNeedsDisplay];
[_rootLayer setFont:[_datePicker currentValueForThemeAttribute:@"clock-hours-font"]];
[_rootLayer setTextColor:[_datePicker currentValueForThemeAttribute:@"clock-hours-text-color"]];
[_rootLayer setRadius:[_datePicker currentValueForThemeAttribute:@"clock-hours-radius"]];
[_rootLayer setNeedsDisplay];
for (var i = 0; i < 12; i++)
{
var label = _hourLabels[i];
[label setHidden:!drawsHours];
if (drawsHours) {
if (hoursFont) [label setFont:hoursFont];
if (hoursColor) [label setTextColor:hoursColor];
[label sizeToFit];
}
}
}
- (void)setDatePickerElements:(CPInteger)aDatePickerElements
{
_datePickerElements = aDatePickerElements;
// 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
[_secondHandView setHidden:!((_datePickerElements & CPHourMinuteSecondDatePickerElementFlag) == CPHourMinuteSecondDatePickerElementFlag)];
}
#pragma mark Layout methods
// MARK: Layout methods
- (void)layoutSubviews
{
// While tracking a hand, we don't want the whole thing to be relayouted at each mouse movement
[self _initHands];
if (_trackingHand)
return;
@@ -219,34 +206,68 @@ _CPDatePickerClockSeconds = 3;
_representedSeconds = dateValue.getSeconds();
_representedHourIsPM = (_representedHours > 11);
// Hours are expressed in 24 hours format, we need 12 hours format
_representedHours -= (_representedHourIsPM ? 12 : 0);
[self _updateHands];
var bounds = [self bounds],
centerX = bounds.size.width / 2.0,
centerY = bounds.size.height / 2.0;
// FIXME: Workaround. Seems that CALayer doesn't redraw without an event
[CALayer runLoopUpdateLayers];
[_PMAMTextField setStringValue:_representedHourIsPM ? @"PM" : @"AM"];
[_PMAMTextField sizeToFit];
[_PMAMTextField setFrameOrigin:CGPointMake(centerX - [_PMAMTextField frameSize].width / 2.0, centerY + 15.0)];
if ([_datePicker currentValueForThemeAttribute:@"clock-draws-hours"])
{
var radius = [_datePicker currentValueForThemeAttribute:@"clock-hours-radius"] || 50.0;
for (var i = 0, angle = 60.0; i < 12; i++, angle -= 30.0)
{
var label = _hourLabels[i],
size = [label frameSize],
x = centerX + radius * COS(angle * RADIANS) - size.width / 2.0,
y = centerY - radius * SIN(angle * RADIANS) - size.height / 2.0;
[label setFrameOrigin:CGPointMake(x, y)];
}
}
var centerView = function(view, size) {[view setFrame:CGRectMake(centerX - size.width / 2.0, centerY - size.height / 2.0, size.width, size.height)];
};
var hSize = [_datePicker currentValueForThemeAttribute:@"hour-hand-size"] || CGSizeMake(4, 64),
mSize = [_datePicker currentValueForThemeAttribute:@"minute-hand-size"] || CGSizeMake(4, 96),
sSize = [_datePicker currentValueForThemeAttribute:@"second-hand-size"] || CGSizeMake(4, 96),
midSize = [_datePicker currentValueForThemeAttribute:@"middle-hand-size"] || CGSizeMake(8, 8);
centerView(_hourHandView, hSize);
centerView(_minuteHandView, mSize);
centerView(_secondHandView, sSize);
centerView(_middleHandView, midSize);
[self _updateHands];
}
// Applies Pure CSS Transforms to rotate the elements natively in the browser
- (void)_rotateView:(CPView)view byAngle:(float)radians
{
#if PLATFORM(DOM)
var style = view._DOMElement.style;
style[CPBrowserStyleProperty("transformOrigin")] = "50% 50%";
style[CPBrowserStyleProperty("transform")] = "rotate(" + radians + "rad)";
#endif
}
- (void)_updateHands
{
var bounds = [self bounds];
_hourAngle = (360.0 * (_representedHours + _representedMinutes / 60.0) / 12.0) * RADIANS;
_minuteAngle = (360.0 * (_representedMinutes + _representedSeconds / 60.0) / 60.0) * RADIANS;
_secondAngle = (360.0 * _representedSeconds / 60.0) * RADIANS;
[_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];
[self _rotateView:_hourHandView byAngle:_hourAngle];
[self _rotateView:_minuteHandView byAngle:_minuteAngle];
[self _rotateView:_secondHandView byAngle:_secondAngle];
}
#pragma mark Accessors
// MARK: Accessors
- (void)setEnabled:(BOOL)shouldEnable
{
@@ -257,11 +278,39 @@ _CPDatePickerClockSeconds = 3;
_isEnabled = shouldEnable;
[self _initHands];
[self setNeedsLayout];
}
#pragma mark Mouse actions
// MARK: Mouse actions
// Since we rotate using Pure CSS, Cappuccino's `convertPoint:` doesn't know about it.
// So we use standard Trigonometry to perfectly hit-test the rotated hands!
- (BOOL)_hitTestHandWithSize:(CGSize)size angle:(float)radians atPoint:(CGPoint)aPoint
{
var bounds = [self bounds],
centerX = bounds.size.width / 2.0,
centerY = bounds.size.height / 2.0;
// 1. Move point to center
var tx = aPoint.x - centerX,
ty = aPoint.y - centerY;
// 2. Rotate point backwards by the angle of the hand
var cosA = COS(-radians),
sinA = SIN(-radians),
rx = tx * cosA - ty * sinA,
ry = tx * sinA + ty * cosA;
// 3. Test if point is within the unrotated hand's rectangle
// The visual needle is in the top half of the hand's box (y from -h/2 to 0)
var w2 = size.width / 2.0,
h2 = size.height / 2.0;
if (rx >= -w2 && rx <= w2 && ry >= -h2 && ry <= 0)
return YES;
return NO;
}
- (void)mouseDown:(CPEvent)anEvent
{
@@ -270,25 +319,29 @@ _CPDatePickerClockSeconds = 3;
var currentLocation = [self convertPoint:[anEvent locationInWindow] fromView:nil];
if ([_secondHandLayer handIsHitAtPoint:currentLocation])
var sSize = [_datePicker currentValueForThemeAttribute:@"second-hand-size"] || CGSizeMake(4, 96),
mSize = [_datePicker currentValueForThemeAttribute:@"minute-hand-size"] || CGSizeMake(4, 96),
hSize = [_datePicker currentValueForThemeAttribute:@"hour-hand-size"] || CGSizeMake(4, 64);
if (![_secondHandView isHidden] && [self _hitTestHandWithSize:sSize angle:_secondAngle atPoint:currentLocation])
{
_currentHandLayer = _secondHandLayer;
_currentHandView = _secondHandView;
_currentHand = _CPDatePickerClockSeconds;
_currentRepresentedValue = _representedSeconds;
_currentValueShift = 0;
_numberOfUnits = 60;
}
else if ([_minuteHandLayer handIsHitAtPoint:currentLocation])
else if (![_minuteHandView isHidden] && [self _hitTestHandWithSize:mSize angle:_minuteAngle atPoint:currentLocation])
{
_currentHandLayer = _minuteHandLayer;
_currentHandView = _minuteHandView;
_currentHand = _CPDatePickerClockMinutes;
_currentRepresentedValue = _representedMinutes;
_currentValueShift = _representedSeconds / 60;
_numberOfUnits = 60;
}
else if ([_hourHandLayer handIsHitAtPoint:currentLocation])
else if (![_hourHandView isHidden] && [self _hitTestHandWithSize:hSize angle:_hourAngle atPoint:currentLocation])
{
_currentHandLayer = _hourHandLayer;
_currentHandView = _hourHandView;
_currentHand = _CPDatePickerClockHours;
_currentRepresentedValue = _representedHours;
_currentValueShift = _representedMinutes / 60;
@@ -296,14 +349,14 @@ _CPDatePickerClockSeconds = 3;
}
else
{
_currentHandLayer = nil;
_currentHandView = nil;
_currentHand = CPNotFound;
_currentRepresentedValue = CPNotFound;
_currentValueShift = CPNotFound;
_numberOfUnits = CPNotFound;
}
if (_currentHandLayer)
if (_currentHandView)
[self trackMouse:anEvent];
}
@@ -315,13 +368,12 @@ _CPDatePickerClockSeconds = 3;
- (BOOL)startTrackingAt:(CGPoint)aPoint
{
_trackingHand = YES;
return YES;
}
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
{
var dx = aPoint.x -_bounds.size.width / 2,
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;
@@ -343,14 +395,10 @@ _CPDatePickerClockSeconds = 3;
_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;
@@ -384,29 +432,26 @@ _CPDatePickerClockSeconds = 3;
#if PLATFORM(DOM)
_datePicker._invokedByUserEvent = YES;
#endif
[_datePicker _setDateValue:dateValue timeInterval:[_datePicker timeInterval]];
[_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;
@@ -424,188 +469,3 @@ _CPDatePickerClockSeconds = 3;
}
@end
#pragma mark -
@implementation HandLayer : CALayer
{
CPImage _image;
HandImageLayer _imageLayer;
float _rotationRadians;
}
#pragma mark Init methods
- (id)initWithSize:(CGSize)aSize
{
if (self = [super init])
{
_imageLayer = [HandImageLayer layer];
_rotationRadians = 0;
[_imageLayer setDelegate:self];
[_imageLayer setBounds:CGRectMake(0.0, 0.0, aSize.width, aSize.height)];
[self addSublayer:_imageLayer];
}
return self;
}
#pragma mark Setter Getter methods
/*!
Set the bounds of the layer. The imageLayer will be at the center of this bounds.
*/
- (void)setBounds:(CGRect)aRect
{
[super setBounds:aRect];
[_imageLayer setPosition:CGPointMake(CGRectGetMidX(aRect), CGRectGetMidY(aRect))];
}
- (void)setImage:(CPImage)anImage
{
if (_image === anImage)
return;
if ([anImage isKindOfClass:[_CPCibCustomResource class]])
_image = [anImage imageFromCoder:nil];
else
_image = anImage;
[_imageLayer setNeedsDisplay];
}
- (void)setRotationRadians:(float)radians
{
if (_rotationRadians === radians)
return;
_rotationRadians = radians;
[_imageLayer setAffineTransform:CGAffineTransformScale(
CGAffineTransformMakeRotation(_rotationRadians),
1.0, 1.0)];
}
- (void)imageDidLoad:(CPImage)anImage
{
[_imageLayer setNeedsDisplay];
}
- (void)drawLayer:(CALayer)aLayer inContext:(CGContext)aContext
{
if ([_image loadStatus] != CPImageLoadStatusCompleted)
[_image setDelegate:self];
else
CGContextDrawImage(aContext, [aLayer bounds], _image);
}
- (BOOL)handIsHitAtPoint:(CGPoint)aPoint
{
return (!_isHidden && [_imageLayer hitTest:aPoint] === _imageLayer);
}
- (void)setNeedsDisplay
{
[super setNeedsDisplay];
[_imageLayer setNeedsDisplay];
}
@end
#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
+7 -7
View File
@@ -41,7 +41,7 @@
}
#pragma mark Init methods
// MARK: Init methods
/*! Create a new instance of _CPDatePickerDayView
@param aFrame
@@ -123,8 +123,8 @@
}
#pragma mark -
#pragma mark Theme methods
// MARK: -
// MARK: Theme methods
/*! Set a theme
*/
@@ -143,8 +143,8 @@
}
#pragma mark -
#pragma mark Getter methods
// MARK: -
// MARK: Getter methods
/*! Select the tile
*/
@@ -205,8 +205,8 @@
}
#pragma mark -
#pragma mark Layout methods
// MARK: -
// MARK: Layout methods
/*! Layout the subviews
*/
@@ -27,12 +27,7 @@
CPDatePickerElementTextFieldBecomeFirstResponder = @"CPDatePickerElementTextFieldBecomeFirstResponder";
CPDatePickerElementTextFieldAMPMChangedNotification = @"CPDatePickerElementTextFieldAMPMChangedNotification";
var CPZeroKeyCode = 48,
CPNineKeyCode = 57,
CPMajAKeyCode = 65,
CPMajPKeyCode = 80,
CPAKeyCode = 97,
CPPKeyCode = 112;
// Removed hardcoded KeyCodes (CPZeroKeyCode, etc) as they are unreliable across browsers/layouts.
CPMonthDateType = 0;
CPDayDateType = 1;
@@ -190,23 +185,38 @@ CPAMPMDateType = 6;
*/
- (void)setValueForKeyEvent:(CPEvent)anEvent
{
var keyCode = [anEvent keyCode];
var keyCode = [anEvent keyCode],
characters = [anEvent characters];
if (keyCode != CPDeleteKeyCode && keyCode != CPDeleteForwardKeyCode && keyCode < CPZeroKeyCode || keyCode > CPNineKeyCode)
// Check if the event is a deletion
var isDelete = (keyCode === CPDeleteKeyCode || keyCode === CPDeleteForwardKeyCode);
// Check if the event is a numeric input.
// By testing the character string against a regex, we support num-pads and
// international keyboards correctly, rather than relying on keyCode ranges.
var isNumeric = (characters && [characters length] > 0 && /^[0-9]$/.test(characters));
// If it is neither a delete command nor a digit, we ignore it.
if (!isDelete && !isNumeric)
return;
var newValue = [self stringValue].replace(/\s/g, ''),
length = [newValue length],
eventKeyValue = parseInt([anEvent characters]).toString();
length = [newValue length];
if (keyCode == CPDeleteKeyCode || keyCode == CPDeleteForwardKeyCode)
if (isDelete)
{
[_timerEdition invalidate];
_timerEdition = nil;
newValue = [newValue substringToIndex:(length - 1)];
// Ensure we don't substring if length is 0
if (length > 0)
newValue = [newValue substringToIndex:(length - 1)];
}
else
{
// Since isNumeric is true, characters is a valid digit string
var eventKeyValue = characters;
if (!_timerEdition)
{
_timerEdition = [CPTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(_timerKeyEvent:) userInfo:nil repeats:NO];
@@ -227,7 +237,12 @@ CPAMPMDateType = 6;
}
}
if (parseInt(newValue) > [self _maxNumberWithMaxDate] || ([_datePicker _isAmericanFormat] && _dateType == CPHourDateType && parseInt(newValue) > 12))
// Safety check for NaN before comparison
var numericValue = parseInt(newValue);
if (isNaN(numericValue))
numericValue = 0;
if (numericValue > [self _maxNumberWithMaxDate] || ([_datePicker _isAmericanFormat] && _dateType == CPHourDateType && numericValue > 12))
return;
_firstEvent = NO;
@@ -385,6 +400,18 @@ CPAMPMDateType = 6;
return;
}
// if we enter a day that is too high for the current month
// we need to increase the month by one
// if we do not do this, the user input would be silently reset
// very poor user experience
if (parseInt(anObjectValue, 10) > [dateValue _daysInMonth])
{
[_datePickerElementView._textFieldMonth setIntValue:(dateValue.getMonth() + 2)];
[super setObjectValue:objectValue];
return;
}
[super setObjectValue:objectValue];
break;
@@ -454,8 +481,8 @@ CPAMPMDateType = 6;
}
#pragma mark -
#pragma mark Mouse event
// MARK: -
// MARK: Mouse event
/*! Mouse down event. Launch a notification to notif the new first responder textField
*/
@@ -469,8 +496,8 @@ CPAMPMDateType = 6;
}
#pragma mark -
#pragma mark Theme functions
// MARK: -
// MARK: Theme functions
/*! Set the theme CPThemeStateSelected
*/
@@ -490,8 +517,8 @@ CPAMPMDateType = 6;
}
#pragma mark -
#pragma mark Override
// MARK: -
// MARK: Override
/*!
We override this method to get all the time the good width
@@ -557,7 +584,7 @@ CPAMPMDateType = 6;
@end
#pragma mark -
// MARK: -
@implementation _CPDatePickerElementSeparator : CPTextField
+15 -15
View File
@@ -50,7 +50,7 @@
}
#pragma mark Init
// MARK: Init
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
{
@@ -141,8 +141,8 @@
}
#pragma mark -
#pragma mark Responder methods
// MARK: -
// MARK: Responder methods
/*! @ignore */
- (BOOL)acceptsFirstResponder
@@ -154,8 +154,8 @@
}
#pragma mark -
#pragma mark Override observers
// MARK: -
// MARK: Override observers
- (void)_removeObservers
{
@@ -177,8 +177,8 @@
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_datePickerElementTextFieldAMPMChangedNotification:) name:CPDatePickerElementTextFieldAMPMChangedNotification object:_textFieldPMAM];
}
#pragma mark -
#pragma mark Mouse event
// MARK: -
// MARK: Mouse event
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
{
@@ -229,8 +229,8 @@
return nil;
}
#pragma mark -
#pragma mark Setter Getter methods
// MARK: -
// MARK: Setter Getter methods
/*! Set the value of the textFields
@param aDateValue the value
@@ -378,8 +378,8 @@
}
#pragma mark -
#pragma mark Notification methods
// MARK: -
// MARK: Notification methods
/*! Called when changing AM or PM
@param aNotification
@@ -413,8 +413,8 @@
}
#pragma mark -
#pragma mark Layout methods
// MARK: -
// MARK: Layout methods
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
{
@@ -886,8 +886,8 @@
}
#pragma mark -
#pragma mark Responder methods
// MARK: -
// MARK: Responder methods
- (void)_updateResponderTextField
{
@@ -50,7 +50,7 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"
}
#pragma mark Init methods
// MARK: Init methods
/*! Init a new instance of _CPDatePickerHeaderView
@param aFrame
@@ -153,8 +153,8 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"
}
#pragma mark -
#pragma mark Getter Setter methods
// MARK: -
// MARK: Getter Setter methods
/*! Return the day names depending on the CPLocale of the datePicker
@return an array
@@ -242,8 +242,8 @@ var CPShortWeekDayNameArrayEn = [@"Mo", @"Tu", @"We", @"Th", @"Fr", @"Sa", @"Su"
}
#pragma mark -
#pragma mark Layout methods
// MARK: -
// MARK: Layout methods
/*! Layout the subviews
*/
+15 -15
View File
@@ -46,7 +46,7 @@
}
#pragma mark Init methods
// MARK: Init methods
/*! Init a _CPDatePickerMonthView
@param aFrame
@@ -77,8 +77,8 @@
}
#pragma mark -
#pragma mark Getter Setter methods
// MARK: -
// MARK: Getter Setter methods
/*! Set the monthDate of the component
@param aDate
@@ -194,8 +194,8 @@
return tileIndex;
}
#pragma mark -
#pragma mark Reload data
// MARK: -
// MARK: Reload data
/*! Reload the data
*/
@@ -235,8 +235,8 @@
}
#pragma mark -
#pragma mark Select methods
// MARK: -
// MARK: Select methods
/*! Select one date or several date depending of the giving interval
@param aStartDate
@@ -297,8 +297,8 @@
}
}
#pragma mark -
#pragma mark Layout methods
// MARK: -
// MARK: Layout methods
/*! Tile the view
*/
@@ -438,8 +438,8 @@
}
#pragma mark -
#pragma mark Mouse event
// MARK: -
// MARK: Mouse event
/*! Mouse down event
*/
@@ -596,8 +596,8 @@
}
#pragma mark -
#pragma mark Timer
// MARK: -
// MARK: Timer
- (void)_timerNextMonthEvent:(CPEvent)anEvent
{
@@ -618,8 +618,8 @@
}
#pragma mark -
#pragma mark Date methods
// MARK: -
// MARK: Date methods
- (CPDate)_hoursMinutesSecondsFromDatePickerForDate:(CPDate)aDate
{
+178 -47
View File
@@ -48,13 +48,6 @@
@global CPYearMonthDayDatePickerElementFlag
@global CPEraDatePickerElementFlag
var CPZeroKeyCode = 48,
CPNineKeyCode = 57,
CPMajAKeyCode = 65,
CPMajPKeyCode = 80,
CPAKeyCode = 97,
CPPKeyCode = 112;
// This class is used to represente the datePicker with the CPTextFieldAndStepperDatePickerStyle/CPTextFieldDatePickerStyle mode
@implementation _CPDatePickerTextField : CPControl
{
@@ -69,7 +62,7 @@ var CPZeroKeyCode = 48,
}
#pragma mark Init
// MARK: Init
- (id)initWithFrame:(CGRect)aFrame withDatePicker:(CPDatePicker)aDatePicker
{
@@ -110,8 +103,8 @@ var CPZeroKeyCode = 48,
}
#pragma mark -
#pragma mark Override responder methods
// MARK: -
// MARK: Override responder methods
- (BOOL)becomeFirstResponder
{
@@ -131,7 +124,7 @@ var CPZeroKeyCode = 48,
// Don't forget to unbind, otherwise several steppers will increase or decrease
[_currentTextField unbind:@"objectValue"];
[_currentTextField makeDeselectable];
_currentTextField = nil
_currentTextField = nil;
// This is usefull when clicking on the stepper when the datePicker is not selected
[_stepper setObjectValue:0];
@@ -145,8 +138,8 @@ var CPZeroKeyCode = 48,
}
#pragma mark -
#pragma mark Setter Getter methods
// MARK: -
// MARK: Setter Getter methods
/*! Set the value of the control
@param aDateValue
@@ -195,8 +188,8 @@ var CPZeroKeyCode = 48,
[self setNeedsLayout];
}
#pragma mark -
#pragma mark Notification methods
// MARK: -
// MARK: Notification methods
/*! This is called to when the user just changed the selected textField
@param aNotification
@@ -214,20 +207,37 @@ var CPZeroKeyCode = 48,
}
#pragma mark -
#pragma mark SelectTextField action
// MARK: -
// MARK: SelectTextField action
- (void)_selectTextFieldWithFlags:(unsigned)flags
{
[_datePickerElementView _updateResponderTextField];
// We select the firstTextField when the datePicker becomes firstResponder if _currentTextField is null. It can be null just when using tab
if (!_currentTextField)
{
var targetField = nil;
if (flags & CPShiftKeyMask)
[self _selectTextField:_lastTextField];
{
// Try last field; if hidden, find previous visible
if ([_lastTextField isHidden])
targetField = [self _previousVisibleTextFieldFrom:_lastTextField];
else
targetField = _lastTextField;
}
else
[self _selectTextField:_firstTextField];
{
// Try first field; if hidden, find next visible
if ([_firstTextField isHidden])
targetField = [self _nextVisibleTextFieldFrom:_firstTextField];
else
targetField = _firstTextField;
}
// Only select if we actually found a valid visible field
if (targetField)
[self _selectTextField:targetField];
}
}
@@ -263,8 +273,8 @@ var CPZeroKeyCode = 48,
}
#pragma mark -
#pragma mark Events
// MARK: -
// MARK: Events
/*! Called when the user click on the stepper
*/
@@ -334,23 +344,92 @@ var CPZeroKeyCode = 48,
return [super performKeyEquivalent:anEvent];
}
- (_CPDatePickerElementTextField)_nextVisibleTextFieldFrom:(_CPDatePickerElementTextField)aTextField
{
var runner = [aTextField nextTextField];
// If we wrapped back to the start immediately, or runner is nil, we are done.
if (!runner || runner == _firstTextField)
return nil;
// Traverse hidden fields
while (runner && [runner isHidden])
{
// If we hit the absolute last field and it is hidden, we've reached the end.
if (runner == _lastTextField)
return nil;
runner = [runner nextTextField];
// Safety: if we wrapped back to the start inside the loop
if (runner == _firstTextField)
return nil;
}
return runner;
}
- (_CPDatePickerElementTextField)_previousVisibleTextFieldFrom:(_CPDatePickerElementTextField)aTextField
{
var runner = [aTextField previousTextField];
// If we wrapped back to the end immediately, or runner is nil, we are done.
if (!runner || runner == _lastTextField)
return nil;
// Traverse hidden fields
while (runner && [runner isHidden])
{
// If we hit the absolute first field and it is hidden, we've reached the start.
if (runner == _firstTextField)
return nil;
runner = [runner previousTextField];
// Safety: if we wrapped back to the end inside the loop
if (runner == _lastTextField)
return nil;
}
return runner;
}
- (void)insertTab:(id)sender
{
if (!_currentTextField)
return;
if (_currentTextField == _lastTextField)
[[self window] selectNextKeyView:self];
// Ensure boundaries are up to date
[_datePickerElementView _updateResponderTextField];
var nextField = [self _nextVisibleTextFieldFrom:_currentTextField];
if (nextField)
{
[self _selectTextField:nextField];
}
else
[self moveRight:sender];
}
{
// We reached the visual end. Manually find the next external view.
// We cannot rely on [[self window] selectNextKeyView:self] because it might
// loop back into our own internal fields or select 'self' which refuses focus.
var nextView = [_currentTextField nextValidKeyView];
- (void)moveRight:(id)sender
{
if (!_currentTextField)
return;
// Skip any view that is part of this control (descendant)
while (nextView && [nextView isDescendantOf:self])
{
// If we looped back to the current field, we are trapped in a closed loop with no exit.
if (nextView == _currentTextField)
{
nextView = nil;
break;
}
nextView = [nextView nextValidKeyView];
}
[self _selectTextField:[_currentTextField nextTextField]];
if (nextView)
[[self window] makeFirstResponder:nextView];
}
}
- (void)insertBacktab:(id)sender
@@ -358,10 +437,47 @@ var CPZeroKeyCode = 48,
if (!_currentTextField)
return;
if (_currentTextField == _firstTextField)
[[self window] selectPreviousKeyView:self];
[_datePickerElementView _updateResponderTextField];
var prevField = [self _previousVisibleTextFieldFrom:_currentTextField];
if (prevField)
{
[self _selectTextField:prevField];
}
else
[self moveLeft:sender];
{
// We reached the visual start. Manually find the previous external view.
var prevView = [_currentTextField previousValidKeyView];
// Skip any view that is part of this control
while (prevView && [prevView isDescendantOf:self])
{
if (prevView == _currentTextField)
{
prevView = nil;
break;
}
prevView = [prevView previousValidKeyView];
}
if (prevView)
[[self window] makeFirstResponder:prevView];
}
}
- (void)moveRight:(id)sender
{
if (!_currentTextField)
return;
[_datePickerElementView _updateResponderTextField];
// Use the helper to skip hidden fields
var nextField = [self _nextVisibleTextFieldFrom:_currentTextField];
if (nextField)
[self _selectTextField:nextField];
}
- (void)moveLeft:(id)sender
@@ -369,7 +485,13 @@ var CPZeroKeyCode = 48,
if (!_currentTextField)
return;
[self _selectTextField:[_currentTextField previousTextField]];
[_datePickerElementView _updateResponderTextField];
// Use the helper to skip hidden fields to be safe
var prevField = [self _previousVisibleTextFieldFrom:_currentTextField];
if (prevField)
[self _selectTextField:prevField];
}
- (void)moveDown:(id)sender
@@ -401,7 +523,7 @@ var CPZeroKeyCode = 48,
}
/*! KeyDown event
We just care care about the event A/P and every numbers
We just care about the event A/P and every numbers
*/
- (void)keyDown:(CPEvent)anEvent
{
@@ -410,24 +532,33 @@ var CPZeroKeyCode = 48,
[self interpretKeyEvents:[anEvent]];
if ([_datePicker _isAmericanFormat] && [_currentTextField dateType] == CPAMPMDateType && ([anEvent keyCode] == CPAKeyCode || [anEvent keyCode] == CPPKeyCode || [anEvent keyCode] == CPMajAKeyCode || [anEvent keyCode] == CPMajPKeyCode))
var characters = [anEvent characters];
if ([_datePicker _isAmericanFormat] && [_currentTextField dateType] == CPAMPMDateType && [characters length] > 0)
{
if ([anEvent keyCode] == CPAKeyCode || [anEvent keyCode] == CPMajAKeyCode)
var charUpper = [characters uppercaseString];
if (charUpper === "A")
{
[_currentTextField setStringValue:@"AM"];
else
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldAMPMChangedNotification object:_currentTextField userInfo:nil];
return;
}
else if (charUpper === "P")
{
[_currentTextField setStringValue:@"PM"];
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldAMPMChangedNotification object:_currentTextField userInfo:nil];
return;
[[CPNotificationCenter defaultCenter] postNotificationName:CPDatePickerElementTextFieldAMPMChangedNotification object:_currentTextField userInfo:nil];
return;
}
}
// Pass the event down to the specific field (which handles numeric input validation via regex)
[_currentTextField setValueForKeyEvent:anEvent];
}
#pragma mark -
#pragma mark Layout methods
// MARK: -
// MARK: Layout methods
/*! Layout the subviews
*/
@@ -476,8 +607,8 @@ var CPZeroKeyCode = 48,
}
#pragma mark -
#pragma mark Override observers
// MARK: -
// MARK: Override observers
- (void)_removeObservers
{
+5 -2
View File
@@ -871,10 +871,13 @@ var CPDocumentUntitledCount = 0;
{
var theDelegate = context.delegate;
if (aDocument === self && shouldClose)
// Only close the document explicitly if there is NO delegate to handle the action.
// If a delegate exists (e.g., the CPWindow), it is responsible for performing the close
// upon receiving the callback below. Calling [self close] here would cause a double-close.
if (aDocument === self && shouldClose && theDelegate == nil)
[self close];
if (theDelegate != null)
if (theDelegate)
theDelegate.isa.objj_msgSend3(theDelegate, context.selector, aDocument, shouldClose, context.context);
}
+56 -2
View File
@@ -26,6 +26,7 @@
@import "CPPasteboard.j"
@import "CPView.j"
@import "CPWindow_Constants.j"
@import "CPViewAnimation.j"
@class CPWindow // This file is imported by CPWindow.j
@class _CPDOMDataTransferPasteboard
@@ -132,6 +133,10 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
unsigned _dragOperation;
CPTimer _draggingUpdateTimer;
// Animation State
CGPoint _pendingEndLocation;
CPDragOperation _pendingEndOperation;
}
/*
@@ -261,10 +266,19 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
{
var contentView = [scrollView contentView],
bounds = [contentView bounds],
insetBounds = CGRectInset(bounds, 30, 30),
eventLocation = [contentView convertPoint:_draggingLocation fromView:nil],
deltaX = 0,
deltaY = 0;
deltaY = 0,
insetSize = 30;
if ([contentView respondsToSelector:@selector(documentView)] &&
[[contentView documentView] respondsToSelector:@selector(rowHeight)])
{
// Adjust inset bounds based on CPTableView row height
insetSize = MAX(insetSize, [[contentView documentView] rowHeight]);
}
var insetBounds = CGRectInset(bounds, insetSize, insetSize);
if (!CGRectContainsPoint(insetBounds, eventLocation))
{
@@ -316,6 +330,46 @@ var CPDraggingSource_draggedImage_movedTo_ = 1 << 0,
[_draggingUpdateTimer invalidate];
_draggingUpdateTimer = nil;
// Check if we should slide back (drag failed + slideBack requested)
if (![CPPlatform supportsDragAndDrop] && _shouldSlideBack && anOperation === CPDragOperationNone)
{
// Store state to finalize drag after animation completes
_pendingEndLocation = aLocation;
_pendingEndOperation = anOperation;
var currentFrame = [_draggedWindow frame],
targetFrame = CGRectMake(_startDragLocation.x, _startDragLocation.y, currentFrame.size.width, currentFrame.size.height);
// We use CPViewAnimation. Even though _draggedWindow is a CPWindow,
// CPViewAnimation supports targets that respond to setFrame: (like NSViewAnimation does for NSWindow).
var animation = [[CPViewAnimation alloc] initWithViewAnimations:[
[CPDictionary dictionaryWithObjects:[_draggedWindow, currentFrame, targetFrame]
forKeys:[CPViewAnimationTargetKey, CPViewAnimationStartFrameKey, CPViewAnimationEndFrameKey]]
]];
[animation setAnimationCurve:CPAnimationEaseOut];
[animation setDuration:0.25];
[animation setDelegate:self];
[animation startAnimation];
return;
}
[self _performFinalCleanupWithLocation:aLocation operation:anOperation];
}
- (void)animationDidEnd:(CPAnimation)anAnimation
{
[self _performFinalCleanupWithLocation:_pendingEndLocation operation:_pendingEndOperation];
}
- (void)animationDidStop:(CPAnimation)anAnimation
{
[self _performFinalCleanupWithLocation:_pendingEndLocation operation:_pendingEndOperation];
}
- (void)_performFinalCleanupWithLocation:(CGPoint)aLocation operation:(CPDragOperation)anOperation
{
[_draggedView removeFromSuperview];
if (![CPPlatform supportsDragAndDrop])
+31 -3
View File
@@ -70,6 +70,7 @@ var _CPEventPeriodicEventPeriod = 0,
BOOL _isARepeat;
unsigned _keyCode;
DOMEvent _DOMEvent;
BOOL _isActionKey;
int _data1;
int _data2;
short _subtype;
@@ -110,17 +111,28 @@ var _CPEventPeriodicEventPeriod = 0,
@param unmodCharacters the string of keys pressed without the presence of any modifiers other than Shift
@param repeatKey \c YES if this is caused by the system repeat as opposed to the user pressing the key again
@param code a number associated with the keyboard key of this event
@param isAnActionKey a BOOL indicating whether this key is an action key (e.g. a function key)
@throws CPInternalInconsistencyException if \c anEventType is not a CPKeyDown,
CPKeyUp or CPFlagsChanged
@return the keyboard event
*/
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code];
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:isAnActionKey];
}
// for backwards compatibility only
+ (CPEvent)keyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)repeatKey keyCode:(unsigned short)code
{
return [[self alloc] _initKeyEventWithType:anEventType location:aPoint modifierFlags:modifierFlags
timestamp:aTimestamp windowNumber:aWindowNumber context:aGraphicsContext
characters:characters charactersIgnoringModifiers:unmodCharacters isARepeat:repeatKey keyCode:code isActionKey:NO];
}
/*!
@@ -252,7 +264,7 @@ var _CPEventPeriodicEventPeriod = 0,
/* @ignore */
- (id)_initKeyEventWithType:(CPEventType)anEventType location:(CGPoint)aPoint modifierFlags:(unsigned int)modifierFlags
timestamp:(CPTimeInterval)aTimestamp windowNumber:(int)aWindowNumber context:(CPGraphicsContext)aGraphicsContext
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code
characters:(CPString)characters charactersIgnoringModifiers:(CPString)unmodCharacters isARepeat:(BOOL)isARepeat keyCode:(unsigned short)code isActionKey:(BOOL)isAnActionKey
{
if (self = [self _initWithType:anEventType])
{
@@ -264,6 +276,7 @@ var _CPEventPeriodicEventPeriod = 0,
_charactersIgnoringModifiers = unmodCharacters;
_isARepeat = isARepeat;
_keyCode = code;
_isActionKey = isAnActionKey;
_windowNumber = aWindowNumber;
}
@@ -571,6 +584,21 @@ var _CPEventPeriodicEventPeriod = 0,
return !firstResponderIsText;
}
- (BOOL)_isActionOrCommandEvent
{
// This method is now platform-agnostic. It checks for abstract properties
// of the event, including the _isActionKey flag that was set at creation time.
return (
// Is it a command shortcut?
(_modifierFlags & (CPCommandKeyMask | CPControlKeyMask)) ||
// Is it a key that doesn't produce a character?
([_characters length] === 0) ||
// Was it identified as an action key by the platform-specific layer?
_isActionKey
);
}
/*!
Return YES if this event is a part of processing a browser controlled cut or paste event
where the browser will go ahead and do the work of cutting or pasting within the input
+17 -1
View File
@@ -220,7 +220,7 @@ following:
if (systemFontStyle)
{
// Yes, so install it in the DOM Style element
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPathInString:systemFontStyle];
document.getElementsByTagName("STYLE")[0].innerHTML += "\n" + [aTheme setCSSResourcesPath:systemFontStyle];
}
}
@@ -464,6 +464,22 @@ following:
return _CPRealFontSize(_size);
}
/*!
Returns the font size. Cocoa/AppKit compatibility alias for -size.
*/
- (float)pointSize
{
return [self size];
}
/*!
Returns the font name. Cocoa/AppKit compatibility alias for -familyName.
*/
- (CPString)fontName
{
return [self familyName];
}
/*!
Returns the font as a CSS string
*/
+5 -1
View File
@@ -208,7 +208,11 @@ CPRemoveTraitFontAction = 7;
- (@action)addFontTrait:(id)sender
{
var tag = [sender tag];
var tag = sender;
if ([sender respondsToSelector:@selector(tag)])
tag = [sender tag];
_activeChange = tag == nil ? @{} : @{ @"addTraits": tag };
_fontAction = CPAddTraitFontAction;
+45 -6
View File
@@ -31,6 +31,7 @@
@import "CGGeometry.j"
@import "CPCompatibility.j"
@import "CPGraphicsContext.j"
@class CPColor
@global document
@@ -516,8 +517,8 @@ function CPAppKitImage(aFilename, aSize)
@end
#pragma mark -
#pragma mark CSS Theming
// MARK: -
// MARK: CSS Theming
// The code below adds support for CSS theming with 100% compatibility with current theming system.
// The idea is to extend CPImage (and CPColor) with CSS components and adapt low level UI components to
@@ -737,7 +738,7 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
CPImageCSSBeforeDictionaryKey = @"CPImageCSSBeforeDictionaryKey",
CPImageCSSAfterDictionaryKey = @"CPImageCSSAfterDictionaryKey";
#pragma mark -
// MARK: -
@implementation CPImage (CPCoding)
@@ -774,7 +775,45 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
@end
#pragma mark -
// MARK: -
// MARK: Drawing
@implementation CPImage (Drawing)
- (void)drawAtPoint:(CGPoint)point fromRect:(CPRect)fromRect operation:(CGBlendMode)op fraction:(float)delta
{
if (_loadStatus !== CPImageLoadStatusCompleted)
return;
var context = [CPGraphicsContext currentContext].graphicsPort;
if (!context)
return;
CGContextSaveGState(context);
CGContextSetBlendMode(context, op);
CGContextSetAlpha(context, delta);
context.drawImage(
_image,
fromRect.origin.x,
fromRect.origin.y,
fromRect.size.width,
fromRect.size.height,
point.x,
point.y,
fromRect.size.width,
fromRect.size.height
);
CGContextRestoreGState(context);
}
@end
// MARK: -
@implementation _CPMaterialIconImage : CPImage
{
@@ -934,7 +973,7 @@ var CPImageCSSDictionaryKey = @"CPImageCSSDictionaryKey",
@end
#pragma mark -
// MARK: -
@implementation CPThreePartImage : CPObject
{
@@ -1077,7 +1116,7 @@ var CPNinePartImageImageSlicesKey = @"CPNinePartImageImageSlicesKey";
@end
#pragma mark -
// MARK: -
@implementation CPImage (Duplication)
+8
View File
@@ -237,6 +237,8 @@ var CPImageViewEmptyPlaceholderImage = nil;
if (_hasShadow)
{
[self setClipsToBounds:NO];
_shadowView = [[CPShadowView alloc] initWithFrame:[self bounds]];
[self addSubview:_shadowView];
@@ -456,6 +458,12 @@ var CPImageViewEmptyPlaceholderImage = nil;
styleNode:_cssStyleNode
previousState:@ref(_cssStylePreviousState)];
}
if ([image isCSSBased])
{
_DOMImageElement.style.width = _DOMImageElement.width + 'px';
_DOMImageElement.style.height = _DOMImageElement.height + 'px';
}
#endif
_imageRect = CGRectMake(x, y, width, height);
+1
View File
@@ -30,6 +30,7 @@ CPStandardKeyBindings = {
@"@.": @"cancelOperation:",
@"@a": @"selectAll:",
@"@~$v": @"pasteAsPlainText:",
@"^a": @"moveToBeginningOfParagraph:",
@"^$a": @"moveToBeginningOfParagraphAndModifySelection:",
@"^b": @"moveBackward:",
+5 -1
View File
@@ -208,7 +208,11 @@ var CPBindingOperationAnd = 0,
options = [_info objectForKey:CPOptionsKey],
newValue = [destination valueForKeyPath:keyPath];
if (CPIsControllerMarker(newValue))
// give nil values the chance to be transformed to true via CPNegateBoolean (issue #1986)
if ((newValue == nil || CPIsControllerMarker(newValue)) && [options objectForKey:CPValueTransformerNameBindingOption] === CPNegateBooleanTransformerName)
[self setValue:[self transformValue:NO withOptions:options] forBinding:theBinding];
else if (CPIsControllerMarker(newValue))
{
[self raiseIfNotApplicable:newValue forKeyPath:keyPath options:options];
+26 -22
View File
@@ -88,43 +88,61 @@ CPRatingLevelIndicatorStyle = 3;
- (void)layoutSubviews
{
// 1. Calculate the Theme State
// We explicitly check the window style mask to see if we are in a HUD.
// This allows us to pass CPThemeStateHUD to the theme system, even if the control
// itself isn't explicitly set to HUD, inheriting the style from the window.
var themeState = [self themeState];
if ([[self window] styleMask] & CPHUDBackgroundWindowMask)
themeState = themeState.and(CPThemeStateHUD);
// 2. Layout the Bezel
var bezelView = [self layoutEphemeralSubviewNamed:"bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
// TODO Make themable.
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color"]];
[bezelView setBackgroundColor:[self valueForThemeAttribute:@"bezel-color" inState:themeState]];
var segmentCount = _maxValue - _minValue;
if (segmentCount <= 0)
return;
var filledColor = [self valueForThemeAttribute:@"color-normal"],
// 3. Determine the Color based on Value and Thresholds
// We pass 'themeState' here. If it contains CPThemeStateHUD, the ThemeDescriptor
// will return the monochrome color for normal/warning/critical.
// If not, it returns Green/Yellow/Red.
var filledColor = [self valueForThemeAttribute:@"color-normal" inState:themeState],
value = [self doubleValue];
if (_warningValue < _criticalValue)
{
// Standard ascending scale (e.g. Volume)
if (value >= _criticalValue)
filledColor = [self valueForThemeAttribute:@"color-critical"];
filledColor = [self valueForThemeAttribute:@"color-critical" inState:themeState];
else if (value >= _warningValue)
filledColor = [self valueForThemeAttribute:@"color-warning"];
filledColor = [self valueForThemeAttribute:@"color-warning" inState:themeState];
}
else
{
// Descending scale (e.g. Battery Life)
if (value <= _criticalValue)
filledColor = [self valueForThemeAttribute:@"color-critical"];
filledColor = [self valueForThemeAttribute:@"color-critical" inState:themeState];
else if (value <= _warningValue)
filledColor = [self valueForThemeAttribute:@"color-warning"];
filledColor = [self valueForThemeAttribute:@"color-warning" inState:themeState];
}
var emptyColor = [self valueForThemeAttribute:@"color-empty" inState:themeState];
// 4. Paint Segments
for (var i = 0; i < segmentCount; i++)
{
var segmentView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:bezelView];
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : [self valueForThemeAttribute:@"color-empty"]];
[segmentView setBackgroundColor:(_minValue + i) < value ? filledColor : emptyColor];
}
}
@@ -305,20 +323,6 @@ CPRatingLevelIndicatorStyle = 3;
[self setNeedsLayout];
}
/*
- (CPTickMarkPosition)tickMarkPosition;
- (void)setTickMarkPosition:(CPTickMarkPosition)position;
- (int)numberOfTickMarks;
- (void)setNumberOfTickMarks:(int)count;
- (int)numberOfMajorTickMarks;
- (void)setNumberOfMajorTickMarks:(int)count;
- (double)tickMarkValueAtIndex:(int)index;
- (CGRect)rectOfTickMarkAtIndex:(int)index;
*/
@end
var CPLevelIndicatorStyleKey = "CPLevelIndicatorStyleKey",
+109 -3
View File
@@ -27,6 +27,7 @@
@import "CPKeyValueBinding.j"
@import "CPMenuItem.j"
@import "CALayer.j"
@global CPApp
@@ -268,10 +269,13 @@ var _CPMenuBarVisible = NO,
if (self)
{
_title = aTitle;
_items = [];
// Use CPMutableArray instead of raw JS array for consistency with removeAllItems
_items = [CPMutableArray array];
_autoenablesItems = YES;
_showsStateColumn = YES;
_themeState = CPThemeStateNormal;
[self setMinimumWidth:0];
}
@@ -284,6 +288,37 @@ var _CPMenuBarVisible = NO,
return [self initWithTitle:@""];
}
// Managing Theme States (HUD Support)
- (void)setThemeState:(CPThemeState)aState
{
if ([self hasThemeState:aState])
return;
_themeState = _themeState.and(aState);
// Propagate to the view if the menu is currently visible
if (_menuWindow)
[[_menuWindow _menuView] setThemeState:_themeState];
}
- (void)unsetThemeState:(CPThemeState)aState
{
if (![self hasThemeState:aState])
return;
_themeState = _themeState.without(aState);
// Propagate to the view if the menu is currently visible
if (_menuWindow)
[[_menuWindow _menuView] setThemeState:_themeState];
}
- (CPThemeState)themeState
{
return _themeState;
}
// Setting Up Menu Commands
/*!
Inserts a menu item at the specified index.
@@ -373,6 +408,10 @@ var _CPMenuBarVisible = NO,
[self willChangeValueForKey:@"items"];
_items = [CPMutableArray array];
[self didChangeValueForKey:@"items"];
// Ensure the main menu updates if cleared
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
/*!
@@ -389,6 +428,9 @@ var _CPMenuBarVisible = NO,
if ([aMenuItem menu] !== self || !_items)
return;
if (_menuWindow)
[[_menuWindow _menuView] tile];
[aMenuItem setValue:[aMenuItem valueForKey:@"changeCount"] + 1 forKey:@"changeCount"];
[[CPNotificationCenter defaultCenter]
@@ -789,6 +831,10 @@ var _CPMenuBarVisible = NO,
// Create the window for our menu.
var menuWindow = [_CPMenuWindow menuWindowWithMenu:self font:[self font]];
// This pushes the state (e.g., CPThemeStateHUD) to the actual view that renders the menu
if (_themeState)
[[menuWindow _menuView] setThemeState:_themeState];
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
if (anItem)
@@ -879,6 +925,10 @@ var _CPMenuBarVisible = NO,
var theWindow = [aView window],
menuWindow = [_CPMenuWindow menuWindowWithMenu:aMenu font:aFont];
// --- APPLY THEME STATE FROM MENU OBJECT TO VIEW ---
if ([aMenu respondsToSelector:@selector(themeState)])
[[menuWindow _menuView] setThemeState:[aMenu themeState]];
[menuWindow setBackgroundStyle:_CPMenuWindowPopUpBackgroundStyle];
var constraintRect = [CPMenu _constraintRectForView:aView],
@@ -1016,6 +1066,10 @@ var _CPMenuBarVisible = NO,
// an additional mouse move after all this, but not in other browsers.
// This will be fixed correctly with the coming run loop changes.
[_CPDisplayServer run];
// If layers are updated within the menu callback, the re-draw & re-layout doesn't occur until a mouse move.
// We force the update here.
[CALayer runLoopUpdateLayers];
}
/* @ignore */
@@ -1057,7 +1111,23 @@ var _CPMenuBarVisible = NO,
if ([anEvent _triggersKeyEquivalent:[item keyEquivalent] withModifierMask:[item keyEquivalentModifierMask]])
{
if ([item isEnabled])
{
// Flash the top-level item if this is the Main Menu
if (self === [CPApp mainMenu])
[self _flashItemAtIndex:index];
anEvent._isKeyEquivalent = YES; // prevent the menu keystroke from beeing inserted into textview
[self performActionForItemAtIndex:index];
#if PLATFORM(DOM)
// we are done with this event in cappuccino space. do not let the browser do something weird additionally (e.g. command-o).
// but we must not stop copy/paste events as these can only be handled by the browser at this time even if they are in our menu
// (until we move to CPTextView as the fieleditor)
if (characters != "c" && characters != "x" && characters != "v")
_CPDOMEventStop(anEvent._DOMEvent);
#endif
}
else
{
//beep?
@@ -1067,7 +1137,13 @@ var _CPMenuBarVisible = NO,
}
if ([[item submenu] performKeyEquivalent:anEvent])
{
// Flash the top-level item if a submenu handled the event
if (self === [CPApp mainMenu])
[self _flashItemAtIndex:index];
return YES;
}
}
return NO;
@@ -1147,6 +1223,25 @@ var _CPMenuBarVisible = NO,
return nil;
}
//
/*
@ignore
*/
- (void)_flashItemAtIndex:(int)anIndex
{
// If we are using a native bridge (like a desktop wrapper), let the OS handle the visual feedback.
if ([CPPlatform supportsNativeMainMenu])
return;
[self _highlightItemAtIndex:anIndex];
[self performSelector:@selector(_stopFlashingItem) withObject:nil afterDelay:0.2];
}
- (void)_stopFlashingItem
{
[self _highlightItemAtIndex:CPNotFound];
}
@end
@@ -1218,6 +1313,11 @@ var _CPMenuBarVisible = NO,
postNotificationName:CPMenuDidAddItemNotification
object:self
userInfo:@{ @"CPMenuItemIndex": anIndex }];
// FIX #1222: If this is the main menu, force the shared menu bar window to refresh its layout.
// This ensures new items are positioned correctly (e.g. not pushed to the far right by previous layout states).
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
- (void)removeObjectFromItemsAtIndex:(CPUInteger)anIndex
@@ -1233,6 +1333,10 @@ var _CPMenuBarVisible = NO,
postNotificationName:CPMenuDidRemoveItemNotification
object:self
userInfo:@{ @"CPMenuItemIndex": anIndex }];
// FIX #1222: Ensure the shared menu bar updates layout when items are removed.
if (self === [CPApp mainMenu] && _CPMenuBarSharedWindow)
[_CPMenuBarSharedWindow setMenu:self];
}
@end
@@ -1265,6 +1369,9 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
_autoenablesItems = ![aCoder containsValueForKey:CPMenuAutoEnablesItemsKey] || [aCoder decodeBoolForKey:CPMenuAutoEnablesItemsKey];
// Ensure theme state is initialized to avoid undefined issues.
_themeState = CPThemeStateNormal;
[self setMinimumWidth:0];
}
@@ -1293,7 +1400,7 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
@end
#pragma mark -
// MARK: -
@implementation CPMenu (CSSTheming)
@@ -1326,4 +1433,3 @@ var CPMenuTitleKey = @"CPMenuTitleKey",
@import "_CPMenuBarWindow.j"
@import "_CPMenuWindow.j"
+49 -23
View File
@@ -370,12 +370,41 @@
- (void)tile
{
var bounds = [[self contentView] bounds],
height = CGRectGetHeight(bounds),
x = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-left-margin" forClass:_CPMenuView];
// 1. Layout the Icon (if present)
if (_iconImageView && ![_iconImageView isHidden])
{
var iconFrame = [_iconImageView frame];
iconFrame.origin.x = x;
// Vertically center
iconFrame.origin.y = (height - CGRectGetHeight(iconFrame)) / 2.0;
[_iconImageView setFrame:iconFrame];
x = CGRectGetMaxX(iconFrame) + 6.0; // Spacing between icon and title
}
// 2. Layout the Title (if present)
if (_titleField && [_titleField stringValue] && [[_titleField stringValue] length] > 0)
{
var titleFrame = [_titleField frame];
titleFrame.origin.x = x;
titleFrame.origin.y = (height - CGRectGetHeight(titleFrame)) / 2.0;
[_titleField setFrame:titleFrame];
x = CGRectGetMaxX(titleFrame) + 12.0; // Spacing between title and menu items
}
// 3. Layout the Menu Items
var items = [_menu itemArray],
index = 0,
count = items.length,
x = [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-bar-window-left-margin" forClass:_CPMenuView],
y = 0.0,
count = items ? items.length : 0,
isLeftAligned = YES;
for (; index < count; ++index)
@@ -390,11 +419,18 @@
continue;
}
if ([item isHidden])
// Fix for #1742: If a main menu item does not have a submenu, it should not appear in the menu bar.
if ([item isHidden] || ![item submenu])
{
[[item _menuItemView] setHidden:YES];
continue;
}
var menuItemView = [item _menuItemView],
frame = [menuItemView frame];
var menuItemView = [item _menuItemView];
[menuItemView setHidden:NO];
var frame = [menuItemView frame];
if (isLeftAligned)
{
@@ -409,21 +445,6 @@
x = CGRectGetMinX([menuItemView frame]);
}
}
var bounds = [[self contentView] bounds],
titleFrame = [_titleField frame];
if ([_iconImageView isHidden])
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - CGRectGetWidth(titleFrame)) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
else
{
var iconFrame = [_iconImageView frame],
iconWidth = CGRectGetWidth(iconFrame),
totalWidth = iconWidth + CGRectGetWidth(titleFrame);
[_iconImageView setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0, (CGRectGetHeight(bounds) - CGRectGetHeight(iconFrame)) / 2.0)];
[_titleField setFrameOrigin:CGPointMake((CGRectGetWidth(bounds) - totalWidth) / 2.0 + iconWidth, (CGRectGetHeight(bounds) - CGRectGetHeight(titleFrame)) / 2.0)];
}
}
- (void)setFrame:(CGRect)aRect display:(BOOL)shouldDisplay animate:(BOOL)shouldAnimate
@@ -464,13 +485,18 @@
{
var item = items[index];
if ([item isHidden] || [item isSeparatorItem])
if ([item isHidden] || [item isSeparatorItem] || ![item submenu])
continue;
if (CGRectContainsPoint([self rectForItemAtIndex:index], aPoint))
return index;
}
// If the mouse is within the menu bar bounds but not over an item
// (e.g. dragging far left or right), force the menu to unhighlight.
if (CGRectContainsPoint([[self contentView] bounds], aPoint))
[_menu _highlightItemAtIndex:CPNotFound];
return CPNotFound;
}
+73 -13
View File
@@ -62,6 +62,9 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
// Do this so that coordinates will be accurate.
[menuWindow setFrameOrigin:CGPointMakeZero()];
// we need to reset the HUD state as this may be a recycled instance with HUD rund on non-HUD
[[menuWindow _windowView] unsetThemeState:CPThemeStateHUD];
[[menuWindow _menuView] unsetThemeState:CPThemeStateHUD];
}
else
menuWindow = [[_CPMenuWindow alloc] init];
@@ -108,23 +111,33 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[contentView addSubview:_menuClipView];
_moreAboveView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[_moreAboveView setImage:[_menuView valueForThemeAttribute:@"menu-window-more-above-image"]];
[_moreAboveView setFrameSize:[[_menuView valueForThemeAttribute:@"menu-window-more-above-image"] size]];
[contentView addSubview:_moreAboveView];
_moreBelowView = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
[_moreBelowView setImage:[_menuView valueForThemeAttribute:@"menu-window-more-below-image"]];
[_moreBelowView setFrameSize:[[_menuView valueForThemeAttribute:@"menu-window-more-below-image"] size]];
[contentView addSubview:_moreBelowView];
// Initial setup using default attributes
[self updateScrollArrows];
}
return self;
}
- (void)updateScrollArrows
{
if (!_menuView)
return;
var aboveImage = [_menuView currentValueForThemeAttribute:@"menu-window-more-above-image"],
belowImage = [_menuView currentValueForThemeAttribute:@"menu-window-more-below-image"];
[_moreAboveView setImage:aboveImage];
[_moreAboveView setFrameSize:[aboveImage size]];
[_moreBelowView setImage:belowImage];
[_moreBelowView setFrameSize:[belowImage size]];
}
+ (float)_standardLeftMargin
{
return [[CPTheme defaultTheme] valueForAttributeWithName:@"menu-window-margin-inset" forClass:_CPMenuView].left;
@@ -162,7 +175,12 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
- (void)setBackgroundStyle:(_CPMenuWindowBackgroundStyle)aBackgroundStyle
{
[self setBackgroundColor:[[self class] backgroundColorForBackgroundStyle:aBackgroundStyle]];
var color = [_menuView currentValueForThemeAttribute:@"menu-window-pop-up-background-style-color"];
if (!color)
color = [[self class] backgroundColorForBackgroundStyle:aBackgroundStyle];
[self setBackgroundColor:color];
}
- (void)setMenu:(CPMenu)aMenu
@@ -234,6 +252,32 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
// FIXME: This gets called far too often.
_unconstrainedFrame = CGRectMakeCopy(aFrame);
// If we are a submenu and we are being displayed off the right of the screen,
// we should try and display on the left of our supermenu.
var supermenu = [[self menu] supermenu];
if (supermenu)
{
if (CGRectGetMaxX(_unconstrainedFrame) > CGRectGetMaxX(_constraintRect))
{
var supermenuWindow = supermenu._menuWindow;
if (supermenuWindow)
{
var supermenuFrame = [supermenuWindow frame];
_unconstrainedFrame.origin.x = CGRectGetMinX(supermenuFrame) - CGRectGetWidth(_unconstrainedFrame);
}
}
// Shift true submenus vertically to fit within the screen if they extend past the bottom
if (supermenu !== [CPApp mainMenu] && CGRectGetMaxY(_unconstrainedFrame) > CGRectGetMaxY(_constraintRect))
_unconstrainedFrame.origin.y -= CGRectGetMaxY(_unconstrainedFrame) - CGRectGetMaxY(_constraintRect);
}
// Ensure no menu starts above the visible screen area, preventing it from incorrectly
// starting scrolled with a top arrow.
if (CGRectGetMinY(_unconstrainedFrame) < CGRectGetMinY(_constraintRect))
_unconstrainedFrame.origin.y = CGRectGetMinY(_constraintRect);
var constrainedFrame = CGRectIntersection(_unconstrainedFrame, _constraintRect),
marginInset = [_menuView valueForThemeAttribute:@"menu-window-margin-inset"],
scrollIndicatorHeight = [_menuView valueForThemeAttribute:@"menu-window-scroll-indicator-height"];
@@ -255,7 +299,8 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
[super setFrame:constrainedFrame display:shouldDisplay animate:shouldAnimate];
// This needs to happen before changing the frame.
var menuViewOrigin = CGPointMake(CGRectGetMinX(aFrame) + marginInset.left, CGRectGetMinY(aFrame) + marginInset.top),
// Base the view origin on our modified _unconstrainedFrame instead of aFrame
var menuViewOrigin = CGPointMake(CGRectGetMinX(_unconstrainedFrame) + marginInset.left, CGRectGetMinY(_unconstrainedFrame) + marginInset.top),
moreAbove = menuViewOrigin.y < CGRectGetMinY(constrainedFrame) + marginInset.top,
moreBelow = menuViewOrigin.y + CGRectGetHeight([_menuView frame]) > CGRectGetMaxY(constrainedFrame) - marginInset.bottom,
@@ -420,7 +465,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end
#pragma mark -
// MARK: -
@implementation _CPMenuWindow (CSSTheming)
@@ -433,7 +478,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end
#pragma mark -
// MARK: -
/*
@ignore
@@ -489,6 +534,21 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
};
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[_menuItemViews makeObjectsPerformSelector:@selector(setThemeState:) withObject:aState];
if ([[self window] respondsToSelector:@selector(updateScrollArrows)])
[[self window] updateScrollArrows];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[_menuItemViews makeObjectsPerformSelector:@selector(unsetThemeState:) withObject:aState];
}
- (unsigned)numberOfUnhiddenItems
{
return _visibleMenuItemInfos.length;
@@ -604,7 +664,7 @@ _CPMenuWindowAttachedMenuBackgroundStyle = 2;
@end
#pragma mark -
// MARK: -
@implementation _CPMenuView (CSSTheming)
+4 -4
View File
@@ -826,7 +826,7 @@ CPControlKeyMask
return [[self menu] highlightedItem] == self;
}
#pragma mark CPObject Overrides
// MARK: CPObject Overrides
/*!
Returns a copy of the item. The copy does not belong If the item has a submenu, it is NOT copied.
@@ -867,7 +867,7 @@ CPControlKeyMask
return [self copy];
}
#pragma mark Internal
// MARK: Internal
/*
@ignore
@@ -897,7 +897,7 @@ CPControlKeyMask
@end
#pragma mark -
// MARK: -
@implementation CPMenuItem (CSSTheming)
@@ -909,7 +909,7 @@ CPControlKeyMask
@end
#pragma mark -
// MARK: -
var CPMenuItemIsSeparatorKey = @"CPMenuItemIsSeparatorKey",
+47 -17
View File
@@ -145,6 +145,26 @@
return self;
}
- (void)setThemeState:(CPThemeState)aState
{
var oldState = [self themeState];
[super setThemeState:aState];
// If the state changed (e.g. adding HUD), we must re-run update
// to fetch the new text color defined for that state.
if (oldState !== [self themeState])
[self update];
}
- (void)unsetThemeState:(CPThemeState)aState
{
var oldState = [self themeState];
[super unsetThemeState:aState];
if (oldState !== [self themeState])
[self update];
}
- (CPColor)textColor
{
if (![_menuItem isEnabled])
@@ -153,7 +173,7 @@
if (_highlighted)
return [CPColor whiteColor];
return [self valueForThemeAttribute:@"menu-item-text-color"];
return [self currentValueForThemeAttribute:@"menu-item-text-color"];
}
- (CPColor)textShadowColor
@@ -164,7 +184,7 @@
if (_highlighted)
return nil;
return [self valueForThemeAttribute:@"menu-item-text-shadow-color"];
return [self currentValueForThemeAttribute:@"menu-item-text-shadow-color"];
}
- (void)setFont:(CPFont)aFont
@@ -178,6 +198,11 @@
return _font || [_menuItem font] || [CPFont systemFontOfSize:CPFontCurrentSystemSize];
}
// override needed to cancel out the standard HUD propagation
- (void)viewDidMoveToWindow
{
}
// FIXME: update is called 2 times at each display. Find why and fix.
- (void)update
{
@@ -188,6 +213,8 @@
// When possible, use specific vertical margin/offset value based on font size (which could have been set by control size)
correspondingControlSize = [myFont controlSizeCorrespondingToFontSize],
controlSizeState = CPControlSizeThemeStates[correspondingControlSize],
queryState = [self themeState] ? [self themeState].and(controlSizeState) : controlSizeState,
verticalMargin = [self valueForThemeAttribute:@"vertical-margin" inState:CPControlSizeThemeStates[correspondingControlSize]],
verticalOffset = [self valueForThemeAttribute:@"vertical-offset" inState:CPControlSizeThemeStates[correspondingControlSize]];
@@ -199,15 +226,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:queryState] || [_menuItem onStateImage]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:queryState] || [_menuItem offStateImage]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedStateImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:queryState] || [_menuItem mixedStateImage]];
break;
default:
@@ -326,7 +353,10 @@
_highlighted = shouldHighlight;
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize];
var correspondingControlSize = [[self font] controlSizeCorrespondingToFontSize],
// Construct the query state including the view's current theme state (e.g. HUD)
controlSizeState = CPControlSizeThemeStates[correspondingControlSize],
queryState = [self themeState] ? [self themeState].and(controlSizeState) : controlSizeState;
[_imageAndTextView setTextColor:[self textColor]];
[_keyEquivalentView setTextColor:[self textColor]];
@@ -339,7 +369,7 @@
[_imageAndTextView setImage:[_menuItem alternateImage] || [_menuItem image]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-highlighted-image" inState:queryState]];
else
[_submenuIndicatorView setColor:[self textColor]];
}
@@ -349,7 +379,7 @@
[_imageAndTextView setImage:[_menuItem image]];
if (_hasSubmenuIndicatorImage)
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_submenuIndicatorView setImage:[self valueForThemeAttribute:@"submenu-indicator-image" inState:queryState]];
else
[_submenuIndicatorView setColor:[self valueForThemeAttribute:@"submenu-indicator-color"]];
}
@@ -361,15 +391,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-highlighted-image" inState:queryState]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-highlighted-image" inState:queryState]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-highlighted-image" inState:queryState]];
break;
default:
@@ -381,15 +411,15 @@
switch ([_menuItem state])
{
case CPOnState:
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[_menuItem onStateImage] || [self valueForThemeAttribute:@"menu-item-default-on-state-image" inState:queryState]];
break;
case CPOffState:
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[_menuItem offStateImage] || [self valueForThemeAttribute:@"menu-item-default-off-state-image" inState:queryState]];
break;
case CPMixedState:
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:CPControlSizeThemeStates[correspondingControlSize]]];
[_stateView setImage:[_menuItem mixedImage] || [self valueForThemeAttribute:@"menu-item-default-mixed-state-image" inState:queryState]];
break;
default:
@@ -406,11 +436,11 @@
@end
#pragma mark -
// MARK: -
@implementation _CPMenuItemStandardView (CSSTheming)
#pragma mark Override
// MARK: Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
@@ -420,7 +450,7 @@
@end
#pragma mark -
// MARK: -
@implementation _CPMenuItemSubmenuIndicatorView : CPView
{
+25 -3
View File
@@ -83,6 +83,28 @@
return self;
}
// override needed to cancel out the standard HUD propagation
- (void)viewDidMoveToWindow
{
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
// Propagate state to the actual content view (StandardView, Separator, etc.)
if ([_view respondsToSelector:@selector(setThemeState:)])
[_view setThemeState:aState];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
if ([_view respondsToSelector:@selector(unsetThemeState:)])
[_view unsetThemeState:aState];
}
- (CGSize)minSize
{
return _minSize;
@@ -252,11 +274,11 @@
@end
#pragma mark -
// MARK: -
@implementation _CPMenuItemView (CSSTheming)
#pragma mark Override
// MARK: Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
@@ -275,7 +297,7 @@
@end
#pragma mark -
// MARK: -
@implementation _CPMenuItemArrowView : CPView
{
+3 -2
View File
@@ -808,7 +808,8 @@ var CPObjectControllerContentKey = @"CPObjectControllerCo
var value = [self _controllerMarkerForValues:values];
[_cachedValues setObject:value forKey:theKeyPath];
return value;
// Apple's implementation returns nil instead of CPNullMarker
return value === CPNullMarker ? nil : value;
}
else
return values;
@@ -948,4 +949,4 @@ var CPManagedProxyEntityNameKey = @"CPManagedProxyEntity
[aCoder encodeObject:[self fetchPredicate] forKey:CPManagedProxyFetchPredicateKey];
}
@end
@end
+1013 -141
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -121,8 +121,8 @@ CPDocModalWindowMask = 1 << 6;
}
#pragma mark -
#pragma mark Overrides
// MARK: -
// MARK: Overrides
/*!
@ignore
+8
View File
@@ -79,6 +79,8 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
if (self)
{
[self setBezelStyle:CPRoundedBezelStyle];
[self selectItemAtIndex:CPNotFound];
_preferredEdge = CPMaxYEdge;
@@ -689,6 +691,12 @@ CPPopUpButtonStatePullsDown = CPThemeState("pulls-down");
// FIXME: setFont: should set the font on the menu.
[menu setFont:[self font]];
// Propagate the HUD theme state to the menu
if ([self hasThemeState:CPThemeStateHUD])
[menu setThemeState:CPThemeStateHUD];
else
[menu unsetThemeState:CPThemeStateHUD];
if ([self pullsDown])
{
var positionedItem = nil,
+10 -10
View File
@@ -84,8 +84,8 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
}
#pragma mark -
#pragma mark Initialization
// MARK: -
// MARK: Initialization
/*!
Initialize the CPPopover witn default values
@@ -105,8 +105,8 @@ var CPPopoverDelegate_popover_willShow_ = 1 << 0,
}
#pragma mark -
#pragma mark Getters / Setters
// MARK: -
// MARK: Getters / Setters
/*!
Returns the current rect of the popover
@@ -220,8 +220,8 @@ Set the behavior of the CPPopover. It can be:
_implementedDelegateMethods |= CPPopoverDelegate_popover_didClose_;
}
#pragma mark -
#pragma mark Positioning
// MARK: -
// MARK: Positioning
/*!
Show the popover
@@ -298,8 +298,8 @@ Set the behavior of the CPPopover. It can be:
}
#pragma mark -
#pragma mark Action
// MARK: -
// MARK: Action
/*!
Close the popover
@@ -318,8 +318,8 @@ Set the behavior of the CPPopover. It can be:
}
#pragma mark -
#pragma mark Delegates
// MARK: -
// MARK: Delegates
/*! @ignore */
- (BOOL)_popoverWindowShouldClose
+121 -33
View File
@@ -43,7 +43,6 @@ CPProgressIndicatorSpinningStyle = 1;
*/
CPProgressIndicatorHUDBarStyle = 2;
var CPProgressIndicatorSpinningStyleColors = [];
/*!
@ingroup appkit
@@ -71,6 +70,43 @@ var CPProgressIndicatorSpinningStyleColors = [];
BOOL _isDisplayedWhenStopped;
}
// Inject CSS Keyframes for spinning animation (Standard + WebKit)
+ (void)initialize
{
if (self !== [CPProgressIndicator class])
return;
#if PLATFORM(DOM)
if (document.getElementById("cp-progress-indicator-style"))
return;
var style = document.createElement("style");
style.id = "cp-progress-indicator-style";
style.type = "text/css";
// We define two animations:
// 1. cp-progress-indicator-spin: Rotates 360 degrees (for spinners)
// 2. cp-progress-indicator-bar-slide: Moves background-position (for striped bars)
var css =
"@keyframes cp-progress-indicator-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } " +
"@-webkit-keyframes cp-progress-indicator-spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } } " +
"@keyframes cp-progress-indicator-bar-slide { 0% { background-position: 0 0; } 100% { background-position: 30px 0; } } " +
"@-webkit-keyframes cp-progress-indicator-bar-slide { 0% { background-position: 0 0; } 100% { background-position: 30px 0; } }";
if (style.styleSheet)
style.styleSheet.cssText = css;
else
style.appendChild(document.createTextNode(css));
var head = document.getElementsByTagName("head")[0];
if (head)
head.appendChild(style);
#endif
}
+ (CPString)defaultThemeClass
{
return @"progress-indicator";
@@ -83,12 +119,19 @@ var CPProgressIndicatorSpinningStyleColors = [];
@"bar-color": [CPNull null],
@"default-height": 20,
@"bezel-color": [CPNull null],
@"spinning-mini-gif": [CPNull null],
@"spinning-small-gif": [CPNull null],
@"spinning-regular-gif": [CPNull null],
// CSS Spinner Attributes
@"spinner-color": [CPColor grayColor],
@"spinner-track-color": [CPColor colorWithWhite:0.9 alpha:1.0],
@"spinner-line-width": 3.0,
@"circular-border-color": [CPNull null],
@"circular-border-size": 1,
@"circular-color": [CPNull null]
@"circular-color": [CPNull null],
@"spinning-mini-gif": [CPNull null],
@"spinning-small-gif": [CPNull null],
@"spinning-regular-gif": [CPNull null]
};
}
@@ -138,6 +181,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
_isAnimating = YES;
[self _hideOrDisplay];
[self setNeedsLayout]; // Trigger layout to update CSS animation state
}
/*!
@@ -149,6 +193,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
_isAnimating = NO;
[self _hideOrDisplay];
[self setNeedsLayout]; // Trigger layout to remove CSS animation state
}
/*!
@@ -233,7 +278,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
_controlSize = aControlSize;
[self updateBackgroundColor];
[self setNeedsLayout];
}
/*!
@@ -285,7 +330,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
_indeterminate = indeterminate;
[self updateBackgroundColor];
[self setNeedsLayout];
}
/*!
@@ -307,9 +352,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
_style = aStyle;
[self setTheme:(_style === CPProgressIndicatorHUDBarStyle) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
[self updateBackgroundColor];
[self setNeedsLayout];
}
/*!
@@ -318,7 +361,14 @@ var CPProgressIndicatorSpinningStyleColors = [];
- (void)sizeToFit
{
if (_style == CPProgressIndicatorSpinningStyle)
[self setFrameSize:[[CPProgressIndicatorSpinningStyleColors[_controlSize] patternImage] size]];
{
var size = 32.0;
if (_controlSize === CPMiniControlSize) size = 16.0;
else if (_controlSize === CPSmallControlSize) size = 24.0;
else if (_controlSize === CPRegularControlSize) size = 32.0;
[self setFrameSize:CGSizeMake(size, size)];
}
else
[self setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), [self valueForThemeAttribute:@"default-height"])];
}
@@ -381,6 +431,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
- (CGRect)rectForEphemeralSubviewNamed:(CPString)aViewName
{
// Handle the standard Bar View
if (aViewName === @"bar-view" && _style !== CPProgressIndicatorSpinningStyle)
{
var width = CGRectGetWidth([self bounds]),
@@ -394,41 +445,78 @@ var CPProgressIndicatorSpinningStyleColors = [];
return CGRectMake(0, 0, barWidth, [self valueForThemeAttribute:@"default-height"]);
}
return nil;
}
/* @ignore */
- (void)updateBackgroundColor
{
if ([CPProgressIndicatorSpinningStyleColors count] === 0)
// Handle the Spinning View (CSS Spinner)
if (aViewName === @"spinner-view" && _style == CPProgressIndicatorSpinningStyle && _indeterminate)
{
CPProgressIndicatorSpinningStyleColors[CPMiniControlSize] = [self valueForThemeAttribute:@"spinning-mini-gif"];
CPProgressIndicatorSpinningStyleColors[CPSmallControlSize] = [self valueForThemeAttribute:@"spinning-small-gif"];
CPProgressIndicatorSpinningStyleColors[CPRegularControlSize] = [self valueForThemeAttribute:@"spinning-regular-gif"];
return [self bounds];
}
[self setNeedsLayout];
// Return nil for views that shouldn't appear in the current style
return nil;
}
- (void)layoutSubviews
{
if (YES)//_isBezeled)
{
// === SPINNING STYLE ===
if (_style == CPProgressIndicatorSpinningStyle)
{
if (!_indeterminate)
return;
// If indeterminate, use CSS spinner
if (_indeterminate)
{
var spinnerView = [self layoutEphemeralSubviewNamed:"spinner-view"
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:nil];
// Ensure other views are hidden
[self layoutEphemeralSubviewNamed:"bar-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
// This will cause the bar view to go away due to having a nil rect when _style == CPProgressIndicatorSpinningStyle.
[self layoutEphemeralSubviewNamed:"bar-view"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
// Configure CSS on the subview, NOT self._DOMElement, to avoid transform conflicts
var domEl = spinnerView._DOMElement,
spinnerColor = [self currentValueForThemeAttribute:@"spinner-color"],
trackColor = [self currentValueForThemeAttribute:@"spinner-track-color"],
lineWidth = [self currentValueForThemeAttribute:@"spinner-line-width"],
widthStr = lineWidth + "px";
[self setBackgroundColor:CPProgressIndicatorSpinningStyleColors[_controlSize]];
domEl.style.boxSizing = "border-box";
domEl.style.borderRadius = "50%";
domEl.style.borderStyle = "solid";
domEl.style.borderWidth = widthStr;
domEl.style.borderColor = [trackColor cssString];
domEl.style.borderTopColor = [spinnerColor cssString];
// Animation logic
if (_isAnimating)
{
var anim = "cp-progress-indicator-spin 1s linear infinite";
domEl.style.animation = anim;
domEl.style.WebkitAnimation = anim;
}
else
{
domEl.style.animation = "none";
domEl.style.WebkitAnimation = "none";
}
// Ensure main view is transparent
[self setBackgroundColor:nil];
}
else
{
// Determinate Spinner (Pie Chart drawn in drawRect)
// Hide CSS spinner
[self layoutEphemeralSubviewNamed:"spinner-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
[self setBackgroundColor:nil];
}
}
// === BAR STYLE ===
else
{
// Hide spinner
[self layoutEphemeralSubviewNamed:"spinner-view" positioned:CPWindowBelow relativeToEphemeralSubviewNamed:nil];
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
var barView = [self layoutEphemeralSubviewNamed:"bar-view"
@@ -447,6 +535,8 @@ var CPProgressIndicatorSpinningStyleColors = [];
- (void)drawRect:(CGRect)aRect
{
// Handle determinate state for Spinning style (Pie chart progress) via CoreGraphics.
// If indeterminate, the CSS animation (spinner-view) takes over and we draw nothing.
if (_style == CPProgressIndicatorSpinningStyle && !_indeterminate)
{
var context = [[CPGraphicsContext currentContext] graphicsPort],
@@ -512,7 +602,7 @@ var CPProgressIndicatorSpinningStyleColors = [];
_isDisplayedWhenStoppedSet = [aCoder decodeObjectForKey:@"_isDisplayedWhenStoppedSet"];
_isDisplayedWhenStopped = [aCoder decodeObjectForKey:@"_isDisplayedWhenStopped"];
[self updateBackgroundColor];
[self setNeedsLayout];
}
return self;
@@ -520,8 +610,6 @@ var CPProgressIndicatorSpinningStyleColors = [];
- (void)encodeWithCoder:(CPCoder)aCoder
{
// Don't encode the background colour. It can be recreated based on the flags
// and if encoded causes hardcoded image paths in the cib while just wasting space.
var backgroundColor = [self backgroundColor];
[self setBackgroundColor:nil];
[super encodeWithCoder:aCoder];
+4 -4
View File
@@ -187,7 +187,7 @@ CPRadioImageOffset = 4.0;
[self _setRadioGroup];
}
#pragma mark Private methods
// MARK: Private methods
- (void)_setRadioGroup
{
@@ -260,8 +260,8 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
[aCoder encodeObject:_radioGroup forKey:CPRadioRadioGroupKey];
}
#pragma mark -
#pragma mark Override methods from CPButton
// MARK: -
// MARK: Override methods from CPButton
- (CPThemeState)_contentVisualState
{
@@ -412,7 +412,7 @@ var CPRadioRadioGroupKey = @"CPRadioRadioGroupKey";
[_radios makeObjectsPerformSelector:@selector(setHidden:) withObject:hidden];
}
#pragma mark Private
// MARK: Private
- (void)_addRadio:(CPRadio)aRadio
{
+11 -6
View File
@@ -34,7 +34,7 @@
SEL _predicateAction @accessors(property=action);
}
#pragma mark public methods
// MARK: public methods
/*!
@ingroup appkit
@class CPPredicateEditor
@@ -251,7 +251,7 @@
return tree;
}
#pragma mark Set the Predicate
// MARK: Set the Predicate
- (void)setObjectValue:(id)objectValue
{
@@ -269,6 +269,8 @@
_currentAnimation = nil;
_sendAction = NO;
var rows = [];
if (predicate != nil)
{
if ((_nestingMode == CPRuleEditorNestingModeSimple || _nestingMode == CPRuleEditorNestingModeCompound)
@@ -276,10 +278,13 @@
predicate = [[CPCompoundPredicate alloc] initWithType:[self _compoundPredicateTypeForRootRows] subpredicates:[CPArray arrayWithObject:predicate]];
var row = [self _rowObjectFromPredicate:predicate];
if (row != nil)
[_boundArrayOwner setValue:[CPArray arrayWithObject:row] forKey:_boundArrayKeyPath];
[rows addObject:row];
}
[_boundArrayOwner setValue:rows forKey:_boundArrayKeyPath];
[self setAnimation:animation];
}
@@ -369,7 +374,7 @@
return row;
}
#pragma mark Get the predicate
// MARK: Get the predicate
- (void)_updatePredicate
{
@@ -447,7 +452,7 @@
return CPAndPredicateType;
}
#pragma mark Control delegate
// MARK: Control delegate
- (void)_sendRuleAction
{
@@ -482,7 +487,7 @@
}
*/
#pragma mark RuleEditor delegate methods
// MARK: RuleEditor delegate methods
- (int)_queryNumberOfChildrenOfItem:(id)rowItem withRowType:(CPRuleEditorRowType)type
{
+169 -50
View File
@@ -80,6 +80,35 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
@n An ordered to-many relation containing the display values for the row.
@n@n @c @@"criteria"
@n An ordered to-many relation containing the criteria for the row.
@n@n
Localization & Positional Reordering
@n
CPRuleEditor supports complete localization of menu items and grammatical positional reordering (sentence structure layout adjustment) via strings resource files (.strings) or custom programmatic CPDictionary tables.
@n@n
Since sentence structures vary significantly across languages, the editor can dynamically reposition views (such as popups, static labels, and text fields) from left to right to form grammatically correct sentences.
@n@n
Formatting Keys (English representation):
@n @c %[%]@@
@n Represents a popup button displaying its selected value (e.g. @c %[firstName]@@).
@n @c %@@
@n Represents an editable text input field.
@n Static text represents a literal label placed directly inside the formatting key.
@n@n
Example English format key:
@n @c "%[firstName]@ %[is equal to]@ %@"
@n@n
Translation Patterns (Target language):
@n Positional specifiers such as @c %1$@@, @c %2$@@, @c %3$@@ dictate the visual order of views from left to right.
@n Bracketed values inside positional specifiers (e.g. @c %1$[Nombre]@@) define the localized title for popup selection items.
@n Literal text outside the specifiers (such as @c "y" or @c "und") is automatically instantiated as static text labels positioned between controls.
@n@n
Example translations:
@n@n
Spanish (Reorders to: [1: Name] y [3: Value] [2: are equal]):
@n @c "%[firstName]@ %[is equal to]@ %@" = "%1$[Nombre]@ y %3$@ %2$[son iguales]@";
@n@n
German (Reorders to: [1: First Name] und [3: Value] [2: are equal]):
@n @c "%[firstName]@ %[is equal to]@ %@" = "%1$[Vorname]@ und %3$@ %2$[sind gleich]@";
*/
@implementation CPRuleEditor : CPControl
@@ -127,7 +156,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
BOOL _isKeyDown;
BOOL _nestingModeDidChange;
_CPRuleEditorLocalizer _standardLocalizer @accessors(property=standardLocalizer);
_CPRuleEditorLocalizer _standardLocalizer;
CPDictionary _itemsAndValuesToAddForRowType;
}
@@ -207,8 +236,34 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
[self registerForDraggedTypes:[CPArray arrayWithObjects:CPRuleEditorItemPBoardType,nil]];
[_boundArrayOwner addObserver:self forKeyPath:_boundArrayKeyPath options:CPKeyValueObservingOptionOld | CPKeyValueObservingOptionNew context:boundArrayContext];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_ruleEditorLocalizerDidLoad:)
name:@"_CPRuleEditorLocalizerDidLoadNotification"
object:nil];
}
- (void)_ruleEditorLocalizerDidLoad:(CPNotification)aNotification
{
if ([aNotification object] === [self standardLocalizer])
{
// Defer execution to the next run loop cycle so that any active slice
// insertions have fully completed and are present in the `_slices` array.
[[CPRunLoop mainRunLoop] performBlock:function() {
var count = [_slices count];
for (var i = 0; i < count; i++)
{
var slice = [_slices objectAtIndex:i];
[slice _reconfigureSubviews];
[slice _updateButtonVisibilities]; // Force updates on row button tooltips
}
[self _updatePredicate];
[self _sendRuleAction];
} argument:nil order:0 modes:[CPDefaultRunLoopMode]];
}
}
/*! @endcond */
/*!
@@ -384,7 +439,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/
- (CPDictionary)formattingDictionary
{
return [_standardLocalizer dictionary];
return [[self standardLocalizer] dictionary];
}
/*!
@@ -396,6 +451,9 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
*/
- (void)setFormattingDictionary:(CPDictionary)dictionary
{
if (_standardLocalizer == nil)
_standardLocalizer = [_CPRuleEditorLocalizer new];
[_standardLocalizer setDictionary:dictionary];
_stringsFilename = nil;
}
@@ -440,6 +498,19 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
}
}
- (_CPRuleEditorLocalizer)standardLocalizer
{
if (_standardLocalizer == nil)
_standardLocalizer = [_CPRuleEditorLocalizer new];
return _standardLocalizer;
}
- (void)setStandardLocalizer:(_CPRuleEditorLocalizer)aLocalizer
{
_standardLocalizer = aLocalizer;
}
/*!
@name Providing Data
*/
@@ -538,7 +609,7 @@ var CPRuleEditorItemPBoardType = @"CPRuleEditorItemPBoardType",
if ([self rowTypeForRow:current_index] === CPRuleEditorRowTypeCompound)
{
var candidate = [[self _rowCacheForIndex:current_index] rowObject],
subObjects = [[self _subrowObjectsOfObject:candidate] _representedObject];
subObjects = [self _subrowObjectsOfObject:candidate]; // Standard direct array query
if ([subObjects indexOfObjectIdenticalTo:targetObject] !== CPNotFound)
return current_index;
@@ -605,7 +676,7 @@ TODO: implement
for (var i = rowIndex + 1; i < count; i++)
{
var candidate = [[self _rowCacheForIndex:i] rowObject],
indexInSubrows = [[subobjects _representedObject] indexOfObjectIdenticalTo:candidate];
indexInSubrows = [subobjects indexOfObjectIdenticalTo:candidate]; // Standard direct array query
if (indexInSubrows !== CPNotFound)
{
@@ -774,7 +845,7 @@ TODO: implement
while (current_index !== CPNotFound)
{
var rowObject = [[self _rowCacheForIndex:current_index] rowObject],
relativeChildIndex = [[subrows _representedObject] indexOfObjectIdenticalTo:rowObject];
relativeChildIndex = [subrows indexOfObjectIdenticalTo:rowObject]; // Standard direct array query
if (relativeChildIndex !== CPNotFound)
[childsIndexes addIndex:relativeChildIndex];
@@ -831,7 +902,6 @@ TODO: implement
for (i = 0; i < count; i++)
{
var item = [items objectAtIndex:i],
//var displayValue = [self _queryValueForItem:item inRow:aRow]; Ask the delegate or get cached value ?.
displayValue = [[self displayValuesForRow:aRow] objectAtIndex:i],
predpart = [self _sendDelegateRuleEditorPredicatePartsForCriterion:item withDisplayValue:displayValue inRow:aRow];
@@ -849,6 +919,7 @@ TODO: implement
return nil;
var current_index = [subrowsIndexes firstIndex];
while (current_index !== CPNotFound)
{
var subpredicate = [self predicateForRow:current_index];
@@ -1233,7 +1304,7 @@ TODO: implement
return shouldHide;
}
#pragma mark Rows management
// MARK: Rows management
- (id)_rowCacheForIndex:(int)index
{
@@ -1278,23 +1349,28 @@ TODO: implement
while (current_index !== CPNotFound)
{
var parentIndex = [self parentRowForRow:current_index],
subrowsIndexes = [self subrowIndexesForRow:parentIndex];
if ([subrowsIndexes count] === 1)
var parentIndex = [self parentRowForRow:current_index];
// If the row has a valid parent in the editor (i.e. not a root row)
if (parentIndex !== -1)
{
if (parentIndex !== -1)
return [CPIndexSet indexSetWithIndex:0];
var subrowsIndexes = [self subrowIndexesForRow:parentIndex];
var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]];
[childlessParents addIndexes:childlessGranPa];
// If deleting this row leaves the parent with no remaining child rows
if ([subrowsIndexes count] === 1)
{
[childlessParents addIndex:parentIndex];
// Recursively check if deleting this parent row leaves the grandparent childless
var childlessGranPa = [self _childlessParentsIfSlicesWereDeletedAtIndexes:[CPIndexSet indexSetWithIndex:parentIndex]];
[childlessParents addIndexes:childlessGranPa];
}
}
current_index = [indexes indexGreaterThanIndex:current_index];
}
return childlessParents;
// (id)-[RuleEditor _includeSubslicesForSlicesAtIndexes:]
}
- (CPIndexSet)_includeSubslicesForSlicesAtIndexes:(CPIndexSet)indexes
@@ -1356,8 +1432,25 @@ TODO: implement
if ([self rowTypeForRow:row] === type && itemIndex < [aCriteria count])
{
var crit = [aCriteria objectAtIndex:itemIndex];
[current_criterions addObject:crit];
// Verify that this row's parent path matches the path currently being built
var pathMatches = true;
for (var p = 0; p < itemIndex; p++)
{
var criterionA = [aCriteria objectAtIndex:p],
criterionB = [items objectAtIndex:p];
if (criterionA !== criterionB && (typeof criterionA.isEqual !== "function" || ![criterionA isEqual:criterionB]))
{
pathMatches = false;
break;
}
}
if (pathMatches)
{
var crit = [aCriteria objectAtIndex:itemIndex];
[current_criterions addObject:crit];
}
}
}
@@ -1473,7 +1566,7 @@ TODO: implement
return row;
}
#pragma mark Key value observing
// MARK: Key value observing
- (void)_startObservingRowObjectsRecursively:(CPArray)rowObjects
{
@@ -1774,7 +1867,12 @@ TODO: implement
- (_CPRuleEditorViewSliceRow)_createNewSliceWithFrame:(CGRect)frame ruleEditorView:(CPRuleEditor)editor
{
return [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor];
var slice = [[_CPRuleEditorViewSliceRow alloc] initWithFrame:frame ruleEditorView:editor];
// Ensure the slice resizes with the editor
[slice setAutoresizingMask:CPViewWidthSizable];
return slice;
}
- (void)_reconfigureSubviewsAnimate:(BOOL)animate
@@ -1859,57 +1957,63 @@ TODO: implement
- (CPArray)_backgroundColors
{
return [self valueForThemeAttribute:@"alternating-row-colors"];
return [self currentValueForThemeAttribute:@"alternating-row-colors"];
}
- (CPColor)_selectedRowColor
{
return [self valueForThemeAttribute:@"selected-color"];
return [self currentValueForThemeAttribute:@"selected-color"];
}
- (CPColor)_sliceTopBorderColor
{
return [self valueForThemeAttribute:@"slice-top-border-color"];
return [self currentValueForThemeAttribute:@"slice-top-border-color"];
}
- (CPColor)_sliceBottomBorderColor
{
return [self valueForThemeAttribute:@"slice-bottom-border-color"];
return [self currentValueForThemeAttribute:@"slice-bottom-border-color"];
}
- (CPColor)_sliceLastBottomBorderColor
{
return [self valueForThemeAttribute:@"slice-last-bottom-border-color"];
return [self currentValueForThemeAttribute:@"slice-last-bottom-border-color"];
}
- (CPFont)font
{
return [self valueForThemeAttribute:@"font"];
return [self currentValueForThemeAttribute:@"font"];
}
- (CPColor)_fontColor
{
return [self valueForThemeAttribute:@"font-color"];
return [self currentValueForThemeAttribute:@"font-color"];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[_slices makeObjectsPerformSelector:@selector(setThemeState:) withObject:aState];
}
- (CPImage)_imageAdd
{
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateNormal];
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateNormal.and([self themeState])];
}
- (CPImage)_imageAddHighlighted
{
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateHighlighted];
return [self valueForThemeAttribute:@"add-image" inState:CPThemeStateHighlighted.and([self themeState])];
}
- (CPImage)_imageRemove
{
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateNormal];
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateNormal.and([self themeState])];
}
- (CPImage)_imageRemoveHighlighted
{
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateHighlighted];
return [self valueForThemeAttribute:@"remove-image" inState:CPThemeStateHighlighted.and([self themeState])];
}
- (CPVerticalTextAlignment)_verticalAlignment
@@ -1919,17 +2023,17 @@ TODO: implement
- (CPString)_toolTipForAddCompoundRowButton
{
return [_standardLocalizer localizedStringForString:@"Add compound row"];
return [[self standardLocalizer] localizedStringForString:@"Add compound row"];
}
- (CPString)_toolTipForAddSimpleRowButton
{
return [_standardLocalizer localizedStringForString:@"Add row"];
return [[self standardLocalizer] localizedStringForString:@"Add row"];
}
- (CPString)_toolTipForDeleteRowButton
{
return [_standardLocalizer localizedStringForString:@"Delete row"];
return [[self standardLocalizer] localizedStringForString:@"Delete row"];
}
- (void)_updateSliceIndentations
@@ -2121,7 +2225,13 @@ TODO: implement
return;
var point = [self convertPoint:[event locationInWindow] fromView:nil],
view = [_slices objectAtIndex:FLOOR(MAX(0, point.y) / _sliceHeight)];
index = FLOOR(MAX(0, point.y) / _sliceHeight);
// Check bounds before accessing the array to prevent CPRangeException
if (index >= [_slices count])
return;
var view = [_slices objectAtIndex:FLOOR(MAX(0, point.y) / _sliceHeight)];
if ([self _dragShouldBeginFromMouseDown:view])
[self _performDragForSlice:view withEvent:event];
@@ -2244,33 +2354,41 @@ TODO: implement
- (BOOL)performDragOperation:(id /*< CPDraggingInfo >*/)info
{
var aboveInsertIndexCount = 0,
object,
removeIndex;
var object;
var rowObjects = [_rowCache valueForKey:@"rowObject"],
index = [_draggingRows lastIndex];
var parentRowIndex = [self parentRowForRow:index], // first index of draggingrows
var firstDraggingIndex = [_draggingRows firstIndex],
parentRowIndex = [self parentRowForRow:firstDraggingIndex],
parentRowObject = (parentRowIndex === -1) ? _boundArrayOwner : [[self _rowCacheForIndex:parentRowIndex] rowObject],
insertIndex = _subviewIndexOfDropLine;
while (index !== CPNotFound)
{
if (index >= insertIndex)
// Identify if this row's parent is also inside the dragging set.
// If it is, we skip it because it will move automatically with its parent.
var parentOfCurrent = [self parentRowForRow:index];
if (parentOfCurrent !== -1 && [_draggingRows containsIndex:parentOfCurrent])
{
removeIndex = index + aboveInsertIndexCount;
aboveInsertIndexCount += 1;
}
else
{
removeIndex = index;
insertIndex -= 1;
index = [_draggingRows indexLessThanIndex:index];
continue;
}
object = [rowObjects objectAtIndex:removeIndex];
[self removeRowAtIndex:removeIndex];
[[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1];
object = [rowObjects objectAtIndex:index];
// Find the current live index of the object inside the editor
var cache = [self _searchCacheForRowObject:object];
var liveIndex = [_rowCache indexOfObjectIdenticalTo:cache];
if (liveIndex !== CPNotFound)
{
if (liveIndex < insertIndex)
insertIndex -= 1;
[self removeRowAtIndex:liveIndex];
[[self _subrowObjectsOfObject:parentRowObject] insertObject:object atIndex:insertIndex - parentRowIndex - 1];
}
index = [_draggingRows indexLessThanIndex:index];
}
@@ -2280,6 +2398,7 @@ TODO: implement
return YES;
}
- (CPIndexSet)_draggingTypes
{
return [CPIndexSet indexSetWithIndex:CPDragOperationMove];
@@ -77,6 +77,9 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
}
_dictionary = [CPDictionary dictionaryWithDictionary:dict];
// Post notification to let the rule editor know the translation dictionary is ready
[[CPNotificationCenter defaultCenter] postNotificationName:@"_CPRuleEditorLocalizerDidLoadNotification" object:self];
}
- (CPString)localizedStringForString:(CPString)aString
@@ -94,4 +97,231 @@ var LocalizerStringsRegex = new RegExp("\"(.+)\"\\s*=\\s*\"(.+)\"\\s*;\\s*(//.+)
return aString;
}
// MARK: - Formatting & Reordering Helpers
- (CPString)_englishRepresentationForView:(id)aView
{
if ([aView isKindOfClass:[CPPopUpButton class]])
{
var selectedItem = [aView selectedItem];
if (selectedItem)
{
var originalTitle = selectedItem._originalTitle;
// Fallback: If not cached directly, inspect representedObject payload dictionary
if (!originalTitle)
{
var rep = [selectedItem representedObject];
if (rep && typeof rep === "object" && [rep respondsToSelector:@selector(objectForKey:)])
{
originalTitle = [rep objectForKey:@"value"];
}
else if (rep && typeof rep === "string")
{
originalTitle = rep;
}
}
if (!originalTitle)
{
originalTitle = [selectedItem title];
}
return "%[" + originalTitle + "]@";
}
return "%[]@";
}
else if ([aView isKindOfClass:[CPTextField class]] && ![aView isEditable])
{
return aView._originalText || [aView stringValue];
}
else
{
return "%@";
}
}
- (CPString)formattingKeyForViews:(CPArray)views
{
var keyParts = [];
var count = [views count];
for (var i = 0; i < count; i++)
{
var view = [views objectAtIndex:i];
[keyParts addObject:[self _englishRepresentationForView:view]];
}
return [keyParts componentsJoinedByString:@" "];
}
- (void)localizeMenuItemsForViews:(CPArray)views
{
var count = [views count];
for (var i = 0; i < count; i++)
{
var view = [views objectAtIndex:i];
if ([view isKindOfClass:[CPPopUpButton class]])
{
var menuItems = [view itemArray];
var menuItemsCount = [menuItems count];
var selectedItem = [view selectedItem];
for (var j = 0; j < menuItemsCount; j++)
{
var item = [menuItems objectAtIndex:j];
if (!item._originalTitle)
{
var rep = [item representedObject];
if (rep && typeof rep === "object" && [rep respondsToSelector:@selector(objectForKey:)])
{
item._originalTitle = [rep objectForKey:@"value"];
}
else
{
item._originalTitle = [item title];
}
}
// Temporarily select item to generate formatting key context
[view selectItem:item];
var tempKey = [self formattingKeyForViews:views];
var tempPattern = [self localizedStringForString:tempKey];
if (tempPattern !== tempKey)
{
var regex = /%(\d+)\$(?:\[([^\]]+)\])?@/g;
var match;
while ((match = regex.exec(tempPattern)) !== null)
{
var position = parseInt(match[1], 10) - 1;
var translatedValue = match[2];
if (position === i && translatedValue)
{
[item setTitle:translatedValue];
break;
}
}
}
else
{
[item setTitle:item._originalTitle];
}
}
if (selectedItem)
{
[view selectItem:selectedItem];
}
}
}
}
- (CPArray)localizeAndReorderViews:(CPArray)views
{
var key = [self formattingKeyForViews:views];
var localizedPattern = [self localizedStringForString:key];
if (localizedPattern === key)
{
var count = [views count];
for (var i = 0; i < count; i++)
{
var originalView = [views objectAtIndex:i];
if ([originalView isKindOfClass:[CPPopUpButton class]])
{
var selectedItem = [originalView selectedItem];
if (selectedItem && selectedItem._originalTitle)
{
[selectedItem setTitle:selectedItem._originalTitle];
}
}
else if ([originalView respondsToSelector:@selector(setStringValue:)] && originalView._originalText)
{
[originalView setStringValue:originalView._originalText];
if ([originalView isKindOfClass:[CPTextField class]] && ![originalView isEditable])
{
var font = [originalView font] || [CPFont systemFontOfSize:[CPFont systemFontSize]],
size = [originalView._originalText sizeWithFont:font];
[originalView setFrameSize:CGSizeMake(size.width + 4, CGRectGetHeight([originalView frame]))];
}
}
}
return views;
}
var newViews = [CPMutableArray array];
var regex = /%(\d+)\$(?:\[([^\]]+)\])?@/g;
var lastIndex = 0;
var match;
while ((match = regex.exec(localizedPattern)) !== null)
{
var literalText = localizedPattern.substring(lastIndex, match.index);
// Only add a label if there are actual non-whitespace characters (like 'y')
if (literalText.length > 0 && /\S/.test(literalText))
{
var label = [CPTextField labelWithTitle:literalText];
[newViews addObject:label];
}
var position = parseInt(match[1], 10) - 1;
var translatedValue = match[2];
if (position >= 0 && position < [views count])
{
var originalView = [views objectAtIndex:position];
if (translatedValue !== undefined && translatedValue !== null)
{
if ([originalView isKindOfClass:[CPPopUpButton class]])
{
var selectedItem = [originalView selectedItem];
if (selectedItem)
{
if (!selectedItem._originalTitle)
{
selectedItem._originalTitle = [selectedItem title];
}
[selectedItem setTitle:translatedValue];
}
}
else if ([originalView respondsToSelector:@selector(setStringValue:)])
{
[originalView setStringValue:translatedValue];
// Recalculate frame size if it is a static CPTextField to avoid visual clipping
if ([originalView isKindOfClass:[CPTextField class]] && ![originalView isEditable])
{
var font = [originalView font] || [CPFont systemFontOfSize:[CPFont systemFontSize]],
size = [translatedValue sizeWithFont:font];
[originalView setFrameSize:CGSizeMake(size.width + 4, CGRectGetHeight([originalView frame]))];
}
}
}
[newViews addObject:originalView];
}
lastIndex = regex.lastIndex;
}
if (lastIndex < localizedPattern.length)
{
var literalText = localizedPattern.substring(lastIndex);
// Only add a label if there are actual non-whitespace characters
if (literalText.length > 0 && /\S/.test(literalText))
{
var label = [CPTextField labelWithTitle:literalText];
[newViews addObject:label];
}
}
return newViews;
}
@end
+67 -23
View File
@@ -49,6 +49,13 @@
return self;
}
- (void)setRowIndex:(int)anIndex
{
_rowIndex = anIndex;
[self _updateBackgroundColor];
[self setNeedsDisplay:YES];
}
- (void)_setSelected:(BOOL)select
{
if (select == _selected)
@@ -57,6 +64,47 @@
var selector = select ? @selector(setThemeState:) : @selector(unsetThemeState:);
[[self subviews] makeObjectsPerformSelector:selector withObject:CPThemeStateSelectedDataView];
_selected = select;
[self _updateBackgroundColor];
[self setNeedsDisplay:YES];
}
- (void)_updateBackgroundColor
{
var color = nil;
if ([self _isSelected])
{
color = [_ruleEditor _selectedRowColor];
}
else
{
var colors = [_ruleEditor _backgroundColors],
count = [colors count];
if (count > 0)
color = [colors objectAtIndex:(_rowIndex % count)];
}
[self setBackgroundColor:color];
}
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
[self _updateBackgroundColor];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[self _updateBackgroundColor];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self _updateBackgroundColor];
}
- (void)drawRect:(CGRect)rect
@@ -66,35 +114,31 @@
maxX = CGRectGetWidth(bounds),
maxY = CGRectGetHeight(bounds);
// Draw background
if ([self _isSelected])
_backgroundColor = [_ruleEditor _selectedRowColor];
else
{
var colors = [_ruleEditor _backgroundColors],
count = [colors count];
_backgroundColor = [colors objectAtIndex:(_rowIndex % count)];
}
CGContextSetFillColor(context, _backgroundColor);
CGContextFillRect(context, rect);
// Note: Background is now handled by setBackgroundColor in _updateBackgroundColor
// to support CSS-based colors and transparency correctly.
// Draw Top Border
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, maxX, 0);
CGContextSetStrokeColor(context, [_ruleEditor _sliceTopBorderColor]);
CGContextStrokePath(context);
var topColor = [_ruleEditor _sliceTopBorderColor];
if (topColor)
{
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, maxX, 0);
CGContextSetStrokeColor(context, topColor);
CGContextStrokePath(context);
}
// Draw Bottom Border
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, maxY);
CGContextAddLineToPoint(context, maxX, maxY);
var bottomColor = (_rowIndex == [_ruleEditor _lastRow]) ? [_ruleEditor _sliceLastBottomBorderColor] : [_ruleEditor _sliceBottomBorderColor];
CGContextSetStrokeColor(context, bottomColor);
CGContextStrokePath(context);
if (bottomColor)
{
CGContextBeginPath(context);
CGContextMoveToPoint(context, 0, maxY);
CGContextAddLineToPoint(context, maxX, maxY);
CGContextSetStrokeColor(context, bottomColor);
CGContextStrokePath(context);
}
}
- (void)mouseDown:(CPEvent)theEvent
+86 -18
View File
@@ -67,6 +67,23 @@
[self setAutoresizingMask:CPViewWidthSizable];
}
- (void)_setSelected:(BOOL)isSelected
{
[super _setSelected:isSelected];
[self _updateButtonImages];
}
- (void)_updateButtonImages
{
var rowState = [self themeState];
if ([self _isSelected])
rowState = rowState.and(CPThemeStateSelected);
[_addButton setImage:[_ruleEditor valueForThemeAttribute:@"add-image" inState:rowState]];
[_subtractButton setImage:[_ruleEditor valueForThemeAttribute:@"remove-image" inState:rowState]];
}
- (CPButton)_createRowButton
{
var button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
@@ -89,6 +106,8 @@
var button = [self _createRowButton];
[button setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
// Initial setup, _updateButtonImages will be called later to set correct state-based images
[button setValue:[_ruleEditor _imageAdd] forThemeAttribute:@"image" inState:CPThemeStateNormal];
[button setValue:[_ruleEditor _imageAddHighlighted] forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
@@ -103,6 +122,8 @@
var button = [self _createRowButton];
[button setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
// Initial setup
[button setValue:[_ruleEditor _imageRemove] forThemeAttribute:@"image" inState:CPThemeStateNormal];
[button setValue:[_ruleEditor _imageRemoveHighlighted] forThemeAttribute:@"image" inState:CPThemeStateHighlighted];
@@ -114,8 +135,32 @@
- (CPMenuItem)_createMenuItemWithTitle:(CPString )title
{
title = [[_ruleEditor standardLocalizer] localizedStringForString:title];
return [[CPMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
var originalTitle = title;
var localizedTitle = [[_ruleEditor standardLocalizer] localizedStringForString:title];
var item = [[CPMenuItem alloc] initWithTitle:localizedTitle action:nil keyEquivalent:@""];
// Cache the raw English title for pattern-matching
item._originalTitle = originalTitle;
return item;
}
- (CPTextField)_createStaticTextFieldWithStringValue:(CPString)text
{
var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()],
ruleEditorFont = [_ruleEditor font],
font = [CPFont fontWithName:[ruleEditorFont familyName] size:[ruleEditorFont size] + 2],
localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text],
size = [localizedText sizeWithFont:font];
[textField setFrameSize:CGSizeMake(size.width + 4, [_ruleEditor rowHeight])];
[textField setValue:font forThemeAttribute:@"font"];
[textField setValue:[_ruleEditor _verticalAlignment] forThemeAttribute:@"vertical-alignment"];
[textField setStringValue:localizedText];
// Cache the raw English text for pattern-matching
textField._originalText = text;
return textField;
}
- (CPPopUpButton)_createPopUpButtonWithItems:(CPArray)itemsArray selectedItemIndex:(int)index
@@ -142,22 +187,6 @@
return [CPMenuItem separatorItem];
}
- (CPTextField)_createStaticTextFieldWithStringValue:(CPString)text
{
var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()],
ruleEditorFont = [_ruleEditor font],
font = [CPFont fontWithName:[ruleEditorFont familyName] size:[ruleEditorFont size] + 2],
localizedText = [[_ruleEditor standardLocalizer] localizedStringForString:text],
size = [localizedText sizeWithFont:font];
[textField setFrameSize:CGSizeMake(size.width + 4, [_ruleEditor rowHeight])];
[textField setValue:font forThemeAttribute:@"font"];
[textField setValue:[_ruleEditor _verticalAlignment] forThemeAttribute:@"vertical-alignment"];
[textField setStringValue:localizedText];
return textField;
}
- (void)_addOption:(id)sender
{
if (_rowIndex == [_ruleEditor numberOfRows] - 1)
@@ -334,6 +363,28 @@
[_correspondingRuleItems setArray:ruleItems];
// Localize drop-down options in context and reorder/insert intermediate labels natively
var localizer = [_ruleEditor standardLocalizer];
if (localizer)
{
[localizer localizeMenuItemsForViews:_ruleOptionViews];
_ruleOptionViews = [localizer localizeAndReorderViews:_ruleOptionViews];
}
// Rebuild frame configurations to match the new localized layout order
[_ruleOptionFrames removeAllObjects];
[_ruleOptionInitialViewFrames removeAllObjects];
var newCount = [_ruleOptionViews count];
for (var i = 0; i < newCount; i++)
{
var view = [_ruleOptionViews objectAtIndex:i],
frame = [view frame];
[_ruleOptionFrames addObject:frame];
[_ruleOptionInitialViewFrames addObject:frame];
}
if (!_editable)
[self _updateEnabledStateForSubviews];
@@ -410,6 +461,9 @@
{
[_addButton setHidden:[_ruleEditor _shouldHideAddButtonForSlice:self]];
[_subtractButton setHidden:[_ruleEditor _shouldHideSubtractButtonForSlice:self]];
[_addButton setToolTip:[_ruleEditor _toolTipForAddSimpleRowButton]];
[_subtractButton setToolTip:[_ruleEditor _toolTipForDeleteRowButton]];
}
- (void)_configurePlusButtonByRowType:(CPRuleEditorRowType)type
@@ -492,9 +546,23 @@
- (void)viewDidMoveToWindow
{
[super viewDidMoveToWindow];
[self _updateButtonImages];
[self layoutSubviews];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
[self _updateButtonImages];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self _updateButtonImages];
}
- (void)_addObservers
{
if (_isObserving)
+309 -37
View File
@@ -29,8 +29,10 @@
@import "CPClipView.j"
@import "CPScroller.j"
@import "CPView.j"
@import "CPRulerView.j"
@class CPTableView
@class CPRulerView
#define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden])
@@ -94,9 +96,19 @@ var TIMER_INTERVAL = 0.2,
CPScrollViewFadeOutTime = 1.3;
var CPScrollViewWillStartLiveScrollNotification = @"CPScrollViewWillStartLiveScrollNotification",
CPScrollViewDidLiveScrollNotification = @"CPScrollViewDidLiveScrollNotification",
CPScrollViewDidEndLiveScrollNotification = @"CPScrollViewDidEndLiveScrollNotification";
var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
CPScrollerStyleGlobalChangeNotification = @"CPScrollerStyleGlobalChangeNotification";
var CPScrollViewBorderSuffixes = @[@"no-border", @"line-border", @"bezel-border", @"groove-border"];
// _CPScrollViews will hold all created CPScrollView's in order to propagate changes
// of scroller global style
var _CPScrollViews;
/*!
@ingroup appkit
@class CPScrollView
@@ -136,11 +148,19 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
int _scrollerStyle;
int _scrollerKnobStyle;
// Ruler Support
BOOL _hasVerticalRuler;
BOOL _hasHorizontalRuler;
BOOL _rulersVisible;
CPRulerView _verticalRuler;
CPRulerView _horizontalRuler;
}
#pragma mark -
#pragma mark Class methods
// MARK: -
// MARK: Class methods
+ (void)initialize
{
@@ -153,6 +173,8 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
CPScrollerStyleGlobal = _isBrowserUsingOverlayScrollers() ? CPScrollerStyleOverlay : CPScrollerStyleLegacy
else
CPScrollerStyleGlobal = globalValue;
_CPScrollViews = @[];
}
+ (CPString)defaultThemeClass
@@ -164,7 +186,15 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
{
return @{
@"bottom-corner-color": [CPColor whiteColor],
@"border-color": [CPColor blackColor]
@"border-color": [CPColor blackColor],
@"content-inset-no-border": CGInsetMake(0, 0, 0, 0),
@"content-inset-line-border": CGInsetMake(1, 1, 1, 1),
@"content-inset-bezel-border": CGInsetMake(1, 1, 1, 1),
@"content-inset-groove-border": CGInsetMake(2, 2, 2, 2),
@"background-color-no-border": [CPNull null],
@"background-color-line-border": [CPNull null],
@"background-color-bezel-border": [CPNull null],
@"background-color-groove-border": [CPNull null]
};
}
@@ -233,19 +263,35 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
+ (CGRect)_insetBounds:(CGRect)bounds borderType:(CPBorderType)borderType
{
// First, we have to check if we are compiling a theme or running an application because if working on a theme,
// we can't use theme attributes to determine the inset ! This would be a kind of circular reference...
var compilingATheme = [[[CPBundle mainBundle] objectForInfoDictionaryKey:@"CPApplicationDelegateClass"] isEqualToString:@"BKShowcaseController"];
if (compilingATheme)
return bounds;
var contentInset = [[CPTheme defaultTheme] valueForAttributeWithName:@"content-inset-"+CPScrollViewBorderSuffixes[borderType] forClass:CPScrollView];
// As this is a class method, we don't have object attributes, so if the theme doesn't declare content insets, we don't automatically get default value.
// Get it by hand.
if (!contentInset)
contentInset = [[self themeAttributes] objectForKey:@"content-inset-"+CPScrollViewBorderSuffixes[borderType]];
switch (borderType)
{
case CPNoBorder:
case CPLineBorder:
case CPBezelBorder:
return CGRectInset(bounds, 1.0, 1.0);
return CGRectInsetByInset(bounds, contentInset);
case CPGrooveBorder:
bounds = CGRectInset(bounds, 2.0, 2.0);
// FIXME: Do something better with this
bounds = CGRectInsetByInset(bounds, contentInset);
++bounds.origin.y;
--bounds.size.height;
return bounds;
case CPNoBorder:
default:
return bounds;
}
@@ -267,12 +313,15 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
+ (void)setGlobalScrollerStyle:(CPScrollerStyle)aStyle
{
CPScrollerStyleGlobal = aStyle;
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollerStyleGlobalChangeNotification object:nil];
// We propagate the new scroller global style to all existing CPScrollView's
for (var i = 0, count = [_CPScrollViews count]; i < count; i++)
[_CPScrollViews[i] setScrollerStyle:CPScrollerStyleGlobal];
}
#pragma mark -
#pragma mark Initialization
// MARK: -
// MARK: Initialization
- (id)initWithFrame:(CGRect)aFrame
{
@@ -302,17 +351,23 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
_scrollerKnobStyle = CPScrollerKnobStyleDefault;
[self setScrollerStyle:CPScrollerStyleGlobal];
_hasVerticalRuler = NO;
_hasHorizontalRuler = NO;
_rulersVisible = NO;
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
[_CPScrollViews addObject:self];
}
return self;
}
#pragma mark -
#pragma mark Getters / Setters
// MARK: -
// MARK: Getters / Setters
/*!
The delegate of the scroll view
@@ -790,8 +845,105 @@ Notifies the delegate when the scroll view has finished scrolling.
}
#pragma mark -
#pragma mark Privates
// MARK: -
// MARK: Rulers
- (BOOL)hasHorizontalRuler
{
return _hasHorizontalRuler;
}
- (void)setHasHorizontalRuler:(BOOL)shouldHaveHorizontalRuler
{
if (_hasHorizontalRuler === shouldHaveHorizontalRuler)
return;
_hasHorizontalRuler = shouldHaveHorizontalRuler;
if (_hasHorizontalRuler && !_horizontalRuler)
{
_horizontalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationHorizontal];
}
[self tile];
}
- (BOOL)hasVerticalRuler
{
return _hasVerticalRuler;
}
- (void)setHasVerticalRuler:(BOOL)shouldHaveVerticalRuler
{
if (_hasVerticalRuler === shouldHaveVerticalRuler)
return;
_hasVerticalRuler = shouldHaveVerticalRuler;
if (_hasVerticalRuler && !_verticalRuler)
{
_verticalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationVertical];
}
[self tile];
}
- (BOOL)rulersVisible
{
return _rulersVisible;
}
- (void)setRulersVisible:(BOOL)areRulersVisible
{
if (_rulersVisible === areRulersVisible)
return;
_rulersVisible = areRulersVisible;
[self tile];
}
- (CPRulerView)horizontalRulerView
{
return _horizontalRuler;
}
- (void)setHorizontalRulerView:(CPRulerView)aRulerView
{
if (_horizontalRuler === aRulerView)
return;
[_horizontalRuler removeFromSuperview];
_horizontalRuler = aRulerView;
if (_horizontalRuler)
[self addSubview:_horizontalRuler];
[self tile];
}
- (CPRulerView)verticalRulerView
{
return _verticalRuler;
}
- (void)setVerticalRulerView:(CPRulerView)aRulerView
{
if (_verticalRuler === aRulerView)
return;
[_verticalRuler removeFromSuperview];
_verticalRuler = aRulerView;
if (_verticalRuler)
[self addSubview:_verticalRuler];
[self tile];
}
// MARK: -
// MARK: Privates
/* @ignore */
- (void)_updateScrollerStyle
@@ -987,6 +1139,8 @@ Notifies the delegate when the scroll view has finished scrolling.
[self _sendDelegateMessages];
[_contentView scrollToPoint:contentBounds.origin];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
}
/* @ignore */
@@ -1025,6 +1179,8 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView scrollToPoint:contentBounds.origin];
[_headerClipView scrollToPoint:CGPointMake(contentBounds.origin.x, 0.0)];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
}
/* @ignore */
@@ -1035,6 +1191,7 @@ Notifies the delegate when the scroll view has finished scrolling.
if (!_scrollTimer)
{
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewWillStartLiveScrollNotification object:self];
[self _scrollViewWillScroll];
_scrollTimer = [CPTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(_scrollViewDidScroll) userInfo:nil repeats:YES];
}
@@ -1073,6 +1230,8 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView scrollToPoint:constrainedOrigin];
[_headerClipView scrollToPoint:CGPointMake(constrainedOrigin.x, 0.0)];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidLiveScrollNotification object:self];
if (extraX || extraY)
[enclosingScrollView _respondToScrollWheelEventWithDeltaX:extraX deltaY:extraY];
}
@@ -1092,6 +1251,8 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_implementedDelegateMethods & CPScrollViewDelegate_scrollViewDidScroll_)
[_delegate scrollViewDidScroll:self];
[[CPNotificationCenter defaultCenter] postNotificationName:CPScrollViewDidEndLiveScrollNotification object:self];
}
/*! @ignore*/
@@ -1102,18 +1263,15 @@ Notifies the delegate when the scroll view has finished scrolling.
#pragma mark -
#pragma mark Utilities
// MARK: -
// MARK: Utilities
/*!
Lays out the scroll view's components.
*/
- (void)tile
{
// yuck.
// RESIZE: tile->setHidden AND refl
// Outside Change: refl->tile->setHidden AND refl
// scroll: refl.
[self reflectScrolledClipView:_contentView];
}
/*!
@@ -1157,6 +1315,41 @@ Notifies the delegate when the scroll view has finished scrolling.
contentFrame.origin.y += headerClipViewHeight;
contentFrame.size.height -= headerClipViewHeight;
// Adjust content view based on horizontal / vertical ruler presence
var showHorizontalRuler = _rulersVisible && _hasHorizontalRuler && _horizontalRuler,
showVerticalRuler = _rulersVisible && _hasVerticalRuler && _verticalRuler;
var horizRulerThickness = showHorizontalRuler ? ([_horizontalRuler respondsToSelector:@selector(ruleThickness)] ? [_horizontalRuler ruleThickness] : 16.0) : 0.0,
vertRulerThickness = showVerticalRuler ? ([_verticalRuler respondsToSelector:@selector(ruleThickness)] ? [_verticalRuler ruleThickness] : 24.0) : 0.0;
if (showHorizontalRuler)
{
if ([_horizontalRuler superview] !== self)
[self addSubview:_horizontalRuler];
[_horizontalRuler setHidden:NO];
}
else if (_horizontalRuler)
{
[_horizontalRuler setHidden:YES];
}
if (showVerticalRuler)
{
if ([_verticalRuler superview] !== self)
[self addSubview:_verticalRuler];
[_verticalRuler setHidden:NO];
}
else if (_verticalRuler)
{
[_verticalRuler setHidden:YES];
}
contentFrame.origin.y += horizRulerThickness;
contentFrame.size.height -= horizRulerThickness;
contentFrame.origin.x += vertRulerThickness;
contentFrame.size.width -= vertRulerThickness;
var difference = CGSizeMake(CGRectGetWidth(documentFrame) - CGRectGetWidth(contentFrame), CGRectGetHeight(documentFrame) - CGRectGetHeight(contentFrame)),
verticalScrollerWidth = [_verticalScroller scrollerWidth],
horizontalScrollerHeight = [_horizontalScroller scrollerWidth],
@@ -1249,6 +1442,7 @@ Notifies the delegate when the scroll view has finished scrolling.
[_contentView setFrame:contentFrame];
[_headerClipView setFrame:[self _headerClipViewFrame]];
[[_headerClipView documentView] setNeedsDisplay:YES];
if (SHOULD_SHOW_CORNER_VIEW())
{
[_cornerView setFrame:[self _cornerViewFrame]];
@@ -1259,10 +1453,43 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_scrollerStyle === CPScrollerStyleLegacy)
{
[[self bottomCornerView] setFrame:[self _bottomCornerViewFrame]];
var bottomCornerFrame = [self _bottomCornerViewFrame];
[[self bottomCornerView] setFrame:bottomCornerFrame];
[[self bottomCornerView] setHidden:CGRectIsEmpty(bottomCornerFrame)];
[[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]];
}
// Position and redraw rulers to track viewport updates
if (showHorizontalRuler)
{
[_horizontalRuler setFrame:CGRectMake(
CGRectGetMinX(contentFrame),
CGRectGetMinY(contentFrame) - horizRulerThickness,
CGRectGetWidth(contentFrame),
horizRulerThickness
)];
if ([_horizontalRuler respondsToSelector:@selector(updateRuler)])
[_horizontalRuler updateRuler];
else
[_horizontalRuler setNeedsDisplay:YES];
}
if (showVerticalRuler)
{
[_verticalRuler setFrame:CGRectMake(
CGRectGetMinX(contentFrame) - vertRulerThickness,
CGRectGetMinY(contentFrame),
vertRulerThickness,
CGRectGetHeight(contentFrame)
)];
if ([_verticalRuler respondsToSelector:@selector(updateRuler)])
[_verticalRuler updateRuler];
else
[_verticalRuler setNeedsDisplay:YES];
}
--_recursionCount;
}
@@ -1303,9 +1530,8 @@ Notifies the delegate when the scroll view has finished scrolling.
return [_contentView documentVisibleRect];
}
#pragma mark -
#pragma mark Overrides
// MARK: -
// MARK: Overrides
- (void)_removeObservers
{
@@ -1324,9 +1550,6 @@ Notifies the delegate when the scroll view has finished scrolling.
if (_isObserving)
return;
//Make sure to have the last global style for the scroller
[self _didReceiveDefaultStyleChange:nil];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_didReceiveDefaultStyleChange:)
name:CPScrollerStyleGlobalChangeNotification
@@ -1335,13 +1558,11 @@ Notifies the delegate when the scroll view has finished scrolling.
[super _addObservers];
}
- (void)drawRect:(CGRect)aRect
{
[super drawRect:aRect];
if (_borderType == CPNoBorder)
if ([self isCSSBased] || (_borderType == CPNoBorder))
return;
var strokeRect = [self bounds],
@@ -1425,8 +1646,8 @@ Notifies the delegate when the scroll view has finished scrolling.
y = maxY - 1.5;
CGContextMoveToPoint(context, maxX - 1.0, y);
CGContextAddLineToPoint(context, minX + 2.0, y);
CGContextMoveToPoint(maxX - 1.0, y);
CGContextAddLineToPoint(minX + 2.0, y);
x = minX + 0.5;
@@ -1544,6 +1765,37 @@ Notifies the delegate when the scroll view has finished scrolling.
@end
// MARK: -
@implementation CPScrollView (CSSTheming)
- (void)layoutSubviews
{
if (![self isCSSBased])
return;
if (_borderType !== CPNoBorder)
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color-"+CPScrollViewBorderSuffixes[_borderType]]];
if (_scrollerStyle === CPScrollerStyleLegacy)
[[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]];
}
- (BOOL)isCSSBased
{
return [[self theme] isCSSBased];
}
- (void)refreshDisplay
{
if ([self isCSSBased])
[self setNeedsLayout:YES];
else
[self setNeedsDisplay:YES];
}
@end
#pragma mark -
@implementation CPScrollView (FirstResponder)
@@ -1588,7 +1840,7 @@ Notifies the delegate when the scroll view has finished scrolling.
@end
#pragma mark -
// MARK: -
var CPScrollViewContentViewKey = @"CPScrollViewContentView",
CPScrollViewHeaderClipViewKey = @"CPScrollViewHeaderClipViewKey",
@@ -1605,7 +1857,14 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
CPScrollViewBottomCornerViewKey = @"CPScrollViewBottomCornerViewKey",
CPScrollViewBorderTypeKey = @"CPScrollViewBorderTypeKey",
CPScrollViewScrollerStyleKey = @"CPScrollViewScrollerStyleKey",
CPScrollViewScrollerKnobStyleKey = @"CPScrollViewScrollerKnobStyleKey";
CPScrollViewScrollerKnobStyleKey = @"CPScrollViewScrollerKnobStyleKey",
// Ruler Coding Keys
CPScrollViewHasVRulerKey = @"CPScrollViewHasVRuler",
CPScrollViewHasHRulerKey = @"CPScrollViewHasHRuler",
CPScrollViewRulersVisibleKey = @"CPScrollViewRulersVisible",
CPScrollViewVRulerKey = @"CPScrollViewVRuler",
CPScrollViewHRulerKey = @"CPScrollViewHRuler";
@implementation CPScrollView (CPCoding)
@@ -1640,6 +1899,14 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
_cornerView = [aCoder decodeObjectForKey:CPScrollViewCornerViewKey];
_bottomCornerView = [aCoder decodeObjectForKey:CPScrollViewBottomCornerViewKey];
// Ruler decoding
_hasVerticalRuler = [aCoder decodeBoolForKey:CPScrollViewHasVRulerKey];
_hasHorizontalRuler = [aCoder decodeBoolForKey:CPScrollViewHasHRulerKey];
_rulersVisible = [aCoder decodeBoolForKey:CPScrollViewRulersVisibleKey];
_verticalRuler = [aCoder decodeObjectForKey:CPScrollViewVRulerKey];
_horizontalRuler = [aCoder decodeObjectForKey:CPScrollViewHRulerKey];
_delegate = nil;
_scrollTimer = nil;
_implementedDelegateMethods = 0;
@@ -1647,10 +1914,7 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
_scrollerStyle = [aCoder decodeObjectForKey:CPScrollViewScrollerStyleKey] || CPScrollerStyleGlobal;
_scrollerKnobStyle = [aCoder decodeObjectForKey:CPScrollViewScrollerKnobStyleKey] || CPScrollerKnobStyleDefault;
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(_didReceiveDefaultStyleChange:)
name:CPScrollerStyleGlobalChangeNotification
object:nil];
[_CPScrollViews addObject:self];
}
return self;
@@ -1693,6 +1957,14 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
[aCoder encodeInt:_scrollerStyle forKey:CPScrollViewScrollerStyleKey];
[aCoder encodeInt:_scrollerKnobStyle forKey:CPScrollViewScrollerKnobStyleKey];
// Ruler encoding
[aCoder encodeBool:_hasVerticalRuler forKey:CPScrollViewHasVRulerKey];
[aCoder encodeBool:_hasHorizontalRuler forKey:CPScrollViewHasHRulerKey];
[aCoder encodeBool:_rulersVisible forKey:CPScrollViewRulersVisibleKey];
[aCoder encodeObject:_verticalRuler forKey:CPScrollViewVRulerKey];
[aCoder encodeObject:_horizontalRuler forKey:CPScrollViewHRulerKey];
}
@end
+37 -20
View File
@@ -23,12 +23,11 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPAnimation.j"
@import "CPControl.j"
@import "CPWindow_Constants.j"
@import "CPViewAnimation.j"
@import "CPWindow_Constants.j"
@global CPApp
@@ -102,8 +101,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
#pragma mark -
#pragma mark Class methods
// MARK: -
// MARK: Class methods
+ (CPString)defaultThemeClass
{
@@ -167,8 +166,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
#pragma mark -
#pragma mark Initialization
// MARK: -
// MARK: Initialization
- (id)initWithFrame:(CGRect)aFrame
{
@@ -203,8 +202,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
#pragma mark -
#pragma mark Getters / Setters
// MARK: -
// MARK: Getters / Setters
/*!
Returns the scroller's style
@@ -259,7 +258,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
*/
- (void)setKnobProportion:(float)aProportion
{
if (!_IS_NUMERIC(aProportion))
if (!CPIsNumeric(aProportion))
[CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric, was: "+aProportion];
_knobProportion = MIN(1.0, MAX(0.0001, aProportion));
@@ -269,8 +268,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
#pragma mark -
#pragma mark Privates
// MARK: -
// MARK: Privates
/*! @ignore */
- (void)_adjustScrollerSize
@@ -295,8 +294,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
#pragma mark -
#pragma mark Utilities
// MARK: -
// MARK: Utilities
- (CGRect)rectForPart:(CPScrollerPart)aPart
{
@@ -321,7 +320,25 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
if (![self hasThemeState:CPThemeStateSelected] && ![self hasThemeState:CPThemeStateScrollViewLegacy])
return CPScrollerNoPart;
if (CGRectContainsPoint([self rectForPart:CPScrollerKnob], aPoint))
// Fetch the visual rect for the knob
var bounds = [self bounds],
knobRect = [self rectForPart:CPScrollerKnob],
hitKnobRect = CGRectMake(CGRectGetMinX(knobRect), CGRectGetMinY(knobRect), CGRectGetWidth(knobRect), CGRectGetHeight(knobRect));
// Expand the hit test rect to span the entire track on the minor axis
// so dragging works smoothly even if the user clicks slightly off-center.
if ([self isVertical])
{
hitKnobRect.origin.x = 0;
hitKnobRect.size.width = CGRectGetWidth(bounds);
}
else
{
hitKnobRect.origin.y = 0;
hitKnobRect.size.height = CGRectGetHeight(bounds);
}
if (CGRectContainsPoint(hitKnobRect, aPoint))
return CPScrollerKnob;
if (CGRectContainsPoint([self rectForPart:CPScrollerDecrementPage], aPoint))
@@ -468,8 +485,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
#pragma mark -
#pragma mark Drawing
// MARK: -
// MARK: Drawing
/*!
Draws the specified arrow and sets the highlight.
@@ -703,8 +720,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
}
#pragma mark -
#pragma mark Overrides
// MARK: -
// MARK: Overrides
- (id)currentValueForThemeAttribute:(CPString)anAttributeName
{
@@ -780,8 +797,8 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark");
return [self currentValueForThemeAttribute:@"scroller-width"];
}
#pragma mark -
#pragma mark Delegates
// MARK: -
// MARK: Delegates
- (void)animationDidEnd:(CPAnimation)animation
{
+14 -7
View File
@@ -104,6 +104,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
_sendsWholeSearchString = NO;
_sendsSearchStringImmediately = NO;
_recentsAutosaveName = nil;
[self setPlaceholderString:@"Search"];
[self _init];
}
@@ -122,8 +124,13 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
[self setContinuous:YES];
var bounds = [self bounds],
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]],
searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
cancelButton = nil,
searchButton = nil;
#if PLATFORM(DOM)
cancelButton = [[CPButton alloc] initWithFrame:[self cancelButtonRectForBounds:bounds]];
searchButton = [[CPButton alloc] initWithFrame:[self searchButtonRectForBounds:bounds]];
#endif
[self setCancelButton:cancelButton];
[self resetCancelButton];
@@ -136,8 +143,8 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
}
#pragma mark -
#pragma mark Override observers
// MARK: -
// MARK: Override observers
- (void)_removeObservers
{
@@ -788,7 +795,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@end
#pragma mark -
// MARK: -
@implementation CPSearchField (ThemingAdditions)
@@ -980,7 +987,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@end
#pragma mark -
// MARK: -
@implementation CPSearchField (CPTrackingArea)
{
@@ -1035,7 +1042,7 @@ var CPAutosavedRecentsChangedNotification = @"CPAutosavedRecentsChangedNotificat
@end
#pragma mark -
// MARK: -
var CPRecentsAutosaveNameKey = @"CPRecentsAutosaveNameKey",
CPSendsWholeSearchStringKey = @"CPSendsWholeSearchStringKey",
+283 -153
View File
@@ -71,7 +71,7 @@ CPSegmentSwitchTrackingMomentary = 2;
@"right-segment-bezel-color": [CPNull null],
@"center-segment-bezel-color": [CPNull null],
@"divider-bezel-color": [CPNull null],
@"divider-thickness": 1.0,
@"divider-thickness": 1.0
};
}
@@ -316,7 +316,7 @@ CPSegmentSwitchTrackingMomentary = 2;
// Working with Individual Segments
/*!
Sets the width of the specified segment.
@param aWidth the new width for the segment
@param aWidth the new width for the segment (0 for automatic width)
@param aSegment the segment to set the width for
@throws CPRangeException if \c aSegment is out of bounds
*/
@@ -420,7 +420,14 @@ CPSegmentSwitchTrackingMomentary = 2;
[segment setSelected:isSelected];
_themeStates[aSegment] = isSelected ? CPThemeStateSelected : CPThemeStateNormal;
if (_themeStates[aSegment] == undefined)
_themeStates[aSegment] = CPThemeStateNormal;
// Update state using .and() / .without() to preserve existing states (like Disabled)
if (isSelected)
_themeStates[aSegment] = _themeStates[aSegment].and(CPThemeStateSelected);
else
_themeStates[aSegment] = _themeStates[aSegment].without(CPThemeStateSelected);
// We need to do some cleanup if we only allow one selection.
if (isSelected)
@@ -432,7 +439,10 @@ CPSegmentSwitchTrackingMomentary = 2;
if (_trackingMode == CPSegmentSwitchTrackingSelectOne && oldSelectedSegment != aSegment && oldSelectedSegment != -1 && oldSelectedSegment < _segments.length)
{
[_segments[oldSelectedSegment] setSelected:NO];
_themeStates[oldSelectedSegment] = CPThemeStateNormal;
// Update old segment using .without() to preserve other states
// Previously: _themeStates[oldSelectedSegment] = CPThemeStateNormal;
_themeStates[oldSelectedSegment] = _themeStates[oldSelectedSegment].without(CPThemeStateSelected);
[self drawSegmentBezel:oldSelectedSegment highlight:NO];
}
@@ -530,12 +540,24 @@ CPSegmentSwitchTrackingMomentary = 2;
- (float)_leftOffsetForSegment:(unsigned)segment
{
if (segment == 0)
return [self currentValueForThemeAttribute:@"bezel-inset"].left;
if ([[self actualTheme] isCSSBased])
{
// CSS styling
if (segment == 0)
return 0;
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) - 2; // FIXME: -2 for collapse of borders
}
else
{
// Legacy styling
if (segment == 0)
return [self currentValueForThemeAttribute:@"bezel-inset"].left;
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) + thickness;
var thickness = [self currentValueForThemeAttribute:@"divider-thickness"];
return [self _leftOffsetForSegment:segment - 1] + CGRectGetWidth([self frameForSegment:segment - 1]) + thickness;
}
}
- (unsigned)_indexOfLastSegment
@@ -561,26 +583,36 @@ CPSegmentSwitchTrackingMomentary = 2;
}
else if (aName === "right-segment-bezel")
{
return CGRectMake(CGRectGetWidth([self bounds]) - contentInset.right,
bezelInset.top,
contentInset.right,
height);
// we have to FLOOR the coordinates to prevent a 1px glitch in Safari
return CGRectMake(FLOOR(CGRectGetWidth([self bounds]) - contentInset.right),
FLOOR(bezelInset.top),
FLOOR(contentInset.right),
FLOOR(height));
}
else if (aName.indexOf("segment-bezel") === 0)
{
var segment = parseInt(aName.substring("segment-bezel-".length), 10),
if ([[self actualTheme] isCSSBased])
{
var segment = parseInt(aName.substring("segment-bezel-".length), 10);
return [self bezelFrameForSegment:segment];
}
else
{
var segment = parseInt(aName.substring("segment-bezel-".length), 10),
frame = CGRectCreateCopy([self frameForSegment:segment]);
if (segment === 0)
{
frame.origin.x += contentInset.left;
frame.size.width -= contentInset.left;
if (segment === 0)
{
frame.origin.x += contentInset.left;
frame.size.width -= contentInset.left;
}
if (segment === [self segmentCount] - 1)
frame.size.width = CGRectGetWidth([self bounds]) - contentInset.right - frame.origin.x;
return frame;
}
if (segment === [self segmentCount] - 1)
frame.size.width = CGRectGetWidth([self bounds]) - contentInset.right - frame.origin.x;
return frame;
}
else if (aName.indexOf("divider-bezel") === 0)
{
@@ -619,48 +651,85 @@ CPSegmentSwitchTrackingMomentary = 2;
if ([self segmentCount] <= 0)
return;
var themeState = _themeStates[0],
isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini];
// Check for HUD state globally
var isHUD = [self hasThemeState:CPThemeStateHUD];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
var leftCapColor = [self valueForThemeAttribute:@"left-segment-bezel-color"
inState:themeState],
leftBezelView = [self layoutEphemeralSubviewNamed:@"left-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[leftBezelView setBackgroundColor:leftCapColor];
var themeState = _themeStates[_themeStates.length - 1];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
var rightCapColor = [self valueForThemeAttribute:@"right-segment-bezel-color"
inState:themeState],
rightBezelView = [self layoutEphemeralSubviewNamed:@"right-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[rightBezelView setBackgroundColor:rightCapColor];
for (var i = 0, count = _themeStates.length; i < count; i++)
if ([[self actualTheme] isCSSBased])
{
var themeState = _themeStates[i];
// CSS Styling
var isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini],
isKeyWindow = [self hasThemeState:CPThemeStateKeyWindow];
for (var i = 0, count = _themeStates.length; i < count; i++)
{
var themeState = _themeStates[i];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
// Apply HUD state to lookup
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
themeState = isKeyWindow ? themeState.and(CPThemeStateKeyWindow) : themeState;
var bezelColor,
segment = _segments[i],
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:"segment-bezel-" + i];
if (i == 0)
bezelColor = [self valueForThemeAttribute:@"left-segment-bezel-color" inState:themeState];
else if (i < count - 1)
bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color" inState:themeState];
else
bezelColor = [self valueForThemeAttribute:@"right-segment-bezel-color" inState:themeState];
[bezelView setBackgroundColor:bezelColor];
// Trick : Put selected segments over unselected ones to automaticaly manage borders
#if PLATFORM(DOM)
contentView._DOMElement.style.zIndex = ([segment selected] ? 1 : 0);
bezelView._DOMElement.style.zIndex = ([segment selected] ? 1 : 0);
#endif
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:([segment enabled] ? themeState : themeState.and(CPThemeStateDisabled))]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
}
}
else
{
// Legacy (Canvas) Styling
var themeState = _themeStates[0],
isDisabled = [self hasThemeState:CPThemeStateDisabled],
isControlSizeSmall = [self hasThemeState:CPThemeStateControlSizeSmall],
isControlSizeMini = [self hasThemeState:CPThemeStateControlSizeMini];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
@@ -669,59 +738,114 @@ CPSegmentSwitchTrackingMomentary = 2;
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
var bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color"
inState:themeState],
// Apply HUD state to Left Cap
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
var leftCapColor = [self valueForThemeAttribute:@"left-segment-bezel-color"
inState:themeState],
[bezelView setBackgroundColor:bezelColor];
leftBezelView = [self layoutEphemeralSubviewNamed:@"left-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
// layout image/title views
var segment = _segments[i],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
[leftBezelView setBackgroundColor:leftCapColor];
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
var themeState = _themeStates[_themeStates.length - 1];
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:themeState]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
if (i == count - 1)
continue;
var borderState = _themeStates[i].and(_themeStates[i + 1]);
borderState = isDisabled ? borderState.and(CPThemeStateDisabled) : borderState;
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
borderState = borderState.and(CPThemeStateControlSizeSmall);
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
borderState = borderState.and(CPThemeStateControlSizeMini);
themeState = themeState.and(CPThemeStateControlSizeMini);
var borderColor = [self valueForThemeAttribute:@"divider-bezel-color"
inState:borderState],
// Apply HUD state to Right Cap
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
var rightCapColor = [self valueForThemeAttribute:@"right-segment-bezel-color"
inState:themeState],
rightBezelView = [self layoutEphemeralSubviewNamed:@"right-segment-bezel"
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[rightBezelView setBackgroundColor:rightCapColor];
for (var i = 0, count = _themeStates.length; i < count; i++)
{
var themeState = _themeStates[i];
themeState = isDisabled ? themeState.and(CPThemeStateDisabled) : themeState;
if (isControlSizeSmall)
themeState = themeState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
themeState = themeState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Center Segments
if (isHUD)
themeState = themeState.and(CPThemeStateHUD);
var bezelColor = [self valueForThemeAttribute:@"center-segment-bezel-color"
inState:themeState],
bezelView = [self layoutEphemeralSubviewNamed:"segment-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[bezelView setBackgroundColor:bezelColor];
// layout image/title views
var segment = _segments[i],
contentView = [self layoutEphemeralSubviewNamed:@"segment-content-" + i
positioned:CPWindowAbove
relativeToEphemeralSubviewNamed:@"segment-bezel-" + i];
[contentView setText:[segment label]];
[contentView setImage:[segment image]];
[contentView setFont:[self valueForThemeAttribute:@"font" inState:themeState]];
[contentView setTextColor:[self valueForThemeAttribute:@"text-color" inState:themeState]];
[contentView setAlignment:[self valueForThemeAttribute:@"alignment" inState:themeState]];
[contentView setVerticalAlignment:[self valueForThemeAttribute:@"vertical-alignment" inState:themeState]];
[contentView setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode" inState:themeState]];
[contentView setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color" inState:themeState]];
[contentView setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset" inState:themeState]];
[contentView setImageScaling:[self valueForThemeAttribute:@"image-scaling" inState:themeState]];
if ([segment image] && [segment label])
[contentView setImagePosition:[self valueForThemeAttribute:@"image-position" inState:themeState]];
else if ([segment image])
[contentView setImagePosition:CPImageOnly];
if (i == count - 1)
continue;
var borderState = _themeStates[i].and(_themeStates[i + 1]);
borderState = isDisabled ? borderState.and(CPThemeStateDisabled) : borderState;
if (isControlSizeSmall)
borderState = borderState.and(CPThemeStateControlSizeSmall);
else if (isControlSizeMini)
borderState = borderState.and(CPThemeStateControlSizeMini);
// Apply HUD state to Dividers
if (isHUD)
borderState = borderState.and(CPThemeStateHUD);
var borderColor = [self valueForThemeAttribute:@"divider-bezel-color"
inState:borderState],
borderView = [self layoutEphemeralSubviewNamed:"divider-bezel-" + i
positioned:CPWindowBelow
relativeToEphemeralSubviewNamed:nil];
[borderView setBackgroundColor:borderColor];
[borderView setBackgroundColor:borderColor];
}
}
}
@@ -802,8 +926,10 @@ CPSegmentSwitchTrackingMomentary = 2;
label = [segment label],
image = [segment image];
contentInsetWidth += ([[self actualTheme] isCSSBased] && ((aSegment == 0) || (aSegment == [self _indexOfLastSegment])) ? [self valueForThemeAttribute:@"divider-thickness" inState:themeState] : 0);
// add 1 pixel to account for possible fractional pixels at right edge
width = (label ? [label sizeWithFont:[self font]].width + 1 : 4.0) + (image ? [image size].width : 0) + contentInsetWidth;
width = CEIL((label ? [label sizeWithFont:[self font]].width + 1: 4.0) + (image ? [image size].width : 0) + contentInsetWidth);
}
return CGRectMake(left, top, width, height);
@@ -811,6 +937,14 @@ CPSegmentSwitchTrackingMomentary = 2;
- (CGRect)contentFrameForSegment:(unsigned)aSegment
{
if ([[self actualTheme] isCSSBased])
{
var bezelFrame = [self bezelFrameForSegment:aSegment],
contentInset = [self currentValueForThemeAttribute:@"content-inset"];
return CGRectInsetByInset(bezelFrame, contentInset);
}
var height = [self currentValueForThemeAttribute:@"min-size"].height,
contentInset = [self currentValueForThemeAttribute:@"content-inset"],
width = CGRectGetWidth([self frameForSegment:aSegment]),
@@ -873,68 +1007,64 @@ CPSegmentSwitchTrackingMomentary = 2;
var type = [anEvent type],
location = [self convertPoint:[anEvent locationInWindow] fromView:nil];
switch (type)
if (type == CPLeftMouseUp)
{
case CPLeftMouseUp:
if (_trackingSegment === CPNotFound)
return;
if (_trackingSegment === [self testSegment:location])
{
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
{
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
// With ANY, _selectedSegment means last pressed.
_selectedSegment = _trackingSegment;
}
else
[self setSelected:YES forSegment:_trackingSegment];
[self sendAction:[self action] to:[self target]];
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
{
[self setSelected:NO forSegment:_trackingSegment];
_selectedSegment = CPNotFound;
}
}
[self drawSegmentBezel:_trackingSegment highlight:NO];
_trackingSegment = CPNotFound;
if (_trackingSegment == -1)
return;
case CPLeftMouseDown:
var trackingSegment = [self testSegment:location];
if (trackingSegment > CPNotFound && [self isEnabledForSegment:trackingSegment])
if (_trackingSegment === [self testSegment:location])
{
if (_trackingMode == CPSegmentSwitchTrackingSelectAny)
{
_trackingHighlighted = YES;
_trackingSegment = trackingSegment;
[self drawSegmentBezel:_trackingSegment highlight:YES];
[self setSelected:![self isSelectedForSegment:_trackingSegment] forSegment:_trackingSegment];
// With ANY, _selectedSegment means last pressed.
_selectedSegment = _trackingSegment;
}
else
[self setSelected:YES forSegment:_trackingSegment];
break;
[self sendAction:[self action] to:[self target]];
case CPLeftMouseDragged:
if (_trackingSegment === CPNotFound)
return;
var highlighted = [self testSegment:location] === _trackingSegment;
if (highlighted != _trackingHighlighted)
if (_trackingMode == CPSegmentSwitchTrackingMomentary)
{
_trackingHighlighted = highlighted;
[self setSelected:NO forSegment:_trackingSegment];
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
_selectedSegment = CPNotFound;
}
}
break;
[self drawSegmentBezel:_trackingSegment highlight:NO];
_trackingSegment = -1;
return;
}
if (type == CPLeftMouseDown)
{
var trackingSegment = [self testSegment:location];
if (trackingSegment > -1 && [self isEnabledForSegment:trackingSegment])
{
_trackingHighlighted = YES;
_trackingSegment = trackingSegment;
[self drawSegmentBezel:_trackingSegment highlight:YES];
}
}
else if (type == CPLeftMouseDragged)
{
if (_trackingSegment == -1)
return;
var highlighted = [self testSegment:location] === _trackingSegment;
if (highlighted != _trackingHighlighted)
{
_trackingHighlighted = highlighted;
[self drawSegmentBezel:_trackingSegment highlight:_trackingHighlighted];
}
}
[CPApp setTarget:self selector:@selector(trackSegment:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES];
+1 -1
View File
@@ -102,6 +102,7 @@ CPThemeStateShadowViewHeavy = CPThemeState("shadowview-style-heavy");
[self setWeight:CPLightShadow];
[self setHitTests:NO];
[self setClipsToBounds:NO];
}
return self;
@@ -169,7 +170,6 @@ CPThemeStateShadowViewHeavy = CPThemeState("shadowview-style-heavy");
- (void)layoutSubviews
{
[super layoutSubviews];
[self setBackgroundColor:[self currentValueForThemeAttribute:@"bezel-color"]];
}
+10 -4
View File
@@ -312,6 +312,12 @@ var AFFINITY = 5;
if (!trackRect || CGRectIsEmpty(trackRect))
trackRect = bounds;
if (_allowsTickMarkValuesOnly)
{
[self closestTickMarkValueToValue:[self doubleValue]]; // we only need the side effect …
_currentTickMarkSegment = _closestTickMarkIndex;
}
if (_isCircular)
{
var angle = 3 * PI_2 - (1.0 - [self doubleValue] - _minValue) / (_maxValue - _minValue) * PI2,
@@ -580,7 +586,7 @@ var AFFINITY = 5;
return [self setFloatValue:1.0];
}
#pragma mark - New methods as in High Sierra (10.13)
// MARK: - New methods as in High Sierra (10.13)
/*!
Creates and returns a continuous horizontal slider whose values range from 0.0 to 1.0.
@@ -806,7 +812,7 @@ var AFFINITY = 5;
return [self valueForThemeAttribute:@"left-track-color" inStates:normalState];
}
#pragma mark - Private methods
// MARK: - Private methods
- (void)_refreshCachesAndStates
{
@@ -1044,7 +1050,7 @@ var AFFINITY = 5;
return value;
}
#pragma mark - Overrides
// MARK: - Overrides
- (void)setFrameSize:(CGSize)aSize
{
@@ -1054,7 +1060,7 @@ var AFFINITY = 5;
[self setNeedsDisplay:YES];
}
#pragma mark -
// MARK: -
@end
+11 -11
View File
@@ -66,8 +66,8 @@ CPSoundPlayBackStatePause = 2;
unsigned _implementedDelegateMethods;
}
#pragma mark -
#pragma mark Initialization
// MARK: -
// MARK: Initialization
- (id)init
{
@@ -147,8 +147,8 @@ CPSoundPlayBackStatePause = 2;
}
#pragma mark -
#pragma mark Delegate methods
// MARK: -
// MARK: Delegate methods
/*!
Sets the sound's delegate.
@@ -166,8 +166,8 @@ CPSoundPlayBackStatePause = 2;
_implementedDelegateMethods |= CPSoundDelegate_sound_didFinishPlaying_;
}
#pragma mark -
#pragma mark Events listener
// MARK: -
// MARK: Events listener
/*! @ignore
*/
@@ -199,8 +199,8 @@ CPSoundPlayBackStatePause = 2;
}
#pragma mark -
#pragma mark Media controls
// MARK: -
// MARK: Media controls
/*!
Play the sound.
@@ -324,8 +324,8 @@ CPSoundPlayBackStatePause = 2;
_audioTag.volume = aVolume;
}
#pragma mark -
#pragma mark Accessors
// MARK: -
// MARK: Accessors
/*!
Returns the duration in seconds of the sound.
@@ -364,4 +364,4 @@ CPSoundPlayBackStatePause = 2;
[_delegate sound:self didFinishPlaying:finishedPlaying];
}
@end
@end
+20 -16
View File
@@ -23,13 +23,11 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "../Foundation/Foundation.h"
@import "CPButtonBar.j"
@import "CPImage.j"
@import "CPView.j"
@import "CPCursor.j"
@import "CPImage.j"
@import "CPTrackingArea.j"
@import "CPView.j"
@class CPUserDefaults
@global CPApp
@@ -216,7 +214,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[self _setVertical:YES];
}
#pragma mark - Properties
// MARK: - Properties
- (CPSplitViewDividerStyle)dividerStyle
{
@@ -479,7 +477,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
return _delegate;
}
#pragma mark - Subviews management
// MARK: - Subviews management
// FIXME: il faut également tenir compte des button bars quand on ajouter / insert une vue.
// Par exemple, si une button bar est placée sur la dernière vue, pas de resize à droite mais
@@ -652,7 +650,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
_subviewsManagementDisabled = NO;
}
#pragma mark - Layout subviews
// MARK: - Layout subviews
- (CGRect)rectOfDividerAtIndex:(int)aDivider
{
@@ -824,6 +822,9 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
- (void)layoutSubviews
{
// Retrieve the current color based on the current theme state (Standard vs HUD)
var currentDividerColor = [self dividerColor];
for (var i = 0, count = _arrangedSubviews.length, position = 0, origin; i < count; i++)
{
origin = CGPointMakeZero();
@@ -838,6 +839,9 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
origin[_originComponent] = position;
position += [_dividerSubviews[i] frame].size[_sizeComponent];
// Apply the color to the specific divider view
[_dividerSubviews[i] setBackgroundColor:currentDividerColor];
[_dividerSubviews[i] setFrameOrigin:origin];
}
}
@@ -845,7 +849,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[self updateTrackingAreas];
}
#pragma mark - Private layout utilities
// MARK: - Private layout utilities
- (void)_distribute:(CPInteger)remainingSpace amoung:(CPInteger)count onFlexible:(BOOL)onFlexible fromIndex:(CPInteger)fromIndex toIndex:(CPInteger)toIndex
{
@@ -900,7 +904,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
[_ratios addObject:(_initialSizes[i] / fixedSpace)];
}
#pragma mark -
// MARK: -
/*!
Returns YES if the supplied subview is collapsed, otherwise NO.
@@ -1171,7 +1175,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
// Silently ignore bad positions which could result from odd delegate responses. We don't want these
// bad results to go into the system and cause havoc with frame sizes as the split view tries to resize
// its subviews.
if (_IS_NUMERIC(proposedPosition))
if (CPIsNumeric(proposedPosition))
position = proposedPosition;
var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex],
@@ -1181,10 +1185,10 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
proposedActualMin = [self _sendDelegateSplitViewConstrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex],
proposedActualMax = [self _sendDelegateSplitViewConstrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex];
if (_IS_NUMERIC(proposedActualMin))
if (CPIsNumeric(proposedActualMin))
actualMin = proposedActualMin;
if (_IS_NUMERIC(proposedActualMax))
if (CPIsNumeric(proposedActualMax))
actualMax = proposedActualMax;
var viewA = _arrangedSubviews[dividerIndex],
@@ -1487,7 +1491,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
@end
#pragma mark -
// MARK: -
@implementation CPSplitView (CPTrackingArea)
{
@@ -1567,7 +1571,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
@end
#pragma mark -
// MARK: -
@implementation CPSplitView (CPSplitViewDelegate)
@@ -1745,7 +1749,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle
@end
#pragma mark -
// MARK: -
var CPSplitViewDelegateKey = @"CPSplitViewDelegateKey",
CPSplitViewIsVerticalKey = @"CPSplitViewIsVerticalKey",
@@ -1870,7 +1874,7 @@ var CPSplitViewDelegateKey = @"CPSplitViewDelegateKey",
@end
#pragma mark -
// MARK: -
@implementation CPSplitView (Deprecated)
+388
View File
@@ -0,0 +1,388 @@
/*
* CPSplitViewController.j
*
* Created by Daniel Boehringer on September 2, 2025.
* Copyright (c) 2025 Daniel Boehringer. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPViewController.j"
@import "CPSplitView.j"
var CPSplitViewControllerAutomaticDimension = -1.0;
/**
* A container view controller that manages two or more child view
* controllers in a split view interface.
*
* This class provides a controller-level abstraction for CPSplitView,
* managing the addition, removal, and arrangement of view controllers
* through CPSplitViewItem instances.
*
* This implementation was synthesized with the assistance of an LLM,
* directed by the author.
*/
@implementation CPSplitViewController : CPViewController <CPSplitViewDelegate>
{
/** @private The underlying split view that arranges the child views. */
CPSplitView _splitView;
/** @private An array of CPSplitViewItem objects managed by this controller. */
CPMutableArray _splitViewItems;
/** @private The minimum thickness for sidebars to be displayed inline. */
CPNumber _minimumThicknessForInlineSidebars;
}
// MARK: - Initialization
- (id)init
{
if (self = [super init])
{
_splitViewItems = [CPMutableArray array];
_minimumThicknessForInlineSidebars = 20.0;
}
return self;
}
// MARK: - View Lifecycle
- (void)loadView
{
if (!_splitView)
{
_splitView = [[CPSplitView alloc] initWithFrame:CGRectMake(0,0,400,400)];
[_splitView setDelegate:self];
}
[self setView:_splitView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
for (var i = 0; i < [_splitViewItems count]; i++)
{
var viewController = [[_splitViewItems objectAtIndex:i] viewController];
[[self splitView] addArrangedSubview:[viewController view]];
}
}
// MARK: - Accessors
/**
* Returns the CPSplitView instance managed by the controller.
*
* @returns {CPSplitView} The split view.
*/
- (CPSplitView)splitView
{
return _splitView;
}
/**
* Sets a custom split view for the controller.
*
* @param {CPSplitView} splitView The custom split view to use.
*/
- (void)setSplitView:(CPSplitView)splitView
{
if (_splitView !== splitView)
{
_splitView = splitView;
[_splitView setDelegate:self];
if ([self isViewLoaded])
[self setView:_splitView];
}
}
/**
* Returns the array of split view items.
*
* @returns {CPArray} The array of CPSplitViewItem objects.
*/
- (CPArray)splitViewItems
{
return _splitViewItems;
}
/**
* Sets the array of split view items, replacing any existing items.
*
* @param {CPArray} splitViewItems An array of CPSplitViewItem objects.
*/
- (void)setSplitViewItems:(CPArray)splitViewItems
{
// Remove all existing items
while ([_splitViewItems count] > 0)
{
[self removeSplitViewItem:[_splitViewItems lastObject]];
}
// Add new items
for (var i = 0; i < [splitViewItems count]; i++)
{
[self addSplitViewItem:[splitViewItems objectAtIndex:i]];
}
}
// MARK: - Managing Split View Items
/**
* Adds a split view item to the end of the split view.
*
* @param {CPSplitViewItem} splitViewItem The split view item to add.
*/
- (void)addSplitViewItem:(CPSplitViewItem)splitViewItem
{
[self insertSplitViewItem:splitViewItem atIndex:[_splitViewItems count]];
}
/**
* Inserts a split view item at a specific index.
*
* @param {CPSplitViewItem} splitViewItem The split view item to insert.
* @param {CPInteger} index The zero-based index at which to insert the item.
*/
- (void)insertSplitViewItem:(CPSplitViewItem)splitViewItem atIndex:(CPInteger)index
{
[splitViewItem _setSplitViewController:self];
[_splitViewItems insertObject:splitViewItem atIndex:index];
[self addChildViewController:[splitViewItem viewController]];
if ([self isViewLoaded])
[[self splitView] insertArrangedSubview:[[splitViewItem viewController] view] atIndex:index];
}
/**
* Removes the specified split view item.
*
* @param {CPSplitViewItem} splitViewItem The split view item to remove.
*/
- (void)removeSplitViewItem:(CPSplitViewItem)splitViewItem
{
var viewController = [splitViewItem viewController];
if ([self isViewLoaded])
[[viewController view] removeFromSuperview];
[viewController removeFromParentViewController];
[splitViewItem _setSplitViewController:nil];
[_splitViewItems removeObject:splitViewItem];
}
/**
* Retrieves the split view item associated with a given view controller.
*
* @param {CPViewController} viewController The view controller to find.
* @returns {CPSplitViewItem | null} The corresponding split view item, or nil if not found.
*/
- (CPSplitViewItem)splitViewItemForViewController:(CPViewController)viewController
{
for (var i = 0; i < [_splitViewItems count]; i++)
{
var item = [_splitViewItems objectAtIndex:i];
if ([item viewController] === viewController)
return item;
}
return nil;
}
// MARK: - Managing Sidebars and Inspectors
/**
* Toggles the collapsed state of the first split view item, typically a sidebar.
*
* @param {id} sender The object that initiated the action.
*/
- (void)toggleSidebar:(id)sender
{
if ([_splitViewItems count] > 0)
{
var sidebarItem = [_splitViewItems objectAtIndex:0];
[sidebarItem setCollapsed:![sidebarItem isCollapsed]];
}
}
/**
* Toggles the collapsed state of the last split view item, typically an inspector.
*
* @param {id} sender The object that initiated the action.
*/
- (void)toggleInspector:(id)sender
{
if ([_splitViewItems count] > 1)
{
var inspectorItem = [_splitViewItems lastObject];
[inspectorItem setCollapsed:![inspectorItem isCollapsed]];
}
}
/**
* Returns the minimum thickness for sidebars to be displayed inline.
*
* @returns {CPNumber} The minimum thickness.
*/
- (CPNumber)minimumThicknessForInlineSidebars
{
return _minimumThicknessForInlineSidebars;
}
// MARK: - CPSplitViewDelegate Methods
// Note: A more complete implementation would forward these delegate methods
// to a separate delegate property on the CPSplitViewController itself.
// For now, these are stubbed to demonstrate where they would be handled.
- (BOOL)splitView:(CPSplitView)splitView canCollapseSubview:(CPView)subview
{
// Default behavior: allow all subviews to be collapsed.
return YES;
}
- (CGFloat)splitView:(CPSplitView)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(CPInteger)dividerIndex
{
return proposedMinimumPosition;
}
- (CGFloat)splitView:(CPSplitView)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(CPInteger)dividerIndex
{
return proposedMaximumPosition;
}
- (CGRect)splitView:(CPSplitView)splitView effectiveRect:(CGRect)proposedEffectiveRect forDrawnRect:(CGRect)drawnRect ofDividerAtIndex:(CPInteger)dividerIndex
{
// Default behavior: return the proposed rectangle.
// This can be overridden to provide a larger or custom hit area for the divider.
return proposedEffectiveRect;
}
- (void)splitViewDidResizeSubviews:(CPNotification)notification
{
// Can be used to respond to user-initiated resizing.
}
@end
/**
* An object that manages a view controller within a CPSplitViewController.
*
* A CPSplitViewItem acts as a wrapper around a CPViewController,
* maintaining properties like its collapsed state within the parent
* split view controller.
*/
@implementation CPSplitViewItem : CPObject
{
/** @private The view controller managed by this item. */
CPViewController _viewController;
/** @private A boolean indicating whether the item is collapsed. */
BOOL _isCollapsed;
/** @private A weak reference to the owning split view controller. */
CPSplitViewController _splitViewController;
}
// MARK: - Class Methods
/**
* Creates and returns a new split view item with the specified view controller.
*
* @param {CPViewController} viewController The view controller for the new item.
* @returns {instancetype} A new CPSplitViewItem instance.
*/
+ (instancetype)splitViewItemWithViewController:(CPViewController)viewController
{
return [[self alloc] initWithViewController:viewController];
}
// MARK: - Initialization
/**
* Initializes a new split view item with the specified view controller.
*
* @param {CPViewController} viewController The view controller for the new item.
* @returns {id} The initialized CPSplitViewItem instance.
*/
- (id)initWithViewController:(CPViewController)viewController
{
if (self = [super init])
{
_viewController = viewController;
_isCollapsed = NO;
}
return self;
}
// MARK: - Accessors
/**
* Returns the view controller associated with the item.
*
* @returns {CPViewController} The associated view controller.
*/
- (CPViewController)viewController
{
return _viewController;
}
/**
* Returns a boolean value indicating whether the item is collapsed.
*
* @returns {BOOL} YES if the item is collapsed, otherwise NO.
*/
- (BOOL)isCollapsed
{
return _isCollapsed;
}
/**
* Sets the collapsed state of the item.
* When collapsed, the view controller's view is hidden.
*
* @param {BOOL} shouldCollapse YES to collapse the item, NO to expand it.
*/
- (void)setCollapsed:(BOOL)shouldCollapse
{
if (_isCollapsed === shouldCollapse)
return;
_isCollapsed = shouldCollapse;
[[_viewController view] setHidden:shouldCollapse];
}
/**
* Returns the split view controller that owns this item.
*
* @returns {CPSplitViewController | null} The parent split view controller.
*/
- (CPSplitViewController)splitViewController
{
return _splitViewController;
}
/**
* @private
* Sets the owning split view controller. This method is for internal use by
* CPSplitViewController.
*
* @param {CPSplitViewController} splitViewController The parent controller.
*/
- (void)_setSplitViewController:(CPSplitViewController)splitViewController
{
_splitViewController = splitViewController;
}
@end
+698
View File
@@ -0,0 +1,698 @@
/*
* CPStackView.j
* AppKit
*
* Created by Daniel Boehringer.
* Copyright 2025, Cappuccino Project.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPView.j"
// Gravity Areas
@typedef CPStackViewGravity
CPStackViewGravityTop = 1;
CPStackViewGravityLeading = 1;
CPStackViewGravityCenter = 2;
CPStackViewGravityBottom = 3;
CPStackViewGravityTrailing = 3;
// Distribution (Deprecated in modern macOS, but kept for compatibility/logic)
@typedef CPStackViewDistribution
CPStackViewDistributionGravityAreas = 0;
CPStackViewDistributionFill = 1;
CPStackViewDistributionFillEqually = 2;
CPStackViewDistributionFillProportionally = 3;
CPStackViewDistributionEqualSpacing = 4;
CPStackViewDistributionEqualCentering = 5;
// Visibility Priority
@typedef CPStackViewVisibilityPriority
CPStackViewVisibilityPriorityMustHold = 1000.0;
CPStackViewVisibilityPriorityNotVisible = 0.0;
var CPStackViewSpacingUseDefault = 3.40282347e+38; // FLT_MAX
/*!
@ingroup appkit
@class CPStackView
CPStackView arranges an array of views horizontally or vertically and updates
their placement and sizing when the window size changes.
Unlike a simple list, CPStackView supports "Gravity Areas" (Leading, Center, Trailing),
allowing you to pin groups of views to specific sections of the layout.
*/
@implementation CPStackView : CPView
{
CPUserInterfaceLayoutOrientation _orientation;
CPLayoutAttribute _alignment;
float _spacing;
CPEdgeInsets _edgeInsets;
BOOL _detachesHiddenViews;
// View Storage by Gravity
CPMutableArray _viewsLeading;
CPMutableArray _viewsCenter;
CPMutableArray _viewsTrailing;
// Internal cache of all arranged subviews to maintain order for hittesting/iterating
CPMutableArray _arrangedSubviews;
// Custom Spacing storage
CPMapTable _customSpacings;
// Visibility Priorities
CPMapTable _visibilityPriorities;
}
// MARK: -
// MARK: Initialization
+ (CPStackView)stackViewWithViews:(CPArray)views
{
var stackView = [[CPStackView alloc] initWithFrame:CGRectMakeZero()];
for (var i = 0, count = [views count]; i < count; i++)
[stackView addView:views[i] inGravity:CPStackViewGravityLeading];
return stackView;
}
- (id)initWithFrame:(CGRect)aFrame
{
if (self = [super initWithFrame:aFrame])
{
_orientation = CPUserInterfaceLayoutOrientationHorizontal;
_alignment = CPLayoutAttributeCenterY; // Default alignment
_spacing = 8.0; // Default Cocoa spacing
_edgeInsets = CPEdgeInsetsMake(0, 0, 0, 0);
_detachesHiddenViews = YES;
_viewsLeading = [[CPMutableArray alloc] init];
_viewsCenter = [[CPMutableArray alloc] init];
_viewsTrailing = [[CPMutableArray alloc] init];
_arrangedSubviews = [[CPMutableArray alloc] init];
_customSpacings = [[CPMapTable alloc] init];
_visibilityPriorities = [[CPMapTable alloc] init];
}
return self;
}
// MARK: -
// MARK: Configuration
/*!
The horizontal or vertical layout direction of the stack view.
*/
- (CPUserInterfaceLayoutOrientation)orientation
{
return _orientation;
}
- (void)setOrientation:(CPUserInterfaceLayoutOrientation)anOrientation
{
if (_orientation === anOrientation)
return;
_orientation = anOrientation;
// Reset default alignment based on new orientation if needed,
// though usually developer sets alignment explicitly.
// If switching to Vertical, CenterY makes less sense, usually CenterX.
if (_orientation === CPUserInterfaceLayoutOrientationVertical)
{
if (_alignment === CPLayoutAttributeCenterY)
_alignment = CPLayoutAttributeCenterX;
}
else
{
if (_alignment === CPLayoutAttributeCenterX)
_alignment = CPLayoutAttributeCenterY;
}
[self setNeedsLayout:YES];
}
/*!
The view alignment within the stack view.
Common values:
Horizontal: CPLayoutAttributeTop, CPLayoutAttributeBottom, CPLayoutAttributeCenterY, CPLayoutAttributeHeight (fill)
Vertical: CPLayoutAttributeLeading, CPLayoutAttributeTrailing, CPLayoutAttributeCenterX, CPLayoutAttributeWidth (fill)
*/
- (CPLayoutAttribute)alignment
{
return _alignment;
}
- (void)setAlignment:(CPLayoutAttribute)anAlignment
{
if (_alignment === anAlignment)
return;
_alignment = anAlignment;
[self setNeedsLayout:YES];
}
/*!
The minimum spacing, in points, between adjacent views in the stack view.
*/
- (float)spacing
{
return _spacing;
}
- (void)setSpacing:(float)aSpacing
{
if (_spacing === aSpacing)
return;
_spacing = aSpacing;
[self setNeedsLayout:YES];
}
/*!
The geometric padding, in points, inside the stack view, surrounding its views.
*/
- (CPEdgeInsets)edgeInsets
{
return _edgeInsets;
}
- (void)setEdgeInsets:(CPEdgeInsets)insets
{
if (CPEdgeInsetsEqualToEdgeInsets(_edgeInsets, insets))
return;
_edgeInsets = insets;
[self setNeedsLayout:YES];
}
/*!
A Boolean value that indicates whether the stack view removes hidden views from its view hierarchy.
*/
- (BOOL)detachesHiddenViews
{
return _detachesHiddenViews;
}
- (void)setDetachesHiddenViews:(BOOL)shouldDetach
{
if (_detachesHiddenViews === shouldDetach)
return;
_detachesHiddenViews = shouldDetach;
[self setNeedsLayout:YES];
}
// MARK: -
// MARK: Managing Views in Gravity Areas
- (CPArray)_containerForGravity:(CPStackViewGravity)gravity
{
if (gravity === CPStackViewGravityCenter)
return _viewsCenter;
else if (gravity === CPStackViewGravityTrailing) // or Bottom
return _viewsTrailing;
return _viewsLeading; // Leading or Top
}
/*!
Adds a view to the end of the stack view gravity area.
*/
- (void)addView:(CPView)aView inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
// Check if view is already in a container
if ([_arrangedSubviews containsObject:aView])
[self removeView:aView];
[container addObject:aView];
[_arrangedSubviews addObject:aView];
// Add as actual subview
if ([aView superview] !== self)
[self addSubview:aView];
[self setNeedsLayout:YES];
}
/*!
Adds a view to a stack view gravity area at a specified index position.
*/
- (void)insertView:(CPView)aView atIndex:(CPInteger)index inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
if ([_arrangedSubviews containsObject:aView])
[self removeView:aView];
if (index >= [container count])
[container addObject:aView];
else
[container insertObject:aView atIndex:index];
[_arrangedSubviews addObject:aView];
if ([aView superview] !== self)
[self addSubview:aView];
[self setNeedsLayout:YES];
}
/*!
Specifies an array of views for a specified gravity area in the stack view, replacing any previous views in that area.
*/
- (void)setViews:(CPArray)views inGravity:(CPStackViewGravity)gravity
{
var container = [self _containerForGravity:gravity];
// Remove old views from arranged list and superview
for (var i = 0; i < [container count]; i++)
{
var oldView = container[i];
[oldView removeFromSuperview];
[_arrangedSubviews removeObject:oldView];
}
[container removeAllObjects];
for (var i = 0; i < [views count]; i++)
{
var newView = views[i];
[container addObject:newView];
[_arrangedSubviews addObject:newView];
[self addSubview:newView];
}
[self setNeedsLayout:YES];
}
/*!
Removes a specified view from the stack view.
*/
- (void)removeView:(CPView)aView
{
if (![_arrangedSubviews containsObject:aView])
return;
[_viewsLeading removeObject:aView];
[_viewsCenter removeObject:aView];
[_viewsTrailing removeObject:aView];
[_arrangedSubviews removeObject:aView];
[aView removeFromSuperview];
[self setNeedsLayout:YES];
}
/*!
Returns the array of views in the specified gravity area in the stack view.
*/
- (CPArray)viewsInGravity:(CPStackViewGravity)gravity
{
return [[self _containerForGravity:gravity] copy];
}
/*!
The array of views arranged by the stack view.
*/
- (CPArray)arrangedSubviews
{
return [_arrangedSubviews copy];
}
/*!
Adds the specified view to the end of the arranged subviews list.
(Defaults to Leading gravity if not specified).
*/
- (void)addArrangedSubview:(CPView)view
{
[self addView:view inGravity:CPStackViewGravityLeading];
}
/*!
Removes the provided view from the stacks array of arranged subviews.
*/
- (void)removeArrangedSubview:(CPView)view
{
[self removeView:view];
}
// MARK: -
// MARK: Custom Spacing
- (float)customSpacingAfterView:(CPView)aView
{
var val = [_customSpacings objectForKey:aView];
if (val)
return [val floatValue];
return CPStackViewSpacingUseDefault;
}
- (void)setCustomSpacing:(float)spacing afterView:(CPView)aView
{
if (spacing === CPStackViewSpacingUseDefault)
[_customSpacings removeObjectForKey:aView];
else
[_customSpacings setObject:spacing forKey:aView];
[self setNeedsLayout:YES];
}
- (float)_spacingAfterView:(CPView)aView
{
var custom = [self customSpacingAfterView:aView];
if (custom !== CPStackViewSpacingUseDefault)
return custom;
return _spacing;
}
// MARK: -
// MARK: Visibility Priority
- (void)setVisibilityPriority:(float)priority forView:(CPView)aView
{
[_visibilityPriorities setObject:priority forKey:aView];
if (priority === CPStackViewVisibilityPriorityNotVisible)
{
[aView setHidden:YES];
}
else if (priority === CPStackViewVisibilityPriorityMustHold)
{
[aView setHidden:NO];
}
// Note: Intermediate priorities require complex constraint logic
// or a multi-pass layout system to determine fitting, which is
// simplified here to basic Hidden/Visible states.
[self setNeedsLayout:YES];
}
- (float)visibilityPriorityForView:(CPView)aView
{
var val = [_visibilityPriorities objectForKey:aView];
if (val)
return [val floatValue];
return CPStackViewVisibilityPriorityMustHold;
}
// MARK: -
// MARK: Layout
- (void)resizeSubviewsWithOldSize:(CGSize)oldSize
{
[self layoutSubviews];
}
- (void)layoutSubviews
{
if (_orientation === CPUserInterfaceLayoutOrientationVertical)
[self _layoutVertical];
else
[self _layoutHorizontal];
}
- (void)_layoutHorizontal
{
var bounds = [self bounds],
availWidth = CGRectGetWidth(bounds) - _edgeInsets.left - _edgeInsets.right,
availHeight = CGRectGetHeight(bounds) - _edgeInsets.top - _edgeInsets.bottom,
currentX = _edgeInsets.left;
// 1. Layout Leading Views
currentX = [self _layoutViews:_viewsLeading startOffset:currentX availableOrthogonalSize:availHeight direction:1];
// 2. Layout Trailing Views
// We layout backwards from the right
var startRight = CGRectGetWidth(bounds) - _edgeInsets.right;
[self _layoutViews:_viewsTrailing startOffset:startRight availableOrthogonalSize:availHeight direction:-1];
// 3. Layout Center Views
if ([_viewsCenter count] > 0)
{
// Calculate total width of center stack
var centerStackWidth = 0.0;
for (var i = 0; i < [_viewsCenter count]; i++)
{
var view = _viewsCenter[i];
if (_detachesHiddenViews && [view isHidden]) continue;
centerStackWidth += CGRectGetWidth([view frame]);
if (i < [_viewsCenter count] - 1)
centerStackWidth += [self _spacingAfterView:view];
}
var centerStart = (CGRectGetWidth(bounds) / 2.0) - (centerStackWidth / 2.0);
// Clamp to prevent overlap with Leading (simplified collision logic)
// ideally stack view compresses views, but here we just shift/clip
if (centerStart < currentX)
centerStart = currentX;
[self _layoutViews:_viewsCenter startOffset:centerStart availableOrthogonalSize:availHeight direction:1];
}
}
- (void)_layoutVertical
{
var bounds = [self bounds],
availWidth = CGRectGetWidth(bounds) - _edgeInsets.left - _edgeInsets.right,
availHeight = CGRectGetHeight(bounds) - _edgeInsets.top - _edgeInsets.bottom,
currentY = _edgeInsets.top;
// 1. Layout Top (Leading) Views
currentY = [self _layoutViews:_viewsLeading startOffset:currentY availableOrthogonalSize:availWidth direction:1];
// 2. Layout Bottom (Trailing) Views
var startBottom = CGRectGetHeight(bounds) - _edgeInsets.bottom;
[self _layoutViews:_viewsTrailing startOffset:startBottom availableOrthogonalSize:availWidth direction:-1];
// 3. Layout Center Views
if ([_viewsCenter count] > 0)
{
var centerStackHeight = 0.0;
for (var i = 0; i < [_viewsCenter count]; i++)
{
var view = _viewsCenter[i];
if (_detachesHiddenViews && [view isHidden]) continue;
centerStackHeight += CGRectGetHeight([view frame]);
if (i < [_viewsCenter count] - 1)
centerStackHeight += [self _spacingAfterView:view];
}
var centerStart = (CGRectGetHeight(bounds) / 2.0) - (centerStackHeight / 2.0);
if (centerStart < currentY)
centerStart = currentY;
[self _layoutViews:_viewsCenter startOffset:centerStart availableOrthogonalSize:availWidth direction:1];
}
}
// Helper to layout a specific array of views in one direction
// Returns the ending offset
- (float)_layoutViews:(CPArray)views startOffset:(float)offset availableOrthogonalSize:(float)orthoSize direction:(int)dir
{
var cursor = offset;
var isVert = (_orientation === CPUserInterfaceLayoutOrientationVertical);
// If direction is -1 (Trailing/Bottom), we iterate backwards
// However, the standard behavior for trailing gravity is that the *last* view added is at the *end*.
// Leading: [A] [B] ->
// Trailing: -> [C] [D] (where D is rightmost)
// To support Trailing logic: We start at Right Edge, move left by Width(D), place D, move left by Spacing...
var count = [views count];
if (count === 0) return cursor;
// If direction is negative (Trailing), we process list in reverse order to stack them from edge inwards
var i = (dir === 1) ? 0 : count - 1;
var limit = (dir === 1) ? count : -1;
var step = (dir === 1) ? 1 : -1;
for (; i !== limit; i += step)
{
var view = views[i];
if (_detachesHiddenViews && [view isHidden])
continue;
var viewFrame = [view frame];
var viewSizePrimary = isVert ? CGRectGetHeight(viewFrame) : CGRectGetWidth(viewFrame);
// Handle Alignment (Orthogonal Axis)
var orthoPos = 0.0;
var viewOrthoSize = isVert ? CGRectGetWidth(viewFrame) : CGRectGetHeight(viewFrame);
// Apply Stretch/Fill Alignment
if (isVert)
{
// Vertical Stack, dealing with Width
if (_alignment === CPLayoutAttributeWidth || _alignment === CPLayoutAttributeLeading || _alignment === CPLayoutAttributeTrailing)
{
// Note: CPLayoutAttributeLeading/Trailing in this context implies filling width usually,
// or aligning to edges. Let's assume Width/Fill for Leading/Trailing/Left/Right
// in this simplified implementation, or strictly left/right.
if (_alignment === CPLayoutAttributeWidth || _alignment === CPLayoutAttributeLeft || _alignment === CPLayoutAttributeLeading)
{
// Fill width if explicit, or just align left
if (_alignment === CPLayoutAttributeWidth) viewOrthoSize = orthoSize;
orthoPos = _edgeInsets.left;
}
else if (_alignment === CPLayoutAttributeRight || _alignment === CPLayoutAttributeTrailing)
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize);
}
else // CenterX
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize) / 2.0;
}
}
else // Default CenterX
{
orthoPos = _edgeInsets.left + (orthoSize - viewOrthoSize) / 2.0;
}
}
else
{
// Horizontal Stack, dealing with Height
if (_alignment === CPLayoutAttributeHeight || _alignment === CPLayoutAttributeTop || _alignment === CPLayoutAttributeBottom)
{
if (_alignment === CPLayoutAttributeHeight)
{
viewOrthoSize = orthoSize;
orthoPos = _edgeInsets.top;
}
else if (_alignment === CPLayoutAttributeTop)
{
orthoPos = _edgeInsets.top;
}
else if (_alignment === CPLayoutAttributeBottom)
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize);
}
else // CenterY
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize) / 2.0;
}
}
else // Default CenterY
{
orthoPos = _edgeInsets.top + (orthoSize - viewOrthoSize) / 2.0;
}
}
// Calculate Position
var originX = 0.0, originY = 0.0;
var sizeW = 0.0, sizeH = 0.0;
if (isVert)
{
// Vertical
sizeH = viewSizePrimary;
sizeW = viewOrthoSize;
originX = orthoPos;
if (dir === 1) {
originY = cursor;
cursor += sizeH + [self _spacingAfterView:view];
} else {
cursor -= sizeH;
originY = cursor;
cursor -= [self _spacingAfterView:view];
}
}
else
{
// Horizontal
sizeW = viewSizePrimary;
sizeH = viewOrthoSize;
originY = orthoPos;
if (dir === 1) {
originX = cursor;
cursor += sizeW + [self _spacingAfterView:view];
} else {
cursor -= sizeW;
originX = cursor;
cursor -= [self _spacingAfterView:view];
}
}
[view setFrame:CGRectMake(originX, originY, sizeW, sizeH)];
}
return cursor;
}
// MARK: -
// MARK: CPCoding
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
_orientation = [aCoder decodeIntForKey:@"CPStackViewOrientation"];
_alignment = [aCoder decodeIntForKey:@"CPStackViewAlignment"];
_spacing = [aCoder decodeFloatForKey:@"CPStackViewSpacing"];
_edgeInsets = [aCoder decodeObjectForKey:@"CPStackViewEdgeInsets"]; // Assuming CPEdgeInsets supports obj coding or manual decode
if (!_edgeInsets) _edgeInsets = CPEdgeInsetsMake(0,0,0,0);
_detachesHiddenViews = [aCoder decodeBoolForKey:@"CPStackViewDetachesHiddenViews"];
_viewsLeading = [aCoder decodeObjectForKey:@"CPStackViewViewsLeading"] || [];
_viewsCenter = [aCoder decodeObjectForKey:@"CPStackViewViewsCenter"] || [];
_viewsTrailing = [aCoder decodeObjectForKey:@"CPStackViewViewsTrailing"] || [];
// Rebuild arranged subviews cache
_arrangedSubviews = [[CPMutableArray alloc] init];
[_arrangedSubviews addObjectsFromArray:_viewsLeading];
[_arrangedSubviews addObjectsFromArray:_viewsCenter];
[_arrangedSubviews addObjectsFromArray:_viewsTrailing];
_customSpacings = [aCoder decodeObjectForKey:@"CPStackViewCustomSpacings"] || [[CPMapTable alloc] init];
_visibilityPriorities = [[CPMapTable alloc] init]; // usually not persisted
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeInt:_orientation forKey:@"CPStackViewOrientation"];
[aCoder encodeInt:_alignment forKey:@"CPStackViewAlignment"];
[aCoder encodeFloat:_spacing forKey:@"CPStackViewSpacing"];
[aCoder encodeObject:_edgeInsets forKey:@"CPStackViewEdgeInsets"];
[aCoder encodeBool:_detachesHiddenViews forKey:@"CPStackViewDetachesHiddenViews"];
[aCoder encodeObject:_viewsLeading forKey:@"CPStackViewViewsLeading"];
[aCoder encodeObject:_viewsCenter forKey:@"CPStackViewViewsCenter"];
[aCoder encodeObject:_viewsTrailing forKey:@"CPStackViewViewsTrailing"];
[aCoder encodeObject:_customSpacings forKey:@"CPStackViewCustomSpacings"];
}
@end
+75 -15
View File
@@ -42,8 +42,8 @@
CPButton _buttonUp;
}
#pragma mark -
#pragma mark Initialization
// MARK: -
// MARK: Initialization
/*!
Initializes a CPStepper with given values.
@@ -147,8 +147,8 @@
[self _sizeToFit];
}
#pragma mark -
#pragma mark Superclass overrides
// MARK: -
// MARK: Superclass overrides
/*!
Set if the CPStepper is enabled or not.
@@ -175,12 +175,53 @@
[super setFrame:frame];
}
- (void)setThemeState:(CPThemeState)aState
{
[super setThemeState:aState];
// Force a layout update because the internal buttons (_buttonUp and _buttonDown)
// rely on layoutSubviews to receive the new theme attributes (like HUD colors).
[self setNeedsLayout];
}
- (void)unsetThemeState:(CPThemeState)aState
{
[super unsetThemeState:aState];
[self setNeedsLayout];
}
/*! @ignore */
- (void)layoutSubviews
{
var controlSizeThemeState = [self _controlSizeThemeState],
aFrame = [self frame],
upSize = [self valueForThemeAttribute:@"up-button-size" inState:controlSizeThemeState],
isHUD = [self hasThemeState:CPThemeStateHUD],
// 1. Prepare Lookup States (To fetch the correct image/color from the Stepper's theme)
normalLookupStates = [controlSizeThemeState, CPThemeStateBordered],
disabledLookupStates = [controlSizeThemeState, CPThemeStateBordered, CPThemeStateDisabled],
highlightedLookupStates = [controlSizeThemeState, CPThemeStateBordered, CPThemeStateHighlighted],
// 2. Prepare Target States (To tell the child buttons when to use this image)
normalTargetStates = [CPThemeStateBordered, CPButtonStateBezelStyleRoundRect],
disabledTargetStates = [CPThemeStateBordered, CPThemeStateDisabled, CPButtonStateBezelStyleRoundRect],
highlightedTargetStates = [CPThemeStateBordered, CPThemeStateHighlighted, CPButtonStateBezelStyleRoundRect];
// If we are in HUD mode, add CPThemeStateHUD to both lookup and target arrays
if (isHUD)
{
// Lookup: Ask theme for "HUD" version of the stepper arrows
normalLookupStates.push(CPThemeStateHUD);
disabledLookupStates.push(CPThemeStateHUD);
highlightedLookupStates.push(CPThemeStateHUD);
// Target: Tell the child buttons "Use this when you are in HUD state"
normalTargetStates.push(CPThemeStateHUD);
disabledTargetStates.push(CPThemeStateHUD);
highlightedTargetStates.push(CPThemeStateHUD);
}
var upSize = [self valueForThemeAttribute:@"up-button-size" inState:controlSizeThemeState],
downSize = [self valueForThemeAttribute:@"down-button-size" inState:controlSizeThemeState],
upFrame = CGRectMake(0, 0, upSize.width, upSize.height),
downFrame = CGRectMake(0, upSize.height, downSize.width, downSize.height);
@@ -188,12 +229,31 @@
[_buttonUp setFrame:upFrame];
[_buttonDown setFrame:downFrame];
[_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, CPButtonStateBezelStyleRoundRect]];
[_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" inStates:[CPThemeStateBordered, CPButtonStateBezelStyleRoundRect]];
[_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, CPButtonStateBezelStyleRoundRect]];
// Apply Up Button Attributes
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:normalLookupStates]
forThemeAttribute:@"bezel-color"
inStates:normalTargetStates];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:disabledLookupStates]
forThemeAttribute:@"bezel-color"
inStates:disabledTargetStates];
[_buttonUp setValue:[self valueForThemeAttribute:@"bezel-color-up-button" inStates:highlightedLookupStates]
forThemeAttribute:@"bezel-color"
inStates:highlightedTargetStates];
// Apply Down Button Attributes
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:normalLookupStates]
forThemeAttribute:@"bezel-color"
inStates:normalTargetStates];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:disabledLookupStates]
forThemeAttribute:@"bezel-color"
inStates:disabledTargetStates];
[_buttonDown setValue:[self valueForThemeAttribute:@"bezel-color-down-button" inStates:highlightedLookupStates]
forThemeAttribute:@"bezel-color"
inStates:highlightedTargetStates];
}
- (void)_sizeToFit
@@ -230,8 +290,8 @@
[super setDoubleValue:aValue];
}
#pragma mark -
#pragma mark Actions
// MARK: -
// MARK: Actions
/*! @ignore */
- (IBAction)_buttonDidClick:(id)aSender
@@ -266,8 +326,8 @@
}
#pragma mark -
#pragma mark Theming
// MARK: -
// MARK: Theming
+ (CPString)defaultThemeClass
{
+3 -7
View File
@@ -69,13 +69,9 @@ CPCanvasStringSizingIsFunctional = NO;
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);
// Unfortunately, as of today canvas sizing is not funtional any more. Neither in Chrome nor in FF
CPCanvasStringSizingIsFunctional = NO;
}
#endif
}
+8 -8
View File
@@ -117,7 +117,7 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
var height = [_tabs valueForThemeAttribute:@"min-size"].height;
[_tabs setFrameSize:CGSizeMake(0, height)];
_box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
_box = [[_CPTabViewBox alloc] initWithFrame:[self bounds]];
[_box setTabView:self];
[_box setContentInset:[self currentValueForThemeAttribute:@"box-content-inset"]];
[_box setContentViewMargins:CGSizeMakeZero()];
@@ -696,8 +696,8 @@ var CPTabViewDidSelectTabViewItemSelector = 1 << 1,
[self _displayItemView:_placeholderView];
}
#pragma mark -
#pragma mark Override
// MARK: -
// MARK: Override
/*!
Enabled controls accept first mouse by default.
@@ -886,11 +886,11 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
@end
#pragma mark -
// MARK: -
@implementation CPTabView (CSSTheming)
#pragma mark Override
// MARK: Override
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
@@ -906,7 +906,7 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
@end
#pragma mark -
// MARK: -
@implementation _CPTabViewBox : CPBox
{
@@ -915,8 +915,8 @@ var CPTabViewItemsKey = "CPTabViewItemsKey",
}
#pragma mark -
#pragma mark Override
// MARK: -
// MARK: Override
- (id)initWithFrame:(CGRect)aFrame
{
+231 -23
View File
@@ -25,6 +25,10 @@
@import "CPCursor.j"
@import "_CPImageAndTextView.j"
@import "CPTrackingArea.j"
@import "CPAnimationContext.j"
@import "CPViewAnimator.j"
@import "CPScrollView.j"
@import <Foundation/CPGeometry.j>
@class CPTableView
@@ -50,10 +54,16 @@
@"text-color": [CPNull null],
@"font": [CPNull null],
@"text-shadow-color": [CPNull null],
@"text-shadow-offset": CGSizeMakeZero()
@"text-shadow-offset": CGSizeMakeZero(),
@"dont-draw-separator": NO
};
}
- (BOOL)acceptsFirstMouse:(CPEvent)anEvent
{
return YES;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
@@ -66,18 +76,22 @@
- (void)_init
{
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
var inset = [self valueForThemeAttribute:@"text-inset"];
_textField = [[_CPImageAndTextView alloc] initWithFrame:
CGRectMake(5.0, 0.0, CGRectGetWidth([self bounds]) - 10.0, CGRectGetHeight([self bounds]))];
CGRectMake(inset.left, inset.top, CGRectGetWidth([self bounds]) - (inset.left + inset.right), CGRectGetHeight([self bounds]) - (inset.top + inset.bottom))];
[_textField setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_textField setLineBreakMode:CPLineBreakByTruncatingTail];
[_textField setTextColor:[CPColor colorWithRed:51.0 / 255.0 green:51.0 / 255.0 blue:51.0 / 255.0 alpha:1.0]];
[_textField setFont:[CPFont boldSystemFontOfSize:12.0]];
[_textField setAlignment:CPLeftTextAlignment];
[_textField setLineBreakMode:[self valueForThemeAttribute:@"line-break-mode"]];
[_textField setTextColor:[self valueForThemeAttribute:@"text-color"]];
[_textField setFont:[self valueForThemeAttribute:@"font"]];
[_textField setAlignment:[self valueForThemeAttribute:@"text-alignment"]];
[_textField setVerticalAlignment:CPCenterVerticalTextAlignment];
[_textField setTextShadowColor:[CPColor whiteColor]];
[_textField setTextShadowOffset:CGSizeMake(0,1)];
[_textField setTextShadowColor:[self valueForThemeAttribute:@"text-shadow-color"]];
[_textField setTextShadowOffset:[self valueForThemeAttribute:@"text-shadow-offset"]];
[self addSubview:_textField];
}
@@ -188,6 +202,9 @@
- (void)drawRect:(CGRect)aRect
{
if ([self valueForThemeAttribute:@"dont-draw-separator"])
return;
var bounds = [self bounds];
if (!CGRectIntersectsRect(aRect, bounds))
@@ -223,7 +240,8 @@ var _CPTableColumnHeaderViewStringValueKey = @"_CPTableColumnHeaderViewStringVal
[self _init];
[self _setIndicatorImage:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewImageKey]];
[self setStringValue:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewStringValueKey]];
[self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
// FIXME: pourquoi dans actif, font=null ?
// [self setFont:[aCoder decodeObjectForKey:_CPTableColumnHeaderViewFontKey]];
}
return self;
@@ -257,11 +275,13 @@ var CPTableHeaderViewResizeZone = 3.0,
BOOL _isResizing;
BOOL _isDragging;
BOOL _isAnimating;
BOOL _canDragColumn;
CPView _columnDragView;
CPView _columnDragHeaderView;
CPView _columnDragClipView;
CPScrollView _columnDragScrollView;
float _columnOldWidth;
@@ -278,7 +298,9 @@ var CPTableHeaderViewResizeZone = 3.0,
return @{
@"background-color": [CPNull null],
@"divider-color": [CPColor grayColor],
@"divider-thickness": 1.0
@"divider-thickness": 1.0,
@"swap-animation": [CPNull null],
@"return-animation": [CPNull null]
};
}
@@ -340,7 +362,24 @@ var CPTableHeaderViewResizeZone = 3.0,
- (CPInteger)columnAtPoint:(CGPoint)aPoint
{
return [_tableView columnAtPoint:aPoint];
var tableView = [self tableView],
tableColumns = [tableView tableColumns],
count = [tableColumns count],
bounds = [self bounds],
// Create a point that keeps the X position but forces Y to be safely
// in the middle of the header view.
constrainedPoint = CGPointMake(aPoint.x, CGRectGetMidY(bounds));
// Iterate through columns to find which one contains the constrained X coordinate
for (var i = 0; i < count; i++)
{
// headerRectOfColumn: is a utility method defined in CPTableHeaderView
// that handles the coordinate conversion from the table view relative to the header.
if (CGRectContainsPoint([self headerRectOfColumn:i], constrainedPoint))
return i;
}
return -1;
}
- (CGRect)headerRectOfColumn:(CPInteger)aColumnIndex
@@ -365,6 +404,8 @@ var CPTableHeaderViewResizeZone = 3.0,
- (void)layoutSubviews
{
[self setBackgroundColor:[self currentValueForThemeAttribute:@"background-color"]];
var tableColumns = [_tableView tableColumns],
count = [tableColumns count];
@@ -442,6 +483,14 @@ var CPTableHeaderViewResizeZone = 3.0,
}
else if (_isDragging)
{
// First, we have to avoid a running condition where user stops dragging while a swap animation is running
if (_isAnimating)
{
[CPTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(_retry_mouseUp:) userInfo:theEvent repeats:NO];
return;
}
[self _stopDraggingTableColumn:_activeColumn];
}
else if (_activeColumn != -1)
@@ -458,6 +507,11 @@ var CPTableHeaderViewResizeZone = 3.0,
_activeColumn = -1;
}
- (void)_retry_mouseUp:(CPTimer)aTimer
{
[self mouseUp:[aTimer userInfo]];
}
@end
@implementation CPTableHeaderView (CPTrackingArea)
@@ -562,7 +616,21 @@ var CPTableHeaderViewResizeZone = 3.0,
pressure:[theEvent pressure]];
[self autoscroll:constrainedEvent];
var contentView = [_tableView superview],
boundsOriginBefore = [contentView boundsOrigin];
[_tableView autoscroll:constrainedEvent];
var boundsOriginAfter = [contentView boundsOrigin],
deltaX = boundsOriginAfter.x - boundsOriginBefore.x;
if (_isDragging)
{
var dragContentView = [_columnDragScrollView contentView],
dragContentBoundsOrigin = [dragContentView boundsOrigin];
[dragContentView setBoundsOrigin:CGPointMake(dragContentBoundsOrigin.x + deltaX, dragContentBoundsOrigin.y)];
}
}
- (CGRect)_headerRectOfLastVisibleColumn
@@ -584,16 +652,91 @@ var CPTableHeaderViewResizeZone = 3.0,
- (CGPoint)_constrainDragPoint:(CGPoint)aPoint
{
// This effectively clamps the value between the minimum and maximum
var visibleRect = [_tableView visibleRect],
lastColumnRect = [self _headerRectOfLastVisibleColumn],
activeColumnRect = [self headerRectOfColumn:_activeColumn],
maxX = CGRectGetMaxX(lastColumnRect) - CGRectGetWidth(activeColumnRect) - CGRectGetMinX(visibleRect),
point = CGPointMake(MAX(MIN(aPoint.x, maxX), -CGRectGetMinX(visibleRect)), aPoint.y);
var tableFrame = [_tableView frame],
dragFrame = [_columnDragView frame],
maxX = tableFrame.size.width - dragFrame.size.width,
point = CGPointMake(MAX(MIN(aPoint.x, maxX),0), aPoint.y);
return point;
}
- (void)_moveColumn:(CPInteger)aFromIndex toColumn:(CPInteger)aToIndex
{
if (_isAnimating)
return;
var swapAnimation = [self currentValueForThemeAttribute:@"swap-animation"];
if (swapAnimation)
{
_isAnimating = YES;
// There's a theme defined animation function, just use it
objj_eval("("+swapAnimation+")")(self, aFromIndex, aToIndex, _columnDragClipView, _columnDragView);
// var animatedColumn = [[_tableView tableColumns] objectAtIndex:aToIndex],
// animatedHeader = [animatedColumn headerView],
// animatedHeaderOrigin = [animatedHeader frameOrigin],
//
// destinationX,
// draggedHeader = [[[_tableView tableColumns] objectAtIndex:aFromIndex] headerView],
//
// scrollView = [self enclosingScrollView],
// animatedView = [_tableView _animationViewForColumn:aToIndex],
// animatedOrigin = [animatedView frameOrigin];
//
// [_columnDragClipView addSubview:animatedView positioned:CPWindowBelow relativeTo:_columnDragView];
//
// [[animatedHeader subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:YES];
// [animatedHeader setThemeState:CPThemeStateVertical];
//
// if (aFromIndex < aToIndex)
// destinationX = CGRectGetMinX([_tableView rectOfColumn:aFromIndex]);
// else
// destinationX = animatedOrigin.x + CGRectGetWidth([_tableView rectOfColumn:aFromIndex]);
//
// [CPAnimationContext beginGrouping];
//
// var context = [CPAnimationContext currentContext];
//
// [context setDuration:0.15];
// [context setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
// [context setCompletionHandler:function() {
// [animatedView removeFromSuperview];
//
// [self _finalize_moveColumn:aFromIndex toColumn:aToIndex];
//
// [animatedHeader unsetThemeState:CPThemeStateVertical];
// [[animatedHeader subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:NO];
//
// if ([animatedView isSelected])
// {
// [animatedHeader setThemeState:CPThemeStateSelected];
//
// // We have to reselect the animated column
// [[_tableView selectedColumnIndexes] addIndex:aFromIndex];
// }
//
// // Reload animated column
// var columnVisRect = CGRectIntersection([_tableView rectOfColumn:aFromIndex], [_tableView visibleRect]),
// rowsIndexes = [CPIndexSet indexSetWithIndexesInRange:[_tableView rowsInRect:columnVisRect]],
// columnsIndexes = [CPIndexSet indexSetWithIndex:aFromIndex];
//
// [_tableView _loadDataViewsInRows:rowsIndexes columns:columnsIndexes];
// [_tableView _layoutViewsForRowIndexes:rowsIndexes columnIndexes:columnsIndexes];
//
// [_tableView._tableDrawView displayRect:columnVisRect];
// }];
//
// [[animatedView animator] setFrameOrigin:CGPointMake(destinationX, animatedOrigin.y)];
//
// [CPAnimationContext endGrouping];
}
else
[self _finalize_moveColumn:aFromIndex toColumn:aToIndex];
}
- (void)_finalize_moveColumn:(CPInteger)aFromIndex toColumn:(CPInteger)aToIndex
{
[_tableView moveColumn:aFromIndex toColumn:aToIndex];
_activeColumn = aToIndex;
@@ -602,6 +745,8 @@ var CPTableHeaderViewResizeZone = 3.0,
[_tableView _setDraggedColumn:_activeColumn];
[self setNeedsDisplay:YES];
_isAnimating = NO;
}
- (BOOL)isDragging
@@ -618,17 +763,30 @@ var CPTableHeaderViewResizeZone = 3.0,
// Create a new clip view for the drag view that clips to the header + visible content
var headerHeight = CGRectGetHeight([self frame]),
scrollView = [self enclosingScrollView],
contentFrame = [[scrollView contentView] frame];
contentFrame = [[scrollView contentView] frame],
contentBounds = [[scrollView contentView] bounds];
contentFrame.origin.y -= headerHeight;
contentFrame.size.height += headerHeight;
_columnDragClipView = [[CPView alloc] initWithFrame:contentFrame];
_columnDragScrollView = [[CPScrollView alloc] initWithFrame:contentFrame];
[_columnDragScrollView setHasHorizontalScroller:NO];
[_columnDragScrollView setHasVerticalScroller:NO];
[_columnDragScrollView setBorderType:CPNoBorder];
var tableFrame = [_tableView frame],
clipFrame = CGRectMake(0, 0, tableFrame.size.width, contentFrame.size.height);
_columnDragClipView = [[CPView alloc] initWithFrame:clipFrame];
[_columnDragClipView addSubview:_columnDragView];
[_columnDragScrollView setDocumentView:_columnDragClipView];
[[_columnDragScrollView contentView] setBoundsOrigin:CGPointMake(contentBounds.origin.x, 0)];
// Insert the clip view above the table header (and content)
[scrollView addSubview:_columnDragClipView positioned:CPWindowAbove relativeTo:self];
[scrollView addSubview:_columnDragScrollView positioned:CPWindowAbove relativeTo:self];
// Hide the underlying column header subviews, we just want to draw the chrome
var headerView = [[[_tableView tableColumns] objectAtIndex:aColumnIndex] headerView];
@@ -638,6 +796,9 @@ var CPTableHeaderViewResizeZone = 3.0,
// The underlying column header shows normal state
[headerView unsetThemeStates:[CPThemeStateHighlighted, CPThemeStateSelected]];
// FIXME: Just a little hack to get a special background (using an unused theme state)
[headerView setThemeState:CPThemeStateVertical];
// Keep track of the location within the column header where the original mousedown occurred
_columnDragHeaderView = [_columnDragView viewWithTag:CPTableHeaderViewDragColumnHeaderTag];
@@ -692,10 +853,43 @@ var CPTableHeaderViewResizeZone = 3.0,
}
- (void)_stopDraggingTableColumn:(CPInteger)aColumnIndex
{
var returnAnimation = [self currentValueForThemeAttribute:@"return-animation"];
if (returnAnimation)
{
_isAnimating = YES;
// There's a theme defined animation function, just use it
objj_eval("("+returnAnimation+")")(self, aColumnIndex, _columnDragView);
// var animatedColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
// animatedHeader = [animatedColumn headerView],
// animatedHeaderOrigin = [animatedHeader frameOrigin];
//
// [CPAnimationContext beginGrouping];
//
// var context = [CPAnimationContext currentContext];
//
// [context setDuration:0.15];
// [context setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
// [context setCompletionHandler:function() {
//
// [self _finalize_stopDraggingTableColumn:aColumnIndex];
// }];
//
// [[_columnDragView animator] setFrameOrigin:CGPointMake(animatedHeaderOrigin.x, 0)];
//
// [CPAnimationContext endGrouping];
}
else
[self _finalize_stopDraggingTableColumn:aColumnIndex];
}
- (void)_finalize_stopDraggingTableColumn:(CPInteger)aColumnIndex
{
_isDragging = NO;
[_columnDragClipView removeFromSuperview];
[_tableView _setDraggedColumn:-1];
var tableColumn = [[_tableView tableColumns] objectAtIndex:aColumnIndex],
@@ -703,16 +897,30 @@ var CPTableHeaderViewResizeZone = 3.0,
[[headerView subviews] makeObjectsPerformSelector:@selector(setHidden:) withObject:NO];
// Restore headerView background
[headerView unsetThemeState:CPThemeStateVertical];
if (_tableView._draggedColumnIsSelected)
[headerView setThemeState:CPThemeStateSelected];
[_tableView _reloadDataViews];
[[_tableView headerView] setNeedsLayout];
// Reload animated column
var columnVisRect = CGRectIntersection([_tableView rectOfColumn:aColumnIndex], [_tableView visibleRect]),
rowsIndexes = [CPIndexSet indexSetWithIndexesInRange:[_tableView rowsInRect:columnVisRect]],
columnsIndexes = [CPIndexSet indexSetWithIndex:aColumnIndex];
[[CPCursor arrowCursor] set];
[_tableView _loadDataViewsInRows:rowsIndexes columns:columnsIndexes];
[_tableView _layoutViewsForRowIndexes:rowsIndexes columnIndexes:columnsIndexes];
[_tableView _updateDataViewsFocusState];
[_tableView._tableDrawView displayRect:columnVisRect];
[[CPCursor arrowCursor] set]; // FIXME: retirer ?
[self updateTrackingAreas];
[_columnDragScrollView removeFromSuperview];
[_tableView _sendDelegateDidDragTableColumn:tableColumn];
_isAnimating = NO;
}
- (BOOL)_shouldResizeTableColumn:(CPInteger)aColumnIndex at:(CGPoint)aPoint
+569 -481
View File
File diff suppressed because it is too large Load Diff
+96 -25
View File
@@ -68,7 +68,11 @@ var CPTextFieldDOMCurrentElement = nil,
CPTextFieldCachedDragFunction = nil,
CPTextFieldBlurHandler = nil,
CPTextFieldInputFunction = nil,
CPTexFieldCurrentCSSSelectableField = nil;
CPTexFieldCurrentCSSSelectableField = nil,
CPTextFieldLastValidationFailureEvent = nil,
CPTextFieldLastValidationFailureString = nil,
CPTextFieldLastValidationFailureField = nil,
CPTextFieldLastValidationFailureResult = NO;
var CPSecureTextFieldCharacter = "\u2022";
@@ -241,8 +245,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
#pragma mark -
#pragma mark Control Size
// MARK: -
// MARK: Control Size
- (void)setControlSize:(CPControlSize)aControlSize
{
@@ -253,7 +257,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
#pragma mark -
// MARK: -
#if PLATFORM(DOM)
- (DOMElement)_inputElement
@@ -332,7 +336,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
characters:nil
charactersIgnoringModifiers:nil
isARepeat:NO
keyCode:nil];
keyCode:nil
isActionKey:NO];
[CPTextFieldInputOwner keyUp:cappEvent];
@@ -376,7 +381,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return self;
}
#pragma mark Controlling Editability and Selectability
// MARK: Controlling Editability and Selectability
/*!
Sets whether or not the receiver text field can be edited. If NO, any
@@ -766,33 +771,27 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
verticalAlign = [self currentValueForThemeAttribute:"vertical-alignment"],
left = CGRectGetMinX(contentRect);
// If the browser has a built in left padding, compensate for it. We need the input text to be exactly on top of the original text.
if (CPFeatureIsCompatible(CPInput1PxLeftPadding))
left -= 1;
switch (verticalAlign)
{
case CPTopVerticalTextAlignment:
var topPoint = CGRectGetMinY(contentRect) + "px";
var topPoint = CEIL(CGRectGetMinY(contentRect)) + "px";
break;
case CPCenterVerticalTextAlignment:
var topPoint = (CGRectGetMidY(contentRect) - (lineHeight / 2)) + "px";
var topPoint = CEIL((CGRectGetMidY(contentRect) - (lineHeight / 2))) + "px";
break;
case CPBottomVerticalTextAlignment:
var topPoint = (CGRectGetMaxY(contentRect) - lineHeight) + "px";
var topPoint = CEIL((CGRectGetMaxY(contentRect) - lineHeight)) + "px";
break;
default:
var topPoint = CGRectGetMinY(contentRect) + "px";
var topPoint = CEIL(CGRectGetMinY(contentRect)) + "px";
break;
}
if ([self hasThemeState:CPTextFieldStatePlaceholder])
element.style.color = [[self valueForThemeAttribute:@"text-color" inState:CPTextFieldStatePlaceholder] cssString];
else
element.style.color = [[self valueForThemeAttribute:@"text-color" inState:CPThemeStateEditing] cssString];
// Use currentValueForThemeAttribute to respect all current states (HUD, Placeholder, Editing, etc.)
element.style.color = [[self currentValueForThemeAttribute:@"text-color"] cssString];
switch ([self alignment])
{
@@ -996,7 +995,26 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
var acceptInvalidValue = NO;
if (_implementedDelegateMethods & CPTextFieldDelegate_control_didFailToFormatString_errorDescription_)
acceptInvalidValue = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
{
var currentEvent = [CPApp currentEvent];
if (currentEvent &&
CPTextFieldLastValidationFailureField === self &&
CPTextFieldLastValidationFailureString === aValue &&
CPTextFieldLastValidationFailureEvent === currentEvent)
{
acceptInvalidValue = CPTextFieldLastValidationFailureResult;
}
else
{
acceptInvalidValue = [_delegate control:self didFailToFormatString:aValue errorDescription:error];
CPTextFieldLastValidationFailureField = self;
CPTextFieldLastValidationFailureString = aValue;
CPTextFieldLastValidationFailureEvent = currentEvent;
CPTextFieldLastValidationFailureResult = acceptInvalidValue;
}
}
if (acceptInvalidValue === NO)
return NO;
@@ -1130,6 +1148,12 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
return;
if ([self isEditable] && !_isEditing)
{
_isEditing = YES;
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
}
// CPTextField uses an HTML input element to take the input so we need to
// propagate the dom event so the element is updated. This has to be done
// before interpretKeyEvents: though so individual commands have a chance
@@ -1271,6 +1295,16 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[super textDidChange:note];
}
- (void)validateEditing
{
#if PLATFORM(DOM)
var element = [self _inputElement];
if (element)
[self _setStringValue:element.value isNewValue:YES errorDescription:nil];
#endif
}
- (void)textDidBeginEditing:(CPNotification)note
{
//this looks to prevent false propagation of notifications for other objects
@@ -1472,7 +1506,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
![self isBezeled] &&
(lineBreakMode === CPLineBreakByWordWrapping || lineBreakMode === CPLineBreakByCharWrapping))
{
textSize = [text sizeWithFont:font inWidth:textSize.width] + 1;
textSize = [text sizeWithFont:font inWidth:textSize.width];
}
else
{
@@ -1814,7 +1848,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _didEdit];
}
#pragma mark Setting the Delegate
// MARK: Setting the Delegate
- (void)setDelegate:(id <CPTextFieldDelegate>)aDelegate
{
@@ -1974,6 +2008,43 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
[self _setCSSStyleForInputElement];
}
// MARK: Overrides
/*!
Sets the font of the receiver.
@param aFont - A CPFont object.
*/
- (void)setFont:(CPFont)aFont
{
if ([self currentValueForThemeAttribute:@"font"] === aFont)
return;
// Apply the font to the default/normal state
[self setValue:aFont forThemeAttribute:@"font"];
// Apply to standard editing and border states
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateEditing];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateBezeled];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateBordered];
[self setValue:aFont forThemeAttribute:@"font" inState:CPTextFieldStateRounded];
// Use CPThemeState() function to create composite states instead of array literals
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPTextFieldStateRounded, CPThemeStateEditing)];
// Apply across all standard control size states
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeRegular];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeSmall];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateControlSizeMini];
// Apply to table data view states (ensuring Interface Builder-style lists respect the font)
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeStateTableDataView];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPThemeStateTableDataView, CPThemeStateSelectedDataView)];
[self setValue:aFont forThemeAttribute:@"font" inState:CPThemeState(CPThemeStateTableDataView, CPThemeStateSelectedDataView, CPThemeStateFirstResponder, CPThemeStateKeyWindow)];
[self layoutSubviews];
}
- (void)takeValueFromKeyPath:(CPString)aKeyPath ofObjects:(CPArray)objects
{
var count = objects.length,
@@ -1990,7 +2061,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
}
}
#pragma mark Overrides
// MARK: Overrides
/*!
Sets the text color of the receiver.
@@ -2059,7 +2130,7 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
return YES;
}
#pragma mark Private
// MARK: Private
- (BOOL)_isWithinUsablePlatformRect
{
@@ -2358,7 +2429,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
@end
#pragma mark -
// MARK: -
@implementation CPTextField (TableDataView)
@@ -2375,7 +2446,7 @@ var CPTextFieldIsEditableKey = "CPTextFieldIsEditableKey",
@end
#pragma mark -
// MARK: -
@implementation CPTextField (Deprecated)
+262 -70
View File
@@ -1,34 +1,29 @@
/*
* CPFontPanel.j
* AppKit
*
* TODOs:
* 1. make browser-width for size smaller and fix columns
* 2. add all the missing features from the MacOS X counterpart (sampleview)
*
*
* Created by Daniel Boehringer on 2/JAN/2014.
* All modifications copyright Daniel Boehringer 2013.
* Extensive code formatting and review by Andrew Hankinson
* Based on original work by
* Created by Emmanuel Maillard on 06/03/2010.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
CPFontPanel.j
AppKit
Created by Daniel Boehringer on 2/JAN/2014.
All modifications copyright Daniel Boehringer 2013.
Extensive code formatting and review by Andrew Hankinson
Based on original work by
Created by Emmanuel Maillard on 06/03/2010.
Copyright Emmanuel Maillard 2010.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPPanel.j"
@import "CPColorWell.j"
@@ -37,7 +32,6 @@
@import "CPText.j"
@import "CPFontManager.j"
@class CPTextStorage
@class CPLayoutManager
@class CPTextContainer
@@ -46,27 +40,30 @@
/*
Collection indexes
*/
var kTypefaceIndex_Normal = 0,
kTypefaceIndex_Italic = 1,
kTypefaceIndex_Bold = 2,
var kTypefaceIndex_Normal = 0,
kTypefaceIndex_Italic = 1,
kTypefaceIndex_Bold = 2,
kTypefaceIndex_BoldItalic = 3,
kToolbarHeight = 32,
kBorderSpacing = 6,
kInnerSpacing = 2,
kNothingChanged = 0,
kFontNameChanged = 1,
kTypefaceChanged = 2,
kSizeChanged = 3,
kTextColorChanged = 4,
kBackgroundColorChanged = 5,
kUnderlineChanged = 6,
kWeightChanged = 7,
kToolbarHeight = 32,
kPreviewHeight = 70,
kBorderSpacing = 6,
kInnerSpacing = 2,
kNothingChanged = 0,
kFontNameChanged = 1,
kTypefaceChanged = 2,
kSizeChanged = 3,
kTextColorChanged = 4,
kBackgroundColorChanged = 5,
kUnderlineChanged = 6,
kWeightChanged = 7,
_sharedFontPanel;
// FIXME<!> Locale support
var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"72", @"96"];
var _availableTraits = [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_availableSizes = [@"9", @"10", @"11", @"12", @"13", @"14", @"18", @"24", @"36", @"48", @"64", @"72", @"96", @"144", @"288"];
/*!
@ingroup appkit
@@ -77,6 +74,10 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
id _fontBrowser;
id _traitBrowser;
id _sizeBrowser;
// Preview
_CPFontPanelPreviewView _previewView;
CPArray _availableFonts;
id _textColorWell;
CPColor _textColor;
@@ -85,9 +86,8 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
int _fontChanges;
}
#pragma mark -
#pragma mark Class methods
// MARK: -
// MARK: Class methods
/*!
Check if the shared Font panel exists.
@@ -108,14 +108,18 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
return _sharedFontPanel;
}
- (BOOL)acceptsFirstResponder
{
return NO;
}
#pragma mark -
#pragma mark Init methods
// MARK: -
// MARK: Init methods
/*! @ignore */
- (id)init
{
if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 394) styleMask:(CPTitledWindowMask | CPClosableWindowMask /*| CPResizableWindowMask*/ )])
if (self = [super initWithContentRect:CGRectMake(100, 90, 450, 420) styleMask:(CPTitledWindowMask | CPClosableWindowMask | CPResizableWindowMask)])
{
[[self contentView] setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
[self setTitle:@"Font Panel"];
@@ -152,10 +156,49 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
[aBrowser setDoubleAction:@selector(dblClicked:)];
[aBrowser setAllowsEmptySelection:NO];
[aBrowser setAllowsMultipleSelection:NO];
// Config Scrollers
//[aBrowser setHasHorizontalScroller:NO];
//[aBrowser setHasVerticalScroller:YES];
//[aBrowser setAutohidesScrollers:YES];
//[aBrowser setMaxVisibleColumns:1];
[aBrowser setDelegate:self];
[[self contentView] addSubview:aBrowser];
}
- (void)_layoutBrowsers
{
var contentView = [self contentView],
contentBounds = [contentView bounds],
previewY = kBorderSpacing + kToolbarHeight + kInnerSpacing,
browserY = previewY + kPreviewHeight + 10,
browserHeight = CGRectGetHeight(contentBounds) - browserY - 10,
availableWidth = CGRectGetWidth(contentBounds) - 20; // 10px padding L/R
// Layout Calculations
// Increase sizeWidth slightly to 60 to allow space for the vertical scrollbar without clipping text
var sizeWidth = 90,
spacing = 5,
remainingWidth = availableWidth - sizeWidth - (spacing * 2),
// Split remaining roughly 60% font name, 40% trait
fontWidth = FLOOR(remainingWidth * 0.60),
traitWidth = remainingWidth - fontWidth;
// Apply frames and column constraints
[_fontBrowser setFrame:CGRectMake(10, browserY, fontWidth, browserHeight)];
[_fontBrowser setDefaultColumnWidth:fontWidth];
[_fontBrowser setLastColumn:0];
[_traitBrowser setFrame:CGRectMake(10 + fontWidth + spacing, browserY, traitWidth, browserHeight)];
[_traitBrowser setDefaultColumnWidth:traitWidth];
[_traitBrowser setLastColumn:0];
[_sizeBrowser setFrame:CGRectMake(10 + fontWidth + traitWidth + (spacing * 2), browserY, sizeWidth, browserHeight)];
[_sizeBrowser setDefaultColumnWidth:sizeWidth];
[_sizeBrowser setLastColumn:0];
}
- (void)_setupContents
{
if (_setupDone)
@@ -163,33 +206,53 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
_setupDone = YES;
// We set ourselves as delegate to handle resizing layout manually
[self setDelegate:self];
[self _setupToolbarView];
var contentView = [self contentView],
label = [CPTextField labelWithTitle:@"Font name"],
contentBounds = [contentView bounds],
upperView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(contentBounds), CGRectGetHeight(contentBounds) - (kBorderSpacing + kToolbarHeight + kInnerSpacing))];
contentBounds = [contentView bounds];
[contentView addSubview:_toolbarView];
_fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(10, 35, 150, 350)];
_traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(155, 35, 150, 350)];
_sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMake(300, 35, 140, 350)];
// Preview View
var previewY = kBorderSpacing + kToolbarHeight + kInnerSpacing;
_previewView = [[_CPFontPanelPreviewView alloc] initWithFrame:CGRectMake(10, previewY, CGRectGetWidth(contentBounds) - 20, kPreviewHeight)];
[_previewView setAutoresizingMask:CPViewWidthSizable];
[contentView addSubview:_previewView];
// Initialize Browsers with zero rect, _layoutBrowsers will size them
_fontBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
_traitBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
_sizeBrowser = [[CPBrowser alloc] initWithFrame:CGRectMakeZero()];
// Disable autoresizing masks because we are laying out manually in windowDidResize
[_fontBrowser setAutoresizingMask:CPViewNotSizable];
[_traitBrowser setAutoresizingMask:CPViewNotSizable];
[_sizeBrowser setAutoresizingMask:CPViewNotSizable];
[self _setupBrowser:_fontBrowser];
[self _setupBrowser:_traitBrowser];
[self _setupBrowser:_sizeBrowser];
// Perform initial layout
[self _layoutBrowsers];
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewDidChangeSelection:)
name:CPTextViewDidChangeSelectionNotification
object:nil];
}
- (void)windowDidResize:(CPNotification)aNotification
{
[self _layoutBrowsers];
}
- (void)textViewDidChangeSelection:(CPNotification)notification
{
[self _refreshWithTextView:[notification object]];
[self _refreshWithTextView:[notification object]];
}
- (void)_refreshWithTextView:(CPTextView)textView
@@ -197,6 +260,9 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if (![self isVisible])
return;
if (![textView respondsToSelector:@selector(_attributesForFontPanel)])
return;
var attribs = [textView _attributesForFontPanel],
font = [attribs objectForKey:CPFontAttributeName] || [[textView textStorage] font] || [CPFont systemFontOfSize:12.0],
color = [attribs objectForKey:CPForegroundColorAttributeName];
@@ -217,6 +283,9 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
[self setCurrentTrait:trait];
[self setCurrentSize:[font size] + ""]; //cast to string
// Update Preview
[_previewView setPreviewFont:font];
if (!color)
return;
@@ -253,7 +322,7 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
{
case kFontNameChanged:
newFont = [CPFont fontWithDescriptor:[[aFont fontDescriptor] fontDescriptorByAddingAttributes:
[CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0];
[CPDictionary dictionaryWithObject:[self currentFont] forKey:CPFontNameAttribute]] size:0.0];
break;
case kTypefaceChanged:
@@ -272,12 +341,13 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
newFont = [[CPFontManager sharedFontManager] convertFont:aFont toSize:[self currentSize]];
break;
case kNothingChanged:
case kNothingChanged:
break;
default:
CPLog.trace(@"FIXME: -[" + [self className] + " " + _cmd + "] unhandled _fontChanges: " + _fontChanges);
break;
}
return newFont;
@@ -285,7 +355,7 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
- (void)setCurrentSize:(CGSize)aSize
{
[_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0];
[_sizeBrowser selectRow:[_availableSizes indexOfObject:aSize] inColumn:0];
}
- (CPString)currentSize
@@ -295,7 +365,7 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
- (void)setCurrentFont:(CPFont)aFont
{
[_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0];
[_fontBrowser selectRow:[_availableFonts indexOfObject:[aFont familyName]] inColumn:0];
}
- (CPString)currentFont
@@ -320,9 +390,10 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
case kTypefaceIndex_BoldItalic:
row = 3;
break;
}
[_traitBrowser selectRow:row inColumn:0];
[_traitBrowser selectRow:row inColumn:0];
}
// FIXME<!> Locale support
@@ -370,6 +441,8 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if ([self currentTrait] != typefaceIndex)
[self setCurrentTrait:typefaceIndex ];
[_previewView setPreviewFont:font];
_fontChanges = kNothingChanged;
}
@@ -382,23 +455,30 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
////////////////////////////////////////////////////////////////////
// TODO: ask CPFontManager for traits //
- (void)browserClicked:(id)aBrowser
{
if (aBrowser === _fontBrowser)
{
_fontChanges = kFontNameChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
else if (aBrowser === _traitBrowser)
{
_fontChanges = kTypefaceChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
else if (aBrowser === _sizeBrowser)
{
_fontChanges = kSizeChanged;
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
}
// Apply change immediately to manager (standard behavior)
[[CPFontManager sharedFontManager] modifyFontViaPanel:self];
// Update our preview manually because convertFont: calls rely on selected rows
// We construct a temporary font to update the preview view immediately
var updatedFont = [self panelConvertFont:[_previewView font]];
if (updatedFont)
[_previewView setPreviewFont:updatedFont];
}
- (void)dblClicked:(id)sender
@@ -414,7 +494,7 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
if (aBrowser === _traitBrowser)
return [_availableTraits count];
return [_availableSizes count]
return [_availableSizes count];
}
- (id)browser:(id)aBrowser child:(int)index ofItem:(id)anItem
@@ -440,4 +520,116 @@ var _availableTraits= [@"Normal", @"Italic", @"Bold", @"Bold Italic"],
@end
// -----------------------------------------------------------------------------
// _CPFontPanelPreviewView
// A helper class to display a font sample with metrics grid
// -----------------------------------------------------------------------------
@implementation _CPFontPanelPreviewView : CPView
{
CPTextField _sampleText;
CPColor _gridColor;
float _gridSize;
}
- (id)initWithFrame:(CGRect)aRect
{
self = [super initWithFrame:aRect];
if (self)
{
[self setBackgroundColor:[CPColor whiteColor]];
_gridColor = [CPColor colorWithHexString:@"e4f4ff"];
_gridSize = 10.0;
_sampleText = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(aRect), CGRectGetHeight(aRect))];
[_sampleText setStringValue:@"AaYy-0123"];
[_sampleText setAlignment:CPCenterTextAlignment];
[_sampleText setVerticalAlignment:CPCenterVerticalTextAlignment];
[_sampleText setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_sampleText setTextColor:[CPColor blackColor]];
[self addSubview:_sampleText];
}
return self;
}
- (void)setPreviewFont:(CPFont)aFont
{
[_sampleText setFont:aFont];
[self setNeedsDisplay:YES];
}
- (CPFont)font
{
return [_sampleText font];
}
- (void)drawRect:(CGRect)dirtyRect
{
// Draw Grid (from MetricsView inspiration)
var context = [[CPGraphicsContext currentContext] graphicsPort],
bounds = [self bounds],
maxX = CGRectGetMaxX(bounds),
maxY = CGRectGetMaxY(bounds);
CGContextSetLineWidth(context, 1.0);
CGContextSetStrokeColor(context, _gridColor);
CGContextBeginPath(context);
for (var y = 0.5; y <= maxY; y += _gridSize)
{
CGContextMoveToPoint(context, 0.0, y);
CGContextAddLineToPoint(context, maxX, y);
}
for (var x = 0.5; x <= maxX; x += _gridSize)
{
CGContextMoveToPoint(context, x, 0.0);
CGContextAddLineToPoint(context, x, maxY);
}
CGContextStrokePath(context);
// Draw Baseline/Ascender/Descender (from BaselineView inspiration)
var font = [_sampleText font];
if (!font) return;
var ascender = [font ascender],
descender = [font descender],
lineHeight = [font defaultLineHeightForFont];
// Calculate the baseline.
// CPTextField with CPCenterVerticalTextAlignment usually centers the line height.
// Top of line = midY - (lineHeight / 2.0)
// Baseline = Top of line + ascender
var midY = maxY / 2.0,
baselineY = midY - (lineHeight / 2.0) + ascender;
CGContextSetStrokeColor(context, [CPColor redColor]);
CGContextBeginPath(context);
// Baseline
CGContextMoveToPoint(context, 0, baselineY);
CGContextAddLineToPoint(context, maxX, baselineY);
// Ascender Line
CGContextMoveToPoint(context, 0, baselineY - ascender);
CGContextAddLineToPoint(context, maxX, baselineY - ascender);
// Descender Line
CGContextMoveToPoint(context, 0, baselineY - descender);
CGContextAddLineToPoint(context, maxX, baselineY - descender);
CGContextStrokePath(context);
}
- (void)mouseDown:(CPEvent)anEvent
{
var text = prompt("Enter sample text:", [_sampleText stringValue]);
if (text)
[_sampleText setStringValue:text];
}
@end
[CPFontManager setFontPanelFactory:[CPFontPanel class]];
+250 -88
View File
@@ -31,8 +31,9 @@
@import "CPFont.j"
@global _MakeRangeFromAbs
@global document
@global CPBaselineOffsetAttributeName
@global CPSuperscriptAttributeName
@class CPTextContainer
@class CPTextView
@@ -71,11 +72,12 @@ _oncontextmenuhandler = function () { return false; };
BOOL _isValidatingLayoutAndGlyphs;
CPRange _removeInvalidLineFragmentsRange;
CPRange _lastEditedRange;
}
#pragma mark -
#pragma mark Init methods
// MARK: -
// MARK: Init methods
- (id)init
{
@@ -95,13 +97,14 @@ _oncontextmenuhandler = function () { return false; };
_textContainers = [[CPMutableArray alloc] init];
_textStorage = [[CPTextStorage alloc] init];
_typesetter = [CPTypesetter sharedSystemTypesetter];
_lastEditedRange = nil;
[_textStorage addLayoutManager:self];
}
#pragma mark -
#pragma mark Text containes method
// MARK: -
// MARK: Text containes method
- (void)insertTextContainer:(CPTextContainer)aContainer atIndex:(int)index
{
@@ -286,11 +289,12 @@ _oncontextmenuhandler = function () { return false; };
if (removeRange.length)
_removeInvalidLineFragmentsRange = CPMakeRangeCopy(removeRange);
else
_removeInvalidLineFragmentsRange = nil;
// We erased all lines
if (!startIndex)
[self setExtraLineFragmentRect:CGRectMake(0, 0) usedRect:CGRectMake(0, 0) textContainer:nil];
// document.title=startIndex;
[_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil];
@@ -303,13 +307,17 @@ _oncontextmenuhandler = function () { return false; };
- (BOOL)_rescuingInvalidFragmentsWasPossibleForGlyphRange:(CPRange)aRange
{
var l = _lineFragments.length,
location = aRange.location,
found = NO,
targetLine = 0;
// 1. EARLY EXIT: If there are no fragments to rescue (e.g. setting new text), do nothing.
if (!_lineFragmentsForRescue || _lineFragmentsForRescue.length === 0)
return NO;
// try to find the first linefragment of the desired range
for (; targetLine < l; targetLine++)
var l = _lineFragments.length,
location = aRange.location,
found = NO,
targetLine = l - 1; // Start from the END of the array
// 2. REVERSE SEARCH: The fragment we want is almost always at the end.
for (; targetLine >= 0; targetLine--)
{
if (CPLocationInRange(location, _lineFragments[targetLine]._range))
{
@@ -332,9 +340,6 @@ _oncontextmenuhandler = function () { return false; };
newLength = [[_textStorage string].length],
removalSkip = 1;
// if (ABS(newLength - oldLength) > 1)
// return NO;
if (![oldLineFragment isVisuallyIdenticalToFragment:newLineFragment])
{
isIdentical = NO;
@@ -367,6 +372,16 @@ _oncontextmenuhandler = function () { return false; };
l = _lineFragmentsForRescue.length,
newTargetLine = startLineForDOMRemoval + removalSkip;
// Ensure that the remaining lines we are attempting to rescue
// start after the end of the edited region.
if (newTargetLine < l && _lastEditedRange)
{
var firstRescuedLineNewLocation = _lineFragmentsForRescue[newTargetLine]._range.location + rangeOffset;
if (firstRescuedLineNewLocation < CPMaxRange(_lastEditedRange))
return NO;
}
for (; newTargetLine < l; newTargetLine++)
{
_lineFragmentsForRescue[newTargetLine]._isInvalid = NO; // protect them from final removal
@@ -438,6 +453,8 @@ _oncontextmenuhandler = function () { return false; };
{
var actualRange = CPMakeRange(CPNotFound,0);
_lastEditedRange = CPMakeRangeCopy(charRange);
[self invalidateLayoutForCharacterRange:invalidatedRange isSoft:NO actualCharacterRange:actualRange];
[self invalidateDisplayForGlyphRange:actualRange];
[self _validateLayoutAndGlyphs];
@@ -529,20 +546,26 @@ _oncontextmenuhandler = function () { return false; };
var frames = [fragment glyphFrames],
len = fragment._range.length;
for (var j = 0; j < len; j++)
if (frames)
{
if (CGRectContainsPoint(frames[j], point))
{
if (partialFraction)
partialFraction[0] = (point.x - frames[j].origin.x) / frames[j].size.width;
var maxLen = MIN(len, frames.length);
return fragment._range.location + j;
for (var j = 0; j < maxLen; j++)
{
var frame = frames[j];
if (frame && CGRectContainsPoint(frame, point))
{
if (partialFraction)
partialFraction[0] = (point.x - frame.origin.x) / frame.size.width;
return fragment._range.location + j;
}
}
}
}
}
// Not found, maybe a point left to the last character was clicked -> search again with broader constraints
if ([[_textStorage string] length])
{
for (var i = 0; i < c; i++)
@@ -551,30 +574,33 @@ _oncontextmenuhandler = function () { return false; };
if (fragment._textContainer === container)
{
// Within the horizontal territory of the current (not-empty) line?
if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y &&
point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height)
if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y &&
point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height)
{
if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y)
continue;
var nlLoc = CPMaxRange(fragment._range),
frames = [fragment glyphFrames];
if (frames && frames.length > 0)
{
// Skip tabs and move on the last fragment in this line
if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y)
continue;
var lastFrame = frames[frames.length - 1],
firstFrame = frames[0];
var nlLoc = CPMaxRange(fragment._range),
lastFrame = [fragment glyphFrames][fragment._range.length - 1],
firstFrame = [fragment glyphFrames][0];
if (lastFrame && firstFrame)
{
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0]))
nlLoc--;
// stay on the line the newline character belongs to
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0]))
nlLoc--;
// Clicked right to the last character
if (point.x > CGRectGetMaxX(lastFrame))
return nlLoc;
// Clicked left to the last character
else if (point.x <= CGRectGetMinX(firstFrame))
return fragment._range.location;
else
return nlLoc;
if (point.x > CGRectGetMaxX(lastFrame))
return nlLoc;
else if (point.x <= CGRectGetMinX(firstFrame))
return fragment._range.location;
else
return nlLoc;
}
}
}
}
}
@@ -704,7 +730,11 @@ _oncontextmenuhandler = function () { return false; };
var index = location - lineFragment._range.location;
return lineFragment._glyphsOffsets[index];
if (index < 0 || !lineFragment._glyphsOffsets || index >= lineFragment._glyphsOffsets.length)
return 0.0;
var offset = lineFragment._glyphsOffsets[index];
return (offset === undefined) ? 0.0 : offset;
}
- (double)_descentAtLocation:(unsigned)location
@@ -716,7 +746,11 @@ _oncontextmenuhandler = function () { return false; };
var index = location - lineFragment._range.location;
return lineFragment._glyphsFrames[index]._descent;
if (index < 0 || !lineFragment._glyphsFrames || index >= lineFragment._glyphsFrames.length)
return 0.0;
var frame = lineFragment._glyphsFrames[index];
return (frame && frame._descent !== undefined) ? frame._descent : 0.0;
}
- (void)setLineFragmentRect:(CGRect)fragmentRect forGlyphRange:(CPRange)glyphRange usedRect:(CGRect)usedRect
@@ -840,11 +874,16 @@ _oncontextmenuhandler = function () { return false; };
{
if (_lineFragments.length > 0 && index >= [self numberOfGlyphs] - 1)
{
var lineFragment= _lineFragments[_lineFragments.length - 1],
var lineFragment = _lineFragments[_lineFragments.length - 1],
glyphFrames = [lineFragment glyphFrames];
if (glyphFrames.length > 0)
return CGPointCreateCopy(glyphFrames[glyphFrames.length - 1].origin);
if (glyphFrames && glyphFrames.length > 0)
{
var frame = glyphFrames[glyphFrames.length - 1];
if (frame)
return CGPointCreateCopy(frame.origin);
}
}
var lineFragment = _objectWithLocationInRange(_lineFragments, index);
@@ -855,8 +894,17 @@ _oncontextmenuhandler = function () { return false; };
return CGPointCreateCopy(lineFragment._location);
var glyphFrames = [lineFragment glyphFrames];
var relativeIndex = index - lineFragment._range.location;
return CGPointCreateCopy(glyphFrames[index - lineFragment._range.location].origin);
if (glyphFrames && relativeIndex >= 0 && relativeIndex < glyphFrames.length)
{
var frame = glyphFrames[relativeIndex];
if (frame)
return CGPointCreateCopy(frame.origin);
}
return CGPointCreateCopy(lineFragment._location);
}
return CGPointMakeZero();
@@ -902,7 +950,6 @@ _oncontextmenuhandler = function () { return false; };
inTextContainer:(CPTextContainer)container
rectCount:(CGRectPointer)rectCount
{
var rectArray = [],
lineFragments = _objectsInRange(_lineFragments, selectedCharRange);
@@ -921,21 +968,24 @@ _oncontextmenuhandler = function () { return false; };
rect = nil,
len = fragment._range.length;
for (var j = 0; j < len; j++)
if (frames)
{
if (CPLocationInRange(fragment._range.location + j, selectedCharRange))
for (var j = 0; j < len; j++)
{
var correctedRect = CGRectCreateCopy(frames[j]);
correctedRect.size.height -= frames[j]._descent;
correctedRect.origin.y -= frames[j]._descent;
if (j < frames.length && CPLocationInRange(fragment._range.location + j, selectedCharRange))
{
var frame = frames[j];
if (!rect)
rect = CGRectCreateCopy(correctedRect);
else
rect = CGRectUnion(rect, correctedRect);
if (frame)
{
var correctedRect = CGRectCreateCopy(frame);
if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)]))
rect.size.width = containerSize.width - rect.origin.x;
if (!rect)
rect = CGRectCreateCopy(correctedRect);
else
rect = CGRectUnion(rect, correctedRect);
}
}
}
}
@@ -946,7 +996,7 @@ _oncontextmenuhandler = function () { return false; };
var len = rectArray.length;
for (var i = 0; i < len - 1; i++) // extend the width of all but the last one
for (var i = 0; i < len - 1; i++)
{
if (FLOOR(CGRectGetMaxY(rectArray[i])) == FLOOR(CGRectGetMaxY(rectArray[i + 1])))
continue;
@@ -1082,8 +1132,8 @@ var _objectsInRange = function(aList, aRange)
CPMutableArray _runs;
}
#pragma mark -
#pragma mark Init methods
// MARK: -
// MARK: Init methods
- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)aColor
{
@@ -1092,6 +1142,10 @@ var _objectsInRange = function(aList, aRange)
- (id)createDOMElementWithText:(CPString)aString andFont:(CPFont)aFont andColor:(CPColor)fgColor andBackgroundColor:(CPColor)bgColor andUnderline:(CPUnderlineStyle)aUnderline
{
if (!aString || aString.length === 0)
return nil;
#if PLATFORM(DOM)
var style,
span = document.createElement("span");
@@ -1172,18 +1226,16 @@ var _objectsInRange = function(aList, aRange)
effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange;
var string = [textStorage._string substringWithRange:effectiveRange],
underline = [attributes objectForKey:CPUnderlineStyleAttributeName] || CPUnderlineStyleNone;
underline = [attributes objectForKey:CPUnderlineStyleAttributeName] || CPUnderlineStyleNone,
paragraphStyle = [attributes objectForKey:CPParagraphStyleAttributeName] || [CPParagraphStyle defaultParagraphStyle];
// this is an attachment -> create a run for it
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 view = [attributes objectForKey:_CPAttachmentView];
var run = {_range:CPMakeRangeCopy(effectiveRange), color:nil, font:nil, elem:nil, string:nil, view:view, paragraphStyle:paragraphStyle, underline:underline, baselineOffset:0.0};
_runs.push(run);
}
}
@@ -1191,10 +1243,97 @@ var _objectsInRange = function(aList, aRange)
{
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};
font = [attributes objectForKey:CPFontAttributeName] || [textStorage font] || [CPFont systemFontOfSize:12.0];
_runs.push(run);
var baselineOffset = [attributes objectForKey:CPBaselineOffsetAttributeName],
superscript = [attributes objectForKey:CPSuperscriptAttributeName];
if (baselineOffset === nil || baselineOffset === undefined || typeof baselineOffset !== "number")
baselineOffset = 0.0;
if (superscript === nil || superscript === undefined || typeof superscript !== "number")
superscript = 0;
if (superscript !== 0)
{
var size = [font size],
scaledSize = size * 0.65,
fontName = [font familyName],
isBold = [font isBold],
isItalic = [font isItalic];
font = [CPFont _fontWithName:fontName size:scaledSize bold:isBold italic:isItalic];
if (baselineOffset === 0.0)
{
if (superscript > 0)
baselineOffset = size * 0.35;
else
baselineOffset = -size * 0.15;
}
}
var currentLoc = effectiveRange.location,
strLen = string.length,
startIdx = 0;
for (var i = 0; i < strLen; i++)
{
if (string.charCodeAt(i) === 9) // Tabulator-Zeichen '\t'
{
if (i > startIdx)
{
var subString = string.substring(startIdx, i),
subRange = CPMakeRange(currentLoc + startIdx, i - startIdx),
run = {
_range: subRange,
color: color,
font: font,
elem: nil,
string: subString,
bgcolor: bgcolor,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: baselineOffset
};
_runs.push(run);
}
var tabRange = CPMakeRange(currentLoc + i, 1),
tabRun = {
_range: tabRange,
color: nil,
font: nil,
elem: nil,
string: nil,
bgcolor: nil,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: 0.0
};
_runs.push(tabRun);
startIdx = i + 1;
}
}
if (startIdx < strLen)
{
var subString = string.substring(startIdx, strLen),
subRange = CPMakeRange(currentLoc + startIdx, strLen - startIdx),
run = {
_range: subRange,
color: color,
font: font,
elem: nil,
string: subString,
bgcolor: bgcolor,
paragraphStyle: paragraphStyle,
underline: underline,
baselineOffset: baselineOffset
};
_runs.push(run);
}
}
if (!CPMaxRange(effectiveRange))
@@ -1218,7 +1357,10 @@ var _objectsInRange = function(aList, aRange)
{
_glyphsFrames[i] = CGRectMake(origin.x, origin.y, someAdvancements[i].width, height);
_glyphsFrames[i]._descent = someAdvancements[i].descent;
_glyphsOffsets[i] = height - someAdvancements[i].height;
// Align the run's baseline with the common line baseline (_location.y)
_glyphsOffsets[i] = _location.y - someAdvancements[i].height;
origin.x += someAdvancements[i].width;
}
}
@@ -1266,13 +1408,11 @@ var _objectsInRange = function(aList, aRange)
for (var i = 0; i < l; i++)
{
if (_runs[i].view && _runs[i].DOMactive)
[_runs[i].view removeFromSuperview];
if (_runs[i].elem && _runs[i].DOMactive)
{
if (_runs[i].view)
[_runs[i].view removeFromSuperview];
else
_textContainer._textView._DOMElement.removeChild(_runs[i].elem);
}
_textContainer._textView._DOMElement.removeChild(_runs[i].elem);
_runs[i].elem = nil;
_runs[i].DOMactive = NO;
@@ -1285,6 +1425,9 @@ var _objectsInRange = function(aList, aRange)
c = runs.length,
orig = CGPointMake(_fragmentRect.origin.x, _fragmentRect.origin.y);
if (_runs.length === 0)
return;
for (var i = 0; i < c; i++)
{
var run = runs[i];
@@ -1299,13 +1442,21 @@ var _objectsInRange = function(aList, aRange)
continue;
var loc = run._range.location - _runs[0]._range.location;
// Safety bounds check to protect against uninitialized/empty glyph frames or offsets
if (loc < 0 || loc >= _glyphsFrames.length || !_glyphsFrames[loc] || !_glyphsOffsets || loc >= _glyphsOffsets.length)
continue;
orig.x = _glyphsFrames[loc].origin.x + aPoint.x;
orig.y = _glyphsFrames[loc].origin.y + aPoint.y + _glyphsOffsets[loc];
if(run.elem)
if(run.elem || run.view)
{
run.elem.style.left = (orig.x) + "px";
run.elem.style.top = (orig.y) + "px";
if (run.elem)
{
run.elem.style.left = (orig.x) + "px";
run.elem.style.top = (orig.y) + "px";
}
if (run.view)
[run.view setFrameOrigin:orig];
@@ -1313,8 +1464,9 @@ var _objectsInRange = function(aList, aRange)
if (!run.DOMactive)
{
if (run.view)
[self._textContainer._textView addSubview:run.view];
else
[_textContainer._textView addSubview:run.view];
if (run.elem)
_textContainer._textView._DOMElement.appendChild(run.elem);
}
@@ -1353,9 +1505,17 @@ var _objectsInRange = function(aList, aRange)
if (!_RectEqualToRectHorizontally(newLineFragment._fragmentRect, _fragmentRect))
return NO;
if (newFragmentRuns[i].color !== oldFragmentRuns[i].color || newFragmentRuns[i].font !== oldFragmentRuns[i].font)
if (newFragmentRuns[i].color !== oldFragmentRuns[i].color ||
newFragmentRuns[i].bgcolor !== oldFragmentRuns[i].bgcolor ||
newFragmentRuns[i].font !== oldFragmentRuns[i].font ||
newFragmentRuns[i].baselineOffset !== oldFragmentRuns[i].baselineOffset)
return NO;
var oldStyle = oldFragmentRuns[i].paragraphStyle || [CPParagraphStyle defaultParagraphStyle],
newStyle = newFragmentRuns[i].paragraphStyle || [CPParagraphStyle defaultParagraphStyle];
if (![oldStyle isEqual:newStyle])
return NO;
}
return YES;
@@ -1371,12 +1531,14 @@ var _objectsInRange = function(aList, aRange)
{
_runs[i]._range.location += rangeOffset;
if (verticalOffset && _runs[i].elem)
if (verticalOffset)
{
if (_runs[i].view)
_runs[i].view._frame.origin.y += verticalOffset;
_runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
if (_runs[i].elem)
_runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px';
_runs[i].DOMpatched = YES;
}
}
+304 -141
View File
@@ -2,10 +2,6 @@
* CPParagraphStyle.j
* AppKit
*
* FIXME
* This is basically a stub.
* We need to store all the spacing informations as well as writing direction (among others)
*
* Created by Daniel Boehringer on 11/01/2014
* Copyright Daniel Boehringer 2014.
*
@@ -26,198 +22,365 @@
@import <Foundation/CPObject.j>
@import <Foundation/CPArray.j>
@import "CPText.j"
CPLeftTabStopType = 0;
@import <Foundation/CPDictionary.j>
CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName";
var _sharedDefaultParagraphStyle,
_defaultTabStopArray;
// Define missing global tab stop type constants
CPLeftTabStopType = 0;
CPRightTabStopType = 1;
CPCenterTabStopType = 2;
CPDecimalTabStopType = 3;
@implementation CPParagraphStyle : CPObject
// Standard Tab Interval (28pts is roughly 4 spaces in standard fonts)
var kDefaultTabInterval = 28.0;
// MARK: - CPTextTab Implementation
@implementation CPTextTab : CPObject
{
CPArray _tabStops @accessors(property=tabStops);
CPTextAlignment _alignment @accessors(property=alignment);
unsigned _firstLineHeadIndent @accessors(property=firstLineHeadIndent);
unsigned _headIndent @accessors(property=headIndent);
unsigned _tailIndent @accessors(property=tailIndent);
unsigned _paragraphSpacing @accessors(property=paragraphSpacing);
unsigned _minimumLineHeight @accessors(property=minimumLineHeight);
unsigned _maximumLineHeight @accessors(property=maximumLineHeight);
unsigned _lineSpacing @accessors(property=lineSpacing);
CPTextAlignment _alignment @accessors(readonly, property=alignment);
float _location @accessors(readonly, property=location);
CPDictionary _options @accessors(readonly, property=options);
}
#pragma mark -
#pragma mark Class methods
+ (CPParagraphStyle)defaultParagraphStyle
- (id)initWithTextAlignment:(CPTextAlignment)anAlignment location:(float)aLocation options:(CPDictionary)options
{
if (!_sharedDefaultParagraphStyle)
_sharedDefaultParagraphStyle = [self new];
return _sharedDefaultParagraphStyle;
}
+ (CPArray)_defaultTabStops
{
if (!_defaultTabStopArray)
if (self = [super init])
{
var i;
_defaultTabStopArray = [];
// <!> FIXME: Define constants for these magic numbers: 13, 28
for (i = 1; i < 16 ; i++)
{
_defaultTabStopArray.push([[CPTextTab alloc] initWithType:CPLeftTabStopType location:i * 28]);
}
_alignment = anAlignment;
_location = aLocation;
_options = [options copy];
}
return _defaultTabStopArray;
}
#pragma mark -
#pragma mark Init methods
- (id)init
{
[self _initWithDefaults];
return self;
}
- (CPParagraphStyle)initWithParagraphStyle:(CPParagraphStyle)other
// Convenience initializer matching AppKit behavior
- (id)initWithType:(CPTabStopType)aType location:(float)aLocation
{
self = [super init];
_tabStops = [other._tabStops copy];
_alignment = other._alignment;
_firstLineHeadIndent = other._firstLineHeadIndent;
_headIndent = other._headIndent;
_tailIndent = other._tailIndent;
_paragraphSpacing = other._paragraphSpacing;
_minimumLineHeight = other._minimumLineHeight;
_maximumLineHeight = other._maximumLineHeight;
_lineSpacing = other._lineSpacing;
return self;
// Map old TabStopType to TextAlignment for modern compatibility
return [self initWithTextAlignment:aType location:aLocation options:nil];
}
- (void)_initWithDefaults
// Added to resolve the unrecognized selector exception in the RTF producer
- (CPTabStopType)tabStopType
{
_alignment = CPLeftTextAlignment;
_tabStops = [[[self class] _defaultTabStops] copy];
return _alignment;
}
- (void)addTabStop:(CPTextTab)aStop
- (BOOL)isEqual:(id)other
{
_tabStops.push(aStop);
if (self === other) return YES;
if (![other isKindOfClass:[CPTextTab class]]) return NO;
return _location === [other location] &&
_alignment === [other alignment] &&
((_options == nil && [other options] == nil) || [_options isEqualToDictionary:[other options]]);
}
- (id)copy
{
var other = [[self class] alloc];
return [other initWithParagraphStyle:self];
return [[CPTextTab alloc] initWithTextAlignment:_alignment location:_location options:_options];
}
@end
var CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey",
CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey",
CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey",
CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey",
CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey",
CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey",
CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey",
CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey",
CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey";
@implementation CPParagraphStyle (CPCoding)
- (id)initWithCoder:(id)aCoder
- (id)initWithCoder:(CPCoder)aCoder
{
self = [self init];
if (self)
if (self = [super init])
{
_tabStops = [aCoder decodeObjectForKey:"CPParagraphStyleTabStopsKey"];
_alignment = [aCoder decodeIntForKey:"CPParagraphStyleAlignmentKey"];
_firstLineHeadIndent = [aCoder decodeIntForKey:"CPParagraphStyleFirstLineHeadIndentKey"];
_headIndent = [aCoder decodeIntForKey:"CPParagraphStyleHeadIndentKey"];
_tailIndent = [aCoder decodeIntForKey:"CPParagraphStyleTailIndentKey"];
_paragraphSpacing = [aCoder decodeIntForKey:"CPParagraphStyleParagraphSpacingKey"];
_minimumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMinimumLineHeightKey"];
_maximumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMaximumLineHeightKey"];
_lineSpacing = [aCoder decodeIntForKey:"CPParagraphStyleLineSpacingKey"];
_alignment = [aCoder decodeIntForKey:@"CPTextTabAlignment"];
_location = [aCoder decodeFloatForKey:@"CPTextTabLocation"];
_options = [aCoder decodeObjectForKey:@"CPTextTabOptions"];
}
return self;
}
- (void)encodeWithCoder:(id)aCoder
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeInt:_alignment forKey:"CPParagraphStyleAlignmentKey"];
[aCoder encodeObject:_tabStops forKey:"CPParagraphStyleTabStopsKey"];
[aCoder encodeInt:_firstLineHeadIndent forKey:"CPParagraphStyleFirstLineHeadIndentKey"];
[aCoder encodeInt:_headIndent forKey:"CPParagraphStyleHeadIndentKey"];
[aCoder encodeInt:_tailIndent forKey:"CPParagraphStyleTailIndentKey"];
[aCoder encodeInt:_paragraphSpacing forKey:"CPParagraphStyleParagraphSpacingKey"];
[aCoder encodeInt:_minimumLineHeight forKey:"CPParagraphStyleMinimumLineHeightKey"];
[aCoder encodeInt:_maximumLineHeight forKey:"CPParagraphStyleMaximumLineHeightKey"];
[aCoder encodeInt:_lineSpacing forKey:"CPParagraphStyleLineSpacingKey"];
[aCoder encodeInt:_alignment forKey:@"CPTextTabAlignment"];
[aCoder encodeFloat:_location forKey:@"CPTextTabLocation"];
[aCoder encodeObject:_options forKey:@"CPTextTabOptions"];
}
@end
@implementation CPTextTab : CPObject
// MARK: - CPParagraphStyle Implementation
var _sharedDefaultParagraphStyle = nil;
@implementation CPParagraphStyle : CPObject
{
int _type @accessors(property = tabStopType);
double _location @accessors(property = location);
float _lineSpacing @accessors(readonly, property=lineSpacing);
float _paragraphSpacing @accessors(readonly, property=paragraphSpacing);
CPTextAlignment _alignment @accessors(readonly, property=alignment);
float _headIndent @accessors(readonly, property=headIndent);
float _tailIndent @accessors(readonly, property=tailIndent);
float _firstLineHeadIndent @accessors(readonly, property=firstLineHeadIndent);
float _minimumLineHeight @accessors(readonly, property=minimumLineHeight);
float _maximumLineHeight @accessors(readonly, property=maximumLineHeight);
CPLineBreakMode _lineBreakMode @accessors(readonly, property=lineBreakMode);
CPWritingDirection _baseWritingDirection @accessors(readonly, property=baseWritingDirection);
float _lineHeightMultiple @accessors(readonly, property=lineHeightMultiple);
float _paragraphSpacingBefore @accessors(readonly, property=paragraphSpacingBefore);
float _defaultTabInterval @accessors(readonly, property=defaultTabInterval);
CPArray _tabStops @accessors(readonly, property=tabStops);
}
- (id)initWithType:(CPTabStopType) aType location:(double) aLocation
+ (CPParagraphStyle)defaultParagraphStyle
{
if ([self = [super init]])
if (!_sharedDefaultParagraphStyle)
{
_type = aType;
_location = aLocation;
_sharedDefaultParagraphStyle = [[CPParagraphStyle alloc] init];
// Ensure defaults are set on the shared instance internal vars
// Since it's immutable, we rely on the init to set these.
}
return _sharedDefaultParagraphStyle;
}
+ (CPWritingDirection)defaultWritingDirectionForLanguage:(CPString)languageName
{
// Simplified: Cappuccino usually assumes LTR unless specified otherwise.
return CPWritingDirectionLeftToRight;
}
- (id)init
{
if (self = [super init])
{
_lineSpacing = 0.0;
_paragraphSpacing = 0.0;
_alignment = CPLeftTextAlignment;
_headIndent = 0.0;
_tailIndent = 0.0;
_firstLineHeadIndent = 0.0;
_minimumLineHeight = 0.0;
_maximumLineHeight = 0.0;
_lineBreakMode = CPLineBreakByWordWrapping;
_baseWritingDirection = CPWritingDirectionNatural;
_lineHeightMultiple = 0.0;
_paragraphSpacingBefore = 0.0;
_defaultTabInterval = kDefaultTabInterval;
// Generate default tab stops
_tabStops = [];
for (var i = 1; i <= 12; i++)
{
[_tabStops addObject:[[CPTextTab alloc] initWithType:CPLeftTextAlignment
location:i * kDefaultTabInterval]];
}
}
return self;
}
@end
var CPTextTabTypeKey = @"CPTextTabTypeKey",
CPTextTabLocationKey = @"CPTextTabLocationKey";
@implementation CPTextTab (CPCoding)
- (id)initWithCoder:(id)aCoder
- (id)initWithParagraphStyle:(CPParagraphStyle)other
{
self = [self init];
if (self)
if (self = [super init])
{
_type = [aCoder decodeIntForKey:"CPTextTabTypeKey"];
_location = [aCoder decodeDoubleForKey:"CPTextTabLocationKey"];
_lineSpacing = [other lineSpacing];
_paragraphSpacing = [other paragraphSpacing];
_alignment = [other alignment];
_headIndent = [other headIndent];
_tailIndent = [other tailIndent];
_firstLineHeadIndent = [other firstLineHeadIndent];
_minimumLineHeight = [other minimumLineHeight];
_maximumLineHeight = [other maximumLineHeight];
_lineBreakMode = [other lineBreakMode];
_baseWritingDirection = [other baseWritingDirection];
_lineHeightMultiple = [other lineHeightMultiple];
_paragraphSpacingBefore = [other paragraphSpacingBefore];
_defaultTabInterval = [other defaultTabInterval];
_tabStops = [[other tabStops] copy];
}
return self;
}
- (void)encodeWithCoder:(id)aCoder
- (id)copy
{
[aCoder encodeInt:_type forKey:"CPTextTabTypeKey"];
[aCoder encodeDouble:_location forKey:"CPTextTabLocationKey"];
// Since this class is immutable, return self.
// Subclasses (Mutable) will override.
if ([self class] === [CPParagraphStyle class])
return self;
return [[CPParagraphStyle alloc] initWithParagraphStyle:self];
}
- (id)mutableCopy
{
return [[CPMutableParagraphStyle alloc] initWithParagraphStyle:self];
}
// MARK: - Coding Support
- (id)initWithCoder:(CPCoder)aCoder
{
if (self = [super init])
{
_lineSpacing = [aCoder decodeFloatForKey:@"CPParagraphStyleLineSpacing"];
_paragraphSpacing = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacing"];
_alignment = [aCoder decodeIntForKey:@"CPParagraphStyleAlignment"];
_headIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleHeadIndent"];
_tailIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleTailIndent"];
_firstLineHeadIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleFirstLineHeadIndent"];
_minimumLineHeight = [aCoder decodeFloatForKey:@"CPParagraphStyleMinimumLineHeight"];
_maximumLineHeight = [aCoder decodeFloatForKey:@"CPParagraphStyleMaximumLineHeight"];
_lineBreakMode = [aCoder decodeIntForKey:@"CPParagraphStyleLineBreakMode"];
_baseWritingDirection = [aCoder decodeIntForKey:@"CPParagraphStyleBaseWritingDirection"];
_lineHeightMultiple = [aCoder decodeFloatForKey:@"CPParagraphStyleLineHeightMultiple"];
_paragraphSpacingBefore = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacingBefore"];
_defaultTabInterval = [aCoder decodeFloatForKey:@"CPParagraphStyleDefaultTabInterval"];
_tabStops = [aCoder decodeObjectForKey:@"CPParagraphStyleTabStops"];
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeFloat:_lineSpacing forKey:@"CPParagraphStyleLineSpacing"];
[aCoder encodeFloat:_paragraphSpacing forKey:@"CPParagraphStyleParagraphSpacing"];
[aCoder encodeInt:_alignment forKey:@"CPParagraphStyleAlignment"];
[aCoder encodeFloat:_headIndent forKey:@"CPParagraphStyleHeadIndent"];
[aCoder encodeFloat:_tailIndent forKey:@"CPParagraphStyleTailIndent"];
[aCoder encodeFloat:_firstLineHeadIndent forKey:@"CPParagraphStyleFirstLineHeadIndent"];
[aCoder encodeFloat:_minimumLineHeight forKey:@"CPParagraphStyleMinimumLineHeight"];
[aCoder encodeFloat:_maximumLineHeight forKey:@"CPParagraphStyleMaximumLineHeight"];
[aCoder encodeInt:_lineBreakMode forKey:@"CPParagraphStyleLineBreakMode"];
[aCoder encodeInt:_baseWritingDirection forKey:@"CPParagraphStyleBaseWritingDirection"];
[aCoder encodeFloat:_lineHeightMultiple forKey:@"CPParagraphStyleLineHeightMultiple"];
[aCoder encodeFloat:_paragraphSpacingBefore forKey:@"CPParagraphStyleParagraphSpacingBefore"];
[aCoder encodeFloat:_defaultTabInterval forKey:@"CPParagraphStyleDefaultTabInterval"];
[aCoder encodeObject:_tabStops forKey:@"CPParagraphStyleTabStops"];
}
// MARK: - Equality
- (BOOL)isEqual:(id)other
{
if (self === other) return YES;
if (![other isKindOfClass:[CPParagraphStyle class]]) return NO;
return _lineSpacing === [other lineSpacing] &&
_paragraphSpacing === [other paragraphSpacing] &&
_alignment === [other alignment] &&
_headIndent === [other headIndent] &&
_tailIndent === [other tailIndent] &&
_firstLineHeadIndent === [other firstLineHeadIndent] &&
_lineBreakMode === [other lineBreakMode] &&
[_tabStops isEqualToArray:[other tabStops]];
}
@end
// MARK: - CPMutableParagraphStyle Implementation
@implementation CPMutableParagraphStyle : CPParagraphStyle
{
}
- (id)initWithParagraphStyle:(CPParagraphStyle)other
{
if (self = [super initWithParagraphStyle:other])
{
// Ensure our tab stops array is mutable in the mutable subclass
_tabStops = [[other tabStops] mutableCopy];
}
return self;
}
- (id)initWithCoder:(CPCoder)aCoder
{
if (self = [super initWithCoder:aCoder])
{
_tabStops = [_tabStops mutableCopy];
}
return self;
}
- (void)setLineSpacing:(float)aLineSpacing
{
_lineSpacing = aLineSpacing;
}
- (void)setParagraphSpacing:(float)aParagraphSpacing
{
_paragraphSpacing = aParagraphSpacing;
}
- (void)setAlignment:(CPTextAlignment)anAlignment
{
_alignment = anAlignment;
}
- (void)setHeadIndent:(float)aHeadIndent
{
_headIndent = aHeadIndent;
}
- (void)setTailIndent:(float)aTailIndent
{
_tailIndent = aTailIndent;
}
- (void)setFirstLineHeadIndent:(float)aFirstLineHeadIndent
{
_firstLineHeadIndent = aFirstLineHeadIndent;
}
- (void)setMinimumLineHeight:(float)aMinimumLineHeight
{
_minimumLineHeight = aMinimumLineHeight;
}
- (void)setMaximumLineHeight:(float)aMaximumLineHeight
{
_maximumLineHeight = aMaximumLineHeight;
}
- (void)setLineBreakMode:(CPLineBreakMode)aLineBreakMode
{
_lineBreakMode = aLineBreakMode;
}
- (void)setBaseWritingDirection:(CPWritingDirection)aBaseWritingDirection
{
_baseWritingDirection = aBaseWritingDirection;
}
- (void)setLineHeightMultiple:(float)aLineHeightMultiple
{
_lineHeightMultiple = aLineHeightMultiple;
}
- (void)setParagraphSpacingBefore:(float)aParagraphSpacingBefore
{
_paragraphSpacingBefore = aParagraphSpacingBefore;
}
- (void)setDefaultTabInterval:(float)aDefaultTabInterval
{
_defaultTabInterval = aDefaultTabInterval;
}
- (void)addTabStop:(CPTextTab)aTabStop
{
[_tabStops addObject:aTabStop];
}
- (void)removeTabStop:(CPTextTab)aTabStop
{
[_tabStops removeObject:aTabStop];
}
- (void)setTabStops:(CPArray)newTabStops
{
if (_tabStops === newTabStops) return;
_tabStops = [newTabStops mutableCopy];
}
- (id)copyWithZone:(CPZone)aZone
{
// Return an immutable copy
return [[CPParagraphStyle alloc] initWithParagraphStyle:self];
}
@end
+670
View File
@@ -0,0 +1,670 @@
/*
* CPRulerView.j
* AppKit
*
* Created by Daniel Boehringer on 11/01/2014
* Copyright Daniel Boehringer 2014.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPObject.j>
@import "CPView.j"
@import "CPTextField.j"
@import "CPColor.j"
@import "CPFont.j"
@import "CPMenu.j"
@import "CPMenuItem.j"
// Orientations matching AppKit standards
// typedef enum CPRulerOrientation
CPHorizontalRuler = 0,
CPVerticalRuler = 1,
CPRulerOrientationHorizontal = 0,
CPRulerOrientationVertical = 1
@class CPRulerView;
// MARK: - CPRulerMarker (Interactive Handles with Dynamic Alignment Icons)
@implementation CPRulerMarker : CPView
{
CPRulerView _rulerView @accessors(property=rulerView);
float _imageValue @accessors(property=imageValue);
id _representedObject @accessors(property=representedObject);
CPTextField _label;
CPView _customHandleView;
}
- (id)initWithRulerView:(CPRulerView)aRulerView markerLocation:(float)aLocation imageValue:(float)anImageValue representedObject:(id)anObject
{
if (self = [super initWithFrame:CGRectMake(0, 0, 12, 12)])
{
_rulerView = aRulerView;
_imageValue = anImageValue;
_representedObject = anObject;
_label = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 12, 12)];
[_label setFont:[CPFont systemFontOfSize:10.0]];
[_label setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
[_label setAlignment:CPCenterTextAlignment];
[self addSubview:_label];
_customHandleView = [[CPView alloc] initWithFrame:CGRectMakeZero()];
[self addSubview:_customHandleView];
[self updateMarkerIcon];
}
return self;
}
- (CPTextField)label
{
return _label;
}
- (void)setRepresentedObject:(id)anObject
{
_representedObject = anObject;
[self updateMarkerIcon];
}
- (void)setFrame:(CGRect)aFrame
{
[super setFrame:aFrame];
[self updateMarkerIcon];
}
// Dynamically sets the Unicode triangle direction based on the alignment or indent type,
// or draws custom split-height grab handles for indentation controls.
- (void)updateMarkerIcon
{
var isIndentMarker = (_representedObject === @"CPFirstLineIndent" || _representedObject === @"CPHeadIndent");
if (isIndentMarker)
{
[_label setHidden:YES];
[_customHandleView setHidden:NO];
var frame = [self bounds];
[_customHandleView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
// Remove old internal rendering to update cleanly
[[_customHandleView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
var isFirstLine = (_representedObject === @"CPFirstLineIndent");
// Dark outline/border representation
[_customHandleView setBackgroundColor:[CPColor colorWithWhite:0.5 alpha:1.0]];
// Inner fill (top handle is lighter, bottom is slightly darker)
var innerView = [[CPView alloc] initWithFrame:CGRectMake(1.0, 1.0, frame.size.width - 2.0, frame.size.height - 2.0)];
if (isFirstLine)
[innerView setBackgroundColor:[CPColor colorWithWhite:0.92 alpha:1.0]];
else
[innerView setBackgroundColor:[CPColor colorWithWhite:0.80 alpha:1.0]];
[_customHandleView addSubview:innerView];
// Horizontal indicator line to visually guide drag interactions
var gripLine = [[CPView alloc] initWithFrame:CGRectMake(Math.floor(frame.size.width / 2.0) - 1.0, 2.0, 1.0, frame.size.height - 4.0)];
[gripLine setBackgroundColor:[CPColor colorWithWhite:0.6 alpha:1.0]];
[innerView addSubview:gripLine];
}
else
{
[_label setHidden:NO];
[_customHandleView setHidden:YES];
[_label setFrame:[self bounds]];
if ([_representedObject isKindOfClass:[CPTextTab class]])
{
var align = [_representedObject alignment];
if (align === CPLeftTextAlignment)
[_label setStringValue:@"▶"]; // Left-aligned points Right
else if (align === CPCenterTextAlignment)
[_label setStringValue:@"▼"]; // Center-aligned points Down
else if (align === CPRightTextAlignment)
[_label setStringValue:@"◀"]; // Right-aligned points Left
}
else if ([_representedObject isKindOfClass:[CPString class]])
{
if (_representedObject === @"CPTailIndent")
[_label setStringValue:@"⥘"]; // Solid downward triangle for tail indent
else
[_label setStringValue:@"⇡"]; // Fallback standard up marker
}
else
{
[_label setStringValue:@"⇡"]; // Fallback standard up marker
}
}
}
// MARK: -
// MARK: Context Menu Support
- (CPMenu)menuForEvent:(CPEvent)anEvent
{
var menu = [[CPMenu alloc] initWithTitle:@"Marker Context Menu"];
// If the marker represents a standard tab stop, allow changing its type
if ([_representedObject isKindOfClass:[CPTextTab class]])
{
var itemLeft = [menu addItemWithTitle:@"Left Tab Stop" action:@selector(changeTypeToLeft:) keyEquivalent:@""],
itemCenter = [menu addItemWithTitle:@"Center Tab Stop" action:@selector(changeTypeToCenter:) keyEquivalent:@""],
itemRight = [menu addItemWithTitle:@"Right Tab Stop" action:@selector(changeTypeToRight:) keyEquivalent:@""];
[itemLeft setTarget:self];
[itemCenter setTarget:self];
[itemRight setTarget:self];
var align = [_representedObject alignment];
if (align === CPLeftTextAlignment) [itemLeft setState:CPOnState];
else if (align === CPCenterTextAlignment) [itemCenter setState:CPOnState];
else if (align === CPRightTextAlignment) [itemRight setState:CPOnState];
[menu addItem:[CPMenuItem separatorItem]];
}
// Determine the context-specific delete title
var deleteTitle = @"Delete Tab Stop";
if ([_representedObject isKindOfClass:[CPString class]])
{
if (_representedObject === @"CPFirstLineIndent")
deleteTitle = @"Delete 1st line indentation marker";
else if (_representedObject === @"CPHeadIndent")
deleteTitle = @"Delete head indentation marker";
else if (_representedObject === @"CPTailIndent")
deleteTitle = @"Delete tail indentation marker";
}
var itemDelete = [menu addItemWithTitle:deleteTitle action:@selector(deleteMarker:) keyEquivalent:@""];
[itemDelete setTarget:self];
return menu;
}
- (void)changeTypeToLeft:(id)sender
{
[self _changeAlignment:CPLeftTextAlignment];
}
- (void)changeTypeToCenter:(id)sender
{
[self _changeAlignment:CPCenterTextAlignment];
}
- (void)changeTypeToRight:(id)sender
{
[self _changeAlignment:CPRightTextAlignment];
}
- (void)_changeAlignment:(CPTextAlignment)alignment
{
if (![_representedObject isKindOfClass:[CPTextTab class]])
return;
var oldTab = _representedObject;
var newTab = [[CPTextTab alloc] initWithType:alignment location:_imageValue];
// Using setRepresentedObject: automatically updates the marker triangle direction
[self setRepresentedObject:newTab];
var client = [_rulerView clientView];
if (client && [client respondsToSelector:@selector(rulerView:didUpdateMarker:oldTab:)])
{
[client rulerView:_rulerView didUpdateMarker:self oldTab:oldTab];
}
}
- (void)deleteMarker:(id)sender
{
var client = [_rulerView clientView];
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
{
[client rulerView:_rulerView didRemoveMarker:self];
}
[_rulerView removeMarker:self];
}
@end
// MARK: - CPRulerView (Pure DOM + Interactive Engine)
@implementation CPRulerView : CPView
{
CPScrollView _scrollView @accessors(property=scrollView);
CPRulerOrientation _orientation @accessors(property=orientation);
CPView _clientView @accessors(property=clientView);
float _ruleThickness @accessors(property=ruleThickness);
float _reservedThicknessForMarkers;
CPArray _markers;
// Dragger variables
CPRulerMarker _draggingMarker;
CGPoint _dragStartPoint;
float _dragStartLocation;
}
- (id)initWithScrollView:(CPScrollView)aScrollView orientation:(CPRulerOrientation)anOrientation
{
if (self = [super initWithFrame:CGRectMakeZero()])
{
_scrollView = aScrollView;
_orientation = anOrientation;
_clientView = [aScrollView documentView];
_ruleThickness = (anOrientation === CPHorizontalRuler) ? 16.0 : 24.0;
_reservedThicknessForMarkers = 0.0;
_markers = [];
[self setBackgroundColor:[CPColor colorWithWhite:0.96 alpha:1.0]];
}
return self;
}
- (void)setFrame:(CGRect)aFrame
{
[super setFrame:aFrame];
[self updateRuler];
}
// Markers registration
- (void)addMarker:(CPRulerMarker)aMarker
{
if ([_markers containsObject:aMarker])
return;
[_markers addObject:aMarker];
[self addSubview:aMarker];
[self _positionMarker:aMarker];
}
- (void)removeMarker:(CPRulerMarker)aMarker
{
[_markers removeObject:aMarker];
[aMarker removeFromSuperview];
}
- (void)setMarkers:(CPArray)newMarkers
{
for (var i = 0; i < [_markers count]; i++)
[[_markers objectAtIndex:i] removeFromSuperview];
_markers = [newMarkers mutableCopy];
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
[self addSubview:marker];
[self _positionMarker:marker];
}
}
- (CPRulerMarker)_markerAtPoint:(CGPoint)aPoint
{
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
if (CGRectContainsPoint([marker frame], aPoint))
return marker;
}
return nil;
}
- (void)_positionMarker:(CPRulerMarker)aMarker
{
if (!_scrollView)
return;
var clipView = [_scrollView contentView],
scrollPoint = [clipView bounds].origin,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]),
markerLocation = [aMarker imageValue];
if (isHorizontal)
{
var x = markerLocation - scrollPoint.x - 6.0, // Center the 12px wide marker
y = rulerHeight - 11.0,
w = 12.0,
h = 12.0;
// Align the First Line Indent (upper half) and Head Indent (lower half) controls
if ([aMarker representedObject] === @"CPFirstLineIndent")
{
y = 0.0;
h = Math.floor(rulerHeight / 2.0);
}
else if ([aMarker representedObject] === @"CPHeadIndent")
{
y = Math.floor(rulerHeight / 2.0);
h = rulerHeight - y - 1.0; // Subtract 1px to stay cleanly above bottom border
}
else
{
// Keep normal horizontal markers within the bounds of the ruler to prevent clipping
if (x < 0.0)
x = 0.0;
else if (x + 12.0 > rulerWidth)
x = rulerWidth - 12.0;
}
[aMarker setFrame:CGRectMake(x, y, w, h)];
}
else
{
var x = rulerWidth - 11.0,
y = markerLocation - scrollPoint.y - 6.0;
// Keep vertical marker within the bounds of the ruler to prevent clipping
if (y < 0.0)
y = 0.0;
else if (y + 12.0 > rulerHeight)
y = rulerHeight - 12.0;
[aMarker setFrame:CGRectMake(x, y, 12.0, 12.0)];
}
}
// MARK: -
// MARK: Interaction Handlers
- (void)mouseDown:(CPEvent)anEvent
{
var locationInWindow = [anEvent locationInWindow],
localPoint = [self convertPoint:locationInWindow fromView:nil],
clipView = [_scrollView contentView],
scrollPoint = [clipView bounds].origin,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
var rulerLocation = isHorizontal ? (localPoint.x + scrollPoint.x) : (localPoint.y + scrollPoint.y);
// 1. Check if clicked an existing marker
var clickedMarker = [self _markerAtPoint:localPoint];
if (clickedMarker)
{
_draggingMarker = clickedMarker;
_dragStartPoint = localPoint;
_dragStartLocation = [_draggingMarker imageValue];
}
// 2. Otherwise, create a new marker dynamically where the user clicked
else
{
var newMarker = [[CPRulerMarker alloc] initWithRulerView:self
markerLocation:rulerLocation
imageValue:rulerLocation
representedObject:nil];
[self addMarker:newMarker];
// Notify the client view (e.g., CPTextView) that a new marker was added
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didAddMarker:)])
[client rulerView:self didAddMarker:newMarker];
_draggingMarker = newMarker;
_dragStartPoint = localPoint;
_dragStartLocation = rulerLocation;
}
}
- (void)mouseDragged:(CPEvent)anEvent
{
if (!_draggingMarker)
return;
var locationInWindow = [anEvent locationInWindow],
localPoint = [self convertPoint:locationInWindow fromView:nil],
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
var delta = isHorizontal ? (localPoint.x - _dragStartPoint.x) : (localPoint.y - _dragStartPoint.y),
newLocation = _dragStartLocation + delta;
if (newLocation < 0) newLocation = 0;
[_draggingMarker setImageValue:newLocation];
// Smoothly redraw ruler and margin bounds on every drag step
[self updateRuler];
// Check if dragged off the ruler (more than 15px off the boundary)
var draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15);
if (draggedOff)
{
// Visual feedback: Dim the handle to 40% and turn the triangle icon gray
[_draggingMarker setAlphaValue:0.4];
[[_draggingMarker label] setTextColor:[CPColor grayColor]];
}
else
{
// Restore standard styling when dragged back into the active strip
[_draggingMarker setAlphaValue:1.0];
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
}
// Notify the CPTextView that the marker coordinates shifted
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didMoveMarker:)])
[client rulerView:self didMoveMarker:_draggingMarker];
}
- (void)mouseUp:(CPEvent)anEvent
{
if (!_draggingMarker)
return;
var localPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil],
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
// If dragged more than 15 pixels off the ruler, delete the marker
draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15);
if (draggedOff)
{
var client = [self clientView];
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
[client rulerView:self didRemoveMarker:_draggingMarker];
[self removeMarker:_draggingMarker];
}
else
{
// Ensure marker style is fully restored if not deleted
[_draggingMarker setAlphaValue:1.0];
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
}
_draggingMarker = nil;
[self updateRuler];
}
// MARK: -
// MARK: DOM Layout Builder
- (void)updateRuler
{
// Wipe subviews to redraw the dynamic visible tick lines/numbers
[self setSubviews:@[]];
if (!_scrollView)
return;
var clipView = [_scrollView contentView],
scrollBounds = [clipView bounds],
scrollPoint = scrollBounds.origin,
visibleSize = scrollBounds.size,
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
if (isHorizontal)
{
var start = Math.floor(scrollPoint.x / 10) * 10,
end = scrollPoint.x + visibleSize.width,
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]);
// Draw solid horizontal bottom border (pure, razor-sharp CSS DOM view)
var bottomBorder = [[CPView alloc] initWithFrame:CGRectMake(0, rulerHeight - 1, rulerWidth, 1)];
[bottomBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
[self addSubview:bottomBorder];
// Find indent markers to determine background highlight boundaries
var firstLineMarker = nil,
headMarker = nil;
for (var i = 0; i < [_markers count]; i++)
{
var m = [_markers objectAtIndex:i];
if ([m representedObject] === @"CPFirstLineIndent")
firstLineMarker = m;
else if ([m representedObject] === @"CPHeadIndent")
headMarker = m;
}
var halfHeight = Math.floor(rulerHeight / 2.0);
// Draw First Line Indent background - top half (lighter gray)
if (firstLineMarker)
{
var firstLineX = [firstLineMarker imageValue] - scrollPoint.x;
if (firstLineX > 0)
{
var firstLineBg = [[CPView alloc] initWithFrame:CGRectMake(0, 0, firstLineX, halfHeight)];
[firstLineBg setBackgroundColor:[CPColor colorWithWhite:0.93 alpha:1.0]];
[self addSubview:firstLineBg];
}
}
// Draw Head Indent background - bottom half (slightly darker gray)
if (headMarker)
{
var headX = [headMarker imageValue] - scrollPoint.x;
if (headX > 0)
{
var headBg = [[CPView alloc] initWithFrame:CGRectMake(0, halfHeight, headX, rulerHeight - halfHeight - 1.0)];
[headBg setBackgroundColor:[CPColor colorWithWhite:0.86 alpha:1.0]];
[self addSubview:headBg];
}
}
// Render ruler tick lines and labels on top of shaded areas
for (var val = start; val <= end; val += 10)
{
if (val < 0) continue;
var screenX = val - scrollPoint.x,
isMajor = (val % 50 === 0),
tickHeight = isMajor ? 8.0 : 4.0,
tickY = rulerHeight - tickHeight - 1.0;
// Tick mark CSS line view
var tick = [[CPView alloc] initWithFrame:CGRectMake(screenX, tickY, 1.0, tickHeight)];
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
[self addSubview:tick];
// Unit label
if (isMajor)
{
var labelX = screenX - 20.0,
alignment = CPCenterTextAlignment;
// Adjust label frame and alignment if it lands near left/right bounds
if (labelX < 0.0)
{
labelX = Math.max(0.0, screenX);
alignment = CPLeftTextAlignment;
}
else if (labelX + 40.0 > rulerWidth)
{
labelX = rulerWidth - 40.0;
alignment = CPRightTextAlignment;
}
var label = [[CPTextField alloc] initWithFrame:CGRectMake(labelX, 1.0, 40.0, 12.0)];
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
[label setFont:[CPFont systemFontOfSize:8.0]];
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
[label setAlignment:alignment];
[self addSubview:label];
}
}
}
else
{
// Vertical Ruler
var start = Math.floor(scrollPoint.y / 10) * 10,
end = scrollPoint.y + visibleSize.height,
rulerHeight = CGRectGetHeight([self bounds]),
rulerWidth = CGRectGetWidth([self bounds]);
// Draw solid vertical right border (pure DOM)
var rightBorder = [[CPView alloc] initWithFrame:CGRectMake(rulerWidth - 1, 0, 1, rulerHeight)];
[rightBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
[self addSubview:rightBorder];
for (var val = start; val <= end; val += 10)
{
if (val < 0) continue;
var screenY = val - scrollPoint.y,
isMajor = (val % 50 === 0),
tickWidth = isMajor ? 8.0 : 4.0,
tickX = rulerWidth - tickWidth - 1.0;
// Tick mark CSS line view
var tick = [[CPView alloc] initWithFrame:CGRectMake(tickX, screenY, tickWidth, 1.0)];
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
[self addSubview:tick];
// Unit label
if (isMajor)
{
var labelY = screenY - 6.0;
// Adjust label frame if it lands near top/bottom bounds
if (labelY < 0.0)
labelY = 0.0;
else if (labelY + 12.0 > rulerHeight)
labelY = rulerHeight - 12.0;
var label = [[CPTextField alloc] initWithFrame:CGRectMake(1.0, labelY, rulerWidth - 12.0, 12.0)];
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
[label setFont:[CPFont systemFontOfSize:8.0]];
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
[label setAlignment:CPRightTextAlignment];
[self addSubview:label];
}
}
}
// Reposition and display active markers
for (var i = 0; i < [_markers count]; i++)
{
var marker = [_markers objectAtIndex:i];
if ([marker superview] !== self)
[self addSubview:marker];
[self _positionMarker:marker];
}
}
@end
+50 -17
View File
@@ -89,8 +89,8 @@ CPLineMovesUp = 4;
}
#pragma mark -
#pragma mark Init methods
// MARK: -
// MARK: Init methods
- (id)initWithContainerSize:(CGSize)aSize
{
@@ -118,8 +118,8 @@ CPLineMovesUp = 4;
[_layoutManager addTextContainer:self];
}
#pragma mark -
#pragma mark Setter methods
// MARK: -
// MARK: Setter methods
- (void)setContainerSize:(CGSize)someSize
{
@@ -148,27 +148,58 @@ CPLineMovesUp = 4;
- (void)setWidthTracksTextView:(BOOL)flag
{
_widthTracksTextView = flag;
[_textView setPostsFrameChangedNotifications:flag];
if (_widthTracksTextView === flag)
return;
if (flag && _textView)
{
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewFrameChanged:)
name:CPViewFrameDidChangeNotification
object:_textView];
}
else
_widthTracksTextView = flag;
[self _updateFrameObserver];
}
// Controls whether the receiver adjusts the height of its bounding rectangle when its text view is resized.
- (BOOL)heightTracksTextView
{
return _heightTracksTextView;
}
- (void)setHeightTracksTextView:(BOOL)flag
{
if (_heightTracksTextView === flag)
return;
_heightTracksTextView = flag;
[self _updateFrameObserver];
}
- (void)_updateFrameObserver
{
if (_textView)
{
[[CPNotificationCenter defaultCenter] removeObserver:self
name:CPViewFrameDidChangeNotification
object:_textView];
var flag = _widthTracksTextView || _heightTracksTextView;
[_textView setPostsFrameChangedNotifications:flag];
if (flag)
{
[[CPNotificationCenter defaultCenter] addObserver:self
selector:@selector(textViewFrameChanged:)
name:CPViewFrameDidChangeNotification
object:_textView];
}
}
}
- (void)textViewFrameChanged:(CPNotification)aNotification
{
var newSize = CGSizeMake([_textView frame].size.width, _size.height);
var newSize = CGSizeMake(_size.width, _size.height);
if (_widthTracksTextView)
newSize.width = [_textView frame].size.width;
if (_heightTracksTextView)
newSize.height = [_textView frame].size.height;
[self setContainerSize:newSize];
}
@@ -177,7 +208,9 @@ CPLineMovesUp = 4;
{
if (_textView)
{
[self setWidthTracksTextView:NO]; // We only support width
[[CPNotificationCenter defaultCenter] removeObserver:self
name:CPViewFrameDidChangeNotification
object:_textView];
[_textView setTextContainer:nil];
}
@@ -185,7 +218,7 @@ CPLineMovesUp = 4;
if (_textView)
{
[self setWidthTracksTextView:_widthTracksTextView]; // We only support width
[self _updateFrameObserver];
[_textView setTextContainer:self];
}
+8 -8
View File
@@ -70,8 +70,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
#pragma mark -
#pragma mark Init methods
// MARK: -
// MARK: Init methods
- (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
{
@@ -99,8 +99,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
#pragma mark -
#pragma mark Delegate methods
// MARK: -
// MARK: Delegate methods
- (void)setDelegate:(id <CPTextStorageDelegate>)aDelegate
{
@@ -121,8 +121,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
#pragma mark -
#pragma mark Layout manager methods
// MARK: -
// MARK: Layout manager methods
- (void)addLayoutManager:(CPLayoutManager)aManager
{
@@ -148,8 +148,8 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1,
}
#pragma mark -
#pragma mark Editing methods
// MARK: -
// MARK: Editing methods
- (void)processEditing
{
File diff suppressed because it is too large Load Diff
+192 -30
View File
@@ -6,7 +6,7 @@
* All modifications copyright Daniel Boehringer 2013.
* Extensive code formatting and review by Andrew Hankinson
* Based on original work by
* Emmanuel Maillard on 27/02/2010.
* Created by Emmanuel Maillard on 27/02/2010.
* Copyright Emmanuel Maillard 2010.
*
* This library is free software; you can redistribute it and/or
@@ -30,6 +30,9 @@
@import "CPTextStorage.j"
@import "CPFont.j"
@global CPBaselineOffsetAttributeName
@global CPSuperscriptAttributeName
// forward declare these classes for type matching
@class CPLayoutManager
@class CPTextContainer
@@ -51,8 +54,8 @@ var CPSystemTypesetterFactory,
@implementation CPTypesetter : CPObject
#pragma mark -
#pragma mark Class methods
// MARK: -
// MARK: Class methods
+ (void)initialize
{
@@ -119,8 +122,8 @@ var CPSystemTypesetterFactory,
}
#pragma mark -
#pragma mark Class methods
// MARK: -
// MARK: Class methods
+ (id)sharedInstance
{
@@ -135,31 +138,34 @@ var CPSystemTypesetterFactory,
return [_layoutManager textContainers];
}
// Retrieves correct CPTextTab stop accounting for CPArray properties
- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction
{
var tabStops = [_currentParagraph tabStops];
if (!tabStops)
tabStops = [CPParagraphStyle _defaultTabStops];
tabStops = [[CPParagraphStyle defaultParagraphStyle] tabStops];
var l = tabStops.length;
var l = [tabStops count];
if (aWidth > tabStops[l - 1]._location)
if (l === 0)
return nil;
for (var i = l - 1; i >= 0; i--)
// Find the first tab stop that is strictly greater than the current width
for (var i = 0; i < l; i++)
{
if (aWidth > tabStops[i]._location)
{
if (i + 1 < l)
return tabStops[i + 1];
}
var tab = [tabStops objectAtIndex:i];
if ([tab location] > aWidth)
return tab;
}
if (i === -1)
return tabStops[0];
// If aWidth exceeds the last tab stop, dynamically calculate the next
// tab location using the default tab interval.
var defaultInterval = [_currentParagraph defaultTabInterval] || 28.0;
var nextLocation = CEIL((aWidth + 1.0) / defaultInterval) * defaultInterval;
return nil;
return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextLocation];
}
- (BOOL)_flushRange:(CPRange)lineRange
@@ -263,6 +269,14 @@ var CPSystemTypesetterFactory,
currentParagraphMaximumLineHeight,
currentParagraphLineSpacing;
// Track physical line starts to prevent overwriting lineOrigin.x in tab segments
var isStartOfPhysicalLine = YES;
// Track paragraph indents and margins
var isFirstLineOfLayout = YES,
isFirstLineOfParagraph = YES,
rightMargin = containerSizeWidth;
if (glyphIndex > 0)
lineOrigin = CGPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin);
else if ([_layoutManager extraLineFragmentTextContainer])
@@ -289,12 +303,94 @@ var CPSystemTypesetterFactory,
currentParagraphMaximumLineHeight = [_currentParagraph maximumLineHeight];
currentParagraphLineSpacing = [_currentParagraph lineSpacing];
// Recalculate right margin on paragraph style change
var tailIndent = [_currentParagraph tailIndent];
if (tailIndent > 0.0)
rightMargin = tailIndent;
else if (tailIndent < 0.0)
rightMargin = containerSizeWidth + tailIndent;
else
rightMargin = containerSizeWidth;
// If we are at the start of a physical line, we update lineOrigin.x
if (isStartOfPhysicalLine)
{
if (glyphIndex > 0)
{
var prevChar = theString.charCodeAt(glyphIndex - 1);
isFirstLineOfParagraph = (prevChar === 10 || prevChar === 13);
}
else
{
isFirstLineOfParagraph = YES;
}
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
isFirstLineOfLayout = NO;
}
// Calculate the right wrapping margin based on tail indent
var tailIndent = [_currentParagraph tailIndent];
if (tailIndent > 0.0)
rightMargin = tailIndent;
else if (tailIndent < 0.0)
rightMargin = containerSizeWidth + tailIndent;
else
rightMargin = containerSizeWidth;
// Handle the layout's very first line indentation
if (isFirstLineOfLayout)
{
if (glyphIndex > 0)
{
var prevChar = theString.charCodeAt(glyphIndex - 1);
isFirstLineOfParagraph = (prevChar === 10 || prevChar === 13);
}
else
{
isFirstLineOfParagraph = YES;
}
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
isFirstLineOfLayout = NO;
}
if (!currentFont)
currentFont = [_textStorage font] || [CPFont systemFontOfSize:12.0];
ascent = [currentFont ascender];
descent = [currentFont descender];
leading = (ascent - descent) * 0.2; // FAKE leading
// Safely retrieve and validate CPBaselineOffsetAttributeName
var baselineOffset = [_currentAttributes objectForKey:CPBaselineOffsetAttributeName];
if (baselineOffset === nil || baselineOffset === undefined || typeof baselineOffset !== "number")
baselineOffset = 0.0;
// Safely retrieve and validate CPSuperscriptAttributeName
var superscript = [_currentAttributes objectForKey:CPSuperscriptAttributeName];
if (superscript === nil || superscript === undefined || typeof superscript !== "number")
superscript = 0;
if (superscript !== 0)
{
var size = [currentFont size],
scaledSize = size * 0.65,
fontName = [currentFont familyName],
isBold = [currentFont isBold],
isItalic = [currentFont isItalic];
currentFont = [CPFont _fontWithName:fontName size:scaledSize bold:isBold italic:isItalic];
if (baselineOffset === 0.0)
{
if (superscript > 0)
baselineOffset = size * 0.35;
else
baselineOffset = -size * 0.15;
}
}
var fontAscent = [currentFont ascender] || 0.0,
fontDescent = [currentFont descender] || 0.0;
ascent = fontAscent + baselineOffset;
descent = fontDescent + baselineOffset;
leading = (fontAscent - fontDescent) * 0.2; // FAKE leading
currentFontLineHeight = ascent - descent + leading;
@@ -307,15 +403,22 @@ var CPSystemTypesetterFactory,
}
if (currentFontLineHeight > _lineHeight)
_lineHeight = currentFontLineHeight;
// Clean bounds logic to prevent NaN and layout calculation overhead
var currentAscent = (ascent === undefined || isNaN(ascent)) ? 0.0 : ascent,
currentLineHeight = (currentFontLineHeight === undefined || isNaN(currentFontLineHeight)) ? 12.0 : currentFontLineHeight;
if (ascent > _lineBase)
_lineBase = ascent;
if (currentLineHeight > _lineHeight)
_lineHeight = currentLineHeight;
if (currentAscent > _lineBase)
_lineBase = currentAscent;
lineRange.length++;
measuringRange.length++;
// We are processing characters, so we are no longer at the start of a physical line
isStartOfPhysicalLine = NO;
var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons
rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor;
@@ -356,15 +459,68 @@ var CPSystemTypesetterFactory,
isTabStop = YES;
if (nextTab)
rangeWidth = nextTab._location - lineOrigin.x;
{
// Look-ahead to measure the width of the incoming text segment for alignment
var nextSegmentWidth = 0.0,
tempIndex = glyphIndex + 1,
segmentString = "";
while (tempIndex < numberOfGlyphs)
{
var nextCharCode = theString.charCodeAt(tempIndex);
if (nextCharCode === 9 || nextCharCode === 10 || nextCharCode === 13)
break;
segmentString += theString.charAt(tempIndex);
tempIndex++;
}
if (segmentString.length > 0)
nextSegmentWidth = [segmentString sizeWithFont:currentFont inWidth:NULL].width;
var tabLocation = [nextTab location],
tabAlignment = [nextTab alignment];
// Mathematically offset the tab character's right boundary
if (tabAlignment === CPCenterTextAlignment)
{
rangeWidth = (tabLocation - nextSegmentWidth / 2.0) - lineOrigin.x;
}
else if (tabAlignment === CPRightTextAlignment)
{
rangeWidth = (tabLocation - nextSegmentWidth) - lineOrigin.x;
}
else // Left align tab stop
{
rangeWidth = tabLocation - lineOrigin.x;
}
// Enforce a minimum safety spacer width to avoid character overlapping
var minRangeWidth = prevRangeWidth + 5.0;
if (rangeWidth < minRangeWidth)
rangeWidth = minRangeWidth;
}
else
rangeWidth += 28; //FIXME
} // fallthrough intentional
{
rangeWidth += 28.0; // standard fallback spacer
}
break;
}
case 32: // ' '
wrapRange = CPMakeRangeCopy(lineRange);
wrapWidth = rangeWidth;
wrapRange._height = _lineHeight;
wrapRange._base = _lineBase;
// Optimization: Start measuring from the next character to avoid O(n^2)
// string width calculation within a line since spaces do not carry ligatures or kerning.
// Only reset the measuring range if the next character is NOT another space.
// This prevents compounded subpixel rounding errors with contiguous spaces.
if (theString.charCodeAt(glyphIndex + 1) !== 32)
{
currentAnchor = rangeWidth;
measuringRange = CPMakeRange(glyphIndex + 1, 0);
}
break;
case 10:
@@ -375,7 +531,8 @@ var CPSystemTypesetterFactory,
advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent});
prevRangeWidth = _lineWidth = rangeWidth;
if (lineOrigin.x + rangeWidth > containerSizeWidth)
// Wrap lines against the tail indent (rightMargin) instead of container boundaries
if (lineOrigin.x + rangeWidth > rightMargin)
{
if (wrapWidth)
{
@@ -419,12 +576,17 @@ var CPSystemTypesetterFactory,
containerSizeHeight = containerSize.height;
}
lineOrigin.x = 0;
// If this is a soft wrap (isWordWrapped), next line gets headIndent.
// If it was a paragraph return, it gets firstLineHeadIndent.
isFirstLineOfParagraph = !isWordWrapped;
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
numLines++;
isNewline = NO;
_lineFragments = [];
_lineHeight = 0;
_lineBase = ascent;
_lineBase = 0;
isStartOfPhysicalLine = YES;
}
isTabStop = NO;
File diff suppressed because it is too large Load Diff
+256 -74
View File
@@ -1,12 +1,12 @@
/*
_CPRTFProducer.j
Serialize CPAttributedString to a RTF String
Copyright (C) 2014 Daniel Boehringer
This file is based on the RTFProducer from GNUStep
(which i co-authored with Fred Kiefer in 1999)
* _CPRTFProducer.j
*
* Serialize CPAttributedString to a RTF String
*
* Copyright (C) 2014 Daniel Boehringer
* This file is based on the RTFProducer from GNUStep
* (which I co-authored with Fred Kiefer in 1999)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
@@ -27,6 +27,7 @@
@import "CPColor.j"
@import "CPGraphics.j"
@import "CPFontManager.j"
@import "_CPTableTextAttachment.j"
@global CPForegroundColorAttributeName
@global CPBackgroundColorAttributeName
@@ -43,6 +44,11 @@
@global CPJustifiedTextAlignment
@global CPNaturalTextAlignment
@global CPLeftTabStopType
@global CPRightTabStopType
@global CPCenterTabStopType
@global CPDecimalTabStopType
var PAPERSIZE = @"PaperSize",
LEFTMARGIN = @"LeftMargin",
RIGHTMARGIN = @"RightMargin",
@@ -64,8 +70,8 @@ function _points2twips(a) { return (a) * 20.0; }
CPColor ulColor;
}
#pragma mark -
#pragma mark Class methods
// MARK: -
// MARK: Class methods
+ (CPString)produceRTF:(CPAttributedString)aText documentAttributes:(CPDictionary)dict
@@ -76,19 +82,14 @@ function _points2twips(a) { return (a) * 20.0; }
}
#pragma mark -
#pragma mark init methods
// MARK: -
// MARK: init methods
- (id)init
{
if (self = [super init])
{
// maintain a dictionary for the used colours
// (for rtf-header generation)
colorDict = [CPMutableDictionary new];
//maintain a dictionary for the used fonts
//(for rtf-header generation)
fontDict = [CPMutableDictionary new];
fgColor = [CPColor blackColor];
@@ -98,7 +99,6 @@ function _points2twips(a) { return (a) * 20.0; }
return self;
}
// private stuff follows
- (CPString)fontTable
{
if (![fontDict count])
@@ -313,7 +313,6 @@ function _points2twips(a) { return (a) * 20.0; }
break;
}
// write first line indent and left indent
var twips = _points2twips([paraStyle firstLineHeadIndent]);
if (twips != 0.0)
@@ -351,26 +350,29 @@ function _points2twips(a) { return (a) * 20.0; }
while ((tab = [enumerator nextObject]))
{
switch ([tab tabStopType])
var tabType = [tab respondsToSelector:@selector(tabStopType)] ? [tab tabStopType] : nil;
if (tabType === nil && [tab respondsToSelector:@selector(alignment)])
tabType = [tab alignment];
switch (tabType)
{
case CPLeftTabStopType:
// no tabkind emission needed
case CPLeftTextAlignment:
break;
/* case NSRightTabStopType:
case CPRightTabStopType:
case CPRightTextAlignment:
headerString += @"\\tqr";
break;
case NSCenterTabStopType:
headerString += @"\\tqc";
break;
case NSDecimalTabStopType:
break;
case CPCenterTabStopType:
case CPCenterTextAlignment:
headerString += @"\\tqc";
break;
case CPDecimalTabStopType:
headerString += @"\\tqdec";
break;
default:
NSLog(@"Unknown tab stop type.");
*/
}
break;
}
headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])];
headerString += [CPString stringWithFormat:@"\\tx%d",_points2twips([tab location])];
}
return headerString;
@@ -380,6 +382,205 @@ function _points2twips(a) { return (a) * 20.0; }
attributes:(CPDictionary) attributes
paragraphStart:(BOOL) first
{
var unwrap = function(obj) {
if (!obj) return null;
if ((typeof obj.respondsToSelector === "function" && ([obj respondsToSelector:@selector(headers)] || [obj respondsToSelector:@selector(rows)])) ||
obj.headers || obj._headers || obj.rows || obj._rows) {
return obj;
}
var unwrapped = null;
if (typeof obj.respondsToSelector === "function") {
if ([obj respondsToSelector:@selector(attachmentCell)]) {
unwrapped = [obj attachmentCell];
} else if ([obj respondsToSelector:@selector(content)]) {
unwrapped = [obj content];
} else if ([obj respondsToSelector:@selector(view)]) {
unwrapped = [obj view];
}
}
if (!unwrapped) {
unwrapped = obj._attachmentCell || obj._content || obj._view || obj.attachmentCell || obj.content || obj.view;
}
return unwrapped ? unwrapped : obj;
};
var tableAttachment = null;
if (typeof CPAttachmentAttributeName !== "undefined") {
tableAttachment = [attributes objectForKey:CPAttachmentAttributeName];
}
if (!tableAttachment) {
tableAttachment = [attributes objectForKey:@"CPAttachmentAttributeName"];
}
if (!tableAttachment) {
tableAttachment = [attributes objectForKey:@"TableAttachmentAttribute"];
}
if (!tableAttachment) {
tableAttachment = [attributes objectForKey:@"_CPAttachmentView"];
}
if (!tableAttachment && typeof _CPAttachmentView !== "undefined") {
tableAttachment = [attributes objectForKey:_CPAttachmentView];
}
tableAttachment = unwrap(tableAttachment);
if (!tableAttachment && (substring === "\uFFFC" || substring === ""))
{
var keys = [attributes allKeys],
count = [keys count];
for (var i = 0; i < count; i++)
{
var key = [keys objectAtIndex:i],
val = unwrap([attributes objectForKey:key]);
if (val && (
(typeof val.respondsToSelector === "function" && [val respondsToSelector:@selector(headers)]) ||
val._headers ||
val.headers ||
(typeof _CPTableTextAttachment !== "undefined" && [val isKindOfClass:[_CPTableTextAttachment class]])
)) {
tableAttachment = val;
break;
}
}
}
if (tableAttachment)
{
var headers = null,
rows = null;
// Try to fetch from the active live view of the attachment first to capture user edits
var activeView = null;
if (typeof tableAttachment.respondsToSelector === "function" && [tableAttachment respondsToSelector:@selector(view)]) {
activeView = [tableAttachment view];
}
if (!activeView) {
activeView = tableAttachment._view || tableAttachment.view;
}
if (activeView) {
if (typeof activeView.respondsToSelector === "function") {
if ([activeView respondsToSelector:@selector(headers)]) {
headers = [activeView headers];
}
if ([activeView respondsToSelector:@selector(rows)]) {
rows = [activeView rows];
}
}
if (!headers) {
headers = activeView._headers || activeView.headers;
}
if (!rows) {
rows = activeView._rows || activeView.rows;
}
}
// Fall back to the attachment's parsed properties if the view is nil or lacks the properties
if (!headers || !rows) {
if (typeof tableAttachment.respondsToSelector === "function") {
if ([tableAttachment respondsToSelector:@selector(headers)]) {
headers = [tableAttachment headers];
}
if ([tableAttachment respondsToSelector:@selector(rows)]) {
rows = [tableAttachment rows];
}
}
if (!headers) {
headers = tableAttachment._headers || tableAttachment.headers;
}
if (!rows) {
rows = tableAttachment._rows || tableAttachment.rows;
}
}
var getCount = function(arr) {
if (!arr) return 0;
if (typeof arr.count === "function") return [arr count];
return arr.length;
};
var getObjectAtIndex = function(arr, idx) {
if (!arr) return null;
if (typeof arr.objectAtIndex === "function") return [arr objectAtIndex:idx];
return arr[idx];
};
var numCols = getCount(headers);
if (numCols == 0 && getCount(rows) > 0) {
numCols = getCount(getObjectAtIndex(rows, 0));
}
if (numCols > 0)
{
var totalWidthTwips = 10000;
var colWidthTwips = Math.floor(totalWidthTwips / numCols);
var cellBoundaries = [];
var currentBoundary = 0;
for (var i = 0; i < numCols; i++)
{
currentBoundary += colWidthTwips;
cellBoundaries.push(currentBoundary);
}
var tableRTF = "";
var writeRow = function(rowData, isHeaderRow) {
var rowRTF = "\\trowd\\trgaph115\\trleft0";
for (var c = 0; c < numCols; c++) {
rowRTF += "\\clbrdrt\\brdrs\\brdrw10\\clbrdrb\\brdrs\\brdrw10\\clbrdrl\\brdrs\\brdrw10\\clbrdrr\\brdrs\\brdrw10";
rowRTF += "\\cellx" + cellBoundaries[c];
}
for (var c = 0; c < numCols; c++) {
var cellText = "";
if (c < getCount(rowData)) {
cellText = getObjectAtIndex(rowData, c);
}
if (cellText === null || cellText === undefined) {
cellText = "";
}
// Safely extract text representation from CPAttributedString / CPTextStorage if present
if (cellText && typeof cellText === "object") {
if (typeof cellText.string === "function") {
cellText = [cellText string];
} else if (cellText._string !== undefined) {
cellText = cellText._string;
} else if (cellText.string !== undefined) {
cellText = cellText.string;
}
}
cellText = String(cellText);
cellText = cellText.replace(/\\/g, '\\\\');
cellText = cellText.replace(/{/g, '\\{');
cellText = cellText.replace(/}/g, '\\}');
cellText = cellText.replace(/\n/g, '\\line ');
if (isHeaderRow) {
rowRTF += "{\\intbl\\b " + cellText + "\\b0\\cell}";
} else {
rowRTF += "{\\intbl " + cellText + "\\cell}";
}
}
rowRTF += "\\row\n";
return rowRTF;
};
if (getCount(headers) > 0) {
tableRTF += writeRow(headers, YES);
}
var rowCount = getCount(rows);
for (var r = 0; r < rowCount; r++) {
tableRTF += writeRow(getObjectAtIndex(rows, r), NO);
}
return tableRTF;
}
}
var result = "",
headerString = "",
trailerString = "",
@@ -392,23 +593,12 @@ function _points2twips(a) { return (a) * 20.0; }
headerString += [self paragraphStyle:paraStyle];
}
/*
* analyze attributes of current run
*
* FIXME: All the character attributes should be output relative to the font
* attributes of the paragraph. So if the paragraph has underline on it should
* still be possible to switch it off for some characters, which currently is
* not possible.
*/
attribEnum = [attributes keyEnumerator];
while ((currAttrib = [attribEnum nextObject]) != nil)
{
if ([currAttrib isEqualToString:CPFontAttributeName])
{
/*
* handle fonts
*/
var font,
fontName,
traits;
@@ -417,15 +607,9 @@ function _points2twips(a) { return (a) * 20.0; }
fontName = [font familyName];
traits = [[CPFontManager sharedFontManager] traitsOfFont:font];
/*
* font name
*/
if (currentFont == nil || ![fontName isEqualToString:[currentFont familyName]])
headerString += [self fontToken:fontName];
/*
* font size
*/
if (currentFont == nil || [font size] != [currentFont size])
{
var points = [font size] * 2,
@@ -434,9 +618,7 @@ function _points2twips(a) { return (a) * 20.0; }
pString = [CPString stringWithFormat:@"\\fs%d", points];
headerString += pString;
}
/*
* font attributes
*/
if (traits & CPItalicFontMask)
{
headerString += @"\\i";
@@ -475,28 +657,29 @@ function _points2twips(a) { return (a) * 20.0; }
else if ([currAttrib isEqualToString:CPUnderlineStyleAttributeName])
{
headerString += @"\\ul";
trailerString += @"\\ulnone";
trailerString += @"\\ulnone "; // trailing space important!
}
else if ([currAttrib isEqualToString:CPSuperscriptAttributeName])
{
var value = [attributes objectForKey:CPSuperscriptAttributeName],
svalue = [value intValue] * 6;
ivalue = [value intValue];
if (svalue > 0)
if (ivalue > 0)
{
headerString += [CPString stringWithFormat:@"\\up%d", svalue];
trailerString += @"\\up0";
headerString += @"\\super";
trailerString += @"\\nosupersub "; // trailing space important!
}
else if (svalue < 0)
else if (ivalue < 0)
{
headerString += [CPString stringWithFormat:@"\\dn-%d", svalue];
trailerString += @"\\dn0";
headerString += @"\\sub";
trailerString += @"\\nosupersub "; // trailing space important!
}
}
else if ([currAttrib isEqualToString:CPBaselineOffsetAttributeName])
{
var value = [attributes objectForKey:CPBaselineOffsetAttributeName],
svalue = [value floatValue] * 2;
fvalue = [value floatValue],
svalue = Math.round(fvalue * 2.0); // Convert standard points to RTF half-points
if (svalue > 0)
{
@@ -505,7 +688,8 @@ function _points2twips(a) { return (a) * 20.0; }
}
else if (svalue < 0)
{
headerString += [CPString stringWithFormat:@"\\dn-%d", svalue];
// Correct negative formatting using safe positive boundary
headerString += [CPString stringWithFormat:@"\\dn%d", Math.abs(svalue)];
trailerString += @"\\dn0";
}
}
@@ -522,11 +706,9 @@ function _points2twips(a) { return (a) * 20.0; }
substring = substring.replace(/\\/g, '\\\\');
substring = substring.replace(/\n/g, '\\par\n');
substring = substring.replace(/\t/g, '\\tab');
substring = substring.replace(/\t/g, '\\tab ');
substring = substring.replace(/{/g, '\\{');
substring = substring.replace(/}/g, '\\}');
// FIXME: All characters not in the standard encoding must be
// replaced by \'xx
if (!first)
{
@@ -562,10 +744,9 @@ function _points2twips(a) { return (a) * 20.0; }
length = [string length],
currRange = CPMakeRange(loc, 0),
completeRange = CPMakeRange(0, length),
first = YES;
paragraphStart = YES;
// FIXME <!> split along newline characters and run as outer loop
while (CPMaxRange(currRange) < CPMaxRange(completeRange)) // save all "runs"
while (CPMaxRange(currRange) < CPMaxRange(completeRange))
{
var attributes,
substring,
@@ -577,10 +758,14 @@ function _points2twips(a) { return (a) * 20.0; }
substring = [string substringWithRange:currRange];
runString = [self runStringForString:substring
attributes:attributes
paragraphStart:YES];
paragraphStart:paragraphStart];
result += runString;
first = NO;
if (substring.length > 0 && substring.charAt(substring.length - 1) === '\n')
paragraphStart = YES;
else
paragraphStart = NO;
}
return result;
@@ -598,9 +783,6 @@ function _points2twips(a) { return (a) * 20.0; }
text = aText;
docDict = dict;
/*
* do not change order! (esp. body has to be generated first; builds context)
*/
bodyString = [self bodyString];
trailerString = [self trailerString];
headerString = [self headerString];
+557
View File
@@ -0,0 +1,557 @@
/* _CPTableTextAttachment.j
* A self-contained, renderable text attachment representation of a table.
*
* Copyright (C) 2026 Daniel Boehringer
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import "CPView.j"
@import "CPTextView.j"
@import "CPTextField.j"
@import <Foundation/CPAttributedString.j>
@implementation _CPTableTextAttachment : CPView
{
CPArray _headers;
CPArray _rows;
BOOL _isResizing;
BOOL _isEditable;
BOOL _acceptsRichText;
}
- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows
{
return [self initWithHeaders:headers rows:rows width:500.0 isEditable:YES acceptsRichText:YES];
}
- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows width:(float)totalWidth
{
return [self initWithHeaders:headers rows:rows width:totalWidth isEditable:YES acceptsRichText:YES];
}
- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows width:(float)totalWidth isEditable:(BOOL)isEditable acceptsRichText:(BOOL)acceptsRichText
{
self = [super initWithFrame:CGRectMake(0, 0, totalWidth, 20)];
if (self)
{
_headers = headers;
_rows = rows;
_isEditable = isEditable;
_acceptsRichText = acceptsRichText;
_isResizing = NO;
[self _rebuildTableWithWidth:totalWidth];
}
return self;
}
- (void)_rebuildTableWithWidth:(float)totalWidth
{
var numCols = _headers ? [_headers count] : 0;
if (numCols == 0 && _rows && [_rows count] > 0)
numCols = [[_rows objectAtIndex:0] count];
// Apply borders on the outer left and top container edges
if (self._DOMElement) {
self._DOMElement.style.borderTop = "1px solid #e0e0e0";
self._DOMElement.style.borderLeft = "1px solid #e0e0e0";
self._DOMElement.style.boxSizing = "border-box";
}
// Initialize header cells
if (_headers && [_headers count] > 0) {
for (var c = 0; c < numCols; c++) {
var headerText = [_headers objectAtIndex:c];
var cellView = [self createCellWithText:headerText frame:CGRectMakeZero() isHeader:YES];
[self addSubview:cellView];
}
}
// Initialize body rows
if (_rows) {
for (var r = 0; r < [_rows count]; r++) {
var rowData = [_rows objectAtIndex:r];
for (var c = 0; c < numCols; c++) {
var cellText = @"";
if (c < [rowData count])
cellText = [rowData objectAtIndex:c];
var cellView = [self createCellWithText:cellText frame:CGRectMakeZero() isHeader:NO];
[self addSubview:cellView];
}
}
}
[self resizeToWidth:totalWidth];
// Trigger parent layout engine re-calculation
var textView = [self superview];
if (textView && [textView isKindOfClass:[CPTextView class]])
{
var layoutManager = [textView layoutManager];
if (layoutManager)
{
var charRange = [self _findCharacterRangeInLayoutManager:layoutManager];
if (charRange && charRange.location !== CPNotFound)
{
[layoutManager invalidateLayoutForCharacterRange:charRange isSoft:NO actualCharacterRange:nil];
[layoutManager invalidateDisplayForGlyphRange:charRange];
[layoutManager _validateLayoutAndGlyphs];
[textView sizeToFit];
}
}
}
}
- (CPRange)_findCharacterRangeInLayoutManager:(CPLayoutManager)layoutManager
{
var lineFragments = layoutManager._lineFragments;
if (lineFragments)
{
var l = lineFragments.length;
for (var i = 0; i < l; i++)
{
var fragment = lineFragments[i];
var runs = fragment._runs;
if (runs)
{
var rc = runs.length;
for (var j = 0; j < rc; j++)
{
if (runs[j].view === self)
{
return runs[j]._range;
}
}
}
}
}
return CPMakeRange(CPNotFound, 0);
}
- (CPArray)headers
{
var numCols = _headers ? [_headers count] : 0;
if (numCols == 0 && _rows && [_rows count] > 0)
numCols = [[_rows objectAtIndex:0] count];
if (numCols == 0)
return _headers;
var subviews = [self subviews];
if ([subviews count] < numCols)
return _headers; // Subviews are not yet rendered, return cached fallback
var currentHeaders = [CPMutableArray array];
for (var c = 0; c < numCols; c++)
{
var cellView = [subviews objectAtIndex:c];
var textView = [self getTextViewFromCell:cellView];
var text = @"";
if (textView)
{
text = _acceptsRichText ? [[textView textStorage] copy] : [textView string];
}
[currentHeaders addObject:text];
}
_headers = currentHeaders;
return _headers;
}
- (CPArray)rows
{
var numCols = _headers ? [_headers count] : 0;
if (numCols == 0 && _rows && [_rows count] > 0)
numCols = [[_rows objectAtIndex:0] count];
if (numCols == 0 || !_rows)
return _rows;
var subviews = [self subviews];
var headerOffset = (_headers && [_headers count] > 0) ? numCols : 0;
var expectedCount = headerOffset + ([_rows count] * numCols);
if ([subviews count] < expectedCount)
return _rows; // Subviews are not yet rendered, return cached fallback
var currentRows = [CPMutableArray array];
var cellIndex = headerOffset;
for (var r = 0; r < [_rows count]; r++)
{
var rowData = [CPMutableArray array];
for (var c = 0; c < numCols; c++)
{
var cellView = [subviews objectAtIndex:cellIndex++];
var textView = [self getTextViewFromCell:cellView];
var text = @"";
if (textView)
{
text = _acceptsRichText ? [[textView textStorage] copy] : [textView string];
}
[rowData addObject:text];
}
[currentRows addObject:rowData];
}
_rows = currentRows;
return _rows;
}
- (CPView)viewForWidth:(float)width
{
[self resizeToWidth:width];
return self;
}
- (CPView)createCellWithText:(id)text frame:(CGRect)frame isHeader:(BOOL)isHeader
{
var initialWidth = (frame.size.width > 0) ? frame.size.width : 120.0;
var initialHeight = (frame.size.height > 0) ? frame.size.height : 28.0;
var cellContainer = [[CPView alloc] initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, initialWidth, initialHeight)];
[cellContainer setBackgroundColor:isHeader ? [CPColor colorWithWhite:0.92 alpha:1.0] : [CPColor whiteColor]];
// Bottom and right edge borders to construct the grid
var borderView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, initialWidth, initialHeight)];
[borderView setBackgroundColor:[CPColor clearColor]];
[borderView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
if (borderView._DOMElement)
{
borderView._DOMElement.style.borderBottom = "1px solid #e0e0e0";
borderView._DOMElement.style.borderRight = "1px solid #e0e0e0";
borderView._DOMElement.style.boxSizing = "border-box";
}
[cellContainer addSubview:borderView];
var textContainer = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(initialWidth - 8, 1e7)];
var textView = [[CPTextView alloc] initWithFrame:CGRectMake(4, 2, initialWidth - 8, initialHeight - 4) textContainer:textContainer];
// Set explicit zero margins inside the text view to keep height measurements aligned with usedRect
[textView setTextContainerInset:CGSizeMake(0, 0)];
[textView setEditable:_isEditable];
[textView setSelectable:YES];
[textView setBackgroundColor:[CPColor clearColor]];
[textView setVerticallyResizable:YES];
[textView setHorizontallyResizable:NO];
[[textView textContainer] setWidthTracksTextView:YES];
// Configure cell rich text mode
[textView setRichText:_acceptsRichText];
// Intercept changes within cell TextViews to notify the table
[textView setDelegate:self];
// Configure cell text style using standard CPTextView APIs
var cellFont = isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0];
[textView setFont:cellFont];
[textView setTextColor:[CPColor blackColor]];
if (text)
[textView insertText:text];
[cellContainer addSubview:textView];
return cellContainer;
}
- (void)textDidChange:(CPNotification)aNotification
{
// Live update cell layouts and heights when changes are typed
[self resizeToWidth:CGRectGetWidth([self frame])];
}
- (CPTextView)getTextViewFromCell:(CPView)cellView
{
var subviews = [cellView subviews];
for (var i = 0; i < [subviews count]; i++) {
var sub = [subviews objectAtIndex:i];
if ([sub isKindOfClass:[CPTextView class]]) {
return sub;
}
}
return nil;
}
- (void)resizeToWidth:(float)newWidth
{
if (_isResizing)
return;
_isResizing = YES;
// Use dynamic getters to fetch live edited strings
var currentHeaders = [self headers];
var currentRows = [self rows];
var numCols = currentHeaders ? [currentHeaders count] : 0;
if (numCols == 0 && currentRows && [currentRows count] > 0) {
numCols = [[currentRows objectAtIndex:0] count];
}
if (numCols == 0) {
_isResizing = NO;
return;
}
var subviews = [self subviews];
var colNaturalWidths = [];
var colMinWidths = [];
for (var c = 0; c < numCols; c++) {
colNaturalWidths[c] = 80.0;
colMinWidths[c] = 60.0;
}
var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)];
[measureTextField setFont:[CPFont systemFontOfSize:13.0]];
var measureCell = function(cellText, isHeader, colIndex) {
var cellFont = isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0];
[measureTextField setFont:cellFont];
var plainText = (cellText && typeof cellText.string === "function") ? [cellText string] : (cellText || @"");
plainText = String(plainText);
if (cellText && typeof cellText.string === "function") {
if ([measureTextField respondsToSelector:@selector(setAttributedStringValue:)]) {
[measureTextField setAttributedStringValue:cellText];
} else {
[measureTextField setStringValue:plainText];
}
} else {
[measureTextField setStringValue:plainText];
}
[measureTextField sizeToFit];
var naturalW = CGRectGetWidth([measureTextField frame]) + 24.0;
if (naturalW > colNaturalWidths[colIndex]) {
colNaturalWidths[colIndex] = naturalW;
}
var words = plainText.split(/[\s\-]/);
var maxWordW = 50.0;
for (var w = 0; w < words.length; w++) {
var word = words[w].trim();
if (word.length === 0) continue;
[measureTextField setStringValue:word];
[measureTextField sizeToFit];
var wordW = CGRectGetWidth([measureTextField frame]) + 30.0;
if (wordW > maxWordW) {
maxWordW = wordW;
}
}
if (maxWordW > colMinWidths[colIndex]) {
colMinWidths[colIndex] = maxWordW;
}
};
if (currentHeaders) {
for (var c = 0; c < [currentHeaders count]; c++) {
measureCell([currentHeaders objectAtIndex:c], YES, c);
}
}
if (currentRows) {
for (var r = 0; r < [currentRows count]; r++) {
var rowData = [currentRows objectAtIndex:r];
for (var c = 0; c < numCols; c++) {
var cellText = @"";
if (c < [rowData count]) {
cellText = [rowData objectAtIndex:c];
}
measureCell(cellText, NO, c);
}
}
}
var totalMinWidth = 0.0;
for (var c = 0; c < numCols; c++) {
totalMinWidth += colMinWidths[c];
}
var colWidths = [];
if (newWidth <= totalMinWidth) {
var remainingWidth = newWidth;
for (var c = 0; c < numCols; c++) {
var w = Math.floor((colMinWidths[c] / totalMinWidth) * newWidth);
colWidths[c] = w;
remainingWidth -= w;
}
if (numCols > 0) colWidths[numCols - 1] += remainingWidth;
} else {
for (var c = 0; c < numCols; c++) {
colWidths[c] = colMinWidths[c];
}
var totalGrowthCapacity = 0.0;
var growthCapacities = [];
for (var c = 0; c < numCols; c++) {
var capacity = Math.max(0.0, colNaturalWidths[c] - colMinWidths[c]);
growthCapacities[c] = capacity;
totalGrowthCapacity += capacity;
}
var extraWidth = newWidth - totalMinWidth;
var remainingExtra = extraWidth;
for (var c = 0; c < numCols; c++) {
if (totalGrowthCapacity > 0) {
var w = Math.floor((growthCapacities[c] / totalGrowthCapacity) * extraWidth);
colWidths[c] += w;
remainingExtra -= w;
}
}
if (numCols > 0) {
colWidths[numCols - 1] += remainingExtra;
}
}
var cellIndex = 0;
var currentY = 0;
var layoutRow = function(startIndex) {
var maxCellHeight = 28.0;
for (var c = 0; c < numCols; c++) {
var idx = startIndex + c;
if (idx < [subviews count]) {
var cellView = [subviews objectAtIndex:idx];
var textView = [self getTextViewFromCell:cellView];
if (textView)
{
var targetWidth = Math.max(10.0, colWidths[c] - 8);
// Update frame size directly so that textContainer auto-resizes.
// Set a large temporary height to allow accurate wrapping measurements.
[textView setFrameSize:CGSizeMake(targetWidth, 1e7)];
var layoutManager = [textView layoutManager];
if (layoutManager)
{
// FORCE LAYOUT RECALCULATION:
// Because the width changed, we must force the lazy layout manager
// to synchronously calculate glyphs and wraps at the new width.
[layoutManager glyphRangeForTextContainer:[textView textContainer]];
}
var usedRect = layoutManager ? [layoutManager usedRectForTextContainer:[textView textContainer]] : nil;
var textHeight = usedRect ? CGRectGetHeight(usedRect) : 0.0;
// Exact height + 8.0px padding (4.0px top, 4.0px bottom margin) inside the cell
var wrappedHeight = textHeight + 8.0;
if (wrappedHeight > maxCellHeight)
maxCellHeight = wrappedHeight;
}
}
}
var currentX = 0;
for (var c = 0; c < numCols; c++)
{
var idx = startIndex + c;
if (idx < [subviews count])
{
var cellView = [subviews objectAtIndex:idx];
[cellView setFrame:CGRectMake(currentX, currentY, colWidths[c], maxCellHeight)];
var textView = [self getTextViewFromCell:cellView];
if (textView)
{
var targetWidth = Math.max(10.0, colWidths[c] - 8);
var textY = 4.0;
var finalTextViewHeight = maxCellHeight - 8.0;
[textView setFrame:CGRectMake(4, textY, targetWidth, finalTextViewHeight)];
}
var cellSubviews = [cellView subviews];
if ([cellSubviews count] > 0)
[[cellSubviews objectAtIndex:0] setFrame:CGRectMake(0, 0, colWidths[c], maxCellHeight)];
}
currentX += colWidths[c];
}
return maxCellHeight;
};
if (currentHeaders && [currentHeaders count] > 0)
{
var headerHeight = layoutRow(cellIndex);
cellIndex += numCols;
currentY += headerHeight;
}
if (currentRows)
{
for (var r = 0; r < [currentRows count]; r++)
{
var rowHeight = layoutRow(cellIndex);
cellIndex += numCols;
currentY += rowHeight;
}
}
// GUARD FRAME SIZE MUTATIONS
var currentSize = [self frame].size;
if (ABS(currentSize.width - newWidth) > 0.1 || ABS(currentSize.height - currentY) > 0.1)
{
[self setFrameSize:CGSizeMake(newWidth, currentY)];
}
// we need to re-layout the textview here.
// but this is not easy as the layout engine is not re-entrant
// this does not work: (delay does not matter), layout is always off
// setTimeout(function() {
// var textView = [self superview];
//
// if (textView && [textView isKindOfClass:[CPTextView class]])
// {
// var layoutManager = [textView layoutManager];
//
// if (layoutManager)
// {
// var charRange = [self _findCharacterRangeInLayoutManager:layoutManager];
//
// if (charRange && charRange.location !== CPNotFound)
// {
// [layoutManager invalidateLayoutForCharacterRange:charRange isSoft:NO actualCharacterRange:nil];
// [layoutManager invalidateDisplayForGlyphRange:charRange];
// [layoutManager _validateLayoutAndGlyphs];
// [textView sizeToFit];
// }
// }
// }
// }, 0);
_isResizing = NO;
}
@end
+6 -4
View File
@@ -319,8 +319,8 @@ var CPThemeNameKey = @"CPThemeNameKey",
@end
#pragma mark -
#pragma mark CSS Theming
// MARK: -
// MARK: CSS Theming
// The code below adds support for CSS theming with 100% compatibility with current theming system.
// The idea is to extend CPColor and CPImage with CSS components and adapt low level UI components to
@@ -556,7 +556,7 @@ var _savedThemesByName = { };
@end
#pragma mark -
// MARK: -
/*!
* ThemeStates are immutable objects representing a particular ThemeState. Applications should never be creating
@@ -814,10 +814,12 @@ CPThemeStateKeyWindow = CPThemeState("keyWindow");
CPThemeStateControlSizeRegular = CPThemeState("controlSizeRegular");
CPThemeStateControlSizeSmall = CPThemeState("controlSizeSmall");
CPThemeStateControlSizeMini = CPThemeState("controlSizeMini");
CPThemeStateControlSizeLarge = CPThemeState("controlSizeLarge");
CPThemeStateAlternateState = CPThemeState("alternate");
CPThemeStateComposedControl = CPThemeState("composed");
CPThemeStateWindowsPlatform = CPThemeState("windowsPlatform");
CPThemeStateNormalString = String(CPThemeStateNormal);
CPThemeStateHUD = CPThemeState("hud");
@implementation _CPThemeAttribute : CPObject
+31 -7
View File
@@ -169,8 +169,8 @@ CPTokenFieldDeleteButtonType = 1;
[self addSubview:_tokenScrollView];
}
#pragma mark -
#pragma mark Delegate methods
// MARK: -
// MARK: Delegate methods
/*!
Set the delegate of the receiver
@@ -349,6 +349,12 @@ CPTokenFieldDeleteButtonType = 1;
if (theBinding)
[theBinding reverseSetValueFor:@"objectValue"];
if (!_isEditing)
{
_isEditing = YES;
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
}
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
_shouldNotifyTarget = YES;
@@ -453,8 +459,8 @@ CPTokenFieldDeleteButtonType = 1;
{
[_tokenScrollView documentView]._DOMElement.appendChild(element);
//post CPControlTextDidBeginEditingNotification
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
// Removed so CPTokenField doesn't fire the notification the moment it becomes the first responder, but instead defers to the first keystroke, just like Cocoa (see keyDown: in CPTextField).
// [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
[[CPRunLoop mainRunLoop] performBlock:function()
{
@@ -498,6 +504,8 @@ CPTokenFieldDeleteButtonType = 1;
[self _resignFirstKeyResponder];
_isEditing = NO;
if (_shouldNotifyTarget)
{
_shouldNotifyTarget = NO;
@@ -551,7 +559,7 @@ CPTokenFieldDeleteButtonType = 1;
CPTokenFieldCachedDragFunction = nil;
document.body.ondrag = CPTokenFieldCachedDragFunction;
document.body.onselectstart = CPTokenFieldCachedSelectStartFunction
document.body.onselectstart = CPTokenFieldCachedSelectStartFunction;
}
#endif
@@ -585,6 +593,12 @@ CPTokenFieldDeleteButtonType = 1;
// Snap to the token if it's only half visible due to mouse wheel scrolling.
_shouldScrollTo = aToken;
}
// this is a hack to compensate for a recent adoption in FR management as introduced by commit #26aab29
// this PR makes the tokenfield loose FR status prematurely, so we have to regain here in order to make deleteForward: and friends work
setTimeout(function(){
[[self window] makeFirstResponder:self];
}, 50);
}
// ===========
@@ -965,8 +979,8 @@ CPTokenFieldDeleteButtonType = 1;
[self _selectToken:tokenView byExtendingSelection:NO];
}
}
else
[self _removeSelectedTokens:nil];
// we have to remove unconditionally because the backspace is not propagated anymore starting from commit #26aab29
[self _removeSelectedTokens:nil];
}
else
{
@@ -1026,6 +1040,16 @@ CPTokenFieldDeleteButtonType = 1;
CPTokenFieldTextDidChangeValue = [self stringValue];
#endif
// Has to be enabled, and it also has to be editable or selectable.
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
return;
if ([self isEditable] && !_isEditing)
{
_isEditing = YES;
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
}
// Leave the default _propagateCurrentDOMEvent setting in place. This might be YES or NO depending
// on if something that could be a browser shortcut was pressed or not, such as Cmd-R to reload.
// If it was NO we want to leave it at NO however and only enable it in insertText:. This is what
+31
View File
@@ -1014,12 +1014,29 @@ var LABEL_MARGIN = 2.0;
CPImageView _imageView;
CPView _view;
CPView _highlightView;
CPTextField _labelField;
BOOL _FIXME_isHUD;
}
- (BOOL)acceptsFirstResponder
{
if (_view && [_view acceptsFirstResponder])
return YES;
return NO;
}
- (BOOL)becomeFirstResponder
{
if (_view && [_view acceptsFirstResponder])
return [[self window] makeFirstResponder:_view];
return [super becomeFirstResponder];
}
- (id)initWithToolbarItem:(CPToolbarItem)aToolbarItem toolbar:(CPToolbar)aToolbar
{
self = [super init];
@@ -1243,6 +1260,18 @@ var LABEL_MARGIN = 2.0;
if (alternateImage)
[_imageView setImage:alternateImage];
else
{
if (!_highlightView)
{
_highlightView = [[CPView alloc] initWithFrame:[_imageView bounds]];
[_highlightView setBackgroundColor:[CPColor blackColor]];
[_highlightView setAlphaValue:0.3];
[_highlightView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
}
[_imageView addSubview:_highlightView];
}
[_labelField setTextShadowOffset:CGSizeMakeZero()];
}
@@ -1253,6 +1282,8 @@ var LABEL_MARGIN = 2.0;
if (image)
[_imageView setImage:image];
[_highlightView removeFromSuperview];
[_labelField setTextShadowOffset:CGSizeMake(0.0, 1.0)];
}
+6 -6
View File
@@ -69,8 +69,8 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
}
#pragma mark -
#pragma mark Initialization
// MARK: -
// MARK: Initialization
/*!
Initializes and returns an object defining a region of a view to receive mouse-tracking events, mouse-moved events, cursor-update events, or possibly
@@ -121,8 +121,8 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
}
#pragma mark -
#pragma mark Implementation
// MARK: -
// MARK: Implementation
- (void)_updateWindowRect
{
@@ -140,8 +140,8 @@ CPTrackingOwnerImplementsCursorUpdate = 1 << 4;
@end
#pragma mark -
#pragma mark CPCoding
// MARK: -
// MARK: CPCoding
@implementation CPTrackingArea (CPCoding)
+697
View File
@@ -0,0 +1,697 @@
/*
* CPTreeController.j
* AppKit
*
* Daniel Boehringer Mar/2026
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPArray.j>
@import <Foundation/CPIndexPath.j>
@import "CPObjectController.j"
@import "CPKeyValueBinding.j"
@import "CPTreeNode.j"
@implementation CPTreeController : CPObjectController
{
BOOL _avoidsEmptySelection;
BOOL _preservesSelection;
BOOL _selectsInsertedObjects;
BOOL _alwaysUsesMultipleValuesMarker;
CPString _childrenKeyPath;
CPString _countKeyPath;
CPString _leafKeyPath;
CPArray _sortDescriptors;
id _arrangedObjects;
CPArray _selectionIndexPaths;
BOOL _disableSetContent;
}
+ (void)initialize
{
if (self !== [CPTreeController class])
return;
[self exposeBinding:@"contentArray"];
[self exposeBinding:@"sortDescriptors"];
}
+ (CPSet)keyPathsForValuesAffectingContentArray
{
return [CPSet setWithObjects:@"content"];
}
+ (CPSet)keyPathsForValuesAffectingArrangedObjects
{
return [CPSet setWithObjects:@"content", @"sortDescriptors", @"childrenKeyPath"];
}
+ (CPSet)keyPathsForValuesAffectingSelectionIndexPath
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingSelectedObjects
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingSelectedNodes
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanAddChild
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsert
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
+ (CPSet)keyPathsForValuesAffectingCanInsertChild
{
return [CPSet setWithObjects:@"selectionIndexPaths"];
}
- (id)init
{
if (self = [super init])
{
_preservesSelection = YES;
_selectsInsertedObjects = YES;
_avoidsEmptySelection = YES;
_alwaysUsesMultipleValuesMarker = NO;
_childrenKeyPath = @"children";
[self _init];
}
return self;
}
- (void)_init
{
_sortDescriptors = [CPArray array];
_selectionIndexPaths = [CPArray array];
_arrangedObjects = [[CPTreeNode alloc] initWithRepresentedObject:nil];
}
- (void)prepareContent
{[self _setContentArray:[CPArray arrayWithObject:[self newObject]]];
}
- (BOOL)preservesSelection { return _preservesSelection; }
- (void)setPreservesSelection:(BOOL)value { _preservesSelection = value; }
- (BOOL)selectsInsertedObjects { return _selectsInsertedObjects; }
- (void)setSelectsInsertedObjects:(BOOL)value { _selectsInsertedObjects = value; }
- (BOOL)avoidsEmptySelection { return _avoidsEmptySelection; }
- (void)setAvoidsEmptySelection:(BOOL)value { _avoidsEmptySelection = value; }
- (BOOL)alwaysUsesMultipleValuesMarker { return _alwaysUsesMultipleValuesMarker; }
- (void)setAlwaysUsesMultipleValuesMarker:(BOOL)aFlag { _alwaysUsesMultipleValuesMarker = aFlag; }
- (CPArray)sortDescriptors { return _sortDescriptors; }
- (void)setSortDescriptors:(CPArray)value
{
if (_sortDescriptors === value)
return;
_sortDescriptors = [value copy];
[self _rearrangeObjects];
}
- (CPString)childrenKeyPath { return _childrenKeyPath; }
- (void)setChildrenKeyPath:(CPString)aKeyPath
{
if (_childrenKeyPath === aKeyPath) return;
_childrenKeyPath = aKeyPath;[self rearrangeObjects];
}
- (CPString)countKeyPath { return _countKeyPath; }
- (void)setCountKeyPath:(CPString)aKeyPath { _countKeyPath = aKeyPath; }
- (CPString)leafKeyPath { return _leafKeyPath; }
- (void)setLeafKeyPath:(CPString)aKeyPath { _leafKeyPath = aKeyPath; }
- (CPString)childrenKeyPathForNode:(CPTreeNode)node { return [self childrenKeyPath]; }
- (CPString)countKeyPathForNode:(CPTreeNode)node { return [self countKeyPath]; }
- (CPString)leafKeyPathForNode:(CPTreeNode)node { return [self leafKeyPath]; }
- (void)setContent:(id)value
{
if (_disableSetContent) return;
if (!value)
value = [CPArray array];
if (![value isKindOfClass:[CPArray class]])
value = [CPArray arrayWithObject:value];
var oldSelectedObjects = nil,
oldSelectionIndexPaths = nil;
if ([self preservesSelection])
oldSelectedObjects = [self selectedObjects];
else
oldSelectionIndexPaths = [self selectionIndexPaths];
_contentObject = value;
[self _rearrangeObjects];
if ([self preservesSelection])
[self __setSelectedObjects:oldSelectedObjects];
else
[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
}
- (void)_setContentArray:(id)anArray {[self setContent:anArray]; }
- (id)contentArray { return _contentObject; }
- (id)arrangedObjects { return _arrangedObjects; }
- (void)rearrangeObjects
{
[self willChangeValueForKey:@"arrangedObjects"];
[self _rearrangeObjects];
[self didChangeValueForKey:@"arrangedObjects"];
}
- (void)_rearrangeObjects
{
var oldSelectedObjects = nil,
oldSelectionIndexPaths = nil;
if ([self preservesSelection])
oldSelectedObjects = [self selectedObjects];
else
oldSelectionIndexPaths = [self selectionIndexPaths];
[self __rebuildArrangedObjectsTree];
if ([self preservesSelection])
[self __setSelectedObjects:oldSelectedObjects];
else
[self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection];
}
- (void)__rebuildArrangedObjectsTree
{
var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil],
contentArray = [self contentArray];
if (contentArray && [contentArray count] > 0)
{
var children = [self _buildTreeNodesForObjects:contentArray];
[[rootNode mutableChildNodes] addObjectsFromArray:children];
}
_arrangedObjects = rootNode;
}
- (CPArray)_buildTreeNodesForObjects:(CPArray)objects
{
var count = [objects count];
if (count === 0)
return [];
var sortedObjects = objects;
if (_sortDescriptors && [_sortDescriptors count] > 0)
sortedObjects = [objects sortedArrayUsingDescriptors:_sortDescriptors];
var nodes = [CPMutableArray arrayWithCapacity:count];
for (var i = 0; i < count; i++)
{
var obj = [sortedObjects objectAtIndex:i],
node = [[CPTreeNode alloc] initWithRepresentedObject:obj];
if (_childrenKeyPath)
{
var childObjects = [obj valueForKeyPath:_childrenKeyPath];
if (childObjects && [childObjects count] > 0)
{
var childNodes = [self _buildTreeNodesForObjects:childObjects];
[[node mutableChildNodes] addObjectsFromArray:childNodes];
}
}
[nodes addObject:node];
}
return nodes;
}
- (CPIndexPath)selectionIndexPath
{
return [_selectionIndexPaths count] > 0 ? [_selectionIndexPaths objectAtIndex:0] : nil;
}
- (BOOL)setSelectionIndexPath:(CPIndexPath)indexPath
{
var paths = indexPath ? [CPArray arrayWithObject:indexPath] : [CPArray array];
return [self setSelectionIndexPaths:paths];
}
- (CPArray)selectionIndexPaths { return _selectionIndexPaths; }
- (BOOL)setSelectionIndexPaths:(CPArray)indexPaths
{
[self _selectionWillChange];
var result = [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO];
[self _selectionDidChange];
return result;
}
- (void)_ensureTreeNodesExistForIndexPaths:(CPArray)indexPaths
{
if (!_childrenKeyPath)
return;
var count = [indexPaths count];
for (var i = 0; i < count; i++)
{
var path = [indexPaths objectAtIndex:i];
var length = [path length];
var currentNode = [self arrangedObjects];
for (var j = 0; j < length; j++)
{
var index = [path indexAtPosition:j];
var obj = [currentNode representedObject];
var expectedChildObjects = nil;
if (!obj && currentNode === [self arrangedObjects])
expectedChildObjects = [self contentArray];
else if (obj)
expectedChildObjects = [obj valueForKeyPath:_childrenKeyPath];
if (expectedChildObjects)
{
var childNodes = [currentNode mutableChildNodes];
var needsRebuild = NO;
if (!childNodes || [childNodes count] !== [expectedChildObjects count])
{
needsRebuild = YES;
}
else
{
for (var k = 0, kCount = [childNodes count]; k < kCount; k++)
{
if ([[childNodes objectAtIndex:k] representedObject] !== [expectedChildObjects objectAtIndex:k])
{
needsRebuild = YES;
break;
}
}
}
if (needsRebuild)
{
[childNodes removeAllObjects];
var newNodes = [self _buildTreeNodesForObjects:expectedChildObjects];[childNodes addObjectsFromArray:newNodes];
}
}
var children = [currentNode childNodes];
if (children && index < [children count])
currentNode = [children objectAtIndex:index];
else
break;
}
}
}
- (BOOL)__setSelectionIndexPaths:(CPArray)indexPaths avoidEmpty:(BOOL)avoidEmpty
{
var newPaths = indexPaths;
if (!newPaths)
newPaths = [CPArray array];
if ([newPaths count] > 0)
[self _ensureTreeNodesExistForIndexPaths:newPaths];
if (![newPaths count] && avoidEmpty)
{
if ([[[self arrangedObjects] childNodes] count] > 0)
newPaths = [CPArray arrayWithObject:[CPIndexPath indexPathWithIndex:0]];
}
if ([_selectionIndexPaths isEqualToArray:newPaths])
return NO;
[self willChangeValueForKey:@"selectionIndexPaths"];
_selectionIndexPaths = [newPaths copy];
var binderClass = [[self class] _binderClassForBinding:@"selectionIndexPaths"];
if (binderClass)
{
var binding = [binderClass getBinding:@"selectionIndexPaths" forObject:self];
if (binding)
[binding reverseSetValueFor:@"selectionIndexPaths"];
}
[self didChangeValueForKey:@"selectionIndexPaths"];
return YES;
}
- (BOOL)addSelectionIndexPaths:(CPArray)indexPaths
{
var newPaths = [_selectionIndexPaths mutableCopy];
[newPaths addObjectsFromArray:indexPaths];
return [self setSelectionIndexPaths:newPaths];
}
- (BOOL)removeSelectionIndexPaths:(CPArray)indexPaths
{
var newPaths = [_selectionIndexPaths mutableCopy];
[newPaths removeObjectsInArray:indexPaths];
return [self setSelectionIndexPaths:newPaths];
}
- (CPArray)selectedNodes
{
var nodes = [CPMutableArray array],
count = [_selectionIndexPaths count];
for (var i = 0; i < count; i++)
{
var node = [[self arrangedObjects] descendantNodeAtIndexPath:[_selectionIndexPaths objectAtIndex:i]];
if (node)
[nodes addObject:node];
}
return nodes;
}
- (CPArray)selectedObjects
{
var objects = [CPMutableArray array],
nodes = [self selectedNodes],
count = [nodes count];
for (var i = 0; i < count; i++)
[objects addObject:[[nodes objectAtIndex:i] representedObject]];
return objects;
}
- (BOOL)__setSelectedObjects:(CPArray)objects
{
if (!objects || [objects count] === 0)
return [self __setSelectionIndexPaths:[CPArray array] avoidEmpty:_avoidsEmptySelection];
var newPaths = [CPMutableArray array];
for (var i = 0, count = [objects count]; i < count; i++)
{
var path = [self _indexPathForObject:[objects objectAtIndex:i] inNode:[self arrangedObjects]];
if (path)
[newPaths addObject:path];
}
return [self __setSelectionIndexPaths:newPaths avoidEmpty:_avoidsEmptySelection];
}
- (CPIndexPath)_indexPathForObject:(id)anObject inNode:(CPTreeNode)node
{
if ([node representedObject] === anObject && [node parentNode] != nil)
return [node indexPath];
var children = [node childNodes];
if (children)
{
for (var i = 0, count = [children count]; i < count; i++)
{
var found = [self _indexPathForObject:anObject inNode:[children objectAtIndex:i]];
if (found)
return found;
}
}
return nil;
}
- (BOOL)canInsert { return [self isEditable]; }
- (BOOL)canInsertChild { return [self isEditable] &&[_selectionIndexPaths count] > 0; }
- (BOOL)canAddChild { return [self canInsertChild]; }
- (void)add:(id)sender
{
if (![self canInsert]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject],
selectionPath = [self selectionIndexPath];
if (!selectionPath)
selectionPath = [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]];
var length = [selectionPath length],
lastIndex = [selectionPath indexAtPosition:length - 1],
insertPath = [selectionPath indexPathByRemovingLastIndex];
insertPath = [insertPath indexPathByAddingIndex:lastIndex + 1];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
- (void)addChild:(id)sender
{
if (![self canAddChild])
return;
var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]],
childCount = [[parentNode childNodes] count],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
- (void)insert:(id)sender
{
if (![self canInsert]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject],
indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0];
[self insertObject:newObject atArrangedObjectIndexPath:indexPath];
}
- (void)insertChild:(id)sender
{
if (![self canInsertChild]) return;
var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject],
insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0];
[self insertObject:newObject atArrangedObjectIndexPath:insertPath];
}
- (void)insertObject:(id)anObject atArrangedObjectIndexPath:(CPIndexPath)indexPath
{
[self insertObjects:[CPArray arrayWithObject:anObject] atArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]];
}
- (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths
{
[self willChangeValueForKey:@"content"];
_disableSetContent = YES;
var count = [objects count];
for (var i = 0; i < count; i++)
{
var object = [objects objectAtIndex:i],
path = [indexPaths objectAtIndex:i],
length = [path length];
if (length === 1)
{[_contentObject insertObject:object atIndex:[path indexAtPosition:0]];
}
else
{
var parentPath = [path indexPathByRemovingLastIndex],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
if (parentNode)
{
var parentObj = [parentNode representedObject],
childIndex = [path indexAtPosition:length - 1];
var children = [parentObj valueForKeyPath:_childrenKeyPath];
if (!children)
{
children = [CPMutableArray array];
[parentObj setValue:children forKeyPath:_childrenKeyPath];
}
var mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
[mutableChildren insertObject:object atIndex:childIndex];
}
}
}
var binding = [[self class] _binderClassForBinding:@"contentArray"];
if (binding)
[[binding getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
[self _rearrangeObjects];
if ([self selectsInsertedObjects])[self setSelectionIndexPaths:indexPaths];
[self didChangeValueForKey:@"content"];
}
- (void)remove:(id)sender
{
[self removeObjectsAtArrangedObjectIndexPaths:_selectionIndexPaths];
}
- (void)removeObjectAtArrangedObjectIndexPath:(CPIndexPath)indexPath
{
[self removeObjectsAtArrangedObjectIndexPaths:[CPArray arrayWithObject:indexPath]];
}
- (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths
{
[self willChangeValueForKey:@"content"];
_disableSetContent = YES;
var sortedPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)],
count = [sortedPaths count];
for (var i = count - 1; i >= 0; i--)
{
var path = [sortedPaths objectAtIndex:i],
length = [path length];
if (length === 1)
{
[_contentObject removeObjectAtIndex:[path indexAtPosition:0]];
}
else
{
var parentPath = [path indexPathByRemovingLastIndex],
parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath];
if (parentNode)
{
var parentObj = [parentNode representedObject],
childIndex = [path indexAtPosition:length - 1],
mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath];
if (mutableChildren && childIndex <[mutableChildren count])
[mutableChildren removeObjectAtIndex:childIndex];
}
}
}
var binding = [[self class] _binderClassForBinding:@"contentArray"];
if (binding)
[[binding getBinding:@"contentArray" forObject:self] _contentArrayDidChange];
_disableSetContent = NO;
[self _rearrangeObjects];
[self didChangeValueForKey:@"content"];
}
- (void)moveNode:(CPTreeNode)node toIndexPath:(CPIndexPath)indexPath
{
[self moveNodes:[CPArray arrayWithObject:node] toIndexPath:indexPath];
}
- (void)moveNodes:(CPArray)nodes toIndexPath:(CPIndexPath)startingIndexPath
{[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."];
}
@end
var CPTreeControllerAvoidsEmptySelection = @"CPTreeControllerAvoidsEmptySelection",
CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection",
CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects",
CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker",
CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath",
CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath",
CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath";
@implementation CPTreeController (CPCoding)
- (id)initWithCoder:(CPCoder)aCoder
{
self = [super initWithCoder:aCoder];
if (self)
{
_avoidsEmptySelection = [aCoder decodeBoolForKey:CPTreeControllerAvoidsEmptySelection];
_preservesSelection = [aCoder decodeBoolForKey:CPTreeControllerPreservesSelection];
_selectsInsertedObjects = [aCoder decodeBoolForKey:CPTreeControllerSelectsInsertedObjects];
_alwaysUsesMultipleValuesMarker = [aCoder decodeBoolForKey:CPTreeControllerAlwaysUsesMultipleValuesMarker];
_childrenKeyPath = [aCoder decodeObjectForKey:CPTreeControllerChildrenKeyPath] || @"children";
_countKeyPath = [aCoder decodeObjectForKey:CPTreeControllerCountKeyPath];
_leafKeyPath = [aCoder decodeObjectForKey:CPTreeControllerLeafKeyPath];
_sortDescriptors = [CPArray array];
_selectionIndexPaths = [CPArray array];
_arrangedObjects = [[CPTreeNode alloc] initWithRepresentedObject:nil];
if (![self content] && [self automaticallyPreparesContent])
[self prepareContent];
else if (![self content])
[self _setContentArray:[CPArray array]];
}
return self;
}
- (void)encodeWithCoder:(CPCoder)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeBool:_avoidsEmptySelection forKey:CPTreeControllerAvoidsEmptySelection];
[aCoder encodeBool:_preservesSelection forKey:CPTreeControllerPreservesSelection];
[aCoder encodeBool:_selectsInsertedObjects forKey:CPTreeControllerSelectsInsertedObjects];
[aCoder encodeBool:_alwaysUsesMultipleValuesMarker forKey:CPTreeControllerAlwaysUsesMultipleValuesMarker];
[aCoder encodeObject:_childrenKeyPath forKey:CPTreeControllerChildrenKeyPath];
[aCoder encodeObject:_countKeyPath forKey:CPTreeControllerCountKeyPath];
[aCoder encodeObject:_leafKeyPath forKey:CPTreeControllerLeafKeyPath];
}
- (void)awakeFromCib
{
[self _selectionWillChange];
[self _selectionDidChange];
}
@end
+83 -39
View File
@@ -22,13 +22,12 @@
@import <Foundation/CPObject.j>
@import <Foundation/CPIndexPath.j>
@import <Foundation/CPArray.j>
@implementation CPTreeNode : CPObject
{
id _representedObject @accessors(readonly, property=representedObject);
CPTreeNode _parentNode @accessors(readonly, property=parentNode);
id _representedObject @accessors(property=representedObject);
CPTreeNode _parentNode @accessors(property=parentNode);
CPMutableArray _childNodes;
}
@@ -52,36 +51,35 @@
- (CPIndexPath)indexPath
{
if (_parentNode != nil)
// If we have a parent, calculate path based on parent's path + our index
if (_parentNode)
{
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;
// Search the parent's child nodes, not our own!
var index = [[_parentNode childNodes] indexOfObjectIdenticalTo:self];
// If the parent is the root (and technically has no path itself in some implementations),
// we might get nil. Handle that gracefully.
var parentPath = [_parentNode indexPath];
if (parentPath)
return [parentPath indexPathByAddingIndex:index];
return [CPIndexPath indexPathWithIndex:index];
}
// If we are the root, we don't have an index path in the context of a tree controller usually,
// or we are [] (empty path). Returning nil is acceptable for the absolute root.
return nil;
}
- (BOOL)isLeaf
{
return [_childNodes count] <= 0;
return [_childNodes count] == 0;
}
- (CPArray)childNodes
{
// Return a copy to prevent external modification without KVC
return [_childNodes copy];
}
@@ -90,18 +88,29 @@
return [self mutableArrayValueForKey:@"childNodes"];
}
- (void)insertObject:(id)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
{
[[aTreeNode._parentNode mutableChildNodes] removeObjectIdenticalTo:aTreeNode];
// MARK: - KVC Compliance Methods
aTreeNode._parentNode = self;
- (void)insertObject:(CPTreeNode)aTreeNode inChildNodesAtIndex:(CPInteger)anIndex
{
// Optional: Auto-detach from old parent if strictly moving nodes
if ([aTreeNode isKindOfClass:[CPTreeNode class]] && aTreeNode._parentNode)
{
[[aTreeNode._parentNode mutableChildNodes] removeObjectIdenticalTo:aTreeNode];
}
// Direct ivar access is allowed here since we are inside the class implementation
if ([aTreeNode isKindOfClass:[CPTreeNode class]])
aTreeNode._parentNode = self;
[_childNodes insertObject:aTreeNode atIndex:anIndex];
}
- (void)removeObjectFromChildNodesAtIndex:(CPInteger)anIndex
{
[_childNodes objectAtIndex:anIndex]._parentNode = nil;
var node = [_childNodes objectAtIndex:anIndex];
if ([node isKindOfClass:[CPTreeNode class]])
node._parentNode = nil;
[_childNodes removeObjectAtIndex:anIndex];
}
@@ -110,17 +119,34 @@
{
var oldTreeNode = [_childNodes objectAtIndex:anIndex];
oldTreeNode._parentNode = nil;
aTreeNode._parentNode = self;
if ([oldTreeNode isKindOfClass:[CPTreeNode class]])
oldTreeNode._parentNode = nil;
if ([aTreeNode isKindOfClass:[CPTreeNode class]])
aTreeNode._parentNode = self;
[_childNodes replaceObjectAtIndex:anIndex withObject:aTreeNode];
}
// MARK: - Convenience Accessors
- (id)objectInChildNodesAtIndex:(CPInteger)anIndex
{
return _childNodes[anIndex];
return [_childNodes objectAtIndex:anIndex];
}
- (CPInteger)count
{
return [_childNodes count];
}
- (id)objectAtIndex:(CPInteger)anIndex
{
return [_childNodes objectAtIndex:anIndex];
}
// MARK: - Utility
- (void)sortWithSortDescriptors:(CPArray)sortDescriptors recursively:(BOOL)shouldSortRecursively
{
[_childNodes sortUsingDescriptors:sortDescriptors];
@@ -129,25 +155,39 @@
return;
var count = [_childNodes count];
while (count--)
[_childNodes[count] sortWithSortDescriptors:sortDescriptors recursively:YES];
{
var child = [_childNodes objectAtIndex:count];
if ([child respondsToSelector:@selector(sortWithSortDescriptors:recursively:)])
[child sortWithSortDescriptors:sortDescriptors recursively:YES];
}
}
- (CPTreeNode)descendantNodeAtIndexPath:(CPIndexPath)indexPath
{
var index = 0,
count = [indexPath length],
node = self;
if (!indexPath || [indexPath length] == 0)
return self;
for (; index < count; ++index)
node = [node objectInChildNodesAtIndex:[indexPath indexAtPosition:index]];
var node = self,
length = [indexPath length];
for (var i = 0; i < length; i++)
{
var index = [indexPath indexAtPosition:i],
count = [node count];
if (index >= count || index < 0)
return nil;
node = [node objectAtIndex:index];
}
return node;
}
@end
// Coding implementation remains correct
var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
CPTreeNodeParentNodeKey = @"CPTreeNodeParentNodeKey",
CPTreeNodeChildNodesKey = @"CPTreeNodeChildNodesKey";
@@ -163,6 +203,10 @@ var CPTreeNodeRepresentedObjectKey = @"CPTreeNodeRepresentedObjectKey",
_representedObject = [aCoder decodeObjectForKey:CPTreeNodeRepresentedObjectKey];
_parentNode = [aCoder decodeObjectForKey:CPTreeNodeParentNodeKey];
_childNodes = [aCoder decodeObjectForKey:CPTreeNodeChildNodesKey];
// Safety check to ensure decoding gave us a CPArray
if (!_childNodes)
_childNodes = [[CPMutableArray alloc] init];
}
return self;
+139 -20
View File
@@ -44,6 +44,7 @@
@class CPClipView
@class CPScrollView
@class CALayer
@class CPBinder
@global appkit_tag_dom_elements
@@ -232,7 +233,7 @@ var CPViewHighDPIDrawingEnabled = YES;
JSObject _ephemeralSubviews;
JSObject _ephemeralSubviewsForNames;
CPSet _ephereralSubviews;
CPSet _ephemeralSubviews;
// Key View Support
CPView _nextKeyView;
@@ -333,8 +334,9 @@ var CPViewHighDPIDrawingEnabled = YES;
{
return @{
@"css-based": NO,
@"dynamic-set": [CPNull null],
@"nib2cib-adjustment-frame": CGRectMakeZero()
@"nib2cib-adjustment-frame": [CPNull null],
@"direct-nib2cib-adjustment": NO,
@"dynamic-set": [CPNull null]
};
}
@@ -601,7 +603,8 @@ var CPViewHighDPIDrawingEnabled = YES;
// We will have to adjust the z-index of all views starting at this index.
var count = _subviews.length,
lastWindow;
lastWindow,
isNewAddOrMove = aSubview._superview !== self;
// Dirty the key view loop, in case the window wants to auto recalculate it
[[self window] _dirtyKeyViewLoop];
@@ -660,6 +663,11 @@ var CPViewHighDPIDrawingEnabled = YES;
#endif
}
#if PLATFORM(DOM)
var origin = aSubview._frame.origin;
CPDOMDisplayServerSetStyleLeftTop(aSubview._DOMElement, _boundsTransform, origin.x, origin.y);
#endif
[aSubview setNextResponder:self];
[aSubview _scaleSizeUnitSquareToSize:[self _hierarchyScaleSize]];
@@ -672,6 +680,9 @@ var CPViewHighDPIDrawingEnabled = YES;
if (!_window && lastWindow)
[aSubview _setWindow:nil];
if (isNewAddOrMove)
[aSubview _postViewDidAppearNotification];
// This method might be called before we are fully unarchived, in which case the theme state isn't set up yet
// and none of the below matters anyhow.
if (_themeState)
@@ -734,6 +745,7 @@ var CPViewHighDPIDrawingEnabled = YES;
// If the view is not hidden and one of its ancestors is hidden,
// notify the view that it is now unhidden.
[self _setSuperview:nil];
[self _postViewDidDisappearNotification];
[self _notifyWindowDidResignKey];
[self _notifyViewDidResignFirstResponder];
@@ -917,6 +929,13 @@ var CPViewHighDPIDrawingEnabled = YES;
*/
- (void)viewDidMoveToWindow
{
var window = [self window];
// If the view is inside a window with the HUD style mask, turn on the HUD state
if (window && ([window styleMask] & CPHUDBackgroundWindowMask))
[self setThemeState:CPThemeStateHUD];
else
[self unsetThemeState:CPThemeStateHUD];
}
/*!
@@ -1404,6 +1423,9 @@ var CPViewHighDPIDrawingEnabled = YES;
_inverseBoundsTransform = nil;
}
if (_layer)
[_layer _owningViewBoundsChanged];
#if PLATFORM(DOM)
var index = _subviews.length;
@@ -1460,6 +1482,9 @@ var CPViewHighDPIDrawingEnabled = YES;
origin.y *= size.height / frameSize.height;
}
if (_layer)
[_layer _owningViewBoundsChanged];
if (_postsBoundsChangedNotifications && !_inhibitFrameAndBoundsChangedNotifications)
[CachedNotificationCenter postNotificationName:CPViewBoundsDidChangeNotification object:self];
@@ -1718,11 +1743,7 @@ var CPViewHighDPIDrawingEnabled = YES;
_superview = aSuperview;
if (hasOldSuperview)
[self _postViewDidDisappearNotification];
if (hasNewSuperview)
[self _postViewDidAppearNotification];
// Notifications are now posted manually from _insertSubview and _removeFromSuperview
}
- (void)_recursiveLostHiddenAncestor
@@ -3213,7 +3234,8 @@ setBoundsOrigin:
{
_layer._owningView = nil;
#if PLATFORM(DOM)
_DOMElement.removeChild(_layer._DOMElement);
if (_layer._DOMElement && _layer._DOMElement.parentNode === _DOMElement)
_DOMElement.removeChild(_layer._DOMElement);
#endif
}
@@ -3221,33 +3243,56 @@ setBoundsOrigin:
if (_layer)
{
var bounds = CGRectMakeCopy([self bounds]);
[_layer _setOwningView:self];
[_layer setFrame:[self bounds]]; // Sync layer frame with view bounds
#if PLATFORM(DOM)
_layer._DOMElement.style.zIndex = 100;
_DOMElement.appendChild(_layer._DOMElement);
#endif
}
}
/*!
Returns the core animation layer used by the receiver.
Returns the core animation layer used by the receiver and creates one if necessary.
*/
- (CALayer)layer
{
if (_wantsLayer && !_layer)
{
var layer = [[CALayer alloc] init];
[self setLayer:layer];
[self setNeedsLayout:YES];
[self setNeedsDisplay:YES];
}
return _layer;
}
/*!
Sets whether the receiver wants a core animation layer.
@param \c YES means the receiver wants a layer.
@param aFlag \c YES means the receiver wants a layer.
*/
- (void)setWantsLayer:(BOOL)aFlag
{
_wantsLayer = !!aFlag;
aFlag = !!aFlag;
if (_wantsLayer === aFlag)
return;
_wantsLayer = aFlag;
if (_wantsLayer)
{
// Accessing the layer will create it if it doesn't exist.
[self layer];
}
else
{
// Remove the layer if we no longer want it.
if (_layer)
[self setLayer:nil];
}
}
/*!
@@ -3259,6 +3304,38 @@ setBoundsOrigin:
return _wantsLayer;
}
/*!
Rotates the view's visual representation by a given angle (in degrees) around its center point.
This method achieves the rotation by applying a transform directly to the view's backing CALayer.
Because this is a direct layer manipulation, the view's own `frame` property is not updated to
reflect the new visual bounding box. Consequently, a `CPViewBoundsDidChangeNotification` is
**not** posted by this method. Note that this is a deviation from Cocoa's behavior.
This method requires the view to be layer-backed. If the view is not
already layer-backed, this method will automatically set wantsLayer to YES.
@param angle The angle in degrees to rotate the view.
*/
- (void)rotateByAngle:(CGFloat)angle
{
// Ensure the view is layer-backed
[self setWantsLayer:YES];
var layer = [self layer];
if (!layer)
return;
// Convert degrees to radians for the transform
var radians = angle * Math.PI / 180.0;
var rotationTransform = CGAffineTransformMakeRotation(radians);
var currentTransform = [layer affineTransform];
var newTransform = CGAffineTransformConcat(currentTransform, rotationTransform);
[layer setAffineTransform:newTransform];
}
@end
@@ -3298,7 +3375,7 @@ setBoundsOrigin:
@implementation CPView (Theming)
#pragma mark Override
// MARK: Override
- (BOOL)setThemeState:(ThemeState)aState
{
@@ -3335,7 +3412,7 @@ setBoundsOrigin:
}
#pragma mark First responder
// MARK: First responder
- (BOOL)becomeFirstResponder
{
@@ -3397,7 +3474,7 @@ setBoundsOrigin:
[_subviews[count] _notifyWindowDidResignKey];
}
#pragma mark Theme Attributes
// MARK: Theme Attributes
- (void)_setThemeIncludingDescendants:(CPTheme)aTheme
{
@@ -3732,6 +3809,16 @@ var CPAppearanceVibrantDark = [CPAppearance appearanceNamed:CPAppearanceNameVibr
[owners[i] updateTrackingAreas];
}
// needed by CPWindow's releasedWhenClosed property
- (void)_releaseRecursively
{
[_subviews makeObjectsPerformSelector:@selector(_releaseRecursively)];
[self _removeObservers];
[CPBinder unbindAllForObject:self];
[self removeFromSuperview];
}
@end
var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
@@ -3754,7 +3841,8 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
CPViewSizeScaleKey = @"CPViewSizeScaleKey",
CPViewIsScaledKey = @"CPViewIsScaledKey",
CPViewAppearanceKey = @"CPViewAppearanceKey",
CPViewTrackingAreasKey = @"CPViewTrackingAreasKey";
CPViewTrackingAreasKey = @"CPViewTrackingAreasKey",
CPViewWantsLayerKey = @"CPViewWantsLayerKey";
@implementation CPView (CPCoding)
@@ -3857,6 +3945,10 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
_opacity = 1.0;
[self setBackgroundColor:[aCoder decodeObjectForKey:CPViewBackgroundColorKey]];
if ([aCoder containsValueForKey:CPViewWantsLayerKey])
[self setWantsLayer:[aCoder decodeBoolForKey:CPViewWantsLayerKey]];
[self _setupViewFlags];
[self setAppearance:[aCoder decodeObjectForKey:CPViewAppearanceKey]];
@@ -3944,6 +4036,9 @@ var CPViewAutoresizingMaskKey = @"CPViewAutoresizingMask",
if (_identifier)
[aCoder encodeObject:_identifier forKey:CPReuseIdentifierKey];
if (_wantsLayer)
[aCoder encodeBool:_wantsLayer forKey:CPViewWantsLayerKey];
[aCoder encodeSize:[self scaleSize] forKey:CPViewScaleKey];
[aCoder encodeSize:[self _hierarchyScaleSize] forKey:CPViewSizeScaleKey];
[aCoder encodeBool:_isScaled forKey:CPViewIsScaledKey];
@@ -4084,3 +4179,27 @@ var _CPViewGetTransform = function(/*CPView*/ fromView, /*CPView */ toView)
return transform;
};
@implementation CPView (ThemingAdditions)
- (void)_setThemeStateRecursively:(ThemeState)aState
{
[_subviews makeObjectsPerformSelector:@selector(_setThemeStateRecursively:) withObject:aState];
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view setThemeState:aState];
}];
}
- (void)_unsetThemeStateRecursively:(ThemeState)aState
{
[_subviews makeObjectsPerformSelector:@selector(_unsetThemeStateRecursively:) withObject:aState];
[_subviews enumerateObjectsUsingBlock:function(view, idx, stop)
{
[view unsetThemeState:aState];
}];
}
@end
+34 -11
View File
@@ -28,6 +28,8 @@ CPViewAnimationTargetKey = @"CPViewAnimationTargetKey";
CPViewAnimationStartFrameKey = @"CPViewAnimationStartFrameKey";
CPViewAnimationEndFrameKey = @"CPViewAnimationEndFrameKey";
CPViewAnimationEffectKey = @"CPViewAnimationEffectKey";
CPViewAnimationStartOpacityKey = @"CPViewAnimationStartOpacityKey";
CPViewAnimationEndOpacityKey = @"CPViewAnimationEndOpacityKey";
CPViewAnimationFadeInEffect = @"CPViewAnimationFadeInEffect";
CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
@@ -132,12 +134,24 @@ CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
[view setFrame:intermediateFrame];
// Update the view's alpha value
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0 * value];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:1.0 + ( 0.0 - 1.0 ) * value];
// Check for explicit Opacity keys first
var startOpacity = [dictionary objectForKey:CPViewAnimationStartOpacityKey],
endOpacity = [dictionary objectForKey:CPViewAnimationEndOpacityKey];
if (startOpacity != nil && endOpacity != nil)
{
// Interpolate alpha
[view setAlphaValue:startOpacity + (endOpacity - startOpacity) * value];
}
else
{
// Fallback to legacy Effect keys
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0 * value];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:1.0 + ( 0.0 - 1.0 ) * value];
}
if (progress === 1.0)
[self _targetView:view setHidden:CGRectIsEmpty(endFrame) || [view alphaValue] === 0.0];
@@ -155,11 +169,20 @@ CPViewAnimationFadeOutEffect = @"CPViewAnimationFadeOutEffect";
[view setFrame:endFrame];
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:0.0];
var endOpacity = [dictionary objectForKey:CPViewAnimationEndOpacityKey];
if (endOpacity != nil)
{
[view setAlphaValue:endOpacity];
}
else
{
var effect = [self _effect:dictionary];
if (effect === CPViewAnimationFadeInEffect)
[view setAlphaValue:1.0];
else if (effect === CPViewAnimationFadeOutEffect)
[view setAlphaValue:0.0];
}
[self _targetView:view setHidden:CGRectIsEmpty(endFrame) || [view alphaValue] === 0.0];
}
+26 -1
View File
@@ -166,7 +166,7 @@ var CPViewControllerCachedCibs;
if (!cib)
{
// if the cib isn't cached yet : fetch it and cache it
// if the cib isn't cached yet: fetch it and cache it
cib = [[CPCib alloc] initWithCibNamed:_cibName bundle:_cibBundle];
[CPViewControllerCachedCibs setObject:cib forKey:_cibName];
}
@@ -261,6 +261,11 @@ var CPViewControllerCachedCibs;
if (_view == nil && [cibOwner isKindOfClass:[CPDocument class]])
[self setView:[cibOwner valueForKey:@"view"]];
// If the view was just loaded, we must set its next responder.
// This is the first half of inserting the controller into the responder chain.
if (_view)
[_view setNextResponder:self];
if (!_view)
{
var reason = [CPString stringWithFormat:@"View for %@ could not be loaded from Cib or no view specified. Override loadView to load the view manually.", self];
@@ -408,12 +413,28 @@ var CPViewControllerCachedCibs;
[self willChangeValueForKey:"isViewLoaded"];
_view = aView;
// When the view is set manually, we must set its next responder.
if (_view)
[_view setNextResponder:self];
_isViewLoaded = aView != nil;
if (willChangeIsViewLoaded)
[self didChangeValueForKey:"isViewLoaded"];
}
/*!
@method nextResponder
@discussion The CPViewController implementation of this method returns the superview
of the view controller's view. This is the second half of the insertion,
completing the chain: view -> viewController -> superview.
*/
- (id)nextResponder
{
return [_view superview];
}
- (BOOL)automaticallyNotifiesObserversOfIsViewLoaded
{
return NO;
@@ -465,6 +486,10 @@ var CPViewControllerViewKey = @"CPViewControllerViewKey",
if (self)
{
_view = [aCoder decodeObjectForKey:CPViewControllerViewKey];
// When the view is unarchived, we must also set its next responder.
if (_view)
[_view setNextResponder:self];
_title = [aCoder decodeObjectForKey:CPViewControllerTitleKey];
_cibName = [aCoder decodeObjectForKey:CPViewControllerCibNameKey];
+9 -9
View File
@@ -57,8 +57,8 @@ CPVisualEffectStateInactive = 2;
}
#pragma mark -
#pragma mark Initialization
// MARK: -
// MARK: Initialization
- (id)initWithFrame:(CGRect)aFrame
{
@@ -74,8 +74,8 @@ CPVisualEffectStateInactive = 2;
}
#pragma mark -
#pragma mark CPVisualEffectView API
// MARK: -
// MARK: CPVisualEffectView API
/*! Sets the appearance of the CPVisualEffectView.
@@ -114,8 +114,8 @@ CPVisualEffectStateInactive = 2;
}
#pragma mark -
#pragma mark Utilities
// MARK: -
// MARK: Utilities
- (void)_setEffectEnabled:(BOOL)shouldEnable
{
@@ -156,8 +156,8 @@ CPVisualEffectStateInactive = 2;
}
#pragma mark -
#pragma mark CPCoding
// MARK: -
// MARK: CPCoding
- (id)initWithCoder:(CPCoder)aCoder
{
@@ -183,4 +183,4 @@ CPVisualEffectStateInactive = 2;
}
@end
@end
+5 -1
View File
@@ -278,7 +278,11 @@ CPWebViewAppKitScrollMaxPollCount = 3;
if (_effectiveScrollMode === CPWebViewScrollAppKit)
{
var visibleRect = [_frameView visibleRect];
// Use `[_scrollView documentVisibleRect]` instead of `[_frameView visibleRect]`.
// `visibleRect` accounts for window clipping, which collapses to 0x0 if the view is
// animated off-screen, resulting in an incorrectly shrunken web layout.
var visibleRect = [_scrollView documentVisibleRect];
[_frameView setFrameSize:CGSizeMake(CGRectGetMaxX(visibleRect), CGRectGetMaxY(visibleRect))];
// try to get the document size so we can correctly set the frame
+19 -3
View File
@@ -186,6 +186,7 @@ var CPWindowActionMessageKeys = [
BOOL _constrainsToUsableScreen;
unsigned _shadowStyle;
BOOL _showsResizeIndicator;
BOOL _releasedWhenClosed @accessors(property=releasedWhenClosed);
int _positioningMask;
CGRect _positioningScreenRect;
@@ -978,6 +979,9 @@ CPTexturedBackgroundWindowMask
- (void)orderFront:(id)aSender
{
[self orderWindow:CPWindowAbove relativeTo:0];
if (_styleMask & CPHUDBackgroundWindowMask)
[_contentView _setThemeStateRecursively:CPThemeStateHUD];
}
- (void)_orderFront
@@ -1265,6 +1269,10 @@ CPTexturedBackgroundWindowMask
[_contentView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[_windowView addSubview:_contentView];
// The window view manages the exact layout of the content view (e.g. offsetting for the toolbar).
if ([_windowView respondsToSelector:@selector(tile)])
[_windowView tile];
/*
If the initial first responder has been set to something other than
the window, set it to the window because it will no longer be valid.
@@ -1946,7 +1954,8 @@ CPTexturedBackgroundWindowMask
return [[self firstResponder] keyUp:anEvent];
case CPKeyDown:
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter)
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter &&
!([anEvent modifierFlags] & (CPAlternateKeyMask | CPCommandKeyMask)))
{
if ([anEvent modifierFlags] & CPShiftKeyMask)
[self selectPreviousKeyView:self];
@@ -2533,9 +2542,16 @@ CPTexturedBackgroundWindowMask
[[CPNotificationCenter defaultCenter] postNotificationName:CPWindowWillCloseNotification object:self];
// Give a chance to the _windowView to do things before closing (for example, remove an observer)
if ([_windowView respondsToSelector:@selector(_close)])
[_windowView _close];
[_parentWindow removeChildWindow:self];
[self _orderOutRecursively:NO];
[self _detachFromChildrenClosing:!_parentWindow];
if (_releasedWhenClosed)
[_contentView _releaseRecursively];
}
- (void)_detachFromChildrenClosing:(BOOL)shouldCloseChildren
@@ -4446,7 +4462,7 @@ var interpolate = function(fromValue, toValue, progress)
@end
#pragma mark -
// MARK: -
@implementation CPWindow (CSSTheming)
@@ -4457,7 +4473,7 @@ var interpolate = function(fromValue, toValue, progress)
@end
#pragma mark -
// MARK: -
function _CPWindowFullPlatformWindowSessionMake(aWindowView, aContentRect, hasShadow, aLevel)
{
@@ -39,6 +39,22 @@
};
}
- (id)initWithFrame:(CGRect)aFrame styleMask:(unsigned)aStyleMask
{
self = [super initWithFrame:aFrame styleMask:aStyleMask];
if (self)
{
// Check if the mask contains the HUD flag
if (aStyleMask & CPHUDBackgroundWindowMask)
{
[self setBackgroundColor:[CPColor blackColor]];
}
}
return self;
}
- (void)setShowsResizeIndicator:(BOOL)shouldShowResizeIndicator
{
// We don't ever want to show the resize indicator.
+331 -55
View File
@@ -22,6 +22,7 @@
@import "CPButton.j"
@import "_CPTitleableWindowView.j"
@import "CPApplication_Constants.j"
@class _CPDocModalWindowView
@@ -43,7 +44,11 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
+ (CPDictionary)themeAttributes
{
return @{};
return @{
@"close-active-image-button": [CPNull null],
@"minimize-active-image-button": [CPNull null],
@"zoom-active-image-button": [CPNull null],
};
}
- (id)initWithFrame:(CGRect)aFrame windowView:(_CPWindowView)parentView
@@ -71,19 +76,26 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
bounds = [self bounds],
bezelHeadColor = [[CPTheme defaultTheme] valueForAttributeWithName:_isSheet ? @"bezel-head-sheet-color" : @"bezel-head-color" inState:[_parentView themeState] forClass:_CPStandardWindowView];
[_gradientView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds), gradientHeight)];
[_gradientView setBackgroundColor:bezelHeadColor];
// Apply the border/background color to self (the container) so borders are drawn on the outside
[self setBackgroundColor:bezelHeadColor];
[_solidView setFrame:CGRectMake(0.0, gradientHeight, CGRectGetWidth(bounds), CGRectGetHeight(bounds) - gradientHeight)];
// Set x=0.0 to avoid 1px gap. Width is reduced by 2.0 to fit inside the 1px borders on each side.
[_gradientView setFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(bounds) - 2.0, gradientHeight)];
// Set x=0.0 to avoid 1px gap. Width is reduced by 2.0.
[_solidView setFrame:CGRectMake(0.0, gradientHeight, CGRectGetWidth(bounds) - 2.0, CGRectGetHeight(bounds) - gradientHeight)];
[_solidView setBackgroundColor:[[CPTheme defaultTheme] valueForAttributeWithName:@"solid-color" forClass:_CPStandardWindowView]];
}
- (void)resizeSubviewsWithOldSize:(CGSize)aSize
{
var bounds = [self bounds];
var bounds = [self bounds],
width = CGRectGetWidth(bounds) - 2.0; // Account for border inset
[_gradientView setFrameSize:CGSizeMake(CGRectGetWidth(bounds), [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
[_solidView setFrameSize:CGSizeMake(CGRectGetWidth(bounds), CGRectGetHeight(bounds) - [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
if (width < 0) width = 0;
[_gradientView setFrameSize:CGSizeMake(width, [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
[_solidView setFrameSize:CGSizeMake(width, CGRectGetHeight(bounds) - [[CPTheme defaultTheme] valueForAttributeWithName:@"gradient-height" forClass:_CPStandardWindowView])];
}
@end
@@ -97,9 +109,16 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
CPButton _closeButton;
CPButton _minimizeButton;
CPButton _zoomButton;
BOOL _isDocumentEdited;
BOOL _isSheet;
CPTrackingArea _closeButtonTrackingArea;
CPTrackingArea _minimizeButtonTrackingArea;
CPTrackingArea _zoomButtonTrackingArea;
int _buttonsWidth;
}
+ (CPString)defaultThemeClass
@@ -123,6 +142,8 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
@"close-image-highlighted-button": [CPNull null],
@"unsaved-image-button": [CPNull null],
@"unsaved-image-highlighted-button": [CPNull null],
@"zoom-image-button": [CPNull null],
@"zoom-image-highlighted-button": [CPNull null]
};
}
@@ -154,7 +175,7 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
var theClass = [self class],
bounds = [self bounds];
_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), _titleBarHeight) windowView:self];
[_headView setAutoresizingMask:CPViewWidthSizable];
[_headView setHitTests:NO];
@@ -181,30 +202,50 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
if (_styleMask & CPClosableWindowMask)
{
_closeButton = [[CPButton alloc] initWithFrame:CGRectMake(8.0, 8.0, 16.0, 16.0)];
_closeButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
[_closeButton setButtonType:CPMomentaryChangeButton];
[_closeButton setBordered:NO];
[self _updateCloseButton];
[self addSubview:_closeButton];
}
if (_styleMask & CPMiniaturizableWindowMask && ![CPPlatform isBrowser])
if (_styleMask & CPMiniaturizableWindowMask)
{
_minimizeButton = [[CPButton alloc] initWithFrame:CGRectMake(27.0, 7.0, 16.0, 16.0)];
_minimizeButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
[_minimizeButton setButtonType:CPMomentaryChangeButton];
[_minimizeButton setBordered:NO];
[self addSubview:_minimizeButton];
}
if (_styleMask & CPResizableWindowMask)
{
_zoomButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
[_zoomButton setButtonType:CPMomentaryChangeButton];
[_zoomButton setBordered:NO];
[self addSubview:_zoomButton];
}
[self _updateWindowButtons:YES];
[self tile];
// Observe CPApplicationOSBehaviorDidChangeNotification
[[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_osBehaviorDidChange:) name:CPApplicationOSBehaviorDidChangeNotification object:CPApp];
}
return self;
}
// This will be called by CPWindow -close so the observer can be removed
- (void)_close
{
[[CPNotificationCenter defaultCenter] removeObserver:self name:CPApplicationOSBehaviorDidChangeNotification object:CPApp];
}
- (void)viewDidMoveToWindow
{
[_closeButton setTarget:[self window]];
@@ -212,11 +253,14 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_minimizeButton setTarget:[self window]];
[_minimizeButton setAction:@selector(performMiniaturize:)];
[_zoomButton setTarget:[self window]];
[_zoomButton setAction:@selector(performZoom:)];
}
- (CGSize)toolbarOffset
{
return CGSizeMake(0.0, [self valueForThemeAttribute:@"title-bar-height"]);
return CGSizeMake(0.0, _titleBarHeight);
}
- (void)tile
@@ -236,6 +280,7 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_headView setFrameSize:CGSizeMake(width, headHeight)];
// Set divider to full width to prevent gaps/holes at the sides.
[_dividerView setFrame:CGRectMake(0.0, headHeight, width, _CPStandardWindowViewDividerViewHeight)];
var dividerMinY = 0,
@@ -246,52 +291,179 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_bodyView setFrame:CGRectMake(0.0, dividerMinY, width, CGRectGetHeight(bounds) - dividerMinY)];
var leftOffset = 8;
if (_closeButton)
leftOffset += 19.0;
if (_minimizeButton)
leftOffset += 19.0;
[_titleField setFrame:CGRectMake(leftOffset, 0, width - leftOffset * 2.0, [self valueForThemeAttribute:@"title-bar-height"])];
[_titleField setFrame:CGRectMake(_buttonsWidth, 0, width - _buttonsWidth * 2.0, _titleBarHeight)];
var contentFrame = [_bodyView frame];
[[theWindow contentView] setFrame:CGRectInset(contentFrame, 1.0, 1.0)];
}
/*
- (void)setAnimatingToolbar:(BOOL)isAnimatingToolbar
- (void)_updateWindowButtons:(BOOL)shouldRefreshLayout
{
[super setAnimatingToolbar:isAnimatingToolbar];
if ([self isAnimatingToolbar])
if (shouldRefreshLayout)
{
[[self toolbarView] setAutoresizingMask:CPViewHeightSizable];
var mimicWindows = [CPApp shouldMimicWindows];
[_headView setAutoresizingMask:CPViewHeightSizable];
[_dividerView setAutoresizingMask:CPViewMinYMargin];
[_bodyView setAutoresizingMask:CPViewMinYMargin];
if (mimicWindows)
[self setThemeState:CPThemeStateWindowsPlatform];
else
[self unsetThemeState:CPThemeStateWindowsPlatform];
[[[self window] contentView] setAutoresizingMask:CPViewNotSizable];
// Remember that on Cappuccino, buttons are (left to right) : close - minimize - zoom
// and that on Windows, (also left to right) : minimize - zoom - close
var offset,
mask,
closeThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"close-image-origin"] || CGPointMakeZero()),
minimizeThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"minimize-image-origin"] || CGPointMakeZero()),
zoomThemeOrigin = CGPointMakeCopy([self currentValueForThemeAttribute:@"zoom-image-origin"] || CGPointMakeZero()),
closeThemeSize = CGSizeMakeZero(),
minimizeThemeSize = CGSizeMakeZero(),
zoomThemeSize = CGSizeMakeZero(),
delta1,
delta2;
// For retro-compatibility
if (![self isCSSBased])
{
if (mimicWindows)
{
// There's no zoom button in Aristo2
closeThemeOrigin = CGPointMake(-24.0, 8.0);
minimizeThemeOrigin = CGPointMake(-43.0, 8.0);
// FIXME: if someone designs a zoom button in Aristo2 one day, use those values
// closeThemeOrigin = CGPointMake(-24.0, 8.0);
// minimizeThemeOrigin = CGPointMake(-62.0, 8.0);
// zoomThemeOrigin = CGPointMake(-43.0, 8.0);
}
else
{
closeThemeOrigin = CGPointMake(8.0, 8.0);
minimizeThemeOrigin = CGPointMake(27.0, 8.0);
zoomThemeOrigin = CGPointMake(46.0, 8.0);
}
}
if (mimicWindows)
{
offset = [self bounds].size.width;
mask = CPViewMinXMargin;
delta1 = zoomThemeOrigin.x - closeThemeOrigin.x;
delta2 = minimizeThemeOrigin.x - zoomThemeOrigin.x;
}
else
{
offset = 0;
mask = CPViewMaxXMargin;
delta1 = minimizeThemeOrigin.x - closeThemeOrigin.x;
delta2 = zoomThemeOrigin.x - minimizeThemeOrigin.x;
}
_buttonsWidth = 0;
if (_styleMask & CPClosableWindowMask)
{
closeThemeSize = [self currentValueForThemeAttribute:@"close-image-size"];
// For retro-compatibility:
if (!closeThemeSize)
closeThemeSize = CGSizeMake(16.0, 16.0);
[_closeButton setFrame:CGRectMake(closeThemeOrigin.x + offset, closeThemeOrigin.y, closeThemeSize.width, closeThemeSize.height)];
[_closeButton setAutoresizingMask:mask];
_buttonsWidth = ABS(closeThemeOrigin.x) + (mimicWindows ? 0 : closeThemeSize.width);
}
else
{
minimizeThemeOrigin.x -= delta1;
zoomThemeOrigin.x -= delta1;
}
if (mimicWindows)
{
if (_styleMask & CPResizableWindowMask)
{
zoomThemeSize = [self currentValueForThemeAttribute:@"zoom-image-size"];
// For retro-compatibility:
if (!zoomThemeSize)
zoomThemeSize = CGSizeMake(16.0, 16.0);
[_zoomButton setFrame:CGRectMake(zoomThemeOrigin.x + offset, zoomThemeOrigin.y, zoomThemeSize.width, zoomThemeSize.height)];
[_zoomButton setAutoresizingMask:mask];
_buttonsWidth = ABS(zoomThemeOrigin.x);
}
else
minimizeThemeOrigin.x -= delta2;
if (_styleMask & CPMiniaturizableWindowMask)
{
minimizeThemeSize = [self currentValueForThemeAttribute:@"minimize-image-size"];
// For retro-compatibility:
if (!minimizeThemeSize)
minimizeThemeSize = CGSizeMake(16.0, 16.0);
[_minimizeButton setFrame:CGRectMake(minimizeThemeOrigin.x + offset, minimizeThemeOrigin.y, minimizeThemeSize.width, minimizeThemeSize.height)];
[_minimizeButton setAutoresizingMask:mask];
_buttonsWidth = ABS(minimizeThemeOrigin.x);
}
if (_buttonsWidth > 0)
_buttonsWidth += ABS(closeThemeOrigin.x) - closeThemeSize.width;
}
else // not win
{
if (_styleMask & CPMiniaturizableWindowMask)
{
minimizeThemeSize = [self currentValueForThemeAttribute:@"minimize-image-size"];
// For retro-compatibility:
if (!minimizeThemeSize)
minimizeThemeSize = CGSizeMake(16.0, 16.0);
[_minimizeButton setFrame:CGRectMake(minimizeThemeOrigin.x + offset, minimizeThemeOrigin.y, minimizeThemeSize.width, minimizeThemeSize.height)];
[_minimizeButton setAutoresizingMask:mask];
_buttonsWidth = minimizeThemeOrigin.x + minimizeThemeSize.width;
}
else
zoomThemeOrigin.x -= delta2;
if (_styleMask & CPResizableWindowMask)
{
zoomThemeSize = [self currentValueForThemeAttribute:@"zoom-image-size"];
// For retro-compatibility:
if (!zoomThemeSize)
zoomThemeSize = CGSizeMake(16.0, 16.0);
[_zoomButton setFrame:CGRectMake(zoomThemeOrigin.x + offset, zoomThemeOrigin.y, zoomThemeSize.width, zoomThemeSize.height)];
[_zoomButton setAutoresizingMask:mask];
_buttonsWidth = zoomThemeOrigin.x + zoomThemeSize.width;
}
if (_buttonsWidth > 0)
_buttonsWidth += closeThemeOrigin.x;
}
[self updateTrackingAreas];
}
else
{
[[self toolbarView] setAutoresizingMask:CPViewWidthSizable];
[_headView setAutoresizingMask:CPViewWidthSizable];
[_dividerView setAutoresizingMask:CPViewWidthSizable];
[_bodyView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
[self _updateCloseButton];
[[[self window] contentView] setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
}
[_minimizeButton setImage:[self currentValueForThemeAttribute:@"minimize-image-button"]];
[_minimizeButton setAlternateImage:[self currentValueForThemeAttribute:@"minimize-image-highlighted-button"]];
[_zoomButton setImage:[self currentValueForThemeAttribute:@"zoom-image-button"]];
[_zoomButton setAlternateImage:[self currentValueForThemeAttribute:@"zoom-image-highlighted-button"]];
}
*/
- (void)_updateCloseButton
{
[_closeButton setFrameSize:[self valueForThemeAttribute:@"close-image-size"]];
[_closeButton setFrameOrigin:[self valueForThemeAttribute:@"close-image-origin"]];
if (_isDocumentEdited)
{
[_closeButton setImage:[self currentValueForThemeAttribute:@"unsaved-image-button"]];
@@ -304,9 +476,15 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
}
}
- (void)_osBehaviorDidChange:(id)application
{
[self _updateWindowButtons:YES];
}
- (void)setDocumentEdited:(BOOL)isEdited
{
_isDocumentEdited = isEdited;
[self _updateCloseButton];
}
@@ -337,9 +515,10 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
[_dividerView setHidden:enable];
}
[_closeButton setHidden:enable];
[_closeButton setHidden:enable];
[_minimizeButton setHidden:enable];
[_titleField setHidden:enable];
[_zoomButton setHidden:enable];
[_titleField setHidden:enable];
[[self window] setMovable:!enable];
@@ -381,28 +560,35 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
- (void)layoutSubviews
{
var bounds = [self bounds];
var width = [self bounds].size.width,
mimicWindows = [CPApp shouldMimicWindows];
[super layoutSubviews];
[self _updateCloseButton];
[_minimizeButton setImage:[self valueForThemeAttribute:@"minimize-image-button"]];
[_minimizeButton setAlternateImage:[self valueForThemeAttribute:@"minimize-image-highlighted-button"]];
if (_closeButton || _minimizeButton || _zoomButton)
[self _updateWindowButtons:NO];
[_dividerView setBackgroundColor:[self valueForThemeAttribute:@"divider-color"]];
[_bodyView setBackgroundColor:[self valueForThemeAttribute:@"body-color"]];
[_headView setNeedsLayout];
if (width - 2 * _buttonsWidth < _minimumTitleFieldSize)
{
if (width - _buttonsWidth - _titleMargin < _minimumTitleFieldSize)
[_titleField setFrame:CGRectMake((mimicWindows ? _titleMargin : _buttonsWidth), 0, width - _buttonsWidth - _titleMargin, _titleBarHeight)];
else
[_titleField setFrame:CGRectMake((mimicWindows ? width - _buttonsWidth - _minimumTitleFieldSize : _buttonsWidth), 0, _minimumTitleFieldSize, _titleBarHeight)];
}
}
- (CGSize)_minimumResizeSize
{
// The minimum width is such that the close button would always be visible.
// We give the same margin to the right of the button as there is to the left.
var size = [super _minimumResizeSize],
closeSize = [self valueForThemeAttribute:@"close-image-size"],
closeOrigin = [self valueForThemeAttribute:@"close-image-origin"];
// The minimum width is such that the close/minimize/zoom button(s) would always be visible.
// We give the same margin to the right of the button(s) as there is to the left.
var size = CGSizeMakeCopy([super _minimumResizeSize]);
size.width = closeSize.width + (closeOrigin.x * 2);
size.width = _buttonsWidth;
size.height += _CPStandardWindowViewDividerViewHeight;
return size;
@@ -413,4 +599,94 @@ var _CPStandardWindowViewDividerViewHeight = 1.0;
return [_bodyView frame].origin.y;
}
#pragma mark -
#pragma mark Hover management for buttons
- (void)updateTrackingAreas
{
if (_closeButtonTrackingArea)
[_closeButton removeTrackingArea:_closeButtonTrackingArea];
if (_minimizeButtonTrackingArea)
[_minimizeButton removeTrackingArea:_minimizeButtonTrackingArea];
if (_zoomButtonTrackingArea)
[_zoomButton removeTrackingArea:_zoomButtonTrackingArea];
if (_closeButton)
{
_closeButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_closeButton];
[_closeButton addTrackingArea:_closeButtonTrackingArea];
}
if (_minimizeButton)
{
_minimizeButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_minimizeButton];
[_minimizeButton addTrackingArea:_minimizeButtonTrackingArea];
}
if (_zoomButton)
{
_zoomButtonTrackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero()
options:CPTrackingMouseEnteredAndExited | CPTrackingActiveAlways | CPTrackingInVisibleRect
owner:self
userInfo:_zoomButton];
[_zoomButton addTrackingArea:_zoomButtonTrackingArea];
}
[super updateTrackingAreas];
}
- (void)mouseEntered:(CPEvent)anEvent
{
var triggeredButton = [[anEvent trackingArea] userInfo],
state = CPThemeStateHovered;
if ([CPApp shouldMimicWindows])
state = state.and(CPThemeStateWindowsPlatform);
if (triggeredButton === _closeButton)
{
if (_isDocumentEdited)
[_closeButton setImage:[self valueForThemeAttribute:@"unsaved-image-button" inState:state]];
else
[_closeButton setImage:[self valueForThemeAttribute:@"close-image-button" inState:state]];
}
else if (triggeredButton === _minimizeButton)
{
[_minimizeButton setImage:[self valueForThemeAttribute:@"minimize-image-button" inState:state]];
}
else if (triggeredButton === _zoomButton)
{
[_zoomButton setImage:[self valueForThemeAttribute:@"zoom-image-button" inState:state]];
}
}
- (void)mouseExited:(CPEvent)anEvent
{
var triggeredButton = [[anEvent trackingArea] userInfo];
if (triggeredButton === _closeButton)
{
if (_isDocumentEdited)
[_closeButton setImage:[self currentValueForThemeAttribute:@"unsaved-image-button"]];
else
[_closeButton setImage:[self currentValueForThemeAttribute:@"close-image-button"]];
}
else if (triggeredButton === _minimizeButton)
{
[_minimizeButton setImage:[self currentValueForThemeAttribute:@"minimize-image-button"]];
}
else if (triggeredButton === _zoomButton)
{
[_zoomButton setImage:[self currentValueForThemeAttribute:@"zoom-image-button"]];
}
}
@end
+13 -4
View File
@@ -27,6 +27,9 @@
@implementation _CPTitleableWindowView : _CPWindowView
{
CPTextField _titleField;
int _minimumTitleFieldSize;
int _titleBarHeight;
int _titleMargin;
}
+ (int)titleBarHeight
@@ -47,7 +50,7 @@
+ (CGRect)frameRectForContentRect:(CGRect)aContentRect
{
var frameRect = CGRectMakeCopy(aContentRect),
var frameRect = [super frameRectForContentRect:aContentRect],
titleBarHeight = [self titleBarHeight];
frameRect.origin.y -= titleBarHeight;
@@ -62,15 +65,19 @@
if (self)
{
// We cache some values for optimization
_titleBarHeight = [[self class] titleBarHeight];
_titleMargin = [self currentValueForThemeAttribute:@"title-margin"];
_titleField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
[_titleField setHitTests:NO];
[_titleField setStringValue:@"Untitled"];
[_titleField sizeToFit];
[_titleField setAutoresizingMask:CPViewWidthSizable];
[_titleField setStringValue:@""];
[self setTitle:@""];
[_titleField setFrame:CGRectMake(20.0, 3.0, CGRectGetWidth([self bounds]) - 40.0, CGRectGetHeight([_titleField frame]))];
[_titleField setFrame:CGRectMake(_titleMargin, 3.0, CGRectGetWidth([self bounds]) - 2 * _titleMargin, CGRectGetHeight([_titleField frame]))];
[self addSubview:_titleField];
@@ -83,6 +90,8 @@
- (void)setTitle:(CPString)aTitle
{
[_titleField setStringValue:aTitle];
_minimumTitleFieldSize = [_titleField _minimumFrameSize].width;
}
- (void)tile
@@ -95,7 +104,7 @@
// The vertical alignment of the title is set by the theme, so just give it all available space. By default
// the title will vertically centre within.
[_titleField setFrame:CGRectMake(20.0, 0, width - 40.0, [[self class] titleBarHeight])];
[_titleField setFrame:CGRectMake(_titleMargin, 0, width - 2 * _titleMargin, _titleBarHeight)];
}
- (void)layoutSubviews

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