From 5fff34be914abb09e8305eafe4b97ea3cf8ae7b7 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 14:50:38 +0200 Subject: [PATCH 01/35] new: CPLanguageModel and manual test --- Foundation/CPLanguageModel.j | 605 ++++++++++++++++++ .../CPLanguageModelChatbot/AppController.j | 572 +++++++++++++++++ .../Manual/CPLanguageModelChatbot/Info.plist | 12 + Tests/Manual/CPLanguageModelChatbot/Jakefile | 94 +++ .../Resources/spinner.gif | Bin 0 -> 1434 bytes .../CPLanguageModelChatbot/index-debug.html | 103 +++ .../Manual/CPLanguageModelChatbot/index.html | 166 +++++ Tests/Manual/CPLanguageModelChatbot/main.j | 18 + 8 files changed, 1570 insertions(+) create mode 100644 Foundation/CPLanguageModel.j create mode 100644 Tests/Manual/CPLanguageModelChatbot/AppController.j create mode 100644 Tests/Manual/CPLanguageModelChatbot/Info.plist create mode 100644 Tests/Manual/CPLanguageModelChatbot/Jakefile create mode 100644 Tests/Manual/CPLanguageModelChatbot/Resources/spinner.gif create mode 100644 Tests/Manual/CPLanguageModelChatbot/index-debug.html create mode 100644 Tests/Manual/CPLanguageModelChatbot/index.html create mode 100644 Tests/Manual/CPLanguageModelChatbot/main.j diff --git a/Foundation/CPLanguageModel.j b/Foundation/CPLanguageModel.j new file mode 100644 index 000000000..d39254ffd --- /dev/null +++ b/Foundation/CPLanguageModel.j @@ -0,0 +1,605 @@ +/* + * CPLanguageModel.j + * Foundation + * + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + */ + +@import +@import +@import +@import +@import +@import + +// File-scoped fallback configuration parameters +var CPLanguageModelSessionFallbackServiceType = @"ollama", + CPLanguageModelSessionFallbackEndpoint = @"http://localhost:11434/api/generate", + CPLanguageModelSessionFallbackModel = @"gemma4:e4b", + CPLanguageModelSessionFallbackAPIKey = @"", + CPLanguageModelSessionFallbackAPIKeyUserDefaultKey = @"", + CPLanguageModelSessionEndorsesFallback = NO; + + +/*! + @ingroup foundation + @class CPSystemLanguageModel + + CPSystemLanguageModel provides a standard query interface to inspect the + availability of client-side, on-device large language models (such as Gemini Nano) + in the active web browser runtime. +*/ +@implementation CPSystemLanguageModel : CPObject + +var sharedInstance = nil; + +/*! + Returns the singleton system language model monitor. + @return the default CPSystemLanguageModel instance +*/ ++ (id)defaultModel +{ + + if (!sharedInstance) + sharedInstance = [[CPSystemLanguageModel alloc] init]; + + return sharedInstance; +} + +/*! + Asynchronously queries the active browser environment to determine if on-device + language models are supported and readily available to execute prompts. + @param completionHandler a callback block executed with a boolean parameter (supported) +*/ +- (void)supportsLocaleWithCompletionHandler:(Function)completionHandler +{ + if (typeof window === "undefined" || !completionHandler) + { + if (completionHandler) + completionHandler(NO); + + return; + } + + (async function() { + var supported = false; + + try { + if (window.ai && window.ai.languageModel) { + // Pass language options to align with the creation options + var options = { + expectedInputs: [{ type: "text", languages: ["en"] }], + expectedOutputs: [{ type: "text", languages: ["en"] }] + }; + + if (typeof window.ai.languageModel.availability === 'function') { + var avail = await window.ai.languageModel.availability(options); + supported = (avail === "readily" || avail === "available" || avail === "after-download"); + } else if (typeof window.ai.languageModel.capabilities === 'function') { + var caps = await window.ai.languageModel.capabilities(options); + supported = (caps.available === "readily" || caps.available === "after-download"); + } else { + supported = true; + } + } + else if (window.LanguageModel) { + supported = true; + } + } catch (e) { + supported = false; + } + + completionHandler(supported); + })(); +} + +@end + + +/*! + @ingroup foundation + @class CPLanguageModelSession + + CPLanguageModelSession manages an active session with a local on-device + language model. If the active browser does not support on-device models, the session + gracefully and transparently falls back to configured remote server endpoints. + + @discussion + CPLanguageModelSession handles text generation prompts. If on-device AI + (like Gemini Nano) is supported by the browser, it is utilized directly. + Otherwise, or if CPLanguageModelSessionEndorsesFallback is configured to YES, + the session automatically falls back to configured network-based providers + (such as local Ollama, Groq, or OpenRouter). + + Fallback configurations can be populated globally using the application's Info.plist + via the following keys: +
+    CPEndorseLanguageModelFallback - YES to bypass on-device models and force fallback
+    CPDefaultLanguageModelService - "ollama" | "groq" | "gemini" | "openrouter"
+    CPDefaultLanguageModelEndpoint - API Endpoint (e.g. Ollama URL)
+    CPDefaultLanguageModelModel - Model name string
+    CPDefaultLanguageModelAPIKeyUserDefaultKey - CPUserDefaults key containing the actual API token
+    CPDefaultLanguageModelAPIKey - Authentication token string (Unsecure direct fallback)
+    
+*/ +@implementation CPLanguageModelSession : CPObject +{ + id _chromeSession @accessors(property=chromeSession); + CPString _instructions @accessors(property=instructions); + CPString _fallbackServiceType @accessors(property=fallbackServiceType); + CPString _fallbackEndpoint @accessors(property=fallbackEndpoint); + CPString _fallbackModel @accessors(property=fallbackModel); + CPString _fallbackAPIKey @accessors(property=fallbackAPIKey); +} + +/*! + Initializes fallback defaults and "Endorsement" flags from the application's Info.plist. +*/ ++ (void)initialize +{ + if (self === [CPLanguageModelSession class]) + { + var bundle = [CPBundle mainBundle]; + + CPLanguageModelSessionEndorsesFallback = !![bundle objectForInfoDictionaryKey:@"CPEndorseLanguageModelFallback"]; + CPLanguageModelSessionFallbackServiceType = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelService"] || @"ollama"; + CPLanguageModelSessionFallbackEndpoint = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelEndpoint"] || @"http://localhost:11434/api/generate"; + CPLanguageModelSessionFallbackModel = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelModel"] || @"gemma4:e4b"; + CPLanguageModelSessionFallbackAPIKey = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelAPIKey"] || @""; + CPLanguageModelSessionFallbackAPIKeyUserDefaultKey = [bundle objectForInfoDictionaryKey:@"CPDefaultLanguageModelAPIKeyUserDefaultKey"] || @""; + } +} + +/*! + Configures whether the session should bypass native browser AI models and force fallback network routing. + @param shouldEndorse YES to bypass native AI; NO to prioritize native AI if available +*/ ++ (void)setEndorsesFallback:(BOOL)shouldEndorse +{ + CPLanguageModelSessionEndorsesFallback = shouldEndorse; +} + +/*! + Indicates if the session bypasses native browser AI models. + @return YES if forced fallback is active; NO otherwise +*/ ++ (BOOL)endorsesFallback +{ + return CPLanguageModelSessionEndorsesFallback; +} + +/*! + Configures fallback details dynamically, overriding any defaults loaded from Info.plist. + @param serviceType the service type (e.g. @"ollama", @"groq", @"gemini", @"openrouter") + @param endpoint the network target URL + @param model the model identifier + @param apiKey the API key string +*/ ++ (void)setFallbackServiceType:(CPString)serviceType endpoint:(CPString)endpoint model:(CPString)model apiKey:(CPString)apiKey +{ + CPLanguageModelSessionFallbackServiceType = serviceType; + CPLanguageModelSessionFallbackEndpoint = endpoint; + CPLanguageModelSessionFallbackModel = model; + CPLanguageModelSessionFallbackAPIKey = apiKey; +} + +/*! + Configures the CPUserDefaults key used to dynamically look up the API key. + @param keyName the user defaults key name containing the actual credentials +*/ ++ (void)setFallbackAPIKeyUserDefaultKey:(CPString)keyName +{ + CPLanguageModelSessionFallbackAPIKeyUserDefaultKey = keyName; +} + +/*! + Gets the CPUserDefaults key name used to dynamically look up the API key. + @return the user defaults key name +*/ ++ (CPString)fallbackAPIKeyUserDefaultKey +{ + return CPLanguageModelSessionFallbackAPIKeyUserDefaultKey; +} + +/*! + Initializes a language model session with specific system instructions. + @param instructions the system instructions or context prompt + @return the initialized session +*/ +- (id)initWithInstructions:(CPString)instructions +{ + self = [super init]; + + if (self) + { + _instructions = instructions; + _chromeSession = nil; + _fallbackServiceType = nil; + _fallbackEndpoint = nil; + _fallbackModel = nil; + _fallbackAPIKey = nil; + } + + return self; +} + +/*! + Initializes a language model session with specific system instructions and an explicit programmatic API key. + @param instructions the system instructions or context prompt + @param apiKey the fallback API key to use specifically for this session + @return the initialized session +*/ +- (id)initWithInstructions:(CPString)instructions apiKey:(CPString)apiKey +{ + self = [self initWithInstructions:instructions]; + + if (self) + { + _fallbackAPIKey = apiKey; + } + + return self; +} + +/*! + Initializes a language model session with instructions and explicit fallback settings. + @param instructions the system instructions or context prompt + @param options dictionary containing custom fallback configuration (e.g. @{ @"serviceType": ..., @"apiKey": ... }) + @return the initialized session +*/ +- (id)initWithInstructions:(CPString)instructions fallbackOptions:(CPDictionary)options +{ + self = [self initWithInstructions:instructions]; + + if (self) + { + if (options) + { + _fallbackServiceType = [options objectForKey:@"serviceType"]; + _fallbackEndpoint = [options objectForKey:@"endpoint"]; + _fallbackModel = [options objectForKey:@"model"]; + _fallbackAPIKey = [options objectForKey:@"apiKey"]; + } + } + + return self; +} + +/*! + Sends a query prompt to the language model session. + @param prompt the query text to analyze + @param completionHandler a callback receiving the response string or a CPError instance +*/ +- (void)respondToPrompt:(CPString)prompt options:(id)options completionHandler:(Function)completionHandler +{ + // If the developer forced fallback, bypass native browser execution + if (CPLanguageModelSessionEndorsesFallback) + { + [self _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler]; + return; + } + + if (_chromeSession) + { + [self _executePrompt:prompt options:options completionHandler:completionHandler]; + return; + } + + var selfRef = self, + instructions = [self instructions]; + + [CPLanguageModelSession _getChromeFactoryWithCompletionHandler:function(factory, error) { + if (error) { + [selfRef _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler]; + return; + } + + // Add the required expected input and output parameters + var sessionOptions = { + expectedInputs: [{ type: "text", languages: ["en"] }], + expectedOutputs: [{ type: "text", languages: ["en"] }] + }; + if (instructions) { + sessionOptions.systemPrompt = instructions; + } + + factory.create(sessionOptions).then(function(session) { + [selfRef setChromeSession:session]; + [selfRef _executePrompt:prompt options:options completionHandler:completionHandler]; + }).catch(function(err) { + [selfRef _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler]; + }); + }]; +} + +/*! + Sends a query prompt and streams the response chunk-by-chunk for live UI rendering. + @param prompt the query text to analyze + @param chunkHandler a callback block executed as text increments are received + @param completionHandler a final callback block executed when generation completes +*/ +- (void)respondToPrompt:(CPString)prompt + onChunkReceived:(Function)chunkHandler + completed:(Function)completionHandler +{ + if (CPLanguageModelSessionEndorsesFallback) + { + [self _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) { + if (!err && chunkHandler) + chunkHandler(res); + completionHandler(res, err); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update + + }]; + return; + } + + if (_chromeSession) + { + [self _executePromptStreaming:prompt onChunkReceived:chunkHandler completed:completionHandler]; + return; + } + + var selfRef = self, + instructions = [self instructions]; + + [CPLanguageModelSession _getChromeFactoryWithCompletionHandler:function(factory, error) { + if (error) { + [selfRef _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) { + if (!err && chunkHandler) + chunkHandler(res); + completionHandler(res, err); + }]; + return; + } + + // Add the required expected input and output parameters + var options = { + expectedInputs: [{ type: "text", languages: ["en"] }], + expectedOutputs: [{ type: "text", languages: ["en"] }] + }; + if (instructions) { + options.systemPrompt = instructions; + } + + factory.create(options).then(function(session) { + [selfRef setChromeSession:session]; + [selfRef _executePromptStreaming:prompt onChunkReceived:chunkHandler completed:completionHandler]; + }).catch(function(err) { + [selfRef _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) { + if (!err && chunkHandler) + chunkHandler(res); + completionHandler(res, err); + }]; + }); + }]; +} + +/*! + Closes the session and releases associated memory resources on-device. +*/ +- (void)destroy +{ + if (_chromeSession && typeof _chromeSession.destroy === "function") + { + _chromeSession.destroy(); + _chromeSession = nil; + } +} + + +// MARK: - Private Helper Methods + ++ (void)_getChromeFactoryWithCompletionHandler:(Function)completionHandler +{ + if (typeof window === "undefined") + { + var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain" code:-1 userInfo:[CPDictionary dictionaryWithObject:@"Execution environment is not a browser window." forKey:CPLocalizedDescriptionKey]]; + completionHandler(nil, cpError); + return; + } + + if (window.ai && window.ai.languageModel) + completionHandler(window.ai.languageModel, nil); + else if (window.LanguageModel) + completionHandler(window.LanguageModel, nil); + else + completionHandler(nil, [CPError errorWithDomain:@"CPLanguageModelErrorDomain" code:0 userInfo:nil]); +} + +- (void)_executePrompt:(CPString)prompt options:(id)options completionHandler:(Function)completionHandler +{ + var promptPromise = options ? _chromeSession.prompt(prompt, options) : _chromeSession.prompt(prompt); + + promptPromise.then(function(result) { + completionHandler(result, nil); + }).catch(function(err) { + var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain" + code:2 + userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]]; + completionHandler(nil, cpError); + }); +} + +- (CPString)_resolvedFallbackServiceType +{ + return _fallbackServiceType || CPLanguageModelSessionFallbackServiceType; +} + +- (CPString)_resolvedFallbackEndpoint +{ + return _fallbackEndpoint || CPLanguageModelSessionFallbackEndpoint; +} + +- (CPString)_resolvedFallbackModel +{ + return _fallbackModel || CPLanguageModelSessionFallbackModel; +} + +- (CPString)_resolvedFallbackAPIKey +{ + // 1. Session instance explicit key has highest priority + if (_fallbackAPIKey) + return _fallbackAPIKey; + + // 2. Class fallback API key set programmatically takes second priority + if (CPLanguageModelSessionFallbackAPIKey) + return CPLanguageModelSessionFallbackAPIKey; + + // 3. Dynamic lookup from standard user defaults takes final priority + if (CPLanguageModelSessionFallbackAPIKeyUserDefaultKey) + { + var defaults = [CPUserDefaults standardUserDefaults], + apiKey = [defaults objectForKey:CPLanguageModelSessionFallbackAPIKeyUserDefaultKey]; + if (apiKey) + return apiKey; + } + + return @""; +} + +- (void)_executeRemoteFallbackWithPrompt:(CPString)prompt options:(id)options completionHandler:(Function)completionHandler +{ + var systemPrompt = [self instructions], + serviceType = [self _resolvedFallbackServiceType], + endpoint = [self _resolvedFallbackEndpoint], + model = [self _resolvedFallbackModel], + apiKey = [self _resolvedFallbackAPIKey]; + + var reqUrl = @"", + headers = { "Content-Type": "application/json" }, + payload = {}; + + if (serviceType === @"groq") + { + reqUrl = "https://api.groq.com/openai/v1/chat/completions"; + headers["Authorization"] = "Bearer " + apiKey; + payload = { + "model": model, + "messages": [ + { "role": "system", "content": systemPrompt }, + { "role": "user", "content": prompt } + ], + "temperature": 0 + }; + } + else if (serviceType === @"gemini") + { + reqUrl = "https://generativelanguage.googleapis.com/v1beta/models/" + model + ":generateContent?key=" + apiKey; + payload = { + "contents": [ + { "parts": [{ "text": systemPrompt + "\n\n" + prompt }] } + ], + "generationConfig": { "temperature": 0 } + }; + } + else if (serviceType === @"openrouter") + { + reqUrl = "https://openrouter.ai/api/v1/chat/completions"; + headers["Authorization"] = "Bearer " + apiKey; + payload = { + "model": model, + "messages": [ + { "role": "system", "content": systemPrompt }, + { "role": "user", "content": prompt } + ], + "temperature": 0 + }; + } + else + { + reqUrl = endpoint || "http://localhost:11434/api/generate"; + payload = { + "model": model, + "prompt": systemPrompt + "\n\n" + prompt, + "stream": false, + "options": { "temperature": 0 } + }; + } + + fetch(reqUrl, { + method: 'POST', + headers: headers, + body: JSON.stringify(payload) + }) + .then(function(response) { + if (!response.ok) { + throw new Error("HTTP error! Status: " + response.status); + } + return response.json(); + }) + .then(function(data) { + var responseText = ""; + + if (serviceType === "groq" || serviceType === "openrouter") { + responseText = (data.choices && data.choices[0] && data.choices[0].message) ? data.choices[0].message.content : ""; + } else if (serviceType === "gemini") { + responseText = (data.candidates && data.candidates[0] && data.candidates[0].content && data.candidates[0].content.parts) ? data.candidates[0].content.parts[0].text : ""; + } else { + responseText = data.response || ""; + } + + completionHandler(responseText, nil); + }) + .catch(function(err) { + var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain" + code:4 + userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]]; + completionHandler(nil, cpError); + }); +} + +- (void)_executePromptStreaming:(CPString)prompt + onChunkReceived:(Function)chunkHandler + completed:(Function)completionHandler +{ + var stream; + + try { + stream = _chromeSession.promptStreaming(prompt); + } catch (err) { + var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain" + code:3 + userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]]; + completionHandler(nil, cpError); + return; + } + + (async function() { + var lastChunk = ""; + + try { + for await (const chunk of stream) { + lastChunk = chunk; + if (chunkHandler) { + chunkHandler(chunk); + } + } + if (completionHandler) + { + completionHandler(lastChunk, nil); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update + + } + } catch (err) { + if (completionHandler) { + var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain" + code:2 + userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]]; + completionHandler(nil, cpError); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update + } + } + })(); +} + +@end diff --git a/Tests/Manual/CPLanguageModelChatbot/AppController.j b/Tests/Manual/CPLanguageModelChatbot/AppController.j new file mode 100644 index 000000000..f7553a496 --- /dev/null +++ b/Tests/Manual/CPLanguageModelChatbot/AppController.j @@ -0,0 +1,572 @@ +// AppController.j +// Manual test application for CPLanguageModelSession & CPSystemLanguageModel +// With custom SpeechBubbleBox drawing and editable System Prompt controls. +// + +@import +@import +@import + +// --- SUBCLASS: SPEECH BUBBLE VIEW --- +@implementation SpeechBubbleBox : CPView +{ + BOOL _isUser; + CPColor _bubbleColor; +} + +- (id)initWithFrame:(CGRect)aFrame isUser:(BOOL)isUser fillColor:(CPColor)aColor +{ + self = [super initWithFrame:aFrame]; + if (self) { + _isUser = isUser; + _bubbleColor = aColor; + [self setAutoresizingMask:CPViewWidthSizable]; + } + return self; +} + +- (void)drawRect:(CGRect)aRect +{ + var context = [[CPGraphicsContext currentContext] graphicsPort]; + var bounds = [self bounds]; + var w = CGRectGetWidth(bounds); + var h = CGRectGetHeight(bounds) - 10.0; // 10px spacing for the bottom triangle pointer + var r = 6.0; // Corner radius + var th = 10.0; // Tail height + + // --- 1. FILL PATH --- + CGContextBeginPath(context); + + // Top-left + CGContextMoveToPoint(context, r, 0); + + // Top edge + CGContextAddLineToPoint(context, w - r, 0); + CGContextAddArcToPoint(context, w, 0, w, r, r); + + // Right edge + CGContextAddLineToPoint(context, w, h - r); + CGContextAddArcToPoint(context, w, h, w - r, h, r); + + // Bottom edge with triangular tail (RHS vs LHS) + if (_isUser) { + CGContextAddLineToPoint(context, w - 21, h); + CGContextAddLineToPoint(context, w - 21, h + th); + CGContextAddLineToPoint(context, w - 35, h); + CGContextAddLineToPoint(context, r, h); + } else { + CGContextAddLineToPoint(context, 35, h); + CGContextAddLineToPoint(context, 21, h + th); + CGContextAddLineToPoint(context, 21, h); + CGContextAddLineToPoint(context, r, h); + } + + // Left edge + CGContextAddArcToPoint(context, 0, h, 0, h - r, r); + CGContextAddLineToPoint(context, 0, r); + CGContextAddArcToPoint(context, 0, 0, r, 0, r); + + CGContextClosePath(context); + + // Fill path + CGContextSetFillColor(context, _bubbleColor); + CGContextFillPath(context); + + // --- 2. OUTLINE PATH --- + CGContextBeginPath(context); + CGContextMoveToPoint(context, r, 0); + CGContextAddLineToPoint(context, w - r, 0); + CGContextAddArcToPoint(context, w, 0, w, r, r); + CGContextAddLineToPoint(context, w, h - r); + CGContextAddArcToPoint(context, w, h, w - r, h, r); + + if (_isUser) { + CGContextAddLineToPoint(context, w - 21, h); + CGContextAddLineToPoint(context, w - 21, h + th); + CGContextAddLineToPoint(context, w - 35, h); + CGContextAddLineToPoint(context, r, h); + } else { + CGContextAddLineToPoint(context, 35, h); + CGContextAddLineToPoint(context, 21, h + th); + CGContextAddLineToPoint(context, 21, h); + CGContextAddLineToPoint(context, r, h); + } + + CGContextAddArcToPoint(context, 0, h, 0, h - r, r); + CGContextAddLineToPoint(context, 0, r); + CGContextAddArcToPoint(context, 0, 0, r, 0, r); + CGContextClosePath(context); + + // Stroke outline + CGContextSetStrokeColor(context, [CPColor colorWithWhite:0.8 alpha:1.0]); + CGContextSetLineWidth(context, 1.0); + CGContextStrokePath(context); +} + +@end + + +// --- MAIN CONTROLLER --- +@implementation AppController : CPObject +{ + CPWindow _mainWindow; + CPScrollView _chatScrollView; + CPView _chatDocumentView; + CPTextField _chatInputField; + CPButton _chatSendButton; + CPButton _settingsButton; + CPCheckBox _forceFallbackCheckbox; + CPTextField _statusLabel; + CPTextView _systemPromptTextView; + + CPWindow _settingsWindow; + CPPopUpButton _servicePopUp; + CPTextField _endpointField; + CPTextField _modelField; + CPTextField _apiKeyField; + + CPLanguageModelSession _session; + float _currentChatY; + id _currentStreamingTextView; +} + +- (void)applicationDidFinishLaunching:(CPNotification)aNotification +{ + // Initialize default test settings in local user defaults + var defaults = [CPUserDefaults standardUserDefaults]; + var defaultSettings = [CPDictionary dictionaryWithObjects:[ + @"ollama", + @"http://localhost:11434/api/generate", + @"gemma4:e4b", + @"" + ] forKeys:[ + @"LLMTestServiceType", + @"LLMTestEndpoint", + @"LLMTestModel", + @"LLMTestAPIKey" + ]]; + [defaults registerDefaults:defaultSettings]; + + // Read values to apply fallback routing to CPLanguageModelSession + var activeService = [defaults objectForKey:@"LLMTestServiceType"], + endpoint = [defaults objectForKey:@"LLMTestEndpoint"], + model = [defaults objectForKey:@"LLMTestModel"], + apiKey = [defaults objectForKey:@"LLMTestAPIKey"]; + + [CPLanguageModelSession setFallbackServiceType:activeService + endpoint:endpoint + model:model + apiKey:apiKey]; + + // Main manual test window setup + _mainWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, 800, 650) + styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask | CPResizableWindowMask]; + [_mainWindow setTitle:@"CPLanguageModel Manual Test Tool"]; + [_mainWindow center]; + + var contentView = [_mainWindow contentView]; + var bounds = [contentView bounds]; + + // --- TOP CONTROL PANEL (Height 135px) --- + var topBar = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(bounds), 135)]; + [topBar setAutoresizingMask:CPViewWidthSizable | CPViewMaxYMargin]; + [topBar setBackgroundColor:[CPColor colorWithWhite:0.92 alpha:1.0]]; + [contentView addSubview:topBar]; + + _statusLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 12, 300, 20)]; + [_statusLabel setStringValue:@"Checking capability..."]; + [_statusLabel setFont:[CPFont systemFontOfSize:12.0]]; + [topBar addSubview:_statusLabel]; + + _forceFallbackCheckbox = [[CPCheckBox alloc] initWithFrame:CGRectMake(330, 12, 140, 20)]; + [_forceFallbackCheckbox setTitle:@"Force Fallback"]; + [_forceFallbackCheckbox setTarget:self]; + [_forceFallbackCheckbox setAction:@selector(toggleForceFallback:)]; + [topBar addSubview:_forceFallbackCheckbox]; + + _settingsButton = [[CPButton alloc] initWithFrame:CGRectMake(CGRectGetWidth(bounds) - 210, 9, 90, 26)]; + [_settingsButton setTitle:@"Settings..."]; + [_settingsButton setAutoresizingMask:CPViewMinXMargin]; + [_settingsButton setTarget:self]; + [_settingsButton setAction:@selector(openSettingsSheet:)]; + [topBar addSubview:_settingsButton]; + + var clearButton = [[CPButton alloc] initWithFrame:CGRectMake(CGRectGetWidth(bounds) - 110, 9, 95, 26)]; + [clearButton setTitle:@"Clear Chat"]; + [clearButton setAutoresizingMask:CPViewMinXMargin]; + [clearButton setTarget:self]; + [clearButton setAction:@selector(clearChatAction:)]; + [topBar addSubview:clearButton]; + + // System Prompt Header & Field [1] + var promptLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 42, 300, 18)]; + [promptLabel setStringValue:@"System Instructions (Persona):"]; + [promptLabel setFont:[CPFont boldSystemFontOfSize:11.0]]; + [promptLabel setTextColor:[CPColor darkGrayColor]]; + [topBar addSubview:promptLabel]; + + var promptScroll = [[CPScrollView alloc] initWithFrame:CGRectMake(15, 62, CGRectGetWidth(bounds) - 30, 60)]; + [promptScroll setAutoresizingMask:CPViewWidthSizable]; + [promptScroll setAutohidesScrollers:YES]; + + _systemPromptTextView = [[CPTextView alloc] initWithFrame:[promptScroll bounds]]; + [_systemPromptTextView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [_systemPromptTextView setEditable:YES]; + [_systemPromptTextView setFont:[CPFont systemFontOfSize:11.0]]; + // Default instruction preset [1] + [_systemPromptTextView setString:@"You are a helpful, concise testing assistant. You structure your explanations clearly using lists where appropriate."]; + + [promptScroll setDocumentView:_systemPromptTextView]; + [topBar addSubview:promptScroll]; + + // --- SCROLLING CHAT CONTAINER --- + var scrollHeight = CGRectGetHeight(bounds) - 195; + _chatScrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 135, CGRectGetWidth(bounds), scrollHeight)]; + [_chatScrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + [_chatScrollView setAutohidesScrollers:YES]; + [_chatScrollView setHasHorizontalScroller:NO]; + [_chatScrollView setBackgroundColor:[CPColor whiteColor]]; + + _chatDocumentView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([_chatScrollView bounds]), scrollHeight)]; + [_chatDocumentView setAutoresizingMask:CPViewWidthSizable]; + [_chatScrollView setDocumentView:_chatDocumentView]; + [contentView addSubview:_chatScrollView]; + + // --- INPUT CONTAINER --- + var bottomBarY = CGRectGetHeight(bounds) - 60; + var bottomBar = [[CPView alloc] initWithFrame:CGRectMake(0, bottomBarY, CGRectGetWidth(bounds), 60)]; + [bottomBar setAutoresizingMask:CPViewWidthSizable | CPViewMinYMargin]; + [bottomBar setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]]; + [contentView addSubview:bottomBar]; + + _chatInputField = [[CPTextField alloc] initWithFrame:CGRectMake(15, 13, CGRectGetWidth(bounds) - 130, 34)]; + [_chatInputField setAutoresizingMask:CPViewWidthSizable]; + [_chatInputField setEditable:YES]; + [_chatInputField setBezeled:YES]; + [_chatInputField setPlaceholderString:@"Type a prompt and press Enter..."]; + [_chatInputField setTarget:self]; + [_chatInputField setAction:@selector(submitPromptAction:)]; + [bottomBar addSubview:_chatInputField]; + + _chatSendButton = [[CPButton alloc] initWithFrame:CGRectMake(CGRectGetWidth(bounds) - 105, 13, 90, 34)]; + [_chatSendButton setTitle:@"Send"]; + [_chatSendButton setAutoresizingMask:CPViewMinXMargin]; + [_chatSendButton setTarget:self]; + [_chatSendButton setAction:@selector(submitPromptAction:)]; + [bottomBar addSubview:_chatSendButton]; + + [_mainWindow orderFront:self]; + + [self checkModelSupport]; + [self resetSession]; +} + +- (void)checkModelSupport +{ + var systemModel = [CPSystemLanguageModel defaultModel]; + [_statusLabel setStringValue:@"Checking capability..."]; + + var selfRef = self; + [systemModel supportsLocaleWithCompletionHandler:function(supported) { + if (supported) { + [selfRef._statusLabel setStringValue:@"On-Device LLM: Supported"]; + } else { + [selfRef._statusLabel setStringValue:@"On-Device LLM: Not available. Using fallback."]; + } + }]; +} + +- (void)toggleForceFallback:(id)sender +{ + var force = [sender state] === CPOnState; + [CPLanguageModelSession setEndorsesFallback:force]; + [_statusLabel setStringValue:(force ? @"Fallback forced programmatically." : @"Prioritizing on-device model.")]; +} + +- (void)clearChatAction:(id)sender +{ + [self resetSession]; +} + +- (void)resetSession +{ + _currentChatY = 15; + _currentStreamingTextView = nil; + + if (_session) { + [_session destroy]; + } + + // Read the custom instructions from the text field upon starting a fresh session [1] + var instructions = [_systemPromptTextView string] || @""; + _session = [[CPLanguageModelSession alloc] initWithInstructions:instructions]; + + [[_chatDocumentView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; + [_chatDocumentView setFrameSize:CGSizeMake(CGRectGetWidth([_chatScrollView bounds]), CGRectGetHeight([_chatScrollView bounds]))]; + + [self appendMessage:@"Session initialized.\n\nEnter a query below to begin." isUser:NO]; +} + +// Append a formatted message using SpeechBubbleBox and sizeToFit calculation +- (void)appendMessage:(CPString)text isUser:(BOOL)isUser +{ + var docWidth = CGRectGetWidth([_chatScrollView bounds]) - 50; + + var textView = [[CPTextView alloc] initWithFrame:CGRectMake(15, 10, docWidth - 30, 20)]; + [textView insertText:text]; + [textView setTextColor:[CPColor blackColor]]; + [textView setFont:[CPFont systemFontOfSize:11.0]]; + [textView setEditable:YES]; + [textView setRichText:NO]; + [textView setBackgroundColor:[CPColor clearColor]]; + [textView setAutoresizingMask:CPViewWidthSizable]; + + [textView sizeToFit]; + var textHeight = CGRectGetHeight([textView frame]); + + var cardHeight = textHeight + 20; // 10px spacing top/bottom + var bubbleHeight = cardHeight + 10; // Extra 10px spacing for the bottom triangle pointer [1] + + var fillColor = isUser ? [CPColor colorWithRed:0.90 green:0.93 blue:1.0 alpha:1.0] : [CPColor colorWithWhite:0.96 alpha:1.0]; + var cardBox = [[SpeechBubbleBox alloc] initWithFrame:CGRectMake(15, _currentChatY, docWidth, bubbleHeight) + isUser:isUser + fillColor:fillColor]; + + [cardBox addSubview:textView]; + [_chatDocumentView addSubview:cardBox]; + + if (!isUser) { + _currentStreamingTextView = textView; + } + + _currentChatY += bubbleHeight + 15; // 15px gap between consecutive messages + [_chatDocumentView setFrameSize:CGSizeMake(CGRectGetWidth([_chatScrollView bounds]), _currentChatY + 20)]; + + var boundsHeight = CGRectGetHeight([_chatScrollView bounds]); + if (_currentChatY > boundsHeight) { + [[_chatScrollView contentView] scrollToPoint:CGPointMake(0, _currentChatY - boundsHeight + 40)]; + } +} + +// Dynamically resize the SpeechBubbleBox to match updated streaming text changes +- (void)updateStreamingMessage:(CPString)newText +{ + if (!_currentStreamingTextView) + return; + + if (newText == "\n\n") + newText = ' '; + + [_currentStreamingTextView setString:[_currentStreamingTextView._textStorage string] + newText]; + //[_currentStreamingTextView sizeToFit]; + + var textHeight = CGRectGetHeight([_currentStreamingTextView frame]); + var cardHeight = textHeight + 20; + var bubbleHeight = cardHeight + 10; + + var container = [_currentStreamingTextView superview]; // Resolves the SpeechBubbleBox + var oldBubbleHeight = CGRectGetHeight([container frame]); + + [container setFrameSize:CGSizeMake(CGRectGetWidth([container frame]), bubbleHeight)]; + [_currentStreamingTextView setFrameSize:CGSizeMake(CGRectGetWidth([_currentStreamingTextView frame]), textHeight)]; + [container setNeedsDisplay:YES]; // Instructs the canvas to clear and redraw paths + [_currentStreamingTextView setNeedsDisplay:YES]; // Instructs the canvas to clear and redraw paths + + var diffHeight = bubbleHeight - oldBubbleHeight; + _currentChatY += diffHeight; + + [_chatDocumentView setFrameSize:CGSizeMake(CGRectGetWidth([_chatScrollView bounds]), _currentChatY + 20)]; + + var boundsHeight = CGRectGetHeight([_chatScrollView bounds]); + if (_currentChatY > boundsHeight) { + [[_chatScrollView contentView] scrollToPoint:CGPointMake(0, _currentChatY - boundsHeight + 40)]; + } +} + +- (void)submitPromptAction:(id)sender +{ + var prompt = [_chatInputField stringValue]; + if (!prompt || [prompt stringByTrimmingWhitespace] === @"") { + return; + } + + [_chatInputField setStringValue:@""]; + [_chatInputField setEnabled:NO]; + [_chatSendButton setEnabled:NO]; + + [self appendMessage:prompt isUser:YES]; + [self appendMessage:@"Generating response..." isUser:NO]; + + var selfRef = self; + + [_session respondToPrompt:prompt + onChunkReceived:function(chunk) { + [selfRef updateStreamingMessage:chunk]; + } + completed:function(finalText, error) { + [selfRef._chatInputField setEnabled:YES]; + [selfRef._chatInputField becomeFirstResponder]; + [selfRef._chatSendButton setEnabled:YES]; + + if (error) { + [selfRef updateStreamingMessage:@"Error: " + [error localizedDescription]]; + } else { + [selfRef updateStreamingMessage:finalText]; + } + }]; +} + +// --- CONFIGURATION POPUP SHEETS --- + +- (void)openSettingsSheet:(id)sender +{ + if (!_settingsWindow) + { + _settingsWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, 420, 260) + styleMask:CPTitledWindowMask | CPClosableWindowMask]; + [_settingsWindow setTitle:@"Fallback Settings"]; + + var sheetContentView = [_settingsWindow contentView]; + var sheetBounds = [sheetContentView bounds]; + + var serviceLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 25, 110, 20)]; + [serviceLabel setStringValue:@"Service Type:"]; + [serviceLabel setFont:[CPFont systemFontOfSize:12.0]]; + [serviceLabel setAlignment:CPRightTextAlignment]; + [sheetContentView addSubview:serviceLabel]; + + _servicePopUp = [[CPPopUpButton alloc] initWithFrame:CGRectMake(135, 22, 180, 26) pullsDown:NO]; + [_servicePopUp addItemWithTitle:@"Ollama (Local)"]; + [[_servicePopUp lastItem] setRepresentedObject:@"ollama"]; + [_servicePopUp addItemWithTitle:@"Groq API"]; + [[_servicePopUp lastItem] setRepresentedObject:@"groq"]; + [_servicePopUp addItemWithTitle:@"Google Gemini"]; + [[_servicePopUp lastItem] setRepresentedObject:@"gemini"]; + [_servicePopUp addItemWithTitle:@"OpenRouter"]; + [[_servicePopUp lastItem] setRepresentedObject:@"openrouter"]; + [_servicePopUp setTarget:self]; + [_servicePopUp setAction:@selector(serviceTypeDidChange:)]; + [sheetContentView addSubview:_servicePopUp]; + + var endpointLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 65, 110, 20)]; + [endpointLabel setStringValue:@"Endpoint URL:"]; + [endpointLabel setFont:[CPFont systemFontOfSize:12.0]]; + [endpointLabel setAlignment:CPRightTextAlignment]; + [sheetContentView addSubview:endpointLabel]; + + _endpointField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 62, CGRectGetWidth(sheetBounds) - 155, 24)]; + [_endpointField setEditable:YES]; + [_endpointField setBezeled:YES]; + [_endpointField setFont:[CPFont systemFontOfSize:12.0]]; + [sheetContentView addSubview:_endpointField]; + + var modelLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 105, 110, 20)]; + [modelLabel setStringValue:@"Model Name:"]; + [modelLabel setFont:[CPFont systemFontOfSize:12.0]]; + [modelLabel setAlignment:CPRightTextAlignment]; + [sheetContentView addSubview:modelLabel]; + + _modelField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 102, CGRectGetWidth(sheetBounds) - 155, 24)]; + [_modelField setEditable:YES]; + [_modelField setBezeled:YES]; + [_modelField setFont:[CPFont systemFontOfSize:12.0]]; + [sheetContentView addSubview:_modelField]; + + var apiKeyLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 145, 110, 20)]; + [apiKeyLabel setStringValue:@"API Key:"]; + [apiKeyLabel setFont:[CPFont systemFontOfSize:12.0]]; + [apiKeyLabel setAlignment:CPRightTextAlignment]; + [sheetContentView addSubview:apiKeyLabel]; + + _apiKeyField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 142, CGRectGetWidth(sheetBounds) - 155, 24)]; + [_apiKeyField setEditable:YES]; + [_apiKeyField setBezeled:YES]; + [_apiKeyField setSecure:YES]; + [_apiKeyField setFont:[CPFont systemFontOfSize:12.0]]; + [sheetContentView addSubview:_apiKeyField]; + + var btnY = CGRectGetHeight(sheetBounds) - 45; + + var cancelBtn = [[CPButton alloc] initWithFrame:CGRectMake(CGRectGetWidth(sheetBounds) - 205, btnY, 90, 26)]; + [cancelBtn setTitle:@"Cancel"]; + [cancelBtn setTarget:self]; + [cancelBtn setAction:@selector(closeSettingsSheet:)]; + [sheetContentView addSubview:cancelBtn]; + + var saveBtn = [[CPButton alloc] initWithFrame:CGRectMake(CGRectGetWidth(sheetBounds) - 105, btnY, 90, 26)]; + [saveBtn setTitle:@"Save"]; + [saveBtn setTarget:self]; + [saveBtn setAction:@selector(saveSettings:)]; + [sheetContentView addSubview:saveBtn]; + } + + var defaults = [CPUserDefaults standardUserDefaults]; + var activeService = [defaults objectForKey:@"LLMTestServiceType"] || @"ollama"; + + if (activeService === @"ollama") [_servicePopUp selectItemAtIndex:0]; + else if (activeService === @"groq") [_servicePopUp selectItemAtIndex:1]; + else if (activeService === @"gemini") [_servicePopUp selectItemAtIndex:2]; + else if (activeService === @"openrouter") [_servicePopUp selectItemAtIndex:3]; + + [_endpointField setStringValue:[defaults objectForKey:@"LLMTestEndpoint"] || @"http://localhost:11434/api/generate"]; + [_modelField setStringValue:[defaults objectForKey:@"LLMTestModel"] || @"gemma4:e4b"]; + [_apiKeyField setStringValue:[defaults objectForKey:@"LLMTestAPIKey"] || @""]; + + [self updateFieldsForService:activeService]; + + [CPApp beginSheet:_settingsWindow + modalForWindow:_mainWindow + modalDelegate:self + didEndSelector:nil + contextInfo:nil]; +} + +- (void)updateFieldsForService:(CPString)serviceType +{ + if (serviceType === @"ollama") { + [_endpointField setEnabled:YES]; + [_apiKeyField setEnabled:NO]; + [_apiKeyField setPlaceholderString:@"Not required"]; + } else { + [_endpointField setEnabled:NO]; + [_endpointField setPlaceholderString:@"Default platform endpoint used"]; + [_apiKeyField setEnabled:YES]; + [_apiKeyField setPlaceholderString:@"API Token values"]; + } +} + +- (void)serviceTypeDidChange:(id)sender +{ + var newService = [[_servicePopUp selectedItem] representedObject]; + [self updateFieldsForService:newService]; +} + +- (void)closeSettingsSheet:(id)sender +{ + [CPApp endSheet:_settingsWindow]; + [_settingsWindow orderOut:self]; +} + +- (void)saveSettings:(id)sender +{ + var defaults = [CPUserDefaults standardUserDefaults]; + var activeService = [[_servicePopUp selectedItem] representedObject] || @"ollama"; + var endpoint = [_endpointField stringValue]; + var model = [_modelField stringValue]; + var apiKey = [_apiKeyField stringValue]; + + [defaults setObject:activeService forKey:@"LLMTestServiceType"]; + [defaults setObject:endpoint forKey:@"LLMTestEndpoint"]; + [defaults setObject:model forKey:@"LLMTestModel"]; + [defaults setObject:apiKey forKey:@"LLMTestAPIKey"]; + + [CPLanguageModelSession setFallbackServiceType:activeService + endpoint:endpoint + model:model + apiKey:apiKey]; + + [self closeSettingsSheet:sender]; + [_statusLabel setStringValue:@"Fallback settings updated."]; +} + +@end diff --git a/Tests/Manual/CPLanguageModelChatbot/Info.plist b/Tests/Manual/CPLanguageModelChatbot/Info.plist new file mode 100644 index 000000000..ca0ffdd38 --- /dev/null +++ b/Tests/Manual/CPLanguageModelChatbot/Info.plist @@ -0,0 +1,12 @@ + + + + + CPApplicationDelegateClass + AppController + CPBundleName + CPLanguageModelChatbot + CPPrincipalClass + CPApplication + + diff --git a/Tests/Manual/CPLanguageModelChatbot/Jakefile b/Tests/Manual/CPLanguageModelChatbot/Jakefile new file mode 100644 index 000000000..bd57b7a0d --- /dev/null +++ b/Tests/Manual/CPLanguageModelChatbot/Jakefile @@ -0,0 +1,94 @@ +/* + * Jakefile + * CPLevelIndicator + * + * Created by Alexander Ljungberg on May 28, 2011. + * Copyright 2011, WireLoad All rights reserved. + */ + +var ENV = require("system").env, + FILE = require("file"), + JAKE = require("jake"), + task = JAKE.task, + FileList = JAKE.FileList, + app = require("cappuccino/jake").app, + configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", + OS = require("os"); + +app ("CPLevelIndicator", function(task) +{ + task.setBuildIntermediatesPath(FILE.join("Build", "CPLevelIndicator.build", configuration)); + task.setBuildPath(FILE.join("Build", configuration)); + + task.setProductName("CPLevelIndicator"); + task.setIdentifier("com.yourcompany.CPLevelIndicator"); + task.setVersion("1.0"); + task.setAuthor("WireLoad"); + task.setEmail("feedback @nospam@ yourcompany.com"); + task.setSummary("CPLevelIndicator"); + task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**"))); + task.setResources(new FileList("Resources/**")); + task.setIndexFilePath("index.html"); + task.setInfoPlistPath("Info.plist"); + task.setNib2CibFlags("-R Resources/"); + + if (configuration === "Debug") + task.setCompilerFlags("-DDEBUG -g"); + else + task.setCompilerFlags("-O"); +}); + +task ("default", ["CPLevelIndicator"], function() +{ + printResults(configuration); +}); + +task ("build", ["default"]); + +task ("debug", function() +{ + ENV["CONFIGURATION"] = "Debug"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("release", function() +{ + ENV["CONFIGURATION"] = "Release"; + JAKE.subjake(["."], "build", ENV); +}); + +task ("run", ["debug"], function() +{ + OS.system(["open", FILE.join("Build", "Debug", "CPLevelIndicator", "index.html")]); +}); + +task ("run-release", ["release"], function() +{ + OS.system(["open", FILE.join("Build", "Release", "CPLevelIndicator", "index.html")]); +}); + +task ("deploy", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Deployment", "CPLevelIndicator")); + OS.system(["press", "-f", FILE.join("Build", "Release", "CPLevelIndicator"), FILE.join("Build", "Deployment", "CPLevelIndicator")]); + printResults("Deployment") +}); + +task ("desktop", ["release"], function() +{ + FILE.mkdirs(FILE.join("Build", "Desktop", "CPLevelIndicator")); + require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", "CPLevelIndicator"), FILE.join("Build", "Desktop", "CPLevelIndicator", "CPLevelIndicator.app")); + printResults("Desktop") +}); + +task ("run-desktop", ["desktop"], function() +{ + OS.system([FILE.join("Build", "Desktop", "CPLevelIndicator", "CPLevelIndicator.app", "Contents", "MacOS", "NativeHost"), "-i"]); +}); + +function printResults(configuration) +{ + print("----------------------------"); + print(configuration+" app built at path: "+FILE.join("Build", configuration, "CPLevelIndicator")); + print("----------------------------"); +} diff --git a/Tests/Manual/CPLanguageModelChatbot/Resources/spinner.gif b/Tests/Manual/CPLanguageModelChatbot/Resources/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..a5e705f6cbdf914e5e714c35a8dfef807f19e3c2 GIT binary patch literal 1434 zcmZvbdrVVj7{t$-UNOu86#VHu^|s&3rWfwHBbPG_8{SrlB{Tv1uvvj4t6zP!)#d!Ogc zP^62J^$0+~?-ZcXXpBaq%jFsw8L`=H2?+@R02Yg-*Xw;gACBV^i3CMahr>Z667S!? zk3LDe>VLEsU;sWo$Py~o!gWuaIAnaG3Sv``&tvc+8DJO!| zt4fcbQ8tW}a&xZfgtQ9BnFYLvGG|PvCNlg&uh@Q^w9Pz}+I^hCj`vu9JQADPqdkyk zR40xycD9geUgJu1e;$L`Fpa)?Wl-2&6hO@U*KrPqK(I1H=1h?2fce}6GhhP1oBZC# z1e@gt-qFa6lZrl%s1>bdxg*W)Ebt__O(4sj6(*2X@ciUClwHH#U(NRM7XAjS z+scDZ32J*PCoslsgS~#ZlCCGo`r>S6F)Ghw5wVlqHioFaw?ucD(XoLJ0j;28oPoPV z9A}dR$0SKn>uExfkl#j09o;v$BZ909R=*p{ATNq8BJW;YduX2QMOU7$a$_L5e!G^g zpbkx5G4&FZ%7m14rTN}5YB(;x7$5XOc_hf3E|Hw$TVg)P#qf~}nOn03uqsp>UG>vi zWThIoO)-1Q#@u#O;fMC#WAf@Xj70-0g9YZ;dA$HH1fW1q=9-c>>_yA0S$7M*5?}vi z)8MU`Q%P!7sEBBnd$DrKgFQ2?O%6LpdaC%F8Pn-)EC2t=j~ zoaLamla$FX!GQqWi!%s>sj-#5SJX0IF!TP=r21Z}s)k#btN0?=I7uD9C)Gb$fZ#sm zaVq7g`LwZ%PfNpN^_N-IGLHj@AV`s#4&va7_{Hw63G6BkSGXHdogTZ%QOiRb bDpZ@qYcJKM>x%b%*HX74c8MnqfHi*u-bC?g literal 0 HcmV?d00001 diff --git a/Tests/Manual/CPLanguageModelChatbot/index-debug.html b/Tests/Manual/CPLanguageModelChatbot/index-debug.html new file mode 100644 index 000000000..1097bcf1b --- /dev/null +++ b/Tests/Manual/CPLanguageModelChatbot/index-debug.html @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + CPLevelIndicator + + + + + + + + + + + + + + +
+
+ + + +
+
+ + + diff --git a/Tests/Manual/CPLanguageModelChatbot/index.html b/Tests/Manual/CPLanguageModelChatbot/index.html new file mode 100644 index 000000000..5a58b20d8 --- /dev/null +++ b/Tests/Manual/CPLanguageModelChatbot/index.html @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + __project.name__ + + + + + + + + + + + + +
+
+
+ +
+
+ +
+ + diff --git a/Tests/Manual/CPLanguageModelChatbot/main.j b/Tests/Manual/CPLanguageModelChatbot/main.j new file mode 100644 index 000000000..7a956f1f5 --- /dev/null +++ b/Tests/Manual/CPLanguageModelChatbot/main.j @@ -0,0 +1,18 @@ +/* + * AppController.j + * CPLevelIndicator + * + * Created by Alexander Ljungberg on May 28, 2011. + * Copyright 2011, WireLoad All rights reserved. + */ + +@import +@import + +@import "AppController.j" + + +function main(args, namedArgs) +{ + CPApplicationMain(args, namedArgs); +} From 8e04b172ce2baf08cea400cac7b337e719e07e87 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 15:34:21 +0200 Subject: [PATCH 02/35] simplification of demo --- .../CPLanguageModelChatbot/AppController.j | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/Tests/Manual/CPLanguageModelChatbot/AppController.j b/Tests/Manual/CPLanguageModelChatbot/AppController.j index f7553a496..6ab037520 100644 --- a/Tests/Manual/CPLanguageModelChatbot/AppController.j +++ b/Tests/Manual/CPLanguageModelChatbot/AppController.j @@ -348,35 +348,31 @@ } } -// Dynamically resize the SpeechBubbleBox to match updated streaming text changes -- (void)updateStreamingMessage:(CPString)newText +// Updates the placeholder message with the final generated response +- (void)updateMessage:(CPString)newText { if (!_currentStreamingTextView) return; - if (newText == "\n\n") - newText = ' '; - - [_currentStreamingTextView setString:[_currentStreamingTextView._textStorage string] + newText]; - //[_currentStreamingTextView sizeToFit]; + [_currentStreamingTextView setString:newText]; var textHeight = CGRectGetHeight([_currentStreamingTextView frame]); var cardHeight = textHeight + 20; var bubbleHeight = cardHeight + 10; - + var container = [_currentStreamingTextView superview]; // Resolves the SpeechBubbleBox var oldBubbleHeight = CGRectGetHeight([container frame]); - + [container setFrameSize:CGSizeMake(CGRectGetWidth([container frame]), bubbleHeight)]; [_currentStreamingTextView setFrameSize:CGSizeMake(CGRectGetWidth([_currentStreamingTextView frame]), textHeight)]; - [container setNeedsDisplay:YES]; // Instructs the canvas to clear and redraw paths - [_currentStreamingTextView setNeedsDisplay:YES]; // Instructs the canvas to clear and redraw paths - + [container setNeedsDisplay:YES]; + [_currentStreamingTextView setNeedsDisplay:YES]; + var diffHeight = bubbleHeight - oldBubbleHeight; _currentChatY += diffHeight; - + [_chatDocumentView setFrameSize:CGSizeMake(CGRectGetWidth([_chatScrollView bounds]), _currentChatY + 20)]; - + var boundsHeight = CGRectGetHeight([_chatScrollView bounds]); if (_currentChatY > boundsHeight) { [[_chatScrollView contentView] scrollToPoint:CGPointMake(0, _currentChatY - boundsHeight + 40)]; @@ -400,20 +396,19 @@ var selfRef = self; [_session respondToPrompt:prompt - onChunkReceived:function(chunk) { - [selfRef updateStreamingMessage:chunk]; - } - completed:function(finalText, error) { - [selfRef._chatInputField setEnabled:YES]; - [selfRef._chatInputField becomeFirstResponder]; - [selfRef._chatSendButton setEnabled:YES]; + options:nil + completionHandler:function(finalText, error) { + [selfRef._chatInputField setEnabled:YES]; + [selfRef._chatInputField becomeFirstResponder]; + [selfRef._chatSendButton setEnabled:YES]; - if (error) { - [selfRef updateStreamingMessage:@"Error: " + [error localizedDescription]]; - } else { - [selfRef updateStreamingMessage:finalText]; - } - }]; + if (error) { + [selfRef updateMessage:@"Error: " + [error localizedDescription]]; + } else { + debugger + [selfRef updateMessage:finalText]; + } + }]; } // --- CONFIGURATION POPUP SHEETS --- From 8c41c7b8ac57827686d286172a0ae2991dc7abac Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 15:54:07 +0200 Subject: [PATCH 03/35] fixed: import semantics --- Foundation/CPLanguageModel.j | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Foundation/CPLanguageModel.j b/Foundation/CPLanguageModel.j index d39254ffd..286abf536 100644 --- a/Foundation/CPLanguageModel.j +++ b/Foundation/CPLanguageModel.j @@ -14,12 +14,12 @@ * Lesser General Public License for more details. */ -@import -@import -@import -@import -@import -@import +@import "CPObject.j" +@import "CPString.j" +@import "CPError.j" +@import "CPDictionary.j" +@import "CPBundle.j" +@import "CPUserDefaults.j" // File-scoped fallback configuration parameters var CPLanguageModelSessionFallbackServiceType = @"ollama", From afc6691f07e18488a00f3c9728a37ee350335d2a Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 16:02:17 +0200 Subject: [PATCH 04/35] formatting --- Foundation/CPLanguageModel.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Foundation/CPLanguageModel.j b/Foundation/CPLanguageModel.j index 286abf536..2acc4f142 100644 --- a/Foundation/CPLanguageModel.j +++ b/Foundation/CPLanguageModel.j @@ -35,7 +35,7 @@ var CPLanguageModelSessionFallbackServiceType = @"ollama", @class CPSystemLanguageModel CPSystemLanguageModel provides a standard query interface to inspect the - availability of client-side, on-device large language models (such as Gemini Nano) + availability of client-side, on-device large language models (such as Gemma Nano on Chrome) in the active web browser runtime. */ @implementation CPSystemLanguageModel : CPObject From 1bd3c93717cd4448505a44fc510ff0c269819e92 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 16:22:34 +0200 Subject: [PATCH 05/35] formatting --- Foundation/CPLanguageModel.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Foundation/CPLanguageModel.j b/Foundation/CPLanguageModel.j index 2acc4f142..3a0e6499c 100644 --- a/Foundation/CPLanguageModel.j +++ b/Foundation/CPLanguageModel.j @@ -115,7 +115,7 @@ var sharedInstance = nil; @discussion CPLanguageModelSession handles text generation prompts. If on-device AI - (like Gemini Nano) is supported by the browser, it is utilized directly. + (like Gemma Nano) is supported by the browser, it is utilized directly. Otherwise, or if CPLanguageModelSessionEndorsesFallback is configured to YES, the session automatically falls back to configured network-based providers (such as local Ollama, Groq, or OpenRouter). From e5278fd17e749fb021f7c39e18ba16f2d8d785e9 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 16:30:16 +0200 Subject: [PATCH 06/35] new: run loop management --- Foundation/CPLanguageModel.j | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Foundation/CPLanguageModel.j b/Foundation/CPLanguageModel.j index 3a0e6499c..06af94c50 100644 --- a/Foundation/CPLanguageModel.j +++ b/Foundation/CPLanguageModel.j @@ -358,6 +358,8 @@ var sharedInstance = nil; if (!err && chunkHandler) chunkHandler(res); completionHandler(res, err); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update + }]; return; } @@ -378,7 +380,11 @@ var sharedInstance = nil; [selfRef _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) { if (!err && chunkHandler) chunkHandler(res); + completionHandler(res, err); + + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update + }]; }); }]; @@ -422,11 +428,13 @@ var sharedInstance = nil; promptPromise.then(function(result) { completionHandler(result, nil); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update }).catch(function(err) { var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain" code:2 userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]]; completionHandler(nil, cpError); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update }); } @@ -549,12 +557,14 @@ var sharedInstance = nil; } completionHandler(responseText, nil); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update }) .catch(function(err) { var cpError = [CPError errorWithDomain:@"CPLanguageModelErrorDomain" code:4 userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]]; completionHandler(nil, cpError); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update }); } @@ -571,6 +581,7 @@ var sharedInstance = nil; code:3 userInfo:[CPDictionary dictionaryWithObject:err.message forKey:CPLocalizedDescriptionKey]]; completionHandler(nil, cpError); + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; // pump run loop to force GUI update return; } From 44f6e0fded10c19e5c50c0099d2e56f5e9c0f638 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 16:38:40 +0200 Subject: [PATCH 07/35] cosmetic fixes --- .../CPLanguageModelChatbot/AppController.j | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Tests/Manual/CPLanguageModelChatbot/AppController.j b/Tests/Manual/CPLanguageModelChatbot/AppController.j index 6ab037520..d66f5a977 100644 --- a/Tests/Manual/CPLanguageModelChatbot/AppController.j +++ b/Tests/Manual/CPLanguageModelChatbot/AppController.j @@ -443,41 +443,35 @@ [_servicePopUp setAction:@selector(serviceTypeDidChange:)]; [sheetContentView addSubview:_servicePopUp]; - var endpointLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 65, 110, 20)]; + var endpointLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 67, 110, 20)]; [endpointLabel setStringValue:@"Endpoint URL:"]; - [endpointLabel setFont:[CPFont systemFontOfSize:12.0]]; [endpointLabel setAlignment:CPRightTextAlignment]; [sheetContentView addSubview:endpointLabel]; - _endpointField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 62, CGRectGetWidth(sheetBounds) - 155, 24)]; + _endpointField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 62, CGRectGetWidth(sheetBounds) - 155, 27)]; [_endpointField setEditable:YES]; [_endpointField setBezeled:YES]; - [_endpointField setFont:[CPFont systemFontOfSize:12.0]]; [sheetContentView addSubview:_endpointField]; - var modelLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 105, 110, 20)]; + var modelLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 107, 110, 20)]; [modelLabel setStringValue:@"Model Name:"]; - [modelLabel setFont:[CPFont systemFontOfSize:12.0]]; [modelLabel setAlignment:CPRightTextAlignment]; [sheetContentView addSubview:modelLabel]; - _modelField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 102, CGRectGetWidth(sheetBounds) - 155, 24)]; + _modelField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 102, CGRectGetWidth(sheetBounds) - 155, 27)]; [_modelField setEditable:YES]; [_modelField setBezeled:YES]; - [_modelField setFont:[CPFont systemFontOfSize:12.0]]; [sheetContentView addSubview:_modelField]; - var apiKeyLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 145, 110, 20)]; + var apiKeyLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 147, 110, 20)]; [apiKeyLabel setStringValue:@"API Key:"]; - [apiKeyLabel setFont:[CPFont systemFontOfSize:12.0]]; [apiKeyLabel setAlignment:CPRightTextAlignment]; [sheetContentView addSubview:apiKeyLabel]; - _apiKeyField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 142, CGRectGetWidth(sheetBounds) - 155, 24)]; + _apiKeyField = [[CPTextField alloc] initWithFrame:CGRectMake(135, 142, CGRectGetWidth(sheetBounds) - 155, 27)]; [_apiKeyField setEditable:YES]; [_apiKeyField setBezeled:YES]; [_apiKeyField setSecure:YES]; - [_apiKeyField setFont:[CPFont systemFontOfSize:12.0]]; [sheetContentView addSubview:_apiKeyField]; var btnY = CGRectGetHeight(sheetBounds) - 45; From 2c275b279b0b36d9bffa1664bae0621754afe3dc Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 18:13:51 +0200 Subject: [PATCH 08/35] new: markdown support in manual test --- .../CPLanguageModelChatbot/AppController.j | 173 +++++- .../CPLanguageModelChatbot/MarkdownParser.j | 553 ++++++++++++++++++ 2 files changed, 702 insertions(+), 24 deletions(-) create mode 100644 Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j diff --git a/Tests/Manual/CPLanguageModelChatbot/AppController.j b/Tests/Manual/CPLanguageModelChatbot/AppController.j index d66f5a977..a96c0c474 100644 --- a/Tests/Manual/CPLanguageModelChatbot/AppController.j +++ b/Tests/Manual/CPLanguageModelChatbot/AppController.j @@ -6,6 +6,7 @@ @import @import @import +@import "MarkdownParser.j" // --- SUBCLASS: SPEECH BUBBLE VIEW --- @implementation SpeechBubbleBox : CPView @@ -307,25 +308,72 @@ [self appendMessage:@"Session initialized.\n\nEnter a query below to begin." isUser:NO]; } -// Append a formatted message using SpeechBubbleBox and sizeToFit calculation - (void)appendMessage:(CPString)text isUser:(BOOL)isUser { var docWidth = CGRectGetWidth([_chatScrollView bounds]) - 50; var textView = [[CPTextView alloc] initWithFrame:CGRectMake(15, 10, docWidth - 30, 20)]; - [textView insertText:text]; - [textView setTextColor:[CPColor blackColor]]; - [textView setFont:[CPFont systemFontOfSize:11.0]]; [textView setEditable:YES]; - [textView setRichText:NO]; + [textView setRichText:YES]; + [textView setSelectable:YES]; [textView setBackgroundColor:[CPColor clearColor]]; [textView setAutoresizingMask:CPViewWidthSizable]; - [textView sizeToFit]; - var textHeight = CGRectGetHeight([textView frame]); + [textView setVerticallyResizable:YES]; + [textView setHorizontallyResizable:NO]; + [[textView textContainer] setWidthTracksTextView:YES]; - var cardHeight = textHeight + 20; // 10px spacing top/bottom - var bubbleHeight = cardHeight + 10; // Extra 10px spacing for the bottom triangle pointer [1] + var textHeight = 20; + + try { + var parsedAttrStr = [MarkdownParser attributedStringFromMarkdown:text]; + [textView insertText:parsedAttrStr]; + + var length = [parsedAttrStr length]; + var searchRange = CPMakeRange(0, 0); + var layoutManager = [textView layoutManager]; + var textContainer = [textView textContainer]; + var textViewWidth = CGRectGetWidth([textView bounds]); + + while (searchRange.location < length) + { + var attrs = [parsedAttrStr attributesAtIndex:searchRange.location effectiveRange:searchRange]; + var tableAttachment = [attrs objectForKey:@"TableAttachmentAttribute"]; + if (tableAttachment) { + var rect = [layoutManager boundingRectForGlyphRange:searchRange inTextContainer:textContainer]; + var inset = [textView textContainerInset]; + var totalWidth = textViewWidth - 40; + + if (totalWidth < 100) + totalWidth = 100; + + rect.origin.x += inset.width; + rect.origin.y += inset.height; + rect.size.width = totalWidth; + + [tableAttachment resizeToWidth:totalWidth]; + [tableAttachment setFrame:rect]; + + [textView addSubview:tableAttachment]; + } + searchRange.location = CPMaxRange(searchRange); + } + + var usedRect = [layoutManager usedRectForTextContainer:textContainer]; + textHeight = CGRectGetHeight(usedRect); + if (textHeight < 20) { + textHeight = 20; + } + } catch (e) { + // Fallback bei Parsing-Fehler + console.error("Markdown append failure: ", e); + [textView setString:text]; + [textView sizeToFit]; + textHeight = CGRectGetHeight([textView frame]); + } + + var cardHeight = textHeight + 20; + var bubbleHeight = cardHeight + 10; var fillColor = isUser ? [CPColor colorWithRed:0.90 green:0.93 blue:1.0 alpha:1.0] : [CPColor colorWithWhite:0.96 alpha:1.0]; var cardBox = [[SpeechBubbleBox alloc] initWithFrame:CGRectMake(15, _currentChatY, docWidth, bubbleHeight) @@ -339,7 +387,7 @@ _currentStreamingTextView = textView; } - _currentChatY += bubbleHeight + 15; // 15px gap between consecutive messages + _currentChatY += bubbleHeight + 15; [_chatDocumentView setFrameSize:CGSizeMake(CGRectGetWidth([_chatScrollView bounds]), _currentChatY + 20)]; var boundsHeight = CGRectGetHeight([_chatScrollView bounds]); @@ -348,28 +396,106 @@ } } -// Updates the placeholder message with the final generated response - (void)updateMessage:(CPString)newText { if (!_currentStreamingTextView) return; - [_currentStreamingTextView setString:newText]; + try { + // 1. Alte TableMatrixView Subviews entfernen + var subviews = [_currentStreamingTextView subviews]; + if (subviews) { + for (var i = [subviews count] - 1; i >= 0; i--) { + var sub = [subviews objectAtIndex:i]; + if (sub && [sub isKindOfClass:[TableMatrixView class]]) { + [sub removeFromSuperview]; + } + } + } - var textHeight = CGRectGetHeight([_currentStreamingTextView frame]); - var cardHeight = textHeight + 20; - var bubbleHeight = cardHeight + 10; + // 2. Text über Standard-Zuweisung neu setzen + var parsedAttrStr = [MarkdownParser attributedStringFromMarkdown:newText]; + + [_currentStreamingTextView setEditable:YES]; + [_currentStreamingTextView setString:@""]; + [_currentStreamingTextView insertText:parsedAttrStr]; + [_currentStreamingTextView setEditable:NO]; - var container = [_currentStreamingTextView superview]; // Resolves the SpeechBubbleBox - var oldBubbleHeight = CGRectGetHeight([container frame]); + // 3. Tabellen-Layout berechnen + var length = [parsedAttrStr length]; + var searchRange = CPMakeRange(0, 0); + var layoutManager = [_currentStreamingTextView layoutManager]; + var textContainer = [_currentStreamingTextView textContainer]; + var textViewWidth = CGRectGetWidth([_currentStreamingTextView bounds]); - [container setFrameSize:CGSizeMake(CGRectGetWidth([container frame]), bubbleHeight)]; - [_currentStreamingTextView setFrameSize:CGSizeMake(CGRectGetWidth([_currentStreamingTextView frame]), textHeight)]; - [container setNeedsDisplay:YES]; - [_currentStreamingTextView setNeedsDisplay:YES]; + while (searchRange.location < length) + { + var attrs = [parsedAttrStr attributesAtIndex:searchRange.location effectiveRange:searchRange]; + var tableAttachment = [attrs objectForKey:@"TableAttachmentAttribute"]; + if (tableAttachment) { + var rect = [layoutManager boundingRectForGlyphRange:searchRange inTextContainer:textContainer]; + var inset = [_currentStreamingTextView textContainerInset]; + var totalWidth = textViewWidth - 40; - var diffHeight = bubbleHeight - oldBubbleHeight; - _currentChatY += diffHeight; + if (totalWidth < 100) + totalWidth = 100; + + rect.origin.x += inset.width; + rect.origin.y += inset.height; + rect.size.width = totalWidth; + + [tableAttachment resizeToWidth:totalWidth]; + [tableAttachment setFrame:rect]; + + [_currentStreamingTextView addSubview:tableAttachment]; + } + searchRange.location = CPMaxRange(searchRange); + } + + // 4. Container-Größen anpassen + var usedRect = [layoutManager usedRectForTextContainer:textContainer]; + var textHeight = CGRectGetHeight(usedRect); + if (textHeight < 20) { + textHeight = 20; + } + + var cardHeight = textHeight + 20; + var bubbleHeight = cardHeight + 10; + + var container = [_currentStreamingTextView superview]; + if (container) { + var oldBubbleHeight = CGRectGetHeight([container frame]); + + [container setFrameSize:CGSizeMake(CGRectGetWidth([container frame]), bubbleHeight)]; + [_currentStreamingTextView setFrameSize:CGSizeMake(CGRectGetWidth([_currentStreamingTextView frame]), textHeight)]; + [container setNeedsDisplay:YES]; + [_currentStreamingTextView setNeedsDisplay:YES]; + + var diffHeight = bubbleHeight - oldBubbleHeight; + _currentChatY += diffHeight; + } + + } catch (e) { + console.error("Markdown rendering failure: ", e); + + [_currentStreamingTextView setEditable:YES]; + [_currentStreamingTextView setString:newText]; + [_currentStreamingTextView setEditable:NO]; + + [_currentStreamingTextView sizeToFit]; + var textHeight = CGRectGetHeight([_currentStreamingTextView frame]); + + var container = [_currentStreamingTextView superview]; + if (container) { + var oldBubbleHeight = CGRectGetHeight([container frame]); + var bubbleHeight = textHeight + 30; + [container setFrameSize:CGSizeMake(CGRectGetWidth([container frame]), bubbleHeight)]; + [container setNeedsDisplay:YES]; + + var diffHeight = bubbleHeight - oldBubbleHeight; + _currentChatY += diffHeight; + } + } [_chatDocumentView setFrameSize:CGSizeMake(CGRectGetWidth([_chatScrollView bounds]), _currentChatY + 20)]; @@ -405,7 +531,6 @@ if (error) { [selfRef updateMessage:@"Error: " + [error localizedDescription]]; } else { - debugger [selfRef updateMessage:finalText]; } }]; diff --git a/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j b/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j new file mode 100644 index 000000000..522ce6d38 --- /dev/null +++ b/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j @@ -0,0 +1,553 @@ +// Markdown parser +// Markdown & Table Rendering Engine for Cappuccino +// + +@import +@import + +// --- SUBCLASS: TABLE MATRIX VIEW (DYNAMIC TEXT-VIEW ENGINE) --- +@implementation TableMatrixView : CPView +{ + CPArray _headers; + CPArray _rows; +} + +- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows width:(float)totalWidth +{ + self = [super initWithFrame:CGRectMake(0, 0, totalWidth, 20)]; + if (self) + { + _headers = headers; + _rows = rows; + + var numCols = [headers count]; + + if (numCols == 0 && [rows count] > 0) + numCols = [[rows objectAtIndex:0] count]; + + if (self._DOMElement) { + self._DOMElement.style.borderTop = "1px solid #e0e0e0"; + self._DOMElement.style.borderLeft = "1px solid #e0e0e0"; + } + + if ([headers count] > 0) { + for (var c = 0; c < numCols; c++) { + var headerText = [headers objectAtIndex:c]; + var cellView = [self createCellWithText:headerText frame:CGRectMakeZero() isHeader:YES]; + [self addSubview:cellView]; + } + } + + for (var r = 0; r < [rows count]; r++) { + var rowData = [rows objectAtIndex:r]; + for (var c = 0; c < numCols; c++) { + var cellText = @""; + + if (c < [rowData count]) + cellText = [rowData objectAtIndex:c]; + + var cellView = [self createCellWithText:cellText frame:CGRectMakeZero() isHeader:NO]; + [self addSubview:cellView]; + } + } + + [self resizeToWidth:totalWidth]; + } + return self; +} + +- (CPView)createCellWithText:(CPString)text frame:(CGRect)frame isHeader:(BOOL)isHeader +{ + var initialWidth = (frame.size.width > 0) ? frame.size.width : 120.0; + var initialHeight = (frame.size.height > 0) ? frame.size.height : 28.0; + + var cellContainer = [[CPView alloc] initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, initialWidth, initialHeight)]; + [cellContainer setBackgroundColor:isHeader ? [CPColor colorWithWhite:0.92 alpha:1.0] : [CPColor whiteColor]]; + + var borderView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, initialWidth, initialHeight)]; + [borderView setBackgroundColor:[CPColor clearColor]]; + [borderView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; + if (borderView._DOMElement) { + borderView._DOMElement.style.borderBottom = "1px solid #e0e0e0"; + borderView._DOMElement.style.borderRight = "1px solid #e0e0e0"; + borderView._DOMElement.style.boxSizing = "border-box"; + } + [cellContainer addSubview:borderView]; + + var textContainer = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(initialWidth - 8, 1e7)]; + var textView = [[CPTextView alloc] initWithFrame:CGRectMake(4, 2, initialWidth - 8, initialHeight - 4) textContainer:textContainer]; + [textView setEditable:NO]; + [textView setSelectable:YES]; + [textView setBackgroundColor:[CPColor clearColor]]; + [textView setVerticallyResizable:YES]; + [textView setHorizontallyResizable:NO]; + [[textView textContainer] setWidthTracksTextView:YES]; + + var parsedText = [MarkdownParser parseInlineMarkdown:text isHeader:isHeader headerLevel:3]; + + var storage = [textView textStorage]; + if (storage && [storage respondsToSelector:@selector(setAttributedString:)]) { + [storage setAttributedString:parsedText]; + } else { + [textView setEditable:YES]; + [textView setString:@""]; + [textView insertText:parsedText]; + [textView setEditable:NO]; + } + + [cellContainer addSubview:textView]; + return cellContainer; +} + +- (CPTextView)getTextViewFromCell:(CPView)cellView +{ + var subviews = [cellView subviews]; + for (var i = 0; i < [subviews count]; i++) { + var sub = [subviews objectAtIndex:i]; + if ([sub isKindOfClass:[CPTextView class]]) { + return sub; + } + } + return nil; +} + +- (void)resizeToWidth:(float)newWidth +{ + var numCols = [_headers count]; + if (numCols == 0 && [_rows count] > 0) { + numCols = [[_rows objectAtIndex:0] count]; + } + if (numCols == 0) return; + + var subviews = [self subviews]; + var colNaturalWidths = []; + var colMinWidths = []; + for (var c = 0; c < numCols; c++) { + colNaturalWidths[c] = 80.0; + colMinWidths[c] = 60.0; + } + + var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)]; + [measureTextField setFont:[CPFont systemFontOfSize:13.0]]; + + var measureCell = function(cellText, isHeader, colIndex) { + var parsedText = [MarkdownParser parseInlineMarkdown:cellText isHeader:isHeader headerLevel:3]; + [measureTextField setFont:isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0]]; + + [measureTextField setStringValue:[parsedText string]]; + [measureTextField sizeToFit]; + var naturalW = CGRectGetWidth([measureTextField frame]) + 24.0; + if (naturalW > colNaturalWidths[colIndex]) { + colNaturalWidths[colIndex] = naturalW; + } + + var words = cellText.split(/[\s\-]/); + var maxWordW = 50.0; + for (var w = 0; w < words.length; w++) { + var word = words[w].trim(); + if (word.length === 0) continue; + [measureTextField setStringValue:word]; + [measureTextField sizeToFit]; + var wordW = CGRectGetWidth([measureTextField frame]) + 30.0; + if (wordW > maxWordW) { + maxWordW = wordW; + } + } + if (maxWordW > colMinWidths[colIndex]) { + colMinWidths[colIndex] = maxWordW; + } + }; + + for (var c = 0; c < [_headers count]; c++) { + measureCell([_headers objectAtIndex:c], YES, c); + } + + for (var r = 0; r < [_rows count]; r++) { + var rowData = [_rows objectAtIndex:r]; + for (var c = 0; c < numCols; c++) { + var cellText = @""; + if (c < [rowData count]) { + cellText = [rowData objectAtIndex:c]; + } + measureCell(cellText, NO, c); + } + } + + var totalMinWidth = 0.0; + for (var c = 0; c < numCols; c++) { + totalMinWidth += colMinWidths[c]; + } + + var colWidths = []; + + if (newWidth <= totalMinWidth) { + var remainingWidth = newWidth; + for (var c = 0; c < numCols; c++) { + var w = Math.floor((colMinWidths[c] / totalMinWidth) * newWidth); + colWidths[c] = w; + remainingWidth -= w; + } + if (numCols > 0) colWidths[numCols - 1] += remainingWidth; + } else { + for (var c = 0; c < numCols; c++) { + colWidths[c] = colMinWidths[c]; + } + + var totalGrowthCapacity = 0.0; + var growthCapacities = []; + for (var c = 0; c < numCols; c++) { + var capacity = Math.max(0.0, colNaturalWidths[c] - colMinWidths[c]); + growthCapacities[c] = capacity; + totalGrowthCapacity += capacity; + } + + var extraWidth = newWidth - totalMinWidth; + var remainingExtra = extraWidth; + + for (var c = 0; c < numCols; c++) { + if (totalGrowthCapacity > 0) { + var w = Math.floor((growthCapacities[c] / totalGrowthCapacity) * extraWidth); + colWidths[c] += w; + remainingExtra -= w; + } + } + if (numCols > 0) { + colWidths[numCols - 1] += remainingExtra; + } + } + + var cellIndex = 0; + var currentY = 0; + + var layoutRow = function(startIndex) { + var maxCellHeight = 28.0; + + for (var c = 0; c < numCols; c++) { + var idx = startIndex + c; + if (idx < [subviews count]) { + var cellView = [subviews objectAtIndex:idx]; + var textView = [self getTextViewFromCell:cellView]; + if (textView) { + var targetWidth = Math.max(10.0, colWidths[c] - 8); + [[textView textContainer] setContainerSize:CGSizeMake(targetWidth, 1e7)]; + + var usedRect = [[textView layoutManager] usedRectForTextContainer:[textView textContainer]]; + var wrappedHeight = CGRectGetHeight(usedRect) + 12.0; + if (wrappedHeight > maxCellHeight) { + maxCellHeight = wrappedHeight; + } + } + } + } + + var currentX = 0; + for (var c = 0; c < numCols; c++) { + var idx = startIndex + c; + if (idx < [subviews count]) { + var cellView = [subviews objectAtIndex:idx]; + [cellView setFrame:CGRectMake(currentX, currentY, colWidths[c], maxCellHeight)]; + + var textView = [self getTextViewFromCell:cellView]; + if (textView) { + var targetWidth = Math.max(10.0, colWidths[c] - 8); + var textY = 4.0; + var finalTextViewHeight = maxCellHeight - 8.0; + [textView setFrame:CGRectMake(4, textY, targetWidth, finalTextViewHeight)]; + } + + var cellSubviews = [cellView subviews]; + if ([cellSubviews count] > 0) { + [[cellSubviews objectAtIndex:0] setFrame:CGRectMake(0, 0, colWidths[c], maxCellHeight)]; + } + } + currentX += colWidths[c]; + } + + return maxCellHeight; + }; + + if ([_headers count] > 0) { + var headerHeight = layoutRow(cellIndex); + cellIndex += numCols; + currentY += headerHeight; + } + + for (var r = 0; r < [_rows count]; r++) { + var rowHeight = layoutRow(cellIndex); + cellIndex += numCols; + currentY += rowHeight; + } + + [self setFrameSize:CGSizeMake(newWidth, currentY)]; +} + +@end + +// --- MARKDOWN PARSER CLASS --- +@implementation MarkdownParser : CPObject + ++ (CPAttributedString)attributedStringFromMarkdown:(CPString)markdown +{ + if (!markdown) { + return [[CPAttributedString alloc] initWithString:@""]; + } + + var result = [[CPMutableAttributedString alloc] initWithString:@""]; + var lines = markdown.split(/\r?\n/); + + var i = 0; + while (i < lines.length) { + var line = lines[i]; + + if ([self isTableHeaderLine:line] && i + 1 < lines.length && [self isTableSeparatorLine:lines[i+1]]) { + var headers = [self parseTableCells:line]; + var rows = [CPMutableArray array]; + + i += 2; + while (i < lines.length && [self isTableRowLine:lines[i]]) { + [rows addObject:[self parseTableCells:lines[i]]]; + i++; + } + + var numCols = [headers count]; + if (numCols == 0 && [rows count] > 0) { + numCols = [[rows objectAtIndex:0] count]; + } + + var totalNaturalW = 0.0; + var colNaturalWidths = []; + var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)]; + [measureTextField setFont:[CPFont systemFontOfSize:11.0]]; + + for (var c = 0; c < numCols; c++) { + var cellW = 80.0; + + if (c < headers.length) { + var parsedText = [self parseInlineMarkdown:headers[c] isHeader:YES headerLevel:3]; + [measureTextField setStringValue:[parsedText string]]; + [measureTextField sizeToFit]; + cellW = Math.max(cellW, CGRectGetWidth([measureTextField frame]) + 24.0); + } + + for (var r = 0; r < [rows count]; r++) { + var rowData = [rows objectAtIndex:r]; + if (c < [rowData count]) { + var parsedText = [self parseInlineMarkdown:rowData[c] isHeader:NO headerLevel:3]; + [measureTextField setStringValue:[parsedText string]]; + [measureTextField sizeToFit]; + cellW = Math.max(cellW, CGRectGetWidth([measureTextField frame]) + 24.0); + } + } + colNaturalWidths[c] = cellW; + totalNaturalW += cellW; + } + + var estimatedHeight = 36.0; + for (var r = 0; r < [rows count]; r++) { + var rowData = [rows objectAtIndex:r]; + var maxCellHeight = 28.0; + + for (var c = 0; c < numCols; c++) { + var cellText = @""; + if (c < [rowData count]) { + cellText = [rowData objectAtIndex:c]; + } + var charCount = cellText.length; + + var proportion = totalNaturalW > 0 ? (colNaturalWidths[c] / totalNaturalW) : (1.0 / numCols); + var estimatedColWidth = proportion * 500.0; + var charsPerLine = Math.max(10.0, Math.floor(estimatedColWidth / 6.5)); + + var estimatedLines = Math.ceil(charCount / charsPerLine); + if (estimatedLines < 1) estimatedLines = 1; + + var cellHeight = (estimatedLines * 16.0) + 12.0; + if (cellHeight > maxCellHeight) { + maxCellHeight = cellHeight; + } + } + estimatedHeight += maxCellHeight; + } + + var lineCount = Math.ceil(estimatedHeight / 16.0) + 1; + var newlineStr = ""; + for (var nl = 0; nl < lineCount; nl++) { + newlineStr += "\n"; + } + + var tableAttrStr = [[CPMutableAttributedString alloc] initWithString:newlineStr]; + var matrixView = [[TableMatrixView alloc] initWithHeaders:headers rows:rows width:500.0]; + + [tableAttrStr addAttribute:@"TableAttachmentAttribute" value:matrixView range:CPMakeRange(0, [tableAttrStr length])]; + [result appendAttributedString:tableAttrStr]; + continue; + } + + var isHeader = false; + var headerLevel = 0; + + var headerMatch = line.match(/^(#{1,6})\s+(.*)$/); + if (headerMatch) { + headerLevel = headerMatch[1].length; + line = headerMatch[2]; + isHeader = true; + } + + var isListItem = false; + var listMatch = line.match(/^(\*|-)\s+(.*)$/); + if (listMatch) { + line = " • " + listMatch[2]; + isListItem = true; + } + + var parsedLine = [self parseInlineMarkdown:line isHeader:isHeader headerLevel:headerLevel]; + [result appendAttributedString:parsedLine]; + + if (i < lines.length - 1) { + [result appendAttributedString:[[CPAttributedString alloc] initWithString:@"\n"]]; + } + + i++; + } + + return result; +} + ++ (BOOL)isTableHeaderLine:(CPString)line +{ + var trimmed = line.trim(); + return trimmed.indexOf('|') !== -1; +} + ++ (BOOL)isTableSeparatorLine:(CPString)line +{ + var trimmed = line.trim(); + if (trimmed.indexOf('|') === -1) return NO; + var stripped = trimmed.replace(/[\s|:\-]/g, ''); + return stripped.length === 0; +} + ++ (BOOL)isTableRowLine:(CPString)line +{ + var trimmed = line.trim(); + return trimmed.indexOf('|') !== -1; +} + ++ (CPArray)parseTableCells:(CPString)line +{ + var parts = line.split('|'); + var cells = [CPMutableArray array]; + var startIdx = 0; + var endIdx = parts.length; + if (parts[0].trim() === "") startIdx = 1; + if (parts[parts.length - 1].trim() === "") endIdx = parts.length - 1; + + for (var j = startIdx; j < endIdx; j++) { + [cells addObject:parts[j].trim()]; + } + return cells; +} + ++ (CPAttributedString)parseInlineMarkdown:(CPString)text isHeader:(BOOL)isHeader headerLevel:(int)level +{ + var baseFontSize = 11.0; + var fontSize = baseFontSize; + var isBold = isHeader; + var isItalic = NO; + + if (isHeader) { + if (level == 1) fontSize = 15.0; + else if (level == 2) fontSize = 13.0; + else fontSize = 12.0; + } + + var result = [[CPMutableAttributedString alloc] initWithString:@""]; + var currentSegment = ""; + var i = 0; + var len = text.length; + + var defaultFont = [CPFont systemFontOfSize:fontSize]; + if (isBold) { + defaultFont = [CPFont boldSystemFontOfSize:fontSize]; + } + + while (i < len) { + if (i + 2 < len && text.substr(i, 3) === "***") { + if (currentSegment.length > 0) { + [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; + currentSegment = ""; + } + isBold = !isBold; + isItalic = !isItalic; + i += 3; + continue; + } + if (i + 1 < len && text.substr(i, 2) === "**") { + if (currentSegment.length > 0) { + [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; + currentSegment = ""; + } + isBold = !isBold; + i += 2; + continue; + } + if (text.charAt(i) === "*") { + if (currentSegment.length > 0) { + [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; + currentSegment = ""; + } + isItalic = !isItalic; + i++; + continue; + } + if (text.charAt(i) === "`") { + if (currentSegment.length > 0) { + [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; + currentSegment = ""; + } + var codeText = ""; + i++; + while (i < len && text.charAt(i) !== "`") { + codeText += text.charAt(i); + i++; + } + [result appendAttributedString:[self attributedStringWithText:codeText font:defaultFont bold:NO italic:NO code:YES]]; + i++; + continue; + } + + currentSegment += text.charAt(i); + i++; + } + + if (currentSegment.length > 0) { + [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; + } + + return result; +} + ++ (CPAttributedString)attributedStringWithText:(CPString)text font:(CPFont)baseFont bold:(BOOL)b italic:(BOOL)it code:(BOOL)c +{ + var fontName = [baseFont familyName]; + var fontSize = [baseFont size]; + var finalFont = baseFont; + + if (c) { + finalFont = [CPFont fontWithName:@"Courier" size:fontSize]; + } else { + finalFont = [CPFont _fontWithName:fontName size:fontSize bold:b italic:it]; + } + + if (!finalFont) { + finalFont = [CPFont systemFontOfSize:fontSize]; + } + + var dict = [CPDictionary dictionaryWithObjectsAndKeys: + finalFont, CPFontAttributeName, + [CPColor blackColor], CPForegroundColorAttributeName + ]; + return [[CPAttributedString alloc] initWithString:text attributes:dict]; +} + +@end \ No newline at end of file From 6139216146987299a2be694c3594356828949624 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sat, 13 Jun 2026 19:53:39 +0200 Subject: [PATCH 09/35] new: RTFProducer PROTOCOL METHOD ADDITIONS --- .../CPLanguageModelChatbot/MarkdownParser.j | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j b/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j index 522ce6d38..646ee53fc 100644 --- a/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j +++ b/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j @@ -25,11 +25,13 @@ if (numCols == 0 && [rows count] > 0) numCols = [[rows objectAtIndex:0] count]; + // Apply outer borders for collapsed table cell grid rendering if (self._DOMElement) { self._DOMElement.style.borderTop = "1px solid #e0e0e0"; self._DOMElement.style.borderLeft = "1px solid #e0e0e0"; } + // Initialize header cells if ([headers count] > 0) { for (var c = 0; c < numCols; c++) { var headerText = [headers objectAtIndex:c]; @@ -38,6 +40,7 @@ } } + // Initialize body rows for (var r = 0; r < [rows count]; r++) { var rowData = [rows objectAtIndex:r]; for (var c = 0; c < numCols; c++) { @@ -56,14 +59,36 @@ return self; } +// --- PUBLIC RTFProducer PROTOCOL METHOD ADDITIONS --- + +/** + * Public getter to expose header values for serialization (e.g., RTF producing) + */ +- (CPArray)headers +{ + return _headers; +} + +/** + * Public getter to expose row values for serialization (e.g., RTF producing) + */ +- (CPArray)rows +{ + return _rows; +} + +// ---------------------------------------- + - (CPView)createCellWithText:(CPString)text frame:(CGRect)frame isHeader:(BOOL)isHeader { + // Prevent zero-width constraints during layout initialization var initialWidth = (frame.size.width > 0) ? frame.size.width : 120.0; var initialHeight = (frame.size.height > 0) ? frame.size.height : 28.0; var cellContainer = [[CPView alloc] initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, initialWidth, initialHeight)]; [cellContainer setBackgroundColor:isHeader ? [CPColor colorWithWhite:0.92 alpha:1.0] : [CPColor whiteColor]]; + // Draw borders only on the right and bottom edges to avoid double lines in the grid var borderView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, initialWidth, initialHeight)]; [borderView setBackgroundColor:[CPColor clearColor]]; [borderView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; @@ -74,6 +99,7 @@ } [cellContainer addSubview:borderView]; + // Create CPTextView with vertical expansion support var textContainer = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(initialWidth - 8, 1e7)]; var textView = [[CPTextView alloc] initWithFrame:CGRectMake(4, 2, initialWidth - 8, initialHeight - 4) textContainer:textContainer]; [textView setEditable:NO]; @@ -83,6 +109,7 @@ [textView setHorizontallyResizable:NO]; [[textView textContainer] setWidthTracksTextView:YES]; + // Parse nested inline styling (e.g., **bold**) inside cell contents var parsedText = [MarkdownParser parseInlineMarkdown:text isHeader:isHeader headerLevel:3]; var storage = [textView textStorage]; @@ -99,6 +126,7 @@ return cellContainer; } +// Helper to extract the core CPTextView from a cell container hierarchy - (CPTextView)getTextViewFromCell:(CPView)cellView { var subviews = [cellView subviews]; @@ -111,6 +139,7 @@ return nil; } +// Calculate grid column widths and row heights dynamically based on contents - (void)resizeToWidth:(float)newWidth { var numCols = [_headers count]; @@ -120,13 +149,16 @@ if (numCols == 0) return; var subviews = [self subviews]; - var colNaturalWidths = []; - var colMinWidths = []; + + // 1. Initialize sizing metrics + var colNaturalWidths = []; // Ideal width without word wrapping + var colMinWidths = []; // Minimum width required to avoid word-level breaking for (var c = 0; c < numCols; c++) { colNaturalWidths[c] = 80.0; colMinWidths[c] = 60.0; } + // Auxiliary text field used to measure layout bounds var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)]; [measureTextField setFont:[CPFont systemFontOfSize:13.0]]; @@ -134,6 +166,7 @@ var parsedText = [MarkdownParser parseInlineMarkdown:cellText isHeader:isHeader headerLevel:3]; [measureTextField setFont:isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0]]; + // Measure un-wrapped natural width [measureTextField setStringValue:[parsedText string]]; [measureTextField sizeToFit]; var naturalW = CGRectGetWidth([measureTextField frame]) + 24.0; @@ -141,6 +174,7 @@ colNaturalWidths[colIndex] = naturalW; } + // Measure strict minimum width boundary dictated by the longest word var words = cellText.split(/[\s\-]/); var maxWordW = 50.0; for (var w = 0; w < words.length; w++) { @@ -158,10 +192,12 @@ } }; + // Measure header row dimensions for (var c = 0; c < [_headers count]; c++) { measureCell([_headers objectAtIndex:c], YES, c); } + // Measure content rows dimensions for (var r = 0; r < [_rows count]; r++) { var rowData = [_rows objectAtIndex:r]; for (var c = 0; c < numCols; c++) { @@ -173,6 +209,7 @@ } } + // 2. Distribute spacing based on minimum-width constraints var totalMinWidth = 0.0; for (var c = 0; c < numCols; c++) { totalMinWidth += colMinWidths[c]; @@ -181,6 +218,7 @@ var colWidths = []; if (newWidth <= totalMinWidth) { + // Fallback for extremely constrained view widths var remainingWidth = newWidth; for (var c = 0; c < numCols; c++) { var w = Math.floor((colMinWidths[c] / totalMinWidth) * newWidth); @@ -189,6 +227,8 @@ } if (numCols > 0) colWidths[numCols - 1] += remainingWidth; } else { + // Standard flow: Ensure each column receives its minimum width, + // and distribute surplus space proportionally to expansion capacities. for (var c = 0; c < numCols; c++) { colWidths[c] = colMinWidths[c]; } @@ -219,9 +259,11 @@ var cellIndex = 0; var currentY = 0; + // Dynamic row calculation using LayoutManager metrics var layoutRow = function(startIndex) { var maxCellHeight = 28.0; + // Pass 1: Assign column widths and determine wrapped text height limits for (var c = 0; c < numCols; c++) { var idx = startIndex + c; if (idx < [subviews count]) { @@ -240,6 +282,7 @@ } } + // Pass 2: Position containers and finalize text view structures var currentX = 0; for (var c = 0; c < numCols; c++) { var idx = startIndex + c; @@ -266,12 +309,14 @@ return maxCellHeight; }; + // Apply layout constraints to headers if ([_headers count] > 0) { var headerHeight = layoutRow(cellIndex); cellIndex += numCols; currentY += headerHeight; } + // Apply layout constraints to content rows sequentially for (var r = 0; r < [_rows count]; r++) { var rowHeight = layoutRow(cellIndex); cellIndex += numCols; @@ -299,6 +344,7 @@ while (i < lines.length) { var line = lines[i]; + // Detect structured Markdown tables if ([self isTableHeaderLine:line] && i + 1 < lines.length && [self isTableSeparatorLine:lines[i+1]]) { var headers = [self parseTableCells:line]; var rows = [CPMutableArray array]; @@ -314,6 +360,7 @@ numCols = [[rows objectAtIndex:0] count]; } + // Measure column boundaries prior to allocation var totalNaturalW = 0.0; var colNaturalWidths = []; var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)]; @@ -342,6 +389,7 @@ totalNaturalW += cellW; } + // Generate proportional lineheight estimations to offset rendering sizes var estimatedHeight = 36.0; for (var r = 0; r < [rows count]; r++) { var rowData = [rows objectAtIndex:r]; @@ -369,6 +417,7 @@ estimatedHeight += maxCellHeight; } + // Format newline spacing metrics for inline rendering layouts var lineCount = Math.ceil(estimatedHeight / 16.0) + 1; var newlineStr = ""; for (var nl = 0; nl < lineCount; nl++) { @@ -386,6 +435,7 @@ var isHeader = false; var headerLevel = 0; + // Parse markdown headers var headerMatch = line.match(/^(#{1,6})\s+(.*)$/); if (headerMatch) { headerLevel = headerMatch[1].length; @@ -393,6 +443,7 @@ isHeader = true; } + // Parse list items var isListItem = false; var listMatch = line.match(/^(\*|-)\s+(.*)$/); if (listMatch) { @@ -550,4 +601,4 @@ return [[CPAttributedString alloc] initWithString:text attributes:dict]; } -@end \ No newline at end of file +@end From 381c1880c282f8ff88e34195872535af2502aca6 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 16 Jun 2026 20:15:34 +0200 Subject: [PATCH 10/35] cleanup --- .../CPLanguageModelChatbot/AppController.j | 117 +--- .../CPLanguageModelChatbot/MarkdownParser.j | 604 ------------------ 2 files changed, 28 insertions(+), 693 deletions(-) delete mode 100644 Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j diff --git a/Tests/Manual/CPLanguageModelChatbot/AppController.j b/Tests/Manual/CPLanguageModelChatbot/AppController.j index a96c0c474..37dd898ba 100644 --- a/Tests/Manual/CPLanguageModelChatbot/AppController.j +++ b/Tests/Manual/CPLanguageModelChatbot/AppController.j @@ -6,7 +6,6 @@ @import @import @import -@import "MarkdownParser.j" // --- SUBCLASS: SPEECH BUBBLE VIEW --- @implementation SpeechBubbleBox : CPView @@ -267,13 +266,12 @@ var systemModel = [CPSystemLanguageModel defaultModel]; [_statusLabel setStringValue:@"Checking capability..."]; - var selfRef = self; [systemModel supportsLocaleWithCompletionHandler:function(supported) { - if (supported) { - [selfRef._statusLabel setStringValue:@"On-Device LLM: Supported"]; - } else { - [selfRef._statusLabel setStringValue:@"On-Device LLM: Not available. Using fallback."]; - } + + if (supported) + [_statusLabel setStringValue:@"On-Device LLM: Supported"]; + else + [_statusLabel setStringValue:@"On-Device LLM: Not available. Using fallback."]; }]; } @@ -324,47 +322,22 @@ [[textView textContainer] setWidthTracksTextView:YES]; var textHeight = 20; + var layoutManager = [textView layoutManager]; + var textContainer = [textView textContainer]; try { - var parsedAttrStr = [MarkdownParser attributedStringFromMarkdown:text]; + var parsedAttrStr = [CPMarkdownParser attributedStringFromMarkdown:text]; [textView insertText:parsedAttrStr]; - - var length = [parsedAttrStr length]; - var searchRange = CPMakeRange(0, 0); - var layoutManager = [textView layoutManager]; - var textContainer = [textView textContainer]; - var textViewWidth = CGRectGetWidth([textView bounds]); - while (searchRange.location < length) - { - var attrs = [parsedAttrStr attributesAtIndex:searchRange.location effectiveRange:searchRange]; - var tableAttachment = [attrs objectForKey:@"TableAttachmentAttribute"]; - if (tableAttachment) { - var rect = [layoutManager boundingRectForGlyphRange:searchRange inTextContainer:textContainer]; - var inset = [textView textContainerInset]; - var totalWidth = textViewWidth - 40; - - if (totalWidth < 100) - totalWidth = 100; - - rect.origin.x += inset.width; - rect.origin.y += inset.height; - rect.size.width = totalWidth; - - [tableAttachment resizeToWidth:totalWidth]; - [tableAttachment setFrame:rect]; - - [textView addSubview:tableAttachment]; - } - searchRange.location = CPMaxRange(searchRange); - } - var usedRect = [layoutManager usedRectForTextContainer:textContainer]; textHeight = CGRectGetHeight(usedRect); + if (textHeight < 20) { textHeight = 20; } - } catch (e) { + } + catch (e) + { // Fallback bei Parsing-Fehler console.error("Markdown append failure: ", e); [textView setString:text]; @@ -391,6 +364,7 @@ [_chatDocumentView setFrameSize:CGSizeMake(CGRectGetWidth([_chatScrollView bounds]), _currentChatY + 20)]; var boundsHeight = CGRectGetHeight([_chatScrollView bounds]); + if (_currentChatY > boundsHeight) { [[_chatScrollView contentView] scrollToPoint:CGPointMake(0, _currentChatY - boundsHeight + 40)]; } @@ -402,57 +376,22 @@ return; try { - // 1. Alte TableMatrixView Subviews entfernen - var subviews = [_currentStreamingTextView subviews]; - if (subviews) { - for (var i = [subviews count] - 1; i >= 0; i--) { - var sub = [subviews objectAtIndex:i]; - if (sub && [sub isKindOfClass:[TableMatrixView class]]) { - [sub removeFromSuperview]; - } - } - } + // 1. Text über Standard-Zuweisung neu setzen + var parsedAttrStr = [CPMarkdownParser attributedStringFromMarkdown:newText]; - // 2. Text über Standard-Zuweisung neu setzen - var parsedAttrStr = [MarkdownParser attributedStringFromMarkdown:newText]; - [_currentStreamingTextView setEditable:YES]; [_currentStreamingTextView setString:@""]; [_currentStreamingTextView insertText:parsedAttrStr]; [_currentStreamingTextView setEditable:NO]; - // 3. Tabellen-Layout berechnen + // 2. Tabellen-Layout berechnen var length = [parsedAttrStr length]; var searchRange = CPMakeRange(0, 0); var layoutManager = [_currentStreamingTextView layoutManager]; var textContainer = [_currentStreamingTextView textContainer]; var textViewWidth = CGRectGetWidth([_currentStreamingTextView bounds]); - while (searchRange.location < length) - { - var attrs = [parsedAttrStr attributesAtIndex:searchRange.location effectiveRange:searchRange]; - var tableAttachment = [attrs objectForKey:@"TableAttachmentAttribute"]; - if (tableAttachment) { - var rect = [layoutManager boundingRectForGlyphRange:searchRange inTextContainer:textContainer]; - var inset = [_currentStreamingTextView textContainerInset]; - var totalWidth = textViewWidth - 40; - - if (totalWidth < 100) - totalWidth = 100; - - rect.origin.x += inset.width; - rect.origin.y += inset.height; - rect.size.width = totalWidth; - - [tableAttachment resizeToWidth:totalWidth]; - [tableAttachment setFrame:rect]; - - [_currentStreamingTextView addSubview:tableAttachment]; - } - searchRange.location = CPMaxRange(searchRange); - } - - // 4. Container-Größen anpassen + // 3. Container-Größen anpassen var usedRect = [layoutManager usedRectForTextContainer:textContainer]; var textHeight = CGRectGetHeight(usedRect); if (textHeight < 20) { @@ -475,7 +414,9 @@ _currentChatY += diffHeight; } - } catch (e) { + } + catch (e) + { console.error("Markdown rendering failure: ", e); [_currentStreamingTextView setEditable:YES]; @@ -508,6 +449,7 @@ - (void)submitPromptAction:(id)sender { var prompt = [_chatInputField stringValue]; + if (!prompt || [prompt stringByTrimmingWhitespace] === @"") { return; } @@ -519,20 +461,17 @@ [self appendMessage:prompt isUser:YES]; [self appendMessage:@"Generating response..." isUser:NO]; - var selfRef = self; - [_session respondToPrompt:prompt options:nil completionHandler:function(finalText, error) { - [selfRef._chatInputField setEnabled:YES]; - [selfRef._chatInputField becomeFirstResponder]; - [selfRef._chatSendButton setEnabled:YES]; + [_chatInputField setEnabled:YES]; + [_chatInputField becomeFirstResponder]; + [_chatSendButton setEnabled:YES]; - if (error) { - [selfRef updateMessage:@"Error: " + [error localizedDescription]]; - } else { - [selfRef updateMessage:finalText]; - } + if (error) + [self updateMessage:@"Error: " + [error localizedDescription]]; + else + [self updateMessage:finalText]; }]; } diff --git a/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j b/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j deleted file mode 100644 index 646ee53fc..000000000 --- a/Tests/Manual/CPLanguageModelChatbot/MarkdownParser.j +++ /dev/null @@ -1,604 +0,0 @@ -// Markdown parser -// Markdown & Table Rendering Engine for Cappuccino -// - -@import -@import - -// --- SUBCLASS: TABLE MATRIX VIEW (DYNAMIC TEXT-VIEW ENGINE) --- -@implementation TableMatrixView : CPView -{ - CPArray _headers; - CPArray _rows; -} - -- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows width:(float)totalWidth -{ - self = [super initWithFrame:CGRectMake(0, 0, totalWidth, 20)]; - if (self) - { - _headers = headers; - _rows = rows; - - var numCols = [headers count]; - - if (numCols == 0 && [rows count] > 0) - numCols = [[rows objectAtIndex:0] count]; - - // Apply outer borders for collapsed table cell grid rendering - if (self._DOMElement) { - self._DOMElement.style.borderTop = "1px solid #e0e0e0"; - self._DOMElement.style.borderLeft = "1px solid #e0e0e0"; - } - - // Initialize header cells - if ([headers count] > 0) { - for (var c = 0; c < numCols; c++) { - var headerText = [headers objectAtIndex:c]; - var cellView = [self createCellWithText:headerText frame:CGRectMakeZero() isHeader:YES]; - [self addSubview:cellView]; - } - } - - // Initialize body rows - for (var r = 0; r < [rows count]; r++) { - var rowData = [rows objectAtIndex:r]; - for (var c = 0; c < numCols; c++) { - var cellText = @""; - - if (c < [rowData count]) - cellText = [rowData objectAtIndex:c]; - - var cellView = [self createCellWithText:cellText frame:CGRectMakeZero() isHeader:NO]; - [self addSubview:cellView]; - } - } - - [self resizeToWidth:totalWidth]; - } - return self; -} - -// --- PUBLIC RTFProducer PROTOCOL METHOD ADDITIONS --- - -/** - * Public getter to expose header values for serialization (e.g., RTF producing) - */ -- (CPArray)headers -{ - return _headers; -} - -/** - * Public getter to expose row values for serialization (e.g., RTF producing) - */ -- (CPArray)rows -{ - return _rows; -} - -// ---------------------------------------- - -- (CPView)createCellWithText:(CPString)text frame:(CGRect)frame isHeader:(BOOL)isHeader -{ - // Prevent zero-width constraints during layout initialization - var initialWidth = (frame.size.width > 0) ? frame.size.width : 120.0; - var initialHeight = (frame.size.height > 0) ? frame.size.height : 28.0; - - var cellContainer = [[CPView alloc] initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, initialWidth, initialHeight)]; - [cellContainer setBackgroundColor:isHeader ? [CPColor colorWithWhite:0.92 alpha:1.0] : [CPColor whiteColor]]; - - // Draw borders only on the right and bottom edges to avoid double lines in the grid - var borderView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, initialWidth, initialHeight)]; - [borderView setBackgroundColor:[CPColor clearColor]]; - [borderView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - if (borderView._DOMElement) { - borderView._DOMElement.style.borderBottom = "1px solid #e0e0e0"; - borderView._DOMElement.style.borderRight = "1px solid #e0e0e0"; - borderView._DOMElement.style.boxSizing = "border-box"; - } - [cellContainer addSubview:borderView]; - - // Create CPTextView with vertical expansion support - var textContainer = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(initialWidth - 8, 1e7)]; - var textView = [[CPTextView alloc] initWithFrame:CGRectMake(4, 2, initialWidth - 8, initialHeight - 4) textContainer:textContainer]; - [textView setEditable:NO]; - [textView setSelectable:YES]; - [textView setBackgroundColor:[CPColor clearColor]]; - [textView setVerticallyResizable:YES]; - [textView setHorizontallyResizable:NO]; - [[textView textContainer] setWidthTracksTextView:YES]; - - // Parse nested inline styling (e.g., **bold**) inside cell contents - var parsedText = [MarkdownParser parseInlineMarkdown:text isHeader:isHeader headerLevel:3]; - - var storage = [textView textStorage]; - if (storage && [storage respondsToSelector:@selector(setAttributedString:)]) { - [storage setAttributedString:parsedText]; - } else { - [textView setEditable:YES]; - [textView setString:@""]; - [textView insertText:parsedText]; - [textView setEditable:NO]; - } - - [cellContainer addSubview:textView]; - return cellContainer; -} - -// Helper to extract the core CPTextView from a cell container hierarchy -- (CPTextView)getTextViewFromCell:(CPView)cellView -{ - var subviews = [cellView subviews]; - for (var i = 0; i < [subviews count]; i++) { - var sub = [subviews objectAtIndex:i]; - if ([sub isKindOfClass:[CPTextView class]]) { - return sub; - } - } - return nil; -} - -// Calculate grid column widths and row heights dynamically based on contents -- (void)resizeToWidth:(float)newWidth -{ - var numCols = [_headers count]; - if (numCols == 0 && [_rows count] > 0) { - numCols = [[_rows objectAtIndex:0] count]; - } - if (numCols == 0) return; - - var subviews = [self subviews]; - - // 1. Initialize sizing metrics - var colNaturalWidths = []; // Ideal width without word wrapping - var colMinWidths = []; // Minimum width required to avoid word-level breaking - for (var c = 0; c < numCols; c++) { - colNaturalWidths[c] = 80.0; - colMinWidths[c] = 60.0; - } - - // Auxiliary text field used to measure layout bounds - var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)]; - [measureTextField setFont:[CPFont systemFontOfSize:13.0]]; - - var measureCell = function(cellText, isHeader, colIndex) { - var parsedText = [MarkdownParser parseInlineMarkdown:cellText isHeader:isHeader headerLevel:3]; - [measureTextField setFont:isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0]]; - - // Measure un-wrapped natural width - [measureTextField setStringValue:[parsedText string]]; - [measureTextField sizeToFit]; - var naturalW = CGRectGetWidth([measureTextField frame]) + 24.0; - if (naturalW > colNaturalWidths[colIndex]) { - colNaturalWidths[colIndex] = naturalW; - } - - // Measure strict minimum width boundary dictated by the longest word - var words = cellText.split(/[\s\-]/); - var maxWordW = 50.0; - for (var w = 0; w < words.length; w++) { - var word = words[w].trim(); - if (word.length === 0) continue; - [measureTextField setStringValue:word]; - [measureTextField sizeToFit]; - var wordW = CGRectGetWidth([measureTextField frame]) + 30.0; - if (wordW > maxWordW) { - maxWordW = wordW; - } - } - if (maxWordW > colMinWidths[colIndex]) { - colMinWidths[colIndex] = maxWordW; - } - }; - - // Measure header row dimensions - for (var c = 0; c < [_headers count]; c++) { - measureCell([_headers objectAtIndex:c], YES, c); - } - - // Measure content rows dimensions - for (var r = 0; r < [_rows count]; r++) { - var rowData = [_rows objectAtIndex:r]; - for (var c = 0; c < numCols; c++) { - var cellText = @""; - if (c < [rowData count]) { - cellText = [rowData objectAtIndex:c]; - } - measureCell(cellText, NO, c); - } - } - - // 2. Distribute spacing based on minimum-width constraints - var totalMinWidth = 0.0; - for (var c = 0; c < numCols; c++) { - totalMinWidth += colMinWidths[c]; - } - - var colWidths = []; - - if (newWidth <= totalMinWidth) { - // Fallback for extremely constrained view widths - var remainingWidth = newWidth; - for (var c = 0; c < numCols; c++) { - var w = Math.floor((colMinWidths[c] / totalMinWidth) * newWidth); - colWidths[c] = w; - remainingWidth -= w; - } - if (numCols > 0) colWidths[numCols - 1] += remainingWidth; - } else { - // Standard flow: Ensure each column receives its minimum width, - // and distribute surplus space proportionally to expansion capacities. - for (var c = 0; c < numCols; c++) { - colWidths[c] = colMinWidths[c]; - } - - var totalGrowthCapacity = 0.0; - var growthCapacities = []; - for (var c = 0; c < numCols; c++) { - var capacity = Math.max(0.0, colNaturalWidths[c] - colMinWidths[c]); - growthCapacities[c] = capacity; - totalGrowthCapacity += capacity; - } - - var extraWidth = newWidth - totalMinWidth; - var remainingExtra = extraWidth; - - for (var c = 0; c < numCols; c++) { - if (totalGrowthCapacity > 0) { - var w = Math.floor((growthCapacities[c] / totalGrowthCapacity) * extraWidth); - colWidths[c] += w; - remainingExtra -= w; - } - } - if (numCols > 0) { - colWidths[numCols - 1] += remainingExtra; - } - } - - var cellIndex = 0; - var currentY = 0; - - // Dynamic row calculation using LayoutManager metrics - var layoutRow = function(startIndex) { - var maxCellHeight = 28.0; - - // Pass 1: Assign column widths and determine wrapped text height limits - for (var c = 0; c < numCols; c++) { - var idx = startIndex + c; - if (idx < [subviews count]) { - var cellView = [subviews objectAtIndex:idx]; - var textView = [self getTextViewFromCell:cellView]; - if (textView) { - var targetWidth = Math.max(10.0, colWidths[c] - 8); - [[textView textContainer] setContainerSize:CGSizeMake(targetWidth, 1e7)]; - - var usedRect = [[textView layoutManager] usedRectForTextContainer:[textView textContainer]]; - var wrappedHeight = CGRectGetHeight(usedRect) + 12.0; - if (wrappedHeight > maxCellHeight) { - maxCellHeight = wrappedHeight; - } - } - } - } - - // Pass 2: Position containers and finalize text view structures - var currentX = 0; - for (var c = 0; c < numCols; c++) { - var idx = startIndex + c; - if (idx < [subviews count]) { - var cellView = [subviews objectAtIndex:idx]; - [cellView setFrame:CGRectMake(currentX, currentY, colWidths[c], maxCellHeight)]; - - var textView = [self getTextViewFromCell:cellView]; - if (textView) { - var targetWidth = Math.max(10.0, colWidths[c] - 8); - var textY = 4.0; - var finalTextViewHeight = maxCellHeight - 8.0; - [textView setFrame:CGRectMake(4, textY, targetWidth, finalTextViewHeight)]; - } - - var cellSubviews = [cellView subviews]; - if ([cellSubviews count] > 0) { - [[cellSubviews objectAtIndex:0] setFrame:CGRectMake(0, 0, colWidths[c], maxCellHeight)]; - } - } - currentX += colWidths[c]; - } - - return maxCellHeight; - }; - - // Apply layout constraints to headers - if ([_headers count] > 0) { - var headerHeight = layoutRow(cellIndex); - cellIndex += numCols; - currentY += headerHeight; - } - - // Apply layout constraints to content rows sequentially - for (var r = 0; r < [_rows count]; r++) { - var rowHeight = layoutRow(cellIndex); - cellIndex += numCols; - currentY += rowHeight; - } - - [self setFrameSize:CGSizeMake(newWidth, currentY)]; -} - -@end - -// --- MARKDOWN PARSER CLASS --- -@implementation MarkdownParser : CPObject - -+ (CPAttributedString)attributedStringFromMarkdown:(CPString)markdown -{ - if (!markdown) { - return [[CPAttributedString alloc] initWithString:@""]; - } - - var result = [[CPMutableAttributedString alloc] initWithString:@""]; - var lines = markdown.split(/\r?\n/); - - var i = 0; - while (i < lines.length) { - var line = lines[i]; - - // Detect structured Markdown tables - if ([self isTableHeaderLine:line] && i + 1 < lines.length && [self isTableSeparatorLine:lines[i+1]]) { - var headers = [self parseTableCells:line]; - var rows = [CPMutableArray array]; - - i += 2; - while (i < lines.length && [self isTableRowLine:lines[i]]) { - [rows addObject:[self parseTableCells:lines[i]]]; - i++; - } - - var numCols = [headers count]; - if (numCols == 0 && [rows count] > 0) { - numCols = [[rows objectAtIndex:0] count]; - } - - // Measure column boundaries prior to allocation - var totalNaturalW = 0.0; - var colNaturalWidths = []; - var measureTextField = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 10000.0, 24.0)]; - [measureTextField setFont:[CPFont systemFontOfSize:11.0]]; - - for (var c = 0; c < numCols; c++) { - var cellW = 80.0; - - if (c < headers.length) { - var parsedText = [self parseInlineMarkdown:headers[c] isHeader:YES headerLevel:3]; - [measureTextField setStringValue:[parsedText string]]; - [measureTextField sizeToFit]; - cellW = Math.max(cellW, CGRectGetWidth([measureTextField frame]) + 24.0); - } - - for (var r = 0; r < [rows count]; r++) { - var rowData = [rows objectAtIndex:r]; - if (c < [rowData count]) { - var parsedText = [self parseInlineMarkdown:rowData[c] isHeader:NO headerLevel:3]; - [measureTextField setStringValue:[parsedText string]]; - [measureTextField sizeToFit]; - cellW = Math.max(cellW, CGRectGetWidth([measureTextField frame]) + 24.0); - } - } - colNaturalWidths[c] = cellW; - totalNaturalW += cellW; - } - - // Generate proportional lineheight estimations to offset rendering sizes - var estimatedHeight = 36.0; - for (var r = 0; r < [rows count]; r++) { - var rowData = [rows objectAtIndex:r]; - var maxCellHeight = 28.0; - - for (var c = 0; c < numCols; c++) { - var cellText = @""; - if (c < [rowData count]) { - cellText = [rowData objectAtIndex:c]; - } - var charCount = cellText.length; - - var proportion = totalNaturalW > 0 ? (colNaturalWidths[c] / totalNaturalW) : (1.0 / numCols); - var estimatedColWidth = proportion * 500.0; - var charsPerLine = Math.max(10.0, Math.floor(estimatedColWidth / 6.5)); - - var estimatedLines = Math.ceil(charCount / charsPerLine); - if (estimatedLines < 1) estimatedLines = 1; - - var cellHeight = (estimatedLines * 16.0) + 12.0; - if (cellHeight > maxCellHeight) { - maxCellHeight = cellHeight; - } - } - estimatedHeight += maxCellHeight; - } - - // Format newline spacing metrics for inline rendering layouts - var lineCount = Math.ceil(estimatedHeight / 16.0) + 1; - var newlineStr = ""; - for (var nl = 0; nl < lineCount; nl++) { - newlineStr += "\n"; - } - - var tableAttrStr = [[CPMutableAttributedString alloc] initWithString:newlineStr]; - var matrixView = [[TableMatrixView alloc] initWithHeaders:headers rows:rows width:500.0]; - - [tableAttrStr addAttribute:@"TableAttachmentAttribute" value:matrixView range:CPMakeRange(0, [tableAttrStr length])]; - [result appendAttributedString:tableAttrStr]; - continue; - } - - var isHeader = false; - var headerLevel = 0; - - // Parse markdown headers - var headerMatch = line.match(/^(#{1,6})\s+(.*)$/); - if (headerMatch) { - headerLevel = headerMatch[1].length; - line = headerMatch[2]; - isHeader = true; - } - - // Parse list items - var isListItem = false; - var listMatch = line.match(/^(\*|-)\s+(.*)$/); - if (listMatch) { - line = " • " + listMatch[2]; - isListItem = true; - } - - var parsedLine = [self parseInlineMarkdown:line isHeader:isHeader headerLevel:headerLevel]; - [result appendAttributedString:parsedLine]; - - if (i < lines.length - 1) { - [result appendAttributedString:[[CPAttributedString alloc] initWithString:@"\n"]]; - } - - i++; - } - - return result; -} - -+ (BOOL)isTableHeaderLine:(CPString)line -{ - var trimmed = line.trim(); - return trimmed.indexOf('|') !== -1; -} - -+ (BOOL)isTableSeparatorLine:(CPString)line -{ - var trimmed = line.trim(); - if (trimmed.indexOf('|') === -1) return NO; - var stripped = trimmed.replace(/[\s|:\-]/g, ''); - return stripped.length === 0; -} - -+ (BOOL)isTableRowLine:(CPString)line -{ - var trimmed = line.trim(); - return trimmed.indexOf('|') !== -1; -} - -+ (CPArray)parseTableCells:(CPString)line -{ - var parts = line.split('|'); - var cells = [CPMutableArray array]; - var startIdx = 0; - var endIdx = parts.length; - if (parts[0].trim() === "") startIdx = 1; - if (parts[parts.length - 1].trim() === "") endIdx = parts.length - 1; - - for (var j = startIdx; j < endIdx; j++) { - [cells addObject:parts[j].trim()]; - } - return cells; -} - -+ (CPAttributedString)parseInlineMarkdown:(CPString)text isHeader:(BOOL)isHeader headerLevel:(int)level -{ - var baseFontSize = 11.0; - var fontSize = baseFontSize; - var isBold = isHeader; - var isItalic = NO; - - if (isHeader) { - if (level == 1) fontSize = 15.0; - else if (level == 2) fontSize = 13.0; - else fontSize = 12.0; - } - - var result = [[CPMutableAttributedString alloc] initWithString:@""]; - var currentSegment = ""; - var i = 0; - var len = text.length; - - var defaultFont = [CPFont systemFontOfSize:fontSize]; - if (isBold) { - defaultFont = [CPFont boldSystemFontOfSize:fontSize]; - } - - while (i < len) { - if (i + 2 < len && text.substr(i, 3) === "***") { - if (currentSegment.length > 0) { - [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; - currentSegment = ""; - } - isBold = !isBold; - isItalic = !isItalic; - i += 3; - continue; - } - if (i + 1 < len && text.substr(i, 2) === "**") { - if (currentSegment.length > 0) { - [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; - currentSegment = ""; - } - isBold = !isBold; - i += 2; - continue; - } - if (text.charAt(i) === "*") { - if (currentSegment.length > 0) { - [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; - currentSegment = ""; - } - isItalic = !isItalic; - i++; - continue; - } - if (text.charAt(i) === "`") { - if (currentSegment.length > 0) { - [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; - currentSegment = ""; - } - var codeText = ""; - i++; - while (i < len && text.charAt(i) !== "`") { - codeText += text.charAt(i); - i++; - } - [result appendAttributedString:[self attributedStringWithText:codeText font:defaultFont bold:NO italic:NO code:YES]]; - i++; - continue; - } - - currentSegment += text.charAt(i); - i++; - } - - if (currentSegment.length > 0) { - [result appendAttributedString:[self attributedStringWithText:currentSegment font:defaultFont bold:isBold italic:isItalic code:NO]]; - } - - return result; -} - -+ (CPAttributedString)attributedStringWithText:(CPString)text font:(CPFont)baseFont bold:(BOOL)b italic:(BOOL)it code:(BOOL)c -{ - var fontName = [baseFont familyName]; - var fontSize = [baseFont size]; - var finalFont = baseFont; - - if (c) { - finalFont = [CPFont fontWithName:@"Courier" size:fontSize]; - } else { - finalFont = [CPFont _fontWithName:fontName size:fontSize bold:b italic:it]; - } - - if (!finalFont) { - finalFont = [CPFont systemFontOfSize:fontSize]; - } - - var dict = [CPDictionary dictionaryWithObjectsAndKeys: - finalFont, CPFontAttributeName, - [CPColor blackColor], CPForegroundColorAttributeName - ]; - return [[CPAttributedString alloc] initWithString:text attributes:dict]; -} - -@end From 9fe0df74b3b24b35b05aea5154f02119284d4207 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 16 Jun 2026 20:20:25 +0200 Subject: [PATCH 11/35] formatting --- Foundation/CPLanguageModel.j | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/Foundation/CPLanguageModel.j b/Foundation/CPLanguageModel.j index 06af94c50..88e611467 100644 --- a/Foundation/CPLanguageModel.j +++ b/Foundation/CPLanguageModel.j @@ -294,12 +294,11 @@ var sharedInstance = nil; return; } - var selfRef = self, - instructions = [self instructions]; + var instructions = [self instructions]; [CPLanguageModelSession _getChromeFactoryWithCompletionHandler:function(factory, error) { if (error) { - [selfRef _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler]; + [self _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler]; return; } @@ -313,10 +312,10 @@ var sharedInstance = nil; } factory.create(sessionOptions).then(function(session) { - [selfRef setChromeSession:session]; - [selfRef _executePrompt:prompt options:options completionHandler:completionHandler]; + [self setChromeSession:session]; + [self _executePrompt:prompt options:options completionHandler:completionHandler]; }).catch(function(err) { - [selfRef _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler]; + [self _executeRemoteFallbackWithPrompt:prompt options:options completionHandler:completionHandler]; }); }]; } @@ -349,12 +348,11 @@ var sharedInstance = nil; return; } - var selfRef = self, - instructions = [self instructions]; + var instructions = [self instructions]; [CPLanguageModelSession _getChromeFactoryWithCompletionHandler:function(factory, error) { if (error) { - [selfRef _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) { + [self _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) { if (!err && chunkHandler) chunkHandler(res); completionHandler(res, err); @@ -369,15 +367,16 @@ var sharedInstance = nil; expectedInputs: [{ type: "text", languages: ["en"] }], expectedOutputs: [{ type: "text", languages: ["en"] }] }; - if (instructions) { + + if (instructions) options.systemPrompt = instructions; - } factory.create(options).then(function(session) { - [selfRef setChromeSession:session]; - [selfRef _executePromptStreaming:prompt onChunkReceived:chunkHandler completed:completionHandler]; - }).catch(function(err) { - [selfRef _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) { + [self setChromeSession:session]; + [self _executePromptStreaming:prompt onChunkReceived:chunkHandler completed:completionHandler]; + }).catch(function(err) + { + [self _executeRemoteFallbackWithPrompt:prompt options:nil completionHandler:function(res, err) { if (!err && chunkHandler) chunkHandler(res); From 227eda9d92dce1fdb1a8302475e51a5e71942157 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 17 Jun 2026 07:57:17 +0200 Subject: [PATCH 12/35] new: spinner in manual test --- .../CPLanguageModelChatbot/AppController.j | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Tests/Manual/CPLanguageModelChatbot/AppController.j b/Tests/Manual/CPLanguageModelChatbot/AppController.j index 37dd898ba..ac5e6899a 100644 --- a/Tests/Manual/CPLanguageModelChatbot/AppController.j +++ b/Tests/Manual/CPLanguageModelChatbot/AppController.j @@ -128,6 +128,7 @@ CPLanguageModelSession _session; float _currentChatY; id _currentStreamingTextView; + CPProgressIndicator _currentSpinner; } - (void)applicationDidFinishLaunching:(CPNotification)aNotification @@ -292,6 +293,12 @@ _currentChatY = 15; _currentStreamingTextView = nil; + if (_currentSpinner) { + [_currentSpinner stopAnimation:self]; + [_currentSpinner removeFromSuperview]; + _currentSpinner = nil; + } + if (_session) { [_session destroy]; } @@ -310,7 +317,21 @@ { var docWidth = CGRectGetWidth([_chatScrollView bounds]) - 50; - var textView = [[CPTextView alloc] initWithFrame:CGRectMake(15, 10, docWidth - 30, 20)]; + var spinner = nil; + var textX = 15; + + // Erstelle den Spinner, falls die Generierung beginnt + if (!isUser && [text isEqualToString:@"Generating response..."]) { + spinner = [[CPProgressIndicator alloc] initWithFrame:CGRectMake(15, 12, 16, 16)]; + [spinner setStyle:CPProgressIndicatorSpinningStyle]; + [spinner setControlSize:CPSmallControlSize]; + [spinner setIndeterminate:YES]; + [spinner startAnimation:self]; + _currentSpinner = spinner; + textX = 38; // Verschiebe den Text nach rechts, um Platz für den Spinner zu schaffen + } + + var textView = [[CPTextView alloc] initWithFrame:CGRectMake(textX, 10, docWidth - 15 - textX, 20)]; [textView setEditable:YES]; [textView setRichText:YES]; [textView setSelectable:YES]; @@ -353,6 +374,10 @@ isUser:isUser fillColor:fillColor]; + if (spinner) { + [cardBox addSubview:spinner]; + } + [cardBox addSubview:textView]; [_chatDocumentView addSubview:cardBox]; @@ -375,6 +400,16 @@ if (!_currentStreamingTextView) return; + // Falls ein aktiver Spinner läuft, stoppe und entferne ihn, und richte das Textfeld wieder links aus + if (_currentSpinner) { + [_currentSpinner stopAnimation:self]; + [_currentSpinner removeFromSuperview]; + _currentSpinner = nil; + + var docWidth = CGRectGetWidth([_chatScrollView bounds]) - 50; + [_currentStreamingTextView setFrame:CGRectMake(15, 10, docWidth - 30, CGRectGetHeight([_currentStreamingTextView frame]))]; + } + try { // 1. Text über Standard-Zuweisung neu setzen var parsedAttrStr = [CPMarkdownParser attributedStringFromMarkdown:newText]; From 2f63a5fd1059b1e7359ab26537f84148510be80f Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 17 Jun 2026 08:25:24 +0200 Subject: [PATCH 13/35] formatting --- .../CPLanguageModelChatbot/AppController.j | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/Tests/Manual/CPLanguageModelChatbot/AppController.j b/Tests/Manual/CPLanguageModelChatbot/AppController.j index ac5e6899a..3d12ec8d3 100644 --- a/Tests/Manual/CPLanguageModelChatbot/AppController.j +++ b/Tests/Manual/CPLanguageModelChatbot/AppController.j @@ -419,25 +419,22 @@ [_currentStreamingTextView insertText:parsedAttrStr]; [_currentStreamingTextView setEditable:NO]; - // 2. Tabellen-Layout berechnen - var length = [parsedAttrStr length]; - var searchRange = CPMakeRange(0, 0); var layoutManager = [_currentStreamingTextView layoutManager]; var textContainer = [_currentStreamingTextView textContainer]; - var textViewWidth = CGRectGetWidth([_currentStreamingTextView bounds]); - // 3. Container-Größen anpassen var usedRect = [layoutManager usedRectForTextContainer:textContainer]; var textHeight = CGRectGetHeight(usedRect); - if (textHeight < 20) { + + if (textHeight < 20) textHeight = 20; - } var cardHeight = textHeight + 20; var bubbleHeight = cardHeight + 10; - var container = [_currentStreamingTextView superview]; - if (container) { + var container = [_currentStreamingTextView superview]; + + if (container) + { var oldBubbleHeight = CGRectGetHeight([container frame]); [container setFrameSize:CGSizeMake(CGRectGetWidth([container frame]), bubbleHeight)]; @@ -462,6 +459,7 @@ var textHeight = CGRectGetHeight([_currentStreamingTextView frame]); var container = [_currentStreamingTextView superview]; + if (container) { var oldBubbleHeight = CGRectGetHeight([container frame]); var bubbleHeight = textHeight + 30; @@ -476,18 +474,17 @@ [_chatDocumentView setFrameSize:CGSizeMake(CGRectGetWidth([_chatScrollView bounds]), _currentChatY + 20)]; var boundsHeight = CGRectGetHeight([_chatScrollView bounds]); - if (_currentChatY > boundsHeight) { + + if (_currentChatY > boundsHeight) [[_chatScrollView contentView] scrollToPoint:CGPointMake(0, _currentChatY - boundsHeight + 40)]; - } } - (void)submitPromptAction:(id)sender { var prompt = [_chatInputField stringValue]; - if (!prompt || [prompt stringByTrimmingWhitespace] === @"") { + if (!prompt || [prompt stringByTrimmingWhitespace] === @"") return; - } [_chatInputField setStringValue:@""]; [_chatInputField setEnabled:NO]; From e288ea36909f1da5e483795496806292be1750dd Mon Sep 17 00:00:00 2001 From: David Richardson Date: Tue, 21 Jul 2026 16:54:35 -0600 Subject: [PATCH 14/35] Modernize CGContextCanvas.j: Remove CPP macros and legacy fallbacks As part of the ongoing effort to remove the C-preprocessor (CPP) from the new Lisette compiler pipeline, this commit refactors CGContextCanvas.j to use native JavaScript and HTML5 Canvas APIs directly. Key changes: - Removed all `#define` macros that wrapped standard Canvas API calls (e.g., `_CGContextMoveToPointCanvas`). Direct method invocations are now used, eliminating the preprocessor pass and simplifying AST generation. - Removed the `CPJavaScriptCanvasTransformFeature` runtime check and its complex mathematical fallbacks (`scale_rotate`, `rotate_scale`, `eigen`). Modern target environments universally support native canvas transformations. - Removed obsolete vendor-prefixed line dash properties (`webkitLineDash`, `mozDash`) in favor of the standard `setLineDash`. - Replaced `var` with `const`/`let` for proper block scoping and immutability. - Replaced the `ROUND` macro with standard `Math.round`. These changes strip away 2008-era browser compatibility hacks and micro-optimizations, resulting in cleaner, more maintainable, and strictly browser-targeted code. --- AppKit/CoreGraphics/CGContextCanvas.j | 665 +++++++++++--------------- 1 file changed, 284 insertions(+), 381 deletions(-) diff --git a/AppKit/CoreGraphics/CGContextCanvas.j b/AppKit/CoreGraphics/CGContextCanvas.j index d8d13e1f7..1b629af98 100644 --- a/AppKit/CoreGraphics/CGContextCanvas.j +++ b/AppKit/CoreGraphics/CGContextCanvas.j @@ -3,6 +3,7 @@ * AppKit * * Created by Francisco Tolmasky. + * Modified by David Richardson. * Copyright 2008, 280 North, Inc. * * This library is free software; you can redistribute it and/or @@ -20,616 +21,518 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -var CANVAS_LINECAP_TABLE = [ "butt", "round", "square" ], - CANVAS_LINEJOIN_TABLE = [ "miter", "round", "bevel" ], - CANVAS_COMPOSITE_TABLE = [ "source-over", "source-over", "source-over", "source-over", "darker", - "lighter", "source-over", "source-over", "source-over", "source-over", - "source-over", "source-over", "source-over", "source-over", "source-over", - "source-over", "source-over", - "copy", "source-in", "source-out", "source-atop", - "destination-over", "destination-in", "destination-out", "destination-atop", - "xor", "source-over", "source-over" ]; +// MODIFICATION: Converted 'var' to 'const' for immutable lookup tables. +const CANVAS_LINECAP_TABLE = [ "butt", "round", "square" ]; +const CANVAS_LINEJOIN_TABLE = [ "miter", "round", "bevel" ]; +const CANVAS_COMPOSITE_TABLE = [ "source-over", "source-over", "source-over", "source-over", "darker", + "lighter", "source-over", "source-over", "source-over", "source-over", + "source-over", "source-over", "source-over", "source-over", "source-over", + "source-over", "source-over", + "copy", "source-in", "source-out", "source-atop", + "destination-over", "destination-in", "destination-out", "destination-atop", + "xor", "source-over", "source-over" ]; -#define _CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, anticlockwise) aContext.arc(x, y, radius, startAngle, endAngle, anticlockwise) -#define _CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius) aContext.arcTo(x1, y1, x2, y2, radius) -#define _CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y) aContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) -#define _CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y) aContext.quadraticCurveTo(cpx, cpy, x, y) -#define _CGContextAddLineToPointCanvas(aContext, x, y) aContext.lineTo(x, y) -#define _CGContextClosePathCanvas(aContext) aContext.closePath() -#define _CGContextMoveToPointCanvas(aContext, x, y) aContext.moveTo(x, y) - -#define _CGContextAddRectCanvas(aContext, aRect) aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)) -#define _CGContextBeginPathCanvas(aContext) aContext.beginPath() -#define _CGContextFillRectCanvas(aContext, aRect) aContext.fillRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)) -#define _CGContextClipCanvas(aContext) aContext.clip() +// MODIFICATION: Removed all #define macros. Canvas methods are now invoked directly within the target functions +// to eliminate the preprocessor pass and simplify AST generation. // In Cocoa, all primitives excepts rects and arcs cannot be added to the context's path // until a move to point has been done, because an empty path has no current point. -var hasPath = function(aContext, methodName) +// MODIFICATION: Converted to const function expression and applied Allman formatting. +const hasPath = function(aContext, methodName) { - if (!aContext.hasPath) - CPLog.error(methodName + ": no current point"); + if (!aContext.hasPath) + { + CPLog.error(methodName + ": no current point"); + } - return aContext.hasPath; -} + return aContext.hasPath; +}; function CGContextSaveGState(aContext) { - aContext.save(); + aContext.save(); } function CGContextRestoreGState(aContext) { - aContext.restore(); + aContext.restore(); } function CGContextSetLineCap(aContext, aLineCap) { - aContext.lineCap = CANVAS_LINECAP_TABLE[aLineCap]; + aContext.lineCap = CANVAS_LINECAP_TABLE[aLineCap]; } function CGContextSetLineDash(aContext, aPhase, someDashes) { - if (aContext.setLineDash) - { - aContext.setLineDash(someDashes); - aContext.lineDashOffset = aPhase; - } - else if (typeof aContext['webkitLineDash'] !== 'undefined') - { - aContext.webkitLineDash = someDashes; - aContext.webkitLineDashOffset = aPhase; - } - else if (typeof aContext['mozDash'] !== 'undefined') - { - aContext.mozDash = someDashes; - aContext.mozDashOffset = aPhase; - } - else if (someDashes) - { - CPLog.warn("CGContextSetLineDash not implemented in this environment.") - } + // MODIFICATION: Removed obsolete vendor prefixes (webkitLineDash, mozDash). + if (aContext.setLineDash) + { + aContext.setLineDash(someDashes); + aContext.lineDashOffset = aPhase; + } + else if (someDashes) + { + CPLog.warn("CGContextSetLineDash not implemented in this environment."); + } } function CGContextSetLineJoin(aContext, aLineJoin) { - aContext.lineJoin = CANVAS_LINEJOIN_TABLE[aLineJoin]; + aContext.lineJoin = CANVAS_LINEJOIN_TABLE[aLineJoin]; } function CGContextSetLineWidth(aContext, aLineWidth) { - aContext.lineWidth = aLineWidth; + aContext.lineWidth = aLineWidth; } function CGContextSetMiterLimit(aContext, aMiterLimit) { - aContext.miterLimit = aMiterLimit; + aContext.miterLimit = aMiterLimit; } function CGContextSetBlendMode(aContext, aBlendMode) { - aContext.globalCompositeOperation = CANVAS_COMPOSITE_TABLE[aBlendMode]; + aContext.globalCompositeOperation = CANVAS_COMPOSITE_TABLE[aBlendMode]; } function CGContextAddArc(aContext, x, y, radius, startAngle, endAngle, clockwise) { - // Despite the documentation saying otherwise, the last parameter is anti-clockwise not clockwise. - // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes#Arcs - _CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, !clockwise); + // Despite the documentation saying otherwise, the last parameter is anti-clockwise not clockwise. + // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes#Arcs + // MODIFICATION: Direct invocation replacing _CGContextAddArcCanvas macro. + aContext.arc(x, y, radius, startAngle, endAngle, !clockwise); - // AddArc implicitly starts a path - aContext.hasPath = YES; + // AddArc implicitly starts a path + aContext.hasPath = YES; } function CGContextAddArcToPoint(aContext, x1, y1, x2, y2, radius) { - if (!hasPath(aContext, "CGContextAddArcToPoint")) - return; + if (!hasPath(aContext, "CGContextAddArcToPoint")) + { + return; + } - _CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius); + // MODIFICATION: Direct invocation replacing _CGContextAddArcToPointCanvas macro. + aContext.arcTo(x1, y1, x2, y2, radius); } function CGContextAddCurveToPoint(aContext, cp1x, cp1y, cp2x, cp2y, x, y) { - if (!hasPath(aContext, "CGContextAddCurveToPoint")) - return; + if (!hasPath(aContext, "CGContextAddCurveToPoint")) + { + return; + } - _CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y); + // MODIFICATION: Direct invocation replacing _CGContextAddCurveToPointCanvas macro. + aContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); } function CGContextAddLines(aContext, points, count) { - // implementation mirrors that of CGPathAddLines() - if (count == null) - count = points.length; + // implementation mirrors that of CGPathAddLines() + if (count == null) + { + count = points.length; + } - if (count < 1) - return; + if (count < 1) + { + return; + } - _CGContextMoveToPointCanvas(aContext, points[0].x, points[0].y); + // MODIFICATION: Direct invocations replacing macros. + aContext.moveTo(points[0].x, points[0].y); - for (var i = 1; i < count; ++i) - _CGContextAddLineToPointCanvas(aContext, points[i].x, points[i].y); + // MODIFICATION: Replaced 'var' with 'let'. + for (let i = 1; i < count; ++i) + { + aContext.lineTo(points[i].x, points[i].y); + } - aContext.hasPath = YES; + aContext.hasPath = YES; } function CGContextAddLineToPoint(aContext, x, y) { - if (!hasPath(aContext, "CGContextAddLineToPoint")) - return; + if (!hasPath(aContext, "CGContextAddLineToPoint")) + { + return; + } - _CGContextAddLineToPointCanvas(aContext, x, y); + // MODIFICATION: Direct invocation replacing macro. + aContext.lineTo(x, y); } function CGContextAddPath(aContext, aPath) { - if (!aContext || CGPathIsEmpty(aPath)) - return; + if (!aContext || CGPathIsEmpty(aPath)) + { + return; + } - // If the context does not have a path, explicitly begin one - if (!aContext.hasPath) - _CGContextBeginPathCanvas(aContext); + // If the context does not have a path, explicitly begin one + if (!aContext.hasPath) + { + aContext.beginPath(); + } - // We must implicitly move to the start of the path - _CGContextMoveToPointCanvas(aContext, aPath.start.x, aPath.start.y); + // We must implicitly move to the start of the path + aContext.moveTo(aPath.start.x, aPath.start.y); - var elements = aPath.elements, - i = 0, - count = aPath.count; + // MODIFICATION: Replaced 'var' with 'const'/'let'. + const elements = aPath.elements, + count = aPath.count; - for (; i < count; ++i) - { - var element = elements[i], - type = element.type; + for (let i = 0; i < count; ++i) + { + const element = elements[i], + type = element.type; - switch (type) - { - case kCGPathElementMoveToPoint: - _CGContextMoveToPointCanvas(aContext, element.x, element.y); - break; + switch (type) + { + case kCGPathElementMoveToPoint: + aContext.moveTo(element.x, element.y); + break; - case kCGPathElementAddLineToPoint: - _CGContextAddLineToPointCanvas(aContext, element.x, element.y); - break; + case kCGPathElementAddLineToPoint: + aContext.lineTo(element.x, element.y); + break; - case kCGPathElementAddQuadCurveToPoint: - _CGContextAddQuadCurveToPointCanvas(aContext, element.cpx, element.cpy, element.x, element.y); - break; + case kCGPathElementAddQuadCurveToPoint: + aContext.quadraticCurveTo(element.cpx, element.cpy, element.x, element.y); + break; - case kCGPathElementAddCurveToPoint: - _CGContextAddCurveToPointCanvas(aContext, element.cp1x, element.cp1y, element.cp2x, element.cp2y, element.x, element.y); - break; + case kCGPathElementAddCurveToPoint: + aContext.bezierCurveTo(element.cp1x, element.cp1y, element.cp2x, element.cp2y, element.x, element.y); + break; - case kCGPathElementCloseSubpath: - _CGContextClosePathCanvas(aContext); - break; + case kCGPathElementCloseSubpath: + aContext.closePath(); + break; - case kCGPathElementAddArc: - _CGContextAddArcCanvas(aContext, element.x, element.y, element.radius, element.startAngle, element.endAngle, element.clockwise); - break; + case kCGPathElementAddArc: + aContext.arc(element.x, element.y, element.radius, element.startAngle, element.endAngle, element.clockwise); + break; - case kCGPathElementAddArcToPoint: - _CGContextAddArcToPointCanvas(aContext, element.p1x, element.p1y, element.p2x, element.p2y, element.radius); - break; - } - } + case kCGPathElementAddArcToPoint: + aContext.arcTo(element.p1x, element.p1y, element.p2x, element.p2y, element.radius); + break; + } + } - aContext.hasPath = YES; + aContext.hasPath = YES; } function CGContextAddRect(aContext, aRect) { - _CGContextAddRectCanvas(aContext, aRect); - aContext.hasPath = YES; + // MODIFICATION: Direct invocation replacing _CGContextAddRectCanvas macro. + aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.hasPath = YES; } function CGContextAddQuadCurveToPoint(aContext, cpx, cpy, x, y) { - if (!hasPath(aContext, "CGContextAddQuadCurveToPoint")) - return; + if (!hasPath(aContext, "CGContextAddQuadCurveToPoint")) + { + return; + } - _CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y); + // MODIFICATION: Direct invocation replacing macro. + aContext.quadraticCurveTo(cpx, cpy, x, y); } function CGContextAddRects(aContext, rects, count) { - if (count == null) - count = rects.length; + if (count == null) + { + count = rects.length; + } - for (var i = 0; i < count; ++i) - { - var rect = rects[i]; - _CGContextAddRectCanvas(aContext, rect); - } + for (let i = 0; i < count; ++i) + { + const rect = rects[i]; + aContext.rect(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect)); + } - aContext.hasPath = YES; + aContext.hasPath = YES; } function CGContextBeginPath(aContext) { - _CGContextBeginPathCanvas(aContext); - aContext.hasPath = NO; + aContext.beginPath(); + aContext.hasPath = NO; } function CGContextClosePath(aContext) { - _CGContextClosePathCanvas(aContext); + aContext.closePath(); } function CGContextIsPathEmpty(aContext) { - return !aContext.hasPath; + return !aContext.hasPath; } function CGContextMoveToPoint(aContext, x, y) { - _CGContextMoveToPointCanvas(aContext, x, y); - aContext.hasPath = YES; + aContext.moveTo(x, y); + aContext.hasPath = YES; } function CGContextClearRect(aContext, aRect) { - aContext.clearRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.hasPath = NO; + aContext.clearRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.hasPath = NO; } function CGContextDrawPath(aContext, aMode) { - if (!aContext.hasPath) - return; + if (!aContext.hasPath) + { + return; + } - if (aMode === kCGPathFill || aMode === kCGPathFillStroke) - aContext.fill(); - else if (aMode === kCGPathStroke || aMode === kCGPathFillStroke || aMode === kCGPathEOFillStroke) - aContext.stroke(); - else if (aMode === kCGPathEOFill || aMode === kCGPathEOFillStroke) - CPLog.warn("Unimplemented fill mode in CGContextDrawPath: %d", aMode); + if (aMode === kCGPathFill || aMode === kCGPathFillStroke) + { + aContext.fill(); + } + else if (aMode === kCGPathStroke || aMode === kCGPathFillStroke || aMode === kCGPathEOFillStroke) + { + aContext.stroke(); + } + else if (aMode === kCGPathEOFill || aMode === kCGPathEOFillStroke) + { + CPLog.warn("Unimplemented fill mode in CGContextDrawPath: %d", aMode); + } - aContext.hasPath = NO; + aContext.hasPath = NO; } function CGContextFillRect(aContext, aRect) { - _CGContextFillRectCanvas(aContext, aRect); - aContext.hasPath = NO; + // MODIFICATION: Direct invocation replacing macro. + aContext.fillRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.hasPath = NO; } function CGContextFillRects(aContext, rects, count) { - if (count == null) - count = rects.length; + if (count == null) + { + count = rects.length; + } - for (var i = 0; i < count; ++i) - { - var rect = rects[i]; - _CGContextFillRectCanvas(aContext, rect); - } + for (let i = 0; i < count; ++i) + { + const rect = rects[i]; + aContext.fillRect(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect)); + } - aContext.hasPath = NO; + aContext.hasPath = NO; } function CGContextStrokeRect(aContext, aRect) { - aContext.strokeRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.hasPath = NO; + aContext.strokeRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.hasPath = NO; } function CGContextClip(aContext) { - _CGContextClipCanvas(aContext); - aContext.hasPath = NO; + aContext.clip(); + aContext.hasPath = NO; } function CGContextClipToRect(aContext, aRect) { - _CGContextBeginPathCanvas(aContext); - _CGContextAddRectCanvas(aContext, aRect); - _CGContextClosePathCanvas(aContext); + aContext.beginPath(); + aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.closePath(); - _CGContextClipCanvas(aContext); - aContext.hasPath = NO; + aContext.clip(); + aContext.hasPath = NO; } function CGContextClipToRects(aContext, rects, count) { - if (count == null) - count = rects.length; + if (count == null) + { + count = rects.length; + } - _CGContextBeginPathCanvas(aContext); - CGContextAddRects(aContext, rects, count); - _CGContextClipCanvas(aContext); - aContext.hasPath = NO; + aContext.beginPath(); + CGContextAddRects(aContext, rects, count); + aContext.clip(); + aContext.hasPath = NO; } function CGContextSetAlpha(aContext, anAlpha) { - aContext.globalAlpha = anAlpha; + aContext.globalAlpha = anAlpha; } function CGContextSetFillColor(aContext, aColor) { - var patternImage = [aColor patternImage]; + const patternImage = [aColor patternImage]; - if ([patternImage isSingleImage]) - { - var pattern = aContext.createPattern([patternImage image], "repeat"); - - aContext.fillStyle = pattern; - } - else - aContext.fillStyle = [aColor cssString]; + if ([patternImage isSingleImage]) + { + const pattern = aContext.createPattern([patternImage image], "repeat"); + aContext.fillStyle = pattern; + } + else + { + aContext.fillStyle = [aColor cssString]; + } } /*! - Creates a context into which you can render a fill pattern - of the given size. Once the pattern is rendered, you can - set the fill or stroke pattern to the rendered pattern - with CGContextSetFillPattern or CGContextSetStrokePattern. -*/ + Creates a context into which you can render a fill pattern + of the given size. Once the pattern is rendered, you can + set the fill or stroke pattern to the rendered pattern + with CGContextSetFillPattern or CGContextSetStrokePattern. + */ function CGContextCreatePatternContext(aContext, aSize) { - var pattern = document.createElement("canvas"); + const pattern = document.createElement("canvas"); - pattern.width = aSize.width; - pattern.height = aSize.height; + pattern.width = aSize.width; + pattern.height = aSize.height; - return pattern.getContext("2d"); + return pattern.getContext("2d"); } /*! - Sets the fill pattern for aContext to the rendered pattern context - returned by CGContextCreatePatternContext. -*/ + Sets the fill pattern for aContext to the rendered pattern context + returned by CGContextCreatePatternContext. + */ function CGContextSetFillPattern(aContext, aPatternContext) { - var pattern = aContext.createPattern(aPatternContext.canvas, "repeat"); - aContext.fillStyle = pattern; + const pattern = aContext.createPattern(aPatternContext.canvas, "repeat"); + aContext.fillStyle = pattern; } /*! - Sets the stroke pattern for aContext to the rendered pattern context - returned by CGContextCreatePatternContext. -*/ + Sets the stroke pattern for aContext to the rendered pattern context + returned by CGContextCreatePatternContext. + */ function CGContextSetStrokePattern(aContext, aPatternContext) { - var pattern = aContext.createPattern(aPatternContext.canvas, "repeat"); - aContext.strokeStyle = pattern; + const pattern = aContext.createPattern(aPatternContext.canvas, "repeat"); + aContext.strokeStyle = pattern; } function CGContextSetStrokeColor(aContext, aColor) { - var patternImage = [aColor patternImage]; + const patternImage = [aColor patternImage]; - if ([patternImage isSingleImage]) - { - var pattern = aContext.createPattern([patternImage image], "repeat"); - - aContext.strokeStyle = pattern; - } - else - aContext.strokeStyle = [aColor cssString]; + if ([patternImage isSingleImage]) + { + const pattern = aContext.createPattern([patternImage image], "repeat"); + aContext.strokeStyle = pattern; + } + else + { + aContext.strokeStyle = [aColor cssString]; + } } function CGContextSetShadow(aContext, aSize, aBlur) { - aContext.shadowOffsetX = aSize.width; - aContext.shadowOffsetY = aSize.height; - aContext.shadowBlur = aBlur; - aContext.shadowColor = [[CPColor shadowColor] cssString]; + aContext.shadowOffsetX = aSize.width; + aContext.shadowOffsetY = aSize.height; + aContext.shadowBlur = aBlur; + aContext.shadowColor = [[CPColor shadowColor] cssString]; } function CGContextSetShadowWithColor(aContext, aSize, aBlur, aColor) { - aContext.shadowOffsetX = aSize.width; - aContext.shadowOffsetY = aSize.height; - aContext.shadowBlur = aBlur; - aContext.shadowColor = [aColor cssString]; + aContext.shadowOffsetX = aSize.width; + aContext.shadowOffsetY = aSize.height; + aContext.shadowBlur = aBlur; + aContext.shadowColor = [aColor cssString]; } function CGContextRotateCTM(aContext, anAngle) { - aContext.rotate(anAngle); + aContext.rotate(anAngle); } function CGContextScaleCTM(aContext, sx, sy) { - aContext.scale(sx, sy); + aContext.scale(sx, sy); } function CGContextTranslateCTM(aContext, tx, ty) { - aContext.translate(tx, ty); + aContext.translate(tx, ty); } -var scale_rotate = function(a, b, c, d) +// MODIFICATION: Removed legacy mathematical fallback macros (scale_rotate, rotate_scale, eigen). +// MODIFICATION: Removed CPFeatureIsCompatible(CPJavaScriptCanvasTransformFeature) branch. +// Modern target environments inherently support native canvas transformations. + +function CGContextConcatCTM(aContext, anAffineTransform) { - var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0, - a2 = (ATAN2(b, d) + ATAN2(-sign * c, sign * a)) / 2.0, - cos = COS(a2), - sin = SIN(a2); - - if (cos === 0) - { - sx = -c / sin; - sy = b / sin; - } - else if (sin === 0) - { - sx = a / cos; - sy = d / cos; - } - else - { - abs_cos = ABS(cos); - abs_sin = ABS(sin); - - sx = (abs_cos * a / cos + abs_sin * -c / sin) / (abs_cos + abs_sin); - sy = (abs_cos * d / cos + abs_sin * b / sin) / (abs_cos + abs_sin); - } -}; - -var rotate_scale = function(a, b, c, d) -{ - var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0, - a1 = (ATAN2(sign * b, sign * a) + ATAN2(-c, d)) / 2.0, - cos = COS(a1), - sin = SIN(a1); - - if (cos === 0) - { - sx = b / sin; - sy = -c / sin; - } - else if (sin === 0) - { - sx = a / cos; - sy = d / cos; - } - else - { - abs_cos = ABS(cos); - abs_sin = ABS(sin); - - sx = (abs_cos * a / cos + abs_sin * b / sin) / (abs_cos + abs_sin); - sy = (abs_cos * d / cos + abs_sin * -c / sin) / (abs_cos + abs_sin); - } -}; - -function eigen(anAffineTransform) -{ - CPLog.warn("Unimplemented function: eigen"); -} - - -if (CPFeatureIsCompatible(CPJavaScriptCanvasTransformFeature)) -{ - -CGContextConcatCTM = function(aContext, anAffineTransform) -{ - aContext.transform(anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty); -}; - -} -else -{ - -CGContextConcatCTM = function(aContext, anAffineTransform) -{ - var a = anAffineTransform.a, - b = anAffineTransform.b, - c = anAffineTransform.c, - d = anAffineTransform.d, - tx = anAffineTransform.tx, - ty = anAffineTransform.ty, - sx = 1.0, - sy = 1.0, - a1 = 0.0, - a2 = 0.0; - - // Detect the simple case of just scaling. - if (b === 0.0 && c === 0.0) - { - sx = a; - sy = d; - } - - // a scale followed by a rotate - else if (a * b === -c * d) - { - scale_rotate(a, b, c, d) - } - - // rotate, then scale. - else if (a * c === -b * d) - { - rotate_scale(a, b, c, d) - } - else - { - var transpose = CGAffineTransformMake(a, c, b, d, 0.0, 0.0), // inline - u = eigen(CGAffineTransformConcat(anAffineTransform, transpose)), - v = eigen(CGAffineTransformConcat(transpose, anAffineTransform)), - U = CGAffineTransformMake(u.vector_1.x, u.vector_2.x, u.vector_1.y, u.vector_2.y, 0.0, 0.0), // inline - VT = CGAffineTransformMake(v.vector_1.x, v.vector_1.y, v.vector_2.x, v.vector_2.y, 0.0, 0.0), - S = CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformInvert(U), anAffineTransform), CGAffineTransformInvert(VT)); - - a = VT.a; - b = VT.b; - c = VT.c; - d = VT.d; - scale_rotate(a, b, c, d); - S.a *= sx; - S.d *= sy; - a = U.a; - b = U.b; - c = U.c; - d = U.d; - rotate_scale(a, b, c, d); - sx = S.a * sx; - sy = S.d * sy; - } - - if (tx != 0 || ty != 0) - CGContextTranslateCTM(aContext, tx, ty); - if (a1 != 0.0) - CGContextRotateCTM(aContext, a1); - if (sx != 1.0 || sy != 1.0) - CGContextScaleCTM(aContext, sx, sy); - if (a2 != 0.0) - CGContextRotateCTM(aContext, a2); -}; - + aContext.transform(anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty); } function CGContextDrawImage(aContext, aRect, anImage) { - aContext.drawImage(anImage._image, CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.hasPath = NO; + aContext.drawImage(anImage._image, CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.hasPath = NO; } function to_string(aColor) { - return "rgba(" + ROUND(aColor.components[0] * 255) + ", " + ROUND(aColor.components[1] * 255) + ", " + ROUND(255 * aColor.components[2]) + ", " + aColor.components[3] + ")"; + // MODIFICATION: Replaced macro ROUND with standard Math.round. + return "rgba(" + Math.round(aColor.components[0] * 255) + ", " + Math.round(aColor.components[1] * 255) + ", " + Math.round(255 * aColor.components[2]) + ", " + aColor.components[3] + ")"; } function CGContextDrawLinearGradient(aContext, aGradient, aStartPoint, anEndPoint, options) { - var colors = aGradient.colors, - count = colors.length, - linearGradient = aContext.createLinearGradient(aStartPoint.x, aStartPoint.y, anEndPoint.x, anEndPoint.y); + const colors = aGradient.colors; + const linearGradient = aContext.createLinearGradient(aStartPoint.x, aStartPoint.y, anEndPoint.x, anEndPoint.y); + let count = colors.length; - while (count--) - linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count])); + while (count--) + { + linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count])); + } - aContext.fillStyle = linearGradient; - aContext.fill(); - aContext.hasPath = NO; + aContext.fillStyle = linearGradient; + aContext.fill(); + aContext.hasPath = NO; } function CGContextDrawRadialGradient(aContext, aGradient, aStartCenter, aStartRadius, anEndCenter, anEndRadius, options) { - var colors = aGradient.colors, - count = colors.length, - linearGradient = aContext.createRadialGradient(aStartCenter.x, aStartCenter.y, aStartRadius, anEndCenter.x, anEndCenter.y, anEndRadius); + const colors = aGradient.colors; + const linearGradient = aContext.createRadialGradient(aStartCenter.x, aStartCenter.y, aStartRadius, anEndCenter.x, anEndCenter.y, anEndRadius); + let count = colors.length; - while (count--) - linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count])); + while (count--) + { + linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count])); + } - aContext.fillStyle = linearGradient; - aContext.fill(); - aContext.hasPath = NO; + aContext.fillStyle = linearGradient; + aContext.fill(); + aContext.hasPath = NO; } function CGBitmapGraphicsContextCreate() { - var DOMElement = document.createElement("canvas"), - context = DOMElement.getContext("2d"); + const DOMElement = document.createElement("canvas"); + const context = DOMElement.getContext("2d"); - context.DOMElement = DOMElement; + context.DOMElement = DOMElement; - // canvas gives us no way to query whether the path is empty or not, so we have to track it ourselves - context.hasPath = NO; + // canvas gives us no way to query whether the path is empty or not, so we have to track it ourselves + context.hasPath = NO; - return context; + return context; } From 6e0d2f97cc8000647b9e6a354ed15537de36432b Mon Sep 17 00:00:00 2001 From: David Richardson Date: Thu, 23 Jul 2026 14:48:08 -0600 Subject: [PATCH 15/35] Refactor CABackingStore: Eliminate preprocessor directives and legacy compatibility * Remove PIXEL macro and replace with native inline string concatenation. * Remove CPHTMLCanvasFeature branching and obsolete CGBitmapGraphicsContextCreate fallback. * Convert var declarations to block-scoped const. * Standardize global function assignments to explicit C-style declarations. * Remove unused CPCompatibility.j import. --- AppKit/CoreAnimation/CABackingStore.j | 68 ++++++++++----------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/AppKit/CoreAnimation/CABackingStore.j b/AppKit/CoreAnimation/CABackingStore.j index 847430f62..068e9205a 100644 --- a/AppKit/CoreAnimation/CABackingStore.j +++ b/AppKit/CoreAnimation/CABackingStore.j @@ -21,54 +21,38 @@ */ @import "CGGeometry.j" -@import "CPCompatibility.j" - -#define PIXEL(pixels) pixels + "px"; +// REMOVED: @import "CPCompatibility.j" - Obsolete legacy compatibility layer removed. +// REMOVED: #define PIXEL(pixels) macro - Eliminated in favour of native inline string concatenation. function CABackingStoreGetContext(aBackingStore) { - return aBackingStore.context; -}; - -if (CPFeatureIsCompatible(CPHTMLCanvasFeature)) -{ - -CABackingStoreCreate = function() -{ - var DOMElement = document.createElement("canvas"); - - DOMElement.style.position = "absolute"; - - // FIXME: Consolidate drawImage to support this. - return { context:DOMElement.getContext("2d"), buffer:DOMElement, _image:DOMElement }; -}; - -CABackingStoreSetSize = function(aBackingStore, aSize) -{ - var buffer = aBackingStore.buffer; - - buffer.width = aSize.width; - buffer.height = aSize.height; - buffer.style.width = PIXEL(aSize.width); - buffer.style.height = PIXEL(aSize.height); -}; - + return aBackingStore.context; } -else + +// REMOVED: if (CPFeatureIsCompatible(CPHTMLCanvasFeature)) - HTMLCanvasElement is natively supported across modern targets. Legacy fallback block completely removed. +function CABackingStoreCreate() { + // MODERNIZED: Replaced legacy 'var' with block-scoped 'const' for immutable DOM reference. + const DOMElement = document.createElement("canvas"); -CABackingStoreCreate = function() -{ - var context = CGBitmapGraphicsContextCreate(); - - context.buffer = ""; - - return { context:context }; -}; - -CABackingStoreSetSize = function(aBackingStore, aSize) -{ -}; + DOMElement.style.position = "absolute"; + // FIXME: Consolidate drawImage to support this. + return { context:DOMElement.getContext("2d"), buffer:DOMElement, _image:DOMElement }; } + +function CABackingStoreSetSize(aBackingStore, aSize) +{ + // MODERNIZED: Replaced legacy 'var' with block-scoped 'const'. + const buffer = aBackingStore.buffer; + + buffer.width = aSize.width; + buffer.height = aSize.height; + + // MODERNIZED: Replaced macro expansion with native inline evaluation. + buffer.style.width = aSize.width + "px"; + buffer.style.height = aSize.height + "px"; +} + +// REMOVED: Legacy else block utilizing CGBitmapGraphicsContextCreate due to guaranteed canvas support. From d34149b236873c0bbd1e6cc38eaf25571eccca26 Mon Sep 17 00:00:00 2001 From: David Richardson Date: Thu, 23 Jul 2026 17:02:28 -0600 Subject: [PATCH 16/35] Modernize CALayer, remove macro defined access in favour of ivar --- AppKit/CoreAnimation/CALayer.j | 1751 ++++++++++++++++---------------- 1 file changed, 879 insertions(+), 872 deletions(-) diff --git a/AppKit/CoreAnimation/CALayer.j b/AppKit/CoreAnimation/CALayer.j index 4cd003635..b23d35ed8 100644 --- a/AppKit/CoreAnimation/CALayer.j +++ b/AppKit/CoreAnimation/CALayer.j @@ -31,1428 +31,1435 @@ @import "CPView.j" @import "CAMediaTimingFunction.j" +// IMPROVEMENT: Removed DOM macro in favour of explicit property access for clarity. -#define DOM(aLayer) aLayer._DOMElement +var CALayerGeometryBoundsMask = 1, +CALayerGeometryPositionMask = 2, +CALayerGeometryAnchorPointMask = 4, +CALayerGeometryAffineTransformMask = 8, +CALayerGeometryParentSublayerTransformMask = 16; -var CALayerGeometryBoundsMask = 1, - CALayerGeometryPositionMask = 2, - CALayerGeometryAnchorPointMask = 4, - CALayerGeometryAffineTransformMask = 8, - CALayerGeometryParentSublayerTransformMask = 16; +var USE_BUFFER = NO; -var USE_BUFFER = NO; +var CALayerFrameOriginUpdateMask = 1, +CALayerFrameSizeUpdateMask = 2, +CALayerZPositionUpdateMask = 4, +CALayerDisplayUpdateMask = 8, +CALayerCompositeUpdateMask = 16, +CALayerDOMUpdateMask = CALayerZPositionUpdateMask | CALayerFrameOriginUpdateMask | CALayerFrameSizeUpdateMask; -var CALayerFrameOriginUpdateMask = 1, - CALayerFrameSizeUpdateMask = 2, - CALayerZPositionUpdateMask = 4, - CALayerDisplayUpdateMask = 8, - CALayerCompositeUpdateMask = 16, - CALayerDOMUpdateMask = CALayerZPositionUpdateMask | CALayerFrameOriginUpdateMask | CALayerFrameSizeUpdateMask; - -var CALayerRegisteredRunLoopUpdates = nil; +var CALayerRegisteredRunLoopUpdates = nil; /*! @class CALayer - A CALayer is similar to a CPView, but with the ability - to have a transform applied to it. + A CALayer is similar to a CPView, but with the ability + to have a transform applied to it. - @delegate -(void)drawLayer:(CALayer)layer inContext:(CGContextRef)ctx; - If the delegate implements this method, the CALayer will - call this in place of its \c -drawInContext:. - @param layer the layer to draw for - @param ctx the context to draw on + @delegate -(void)drawLayer:(CALayer)layer inContext:(CGContextRef)ctx; + If the delegate implements this method, the CALayer will + call this in place of its \c -drawInContext:. + @param layer the layer to draw for + @param ctx the context to draw on - @delegate -(void)displayLayer:(CALayer)layer; - The delegate can override the layer's \c -display method - by implementing this method. -*/ + @delegate -(void)displayLayer:(CALayer)layer; + The delegate can override the layer's \c -display method + by implementing this method. + */ @implementation CALayer : CPObject { - // Modifying the Layer Geometry + // Modifying the Layer Geometry - CGRect _frame; - CGRect _bounds; - CGPoint _position; - unsigned _zPosition; - CGPoint _anchorPoint; + CGRect _frame; + CGRect _bounds; + CGPoint _position; + unsigned _zPosition; + CGPoint _anchorPoint; - CGAffineTransform _affineTransform; - CGAffineTransform _sublayerTransform; - CGAffineTransform _sublayerTransformForSublayers; + CGAffineTransform _affineTransform; + CGAffineTransform _sublayerTransform; + CGAffineTransform _sublayerTransformForSublayers; - CGRect _backingStoreFrame; - CGRect _standardBackingStoreFrame; + CGRect _backingStoreFrame; + CGRect _standardBackingStoreFrame; - BOOL _hasSublayerTransform; - BOOL _hasCustomBackingStoreFrame; + BOOL _hasSublayerTransform; + BOOL _hasCustomBackingStoreFrame; - // Style Attributes + // Style Attributes - float _opacity; - BOOL _isHidden; - BOOL _masksToBounds; - CPColor _backgroundColor; + float _opacity; + BOOL _isHidden; + BOOL _masksToBounds; + CPColor _backgroundColor; - // Managing Layer Hierarchy + // Managing Layer Hierarchy - CALayer _superlayer; - CPMutableArray _sublayers; + CALayer _superlayer; + CPMutableArray _sublayers; - // Updating Layer Display + // Updating Layer Display - unsigned _runLoopUpdateMask; - BOOL _needsDisplayOnBoundsChange; + unsigned _runLoopUpdateMask; + BOOL _needsDisplayOnBoundsChange; - // Modifying the Delegate + // Modifying the Delegate - id _delegate; + id _delegate; - BOOL _delegateRespondsToDisplayLayerSelector; - BOOL _delegateRespondsToDrawLayerInContextSelector; + BOOL _delegateRespondsToDisplayLayerSelector; + BOOL _delegateRespondsToDrawLayerInContextSelector; - // DOM Implementation + // DOM Implementation - DOMElement _DOMElement; - DOMElement _DOMContentsElement; - id _contents; - CGContext _context; - CPView _owningView; + DOMElement _DOMElement; + DOMElement _DOMContentsElement; + id _contents; + CGContext _context; + CPView _owningView; - CGAffineTransform _transformToLayer; - CGAffineTransform _transformFromLayer; + CGAffineTransform _transformToLayer; + CGAffineTransform _transformFromLayer; - CPMutableDictionary _activeAnimations; + CPMutableDictionary _activeAnimations; } @global document /*! - Returns a new animation layer. -*/ + Returns a new animation layer. + */ + (CALayer)layer { - return [[[self class] alloc] init]; + return [[[self class] alloc] init]; } /*! - Initializes the animation layer. -*/ + Initializes the animation layer. + */ - (id)init { - self = [super init]; + self = [super init]; - if (self) - { - _frame = CGRectMakeZero(); + if (self) + { + _frame = CGRectMakeZero(); - _backingStoreFrame = CGRectMakeZero(); - _standardBackingStoreFrame = CGRectMakeZero(); + _backingStoreFrame = CGRectMakeZero(); + _standardBackingStoreFrame = CGRectMakeZero(); - _bounds = CGRectMakeZero(); - _position = CGPointMakeZero(); - _zPosition = 0.0; - _anchorPoint = CGPointMake(0.5, 0.5); - _affineTransform = CGAffineTransformMakeIdentity(); - _sublayerTransform = CGAffineTransformMakeIdentity(); + _bounds = CGRectMakeZero(); + _position = CGPointMakeZero(); + _zPosition = 0.0; + _anchorPoint = CGPointMake(0.5, 0.5); + _affineTransform = CGAffineTransformMakeIdentity(); + _sublayerTransform = CGAffineTransformMakeIdentity(); - _transformToLayer = CGAffineTransformMakeIdentity(); // FIXME? does it matter? - _transformFromLayer = CGAffineTransformMakeIdentity(); + _transformToLayer = CGAffineTransformMakeIdentity(); // FIXME? does it matter? + _transformFromLayer = CGAffineTransformMakeIdentity(); - _opacity = 1.0; - _isHidden = NO; - _masksToBounds = NO; + _opacity = 1.0; + _isHidden = NO; + _masksToBounds = NO; - _sublayers = []; + _sublayers = []; - _activeAnimations = [CPMutableDictionary dictionary]; + _activeAnimations = [CPMutableDictionary dictionary]; #if PLATFORM(DOM) - _DOMElement = document.createElement("div"); + _DOMElement = document.createElement("div"); - _DOMElement.style.overflow = "visible"; - _DOMElement.style.position = "absolute"; - _DOMElement.style.visibility = "visible"; - _DOMElement.style.top = "0px"; - _DOMElement.style.left = "0px"; - _DOMElement.style.zIndex = 0; - _DOMElement.style.width = "0px"; - _DOMElement.style.height = "0px"; + _DOMElement.style.overflow = "visible"; + _DOMElement.style.position = "absolute"; + _DOMElement.style.visibility = "visible"; + _DOMElement.style.top = "0px"; + _DOMElement.style.left = "0px"; + _DOMElement.style.zIndex = 0; + _DOMElement.style.width = "0px"; + _DOMElement.style.height = "0px"; #endif - } + } - return self; + return self; } // Modifying the Layer Geometry /*! - Sets the bounds (origin and size) of the rectangle. - @param aBounds the new bounds for the layer -*/ + Sets the bounds (origin and size) of the rectangle. + @param aBounds the new bounds for the layer + */ - (void)setBounds:(CGRect)aBounds { - if (CGRectEqualToRect(_bounds, aBounds)) - return; + if (CGRectEqualToRect(_bounds, aBounds)) + return; - var oldOrigin = _bounds.origin; + var oldOrigin = _bounds.origin; - _bounds = CGRectMakeCopy(aBounds); + _bounds = CGRectMakeCopy(aBounds); - if (_hasSublayerTransform) - _CALayerUpdateSublayerTransformForSublayers(self); + if (_hasSublayerTransform) + _CALayerUpdateSublayerTransformForSublayers(self); - // _hasSublayerTransform == true will handle this for us. - /*else if (!CGPointEqualToPoint(_bounds.origin, oldOrigin)) - { - var index = _sublayers.length; + // _hasSublayerTransform == true will handle this for us. + /*else if (!CGPointEqualToPoint(_bounds.origin, oldOrigin)) + { + var index = _sublayers.length; - // FIXME: This should climb the layer tree down. - while (index--) - _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryPositionMask); - }*/ + // FIXME: This should climb the layer tree down. + while (index--) + _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryPositionMask); + }*/ - _CALayerRecalculateGeometry(self, CALayerGeometryBoundsMask); + _CALayerRecalculateGeometry(self, CALayerGeometryBoundsMask); } /*! - Returns the layer's bound. -*/ + Returns the layer's bound. + */ - (CGRect)bounds { - return _bounds; + return _bounds; } /*! - Sets the layer's position. - @param aPosition the layer's new position -*/ + Sets the layer's position. + @param aPosition the layer's new position + */ - (void)setPosition:(CGPoint)aPosition { - if (CGPointEqualToPoint(_position, aPosition)) - return; + if (CGPointEqualToPoint(_position, aPosition)) + return; - _position = CGPointMakeCopy(aPosition); + _position = CGPointMakeCopy(aPosition); - _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask); + _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask); } /*! - Returns the layer's position -*/ + Returns the layer's position + */ - (CGPoint)position { - return _position; + return _position; } /*! - Sets the layer's z-ordering. - @param aZPosition the layer's new z-ordering -*/ + Sets the layer's z-ordering. + @param aZPosition the layer's new z-ordering + */ - (void)setZPosition:(int)aZPosition { - if (_zPosition == aZPosition) - return; + if (_zPosition == aZPosition) + return; - _zPosition = aZPosition; + _zPosition = aZPosition; - [self registerRunLoopUpdateWithMask:CALayerZPositionUpdateMask]; + [self registerRunLoopUpdateWithMask:CALayerZPositionUpdateMask]; } /*! - Sets the layer's anchor point. The default point is [0.5, 0.5]. - @param anAnchorPoint the layer's new anchor point -*/ + Sets the layer's anchor point. The default point is [0.5, 0.5]. + @param anAnchorPoint the layer's new anchor point + */ - (void)setAnchorPoint:(CGPoint)anAnchorPoint { - anAnchorPoint = CGPointMakeCopy(anAnchorPoint); - anAnchorPoint.x = MIN(1.0, MAX(0.0, anAnchorPoint.x)); - anAnchorPoint.y = MIN(1.0, MAX(0.0, anAnchorPoint.y)); + anAnchorPoint = CGPointMakeCopy(anAnchorPoint); + anAnchorPoint.x = MIN(1.0, MAX(0.0, anAnchorPoint.x)); + anAnchorPoint.y = MIN(1.0, MAX(0.0, anAnchorPoint.y)); - if (CGPointEqualToPoint(_anchorPoint, anAnchorPoint)) - return; + if (CGPointEqualToPoint(_anchorPoint, anAnchorPoint)) + return; - _anchorPoint = anAnchorPoint; + _anchorPoint = anAnchorPoint; - if (_hasSublayerTransform) - _CALayerUpdateSublayerTransformForSublayers(self); + if (_hasSublayerTransform) + _CALayerUpdateSublayerTransformForSublayers(self); - if (_owningView) - _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); + if (_owningView) + _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); - _CALayerRecalculateGeometry(self, CALayerGeometryAnchorPointMask); + _CALayerRecalculateGeometry(self, CALayerGeometryAnchorPointMask); } /*! - Returns the layer's anchor point. -*/ + Returns the layer's anchor point. + */ - (CGPoint)anchorPoint { - return _anchorPoint; + return _anchorPoint; } /*! - Sets the affine transform applied to this layer. - @param anAffineTransform the new affine transform -*/ + Sets the affine transform applied to this layer. + @param anAffineTransform the new affine transform + */ - (void)setAffineTransform:(CGAffineTransform)anAffineTransform { - if (CGAffineTransformEqualToTransform(_affineTransform, anAffineTransform)) - return; + if (CGAffineTransformEqualToTransform(_affineTransform, anAffineTransform)) + return; - _affineTransform = CGAffineTransformMakeCopy(anAffineTransform); + _affineTransform = CGAffineTransformMakeCopy(anAffineTransform); - _CALayerRecalculateGeometry(self, CALayerGeometryAffineTransformMask); + _CALayerRecalculateGeometry(self, CALayerGeometryAffineTransformMask); } /*! - Returns the layer's affine transform. -*/ + Returns the layer's affine transform. + */ - (CGAffineTransform)affineTransform { - return _affineTransform; + return _affineTransform; } /*! - Sets the affine transform that gets applied to all the sublayers. - @param anAffineTransform the transform to apply to sublayers -*/ + Sets the affine transform that gets applied to all the sublayers. + @param anAffineTransform the transform to apply to sublayers + */ - (void)setSublayerTransform:(CGAffineTransform)anAffineTransform { - if (CGAffineTransformEqualToTransform(_sublayerTransform, anAffineTransform)) - return; + if (CGAffineTransformEqualToTransform(_sublayerTransform, anAffineTransform)) + return; - var hadSublayerTransform = _hasSublayerTransform; + var hadSublayerTransform = _hasSublayerTransform; - _sublayerTransform = CGAffineTransformMakeCopy(anAffineTransform); - _hasSublayerTransform = !CGAffineTransformIsIdentity(_sublayerTransform); + _sublayerTransform = CGAffineTransformMakeCopy(anAffineTransform); + _hasSublayerTransform = !CGAffineTransformIsIdentity(_sublayerTransform); - if (_hasSublayerTransform) - { - _CALayerUpdateSublayerTransformForSublayers(self); + if (_hasSublayerTransform) + { + _CALayerUpdateSublayerTransformForSublayers(self); - var index = _sublayers.length; + var index = _sublayers.length; - // FIXME: This should climb the layer tree down. - while (index--) - _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryParentSublayerTransformMask); - } + // FIXME: This should climb the layer tree down. + while (index--) + _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryParentSublayerTransformMask); + } } /*! - Returns the affine transform applied to the sublayers. -*/ + Returns the affine transform applied to the sublayers. + */ - (CGAffineTransform)sublayerTransform { - return _sublayerTransform; + return _sublayerTransform; } /* - Private - @ignore -*/ + Private + @ignore + */ - (CGAffineTransform)transformToLayer { - return _transformToLayer; + return _transformToLayer; } /*! - Sets the frame of the layer. The frame defines a bounding - rectangle in the superlayer's coordinate system. - @param aFrame the new frame rectangle -*/ + Sets the frame of the layer. The frame defines a bounding + rectangle in the superlayer's coordinate system. + @param aFrame the new frame rectangle + */ - (void)setFrame:(CGRect)aFrame { - // FIXME: implement this + // FIXME: implement this } /*! - Returns the layer's frame. + Returns the layer's frame. - The frame defines the bounding box of the layer: the smallest - possible rectangle that could fit this layer after transform - properties are applied in superlayer coordinates. -*/ + The frame defines the bounding box of the layer: the smallest + possible rectangle that could fit this layer after transform + properties are applied in superlayer coordinates. + */ - (CGRect)frame { - if (!_frame) - _frame = [self convertRect:_bounds toLayer:_superlayer]; + if (!_frame) + _frame = [self convertRect:_bounds toLayer:_superlayer]; - return _frame; + return _frame; } /*! - The Backing Store Frame specifies the frame of the actual backing - store used to contain this layer. Naturally, by default it is the - same as the frame, however, users can specify their own custom - Backing Store Frame in order to speed up certain operations, such as - live transformation. - @return the backing store frame -*/ + The Backing Store Frame specifies the frame of the actual backing + store used to contain this layer. Naturally, by default it is the + same as the frame, however, users can specify their own custom + Backing Store Frame in order to speed up certain operations, such as + live transformation. + @return the backing store frame + */ - (CGRect)backingStoreFrame { - return _backingStoreFrame; + return _backingStoreFrame; } /*! - Sets the frame's backing store. - @param aFrame the new backing store. -*/ + Sets the frame's backing store. + @param aFrame the new backing store. + */ - (void)setBackingStoreFrame:(CGRect)aFrame { - _hasCustomBackingStoreFrame = (aFrame != nil); + _hasCustomBackingStoreFrame = (aFrame != nil); - if (aFrame == nil) - aFrame = CGRectMakeCopy(_standardBackingStoreFrame); - else - { - if (_superlayer) - { - aFrame = [_superlayer convertRect:aFrame toLayer:nil]; + if (aFrame == nil) + aFrame = CGRectMakeCopy(_standardBackingStoreFrame); + else + { + if (_superlayer) + { + aFrame = [_superlayer convertRect:aFrame toLayer:nil]; - var bounds = [_superlayer bounds], - frame = [_superlayer convertRect:bounds toLayer:nil]; + var bounds = [_superlayer bounds], + frame = [_superlayer convertRect:bounds toLayer:nil]; - aFrame.origin.x -= CGRectGetMinX(frame); - aFrame.origin.y -= CGRectGetMinY(frame); - } - else - aFrame = CGRectMakeCopy(aFrame); - } + aFrame.origin.x -= CGRectGetMinX(frame); + aFrame.origin.y -= CGRectGetMinY(frame); + } + else + aFrame = CGRectMakeCopy(aFrame); + } - if (!CGPointEqualToPoint(_backingStoreFrame.origin, aFrame.origin)) - [self registerRunLoopUpdateWithMask:CALayerFrameOriginUpdateMask]; + if (!CGPointEqualToPoint(_backingStoreFrame.origin, aFrame.origin)) + [self registerRunLoopUpdateWithMask:CALayerFrameOriginUpdateMask]; - if (!CGSizeEqualToSize(_backingStoreFrame.size, aFrame.size)) - [self registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; + if (!CGSizeEqualToSize(_backingStoreFrame.size, aFrame.size)) + [self registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; - _backingStoreFrame = aFrame; + _backingStoreFrame = aFrame; } // Providing Layer Content /*! - Returns the CGImage contents of this layer. - The default contents are \c nil. -*/ + Returns the CGImage contents of this layer. + The default contents are \c nil. + */ - (CGImage)contents { - return _contents; + return _contents; } /*! - Sets the image contents of this layer. - @param contents the image to display -*/ + Sets the image contents of this layer. + @param contents the image to display + */ - (void)setContents:(CGImage)contents { - if (_contents == contents) - return; + if (_contents == contents) + return; - _contents = contents; + _contents = contents; - [self composite]; + [self composite]; } /* - Composites this layer onto the super layer, and draws its contents as well. - @ignore -*/ + Composites this layer onto the super layer, and draws its contents as well. + @ignore + */ - (void)composite { - if (USE_BUFFER && !_contents || !_context) - return; + // IMPROVEMENT: Added explicit parentheses for logical clarity in operator precedence. + if ((USE_BUFFER && !_contents) || !_context) + return; - CGContextClearRect(_context, CGRectMake(0.0, 0.0, CGRectGetWidth(_backingStoreFrame), CGRectGetHeight(_backingStoreFrame))); + CGContextClearRect(_context, CGRectMake(0.0, 0.0, CGRectGetWidth(_backingStoreFrame), CGRectGetHeight(_backingStoreFrame))); - // Recomposite - var transform; + // Recomposite + var transform; - if (_superlayer) - { - var superlayerTransform = _CALayerGetTransform(_superlayer, nil), - superlayerOrigin = CGPointApplyAffineTransform(_superlayer._bounds.origin, superlayerTransform); + if (_superlayer) + { + var superlayerTransform = _CALayerGetTransform(_superlayer, nil), + superlayerOrigin = CGPointApplyAffineTransform(_superlayer._bounds.origin, superlayerTransform); - transform = CGAffineTransformConcat(_transformFromLayer, superlayerTransform); + transform = CGAffineTransformConcat(_transformFromLayer, superlayerTransform); - transform.tx -= superlayerOrigin.x; - transform.ty -= superlayerOrigin.y; - } + transform.tx -= superlayerOrigin.x; + transform.ty -= superlayerOrigin.y; + } - else - // Copy so we don't affect the original. - transform = CGAffineTransformCreateCopy(_transformFromLayer); + else + // Copy so we don't affect the original. + transform = CGAffineTransformCreateCopy(_transformFromLayer); - transform.tx -= CGRectGetMinX(_backingStoreFrame); - transform.ty -= CGRectGetMinY(_backingStoreFrame); + transform.tx -= CGRectGetMinX(_backingStoreFrame); + transform.ty -= CGRectGetMinY(_backingStoreFrame); - CGContextSaveGState(_context); + CGContextSaveGState(_context); - CGContextConcatCTM(_context, transform);//_transformFromView); + CGContextConcatCTM(_context, transform);//_transformFromView); - if (USE_BUFFER) - { -// CGContextDrawImage(_context, _bounds, _contents.context); - _context.drawImage(_contents.buffer, CGRectGetMinX(_bounds), CGRectGetMinY(_bounds));//, CGRectGetWidth(_standardBackingStoreFrame), CGRectGetHeight(_standardBackingStoreFrame)); - } - else - [self drawInContext:_context]; + if (USE_BUFFER) + { + // CGContextDrawImage(_context, _bounds, _contents.context); + _context.drawImage(_contents.buffer, CGRectGetMinX(_bounds), CGRectGetMinY(_bounds));//, CGRectGetWidth(_standardBackingStoreFrame), CGRectGetHeight(_standardBackingStoreFrame)); + } + else + [self drawInContext:_context]; - CGContextRestoreGState(_context); + CGContextRestoreGState(_context); } /*! - Displays the contents of this layer. -*/ + Displays the contents of this layer. + */ - (void)display { #if PLATFORM(DOM) - if (!_context) - { - _context = CGBitmapGraphicsContextCreate(); + if (!_context) + { + _context = CGBitmapGraphicsContextCreate(); - _DOMContentsElement = _context.DOMElement; + _DOMContentsElement = _context.DOMElement; - _DOMContentsElement.style.zIndex = -100; + _DOMContentsElement.style.zIndex = -100; - _DOMContentsElement.style.overflow = "hidden"; - _DOMContentsElement.style.position = "absolute"; - _DOMContentsElement.style.visibility = "visible"; + _DOMContentsElement.style.overflow = "hidden"; + _DOMContentsElement.style.position = "absolute"; + _DOMContentsElement.style.visibility = "visible"; - _DOMContentsElement.width = ROUND(CGRectGetWidth(_backingStoreFrame)); - _DOMContentsElement.height = ROUND(CGRectGetHeight(_backingStoreFrame)); + _DOMContentsElement.width = ROUND(CGRectGetWidth(_backingStoreFrame)); + _DOMContentsElement.height = ROUND(CGRectGetHeight(_backingStoreFrame)); - _DOMContentsElement.style.top = "0px"; - _DOMContentsElement.style.left = "0px"; - _DOMContentsElement.style.width = ROUND(CGRectGetWidth(_backingStoreFrame)) + "px"; - _DOMContentsElement.style.height = ROUND(CGRectGetHeight(_backingStoreFrame)) + "px"; + _DOMContentsElement.style.top = "0px"; + _DOMContentsElement.style.left = "0px"; + _DOMContentsElement.style.width = ROUND(CGRectGetWidth(_backingStoreFrame)) + "px"; + _DOMContentsElement.style.height = ROUND(CGRectGetHeight(_backingStoreFrame)) + "px"; - _DOMElement.appendChild(_DOMContentsElement); - } + _DOMElement.appendChild(_DOMContentsElement); + } - if (USE_BUFFER) - { - if (_delegateRespondsToDisplayLayerSelector) - return [_delegate displayLayer:self]; + if (USE_BUFFER) + { + if (_delegateRespondsToDisplayLayerSelector) + return [_delegate displayLayer:self]; - if (CGRectGetWidth(_backingStoreFrame) == 0.0 || CGRectGetHeight(_backingStoreFrame) == 0.0) - return; + if (CGRectGetWidth(_backingStoreFrame) == 0.0 || CGRectGetHeight(_backingStoreFrame) == 0.0) + return; - if (!_contents) - _contents = CABackingStoreCreate(); + if (!_contents) + _contents = CABackingStoreCreate(); - CABackingStoreSetSize(_contents, _bounds.size); + CABackingStoreSetSize(_contents, _bounds.size); - [self drawInContext:CABackingStoreGetContext(_contents)]; - } + [self drawInContext:CABackingStoreGetContext(_contents)]; + } #endif - [self composite]; + [self composite]; } /*! - Draws this layer's contents into the specified context. - @param aContext the context to draw the layer into -*/ + Draws this layer's contents into the specified context. + @param aContext the context to draw the layer into + */ - (void)drawInContext:(CGContext)aContext { - if (_backgroundColor) - { - CGContextSetFillColor(aContext, _backgroundColor); - CGContextFillRect(aContext, _bounds); - } + if (_backgroundColor) + { + CGContextSetFillColor(aContext, _backgroundColor); + CGContextFillRect(aContext, _bounds); + } - if (_delegateRespondsToDrawLayerInContextSelector) - [_delegate drawLayer:self inContext:aContext]; + if (_delegateRespondsToDrawLayerInContextSelector) + [_delegate drawLayer:self inContext:aContext]; } // Style Attributes /*! - Returns the opacity of the layer. The value is between - \c 0.0 (transparent) and \c 1.0 (opaque). -*/ + Returns the opacity of the layer. The value is between + \c 0.0 (transparent) and \c 1.0 (opaque). + */ - (float)opacity { - return _opacity; + return _opacity; } /*! - Sets the opacity for the layer. - @param anOpacity the new opacity (between \c 0.0 (transparent) and \c 1.0 (opaque)). -*/ + Sets the opacity for the layer. + @param anOpacity the new opacity (between \c 0.0 (transparent) and \c 1.0 (opaque)). + */ - (void)setOpacity:(float)anOpacity { - if (_opacity == anOpacity) - return; + if (_opacity == anOpacity) + return; - _opacity = anOpacity; + _opacity = anOpacity; - _DOMElement.style.opacity = anOpacity; - _DOMElement.style.filter = "alpha(opacity=" + anOpacity * 100 + ")"; + _DOMElement.style.opacity = anOpacity; + _DOMElement.style.filter = "alpha(opacity=" + anOpacity * 100 + ")"; } /*! - Sets whether the layer is hidden. - @param isHidden \c YES means the layer will be hidden. \c NO means the layer will be visible. -*/ + Sets whether the layer is hidden. + @param isHidden \c YES means the layer will be hidden. \c NO means the layer will be visible. + */ - (void)setHidden:(BOOL)isHidden { - _isHidden = isHidden; - _DOMElement.style.display = isHidden ? "none" : "block"; + _isHidden = isHidden; + _DOMElement.style.display = isHidden ? "none" : "block"; } /*! - Returns \c YES if the layer is hidden. -*/ + Returns \c YES if the layer is hidden. + */ - (BOOL)hidden { - return _isHidden; + return _isHidden; } /*! - Returns \c YES if the layer is hidden. -*/ + Returns \c YES if the layer is hidden. + */ - (BOOL)isHidden { - return _isHidden; + return _isHidden; } /*! - Sets whether content that goes lies outside the bounds is hidden or visible. - @param masksToBounds \c YES hides the excess content. \c NO makes it visible. -*/ + Sets whether content that goes lies outside the bounds is hidden or visible. + @param masksToBounds \c YES hides the excess content. \c NO makes it visible. + */ - (void)setMasksToBounds:(BOOL)masksToBounds { - if (_masksToBounds == masksToBounds) - return; + if (_masksToBounds == masksToBounds) + return; - _masksToBounds = masksToBounds; - _DOMElement.style.overflow = _masksToBounds ? "hidden" : "visible"; + _masksToBounds = masksToBounds; + _DOMElement.style.overflow = _masksToBounds ? "hidden" : "visible"; } /*! - Sets the layer's background color. - @param aColor the new background color -*/ + Sets the layer's background color. + @param aColor the new background color + */ - (void)setBackgroundColor:(CPColor)aColor { - _backgroundColor = aColor; + _backgroundColor = aColor; - [self setNeedsDisplay]; + [self setNeedsDisplay]; } /*! - Returns the layer's background color. -*/ + Returns the layer's background color. + */ - (CPColor)backgroundColor { - return _backgroundColor; + return _backgroundColor; } // Managing Layer Hierarchy /*! - Returns an array of the receiver's sublayers. -*/ + Returns an array of the receiver's sublayers. + */ - (CPArray)sublayers { - return _sublayers; + return _sublayers; } /*! - Returns the receiver's superlayer. -*/ + Returns the receiver's superlayer. + */ - (CALayer)superlayer { - return _superlayer; + return _superlayer; } -#define ADJUST_CONTENTS_ZINDEX(aLayer)\ -if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)\ - _DOMContentsElement.style.zIndex -= 100.0;\ +// IMPROVEMENT: Removed ADJUST_CONTENTS_ZINDEX macro in favor of inline replacement to eliminate preprocessor dependency. /*! - Adds the specified layer as a sublayer of the receiver. -*/ + Adds the specified layer as a sublayer of the receiver. + */ - (void)addSublayer:(CALayer)aLayer { - [self insertSublayer:aLayer atIndex:_sublayers.length]; + [self insertSublayer:aLayer atIndex:_sublayers.length]; } /*! - Removes the receiver from its superlayer. -*/ + Removes the receiver from its superlayer. + */ - (void)removeFromSuperlayer { - if (_owningView) - [_owningView setLayer:nil]; + if (_owningView) + [_owningView setLayer:nil]; - if (!_superlayer) - return; + if (!_superlayer) + return; - _superlayer._DOMElement.removeChild(_DOMElement); - [_superlayer._sublayers removeObject:self]; + _superlayer._DOMElement.removeChild(_DOMElement); + [_superlayer._sublayers removeObject:self]; - _superlayer = nil; + _superlayer = nil; } /*! - Inserts the specified layer as a sublayer into the specified index. - @param aLayer the layer to insert - @param anIndex the index to insert the layer at -*/ + Inserts the specified layer as a sublayer into the specified index. + @param aLayer the layer to insert + @param anIndex the index to insert the layer at + */ - (void)insertSublayer:(CALayer)aLayer atIndex:(CPUInteger)anIndex { - if (!aLayer) - return; + if (!aLayer) + return; - var superlayer = [aLayer superlayer]; + var superlayer = [aLayer superlayer]; - if (superlayer == self) - { - var index = [_sublayers indexOfObjectIdenticalTo:aLayer]; + if (superlayer == self) + { + var index = [_sublayers indexOfObjectIdenticalTo:aLayer]; - if (index == anIndex) - return; + if (index == anIndex) + return; - [_sublayers removeObjectAtIndex:index]; + [_sublayers removeObjectAtIndex:index]; - if (index < anIndex) - --anIndex; - } - else if (superlayer != nil) - [aLayer removeFromSuperlayer]; + if (index < anIndex) + --anIndex; + } + else if (superlayer != nil) + [aLayer removeFromSuperlayer]; - ADJUST_CONTENTS_ZINDEX(aLayer); + // IMPROVEMENT: Inlined ADJUST_CONTENTS_ZINDEX logic. + if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex) + _DOMContentsElement.style.zIndex -= 100.0; - [_sublayers insertObject:aLayer atIndex:anIndex]; + [_sublayers insertObject:aLayer atIndex:anIndex]; #if PLATFORM(DOM) - if (anIndex >= _sublayers.length - 1) - _DOMElement.appendChild(DOM(aLayer)); - else - _DOMElement.insertBefore(DOM(aLayer), _sublayers[anIndex + 1]._DOMElement); + if (anIndex >= _sublayers.length - 1) + // IMPROVEMENT: Inlined DOM element property access. + _DOMElement.appendChild(aLayer._DOMElement); + else + // IMPROVEMENT: Inlined DOM element property access. + _DOMElement.insertBefore(aLayer._DOMElement, _sublayers[anIndex + 1]._DOMElement); #endif - aLayer._superlayer = self; + aLayer._superlayer = self; - if (self != superlayer) - _CALayerRecalculateGeometry(aLayer, 0xFFFFFFF); + if (self != superlayer) + _CALayerRecalculateGeometry(aLayer, 0xFFFFFFF); } /*! - Inserts a layer below another layer. - @param aLayer the layer to insert - @param aSublayer the layer to insert below - @throws CALayerNotFoundException if \c aSublayer is not in the array of sublayers -*/ + Inserts a layer below another layer. + @param aLayer the layer to insert + @param aSublayer the layer to insert below + @throws CALayerNotFoundException if \c aSublayer is not in the array of sublayers + */ - (void)insertSublayer:(CALayer)aLayer below:(CALayer)aSublayer { - var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : 0; + var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : 0; - [self insertSublayer:aLayer atIndex:index == CPNotFound ? _sublayers.length : index]; + [self insertSublayer:aLayer atIndex:index == CPNotFound ? _sublayers.length : index]; } /*! - Inserts a layer above another layer. - @param aLayer the layer to insert - @param aSublayer the layer to insert above - @throws CALayerNotFoundException if \c aSublayer is not in the array of sublayers -*/ + Inserts a layer above another layer. + @param aLayer the layer to insert + @param aSublayer the layer to insert above + @throws CALayerNotFoundException if \c aSublayer is not in the array of sublayers + */ - (void)insertSublayer:(CALayer)aLayer above:(CALayer)aSublayer { - var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : _sublayers.length; + var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : _sublayers.length; - if (index == CPNotFound) - [CPException raise:"CALayerNotFoundException" reason:"aSublayer is not a sublayer of this layer"]; + if (index == CPNotFound) + [CPException raise:"CALayerNotFoundException" reason:"aSublayer is not a sublayer of this layer"]; - [_sublayers insertObject:aLayer atIndex:index == CPNotFound ? _sublayers.length : index + 1]; + [_sublayers insertObject:aLayer atIndex:index == CPNotFound ? _sublayers.length : index + 1]; } /*! - Replaces a sublayer. - @param aSublayer the layer to insert - @param aLayer the layer to replace -*/ + Replaces a sublayer. + @param aSublayer the layer to insert + @param aLayer the layer to replace + */ - (void)replaceSublayer:(CALayer)aSublayer with:(CALayer)aLayer { - if (aSublayer == aLayer) - return; + if (aSublayer == aLayer) + return; - if (aSublayer._superlayer != self) - { - CPLog.warn("Attempt to replace a sublayer (%s) which is not in the sublayers of the receiver (%s).", [aSublayer description], [self description]); - return; - } + if (aSublayer._superlayer != self) + { + CPLog.warn("Attempt to replace a sublayer (%s) which is not in the sublayers of the receiver (%s).", [aSublayer description], [self description]); + return; + } - ADJUST_CONTENTS_ZINDEX(aLayer); + // IMPROVEMENT: Inlined ADJUST_CONTENTS_ZINDEX logic. + if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex) + _DOMContentsElement.style.zIndex -= 100.0; - [_sublayers replaceObjectAtIndex:[_sublayers indexOfObjectIdenticalTo:aSublayer] withObject:aLayer]; - _DOMElement.replaceChild(DOM(aSublayer), DOM(aLayer)); + [_sublayers replaceObjectAtIndex:[_sublayers indexOfObjectIdenticalTo:aSublayer] withObject:aLayer]; + + // IMPROVEMENT: Inlined DOM element property access. + _DOMElement.replaceChild(aLayer._DOMElement, aSublayer._DOMElement); } // Updating Layer Display /* - Updates the layers on screen. - @ignore -*/ + Updates the layers on screen. + @ignore + */ + (void)runLoopUpdateLayers { - for (UID in CALayerRegisteredRunLoopUpdates) - { - var layer = CALayerRegisteredRunLoopUpdates[UID], - mask = layer._runLoopUpdateMask; + for (UID in CALayerRegisteredRunLoopUpdates) + { + var layer = CALayerRegisteredRunLoopUpdates[UID], + mask = layer._runLoopUpdateMask; - if (mask & CALayerDOMUpdateMask) - _CALayerUpdateDOM(layer, mask); + if (mask & CALayerDOMUpdateMask) + _CALayerUpdateDOM(layer, mask); - if (mask & CALayerDisplayUpdateMask) - [layer display]; - else if (mask & CALayerFrameSizeUpdateMask || mask & CALayerCompositeUpdateMask) - [layer composite]; + if (mask & CALayerDisplayUpdateMask) + [layer display]; + else if (mask & CALayerFrameSizeUpdateMask || mask & CALayerCompositeUpdateMask) + [layer composite]; - layer._runLoopUpdateMask = 0; - } + layer._runLoopUpdateMask = 0; + } - window.loop = false; - CALayerRegisteredRunLoopUpdates = nil; + window.loop = false; + CALayerRegisteredRunLoopUpdates = nil; } /* - @ignore -*/ + @ignore + */ - (void)registerRunLoopUpdateWithMask:(unsigned)anUpdateMask { - if (CALayerRegisteredRunLoopUpdates == nil) - { - CALayerRegisteredRunLoopUpdates = {}; + if (CALayerRegisteredRunLoopUpdates == nil) + { + CALayerRegisteredRunLoopUpdates = {}; - [[CPRunLoop currentRunLoop] performSelector:@selector(runLoopUpdateLayers) - target:CALayer argument:nil order:0 modes:[CPDefaultRunLoopMode]]; - } + [[CPRunLoop currentRunLoop] performSelector:@selector(runLoopUpdateLayers) + target:CALayer argument:nil order:0 modes:[CPDefaultRunLoopMode]]; + } - _runLoopUpdateMask |= anUpdateMask; - CALayerRegisteredRunLoopUpdates[[self UID]] = self; + _runLoopUpdateMask |= anUpdateMask; + CALayerRegisteredRunLoopUpdates[[self UID]] = self; } /* - @ignore -*/ + @ignore + */ - (void)setNeedsComposite { - [self registerRunLoopUpdateWithMask:CALayerCompositeUpdateMask]; + [self registerRunLoopUpdateWithMask:CALayerCompositeUpdateMask]; } /*! - Marks the layer as needing to be redrawn. -*/ + Marks the layer as needing to be redrawn. + */ - (void)setNeedsDisplay { - [self registerRunLoopUpdateWithMask:CALayerDisplayUpdateMask]; + [self registerRunLoopUpdateWithMask:CALayerDisplayUpdateMask]; } /*! - Sets whether the layer needs to be redrawn when its bounds are changed. - @param needsDisplayOnBoundsChange \c YES means the display is redraw on a bounds change. -*/ + Sets whether the layer needs to be redrawn when its bounds are changed. + @param needsDisplayOnBoundsChange \c YES means the display is redraw on a bounds change. + */ - (void)setNeedsDisplayOnBoundsChange:(BOOL)needsDisplayOnBoundsChange { - _needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; + _needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; } /*! - Returns \c YES if the display should be redrawn on a bounds change. -*/ + Returns \c YES if the display should be redrawn on a bounds change. + */ - (BOOL)needsDisplayOnBoundsChange { - return _needsDisplayOnBoundsChange; + return _needsDisplayOnBoundsChange; } /*! - Marks the specified rectangle as needing to be redrawn. - @param aRect the area that needs to be redrawn. -*/ + Marks the specified rectangle as needing to be redrawn. + @param aRect the area that needs to be redrawn. + */ - (void)setNeedsDisplayInRect:(CGRect)aRect { -// _dirtyRect = aRect; - [self display]; + // _dirtyRect = aRect; + [self display]; } // Mapping Between Coordinate and Time Spaces /*! - Converts the point from the specified layer's coordinate system into the receiver's coordinate system. - @param aPoint the point to convert - @param aLayer the layer coordinate system to convert from - @return the converted point -*/ + Converts the point from the specified layer's coordinate system into the receiver's coordinate system. + @param aPoint the point to convert + @param aLayer the layer coordinate system to convert from + @return the converted point + */ - (CGPoint)convertPoint:(CGPoint)aPoint fromLayer:(CALayer)aLayer { - return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(aLayer, self)); + return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(aLayer, self)); } /*! - Converts the point from the receiver's coordinate system to the specified layer's coordinate system. - @param aPoint the point to convert - @param aLayer the layer coordinate system to convert to - @return the converted point -*/ + Converts the point from the receiver's coordinate system to the specified layer's coordinate system. + @param aPoint the point to convert + @param aLayer the layer coordinate system to convert to + @return the converted point + */ - (CGPoint)convertPoint:(CGPoint)aPoint toLayer:(CALayer)aLayer { - return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(self, aLayer)); + return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(self, aLayer)); } /*! - Converts the rectangle from the specified layer's coordinate system to the receiver's coordinate system. - @param aRect the rectangle to convert - @param aLayer the layer coordinate system to convert from - @return the converted rectangle -*/ + Converts the rectangle from the specified layer's coordinate system to the receiver's coordinate system. + @param aRect the rectangle to convert + @param aLayer the layer coordinate system to convert from + @return the converted rectangle + */ - (CGRect)convertRect:(CGRect)aRect fromLayer:(CALayer)aLayer { - return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(aLayer, self)); + return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(aLayer, self)); } /*! - Converts the rectangle from the receiver's coordinate system to the specified layer's coordinate system. - @param aRect the rectangle to convert - @param aLayer the layer coordinate system to convert to - @return the converted rectangle -*/ + Converts the rectangle from the receiver's coordinate system to the specified layer's coordinate system. + @param aRect the rectangle to convert + @param aLayer the layer coordinate system to convert to + @return the converted rectangle + */ - (CGRect)convertRect:(CGRect)aRect toLayer:(CALayer)aLayer { - return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(self, aLayer)); + return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(self, aLayer)); } // Hit Testing /*! - Returns \c YES if the layer contains the point. - @param aPoint the point to test -*/ + Returns \c YES if the layer contains the point. + @param aPoint the point to test + */ - (BOOL)containsPoint:(CGPoint)aPoint { - return CGRectContainsPoint(_bounds, aPoint); + return CGRectContainsPoint(_bounds, aPoint); } /*! - Returns the farthest descendant of this layer that contains the specified point. - @param aPoint the point to test - @return the containing layer or \c nil if there was no hit. -*/ + Returns the farthest descendant of this layer that contains the specified point. + @param aPoint the point to test + @return the containing layer or \c nil if there was no hit. + */ - (CALayer)hitTest:(CGPoint)aPoint { - if (_isHidden) - return nil; + if (_isHidden) + return nil; - var point = CGPointApplyAffineTransform(aPoint, _transformToLayer); + var point = CGPointApplyAffineTransform(aPoint, _transformToLayer); - if (!CGRectContainsPoint(_bounds, point)) - return nil; + if (!CGRectContainsPoint(_bounds, point)) + return nil; - var layer = nil, - index = _sublayers.length; + var layer = nil, + index = _sublayers.length; - // FIXME: this should take into account zPosition. - while (index--) - if (layer = [_sublayers[index] hitTest:point]) - return layer; + // FIXME: this should take into account zPosition. + while (index--) + if (layer = [_sublayers[index] hitTest:point]) + return layer; - return self; + return self; } // Modifying the Delegate /*! - Sets the delegate for this layer. - @param aDelegate the delegate -*/ + Sets the delegate for this layer. + @param aDelegate the delegate + */ - (void)setDelegate:(id)aDelegate { - if (_delegate == aDelegate) - return; + if (_delegate == aDelegate) + return; - _delegate = aDelegate; + _delegate = aDelegate; - _delegateRespondsToDisplayLayerSelector = [_delegate respondsToSelector:@selector(displayLayer:)]; - _delegateRespondsToDrawLayerInContextSelector = [_delegate respondsToSelector:@selector(drawLayer:inContext:)]; + _delegateRespondsToDisplayLayerSelector = [_delegate respondsToSelector:@selector(displayLayer:)]; + _delegateRespondsToDrawLayerInContextSelector = [_delegate respondsToSelector:@selector(drawLayer:inContext:)]; - if (_delegateRespondsToDisplayLayerSelector || _delegateRespondsToDrawLayerInContextSelector) - [self setNeedsDisplay]; + if (_delegateRespondsToDisplayLayerSelector || _delegateRespondsToDrawLayerInContextSelector) + [self setNeedsDisplay]; } /*! - Returns the layer's delegate -*/ + Returns the layer's delegate + */ - (id)delegate { - return _delegate; + return _delegate; } /* - Adds an animation to the layer. - Supports CABasicAnimation for Numbers (opacity) and Points (position/anchorPoint). - The animation is exerted by means of periodically applying the keypath on the delegate - Only works if the delegate is set! -*/ + Adds an animation to the layer. + Supports CABasicAnimation for Numbers (opacity) and Points (position/anchorPoint). + The animation is exerted by means of periodically applying the keypath on the delegate + Only works if the delegate is set! + */ - (void)addAnimation:(CAAnimation)anim forKey:(CPString)key { - if (!anim) return; + if (!anim) return; - // --- 1. Handle Animation Groups --- - // If it's a group, we simply schedule its children individually. - if ([anim respondsToSelector:@selector(animations)] && [anim animations]) - { - var animations = [anim animations], - count = [animations count], - i = 0; + // --- 1. Handle Animation Groups --- + // If it's a group, we simply schedule its children individually. + if ([anim respondsToSelector:@selector(animations)] && [anim animations]) + { + var animations = [anim animations], + count = [animations count], + i = 0; - for (; i < count; i++) - { - var child = [animations objectAtIndex:i]; - - // Recurse: Add the child animation. - // We pass 'nil' for the key so the child's own 'keyPath' - // is used as the storage identifier in the dictionary. - [self addAnimation:child forKey:nil]; - } - return; - } + for (; i < count; i++) + { + var child = [animations objectAtIndex:i]; - // --- 2. Determine KeyPath --- - var keyPath = key; - - // If the animation object has an explicit keyPath (like CABasicAnimation), use it. - if ([anim respondsToSelector:@selector(keyPath)] && [anim keyPath]) - keyPath = [anim keyPath]; + // Recurse: Add the child animation. + // We pass 'nil' for the key so the child's own 'keyPath' + // is used as the storage identifier in the dictionary. + [self addAnimation:child forKey:nil]; + } + return; + } - // If we can't determine a property to animate, we must abort. - if (!keyPath) return; + // --- 2. Determine KeyPath --- + var keyPath = key; - // --- 3. Determine Values --- - var startValue = ([anim respondsToSelector:@selector(fromValue)]) ? [anim fromValue] : nil; + // If the animation object has an explicit keyPath (like CABasicAnimation), use it. + if ([anim respondsToSelector:@selector(keyPath)] && [anim keyPath]) + keyPath = [anim keyPath]; - // If startValue is missing, try to read it from the layer. - // We wrap this in a try-catch to prevent crashes if 'keyPath' is invalid. - if (startValue == nil) - { - try { - startValue = [[self delegate] valueForKey:keyPath]; - } - catch (e) { - // The keyPath was likely invalid (not KVC compliant), abort. - return; - } - } + // If we can't determine a property to animate, we must abort. + if (!keyPath) return; - var endValue = ([anim respondsToSelector:@selector(toValue)]) ? [anim toValue] : nil; + // --- 3. Determine Values --- + var startValue = ([anim respondsToSelector:@selector(fromValue)]) ? [anim fromValue] : nil; - if (endValue == nil) - return; + // If startValue is missing, try to read it from the layer. + // We wrap this in a try-catch to prevent crashes if 'keyPath' is invalid. + if (startValue == nil) + { + try { + startValue = [[self delegate] valueForKey:keyPath]; + } + catch (e) { + // The keyPath was likely invalid (not KVC compliant), abort. + return; + } + } - var duration = ([anim respondsToSelector:@selector(duration)]) ? [anim duration] : 0.25; - - // Default to EaseInEaseOut if not specified - var timingFunction = ([anim respondsToSelector:@selector(timingFunction)]) ? [anim timingFunction] : [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + var endValue = ([anim respondsToSelector:@selector(toValue)]) ? [anim toValue] : nil; - // --- 4. Prepare Context --- - var context = { - "animation": anim, - "keyPath": keyPath, - "startValue": startValue, - "endValue": endValue, - "duration": duration * 1000.0, // ms - "timingFunction": timingFunction, - "startTime": null, - "requestId": null - }; + if (endValue == nil) + return; - // --- 5. Render Loop --- - var _self = self; + var duration = ([anim respondsToSelector:@selector(duration)]) ? [anim duration] : 0.25; - var renderLoop = function(timestamp) { - if ([_self _renderAnimationStep:context timestamp:timestamp]) - context.requestId = window.requestAnimationFrame(renderLoop); - else - context.requestId = null; - }; + // Default to EaseInEaseOut if not specified + var timingFunction = ([anim respondsToSelector:@selector(timingFunction)]) ? [anim timingFunction] : [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - // --- 6. Storage & Kickoff --- - // Use the keyPath as the identifier if no specific key was provided - var storageKey = (key && key.length > 0) ? key : keyPath; + // --- 4. Prepare Context --- + var context = { + "animation": anim, + "keyPath": keyPath, + "startValue": startValue, + "endValue": endValue, + "duration": duration * 1000.0, // ms + "timingFunction": timingFunction, + "startTime": null, + "requestId": null + }; - // Remove any conflicting animation on this specific property/key - [self removeAnimationForKey:storageKey]; + // --- 5. Render Loop --- + var _self = self; - context.requestId = window.requestAnimationFrame(renderLoop); - [_activeAnimations setObject:context forKey:storageKey]; + var renderLoop = function(timestamp) { + if ([_self _renderAnimationStep:context timestamp:timestamp]) + context.requestId = window.requestAnimationFrame(renderLoop); + else + context.requestId = null; + }; + + // --- 6. Storage & Kickoff --- + // Use the keyPath as the identifier if no specific key was provided + var storageKey = (key && key.length > 0) ? key : keyPath; + + // Remove any conflicting animation on this specific property/key + [self removeAnimationForKey:storageKey]; + + context.requestId = window.requestAnimationFrame(renderLoop); + [_activeAnimations setObject:context forKey:storageKey]; } - (void)removeAnimationForKey:(CPString)key { - var context = [_activeAnimations objectForKey:key]; - if (context) - { - if (context.requestId !== null) - window.cancelAnimationFrame(context.requestId); - [_activeAnimations removeObjectForKey:key]; - } + var context = [_activeAnimations objectForKey:key]; + if (context) + { + if (context.requestId !== null) + window.cancelAnimationFrame(context.requestId); + [_activeAnimations removeObjectForKey:key]; + } } - (void)removeAllAnimations { - var keys = [_activeAnimations allKeys], - count = [keys count]; - while (count--) - [self removeAnimationForKey:[keys objectAtIndex:count]]; + var keys = [_activeAnimations allKeys], + count = [keys count]; + while (count--) + [self removeAnimationForKey:[keys objectAtIndex:count]]; } /* - Solves Cubic Bezier for t. - p1, p2 are the control points (x,y). p0 is 0,0, p3 is 1,1. - This is a simplified solver for standard Core Animation timing functions. -*/ + Solves Cubic Bezier for t. + p1, p2 are the control points (x,y). p0 is 0,0, p3 is 1,1. + This is a simplified solver for standard Core Animation timing functions. + */ - (float)_solveBezier:(float)t forTimingFunction:(CAMediaTimingFunction)tf { - if (!tf) return t; - - // Linear optimization - if (tf === [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]) - return t; + if (!tf) return t; - var points = [tf controlPoints]; // [c1x, c1y, c2x, c2y] - var p1x = points[0], p1y = points[1], - p2x = points[2], p2y = points[3]; + // Linear optimization + if (tf === [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]) + return t; - // Simple polynomial evaluation (De Casteljau's algorithm/Cubic formula subset) - // Since we are usually dealing with standard easing, we can approximate 1D easing on the Y axis - // based on linear time X, or do a full solve. - // For brevity/speed in JS, we often approximate basic easing: - - // 3t^2 * (1-t) + t^3 ... standard bezier blending functions - var cx = 3.0 * p1x; - var bx = 3.0 * (p2x - p1x) - cx; - var ax = 1.0 - cx - bx; + var points = [tf controlPoints]; // [c1x, c1y, c2x, c2y] + var p1x = points[0], p1y = points[1], + p2x = points[2], p2y = points[3]; - var cy = 3.0 * p1y; - var by = 3.0 * (p2y - p1y) - cy; - var ay = 1.0 - cy - by; + // Simple polynomial evaluation (De Casteljau's algorithm/Cubic formula subset) + // Since we are usually dealing with standard easing, we can approximate 1D easing on the Y axis + // based on linear time X, or do a full solve. + // For brevity/speed in JS, we often approximate basic easing: - // Solve for X given t (time) using Newton-Raphson - var sampleT = t; - for (var i = 0; i < 5; i++) { - var x = ((ax * sampleT + bx) * sampleT + cx) * sampleT - t; - if (Math.abs(x) < 1e-3) break; - var d = (3.0 * ax * sampleT + 2.0 * bx) * sampleT + cx; - if (Math.abs(d) < 1e-6) break; - sampleT = sampleT - x / d; - } + // 3t^2 * (1-t) + t^3 ... standard bezier blending functions + var cx = 3.0 * p1x; + var bx = 3.0 * (p2x - p1x) - cx; + var ax = 1.0 - cx - bx; - // Solve for Y given derived T - return ((ay * sampleT + by) * sampleT + cy) * sampleT; + var cy = 3.0 * p1y; + var by = 3.0 * (p2y - p1y) - cy; + var ay = 1.0 - cy - by; + + // Solve for X given t (time) using Newton-Raphson + var sampleT = t; + for (var i = 0; i < 5; i++) { + var x = ((ax * sampleT + bx) * sampleT + cx) * sampleT - t; + if (Math.abs(x) < 1e-3) break; + var d = (3.0 * ax * sampleT + 2.0 * bx) * sampleT + cx; + if (Math.abs(d) < 1e-6) break; + sampleT = sampleT - x / d; + } + + // Solve for Y given derived T + return ((ay * sampleT + by) * sampleT + cy) * sampleT; } - (BOOL)_renderAnimationStep:(JSObject)context timestamp:(double)timestamp { - if (context.startTime === null) - context.startTime = timestamp; + if (context.startTime === null) + context.startTime = timestamp; - var elapsed = timestamp - context.startTime, - linearProgress = elapsed / context.duration; + var elapsed = timestamp - context.startTime, + linearProgress = elapsed / context.duration; - if (linearProgress > 1.0) linearProgress = 1.0; + if (linearProgress > 1.0) linearProgress = 1.0; - // Apply Timing Function - var progress = [self _solveBezier:linearProgress forTimingFunction:context.timingFunction]; + // Apply Timing Function + var progress = [self _solveBezier:linearProgress forTimingFunction:context.timingFunction]; - var start = context.startValue, - end = context.endValue, - current = nil; + var start = context.startValue, + end = context.endValue, + current = nil; - // Number - if (typeof start === "number") - { - current = start + (end - start) * progress; - } - // Point / Size / Rect - else if (start && start.x !== undefined && start.y !== undefined) // CGPoint - { - current = CGPointMake(start.x + (end.x - start.x) * progress, - start.y + (end.y - start.y) * progress); - } - else if (start && start.width !== undefined && start.height !== undefined) // CGSize - { - current = CGSizeMake(start.width + (end.width - start.width) * progress, - start.height + (end.height - start.height) * progress); - } - else if (start && start.origin !== undefined && start.size !== undefined) // CGRect - { - current = CGRectMake( - start.origin.x + (end.origin.x - start.origin.x) * progress, - start.origin.y + (end.origin.y - start.origin.y) * progress, - start.size.width + (end.size.width - start.size.width) * progress, - start.size.height + (end.size.height - start.size.height) * progress - ); - } + // Number + if (typeof start === "number") + { + current = start + (end - start) * progress; + } + // Point / Size / Rect + else if (start && start.x !== undefined && start.y !== undefined) // CGPoint + { + current = CGPointMake(start.x + (end.x - start.x) * progress, + start.y + (end.y - start.y) * progress); + } + else if (start && start.width !== undefined && start.height !== undefined) // CGSize + { + current = CGSizeMake(start.width + (end.width - start.width) * progress, + start.height + (end.height - start.height) * progress); + } + else if (start && start.origin !== undefined && start.size !== undefined) // CGRect + { + current = CGRectMake( + start.origin.x + (end.origin.x - start.origin.x) * progress, + start.origin.y + (end.origin.y - start.origin.y) * progress, + start.size.width + (end.size.width - start.size.width) * progress, + start.size.height + (end.size.height - start.size.height) * progress + ); + } - if (current !== nil) - [[self delegate] setValue:current forKey:context.keyPath]; + if (current !== nil) + [[self delegate] setValue:current forKey:context.keyPath]; - if (linearProgress >= 1.0) - { - var anim = context.animation; - - // Cleanup - var shouldRemove = [anim respondsToSelector:@selector(isRemovedOnCompletion)] ? [anim isRemovedOnCompletion] : YES; - - if (shouldRemove) { - // Find key by context identity to handle groups correctly - var keys = [_activeAnimations allKeys]; - for (var i = 0; i < keys.length; i++) { - if ([_activeAnimations objectForKey:keys[i]] === context) { - [_activeAnimations removeObjectForKey:keys[i]]; - break; - } - } - } + if (linearProgress >= 1.0) + { + var anim = context.animation; - // Delegate - var delegate = [anim delegate]; - if (delegate && [delegate respondsToSelector:@selector(animationDidStop:finished:)]) - [delegate animationDidStop:anim finished:YES]; + // Cleanup + var shouldRemove = [anim respondsToSelector:@selector(isRemovedOnCompletion)] ? [anim isRemovedOnCompletion] : YES; - return NO; - } + if (shouldRemove) { + // Find key by context identity to handle groups correctly + var keys = [_activeAnimations allKeys]; + for (var i = 0; i < keys.length; i++) { + if ([_activeAnimations objectForKey:keys[i]] === context) { + [_activeAnimations removeObjectForKey:keys[i]]; + break; + } + } + } - return YES; + // Delegate + var delegate = [anim delegate]; + if (delegate && [delegate respondsToSelector:@selector(animationDidStop:finished:)]) + [delegate animationDidStop:anim finished:YES]; + + return NO; + } + + return YES; } /* @ignore */ - (void)_setOwningView:(CPView)anOwningView { - _owningView = anOwningView; + _owningView = anOwningView; - if (_owningView) - { - _owningView = anOwningView; + if (_owningView) + { + _owningView = anOwningView; - _bounds.size = CGSizeMakeCopy([_owningView bounds].size); - _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); - } + _bounds.size = CGSizeMakeCopy([_owningView bounds].size); + _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); + } - _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask | CALayerGeometryBoundsMask); + _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask | CALayerGeometryBoundsMask); } /* @ignore */ - (void)_owningViewBoundsChanged { - _bounds.size = CGSizeMakeCopy([_owningView bounds].size); - _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); + _bounds.size = CGSizeMakeCopy([_owningView bounds].size); + _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); - _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask | CALayerGeometryBoundsMask); + _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask | CALayerGeometryBoundsMask); } /* @ignore */ - (void)_update { - window.loop = true; + window.loop = true; - var mask = _runLoopUpdateMask; + var mask = _runLoopUpdateMask; - if (mask & CALayerDOMUpdateMask) - _CALayerUpdateDOM(self, mask); + if (mask & CALayerDOMUpdateMask) + _CALayerUpdateDOM(self, mask); - if (mask & CALayerDisplayUpdateMask) - [self display]; + if (mask & CALayerDisplayUpdateMask) + [self display]; - else if (mask & CALayerFrameSizeUpdateMask || mask & CALayerCompositeUpdateMask) - [self composite]; + else if (mask & CALayerFrameSizeUpdateMask || mask & CALayerCompositeUpdateMask) + [self composite]; - _runLoopUpdateMask = 0; + _runLoopUpdateMask = 0; - window.loop = false; + window.loop = false; } @end function _CALayerUpdateSublayerTransformForSublayers(aLayer) { - var bounds = aLayer._bounds, - anchorPoint = aLayer._anchorPoint, - translateX = CGRectGetWidth(bounds) * anchorPoint.x, - translateY = CGRectGetHeight(bounds) * anchorPoint.y; + var bounds = aLayer._bounds, + anchorPoint = aLayer._anchorPoint, + translateX = CGRectGetWidth(bounds) * anchorPoint.x, + translateY = CGRectGetHeight(bounds) * anchorPoint.y; - aLayer._sublayerTransformForSublayers = CGAffineTransformConcat( - CGAffineTransformMakeTranslation(-translateX, -translateY), - CGAffineTransformConcat(aLayer._sublayerTransform, - CGAffineTransformMakeTranslation(translateX, translateY))); + aLayer._sublayerTransformForSublayers = CGAffineTransformConcat( + CGAffineTransformMakeTranslation(-translateX, -translateY), + CGAffineTransformConcat(aLayer._sublayerTransform, + CGAffineTransformMakeTranslation(translateX, translateY))); } function _CALayerUpdateDOM(aLayer, aMask) { #if PLATFORM(DOM) - var DOMElementStyle = aLayer._DOMElement.style; + var DOMElementStyle = aLayer._DOMElement.style; - if (aMask & CALayerZPositionUpdateMask) - DOMElementStyle.zIndex = aLayer._zPosition; + if (aMask & CALayerZPositionUpdateMask) + DOMElementStyle.zIndex = aLayer._zPosition; - var frame = aLayer._backingStoreFrame; + var frame = aLayer._backingStoreFrame; - if (aMask & CALayerFrameOriginUpdateMask) - { - DOMElementStyle.top = ROUND(CGRectGetMinY(frame)) + "px"; - DOMElementStyle.left = ROUND(CGRectGetMinX(frame)) + "px"; - } + if (aMask & CALayerFrameOriginUpdateMask) + { + DOMElementStyle.top = ROUND(CGRectGetMinY(frame)) + "px"; + DOMElementStyle.left = ROUND(CGRectGetMinX(frame)) + "px"; + } - if (aMask & CALayerFrameSizeUpdateMask) - { - var width = MAX(0.0, ROUND(CGRectGetWidth(frame))), - height = MAX(0.0, ROUND(CGRectGetHeight(frame))), - DOMContentsElement = aLayer._DOMContentsElement; + if (aMask & CALayerFrameSizeUpdateMask) + { + var width = MAX(0.0, ROUND(CGRectGetWidth(frame))), + height = MAX(0.0, ROUND(CGRectGetHeight(frame))), + DOMContentsElement = aLayer._DOMContentsElement; - DOMElementStyle.width = width + "px"; - DOMElementStyle.height = height + "px"; + DOMElementStyle.width = width + "px"; + DOMElementStyle.height = height + "px"; - if (DOMContentsElement) - { - DOMContentsElement.width = width; - DOMContentsElement.height = height; - DOMContentsElement.style.width = width + "px"; - DOMContentsElement.style.height = height + "px"; - } - } + if (DOMContentsElement) + { + DOMContentsElement.width = width; + DOMContentsElement.height = height; + DOMContentsElement.style.width = width + "px"; + DOMContentsElement.style.height = height + "px"; + } + } #endif } function _CALayerRecalculateGeometry(aLayer, aGeometryChange) { - var bounds = aLayer._bounds, - superlayer = aLayer._superlayer, - width = CGRectGetWidth(bounds), - height = CGRectGetHeight(bounds), - position = aLayer._position, - anchorPoint = aLayer._anchorPoint, - affineTransform = aLayer._affineTransform, - backingStoreFrameSize = CGSizeMakeCopy(aLayer._backingStoreFrame), - hasCustomBackingStoreFrame = aLayer._hasCustomBackingStoreFrame; + var bounds = aLayer._bounds, + superlayer = aLayer._superlayer, + width = CGRectGetWidth(bounds), + height = CGRectGetHeight(bounds), + position = aLayer._position, + anchorPoint = aLayer._anchorPoint, + affineTransform = aLayer._affineTransform, + backingStoreFrameSize = CGSizeMakeCopy(aLayer._backingStoreFrame), + hasCustomBackingStoreFrame = aLayer._hasCustomBackingStoreFrame; - // Go to anchor, transform, go back to bounds. - aLayer._transformFromLayer = CGAffineTransformConcat( - CGAffineTransformMakeTranslation(-width * anchorPoint.x - CGRectGetMinX(aLayer._bounds), -height * anchorPoint.y - CGRectGetMinY(aLayer._bounds)), - CGAffineTransformConcat(affineTransform, - CGAffineTransformMakeTranslation(position.x, position.y))); + // Go to anchor, transform, go back to bounds. + aLayer._transformFromLayer = CGAffineTransformConcat( + CGAffineTransformMakeTranslation(-width * anchorPoint.x - CGRectGetMinX(aLayer._bounds), -height * anchorPoint.y - CGRectGetMinY(aLayer._bounds)), + CGAffineTransformConcat(affineTransform, + CGAffineTransformMakeTranslation(position.x, position.y))); - if (superlayer && superlayer._hasSublayerTransform) - { - // aLayer._transformFromLayer = CGAffineTransformConcat(aLayer._transformFromLayer, superlayer._sublayerTransformForSublayers); - CGAffineTransformConcatTo(aLayer._transformFromLayer, superlayer._sublayerTransformForSublayers, aLayer._transformFromLayer); - } + if (superlayer && superlayer._hasSublayerTransform) + { + // aLayer._transformFromLayer = CGAffineTransformConcat(aLayer._transformFromLayer, superlayer._sublayerTransformForSublayers); + CGAffineTransformConcatTo(aLayer._transformFromLayer, superlayer._sublayerTransformForSublayers, aLayer._transformFromLayer); + } - aLayer._transformToLayer = CGAffineTransformInvert(aLayer._transformFromLayer); + aLayer._transformToLayer = CGAffineTransformInvert(aLayer._transformFromLayer); - //aLayer._transformFromLayer.tx = ROUND(aLayer._transformFromLayer.tx); - //aLayer._transformFromLayer.ty = ROUND(aLayer._transformFromLayer.ty); + //aLayer._transformFromLayer.tx = ROUND(aLayer._transformFromLayer.tx); + //aLayer._transformFromLayer.ty = ROUND(aLayer._transformFromLayer.ty); - aLayer._frame = nil; - aLayer._standardBackingStoreFrame = [aLayer convertRect:bounds toLayer:nil]; + aLayer._frame = nil; + aLayer._standardBackingStoreFrame = [aLayer convertRect:bounds toLayer:nil]; - if (superlayer) - { - var bounds = [superlayer bounds], - frame = [superlayer convertRect:bounds toLayer:nil]; + if (superlayer) + { + // IMPROVEMENT: Renamed local 'bounds' to 'superlayerBounds' to prevent shadowing of outer 'bounds' declaration. + var superlayerBounds = [superlayer bounds], + frame = [superlayer convertRect:superlayerBounds toLayer:nil]; - aLayer._standardBackingStoreFrame.origin.x -= CGRectGetMinX(frame); - aLayer._standardBackingStoreFrame.origin.y -= CGRectGetMinY(frame); - } + aLayer._standardBackingStoreFrame.origin.x -= CGRectGetMinX(frame); + aLayer._standardBackingStoreFrame.origin.y -= CGRectGetMinY(frame); + } - // We used to use CGRectIntegral here, but what we actually want, is the largest integral - // rect that would ever contain this box, since for any width/height, there are 2 (4) - // possible integral rects for it depending on it's position. It's OK that this is sometimes - // bigger than the "optimal" bounding integral rect since that doesn't change drawing. + // We used to use CGRectIntegral here, but what we actually want, is the largest integral + // rect that would ever contain this box, since for any width/height, there are 2 (4) + // possible integral rects for it depending on it's position. It's OK that this is sometimes + // bigger than the "optimal" bounding integral rect since that doesn't change drawing. - var origin = aLayer._standardBackingStoreFrame.origin, - size = aLayer._standardBackingStoreFrame.size; + var origin = aLayer._standardBackingStoreFrame.origin, + size = aLayer._standardBackingStoreFrame.size; - origin.x = FLOOR(origin.x); - origin.y = FLOOR(origin.y); - size.width = CEIL(size.width) + 1.0; - size.height = CEIL(size.height) + 1.0; + origin.x = FLOOR(origin.x); + origin.y = FLOOR(origin.y); + size.width = CEIL(size.width) + 1.0; + size.height = CEIL(size.height) + 1.0; - // FIXME: This avoids the central issue that a position change is sometimes a display and sometimes - // a div move, and sometimes both. + // FIXME: This avoids the central issue that a position change is sometimes a display and sometimes + // a div move, and sometimes both. - // Only use this frame if we don't currently have a custom backing store frame. - if (!hasCustomBackingStoreFrame) - { - var backingStoreFrame = CGRectMakeCopy(aLayer._standardBackingStoreFrame); + // Only use this frame if we don't currently have a custom backing store frame. + if (!hasCustomBackingStoreFrame) + { + var backingStoreFrame = CGRectMakeCopy(aLayer._standardBackingStoreFrame); - // These values get rounded in the DOM, so don't both updating them if they're - // not going to be different after rounding. - if (ROUND(CGRectGetMinX(backingStoreFrame)) != ROUND(CGRectGetMinX(aLayer._backingStoreFrame)) || - ROUND(CGRectGetMinY(backingStoreFrame)) != ROUND(CGRectGetMinY(aLayer._backingStoreFrame))) - [aLayer registerRunLoopUpdateWithMask:CALayerFrameOriginUpdateMask]; + // These values get rounded in the DOM, so don't both updating them if they're + // not going to be different after rounding. + if (ROUND(CGRectGetMinX(backingStoreFrame)) != ROUND(CGRectGetMinX(aLayer._backingStoreFrame)) || + ROUND(CGRectGetMinY(backingStoreFrame)) != ROUND(CGRectGetMinY(aLayer._backingStoreFrame))) + [aLayer registerRunLoopUpdateWithMask:CALayerFrameOriginUpdateMask]; - // Any change in size due to a geometry change is purely due to rounding error. - if ((CGRectGetWidth(backingStoreFrame) != ROUND(CGRectGetWidth(aLayer._backingStoreFrame)) || - CGRectGetHeight(backingStoreFrame) != ROUND(CGRectGetHeight(aLayer._backingStoreFrame)))) - [aLayer registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; + // Any change in size due to a geometry change is purely due to rounding error. + if ((CGRectGetWidth(backingStoreFrame) != ROUND(CGRectGetWidth(aLayer._backingStoreFrame)) || + CGRectGetHeight(backingStoreFrame) != ROUND(CGRectGetHeight(aLayer._backingStoreFrame)))) + [aLayer registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; - aLayer._backingStoreFrame = backingStoreFrame; - } + aLayer._backingStoreFrame = backingStoreFrame; + } - if (aGeometryChange & CALayerGeometryBoundsMask && aLayer._needsDisplayOnBoundsChange) - [aLayer setNeedsDisplay]; - // We need to recompose if we have a custom backing store frame, OR - // If the change is not solely composed of position and anchor points changes. - // Anchor point and position changes simply move the object, requiring - // no re-rendering. - else if (hasCustomBackingStoreFrame || (aGeometryChange & ~(CALayerGeometryPositionMask | CALayerGeometryAnchorPointMask))) - [aLayer setNeedsComposite]; + if (aGeometryChange & CALayerGeometryBoundsMask && aLayer._needsDisplayOnBoundsChange) + [aLayer setNeedsDisplay]; + // We need to recompose if we have a custom backing store frame, OR + // If the change is not solely composed of position and anchor points changes. + // Anchor point and position changes simply move the object, requiring + // no re-rendering. + else if (hasCustomBackingStoreFrame || (aGeometryChange & ~(CALayerGeometryPositionMask | CALayerGeometryAnchorPointMask))) + [aLayer setNeedsComposite]; - var sublayers = aLayer._sublayers, - index = 0, - count = sublayers.length; + var sublayers = aLayer._sublayers, + index = 0, + count = sublayers.length; - for (; index < count; ++index) - _CALayerRecalculateGeometry(sublayers[index], aGeometryChange); + for (; index < count; ++index) + _CALayerRecalculateGeometry(sublayers[index], aGeometryChange); } function _CALayerGetTransform(fromLayer, toLayer) { - var transform = CGAffineTransformMakeIdentity(); + var transform = CGAffineTransformMakeIdentity(); - if (fromLayer) - { - var layer = fromLayer; + if (fromLayer) + { + var layer = fromLayer; - // If we have a fromLayer, "climb up" the layer tree until - // we hit the root node or we hit the toLayer. - while (layer && layer != toLayer) - { - var transformFromLayer = layer._transformFromLayer; + // If we have a fromLayer, "climb up" the layer tree until + // we hit the root node or we hit the toLayer. + while (layer && layer != toLayer) + { + var transformFromLayer = layer._transformFromLayer; - //transform = CGAffineTransformConcat(transform, layer._transformFromLayer); - CGAffineTransformConcatTo(transform, transformFromLayer, transform); + //transform = CGAffineTransformConcat(transform, layer._transformFromLayer); + CGAffineTransformConcatTo(transform, transformFromLayer, transform); - layer = layer._superlayer; - } + layer = layer._superlayer; + } - // If we hit toLayer, then we're done. - if (layer == toLayer) - return transform; - } + // If we hit toLayer, then we're done. + if (layer == toLayer) + return transform; + } - var layers = [], - layer = toLayer; + var layers = [], + layer = toLayer; - while (layer) - { - layers.push(layer); - layer = layer._superlayer; - } + while (layer) + { + layers.push(layer); + layer = layer._superlayer; + } - var index = layers.length; + var index = layers.length; - while (index--) - { - var transformToLayer = layers[index]._transformToLayer; + while (index--) + { + var transformToLayer = layers[index]._transformToLayer; - CGAffineTransformConcatTo(transform, transformToLayer, transform); - } + CGAffineTransformConcatTo(transform, transformToLayer, transform); + } - return transform; + return transform; } From c97839905c75745475fa104c9105be9633885665 Mon Sep 17 00:00:00 2001 From: David Richardson Date: Thu, 23 Jul 2026 17:22:36 -0600 Subject: [PATCH 17/35] Refactor CPPlatformPasteboard: Remove CPP macros and legacy IE fallbacks As part of the ongoing technical debt cleanup, this commit modernizes CPPlatformPasteboard.j by removing its reliance on the C-preprocessor and stripping obsolete Internet Explorer compatibility code. Key changes: - Inlined the `SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT` and `SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT` macros directly to standard property assignments. - Removed the IE `srcElement` fallback in `hasEditableTarget`. - Removed the IE `attachEvent`/`detachEvent` branch in `createDOMElements`, keeping only the standard `addEventListener` path. - Replaced `var` with `const` and `let` for proper block scoping and immutability. - Retained the `#if PLATFORM(DOM)` guards to protect the Node.js CI during the phased modernization effort. All changes are annotated with `// MODIFICATION:` comments for clear review tracking. --- AppKit/Platform/DOM/CPPlatformPasteboard.j | 661 +++++++++++---------- 1 file changed, 339 insertions(+), 322 deletions(-) diff --git a/AppKit/Platform/DOM/CPPlatformPasteboard.j b/AppKit/Platform/DOM/CPPlatformPasteboard.j index 102feb670..6bc533eda 100644 --- a/AppKit/Platform/DOM/CPPlatformPasteboard.j +++ b/AppKit/Platform/DOM/CPPlatformPasteboard.j @@ -37,22 +37,24 @@ #if PLATFORM(DOM) -#define SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent) anEvent._suppressCappuccinoCut = YES -#define SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent) anEvent._suppressCappuccinoPaste = YES +// MODIFICATION: Removed #define macros for SUPPRESS_CAPPUCCINO_CUT/PASTE. +// Property assignments are now inlined directly at call sites for readability. var hasEditableTarget = function(aDOMEvent) { - var target = aDOMEvent.target || aDOMEvent.srcElement; + // MODIFICATION: Removed IE 'srcElement' fallback. Replaced 'var' with 'let'. + let target = aDOMEvent.target; - if (!target) - return NO; + if (!target) + return NO; - if (target.contentEditable == "true") - return YES; + if (target.contentEditable == "true") + return YES; - var nodeName = target.nodeName.toUpperCase(); + // MODIFICATION: Replaced 'var' with 'let'. + let nodeName = target.nodeName.toUpperCase(); - return nodeName === "INPUT" || nodeName == "TEXTAREA"; + return nodeName === "INPUT" || nodeName == "TEXTAREA"; } /* @@ -63,466 +65,481 @@ var hasEditableTarget = function(aDOMEvent) */ @implementation CPPlatformPasteboard : CPObject { - DOMWindow _DOMWindow; + DOMWindow _DOMWindow; - DOMElement _DOMPasteboardElement; + DOMElement _DOMPasteboardElement; - BOOL supportsNativeCopyAndPaste; - BOOL hasBugWhichPreventsNonEditablePaste; - BOOL hasBugWhichPreventsNonEditablePasteRedirect; + BOOL supportsNativeCopyAndPaste; + BOOL hasBugWhichPreventsNonEditablePaste; + BOOL hasBugWhichPreventsNonEditablePasteRedirect; - BOOL currentEventIsNativePasteEvent; - BOOL currentEventIsNativeCopyOrCutEvent; - BOOL currentEventShouldBeSuppressed; - BOOL currentEventShouldDefinitelyBubble; - BOOL currentEventShouldDefinitelyNotBubble; + BOOL currentEventIsNativePasteEvent; + BOOL currentEventIsNativeCopyOrCutEvent; + BOOL currentEventShouldBeSuppressed; + BOOL currentEventShouldDefinitelyBubble; + BOOL currentEventShouldDefinitelyNotBubble; - BOOL _ignoreNativeCopyOrCutEvent; - BOOL _ignoreNativePastePreparation; + BOOL _ignoreNativeCopyOrCutEvent; + BOOL _ignoreNativePastePreparation; } - (id)init { - if (self = [super init]) - { - supportsNativeCopyAndPaste = CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature); - hasBugWhichPreventsNonEditablePaste = CPPlatformHasBug(CPJavaScriptPasteRequiresEditableTarget); - hasBugWhichPreventsNonEditablePasteRedirect = CPPlatformHasBug(CPJavaScriptPasteCantRefocus); - } + if (self = [super init]) + { + supportsNativeCopyAndPaste = CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature); + hasBugWhichPreventsNonEditablePaste = CPPlatformHasBug(CPJavaScriptPasteRequiresEditableTarget); + hasBugWhichPreventsNonEditablePasteRedirect = CPPlatformHasBug(CPJavaScriptPasteCantRefocus); + } - return self; + return self; } - (void)setDOMWindow:(DOMWindow)aDOMWindow { - if (_DOMWindow === aDOMWindow) - return; + if (_DOMWindow === aDOMWindow) + return; - if (_DOMWindow) - [self destroyDOMElements]; + if (_DOMWindow) + [self destroyDOMElements]; - _DOMWindow = aDOMWindow; + _DOMWindow = aDOMWindow; - if (_DOMWindow) - [self createDOMElements]; + if (_DOMWindow) + [self createDOMElements]; } - (void)createDOMElements { - var theDocument = _DOMWindow.document, - _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; + // MODIFICATION: Replaced 'var' with 'const'/'let'. + const theDocument = _DOMWindow.document, + _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; - // Create Native Pasteboard handler. - _DOMPasteboardElement = theDocument.createElement("textarea"); + // Create Native Pasteboard handler. + _DOMPasteboardElement = theDocument.createElement("textarea"); - _DOMPasteboardElement.style.position = "absolute"; - _DOMPasteboardElement.style.top = "-10000px"; - _DOMPasteboardElement.style.zIndex = "999"; - _DOMPasteboardElement.className = "cpdontremove"; + _DOMPasteboardElement.style.position = "absolute"; + _DOMPasteboardElement.style.top = "-10000px"; + _DOMPasteboardElement.style.zIndex = "999"; + _DOMPasteboardElement.className = "cpdontremove"; - _DOMBodyElement.appendChild(_DOMPasteboardElement); + _DOMBodyElement.appendChild(_DOMPasteboardElement); - _DOMPasteboardElement.blur(); + _DOMPasteboardElement.blur(); - var copyEventCallback = function (anEvent) { return [self beforeCopyEvent:anEvent]; }, - nativeBeforeClipboardEventCallback = function (anEvent) { return [self nativeBeforeClipboardEvent:anEvent]; }, - nativeCopyOrCutEventCallback = function (anEvent) { return [self nativeCopyOrCutEvent:anEvent]; }, - pasteEventCallback = function (anEvent) { return [self beforePasteEvent:anEvent]; }, - nativePasteEventCallback = function (anEvent) { return [self nativePasteEvent:anEvent]; }; + // MODIFICATION: Replaced 'var' with 'const'. + const copyEventCallback = function (anEvent) { return [self beforeCopyEvent:anEvent]; }, + nativeBeforeClipboardEventCallback = function (anEvent) { return [self nativeBeforeClipboardEvent:anEvent]; }, + nativeCopyOrCutEventCallback = function (anEvent) { return [self nativeCopyOrCutEvent:anEvent]; }, + pasteEventCallback = function (anEvent) { return [self beforePasteEvent:anEvent]; }, + nativePasteEventCallback = function (anEvent) { return [self nativePasteEvent:anEvent]; }; - if (theDocument.addEventListener) - { - if (supportsNativeCopyAndPaste) - { - _DOMWindow.addEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("copy", nativeCopyOrCutEventCallback, NO); - _DOMWindow.addEventListener("cut", nativeCopyOrCutEventCallback, NO); - _DOMWindow.addEventListener("paste", nativePasteEventCallback, NO); - } - else - { - theDocument.addEventListener("beforepaste", pasteEventCallback, NO); - theDocument.addEventListener("beforecopy", copyEventCallback, NO); - theDocument.addEventListener("beforecut", copyEventCallback, NO); - } + // MODIFICATION: Removed the 'if (theDocument.addEventListener)' check and the IE 'else' block. + // Modern browsers natively support addEventListener. + if (supportsNativeCopyAndPaste) + { + _DOMWindow.addEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("copy", nativeCopyOrCutEventCallback, NO); + _DOMWindow.addEventListener("cut", nativeCopyOrCutEventCallback, NO); + _DOMWindow.addEventListener("paste", nativePasteEventCallback, NO); + } + else + { + theDocument.addEventListener("beforepaste", pasteEventCallback, NO); + theDocument.addEventListener("beforecopy", copyEventCallback, NO); + theDocument.addEventListener("beforecut", copyEventCallback, NO); + } - _DOMWindow.addEventListener("unload", function() - { - if (supportsNativeCopyAndPaste) - { - _DOMWindow.removeEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("copy", nativeCopyOrCutEventCallback, NO); - _DOMWindow.removeEventListener("cut", nativeCopyOrCutEventCallback, NO); - _DOMWindow.removeEventListener("paste", nativePasteEventCallback, NO); - } - else - { - theDocument.removeEventListener("beforepaste", pasteEventCallback, NO); - theDocument.removeEventListener("beforecopy", copyEventCallback, NO); - theDocument.removeEventListener("beforecut", copyEventCallback, NO); - } - }, NO); - } - else - { - // TODO If we wanted IE 8 and lower copy and paste it'd go here. - } + _DOMWindow.addEventListener("unload", function() + { + if (supportsNativeCopyAndPaste) + { + _DOMWindow.removeEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("copy", nativeCopyOrCutEventCallback, NO); + _DOMWindow.removeEventListener("cut", nativeCopyOrCutEventCallback, NO); + _DOMWindow.removeEventListener("paste", nativePasteEventCallback, NO); + } + else + { + theDocument.removeEventListener("beforepaste", pasteEventCallback, NO); + theDocument.removeEventListener("beforecopy", copyEventCallback, NO); + theDocument.removeEventListener("beforecut", copyEventCallback, NO); + } + }, NO); } - (void)destroyDOMElements { - var theDocument = _DOMWindow.document, - _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; + // MODIFICATION: Replaced 'var' with 'const'. + const theDocument = _DOMWindow.document, + _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; - _DOMBodyElement.removeChild(_DOMPasteboardElement); - _DOMPasteboardElement = nil; + _DOMBodyElement.removeChild(_DOMPasteboardElement); + _DOMPasteboardElement = nil; } - (void)windowMaySendKeyEvent:(CPEvent)anEvent { - // Reset our opinions. - currentEventIsNativePasteEvent = NO; - currentEventIsNativeCopyOrCutEvent = NO; - currentEventShouldBeSuppressed = NO; - currentEventShouldDefinitelyNotBubble = NO; - currentEventShouldDefinitelyBubble = NO; + // Reset our opinions. + currentEventIsNativePasteEvent = NO; + currentEventIsNativeCopyOrCutEvent = NO; + currentEventShouldBeSuppressed = NO; + currentEventShouldDefinitelyNotBubble = NO; + currentEventShouldDefinitelyBubble = NO; - if (!anEvent) - return; + if (!anEvent) + return; - if ([anEvent type] !== CPKeyDown) - { - // Reset these flags on key up. - _ignoreNativePastePreparation = NO; - _ignoreNativeCopyOrCutEvent = NO; - return; - } + if ([anEvent type] !== CPKeyDown) + { + // Reset these flags on key up. + _ignoreNativePastePreparation = NO; + _ignoreNativeCopyOrCutEvent = NO; + return; + } - var modifierFlags = [anEvent modifierFlags]; + // MODIFICATION: Replaced 'var' with 'const'. + const modifierFlags = [anEvent modifierFlags]; - if (!(modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) - return; + if (!(modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) + return; - var aDOMEvent = anEvent._DOMEvent, - characters = [anEvent characters], - mayRequireDOMPasteboardElement = [self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:modifierFlags]; + const aDOMEvent = anEvent._DOMEvent, + characters = [anEvent characters], + mayRequireDOMPasteboardElement = [self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:modifierFlags]; - if (characters === "v" && mayRequireDOMPasteboardElement) - { - if (supportsNativeCopyAndPaste && hasBugWhichPreventsNonEditablePaste && hasBugWhichPreventsNonEditablePasteRedirect && !hasEditableTarget(aDOMEvent)) - { - // You can't paste from the system clipboard into a non-editable area in Safari, neither using native - // copy and paste nor our _DOMPasteboardElement hack. We will paste from the Cappuccino pasteboard only - // and allow Safari to "beep" to indicate something went wrong. + if (characters === "v" && mayRequireDOMPasteboardElement) + { + if (supportsNativeCopyAndPaste && hasBugWhichPreventsNonEditablePaste && hasBugWhichPreventsNonEditablePasteRedirect && !hasEditableTarget(aDOMEvent)) + { + // You can't paste from the system clipboard into a non-editable area in Safari, neither using native + // copy and paste nor our _DOMPasteboardElement hack. We will paste from the Cappuccino pasteboard only + // and allow Safari to "beep" to indicate something went wrong. - // The key down will not result in a paste event being sent by Safari, so it's not a paste event. - currentEventIsNativePasteEvent = NO; - // Yes to get the beep. - currentEventShouldDefinitelyBubble = YES; - } - else if (!(supportsNativeCopyAndPaste || hasBugWhichPreventsNonEditablePaste) && !_ignoreNativePastePreparation) - { - // We don't support native copy and paste so we must focus the _DOMPasteboardElement to receive the - // paste content. This needs to be done at keyDown time (or in the case of modern Safari, before the keyDown, - // which unfortunately isn't possible. See above.) - _DOMPasteboardElement.focus(); - _DOMPasteboardElement.select(); - _DOMPasteboardElement.value = ""; - _DOMWindow.setNativeTimeout(function () { [self _checkDOMPasteboardElement]; }, 0); + // The key down will not result in a paste event being sent by Safari, so it's not a paste event. + currentEventIsNativePasteEvent = NO; + // Yes to get the beep. + currentEventShouldDefinitelyBubble = YES; + } + else if (!(supportsNativeCopyAndPaste || hasBugWhichPreventsNonEditablePaste) && !_ignoreNativePastePreparation) + { + // We don't support native copy and paste so we must focus the _DOMPasteboardElement to receive the + // paste content. This needs to be done at keyDown time (or in the case of modern Safari, before the keyDown, + // which unfortunately isn't possible. See above.) + _DOMPasteboardElement.focus(); + _DOMPasteboardElement.select(); + _DOMPasteboardElement.value = ""; + _DOMWindow.setNativeTimeout(function () { [self _checkDOMPasteboardElement]; }, 0); - currentEventIsNativePasteEvent = YES; - } - else if (supportsNativeCopyAndPaste) - currentEventIsNativePasteEvent = YES; + currentEventIsNativePasteEvent = YES; + } + else if (supportsNativeCopyAndPaste) + currentEventIsNativePasteEvent = YES; - if (currentEventIsNativePasteEvent) - { - // We need the event to propagate or nothing will be pasted. - currentEventShouldDefinitelyBubble = YES; + if (currentEventIsNativePasteEvent) + { + // We need the event to propagate or nothing will be pasted. + currentEventShouldDefinitelyBubble = YES; - // And we don't send the keydown because either our native paste envent handler will send it, - // or the _checkDOMPasteboardElement check will. - currentEventShouldBeSuppressed = YES; - } - } - else if ((characters == "c" || characters == "x") && mayRequireDOMPasteboardElement) - { - currentEventIsNativeCopyOrCutEvent = YES; + // And we don't send the keydown because either our native paste envent handler will send it, + // or the _checkDOMPasteboardElement check will. + currentEventShouldBeSuppressed = YES; + } + } + else if ((characters == "c" || characters == "x") && mayRequireDOMPasteboardElement) + { + currentEventIsNativeCopyOrCutEvent = YES; - // If _ignoreNativeCopyOrCutEvent, we already handled the copy/cut in beforeCopyEvent:. - // If supportsNativeCopyAndPaste, we will be handling it in the nativebeforeCopyEvent: handler. - // In both of those cases we don't want to send the event on keydown too, or we'll get 2X copy/cut operations. - if (supportsNativeCopyAndPaste || _ignoreNativeCopyOrCutEvent) - currentEventShouldBeSuppressed = YES; - } + // If _ignoreNativeCopyOrCutEvent, we already handled the copy/cut in beforeCopyEvent:. + // If supportsNativeCopyAndPaste, we will be handling it in the nativebeforeCopyEvent: handler. + // In both of those cases we don't want to send the event on keydown too, or we'll get 2X copy/cut operations. + if (supportsNativeCopyAndPaste || _ignoreNativeCopyOrCutEvent) + currentEventShouldBeSuppressed = YES; + } - if (CPFeatureIsCompatible(CPBackspaceTriggersPageBack) && characters === CPDeleteCharacter && !hasEditableTarget(aDOMEvent)) - currentEventShouldBeSuppressed = YES; + if (CPFeatureIsCompatible(CPBackspaceTriggersPageBack) && characters === CPDeleteCharacter && !hasEditableTarget(aDOMEvent)) + currentEventShouldBeSuppressed = YES; - if (!currentEventShouldBeSuppressed) - { - if (characters === "v") - SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent); - else if (characters === "x") - SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent); - } + if (!currentEventShouldBeSuppressed) + { + // MODIFICATION: Inlined the SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT macro. + if (characters === "v") + anEvent._suppressCappuccinoPaste = YES; + // MODIFICATION: Inlined the SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT macro. + else if (characters === "x") + anEvent._suppressCappuccinoCut = YES; + } } - (BOOL)windowShouldSuppressKeyEvent { - return currentEventShouldBeSuppressed; + return currentEventShouldBeSuppressed; } - (void)windowDidSendKeyEvent:(CPEvent)anEvent { - // Now that the copy event has been sent through the Cappuccino event stack, we can load any Cappuccino - // pasteboard string into our DOMPasteboardElement hack, if necessary. - if (!supportsNativeCopyAndPaste && currentEventIsNativeCopyOrCutEvent) - [self _primeDOMPasteboardElement]; + // Now that the copy event has been sent through the Cappuccino event stack, we can load any Cappuccino + // pasteboard string into our DOMPasteboardElement hack, if necessary. + if (!supportsNativeCopyAndPaste && currentEventIsNativeCopyOrCutEvent) + [self _primeDOMPasteboardElement]; } - (BOOL)windowShouldStopPropagation { - return currentEventShouldDefinitelyNotBubble; + return currentEventShouldDefinitelyNotBubble; } - (BOOL)windowShouldNotStopPropagation { - return currentEventShouldDefinitelyBubble; + return currentEventShouldDefinitelyBubble; } - (CPEvent)_fakeClipboardEvent:(DOMEvent)aDOMEvent type:(CPString)aType { - var keyCode = aType === "x" ? CPKeyCodes.X : (aType === "c" ? CPKeyCodes.C : CPKeyCodes.V), - characters = aType, - timestamp = [CPEvent currentTimestamp], // fake event, might as well use current timestamp - windowNumber = [[CPApp keyWindow] windowNumber], - modifierFlags = CPPlatformActionKeyMask, - 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 isActionKey:YES]; + // MODIFICATION: Replaced 'var' with 'const'. + const keyCode = aType === "x" ? CPKeyCodes.X : (aType === "c" ? CPKeyCodes.C : CPKeyCodes.V), + characters = aType, + timestamp = [CPEvent currentTimestamp], // fake event, might as well use current timestamp + windowNumber = [[CPApp keyWindow] windowNumber], + modifierFlags = CPPlatformActionKeyMask, + 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 isActionKey:YES]; - anEvent._data1 = @{ "simulated": YES }; - anEvent._DOMEvent = aDOMEvent; + anEvent._data1 = @{ "simulated": YES }; + anEvent._DOMEvent = aDOMEvent; - return anEvent; + return anEvent; } - (void)beforeCopyEvent:(DOMEvent)aDOMEvent { - if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask] && !_ignoreNativeCopyOrCutEvent) - { - // we have to send out a fake copy or cut event so that we can force the copy/cut mechanisms to take place - var anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type === "beforecut" ? "x" : "c")]; + if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask] && !_ignoreNativeCopyOrCutEvent) + { + // we have to send out a fake copy or cut event so that we can force the copy/cut mechanisms to take place + // MODIFICATION: Replaced 'var' with 'const'. + const anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type === "beforecut" ? "x" : "c")]; - [CPApp sendEvent:anEvent]; + [CPApp sendEvent:anEvent]; - // Once we've sent it, we can load the copy information into the pasteboard hack. - [self _primeDOMPasteboardElement]; + // Once we've sent it, we can load the copy information into the pasteboard hack. + [self _primeDOMPasteboardElement]; - //then we have to IGNORE the real keyboard event to prevent a double copy - //safari also sends the beforecopy event twice, so we additionally check here and prevent two events - _ignoreNativeCopyOrCutEvent = YES; - } + //then we have to IGNORE the real keyboard event to prevent a double copy + //safari also sends the beforecopy event twice, so we additionally check here and prevent two events + _ignoreNativeCopyOrCutEvent = YES; + } - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } - (BOOL)beforePasteEvent:(DOMEvent)aDOMEvent { - // Set up to capture the paste in a temporary input field. We'll send the event after capture. - if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask]) - { - _DOMPasteboardElement.focus(); - _DOMPasteboardElement.select(); - _DOMPasteboardElement.value = ""; + // Set up to capture the paste in a temporary input field. We'll send the event after capture. + if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask]) + { + _DOMPasteboardElement.focus(); + _DOMPasteboardElement.select(); + _DOMPasteboardElement.value = ""; - _ignoreNativePastePreparation = YES; - } + _ignoreNativePastePreparation = YES; + } - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } /* -Return true if the event may be a copy and paste event, but the target is not an input or text area. -*/ + Return true if the event may be a copy and paste event, but the target is not an input or text area. + */ - (BOOL)_mayRequireDOMPasteboardElementHack:(DOMEvent)aDOMEvent flags:(unsigned)modifierFlags { - return !hasEditableTarget(aDOMEvent) && (modifierFlags & CPPlatformActionKeyMask); + return !hasEditableTarget(aDOMEvent) && (modifierFlags & CPPlatformActionKeyMask); } - (void)_primeDOMPasteboardElement { - var pasteboard = [CPPasteboard generalPasteboard], - types = [pasteboard types]; + // MODIFICATION: Replaced 'var' with 'const'. + const pasteboard = [CPPasteboard generalPasteboard], + types = [pasteboard types]; - if (types.length) - { - if ([types indexOfObjectIdenticalTo:CPStringPboardType] !== CPNotFound) - _DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType]; - else - _DOMPasteboardElement.value = [pasteboard _generateStateUID]; + if (types.length) + { + if ([types indexOfObjectIdenticalTo:CPStringPboardType] !== CPNotFound) + _DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType]; + else + _DOMPasteboardElement.value = [pasteboard _generateStateUID]; - _DOMPasteboardElement.focus(); - _DOMPasteboardElement.select(); + _DOMPasteboardElement.focus(); + _DOMPasteboardElement.select(); - window.setNativeTimeout(function() { [self _clearDOMPasteboardElement]; }, 0); - } + window.setNativeTimeout(function() { [self _clearDOMPasteboardElement]; }, 0); + } } - (void)_checkDOMPasteboardElement { - if (supportsNativeCopyAndPaste) - { - [self _clearDOMPasteboardElement]; - return; - } + if (supportsNativeCopyAndPaste) + { + [self _clearDOMPasteboardElement]; + return; + } - var value = _DOMPasteboardElement.value; + // MODIFICATION: Replaced 'var' with 'const'. + const value = _DOMPasteboardElement.value; - if ([value length]) - { - var pasteboard = [CPPasteboard generalPasteboard], - cappString = [pasteboard stringForType:CPStringPboardType]; + if ([value length]) + { + // MODIFICATION: Replaced 'var' with 'const'. + const pasteboard = [CPPasteboard generalPasteboard], + cappString = [pasteboard stringForType:CPStringPboardType]; - if (cappString != value) - { - [pasteboard declareTypes:[CPStringPboardType] owner:self]; - [pasteboard setString:value forType:CPStringPboardType]; - } - } + if (cappString != value) + { + [pasteboard declareTypes:[CPStringPboardType] owner:self]; + [pasteboard setString:value forType:CPStringPboardType]; + } + } - [self _clearDOMPasteboardElement]; + [self _clearDOMPasteboardElement]; - [CPApp sendEvent:[self _fakeClipboardEvent:nil type:@"v"]]; + [CPApp sendEvent:[self _fakeClipboardEvent:nil type:@"v"]]; - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } - (void)_clearDOMPasteboardElement { - _DOMPasteboardElement.value = ""; - _DOMPasteboardElement.blur(); + _DOMPasteboardElement.value = ""; + _DOMPasteboardElement.blur(); } - (BOOL)nativeBeforeClipboardEvent:(DOMEvent)aDOMEvent { - // Our job here is to return "false" if the given clipboard operation should be enabled even in a situation where - // the browser might normally grey the option out. + // Our job here is to return "false" if the given clipboard operation should be enabled even in a situation where + // the browser might normally grey the option out. - // Allow these fields to do their own thing. - if (hasEditableTarget(aDOMEvent)) - return true; + // Allow these fields to do their own thing. + if (hasEditableTarget(aDOMEvent)) + return true; - var returnValue = YES; + // MODIFICATION: Replaced 'var' with 'let'. + let returnValue = YES; - switch (aDOMEvent.type) - { - case "beforecopy": - returnValue = !([CPApp targetForAction:@selector(copy:)]); - break; - case "beforecut": - returnValue = !([CPApp targetForAction:@selector(cut:)]); - break; - case "beforepaste": - returnValue = !([CPApp targetForAction:@selector(paste:)]); - break; - } + switch (aDOMEvent.type) + { + case "beforecopy": + returnValue = !([CPApp targetForAction:@selector(copy:)]); + break; + case "beforecut": + returnValue = !([CPApp targetForAction:@selector(cut:)]); + break; + case "beforepaste": + returnValue = !([CPApp targetForAction:@selector(paste:)]); + break; + } - if (!returnValue) - _CPDOMEventStop(aDOMEvent, self); + if (!returnValue) + _CPDOMEventStop(aDOMEvent, self); - return returnValue; + return returnValue; } - (BOOL)nativePasteEvent:(DOMEvent)aDOMEvent { - // This shouldn't happen. - if (!supportsNativeCopyAndPaste) - return; + // This shouldn't happen. + if (!supportsNativeCopyAndPaste) + return; - var value; - if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) - value = aDOMEvent.clipboardData.getData('text/plain'); - else - value = _DOMWindow.clipboardData.getData("Text"); + // MODIFICATION: Replaced 'var' with 'let'. + let value; + if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) + value = aDOMEvent.clipboardData.getData('text/plain'); + else + value = _DOMWindow.clipboardData.getData("Text"); - if ([value length]) - { - var pasteboard = [CPPasteboard generalPasteboard], - cappString = [pasteboard stringForType:CPStringPboardType]; + if ([value length]) + { + // MODIFICATION: Replaced 'var' with 'const'. + const pasteboard = [CPPasteboard generalPasteboard], + cappString = [pasteboard stringForType:CPStringPboardType]; - if (cappString != value) - { - [pasteboard declareTypes:[CPStringPboardType] owner:self]; - [pasteboard setString:value forType:CPStringPboardType]; - } - } + if (cappString != value) + { + [pasteboard declareTypes:[CPStringPboardType] owner:self]; + [pasteboard setString:value forType:CPStringPboardType]; + } + } - var anEvent = [self _fakeClipboardEvent:aDOMEvent type:"v"], - platformWindow = [[anEvent window] platformWindow]; + // MODIFICATION: Replaced 'var' with 'const'. + const anEvent = [self _fakeClipboardEvent:aDOMEvent type:"v"], + platformWindow = [[anEvent window] platformWindow]; - SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent); + // MODIFICATION: Inlined the SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT macro. + anEvent._suppressCappuccinoPaste = YES; - // By default we'll stop the native handling of the event since we're handling it ourselves. However, we need to - // stop it before we send the event so that the event can overrule our choice. CPTextField for instance wants the - // default handling when focused (which is to insert into the field). - [platformWindow _propagateCurrentDOMEvent:NO]; + // By default we'll stop the native handling of the event since we're handling it ourselves. However, we need to + // stop it before we send the event so that the event can overrule our choice. CPTextField for instance wants the + // default handling when focused (which is to insert into the field). + [platformWindow _propagateCurrentDOMEvent:NO]; - [CPApp sendEvent:anEvent]; + [CPApp sendEvent:anEvent]; - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - if (![platformWindow _willPropagateCurrentDOMEvent]) - _CPDOMEventStop(aDOMEvent, self); + if (![platformWindow _willPropagateCurrentDOMEvent]) + _CPDOMEventStop(aDOMEvent, self); - return false; + return false; } - (BOOL)nativeCopyOrCutEvent:(DOMEvent)aDOMEvent { - // This shouldn't happen. - if (!supportsNativeCopyAndPaste) - return; + // This shouldn't happen. + if (!supportsNativeCopyAndPaste) + return; - var anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type.indexOf("cut") != CPNotFound ? "x" : "c")], - platformWindow = [[anEvent window] platformWindow]; + // MODIFICATION: Replaced 'var' with 'const'. + const anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type.indexOf("cut") != CPNotFound ? "x" : "c")], + platformWindow = [[anEvent window] platformWindow]; - SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent); + // MODIFICATION: Inlined the SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT macro. + anEvent._suppressCappuccinoCut = YES; - [platformWindow _propagateCurrentDOMEvent:NO]; + [platformWindow _propagateCurrentDOMEvent:NO]; - // Let the app react through copy: and cut: actions. - [CPApp sendEvent:anEvent]; - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + // Let the app react through copy: and cut: actions. + [CPApp sendEvent:anEvent]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - // If StopDOMEventPropagation was set to NO, we don't try to write to the system clipboard. The control that did this - // wants to use the default copy/cut functionality. - if (![platformWindow _willPropagateCurrentDOMEvent]) - { - // Now the app should have written whatever it wants to have copied to the Cappuccino clipboard. So now we need - // to write it to the system board. - _CPDOMEventStop(aDOMEvent, self); + // If StopDOMEventPropagation was set to NO, we don't try to write to the system clipboard. The control that did this + // wants to use the default copy/cut functionality. + if (![platformWindow _willPropagateCurrentDOMEvent]) + { + // Now the app should have written whatever it wants to have copied to the Cappuccino clipboard. So now we need + // to write it to the system board. + _CPDOMEventStop(aDOMEvent, self); - var pasteboard = [CPPasteboard generalPasteboard]; + // MODIFICATION: Replaced 'var' with 'const'. + const pasteboard = [CPPasteboard generalPasteboard]; - if ([[pasteboard types] containsObject:CPStringPboardType]) - { - var stringValue = [pasteboard stringForType:CPStringPboardType]; + if ([[pasteboard types] containsObject:CPStringPboardType]) + { + // MODIFICATION: Replaced 'var' with 'const'. + const stringValue = [pasteboard stringForType:CPStringPboardType]; - if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) - aDOMEvent.clipboardData.setData('text/plain', stringValue); - else - _DOMWindow.clipboardData.setData('Text', stringValue); - } - } + if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) + aDOMEvent.clipboardData.setData('text/plain', stringValue); + else + _DOMWindow.clipboardData.setData('Text', stringValue); + } + } - return ![platformWindow _willPropagateCurrentDOMEvent]; + return ![platformWindow _willPropagateCurrentDOMEvent]; } @end From 59569dcb34e4f883162932fb087e47f359c906b0 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Mon, 17 Aug 2026 18:20:09 +0200 Subject: [PATCH 18/35] new: prevent google translator in cappuccino --- Tools/capp/Resources/Templates/Application/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/capp/Resources/Templates/Application/index.html b/Tools/capp/Resources/Templates/Application/index.html index 0563f49ba..88e50a199 100644 --- a/Tools/capp/Resources/Templates/Application/index.html +++ b/Tools/capp/Resources/Templates/Application/index.html @@ -9,7 +9,7 @@ --> - + From 567ec55875d1ff0860751e8921e5509a22c33c18 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 18 Aug 2026 19:46:22 +0200 Subject: [PATCH 19/35] fixed: usage of tab character instead of 4 spaces --- AppKit/CPTextView/CPTextView.j | 4 +- AppKit/CPTextView/_CPRTFProducer.j | 2 +- Foundation/CPException.j | 4 +- Foundation/CPNumberFormatter.j | 28 +- Foundation/CPSet/CPMutableSet.j | 740 +++++++++--------- Foundation/CPSet/_CPSet.j | 220 +++--- Foundation/CPTimer.j | 230 +++--- Foundation/CPURLConnection.j | 28 +- Tests/AppKit/CPColorPanelTest.j | 136 ++-- Tests/Manual/AttachedSheet2/main.j | 2 +- Tests/Manual/CPCursor/AppController.j | 10 +- .../Manual/CPRuleEditorCibTest/RuleDelegate.j | 6 +- Tests/Manual/CPWebViewTest2/AppController.j | 52 +- Tests/Manual/CompilationTests/TestCases/11.j | 4 +- .../AppController.j | 2 +- .../Manual/PopUpButtonEscTest/AppController.j | 10 +- .../AppController.j | 2 +- Tests/Manual/SmartFoldersDemo/AppController.j | 16 +- .../OutputTests/Messages/super-selector.j | 2 +- 19 files changed, 749 insertions(+), 749 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index ad6c1cbc0..834ecbc4c 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -3088,8 +3088,8 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey", { var rect = [_textView._layoutManager boundingRectForGlyphRange:CPMakeRange(aLoc, 1) inTextContainer:_textView._textContainer]; - if (aLoc >= [_textView._layoutManager numberOfCharacters]) - rect.origin.x = CGRectGetMaxX(rect); + if (aLoc >= [_textView._layoutManager numberOfCharacters]) + rect.origin.x = CGRectGetMaxX(rect); [self setRect:rect]; } diff --git a/AppKit/CPTextView/_CPRTFProducer.j b/AppKit/CPTextView/_CPRTFProducer.j index 65d6f86cf..e0def4985 100644 --- a/AppKit/CPTextView/_CPRTFProducer.j +++ b/AppKit/CPTextView/_CPRTFProducer.j @@ -277,7 +277,7 @@ function _points2twips(a) { return (a) * 20.0; } if (!num) [colorDict setObject:num = [CPNumber numberWithInt:[colorDict count] + 1] - forKey:[color cssString]]; + forKey:[color cssString]]; return [num intValue]; } diff --git a/Foundation/CPException.j b/Foundation/CPException.j index 0bfdd1f9c..7ff6db223 100755 --- a/Foundation/CPException.j +++ b/Foundation/CPException.j @@ -252,8 +252,8 @@ if (Error.prototype._userInfo !== null) function _CPMethodCallString(anObject, aSelector) { - var prefix = class_isMetaClass(anObject.isa) ? "+" : "-"; - return prefix + "[" + [anObject className] + " " + aSelector + "]: "; + var prefix = class_isMetaClass(anObject.isa) ? "+" : "-"; + return prefix + "[" + [anObject className] + " " + aSelector + "]: "; } function _CPRaiseInvalidAbstractInvocation(anObject, aSelector) diff --git a/Foundation/CPNumberFormatter.j b/Foundation/CPNumberFormatter.j index ee0f223a5..4a71d6598 100644 --- a/Foundation/CPNumberFormatter.j +++ b/Foundation/CPNumberFormatter.j @@ -231,8 +231,8 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); _minimumFractionDigits = 0; _maximumFractionDigits = 3; // Invalidate the cached number handler. - // It rebuilds on next use. - // Replaces pre-processor directive, which is incompatible with the new compiler + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler _numberHandler = nil; break; @@ -240,8 +240,8 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); _minimumFractionDigits = 2; _maximumFractionDigits = 2; // Invalidate the cached number handler. - // It rebuilds on next use. - // Replaces pre-processor directive, which is incompatible with the new compiler + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler _numberHandler = nil; break; } @@ -251,8 +251,8 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); { _roundingMode = aRoundingMode; // Invalidate the cached number handler. - // It rebuilds on next use. - // Replaces pre-processor directive, which is incompatible with the new compiler + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler _numberHandler = nil; } @@ -260,8 +260,8 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); { _minimumFractionDigits = aNumber; // Invalidate the cached number handler. - // It rebuilds on next use. - // Replaces pre-processor directive, which is incompatible with the new compiler + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler _numberHandler = nil; } @@ -269,8 +269,8 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); { _maximumFractionDigits = aNumber; // Invalidate the cached number handler. - // It rebuilds on next use. - // Replaces pre-processor directive, which is incompatible with the new compiler + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler _numberHandler = nil; } @@ -278,8 +278,8 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); { _minimum = aNumber; // Invalidate the cached number handler. - // It rebuilds on next use. - // Replaces pre-processor directive, which is incompatible with the new compiler + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler _numberHandler = nil; } @@ -287,8 +287,8 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); { _maximum = aNumber; // Invalidate the cached number handler. - // It rebuilds on next use. - // Replaces pre-processor directive, which is incompatible with the new compiler + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler _numberHandler = nil; } diff --git a/Foundation/CPSet/CPMutableSet.j b/Foundation/CPSet/CPMutableSet.j index 850128589..49949ac9a 100644 --- a/Foundation/CPSet/CPMutableSet.j +++ b/Foundation/CPSet/CPMutableSet.j @@ -23,7 +23,7 @@ */ - (id)initWithCapacity:(unsigned)aCapacity { - return [self init]; + return [self init]; } /*! @@ -32,7 +32,7 @@ */ + (id)setWithCapacity:(CPUInteger)aCapacity { - return [[self alloc] initWithCapacity:aCapacity]; + return [[self alloc] initWithCapacity:aCapacity]; } /*! @@ -41,16 +41,16 @@ */ - (void)filterUsingPredicate:(CPPredicate)aPredicate { - var object, - objectEnumerator = [self objectEnumerator]; + var object, + objectEnumerator = [self objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - if (![aPredicate evaluateWithObject:object]) - { - [self removeObject:object]; - } - } + while ((object = [objectEnumerator nextObject]) != nil) + { + if (![aPredicate evaluateWithObject:object]) + { + [self removeObject:object]; + } + } } /*! @@ -59,7 +59,7 @@ */ - (void)removeObject:(id)anObject { - _CPRaiseInvalidAbstractInvocation(self, _cmd); + _CPRaiseInvalidAbstractInvocation(self, _cmd); } /*! @@ -68,13 +68,13 @@ */ - (void)removeObjectsInArray:(CPArray)anArray { - var index = 0, - count = [anArray count]; + var index = 0, + count = [anArray count]; - for (; index < count; ++index) - { - [self removeObject:[anArray objectAtIndex:index]]; - } + for (; index < count; ++index) + { + [self removeObject:[anArray objectAtIndex:index]]; + } } /*! @@ -82,13 +82,13 @@ */ - (void)removeAllObjects { - var object, - objectEnumerator = [self objectEnumerator]; + var object, + objectEnumerator = [self objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - [self removeObject:object]; - } + while ((object = [objectEnumerator nextObject]) != nil) + { + [self removeObject:object]; + } } /*! @@ -97,12 +97,12 @@ */ - (void)addObjectsFromArray:(CPArray)objects { - var count = [objects count]; + var count = [objects count]; - while (count--) - { - [self addObject:objects[count]]; - } + while (count--) + { + [self addObject:objects[count]]; + } } /*! @@ -111,13 +111,13 @@ */ - (void)unionSet:(CPSet)aSet { - var object, - objectEnumerator = [aSet objectEnumerator]; + var object, + objectEnumerator = [aSet objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - [self addObject:object]; - } + while ((object = [objectEnumerator nextObject]) != nil) + { + [self addObject:object]; + } } /*! @@ -126,13 +126,13 @@ */ - (void)minusSet:(CPSet)aSet { - var object, - objectEnumerator = [aSet objectEnumerator]; + var object, + objectEnumerator = [aSet objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - [self removeObject:object]; - } + while ((object = [objectEnumerator nextObject]) != nil) + { + [self removeObject:object]; + } } /*! @@ -141,24 +141,24 @@ */ - (void)intersectSet:(CPSet)aSet { - var object, - objectEnumerator = [self objectEnumerator], - objectsToRemove = []; + var object, + objectEnumerator = [self objectEnumerator], + objectsToRemove = []; - while ((object = [objectEnumerator nextObject]) != nil) - { - if (![aSet containsObject:object]) - { - objectsToRemove.push(object); - } - } + while ((object = [objectEnumerator nextObject]) != nil) + { + if (![aSet containsObject:object]) + { + objectsToRemove.push(object); + } + } - var count = [objectsToRemove count]; + var count = [objectsToRemove count]; - while (count--) - { - [self removeObject:objectsToRemove[count]]; - } + while (count--) + { + [self removeObject:objectsToRemove[count]]; + } } /*! @@ -167,8 +167,8 @@ */ - (void)setSet:(CPSet)aSet { - [self removeAllObjects]; - [self unionSet:aSet]; + [self removeAllObjects]; + [self unionSet:aSet]; } @end @@ -180,69 +180,69 @@ - (id)valueForKeyPath:(CPString)aKeyPath { - if (!aKeyPath) - { - [self valueForUndefinedKey:@""]; - } + if (!aKeyPath) + { + [self valueForUndefinedKey:@""]; + } - if (aKeyPath.charAt(0) === "@") - { - var dotIndex = aKeyPath.indexOf("."), - operator, - parameter; + if (aKeyPath.charAt(0) === "@") + { + var dotIndex = aKeyPath.indexOf("."), + operator, + parameter; - if (dotIndex !== -1) - { - operator = aKeyPath.substring(1, dotIndex); - parameter = aKeyPath.substring(dotIndex + 1); - } - else - { - operator = aKeyPath.substring(1); - } + if (dotIndex !== -1) + { + operator = aKeyPath.substring(1, dotIndex); + parameter = aKeyPath.substring(dotIndex + 1); + } + else + { + operator = aKeyPath.substring(1); + } - return [_CPCollectionKVCOperator performOperation:operator withCollection:self propertyPath:parameter]; - } - else - { - var valuesForKeySet = [CPSet set], - containedObject, - containedObjectValue, - containedObjectEnumerator = [self objectEnumerator]; + return [_CPCollectionKVCOperator performOperation:operator withCollection:self propertyPath:parameter]; + } + else + { + var valuesForKeySet = [CPSet set], + containedObject, + containedObjectValue, + containedObjectEnumerator = [self objectEnumerator]; - while ((containedObject = [containedObjectEnumerator nextObject]) != nil) - { - containedObjectValue = [containedObject valueForKeyPath:aKeyPath]; + while ((containedObject = [containedObjectEnumerator nextObject]) != nil) + { + containedObjectValue = [containedObject valueForKeyPath:aKeyPath]; - if (containedObjectValue == nil) - { - containedObjectValue = [CPNull null]; - } + if (containedObjectValue == nil) + { + containedObjectValue = [CPNull null]; + } - [valuesForKeySet addObject:containedObjectValue]; - } + [valuesForKeySet addObject:containedObjectValue]; + } - return valuesForKeySet; - } + return valuesForKeySet; + } } - (id)valueForKey:(CPString)aKey { - // If the key starts with @, it is an operator path. - // If not, it is a property path that we want applied to all members. - // In either case, valueForKeyPath: handles both scenarios correctly. - return [self valueForKeyPath:aKey]; + // If the key starts with @, it is an operator path. + // If not, it is a property path that we want applied to all members. + // In either case, valueForKeyPath: handles both scenarios correctly. + return [self valueForKeyPath:aKey]; } - (void)setValue:(id)aValue forKey:(CPString)aKey { - var containedObject, - containedObjectEnumerator = [self objectEnumerator]; + var containedObject, + containedObjectEnumerator = [self objectEnumerator]; - while ((containedObject = [containedObjectEnumerator nextObject]) != nil) - { - [containedObject setValue:aValue forKey:aKey]; - } + while ((containedObject = [containedObjectEnumerator nextObject]) != nil) + { + [containedObject setValue:aValue forKey:aKey]; + } } @end @@ -254,22 +254,22 @@ - (id)mutableSetValueForKey:(id)aKey { - return [[_CPKVCSet alloc] initWithKey:aKey forProxyObject:self]; + return [[_CPKVCSet alloc] initWithKey:aKey forProxyObject:self]; } - (id)mutableSetValueForKeyPath:(id)aKeyPath { - var dotIndex = aKeyPath.indexOf("."); + var dotIndex = aKeyPath.indexOf("."); - if (dotIndex < 0) - { - return [self mutableSetValueForKey:aKeyPath]; - } + if (dotIndex < 0) + { + return [self mutableSetValueForKey:aKeyPath]; + } - var firstPart = aKeyPath.substring(0, dotIndex), - lastPart = aKeyPath.substring(dotIndex + 1); + var firstPart = aKeyPath.substring(0, dotIndex), + lastPart = aKeyPath.substring(dotIndex + 1); - return [[self valueForKeyPath:firstPart] mutableSetValueForKeyPath:lastPart]; + return [[self valueForKeyPath:firstPart] mutableSetValueForKeyPath:lastPart]; } @end @@ -279,387 +279,387 @@ @implementation _CPKVCSet : CPMutableSet { - id _proxyObject; - id _key; + id _proxyObject; + id _key; - SEL _accessSEL; - Function _access; + SEL _accessSEL; + Function _access; - SEL _setSEL; - Function _set; + SEL _setSEL; + Function _set; - SEL _countSEL; - Function _count; + SEL _countSEL; + Function _count; - SEL _enumeratorSEL; - Function _enumerator; + SEL _enumeratorSEL; + Function _enumerator; - SEL _memberSEL; - Function _member; + SEL _memberSEL; + Function _member; - SEL _addSEL; - Function _add; + SEL _addSEL; + Function _add; - SEL _addManySEL; - Function _addMany; + SEL _addManySEL; + Function _addMany; - SEL _removeSEL; - Function _remove; + SEL _removeSEL; + Function _remove; - SEL _removeManySEL; - Function _removeMany; + SEL _removeManySEL; + Function _removeMany; - SEL _intersectSEL; - Function _intersect; + SEL _intersectSEL; + Function _intersect; } + (id)alloc { - var set = [CPMutableSet set]; + var set = [CPMutableSet set]; - set.isa = self; + set.isa = self; - var ivars = class_copyIvarList(self), - count = ivars.length; + var ivars = class_copyIvarList(self), + count = ivars.length; - while (count--) - { - set[ivar_getName(ivars[count])] = nil; - } + while (count--) + { + set[ivar_getName(ivars[count])] = nil; + } - return set; + return set; } - (id)initWithKey:(id)aKey forProxyObject:(id)anObject { - self = [super init]; + self = [super init]; - _key = aKey; - _proxyObject = anObject; + _key = aKey; + _proxyObject = anObject; - var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1); + var capitalizedKey = _key.charAt(0).toUpperCase() + _key.substring(1); - _accessSEL = sel_getName(_key); - if ([_proxyObject respondsToSelector:_accessSEL]) - { - _access = [_proxyObject methodForSelector:_accessSEL]; - } + _accessSEL = sel_getName(_key); + if ([_proxyObject respondsToSelector:_accessSEL]) + { + _access = [_proxyObject methodForSelector:_accessSEL]; + } - _setSEL = sel_getName(@"set" + capitalizedKey + ":"); - if ([_proxyObject respondsToSelector:_setSEL]) - { - _set = [_proxyObject methodForSelector:_setSEL]; - } + _setSEL = sel_getName(@"set" + capitalizedKey + ":"); + if ([_proxyObject respondsToSelector:_setSEL]) + { + _set = [_proxyObject methodForSelector:_setSEL]; + } - _countSEL = sel_getName(@"countOf" + capitalizedKey); - if ([_proxyObject respondsToSelector:_countSEL]) - { - _count = [_proxyObject methodForSelector:_countSEL]; - } + _countSEL = sel_getName(@"countOf" + capitalizedKey); + if ([_proxyObject respondsToSelector:_countSEL]) + { + _count = [_proxyObject methodForSelector:_countSEL]; + } - _enumeratorSEL = sel_getName(@"enumeratorOf" + capitalizedKey); - if ([_proxyObject respondsToSelector:_enumeratorSEL]) - { - _enumerator = [_proxyObject methodForSelector:_enumeratorSEL]; - } + _enumeratorSEL = sel_getName(@"enumeratorOf" + capitalizedKey); + if ([_proxyObject respondsToSelector:_enumeratorSEL]) + { + _enumerator = [_proxyObject methodForSelector:_enumeratorSEL]; + } - _memberSEL = sel_getName(@"memberOf" + capitalizedKey + ":"); - if ([_proxyObject respondsToSelector:_memberSEL]) - { - _member = [_proxyObject methodForSelector:_memberSEL]; - } + _memberSEL = sel_getName(@"memberOf" + capitalizedKey + ":"); + if ([_proxyObject respondsToSelector:_memberSEL]) + { + _member = [_proxyObject methodForSelector:_memberSEL]; + } - _addSEL = sel_getName(@"add" + capitalizedKey + "Object:"); - if ([_proxyObject respondsToSelector:_addSEL]) - { - _add = [_proxyObject methodForSelector:_addSEL]; - } + _addSEL = sel_getName(@"add" + capitalizedKey + "Object:"); + if ([_proxyObject respondsToSelector:_addSEL]) + { + _add = [_proxyObject methodForSelector:_addSEL]; + } - _addManySEL = sel_getName(@"add" + capitalizedKey + ":"); - if ([_proxyObject respondsToSelector:_addManySEL]) - { - _addMany = [_proxyObject methodForSelector:_addManySEL]; - } + _addManySEL = sel_getName(@"add" + capitalizedKey + ":"); + if ([_proxyObject respondsToSelector:_addManySEL]) + { + _addMany = [_proxyObject methodForSelector:_addManySEL]; + } - _removeSEL = sel_getName(@"remove" + capitalizedKey + "Object:"); - if ([_proxyObject respondsToSelector:_removeSEL]) - { - _remove = [_proxyObject methodForSelector:_removeSEL]; - } + _removeSEL = sel_getName(@"remove" + capitalizedKey + "Object:"); + if ([_proxyObject respondsToSelector:_removeSEL]) + { + _remove = [_proxyObject methodForSelector:_removeSEL]; + } - _removeManySEL = sel_getName(@"remove" + capitalizedKey + ":"); - if ([_proxyObject respondsToSelector:_removeManySEL]) - { - _removeMany = [_proxyObject methodForSelector:_removeManySEL]; - } + _removeManySEL = sel_getName(@"remove" + capitalizedKey + ":"); + if ([_proxyObject respondsToSelector:_removeManySEL]) + { + _removeMany = [_proxyObject methodForSelector:_removeManySEL]; + } - _intersectSEL = sel_getName(@"intersect" + capitalizedKey + ":"); - if ([_proxyObject respondsToSelector:_intersectSEL]) - { - _intersect = [_proxyObject methodForSelector:_intersectSEL]; - } + _intersectSEL = sel_getName(@"intersect" + capitalizedKey + ":"); + if ([_proxyObject respondsToSelector:_intersectSEL]) + { + _intersect = [_proxyObject methodForSelector:_intersectSEL]; + } - return self; + return self; } - (id)_representedObject { - if (_access) - { - return _access(_proxyObject, _accessSEL); - } + if (_access) + { + return _access(_proxyObject, _accessSEL); + } - return [_proxyObject valueForKey:_key]; + return [_proxyObject valueForKey:_key]; } - (void)_setRepresentedObject:(id)anObject { - if (_set) - { - return _set(_proxyObject, _setSEL, anObject); - } + if (_set) + { + return _set(_proxyObject, _setSEL, anObject); + } - [_proxyObject setValue:anObject forKey:_key]; + [_proxyObject setValue:anObject forKey:_key]; } - (CPUInteger)count { - if (_count) - { - return _count(_proxyObject, _countSEL); - } + if (_count) + { + return _count(_proxyObject, _countSEL); + } - return [[self _representedObject] count]; + return [[self _representedObject] count]; } - (CPEnumerator)objectEnumerator { - if (_enumerator) - { - return _enumerator(_proxyObject, _enumeratorSEL); - } + if (_enumerator) + { + return _enumerator(_proxyObject, _enumeratorSEL); + } - return [[self _representedObject] objectEnumerator]; + return [[self _representedObject] objectEnumerator]; } - (id)member:(id)anObject { - if (_member) - { - return _member(_proxyObject, _memberSEL, anObject); - } + if (_member) + { + return _member(_proxyObject, _memberSEL, anObject); + } - return [[self _representedObject] member:anObject]; + return [[self _representedObject] member:anObject]; } - (void)addObject:(id)anObject { - if (_add) - { - _add(_proxyObject, _addSEL, anObject); - } - else if (_addMany) - { - var objectSet = [CPSet setWithObject:anObject]; - _addMany(_proxyObject, _addManySEL, objectSet); - } - else - { - var target = [[self _representedObject] copy]; - [target addObject:anObject]; - [self _setRepresentedObject:target]; - } + if (_add) + { + _add(_proxyObject, _addSEL, anObject); + } + else if (_addMany) + { + var objectSet = [CPSet setWithObject:anObject]; + _addMany(_proxyObject, _addManySEL, objectSet); + } + else + { + var target = [[self _representedObject] copy]; + [target addObject:anObject]; + [self _setRepresentedObject:target]; + } } - (void)addObjectsFromArray:(CPArray)objects { - if (_addMany) - { - var objectSet = [CPSet setWithArray:objects]; - _addMany(_proxyObject, _addManySEL, objectSet); - } - else if (_add) - { - var object, - objectEnumerator = [objects objectEnumerator]; + if (_addMany) + { + var objectSet = [CPSet setWithArray:objects]; + _addMany(_proxyObject, _addManySEL, objectSet); + } + else if (_add) + { + var object, + objectEnumerator = [objects objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - _add(_proxyObject, _addSEL, object); - } - } - else - { - var target = [[self _representedObject] copy]; - [target addObjectsFromArray:objects]; - [self _setRepresentedObject:target]; - } + while ((object = [objectEnumerator nextObject]) != nil) + { + _add(_proxyObject, _addSEL, object); + } + } + else + { + var target = [[self _representedObject] copy]; + [target addObjectsFromArray:objects]; + [self _setRepresentedObject:target]; + } } - (void)unionSet:(CPSet)aSet { - if (_addMany) - { - _addMany(_proxyObject, _addManySEL, aSet); - } - else if (_add) - { - var object, - objectEnumerator = [aSet objectEnumerator]; + if (_addMany) + { + _addMany(_proxyObject, _addManySEL, aSet); + } + else if (_add) + { + var object, + objectEnumerator = [aSet objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - _add(_proxyObject, _addSEL, object); - } - } - else - { - var target = [[self _representedObject] copy]; - [target unionSet:aSet]; - [self _setRepresentedObject:target]; - } + while ((object = [objectEnumerator nextObject]) != nil) + { + _add(_proxyObject, _addSEL, object); + } + } + else + { + var target = [[self _representedObject] copy]; + [target unionSet:aSet]; + [self _setRepresentedObject:target]; + } } - (void)removeObject:(id)anObject { - if (_remove) - { - _remove(_proxyObject, _removeSEL, anObject); - } - else if (_removeMany) - { - var objectSet = [CPSet setWithObject:anObject]; - _removeMany(_proxyObject, _removeManySEL, objectSet); - } - else - { - var target = [[self _representedObject] copy]; - [target removeObject:anObject]; - [self _setRepresentedObject:target]; - } + if (_remove) + { + _remove(_proxyObject, _removeSEL, anObject); + } + else if (_removeMany) + { + var objectSet = [CPSet setWithObject:anObject]; + _removeMany(_proxyObject, _removeManySEL, objectSet); + } + else + { + var target = [[self _representedObject] copy]; + [target removeObject:anObject]; + [self _setRepresentedObject:target]; + } } - (void)minusSet:(CPSet)aSet { - if (_removeMany) - { - _removeMany(_proxyObject, _removeManySEL, aSet); - } - else if (_remove) - { - var object, - objectEnumerator = [aSet objectEnumerator]; + if (_removeMany) + { + _removeMany(_proxyObject, _removeManySEL, aSet); + } + else if (_remove) + { + var object, + objectEnumerator = [aSet objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - _remove(_proxyObject, _removeSEL, object); - } - } - else - { - var target = [[self _representedObject] copy]; - [target minusSet:aSet]; - [self _setRepresentedObject:target]; - } + while ((object = [objectEnumerator nextObject]) != nil) + { + _remove(_proxyObject, _removeSEL, object); + } + } + else + { + var target = [[self _representedObject] copy]; + [target minusSet:aSet]; + [self _setRepresentedObject:target]; + } } - (void)removeObjectsInArray:(CPArray)objects { - if (_removeMany) - { - var objectSet = [CPSet setWithArray:objects]; - _removeMany(_proxyObject, _removeManySEL, objectSet); - } - else if (_remove) - { - var object, - objectEnumerator = [objects objectEnumerator]; + if (_removeMany) + { + var objectSet = [CPSet setWithArray:objects]; + _removeMany(_proxyObject, _removeManySEL, objectSet); + } + else if (_remove) + { + var object, + objectEnumerator = [objects objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - _remove(_proxyObject, _removeSEL, object); - } - } - else - { - var target = [[self _representedObject] copy]; - [target removeObjectsInArray:objects]; - [self _setRepresentedObject:target]; - } + while ((object = [objectEnumerator nextObject]) != nil) + { + _remove(_proxyObject, _removeSEL, object); + } + } + else + { + var target = [[self _representedObject] copy]; + [target removeObjectsInArray:objects]; + [self _setRepresentedObject:target]; + } } - (void)removeAllObjects { - if (_removeMany) - { - var allObjectsSet = [[self _representedObject] copy]; - _removeMany(_proxyObject, _removeManySEL, allObjectsSet); - } - else if (_remove) - { - var object, - objectEnumerator = [[[self _representedObject] copy] objectEnumerator]; + if (_removeMany) + { + var allObjectsSet = [[self _representedObject] copy]; + _removeMany(_proxyObject, _removeManySEL, allObjectsSet); + } + else if (_remove) + { + var object, + objectEnumerator = [[[self _representedObject] copy] objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - { - _remove(_proxyObject, _removeSEL, object); - } - } - else - { - var target = [[self _representedObject] copy]; - [target removeAllObjects]; - [self _setRepresentedObject:target]; - } + while ((object = [objectEnumerator nextObject]) != nil) + { + _remove(_proxyObject, _removeSEL, object); + } + } + else + { + var target = [[self _representedObject] copy]; + [target removeAllObjects]; + [self _setRepresentedObject:target]; + } } - (void)intersectSet:(CPSet)aSet { - if (_intersect) - { - _intersect(_proxyObject, _intersectSEL, aSet); - } - else - { - var target = [[self _representedObject] copy]; - [target intersectSet:aSet]; - [self _setRepresentedObject:target]; - } + if (_intersect) + { + _intersect(_proxyObject, _intersectSEL, aSet); + } + else + { + var target = [[self _representedObject] copy]; + [target intersectSet:aSet]; + [self _setRepresentedObject:target]; + } } - (void)setSet:(CPSet)set { - [self _setRepresentedObject:set]; + [self _setRepresentedObject:set]; } - (CPArray)allObjects { - return [[self _representedObject] allObjects]; + return [[self _representedObject] allObjects]; } - (id)anyObject { - return [[self _representedObject] anyObject]; + return [[self _representedObject] anyObject]; } - (BOOL)containsObject:(id)anObject { - return [[self _representedObject] containsObject:anObject]; + return [[self _representedObject] containsObject:anObject]; } - (BOOL)intersectsSet:(CPSet)aSet { - return [[self _representedObject] intersectsSet:aSet]; + return [[self _representedObject] intersectsSet:aSet]; } - (BOOL)isEqualToSet:(CPSet)aSet { - return [[self _representedObject] isEqualToSet:aSet]; + return [[self _representedObject] isEqualToSet:aSet]; } - (id)copy { - return [[self _representedObject] copy]; + return [[self _representedObject] copy]; } @end diff --git a/Foundation/CPSet/_CPSet.j b/Foundation/CPSet/_CPSet.j index 64849c35d..065808144 100644 --- a/Foundation/CPSet/_CPSet.j +++ b/Foundation/CPSet/_CPSet.j @@ -34,263 +34,263 @@ + (id)alloc { - if (self === [CPSet class] || self === [CPMutableSet class]) - return [_CPPlaceholderSet alloc]; + if (self === [CPSet class] || self === [CPMutableSet class]) + return [_CPPlaceholderSet alloc]; - return [super alloc]; + return [super alloc]; } + (id)set { - return [[self alloc] init]; + return [[self alloc] init]; } + (id)setWithArray:(CPArray)anArray { - return [[self alloc] initWithArray:anArray]; + return [[self alloc] initWithArray:anArray]; } + (id)setWithObject:(id)anObject { - return [[self alloc] initWithObjects:anObject]; + return [[self alloc] initWithObjects:anObject]; } + (id)setWithObjects:(id)objects count:(CPUInteger)count { - return [[self alloc] initWithObjects:objects count:count]; + return [[self alloc] initWithObjects:objects count:count]; } + (id)setWithObjects:(id)anObject, ... { - var argumentsArray = Array.prototype.slice.apply(arguments); + var argumentsArray = Array.prototype.slice.apply(arguments); - argumentsArray[0] = [self alloc]; - argumentsArray[1] = @selector(initWithObjects:); + argumentsArray[0] = [self alloc]; + argumentsArray[1] = @selector(initWithObjects:); - return objj_msgSend.apply(this, argumentsArray); + return objj_msgSend.apply(this, argumentsArray); } + (id)setWithSet:(CPSet)set { - return [[self alloc] initWithSet:set]; + return [[self alloc] initWithSet:set]; } - (id)setByAddingObject:(id)anObject { - return [[self class] setWithArray:[[self allObjects] arrayByAddingObject:anObject]]; + return [[self class] setWithArray:[[self allObjects] arrayByAddingObject:anObject]]; } - (id)setByAddingObjectsFromSet:(CPSet)aSet { - return [self setByAddingObjectsFromArray:[aSet allObjects]]; + return [self setByAddingObjectsFromArray:[aSet allObjects]]; } - (id)setByAddingObjectsFromArray:(CPArray)anArray { - return [[self class] setWithArray:[[self allObjects] arrayByAddingObjectsFromArray:anArray]]; + return [[self class] setWithArray:[[self allObjects] arrayByAddingObjectsFromArray:anArray]]; } - (id)init { - return [self initWithObjects:nil count:0]; + return [self initWithObjects:nil count:0]; } - (id)initWithArray:(CPArray)anArray { - return [self initWithObjects:anArray count:[anArray count]]; + return [self initWithObjects:anArray count:[anArray count]]; } - (id)initWithObjects:(id)anObject, ... { - var index = 2, - count = arguments.length; + var index = 2, + count = arguments.length; - for (; index < count; ++index) - if (arguments[index] === nil) - break; + for (; index < count; ++index) + if (arguments[index] === nil) + break; - return [self initWithObjects:Array.prototype.slice.call(arguments, 2, index) count:index - 2]; + return [self initWithObjects:Array.prototype.slice.call(arguments, 2, index) count:index - 2]; } - (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount { - if (self === _CPSharedPlaceholderSet) - return [[_CPConcreteMutableSet alloc] initWithObjects:objects count:aCount]; + if (self === _CPSharedPlaceholderSet) + return [[_CPConcreteMutableSet alloc] initWithObjects:objects count:aCount]; - return [super init]; + return [super init]; } - (id)initWithSet:(CPSet)aSet { - return [self initWithArray:[aSet allObjects]]; + return [self initWithArray:[aSet allObjects]]; } - (id)initWithSet:(CPSet)aSet copyItems:(BOOL)shouldCopyItems { - if (shouldCopyItems) - return [aSet valueForKey:@"copy"]; + if (shouldCopyItems) + return [aSet valueForKey:@"copy"]; - return [self initWithSet:aSet]; + return [self initWithSet:aSet]; } - (CPUInteger)count { - _CPRaiseInvalidAbstractInvocation(self, _cmd); + _CPRaiseInvalidAbstractInvocation(self, _cmd); } - (CPArray)allObjects { - var objects = [], - object, - objectEnumerator = [self objectEnumerator]; + var objects = [], + object, + objectEnumerator = [self objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - objects.push(object); + while ((object = [objectEnumerator nextObject]) != nil) + objects.push(object); - return objects; + return objects; } - (id)anyObject { - return [[self objectEnumerator] nextObject]; + return [[self objectEnumerator] nextObject]; } - (BOOL)containsObject:(id)anObject { - return [self member:anObject] != nil; + return [self member:anObject] != nil; } - (CPSet)filteredSetUsingPredicate:(CPPredicate)aPredicate { - var objects = [], - object, - objectEnumerator = [self objectEnumerator]; + var objects = [], + object, + objectEnumerator = [self objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - if ([aPredicate evaluateWithObject:object]) - objects.push(object); + while ((object = [objectEnumerator nextObject]) != nil) + if ([aPredicate evaluateWithObject:object]) + objects.push(object); - return [[[self class] alloc] initWithArray:objects]; + return [[[self class] alloc] initWithArray:objects]; } - (void)makeObjectsPerformSelector:(SEL)aSelector { - [self makeObjectsPerformSelector:aSelector withObjects:nil]; + [self makeObjectsPerformSelector:aSelector withObjects:nil]; } - (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)anObject { - [self makeObjectsPerformSelector:aSelector withObjects:[anObject]]; + [self makeObjectsPerformSelector:aSelector withObjects:[anObject]]; } - (void)makeObjectsPerformSelector:(SEL)aSelector withObjects:(CPArray)objects { - var object, - objectEnumerator = [self objectEnumerator], - argumentsArray = [nil, aSelector].concat(objects || []); + var object, + objectEnumerator = [self objectEnumerator], + argumentsArray = [nil, aSelector].concat(objects || []); - while ((object = [objectEnumerator nextObject]) != nil) - { - argumentsArray[0] = object; - objj_msgSend.apply(this, argumentsArray); - } + while ((object = [objectEnumerator nextObject]) != nil) + { + argumentsArray[0] = object; + objj_msgSend.apply(this, argumentsArray); + } } - (id)member:(id)anObject { - _CPRaiseInvalidAbstractInvocation(self, _cmd); + _CPRaiseInvalidAbstractInvocation(self, _cmd); } - (CPEnumerator)objectEnumerator { - _CPRaiseInvalidAbstractInvocation(self, _cmd); + _CPRaiseInvalidAbstractInvocation(self, _cmd); } - (void)enumerateObjectsUsingBlock:(Function)aFunction { - var object, - objectEnumerator = [self objectEnumerator], - shouldStop = NO; + var object, + objectEnumerator = [self objectEnumerator], + shouldStop = NO; - while (!shouldStop && (object = [objectEnumerator nextObject]) != nil) { - if (aFunction(object, @ref(shouldStop)) !== undefined) { - throw "DEPRECATED: The method enumerateObjectsUsingBlock: does not support returning a value in the block to stop the iteration."; - } - } + while (!shouldStop && (object = [objectEnumerator nextObject]) != nil) { + if (aFunction(object, @ref(shouldStop)) !== undefined) { + throw "DEPRECATED: The method enumerateObjectsUsingBlock: does not support returning a value in the block to stop the iteration."; + } + } } - (CPSet)objectsPassingTest:(Function)aFunction { - var objects = [], - object = nil, - objectEnumerator = [self objectEnumerator]; + var objects = [], + object = nil, + objectEnumerator = [self objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - if (aFunction(object)) - objects.push(object); + while ((object = [objectEnumerator nextObject]) != nil) + if (aFunction(object)) + objects.push(object); - return [[[self class] alloc] initWithArray:objects]; + return [[[self class] alloc] initWithArray:objects]; } - (BOOL)isSubsetOfSet:(CPSet)aSet { - var object = nil, - objectEnumerator = [self objectEnumerator]; + var object = nil, + objectEnumerator = [self objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - if (![aSet containsObject:object]) - return NO; + while ((object = [objectEnumerator nextObject]) != nil) + if (![aSet containsObject:object]) + return NO; - return YES; + return YES; } - (BOOL)intersectsSet:(CPSet)aSet { - if (self === aSet) - return [self count] > 0; + if (self === aSet) + return [self count] > 0; - var object = nil, - objectEnumerator = [self objectEnumerator]; + var object = nil, + objectEnumerator = [self objectEnumerator]; - while ((object = [objectEnumerator nextObject]) != nil) - if ([aSet containsObject:object]) - return YES; + while ((object = [objectEnumerator nextObject]) != nil) + if ([aSet containsObject:object]) + return YES; - return NO; + return NO; } - (CPArray)sortedArrayUsingDescriptors:(CPArray)someSortDescriptors { - return [[self allObjects] sortedArrayUsingDescriptors:someSortDescriptors]; + return [[self allObjects] sortedArrayUsingDescriptors:someSortDescriptors]; } - (BOOL)isEqualToSet:(CPSet)aSet { - return [self isEqual:aSet]; + return [self isEqual:aSet]; } - (BOOL)isEqual:(CPSet)aSet { - return self === aSet || - [aSet isKindOfClass:[CPSet class]] && - ([self count] === [aSet count] && - [aSet isSubsetOfSet:self]); + return self === aSet || + [aSet isKindOfClass:[CPSet class]] && + ([self count] === [aSet count] && + [aSet isSubsetOfSet:self]); } - (CPString)description { - var string = "{(\n", - objects = [self allObjects], - index = 0, - count = [objects count]; + var string = "{(\n", + objects = [self allObjects], + index = 0, + count = [objects count]; - for (; index < count; ++index) - { - var object = objects[index]; - string += "\t" + String(object).split('\n').join("\n\t") + "\n"; - } + for (; index < count; ++index) + { + var object = objects[index]; + string += "\t" + String(object).split('\n').join("\n\t") + "\n"; + } - return string + ")}"; + return string + ")}"; } @end @@ -302,12 +302,12 @@ - (id)copy { - return [[self class] setWithSet:self]; + return [[self class] setWithSet:self]; } - (id)mutableCopy { - return [self copy]; + return [self copy]; } @end @@ -321,12 +321,12 @@ var CPSetObjectsKey = @"CPSetObjectsKey"; - (id)initWithCoder:(CPCoder)aCoder { - return [self initWithArray:[aCoder decodeObjectForKey:CPSetObjectsKey]]; + return [self initWithArray:[aCoder decodeObjectForKey:CPSetObjectsKey]]; } - (void)encodeWithCoder:(CPCoder)aCoder { - [aCoder encodeObject:[self allObjects] forKey:CPSetObjectsKey]; + [aCoder encodeObject:[self allObjects] forKey:CPSetObjectsKey]; } @end @@ -342,10 +342,10 @@ var _CPSharedPlaceholderSet = nil; + (id)alloc { - if (!_CPSharedPlaceholderSet) - _CPSharedPlaceholderSet = [super alloc]; + if (!_CPSharedPlaceholderSet) + _CPSharedPlaceholderSet = [super alloc]; - return _CPSharedPlaceholderSet; + return _CPSharedPlaceholderSet; } @end diff --git a/Foundation/CPTimer.j b/Foundation/CPTimer.j index 89dc8b033..e7ee64de2 100644 --- a/Foundation/CPTimer.j +++ b/Foundation/CPTimer.j @@ -36,14 +36,14 @@ const CPTimerDefaultTimeInterval = 0.1; */ @implementation CPTimer : CPObject { - CPTimeInterval _timeInterval; - CPInvocation _invocation; - Function _callback; + CPTimeInterval _timeInterval; + CPInvocation _invocation; + Function _callback; - BOOL _repeats; - BOOL _isValid; - CPDate _fireDate; - id _userInfo; + BOOL _repeats; + BOOL _isValid; + CPDate _fireDate; + id _userInfo; } /*! @@ -51,11 +51,11 @@ const CPTimerDefaultTimeInterval = 0.1; */ + (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat { - const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; + const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; - [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; - return timer; + return timer; } /*! @@ -63,11 +63,11 @@ const CPTimerDefaultTimeInterval = 0.1; */ + (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat { - const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; + const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; - [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; - return timer; + return timer; } /*! @@ -75,11 +75,11 @@ const CPTimerDefaultTimeInterval = 0.1; */ + (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat { - const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; + const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; - [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; - return timer; + return timer; } /*! @@ -87,7 +87,7 @@ const CPTimerDefaultTimeInterval = 0.1; */ + (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat { - return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; + return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; } /*! @@ -95,7 +95,7 @@ const CPTimerDefaultTimeInterval = 0.1; */ + (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat { - return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; + return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; } /*! @@ -103,7 +103,7 @@ const CPTimerDefaultTimeInterval = 0.1; */ + (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat { - return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; + return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; } /*! @@ -111,18 +111,18 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat { - self = [super init]; + self = [super init]; - if (self) - { - _timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds; - _invocation = anInvocation; - _repeats = shouldRepeat; - _isValid = YES; - _fireDate = aDate; - } + if (self) + { + _timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds; + _invocation = anInvocation; + _repeats = shouldRepeat; + _isValid = YES; + _fireDate = aDate; + } - return self; + return self; } /*! @@ -130,18 +130,18 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat { - const invocation = [CPInvocation invocationWithMethodSignature:1]; + const invocation = [CPInvocation invocationWithMethodSignature:1]; - [invocation setTarget:aTarget]; - [invocation setSelector:aSelector]; - [invocation setArgument:self atIndex:2]; + [invocation setTarget:aTarget]; + [invocation setSelector:aSelector]; + [invocation setArgument:self atIndex:2]; - self = [self initWithFireDate:aDate interval:seconds invocation:invocation repeats:shouldRepeat]; + self = [self initWithFireDate:aDate interval:seconds invocation:invocation repeats:shouldRepeat]; - if (self) - _userInfo = userInfo; + if (self) + _userInfo = userInfo; - return self; + return self; } /*! @@ -149,18 +149,18 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat { - self = [super init]; + self = [super init]; - if (self) - { - _timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds; - _callback = aFunction; - _repeats = shouldRepeat; - _isValid = YES; - _fireDate = aDate; - } + if (self) + { + _timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds; + _callback = aFunction; + _repeats = shouldRepeat; + _isValid = YES; + _fireDate = aDate; + } - return self; + return self; } /*! @@ -168,7 +168,7 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (CPTimeInterval)timeInterval { - return _timeInterval; + return _timeInterval; } /*! @@ -176,7 +176,7 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (CPDate)fireDate { - return _fireDate; + return _fireDate; } /*! @@ -184,7 +184,7 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (void)setFireDate:(CPDate)aDate { - _fireDate = aDate; + _fireDate = aDate; } /*! @@ -192,21 +192,21 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (void)fire { - if (!_isValid) - return; + if (!_isValid) + return; - if (_callback) - _callback(); - else - [_invocation invoke]; + if (_callback) + _callback(); + else + [_invocation invoke]; - if (!_isValid) - return; + if (!_isValid) + return; - if (_repeats) - _fireDate = [CPDate dateWithTimeIntervalSinceNow:_timeInterval]; - else - [self invalidate]; + if (_repeats) + _fireDate = [CPDate dateWithTimeIntervalSinceNow:_timeInterval]; + else + [self invalidate]; } /*! @@ -214,7 +214,7 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (BOOL)isValid { - return _isValid; + return _isValid; } /*! @@ -222,10 +222,10 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (void)invalidate { - _isValid = NO; - _userInfo = nil; - _invocation = nil; - _callback = nil; + _isValid = NO; + _userInfo = nil; + _invocation = nil; + _callback = nil; } /*! @@ -233,7 +233,7 @@ const CPTimerDefaultTimeInterval = 0.1; */ - (id)userInfo { - return _userInfo; + return _userInfo; } @end @@ -250,68 +250,68 @@ const CPTimersForTimeoutIDs = {}; const _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, functionArgs) { - const timeoutID = CPTimersTimeoutID++; - let theFunction = nil; + const timeoutID = CPTimersTimeoutID++; + let theFunction = nil; - if (typeof codeOrFunction === "string") - { - // FIXME: Anti-pattern: Dynamic Evaluation. Evaluating string payloads via `new Function` - // is a strict Content Security Policy (CSP) violation. - theFunction = function() - { - new Function(codeOrFunction)(); + if (typeof codeOrFunction === "string") + { + // FIXME: Anti-pattern: Dynamic Evaluation. Evaluating string payloads via `new Function` + // is a strict Content Security Policy (CSP) violation. + theFunction = function() + { + new Function(codeOrFunction)(); - if (!shouldRepeat) - delete CPTimersForTimeoutIDs[timeoutID]; - } - } - else - { - if (!functionArgs) - functionArgs = []; + if (!shouldRepeat) + delete CPTimersForTimeoutIDs[timeoutID]; + } + } + else + { + if (!functionArgs) + functionArgs = []; - theFunction = function() - { - codeOrFunction.apply(window, functionArgs); + theFunction = function() + { + codeOrFunction.apply(window, functionArgs); - if (!shouldRepeat) - delete CPTimersForTimeoutIDs[timeoutID]; - } - } + if (!shouldRepeat) + delete CPTimersForTimeoutIDs[timeoutID]; + } + } - // A call such as setTimeout(f) is technically invalid but browsers seem to treat it as setTimeout(f, 0), so so will we. - aDelay = aDelay | 0.0; + // A call such as setTimeout(f) is technically invalid but browsers seem to treat it as setTimeout(f, 0), so so will we. + aDelay = aDelay | 0.0; - CPTimersForTimeoutIDs[timeoutID] = [CPTimer scheduledTimerWithTimeInterval:aDelay / 1000 callback:theFunction repeats:shouldRepeat]; + CPTimersForTimeoutIDs[timeoutID] = [CPTimer scheduledTimerWithTimeInterval:aDelay / 1000 callback:theFunction repeats:shouldRepeat]; - return timeoutID; + return timeoutID; }; // Avoid "TypeError: Result of expression 'window' [undefined] is not an object" when running unit tests. if (typeof(window) !== 'undefined') { - window.setTimeout = function(codeOrFunction, aDelay) - { - return _CPTimerBridgeTimer(codeOrFunction, aDelay, NO, Array.prototype.slice.apply(arguments, [2])); - }; + window.setTimeout = function(codeOrFunction, aDelay) + { + return _CPTimerBridgeTimer(codeOrFunction, aDelay, NO, Array.prototype.slice.apply(arguments, [2])); + }; - window.clearTimeout = function(aTimeoutID) - { - const timer = CPTimersForTimeoutIDs[aTimeoutID]; + window.clearTimeout = function(aTimeoutID) + { + const timer = CPTimersForTimeoutIDs[aTimeoutID]; - if (timer) - [timer invalidate]; + if (timer) + [timer invalidate]; - delete CPTimersForTimeoutIDs[aTimeoutID]; - }; + delete CPTimersForTimeoutIDs[aTimeoutID]; + }; - window.setInterval = function(codeOrFunction, aDelay, functionArgs) - { - return _CPTimerBridgeTimer(codeOrFunction, aDelay, YES, Array.prototype.slice.apply(arguments, [2])); - }; + window.setInterval = function(codeOrFunction, aDelay, functionArgs) + { + return _CPTimerBridgeTimer(codeOrFunction, aDelay, YES, Array.prototype.slice.apply(arguments, [2])); + }; - window.clearInterval = function(aTimeoutID) - { - window.clearTimeout(aTimeoutID); - }; + window.clearInterval = function(aTimeoutID) + { + window.clearTimeout(aTimeoutID); + }; } diff --git a/Foundation/CPURLConnection.j b/Foundation/CPURLConnection.j index 3f86ef8d2..62fff998c 100644 --- a/Foundation/CPURLConnection.j +++ b/Foundation/CPURLConnection.j @@ -161,14 +161,14 @@ var CPURLConnectionDelegate = nil; Typical use can be like this: - - (async @action)doAction:(id)sender { - const { response, data, error } = await [CPURLConnection sendAsynchronousRequest:[CPURLRequest requestWithURL:@"http://cappuccino.dev"]]; - if (error == nil) { - //do the stuff... - } else { - // Handle errors - } - } + - (async @action)doAction:(id)sender { + const { response, data, error } = await [CPURLConnection sendAsynchronousRequest:[CPURLRequest requestWithURL:@"http://cappuccino.dev"]]; + if (error == nil) { + //do the stuff... + } else { + // Handle errors + } + } */ + (async JSObject /* { response: CPURLResponse, data: CPData, error: CPError } */)sendAsynchronousRequest:(CPURLRequest)aRequest { @@ -218,15 +218,15 @@ var CPURLConnectionDelegate = nil; - (void)_initWithRequest:(CPURLRequest)aRequest { - _request = aRequest; + _request = aRequest; _originalRequest = [aRequest copy]; - _isCanceled = NO; + _isCanceled = NO; - var URL = [_request URL], - scheme = [URL scheme]; + var URL = [_request URL], + scheme = [URL scheme]; - // Browsers use "file:", Titanium uses "app:" - _isLocalFileConnection = scheme === "file" || + // Browsers use "file:", Titanium uses "app:" + _isLocalFileConnection = scheme === "file" || ((scheme === "http" || scheme === "https") && window.location && (window.location.protocol === "file:" || window.location.protocol === "app:")); diff --git a/Tests/AppKit/CPColorPanelTest.j b/Tests/AppKit/CPColorPanelTest.j index 59ac3dfda..d245468f6 100644 --- a/Tests/AppKit/CPColorPanelTest.j +++ b/Tests/AppKit/CPColorPanelTest.j @@ -12,18 +12,18 @@ */ @implementation _MockColorPicker : CPObject { - CPArray _receivedColors; - CPView _view; + CPArray _receivedColors; + CPView _view; } - (id)init { - if (self = [super init]) - { - _receivedColors = []; - _view = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; - } - return self; + if (self = [super init]) + { + _receivedColors = []; + _view = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; + } + return self; } - (void)setColor:(CPColor)aColor @@ -33,32 +33,32 @@ - (CPArray)receivedColors { - return _receivedColors; + return _receivedColors; } - (CPView)provideNewView:(BOOL)initial { - return _view; + return _view; } @end @implementation CPColorPanelTest : OJTestCase { - CPColorPanel _panel; + CPColorPanel _panel; } - (void)setUp { - // Get shared panel and ensure it's initialized - _panel = [CPColorPanel sharedColorPanel]; - [_panel _loadContentsIfNecessary]; + // Get shared panel and ensure it's initialized + _panel = [CPColorPanel sharedColorPanel]; + [_panel _loadContentsIfNecessary]; } - (void)tearDown { - // Reset panel state between tests - [_panel setColor:[CPColor whiteColor]]; + // Reset panel state between tests + [_panel setColor:[CPColor whiteColor]]; } /* @@ -67,10 +67,10 @@ */ - (void)testSetColorUpdatesOpacitySlider { - var initialColor = [CPColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5];[_panel setColor:initialColor]; + var initialColor = [CPColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.5];[_panel setColor:initialColor]; - [self assert:0.5 equals:[_panel._opacitySlider floatValue] - message:"Opacity slider should match the color's alpha component"]; + [self assert:0.5 equals:[_panel._opacitySlider floatValue] + message:"Opacity slider should match the color's alpha component"]; } /* @@ -78,19 +78,19 @@ */ - (void)testOpacityChangeUpdatesColorAlpha { - var initialColor = [CPColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:1.0]; - [_panel setColor:initialColor];[_panel._opacitySlider setFloatValue:0.3]; - [_panel setOpacity:_panel._opacitySlider]; + var initialColor = [CPColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:1.0]; + [_panel setColor:initialColor];[_panel._opacitySlider setFloatValue:0.3]; + [_panel setOpacity:_panel._opacitySlider]; - var newColor = [_panel color]; - [self assert:0.3 equals:[newColor alphaComponent] - message:"Color's alpha should match slider value"]; + var newColor = [_panel color]; + [self assert:0.3 equals:[newColor alphaComponent] + message:"Color's alpha should match slider value"]; - // RGB components should remain unchanged - var components = [newColor components]; - [self assert:0.5 equals:components[0] message:"Red component should be unchanged"]; - [self assert:0.5 equals:components[1] message:"Green component should be unchanged"]; - [self assert:1.0 equals:components[2] message:"Blue component should be unchanged"]; + // RGB components should remain unchanged + var components = [newColor components]; + [self assert:0.5 equals:components[0] message:"Red component should be unchanged"]; + [self assert:0.5 equals:components[1] message:"Green component should be unchanged"]; + [self assert:1.0 equals:components[2] message:"Blue component should be unchanged"]; } /* @@ -99,20 +99,20 @@ */ - (void)testActivePickerNotifiedOfColorChanges { - var mockPicker = [[_MockColorPicker alloc] init]; - _panel._activePicker = mockPicker; + var mockPicker = [[_MockColorPicker alloc] init]; + _panel._activePicker = mockPicker; - var color1 = [CPColor redColor]; - var color2 = [CPColor blueColor]; + var color1 = [CPColor redColor]; + var color2 = [CPColor blueColor]; - [_panel setColor:color1]; - [_panel setColor:color2]; + [_panel setColor:color1]; + [_panel setColor:color2]; - var receivedColors = [mockPicker receivedColors]; - [self assert:2 equals:[receivedColors count] - message:"Active picker should receive setColor for each color change"]; - [self assert:color1 same:receivedColors[0]]; - [self assert:color2 same:receivedColors[1]]; + var receivedColors = [mockPicker receivedColors]; + [self assert:2 equals:[receivedColors count] + message:"Active picker should receive setColor for each color change"]; + [self assert:color1 same:receivedColors[0]]; + [self assert:color2 same:receivedColors[1]]; } /* @@ -121,21 +121,21 @@ */ - (void)testPickerNotifiedOnActivation { - var testColor = [CPColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:1.0]; - [_panel setColor:testColor]; + var testColor = [CPColor colorWithRed:0.5 green:0.5 blue:1.0 alpha:1.0]; + [_panel setColor:testColor]; - var mockPicker = [[_MockColorPicker alloc] init]; - _panel._colorPickers = [mockPicker]; + var mockPicker = [[_MockColorPicker alloc] init]; + _panel._colorPickers = [mockPicker]; - var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]; - [button setTag:0]; + var button = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]; + [button setTag:0]; - [_panel _setPicker:button]; + [_panel _setPicker:button]; - var receivedColors = [mockPicker receivedColors]; - [self assert:1 equals:[receivedColors count] - message:"Picker should receive setColor when activated"]; - [self assert:testColor same:receivedColors[0]]; + var receivedColors = [mockPicker receivedColors]; + [self assert:1 equals:[receivedColors count] + message:"Picker should receive setColor when activated"]; + [self assert:testColor same:receivedColors[0]]; } /* @@ -143,11 +143,11 @@ */ - (void)testOpacityMethodReturnsAlpha { - var testColor = [CPColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.42]; - [_panel setColor:testColor]; + var testColor = [CPColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.42]; + [_panel setColor:testColor]; - [self assert:0.42 equals:[_panel opacity] - message:"opacity method should return color's alpha component"]; + [self assert:0.42 equals:[_panel opacity] + message:"opacity method should return color's alpha component"]; } /* @@ -156,18 +156,18 @@ */ - (void)testSetColorWithEqualColorDoesNothing { - var testColor = [CPColor colorWithRed:1.0 green:0.5 blue:0.0 alpha:0.7]; - [_panel setColor:testColor]; + var testColor = [CPColor colorWithRed:1.0 green:0.5 blue:0.0 alpha:0.7]; + [_panel setColor:testColor]; - var mockPicker = [[_MockColorPicker alloc] init]; - _panel._activePicker = mockPicker; + var mockPicker = [[_MockColorPicker alloc] init]; + _panel._activePicker = mockPicker; - // Set same color again - [_panel setColor:testColor]; + // Set same color again + [_panel setColor:testColor]; - var receivedColors = [mockPicker receivedColors]; - [self assert:0 equals:[receivedColors count] - message:"Setting equal color should not trigger picker update"]; + var receivedColors = [mockPicker receivedColors]; + [self assert:0 equals:[receivedColors count] + message:"Setting equal color should not trigger picker update"]; } /* @@ -175,10 +175,10 @@ */ - (void)testColorMethodReturnsCurrentColor { - var testColor = [CPColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:0.8];[_panel setColor:testColor]; + var testColor = [CPColor colorWithRed:0.2 green:0.4 blue:0.6 alpha:0.8];[_panel setColor:testColor]; - [self assert:testColor same:[_panel color] - message:"color method should return current color"]; + [self assert:testColor same:[_panel color] + message:"color method should return current color"]; } @end diff --git a/Tests/Manual/AttachedSheet2/main.j b/Tests/Manual/AttachedSheet2/main.j index b722e04dc..9f693fa84 100644 --- a/Tests/Manual/AttachedSheet2/main.j +++ b/Tests/Manual/AttachedSheet2/main.j @@ -14,6 +14,6 @@ function main(args, namedArgs) { - CPLogRegister(CPLogDefault); + CPLogRegister(CPLogDefault); CPApplicationMain(args, namedArgs); } diff --git a/Tests/Manual/CPCursor/AppController.j b/Tests/Manual/CPCursor/AppController.j index 0e7d529b8..44c3d0654 100644 --- a/Tests/Manual/CPCursor/AppController.j +++ b/Tests/Manual/CPCursor/AppController.j @@ -62,9 +62,9 @@ var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask]; [theWindow setAcceptsMouseMovedEvents:YES]; var contentView = [theWindow contentView], - x = 20, - y = 20, - yInc = 25, + x = 20, + y = 20, + yInc = 25, imageCursorTester = [[CursorTester alloc] initWithText:@"Image cursor" origin:CGPointMake(x, y) cursor:[[CPCursor alloc] initWithImage:[[CPImage alloc] initWithContentsOfFile:[[CPBundle mainBundle] resourcePath] + @"spinner.gif"] @@ -83,7 +83,7 @@ cursor:[CPCursor pointingHandCursor]], resizeDownCursorTester = [[CursorTester alloc] initWithText:@"Resize down cursor" origin:CGPointMake(x, y+=yInc) - cursor:[CPCursor resizeDownCursor]], + cursor:[CPCursor resizeDownCursor]], resizeUpCursorTester = [[CursorTester alloc] initWithText:@"Resize up cursor" origin:CGPointMake(x, y+=yInc) cursor:[CPCursor resizeUpCursor]], @@ -119,7 +119,7 @@ cursor:[CPCursor closedHandCursor]], disappearingItemCursorTester = [[CursorTester alloc] initWithText:@"Disappearing item cursor" origin:CGPointMake(x, y+=yInc) - cursor:[CPCursor disappearingItemCursor]], + cursor:[CPCursor disappearingItemCursor]], verticalTextCursorTester = [[CursorTester alloc] initWithText:@"Vertical text cursor" origin:CGPointMake(x, y+=yInc) cursor:[CPCursor IBeamCursorForVerticalLayout]]; diff --git a/Tests/Manual/CPRuleEditorCibTest/RuleDelegate.j b/Tests/Manual/CPRuleEditorCibTest/RuleDelegate.j index b1caa79bd..6ba6ac631 100644 --- a/Tests/Manual/CPRuleEditorCibTest/RuleDelegate.j +++ b/Tests/Manual/CPRuleEditorCibTest/RuleDelegate.j @@ -106,7 +106,7 @@ var CPRuleEditorCustomControlClass = @"CPRuleEditorCustomControlClass"; { var compound_type = [criterion objectForKey:CPRuleEditorPredicateCompoundType]; if (compound_type != nil) - [predicatePartsForCriterion setObject:compound_type forKey:CPRuleEditorPredicateCompoundType]; + [predicatePartsForCriterion setObject:compound_type forKey:CPRuleEditorPredicateCompoundType]; return predicatePartsForCriterion; } @@ -131,8 +131,8 @@ var CPRuleEditorCustomControlClass = @"CPRuleEditorCustomControlClass"; predicatePart = [CPExpression expressionForConstantValue:transformedValue]; } else if ([key isEqualToString:CPRuleEditorPredicateOperatorType] || - [key isEqualToString:CPRuleEditorPredicateCustomSelector] || - [key isEqualToString:CPRuleEditorPredicateOptions]) + [key isEqualToString:CPRuleEditorPredicateCustomSelector] || + [key isEqualToString:CPRuleEditorPredicateOptions]) { var value = [criterion objectForKey:key]; if (value != nil) predicatePart = value; diff --git a/Tests/Manual/CPWebViewTest2/AppController.j b/Tests/Manual/CPWebViewTest2/AppController.j index 445a632ac..a4512d609 100644 --- a/Tests/Manual/CPWebViewTest2/AppController.j +++ b/Tests/Manual/CPWebViewTest2/AppController.j @@ -13,10 +13,10 @@ */ @implementation AppController : CPObject { - CPButton unhideHtmlButton; - CPButton unhideUrlButton; - CPWebView webview; - CPWebView webview2; + CPButton unhideHtmlButton; + CPButton unhideUrlButton; + CPWebView webview; + CPWebView webview2; } - (void)applicationDidFinishLaunching:(CPNotification)aNotification @@ -24,31 +24,31 @@ var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask], contentView = [theWindow contentView]; - unhideHtmlButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()]; - [unhideHtmlButton setTitle:"Unhide (loadHTMLString)"]; - [unhideHtmlButton sizeToFit]; - [unhideHtmlButton setTarget:self]; - [unhideHtmlButton setAction:@selector(unhideHtmlButtonClicked:)]; - [contentView addSubview:unhideHtmlButton]; + unhideHtmlButton = [[CPButton alloc] initWithFrame:CGRectMakeZero()]; + [unhideHtmlButton setTitle:"Unhide (loadHTMLString)"]; + [unhideHtmlButton sizeToFit]; + [unhideHtmlButton setTarget:self]; + [unhideHtmlButton setAction:@selector(unhideHtmlButtonClicked:)]; + [contentView addSubview:unhideHtmlButton]; - unhideUrlButton = [[CPButton alloc] initWithFrame:CGRectMake([contentView frameSize].width / 2 + 10, 0, 0, 0)]; - [unhideUrlButton setTitle:"Unhide (URL)"]; - [unhideUrlButton sizeToFit]; - [unhideUrlButton setTarget:self]; - [unhideUrlButton setAction:@selector(unhideUrlButtonClicked:)]; - [contentView addSubview:unhideUrlButton]; + unhideUrlButton = [[CPButton alloc] initWithFrame:CGRectMake([contentView frameSize].width / 2 + 10, 0, 0, 0)]; + [unhideUrlButton setTitle:"Unhide (URL)"]; + [unhideUrlButton sizeToFit]; + [unhideUrlButton setTarget:self]; + [unhideUrlButton setAction:@selector(unhideUrlButtonClicked:)]; + [contentView addSubview:unhideUrlButton]; webview = [[CPWebView alloc] initWithFrame:CGRectMake(0, 40, [contentView frameSize].width / 2 - 10, [contentView frameSize].height - 40)]; - [webview setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable]; - [webview setHidden:YES]; - [webview loadHTMLString:" Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."]; - [contentView addSubview:webview]; + [webview setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable]; + [webview setHidden:YES]; + [webview loadHTMLString:" Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."]; + [contentView addSubview:webview]; webview2 = [[CPWebView alloc] initWithFrame:CGRectMake([contentView frameSize].width / 2 + 10, 40, [contentView frameSize].width / 2 - 10, [contentView frameSize].height - 40)]; - [webview2 setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable]; - [webview2 setHidden:YES]; - [webview2 setMainFrameURL:"http://www.cappuccino-project.org"]; - [contentView addSubview:webview2]; + [webview2 setAutoresizingMask:CPViewWidthSizable|CPViewHeightSizable]; + [webview2 setHidden:YES]; + [webview2 setMainFrameURL:"http://www.cappuccino-project.org"]; + [contentView addSubview:webview2]; [theWindow orderFront:self]; @@ -58,12 +58,12 @@ - (void)unhideHtmlButtonClicked:(id)sender { - [webview setHidden:NO]; + [webview setHidden:NO]; } - (void)unhideUrlButtonClicked:(id)sender { - [webview2 setHidden:NO]; + [webview2 setHidden:NO]; } @end diff --git a/Tests/Manual/CompilationTests/TestCases/11.j b/Tests/Manual/CompilationTests/TestCases/11.j index c7900c4d9..30f7accf3 100644 --- a/Tests/Manual/CompilationTests/TestCases/11.j +++ b/Tests/Manual/CompilationTests/TestCases/11.j @@ -15,14 +15,14 @@ END_EXPECTED @implementation MySuperclass : CPObject { - int myIvar; + int myIvar; } @end @implementation MySubclass : MySuperclass { - int myIvar; + int myIvar; } @end \ No newline at end of file diff --git a/Tests/Manual/KeyWindowInTheBackgroundActivation/AppController.j b/Tests/Manual/KeyWindowInTheBackgroundActivation/AppController.j index 959fc5541..e1773de50 100644 --- a/Tests/Manual/KeyWindowInTheBackgroundActivation/AppController.j +++ b/Tests/Manual/KeyWindowInTheBackgroundActivation/AppController.j @@ -36,7 +36,7 @@ - (IBAction)bringWindow2Front:(id)sender { - [window2 orderFront:self]; + [window2 orderFront:self]; } @end diff --git a/Tests/Manual/PopUpButtonEscTest/AppController.j b/Tests/Manual/PopUpButtonEscTest/AppController.j index b9edb7a51..1abdc9c76 100755 --- a/Tests/Manual/PopUpButtonEscTest/AppController.j +++ b/Tests/Manual/PopUpButtonEscTest/AppController.j @@ -12,9 +12,9 @@ @implementation AppController : CPObject { @outlet CPWindow theWindow; - @outlet CPButton clearButton; - @outlet CPButton escButton; - @outlet CPTextField wasPressedLabel; + @outlet CPButton clearButton; + @outlet CPButton escButton; + @outlet CPTextField wasPressedLabel; } - (void)applicationDidFinishLaunching:(CPNotification)aNotification @@ -33,11 +33,11 @@ } - (@action)clearButtonPressed:(id)sender { - [wasPressedLabel setStringValue:@"No"]; + [wasPressedLabel setStringValue:@"No"]; } - (@action)escButtonPressed:(id)sender { - [wasPressedLabel setStringValue:@"Yes"]; + [wasPressedLabel setStringValue:@"Yes"]; } @end diff --git a/Tests/Manual/SheetWindowOrderFrontIssue/AppController.j b/Tests/Manual/SheetWindowOrderFrontIssue/AppController.j index 14bdf96eb..0e658b986 100644 --- a/Tests/Manual/SheetWindowOrderFrontIssue/AppController.j +++ b/Tests/Manual/SheetWindowOrderFrontIssue/AppController.j @@ -38,7 +38,7 @@ - (IBAction)closeSheet:(idf)sender { - [CPApp endSheet:sheetWindow]; + [CPApp endSheet:sheetWindow]; } @end diff --git a/Tests/Manual/SmartFoldersDemo/AppController.j b/Tests/Manual/SmartFoldersDemo/AppController.j index 810e6a8e0..1ccf3820a 100644 --- a/Tests/Manual/SmartFoldersDemo/AppController.j +++ b/Tests/Manual/SmartFoldersDemo/AppController.j @@ -63,8 +63,8 @@ var rootItems = [CPArray arrayWithObject:[CPDictionary dictionaryWithObjectsAndK - (void)_saveSmartFolders { - var defaults = [CPUserDefaults standardUserDefaults]; - [defaults setObject:smartFolders forKey:@"SmartFolders"]; + var defaults = [CPUserDefaults standardUserDefaults]; + [defaults setObject:smartFolders forKey:@"SmartFolders"]; } - (void)awakeFromCib @@ -204,7 +204,7 @@ var rootItems = [CPArray arrayWithObject:[CPDictionary dictionaryWithObjectsAndK - (IBAction)closeSheet:(id)sender { if ([sender tag] == CPOKButton) - [predicateEditor reloadPredicate]; + [predicateEditor reloadPredicate]; [CPApp endSheet:predicateSheet returnCode:[sender tag]]; } @@ -218,13 +218,13 @@ var rootItems = [CPArray arrayWithObject:[CPDictionary dictionaryWithObjectsAndK var predicate = [predicateEditor objectValue]; [self setFilterPredicate:predicate]; - [folder setObject:predicate forKey:@"predicate"]; - [folder setObject:name forKey:@"name"]; + [folder setObject:predicate forKey:@"predicate"]; + [folder setObject:name forKey:@"name"]; [folder setObject:[[tableController arrangedObjects] count] forKey:@"count"]; - [smartOutlineView reloadData]; + [smartOutlineView reloadData]; - [self saveSmartFolders]; - [aSheet orderOut:nil]; + [self saveSmartFolders]; + [aSheet orderOut:nil]; } } // convenience method diff --git a/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.j b/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.j index 3e7035be5..6a26433da 100644 --- a/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.j +++ b/Tests/Objective-J/Preprocessor/OutputTests/Messages/super-selector.j @@ -5,7 +5,7 @@ - (void)xxxx:(id)argument { - [super xxxx:argument]; + [super xxxx:argument]; } @end \ No newline at end of file From ae409d05c161633a0dc6303047721ae87472d606 Mon Sep 17 00:00:00 2001 From: David Richardson Date: Fri, 21 Aug 2026 09:21:51 -0600 Subject: [PATCH 20/35] Convert tabs to spaces --- AppKit/CoreAnimation/CALayer.j | 1344 ++++++++++++++++---------------- 1 file changed, 672 insertions(+), 672 deletions(-) diff --git a/AppKit/CoreAnimation/CALayer.j b/AppKit/CoreAnimation/CALayer.j index b23d35ed8..23bf5e7ce 100644 --- a/AppKit/CoreAnimation/CALayer.j +++ b/AppKit/CoreAnimation/CALayer.j @@ -67,60 +67,60 @@ var CALayerRegisteredRunLoopUpdates = nil; */ @implementation CALayer : CPObject { - // Modifying the Layer Geometry + // Modifying the Layer Geometry - CGRect _frame; - CGRect _bounds; - CGPoint _position; - unsigned _zPosition; - CGPoint _anchorPoint; + CGRect _frame; + CGRect _bounds; + CGPoint _position; + unsigned _zPosition; + CGPoint _anchorPoint; - CGAffineTransform _affineTransform; - CGAffineTransform _sublayerTransform; - CGAffineTransform _sublayerTransformForSublayers; + CGAffineTransform _affineTransform; + CGAffineTransform _sublayerTransform; + CGAffineTransform _sublayerTransformForSublayers; - CGRect _backingStoreFrame; - CGRect _standardBackingStoreFrame; + CGRect _backingStoreFrame; + CGRect _standardBackingStoreFrame; - BOOL _hasSublayerTransform; - BOOL _hasCustomBackingStoreFrame; + BOOL _hasSublayerTransform; + BOOL _hasCustomBackingStoreFrame; - // Style Attributes + // Style Attributes - float _opacity; - BOOL _isHidden; - BOOL _masksToBounds; - CPColor _backgroundColor; + float _opacity; + BOOL _isHidden; + BOOL _masksToBounds; + CPColor _backgroundColor; - // Managing Layer Hierarchy + // Managing Layer Hierarchy - CALayer _superlayer; - CPMutableArray _sublayers; + CALayer _superlayer; + CPMutableArray _sublayers; - // Updating Layer Display + // Updating Layer Display - unsigned _runLoopUpdateMask; - BOOL _needsDisplayOnBoundsChange; + unsigned _runLoopUpdateMask; + BOOL _needsDisplayOnBoundsChange; - // Modifying the Delegate + // Modifying the Delegate - id _delegate; + id _delegate; - BOOL _delegateRespondsToDisplayLayerSelector; - BOOL _delegateRespondsToDrawLayerInContextSelector; + BOOL _delegateRespondsToDisplayLayerSelector; + BOOL _delegateRespondsToDrawLayerInContextSelector; - // DOM Implementation + // DOM Implementation - DOMElement _DOMElement; - DOMElement _DOMContentsElement; - id _contents; - CGContext _context; - CPView _owningView; + DOMElement _DOMElement; + DOMElement _DOMContentsElement; + id _contents; + CGContext _context; + CPView _owningView; - CGAffineTransform _transformToLayer; - CGAffineTransform _transformFromLayer; + CGAffineTransform _transformToLayer; + CGAffineTransform _transformFromLayer; - CPMutableDictionary _activeAnimations; + CPMutableDictionary _activeAnimations; } @global document @@ -130,7 +130,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ + (CALayer)layer { - return [[[self class] alloc] init]; + return [[[self class] alloc] init]; } /*! @@ -138,48 +138,48 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (id)init { - self = [super init]; + self = [super init]; - if (self) - { - _frame = CGRectMakeZero(); + if (self) + { + _frame = CGRectMakeZero(); - _backingStoreFrame = CGRectMakeZero(); - _standardBackingStoreFrame = CGRectMakeZero(); + _backingStoreFrame = CGRectMakeZero(); + _standardBackingStoreFrame = CGRectMakeZero(); - _bounds = CGRectMakeZero(); - _position = CGPointMakeZero(); - _zPosition = 0.0; - _anchorPoint = CGPointMake(0.5, 0.5); - _affineTransform = CGAffineTransformMakeIdentity(); - _sublayerTransform = CGAffineTransformMakeIdentity(); + _bounds = CGRectMakeZero(); + _position = CGPointMakeZero(); + _zPosition = 0.0; + _anchorPoint = CGPointMake(0.5, 0.5); + _affineTransform = CGAffineTransformMakeIdentity(); + _sublayerTransform = CGAffineTransformMakeIdentity(); - _transformToLayer = CGAffineTransformMakeIdentity(); // FIXME? does it matter? - _transformFromLayer = CGAffineTransformMakeIdentity(); + _transformToLayer = CGAffineTransformMakeIdentity(); // FIXME? does it matter? + _transformFromLayer = CGAffineTransformMakeIdentity(); - _opacity = 1.0; - _isHidden = NO; - _masksToBounds = NO; + _opacity = 1.0; + _isHidden = NO; + _masksToBounds = NO; - _sublayers = []; + _sublayers = []; - _activeAnimations = [CPMutableDictionary dictionary]; + _activeAnimations = [CPMutableDictionary dictionary]; #if PLATFORM(DOM) - _DOMElement = document.createElement("div"); + _DOMElement = document.createElement("div"); - _DOMElement.style.overflow = "visible"; - _DOMElement.style.position = "absolute"; - _DOMElement.style.visibility = "visible"; - _DOMElement.style.top = "0px"; - _DOMElement.style.left = "0px"; - _DOMElement.style.zIndex = 0; - _DOMElement.style.width = "0px"; - _DOMElement.style.height = "0px"; + _DOMElement.style.overflow = "visible"; + _DOMElement.style.position = "absolute"; + _DOMElement.style.visibility = "visible"; + _DOMElement.style.top = "0px"; + _DOMElement.style.left = "0px"; + _DOMElement.style.zIndex = 0; + _DOMElement.style.width = "0px"; + _DOMElement.style.height = "0px"; #endif - } + } - return self; + return self; } // Modifying the Layer Geometry @@ -189,27 +189,27 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setBounds:(CGRect)aBounds { - if (CGRectEqualToRect(_bounds, aBounds)) - return; + if (CGRectEqualToRect(_bounds, aBounds)) + return; - var oldOrigin = _bounds.origin; + var oldOrigin = _bounds.origin; - _bounds = CGRectMakeCopy(aBounds); + _bounds = CGRectMakeCopy(aBounds); - if (_hasSublayerTransform) - _CALayerUpdateSublayerTransformForSublayers(self); + if (_hasSublayerTransform) + _CALayerUpdateSublayerTransformForSublayers(self); - // _hasSublayerTransform == true will handle this for us. - /*else if (!CGPointEqualToPoint(_bounds.origin, oldOrigin)) - { - var index = _sublayers.length; + // _hasSublayerTransform == true will handle this for us. + /*else if (!CGPointEqualToPoint(_bounds.origin, oldOrigin)) + { + var index = _sublayers.length; - // FIXME: This should climb the layer tree down. - while (index--) - _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryPositionMask); - }*/ + // FIXME: This should climb the layer tree down. + while (index--) + _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryPositionMask); + }*/ - _CALayerRecalculateGeometry(self, CALayerGeometryBoundsMask); + _CALayerRecalculateGeometry(self, CALayerGeometryBoundsMask); } /*! @@ -217,7 +217,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGRect)bounds { - return _bounds; + return _bounds; } /*! @@ -226,12 +226,12 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setPosition:(CGPoint)aPosition { - if (CGPointEqualToPoint(_position, aPosition)) - return; + if (CGPointEqualToPoint(_position, aPosition)) + return; - _position = CGPointMakeCopy(aPosition); + _position = CGPointMakeCopy(aPosition); - _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask); + _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask); } /*! @@ -239,7 +239,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGPoint)position { - return _position; + return _position; } /*! @@ -248,12 +248,12 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setZPosition:(int)aZPosition { - if (_zPosition == aZPosition) - return; + if (_zPosition == aZPosition) + return; - _zPosition = aZPosition; + _zPosition = aZPosition; - [self registerRunLoopUpdateWithMask:CALayerZPositionUpdateMask]; + [self registerRunLoopUpdateWithMask:CALayerZPositionUpdateMask]; } /*! @@ -262,22 +262,22 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setAnchorPoint:(CGPoint)anAnchorPoint { - anAnchorPoint = CGPointMakeCopy(anAnchorPoint); - anAnchorPoint.x = MIN(1.0, MAX(0.0, anAnchorPoint.x)); - anAnchorPoint.y = MIN(1.0, MAX(0.0, anAnchorPoint.y)); + anAnchorPoint = CGPointMakeCopy(anAnchorPoint); + anAnchorPoint.x = MIN(1.0, MAX(0.0, anAnchorPoint.x)); + anAnchorPoint.y = MIN(1.0, MAX(0.0, anAnchorPoint.y)); - if (CGPointEqualToPoint(_anchorPoint, anAnchorPoint)) - return; + if (CGPointEqualToPoint(_anchorPoint, anAnchorPoint)) + return; - _anchorPoint = anAnchorPoint; + _anchorPoint = anAnchorPoint; - if (_hasSublayerTransform) - _CALayerUpdateSublayerTransformForSublayers(self); + if (_hasSublayerTransform) + _CALayerUpdateSublayerTransformForSublayers(self); - if (_owningView) - _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); + if (_owningView) + _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); - _CALayerRecalculateGeometry(self, CALayerGeometryAnchorPointMask); + _CALayerRecalculateGeometry(self, CALayerGeometryAnchorPointMask); } /*! @@ -285,7 +285,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGPoint)anchorPoint { - return _anchorPoint; + return _anchorPoint; } /*! @@ -294,12 +294,12 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setAffineTransform:(CGAffineTransform)anAffineTransform { - if (CGAffineTransformEqualToTransform(_affineTransform, anAffineTransform)) - return; + if (CGAffineTransformEqualToTransform(_affineTransform, anAffineTransform)) + return; - _affineTransform = CGAffineTransformMakeCopy(anAffineTransform); + _affineTransform = CGAffineTransformMakeCopy(anAffineTransform); - _CALayerRecalculateGeometry(self, CALayerGeometryAffineTransformMask); + _CALayerRecalculateGeometry(self, CALayerGeometryAffineTransformMask); } /*! @@ -307,7 +307,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGAffineTransform)affineTransform { - return _affineTransform; + return _affineTransform; } /*! @@ -316,24 +316,24 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setSublayerTransform:(CGAffineTransform)anAffineTransform { - if (CGAffineTransformEqualToTransform(_sublayerTransform, anAffineTransform)) - return; + if (CGAffineTransformEqualToTransform(_sublayerTransform, anAffineTransform)) + return; - var hadSublayerTransform = _hasSublayerTransform; + var hadSublayerTransform = _hasSublayerTransform; - _sublayerTransform = CGAffineTransformMakeCopy(anAffineTransform); - _hasSublayerTransform = !CGAffineTransformIsIdentity(_sublayerTransform); + _sublayerTransform = CGAffineTransformMakeCopy(anAffineTransform); + _hasSublayerTransform = !CGAffineTransformIsIdentity(_sublayerTransform); - if (_hasSublayerTransform) - { - _CALayerUpdateSublayerTransformForSublayers(self); + if (_hasSublayerTransform) + { + _CALayerUpdateSublayerTransformForSublayers(self); - var index = _sublayers.length; + var index = _sublayers.length; - // FIXME: This should climb the layer tree down. - while (index--) - _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryParentSublayerTransformMask); - } + // FIXME: This should climb the layer tree down. + while (index--) + _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryParentSublayerTransformMask); + } } /*! @@ -341,7 +341,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGAffineTransform)sublayerTransform { - return _sublayerTransform; + return _sublayerTransform; } /* @@ -350,7 +350,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGAffineTransform)transformToLayer { - return _transformToLayer; + return _transformToLayer; } /*! @@ -360,7 +360,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setFrame:(CGRect)aFrame { - // FIXME: implement this + // FIXME: implement this } /*! @@ -372,10 +372,10 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGRect)frame { - if (!_frame) - _frame = [self convertRect:_bounds toLayer:_superlayer]; + if (!_frame) + _frame = [self convertRect:_bounds toLayer:_superlayer]; - return _frame; + return _frame; } /*! @@ -388,7 +388,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGRect)backingStoreFrame { - return _backingStoreFrame; + return _backingStoreFrame; } /*! @@ -397,33 +397,33 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setBackingStoreFrame:(CGRect)aFrame { - _hasCustomBackingStoreFrame = (aFrame != nil); + _hasCustomBackingStoreFrame = (aFrame != nil); - if (aFrame == nil) - aFrame = CGRectMakeCopy(_standardBackingStoreFrame); - else - { - if (_superlayer) - { - aFrame = [_superlayer convertRect:aFrame toLayer:nil]; + if (aFrame == nil) + aFrame = CGRectMakeCopy(_standardBackingStoreFrame); + else + { + if (_superlayer) + { + aFrame = [_superlayer convertRect:aFrame toLayer:nil]; - var bounds = [_superlayer bounds], - frame = [_superlayer convertRect:bounds toLayer:nil]; + var bounds = [_superlayer bounds], + frame = [_superlayer convertRect:bounds toLayer:nil]; - aFrame.origin.x -= CGRectGetMinX(frame); - aFrame.origin.y -= CGRectGetMinY(frame); - } - else - aFrame = CGRectMakeCopy(aFrame); - } + aFrame.origin.x -= CGRectGetMinX(frame); + aFrame.origin.y -= CGRectGetMinY(frame); + } + else + aFrame = CGRectMakeCopy(aFrame); + } - if (!CGPointEqualToPoint(_backingStoreFrame.origin, aFrame.origin)) - [self registerRunLoopUpdateWithMask:CALayerFrameOriginUpdateMask]; + if (!CGPointEqualToPoint(_backingStoreFrame.origin, aFrame.origin)) + [self registerRunLoopUpdateWithMask:CALayerFrameOriginUpdateMask]; - if (!CGSizeEqualToSize(_backingStoreFrame.size, aFrame.size)) - [self registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; + if (!CGSizeEqualToSize(_backingStoreFrame.size, aFrame.size)) + [self registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; - _backingStoreFrame = aFrame; + _backingStoreFrame = aFrame; } // Providing Layer Content @@ -433,7 +433,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGImage)contents { - return _contents; + return _contents; } /*! @@ -442,12 +442,12 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setContents:(CGImage)contents { - if (_contents == contents) - return; + if (_contents == contents) + return; - _contents = contents; + _contents = contents; - [self composite]; + [self composite]; } /* @@ -456,46 +456,46 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)composite { - // IMPROVEMENT: Added explicit parentheses for logical clarity in operator precedence. - if ((USE_BUFFER && !_contents) || !_context) - return; + // IMPROVEMENT: Added explicit parentheses for logical clarity in operator precedence. + if ((USE_BUFFER && !_contents) || !_context) + return; - CGContextClearRect(_context, CGRectMake(0.0, 0.0, CGRectGetWidth(_backingStoreFrame), CGRectGetHeight(_backingStoreFrame))); + CGContextClearRect(_context, CGRectMake(0.0, 0.0, CGRectGetWidth(_backingStoreFrame), CGRectGetHeight(_backingStoreFrame))); - // Recomposite - var transform; + // Recomposite + var transform; - if (_superlayer) - { - var superlayerTransform = _CALayerGetTransform(_superlayer, nil), - superlayerOrigin = CGPointApplyAffineTransform(_superlayer._bounds.origin, superlayerTransform); + if (_superlayer) + { + var superlayerTransform = _CALayerGetTransform(_superlayer, nil), + superlayerOrigin = CGPointApplyAffineTransform(_superlayer._bounds.origin, superlayerTransform); - transform = CGAffineTransformConcat(_transformFromLayer, superlayerTransform); + transform = CGAffineTransformConcat(_transformFromLayer, superlayerTransform); - transform.tx -= superlayerOrigin.x; - transform.ty -= superlayerOrigin.y; - } + transform.tx -= superlayerOrigin.x; + transform.ty -= superlayerOrigin.y; + } - else - // Copy so we don't affect the original. - transform = CGAffineTransformCreateCopy(_transformFromLayer); + else + // Copy so we don't affect the original. + transform = CGAffineTransformCreateCopy(_transformFromLayer); - transform.tx -= CGRectGetMinX(_backingStoreFrame); - transform.ty -= CGRectGetMinY(_backingStoreFrame); + transform.tx -= CGRectGetMinX(_backingStoreFrame); + transform.ty -= CGRectGetMinY(_backingStoreFrame); - CGContextSaveGState(_context); + CGContextSaveGState(_context); - CGContextConcatCTM(_context, transform);//_transformFromView); + CGContextConcatCTM(_context, transform);//_transformFromView); - if (USE_BUFFER) - { - // CGContextDrawImage(_context, _bounds, _contents.context); - _context.drawImage(_contents.buffer, CGRectGetMinX(_bounds), CGRectGetMinY(_bounds));//, CGRectGetWidth(_standardBackingStoreFrame), CGRectGetHeight(_standardBackingStoreFrame)); - } - else - [self drawInContext:_context]; + if (USE_BUFFER) + { + // CGContextDrawImage(_context, _bounds, _contents.context); + _context.drawImage(_contents.buffer, CGRectGetMinX(_bounds), CGRectGetMinY(_bounds));//, CGRectGetWidth(_standardBackingStoreFrame), CGRectGetHeight(_standardBackingStoreFrame)); + } + else + [self drawInContext:_context]; - CGContextRestoreGState(_context); + CGContextRestoreGState(_context); } /*! @@ -504,47 +504,47 @@ var CALayerRegisteredRunLoopUpdates = nil; - (void)display { #if PLATFORM(DOM) - if (!_context) - { - _context = CGBitmapGraphicsContextCreate(); + if (!_context) + { + _context = CGBitmapGraphicsContextCreate(); - _DOMContentsElement = _context.DOMElement; + _DOMContentsElement = _context.DOMElement; - _DOMContentsElement.style.zIndex = -100; + _DOMContentsElement.style.zIndex = -100; - _DOMContentsElement.style.overflow = "hidden"; - _DOMContentsElement.style.position = "absolute"; - _DOMContentsElement.style.visibility = "visible"; + _DOMContentsElement.style.overflow = "hidden"; + _DOMContentsElement.style.position = "absolute"; + _DOMContentsElement.style.visibility = "visible"; - _DOMContentsElement.width = ROUND(CGRectGetWidth(_backingStoreFrame)); - _DOMContentsElement.height = ROUND(CGRectGetHeight(_backingStoreFrame)); + _DOMContentsElement.width = ROUND(CGRectGetWidth(_backingStoreFrame)); + _DOMContentsElement.height = ROUND(CGRectGetHeight(_backingStoreFrame)); - _DOMContentsElement.style.top = "0px"; - _DOMContentsElement.style.left = "0px"; - _DOMContentsElement.style.width = ROUND(CGRectGetWidth(_backingStoreFrame)) + "px"; - _DOMContentsElement.style.height = ROUND(CGRectGetHeight(_backingStoreFrame)) + "px"; + _DOMContentsElement.style.top = "0px"; + _DOMContentsElement.style.left = "0px"; + _DOMContentsElement.style.width = ROUND(CGRectGetWidth(_backingStoreFrame)) + "px"; + _DOMContentsElement.style.height = ROUND(CGRectGetHeight(_backingStoreFrame)) + "px"; - _DOMElement.appendChild(_DOMContentsElement); - } + _DOMElement.appendChild(_DOMContentsElement); + } - if (USE_BUFFER) - { - if (_delegateRespondsToDisplayLayerSelector) - return [_delegate displayLayer:self]; + if (USE_BUFFER) + { + if (_delegateRespondsToDisplayLayerSelector) + return [_delegate displayLayer:self]; - if (CGRectGetWidth(_backingStoreFrame) == 0.0 || CGRectGetHeight(_backingStoreFrame) == 0.0) - return; + if (CGRectGetWidth(_backingStoreFrame) == 0.0 || CGRectGetHeight(_backingStoreFrame) == 0.0) + return; - if (!_contents) - _contents = CABackingStoreCreate(); + if (!_contents) + _contents = CABackingStoreCreate(); - CABackingStoreSetSize(_contents, _bounds.size); + CABackingStoreSetSize(_contents, _bounds.size); - [self drawInContext:CABackingStoreGetContext(_contents)]; - } + [self drawInContext:CABackingStoreGetContext(_contents)]; + } #endif - [self composite]; + [self composite]; } /*! @@ -553,14 +553,14 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)drawInContext:(CGContext)aContext { - if (_backgroundColor) - { - CGContextSetFillColor(aContext, _backgroundColor); - CGContextFillRect(aContext, _bounds); - } + if (_backgroundColor) + { + CGContextSetFillColor(aContext, _backgroundColor); + CGContextFillRect(aContext, _bounds); + } - if (_delegateRespondsToDrawLayerInContextSelector) - [_delegate drawLayer:self inContext:aContext]; + if (_delegateRespondsToDrawLayerInContextSelector) + [_delegate drawLayer:self inContext:aContext]; } @@ -571,7 +571,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (float)opacity { - return _opacity; + return _opacity; } /*! @@ -580,13 +580,13 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setOpacity:(float)anOpacity { - if (_opacity == anOpacity) - return; + if (_opacity == anOpacity) + return; - _opacity = anOpacity; + _opacity = anOpacity; - _DOMElement.style.opacity = anOpacity; - _DOMElement.style.filter = "alpha(opacity=" + anOpacity * 100 + ")"; + _DOMElement.style.opacity = anOpacity; + _DOMElement.style.filter = "alpha(opacity=" + anOpacity * 100 + ")"; } /*! @@ -595,8 +595,8 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setHidden:(BOOL)isHidden { - _isHidden = isHidden; - _DOMElement.style.display = isHidden ? "none" : "block"; + _isHidden = isHidden; + _DOMElement.style.display = isHidden ? "none" : "block"; } /*! @@ -604,7 +604,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (BOOL)hidden { - return _isHidden; + return _isHidden; } /*! @@ -612,7 +612,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (BOOL)isHidden { - return _isHidden; + return _isHidden; } /*! @@ -621,11 +621,11 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setMasksToBounds:(BOOL)masksToBounds { - if (_masksToBounds == masksToBounds) - return; + if (_masksToBounds == masksToBounds) + return; - _masksToBounds = masksToBounds; - _DOMElement.style.overflow = _masksToBounds ? "hidden" : "visible"; + _masksToBounds = masksToBounds; + _DOMElement.style.overflow = _masksToBounds ? "hidden" : "visible"; } /*! @@ -634,9 +634,9 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setBackgroundColor:(CPColor)aColor { - _backgroundColor = aColor; + _backgroundColor = aColor; - [self setNeedsDisplay]; + [self setNeedsDisplay]; } /*! @@ -644,7 +644,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CPColor)backgroundColor { - return _backgroundColor; + return _backgroundColor; } // Managing Layer Hierarchy @@ -653,7 +653,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CPArray)sublayers { - return _sublayers; + return _sublayers; } /*! @@ -661,7 +661,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CALayer)superlayer { - return _superlayer; + return _superlayer; } // IMPROVEMENT: Removed ADJUST_CONTENTS_ZINDEX macro in favor of inline replacement to eliminate preprocessor dependency. @@ -671,7 +671,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)addSublayer:(CALayer)aLayer { - [self insertSublayer:aLayer atIndex:_sublayers.length]; + [self insertSublayer:aLayer atIndex:_sublayers.length]; } /*! @@ -679,16 +679,16 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)removeFromSuperlayer { - if (_owningView) - [_owningView setLayer:nil]; + if (_owningView) + [_owningView setLayer:nil]; - if (!_superlayer) - return; + if (!_superlayer) + return; - _superlayer._DOMElement.removeChild(_DOMElement); - [_superlayer._sublayers removeObject:self]; + _superlayer._DOMElement.removeChild(_DOMElement); + [_superlayer._sublayers removeObject:self]; - _superlayer = nil; + _superlayer = nil; } /*! @@ -698,45 +698,45 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)insertSublayer:(CALayer)aLayer atIndex:(CPUInteger)anIndex { - if (!aLayer) - return; + if (!aLayer) + return; - var superlayer = [aLayer superlayer]; + var superlayer = [aLayer superlayer]; - if (superlayer == self) - { - var index = [_sublayers indexOfObjectIdenticalTo:aLayer]; + if (superlayer == self) + { + var index = [_sublayers indexOfObjectIdenticalTo:aLayer]; - if (index == anIndex) - return; + if (index == anIndex) + return; - [_sublayers removeObjectAtIndex:index]; + [_sublayers removeObjectAtIndex:index]; - if (index < anIndex) - --anIndex; - } - else if (superlayer != nil) - [aLayer removeFromSuperlayer]; + if (index < anIndex) + --anIndex; + } + else if (superlayer != nil) + [aLayer removeFromSuperlayer]; - // IMPROVEMENT: Inlined ADJUST_CONTENTS_ZINDEX logic. - if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex) - _DOMContentsElement.style.zIndex -= 100.0; + // IMPROVEMENT: Inlined ADJUST_CONTENTS_ZINDEX logic. + if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex) + _DOMContentsElement.style.zIndex -= 100.0; - [_sublayers insertObject:aLayer atIndex:anIndex]; + [_sublayers insertObject:aLayer atIndex:anIndex]; #if PLATFORM(DOM) - if (anIndex >= _sublayers.length - 1) - // IMPROVEMENT: Inlined DOM element property access. - _DOMElement.appendChild(aLayer._DOMElement); - else - // IMPROVEMENT: Inlined DOM element property access. - _DOMElement.insertBefore(aLayer._DOMElement, _sublayers[anIndex + 1]._DOMElement); + if (anIndex >= _sublayers.length - 1) + // IMPROVEMENT: Inlined DOM element property access. + _DOMElement.appendChild(aLayer._DOMElement); + else + // IMPROVEMENT: Inlined DOM element property access. + _DOMElement.insertBefore(aLayer._DOMElement, _sublayers[anIndex + 1]._DOMElement); #endif - aLayer._superlayer = self; + aLayer._superlayer = self; - if (self != superlayer) - _CALayerRecalculateGeometry(aLayer, 0xFFFFFFF); + if (self != superlayer) + _CALayerRecalculateGeometry(aLayer, 0xFFFFFFF); } /*! @@ -747,9 +747,9 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)insertSublayer:(CALayer)aLayer below:(CALayer)aSublayer { - var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : 0; + var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : 0; - [self insertSublayer:aLayer atIndex:index == CPNotFound ? _sublayers.length : index]; + [self insertSublayer:aLayer atIndex:index == CPNotFound ? _sublayers.length : index]; } /*! @@ -760,12 +760,12 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)insertSublayer:(CALayer)aLayer above:(CALayer)aSublayer { - var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : _sublayers.length; + var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : _sublayers.length; - if (index == CPNotFound) - [CPException raise:"CALayerNotFoundException" reason:"aSublayer is not a sublayer of this layer"]; + if (index == CPNotFound) + [CPException raise:"CALayerNotFoundException" reason:"aSublayer is not a sublayer of this layer"]; - [_sublayers insertObject:aLayer atIndex:index == CPNotFound ? _sublayers.length : index + 1]; + [_sublayers insertObject:aLayer atIndex:index == CPNotFound ? _sublayers.length : index + 1]; } /*! @@ -775,23 +775,23 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)replaceSublayer:(CALayer)aSublayer with:(CALayer)aLayer { - if (aSublayer == aLayer) - return; + if (aSublayer == aLayer) + return; - if (aSublayer._superlayer != self) - { - CPLog.warn("Attempt to replace a sublayer (%s) which is not in the sublayers of the receiver (%s).", [aSublayer description], [self description]); - return; - } + if (aSublayer._superlayer != self) + { + CPLog.warn("Attempt to replace a sublayer (%s) which is not in the sublayers of the receiver (%s).", [aSublayer description], [self description]); + return; + } - // IMPROVEMENT: Inlined ADJUST_CONTENTS_ZINDEX logic. - if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex) - _DOMContentsElement.style.zIndex -= 100.0; + // IMPROVEMENT: Inlined ADJUST_CONTENTS_ZINDEX logic. + if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex) + _DOMContentsElement.style.zIndex -= 100.0; - [_sublayers replaceObjectAtIndex:[_sublayers indexOfObjectIdenticalTo:aSublayer] withObject:aLayer]; + [_sublayers replaceObjectAtIndex:[_sublayers indexOfObjectIdenticalTo:aSublayer] withObject:aLayer]; - // IMPROVEMENT: Inlined DOM element property access. - _DOMElement.replaceChild(aLayer._DOMElement, aSublayer._DOMElement); + // IMPROVEMENT: Inlined DOM element property access. + _DOMElement.replaceChild(aLayer._DOMElement, aSublayer._DOMElement); } // Updating Layer Display @@ -801,24 +801,24 @@ var CALayerRegisteredRunLoopUpdates = nil; */ + (void)runLoopUpdateLayers { - for (UID in CALayerRegisteredRunLoopUpdates) - { - var layer = CALayerRegisteredRunLoopUpdates[UID], - mask = layer._runLoopUpdateMask; + for (UID in CALayerRegisteredRunLoopUpdates) + { + var layer = CALayerRegisteredRunLoopUpdates[UID], + mask = layer._runLoopUpdateMask; - if (mask & CALayerDOMUpdateMask) - _CALayerUpdateDOM(layer, mask); + if (mask & CALayerDOMUpdateMask) + _CALayerUpdateDOM(layer, mask); - if (mask & CALayerDisplayUpdateMask) - [layer display]; - else if (mask & CALayerFrameSizeUpdateMask || mask & CALayerCompositeUpdateMask) - [layer composite]; + if (mask & CALayerDisplayUpdateMask) + [layer display]; + else if (mask & CALayerFrameSizeUpdateMask || mask & CALayerCompositeUpdateMask) + [layer composite]; - layer._runLoopUpdateMask = 0; - } + layer._runLoopUpdateMask = 0; + } - window.loop = false; - CALayerRegisteredRunLoopUpdates = nil; + window.loop = false; + CALayerRegisteredRunLoopUpdates = nil; } /* @@ -826,16 +826,16 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)registerRunLoopUpdateWithMask:(unsigned)anUpdateMask { - if (CALayerRegisteredRunLoopUpdates == nil) - { - CALayerRegisteredRunLoopUpdates = {}; + if (CALayerRegisteredRunLoopUpdates == nil) + { + CALayerRegisteredRunLoopUpdates = {}; - [[CPRunLoop currentRunLoop] performSelector:@selector(runLoopUpdateLayers) - target:CALayer argument:nil order:0 modes:[CPDefaultRunLoopMode]]; - } + [[CPRunLoop currentRunLoop] performSelector:@selector(runLoopUpdateLayers) + target:CALayer argument:nil order:0 modes:[CPDefaultRunLoopMode]]; + } - _runLoopUpdateMask |= anUpdateMask; - CALayerRegisteredRunLoopUpdates[[self UID]] = self; + _runLoopUpdateMask |= anUpdateMask; + CALayerRegisteredRunLoopUpdates[[self UID]] = self; } /* @@ -843,7 +843,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setNeedsComposite { - [self registerRunLoopUpdateWithMask:CALayerCompositeUpdateMask]; + [self registerRunLoopUpdateWithMask:CALayerCompositeUpdateMask]; } /*! @@ -851,7 +851,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setNeedsDisplay { - [self registerRunLoopUpdateWithMask:CALayerDisplayUpdateMask]; + [self registerRunLoopUpdateWithMask:CALayerDisplayUpdateMask]; } /*! @@ -860,7 +860,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setNeedsDisplayOnBoundsChange:(BOOL)needsDisplayOnBoundsChange { - _needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; + _needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; } /*! @@ -868,7 +868,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (BOOL)needsDisplayOnBoundsChange { - return _needsDisplayOnBoundsChange; + return _needsDisplayOnBoundsChange; } /*! @@ -877,8 +877,8 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setNeedsDisplayInRect:(CGRect)aRect { - // _dirtyRect = aRect; - [self display]; + // _dirtyRect = aRect; + [self display]; } // Mapping Between Coordinate and Time Spaces @@ -890,7 +890,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGPoint)convertPoint:(CGPoint)aPoint fromLayer:(CALayer)aLayer { - return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(aLayer, self)); + return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(aLayer, self)); } /*! @@ -901,7 +901,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGPoint)convertPoint:(CGPoint)aPoint toLayer:(CALayer)aLayer { - return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(self, aLayer)); + return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(self, aLayer)); } /*! @@ -912,7 +912,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGRect)convertRect:(CGRect)aRect fromLayer:(CALayer)aLayer { - return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(aLayer, self)); + return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(aLayer, self)); } /*! @@ -923,7 +923,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CGRect)convertRect:(CGRect)aRect toLayer:(CALayer)aLayer { - return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(self, aLayer)); + return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(self, aLayer)); } // Hit Testing @@ -933,7 +933,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (BOOL)containsPoint:(CGPoint)aPoint { - return CGRectContainsPoint(_bounds, aPoint); + return CGRectContainsPoint(_bounds, aPoint); } /*! @@ -943,23 +943,23 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (CALayer)hitTest:(CGPoint)aPoint { - if (_isHidden) - return nil; + if (_isHidden) + return nil; - var point = CGPointApplyAffineTransform(aPoint, _transformToLayer); + var point = CGPointApplyAffineTransform(aPoint, _transformToLayer); - if (!CGRectContainsPoint(_bounds, point)) - return nil; + if (!CGRectContainsPoint(_bounds, point)) + return nil; - var layer = nil, - index = _sublayers.length; + var layer = nil, + index = _sublayers.length; - // FIXME: this should take into account zPosition. - while (index--) - if (layer = [_sublayers[index] hitTest:point]) - return layer; + // FIXME: this should take into account zPosition. + while (index--) + if (layer = [_sublayers[index] hitTest:point]) + return layer; - return self; + return self; } // Modifying the Delegate @@ -969,16 +969,16 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)setDelegate:(id)aDelegate { - if (_delegate == aDelegate) - return; + if (_delegate == aDelegate) + return; - _delegate = aDelegate; + _delegate = aDelegate; - _delegateRespondsToDisplayLayerSelector = [_delegate respondsToSelector:@selector(displayLayer:)]; - _delegateRespondsToDrawLayerInContextSelector = [_delegate respondsToSelector:@selector(drawLayer:inContext:)]; + _delegateRespondsToDisplayLayerSelector = [_delegate respondsToSelector:@selector(displayLayer:)]; + _delegateRespondsToDrawLayerInContextSelector = [_delegate respondsToSelector:@selector(drawLayer:inContext:)]; - if (_delegateRespondsToDisplayLayerSelector || _delegateRespondsToDrawLayerInContextSelector) - [self setNeedsDisplay]; + if (_delegateRespondsToDisplayLayerSelector || _delegateRespondsToDrawLayerInContextSelector) + [self setNeedsDisplay]; } /*! @@ -986,7 +986,7 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (id)delegate { - return _delegate; + return _delegate; } /* @@ -997,114 +997,114 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (void)addAnimation:(CAAnimation)anim forKey:(CPString)key { - if (!anim) return; + if (!anim) return; - // --- 1. Handle Animation Groups --- - // If it's a group, we simply schedule its children individually. - if ([anim respondsToSelector:@selector(animations)] && [anim animations]) - { - var animations = [anim animations], - count = [animations count], - i = 0; + // --- 1. Handle Animation Groups --- + // If it's a group, we simply schedule its children individually. + if ([anim respondsToSelector:@selector(animations)] && [anim animations]) + { + var animations = [anim animations], + count = [animations count], + i = 0; - for (; i < count; i++) - { - var child = [animations objectAtIndex:i]; + for (; i < count; i++) + { + var child = [animations objectAtIndex:i]; - // Recurse: Add the child animation. - // We pass 'nil' for the key so the child's own 'keyPath' - // is used as the storage identifier in the dictionary. - [self addAnimation:child forKey:nil]; - } - return; - } + // Recurse: Add the child animation. + // We pass 'nil' for the key so the child's own 'keyPath' + // is used as the storage identifier in the dictionary. + [self addAnimation:child forKey:nil]; + } + return; + } - // --- 2. Determine KeyPath --- - var keyPath = key; + // --- 2. Determine KeyPath --- + var keyPath = key; - // If the animation object has an explicit keyPath (like CABasicAnimation), use it. - if ([anim respondsToSelector:@selector(keyPath)] && [anim keyPath]) - keyPath = [anim keyPath]; + // If the animation object has an explicit keyPath (like CABasicAnimation), use it. + if ([anim respondsToSelector:@selector(keyPath)] && [anim keyPath]) + keyPath = [anim keyPath]; - // If we can't determine a property to animate, we must abort. - if (!keyPath) return; + // If we can't determine a property to animate, we must abort. + if (!keyPath) return; - // --- 3. Determine Values --- - var startValue = ([anim respondsToSelector:@selector(fromValue)]) ? [anim fromValue] : nil; + // --- 3. Determine Values --- + var startValue = ([anim respondsToSelector:@selector(fromValue)]) ? [anim fromValue] : nil; - // If startValue is missing, try to read it from the layer. - // We wrap this in a try-catch to prevent crashes if 'keyPath' is invalid. - if (startValue == nil) - { - try { - startValue = [[self delegate] valueForKey:keyPath]; - } - catch (e) { - // The keyPath was likely invalid (not KVC compliant), abort. - return; - } - } + // If startValue is missing, try to read it from the layer. + // We wrap this in a try-catch to prevent crashes if 'keyPath' is invalid. + if (startValue == nil) + { + try { + startValue = [[self delegate] valueForKey:keyPath]; + } + catch (e) { + // The keyPath was likely invalid (not KVC compliant), abort. + return; + } + } - var endValue = ([anim respondsToSelector:@selector(toValue)]) ? [anim toValue] : nil; + var endValue = ([anim respondsToSelector:@selector(toValue)]) ? [anim toValue] : nil; - if (endValue == nil) - return; + if (endValue == nil) + return; - var duration = ([anim respondsToSelector:@selector(duration)]) ? [anim duration] : 0.25; + var duration = ([anim respondsToSelector:@selector(duration)]) ? [anim duration] : 0.25; - // Default to EaseInEaseOut if not specified - var timingFunction = ([anim respondsToSelector:@selector(timingFunction)]) ? [anim timingFunction] : [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; + // Default to EaseInEaseOut if not specified + var timingFunction = ([anim respondsToSelector:@selector(timingFunction)]) ? [anim timingFunction] : [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; - // --- 4. Prepare Context --- - var context = { - "animation": anim, - "keyPath": keyPath, - "startValue": startValue, - "endValue": endValue, - "duration": duration * 1000.0, // ms - "timingFunction": timingFunction, - "startTime": null, - "requestId": null - }; + // --- 4. Prepare Context --- + var context = { + "animation": anim, + "keyPath": keyPath, + "startValue": startValue, + "endValue": endValue, + "duration": duration * 1000.0, // ms + "timingFunction": timingFunction, + "startTime": null, + "requestId": null + }; - // --- 5. Render Loop --- - var _self = self; + // --- 5. Render Loop --- + var _self = self; - var renderLoop = function(timestamp) { - if ([_self _renderAnimationStep:context timestamp:timestamp]) - context.requestId = window.requestAnimationFrame(renderLoop); - else - context.requestId = null; - }; + var renderLoop = function(timestamp) { + if ([_self _renderAnimationStep:context timestamp:timestamp]) + context.requestId = window.requestAnimationFrame(renderLoop); + else + context.requestId = null; + }; - // --- 6. Storage & Kickoff --- - // Use the keyPath as the identifier if no specific key was provided - var storageKey = (key && key.length > 0) ? key : keyPath; + // --- 6. Storage & Kickoff --- + // Use the keyPath as the identifier if no specific key was provided + var storageKey = (key && key.length > 0) ? key : keyPath; - // Remove any conflicting animation on this specific property/key - [self removeAnimationForKey:storageKey]; + // Remove any conflicting animation on this specific property/key + [self removeAnimationForKey:storageKey]; - context.requestId = window.requestAnimationFrame(renderLoop); - [_activeAnimations setObject:context forKey:storageKey]; + context.requestId = window.requestAnimationFrame(renderLoop); + [_activeAnimations setObject:context forKey:storageKey]; } - (void)removeAnimationForKey:(CPString)key { - var context = [_activeAnimations objectForKey:key]; - if (context) - { - if (context.requestId !== null) - window.cancelAnimationFrame(context.requestId); - [_activeAnimations removeObjectForKey:key]; - } + var context = [_activeAnimations objectForKey:key]; + if (context) + { + if (context.requestId !== null) + window.cancelAnimationFrame(context.requestId); + [_activeAnimations removeObjectForKey:key]; + } } - (void)removeAllAnimations { - var keys = [_activeAnimations allKeys], - count = [keys count]; - while (count--) - [self removeAnimationForKey:[keys objectAtIndex:count]]; + var keys = [_activeAnimations allKeys], + count = [keys count]; + while (count--) + [self removeAnimationForKey:[keys objectAtIndex:count]]; } /* @@ -1114,352 +1114,352 @@ var CALayerRegisteredRunLoopUpdates = nil; */ - (float)_solveBezier:(float)t forTimingFunction:(CAMediaTimingFunction)tf { - if (!tf) return t; + if (!tf) return t; - // Linear optimization - if (tf === [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]) - return t; + // Linear optimization + if (tf === [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]) + return t; - var points = [tf controlPoints]; // [c1x, c1y, c2x, c2y] - var p1x = points[0], p1y = points[1], - p2x = points[2], p2y = points[3]; + var points = [tf controlPoints]; // [c1x, c1y, c2x, c2y] + var p1x = points[0], p1y = points[1], + p2x = points[2], p2y = points[3]; - // Simple polynomial evaluation (De Casteljau's algorithm/Cubic formula subset) - // Since we are usually dealing with standard easing, we can approximate 1D easing on the Y axis - // based on linear time X, or do a full solve. - // For brevity/speed in JS, we often approximate basic easing: + // Simple polynomial evaluation (De Casteljau's algorithm/Cubic formula subset) + // Since we are usually dealing with standard easing, we can approximate 1D easing on the Y axis + // based on linear time X, or do a full solve. + // For brevity/speed in JS, we often approximate basic easing: - // 3t^2 * (1-t) + t^3 ... standard bezier blending functions - var cx = 3.0 * p1x; - var bx = 3.0 * (p2x - p1x) - cx; - var ax = 1.0 - cx - bx; + // 3t^2 * (1-t) + t^3 ... standard bezier blending functions + var cx = 3.0 * p1x; + var bx = 3.0 * (p2x - p1x) - cx; + var ax = 1.0 - cx - bx; - var cy = 3.0 * p1y; - var by = 3.0 * (p2y - p1y) - cy; - var ay = 1.0 - cy - by; + var cy = 3.0 * p1y; + var by = 3.0 * (p2y - p1y) - cy; + var ay = 1.0 - cy - by; - // Solve for X given t (time) using Newton-Raphson - var sampleT = t; - for (var i = 0; i < 5; i++) { - var x = ((ax * sampleT + bx) * sampleT + cx) * sampleT - t; - if (Math.abs(x) < 1e-3) break; - var d = (3.0 * ax * sampleT + 2.0 * bx) * sampleT + cx; - if (Math.abs(d) < 1e-6) break; - sampleT = sampleT - x / d; - } + // Solve for X given t (time) using Newton-Raphson + var sampleT = t; + for (var i = 0; i < 5; i++) { + var x = ((ax * sampleT + bx) * sampleT + cx) * sampleT - t; + if (Math.abs(x) < 1e-3) break; + var d = (3.0 * ax * sampleT + 2.0 * bx) * sampleT + cx; + if (Math.abs(d) < 1e-6) break; + sampleT = sampleT - x / d; + } - // Solve for Y given derived T - return ((ay * sampleT + by) * sampleT + cy) * sampleT; + // Solve for Y given derived T + return ((ay * sampleT + by) * sampleT + cy) * sampleT; } - (BOOL)_renderAnimationStep:(JSObject)context timestamp:(double)timestamp { - if (context.startTime === null) - context.startTime = timestamp; + if (context.startTime === null) + context.startTime = timestamp; - var elapsed = timestamp - context.startTime, - linearProgress = elapsed / context.duration; + var elapsed = timestamp - context.startTime, + linearProgress = elapsed / context.duration; - if (linearProgress > 1.0) linearProgress = 1.0; + if (linearProgress > 1.0) linearProgress = 1.0; - // Apply Timing Function - var progress = [self _solveBezier:linearProgress forTimingFunction:context.timingFunction]; + // Apply Timing Function + var progress = [self _solveBezier:linearProgress forTimingFunction:context.timingFunction]; - var start = context.startValue, - end = context.endValue, - current = nil; + var start = context.startValue, + end = context.endValue, + current = nil; - // Number - if (typeof start === "number") - { - current = start + (end - start) * progress; - } - // Point / Size / Rect - else if (start && start.x !== undefined && start.y !== undefined) // CGPoint - { - current = CGPointMake(start.x + (end.x - start.x) * progress, - start.y + (end.y - start.y) * progress); - } - else if (start && start.width !== undefined && start.height !== undefined) // CGSize - { - current = CGSizeMake(start.width + (end.width - start.width) * progress, - start.height + (end.height - start.height) * progress); - } - else if (start && start.origin !== undefined && start.size !== undefined) // CGRect - { - current = CGRectMake( - start.origin.x + (end.origin.x - start.origin.x) * progress, - start.origin.y + (end.origin.y - start.origin.y) * progress, - start.size.width + (end.size.width - start.size.width) * progress, - start.size.height + (end.size.height - start.size.height) * progress - ); - } + // Number + if (typeof start === "number") + { + current = start + (end - start) * progress; + } + // Point / Size / Rect + else if (start && start.x !== undefined && start.y !== undefined) // CGPoint + { + current = CGPointMake(start.x + (end.x - start.x) * progress, + start.y + (end.y - start.y) * progress); + } + else if (start && start.width !== undefined && start.height !== undefined) // CGSize + { + current = CGSizeMake(start.width + (end.width - start.width) * progress, + start.height + (end.height - start.height) * progress); + } + else if (start && start.origin !== undefined && start.size !== undefined) // CGRect + { + current = CGRectMake( + start.origin.x + (end.origin.x - start.origin.x) * progress, + start.origin.y + (end.origin.y - start.origin.y) * progress, + start.size.width + (end.size.width - start.size.width) * progress, + start.size.height + (end.size.height - start.size.height) * progress + ); + } - if (current !== nil) - [[self delegate] setValue:current forKey:context.keyPath]; + if (current !== nil) + [[self delegate] setValue:current forKey:context.keyPath]; - if (linearProgress >= 1.0) - { - var anim = context.animation; + if (linearProgress >= 1.0) + { + var anim = context.animation; - // Cleanup - var shouldRemove = [anim respondsToSelector:@selector(isRemovedOnCompletion)] ? [anim isRemovedOnCompletion] : YES; + // Cleanup + var shouldRemove = [anim respondsToSelector:@selector(isRemovedOnCompletion)] ? [anim isRemovedOnCompletion] : YES; - if (shouldRemove) { - // Find key by context identity to handle groups correctly - var keys = [_activeAnimations allKeys]; - for (var i = 0; i < keys.length; i++) { - if ([_activeAnimations objectForKey:keys[i]] === context) { - [_activeAnimations removeObjectForKey:keys[i]]; - break; - } - } - } + if (shouldRemove) { + // Find key by context identity to handle groups correctly + var keys = [_activeAnimations allKeys]; + for (var i = 0; i < keys.length; i++) { + if ([_activeAnimations objectForKey:keys[i]] === context) { + [_activeAnimations removeObjectForKey:keys[i]]; + break; + } + } + } - // Delegate - var delegate = [anim delegate]; - if (delegate && [delegate respondsToSelector:@selector(animationDidStop:finished:)]) - [delegate animationDidStop:anim finished:YES]; + // Delegate + var delegate = [anim delegate]; + if (delegate && [delegate respondsToSelector:@selector(animationDidStop:finished:)]) + [delegate animationDidStop:anim finished:YES]; - return NO; - } + return NO; + } - return YES; + return YES; } /* @ignore */ - (void)_setOwningView:(CPView)anOwningView { - _owningView = anOwningView; + _owningView = anOwningView; - if (_owningView) - { - _owningView = anOwningView; + if (_owningView) + { + _owningView = anOwningView; - _bounds.size = CGSizeMakeCopy([_owningView bounds].size); - _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); - } + _bounds.size = CGSizeMakeCopy([_owningView bounds].size); + _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); + } - _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask | CALayerGeometryBoundsMask); + _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask | CALayerGeometryBoundsMask); } /* @ignore */ - (void)_owningViewBoundsChanged { - _bounds.size = CGSizeMakeCopy([_owningView bounds].size); - _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); + _bounds.size = CGSizeMakeCopy([_owningView bounds].size); + _position = CGPointMake(CGRectGetWidth(_bounds) * _anchorPoint.x, CGRectGetHeight(_bounds) * _anchorPoint.y); - _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask | CALayerGeometryBoundsMask); + _CALayerRecalculateGeometry(self, CALayerGeometryPositionMask | CALayerGeometryBoundsMask); } /* @ignore */ - (void)_update { - window.loop = true; + window.loop = true; - var mask = _runLoopUpdateMask; + var mask = _runLoopUpdateMask; - if (mask & CALayerDOMUpdateMask) - _CALayerUpdateDOM(self, mask); + if (mask & CALayerDOMUpdateMask) + _CALayerUpdateDOM(self, mask); - if (mask & CALayerDisplayUpdateMask) - [self display]; + if (mask & CALayerDisplayUpdateMask) + [self display]; - else if (mask & CALayerFrameSizeUpdateMask || mask & CALayerCompositeUpdateMask) - [self composite]; + else if (mask & CALayerFrameSizeUpdateMask || mask & CALayerCompositeUpdateMask) + [self composite]; - _runLoopUpdateMask = 0; + _runLoopUpdateMask = 0; - window.loop = false; + window.loop = false; } @end function _CALayerUpdateSublayerTransformForSublayers(aLayer) { - var bounds = aLayer._bounds, - anchorPoint = aLayer._anchorPoint, - translateX = CGRectGetWidth(bounds) * anchorPoint.x, - translateY = CGRectGetHeight(bounds) * anchorPoint.y; + var bounds = aLayer._bounds, + anchorPoint = aLayer._anchorPoint, + translateX = CGRectGetWidth(bounds) * anchorPoint.x, + translateY = CGRectGetHeight(bounds) * anchorPoint.y; - aLayer._sublayerTransformForSublayers = CGAffineTransformConcat( - CGAffineTransformMakeTranslation(-translateX, -translateY), - CGAffineTransformConcat(aLayer._sublayerTransform, - CGAffineTransformMakeTranslation(translateX, translateY))); + aLayer._sublayerTransformForSublayers = CGAffineTransformConcat( + CGAffineTransformMakeTranslation(-translateX, -translateY), + CGAffineTransformConcat(aLayer._sublayerTransform, + CGAffineTransformMakeTranslation(translateX, translateY))); } function _CALayerUpdateDOM(aLayer, aMask) { #if PLATFORM(DOM) - var DOMElementStyle = aLayer._DOMElement.style; + var DOMElementStyle = aLayer._DOMElement.style; - if (aMask & CALayerZPositionUpdateMask) - DOMElementStyle.zIndex = aLayer._zPosition; + if (aMask & CALayerZPositionUpdateMask) + DOMElementStyle.zIndex = aLayer._zPosition; - var frame = aLayer._backingStoreFrame; + var frame = aLayer._backingStoreFrame; - if (aMask & CALayerFrameOriginUpdateMask) - { - DOMElementStyle.top = ROUND(CGRectGetMinY(frame)) + "px"; - DOMElementStyle.left = ROUND(CGRectGetMinX(frame)) + "px"; - } + if (aMask & CALayerFrameOriginUpdateMask) + { + DOMElementStyle.top = ROUND(CGRectGetMinY(frame)) + "px"; + DOMElementStyle.left = ROUND(CGRectGetMinX(frame)) + "px"; + } - if (aMask & CALayerFrameSizeUpdateMask) - { - var width = MAX(0.0, ROUND(CGRectGetWidth(frame))), - height = MAX(0.0, ROUND(CGRectGetHeight(frame))), - DOMContentsElement = aLayer._DOMContentsElement; + if (aMask & CALayerFrameSizeUpdateMask) + { + var width = MAX(0.0, ROUND(CGRectGetWidth(frame))), + height = MAX(0.0, ROUND(CGRectGetHeight(frame))), + DOMContentsElement = aLayer._DOMContentsElement; - DOMElementStyle.width = width + "px"; - DOMElementStyle.height = height + "px"; + DOMElementStyle.width = width + "px"; + DOMElementStyle.height = height + "px"; - if (DOMContentsElement) - { - DOMContentsElement.width = width; - DOMContentsElement.height = height; - DOMContentsElement.style.width = width + "px"; - DOMContentsElement.style.height = height + "px"; - } - } + if (DOMContentsElement) + { + DOMContentsElement.width = width; + DOMContentsElement.height = height; + DOMContentsElement.style.width = width + "px"; + DOMContentsElement.style.height = height + "px"; + } + } #endif } function _CALayerRecalculateGeometry(aLayer, aGeometryChange) { - var bounds = aLayer._bounds, - superlayer = aLayer._superlayer, - width = CGRectGetWidth(bounds), - height = CGRectGetHeight(bounds), - position = aLayer._position, - anchorPoint = aLayer._anchorPoint, - affineTransform = aLayer._affineTransform, - backingStoreFrameSize = CGSizeMakeCopy(aLayer._backingStoreFrame), - hasCustomBackingStoreFrame = aLayer._hasCustomBackingStoreFrame; + var bounds = aLayer._bounds, + superlayer = aLayer._superlayer, + width = CGRectGetWidth(bounds), + height = CGRectGetHeight(bounds), + position = aLayer._position, + anchorPoint = aLayer._anchorPoint, + affineTransform = aLayer._affineTransform, + backingStoreFrameSize = CGSizeMakeCopy(aLayer._backingStoreFrame), + hasCustomBackingStoreFrame = aLayer._hasCustomBackingStoreFrame; - // Go to anchor, transform, go back to bounds. - aLayer._transformFromLayer = CGAffineTransformConcat( - CGAffineTransformMakeTranslation(-width * anchorPoint.x - CGRectGetMinX(aLayer._bounds), -height * anchorPoint.y - CGRectGetMinY(aLayer._bounds)), - CGAffineTransformConcat(affineTransform, - CGAffineTransformMakeTranslation(position.x, position.y))); + // Go to anchor, transform, go back to bounds. + aLayer._transformFromLayer = CGAffineTransformConcat( + CGAffineTransformMakeTranslation(-width * anchorPoint.x - CGRectGetMinX(aLayer._bounds), -height * anchorPoint.y - CGRectGetMinY(aLayer._bounds)), + CGAffineTransformConcat(affineTransform, + CGAffineTransformMakeTranslation(position.x, position.y))); - if (superlayer && superlayer._hasSublayerTransform) - { - // aLayer._transformFromLayer = CGAffineTransformConcat(aLayer._transformFromLayer, superlayer._sublayerTransformForSublayers); - CGAffineTransformConcatTo(aLayer._transformFromLayer, superlayer._sublayerTransformForSublayers, aLayer._transformFromLayer); - } + if (superlayer && superlayer._hasSublayerTransform) + { + // aLayer._transformFromLayer = CGAffineTransformConcat(aLayer._transformFromLayer, superlayer._sublayerTransformForSublayers); + CGAffineTransformConcatTo(aLayer._transformFromLayer, superlayer._sublayerTransformForSublayers, aLayer._transformFromLayer); + } - aLayer._transformToLayer = CGAffineTransformInvert(aLayer._transformFromLayer); + aLayer._transformToLayer = CGAffineTransformInvert(aLayer._transformFromLayer); - //aLayer._transformFromLayer.tx = ROUND(aLayer._transformFromLayer.tx); - //aLayer._transformFromLayer.ty = ROUND(aLayer._transformFromLayer.ty); + //aLayer._transformFromLayer.tx = ROUND(aLayer._transformFromLayer.tx); + //aLayer._transformFromLayer.ty = ROUND(aLayer._transformFromLayer.ty); - aLayer._frame = nil; - aLayer._standardBackingStoreFrame = [aLayer convertRect:bounds toLayer:nil]; + aLayer._frame = nil; + aLayer._standardBackingStoreFrame = [aLayer convertRect:bounds toLayer:nil]; - if (superlayer) - { - // IMPROVEMENT: Renamed local 'bounds' to 'superlayerBounds' to prevent shadowing of outer 'bounds' declaration. - var superlayerBounds = [superlayer bounds], - frame = [superlayer convertRect:superlayerBounds toLayer:nil]; + if (superlayer) + { + // IMPROVEMENT: Renamed local 'bounds' to 'superlayerBounds' to prevent shadowing of outer 'bounds' declaration. + var superlayerBounds = [superlayer bounds], + frame = [superlayer convertRect:superlayerBounds toLayer:nil]; - aLayer._standardBackingStoreFrame.origin.x -= CGRectGetMinX(frame); - aLayer._standardBackingStoreFrame.origin.y -= CGRectGetMinY(frame); - } + aLayer._standardBackingStoreFrame.origin.x -= CGRectGetMinX(frame); + aLayer._standardBackingStoreFrame.origin.y -= CGRectGetMinY(frame); + } - // We used to use CGRectIntegral here, but what we actually want, is the largest integral - // rect that would ever contain this box, since for any width/height, there are 2 (4) - // possible integral rects for it depending on it's position. It's OK that this is sometimes - // bigger than the "optimal" bounding integral rect since that doesn't change drawing. + // We used to use CGRectIntegral here, but what we actually want, is the largest integral + // rect that would ever contain this box, since for any width/height, there are 2 (4) + // possible integral rects for it depending on it's position. It's OK that this is sometimes + // bigger than the "optimal" bounding integral rect since that doesn't change drawing. - var origin = aLayer._standardBackingStoreFrame.origin, - size = aLayer._standardBackingStoreFrame.size; + var origin = aLayer._standardBackingStoreFrame.origin, + size = aLayer._standardBackingStoreFrame.size; - origin.x = FLOOR(origin.x); - origin.y = FLOOR(origin.y); - size.width = CEIL(size.width) + 1.0; - size.height = CEIL(size.height) + 1.0; + origin.x = FLOOR(origin.x); + origin.y = FLOOR(origin.y); + size.width = CEIL(size.width) + 1.0; + size.height = CEIL(size.height) + 1.0; - // FIXME: This avoids the central issue that a position change is sometimes a display and sometimes - // a div move, and sometimes both. + // FIXME: This avoids the central issue that a position change is sometimes a display and sometimes + // a div move, and sometimes both. - // Only use this frame if we don't currently have a custom backing store frame. - if (!hasCustomBackingStoreFrame) - { - var backingStoreFrame = CGRectMakeCopy(aLayer._standardBackingStoreFrame); + // Only use this frame if we don't currently have a custom backing store frame. + if (!hasCustomBackingStoreFrame) + { + var backingStoreFrame = CGRectMakeCopy(aLayer._standardBackingStoreFrame); - // These values get rounded in the DOM, so don't both updating them if they're - // not going to be different after rounding. - if (ROUND(CGRectGetMinX(backingStoreFrame)) != ROUND(CGRectGetMinX(aLayer._backingStoreFrame)) || - ROUND(CGRectGetMinY(backingStoreFrame)) != ROUND(CGRectGetMinY(aLayer._backingStoreFrame))) - [aLayer registerRunLoopUpdateWithMask:CALayerFrameOriginUpdateMask]; + // These values get rounded in the DOM, so don't both updating them if they're + // not going to be different after rounding. + if (ROUND(CGRectGetMinX(backingStoreFrame)) != ROUND(CGRectGetMinX(aLayer._backingStoreFrame)) || + ROUND(CGRectGetMinY(backingStoreFrame)) != ROUND(CGRectGetMinY(aLayer._backingStoreFrame))) + [aLayer registerRunLoopUpdateWithMask:CALayerFrameOriginUpdateMask]; - // Any change in size due to a geometry change is purely due to rounding error. - if ((CGRectGetWidth(backingStoreFrame) != ROUND(CGRectGetWidth(aLayer._backingStoreFrame)) || - CGRectGetHeight(backingStoreFrame) != ROUND(CGRectGetHeight(aLayer._backingStoreFrame)))) - [aLayer registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; + // Any change in size due to a geometry change is purely due to rounding error. + if ((CGRectGetWidth(backingStoreFrame) != ROUND(CGRectGetWidth(aLayer._backingStoreFrame)) || + CGRectGetHeight(backingStoreFrame) != ROUND(CGRectGetHeight(aLayer._backingStoreFrame)))) + [aLayer registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; - aLayer._backingStoreFrame = backingStoreFrame; - } + aLayer._backingStoreFrame = backingStoreFrame; + } - if (aGeometryChange & CALayerGeometryBoundsMask && aLayer._needsDisplayOnBoundsChange) - [aLayer setNeedsDisplay]; - // We need to recompose if we have a custom backing store frame, OR - // If the change is not solely composed of position and anchor points changes. - // Anchor point and position changes simply move the object, requiring - // no re-rendering. - else if (hasCustomBackingStoreFrame || (aGeometryChange & ~(CALayerGeometryPositionMask | CALayerGeometryAnchorPointMask))) - [aLayer setNeedsComposite]; + if (aGeometryChange & CALayerGeometryBoundsMask && aLayer._needsDisplayOnBoundsChange) + [aLayer setNeedsDisplay]; + // We need to recompose if we have a custom backing store frame, OR + // If the change is not solely composed of position and anchor points changes. + // Anchor point and position changes simply move the object, requiring + // no re-rendering. + else if (hasCustomBackingStoreFrame || (aGeometryChange & ~(CALayerGeometryPositionMask | CALayerGeometryAnchorPointMask))) + [aLayer setNeedsComposite]; - var sublayers = aLayer._sublayers, - index = 0, - count = sublayers.length; + var sublayers = aLayer._sublayers, + index = 0, + count = sublayers.length; - for (; index < count; ++index) - _CALayerRecalculateGeometry(sublayers[index], aGeometryChange); + for (; index < count; ++index) + _CALayerRecalculateGeometry(sublayers[index], aGeometryChange); } function _CALayerGetTransform(fromLayer, toLayer) { - var transform = CGAffineTransformMakeIdentity(); + var transform = CGAffineTransformMakeIdentity(); - if (fromLayer) - { - var layer = fromLayer; + if (fromLayer) + { + var layer = fromLayer; - // If we have a fromLayer, "climb up" the layer tree until - // we hit the root node or we hit the toLayer. - while (layer && layer != toLayer) - { - var transformFromLayer = layer._transformFromLayer; + // If we have a fromLayer, "climb up" the layer tree until + // we hit the root node or we hit the toLayer. + while (layer && layer != toLayer) + { + var transformFromLayer = layer._transformFromLayer; - //transform = CGAffineTransformConcat(transform, layer._transformFromLayer); - CGAffineTransformConcatTo(transform, transformFromLayer, transform); + //transform = CGAffineTransformConcat(transform, layer._transformFromLayer); + CGAffineTransformConcatTo(transform, transformFromLayer, transform); - layer = layer._superlayer; - } + layer = layer._superlayer; + } - // If we hit toLayer, then we're done. - if (layer == toLayer) - return transform; - } + // If we hit toLayer, then we're done. + if (layer == toLayer) + return transform; + } - var layers = [], - layer = toLayer; + var layers = [], + layer = toLayer; - while (layer) - { - layers.push(layer); - layer = layer._superlayer; - } + while (layer) + { + layers.push(layer); + layer = layer._superlayer; + } - var index = layers.length; + var index = layers.length; - while (index--) - { - var transformToLayer = layers[index]._transformToLayer; + while (index--) + { + var transformToLayer = layers[index]._transformToLayer; - CGAffineTransformConcatTo(transform, transformToLayer, transform); - } + CGAffineTransformConcatTo(transform, transformToLayer, transform); + } - return transform; + return transform; } From 0a6f2e569aaac35b1205e83e0c19f93935945ab6 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 25 Aug 2026 20:35:09 +0200 Subject: [PATCH 21/35] fixed: fix selection bindings and correct KVO dependencies in CPTreeController --- AppKit/CPTreeController.j | 133 ++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 69 deletions(-) diff --git a/AppKit/CPTreeController.j b/AppKit/CPTreeController.j index 550d1cca7..6982a580a 100644 --- a/AppKit/CPTreeController.j +++ b/AppKit/CPTreeController.j @@ -50,6 +50,9 @@ [self exposeBinding:@"contentArray"]; [self exposeBinding:@"sortDescriptors"]; + [self exposeBinding:@"selectionIndexPaths"]; + [self exposeBinding:@"selectionIndexPath"]; + [self exposeBinding:@"selectedObjects"]; } + (CPSet)keyPathsForValuesAffectingContentArray @@ -59,7 +62,7 @@ + (CPSet)keyPathsForValuesAffectingArrangedObjects { - return [CPSet setWithObjects:@"content", @"sortDescriptors", @"childrenKeyPath"]; + return [CPSet setWithObjects:@"content", @"contentArray", @"sortDescriptors", @"childrenKeyPath"]; } + (CPSet)keyPathsForValuesAffectingSelectionIndexPath @@ -69,27 +72,27 @@ + (CPSet)keyPathsForValuesAffectingSelectedObjects { - return [CPSet setWithObjects:@"selectionIndexPaths"]; + return [CPSet setWithObjects:@"selectionIndexPaths", @"arrangedObjects"]; } + (CPSet)keyPathsForValuesAffectingSelectedNodes { - return [CPSet setWithObjects:@"selectionIndexPaths"]; -} - -+ (CPSet)keyPathsForValuesAffectingCanAddChild -{ - return [CPSet setWithObjects:@"selectionIndexPaths"]; + return [CPSet setWithObjects:@"selectionIndexPaths", @"arrangedObjects"]; } + (CPSet)keyPathsForValuesAffectingCanInsert { - return [CPSet setWithObjects:@"selectionIndexPaths"]; + return [CPSet setWithObjects:@"editable"]; } + (CPSet)keyPathsForValuesAffectingCanInsertChild { - return [CPSet setWithObjects:@"selectionIndexPaths"]; + return [CPSet setWithObjects:@"selectionIndexPaths", @"editable"]; +} + ++ (CPSet)keyPathsForValuesAffectingCanAddChild +{ + return [CPSet setWithObjects:@"selectionIndexPaths", @"editable"]; } - (id)init @@ -115,7 +118,8 @@ } - (void)prepareContent -{[self _setContentArray:[CPArray arrayWithObject:[self newObject]]]; +{ + [self _setContentArray:[CPArray arrayWithObject:[self newObject]]]; } - (BOOL)preservesSelection { return _preservesSelection; } @@ -144,7 +148,8 @@ - (void)setChildrenKeyPath:(CPString)aKeyPath { if (_childrenKeyPath === aKeyPath) return; - _childrenKeyPath = aKeyPath;[self rearrangeObjects]; + _childrenKeyPath = aKeyPath; + [self rearrangeObjects]; } - (CPString)countKeyPath { return _countKeyPath; } @@ -167,7 +172,7 @@ value = [CPArray arrayWithObject:value]; var oldSelectedObjects = nil, - oldSelectionIndexPaths = nil; + oldSelectionIndexPaths = nil; if ([self preservesSelection]) oldSelectedObjects = [self selectedObjects]; @@ -184,7 +189,7 @@ [self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection]; } -- (void)_setContentArray:(id)anArray {[self setContent:anArray]; } +- (void)_setContentArray:(id)anArray { [self setContent:anArray]; } - (id)contentArray { return _contentObject; } - (id)arrangedObjects { return _arrangedObjects; } @@ -198,7 +203,7 @@ - (void)_rearrangeObjects { var oldSelectedObjects = nil, - oldSelectionIndexPaths = nil; + oldSelectionIndexPaths = nil; if ([self preservesSelection]) oldSelectedObjects = [self selectedObjects]; @@ -216,7 +221,7 @@ - (void)__rebuildArrangedObjectsTree { var rootNode = [[CPTreeNode alloc] initWithRepresentedObject:nil], - contentArray = [self contentArray]; + contentArray = [self contentArray]; if (contentArray && [contentArray count] > 0) { @@ -244,7 +249,7 @@ for (var i = 0; i < count; i++) { var obj = [sortedObjects objectAtIndex:i], - node = [[CPTreeNode alloc] initWithRepresentedObject:obj]; + node = [[CPTreeNode alloc] initWithRepresentedObject:obj]; if (_childrenKeyPath) { @@ -278,11 +283,7 @@ - (BOOL)setSelectionIndexPaths:(CPArray)indexPaths { - [self _selectionWillChange]; - var result = [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO]; - [self _selectionDidChange]; - - return result; + return [self __setSelectionIndexPaths:indexPaths avoidEmpty:NO]; } - (void)_ensureTreeNodesExistForIndexPaths:(CPArray)indexPaths @@ -334,7 +335,8 @@ if (needsRebuild) { [childNodes removeAllObjects]; - var newNodes = [self _buildTreeNodesForObjects:expectedChildObjects];[childNodes addObjectsFromArray:newNodes]; + var newNodes = [self _buildTreeNodesForObjects:expectedChildObjects]; + [childNodes addObjectsFromArray:newNodes]; } } @@ -366,21 +368,13 @@ if ([_selectionIndexPaths isEqualToArray:newPaths]) return NO; + [self _selectionWillChange]; [self willChangeValueForKey:@"selectionIndexPaths"]; _selectionIndexPaths = [newPaths copy]; - var binderClass = [[self class] _binderClassForBinding:@"selectionIndexPaths"]; - - if (binderClass) - { - var binding = [binderClass getBinding:@"selectionIndexPaths" forObject:self]; - - if (binding) - [binding reverseSetValueFor:@"selectionIndexPaths"]; - } - [self didChangeValueForKey:@"selectionIndexPaths"]; + [self _selectionDidChange]; return YES; } @@ -388,9 +382,7 @@ - (BOOL)addSelectionIndexPaths:(CPArray)indexPaths { var newPaths = [_selectionIndexPaths mutableCopy]; - [newPaths addObjectsFromArray:indexPaths]; - return [self setSelectionIndexPaths:newPaths]; } @@ -404,7 +396,7 @@ - (CPArray)selectedNodes { var nodes = [CPMutableArray array], - count = [_selectionIndexPaths count]; + count = [_selectionIndexPaths count]; for (var i = 0; i < count; i++) { @@ -418,8 +410,8 @@ - (CPArray)selectedObjects { var objects = [CPMutableArray array], - nodes = [self selectedNodes], - count = [nodes count]; + nodes = [self selectedNodes], + count = [nodes count]; for (var i = 0; i < count; i++) [objects addObject:[[nodes objectAtIndex:i] representedObject]]; @@ -462,22 +454,22 @@ } - (BOOL)canInsert { return [self isEditable]; } -- (BOOL)canInsertChild { return [self isEditable] &&[_selectionIndexPaths count] > 0; } +- (BOOL)canInsertChild { return [self isEditable] && [_selectionIndexPaths count] > 0; } - (BOOL)canAddChild { return [self canInsertChild]; } - (void)add:(id)sender { if (![self canInsert]) return; - var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject], - selectionPath = [self selectionIndexPath]; + var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject], + selectionPath = [self selectionIndexPath]; if (!selectionPath) selectionPath = [CPIndexPath indexPathWithIndex:[[[self arrangedObjects] childNodes] count]]; var length = [selectionPath length], - lastIndex = [selectionPath indexAtPosition:length - 1], - insertPath = [selectionPath indexPathByRemovingLastIndex]; + lastIndex = [selectionPath indexAtPosition:length - 1], + insertPath = [selectionPath indexPathByRemovingLastIndex]; insertPath = [insertPath indexPathByAddingIndex:lastIndex + 1]; @@ -489,10 +481,10 @@ if (![self canAddChild]) return; - var newObject = [self automaticallyPreparesContent] ?[self newObject] : [self _defaultNewObject], - parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]], - childCount = [[parentNode childNodes] count], - insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount]; + var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject], + parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:[self selectionIndexPath]], + childCount = [[parentNode childNodes] count], + insertPath = [[self selectionIndexPath] indexPathByAddingIndex:childCount]; [self insertObject:newObject atArrangedObjectIndexPath:insertPath]; } @@ -501,8 +493,8 @@ { if (![self canInsert]) return; - var newObject = [self automaticallyPreparesContent] ? [self newObject] :[self _defaultNewObject], - indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0]; + var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject], + indexPath = [self selectionIndexPath] || [CPIndexPath indexPathWithIndex:0]; [self insertObject:newObject atArrangedObjectIndexPath:indexPath]; } @@ -512,7 +504,7 @@ if (![self canInsertChild]) return; var newObject = [self automaticallyPreparesContent] ? [self newObject] : [self _defaultNewObject], - insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0]; + insertPath = [[self selectionIndexPath] indexPathByAddingIndex:0]; [self insertObject:newObject atArrangedObjectIndexPath:insertPath]; } @@ -531,21 +523,22 @@ for (var i = 0; i < count; i++) { var object = [objects objectAtIndex:i], - path = [indexPaths objectAtIndex:i], - length = [path length]; + path = [indexPaths objectAtIndex:i], + length = [path length]; if (length === 1) - {[_contentObject insertObject:object atIndex:[path indexAtPosition:0]]; + { + [_contentObject insertObject:object atIndex:[path indexAtPosition:0]]; } else { var parentPath = [path indexPathByRemovingLastIndex], - parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath]; + parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath]; if (parentNode) { var parentObj = [parentNode representedObject], - childIndex = [path indexAtPosition:length - 1]; + childIndex = [path indexAtPosition:length - 1]; var children = [parentObj valueForKeyPath:_childrenKeyPath]; if (!children) @@ -568,7 +561,8 @@ _disableSetContent = NO; [self _rearrangeObjects]; - if ([self selectsInsertedObjects])[self setSelectionIndexPaths:indexPaths]; + if ([self selectsInsertedObjects]) + [self setSelectionIndexPaths:indexPaths]; [self didChangeValueForKey:@"content"]; } @@ -589,12 +583,12 @@ _disableSetContent = YES; var sortedPaths = [indexPaths sortedArrayUsingSelector:@selector(compare:)], - count = [sortedPaths count]; + count = [sortedPaths count]; for (var i = count - 1; i >= 0; i--) { var path = [sortedPaths objectAtIndex:i], - length = [path length]; + length = [path length]; if (length === 1) { @@ -603,15 +597,15 @@ else { var parentPath = [path indexPathByRemovingLastIndex], - parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath]; + parentNode = [[self arrangedObjects] descendantNodeAtIndexPath:parentPath]; if (parentNode) { var parentObj = [parentNode representedObject], - childIndex = [path indexAtPosition:length - 1], - mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath]; + childIndex = [path indexAtPosition:length - 1], + mutableChildren = [parentObj mutableArrayValueForKeyPath:_childrenKeyPath]; - if (mutableChildren && childIndex <[mutableChildren count]) + if (mutableChildren && childIndex < [mutableChildren count]) [mutableChildren removeObjectAtIndex:childIndex]; } } @@ -632,18 +626,19 @@ } - (void)moveNodes:(CPArray)nodes toIndexPath:(CPIndexPath)startingIndexPath -{[CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."]; +{ + [CPException raise:CPUnsupportedMethodException reason:@"moveNodes:toIndexPath: is not yet implemented in CPTreeController."]; } @end var CPTreeControllerAvoidsEmptySelection = @"CPTreeControllerAvoidsEmptySelection", -CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection", -CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects", -CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker", -CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath", -CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath", -CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath"; + CPTreeControllerPreservesSelection = @"CPTreeControllerPreservesSelection", + CPTreeControllerSelectsInsertedObjects = @"CPTreeControllerSelectsInsertedObjects", + CPTreeControllerAlwaysUsesMultipleValuesMarker = @"CPTreeControllerAlwaysUsesMultipleValuesMarker", + CPTreeControllerChildrenKeyPath = @"CPTreeControllerChildrenKeyPath", + CPTreeControllerCountKeyPath = @"CPTreeControllerCountKeyPath", + CPTreeControllerLeafKeyPath = @"CPTreeControllerLeafKeyPath"; @implementation CPTreeController (CPCoding) From 6ef3cf776fe83d0af4bfb87b71a052b98ff07843 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 25 Aug 2026 20:39:34 +0200 Subject: [PATCH 22/35] new: selection tests --- Tests/AppKit/CPTreeControllerTest.j | 151 ++++++++++++++++++++++++++-- 1 file changed, 143 insertions(+), 8 deletions(-) diff --git a/Tests/AppKit/CPTreeControllerTest.j b/Tests/AppKit/CPTreeControllerTest.j index 9487e4e46..18b7fe3cb 100644 --- a/Tests/AppKit/CPTreeControllerTest.j +++ b/Tests/AppKit/CPTreeControllerTest.j @@ -17,22 +17,21 @@ CPTreeController _treeController @accessors(property=treeController); CPArray _contentArray @accessors(property=contentArray); - CPArray observations; - int aCount @accessors; + CPMutableArray _observedKeyPaths; } - (CPArray)makeTestTree { var engineering = [OrgNode nodeWithName:@"Engineering"], - marketing = [OrgNode nodeWithName:@"Marketing"]; + marketing = [OrgNode nodeWithName:@"Marketing"]; var webTeam = [OrgNode nodeWithName:@"Web Team"], - backendTeam = [OrgNode nodeWithName:@"Backend Team"]; + backendTeam = [OrgNode nodeWithName:@"Backend Team"]; [engineering setChildren:[CPMutableArray arrayWithObjects:webTeam, backendTeam]]; var dev1 = [OrgNode nodeWithName:@"Francisco"], - dev2 = [OrgNode nodeWithName:@"Ross"]; + dev2 = [OrgNode nodeWithName:@"Ross"]; [webTeam setChildren:[CPMutableArray arrayWithObjects:dev1, dev2]]; @@ -43,12 +42,23 @@ { [[CPApplication alloc] init]; + _observedKeyPaths = [CPMutableArray array]; _contentArray = [self makeTestTree]; _treeController = [[CPTreeController alloc] init]; [_treeController setChildrenKeyPath:@"children"]; [_treeController setContent:[_contentArray copy]]; } +- (void)tearDown +{ + _observedKeyPaths = nil; +} + +- (void)observeValueForKeyPath:(CPString)aKeyPath ofObject:(id)anObject change:(CPDictionary)aChange context:(id)aContext +{ + [_observedKeyPaths addObject:aKeyPath]; +} + - (void)testInitWithContent { [self assert:[_contentArray count] equals:[[_treeController contentArray] count]]; @@ -99,7 +109,7 @@ [controller insertObject:newDev atArrangedObjectIndexPath:insertPath]; var engineering = [[controller contentArray] objectAtIndex:0], - backendTeam = [[engineering children] objectAtIndex:1]; + backendTeam = [[engineering children] objectAtIndex:1]; [self assert:1 equals:[[backendTeam children] count] message:@"Child should be added to the model object's children array"]; [self assert:@"Tom" equals:[[[backendTeam children] objectAtIndex:0] name]]; } @@ -132,7 +142,7 @@ [controller removeObjectAtArrangedObjectIndexPath:path]; var engineering = [[controller contentArray] objectAtIndex:0], - webTeam = [[engineering children] objectAtIndex:0]; + webTeam = [[engineering children] objectAtIndex:0]; [self assert:1 equals:[[webTeam children] count] message:@"Francisco should be removed, leaving only Ross"]; [self assert:@"Ross" equals:[[[webTeam children] objectAtIndex:0] name]]; @@ -176,7 +186,8 @@ // "Marketing" is now at index 0 [self assert:[CPIndexPath indexPathWithIndex:0] equals:[controller selectionIndexPath]]; - // Test behavior when AvoidsEmptySelection is NO[controller insertObject:[OrgNode nodeWithName:@"New Dept"] atArrangedObjectIndexPath:[CPIndexPath indexPathWithIndex:1]]; + // Test behavior when AvoidsEmptySelection is NO + [controller insertObject:[OrgNode nodeWithName:@"New Dept"] atArrangedObjectIndexPath:[CPIndexPath indexPathWithIndex:1]]; [controller setAvoidsEmptySelection:NO]; // Reselect "Marketing" at index 0 @@ -229,6 +240,130 @@ [self assert:@"Marketing" equals:[[selectedObjects objectAtIndex:0] name]]; } +/* + * New Tests for Selection Bindings, KVO, and UI Action Status + */ + +- (void)testExposedBindings +{ + var exposedBindings = [CPTreeController exposedBindings]; + + [self assertTrue:[exposedBindings containsObject:@"contentArray"] message:@"contentArray should be exposed"]; + [self assertTrue:[exposedBindings containsObject:@"sortDescriptors"] message:@"sortDescriptors should be exposed"]; + [self assertTrue:[exposedBindings containsObject:@"selectionIndexPaths"] message:@"selectionIndexPaths should be exposed"]; + [self assertTrue:[exposedBindings containsObject:@"selectionIndexPath"] message:@"selectionIndexPath should be exposed"]; + [self assertTrue:[exposedBindings containsObject:@"selectedObjects"] message:@"selectedObjects should be exposed"]; +} + +- (void)testDetailBindingToSelectionProxyUpdatesOnSelectionChange +{ + var controller = [self treeController], + textField = [[CPTextField alloc] init]; + + [textField bind:@"value" toObject:controller withKeyPath:@"selection.name" options:nil]; + + // Select "Engineering" (index 0) + [controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:0]]; + [self assert:@"Engineering" equals:[textField stringValue] message:@"Bound view should reflect root selection"]; + + // Select "Web Team" (index [0, 0]) + var nestedPath = [[CPIndexPath indexPathWithIndex:0] indexPathByAddingIndex:0]; + [controller setSelectionIndexPath:nestedPath]; + [self assert:@"Web Team" equals:[textField stringValue] message:@"Bound view should reflect nested selection"]; + + // Select "Marketing" (index 1) + [controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:1]]; + [self assert:@"Marketing" equals:[textField stringValue] message:@"Bound view should reflect changed selection"]; +} + +- (void)testDetailBindingUpdatesOnSetContent +{ + var controller = [self treeController], + textField = [[CPTextField alloc] init]; + + [textField bind:@"value" toObject:controller withKeyPath:@"selection.name" options:nil]; + + // Select "Engineering" + [controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:0]]; + [self assert:@"Engineering" equals:[textField stringValue]]; + + // Replace content + var newDept = [OrgNode nodeWithName:@"Design Dept"]; + [controller setContent:[CPMutableArray arrayWithObject:newDept]]; + + // Detail binding should update to the preserved selection or new root + [self assert:@"Design Dept" equals:[textField stringValue] message:@"Detail binding must update when content changes"]; +} + +- (void)testSelectedObjectsKVOTriggeredOnContentChange +{ + var controller = [self treeController]; + [controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:0]]; + + [controller addObserver:self forKeyPath:@"selectedObjects" options:0 context:nil]; + + // Set new content + var newDept = [OrgNode nodeWithName:@"Operations"]; + [controller setContent:[CPMutableArray arrayWithObject:newDept]]; + + [self assertTrue:[_observedKeyPaths containsObject:@"selectedObjects"] message:@"selectedObjects KVO notification must fire when content changes"]; + + [controller removeObserver:self forKeyPath:@"selectedObjects"]; +} + +- (void)testCanInsertDependsOnEditable +{ + var controller = [self treeController]; + + [controller addObserver:self forKeyPath:@"canInsert" options:0 context:nil]; + + [controller setEditable:YES]; + [self assertTrue:[controller canInsert]]; + + [_observedKeyPaths removeAllObjects]; + + [controller setEditable:NO]; + [self assertFalse:[controller canInsert] message:@"canInsert should be NO when editable is NO"]; + [self assertTrue:[_observedKeyPaths containsObject:@"canInsert"] message:@"canInsert KVO must fire when editable changes"]; + + [controller removeObserver:self forKeyPath:@"canInsert"]; +} + +- (void)testCanAddChildAndCanInsertChildDependOnEditableAndSelection +{ + var controller = [self treeController]; + [controller setEditable:YES]; + + [controller addObserver:self forKeyPath:@"canAddChild" options:0 context:nil]; + [controller addObserver:self forKeyPath:@"canInsertChild" options:0 context:nil]; + + // Empty selection -> canAddChild/canInsertChild should be NO + [controller setSelectionIndexPaths:[CPArray array]]; + [self assertFalse:[controller canAddChild]]; + [self assertFalse:[controller canInsertChild]]; + + [_observedKeyPaths removeAllObjects]; + + // Select an item -> canAddChild/canInsertChild should become YES + [controller setSelectionIndexPath:[CPIndexPath indexPathWithIndex:0]]; + [self assertTrue:[controller canAddChild]]; + [self assertTrue:[controller canInsertChild]]; + [self assertTrue:[_observedKeyPaths containsObject:@"canAddChild"] message:@"canAddChild KVO should fire on selection change"]; + [self assertTrue:[_observedKeyPaths containsObject:@"canInsertChild"] message:@"canInsertChild KVO should fire on selection change"]; + + [_observedKeyPaths removeAllObjects]; + + // Set editable to NO -> canAddChild/canInsertChild should become NO + [controller setEditable:NO]; + [self assertFalse:[controller canAddChild]]; + [self assertFalse:[controller canInsertChild]]; + [self assertTrue:[_observedKeyPaths containsObject:@"canAddChild"] message:@"canAddChild KVO should fire when editable changes"]; + [self assertTrue:[_observedKeyPaths containsObject:@"canInsertChild"] message:@"canInsertChild KVO should fire when editable changes"]; + + [controller removeObserver:self forKeyPath:@"canAddChild"]; + [controller removeObserver:self forKeyPath:@"canInsertChild"]; +} + @end /* From d1b5c08272e41d3e50f70dbf94d34b88cc84094c Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 25 Aug 2026 21:03:07 +0200 Subject: [PATCH 23/35] fixed: KVO observer was missing --- AppKit/CPTreeController.j | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/AppKit/CPTreeController.j b/AppKit/CPTreeController.j index 6982a580a..17c61bab2 100644 --- a/AppKit/CPTreeController.j +++ b/AppKit/CPTreeController.j @@ -414,9 +414,13 @@ count = [nodes count]; for (var i = 0; i < count; i++) - [objects addObject:[[nodes objectAtIndex:i] representedObject]]; + { + var representedObject = [[nodes objectAtIndex:i] representedObject]; + if (representedObject) + [objects addObject:representedObject]; + } - return objects; + return [_CPObservableArray arrayWithArray:objects]; } - (BOOL)__setSelectedObjects:(CPArray)objects From 8d1429d063027991bc3d363b13b3dedd355d44aa Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 25 Aug 2026 21:09:51 +0200 Subject: [PATCH 24/35] proxy fix --- AppKit/CPTreeController.j | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/AppKit/CPTreeController.j b/AppKit/CPTreeController.j index 17c61bab2..9d650f4c3 100644 --- a/AppKit/CPTreeController.j +++ b/AppKit/CPTreeController.j @@ -171,6 +171,9 @@ if (![value isKindOfClass:[CPArray class]]) value = [CPArray arrayWithObject:value]; + if (_contentObject === value) + return; + var oldSelectedObjects = nil, oldSelectionIndexPaths = nil; @@ -179,6 +182,10 @@ else oldSelectionIndexPaths = [self selectionIndexPaths]; + [self _selectionWillChange]; + [self willChangeValueForKey:@"content"]; + [self willChangeValueForKey:@"contentArray"]; + _contentObject = value; [self _rearrangeObjects]; @@ -187,17 +194,26 @@ [self __setSelectedObjects:oldSelectedObjects]; else [self __setSelectionIndexPaths:oldSelectionIndexPaths avoidEmpty:_avoidsEmptySelection]; + + [self didChangeValueForKey:@"contentArray"]; + [self didChangeValueForKey:@"content"]; + [self _selectionDidChange]; } + - (void)_setContentArray:(id)anArray { [self setContent:anArray]; } - (id)contentArray { return _contentObject; } - (id)arrangedObjects { return _arrangedObjects; } - (void)rearrangeObjects { + [self _selectionWillChange]; [self willChangeValueForKey:@"arrangedObjects"]; + [self _rearrangeObjects]; + [self didChangeValueForKey:@"arrangedObjects"]; + [self _selectionDidChange]; } - (void)_rearrangeObjects @@ -520,6 +536,7 @@ - (void)insertObjects:(CPArray)objects atArrangedObjectIndexPaths:(CPArray)indexPaths { + [self _selectionWillChange]; [self willChangeValueForKey:@"content"]; _disableSetContent = YES; @@ -569,6 +586,7 @@ [self setSelectionIndexPaths:indexPaths]; [self didChangeValueForKey:@"content"]; + [self _selectionDidChange]; } - (void)remove:(id)sender @@ -583,6 +601,7 @@ - (void)removeObjectsAtArrangedObjectIndexPaths:(CPArray)indexPaths { + [self _selectionWillChange]; [self willChangeValueForKey:@"content"]; _disableSetContent = YES; @@ -622,6 +641,7 @@ _disableSetContent = NO; [self _rearrangeObjects]; [self didChangeValueForKey:@"content"]; + [self _selectionDidChange]; } - (void)moveNode:(CPTreeNode)node toIndexPath:(CPIndexPath)indexPath From d8dd2e8636e772eaaab2f8f8e08faaa60f400bcc Mon Sep 17 00:00:00 2001 From: daboe01 Date: Tue, 25 Aug 2026 22:18:37 +0200 Subject: [PATCH 25/35] improved: manual test for treecontroller --- .../CPTreeControllerTest/AppController.j | 131 +++++++++++++----- 1 file changed, 98 insertions(+), 33 deletions(-) diff --git a/Tests/Manual/CPTreeControllerTest/AppController.j b/Tests/Manual/CPTreeControllerTest/AppController.j index 15e24c0e4..65640056f 100644 --- a/Tests/Manual/CPTreeControllerTest/AppController.j +++ b/Tests/Manual/CPTreeControllerTest/AppController.j @@ -16,9 +16,11 @@ - (void)applicationDidFinishLaunching:(CPNotification)aNotification { - var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask], + var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMake(0, 0, 850, 400) styleMask:CPTitledWindowMask | CPClosableWindowMask | CPMiniaturizableWindowMask], contentView = [theWindow contentView]; + [theWindow setTitle:@"CPTreeController Bindings Test"]; + // 1. Create the Data Model var root1 = [[Node alloc] initWithName:@"Root 1" children:[]], child1 = [[Node alloc] initWithName:@"Child 1.1" children:[]], @@ -32,36 +34,39 @@ // 2. Setup the Tree Controller treeController = [[CPTreeController alloc] init]; + [treeController setObjectClass:[Node class]]; [treeController setChildrenKeyPath:@"children"]; [treeController setContent:contentArray]; - // 3. Setup the Outline View - var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(20, 20, 250, 300)]; - [scrollView setAutohidesScrollers:YES]; + // 3. Setup Outline View 1 (Left) + var scrollView1 = [[CPScrollView alloc] initWithFrame:CGRectMake(20, 20, 240, 300)]; + [scrollView1 setAutohidesScrollers:YES]; - var outlineView = [[CPOutlineView alloc] initWithFrame:CGRectMake(0, 0, 250, 300)]; - var column = [[CPTableColumn alloc] initWithIdentifier:@"name"]; - [[column headerView] setStringValue:@"Node Name"]; - [column setWidth:240]; - [column setEditable:YES]; // Editable to test bidirectional bindings in the tree - - [outlineView addTableColumn:column]; - [outlineView setOutlineTableColumn:column]; - [outlineView setAllowsMultipleSelection:YES]; - [scrollView setDocumentView:outlineView]; - [contentView addSubview:scrollView]; + var outlineView1 = [[CPOutlineView alloc] initWithFrame:CGRectMake(0, 0, 240, 300)], + column1 = [[CPTableColumn alloc] initWithIdentifier:@"name"]; + [[column1 headerView] setStringValue:@"Outline View 1"]; + [column1 setWidth:230]; + [column1 setEditable:YES]; - // 4. Establish Bindings for the Outline View - [outlineView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil]; - [outlineView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil]; + [outlineView1 addTableColumn:column1]; + [outlineView1 setOutlineTableColumn:column1]; + [outlineView1 setAllowsMultipleSelection:YES]; + [scrollView1 setDocumentView:outlineView1]; + [contentView addSubview:scrollView1]; - var scrollView2 = [[CPScrollView alloc] initWithFrame:CGRectMake(300, 20, 250, 300)]; + // Bind Outline View 1 (Content & Selection) + [outlineView1 bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil]; + [outlineView1 bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil]; + + // 4. Setup Outline View 2 (Middle - Synced) + var scrollView2 = [[CPScrollView alloc] initWithFrame:CGRectMake(280, 20, 240, 300)]; [scrollView2 setAutohidesScrollers:YES]; - var outlineView2 = [[CPOutlineView alloc] initWithFrame:CGRectMake(0, 0, 250, 300)]; - var column2 = [[CPTableColumn alloc] initWithIdentifier:@"name"]; - [[column2 headerView] setStringValue:@"Node Name"]; - [column2 setWidth:240]; - [column2 setEditable:YES]; // Editable to test bidirectional bindings in the tree + + var outlineView2 = [[CPOutlineView alloc] initWithFrame:CGRectMake(0, 0, 240, 300)], + column2 = [[CPTableColumn alloc] initWithIdentifier:@"name"]; + [[column2 headerView] setStringValue:@"Outline View 2 (Synced)"]; + [column2 setWidth:230]; + [column2 setEditable:YES]; [outlineView2 addTableColumn:column2]; [outlineView2 setOutlineTableColumn:column2]; @@ -69,20 +74,68 @@ [scrollView2 setDocumentView:outlineView2]; [contentView addSubview:scrollView2]; - // 4. Establish Bindings for the Outline View + // Bind Outline View 2 (Content & Selection) [outlineView2 bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil]; [outlineView2 bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil]; + // 5. Setup Inspector / Detail Controls (Right Panel) + var panelX = 540; + // Label: Detail Inspector + var detailLabel = [CPTextField labelWithTitle:@"Detail Binding (selection.name):"]; + [detailLabel setFrameOrigin:CGPointMake(panelX, 20)]; + [contentView addSubview:detailLabel]; + // 2-Way Detail Binding TextField + var nameField = [CPTextField textFieldWithStringValue:@"" placeholder:@"No Selection" width:260]; + [nameField setFrameOrigin:CGPointMake(panelX, 45)]; + [nameField setEditable:YES]; + [nameField bind:@"value" toObject:treeController withKeyPath:@"selection.name" options:nil]; + [contentView addSubview:nameField]; + + // Programmatic Selection Button + var selectBtn = [CPButton buttonWithTitle:@"Select 'Child 1.2' [0, 1]"]; + [selectBtn setFrame:CGRectMake(panelX, 90, 260, 28)]; + [selectBtn setTarget:self]; + [selectBtn setAction:@selector(selectSpecificNode:)]; + [contentView addSubview:selectBtn]; + + // Add Child Button (Action + Enabled Binding) + var addChildBtn = [CPButton buttonWithTitle:@"Add Child to Selection"]; + [addChildBtn setFrame:CGRectMake(panelX, 125, 260, 28)]; + [addChildBtn setTarget:treeController]; + [addChildBtn setAction:@selector(addChild:)]; + [addChildBtn bind:@"enabled" toObject:treeController withKeyPath:@"canAddChild" options:nil]; + [contentView addSubview:addChildBtn]; + + // Remove Selected Button + var removeBtn = [CPButton buttonWithTitle:@"Remove Selected"]; + [removeBtn setFrame:CGRectMake(panelX, 160, 260, 28)]; + [removeBtn setTarget:treeController]; + [removeBtn setAction:@selector(remove:)]; + [contentView addSubview:removeBtn]; + + // Selection Log Label + var logHeaderLabel = [CPTextField labelWithTitle:@"KVO selectionIndexPaths Log:"]; + [logHeaderLabel setFrameOrigin:CGPointMake(panelX, 210)]; + [contentView addSubview:logHeaderLabel]; + + logField = [CPTextField labelWithTitle:@"Selected: Root 1"]; + [logField setFrame:CGRectMake(panelX, 235, 260, 60)]; + [logField setLineBreakMode:CPLineBreakByWordWrapping]; + [contentView addSubview:logField]; + + // 6. Register Observer for Live Selection Log + [treeController addObserver:self forKeyPath:@"selectionIndexPaths" options:CPKeyValueObservingOptionNew context:nil]; + + [theWindow center]; [theWindow orderFront:self]; } - (void)selectSpecificNode:(id)sender { // Programmatically select index path [0, 1] which is "Child 1.2" - // This tests the `_CPOutlineViewSelectionIndexPathsBinder` auto-expand logic. - var path =[CPIndexPath indexPathWithIndexes:[0, 1]]; + var path = [CPIndexPath indexPathWithIndexes:[0, 1]]; [treeController setSelectionIndexPath:path]; } @@ -94,12 +147,14 @@ if ([selectedObjects count] > 0) { var names = [CPMutableArray array]; - for (var i = 0; i <[selectedObjects count]; i++) - [names addObject:[selectedObjects[i] name]];[logField setStringValue:[names componentsJoinedByString:@", "]]; + for (var i = 0; i < [selectedObjects count]; i++) + [names addObject:[selectedObjects[i] name]]; + + [logField setStringValue:[CPString stringWithFormat:@"Selected: %@", [names componentsJoinedByString:@", "]]]; } else { - [logField setStringValue:@"Nothing selected"]; + [logField setStringValue:@"Selected: (Nothing selected)"]; } } } @@ -121,16 +176,21 @@ if (self) { name = aName; - children = someChildren; + children = someChildren || []; } return self; } -// Explicit accessors to ensure Key-Value Observing (KVO) works flawlessly. +- (id)init +{ + return [self initWithName:@"New Node" children:[]]; +} + - (void)setName:(CPString)aName { [self willChangeValueForKey:@"name"]; - name = aName;[self didChangeValueForKey:@"name"]; + name = aName; + [self didChangeValueForKey:@"name"]; } - (CPString)name @@ -150,4 +210,9 @@ return children; } +- (CPString)description +{ + return [CPString stringWithFormat:@"", [self name]]; +} + @end From 9ffa4decd4391dfb736a7820fe2c497604b7c059 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 26 Aug 2026 17:06:34 +0200 Subject: [PATCH 26/35] fixed: indentation paddle dragging and clientView binding --- AppKit/CPTextView/CPRulerView.j | 238 +++++++++++++------------------- AppKit/CPTextView/CPTextView.j | 234 ++++++++++++------------------- 2 files changed, 182 insertions(+), 290 deletions(-) diff --git a/AppKit/CPTextView/CPRulerView.j b/AppKit/CPTextView/CPRulerView.j index ab0e4d3ff..2a1080269 100644 --- a/AppKit/CPTextView/CPRulerView.j +++ b/AppKit/CPTextView/CPRulerView.j @@ -28,7 +28,6 @@ @import "CPMenu.j" @import "CPMenuItem.j" -// Orientations matching AppKit standards @typedef CPRulerOrientation CPHorizontalRuler = 0; CPVerticalRuler = 1; @@ -40,7 +39,7 @@ CPRulerOrientationVertical = 1; @class CPTextTab; -// MARK: - CPRulerMarker (Interactive Handles with Dynamic Alignment Icons) +// MARK: - CPRulerMarker @implementation CPRulerMarker : CPView { @@ -59,13 +58,18 @@ CPRulerOrientationVertical = 1; _imageValue = anImageValue; _representedObject = anObject; + // CRITICAL: Subviews must not intercept mouse events so CPRulerView receives all mouseDragged: events + [self setHitTests:NO]; + _label = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 12, 12)]; [_label setFont:[CPFont systemFontOfSize:10.0]]; [_label setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]]; [_label setAlignment:CPCenterTextAlignment]; + [_label setHitTests:NO]; [self addSubview:_label]; _customHandleView = [[CPView alloc] initWithFrame:CGRectMakeZero()]; + [_customHandleView setHitTests:NO]; [self addSubview:_customHandleView]; [self updateMarkerIcon]; @@ -90,8 +94,6 @@ CPRulerOrientationVertical = 1; [self updateMarkerIcon]; } -// Dynamically sets the Unicode triangle direction based on the alignment or indent type, -// or draws custom split-height grab handles for indentation controls. - (void)updateMarkerIcon { var isIndentMarker = (_representedObject === @"CPFirstLineIndent" || _representedObject === @"CPHeadIndent"); @@ -103,28 +105,19 @@ CPRulerOrientationVertical = 1; var frame = [self bounds]; [_customHandleView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)]; - - // Remove old internal rendering to update cleanly [[_customHandleView subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)]; var isFirstLine = (_representedObject === @"CPFirstLineIndent"); + [_customHandleView setBackgroundColor:[CPColor colorWithWhite:0.45 alpha:1.0]]; - // Dark outline/border representation - [_customHandleView setBackgroundColor:[CPColor colorWithWhite:0.5 alpha:1.0]]; - - // Inner fill (top handle is lighter, bottom is slightly darker) - var innerView = [[CPView alloc] initWithFrame:CGRectMake(1.0, 1.0, frame.size.width - 2.0, frame.size.height - 2.0)]; - - if (isFirstLine) - [innerView setBackgroundColor:[CPColor colorWithWhite:0.92 alpha:1.0]]; - else - [innerView setBackgroundColor:[CPColor colorWithWhite:0.80 alpha:1.0]]; - + var innerView = [[CPView alloc] initWithFrame:CGRectMake(1.0, 1.0, Math.max(1.0, frame.size.width - 2.0), Math.max(1.0, frame.size.height - 2.0))]; + [innerView setHitTests:NO]; + [innerView setBackgroundColor:isFirstLine ? [CPColor colorWithWhite:0.95 alpha:1.0] : [CPColor colorWithWhite:0.80 alpha:1.0]]; [_customHandleView addSubview:innerView]; - // Horizontal indicator line to visually guide drag interactions - var gripLine = [[CPView alloc] initWithFrame:CGRectMake(Math.floor(frame.size.width / 2.0) - 1.0, 2.0, 1.0, frame.size.height - 4.0)]; - [gripLine setBackgroundColor:[CPColor colorWithWhite:0.6 alpha:1.0]]; + var gripLine = [[CPView alloc] initWithFrame:CGRectMake(Math.floor(frame.size.width / 2.0) - 1.0, 1.0, 1.0, Math.max(1.0, frame.size.height - 3.0))]; + [gripLine setHitTests:NO]; + [gripLine setBackgroundColor:[CPColor colorWithWhite:0.5 alpha:1.0]]; [innerView addSubview:gripLine]; } else @@ -137,33 +130,23 @@ CPRulerOrientationVertical = 1; { var align = [_representedObject alignment]; if (align === CPLeftTextAlignment) - [_label setStringValue:@"▶"]; // Left-aligned points Right + [_label setStringValue:@"▶"]; else if (align === CPCenterTextAlignment) - [_label setStringValue:@"▼"]; // Center-aligned points Down + [_label setStringValue:@"▼"]; else if (align === CPRightTextAlignment) - [_label setStringValue:@"◀"]; // Right-aligned points Left - } - else if ([_representedObject isKindOfClass:[CPString class]]) - { - if (_representedObject === @"CPTailIndent") - [_label setStringValue:@"⥘"]; // Solid downward triangle for tail indent - else - [_label setStringValue:@"⇡"]; // Fallback standard up marker + [_label setStringValue:@"◀"]; } else { - [_label setStringValue:@"⇡"]; // Fallback standard up marker + [_label setStringValue:@"▲"]; } } } -// MARK: - -// MARK: Context Menu Support - (CPMenu)menuForEvent:(CPEvent)anEvent { var menu = [[CPMenu alloc] initWithTitle:@"Marker Context Menu"]; - // If the marker represents a standard tab stop, allow changing its type if ([_representedObject isKindOfClass:[CPTextTab class]]) { var itemLeft = [menu addItemWithTitle:@"Left Tab Stop" action:@selector(changeTypeToLeft:) keyEquivalent:@""], @@ -182,17 +165,11 @@ CPRulerOrientationVertical = 1; [menu addItem:[CPMenuItem separatorItem]]; } - // Determine the context-specific delete title var deleteTitle = @"Delete Tab Stop"; - if ([_representedObject isKindOfClass:[CPString class]]) - { - if (_representedObject === @"CPFirstLineIndent") - deleteTitle = @"Delete 1st line indentation marker"; - else if (_representedObject === @"CPHeadIndent") - deleteTitle = @"Delete head indentation marker"; - else if (_representedObject === @"CPTailIndent") - deleteTitle = @"Delete tail indentation marker"; - } + if (_representedObject === @"CPFirstLineIndent") + deleteTitle = @"Reset 1st line indentation"; + else if (_representedObject === @"CPHeadIndent") + deleteTitle = @"Reset head indentation"; var itemDelete = [menu addItemWithTitle:deleteTitle action:@selector(deleteMarker:) keyEquivalent:@""]; [itemDelete setTarget:self]; @@ -200,66 +177,49 @@ CPRulerOrientationVertical = 1; return menu; } -- (void)changeTypeToLeft:(id)sender -{ - [self _changeAlignment:CPLeftTextAlignment]; -} - -- (void)changeTypeToCenter:(id)sender -{ - [self _changeAlignment:CPCenterTextAlignment]; -} - -- (void)changeTypeToRight:(id)sender -{ - [self _changeAlignment:CPRightTextAlignment]; -} +- (void)changeTypeToLeft:(id)sender { [self _changeAlignment:CPLeftTextAlignment]; } +- (void)changeTypeToCenter:(id)sender { [self _changeAlignment:CPCenterTextAlignment]; } +- (void)changeTypeToRight:(id)sender { [self _changeAlignment:CPRightTextAlignment]; } - (void)_changeAlignment:(CPTextAlignment)alignment { if (![_representedObject isKindOfClass:[CPTextTab class]]) return; - var oldTab = _representedObject; - var newTab = [[CPTextTab alloc] initWithType:alignment location:_imageValue]; + var oldTab = _representedObject, + newTab = [[CPTextTab alloc] initWithType:alignment location:_imageValue]; - // Using setRepresentedObject: automatically updates the marker triangle direction [self setRepresentedObject:newTab]; var client = [_rulerView clientView]; if (client && [client respondsToSelector:@selector(rulerView:didUpdateMarker:oldTab:)]) - { [client rulerView:_rulerView didUpdateMarker:self oldTab:oldTab]; - } } - (void)deleteMarker:(id)sender { var client = [_rulerView clientView]; if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)]) - { [client rulerView:_rulerView didRemoveMarker:self]; - } + [_rulerView removeMarker:self]; } @end -// MARK: - CPRulerView (Pure DOM + Interactive Engine) +// MARK: - CPRulerView @implementation CPRulerView : CPView { CPScrollView _scrollView @accessors(property=scrollView); CPRulerOrientation _orientation @accessors(property=orientation); - CPView _clientView @accessors(property=clientView); - + float _ruleThickness @accessors(property=ruleThickness); float _reservedThicknessForMarkers; CPArray _markers; - // Dragger variables - CPRulerMarker _draggingMarker; + CPRulerMarker _draggingMarker @accessors(getter=draggingMarker); CGPoint _dragStartPoint; float _dragStartLocation; } @@ -287,7 +247,16 @@ CPRulerOrientationVertical = 1; [self updateRuler]; } -// Markers registration +- (CPView)clientView +{ + return _clientView || [_scrollView documentView]; +} + +- (void)setClientView:(CPView)aView +{ + _clientView = aView; +} + - (void)addMarker:(CPRulerMarker)aMarker { if ([_markers containsObject:aMarker]) @@ -317,14 +286,20 @@ CPRulerOrientationVertical = 1; [self addSubview:marker]; [self _positionMarker:marker]; } + [self updateRuler]; } - (CPRulerMarker)_markerAtPoint:(CGPoint)aPoint { - for (var i = 0; i < [_markers count]; i++) + // Search in reverse order so indent paddles (added last) are prioritized over overlapping 0-location tabs + for (var i = [_markers count] - 1; i >= 0; i--) { - var marker = [_markers objectAtIndex:i]; - if (CGRectContainsPoint([marker frame], aPoint)) + var marker = [_markers objectAtIndex:i], + frame = [marker frame]; + + // Expanded hit-box by 3px on all sides for easy grabbing + var hitFrame = CGRectMake(frame.origin.x - 3.0, frame.origin.y - 2.0, frame.size.width + 6.0, frame.size.height + 4.0); + if (CGRectContainsPoint(hitFrame, aPoint)) return marker; } return nil; @@ -340,55 +315,48 @@ CPRulerOrientationVertical = 1; isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal), rulerHeight = CGRectGetHeight([self bounds]), rulerWidth = CGRectGetWidth([self bounds]), - markerLocation = [aMarker imageValue]; + markerLocation = [aMarker imageValue], + rep = [aMarker representedObject]; if (isHorizontal) { - var x = markerLocation - scrollPoint.x - 6.0, // Center the 12px wide marker + var x = markerLocation - scrollPoint.x - 6.0, y = rulerHeight - 11.0, w = 12.0, h = 12.0; - - // Align the First Line Indent (upper half) and Head Indent (lower half) controls - if ([aMarker representedObject] === @"CPFirstLineIndent") + + if (rep === @"CPFirstLineIndent") { y = 0.0; h = Math.floor(rulerHeight / 2.0); } - else if ([aMarker representedObject] === @"CPHeadIndent") + else if (rep === @"CPHeadIndent") { y = Math.floor(rulerHeight / 2.0); - h = rulerHeight - y - 1.0; // Subtract 1px to stay cleanly above bottom border + h = rulerHeight - y - 1.0; } else { - // Keep normal horizontal markers within the bounds of the ruler to prevent clipping - if (x < 0.0) - x = 0.0; - else if (x + 12.0 > rulerWidth) - x = rulerWidth - 12.0; + if (x < -6.0) x = -6.0; + else if (x + 12.0 > rulerWidth) x = rulerWidth - 12.0; } - + [aMarker setFrame:CGRectMake(x, y, w, h)]; } else { var x = rulerWidth - 11.0, y = markerLocation - scrollPoint.y - 6.0; - - // Keep vertical marker within the bounds of the ruler to prevent clipping - if (y < 0.0) - y = 0.0; - else if (y + 12.0 > rulerHeight) - y = rulerHeight - 12.0; - + + if (y < 0.0) y = 0.0; + else if (y + 12.0 > rulerHeight) y = rulerHeight - 12.0; + [aMarker setFrame:CGRectMake(x, y, 12.0, 12.0)]; } } -// MARK: - -// MARK: Interaction Handlers +// MARK: - Interaction Handlers - (void)mouseDown:(CPEvent)anEvent { @@ -400,7 +368,6 @@ CPRulerOrientationVertical = 1; var rulerLocation = isHorizontal ? (localPoint.x + scrollPoint.x) : (localPoint.y + scrollPoint.y); - // 1. Check if clicked an existing marker var clickedMarker = [self _markerAtPoint:localPoint]; if (clickedMarker) { @@ -408,7 +375,6 @@ CPRulerOrientationVertical = 1; _dragStartPoint = localPoint; _dragStartLocation = [_draggingMarker imageValue]; } - // 2. Otherwise, create a new marker dynamically where the user clicked else { var newMarker = [[CPRulerMarker alloc] initWithRulerView:self @@ -416,12 +382,11 @@ CPRulerOrientationVertical = 1; imageValue:rulerLocation representedObject:nil]; [self addMarker:newMarker]; - - // Notify the client view (e.g., CPTextView) that a new marker was added + var client = [self clientView]; if (client && [client respondsToSelector:@selector(rulerView:didAddMarker:)]) [client rulerView:self didAddMarker:newMarker]; - + _draggingMarker = newMarker; _dragStartPoint = localPoint; _dragStartLocation = rulerLocation; @@ -438,33 +403,29 @@ CPRulerOrientationVertical = 1; isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal); var delta = isHorizontal ? (localPoint.x - _dragStartPoint.x) : (localPoint.y - _dragStartPoint.y), - newLocation = _dragStartLocation + delta; - - if (newLocation < 0) newLocation = 0; + newLocation = Math.max(0.0, _dragStartLocation + delta); [_draggingMarker setImageValue:newLocation]; - - // Smoothly redraw ruler and margin bounds on every drag step - [self updateRuler]; - - // Check if dragged off the ruler (more than 15px off the boundary) - var draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15) - : (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15); + [self _positionMarker:_draggingMarker]; + + var rep = [_draggingMarker representedObject], + isIndent = (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent"); + + var draggedOff = !isIndent && (isHorizontal + ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15) + : (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15)); if (draggedOff) { - // Visual feedback: Dim the handle to 40% and turn the triangle icon gray [_draggingMarker setAlphaValue:0.4]; [[_draggingMarker label] setTextColor:[CPColor grayColor]]; } else { - // Restore standard styling when dragged back into the active strip [_draggingMarker setAlphaValue:1.0]; [[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]]; } - - // Notify the CPTextView that the marker coordinates shifted + var client = [self clientView]; if (client && [client respondsToSelector:@selector(rulerView:didMoveMarker:)]) [client rulerView:self didMoveMarker:_draggingMarker]; @@ -477,10 +438,12 @@ CPRulerOrientationVertical = 1; var localPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil], isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal), - - // If dragged more than 15 pixels off the ruler, delete the marker - draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15) - : (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15); + rep = [_draggingMarker representedObject], + isIndent = (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent"); + + var draggedOff = !isIndent && (isHorizontal + ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15) + : (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15)); if (draggedOff) { @@ -492,22 +455,19 @@ CPRulerOrientationVertical = 1; } else { - // Ensure marker style is fully restored if not deleted [_draggingMarker setAlphaValue:1.0]; [[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]]; } - + _draggingMarker = nil; [self updateRuler]; } -// MARK: - -// MARK: DOM Layout Builder +// MARK: - Layout Builder - (void)updateRuler { - // Wipe subviews to redraw the dynamic visible tick lines/numbers [self setSubviews:@[]]; if (!_scrollView) @@ -526,12 +486,11 @@ CPRulerOrientationVertical = 1; rulerHeight = CGRectGetHeight([self bounds]), rulerWidth = CGRectGetWidth([self bounds]); - // Draw solid horizontal bottom border (pure, razor-sharp CSS DOM view) var bottomBorder = [[CPView alloc] initWithFrame:CGRectMake(0, rulerHeight - 1, rulerWidth, 1)]; + [bottomBorder setHitTests:NO]; [bottomBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]]; [self addSubview:bottomBorder]; - // Find indent markers to determine background highlight boundaries var firstLineMarker = nil, headMarker = nil; for (var i = 0; i < [_markers count]; i++) @@ -545,31 +504,30 @@ CPRulerOrientationVertical = 1; var halfHeight = Math.floor(rulerHeight / 2.0); - // Draw First Line Indent background - top half (lighter gray) if (firstLineMarker) { var firstLineX = [firstLineMarker imageValue] - scrollPoint.x; if (firstLineX > 0) { var firstLineBg = [[CPView alloc] initWithFrame:CGRectMake(0, 0, firstLineX, halfHeight)]; + [firstLineBg setHitTests:NO]; [firstLineBg setBackgroundColor:[CPColor colorWithWhite:0.93 alpha:1.0]]; [self addSubview:firstLineBg]; } } - // Draw Head Indent background - bottom half (slightly darker gray) if (headMarker) { var headX = [headMarker imageValue] - scrollPoint.x; if (headX > 0) { var headBg = [[CPView alloc] initWithFrame:CGRectMake(0, halfHeight, headX, rulerHeight - halfHeight - 1.0)]; + [headBg setHitTests:NO]; [headBg setBackgroundColor:[CPColor colorWithWhite:0.86 alpha:1.0]]; [self addSubview:headBg]; } } - // Render ruler tick lines and labels on top of shaded areas for (var val = start; val <= end; val += 10) { if (val < 0) continue; @@ -579,30 +537,21 @@ CPRulerOrientationVertical = 1; tickHeight = isMajor ? 8.0 : 4.0, tickY = rulerHeight - tickHeight - 1.0; - // Tick mark CSS line view var tick = [[CPView alloc] initWithFrame:CGRectMake(screenX, tickY, 1.0, tickHeight)]; + [tick setHitTests:NO]; [tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]]; [self addSubview:tick]; - // Unit label if (isMajor) { var labelX = screenX - 20.0, alignment = CPCenterTextAlignment; - // Adjust label frame and alignment if it lands near left/right bounds - if (labelX < 0.0) - { - labelX = Math.max(0.0, screenX); - alignment = CPLeftTextAlignment; - } - else if (labelX + 40.0 > rulerWidth) - { - labelX = rulerWidth - 40.0; - alignment = CPRightTextAlignment; - } + if (labelX < 0.0) { labelX = Math.max(0.0, screenX); alignment = CPLeftTextAlignment; } + else if (labelX + 40.0 > rulerWidth) { labelX = rulerWidth - 40.0; alignment = CPRightTextAlignment; } var label = [[CPTextField alloc] initWithFrame:CGRectMake(labelX, 1.0, 40.0, 12.0)]; + [label setHitTests:NO]; [label setStringValue:[CPString stringWithFormat:@"%d", val]]; [label setFont:[CPFont systemFontOfSize:8.0]]; [label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]]; @@ -659,12 +608,11 @@ CPRulerOrientationVertical = 1; } } - // Reposition and display active markers + // Add back existing markers without re-creating them for (var i = 0; i < [_markers count]; i++) { var marker = [_markers objectAtIndex:i]; - if ([marker superview] !== self) - [self addSubview:marker]; + [self addSubview:marker]; [self _positionMarker:marker]; } } diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index ad6c1cbc0..b9257fbc1 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2472,6 +2472,10 @@ Sets the selection to a range of characters in response to user action. if (!ruler) return; + // Do not rebuild markers if user is currently dragging one + if ([ruler draggingMarker]) + return; + var selectedRange = [self selectedRange], paragraphStyle = [CPParagraphStyle defaultParagraphStyle], currentAttributes = _typingAttributes; @@ -2480,10 +2484,10 @@ Sets the selection to a range of characters in response to user action. if (textLength > 0) { var charIndex = selectedRange.location; - - // Safety bounds checks for cursor placements + if (charIndex >= textLength) charIndex = textLength - 1; + if (charIndex < 0) charIndex = 0; @@ -2497,7 +2501,6 @@ Sets the selection to a range of characters in response to user action. tabStops = [paragraphStyle tabStops], count = [tabStops count]; - // A. Load existing tab stop markers onto the ruler for (var i = 0; i < count; i++) { var tab = [tabStops objectAtIndex:i], @@ -2508,7 +2511,6 @@ Sets the selection to a range of characters in response to user action. [markers addObject:marker]; } - // B. Add Indentation Handles (First line indent & Head indent) var firstLineMarker = [[CPRulerMarker alloc] initWithRulerView:ruler markerLocation:[paragraphStyle firstLineHeadIndent] imageValue:[paragraphStyle firstLineHeadIndent] @@ -2524,7 +2526,6 @@ Sets the selection to a range of characters in response to user action. [ruler setMarkers:markers]; } -// Local comparison function helper for tab sorting var compareTabStops = function(obj1, obj2, context) { if ([obj1 location] < [obj2 location]) return CPOrderedAscending; if ([obj1 location] > [obj2 location]) return CPOrderedDescending; @@ -2533,72 +2534,6 @@ var compareTabStops = function(obj1, obj2, context) { - (void)rulerView:(CPRulerView)rulerView didAddMarker:(CPRulerMarker)marker { - if (![self _didBeginEditing] || ![self shouldChangeTextInRange:_selectionRange replacementString:nil]) - return; - - var selectedRange = [self selectedRange], - paragraphStyle = [CPParagraphStyle defaultParagraphStyle], - currentAttributes = _typingAttributes; - - // Retrieve active attributes at the cursor position if there is no selection - var textLength = [_textStorage length], - charIndex = selectedRange.location; - - if (textLength > 0) - { - if (charIndex >= textLength) - charIndex = textLength - 1; - if (charIndex < 0) - charIndex = 0; - - currentAttributes = [_textStorage attributesAtIndex:charIndex effectiveRange:nil]; - } - - if ([currentAttributes objectForKey:CPParagraphStyleAttributeName]) - paragraphStyle = [currentAttributes objectForKey:CPParagraphStyleAttributeName]; - - var mutableStyle = [paragraphStyle mutableCopy]; - - // Create a new Left-aligned tab stop where the user clicked - var newTab = [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:[marker imageValue]], - tabs = [[mutableStyle tabStops] mutableCopy]; - - [tabs addObject:newTab]; - - // Sort tabs by location ascending - [tabs sortUsingFunction:compareTabStops context:nil]; - - [mutableStyle setTabStops:tabs]; - [marker setRepresentedObject:newTab]; - - // Find the target text range to modify (selection or containing paragraph) - var targetRange = selectedRange; - if (targetRange.length === 0) - targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph]; - - if (targetRange.length > 0) - { - [_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)]; - - [_layoutManager textStorage:_textStorage - edited:0 - range:CPMakeRangeCopy(targetRange) - changeInLength:0 - invalidatedRange:CPMakeRangeCopy(targetRange)]; - } - - if (selectedRange.length === 0) - { - [_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName]; - [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self]; - } -} - -- (void)rulerView:(CPRulerView)rulerView didMoveMarker:(CPRulerMarker)marker -{ - if (![self _didBeginEditing] || ![self shouldChangeTextInRange:_selectionRange replacementString:nil]) - return; - var selectedRange = [self selectedRange], paragraphStyle = [CPParagraphStyle defaultParagraphStyle], currentAttributes = _typingAttributes; @@ -2610,6 +2545,7 @@ var compareTabStops = function(obj1, obj2, context) { { if (charIndex >= textLength) charIndex = textLength - 1; + if (charIndex < 0) charIndex = 0; @@ -2620,69 +2556,31 @@ var compareTabStops = function(obj1, obj2, context) { paragraphStyle = [currentAttributes objectForKey:CPParagraphStyleAttributeName]; var mutableStyle = [paragraphStyle mutableCopy], - oldTab = [marker representedObject]; + newTab = [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:[marker imageValue]], + tabs = [[mutableStyle tabStops] mutableCopy]; - if (!oldTab) - return; + [tabs addObject:newTab]; + [tabs sortUsingFunction:compareTabStops context:nil]; - // A. Handle standard tab stops - if ([oldTab isKindOfClass:[CPTextTab class]]) - { - var newTab = [[CPTextTab alloc] initWithType:[oldTab alignment] location:[marker imageValue]], - tabs = [[mutableStyle tabStops] mutableCopy]; + [mutableStyle setTabStops:tabs]; + [marker setRepresentedObject:newTab]; - [tabs removeObject:oldTab]; - [tabs addObject:newTab]; - - // Sort tabs by location ascending - [tabs sortUsingFunction:compareTabStops context:nil]; - - [mutableStyle setTabStops:tabs]; - [marker setRepresentedObject:newTab]; - } - // B. Handle Indentation Marker drags (First Line, Left, and Right indents) - else if ([oldTab isKindOfClass:[CPString class]]) - { - if (oldTab === @"CPFirstLineIndent") - [mutableStyle setFirstLineHeadIndent:[marker imageValue]]; - else if (oldTab === @"CPHeadIndent") - [mutableStyle setHeadIndent:[marker imageValue]]; - else if (oldTab === @"CPTailIndent") - [mutableStyle setTailIndent:[marker imageValue]]; - } - - // Find the target text range to modify (selection or containing paragraph) var targetRange = selectedRange; - if (targetRange.length === 0) + if (targetRange.length === 0 && textLength > 0) targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph]; if (targetRange.length > 0) { [_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)]; - - [_layoutManager textStorage:_textStorage - edited:0 - range:CPMakeRangeCopy(targetRange) - changeInLength:0 - invalidatedRange:CPMakeRangeCopy(targetRange)]; + [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)]; } - if (selectedRange.length === 0) - { - [_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName]; - [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self]; - } - - [_layoutManager _validateLayoutAndGlyphs]; - [self sizeToFit]; - [self setNeedsDisplay:YES]; + [_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName]; + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self]; } -- (void)rulerView:(CPRulerView)rulerView didRemoveMarker:(CPRulerMarker)marker +- (void)rulerView:(CPRulerView)rulerView didMoveMarker:(CPRulerMarker)marker { - if (![self _didBeginEditing] || ![self shouldChangeTextInRange:_selectionRange replacementString:nil]) - return; - var selectedRange = [self selectedRange], paragraphStyle = [CPParagraphStyle defaultParagraphStyle], currentAttributes = _typingAttributes; @@ -2694,12 +2592,78 @@ var compareTabStops = function(obj1, obj2, context) { { if (charIndex >= textLength) charIndex = textLength - 1; + if (charIndex < 0) charIndex = 0; currentAttributes = [_textStorage attributesAtIndex:charIndex effectiveRange:nil]; } + if ([currentAttributes objectForKey:CPParagraphStyleAttributeName]) + paragraphStyle = [currentAttributes objectForKey:CPParagraphStyleAttributeName]; + + var mutableStyle = [paragraphStyle mutableCopy], + rep = [marker representedObject]; + + if (!rep) + return; + + // A. Handle standard tab stops + if ([rep isKindOfClass:[CPTextTab class]]) + { + var newTab = [[CPTextTab alloc] initWithType:[rep alignment] location:[marker imageValue]], + tabs = [[mutableStyle tabStops] mutableCopy]; + + [tabs removeObject:rep]; + [tabs addObject:newTab]; + [tabs sortUsingFunction:compareTabStops context:nil]; + + [mutableStyle setTabStops:tabs]; + [marker setRepresentedObject:newTab]; + } + + // B. Handle Indentation Marker drags directly by string literal + else if (rep === @"CPFirstLineIndent") + [mutableStyle setFirstLineHeadIndent:[marker imageValue]]; + else if (rep === @"CPHeadIndent") + [mutableStyle setHeadIndent:[marker imageValue]]; + else if (rep === @"CPTailIndent") + [mutableStyle setTailIndent:[marker imageValue]]; + + var targetRange = selectedRange; + + if (targetRange.length === 0 && textLength > 0) + targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph]; + + if (targetRange.length > 0) + { + [_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)]; + [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)]; + } + + [_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName]; + + [_layoutManager _validateLayoutAndGlyphs]; + [self sizeToFit]; + [self setNeedsDisplay:YES]; +} + +- (void)rulerView:(CPRulerView)rulerView didRemoveMarker:(CPRulerMarker)marker +{ + var selectedRange = [self selectedRange], + paragraphStyle = [CPParagraphStyle defaultParagraphStyle], + currentAttributes = _typingAttributes; + + var textLength = [_textStorage length], + charIndex = selectedRange.location; + + if (textLength > 0) + { + if (charIndex >= textLength) charIndex = textLength - 1; + if (charIndex < 0) charIndex = 0; + currentAttributes = [_textStorage attributesAtIndex:charIndex effectiveRange:nil]; + } + if ([currentAttributes objectForKey:CPParagraphStyleAttributeName]) paragraphStyle = [currentAttributes objectForKey:CPParagraphStyleAttributeName]; @@ -2711,31 +2675,20 @@ var compareTabStops = function(obj1, obj2, context) { var tabs = [[mutableStyle tabStops] mutableCopy]; [tabs removeObject:oldTab]; - [mutableStyle setTabStops:tabs]; - // Find the target text range to modify (selection or containing paragraph) var targetRange = selectedRange; - - if (targetRange.length === 0) + if (targetRange.length === 0 && textLength > 0) targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph]; if (targetRange.length > 0) { [_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)]; - - [_layoutManager textStorage:_textStorage - edited:0 - range:CPMakeRangeCopy(targetRange) - changeInLength:0 - invalidatedRange:CPMakeRangeCopy(targetRange)]; + [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)]; } - if (selectedRange.length === 0) - { - [_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName]; - [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self]; - } + [_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName]; + [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self]; } - (void)rulerView:(CPRulerView)rulerView didUpdateMarker:(CPRulerMarker)marker oldTab:(id)oldTab @@ -2759,19 +2712,11 @@ var compareTabStops = function(obj1, obj2, context) { [tabs removeObject:oldTab]; [tabs addObject:newTab]; - - // Sort tabs ascending [tabs sortUsingFunction:compareTabStops context:nil]; - [mutableStyle setTabStops:tabs]; [_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(targetRange)]; - - [_layoutManager textStorage:_textStorage - edited:0 - range:CPMakeRangeCopy(targetRange) - changeInLength:0 - invalidatedRange:CPMakeRangeCopy(targetRange)]; + [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)]; if (selectedRange.length === 0) { @@ -2779,7 +2724,6 @@ var compareTabStops = function(obj1, obj2, context) { [[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self]; } - // Force layouts and view canvas update [_layoutManager _validateLayoutAndGlyphs]; [self sizeToFit]; [self setNeedsDisplay:YES]; From 22a43bcfc547fd3a23303be39541e81993dcef09 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 26 Aug 2026 17:56:48 +0200 Subject: [PATCH 27/35] fixed: resolve tab stop skipping and dragging positioning --- AppKit/CPTextView/CPTypesetter.j | 54 ++++++++++++++------------------ 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/AppKit/CPTextView/CPTypesetter.j b/AppKit/CPTextView/CPTypesetter.j index f8f00a9c1..74cf11349 100644 --- a/AppKit/CPTextView/CPTypesetter.j +++ b/AppKit/CPTextView/CPTypesetter.j @@ -141,31 +141,34 @@ var CPSystemTypesetterFactory, // Retrieves correct CPTextTab stop accounting for CPArray properties - (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction { - var tabStops = [_currentParagraph tabStops]; + var tabStops = [_currentParagraph tabStops], + defaultInterval = [_currentParagraph defaultTabInterval] || 28.0; - if (!tabStops) - tabStops = [[CPParagraphStyle defaultParagraphStyle] tabStops]; + // Calculate the immediate next standard tab stop location based on defaultInterval + var nextDefaultLocation = (Math.floor(aWidth / defaultInterval) + 1) * defaultInterval; - var l = [tabStops count]; - - if (l === 0) - return nil; - - // Find the first tab stop that is strictly greater than the current width - for (var i = 0; i < l; i++) + // Search for the first custom tab stop strictly greater than aWidth + var nextCustomTab = nil; + if (tabStops) { - var tab = [tabStops objectAtIndex:i]; - - if ([tab location] > aWidth) - return tab; + var l = [tabStops count]; + for (var i = 0; i < l; i++) + { + var tab = [tabStops objectAtIndex:i]; + if ([tab location] > aWidth) + { + nextCustomTab = tab; + break; + } + } } - // If aWidth exceeds the last tab stop, dynamically calculate the next - // tab location using the default tab interval. - var defaultInterval = [_currentParagraph defaultTabInterval] || 28.0; - var nextLocation = CEIL((aWidth + 1.0) / defaultInterval) * defaultInterval; + // If a custom tab stop exists and is closer than (or at) the next default interval, use it + if (nextCustomTab && [nextCustomTab location] <= nextDefaultLocation) + return nextCustomTab; - return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextLocation]; + // Otherwise, snap to the immediate next regular tab interval + return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextDefaultLocation]; } - (BOOL)_flushRange:(CPRange)lineRange @@ -327,15 +330,6 @@ var CPSystemTypesetterFactory, lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent]; isFirstLineOfLayout = NO; } - - // Calculate the right wrapping margin based on tail indent - var tailIndent = [_currentParagraph tailIndent]; - if (tailIndent > 0.0) - rightMargin = tailIndent; - else if (tailIndent < 0.0) - rightMargin = containerSizeWidth + tailIndent; - else - rightMargin = containerSizeWidth; // Handle the layout's very first line indentation if (isFirstLineOfLayout) @@ -454,7 +448,7 @@ var CPSystemTypesetterFactory, } case 9: // '\t' { - var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0]; + var nextTab = [self textTabForWidth:prevRangeWidth + lineOrigin.x writingDirection:0]; isTabStop = YES; @@ -501,7 +495,7 @@ var CPSystemTypesetterFactory, } else { - rangeWidth += 28.0; // standard fallback spacer + rangeWidth = prevRangeWidth + 28.0; // standard fallback spacer } break; } From ca13595e374a97076b0374dd96908f23581289b0 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 26 Aug 2026 17:58:39 +0200 Subject: [PATCH 28/35] formatting --- AppKit/CPTextView/CPTypesetter.j | 1 + 1 file changed, 1 insertion(+) diff --git a/AppKit/CPTextView/CPTypesetter.j b/AppKit/CPTextView/CPTypesetter.j index 74cf11349..eee7c2efe 100644 --- a/AppKit/CPTextView/CPTypesetter.j +++ b/AppKit/CPTextView/CPTypesetter.j @@ -149,6 +149,7 @@ var CPSystemTypesetterFactory, // Search for the first custom tab stop strictly greater than aWidth var nextCustomTab = nil; + if (tabStops) { var l = [tabStops count]; From 874792fa6e6e295fbed873191034dca19c3f7b0a Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 26 Aug 2026 19:01:32 +0200 Subject: [PATCH 29/35] new: constrain tab stop marker dragging to preceding text boundary --- AppKit/CPTextView/CPRulerView.j | 13 ++++- AppKit/CPTextView/CPTextView.j | 90 ++++++++++++++++++++++++++++++++ AppKit/CPTextView/CPTypesetter.j | 47 ++++++----------- 3 files changed, 118 insertions(+), 32 deletions(-) diff --git a/AppKit/CPTextView/CPRulerView.j b/AppKit/CPTextView/CPRulerView.j index 2a1080269..639e6d9cb 100644 --- a/AppKit/CPTextView/CPRulerView.j +++ b/AppKit/CPTextView/CPRulerView.j @@ -377,13 +377,17 @@ CPRulerOrientationVertical = 1; } else { + // Allow client view to constrain initial placement + var client = [self clientView]; + if (client && [client respondsToSelector:@selector(rulerView:willAddMarker:atLocation:)]) + rulerLocation = [client rulerView:self willAddMarker:nil atLocation:rulerLocation]; + var newMarker = [[CPRulerMarker alloc] initWithRulerView:self markerLocation:rulerLocation imageValue:rulerLocation representedObject:nil]; [self addMarker:newMarker]; - var client = [self clientView]; if (client && [client respondsToSelector:@selector(rulerView:didAddMarker:)]) [client rulerView:self didAddMarker:newMarker]; @@ -405,6 +409,12 @@ CPRulerOrientationVertical = 1; var delta = isHorizontal ? (localPoint.x - _dragStartPoint.x) : (localPoint.y - _dragStartPoint.y), newLocation = Math.max(0.0, _dragStartLocation + delta); + // Constrain marker location to last possible position + var client = [self clientView]; + + if (client && [client respondsToSelector:@selector(rulerView:willMoveMarker:toLocation:)]) + newLocation = [client rulerView:self willMoveMarker:_draggingMarker toLocation:newLocation]; + [_draggingMarker setImageValue:newLocation]; [self _positionMarker:_draggingMarker]; @@ -426,7 +436,6 @@ CPRulerOrientationVertical = 1; [[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]]; } - var client = [self clientView]; if (client && [client respondsToSelector:@selector(rulerView:didMoveMarker:)]) [client rulerView:self didMoveMarker:_draggingMarker]; } diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index b9257fbc1..2ccaaa0a4 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2729,6 +2729,96 @@ var compareTabStops = function(obj1, obj2, context) { [self setNeedsDisplay:YES]; } +- (float)rulerView:(CPRulerView)aRulerView willMoveMarker:(CPRulerMarker)aMarker toLocation:(float)proposedLocation +{ + var rep = [aMarker representedObject]; + + // Indent markers can be dragged anywhere >= 0 + if (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent") + return Math.max(0.0, proposedLocation); + + var textLength = [_textStorage length]; + if (textLength === 0) + return Math.max(0.0, proposedLocation); + + var selectedRange = [self selectedRange], + targetRange = selectedRange; + + if (targetRange.length === 0) + targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph]; + + if (targetRange.length === 0) + return Math.max(0.0, proposedLocation); + + [_layoutManager _validateLayoutAndGlyphs]; + + var theString = [_textStorage string], + minLocation = 0.0, + currentParagraphStyle = [_textStorage attribute:CPParagraphStyleAttributeName atIndex:targetRange.location effectiveRange:nil] || [CPParagraphStyle defaultParagraphStyle], + tabStops = [currentParagraphStyle tabStops] || []; + + // Find which tab stop index this marker represents + var targetTabIndex = -1; + for (var i = 0; i < [tabStops count]; i++) + { + var tab = [tabStops objectAtIndex:i]; + if (tab === rep || ([tab location] === [rep location] && [tab alignment] === [rep alignment])) + { + targetTabIndex = i; + break; + } + } + + var start = targetRange.location, + end = CPMaxRange(targetRange), + tabCountInLine = 0, + lineStart = start; + + for (var i = start; i < end; i++) + { + var charCode = theString.charCodeAt(i); + if (charCode === 10 || charCode === 13) + { + tabCountInLine = 0; + lineStart = i + 1; + continue; + } + + if (charCode === 9) // '\t' + { + // If this tab corresponds to the marker being dragged + if (tabCountInLine === targetTabIndex || targetTabIndex === -1) + { + var precedingX = 0.0; + if (i === lineStart) + { + var isFirstLine = (lineStart === 0 || theString.charCodeAt(lineStart - 1) === 10 || theString.charCodeAt(lineStart - 1) === 13); + precedingX = isFirstLine ? [currentParagraphStyle firstLineHeadIndent] : [currentParagraphStyle headIndent]; + } + else + { + // Find right edge of glyph immediately preceding the tab + var glyphRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(i - 1, 1) inTextContainer:_textContainer]; + precedingX = CGRectGetMaxX(glyphRect); + } + + // Minimum safety spacer (e.g. + 2px after the preceding glyph) + var limit = precedingX + 2.0; + if (limit > minLocation) + minLocation = limit; + } + tabCountInLine++; + } + } + + return Math.max(minLocation, proposedLocation); +} + +- (float)rulerView:(CPRulerView)aRulerView willAddMarker:(CPRulerMarker)aMarker atLocation:(float)proposedLocation +{ + return [self rulerView:aRulerView willMoveMarker:aMarker toLocation:proposedLocation]; +} + @end @implementation CPTextView (CPTextViewDelegate) diff --git a/AppKit/CPTextView/CPTypesetter.j b/AppKit/CPTextView/CPTypesetter.j index eee7c2efe..7100bd051 100644 --- a/AppKit/CPTextView/CPTypesetter.j +++ b/AppKit/CPTextView/CPTypesetter.j @@ -138,38 +138,30 @@ var CPSystemTypesetterFactory, return [_layoutManager textContainers]; } -// Retrieves correct CPTextTab stop accounting for CPArray properties +// Retrieves correct CPTextTab stop accounting for custom stops and default intervals - (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction { var tabStops = [_currentParagraph tabStops], defaultInterval = [_currentParagraph defaultTabInterval] || 28.0; - // Calculate the immediate next standard tab stop location based on defaultInterval - var nextDefaultLocation = (Math.floor(aWidth / defaultInterval) + 1) * defaultInterval; + var l = tabStops ? [tabStops count] : 0; - // Search for the first custom tab stop strictly greater than aWidth - var nextCustomTab = nil; - - if (tabStops) + // 1. If custom tab stops exist ahead of current position, use the first one encountered + if (l > 0) { - var l = [tabStops count]; for (var i = 0; i < l; i++) { var tab = [tabStops objectAtIndex:i]; + if ([tab location] > aWidth) - { - nextCustomTab = tab; - break; - } + return tab; } } - // If a custom tab stop exists and is closer than (or at) the next default interval, use it - if (nextCustomTab && [nextCustomTab location] <= nextDefaultLocation) - return nextCustomTab; + // 2. Otherwise (or when all custom tab stops are behind the text), advance to the next default interval + var nextLocation = (Math.floor(aWidth / defaultInterval) + 1) * defaultInterval; - // Otherwise, snap to the immediate next regular tab interval - return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextDefaultLocation]; + return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextLocation]; } - (BOOL)_flushRange:(CPRange)lineRange @@ -209,7 +201,7 @@ var CPSystemTypesetterFactory, [_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange]; [_layoutManager _setAdvancements:advancements forGlyphRange:lineRange]; - //fix the _lineFragments when fontsizes differ + // fix the _lineFragments when fontsizes differ var l = _lineFragments.length; for (var i = 0 ; i < l ; i++) @@ -255,7 +247,7 @@ var CPSystemTypesetterFactory, isTabStop = NO, isAttachment = NO, isWordWrapped = NO, - numberOfGlyphs= [_textStorage length], + numberOfGlyphs = [_textStorage length], leading, numLines = 0, theString = [_textStorage string], @@ -297,7 +289,7 @@ var CPSystemTypesetterFactory, for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++) { - // check whether there any change in the attributes from here on + // check whether there is any change in the attributes from here on if (!CPLocationInRange(glyphIndex, _attributesRange)) { _currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange]; @@ -414,10 +406,10 @@ var CPSystemTypesetterFactory, // We are processing characters, so we are no longer at the start of a physical line isStartOfPhysicalLine = NO; - var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons + var currentCharCode = theString.charCodeAt(glyphIndex), rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor; - switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char. + switch (currentCharCode) { case CPAttachmentCharacter: { @@ -449,6 +441,7 @@ var CPSystemTypesetterFactory, } case 9: // '\t' { + // Measure against the actual text position before the tab stop var nextTab = [self textTabForWidth:prevRangeWidth + lineOrigin.x writingDirection:0]; isTabStop = YES; @@ -506,10 +499,6 @@ var CPSystemTypesetterFactory, wrapRange._height = _lineHeight; wrapRange._base = _lineBase; - // Optimization: Start measuring from the next character to avoid O(n^2) - // string width calculation within a line since spaces do not carry ligatures or kerning. - // Only reset the measuring range if the next character is NOT another space. - // This prevents compounded subpixel rounding errors with contiguous spaces. if (theString.charCodeAt(glyphIndex + 1) !== 32) { currentAnchor = rangeWidth; @@ -539,7 +528,7 @@ var CPSystemTypesetterFactory, isNewline = YES; isWordWrapped = YES; - glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character + glyphIndex = CPMaxRange(lineRange) - 1; } if (isNewline || isTabStop || isAttachment) @@ -571,8 +560,6 @@ var CPSystemTypesetterFactory, containerSizeHeight = containerSize.height; } - // If this is a soft wrap (isWordWrapped), next line gets headIndent. - // If it was a paragraph return, it gets firstLineHeadIndent. isFirstLineOfParagraph = !isWordWrapped; lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent]; @@ -598,7 +585,7 @@ var CPSystemTypesetterFactory, } } - // this is to "flush" the remaining characters + // Flush remaining characters if (lineRange.length) [self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO]; From f85ac1ea103cf92a66031b3a2bdfd08959db9306 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Wed, 26 Aug 2026 19:16:12 +0200 Subject: [PATCH 30/35] new: constrain tab stop marker movement between preceding text and adjacent tab stops --- AppKit/CPTextView/CPTextView.j | 64 ++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 2ccaaa0a4..cd16562fe 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -2754,12 +2754,22 @@ var compareTabStops = function(obj1, obj2, context) { var theString = [_textStorage string], minLocation = 0.0, + containerWidth = [_textContainer containerSize].width, + maxLocation = containerWidth, currentParagraphStyle = [_textStorage attribute:CPParagraphStyleAttributeName atIndex:targetRange.location effectiveRange:nil] || [CPParagraphStyle defaultParagraphStyle], - tabStops = [currentParagraphStyle tabStops] || []; + tabStops = [currentParagraphStyle tabStops] || [], + tabCount = [tabStops count]; - // Find which tab stop index this marker represents + // 1. Right boundary: Tail indent (if configured) + var tailIndent = [currentParagraphStyle tailIndent]; + if (tailIndent > 0.0) + maxLocation = Math.min(maxLocation, tailIndent); + else if (tailIndent < 0.0) + maxLocation = Math.min(maxLocation, containerWidth + tailIndent); + + // 2. Identify active tab stop index var targetTabIndex = -1; - for (var i = 0; i < [tabStops count]; i++) + for (var i = 0; i < tabCount; i++) { var tab = [tabStops objectAtIndex:i]; if (tab === rep || ([tab location] === [rep location] && [tab alignment] === [rep alignment])) @@ -2769,6 +2779,39 @@ var compareTabStops = function(obj1, obj2, context) { } } + // 3. Constrain to neighboring tab stops (Left and Right) + var safetySpacer = 2.0; + + if (targetTabIndex !== -1) + { + // Left neighbor constraint (previous tab stop) + if (targetTabIndex > 0) + { + var prevTab = [tabStops objectAtIndex:targetTabIndex - 1]; + minLocation = Math.max(minLocation, [prevTab location] + safetySpacer); + } + + // Right neighbor constraint (next tab stop) + if (targetTabIndex + 1 < tabCount) + { + var nextTab = [tabStops objectAtIndex:targetTabIndex + 1]; + maxLocation = Math.min(maxLocation, [nextTab location] - safetySpacer); + } + } + else + { + // For new markers being added between existing tab stops + for (var i = 0; i < tabCount; i++) + { + var tabLoc = [[tabStops objectAtIndex:i] location]; + if (tabLoc < proposedLocation) + minLocation = Math.max(minLocation, tabLoc + safetySpacer); + else if (tabLoc > proposedLocation) + maxLocation = Math.min(maxLocation, tabLoc - safetySpacer); + } + } + + // 4. Constrain to preceding text on active lines var start = targetRange.location, end = CPMaxRange(targetRange), tabCountInLine = 0, @@ -2786,7 +2829,6 @@ var compareTabStops = function(obj1, obj2, context) { if (charCode === 9) // '\t' { - // If this tab corresponds to the marker being dragged if (tabCountInLine === targetTabIndex || targetTabIndex === -1) { var precedingX = 0.0; @@ -2797,21 +2839,23 @@ var compareTabStops = function(obj1, obj2, context) { } else { - // Find right edge of glyph immediately preceding the tab var glyphRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(i - 1, 1) inTextContainer:_textContainer]; precedingX = CGRectGetMaxX(glyphRect); } - // Minimum safety spacer (e.g. + 2px after the preceding glyph) - var limit = precedingX + 2.0; - if (limit > minLocation) - minLocation = limit; + var textLimit = precedingX + safetySpacer; + if (textLimit > minLocation) + minLocation = textLimit; } tabCountInLine++; } } - return Math.max(minLocation, proposedLocation); + if (minLocation > maxLocation) + minLocation = maxLocation; + + // 5. Clamp proposedLocation within [minLocation, maxLocation] + return Math.min(maxLocation, Math.max(minLocation, proposedLocation)); } - (float)rulerView:(CPRulerView)aRulerView willAddMarker:(CPRulerMarker)aMarker atLocation:(float)proposedLocation From be08bed01e02b71752d07313509bf4a5bc018061 Mon Sep 17 00:00:00 2001 From: David Richardson Date: Thu, 27 Aug 2026 11:19:56 -0600 Subject: [PATCH 31/35] Revert "Merge pull request #3256 from enquora/modernize-cgcontext-canvas" This reverts commit e107e2d8046ba84e0af7cc03372edee4af4f710d, reversing changes made to 10351c3d4a898b4d6a0f0b74e4f4c616847b66bb. --- AppKit/CoreGraphics/CGContextCanvas.j | 665 +++++++++++++++----------- 1 file changed, 381 insertions(+), 284 deletions(-) diff --git a/AppKit/CoreGraphics/CGContextCanvas.j b/AppKit/CoreGraphics/CGContextCanvas.j index 1b629af98..d8d13e1f7 100644 --- a/AppKit/CoreGraphics/CGContextCanvas.j +++ b/AppKit/CoreGraphics/CGContextCanvas.j @@ -3,7 +3,6 @@ * AppKit * * Created by Francisco Tolmasky. - * Modified by David Richardson. * Copyright 2008, 280 North, Inc. * * This library is free software; you can redistribute it and/or @@ -21,518 +20,616 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -// MODIFICATION: Converted 'var' to 'const' for immutable lookup tables. -const CANVAS_LINECAP_TABLE = [ "butt", "round", "square" ]; -const CANVAS_LINEJOIN_TABLE = [ "miter", "round", "bevel" ]; -const CANVAS_COMPOSITE_TABLE = [ "source-over", "source-over", "source-over", "source-over", "darker", - "lighter", "source-over", "source-over", "source-over", "source-over", - "source-over", "source-over", "source-over", "source-over", "source-over", - "source-over", "source-over", - "copy", "source-in", "source-out", "source-atop", - "destination-over", "destination-in", "destination-out", "destination-atop", - "xor", "source-over", "source-over" ]; +var CANVAS_LINECAP_TABLE = [ "butt", "round", "square" ], + CANVAS_LINEJOIN_TABLE = [ "miter", "round", "bevel" ], + CANVAS_COMPOSITE_TABLE = [ "source-over", "source-over", "source-over", "source-over", "darker", + "lighter", "source-over", "source-over", "source-over", "source-over", + "source-over", "source-over", "source-over", "source-over", "source-over", + "source-over", "source-over", + "copy", "source-in", "source-out", "source-atop", + "destination-over", "destination-in", "destination-out", "destination-atop", + "xor", "source-over", "source-over" ]; -// MODIFICATION: Removed all #define macros. Canvas methods are now invoked directly within the target functions -// to eliminate the preprocessor pass and simplify AST generation. +#define _CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, anticlockwise) aContext.arc(x, y, radius, startAngle, endAngle, anticlockwise) +#define _CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius) aContext.arcTo(x1, y1, x2, y2, radius) +#define _CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y) aContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) +#define _CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y) aContext.quadraticCurveTo(cpx, cpy, x, y) +#define _CGContextAddLineToPointCanvas(aContext, x, y) aContext.lineTo(x, y) +#define _CGContextClosePathCanvas(aContext) aContext.closePath() +#define _CGContextMoveToPointCanvas(aContext, x, y) aContext.moveTo(x, y) + +#define _CGContextAddRectCanvas(aContext, aRect) aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)) +#define _CGContextBeginPathCanvas(aContext) aContext.beginPath() +#define _CGContextFillRectCanvas(aContext, aRect) aContext.fillRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)) +#define _CGContextClipCanvas(aContext) aContext.clip() // In Cocoa, all primitives excepts rects and arcs cannot be added to the context's path // until a move to point has been done, because an empty path has no current point. -// MODIFICATION: Converted to const function expression and applied Allman formatting. -const hasPath = function(aContext, methodName) +var hasPath = function(aContext, methodName) { - if (!aContext.hasPath) - { - CPLog.error(methodName + ": no current point"); - } + if (!aContext.hasPath) + CPLog.error(methodName + ": no current point"); - return aContext.hasPath; -}; + return aContext.hasPath; +} function CGContextSaveGState(aContext) { - aContext.save(); + aContext.save(); } function CGContextRestoreGState(aContext) { - aContext.restore(); + aContext.restore(); } function CGContextSetLineCap(aContext, aLineCap) { - aContext.lineCap = CANVAS_LINECAP_TABLE[aLineCap]; + aContext.lineCap = CANVAS_LINECAP_TABLE[aLineCap]; } function CGContextSetLineDash(aContext, aPhase, someDashes) { - // MODIFICATION: Removed obsolete vendor prefixes (webkitLineDash, mozDash). - if (aContext.setLineDash) - { - aContext.setLineDash(someDashes); - aContext.lineDashOffset = aPhase; - } - else if (someDashes) - { - CPLog.warn("CGContextSetLineDash not implemented in this environment."); - } + if (aContext.setLineDash) + { + aContext.setLineDash(someDashes); + aContext.lineDashOffset = aPhase; + } + else if (typeof aContext['webkitLineDash'] !== 'undefined') + { + aContext.webkitLineDash = someDashes; + aContext.webkitLineDashOffset = aPhase; + } + else if (typeof aContext['mozDash'] !== 'undefined') + { + aContext.mozDash = someDashes; + aContext.mozDashOffset = aPhase; + } + else if (someDashes) + { + CPLog.warn("CGContextSetLineDash not implemented in this environment.") + } } function CGContextSetLineJoin(aContext, aLineJoin) { - aContext.lineJoin = CANVAS_LINEJOIN_TABLE[aLineJoin]; + aContext.lineJoin = CANVAS_LINEJOIN_TABLE[aLineJoin]; } function CGContextSetLineWidth(aContext, aLineWidth) { - aContext.lineWidth = aLineWidth; + aContext.lineWidth = aLineWidth; } function CGContextSetMiterLimit(aContext, aMiterLimit) { - aContext.miterLimit = aMiterLimit; + aContext.miterLimit = aMiterLimit; } function CGContextSetBlendMode(aContext, aBlendMode) { - aContext.globalCompositeOperation = CANVAS_COMPOSITE_TABLE[aBlendMode]; + aContext.globalCompositeOperation = CANVAS_COMPOSITE_TABLE[aBlendMode]; } function CGContextAddArc(aContext, x, y, radius, startAngle, endAngle, clockwise) { - // Despite the documentation saying otherwise, the last parameter is anti-clockwise not clockwise. - // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes#Arcs - // MODIFICATION: Direct invocation replacing _CGContextAddArcCanvas macro. - aContext.arc(x, y, radius, startAngle, endAngle, !clockwise); + // Despite the documentation saying otherwise, the last parameter is anti-clockwise not clockwise. + // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes#Arcs + _CGContextAddArcCanvas(aContext, x, y, radius, startAngle, endAngle, !clockwise); - // AddArc implicitly starts a path - aContext.hasPath = YES; + // AddArc implicitly starts a path + aContext.hasPath = YES; } function CGContextAddArcToPoint(aContext, x1, y1, x2, y2, radius) { - if (!hasPath(aContext, "CGContextAddArcToPoint")) - { - return; - } + if (!hasPath(aContext, "CGContextAddArcToPoint")) + return; - // MODIFICATION: Direct invocation replacing _CGContextAddArcToPointCanvas macro. - aContext.arcTo(x1, y1, x2, y2, radius); + _CGContextAddArcToPointCanvas(aContext, x1, y1, x2, y2, radius); } function CGContextAddCurveToPoint(aContext, cp1x, cp1y, cp2x, cp2y, x, y) { - if (!hasPath(aContext, "CGContextAddCurveToPoint")) - { - return; - } + if (!hasPath(aContext, "CGContextAddCurveToPoint")) + return; - // MODIFICATION: Direct invocation replacing _CGContextAddCurveToPointCanvas macro. - aContext.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y); + _CGContextAddCurveToPointCanvas(aContext, cp1x, cp1y, cp2x, cp2y, x, y); } function CGContextAddLines(aContext, points, count) { - // implementation mirrors that of CGPathAddLines() - if (count == null) - { - count = points.length; - } + // implementation mirrors that of CGPathAddLines() + if (count == null) + count = points.length; - if (count < 1) - { - return; - } + if (count < 1) + return; - // MODIFICATION: Direct invocations replacing macros. - aContext.moveTo(points[0].x, points[0].y); + _CGContextMoveToPointCanvas(aContext, points[0].x, points[0].y); - // MODIFICATION: Replaced 'var' with 'let'. - for (let i = 1; i < count; ++i) - { - aContext.lineTo(points[i].x, points[i].y); - } + for (var i = 1; i < count; ++i) + _CGContextAddLineToPointCanvas(aContext, points[i].x, points[i].y); - aContext.hasPath = YES; + aContext.hasPath = YES; } function CGContextAddLineToPoint(aContext, x, y) { - if (!hasPath(aContext, "CGContextAddLineToPoint")) - { - return; - } + if (!hasPath(aContext, "CGContextAddLineToPoint")) + return; - // MODIFICATION: Direct invocation replacing macro. - aContext.lineTo(x, y); + _CGContextAddLineToPointCanvas(aContext, x, y); } function CGContextAddPath(aContext, aPath) { - if (!aContext || CGPathIsEmpty(aPath)) - { - return; - } + if (!aContext || CGPathIsEmpty(aPath)) + return; - // If the context does not have a path, explicitly begin one - if (!aContext.hasPath) - { - aContext.beginPath(); - } + // If the context does not have a path, explicitly begin one + if (!aContext.hasPath) + _CGContextBeginPathCanvas(aContext); - // We must implicitly move to the start of the path - aContext.moveTo(aPath.start.x, aPath.start.y); + // We must implicitly move to the start of the path + _CGContextMoveToPointCanvas(aContext, aPath.start.x, aPath.start.y); - // MODIFICATION: Replaced 'var' with 'const'/'let'. - const elements = aPath.elements, - count = aPath.count; + var elements = aPath.elements, + i = 0, + count = aPath.count; - for (let i = 0; i < count; ++i) - { - const element = elements[i], - type = element.type; + for (; i < count; ++i) + { + var element = elements[i], + type = element.type; - switch (type) - { - case kCGPathElementMoveToPoint: - aContext.moveTo(element.x, element.y); - break; + switch (type) + { + case kCGPathElementMoveToPoint: + _CGContextMoveToPointCanvas(aContext, element.x, element.y); + break; - case kCGPathElementAddLineToPoint: - aContext.lineTo(element.x, element.y); - break; + case kCGPathElementAddLineToPoint: + _CGContextAddLineToPointCanvas(aContext, element.x, element.y); + break; - case kCGPathElementAddQuadCurveToPoint: - aContext.quadraticCurveTo(element.cpx, element.cpy, element.x, element.y); - break; + case kCGPathElementAddQuadCurveToPoint: + _CGContextAddQuadCurveToPointCanvas(aContext, element.cpx, element.cpy, element.x, element.y); + break; - case kCGPathElementAddCurveToPoint: - aContext.bezierCurveTo(element.cp1x, element.cp1y, element.cp2x, element.cp2y, element.x, element.y); - break; + case kCGPathElementAddCurveToPoint: + _CGContextAddCurveToPointCanvas(aContext, element.cp1x, element.cp1y, element.cp2x, element.cp2y, element.x, element.y); + break; - case kCGPathElementCloseSubpath: - aContext.closePath(); - break; + case kCGPathElementCloseSubpath: + _CGContextClosePathCanvas(aContext); + break; - case kCGPathElementAddArc: - aContext.arc(element.x, element.y, element.radius, element.startAngle, element.endAngle, element.clockwise); - break; + case kCGPathElementAddArc: + _CGContextAddArcCanvas(aContext, element.x, element.y, element.radius, element.startAngle, element.endAngle, element.clockwise); + break; - case kCGPathElementAddArcToPoint: - aContext.arcTo(element.p1x, element.p1y, element.p2x, element.p2y, element.radius); - break; - } - } + case kCGPathElementAddArcToPoint: + _CGContextAddArcToPointCanvas(aContext, element.p1x, element.p1y, element.p2x, element.p2y, element.radius); + break; + } + } - aContext.hasPath = YES; + aContext.hasPath = YES; } function CGContextAddRect(aContext, aRect) { - // MODIFICATION: Direct invocation replacing _CGContextAddRectCanvas macro. - aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.hasPath = YES; + _CGContextAddRectCanvas(aContext, aRect); + aContext.hasPath = YES; } function CGContextAddQuadCurveToPoint(aContext, cpx, cpy, x, y) { - if (!hasPath(aContext, "CGContextAddQuadCurveToPoint")) - { - return; - } + if (!hasPath(aContext, "CGContextAddQuadCurveToPoint")) + return; - // MODIFICATION: Direct invocation replacing macro. - aContext.quadraticCurveTo(cpx, cpy, x, y); + _CGContextAddQuadCurveToPointCanvas(aContext, cpx, cpy, x, y); } function CGContextAddRects(aContext, rects, count) { - if (count == null) - { - count = rects.length; - } + if (count == null) + count = rects.length; - for (let i = 0; i < count; ++i) - { - const rect = rects[i]; - aContext.rect(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect)); - } + for (var i = 0; i < count; ++i) + { + var rect = rects[i]; + _CGContextAddRectCanvas(aContext, rect); + } - aContext.hasPath = YES; + aContext.hasPath = YES; } function CGContextBeginPath(aContext) { - aContext.beginPath(); - aContext.hasPath = NO; + _CGContextBeginPathCanvas(aContext); + aContext.hasPath = NO; } function CGContextClosePath(aContext) { - aContext.closePath(); + _CGContextClosePathCanvas(aContext); } function CGContextIsPathEmpty(aContext) { - return !aContext.hasPath; + return !aContext.hasPath; } function CGContextMoveToPoint(aContext, x, y) { - aContext.moveTo(x, y); - aContext.hasPath = YES; + _CGContextMoveToPointCanvas(aContext, x, y); + aContext.hasPath = YES; } function CGContextClearRect(aContext, aRect) { - aContext.clearRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.hasPath = NO; + aContext.clearRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.hasPath = NO; } function CGContextDrawPath(aContext, aMode) { - if (!aContext.hasPath) - { - return; - } + if (!aContext.hasPath) + return; - if (aMode === kCGPathFill || aMode === kCGPathFillStroke) - { - aContext.fill(); - } - else if (aMode === kCGPathStroke || aMode === kCGPathFillStroke || aMode === kCGPathEOFillStroke) - { - aContext.stroke(); - } - else if (aMode === kCGPathEOFill || aMode === kCGPathEOFillStroke) - { - CPLog.warn("Unimplemented fill mode in CGContextDrawPath: %d", aMode); - } + if (aMode === kCGPathFill || aMode === kCGPathFillStroke) + aContext.fill(); + else if (aMode === kCGPathStroke || aMode === kCGPathFillStroke || aMode === kCGPathEOFillStroke) + aContext.stroke(); + else if (aMode === kCGPathEOFill || aMode === kCGPathEOFillStroke) + CPLog.warn("Unimplemented fill mode in CGContextDrawPath: %d", aMode); - aContext.hasPath = NO; + aContext.hasPath = NO; } function CGContextFillRect(aContext, aRect) { - // MODIFICATION: Direct invocation replacing macro. - aContext.fillRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.hasPath = NO; + _CGContextFillRectCanvas(aContext, aRect); + aContext.hasPath = NO; } function CGContextFillRects(aContext, rects, count) { - if (count == null) - { - count = rects.length; - } + if (count == null) + count = rects.length; - for (let i = 0; i < count; ++i) - { - const rect = rects[i]; - aContext.fillRect(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect), CGRectGetHeight(rect)); - } + for (var i = 0; i < count; ++i) + { + var rect = rects[i]; + _CGContextFillRectCanvas(aContext, rect); + } - aContext.hasPath = NO; + aContext.hasPath = NO; } function CGContextStrokeRect(aContext, aRect) { - aContext.strokeRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.hasPath = NO; + aContext.strokeRect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.hasPath = NO; } function CGContextClip(aContext) { - aContext.clip(); - aContext.hasPath = NO; + _CGContextClipCanvas(aContext); + aContext.hasPath = NO; } function CGContextClipToRect(aContext, aRect) { - aContext.beginPath(); - aContext.rect(CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.closePath(); + _CGContextBeginPathCanvas(aContext); + _CGContextAddRectCanvas(aContext, aRect); + _CGContextClosePathCanvas(aContext); - aContext.clip(); - aContext.hasPath = NO; + _CGContextClipCanvas(aContext); + aContext.hasPath = NO; } function CGContextClipToRects(aContext, rects, count) { - if (count == null) - { - count = rects.length; - } + if (count == null) + count = rects.length; - aContext.beginPath(); - CGContextAddRects(aContext, rects, count); - aContext.clip(); - aContext.hasPath = NO; + _CGContextBeginPathCanvas(aContext); + CGContextAddRects(aContext, rects, count); + _CGContextClipCanvas(aContext); + aContext.hasPath = NO; } function CGContextSetAlpha(aContext, anAlpha) { - aContext.globalAlpha = anAlpha; + aContext.globalAlpha = anAlpha; } function CGContextSetFillColor(aContext, aColor) { - const patternImage = [aColor patternImage]; + var patternImage = [aColor patternImage]; - if ([patternImage isSingleImage]) - { - const pattern = aContext.createPattern([patternImage image], "repeat"); - aContext.fillStyle = pattern; - } - else - { - aContext.fillStyle = [aColor cssString]; - } + if ([patternImage isSingleImage]) + { + var pattern = aContext.createPattern([patternImage image], "repeat"); + + aContext.fillStyle = pattern; + } + else + aContext.fillStyle = [aColor cssString]; } /*! - Creates a context into which you can render a fill pattern - of the given size. Once the pattern is rendered, you can - set the fill or stroke pattern to the rendered pattern - with CGContextSetFillPattern or CGContextSetStrokePattern. - */ + Creates a context into which you can render a fill pattern + of the given size. Once the pattern is rendered, you can + set the fill or stroke pattern to the rendered pattern + with CGContextSetFillPattern or CGContextSetStrokePattern. +*/ function CGContextCreatePatternContext(aContext, aSize) { - const pattern = document.createElement("canvas"); + var pattern = document.createElement("canvas"); - pattern.width = aSize.width; - pattern.height = aSize.height; + pattern.width = aSize.width; + pattern.height = aSize.height; - return pattern.getContext("2d"); + return pattern.getContext("2d"); } /*! - Sets the fill pattern for aContext to the rendered pattern context - returned by CGContextCreatePatternContext. - */ + Sets the fill pattern for aContext to the rendered pattern context + returned by CGContextCreatePatternContext. +*/ function CGContextSetFillPattern(aContext, aPatternContext) { - const pattern = aContext.createPattern(aPatternContext.canvas, "repeat"); - aContext.fillStyle = pattern; + var pattern = aContext.createPattern(aPatternContext.canvas, "repeat"); + aContext.fillStyle = pattern; } /*! - Sets the stroke pattern for aContext to the rendered pattern context - returned by CGContextCreatePatternContext. - */ + Sets the stroke pattern for aContext to the rendered pattern context + returned by CGContextCreatePatternContext. +*/ function CGContextSetStrokePattern(aContext, aPatternContext) { - const pattern = aContext.createPattern(aPatternContext.canvas, "repeat"); - aContext.strokeStyle = pattern; + var pattern = aContext.createPattern(aPatternContext.canvas, "repeat"); + aContext.strokeStyle = pattern; } function CGContextSetStrokeColor(aContext, aColor) { - const patternImage = [aColor patternImage]; + var patternImage = [aColor patternImage]; - if ([patternImage isSingleImage]) - { - const pattern = aContext.createPattern([patternImage image], "repeat"); - aContext.strokeStyle = pattern; - } - else - { - aContext.strokeStyle = [aColor cssString]; - } + if ([patternImage isSingleImage]) + { + var pattern = aContext.createPattern([patternImage image], "repeat"); + + aContext.strokeStyle = pattern; + } + else + aContext.strokeStyle = [aColor cssString]; } function CGContextSetShadow(aContext, aSize, aBlur) { - aContext.shadowOffsetX = aSize.width; - aContext.shadowOffsetY = aSize.height; - aContext.shadowBlur = aBlur; - aContext.shadowColor = [[CPColor shadowColor] cssString]; + aContext.shadowOffsetX = aSize.width; + aContext.shadowOffsetY = aSize.height; + aContext.shadowBlur = aBlur; + aContext.shadowColor = [[CPColor shadowColor] cssString]; } function CGContextSetShadowWithColor(aContext, aSize, aBlur, aColor) { - aContext.shadowOffsetX = aSize.width; - aContext.shadowOffsetY = aSize.height; - aContext.shadowBlur = aBlur; - aContext.shadowColor = [aColor cssString]; + aContext.shadowOffsetX = aSize.width; + aContext.shadowOffsetY = aSize.height; + aContext.shadowBlur = aBlur; + aContext.shadowColor = [aColor cssString]; } function CGContextRotateCTM(aContext, anAngle) { - aContext.rotate(anAngle); + aContext.rotate(anAngle); } function CGContextScaleCTM(aContext, sx, sy) { - aContext.scale(sx, sy); + aContext.scale(sx, sy); } function CGContextTranslateCTM(aContext, tx, ty) { - aContext.translate(tx, ty); + aContext.translate(tx, ty); } -// MODIFICATION: Removed legacy mathematical fallback macros (scale_rotate, rotate_scale, eigen). -// MODIFICATION: Removed CPFeatureIsCompatible(CPJavaScriptCanvasTransformFeature) branch. -// Modern target environments inherently support native canvas transformations. - -function CGContextConcatCTM(aContext, anAffineTransform) +var scale_rotate = function(a, b, c, d) { - aContext.transform(anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty); + var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0, + a2 = (ATAN2(b, d) + ATAN2(-sign * c, sign * a)) / 2.0, + cos = COS(a2), + sin = SIN(a2); + + if (cos === 0) + { + sx = -c / sin; + sy = b / sin; + } + else if (sin === 0) + { + sx = a / cos; + sy = d / cos; + } + else + { + abs_cos = ABS(cos); + abs_sin = ABS(sin); + + sx = (abs_cos * a / cos + abs_sin * -c / sin) / (abs_cos + abs_sin); + sy = (abs_cos * d / cos + abs_sin * b / sin) / (abs_cos + abs_sin); + } +}; + +var rotate_scale = function(a, b, c, d) +{ + var sign = (a * d < 0.0 || b * c > 0.0) ? -1.0 : 1.0, + a1 = (ATAN2(sign * b, sign * a) + ATAN2(-c, d)) / 2.0, + cos = COS(a1), + sin = SIN(a1); + + if (cos === 0) + { + sx = b / sin; + sy = -c / sin; + } + else if (sin === 0) + { + sx = a / cos; + sy = d / cos; + } + else + { + abs_cos = ABS(cos); + abs_sin = ABS(sin); + + sx = (abs_cos * a / cos + abs_sin * b / sin) / (abs_cos + abs_sin); + sy = (abs_cos * d / cos + abs_sin * -c / sin) / (abs_cos + abs_sin); + } +}; + +function eigen(anAffineTransform) +{ + CPLog.warn("Unimplemented function: eigen"); +} + + +if (CPFeatureIsCompatible(CPJavaScriptCanvasTransformFeature)) +{ + +CGContextConcatCTM = function(aContext, anAffineTransform) +{ + aContext.transform(anAffineTransform.a, anAffineTransform.b, anAffineTransform.c, anAffineTransform.d, anAffineTransform.tx, anAffineTransform.ty); +}; + +} +else +{ + +CGContextConcatCTM = function(aContext, anAffineTransform) +{ + var a = anAffineTransform.a, + b = anAffineTransform.b, + c = anAffineTransform.c, + d = anAffineTransform.d, + tx = anAffineTransform.tx, + ty = anAffineTransform.ty, + sx = 1.0, + sy = 1.0, + a1 = 0.0, + a2 = 0.0; + + // Detect the simple case of just scaling. + if (b === 0.0 && c === 0.0) + { + sx = a; + sy = d; + } + + // a scale followed by a rotate + else if (a * b === -c * d) + { + scale_rotate(a, b, c, d) + } + + // rotate, then scale. + else if (a * c === -b * d) + { + rotate_scale(a, b, c, d) + } + else + { + var transpose = CGAffineTransformMake(a, c, b, d, 0.0, 0.0), // inline + u = eigen(CGAffineTransformConcat(anAffineTransform, transpose)), + v = eigen(CGAffineTransformConcat(transpose, anAffineTransform)), + U = CGAffineTransformMake(u.vector_1.x, u.vector_2.x, u.vector_1.y, u.vector_2.y, 0.0, 0.0), // inline + VT = CGAffineTransformMake(v.vector_1.x, v.vector_1.y, v.vector_2.x, v.vector_2.y, 0.0, 0.0), + S = CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformInvert(U), anAffineTransform), CGAffineTransformInvert(VT)); + + a = VT.a; + b = VT.b; + c = VT.c; + d = VT.d; + scale_rotate(a, b, c, d); + S.a *= sx; + S.d *= sy; + a = U.a; + b = U.b; + c = U.c; + d = U.d; + rotate_scale(a, b, c, d); + sx = S.a * sx; + sy = S.d * sy; + } + + if (tx != 0 || ty != 0) + CGContextTranslateCTM(aContext, tx, ty); + if (a1 != 0.0) + CGContextRotateCTM(aContext, a1); + if (sx != 1.0 || sy != 1.0) + CGContextScaleCTM(aContext, sx, sy); + if (a2 != 0.0) + CGContextRotateCTM(aContext, a2); +}; + } function CGContextDrawImage(aContext, aRect, anImage) { - aContext.drawImage(anImage._image, CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); - aContext.hasPath = NO; + aContext.drawImage(anImage._image, CGRectGetMinX(aRect), CGRectGetMinY(aRect), CGRectGetWidth(aRect), CGRectGetHeight(aRect)); + aContext.hasPath = NO; } function to_string(aColor) { - // MODIFICATION: Replaced macro ROUND with standard Math.round. - return "rgba(" + Math.round(aColor.components[0] * 255) + ", " + Math.round(aColor.components[1] * 255) + ", " + Math.round(255 * aColor.components[2]) + ", " + aColor.components[3] + ")"; + return "rgba(" + ROUND(aColor.components[0] * 255) + ", " + ROUND(aColor.components[1] * 255) + ", " + ROUND(255 * aColor.components[2]) + ", " + aColor.components[3] + ")"; } function CGContextDrawLinearGradient(aContext, aGradient, aStartPoint, anEndPoint, options) { - const colors = aGradient.colors; - const linearGradient = aContext.createLinearGradient(aStartPoint.x, aStartPoint.y, anEndPoint.x, anEndPoint.y); - let count = colors.length; + var colors = aGradient.colors, + count = colors.length, + linearGradient = aContext.createLinearGradient(aStartPoint.x, aStartPoint.y, anEndPoint.x, anEndPoint.y); - while (count--) - { - linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count])); - } + while (count--) + linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count])); - aContext.fillStyle = linearGradient; - aContext.fill(); - aContext.hasPath = NO; + aContext.fillStyle = linearGradient; + aContext.fill(); + aContext.hasPath = NO; } function CGContextDrawRadialGradient(aContext, aGradient, aStartCenter, aStartRadius, anEndCenter, anEndRadius, options) { - const colors = aGradient.colors; - const linearGradient = aContext.createRadialGradient(aStartCenter.x, aStartCenter.y, aStartRadius, anEndCenter.x, anEndCenter.y, anEndRadius); - let count = colors.length; + var colors = aGradient.colors, + count = colors.length, + linearGradient = aContext.createRadialGradient(aStartCenter.x, aStartCenter.y, aStartRadius, anEndCenter.x, anEndCenter.y, anEndRadius); - while (count--) - { - linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count])); - } + while (count--) + linearGradient.addColorStop(aGradient.locations[count], to_string(colors[count])); - aContext.fillStyle = linearGradient; - aContext.fill(); - aContext.hasPath = NO; + aContext.fillStyle = linearGradient; + aContext.fill(); + aContext.hasPath = NO; } function CGBitmapGraphicsContextCreate() { - const DOMElement = document.createElement("canvas"); - const context = DOMElement.getContext("2d"); + var DOMElement = document.createElement("canvas"), + context = DOMElement.getContext("2d"); - context.DOMElement = DOMElement; + context.DOMElement = DOMElement; - // canvas gives us no way to query whether the path is empty or not, so we have to track it ourselves - context.hasPath = NO; + // canvas gives us no way to query whether the path is empty or not, so we have to track it ourselves + context.hasPath = NO; - return context; + return context; } From 4f29b77d652acec5f5cbb55c49bf86f74755d7d5 Mon Sep 17 00:00:00 2001 From: David Richardson Date: Thu, 27 Aug 2026 11:20:20 -0600 Subject: [PATCH 32/35] Revert "Merge pull request #3261 from enquora/modernize-CPPlatformpasteboard" This reverts commit 10351c3d4a898b4d6a0f0b74e4f4c616847b66bb, reversing changes made to 5bdac195fc0495e05ac241e3ff41b292e09009f7. --- AppKit/Platform/DOM/CPPlatformPasteboard.j | 661 ++++++++++----------- 1 file changed, 322 insertions(+), 339 deletions(-) diff --git a/AppKit/Platform/DOM/CPPlatformPasteboard.j b/AppKit/Platform/DOM/CPPlatformPasteboard.j index 6bc533eda..102feb670 100644 --- a/AppKit/Platform/DOM/CPPlatformPasteboard.j +++ b/AppKit/Platform/DOM/CPPlatformPasteboard.j @@ -37,24 +37,22 @@ #if PLATFORM(DOM) -// MODIFICATION: Removed #define macros for SUPPRESS_CAPPUCCINO_CUT/PASTE. -// Property assignments are now inlined directly at call sites for readability. +#define SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent) anEvent._suppressCappuccinoCut = YES +#define SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent) anEvent._suppressCappuccinoPaste = YES var hasEditableTarget = function(aDOMEvent) { - // MODIFICATION: Removed IE 'srcElement' fallback. Replaced 'var' with 'let'. - let target = aDOMEvent.target; + var target = aDOMEvent.target || aDOMEvent.srcElement; - if (!target) - return NO; + if (!target) + return NO; - if (target.contentEditable == "true") - return YES; + if (target.contentEditable == "true") + return YES; - // MODIFICATION: Replaced 'var' with 'let'. - let nodeName = target.nodeName.toUpperCase(); + var nodeName = target.nodeName.toUpperCase(); - return nodeName === "INPUT" || nodeName == "TEXTAREA"; + return nodeName === "INPUT" || nodeName == "TEXTAREA"; } /* @@ -65,481 +63,466 @@ var hasEditableTarget = function(aDOMEvent) */ @implementation CPPlatformPasteboard : CPObject { - DOMWindow _DOMWindow; + DOMWindow _DOMWindow; - DOMElement _DOMPasteboardElement; + DOMElement _DOMPasteboardElement; - BOOL supportsNativeCopyAndPaste; - BOOL hasBugWhichPreventsNonEditablePaste; - BOOL hasBugWhichPreventsNonEditablePasteRedirect; + BOOL supportsNativeCopyAndPaste; + BOOL hasBugWhichPreventsNonEditablePaste; + BOOL hasBugWhichPreventsNonEditablePasteRedirect; - BOOL currentEventIsNativePasteEvent; - BOOL currentEventIsNativeCopyOrCutEvent; - BOOL currentEventShouldBeSuppressed; - BOOL currentEventShouldDefinitelyBubble; - BOOL currentEventShouldDefinitelyNotBubble; + BOOL currentEventIsNativePasteEvent; + BOOL currentEventIsNativeCopyOrCutEvent; + BOOL currentEventShouldBeSuppressed; + BOOL currentEventShouldDefinitelyBubble; + BOOL currentEventShouldDefinitelyNotBubble; - BOOL _ignoreNativeCopyOrCutEvent; - BOOL _ignoreNativePastePreparation; + BOOL _ignoreNativeCopyOrCutEvent; + BOOL _ignoreNativePastePreparation; } - (id)init { - if (self = [super init]) - { - supportsNativeCopyAndPaste = CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature); - hasBugWhichPreventsNonEditablePaste = CPPlatformHasBug(CPJavaScriptPasteRequiresEditableTarget); - hasBugWhichPreventsNonEditablePasteRedirect = CPPlatformHasBug(CPJavaScriptPasteCantRefocus); - } + if (self = [super init]) + { + supportsNativeCopyAndPaste = CPFeatureIsCompatible(CPJavaScriptClipboardEventsFeature); + hasBugWhichPreventsNonEditablePaste = CPPlatformHasBug(CPJavaScriptPasteRequiresEditableTarget); + hasBugWhichPreventsNonEditablePasteRedirect = CPPlatformHasBug(CPJavaScriptPasteCantRefocus); + } - return self; + return self; } - (void)setDOMWindow:(DOMWindow)aDOMWindow { - if (_DOMWindow === aDOMWindow) - return; + if (_DOMWindow === aDOMWindow) + return; - if (_DOMWindow) - [self destroyDOMElements]; + if (_DOMWindow) + [self destroyDOMElements]; - _DOMWindow = aDOMWindow; + _DOMWindow = aDOMWindow; - if (_DOMWindow) - [self createDOMElements]; + if (_DOMWindow) + [self createDOMElements]; } - (void)createDOMElements { - // MODIFICATION: Replaced 'var' with 'const'/'let'. - const theDocument = _DOMWindow.document, - _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; + var theDocument = _DOMWindow.document, + _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; - // Create Native Pasteboard handler. - _DOMPasteboardElement = theDocument.createElement("textarea"); + // Create Native Pasteboard handler. + _DOMPasteboardElement = theDocument.createElement("textarea"); - _DOMPasteboardElement.style.position = "absolute"; - _DOMPasteboardElement.style.top = "-10000px"; - _DOMPasteboardElement.style.zIndex = "999"; - _DOMPasteboardElement.className = "cpdontremove"; + _DOMPasteboardElement.style.position = "absolute"; + _DOMPasteboardElement.style.top = "-10000px"; + _DOMPasteboardElement.style.zIndex = "999"; + _DOMPasteboardElement.className = "cpdontremove"; - _DOMBodyElement.appendChild(_DOMPasteboardElement); + _DOMBodyElement.appendChild(_DOMPasteboardElement); - _DOMPasteboardElement.blur(); + _DOMPasteboardElement.blur(); - // MODIFICATION: Replaced 'var' with 'const'. - const copyEventCallback = function (anEvent) { return [self beforeCopyEvent:anEvent]; }, - nativeBeforeClipboardEventCallback = function (anEvent) { return [self nativeBeforeClipboardEvent:anEvent]; }, - nativeCopyOrCutEventCallback = function (anEvent) { return [self nativeCopyOrCutEvent:anEvent]; }, - pasteEventCallback = function (anEvent) { return [self beforePasteEvent:anEvent]; }, - nativePasteEventCallback = function (anEvent) { return [self nativePasteEvent:anEvent]; }; + var copyEventCallback = function (anEvent) { return [self beforeCopyEvent:anEvent]; }, + nativeBeforeClipboardEventCallback = function (anEvent) { return [self nativeBeforeClipboardEvent:anEvent]; }, + nativeCopyOrCutEventCallback = function (anEvent) { return [self nativeCopyOrCutEvent:anEvent]; }, + pasteEventCallback = function (anEvent) { return [self beforePasteEvent:anEvent]; }, + nativePasteEventCallback = function (anEvent) { return [self nativePasteEvent:anEvent]; }; - // MODIFICATION: Removed the 'if (theDocument.addEventListener)' check and the IE 'else' block. - // Modern browsers natively support addEventListener. - if (supportsNativeCopyAndPaste) - { - _DOMWindow.addEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.addEventListener("copy", nativeCopyOrCutEventCallback, NO); - _DOMWindow.addEventListener("cut", nativeCopyOrCutEventCallback, NO); - _DOMWindow.addEventListener("paste", nativePasteEventCallback, NO); - } - else - { - theDocument.addEventListener("beforepaste", pasteEventCallback, NO); - theDocument.addEventListener("beforecopy", copyEventCallback, NO); - theDocument.addEventListener("beforecut", copyEventCallback, NO); - } + if (theDocument.addEventListener) + { + if (supportsNativeCopyAndPaste) + { + _DOMWindow.addEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.addEventListener("copy", nativeCopyOrCutEventCallback, NO); + _DOMWindow.addEventListener("cut", nativeCopyOrCutEventCallback, NO); + _DOMWindow.addEventListener("paste", nativePasteEventCallback, NO); + } + else + { + theDocument.addEventListener("beforepaste", pasteEventCallback, NO); + theDocument.addEventListener("beforecopy", copyEventCallback, NO); + theDocument.addEventListener("beforecut", copyEventCallback, NO); + } - _DOMWindow.addEventListener("unload", function() - { - if (supportsNativeCopyAndPaste) - { - _DOMWindow.removeEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); - _DOMWindow.removeEventListener("copy", nativeCopyOrCutEventCallback, NO); - _DOMWindow.removeEventListener("cut", nativeCopyOrCutEventCallback, NO); - _DOMWindow.removeEventListener("paste", nativePasteEventCallback, NO); - } - else - { - theDocument.removeEventListener("beforepaste", pasteEventCallback, NO); - theDocument.removeEventListener("beforecopy", copyEventCallback, NO); - theDocument.removeEventListener("beforecut", copyEventCallback, NO); - } - }, NO); + _DOMWindow.addEventListener("unload", function() + { + if (supportsNativeCopyAndPaste) + { + _DOMWindow.removeEventListener("beforecopy", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("beforecut", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("beforepaste", nativeBeforeClipboardEventCallback, NO); + _DOMWindow.removeEventListener("copy", nativeCopyOrCutEventCallback, NO); + _DOMWindow.removeEventListener("cut", nativeCopyOrCutEventCallback, NO); + _DOMWindow.removeEventListener("paste", nativePasteEventCallback, NO); + } + else + { + theDocument.removeEventListener("beforepaste", pasteEventCallback, NO); + theDocument.removeEventListener("beforecopy", copyEventCallback, NO); + theDocument.removeEventListener("beforecut", copyEventCallback, NO); + } + }, NO); + } + else + { + // TODO If we wanted IE 8 and lower copy and paste it'd go here. + } } - (void)destroyDOMElements { - // MODIFICATION: Replaced 'var' with 'const'. - const theDocument = _DOMWindow.document, - _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; + var theDocument = _DOMWindow.document, + _DOMBodyElement = theDocument.getElementById("cappuccino-body") || theDocument.body; - _DOMBodyElement.removeChild(_DOMPasteboardElement); - _DOMPasteboardElement = nil; + _DOMBodyElement.removeChild(_DOMPasteboardElement); + _DOMPasteboardElement = nil; } - (void)windowMaySendKeyEvent:(CPEvent)anEvent { - // Reset our opinions. - currentEventIsNativePasteEvent = NO; - currentEventIsNativeCopyOrCutEvent = NO; - currentEventShouldBeSuppressed = NO; - currentEventShouldDefinitelyNotBubble = NO; - currentEventShouldDefinitelyBubble = NO; + // Reset our opinions. + currentEventIsNativePasteEvent = NO; + currentEventIsNativeCopyOrCutEvent = NO; + currentEventShouldBeSuppressed = NO; + currentEventShouldDefinitelyNotBubble = NO; + currentEventShouldDefinitelyBubble = NO; - if (!anEvent) - return; + if (!anEvent) + return; - if ([anEvent type] !== CPKeyDown) - { - // Reset these flags on key up. - _ignoreNativePastePreparation = NO; - _ignoreNativeCopyOrCutEvent = NO; - return; - } + if ([anEvent type] !== CPKeyDown) + { + // Reset these flags on key up. + _ignoreNativePastePreparation = NO; + _ignoreNativeCopyOrCutEvent = NO; + return; + } - // MODIFICATION: Replaced 'var' with 'const'. - const modifierFlags = [anEvent modifierFlags]; + var modifierFlags = [anEvent modifierFlags]; - if (!(modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) - return; + if (!(modifierFlags & (CPControlKeyMask | CPCommandKeyMask))) + return; - const aDOMEvent = anEvent._DOMEvent, - characters = [anEvent characters], - mayRequireDOMPasteboardElement = [self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:modifierFlags]; + var aDOMEvent = anEvent._DOMEvent, + characters = [anEvent characters], + mayRequireDOMPasteboardElement = [self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:modifierFlags]; - if (characters === "v" && mayRequireDOMPasteboardElement) - { - if (supportsNativeCopyAndPaste && hasBugWhichPreventsNonEditablePaste && hasBugWhichPreventsNonEditablePasteRedirect && !hasEditableTarget(aDOMEvent)) - { - // You can't paste from the system clipboard into a non-editable area in Safari, neither using native - // copy and paste nor our _DOMPasteboardElement hack. We will paste from the Cappuccino pasteboard only - // and allow Safari to "beep" to indicate something went wrong. + if (characters === "v" && mayRequireDOMPasteboardElement) + { + if (supportsNativeCopyAndPaste && hasBugWhichPreventsNonEditablePaste && hasBugWhichPreventsNonEditablePasteRedirect && !hasEditableTarget(aDOMEvent)) + { + // You can't paste from the system clipboard into a non-editable area in Safari, neither using native + // copy and paste nor our _DOMPasteboardElement hack. We will paste from the Cappuccino pasteboard only + // and allow Safari to "beep" to indicate something went wrong. - // The key down will not result in a paste event being sent by Safari, so it's not a paste event. - currentEventIsNativePasteEvent = NO; - // Yes to get the beep. - currentEventShouldDefinitelyBubble = YES; - } - else if (!(supportsNativeCopyAndPaste || hasBugWhichPreventsNonEditablePaste) && !_ignoreNativePastePreparation) - { - // We don't support native copy and paste so we must focus the _DOMPasteboardElement to receive the - // paste content. This needs to be done at keyDown time (or in the case of modern Safari, before the keyDown, - // which unfortunately isn't possible. See above.) - _DOMPasteboardElement.focus(); - _DOMPasteboardElement.select(); - _DOMPasteboardElement.value = ""; - _DOMWindow.setNativeTimeout(function () { [self _checkDOMPasteboardElement]; }, 0); + // The key down will not result in a paste event being sent by Safari, so it's not a paste event. + currentEventIsNativePasteEvent = NO; + // Yes to get the beep. + currentEventShouldDefinitelyBubble = YES; + } + else if (!(supportsNativeCopyAndPaste || hasBugWhichPreventsNonEditablePaste) && !_ignoreNativePastePreparation) + { + // We don't support native copy and paste so we must focus the _DOMPasteboardElement to receive the + // paste content. This needs to be done at keyDown time (or in the case of modern Safari, before the keyDown, + // which unfortunately isn't possible. See above.) + _DOMPasteboardElement.focus(); + _DOMPasteboardElement.select(); + _DOMPasteboardElement.value = ""; + _DOMWindow.setNativeTimeout(function () { [self _checkDOMPasteboardElement]; }, 0); - currentEventIsNativePasteEvent = YES; - } - else if (supportsNativeCopyAndPaste) - currentEventIsNativePasteEvent = YES; + currentEventIsNativePasteEvent = YES; + } + else if (supportsNativeCopyAndPaste) + currentEventIsNativePasteEvent = YES; - if (currentEventIsNativePasteEvent) - { - // We need the event to propagate or nothing will be pasted. - currentEventShouldDefinitelyBubble = YES; + if (currentEventIsNativePasteEvent) + { + // We need the event to propagate or nothing will be pasted. + currentEventShouldDefinitelyBubble = YES; - // And we don't send the keydown because either our native paste envent handler will send it, - // or the _checkDOMPasteboardElement check will. - currentEventShouldBeSuppressed = YES; - } - } - else if ((characters == "c" || characters == "x") && mayRequireDOMPasteboardElement) - { - currentEventIsNativeCopyOrCutEvent = YES; + // And we don't send the keydown because either our native paste envent handler will send it, + // or the _checkDOMPasteboardElement check will. + currentEventShouldBeSuppressed = YES; + } + } + else if ((characters == "c" || characters == "x") && mayRequireDOMPasteboardElement) + { + currentEventIsNativeCopyOrCutEvent = YES; - // If _ignoreNativeCopyOrCutEvent, we already handled the copy/cut in beforeCopyEvent:. - // If supportsNativeCopyAndPaste, we will be handling it in the nativebeforeCopyEvent: handler. - // In both of those cases we don't want to send the event on keydown too, or we'll get 2X copy/cut operations. - if (supportsNativeCopyAndPaste || _ignoreNativeCopyOrCutEvent) - currentEventShouldBeSuppressed = YES; - } + // If _ignoreNativeCopyOrCutEvent, we already handled the copy/cut in beforeCopyEvent:. + // If supportsNativeCopyAndPaste, we will be handling it in the nativebeforeCopyEvent: handler. + // In both of those cases we don't want to send the event on keydown too, or we'll get 2X copy/cut operations. + if (supportsNativeCopyAndPaste || _ignoreNativeCopyOrCutEvent) + currentEventShouldBeSuppressed = YES; + } - if (CPFeatureIsCompatible(CPBackspaceTriggersPageBack) && characters === CPDeleteCharacter && !hasEditableTarget(aDOMEvent)) - currentEventShouldBeSuppressed = YES; + if (CPFeatureIsCompatible(CPBackspaceTriggersPageBack) && characters === CPDeleteCharacter && !hasEditableTarget(aDOMEvent)) + currentEventShouldBeSuppressed = YES; - if (!currentEventShouldBeSuppressed) - { - // MODIFICATION: Inlined the SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT macro. - if (characters === "v") - anEvent._suppressCappuccinoPaste = YES; - // MODIFICATION: Inlined the SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT macro. - else if (characters === "x") - anEvent._suppressCappuccinoCut = YES; - } + if (!currentEventShouldBeSuppressed) + { + if (characters === "v") + SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent); + else if (characters === "x") + SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent); + } } - (BOOL)windowShouldSuppressKeyEvent { - return currentEventShouldBeSuppressed; + return currentEventShouldBeSuppressed; } - (void)windowDidSendKeyEvent:(CPEvent)anEvent { - // Now that the copy event has been sent through the Cappuccino event stack, we can load any Cappuccino - // pasteboard string into our DOMPasteboardElement hack, if necessary. - if (!supportsNativeCopyAndPaste && currentEventIsNativeCopyOrCutEvent) - [self _primeDOMPasteboardElement]; + // Now that the copy event has been sent through the Cappuccino event stack, we can load any Cappuccino + // pasteboard string into our DOMPasteboardElement hack, if necessary. + if (!supportsNativeCopyAndPaste && currentEventIsNativeCopyOrCutEvent) + [self _primeDOMPasteboardElement]; } - (BOOL)windowShouldStopPropagation { - return currentEventShouldDefinitelyNotBubble; + return currentEventShouldDefinitelyNotBubble; } - (BOOL)windowShouldNotStopPropagation { - return currentEventShouldDefinitelyBubble; + return currentEventShouldDefinitelyBubble; } - (CPEvent)_fakeClipboardEvent:(DOMEvent)aDOMEvent type:(CPString)aType { - // MODIFICATION: Replaced 'var' with 'const'. - const keyCode = aType === "x" ? CPKeyCodes.X : (aType === "c" ? CPKeyCodes.C : CPKeyCodes.V), - characters = aType, - timestamp = [CPEvent currentTimestamp], // fake event, might as well use current timestamp - windowNumber = [[CPApp keyWindow] windowNumber], - modifierFlags = CPPlatformActionKeyMask, - 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 isActionKey:YES]; + var keyCode = aType === "x" ? CPKeyCodes.X : (aType === "c" ? CPKeyCodes.C : CPKeyCodes.V), + characters = aType, + timestamp = [CPEvent currentTimestamp], // fake event, might as well use current timestamp + windowNumber = [[CPApp keyWindow] windowNumber], + modifierFlags = CPPlatformActionKeyMask, + 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 isActionKey:YES]; - anEvent._data1 = @{ "simulated": YES }; - anEvent._DOMEvent = aDOMEvent; + anEvent._data1 = @{ "simulated": YES }; + anEvent._DOMEvent = aDOMEvent; - return anEvent; + return anEvent; } - (void)beforeCopyEvent:(DOMEvent)aDOMEvent { - if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask] && !_ignoreNativeCopyOrCutEvent) - { - // we have to send out a fake copy or cut event so that we can force the copy/cut mechanisms to take place - // MODIFICATION: Replaced 'var' with 'const'. - const anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type === "beforecut" ? "x" : "c")]; + if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask] && !_ignoreNativeCopyOrCutEvent) + { + // we have to send out a fake copy or cut event so that we can force the copy/cut mechanisms to take place + var anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type === "beforecut" ? "x" : "c")]; - [CPApp sendEvent:anEvent]; + [CPApp sendEvent:anEvent]; - // Once we've sent it, we can load the copy information into the pasteboard hack. - [self _primeDOMPasteboardElement]; + // Once we've sent it, we can load the copy information into the pasteboard hack. + [self _primeDOMPasteboardElement]; - //then we have to IGNORE the real keyboard event to prevent a double copy - //safari also sends the beforecopy event twice, so we additionally check here and prevent two events - _ignoreNativeCopyOrCutEvent = YES; - } + //then we have to IGNORE the real keyboard event to prevent a double copy + //safari also sends the beforecopy event twice, so we additionally check here and prevent two events + _ignoreNativeCopyOrCutEvent = YES; + } - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } - (BOOL)beforePasteEvent:(DOMEvent)aDOMEvent { - // Set up to capture the paste in a temporary input field. We'll send the event after capture. - if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask]) - { - _DOMPasteboardElement.focus(); - _DOMPasteboardElement.select(); - _DOMPasteboardElement.value = ""; + // Set up to capture the paste in a temporary input field. We'll send the event after capture. + if ([self _mayRequireDOMPasteboardElementHack:aDOMEvent flags:CPPlatformActionKeyMask]) + { + _DOMPasteboardElement.focus(); + _DOMPasteboardElement.select(); + _DOMPasteboardElement.value = ""; - _ignoreNativePastePreparation = YES; - } + _ignoreNativePastePreparation = YES; + } - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } /* - Return true if the event may be a copy and paste event, but the target is not an input or text area. - */ +Return true if the event may be a copy and paste event, but the target is not an input or text area. +*/ - (BOOL)_mayRequireDOMPasteboardElementHack:(DOMEvent)aDOMEvent flags:(unsigned)modifierFlags { - return !hasEditableTarget(aDOMEvent) && (modifierFlags & CPPlatformActionKeyMask); + return !hasEditableTarget(aDOMEvent) && (modifierFlags & CPPlatformActionKeyMask); } - (void)_primeDOMPasteboardElement { - // MODIFICATION: Replaced 'var' with 'const'. - const pasteboard = [CPPasteboard generalPasteboard], - types = [pasteboard types]; + var pasteboard = [CPPasteboard generalPasteboard], + types = [pasteboard types]; - if (types.length) - { - if ([types indexOfObjectIdenticalTo:CPStringPboardType] !== CPNotFound) - _DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType]; - else - _DOMPasteboardElement.value = [pasteboard _generateStateUID]; + if (types.length) + { + if ([types indexOfObjectIdenticalTo:CPStringPboardType] !== CPNotFound) + _DOMPasteboardElement.value = [pasteboard stringForType:CPStringPboardType]; + else + _DOMPasteboardElement.value = [pasteboard _generateStateUID]; - _DOMPasteboardElement.focus(); - _DOMPasteboardElement.select(); + _DOMPasteboardElement.focus(); + _DOMPasteboardElement.select(); - window.setNativeTimeout(function() { [self _clearDOMPasteboardElement]; }, 0); - } + window.setNativeTimeout(function() { [self _clearDOMPasteboardElement]; }, 0); + } } - (void)_checkDOMPasteboardElement { - if (supportsNativeCopyAndPaste) - { - [self _clearDOMPasteboardElement]; - return; - } + if (supportsNativeCopyAndPaste) + { + [self _clearDOMPasteboardElement]; + return; + } - // MODIFICATION: Replaced 'var' with 'const'. - const value = _DOMPasteboardElement.value; + var value = _DOMPasteboardElement.value; - if ([value length]) - { - // MODIFICATION: Replaced 'var' with 'const'. - const pasteboard = [CPPasteboard generalPasteboard], - cappString = [pasteboard stringForType:CPStringPboardType]; + if ([value length]) + { + var pasteboard = [CPPasteboard generalPasteboard], + cappString = [pasteboard stringForType:CPStringPboardType]; - if (cappString != value) - { - [pasteboard declareTypes:[CPStringPboardType] owner:self]; - [pasteboard setString:value forType:CPStringPboardType]; - } - } + if (cappString != value) + { + [pasteboard declareTypes:[CPStringPboardType] owner:self]; + [pasteboard setString:value forType:CPStringPboardType]; + } + } - [self _clearDOMPasteboardElement]; + [self _clearDOMPasteboardElement]; - [CPApp sendEvent:[self _fakeClipboardEvent:nil type:@"v"]]; + [CPApp sendEvent:[self _fakeClipboardEvent:nil type:@"v"]]; - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; } - (void)_clearDOMPasteboardElement { - _DOMPasteboardElement.value = ""; - _DOMPasteboardElement.blur(); + _DOMPasteboardElement.value = ""; + _DOMPasteboardElement.blur(); } - (BOOL)nativeBeforeClipboardEvent:(DOMEvent)aDOMEvent { - // Our job here is to return "false" if the given clipboard operation should be enabled even in a situation where - // the browser might normally grey the option out. + // Our job here is to return "false" if the given clipboard operation should be enabled even in a situation where + // the browser might normally grey the option out. - // Allow these fields to do their own thing. - if (hasEditableTarget(aDOMEvent)) - return true; + // Allow these fields to do their own thing. + if (hasEditableTarget(aDOMEvent)) + return true; - // MODIFICATION: Replaced 'var' with 'let'. - let returnValue = YES; + var returnValue = YES; - switch (aDOMEvent.type) - { - case "beforecopy": - returnValue = !([CPApp targetForAction:@selector(copy:)]); - break; - case "beforecut": - returnValue = !([CPApp targetForAction:@selector(cut:)]); - break; - case "beforepaste": - returnValue = !([CPApp targetForAction:@selector(paste:)]); - break; - } + switch (aDOMEvent.type) + { + case "beforecopy": + returnValue = !([CPApp targetForAction:@selector(copy:)]); + break; + case "beforecut": + returnValue = !([CPApp targetForAction:@selector(cut:)]); + break; + case "beforepaste": + returnValue = !([CPApp targetForAction:@selector(paste:)]); + break; + } - if (!returnValue) - _CPDOMEventStop(aDOMEvent, self); + if (!returnValue) + _CPDOMEventStop(aDOMEvent, self); - return returnValue; + return returnValue; } - (BOOL)nativePasteEvent:(DOMEvent)aDOMEvent { - // This shouldn't happen. - if (!supportsNativeCopyAndPaste) - return; + // This shouldn't happen. + if (!supportsNativeCopyAndPaste) + return; - // MODIFICATION: Replaced 'var' with 'let'. - let value; - if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) - value = aDOMEvent.clipboardData.getData('text/plain'); - else - value = _DOMWindow.clipboardData.getData("Text"); + var value; + if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) + value = aDOMEvent.clipboardData.getData('text/plain'); + else + value = _DOMWindow.clipboardData.getData("Text"); - if ([value length]) - { - // MODIFICATION: Replaced 'var' with 'const'. - const pasteboard = [CPPasteboard generalPasteboard], - cappString = [pasteboard stringForType:CPStringPboardType]; + if ([value length]) + { + var pasteboard = [CPPasteboard generalPasteboard], + cappString = [pasteboard stringForType:CPStringPboardType]; - if (cappString != value) - { - [pasteboard declareTypes:[CPStringPboardType] owner:self]; - [pasteboard setString:value forType:CPStringPboardType]; - } - } + if (cappString != value) + { + [pasteboard declareTypes:[CPStringPboardType] owner:self]; + [pasteboard setString:value forType:CPStringPboardType]; + } + } - // MODIFICATION: Replaced 'var' with 'const'. - const anEvent = [self _fakeClipboardEvent:aDOMEvent type:"v"], - platformWindow = [[anEvent window] platformWindow]; + var anEvent = [self _fakeClipboardEvent:aDOMEvent type:"v"], + platformWindow = [[anEvent window] platformWindow]; - // MODIFICATION: Inlined the SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT macro. - anEvent._suppressCappuccinoPaste = YES; + SUPPRESS_CAPPUCCINO_PASTE_FOR_EVENT(anEvent); - // By default we'll stop the native handling of the event since we're handling it ourselves. However, we need to - // stop it before we send the event so that the event can overrule our choice. CPTextField for instance wants the - // default handling when focused (which is to insert into the field). - [platformWindow _propagateCurrentDOMEvent:NO]; + // By default we'll stop the native handling of the event since we're handling it ourselves. However, we need to + // stop it before we send the event so that the event can overrule our choice. CPTextField for instance wants the + // default handling when focused (which is to insert into the field). + [platformWindow _propagateCurrentDOMEvent:NO]; - [CPApp sendEvent:anEvent]; + [CPApp sendEvent:anEvent]; - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - if (![platformWindow _willPropagateCurrentDOMEvent]) - _CPDOMEventStop(aDOMEvent, self); + if (![platformWindow _willPropagateCurrentDOMEvent]) + _CPDOMEventStop(aDOMEvent, self); - return false; + return false; } - (BOOL)nativeCopyOrCutEvent:(DOMEvent)aDOMEvent { - // This shouldn't happen. - if (!supportsNativeCopyAndPaste) - return; + // This shouldn't happen. + if (!supportsNativeCopyAndPaste) + return; - // MODIFICATION: Replaced 'var' with 'const'. - const anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type.indexOf("cut") != CPNotFound ? "x" : "c")], - platformWindow = [[anEvent window] platformWindow]; + var anEvent = [self _fakeClipboardEvent:aDOMEvent type:(aDOMEvent.type.indexOf("cut") != CPNotFound ? "x" : "c")], + platformWindow = [[anEvent window] platformWindow]; - // MODIFICATION: Inlined the SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT macro. - anEvent._suppressCappuccinoCut = YES; + SUPPRESS_CAPPUCCINO_CUT_FOR_EVENT(anEvent); - [platformWindow _propagateCurrentDOMEvent:NO]; + [platformWindow _propagateCurrentDOMEvent:NO]; - // Let the app react through copy: and cut: actions. - [CPApp sendEvent:anEvent]; - [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; + // Let the app react through copy: and cut: actions. + [CPApp sendEvent:anEvent]; + [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; - // If StopDOMEventPropagation was set to NO, we don't try to write to the system clipboard. The control that did this - // wants to use the default copy/cut functionality. - if (![platformWindow _willPropagateCurrentDOMEvent]) - { - // Now the app should have written whatever it wants to have copied to the Cappuccino clipboard. So now we need - // to write it to the system board. - _CPDOMEventStop(aDOMEvent, self); + // If StopDOMEventPropagation was set to NO, we don't try to write to the system clipboard. The control that did this + // wants to use the default copy/cut functionality. + if (![platformWindow _willPropagateCurrentDOMEvent]) + { + // Now the app should have written whatever it wants to have copied to the Cappuccino clipboard. So now we need + // to write it to the system board. + _CPDOMEventStop(aDOMEvent, self); - // MODIFICATION: Replaced 'var' with 'const'. - const pasteboard = [CPPasteboard generalPasteboard]; + var pasteboard = [CPPasteboard generalPasteboard]; - if ([[pasteboard types] containsObject:CPStringPboardType]) - { - // MODIFICATION: Replaced 'var' with 'const'. - const stringValue = [pasteboard stringForType:CPStringPboardType]; + if ([[pasteboard types] containsObject:CPStringPboardType]) + { + var stringValue = [pasteboard stringForType:CPStringPboardType]; - if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) - aDOMEvent.clipboardData.setData('text/plain', stringValue); - else - _DOMWindow.clipboardData.setData('Text', stringValue); - } - } + if (aDOMEvent.clipboardData && aDOMEvent.clipboardData.setData) + aDOMEvent.clipboardData.setData('text/plain', stringValue); + else + _DOMWindow.clipboardData.setData('Text', stringValue); + } + } - return ![platformWindow _willPropagateCurrentDOMEvent]; + return ![platformWindow _willPropagateCurrentDOMEvent]; } @end From 4d13785119919e1595fd9f302c895c8c4b9ac37a Mon Sep 17 00:00:00 2001 From: David Richardson Date: Thu, 27 Aug 2026 11:20:30 -0600 Subject: [PATCH 33/35] Revert "Merge pull request #3257 from enquora/modernize-CABackingStore" This reverts commit 5bdac195fc0495e05ac241e3ff41b292e09009f7, reversing changes made to 8134b6dff768d750c70a36b32084acf99212b1a0. --- AppKit/CoreAnimation/CABackingStore.j | 62 +++++++++++++++++---------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/AppKit/CoreAnimation/CABackingStore.j b/AppKit/CoreAnimation/CABackingStore.j index 068e9205a..847430f62 100644 --- a/AppKit/CoreAnimation/CABackingStore.j +++ b/AppKit/CoreAnimation/CABackingStore.j @@ -21,38 +21,54 @@ */ @import "CGGeometry.j" -// REMOVED: @import "CPCompatibility.j" - Obsolete legacy compatibility layer removed. +@import "CPCompatibility.j" + +#define PIXEL(pixels) pixels + "px"; -// REMOVED: #define PIXEL(pixels) macro - Eliminated in favour of native inline string concatenation. function CABackingStoreGetContext(aBackingStore) { - return aBackingStore.context; -} + return aBackingStore.context; +}; -// REMOVED: if (CPFeatureIsCompatible(CPHTMLCanvasFeature)) - HTMLCanvasElement is natively supported across modern targets. Legacy fallback block completely removed. -function CABackingStoreCreate() +if (CPFeatureIsCompatible(CPHTMLCanvasFeature)) { - // MODERNIZED: Replaced legacy 'var' with block-scoped 'const' for immutable DOM reference. - const DOMElement = document.createElement("canvas"); - DOMElement.style.position = "absolute"; - - // FIXME: Consolidate drawImage to support this. - return { context:DOMElement.getContext("2d"), buffer:DOMElement, _image:DOMElement }; -} - -function CABackingStoreSetSize(aBackingStore, aSize) +CABackingStoreCreate = function() { - // MODERNIZED: Replaced legacy 'var' with block-scoped 'const'. - const buffer = aBackingStore.buffer; + var DOMElement = document.createElement("canvas"); - buffer.width = aSize.width; - buffer.height = aSize.height; + DOMElement.style.position = "absolute"; + + // FIXME: Consolidate drawImage to support this. + return { context:DOMElement.getContext("2d"), buffer:DOMElement, _image:DOMElement }; +}; + +CABackingStoreSetSize = function(aBackingStore, aSize) +{ + var buffer = aBackingStore.buffer; + + buffer.width = aSize.width; + buffer.height = aSize.height; + buffer.style.width = PIXEL(aSize.width); + buffer.style.height = PIXEL(aSize.height); +}; - // MODERNIZED: Replaced macro expansion with native inline evaluation. - buffer.style.width = aSize.width + "px"; - buffer.style.height = aSize.height + "px"; } +else +{ -// REMOVED: Legacy else block utilizing CGBitmapGraphicsContextCreate due to guaranteed canvas support. +CABackingStoreCreate = function() +{ + var context = CGBitmapGraphicsContextCreate(); + + context.buffer = ""; + + return { context:context }; +}; + +CABackingStoreSetSize = function(aBackingStore, aSize) +{ +}; + +} From 690b79f11304ac226e9aafb701ee6546b9570b0b Mon Sep 17 00:00:00 2001 From: David Richardson Date: Thu, 27 Aug 2026 11:20:41 -0600 Subject: [PATCH 34/35] Revert "Merge pull request #3260 from enquora/modernize-CALayer" This reverts commit 8134b6dff768d750c70a36b32084acf99212b1a0, reversing changes made to 7083f7b2685d9f0b60c533136572f2fa8a585fa1. --- AppKit/CoreAnimation/CALayer.j | 553 ++++++++++++++++----------------- 1 file changed, 273 insertions(+), 280 deletions(-) diff --git a/AppKit/CoreAnimation/CALayer.j b/AppKit/CoreAnimation/CALayer.j index 23bf5e7ce..4cd003635 100644 --- a/AppKit/CoreAnimation/CALayer.j +++ b/AppKit/CoreAnimation/CALayer.j @@ -31,40 +31,41 @@ @import "CPView.j" @import "CAMediaTimingFunction.j" -// IMPROVEMENT: Removed DOM macro in favour of explicit property access for clarity. -var CALayerGeometryBoundsMask = 1, -CALayerGeometryPositionMask = 2, -CALayerGeometryAnchorPointMask = 4, -CALayerGeometryAffineTransformMask = 8, -CALayerGeometryParentSublayerTransformMask = 16; +#define DOM(aLayer) aLayer._DOMElement -var USE_BUFFER = NO; +var CALayerGeometryBoundsMask = 1, + CALayerGeometryPositionMask = 2, + CALayerGeometryAnchorPointMask = 4, + CALayerGeometryAffineTransformMask = 8, + CALayerGeometryParentSublayerTransformMask = 16; -var CALayerFrameOriginUpdateMask = 1, -CALayerFrameSizeUpdateMask = 2, -CALayerZPositionUpdateMask = 4, -CALayerDisplayUpdateMask = 8, -CALayerCompositeUpdateMask = 16, -CALayerDOMUpdateMask = CALayerZPositionUpdateMask | CALayerFrameOriginUpdateMask | CALayerFrameSizeUpdateMask; +var USE_BUFFER = NO; -var CALayerRegisteredRunLoopUpdates = nil; +var CALayerFrameOriginUpdateMask = 1, + CALayerFrameSizeUpdateMask = 2, + CALayerZPositionUpdateMask = 4, + CALayerDisplayUpdateMask = 8, + CALayerCompositeUpdateMask = 16, + CALayerDOMUpdateMask = CALayerZPositionUpdateMask | CALayerFrameOriginUpdateMask | CALayerFrameSizeUpdateMask; + +var CALayerRegisteredRunLoopUpdates = nil; /*! @class CALayer - A CALayer is similar to a CPView, but with the ability - to have a transform applied to it. + A CALayer is similar to a CPView, but with the ability + to have a transform applied to it. - @delegate -(void)drawLayer:(CALayer)layer inContext:(CGContextRef)ctx; - If the delegate implements this method, the CALayer will - call this in place of its \c -drawInContext:. - @param layer the layer to draw for - @param ctx the context to draw on + @delegate -(void)drawLayer:(CALayer)layer inContext:(CGContextRef)ctx; + If the delegate implements this method, the CALayer will + call this in place of its \c -drawInContext:. + @param layer the layer to draw for + @param ctx the context to draw on - @delegate -(void)displayLayer:(CALayer)layer; - The delegate can override the layer's \c -display method - by implementing this method. - */ + @delegate -(void)displayLayer:(CALayer)layer; + The delegate can override the layer's \c -display method + by implementing this method. +*/ @implementation CALayer : CPObject { // Modifying the Layer Geometry @@ -126,16 +127,16 @@ var CALayerRegisteredRunLoopUpdates = nil; @global document /*! - Returns a new animation layer. - */ + Returns a new animation layer. +*/ + (CALayer)layer { return [[[self class] alloc] init]; } /*! - Initializes the animation layer. - */ + Initializes the animation layer. +*/ - (id)init { self = [super init]; @@ -184,9 +185,9 @@ var CALayerRegisteredRunLoopUpdates = nil; // Modifying the Layer Geometry /*! - Sets the bounds (origin and size) of the rectangle. - @param aBounds the new bounds for the layer - */ + Sets the bounds (origin and size) of the rectangle. + @param aBounds the new bounds for the layer +*/ - (void)setBounds:(CGRect)aBounds { if (CGRectEqualToRect(_bounds, aBounds)) @@ -201,29 +202,29 @@ var CALayerRegisteredRunLoopUpdates = nil; // _hasSublayerTransform == true will handle this for us. /*else if (!CGPointEqualToPoint(_bounds.origin, oldOrigin)) - { - var index = _sublayers.length; + { + var index = _sublayers.length; - // FIXME: This should climb the layer tree down. - while (index--) - _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryPositionMask); - }*/ + // FIXME: This should climb the layer tree down. + while (index--) + _CALayerRecalculateGeometry(_sublayers[index], CALayerGeometryPositionMask); + }*/ _CALayerRecalculateGeometry(self, CALayerGeometryBoundsMask); } /*! - Returns the layer's bound. - */ + Returns the layer's bound. +*/ - (CGRect)bounds { return _bounds; } /*! - Sets the layer's position. - @param aPosition the layer's new position - */ + Sets the layer's position. + @param aPosition the layer's new position +*/ - (void)setPosition:(CGPoint)aPosition { if (CGPointEqualToPoint(_position, aPosition)) @@ -235,17 +236,17 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Returns the layer's position - */ + Returns the layer's position +*/ - (CGPoint)position { return _position; } /*! - Sets the layer's z-ordering. - @param aZPosition the layer's new z-ordering - */ + Sets the layer's z-ordering. + @param aZPosition the layer's new z-ordering +*/ - (void)setZPosition:(int)aZPosition { if (_zPosition == aZPosition) @@ -257,9 +258,9 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Sets the layer's anchor point. The default point is [0.5, 0.5]. - @param anAnchorPoint the layer's new anchor point - */ + Sets the layer's anchor point. The default point is [0.5, 0.5]. + @param anAnchorPoint the layer's new anchor point +*/ - (void)setAnchorPoint:(CGPoint)anAnchorPoint { anAnchorPoint = CGPointMakeCopy(anAnchorPoint); @@ -281,17 +282,17 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Returns the layer's anchor point. - */ + Returns the layer's anchor point. +*/ - (CGPoint)anchorPoint { return _anchorPoint; } /*! - Sets the affine transform applied to this layer. - @param anAffineTransform the new affine transform - */ + Sets the affine transform applied to this layer. + @param anAffineTransform the new affine transform +*/ - (void)setAffineTransform:(CGAffineTransform)anAffineTransform { if (CGAffineTransformEqualToTransform(_affineTransform, anAffineTransform)) @@ -303,17 +304,17 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Returns the layer's affine transform. - */ + Returns the layer's affine transform. +*/ - (CGAffineTransform)affineTransform { return _affineTransform; } /*! - Sets the affine transform that gets applied to all the sublayers. - @param anAffineTransform the transform to apply to sublayers - */ + Sets the affine transform that gets applied to all the sublayers. + @param anAffineTransform the transform to apply to sublayers +*/ - (void)setSublayerTransform:(CGAffineTransform)anAffineTransform { if (CGAffineTransformEqualToTransform(_sublayerTransform, anAffineTransform)) @@ -337,39 +338,39 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Returns the affine transform applied to the sublayers. - */ + Returns the affine transform applied to the sublayers. +*/ - (CGAffineTransform)sublayerTransform { return _sublayerTransform; } /* - Private - @ignore - */ + Private + @ignore +*/ - (CGAffineTransform)transformToLayer { return _transformToLayer; } /*! - Sets the frame of the layer. The frame defines a bounding - rectangle in the superlayer's coordinate system. - @param aFrame the new frame rectangle - */ + Sets the frame of the layer. The frame defines a bounding + rectangle in the superlayer's coordinate system. + @param aFrame the new frame rectangle +*/ - (void)setFrame:(CGRect)aFrame { // FIXME: implement this } /*! - Returns the layer's frame. + Returns the layer's frame. - The frame defines the bounding box of the layer: the smallest - possible rectangle that could fit this layer after transform - properties are applied in superlayer coordinates. - */ + The frame defines the bounding box of the layer: the smallest + possible rectangle that could fit this layer after transform + properties are applied in superlayer coordinates. +*/ - (CGRect)frame { if (!_frame) @@ -379,22 +380,22 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - The Backing Store Frame specifies the frame of the actual backing - store used to contain this layer. Naturally, by default it is the - same as the frame, however, users can specify their own custom - Backing Store Frame in order to speed up certain operations, such as - live transformation. - @return the backing store frame - */ + The Backing Store Frame specifies the frame of the actual backing + store used to contain this layer. Naturally, by default it is the + same as the frame, however, users can specify their own custom + Backing Store Frame in order to speed up certain operations, such as + live transformation. + @return the backing store frame +*/ - (CGRect)backingStoreFrame { return _backingStoreFrame; } /*! - Sets the frame's backing store. - @param aFrame the new backing store. - */ + Sets the frame's backing store. + @param aFrame the new backing store. +*/ - (void)setBackingStoreFrame:(CGRect)aFrame { _hasCustomBackingStoreFrame = (aFrame != nil); @@ -408,7 +409,7 @@ var CALayerRegisteredRunLoopUpdates = nil; aFrame = [_superlayer convertRect:aFrame toLayer:nil]; var bounds = [_superlayer bounds], - frame = [_superlayer convertRect:bounds toLayer:nil]; + frame = [_superlayer convertRect:bounds toLayer:nil]; aFrame.origin.x -= CGRectGetMinX(frame); aFrame.origin.y -= CGRectGetMinY(frame); @@ -428,18 +429,18 @@ var CALayerRegisteredRunLoopUpdates = nil; // Providing Layer Content /*! - Returns the CGImage contents of this layer. - The default contents are \c nil. - */ + Returns the CGImage contents of this layer. + The default contents are \c nil. +*/ - (CGImage)contents { return _contents; } /*! - Sets the image contents of this layer. - @param contents the image to display - */ + Sets the image contents of this layer. + @param contents the image to display +*/ - (void)setContents:(CGImage)contents { if (_contents == contents) @@ -451,13 +452,12 @@ var CALayerRegisteredRunLoopUpdates = nil; } /* - Composites this layer onto the super layer, and draws its contents as well. - @ignore - */ + Composites this layer onto the super layer, and draws its contents as well. + @ignore +*/ - (void)composite { - // IMPROVEMENT: Added explicit parentheses for logical clarity in operator precedence. - if ((USE_BUFFER && !_contents) || !_context) + if (USE_BUFFER && !_contents || !_context) return; CGContextClearRect(_context, CGRectMake(0.0, 0.0, CGRectGetWidth(_backingStoreFrame), CGRectGetHeight(_backingStoreFrame))); @@ -468,7 +468,7 @@ var CALayerRegisteredRunLoopUpdates = nil; if (_superlayer) { var superlayerTransform = _CALayerGetTransform(_superlayer, nil), - superlayerOrigin = CGPointApplyAffineTransform(_superlayer._bounds.origin, superlayerTransform); + superlayerOrigin = CGPointApplyAffineTransform(_superlayer._bounds.origin, superlayerTransform); transform = CGAffineTransformConcat(_transformFromLayer, superlayerTransform); @@ -489,7 +489,7 @@ var CALayerRegisteredRunLoopUpdates = nil; if (USE_BUFFER) { - // CGContextDrawImage(_context, _bounds, _contents.context); +// CGContextDrawImage(_context, _bounds, _contents.context); _context.drawImage(_contents.buffer, CGRectGetMinX(_bounds), CGRectGetMinY(_bounds));//, CGRectGetWidth(_standardBackingStoreFrame), CGRectGetHeight(_standardBackingStoreFrame)); } else @@ -499,8 +499,8 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Displays the contents of this layer. - */ + Displays the contents of this layer. +*/ - (void)display { #if PLATFORM(DOM) @@ -548,9 +548,9 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Draws this layer's contents into the specified context. - @param aContext the context to draw the layer into - */ + Draws this layer's contents into the specified context. + @param aContext the context to draw the layer into +*/ - (void)drawInContext:(CGContext)aContext { if (_backgroundColor) @@ -566,18 +566,18 @@ var CALayerRegisteredRunLoopUpdates = nil; // Style Attributes /*! - Returns the opacity of the layer. The value is between - \c 0.0 (transparent) and \c 1.0 (opaque). - */ + Returns the opacity of the layer. The value is between + \c 0.0 (transparent) and \c 1.0 (opaque). +*/ - (float)opacity { return _opacity; } /*! - Sets the opacity for the layer. - @param anOpacity the new opacity (between \c 0.0 (transparent) and \c 1.0 (opaque)). - */ + Sets the opacity for the layer. + @param anOpacity the new opacity (between \c 0.0 (transparent) and \c 1.0 (opaque)). +*/ - (void)setOpacity:(float)anOpacity { if (_opacity == anOpacity) @@ -590,9 +590,9 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Sets whether the layer is hidden. - @param isHidden \c YES means the layer will be hidden. \c NO means the layer will be visible. - */ + Sets whether the layer is hidden. + @param isHidden \c YES means the layer will be hidden. \c NO means the layer will be visible. +*/ - (void)setHidden:(BOOL)isHidden { _isHidden = isHidden; @@ -600,25 +600,25 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Returns \c YES if the layer is hidden. - */ + Returns \c YES if the layer is hidden. +*/ - (BOOL)hidden { return _isHidden; } /*! - Returns \c YES if the layer is hidden. - */ + Returns \c YES if the layer is hidden. +*/ - (BOOL)isHidden { return _isHidden; } /*! - Sets whether content that goes lies outside the bounds is hidden or visible. - @param masksToBounds \c YES hides the excess content. \c NO makes it visible. - */ + Sets whether content that goes lies outside the bounds is hidden or visible. + @param masksToBounds \c YES hides the excess content. \c NO makes it visible. +*/ - (void)setMasksToBounds:(BOOL)masksToBounds { if (_masksToBounds == masksToBounds) @@ -629,9 +629,9 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Sets the layer's background color. - @param aColor the new background color - */ + Sets the layer's background color. + @param aColor the new background color +*/ - (void)setBackgroundColor:(CPColor)aColor { _backgroundColor = aColor; @@ -640,8 +640,8 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Returns the layer's background color. - */ + Returns the layer's background color. +*/ - (CPColor)backgroundColor { return _backgroundColor; @@ -649,34 +649,36 @@ var CALayerRegisteredRunLoopUpdates = nil; // Managing Layer Hierarchy /*! - Returns an array of the receiver's sublayers. - */ + Returns an array of the receiver's sublayers. +*/ - (CPArray)sublayers { return _sublayers; } /*! - Returns the receiver's superlayer. - */ + Returns the receiver's superlayer. +*/ - (CALayer)superlayer { return _superlayer; } -// IMPROVEMENT: Removed ADJUST_CONTENTS_ZINDEX macro in favor of inline replacement to eliminate preprocessor dependency. +#define ADJUST_CONTENTS_ZINDEX(aLayer)\ +if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex)\ + _DOMContentsElement.style.zIndex -= 100.0;\ /*! - Adds the specified layer as a sublayer of the receiver. - */ + Adds the specified layer as a sublayer of the receiver. +*/ - (void)addSublayer:(CALayer)aLayer { [self insertSublayer:aLayer atIndex:_sublayers.length]; } /*! - Removes the receiver from its superlayer. - */ + Removes the receiver from its superlayer. +*/ - (void)removeFromSuperlayer { if (_owningView) @@ -692,10 +694,10 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Inserts the specified layer as a sublayer into the specified index. - @param aLayer the layer to insert - @param anIndex the index to insert the layer at - */ + Inserts the specified layer as a sublayer into the specified index. + @param aLayer the layer to insert + @param anIndex the index to insert the layer at +*/ - (void)insertSublayer:(CALayer)aLayer atIndex:(CPUInteger)anIndex { if (!aLayer) @@ -718,19 +720,15 @@ var CALayerRegisteredRunLoopUpdates = nil; else if (superlayer != nil) [aLayer removeFromSuperlayer]; - // IMPROVEMENT: Inlined ADJUST_CONTENTS_ZINDEX logic. - if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex) - _DOMContentsElement.style.zIndex -= 100.0; + ADJUST_CONTENTS_ZINDEX(aLayer); [_sublayers insertObject:aLayer atIndex:anIndex]; #if PLATFORM(DOM) if (anIndex >= _sublayers.length - 1) - // IMPROVEMENT: Inlined DOM element property access. - _DOMElement.appendChild(aLayer._DOMElement); + _DOMElement.appendChild(DOM(aLayer)); else - // IMPROVEMENT: Inlined DOM element property access. - _DOMElement.insertBefore(aLayer._DOMElement, _sublayers[anIndex + 1]._DOMElement); + _DOMElement.insertBefore(DOM(aLayer), _sublayers[anIndex + 1]._DOMElement); #endif aLayer._superlayer = self; @@ -740,11 +738,11 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Inserts a layer below another layer. - @param aLayer the layer to insert - @param aSublayer the layer to insert below - @throws CALayerNotFoundException if \c aSublayer is not in the array of sublayers - */ + Inserts a layer below another layer. + @param aLayer the layer to insert + @param aSublayer the layer to insert below + @throws CALayerNotFoundException if \c aSublayer is not in the array of sublayers +*/ - (void)insertSublayer:(CALayer)aLayer below:(CALayer)aSublayer { var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : 0; @@ -753,11 +751,11 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Inserts a layer above another layer. - @param aLayer the layer to insert - @param aSublayer the layer to insert above - @throws CALayerNotFoundException if \c aSublayer is not in the array of sublayers - */ + Inserts a layer above another layer. + @param aLayer the layer to insert + @param aSublayer the layer to insert above + @throws CALayerNotFoundException if \c aSublayer is not in the array of sublayers +*/ - (void)insertSublayer:(CALayer)aLayer above:(CALayer)aSublayer { var index = aSublayer ? [_sublayers indexOfObjectIdenticalTo:aSublayer] : _sublayers.length; @@ -769,10 +767,10 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Replaces a sublayer. - @param aSublayer the layer to insert - @param aLayer the layer to replace - */ + Replaces a sublayer. + @param aSublayer the layer to insert + @param aLayer the layer to replace +*/ - (void)replaceSublayer:(CALayer)aSublayer with:(CALayer)aLayer { if (aSublayer == aLayer) @@ -784,27 +782,23 @@ var CALayerRegisteredRunLoopUpdates = nil; return; } - // IMPROVEMENT: Inlined ADJUST_CONTENTS_ZINDEX logic. - if (_DOMContentsElement && aLayer._zPosition > _DOMContentsElement.style.zIndex) - _DOMContentsElement.style.zIndex -= 100.0; + ADJUST_CONTENTS_ZINDEX(aLayer); [_sublayers replaceObjectAtIndex:[_sublayers indexOfObjectIdenticalTo:aSublayer] withObject:aLayer]; - - // IMPROVEMENT: Inlined DOM element property access. - _DOMElement.replaceChild(aLayer._DOMElement, aSublayer._DOMElement); + _DOMElement.replaceChild(DOM(aSublayer), DOM(aLayer)); } // Updating Layer Display /* - Updates the layers on screen. - @ignore - */ + Updates the layers on screen. + @ignore +*/ + (void)runLoopUpdateLayers { for (UID in CALayerRegisteredRunLoopUpdates) { var layer = CALayerRegisteredRunLoopUpdates[UID], - mask = layer._runLoopUpdateMask; + mask = layer._runLoopUpdateMask; if (mask & CALayerDOMUpdateMask) _CALayerUpdateDOM(layer, mask); @@ -822,8 +816,8 @@ var CALayerRegisteredRunLoopUpdates = nil; } /* - @ignore - */ + @ignore +*/ - (void)registerRunLoopUpdateWithMask:(unsigned)anUpdateMask { if (CALayerRegisteredRunLoopUpdates == nil) @@ -831,7 +825,7 @@ var CALayerRegisteredRunLoopUpdates = nil; CALayerRegisteredRunLoopUpdates = {}; [[CPRunLoop currentRunLoop] performSelector:@selector(runLoopUpdateLayers) - target:CALayer argument:nil order:0 modes:[CPDefaultRunLoopMode]]; + target:CALayer argument:nil order:0 modes:[CPDefaultRunLoopMode]]; } _runLoopUpdateMask |= anUpdateMask; @@ -839,88 +833,88 @@ var CALayerRegisteredRunLoopUpdates = nil; } /* - @ignore - */ + @ignore +*/ - (void)setNeedsComposite { [self registerRunLoopUpdateWithMask:CALayerCompositeUpdateMask]; } /*! - Marks the layer as needing to be redrawn. - */ + Marks the layer as needing to be redrawn. +*/ - (void)setNeedsDisplay { [self registerRunLoopUpdateWithMask:CALayerDisplayUpdateMask]; } /*! - Sets whether the layer needs to be redrawn when its bounds are changed. - @param needsDisplayOnBoundsChange \c YES means the display is redraw on a bounds change. - */ + Sets whether the layer needs to be redrawn when its bounds are changed. + @param needsDisplayOnBoundsChange \c YES means the display is redraw on a bounds change. +*/ - (void)setNeedsDisplayOnBoundsChange:(BOOL)needsDisplayOnBoundsChange { _needsDisplayOnBoundsChange = needsDisplayOnBoundsChange; } /*! - Returns \c YES if the display should be redrawn on a bounds change. - */ + Returns \c YES if the display should be redrawn on a bounds change. +*/ - (BOOL)needsDisplayOnBoundsChange { return _needsDisplayOnBoundsChange; } /*! - Marks the specified rectangle as needing to be redrawn. - @param aRect the area that needs to be redrawn. - */ + Marks the specified rectangle as needing to be redrawn. + @param aRect the area that needs to be redrawn. +*/ - (void)setNeedsDisplayInRect:(CGRect)aRect { - // _dirtyRect = aRect; +// _dirtyRect = aRect; [self display]; } // Mapping Between Coordinate and Time Spaces /*! - Converts the point from the specified layer's coordinate system into the receiver's coordinate system. - @param aPoint the point to convert - @param aLayer the layer coordinate system to convert from - @return the converted point - */ + Converts the point from the specified layer's coordinate system into the receiver's coordinate system. + @param aPoint the point to convert + @param aLayer the layer coordinate system to convert from + @return the converted point +*/ - (CGPoint)convertPoint:(CGPoint)aPoint fromLayer:(CALayer)aLayer { return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(aLayer, self)); } /*! - Converts the point from the receiver's coordinate system to the specified layer's coordinate system. - @param aPoint the point to convert - @param aLayer the layer coordinate system to convert to - @return the converted point - */ + Converts the point from the receiver's coordinate system to the specified layer's coordinate system. + @param aPoint the point to convert + @param aLayer the layer coordinate system to convert to + @return the converted point +*/ - (CGPoint)convertPoint:(CGPoint)aPoint toLayer:(CALayer)aLayer { return CGPointApplyAffineTransform(aPoint, _CALayerGetTransform(self, aLayer)); } /*! - Converts the rectangle from the specified layer's coordinate system to the receiver's coordinate system. - @param aRect the rectangle to convert - @param aLayer the layer coordinate system to convert from - @return the converted rectangle - */ + Converts the rectangle from the specified layer's coordinate system to the receiver's coordinate system. + @param aRect the rectangle to convert + @param aLayer the layer coordinate system to convert from + @return the converted rectangle +*/ - (CGRect)convertRect:(CGRect)aRect fromLayer:(CALayer)aLayer { return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(aLayer, self)); } /*! - Converts the rectangle from the receiver's coordinate system to the specified layer's coordinate system. - @param aRect the rectangle to convert - @param aLayer the layer coordinate system to convert to - @return the converted rectangle - */ + Converts the rectangle from the receiver's coordinate system to the specified layer's coordinate system. + @param aRect the rectangle to convert + @param aLayer the layer coordinate system to convert to + @return the converted rectangle +*/ - (CGRect)convertRect:(CGRect)aRect toLayer:(CALayer)aLayer { return CGRectApplyAffineTransform(aRect, _CALayerGetTransform(self, aLayer)); @@ -928,19 +922,19 @@ var CALayerRegisteredRunLoopUpdates = nil; // Hit Testing /*! - Returns \c YES if the layer contains the point. - @param aPoint the point to test - */ + Returns \c YES if the layer contains the point. + @param aPoint the point to test +*/ - (BOOL)containsPoint:(CGPoint)aPoint { return CGRectContainsPoint(_bounds, aPoint); } /*! - Returns the farthest descendant of this layer that contains the specified point. - @param aPoint the point to test - @return the containing layer or \c nil if there was no hit. - */ + Returns the farthest descendant of this layer that contains the specified point. + @param aPoint the point to test + @return the containing layer or \c nil if there was no hit. +*/ - (CALayer)hitTest:(CGPoint)aPoint { if (_isHidden) @@ -952,7 +946,7 @@ var CALayerRegisteredRunLoopUpdates = nil; return nil; var layer = nil, - index = _sublayers.length; + index = _sublayers.length; // FIXME: this should take into account zPosition. while (index--) @@ -964,9 +958,9 @@ var CALayerRegisteredRunLoopUpdates = nil; // Modifying the Delegate /*! - Sets the delegate for this layer. - @param aDelegate the delegate - */ + Sets the delegate for this layer. + @param aDelegate the delegate +*/ - (void)setDelegate:(id)aDelegate { if (_delegate == aDelegate) @@ -982,19 +976,19 @@ var CALayerRegisteredRunLoopUpdates = nil; } /*! - Returns the layer's delegate - */ + Returns the layer's delegate +*/ - (id)delegate { return _delegate; } /* - Adds an animation to the layer. - Supports CABasicAnimation for Numbers (opacity) and Points (position/anchorPoint). - The animation is exerted by means of periodically applying the keypath on the delegate - Only works if the delegate is set! - */ + Adds an animation to the layer. + Supports CABasicAnimation for Numbers (opacity) and Points (position/anchorPoint). + The animation is exerted by means of periodically applying the keypath on the delegate + Only works if the delegate is set! +*/ - (void)addAnimation:(CAAnimation)anim forKey:(CPString)key { if (!anim) return; @@ -1004,24 +998,24 @@ var CALayerRegisteredRunLoopUpdates = nil; if ([anim respondsToSelector:@selector(animations)] && [anim animations]) { var animations = [anim animations], - count = [animations count], - i = 0; + count = [animations count], + i = 0; for (; i < count; i++) { var child = [animations objectAtIndex:i]; - - // Recurse: Add the child animation. - // We pass 'nil' for the key so the child's own 'keyPath' + + // Recurse: Add the child animation. + // We pass 'nil' for the key so the child's own 'keyPath' // is used as the storage identifier in the dictionary. [self addAnimation:child forKey:nil]; } - return; + return; } // --- 2. Determine KeyPath --- var keyPath = key; - + // If the animation object has an explicit keyPath (like CABasicAnimation), use it. if ([anim respondsToSelector:@selector(keyPath)] && [anim keyPath]) keyPath = [anim keyPath]; @@ -1051,7 +1045,7 @@ var CALayerRegisteredRunLoopUpdates = nil; return; var duration = ([anim respondsToSelector:@selector(duration)]) ? [anim duration] : 0.25; - + // Default to EaseInEaseOut if not specified var timingFunction = ([anim respondsToSelector:@selector(timingFunction)]) ? [anim timingFunction] : [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; @@ -1068,7 +1062,7 @@ var CALayerRegisteredRunLoopUpdates = nil; }; // --- 5. Render Loop --- - var _self = self; + var _self = self; var renderLoop = function(timestamp) { if ([_self _renderAnimationStep:context timestamp:timestamp]) @@ -1102,33 +1096,33 @@ var CALayerRegisteredRunLoopUpdates = nil; - (void)removeAllAnimations { var keys = [_activeAnimations allKeys], - count = [keys count]; + count = [keys count]; while (count--) [self removeAnimationForKey:[keys objectAtIndex:count]]; } /* - Solves Cubic Bezier for t. - p1, p2 are the control points (x,y). p0 is 0,0, p3 is 1,1. - This is a simplified solver for standard Core Animation timing functions. - */ + Solves Cubic Bezier for t. + p1, p2 are the control points (x,y). p0 is 0,0, p3 is 1,1. + This is a simplified solver for standard Core Animation timing functions. +*/ - (float)_solveBezier:(float)t forTimingFunction:(CAMediaTimingFunction)tf { if (!tf) return t; - + // Linear optimization - if (tf === [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]) + if (tf === [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]) return t; var points = [tf controlPoints]; // [c1x, c1y, c2x, c2y] - var p1x = points[0], p1y = points[1], - p2x = points[2], p2y = points[3]; + var p1x = points[0], p1y = points[1], + p2x = points[2], p2y = points[3]; // Simple polynomial evaluation (De Casteljau's algorithm/Cubic formula subset) - // Since we are usually dealing with standard easing, we can approximate 1D easing on the Y axis + // Since we are usually dealing with standard easing, we can approximate 1D easing on the Y axis // based on linear time X, or do a full solve. // For brevity/speed in JS, we often approximate basic easing: - + // 3t^2 * (1-t) + t^3 ... standard bezier blending functions var cx = 3.0 * p1x; var bx = 3.0 * (p2x - p1x) - cx; @@ -1158,7 +1152,7 @@ var CALayerRegisteredRunLoopUpdates = nil; context.startTime = timestamp; var elapsed = timestamp - context.startTime, - linearProgress = elapsed / context.duration; + linearProgress = elapsed / context.duration; if (linearProgress > 1.0) linearProgress = 1.0; @@ -1166,8 +1160,8 @@ var CALayerRegisteredRunLoopUpdates = nil; var progress = [self _solveBezier:linearProgress forTimingFunction:context.timingFunction]; var start = context.startValue, - end = context.endValue, - current = nil; + end = context.endValue, + current = nil; // Number if (typeof start === "number") @@ -1188,11 +1182,11 @@ var CALayerRegisteredRunLoopUpdates = nil; else if (start && start.origin !== undefined && start.size !== undefined) // CGRect { current = CGRectMake( - start.origin.x + (end.origin.x - start.origin.x) * progress, - start.origin.y + (end.origin.y - start.origin.y) * progress, - start.size.width + (end.size.width - start.size.width) * progress, - start.size.height + (end.size.height - start.size.height) * progress - ); + start.origin.x + (end.origin.x - start.origin.x) * progress, + start.origin.y + (end.origin.y - start.origin.y) * progress, + start.size.width + (end.size.width - start.size.width) * progress, + start.size.height + (end.size.height - start.size.height) * progress + ); } if (current !== nil) @@ -1201,10 +1195,10 @@ var CALayerRegisteredRunLoopUpdates = nil; if (linearProgress >= 1.0) { var anim = context.animation; - + // Cleanup var shouldRemove = [anim respondsToSelector:@selector(isRemovedOnCompletion)] ? [anim isRemovedOnCompletion] : YES; - + if (shouldRemove) { // Find key by context identity to handle groups correctly var keys = [_activeAnimations allKeys]; @@ -1278,14 +1272,14 @@ var CALayerRegisteredRunLoopUpdates = nil; function _CALayerUpdateSublayerTransformForSublayers(aLayer) { var bounds = aLayer._bounds, - anchorPoint = aLayer._anchorPoint, - translateX = CGRectGetWidth(bounds) * anchorPoint.x, - translateY = CGRectGetHeight(bounds) * anchorPoint.y; + anchorPoint = aLayer._anchorPoint, + translateX = CGRectGetWidth(bounds) * anchorPoint.x, + translateY = CGRectGetHeight(bounds) * anchorPoint.y; aLayer._sublayerTransformForSublayers = CGAffineTransformConcat( - CGAffineTransformMakeTranslation(-translateX, -translateY), - CGAffineTransformConcat(aLayer._sublayerTransform, - CGAffineTransformMakeTranslation(translateX, translateY))); + CGAffineTransformMakeTranslation(-translateX, -translateY), + CGAffineTransformConcat(aLayer._sublayerTransform, + CGAffineTransformMakeTranslation(translateX, translateY))); } function _CALayerUpdateDOM(aLayer, aMask) @@ -1307,8 +1301,8 @@ function _CALayerUpdateDOM(aLayer, aMask) if (aMask & CALayerFrameSizeUpdateMask) { var width = MAX(0.0, ROUND(CGRectGetWidth(frame))), - height = MAX(0.0, ROUND(CGRectGetHeight(frame))), - DOMContentsElement = aLayer._DOMContentsElement; + height = MAX(0.0, ROUND(CGRectGetHeight(frame))), + DOMContentsElement = aLayer._DOMContentsElement; DOMElementStyle.width = width + "px"; DOMElementStyle.height = height + "px"; @@ -1327,20 +1321,20 @@ function _CALayerUpdateDOM(aLayer, aMask) function _CALayerRecalculateGeometry(aLayer, aGeometryChange) { var bounds = aLayer._bounds, - superlayer = aLayer._superlayer, - width = CGRectGetWidth(bounds), - height = CGRectGetHeight(bounds), - position = aLayer._position, - anchorPoint = aLayer._anchorPoint, - affineTransform = aLayer._affineTransform, - backingStoreFrameSize = CGSizeMakeCopy(aLayer._backingStoreFrame), - hasCustomBackingStoreFrame = aLayer._hasCustomBackingStoreFrame; + superlayer = aLayer._superlayer, + width = CGRectGetWidth(bounds), + height = CGRectGetHeight(bounds), + position = aLayer._position, + anchorPoint = aLayer._anchorPoint, + affineTransform = aLayer._affineTransform, + backingStoreFrameSize = CGSizeMakeCopy(aLayer._backingStoreFrame), + hasCustomBackingStoreFrame = aLayer._hasCustomBackingStoreFrame; // Go to anchor, transform, go back to bounds. aLayer._transformFromLayer = CGAffineTransformConcat( - CGAffineTransformMakeTranslation(-width * anchorPoint.x - CGRectGetMinX(aLayer._bounds), -height * anchorPoint.y - CGRectGetMinY(aLayer._bounds)), - CGAffineTransformConcat(affineTransform, - CGAffineTransformMakeTranslation(position.x, position.y))); + CGAffineTransformMakeTranslation(-width * anchorPoint.x - CGRectGetMinX(aLayer._bounds), -height * anchorPoint.y - CGRectGetMinY(aLayer._bounds)), + CGAffineTransformConcat(affineTransform, + CGAffineTransformMakeTranslation(position.x, position.y))); if (superlayer && superlayer._hasSublayerTransform) { @@ -1358,9 +1352,8 @@ function _CALayerRecalculateGeometry(aLayer, aGeometryChange) if (superlayer) { - // IMPROVEMENT: Renamed local 'bounds' to 'superlayerBounds' to prevent shadowing of outer 'bounds' declaration. - var superlayerBounds = [superlayer bounds], - frame = [superlayer convertRect:superlayerBounds toLayer:nil]; + var bounds = [superlayer bounds], + frame = [superlayer convertRect:bounds toLayer:nil]; aLayer._standardBackingStoreFrame.origin.x -= CGRectGetMinX(frame); aLayer._standardBackingStoreFrame.origin.y -= CGRectGetMinY(frame); @@ -1372,7 +1365,7 @@ function _CALayerRecalculateGeometry(aLayer, aGeometryChange) // bigger than the "optimal" bounding integral rect since that doesn't change drawing. var origin = aLayer._standardBackingStoreFrame.origin, - size = aLayer._standardBackingStoreFrame.size; + size = aLayer._standardBackingStoreFrame.size; origin.x = FLOOR(origin.x); origin.y = FLOOR(origin.y); @@ -1395,7 +1388,7 @@ function _CALayerRecalculateGeometry(aLayer, aGeometryChange) // Any change in size due to a geometry change is purely due to rounding error. if ((CGRectGetWidth(backingStoreFrame) != ROUND(CGRectGetWidth(aLayer._backingStoreFrame)) || - CGRectGetHeight(backingStoreFrame) != ROUND(CGRectGetHeight(aLayer._backingStoreFrame)))) + CGRectGetHeight(backingStoreFrame) != ROUND(CGRectGetHeight(aLayer._backingStoreFrame)))) [aLayer registerRunLoopUpdateWithMask:CALayerFrameSizeUpdateMask]; aLayer._backingStoreFrame = backingStoreFrame; @@ -1411,8 +1404,8 @@ function _CALayerRecalculateGeometry(aLayer, aGeometryChange) [aLayer setNeedsComposite]; var sublayers = aLayer._sublayers, - index = 0, - count = sublayers.length; + index = 0, + count = sublayers.length; for (; index < count; ++index) _CALayerRecalculateGeometry(sublayers[index], aGeometryChange); @@ -1444,7 +1437,7 @@ function _CALayerGetTransform(fromLayer, toLayer) } var layers = [], - layer = toLayer; + layer = toLayer; while (layer) { From 16beb1a86cf8c31d78f042fa7c5df2d5aad7105f Mon Sep 17 00:00:00 2001 From: daboe01 Date: Thu, 27 Aug 2026 19:28:31 +0200 Subject: [PATCH 35/35] fix: replace legacy `unload` event listener cleanup with `pagehide` --- AppKit/Platform/DOM/CPPlatformWindow+DOM.j | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j index 20413329b..bac1994c4 100644 --- a/AppKit/Platform/DOM/CPPlatformWindow+DOM.j +++ b/AppKit/Platform/DOM/CPPlatformWindow+DOM.j @@ -483,7 +483,7 @@ _CPPlatformWindowWillCloseNotification = @"_CPPlatformWindowWillCloseNotificatio _DOMWindow.addEventListener("pagehide", function() { - _DOMWindow.removeEventListener("unload", arguments.callee, NO); + _DOMWindow.removeEventListener("pagehide", arguments.callee, NO); [self blurEvent:nil]; [self _notifyPlatformWindowWillClose];