From 6ce85bae7e260a8ac9207070bebc2b7f715ba650 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Mon, 23 Jun 2025 07:21:08 +0200 Subject: [PATCH 01/40] fixed: Keyboard-event handling is based on the deprecated charCode --- AppKit/CPTextView/CPTextView.j | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index f51a0da11..56a4296f5 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2701,11 +2701,12 @@ var _CPCopyPlaceholder = '-'; { _CPNativeInputFieldKeyUpCalled = YES; - // filter out the shift-up, cursor keys and friends used to access the deadkeys - // fixme: e.which is depreciated(?) -> find a better way to identify the modifier-keyups - if (e.which < 27 || e.which == 91 || e.which == 93) // include apple command keys + // Filter out non-printable keys like modifiers, cursor keys, etc. + // A key with a name longer than one character is typically a non-printable control key. + // We exclude 'Dead' and 'Process' which are handled as part of dead-key composition. + if (e.key.length > 1 && e.key !== 'Dead' && e.key !== 'Process') { - if (e.which == 13) + if (e.key === 'Enter') _CPNativeInputField.innerHTML = ''; if (_CPNativeInputField.innerHTML.length == 0 || _CPNativeInputField.innerHTML.length > 2) // backspace @@ -2752,7 +2753,7 @@ var _CPCopyPlaceholder = '-'; var currentFirstResponder = [[CPApp keyWindow] firstResponder]; // webkit-browsers: cursor keys do not emit keypressed and would otherwise activate deadkey mode - if (!CPBrowserIsEngine(CPGeckoBrowserEngine) && e.which >= 37 && e.which <= 40) + if (!CPBrowserIsEngine(CPGeckoBrowserEngine) && e.key.startsWith('Arrow')) _CPNativeInputFieldKeyPressedCalled = YES; if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) From 36d4bd32b55cf116b7099f835fc8d11f20b4584e Mon Sep 17 00:00:00 2001 From: daboe01 Date: Mon, 23 Jun 2025 08:58:12 +0200 Subject: [PATCH 02/40] Update CPPlatformWindow+DOM.j --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 257 ++++++++------------- 1 file changed, 97 insertions(+), 160 deletions(-) diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index 3cde84b3e..3fa5546bb 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -20,93 +20,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ - -/* - * THIS DOCUMENTATION STOLEN DIRECTLY FROM GOOGLE CLOSURE (licensed under Apache 2) - * - * Different web browsers have very different keyboard event handling. Most - * importantly is that only certain browsers repeat keydown events: - * IE, Opera, FF/Win32, and Safari 3 repeat keydown events. - * FF/Mac and Safari 2 do not. - * - * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit - * decided that they should try to match IE's key handling behavior. - * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the - * Safari 2 behavior. - * - * Firefox, Safari, Opera prevent on keypress - * - * IE prevents on keydown - * - * Firefox does not fire keypress for shift, ctrl, alt - * Firefox does fire keydown for shift, ctrl, alt, meta - * Firefox does not repeat keydown for shift, ctrl, alt, meta - * - * Firefox does not fire keypress for up and down in an input - * - * Opera fires keypress for shift, ctrl, alt, meta - * Opera does not repeat keypress for shift, ctrl, alt, meta - * - * Safari 2 and 3 do not fire keypress for shift, ctrl, alt - * Safari 2 does not fire keydown for shift, ctrl, alt - * Safari 3 *does* fire keydown for shift, ctrl, alt - * - * IE provides the keycode for keyup/down events and the charcode (in the - * keycode field) for keypress. - * - * Mozilla provides the keycode for keyup/down and the charcode for keypress - * unless it's a non text modifying key in which case the keycode is provided. - * - * Safari 3 provides the keycode and charcode for all events. - * - * Opera provides the keycode for keyup/down event and either the charcode or - * the keycode (in the keycode field) for keypress events. - * - * Firefox x11 doesn't fire keydown events if a another key is already held down - * until the first key is released. This can cause a key event to be fired with - * a keyCode for the first key and a charCode for the second key. - * - * Safari 2 in keypress (not supported) - * - * charCode keyCode which - * ENTER: 13 13 13 - * F1: 63236 63236 63236 - * F8: 63243 63243 63243 - * ... - * p: 112 112 112 - * P: 80 80 80 - * - * Firefox, keypress: - * - * charCode keyCode which - * ENTER: 0 13 13 - * F1: 0 112 0 - * F8: 0 119 0 - * ... - * p: 112 0 112 - * P: 80 0 80 - * - * Opera, Mac+Win32, keypress: - * - * charCode keyCode which - * ENTER: undefined 13 13 - * F1: undefined 112 0 - * F8: undefined 119 0 - * ... - * p: undefined 112 112 - * P: undefined 80 80 - * - * IE7, keydown - * - * charCode keyCode which - * ENTER: undefined 13 undefined - * F1: undefined 112 undefined - * F8: undefined 119 undefined - * ... - * p: undefined 80 undefined - * P: undefined 80 undefined - */ - @import @import @import @@ -152,6 +65,29 @@ var KeyCodesToPrevent = {}, }, KeyCodesToUnicodeMap = {}; +// New map from event.key to our internal Unicode function keys. +// This is more reliable than mapping from keyCode. +var KeyToUnicodeMapFromKey = { + "Backspace": CPDeleteCharacter, + "Delete": CPDeleteFunctionKey, + "Tab": CPTabCharacter, + "Enter": CPCarriageReturnCharacter, + "Escape": CPEscapeFunctionKey, + "PageUp": CPPageUpFunctionKey, + "PageDown": CPPageDownFunctionKey, + "ArrowLeft": CPLeftArrowFunctionKey, + "ArrowUp": CPUpArrowFunctionKey, + "ArrowRight": CPRightArrowFunctionKey, + "ArrowDown": CPDownArrowFunctionKey, + "Home": CPHomeFunctionKey, + "End": CPEndFunctionKey +}; + +// Map F-keys dynamically. Assumes CP_F1_KEY (0xF704) to CP_F12_KEY (0xF70F) are defined elsewhere. +for (var i = 1; i <= 12; i++) + KeyToUnicodeMapFromKey['F' + i] = 0xF703 + i; + + KeyCodesToPrevent[CPKeyCodes.A] = YES; KeyCodesToAllow[CPKeyCodes.F1] = YES; @@ -691,6 +627,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio timestamp = [CPEvent currentTimestamp], sourceElement = aDOMEvent.target || aDOMEvent.srcElement, windowNumber = [[CPApp keyWindow] windowNumber], + eventKey = aDOMEvent.key, modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) | (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) | (aDOMEvent.altKey ? CPAlternateKeyMask : 0) | @@ -698,22 +635,14 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio (_capsLockActive ? CPAlphaShiftKeyMask : 0); // With a few exceptions, all key events are blocked from propagating to - // the browser. Here the following exceptions are being allowed: - // - // - All keys pressed along with a ctrl or cmd key _unless_ they are in - // one of the two blacklists. - // - Any key listed in the whitelist. - // - // The ctrl/cmd keys are used for browser hotkeys as are the keys listed in - // the whitelist (F1-F12 at the time of writing). - // - // If a key is listed in both the blacklist and whitelist, the blacklist is - // checked first. The key will be blocked from propagating in that case. - + // the browser. We check against blacklists and whitelists. StopDOMEventPropagation = YES; + // Use event.key for checking character keys. This is more reliable across keyboard layouts. + var charToTest = (eventKey && eventKey.length === 1) ? eventKey.toLowerCase() : null; + // Make sure it is not in the blacklists. - if (!(CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode])) + if (!((charToTest && CharacterKeysToPrevent[charToTest]) || KeyCodesToPrevent[aDOMEvent.keyCode])) { // It is not in the blacklist, let it through if the ctrl/cmd key is // also down or it's in the whitelist. @@ -727,7 +656,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio switch (aDOMEvent.type) { case "keydown": - // Grab and store the keycode now since it is correct and consistent at this point. + // Grab and store the keycode for compatibility with other parts of the system. if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap) _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode]; else @@ -735,62 +664,68 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio var characters; - // Handle key codes for which String.fromCharCode won't work. - // Refs #1036: In Internet Explorer, both 'which' and 'charCode' are undefined for special keys. - if (aDOMEvent.which === 0 || aDOMEvent.charCode === 0 || (aDOMEvent.which === undefined && aDOMEvent.charCode === undefined)) - characters = KeyCodesToUnicodeMap[_keyCode]; - - // The problem with keyCode is that this property refers to keys on the keyboard and not to characters - // This is why String.fromCharCode does not always work in more recent versions of Firefox - // E.g. pressing a '#' on a German keyboard gives you a charCode of 163, which refers to '£' and not '#' - // The property key works fine, though. From there we can get the actual character more robustly. - // Therefore we prefer key over keyCode whenever possible - - if (!characters) - characters = (aDOMEvent.key && aDOMEvent.key.length == 1) ? aDOMEvent.key.toLowerCase() : String.fromCharCode(_keyCode).toLowerCase(); - - overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters; - - // check for caps lock state - if (_keyCode === CPKeyCodes.CAPS_LOCK) + // Determine characters using modern event.key property first. + if (eventKey) { - _capsLockActive = YES; - - // Make sure the caps lock flag is set in modifierFlags - modifierFlags |= CPAlphaShiftKeyMask; + if (eventKey.length === 1) + characters = eventKey; // Printable character, already correctly cased. + else + characters = String.fromCharCode(KeyToUnicodeMapFromKey[eventKey]); // Special key. } - if ([ModifierKeyCodes containsObject:_keyCode]) + // Fallback for older browsers or unhandled keys. + if (!characters) { - // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. + if (aDOMEvent.charCode === 0) // Keydown for non-printable keys have charCode === 0 + characters = KeyCodesToUnicodeMap[_keyCode]; + if (!characters) + characters = String.fromCharCode(_keyCode); + } + + // Set characters for the event. event.key is already cased correctly for printable keys. + if (eventKey && eventKey.length === 1) + { + overrideCharacters = eventKey; + charactersIgnoringModifiers = eventKey.toLowerCase(); + } + else + { + charactersIgnoringModifiers = (characters || "").toLowerCase(); + overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? (characters || "").toUpperCase() : charactersIgnoringModifiers; + } + + // Handle caps lock state. + if (eventKey === "CapsLock") + { + // This is a simplification; getModifierState("CapsLock") would be ideal but is not used here to avoid further changes. + _capsLockActive = !_capsLockActive; + modifierFlags = (modifierFlags & ~CPAlphaShiftKeyMask) | (_capsLockActive ? CPAlphaShiftKeyMask : 0); + } + + var isModifier = (eventKey === "Control" || eventKey === "Shift" || eventKey === "Alt" || eventKey === "Meta" || eventKey === "CapsLock"); + if (isModifier) + { + // A modifier key will never fire keypress. We fire a CPFlagsChanged event and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; - break; } else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) { - //we are simply going to skip all keypress events that use cmd/ctrl key - //this lets us be consistent in all browsers and send on the keydown - //which means we can cancel the event early enough, but only if sendEvent needs to + // Let Cmd/Ctrl combinations be sent on keydown to allow for early cancellation. } - else if (CPKeyCodes.firesKeyPressEvent(_keyCode, aDOMEvent.key, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) + else if (CPKeyCodes.firesKeyPressEvent(_keyCode, eventKey, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) { - // this branch is taken by events which fire keydown, keypress, and keyup. - // this is the only time we'll ALLOW character keys to propagate (needed for text fields) + // This branch is for keys that fire a `keypress` event. + // We allow propagation to let the browser handle input in text fields. StopDOMEventPropagation = NO; break; } - else - { - //this branch is taken by "remedial" key events - // In this state we continue to keypress and send the CPEvent - } case "keypress": - // we unconditionally break on keypress events with modifiers, - // because we forced the event to be sent on the keydown + // We unconditionally break on keypress events with modifiers, + // as we forced the event to be sent on the keydown. if (aDOMEvent.type === "keypress" && (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) break; @@ -801,15 +736,20 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = keyCode; _charCodes[keyCode] = charCode; + // Use the character determined during keydown if available. var characters = overrideCharacters; - // Is this a special key? - if (!characters && (aDOMEvent.which === 0 || aDOMEvent.charCode === 0)) - characters = KeyCodesToUnicodeMap[charCode]; - if (!characters) - characters = String.fromCharCode(charCode); + { + // Fallback for printable keys on keypress. + if (eventKey && eventKey.length === 1) + characters = eventKey; + else if (charCode !== 0) + characters = String.fromCharCode(charCode); + else + characters = KeyCodesToUnicodeMap[keyCode] || ""; + } - charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift. + charactersIgnoringModifiers = characters.toLowerCase(); // Safari won't send proper capitalization during cmd-key events if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive)) @@ -818,7 +758,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode]; - break; case "keyup": @@ -829,26 +768,26 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = -1; _charCodes[keyCode] = nil; - // check for caps lock state - if (keyCode === CPKeyCodes.CAPS_LOCK) + if (eventKey === "CapsLock") { - _capsLockActive = NO; - - // Make sure the caps lock flag is cleared in modifierFlags - modifierFlags &= ~CPAlphaShiftKeyMask; + // The state was handled on keydown. Nothing to do for state change on keyup for a toggle key. } - if ([ModifierKeyCodes containsObject:keyCode]) + var isModifier = (eventKey === "Control" || eventKey === "Shift" || eventKey === "Alt" || eventKey === "Meta" || eventKey === "CapsLock"); + if (isModifier) { - // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; - + characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode]; break; } - var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode); + var characters; + if (eventKey && eventKey.length === 1) + characters = eventKey; + else + characters = KeyCodesToUnicodeMap[keyCode] || String.fromCharCode(charCode) || ""; + charactersIgnoringModifiers = characters.toLowerCase(); if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive) @@ -857,7 +796,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags timestamp: timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode]; - break; } @@ -869,7 +807,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (event && ![_platformPasteboard windowShouldSuppressKeyEvent]) { [CPApp sendEvent:event]; - [_platformPasteboard windowDidSendKeyEvent:event]; } @@ -1226,7 +1163,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio return; } } - + // cancel other touch cases preventively if (aDOMEvent.preventDefault) From 751e1d27a637c4f5b8bcdae967e2b6e09adef2c5 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Mon, 23 Jun 2025 09:02:28 +0200 Subject: [PATCH 03/40] Update CPPlatformWindow+DOMKeys.j --- .../Platform/DOM/CPPlatformWindow+DOMKeys.j | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j b/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j index a24d241f3..a76bed207 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOMKeys.j @@ -132,38 +132,36 @@ CPKeyCodes = { /*! * Returns true if the key fires a keypress event in the current browser. + * The keypress event is deprecated, but this function helps manage legacy + * event handling by predicting its behavior. * - * Accoridng to MSDN [1] IE only fires keypress events for the following keys: - * - Letters: A - Z (uppercase and lowercase) - * - Numerals: 0 - 9 - * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~ - * - System: ESC, SPACEBAR, ENTER - * - * That's not entirely correct though, for instance there's no distinction - * between upper and lower case letters. - * - * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx) - * - * Safari is similar to IE, but does not fire keypress for ESC. - * - * Additionally, IE6 does not fire keydown or keypress events for letters when - * the control or alt keys are held down and the shift key is not. IE7 does - * fire keydown in these cases, though, but not keypress. - * - * @param keyCode A key code. - * @param opt_heldKeyCode Key code of a currently-held key. - * @param opt_shiftKey Whether the shift key is held down. - * @param opt_ctrlKey Whether the control key is held down. - * @param opt_altKey Whether the alt key is held down. - * @return Returns YES if it's a key that fires a keypress event. + * @param {number} keyCode A key code. + * @param {string} key The `key` property from the keyboard event. + * @param {number} opt_heldKeyCode Key code of a currently-held key. + * @param {boolean} opt_shiftKey Whether the shift key is held down. + * @param {boolean} opt_ctrlKey Whether the control key is held down. + * @param {boolean} opt_altKey Whether the alt key is held down. + * @return {boolean} Returns YES if it's a key that fires a keypress event. */ CPKeyCodes.firesKeyPressEvent = function(keyCode, key, opt_heldKeyCode, opt_shiftKey, opt_ctrlKey, opt_altKey) { - // The property key from event is one character wide in case of 'regular' keys (as opposed e.g. to arrow keys) - // Regular keys all fire the keypress event + // Modern approach: Use event.key if available, as it is the most reliable standard. + if (key) + { + // Any key that produces a single, printable character fires a keypress event. + if (key.length === 1) + return true; - if (key && key.length == 1) - return true; + // "Enter" is a special non-printable key that historically fires keypress for compatibility. + if (key === "Enter") + return true; + + // For all other non-printable keys (e.g., "ArrowLeft", "Escape", "F1"), + // modern browsers do not fire a keypress event. + return false; + } + + // --- Legacy Fallback Logic (for browsers that don't support event.key) --- if (!CPFeatureIsCompatible(CPJavaScriptRemedialKeySupport)) return true; @@ -196,9 +194,11 @@ CPKeyCodes.firesKeyPressEvent = function(keyCode, key, opt_heldKeyCode, opt_shif /*! * Test for whether or not a given keyCode represents a character key. + * NOTE: This is a legacy function for browsers that don't support `event.key`. + * It is unreliable because `keyCode` represents a physical key, not the character produced. * - * @param keyCode A key code. - * @return Returns YES if the keyCode is a character key. + * @param {number} keyCode A key code. + * @return {boolean} Returns YES if the keyCode is a character key. */ CPKeyCodes.isCharacterKey = function(keyCode) { From c814b5daed98459d0a0b4c4fb73205b5c8e5f7e2 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 1 Jul 2025 19:36:02 +0200 Subject: [PATCH 04/40] fixed: arrow keys did not work --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 264 ++++++++++++--------- dist/cappuccino/bin/flatten | 2 +- dist/cappuccino/bin/fontinfo | Bin 69888 -> 70736 bytes dist/cappuccino/bin/imagesize | Bin 69536 -> 0 bytes dist/cappuccino/bin/objj2objcskeleton | 22 +- 5 files changed, 175 insertions(+), 113 deletions(-) delete mode 100755 dist/cappuccino/bin/imagesize diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index 3fa5546bb..fe17ec2c8 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -20,6 +20,92 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +/* + * THIS DOCUMENTATION STOLEN DIRECTLY FROM GOOGLE CLOSURE (licensed under Apache 2) + * + * Different web browsers have very different keyboard event handling. Most + * importantly is that only certain browsers repeat keydown events: + * IE, Opera, FF/Win32, and Safari 3 repeat keydown events. + * FF/Mac and Safari 2 do not. + * + * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit + * decided that they should try to match IE's key handling behavior. + * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the + * Safari 2 behavior. + * + * Firefox, Safari, Opera prevent on keypress + * + * IE prevents on keydown + * + * Firefox does not fire keypress for shift, ctrl, alt + * Firefox does fire keydown for shift, ctrl, alt, meta + * Firefox does not repeat keydown for shift, ctrl, alt, meta + * + * Firefox does not fire keypress for up and down in an input + * + * Opera fires keypress for shift, ctrl, alt, meta + * Opera does not repeat keypress for shift, ctrl, alt, meta + * + * Safari 2 and 3 do not fire keypress for shift, ctrl, alt + * Safari 2 does not fire keydown for shift, ctrl, alt + * Safari 3 *does* fire keydown for shift, ctrl, alt + * + * IE provides the keycode for keyup/down events and the charcode (in the + * keycode field) for keypress. + * + * Mozilla provides the keycode for keyup/down and the charcode for keypress + * unless it's a non text modifying key in which case the keycode is provided. + * + * Safari 3 provides the keycode and charcode for all events. + * + * Opera provides the keycode for keyup/down event and either the charcode or + * the keycode (in the keycode field) for keypress events. + * + * Firefox x11 doesn't fire keydown events if a another key is already held down + * until the first key is released. This can cause a key event to be fired with + * a keyCode for the first key and a charCode for the second key. + * + * Safari 2 in keypress (not supported) + * + * charCode keyCode which + * ENTER: 13 13 13 + * F1: 63236 63236 63236 + * F8: 63243 63243 63243 + * ... + * p: 112 112 112 + * P: 80 80 80 + * + * Firefox, keypress: + * + * charCode keyCode which + * ENTER: 0 13 13 + * F1: 0 112 0 + * F8: 0 119 0 + * ... + * p: 112 0 112 + * P: 80 0 80 + * + * Opera, Mac+Win32, keypress: + * + * charCode keyCode which + * ENTER: undefined 13 13 + * F1: undefined 112 0 + * F8: undefined 119 0 + * ... + * p: undefined 112 112 + * P: undefined 80 80 + * + * IE7, keydown + * + * charCode keyCode which + * ENTER: undefined 13 undefined + * F1: undefined 112 undefined + * F8: undefined 119 undefined + * ... + * p: undefined 80 undefined + * P: undefined 80 undefined + */ + @import @import @import @@ -65,29 +151,6 @@ var KeyCodesToPrevent = {}, }, KeyCodesToUnicodeMap = {}; -// New map from event.key to our internal Unicode function keys. -// This is more reliable than mapping from keyCode. -var KeyToUnicodeMapFromKey = { - "Backspace": CPDeleteCharacter, - "Delete": CPDeleteFunctionKey, - "Tab": CPTabCharacter, - "Enter": CPCarriageReturnCharacter, - "Escape": CPEscapeFunctionKey, - "PageUp": CPPageUpFunctionKey, - "PageDown": CPPageDownFunctionKey, - "ArrowLeft": CPLeftArrowFunctionKey, - "ArrowUp": CPUpArrowFunctionKey, - "ArrowRight": CPRightArrowFunctionKey, - "ArrowDown": CPDownArrowFunctionKey, - "Home": CPHomeFunctionKey, - "End": CPEndFunctionKey -}; - -// Map F-keys dynamically. Assumes CP_F1_KEY (0xF704) to CP_F12_KEY (0xF70F) are defined elsewhere. -for (var i = 1; i <= 12; i++) - KeyToUnicodeMapFromKey['F' + i] = 0xF703 + i; - - KeyCodesToPrevent[CPKeyCodes.A] = YES; KeyCodesToAllow[CPKeyCodes.F1] = YES; @@ -627,7 +690,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio timestamp = [CPEvent currentTimestamp], sourceElement = aDOMEvent.target || aDOMEvent.srcElement, windowNumber = [[CPApp keyWindow] windowNumber], - eventKey = aDOMEvent.key, modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) | (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) | (aDOMEvent.altKey ? CPAlternateKeyMask : 0) | @@ -635,14 +697,22 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio (_capsLockActive ? CPAlphaShiftKeyMask : 0); // With a few exceptions, all key events are blocked from propagating to - // the browser. We check against blacklists and whitelists. + // the browser. Here the following exceptions are being allowed: + // + // - All keys pressed along with a ctrl or cmd key _unless_ they are in + // one of the two blacklists. + // - Any key listed in the whitelist. + // + // The ctrl/cmd keys are used for browser hotkeys as are the keys listed in + // the whitelist (F1-F12 at the time of writing). + // + // If a key is listed in both the blacklist and whitelist, the blacklist is + // checked first. The key will be blocked from propagating in that case. + StopDOMEventPropagation = YES; - // Use event.key for checking character keys. This is more reliable across keyboard layouts. - var charToTest = (eventKey && eventKey.length === 1) ? eventKey.toLowerCase() : null; - // Make sure it is not in the blacklists. - if (!((charToTest && CharacterKeysToPrevent[charToTest]) || KeyCodesToPrevent[aDOMEvent.keyCode])) + if (!(CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode])) { // It is not in the blacklist, let it through if the ctrl/cmd key is // also down or it's in the whitelist. @@ -656,7 +726,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio switch (aDOMEvent.type) { case "keydown": - // Grab and store the keycode for compatibility with other parts of the system. + // Grab and store the keycode now since it is correct and consistent at this point. if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap) _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode]; else @@ -664,68 +734,62 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio var characters; - // Determine characters using modern event.key property first. - if (eventKey) - { - if (eventKey.length === 1) - characters = eventKey; // Printable character, already correctly cased. - else - characters = String.fromCharCode(KeyToUnicodeMapFromKey[eventKey]); // Special key. - } + // Handle key codes for which String.fromCharCode won't work. + // Refs #1036: In Internet Explorer, both 'which' and 'charCode' are undefined for special keys. + if (aDOMEvent.which === 0 || aDOMEvent.charCode === 0 || (aDOMEvent.which === undefined && aDOMEvent.charCode === undefined)) + characters = KeyCodesToUnicodeMap[_keyCode]; + + // The problem with keyCode is that this property refers to keys on the keyboard and not to characters + // This is why String.fromCharCode does not always work in more recent versions of Firefox + // E.g. pressing a '#' on a German keyboard gives you a charCode of 163, which refers to '£' and not '#' + // The property key works fine, though. From there we can get the actual character more robustly. + // Therefore we prefer key over keyCode whenever possible - // Fallback for older browsers or unhandled keys. if (!characters) + characters = (aDOMEvent.key && aDOMEvent.key.length == 1) ? aDOMEvent.key.toLowerCase() : String.fromCharCode(_keyCode).toLowerCase(); + + overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters; + + // check for caps lock state + if (_keyCode === CPKeyCodes.CAPS_LOCK) { - if (aDOMEvent.charCode === 0) // Keydown for non-printable keys have charCode === 0 - characters = KeyCodesToUnicodeMap[_keyCode]; - if (!characters) - characters = String.fromCharCode(_keyCode); + _capsLockActive = YES; + + // Make sure the caps lock flag is set in modifierFlags + modifierFlags |= CPAlphaShiftKeyMask; } - // Set characters for the event. event.key is already cased correctly for printable keys. - if (eventKey && eventKey.length === 1) + if ([ModifierKeyCodes containsObject:_keyCode]) { - overrideCharacters = eventKey; - charactersIgnoringModifiers = eventKey.toLowerCase(); - } - else - { - charactersIgnoringModifiers = (characters || "").toLowerCase(); - overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? (characters || "").toUpperCase() : charactersIgnoringModifiers; - } - - // Handle caps lock state. - if (eventKey === "CapsLock") - { - // This is a simplification; getModifierState("CapsLock") would be ideal but is not used here to avoid further changes. - _capsLockActive = !_capsLockActive; - modifierFlags = (modifierFlags & ~CPAlphaShiftKeyMask) | (_capsLockActive ? CPAlphaShiftKeyMask : 0); - } - - var isModifier = (eventKey === "Control" || eventKey === "Shift" || eventKey === "Alt" || eventKey === "Meta" || eventKey === "CapsLock"); - if (isModifier) - { - // A modifier key will never fire keypress. We fire a CPFlagsChanged event and break. + // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; + break; } else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) { - // Let Cmd/Ctrl combinations be sent on keydown to allow for early cancellation. + //we are simply going to skip all keypress events that use cmd/ctrl key + //this lets us be consistent in all browsers and send on the keydown + //which means we can cancel the event early enough, but only if sendEvent needs to } - else if (CPKeyCodes.firesKeyPressEvent(_keyCode, eventKey, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) + else if (CPKeyCodes.firesKeyPressEvent(_keyCode, aDOMEvent.key, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) { - // This branch is for keys that fire a `keypress` event. - // We allow propagation to let the browser handle input in text fields. + // this branch is taken by events which fire keydown, keypress, and keyup. + // this is the only time we'll ALLOW character keys to propagate (needed for text fields) StopDOMEventPropagation = NO; break; } + else + { + //this branch is taken by "remedial" key events + // In this state we continue to keypress and send the CPEvent + } case "keypress": - // We unconditionally break on keypress events with modifiers, - // as we forced the event to be sent on the keydown. + // we unconditionally break on keypress events with modifiers, + // because we forced the event to be sent on the keydown if (aDOMEvent.type === "keypress" && (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) break; @@ -736,20 +800,15 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = keyCode; _charCodes[keyCode] = charCode; - // Use the character determined during keydown if available. var characters = overrideCharacters; - if (!characters) - { - // Fallback for printable keys on keypress. - if (eventKey && eventKey.length === 1) - characters = eventKey; - else if (charCode !== 0) - characters = String.fromCharCode(charCode); - else - characters = KeyCodesToUnicodeMap[keyCode] || ""; - } + // Is this a special key? + if (!characters && (aDOMEvent.which === 0 || aDOMEvent.charCode === 0)) + characters = KeyCodesToUnicodeMap[charCode]; - charactersIgnoringModifiers = characters.toLowerCase(); + if (!characters) + characters = String.fromCharCode(charCode); + + charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift. // Safari won't send proper capitalization during cmd-key events if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive)) @@ -758,6 +817,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode]; + break; case "keyup": @@ -768,26 +828,26 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = -1; _charCodes[keyCode] = nil; - if (eventKey === "CapsLock") + // check for caps lock state + if (keyCode === CPKeyCodes.CAPS_LOCK) { - // The state was handled on keydown. Nothing to do for state change on keyup for a toggle key. + _capsLockActive = NO; + + // Make sure the caps lock flag is cleared in modifierFlags + modifierFlags &= ~CPAlphaShiftKeyMask; } - var isModifier = (eventKey === "Control" || eventKey === "Shift" || eventKey === "Alt" || eventKey === "Meta" || eventKey === "CapsLock"); - if (isModifier) + if ([ModifierKeyCodes containsObject:keyCode]) { + // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode]; + characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; + break; } - var characters; - if (eventKey && eventKey.length === 1) - characters = eventKey; - else - characters = KeyCodesToUnicodeMap[keyCode] || String.fromCharCode(charCode) || ""; - + var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode); charactersIgnoringModifiers = characters.toLowerCase(); if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive) @@ -796,6 +856,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags timestamp: timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode]; + break; } @@ -807,6 +868,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (event && ![_platformPasteboard windowShouldSuppressKeyEvent]) { [CPApp sendEvent:event]; + [_platformPasteboard windowDidSendKeyEvent:event]; } @@ -1163,7 +1225,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio return; } } - + // cancel other touch cases preventively if (aDOMEvent.preventDefault) @@ -1846,13 +1908,3 @@ function CPWindowObjectList() return windowObjects; } - -function CPWindowList() -{ - var windowObjectList = CPWindowObjectList(); - - return [windowObjectList arrayByApplyingBlock:function(windowObject) - { - return [windowObject windowNumber]; - }]; -} diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten index 1df033252..80b9ecf8e 100755 --- a/dist/cappuccino/bin/flatten +++ b/dist/cappuccino/bin/flatten @@ -233,7 +233,7 @@ ObjectiveJFlattener.prototype.serializeFunctions = function() { var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK var relative = this.rootPath.relative(path).toString(); - this.functionsBuffer.push("ObjectiveJ.StaticResource._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); + this.functionsBuffer.push("ObjectiveJ.FileExecutable._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); } var bundle = this.context.global.CFBundle.bundleContainingURL(path); diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo index f1ff06158a2525d34541aff240713c0067bd8633..1a91ba1c8040bbcfe787719cb7e44c0e7578d180 100755 GIT binary patch literal 70736 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 delta 3394 zcmZ`*3s6+o89w`DsqFGx+h670NB67 z0BER_-s?-eqvet#M>G=>)tO2(O5#IV%?yIz_H@(>o1IN<&UUZ~LaljSEpq}>J}u!b zVr7xn4nNt^c`Qus7M+>LN3Ns}%e^# zxB<8vC=Ez+f{-lWvAl8B06|2vfltV9N$Vy>N(-^UM2r-KiqFN|))av-q#`Yxy;N9bap; zbl)tk_TTZ}vjob6WVpw18Q|(jmB3tzoS61`(Bi_#J{o&92UPh z1kMEhI4Gn~<~Ls&Pedbs907*Sm!KzryabTV!B&|iP}1kEA|#@JLkYP4ll)U(?U-NX zAFB>59U7~hoVp=??dgX2CkHG6b144R477I-%gz1=@l%E%bP;IYh+YSQ<68b0W_{yhNVO;gS%)DY9|CFu^Cw_= zYy98vk0JR4z<0VfKmeuBn;21b1(;^GjCA5h9;5Oo%! z21?$Q5G{eau*LtS6tcp9AL}t6+`C0LOp)vbxba+Y#jpKaRba;@D5&``bSwIxt2;VA z7m6Rkl)~s0(;9<9JrV}$1}Ip05ODcBLvl+{l?1>wLG3sMukeR!;;Ta7kO9ZS>T&*{ z{zEzOLM8vvr;O+WxQhNk^R7t8bcndWOpKTit&nIF3W1UdG-GYnqaG}e!0VceCM^D; zqR<$gLFfq+Os>atD9(nKXg?6aV+ z31%YzZ;ceFP(~ny?u;8q%gFHMF*P z*19~KSK6ALWp3At&N8}5o0%b<<}Q0}yUpcs6Pw%aY;nLP>2T5{?Mzys&7!~17U$a9 z+Q4r0w7Xgw%Sc`AGo_MD95AkhP+wdpy{^rjho{UUm=x(7xTUe73@bt6;B-ll&Mk6b zok~V|ozWY{djeYYs%w5>{$De{>i_urm>(8vvT=m;M>tsp975~7 z#q3f}VLXR%BjZaMk6zvlOz&cR7vq%i1C0NM@!vCkj`5p}$4H6gkT{lM4&(C}UyNKl zEc;jK_Gyz6^9p^6wAC}orWEl_k~sS8sTu0k?y#(fqfMs_THkqCezBqDpP?MD&)CBW z&n;VCDOA?*R;!RNthBNPoEn_WNf&anyU_~EP1-duOuVAoolQ=g+evy-I7e5LL;9bp zcZpl2N6!J=GqAsS4{VV74v!Nz8S!0>rjZSKJUTtb6L%kGxNjgw(UZ#2PX;pOpvDj7 zZEQD$6lpz#xJe2p*gUNbq=&dQ(fn*pO|5pcB>cc4xLRBuG^a()t!*Bm#km!21^{Y< z?dt(3`fwo4I~_hNsom+2_*U2)IY80>&K)4@bVU41z^&d{@~((mC%WL|DnN|GIIl{@Jw!dTys4T{x9RzodJaZ z4tRN|17!j|7So@7$|alo*un|T`0c_^6Rs%KW{b_^Bnv#vZFED}sE~LjJrZ7`>?}12 z^j0`ibN5PI2(t0gw9_+V&CZvc1KBrsh$+yI+k6k&Di|x%8J~FIq82mvzXg|eCJcY(|mto zMx}1;LO?SZmK-?9EuXVz>5=5^X}>L7k@!{fg5Ehp)(=xh-#lb|+cpY89Bmol413={ z-q`FoHlKWPG2x=~EpMW(JNwJmf~muwCO=3yz5WY&c7)^fYphqZ_NR{CzQ7&xoUOR< zAJ@MQUj86?*7l0)Tdq}e?|z(>fA3J|=hi;mdw(7(Fm6j396lJl*4(&vgpTzG?tKj) zn+%nU^QL|O$r072fIIj3og;U;LIdSB8QV|n`;}GwcKVJL7_=y2daz~N;7i#ZKlc9g ziFc;uy>z+dxtB-kDrb~^yD_)tRtD4(7aR|Ea`(y$F95qo>p%S?-EgKtQCwuCBWGsE Q%I;p7hEba639vimze2ENzW@LL diff --git a/dist/cappuccino/bin/imagesize b/dist/cappuccino/bin/imagesize deleted file mode 100755 index 5d67a1ce0a9a1e20577f82ccdfe0d3b3e94143b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69536 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 Z009sH0T2KI5C8!X009sHfo}nU{{v@rhV}pe diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton index b7972dbb3..51ecab38d 100755 --- a/dist/cappuccino/bin/objj2objcskeleton +++ b/dist/cappuccino/bin/objj2objcskeleton @@ -4,14 +4,11 @@ var fs = require("fs"), acorn = require("objj-parser"), - walk = require("objj-parser/util/walk"), + walk = require("acorn-walk"), stream = ObjectiveJ.term; - debugger; - function main(args) { - debugger; args.shift(); if (args.length < 1) @@ -40,6 +37,8 @@ function raise(pos, message) throw syntaxError; } +function ignore(_node, _st, _c) {} + var errors = [], xcc = walk.make( { @@ -115,7 +114,18 @@ var errors = [], else raise(node.loc.start, "Action methods must have exactly one parameter"); } - } + }, + TypeDefStatement: ignore, + ClassStatement: ignore, + MessageSendExpression: ignore, + GlobalStatement: ignore, + ProtocolDeclarationStatement: ignore, + ArrayLiteral: ignore, + Reference: ignore, + DictionaryLiteral: ignore, + Dereference: ignore, + ImportStatement: ignore, + SelectorLiteralExpression: ignore } ); @@ -154,7 +164,7 @@ function parser(args) outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), source = fs.readFileSync(sourcePath, { encoding: "utf8" }), - tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath }), + tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath, ecmaVersion: 2022 }), classesInformation = [], ObjectiveCSource = "", ObjectiveCHeader = "", From 0867e718d10df151c47a748ec4462e1ccfc2ff0b Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 1 Jul 2025 19:40:47 +0200 Subject: [PATCH 05/40] Revert "fixed: arrow keys did not work" This reverts commit c814b5daed98459d0a0b4c4fb73205b5c8e5f7e2. --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 266 +++++++++------------ dist/cappuccino/bin/flatten | 2 +- dist/cappuccino/bin/fontinfo | Bin 70736 -> 69888 bytes dist/cappuccino/bin/imagesize | Bin 0 -> 69536 bytes dist/cappuccino/bin/objj2objcskeleton | 22 +- 5 files changed, 114 insertions(+), 176 deletions(-) create mode 100755 dist/cappuccino/bin/imagesize diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index fe17ec2c8..3fa5546bb 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -20,92 +20,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -/* - * THIS DOCUMENTATION STOLEN DIRECTLY FROM GOOGLE CLOSURE (licensed under Apache 2) - * - * Different web browsers have very different keyboard event handling. Most - * importantly is that only certain browsers repeat keydown events: - * IE, Opera, FF/Win32, and Safari 3 repeat keydown events. - * FF/Mac and Safari 2 do not. - * - * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit - * decided that they should try to match IE's key handling behavior. - * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the - * Safari 2 behavior. - * - * Firefox, Safari, Opera prevent on keypress - * - * IE prevents on keydown - * - * Firefox does not fire keypress for shift, ctrl, alt - * Firefox does fire keydown for shift, ctrl, alt, meta - * Firefox does not repeat keydown for shift, ctrl, alt, meta - * - * Firefox does not fire keypress for up and down in an input - * - * Opera fires keypress for shift, ctrl, alt, meta - * Opera does not repeat keypress for shift, ctrl, alt, meta - * - * Safari 2 and 3 do not fire keypress for shift, ctrl, alt - * Safari 2 does not fire keydown for shift, ctrl, alt - * Safari 3 *does* fire keydown for shift, ctrl, alt - * - * IE provides the keycode for keyup/down events and the charcode (in the - * keycode field) for keypress. - * - * Mozilla provides the keycode for keyup/down and the charcode for keypress - * unless it's a non text modifying key in which case the keycode is provided. - * - * Safari 3 provides the keycode and charcode for all events. - * - * Opera provides the keycode for keyup/down event and either the charcode or - * the keycode (in the keycode field) for keypress events. - * - * Firefox x11 doesn't fire keydown events if a another key is already held down - * until the first key is released. This can cause a key event to be fired with - * a keyCode for the first key and a charCode for the second key. - * - * Safari 2 in keypress (not supported) - * - * charCode keyCode which - * ENTER: 13 13 13 - * F1: 63236 63236 63236 - * F8: 63243 63243 63243 - * ... - * p: 112 112 112 - * P: 80 80 80 - * - * Firefox, keypress: - * - * charCode keyCode which - * ENTER: 0 13 13 - * F1: 0 112 0 - * F8: 0 119 0 - * ... - * p: 112 0 112 - * P: 80 0 80 - * - * Opera, Mac+Win32, keypress: - * - * charCode keyCode which - * ENTER: undefined 13 13 - * F1: undefined 112 0 - * F8: undefined 119 0 - * ... - * p: undefined 112 112 - * P: undefined 80 80 - * - * IE7, keydown - * - * charCode keyCode which - * ENTER: undefined 13 undefined - * F1: undefined 112 undefined - * F8: undefined 119 undefined - * ... - * p: undefined 80 undefined - * P: undefined 80 undefined - */ - @import @import @import @@ -151,6 +65,29 @@ var KeyCodesToPrevent = {}, }, KeyCodesToUnicodeMap = {}; +// New map from event.key to our internal Unicode function keys. +// This is more reliable than mapping from keyCode. +var KeyToUnicodeMapFromKey = { + "Backspace": CPDeleteCharacter, + "Delete": CPDeleteFunctionKey, + "Tab": CPTabCharacter, + "Enter": CPCarriageReturnCharacter, + "Escape": CPEscapeFunctionKey, + "PageUp": CPPageUpFunctionKey, + "PageDown": CPPageDownFunctionKey, + "ArrowLeft": CPLeftArrowFunctionKey, + "ArrowUp": CPUpArrowFunctionKey, + "ArrowRight": CPRightArrowFunctionKey, + "ArrowDown": CPDownArrowFunctionKey, + "Home": CPHomeFunctionKey, + "End": CPEndFunctionKey +}; + +// Map F-keys dynamically. Assumes CP_F1_KEY (0xF704) to CP_F12_KEY (0xF70F) are defined elsewhere. +for (var i = 1; i <= 12; i++) + KeyToUnicodeMapFromKey['F' + i] = 0xF703 + i; + + KeyCodesToPrevent[CPKeyCodes.A] = YES; KeyCodesToAllow[CPKeyCodes.F1] = YES; @@ -690,6 +627,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio timestamp = [CPEvent currentTimestamp], sourceElement = aDOMEvent.target || aDOMEvent.srcElement, windowNumber = [[CPApp keyWindow] windowNumber], + eventKey = aDOMEvent.key, modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) | (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) | (aDOMEvent.altKey ? CPAlternateKeyMask : 0) | @@ -697,22 +635,14 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio (_capsLockActive ? CPAlphaShiftKeyMask : 0); // With a few exceptions, all key events are blocked from propagating to - // the browser. Here the following exceptions are being allowed: - // - // - All keys pressed along with a ctrl or cmd key _unless_ they are in - // one of the two blacklists. - // - Any key listed in the whitelist. - // - // The ctrl/cmd keys are used for browser hotkeys as are the keys listed in - // the whitelist (F1-F12 at the time of writing). - // - // If a key is listed in both the blacklist and whitelist, the blacklist is - // checked first. The key will be blocked from propagating in that case. - + // the browser. We check against blacklists and whitelists. StopDOMEventPropagation = YES; + // Use event.key for checking character keys. This is more reliable across keyboard layouts. + var charToTest = (eventKey && eventKey.length === 1) ? eventKey.toLowerCase() : null; + // Make sure it is not in the blacklists. - if (!(CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode])) + if (!((charToTest && CharacterKeysToPrevent[charToTest]) || KeyCodesToPrevent[aDOMEvent.keyCode])) { // It is not in the blacklist, let it through if the ctrl/cmd key is // also down or it's in the whitelist. @@ -726,7 +656,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio switch (aDOMEvent.type) { case "keydown": - // Grab and store the keycode now since it is correct and consistent at this point. + // Grab and store the keycode for compatibility with other parts of the system. if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap) _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode]; else @@ -734,62 +664,68 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio var characters; - // Handle key codes for which String.fromCharCode won't work. - // Refs #1036: In Internet Explorer, both 'which' and 'charCode' are undefined for special keys. - if (aDOMEvent.which === 0 || aDOMEvent.charCode === 0 || (aDOMEvent.which === undefined && aDOMEvent.charCode === undefined)) - characters = KeyCodesToUnicodeMap[_keyCode]; - - // The problem with keyCode is that this property refers to keys on the keyboard and not to characters - // This is why String.fromCharCode does not always work in more recent versions of Firefox - // E.g. pressing a '#' on a German keyboard gives you a charCode of 163, which refers to '£' and not '#' - // The property key works fine, though. From there we can get the actual character more robustly. - // Therefore we prefer key over keyCode whenever possible - - if (!characters) - characters = (aDOMEvent.key && aDOMEvent.key.length == 1) ? aDOMEvent.key.toLowerCase() : String.fromCharCode(_keyCode).toLowerCase(); - - overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters; - - // check for caps lock state - if (_keyCode === CPKeyCodes.CAPS_LOCK) + // Determine characters using modern event.key property first. + if (eventKey) { - _capsLockActive = YES; - - // Make sure the caps lock flag is set in modifierFlags - modifierFlags |= CPAlphaShiftKeyMask; + if (eventKey.length === 1) + characters = eventKey; // Printable character, already correctly cased. + else + characters = String.fromCharCode(KeyToUnicodeMapFromKey[eventKey]); // Special key. } - if ([ModifierKeyCodes containsObject:_keyCode]) + // Fallback for older browsers or unhandled keys. + if (!characters) { - // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. + if (aDOMEvent.charCode === 0) // Keydown for non-printable keys have charCode === 0 + characters = KeyCodesToUnicodeMap[_keyCode]; + if (!characters) + characters = String.fromCharCode(_keyCode); + } + + // Set characters for the event. event.key is already cased correctly for printable keys. + if (eventKey && eventKey.length === 1) + { + overrideCharacters = eventKey; + charactersIgnoringModifiers = eventKey.toLowerCase(); + } + else + { + charactersIgnoringModifiers = (characters || "").toLowerCase(); + overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? (characters || "").toUpperCase() : charactersIgnoringModifiers; + } + + // Handle caps lock state. + if (eventKey === "CapsLock") + { + // This is a simplification; getModifierState("CapsLock") would be ideal but is not used here to avoid further changes. + _capsLockActive = !_capsLockActive; + modifierFlags = (modifierFlags & ~CPAlphaShiftKeyMask) | (_capsLockActive ? CPAlphaShiftKeyMask : 0); + } + + var isModifier = (eventKey === "Control" || eventKey === "Shift" || eventKey === "Alt" || eventKey === "Meta" || eventKey === "CapsLock"); + if (isModifier) + { + // A modifier key will never fire keypress. We fire a CPFlagsChanged event and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; - break; } else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) { - //we are simply going to skip all keypress events that use cmd/ctrl key - //this lets us be consistent in all browsers and send on the keydown - //which means we can cancel the event early enough, but only if sendEvent needs to + // Let Cmd/Ctrl combinations be sent on keydown to allow for early cancellation. } - else if (CPKeyCodes.firesKeyPressEvent(_keyCode, aDOMEvent.key, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) + else if (CPKeyCodes.firesKeyPressEvent(_keyCode, eventKey, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) { - // this branch is taken by events which fire keydown, keypress, and keyup. - // this is the only time we'll ALLOW character keys to propagate (needed for text fields) + // This branch is for keys that fire a `keypress` event. + // We allow propagation to let the browser handle input in text fields. StopDOMEventPropagation = NO; break; } - else - { - //this branch is taken by "remedial" key events - // In this state we continue to keypress and send the CPEvent - } case "keypress": - // we unconditionally break on keypress events with modifiers, - // because we forced the event to be sent on the keydown + // We unconditionally break on keypress events with modifiers, + // as we forced the event to be sent on the keydown. if (aDOMEvent.type === "keypress" && (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) break; @@ -800,15 +736,20 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = keyCode; _charCodes[keyCode] = charCode; + // Use the character determined during keydown if available. var characters = overrideCharacters; - // Is this a special key? - if (!characters && (aDOMEvent.which === 0 || aDOMEvent.charCode === 0)) - characters = KeyCodesToUnicodeMap[charCode]; - if (!characters) - characters = String.fromCharCode(charCode); + { + // Fallback for printable keys on keypress. + if (eventKey && eventKey.length === 1) + characters = eventKey; + else if (charCode !== 0) + characters = String.fromCharCode(charCode); + else + characters = KeyCodesToUnicodeMap[keyCode] || ""; + } - charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift. + charactersIgnoringModifiers = characters.toLowerCase(); // Safari won't send proper capitalization during cmd-key events if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive)) @@ -817,7 +758,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode]; - break; case "keyup": @@ -828,26 +768,26 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = -1; _charCodes[keyCode] = nil; - // check for caps lock state - if (keyCode === CPKeyCodes.CAPS_LOCK) + if (eventKey === "CapsLock") { - _capsLockActive = NO; - - // Make sure the caps lock flag is cleared in modifierFlags - modifierFlags &= ~CPAlphaShiftKeyMask; + // The state was handled on keydown. Nothing to do for state change on keyup for a toggle key. } - if ([ModifierKeyCodes containsObject:keyCode]) + var isModifier = (eventKey === "Control" || eventKey === "Shift" || eventKey === "Alt" || eventKey === "Meta" || eventKey === "CapsLock"); + if (isModifier) { - // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; - + characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode]; break; } - var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode); + var characters; + if (eventKey && eventKey.length === 1) + characters = eventKey; + else + characters = KeyCodesToUnicodeMap[keyCode] || String.fromCharCode(charCode) || ""; + charactersIgnoringModifiers = characters.toLowerCase(); if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive) @@ -856,7 +796,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags timestamp: timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode]; - break; } @@ -868,7 +807,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (event && ![_platformPasteboard windowShouldSuppressKeyEvent]) { [CPApp sendEvent:event]; - [_platformPasteboard windowDidSendKeyEvent:event]; } @@ -1225,7 +1163,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio return; } } - + // cancel other touch cases preventively if (aDOMEvent.preventDefault) @@ -1908,3 +1846,13 @@ function CPWindowObjectList() return windowObjects; } + +function CPWindowList() +{ + var windowObjectList = CPWindowObjectList(); + + return [windowObjectList arrayByApplyingBlock:function(windowObject) + { + return [windowObject windowNumber]; + }]; +} diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten index 80b9ecf8e..1df033252 100755 --- a/dist/cappuccino/bin/flatten +++ b/dist/cappuccino/bin/flatten @@ -233,7 +233,7 @@ ObjectiveJFlattener.prototype.serializeFunctions = function() { var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK var relative = this.rootPath.relative(path).toString(); - this.functionsBuffer.push("ObjectiveJ.FileExecutable._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); + this.functionsBuffer.push("ObjectiveJ.StaticResource._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); } var bundle = this.context.global.CFBundle.bundleContainingURL(path); diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo index 1a91ba1c8040bbcfe787719cb7e44c0e7578d180..f1ff06158a2525d34541aff240713c0067bd8633 100755 GIT binary patch delta 3394 zcmZ`*3s6+o89w`DsqFGx+h670NB67 z0BER_-s?-eqvet#M>G=>)tO2(O5#IV%?yIz_H@(>o1IN<&UUZ~LaljSEpq}>J}u!b zVr7xn4nNt^c`Qus7M+>LN3Ns}%e^# zxB<8vC=Ez+f{-lWvAl8B06|2vfltV9N$Vy>N(-^UM2r-KiqFN|))av-q#`Yxy;N9bap; zbl)tk_TTZ}vjob6WVpw18Q|(jmB3tzoS61`(Bi_#J{o&92UPh z1kMEhI4Gn~<~Ls&Pedbs907*Sm!KzryabTV!B&|iP}1kEA|#@JLkYP4ll)U(?U-NX zAFB>59U7~hoVp=??dgX2CkHG6b144R477I-%gz1=@l%E%bP;IYh+YSQ<68b0W_{yhNVO;gS%)DY9|CFu^Cw_= zYy98vk0JR4z<0VfKmeuBn;21b1(;^GjCA5h9;5Oo%! z21?$Q5G{eau*LtS6tcp9AL}t6+`C0LOp)vbxba+Y#jpKaRba;@D5&``bSwIxt2;VA z7m6Rkl)~s0(;9<9JrV}$1}Ip05ODcBLvl+{l?1>wLG3sMukeR!;;Ta7kO9ZS>T&*{ z{zEzOLM8vvr;O+WxQhNk^R7t8bcndWOpKTit&nIF3W1UdG-GYnqaG}e!0VceCM^D; zqR<$gLFfq+Os>atD9(nKXg?6aV+ z31%YzZ;ceFP(~ny?u;8q%gFHMF*P z*19~KSK6ALWp3At&N8}5o0%b<<}Q0}yUpcs6Pw%aY;nLP>2T5{?Mzys&7!~17U$a9 z+Q4r0w7Xgw%Sc`AGo_MD95AkhP+wdpy{^rjho{UUm=x(7xTUe73@bt6;B-ll&Mk6b zok~V|ozWY{djeYYs%w5>{$De{>i_urm>(8vvT=m;M>tsp975~7 z#q3f}VLXR%BjZaMk6zvlOz&cR7vq%i1C0NM@!vCkj`5p}$4H6gkT{lM4&(C}UyNKl zEc;jK_Gyz6^9p^6wAC}orWEl_k~sS8sTu0k?y#(fqfMs_THkqCezBqDpP?MD&)CBW z&n;VCDOA?*R;!RNthBNPoEn_WNf&anyU_~EP1-duOuVAoolQ=g+evy-I7e5LL;9bp zcZpl2N6!J=GqAsS4{VV74v!Nz8S!0>rjZSKJUTtb6L%kGxNjgw(UZ#2PX;pOpvDj7 zZEQD$6lpz#xJe2p*gUNbq=&dQ(fn*pO|5pcB>cc4xLRBuG^a()t!*Bm#km!21^{Y< z?dt(3`fwo4I~_hNsom+2_*U2)IY80>&K)4@bVU41z^&d{@~((mC%WL|DnN|GIIl{@Jw!dTys4T{x9RzodJaZ z4tRN|17!j|7So@7$|alo*un|T`0c_^6Rs%KW{b_^Bnv#vZFED}sE~LjJrZ7`>?}12 z^j0`ibN5PI2(t0gw9_+V&CZvc1KBrsh$+yI+k6k&Di|x%8J~FIq82mvzXg|eCJcY(|mto zMx}1;LO?SZmK-?9EuXVz>5=5^X}>L7k@!{fg5Ehp)(=xh-#lb|+cpY89Bmol413={ z-q`FoHlKWPG2x=~EpMW(JNwJmf~muwCO=3yz5WY&c7)^fYphqZ_NR{CzQ7&xoUOR< zAJ@MQUj86?*7l0)Tdq}e?|z(>fA3J|=hi;mdw(7(Fm6j396lJl*4(&vgpTzG?tKj) zn+%nU^QL|O$r072fIIj3og;U;LIdSB8QV|n`;}GwcKVJL7_=y2daz~N;7i#ZKlc9g ziFc;uy>z+dxtB-kDrb~^yD_)tRtD4(7aR|Ea`(y$F95qo>p%S?-EgKtQCwuCBWGsE Q%I;p7hEba639vimze2ENzW@LL literal 70736 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 diff --git a/dist/cappuccino/bin/imagesize b/dist/cappuccino/bin/imagesize new file mode 100755 index 0000000000000000000000000000000000000000..5d67a1ce0a9a1e20577f82ccdfe0d3b3e94143b4 GIT binary patch literal 69536 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 Z009sH0T2KI5C8!X009sHfo}nU{{v@rhV}pe literal 0 HcmV?d00001 diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton index 51ecab38d..b7972dbb3 100755 --- a/dist/cappuccino/bin/objj2objcskeleton +++ b/dist/cappuccino/bin/objj2objcskeleton @@ -4,11 +4,14 @@ var fs = require("fs"), acorn = require("objj-parser"), - walk = require("acorn-walk"), + walk = require("objj-parser/util/walk"), stream = ObjectiveJ.term; + debugger; + function main(args) { + debugger; args.shift(); if (args.length < 1) @@ -37,8 +40,6 @@ function raise(pos, message) throw syntaxError; } -function ignore(_node, _st, _c) {} - var errors = [], xcc = walk.make( { @@ -114,18 +115,7 @@ var errors = [], else raise(node.loc.start, "Action methods must have exactly one parameter"); } - }, - TypeDefStatement: ignore, - ClassStatement: ignore, - MessageSendExpression: ignore, - GlobalStatement: ignore, - ProtocolDeclarationStatement: ignore, - ArrayLiteral: ignore, - Reference: ignore, - DictionaryLiteral: ignore, - Dereference: ignore, - ImportStatement: ignore, - SelectorLiteralExpression: ignore + } } ); @@ -164,7 +154,7 @@ function parser(args) outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), source = fs.readFileSync(sourcePath, { encoding: "utf8" }), - tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath, ecmaVersion: 2022 }), + tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath }), classesInformation = [], ObjectiveCSource = "", ObjectiveCHeader = "", From 223bbc86e1d7400817d5930449c04000f1a13ecd Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 1 Jul 2025 19:42:12 +0200 Subject: [PATCH 06/40] fixed: arrow keys did not work --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 256 +++++++++++++-------- 1 file changed, 160 insertions(+), 96 deletions(-) diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index 3fa5546bb..7c8ad47f5 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -20,6 +20,93 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +/* + * THIS DOCUMENTATION STOLEN DIRECTLY FROM GOOGLE CLOSURE (licensed under Apache 2) + * + * Different web browsers have very different keyboard event handling. Most + * importantly is that only certain browsers repeat keydown events: + * IE, Opera, FF/Win32, and Safari 3 repeat keydown events. + * FF/Mac and Safari 2 do not. + * + * For the purposes of this code, "Safari 3" means WebKit 525+, when WebKit + * decided that they should try to match IE's key handling behavior. + * Safari 3.0.4, which shipped with Leopard (WebKit 523), has the + * Safari 2 behavior. + * + * Firefox, Safari, Opera prevent on keypress + * + * IE prevents on keydown + * + * Firefox does not fire keypress for shift, ctrl, alt + * Firefox does fire keydown for shift, ctrl, alt, meta + * Firefox does not repeat keydown for shift, ctrl, alt, meta + * + * Firefox does not fire keypress for up and down in an input + * + * Opera fires keypress for shift, ctrl, alt, meta + * Opera does not repeat keypress for shift, ctrl, alt, meta + * + * Safari 2 and 3 do not fire keypress for shift, ctrl, alt + * Safari 2 does not fire keydown for shift, ctrl, alt + * Safari 3 *does* fire keydown for shift, ctrl, alt + * + * IE provides the keycode for keyup/down events and the charcode (in the + * keycode field) for keypress. + * + * Mozilla provides the keycode for keyup/down and the charcode for keypress + * unless it's a non text modifying key in which case the keycode is provided. + * + * Safari 3 provides the keycode and charcode for all events. + * + * Opera provides the keycode for keyup/down event and either the charcode or + * the keycode (in the keycode field) for keypress events. + * + * Firefox x11 doesn't fire keydown events if a another key is already held down + * until the first key is released. This can cause a key event to be fired with + * a keyCode for the first key and a charCode for the second key. + * + * Safari 2 in keypress (not supported) + * + * charCode keyCode which + * ENTER: 13 13 13 + * F1: 63236 63236 63236 + * F8: 63243 63243 63243 + * ... + * p: 112 112 112 + * P: 80 80 80 + * + * Firefox, keypress: + * + * charCode keyCode which + * ENTER: 0 13 13 + * F1: 0 112 0 + * F8: 0 119 0 + * ... + * p: 112 0 112 + * P: 80 0 80 + * + * Opera, Mac+Win32, keypress: + * + * charCode keyCode which + * ENTER: undefined 13 13 + * F1: undefined 112 0 + * F8: undefined 119 0 + * ... + * p: undefined 112 112 + * P: undefined 80 80 + * + * IE7, keydown + * + * charCode keyCode which + * ENTER: undefined 13 undefined + * F1: undefined 112 undefined + * F8: undefined 119 undefined + * ... + * p: undefined 80 undefined + * P: undefined 80 undefined + */ + @import @import @import @@ -65,29 +152,6 @@ var KeyCodesToPrevent = {}, }, KeyCodesToUnicodeMap = {}; -// New map from event.key to our internal Unicode function keys. -// This is more reliable than mapping from keyCode. -var KeyToUnicodeMapFromKey = { - "Backspace": CPDeleteCharacter, - "Delete": CPDeleteFunctionKey, - "Tab": CPTabCharacter, - "Enter": CPCarriageReturnCharacter, - "Escape": CPEscapeFunctionKey, - "PageUp": CPPageUpFunctionKey, - "PageDown": CPPageDownFunctionKey, - "ArrowLeft": CPLeftArrowFunctionKey, - "ArrowUp": CPUpArrowFunctionKey, - "ArrowRight": CPRightArrowFunctionKey, - "ArrowDown": CPDownArrowFunctionKey, - "Home": CPHomeFunctionKey, - "End": CPEndFunctionKey -}; - -// Map F-keys dynamically. Assumes CP_F1_KEY (0xF704) to CP_F12_KEY (0xF70F) are defined elsewhere. -for (var i = 1; i <= 12; i++) - KeyToUnicodeMapFromKey['F' + i] = 0xF703 + i; - - KeyCodesToPrevent[CPKeyCodes.A] = YES; KeyCodesToAllow[CPKeyCodes.F1] = YES; @@ -627,7 +691,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio timestamp = [CPEvent currentTimestamp], sourceElement = aDOMEvent.target || aDOMEvent.srcElement, windowNumber = [[CPApp keyWindow] windowNumber], - eventKey = aDOMEvent.key, modifierFlags = (aDOMEvent.shiftKey ? CPShiftKeyMask : 0) | (aDOMEvent.ctrlKey ? CPControlKeyMask : 0) | (aDOMEvent.altKey ? CPAlternateKeyMask : 0) | @@ -635,14 +698,22 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio (_capsLockActive ? CPAlphaShiftKeyMask : 0); // With a few exceptions, all key events are blocked from propagating to - // the browser. We check against blacklists and whitelists. + // the browser. Here the following exceptions are being allowed: + // + // - All keys pressed along with a ctrl or cmd key _unless_ they are in + // one of the two blacklists. + // - Any key listed in the whitelist. + // + // The ctrl/cmd keys are used for browser hotkeys as are the keys listed in + // the whitelist (F1-F12 at the time of writing). + // + // If a key is listed in both the blacklist and whitelist, the blacklist is + // checked first. The key will be blocked from propagating in that case. + StopDOMEventPropagation = YES; - // Use event.key for checking character keys. This is more reliable across keyboard layouts. - var charToTest = (eventKey && eventKey.length === 1) ? eventKey.toLowerCase() : null; - // Make sure it is not in the blacklists. - if (!((charToTest && CharacterKeysToPrevent[charToTest]) || KeyCodesToPrevent[aDOMEvent.keyCode])) + if (!(CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode])) { // It is not in the blacklist, let it through if the ctrl/cmd key is // also down or it's in the whitelist. @@ -656,7 +727,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio switch (aDOMEvent.type) { case "keydown": - // Grab and store the keycode for compatibility with other parts of the system. + // Grab and store the keycode now since it is correct and consistent at this point. if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap) _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode]; else @@ -664,68 +735,62 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio var characters; - // Determine characters using modern event.key property first. - if (eventKey) - { - if (eventKey.length === 1) - characters = eventKey; // Printable character, already correctly cased. - else - characters = String.fromCharCode(KeyToUnicodeMapFromKey[eventKey]); // Special key. - } + // Handle key codes for which String.fromCharCode won't work. + // Refs #1036: In Internet Explorer, both 'which' and 'charCode' are undefined for special keys. + if (aDOMEvent.which === 0 || aDOMEvent.charCode === 0 || (aDOMEvent.which === undefined && aDOMEvent.charCode === undefined)) + characters = KeyCodesToUnicodeMap[_keyCode]; + + // The problem with keyCode is that this property refers to keys on the keyboard and not to characters + // This is why String.fromCharCode does not always work in more recent versions of Firefox + // E.g. pressing a '#' on a German keyboard gives you a charCode of 163, which refers to '£' and not '#' + // The property key works fine, though. From there we can get the actual character more robustly. + // Therefore we prefer key over keyCode whenever possible - // Fallback for older browsers or unhandled keys. if (!characters) + characters = (aDOMEvent.key && aDOMEvent.key.length == 1) ? aDOMEvent.key.toLowerCase() : String.fromCharCode(_keyCode).toLowerCase(); + + overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters; + + // check for caps lock state + if (_keyCode === CPKeyCodes.CAPS_LOCK) { - if (aDOMEvent.charCode === 0) // Keydown for non-printable keys have charCode === 0 - characters = KeyCodesToUnicodeMap[_keyCode]; - if (!characters) - characters = String.fromCharCode(_keyCode); + _capsLockActive = YES; + + // Make sure the caps lock flag is set in modifierFlags + modifierFlags |= CPAlphaShiftKeyMask; } - // Set characters for the event. event.key is already cased correctly for printable keys. - if (eventKey && eventKey.length === 1) + if ([ModifierKeyCodes containsObject:_keyCode]) { - overrideCharacters = eventKey; - charactersIgnoringModifiers = eventKey.toLowerCase(); - } - else - { - charactersIgnoringModifiers = (characters || "").toLowerCase(); - overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? (characters || "").toUpperCase() : charactersIgnoringModifiers; - } - - // Handle caps lock state. - if (eventKey === "CapsLock") - { - // This is a simplification; getModifierState("CapsLock") would be ideal but is not used here to avoid further changes. - _capsLockActive = !_capsLockActive; - modifierFlags = (modifierFlags & ~CPAlphaShiftKeyMask) | (_capsLockActive ? CPAlphaShiftKeyMask : 0); - } - - var isModifier = (eventKey === "Control" || eventKey === "Shift" || eventKey === "Alt" || eventKey === "Meta" || eventKey === "CapsLock"); - if (isModifier) - { - // A modifier key will never fire keypress. We fire a CPFlagsChanged event and break. + // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; + break; } else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) { - // Let Cmd/Ctrl combinations be sent on keydown to allow for early cancellation. + //we are simply going to skip all keypress events that use cmd/ctrl key + //this lets us be consistent in all browsers and send on the keydown + //which means we can cancel the event early enough, but only if sendEvent needs to } - else if (CPKeyCodes.firesKeyPressEvent(_keyCode, eventKey, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) + else if (CPKeyCodes.firesKeyPressEvent(_keyCode, aDOMEvent.key, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) { - // This branch is for keys that fire a `keypress` event. - // We allow propagation to let the browser handle input in text fields. + // this branch is taken by events which fire keydown, keypress, and keyup. + // this is the only time we'll ALLOW character keys to propagate (needed for text fields) StopDOMEventPropagation = NO; break; } + else + { + //this branch is taken by "remedial" key events + // In this state we continue to keypress and send the CPEvent + } case "keypress": - // We unconditionally break on keypress events with modifiers, - // as we forced the event to be sent on the keydown. + // we unconditionally break on keypress events with modifiers, + // because we forced the event to be sent on the keydown if (aDOMEvent.type === "keypress" && (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) break; @@ -736,20 +801,15 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = keyCode; _charCodes[keyCode] = charCode; - // Use the character determined during keydown if available. var characters = overrideCharacters; - if (!characters) - { - // Fallback for printable keys on keypress. - if (eventKey && eventKey.length === 1) - characters = eventKey; - else if (charCode !== 0) - characters = String.fromCharCode(charCode); - else - characters = KeyCodesToUnicodeMap[keyCode] || ""; - } + // Is this a special key? + if (!characters && (aDOMEvent.which === 0 || aDOMEvent.charCode === 0)) + characters = KeyCodesToUnicodeMap[charCode]; - charactersIgnoringModifiers = characters.toLowerCase(); + if (!characters) + characters = String.fromCharCode(charCode); + + charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift. // Safari won't send proper capitalization during cmd-key events if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive)) @@ -758,6 +818,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode]; + break; case "keyup": @@ -768,26 +829,26 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = -1; _charCodes[keyCode] = nil; - if (eventKey === "CapsLock") + // check for caps lock state + if (keyCode === CPKeyCodes.CAPS_LOCK) { - // The state was handled on keydown. Nothing to do for state change on keyup for a toggle key. + _capsLockActive = NO; + + // Make sure the caps lock flag is cleared in modifierFlags + modifierFlags &= ~CPAlphaShiftKeyMask; } - var isModifier = (eventKey === "Control" || eventKey === "Shift" || eventKey === "Alt" || eventKey === "Meta" || eventKey === "CapsLock"); - if (isModifier) + if ([ModifierKeyCodes containsObject:keyCode]) { + // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode]; + characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; + break; } - var characters; - if (eventKey && eventKey.length === 1) - characters = eventKey; - else - characters = KeyCodesToUnicodeMap[keyCode] || String.fromCharCode(charCode) || ""; - + var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode); charactersIgnoringModifiers = characters.toLowerCase(); if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive) @@ -796,6 +857,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags timestamp: timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode]; + break; } @@ -807,6 +869,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (event && ![_platformPasteboard windowShouldSuppressKeyEvent]) { [CPApp sendEvent:event]; + [_platformPasteboard windowDidSendKeyEvent:event]; } @@ -1163,7 +1226,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio return; } } - + // cancel other touch cases preventively if (aDOMEvent.preventDefault) @@ -1856,3 +1919,4 @@ function CPWindowList() return [windowObject windowNumber]; }]; } + From 5ecdaafadfa5f0413415ea7eda0ffc6550bc35d1 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 1 Jul 2025 20:23:29 +0200 Subject: [PATCH 07/40] fixed: arrow keys did not work --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index 7c8ad47f5..2ecd0c48d 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -736,8 +736,9 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio var characters; // Handle key codes for which String.fromCharCode won't work. - // Refs #1036: In Internet Explorer, both 'which' and 'charCode' are undefined for special keys. - if (aDOMEvent.which === 0 || aDOMEvent.charCode === 0 || (aDOMEvent.which === undefined && aDOMEvent.charCode === undefined)) + // This condition is for identifying non-printing keys based on the keydown event. + // We've replaced the deprecated '.which' with a direct check on charCode. + if (!aDOMEvent.charCode) characters = KeyCodesToUnicodeMap[_keyCode]; // The problem with keyCode is that this property refers to keys on the keyboard and not to characters @@ -802,11 +803,19 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _charCodes[keyCode] = charCode; var characters = overrideCharacters; - // Is this a special key? - if (!characters && (aDOMEvent.which === 0 || aDOMEvent.charCode === 0)) - characters = KeyCodesToUnicodeMap[charCode]; - if (!characters) + // This condition is met for non-printing keys (like Enter, Tab, arrows) that fall through + // from the 'keydown' case, as their charCode is 0 in the keydown DOM event. + if (!characters && (!aDOMEvent.charCode || aDOMEvent.charCode === 0)) + characters = KeyCodesToUnicodeMap[charCode]; // Note: for fall-through, charCode is actually the keyCode from the keydown event. + + // For modern browsers, event.key is the most reliable way to get the actual character, + // especially for international keyboards. We only use it for single-character keys. + if (!characters && aDOMEvent.key && aDOMEvent.key.length === 1) + characters = aDOMEvent.key; + + // Fallback for older browsers that support charCode but not key. + if (!characters && charCode > 0) characters = String.fromCharCode(charCode); charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift. From b1d96dfdee03abd4378aeefb9124f6dddaf66785 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 1 Jul 2025 21:53:53 +0200 Subject: [PATCH 08/40] improved reliance of key --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 38 +++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index 2ecd0c48d..dc134912e 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -505,6 +505,8 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _DOMBodyElement.ondrag = function () { return NO; }; _DOMBodyElement.onselectstart = function () { return _DOMWindow.event.srcElement === _platformPasteboard._DOMPasteboardElement; }; + + _DOMWindow.attachEvent("onunload", function() { _DOMWindow.detachEvent("unload", arguments.callee); @@ -713,6 +715,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio StopDOMEventPropagation = YES; // Make sure it is not in the blacklists. + // Note: keyCode is deprecated but is kept here for legacy compatibility with the KeyCodesToPrevent/Allow maps. if (!(CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode])) { // It is not in the blacklist, let it through if the ctrl/cmd key is @@ -727,7 +730,8 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio switch (aDOMEvent.type) { case "keydown": - // Grab and store the keycode now since it is correct and consistent at this point. + // Grab and store the keyCode now since it is correct and consistent at this point. + // Note: keyCode is deprecated but is required for the existing compatibility logic. if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap) _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode]; else @@ -737,28 +741,22 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio // Handle key codes for which String.fromCharCode won't work. // This condition is for identifying non-printing keys based on the keydown event. - // We've replaced the deprecated '.which' with a direct check on charCode. if (!aDOMEvent.charCode) characters = KeyCodesToUnicodeMap[_keyCode]; - // The problem with keyCode is that this property refers to keys on the keyboard and not to characters - // This is why String.fromCharCode does not always work in more recent versions of Firefox - // E.g. pressing a '#' on a German keyboard gives you a charCode of 163, which refers to '£' and not '#' - // The property key works fine, though. From there we can get the actual character more robustly. - // Therefore we prefer key over keyCode whenever possible - + // The problem with the deprecated `keyCode` is that it refers to keys on the keyboard, not characters. + // This is why String.fromCharCode does not always work, e.g. on international layouts. + // The modern `key` property provides the actual character, so we prefer it when available. if (!characters) - characters = (aDOMEvent.key && aDOMEvent.key.length == 1) ? aDOMEvent.key.toLowerCase() : String.fromCharCode(_keyCode).toLowerCase(); + characters = (aDOMEvent.key && aDOMEvent.key.length === 1) ? aDOMEvent.key.toLowerCase() : String.fromCharCode(_keyCode).toLowerCase(); overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters; // check for caps lock state if (_keyCode === CPKeyCodes.CAPS_LOCK) { - _capsLockActive = YES; - - // Make sure the caps lock flag is set in modifierFlags - modifierFlags |= CPAlphaShiftKeyMask; + // The original logic was incorrect, treating Caps Lock as a momentary key. + // It is now correctly handled as a toggle on keyup. } if ([ModifierKeyCodes containsObject:_keyCode]) @@ -814,7 +812,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (!characters && aDOMEvent.key && aDOMEvent.key.length === 1) characters = aDOMEvent.key; - // Fallback for older browsers that support charCode but not key. + // Fallback for older browsers that use the deprecated charCode property. if (!characters && charCode > 0) characters = String.fromCharCode(charCode); @@ -838,13 +836,16 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _lastKey = -1; _charCodes[keyCode] = nil; - // check for caps lock state + // check for caps lock state toggle if (keyCode === CPKeyCodes.CAPS_LOCK) { - _capsLockActive = NO; + _capsLockActive = !_capsLockActive; - // Make sure the caps lock flag is cleared in modifierFlags - modifierFlags &= ~CPAlphaShiftKeyMask; + // Update modifierFlags to reflect the new state of Caps Lock for this event + if (_capsLockActive) + modifierFlags |= CPAlphaShiftKeyMask; + else + modifierFlags &= ~CPAlphaShiftKeyMask; } if ([ModifierKeyCodes containsObject:keyCode]) @@ -1928,4 +1929,3 @@ function CPWindowList() return [windowObject windowNumber]; }]; } - From b3cb22caef0a7b8b7492470254246e8093253771 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 1 Jul 2025 22:16:33 +0200 Subject: [PATCH 09/40] more modernisations --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 221 ++++++++++----------- 1 file changed, 104 insertions(+), 117 deletions(-) diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index dc134912e..6439aa5da 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -192,6 +192,32 @@ KeyCodesToUnicodeMap[CPKeyCodes.OPEN_SQUARE_BRACKET] = "["; KeyCodesToUnicodeMap[CPKeyCodes.BACKSLASH] = "\\"; KeyCodesToUnicodeMap[CPKeyCodes.CLOSE_SQUARE_BRACKET] = "]"; +var KeyNameToUnicodeMap = {}; +KeyNameToUnicodeMap["Backspace"] = CPDeleteCharacter; +KeyNameToUnicodeMap["Delete"] = CPDeleteFunctionKey; +KeyNameToUnicodeMap["Tab"] = CPTabCharacter; +KeyNameToUnicodeMap["Enter"] = CPCarriageReturnCharacter; +KeyNameToUnicodeMap["Escape"] = CPEscapeFunctionKey; +KeyNameToUnicodeMap["PageUp"] = CPPageUpFunctionKey; +KeyNameToUnicodeMap["PageDown"] = CPPageDownFunctionKey; +KeyNameToUnicodeMap["ArrowLeft"] = CPLeftArrowFunctionKey; +KeyNameToUnicodeMap["ArrowUp"] = CPUpArrowFunctionKey; +KeyNameToUnicodeMap["ArrowRight"] = CPRightArrowFunctionKey; +KeyNameToUnicodeMap["ArrowDown"] = CPDownArrowFunctionKey; +KeyNameToUnicodeMap["Home"] = CPHomeFunctionKey; +KeyNameToUnicodeMap["End"] = CPEndFunctionKey; +KeyNameToUnicodeMap[";"] = ";"; +KeyNameToUnicodeMap["-"] = "-"; +KeyNameToUnicodeMap["="] = "="; +KeyNameToUnicodeMap[","] = ","; +KeyNameToUnicodeMap["."] = "."; +KeyNameToUnicodeMap["/"] = "/"; +KeyNameToUnicodeMap["`"] = "`"; +KeyNameToUnicodeMap["'"] = "'"; +KeyNameToUnicodeMap["["] = "["; +KeyNameToUnicodeMap["\\"] = "\\"; +KeyNameToUnicodeMap["]"] = "]"; + var ModifierKeyCodes = [ CPKeyCodes.META, CPKeyCodes.WEBKIT_RIGHT_META, @@ -427,7 +453,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio theDocument.addEventListener("keyup", keyEventCallback, NO); theDocument.addEventListener("keydown", keyEventCallback, NO); - theDocument.addEventListener("keypress", keyEventCallback, NO); + // "keypress" listener removed as it's deprecated and no longer used in the new logic. theDocument.addEventListener("touchstart", touchEventCallback, {passive: false}); theDocument.addEventListener("touchend", touchEventCallback, {passive: false}); @@ -459,7 +485,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio theDocument.removeEventListener("keyup", keyEventCallback, NO); theDocument.removeEventListener("keydown", keyEventCallback, NO); - theDocument.removeEventListener("keypress", keyEventCallback, NO); theDocument.removeEventListener("touchstart", touchEventCallback, NO); theDocument.removeEventListener("touchend", touchEventCallback, NO); @@ -492,7 +517,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio theDocument.attachEvent("onkeyup", keyEventCallback); theDocument.attachEvent("onkeydown", keyEventCallback); - theDocument.attachEvent("onkeypress", keyEventCallback); + // "onkeypress" listener removed. _DOMWindow.attachEvent("onresize", resizeEventCallback); @@ -505,8 +530,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _DOMBodyElement.ondrag = function () { return NO; }; _DOMBodyElement.onselectstart = function () { return _DOMWindow.event.srcElement === _platformPasteboard._DOMPasteboardElement; }; - - _DOMWindow.attachEvent("onunload", function() { _DOMWindow.detachEvent("unload", arguments.callee); @@ -524,7 +547,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio theDocument.detachEvent("onkeyup", keyEventCallback); theDocument.detachEvent("onkeydown", keyEventCallback); - theDocument.detachEvent("onkeypress", keyEventCallback); _DOMWindow.detachEvent("onresize", resizeEventCallback); @@ -700,165 +722,130 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio (_capsLockActive ? CPAlphaShiftKeyMask : 0); // With a few exceptions, all key events are blocked from propagating to - // the browser. Here the following exceptions are being allowed: - // - // - All keys pressed along with a ctrl or cmd key _unless_ they are in - // one of the two blacklists. - // - Any key listed in the whitelist. - // - // The ctrl/cmd keys are used for browser hotkeys as are the keys listed in - // the whitelist (F1-F12 at the time of writing). - // - // If a key is listed in both the blacklist and whitelist, the blacklist is - // checked first. The key will be blocked from propagating in that case. - + // the browser. The logic here allows browser shortcuts (Cmd/Ctrl keys) + // and function keys (F1-F12) to pass through, unless explicitly blacklisted. StopDOMEventPropagation = YES; + var keyCodeForPropagationCheck = aDOMEvent.keyCode || 0; + var charForPropagationCheck = String.fromCharCode(keyCodeForPropagationCheck).toLowerCase(); + // Make sure it is not in the blacklists. - // Note: keyCode is deprecated but is kept here for legacy compatibility with the KeyCodesToPrevent/Allow maps. - if (!(CharacterKeysToPrevent[String.fromCharCode(aDOMEvent.keyCode || aDOMEvent.charCode).toLowerCase()] || KeyCodesToPrevent[aDOMEvent.keyCode])) + if (!(CharacterKeysToPrevent[charForPropagationCheck] || KeyCodesToPrevent[keyCodeForPropagationCheck])) { // It is not in the blacklist, let it through if the ctrl/cmd key is // also down or it's in the whitelist. - if ((modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || KeyCodesToAllow[aDOMEvent.keyCode]) + if ((modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || KeyCodesToAllow[keyCodeForPropagationCheck]) StopDOMEventPropagation = NO; } - var overrideCharacters = nil, + var characters = @"", charactersIgnoringModifiers = @""; + // Grab and store the keyCode for mapping and compatibility. + // This property is deprecated but necessary for the key maps. + var keyCode = aDOMEvent.keyCode; + if (keyCode in MozKeyCodeToKeyCodeMap) + keyCode = MozKeyCodeToKeyCodeMap[keyCode]; + switch (aDOMEvent.type) { case "keydown": - // Grab and store the keyCode now since it is correct and consistent at this point. - // Note: keyCode is deprecated but is required for the existing compatibility logic. - if (aDOMEvent.keyCode in MozKeyCodeToKeyCodeMap) - _keyCode = MozKeyCodeToKeyCodeMap[aDOMEvent.keyCode]; - else - _keyCode = aDOMEvent.keyCode; - - var characters; - - // Handle key codes for which String.fromCharCode won't work. - // This condition is for identifying non-printing keys based on the keydown event. - if (!aDOMEvent.charCode) - characters = KeyCodesToUnicodeMap[_keyCode]; - - // The problem with the deprecated `keyCode` is that it refers to keys on the keyboard, not characters. - // This is why String.fromCharCode does not always work, e.g. on international layouts. - // The modern `key` property provides the actual character, so we prefer it when available. - if (!characters) - characters = (aDOMEvent.key && aDOMEvent.key.length === 1) ? aDOMEvent.key.toLowerCase() : String.fromCharCode(_keyCode).toLowerCase(); - - overrideCharacters = (modifierFlags & CPShiftKeyMask || _capsLockActive) ? characters.toUpperCase() : characters; - - // check for caps lock state - if (_keyCode === CPKeyCodes.CAPS_LOCK) + // For modifier keys, we create a CPFlagsChanged event and stop processing. + if ([ModifierKeyCodes containsObject:keyCode]) { - // The original logic was incorrect, treating Caps Lock as a momentary key. - // It is now correctly handled as a toggle on keyup. - } - - if ([ModifierKeyCodes containsObject:_keyCode]) - { - // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; + characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode]; + break; + } - break; - } - else if (modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) + // Determine if the event is a repeat. Use the modern `event.repeat` property + // with a fallback to our manual state tracking for older browsers. + var isARepeat = !!aDOMEvent.repeat || (_charCodes[keyCode] != nil); + _charCodes[keyCode] = YES; // Mark the key as down for fallback repeat detection. + + // Determine the character for the event. + // Priority 1: Use the modern `event.key` property. It's the most reliable. + if (aDOMEvent.key) { - //we are simply going to skip all keypress events that use cmd/ctrl key - //this lets us be consistent in all browsers and send on the keydown - //which means we can cancel the event early enough, but only if sendEvent needs to - } - else if (CPKeyCodes.firesKeyPressEvent(_keyCode, aDOMEvent.key, _lastKey, aDOMEvent.shiftKey, aDOMEvent.ctrlKey, aDOMEvent.altKey)) - { - // this branch is taken by events which fire keydown, keypress, and keyup. - // this is the only time we'll ALLOW character keys to propagate (needed for text fields) - StopDOMEventPropagation = NO; - break; + if (aDOMEvent.key.length === 1) + { + // This is a printing character (e.g., "a", "P", "#"). + characters = aDOMEvent.key; + } + else + { + // This is a named, non-printing key (e.g., "Enter", "ArrowLeft"). + // Map the standard key name to our framework's internal constant. + characters = KeyNameToUnicodeMap[aDOMEvent.key] || aDOMEvent.key; + } } + // Priority 2: Fallback for older browsers without `event.key`. else { - //this branch is taken by "remedial" key events - // In this state we continue to keypress and send the CPEvent + // First, check if it's a known non-printing key in our legacy map. + characters = KeyCodesToUnicodeMap[keyCode]; + + // If not, it's likely a printing character. Use the deprecated fromCharCode. + // This is less reliable for international layouts but is the best fallback. + if (!characters) + { + characters = String.fromCharCode(keyCode); + // Manually handle capitalization for this fallback path. + if (modifierFlags & CPShiftKeyMask || _capsLockActive) + characters = characters.toUpperCase(); + else + characters = characters.toLowerCase(); + } } - case "keypress": - // we unconditionally break on keypress events with modifiers, - // because we forced the event to be sent on the keydown - if (aDOMEvent.type === "keypress" && (modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) - break; - - var keyCode = _keyCode, - charCode = aDOMEvent.keyCode || aDOMEvent.charCode, - isARepeat = (_charCodes[keyCode] != nil); - - _lastKey = keyCode; - _charCodes[keyCode] = charCode; - - var characters = overrideCharacters; - - // This condition is met for non-printing keys (like Enter, Tab, arrows) that fall through - // from the 'keydown' case, as their charCode is 0 in the keydown DOM event. - if (!characters && (!aDOMEvent.charCode || aDOMEvent.charCode === 0)) - characters = KeyCodesToUnicodeMap[charCode]; // Note: for fall-through, charCode is actually the keyCode from the keydown event. - - // For modern browsers, event.key is the most reliable way to get the actual character, - // especially for international keyboards. We only use it for single-character keys. - if (!characters && aDOMEvent.key && aDOMEvent.key.length === 1) - characters = aDOMEvent.key; - - // Fallback for older browsers that use the deprecated charCode property. - if (!characters && charCode > 0) - characters = String.fromCharCode(charCode); - - charactersIgnoringModifiers = characters.toLowerCase(); // FIXME: This isn't correct. It SHOULD include Shift. - - // Safari won't send proper capitalization during cmd-key events - if (!overrideCharacters && (modifierFlags & CPCommandKeyMask) && ((modifierFlags & CPShiftKeyMask) || _capsLockActive)) - characters = characters.toUpperCase(); + // This is a simplification; a fully correct implementation would require extensive mapping. + charactersIgnoringModifiers = characters.toLowerCase(); event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:charCode]; + characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:keyCode]; break; case "keyup": - var keyCode = aDOMEvent.keyCode, - charCode = _charCodes[keyCode]; - - _keyCode = -1; - _lastKey = -1; + // Clear the key's state for our fallback repeat detection. _charCodes[keyCode] = nil; - // check for caps lock state toggle + // Handle the toggling of Caps Lock state. if (keyCode === CPKeyCodes.CAPS_LOCK) { _capsLockActive = !_capsLockActive; - - // Update modifierFlags to reflect the new state of Caps Lock for this event + // Update modifierFlags to reflect the new state for this event. if (_capsLockActive) modifierFlags |= CPAlphaShiftKeyMask; else modifierFlags &= ~CPAlphaShiftKeyMask; } + // For modifier keys, create a CPFlagsChanged event and stop. if ([ModifierKeyCodes containsObject:keyCode]) { - // A modifier key will never fire keypress. We don't need to do any other processing so we just fire it here and break. event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:_keyCode]; - + characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode]; break; } - var characters = KeyCodesToUnicodeMap[charCode] || String.fromCharCode(charCode); + // Determine the character for the keyup event using the same logic as keydown + // for consistency, as this event no longer has access to `keypress` state. + if (aDOMEvent.key) + { + if (aDOMEvent.key.length === 1) + characters = aDOMEvent.key; + else + characters = KeyNameToUnicodeMap[aDOMEvent.key] || aDOMEvent.key; + } + else + { + characters = KeyCodesToUnicodeMap[keyCode] || String.fromCharCode(keyCode); + } + charactersIgnoringModifiers = characters.toLowerCase(); if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive) @@ -884,7 +871,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio } var didStop = NO; - // Platform pasteboard can overrule the decision to stop propagation either way, or it might have no opinion. + // Platform pasteboard can overrule the decision to stop propagation. if ([_platformPasteboard windowShouldStopPropagation] || (StopDOMEventPropagation && ![_platformPasteboard windowShouldNotStopPropagation])) { didStop = YES; From f4ac0630b8e2b9c826962bad7ed71504358d9cc5 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 1 Jul 2025 22:25:20 +0200 Subject: [PATCH 10/40] formatting --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 1 + 1 file changed, 1 insertion(+) diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index 6439aa5da..a4fc8cae8 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -206,6 +206,7 @@ KeyNameToUnicodeMap["ArrowRight"] = CPRightArrowFunctionKey; KeyNameToUnicodeMap["ArrowDown"] = CPDownArrowFunctionKey; KeyNameToUnicodeMap["Home"] = CPHomeFunctionKey; KeyNameToUnicodeMap["End"] = CPEndFunctionKey; +// Add safeguards for punctuation KeyNameToUnicodeMap[";"] = ";"; KeyNameToUnicodeMap["-"] = "-"; KeyNameToUnicodeMap["="] = "="; From 1226f5120cd7aa5fbc5bb1018272f00f13732709 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 1 Jul 2025 22:55:53 +0200 Subject: [PATCH 11/40] improved: deadkey management --- AppKit/CPTextView/CPTextView.j | 232 +++++++++++++++------------------ 1 file changed, 103 insertions(+), 129 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 56a4296f5..4a8d0bc3a 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2626,9 +2626,6 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey", var _CPNativeInputField, - _CPNativeInputFieldKeyDownCalled, - _CPNativeInputFieldKeyUpCalled, - _CPNativeInputFieldKeyPressedCalled, _CPNativeInputFieldActive; var _CPCopyPlaceholder = '-'; @@ -2639,22 +2636,26 @@ var _CPCopyPlaceholder = '-'; { return _CPNativeInputFieldActive; } + + (void)isDeadKey:(CPEvent)event { #if PLATFORM(DOM) - return event._DOMEvent && (event._DOMEvent.key == 'Dead' || event._DOMEvent.key == 'Process'); + // This identifies dead key events during the keydown phase on some platforms/layouts. + return event._DOMEvent && (event._DOMEvent.key === 'Dead' || event._DOMEvent.key === 'Process'); #endif - return NO; } + + (void)cancelCurrentNativeInputSession { + if (!_CPNativeInputFieldActive) + return; #if PLATFORM(DOM) _CPNativeInputField.innerHTML = ''; #endif - [self _endInputSessionWithString:_CPNativeInputField.innerHTML]; + [self _endInputSessionWithString:@""]; } + (void)cancelCurrentInputSessionIfNeeded @@ -2668,17 +2669,25 @@ var _CPCopyPlaceholder = '-'; + (void)_endInputSessionWithString:(CPString)aStr { _CPNativeInputFieldActive = NO; + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - var currentFirstResponder = [[CPApp keyWindow] firstResponder], - placeholderRange = CPMakeRange([currentFirstResponder selectedRange].location - 1, 1); + if (currentFirstResponder && [currentFirstResponder respondsToSelector:@selector(insertText:)]) + { + var placeholderRange = CPMakeRange([currentFirstResponder selectedRange].location - 1, 1); + [currentFirstResponder setSelectedRange:placeholderRange]; - [currentFirstResponder setSelectedRange:placeholderRange]; - [currentFirstResponder insertText:aStr]; + if(aStr) + [currentFirstResponder insertText:aStr]; + } + +#if PLATFORM(DOM) _CPNativeInputField.innerHTML = ''; - +#endif [self hideInputElement]; - [currentFirstResponder updateInsertionPointStateAndRestartTimer:YES]; + + if (currentFirstResponder) + [currentFirstResponder updateInsertionPointStateAndRestartTimer:YES]; } + (void)initialize @@ -2697,95 +2706,59 @@ var _CPCopyPlaceholder = '-'; document.body.appendChild(_CPNativeInputField); - _CPNativeInputField.addEventListener("keyup", function(e) - { - _CPNativeInputFieldKeyUpCalled = YES; - - // Filter out non-printable keys like modifiers, cursor keys, etc. - // A key with a name longer than one character is typically a non-printable control key. - // We exclude 'Dead' and 'Process' which are handled as part of dead-key composition. - if (e.key.length > 1 && e.key !== 'Dead' && e.key !== 'Process') - { - if (e.key === 'Enter') - _CPNativeInputField.innerHTML = ''; - - if (_CPNativeInputField.innerHTML.length == 0 || _CPNativeInputField.innerHTML.length > 2) // backspace - [self cancelCurrentInputSessionIfNeeded]; - - return false; // prevent the default behaviour - } - - var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - - if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) - return false; // prevent the default behaviour - - // chrome-trigger: keypressed is omitted for deadkeys - if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyPressedCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3) - { - _CPNativeInputFieldActive = YES; - [currentFirstResponder _activateNativeInputElement:_CPNativeInputField]; - } - else - { - if (_CPNativeInputFieldActive) - [self _endInputSessionWithString:_CPNativeInputField.innerHTML]; - - // prevent the copy placeholder beeing removed by cursor keys - if (_CPNativeInputFieldKeyPressedCalled) - _CPNativeInputField.innerHTML = ''; - } - - _CPNativeInputFieldKeyDownCalled = NO; - - return false; // prevent the default behaviour - }, true); - + // The 'keydown' event is used to detect non-printable/control keys + // that should either be handled by the text view directly or should + // cancel the native input session. _CPNativeInputField.addEventListener("keydown", function(e) { - // this protects from heavy typing and the shift key - if (_CPNativeInputFieldKeyDownCalled) - return true; + // Filter out non-printable keys like modifiers, cursor keys, etc. + // A key with a name longer than one character is typically a non-printable control key. + if (e.key.length > 1 && e.key !== 'Dead' && e.key !== 'Process') + { + if (e.key === 'Enter' || e.key === 'Escape') + [self cancelCurrentInputSessionIfNeeded]; + + // For backspace, if the field is empty, cancel the session. + // Otherwise, let the 'input' event handle the change. + if (e.key === 'Backspace' && _CPNativeInputField.innerHTML.length === 0) + [self cancelCurrentInputSessionIfNeeded]; + + // Let the browser handle other control keys within the contentEditable, + // but don't propagate to our text view. + return; + } - _CPNativeInputFieldKeyDownCalled = YES; - _CPNativeInputFieldKeyUpCalled = NO; - _CPNativeInputFieldKeyPressedCalled = NO; var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - // webkit-browsers: cursor keys do not emit keypressed and would otherwise activate deadkey mode - if (!CPBrowserIsEngine(CPGeckoBrowserEngine) && e.key.startsWith('Arrow')) - _CPNativeInputFieldKeyPressedCalled = YES; - if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) return; - // FF-trigger: here the best way to detect a dead key is the missing keyup event - if (CPBrowserIsEngine(CPGeckoBrowserEngine)) - setTimeout(function(){ - _CPNativeInputFieldKeyDownCalled = NO; + // If not already active, start a new input session. + if (!_CPNativeInputFieldActive) + { + _CPNativeInputFieldActive = YES; + [currentFirstResponder _activateNativeInputElement:_CPNativeInputField]; + } - if (!_CPNativeInputFieldActive && _CPNativeInputFieldKeyUpCalled == NO && _CPNativeInputField.innerHTML.length && _CPNativeInputField.innerHTML != _CPCopyPlaceholder && _CPNativeInputField.innerHTML.length < 3 && !e.repeat) - { - _CPNativeInputFieldActive = YES; - [currentFirstResponder _activateNativeInputElement:_CPNativeInputField]; - } - else if (!_CPNativeInputFieldActive) - [self hideInputElement]; - }, 200); + }, true); - return false; - }, true); // capture mode - - _CPNativeInputField.addEventListener("keypress", function(e) + // The 'input' event is the modern, unified way to handle all character input. + // It fires after a regular keypress, a dead key composition, or an IME insertion. + // This replaces the need for complex keypress/keyup timing logic. + _CPNativeInputField.addEventListener("input", function(e) { - _CPNativeInputFieldKeyUpCalled = YES; - _CPNativeInputFieldKeyPressedCalled = YES; - return false; + var inputText = _CPNativeInputField.innerHTML; - }, true); // capture mode + // An empty input could be from a backspace, so we just end the session. + // We pass the final content of the div to the handler. + [self _endInputSessionWithString:inputText]; + + }, true); _CPNativeInputField.onpaste = function(e) { + e.preventDefault(); + var nativeClipboard = (e.originalEvent || e).clipboardData, richtext, pasteboard = [CPPasteboard generalPasteboard], @@ -2795,21 +2768,18 @@ var _CPCopyPlaceholder = '-'; if ([currentFirstResponder respondsToSelector:@selector(isRichText)] && ![currentFirstResponder isRichText]) isPlain = YES; - // this is the rich chrome / FF codepath (where we can use RTF directly) - if ((richtext = nativeClipboard.getData('text/rtf')) && !(!!(e.originalEvent || e).shiftKey) && !isPlain) + // Rich text path for browsers supporting text/rtf + // handle shift key to trigger plain text paste + if (!isPlain && (richtext = nativeClipboard.getData('text/rtf')) && !(!!(e.originalEvent || e).shiftKey)) { - e.preventDefault(); - // setTimeout to prevent flickering in FF setTimeout(function(){ [currentFirstResponder insertText:[[_CPRTFParser new] parseRTF:richtext]] }, 20); - - return false; + return; } - // plain is the same in all browsers... - + // Plain text for all other cases var data = e.clipboardData.getData('text/plain'), cappString = [pasteboard stringForType:CPStringPboardType]; @@ -2819,50 +2789,54 @@ var _CPCopyPlaceholder = '-'; [pasteboard setString:data forType:CPStringPboardType]; } - setTimeout(function(){ // prevent dom-flickering (only needed for FF) + setTimeout(function(){ // prevent dom-flickering [currentFirstResponder paste:self]; }, 20); - - return false; }; - if (CPBrowserIsEngine(CPGeckoBrowserEngine)) + // Unify oncopy/oncut for all browsers + _CPNativeInputField.oncopy = function(e) { - _CPNativeInputField.oncopy = function(e) - { - var pasteboard = [CPPasteboard generalPasteboard], - string, - currentFirstResponder = [[CPApp keyWindow] firstResponder]; + e.preventDefault(); + var pasteboard = [CPPasteboard generalPasteboard], + currentFirstResponder = [[CPApp keyWindow] firstResponder]; - [currentFirstResponder copy:self]; + [currentFirstResponder copy:self]; - var stringForPasting = [pasteboard stringForType:CPStringPboardType]; - e.clipboardData.setData('text/plain', stringForPasting); + var stringForPasting = [pasteboard stringForType:CPStringPboardType]; + e.clipboardData.setData('text/plain', stringForPasting); - return false; - }; - - _CPNativeInputField.oncut = function(e) - { - var pasteboard = [CPPasteboard generalPasteboard], - string, - currentFirstResponder = [[CPApp keyWindow] firstResponder]; - - // prevent dom-flickering - setTimeout(function(){ - [currentFirstResponder cut:self]; - }, 20); - - // this is necessary because cut will only execute in the future - [currentFirstResponder copy:self]; - - var stringForPasting = [pasteboard stringForType:CPStringPboardType]; - - e.clipboardData.setData('text/plain', stringForPasting); - - return false; + var rtfForPasting = [pasteboard stringForType:CPRTFPboardType]; + if (rtfForPasting) { + e.clipboardData.setData('text/rtf', rtfForPasting); } - } + }; + + _CPNativeInputField.oncut = function(e) + { + e.preventDefault(); + var pasteboard = [CPPasteboard generalPasteboard], + currentFirstResponder = [[CPApp keyWindow] firstResponder]; + + // This is necessary because cut will only execute in the future. + // We copy first to populate the clipboard data for the event. + [currentFirstResponder copy:self]; + + var stringForPasting = [pasteboard stringForType:CPStringPboardType]; + e.clipboardData.setData('text/plain', stringForPasting); + var rtfForPasting = [pasteboard stringForType:CPRTFPboardType]; + + if (rtfForPasting) + { + e.clipboardData.setData('text/rtf', rtfForPasting); + } + + // Then, perform the actual cut operation from the text view. + // setTimeout prevents DOM flickering. + setTimeout(function(){ + [currentFirstResponder cut:self]; + }, 20); + }; #endif } From 266965f5061970fa4043c3815b54424ed692f0be Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 2 Jul 2025 07:50:46 +0200 Subject: [PATCH 12/40] improved: native input manager --- AppKit/CPTextView/CPTextView.j | 289 +++++++++++---------------------- 1 file changed, 92 insertions(+), 197 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 4a8d0bc3a..c2357c51d 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -1012,35 +1012,6 @@ Sets the selection to a range of characters in response to user action. } #endif - -// interface to the _CPNativeInputManager -- (void)_activateNativeInputElement:(DOMElement)aNativeField -{ - var attributes = [[self typingAttributes] copy]; - - // make it invisible - [attributes setObject:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] forKey:CPForegroundColorAttributeName]; - - // FIXME: this hack to provide the visual space for the inputmanager should at least bypass the undomanager - var placeholderString = [[CPAttributedString alloc] initWithString:aNativeField.innerHTML attributes:attributes]; - [self insertText:placeholderString]; - - var caretOrigin = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0, _selectionRange.location - 1), 1) inTextContainer:_textContainer].origin; - caretOrigin.y += [_layoutManager _characterOffsetAtLocation:MAX(0, _selectionRange.location - 1)]; - caretOrigin.x += 2; // two pixel offset to the LHS character - var cumulativeOffset = [self _cumulativeOffset]; - - -#if PLATFORM(DOM) - aNativeField.style.left = (caretOrigin.x + cumulativeOffset.x) + "px"; - aNativeField.style.top = (caretOrigin.y + cumulativeOffset.y) + "px"; - aNativeField.style.font = [[_typingAttributes objectForKey:CPFontAttributeName] cssString]; - aNativeField.style.color = [[_typingAttributes objectForKey:CPForegroundColorAttributeName] cssString]; -#endif - - [_caret setVisibility:NO]; // hide our caret because now the system caret takes over -} - - (CPArray)selectedRanges { return [_selectionRange]; @@ -1054,7 +1025,12 @@ Sets the selection to a range of characters in response to user action. [[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome) - if (![_CPNativeInputManager isNativeInputFieldActive] && ![_CPNativeInputManager isDeadKey:event]) + // Only call interpretKeyEvents for non-printable keys (navigation, commands). + // Printable characters are handled exclusively by _CPNativeInputManager. + + var key = event.key; + + if (key && (key.length > 1 || (event.modifierFlags & (CPCommandKeyMask | CPAlternateKeyMask | CPControlKeyMask)))) [self interpretKeyEvents:[event]]; [_caret setPermanentlyVisible:YES]; @@ -2626,68 +2602,35 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey", var _CPNativeInputField, - _CPNativeInputFieldActive; + _isComposing = NO; // Flag to track if an IME/dead key session is active. var _CPCopyPlaceholder = '-'; @implementation _CPNativeInputManager : CPObject +// This method is no longer used by the new design, but we keep it +// for any other part of the system that might call it. + (BOOL)isNativeInputFieldActive { - return _CPNativeInputFieldActive; + return NO; } + (void)isDeadKey:(CPEvent)event { #if PLATFORM(DOM) - // This identifies dead key events during the keydown phase on some platforms/layouts. return event._DOMEvent && (event._DOMEvent.key === 'Dead' || event._DOMEvent.key === 'Process'); #endif return NO; } -+ (void)cancelCurrentNativeInputSession -{ - if (!_CPNativeInputFieldActive) - return; - -#if PLATFORM(DOM) - _CPNativeInputField.innerHTML = ''; -#endif - - [self _endInputSessionWithString:@""]; -} - + (void)cancelCurrentInputSessionIfNeeded { - if (!_CPNativeInputFieldActive) - return; - - [self cancelCurrentNativeInputSession]; -} - -+ (void)_endInputSessionWithString:(CPString)aStr -{ - _CPNativeInputFieldActive = NO; - var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - - if (currentFirstResponder && [currentFirstResponder respondsToSelector:@selector(insertText:)]) - { - var placeholderRange = CPMakeRange([currentFirstResponder selectedRange].location - 1, 1); - [currentFirstResponder setSelectedRange:placeholderRange]; - - if(aStr) - [currentFirstResponder insertText:aStr]; - } - #if PLATFORM(DOM) - _CPNativeInputField.innerHTML = ''; + if (_CPNativeInputField) { + _CPNativeInputField.innerHTML = ''; + } + _isComposing = NO; #endif - - [self hideInputElement]; - - if (currentFirstResponder) - [currentFirstResponder updateInsertionPointStateAndRestartTimer:YES]; } + (void)initialize @@ -2695,115 +2638,89 @@ var _CPCopyPlaceholder = '-'; #if PLATFORM(DOM) _CPNativeInputField = document.createElement("div"); _CPNativeInputField.contentEditable = YES; - _CPNativeInputField.style.width = "64px"; - _CPNativeInputField.style.zIndex = 10000; + + // Style the input field to be invisible but focusable _CPNativeInputField.style.position = "absolute"; - _CPNativeInputField.style.visibility = "visible"; - _CPNativeInputField.style.padding = "0px"; - _CPNativeInputField.style.margin = "0px"; + _CPNativeInputField.style.top = "-1000px"; + _CPNativeInputField.style.left = "-1000px"; + _CPNativeInputField.style.width = "1px"; + _CPNativeInputField.style.height = "1px"; + _CPNativeInputField.style.opacity = "0"; + _CPNativeInputField.style.overflow = "hidden"; _CPNativeInputField.style.whiteSpace = "pre"; - _CPNativeInputField.style.outline = "0px solid transparent"; + _CPNativeInputField.style.zIndex = -1; // Put it behind everything document.body.appendChild(_CPNativeInputField); - // The 'keydown' event is used to detect non-printable/control keys - // that should either be handled by the text view directly or should - // cancel the native input session. - _CPNativeInputField.addEventListener("keydown", function(e) + // Central function to handle inserting text into the CPTextView + var handleInput = function(textToInsert) { - // Filter out non-printable keys like modifiers, cursor keys, etc. - // A key with a name longer than one character is typically a non-printable control key. - if (e.key.length > 1 && e.key !== 'Dead' && e.key !== 'Process') - { - if (e.key === 'Enter' || e.key === 'Escape') - [self cancelCurrentInputSessionIfNeeded]; - - // For backspace, if the field is empty, cancel the session. - // Otherwise, let the 'input' event handle the change. - if (e.key === 'Backspace' && _CPNativeInputField.innerHTML.length === 0) - [self cancelCurrentInputSessionIfNeeded]; - - // Let the browser handle other control keys within the contentEditable, - // but don't propagate to our text view. + if (!textToInsert) return; - } var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) + if (currentFirstResponder && [currentFirstResponder respondsToSelector:@selector(insertText:)]) + [currentFirstResponder insertText:textToInsert]; + + // CRUCIAL: Clear the field immediately after grabbing its content. + _CPNativeInputField.innerHTML = ''; + }; + + // Fires for simple key presses (a, b, 1, 2) + _CPNativeInputField.addEventListener('input', function(e) { + // If we are in a composition (e.g., IME), we do nothing. + // We wait for 'compositionend' to get the final, complete text. + if (_isComposing) { return; - - // If not already active, start a new input session. - if (!_CPNativeInputFieldActive) - { - _CPNativeInputFieldActive = YES; - [currentFirstResponder _activateNativeInputElement:_CPNativeInputField]; } + // If not composing, this is a simple character. Handle it immediately. + handleInput(e.target.innerHTML); + }); - }, true); + // Fires when a composition session starts (e.g., user presses a dead key or starts an IME). + _CPNativeInputField.addEventListener('compositionstart', function(e) { + _isComposing = YES; + }); - // The 'input' event is the modern, unified way to handle all character input. - // It fires after a regular keypress, a dead key composition, or an IME insertion. - // This replaces the need for complex keypress/keyup timing logic. - _CPNativeInputField.addEventListener("input", function(e) - { - var inputText = _CPNativeInputField.innerHTML; - - // An empty input could be from a backspace, so we just end the session. - // We pass the final content of the div to the handler. - [self _endInputSessionWithString:inputText]; - - }, true); + // Fires when the composition is finished. + _CPNativeInputField.addEventListener('compositionend', function(e) { + // The composition is over. `e.data` has the final string (e.g., "é"). + handleInput(e.data); + _isComposing = NO; + }); + // PASTE handler _CPNativeInputField.onpaste = function(e) { e.preventDefault(); + var nativeClipboard = (e.originalEvent || e).clipboardData; + var richtext; + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; + var isPlain = ![currentFirstResponder isRichText]; - var nativeClipboard = (e.originalEvent || e).clipboardData, - richtext, - pasteboard = [CPPasteboard generalPasteboard], - currentFirstResponder = [[CPApp keyWindow] firstResponder], - isPlain = NO; - - if ([currentFirstResponder respondsToSelector:@selector(isRichText)] && ![currentFirstResponder isRichText]) - isPlain = YES; - - // Rich text path for browsers supporting text/rtf - // handle shift key to trigger plain text paste - if (!isPlain && (richtext = nativeClipboard.getData('text/rtf')) && !(!!(e.originalEvent || e).shiftKey)) - { - // setTimeout to prevent flickering in FF - setTimeout(function(){ + // Correctly check for shift key to force plain text paste. + if (!isPlain && !e.shiftKey && (richtext = nativeClipboard.getData('text/rtf'))) { + setTimeout(function() { [currentFirstResponder insertText:[[_CPRTFParser new] parseRTF:richtext]] - }, 20); + }, 0); return; } - // Plain text for all other cases - var data = e.clipboardData.getData('text/plain'), - cappString = [pasteboard stringForType:CPStringPboardType]; - - if (cappString != data) - { - [pasteboard declareTypes:[CPStringPboardType] owner:nil]; - [pasteboard setString:data forType:CPStringPboardType]; - } - - setTimeout(function(){ // prevent dom-flickering - [currentFirstResponder paste:self]; - }, 20); + var data = nativeClipboard.getData('text/plain'); + [currentFirstResponder insertText:data]; }; - // Unify oncopy/oncut for all browsers + // COPY handler _CPNativeInputField.oncopy = function(e) { e.preventDefault(); - var pasteboard = [CPPasteboard generalPasteboard], - currentFirstResponder = [[CPApp keyWindow] firstResponder]; + var pasteboard = [CPPasteboard generalPasteboard]; + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - [currentFirstResponder copy:self]; + [currentFirstResponder copy:self]; // This populates the CP pasteboard - var stringForPasting = [pasteboard stringForType:CPStringPboardType]; + var stringForPasting = [pasteboard stringForType:CPStringPboardType] || ''; e.clipboardData.setData('text/plain', stringForPasting); var rtfForPasting = [pasteboard stringForType:CPRTFPboardType]; @@ -2812,85 +2729,63 @@ var _CPCopyPlaceholder = '-'; } }; + // CUT handler _CPNativeInputField.oncut = function(e) { e.preventDefault(); - var pasteboard = [CPPasteboard generalPasteboard], - currentFirstResponder = [[CPApp keyWindow] firstResponder]; + var pasteboard = [CPPasteboard generalPasteboard]; + var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - // This is necessary because cut will only execute in the future. - // We copy first to populate the clipboard data for the event. + // First, copy the data to populate the clipboard [currentFirstResponder copy:self]; - var stringForPasting = [pasteboard stringForType:CPStringPboardType]; + var stringForPasting = [pasteboard stringForType:CPStringPboardType] || ''; e.clipboardData.setData('text/plain', stringForPasting); var rtfForPasting = [pasteboard stringForType:CPRTFPboardType]; - - if (rtfForPasting) - { + if (rtfForPasting) { e.clipboardData.setData('text/rtf', rtfForPasting); } - // Then, perform the actual cut operation from the text view. - // setTimeout prevents DOM flickering. - setTimeout(function(){ - [currentFirstResponder cut:self]; - }, 20); + // Then, perform the delete part of the cut operation in the text view + [currentFirstResponder delete:self]; }; #endif } + (void)focusForTextView:(CPTextView)currentFirstResponder { - if (![currentFirstResponder respondsToSelector:@selector(_activateNativeInputElement:)]) - return; - - [self hideInputElement]; - #if PLATFORM(DOM) - _CPNativeInputField.focus(); + // The field no longer needs to move. It just needs focus. + if (_CPNativeInputField && document.activeElement !== _CPNativeInputField) { + _CPNativeInputField.focus(); + } #endif - } + (void)focusForClipboardOfTextView:(CPTextView)textview { - #if PLATFORM(DOM) - if (!_CPNativeInputFieldActive && _CPNativeInputField.innerHTML.length == 0) - _CPNativeInputField.innerHTML = _CPCopyPlaceholder; // make sure we have a selection to allow the native pasteboard work in safari + var selectedRange = [textview selectedRange]; + if (selectedRange.length > 0) { + // Put the selected text into the hidden div so the browser can natively copy it. + var textToCopy = [[textview textStorage] substringWithRange:selectedRange]; + _CPNativeInputField.innerHTML = textToCopy; + } else { + // For paste, we just need the field to be focusable. + _CPNativeInputField.innerHTML = _CPCopyPlaceholder; + } [self focusForTextView:textview]; - // select all in the contenteditable div (http://stackoverflow.com/questions/12243898/how-to-select-all-text-in-contenteditable-div) - if (document.body.createTextRange) - { - var range = document.body.createTextRange(); - - range.moveToElementText(_CPNativeInputField); - range.select(); - } - else if (window.getSelection) - { - var selection = window.getSelection(), - range = document.createRange(); - + // Select the content of the hidden div so copy/cut works. + if (window.getSelection && document.createRange) { + var selection = window.getSelection(); + var range = document.createRange(); range.selectNodeContents(_CPNativeInputField); selection.removeAllRanges(); selection.addRange(range); } #endif - -} - -+ (void)hideInputElement -{ - -#if PLATFORM(DOM) - _CPNativeInputField.style.top = "-10000px"; - _CPNativeInputField.style.left = "-10000px"; -#endif - } @end From 6b2494ede49b6284df507b918aed7b320c38ae4e Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 2 Jul 2025 19:54:52 +0200 Subject: [PATCH 13/40] fixed: arrow navigation --- AppKit/CPEvent.j | 24 ++++++- AppKit/CPTextView/CPTextView.j | 23 ++++--- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 75 +++++++++++++--------- 3 files changed, 78 insertions(+), 44 deletions(-) diff --git a/AppKit/CPEvent.j b/AppKit/CPEvent.j index 7bc06d501..41d1cefef 100644 --- a/AppKit/CPEvent.j +++ b/AppKit/CPEvent.j @@ -70,6 +70,7 @@ var _CPEventPeriodicEventPeriod = 0, BOOL _isARepeat; unsigned _keyCode; DOMEvent _DOMEvent; + BOOL _isActionKey; int _data1; int _data2; short _subtype; @@ -110,17 +111,18 @@ 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]; } /*! @@ -252,7 +254,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 +266,7 @@ var _CPEventPeriodicEventPeriod = 0, _charactersIgnoringModifiers = unmodCharacters; _isARepeat = isARepeat; _keyCode = code; + _isActionKey = isAnActionKey; _windowNumber = aWindowNumber; } @@ -571,6 +574,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 | CPAlternateKeyMask)) || + + // 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 diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index c2357c51d..938cc7c32 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -1022,16 +1022,18 @@ Sets the selection to a range of characters in response to user action. - (void)keyDown:(CPEvent)event { + [[_window platformWindow] _propagateCurrentDOMEvent:YES]; - [[_window platformWindow] _propagateCurrentDOMEvent:YES]; // for the _CPNativeInputManager (necessary at least on FF and chrome) - - // Only call interpretKeyEvents for non-printable keys (navigation, commands). - // Printable characters are handled exclusively by _CPNativeInputManager. - - var key = event.key; - - if (key && (key.length > 1 || (event.modifierFlags & (CPCommandKeyMask | CPAlternateKeyMask | CPControlKeyMask)))) + if ([event _isActionOrCommandEvent]) + { + // This is a navigation key, action key, or command shortcut. + // Let the Cappuccino framework's key binding system handle it. [self interpretKeyEvents:[event]]; + } + + // This is a normal printable character ('a', '1', '$', 'é'). + // We do nothing, preventing the double-insertion bug. The _CPNativeInputManager + // will capture it from the hidden input field and insert it correctly. [_caret setPermanentlyVisible:YES]; } @@ -2661,7 +2663,10 @@ var _CPCopyPlaceholder = '-'; var currentFirstResponder = [[CPApp keyWindow] firstResponder]; if (currentFirstResponder && [currentFirstResponder respondsToSelector:@selector(insertText:)]) - [currentFirstResponder insertText:textToInsert]; + // setTimeout to prevent flickering + setTimeout(function(){ + [currentFirstResponder insertText:textToInsert] + }, 20); // CRUCIAL: Clear the field immediately after grabbing its content. _CPNativeInputField.innerHTML = ''; diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index a4fc8cae8..ece9c92c3 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -725,16 +725,13 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio // With a few exceptions, all key events are blocked from propagating to // the browser. The logic here allows browser shortcuts (Cmd/Ctrl keys) // and function keys (F1-F12) to pass through, unless explicitly blacklisted. - StopDOMEventPropagation = YES; + StopDOMEventPropagation = YES; var keyCodeForPropagationCheck = aDOMEvent.keyCode || 0; var charForPropagationCheck = String.fromCharCode(keyCodeForPropagationCheck).toLowerCase(); - // Make sure it is not in the blacklists. if (!(CharacterKeysToPrevent[charForPropagationCheck] || KeyCodesToPrevent[keyCodeForPropagationCheck])) { - // It is not in the blacklist, let it through if the ctrl/cmd key is - // also down or it's in the whitelist. if ((modifierFlags & (CPControlKeyMask | CPCommandKeyMask)) || KeyCodesToAllow[keyCodeForPropagationCheck]) StopDOMEventPropagation = NO; } @@ -742,57 +739,75 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio var characters = @"", charactersIgnoringModifiers = @""; - // Grab and store the keyCode for mapping and compatibility. - // This property is deprecated but necessary for the key maps. var keyCode = aDOMEvent.keyCode; if (keyCode in MozKeyCodeToKeyCodeMap) keyCode = MozKeyCodeToKeyCodeMap[keyCode]; + var isActionKey; + var key = aDOMEvent.key; + + // Modern Browser Path (use event.key) + if (key) { + isActionKey = + key === 'Enter' || + key === 'Backspace' || + key === 'Tab' || + key === 'Escape' || + key === 'Delete' || + key.startsWith('Arrow') || + key === 'Home' || + key === 'End' || + key === 'PageUp' || + key === 'PageDown'; + } + // Legacy Browser Fallback (use event.keyCode) + else + { + isActionKey = + (keyCode === 13) || // Enter + (keyCode === 8) || // Backspace + (keyCode === 9) || // Tab + (keyCode === 27) || // Escape + (keyCode === 46) || // Delete + (keyCode >= 37 && keyCode <= 40); // Arrow keys + } + + switch (aDOMEvent.type) { case "keydown": - // For modifier keys, we create a CPFlagsChanged event and stop processing. + // For modifier keys, create a CPFlagsChanged event and stop processing. + // These are always considered "action keys". if ([ModifierKeyCodes containsObject:keyCode]) { event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode]; + characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode isActionKey:YES]; break; } - // Determine if the event is a repeat. Use the modern `event.repeat` property - // with a fallback to our manual state tracking for older browsers. var isARepeat = !!aDOMEvent.repeat || (_charCodes[keyCode] != nil); - _charCodes[keyCode] = YES; // Mark the key as down for fallback repeat detection. + _charCodes[keyCode] = YES; - // Determine the character for the event. - // Priority 1: Use the modern `event.key` property. It's the most reliable. if (aDOMEvent.key) { if (aDOMEvent.key.length === 1) { - // This is a printing character (e.g., "a", "P", "#"). characters = aDOMEvent.key; } else { - // This is a named, non-printing key (e.g., "Enter", "ArrowLeft"). - // Map the standard key name to our framework's internal constant. characters = KeyNameToUnicodeMap[aDOMEvent.key] || aDOMEvent.key; } } - // Priority 2: Fallback for older browsers without `event.key`. else { - // First, check if it's a known non-printing key in our legacy map. characters = KeyCodesToUnicodeMap[keyCode]; - // If not, it's likely a printing character. Use the deprecated fromCharCode. - // This is less reliable for international layouts but is the best fallback. if (!characters) { characters = String.fromCharCode(keyCode); - // Manually handle capitalization for this fallback path. + if (modifierFlags & CPShiftKeyMask || _capsLockActive) characters = characters.toUpperCase(); else @@ -800,24 +815,21 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio } } - // This is a simplification; a fully correct implementation would require extensive mapping. charactersIgnoringModifiers = characters.toLowerCase(); + // Pass the determined `isActionKey` flag. event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:keyCode]; + characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:keyCode isActionKey:isActionKey]; break; case "keyup": - // Clear the key's state for our fallback repeat detection. _charCodes[keyCode] = nil; - // Handle the toggling of Caps Lock state. if (keyCode === CPKeyCodes.CAPS_LOCK) { _capsLockActive = !_capsLockActive; - // Update modifierFlags to reflect the new state for this event. if (_capsLockActive) modifierFlags |= CPAlphaShiftKeyMask; else @@ -825,16 +837,15 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio } // For modifier keys, create a CPFlagsChanged event and stop. + // These are always considered "action keys". if ([ModifierKeyCodes containsObject:keyCode]) { event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode]; + characters:nil charactersIgnoringModifiers:nil isARepeat:NO keyCode:keyCode isActionKey:YES]; break; } - // Determine the character for the keyup event using the same logic as keydown - // for consistency, as this event no longer has access to `keypress` state. if (aDOMEvent.key) { if (aDOMEvent.key.length === 1) @@ -852,9 +863,10 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive) characters = charactersIgnoringModifiers; + // Pass the determined `isActionKey` flag for keyup as well. event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags timestamp: timestamp windowNumber:windowNumber context:nil - characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode]; + characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode isActionKey:isActionKey]; break; } @@ -867,12 +879,11 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (event && ![_platformPasteboard windowShouldSuppressKeyEvent]) { [CPApp sendEvent:event]; - [_platformPasteboard windowDidSendKeyEvent:event]; } var didStop = NO; - // Platform pasteboard can overrule the decision to stop propagation. + if ([_platformPasteboard windowShouldStopPropagation] || (StopDOMEventPropagation && ![_platformPasteboard windowShouldNotStopPropagation])) { didStop = YES; From 9e15700f9335ce44b0958346737def58771b974d Mon Sep 17 00:00:00 2001 From: daboe01 Date: Thu, 3 Jul 2025 22:10:57 +0200 Subject: [PATCH 14/40] cleanup stuff --- AppKit/CPTextView/CPTextView.j | 22 ++++--------- AppKit/Platform/DOM/CPPlatformPasteboard.j | 2 +- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 38 ++++++++++------------ 3 files changed, 25 insertions(+), 37 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 938cc7c32..eaaad5466 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2610,13 +2610,6 @@ var _CPCopyPlaceholder = '-'; @implementation _CPNativeInputManager : CPObject -// This method is no longer used by the new design, but we keep it -// for any other part of the system that might call it. -+ (BOOL)isNativeInputFieldActive -{ - return NO; -} - + (void)isDeadKey:(CPEvent)event { #if PLATFORM(DOM) @@ -2707,13 +2700,13 @@ var _CPCopyPlaceholder = '-'; // Correctly check for shift key to force plain text paste. if (!isPlain && !e.shiftKey && (richtext = nativeClipboard.getData('text/rtf'))) { setTimeout(function() { - [currentFirstResponder insertText:[[_CPRTFParser new] parseRTF:richtext]] + [currentFirstResponder paste:self]; }, 0); return; } var data = nativeClipboard.getData('text/plain'); - [currentFirstResponder insertText:data]; + [currentFirstResponder paste:self]; }; // COPY handler @@ -2747,9 +2740,9 @@ var _CPCopyPlaceholder = '-'; var stringForPasting = [pasteboard stringForType:CPStringPboardType] || ''; e.clipboardData.setData('text/plain', stringForPasting); var rtfForPasting = [pasteboard stringForType:CPRTFPboardType]; - if (rtfForPasting) { + + if (rtfForPasting) e.clipboardData.setData('text/rtf', rtfForPasting); - } // Then, perform the delete part of the cut operation in the text view [currentFirstResponder delete:self]; @@ -2760,10 +2753,9 @@ var _CPCopyPlaceholder = '-'; + (void)focusForTextView:(CPTextView)currentFirstResponder { #if PLATFORM(DOM) - // The field no longer needs to move. It just needs focus. - if (_CPNativeInputField && document.activeElement !== _CPNativeInputField) { + + if (_CPNativeInputField && document.activeElement !== _CPNativeInputField) _CPNativeInputField.focus(); - } #endif } @@ -2773,7 +2765,7 @@ var _CPCopyPlaceholder = '-'; var selectedRange = [textview selectedRange]; if (selectedRange.length > 0) { // Put the selected text into the hidden div so the browser can natively copy it. - var textToCopy = [[textview textStorage] substringWithRange:selectedRange]; + var textToCopy = [[[textview textStorage] string] substringWithRange:selectedRange]; _CPNativeInputField.innerHTML = textToCopy; } else { // For paste, we just need the field to be focusable. diff --git a/AppKit/Platform/DOM/CPPlatformPasteboard.j b/AppKit/Platform/DOM/CPPlatformPasteboard.j index 50310503f..102feb670 100644 --- a/AppKit/Platform/DOM/CPPlatformPasteboard.j +++ b/AppKit/Platform/DOM/CPPlatformPasteboard.j @@ -305,7 +305,7 @@ var hasEditableTarget = function(aDOMEvent) location = [[CPApp currentEvent] locationInWindow], anEvent = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil - characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode]; + characters:characters charactersIgnoringModifiers:characters isARepeat:NO keyCode:keyCode isActionKey:YES]; anEvent._data1 = @{ "simulated": YES }; anEvent._DOMEvent = aDOMEvent; diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index ece9c92c3..1ece9b38c 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -746,7 +746,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio var isActionKey; var key = aDOMEvent.key; - // Modern Browser Path (use event.key) if (key) { isActionKey = key === 'Enter' || @@ -760,24 +759,16 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio key === 'PageUp' || key === 'PageDown'; } - // Legacy Browser Fallback (use event.keyCode) else { isActionKey = - (keyCode === 13) || // Enter - (keyCode === 8) || // Backspace - (keyCode === 9) || // Tab - (keyCode === 27) || // Escape - (keyCode === 46) || // Delete - (keyCode >= 37 && keyCode <= 40); // Arrow keys + (keyCode === 13) || (keyCode === 8) || (keyCode === 9) || + (keyCode === 27) || (keyCode === 46) || (keyCode >= 37 && keyCode <= 40); } - switch (aDOMEvent.type) { case "keydown": - // For modifier keys, create a CPFlagsChanged event and stop processing. - // These are always considered "action keys". if ([ModifierKeyCodes containsObject:keyCode]) { event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags @@ -795,9 +786,14 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio { characters = aDOMEvent.key; } + // Correctly handle dead keys to prevent inserting "Dead" + else if (aDOMEvent.key === "Dead" || aDOMEvent.key === "Process") { + characters = @""; + } + // For other named keys, map them or fall back to an empty string. else { - characters = KeyNameToUnicodeMap[aDOMEvent.key] || aDOMEvent.key; + characters = KeyNameToUnicodeMap[aDOMEvent.key] || @""; } } else @@ -807,7 +803,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (!characters) { characters = String.fromCharCode(keyCode); - if (modifierFlags & CPShiftKeyMask || _capsLockActive) characters = characters.toUpperCase(); else @@ -817,7 +812,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio charactersIgnoringModifiers = characters.toLowerCase(); - // Pass the determined `isActionKey` flag. event = [CPEvent keyEventWithType:CPKeyDown location:location modifierFlags:modifierFlags timestamp:timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:isARepeat keyCode:keyCode isActionKey:isActionKey]; @@ -836,8 +830,6 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio modifierFlags &= ~CPAlphaShiftKeyMask; } - // For modifier keys, create a CPFlagsChanged event and stop. - // These are always considered "action keys". if ([ModifierKeyCodes containsObject:keyCode]) { event = [CPEvent keyEventWithType:CPFlagsChanged location:location modifierFlags:modifierFlags @@ -848,22 +840,26 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio if (aDOMEvent.key) { - if (aDOMEvent.key.length === 1) + if (aDOMEvent.key.length === 1) { characters = aDOMEvent.key; + } + // Ensure keyup events also don't produce "Dead" + else if (aDOMEvent.key === "Dead" || aDOMEvent.key === "Process") { + characters = @""; + } else - characters = KeyNameToUnicodeMap[aDOMEvent.key] || aDOMEvent.key; + { + characters = KeyNameToUnicodeMap[aDOMEvent.key] || @""; + } } else - { characters = KeyCodesToUnicodeMap[keyCode] || String.fromCharCode(keyCode); - } charactersIgnoringModifiers = characters.toLowerCase(); if (!(modifierFlags & CPShiftKeyMask) && (modifierFlags & CPCommandKeyMask) && !_capsLockActive) characters = charactersIgnoringModifiers; - // Pass the determined `isActionKey` flag for keyup as well. event = [CPEvent keyEventWithType:CPKeyUp location:location modifierFlags:modifierFlags timestamp: timestamp windowNumber:windowNumber context:nil characters:characters charactersIgnoringModifiers:charactersIgnoringModifiers isARepeat:NO keyCode:keyCode isActionKey:isActionKey]; From b0186bf4f2458dbcc6d9e243a017776da99923f8 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 08:49:08 +0200 Subject: [PATCH 15/40] fixed: smart copy/paste --- AppKit/CPTextView/CPTextView.j | 26 ++++++++++++++++---------- dist/cappuccino/bin/flatten | 2 +- dist/cappuccino/bin/fontinfo | Bin 69888 -> 70736 bytes dist/cappuccino/bin/imagesize | Bin 69536 -> 0 bytes dist/cappuccino/bin/objj2objcskeleton | 22 ++++++++++++++++------ 5 files changed, 33 insertions(+), 17 deletions(-) delete mode 100755 dist/cappuccino/bin/imagesize diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index eaaad5466..3666c321c 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -449,19 +449,26 @@ var kDelegateRespondsTo_textShouldBeginEditing { [super copy:sender]; - if (![self isRichText]) - return; var selectedRange = [self selectedRange], pasteboard = [CPPasteboard generalPasteboard], stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)], richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}]; + if ([self isRichText]) + { [pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType, _CPSmartPboardType, _CPASPboardType] owner:nil]; [pasteboard setString:[stringForPasting._string stringByReplacingOccurrencesOfString:_CPAttachmentCharacterAsString withString:''] forType:CPStringPboardType]; [pasteboard setString:richData forType:CPRTFPboardType]; [pasteboard setString:_previousSelectionGranularity + '' forType:_CPSmartPboardType]; [pasteboard setString:[[CPKeyedArchiver archivedDataWithRootObject:stringForPasting] rawString] forType:_CPASPboardType]; + } + else + { + [pasteboard declareTypes:[CPStringPboardType, _CPSmartPboardType] owner:nil]; + [pasteboard setString:stringForPasting._string forType:CPStringPboardType]; + [pasteboard setString:_previousSelectionGranularity + '' forType:_CPSmartPboardType]; + } } - (void)_pasteString:(id)stringForPasting @@ -510,9 +517,6 @@ var kDelegateRespondsTo_textShouldBeginEditing - (void)paste:(id)sender { - if (![sender isKindOfClass:_CPNativeInputManager] && [[CPApp currentEvent] type] != CPAppKitDefined) - return - [self _pasteString:[self _stringForPasting]]; } @@ -2697,9 +2701,11 @@ var _CPCopyPlaceholder = '-'; var currentFirstResponder = [[CPApp keyWindow] firstResponder]; var isPlain = ![currentFirstResponder isRichText]; - // Correctly check for shift key to force plain text paste. - if (!isPlain && !e.shiftKey && (richtext = nativeClipboard.getData('text/rtf'))) { - setTimeout(function() { + // Check for shift key to force plain text paste. + if (!isPlain && !e.shiftKey && (richtext = nativeClipboard.getData('text/rtf'))) + { + setTimeout(function() + { [currentFirstResponder paste:self]; }, 0); return; @@ -2722,9 +2728,9 @@ var _CPCopyPlaceholder = '-'; e.clipboardData.setData('text/plain', stringForPasting); var rtfForPasting = [pasteboard stringForType:CPRTFPboardType]; - if (rtfForPasting) { + + if (rtfForPasting) e.clipboardData.setData('text/rtf', rtfForPasting); - } }; // CUT handler diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten index 1df033252..80b9ecf8e 100755 --- a/dist/cappuccino/bin/flatten +++ b/dist/cappuccino/bin/flatten @@ -233,7 +233,7 @@ ObjectiveJFlattener.prototype.serializeFunctions = function() { var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK var relative = this.rootPath.relative(path).toString(); - this.functionsBuffer.push("ObjectiveJ.StaticResource._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); + this.functionsBuffer.push("ObjectiveJ.FileExecutable._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); } var bundle = this.context.global.CFBundle.bundleContainingURL(path); diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo index f1ff06158a2525d34541aff240713c0067bd8633..1a91ba1c8040bbcfe787719cb7e44c0e7578d180 100755 GIT binary patch literal 70736 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 delta 3394 zcmZ`*3s6+o89w`DsqFGx+h670NB67 z0BER_-s?-eqvet#M>G=>)tO2(O5#IV%?yIz_H@(>o1IN<&UUZ~LaljSEpq}>J}u!b zVr7xn4nNt^c`Qus7M+>LN3Ns}%e^# zxB<8vC=Ez+f{-lWvAl8B06|2vfltV9N$Vy>N(-^UM2r-KiqFN|))av-q#`Yxy;N9bap; zbl)tk_TTZ}vjob6WVpw18Q|(jmB3tzoS61`(Bi_#J{o&92UPh z1kMEhI4Gn~<~Ls&Pedbs907*Sm!KzryabTV!B&|iP}1kEA|#@JLkYP4ll)U(?U-NX zAFB>59U7~hoVp=??dgX2CkHG6b144R477I-%gz1=@l%E%bP;IYh+YSQ<68b0W_{yhNVO;gS%)DY9|CFu^Cw_= zYy98vk0JR4z<0VfKmeuBn;21b1(;^GjCA5h9;5Oo%! z21?$Q5G{eau*LtS6tcp9AL}t6+`C0LOp)vbxba+Y#jpKaRba;@D5&``bSwIxt2;VA z7m6Rkl)~s0(;9<9JrV}$1}Ip05ODcBLvl+{l?1>wLG3sMukeR!;;Ta7kO9ZS>T&*{ z{zEzOLM8vvr;O+WxQhNk^R7t8bcndWOpKTit&nIF3W1UdG-GYnqaG}e!0VceCM^D; zqR<$gLFfq+Os>atD9(nKXg?6aV+ z31%YzZ;ceFP(~ny?u;8q%gFHMF*P z*19~KSK6ALWp3At&N8}5o0%b<<}Q0}yUpcs6Pw%aY;nLP>2T5{?Mzys&7!~17U$a9 z+Q4r0w7Xgw%Sc`AGo_MD95AkhP+wdpy{^rjho{UUm=x(7xTUe73@bt6;B-ll&Mk6b zok~V|ozWY{djeYYs%w5>{$De{>i_urm>(8vvT=m;M>tsp975~7 z#q3f}VLXR%BjZaMk6zvlOz&cR7vq%i1C0NM@!vCkj`5p}$4H6gkT{lM4&(C}UyNKl zEc;jK_Gyz6^9p^6wAC}orWEl_k~sS8sTu0k?y#(fqfMs_THkqCezBqDpP?MD&)CBW z&n;VCDOA?*R;!RNthBNPoEn_WNf&anyU_~EP1-duOuVAoolQ=g+evy-I7e5LL;9bp zcZpl2N6!J=GqAsS4{VV74v!Nz8S!0>rjZSKJUTtb6L%kGxNjgw(UZ#2PX;pOpvDj7 zZEQD$6lpz#xJe2p*gUNbq=&dQ(fn*pO|5pcB>cc4xLRBuG^a()t!*Bm#km!21^{Y< z?dt(3`fwo4I~_hNsom+2_*U2)IY80>&K)4@bVU41z^&d{@~((mC%WL|DnN|GIIl{@Jw!dTys4T{x9RzodJaZ z4tRN|17!j|7So@7$|alo*un|T`0c_^6Rs%KW{b_^Bnv#vZFED}sE~LjJrZ7`>?}12 z^j0`ibN5PI2(t0gw9_+V&CZvc1KBrsh$+yI+k6k&Di|x%8J~FIq82mvzXg|eCJcY(|mto zMx}1;LO?SZmK-?9EuXVz>5=5^X}>L7k@!{fg5Ehp)(=xh-#lb|+cpY89Bmol413={ z-q`FoHlKWPG2x=~EpMW(JNwJmf~muwCO=3yz5WY&c7)^fYphqZ_NR{CzQ7&xoUOR< zAJ@MQUj86?*7l0)Tdq}e?|z(>fA3J|=hi;mdw(7(Fm6j396lJl*4(&vgpTzG?tKj) zn+%nU^QL|O$r072fIIj3og;U;LIdSB8QV|n`;}GwcKVJL7_=y2daz~N;7i#ZKlc9g ziFc;uy>z+dxtB-kDrb~^yD_)tRtD4(7aR|Ea`(y$F95qo>p%S?-EgKtQCwuCBWGsE Q%I;p7hEba639vimze2ENzW@LL diff --git a/dist/cappuccino/bin/imagesize b/dist/cappuccino/bin/imagesize deleted file mode 100755 index 5d67a1ce0a9a1e20577f82ccdfe0d3b3e94143b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69536 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 Z009sH0T2KI5C8!X009sHfo}nU{{v@rhV}pe diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton index b7972dbb3..51ecab38d 100755 --- a/dist/cappuccino/bin/objj2objcskeleton +++ b/dist/cappuccino/bin/objj2objcskeleton @@ -4,14 +4,11 @@ var fs = require("fs"), acorn = require("objj-parser"), - walk = require("objj-parser/util/walk"), + walk = require("acorn-walk"), stream = ObjectiveJ.term; - debugger; - function main(args) { - debugger; args.shift(); if (args.length < 1) @@ -40,6 +37,8 @@ function raise(pos, message) throw syntaxError; } +function ignore(_node, _st, _c) {} + var errors = [], xcc = walk.make( { @@ -115,7 +114,18 @@ var errors = [], else raise(node.loc.start, "Action methods must have exactly one parameter"); } - } + }, + TypeDefStatement: ignore, + ClassStatement: ignore, + MessageSendExpression: ignore, + GlobalStatement: ignore, + ProtocolDeclarationStatement: ignore, + ArrayLiteral: ignore, + Reference: ignore, + DictionaryLiteral: ignore, + Dereference: ignore, + ImportStatement: ignore, + SelectorLiteralExpression: ignore } ); @@ -154,7 +164,7 @@ function parser(args) outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), source = fs.readFileSync(sourcePath, { encoding: "utf8" }), - tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath }), + tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath, ecmaVersion: 2022 }), classesInformation = [], ObjectiveCSource = "", ObjectiveCHeader = "", From d12770c65d4962fa5b0db9413e924681dda7cbde Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 08:57:29 +0200 Subject: [PATCH 16/40] fixed: testcase for keyevent --- Tests/AppKit/CPResponderTest.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AppKit/CPResponderTest.j b/Tests/AppKit/CPResponderTest.j index 4c3159ab3..5c4729b71 100644 --- a/Tests/AppKit/CPResponderTest.j +++ b/Tests/AppKit/CPResponderTest.j @@ -45,7 +45,7 @@ var keyEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:character charactersIgnoringModifiers:character isARepeat:NO keyCode:keyCode]; + characters:character charactersIgnoringModifiers:character isARepeat:NO keyCode:keyCode isActionKey:YES]; [responder interpretKeyEvents:[keyEvent]]; [self assert:[selector] equals:responder.doCommandCalls]; } From a0136e21442d6f8fcccd6baf0518a15afd3ae3e2 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 08:58:57 +0200 Subject: [PATCH 17/40] removed accidentally committed stuff --- dist/cappuccino/bin/flatten | 368 --------------- dist/cappuccino/bin/fontinfo | Bin 70736 -> 0 bytes dist/cappuccino/bin/objj2objcskeleton | 623 -------------------------- 3 files changed, 991 deletions(-) delete mode 100755 dist/cappuccino/bin/flatten delete mode 100755 dist/cappuccino/bin/fontinfo delete mode 100755 dist/cappuccino/bin/objj2objcskeleton diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten deleted file mode 100755 index 80b9ecf8e..000000000 --- a/dist/cappuccino/bin/flatten +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/env objj - -require("narwhal").ensureEngine("rhino"); - -@import - -@import "../lib/cappuccino/objj-analysis-tools.j" - -var FILE = require("file"); -var OS = require("os"); -var UTIL = require("narwhal/util"); - -var CACHEMANIFEST = require("objective-j/cache-manifest"); - -var stream = require("narwhal/term").stream; -var parser = new (require("narwhal/args").Parser)(); - -parser.usage("INPUT_PROJECT OUTPUT_PROJECT"); -parser.help("Combine a Cappuccino application into a single JavaScript file."); - -parser.option("-m", "--main", "main") - .def("main.j") - .set() - .help("The relative path (from INPUT_PROJECT) to the main file (default: 'main.j')"); - -parser.option("-F", "--framework", "frameworks") - .push() - .help("Add a frameworks directory, relative to INPUT_PROJECT (default: ['Frameworks'])"); - -parser.option("-P", "--path", "paths") - .push() - .help("Add a path (relative to the application root) to inline."); - -parser.option("-f", "--force", "force") - .def(false) - .set(true) - .help("Force overwriting OUTPUT_PROJECT if it exists"); - -parser.option("--index", "index") - .def("index.html") - .set() - .help("The root HTML file to modify (default: index.html)"); - -parser.option("-s", "--split", "number", "split") - .natural() - .def(0) - .help("Split into multiple files"); - -parser.option("-c", "--compressor", "compressor") - .def("shrinksafe") - .set() - .help("Select a compressor to use (closure-compiler, yuicompressor, shrinksafe), or \"none\" (default: shrinksafe)"); - -parser.option("--manifest", "manifest") - .set(true) - .help("Generate HTML5 cache manifest."); - -parser.option("-v", "--verbose", "verbose") - .def(false) - .set(true) - .help("Verbose logging"); - -parser.helpful(); - -function main(args) -{ - var options = parser.parse(args); - - if (options.args.length < 2) { - parser.printUsage(options); - return; - } - - var rootPath = FILE.path(options.args[0]).join("").absolute(); - var outputPath = FILE.path(options.args[1]).join("").absolute(); - - if (outputPath.exists()) { - if (options.force) { - // FIXME: why doesn't this work?! - //outputPath.rmtree(); - OS.system(["rm", "-rf", outputPath]); - } else { - stream.print("\0red(OUTPUT_PROJECT " + outputPath + " exists. Use -f to overwrite.\0)"); - OS.exit(1); - } - } - - options.frameworks.push("Frameworks"); - - var mainPath = String(rootPath.join(options.main)); - var frameworks = options.frameworks.map(function(framework) { return rootPath.join(framework); }); - var environment = "Browser"; - - stream.print("\0yellow("+Array(81).join("=")+"\0)"); - stream.print("Application root: \0green(" + rootPath + "\0)"); - stream.print("Output directory: \0green(" + outputPath + "\0)"); - - stream.print("\0yellow("+Array(81).join("=")+"\0)"); - stream.print("Main file: \0green(" + mainPath + "\0)"); - stream.print("Frameworks: \0green(" + frameworks + "\0)"); - stream.print("Environment: \0green(" + environment + "\0)"); - - var flattener = new ObjectiveJFlattener(rootPath); - - flattener.options = options; - - flattener.setIncludePaths(frameworks); - flattener.setEnvironments([environment, "ObjJ"]); - - print("Loading application."); - flattener.load(mainPath); - - print("Loading default theme."); - flattener.require("objective-j").objj_eval("("+(function() { - - var defaultThemeName = [CPApplication defaultThemeName], - bundle = nil; - - if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2") - bundle = [CPBundle bundleForClass:[CPApplication class]]; - else - bundle = [CPBundle mainBundle]; - - var blend = [[CPThemeBlend alloc] initWithContentsOfURL:[bundle pathForResource:defaultThemeName + @".blend"]]; - [blend loadWithDelegate:nil]; - - })+")")(); - - var applicationJSs = flattener.buildApplicationJS(); - - FILE.copyTree(rootPath, outputPath); - - applicationJSs.forEach(function(applicationJS, n) { - var name = "Application"+(n||"")+".js"; - if (options.compressor === "none") { - print("skipping compression: " + name); - } else { - print("compressing: " + name); - applicationJS = require("minify/"+options.compressor).compress(applicationJS, { charset : "UTF-8", useServer : true }); - } - outputPath.join(name).write(applicationJS, { charset : "UTF-8" }); - }); - - rewriteMainHTML(outputPath.join(options.index)); - - if (options.manifest) { - CACHEMANIFEST.generateManifest(outputPath, { - index : outputPath.join(options.index), - exclude : Object.keys(flattener.filesToCache).map(function(path) { return outputPath.join(path).toString(); }) - }); - } -} - -// ObjectiveJFlattener inherits from ObjectiveJRuntimeAnalyzer -function ObjectiveJFlattener(rootPath) { - ObjectiveJRuntimeAnalyzer.apply(this, arguments); - - this.filesToCache = {}; - this.fileCacheBuffer = []; - this.functionsBuffer = []; -} - -ObjectiveJFlattener.prototype = Object.create(ObjectiveJRuntimeAnalyzer.prototype); - -ObjectiveJFlattener.prototype.buildApplicationJS = function() { - - this.setupFileCache(); - this.serializeFunctions(); - this.serializeFileCache(); - - var additions = FILE.read(FILE.join(FILE.dirname(module.path), "..", "..", "cappuccino", "lib", "cappuccino", "objj-flatten-additions.js"), { charset:"UTF-8" }); - - var applicationJSs = []; - - if (this.options.split === 0) { - var buffer = []; - buffer.push("var baseURL = new CFURL(\".\", ObjectiveJ.pageURL);"); - buffer.push(additions); - buffer.push(this.fileCacheBuffer.join("\n")); - buffer.push(this.functionsBuffer.join("\n")); - buffer.push("ObjectiveJ.bootstrap();"); - applicationJSs.push(buffer.join("\n")); - } else { - var appFilesCount = this.options.split; - - var buffers = []; - for (var i = 0; i <= appFilesCount; i++) - buffers.push([]); - - var chunks = this.fileCacheBuffer.concat(this.functionsBuffer).sort(function(chunkA, chunkB) { - return chunkA.length - chunkB.length; - }); - - // try to equally distribute the chunks. could be better but good enough for now. - var n = 0; - while (chunks.length) { - buffers[(n++ % appFilesCount) + 1].push(chunks.pop()); - } - - buffers[0].push("var baseURL = new CFURL(\".\", ObjectiveJ.pageURL);"); - buffers[0].push(additions); - - buffers[0].push("var appFilesCount = " + appFilesCount +";"); - buffers[0].push("for (var i = 1; i <= appFilesCount; i++) {"); - buffers[0].push(" var script = document.createElement(\"script\");"); - buffers[0].push(" script.src = \"Application\"+i+\".js\";"); - buffers[0].push(" script.charset = \"UTF-8\";"); - buffers[0].push(" script.onload = function() { if (--appFilesCount === 0) ObjectiveJ.bootstrap(); };"); - buffers[0].push(" document.getElementsByTagName(\"head\")[0].appendChild(script);"); - buffers[0].push("}"); - - buffers.forEach(function(buffer) { - applicationJSs.push(buffer.join("\n")); - }); - } - - return applicationJSs; -} - -ObjectiveJFlattener.prototype.serializeFunctions = function() { - var inlineFunctions = true;//this.options.inlineFunctions; - - var outputFiles = {}; - - var _cachedExecutableFunctions = {}; - - this.require("objective-j").FileExecutable.allFileExecutables().forEach(function(executable) { - var path = executable.path(); - - if (inlineFunctions) - { - // stringify the function, replacing arguments - var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK - - var relative = this.rootPath.relative(path).toString(); - this.functionsBuffer.push("ObjectiveJ.FileExecutable._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); - } - - var bundle = this.context.global.CFBundle.bundleContainingURL(path); - if (bundle && bundle.infoDictionary()) - { - var executablePath = bundle.executablePath(), - relativeToBundle = FILE.relative(FILE.join(bundle.path(), ""), path); - - if (executablePath) - { - if (inlineFunctions) - { - // remove the code since we're inlining the functions - executable._code = "alert("+JSON.stringify(relativeToBundle)+");"; - } - - if (!outputFiles[executablePath]) - { - outputFiles[executablePath] = []; - outputFiles[executablePath].push("@STATIC;1.0;"); - } - - var fileContents = executable.toMarkedString(); - - outputFiles[executablePath].push("p;" + relativeToBundle.length + ";" + relativeToBundle); - outputFiles[executablePath].push("t;" + fileContents.length + ";" + fileContents); - - // stream.print("Adding \0green(" + this.rootPath.relative(path) + "\0) to \0cyan(" + this.rootPath.relative(executablePath) + "\0)"); - } - } - else - CPLog.warn("No bundle (or info dictionary for) " + rootPath.relative(path)); - }, this); - - for (var executablePath in outputFiles) - { - var relative = this.rootPath.relative(executablePath).toString(); - var contents = outputFiles[executablePath].join(""); - this.filesToCache[relative] = contents; - } -} - -ObjectiveJFlattener.prototype.serializeFileCache = function() { - for (var relative in this.filesToCache) { - var contents = this.filesToCache[relative]; - print("caching: " + relative + " => " + (contents == null ? 404 : 200)); - if (contents == null) - this.fileCacheBuffer.push("CFHTTPRequest._cacheRequest(new CFURL("+JSON.stringify(relative)+", baseURL), 404);"); - else - this.fileCacheBuffer.push("CFHTTPRequest._cacheRequest(new CFURL("+JSON.stringify(relative)+", baseURL), 200, {}, "+JSON.stringify(contents)+");"); - } -} - -ObjectiveJFlattener.prototype.setupFileCache = function() { - var paths = {}; - - UTIL.update(paths, this.requestedURLs); - - this.options.paths.forEach(function(relativePath) { - paths[this.rootPath.join(relativePath)] = true; - }, this); - - Object.keys(paths).forEach(function(absolute) { - var relative = this.rootPath.relative(absolute).toString(); - if (relative.indexOf("..") === 0) - { - print("skipping (parent of app root): " + absolute); - return; - } - - if (FILE.isFile(absolute)) - { - // if (this.options.maxCachedSize && FILE.size(absolute) > this.options.maxCachedSize) - // { - // print("skipping (larger than "+this.options.maxCachedSize+" bytes): " + absolute); - // return; - // } - - var contents = FILE.read(absolute, { charset : "UTF-8" }); - this.filesToCache[relative] = contents; - } else { - this.filesToCache[relative] = null; - } - }, this); -} - -// "$1" is the matching indentation -var scriptTagsBefore = - '$1'; - -var scriptTagsAfter = - '$1'; - -// enable CPLog: -// scriptTagsAfter = '$1\n' + scriptTagsAfter; - -function rewriteMainHTML(indexHTMLPath) { - if (indexHTMLPath.isFile()) { - var indexHTML = indexHTMLPath.read({ charset : "UTF-8" }); - - // inline the Application.js if it's smallish - var applicationJSPath = indexHTMLPath.dirname().join("Application.js"); - if (applicationJSPath.size() < 10*1024) { - // escape any dollar signs by replacing them with two - // then indent by splitting/joining on newlines - scriptTagsAfter = - '$1'; - } - - // attempt to find Objective-J script tag and add ours - var newIndexHTML = indexHTML.replace(/([ \t]+)]+Objective-J\.js[^>]+>(?:\s*<\/script>)?/, - scriptTagsBefore+'\n$&\n'+scriptTagsAfter); - - if (newIndexHTML !== indexHTML) { - stream.print("\0green(Modified: "+indexHTMLPath+".\0)"); - indexHTMLPath.write(newIndexHTML, { charset : "UTF-8" }); - return; - } - } else { - stream.print("\0yellow(Warning: "+indexHTMLPath+" does not exist. Specify an alternate index HTML file with the --index option.\0)"); - } - - stream.print("\0yellow(Warning: Unable to automatically modify "+indexHTMLPath + ".\0)"); - stream.print("\nAdd the following before the Objective-J script tag:"); - stream.print(scriptTagsBefore.replace(/\$1/g, " ")); - stream.print("\nAdd the following after the Objective-J script tag:"); - stream.print(scriptTagsAfter.replace(/\$1/g, " ")); -} diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo deleted file mode 100755 index 1a91ba1c8040bbcfe787719cb7e44c0e7578d180..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70736 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 diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton deleted file mode 100755 index 51ecab38d..000000000 --- a/dist/cappuccino/bin/objj2objcskeleton +++ /dev/null @@ -1,623 +0,0 @@ -#!/usr/bin/env objj - -@import - -var fs = require("fs"), - acorn = require("objj-parser"), - walk = require("acorn-walk"), - stream = ObjectiveJ.term; - -function main(args) -{ - args.shift(); - - if (args.length < 1) - return printUsage(); - - parser(args); -} - -function printUsage() -{ - console.log("objj2objskeleton [FILE] [DESTINATION]"); - console.log("Convert a objective-j file to an objective-c files skeleton (.h and .m)") -} - -// Debug function to print some JS objects -function dump(obj) -{ - console.log(JSON.stringify(obj)); -} - -function raise(pos, message) -{ - var syntaxError = new SyntaxError(message); - syntaxError.line = pos.line; - - throw syntaxError; -} - -function ignore(_node, _st, _c) {} - -var errors = [], - xcc = walk.make( - { - ClassDeclarationStatement: function(node, st, c) - { - var className = node.classname.name, - superclassname = node.superclassname ? node.superclassname.name : "", - declaredOutletsName = [], - classInfo = { - "name": className, - "category": node.categoryname ? node.categoryname.name : "", - "superClass": superclassname, - "outlets": [], - "actions": [], - "actionNames": [] - }; - - if (node.ivardeclarations) - { - for (var i = 0; i < node.ivardeclarations.length; ++i) - { - var ivarDecl = node.ivardeclarations[i], - ivarType = ivarDecl.ivartype ? ivarDecl.ivartype.name : null, - ivarName = ivarDecl.id.name, - ivarHasOutlet = ivarDecl.outlet ? "@outlet" : null; - - if (ivarHasOutlet) - { - if (declaredOutletsName.indexOf(ivarName) !== -1) - raise(ivarDecl.loc.start, "Outlet '" + ivarName + "' declared more than once"); - - declaredOutletsName.push(ivarName); - classInfo.outlets.push({"type": ivarType, "name": ivarName}); - } - } - } - - st.push(classInfo) - - for (var i = 0; i < node.body.length; ++i) - c(node.body[i], classInfo, "Statement"); - }, - - MethodDeclarationStatement: function(node, st, c) - { - var selectors = node.selectors, - arguments = node.arguments, - //methodReturnType = [node.returntype ? node.returntype.name : "id"], - methodHasAction = node.action ? "IBAction" : null, - selector = selectors[0].name, - actionInfo = {"name": selector, "arguments":[]}; - - if (methodHasAction) - { - if (arguments.length == 1) - { - if (st.actionNames.indexOf(selector) !== -1) - raise(node.loc.start, "Action '" + selector + "' declared more than once"); - - st.actionNames.push(selector); - - for (var i = 0; i < arguments.length; i++) - { - var argument = arguments[i], - argumentName = argument.identifier.name, - argumentType = argument.type ? argument.type.name : null; - - actionInfo.arguments.push({"type": argumentType, "name": argumentName}); - } - - st.actions.push(actionInfo) - } - else - raise(node.loc.start, "Action methods must have exactly one parameter"); - } - }, - TypeDefStatement: ignore, - ClassStatement: ignore, - MessageSendExpression: ignore, - GlobalStatement: ignore, - ProtocolDeclarationStatement: ignore, - ArrayLiteral: ignore, - Reference: ignore, - DictionaryLiteral: ignore, - Dereference: ignore, - ImportStatement: ignore, - SelectorLiteralExpression: ignore - } -); - -function compile(node, state, visitor) -{ - function c(node, st, override) - { - visitor[override || node.type](node, st, c); - } - - c(node, state); -}; - -function removeLastSlashIfNecessary(path) -{ - if (path[path.length - 1] == "/") - return path.substring(0, path.length - 1); - - return path; -} - -/* - $1 Full project source path - $2 Destination - $-n name of the cocoa files -*/ -function parser(args) -{ - try - { - var sourcePath = args.shift(), - projectBasePath = removeLastSlashIfNecessary(args.shift()), - outputDirectory = projectBasePath, - baseFilename = [sourcePath lastPathComponent], - baseFilenameWithNoExtension = args.shift() == "-n" ? args.shift() : baseFilename.substring(0, baseFilename.length - 2), - outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), - outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), - source = fs.readFileSync(sourcePath, { encoding: "utf8" }), - tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath, ecmaVersion: 2022 }), - classesInformation = [], - ObjectiveCSource = "", - ObjectiveCHeader = "", - hasErrors = NO; - - compile(tokens, classesInformation, xcc); - - // dump(classesInformation) - - ObjectiveCHeader += - "#import \n" + - '#import "xcc_general_include.h"\n'; - - ObjectiveCSource += "#import \"" + outputHeaderURL.lastPathComponent() + "\"\n"; - - // Traverse each found classes - classesInformation.forEach(function(aClass) - { - // add new class definition - if (aClass.superClass) - ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ : %@", aClass.name, NSCompatibleClassName(aClass.superClass)]; - else - ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ (%@)", NSCompatibleClassName(aClass.name, NO), aClass.category]; - - // add each outlet in header - if (aClass.outlets.length > 0) - ObjectiveCHeader += "\n"; - - aClass.outlets.forEach(function(anOutlet) - { - ObjectiveCHeader += [CPString stringWithFormat:@"\n@property (assign) IBOutlet %@ %@;", NSCompatibleClassName(anOutlet.type, YES), anOutlet.name]; - }); - - if (aClass.actions.length > 0) - ObjectiveCHeader += "\n"; - - // add each action in header - aClass.actions.forEach(function(anAction) - { - ObjectiveCHeader += [CPString stringWithFormat:@"\n- (IBAction)%@:(%@)%@;", anAction.name, anAction.arguments[0].type, anAction.arguments[0].name]; - }); - - if (aClass.outlets.length > 0 || aClass.actions.length > 0) - ObjectiveCHeader += "\n"; - - ObjectiveCHeader += "\n@end\n"; - - // fill up the implementation file - ObjectiveCSource += "\n@implementation " + NSCompatibleClassName(aClass.name, NO) + "\n@end\n"; - }); - - if (ObjectiveCSource.length) - fs.writeFileSync(outputImplementationURL.absoluteString(), ObjectiveCSource, 'utf8'); - - if (ObjectiveCHeader.length) - fs.writeFileSync(outputHeaderURL.absoluteString(), ObjectiveCHeader, 'utf8'); - } - catch (e) - { - [errors addObject:@{ - @"message": e.message, - @"sourcePath": sourcePath, - @"line": e.line - }]; - - hasErrors = YES; - } - - if ([errors count]) - { - var plist = [CPPropertyListSerialization dataFromPropertyList:errors format:CPPropertyListXMLFormat_v1_0]; - - stream.printError([plist rawString]); - - // If there were category warnings, hasErrors is NO, so return a warning status - process.exit(hasErrors ? 1 : 2); - } -} - -function NSCompatibleClassName(aClassName, asPointer) -{ - if (aClassName === "var" || aClassName === "id") - return "id"; - - var prefix = aClassName.substr(0, 2), - asterisk = asPointer ? "*" : ""; - - if (prefix !== "CP") - return aClassName + asterisk; - - var NSClassName = "NS" + aClassName.substr(2); - - if (NSClasses[NSClassName]) - return NSClassName + asterisk; - - if (ReplacementClasses[aClassName]) - return ReplacementClasses[aClassName] + asterisk; - - return aClassName + asterisk; -} - -var ReplacementClasses = { - "CPWebView": "WebView", - "CPRadio": "NSButtonCell", - "CPRadioGroup": "NSMatrix" - }; - -var NSClasses = { - "NSAffineTransform" : YES, - "NSAppleEventDescriptor" : YES, - "NSAppleEventManager" : YES, - "NSAppleScript" : YES, - "NSArchiver" : YES, - "NSArray" : YES, - "NSAssertionHandler" : YES, - "NSAttributedString" : YES, - "NSAutoreleasePool" : YES, - "NSBlockOperation" : YES, - "NSBundle" : YES, - "NSCache" : YES, - "NSCachedURLResponse" : YES, - "NSCalendar" : YES, - "NSCharacterSet" : YES, - "NSClassDescription" : YES, - "NSCloneCommand" : YES, - "NSCloseCommand" : YES, - "NSCoder" : YES, - "NSComparisonPredicate" : YES, - "NSCompoundPredicate" : YES, - "NSCondition" : YES, - "NSConditionLock" : YES, - "NSConnection" : YES, - "NSCountCommand" : YES, - "NSCountedSet" : YES, - "NSCreateCommand" : YES, - "NSData" : YES, - "NSDate" : YES, - "NSDateComponents" : YES, - "NSDateFormatter" : YES, - "NSDecimalNumber" : YES, - "NSDecimalNumberHandler" : YES, - "NSDeleteCommand" : YES, - "NSDeserializer" : YES, - "NSDictionary" : YES, - "NSDirectoryEnumerator" : YES, - "NSDistantObject" : YES, - "NSDistantObjectRequest" : YES, - "NSDistributedLock" : YES, - "NSDistributedNotificationCenter" : YES, - "NSEnumerator" : YES, - "NSError" : YES, - "NSException" : YES, - "NSExistsCommand" : YES, - "NSExpression" : YES, - "NSFileHandle" : YES, - "NSFileManager" : YES, - "NSFileWrapper" : YES, - "NSFormatter" : YES, - "NSGarbageCollector" : YES, - "NSGetCommand" : YES, - "NSHashTable" : YES, - "NSHost" : YES, - "NSHTTPCookie" : YES, - "NSHTTPCookieStorage" : YES, - "NSHTTPURLResponse" : YES, - "NSIndexPath" : YES, - "NSIndexSet" : YES, - "NSIndexSpecifier" : YES, - "NSInputStream" : YES, - "NSInvocation" : YES, - "NSInvocationOperation" : YES, - "NSKeyedArchiver" : YES, - "NSKeyedUnarchiver" : YES, - "NSLocale" : YES, - "NSLock" : YES, - "NSLogicalTest" : YES, - "NSMachBootstrapServer" : YES, - "NSMachPort" : YES, - "NSMapTable" : YES, - "NSMessagePort" : YES, - "NSMessagePortNameServer" : YES, - "NSMetadataItem" : YES, - "NSMetadataQuery" : YES, - "NSMetadataQueryAttributeValueTuple" : YES, - "NSMetadataQueryResultGroup" : YES, - "NSMethodSignature" : YES, - "NSMiddleSpecifier" : YES, - "NSMoveCommand" : YES, - "NSMutableArray" : YES, - "NSMutableAttributedString" : YES, - "NSMutableCharacterSet" : YES, - "NSMutableData" : YES, - "NSMutableDictionary" : YES, - "NSMutableIndexSet" : YES, - "NSMutableSet" : YES, - "NSMutableString" : YES, - "NSMutableURLRequest" : YES, - "NSNameSpecifier" : YES, - "NSNetService" : YES, - "NSNetServiceBrowser" : YES, - "NSNotification" : YES, - "NSNotificationCenter" : YES, - "NSNotificationQueue" : YES, - "NSNull" : YES, - "NSNumber" : YES, - "NSNumberFormatter" : YES, - "NSObject" : YES, - "NSOperation" : YES, - "NSOperationQueue" : YES, - "NSOrthography" : YES, - "NSOutputStream" : YES, - "NSPipe" : YES, - "NSPointerArray" : YES, - "NSPointerFunctions" : YES, - "NSPort" : YES, - "NSPortCoder" : YES, - "NSPortMessage" : YES, - "NSPortNameServer" : YES, - "NSPositionalSpecifier" : YES, - "NSPredicate" : YES, - "NSProcessInfo" : YES, - "NSPropertyListSerialization" : YES, - "NSPropertySpecifier" : YES, - "NSProtocolChecker" : YES, - "NSProxy" : YES, - "NSPurgeableData" : YES, - "NSQuitCommand" : YES, - "NSRandomSpecifier" : YES, - "NSRangeSpecifier" : YES, - "NSRecursiveLock" : YES, - "NSRelativeSpecifier" : YES, - "NSRunLoop" : YES, - "NSScanner" : YES, - "NSScriptClassDescription" : YES, - "NSScriptCoercionHandler" : YES, - "NSScriptCommand" : YES, - "NSScriptCommandDescription" : YES, - "NSScriptExecutionContext" : YES, - "NSScriptObjectSpecifier" : YES, - "NSScriptSuiteRegistry" : YES, - "NSScriptWhoseTest" : YES, - "NSSerializer" : YES, - "NSSet" : YES, - "NSSetCommand" : YES, - "NSSocketPort" : YES, - "NSSocketPortNameServer" : YES, - "NSSortDescriptor" : YES, - "NSSpecifierTest" : YES, - "NSSpellServer" : YES, - "NSStream" : YES, - "NSString" : YES, - "NSTask" : YES, - "NSTextCheckingResult" : YES, - "NSThread" : YES, - "NSTimer" : YES, - "NSTimeZone" : YES, - "NSUnarchiver" : YES, - "NSUndoManager" : YES, - "NSUniqueIDSpecifier" : YES, - "NSURL" : YES, - "NSURLAuthenticationChallenge" : YES, - "NSURLCache" : YES, - "NSURLConnection" : YES, - "NSURLCredential" : YES, - "NSURLCredentialStorage" : YES, - "NSURLDownload" : YES, - "NSURLHandle" : YES, - "NSURLProtectionSpace" : YES, - "NSURLProtocol" : YES, - "NSURLRequest" : YES, - "NSURLResponse" : YES, - "NSUserDefaults" : YES, - "NSValue" : YES, - "NSValueTransformer" : YES, - "NSWhoseSpecifier" : YES, - "NSXMLDocument" : YES, - "NSXMLDTD" : YES, - "NSXMLDTDNode" : YES, - "NSXMLElement" : YES, - "NSXMLNode" : YES, - "NSXMLParser" : YES, - "NSActionCell" : YES, - "NSAffineTransform Additions" : YES, - "NSAlert" : YES, - "NSAnimation" : YES, - "NSAnimationContext" : YES, - "NSAppleScript Additions" : YES, - "NSApplication" : YES, - "NSArrayController" : YES, - "NSATSTypesetter" : YES, - "NSAttributedString Application Kit Additions" : YES, - "NSBezierPath" : YES, - "NSBitmapImageRep" : YES, - "NSBox" : YES, - "NSBrowser" : YES, - "NSBrowserCell" : YES, - "NSBundle Additions" : YES, - "NSButton" : YES, - "NSButtonCell" : YES, - "NSCachedImageRep" : YES, - "NSCell" : YES, - "NSCIImageRep" : YES, - "NSClipView" : YES, - "NSCoder Application Kit Additions" : YES, - "NSCollectionView" : YES, - "NSCollectionViewItem" : YES, - "NSColor" : YES, - "NSColorList" : YES, - "NSColorPanel" : YES, - "NSColorPicker" : YES, - "NSColorSpace" : YES, - "NSColorWell" : YES, - "NSComboBox" : YES, - "NSComboBoxCell" : YES, - "NSControl" : YES, - "NSController" : YES, - "NSCursor" : YES, - "NSCustomImageRep" : YES, - "NSDatePicker" : YES, - "NSDatePickerCell" : YES, - "NSDictionaryController" : YES, - "NSDockTile" : YES, - "NSDocument" : YES, - "NSDocumentController" : YES, - "NSDrawer" : YES, - "NSEPSImageRep" : YES, - "NSEvent" : YES, - "NSFileWrapper" : YES, - "NSFont" : YES, - "NSFontDescriptor" : YES, - "NSFontManager" : YES, - "NSFontPanel" : YES, - "NSForm" : YES, - "NSFormCell" : YES, - "NSGlyphGenerator" : YES, - "NSGlyphInfo" : YES, - "NSGradient" : YES, - "NSGraphicsContext" : YES, - "NSHelpManager" : YES, - "NSImage" : YES, - "NSImageCell" : YES, - "NSImageRep" : YES, - "NSImageView" : YES, - "NSLayoutManager" : YES, - "NSLevelIndicator" : YES, - "NSLevelIndicatorCell" : YES, - "NSMatrix" : YES, - "NSMenu" : YES, - "NSMenuItem" : YES, - "NSMenuItemCell" : YES, - "NSMenuView" : YES, - "NSMutableAttributedString Additions" : YES, - "NSMutableParagraphStyle" : YES, - "NSNib" : YES, - "NSNibConnector" : YES, - "NSNibControlConnector" : YES, - "NSNibOutletConnector" : YES, - "NSObjectController" : YES, - "NSOpenGLContext" : YES, - "NSOpenGLLayer" : YES, - "NSOpenGLPixelBuffer" : YES, - "NSOpenGLPixelFormat" : YES, - "NSOpenGLView" : YES, - "NSOpenPanel" : YES, - "NSOutlineView" : YES, - "NSPageLayout" : YES, - "NSPanel" : YES, - "NSParagraphStyle" : YES, - "NSPasteboard" : YES, - "NSPasteboardItem" : YES, - "NSPathCell" : YES, - "NSPathComponentCell" : YES, - "NSPathControl" : YES, - "NSPDFImageRep" : YES, - "NSPersistentDocument" : YES, - "NSPICTImageRep" : YES, - "NSPopUpButton" : YES, - "NSPopUpButtonCell" : YES, - "NSPredicateEditor" : YES, - "NSPredicateEditorRowTemplate" : YES, - "NSPrinter" : YES, - "NSPrintInfo" : YES, - "NSPrintOperation" : YES, - "NSPrintPanel" : YES, - "NSProgressIndicator" : YES, - "NSResponder" : YES, - "NSRuleEditor" : YES, - "NSRulerMarker" : YES, - "NSRulerView" : YES, - "NSRunningApplication" : YES, - "NSSavePanel" : YES, - "NSScreen" : YES, - "NSScroller" : YES, - "NSScrollView" : YES, - "NSSearchField" : YES, - "NSSearchFieldCell" : YES, - "NSSecureTextField" : YES, - "NSSecureTextFieldCell" : YES, - "NSSegmentedCell" : YES, - "NSSegmentedControl" : YES, - "NSShadow" : YES, - "NSSlider" : YES, - "NSSliderCell" : YES, - "NSSound" : YES, - "NSSpeechRecognizer" : YES, - "NSSpeechSynthesizer" : YES, - "NSSpellChecker" : YES, - "NSSplitView" : YES, - "NSStatusBar" : YES, - "NSStatusItem" : YES, - "NSStepper" : YES, - "NSStepperCell" : YES, - "NSString Application Kit Additions" : YES, - "NSTableCellView" : YES, - "NSTableColumn" : YES, - "NSTableHeaderCell" : YES, - "NSTableHeaderView" : YES, - "NSTableView" : YES, - "NSTabView" : YES, - "NSTabViewItem" : YES, - "NSText" : YES, - "NSTextAttachment" : YES, - "NSTextAttachmentCell" : YES, - "NSTextBlock" : YES, - "NSTextContainer" : YES, - "NSTextField" : YES, - "NSTextFieldCell" : YES, - "NSTextInputContext" : YES, - "NSTextList" : YES, - "NSTextStorage" : YES, - "NSTextTab" : YES, - "NSTextTable" : YES, - "NSTextTableBlock" : YES, - "NSTextView" : YES, - "NSTokenField" : YES, - "NSTokenFieldCell" : YES, - "NSToolbar" : YES, - "NSToolbarItem" : YES, - "NSToolbarItemGroup" : YES, - "NSTouch" : YES, - "NSTrackingArea" : YES, - "NSTreeController" : YES, - "NSTreeNode" : YES, - "NSTypesetter" : YES, - "NSURL Additions" : YES, - "NSUserDefaultsController" : YES, - "NSView" : YES, - "NSViewAnimation" : YES, - "NSViewController" : YES, - "NSWindow" : YES, - "NSWindowController" : YES, - "NSWorkspace" : YES, - "NSPopover": YES, - "NSAppearance" : YES, - "NSVisualEffectView" : YES, - }; From 0eb87f85b4523ccd1057bfd6d6fcd95104316151 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 09:01:12 +0200 Subject: [PATCH 18/40] fixed: accidentally removed files --- dist/cappuccino/bin/flatten | 368 ++++++++++++++++ dist/cappuccino/bin/fontinfo | Bin 0 -> 69888 bytes dist/cappuccino/bin/imagesize | Bin 0 -> 69536 bytes dist/cappuccino/bin/objj2objcskeleton | 613 ++++++++++++++++++++++++++ 4 files changed, 981 insertions(+) create mode 100755 dist/cappuccino/bin/flatten create mode 100755 dist/cappuccino/bin/fontinfo create mode 100755 dist/cappuccino/bin/imagesize create mode 100755 dist/cappuccino/bin/objj2objcskeleton diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten new file mode 100755 index 000000000..1df033252 --- /dev/null +++ b/dist/cappuccino/bin/flatten @@ -0,0 +1,368 @@ +#!/usr/bin/env objj + +require("narwhal").ensureEngine("rhino"); + +@import + +@import "../lib/cappuccino/objj-analysis-tools.j" + +var FILE = require("file"); +var OS = require("os"); +var UTIL = require("narwhal/util"); + +var CACHEMANIFEST = require("objective-j/cache-manifest"); + +var stream = require("narwhal/term").stream; +var parser = new (require("narwhal/args").Parser)(); + +parser.usage("INPUT_PROJECT OUTPUT_PROJECT"); +parser.help("Combine a Cappuccino application into a single JavaScript file."); + +parser.option("-m", "--main", "main") + .def("main.j") + .set() + .help("The relative path (from INPUT_PROJECT) to the main file (default: 'main.j')"); + +parser.option("-F", "--framework", "frameworks") + .push() + .help("Add a frameworks directory, relative to INPUT_PROJECT (default: ['Frameworks'])"); + +parser.option("-P", "--path", "paths") + .push() + .help("Add a path (relative to the application root) to inline."); + +parser.option("-f", "--force", "force") + .def(false) + .set(true) + .help("Force overwriting OUTPUT_PROJECT if it exists"); + +parser.option("--index", "index") + .def("index.html") + .set() + .help("The root HTML file to modify (default: index.html)"); + +parser.option("-s", "--split", "number", "split") + .natural() + .def(0) + .help("Split into multiple files"); + +parser.option("-c", "--compressor", "compressor") + .def("shrinksafe") + .set() + .help("Select a compressor to use (closure-compiler, yuicompressor, shrinksafe), or \"none\" (default: shrinksafe)"); + +parser.option("--manifest", "manifest") + .set(true) + .help("Generate HTML5 cache manifest."); + +parser.option("-v", "--verbose", "verbose") + .def(false) + .set(true) + .help("Verbose logging"); + +parser.helpful(); + +function main(args) +{ + var options = parser.parse(args); + + if (options.args.length < 2) { + parser.printUsage(options); + return; + } + + var rootPath = FILE.path(options.args[0]).join("").absolute(); + var outputPath = FILE.path(options.args[1]).join("").absolute(); + + if (outputPath.exists()) { + if (options.force) { + // FIXME: why doesn't this work?! + //outputPath.rmtree(); + OS.system(["rm", "-rf", outputPath]); + } else { + stream.print("\0red(OUTPUT_PROJECT " + outputPath + " exists. Use -f to overwrite.\0)"); + OS.exit(1); + } + } + + options.frameworks.push("Frameworks"); + + var mainPath = String(rootPath.join(options.main)); + var frameworks = options.frameworks.map(function(framework) { return rootPath.join(framework); }); + var environment = "Browser"; + + stream.print("\0yellow("+Array(81).join("=")+"\0)"); + stream.print("Application root: \0green(" + rootPath + "\0)"); + stream.print("Output directory: \0green(" + outputPath + "\0)"); + + stream.print("\0yellow("+Array(81).join("=")+"\0)"); + stream.print("Main file: \0green(" + mainPath + "\0)"); + stream.print("Frameworks: \0green(" + frameworks + "\0)"); + stream.print("Environment: \0green(" + environment + "\0)"); + + var flattener = new ObjectiveJFlattener(rootPath); + + flattener.options = options; + + flattener.setIncludePaths(frameworks); + flattener.setEnvironments([environment, "ObjJ"]); + + print("Loading application."); + flattener.load(mainPath); + + print("Loading default theme."); + flattener.require("objective-j").objj_eval("("+(function() { + + var defaultThemeName = [CPApplication defaultThemeName], + bundle = nil; + + if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2") + bundle = [CPBundle bundleForClass:[CPApplication class]]; + else + bundle = [CPBundle mainBundle]; + + var blend = [[CPThemeBlend alloc] initWithContentsOfURL:[bundle pathForResource:defaultThemeName + @".blend"]]; + [blend loadWithDelegate:nil]; + + })+")")(); + + var applicationJSs = flattener.buildApplicationJS(); + + FILE.copyTree(rootPath, outputPath); + + applicationJSs.forEach(function(applicationJS, n) { + var name = "Application"+(n||"")+".js"; + if (options.compressor === "none") { + print("skipping compression: " + name); + } else { + print("compressing: " + name); + applicationJS = require("minify/"+options.compressor).compress(applicationJS, { charset : "UTF-8", useServer : true }); + } + outputPath.join(name).write(applicationJS, { charset : "UTF-8" }); + }); + + rewriteMainHTML(outputPath.join(options.index)); + + if (options.manifest) { + CACHEMANIFEST.generateManifest(outputPath, { + index : outputPath.join(options.index), + exclude : Object.keys(flattener.filesToCache).map(function(path) { return outputPath.join(path).toString(); }) + }); + } +} + +// ObjectiveJFlattener inherits from ObjectiveJRuntimeAnalyzer +function ObjectiveJFlattener(rootPath) { + ObjectiveJRuntimeAnalyzer.apply(this, arguments); + + this.filesToCache = {}; + this.fileCacheBuffer = []; + this.functionsBuffer = []; +} + +ObjectiveJFlattener.prototype = Object.create(ObjectiveJRuntimeAnalyzer.prototype); + +ObjectiveJFlattener.prototype.buildApplicationJS = function() { + + this.setupFileCache(); + this.serializeFunctions(); + this.serializeFileCache(); + + var additions = FILE.read(FILE.join(FILE.dirname(module.path), "..", "..", "cappuccino", "lib", "cappuccino", "objj-flatten-additions.js"), { charset:"UTF-8" }); + + var applicationJSs = []; + + if (this.options.split === 0) { + var buffer = []; + buffer.push("var baseURL = new CFURL(\".\", ObjectiveJ.pageURL);"); + buffer.push(additions); + buffer.push(this.fileCacheBuffer.join("\n")); + buffer.push(this.functionsBuffer.join("\n")); + buffer.push("ObjectiveJ.bootstrap();"); + applicationJSs.push(buffer.join("\n")); + } else { + var appFilesCount = this.options.split; + + var buffers = []; + for (var i = 0; i <= appFilesCount; i++) + buffers.push([]); + + var chunks = this.fileCacheBuffer.concat(this.functionsBuffer).sort(function(chunkA, chunkB) { + return chunkA.length - chunkB.length; + }); + + // try to equally distribute the chunks. could be better but good enough for now. + var n = 0; + while (chunks.length) { + buffers[(n++ % appFilesCount) + 1].push(chunks.pop()); + } + + buffers[0].push("var baseURL = new CFURL(\".\", ObjectiveJ.pageURL);"); + buffers[0].push(additions); + + buffers[0].push("var appFilesCount = " + appFilesCount +";"); + buffers[0].push("for (var i = 1; i <= appFilesCount; i++) {"); + buffers[0].push(" var script = document.createElement(\"script\");"); + buffers[0].push(" script.src = \"Application\"+i+\".js\";"); + buffers[0].push(" script.charset = \"UTF-8\";"); + buffers[0].push(" script.onload = function() { if (--appFilesCount === 0) ObjectiveJ.bootstrap(); };"); + buffers[0].push(" document.getElementsByTagName(\"head\")[0].appendChild(script);"); + buffers[0].push("}"); + + buffers.forEach(function(buffer) { + applicationJSs.push(buffer.join("\n")); + }); + } + + return applicationJSs; +} + +ObjectiveJFlattener.prototype.serializeFunctions = function() { + var inlineFunctions = true;//this.options.inlineFunctions; + + var outputFiles = {}; + + var _cachedExecutableFunctions = {}; + + this.require("objective-j").FileExecutable.allFileExecutables().forEach(function(executable) { + var path = executable.path(); + + if (inlineFunctions) + { + // stringify the function, replacing arguments + var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK + + var relative = this.rootPath.relative(path).toString(); + this.functionsBuffer.push("ObjectiveJ.StaticResource._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); + } + + var bundle = this.context.global.CFBundle.bundleContainingURL(path); + if (bundle && bundle.infoDictionary()) + { + var executablePath = bundle.executablePath(), + relativeToBundle = FILE.relative(FILE.join(bundle.path(), ""), path); + + if (executablePath) + { + if (inlineFunctions) + { + // remove the code since we're inlining the functions + executable._code = "alert("+JSON.stringify(relativeToBundle)+");"; + } + + if (!outputFiles[executablePath]) + { + outputFiles[executablePath] = []; + outputFiles[executablePath].push("@STATIC;1.0;"); + } + + var fileContents = executable.toMarkedString(); + + outputFiles[executablePath].push("p;" + relativeToBundle.length + ";" + relativeToBundle); + outputFiles[executablePath].push("t;" + fileContents.length + ";" + fileContents); + + // stream.print("Adding \0green(" + this.rootPath.relative(path) + "\0) to \0cyan(" + this.rootPath.relative(executablePath) + "\0)"); + } + } + else + CPLog.warn("No bundle (or info dictionary for) " + rootPath.relative(path)); + }, this); + + for (var executablePath in outputFiles) + { + var relative = this.rootPath.relative(executablePath).toString(); + var contents = outputFiles[executablePath].join(""); + this.filesToCache[relative] = contents; + } +} + +ObjectiveJFlattener.prototype.serializeFileCache = function() { + for (var relative in this.filesToCache) { + var contents = this.filesToCache[relative]; + print("caching: " + relative + " => " + (contents == null ? 404 : 200)); + if (contents == null) + this.fileCacheBuffer.push("CFHTTPRequest._cacheRequest(new CFURL("+JSON.stringify(relative)+", baseURL), 404);"); + else + this.fileCacheBuffer.push("CFHTTPRequest._cacheRequest(new CFURL("+JSON.stringify(relative)+", baseURL), 200, {}, "+JSON.stringify(contents)+");"); + } +} + +ObjectiveJFlattener.prototype.setupFileCache = function() { + var paths = {}; + + UTIL.update(paths, this.requestedURLs); + + this.options.paths.forEach(function(relativePath) { + paths[this.rootPath.join(relativePath)] = true; + }, this); + + Object.keys(paths).forEach(function(absolute) { + var relative = this.rootPath.relative(absolute).toString(); + if (relative.indexOf("..") === 0) + { + print("skipping (parent of app root): " + absolute); + return; + } + + if (FILE.isFile(absolute)) + { + // if (this.options.maxCachedSize && FILE.size(absolute) > this.options.maxCachedSize) + // { + // print("skipping (larger than "+this.options.maxCachedSize+" bytes): " + absolute); + // return; + // } + + var contents = FILE.read(absolute, { charset : "UTF-8" }); + this.filesToCache[relative] = contents; + } else { + this.filesToCache[relative] = null; + } + }, this); +} + +// "$1" is the matching indentation +var scriptTagsBefore = + '$1'; + +var scriptTagsAfter = + '$1'; + +// enable CPLog: +// scriptTagsAfter = '$1\n' + scriptTagsAfter; + +function rewriteMainHTML(indexHTMLPath) { + if (indexHTMLPath.isFile()) { + var indexHTML = indexHTMLPath.read({ charset : "UTF-8" }); + + // inline the Application.js if it's smallish + var applicationJSPath = indexHTMLPath.dirname().join("Application.js"); + if (applicationJSPath.size() < 10*1024) { + // escape any dollar signs by replacing them with two + // then indent by splitting/joining on newlines + scriptTagsAfter = + '$1'; + } + + // attempt to find Objective-J script tag and add ours + var newIndexHTML = indexHTML.replace(/([ \t]+)]+Objective-J\.js[^>]+>(?:\s*<\/script>)?/, + scriptTagsBefore+'\n$&\n'+scriptTagsAfter); + + if (newIndexHTML !== indexHTML) { + stream.print("\0green(Modified: "+indexHTMLPath+".\0)"); + indexHTMLPath.write(newIndexHTML, { charset : "UTF-8" }); + return; + } + } else { + stream.print("\0yellow(Warning: "+indexHTMLPath+" does not exist. Specify an alternate index HTML file with the --index option.\0)"); + } + + stream.print("\0yellow(Warning: Unable to automatically modify "+indexHTMLPath + ".\0)"); + stream.print("\nAdd the following before the Objective-J script tag:"); + stream.print(scriptTagsBefore.replace(/\$1/g, " ")); + stream.print("\nAdd the following after the Objective-J script tag:"); + stream.print(scriptTagsAfter.replace(/\$1/g, " ")); +} diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo new file mode 100755 index 0000000000000000000000000000000000000000..f1ff06158a2525d34541aff240713c0067bd8633 GIT binary patch literal 69888 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 literal 0 HcmV?d00001 diff --git a/dist/cappuccino/bin/imagesize b/dist/cappuccino/bin/imagesize new file mode 100755 index 0000000000000000000000000000000000000000..5d67a1ce0a9a1e20577f82ccdfe0d3b3e94143b4 GIT binary patch literal 69536 zcmeI5e{fXQ702%;q(+D&L6Bcs*R65Dl6?dcNKyj33_G?93N5|S^|7AZqlV*Zf+pqaePQXj zEdh?1`-qKOUo5z>Q;w)cPgIGhZkW3*{NmOFJLp+&o`NxAF$sik+h zHO_Iijn_*?d$njt)}mc8nwYD%&(iZ-A{^&xWb5-jsitQq%MIRkuUy~O8ZdW@$GlHL zZN_q1-oLh&P+yk2V_Dneym~x0V;-~KiZc6+^=u<P584{%=@%Qg_uS4e4o`;isNiOdiO^j z5l^x7I>U;tvtq8EpXyo9NzTS`wjQN&^g@cE@J!Qk_2T`ep8Xs+&e5|f&1Up_+0xv) zrm>;9-5MS;b(mkqhRkNS^*!Y^v)kq((hE5lG8bGz?Rym3c6!KUHt&#mue>+-nfNK5 zLv6`nDkZ@woFiu|ww$lS5GIqK&rUx(y7#i@Or^j{UPLY>^@J4Gf>Nk29HN+QE;+Ab z{`>xtn-?q^tD_~X2*sh|B|=F$DL&QAJ1W*c9( zgPh0sp6^?5oVHxKkOF3PMHnN{p>DZ{58zyOQgA$j9E#&%cUOeJr7luK8T|qQ=V__ue5tp2 zc-N4ziISI^NzM$^t>R#ys)U=nXPl&Sdi(NBsm41#HD=P#S#M_O6b<=O@1|!w&Ygj( zQ=H~_FUNP+`KYP*yDxRLV(7$OboCDlp30*p^*7(`M+!H1Z}Q4F9=319+a736eU>^y z*MFI0POPPg&8ef_r%6NHekN~+t9jto!hMeZ73ImI{#zZ-oH%(r zolf^m|ynzXQ5D|Bs} zDn?71Vu^@i)CwgYSEC`jcvMJ)5{ecT+g-g{$mns^mUS<1x_VTtyT{<*W;&}`mN}&E zpygaj&X(OWRzB-qNiF+_*11XU^BKz`IxRWfJVESuf#T9BlN^d8pXJVJ&+|cEGW$F@ zpKHxFd;B-n`R;5XPxeIgK>!3m00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`; zKmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l00ck)1V8`;KmY_l z00ck)1STXt!`moq-=+V6=QtdUZW+_rLcyxRMCn$=wR*?kv~gqJq!CM~VO7ype=HUj{e^?$=3J9Blwep57zr)fE&5NV zEpv>YYR@?_I_F}K91Cvjlp}g~K#hh(LJUs#g!;lESvQhFIjBWLD&=N)WLd6ns*go= zLx~!uP<>d@^|fl3noy&is`%p|-J>pSMI{`Lb&5fu7mmruT2wPQT{7Jx$7#(*7ezui zWI56!t6SC1q@l_^s>0WunP&q%u>A!G_y0xyUFsdEqY~Y3Wb|~Z2gC*e`Qw8<5!ZuK+eW-EB$20TyL-Eaq4ru z9aA?`-^0aZ9CLkk9O9&944~Lil8HEd2|2YRT$Y&eNJUG|_{N<0HZv}uCSwoMFBRuo zp?D-G9?FSdGUG|qWbCi>OZ6l142pS;v&d(X&nC~#ImNl2bM8BlyYF+y&37g@JN8Y; z9kn+9#QWHMA)^7Unt>R{u*j8pgB#M%`GNv zr5YZeBJwiZg!+z6G^fLyC+KZG@`^K06jA&&%wx}Si~uUHBCD*atXx#1R#q*nSXiYl zUbMKTOQ~5Ltf{D2QW08Q5mc%|A+hM=XD+M0Xim?X))kk3`Q6!n`pZ(!?t{0~>@WQ9 zvsctlUBB4YK41HTJ-_m-o`3JMM=#zn>#^EvO5ce{KbwCv@bD$a@7p`?e&zW2>b`FN z7k$pb*59d9lUsjMuzL5twXGZb4?Z{N!SesTw)EFC>p#D9BAplSExx1nZ1KxWU)nP; z<;eQd;ywR-Yxf(eQ^UI+`12QYU%&njxBmXIj@Z$YT|<-JeD&2$d2Jm9=^gubY}@tr zGXuWVf`=v=Z@F;urH9Tg{;aX!^X*sP^Vrcs5 zzhC^@r|Syde!lRjF6Hst4}MeqiR#a8f1>YD`sNLftg5b_bf5jPH#*~CO*fqWbt_w%>z%H0N%F?yVO5eE+8dnymgYdalQbl$vDM{t z^%zFHR+4&qd)@S@39IhTScE6)l0OlPs|lm8g~ZCKz#TF|F1n1VK4v+T4QZXm^2rx2 z+obj_CkabTr@z(-&FFK}=dRpP^iAdb+3Z~=@idxkB$BE`)sk7hOv;EapG>!&pj%&5 zF>m2jMORFy=WsZNk4<54h2$_0009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI z5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X009sH0T2KI5C8!X Z009sH0T2KI5C8!X009sHfo}nU{{v@rhV}pe literal 0 HcmV?d00001 diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton new file mode 100755 index 000000000..b7972dbb3 --- /dev/null +++ b/dist/cappuccino/bin/objj2objcskeleton @@ -0,0 +1,613 @@ +#!/usr/bin/env objj + +@import + +var fs = require("fs"), + acorn = require("objj-parser"), + walk = require("objj-parser/util/walk"), + stream = ObjectiveJ.term; + + debugger; + +function main(args) +{ + debugger; + args.shift(); + + if (args.length < 1) + return printUsage(); + + parser(args); +} + +function printUsage() +{ + console.log("objj2objskeleton [FILE] [DESTINATION]"); + console.log("Convert a objective-j file to an objective-c files skeleton (.h and .m)") +} + +// Debug function to print some JS objects +function dump(obj) +{ + console.log(JSON.stringify(obj)); +} + +function raise(pos, message) +{ + var syntaxError = new SyntaxError(message); + syntaxError.line = pos.line; + + throw syntaxError; +} + +var errors = [], + xcc = walk.make( + { + ClassDeclarationStatement: function(node, st, c) + { + var className = node.classname.name, + superclassname = node.superclassname ? node.superclassname.name : "", + declaredOutletsName = [], + classInfo = { + "name": className, + "category": node.categoryname ? node.categoryname.name : "", + "superClass": superclassname, + "outlets": [], + "actions": [], + "actionNames": [] + }; + + if (node.ivardeclarations) + { + for (var i = 0; i < node.ivardeclarations.length; ++i) + { + var ivarDecl = node.ivardeclarations[i], + ivarType = ivarDecl.ivartype ? ivarDecl.ivartype.name : null, + ivarName = ivarDecl.id.name, + ivarHasOutlet = ivarDecl.outlet ? "@outlet" : null; + + if (ivarHasOutlet) + { + if (declaredOutletsName.indexOf(ivarName) !== -1) + raise(ivarDecl.loc.start, "Outlet '" + ivarName + "' declared more than once"); + + declaredOutletsName.push(ivarName); + classInfo.outlets.push({"type": ivarType, "name": ivarName}); + } + } + } + + st.push(classInfo) + + for (var i = 0; i < node.body.length; ++i) + c(node.body[i], classInfo, "Statement"); + }, + + MethodDeclarationStatement: function(node, st, c) + { + var selectors = node.selectors, + arguments = node.arguments, + //methodReturnType = [node.returntype ? node.returntype.name : "id"], + methodHasAction = node.action ? "IBAction" : null, + selector = selectors[0].name, + actionInfo = {"name": selector, "arguments":[]}; + + if (methodHasAction) + { + if (arguments.length == 1) + { + if (st.actionNames.indexOf(selector) !== -1) + raise(node.loc.start, "Action '" + selector + "' declared more than once"); + + st.actionNames.push(selector); + + for (var i = 0; i < arguments.length; i++) + { + var argument = arguments[i], + argumentName = argument.identifier.name, + argumentType = argument.type ? argument.type.name : null; + + actionInfo.arguments.push({"type": argumentType, "name": argumentName}); + } + + st.actions.push(actionInfo) + } + else + raise(node.loc.start, "Action methods must have exactly one parameter"); + } + } + } +); + +function compile(node, state, visitor) +{ + function c(node, st, override) + { + visitor[override || node.type](node, st, c); + } + + c(node, state); +}; + +function removeLastSlashIfNecessary(path) +{ + if (path[path.length - 1] == "/") + return path.substring(0, path.length - 1); + + return path; +} + +/* + $1 Full project source path + $2 Destination + $-n name of the cocoa files +*/ +function parser(args) +{ + try + { + var sourcePath = args.shift(), + projectBasePath = removeLastSlashIfNecessary(args.shift()), + outputDirectory = projectBasePath, + baseFilename = [sourcePath lastPathComponent], + baseFilenameWithNoExtension = args.shift() == "-n" ? args.shift() : baseFilename.substring(0, baseFilename.length - 2), + outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), + outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), + source = fs.readFileSync(sourcePath, { encoding: "utf8" }), + tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath }), + classesInformation = [], + ObjectiveCSource = "", + ObjectiveCHeader = "", + hasErrors = NO; + + compile(tokens, classesInformation, xcc); + + // dump(classesInformation) + + ObjectiveCHeader += + "#import \n" + + '#import "xcc_general_include.h"\n'; + + ObjectiveCSource += "#import \"" + outputHeaderURL.lastPathComponent() + "\"\n"; + + // Traverse each found classes + classesInformation.forEach(function(aClass) + { + // add new class definition + if (aClass.superClass) + ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ : %@", aClass.name, NSCompatibleClassName(aClass.superClass)]; + else + ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ (%@)", NSCompatibleClassName(aClass.name, NO), aClass.category]; + + // add each outlet in header + if (aClass.outlets.length > 0) + ObjectiveCHeader += "\n"; + + aClass.outlets.forEach(function(anOutlet) + { + ObjectiveCHeader += [CPString stringWithFormat:@"\n@property (assign) IBOutlet %@ %@;", NSCompatibleClassName(anOutlet.type, YES), anOutlet.name]; + }); + + if (aClass.actions.length > 0) + ObjectiveCHeader += "\n"; + + // add each action in header + aClass.actions.forEach(function(anAction) + { + ObjectiveCHeader += [CPString stringWithFormat:@"\n- (IBAction)%@:(%@)%@;", anAction.name, anAction.arguments[0].type, anAction.arguments[0].name]; + }); + + if (aClass.outlets.length > 0 || aClass.actions.length > 0) + ObjectiveCHeader += "\n"; + + ObjectiveCHeader += "\n@end\n"; + + // fill up the implementation file + ObjectiveCSource += "\n@implementation " + NSCompatibleClassName(aClass.name, NO) + "\n@end\n"; + }); + + if (ObjectiveCSource.length) + fs.writeFileSync(outputImplementationURL.absoluteString(), ObjectiveCSource, 'utf8'); + + if (ObjectiveCHeader.length) + fs.writeFileSync(outputHeaderURL.absoluteString(), ObjectiveCHeader, 'utf8'); + } + catch (e) + { + [errors addObject:@{ + @"message": e.message, + @"sourcePath": sourcePath, + @"line": e.line + }]; + + hasErrors = YES; + } + + if ([errors count]) + { + var plist = [CPPropertyListSerialization dataFromPropertyList:errors format:CPPropertyListXMLFormat_v1_0]; + + stream.printError([plist rawString]); + + // If there were category warnings, hasErrors is NO, so return a warning status + process.exit(hasErrors ? 1 : 2); + } +} + +function NSCompatibleClassName(aClassName, asPointer) +{ + if (aClassName === "var" || aClassName === "id") + return "id"; + + var prefix = aClassName.substr(0, 2), + asterisk = asPointer ? "*" : ""; + + if (prefix !== "CP") + return aClassName + asterisk; + + var NSClassName = "NS" + aClassName.substr(2); + + if (NSClasses[NSClassName]) + return NSClassName + asterisk; + + if (ReplacementClasses[aClassName]) + return ReplacementClasses[aClassName] + asterisk; + + return aClassName + asterisk; +} + +var ReplacementClasses = { + "CPWebView": "WebView", + "CPRadio": "NSButtonCell", + "CPRadioGroup": "NSMatrix" + }; + +var NSClasses = { + "NSAffineTransform" : YES, + "NSAppleEventDescriptor" : YES, + "NSAppleEventManager" : YES, + "NSAppleScript" : YES, + "NSArchiver" : YES, + "NSArray" : YES, + "NSAssertionHandler" : YES, + "NSAttributedString" : YES, + "NSAutoreleasePool" : YES, + "NSBlockOperation" : YES, + "NSBundle" : YES, + "NSCache" : YES, + "NSCachedURLResponse" : YES, + "NSCalendar" : YES, + "NSCharacterSet" : YES, + "NSClassDescription" : YES, + "NSCloneCommand" : YES, + "NSCloseCommand" : YES, + "NSCoder" : YES, + "NSComparisonPredicate" : YES, + "NSCompoundPredicate" : YES, + "NSCondition" : YES, + "NSConditionLock" : YES, + "NSConnection" : YES, + "NSCountCommand" : YES, + "NSCountedSet" : YES, + "NSCreateCommand" : YES, + "NSData" : YES, + "NSDate" : YES, + "NSDateComponents" : YES, + "NSDateFormatter" : YES, + "NSDecimalNumber" : YES, + "NSDecimalNumberHandler" : YES, + "NSDeleteCommand" : YES, + "NSDeserializer" : YES, + "NSDictionary" : YES, + "NSDirectoryEnumerator" : YES, + "NSDistantObject" : YES, + "NSDistantObjectRequest" : YES, + "NSDistributedLock" : YES, + "NSDistributedNotificationCenter" : YES, + "NSEnumerator" : YES, + "NSError" : YES, + "NSException" : YES, + "NSExistsCommand" : YES, + "NSExpression" : YES, + "NSFileHandle" : YES, + "NSFileManager" : YES, + "NSFileWrapper" : YES, + "NSFormatter" : YES, + "NSGarbageCollector" : YES, + "NSGetCommand" : YES, + "NSHashTable" : YES, + "NSHost" : YES, + "NSHTTPCookie" : YES, + "NSHTTPCookieStorage" : YES, + "NSHTTPURLResponse" : YES, + "NSIndexPath" : YES, + "NSIndexSet" : YES, + "NSIndexSpecifier" : YES, + "NSInputStream" : YES, + "NSInvocation" : YES, + "NSInvocationOperation" : YES, + "NSKeyedArchiver" : YES, + "NSKeyedUnarchiver" : YES, + "NSLocale" : YES, + "NSLock" : YES, + "NSLogicalTest" : YES, + "NSMachBootstrapServer" : YES, + "NSMachPort" : YES, + "NSMapTable" : YES, + "NSMessagePort" : YES, + "NSMessagePortNameServer" : YES, + "NSMetadataItem" : YES, + "NSMetadataQuery" : YES, + "NSMetadataQueryAttributeValueTuple" : YES, + "NSMetadataQueryResultGroup" : YES, + "NSMethodSignature" : YES, + "NSMiddleSpecifier" : YES, + "NSMoveCommand" : YES, + "NSMutableArray" : YES, + "NSMutableAttributedString" : YES, + "NSMutableCharacterSet" : YES, + "NSMutableData" : YES, + "NSMutableDictionary" : YES, + "NSMutableIndexSet" : YES, + "NSMutableSet" : YES, + "NSMutableString" : YES, + "NSMutableURLRequest" : YES, + "NSNameSpecifier" : YES, + "NSNetService" : YES, + "NSNetServiceBrowser" : YES, + "NSNotification" : YES, + "NSNotificationCenter" : YES, + "NSNotificationQueue" : YES, + "NSNull" : YES, + "NSNumber" : YES, + "NSNumberFormatter" : YES, + "NSObject" : YES, + "NSOperation" : YES, + "NSOperationQueue" : YES, + "NSOrthography" : YES, + "NSOutputStream" : YES, + "NSPipe" : YES, + "NSPointerArray" : YES, + "NSPointerFunctions" : YES, + "NSPort" : YES, + "NSPortCoder" : YES, + "NSPortMessage" : YES, + "NSPortNameServer" : YES, + "NSPositionalSpecifier" : YES, + "NSPredicate" : YES, + "NSProcessInfo" : YES, + "NSPropertyListSerialization" : YES, + "NSPropertySpecifier" : YES, + "NSProtocolChecker" : YES, + "NSProxy" : YES, + "NSPurgeableData" : YES, + "NSQuitCommand" : YES, + "NSRandomSpecifier" : YES, + "NSRangeSpecifier" : YES, + "NSRecursiveLock" : YES, + "NSRelativeSpecifier" : YES, + "NSRunLoop" : YES, + "NSScanner" : YES, + "NSScriptClassDescription" : YES, + "NSScriptCoercionHandler" : YES, + "NSScriptCommand" : YES, + "NSScriptCommandDescription" : YES, + "NSScriptExecutionContext" : YES, + "NSScriptObjectSpecifier" : YES, + "NSScriptSuiteRegistry" : YES, + "NSScriptWhoseTest" : YES, + "NSSerializer" : YES, + "NSSet" : YES, + "NSSetCommand" : YES, + "NSSocketPort" : YES, + "NSSocketPortNameServer" : YES, + "NSSortDescriptor" : YES, + "NSSpecifierTest" : YES, + "NSSpellServer" : YES, + "NSStream" : YES, + "NSString" : YES, + "NSTask" : YES, + "NSTextCheckingResult" : YES, + "NSThread" : YES, + "NSTimer" : YES, + "NSTimeZone" : YES, + "NSUnarchiver" : YES, + "NSUndoManager" : YES, + "NSUniqueIDSpecifier" : YES, + "NSURL" : YES, + "NSURLAuthenticationChallenge" : YES, + "NSURLCache" : YES, + "NSURLConnection" : YES, + "NSURLCredential" : YES, + "NSURLCredentialStorage" : YES, + "NSURLDownload" : YES, + "NSURLHandle" : YES, + "NSURLProtectionSpace" : YES, + "NSURLProtocol" : YES, + "NSURLRequest" : YES, + "NSURLResponse" : YES, + "NSUserDefaults" : YES, + "NSValue" : YES, + "NSValueTransformer" : YES, + "NSWhoseSpecifier" : YES, + "NSXMLDocument" : YES, + "NSXMLDTD" : YES, + "NSXMLDTDNode" : YES, + "NSXMLElement" : YES, + "NSXMLNode" : YES, + "NSXMLParser" : YES, + "NSActionCell" : YES, + "NSAffineTransform Additions" : YES, + "NSAlert" : YES, + "NSAnimation" : YES, + "NSAnimationContext" : YES, + "NSAppleScript Additions" : YES, + "NSApplication" : YES, + "NSArrayController" : YES, + "NSATSTypesetter" : YES, + "NSAttributedString Application Kit Additions" : YES, + "NSBezierPath" : YES, + "NSBitmapImageRep" : YES, + "NSBox" : YES, + "NSBrowser" : YES, + "NSBrowserCell" : YES, + "NSBundle Additions" : YES, + "NSButton" : YES, + "NSButtonCell" : YES, + "NSCachedImageRep" : YES, + "NSCell" : YES, + "NSCIImageRep" : YES, + "NSClipView" : YES, + "NSCoder Application Kit Additions" : YES, + "NSCollectionView" : YES, + "NSCollectionViewItem" : YES, + "NSColor" : YES, + "NSColorList" : YES, + "NSColorPanel" : YES, + "NSColorPicker" : YES, + "NSColorSpace" : YES, + "NSColorWell" : YES, + "NSComboBox" : YES, + "NSComboBoxCell" : YES, + "NSControl" : YES, + "NSController" : YES, + "NSCursor" : YES, + "NSCustomImageRep" : YES, + "NSDatePicker" : YES, + "NSDatePickerCell" : YES, + "NSDictionaryController" : YES, + "NSDockTile" : YES, + "NSDocument" : YES, + "NSDocumentController" : YES, + "NSDrawer" : YES, + "NSEPSImageRep" : YES, + "NSEvent" : YES, + "NSFileWrapper" : YES, + "NSFont" : YES, + "NSFontDescriptor" : YES, + "NSFontManager" : YES, + "NSFontPanel" : YES, + "NSForm" : YES, + "NSFormCell" : YES, + "NSGlyphGenerator" : YES, + "NSGlyphInfo" : YES, + "NSGradient" : YES, + "NSGraphicsContext" : YES, + "NSHelpManager" : YES, + "NSImage" : YES, + "NSImageCell" : YES, + "NSImageRep" : YES, + "NSImageView" : YES, + "NSLayoutManager" : YES, + "NSLevelIndicator" : YES, + "NSLevelIndicatorCell" : YES, + "NSMatrix" : YES, + "NSMenu" : YES, + "NSMenuItem" : YES, + "NSMenuItemCell" : YES, + "NSMenuView" : YES, + "NSMutableAttributedString Additions" : YES, + "NSMutableParagraphStyle" : YES, + "NSNib" : YES, + "NSNibConnector" : YES, + "NSNibControlConnector" : YES, + "NSNibOutletConnector" : YES, + "NSObjectController" : YES, + "NSOpenGLContext" : YES, + "NSOpenGLLayer" : YES, + "NSOpenGLPixelBuffer" : YES, + "NSOpenGLPixelFormat" : YES, + "NSOpenGLView" : YES, + "NSOpenPanel" : YES, + "NSOutlineView" : YES, + "NSPageLayout" : YES, + "NSPanel" : YES, + "NSParagraphStyle" : YES, + "NSPasteboard" : YES, + "NSPasteboardItem" : YES, + "NSPathCell" : YES, + "NSPathComponentCell" : YES, + "NSPathControl" : YES, + "NSPDFImageRep" : YES, + "NSPersistentDocument" : YES, + "NSPICTImageRep" : YES, + "NSPopUpButton" : YES, + "NSPopUpButtonCell" : YES, + "NSPredicateEditor" : YES, + "NSPredicateEditorRowTemplate" : YES, + "NSPrinter" : YES, + "NSPrintInfo" : YES, + "NSPrintOperation" : YES, + "NSPrintPanel" : YES, + "NSProgressIndicator" : YES, + "NSResponder" : YES, + "NSRuleEditor" : YES, + "NSRulerMarker" : YES, + "NSRulerView" : YES, + "NSRunningApplication" : YES, + "NSSavePanel" : YES, + "NSScreen" : YES, + "NSScroller" : YES, + "NSScrollView" : YES, + "NSSearchField" : YES, + "NSSearchFieldCell" : YES, + "NSSecureTextField" : YES, + "NSSecureTextFieldCell" : YES, + "NSSegmentedCell" : YES, + "NSSegmentedControl" : YES, + "NSShadow" : YES, + "NSSlider" : YES, + "NSSliderCell" : YES, + "NSSound" : YES, + "NSSpeechRecognizer" : YES, + "NSSpeechSynthesizer" : YES, + "NSSpellChecker" : YES, + "NSSplitView" : YES, + "NSStatusBar" : YES, + "NSStatusItem" : YES, + "NSStepper" : YES, + "NSStepperCell" : YES, + "NSString Application Kit Additions" : YES, + "NSTableCellView" : YES, + "NSTableColumn" : YES, + "NSTableHeaderCell" : YES, + "NSTableHeaderView" : YES, + "NSTableView" : YES, + "NSTabView" : YES, + "NSTabViewItem" : YES, + "NSText" : YES, + "NSTextAttachment" : YES, + "NSTextAttachmentCell" : YES, + "NSTextBlock" : YES, + "NSTextContainer" : YES, + "NSTextField" : YES, + "NSTextFieldCell" : YES, + "NSTextInputContext" : YES, + "NSTextList" : YES, + "NSTextStorage" : YES, + "NSTextTab" : YES, + "NSTextTable" : YES, + "NSTextTableBlock" : YES, + "NSTextView" : YES, + "NSTokenField" : YES, + "NSTokenFieldCell" : YES, + "NSToolbar" : YES, + "NSToolbarItem" : YES, + "NSToolbarItemGroup" : YES, + "NSTouch" : YES, + "NSTrackingArea" : YES, + "NSTreeController" : YES, + "NSTreeNode" : YES, + "NSTypesetter" : YES, + "NSURL Additions" : YES, + "NSUserDefaultsController" : YES, + "NSView" : YES, + "NSViewAnimation" : YES, + "NSViewController" : YES, + "NSWindow" : YES, + "NSWindowController" : YES, + "NSWorkspace" : YES, + "NSPopover": YES, + "NSAppearance" : YES, + "NSVisualEffectView" : YES, + }; From b8b988c10b41886015e96a51e91f131fca3d6f7c Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 09:04:26 +0200 Subject: [PATCH 19/40] Update CPResponderTest.j --- Tests/AppKit/CPResponderTest.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AppKit/CPResponderTest.j b/Tests/AppKit/CPResponderTest.j index 5c4729b71..2aba5adc3 100644 --- a/Tests/AppKit/CPResponderTest.j +++ b/Tests/AppKit/CPResponderTest.j @@ -57,7 +57,7 @@ var keyEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask timestamp:0 windowNumber:0 context:nil - characters:CPLeftArrowFunctionKey charactersIgnoringModifiers:CPLeftArrowFunctionKey isARepeat:NO keyCode:CPKeyCodes.LEFT]; + characters:CPLeftArrowFunctionKey charactersIgnoringModifiers:CPLeftArrowFunctionKey isARepeat:NO keyCode:CPKeyCodes.LEFT isActionKey:YES]; [responder interpretKeyEvents:[keyEvent]]; [self assert:[@selector(moveLeftAndModifySelection:)] equals:responder.doCommandCalls]; } From 1c4d9b1d8fc8c83406c8c1445e4363093ec8b2ef Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 12:29:11 +0200 Subject: [PATCH 20/40] Update CPButtonTest.j --- Tests/AppKit/CPButtonTest.j | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/AppKit/CPButtonTest.j b/Tests/AppKit/CPButtonTest.j index 19c0172fb..9abf42e27 100644 --- a/Tests/AppKit/CPButtonTest.j +++ b/Tests/AppKit/CPButtonTest.j @@ -44,11 +44,11 @@ [button setKeyEquivalent:"a"]; [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:"b" charactersIgnoringModifiers:"b" isARepeat:NO keyCode:0]]; + characters:"b" charactersIgnoringModifiers:"b" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:wasClicked]; [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]]; + characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertTrue:wasClicked]; } @@ -60,11 +60,11 @@ [button setKeyEquivalentModifierMask:CPAlternateKeyMask]; [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]]; + characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:wasClicked]; [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:CPAlternateKeyMask timestamp:0 windowNumber:0 context:nil - characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]]; + characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertTrue:wasClicked]; } @@ -76,12 +76,12 @@ [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]]; + characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:wasClicked]; [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:CPShiftKeyMask timestamp:0 windowNumber:0 context:nil - characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]]; + characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertTrue:wasClicked]; } @@ -92,15 +92,15 @@ [button setKeyEquivalent:CPEscapeFunctionKey]; [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:CPDeleteCharacter charactersIgnoringModifiers:CPDeleteCharacter isARepeat:NO keyCode:0]]; + characters:CPDeleteCharacter charactersIgnoringModifiers:CPDeleteCharacter isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:wasClicked]; [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]]; + characters:"a" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:wasClicked]; [button performKeyEquivalent:[CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0]]; + characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertTrue:wasClicked]; } From 98ef6fd902d00e2532dec2508988419b69b1c68a Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 12:30:58 +0200 Subject: [PATCH 21/40] Update CPEventTest.j --- Tests/AppKit/CPEventTest.j | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/AppKit/CPEventTest.j b/Tests/AppKit/CPEventTest.j index 7570583dc..b64f92312 100644 --- a/Tests/AppKit/CPEventTest.j +++ b/Tests/AppKit/CPEventTest.j @@ -44,13 +44,13 @@ { [self assert:0 equals:[CPEvent modifierFlags] message:@"no modifier flags active in a newly started app"]; - var anEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask timestamp:0 windowNumber:0 context:nil characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]; + var anEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask timestamp:0 windowNumber:0 context:nil characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]; [CPApp sendEvent:anEvent]; [self assert:CPShiftKeyMask equals:[CPEvent modifierFlags] message:@"shift key pressed"]; // When the key up event is sent the modifier flags are cleared. - anEvent = [CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0]; + anEvent = [CPEvent keyEventWithType:CPKeyUp location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil characters:"A" charactersIgnoringModifiers:"a" isARepeat:NO keyCode:0 isActionKey:NO]; [CPApp sendEvent:anEvent]; [self assert:0 equals:[CPEvent modifierFlags] message:@"shift key released"]; From ce7fec170a56579d1feb99c9bb0bf4532fd385d1 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 12:33:21 +0200 Subject: [PATCH 22/40] Update CPEventTest.j --- Tests/AppKit/CPEventTest.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/AppKit/CPEventTest.j b/Tests/AppKit/CPEventTest.j index b64f92312..feef12694 100644 --- a/Tests/AppKit/CPEventTest.j +++ b/Tests/AppKit/CPEventTest.j @@ -31,7 +31,7 @@ timestamp:400.5 windowNumber:300 context:nil eventNumber:0 clickCount:2 pressure:0.5]; [self assert:@"CPEvent: type=2 loc={50, 50} time=400.5 flags=0x20000 win=undefined winNum=0 ctxt=null evNum=0 click=2 buttonNumber=0 pressure=0.5" equals:[anEvent description]]; - anEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask | CPCommandKeyMask timestamp:12345.6 windowNumber:10 context:nil characters:"X" charactersIgnoringModifiers:"x" isARepeat:NO keyCode:10]; + anEvent = [CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPShiftKeyMask | CPCommandKeyMask timestamp:12345.6 windowNumber:10 context:nil characters:"X" charactersIgnoringModifiers:"x" isARepeat:NO keyCode:10 isActionKey:NO]; [self assert:@"CPEvent: type=10 loc={0, 0} time=12345.6 flags=0x120000 win=null winNum=10 ctxt=null chars=\"X\" unmodchars=\"x\" repeat=0 keyCode=10" equals:[anEvent description]]; From 27280f6da1e1b1b6644b63c2c8014af0f5f29140 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 12:35:43 +0200 Subject: [PATCH 23/40] Update CPMenuTest.j --- Tests/AppKit/CPMenuTest.j | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/AppKit/CPMenuTest.j b/Tests/AppKit/CPMenuTest.j index 6449b65aa..b50afdea3 100644 --- a/Tests/AppKit/CPMenuTest.j +++ b/Tests/AppKit/CPMenuTest.j @@ -149,24 +149,24 @@ // Don't match anything. [menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask timestamp:0 windowNumber:0 context:nil - characters:"b" charactersIgnoringModifiers:"b" isARepeat:NO keyCode:0]]; + characters:"b" charactersIgnoringModifiers:"b" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled || undoWasCalled]; [menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:"o" charactersIgnoringModifiers:"o" isARepeat:NO keyCode:0]]; + characters:"o" charactersIgnoringModifiers:"o" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled || undoWasCalled]; [menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask timestamp:0 windowNumber:0 context:nil - characters:"o" charactersIgnoringModifiers:"o" isARepeat:NO keyCode:0]]; + characters:"o" charactersIgnoringModifiers:"o" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || undoWasCalled]; [self assertTrue:openDocumentWasCalled message:"expect openDocumentWasCalled"]; openDocumentWasCalled = NO; [menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask timestamp:0 windowNumber:0 context:nil - characters:CPUndoKeyEquivalent charactersIgnoringModifiers:CPUndoKeyEquivalent isARepeat:NO keyCode:0]]; + characters:CPUndoKeyEquivalent charactersIgnoringModifiers:CPUndoKeyEquivalent isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled]; [self assertTrue:undoWasCalled]; } @@ -177,7 +177,7 @@ [menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0]]; + characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:escapeWasCalled || openDocumentWasCalled || undoWasCalled]; [self assertTrue:escapeNoModifierWasCalled]; @@ -185,7 +185,7 @@ [menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask timestamp:0 windowNumber:0 context:nil - characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0]]; + characters:CPEscapeFunctionKey charactersIgnoringModifiers:CPEscapeFunctionKey isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:escapeNoModifierWasCalled || openDocumentWasCalled || undoWasCalled]; [self assertTrue:escapeWasCalled]; } @@ -196,7 +196,7 @@ [menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask timestamp:0 windowNumber:0 context:nil - characters:@"s" charactersIgnoringModifiers:@"s" isARepeat:NO keyCode:0]]; + characters:@"s" charactersIgnoringModifiers:@"s" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled || saveDocumentAsWasCalled || undoWasCalled]; [self assertTrue:saveDocumentWasCalled message:"saveDocumentWasCalled"]; @@ -204,7 +204,7 @@ [menu performKeyEquivalent:[CPEvent keyEventWithType:CPKeyDown location:CGPointMakeZero() modifierFlags:CPPlatformActionKeyMask | CPShiftKeyMask timestamp:0 windowNumber:0 context:nil - characters:@"s" charactersIgnoringModifiers:@"s" isARepeat:NO keyCode:0]]; + characters:@"s" charactersIgnoringModifiers:@"s" isARepeat:NO keyCode:0 isActionKey:NO]]; [self assertFalse:escapeWasCalled || escapeNoModifierWasCalled || openDocumentWasCalled || saveDocumentWasCalled || undoWasCalled]; [self assertTrue:saveDocumentAsWasCalled message:"saveDocumentAsWasCalled"]; } From 2fe29d4a262ec9f54a1c60ef7134d44e71d951d0 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 13:36:14 +0200 Subject: [PATCH 24/40] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fdcc77f81..36beffcd1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,4 @@ node_modules /dist/objective-j/lib /dist/cappuccino/package.json /dist/cappuccino/lib - +/dist/cappuccino/bin From ce00fdaae82709c2eaea946295742589ca504df8 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 5 Jul 2025 15:37:32 +0200 Subject: [PATCH 25/40] fixed: copy-paste-issues --- AppKit/CPTextView/CPTextView.j | 39 +++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 3666c321c..a6585087e 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -449,7 +449,6 @@ var kDelegateRespondsTo_textShouldBeginEditing { [super copy:sender]; - var selectedRange = [self selectedRange], pasteboard = [CPPasteboard generalPasteboard], stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)], @@ -517,6 +516,9 @@ var kDelegateRespondsTo_textShouldBeginEditing - (void)paste:(id)sender { + if ([[CPApp currentEvent] type] != CPAppKitDefined) + return; + [self _pasteString:[self _stringForPasting]]; } @@ -1705,6 +1707,9 @@ Sets the selection to a range of characters in response to user action. - (void)cut:(id)sender { + if ([[CPApp currentEvent] type] != CPAppKitDefined) + return; + var selectedRange = [self selectedRange]; if (selectedRange.length < 1) @@ -2670,7 +2675,8 @@ var _CPCopyPlaceholder = '-'; }; // Fires for simple key presses (a, b, 1, 2) - _CPNativeInputField.addEventListener('input', function(e) { + _CPNativeInputField.addEventListener('input', function(e) + { // If we are in a composition (e.g., IME), we do nothing. // We wait for 'compositionend' to get the final, complete text. if (_isComposing) { @@ -2699,20 +2705,16 @@ var _CPCopyPlaceholder = '-'; var nativeClipboard = (e.originalEvent || e).clipboardData; var richtext; var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - var isPlain = ![currentFirstResponder isRichText]; - // Check for shift key to force plain text paste. - if (!isPlain && !e.shiftKey && (richtext = nativeClipboard.getData('text/rtf'))) + // Check for richtext (shift key to force plain text paste). + if ([currentFirstResponder isRichText] && !e.shiftKey && (richtext = nativeClipboard.getData('text/rtf'))) { - setTimeout(function() - { - [currentFirstResponder paste:self]; - }, 0); + [currentFirstResponder _pasteString:richtext]; return; } var data = nativeClipboard.getData('text/plain'); - [currentFirstResponder paste:self]; + [currentFirstResponder _pasteString:data]; }; // COPY handler @@ -2720,10 +2722,11 @@ var _CPCopyPlaceholder = '-'; { e.preventDefault(); var pasteboard = [CPPasteboard generalPasteboard]; - var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - [currentFirstResponder copy:self]; // This populates the CP pasteboard + // First, copy the data to populate the CP clipboard + [[[CPApp keyWindow] firstResponder] copy:self]; + // Now, copy the data to the native clipboard var stringForPasting = [pasteboard stringForType:CPStringPboardType] || ''; e.clipboardData.setData('text/plain', stringForPasting); @@ -2738,20 +2741,22 @@ var _CPCopyPlaceholder = '-'; { e.preventDefault(); var pasteboard = [CPPasteboard generalPasteboard]; + var nativeClipboard = (e.originalEvent || e).clipboardData; var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - - // First, copy the data to populate the clipboard +debugger + // First, copy the data to populate the CP clipboard [currentFirstResponder copy:self]; + // Now, copy the data to the native clipboard var stringForPasting = [pasteboard stringForType:CPStringPboardType] || ''; - e.clipboardData.setData('text/plain', stringForPasting); + nativeClipboard.setData('text/plain', stringForPasting); var rtfForPasting = [pasteboard stringForType:CPRTFPboardType]; if (rtfForPasting) - e.clipboardData.setData('text/rtf', rtfForPasting); + nativeClipboard.setData('text/rtf', rtfForPasting); // Then, perform the delete part of the cut operation in the text view - [currentFirstResponder delete:self]; + [currentFirstResponder deleteBackward:self]; }; #endif } From 570c31f24825d4e054dc46623890884753c73952 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sun, 6 Jul 2025 14:04:35 +0200 Subject: [PATCH 26/40] fixed: richtext copypaste --- AppKit/CPTextView/CPTextView.j | 42 +++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index a6585087e..0b0f00253 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2703,18 +2703,37 @@ var _CPCopyPlaceholder = '-'; { e.preventDefault(); var nativeClipboard = (e.originalEvent || e).clipboardData; - var richtext; var currentFirstResponder = [[CPApp keyWindow] firstResponder]; - // Check for richtext (shift key to force plain text paste). - if ([currentFirstResponder isRichText] && !e.shiftKey && (richtext = nativeClipboard.getData('text/rtf'))) + // Can we accept richtext? Then this is our preference (fixme: shift key to force plain text paste) + if ([currentFirstResponder isRichText]) { - [currentFirstResponder _pasteString:richtext]; - return; + var richtext = nativeClipboard.getData('text/rtf'); + + // prefer RTF form the outside of cappuccino + if (richtext) + richtext = [[_CPRTFParser new] parseRTF:richtext]; + else + { + var pasteboard = [CPPasteboard generalPasteboard]; + // If no RTF is available, try to get the internal represatation of richtext from the pasteboard + var richData = [pasteboard stringForType:_CPASPboardType]; + + if (richData) + richtext = [CPKeyedUnarchiver unarchiveObjectWithData:[CPData dataWithRawString:richData]]; + } + + if (richtext) + { + [currentFirstResponder _pasteString:richtext]; + + return; + } + // If no richtext is available, fall back to plain text } - var data = nativeClipboard.getData('text/plain'); - [currentFirstResponder _pasteString:data]; + var nativeString = nativeClipboard.getData('text/plain'); + [currentFirstResponder _pasteString:nativeString || [pasteboard stringForType:CPStringPboardType] || '']; }; // COPY handler @@ -2722,18 +2741,19 @@ var _CPCopyPlaceholder = '-'; { e.preventDefault(); var pasteboard = [CPPasteboard generalPasteboard]; + var nativeClipboard = (e.originalEvent || e).clipboardData; // First, copy the data to populate the CP clipboard [[[CPApp keyWindow] firstResponder] copy:self]; - // Now, copy the data to the native clipboard + // Now, copy the data over to the native clipboard var stringForPasting = [pasteboard stringForType:CPStringPboardType] || ''; - e.clipboardData.setData('text/plain', stringForPasting); + nativeClipboard.setData('text/plain', stringForPasting); var rtfForPasting = [pasteboard stringForType:CPRTFPboardType]; if (rtfForPasting) - e.clipboardData.setData('text/rtf', rtfForPasting); + nativeClipboard.setData('text/rtf', rtfForPasting); }; // CUT handler @@ -2743,7 +2763,7 @@ var _CPCopyPlaceholder = '-'; var pasteboard = [CPPasteboard generalPasteboard]; var nativeClipboard = (e.originalEvent || e).clipboardData; var currentFirstResponder = [[CPApp keyWindow] firstResponder]; -debugger + // First, copy the data to populate the CP clipboard [currentFirstResponder copy:self]; From 38f523b3c278aa98e986c59ad2e9312c7ffb5a5a Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 15 Jul 2025 18:54:09 +0200 Subject: [PATCH 27/40] new: UIBuilder demo application --- Tests/Manual/UIBuilderDemo/AppController.j | 422 ++++++ Tests/Manual/UIBuilderDemo/ConnectionView.j | 97 ++ Tests/Manual/UIBuilderDemo/Info.plist | 14 + .../UIBuilderDemo/InspectorController.j | 215 +++ .../UIBuilderDemo/Resources/spinner.gif | Bin 0 -> 1849 bytes .../Manual/UIBuilderDemo/UIBuilderConstants.j | 10 + .../UIBuilderDemo/UIBuilderController.j | 645 ++++++++ Tests/Manual/UIBuilderDemo/UICanvasView.j | 996 ++++++++++++ Tests/Manual/UIBuilderDemo/UIElementView.j | 1349 +++++++++++++++++ Tests/Manual/UIBuilderDemo/index.html | 164 ++ Tests/Manual/UIBuilderDemo/main.j | 10 + 11 files changed, 3922 insertions(+) create mode 100644 Tests/Manual/UIBuilderDemo/AppController.j create mode 100644 Tests/Manual/UIBuilderDemo/ConnectionView.j create mode 100755 Tests/Manual/UIBuilderDemo/Info.plist create mode 100644 Tests/Manual/UIBuilderDemo/InspectorController.j create mode 100755 Tests/Manual/UIBuilderDemo/Resources/spinner.gif create mode 100644 Tests/Manual/UIBuilderDemo/UIBuilderConstants.j create mode 100644 Tests/Manual/UIBuilderDemo/UIBuilderController.j create mode 100644 Tests/Manual/UIBuilderDemo/UICanvasView.j create mode 100644 Tests/Manual/UIBuilderDemo/UIElementView.j create mode 100644 Tests/Manual/UIBuilderDemo/index.html create mode 100755 Tests/Manual/UIBuilderDemo/main.j diff --git a/Tests/Manual/UIBuilderDemo/AppController.j b/Tests/Manual/UIBuilderDemo/AppController.j new file mode 100644 index 000000000..6d9fa30a7 --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/AppController.j @@ -0,0 +1,422 @@ +// +// AppController.j +// Main application controller. Sets up the window, canvas, palette, +// and controllers on launch. +// + +@import +@import "UIBuilderController.j" +@import "UICanvasView.j" +@import "UIElementView.j"; +@import "InspectorController.j"; + +@implementation CPColor (StandardColors) + +// A standard light gray for control backgrounds, like buttons. ++ (CPColor)controlColor +{ + return [CPColor colorWithCalibratedWhite:0.9 alpha:1.0]; +} + +// A medium gray for shadows or borders. ++ (CPColor)controlShadowColor +{ + return [CPColor grayColor]; +} + +// A dark gray for text on light controls. ++ (CPColor)controlDarkShadowColor +{ + return [CPColor darkGrayColor]; +} + +// The primary color for selected items. ++ (CPColor)selectedControlColor +{ + // Corresponds to the default blue selection color in macOS. + return [CPColor colorWithCalibratedRed:0.0 green:0.478 blue:1.0 alpha:1.0]; +} + +// A secondary selection color, often used for inactive windows or rubber-band selections. ++ (CPColor)alternateSelectedControlColor +{ + return [CPColor colorWithCalibratedRed:0.2 green:0.5 blue:0.9 alpha:1.0]; +} + +// The color for an inactive or secondary selection, like a window title bar. ++ (CPColor)secondarySelectedControlColor +{ + return [CPColor lightGrayColor]; +} + +// The highlight color for an element that has keyboard focus. ++ (CPColor)keyboardFocusIndicatorColor +{ + return [CPColor colorWithCalibratedRed:0.3 green:0.6 blue:1.0 alpha:1.0]; +} + +// The standard background color for a window's content area. ++ (CPColor)windowBackgroundColor +{ + return [CPColor colorWithCalibratedWhite:0.93 alpha:1.0]; +} + +// The background color for text-editing views. ++ (CPColor)textBackgroundColor +{ + return [CPColor whiteColor]; +} + +@end + +// Required additions from original EFView.j for graphics and text handling +@implementation CPString(SizingAddition) +- (CPSize)sizeWithAttributes:(CPDictionary)stringAttributes +{ + var font = [stringAttributes objectForKey:CPFontAttributeName] || [CPFont systemFontOfSize:12]; + // This is a simplified implementation. For more complex text, you might need a more robust solution. + var ctx = [[CPGraphicsContext currentContext] graphicsPort]; + var oldFont = ctx.font; + ctx.font = [font cssString]; + var metrics = ctx.measureText(self); + ctx.font = oldFont; + return CGSizeMake(metrics.width, [[font fontDescriptor] pointSize]); +} +- (void)drawAtPoint:(CGPoint)aPoint withAttributes:(CPDictionary)attributes +{ + var ctx = [[CPGraphicsContext currentContext] graphicsPort]; + var font = [attributes objectForKey:CPFontAttributeName] || [CPFont systemFontOfSize:12]; + var color = [attributes objectForKey:CPForegroundColorAttributeName] || [CPColor blackColor]; + + ctx.font = [font cssString]; + [color setFill]; + ctx.fillText(self, aPoint.x, aPoint.y + [[font fontDescriptor] pointSize]); +} +@end + +@implementation CPBezierPath(RoundedRectangle) ++ (CPBezierPath)bezierPathWithRoundedRect:(CPRect)aRect radius:(float)radius +{ + return [self bezierPathWithRoundedRect:aRect xRadius:radius yRadius:radius]; +} +@end + + +// A simple draggable symbol for the palette +@implementation DraggableSymbolView : CPView +{ + CPString _dragType; +} + +- (void)setDragType:(CPString)aType +{ + _dragType = aType; +} +-(BOOL)acceptsFirstMouse:(CPEvent)aEvent +{ + return YES; +} + +- (void)mouseDown:(CPEvent)theEvent +{ + // 1. Create a placeholder view that is a visual copy of this one. + var dragPlaceholder = [[DraggableSymbolView alloc] initWithFrame:[self bounds]]; + [dragPlaceholder setDragType:_dragType]; // Ensure it can draw its title correctly + [dragPlaceholder setAlphaValue:0.75]; // Make it semi-transparent for good UX + + var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard]; + [pasteboard declareTypes:[_dragType] owner:nil]; + [pasteboard setString:@"1" forType:_dragType]; + + [self dragView:dragPlaceholder + at:[self bounds].origin + offset:nil + event:theEvent + pasteboard:pasteboard + source:self + slideBack:YES]; +} + +// The drawRect: method defines what the view looks like, and therefore +// what the dragged placeholder view will look like. +- (void)drawRect:(CGRect)rect +{ + var bounds = [self bounds]; + + // Background + [[CPColor controlColor] set]; + [CPBezierPath fillRect:bounds]; + [[CPColor controlShadowColor] set]; + [CPBezierPath strokeRect:bounds]; + + if ([_dragType isEqualToString:UIWindowDragType]) + { + // Draw a window + var windowRect = CGRectInset(bounds, 5, 5); + var titleBarHeight = 10; + + // Draw the title bar + var titleBarRect = CGRectMake(windowRect.origin.x, windowRect.origin.y, windowRect.size.width, titleBarHeight); + [[CPColor grayColor] set]; + [CPBezierPath fillRect:titleBarRect]; + + // Draw the content area + var contentRect = CGRectMake(windowRect.origin.x, windowRect.origin.y + titleBarHeight, windowRect.size.width, windowRect.size.height - titleBarHeight); + [[CPColor whiteColor] set]; + [CPBezierPath fillRect:contentRect]; + + // Draw the border for the whole window + [[CPColor blackColor] set]; + [CPBezierPath strokeRect:windowRect]; + } + else if ([_dragType isEqualToString:UIButtonDragType]) + { + // Draw a button + var buttonRect = CGRectInset(bounds, 8, 10); + var path = [CPBezierPath bezierPathWithRoundedRect:buttonRect radius:5]; + [[CPColor whiteColor] set]; + [path fill]; + [[CPColor blackColor] set]; + [path stroke]; + } + else if ([_dragType isEqualToString:UISliderDragType]) + { + // Draw a slider + var sliderY = bounds.size.height / 2; + var path = [CPBezierPath bezierPath]; + [path moveToPoint:CGPointMake(bounds.origin.x + 5, sliderY)]; + [path lineToPoint:CGPointMake(bounds.origin.x + bounds.size.width - 5, sliderY)]; + [[CPColor blackColor] set]; + [path stroke]; + + var knobRect = CGRectMake(bounds.size.width / 2 - 5, sliderY - 5, 10, 10); + var knobPath = [CPBezierPath bezierPathWithOvalInRect:knobRect]; + [[CPColor whiteColor] set]; + [knobPath fill]; + [[CPColor blackColor] set]; + [knobPath stroke]; + } + else if ([_dragType isEqualToString:UITextFieldDragType]) + { + // Draw a text field + var fieldRect = CGRectInset(bounds, 5, 12); + [[CPColor whiteColor] set]; + [CPBezierPath fillRect:fieldRect]; + [[CPColor blackColor] set]; + [CPBezierPath strokeRect:fieldRect]; + + // Draw an I-beam cursor + var ibeamX = CGRectGetMidX(fieldRect); + var ibeamY1 = CGRectGetMinY(fieldRect) + 3; + var ibeamY2 = CGRectGetMaxY(fieldRect) - 3; + + var ibeamPath = [CPBezierPath bezierPath]; + [ibeamPath moveToPoint:CGPointMake(ibeamX, ibeamY1)]; + [ibeamPath lineToPoint:CGPointMake(ibeamX, ibeamY2)]; + [ibeamPath moveToPoint:CGPointMake(ibeamX - 2, ibeamY1)]; + [ibeamPath lineToPoint:CGPointMake(ibeamX + 2, ibeamY1)]; + [ibeamPath moveToPoint:CGPointMake(ibeamX - 2, ibeamY2)]; + [ibeamPath lineToPoint:CGPointMake(ibeamX + 2, ibeamY2)]; + + [ibeamPath setLineWidth:0.5]; + [[CPColor blackColor] set]; + [ibeamPath stroke]; + } + else + { + // Fallback to original text drawing + var title = [[_dragType componentsSeparatedByString:@"DragType"] objectAtIndex:0]; + var textAttributes = @{ + CPFontAttributeName: [CPFont systemFontOfSize:10], + CPForegroundColorAttributeName: [CPColor blackColor] + }; + var titleSize = [title sizeWithAttributes:textAttributes]; + var titlePoint = CGPointMake( + (bounds.size.width - titleSize.width) / 2.0, + (bounds.size.height - titleSize.height) / 2.0 + ); + [title drawAtPoint:titlePoint withAttributes:textAttributes]; + } +} + +@end + +@implementation AppController : CPObject +{ + CPWindow _window; + CPPanel _palette; + UIBuilderController _builderController; + UICanvasView _canvasView; + InspectorController _inspectorController; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + // 1. Create the main window and canvas + _window = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask]; + [_window setTitle:@"Cappuccino UI Builder"]; + [_window setAcceptsMouseMovedEvents:YES]; + + _canvasView = [[UICanvasView alloc] initWithFrame:[[_window contentView] bounds]]; + [_canvasView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [[_window contentView] addSubview:_canvasView]; + + // 2. Create the controllers + _builderController = [[UIBuilderController alloc] init]; + + // 3. Wire everything together + [_canvasView setDelegate:_builderController]; + + // Bind the canvas to the controller's data model. This is the core of the architecture. + [_canvasView bind:"dataObjects" toObject:_builderController withKeyPath:@"elementsController.arrangedObjects" options:nil]; + [_canvasView bind:"selectionIndexes" toObject:_builderController withKeyPath:@"elementsController.selectionIndexes" options:nil]; + [_canvasView bind:"connections" toObject:_builderController withKeyPath:@"connectionsController.arrangedObjects" options:nil]; + [_canvasView bind:"selectedConnections" toObject:_builderController withKeyPath:@"connectionsController.selectedObjects" options:nil]; + + [self createPalette]; + [self createInspector]; + + // 5. Create the main menu + var mainMenuBar = [[CPMenu alloc] initWithTitle:@"MainMenu"]; + var editMenuItem = [[CPMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""]; + + + var editMenu = [[CPMenu alloc] initWithTitle:@"Edit"]; + [editMenu addItemWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"]; + [editMenu addItemWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"]; + [editMenu addItem:[CPMenuItem separatorItem]]; + [editMenu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"]; + [editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"]; + [editMenu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"]; + [editMenu addItemWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""]; + + [editMenuItem setSubmenu:editMenu]; + + var fileMenuItem = [[CPMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""]; + var fileMenu = [[CPMenu alloc] initWithTitle:@"File"]; + [fileMenu addItemWithTitle:@"Run" action:@selector(run:) keyEquivalent:@"r"]; + [fileMenuItem setSubmenu:fileMenu]; + [mainMenuBar addItem:fileMenuItem]; + + [mainMenuBar addItem:editMenuItem]; + + [CPApp setMainMenu:mainMenuBar]; + [CPMenu setMenuBarVisible:YES]; + + [_window makeKeyAndOrderFront:self]; +} + +- (void)createPalette +{ + var screenWidth = window.innerWidth; + var paletteWidth = 220; + var paletteHeight = 60; + var paletteX = (screenWidth - paletteWidth) / 2; + var paletteY = 22; // Position near the top of the screen + + _palette = [[CPPanel alloc] initWithContentRect:CGRectMake(paletteX, paletteY, paletteWidth, paletteHeight) + styleMask:CPHUDBackgroundWindowMask | CPTitledWindowMask | CPClosableWindowMask]; + [_palette setTitle:@"Elements"]; + [_palette setFloatingPanel:YES]; + + var xPos = 10; + var types = [UIWindowDragType, UIButtonDragType, UISliderDragType, UITextFieldDragType]; + + // Create draggable symbols for each type + [_canvasView registerForDraggedTypes:types]; + + for (var i=0; i < [types count]; i++) { + var symbol = [[DraggableSymbolView alloc] initWithFrame:CGRectMake(xPos, 10, 40, 40)]; + symbol._dragType = types[i]; + + [[_palette contentView] addSubview:symbol]; + xPos += 50; + } + + [_palette orderFront:self]; +} + +- (void)createInspector +{ + var inspectorPanel = [[CPPanel alloc] initWithContentRect:CGRectMake(20, 200, 300, 150) + styleMask:CPTitledWindowMask | CPClosableWindowMask]; + [inspectorPanel setTitle:@"Inspector"]; + [inspectorPanel setFloatingPanel:YES]; + + var contentView = [inspectorPanel contentView]; + + _inspectorController = [[InspectorController alloc] init]; + [_inspectorController setBuilderController:_builderController]; + [_inspectorController setPanel:inspectorPanel]; + [_inspectorController setView:contentView]; + + [_inspectorController awakeFromMarkup]; // Manually call this + + [inspectorPanel orderFront:self]; +} + +- (void)run:(id)sender +{ + console.log("Run: Starting native UI generation..."); + var canvasSubviews = [_canvasView subviews]; + var nativeElementMap = [CPMutableDictionary dictionary]; + + // First pass: create all native elements and map them by their ID + console.log("Run: Creating native elements and building map..."); + for (var i = 0; i < [canvasSubviews count]; i++) + { + var view = [canvasSubviews objectAtIndex:i]; + if ([view isKindOfClass:[UIElementView class]]) + { + // This will now recursively build the map + [view nativeUIElementWithMap:nativeElementMap]; + } + } + + // Second pass: connect the native elements + console.log("Run: Processing connections..."); + var connections = [[_builderController connectionsController] content]; + for (var i = 0; i < [connections count]; i++) + { + var connection = [connections objectAtIndex:i]; + var sourceID = [connection valueForKey:@"sourceID"]; + var targetID = [connection valueForKey:@"targetID"]; + var action = [connection valueForKey:@"action"]; + + console.log(" - Connecting: " + sourceID + " -> " + targetID + " (Action: " + action + ")"); + + var nativeSource = [nativeElementMap objectForKey:sourceID]; + var nativeTarget = [nativeElementMap objectForKey:targetID]; + + if (nativeSource && nativeTarget && action) + { + console.log(" - Found native source and target. Applying connection."); + [nativeSource setTarget:nativeTarget]; + [nativeSource setAction:CPSelectorFromString(action)]; + } + else + { + console.log(" - WARNING: Could not find native source or target for connection."); + } + } + + // Third pass: show the windows + console.log("Run: Showing windows..."); + for (var i = 0; i < [canvasSubviews count]; i++) + { + var view = [canvasSubviews objectAtIndex:i]; + if ([view isKindOfClass:[UIWindowView class]]) + { + var elementID = [[view dataObject] valueForKey:@"id"]; + var nativeWindow = [nativeElementMap objectForKey:elementID]; + if (nativeWindow) + { + console.log(" - Showing window for ID: " + elementID); + [nativeWindow makeKeyAndOrderFront:self]; + } + } + } + console.log("Run: Finished."); +} + +@end diff --git a/Tests/Manual/UIBuilderDemo/ConnectionView.j b/Tests/Manual/UIBuilderDemo/ConnectionView.j new file mode 100644 index 000000000..d77154cfb --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/ConnectionView.j @@ -0,0 +1,97 @@ + +@import + +function treshold(value, limit) +{ + return value > 0 ? Math.min(value, limit) : Math.max(value, -limit); +} + +@implementation ConnectionView : CPView +{ + CGPoint _startPoint; + CGPoint _endPoint; + CPColor _color; +} + +- (id)initWithFrame:(CGRect)aRect +{ + self = [super initWithFrame:aRect]; + if (self) + { + [self setBackgroundColor:[CPColor clearColor]]; + _color = [CPColor redColor]; + [self setHidden:YES]; // Hidden by default + } + return self; +} + +- (void)setStartPoint:(CGPoint)startPoint { _startPoint = startPoint; } +- (void)setEndPoint:(CGPoint)endPoint { _endPoint = endPoint; } +- (void)setColor:(CPColor)color { _color = color; } + +- (void)drawRect:(CGRect)rect +{ + console.log("ConnectionView: drawRect - Drawing connection from ", _startPoint, " to ", _endPoint); + if (_startPoint && _endPoint) + { + [self drawLinkFrom:_startPoint to:_endPoint color:_color]; + } +} + +- (void)drawLinkFrom:(CGPoint)startPoint to:(CGPoint)endPoint color:(CPColor)insideColor +{ + var dist = Math.sqrt(Math.pow(startPoint.x - endPoint.x, 2) + Math.pow(startPoint.y - endPoint.y, 2)); + var p0 = CGPointMake(startPoint.x, startPoint.y); + var p3 = CGPointMake(endPoint.x, endPoint.y); + var p1 = CGPointMake(startPoint.x + treshold((endPoint.x - startPoint.x) / 2, 50), startPoint.y); + var p2 = CGPointMake(endPoint.x - treshold((endPoint.x - startPoint.x) / 2, 50), endPoint.y); + var path = [CPBezierPath bezierPath]; + [path setLineWidth:0]; + [[CPColor grayColor] set]; + [path appendBezierPathWithOvalInRect:CGRectMake(startPoint.x-2.5,startPoint.y-2.5,5,5)]; + [path fill]; + path = [CPBezierPath bezierPath]; + [path setLineWidth:0]; + [insideColor set]; + [path appendBezierPathWithOvalInRect:CGRectMake(startPoint.x-1.5,startPoint.y-1.5,3,3)]; + [path fill]; + path = [CPBezierPath bezierPath]; + [path setLineWidth:0]; + [[CPColor grayColor] set]; + [path appendBezierPathWithOvalInRect:CGRectMake(endPoint.x-2.5,endPoint.y-2.5,5,5)]; + [path fill]; + path = [CPBezierPath bezierPath]; + [path setLineWidth:0]; + [insideColor set]; + [path appendBezierPathWithOvalInRect:CGRectMake(endPoint.x-1.5,endPoint.y-1.5,3,3)]; + [path fill]; + if (dist < 40) + { + path = [CPBezierPath bezierPath]; + [path setLineWidth:5]; + [path moveToPoint:startPoint]; + [path lineToPoint:endPoint]; + [[CPColor grayColor] set]; + [path stroke]; + path = [CPBezierPath bezierPath]; + [path setLineWidth:3]; + [path moveToPoint:startPoint]; + [path lineToPoint:endPoint]; + [insideColor set]; + [path stroke]; + return; + } + path = [CPBezierPath bezierPath]; + [path setLineWidth:5]; + [path moveToPoint:p0]; + [path curveToPoint:p3 controlPoint1:p1 controlPoint2:p2]; + [[CPColor grayColor] set]; + [path stroke]; + path = [CPBezierPath bezierPath]; + [path setLineWidth:3]; + [path moveToPoint:p0]; + [path curveToPoint:p3 controlPoint1:p1 controlPoint2:p2]; + [insideColor set]; + [path stroke]; +} +@end diff --git a/Tests/Manual/UIBuilderDemo/Info.plist b/Tests/Manual/UIBuilderDemo/Info.plist new file mode 100755 index 000000000..8bbc29491 --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/Info.plist @@ -0,0 +1,14 @@ + + + + + CPApplicationDelegateClass + AppController + CPBundleName + UIBuilderDemo + CPPrincipalClass + CPApplication + CPDefaultTheme + Aristo2 + + diff --git a/Tests/Manual/UIBuilderDemo/InspectorController.j b/Tests/Manual/UIBuilderDemo/InspectorController.j new file mode 100644 index 000000000..dd3d0af2f --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/InspectorController.j @@ -0,0 +1,215 @@ +@import + +@class UIBuilderController; + +@implementation InspectorController : CPViewController +{ + UIBuilderController _builderController @accessors(property=builderController); + CPPanel _panel @accessors(property=panel); + CPTableView _connectionsTableView; +} + +- (void)awakeFromMarkup +{ + [_builderController addObserver:self forKeyPath:@"elementsController.selectionIndexes" options:CPKeyValueObservingOptionNew context:nil]; + + // Create Tab View + var tabView = [[CPTabView alloc] initWithFrame:[[_panel contentView] bounds]]; + [tabView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [tabView setDelegate:self]; + + // Properties Tab + var propertiesView = [[CPView alloc] initWithFrame:CGRectMakeZero()]; + var propertiesTabItem = [[CPTabViewItem alloc] initWithIdentifier:@"properties"]; + [propertiesTabItem setLabel:@"Properties"]; + [propertiesTabItem setView:propertiesView]; + [tabView addTabViewItem:propertiesTabItem]; + + // Connections Tab + var connectionsView = [[CPView alloc] initWithFrame:CGRectMakeZero()]; + var connectionsTabItem = [[CPTabViewItem alloc] initWithIdentifier:@"connections"]; + [connectionsTabItem setLabel:@"Connections"]; + [connectionsTabItem setView:connectionsView]; + [tabView addTabViewItem:connectionsTabItem]; + + // Connections TableView + _connectionsTableView = [[CPTableView alloc] initWithFrame:[connectionsView bounds]]; + [_connectionsTableView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + + var columns = [ + {identifier: "outlet", title: "Outlet", width: 80}, + {identifier: "action", title: "Action", width: 120} + ]; + + // Keep a reference to the array controller + var connectionsController = [_builderController connectionsController]; + + for (var i = 0; i < [columns count]; i++) { + var colInfo = columns[i]; + var column = [[CPTableColumn alloc] initWithIdentifier:colInfo.identifier]; + [[column headerView] setStringValue:colInfo.title]; + [column setWidth:colInfo.width]; + [_connectionsTableView addTableColumn:column]; + // Bind the value of each column to the corresponding key path on the arranged objects + [column bind:CPValueBinding toObject:connectionsController withKeyPath:("arrangedObjects." + colInfo.identifier) options:nil]; + } + + // Bind the table's selection to the array controller's selection + [_connectionsTableView bind:@"selectionIndexes" toObject:connectionsController withKeyPath:@"selectionIndexes" options:nil]; + + var connectionsViewBounds = [connectionsView bounds]; + var buttonBarHeight = 28; + var tableHeight = connectionsViewBounds.size.height - buttonBarHeight; + + var scrollViewFrame = CGRectMake(3, 3, connectionsViewBounds.size.width - 6, tableHeight - 6); + var buttonBarFrame = CGRectMake(0, tableHeight, connectionsViewBounds.size.width, buttonBarHeight); + + var scrollView = [[CPScrollView alloc] initWithFrame:scrollViewFrame]; + [scrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [scrollView setDocumentView:_connectionsTableView]; + [connectionsView addSubview:scrollView]; + + var buttonBar = [[CPView alloc] initWithFrame:buttonBarFrame]; + [buttonBar setAutoresizingMask:CPViewWidthSizable | CPViewMinYMargin]; // Stick to bottom + [connectionsView addSubview:buttonBar]; + + var deleteButton = [CPButtonBar minusButton]; + [deleteButton setAction:@selector(deleteSelectedConnection:)]; + [deleteButton setTarget:self]; + [buttonBar addSubview:deleteButton]; + + // Replace panel's content view with the tab view + [_panel setContentView:tabView]; + + [self updateInspector]; +} + +- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(int)aRow +{ + var connection = [[[_builderController connectionsController] arrangedObjects] objectAtIndex:aRow]; + var identifier = [aTableColumn identifier]; + + return [connection valueForKey:identifier]; +} + +- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context +{ + // We only observe selection changes now. + if (keyPath === @"elementsController.selectionIndexes") + { + [self updateInspector]; + [self _updateConnectionVisibility]; + } +} + +- (void)_updateConnectionVisibility +{ + var tabView = [[self panel] contentView]; + if (![tabView isKindOfClass:[CPTabView class]]) + return; + + var selectedTabViewItem = [tabView selectedTabViewItem]; + var connectionsController = [_builderController connectionsController]; + var selectedObjects = [[_builderController elementsController] selectedObjects]; + + // 1. Filter the connections based on the selected UI element. + if ([selectedObjects count] === 1) + { + var selectedID = [[selectedObjects objectAtIndex:0] valueForKey:@"id"]; + var predicate = [CPPredicate predicateWithFormat:@"sourceID == %@ OR targetID == %@", selectedID, selectedID]; + [connectionsController setFilterPredicate:predicate]; + } + else + { + [connectionsController setFilterPredicate:[CPPredicate predicateWithFormat:@"FALSEPREDICATE"]]; + } +} + +- (void)tabView:(CPTabView)aTabView didSelectTabViewItem:(CPTabViewItem)aTabViewItem +{ + [self _updateConnectionVisibility]; +} + +- (void)deleteSelectedConnection:(id)sender +{ + var selectedObjects = [[_builderController connectionsController] selectedObjects]; + if ([selectedObjects count] > 0) + [[_builderController connectionsController] removeObjects:selectedObjects]; +} + +- (void)updateInspector +{ + var selectedObjects = [[_builderController elementsController] selectedObjects]; + var propertiesView = [[[_panel contentView] tabViewItemAtIndex:0] view]; + + // Clear existing views from properties tab + var subviews = [propertiesView subviews]; + for (var i = [subviews count] - 1; i >= 0; i--) { + [subviews[i] removeFromSuperview]; + } + + if ([selectedObjects count] === 1) + { + var selectedObject = selectedObjects[0]; + var elementType = [selectedObject valueForKey:@"type"]; + var viewClass = [UIBuilderController classForElementType:elementType]; + var properties = [viewClass persistentProperties]; + + var yPos = 10; + + // Set panel title + [_panel setTitle:elementType]; + + for (var i = 0; i < [properties count]; i++) + { + var propertyName = properties[i]; + var value = [selectedObject valueForKey:propertyName]; + var propertyType = [[viewClass propertyTypes] valueForKey:propertyName]; + + // Create Label + var label = [[CPTextField alloc] initWithFrame:CGRectMake(10, yPos + 3, 100, 20)]; + [label setStringValue:propertyName]; + [label setBezeled:NO]; + [label setDrawsBackground:NO]; + [label setEditable:NO]; + [propertiesView addSubview:label]; + [label setTextColor:[CPColor grayColor]]; + + // Create Control based on property type + if (propertyType === UIBBoolean) { + var checkbox = [[CPCheckBox alloc] initWithFrame:CGRectMake(120, yPos, 100, 20)]; + [checkbox setTitle:@""]; + [checkbox bind:@"value" toObject:selectedObject withKeyPath:propertyName options:nil]; + [propertiesView addSubview:checkbox]; + } else if (propertyType === UIBString || propertyType === UIBNumber) { + var textField = [[CPTextField alloc] initWithFrame:CGRectMake(120, yPos, 150, 27)]; + [textField bind:@"value" toObject:selectedObject withKeyPath:propertyName options:nil]; + [textField setBezeled:YES]; + [textField setEditable:YES]; + [propertiesView addSubview:textField]; + } else { // Fallback for unknown types + var textField = [[CPTextField alloc] initWithFrame:CGRectMake(120, yPos, 150, 25)]; + [textField bind:@"value" toObject:selectedObject withKeyPath:propertyName options:nil]; + [textField setBezeled:YES]; + [textField setEditable:YES]; + [propertiesView addSubview:textField]; + } + + yPos += 30; + } + + [[self panel] orderFront:self]; + } + else + { + [[self panel] orderOut:self]; + } +} + +- (void)dealloc +{ + [_builderController removeObserver:self forKeyPath:@"elementsController.selectionIndexes"]; + [super dealloc]; +} + +@end diff --git a/Tests/Manual/UIBuilderDemo/Resources/spinner.gif b/Tests/Manual/UIBuilderDemo/Resources/spinner.gif new file mode 100755 index 0000000000000000000000000000000000000000..06dbc2bc21dddcf0e09b566d5b211aee89570f52 GIT binary patch literal 1849 zcma*odr(tX9tZHtz31lM+(&Y`A`Ou`NeG&R#DrIfV%?hng21vs6v>ooRQQFbk7E_K!V( z{?5!fpZWgIZ%*d6t%i*j24bL}AZUJm9)h6R*;%L4IWse3G#VWaN1#$zSSXcBhlYlH zJ|D}n{r&w2f+Qp)SS*&n?*G4}{}!h?td_mjU6Kad-rW*QwWYCUk7d^e+sjpZAn6kT z5rM;`{~_}-wm+L@%+E;JphLm}C3WzQAQD0wBoY|rOTQX{(@pp-WAA8Iw0HKl|15j) zhGh(SFWu#5zUCw^T!}C~eC}~?{xG-r7ugQC>taC+|L^xr&a%{#cgUOZXIM&v?)k^2 z1i5%0!U00wq)r4vlAwXBA_m5J5WNeuBm-XJQ5pG4GD%vc|K){+TJ{AkHqN@SJKczX zU}QRd9n*_bf^h94)6Ctv{CUfz_;qs#RDuQ$DR%y1&5G-a6rhf*4Y zl+UOD1*GE{GQ7vv;c-tTIMkz0ovO>9j%b5hpa@a*CBJ#W!0@ndSwF|%hMkdFtXiU) z`BEp+yhkt8ZY>tg&uyH@%^%K4jDrbA;et&vnnzZ3z22@e6<27z2Vk13kb7OtISyxv z(XoO-LNQCZe3lfzc%*pNqU<9T+p~teThOEB+~e&U0mC|zS{Q)dJZ)!^1f7L(zg*L> z*vSW%?dR~@@0(}x6-V^A`~ImD;)NaBUnP8;RH=bS2@pk`wp>>8r&jGjmGPy%1BW}v zoPo}ca$~bze@efc3kapuEVW1!%teOZT3j2Tw5=hjiAf02d}7dL0oFC%@=RXp5Ow#% z@a>+AM|YWt$n&e`>sB-32gBcuTi>R>*|8@lp`y6thc7ydyqDsuUn~YzZf|{-R@-3y zL#wx{Ii}xxL_csiW*LBn0-A$>zp4WOmkf6=ilrjlW?-l9+bCd7|i?*b5NTmy<<6NZ0T8$SN@mwdC2f z6jqK=N@bS@!=YSj%>h0}*4p+%0HbTIrE$w7UMT6+AZZ&DAo*qZAAi(OtNbIfl#Dw^ zJWeiCp~zi#&t6x}m9)O^eR4HiLV3QA<<0>HZ8%$^lrSE94Wb}=+MM^!b>n#5&-JQR zkr-CEu9C;_F*7DqDisulV6Pmg$nFL0TPn%~*m^-`Z3^BgU(sNpnx%nW(!eV9A&FvI zHL3X3lw8Wji^6=8KbQDE-e%b?svdFxxdh$8r97@7$ojeI`6AL8Ob6QC$qh&>ZWVDX1F{0vJnT%%mE;GveKWR%C} zM&4d;Jf3s9|HAA)yVUPo`Aq;0do#)uHSXi5*QF*)x@MVVHr+cN)uMZ__F|&Ta#p8d z53TOKtce!PJUuie8UWol-S(`c2nH?UGqJP{!4RR4u$LCfn)q-hj0^f=h(VYy)T6eN zhRO!ja-aDBTcgeyP(8Ua4IdiOog^*CQa?R(cP#9AgL9`j>EX-6Yf1lzX(!~``M1XC zNmM<4<6d~wWZ$Xrk0K}UteTrq@LBBk#MsjkK;pbuVhe)NI7(7Pf(l?lxC7=1Z7Pzl zMbS;nV4NI5_N{1$P)&XC)huOGQ+h^zpYUZfb*1n6>!`#*b3y52LGmi+<4sY5jyD#- zw&$d}DTguLAfnRtjrM*JfqtHqUu6rQoU=g%1E9xk%;)TDm^2<8n-0IhTsQFaek)MY}>PK4;nBk=ow4pF>vzkO& gna%Org-kgQCf=+BeMi^RbuY;YE~rTjend;_cdi8t>i_@% literal 0 HcmV?d00001 diff --git a/Tests/Manual/UIBuilderDemo/UIBuilderConstants.j b/Tests/Manual/UIBuilderDemo/UIBuilderConstants.j new file mode 100644 index 000000000..1e5c5b5ec --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/UIBuilderConstants.j @@ -0,0 +1,10 @@ +// +// UIBuilderConstants.j +// Defines global constants for the UI Builder application. +// + +UIBuilderElementPboardType = "UIBuilderElementPboardType"; +UIWindowDragType = "UIWindowDragType"; +UIButtonDragType = "UIButtonDragType"; +UISliderDragType = "UISliderDragType"; +UITextFieldDragType = "UITextFieldDragType"; diff --git a/Tests/Manual/UIBuilderDemo/UIBuilderController.j b/Tests/Manual/UIBuilderDemo/UIBuilderController.j new file mode 100644 index 000000000..cb4ba5c95 --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/UIBuilderController.j @@ -0,0 +1,645 @@ +// +// UIBuilderController.j +// This is the main controller for the UI Builder application. +// It manages the data model for all elements on the canvas and acts +// as a delegate for the UICanvasView to respond to user interactions. +// +// By Daniel Boehringer in 2025. +// + +@import +@import "UIElementView.j" +@import "UICanvasView.j" +@import "UIBuilderConstants.j"; + +// This is a simple data model. In a real app, it might have more properties. +// We use a custom dictionary to ensure KVO compatibility and proper value setting. +@implementation CPConservativeDictionary : CPDictionary +{ } + +- (id)init +{ + self = [super init]; + if (self) { + // Rely on superclass to initialize _buckets + } + return self; +} + ++ (id)dictionary +{ + return [[self alloc] init]; +} + +- (void)setValue:(id)aVal forKey:(CPString)aKey +{ + // Only set the value if it's different from the current value + var currentValue = [super valueForKey:aKey]; + + + // Always set the value if the current value is null or undefined + if (currentValue == null || currentValue == undefined || currentValue != aVal) { + [super setValue:aVal forKey:aKey]; + } +} + +- (BOOL)isEqual:(id)otherObject +{ + return [self valueForKey:'id'] == [otherObject valueForKey:'id']; +} + +- (id)initWithCoder:(CPCoder)aCoder +{ + self = [super initWithCoder:aCoder]; + if (self) + { + var allKeys = [aCoder decodeObjectForKey:@"CPConservativeDictionaryKeys"]; + if (allKeys) + { + for (var i = 0; i < [allKeys count]; i++) + { + var key = allKeys[i]; + var value = [aCoder decodeObjectForKey:key]; + [self setObject:value forKey:key]; + } + } + } + return self; +} + +- (void)encodeWithCoder:(CPCoder)aCoder +{ + [super encodeWithCoder:aCoder]; + var allKeys = [self allKeys]; + [aCoder encodeObject:allKeys forKey:@"CPConservativeDictionaryKeys"]; + for (var i = 0; i < [allKeys count]; i++) + { + var key = allKeys[i]; + [aCoder encodeObject:[self objectForKey:key] forKey:key]; + } +} + +@end + + +@implementation UIBuilderController : CPViewController +{ + CPArrayController _elementsController @accessors(property=elementsController); + CPArrayController _connectionsController @accessors(property=connectionsController); + CPMutableArray _connections; + int _elementCounter; // To generate unique IDs +} + ++ (Class)classForElementType:(CPString)elementType +{ + if (elementType === "window") return UIWindowView; + if (elementType === "button") return UIButtonView; + if (elementType === "slider") return UISliderView; + if (elementType === "textfield") return UITextFieldView; + return UIElementView; +} + +- (id)init +{ + self = [super init]; + if (self) { + _elementsController = [[CPArrayController alloc] init]; + _connectionsController = [[CPArrayController alloc] init]; + _elementCounter = 0; + } + return self; +} + +#pragma mark - +#pragma mark Data Management + +- (CPDictionary)_containerDataAtPoint:(CGPoint)aPoint +{ + var allElements = [_elementsController arrangedObjects]; + for (var i = [allElements count] - 1; i >= 0; i--) + { + var elementData = allElements[i]; + var type = [elementData valueForKey:@"type"]; + if (type === "window") + { + var frame = CGRectMake([elementData valueForKey:@"originX"], [elementData valueForKey:@"originY"], [elementData valueForKey:@"width"], [elementData valueForKey:@"height"]); + if (CGRectContainsPoint(frame, aPoint)) + return elementData; + } + } + return nil; +} + +- (void)addNewElementOfType:(CPString)elementType atPoint:(CGPoint)aPoint +{ + var newElementData = [CPConservativeDictionary dictionary]; + var containerData = [self _containerDataAtPoint:aPoint]; + var viewClass = [UIBuilderController classForElementType:elementType]; + + // Set default properties based on type + [newElementData setValue:elementType forKey:@"type"]; + [newElementData setValue:@"id_" + _elementCounter++ forKey:@"id"]; + + // Set default values from the view class + var defaultValues = [viewClass defaultValues]; + for (var key in defaultValues) { + [newElementData setValue:defaultValues[key] forKey:key]; + } + + // Set default sizes + if (elementType === "window") { + [newElementData setValue:250 forKey:@"width"]; + [newElementData setValue:200 forKey:@"height"]; + [newElementData setValue:[] forKey:@"children"]; + } else if (elementType === "button") { + [newElementData setValue:100 forKey:@"width"]; + [newElementData setValue:24 forKey:@"height"]; + } else if (elementType === "slider") { + [newElementData setValue:150 forKey:@"width"]; + [newElementData setValue:20 forKey:@"height"]; + } else { // textfield + [newElementData setValue:150 forKey:@"width"]; + [newElementData setValue:22 forKey:@"height"]; + } + + // Calculate centered position + var elementWidth = [newElementData valueForKey:@"width"]; + var elementHeight = [newElementData valueForKey:@"height"]; + var centeredX = aPoint.x - (elementWidth / 2); + var centeredY = aPoint.y - (elementHeight / 2); + [newElementData setValue:centeredX forKey:@"originX"]; + [newElementData setValue:centeredY forKey:@"originY"]; + + if (containerData && elementType !== "window") + { + // Convert point to be relative to the container and center the element + var elementWidth = [newElementData valueForKey:@"width"]; + var elementHeight = [newElementData valueForKey:@"height"]; + var relativeX = (aPoint.x - [containerData valueForKey:@"originX"]) - (elementWidth / 2); + var relativeY = (aPoint.y - [containerData valueForKey:@"originY"]) - (elementHeight / 2); + [newElementData setValue:relativeX forKey:@"originX"]; + [newElementData setValue:relativeY forKey:@"originY"]; + + // Add as a child to the container + [newElementData setValue:[containerData valueForKey:@"id"] forKey:@"parentID"]; + [[containerData mutableArrayValueForKey:@"children"] addObject:newElementData]; + } + + // Add to the main controller regardless, so selection works. + [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_elementsController] removeObject:newElementData]; + [[[CPApp keyWindow] undoManager] setActionName:@"Add Element"]; + [_elementsController addObject:newElementData]; + + [_elementsController setSelectedObjects:[CPArray arrayWithObject:newElementData]]; +} + +- (void)removeSelectedElementsWithActionName:(CPString)actionName +{ + var selectedObjects = [[_elementsController selectedObjects] copy]; + if ([selectedObjects count] === 0) return; + + [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_elementsController] addObjects:selectedObjects]; + [[[CPApp keyWindow] undoManager] setActionName:actionName]; + [_elementsController removeObjects:selectedObjects]; +} + +- (void)removeSelectedElements +{ + [self removeSelectedElementsWithActionName:@"Delete"]; +} + +- (void)cut:(id)sender +{ + [self copy:sender]; + [self removeSelectedElementsWithActionName:@"Cut"]; +} + +#pragma mark - +#pragma mark Keyboard Movement + +- (void)moveSelectedElementsByDeltaX:(int)deltaX deltaY:(int)deltaY +{ + var selectedDataObjects = [_elementsController selectedObjects]; + var changes = [CPMutableArray array]; + for (var i = 0; i < [selectedDataObjects count]; i++) + { + var data = selectedDataObjects[i]; + var newFrame = { + origin: { + x: [data valueForKey:@"originX"] + deltaX, + y: [data valueForKey:@"originY"] + deltaY + } + }; + [changes addObject:{ data: data, frame: newFrame }]; + } + [self applyFrameChanges:changes withActionName:@"Move"]; +} + +- (void)moveLeft:(id)sender +{ + [self moveSelectedElementsByDeltaX:-1 deltaY:0]; +} + +- (void)moveRight:(id)sender +{ + [self moveSelectedElementsByDeltaX:1 deltaY:0]; +} + +- (void)moveUp:(id)sender +{ + [self moveSelectedElementsByDeltaX:0 deltaY:-1]; +} + +- (void)moveDown:(id)sender +{ + [self moveSelectedElementsByDeltaX:0 deltaY:1]; +} + +#pragma mark - +#pragma mark Copy & Paste + +- (void)copy:(id)sender +{ + var selectedData = [_elementsController selectedObjects]; + + if ([selectedData count] > 0) + { + var pboard = [CPPasteboard generalPasteboard]; + var data = [CPKeyedArchiver archivedDataWithRootObject:selectedData]; + + // 1. Declare that you are providing BOTH a custom type and a standard string type. + [pboard declareTypes:[UIBuilderElementPboardType, CPStringPboardType] owner:nil]; + + // 2. Set the data for your custom type, for your app's internal 'paste' to use. + [pboard setData:data forType:UIBuilderElementPboardType]; + + // 3. Set a string representation for the browser and other applications. + // This can be a simple description or a more complex JSON representation. + var description = [selectedData count] + " UI element(s) copied."; + [pboard setString:description forType:CPStringPboardType]; + } +} + +- (void)_assignNewIDsToElement:(CPMutableDictionary)elementData +{ + [elementData setValue:@"id_" + _elementCounter++ forKey:@"id"]; + + var children = [elementData valueForKey:@"children"]; + if (children) + { + var newChildren = [CPMutableArray array]; + for (var i = 0; i < [children count]; i++) + { + var child = children[i]; + // Deep copy child before modifying + var newChild = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:child]]; + [newChild setValue:[elementData valueForKey:@"id"] forKey:@"parentID"]; + [self _assignNewIDsToElement:newChild]; + [newChildren addObject:newChild]; + } + [elementData setValue:newChildren forKey:@"children"]; + } +} + +- (void)paste:(id)sender +{ + var pboard = [CPPasteboard generalPasteboard]; + var types = [pboard types]; + + if ([types containsObject:UIBuilderElementPboardType]) + { + var data = [pboard dataForType:UIBuilderElementPboardType]; + var pastedElements = [CPKeyedUnarchiver unarchiveObjectWithData:data]; + var newSelection = [CPMutableArray array]; + + // Determine the target container + var targetContainer = nil; + var selectedObjects = [_elementsController selectedObjects]; + if ([selectedObjects count] > 0) + { + var firstSelected = selectedObjects[0]; + var parentID = [firstSelected valueForKey:@"parentID"]; + if (parentID) + { + // Find the parent container in the elements controller + var allElements = [_elementsController arrangedObjects]; + for (var i = 0; i < [allElements count]; i++) + { + if ([[allElements[i] valueForKey:@"id"] isEqualToString:parentID]) + { + targetContainer = allElements[i]; + break; + } + } + } + else + { + // If the selected object has no parent, it must be a window + targetContainer = firstSelected; + } + } + else + { + // If no selection, find the first window + var allElements = [_elementsController arrangedObjects]; + for (var i = 0; i < [allElements count]; i++) + { + if ([[allElements[i] valueForKey:@"type"] isEqualToString:@"window"]) + { + targetContainer = allElements[i]; + break; + } + } + } + + for (var i = 0; i < [pastedElements count]; i++) + { + var newElement = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:pastedElements[i]]]; + + [newElement setValue:[newElement valueForKey:@"originX"] + 10 forKey:@"originX"]; + [newElement setValue:[newElement valueForKey:@"originY"] + 10 forKey:@"originY"]; + + [self _assignNewIDsToElement:newElement]; + + if (targetContainer && [newElement valueForKey:@"type"] !== @"window") + { + [newElement setValue:[targetContainer valueForKey:@"id"] forKey:@"parentID"]; + [[targetContainer mutableArrayValueForKey:@"children"] addObject:newElement]; + } + else + { + [newElement removeObjectForKey:@"parentID"]; + } + + [_elementsController addObject:newElement]; + + if ([newElement valueForKey:@"children"]) + [_elementsController addObjects:[newElement valueForKey:@"children"]]; + + [newSelection addObject:newElement]; + } + + [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_elementsController] removeObjects:newSelection]; + [[[CPApp keyWindow] undoManager] setActionName:@"Paste"]; + [_elementsController setSelectedObjects:newSelection]; + } +} + +- (void)addNewElementOfType:(CPString)elementType inNewWindowAtPoint:(CGPoint)aPoint +{ + // 1. Create the new element to be placed in the window + var newElementData = [CPConservativeDictionary dictionary]; + var viewClass = [UIBuilderController classForElementType:elementType]; + [newElementData setValue:elementType forKey:@"type"]; + [newElementData setValue:@"id_" + _elementCounter++ forKey:@"id"]; + + var defaultValues = [viewClass defaultValues]; + for (var key in defaultValues) + [newElementData setValue:defaultValues[key] forKey:key]; + + var elementWidth, elementHeight; + + if (elementType === "button") { + elementWidth = 100; + elementHeight = 24; + } else if (elementType === "slider") { + elementWidth = 150; + elementHeight = 20; + } else { // textfield + elementWidth = 150; + elementHeight = 22; + } + [newElementData setValue:elementWidth forKey:@"width"]; + [newElementData setValue:elementHeight forKey:@"height"]; + + // 2. Create the window that will contain the new element + var windowData = [CPConservativeDictionary dictionary]; + var windowClass = [UIBuilderController classForElementType:"window"]; + var windowWidth = 250, windowHeight = 200; + [windowData setValue:@"window" forKey:@"type"]; + [windowData setValue:@"id_" + _elementCounter++ forKey:@"id"]; + [windowData setValue:windowWidth forKey:@"width"]; + [windowData setValue:windowHeight forKey:@"height"]; + [windowData setValue:[] forKey:@"children"]; + + defaultValues = [windowClass defaultValues]; + for (var key in defaultValues) { + [windowData setValue:defaultValues[key] forKey:key]; + } + + // 3. Position the new element in the center of the window + var elementX = (windowWidth - elementWidth) / 2; + var elementY = (windowHeight - elementHeight) / 2; + [newElementData setValue:elementX forKey:@"originX"]; + [newElementData setValue:elementY forKey:@"originY"]; + + // 4. Position the window so the element is at the drop point + var windowX = aPoint.x - elementX; + var windowY = aPoint.y - elementY; + [windowData setValue:windowX forKey:@"originX"]; + [windowData setValue:windowY forKey:@"originY"]; + + // 5. Add the element to the window's children + [newElementData setValue:[windowData valueForKey:@"id"] forKey:@"parentID"]; + [[windowData mutableArrayValueForKey:@"children"] addObject:newElementData]; + + console.log("UIBuilderController: addNewElementOfType:inNewWindowAtPoint: - Adding new element to window's children:", newElementData); + + // 6. Add both to the elements controller + var undoManager = [[CPApp keyWindow] undoManager]; + [undoManager beginUndoGrouping]; + [[undoManager prepareWithInvocationTarget:_elementsController] removeObject:newElementData]; + [[undoManager prepareWithInvocationTarget:_elementsController] removeObject:windowData]; + [undoManager setActionName:@"Add Element in New Window"]; + [_elementsController addObject:windowData]; + [_elementsController addObject:newElementData]; + [undoManager endUndoGrouping]; + + // 7. Select the new element + [_elementsController setSelectedObjects:[CPArray arrayWithObject:newElementData]]; +} + +- (void)addNewElementOfType:(CPString)elementType inWindow:(CPDictionary)windowData atPoint:(CGPoint)aPoint +{ + console.log("UIBuilderController: addNewElementOfType:inWindow:atPoint: - Adding element ", elementType, " to window ", windowData, " at point ", aPoint); + var newElementData = [CPConservativeDictionary dictionary]; + var viewClass = [UIBuilderController classForElementType:elementType]; + + [newElementData setValue:elementType forKey:@"type"]; + [newElementData setValue:@"id_" + _elementCounter++ forKey:@"id"]; + + var defaultValues = [viewClass defaultValues]; + for (var key in defaultValues) + [newElementData setValue:defaultValues[key] forKey:key]; + + var elementWidth, elementHeight; + + if (elementType === "button") { + elementWidth = 100; + elementHeight = 24; + } else if (elementType === "slider") { + elementWidth = 150; + elementHeight = 20; + } else { // textfield + elementWidth = 150; + elementHeight = 22; + } + [newElementData setValue:elementWidth forKey:@"width"]; + [newElementData setValue:elementHeight forKey:@"height"]; + + // Position the new element relative to the window's origin + [newElementData setValue:aPoint.x forKey:@"originX"]; + [newElementData setValue:aPoint.y forKey:@"originY"]; + + // Add as a child to the container window + [newElementData setValue:[windowData valueForKey:@"id"] forKey:@"parentID"]; + [[windowData mutableArrayValueForKey:@"children"] addObject:newElementData]; + + // Add to the main controller + var undoManager = [[CPApp keyWindow] undoManager]; + [undoManager beginUndoGrouping]; + [[undoManager prepareWithInvocationTarget:_elementsController] removeObject:newElementData]; + [undoManager setActionName:@"Add Element to Window"]; + [_elementsController addObject:newElementData]; + [undoManager endUndoGrouping]; + + [_elementsController setSelectedObjects:[CPArray arrayWithObject:newElementData]]; +} + +- (void)addConnectionFrom:(CPDictionary)sourceData to:(CPDictionary)targetData atPoint:(CGPoint)atPoint outlet:(CPString)outlet action:(CPString)action +{ + var newConnection = [CPConservativeDictionary dictionary]; + [newConnection setValue:[sourceData valueForKey:@"id"] forKey:@"sourceID"]; + [newConnection setValue:[targetData valueForKey:@"id"] forKey:@"targetID"]; + [newConnection setValue:outlet forKey:@"outlet"]; + [newConnection setValue:action forKey:@"action"]; + [newConnection setValue:@"connection_" + _elementCounter++ forKey:@"id"]; + + if (atPoint) + [newConnection setValue:{x: atPoint.x, y: atPoint.y} forKey:@"atPoint"]; + + [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_connectionsController] removeObject:newConnection]; + [[[CPApp keyWindow] undoManager] setActionName:@"Add Connection"]; + + [_connectionsController addObject:newConnection]; + + console.log("UIBuilderController: addConnectionFrom:to: - Added connection: ", newConnection); + console.log("Connections controller count after add: " + [[_connectionsController arrangedObjects] count]); +} + +- (void)removeConnection:(CPDictionary)connection +{ + [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_connectionsController] addObject:connection]; + [[[CPApp keyWindow] undoManager] setActionName:@"Remove Connection"]; + + [_connectionsController removeObject:connection]; +} + +#pragma mark - +#pragma mark UICanvasView Delegate Methods + +- (void)applyFrameChanges:(CPArray)changes withActionName:(CPString)actionName +{ + var undoManager = [[CPApp keyWindow] undoManager]; + var undoChanges = [CPMutableArray array]; + + [undoManager beginUndoGrouping]; + [undoManager setActionName:actionName]; + + for (var i = 0; i < [changes count]; i++) + { + var change = changes[i]; + var data = change.data; + var newFrame = change.frame; + var oldValues = { data: data, frame: {} }; + + if (newFrame.origin) + { + oldValues.frame.origin = { + x: [data valueForKey:@"originX"], + y: [data valueForKey:@"originY"] + }; + [data setValue:newFrame.origin.x forKey:@"originX"]; + [data setValue:newFrame.origin.y forKey:@"originY"]; + } + + if (newFrame.size) + { + oldValues.frame.size = { + width: [data valueForKey:@"width"], + height: [data valueForKey:@"height"] + }; + [data setValue:newFrame.size.width forKey:@"width"]; + [data setValue:newFrame.size.height forKey:@"height"]; + } + [undoChanges addObject:oldValues]; + } + + [[undoManager prepareWithInvocationTarget:self] applyFrameChanges:undoChanges withActionName:actionName]; + [undoManager endUndoGrouping]; +} + +- (void)canvasView:(UICanvasView)aCanvas didMoveElement:(UIElementView)anElement +{ + var selectedViews = [aCanvas selectedSubViews]; + var changes = [CPMutableArray array]; + for (var i = 0; i < [selectedViews count]; i++) + { + var view = selectedViews[i]; + [changes addObject:{ data: [view dataObject], frame: { origin: [view frame].origin } }]; + } + [self applyFrameChanges:changes withActionName:@"Move"]; +} + +- (void)canvasView:(UICanvasView)aCanvas didResizeElement:(UIElementView)anElement +{ + var changes = [CPMutableArray array]; + var frame = [anElement frame]; + [changes addObject:{ data: [anElement dataObject], frame: { origin: frame.origin, size: frame.size } }]; + [self applyFrameChanges:changes withActionName:@"Resize"]; +} + +- (void)canvasView:(UICanvasView)aCanvas didConnectElement:(UIElementView)sourceElement toElement:(UIElementView)targetElement asTargetAction:(CPString)actionName +{ + var sourceData = [sourceElement dataObject]; + var targetData = [targetElement dataObject]; + + // For a target-action, the outlet is typically 'target' + var outletName = @"target"; + + [self addConnectionFrom:sourceData to:targetData atPoint:nil outlet:outletName action:actionName]; +} + +- (void)canvasView:(UICanvasView)aCanvas didConnectElement:(UIElementView)sourceElement toElement:(UIElementView)targetElement asOutlet:(CPString)outletName +{ + var sourceData = [sourceElement dataObject]; + var targetData = [targetElement dataObject]; + + // For a simple outlet connection, there is no action. + var actionName = nil; + + [self addConnectionFrom:sourceData to:targetData atPoint:nil outlet:outletName action:actionName]; +} + +- (void)changeValue:(id)newValue forObject:(id)dataObject +{ + var oldValue = [dataObject valueForKey:@"value"]; + if (oldValue != newValue) + { + var undoManager = [[CPApp keyWindow] undoManager]; + [[undoManager prepareWithInvocationTarget:self] changeValue:oldValue forObject:dataObject]; + [undoManager setActionName:@"Change Value"]; + [dataObject setValue:newValue forKey:@"value"]; + } +} + +- (void)changeValueForSelectedObject:(id)newValue +{ + var selectedObjects = [[self elementsController] selectedObjects]; + if ([selectedObjects count] === 1) + { + [self changeValue:newValue forObject:selectedObjects[0]]; + } +} + +@end diff --git a/Tests/Manual/UIBuilderDemo/UICanvasView.j b/Tests/Manual/UIBuilderDemo/UICanvasView.j new file mode 100644 index 000000000..c374e9f42 --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/UICanvasView.j @@ -0,0 +1,996 @@ +// +// UICanvasView.j +// A full-window canvas for the UI Builder. +// +// By Daniel Boehringer in 2025. +// - It acts as a drag-and-drop destination for new UI elements from the palette. +// - It correctly instantiates different UIElementView subclasses based on the data model. +// + +@import "UIBuilderConstants.j"; +@import "UIElementView.j"; +@import "ConnectionView.j"; +@import "UIBuilderConstants.j"; + +function treshold(value, limit) +{ + return value > 0 ? Math.min(value, limit) : Math.max(value, -limit); +} + +@implementation UICanvasView : CPView +{ + // Data binding ivars + id _dataObjectsContainer; + CPString _dataObjectsKeyPath; + id _selectionIndexesContainer; + CPString _selectionIndexesKeyPath; + CPArray _oldDataObjects; + + // Connections ivars + id _connectionsContainer; + CPString _connectionsKeyPath; + CPArray _oldConnections; + id _selectedConnectionsContainer; + CPString _selectedConnectionsKeyPath; + + // Rubber-band selection ivars + CGPoint _rubberStart; + CGPoint _rubberEnd; + BOOL _isRubbing; + + ConnectionView _connectionView; + + id _delegate; + + // Connection Menu ivars + UIElementView _connectionSource; + UIElementView _connectionTarget; + BOOL _connectionMade; +} + +-(BOOL)acceptsFirstMouse:(CPEvent)aEvent +{ + return YES; +} + +// KVO contexts +var _propertyObservationContext = 1091; +var _dataObjectsObservationContext = 1092; +var _selectionIndexesObservationContext = 1093; +var _connectionsObservationContext = 1094; +var _selectedConnectionsObservationContext = 1095; + +- (id)initWithFrame:(CGRect)aRect +{ + self = [super initWithFrame:aRect]; + + if (self) + { + // Register to accept drops from the palette + [self registerForDraggedTypes:[ + UIWindowDragType, + UIButtonDragType, + UISliderDragType, + UITextFieldDragType + ]]; + + _connectionView = [[ConnectionView alloc] initWithFrame:[self bounds]]; + [_connectionView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [self addSubview:_connectionView]; + } + return self; +} + +#pragma mark - Bindings & KVO (Largely from EFLaceView) + ++ (void)initialize +{ + [self exposeBinding:"dataObjects"]; + [self exposeBinding:@"selectionIndexes"]; + [self exposeBinding:@"connections"]; + [self exposeBinding:@"selectedConnections"]; +} + +- (void)bind:(CPString)bindingName toObject:(id)observableObject withKeyPath:(CPString)observableKeyPath options:(CPDictionary)options +{ + if ([bindingName isEqualToString:@"dataObjects"]) + { + _dataObjectsContainer = observableObject; + _dataObjectsKeyPath = observableKeyPath; + [_dataObjectsContainer addObserver:self forKeyPath:_dataObjectsKeyPath options:(CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld) context:_dataObjectsObservationContext]; + [self startObservingDataObjects:[self dataObjects]]; + _oldDataObjects = [[self dataObjects] copy] || @[]; + } + else if ([bindingName isEqualToString:@"selectionIndexes"]) + { + _selectionIndexesContainer = observableObject; + _selectionIndexesKeyPath = observableKeyPath; + [_selectionIndexesContainer addObserver:self forKeyPath:_selectionIndexesKeyPath options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:_selectionIndexesObservationContext]; + } + else if ([bindingName isEqualToString:@"connections"]) + { + _connectionsContainer = observableObject; + _connectionsKeyPath = observableKeyPath; + [_connectionsContainer addObserver:self forKeyPath:_connectionsKeyPath options:(CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld) context:_connectionsObservationContext]; + _oldConnections = [[self connections] copy] || @[]; + } + else if ([bindingName isEqualToString:@"selectedConnections"]) + { + _selectedConnectionsContainer = observableObject; + _selectedConnectionsKeyPath = observableKeyPath; + [_selectedConnectionsContainer addObserver:self forKeyPath:_selectedConnectionsKeyPath options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:_selectedConnectionsObservationContext]; + } + else { [super bind:bindingName toObject:observableObject withKeyPath:observableKeyPath options:options]; } + + [self setNeedsDisplay:YES]; +} + +- (void)unbind:(CPString)bindingName +{ + if ([bindingName isEqualToString:@"dataObjects"]) { + [self stopObservingDataObjects:[self dataObjects]]; + [_dataObjectsContainer removeObserver:self forKeyPath:_dataObjectsKeyPath]; + _dataObjectsContainer = nil; _dataObjectsKeyPath = nil; + } else if ([bindingName isEqualToString:@"selectionIndexes"]) { + [_selectionIndexesContainer removeObserver:self forKeyPath:_selectionIndexesKeyPath]; + _selectionIndexesContainer = nil; _selectionIndexesKeyPath = nil; + } else if ([bindingName isEqualToString:@"connections"]) { + [_connectionsContainer removeObserver:self forKeyPath:_connectionsKeyPath]; + _connectionsContainer = nil; _connectionsKeyPath = nil; + } else if ([bindingName isEqualToString:@"selectedConnections"]) { + [_selectedConnectionsContainer removeObserver:self forKeyPath:_selectedConnectionsKeyPath]; + _selectedConnectionsContainer = nil; _selectedConnectionsKeyPath = nil; + } else { [super unbind:bindingName]; } + [self setNeedsDisplay:YES]; +} + +- (CPArray)dataObjects +{ + var result = [_dataObjectsContainer valueForKeyPath:_dataObjectsKeyPath]; + return (result == [CPNull null]) ? @[] : result; +} + +- (CPIndexSet)selectionIndexes +{ + return [_selectionIndexesContainer valueForKeyPath:_selectionIndexesKeyPath]; +} + +- (CPArray)connections +{ + var result = [_connectionsContainer valueForKeyPath:_connectionsKeyPath]; + return (result == [CPNull null]) ? @[] : result; +} + +- (CPArray)selectedConnections +{ + var result = [_selectedConnectionsContainer valueForKeyPath:_selectedConnectionsKeyPath]; + return (result == [CPNull null]) ? @[] : result; +} + +- (void)setSelectionIndexes:(CPIndexSet)indexes +{ + [_selectionIndexesContainer setValue:indexes forKeyPath:_selectionIndexesKeyPath]; +} + +- (void)startObservingDataObjects:(CPArray)dataObjects +{ + if (!dataObjects || dataObjects == [CPNull null]) + return; + + for (var i = 0; i < [dataObjects count]; i++) + { + var newDataObject = dataObjects[i]; + // Only create views for top-level objects. Children are handled by their parents. + if (![newDataObject valueForKey:@"parentID"]) + [self _createViewForDataObject:newDataObject superview:self]; + } +} + +- (void)_createViewForDataObject:(CPDictionary)dataObject superview:(CPView)superview +{ + var type = [dataObject valueForKey:@"type"]; + var newView; + + // Instantiate the correct view based on the data model's 'type' + if (type === "window") + newView = [[UIWindowView alloc] init]; + else if (type === "button") + newView = [[UIButtonView alloc] init]; + else if (type === "slider") + newView = [[UISliderView alloc] init]; + else if (type === "textfield") + newView = [[UITextFieldView alloc] init]; + else + newView = [[UIElementView alloc] init]; // Fallback + + [newView setDataObject:dataObject]; + + // Bind view properties to the data model + [newView bind:@"originX" toObject:dataObject withKeyPath:@"originX" options:nil]; + [newView bind:@"originY" toObject:dataObject withKeyPath:@"originY" options:nil]; + [newView bind:@"width" toObject:dataObject withKeyPath:@"width" options:nil]; + [newView bind:@"height" toObject:dataObject withKeyPath:@"height" options:nil]; + + if (type === "window") + { + var children = [dataObject valueForKey:@"children"]; + for (var j = 0; j < [children count]; j++) + { + [self _createViewForDataObject:children[j] superview:newView]; + } + } + + [superview addSubview:newView]; + // i have no idea why this is needed, but it is to make the initial click work + [CPApp._delegate._window makeKeyAndOrderFront:self]; +} + +- (void)stopObservingDataObjects:(CPArray)dataObjects +{ + if (!dataObjects || dataObjects == [CPNull null]) return; + + var viewsToRemove = [CPMutableArray array]; + [self _findViewsForDataObjects:dataObjects inView:self foundViews:viewsToRemove]; + + for (var i = 0; i < [viewsToRemove count]; i++) { + var viewToRemove = viewsToRemove[i]; + [self _removeViewAndChildren:viewToRemove]; + } +} + +- (void)_removeViewAndChildren:(UIElementView)viewToRemove +{ + // Unbind everything before removing + [viewToRemove unbind:@"value"]; + [viewToRemove unbind:@"originX"]; + [viewToRemove unbind:@"originY"]; + [viewToRemove unbind:@"width"]; + [viewToRemove unbind:@"height"]; + + var subviews = [[viewToRemove subviews] copy]; + for (var i = 0; i < [subviews count]; i++) + { + [self _removeViewAndChildren:subviews[i]]; + } + + [viewToRemove removeFromSuperview]; +} + +- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context +{ + if (context == _dataObjectsObservationContext) + { + var newDataObjects = [object valueForKeyPath:_dataObjectsKeyPath]; + var oldDataObjects = _oldDataObjects; + + var added = [newDataObjects mutableCopy]; + [added removeObjectsInArray:oldDataObjects]; + [self startObservingDataObjects:added]; + + var removed = [oldDataObjects mutableCopy]; + [removed removeObjectsInArray:newDataObjects]; + [self stopObservingDataObjects:removed]; + + _oldDataObjects = [newDataObjects copy]; + [self setNeedsDisplay:YES]; + } + else if (context == _selectionIndexesObservationContext) + { + var allDataObjects = [self dataObjects]; + var newIndexes = [change objectForKey:CPKeyValueChangeNewKey] || [CPIndexSet indexSet]; + var oldIndexes = [change objectForKey:CPKeyValueChangeOldKey] || [CPIndexSet indexSet]; + + // Find views for newly selected objects and redraw them + var newSelectedDataObjects = [allDataObjects objectsAtIndexes:newIndexes]; + var newlySelectedViews = [CPMutableArray array]; + [self _findViewsForDataObjects:newSelectedDataObjects inView:self foundViews:newlySelectedViews]; + [newlySelectedViews makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:YES]; + + // Find views for deselected objects and redraw them, but only if those objects still exist. + var previouslySelectedViews = [CPMutableArray array]; + var oldSelectedDataObjects = [CPMutableArray array]; + var lastIndex = [oldIndexes lastIndex]; + + if (lastIndex != CPNotFound && lastIndex < [allDataObjects count]) + { + oldSelectedDataObjects = [allDataObjects objectsAtIndexes:oldIndexes]; + } + else + { + // If the indexes are out of bounds, it likely means the objects were deleted. + // We need to find the views that were associated with the old indexes another way. + // This is a tricky state to recover from. For now, we will just redraw all views. + // A more sophisticated solution might involve caching view-data relationships. + [[self subviews] makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:YES]; + return; + } + + [self _findViewsForDataObjects:oldSelectedDataObjects inView:self foundViews:previouslySelectedViews]; + [previouslySelectedViews makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:YES]; + } + else if (context == _connectionsObservationContext) + { + var newConnections = [object valueForKeyPath:_connectionsKeyPath]; + var oldConnections = _oldConnections; + + // For now, simply redraw all connections. A more optimized approach would be to only redraw changed connections. + [self setNeedsDisplay:YES]; + _oldConnections = [newConnections copy]; + } + else if (context == _selectedConnectionsObservationContext) + { + [self setNeedsDisplay:YES]; + } +} + +#pragma mark - Drawing & Mouse + +- (void)drawRect:(CPRect)rect +{ + // === START: Infographic Drawing === + + var bounds = [self bounds]; + + // 1. Define text attributes for the infographic + var titleFont = [CPFont fontWithName:@"Helvetica-Bold" size:36]; + var subtitleFont = [CPFont fontWithName:@"Helvetica" size:18]; + var featureFont = [CPFont fontWithName:@"Helvetica" size:14]; + var watermarkColor = [CPColor colorWithCalibratedWhite:0.85 alpha:1.0]; // A light gray for the watermark effect + + var titleAttributes = @{ + CPFontAttributeName: titleFont, + CPForegroundColorAttributeName: watermarkColor + }; + var subtitleAttributes = @{ + CPFontAttributeName: subtitleFont, + CPForegroundColorAttributeName: watermarkColor + }; + var featureAttributes = @{ + CPFontAttributeName: featureFont, + CPForegroundColorAttributeName: watermarkColor + }; + + // 2. Prepare the text content + var title = @"Cappuccino JS"; + var subtitle = @"Desktop-Quality Applications in the Browser"; + var features = [ + @"• Drag-and-Drop UI Creation", + @"• Direct Manipulation: Move & Resize (Keyboard / Mouse)", + @"• Undo/Redo & Keyboard Navigation", + @"• Control-Draggin -> Target-Action & Outlet Connections", + @"• Context sensitive inspector panel", + @"• Run the 'real thing' in a separate native window", + @"• Source: https://github.com/daboe01/UIBuilder" + + ]; + + // 3. Calculate positions and draw the text, centering it on the canvas + var titleSize = [title sizeWithAttributes:titleAttributes]; + var subtitleSize = [subtitle sizeWithAttributes:subtitleAttributes]; + var totalHeight = titleSize.height + subtitleSize.height + ([features count] * 20) + 40; // Approximate total height + var currentY = (bounds.size.height - totalHeight) / 2.0; + + // Draw Title + var titlePoint = CGPointMake((bounds.size.width - titleSize.width) / 2.0, currentY); + [title drawAtPoint:titlePoint withAttributes:titleAttributes]; + currentY += titleSize.height + 10; + + // Draw Subtitle + var subtitlePoint = CGPointMake((bounds.size.width - subtitleSize.width) / 2.0, currentY); + [subtitle drawAtPoint:subtitlePoint withAttributes:subtitleAttributes]; + currentY += subtitleSize.height + 30; + + // Draw Feature List + for (var i = 0; i < [features count]; i++) { + var feature = features[i]; + var featureSize = [feature sizeWithAttributes:featureAttributes]; + var featurePoint = CGPointMake((bounds.size.width - featureSize.width) / 2.0, currentY); + [feature drawAtPoint:featurePoint withAttributes:featureAttributes]; + currentY += featureSize.height + 5; + } + + // === END: Infographic Drawing === + + // The background is drawn by the window. We only draw the rubber-band. + if (_isRubbing) + { + var rubber = CGRectUnion(CGRectMake(_rubberStart.x, _rubberStart.y, 0.1, 0.1), CGRectMake(_rubberEnd.x, _rubberEnd.y, 0.1, 0.1)); + [[[[CPColor alternateSelectedControlColor] colorWithAlphaComponent:0.2] setFill]]; + [CPBezierPath fillRect:rubber]; + [[CPColor alternateSelectedControlColor] setStroke]; + [CPBezierPath setDefaultLineWidth:1.0]; + [CPBezierPath strokeRect:rubber]; + } + + // Draw existing connections that are selected in the connections controller. + var selectedConnections = [self selectedConnections]; + + if (selectedConnections && [selectedConnections count] > 0) + { + for (var i = 0; i < [selectedConnections count]; i++) + { + var connection = [selectedConnections objectAtIndex:i]; + var sourceID = [connection valueForKey:@"sourceID"]; + var targetID = [connection valueForKey:@"targetID"]; + var sourceView = [self viewForElementWithID:sourceID]; + var targetView = [self viewForElementWithID:targetID]; + + if (sourceView && targetView) + { + var startPoint = [sourceView convertPoint:CGPointMake(CGRectGetMidX([sourceView bounds]), CGRectGetMidY([sourceView bounds])) toView:self]; + var endPoint; + var connectionPoint = [connection valueForKey:@"atPoint"]; + + if (connectionPoint) { + endPoint = CGPointMake(connectionPoint.x, connectionPoint.y); + } else { + endPoint = [targetView convertPoint:CGPointMake(CGRectGetMidX([targetView bounds]), CGRectGetMidY([targetView bounds])) toView:self]; + } + + // Draw the link with a distinct color, like blue. + [self drawLinkFrom:startPoint to:endPoint color:[CPColor blueColor]]; + } + } + } +} + +- (void)drawLinkFrom:(CGPoint)startPoint to:(CGPoint)endPoint color:(CPColor)insideColor +{ + + var dist = Math.sqrt(Math.pow(startPoint.x - endPoint.x, 2) + Math.pow(startPoint.y - endPoint.y, 2)); + + // a lace is made of an outside gray line of width 5, and a inside insideColor(ed) line of width 3 + var p0 = CGPointMake(startPoint.x, startPoint.y); + var p3 = CGPointMake(endPoint.x, endPoint.y); + + var p1 = CGPointMake(startPoint.x + treshold((endPoint.x - startPoint.x) / 2, 50), startPoint.y); + var p2 = CGPointMake(endPoint.x - treshold((endPoint.x - startPoint.x) / 2, 50), endPoint.y); + + // p0 and p1 are on the same horizontal line + // distance between p0 and p1 is set with the treshold fuction + // the same holds for p2 and p3 + + var path = [CPBezierPath bezierPath]; + [path setLineWidth:0]; + [[CPColor grayColor] set]; + [path appendBezierPathWithOvalInRect:CGRectMake(startPoint.x-2.5,startPoint.y-2.5,5,5)]; + [path fill]; + + path = [CPBezierPath bezierPath]; + [path setLineWidth:0]; + [insideColor set]; + [path appendBezierPathWithOvalInRect:CGRectMake(startPoint.x-1.5,startPoint.y-1.5,3,3)]; + [path fill]; + + path = [CPBezierPath bezierPath]; + [path setLineWidth:0]; + [[CPColor grayColor] set]; + [path appendBezierPathWithOvalInRect:CGRectMake(endPoint.x-2.5,endPoint.y-2.5,5,5)]; + [path fill]; + + path = [CPBezierPath bezierPath]; + [path setLineWidth:0]; + [insideColor set]; + [path appendBezierPathWithOvalInRect:CGRectMake(endPoint.x-1.5,endPoint.y-1.5,3,3)]; + [path fill]; + + // if the line is rather short, draw a straight line. the curve would look rather odd in this case. + if (dist < 40) + { + path = [CPBezierPath bezierPath]; + [path setLineWidth:5]; + [path moveToPoint:startPoint]; + [path lineToPoint:endPoint]; + [[CPColor grayColor] set]; + [path stroke]; + + path = [CPBezierPath bezierPath]; + [path setLineWidth:3]; + [path moveToPoint:startPoint]; + [path lineToPoint:endPoint]; + [insideColor set]; + [path stroke]; + + return; + } + + path = [CPBezierPath bezierPath]; + [path setLineWidth:5]; + [path moveToPoint:p0]; + [path curveToPoint:p3 controlPoint1:p1 controlPoint2:p2]; + [[CPColor grayColor] set]; + [path stroke]; + + path = [CPBezierPath bezierPath]; + [path setLineWidth:3]; + [path moveToPoint:p0]; + [path curveToPoint:p3 controlPoint1:p1 controlPoint2:p2]; + [insideColor set]; + [path stroke]; +} + +#pragma mark - View Lookup + +// Private recursive helper method to search the entire view hierarchy. +- (UIElementView)_findViewForElementWithID:(CPString)elementID inView:(CPView)aView +{ + // Iterate through all subviews of the current view + var subviews = [aView subviews]; + for (var i = 0; i < [subviews count]; i++) + { + var subview = subviews[i]; + + // We are only interested in UIElementView subclasses + if (![subview isKindOfClass:[UIElementView class]]) + continue; + + // 1. Check if the current subview is the one we are looking for. + if ([[subview dataObject] valueForKey:@"id"] === elementID) + { + return subview; // Found it! + } + + // 2. If not, and this subview has children, recurse into it. + // This is the key step to search inside containers like UIWindowView. + if ([[subview subviews] count] > 0) + { + var foundView = [self _findViewForElementWithID:elementID inView:subview]; + if (foundView) + { + return foundView; // Found it in a nested hierarchy. + } + } + } + + // If we've searched this entire branch and found nothing, return nil. + return nil; +} + +// Public method to start the search from the canvas itself. +- (UIElementView)viewForElementWithID:(CPString)elementID +{ + if (!elementID) + return nil; + + // Start the recursive search from the top-level canvas view. + return [self _findViewForElementWithID:elementID inView:self]; +} + +- (void)drawConnectionFrom:(CGPoint)startPoint to:(CGPoint)endPoint +{ + [_connectionView setStartPoint:startPoint]; + [_connectionView setEndPoint:endPoint]; + [_connectionView setHidden:NO]; // Ensure it's visible when drawing + [self addSubview:_connectionView]; // Bring to front + [_connectionView setNeedsDisplay:YES]; +} + +- (void)clearConnection +{ + [_connectionView setHidden:YES]; + [_connectionView setNeedsDisplay:YES]; // Request redraw to clear old line +} + +- (UIElementView)viewAtPoint:(CGPoint)aPoint +{ + return [self _findDeepestUIElementViewAtPoint:aPoint inView:self]; +} + +- (UIElementView)_findDeepestUIElementViewAtPoint:(CGPoint)aPoint inView:(CPView)currentView +{ + // Iterate through subviews in reverse order to get the topmost view + for (var i = [[currentView subviews] count] - 1; i >= 0; i--) + { + var subview = [[currentView subviews] objectAtIndex:i]; + + // Convert the point to the subview's coordinate system + var localPoint = [subview convertPoint:aPoint fromView:currentView]; + + if ([subview isKindOfClass:[UIElementView class]]) + { + if (CGRectContainsPoint([subview bounds], localPoint)) + { + // If this is a container view, recursively search its subviews + if (subview._isContainer) + { + var deepestView = [self _findDeepestUIElementViewAtPoint:localPoint inView:subview]; + + if (deepestView) + return deepestView; + } + // If not a container, or no deeper view found, return this view + return subview; + } + } + } + + return nil; +} + +- (void)mouseDown:(CPEvent)theEvent +{ + if (_connectionSource) + { + [self menuDidEndTracking:nil]; + return; + } + // A click on the canvas background starts a rubber-band selection. + [self deselectViews]; + _isRubbing = YES; + _rubberStart = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + _rubberEnd = _rubberStart; + + [CPApp setTarget:self selector:@selector(_dragOpenSpaceWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; +} + +- (void)_dragOpenSpaceWithEvent:(CPEvent)theEvent +{ + var mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + _rubberEnd = mouseLoc; + var rubberRect = CGRectUnion(CGRectMake(_rubberStart.x, _rubberStart.y, 1, 1), CGRectMake(_rubberEnd.x, _rubberEnd.y, 1, 1)); + + switch ([theEvent type]) + { + case CPLeftMouseDragged: + var indexesToSelect = [CPMutableIndexSet indexSet]; + var allDataObjects = [self dataObjects]; + for (var i = 0; i < [[self subviews] count]; i++) { + var aView = [self subviews][i]; + if ([aView isKindOfClass:[UIElementView class]] && CGRectIntersectsRect([aView frame], rubberRect)) { + var dataIndex = [allDataObjects indexOfObject:[aView dataObject]]; + if (dataIndex != CPNotFound) { + [indexesToSelect addIndex:dataIndex]; + } + } + } + [_selectionIndexesContainer setValue:indexesToSelect forKeyPath:_selectionIndexesKeyPath]; + [self setNeedsDisplay:YES]; + [CPApp setTarget:self selector:@selector(_dragOpenSpaceWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; + break; + + case CPLeftMouseUp: + _isRubbing = NO; + [self setNeedsDisplay:YES]; + break; + } +} + +- (void)delete:(id)sender +{ + // Forward the delete action to the delegate/controller + if (_delegate && [_delegate respondsToSelector:@selector(removeSelectedElements)]) { + [_delegate removeSelectedElements]; + } +} + +- (void)cut:(id)sender +{ + if (_delegate && [_delegate respondsToSelector:@selector(cut:)]) { + [_delegate cut:sender]; + } +} + +- (void)copy:(id)sender +{ + if (_delegate && [_delegate respondsToSelector:@selector(copy:)]) { + [_delegate copy:sender]; + } +} + +- (void)paste:(id)sender +{ + if (_delegate && [_delegate respondsToSelector:@selector(paste:)]) { + [_delegate paste:sender]; + } +} + +- (void)viewDidMoveToWindow +{ + [super viewDidMoveToWindow]; + + if ([self window]) + { + [[self window] makeFirstResponder:self]; + } +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +/* +- (BOOL)validateMenuItem:(CPMenuItem)aMenuItem +{ + var action = [aMenuItem action]; + + if (action == @selector(copy:) || action == @selector(cut:) || action == @selector(delete:)) + { + return [[self selectionIndexes] count] > 0; + } + + if (action == @selector(paste:)) + { + return [[[CPPasteboard generalPasteboard] types] containsObject:UIBuilderElementPboardType]; + } + + if (action == @selector(createTargetActionConnection:) || action == @selector(createOutletConnection:)) + { + return YES; + } + + var undoManager = [[self window] undoManager]; + + if (action == @selector(undo:)) + { + return [undoManager canUndo]; + } + + if (action == @selector(redo:)) + { + return [undoManager canRedo]; + } + + return [super validateMenuItem:aMenuItem]; +} +*/ + +- (void)keyDown:(CPEvent)theEvent +{ + var characters = [theEvent characters]; + var flags = [theEvent modifierFlags]; + var selectors = [CPKeyBinding selectorsForKey:characters modifierFlags:flags]; + var delegate = [self delegate]; + var handled = NO; + + if (selectors && delegate) + { + for (var i = 0; i < [selectors count]; i++) + { + var selectorName = selectors[i]; + if ([delegate respondsToSelector:selectorName]) + { + [delegate performSelector:selectorName withObject:self]; + handled = YES; + break; + } + } + } + + if (!handled) + [super keyDown:theEvent]; +} + +#pragma mark - Drag and Drop Destination + +- (CPDragOperation)draggingEntered:(CPDraggingInfo)sender +{ + // We accept any of the registered types + return CPDragOperationCopy; +} + +- (BOOL)performDragOperation:(CPDraggingInfo)sender +{ + var dropPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; + var pasteboard = [sender draggingPasteboard]; + var types = [pasteboard types]; + var draggedType = types[0]; // Assuming only one type is being dragged + var elementType; + + if (draggedType === UIWindowDragType) elementType = "window"; + else if (draggedType === UIButtonDragType) elementType = "button"; + else if (draggedType === UISliderDragType) elementType = "slider"; + else if (draggedType === UITextFieldDragType) elementType = "textfield"; + + if (elementType && _delegate) + { + if (elementType === "window") { + if ([_delegate respondsToSelector:@selector(addNewElementOfType:atPoint:)]) + { + [_delegate addNewElementOfType:elementType atPoint:dropPoint]; + [self setNeedsDisplay:YES]; + return YES; + } + } else { + if ([_delegate respondsToSelector:@selector(addNewElementOfType:inNewWindowAtPoint:)]) + { + [_delegate addNewElementOfType:elementType inNewWindowAtPoint:dropPoint]; + [self setNeedsDisplay:YES]; + return YES; + } + } + } + + return NO; +} + +#pragma mark - Delegate & Selection Management + +- (id)delegate { return _delegate; } +- (void)setDelegate:(id)newDelegate { _delegate = newDelegate; } + +- (void)deselectViews +{ + [_selectionIndexesContainer setValue:nil forKeyPath:_selectionIndexesKeyPath]; +} + +- (void)selectView:(UIElementView)aView state:(BOOL)select +{ + var selection = [[self selectionIndexes] mutableCopy] || [CPMutableIndexSet indexSet]; + var dataObjectIndex = [[self dataObjects] indexOfObject:[aView dataObject]]; + + + + if (dataObjectIndex != CPNotFound) + { + if (select) + [selection addIndex:dataObjectIndex]; + + else [selection removeIndex:dataObjectIndex]; + } + + [_selectionIndexesContainer setValue:selection forKeyPath:_selectionIndexesKeyPath]; +} + +- (CPArray)selectedSubViews +{ + var selectedDataObjects = [[self dataObjects] objectsAtIndexes:[self selectionIndexes]]; + var selectedViews = [CPMutableArray array]; + + [self _findViewsForDataObjects:selectedDataObjects inView:self foundViews:selectedViews]; + + return selectedViews; +} + +- (BOOL)isViewSelected:(CPView)aView +{ + var selected = [self selectedSubViews]; + + return [selected containsObject:aView]; +} + +- (void)_findViewsForDataObjects:(CPArray)dataObjects inView:(CPView)aView foundViews:(CPMutableArray)foundViews +{ + var subviews = [aView subviews]; + + for (var i = 0; i < [subviews count]; i++) + { + var subview = subviews[i]; + + // Skip the connection view and any other non-UIElementView instances + if (![subview isKindOfClass:[UIElementView class]]) + continue; + + var contains = [dataObjects containsObject:[subview dataObject]]; + + if (contains) + { + [foundViews addObject:subview]; + } + + // Recurse into subviews + [self _findViewsForDataObjects:dataObjects inView:subview foundViews:foundViews]; + } +} + +// These methods are called by the UIElementView children to notify the controller +- (void)elementDidMove:(UIElementView)anElement +{ + if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didMoveElement:)]) { + [_delegate canvasView:self didMoveElement:anElement]; + } +} + +- (void)elementDidResize:(UIElementView)anElement +{ + if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didResizeElement:)]) { + [_delegate canvasView:self didResizeElement:anElement]; + } +} + +- (void)elementDidConnect:(UIElementView)sourceElement to:(UIElementView)targetElement atPoint:(CGPoint)aPoint +{ + if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didConnectElement:toElement:atPoint:)]) { + [_delegate canvasView:self didConnectElement:sourceElement toElement:targetElement atPoint:aPoint]; + } +} + +#pragma mark - Connection Menu + +- (void)showConnectionMenuForSource:(UIElementView)sourceView target:(UIElementView)targetView at:(CGPoint)aPoint +{ + _connectionSource = sourceView; + _connectionTarget = targetView; + _connectionMade = NO; + + var menu = [[CPMenu alloc] initWithTitle:@"Connection Menu"]; + [menu setDelegate:self]; + + // 1. Add Target's Actions + var targetActions = [[_connectionTarget dataObject] valueForKey:@"actions"]; + if (targetActions && [targetActions length] > 0) + { + var actionsArray = [targetActions componentsSeparatedByString:@", "]; + for (var i = 0; i < [actionsArray count]; i++) + { + var actionName = actionsArray[i]; + var menuItem = [[CPMenuItem alloc] initWithTitle:actionName action:@selector(createTargetActionConnection:) keyEquivalent:@""]; + [menu addItem:menuItem]; + } + } + + // 2. Add Separator + if ([menu numberOfItems] > 0) + [menu addItem:[CPMenuItem separatorItem]]; + + // 3. Add Source's Outlets + var sourceOutlets = [[_connectionSource dataObject] valueForKey:@"outlets"]; + if (sourceOutlets && [sourceOutlets length] > 0) + { + var outletsArray = [sourceOutlets componentsSeparatedByString:@", "]; + for (var i = 0; i < [outletsArray count]; i++) + { + var outletName = outletsArray[i]; + if (outletName === @"target") continue; // Skip 'target' outlet as requested + var menuItem = [[CPMenuItem alloc] initWithTitle:outletName action:@selector(createOutletConnection:) keyEquivalent:@""]; + [menu addItem:menuItem]; + } + } + + if ([menu numberOfItems] > 0) + { + [CPMenu popUpContextMenu:menu withEvent:[CPApp currentEvent] forView:self]; + } + else + { + [self menuDidEndTracking:menu]; // No items, so clean up immediately + } +} + +- (void)createTargetActionConnection:(CPMenuItem)sender +{ + var actionName = [sender title]; + if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didConnectElement:toElement:asTargetAction:)]) + { + _connectionMade = YES; + [self clearConnection]; + if (_connectionTarget) + [_connectionTarget setAsDropTarget:NO]; + + [_delegate canvasView:self didConnectElement:_connectionSource toElement:_connectionTarget asTargetAction:actionName]; + } +} + +- (void)createOutletConnection:(CPMenuItem)sender +{ + var outletName = [sender title]; + if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didConnectElement:toElement:asOutlet:)]) + { + _connectionMade = YES; + [self clearConnection]; + if (_connectionTarget) + [_connectionTarget setAsDropTarget:NO]; + + [_delegate canvasView:self didConnectElement:_connectionSource toElement:_connectionTarget asOutlet:outletName]; + } +} + +- (void)menuDidEndTracking:(CPMenu)aMenu +{ + // This delegate method is called after a menu item is selected OR the menu is cancelled. + if (!_connectionMade) + { + [self clearConnection]; + if (_connectionTarget) + [_connectionTarget setAsDropTarget:NO]; + } + + // Reset state + _connectionSource = nil; + _connectionTarget = nil; + _connectionMade = NO; + + [self setNeedsDisplay:YES]; +} + +@end diff --git a/Tests/Manual/UIBuilderDemo/UIElementView.j b/Tests/Manual/UIBuilderDemo/UIElementView.j new file mode 100644 index 000000000..d220fc7fd --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/UIElementView.j @@ -0,0 +1,1349 @@ +// +// UIElementView.j by Daniel Böhringer in 2025 + +// This file is a drawing engine for a UI builder, with features such as: +// - Skeleton drawing for common UI elements (Window, Button, Slider, TextField). +// - Selection highlights. +// - Resize handles ("dimples") on selected views. +// - Mouse logic for moving and resizing elements. +// - Visual hints for drop targets (e.g., a Window accepting a Button). +// +// + +@import "UIBuilderConstants.j"; + +// --- Property Types --- +UIBString = "UIBString"; +UIBNumber = "UIBNumber"; +UIBBoolean = "UIBBoolean"; + +// --- Constants for Resizing --- +var kUIElementHandleSize = 8.0; +var kUIElementNoHandle = 0; +var kUIElementTopLeftHandle = 1; +var kUIElementTopMiddleHandle = 2; +var kUIElementTopRightHandle = 3; +var kUIElementMiddleLeftHandle = 4; +var kUIElementMiddleRightHandle = 5; +var kUIElementBottomLeftHandle = 6; +var kUIElementBottomMiddleHandle = 7; +var kUIElementBottomRightHandle = 8; + + +@class UIWindowView +@class UIButtonView +@class UISliderView +@class UITextFieldView; + +@implementation UIElementView : CPView +{ + CPMutableDictionary _stringAttributes; + id _dataObject @accessors(property=dataObject); + + // State for dragging and resizing + CGPoint _lastMouseLoc; + int _activeHandle; + BOOL _isDragTarget; // Used by subclasses (e.g. UIWindowView) + CPTrackingArea _trackingArea; + BOOL _isContainer; + BOOL _isConnecting; +} + +#pragma mark - +#pragma mark *** Class Methods *** + ++ (CPArray)persistentProperties +{ + return ["value"]; +} + ++ (CPDictionary)defaultValues +{ + return {value: "Element"}; +} + ++ (CPDictionary)propertyTypes +{ + return [CPDictionary dictionaryWithObjects:[UIBString] forKeys:["value"]]; +} + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self) + { + _stringAttributes = [[CPMutableDictionary alloc] init]; + [_stringAttributes setObject:[CPFont boldSystemFontOfSize:12] forKey:CPFontAttributeName]; + [_stringAttributes setObject:[CPColor blackColor] forKey:CPForegroundColorAttributeName]; + + + _activeHandle = kUIElementNoHandle; + + if ([self frame].size.width < 50 || [self frame].size.height < 20) + [self setFrameSize:CGSizeMake(MAX(50, [self frame].size.width), MAX(20, [self frame].size.height))]; + + [self setNeedsDisplay:YES]; + + _trackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() + options:CPTrackingMouseMoved | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect | CPTrackingMouseEnteredAndExited + owner:self + userInfo:nil]; + [self addTrackingArea:_trackingArea]; + _isContainer = NO; + _isConnecting = NO; + } + return self; +} + +- (void)dealloc +{ + [self setDataObject:nil]; + [super dealloc]; +} + +- (void)setDataObject:(id)newDataObject +{ + var oldDataObject = [self dataObject]; + if (newDataObject != oldDataObject) + { + var properties = [[self class] persistentProperties]; + if (oldDataObject) + for (var i = 0; i < [properties count]; i++) + [oldDataObject removeObserver:self forKeyPath:properties[i]]; + + _dataObject = newDataObject; + + if (newDataObject) + { + for (var i = 0; i < [properties count]; i++) + { + var propertyName = properties[i]; + [newDataObject addObserver:self forKeyPath:propertyName options:CPKeyValueObservingOptionNew context:self]; + } + } + } +} + +- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context +{ + if (context == self) + { + // When a property on the dataObject changes, simply tell the view to redraw itself. + [self setNeedsDisplay:YES]; + } + else + { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + +- (BOOL)acceptsFirstMouse +{ + // This view should accept first mouse events for interaction. + return YES; +} + +- (void)removeFromSuperview +{ + // This is the correct place to clean up view-related resources. + // When the view is removed from its superview, we no longer need to track + // mouse events within its bounds. + [self removeTrackingArea:_trackingArea]; + + // It's crucial to call the superclass's implementation at the end. + [super removeFromSuperview]; +} + +#pragma mark - +#pragma mark *** Geometry Accessors (for KVC Binding) *** + +- (float)originX +{ + return [self frame].origin.x; +} + +- (void)setOriginX:(float)aFloat +{ + // Only update if the value has actually changed. + if (aFloat !== [self originX]) + { + var frame = [self frame]; + frame.origin.x = aFloat; + [self setFrame:frame]; + + // Notify the superview (the canvas) that it might need to redraw + // if anything depends on this view's position. + [[self superview] setNeedsDisplay:YES]; + } +} + +- (float)originY +{ + return [self frame].origin.y; +} + +- (void)setOriginY:(float)aFloat +{ + if (aFloat !== [self originY]) + { + var frame = [self frame]; + frame.origin.y = aFloat; + [self setFrame:frame]; + [[self superview] setNeedsDisplay:YES]; + } +} + +- (float)width +{ + return [self frame].size.width; +} + +- (void)setWidth:(float)aFloat +{ + if (aFloat !== [self width]) + { + var frame = [self frame]; + // Enforce a minimum width to prevent rendering issues. + frame.size.width = MAX(aFloat, 20.0); + [self setFrame:frame]; + [[self superview] setNeedsDisplay:YES]; + } +} + +- (float)height +{ + return [self frame].size.height; +} + +- (void)setHeight:(float)aFloat +{ + if (aFloat !== [self height]) + { + var frame = [self frame]; + // Enforce a minimum height. + frame.size.height = MAX(aFloat, 20.0); + [self setFrame:frame]; + [[self superview] setNeedsDisplay:YES]; + } +} + +#pragma mark - +#pragma mark *** Accessors *** + +- (id)value +{ + return ([self dataObject] == nil) ? @"" : [[self dataObject] valueForKey:@"value"]; +} + +// You will need a way to get a reference to the canvas. +// This is often done by walking up the superview chain. +- (UICanvasView)canvas +{ + var aView = self; + while (aView = [aView superview]) { + if ([aView isKindOfClass:[UICanvasView class]]) + return aView; + } + return nil; +} + +#pragma mark - +#pragma mark *** Drawing *** + +- (void)drawRect:(CGRect)rect +{ + // 1. Draw the specific skeleton for the element subclass + [self drawSkeleton:rect]; + + // 2. If this view is a drop target, draw a highlight + if (_isDragTarget) + { + [[[CPColor redColor] colorWithAlphaComponent:0.8] setStroke]; + var highlightPath = [CPBezierPath bezierPathWithRect:CGRectInset([self bounds], 1, 1)]; + [highlightPath setLineWidth:2.0]; + [highlightPath stroke]; + } + + // 3. If selected, draw selection outline and resize handles + if ([self isSelected]) + { + // Draw selection highlight + [[CPColor keyboardFocusIndicatorColor] setStroke]; + var selectionPath = [CPBezierPath bezierPathWithRect:CGRectInset([self bounds], -2, -2)]; + [selectionPath setLineWidth:1.0]; + [selectionPath stroke]; + + // Draw resize handles ("dimples") + [self drawHandles]; + } +} + +- (void)drawSkeleton:(CGRect)rect +{ + // Base implementation: a simple placeholder box. + // Subclasses should override this to draw their specific look. + var bounds = [self bounds]; + [[CPColor lightGrayColor] setFill]; + [CPBezierPath fillRect:bounds]; + [[CPColor darkGrayColor] setStroke]; + [CPBezierPath strokeRect:bounds]; + + var valueSize = [[self value] sizeWithAttributes:_stringAttributes]; + [[self value] drawAtPoint:CGPointMake((bounds.size.width - valueSize.width) / 2.0, (bounds.size.height - valueSize.height) / 2.0) withAttributes:_stringAttributes]; +} + +- (CGRect)rectForHandle:(int)handle +{ + var bounds = [self bounds]; + var x, y; + + // Top Row + if (handle >= kUIElementTopLeftHandle && handle <= kUIElementTopRightHandle) + y = bounds.origin.y - kUIElementHandleSize / 2.0; + // Middle Row + if (handle === kUIElementMiddleLeftHandle || handle === kUIElementMiddleRightHandle) + y = bounds.origin.y + bounds.size.height / 2.0 - kUIElementHandleSize / 2.0; + // Bottom Row + if (handle >= kUIElementBottomLeftHandle && handle <= kUIElementBottomRightHandle) + y = bounds.origin.y + bounds.size.height - kUIElementHandleSize / 2.0; + + // Left Column + if (handle === kUIElementTopLeftHandle || handle === kUIElementMiddleLeftHandle || handle === kUIElementBottomLeftHandle) + x = bounds.origin.x - kUIElementHandleSize / 2.0; + // Center Column + if (handle === kUIElementTopMiddleHandle || handle === kUIElementBottomMiddleHandle) + x = bounds.origin.x + bounds.size.width / 2.0 - kUIElementHandleSize / 2.0; + // Right Column + if (handle === kUIElementTopRightHandle || handle === kUIElementMiddleRightHandle || handle === kUIElementBottomRightHandle) + x = bounds.origin.x + bounds.size.width - kUIElementHandleSize / 2.0; + + return CGRectMake(x, y, kUIElementHandleSize, kUIElementHandleSize); +} + +- (void)drawHandles +{ + [[CPColor controlDarkShadowColor] setFill]; + for (var i = 1; i <= 8; i++) + { + [CPBezierPath fillRect:[self rectForHandle:i]]; + } +} + +- (BOOL)isSelected +{ + return [[self canvas] isViewSelected:self]; +} + +#pragma mark - +#pragma mark *** Mouse Handling & Resizing *** + +- (int)handleAtPoint:(CGPoint)aPoint +{ + if (![self isSelected]) return kUIElementNoHandle; + + for (var i = 1; i <= 8; i++) + { + if (CGRectContainsPoint([self rectForHandle:i], aPoint)) + return i; + } + return kUIElementNoHandle; +} + +- (void)rightMouseDown:(CPEvent)theEvent +{ + [self mouseDown:theEvent]; +} +- (void)rightMouseUp:(CPEvent)theEvent +{ + [self mouseUp:theEvent]; +} + +- (void)mouseDown:(CPEvent)theEvent +{ + var canvas = [self canvas]; + var localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + + _lastMouseLoc = [[self canvas] convertPoint:[theEvent locationInWindow] fromView:nil]; + + // First, check if we clicked a resize handle + _activeHandle = [self handleAtPoint:localPoint]; + + // No handle was clicked, proceed with selection and movement logic + if ([theEvent modifierFlags] & CPShiftKeyMask) + { + [canvas selectView:self state:YES]; + } + else if ([theEvent modifierFlags] & CPCommandKeyMask) + { + [canvas selectView:self state:![self isSelected]]; + } + else if (![self isSelected]) + { + [canvas deselectViews]; + [canvas selectView:self state:YES]; + } +} + + + +- (void)mouseDragged:(CPEvent)theEvent +{ + var canvas = [self canvas]; + var mouseLoc = [canvas convertPoint:[theEvent locationInWindow] fromView:nil]; + + // If _lastMouseLoc is null, it means the drag started outside this view, + // so we initialize it with the current mouse location to prevent errors. + if (!_lastMouseLoc) { + _lastMouseLoc = mouseLoc; + } + + if ([theEvent modifierFlags] & CPControlKeyMask) + { + _isConnecting = YES; + // If control key is pressed, handle connection drawing + var startPointInView = CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds])); + var startPointInCanvas = [self convertPoint:startPointInView toView:canvas]; + + var canvasSubviews = [canvas subviews]; + for (var k = 0; k < [canvasSubviews count]; k++) { + var subview = [canvasSubviews objectAtIndex:k]; + if ([subview isKindOfClass:[UIElementView class]]) { + [subview setAsDropTarget:NO]; + } + } + var targetView = [canvas viewAtPoint:mouseLoc]; + + if (targetView && targetView != self) + { + var localPoint = [targetView convertPoint:mouseLoc fromView:canvas]; + if ([targetView canAcceptConnectionAtPoint:localPoint]) + { + var endPointInView = CGPointMake(CGRectGetMidX([targetView bounds]), CGRectGetMidY([targetView bounds])); + var endPointInCanvas = [targetView convertPoint:endPointInView toView:canvas]; + [canvas drawConnectionFrom:startPointInCanvas to:endPointInCanvas]; + [targetView setAsDropTarget:YES]; + } + else + { + [canvas drawConnectionFrom:startPointInCanvas to:mouseLoc]; + } + } + else + { + [canvas drawConnectionFrom:startPointInCanvas to:mouseLoc]; + } + } + else if (_activeHandle != kUIElementNoHandle) + { + // Resize logic + var sView = [self superview]; + var deltaX = mouseLoc.x - _lastMouseLoc.x; + var deltaY = mouseLoc.y - _lastMouseLoc.y; + + var frame = [self frame]; + var minSize = CGSizeMake(2 * kUIElementHandleSize, 2 * kUIElementHandleSize); + + // Left handles + if (_activeHandle === kUIElementTopLeftHandle || _activeHandle === kUIElementMiddleLeftHandle || _activeHandle === kUIElementBottomLeftHandle) { + if (frame.size.width - deltaX > minSize.width) { + frame.origin.x += deltaX; + frame.size.width -= deltaX; + } + } + // Right handles + if (_activeHandle === kUIElementTopRightHandle || _activeHandle === kUIElementMiddleRightHandle || _activeHandle === kUIElementBottomRightHandle) { + if (frame.size.width + deltaX > minSize.width) { + frame.size.width += deltaX; + } + } + // Top handles + if (_activeHandle === kUIElementTopLeftHandle || _activeHandle === kUIElementTopMiddleHandle || _activeHandle === kUIElementTopRightHandle) { + if (frame.size.height - deltaY > minSize.height) { + frame.origin.y += deltaY; + frame.size.height -= deltaY; + } + } + // Bottom handles + if (_activeHandle === kUIElementBottomLeftHandle || _activeHandle === kUIElementBottomMiddleHandle || _activeHandle === kUIElementBottomRightHandle) { + if (frame.size.height + deltaY > minSize.height) { + frame.size.height += deltaY; + } + } + + [self setFrame:frame]; + + _lastMouseLoc = mouseLoc; + [canvas setNeedsDisplay:YES]; + } + else + { + // This is the move logic, largely from the original EFView. + [[CPCursor closedHandCursor] set]; + var deltaX = mouseLoc.x - _lastMouseLoc.x; + var deltaY = mouseLoc.y - _lastMouseLoc.y; + + for (var i = 0; i < [[canvas selectedSubViews] count]; i++) + { + var view = [canvas selectedSubViews][i]; + var newOrigin = CGPointMake([view frame].origin.x + deltaX, [view frame].origin.y + deltaY); + + var parentView = [view superview]; + if ([parentView isKindOfClass:[UIWindowView class]]) + { + var parentBounds = [parentView bounds]; + var viewFrame = [view frame]; + newOrigin.x = MAX(0, MIN(newOrigin.x, parentBounds.size.width - viewFrame.size.width)); + newOrigin.y = MAX(0, MIN(newOrigin.y, parentBounds.size.height - viewFrame.size.height)); + } + + [view setFrameOrigin:newOrigin]; + } + + _lastMouseLoc = mouseLoc; + [canvas setNeedsDisplay:YES]; + } +} + +- (void)mouseUp:(CPEvent)theEvent +{ + var canvas = [self canvas]; + var mouseLoc = [canvas convertPoint:[theEvent locationInWindow] fromView:nil]; + + if (_isConnecting) + { + // Handle mouse up for connection + var targetView = [canvas viewAtPoint:mouseLoc]; + + if (targetView && targetView != self) + { + var localPoint = [targetView convertPoint:mouseLoc fromView:canvas]; + if ([targetView canAcceptConnectionAtPoint:localPoint]) + { + [canvas showConnectionMenuForSource:self target:targetView at:mouseLoc]; + } + else + { + [canvas clearConnection]; + } + } + else + { + [canvas clearConnection]; + } + + var canvasSubviews = [canvas subviews]; + + for (var k = 0; k < [canvasSubviews count]; k++) { + var subview = [canvasSubviews objectAtIndex:k]; + if ([subview isKindOfClass:[UIElementView class]] && subview != targetView) { + [subview setAsDropTarget:NO]; + } + } + [canvas setNeedsDisplay:YES]; + _isConnecting = NO; + } + else if (_activeHandle != kUIElementNoHandle) + { + // Handle mouse up for resize + [[CPCursor arrowCursor] set]; + _activeHandle = kUIElementNoHandle; + _lastMouseLoc = null; + [canvas setNeedsDisplay:YES]; + [canvas elementDidResize:self]; + } + else + { + // Handle mouse up for move + [[CPCursor openHandCursor] set]; + _lastMouseLoc = null; + [canvas setNeedsDisplay:YES]; + [canvas elementDidMove:self]; + } +} + +- (void)_resizeWithEvent:(CPEvent)theEvent +{ + var sView = [self superview]; + var canvas = [self canvas]; + var mouseLoc; + + switch ([theEvent type]) + { + case CPLeftMouseDragged: + [[CPCursor crosshairCursor] set]; // A generic resize cursor + mouseLoc = [sView convertPoint:[theEvent locationInWindow] fromView:nil]; + var deltaX = mouseLoc.x - _lastMouseLoc.x; + var deltaY = mouseLoc.y - _lastMouseLoc.y; + + var frame = [self frame]; + var minSize = CGSizeMake(2 * kUIElementHandleSize, 2 * kUIElementHandleSize); + + // Left handles + if (_activeHandle === kUIElementTopLeftHandle || _activeHandle === kUIElementMiddleLeftHandle || _activeHandle === kUIElementBottomLeftHandle) { + if (frame.size.width - deltaX > minSize.width) { + frame.origin.x += deltaX; + frame.size.width -= deltaX; + } + } + // Right handles + if (_activeHandle === kUIElementTopRightHandle || _activeHandle === kUIElementMiddleRightHandle || _activeHandle === kUIElementBottomRightHandle) { + if (frame.size.width + deltaX > minSize.width) { + frame.size.width += deltaX; + } + } + // Top handles + if (_activeHandle === kUIElementTopLeftHandle || _activeHandle === kUIElementTopMiddleHandle || _activeHandle === kUIElementTopRightHandle) { + if (frame.size.height - deltaY > minSize.height) { + frame.origin.y += deltaY; + frame.size.height -= deltaY; + } + } + // Bottom handles + if (_activeHandle === kUIElementBottomLeftHandle || _activeHandle === kUIElementBottomMiddleHandle || _activeHandle === kUIElementBottomRightHandle) { + if (frame.size.height + deltaY > minSize.height) { + frame.size.height += deltaY; + } + } + + [self setFrame:frame]; + + _lastMouseLoc = mouseLoc; + [canvas setNeedsDisplay:YES]; + [CPApp setTarget:self selector:@selector(_resizeWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; + break; + case CPLeftMouseUp: + [[CPCursor arrowCursor] set]; + _activeHandle = kUIElementNoHandle; + _lastMouseLoc = null; + [canvas setNeedsDisplay:YES]; + [canvas elementDidResize:self]; + break; + } +} + +- (void)setAsDropTarget:(BOOL)isTarget +{ + if (_isDragTarget !== isTarget) + { + _isDragTarget = isTarget; + [self setNeedsDisplay:YES]; + } +} + +- (void)_connectWithEvent:(CPEvent)theEvent +{ + var canvas = [self canvas]; + var mouseLoc = [canvas convertPoint:[theEvent locationInWindow] fromView:nil]; + + // Convert the start point (center of the view) to the canvas's coordinate system + var startPointInView = CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds])); + var startPointInCanvas = [self convertPoint:startPointInView toView:canvas]; + + var canvasSubviews = [canvas subviews]; + for (var k = 0; k < [canvasSubviews count]; k++) { + var subview = [canvasSubviews objectAtIndex:k]; + if ([subview isKindOfClass:[UIElementView class]]) { + [subview setAsDropTarget:NO]; + } + } + var targetView = [canvas viewAtPoint:mouseLoc]; + var validTargetFound = NO; + var endPointForDrawing = mouseLoc; // Default to follow mouse + + if (targetView && targetView != self) + { + if ([targetView isKindOfClass:[UIWindowView class]]) + { + validTargetFound = YES; + // Snap to center of the window for drawing feedback + endPointForDrawing = CGPointMake(CGRectGetMidX([targetView bounds]), CGRectGetMidY([targetView bounds])); + endPointForDrawing = [targetView convertPoint:endPointForDrawing toView:canvas]; + } + else + { + // For non-window elements, allow connection anywhere on their bounds + validTargetFound = YES; + endPointForDrawing = mouseLoc; // Follow mouse for other elements during drag + } + } + + if ([theEvent type] == CPLeftMouseDragged) + { + if (validTargetFound) + { + [canvas drawConnectionFrom:startPointInCanvas to:endPointForDrawing]; + [targetView setAsDropTarget:YES]; + } + else + { + [canvas drawConnectionFrom:startPointInCanvas to:mouseLoc]; + } + } + else if ([theEvent type] == CPLeftMouseUp) + { + // For final connection, snap to center of non-window elements, or title bar for windows + var finalEndPoint = mouseLoc; + var currentValidTarget = validTargetFound; // Store the initial state + + if (targetView && targetView != self) { + finalEndPoint = CGPointMake(CGRectGetMidX([targetView bounds]), CGRectGetMidY([targetView bounds])); + finalEndPoint = [targetView convertPoint:finalEndPoint toView:canvas]; + } else { + currentValidTarget = NO; // No valid target or target is self + } + + if (currentValidTarget) { + [[self canvas] elementDidConnect:self to:targetView atPoint:finalEndPoint]; // Pass finalEndPoint + } + [[self canvas] clearConnection]; + var canvasSubviews = [canvas subviews]; + for (var k = 0; k < [canvasSubviews count]; k++) { + var subview = [canvasSubviews objectAtIndex:k]; + if ([subview isKindOfClass:[UIElementView class]]) { + [subview setAsDropTarget:NO]; + } + } + } +} + +- (void)mouseEntered:(CPEvent)theEvent +{ + [[CPCursor openHandCursor] set]; +} + +- (void)mouseExited:(CPEvent)theEvent +{ + [[CPCursor arrowCursor] set]; +} + +- (void)mouseMoved:(CPEvent)theEvent +{ + var localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + var handle = [self handleAtPoint:localPoint]; + + if (handle != kUIElementNoHandle) { + // In a full implementation, you could return a specific two-headed arrow cursor + // based on the handle. For now, we use a generic one. + [[CPCursor crosshairCursor] set]; + } else { + [[CPCursor openHandCursor] set]; + } +} + +- (id)nativeUIElement +{ + return [self nativeUIElementWithMap:nil]; +} + +- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap +{ + // Base implementation returns a generic view with a red background to indicate it's not a real UI element. + var view = [[CPView alloc] initWithFrame:[self frame]]; + [view setBackgroundColor:[CPColor redColor]]; + + if (aMap) + { + var elementID = [[self dataObject] valueForKey:@"id"]; + [aMap setObject:view forKey:elementID]; + } + + return view; +} + +- (BOOL)canAcceptConnectionAtPoint:(CGPoint)aPoint +{ + // By default, any part of the view can be a connection target. + return YES; +} + +@end + + +#pragma mark - +#pragma mark *** UI Element Subclasses *** + +// ================================================================================================= +// UIWindowView +// A skeleton that looks like a window, and can act as a drop target. +// ================================================================================================= + +var _windowChildrenObservationContext = 1094; + +@implementation UIWindowView : UIElementView +{ + CGPoint _rubberStart; + CGPoint _rubberEnd; + BOOL _isRubbing; +} + ++ (CPDictionary)propertyTypes +{ + var types = [super propertyTypes]; + [types setObject:UIBBoolean forKey:@"CPHUDBackgroundWindowMask"]; + [types setObject:UIBBoolean forKey:@"CPTitledWindowMask"]; + [types setObject:UIBBoolean forKey:@"CPClosableWindowMask"]; + return types; +} + ++ (CPArray)persistentProperties +{ + return [super persistentProperties].concat(["CPHUDBackgroundWindowMask", "CPTitledWindowMask", "CPClosableWindowMask"]); +} + ++ (CPDictionary)defaultValues +{ + return { + value: "Untitled Window", + CPHUDBackgroundWindowMask: true, + CPTitledWindowMask: true, + CPClosableWindowMask: true, + outlets: "delegate", + actions: "makeKeyAndOrderFront:, orderOut:" + }; +} + +- (void)drawRect:(CGRect)rect +{ + [super drawRect:rect]; + + if (_isRubbing) + { + var rubber = CGRectUnion(CGRectMake(_rubberStart.x, _rubberStart.y, 0.1, 0.1), CGRectMake(_rubberEnd.x, _rubberEnd.y, 0.1, 0.1)); + [[[[CPColor alternateSelectedControlColor] colorWithAlphaComponent:0.2] setFill]]; + [CPBezierPath fillRect:rubber]; + [[CPColor alternateSelectedControlColor] setStroke]; + [CPBezierPath setDefaultLineWidth:1.0]; + [CPBezierPath strokeRect:rubber]; + } +} + +- (void)mouseDown:(CPEvent)theEvent +{ + var localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + var titleBarHeight = 30.0; + + // 1. Check for resize handle click first. + if ([self handleAtPoint:localPoint] != kUIElementNoHandle) { + [super mouseDown:theEvent]; + return; + } + + // 2. Check if the click is within the title bar area. + if (localPoint.y <= titleBarHeight) { + // Click is in the title bar. Allow the superclass to handle moving the window. + [super mouseDown:theEvent]; + return; + } + + // On a click into the window's content area, deselect all elements. + [[self canvas] deselectViews]; + + _rubberStart = localPoint; + _rubberEnd = _rubberStart; + _isRubbing = YES; + [CPApp setTarget:self selector:@selector(_dragOpenSpaceWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; +} + +- (void)_dragOpenSpaceWithEvent:(CPEvent)theEvent +{ + var canvas = [self canvas]; + var mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + _rubberEnd = mouseLoc; + var rubberRect = CGRectUnion(CGRectMake(_rubberStart.x, _rubberStart.y, 1, 1), CGRectMake(_rubberEnd.x, _rubberEnd.y, 1, 1)); + + switch ([theEvent type]) + { + case CPLeftMouseDragged: + var indexesToSelect = [CPMutableIndexSet indexSet]; + var allDataObjects = [canvas dataObjects]; + + for (var i = 0; i < [[self subviews] count]; i++) { + var aView = [self subviews][i]; + if (CGRectIntersectsRect([aView frame], rubberRect)) { + var dataIndex = [allDataObjects indexOfObject:[aView dataObject]]; + if (dataIndex != CPNotFound) { + [indexesToSelect addIndex:dataIndex]; + } + } + } + [canvas setSelectionIndexes:indexesToSelect]; + [self setNeedsDisplay:YES]; + [CPApp setTarget:self selector:@selector(_dragOpenSpaceWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; + break; + + case CPLeftMouseUp: + _isRubbing = NO; + [self setNeedsDisplay:YES]; + break; + } +} + +- (void)dealloc +{ + [self setDataObject:nil]; + [super dealloc]; +} + +- (void)setDataObject:(id)newDataObject +{ + var oldDataObject = [self dataObject]; + + if (newDataObject != oldDataObject) + { + if (oldDataObject) + [oldDataObject removeObserver:self forKeyPath:@"children" context:_windowChildrenObservationContext]; + + [super setDataObject:newDataObject]; + + if (newDataObject) + { + [newDataObject addObserver:self forKeyPath:@"children" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:_windowChildrenObservationContext]; + [self _addChildrenViews:[newDataObject valueForKey:@"children"]]; + } + } +} + +- (void)_addChildrenViews:(CPArray)childDataObjects +{ + if (!childDataObjects) return; + + var canvas = [self superview]; + + for (var i = 0; i < [childDataObjects count]; i++) + { + var childData = childDataObjects[i]; + // This is a bit of a hack. We are reaching into the canvas's private method. + // A better solution would be a dedicated ViewFactory or similar. + if ([canvas respondsToSelector:@selector(_createViewForDataObject:superview:)]) + [canvas _createViewForDataObject:childData superview:self]; + } +} + +- (void)_removeChildrenViews:(CPArray)childDataObjects +{ + if (!childDataObjects) return; + + var canvas = [self superview]; + var viewsToRemove = []; + var subviews = [self subviews]; + + for (var i = 0; i < [subviews count]; i++) + { + var subview = subviews[i]; + if ([childDataObjects containsObject:[subview dataObject]]) + [viewsToRemove addObject:subview]; + } + + for (i = 0; i < [viewsToRemove count]; i++) + { + if ([canvas respondsToSelector:@selector(_removeViewAndChildren:)]) + [canvas _removeViewAndChildren:viewsToRemove[i]]; + } +} + + +- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context +{ + if (context == _windowChildrenObservationContext) + { + var oldChildren = [change objectForKey:CPKeyValueChangeOldKey]; + var newChildren = [change objectForKey:CPKeyValueChangeNewKey]; + + var added = [newChildren mutableCopy]; + [added removeObjectsInArray:oldChildren]; + [self _addChildrenViews:added]; + + var removed = [oldChildren mutableCopy]; + [removed removeObjectsInArray:newChildren]; + [self _removeChildrenViews:removed]; + } + else + { + [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; + } +} + + +- (id)initWithFrame:(CGRect)aRect +{ + self = [super initWithFrame:aRect]; + if (self) { + + if (CGRectIsEmpty(aRect)) { + [self setFrameSize:CGSizeMake(250, 200)]; + } + _isContainer = YES; + + // This view can accept drops of other elements. + [self registerForDraggedTypes:[ + UIButtonDragType, + UISliderDragType, + UITextFieldDragType + ]]; + } + return self; +} + +- (void)drawSkeleton:(CGRect)rect +{ + var bounds = [self bounds]; + var titleBarHeight = 22.0; + + // Main window background + [[[CPColor windowBackgroundColor] colorWithAlphaComponent:0.9] setFill]; + var bgPath = [CPBezierPath bezierPathWithRoundedRect:bounds radius:6.0]; + [bgPath fill]; + + // Title bar + var titleBarRect = CGRectMake(bounds.origin.x, bounds.origin.y, bounds.size.width, titleBarHeight); + var titleBarPath = [CPBezierPath bezierPathWithRoundedRect:titleBarRect xRadius:6.0 yRadius:6.0]; + [[[CPColor secondarySelectedControlColor] colorWithAlphaComponent:0.6] setFill]; + [titleBarPath fill]; + + // Window border + [[CPColor darkGrayColor] setStroke]; + [bgPath setLineWidth:1.0]; + [bgPath stroke]; + + // Value text + [_stringAttributes setObject:[CPColor whiteColor] forKey:CPForegroundColorAttributeName]; + var valueSize = [[self value] sizeWithAttributes:_stringAttributes]; + [[self value] drawAtPoint:CGPointMake((bounds.size.width - valueSize.width) / 2.0, (titleBarHeight - valueSize.height) / 2.0 - 4) withAttributes:_stringAttributes]; + [_stringAttributes setObject:[CPColor blackColor] forKey:CPForegroundColorAttributeName]; // reset color + + // Traffic light buttons + var circleRadius = 5.0; + var startX = 10.0; + var startY = titleBarHeight / 2.0; + [[CPColor redColor] setFill]; + [CPBezierPath fillRect:CGRectMake(startX, startY - circleRadius, circleRadius*2, circleRadius*2)]; + [[CPColor orangeColor] setFill]; + [CPBezierPath fillRect:CGRectMake(startX + 18, startY - circleRadius, circleRadius*2, circleRadius*2)]; + [[CPColor greenColor] setFill]; + [CPBezierPath fillRect:CGRectMake(startX + 36, startY - circleRadius, circleRadius*2, circleRadius*2)]; +} + +// --- Drag Destination Methods --- + +- (CPDragOperation)draggingEntered:(CPDraggingInfo)sender +{ + var pasteboard = [sender draggingPasteboard]; + var acceptedTypes = [self registeredDraggedTypes]; + var localPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; + var titleBarHeight = 30.0; + + // Check if the dragged type is a new UI element (from the palette) + if ([acceptedTypes containsObject:UIWindowDragType] || [acceptedTypes containsObject:UIButtonDragType] || [acceptedTypes containsObject:UISliderDragType] || [acceptedTypes containsObject:UITextFieldDragType]) + { + _isDragTarget = YES; + [self setNeedsDisplay:YES]; + return CPDragOperationGeneric; + } + // Check if it's a connection drag (control key is pressed) + else if ([sender draggingSourceOperationMask] & CPControlKeyMask && localPoint.y <= titleBarHeight) + + { + debugger + _isDragTarget = YES; + [self setNeedsDisplay:YES]; + return CPDragOperationGeneric; + } + + return CPDragOperationNone; +} + +- (CPDragOperation)draggingUpdated:(CPDraggingInfo)sender +{ + var localPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; + var titleBarHeight = 30.0; + var acceptedTypes = [self registeredDraggedTypes]; + + // Check if the dragged type is a new UI element (from the palette) + if ([acceptedTypes containsObject:UIButtonDragType] || [acceptedTypes containsObject:UISliderDragType] || [acceptedTypes containsObject:UITextFieldDragType]) + { + _isDragTarget = YES; + [self setNeedsDisplay:YES]; + return CPDragOperationGeneric; + } + // Check if it's a connection drag (control key is pressed) + else if ([sender draggingSourceOperationMask] & CPControlKeyMask && localPoint.y <= titleBarHeight) + { + _isDragTarget = YES; + [self setNeedsDisplay:YES]; + return CPDragOperationGeneric; + } + else + { + _isDragTarget = NO; + [self setNeedsDisplay:YES]; + return CPDragOperationNone; + } +} + +- (void)draggingExited:(CPDraggingInfo)sender +{ + _isDragTarget = NO; + [self setNeedsDisplay:YES]; +} + +- (BOOL)performDragOperation:(CPDraggingInfo)sender +{ + var dropPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; + var pasteboard = [sender draggingPasteboard]; + var types = [pasteboard types]; + var draggedType = types[0]; + var elementType; + + // Determine if it's a new UI element drop + if (draggedType === UIButtonDragType) elementType = "button"; + else if (draggedType === UISliderDragType) elementType = "slider"; + else if (draggedType === UITextFieldDragType) elementType = "textfield"; + + if (elementType) + { + // We need to find the canvas and then the delegate + var canvas = [self superview]; + var delegate = [canvas delegate]; + if (delegate && [delegate respondsToSelector:@selector(addNewElementOfType:atPoint:)]) + { + var canvasPoint = [self convertPoint:dropPoint toView:canvas]; + [delegate addNewElementOfType:elementType atPoint:canvasPoint]; + } + } + // If it's a connection drag, the logic is handled in _connectWithEvent: in UIElementView + + _isDragTarget = NO; + [self setNeedsDisplay:YES]; + + return YES; +} + +- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap +{ + var newPlatformWindow = [[CPPlatformWindow alloc] initWithContentRect:[self frame]]; + + var styleMask = 0; + if ([[self dataObject] valueForKey:@"CPHUDBackgroundWindowMask"]) styleMask |= CPHUDBackgroundWindowMask; + if ([[self dataObject] valueForKey:@"CPTitledWindowMask"]) styleMask |= CPTitledWindowMask; + if ([[self dataObject] valueForKey:@"CPClosableWindowMask"]) styleMask |= CPClosableWindowMask; + + var theNewWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, [self frame].size.width, [self frame].size.height) styleMask:styleMask]; + [theNewWindow setPlatformWindow:newPlatformWindow]; + + if (aMap) + { + var elementID = [[self dataObject] valueForKey:@"id"]; + [aMap setObject:theNewWindow forKey:elementID]; + } + + var contentView = [theNewWindow contentView]; + var subviews = [self subviews]; + for (var i = 0; i < [subviews count]; i++) + { + var subview = subviews[i]; + var nativeSubview = [subview nativeUIElementWithMap:aMap]; + [contentView addSubview:nativeSubview]; + } + + return theNewWindow; +} + +- (BOOL)canAcceptConnectionAtPoint:(CGPoint)aPoint +{ + var titleBarHeight = 22.0; + return aPoint.y <= titleBarHeight; +} + +@end + + +// ================================================================================================= +// UIButtonView +// A skeleton that looks like a push button. +// ================================================================================================= +@implementation UIButtonView : UIElementView + ++ (CPDictionary)defaultValues +{ + return {value: "Button", outlets: "target, delegate", actions: "takeValueFrom:"}; +} + ++ (CPDictionary)propertyTypes +{ + return [super propertyTypes].copy({value: UIBString}); +} +- (id)initWithFrame:(CGRect)aRect +{ + self = [super initWithFrame:aRect]; + if (self) { + if (CGRectIsEmpty(aRect)) { + [self setFrameSize:CGSizeMake(100, 24)]; + } + } + return self; +} + +- (void)drawSkeleton:(CGRect)rect +{ + var bounds = CGRectInset([self bounds], 1, 1); + + // Draw button shape with gradient + var buttonPath = [CPBezierPath bezierPathWithRoundedRect:bounds radius:5.0]; + var gradient = [[CPGradient alloc] initWithStartingColor:[CPColor whiteColor] + endingColor:[CPColor controlColor]]; + [gradient drawInBezierPath:buttonPath angle:90]; + + // Draw button border + [[CPColor grayColor] setStroke]; + [buttonPath setLineWidth:1.0]; + [buttonPath stroke]; + + // Draw value + var valueSize = [[self value] sizeWithAttributes:_stringAttributes]; + [[self value] drawAtPoint:CGPointMake((bounds.size.width - valueSize.width) / 2.0 + 1, (bounds.size.height - valueSize.height) / 2.0 - 2) withAttributes:_stringAttributes]; +} + +- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap +{ + var button = [[CPButton alloc] initWithFrame:[self frame]]; + [button setTitle:[self value]]; + + if (aMap) + { + var elementID = [[self dataObject] valueForKey:@"id"]; + [aMap setObject:button forKey:elementID]; + } + + return button; +} + +@end + +// ================================================================================================= +// UISliderView +// A skeleton that looks like a slider. +// ================================================================================================= +@implementation UISliderView : UIElementView + ++ (CPDictionary)defaultValues +{ + return {value: 0.5, outlets: "target, delegate", actions: "takeFloatValueFrom:, takeIntegerValueFrom:"}; +} + ++ (CPDictionary)propertyTypes +{ + return [super propertyTypes].copy({value: UIBNumber}); +} +- (id)initWithFrame:(CGRect)aRect +{ + self = [super initWithFrame:aRect]; + if (self) { + if (CGRectIsEmpty(aRect)) { + [self setFrameSize:CGSizeMake(150, 20)]; + } + } + return self; +} + +- (void)drawSkeleton:(CGRect)rect +{ + var bounds = CGRectInset([self bounds], 8, 0); + var midY = bounds.size.height / 2.0; + + // Draw track + [[CPColor grayColor] setStroke]; + var trackPath = [CPBezierPath bezierPath]; + [trackPath setLineWidth:3.0]; + [trackPath moveToPoint:CGPointMake(bounds.origin.x, midY)]; + [trackPath lineToPoint:CGPointMake(bounds.origin.x + bounds.size.width, midY)]; + [trackPath stroke]; + + // Draw knob + var knobX = bounds.origin.x + bounds.size.width * [self value]; + var knobRect = CGRectMake(knobX - 8, midY - 8, 16, 16); + var knobPath = [CPBezierPath bezierPathWithOvalInRect:knobRect]; + [[CPColor whiteColor] setFill]; + [knobPath fill]; + [[CPColor darkGrayColor] setStroke]; + [knobPath setLineWidth:1.0]; + [knobPath stroke]; +} + +- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap +{ + var slider = [[CPSlider alloc] initWithFrame:[self frame]]; + [slider setFloatValue:[self value]]; + + if (aMap) + { + var elementID = [[self dataObject] valueForKey:@"id"]; + [aMap setObject:slider forKey:elementID]; + } + + return slider; +} + +@end + +// ================================================================================================= +// UITextFieldView +// A skeleton that looks like a text field. +// ================================================================================================= +@implementation UITextFieldView : UIElementView + ++ (CPDictionary)defaultValues +{ + return {value: "Text Field", outlets: "target, delegate", actions: "takeStringValueFrom:, takeIntegerValueFrom:"}; +} + ++ (CPDictionary)propertyTypes +{ + return [super propertyTypes].copy({value: UIBString}); +} +- (id)initWithFrame:(CGRect)aRect +{ + self = [super initWithFrame:aRect]; + if (self) + { + if (CGRectIsEmpty(aRect)) + { + [self setFrameSize:CGSizeMake(150, 22)]; + } + [_stringAttributes setObject:[CPFont systemFontOfSize:12] forKey:CPFontAttributeName]; + [_stringAttributes setObject:[CPColor grayColor] forKey:CPForegroundColorAttributeName]; + } + return self; +} + +- (void)drawSkeleton:(CGRect)rect +{ + var bounds = CGRectInset([self bounds], 1, 1); + + // Background + [[CPColor textBackgroundColor] setFill]; + [CPBezierPath fillRect:bounds]; + + // Inset border + [[CPColor grayColor] setStroke]; + [CPBezierPath strokeRect:bounds]; + + // Draw placeholder value + var valueSize = [[self value] sizeWithAttributes:_stringAttributes]; + [[self value] drawAtPoint:CGPointMake(5, (bounds.size.height - valueSize.height) / 2.0 - 2) withAttributes:_stringAttributes]; +} + +- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap +{ + var textField = [[CPTextField alloc] initWithFrame:[self frame]]; + [textField setStringValue:[self value]]; + + if (aMap) + { + var elementID = [[self dataObject] valueForKey:@"id"]; + [aMap setObject:textField forKey:elementID]; + } + + return textField; +} + +@end diff --git a/Tests/Manual/UIBuilderDemo/index.html b/Tests/Manual/UIBuilderDemo/index.html new file mode 100644 index 000000000..cea729b96 --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/index.html @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + UIBuilderDemo + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/UIBuilderDemo/main.j b/Tests/Manual/UIBuilderDemo/main.j new file mode 100755 index 000000000..993c34a35 --- /dev/null +++ b/Tests/Manual/UIBuilderDemo/main.j @@ -0,0 +1,10 @@ +@import +@import + +@import "UIElementView.j" +@import "AppController.j" + +function main(args, namedArgs) +{ + CPApplicationMain(); +} From d9f31e8543562379828678c282c619210b306259 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 15 Jul 2025 18:55:23 +0200 Subject: [PATCH 28/40] Revert "new: UIBuilder demo application" This reverts commit 38f523b3c278aa98e986c59ad2e9312c7ffb5a5a. --- Tests/Manual/UIBuilderDemo/AppController.j | 422 ------ Tests/Manual/UIBuilderDemo/ConnectionView.j | 97 -- Tests/Manual/UIBuilderDemo/Info.plist | 14 - .../UIBuilderDemo/InspectorController.j | 215 --- .../UIBuilderDemo/Resources/spinner.gif | Bin 1849 -> 0 bytes .../Manual/UIBuilderDemo/UIBuilderConstants.j | 10 - .../UIBuilderDemo/UIBuilderController.j | 645 -------- Tests/Manual/UIBuilderDemo/UICanvasView.j | 996 ------------ Tests/Manual/UIBuilderDemo/UIElementView.j | 1349 ----------------- Tests/Manual/UIBuilderDemo/index.html | 164 -- Tests/Manual/UIBuilderDemo/main.j | 10 - 11 files changed, 3922 deletions(-) delete mode 100644 Tests/Manual/UIBuilderDemo/AppController.j delete mode 100644 Tests/Manual/UIBuilderDemo/ConnectionView.j delete mode 100755 Tests/Manual/UIBuilderDemo/Info.plist delete mode 100644 Tests/Manual/UIBuilderDemo/InspectorController.j delete mode 100755 Tests/Manual/UIBuilderDemo/Resources/spinner.gif delete mode 100644 Tests/Manual/UIBuilderDemo/UIBuilderConstants.j delete mode 100644 Tests/Manual/UIBuilderDemo/UIBuilderController.j delete mode 100644 Tests/Manual/UIBuilderDemo/UICanvasView.j delete mode 100644 Tests/Manual/UIBuilderDemo/UIElementView.j delete mode 100644 Tests/Manual/UIBuilderDemo/index.html delete mode 100755 Tests/Manual/UIBuilderDemo/main.j diff --git a/Tests/Manual/UIBuilderDemo/AppController.j b/Tests/Manual/UIBuilderDemo/AppController.j deleted file mode 100644 index 6d9fa30a7..000000000 --- a/Tests/Manual/UIBuilderDemo/AppController.j +++ /dev/null @@ -1,422 +0,0 @@ -// -// AppController.j -// Main application controller. Sets up the window, canvas, palette, -// and controllers on launch. -// - -@import -@import "UIBuilderController.j" -@import "UICanvasView.j" -@import "UIElementView.j"; -@import "InspectorController.j"; - -@implementation CPColor (StandardColors) - -// A standard light gray for control backgrounds, like buttons. -+ (CPColor)controlColor -{ - return [CPColor colorWithCalibratedWhite:0.9 alpha:1.0]; -} - -// A medium gray for shadows or borders. -+ (CPColor)controlShadowColor -{ - return [CPColor grayColor]; -} - -// A dark gray for text on light controls. -+ (CPColor)controlDarkShadowColor -{ - return [CPColor darkGrayColor]; -} - -// The primary color for selected items. -+ (CPColor)selectedControlColor -{ - // Corresponds to the default blue selection color in macOS. - return [CPColor colorWithCalibratedRed:0.0 green:0.478 blue:1.0 alpha:1.0]; -} - -// A secondary selection color, often used for inactive windows or rubber-band selections. -+ (CPColor)alternateSelectedControlColor -{ - return [CPColor colorWithCalibratedRed:0.2 green:0.5 blue:0.9 alpha:1.0]; -} - -// The color for an inactive or secondary selection, like a window title bar. -+ (CPColor)secondarySelectedControlColor -{ - return [CPColor lightGrayColor]; -} - -// The highlight color for an element that has keyboard focus. -+ (CPColor)keyboardFocusIndicatorColor -{ - return [CPColor colorWithCalibratedRed:0.3 green:0.6 blue:1.0 alpha:1.0]; -} - -// The standard background color for a window's content area. -+ (CPColor)windowBackgroundColor -{ - return [CPColor colorWithCalibratedWhite:0.93 alpha:1.0]; -} - -// The background color for text-editing views. -+ (CPColor)textBackgroundColor -{ - return [CPColor whiteColor]; -} - -@end - -// Required additions from original EFView.j for graphics and text handling -@implementation CPString(SizingAddition) -- (CPSize)sizeWithAttributes:(CPDictionary)stringAttributes -{ - var font = [stringAttributes objectForKey:CPFontAttributeName] || [CPFont systemFontOfSize:12]; - // This is a simplified implementation. For more complex text, you might need a more robust solution. - var ctx = [[CPGraphicsContext currentContext] graphicsPort]; - var oldFont = ctx.font; - ctx.font = [font cssString]; - var metrics = ctx.measureText(self); - ctx.font = oldFont; - return CGSizeMake(metrics.width, [[font fontDescriptor] pointSize]); -} -- (void)drawAtPoint:(CGPoint)aPoint withAttributes:(CPDictionary)attributes -{ - var ctx = [[CPGraphicsContext currentContext] graphicsPort]; - var font = [attributes objectForKey:CPFontAttributeName] || [CPFont systemFontOfSize:12]; - var color = [attributes objectForKey:CPForegroundColorAttributeName] || [CPColor blackColor]; - - ctx.font = [font cssString]; - [color setFill]; - ctx.fillText(self, aPoint.x, aPoint.y + [[font fontDescriptor] pointSize]); -} -@end - -@implementation CPBezierPath(RoundedRectangle) -+ (CPBezierPath)bezierPathWithRoundedRect:(CPRect)aRect radius:(float)radius -{ - return [self bezierPathWithRoundedRect:aRect xRadius:radius yRadius:radius]; -} -@end - - -// A simple draggable symbol for the palette -@implementation DraggableSymbolView : CPView -{ - CPString _dragType; -} - -- (void)setDragType:(CPString)aType -{ - _dragType = aType; -} --(BOOL)acceptsFirstMouse:(CPEvent)aEvent -{ - return YES; -} - -- (void)mouseDown:(CPEvent)theEvent -{ - // 1. Create a placeholder view that is a visual copy of this one. - var dragPlaceholder = [[DraggableSymbolView alloc] initWithFrame:[self bounds]]; - [dragPlaceholder setDragType:_dragType]; // Ensure it can draw its title correctly - [dragPlaceholder setAlphaValue:0.75]; // Make it semi-transparent for good UX - - var pasteboard = [CPPasteboard pasteboardWithName:CPDragPboard]; - [pasteboard declareTypes:[_dragType] owner:nil]; - [pasteboard setString:@"1" forType:_dragType]; - - [self dragView:dragPlaceholder - at:[self bounds].origin - offset:nil - event:theEvent - pasteboard:pasteboard - source:self - slideBack:YES]; -} - -// The drawRect: method defines what the view looks like, and therefore -// what the dragged placeholder view will look like. -- (void)drawRect:(CGRect)rect -{ - var bounds = [self bounds]; - - // Background - [[CPColor controlColor] set]; - [CPBezierPath fillRect:bounds]; - [[CPColor controlShadowColor] set]; - [CPBezierPath strokeRect:bounds]; - - if ([_dragType isEqualToString:UIWindowDragType]) - { - // Draw a window - var windowRect = CGRectInset(bounds, 5, 5); - var titleBarHeight = 10; - - // Draw the title bar - var titleBarRect = CGRectMake(windowRect.origin.x, windowRect.origin.y, windowRect.size.width, titleBarHeight); - [[CPColor grayColor] set]; - [CPBezierPath fillRect:titleBarRect]; - - // Draw the content area - var contentRect = CGRectMake(windowRect.origin.x, windowRect.origin.y + titleBarHeight, windowRect.size.width, windowRect.size.height - titleBarHeight); - [[CPColor whiteColor] set]; - [CPBezierPath fillRect:contentRect]; - - // Draw the border for the whole window - [[CPColor blackColor] set]; - [CPBezierPath strokeRect:windowRect]; - } - else if ([_dragType isEqualToString:UIButtonDragType]) - { - // Draw a button - var buttonRect = CGRectInset(bounds, 8, 10); - var path = [CPBezierPath bezierPathWithRoundedRect:buttonRect radius:5]; - [[CPColor whiteColor] set]; - [path fill]; - [[CPColor blackColor] set]; - [path stroke]; - } - else if ([_dragType isEqualToString:UISliderDragType]) - { - // Draw a slider - var sliderY = bounds.size.height / 2; - var path = [CPBezierPath bezierPath]; - [path moveToPoint:CGPointMake(bounds.origin.x + 5, sliderY)]; - [path lineToPoint:CGPointMake(bounds.origin.x + bounds.size.width - 5, sliderY)]; - [[CPColor blackColor] set]; - [path stroke]; - - var knobRect = CGRectMake(bounds.size.width / 2 - 5, sliderY - 5, 10, 10); - var knobPath = [CPBezierPath bezierPathWithOvalInRect:knobRect]; - [[CPColor whiteColor] set]; - [knobPath fill]; - [[CPColor blackColor] set]; - [knobPath stroke]; - } - else if ([_dragType isEqualToString:UITextFieldDragType]) - { - // Draw a text field - var fieldRect = CGRectInset(bounds, 5, 12); - [[CPColor whiteColor] set]; - [CPBezierPath fillRect:fieldRect]; - [[CPColor blackColor] set]; - [CPBezierPath strokeRect:fieldRect]; - - // Draw an I-beam cursor - var ibeamX = CGRectGetMidX(fieldRect); - var ibeamY1 = CGRectGetMinY(fieldRect) + 3; - var ibeamY2 = CGRectGetMaxY(fieldRect) - 3; - - var ibeamPath = [CPBezierPath bezierPath]; - [ibeamPath moveToPoint:CGPointMake(ibeamX, ibeamY1)]; - [ibeamPath lineToPoint:CGPointMake(ibeamX, ibeamY2)]; - [ibeamPath moveToPoint:CGPointMake(ibeamX - 2, ibeamY1)]; - [ibeamPath lineToPoint:CGPointMake(ibeamX + 2, ibeamY1)]; - [ibeamPath moveToPoint:CGPointMake(ibeamX - 2, ibeamY2)]; - [ibeamPath lineToPoint:CGPointMake(ibeamX + 2, ibeamY2)]; - - [ibeamPath setLineWidth:0.5]; - [[CPColor blackColor] set]; - [ibeamPath stroke]; - } - else - { - // Fallback to original text drawing - var title = [[_dragType componentsSeparatedByString:@"DragType"] objectAtIndex:0]; - var textAttributes = @{ - CPFontAttributeName: [CPFont systemFontOfSize:10], - CPForegroundColorAttributeName: [CPColor blackColor] - }; - var titleSize = [title sizeWithAttributes:textAttributes]; - var titlePoint = CGPointMake( - (bounds.size.width - titleSize.width) / 2.0, - (bounds.size.height - titleSize.height) / 2.0 - ); - [title drawAtPoint:titlePoint withAttributes:textAttributes]; - } -} - -@end - -@implementation AppController : CPObject -{ - CPWindow _window; - CPPanel _palette; - UIBuilderController _builderController; - UICanvasView _canvasView; - InspectorController _inspectorController; -} - -- (void)applicationDidFinishLaunching:(CPNotification)aNotification -{ - // 1. Create the main window and canvas - _window = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask]; - [_window setTitle:@"Cappuccino UI Builder"]; - [_window setAcceptsMouseMovedEvents:YES]; - - _canvasView = [[UICanvasView alloc] initWithFrame:[[_window contentView] bounds]]; - [_canvasView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - [[_window contentView] addSubview:_canvasView]; - - // 2. Create the controllers - _builderController = [[UIBuilderController alloc] init]; - - // 3. Wire everything together - [_canvasView setDelegate:_builderController]; - - // Bind the canvas to the controller's data model. This is the core of the architecture. - [_canvasView bind:"dataObjects" toObject:_builderController withKeyPath:@"elementsController.arrangedObjects" options:nil]; - [_canvasView bind:"selectionIndexes" toObject:_builderController withKeyPath:@"elementsController.selectionIndexes" options:nil]; - [_canvasView bind:"connections" toObject:_builderController withKeyPath:@"connectionsController.arrangedObjects" options:nil]; - [_canvasView bind:"selectedConnections" toObject:_builderController withKeyPath:@"connectionsController.selectedObjects" options:nil]; - - [self createPalette]; - [self createInspector]; - - // 5. Create the main menu - var mainMenuBar = [[CPMenu alloc] initWithTitle:@"MainMenu"]; - var editMenuItem = [[CPMenuItem alloc] initWithTitle:@"Edit" action:nil keyEquivalent:@""]; - - - var editMenu = [[CPMenu alloc] initWithTitle:@"Edit"]; - [editMenu addItemWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"]; - [editMenu addItemWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"]; - [editMenu addItem:[CPMenuItem separatorItem]]; - [editMenu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"]; - [editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"]; - [editMenu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"]; - [editMenu addItemWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""]; - - [editMenuItem setSubmenu:editMenu]; - - var fileMenuItem = [[CPMenuItem alloc] initWithTitle:@"File" action:nil keyEquivalent:@""]; - var fileMenu = [[CPMenu alloc] initWithTitle:@"File"]; - [fileMenu addItemWithTitle:@"Run" action:@selector(run:) keyEquivalent:@"r"]; - [fileMenuItem setSubmenu:fileMenu]; - [mainMenuBar addItem:fileMenuItem]; - - [mainMenuBar addItem:editMenuItem]; - - [CPApp setMainMenu:mainMenuBar]; - [CPMenu setMenuBarVisible:YES]; - - [_window makeKeyAndOrderFront:self]; -} - -- (void)createPalette -{ - var screenWidth = window.innerWidth; - var paletteWidth = 220; - var paletteHeight = 60; - var paletteX = (screenWidth - paletteWidth) / 2; - var paletteY = 22; // Position near the top of the screen - - _palette = [[CPPanel alloc] initWithContentRect:CGRectMake(paletteX, paletteY, paletteWidth, paletteHeight) - styleMask:CPHUDBackgroundWindowMask | CPTitledWindowMask | CPClosableWindowMask]; - [_palette setTitle:@"Elements"]; - [_palette setFloatingPanel:YES]; - - var xPos = 10; - var types = [UIWindowDragType, UIButtonDragType, UISliderDragType, UITextFieldDragType]; - - // Create draggable symbols for each type - [_canvasView registerForDraggedTypes:types]; - - for (var i=0; i < [types count]; i++) { - var symbol = [[DraggableSymbolView alloc] initWithFrame:CGRectMake(xPos, 10, 40, 40)]; - symbol._dragType = types[i]; - - [[_palette contentView] addSubview:symbol]; - xPos += 50; - } - - [_palette orderFront:self]; -} - -- (void)createInspector -{ - var inspectorPanel = [[CPPanel alloc] initWithContentRect:CGRectMake(20, 200, 300, 150) - styleMask:CPTitledWindowMask | CPClosableWindowMask]; - [inspectorPanel setTitle:@"Inspector"]; - [inspectorPanel setFloatingPanel:YES]; - - var contentView = [inspectorPanel contentView]; - - _inspectorController = [[InspectorController alloc] init]; - [_inspectorController setBuilderController:_builderController]; - [_inspectorController setPanel:inspectorPanel]; - [_inspectorController setView:contentView]; - - [_inspectorController awakeFromMarkup]; // Manually call this - - [inspectorPanel orderFront:self]; -} - -- (void)run:(id)sender -{ - console.log("Run: Starting native UI generation..."); - var canvasSubviews = [_canvasView subviews]; - var nativeElementMap = [CPMutableDictionary dictionary]; - - // First pass: create all native elements and map them by their ID - console.log("Run: Creating native elements and building map..."); - for (var i = 0; i < [canvasSubviews count]; i++) - { - var view = [canvasSubviews objectAtIndex:i]; - if ([view isKindOfClass:[UIElementView class]]) - { - // This will now recursively build the map - [view nativeUIElementWithMap:nativeElementMap]; - } - } - - // Second pass: connect the native elements - console.log("Run: Processing connections..."); - var connections = [[_builderController connectionsController] content]; - for (var i = 0; i < [connections count]; i++) - { - var connection = [connections objectAtIndex:i]; - var sourceID = [connection valueForKey:@"sourceID"]; - var targetID = [connection valueForKey:@"targetID"]; - var action = [connection valueForKey:@"action"]; - - console.log(" - Connecting: " + sourceID + " -> " + targetID + " (Action: " + action + ")"); - - var nativeSource = [nativeElementMap objectForKey:sourceID]; - var nativeTarget = [nativeElementMap objectForKey:targetID]; - - if (nativeSource && nativeTarget && action) - { - console.log(" - Found native source and target. Applying connection."); - [nativeSource setTarget:nativeTarget]; - [nativeSource setAction:CPSelectorFromString(action)]; - } - else - { - console.log(" - WARNING: Could not find native source or target for connection."); - } - } - - // Third pass: show the windows - console.log("Run: Showing windows..."); - for (var i = 0; i < [canvasSubviews count]; i++) - { - var view = [canvasSubviews objectAtIndex:i]; - if ([view isKindOfClass:[UIWindowView class]]) - { - var elementID = [[view dataObject] valueForKey:@"id"]; - var nativeWindow = [nativeElementMap objectForKey:elementID]; - if (nativeWindow) - { - console.log(" - Showing window for ID: " + elementID); - [nativeWindow makeKeyAndOrderFront:self]; - } - } - } - console.log("Run: Finished."); -} - -@end diff --git a/Tests/Manual/UIBuilderDemo/ConnectionView.j b/Tests/Manual/UIBuilderDemo/ConnectionView.j deleted file mode 100644 index d77154cfb..000000000 --- a/Tests/Manual/UIBuilderDemo/ConnectionView.j +++ /dev/null @@ -1,97 +0,0 @@ - -@import - -function treshold(value, limit) -{ - return value > 0 ? Math.min(value, limit) : Math.max(value, -limit); -} - -@implementation ConnectionView : CPView -{ - CGPoint _startPoint; - CGPoint _endPoint; - CPColor _color; -} - -- (id)initWithFrame:(CGRect)aRect -{ - self = [super initWithFrame:aRect]; - if (self) - { - [self setBackgroundColor:[CPColor clearColor]]; - _color = [CPColor redColor]; - [self setHidden:YES]; // Hidden by default - } - return self; -} - -- (void)setStartPoint:(CGPoint)startPoint { _startPoint = startPoint; } -- (void)setEndPoint:(CGPoint)endPoint { _endPoint = endPoint; } -- (void)setColor:(CPColor)color { _color = color; } - -- (void)drawRect:(CGRect)rect -{ - console.log("ConnectionView: drawRect - Drawing connection from ", _startPoint, " to ", _endPoint); - if (_startPoint && _endPoint) - { - [self drawLinkFrom:_startPoint to:_endPoint color:_color]; - } -} - -- (void)drawLinkFrom:(CGPoint)startPoint to:(CGPoint)endPoint color:(CPColor)insideColor -{ - var dist = Math.sqrt(Math.pow(startPoint.x - endPoint.x, 2) + Math.pow(startPoint.y - endPoint.y, 2)); - var p0 = CGPointMake(startPoint.x, startPoint.y); - var p3 = CGPointMake(endPoint.x, endPoint.y); - var p1 = CGPointMake(startPoint.x + treshold((endPoint.x - startPoint.x) / 2, 50), startPoint.y); - var p2 = CGPointMake(endPoint.x - treshold((endPoint.x - startPoint.x) / 2, 50), endPoint.y); - var path = [CPBezierPath bezierPath]; - [path setLineWidth:0]; - [[CPColor grayColor] set]; - [path appendBezierPathWithOvalInRect:CGRectMake(startPoint.x-2.5,startPoint.y-2.5,5,5)]; - [path fill]; - path = [CPBezierPath bezierPath]; - [path setLineWidth:0]; - [insideColor set]; - [path appendBezierPathWithOvalInRect:CGRectMake(startPoint.x-1.5,startPoint.y-1.5,3,3)]; - [path fill]; - path = [CPBezierPath bezierPath]; - [path setLineWidth:0]; - [[CPColor grayColor] set]; - [path appendBezierPathWithOvalInRect:CGRectMake(endPoint.x-2.5,endPoint.y-2.5,5,5)]; - [path fill]; - path = [CPBezierPath bezierPath]; - [path setLineWidth:0]; - [insideColor set]; - [path appendBezierPathWithOvalInRect:CGRectMake(endPoint.x-1.5,endPoint.y-1.5,3,3)]; - [path fill]; - if (dist < 40) - { - path = [CPBezierPath bezierPath]; - [path setLineWidth:5]; - [path moveToPoint:startPoint]; - [path lineToPoint:endPoint]; - [[CPColor grayColor] set]; - [path stroke]; - path = [CPBezierPath bezierPath]; - [path setLineWidth:3]; - [path moveToPoint:startPoint]; - [path lineToPoint:endPoint]; - [insideColor set]; - [path stroke]; - return; - } - path = [CPBezierPath bezierPath]; - [path setLineWidth:5]; - [path moveToPoint:p0]; - [path curveToPoint:p3 controlPoint1:p1 controlPoint2:p2]; - [[CPColor grayColor] set]; - [path stroke]; - path = [CPBezierPath bezierPath]; - [path setLineWidth:3]; - [path moveToPoint:p0]; - [path curveToPoint:p3 controlPoint1:p1 controlPoint2:p2]; - [insideColor set]; - [path stroke]; -} -@end diff --git a/Tests/Manual/UIBuilderDemo/Info.plist b/Tests/Manual/UIBuilderDemo/Info.plist deleted file mode 100755 index 8bbc29491..000000000 --- a/Tests/Manual/UIBuilderDemo/Info.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - CPApplicationDelegateClass - AppController - CPBundleName - UIBuilderDemo - CPPrincipalClass - CPApplication - CPDefaultTheme - Aristo2 - - diff --git a/Tests/Manual/UIBuilderDemo/InspectorController.j b/Tests/Manual/UIBuilderDemo/InspectorController.j deleted file mode 100644 index dd3d0af2f..000000000 --- a/Tests/Manual/UIBuilderDemo/InspectorController.j +++ /dev/null @@ -1,215 +0,0 @@ -@import - -@class UIBuilderController; - -@implementation InspectorController : CPViewController -{ - UIBuilderController _builderController @accessors(property=builderController); - CPPanel _panel @accessors(property=panel); - CPTableView _connectionsTableView; -} - -- (void)awakeFromMarkup -{ - [_builderController addObserver:self forKeyPath:@"elementsController.selectionIndexes" options:CPKeyValueObservingOptionNew context:nil]; - - // Create Tab View - var tabView = [[CPTabView alloc] initWithFrame:[[_panel contentView] bounds]]; - [tabView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - [tabView setDelegate:self]; - - // Properties Tab - var propertiesView = [[CPView alloc] initWithFrame:CGRectMakeZero()]; - var propertiesTabItem = [[CPTabViewItem alloc] initWithIdentifier:@"properties"]; - [propertiesTabItem setLabel:@"Properties"]; - [propertiesTabItem setView:propertiesView]; - [tabView addTabViewItem:propertiesTabItem]; - - // Connections Tab - var connectionsView = [[CPView alloc] initWithFrame:CGRectMakeZero()]; - var connectionsTabItem = [[CPTabViewItem alloc] initWithIdentifier:@"connections"]; - [connectionsTabItem setLabel:@"Connections"]; - [connectionsTabItem setView:connectionsView]; - [tabView addTabViewItem:connectionsTabItem]; - - // Connections TableView - _connectionsTableView = [[CPTableView alloc] initWithFrame:[connectionsView bounds]]; - [_connectionsTableView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - - var columns = [ - {identifier: "outlet", title: "Outlet", width: 80}, - {identifier: "action", title: "Action", width: 120} - ]; - - // Keep a reference to the array controller - var connectionsController = [_builderController connectionsController]; - - for (var i = 0; i < [columns count]; i++) { - var colInfo = columns[i]; - var column = [[CPTableColumn alloc] initWithIdentifier:colInfo.identifier]; - [[column headerView] setStringValue:colInfo.title]; - [column setWidth:colInfo.width]; - [_connectionsTableView addTableColumn:column]; - // Bind the value of each column to the corresponding key path on the arranged objects - [column bind:CPValueBinding toObject:connectionsController withKeyPath:("arrangedObjects." + colInfo.identifier) options:nil]; - } - - // Bind the table's selection to the array controller's selection - [_connectionsTableView bind:@"selectionIndexes" toObject:connectionsController withKeyPath:@"selectionIndexes" options:nil]; - - var connectionsViewBounds = [connectionsView bounds]; - var buttonBarHeight = 28; - var tableHeight = connectionsViewBounds.size.height - buttonBarHeight; - - var scrollViewFrame = CGRectMake(3, 3, connectionsViewBounds.size.width - 6, tableHeight - 6); - var buttonBarFrame = CGRectMake(0, tableHeight, connectionsViewBounds.size.width, buttonBarHeight); - - var scrollView = [[CPScrollView alloc] initWithFrame:scrollViewFrame]; - [scrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - [scrollView setDocumentView:_connectionsTableView]; - [connectionsView addSubview:scrollView]; - - var buttonBar = [[CPView alloc] initWithFrame:buttonBarFrame]; - [buttonBar setAutoresizingMask:CPViewWidthSizable | CPViewMinYMargin]; // Stick to bottom - [connectionsView addSubview:buttonBar]; - - var deleteButton = [CPButtonBar minusButton]; - [deleteButton setAction:@selector(deleteSelectedConnection:)]; - [deleteButton setTarget:self]; - [buttonBar addSubview:deleteButton]; - - // Replace panel's content view with the tab view - [_panel setContentView:tabView]; - - [self updateInspector]; -} - -- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(int)aRow -{ - var connection = [[[_builderController connectionsController] arrangedObjects] objectAtIndex:aRow]; - var identifier = [aTableColumn identifier]; - - return [connection valueForKey:identifier]; -} - -- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context -{ - // We only observe selection changes now. - if (keyPath === @"elementsController.selectionIndexes") - { - [self updateInspector]; - [self _updateConnectionVisibility]; - } -} - -- (void)_updateConnectionVisibility -{ - var tabView = [[self panel] contentView]; - if (![tabView isKindOfClass:[CPTabView class]]) - return; - - var selectedTabViewItem = [tabView selectedTabViewItem]; - var connectionsController = [_builderController connectionsController]; - var selectedObjects = [[_builderController elementsController] selectedObjects]; - - // 1. Filter the connections based on the selected UI element. - if ([selectedObjects count] === 1) - { - var selectedID = [[selectedObjects objectAtIndex:0] valueForKey:@"id"]; - var predicate = [CPPredicate predicateWithFormat:@"sourceID == %@ OR targetID == %@", selectedID, selectedID]; - [connectionsController setFilterPredicate:predicate]; - } - else - { - [connectionsController setFilterPredicate:[CPPredicate predicateWithFormat:@"FALSEPREDICATE"]]; - } -} - -- (void)tabView:(CPTabView)aTabView didSelectTabViewItem:(CPTabViewItem)aTabViewItem -{ - [self _updateConnectionVisibility]; -} - -- (void)deleteSelectedConnection:(id)sender -{ - var selectedObjects = [[_builderController connectionsController] selectedObjects]; - if ([selectedObjects count] > 0) - [[_builderController connectionsController] removeObjects:selectedObjects]; -} - -- (void)updateInspector -{ - var selectedObjects = [[_builderController elementsController] selectedObjects]; - var propertiesView = [[[_panel contentView] tabViewItemAtIndex:0] view]; - - // Clear existing views from properties tab - var subviews = [propertiesView subviews]; - for (var i = [subviews count] - 1; i >= 0; i--) { - [subviews[i] removeFromSuperview]; - } - - if ([selectedObjects count] === 1) - { - var selectedObject = selectedObjects[0]; - var elementType = [selectedObject valueForKey:@"type"]; - var viewClass = [UIBuilderController classForElementType:elementType]; - var properties = [viewClass persistentProperties]; - - var yPos = 10; - - // Set panel title - [_panel setTitle:elementType]; - - for (var i = 0; i < [properties count]; i++) - { - var propertyName = properties[i]; - var value = [selectedObject valueForKey:propertyName]; - var propertyType = [[viewClass propertyTypes] valueForKey:propertyName]; - - // Create Label - var label = [[CPTextField alloc] initWithFrame:CGRectMake(10, yPos + 3, 100, 20)]; - [label setStringValue:propertyName]; - [label setBezeled:NO]; - [label setDrawsBackground:NO]; - [label setEditable:NO]; - [propertiesView addSubview:label]; - [label setTextColor:[CPColor grayColor]]; - - // Create Control based on property type - if (propertyType === UIBBoolean) { - var checkbox = [[CPCheckBox alloc] initWithFrame:CGRectMake(120, yPos, 100, 20)]; - [checkbox setTitle:@""]; - [checkbox bind:@"value" toObject:selectedObject withKeyPath:propertyName options:nil]; - [propertiesView addSubview:checkbox]; - } else if (propertyType === UIBString || propertyType === UIBNumber) { - var textField = [[CPTextField alloc] initWithFrame:CGRectMake(120, yPos, 150, 27)]; - [textField bind:@"value" toObject:selectedObject withKeyPath:propertyName options:nil]; - [textField setBezeled:YES]; - [textField setEditable:YES]; - [propertiesView addSubview:textField]; - } else { // Fallback for unknown types - var textField = [[CPTextField alloc] initWithFrame:CGRectMake(120, yPos, 150, 25)]; - [textField bind:@"value" toObject:selectedObject withKeyPath:propertyName options:nil]; - [textField setBezeled:YES]; - [textField setEditable:YES]; - [propertiesView addSubview:textField]; - } - - yPos += 30; - } - - [[self panel] orderFront:self]; - } - else - { - [[self panel] orderOut:self]; - } -} - -- (void)dealloc -{ - [_builderController removeObserver:self forKeyPath:@"elementsController.selectionIndexes"]; - [super dealloc]; -} - -@end diff --git a/Tests/Manual/UIBuilderDemo/Resources/spinner.gif b/Tests/Manual/UIBuilderDemo/Resources/spinner.gif deleted file mode 100755 index 06dbc2bc21dddcf0e09b566d5b211aee89570f52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1849 zcma*odr(tX9tZHtz31lM+(&Y`A`Ou`NeG&R#DrIfV%?hng21vs6v>ooRQQFbk7E_K!V( z{?5!fpZWgIZ%*d6t%i*j24bL}AZUJm9)h6R*;%L4IWse3G#VWaN1#$zSSXcBhlYlH zJ|D}n{r&w2f+Qp)SS*&n?*G4}{}!h?td_mjU6Kad-rW*QwWYCUk7d^e+sjpZAn6kT z5rM;`{~_}-wm+L@%+E;JphLm}C3WzQAQD0wBoY|rOTQX{(@pp-WAA8Iw0HKl|15j) zhGh(SFWu#5zUCw^T!}C~eC}~?{xG-r7ugQC>taC+|L^xr&a%{#cgUOZXIM&v?)k^2 z1i5%0!U00wq)r4vlAwXBA_m5J5WNeuBm-XJQ5pG4GD%vc|K){+TJ{AkHqN@SJKczX zU}QRd9n*_bf^h94)6Ctv{CUfz_;qs#RDuQ$DR%y1&5G-a6rhf*4Y zl+UOD1*GE{GQ7vv;c-tTIMkz0ovO>9j%b5hpa@a*CBJ#W!0@ndSwF|%hMkdFtXiU) z`BEp+yhkt8ZY>tg&uyH@%^%K4jDrbA;et&vnnzZ3z22@e6<27z2Vk13kb7OtISyxv z(XoO-LNQCZe3lfzc%*pNqU<9T+p~teThOEB+~e&U0mC|zS{Q)dJZ)!^1f7L(zg*L> z*vSW%?dR~@@0(}x6-V^A`~ImD;)NaBUnP8;RH=bS2@pk`wp>>8r&jGjmGPy%1BW}v zoPo}ca$~bze@efc3kapuEVW1!%teOZT3j2Tw5=hjiAf02d}7dL0oFC%@=RXp5Ow#% z@a>+AM|YWt$n&e`>sB-32gBcuTi>R>*|8@lp`y6thc7ydyqDsuUn~YzZf|{-R@-3y zL#wx{Ii}xxL_csiW*LBn0-A$>zp4WOmkf6=ilrjlW?-l9+bCd7|i?*b5NTmy<<6NZ0T8$SN@mwdC2f z6jqK=N@bS@!=YSj%>h0}*4p+%0HbTIrE$w7UMT6+AZZ&DAo*qZAAi(OtNbIfl#Dw^ zJWeiCp~zi#&t6x}m9)O^eR4HiLV3QA<<0>HZ8%$^lrSE94Wb}=+MM^!b>n#5&-JQR zkr-CEu9C;_F*7DqDisulV6Pmg$nFL0TPn%~*m^-`Z3^BgU(sNpnx%nW(!eV9A&FvI zHL3X3lw8Wji^6=8KbQDE-e%b?svdFxxdh$8r97@7$ojeI`6AL8Ob6QC$qh&>ZWVDX1F{0vJnT%%mE;GveKWR%C} zM&4d;Jf3s9|HAA)yVUPo`Aq;0do#)uHSXi5*QF*)x@MVVHr+cN)uMZ__F|&Ta#p8d z53TOKtce!PJUuie8UWol-S(`c2nH?UGqJP{!4RR4u$LCfn)q-hj0^f=h(VYy)T6eN zhRO!ja-aDBTcgeyP(8Ua4IdiOog^*CQa?R(cP#9AgL9`j>EX-6Yf1lzX(!~``M1XC zNmM<4<6d~wWZ$Xrk0K}UteTrq@LBBk#MsjkK;pbuVhe)NI7(7Pf(l?lxC7=1Z7Pzl zMbS;nV4NI5_N{1$P)&XC)huOGQ+h^zpYUZfb*1n6>!`#*b3y52LGmi+<4sY5jyD#- zw&$d}DTguLAfnRtjrM*JfqtHqUu6rQoU=g%1E9xk%;)TDm^2<8n-0IhTsQFaek)MY}>PK4;nBk=ow4pF>vzkO& gna%Org-kgQCf=+BeMi^RbuY;YE~rTjend;_cdi8t>i_@% diff --git a/Tests/Manual/UIBuilderDemo/UIBuilderConstants.j b/Tests/Manual/UIBuilderDemo/UIBuilderConstants.j deleted file mode 100644 index 1e5c5b5ec..000000000 --- a/Tests/Manual/UIBuilderDemo/UIBuilderConstants.j +++ /dev/null @@ -1,10 +0,0 @@ -// -// UIBuilderConstants.j -// Defines global constants for the UI Builder application. -// - -UIBuilderElementPboardType = "UIBuilderElementPboardType"; -UIWindowDragType = "UIWindowDragType"; -UIButtonDragType = "UIButtonDragType"; -UISliderDragType = "UISliderDragType"; -UITextFieldDragType = "UITextFieldDragType"; diff --git a/Tests/Manual/UIBuilderDemo/UIBuilderController.j b/Tests/Manual/UIBuilderDemo/UIBuilderController.j deleted file mode 100644 index cb4ba5c95..000000000 --- a/Tests/Manual/UIBuilderDemo/UIBuilderController.j +++ /dev/null @@ -1,645 +0,0 @@ -// -// UIBuilderController.j -// This is the main controller for the UI Builder application. -// It manages the data model for all elements on the canvas and acts -// as a delegate for the UICanvasView to respond to user interactions. -// -// By Daniel Boehringer in 2025. -// - -@import -@import "UIElementView.j" -@import "UICanvasView.j" -@import "UIBuilderConstants.j"; - -// This is a simple data model. In a real app, it might have more properties. -// We use a custom dictionary to ensure KVO compatibility and proper value setting. -@implementation CPConservativeDictionary : CPDictionary -{ } - -- (id)init -{ - self = [super init]; - if (self) { - // Rely on superclass to initialize _buckets - } - return self; -} - -+ (id)dictionary -{ - return [[self alloc] init]; -} - -- (void)setValue:(id)aVal forKey:(CPString)aKey -{ - // Only set the value if it's different from the current value - var currentValue = [super valueForKey:aKey]; - - - // Always set the value if the current value is null or undefined - if (currentValue == null || currentValue == undefined || currentValue != aVal) { - [super setValue:aVal forKey:aKey]; - } -} - -- (BOOL)isEqual:(id)otherObject -{ - return [self valueForKey:'id'] == [otherObject valueForKey:'id']; -} - -- (id)initWithCoder:(CPCoder)aCoder -{ - self = [super initWithCoder:aCoder]; - if (self) - { - var allKeys = [aCoder decodeObjectForKey:@"CPConservativeDictionaryKeys"]; - if (allKeys) - { - for (var i = 0; i < [allKeys count]; i++) - { - var key = allKeys[i]; - var value = [aCoder decodeObjectForKey:key]; - [self setObject:value forKey:key]; - } - } - } - return self; -} - -- (void)encodeWithCoder:(CPCoder)aCoder -{ - [super encodeWithCoder:aCoder]; - var allKeys = [self allKeys]; - [aCoder encodeObject:allKeys forKey:@"CPConservativeDictionaryKeys"]; - for (var i = 0; i < [allKeys count]; i++) - { - var key = allKeys[i]; - [aCoder encodeObject:[self objectForKey:key] forKey:key]; - } -} - -@end - - -@implementation UIBuilderController : CPViewController -{ - CPArrayController _elementsController @accessors(property=elementsController); - CPArrayController _connectionsController @accessors(property=connectionsController); - CPMutableArray _connections; - int _elementCounter; // To generate unique IDs -} - -+ (Class)classForElementType:(CPString)elementType -{ - if (elementType === "window") return UIWindowView; - if (elementType === "button") return UIButtonView; - if (elementType === "slider") return UISliderView; - if (elementType === "textfield") return UITextFieldView; - return UIElementView; -} - -- (id)init -{ - self = [super init]; - if (self) { - _elementsController = [[CPArrayController alloc] init]; - _connectionsController = [[CPArrayController alloc] init]; - _elementCounter = 0; - } - return self; -} - -#pragma mark - -#pragma mark Data Management - -- (CPDictionary)_containerDataAtPoint:(CGPoint)aPoint -{ - var allElements = [_elementsController arrangedObjects]; - for (var i = [allElements count] - 1; i >= 0; i--) - { - var elementData = allElements[i]; - var type = [elementData valueForKey:@"type"]; - if (type === "window") - { - var frame = CGRectMake([elementData valueForKey:@"originX"], [elementData valueForKey:@"originY"], [elementData valueForKey:@"width"], [elementData valueForKey:@"height"]); - if (CGRectContainsPoint(frame, aPoint)) - return elementData; - } - } - return nil; -} - -- (void)addNewElementOfType:(CPString)elementType atPoint:(CGPoint)aPoint -{ - var newElementData = [CPConservativeDictionary dictionary]; - var containerData = [self _containerDataAtPoint:aPoint]; - var viewClass = [UIBuilderController classForElementType:elementType]; - - // Set default properties based on type - [newElementData setValue:elementType forKey:@"type"]; - [newElementData setValue:@"id_" + _elementCounter++ forKey:@"id"]; - - // Set default values from the view class - var defaultValues = [viewClass defaultValues]; - for (var key in defaultValues) { - [newElementData setValue:defaultValues[key] forKey:key]; - } - - // Set default sizes - if (elementType === "window") { - [newElementData setValue:250 forKey:@"width"]; - [newElementData setValue:200 forKey:@"height"]; - [newElementData setValue:[] forKey:@"children"]; - } else if (elementType === "button") { - [newElementData setValue:100 forKey:@"width"]; - [newElementData setValue:24 forKey:@"height"]; - } else if (elementType === "slider") { - [newElementData setValue:150 forKey:@"width"]; - [newElementData setValue:20 forKey:@"height"]; - } else { // textfield - [newElementData setValue:150 forKey:@"width"]; - [newElementData setValue:22 forKey:@"height"]; - } - - // Calculate centered position - var elementWidth = [newElementData valueForKey:@"width"]; - var elementHeight = [newElementData valueForKey:@"height"]; - var centeredX = aPoint.x - (elementWidth / 2); - var centeredY = aPoint.y - (elementHeight / 2); - [newElementData setValue:centeredX forKey:@"originX"]; - [newElementData setValue:centeredY forKey:@"originY"]; - - if (containerData && elementType !== "window") - { - // Convert point to be relative to the container and center the element - var elementWidth = [newElementData valueForKey:@"width"]; - var elementHeight = [newElementData valueForKey:@"height"]; - var relativeX = (aPoint.x - [containerData valueForKey:@"originX"]) - (elementWidth / 2); - var relativeY = (aPoint.y - [containerData valueForKey:@"originY"]) - (elementHeight / 2); - [newElementData setValue:relativeX forKey:@"originX"]; - [newElementData setValue:relativeY forKey:@"originY"]; - - // Add as a child to the container - [newElementData setValue:[containerData valueForKey:@"id"] forKey:@"parentID"]; - [[containerData mutableArrayValueForKey:@"children"] addObject:newElementData]; - } - - // Add to the main controller regardless, so selection works. - [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_elementsController] removeObject:newElementData]; - [[[CPApp keyWindow] undoManager] setActionName:@"Add Element"]; - [_elementsController addObject:newElementData]; - - [_elementsController setSelectedObjects:[CPArray arrayWithObject:newElementData]]; -} - -- (void)removeSelectedElementsWithActionName:(CPString)actionName -{ - var selectedObjects = [[_elementsController selectedObjects] copy]; - if ([selectedObjects count] === 0) return; - - [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_elementsController] addObjects:selectedObjects]; - [[[CPApp keyWindow] undoManager] setActionName:actionName]; - [_elementsController removeObjects:selectedObjects]; -} - -- (void)removeSelectedElements -{ - [self removeSelectedElementsWithActionName:@"Delete"]; -} - -- (void)cut:(id)sender -{ - [self copy:sender]; - [self removeSelectedElementsWithActionName:@"Cut"]; -} - -#pragma mark - -#pragma mark Keyboard Movement - -- (void)moveSelectedElementsByDeltaX:(int)deltaX deltaY:(int)deltaY -{ - var selectedDataObjects = [_elementsController selectedObjects]; - var changes = [CPMutableArray array]; - for (var i = 0; i < [selectedDataObjects count]; i++) - { - var data = selectedDataObjects[i]; - var newFrame = { - origin: { - x: [data valueForKey:@"originX"] + deltaX, - y: [data valueForKey:@"originY"] + deltaY - } - }; - [changes addObject:{ data: data, frame: newFrame }]; - } - [self applyFrameChanges:changes withActionName:@"Move"]; -} - -- (void)moveLeft:(id)sender -{ - [self moveSelectedElementsByDeltaX:-1 deltaY:0]; -} - -- (void)moveRight:(id)sender -{ - [self moveSelectedElementsByDeltaX:1 deltaY:0]; -} - -- (void)moveUp:(id)sender -{ - [self moveSelectedElementsByDeltaX:0 deltaY:-1]; -} - -- (void)moveDown:(id)sender -{ - [self moveSelectedElementsByDeltaX:0 deltaY:1]; -} - -#pragma mark - -#pragma mark Copy & Paste - -- (void)copy:(id)sender -{ - var selectedData = [_elementsController selectedObjects]; - - if ([selectedData count] > 0) - { - var pboard = [CPPasteboard generalPasteboard]; - var data = [CPKeyedArchiver archivedDataWithRootObject:selectedData]; - - // 1. Declare that you are providing BOTH a custom type and a standard string type. - [pboard declareTypes:[UIBuilderElementPboardType, CPStringPboardType] owner:nil]; - - // 2. Set the data for your custom type, for your app's internal 'paste' to use. - [pboard setData:data forType:UIBuilderElementPboardType]; - - // 3. Set a string representation for the browser and other applications. - // This can be a simple description or a more complex JSON representation. - var description = [selectedData count] + " UI element(s) copied."; - [pboard setString:description forType:CPStringPboardType]; - } -} - -- (void)_assignNewIDsToElement:(CPMutableDictionary)elementData -{ - [elementData setValue:@"id_" + _elementCounter++ forKey:@"id"]; - - var children = [elementData valueForKey:@"children"]; - if (children) - { - var newChildren = [CPMutableArray array]; - for (var i = 0; i < [children count]; i++) - { - var child = children[i]; - // Deep copy child before modifying - var newChild = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:child]]; - [newChild setValue:[elementData valueForKey:@"id"] forKey:@"parentID"]; - [self _assignNewIDsToElement:newChild]; - [newChildren addObject:newChild]; - } - [elementData setValue:newChildren forKey:@"children"]; - } -} - -- (void)paste:(id)sender -{ - var pboard = [CPPasteboard generalPasteboard]; - var types = [pboard types]; - - if ([types containsObject:UIBuilderElementPboardType]) - { - var data = [pboard dataForType:UIBuilderElementPboardType]; - var pastedElements = [CPKeyedUnarchiver unarchiveObjectWithData:data]; - var newSelection = [CPMutableArray array]; - - // Determine the target container - var targetContainer = nil; - var selectedObjects = [_elementsController selectedObjects]; - if ([selectedObjects count] > 0) - { - var firstSelected = selectedObjects[0]; - var parentID = [firstSelected valueForKey:@"parentID"]; - if (parentID) - { - // Find the parent container in the elements controller - var allElements = [_elementsController arrangedObjects]; - for (var i = 0; i < [allElements count]; i++) - { - if ([[allElements[i] valueForKey:@"id"] isEqualToString:parentID]) - { - targetContainer = allElements[i]; - break; - } - } - } - else - { - // If the selected object has no parent, it must be a window - targetContainer = firstSelected; - } - } - else - { - // If no selection, find the first window - var allElements = [_elementsController arrangedObjects]; - for (var i = 0; i < [allElements count]; i++) - { - if ([[allElements[i] valueForKey:@"type"] isEqualToString:@"window"]) - { - targetContainer = allElements[i]; - break; - } - } - } - - for (var i = 0; i < [pastedElements count]; i++) - { - var newElement = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:pastedElements[i]]]; - - [newElement setValue:[newElement valueForKey:@"originX"] + 10 forKey:@"originX"]; - [newElement setValue:[newElement valueForKey:@"originY"] + 10 forKey:@"originY"]; - - [self _assignNewIDsToElement:newElement]; - - if (targetContainer && [newElement valueForKey:@"type"] !== @"window") - { - [newElement setValue:[targetContainer valueForKey:@"id"] forKey:@"parentID"]; - [[targetContainer mutableArrayValueForKey:@"children"] addObject:newElement]; - } - else - { - [newElement removeObjectForKey:@"parentID"]; - } - - [_elementsController addObject:newElement]; - - if ([newElement valueForKey:@"children"]) - [_elementsController addObjects:[newElement valueForKey:@"children"]]; - - [newSelection addObject:newElement]; - } - - [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_elementsController] removeObjects:newSelection]; - [[[CPApp keyWindow] undoManager] setActionName:@"Paste"]; - [_elementsController setSelectedObjects:newSelection]; - } -} - -- (void)addNewElementOfType:(CPString)elementType inNewWindowAtPoint:(CGPoint)aPoint -{ - // 1. Create the new element to be placed in the window - var newElementData = [CPConservativeDictionary dictionary]; - var viewClass = [UIBuilderController classForElementType:elementType]; - [newElementData setValue:elementType forKey:@"type"]; - [newElementData setValue:@"id_" + _elementCounter++ forKey:@"id"]; - - var defaultValues = [viewClass defaultValues]; - for (var key in defaultValues) - [newElementData setValue:defaultValues[key] forKey:key]; - - var elementWidth, elementHeight; - - if (elementType === "button") { - elementWidth = 100; - elementHeight = 24; - } else if (elementType === "slider") { - elementWidth = 150; - elementHeight = 20; - } else { // textfield - elementWidth = 150; - elementHeight = 22; - } - [newElementData setValue:elementWidth forKey:@"width"]; - [newElementData setValue:elementHeight forKey:@"height"]; - - // 2. Create the window that will contain the new element - var windowData = [CPConservativeDictionary dictionary]; - var windowClass = [UIBuilderController classForElementType:"window"]; - var windowWidth = 250, windowHeight = 200; - [windowData setValue:@"window" forKey:@"type"]; - [windowData setValue:@"id_" + _elementCounter++ forKey:@"id"]; - [windowData setValue:windowWidth forKey:@"width"]; - [windowData setValue:windowHeight forKey:@"height"]; - [windowData setValue:[] forKey:@"children"]; - - defaultValues = [windowClass defaultValues]; - for (var key in defaultValues) { - [windowData setValue:defaultValues[key] forKey:key]; - } - - // 3. Position the new element in the center of the window - var elementX = (windowWidth - elementWidth) / 2; - var elementY = (windowHeight - elementHeight) / 2; - [newElementData setValue:elementX forKey:@"originX"]; - [newElementData setValue:elementY forKey:@"originY"]; - - // 4. Position the window so the element is at the drop point - var windowX = aPoint.x - elementX; - var windowY = aPoint.y - elementY; - [windowData setValue:windowX forKey:@"originX"]; - [windowData setValue:windowY forKey:@"originY"]; - - // 5. Add the element to the window's children - [newElementData setValue:[windowData valueForKey:@"id"] forKey:@"parentID"]; - [[windowData mutableArrayValueForKey:@"children"] addObject:newElementData]; - - console.log("UIBuilderController: addNewElementOfType:inNewWindowAtPoint: - Adding new element to window's children:", newElementData); - - // 6. Add both to the elements controller - var undoManager = [[CPApp keyWindow] undoManager]; - [undoManager beginUndoGrouping]; - [[undoManager prepareWithInvocationTarget:_elementsController] removeObject:newElementData]; - [[undoManager prepareWithInvocationTarget:_elementsController] removeObject:windowData]; - [undoManager setActionName:@"Add Element in New Window"]; - [_elementsController addObject:windowData]; - [_elementsController addObject:newElementData]; - [undoManager endUndoGrouping]; - - // 7. Select the new element - [_elementsController setSelectedObjects:[CPArray arrayWithObject:newElementData]]; -} - -- (void)addNewElementOfType:(CPString)elementType inWindow:(CPDictionary)windowData atPoint:(CGPoint)aPoint -{ - console.log("UIBuilderController: addNewElementOfType:inWindow:atPoint: - Adding element ", elementType, " to window ", windowData, " at point ", aPoint); - var newElementData = [CPConservativeDictionary dictionary]; - var viewClass = [UIBuilderController classForElementType:elementType]; - - [newElementData setValue:elementType forKey:@"type"]; - [newElementData setValue:@"id_" + _elementCounter++ forKey:@"id"]; - - var defaultValues = [viewClass defaultValues]; - for (var key in defaultValues) - [newElementData setValue:defaultValues[key] forKey:key]; - - var elementWidth, elementHeight; - - if (elementType === "button") { - elementWidth = 100; - elementHeight = 24; - } else if (elementType === "slider") { - elementWidth = 150; - elementHeight = 20; - } else { // textfield - elementWidth = 150; - elementHeight = 22; - } - [newElementData setValue:elementWidth forKey:@"width"]; - [newElementData setValue:elementHeight forKey:@"height"]; - - // Position the new element relative to the window's origin - [newElementData setValue:aPoint.x forKey:@"originX"]; - [newElementData setValue:aPoint.y forKey:@"originY"]; - - // Add as a child to the container window - [newElementData setValue:[windowData valueForKey:@"id"] forKey:@"parentID"]; - [[windowData mutableArrayValueForKey:@"children"] addObject:newElementData]; - - // Add to the main controller - var undoManager = [[CPApp keyWindow] undoManager]; - [undoManager beginUndoGrouping]; - [[undoManager prepareWithInvocationTarget:_elementsController] removeObject:newElementData]; - [undoManager setActionName:@"Add Element to Window"]; - [_elementsController addObject:newElementData]; - [undoManager endUndoGrouping]; - - [_elementsController setSelectedObjects:[CPArray arrayWithObject:newElementData]]; -} - -- (void)addConnectionFrom:(CPDictionary)sourceData to:(CPDictionary)targetData atPoint:(CGPoint)atPoint outlet:(CPString)outlet action:(CPString)action -{ - var newConnection = [CPConservativeDictionary dictionary]; - [newConnection setValue:[sourceData valueForKey:@"id"] forKey:@"sourceID"]; - [newConnection setValue:[targetData valueForKey:@"id"] forKey:@"targetID"]; - [newConnection setValue:outlet forKey:@"outlet"]; - [newConnection setValue:action forKey:@"action"]; - [newConnection setValue:@"connection_" + _elementCounter++ forKey:@"id"]; - - if (atPoint) - [newConnection setValue:{x: atPoint.x, y: atPoint.y} forKey:@"atPoint"]; - - [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_connectionsController] removeObject:newConnection]; - [[[CPApp keyWindow] undoManager] setActionName:@"Add Connection"]; - - [_connectionsController addObject:newConnection]; - - console.log("UIBuilderController: addConnectionFrom:to: - Added connection: ", newConnection); - console.log("Connections controller count after add: " + [[_connectionsController arrangedObjects] count]); -} - -- (void)removeConnection:(CPDictionary)connection -{ - [[[[CPApp keyWindow] undoManager] prepareWithInvocationTarget:_connectionsController] addObject:connection]; - [[[CPApp keyWindow] undoManager] setActionName:@"Remove Connection"]; - - [_connectionsController removeObject:connection]; -} - -#pragma mark - -#pragma mark UICanvasView Delegate Methods - -- (void)applyFrameChanges:(CPArray)changes withActionName:(CPString)actionName -{ - var undoManager = [[CPApp keyWindow] undoManager]; - var undoChanges = [CPMutableArray array]; - - [undoManager beginUndoGrouping]; - [undoManager setActionName:actionName]; - - for (var i = 0; i < [changes count]; i++) - { - var change = changes[i]; - var data = change.data; - var newFrame = change.frame; - var oldValues = { data: data, frame: {} }; - - if (newFrame.origin) - { - oldValues.frame.origin = { - x: [data valueForKey:@"originX"], - y: [data valueForKey:@"originY"] - }; - [data setValue:newFrame.origin.x forKey:@"originX"]; - [data setValue:newFrame.origin.y forKey:@"originY"]; - } - - if (newFrame.size) - { - oldValues.frame.size = { - width: [data valueForKey:@"width"], - height: [data valueForKey:@"height"] - }; - [data setValue:newFrame.size.width forKey:@"width"]; - [data setValue:newFrame.size.height forKey:@"height"]; - } - [undoChanges addObject:oldValues]; - } - - [[undoManager prepareWithInvocationTarget:self] applyFrameChanges:undoChanges withActionName:actionName]; - [undoManager endUndoGrouping]; -} - -- (void)canvasView:(UICanvasView)aCanvas didMoveElement:(UIElementView)anElement -{ - var selectedViews = [aCanvas selectedSubViews]; - var changes = [CPMutableArray array]; - for (var i = 0; i < [selectedViews count]; i++) - { - var view = selectedViews[i]; - [changes addObject:{ data: [view dataObject], frame: { origin: [view frame].origin } }]; - } - [self applyFrameChanges:changes withActionName:@"Move"]; -} - -- (void)canvasView:(UICanvasView)aCanvas didResizeElement:(UIElementView)anElement -{ - var changes = [CPMutableArray array]; - var frame = [anElement frame]; - [changes addObject:{ data: [anElement dataObject], frame: { origin: frame.origin, size: frame.size } }]; - [self applyFrameChanges:changes withActionName:@"Resize"]; -} - -- (void)canvasView:(UICanvasView)aCanvas didConnectElement:(UIElementView)sourceElement toElement:(UIElementView)targetElement asTargetAction:(CPString)actionName -{ - var sourceData = [sourceElement dataObject]; - var targetData = [targetElement dataObject]; - - // For a target-action, the outlet is typically 'target' - var outletName = @"target"; - - [self addConnectionFrom:sourceData to:targetData atPoint:nil outlet:outletName action:actionName]; -} - -- (void)canvasView:(UICanvasView)aCanvas didConnectElement:(UIElementView)sourceElement toElement:(UIElementView)targetElement asOutlet:(CPString)outletName -{ - var sourceData = [sourceElement dataObject]; - var targetData = [targetElement dataObject]; - - // For a simple outlet connection, there is no action. - var actionName = nil; - - [self addConnectionFrom:sourceData to:targetData atPoint:nil outlet:outletName action:actionName]; -} - -- (void)changeValue:(id)newValue forObject:(id)dataObject -{ - var oldValue = [dataObject valueForKey:@"value"]; - if (oldValue != newValue) - { - var undoManager = [[CPApp keyWindow] undoManager]; - [[undoManager prepareWithInvocationTarget:self] changeValue:oldValue forObject:dataObject]; - [undoManager setActionName:@"Change Value"]; - [dataObject setValue:newValue forKey:@"value"]; - } -} - -- (void)changeValueForSelectedObject:(id)newValue -{ - var selectedObjects = [[self elementsController] selectedObjects]; - if ([selectedObjects count] === 1) - { - [self changeValue:newValue forObject:selectedObjects[0]]; - } -} - -@end diff --git a/Tests/Manual/UIBuilderDemo/UICanvasView.j b/Tests/Manual/UIBuilderDemo/UICanvasView.j deleted file mode 100644 index c374e9f42..000000000 --- a/Tests/Manual/UIBuilderDemo/UICanvasView.j +++ /dev/null @@ -1,996 +0,0 @@ -// -// UICanvasView.j -// A full-window canvas for the UI Builder. -// -// By Daniel Boehringer in 2025. -// - It acts as a drag-and-drop destination for new UI elements from the palette. -// - It correctly instantiates different UIElementView subclasses based on the data model. -// - -@import "UIBuilderConstants.j"; -@import "UIElementView.j"; -@import "ConnectionView.j"; -@import "UIBuilderConstants.j"; - -function treshold(value, limit) -{ - return value > 0 ? Math.min(value, limit) : Math.max(value, -limit); -} - -@implementation UICanvasView : CPView -{ - // Data binding ivars - id _dataObjectsContainer; - CPString _dataObjectsKeyPath; - id _selectionIndexesContainer; - CPString _selectionIndexesKeyPath; - CPArray _oldDataObjects; - - // Connections ivars - id _connectionsContainer; - CPString _connectionsKeyPath; - CPArray _oldConnections; - id _selectedConnectionsContainer; - CPString _selectedConnectionsKeyPath; - - // Rubber-band selection ivars - CGPoint _rubberStart; - CGPoint _rubberEnd; - BOOL _isRubbing; - - ConnectionView _connectionView; - - id _delegate; - - // Connection Menu ivars - UIElementView _connectionSource; - UIElementView _connectionTarget; - BOOL _connectionMade; -} - --(BOOL)acceptsFirstMouse:(CPEvent)aEvent -{ - return YES; -} - -// KVO contexts -var _propertyObservationContext = 1091; -var _dataObjectsObservationContext = 1092; -var _selectionIndexesObservationContext = 1093; -var _connectionsObservationContext = 1094; -var _selectedConnectionsObservationContext = 1095; - -- (id)initWithFrame:(CGRect)aRect -{ - self = [super initWithFrame:aRect]; - - if (self) - { - // Register to accept drops from the palette - [self registerForDraggedTypes:[ - UIWindowDragType, - UIButtonDragType, - UISliderDragType, - UITextFieldDragType - ]]; - - _connectionView = [[ConnectionView alloc] initWithFrame:[self bounds]]; - [_connectionView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - [self addSubview:_connectionView]; - } - return self; -} - -#pragma mark - Bindings & KVO (Largely from EFLaceView) - -+ (void)initialize -{ - [self exposeBinding:"dataObjects"]; - [self exposeBinding:@"selectionIndexes"]; - [self exposeBinding:@"connections"]; - [self exposeBinding:@"selectedConnections"]; -} - -- (void)bind:(CPString)bindingName toObject:(id)observableObject withKeyPath:(CPString)observableKeyPath options:(CPDictionary)options -{ - if ([bindingName isEqualToString:@"dataObjects"]) - { - _dataObjectsContainer = observableObject; - _dataObjectsKeyPath = observableKeyPath; - [_dataObjectsContainer addObserver:self forKeyPath:_dataObjectsKeyPath options:(CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld) context:_dataObjectsObservationContext]; - [self startObservingDataObjects:[self dataObjects]]; - _oldDataObjects = [[self dataObjects] copy] || @[]; - } - else if ([bindingName isEqualToString:@"selectionIndexes"]) - { - _selectionIndexesContainer = observableObject; - _selectionIndexesKeyPath = observableKeyPath; - [_selectionIndexesContainer addObserver:self forKeyPath:_selectionIndexesKeyPath options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:_selectionIndexesObservationContext]; - } - else if ([bindingName isEqualToString:@"connections"]) - { - _connectionsContainer = observableObject; - _connectionsKeyPath = observableKeyPath; - [_connectionsContainer addObserver:self forKeyPath:_connectionsKeyPath options:(CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld) context:_connectionsObservationContext]; - _oldConnections = [[self connections] copy] || @[]; - } - else if ([bindingName isEqualToString:@"selectedConnections"]) - { - _selectedConnectionsContainer = observableObject; - _selectedConnectionsKeyPath = observableKeyPath; - [_selectedConnectionsContainer addObserver:self forKeyPath:_selectedConnectionsKeyPath options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:_selectedConnectionsObservationContext]; - } - else { [super bind:bindingName toObject:observableObject withKeyPath:observableKeyPath options:options]; } - - [self setNeedsDisplay:YES]; -} - -- (void)unbind:(CPString)bindingName -{ - if ([bindingName isEqualToString:@"dataObjects"]) { - [self stopObservingDataObjects:[self dataObjects]]; - [_dataObjectsContainer removeObserver:self forKeyPath:_dataObjectsKeyPath]; - _dataObjectsContainer = nil; _dataObjectsKeyPath = nil; - } else if ([bindingName isEqualToString:@"selectionIndexes"]) { - [_selectionIndexesContainer removeObserver:self forKeyPath:_selectionIndexesKeyPath]; - _selectionIndexesContainer = nil; _selectionIndexesKeyPath = nil; - } else if ([bindingName isEqualToString:@"connections"]) { - [_connectionsContainer removeObserver:self forKeyPath:_connectionsKeyPath]; - _connectionsContainer = nil; _connectionsKeyPath = nil; - } else if ([bindingName isEqualToString:@"selectedConnections"]) { - [_selectedConnectionsContainer removeObserver:self forKeyPath:_selectedConnectionsKeyPath]; - _selectedConnectionsContainer = nil; _selectedConnectionsKeyPath = nil; - } else { [super unbind:bindingName]; } - [self setNeedsDisplay:YES]; -} - -- (CPArray)dataObjects -{ - var result = [_dataObjectsContainer valueForKeyPath:_dataObjectsKeyPath]; - return (result == [CPNull null]) ? @[] : result; -} - -- (CPIndexSet)selectionIndexes -{ - return [_selectionIndexesContainer valueForKeyPath:_selectionIndexesKeyPath]; -} - -- (CPArray)connections -{ - var result = [_connectionsContainer valueForKeyPath:_connectionsKeyPath]; - return (result == [CPNull null]) ? @[] : result; -} - -- (CPArray)selectedConnections -{ - var result = [_selectedConnectionsContainer valueForKeyPath:_selectedConnectionsKeyPath]; - return (result == [CPNull null]) ? @[] : result; -} - -- (void)setSelectionIndexes:(CPIndexSet)indexes -{ - [_selectionIndexesContainer setValue:indexes forKeyPath:_selectionIndexesKeyPath]; -} - -- (void)startObservingDataObjects:(CPArray)dataObjects -{ - if (!dataObjects || dataObjects == [CPNull null]) - return; - - for (var i = 0; i < [dataObjects count]; i++) - { - var newDataObject = dataObjects[i]; - // Only create views for top-level objects. Children are handled by their parents. - if (![newDataObject valueForKey:@"parentID"]) - [self _createViewForDataObject:newDataObject superview:self]; - } -} - -- (void)_createViewForDataObject:(CPDictionary)dataObject superview:(CPView)superview -{ - var type = [dataObject valueForKey:@"type"]; - var newView; - - // Instantiate the correct view based on the data model's 'type' - if (type === "window") - newView = [[UIWindowView alloc] init]; - else if (type === "button") - newView = [[UIButtonView alloc] init]; - else if (type === "slider") - newView = [[UISliderView alloc] init]; - else if (type === "textfield") - newView = [[UITextFieldView alloc] init]; - else - newView = [[UIElementView alloc] init]; // Fallback - - [newView setDataObject:dataObject]; - - // Bind view properties to the data model - [newView bind:@"originX" toObject:dataObject withKeyPath:@"originX" options:nil]; - [newView bind:@"originY" toObject:dataObject withKeyPath:@"originY" options:nil]; - [newView bind:@"width" toObject:dataObject withKeyPath:@"width" options:nil]; - [newView bind:@"height" toObject:dataObject withKeyPath:@"height" options:nil]; - - if (type === "window") - { - var children = [dataObject valueForKey:@"children"]; - for (var j = 0; j < [children count]; j++) - { - [self _createViewForDataObject:children[j] superview:newView]; - } - } - - [superview addSubview:newView]; - // i have no idea why this is needed, but it is to make the initial click work - [CPApp._delegate._window makeKeyAndOrderFront:self]; -} - -- (void)stopObservingDataObjects:(CPArray)dataObjects -{ - if (!dataObjects || dataObjects == [CPNull null]) return; - - var viewsToRemove = [CPMutableArray array]; - [self _findViewsForDataObjects:dataObjects inView:self foundViews:viewsToRemove]; - - for (var i = 0; i < [viewsToRemove count]; i++) { - var viewToRemove = viewsToRemove[i]; - [self _removeViewAndChildren:viewToRemove]; - } -} - -- (void)_removeViewAndChildren:(UIElementView)viewToRemove -{ - // Unbind everything before removing - [viewToRemove unbind:@"value"]; - [viewToRemove unbind:@"originX"]; - [viewToRemove unbind:@"originY"]; - [viewToRemove unbind:@"width"]; - [viewToRemove unbind:@"height"]; - - var subviews = [[viewToRemove subviews] copy]; - for (var i = 0; i < [subviews count]; i++) - { - [self _removeViewAndChildren:subviews[i]]; - } - - [viewToRemove removeFromSuperview]; -} - -- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context -{ - if (context == _dataObjectsObservationContext) - { - var newDataObjects = [object valueForKeyPath:_dataObjectsKeyPath]; - var oldDataObjects = _oldDataObjects; - - var added = [newDataObjects mutableCopy]; - [added removeObjectsInArray:oldDataObjects]; - [self startObservingDataObjects:added]; - - var removed = [oldDataObjects mutableCopy]; - [removed removeObjectsInArray:newDataObjects]; - [self stopObservingDataObjects:removed]; - - _oldDataObjects = [newDataObjects copy]; - [self setNeedsDisplay:YES]; - } - else if (context == _selectionIndexesObservationContext) - { - var allDataObjects = [self dataObjects]; - var newIndexes = [change objectForKey:CPKeyValueChangeNewKey] || [CPIndexSet indexSet]; - var oldIndexes = [change objectForKey:CPKeyValueChangeOldKey] || [CPIndexSet indexSet]; - - // Find views for newly selected objects and redraw them - var newSelectedDataObjects = [allDataObjects objectsAtIndexes:newIndexes]; - var newlySelectedViews = [CPMutableArray array]; - [self _findViewsForDataObjects:newSelectedDataObjects inView:self foundViews:newlySelectedViews]; - [newlySelectedViews makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:YES]; - - // Find views for deselected objects and redraw them, but only if those objects still exist. - var previouslySelectedViews = [CPMutableArray array]; - var oldSelectedDataObjects = [CPMutableArray array]; - var lastIndex = [oldIndexes lastIndex]; - - if (lastIndex != CPNotFound && lastIndex < [allDataObjects count]) - { - oldSelectedDataObjects = [allDataObjects objectsAtIndexes:oldIndexes]; - } - else - { - // If the indexes are out of bounds, it likely means the objects were deleted. - // We need to find the views that were associated with the old indexes another way. - // This is a tricky state to recover from. For now, we will just redraw all views. - // A more sophisticated solution might involve caching view-data relationships. - [[self subviews] makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:YES]; - return; - } - - [self _findViewsForDataObjects:oldSelectedDataObjects inView:self foundViews:previouslySelectedViews]; - [previouslySelectedViews makeObjectsPerformSelector:@selector(setNeedsDisplay:) withObject:YES]; - } - else if (context == _connectionsObservationContext) - { - var newConnections = [object valueForKeyPath:_connectionsKeyPath]; - var oldConnections = _oldConnections; - - // For now, simply redraw all connections. A more optimized approach would be to only redraw changed connections. - [self setNeedsDisplay:YES]; - _oldConnections = [newConnections copy]; - } - else if (context == _selectedConnectionsObservationContext) - { - [self setNeedsDisplay:YES]; - } -} - -#pragma mark - Drawing & Mouse - -- (void)drawRect:(CPRect)rect -{ - // === START: Infographic Drawing === - - var bounds = [self bounds]; - - // 1. Define text attributes for the infographic - var titleFont = [CPFont fontWithName:@"Helvetica-Bold" size:36]; - var subtitleFont = [CPFont fontWithName:@"Helvetica" size:18]; - var featureFont = [CPFont fontWithName:@"Helvetica" size:14]; - var watermarkColor = [CPColor colorWithCalibratedWhite:0.85 alpha:1.0]; // A light gray for the watermark effect - - var titleAttributes = @{ - CPFontAttributeName: titleFont, - CPForegroundColorAttributeName: watermarkColor - }; - var subtitleAttributes = @{ - CPFontAttributeName: subtitleFont, - CPForegroundColorAttributeName: watermarkColor - }; - var featureAttributes = @{ - CPFontAttributeName: featureFont, - CPForegroundColorAttributeName: watermarkColor - }; - - // 2. Prepare the text content - var title = @"Cappuccino JS"; - var subtitle = @"Desktop-Quality Applications in the Browser"; - var features = [ - @"• Drag-and-Drop UI Creation", - @"• Direct Manipulation: Move & Resize (Keyboard / Mouse)", - @"• Undo/Redo & Keyboard Navigation", - @"• Control-Draggin -> Target-Action & Outlet Connections", - @"• Context sensitive inspector panel", - @"• Run the 'real thing' in a separate native window", - @"• Source: https://github.com/daboe01/UIBuilder" - - ]; - - // 3. Calculate positions and draw the text, centering it on the canvas - var titleSize = [title sizeWithAttributes:titleAttributes]; - var subtitleSize = [subtitle sizeWithAttributes:subtitleAttributes]; - var totalHeight = titleSize.height + subtitleSize.height + ([features count] * 20) + 40; // Approximate total height - var currentY = (bounds.size.height - totalHeight) / 2.0; - - // Draw Title - var titlePoint = CGPointMake((bounds.size.width - titleSize.width) / 2.0, currentY); - [title drawAtPoint:titlePoint withAttributes:titleAttributes]; - currentY += titleSize.height + 10; - - // Draw Subtitle - var subtitlePoint = CGPointMake((bounds.size.width - subtitleSize.width) / 2.0, currentY); - [subtitle drawAtPoint:subtitlePoint withAttributes:subtitleAttributes]; - currentY += subtitleSize.height + 30; - - // Draw Feature List - for (var i = 0; i < [features count]; i++) { - var feature = features[i]; - var featureSize = [feature sizeWithAttributes:featureAttributes]; - var featurePoint = CGPointMake((bounds.size.width - featureSize.width) / 2.0, currentY); - [feature drawAtPoint:featurePoint withAttributes:featureAttributes]; - currentY += featureSize.height + 5; - } - - // === END: Infographic Drawing === - - // The background is drawn by the window. We only draw the rubber-band. - if (_isRubbing) - { - var rubber = CGRectUnion(CGRectMake(_rubberStart.x, _rubberStart.y, 0.1, 0.1), CGRectMake(_rubberEnd.x, _rubberEnd.y, 0.1, 0.1)); - [[[[CPColor alternateSelectedControlColor] colorWithAlphaComponent:0.2] setFill]]; - [CPBezierPath fillRect:rubber]; - [[CPColor alternateSelectedControlColor] setStroke]; - [CPBezierPath setDefaultLineWidth:1.0]; - [CPBezierPath strokeRect:rubber]; - } - - // Draw existing connections that are selected in the connections controller. - var selectedConnections = [self selectedConnections]; - - if (selectedConnections && [selectedConnections count] > 0) - { - for (var i = 0; i < [selectedConnections count]; i++) - { - var connection = [selectedConnections objectAtIndex:i]; - var sourceID = [connection valueForKey:@"sourceID"]; - var targetID = [connection valueForKey:@"targetID"]; - var sourceView = [self viewForElementWithID:sourceID]; - var targetView = [self viewForElementWithID:targetID]; - - if (sourceView && targetView) - { - var startPoint = [sourceView convertPoint:CGPointMake(CGRectGetMidX([sourceView bounds]), CGRectGetMidY([sourceView bounds])) toView:self]; - var endPoint; - var connectionPoint = [connection valueForKey:@"atPoint"]; - - if (connectionPoint) { - endPoint = CGPointMake(connectionPoint.x, connectionPoint.y); - } else { - endPoint = [targetView convertPoint:CGPointMake(CGRectGetMidX([targetView bounds]), CGRectGetMidY([targetView bounds])) toView:self]; - } - - // Draw the link with a distinct color, like blue. - [self drawLinkFrom:startPoint to:endPoint color:[CPColor blueColor]]; - } - } - } -} - -- (void)drawLinkFrom:(CGPoint)startPoint to:(CGPoint)endPoint color:(CPColor)insideColor -{ - - var dist = Math.sqrt(Math.pow(startPoint.x - endPoint.x, 2) + Math.pow(startPoint.y - endPoint.y, 2)); - - // a lace is made of an outside gray line of width 5, and a inside insideColor(ed) line of width 3 - var p0 = CGPointMake(startPoint.x, startPoint.y); - var p3 = CGPointMake(endPoint.x, endPoint.y); - - var p1 = CGPointMake(startPoint.x + treshold((endPoint.x - startPoint.x) / 2, 50), startPoint.y); - var p2 = CGPointMake(endPoint.x - treshold((endPoint.x - startPoint.x) / 2, 50), endPoint.y); - - // p0 and p1 are on the same horizontal line - // distance between p0 and p1 is set with the treshold fuction - // the same holds for p2 and p3 - - var path = [CPBezierPath bezierPath]; - [path setLineWidth:0]; - [[CPColor grayColor] set]; - [path appendBezierPathWithOvalInRect:CGRectMake(startPoint.x-2.5,startPoint.y-2.5,5,5)]; - [path fill]; - - path = [CPBezierPath bezierPath]; - [path setLineWidth:0]; - [insideColor set]; - [path appendBezierPathWithOvalInRect:CGRectMake(startPoint.x-1.5,startPoint.y-1.5,3,3)]; - [path fill]; - - path = [CPBezierPath bezierPath]; - [path setLineWidth:0]; - [[CPColor grayColor] set]; - [path appendBezierPathWithOvalInRect:CGRectMake(endPoint.x-2.5,endPoint.y-2.5,5,5)]; - [path fill]; - - path = [CPBezierPath bezierPath]; - [path setLineWidth:0]; - [insideColor set]; - [path appendBezierPathWithOvalInRect:CGRectMake(endPoint.x-1.5,endPoint.y-1.5,3,3)]; - [path fill]; - - // if the line is rather short, draw a straight line. the curve would look rather odd in this case. - if (dist < 40) - { - path = [CPBezierPath bezierPath]; - [path setLineWidth:5]; - [path moveToPoint:startPoint]; - [path lineToPoint:endPoint]; - [[CPColor grayColor] set]; - [path stroke]; - - path = [CPBezierPath bezierPath]; - [path setLineWidth:3]; - [path moveToPoint:startPoint]; - [path lineToPoint:endPoint]; - [insideColor set]; - [path stroke]; - - return; - } - - path = [CPBezierPath bezierPath]; - [path setLineWidth:5]; - [path moveToPoint:p0]; - [path curveToPoint:p3 controlPoint1:p1 controlPoint2:p2]; - [[CPColor grayColor] set]; - [path stroke]; - - path = [CPBezierPath bezierPath]; - [path setLineWidth:3]; - [path moveToPoint:p0]; - [path curveToPoint:p3 controlPoint1:p1 controlPoint2:p2]; - [insideColor set]; - [path stroke]; -} - -#pragma mark - View Lookup - -// Private recursive helper method to search the entire view hierarchy. -- (UIElementView)_findViewForElementWithID:(CPString)elementID inView:(CPView)aView -{ - // Iterate through all subviews of the current view - var subviews = [aView subviews]; - for (var i = 0; i < [subviews count]; i++) - { - var subview = subviews[i]; - - // We are only interested in UIElementView subclasses - if (![subview isKindOfClass:[UIElementView class]]) - continue; - - // 1. Check if the current subview is the one we are looking for. - if ([[subview dataObject] valueForKey:@"id"] === elementID) - { - return subview; // Found it! - } - - // 2. If not, and this subview has children, recurse into it. - // This is the key step to search inside containers like UIWindowView. - if ([[subview subviews] count] > 0) - { - var foundView = [self _findViewForElementWithID:elementID inView:subview]; - if (foundView) - { - return foundView; // Found it in a nested hierarchy. - } - } - } - - // If we've searched this entire branch and found nothing, return nil. - return nil; -} - -// Public method to start the search from the canvas itself. -- (UIElementView)viewForElementWithID:(CPString)elementID -{ - if (!elementID) - return nil; - - // Start the recursive search from the top-level canvas view. - return [self _findViewForElementWithID:elementID inView:self]; -} - -- (void)drawConnectionFrom:(CGPoint)startPoint to:(CGPoint)endPoint -{ - [_connectionView setStartPoint:startPoint]; - [_connectionView setEndPoint:endPoint]; - [_connectionView setHidden:NO]; // Ensure it's visible when drawing - [self addSubview:_connectionView]; // Bring to front - [_connectionView setNeedsDisplay:YES]; -} - -- (void)clearConnection -{ - [_connectionView setHidden:YES]; - [_connectionView setNeedsDisplay:YES]; // Request redraw to clear old line -} - -- (UIElementView)viewAtPoint:(CGPoint)aPoint -{ - return [self _findDeepestUIElementViewAtPoint:aPoint inView:self]; -} - -- (UIElementView)_findDeepestUIElementViewAtPoint:(CGPoint)aPoint inView:(CPView)currentView -{ - // Iterate through subviews in reverse order to get the topmost view - for (var i = [[currentView subviews] count] - 1; i >= 0; i--) - { - var subview = [[currentView subviews] objectAtIndex:i]; - - // Convert the point to the subview's coordinate system - var localPoint = [subview convertPoint:aPoint fromView:currentView]; - - if ([subview isKindOfClass:[UIElementView class]]) - { - if (CGRectContainsPoint([subview bounds], localPoint)) - { - // If this is a container view, recursively search its subviews - if (subview._isContainer) - { - var deepestView = [self _findDeepestUIElementViewAtPoint:localPoint inView:subview]; - - if (deepestView) - return deepestView; - } - // If not a container, or no deeper view found, return this view - return subview; - } - } - } - - return nil; -} - -- (void)mouseDown:(CPEvent)theEvent -{ - if (_connectionSource) - { - [self menuDidEndTracking:nil]; - return; - } - // A click on the canvas background starts a rubber-band selection. - [self deselectViews]; - _isRubbing = YES; - _rubberStart = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - _rubberEnd = _rubberStart; - - [CPApp setTarget:self selector:@selector(_dragOpenSpaceWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; -} - -- (void)_dragOpenSpaceWithEvent:(CPEvent)theEvent -{ - var mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - _rubberEnd = mouseLoc; - var rubberRect = CGRectUnion(CGRectMake(_rubberStart.x, _rubberStart.y, 1, 1), CGRectMake(_rubberEnd.x, _rubberEnd.y, 1, 1)); - - switch ([theEvent type]) - { - case CPLeftMouseDragged: - var indexesToSelect = [CPMutableIndexSet indexSet]; - var allDataObjects = [self dataObjects]; - for (var i = 0; i < [[self subviews] count]; i++) { - var aView = [self subviews][i]; - if ([aView isKindOfClass:[UIElementView class]] && CGRectIntersectsRect([aView frame], rubberRect)) { - var dataIndex = [allDataObjects indexOfObject:[aView dataObject]]; - if (dataIndex != CPNotFound) { - [indexesToSelect addIndex:dataIndex]; - } - } - } - [_selectionIndexesContainer setValue:indexesToSelect forKeyPath:_selectionIndexesKeyPath]; - [self setNeedsDisplay:YES]; - [CPApp setTarget:self selector:@selector(_dragOpenSpaceWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; - break; - - case CPLeftMouseUp: - _isRubbing = NO; - [self setNeedsDisplay:YES]; - break; - } -} - -- (void)delete:(id)sender -{ - // Forward the delete action to the delegate/controller - if (_delegate && [_delegate respondsToSelector:@selector(removeSelectedElements)]) { - [_delegate removeSelectedElements]; - } -} - -- (void)cut:(id)sender -{ - if (_delegate && [_delegate respondsToSelector:@selector(cut:)]) { - [_delegate cut:sender]; - } -} - -- (void)copy:(id)sender -{ - if (_delegate && [_delegate respondsToSelector:@selector(copy:)]) { - [_delegate copy:sender]; - } -} - -- (void)paste:(id)sender -{ - if (_delegate && [_delegate respondsToSelector:@selector(paste:)]) { - [_delegate paste:sender]; - } -} - -- (void)viewDidMoveToWindow -{ - [super viewDidMoveToWindow]; - - if ([self window]) - { - [[self window] makeFirstResponder:self]; - } -} - -- (BOOL)acceptsFirstResponder -{ - return YES; -} - -/* -- (BOOL)validateMenuItem:(CPMenuItem)aMenuItem -{ - var action = [aMenuItem action]; - - if (action == @selector(copy:) || action == @selector(cut:) || action == @selector(delete:)) - { - return [[self selectionIndexes] count] > 0; - } - - if (action == @selector(paste:)) - { - return [[[CPPasteboard generalPasteboard] types] containsObject:UIBuilderElementPboardType]; - } - - if (action == @selector(createTargetActionConnection:) || action == @selector(createOutletConnection:)) - { - return YES; - } - - var undoManager = [[self window] undoManager]; - - if (action == @selector(undo:)) - { - return [undoManager canUndo]; - } - - if (action == @selector(redo:)) - { - return [undoManager canRedo]; - } - - return [super validateMenuItem:aMenuItem]; -} -*/ - -- (void)keyDown:(CPEvent)theEvent -{ - var characters = [theEvent characters]; - var flags = [theEvent modifierFlags]; - var selectors = [CPKeyBinding selectorsForKey:characters modifierFlags:flags]; - var delegate = [self delegate]; - var handled = NO; - - if (selectors && delegate) - { - for (var i = 0; i < [selectors count]; i++) - { - var selectorName = selectors[i]; - if ([delegate respondsToSelector:selectorName]) - { - [delegate performSelector:selectorName withObject:self]; - handled = YES; - break; - } - } - } - - if (!handled) - [super keyDown:theEvent]; -} - -#pragma mark - Drag and Drop Destination - -- (CPDragOperation)draggingEntered:(CPDraggingInfo)sender -{ - // We accept any of the registered types - return CPDragOperationCopy; -} - -- (BOOL)performDragOperation:(CPDraggingInfo)sender -{ - var dropPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; - var pasteboard = [sender draggingPasteboard]; - var types = [pasteboard types]; - var draggedType = types[0]; // Assuming only one type is being dragged - var elementType; - - if (draggedType === UIWindowDragType) elementType = "window"; - else if (draggedType === UIButtonDragType) elementType = "button"; - else if (draggedType === UISliderDragType) elementType = "slider"; - else if (draggedType === UITextFieldDragType) elementType = "textfield"; - - if (elementType && _delegate) - { - if (elementType === "window") { - if ([_delegate respondsToSelector:@selector(addNewElementOfType:atPoint:)]) - { - [_delegate addNewElementOfType:elementType atPoint:dropPoint]; - [self setNeedsDisplay:YES]; - return YES; - } - } else { - if ([_delegate respondsToSelector:@selector(addNewElementOfType:inNewWindowAtPoint:)]) - { - [_delegate addNewElementOfType:elementType inNewWindowAtPoint:dropPoint]; - [self setNeedsDisplay:YES]; - return YES; - } - } - } - - return NO; -} - -#pragma mark - Delegate & Selection Management - -- (id)delegate { return _delegate; } -- (void)setDelegate:(id)newDelegate { _delegate = newDelegate; } - -- (void)deselectViews -{ - [_selectionIndexesContainer setValue:nil forKeyPath:_selectionIndexesKeyPath]; -} - -- (void)selectView:(UIElementView)aView state:(BOOL)select -{ - var selection = [[self selectionIndexes] mutableCopy] || [CPMutableIndexSet indexSet]; - var dataObjectIndex = [[self dataObjects] indexOfObject:[aView dataObject]]; - - - - if (dataObjectIndex != CPNotFound) - { - if (select) - [selection addIndex:dataObjectIndex]; - - else [selection removeIndex:dataObjectIndex]; - } - - [_selectionIndexesContainer setValue:selection forKeyPath:_selectionIndexesKeyPath]; -} - -- (CPArray)selectedSubViews -{ - var selectedDataObjects = [[self dataObjects] objectsAtIndexes:[self selectionIndexes]]; - var selectedViews = [CPMutableArray array]; - - [self _findViewsForDataObjects:selectedDataObjects inView:self foundViews:selectedViews]; - - return selectedViews; -} - -- (BOOL)isViewSelected:(CPView)aView -{ - var selected = [self selectedSubViews]; - - return [selected containsObject:aView]; -} - -- (void)_findViewsForDataObjects:(CPArray)dataObjects inView:(CPView)aView foundViews:(CPMutableArray)foundViews -{ - var subviews = [aView subviews]; - - for (var i = 0; i < [subviews count]; i++) - { - var subview = subviews[i]; - - // Skip the connection view and any other non-UIElementView instances - if (![subview isKindOfClass:[UIElementView class]]) - continue; - - var contains = [dataObjects containsObject:[subview dataObject]]; - - if (contains) - { - [foundViews addObject:subview]; - } - - // Recurse into subviews - [self _findViewsForDataObjects:dataObjects inView:subview foundViews:foundViews]; - } -} - -// These methods are called by the UIElementView children to notify the controller -- (void)elementDidMove:(UIElementView)anElement -{ - if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didMoveElement:)]) { - [_delegate canvasView:self didMoveElement:anElement]; - } -} - -- (void)elementDidResize:(UIElementView)anElement -{ - if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didResizeElement:)]) { - [_delegate canvasView:self didResizeElement:anElement]; - } -} - -- (void)elementDidConnect:(UIElementView)sourceElement to:(UIElementView)targetElement atPoint:(CGPoint)aPoint -{ - if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didConnectElement:toElement:atPoint:)]) { - [_delegate canvasView:self didConnectElement:sourceElement toElement:targetElement atPoint:aPoint]; - } -} - -#pragma mark - Connection Menu - -- (void)showConnectionMenuForSource:(UIElementView)sourceView target:(UIElementView)targetView at:(CGPoint)aPoint -{ - _connectionSource = sourceView; - _connectionTarget = targetView; - _connectionMade = NO; - - var menu = [[CPMenu alloc] initWithTitle:@"Connection Menu"]; - [menu setDelegate:self]; - - // 1. Add Target's Actions - var targetActions = [[_connectionTarget dataObject] valueForKey:@"actions"]; - if (targetActions && [targetActions length] > 0) - { - var actionsArray = [targetActions componentsSeparatedByString:@", "]; - for (var i = 0; i < [actionsArray count]; i++) - { - var actionName = actionsArray[i]; - var menuItem = [[CPMenuItem alloc] initWithTitle:actionName action:@selector(createTargetActionConnection:) keyEquivalent:@""]; - [menu addItem:menuItem]; - } - } - - // 2. Add Separator - if ([menu numberOfItems] > 0) - [menu addItem:[CPMenuItem separatorItem]]; - - // 3. Add Source's Outlets - var sourceOutlets = [[_connectionSource dataObject] valueForKey:@"outlets"]; - if (sourceOutlets && [sourceOutlets length] > 0) - { - var outletsArray = [sourceOutlets componentsSeparatedByString:@", "]; - for (var i = 0; i < [outletsArray count]; i++) - { - var outletName = outletsArray[i]; - if (outletName === @"target") continue; // Skip 'target' outlet as requested - var menuItem = [[CPMenuItem alloc] initWithTitle:outletName action:@selector(createOutletConnection:) keyEquivalent:@""]; - [menu addItem:menuItem]; - } - } - - if ([menu numberOfItems] > 0) - { - [CPMenu popUpContextMenu:menu withEvent:[CPApp currentEvent] forView:self]; - } - else - { - [self menuDidEndTracking:menu]; // No items, so clean up immediately - } -} - -- (void)createTargetActionConnection:(CPMenuItem)sender -{ - var actionName = [sender title]; - if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didConnectElement:toElement:asTargetAction:)]) - { - _connectionMade = YES; - [self clearConnection]; - if (_connectionTarget) - [_connectionTarget setAsDropTarget:NO]; - - [_delegate canvasView:self didConnectElement:_connectionSource toElement:_connectionTarget asTargetAction:actionName]; - } -} - -- (void)createOutletConnection:(CPMenuItem)sender -{ - var outletName = [sender title]; - if (_delegate && [_delegate respondsToSelector:@selector(canvasView:didConnectElement:toElement:asOutlet:)]) - { - _connectionMade = YES; - [self clearConnection]; - if (_connectionTarget) - [_connectionTarget setAsDropTarget:NO]; - - [_delegate canvasView:self didConnectElement:_connectionSource toElement:_connectionTarget asOutlet:outletName]; - } -} - -- (void)menuDidEndTracking:(CPMenu)aMenu -{ - // This delegate method is called after a menu item is selected OR the menu is cancelled. - if (!_connectionMade) - { - [self clearConnection]; - if (_connectionTarget) - [_connectionTarget setAsDropTarget:NO]; - } - - // Reset state - _connectionSource = nil; - _connectionTarget = nil; - _connectionMade = NO; - - [self setNeedsDisplay:YES]; -} - -@end diff --git a/Tests/Manual/UIBuilderDemo/UIElementView.j b/Tests/Manual/UIBuilderDemo/UIElementView.j deleted file mode 100644 index d220fc7fd..000000000 --- a/Tests/Manual/UIBuilderDemo/UIElementView.j +++ /dev/null @@ -1,1349 +0,0 @@ -// -// UIElementView.j by Daniel Böhringer in 2025 - -// This file is a drawing engine for a UI builder, with features such as: -// - Skeleton drawing for common UI elements (Window, Button, Slider, TextField). -// - Selection highlights. -// - Resize handles ("dimples") on selected views. -// - Mouse logic for moving and resizing elements. -// - Visual hints for drop targets (e.g., a Window accepting a Button). -// -// - -@import "UIBuilderConstants.j"; - -// --- Property Types --- -UIBString = "UIBString"; -UIBNumber = "UIBNumber"; -UIBBoolean = "UIBBoolean"; - -// --- Constants for Resizing --- -var kUIElementHandleSize = 8.0; -var kUIElementNoHandle = 0; -var kUIElementTopLeftHandle = 1; -var kUIElementTopMiddleHandle = 2; -var kUIElementTopRightHandle = 3; -var kUIElementMiddleLeftHandle = 4; -var kUIElementMiddleRightHandle = 5; -var kUIElementBottomLeftHandle = 6; -var kUIElementBottomMiddleHandle = 7; -var kUIElementBottomRightHandle = 8; - - -@class UIWindowView -@class UIButtonView -@class UISliderView -@class UITextFieldView; - -@implementation UIElementView : CPView -{ - CPMutableDictionary _stringAttributes; - id _dataObject @accessors(property=dataObject); - - // State for dragging and resizing - CGPoint _lastMouseLoc; - int _activeHandle; - BOOL _isDragTarget; // Used by subclasses (e.g. UIWindowView) - CPTrackingArea _trackingArea; - BOOL _isContainer; - BOOL _isConnecting; -} - -#pragma mark - -#pragma mark *** Class Methods *** - -+ (CPArray)persistentProperties -{ - return ["value"]; -} - -+ (CPDictionary)defaultValues -{ - return {value: "Element"}; -} - -+ (CPDictionary)propertyTypes -{ - return [CPDictionary dictionaryWithObjects:[UIBString] forKeys:["value"]]; -} - -- (id)initWithFrame:(CGRect)frame -{ - self = [super initWithFrame:frame]; - if (self) - { - _stringAttributes = [[CPMutableDictionary alloc] init]; - [_stringAttributes setObject:[CPFont boldSystemFontOfSize:12] forKey:CPFontAttributeName]; - [_stringAttributes setObject:[CPColor blackColor] forKey:CPForegroundColorAttributeName]; - - - _activeHandle = kUIElementNoHandle; - - if ([self frame].size.width < 50 || [self frame].size.height < 20) - [self setFrameSize:CGSizeMake(MAX(50, [self frame].size.width), MAX(20, [self frame].size.height))]; - - [self setNeedsDisplay:YES]; - - _trackingArea = [[CPTrackingArea alloc] initWithRect:CGRectMakeZero() - options:CPTrackingMouseMoved | CPTrackingActiveInKeyWindow | CPTrackingInVisibleRect | CPTrackingMouseEnteredAndExited - owner:self - userInfo:nil]; - [self addTrackingArea:_trackingArea]; - _isContainer = NO; - _isConnecting = NO; - } - return self; -} - -- (void)dealloc -{ - [self setDataObject:nil]; - [super dealloc]; -} - -- (void)setDataObject:(id)newDataObject -{ - var oldDataObject = [self dataObject]; - if (newDataObject != oldDataObject) - { - var properties = [[self class] persistentProperties]; - if (oldDataObject) - for (var i = 0; i < [properties count]; i++) - [oldDataObject removeObserver:self forKeyPath:properties[i]]; - - _dataObject = newDataObject; - - if (newDataObject) - { - for (var i = 0; i < [properties count]; i++) - { - var propertyName = properties[i]; - [newDataObject addObserver:self forKeyPath:propertyName options:CPKeyValueObservingOptionNew context:self]; - } - } - } -} - -- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context -{ - if (context == self) - { - // When a property on the dataObject changes, simply tell the view to redraw itself. - [self setNeedsDisplay:YES]; - } - else - { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - -- (BOOL)acceptsFirstMouse -{ - // This view should accept first mouse events for interaction. - return YES; -} - -- (void)removeFromSuperview -{ - // This is the correct place to clean up view-related resources. - // When the view is removed from its superview, we no longer need to track - // mouse events within its bounds. - [self removeTrackingArea:_trackingArea]; - - // It's crucial to call the superclass's implementation at the end. - [super removeFromSuperview]; -} - -#pragma mark - -#pragma mark *** Geometry Accessors (for KVC Binding) *** - -- (float)originX -{ - return [self frame].origin.x; -} - -- (void)setOriginX:(float)aFloat -{ - // Only update if the value has actually changed. - if (aFloat !== [self originX]) - { - var frame = [self frame]; - frame.origin.x = aFloat; - [self setFrame:frame]; - - // Notify the superview (the canvas) that it might need to redraw - // if anything depends on this view's position. - [[self superview] setNeedsDisplay:YES]; - } -} - -- (float)originY -{ - return [self frame].origin.y; -} - -- (void)setOriginY:(float)aFloat -{ - if (aFloat !== [self originY]) - { - var frame = [self frame]; - frame.origin.y = aFloat; - [self setFrame:frame]; - [[self superview] setNeedsDisplay:YES]; - } -} - -- (float)width -{ - return [self frame].size.width; -} - -- (void)setWidth:(float)aFloat -{ - if (aFloat !== [self width]) - { - var frame = [self frame]; - // Enforce a minimum width to prevent rendering issues. - frame.size.width = MAX(aFloat, 20.0); - [self setFrame:frame]; - [[self superview] setNeedsDisplay:YES]; - } -} - -- (float)height -{ - return [self frame].size.height; -} - -- (void)setHeight:(float)aFloat -{ - if (aFloat !== [self height]) - { - var frame = [self frame]; - // Enforce a minimum height. - frame.size.height = MAX(aFloat, 20.0); - [self setFrame:frame]; - [[self superview] setNeedsDisplay:YES]; - } -} - -#pragma mark - -#pragma mark *** Accessors *** - -- (id)value -{ - return ([self dataObject] == nil) ? @"" : [[self dataObject] valueForKey:@"value"]; -} - -// You will need a way to get a reference to the canvas. -// This is often done by walking up the superview chain. -- (UICanvasView)canvas -{ - var aView = self; - while (aView = [aView superview]) { - if ([aView isKindOfClass:[UICanvasView class]]) - return aView; - } - return nil; -} - -#pragma mark - -#pragma mark *** Drawing *** - -- (void)drawRect:(CGRect)rect -{ - // 1. Draw the specific skeleton for the element subclass - [self drawSkeleton:rect]; - - // 2. If this view is a drop target, draw a highlight - if (_isDragTarget) - { - [[[CPColor redColor] colorWithAlphaComponent:0.8] setStroke]; - var highlightPath = [CPBezierPath bezierPathWithRect:CGRectInset([self bounds], 1, 1)]; - [highlightPath setLineWidth:2.0]; - [highlightPath stroke]; - } - - // 3. If selected, draw selection outline and resize handles - if ([self isSelected]) - { - // Draw selection highlight - [[CPColor keyboardFocusIndicatorColor] setStroke]; - var selectionPath = [CPBezierPath bezierPathWithRect:CGRectInset([self bounds], -2, -2)]; - [selectionPath setLineWidth:1.0]; - [selectionPath stroke]; - - // Draw resize handles ("dimples") - [self drawHandles]; - } -} - -- (void)drawSkeleton:(CGRect)rect -{ - // Base implementation: a simple placeholder box. - // Subclasses should override this to draw their specific look. - var bounds = [self bounds]; - [[CPColor lightGrayColor] setFill]; - [CPBezierPath fillRect:bounds]; - [[CPColor darkGrayColor] setStroke]; - [CPBezierPath strokeRect:bounds]; - - var valueSize = [[self value] sizeWithAttributes:_stringAttributes]; - [[self value] drawAtPoint:CGPointMake((bounds.size.width - valueSize.width) / 2.0, (bounds.size.height - valueSize.height) / 2.0) withAttributes:_stringAttributes]; -} - -- (CGRect)rectForHandle:(int)handle -{ - var bounds = [self bounds]; - var x, y; - - // Top Row - if (handle >= kUIElementTopLeftHandle && handle <= kUIElementTopRightHandle) - y = bounds.origin.y - kUIElementHandleSize / 2.0; - // Middle Row - if (handle === kUIElementMiddleLeftHandle || handle === kUIElementMiddleRightHandle) - y = bounds.origin.y + bounds.size.height / 2.0 - kUIElementHandleSize / 2.0; - // Bottom Row - if (handle >= kUIElementBottomLeftHandle && handle <= kUIElementBottomRightHandle) - y = bounds.origin.y + bounds.size.height - kUIElementHandleSize / 2.0; - - // Left Column - if (handle === kUIElementTopLeftHandle || handle === kUIElementMiddleLeftHandle || handle === kUIElementBottomLeftHandle) - x = bounds.origin.x - kUIElementHandleSize / 2.0; - // Center Column - if (handle === kUIElementTopMiddleHandle || handle === kUIElementBottomMiddleHandle) - x = bounds.origin.x + bounds.size.width / 2.0 - kUIElementHandleSize / 2.0; - // Right Column - if (handle === kUIElementTopRightHandle || handle === kUIElementMiddleRightHandle || handle === kUIElementBottomRightHandle) - x = bounds.origin.x + bounds.size.width - kUIElementHandleSize / 2.0; - - return CGRectMake(x, y, kUIElementHandleSize, kUIElementHandleSize); -} - -- (void)drawHandles -{ - [[CPColor controlDarkShadowColor] setFill]; - for (var i = 1; i <= 8; i++) - { - [CPBezierPath fillRect:[self rectForHandle:i]]; - } -} - -- (BOOL)isSelected -{ - return [[self canvas] isViewSelected:self]; -} - -#pragma mark - -#pragma mark *** Mouse Handling & Resizing *** - -- (int)handleAtPoint:(CGPoint)aPoint -{ - if (![self isSelected]) return kUIElementNoHandle; - - for (var i = 1; i <= 8; i++) - { - if (CGRectContainsPoint([self rectForHandle:i], aPoint)) - return i; - } - return kUIElementNoHandle; -} - -- (void)rightMouseDown:(CPEvent)theEvent -{ - [self mouseDown:theEvent]; -} -- (void)rightMouseUp:(CPEvent)theEvent -{ - [self mouseUp:theEvent]; -} - -- (void)mouseDown:(CPEvent)theEvent -{ - var canvas = [self canvas]; - var localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - - _lastMouseLoc = [[self canvas] convertPoint:[theEvent locationInWindow] fromView:nil]; - - // First, check if we clicked a resize handle - _activeHandle = [self handleAtPoint:localPoint]; - - // No handle was clicked, proceed with selection and movement logic - if ([theEvent modifierFlags] & CPShiftKeyMask) - { - [canvas selectView:self state:YES]; - } - else if ([theEvent modifierFlags] & CPCommandKeyMask) - { - [canvas selectView:self state:![self isSelected]]; - } - else if (![self isSelected]) - { - [canvas deselectViews]; - [canvas selectView:self state:YES]; - } -} - - - -- (void)mouseDragged:(CPEvent)theEvent -{ - var canvas = [self canvas]; - var mouseLoc = [canvas convertPoint:[theEvent locationInWindow] fromView:nil]; - - // If _lastMouseLoc is null, it means the drag started outside this view, - // so we initialize it with the current mouse location to prevent errors. - if (!_lastMouseLoc) { - _lastMouseLoc = mouseLoc; - } - - if ([theEvent modifierFlags] & CPControlKeyMask) - { - _isConnecting = YES; - // If control key is pressed, handle connection drawing - var startPointInView = CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds])); - var startPointInCanvas = [self convertPoint:startPointInView toView:canvas]; - - var canvasSubviews = [canvas subviews]; - for (var k = 0; k < [canvasSubviews count]; k++) { - var subview = [canvasSubviews objectAtIndex:k]; - if ([subview isKindOfClass:[UIElementView class]]) { - [subview setAsDropTarget:NO]; - } - } - var targetView = [canvas viewAtPoint:mouseLoc]; - - if (targetView && targetView != self) - { - var localPoint = [targetView convertPoint:mouseLoc fromView:canvas]; - if ([targetView canAcceptConnectionAtPoint:localPoint]) - { - var endPointInView = CGPointMake(CGRectGetMidX([targetView bounds]), CGRectGetMidY([targetView bounds])); - var endPointInCanvas = [targetView convertPoint:endPointInView toView:canvas]; - [canvas drawConnectionFrom:startPointInCanvas to:endPointInCanvas]; - [targetView setAsDropTarget:YES]; - } - else - { - [canvas drawConnectionFrom:startPointInCanvas to:mouseLoc]; - } - } - else - { - [canvas drawConnectionFrom:startPointInCanvas to:mouseLoc]; - } - } - else if (_activeHandle != kUIElementNoHandle) - { - // Resize logic - var sView = [self superview]; - var deltaX = mouseLoc.x - _lastMouseLoc.x; - var deltaY = mouseLoc.y - _lastMouseLoc.y; - - var frame = [self frame]; - var minSize = CGSizeMake(2 * kUIElementHandleSize, 2 * kUIElementHandleSize); - - // Left handles - if (_activeHandle === kUIElementTopLeftHandle || _activeHandle === kUIElementMiddleLeftHandle || _activeHandle === kUIElementBottomLeftHandle) { - if (frame.size.width - deltaX > minSize.width) { - frame.origin.x += deltaX; - frame.size.width -= deltaX; - } - } - // Right handles - if (_activeHandle === kUIElementTopRightHandle || _activeHandle === kUIElementMiddleRightHandle || _activeHandle === kUIElementBottomRightHandle) { - if (frame.size.width + deltaX > minSize.width) { - frame.size.width += deltaX; - } - } - // Top handles - if (_activeHandle === kUIElementTopLeftHandle || _activeHandle === kUIElementTopMiddleHandle || _activeHandle === kUIElementTopRightHandle) { - if (frame.size.height - deltaY > minSize.height) { - frame.origin.y += deltaY; - frame.size.height -= deltaY; - } - } - // Bottom handles - if (_activeHandle === kUIElementBottomLeftHandle || _activeHandle === kUIElementBottomMiddleHandle || _activeHandle === kUIElementBottomRightHandle) { - if (frame.size.height + deltaY > minSize.height) { - frame.size.height += deltaY; - } - } - - [self setFrame:frame]; - - _lastMouseLoc = mouseLoc; - [canvas setNeedsDisplay:YES]; - } - else - { - // This is the move logic, largely from the original EFView. - [[CPCursor closedHandCursor] set]; - var deltaX = mouseLoc.x - _lastMouseLoc.x; - var deltaY = mouseLoc.y - _lastMouseLoc.y; - - for (var i = 0; i < [[canvas selectedSubViews] count]; i++) - { - var view = [canvas selectedSubViews][i]; - var newOrigin = CGPointMake([view frame].origin.x + deltaX, [view frame].origin.y + deltaY); - - var parentView = [view superview]; - if ([parentView isKindOfClass:[UIWindowView class]]) - { - var parentBounds = [parentView bounds]; - var viewFrame = [view frame]; - newOrigin.x = MAX(0, MIN(newOrigin.x, parentBounds.size.width - viewFrame.size.width)); - newOrigin.y = MAX(0, MIN(newOrigin.y, parentBounds.size.height - viewFrame.size.height)); - } - - [view setFrameOrigin:newOrigin]; - } - - _lastMouseLoc = mouseLoc; - [canvas setNeedsDisplay:YES]; - } -} - -- (void)mouseUp:(CPEvent)theEvent -{ - var canvas = [self canvas]; - var mouseLoc = [canvas convertPoint:[theEvent locationInWindow] fromView:nil]; - - if (_isConnecting) - { - // Handle mouse up for connection - var targetView = [canvas viewAtPoint:mouseLoc]; - - if (targetView && targetView != self) - { - var localPoint = [targetView convertPoint:mouseLoc fromView:canvas]; - if ([targetView canAcceptConnectionAtPoint:localPoint]) - { - [canvas showConnectionMenuForSource:self target:targetView at:mouseLoc]; - } - else - { - [canvas clearConnection]; - } - } - else - { - [canvas clearConnection]; - } - - var canvasSubviews = [canvas subviews]; - - for (var k = 0; k < [canvasSubviews count]; k++) { - var subview = [canvasSubviews objectAtIndex:k]; - if ([subview isKindOfClass:[UIElementView class]] && subview != targetView) { - [subview setAsDropTarget:NO]; - } - } - [canvas setNeedsDisplay:YES]; - _isConnecting = NO; - } - else if (_activeHandle != kUIElementNoHandle) - { - // Handle mouse up for resize - [[CPCursor arrowCursor] set]; - _activeHandle = kUIElementNoHandle; - _lastMouseLoc = null; - [canvas setNeedsDisplay:YES]; - [canvas elementDidResize:self]; - } - else - { - // Handle mouse up for move - [[CPCursor openHandCursor] set]; - _lastMouseLoc = null; - [canvas setNeedsDisplay:YES]; - [canvas elementDidMove:self]; - } -} - -- (void)_resizeWithEvent:(CPEvent)theEvent -{ - var sView = [self superview]; - var canvas = [self canvas]; - var mouseLoc; - - switch ([theEvent type]) - { - case CPLeftMouseDragged: - [[CPCursor crosshairCursor] set]; // A generic resize cursor - mouseLoc = [sView convertPoint:[theEvent locationInWindow] fromView:nil]; - var deltaX = mouseLoc.x - _lastMouseLoc.x; - var deltaY = mouseLoc.y - _lastMouseLoc.y; - - var frame = [self frame]; - var minSize = CGSizeMake(2 * kUIElementHandleSize, 2 * kUIElementHandleSize); - - // Left handles - if (_activeHandle === kUIElementTopLeftHandle || _activeHandle === kUIElementMiddleLeftHandle || _activeHandle === kUIElementBottomLeftHandle) { - if (frame.size.width - deltaX > minSize.width) { - frame.origin.x += deltaX; - frame.size.width -= deltaX; - } - } - // Right handles - if (_activeHandle === kUIElementTopRightHandle || _activeHandle === kUIElementMiddleRightHandle || _activeHandle === kUIElementBottomRightHandle) { - if (frame.size.width + deltaX > minSize.width) { - frame.size.width += deltaX; - } - } - // Top handles - if (_activeHandle === kUIElementTopLeftHandle || _activeHandle === kUIElementTopMiddleHandle || _activeHandle === kUIElementTopRightHandle) { - if (frame.size.height - deltaY > minSize.height) { - frame.origin.y += deltaY; - frame.size.height -= deltaY; - } - } - // Bottom handles - if (_activeHandle === kUIElementBottomLeftHandle || _activeHandle === kUIElementBottomMiddleHandle || _activeHandle === kUIElementBottomRightHandle) { - if (frame.size.height + deltaY > minSize.height) { - frame.size.height += deltaY; - } - } - - [self setFrame:frame]; - - _lastMouseLoc = mouseLoc; - [canvas setNeedsDisplay:YES]; - [CPApp setTarget:self selector:@selector(_resizeWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; - break; - case CPLeftMouseUp: - [[CPCursor arrowCursor] set]; - _activeHandle = kUIElementNoHandle; - _lastMouseLoc = null; - [canvas setNeedsDisplay:YES]; - [canvas elementDidResize:self]; - break; - } -} - -- (void)setAsDropTarget:(BOOL)isTarget -{ - if (_isDragTarget !== isTarget) - { - _isDragTarget = isTarget; - [self setNeedsDisplay:YES]; - } -} - -- (void)_connectWithEvent:(CPEvent)theEvent -{ - var canvas = [self canvas]; - var mouseLoc = [canvas convertPoint:[theEvent locationInWindow] fromView:nil]; - - // Convert the start point (center of the view) to the canvas's coordinate system - var startPointInView = CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds])); - var startPointInCanvas = [self convertPoint:startPointInView toView:canvas]; - - var canvasSubviews = [canvas subviews]; - for (var k = 0; k < [canvasSubviews count]; k++) { - var subview = [canvasSubviews objectAtIndex:k]; - if ([subview isKindOfClass:[UIElementView class]]) { - [subview setAsDropTarget:NO]; - } - } - var targetView = [canvas viewAtPoint:mouseLoc]; - var validTargetFound = NO; - var endPointForDrawing = mouseLoc; // Default to follow mouse - - if (targetView && targetView != self) - { - if ([targetView isKindOfClass:[UIWindowView class]]) - { - validTargetFound = YES; - // Snap to center of the window for drawing feedback - endPointForDrawing = CGPointMake(CGRectGetMidX([targetView bounds]), CGRectGetMidY([targetView bounds])); - endPointForDrawing = [targetView convertPoint:endPointForDrawing toView:canvas]; - } - else - { - // For non-window elements, allow connection anywhere on their bounds - validTargetFound = YES; - endPointForDrawing = mouseLoc; // Follow mouse for other elements during drag - } - } - - if ([theEvent type] == CPLeftMouseDragged) - { - if (validTargetFound) - { - [canvas drawConnectionFrom:startPointInCanvas to:endPointForDrawing]; - [targetView setAsDropTarget:YES]; - } - else - { - [canvas drawConnectionFrom:startPointInCanvas to:mouseLoc]; - } - } - else if ([theEvent type] == CPLeftMouseUp) - { - // For final connection, snap to center of non-window elements, or title bar for windows - var finalEndPoint = mouseLoc; - var currentValidTarget = validTargetFound; // Store the initial state - - if (targetView && targetView != self) { - finalEndPoint = CGPointMake(CGRectGetMidX([targetView bounds]), CGRectGetMidY([targetView bounds])); - finalEndPoint = [targetView convertPoint:finalEndPoint toView:canvas]; - } else { - currentValidTarget = NO; // No valid target or target is self - } - - if (currentValidTarget) { - [[self canvas] elementDidConnect:self to:targetView atPoint:finalEndPoint]; // Pass finalEndPoint - } - [[self canvas] clearConnection]; - var canvasSubviews = [canvas subviews]; - for (var k = 0; k < [canvasSubviews count]; k++) { - var subview = [canvasSubviews objectAtIndex:k]; - if ([subview isKindOfClass:[UIElementView class]]) { - [subview setAsDropTarget:NO]; - } - } - } -} - -- (void)mouseEntered:(CPEvent)theEvent -{ - [[CPCursor openHandCursor] set]; -} - -- (void)mouseExited:(CPEvent)theEvent -{ - [[CPCursor arrowCursor] set]; -} - -- (void)mouseMoved:(CPEvent)theEvent -{ - var localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - var handle = [self handleAtPoint:localPoint]; - - if (handle != kUIElementNoHandle) { - // In a full implementation, you could return a specific two-headed arrow cursor - // based on the handle. For now, we use a generic one. - [[CPCursor crosshairCursor] set]; - } else { - [[CPCursor openHandCursor] set]; - } -} - -- (id)nativeUIElement -{ - return [self nativeUIElementWithMap:nil]; -} - -- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap -{ - // Base implementation returns a generic view with a red background to indicate it's not a real UI element. - var view = [[CPView alloc] initWithFrame:[self frame]]; - [view setBackgroundColor:[CPColor redColor]]; - - if (aMap) - { - var elementID = [[self dataObject] valueForKey:@"id"]; - [aMap setObject:view forKey:elementID]; - } - - return view; -} - -- (BOOL)canAcceptConnectionAtPoint:(CGPoint)aPoint -{ - // By default, any part of the view can be a connection target. - return YES; -} - -@end - - -#pragma mark - -#pragma mark *** UI Element Subclasses *** - -// ================================================================================================= -// UIWindowView -// A skeleton that looks like a window, and can act as a drop target. -// ================================================================================================= - -var _windowChildrenObservationContext = 1094; - -@implementation UIWindowView : UIElementView -{ - CGPoint _rubberStart; - CGPoint _rubberEnd; - BOOL _isRubbing; -} - -+ (CPDictionary)propertyTypes -{ - var types = [super propertyTypes]; - [types setObject:UIBBoolean forKey:@"CPHUDBackgroundWindowMask"]; - [types setObject:UIBBoolean forKey:@"CPTitledWindowMask"]; - [types setObject:UIBBoolean forKey:@"CPClosableWindowMask"]; - return types; -} - -+ (CPArray)persistentProperties -{ - return [super persistentProperties].concat(["CPHUDBackgroundWindowMask", "CPTitledWindowMask", "CPClosableWindowMask"]); -} - -+ (CPDictionary)defaultValues -{ - return { - value: "Untitled Window", - CPHUDBackgroundWindowMask: true, - CPTitledWindowMask: true, - CPClosableWindowMask: true, - outlets: "delegate", - actions: "makeKeyAndOrderFront:, orderOut:" - }; -} - -- (void)drawRect:(CGRect)rect -{ - [super drawRect:rect]; - - if (_isRubbing) - { - var rubber = CGRectUnion(CGRectMake(_rubberStart.x, _rubberStart.y, 0.1, 0.1), CGRectMake(_rubberEnd.x, _rubberEnd.y, 0.1, 0.1)); - [[[[CPColor alternateSelectedControlColor] colorWithAlphaComponent:0.2] setFill]]; - [CPBezierPath fillRect:rubber]; - [[CPColor alternateSelectedControlColor] setStroke]; - [CPBezierPath setDefaultLineWidth:1.0]; - [CPBezierPath strokeRect:rubber]; - } -} - -- (void)mouseDown:(CPEvent)theEvent -{ - var localPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - var titleBarHeight = 30.0; - - // 1. Check for resize handle click first. - if ([self handleAtPoint:localPoint] != kUIElementNoHandle) { - [super mouseDown:theEvent]; - return; - } - - // 2. Check if the click is within the title bar area. - if (localPoint.y <= titleBarHeight) { - // Click is in the title bar. Allow the superclass to handle moving the window. - [super mouseDown:theEvent]; - return; - } - - // On a click into the window's content area, deselect all elements. - [[self canvas] deselectViews]; - - _rubberStart = localPoint; - _rubberEnd = _rubberStart; - _isRubbing = YES; - [CPApp setTarget:self selector:@selector(_dragOpenSpaceWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; -} - -- (void)_dragOpenSpaceWithEvent:(CPEvent)theEvent -{ - var canvas = [self canvas]; - var mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - _rubberEnd = mouseLoc; - var rubberRect = CGRectUnion(CGRectMake(_rubberStart.x, _rubberStart.y, 1, 1), CGRectMake(_rubberEnd.x, _rubberEnd.y, 1, 1)); - - switch ([theEvent type]) - { - case CPLeftMouseDragged: - var indexesToSelect = [CPMutableIndexSet indexSet]; - var allDataObjects = [canvas dataObjects]; - - for (var i = 0; i < [[self subviews] count]; i++) { - var aView = [self subviews][i]; - if (CGRectIntersectsRect([aView frame], rubberRect)) { - var dataIndex = [allDataObjects indexOfObject:[aView dataObject]]; - if (dataIndex != CPNotFound) { - [indexesToSelect addIndex:dataIndex]; - } - } - } - [canvas setSelectionIndexes:indexesToSelect]; - [self setNeedsDisplay:YES]; - [CPApp setTarget:self selector:@selector(_dragOpenSpaceWithEvent:) forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask untilDate:nil inMode:nil dequeue:YES]; - break; - - case CPLeftMouseUp: - _isRubbing = NO; - [self setNeedsDisplay:YES]; - break; - } -} - -- (void)dealloc -{ - [self setDataObject:nil]; - [super dealloc]; -} - -- (void)setDataObject:(id)newDataObject -{ - var oldDataObject = [self dataObject]; - - if (newDataObject != oldDataObject) - { - if (oldDataObject) - [oldDataObject removeObserver:self forKeyPath:@"children" context:_windowChildrenObservationContext]; - - [super setDataObject:newDataObject]; - - if (newDataObject) - { - [newDataObject addObserver:self forKeyPath:@"children" options:CPKeyValueObservingOptionNew | CPKeyValueObservingOptionOld context:_windowChildrenObservationContext]; - [self _addChildrenViews:[newDataObject valueForKey:@"children"]]; - } - } -} - -- (void)_addChildrenViews:(CPArray)childDataObjects -{ - if (!childDataObjects) return; - - var canvas = [self superview]; - - for (var i = 0; i < [childDataObjects count]; i++) - { - var childData = childDataObjects[i]; - // This is a bit of a hack. We are reaching into the canvas's private method. - // A better solution would be a dedicated ViewFactory or similar. - if ([canvas respondsToSelector:@selector(_createViewForDataObject:superview:)]) - [canvas _createViewForDataObject:childData superview:self]; - } -} - -- (void)_removeChildrenViews:(CPArray)childDataObjects -{ - if (!childDataObjects) return; - - var canvas = [self superview]; - var viewsToRemove = []; - var subviews = [self subviews]; - - for (var i = 0; i < [subviews count]; i++) - { - var subview = subviews[i]; - if ([childDataObjects containsObject:[subview dataObject]]) - [viewsToRemove addObject:subview]; - } - - for (i = 0; i < [viewsToRemove count]; i++) - { - if ([canvas respondsToSelector:@selector(_removeViewAndChildren:)]) - [canvas _removeViewAndChildren:viewsToRemove[i]]; - } -} - - -- (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(id)context -{ - if (context == _windowChildrenObservationContext) - { - var oldChildren = [change objectForKey:CPKeyValueChangeOldKey]; - var newChildren = [change objectForKey:CPKeyValueChangeNewKey]; - - var added = [newChildren mutableCopy]; - [added removeObjectsInArray:oldChildren]; - [self _addChildrenViews:added]; - - var removed = [oldChildren mutableCopy]; - [removed removeObjectsInArray:newChildren]; - [self _removeChildrenViews:removed]; - } - else - { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - - -- (id)initWithFrame:(CGRect)aRect -{ - self = [super initWithFrame:aRect]; - if (self) { - - if (CGRectIsEmpty(aRect)) { - [self setFrameSize:CGSizeMake(250, 200)]; - } - _isContainer = YES; - - // This view can accept drops of other elements. - [self registerForDraggedTypes:[ - UIButtonDragType, - UISliderDragType, - UITextFieldDragType - ]]; - } - return self; -} - -- (void)drawSkeleton:(CGRect)rect -{ - var bounds = [self bounds]; - var titleBarHeight = 22.0; - - // Main window background - [[[CPColor windowBackgroundColor] colorWithAlphaComponent:0.9] setFill]; - var bgPath = [CPBezierPath bezierPathWithRoundedRect:bounds radius:6.0]; - [bgPath fill]; - - // Title bar - var titleBarRect = CGRectMake(bounds.origin.x, bounds.origin.y, bounds.size.width, titleBarHeight); - var titleBarPath = [CPBezierPath bezierPathWithRoundedRect:titleBarRect xRadius:6.0 yRadius:6.0]; - [[[CPColor secondarySelectedControlColor] colorWithAlphaComponent:0.6] setFill]; - [titleBarPath fill]; - - // Window border - [[CPColor darkGrayColor] setStroke]; - [bgPath setLineWidth:1.0]; - [bgPath stroke]; - - // Value text - [_stringAttributes setObject:[CPColor whiteColor] forKey:CPForegroundColorAttributeName]; - var valueSize = [[self value] sizeWithAttributes:_stringAttributes]; - [[self value] drawAtPoint:CGPointMake((bounds.size.width - valueSize.width) / 2.0, (titleBarHeight - valueSize.height) / 2.0 - 4) withAttributes:_stringAttributes]; - [_stringAttributes setObject:[CPColor blackColor] forKey:CPForegroundColorAttributeName]; // reset color - - // Traffic light buttons - var circleRadius = 5.0; - var startX = 10.0; - var startY = titleBarHeight / 2.0; - [[CPColor redColor] setFill]; - [CPBezierPath fillRect:CGRectMake(startX, startY - circleRadius, circleRadius*2, circleRadius*2)]; - [[CPColor orangeColor] setFill]; - [CPBezierPath fillRect:CGRectMake(startX + 18, startY - circleRadius, circleRadius*2, circleRadius*2)]; - [[CPColor greenColor] setFill]; - [CPBezierPath fillRect:CGRectMake(startX + 36, startY - circleRadius, circleRadius*2, circleRadius*2)]; -} - -// --- Drag Destination Methods --- - -- (CPDragOperation)draggingEntered:(CPDraggingInfo)sender -{ - var pasteboard = [sender draggingPasteboard]; - var acceptedTypes = [self registeredDraggedTypes]; - var localPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; - var titleBarHeight = 30.0; - - // Check if the dragged type is a new UI element (from the palette) - if ([acceptedTypes containsObject:UIWindowDragType] || [acceptedTypes containsObject:UIButtonDragType] || [acceptedTypes containsObject:UISliderDragType] || [acceptedTypes containsObject:UITextFieldDragType]) - { - _isDragTarget = YES; - [self setNeedsDisplay:YES]; - return CPDragOperationGeneric; - } - // Check if it's a connection drag (control key is pressed) - else if ([sender draggingSourceOperationMask] & CPControlKeyMask && localPoint.y <= titleBarHeight) - - { - debugger - _isDragTarget = YES; - [self setNeedsDisplay:YES]; - return CPDragOperationGeneric; - } - - return CPDragOperationNone; -} - -- (CPDragOperation)draggingUpdated:(CPDraggingInfo)sender -{ - var localPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; - var titleBarHeight = 30.0; - var acceptedTypes = [self registeredDraggedTypes]; - - // Check if the dragged type is a new UI element (from the palette) - if ([acceptedTypes containsObject:UIButtonDragType] || [acceptedTypes containsObject:UISliderDragType] || [acceptedTypes containsObject:UITextFieldDragType]) - { - _isDragTarget = YES; - [self setNeedsDisplay:YES]; - return CPDragOperationGeneric; - } - // Check if it's a connection drag (control key is pressed) - else if ([sender draggingSourceOperationMask] & CPControlKeyMask && localPoint.y <= titleBarHeight) - { - _isDragTarget = YES; - [self setNeedsDisplay:YES]; - return CPDragOperationGeneric; - } - else - { - _isDragTarget = NO; - [self setNeedsDisplay:YES]; - return CPDragOperationNone; - } -} - -- (void)draggingExited:(CPDraggingInfo)sender -{ - _isDragTarget = NO; - [self setNeedsDisplay:YES]; -} - -- (BOOL)performDragOperation:(CPDraggingInfo)sender -{ - var dropPoint = [self convertPoint:[sender draggingLocation] fromView:nil]; - var pasteboard = [sender draggingPasteboard]; - var types = [pasteboard types]; - var draggedType = types[0]; - var elementType; - - // Determine if it's a new UI element drop - if (draggedType === UIButtonDragType) elementType = "button"; - else if (draggedType === UISliderDragType) elementType = "slider"; - else if (draggedType === UITextFieldDragType) elementType = "textfield"; - - if (elementType) - { - // We need to find the canvas and then the delegate - var canvas = [self superview]; - var delegate = [canvas delegate]; - if (delegate && [delegate respondsToSelector:@selector(addNewElementOfType:atPoint:)]) - { - var canvasPoint = [self convertPoint:dropPoint toView:canvas]; - [delegate addNewElementOfType:elementType atPoint:canvasPoint]; - } - } - // If it's a connection drag, the logic is handled in _connectWithEvent: in UIElementView - - _isDragTarget = NO; - [self setNeedsDisplay:YES]; - - return YES; -} - -- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap -{ - var newPlatformWindow = [[CPPlatformWindow alloc] initWithContentRect:[self frame]]; - - var styleMask = 0; - if ([[self dataObject] valueForKey:@"CPHUDBackgroundWindowMask"]) styleMask |= CPHUDBackgroundWindowMask; - if ([[self dataObject] valueForKey:@"CPTitledWindowMask"]) styleMask |= CPTitledWindowMask; - if ([[self dataObject] valueForKey:@"CPClosableWindowMask"]) styleMask |= CPClosableWindowMask; - - var theNewWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, [self frame].size.width, [self frame].size.height) styleMask:styleMask]; - [theNewWindow setPlatformWindow:newPlatformWindow]; - - if (aMap) - { - var elementID = [[self dataObject] valueForKey:@"id"]; - [aMap setObject:theNewWindow forKey:elementID]; - } - - var contentView = [theNewWindow contentView]; - var subviews = [self subviews]; - for (var i = 0; i < [subviews count]; i++) - { - var subview = subviews[i]; - var nativeSubview = [subview nativeUIElementWithMap:aMap]; - [contentView addSubview:nativeSubview]; - } - - return theNewWindow; -} - -- (BOOL)canAcceptConnectionAtPoint:(CGPoint)aPoint -{ - var titleBarHeight = 22.0; - return aPoint.y <= titleBarHeight; -} - -@end - - -// ================================================================================================= -// UIButtonView -// A skeleton that looks like a push button. -// ================================================================================================= -@implementation UIButtonView : UIElementView - -+ (CPDictionary)defaultValues -{ - return {value: "Button", outlets: "target, delegate", actions: "takeValueFrom:"}; -} - -+ (CPDictionary)propertyTypes -{ - return [super propertyTypes].copy({value: UIBString}); -} -- (id)initWithFrame:(CGRect)aRect -{ - self = [super initWithFrame:aRect]; - if (self) { - if (CGRectIsEmpty(aRect)) { - [self setFrameSize:CGSizeMake(100, 24)]; - } - } - return self; -} - -- (void)drawSkeleton:(CGRect)rect -{ - var bounds = CGRectInset([self bounds], 1, 1); - - // Draw button shape with gradient - var buttonPath = [CPBezierPath bezierPathWithRoundedRect:bounds radius:5.0]; - var gradient = [[CPGradient alloc] initWithStartingColor:[CPColor whiteColor] - endingColor:[CPColor controlColor]]; - [gradient drawInBezierPath:buttonPath angle:90]; - - // Draw button border - [[CPColor grayColor] setStroke]; - [buttonPath setLineWidth:1.0]; - [buttonPath stroke]; - - // Draw value - var valueSize = [[self value] sizeWithAttributes:_stringAttributes]; - [[self value] drawAtPoint:CGPointMake((bounds.size.width - valueSize.width) / 2.0 + 1, (bounds.size.height - valueSize.height) / 2.0 - 2) withAttributes:_stringAttributes]; -} - -- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap -{ - var button = [[CPButton alloc] initWithFrame:[self frame]]; - [button setTitle:[self value]]; - - if (aMap) - { - var elementID = [[self dataObject] valueForKey:@"id"]; - [aMap setObject:button forKey:elementID]; - } - - return button; -} - -@end - -// ================================================================================================= -// UISliderView -// A skeleton that looks like a slider. -// ================================================================================================= -@implementation UISliderView : UIElementView - -+ (CPDictionary)defaultValues -{ - return {value: 0.5, outlets: "target, delegate", actions: "takeFloatValueFrom:, takeIntegerValueFrom:"}; -} - -+ (CPDictionary)propertyTypes -{ - return [super propertyTypes].copy({value: UIBNumber}); -} -- (id)initWithFrame:(CGRect)aRect -{ - self = [super initWithFrame:aRect]; - if (self) { - if (CGRectIsEmpty(aRect)) { - [self setFrameSize:CGSizeMake(150, 20)]; - } - } - return self; -} - -- (void)drawSkeleton:(CGRect)rect -{ - var bounds = CGRectInset([self bounds], 8, 0); - var midY = bounds.size.height / 2.0; - - // Draw track - [[CPColor grayColor] setStroke]; - var trackPath = [CPBezierPath bezierPath]; - [trackPath setLineWidth:3.0]; - [trackPath moveToPoint:CGPointMake(bounds.origin.x, midY)]; - [trackPath lineToPoint:CGPointMake(bounds.origin.x + bounds.size.width, midY)]; - [trackPath stroke]; - - // Draw knob - var knobX = bounds.origin.x + bounds.size.width * [self value]; - var knobRect = CGRectMake(knobX - 8, midY - 8, 16, 16); - var knobPath = [CPBezierPath bezierPathWithOvalInRect:knobRect]; - [[CPColor whiteColor] setFill]; - [knobPath fill]; - [[CPColor darkGrayColor] setStroke]; - [knobPath setLineWidth:1.0]; - [knobPath stroke]; -} - -- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap -{ - var slider = [[CPSlider alloc] initWithFrame:[self frame]]; - [slider setFloatValue:[self value]]; - - if (aMap) - { - var elementID = [[self dataObject] valueForKey:@"id"]; - [aMap setObject:slider forKey:elementID]; - } - - return slider; -} - -@end - -// ================================================================================================= -// UITextFieldView -// A skeleton that looks like a text field. -// ================================================================================================= -@implementation UITextFieldView : UIElementView - -+ (CPDictionary)defaultValues -{ - return {value: "Text Field", outlets: "target, delegate", actions: "takeStringValueFrom:, takeIntegerValueFrom:"}; -} - -+ (CPDictionary)propertyTypes -{ - return [super propertyTypes].copy({value: UIBString}); -} -- (id)initWithFrame:(CGRect)aRect -{ - self = [super initWithFrame:aRect]; - if (self) - { - if (CGRectIsEmpty(aRect)) - { - [self setFrameSize:CGSizeMake(150, 22)]; - } - [_stringAttributes setObject:[CPFont systemFontOfSize:12] forKey:CPFontAttributeName]; - [_stringAttributes setObject:[CPColor grayColor] forKey:CPForegroundColorAttributeName]; - } - return self; -} - -- (void)drawSkeleton:(CGRect)rect -{ - var bounds = CGRectInset([self bounds], 1, 1); - - // Background - [[CPColor textBackgroundColor] setFill]; - [CPBezierPath fillRect:bounds]; - - // Inset border - [[CPColor grayColor] setStroke]; - [CPBezierPath strokeRect:bounds]; - - // Draw placeholder value - var valueSize = [[self value] sizeWithAttributes:_stringAttributes]; - [[self value] drawAtPoint:CGPointMake(5, (bounds.size.height - valueSize.height) / 2.0 - 2) withAttributes:_stringAttributes]; -} - -- (id)nativeUIElementWithMap:(CPMutableDictionary)aMap -{ - var textField = [[CPTextField alloc] initWithFrame:[self frame]]; - [textField setStringValue:[self value]]; - - if (aMap) - { - var elementID = [[self dataObject] valueForKey:@"id"]; - [aMap setObject:textField forKey:elementID]; - } - - return textField; -} - -@end diff --git a/Tests/Manual/UIBuilderDemo/index.html b/Tests/Manual/UIBuilderDemo/index.html deleted file mode 100644 index cea729b96..000000000 --- a/Tests/Manual/UIBuilderDemo/index.html +++ /dev/null @@ -1,164 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - UIBuilderDemo - - - - - - - - - - - - -
-
-
- -
-
- -
- - diff --git a/Tests/Manual/UIBuilderDemo/main.j b/Tests/Manual/UIBuilderDemo/main.j deleted file mode 100755 index 993c34a35..000000000 --- a/Tests/Manual/UIBuilderDemo/main.j +++ /dev/null @@ -1,10 +0,0 @@ -@import -@import - -@import "UIElementView.j" -@import "AppController.j" - -function main(args, namedArgs) -{ - CPApplicationMain(); -} From e3fd7a295c2c8652af0498be16554ff419c60a9c Mon Sep 17 00:00:00 2001 From: daboe01 Date: Thu, 17 Jul 2025 19:41:48 +0200 Subject: [PATCH 29/40] fixed: ipad keyboard does not show --- AppKit/CPTextView/CPTextView.j | 3 +++ dist/cappuccino/bin/flatten | 2 +- dist/cappuccino/bin/fontinfo | Bin 69888 -> 70736 bytes dist/cappuccino/bin/imagesize | Bin 69536 -> 0 bytes dist/cappuccino/bin/objj2objcskeleton | 22 ++++++++++++++++------ 5 files changed, 20 insertions(+), 7 deletions(-) delete mode 100755 dist/cappuccino/bin/imagesize diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 0b0f00253..2763faac4 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -1100,6 +1100,9 @@ Sets the selection to a range of characters in response to user action. if (![self isSelectable]) return; + // this is for the ipad-keyboard + [_CPNativeInputManager focusForTextView:self]; + [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; [_caret setVisibility:NO]; diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten index 1df033252..80b9ecf8e 100755 --- a/dist/cappuccino/bin/flatten +++ b/dist/cappuccino/bin/flatten @@ -233,7 +233,7 @@ ObjectiveJFlattener.prototype.serializeFunctions = function() { var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK var relative = this.rootPath.relative(path).toString(); - this.functionsBuffer.push("ObjectiveJ.StaticResource._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); + this.functionsBuffer.push("ObjectiveJ.FileExecutable._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); } var bundle = this.context.global.CFBundle.bundleContainingURL(path); diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo index f1ff06158a2525d34541aff240713c0067bd8633..1a91ba1c8040bbcfe787719cb7e44c0e7578d180 100755 GIT binary patch literal 70736 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 delta 3394 zcmZ`*3s6+o89w`DsqFGx+h670NB67 z0BER_-s?-eqvet#M>G=>)tO2(O5#IV%?yIz_H@(>o1IN<&UUZ~LaljSEpq}>J}u!b zVr7xn4nNt^c`Qus7M+>LN3Ns}%e^# zxB<8vC=Ez+f{-lWvAl8B06|2vfltV9N$Vy>N(-^UM2r-KiqFN|))av-q#`Yxy;N9bap; zbl)tk_TTZ}vjob6WVpw18Q|(jmB3tzoS61`(Bi_#J{o&92UPh z1kMEhI4Gn~<~Ls&Pedbs907*Sm!KzryabTV!B&|iP}1kEA|#@JLkYP4ll)U(?U-NX zAFB>59U7~hoVp=??dgX2CkHG6b144R477I-%gz1=@l%E%bP;IYh+YSQ<68b0W_{yhNVO;gS%)DY9|CFu^Cw_= zYy98vk0JR4z<0VfKmeuBn;21b1(;^GjCA5h9;5Oo%! z21?$Q5G{eau*LtS6tcp9AL}t6+`C0LOp)vbxba+Y#jpKaRba;@D5&``bSwIxt2;VA z7m6Rkl)~s0(;9<9JrV}$1}Ip05ODcBLvl+{l?1>wLG3sMukeR!;;Ta7kO9ZS>T&*{ z{zEzOLM8vvr;O+WxQhNk^R7t8bcndWOpKTit&nIF3W1UdG-GYnqaG}e!0VceCM^D; zqR<$gLFfq+Os>atD9(nKXg?6aV+ z31%YzZ;ceFP(~ny?u;8q%gFHMF*P z*19~KSK6ALWp3At&N8}5o0%b<<}Q0}yUpcs6Pw%aY;nLP>2T5{?Mzys&7!~17U$a9 z+Q4r0w7Xgw%Sc`AGo_MD95AkhP+wdpy{^rjho{UUm=x(7xTUe73@bt6;B-ll&Mk6b zok~V|ozWY{djeYYs%w5>{$De{>i_urm>(8vvT=m;M>tsp975~7 z#q3f}VLXR%BjZaMk6zvlOz&cR7vq%i1C0NM@!vCkj`5p}$4H6gkT{lM4&(C}UyNKl zEc;jK_Gyz6^9p^6wAC}orWEl_k~sS8sTu0k?y#(fqfMs_THkqCezBqDpP?MD&)CBW z&n;VCDOA?*R;!RNthBNPoEn_WNf&anyU_~EP1-duOuVAoolQ=g+evy-I7e5LL;9bp zcZpl2N6!J=GqAsS4{VV74v!Nz8S!0>rjZSKJUTtb6L%kGxNjgw(UZ#2PX;pOpvDj7 zZEQD$6lpz#xJe2p*gUNbq=&dQ(fn*pO|5pcB>cc4xLRBuG^a()t!*Bm#km!21^{Y< z?dt(3`fwo4I~_hNsom+2_*U2)IY80>&K)4@bVU41z^&d{@~((mC%WL|DnN|GIIl{@Jw!dTys4T{x9RzodJaZ z4tRN|17!j|7So@7$|alo*un|T`0c_^6Rs%KW{b_^Bnv#vZFED}sE~LjJrZ7`>?}12 z^j0`ibN5PI2(t0gw9_+V&CZvc1KBrsh$+yI+k6k&Di|x%8J~FIq82mvzXg|eCJcY(|mto zMx}1;LO?SZmK-?9EuXVz>5=5^X}>L7k@!{fg5Ehp)(=xh-#lb|+cpY89Bmol413={ z-q`FoHlKWPG2x=~EpMW(JNwJmf~muwCO=3yz5WY&c7)^fYphqZ_NR{CzQ7&xoUOR< zAJ@MQUj86?*7l0)Tdq}e?|z(>fA3J|=hi;mdw(7(Fm6j396lJl*4(&vgpTzG?tKj) zn+%nU^QL|O$r072fIIj3og;U;LIdSB8QV|n`;}GwcKVJL7_=y2daz~N;7i#ZKlc9g ziFc;uy>z+dxtB-kDrb~^yD_)tRtD4(7aR|Ea`(y$F95qo>p%S?-EgKtQCwuCBWGsE Q%I;p7hEba639vimze2ENzW@LL diff --git a/dist/cappuccino/bin/imagesize b/dist/cappuccino/bin/imagesize deleted file mode 100755 index 5d67a1ce0a9a1e20577f82ccdfe0d3b3e94143b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69536 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 Z009sH0T2KI5C8!X009sHfo}nU{{v@rhV}pe diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton index b7972dbb3..51ecab38d 100755 --- a/dist/cappuccino/bin/objj2objcskeleton +++ b/dist/cappuccino/bin/objj2objcskeleton @@ -4,14 +4,11 @@ var fs = require("fs"), acorn = require("objj-parser"), - walk = require("objj-parser/util/walk"), + walk = require("acorn-walk"), stream = ObjectiveJ.term; - debugger; - function main(args) { - debugger; args.shift(); if (args.length < 1) @@ -40,6 +37,8 @@ function raise(pos, message) throw syntaxError; } +function ignore(_node, _st, _c) {} + var errors = [], xcc = walk.make( { @@ -115,7 +114,18 @@ var errors = [], else raise(node.loc.start, "Action methods must have exactly one parameter"); } - } + }, + TypeDefStatement: ignore, + ClassStatement: ignore, + MessageSendExpression: ignore, + GlobalStatement: ignore, + ProtocolDeclarationStatement: ignore, + ArrayLiteral: ignore, + Reference: ignore, + DictionaryLiteral: ignore, + Dereference: ignore, + ImportStatement: ignore, + SelectorLiteralExpression: ignore } ); @@ -154,7 +164,7 @@ function parser(args) outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), source = fs.readFileSync(sourcePath, { encoding: "utf8" }), - tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath }), + tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath, ecmaVersion: 2022 }), classesInformation = [], ObjectiveCSource = "", ObjectiveCHeader = "", From bda5594f0cabb0cf625143f16128f7dfe876ec51 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Fri, 18 Jul 2025 22:05:12 +0200 Subject: [PATCH 30/40] fixed: ipad softkeyboard trigger --- AppKit/CPTextView/CPTextView.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 2763faac4..c0e54f77e 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -1101,7 +1101,7 @@ Sets the selection to a range of characters in response to user action. return; // this is for the ipad-keyboard - [_CPNativeInputManager focusForTextView:self]; + [_CPNativeInputManager focusForClipboardOfTextView:self]; [_CPNativeInputManager cancelCurrentInputSessionIfNeeded]; [_caret setVisibility:NO]; From 2a32934f864a156b69639ae2be3e86b2ca83ec07 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 19 Jul 2025 09:40:14 +0200 Subject: [PATCH 31/40] fixed: ipad backspace issue --- AppKit/CPTextView/CPTextView.j | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index c0e54f77e..1719695df 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2680,12 +2680,21 @@ var _CPCopyPlaceholder = '-'; // Fires for simple key presses (a, b, 1, 2) _CPNativeInputField.addEventListener('input', function(e) { - // If we are in a composition (e.g., IME), we do nothing. + // If we are in a composition (e.g., IME), do nothing. // We wait for 'compositionend' to get the final, complete text. - if (_isComposing) { + if (_isComposing) + return; + + // The 'input' event fires for deletions too. On iPad, repeatedly + // backspacing on an empty field can insert strange content (like
). + // We only want to handle this event for actual insertions. + // Deletions are handled by the standard key binding mechanism (deleteBackward:). + if (e.inputType && e.inputType.startsWith('delete')) + { + _CPNativeInputField.innerHTML = ''; return; } - // If not composing, this is a simple character. Handle it immediately. + handleInput(e.target.innerHTML); }); From 46bab97b496854a4750492598ee23c15da1cdba6 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 19 Jul 2025 13:51:58 +0200 Subject: [PATCH 32/40] fixed: backspace issue on ipad --- AppKit/CPTextView/CPTextView.j | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 1719695df..74afe64ea 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2677,7 +2677,20 @@ var _CPCopyPlaceholder = '-'; _CPNativeInputField.innerHTML = ''; }; - // Fires for simple key presses (a, b, 1, 2) + // Proactively prevent the backspace bug on iPad + // Intercept the keydown event before the browser acts on it. + _CPNativeInputField.addEventListener('keydown', function(e) { + // This bug occurs when backspace is pressed on an *empty* contentEditable div. + if (e.key === 'Backspace' && _CPNativeInputField.innerHTML === '') { + // Prevent the browser's default action (which is to insert a junk character). + // This stops an 'input' event from firing, solving the problem at the source. + // The keydown event will still be handled by Cappuccino's responder chain. + e.preventDefault(); + } + }); + + // Reactively handle input events as a fallback + // Fires for simple key presses, deletions, etc. _CPNativeInputField.addEventListener('input', function(e) { // If we are in a composition (e.g., IME), do nothing. @@ -2685,12 +2698,6 @@ var _CPCopyPlaceholder = '-'; if (_isComposing) return; - // The 'input' event fires for deletions too. On iPad, repeatedly - // backspacing on an empty field can insert strange content (like
). - // We only want to handle this event for actual insertions. - // Deletions are handled by the standard key binding mechanism (deleteBackward:). - if (e.inputType && e.inputType.startsWith('delete')) - { _CPNativeInputField.innerHTML = ''; return; } From 76ba6a4e64b70926912f18aeba9f64e56f59b51a Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 19 Jul 2025 13:53:28 +0200 Subject: [PATCH 33/40] fixed: ipad issue with backspace --- AppKit/CPTextView/CPTextView.j | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 74afe64ea..51ee6d424 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2698,6 +2698,11 @@ var _CPCopyPlaceholder = '-'; if (_isComposing) return; + // This is a safety net. The 'input' event fires for deletions too. + // We only want to handle this event for actual insertions. Deletions are + // handled by the standard key binding mechanism (deleteBackward:). + if (e.inputType && e.inputType.startsWith('delete')) { + // It was a deletion. Ensure the native field is empty and stop processing. _CPNativeInputField.innerHTML = ''; return; } From 0b4d156b5b1bb7aadeafb9a6db7a4d8f1b75e7f2 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 19 Jul 2025 14:19:46 +0200 Subject: [PATCH 34/40] fixed: keyboard issues on ipad --- AppKit/CPTextView/CPTextView.j | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 51ee6d424..d27ec4cdf 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2677,37 +2677,35 @@ var _CPCopyPlaceholder = '-'; _CPNativeInputField.innerHTML = ''; }; - // Proactively prevent the backspace bug on iPad - // Intercept the keydown event before the browser acts on it. + // Intercept problematic keys before the browser acts. _CPNativeInputField.addEventListener('keydown', function(e) { - // This bug occurs when backspace is pressed on an *empty* contentEditable div. - if (e.key === 'Backspace' && _CPNativeInputField.innerHTML === '') { - // Prevent the browser's default action (which is to insert a junk character). - // This stops an 'input' event from firing, solving the problem at the source. - // The keydown event will still be handled by Cappuccino's responder chain. + + if (e.key === 'Enter' || (e.key === 'Backspace' && _CPNativeInputField.innerHTML === '')) { + // Prevent browser default action: + // - 'Enter': Prevents inserting

. + // - 'Backspace' on empty: Prevents inserting junk characters on iPadOS. e.preventDefault(); } }); - // Reactively handle input events as a fallback - // Fires for simple key presses, deletions, etc. + // This listener handles all other character input. _CPNativeInputField.addEventListener('input', function(e) { - // If we are in a composition (e.g., IME), do nothing. - // We wait for 'compositionend' to get the final, complete text. + // If we are in a composition (e.g., IME), do nothing yet. if (_isComposing) return; - // This is a safety net. The 'input' event fires for deletions too. - // We only want to handle this event for actual insertions. Deletions are - // handled by the standard key binding mechanism (deleteBackward:). - if (e.inputType && e.inputType.startsWith('delete')) { - // It was a deletion. Ensure the native field is empty and stop processing. + // Safety net: ignore deletion events, as they are handled by keydown. + if (e.inputType && e.inputType.startsWith('delete')) + { _CPNativeInputField.innerHTML = ''; return; } - - handleInput(e.target.innerHTML); + + // Robustness: Use 'textContent' instead of 'innerHTML' to strip any + // unexpected HTML tags the browser might have inserted. + var textToInsert = e.target.textContent; + handleInput(textToInsert); }); // Fires when a composition session starts (e.g., user presses a dead key or starts an IME). From 7e99e3ca20f48fde3b91c2b2486b33658f48cb9c Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 19 Jul 2025 14:21:10 +0200 Subject: [PATCH 35/40] removed: unwanted files --- dist/cappuccino/bin/flatten | 368 --------------- dist/cappuccino/bin/fontinfo | Bin 70736 -> 0 bytes dist/cappuccino/bin/objj2objcskeleton | 623 -------------------------- 3 files changed, 991 deletions(-) delete mode 100755 dist/cappuccino/bin/flatten delete mode 100755 dist/cappuccino/bin/fontinfo delete mode 100755 dist/cappuccino/bin/objj2objcskeleton diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten deleted file mode 100755 index 80b9ecf8e..000000000 --- a/dist/cappuccino/bin/flatten +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/env objj - -require("narwhal").ensureEngine("rhino"); - -@import - -@import "../lib/cappuccino/objj-analysis-tools.j" - -var FILE = require("file"); -var OS = require("os"); -var UTIL = require("narwhal/util"); - -var CACHEMANIFEST = require("objective-j/cache-manifest"); - -var stream = require("narwhal/term").stream; -var parser = new (require("narwhal/args").Parser)(); - -parser.usage("INPUT_PROJECT OUTPUT_PROJECT"); -parser.help("Combine a Cappuccino application into a single JavaScript file."); - -parser.option("-m", "--main", "main") - .def("main.j") - .set() - .help("The relative path (from INPUT_PROJECT) to the main file (default: 'main.j')"); - -parser.option("-F", "--framework", "frameworks") - .push() - .help("Add a frameworks directory, relative to INPUT_PROJECT (default: ['Frameworks'])"); - -parser.option("-P", "--path", "paths") - .push() - .help("Add a path (relative to the application root) to inline."); - -parser.option("-f", "--force", "force") - .def(false) - .set(true) - .help("Force overwriting OUTPUT_PROJECT if it exists"); - -parser.option("--index", "index") - .def("index.html") - .set() - .help("The root HTML file to modify (default: index.html)"); - -parser.option("-s", "--split", "number", "split") - .natural() - .def(0) - .help("Split into multiple files"); - -parser.option("-c", "--compressor", "compressor") - .def("shrinksafe") - .set() - .help("Select a compressor to use (closure-compiler, yuicompressor, shrinksafe), or \"none\" (default: shrinksafe)"); - -parser.option("--manifest", "manifest") - .set(true) - .help("Generate HTML5 cache manifest."); - -parser.option("-v", "--verbose", "verbose") - .def(false) - .set(true) - .help("Verbose logging"); - -parser.helpful(); - -function main(args) -{ - var options = parser.parse(args); - - if (options.args.length < 2) { - parser.printUsage(options); - return; - } - - var rootPath = FILE.path(options.args[0]).join("").absolute(); - var outputPath = FILE.path(options.args[1]).join("").absolute(); - - if (outputPath.exists()) { - if (options.force) { - // FIXME: why doesn't this work?! - //outputPath.rmtree(); - OS.system(["rm", "-rf", outputPath]); - } else { - stream.print("\0red(OUTPUT_PROJECT " + outputPath + " exists. Use -f to overwrite.\0)"); - OS.exit(1); - } - } - - options.frameworks.push("Frameworks"); - - var mainPath = String(rootPath.join(options.main)); - var frameworks = options.frameworks.map(function(framework) { return rootPath.join(framework); }); - var environment = "Browser"; - - stream.print("\0yellow("+Array(81).join("=")+"\0)"); - stream.print("Application root: \0green(" + rootPath + "\0)"); - stream.print("Output directory: \0green(" + outputPath + "\0)"); - - stream.print("\0yellow("+Array(81).join("=")+"\0)"); - stream.print("Main file: \0green(" + mainPath + "\0)"); - stream.print("Frameworks: \0green(" + frameworks + "\0)"); - stream.print("Environment: \0green(" + environment + "\0)"); - - var flattener = new ObjectiveJFlattener(rootPath); - - flattener.options = options; - - flattener.setIncludePaths(frameworks); - flattener.setEnvironments([environment, "ObjJ"]); - - print("Loading application."); - flattener.load(mainPath); - - print("Loading default theme."); - flattener.require("objective-j").objj_eval("("+(function() { - - var defaultThemeName = [CPApplication defaultThemeName], - bundle = nil; - - if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2") - bundle = [CPBundle bundleForClass:[CPApplication class]]; - else - bundle = [CPBundle mainBundle]; - - var blend = [[CPThemeBlend alloc] initWithContentsOfURL:[bundle pathForResource:defaultThemeName + @".blend"]]; - [blend loadWithDelegate:nil]; - - })+")")(); - - var applicationJSs = flattener.buildApplicationJS(); - - FILE.copyTree(rootPath, outputPath); - - applicationJSs.forEach(function(applicationJS, n) { - var name = "Application"+(n||"")+".js"; - if (options.compressor === "none") { - print("skipping compression: " + name); - } else { - print("compressing: " + name); - applicationJS = require("minify/"+options.compressor).compress(applicationJS, { charset : "UTF-8", useServer : true }); - } - outputPath.join(name).write(applicationJS, { charset : "UTF-8" }); - }); - - rewriteMainHTML(outputPath.join(options.index)); - - if (options.manifest) { - CACHEMANIFEST.generateManifest(outputPath, { - index : outputPath.join(options.index), - exclude : Object.keys(flattener.filesToCache).map(function(path) { return outputPath.join(path).toString(); }) - }); - } -} - -// ObjectiveJFlattener inherits from ObjectiveJRuntimeAnalyzer -function ObjectiveJFlattener(rootPath) { - ObjectiveJRuntimeAnalyzer.apply(this, arguments); - - this.filesToCache = {}; - this.fileCacheBuffer = []; - this.functionsBuffer = []; -} - -ObjectiveJFlattener.prototype = Object.create(ObjectiveJRuntimeAnalyzer.prototype); - -ObjectiveJFlattener.prototype.buildApplicationJS = function() { - - this.setupFileCache(); - this.serializeFunctions(); - this.serializeFileCache(); - - var additions = FILE.read(FILE.join(FILE.dirname(module.path), "..", "..", "cappuccino", "lib", "cappuccino", "objj-flatten-additions.js"), { charset:"UTF-8" }); - - var applicationJSs = []; - - if (this.options.split === 0) { - var buffer = []; - buffer.push("var baseURL = new CFURL(\".\", ObjectiveJ.pageURL);"); - buffer.push(additions); - buffer.push(this.fileCacheBuffer.join("\n")); - buffer.push(this.functionsBuffer.join("\n")); - buffer.push("ObjectiveJ.bootstrap();"); - applicationJSs.push(buffer.join("\n")); - } else { - var appFilesCount = this.options.split; - - var buffers = []; - for (var i = 0; i <= appFilesCount; i++) - buffers.push([]); - - var chunks = this.fileCacheBuffer.concat(this.functionsBuffer).sort(function(chunkA, chunkB) { - return chunkA.length - chunkB.length; - }); - - // try to equally distribute the chunks. could be better but good enough for now. - var n = 0; - while (chunks.length) { - buffers[(n++ % appFilesCount) + 1].push(chunks.pop()); - } - - buffers[0].push("var baseURL = new CFURL(\".\", ObjectiveJ.pageURL);"); - buffers[0].push(additions); - - buffers[0].push("var appFilesCount = " + appFilesCount +";"); - buffers[0].push("for (var i = 1; i <= appFilesCount; i++) {"); - buffers[0].push(" var script = document.createElement(\"script\");"); - buffers[0].push(" script.src = \"Application\"+i+\".js\";"); - buffers[0].push(" script.charset = \"UTF-8\";"); - buffers[0].push(" script.onload = function() { if (--appFilesCount === 0) ObjectiveJ.bootstrap(); };"); - buffers[0].push(" document.getElementsByTagName(\"head\")[0].appendChild(script);"); - buffers[0].push("}"); - - buffers.forEach(function(buffer) { - applicationJSs.push(buffer.join("\n")); - }); - } - - return applicationJSs; -} - -ObjectiveJFlattener.prototype.serializeFunctions = function() { - var inlineFunctions = true;//this.options.inlineFunctions; - - var outputFiles = {}; - - var _cachedExecutableFunctions = {}; - - this.require("objective-j").FileExecutable.allFileExecutables().forEach(function(executable) { - var path = executable.path(); - - if (inlineFunctions) - { - // stringify the function, replacing arguments - var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK - - var relative = this.rootPath.relative(path).toString(); - this.functionsBuffer.push("ObjectiveJ.FileExecutable._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); - } - - var bundle = this.context.global.CFBundle.bundleContainingURL(path); - if (bundle && bundle.infoDictionary()) - { - var executablePath = bundle.executablePath(), - relativeToBundle = FILE.relative(FILE.join(bundle.path(), ""), path); - - if (executablePath) - { - if (inlineFunctions) - { - // remove the code since we're inlining the functions - executable._code = "alert("+JSON.stringify(relativeToBundle)+");"; - } - - if (!outputFiles[executablePath]) - { - outputFiles[executablePath] = []; - outputFiles[executablePath].push("@STATIC;1.0;"); - } - - var fileContents = executable.toMarkedString(); - - outputFiles[executablePath].push("p;" + relativeToBundle.length + ";" + relativeToBundle); - outputFiles[executablePath].push("t;" + fileContents.length + ";" + fileContents); - - // stream.print("Adding \0green(" + this.rootPath.relative(path) + "\0) to \0cyan(" + this.rootPath.relative(executablePath) + "\0)"); - } - } - else - CPLog.warn("No bundle (or info dictionary for) " + rootPath.relative(path)); - }, this); - - for (var executablePath in outputFiles) - { - var relative = this.rootPath.relative(executablePath).toString(); - var contents = outputFiles[executablePath].join(""); - this.filesToCache[relative] = contents; - } -} - -ObjectiveJFlattener.prototype.serializeFileCache = function() { - for (var relative in this.filesToCache) { - var contents = this.filesToCache[relative]; - print("caching: " + relative + " => " + (contents == null ? 404 : 200)); - if (contents == null) - this.fileCacheBuffer.push("CFHTTPRequest._cacheRequest(new CFURL("+JSON.stringify(relative)+", baseURL), 404);"); - else - this.fileCacheBuffer.push("CFHTTPRequest._cacheRequest(new CFURL("+JSON.stringify(relative)+", baseURL), 200, {}, "+JSON.stringify(contents)+");"); - } -} - -ObjectiveJFlattener.prototype.setupFileCache = function() { - var paths = {}; - - UTIL.update(paths, this.requestedURLs); - - this.options.paths.forEach(function(relativePath) { - paths[this.rootPath.join(relativePath)] = true; - }, this); - - Object.keys(paths).forEach(function(absolute) { - var relative = this.rootPath.relative(absolute).toString(); - if (relative.indexOf("..") === 0) - { - print("skipping (parent of app root): " + absolute); - return; - } - - if (FILE.isFile(absolute)) - { - // if (this.options.maxCachedSize && FILE.size(absolute) > this.options.maxCachedSize) - // { - // print("skipping (larger than "+this.options.maxCachedSize+" bytes): " + absolute); - // return; - // } - - var contents = FILE.read(absolute, { charset : "UTF-8" }); - this.filesToCache[relative] = contents; - } else { - this.filesToCache[relative] = null; - } - }, this); -} - -// "$1" is the matching indentation -var scriptTagsBefore = - '$1'; - -var scriptTagsAfter = - '$1'; - -// enable CPLog: -// scriptTagsAfter = '$1\n' + scriptTagsAfter; - -function rewriteMainHTML(indexHTMLPath) { - if (indexHTMLPath.isFile()) { - var indexHTML = indexHTMLPath.read({ charset : "UTF-8" }); - - // inline the Application.js if it's smallish - var applicationJSPath = indexHTMLPath.dirname().join("Application.js"); - if (applicationJSPath.size() < 10*1024) { - // escape any dollar signs by replacing them with two - // then indent by splitting/joining on newlines - scriptTagsAfter = - '$1'; - } - - // attempt to find Objective-J script tag and add ours - var newIndexHTML = indexHTML.replace(/([ \t]+)]+Objective-J\.js[^>]+>(?:\s*<\/script>)?/, - scriptTagsBefore+'\n$&\n'+scriptTagsAfter); - - if (newIndexHTML !== indexHTML) { - stream.print("\0green(Modified: "+indexHTMLPath+".\0)"); - indexHTMLPath.write(newIndexHTML, { charset : "UTF-8" }); - return; - } - } else { - stream.print("\0yellow(Warning: "+indexHTMLPath+" does not exist. Specify an alternate index HTML file with the --index option.\0)"); - } - - stream.print("\0yellow(Warning: Unable to automatically modify "+indexHTMLPath + ".\0)"); - stream.print("\nAdd the following before the Objective-J script tag:"); - stream.print(scriptTagsBefore.replace(/\$1/g, " ")); - stream.print("\nAdd the following after the Objective-J script tag:"); - stream.print(scriptTagsAfter.replace(/\$1/g, " ")); -} diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo deleted file mode 100755 index 1a91ba1c8040bbcfe787719cb7e44c0e7578d180..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70736 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 diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton deleted file mode 100755 index 51ecab38d..000000000 --- a/dist/cappuccino/bin/objj2objcskeleton +++ /dev/null @@ -1,623 +0,0 @@ -#!/usr/bin/env objj - -@import - -var fs = require("fs"), - acorn = require("objj-parser"), - walk = require("acorn-walk"), - stream = ObjectiveJ.term; - -function main(args) -{ - args.shift(); - - if (args.length < 1) - return printUsage(); - - parser(args); -} - -function printUsage() -{ - console.log("objj2objskeleton [FILE] [DESTINATION]"); - console.log("Convert a objective-j file to an objective-c files skeleton (.h and .m)") -} - -// Debug function to print some JS objects -function dump(obj) -{ - console.log(JSON.stringify(obj)); -} - -function raise(pos, message) -{ - var syntaxError = new SyntaxError(message); - syntaxError.line = pos.line; - - throw syntaxError; -} - -function ignore(_node, _st, _c) {} - -var errors = [], - xcc = walk.make( - { - ClassDeclarationStatement: function(node, st, c) - { - var className = node.classname.name, - superclassname = node.superclassname ? node.superclassname.name : "", - declaredOutletsName = [], - classInfo = { - "name": className, - "category": node.categoryname ? node.categoryname.name : "", - "superClass": superclassname, - "outlets": [], - "actions": [], - "actionNames": [] - }; - - if (node.ivardeclarations) - { - for (var i = 0; i < node.ivardeclarations.length; ++i) - { - var ivarDecl = node.ivardeclarations[i], - ivarType = ivarDecl.ivartype ? ivarDecl.ivartype.name : null, - ivarName = ivarDecl.id.name, - ivarHasOutlet = ivarDecl.outlet ? "@outlet" : null; - - if (ivarHasOutlet) - { - if (declaredOutletsName.indexOf(ivarName) !== -1) - raise(ivarDecl.loc.start, "Outlet '" + ivarName + "' declared more than once"); - - declaredOutletsName.push(ivarName); - classInfo.outlets.push({"type": ivarType, "name": ivarName}); - } - } - } - - st.push(classInfo) - - for (var i = 0; i < node.body.length; ++i) - c(node.body[i], classInfo, "Statement"); - }, - - MethodDeclarationStatement: function(node, st, c) - { - var selectors = node.selectors, - arguments = node.arguments, - //methodReturnType = [node.returntype ? node.returntype.name : "id"], - methodHasAction = node.action ? "IBAction" : null, - selector = selectors[0].name, - actionInfo = {"name": selector, "arguments":[]}; - - if (methodHasAction) - { - if (arguments.length == 1) - { - if (st.actionNames.indexOf(selector) !== -1) - raise(node.loc.start, "Action '" + selector + "' declared more than once"); - - st.actionNames.push(selector); - - for (var i = 0; i < arguments.length; i++) - { - var argument = arguments[i], - argumentName = argument.identifier.name, - argumentType = argument.type ? argument.type.name : null; - - actionInfo.arguments.push({"type": argumentType, "name": argumentName}); - } - - st.actions.push(actionInfo) - } - else - raise(node.loc.start, "Action methods must have exactly one parameter"); - } - }, - TypeDefStatement: ignore, - ClassStatement: ignore, - MessageSendExpression: ignore, - GlobalStatement: ignore, - ProtocolDeclarationStatement: ignore, - ArrayLiteral: ignore, - Reference: ignore, - DictionaryLiteral: ignore, - Dereference: ignore, - ImportStatement: ignore, - SelectorLiteralExpression: ignore - } -); - -function compile(node, state, visitor) -{ - function c(node, st, override) - { - visitor[override || node.type](node, st, c); - } - - c(node, state); -}; - -function removeLastSlashIfNecessary(path) -{ - if (path[path.length - 1] == "/") - return path.substring(0, path.length - 1); - - return path; -} - -/* - $1 Full project source path - $2 Destination - $-n name of the cocoa files -*/ -function parser(args) -{ - try - { - var sourcePath = args.shift(), - projectBasePath = removeLastSlashIfNecessary(args.shift()), - outputDirectory = projectBasePath, - baseFilename = [sourcePath lastPathComponent], - baseFilenameWithNoExtension = args.shift() == "-n" ? args.shift() : baseFilename.substring(0, baseFilename.length - 2), - outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), - outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), - source = fs.readFileSync(sourcePath, { encoding: "utf8" }), - tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath, ecmaVersion: 2022 }), - classesInformation = [], - ObjectiveCSource = "", - ObjectiveCHeader = "", - hasErrors = NO; - - compile(tokens, classesInformation, xcc); - - // dump(classesInformation) - - ObjectiveCHeader += - "#import \n" + - '#import "xcc_general_include.h"\n'; - - ObjectiveCSource += "#import \"" + outputHeaderURL.lastPathComponent() + "\"\n"; - - // Traverse each found classes - classesInformation.forEach(function(aClass) - { - // add new class definition - if (aClass.superClass) - ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ : %@", aClass.name, NSCompatibleClassName(aClass.superClass)]; - else - ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ (%@)", NSCompatibleClassName(aClass.name, NO), aClass.category]; - - // add each outlet in header - if (aClass.outlets.length > 0) - ObjectiveCHeader += "\n"; - - aClass.outlets.forEach(function(anOutlet) - { - ObjectiveCHeader += [CPString stringWithFormat:@"\n@property (assign) IBOutlet %@ %@;", NSCompatibleClassName(anOutlet.type, YES), anOutlet.name]; - }); - - if (aClass.actions.length > 0) - ObjectiveCHeader += "\n"; - - // add each action in header - aClass.actions.forEach(function(anAction) - { - ObjectiveCHeader += [CPString stringWithFormat:@"\n- (IBAction)%@:(%@)%@;", anAction.name, anAction.arguments[0].type, anAction.arguments[0].name]; - }); - - if (aClass.outlets.length > 0 || aClass.actions.length > 0) - ObjectiveCHeader += "\n"; - - ObjectiveCHeader += "\n@end\n"; - - // fill up the implementation file - ObjectiveCSource += "\n@implementation " + NSCompatibleClassName(aClass.name, NO) + "\n@end\n"; - }); - - if (ObjectiveCSource.length) - fs.writeFileSync(outputImplementationURL.absoluteString(), ObjectiveCSource, 'utf8'); - - if (ObjectiveCHeader.length) - fs.writeFileSync(outputHeaderURL.absoluteString(), ObjectiveCHeader, 'utf8'); - } - catch (e) - { - [errors addObject:@{ - @"message": e.message, - @"sourcePath": sourcePath, - @"line": e.line - }]; - - hasErrors = YES; - } - - if ([errors count]) - { - var plist = [CPPropertyListSerialization dataFromPropertyList:errors format:CPPropertyListXMLFormat_v1_0]; - - stream.printError([plist rawString]); - - // If there were category warnings, hasErrors is NO, so return a warning status - process.exit(hasErrors ? 1 : 2); - } -} - -function NSCompatibleClassName(aClassName, asPointer) -{ - if (aClassName === "var" || aClassName === "id") - return "id"; - - var prefix = aClassName.substr(0, 2), - asterisk = asPointer ? "*" : ""; - - if (prefix !== "CP") - return aClassName + asterisk; - - var NSClassName = "NS" + aClassName.substr(2); - - if (NSClasses[NSClassName]) - return NSClassName + asterisk; - - if (ReplacementClasses[aClassName]) - return ReplacementClasses[aClassName] + asterisk; - - return aClassName + asterisk; -} - -var ReplacementClasses = { - "CPWebView": "WebView", - "CPRadio": "NSButtonCell", - "CPRadioGroup": "NSMatrix" - }; - -var NSClasses = { - "NSAffineTransform" : YES, - "NSAppleEventDescriptor" : YES, - "NSAppleEventManager" : YES, - "NSAppleScript" : YES, - "NSArchiver" : YES, - "NSArray" : YES, - "NSAssertionHandler" : YES, - "NSAttributedString" : YES, - "NSAutoreleasePool" : YES, - "NSBlockOperation" : YES, - "NSBundle" : YES, - "NSCache" : YES, - "NSCachedURLResponse" : YES, - "NSCalendar" : YES, - "NSCharacterSet" : YES, - "NSClassDescription" : YES, - "NSCloneCommand" : YES, - "NSCloseCommand" : YES, - "NSCoder" : YES, - "NSComparisonPredicate" : YES, - "NSCompoundPredicate" : YES, - "NSCondition" : YES, - "NSConditionLock" : YES, - "NSConnection" : YES, - "NSCountCommand" : YES, - "NSCountedSet" : YES, - "NSCreateCommand" : YES, - "NSData" : YES, - "NSDate" : YES, - "NSDateComponents" : YES, - "NSDateFormatter" : YES, - "NSDecimalNumber" : YES, - "NSDecimalNumberHandler" : YES, - "NSDeleteCommand" : YES, - "NSDeserializer" : YES, - "NSDictionary" : YES, - "NSDirectoryEnumerator" : YES, - "NSDistantObject" : YES, - "NSDistantObjectRequest" : YES, - "NSDistributedLock" : YES, - "NSDistributedNotificationCenter" : YES, - "NSEnumerator" : YES, - "NSError" : YES, - "NSException" : YES, - "NSExistsCommand" : YES, - "NSExpression" : YES, - "NSFileHandle" : YES, - "NSFileManager" : YES, - "NSFileWrapper" : YES, - "NSFormatter" : YES, - "NSGarbageCollector" : YES, - "NSGetCommand" : YES, - "NSHashTable" : YES, - "NSHost" : YES, - "NSHTTPCookie" : YES, - "NSHTTPCookieStorage" : YES, - "NSHTTPURLResponse" : YES, - "NSIndexPath" : YES, - "NSIndexSet" : YES, - "NSIndexSpecifier" : YES, - "NSInputStream" : YES, - "NSInvocation" : YES, - "NSInvocationOperation" : YES, - "NSKeyedArchiver" : YES, - "NSKeyedUnarchiver" : YES, - "NSLocale" : YES, - "NSLock" : YES, - "NSLogicalTest" : YES, - "NSMachBootstrapServer" : YES, - "NSMachPort" : YES, - "NSMapTable" : YES, - "NSMessagePort" : YES, - "NSMessagePortNameServer" : YES, - "NSMetadataItem" : YES, - "NSMetadataQuery" : YES, - "NSMetadataQueryAttributeValueTuple" : YES, - "NSMetadataQueryResultGroup" : YES, - "NSMethodSignature" : YES, - "NSMiddleSpecifier" : YES, - "NSMoveCommand" : YES, - "NSMutableArray" : YES, - "NSMutableAttributedString" : YES, - "NSMutableCharacterSet" : YES, - "NSMutableData" : YES, - "NSMutableDictionary" : YES, - "NSMutableIndexSet" : YES, - "NSMutableSet" : YES, - "NSMutableString" : YES, - "NSMutableURLRequest" : YES, - "NSNameSpecifier" : YES, - "NSNetService" : YES, - "NSNetServiceBrowser" : YES, - "NSNotification" : YES, - "NSNotificationCenter" : YES, - "NSNotificationQueue" : YES, - "NSNull" : YES, - "NSNumber" : YES, - "NSNumberFormatter" : YES, - "NSObject" : YES, - "NSOperation" : YES, - "NSOperationQueue" : YES, - "NSOrthography" : YES, - "NSOutputStream" : YES, - "NSPipe" : YES, - "NSPointerArray" : YES, - "NSPointerFunctions" : YES, - "NSPort" : YES, - "NSPortCoder" : YES, - "NSPortMessage" : YES, - "NSPortNameServer" : YES, - "NSPositionalSpecifier" : YES, - "NSPredicate" : YES, - "NSProcessInfo" : YES, - "NSPropertyListSerialization" : YES, - "NSPropertySpecifier" : YES, - "NSProtocolChecker" : YES, - "NSProxy" : YES, - "NSPurgeableData" : YES, - "NSQuitCommand" : YES, - "NSRandomSpecifier" : YES, - "NSRangeSpecifier" : YES, - "NSRecursiveLock" : YES, - "NSRelativeSpecifier" : YES, - "NSRunLoop" : YES, - "NSScanner" : YES, - "NSScriptClassDescription" : YES, - "NSScriptCoercionHandler" : YES, - "NSScriptCommand" : YES, - "NSScriptCommandDescription" : YES, - "NSScriptExecutionContext" : YES, - "NSScriptObjectSpecifier" : YES, - "NSScriptSuiteRegistry" : YES, - "NSScriptWhoseTest" : YES, - "NSSerializer" : YES, - "NSSet" : YES, - "NSSetCommand" : YES, - "NSSocketPort" : YES, - "NSSocketPortNameServer" : YES, - "NSSortDescriptor" : YES, - "NSSpecifierTest" : YES, - "NSSpellServer" : YES, - "NSStream" : YES, - "NSString" : YES, - "NSTask" : YES, - "NSTextCheckingResult" : YES, - "NSThread" : YES, - "NSTimer" : YES, - "NSTimeZone" : YES, - "NSUnarchiver" : YES, - "NSUndoManager" : YES, - "NSUniqueIDSpecifier" : YES, - "NSURL" : YES, - "NSURLAuthenticationChallenge" : YES, - "NSURLCache" : YES, - "NSURLConnection" : YES, - "NSURLCredential" : YES, - "NSURLCredentialStorage" : YES, - "NSURLDownload" : YES, - "NSURLHandle" : YES, - "NSURLProtectionSpace" : YES, - "NSURLProtocol" : YES, - "NSURLRequest" : YES, - "NSURLResponse" : YES, - "NSUserDefaults" : YES, - "NSValue" : YES, - "NSValueTransformer" : YES, - "NSWhoseSpecifier" : YES, - "NSXMLDocument" : YES, - "NSXMLDTD" : YES, - "NSXMLDTDNode" : YES, - "NSXMLElement" : YES, - "NSXMLNode" : YES, - "NSXMLParser" : YES, - "NSActionCell" : YES, - "NSAffineTransform Additions" : YES, - "NSAlert" : YES, - "NSAnimation" : YES, - "NSAnimationContext" : YES, - "NSAppleScript Additions" : YES, - "NSApplication" : YES, - "NSArrayController" : YES, - "NSATSTypesetter" : YES, - "NSAttributedString Application Kit Additions" : YES, - "NSBezierPath" : YES, - "NSBitmapImageRep" : YES, - "NSBox" : YES, - "NSBrowser" : YES, - "NSBrowserCell" : YES, - "NSBundle Additions" : YES, - "NSButton" : YES, - "NSButtonCell" : YES, - "NSCachedImageRep" : YES, - "NSCell" : YES, - "NSCIImageRep" : YES, - "NSClipView" : YES, - "NSCoder Application Kit Additions" : YES, - "NSCollectionView" : YES, - "NSCollectionViewItem" : YES, - "NSColor" : YES, - "NSColorList" : YES, - "NSColorPanel" : YES, - "NSColorPicker" : YES, - "NSColorSpace" : YES, - "NSColorWell" : YES, - "NSComboBox" : YES, - "NSComboBoxCell" : YES, - "NSControl" : YES, - "NSController" : YES, - "NSCursor" : YES, - "NSCustomImageRep" : YES, - "NSDatePicker" : YES, - "NSDatePickerCell" : YES, - "NSDictionaryController" : YES, - "NSDockTile" : YES, - "NSDocument" : YES, - "NSDocumentController" : YES, - "NSDrawer" : YES, - "NSEPSImageRep" : YES, - "NSEvent" : YES, - "NSFileWrapper" : YES, - "NSFont" : YES, - "NSFontDescriptor" : YES, - "NSFontManager" : YES, - "NSFontPanel" : YES, - "NSForm" : YES, - "NSFormCell" : YES, - "NSGlyphGenerator" : YES, - "NSGlyphInfo" : YES, - "NSGradient" : YES, - "NSGraphicsContext" : YES, - "NSHelpManager" : YES, - "NSImage" : YES, - "NSImageCell" : YES, - "NSImageRep" : YES, - "NSImageView" : YES, - "NSLayoutManager" : YES, - "NSLevelIndicator" : YES, - "NSLevelIndicatorCell" : YES, - "NSMatrix" : YES, - "NSMenu" : YES, - "NSMenuItem" : YES, - "NSMenuItemCell" : YES, - "NSMenuView" : YES, - "NSMutableAttributedString Additions" : YES, - "NSMutableParagraphStyle" : YES, - "NSNib" : YES, - "NSNibConnector" : YES, - "NSNibControlConnector" : YES, - "NSNibOutletConnector" : YES, - "NSObjectController" : YES, - "NSOpenGLContext" : YES, - "NSOpenGLLayer" : YES, - "NSOpenGLPixelBuffer" : YES, - "NSOpenGLPixelFormat" : YES, - "NSOpenGLView" : YES, - "NSOpenPanel" : YES, - "NSOutlineView" : YES, - "NSPageLayout" : YES, - "NSPanel" : YES, - "NSParagraphStyle" : YES, - "NSPasteboard" : YES, - "NSPasteboardItem" : YES, - "NSPathCell" : YES, - "NSPathComponentCell" : YES, - "NSPathControl" : YES, - "NSPDFImageRep" : YES, - "NSPersistentDocument" : YES, - "NSPICTImageRep" : YES, - "NSPopUpButton" : YES, - "NSPopUpButtonCell" : YES, - "NSPredicateEditor" : YES, - "NSPredicateEditorRowTemplate" : YES, - "NSPrinter" : YES, - "NSPrintInfo" : YES, - "NSPrintOperation" : YES, - "NSPrintPanel" : YES, - "NSProgressIndicator" : YES, - "NSResponder" : YES, - "NSRuleEditor" : YES, - "NSRulerMarker" : YES, - "NSRulerView" : YES, - "NSRunningApplication" : YES, - "NSSavePanel" : YES, - "NSScreen" : YES, - "NSScroller" : YES, - "NSScrollView" : YES, - "NSSearchField" : YES, - "NSSearchFieldCell" : YES, - "NSSecureTextField" : YES, - "NSSecureTextFieldCell" : YES, - "NSSegmentedCell" : YES, - "NSSegmentedControl" : YES, - "NSShadow" : YES, - "NSSlider" : YES, - "NSSliderCell" : YES, - "NSSound" : YES, - "NSSpeechRecognizer" : YES, - "NSSpeechSynthesizer" : YES, - "NSSpellChecker" : YES, - "NSSplitView" : YES, - "NSStatusBar" : YES, - "NSStatusItem" : YES, - "NSStepper" : YES, - "NSStepperCell" : YES, - "NSString Application Kit Additions" : YES, - "NSTableCellView" : YES, - "NSTableColumn" : YES, - "NSTableHeaderCell" : YES, - "NSTableHeaderView" : YES, - "NSTableView" : YES, - "NSTabView" : YES, - "NSTabViewItem" : YES, - "NSText" : YES, - "NSTextAttachment" : YES, - "NSTextAttachmentCell" : YES, - "NSTextBlock" : YES, - "NSTextContainer" : YES, - "NSTextField" : YES, - "NSTextFieldCell" : YES, - "NSTextInputContext" : YES, - "NSTextList" : YES, - "NSTextStorage" : YES, - "NSTextTab" : YES, - "NSTextTable" : YES, - "NSTextTableBlock" : YES, - "NSTextView" : YES, - "NSTokenField" : YES, - "NSTokenFieldCell" : YES, - "NSToolbar" : YES, - "NSToolbarItem" : YES, - "NSToolbarItemGroup" : YES, - "NSTouch" : YES, - "NSTrackingArea" : YES, - "NSTreeController" : YES, - "NSTreeNode" : YES, - "NSTypesetter" : YES, - "NSURL Additions" : YES, - "NSUserDefaultsController" : YES, - "NSView" : YES, - "NSViewAnimation" : YES, - "NSViewController" : YES, - "NSWindow" : YES, - "NSWindowController" : YES, - "NSWorkspace" : YES, - "NSPopover": YES, - "NSAppearance" : YES, - "NSVisualEffectView" : YES, - }; From 49e5cb64ad5555f0540fc368402934e633246643 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 19 Jul 2025 14:23:52 +0200 Subject: [PATCH 36/40] Revert "removed: unwanted files" This reverts commit 7e99e3ca20f48fde3b91c2b2486b33658f48cb9c. --- dist/cappuccino/bin/flatten | 368 +++++++++++++++ dist/cappuccino/bin/fontinfo | Bin 0 -> 70736 bytes dist/cappuccino/bin/objj2objcskeleton | 623 ++++++++++++++++++++++++++ 3 files changed, 991 insertions(+) create mode 100755 dist/cappuccino/bin/flatten create mode 100755 dist/cappuccino/bin/fontinfo create mode 100755 dist/cappuccino/bin/objj2objcskeleton diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten new file mode 100755 index 000000000..80b9ecf8e --- /dev/null +++ b/dist/cappuccino/bin/flatten @@ -0,0 +1,368 @@ +#!/usr/bin/env objj + +require("narwhal").ensureEngine("rhino"); + +@import + +@import "../lib/cappuccino/objj-analysis-tools.j" + +var FILE = require("file"); +var OS = require("os"); +var UTIL = require("narwhal/util"); + +var CACHEMANIFEST = require("objective-j/cache-manifest"); + +var stream = require("narwhal/term").stream; +var parser = new (require("narwhal/args").Parser)(); + +parser.usage("INPUT_PROJECT OUTPUT_PROJECT"); +parser.help("Combine a Cappuccino application into a single JavaScript file."); + +parser.option("-m", "--main", "main") + .def("main.j") + .set() + .help("The relative path (from INPUT_PROJECT) to the main file (default: 'main.j')"); + +parser.option("-F", "--framework", "frameworks") + .push() + .help("Add a frameworks directory, relative to INPUT_PROJECT (default: ['Frameworks'])"); + +parser.option("-P", "--path", "paths") + .push() + .help("Add a path (relative to the application root) to inline."); + +parser.option("-f", "--force", "force") + .def(false) + .set(true) + .help("Force overwriting OUTPUT_PROJECT if it exists"); + +parser.option("--index", "index") + .def("index.html") + .set() + .help("The root HTML file to modify (default: index.html)"); + +parser.option("-s", "--split", "number", "split") + .natural() + .def(0) + .help("Split into multiple files"); + +parser.option("-c", "--compressor", "compressor") + .def("shrinksafe") + .set() + .help("Select a compressor to use (closure-compiler, yuicompressor, shrinksafe), or \"none\" (default: shrinksafe)"); + +parser.option("--manifest", "manifest") + .set(true) + .help("Generate HTML5 cache manifest."); + +parser.option("-v", "--verbose", "verbose") + .def(false) + .set(true) + .help("Verbose logging"); + +parser.helpful(); + +function main(args) +{ + var options = parser.parse(args); + + if (options.args.length < 2) { + parser.printUsage(options); + return; + } + + var rootPath = FILE.path(options.args[0]).join("").absolute(); + var outputPath = FILE.path(options.args[1]).join("").absolute(); + + if (outputPath.exists()) { + if (options.force) { + // FIXME: why doesn't this work?! + //outputPath.rmtree(); + OS.system(["rm", "-rf", outputPath]); + } else { + stream.print("\0red(OUTPUT_PROJECT " + outputPath + " exists. Use -f to overwrite.\0)"); + OS.exit(1); + } + } + + options.frameworks.push("Frameworks"); + + var mainPath = String(rootPath.join(options.main)); + var frameworks = options.frameworks.map(function(framework) { return rootPath.join(framework); }); + var environment = "Browser"; + + stream.print("\0yellow("+Array(81).join("=")+"\0)"); + stream.print("Application root: \0green(" + rootPath + "\0)"); + stream.print("Output directory: \0green(" + outputPath + "\0)"); + + stream.print("\0yellow("+Array(81).join("=")+"\0)"); + stream.print("Main file: \0green(" + mainPath + "\0)"); + stream.print("Frameworks: \0green(" + frameworks + "\0)"); + stream.print("Environment: \0green(" + environment + "\0)"); + + var flattener = new ObjectiveJFlattener(rootPath); + + flattener.options = options; + + flattener.setIncludePaths(frameworks); + flattener.setEnvironments([environment, "ObjJ"]); + + print("Loading application."); + flattener.load(mainPath); + + print("Loading default theme."); + flattener.require("objective-j").objj_eval("("+(function() { + + var defaultThemeName = [CPApplication defaultThemeName], + bundle = nil; + + if (defaultThemeName === @"Aristo" || defaultThemeName === @"Aristo2") + bundle = [CPBundle bundleForClass:[CPApplication class]]; + else + bundle = [CPBundle mainBundle]; + + var blend = [[CPThemeBlend alloc] initWithContentsOfURL:[bundle pathForResource:defaultThemeName + @".blend"]]; + [blend loadWithDelegate:nil]; + + })+")")(); + + var applicationJSs = flattener.buildApplicationJS(); + + FILE.copyTree(rootPath, outputPath); + + applicationJSs.forEach(function(applicationJS, n) { + var name = "Application"+(n||"")+".js"; + if (options.compressor === "none") { + print("skipping compression: " + name); + } else { + print("compressing: " + name); + applicationJS = require("minify/"+options.compressor).compress(applicationJS, { charset : "UTF-8", useServer : true }); + } + outputPath.join(name).write(applicationJS, { charset : "UTF-8" }); + }); + + rewriteMainHTML(outputPath.join(options.index)); + + if (options.manifest) { + CACHEMANIFEST.generateManifest(outputPath, { + index : outputPath.join(options.index), + exclude : Object.keys(flattener.filesToCache).map(function(path) { return outputPath.join(path).toString(); }) + }); + } +} + +// ObjectiveJFlattener inherits from ObjectiveJRuntimeAnalyzer +function ObjectiveJFlattener(rootPath) { + ObjectiveJRuntimeAnalyzer.apply(this, arguments); + + this.filesToCache = {}; + this.fileCacheBuffer = []; + this.functionsBuffer = []; +} + +ObjectiveJFlattener.prototype = Object.create(ObjectiveJRuntimeAnalyzer.prototype); + +ObjectiveJFlattener.prototype.buildApplicationJS = function() { + + this.setupFileCache(); + this.serializeFunctions(); + this.serializeFileCache(); + + var additions = FILE.read(FILE.join(FILE.dirname(module.path), "..", "..", "cappuccino", "lib", "cappuccino", "objj-flatten-additions.js"), { charset:"UTF-8" }); + + var applicationJSs = []; + + if (this.options.split === 0) { + var buffer = []; + buffer.push("var baseURL = new CFURL(\".\", ObjectiveJ.pageURL);"); + buffer.push(additions); + buffer.push(this.fileCacheBuffer.join("\n")); + buffer.push(this.functionsBuffer.join("\n")); + buffer.push("ObjectiveJ.bootstrap();"); + applicationJSs.push(buffer.join("\n")); + } else { + var appFilesCount = this.options.split; + + var buffers = []; + for (var i = 0; i <= appFilesCount; i++) + buffers.push([]); + + var chunks = this.fileCacheBuffer.concat(this.functionsBuffer).sort(function(chunkA, chunkB) { + return chunkA.length - chunkB.length; + }); + + // try to equally distribute the chunks. could be better but good enough for now. + var n = 0; + while (chunks.length) { + buffers[(n++ % appFilesCount) + 1].push(chunks.pop()); + } + + buffers[0].push("var baseURL = new CFURL(\".\", ObjectiveJ.pageURL);"); + buffers[0].push(additions); + + buffers[0].push("var appFilesCount = " + appFilesCount +";"); + buffers[0].push("for (var i = 1; i <= appFilesCount; i++) {"); + buffers[0].push(" var script = document.createElement(\"script\");"); + buffers[0].push(" script.src = \"Application\"+i+\".js\";"); + buffers[0].push(" script.charset = \"UTF-8\";"); + buffers[0].push(" script.onload = function() { if (--appFilesCount === 0) ObjectiveJ.bootstrap(); };"); + buffers[0].push(" document.getElementsByTagName(\"head\")[0].appendChild(script);"); + buffers[0].push("}"); + + buffers.forEach(function(buffer) { + applicationJSs.push(buffer.join("\n")); + }); + } + + return applicationJSs; +} + +ObjectiveJFlattener.prototype.serializeFunctions = function() { + var inlineFunctions = true;//this.options.inlineFunctions; + + var outputFiles = {}; + + var _cachedExecutableFunctions = {}; + + this.require("objective-j").FileExecutable.allFileExecutables().forEach(function(executable) { + var path = executable.path(); + + if (inlineFunctions) + { + // stringify the function, replacing arguments + var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK + + var relative = this.rootPath.relative(path).toString(); + this.functionsBuffer.push("ObjectiveJ.FileExecutable._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); + } + + var bundle = this.context.global.CFBundle.bundleContainingURL(path); + if (bundle && bundle.infoDictionary()) + { + var executablePath = bundle.executablePath(), + relativeToBundle = FILE.relative(FILE.join(bundle.path(), ""), path); + + if (executablePath) + { + if (inlineFunctions) + { + // remove the code since we're inlining the functions + executable._code = "alert("+JSON.stringify(relativeToBundle)+");"; + } + + if (!outputFiles[executablePath]) + { + outputFiles[executablePath] = []; + outputFiles[executablePath].push("@STATIC;1.0;"); + } + + var fileContents = executable.toMarkedString(); + + outputFiles[executablePath].push("p;" + relativeToBundle.length + ";" + relativeToBundle); + outputFiles[executablePath].push("t;" + fileContents.length + ";" + fileContents); + + // stream.print("Adding \0green(" + this.rootPath.relative(path) + "\0) to \0cyan(" + this.rootPath.relative(executablePath) + "\0)"); + } + } + else + CPLog.warn("No bundle (or info dictionary for) " + rootPath.relative(path)); + }, this); + + for (var executablePath in outputFiles) + { + var relative = this.rootPath.relative(executablePath).toString(); + var contents = outputFiles[executablePath].join(""); + this.filesToCache[relative] = contents; + } +} + +ObjectiveJFlattener.prototype.serializeFileCache = function() { + for (var relative in this.filesToCache) { + var contents = this.filesToCache[relative]; + print("caching: " + relative + " => " + (contents == null ? 404 : 200)); + if (contents == null) + this.fileCacheBuffer.push("CFHTTPRequest._cacheRequest(new CFURL("+JSON.stringify(relative)+", baseURL), 404);"); + else + this.fileCacheBuffer.push("CFHTTPRequest._cacheRequest(new CFURL("+JSON.stringify(relative)+", baseURL), 200, {}, "+JSON.stringify(contents)+");"); + } +} + +ObjectiveJFlattener.prototype.setupFileCache = function() { + var paths = {}; + + UTIL.update(paths, this.requestedURLs); + + this.options.paths.forEach(function(relativePath) { + paths[this.rootPath.join(relativePath)] = true; + }, this); + + Object.keys(paths).forEach(function(absolute) { + var relative = this.rootPath.relative(absolute).toString(); + if (relative.indexOf("..") === 0) + { + print("skipping (parent of app root): " + absolute); + return; + } + + if (FILE.isFile(absolute)) + { + // if (this.options.maxCachedSize && FILE.size(absolute) > this.options.maxCachedSize) + // { + // print("skipping (larger than "+this.options.maxCachedSize+" bytes): " + absolute); + // return; + // } + + var contents = FILE.read(absolute, { charset : "UTF-8" }); + this.filesToCache[relative] = contents; + } else { + this.filesToCache[relative] = null; + } + }, this); +} + +// "$1" is the matching indentation +var scriptTagsBefore = + '$1'; + +var scriptTagsAfter = + '$1'; + +// enable CPLog: +// scriptTagsAfter = '$1\n' + scriptTagsAfter; + +function rewriteMainHTML(indexHTMLPath) { + if (indexHTMLPath.isFile()) { + var indexHTML = indexHTMLPath.read({ charset : "UTF-8" }); + + // inline the Application.js if it's smallish + var applicationJSPath = indexHTMLPath.dirname().join("Application.js"); + if (applicationJSPath.size() < 10*1024) { + // escape any dollar signs by replacing them with two + // then indent by splitting/joining on newlines + scriptTagsAfter = + '$1'; + } + + // attempt to find Objective-J script tag and add ours + var newIndexHTML = indexHTML.replace(/([ \t]+)]+Objective-J\.js[^>]+>(?:\s*<\/script>)?/, + scriptTagsBefore+'\n$&\n'+scriptTagsAfter); + + if (newIndexHTML !== indexHTML) { + stream.print("\0green(Modified: "+indexHTMLPath+".\0)"); + indexHTMLPath.write(newIndexHTML, { charset : "UTF-8" }); + return; + } + } else { + stream.print("\0yellow(Warning: "+indexHTMLPath+" does not exist. Specify an alternate index HTML file with the --index option.\0)"); + } + + stream.print("\0yellow(Warning: Unable to automatically modify "+indexHTMLPath + ".\0)"); + stream.print("\nAdd the following before the Objective-J script tag:"); + stream.print(scriptTagsBefore.replace(/\$1/g, " ")); + stream.print("\nAdd the following after the Objective-J script tag:"); + stream.print(scriptTagsAfter.replace(/\$1/g, " ")); +} diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo new file mode 100755 index 0000000000000000000000000000000000000000..1a91ba1c8040bbcfe787719cb7e44c0e7578d180 GIT binary patch literal 70736 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 literal 0 HcmV?d00001 diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton new file mode 100755 index 000000000..51ecab38d --- /dev/null +++ b/dist/cappuccino/bin/objj2objcskeleton @@ -0,0 +1,623 @@ +#!/usr/bin/env objj + +@import + +var fs = require("fs"), + acorn = require("objj-parser"), + walk = require("acorn-walk"), + stream = ObjectiveJ.term; + +function main(args) +{ + args.shift(); + + if (args.length < 1) + return printUsage(); + + parser(args); +} + +function printUsage() +{ + console.log("objj2objskeleton [FILE] [DESTINATION]"); + console.log("Convert a objective-j file to an objective-c files skeleton (.h and .m)") +} + +// Debug function to print some JS objects +function dump(obj) +{ + console.log(JSON.stringify(obj)); +} + +function raise(pos, message) +{ + var syntaxError = new SyntaxError(message); + syntaxError.line = pos.line; + + throw syntaxError; +} + +function ignore(_node, _st, _c) {} + +var errors = [], + xcc = walk.make( + { + ClassDeclarationStatement: function(node, st, c) + { + var className = node.classname.name, + superclassname = node.superclassname ? node.superclassname.name : "", + declaredOutletsName = [], + classInfo = { + "name": className, + "category": node.categoryname ? node.categoryname.name : "", + "superClass": superclassname, + "outlets": [], + "actions": [], + "actionNames": [] + }; + + if (node.ivardeclarations) + { + for (var i = 0; i < node.ivardeclarations.length; ++i) + { + var ivarDecl = node.ivardeclarations[i], + ivarType = ivarDecl.ivartype ? ivarDecl.ivartype.name : null, + ivarName = ivarDecl.id.name, + ivarHasOutlet = ivarDecl.outlet ? "@outlet" : null; + + if (ivarHasOutlet) + { + if (declaredOutletsName.indexOf(ivarName) !== -1) + raise(ivarDecl.loc.start, "Outlet '" + ivarName + "' declared more than once"); + + declaredOutletsName.push(ivarName); + classInfo.outlets.push({"type": ivarType, "name": ivarName}); + } + } + } + + st.push(classInfo) + + for (var i = 0; i < node.body.length; ++i) + c(node.body[i], classInfo, "Statement"); + }, + + MethodDeclarationStatement: function(node, st, c) + { + var selectors = node.selectors, + arguments = node.arguments, + //methodReturnType = [node.returntype ? node.returntype.name : "id"], + methodHasAction = node.action ? "IBAction" : null, + selector = selectors[0].name, + actionInfo = {"name": selector, "arguments":[]}; + + if (methodHasAction) + { + if (arguments.length == 1) + { + if (st.actionNames.indexOf(selector) !== -1) + raise(node.loc.start, "Action '" + selector + "' declared more than once"); + + st.actionNames.push(selector); + + for (var i = 0; i < arguments.length; i++) + { + var argument = arguments[i], + argumentName = argument.identifier.name, + argumentType = argument.type ? argument.type.name : null; + + actionInfo.arguments.push({"type": argumentType, "name": argumentName}); + } + + st.actions.push(actionInfo) + } + else + raise(node.loc.start, "Action methods must have exactly one parameter"); + } + }, + TypeDefStatement: ignore, + ClassStatement: ignore, + MessageSendExpression: ignore, + GlobalStatement: ignore, + ProtocolDeclarationStatement: ignore, + ArrayLiteral: ignore, + Reference: ignore, + DictionaryLiteral: ignore, + Dereference: ignore, + ImportStatement: ignore, + SelectorLiteralExpression: ignore + } +); + +function compile(node, state, visitor) +{ + function c(node, st, override) + { + visitor[override || node.type](node, st, c); + } + + c(node, state); +}; + +function removeLastSlashIfNecessary(path) +{ + if (path[path.length - 1] == "/") + return path.substring(0, path.length - 1); + + return path; +} + +/* + $1 Full project source path + $2 Destination + $-n name of the cocoa files +*/ +function parser(args) +{ + try + { + var sourcePath = args.shift(), + projectBasePath = removeLastSlashIfNecessary(args.shift()), + outputDirectory = projectBasePath, + baseFilename = [sourcePath lastPathComponent], + baseFilenameWithNoExtension = args.shift() == "-n" ? args.shift() : baseFilename.substring(0, baseFilename.length - 2), + outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), + outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), + source = fs.readFileSync(sourcePath, { encoding: "utf8" }), + tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath, ecmaVersion: 2022 }), + classesInformation = [], + ObjectiveCSource = "", + ObjectiveCHeader = "", + hasErrors = NO; + + compile(tokens, classesInformation, xcc); + + // dump(classesInformation) + + ObjectiveCHeader += + "#import \n" + + '#import "xcc_general_include.h"\n'; + + ObjectiveCSource += "#import \"" + outputHeaderURL.lastPathComponent() + "\"\n"; + + // Traverse each found classes + classesInformation.forEach(function(aClass) + { + // add new class definition + if (aClass.superClass) + ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ : %@", aClass.name, NSCompatibleClassName(aClass.superClass)]; + else + ObjectiveCHeader += [CPString stringWithFormat:@"\n@interface %@ (%@)", NSCompatibleClassName(aClass.name, NO), aClass.category]; + + // add each outlet in header + if (aClass.outlets.length > 0) + ObjectiveCHeader += "\n"; + + aClass.outlets.forEach(function(anOutlet) + { + ObjectiveCHeader += [CPString stringWithFormat:@"\n@property (assign) IBOutlet %@ %@;", NSCompatibleClassName(anOutlet.type, YES), anOutlet.name]; + }); + + if (aClass.actions.length > 0) + ObjectiveCHeader += "\n"; + + // add each action in header + aClass.actions.forEach(function(anAction) + { + ObjectiveCHeader += [CPString stringWithFormat:@"\n- (IBAction)%@:(%@)%@;", anAction.name, anAction.arguments[0].type, anAction.arguments[0].name]; + }); + + if (aClass.outlets.length > 0 || aClass.actions.length > 0) + ObjectiveCHeader += "\n"; + + ObjectiveCHeader += "\n@end\n"; + + // fill up the implementation file + ObjectiveCSource += "\n@implementation " + NSCompatibleClassName(aClass.name, NO) + "\n@end\n"; + }); + + if (ObjectiveCSource.length) + fs.writeFileSync(outputImplementationURL.absoluteString(), ObjectiveCSource, 'utf8'); + + if (ObjectiveCHeader.length) + fs.writeFileSync(outputHeaderURL.absoluteString(), ObjectiveCHeader, 'utf8'); + } + catch (e) + { + [errors addObject:@{ + @"message": e.message, + @"sourcePath": sourcePath, + @"line": e.line + }]; + + hasErrors = YES; + } + + if ([errors count]) + { + var plist = [CPPropertyListSerialization dataFromPropertyList:errors format:CPPropertyListXMLFormat_v1_0]; + + stream.printError([plist rawString]); + + // If there were category warnings, hasErrors is NO, so return a warning status + process.exit(hasErrors ? 1 : 2); + } +} + +function NSCompatibleClassName(aClassName, asPointer) +{ + if (aClassName === "var" || aClassName === "id") + return "id"; + + var prefix = aClassName.substr(0, 2), + asterisk = asPointer ? "*" : ""; + + if (prefix !== "CP") + return aClassName + asterisk; + + var NSClassName = "NS" + aClassName.substr(2); + + if (NSClasses[NSClassName]) + return NSClassName + asterisk; + + if (ReplacementClasses[aClassName]) + return ReplacementClasses[aClassName] + asterisk; + + return aClassName + asterisk; +} + +var ReplacementClasses = { + "CPWebView": "WebView", + "CPRadio": "NSButtonCell", + "CPRadioGroup": "NSMatrix" + }; + +var NSClasses = { + "NSAffineTransform" : YES, + "NSAppleEventDescriptor" : YES, + "NSAppleEventManager" : YES, + "NSAppleScript" : YES, + "NSArchiver" : YES, + "NSArray" : YES, + "NSAssertionHandler" : YES, + "NSAttributedString" : YES, + "NSAutoreleasePool" : YES, + "NSBlockOperation" : YES, + "NSBundle" : YES, + "NSCache" : YES, + "NSCachedURLResponse" : YES, + "NSCalendar" : YES, + "NSCharacterSet" : YES, + "NSClassDescription" : YES, + "NSCloneCommand" : YES, + "NSCloseCommand" : YES, + "NSCoder" : YES, + "NSComparisonPredicate" : YES, + "NSCompoundPredicate" : YES, + "NSCondition" : YES, + "NSConditionLock" : YES, + "NSConnection" : YES, + "NSCountCommand" : YES, + "NSCountedSet" : YES, + "NSCreateCommand" : YES, + "NSData" : YES, + "NSDate" : YES, + "NSDateComponents" : YES, + "NSDateFormatter" : YES, + "NSDecimalNumber" : YES, + "NSDecimalNumberHandler" : YES, + "NSDeleteCommand" : YES, + "NSDeserializer" : YES, + "NSDictionary" : YES, + "NSDirectoryEnumerator" : YES, + "NSDistantObject" : YES, + "NSDistantObjectRequest" : YES, + "NSDistributedLock" : YES, + "NSDistributedNotificationCenter" : YES, + "NSEnumerator" : YES, + "NSError" : YES, + "NSException" : YES, + "NSExistsCommand" : YES, + "NSExpression" : YES, + "NSFileHandle" : YES, + "NSFileManager" : YES, + "NSFileWrapper" : YES, + "NSFormatter" : YES, + "NSGarbageCollector" : YES, + "NSGetCommand" : YES, + "NSHashTable" : YES, + "NSHost" : YES, + "NSHTTPCookie" : YES, + "NSHTTPCookieStorage" : YES, + "NSHTTPURLResponse" : YES, + "NSIndexPath" : YES, + "NSIndexSet" : YES, + "NSIndexSpecifier" : YES, + "NSInputStream" : YES, + "NSInvocation" : YES, + "NSInvocationOperation" : YES, + "NSKeyedArchiver" : YES, + "NSKeyedUnarchiver" : YES, + "NSLocale" : YES, + "NSLock" : YES, + "NSLogicalTest" : YES, + "NSMachBootstrapServer" : YES, + "NSMachPort" : YES, + "NSMapTable" : YES, + "NSMessagePort" : YES, + "NSMessagePortNameServer" : YES, + "NSMetadataItem" : YES, + "NSMetadataQuery" : YES, + "NSMetadataQueryAttributeValueTuple" : YES, + "NSMetadataQueryResultGroup" : YES, + "NSMethodSignature" : YES, + "NSMiddleSpecifier" : YES, + "NSMoveCommand" : YES, + "NSMutableArray" : YES, + "NSMutableAttributedString" : YES, + "NSMutableCharacterSet" : YES, + "NSMutableData" : YES, + "NSMutableDictionary" : YES, + "NSMutableIndexSet" : YES, + "NSMutableSet" : YES, + "NSMutableString" : YES, + "NSMutableURLRequest" : YES, + "NSNameSpecifier" : YES, + "NSNetService" : YES, + "NSNetServiceBrowser" : YES, + "NSNotification" : YES, + "NSNotificationCenter" : YES, + "NSNotificationQueue" : YES, + "NSNull" : YES, + "NSNumber" : YES, + "NSNumberFormatter" : YES, + "NSObject" : YES, + "NSOperation" : YES, + "NSOperationQueue" : YES, + "NSOrthography" : YES, + "NSOutputStream" : YES, + "NSPipe" : YES, + "NSPointerArray" : YES, + "NSPointerFunctions" : YES, + "NSPort" : YES, + "NSPortCoder" : YES, + "NSPortMessage" : YES, + "NSPortNameServer" : YES, + "NSPositionalSpecifier" : YES, + "NSPredicate" : YES, + "NSProcessInfo" : YES, + "NSPropertyListSerialization" : YES, + "NSPropertySpecifier" : YES, + "NSProtocolChecker" : YES, + "NSProxy" : YES, + "NSPurgeableData" : YES, + "NSQuitCommand" : YES, + "NSRandomSpecifier" : YES, + "NSRangeSpecifier" : YES, + "NSRecursiveLock" : YES, + "NSRelativeSpecifier" : YES, + "NSRunLoop" : YES, + "NSScanner" : YES, + "NSScriptClassDescription" : YES, + "NSScriptCoercionHandler" : YES, + "NSScriptCommand" : YES, + "NSScriptCommandDescription" : YES, + "NSScriptExecutionContext" : YES, + "NSScriptObjectSpecifier" : YES, + "NSScriptSuiteRegistry" : YES, + "NSScriptWhoseTest" : YES, + "NSSerializer" : YES, + "NSSet" : YES, + "NSSetCommand" : YES, + "NSSocketPort" : YES, + "NSSocketPortNameServer" : YES, + "NSSortDescriptor" : YES, + "NSSpecifierTest" : YES, + "NSSpellServer" : YES, + "NSStream" : YES, + "NSString" : YES, + "NSTask" : YES, + "NSTextCheckingResult" : YES, + "NSThread" : YES, + "NSTimer" : YES, + "NSTimeZone" : YES, + "NSUnarchiver" : YES, + "NSUndoManager" : YES, + "NSUniqueIDSpecifier" : YES, + "NSURL" : YES, + "NSURLAuthenticationChallenge" : YES, + "NSURLCache" : YES, + "NSURLConnection" : YES, + "NSURLCredential" : YES, + "NSURLCredentialStorage" : YES, + "NSURLDownload" : YES, + "NSURLHandle" : YES, + "NSURLProtectionSpace" : YES, + "NSURLProtocol" : YES, + "NSURLRequest" : YES, + "NSURLResponse" : YES, + "NSUserDefaults" : YES, + "NSValue" : YES, + "NSValueTransformer" : YES, + "NSWhoseSpecifier" : YES, + "NSXMLDocument" : YES, + "NSXMLDTD" : YES, + "NSXMLDTDNode" : YES, + "NSXMLElement" : YES, + "NSXMLNode" : YES, + "NSXMLParser" : YES, + "NSActionCell" : YES, + "NSAffineTransform Additions" : YES, + "NSAlert" : YES, + "NSAnimation" : YES, + "NSAnimationContext" : YES, + "NSAppleScript Additions" : YES, + "NSApplication" : YES, + "NSArrayController" : YES, + "NSATSTypesetter" : YES, + "NSAttributedString Application Kit Additions" : YES, + "NSBezierPath" : YES, + "NSBitmapImageRep" : YES, + "NSBox" : YES, + "NSBrowser" : YES, + "NSBrowserCell" : YES, + "NSBundle Additions" : YES, + "NSButton" : YES, + "NSButtonCell" : YES, + "NSCachedImageRep" : YES, + "NSCell" : YES, + "NSCIImageRep" : YES, + "NSClipView" : YES, + "NSCoder Application Kit Additions" : YES, + "NSCollectionView" : YES, + "NSCollectionViewItem" : YES, + "NSColor" : YES, + "NSColorList" : YES, + "NSColorPanel" : YES, + "NSColorPicker" : YES, + "NSColorSpace" : YES, + "NSColorWell" : YES, + "NSComboBox" : YES, + "NSComboBoxCell" : YES, + "NSControl" : YES, + "NSController" : YES, + "NSCursor" : YES, + "NSCustomImageRep" : YES, + "NSDatePicker" : YES, + "NSDatePickerCell" : YES, + "NSDictionaryController" : YES, + "NSDockTile" : YES, + "NSDocument" : YES, + "NSDocumentController" : YES, + "NSDrawer" : YES, + "NSEPSImageRep" : YES, + "NSEvent" : YES, + "NSFileWrapper" : YES, + "NSFont" : YES, + "NSFontDescriptor" : YES, + "NSFontManager" : YES, + "NSFontPanel" : YES, + "NSForm" : YES, + "NSFormCell" : YES, + "NSGlyphGenerator" : YES, + "NSGlyphInfo" : YES, + "NSGradient" : YES, + "NSGraphicsContext" : YES, + "NSHelpManager" : YES, + "NSImage" : YES, + "NSImageCell" : YES, + "NSImageRep" : YES, + "NSImageView" : YES, + "NSLayoutManager" : YES, + "NSLevelIndicator" : YES, + "NSLevelIndicatorCell" : YES, + "NSMatrix" : YES, + "NSMenu" : YES, + "NSMenuItem" : YES, + "NSMenuItemCell" : YES, + "NSMenuView" : YES, + "NSMutableAttributedString Additions" : YES, + "NSMutableParagraphStyle" : YES, + "NSNib" : YES, + "NSNibConnector" : YES, + "NSNibControlConnector" : YES, + "NSNibOutletConnector" : YES, + "NSObjectController" : YES, + "NSOpenGLContext" : YES, + "NSOpenGLLayer" : YES, + "NSOpenGLPixelBuffer" : YES, + "NSOpenGLPixelFormat" : YES, + "NSOpenGLView" : YES, + "NSOpenPanel" : YES, + "NSOutlineView" : YES, + "NSPageLayout" : YES, + "NSPanel" : YES, + "NSParagraphStyle" : YES, + "NSPasteboard" : YES, + "NSPasteboardItem" : YES, + "NSPathCell" : YES, + "NSPathComponentCell" : YES, + "NSPathControl" : YES, + "NSPDFImageRep" : YES, + "NSPersistentDocument" : YES, + "NSPICTImageRep" : YES, + "NSPopUpButton" : YES, + "NSPopUpButtonCell" : YES, + "NSPredicateEditor" : YES, + "NSPredicateEditorRowTemplate" : YES, + "NSPrinter" : YES, + "NSPrintInfo" : YES, + "NSPrintOperation" : YES, + "NSPrintPanel" : YES, + "NSProgressIndicator" : YES, + "NSResponder" : YES, + "NSRuleEditor" : YES, + "NSRulerMarker" : YES, + "NSRulerView" : YES, + "NSRunningApplication" : YES, + "NSSavePanel" : YES, + "NSScreen" : YES, + "NSScroller" : YES, + "NSScrollView" : YES, + "NSSearchField" : YES, + "NSSearchFieldCell" : YES, + "NSSecureTextField" : YES, + "NSSecureTextFieldCell" : YES, + "NSSegmentedCell" : YES, + "NSSegmentedControl" : YES, + "NSShadow" : YES, + "NSSlider" : YES, + "NSSliderCell" : YES, + "NSSound" : YES, + "NSSpeechRecognizer" : YES, + "NSSpeechSynthesizer" : YES, + "NSSpellChecker" : YES, + "NSSplitView" : YES, + "NSStatusBar" : YES, + "NSStatusItem" : YES, + "NSStepper" : YES, + "NSStepperCell" : YES, + "NSString Application Kit Additions" : YES, + "NSTableCellView" : YES, + "NSTableColumn" : YES, + "NSTableHeaderCell" : YES, + "NSTableHeaderView" : YES, + "NSTableView" : YES, + "NSTabView" : YES, + "NSTabViewItem" : YES, + "NSText" : YES, + "NSTextAttachment" : YES, + "NSTextAttachmentCell" : YES, + "NSTextBlock" : YES, + "NSTextContainer" : YES, + "NSTextField" : YES, + "NSTextFieldCell" : YES, + "NSTextInputContext" : YES, + "NSTextList" : YES, + "NSTextStorage" : YES, + "NSTextTab" : YES, + "NSTextTable" : YES, + "NSTextTableBlock" : YES, + "NSTextView" : YES, + "NSTokenField" : YES, + "NSTokenFieldCell" : YES, + "NSToolbar" : YES, + "NSToolbarItem" : YES, + "NSToolbarItemGroup" : YES, + "NSTouch" : YES, + "NSTrackingArea" : YES, + "NSTreeController" : YES, + "NSTreeNode" : YES, + "NSTypesetter" : YES, + "NSURL Additions" : YES, + "NSUserDefaultsController" : YES, + "NSView" : YES, + "NSViewAnimation" : YES, + "NSViewController" : YES, + "NSWindow" : YES, + "NSWindowController" : YES, + "NSWorkspace" : YES, + "NSPopover": YES, + "NSAppearance" : YES, + "NSVisualEffectView" : YES, + }; From c077863e7af86a2d67b226e1d85bab87ab14f585 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 19 Jul 2025 14:29:46 +0200 Subject: [PATCH 37/40] fixed: accidental changes --- dist/cappuccino/bin/flatten | 2 +- dist/cappuccino/bin/fontinfo | Bin 70736 -> 69888 bytes dist/cappuccino/bin/objj2objcskeleton | 22 ++++++---------------- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/dist/cappuccino/bin/flatten b/dist/cappuccino/bin/flatten index 80b9ecf8e..1df033252 100755 --- a/dist/cappuccino/bin/flatten +++ b/dist/cappuccino/bin/flatten @@ -233,7 +233,7 @@ ObjectiveJFlattener.prototype.serializeFunctions = function() { var functionString = executable._function.toString().replace(", require, exports, module, system, print, window", ""); // HACK var relative = this.rootPath.relative(path).toString(); - this.functionsBuffer.push("ObjectiveJ.FileExecutable._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); + this.functionsBuffer.push("ObjectiveJ.StaticResource._cacheFunction(new CFURL("+JSON.stringify(relative)+", baseURL),\n"+functionString+");"); } var bundle = this.context.global.CFBundle.bundleContainingURL(path); diff --git a/dist/cappuccino/bin/fontinfo b/dist/cappuccino/bin/fontinfo index 1a91ba1c8040bbcfe787719cb7e44c0e7578d180..f1ff06158a2525d34541aff240713c0067bd8633 100755 GIT binary patch delta 3394 zcmZ`*3s6+o89w`DsqFGx+h670NB67 z0BER_-s?-eqvet#M>G=>)tO2(O5#IV%?yIz_H@(>o1IN<&UUZ~LaljSEpq}>J}u!b zVr7xn4nNt^c`Qus7M+>LN3Ns}%e^# zxB<8vC=Ez+f{-lWvAl8B06|2vfltV9N$Vy>N(-^UM2r-KiqFN|))av-q#`Yxy;N9bap; zbl)tk_TTZ}vjob6WVpw18Q|(jmB3tzoS61`(Bi_#J{o&92UPh z1kMEhI4Gn~<~Ls&Pedbs907*Sm!KzryabTV!B&|iP}1kEA|#@JLkYP4ll)U(?U-NX zAFB>59U7~hoVp=??dgX2CkHG6b144R477I-%gz1=@l%E%bP;IYh+YSQ<68b0W_{yhNVO;gS%)DY9|CFu^Cw_= zYy98vk0JR4z<0VfKmeuBn;21b1(;^GjCA5h9;5Oo%! z21?$Q5G{eau*LtS6tcp9AL}t6+`C0LOp)vbxba+Y#jpKaRba;@D5&``bSwIxt2;VA z7m6Rkl)~s0(;9<9JrV}$1}Ip05ODcBLvl+{l?1>wLG3sMukeR!;;Ta7kO9ZS>T&*{ z{zEzOLM8vvr;O+WxQhNk^R7t8bcndWOpKTit&nIF3W1UdG-GYnqaG}e!0VceCM^D; zqR<$gLFfq+Os>atD9(nKXg?6aV+ z31%YzZ;ceFP(~ny?u;8q%gFHMF*P z*19~KSK6ALWp3At&N8}5o0%b<<}Q0}yUpcs6Pw%aY;nLP>2T5{?Mzys&7!~17U$a9 z+Q4r0w7Xgw%Sc`AGo_MD95AkhP+wdpy{^rjho{UUm=x(7xTUe73@bt6;B-ll&Mk6b zok~V|ozWY{djeYYs%w5>{$De{>i_urm>(8vvT=m;M>tsp975~7 z#q3f}VLXR%BjZaMk6zvlOz&cR7vq%i1C0NM@!vCkj`5p}$4H6gkT{lM4&(C}UyNKl zEc;jK_Gyz6^9p^6wAC}orWEl_k~sS8sTu0k?y#(fqfMs_THkqCezBqDpP?MD&)CBW z&n;VCDOA?*R;!RNthBNPoEn_WNf&anyU_~EP1-duOuVAoolQ=g+evy-I7e5LL;9bp zcZpl2N6!J=GqAsS4{VV74v!Nz8S!0>rjZSKJUTtb6L%kGxNjgw(UZ#2PX;pOpvDj7 zZEQD$6lpz#xJe2p*gUNbq=&dQ(fn*pO|5pcB>cc4xLRBuG^a()t!*Bm#km!21^{Y< z?dt(3`fwo4I~_hNsom+2_*U2)IY80>&K)4@bVU41z^&d{@~((mC%WL|DnN|GIIl{@Jw!dTys4T{x9RzodJaZ z4tRN|17!j|7So@7$|alo*un|T`0c_^6Rs%KW{b_^Bnv#vZFED}sE~LjJrZ7`>?}12 z^j0`ibN5PI2(t0gw9_+V&CZvc1KBrsh$+yI+k6k&Di|x%8J~FIq82mvzXg|eCJcY(|mto zMx}1;LO?SZmK-?9EuXVz>5=5^X}>L7k@!{fg5Ehp)(=xh-#lb|+cpY89Bmol413={ z-q`FoHlKWPG2x=~EpMW(JNwJmf~muwCO=3yz5WY&c7)^fYphqZ_NR{CzQ7&xoUOR< zAJ@MQUj86?*7l0)Tdq}e?|z(>fA3J|=hi;mdw(7(Fm6j396lJl*4(&vgpTzG?tKj) zn+%nU^QL|O$r072fIIj3og;U;LIdSB8QV|n`;}GwcKVJL7_=y2daz~N;7i#ZKlc9g ziFc;uy>z+dxtB-kDrb~^yD_)tRtD4(7aR|Ea`(y$F95qo>p%S?-EgKtQCwuCBWGsE Q%I;p7hEba639vimze2ENzW@LL literal 70736 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 diff --git a/dist/cappuccino/bin/objj2objcskeleton b/dist/cappuccino/bin/objj2objcskeleton index 51ecab38d..b7972dbb3 100755 --- a/dist/cappuccino/bin/objj2objcskeleton +++ b/dist/cappuccino/bin/objj2objcskeleton @@ -4,11 +4,14 @@ var fs = require("fs"), acorn = require("objj-parser"), - walk = require("acorn-walk"), + walk = require("objj-parser/util/walk"), stream = ObjectiveJ.term; + debugger; + function main(args) { + debugger; args.shift(); if (args.length < 1) @@ -37,8 +40,6 @@ function raise(pos, message) throw syntaxError; } -function ignore(_node, _st, _c) {} - var errors = [], xcc = walk.make( { @@ -114,18 +115,7 @@ var errors = [], else raise(node.loc.start, "Action methods must have exactly one parameter"); } - }, - TypeDefStatement: ignore, - ClassStatement: ignore, - MessageSendExpression: ignore, - GlobalStatement: ignore, - ProtocolDeclarationStatement: ignore, - ArrayLiteral: ignore, - Reference: ignore, - DictionaryLiteral: ignore, - Dereference: ignore, - ImportStatement: ignore, - SelectorLiteralExpression: ignore + } } ); @@ -164,7 +154,7 @@ function parser(args) outputHeaderURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".h"]), outputImplementationURL = new CFURL([outputDirectory stringByAppendingPathComponent:baseFilenameWithNoExtension + ".m"]), source = fs.readFileSync(sourcePath, { encoding: "utf8" }), - tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath, ecmaVersion: 2022 }), + tokens = acorn.parse(source, { locations:true, sourceFile:sourcePath }), classesInformation = [], ObjectiveCSource = "", ObjectiveCHeader = "", From 56eb096903022f410d1b55bcedaab86b058a41e1 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 19 Jul 2025 14:44:38 +0200 Subject: [PATCH 38/40] fixed: accidental delete --- dist/cappuccino/bin/imagesize | Bin 0 -> 69536 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 dist/cappuccino/bin/imagesize diff --git a/dist/cappuccino/bin/imagesize b/dist/cappuccino/bin/imagesize new file mode 100755 index 0000000000000000000000000000000000000000..5d67a1ce0a9a1e20577f82ccdfe0d3b3e94143b4 GIT binary patch literal 69536 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 Z009sH0T2KI5C8!X009sHfo}nU{{v@rhV}pe literal 0 HcmV?d00001 From 568d0faef6a69d7ca20a1c12b73573efc2eea86e Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 6 Aug 2025 17:19:14 +0200 Subject: [PATCH 39/40] fixed: missing parameter to CPEvent initializer --- AppKit/CPTextField.j | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AppKit/CPTextField.j b/AppKit/CPTextField.j index 4c66ddb3f..933f2b9c0 100644 --- a/AppKit/CPTextField.j +++ b/AppKit/CPTextField.j @@ -332,7 +332,8 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder"); characters:nil charactersIgnoringModifiers:nil isARepeat:NO - keyCode:nil]; + keyCode:nil + isActionKey:NO]; [CPTextFieldInputOwner keyUp:cappEvent]; From 13688507dc4715bcd3394edbc16efafb6a94d25b Mon Sep 17 00:00:00 2001 From: daboe01 Date: Thu, 7 Aug 2025 10:01:03 +0200 Subject: [PATCH 40/40] new: restore old CPEvent factory method for backwards compatibility --- AppKit/CPEvent.j | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/AppKit/CPEvent.j b/AppKit/CPEvent.j index 41d1cefef..1d486521a 100644 --- a/AppKit/CPEvent.j +++ b/AppKit/CPEvent.j @@ -125,6 +125,16 @@ var _CPEventPeriodicEventPeriod = 0, 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 +{ + 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]; +} + /*! Creates a new mouse event.