mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
fixed: basic ruler view support
This commit is contained in:
+13
-4
@@ -29,6 +29,7 @@
|
||||
@import "CPClipView.j"
|
||||
@import "CPScroller.j"
|
||||
@import "CPView.j"
|
||||
@import "CPRulerView.j"
|
||||
|
||||
@class CPTableView
|
||||
@class CPRulerView
|
||||
@@ -824,7 +825,7 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
|
||||
if (_hasHorizontalRuler && !_horizontalRuler)
|
||||
{
|
||||
_horizontalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPHorizontalRuler];
|
||||
_horizontalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationHorizontal];
|
||||
}
|
||||
|
||||
[self tile];
|
||||
@@ -844,7 +845,7 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
|
||||
if (_hasVerticalRuler && !_verticalRuler)
|
||||
{
|
||||
_verticalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPVerticalRuler];
|
||||
_verticalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationVertical];
|
||||
}
|
||||
|
||||
[self tile];
|
||||
@@ -1428,7 +1429,11 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
CGRectGetWidth(contentFrame),
|
||||
horizRulerThickness
|
||||
)];
|
||||
[_horizontalRuler setNeedsDisplay:YES];
|
||||
|
||||
if ([_horizontalRuler respondsToSelector:@selector(updateRuler)])
|
||||
[_horizontalRuler updateRuler];
|
||||
else
|
||||
[_horizontalRuler setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
if (showVerticalRuler)
|
||||
@@ -1439,7 +1444,11 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
vertRulerThickness,
|
||||
CGRectGetHeight(contentFrame)
|
||||
)];
|
||||
[_verticalRuler setNeedsDisplay:YES];
|
||||
|
||||
if ([_verticalRuler respondsToSelector:@selector(updateRuler)])
|
||||
[_verticalRuler updateRuler];
|
||||
else
|
||||
[_verticalRuler setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
--_recursionCount;
|
||||
|
||||
@@ -217,24 +217,112 @@ var _sharedDefaultParagraphStyle = nil;
|
||||
@end
|
||||
|
||||
|
||||
// MARK: - CPMutableParagraphStyle Implementation
|
||||
|
||||
// MARK: - CPMutableParagraphStyle Implementation
|
||||
|
||||
@implementation CPMutableParagraphStyle : CPParagraphStyle
|
||||
{
|
||||
}
|
||||
|
||||
- (id)initWithParagraphStyle:(CPParagraphStyle)other
|
||||
{
|
||||
if (self = [super initWithParagraphStyle:other])
|
||||
{
|
||||
// Ensure our tab stops array is mutable in the mutable subclass
|
||||
_tabStops = [[other tabStops] mutableCopy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_tabStops = [_tabStops mutableCopy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setLineSpacing:(float)aLineSpacing
|
||||
{
|
||||
_lineSpacing = aLineSpacing;
|
||||
}
|
||||
|
||||
- (void)setParagraphSpacing:(float)aParagraphSpacing
|
||||
{
|
||||
_paragraphSpacing = aParagraphSpacing;
|
||||
}
|
||||
|
||||
- (void)setAlignment:(CPTextAlignment)anAlignment
|
||||
{
|
||||
_alignment = anAlignment;
|
||||
}
|
||||
|
||||
- (void)setHeadIndent:(float)aHeadIndent
|
||||
{
|
||||
_headIndent = aHeadIndent;
|
||||
}
|
||||
|
||||
- (void)setTailIndent:(float)aTailIndent
|
||||
{
|
||||
_tailIndent = aTailIndent;
|
||||
}
|
||||
|
||||
- (void)setFirstLineHeadIndent:(float)aFirstLineHeadIndent
|
||||
{
|
||||
_firstLineHeadIndent = aFirstLineHeadIndent;
|
||||
}
|
||||
|
||||
- (void)setMinimumLineHeight:(float)aMinimumLineHeight
|
||||
{
|
||||
_minimumLineHeight = aMinimumLineHeight;
|
||||
}
|
||||
|
||||
- (void)setMaximumLineHeight:(float)aMaximumLineHeight
|
||||
{
|
||||
_maximumLineHeight = aMaximumLineHeight;
|
||||
}
|
||||
|
||||
- (void)setLineBreakMode:(CPLineBreakMode)aLineBreakMode
|
||||
{
|
||||
_lineBreakMode = aLineBreakMode;
|
||||
}
|
||||
|
||||
- (void)setBaseWritingDirection:(CPWritingDirection)aBaseWritingDirection
|
||||
{
|
||||
_baseWritingDirection = aBaseWritingDirection;
|
||||
}
|
||||
|
||||
- (void)setLineHeightMultiple:(float)aLineHeightMultiple
|
||||
{
|
||||
_lineHeightMultiple = aLineHeightMultiple;
|
||||
}
|
||||
|
||||
- (void)setParagraphSpacingBefore:(float)aParagraphSpacingBefore
|
||||
{
|
||||
_paragraphSpacingBefore = aParagraphSpacingBefore;
|
||||
}
|
||||
|
||||
- (void)setDefaultTabInterval:(float)aDefaultTabInterval
|
||||
{
|
||||
_defaultTabInterval = aDefaultTabInterval;
|
||||
}
|
||||
|
||||
- (void)addTabStop:(CPTextTab)aTabStop
|
||||
{
|
||||
// Copy on write logic would go here if we shared structure,
|
||||
// but here we just mutate the array.
|
||||
[_tabStops addObject:aTabStop];
|
||||
}
|
||||
|
||||
- (void)removeTabStop:(CPTextTab)aTabStop
|
||||
{
|
||||
[_tabStops removeObject:aTabStop];
|
||||
}
|
||||
|
||||
- (void)setTabStops:(CPArray)newTabStops
|
||||
{
|
||||
if (_tabStops === newTabStops) return;
|
||||
_tabStops = [newTabStops copy];
|
||||
_tabStops = [newTabStops mutableCopy];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(CPZone)aZone
|
||||
@@ -244,81 +332,3 @@ var _sharedDefaultParagraphStyle = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// MARK: - CPCoding
|
||||
|
||||
var CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey",
|
||||
CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey",
|
||||
CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey",
|
||||
CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey",
|
||||
CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey",
|
||||
CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey",
|
||||
CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey",
|
||||
CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey",
|
||||
CPParagraphStyleLineBreakModeKey = @"CPParagraphStyleLineBreakModeKey",
|
||||
CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey",
|
||||
CPParagraphStyleBaseWritingDirectionKey = @"CPParagraphStyleBaseWritingDirectionKey",
|
||||
CPParagraphStyleLineHeightMultipleKey = @"CPParagraphStyleLineHeightMultipleKey",
|
||||
CPParagraphStyleParagraphSpacingBeforeKey = @"CPParagraphStyleParagraphSpacingBeforeKey",
|
||||
CPParagraphStyleDefaultTabIntervalKey = @"CPParagraphStyleDefaultTabIntervalKey";
|
||||
|
||||
@implementation CPParagraphStyle (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_lineSpacing = [aCoder decodeFloatForKey:CPParagraphStyleLineSpacingKey];
|
||||
_paragraphSpacing = [aCoder decodeFloatForKey:CPParagraphStyleParagraphSpacingKey];
|
||||
_alignment = [aCoder decodeIntForKey:CPParagraphStyleAlignmentKey];
|
||||
_headIndent = [aCoder decodeFloatForKey:CPParagraphStyleHeadIndentKey];
|
||||
_tailIndent = [aCoder decodeFloatForKey:CPParagraphStyleTailIndentKey];
|
||||
_firstLineHeadIndent = [aCoder decodeFloatForKey:CPParagraphStyleFirstLineHeadIndentKey];
|
||||
_minimumLineHeight = [aCoder decodeFloatForKey:CPParagraphStyleMinimumLineHeightKey];
|
||||
_maximumLineHeight = [aCoder decodeFloatForKey:CPParagraphStyleMaximumLineHeightKey];
|
||||
_lineBreakMode = [aCoder decodeIntForKey:CPParagraphStyleLineBreakModeKey];
|
||||
_tabStops = [aCoder decodeObjectForKey:CPParagraphStyleTabStopsKey];
|
||||
|
||||
// Handle potential missing keys for backward compatibility or new properties
|
||||
if ([aCoder containsValueForKey:CPParagraphStyleBaseWritingDirectionKey])
|
||||
_baseWritingDirection = [aCoder decodeIntForKey:CPParagraphStyleBaseWritingDirectionKey];
|
||||
else
|
||||
_baseWritingDirection = CPWritingDirectionNatural;
|
||||
|
||||
if ([aCoder containsValueForKey:CPParagraphStyleLineHeightMultipleKey])
|
||||
_lineHeightMultiple = [aCoder decodeFloatForKey:CPParagraphStyleLineHeightMultipleKey];
|
||||
|
||||
if ([aCoder containsValueForKey:CPParagraphStyleParagraphSpacingBeforeKey])
|
||||
_paragraphSpacingBefore = [aCoder decodeFloatForKey:CPParagraphStyleParagraphSpacingBeforeKey];
|
||||
|
||||
if ([aCoder containsValueForKey:CPParagraphStyleDefaultTabIntervalKey])
|
||||
_defaultTabInterval = [aCoder decodeFloatForKey:CPParagraphStyleDefaultTabIntervalKey];
|
||||
else
|
||||
_defaultTabInterval = kDefaultTabInterval;
|
||||
|
||||
if (!_tabStops) _tabStops = [];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeFloat:_lineSpacing forKey:CPParagraphStyleLineSpacingKey];
|
||||
[aCoder encodeFloat:_paragraphSpacing forKey:CPParagraphStyleParagraphSpacingKey];
|
||||
[aCoder encodeInt:_alignment forKey:CPParagraphStyleAlignmentKey];
|
||||
[aCoder encodeFloat:_headIndent forKey:CPParagraphStyleHeadIndentKey];
|
||||
[aCoder encodeFloat:_tailIndent forKey:CPParagraphStyleTailIndentKey];
|
||||
[aCoder encodeFloat:_firstLineHeadIndent forKey:CPParagraphStyleFirstLineHeadIndentKey];
|
||||
[aCoder encodeFloat:_minimumLineHeight forKey:CPParagraphStyleMinimumLineHeightKey];
|
||||
[aCoder encodeFloat:_maximumLineHeight forKey:CPParagraphStyleMaximumLineHeightKey];
|
||||
[aCoder encodeInt:_lineBreakMode forKey:CPParagraphStyleLineBreakModeKey];
|
||||
[aCoder encodeObject:_tabStops forKey:CPParagraphStyleTabStopsKey];
|
||||
|
||||
[aCoder encodeInt:_baseWritingDirection forKey:CPParagraphStyleBaseWritingDirectionKey];
|
||||
[aCoder encodeFloat:_lineHeightMultiple forKey:CPParagraphStyleLineHeightMultipleKey];
|
||||
[aCoder encodeFloat:_paragraphSpacingBefore forKey:CPParagraphStyleParagraphSpacingBeforeKey];
|
||||
[aCoder encodeFloat:_defaultTabInterval forKey:CPParagraphStyleDefaultTabIntervalKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+294
-1003
File diff suppressed because it is too large
Load Diff
@@ -542,12 +542,15 @@ var kDelegateRespondsTo_textShouldBeginEditing
|
||||
- (void)_becomeFirstResponder
|
||||
{
|
||||
[self updateInsertionPointStateAndRestartTimer:YES];
|
||||
|
||||
// SYNCHRONIZE ACTIVE PARAGRAPH MARKERS ON EDITOR FOCUS
|
||||
[self updateRuler];
|
||||
|
||||
[[CPFontManager sharedFontManager] setSelectedFont:[self font] isMultiple:NO];
|
||||
[self setNeedsDisplay:YES];
|
||||
[[CPRunLoop currentRunLoop] performSelector:@selector(focusForTextView:) target:[_CPNativeInputManager class] argument:self order:0 modes:[CPDefaultRunLoopMode]];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
[super becomeFirstResponder];
|
||||
@@ -1783,6 +1786,9 @@ Sets the selection to a range of characters in response to user action.
|
||||
[self _enrichEssentialTypingAttributes:_typingAttributes];
|
||||
}
|
||||
|
||||
// SYNCHRONIZE ACTIVE PARAGRAPH MARKERS ON TYPING ATTRIBUTES CHANGE
|
||||
[self updateRuler];
|
||||
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
|
||||
|
||||
// We always clear the saved selection range from the last mouse down event here.
|
||||
@@ -2406,6 +2412,233 @@ Sets the selection to a range of characters in response to user action.
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTextView (CPRulerSupport)
|
||||
|
||||
- (void)updateRuler
|
||||
{
|
||||
var scrollView = [self enclosingScrollView];
|
||||
if (!scrollView || ![scrollView hasHorizontalRuler] || ![scrollView rulersVisible])
|
||||
return;
|
||||
|
||||
var ruler = [scrollView horizontalRulerView];
|
||||
if (!ruler)
|
||||
return;
|
||||
|
||||
var selectedRange = [self selectedRange],
|
||||
paragraphStyle = [CPParagraphStyle defaultParagraphStyle],
|
||||
currentAttributes = _typingAttributes;
|
||||
|
||||
var textLength = [_textStorage length];
|
||||
if (textLength > 0)
|
||||
{
|
||||
var charIndex = selectedRange.location;
|
||||
|
||||
// Safety bounds checks for cursor placements
|
||||
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 markers = [],
|
||||
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],
|
||||
marker = [[CPRulerMarker alloc] initWithRulerView:ruler
|
||||
markerLocation:[tab location]
|
||||
imageValue:[tab location]
|
||||
representedObject:tab];
|
||||
[markers addObject:marker];
|
||||
}
|
||||
|
||||
// B. Add Indentation Handles (First line indent & Head indent)
|
||||
var firstLineMarker = [[CPRulerMarker alloc] initWithRulerView:ruler
|
||||
markerLocation:[paragraphStyle firstLineHeadIndent]
|
||||
imageValue:[paragraphStyle firstLineHeadIndent]
|
||||
representedObject:@"CPFirstLineIndent"];
|
||||
[firstLineMarker._label setStringValue:@"▼"]; // downward arrow styling
|
||||
[markers addObject:firstLineMarker];
|
||||
|
||||
var headMarker = [[CPRulerMarker alloc] initWithRulerView:ruler
|
||||
markerLocation:[paragraphStyle headIndent]
|
||||
imageValue:[paragraphStyle headIndent]
|
||||
representedObject:@"CPHeadIndent"];
|
||||
[headMarker._label setStringValue:@"▼"];
|
||||
[markers addObject:headMarker];
|
||||
|
||||
[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;
|
||||
return CPOrderedSame;
|
||||
};
|
||||
|
||||
- (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;
|
||||
|
||||
if (selectedRange.length > 0)
|
||||
currentAttributes = [_textStorage attributesAtIndex:selectedRange.location 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];
|
||||
|
||||
if (selectedRange.length > 0)
|
||||
{
|
||||
[_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(selectedRange)];
|
||||
|
||||
[_layoutManager textStorage:_textStorage
|
||||
edited:0
|
||||
range:CPMakeRangeCopy(selectedRange)
|
||||
changeInLength:0
|
||||
invalidatedRange:CPMakeRangeCopy(selectedRange)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_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;
|
||||
|
||||
if (selectedRange.length > 0)
|
||||
currentAttributes = [_textStorage attributesAtIndex:selectedRange.location effectiveRange:nil];
|
||||
|
||||
if ([currentAttributes objectForKey:CPParagraphStyleAttributeName])
|
||||
paragraphStyle = [currentAttributes objectForKey:CPParagraphStyleAttributeName];
|
||||
|
||||
var mutableStyle = [paragraphStyle mutableCopy],
|
||||
oldTab = [marker representedObject];
|
||||
|
||||
if (!oldTab)
|
||||
return;
|
||||
|
||||
// A. Handle standard tab stops
|
||||
if ([oldTab isKindOfClass:[CPTextTab class]])
|
||||
{
|
||||
var newTab = [[CPTextTab alloc] initWithType:[oldTab alignment] location:[marker imageValue]],
|
||||
tabs = [[mutableStyle tabStops] mutableCopy];
|
||||
|
||||
[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]];
|
||||
}
|
||||
|
||||
if (selectedRange.length > 0)
|
||||
{
|
||||
[_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(selectedRange)];
|
||||
|
||||
[_layoutManager textStorage:_textStorage
|
||||
edited:0
|
||||
range:CPMakeRangeCopy(selectedRange)
|
||||
changeInLength:0
|
||||
invalidatedRange:CPMakeRangeCopy(selectedRange)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rulerView:(CPRulerView)rulerView didRemoveMarker:(CPRulerMarker)marker
|
||||
{
|
||||
if (![self _didBeginEditing] || ![self shouldChangeTextInRange:_selectionRange replacementString:nil])
|
||||
return;
|
||||
|
||||
var selectedRange = [self selectedRange],
|
||||
paragraphStyle = [CPParagraphStyle defaultParagraphStyle],
|
||||
currentAttributes = _typingAttributes;
|
||||
|
||||
if (selectedRange.length > 0)
|
||||
currentAttributes = [_textStorage attributesAtIndex:selectedRange.location effectiveRange:nil];
|
||||
|
||||
if ([currentAttributes objectForKey:CPParagraphStyleAttributeName])
|
||||
paragraphStyle = [currentAttributes objectForKey:CPParagraphStyleAttributeName];
|
||||
|
||||
var mutableStyle = [paragraphStyle mutableCopy],
|
||||
oldTab = [marker representedObject];
|
||||
|
||||
if (!oldTab)
|
||||
return;
|
||||
|
||||
var tabs = [[mutableStyle tabStops] mutableCopy];
|
||||
[tabs removeObject:oldTab];
|
||||
|
||||
[mutableStyle setTabStops:tabs];
|
||||
|
||||
if (selectedRange.length > 0)
|
||||
{
|
||||
[_textStorage addAttribute:CPParagraphStyleAttributeName value:mutableStyle range:CPMakeRangeCopy(selectedRange)];
|
||||
|
||||
[_layoutManager textStorage:_textStorage
|
||||
edited:0
|
||||
range:CPMakeRangeCopy(selectedRange)
|
||||
changeInLength:0
|
||||
invalidatedRange:CPMakeRangeCopy(selectedRange)];
|
||||
}
|
||||
else
|
||||
{
|
||||
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTextView (CPTextViewDelegate)
|
||||
|
||||
|
||||
@@ -135,29 +135,34 @@ var CPSystemTypesetterFactory,
|
||||
return [_layoutManager textContainers];
|
||||
}
|
||||
|
||||
// Retrieves correct CPTextTab stop accounting for CPArray properties
|
||||
- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction
|
||||
{
|
||||
var tabStops = [_currentParagraph tabStops];
|
||||
|
||||
if (!tabStops)
|
||||
tabStops = [CPParagraphStyle _defaultTabStops];
|
||||
tabStops = [[CPParagraphStyle defaultParagraphStyle] tabStops];
|
||||
|
||||
var l = tabStops.length;
|
||||
var l = [tabStops count];
|
||||
if (l === 0)
|
||||
return nil;
|
||||
|
||||
if (aWidth > tabStops[l - 1]._location)
|
||||
var lastTab = [tabStops lastObject];
|
||||
if (aWidth > [lastTab location])
|
||||
return nil;
|
||||
|
||||
for (var i = l - 1; i >= 0; i--)
|
||||
{
|
||||
if (aWidth > tabStops[i]._location)
|
||||
var tab = [tabStops objectAtIndex:i];
|
||||
if (aWidth > [tab location])
|
||||
{
|
||||
if (i + 1 < l)
|
||||
return tabStops[i + 1];
|
||||
return [tabStops objectAtIndex:i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (i === -1)
|
||||
return tabStops[0];
|
||||
return [tabStops objectAtIndex:0];
|
||||
|
||||
return nil;
|
||||
}
|
||||
@@ -356,9 +361,50 @@ var CPSystemTypesetterFactory,
|
||||
isTabStop = YES;
|
||||
|
||||
if (nextTab)
|
||||
rangeWidth = nextTab._location - lineOrigin.x;
|
||||
{
|
||||
// Look-ahead to measure the width of the incoming text segment for alignment
|
||||
var nextSegmentWidth = 0.0,
|
||||
tempIndex = glyphIndex + 1,
|
||||
segmentString = "";
|
||||
|
||||
while (tempIndex < numberOfGlyphs)
|
||||
{
|
||||
var nextCharCode = theString.charCodeAt(tempIndex);
|
||||
if (nextCharCode === 9 || nextCharCode === 10 || nextCharCode === 13)
|
||||
break;
|
||||
segmentString += theString.charAt(tempIndex);
|
||||
tempIndex++;
|
||||
}
|
||||
|
||||
if (segmentString.length > 0)
|
||||
nextSegmentWidth = [segmentString sizeWithFont:currentFont inWidth:NULL].width;
|
||||
|
||||
var tabLocation = [nextTab location],
|
||||
tabAlignment = [nextTab alignment];
|
||||
|
||||
// Mathematically offset the tab character's right boundary
|
||||
if (tabAlignment === CPCenterTextAlignment)
|
||||
{
|
||||
rangeWidth = (tabLocation - nextSegmentWidth / 2.0) - lineOrigin.x;
|
||||
}
|
||||
else if (tabAlignment === CPRightTextAlignment)
|
||||
{
|
||||
rangeWidth = (tabLocation - nextSegmentWidth) - lineOrigin.x;
|
||||
}
|
||||
else // Left align tab stop
|
||||
{
|
||||
rangeWidth = tabLocation - lineOrigin.x;
|
||||
}
|
||||
|
||||
// Enforce a minimum safety spacer width to avoid character overlapping
|
||||
var minRangeWidth = prevRangeWidth + 5.0;
|
||||
if (rangeWidth < minRangeWidth)
|
||||
rangeWidth = minRangeWidth;
|
||||
}
|
||||
else
|
||||
rangeWidth += 28; //FIXME
|
||||
{
|
||||
rangeWidth += 28.0; // standard fallback spacer
|
||||
}
|
||||
} // fallthrough intentional
|
||||
case 32: // ' '
|
||||
wrapRange = CPMakeRangeCopy(lineRange);
|
||||
|
||||
@@ -75,6 +75,21 @@
|
||||
[_textView alignJustified:self];
|
||||
}
|
||||
|
||||
- (void)insertAttachment:(id)sender
|
||||
{
|
||||
// Insert modern spinner image attachment
|
||||
var tempImageView = [[CPImageView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
|
||||
[tempImageView setImage:[[CPImage alloc] initWithContentsOfFile:@"Resources/spinner.gif" size:CGSizeMake(32, 32)]];
|
||||
[_textView insertText:[CPTextStorage attributedStringWithAttachment:tempImageView]];
|
||||
[_textView insertText:@" "];
|
||||
|
||||
// Insert an interactive button attachment
|
||||
var tempButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 80, 28)];
|
||||
[tempButton setTitle:@"Click Me"];
|
||||
[_textView insertText:[CPTextStorage attributedStringWithAttachment:tempButton]];
|
||||
[_textView insertText:@" "];
|
||||
}
|
||||
|
||||
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
|
||||
{
|
||||
var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask],
|
||||
@@ -112,7 +127,7 @@
|
||||
[rulerButton setTarget:self];
|
||||
[rulerButton setAction:@selector(toggleRuler:)];
|
||||
[toolbarView addSubview:rulerButton];
|
||||
currentX += 140;
|
||||
currentX += 130;
|
||||
|
||||
// RTF Roundtrip Trigger
|
||||
var rtfButton = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 150, 30)];
|
||||
@@ -120,7 +135,15 @@
|
||||
[rtfButton setTarget:self];
|
||||
[rtfButton setAction:@selector(makeRTF:)];
|
||||
[toolbarView addSubview:rtfButton];
|
||||
currentX += 165;
|
||||
currentX += 160;
|
||||
|
||||
// Insert Attachment Trigger
|
||||
var attachButton = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 140, 30)];
|
||||
[attachButton setTitle:@"Insert Attachment"];
|
||||
[attachButton setTarget:self];
|
||||
[attachButton setAction:@selector(insertAttachment:)];
|
||||
[toolbarView addSubview:attachButton];
|
||||
currentX += 150;
|
||||
|
||||
// Text Alignment Group
|
||||
var labelAlign = [[CPTextField alloc] initWithFrame:CGRectMake(currentX, 22, 45, 20)];
|
||||
@@ -129,34 +152,34 @@
|
||||
[toolbarView addSubview:labelAlign];
|
||||
currentX += 45;
|
||||
|
||||
var alignLeftBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 30, 30)];
|
||||
[alignLeftBtn setTitle:@"⃔"]; // Left-align symbol
|
||||
var alignLeftBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 50, 30)];
|
||||
[alignLeftBtn setTitle:@"Left"];
|
||||
[alignLeftBtn setTarget:self];
|
||||
[alignLeftBtn setAction:@selector(alignLeft:)];
|
||||
[toolbarView addSubview:alignLeftBtn];
|
||||
currentX += 32;
|
||||
currentX += 55;
|
||||
|
||||
var alignCenterBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 30, 30)];
|
||||
[alignCenterBtn setTitle:@"↔"]; // Center-align symbol
|
||||
var alignCenterBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 60, 30)];
|
||||
[alignCenterBtn setTitle:@"Center"];
|
||||
[alignCenterBtn setTarget:self];
|
||||
[alignCenterBtn setAction:@selector(alignCenter:)];
|
||||
[toolbarView addSubview:alignCenterBtn];
|
||||
currentX += 32;
|
||||
currentX += 65;
|
||||
|
||||
var alignRightBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 30, 30)];
|
||||
[alignRightBtn setTitle:@"⃕"]; // Right-align symbol
|
||||
var alignRightBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 55, 30)];
|
||||
[alignRightBtn setTitle:@"Right"];
|
||||
[alignRightBtn setTarget:self];
|
||||
[alignRightBtn setAction:@selector(alignRight:)];
|
||||
[toolbarView addSubview:alignRightBtn];
|
||||
currentX += 32;
|
||||
currentX += 60;
|
||||
|
||||
var alignJustifyBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 30, 30)];
|
||||
[alignJustifyBtn setTitle:@"≡"]; // Justify-align symbol
|
||||
var alignJustifyBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 65, 30)];
|
||||
[alignJustifyBtn setTitle:@"Justify"];
|
||||
[alignJustifyBtn setTarget:self];
|
||||
[alignJustifyBtn setAction:@selector(alignJustified:)];
|
||||
[toolbarView addSubview:alignJustifyBtn];
|
||||
|
||||
// Default return key target test (as defined in original source)
|
||||
// Default return key target test
|
||||
var returnButton = [[CPButton alloc] initWithFrame:CGRectMake(CGRectGetWidth([contentView bounds]) - 270, 15, 250, 30)];
|
||||
[returnButton setAutoresizingMask:CPViewMinXMargin];
|
||||
[returnButton setTitle:@"Key Return Target"];
|
||||
@@ -244,44 +267,41 @@
|
||||
[mainMenu setSubmenu:formatMenu forItem:item];
|
||||
|
||||
// 4. Load Rich Sample Text content
|
||||
[_textView insertText:@"123"];
|
||||
var tempImageView = [[CPImageView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
|
||||
[tempImageView setImage:[[CPImage alloc] initWithContentsOfFile:@"Resources/spinner.gif" size:CGSizeMake(32, 32)]];
|
||||
[_textView insertText:@"123 456 "];
|
||||
|
||||
[_textView insertText:[CPTextStorage attributedStringWithAttachment:tempImageView]];
|
||||
[_textView insertText:@" 456 "];
|
||||
|
||||
var tempButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 64, 28)];
|
||||
[_textView insertText:[CPTextStorage attributedStringWithAttachment:tempButton]];
|
||||
|
||||
// Centered paragraph text block
|
||||
var centeredParagraph = [CPParagraphStyle new];
|
||||
// Elegant Slate-Blue & Soft Light Blue paragraph
|
||||
var centeredParagraph = [CPMutableParagraphStyle new];
|
||||
[centeredParagraph setAlignment:CPCenterTextAlignment];
|
||||
[_textView insertText:@"\n"];
|
||||
|
||||
var elegantForeground = [CPColor colorWithRed:0.18 green:0.24 blue:0.35 alpha:1.0]; // Slate Blue
|
||||
var elegantBackground = [CPColor colorWithRed:0.92 green:0.95 blue:0.98 alpha:1.0]; // Soft Sky Blue tint
|
||||
|
||||
[_textView insertText:[[CPAttributedString alloc] initWithString:@"Fusce\n"
|
||||
attributes:[CPDictionary dictionaryWithObjects:[centeredParagraph, [CPFont boldFontWithName:@"Arial" size:18], [CPColor redColor], [CPColor yellowColor]]
|
||||
attributes:[CPDictionary dictionaryWithObjects:[centeredParagraph, [CPFont boldFontWithName:@"Arial" size:18], elegantForeground, elegantBackground]
|
||||
forKeys:[CPParagraphStyleAttributeName, CPFontAttributeName, CPForegroundColorAttributeName, CPBackgroundColorAttributeName]]]];
|
||||
|
||||
// Highlighted Heading
|
||||
// Highlighted Heading - Pine & Sage Green tones
|
||||
[_textView insertText:@"\n"];
|
||||
var showcaseForeground = [CPColor colorWithRed:0.15 green:0.25 blue:0.15 alpha:1.0]; // Forest Green
|
||||
var showcaseBackground = [CPColor colorWithRed:0.94 green:0.97 blue:0.92 alpha:1.0]; // Light Sage Green
|
||||
|
||||
[_textView insertText:[[CPAttributedString alloc] initWithString:@"Interactive Ruler Showcase\n"
|
||||
attributes:[CPDictionary dictionaryWithObjects:[[CPFont boldFontWithName:@"Arial" size:22], [CPColor yellowColor]]
|
||||
forKeys:[CPFontAttributeName, CPBackgroundColorAttributeName]]]];
|
||||
attributes:[CPDictionary dictionaryWithObjects:[[CPFont boldFontWithName:@"Arial" size:22], showcaseForeground, showcaseBackground]
|
||||
forKeys:[CPFontAttributeName, CPForegroundColorAttributeName, CPBackgroundColorAttributeName]]]];
|
||||
|
||||
// DEMONSTRATION OF CPRULERVIEW TAB MARKERS
|
||||
// Creating left, center, and right tabs to showcase the ruler markers dynamically
|
||||
var tabParagraph = [[CPParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
var tab1 = [[CPTextTab alloc] initWithType:CPLeftTabStopType location:100.0];
|
||||
var tab2 = [[CPTextTab alloc] initWithType:CPCenterTabStopType location:220.0];
|
||||
var tab3 = [[CPTextTab alloc] initWithType:CPRightTabStopType location:340.0];
|
||||
var tab1 = [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:100.0];
|
||||
var tab2 = [[CPTextTab alloc] initWithType:CPCenterTextAlignment location:220.0];
|
||||
var tab3 = [[CPTextTab alloc] initWithType:CPRightTextAlignment location:340.0];
|
||||
[tabParagraph setTabStops:[tab1, tab2, tab3]];
|
||||
|
||||
[_textView insertText:@"\n"];
|
||||
[_textView insertText:[[CPAttributedString alloc] initWithString:@"Tab1\tTab2\tTab3\nLeftAlign\tCenterAlign\tRightAlign\n"
|
||||
attributes:[CPDictionary dictionaryWithObject:tabParagraph forKey:CPParagraphStyleAttributeName]]];
|
||||
[_textView insertText:[[CPAttributedString alloc] initWithString:@"\tTab1\tTab2\tTab3\n\tLeftAlign\tCenterAlign\tRightAlign\n"
|
||||
attributes:[CPDictionary dictionaryWithObject:tabParagraph forKey:CPParagraphStyleAttributeName]]];
|
||||
|
||||
// DEMONSTRATION OF INDENTATION MARKERS
|
||||
// Creating custom margin and indentation settings
|
||||
var indentParagraph = [[CPParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
[indentParagraph setFirstLineHeadIndent:30.0];
|
||||
[indentParagraph setHeadIndent:50.0];
|
||||
|
||||
Reference in New Issue
Block a user