mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-29 06:57:03 +00:00
new: constrain tab stop marker dragging to preceding text boundary
This commit is contained in:
@@ -377,13 +377,17 @@ CPRulerOrientationVertical = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Allow client view to constrain initial placement
|
||||
var client = [self clientView];
|
||||
if (client && [client respondsToSelector:@selector(rulerView:willAddMarker:atLocation:)])
|
||||
rulerLocation = [client rulerView:self willAddMarker:nil atLocation:rulerLocation];
|
||||
|
||||
var newMarker = [[CPRulerMarker alloc] initWithRulerView:self
|
||||
markerLocation:rulerLocation
|
||||
imageValue:rulerLocation
|
||||
representedObject:nil];
|
||||
[self addMarker:newMarker];
|
||||
|
||||
var client = [self clientView];
|
||||
if (client && [client respondsToSelector:@selector(rulerView:didAddMarker:)])
|
||||
[client rulerView:self didAddMarker:newMarker];
|
||||
|
||||
@@ -405,6 +409,12 @@ CPRulerOrientationVertical = 1;
|
||||
var delta = isHorizontal ? (localPoint.x - _dragStartPoint.x) : (localPoint.y - _dragStartPoint.y),
|
||||
newLocation = Math.max(0.0, _dragStartLocation + delta);
|
||||
|
||||
// Constrain marker location to last possible position
|
||||
var client = [self clientView];
|
||||
|
||||
if (client && [client respondsToSelector:@selector(rulerView:willMoveMarker:toLocation:)])
|
||||
newLocation = [client rulerView:self willMoveMarker:_draggingMarker toLocation:newLocation];
|
||||
|
||||
[_draggingMarker setImageValue:newLocation];
|
||||
[self _positionMarker:_draggingMarker];
|
||||
|
||||
@@ -426,7 +436,6 @@ CPRulerOrientationVertical = 1;
|
||||
[[_draggingMarker label] setTextColor:[CPColor colorWithWhite:0.2 alpha:1.0]];
|
||||
}
|
||||
|
||||
var client = [self clientView];
|
||||
if (client && [client respondsToSelector:@selector(rulerView:didMoveMarker:)])
|
||||
[client rulerView:self didMoveMarker:_draggingMarker];
|
||||
}
|
||||
|
||||
@@ -2729,6 +2729,96 @@ var compareTabStops = function(obj1, obj2, context) {
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (float)rulerView:(CPRulerView)aRulerView willMoveMarker:(CPRulerMarker)aMarker toLocation:(float)proposedLocation
|
||||
{
|
||||
var rep = [aMarker representedObject];
|
||||
|
||||
// Indent markers can be dragged anywhere >= 0
|
||||
if (rep === @"CPFirstLineIndent" || rep === @"CPHeadIndent" || rep === @"CPTailIndent")
|
||||
return Math.max(0.0, proposedLocation);
|
||||
|
||||
var textLength = [_textStorage length];
|
||||
if (textLength === 0)
|
||||
return Math.max(0.0, proposedLocation);
|
||||
|
||||
var selectedRange = [self selectedRange],
|
||||
targetRange = selectedRange;
|
||||
|
||||
if (targetRange.length === 0)
|
||||
targetRange = [self selectionRangeForProposedRange:CPMakeRange(targetRange.location, 0) granularity:CPSelectByParagraph];
|
||||
|
||||
if (targetRange.length === 0)
|
||||
return Math.max(0.0, proposedLocation);
|
||||
|
||||
[_layoutManager _validateLayoutAndGlyphs];
|
||||
|
||||
var theString = [_textStorage string],
|
||||
minLocation = 0.0,
|
||||
currentParagraphStyle = [_textStorage attribute:CPParagraphStyleAttributeName atIndex:targetRange.location effectiveRange:nil] || [CPParagraphStyle defaultParagraphStyle],
|
||||
tabStops = [currentParagraphStyle tabStops] || [];
|
||||
|
||||
// Find which tab stop index this marker represents
|
||||
var targetTabIndex = -1;
|
||||
for (var i = 0; i < [tabStops count]; i++)
|
||||
{
|
||||
var tab = [tabStops objectAtIndex:i];
|
||||
if (tab === rep || ([tab location] === [rep location] && [tab alignment] === [rep alignment]))
|
||||
{
|
||||
targetTabIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var start = targetRange.location,
|
||||
end = CPMaxRange(targetRange),
|
||||
tabCountInLine = 0,
|
||||
lineStart = start;
|
||||
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
var charCode = theString.charCodeAt(i);
|
||||
if (charCode === 10 || charCode === 13)
|
||||
{
|
||||
tabCountInLine = 0;
|
||||
lineStart = i + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (charCode === 9) // '\t'
|
||||
{
|
||||
// If this tab corresponds to the marker being dragged
|
||||
if (tabCountInLine === targetTabIndex || targetTabIndex === -1)
|
||||
{
|
||||
var precedingX = 0.0;
|
||||
if (i === lineStart)
|
||||
{
|
||||
var isFirstLine = (lineStart === 0 || theString.charCodeAt(lineStart - 1) === 10 || theString.charCodeAt(lineStart - 1) === 13);
|
||||
precedingX = isFirstLine ? [currentParagraphStyle firstLineHeadIndent] : [currentParagraphStyle headIndent];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Find right edge of glyph immediately preceding the tab
|
||||
var glyphRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(i - 1, 1) inTextContainer:_textContainer];
|
||||
precedingX = CGRectGetMaxX(glyphRect);
|
||||
}
|
||||
|
||||
// Minimum safety spacer (e.g. + 2px after the preceding glyph)
|
||||
var limit = precedingX + 2.0;
|
||||
if (limit > minLocation)
|
||||
minLocation = limit;
|
||||
}
|
||||
tabCountInLine++;
|
||||
}
|
||||
}
|
||||
|
||||
return Math.max(minLocation, proposedLocation);
|
||||
}
|
||||
|
||||
- (float)rulerView:(CPRulerView)aRulerView willAddMarker:(CPRulerMarker)aMarker atLocation:(float)proposedLocation
|
||||
{
|
||||
return [self rulerView:aRulerView willMoveMarker:aMarker toLocation:proposedLocation];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CPTextView (CPTextViewDelegate)
|
||||
|
||||
@@ -138,38 +138,30 @@ var CPSystemTypesetterFactory,
|
||||
return [_layoutManager textContainers];
|
||||
}
|
||||
|
||||
// Retrieves correct CPTextTab stop accounting for CPArray properties
|
||||
// Retrieves correct CPTextTab stop accounting for custom stops and default intervals
|
||||
- (CPTextTab)textTabForWidth:(double)aWidth writingDirection:(CPWritingDirection)direction
|
||||
{
|
||||
var tabStops = [_currentParagraph tabStops],
|
||||
defaultInterval = [_currentParagraph defaultTabInterval] || 28.0;
|
||||
|
||||
// Calculate the immediate next standard tab stop location based on defaultInterval
|
||||
var nextDefaultLocation = (Math.floor(aWidth / defaultInterval) + 1) * defaultInterval;
|
||||
var l = tabStops ? [tabStops count] : 0;
|
||||
|
||||
// Search for the first custom tab stop strictly greater than aWidth
|
||||
var nextCustomTab = nil;
|
||||
|
||||
if (tabStops)
|
||||
// 1. If custom tab stops exist ahead of current position, use the first one encountered
|
||||
if (l > 0)
|
||||
{
|
||||
var l = [tabStops count];
|
||||
for (var i = 0; i < l; i++)
|
||||
{
|
||||
var tab = [tabStops objectAtIndex:i];
|
||||
|
||||
if ([tab location] > aWidth)
|
||||
{
|
||||
nextCustomTab = tab;
|
||||
break;
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
}
|
||||
|
||||
// If a custom tab stop exists and is closer than (or at) the next default interval, use it
|
||||
if (nextCustomTab && [nextCustomTab location] <= nextDefaultLocation)
|
||||
return nextCustomTab;
|
||||
// 2. Otherwise (or when all custom tab stops are behind the text), advance to the next default interval
|
||||
var nextLocation = (Math.floor(aWidth / defaultInterval) + 1) * defaultInterval;
|
||||
|
||||
// Otherwise, snap to the immediate next regular tab interval
|
||||
return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextDefaultLocation];
|
||||
return [[CPTextTab alloc] initWithType:CPLeftTextAlignment location:nextLocation];
|
||||
}
|
||||
|
||||
- (BOOL)_flushRange:(CPRange)lineRange
|
||||
@@ -209,7 +201,7 @@ var CPSystemTypesetterFactory,
|
||||
[_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange];
|
||||
[_layoutManager _setAdvancements:advancements forGlyphRange:lineRange];
|
||||
|
||||
//fix the _lineFragments when fontsizes differ
|
||||
// fix the _lineFragments when fontsizes differ
|
||||
var l = _lineFragments.length;
|
||||
|
||||
for (var i = 0 ; i < l ; i++)
|
||||
@@ -255,7 +247,7 @@ var CPSystemTypesetterFactory,
|
||||
isTabStop = NO,
|
||||
isAttachment = NO,
|
||||
isWordWrapped = NO,
|
||||
numberOfGlyphs= [_textStorage length],
|
||||
numberOfGlyphs = [_textStorage length],
|
||||
leading,
|
||||
numLines = 0,
|
||||
theString = [_textStorage string],
|
||||
@@ -297,7 +289,7 @@ var CPSystemTypesetterFactory,
|
||||
|
||||
for (; numLines != maxNumLines && glyphIndex < numberOfGlyphs; glyphIndex++)
|
||||
{
|
||||
// check whether there any change in the attributes from here on
|
||||
// check whether there is any change in the attributes from here on
|
||||
if (!CPLocationInRange(glyphIndex, _attributesRange))
|
||||
{
|
||||
_currentAttributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:_attributesRange];
|
||||
@@ -414,10 +406,10 @@ var CPSystemTypesetterFactory,
|
||||
// We are processing characters, so we are no longer at the start of a physical line
|
||||
isStartOfPhysicalLine = NO;
|
||||
|
||||
var currentCharCode = theString.charCodeAt(glyphIndex), // use pure javascript methods for performance reasons
|
||||
var currentCharCode = theString.charCodeAt(glyphIndex),
|
||||
rangeWidth = [theString.substr(measuringRange.location, measuringRange.length) sizeWithFont:currentFont inWidth:NULL].width + currentAnchor;
|
||||
|
||||
switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char.
|
||||
switch (currentCharCode)
|
||||
{
|
||||
case CPAttachmentCharacter:
|
||||
{
|
||||
@@ -449,6 +441,7 @@ var CPSystemTypesetterFactory,
|
||||
}
|
||||
case 9: // '\t'
|
||||
{
|
||||
// Measure against the actual text position before the tab stop
|
||||
var nextTab = [self textTabForWidth:prevRangeWidth + lineOrigin.x writingDirection:0];
|
||||
|
||||
isTabStop = YES;
|
||||
@@ -506,10 +499,6 @@ var CPSystemTypesetterFactory,
|
||||
wrapRange._height = _lineHeight;
|
||||
wrapRange._base = _lineBase;
|
||||
|
||||
// Optimization: Start measuring from the next character to avoid O(n^2)
|
||||
// string width calculation within a line since spaces do not carry ligatures or kerning.
|
||||
// Only reset the measuring range if the next character is NOT another space.
|
||||
// This prevents compounded subpixel rounding errors with contiguous spaces.
|
||||
if (theString.charCodeAt(glyphIndex + 1) !== 32)
|
||||
{
|
||||
currentAnchor = rangeWidth;
|
||||
@@ -539,7 +528,7 @@ var CPSystemTypesetterFactory,
|
||||
|
||||
isNewline = YES;
|
||||
isWordWrapped = YES;
|
||||
glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character
|
||||
glyphIndex = CPMaxRange(lineRange) - 1;
|
||||
}
|
||||
|
||||
if (isNewline || isTabStop || isAttachment)
|
||||
@@ -571,8 +560,6 @@ var CPSystemTypesetterFactory,
|
||||
containerSizeHeight = containerSize.height;
|
||||
}
|
||||
|
||||
// If this is a soft wrap (isWordWrapped), next line gets headIndent.
|
||||
// If it was a paragraph return, it gets firstLineHeadIndent.
|
||||
isFirstLineOfParagraph = !isWordWrapped;
|
||||
lineOrigin.x = isFirstLineOfParagraph ? [_currentParagraph firstLineHeadIndent] : [_currentParagraph headIndent];
|
||||
|
||||
@@ -598,7 +585,7 @@ var CPSystemTypesetterFactory,
|
||||
}
|
||||
}
|
||||
|
||||
// this is to "flush" the remaining characters
|
||||
// Flush remaining characters
|
||||
if (lineRange.length)
|
||||
[self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user