mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
+205
-7
@@ -29,8 +29,10 @@
|
||||
@import "CPClipView.j"
|
||||
@import "CPScroller.j"
|
||||
@import "CPView.j"
|
||||
@import "CPRulerView.j"
|
||||
|
||||
@class CPTableView
|
||||
@class CPRulerView
|
||||
|
||||
#define SHOULD_SHOW_CORNER_VIEW() (_scrollerStyle === CPScrollerStyleLegacy && _verticalScroller && ![_verticalScroller isHidden])
|
||||
|
||||
@@ -140,6 +142,14 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
|
||||
|
||||
int _scrollerStyle;
|
||||
int _scrollerKnobStyle;
|
||||
|
||||
// Ruler Support
|
||||
BOOL _hasVerticalRuler;
|
||||
BOOL _hasHorizontalRuler;
|
||||
BOOL _rulersVisible;
|
||||
|
||||
CPRulerView _verticalRuler;
|
||||
CPRulerView _horizontalRuler;
|
||||
}
|
||||
|
||||
|
||||
@@ -306,6 +316,10 @@ var CPScrollerStyleGlobal = CPScrollerStyleOverlay,
|
||||
_scrollerKnobStyle = CPScrollerKnobStyleDefault;
|
||||
[self setScrollerStyle:CPScrollerStyleGlobal];
|
||||
|
||||
_hasVerticalRuler = NO;
|
||||
_hasHorizontalRuler = NO;
|
||||
_rulersVisible = NO;
|
||||
|
||||
_delegate = nil;
|
||||
_scrollTimer = nil;
|
||||
_implementedDelegateMethods = 0;
|
||||
@@ -794,6 +808,103 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Rulers
|
||||
|
||||
- (BOOL)hasHorizontalRuler
|
||||
{
|
||||
return _hasHorizontalRuler;
|
||||
}
|
||||
|
||||
- (void)setHasHorizontalRuler:(BOOL)shouldHaveHorizontalRuler
|
||||
{
|
||||
if (_hasHorizontalRuler === shouldHaveHorizontalRuler)
|
||||
return;
|
||||
|
||||
_hasHorizontalRuler = shouldHaveHorizontalRuler;
|
||||
|
||||
if (_hasHorizontalRuler && !_horizontalRuler)
|
||||
{
|
||||
_horizontalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationHorizontal];
|
||||
}
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (BOOL)hasVerticalRuler
|
||||
{
|
||||
return _hasVerticalRuler;
|
||||
}
|
||||
|
||||
- (void)setHasVerticalRuler:(BOOL)shouldHaveVerticalRuler
|
||||
{
|
||||
if (_hasVerticalRuler === shouldHaveVerticalRuler)
|
||||
return;
|
||||
|
||||
_hasVerticalRuler = shouldHaveVerticalRuler;
|
||||
|
||||
if (_hasVerticalRuler && !_verticalRuler)
|
||||
{
|
||||
_verticalRuler = [[CPRulerView alloc] initWithScrollView:self orientation:CPRulerOrientationVertical];
|
||||
}
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (BOOL)rulersVisible
|
||||
{
|
||||
return _rulersVisible;
|
||||
}
|
||||
|
||||
- (void)setRulersVisible:(BOOL)areRulersVisible
|
||||
{
|
||||
if (_rulersVisible === areRulersVisible)
|
||||
return;
|
||||
|
||||
_rulersVisible = areRulersVisible;
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (CPRulerView)horizontalRulerView
|
||||
{
|
||||
return _horizontalRuler;
|
||||
}
|
||||
|
||||
- (void)setHorizontalRulerView:(CPRulerView)aRulerView
|
||||
{
|
||||
if (_horizontalRuler === aRulerView)
|
||||
return;
|
||||
|
||||
[_horizontalRuler removeFromSuperview];
|
||||
_horizontalRuler = aRulerView;
|
||||
|
||||
if (_horizontalRuler)
|
||||
[self addSubview:_horizontalRuler];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
- (CPRulerView)verticalRulerView
|
||||
{
|
||||
return _verticalRuler;
|
||||
}
|
||||
|
||||
- (void)setVerticalRulerView:(CPRulerView)aRulerView
|
||||
{
|
||||
if (_verticalRuler === aRulerView)
|
||||
return;
|
||||
|
||||
[_verticalRuler removeFromSuperview];
|
||||
_verticalRuler = aRulerView;
|
||||
|
||||
if (_verticalRuler)
|
||||
[self addSubview:_verticalRuler];
|
||||
|
||||
[self tile];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Privates
|
||||
|
||||
@@ -1123,10 +1234,7 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
*/
|
||||
- (void)tile
|
||||
{
|
||||
// yuck.
|
||||
// RESIZE: tile->setHidden AND refl
|
||||
// Outside Change: refl->tile->setHidden AND refl
|
||||
// scroll: refl.
|
||||
[self reflectScrolledClipView:_contentView];
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1170,6 +1278,41 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
contentFrame.origin.y += headerClipViewHeight;
|
||||
contentFrame.size.height -= headerClipViewHeight;
|
||||
|
||||
// Adjust content view based on horizontal / vertical ruler presence
|
||||
var showHorizontalRuler = _rulersVisible && _hasHorizontalRuler && _horizontalRuler,
|
||||
showVerticalRuler = _rulersVisible && _hasVerticalRuler && _verticalRuler;
|
||||
|
||||
var horizRulerThickness = showHorizontalRuler ? ([_horizontalRuler respondsToSelector:@selector(ruleThickness)] ? [_horizontalRuler ruleThickness] : 16.0) : 0.0,
|
||||
vertRulerThickness = showVerticalRuler ? ([_verticalRuler respondsToSelector:@selector(ruleThickness)] ? [_verticalRuler ruleThickness] : 24.0) : 0.0;
|
||||
|
||||
if (showHorizontalRuler)
|
||||
{
|
||||
if ([_horizontalRuler superview] !== self)
|
||||
[self addSubview:_horizontalRuler];
|
||||
[_horizontalRuler setHidden:NO];
|
||||
}
|
||||
else if (_horizontalRuler)
|
||||
{
|
||||
[_horizontalRuler setHidden:YES];
|
||||
}
|
||||
|
||||
if (showVerticalRuler)
|
||||
{
|
||||
if ([_verticalRuler superview] !== self)
|
||||
[self addSubview:_verticalRuler];
|
||||
[_verticalRuler setHidden:NO];
|
||||
}
|
||||
else if (_verticalRuler)
|
||||
{
|
||||
[_verticalRuler setHidden:YES];
|
||||
}
|
||||
|
||||
contentFrame.origin.y += horizRulerThickness;
|
||||
contentFrame.size.height -= horizRulerThickness;
|
||||
|
||||
contentFrame.origin.x += vertRulerThickness;
|
||||
contentFrame.size.width -= vertRulerThickness;
|
||||
|
||||
var difference = CGSizeMake(CGRectGetWidth(documentFrame) - CGRectGetWidth(contentFrame), CGRectGetHeight(documentFrame) - CGRectGetHeight(contentFrame)),
|
||||
verticalScrollerWidth = [_verticalScroller scrollerWidth],
|
||||
horizontalScrollerHeight = [_horizontalScroller scrollerWidth],
|
||||
@@ -1262,6 +1405,7 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
[_contentView setFrame:contentFrame];
|
||||
[_headerClipView setFrame:[self _headerClipViewFrame]];
|
||||
[[_headerClipView documentView] setNeedsDisplay:YES];
|
||||
|
||||
if (SHOULD_SHOW_CORNER_VIEW())
|
||||
{
|
||||
[_cornerView setFrame:[self _cornerViewFrame]];
|
||||
@@ -1276,6 +1420,37 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
[[self bottomCornerView] setBackgroundColor:[self currentValueForThemeAttribute:@"bottom-corner-color"]];
|
||||
}
|
||||
|
||||
// Position and redraw rulers to track viewport updates
|
||||
if (showHorizontalRuler)
|
||||
{
|
||||
[_horizontalRuler setFrame:CGRectMake(
|
||||
CGRectGetMinX(contentFrame),
|
||||
CGRectGetMinY(contentFrame) - horizRulerThickness,
|
||||
CGRectGetWidth(contentFrame),
|
||||
horizRulerThickness
|
||||
)];
|
||||
|
||||
if ([_horizontalRuler respondsToSelector:@selector(updateRuler)])
|
||||
[_horizontalRuler updateRuler];
|
||||
else
|
||||
[_horizontalRuler setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
if (showVerticalRuler)
|
||||
{
|
||||
[_verticalRuler setFrame:CGRectMake(
|
||||
CGRectGetMinX(contentFrame) - vertRulerThickness,
|
||||
CGRectGetMinY(contentFrame),
|
||||
vertRulerThickness,
|
||||
CGRectGetHeight(contentFrame)
|
||||
)];
|
||||
|
||||
if ([_verticalRuler respondsToSelector:@selector(updateRuler)])
|
||||
[_verticalRuler updateRuler];
|
||||
else
|
||||
[_verticalRuler setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
--_recursionCount;
|
||||
}
|
||||
|
||||
@@ -1438,8 +1613,8 @@ Notifies the delegate when the scroll view has finished scrolling.
|
||||
|
||||
y = maxY - 1.5;
|
||||
|
||||
CGContextMoveToPoint(context, maxX - 1.0, y);
|
||||
CGContextAddLineToPoint(context, minX + 2.0, y);
|
||||
CGContextMoveToPoint(maxX - 1.0, y);
|
||||
CGContextAddLineToPoint(minX + 2.0, y);
|
||||
|
||||
x = minX + 0.5;
|
||||
|
||||
@@ -1618,7 +1793,14 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
|
||||
CPScrollViewBottomCornerViewKey = @"CPScrollViewBottomCornerViewKey",
|
||||
CPScrollViewBorderTypeKey = @"CPScrollViewBorderTypeKey",
|
||||
CPScrollViewScrollerStyleKey = @"CPScrollViewScrollerStyleKey",
|
||||
CPScrollViewScrollerKnobStyleKey = @"CPScrollViewScrollerKnobStyleKey";
|
||||
CPScrollViewScrollerKnobStyleKey = @"CPScrollViewScrollerKnobStyleKey",
|
||||
|
||||
// Ruler Coding Keys
|
||||
CPScrollViewHasVRulerKey = @"CPScrollViewHasVRuler",
|
||||
CPScrollViewHasHRulerKey = @"CPScrollViewHasHRuler",
|
||||
CPScrollViewRulersVisibleKey = @"CPScrollViewRulersVisible",
|
||||
CPScrollViewVRulerKey = @"CPScrollViewVRuler",
|
||||
CPScrollViewHRulerKey = @"CPScrollViewHRuler";
|
||||
|
||||
@implementation CPScrollView (CPCoding)
|
||||
|
||||
@@ -1653,6 +1835,14 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
|
||||
_cornerView = [aCoder decodeObjectForKey:CPScrollViewCornerViewKey];
|
||||
_bottomCornerView = [aCoder decodeObjectForKey:CPScrollViewBottomCornerViewKey];
|
||||
|
||||
// Ruler decoding
|
||||
_hasVerticalRuler = [aCoder decodeBoolForKey:CPScrollViewHasVRulerKey];
|
||||
_hasHorizontalRuler = [aCoder decodeBoolForKey:CPScrollViewHasHRulerKey];
|
||||
_rulersVisible = [aCoder decodeBoolForKey:CPScrollViewRulersVisibleKey];
|
||||
|
||||
_verticalRuler = [aCoder decodeObjectForKey:CPScrollViewVRulerKey];
|
||||
_horizontalRuler = [aCoder decodeObjectForKey:CPScrollViewHRulerKey];
|
||||
|
||||
_delegate = nil;
|
||||
_scrollTimer = nil;
|
||||
_implementedDelegateMethods = 0;
|
||||
@@ -1706,6 +1896,14 @@ var CPScrollViewContentViewKey = @"CPScrollViewContentView",
|
||||
|
||||
[aCoder encodeInt:_scrollerStyle forKey:CPScrollViewScrollerStyleKey];
|
||||
[aCoder encodeInt:_scrollerKnobStyle forKey:CPScrollViewScrollerKnobStyleKey];
|
||||
|
||||
// Ruler encoding
|
||||
[aCoder encodeBool:_hasVerticalRuler forKey:CPScrollViewHasVRulerKey];
|
||||
[aCoder encodeBool:_hasHorizontalRuler forKey:CPScrollViewHasHRulerKey];
|
||||
[aCoder encodeBool:_rulersVisible forKey:CPScrollViewRulersVisibleKey];
|
||||
|
||||
[aCoder encodeObject:_verticalRuler forKey:CPScrollViewVRulerKey];
|
||||
[aCoder encodeObject:_horizontalRuler forKey:CPScrollViewHRulerKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
+78
-6
@@ -296,6 +296,8 @@ CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
|
||||
_CPTableDrawView _tableDrawView;
|
||||
|
||||
SEL _doubleAction;
|
||||
id _doubleClickTarget @accessors(property=doubleClickTarget);
|
||||
id _doubleClickArgument @accessors(property=doubleClickArgument);
|
||||
CPInteger _clickedRow;
|
||||
CPInteger _clickedColumn;
|
||||
unsigned _columnAutoResizingStyle;
|
||||
@@ -3486,13 +3488,73 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
[removeIndexes addIndex:columnIdx];
|
||||
}
|
||||
|
||||
var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
|
||||
[self _unloadDataViewsInRows:rowIndexes columns:removeIndexes];
|
||||
if ([removeIndexes count] > 0)
|
||||
{
|
||||
var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
|
||||
[self _unloadDataViewsInRows:rowIndexes columns:removeIndexes];
|
||||
|
||||
[_tableColumns removeObjectsAtIndexes:removeIndexes];
|
||||
[_tableColumns removeObjectsAtIndexes:removeIndexes];
|
||||
|
||||
_dirtyTableColumnRangeIndex = 0;
|
||||
[self _recalculateTableColumnRanges];
|
||||
_dirtyTableColumnRangeIndex = 0;
|
||||
[self _recalculateTableColumnRanges];
|
||||
|
||||
// Shift cached index sets downwards to account for the removed columns
|
||||
var shiftIndexSet = function(indexSet)
|
||||
{
|
||||
var newSet = [CPIndexSet indexSet];
|
||||
[indexSet enumerateIndexesUsingBlock:function(idx, stop)
|
||||
{
|
||||
if (![removeIndexes containsIndex:idx])
|
||||
{
|
||||
var shift = 0,
|
||||
remIdx = [removeIndexes firstIndex];
|
||||
|
||||
while (remIdx !== CPNotFound && remIdx < idx)
|
||||
{
|
||||
shift++;
|
||||
remIdx = [removeIndexes indexGreaterThanIndex:remIdx];
|
||||
}
|
||||
|
||||
[newSet addIndex:idx - shift];
|
||||
}
|
||||
}];
|
||||
return newSet;
|
||||
};
|
||||
|
||||
_exposedColumns = shiftIndexSet(_exposedColumns);
|
||||
_selectedColumnIndexes = shiftIndexSet(_selectedColumnIndexes);
|
||||
|
||||
// Shift individual index variables
|
||||
var shiftIndex = function(idx)
|
||||
{
|
||||
if (idx === CPNotFound || idx === -1)
|
||||
return idx;
|
||||
|
||||
if ([removeIndexes containsIndex:idx])
|
||||
return CPNotFound;
|
||||
|
||||
var shift = 0,
|
||||
remIdx = [removeIndexes firstIndex];
|
||||
|
||||
while (remIdx !== CPNotFound && remIdx < idx)
|
||||
{
|
||||
shift++;
|
||||
remIdx = [removeIndexes indexGreaterThanIndex:remIdx];
|
||||
}
|
||||
|
||||
return idx - shift;
|
||||
};
|
||||
|
||||
_editingColumn = shiftIndex(_editingColumn);
|
||||
|
||||
_draggedColumnIndex = shiftIndex(_draggedColumnIndex);
|
||||
if (_draggedColumnIndex === CPNotFound)
|
||||
_draggedColumnIndex = -1;
|
||||
|
||||
_clickedColumn = shiftIndex(_clickedColumn);
|
||||
if (_clickedColumn === CPNotFound)
|
||||
_clickedColumn = -1;
|
||||
}
|
||||
|
||||
[_differedColumnDataToRemove removeAllObjects];
|
||||
_needsDifferedTableColumnRemove = NO;
|
||||
@@ -4734,7 +4796,12 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
|
||||
//double click actions
|
||||
if ([[CPApp currentEvent] clickCount] === 2 && _doubleAction)
|
||||
[self sendAction:_doubleAction to:_target];
|
||||
{
|
||||
var target = _doubleClickTarget || _target,
|
||||
argument = [self infoForBinding:@"doubleClickArgument"] ? _doubleClickArgument : self;
|
||||
|
||||
[CPApp sendAction:_doubleAction to:target from:argument];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -6098,6 +6165,11 @@ Your delegate can implement this method to avoid subclassing the tableview to ad
|
||||
{
|
||||
if (aBinding == @"content")
|
||||
_contentBindingExplicitlySet = YES;
|
||||
else if (aBinding == @"doubleClickTarget")
|
||||
{
|
||||
if ([options objectForKey:CPSelectorNameBindingOption])
|
||||
[self setDoubleAction:CPSelectorFromString([options objectForKey:CPSelectorNameBindingOption])];
|
||||
}
|
||||
|
||||
[super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
|
||||
}
|
||||
|
||||
@@ -1154,6 +1154,12 @@ CPTextFieldStatePlaceholder = CPThemeState("placeholder");
|
||||
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
|
||||
return;
|
||||
|
||||
if ([self isEditable] && !_isEditing)
|
||||
{
|
||||
_isEditing = YES;
|
||||
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
// CPTextField uses an HTML input element to take the input so we need to
|
||||
// propagate the dom event so the element is updated. This has to be done
|
||||
// before interpretKeyEvents: though so individual commands have a chance
|
||||
|
||||
@@ -286,6 +286,8 @@ _oncontextmenuhandler = function () { return false; };
|
||||
|
||||
if (removeRange.length)
|
||||
_removeInvalidLineFragmentsRange = CPMakeRangeCopy(removeRange);
|
||||
else
|
||||
_removeInvalidLineFragmentsRange = nil;
|
||||
|
||||
// We erased all lines
|
||||
if (!startIndex)
|
||||
@@ -303,13 +305,17 @@ _oncontextmenuhandler = function () { return false; };
|
||||
|
||||
- (BOOL)_rescuingInvalidFragmentsWasPossibleForGlyphRange:(CPRange)aRange
|
||||
{
|
||||
var l = _lineFragments.length,
|
||||
location = aRange.location,
|
||||
found = NO,
|
||||
targetLine = 0;
|
||||
// 1. EARLY EXIT: If there are no fragments to rescue (e.g. setting new text), do nothing.
|
||||
if (!_lineFragmentsForRescue || _lineFragmentsForRescue.length === 0)
|
||||
return NO;
|
||||
|
||||
// try to find the first linefragment of the desired range
|
||||
for (; targetLine < l; targetLine++)
|
||||
var l = _lineFragments.length,
|
||||
location = aRange.location,
|
||||
found = NO,
|
||||
targetLine = l - 1; // Start from the END of the array
|
||||
|
||||
// 2. REVERSE SEARCH: The fragment we want is almost always at the end.
|
||||
for (; targetLine >= 0; targetLine--)
|
||||
{
|
||||
if (CPLocationInRange(location, _lineFragments[targetLine]._range))
|
||||
{
|
||||
@@ -332,9 +338,6 @@ _oncontextmenuhandler = function () { return false; };
|
||||
newLength = [[_textStorage string].length],
|
||||
removalSkip = 1;
|
||||
|
||||
// if (ABS(newLength - oldLength) > 1)
|
||||
// return NO;
|
||||
|
||||
if (![oldLineFragment isVisuallyIdenticalToFragment:newLineFragment])
|
||||
{
|
||||
isIdentical = NO;
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
* CPParagraphStyle.j
|
||||
* AppKit
|
||||
*
|
||||
* FIXME
|
||||
* This is basically a stub.
|
||||
* We need to store all the spacing informations as well as writing direction (among others)
|
||||
*
|
||||
* Created by Daniel Boehringer on 11/01/2014
|
||||
* Copyright Daniel Boehringer 2014.
|
||||
*
|
||||
@@ -26,198 +22,365 @@
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import <Foundation/CPArray.j>
|
||||
|
||||
@import "CPText.j"
|
||||
|
||||
CPLeftTabStopType = 0;
|
||||
@import <Foundation/CPDictionary.j>
|
||||
|
||||
CPParagraphStyleAttributeName = @"CPParagraphStyleAttributeName";
|
||||
|
||||
var _sharedDefaultParagraphStyle,
|
||||
_defaultTabStopArray;
|
||||
// Define missing global tab stop type constants
|
||||
CPLeftTabStopType = 0;
|
||||
CPRightTabStopType = 1;
|
||||
CPCenterTabStopType = 2;
|
||||
CPDecimalTabStopType = 3;
|
||||
|
||||
@implementation CPParagraphStyle : CPObject
|
||||
// Standard Tab Interval (28pts is roughly 4 spaces in standard fonts)
|
||||
var kDefaultTabInterval = 28.0;
|
||||
|
||||
// MARK: - CPTextTab Implementation
|
||||
|
||||
@implementation CPTextTab : CPObject
|
||||
{
|
||||
CPArray _tabStops @accessors(property=tabStops);
|
||||
CPTextAlignment _alignment @accessors(property=alignment);
|
||||
unsigned _firstLineHeadIndent @accessors(property=firstLineHeadIndent);
|
||||
unsigned _headIndent @accessors(property=headIndent);
|
||||
unsigned _tailIndent @accessors(property=tailIndent);
|
||||
unsigned _paragraphSpacing @accessors(property=paragraphSpacing);
|
||||
unsigned _minimumLineHeight @accessors(property=minimumLineHeight);
|
||||
unsigned _maximumLineHeight @accessors(property=maximumLineHeight);
|
||||
unsigned _lineSpacing @accessors(property=lineSpacing);
|
||||
CPTextAlignment _alignment @accessors(readonly, property=alignment);
|
||||
float _location @accessors(readonly, property=location);
|
||||
CPDictionary _options @accessors(readonly, property=options);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Class methods
|
||||
|
||||
+ (CPParagraphStyle)defaultParagraphStyle
|
||||
- (id)initWithTextAlignment:(CPTextAlignment)anAlignment location:(float)aLocation options:(CPDictionary)options
|
||||
{
|
||||
if (!_sharedDefaultParagraphStyle)
|
||||
_sharedDefaultParagraphStyle = [self new];
|
||||
|
||||
return _sharedDefaultParagraphStyle;
|
||||
}
|
||||
|
||||
+ (CPArray)_defaultTabStops
|
||||
{
|
||||
if (!_defaultTabStopArray)
|
||||
if (self = [super init])
|
||||
{
|
||||
var i;
|
||||
_defaultTabStopArray = [];
|
||||
|
||||
// <!> FIXME: Define constants for these magic numbers: 13, 28
|
||||
for (i = 1; i < 16 ; i++)
|
||||
{
|
||||
_defaultTabStopArray.push([[CPTextTab alloc] initWithType:CPLeftTabStopType location:i * 28]);
|
||||
}
|
||||
_alignment = anAlignment;
|
||||
_location = aLocation;
|
||||
_options = [options copy];
|
||||
}
|
||||
|
||||
return _defaultTabStopArray;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Init methods
|
||||
|
||||
- (id)init
|
||||
{
|
||||
[self _initWithDefaults];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPParagraphStyle)initWithParagraphStyle:(CPParagraphStyle)other
|
||||
// Convenience initializer matching AppKit behavior
|
||||
- (id)initWithType:(CPTabStopType)aType location:(float)aLocation
|
||||
{
|
||||
self = [super init];
|
||||
|
||||
_tabStops = [other._tabStops copy];
|
||||
_alignment = other._alignment;
|
||||
_firstLineHeadIndent = other._firstLineHeadIndent;
|
||||
_headIndent = other._headIndent;
|
||||
_tailIndent = other._tailIndent;
|
||||
_paragraphSpacing = other._paragraphSpacing;
|
||||
_minimumLineHeight = other._minimumLineHeight;
|
||||
_maximumLineHeight = other._maximumLineHeight;
|
||||
_lineSpacing = other._lineSpacing;
|
||||
|
||||
return self;
|
||||
// Map old TabStopType to TextAlignment for modern compatibility
|
||||
return [self initWithTextAlignment:aType location:aLocation options:nil];
|
||||
}
|
||||
|
||||
- (void)_initWithDefaults
|
||||
// Added to resolve the unrecognized selector exception in the RTF producer
|
||||
- (CPTabStopType)tabStopType
|
||||
{
|
||||
_alignment = CPLeftTextAlignment;
|
||||
_tabStops = [[[self class] _defaultTabStops] copy];
|
||||
return _alignment;
|
||||
}
|
||||
|
||||
- (void)addTabStop:(CPTextTab)aStop
|
||||
- (BOOL)isEqual:(id)other
|
||||
{
|
||||
_tabStops.push(aStop);
|
||||
if (self === other) return YES;
|
||||
if (![other isKindOfClass:[CPTextTab class]]) return NO;
|
||||
|
||||
return _location === [other location] &&
|
||||
_alignment === [other alignment] &&
|
||||
((_options == nil && [other options] == nil) || [_options isEqualToDictionary:[other options]]);
|
||||
}
|
||||
|
||||
- (id)copy
|
||||
{
|
||||
var other = [[self class] alloc];
|
||||
|
||||
return [other initWithParagraphStyle:self];
|
||||
return [[CPTextTab alloc] initWithTextAlignment:_alignment location:_location options:_options];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPParagraphStyleTabStopsKey = @"CPParagraphStyleTabStopsKey",
|
||||
CPParagraphStyleAlignmentKey = @"CPParagraphStyleAlignmentKey",
|
||||
CPParagraphStyleFirstLineHeadIndentKey = @"CPParagraphStyleFirstLineHeadIndentKey",
|
||||
CPParagraphStyleHeadIndentKey = @"CPParagraphStyleHeadIndentKey",
|
||||
CPParagraphStyleTailIndentKey = @"CPParagraphStyleTailIndentKey",
|
||||
CPParagraphStyleParagraphSpacingKey = @"CPParagraphStyleParagraphSpacingKey",
|
||||
CPParagraphStyleMinimumLineHeightKey = @"CPParagraphStyleMinimumLineHeightKey",
|
||||
CPParagraphStyleMaximumLineHeightKey = @"CPParagraphStyleMaximumLineHeightKey",
|
||||
CPParagraphStyleLineSpacingKey = @"CPParagraphStyleLineSpacingKey";
|
||||
|
||||
@implementation CPParagraphStyle (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(id)aCoder
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
if (self = [super init])
|
||||
{
|
||||
_tabStops = [aCoder decodeObjectForKey:"CPParagraphStyleTabStopsKey"];
|
||||
_alignment = [aCoder decodeIntForKey:"CPParagraphStyleAlignmentKey"];
|
||||
_firstLineHeadIndent = [aCoder decodeIntForKey:"CPParagraphStyleFirstLineHeadIndentKey"];
|
||||
_headIndent = [aCoder decodeIntForKey:"CPParagraphStyleHeadIndentKey"];
|
||||
_tailIndent = [aCoder decodeIntForKey:"CPParagraphStyleTailIndentKey"];
|
||||
_paragraphSpacing = [aCoder decodeIntForKey:"CPParagraphStyleParagraphSpacingKey"];
|
||||
_minimumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMinimumLineHeightKey"];
|
||||
_maximumLineHeight = [aCoder decodeIntForKey:"CPParagraphStyleMaximumLineHeightKey"];
|
||||
_lineSpacing = [aCoder decodeIntForKey:"CPParagraphStyleLineSpacingKey"];
|
||||
_alignment = [aCoder decodeIntForKey:@"CPTextTabAlignment"];
|
||||
_location = [aCoder decodeFloatForKey:@"CPTextTabLocation"];
|
||||
_options = [aCoder decodeObjectForKey:@"CPTextTabOptions"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(id)aCoder
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeInt:_alignment forKey:"CPParagraphStyleAlignmentKey"];
|
||||
[aCoder encodeObject:_tabStops forKey:"CPParagraphStyleTabStopsKey"];
|
||||
[aCoder encodeInt:_firstLineHeadIndent forKey:"CPParagraphStyleFirstLineHeadIndentKey"];
|
||||
[aCoder encodeInt:_headIndent forKey:"CPParagraphStyleHeadIndentKey"];
|
||||
[aCoder encodeInt:_tailIndent forKey:"CPParagraphStyleTailIndentKey"];
|
||||
[aCoder encodeInt:_paragraphSpacing forKey:"CPParagraphStyleParagraphSpacingKey"];
|
||||
[aCoder encodeInt:_minimumLineHeight forKey:"CPParagraphStyleMinimumLineHeightKey"];
|
||||
[aCoder encodeInt:_maximumLineHeight forKey:"CPParagraphStyleMaximumLineHeightKey"];
|
||||
[aCoder encodeInt:_lineSpacing forKey:"CPParagraphStyleLineSpacingKey"];
|
||||
[aCoder encodeInt:_alignment forKey:@"CPTextTabAlignment"];
|
||||
[aCoder encodeFloat:_location forKey:@"CPTextTabLocation"];
|
||||
[aCoder encodeObject:_options forKey:@"CPTextTabOptions"];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CPTextTab : CPObject
|
||||
// MARK: - CPParagraphStyle Implementation
|
||||
|
||||
var _sharedDefaultParagraphStyle = nil;
|
||||
|
||||
@implementation CPParagraphStyle : CPObject
|
||||
{
|
||||
int _type @accessors(property = tabStopType);
|
||||
double _location @accessors(property = location);
|
||||
float _lineSpacing @accessors(readonly, property=lineSpacing);
|
||||
float _paragraphSpacing @accessors(readonly, property=paragraphSpacing);
|
||||
CPTextAlignment _alignment @accessors(readonly, property=alignment);
|
||||
float _headIndent @accessors(readonly, property=headIndent);
|
||||
float _tailIndent @accessors(readonly, property=tailIndent);
|
||||
float _firstLineHeadIndent @accessors(readonly, property=firstLineHeadIndent);
|
||||
float _minimumLineHeight @accessors(readonly, property=minimumLineHeight);
|
||||
float _maximumLineHeight @accessors(readonly, property=maximumLineHeight);
|
||||
CPLineBreakMode _lineBreakMode @accessors(readonly, property=lineBreakMode);
|
||||
CPWritingDirection _baseWritingDirection @accessors(readonly, property=baseWritingDirection);
|
||||
float _lineHeightMultiple @accessors(readonly, property=lineHeightMultiple);
|
||||
float _paragraphSpacingBefore @accessors(readonly, property=paragraphSpacingBefore);
|
||||
float _defaultTabInterval @accessors(readonly, property=defaultTabInterval);
|
||||
CPArray _tabStops @accessors(readonly, property=tabStops);
|
||||
}
|
||||
|
||||
- (id)initWithType:(CPTabStopType) aType location:(double) aLocation
|
||||
+ (CPParagraphStyle)defaultParagraphStyle
|
||||
{
|
||||
if ([self = [super init]])
|
||||
if (!_sharedDefaultParagraphStyle)
|
||||
{
|
||||
_type = aType;
|
||||
_location = aLocation;
|
||||
_sharedDefaultParagraphStyle = [[CPParagraphStyle alloc] init];
|
||||
// Ensure defaults are set on the shared instance internal vars
|
||||
// Since it's immutable, we rely on the init to set these.
|
||||
}
|
||||
return _sharedDefaultParagraphStyle;
|
||||
}
|
||||
|
||||
+ (CPWritingDirection)defaultWritingDirectionForLanguage:(CPString)languageName
|
||||
{
|
||||
// Simplified: Cappuccino usually assumes LTR unless specified otherwise.
|
||||
return CPWritingDirectionLeftToRight;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_lineSpacing = 0.0;
|
||||
_paragraphSpacing = 0.0;
|
||||
_alignment = CPLeftTextAlignment;
|
||||
_headIndent = 0.0;
|
||||
_tailIndent = 0.0;
|
||||
_firstLineHeadIndent = 0.0;
|
||||
_minimumLineHeight = 0.0;
|
||||
_maximumLineHeight = 0.0;
|
||||
_lineBreakMode = CPLineBreakByWordWrapping;
|
||||
_baseWritingDirection = CPWritingDirectionNatural;
|
||||
_lineHeightMultiple = 0.0;
|
||||
_paragraphSpacingBefore = 0.0;
|
||||
_defaultTabInterval = kDefaultTabInterval;
|
||||
|
||||
// Generate default tab stops
|
||||
_tabStops = [];
|
||||
for (var i = 1; i <= 12; i++)
|
||||
{
|
||||
[_tabStops addObject:[[CPTextTab alloc] initWithType:CPLeftTextAlignment
|
||||
location:i * kDefaultTabInterval]];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
var CPTextTabTypeKey = @"CPTextTabTypeKey",
|
||||
CPTextTabLocationKey = @"CPTextTabLocationKey";
|
||||
|
||||
@implementation CPTextTab (CPCoding)
|
||||
|
||||
- (id)initWithCoder:(id)aCoder
|
||||
- (id)initWithParagraphStyle:(CPParagraphStyle)other
|
||||
{
|
||||
self = [self init];
|
||||
|
||||
if (self)
|
||||
if (self = [super init])
|
||||
{
|
||||
_type = [aCoder decodeIntForKey:"CPTextTabTypeKey"];
|
||||
_location = [aCoder decodeDoubleForKey:"CPTextTabLocationKey"];
|
||||
_lineSpacing = [other lineSpacing];
|
||||
_paragraphSpacing = [other paragraphSpacing];
|
||||
_alignment = [other alignment];
|
||||
_headIndent = [other headIndent];
|
||||
_tailIndent = [other tailIndent];
|
||||
_firstLineHeadIndent = [other firstLineHeadIndent];
|
||||
_minimumLineHeight = [other minimumLineHeight];
|
||||
_maximumLineHeight = [other maximumLineHeight];
|
||||
_lineBreakMode = [other lineBreakMode];
|
||||
_baseWritingDirection = [other baseWritingDirection];
|
||||
_lineHeightMultiple = [other lineHeightMultiple];
|
||||
_paragraphSpacingBefore = [other paragraphSpacingBefore];
|
||||
_defaultTabInterval = [other defaultTabInterval];
|
||||
_tabStops = [[other tabStops] copy];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(id)aCoder
|
||||
- (id)copy
|
||||
{
|
||||
[aCoder encodeInt:_type forKey:"CPTextTabTypeKey"];
|
||||
[aCoder encodeDouble:_location forKey:"CPTextTabLocationKey"];
|
||||
// Since this class is immutable, return self.
|
||||
// Subclasses (Mutable) will override.
|
||||
if ([self class] === [CPParagraphStyle class])
|
||||
return self;
|
||||
|
||||
return [[CPParagraphStyle alloc] initWithParagraphStyle:self];
|
||||
}
|
||||
|
||||
- (id)mutableCopy
|
||||
{
|
||||
return [[CPMutableParagraphStyle alloc] initWithParagraphStyle:self];
|
||||
}
|
||||
|
||||
// MARK: - Coding Support
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super init])
|
||||
{
|
||||
_lineSpacing = [aCoder decodeFloatForKey:@"CPParagraphStyleLineSpacing"];
|
||||
_paragraphSpacing = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacing"];
|
||||
_alignment = [aCoder decodeIntForKey:@"CPParagraphStyleAlignment"];
|
||||
_headIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleHeadIndent"];
|
||||
_tailIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleTailIndent"];
|
||||
_firstLineHeadIndent = [aCoder decodeFloatForKey:@"CPParagraphStyleFirstLineHeadIndent"];
|
||||
_minimumLineHeight = [aCoder decodeFloatForKey:@"CPParagraphStyleMinimumLineHeight"];
|
||||
_maximumLineHeight = [aCoder decodeFloatForKey:@"CPParagraphStyleMaximumLineHeight"];
|
||||
_lineBreakMode = [aCoder decodeIntForKey:@"CPParagraphStyleLineBreakMode"];
|
||||
_baseWritingDirection = [aCoder decodeIntForKey:@"CPParagraphStyleBaseWritingDirection"];
|
||||
_lineHeightMultiple = [aCoder decodeFloatForKey:@"CPParagraphStyleLineHeightMultiple"];
|
||||
_paragraphSpacingBefore = [aCoder decodeFloatForKey:@"CPParagraphStyleParagraphSpacingBefore"];
|
||||
_defaultTabInterval = [aCoder decodeFloatForKey:@"CPParagraphStyleDefaultTabInterval"];
|
||||
_tabStops = [aCoder decodeObjectForKey:@"CPParagraphStyleTabStops"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
[aCoder encodeFloat:_lineSpacing forKey:@"CPParagraphStyleLineSpacing"];
|
||||
[aCoder encodeFloat:_paragraphSpacing forKey:@"CPParagraphStyleParagraphSpacing"];
|
||||
[aCoder encodeInt:_alignment forKey:@"CPParagraphStyleAlignment"];
|
||||
[aCoder encodeFloat:_headIndent forKey:@"CPParagraphStyleHeadIndent"];
|
||||
[aCoder encodeFloat:_tailIndent forKey:@"CPParagraphStyleTailIndent"];
|
||||
[aCoder encodeFloat:_firstLineHeadIndent forKey:@"CPParagraphStyleFirstLineHeadIndent"];
|
||||
[aCoder encodeFloat:_minimumLineHeight forKey:@"CPParagraphStyleMinimumLineHeight"];
|
||||
[aCoder encodeFloat:_maximumLineHeight forKey:@"CPParagraphStyleMaximumLineHeight"];
|
||||
[aCoder encodeInt:_lineBreakMode forKey:@"CPParagraphStyleLineBreakMode"];
|
||||
[aCoder encodeInt:_baseWritingDirection forKey:@"CPParagraphStyleBaseWritingDirection"];
|
||||
[aCoder encodeFloat:_lineHeightMultiple forKey:@"CPParagraphStyleLineHeightMultiple"];
|
||||
[aCoder encodeFloat:_paragraphSpacingBefore forKey:@"CPParagraphStyleParagraphSpacingBefore"];
|
||||
[aCoder encodeFloat:_defaultTabInterval forKey:@"CPParagraphStyleDefaultTabInterval"];
|
||||
[aCoder encodeObject:_tabStops forKey:@"CPParagraphStyleTabStops"];
|
||||
}
|
||||
|
||||
// MARK: - Equality
|
||||
|
||||
- (BOOL)isEqual:(id)other
|
||||
{
|
||||
if (self === other) return YES;
|
||||
if (![other isKindOfClass:[CPParagraphStyle class]]) return NO;
|
||||
|
||||
return _lineSpacing === [other lineSpacing] &&
|
||||
_paragraphSpacing === [other paragraphSpacing] &&
|
||||
_alignment === [other alignment] &&
|
||||
_headIndent === [other headIndent] &&
|
||||
_tailIndent === [other tailIndent] &&
|
||||
_firstLineHeadIndent === [other firstLineHeadIndent] &&
|
||||
_lineBreakMode === [other lineBreakMode] &&
|
||||
[_tabStops isEqualToArray:[other tabStops]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// MARK: - CPMutableParagraphStyle Implementation
|
||||
|
||||
@implementation CPMutableParagraphStyle : CPParagraphStyle
|
||||
{
|
||||
}
|
||||
|
||||
- (id)initWithParagraphStyle:(CPParagraphStyle)other
|
||||
{
|
||||
if (self = [super initWithParagraphStyle:other])
|
||||
{
|
||||
// Ensure our tab stops array is mutable in the mutable subclass
|
||||
_tabStops = [[other tabStops] mutableCopy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(CPCoder)aCoder
|
||||
{
|
||||
if (self = [super initWithCoder:aCoder])
|
||||
{
|
||||
_tabStops = [_tabStops mutableCopy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setLineSpacing:(float)aLineSpacing
|
||||
{
|
||||
_lineSpacing = aLineSpacing;
|
||||
}
|
||||
|
||||
- (void)setParagraphSpacing:(float)aParagraphSpacing
|
||||
{
|
||||
_paragraphSpacing = aParagraphSpacing;
|
||||
}
|
||||
|
||||
- (void)setAlignment:(CPTextAlignment)anAlignment
|
||||
{
|
||||
_alignment = anAlignment;
|
||||
}
|
||||
|
||||
- (void)setHeadIndent:(float)aHeadIndent
|
||||
{
|
||||
_headIndent = aHeadIndent;
|
||||
}
|
||||
|
||||
- (void)setTailIndent:(float)aTailIndent
|
||||
{
|
||||
_tailIndent = aTailIndent;
|
||||
}
|
||||
|
||||
- (void)setFirstLineHeadIndent:(float)aFirstLineHeadIndent
|
||||
{
|
||||
_firstLineHeadIndent = aFirstLineHeadIndent;
|
||||
}
|
||||
|
||||
- (void)setMinimumLineHeight:(float)aMinimumLineHeight
|
||||
{
|
||||
_minimumLineHeight = aMinimumLineHeight;
|
||||
}
|
||||
|
||||
- (void)setMaximumLineHeight:(float)aMaximumLineHeight
|
||||
{
|
||||
_maximumLineHeight = aMaximumLineHeight;
|
||||
}
|
||||
|
||||
- (void)setLineBreakMode:(CPLineBreakMode)aLineBreakMode
|
||||
{
|
||||
_lineBreakMode = aLineBreakMode;
|
||||
}
|
||||
|
||||
- (void)setBaseWritingDirection:(CPWritingDirection)aBaseWritingDirection
|
||||
{
|
||||
_baseWritingDirection = aBaseWritingDirection;
|
||||
}
|
||||
|
||||
- (void)setLineHeightMultiple:(float)aLineHeightMultiple
|
||||
{
|
||||
_lineHeightMultiple = aLineHeightMultiple;
|
||||
}
|
||||
|
||||
- (void)setParagraphSpacingBefore:(float)aParagraphSpacingBefore
|
||||
{
|
||||
_paragraphSpacingBefore = aParagraphSpacingBefore;
|
||||
}
|
||||
|
||||
- (void)setDefaultTabInterval:(float)aDefaultTabInterval
|
||||
{
|
||||
_defaultTabInterval = aDefaultTabInterval;
|
||||
}
|
||||
|
||||
- (void)addTabStop:(CPTextTab)aTabStop
|
||||
{
|
||||
[_tabStops addObject:aTabStop];
|
||||
}
|
||||
|
||||
- (void)removeTabStop:(CPTextTab)aTabStop
|
||||
{
|
||||
[_tabStops removeObject:aTabStop];
|
||||
}
|
||||
|
||||
- (void)setTabStops:(CPArray)newTabStops
|
||||
{
|
||||
if (_tabStops === newTabStops) return;
|
||||
_tabStops = [newTabStops mutableCopy];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(CPZone)aZone
|
||||
{
|
||||
// Return an immutable copy
|
||||
return [[CPParagraphStyle alloc] initWithParagraphStyle:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,566 @@
|
||||
/*
|
||||
* CPRulerView.j
|
||||
* AppKit
|
||||
*
|
||||
* Created by Daniel Boehringer on 11/01/2014
|
||||
* Copyright Daniel Boehringer 2014.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
@import <Foundation/CPObject.j>
|
||||
@import "CPView.j"
|
||||
@import "CPTextField.j"
|
||||
@import "CPColor.j"
|
||||
@import "CPFont.j"
|
||||
@import "CPMenu.j"
|
||||
@import "CPMenuItem.j"
|
||||
|
||||
// Orientations matching AppKit standards
|
||||
// typedef enum CPRulerOrientation
|
||||
CPHorizontalRuler = 0,
|
||||
CPVerticalRuler = 1,
|
||||
CPRulerOrientationHorizontal = 0,
|
||||
CPRulerOrientationVertical = 1
|
||||
|
||||
@class CPRulerView;
|
||||
|
||||
|
||||
// MARK: - CPRulerMarker (Interactive Handles with Dynamic Alignment Icons)
|
||||
|
||||
@implementation CPRulerMarker : CPView
|
||||
{
|
||||
CPRulerView _rulerView @accessors(property=rulerView);
|
||||
float _imageValue @accessors(property=imageValue);
|
||||
id _representedObject @accessors(property=representedObject);
|
||||
CPTextField _label;
|
||||
}
|
||||
|
||||
- (id)initWithRulerView:(CPRulerView)aRulerView markerLocation:(float)aLocation imageValue:(float)anImageValue representedObject:(id)anObject
|
||||
{
|
||||
if (self = [super initWithFrame:CGRectMake(0, 0, 12, 12)])
|
||||
{
|
||||
_rulerView = aRulerView;
|
||||
_imageValue = anImageValue;
|
||||
_representedObject = anObject;
|
||||
|
||||
_label = [[CPTextField alloc] initWithFrame:CGRectMake(0, 0, 12, 12)];
|
||||
[_label setFont:[CPFont systemFontOfSize:10.0]];
|
||||
[_label setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
|
||||
[_label setAlignment:CPCenterTextAlignment];
|
||||
[self addSubview:_label];
|
||||
|
||||
[self updateMarkerIcon];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (CPTextField)label
|
||||
{
|
||||
return _label;
|
||||
}
|
||||
|
||||
- (void)setRepresentedObject:(id)anObject
|
||||
{
|
||||
_representedObject = anObject;
|
||||
[self updateMarkerIcon];
|
||||
}
|
||||
|
||||
// Dynamically sets the Unicode triangle direction based on the alignment or indent type
|
||||
// Dynamically sets the Unicode arrow direction/type based on the alignment or indent type
|
||||
- (void)updateMarkerIcon
|
||||
{
|
||||
if ([_representedObject isKindOfClass:[CPTextTab class]])
|
||||
{
|
||||
var align = [_representedObject alignment];
|
||||
if (align === CPLeftTextAlignment)
|
||||
[_label setStringValue:@"▶"]; // Left-aligned points Right
|
||||
else if (align === CPCenterTextAlignment)
|
||||
[_label setStringValue:@"▼"]; // Center-aligned points Down
|
||||
else if (align === CPRightTextAlignment)
|
||||
[_label setStringValue:@"◀"]; // Right-aligned points Left
|
||||
}
|
||||
else if ([_representedObject isKindOfClass:[CPString class]])
|
||||
{
|
||||
if (_representedObject === @"CPFirstLineIndent")
|
||||
[_label setStringValue:@"⥔"]; // Dotted shaft arrow pointing down for first-line indent
|
||||
else if (_representedObject === @"CPHeadIndent")
|
||||
[_label setStringValue:@"⥜"]; // Solid shaft arrow pointing down for following-lines (head) indent
|
||||
else if (_representedObject === @"CPTailIndent")
|
||||
[_label setStringValue:@"⥘"]; // Solid downward triangle for tail indent
|
||||
else
|
||||
[_label setStringValue:@"⇡"]; // Fallback standard up marker
|
||||
}
|
||||
else
|
||||
{
|
||||
[_label setStringValue:@"⇡"]; // Fallback standard up marker
|
||||
}
|
||||
}
|
||||
#pragma mark -
|
||||
#pragma mark Context Menu Support
|
||||
|
||||
- (CPMenu)menuForEvent:(CPEvent)anEvent
|
||||
{
|
||||
var menu = [[CPMenu alloc] initWithTitle:@"Marker Context Menu"];
|
||||
|
||||
// If the marker represents a standard tab stop, allow changing its type
|
||||
if ([_representedObject isKindOfClass:[CPTextTab class]])
|
||||
{
|
||||
var itemLeft = [menu addItemWithTitle:@"Left Tab Stop" action:@selector(changeTypeToLeft:) keyEquivalent:@""],
|
||||
itemCenter = [menu addItemWithTitle:@"Center Tab Stop" action:@selector(changeTypeToCenter:) keyEquivalent:@""],
|
||||
itemRight = [menu addItemWithTitle:@"Right Tab Stop" action:@selector(changeTypeToRight:) keyEquivalent:@""];
|
||||
|
||||
[itemLeft setTarget:self];
|
||||
[itemCenter setTarget:self];
|
||||
[itemRight setTarget:self];
|
||||
|
||||
var align = [_representedObject alignment];
|
||||
if (align === CPLeftTextAlignment) [itemLeft setState:CPOnState];
|
||||
else if (align === CPCenterTextAlignment) [itemCenter setState:CPOnState];
|
||||
else if (align === CPRightTextAlignment) [itemRight setState:CPOnState];
|
||||
|
||||
[menu addItem:[CPMenuItem separatorItem]];
|
||||
}
|
||||
|
||||
// Determine the context-specific delete title
|
||||
var deleteTitle = @"Delete Tab Stop";
|
||||
if ([_representedObject isKindOfClass:[CPString class]])
|
||||
{
|
||||
if (_representedObject === @"CPFirstLineIndent")
|
||||
deleteTitle = @"Delete 1st line indentation marker";
|
||||
else if (_representedObject === @"CPHeadIndent")
|
||||
deleteTitle = @"Delete head indentation marker";
|
||||
else if (_representedObject === @"CPTailIndent")
|
||||
deleteTitle = @"Delete tail indentation marker";
|
||||
}
|
||||
|
||||
var itemDelete = [menu addItemWithTitle:deleteTitle action:@selector(deleteMarker:) keyEquivalent:@""];
|
||||
[itemDelete setTarget:self];
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (void)changeTypeToLeft:(id)sender
|
||||
{
|
||||
[self _changeAlignment:CPLeftTextAlignment];
|
||||
}
|
||||
|
||||
- (void)changeTypeToCenter:(id)sender
|
||||
{
|
||||
[self _changeAlignment:CPCenterTextAlignment];
|
||||
}
|
||||
|
||||
- (void)changeTypeToRight:(id)sender
|
||||
{
|
||||
[self _changeAlignment:CPRightTextAlignment];
|
||||
}
|
||||
|
||||
- (void)_changeAlignment:(CPTextAlignment)alignment
|
||||
{
|
||||
if (![_representedObject isKindOfClass:[CPTextTab class]])
|
||||
return;
|
||||
|
||||
var oldTab = _representedObject;
|
||||
var newTab = [[CPTextTab alloc] initWithType:alignment location:_imageValue];
|
||||
|
||||
// Using setRepresentedObject: automatically updates the marker triangle direction
|
||||
[self setRepresentedObject:newTab];
|
||||
|
||||
var client = [_rulerView clientView];
|
||||
if (client && [client respondsToSelector:@selector(rulerView:didUpdateMarker:oldTab:)])
|
||||
{
|
||||
[client rulerView:_rulerView didUpdateMarker:self oldTab:oldTab];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deleteMarker:(id)sender
|
||||
{
|
||||
var client = [_rulerView clientView];
|
||||
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
|
||||
{
|
||||
[client rulerView:_rulerView didRemoveMarker:self];
|
||||
}
|
||||
[_rulerView removeMarker:self];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// MARK: - CPRulerView (Pure DOM + Interactive Engine)
|
||||
|
||||
@implementation CPRulerView : CPView
|
||||
{
|
||||
CPScrollView _scrollView @accessors(property=scrollView);
|
||||
CPRulerOrientation _orientation @accessors(property=orientation);
|
||||
CPView _clientView @accessors(property=clientView);
|
||||
|
||||
float _ruleThickness @accessors(property=ruleThickness);
|
||||
float _reservedThicknessForMarkers;
|
||||
CPArray _markers;
|
||||
|
||||
// Dragger variables
|
||||
CPRulerMarker _draggingMarker;
|
||||
CGPoint _dragStartPoint;
|
||||
float _dragStartLocation;
|
||||
}
|
||||
|
||||
- (id)initWithScrollView:(CPScrollView)aScrollView orientation:(CPRulerOrientation)anOrientation
|
||||
{
|
||||
if (self = [super initWithFrame:CGRectMakeZero()])
|
||||
{
|
||||
_scrollView = aScrollView;
|
||||
_orientation = anOrientation;
|
||||
_clientView = [aScrollView documentView];
|
||||
|
||||
_ruleThickness = (anOrientation === CPHorizontalRuler) ? 16.0 : 24.0;
|
||||
_reservedThicknessForMarkers = 0.0;
|
||||
_markers = [];
|
||||
|
||||
[self setBackgroundColor:[CPColor colorWithWhite:0.96 alpha:1.0]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setFrame:(CGRect)aFrame
|
||||
{
|
||||
[super setFrame:aFrame];
|
||||
[self updateRuler];
|
||||
}
|
||||
|
||||
// Markers registration
|
||||
- (void)addMarker:(CPRulerMarker)aMarker
|
||||
{
|
||||
if ([_markers containsObject:aMarker])
|
||||
return;
|
||||
|
||||
[_markers addObject:aMarker];
|
||||
[self addSubview:aMarker];
|
||||
[self _positionMarker:aMarker];
|
||||
}
|
||||
|
||||
- (void)removeMarker:(CPRulerMarker)aMarker
|
||||
{
|
||||
[_markers removeObject:aMarker];
|
||||
[aMarker removeFromSuperview];
|
||||
}
|
||||
|
||||
- (void)setMarkers:(CPArray)newMarkers
|
||||
{
|
||||
for (var i = 0; i < [_markers count]; i++)
|
||||
[[_markers objectAtIndex:i] removeFromSuperview];
|
||||
|
||||
_markers = [newMarkers mutableCopy];
|
||||
|
||||
for (var i = 0; i < [_markers count]; i++)
|
||||
{
|
||||
var marker = [_markers objectAtIndex:i];
|
||||
[self addSubview:marker];
|
||||
[self _positionMarker:marker];
|
||||
}
|
||||
}
|
||||
|
||||
- (CPRulerMarker)_markerAtPoint:(CGPoint)aPoint
|
||||
{
|
||||
for (var i = 0; i < [_markers count]; i++)
|
||||
{
|
||||
var marker = [_markers objectAtIndex:i];
|
||||
if (CGRectContainsPoint([marker frame], aPoint))
|
||||
return marker;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)_positionMarker:(CPRulerMarker)aMarker
|
||||
{
|
||||
if (!_scrollView)
|
||||
return;
|
||||
|
||||
var clipView = [_scrollView contentView],
|
||||
scrollPoint = [clipView bounds].origin,
|
||||
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
|
||||
rulerHeight = CGRectGetHeight([self bounds]),
|
||||
rulerWidth = CGRectGetWidth([self bounds]),
|
||||
markerLocation = [aMarker imageValue];
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
var x = markerLocation - scrollPoint.x - 6.0, // Center the 12px wide marker
|
||||
y = rulerHeight - 11.0; // Sit perfectly above bottom border
|
||||
|
||||
// Keep horizontal marker within the bounds of the ruler to prevent clipping
|
||||
if (x < 0.0)
|
||||
x = 0.0;
|
||||
else if (x + 12.0 > rulerWidth)
|
||||
x = rulerWidth - 12.0;
|
||||
|
||||
[aMarker setFrame:CGRectMake(x, y, 12.0, 12.0)];
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = rulerWidth - 11.0,
|
||||
y = markerLocation - scrollPoint.y - 6.0;
|
||||
|
||||
// Keep vertical marker within the bounds of the ruler to prevent clipping
|
||||
if (y < 0.0)
|
||||
y = 0.0;
|
||||
else if (y + 12.0 > rulerHeight)
|
||||
y = rulerHeight - 12.0;
|
||||
|
||||
[aMarker setFrame:CGRectMake(x, y, 12.0, 12.0)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Interaction Handlers
|
||||
|
||||
- (void)mouseDown:(CPEvent)anEvent
|
||||
{
|
||||
var locationInWindow = [anEvent locationInWindow],
|
||||
localPoint = [self convertPoint:locationInWindow fromView:nil],
|
||||
clipView = [_scrollView contentView],
|
||||
scrollPoint = [clipView bounds].origin,
|
||||
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
|
||||
|
||||
var rulerLocation = isHorizontal ? (localPoint.x + scrollPoint.x) : (localPoint.y + scrollPoint.y);
|
||||
|
||||
// 1. Check if clicked an existing marker
|
||||
var clickedMarker = [self _markerAtPoint:localPoint];
|
||||
if (clickedMarker)
|
||||
{
|
||||
_draggingMarker = clickedMarker;
|
||||
_dragStartPoint = localPoint;
|
||||
_dragStartLocation = [_draggingMarker imageValue];
|
||||
}
|
||||
// 2. Otherwise, create a new marker dynamically where the user clicked
|
||||
else
|
||||
{
|
||||
var newMarker = [[CPRulerMarker alloc] initWithRulerView:self
|
||||
markerLocation:rulerLocation
|
||||
imageValue:rulerLocation
|
||||
representedObject:nil];
|
||||
[self addMarker:newMarker];
|
||||
|
||||
// Notify the client view (e.g., CPTextView) that a new marker was added
|
||||
var client = [self clientView];
|
||||
if (client && [client respondsToSelector:@selector(rulerView:didAddMarker:)])
|
||||
[client rulerView:self didAddMarker:newMarker];
|
||||
|
||||
_draggingMarker = newMarker;
|
||||
_dragStartPoint = localPoint;
|
||||
_dragStartLocation = rulerLocation;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)mouseDragged:(CPEvent)anEvent
|
||||
{
|
||||
if (!_draggingMarker)
|
||||
return;
|
||||
|
||||
var locationInWindow = [anEvent locationInWindow],
|
||||
localPoint = [self convertPoint:locationInWindow fromView:nil],
|
||||
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
|
||||
|
||||
var delta = isHorizontal ? (localPoint.x - _dragStartPoint.x) : (localPoint.y - _dragStartPoint.y),
|
||||
newLocation = _dragStartLocation + delta;
|
||||
|
||||
if (newLocation < 0) newLocation = 0;
|
||||
|
||||
[_draggingMarker setImageValue:newLocation];
|
||||
[self _positionMarker:_draggingMarker];
|
||||
|
||||
// Check if dragged off the ruler (more than 15px off the boundary)
|
||||
var draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
|
||||
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15);
|
||||
|
||||
if (draggedOff)
|
||||
{
|
||||
// Visual feedback: Dim the handle to 40% and turn the triangle icon gray
|
||||
[_draggingMarker setAlphaValue:0.4];
|
||||
[[_draggingMarker label] setTextColor:[CPColor grayColor]];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restore standard styling when dragged back into the active strip
|
||||
[_draggingMarker setAlphaValue:1.0];
|
||||
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
|
||||
}
|
||||
|
||||
// Notify the CPTextView that the marker coordinates shifted
|
||||
var client = [self clientView];
|
||||
if (client && [client respondsToSelector:@selector(rulerView:didMoveMarker:)])
|
||||
[client rulerView:self didMoveMarker:_draggingMarker];
|
||||
}
|
||||
|
||||
- (void)mouseUp:(CPEvent)anEvent
|
||||
{
|
||||
if (!_draggingMarker)
|
||||
return;
|
||||
|
||||
var localPoint = [self convertPoint:[anEvent locationInWindow] fromView:nil],
|
||||
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal),
|
||||
|
||||
// If dragged more than 15 pixels off the ruler, delete the marker
|
||||
draggedOff = isHorizontal ? (localPoint.y < -15 || localPoint.y > CGRectGetHeight([self bounds]) + 15)
|
||||
: (localPoint.x < -15 || localPoint.x > CGRectGetWidth([self bounds]) + 15);
|
||||
|
||||
if (draggedOff)
|
||||
{
|
||||
var client = [self clientView];
|
||||
if (client && [client respondsToSelector:@selector(rulerView:didRemoveMarker:)])
|
||||
[client rulerView:self didRemoveMarker:_draggingMarker];
|
||||
|
||||
[self removeMarker:_draggingMarker];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure marker style is fully restored if not deleted
|
||||
[_draggingMarker setAlphaValue:1.0];
|
||||
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
|
||||
}
|
||||
|
||||
_draggingMarker = nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark DOM Layout Builder
|
||||
|
||||
- (void)updateRuler
|
||||
{
|
||||
// Wipe subviews to redraw the dynamic visible tick lines/numbers
|
||||
[self setSubviews:@[]];
|
||||
|
||||
if (!_scrollView)
|
||||
return;
|
||||
|
||||
var clipView = [_scrollView contentView],
|
||||
scrollBounds = [clipView bounds],
|
||||
scrollPoint = scrollBounds.origin,
|
||||
visibleSize = scrollBounds.size,
|
||||
isHorizontal = (_orientation === CPHorizontalRuler || _orientation === CPRulerOrientationHorizontal);
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
var start = Math.floor(scrollPoint.x / 10) * 10,
|
||||
end = scrollPoint.x + visibleSize.width,
|
||||
rulerHeight = CGRectGetHeight([self bounds]),
|
||||
rulerWidth = CGRectGetWidth([self bounds]);
|
||||
|
||||
// Draw solid horizontal bottom border (pure, razor-sharp CSS DOM view)
|
||||
var bottomBorder = [[CPView alloc] initWithFrame:CGRectMake(0, rulerHeight - 1, rulerWidth, 1)];
|
||||
[bottomBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
|
||||
[self addSubview:bottomBorder];
|
||||
|
||||
for (var val = start; val <= end; val += 10)
|
||||
{
|
||||
if (val < 0) continue;
|
||||
|
||||
var screenX = val - scrollPoint.x,
|
||||
isMajor = (val % 50 === 0),
|
||||
tickHeight = isMajor ? 8.0 : 4.0,
|
||||
tickY = rulerHeight - tickHeight - 1.0;
|
||||
|
||||
// Tick mark CSS line view
|
||||
var tick = [[CPView alloc] initWithFrame:CGRectMake(screenX, tickY, 1.0, tickHeight)];
|
||||
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
|
||||
[self addSubview:tick];
|
||||
|
||||
// Unit label
|
||||
if (isMajor)
|
||||
{
|
||||
var labelX = screenX - 20.0,
|
||||
alignment = CPCenterTextAlignment;
|
||||
|
||||
// Adjust label frame and alignment if it lands near left/right bounds
|
||||
if (labelX < 0.0)
|
||||
{
|
||||
labelX = Math.max(0.0, screenX);
|
||||
alignment = CPLeftTextAlignment;
|
||||
}
|
||||
else if (labelX + 40.0 > rulerWidth)
|
||||
{
|
||||
labelX = rulerWidth - 40.0;
|
||||
alignment = CPRightTextAlignment;
|
||||
}
|
||||
|
||||
var label = [[CPTextField alloc] initWithFrame:CGRectMake(labelX, 1.0, 40.0, 12.0)];
|
||||
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
|
||||
[label setFont:[CPFont systemFontOfSize:8.0]];
|
||||
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
|
||||
[label setAlignment:alignment];
|
||||
[self addSubview:label];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vertical Ruler
|
||||
var start = Math.floor(scrollPoint.y / 10) * 10,
|
||||
end = scrollPoint.y + visibleSize.height,
|
||||
rulerHeight = CGRectGetHeight([self bounds]),
|
||||
rulerWidth = CGRectGetWidth([self bounds]);
|
||||
|
||||
// Draw solid vertical right border (pure DOM)
|
||||
var rightBorder = [[CPView alloc] initWithFrame:CGRectMake(rulerWidth - 1, 0, 1, rulerHeight)];
|
||||
[rightBorder setBackgroundColor:[CPColor colorWithWhite:0.75 alpha:1.0]];
|
||||
[self addSubview:rightBorder];
|
||||
|
||||
for (var val = start; val <= end; val += 10)
|
||||
{
|
||||
if (val < 0) continue;
|
||||
|
||||
var screenY = val - scrollPoint.y,
|
||||
isMajor = (val % 50 === 0),
|
||||
tickWidth = isMajor ? 8.0 : 4.0,
|
||||
tickX = rulerWidth - tickWidth - 1.0;
|
||||
|
||||
// Tick mark CSS line view
|
||||
var tick = [[CPView alloc] initWithFrame:CGRectMake(tickX, screenY, tickWidth, 1.0)];
|
||||
[tick setBackgroundColor:[CPColor colorWithWhite:0.65 alpha:1.0]];
|
||||
[self addSubview:tick];
|
||||
|
||||
// Unit label
|
||||
if (isMajor)
|
||||
{
|
||||
var labelY = screenY - 6.0;
|
||||
|
||||
// Adjust label frame if it lands near top/bottom bounds
|
||||
if (labelY < 0.0)
|
||||
labelY = 0.0;
|
||||
else if (labelY + 12.0 > rulerHeight)
|
||||
labelY = rulerHeight - 12.0;
|
||||
|
||||
var label = [[CPTextField alloc] initWithFrame:CGRectMake(1.0, labelY, rulerWidth - 12.0, 12.0)];
|
||||
[label setStringValue:[CPString stringWithFormat:@"%d", val]];
|
||||
[label setFont:[CPFont systemFontOfSize:8.0]];
|
||||
[label setTextColor:[CPColor colorWithWhite:0.4 alpha:1.0]];
|
||||
[label setAlignment:CPRightTextAlignment];
|
||||
[self addSubview:label];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reposition and display active markers
|
||||
for (var i = 0; i < [_markers count]; i++)
|
||||
{
|
||||
var marker = [_markers objectAtIndex:i];
|
||||
if ([marker superview] !== self)
|
||||
[self addSubview:marker];
|
||||
[self _positionMarker:marker];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -31,6 +31,7 @@
|
||||
@import "CPTextStorage.j"
|
||||
@import "CPTextContainer.j"
|
||||
@import "CPLayoutManager.j"
|
||||
@import "CPParagraphStyle.j"
|
||||
|
||||
@import "_CPRTFParser.j"
|
||||
@import "_CPRTFProducer.j"
|
||||
@@ -69,7 +70,7 @@ _MidRange = function(a1)
|
||||
|
||||
function _isWhitespaceCharacter(chr)
|
||||
{
|
||||
return (chr === '\n' || chr === '\r' || chr === ' ' || chr === '\t');
|
||||
return (chr === '\n' || chr === '\r' || chr === ' '); // || chr === '\t'
|
||||
}
|
||||
|
||||
_characterTripletFromStringAtIndex = function(string, index)
|
||||
@@ -541,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];
|
||||
@@ -1782,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.
|
||||
@@ -2405,6 +2412,328 @@ 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"];
|
||||
[markers addObject:firstLineMarker];
|
||||
|
||||
var headMarker = [[CPRulerMarker alloc] initWithRulerView:ruler
|
||||
markerLocation:[paragraphStyle headIndent]
|
||||
imageValue:[paragraphStyle headIndent]
|
||||
representedObject:@"CPHeadIndent"];
|
||||
[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;
|
||||
|
||||
// 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;
|
||||
|
||||
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],
|
||||
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]];
|
||||
}
|
||||
|
||||
// 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 didRemoveMarker:(CPRulerMarker)marker
|
||||
{
|
||||
if (![self _didBeginEditing] || ![self shouldChangeTextInRange:_selectionRange replacementString:nil])
|
||||
return;
|
||||
|
||||
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];
|
||||
|
||||
var mutableStyle = [paragraphStyle mutableCopy],
|
||||
oldTab = [marker representedObject];
|
||||
|
||||
if (!oldTab)
|
||||
return;
|
||||
|
||||
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)
|
||||
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 didUpdateMarker:(CPRulerMarker)marker oldTab:(id)oldTab
|
||||
{
|
||||
var selectedRange = [self selectedRange],
|
||||
targetRange = selectedRange;
|
||||
|
||||
if (targetRange.length === 0)
|
||||
targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph];
|
||||
|
||||
if (targetRange.length === 0)
|
||||
return;
|
||||
|
||||
var paragraphStyle = [[self textStorage] attribute:CPParagraphStyleAttributeName atIndex:targetRange.location effectiveRange:NULL];
|
||||
if (!paragraphStyle)
|
||||
paragraphStyle = [CPParagraphStyle defaultParagraphStyle];
|
||||
|
||||
var mutableStyle = [paragraphStyle mutableCopy],
|
||||
newTab = [marker representedObject],
|
||||
tabs = [[mutableStyle tabStops] mutableCopy];
|
||||
|
||||
[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)];
|
||||
|
||||
if (selectedRange.length === 0)
|
||||
{
|
||||
[_typingAttributes setObject:mutableStyle forKey:CPParagraphStyleAttributeName];
|
||||
[[CPNotificationCenter defaultCenter] postNotificationName:CPTextViewDidChangeTypingAttributesNotification object:self];
|
||||
}
|
||||
|
||||
// Force layouts and view canvas update
|
||||
[_layoutManager _validateLayoutAndGlyphs];
|
||||
[self sizeToFit];
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTextView (CPTextViewDelegate)
|
||||
|
||||
@@ -2778,8 +3107,10 @@ var _CPCopyPlaceholder = '-';
|
||||
// Intercept problematic keys before the browser acts.
|
||||
_CPNativeInputField.addEventListener('keydown', function(e) {
|
||||
|
||||
if (e.key === 'Enter' || (e.key === 'Backspace' && _CPNativeInputField.innerHTML === '')) {
|
||||
if (e.key === 'Tab' || e.key === 'Enter' || (e.key === 'Backspace' && _CPNativeInputField.innerHTML === ''))
|
||||
{
|
||||
// Prevent browser default action:
|
||||
// - 'Tab': Prevents browser focus navigation, allowing Cappuccino key bindings (like option-tab or tab) to work.
|
||||
// - 'Enter': Prevents inserting <div><br></div>.
|
||||
// - 'Backspace' on empty: Prevents inserting junk characters on iPadOS.
|
||||
e.preventDefault();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* All modifications copyright Daniel Boehringer 2013.
|
||||
* Extensive code formatting and review by Andrew Hankinson
|
||||
* Based on original work by
|
||||
* Emmanuel Maillard on 27/02/2010.
|
||||
* Created by Emmanuel Maillard on 27/02/2010.
|
||||
* Copyright Emmanuel Maillard 2010.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
@@ -135,31 +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 (aWidth > tabStops[l - 1]._location)
|
||||
if (l === 0)
|
||||
return nil;
|
||||
|
||||
for (var i = l - 1; i >= 0; i--)
|
||||
// Find the first tab stop that is strictly greater than the current width
|
||||
for (var i = 0; i < l; i++)
|
||||
{
|
||||
if (aWidth > tabStops[i]._location)
|
||||
{
|
||||
if (i + 1 < l)
|
||||
return tabStops[i + 1];
|
||||
}
|
||||
var tab = [tabStops objectAtIndex:i];
|
||||
|
||||
if ([tab location] > aWidth)
|
||||
return tab;
|
||||
}
|
||||
|
||||
if (i === -1)
|
||||
return tabStops[0];
|
||||
// If aWidth exceeds the last tab stop, dynamically calculate the next
|
||||
// tab location using the default tab interval.
|
||||
var defaultInterval = [_currentParagraph defaultTabInterval] || 28.0;
|
||||
var nextLocation = CEIL((aWidth + 1.0) / defaultInterval) * defaultInterval;
|
||||
|
||||
return nil;
|
||||
return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextLocation];
|
||||
}
|
||||
|
||||
- (BOOL)_flushRange:(CPRange)lineRange
|
||||
@@ -263,6 +266,14 @@ var CPSystemTypesetterFactory,
|
||||
currentParagraphMaximumLineHeight,
|
||||
currentParagraphLineSpacing;
|
||||
|
||||
// Track physical line starts to prevent overwriting lineOrigin.x in tab segments
|
||||
var isStartOfPhysicalLine = YES;
|
||||
|
||||
// Track paragraph indents and margins
|
||||
var isFirstLineOfLayout = YES,
|
||||
isFirstLineOfParagraph = YES,
|
||||
rightMargin = containerSizeWidth;
|
||||
|
||||
if (glyphIndex > 0)
|
||||
lineOrigin = CGPointCreateCopy([_layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:nil].origin);
|
||||
else if ([_layoutManager extraLineFragmentTextContainer])
|
||||
@@ -289,6 +300,56 @@ var CPSystemTypesetterFactory,
|
||||
currentParagraphMaximumLineHeight = [_currentParagraph maximumLineHeight];
|
||||
currentParagraphLineSpacing = [_currentParagraph lineSpacing];
|
||||
|
||||
// Recalculate right margin on paragraph style change
|
||||
var tailIndent = [_currentParagraph tailIndent];
|
||||
if (tailIndent > 0.0)
|
||||
rightMargin = tailIndent;
|
||||
else if (tailIndent < 0.0)
|
||||
rightMargin = containerSizeWidth + tailIndent;
|
||||
else
|
||||
rightMargin = containerSizeWidth;
|
||||
|
||||
// If we are at the start of a physical line, we update lineOrigin.x
|
||||
if (isStartOfPhysicalLine)
|
||||
{
|
||||
if (glyphIndex > 0)
|
||||
{
|
||||
var prevChar = theString.charCodeAt(glyphIndex - 1);
|
||||
isFirstLineOfParagraph = (prevChar === 10 || prevChar === 13);
|
||||
}
|
||||
else
|
||||
{
|
||||
isFirstLineOfParagraph = YES;
|
||||
}
|
||||
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
|
||||
isFirstLineOfLayout = NO;
|
||||
}
|
||||
|
||||
// Calculate the right wrapping margin based on tail indent
|
||||
var tailIndent = [_currentParagraph tailIndent];
|
||||
if (tailIndent > 0.0)
|
||||
rightMargin = tailIndent;
|
||||
else if (tailIndent < 0.0)
|
||||
rightMargin = containerSizeWidth + tailIndent;
|
||||
else
|
||||
rightMargin = containerSizeWidth;
|
||||
|
||||
// Handle the layout's very first line indentation
|
||||
if (isFirstLineOfLayout)
|
||||
{
|
||||
if (glyphIndex > 0)
|
||||
{
|
||||
var prevChar = theString.charCodeAt(glyphIndex - 1);
|
||||
isFirstLineOfParagraph = (prevChar === 10 || prevChar === 13);
|
||||
}
|
||||
else
|
||||
{
|
||||
isFirstLineOfParagraph = YES;
|
||||
}
|
||||
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
|
||||
isFirstLineOfLayout = NO;
|
||||
}
|
||||
|
||||
if (!currentFont)
|
||||
currentFont = [_textStorage font] || [CPFont systemFontOfSize:12.0];
|
||||
|
||||
@@ -316,6 +377,9 @@ var CPSystemTypesetterFactory,
|
||||
lineRange.length++;
|
||||
measuringRange.length++;
|
||||
|
||||
// We are processing characters, so we are no longer at the start of a physical line
|
||||
isStartOfPhysicalLine = NO;
|
||||
|
||||
var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons
|
||||
rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor;
|
||||
|
||||
@@ -356,15 +420,68 @@ var CPSystemTypesetterFactory,
|
||||
isTabStop = YES;
|
||||
|
||||
if (nextTab)
|
||||
rangeWidth = nextTab._location - lineOrigin.x;
|
||||
{
|
||||
// Look-ahead to measure the width of the incoming text segment for alignment
|
||||
var nextSegmentWidth = 0.0,
|
||||
tempIndex = glyphIndex + 1,
|
||||
segmentString = "";
|
||||
|
||||
while (tempIndex < numberOfGlyphs)
|
||||
{
|
||||
var nextCharCode = theString.charCodeAt(tempIndex);
|
||||
if (nextCharCode === 9 || nextCharCode === 10 || nextCharCode === 13)
|
||||
break;
|
||||
segmentString += theString.charAt(tempIndex);
|
||||
tempIndex++;
|
||||
}
|
||||
|
||||
if (segmentString.length > 0)
|
||||
nextSegmentWidth = [segmentString sizeWithFont:currentFont inWidth:NULL].width;
|
||||
|
||||
var tabLocation = [nextTab location],
|
||||
tabAlignment = [nextTab alignment];
|
||||
|
||||
// Mathematically offset the tab character's right boundary
|
||||
if (tabAlignment === CPCenterTextAlignment)
|
||||
{
|
||||
rangeWidth = (tabLocation - nextSegmentWidth / 2.0) - lineOrigin.x;
|
||||
}
|
||||
else if (tabAlignment === CPRightTextAlignment)
|
||||
{
|
||||
rangeWidth = (tabLocation - nextSegmentWidth) - lineOrigin.x;
|
||||
}
|
||||
else // Left align tab stop
|
||||
{
|
||||
rangeWidth = tabLocation - lineOrigin.x;
|
||||
}
|
||||
|
||||
// Enforce a minimum safety spacer width to avoid character overlapping
|
||||
var minRangeWidth = prevRangeWidth + 5.0;
|
||||
if (rangeWidth < minRangeWidth)
|
||||
rangeWidth = minRangeWidth;
|
||||
}
|
||||
else
|
||||
rangeWidth += 28; //FIXME
|
||||
} // fallthrough intentional
|
||||
{
|
||||
rangeWidth += 28.0; // standard fallback spacer
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 32: // ' '
|
||||
wrapRange = CPMakeRangeCopy(lineRange);
|
||||
wrapWidth = rangeWidth;
|
||||
wrapRange._height = _lineHeight;
|
||||
wrapRange._base = _lineBase;
|
||||
|
||||
// Optimization: Start measuring from the next character to avoid O(n^2)
|
||||
// string width calculation within a line since spaces do not carry ligatures or kerning.
|
||||
// Only reset the measuring range if the next character is NOT another space.
|
||||
// This prevents compounded subpixel rounding errors with contiguous spaces.
|
||||
if (theString.charCodeAt(glyphIndex + 1) !== 32)
|
||||
{
|
||||
currentAnchor = rangeWidth;
|
||||
measuringRange = CPMakeRange(glyphIndex + 1, 0);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 10:
|
||||
@@ -375,7 +492,8 @@ var CPSystemTypesetterFactory,
|
||||
advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent});
|
||||
prevRangeWidth = _lineWidth = rangeWidth;
|
||||
|
||||
if (lineOrigin.x + rangeWidth > containerSizeWidth)
|
||||
// Wrap lines against the tail indent (rightMargin) instead of container boundaries
|
||||
if (lineOrigin.x + rangeWidth > rightMargin)
|
||||
{
|
||||
if (wrapWidth)
|
||||
{
|
||||
@@ -419,12 +537,17 @@ var CPSystemTypesetterFactory,
|
||||
containerSizeHeight = containerSize.height;
|
||||
}
|
||||
|
||||
lineOrigin.x = 0;
|
||||
// If this is a soft wrap (isWordWrapped), next line gets headIndent.
|
||||
// If it was a paragraph return, it gets firstLineHeadIndent.
|
||||
isFirstLineOfParagraph = !isWordWrapped;
|
||||
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
|
||||
|
||||
numLines++;
|
||||
isNewline = NO;
|
||||
_lineFragments = [];
|
||||
_lineHeight = 0;
|
||||
_lineBase = ascent;
|
||||
isStartOfPhysicalLine = YES;
|
||||
}
|
||||
|
||||
isTabStop = NO;
|
||||
|
||||
@@ -4,11 +4,6 @@
|
||||
|
||||
Copyright (C) 2014 Daniel Boehringer
|
||||
|
||||
FIXME: this class should be redone using a 'real' parser
|
||||
|
||||
* all paragraph spacing information is currently not parsed
|
||||
|
||||
|
||||
* 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
|
||||
@@ -37,9 +32,25 @@ FIXME: this class should be redone using a 'real' parser
|
||||
|
||||
@global CPFontAttributeName
|
||||
@global CPForegroundColorAttributeName
|
||||
@global CPBackgroundColorAttributeName
|
||||
@global CPParagraphStyleAttributeName
|
||||
|
||||
@global CPLeftTabStopType
|
||||
@global CPRightTabStopType
|
||||
@global CPCenterTabStopType
|
||||
@global CPDecimalTabStopType
|
||||
|
||||
var hexTable = [];
|
||||
|
||||
var cp1252Map = {
|
||||
0x80: 0x20AC, 0x82: 0x201A, 0x83: 0x0192, 0x84: 0x201E, 0x85: 0x2026,
|
||||
0x86: 0x2020, 0x87: 0x2021, 0x88: 0x02C6, 0x89: 0x2030, 0x8A: 0x0160,
|
||||
0x8B: 0x2039, 0x8C: 0x0152, 0x8E: 0x017D, 0x91: 0x2018, 0x92: 0x2019,
|
||||
0x93: 0x201C, 0x94: 0x201D, 0x95: 0x2022, 0x96: 0x2013, 0x97: 0x2014,
|
||||
0x98: 0x02DC, 0x99: 0x2122, 0x9A: 0x0161, 0x9B: 0x203A, 0x9C: 0x0153,
|
||||
0x9E: 0x017E, 0x9F: 0x0178
|
||||
};
|
||||
|
||||
// Hold the attributes of the current run
|
||||
@implementation _RTFAttribute : CPObject
|
||||
{
|
||||
@@ -56,6 +67,7 @@ var hexTable = [];
|
||||
BOOL strikethrough;
|
||||
BOOL script;
|
||||
BOOL _tabChanged;
|
||||
CPTabStopType _nextTabType;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
@@ -65,6 +77,7 @@ var hexTable = [];
|
||||
[self resetFont];
|
||||
[self resetParagraphStyle];
|
||||
_range = CPMakeRange(0, 0);
|
||||
_nextTabType = CPLeftTabStopType;
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -74,11 +87,19 @@ var hexTable = [];
|
||||
{
|
||||
var mynew = [_RTFAttribute new];
|
||||
|
||||
mynew.paragraph = [paragraph copy];
|
||||
mynew.paragraph = [paragraph mutableCopy];
|
||||
mynew.fontName = fontName;
|
||||
mynew.fontSize = fontSize;
|
||||
mynew.bold = bold;
|
||||
mynew.italic = italic;
|
||||
mynew.underline = underline;
|
||||
mynew.strikethrough = strikethrough;
|
||||
mynew.script = script;
|
||||
mynew.fgColour = fgColour;
|
||||
mynew.bgColour = bgColour;
|
||||
mynew.ulColour = ulColour;
|
||||
mynew._tabChanged = _tabChanged;
|
||||
mynew._nextTabType = _nextTabType;
|
||||
|
||||
return mynew;
|
||||
}
|
||||
@@ -134,7 +155,9 @@ var hexTable = [];
|
||||
|
||||
- (void)resetParagraphStyle
|
||||
{
|
||||
paragraph = [[CPParagraphStyle defaultParagraphStyle] copy];
|
||||
paragraph = [[CPParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
_tabChanged = NO;
|
||||
_nextTabType = CPLeftTabStopType;
|
||||
}
|
||||
|
||||
- (void)resetFont
|
||||
@@ -152,7 +175,7 @@ var hexTable = [];
|
||||
|
||||
- (void)addTab:(float)location type:(CPTextTabType)type
|
||||
{
|
||||
var tab = [[CPTextTab alloc] initWithType:CPLeftTabStopType
|
||||
var tab = [[CPTextTab alloc] initWithType:type
|
||||
location:location];
|
||||
|
||||
if (!_tabChanged)
|
||||
@@ -164,6 +187,8 @@ var hexTable = [];
|
||||
{
|
||||
[paragraph addTabStop: tab];
|
||||
}
|
||||
|
||||
_nextTabType = CPLeftTabStopType;
|
||||
}
|
||||
|
||||
- (CPDictionary)dictionary
|
||||
@@ -175,6 +200,9 @@ var hexTable = [];
|
||||
if (fgColour)
|
||||
[ret setObject:fgColour forKey:CPForegroundColorAttributeName];
|
||||
|
||||
if (bgColour)
|
||||
[ret setObject:bgColour forKey:CPBackgroundColorAttributeName];
|
||||
|
||||
return ret;
|
||||
}
|
||||
@end
|
||||
@@ -193,7 +221,7 @@ var kRgsymRtf = {
|
||||
"b" : [ "b", 1, false, kRTFParserType_prop, "propBold"],
|
||||
"ul" : [ "ul", 1, false, kRTFParserType_prop, "propUnderline"],
|
||||
"i" : [ "i", 1, false, kRTFParserType_prop, "propItalic"],
|
||||
"li" : [ "li", 0, false, kRTFParserType_prop, "propPgnFormat"],
|
||||
// "li" : [ "li", 0, false, kRTFParserType_prop, "propPgnFormat"],
|
||||
"pgnucltr" : [ "pgnucltr", "pgULtr", true, kRTFParserType_prop, "propPgnFormat"],
|
||||
"pgnlcltr" : [ "pgnlcltr", "pgLLtr", true, kRTFParserType_prop, "propPgnFormat"],
|
||||
"qc" : [ "qc", "justC", true, kRTFParserType_prop, "propJust"],
|
||||
@@ -321,17 +349,25 @@ var kRgsymRtf = {
|
||||
|
||||
- (BOOL)pushState
|
||||
{
|
||||
_states.push["group"];
|
||||
// Push stack as an object containing scoping context
|
||||
_states.push({
|
||||
curState: _curState,
|
||||
run: [_currentRun copy]
|
||||
});
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)popState
|
||||
{
|
||||
_states.pop();
|
||||
|
||||
if (_curState > 0)
|
||||
_curState--;
|
||||
if (_states.length > 0)
|
||||
{
|
||||
var state = _states.pop();
|
||||
_curState = state.curState;
|
||||
|
||||
[self _flushCurrentRun];
|
||||
_currentRun = state.run;
|
||||
_currentRun._range = CPMakeRange([_result length], 0);
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -346,25 +382,23 @@ var kRgsymRtf = {
|
||||
return '';
|
||||
|
||||
case "ipfnHex":
|
||||
ch = _rtf.charAt(++_currentParseIndex);
|
||||
|
||||
var hex = '';
|
||||
|
||||
while (new RegExp("[a-fA-F0-9\\']").test(ch))
|
||||
// Konsumiere exakt 2 Zeichen nach dem \'
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
if (ch == "'")
|
||||
var nextCh = _rtf.charAt(++_currentParseIndex);
|
||||
if (/[a-fA-F0-9]/.test(nextCh))
|
||||
{
|
||||
_currentParseIndex++;
|
||||
continue;
|
||||
hex += nextCh;
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentParseIndex--;
|
||||
break;
|
||||
}
|
||||
|
||||
hex += (ch + '');
|
||||
ch = _rtf.charAt(++_currentParseIndex);
|
||||
}
|
||||
//ch = parseInt(ch, 16);
|
||||
//console.log("hex : " + hex);
|
||||
|
||||
_hexreturn = YES;
|
||||
_currentParseIndex--;
|
||||
|
||||
if (_curState !== 0)
|
||||
return '';
|
||||
@@ -406,10 +440,14 @@ var kRgsymRtf = {
|
||||
var dict = [_currentRun dictionary];
|
||||
|
||||
[_result setAttributes:dict range:_currentRun._range]; // flush previous run
|
||||
_currentRun.fgColour = [CPColor blackColor];
|
||||
|
||||
// Deep copy the current run style for the next sequence of characters
|
||||
_currentRun = [_currentRun copy];
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentRun = [_RTFAttribute new];
|
||||
}
|
||||
|
||||
_currentRun._range = CPMakeRange(newOffset, 0); // open a new one
|
||||
}
|
||||
@@ -422,6 +460,7 @@ var kRgsymRtf = {
|
||||
{
|
||||
case "pard":
|
||||
[self _flushCurrentRun];
|
||||
[_currentRun resetParagraphStyle];
|
||||
break;
|
||||
|
||||
case "b": // bold
|
||||
@@ -430,7 +469,7 @@ var kRgsymRtf = {
|
||||
if (_currentRun && _currentRun.bold)
|
||||
[self _flushCurrentRun];
|
||||
|
||||
_currentRun.bold = NO
|
||||
_currentRun.bold = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -448,7 +487,7 @@ var kRgsymRtf = {
|
||||
if (_currentRun && _currentRun.italic)
|
||||
[self _flushCurrentRun];
|
||||
|
||||
_currentRun.italic = NO
|
||||
_currentRun.italic = NO;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -464,6 +503,18 @@ var kRgsymRtf = {
|
||||
[_currentRun.paragraph setAlignment:CPCenterTextAlignment];
|
||||
break;
|
||||
|
||||
case "ql": // paragraph left
|
||||
[_currentRun.paragraph setAlignment:CPLeftTextAlignment];
|
||||
break;
|
||||
|
||||
case "qr": // paragraph right
|
||||
[_currentRun.paragraph setAlignment:CPRightTextAlignment];
|
||||
break;
|
||||
|
||||
case "qj": // paragraph justified
|
||||
[_currentRun.paragraph setAlignment:CPJustifiedTextAlignment];
|
||||
break;
|
||||
|
||||
case "paperw":
|
||||
_paper.width = param;
|
||||
break;
|
||||
@@ -570,6 +621,20 @@ var kRgsymRtf = {
|
||||
|
||||
break;
|
||||
|
||||
case "cb": // change background color
|
||||
case "highlight":
|
||||
[self _flushCurrentRun];
|
||||
var colorIndex = parseInt(param) - 1;
|
||||
|
||||
if (_currentRun)
|
||||
{
|
||||
if (colorIndex >= 0 && colorIndex < _colorArray.length)
|
||||
_currentRun.bgColour = _colorArray[colorIndex];
|
||||
else
|
||||
_currentRun.bgColour = nil;
|
||||
}
|
||||
break;
|
||||
|
||||
case "f": // change font
|
||||
[self _flushCurrentRun];
|
||||
var fontIndex = parseInt(param);
|
||||
@@ -583,11 +648,41 @@ var kRgsymRtf = {
|
||||
_currentRun.fontSize = parseInt(param) / 2;
|
||||
break;
|
||||
|
||||
case "tx": // tabstop
|
||||
case "fi": // first line indent
|
||||
if (_currentRun)
|
||||
[_currentRun.paragraph setFirstLineHeadIndent:parseInt(param) / 20.0];
|
||||
break;
|
||||
|
||||
case "li": // left indent / head indent
|
||||
if (_currentRun)
|
||||
[_currentRun.paragraph setHeadIndent:parseInt(param) / 20.0];
|
||||
break;
|
||||
|
||||
case "ri": // right indent / tail indent
|
||||
if (_currentRun)
|
||||
[_currentRun.paragraph setTailIndent:parseInt(param) / 20.0];
|
||||
break;
|
||||
|
||||
case "tqc": // center tab stop style flag
|
||||
if (_currentRun)
|
||||
_currentRun._nextTabType = CPCenterTabStopType;
|
||||
break;
|
||||
|
||||
case "tqr": // right tab stop style flag
|
||||
if (_currentRun)
|
||||
_currentRun._nextTabType = CPRightTabStopType;
|
||||
break;
|
||||
|
||||
case "tqdec": // decimal tab stop style flag
|
||||
if (_currentRun)
|
||||
_currentRun._nextTabType = CPDecimalTabStopType;
|
||||
break;
|
||||
|
||||
case "tx": // tabstop location definition
|
||||
var location = parseInt(param) / 20;
|
||||
|
||||
if (_currentRun)
|
||||
[_currentRun addTab:location type:CPLeftTabStopType];
|
||||
[_currentRun addTab:location type:_currentRun._nextTabType];
|
||||
|
||||
break;
|
||||
|
||||
@@ -596,9 +691,6 @@ var kRgsymRtf = {
|
||||
|
||||
}
|
||||
|
||||
if (_states.length > 0)
|
||||
_curState = 1;
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -734,32 +826,16 @@ var kRgsymRtf = {
|
||||
{
|
||||
if (ch.length > 0)
|
||||
{
|
||||
if (parseInt(ch, 16) & 0x80)
|
||||
{
|
||||
hex += ch.toUpperCase();
|
||||
}
|
||||
else
|
||||
{
|
||||
[self _appendPlainString: String.fromCharCode(parseInt((hex + ch), 16))];
|
||||
hex = '';
|
||||
var byteVal = parseInt(ch, 16);
|
||||
var unicodeVal = byteVal;
|
||||
|
||||
// Windows-1252 Mapping für den Bereich 0x80 - 0x9F anwenden
|
||||
if (byteVal >= 0x80 && byteVal <= 0x9F) {
|
||||
unicodeVal = cp1252Map[byteVal] || byteVal;
|
||||
}
|
||||
|
||||
if (hex.length == 4)
|
||||
{
|
||||
var temp = parseInt(hex, 16);
|
||||
|
||||
if (hexTable && hexTable[hex.toUpperCase()] !== undefined)
|
||||
temp = parseInt(hexTable[hex.toUpperCase()], 16);
|
||||
|
||||
[self _appendPlainString: String.fromCharCode(temp)];
|
||||
hex = '';
|
||||
}
|
||||
[self _appendPlainString: String.fromCharCode(unicodeVal)];
|
||||
}
|
||||
else
|
||||
{
|
||||
CPLogConsole("hex skipped");
|
||||
}
|
||||
|
||||
_hexreturn = NO;
|
||||
}
|
||||
else if (ch !== undefined && _curState === 0)
|
||||
|
||||
+20
-2
@@ -349,6 +349,12 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
if (theBinding)
|
||||
[theBinding reverseSetValueFor:@"objectValue"];
|
||||
|
||||
if (!_isEditing)
|
||||
{
|
||||
_isEditing = YES;
|
||||
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
[self textDidChange:[CPNotification notificationWithName:CPControlTextDidChangeNotification object:self userInfo:nil]];
|
||||
|
||||
_shouldNotifyTarget = YES;
|
||||
@@ -453,8 +459,8 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
{
|
||||
[_tokenScrollView documentView]._DOMElement.appendChild(element);
|
||||
|
||||
//post CPControlTextDidBeginEditingNotification
|
||||
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
||||
// Removed so CPTokenField doesn't fire the notification the moment it becomes the first responder, but instead defers to the first keystroke, just like Cocoa (see keyDown: in CPTextField).
|
||||
// [self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
||||
|
||||
[[CPRunLoop mainRunLoop] performBlock:function()
|
||||
{
|
||||
@@ -498,6 +504,8 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
|
||||
[self _resignFirstKeyResponder];
|
||||
|
||||
_isEditing = NO;
|
||||
|
||||
if (_shouldNotifyTarget)
|
||||
{
|
||||
_shouldNotifyTarget = NO;
|
||||
@@ -1032,6 +1040,16 @@ CPTokenFieldDeleteButtonType = 1;
|
||||
CPTokenFieldTextDidChangeValue = [self stringValue];
|
||||
#endif
|
||||
|
||||
// Has to be enabled, and it also has to be editable or selectable.
|
||||
if (![self isEnabled] || !([self isEditable] || [self isSelectable]))
|
||||
return;
|
||||
|
||||
if ([self isEditable] && !_isEditing)
|
||||
{
|
||||
_isEditing = YES;
|
||||
[self textDidBeginEditing:[CPNotification notificationWithName:CPControlTextDidBeginEditingNotification object:self userInfo:nil]];
|
||||
}
|
||||
|
||||
// Leave the default _propagateCurrentDOMEvent setting in place. This might be YES or NO depending
|
||||
// on if something that could be a browser shortcut was pressed or not, such as Cmd-R to reload.
|
||||
// If it was NO we want to leave it at NO however and only enable it in insertText:. This is what
|
||||
|
||||
@@ -1951,7 +1951,8 @@ CPTexturedBackgroundWindowMask
|
||||
return [[self firstResponder] keyUp:anEvent];
|
||||
|
||||
case CPKeyDown:
|
||||
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter)
|
||||
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter &&
|
||||
!([anEvent modifierFlags] & (CPAlternateKeyMask | CPCommandKeyMask)))
|
||||
{
|
||||
if ([anEvent modifierFlags] & CPShiftKeyMask)
|
||||
[self selectPreviousKeyView:self];
|
||||
|
||||
+2
-8
@@ -3,18 +3,12 @@
|
||||
|
||||
# Cappuccino: Build Desktop-Class Web Applications
|
||||
|
||||
Cappuccino is an open-source framework that supports building powerful, desktop-class applications running in any modern web browser. Instead of direct manipulation of HTML, CSS, and the DOM, applications are built using Objective-J, a superset of JavaScript modeled on Objective-C.
|
||||
|
||||
Cappuccino faithfully implements the proven design patterns of NeXTSTEP/Apple's Cocoa frameworks, enabling the creation of incredibly complex and reliable applications with a fraction of the code.
|
||||
|
||||
> **✨ Project Status: Active Development & Node.js Transition**
|
||||
> Cappuccino has been under continuous development since 2008 and is actively maintained. A major transition to a modern, **Node.js-based toolchain** has recently been finalized. The current release is a production-ready Release Candidate, with a formal release scheduled for 2026. It is stable, fast, and ready for new projects.
|
||||
|
||||
---
|
||||
|
||||
## Why Use Cappuccino?
|
||||
|
||||
Cappuccino is not intended for building simple websites. It is for building **applications**—especially complex, data-rich, line-of-business tools where productivity and user experience are paramount.
|
||||
Cappuccino is for building **applications** in the browser — especially complex, data-rich, line-of-business tools where productivity and user experience are paramount. Instead of direct manipulation of HTML, CSS, and the DOM, applications are built using Objective-J, a superset of JavaScript modeled on Objective-C. This gives you a lot of benefits:
|
||||
|
||||
* **💻 True Desktop Behavior, Out-of-the-Box:** Applications built with Cappuccino behave like native desktop software by default. This includes a rich palette of UI controls, **full keyboard navigation and focus management**, and **multi-level undo/redo support** — as you can see in this [Showcase application](https://ansb.uniklinik-freiburg.de/ThemeKitchenSinkA3). Also take a look at the [Cookbook tutorial](https://cappuccino-cookbook.5apps.com/).
|
||||
* **🚀 Incredible Productivity:** Less code is needed. High-level abstractions and a powerful object-oriented model mean development is focused on application logic, not browser quirks.
|
||||
@@ -84,7 +78,7 @@ Pure JavaScript and Objective-J can be mixed and matched, even in the same file.
|
||||
## Find Out More
|
||||
|
||||
* **Official Website:** [cappuccino.dev](http://cappuccino.dev)
|
||||
* **Documentation & Tutorials:** [cappuccino.dev/learn/](http://cappuccino.dev/learn/), [cappuccino cookbook](https://cappuccino-cookbook.5apps.com)
|
||||
* **Documentation & Tutorials:** [cappuccino.dev/learn/](http://cappuccino.dev/learn/), [Browser online documentation](https://daboe01.github.io/CappDoc/), [cappuccino cookbook](https://cappuccino-cookbook.5apps.com)
|
||||
* **Gitter Community Chat:** [gitter.im/cappuccino/cappuccino](https://gitter.im/cappuccino/cappuccino)
|
||||
* **GitHub Wiki:** [github.com/cappuccino/cappuccino/wiki](https://github.com/cappuccino/cappuccino/wiki)
|
||||
* **FAQ:** [cappuccino.dev/support/faq.html](http://cappuccino.dev/support/faq.html)
|
||||
|
||||
@@ -8,33 +8,41 @@
|
||||
@import <Foundation/Foundation.j>
|
||||
@import <AppKit/CPTextView.j>
|
||||
@import <AppKit/CPFontPanel.j>
|
||||
@import <AppKit/CPRulerView.j>
|
||||
@import <AppKit/CPSplitView.j>
|
||||
@import <AppKit/CPButton.j>
|
||||
@import <AppKit/CPTextField.j>
|
||||
@import <AppKit/CPScrollView.j>
|
||||
@import <AppKit/CPParagraphStyle.j>
|
||||
|
||||
@implementation AppController : CPObject
|
||||
{
|
||||
CPTextView _textView;
|
||||
CPTextView _textView2;
|
||||
CPTextView _textView;
|
||||
CPTextView _textView2;
|
||||
CPScrollView _scrollView;
|
||||
CPScrollView _scrollView2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void) openSheet:(id)sender
|
||||
- (void)openSheet:(id)sender
|
||||
{
|
||||
var plusPopover =[CPPopover new];
|
||||
var plusPopover = [CPPopover new];
|
||||
[plusPopover setDelegate:self];
|
||||
[plusPopover setAnimates:NO];
|
||||
[plusPopover setBehavior:CPPopoverBehaviorTransient];
|
||||
[plusPopover setAppearance:CPPopoverAppearanceMinimal];
|
||||
var myViewController=[CPViewController new];
|
||||
|
||||
var myViewController = [CPViewController new];
|
||||
[plusPopover setContentViewController:myViewController];
|
||||
|
||||
var textView = [[CPTextView alloc] initWithFrame:CGRectMake(0, 0, 200, 10)];
|
||||
[textView setBackgroundColor:[CPColor whiteColor]];
|
||||
|
||||
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0, 0, 200, 150)];
|
||||
|
||||
[scrollView setDocumentView:textView];
|
||||
|
||||
[myViewController setView:scrollView];
|
||||
[plusPopover showRelativeToRect:NULL ofView:sender preferredEdge:nil];
|
||||
[[textView window] makeFirstResponder:textView];
|
||||
|
||||
}
|
||||
|
||||
- (void)orderFrontFontPanel:(id)sender
|
||||
@@ -42,100 +50,282 @@
|
||||
[[CPFontManager sharedFontManager] orderFrontFontPanel:self];
|
||||
}
|
||||
|
||||
- (void)toggleRuler:(id)sender
|
||||
{
|
||||
[_scrollView setRulersVisible:![_scrollView rulersVisible]];
|
||||
}
|
||||
|
||||
- (void)alignLeft:(id)sender
|
||||
{
|
||||
[_textView alignLeft:self];
|
||||
}
|
||||
|
||||
- (void)alignCenter:(id)sender
|
||||
{
|
||||
[_textView alignCenter:self];
|
||||
}
|
||||
|
||||
- (void)alignRight:(id)sender
|
||||
{
|
||||
[_textView alignRight:self];
|
||||
}
|
||||
|
||||
- (void)alignJustified:(id)sender
|
||||
{
|
||||
[_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
|
||||
{
|
||||
// CPLogRegister(CPLogConsole);
|
||||
|
||||
var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask],
|
||||
contentView = [theWindow contentView];
|
||||
|
||||
[contentView setBackgroundColor:[CPColor colorWithWhite:0.95 alpha:1.0]];
|
||||
|
||||
var mybutton=[[CPButton alloc] initWithFrame:CGRectMake(0, 0, 250, 25)];
|
||||
[mybutton setTitle:"Open sheet (must not be triggered by return)"]
|
||||
[mybutton setTarget:self];
|
||||
[mybutton setAction:@selector(openSheet:)];
|
||||
[mybutton setKeyEquivalent:@"\r"];
|
||||
// 1. Clean visual header / toolbar area
|
||||
var toolbarView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([contentView bounds]), 60)];
|
||||
[toolbarView setAutoresizingMask:CPViewWidthSizable];
|
||||
[toolbarView setBackgroundColor:[CPColor colorWithWhite:0.88 alpha:1.0]];
|
||||
[contentView addSubview:toolbarView];
|
||||
|
||||
[contentView addSubview:mybutton];
|
||||
var currentX = 15;
|
||||
|
||||
// Popover Trigger
|
||||
var sheetButton = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 145, 30)];
|
||||
[sheetButton setTitle:@"Open Popover Sheet"];
|
||||
[sheetButton setTarget:self];
|
||||
[sheetButton setAction:@selector(openSheet:)];
|
||||
[toolbarView addSubview:sheetButton];
|
||||
currentX += 155;
|
||||
|
||||
_textView = [[CPTextView alloc] initWithFrame:CGRectMake(0, 0, 500, 200)];
|
||||
// Font Panel Trigger
|
||||
var fontButton = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 125, 30)];
|
||||
[fontButton setTitle:@"Show Font Panel"];
|
||||
[fontButton setTarget:self];
|
||||
[fontButton setAction:@selector(orderFrontFontPanel:)];
|
||||
[toolbarView addSubview:fontButton];
|
||||
currentX += 135;
|
||||
|
||||
// Toggle Ruler Trigger
|
||||
var rulerButton = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 120, 30)];
|
||||
[rulerButton setTitle:@"Toggle Ruler"];
|
||||
[rulerButton setTarget:self];
|
||||
[rulerButton setAction:@selector(toggleRuler:)];
|
||||
[toolbarView addSubview:rulerButton];
|
||||
currentX += 130;
|
||||
|
||||
// RTF Roundtrip Trigger
|
||||
var rtfButton = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 150, 30)];
|
||||
[rtfButton setTitle:@"RTF Round-trip ➔"];
|
||||
[rtfButton setTarget:self];
|
||||
[rtfButton setAction:@selector(makeRTF:)];
|
||||
[toolbarView addSubview:rtfButton];
|
||||
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)];
|
||||
[labelAlign setStringValue:@"Align:"];
|
||||
[labelAlign setFont:[CPFont systemFontOfSize:12]];
|
||||
[toolbarView addSubview:labelAlign];
|
||||
currentX += 45;
|
||||
|
||||
var alignLeftBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 50, 30)];
|
||||
[alignLeftBtn setTitle:@"Left"];
|
||||
[alignLeftBtn setTarget:self];
|
||||
[alignLeftBtn setAction:@selector(alignLeft:)];
|
||||
[toolbarView addSubview:alignLeftBtn];
|
||||
currentX += 55;
|
||||
|
||||
var alignCenterBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 60, 30)];
|
||||
[alignCenterBtn setTitle:@"Center"];
|
||||
[alignCenterBtn setTarget:self];
|
||||
[alignCenterBtn setAction:@selector(alignCenter:)];
|
||||
[toolbarView addSubview:alignCenterBtn];
|
||||
currentX += 65;
|
||||
|
||||
var alignRightBtn = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 55, 30)];
|
||||
[alignRightBtn setTitle:@"Right"];
|
||||
[alignRightBtn setTarget:self];
|
||||
[alignRightBtn setAction:@selector(alignRight:)];
|
||||
[toolbarView addSubview:alignRightBtn];
|
||||
currentX += 60;
|
||||
|
||||
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
|
||||
var returnButton = [[CPButton alloc] initWithFrame:CGRectMake(CGRectGetWidth([contentView bounds]) - 270, 15, 250, 30)];
|
||||
[returnButton setAutoresizingMask:CPViewMinXMargin];
|
||||
[returnButton setTitle:@"Key Return Target"];
|
||||
[returnButton setTarget:self];
|
||||
[returnButton setAction:@selector(openSheet:)];
|
||||
[returnButton setKeyEquivalent:@"\r"];
|
||||
[toolbarView addSubview:returnButton];
|
||||
|
||||
// 2. Main content area: Split View Layout for side-by-side comparison
|
||||
var splitView = [[CPSplitView alloc] initWithFrame:CGRectMake(0, 60, CGRectGetWidth([contentView bounds]), CGRectGetHeight([contentView bounds]) - 60)];
|
||||
[splitView setVertical:YES];
|
||||
[splitView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
var leftContainer = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([splitView bounds]) / 2, CGRectGetHeight([splitView bounds]))];
|
||||
[leftContainer setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
var rightContainer = [[CPView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([splitView bounds]) / 2, CGRectGetHeight([splitView bounds]))];
|
||||
[rightContainer setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
|
||||
[splitView addSubview:leftContainer];
|
||||
[splitView addSubview:rightContainer];
|
||||
[contentView addSubview:splitView];
|
||||
|
||||
// Left Container: label & Scroll/TextView containing Ruler
|
||||
var leftLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 10, CGRectGetWidth([leftContainer bounds]) - 30, 20)];
|
||||
[leftLabel setStringValue:@"Rich Text Editor"];
|
||||
[leftLabel setFont:[CPFont boldSystemFontOfSize:14]];
|
||||
[leftLabel setAutoresizingMask:CPViewWidthSizable];
|
||||
[leftContainer addSubview:leftLabel];
|
||||
|
||||
_textView = [[CPTextView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([leftContainer bounds]) - 30, CGRectGetHeight([leftContainer bounds]) - 70)];
|
||||
[_textView setRichText:YES];
|
||||
|
||||
_textView2 = [[CPTextView alloc] initWithFrame:CGRectMake(0, 0, 1000, 200)];
|
||||
_textView2._isRichText = NO;
|
||||
[_textView setBackgroundColor:[CPColor whiteColor]];
|
||||
|
||||
_scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(15, 40, CGRectGetWidth([leftContainer bounds]) - 30, CGRectGetHeight([leftContainer bounds]) - 65)];
|
||||
[_scrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
[_scrollView setDocumentView:_textView];
|
||||
|
||||
// ATTACH THE NEW CPRULERVIEW SYSTEM
|
||||
[_scrollView setHasHorizontalRuler:YES];
|
||||
[_scrollView setRulersVisible:YES];
|
||||
[leftContainer addSubview:_scrollView];
|
||||
|
||||
// Right Container: RTF Plain-Text Source and Parser Window
|
||||
var rightLabel = [[CPTextField alloc] initWithFrame:CGRectMake(15, 10, CGRectGetWidth([rightContainer bounds]) - 30, 20)];
|
||||
[rightLabel setStringValue:@"RTF Raw Output & Source Parser Window"];
|
||||
[rightLabel setFont:[CPFont boldSystemFontOfSize:14]];
|
||||
[rightLabel setAutoresizingMask:CPViewWidthSizable];
|
||||
[rightContainer addSubview:rightLabel];
|
||||
|
||||
_textView2 = [[CPTextView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([rightContainer bounds]) - 30, CGRectGetHeight([rightContainer bounds]) - 70)];
|
||||
_textView2._isRichText = NO;
|
||||
[_textView2 setBackgroundColor:[CPColor whiteColor]];
|
||||
|
||||
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(20, 70, 520, 220)];
|
||||
var scrollView2 = [[CPScrollView alloc] initWithFrame:CGRectMake(20, 550, 1020, 220)];
|
||||
_scrollView2 = [[CPScrollView alloc] initWithFrame:CGRectMake(15, 40, CGRectGetWidth([rightContainer bounds]) - 30, CGRectGetHeight([rightContainer bounds]) - 65)];
|
||||
[_scrollView2 setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
|
||||
[_scrollView2 setDocumentView:_textView2];
|
||||
[rightContainer addSubview:_scrollView2];
|
||||
|
||||
[scrollView setDocumentView:_textView];
|
||||
[scrollView2 setDocumentView:_textView2];
|
||||
// 3. Build application Main Menu
|
||||
var mainMenu = [CPApp mainMenu];
|
||||
|
||||
[contentView addSubview: scrollView];
|
||||
[contentView addSubview: scrollView2];
|
||||
while ([mainMenu numberOfItems] > 0)
|
||||
[mainMenu removeItemAtIndex:0];
|
||||
|
||||
// build our menu
|
||||
var mainMenu = [CPApp mainMenu];
|
||||
var item = [mainMenu insertItemWithTitle:@"Edit" action:nil keyEquivalent:nil atIndex:0],
|
||||
editMenu = [[CPMenu alloc] initWithTitle:@"Edit Menu"];
|
||||
|
||||
while ([mainMenu numberOfItems] > 0)
|
||||
[mainMenu removeItemAtIndex:0];
|
||||
[editMenu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"];
|
||||
[editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
|
||||
[editMenu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"];
|
||||
[editMenu addItemWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""];
|
||||
[editMenu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
|
||||
[editMenu addItemWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"];
|
||||
[editMenu addItemWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"];
|
||||
[mainMenu setSubmenu:editMenu forItem:item];
|
||||
|
||||
var item = [mainMenu insertItemWithTitle:@"Edit" action:nil keyEquivalent:nil atIndex:0],
|
||||
editMenu = [[CPMenu alloc] initWithTitle:@"Edit Menu"];
|
||||
item = [mainMenu insertItemWithTitle:@"Format" action:nil keyEquivalent:nil atIndex:0];
|
||||
// Format Menu
|
||||
item = [mainMenu insertItemWithTitle:@"Format" action:nil keyEquivalent:nil atIndex:0];
|
||||
var formatMenu = [[CPMenu alloc] initWithTitle:@"Format Menu"];
|
||||
|
||||
[formatMenu addItemWithTitle:@"Font panel" action:@selector(orderFrontFontPanel:) keyEquivalent:@"t"];
|
||||
[formatMenu addItem:[CPMenuItem separatorItem]];
|
||||
// Styles
|
||||
[formatMenu addItemWithTitle:@"Bold" action:@selector(bold:) keyEquivalent:@"b"];
|
||||
[formatMenu addItemWithTitle:@"Italic" action:@selector(italic:) keyEquivalent:@"i"];
|
||||
[formatMenu addItemWithTitle:@"Underline" action:@selector(underline:) keyEquivalent:@"u"];
|
||||
[formatMenu addItem:[CPMenuItem separatorItem]];
|
||||
// Alignment
|
||||
[formatMenu addItemWithTitle:@"Align Left" action:@selector(alignLeft:) keyEquivalent:@"{"];
|
||||
[formatMenu addItemWithTitle:@"Center" action:@selector(alignCenter:) keyEquivalent:@"|"];
|
||||
[formatMenu addItemWithTitle:@"Align Right" action:@selector(alignRight:) keyEquivalent:@"}"];
|
||||
[formatMenu addItemWithTitle:@"Justify" action:@selector(alignJustified:) keyEquivalent:@""];
|
||||
[mainMenu setSubmenu:formatMenu forItem:item];
|
||||
|
||||
[editMenu addItemWithTitle:@"Cut" action:@selector(cut:) keyEquivalent:@"x"];
|
||||
[editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
|
||||
[editMenu addItemWithTitle:@"Paste" action:@selector(paste:) keyEquivalent:@"v"];
|
||||
[editMenu addItemWithTitle:@"Delete" action:@selector(delete:) keyEquivalent:@""];
|
||||
[editMenu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
|
||||
[editMenu addItemWithTitle:@"Undo" action:@selector(undo:) keyEquivalent:@"z"];
|
||||
[editMenu addItemWithTitle:@"Redo" action:@selector(redo:) keyEquivalent:@"Z"];
|
||||
// 4. Load Rich Sample Text content
|
||||
[_textView insertText:@"123 456 "];
|
||||
|
||||
[mainMenu setSubmenu:editMenu forItem:item];
|
||||
|
||||
item = [mainMenu insertItemWithTitle:@"Format" action:nil keyEquivalent:nil atIndex:0];
|
||||
var formatMenu = [[CPMenu alloc] initWithTitle:@"Format Menu"];
|
||||
[formatMenu addItemWithTitle:@"Font panel" action:@selector(orderFrontFontPanel:) keyEquivalent:@"f"];
|
||||
[formatMenu addItemWithTitle:@"Underline" action:@selector(underline:) keyEquivalent:@"u"];
|
||||
[mainMenu setSubmenu:formatMenu forItem:item];
|
||||
|
||||
[_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:[CPTextStorage attributedStringWithAttachment:tempImageView]];
|
||||
[_textView insertText:" 456 "];
|
||||
|
||||
var tempButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 64, 28)]
|
||||
[_textView insertText:[CPTextStorage attributedStringWithAttachment:tempButton]];
|
||||
|
||||
var centeredParagraph=[CPParagraphStyle new];
|
||||
[centeredParagraph setAlignment: CPCenterTextAlignment];
|
||||
[_textView insertText:"\n"];
|
||||
[_textView insertText:[[CPAttributedString alloc] initWithString:@"Fusce\n"
|
||||
attributes:[CPDictionary dictionaryWithObjects:[centeredParagraph, [CPFont boldFontWithName:"Arial" size:18], [CPColor redColor], [CPColor yellowColor]]
|
||||
// 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:@"My Headline with blue background\n"
|
||||
attributes:[CPDictionary dictionaryWithObjects:[centeredParagraph, [CPFont boldFontWithName:@"Arial" size:18], elegantForeground, elegantBackground]
|
||||
forKeys:[CPParagraphStyleAttributeName, CPFontAttributeName, CPForegroundColorAttributeName, CPBackgroundColorAttributeName]]]];
|
||||
|
||||
[_textView insertText:"\n"];
|
||||
[_textView insertText:[[CPAttributedString alloc] initWithString:@"Yellow\n"
|
||||
attributes:[CPDictionary dictionaryWithObjects:[[CPFont boldFontWithName:"Arial" size:25], [CPColor yellowColor]]
|
||||
forKeys:[CPFontAttributeName, CPBackgroundColorAttributeName]]]];
|
||||
[theWindow orderFront:self];
|
||||
// [_textView setEditable:NO];
|
||||
[CPMenu setMenuBarVisible:YES];
|
||||
// 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], showcaseForeground, showcaseBackground]
|
||||
forKeys:[CPFontAttributeName, CPForegroundColorAttributeName, CPBackgroundColorAttributeName]]]];
|
||||
|
||||
console.log([[CPFont systemFontOfSize:12] cssString])
|
||||
var context = document.createElement("canvas").getContext("2d");
|
||||
context.font = '12px Arial, sans-serif';
|
||||
var testingText = 'A A A A A A A A';
|
||||
console.log(ROUND(context.measureText(testingText).width));
|
||||
console.log(ROUND([CPPlatformString sizeOfString:testingText withFont:[CPFont systemFontOfSize:12] forWidth:NULL].width));
|
||||
// DEMONSTRATION OF CPRULERVIEW TAB MARKERS
|
||||
var tabParagraph = [[CPParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
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:@"\tTab1\tTab2\tTab3\n\tLeftAlign\tCenterAlign\tRightAlign\n"
|
||||
attributes:[CPDictionary dictionaryWithObject:tabParagraph forKey:CPParagraphStyleAttributeName]]];
|
||||
|
||||
// DEMONSTRATION OF INDENTATION MARKERS
|
||||
var indentParagraph = [[CPParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
[indentParagraph setFirstLineHeadIndent:30.0];
|
||||
[indentParagraph setHeadIndent:50.0];
|
||||
[indentParagraph setTailIndent:-30.0];
|
||||
|
||||
[_textView insertText:@"\n"];
|
||||
[_textView insertText:[[CPAttributedString alloc] initWithString:@"This paragraph has a first-line indent of 30pt, a head indent of 50pt, and a tail indent of -30pt. Check the horizontal ruler above to see how the indent markers align with this paragraph, and adjust them directly!\n"
|
||||
attributes:[CPDictionary dictionaryWithObject:indentParagraph forKey:CPParagraphStyleAttributeName]]];
|
||||
|
||||
[theWindow orderFront:self];
|
||||
[CPMenu setMenuBarVisible:YES];
|
||||
}
|
||||
|
||||
- (void) makeRTF:(id)sender
|
||||
- (void)makeRTF:(id)sender
|
||||
{
|
||||
[_textView2 setString: [_CPRTFProducer produceRTF:[_textView textStorage] documentAttributes: @{}] ];
|
||||
var tc = [_CPRTFParser new];
|
||||
|
||||
@@ -164,10 +164,20 @@ tableTestDragType = @"CPTableViewTestDragType";
|
||||
|
||||
- (void)removeColumn:(id)sender
|
||||
{
|
||||
// if ([[tableView tableColumns] containsObject:randomColumn])
|
||||
[tableView removeTableColumn:randomColumn];
|
||||
//else
|
||||
// [tableView addTableColumn:randomColumn];
|
||||
var columns = [tableView tableColumns];
|
||||
|
||||
// Check if we still have columns left to remove
|
||||
if (columns && [columns count] > 0)
|
||||
{
|
||||
var columnToRemove = [columns lastObject];
|
||||
[tableView removeTableColumn:columnToRemove];
|
||||
|
||||
CPLog.debug(@"Removed column with identifier: " + [columnToRemove identifier]);
|
||||
}
|
||||
else
|
||||
{
|
||||
CPLog.debug(@"No more columns to remove!");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)addColumn:(id)sender
|
||||
|
||||
Reference in New Issue
Block a user