From 62f699e37612c591634bb5eedae1a303f5061bee Mon Sep 17 00:00:00 2001 From: David Richardson Date: Fri, 23 Oct 2026 21:49:41 -0600 Subject: [PATCH] AppKit: Add Copy Ruler and Paste Ruler, and undo for ruler changes CPTextView had no copyRuler: or pasteRuler:, so Format > Text > Copy Ruler and Paste Ruler did nothing, and ruler changes could not be undone. It now follows Cocoa: Copy Ruler puts the paragraph style at the selection on the ruler pasteboard, Paste Ruler applies it to the selected paragraphs, and every ruler change is registered with the undo manager. * CPPasteboard.j: Add CPRulerPboardType. * CPTextView/CPTextView.j: Add copyRuler: and pasteRuler: with validation for rich text; register ruler paragraph style changes with the undo manager; refresh the ruler after a replacement. * Tests/Manual/TextEditor/WORKLIST.md: Record Copy Ruler and Paste Ruler as tested, and add the Copy Style and Paste Style exception. --- AppKit/CPPasteboard.j | 1 + AppKit/CPTextView/CPTextView.j | 45 ++++++++++++++++++++++++++++- Tests/Manual/TextEditor/WORKLIST.md | 5 ++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/AppKit/CPPasteboard.j b/AppKit/CPPasteboard.j index 2ef6b3461..dc4be9839 100644 --- a/AppKit/CPPasteboard.j +++ b/AppKit/CPPasteboard.j @@ -45,6 +45,7 @@ CPURLPboardType = @"CPURLPboardType"; CPImagesPboardType = @"CPImagesPboardType"; CPVideosPboardType = @"CPVideosPboardType"; CPRTFPboardType = @"CPRTFPboardType"; +CPRulerPboardType = @"CPRulerPboardType"; _CPSmartPboardType = @"_CPSmartPboardType"; _CPASPboardType = @"_CPASPboardType"; diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 1be62d906..57cd3c91e 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -720,7 +720,8 @@ var kDelegateRespondsTo_textShouldBeginEditing Enables the Undo and Redo items only when the undo manager has a change to undo or redo. For \c -toggleRuler:, the item's title becomes "Hide Ruler" or "Show Ruler" to reflect the ruler's visibility, and the item is enabled only when the text view uses a ruler and lies - in a scroll view. Every other item is enabled. + in a scroll view. \c -copyRuler: is enabled in rich text, and \c -pasteRuler: in editable + rich text when the ruler pasteboard holds a ruler. Every other item is enabled. @param anItem the item to validate @return \c YES if the item should be enabled */ @@ -734,6 +735,13 @@ var kDelegateRespondsTo_textShouldBeginEditing if (action === @selector(redo:)) return [[[self window] undoManager] canRedo]; + // As in Cocoa, rulers can be copied and pasted only in rich text. + if (action === @selector(copyRuler:)) + return [self isRichText]; + + if (action === @selector(pasteRuler:)) + return [self isRichText] && [self isEditable] && !![[CPPasteboard pasteboardWithName:CPRulerPboard] availableTypeFromArray:[CPRulerPboardType]]; + if (action === @selector(toggleRuler:)) { if ([anItem respondsToSelector:@selector(setTitle:)]) @@ -874,6 +882,9 @@ var kDelegateRespondsTo_textShouldBeginEditing [self sizeToFit]; [self scrollRangeToVisible:_selectionRange]; [self setNeedsDisplay:YES]; + + // An undone or redone change may alter the paragraph style even when the selection stays the same. + [self updateRuler]; } - (void)_replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPString)aString selectionRange:(CPRange)selectionRange @@ -2696,6 +2707,32 @@ var _CPTextViewRulerImageNameForTab = function(aTab) [self setRulerVisible:![self isRulerVisible]]; } +/*! + Copies the paragraph style at the selection, with its tab stops, indents and alignment, + to the ruler pasteboard, so that Paste Ruler can apply it to other paragraphs. + @param aSender the object requesting this +*/ +- (@action)copyRuler:(id)aSender +{ + var pasteboard = [CPPasteboard pasteboardWithName:CPRulerPboard]; + + [pasteboard declareTypes:[CPRulerPboardType] owner:nil]; + [pasteboard setData:[CPKeyedArchiver archivedDataWithRootObject:[self _rulerParagraphStyle]] forType:CPRulerPboardType]; +} + +/*! + Applies the paragraph style on the ruler pasteboard to the selected paragraphs, or to + the paragraph at the insertion point. + @param aSender the object requesting this +*/ +- (@action)pasteRuler:(id)aSender +{ + var data = [[CPPasteboard pasteboardWithName:CPRulerPboard] dataForType:CPRulerPboardType]; + + if (data) + [self _applyRulerParagraphStyle:[CPKeyedUnarchiver unarchiveObjectWithData:data]]; +} + /*! Shows the tab stops and indents of the paragraph at the selection on the ruler, and lines the ruler's zero up with the start of the text. Does nothing while the user drags @@ -3058,6 +3095,12 @@ var _CPTextViewRulerImageNameForTab = function(aTab) if (targetRange.length > 0) { + // Undo restores the paragraphs' previous attributes, and registers the reverse for redo. + [[[self _registeringUndoManager] prepareWithInvocationTarget:self] + _replaceCharactersInRange:CPMakeRangeCopy(targetRange) + withAttributedString:[_textStorage attributedSubstringFromRange:CPMakeRangeCopy(targetRange)] + selectionRange:CPMakeRangeCopy(_selectionRange)]; + [_textStorage addAttribute:CPParagraphStyleAttributeName value:aParagraphStyle range:CPMakeRangeCopy(targetRange)]; [_layoutManager textStorage:_textStorage edited:0 range:CPMakeRangeCopy(targetRange) changeInLength:0 invalidatedRange:CPMakeRangeCopy(targetRange)]; } diff --git a/Tests/Manual/TextEditor/WORKLIST.md b/Tests/Manual/TextEditor/WORKLIST.md index 56ac2b07c..c993e5c3e 100644 --- a/Tests/Manual/TextEditor/WORKLIST.md +++ b/Tests/Manual/TextEditor/WORKLIST.md @@ -24,6 +24,10 @@ TextEditor is a document-based test application and a tutorial. When the app use - **Images in text.** AppKit has no general `CPTextAttachment` class, only the table attachment `_CPTableTextAttachment`. The RTF parser and producer do not handle `\pict`. - **Lists.** There is no `CPTextList`, and `CPParagraphStyle` has no `textLists`. +### Text view + +- **Format > Font > Copy Style and Paste Style raise an exception.** They send `copyFont:` and `pasteFont:`, which `CPText` declares as abstract methods that raise (`CPText.j:210` and `:261`). `CPTextView` does not implement them, so the menu items are enabled and choosing one raises. `CPTextView` needs Cocoa's implementations: copy the font and character attributes at the selection to the font pasteboard (`CPFontPboard`), and apply them to the selection. + ### Ruler - **Marker appearance and vertical alignment differ from TextEdit.** The marker images, and where markers sit across the ruler, are interim until Aristo3 supplies its own; adjust them to match TextEdit then. A tab stop dragged up off the ruler follows the pointer and is clipped by the ruler's edge, as in TextEdit. Dragged down off the ruler, it stays on its line and is drawn faded until release; TextEdit's behaviour in that direction has not been checked. @@ -68,4 +72,5 @@ TextEditor is a document-based test application and a tutorial. When the app use - The ruler: markers follow the paragraph at the selection; first line, head and right indents change the layout as shown; clicking adds a tab stop, and dragging moves or removes one; the Control-click menu changes a tab stop's type; the ruler's zero lines up with the start of the text, and markers at 0 are fully visible. - Text stays inside the visible width, with Cocoa's 5-point line fragment padding. - Tab characters: the Tab key inserts a tab in the text view, and left, centre, right and decimal tab stops align their columns. +- Format > Text > Copy Ruler and Paste Ruler apply one paragraph's indents and tab stops to others. - Builds with both the legacy toolchain and capp-build.