mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-10-24 17:18:54 +00:00
nib2cib ignored the text view's Uses Ruler setting, and CPTextView, CPRulerView and CPRulerMarker archived none of their ruler state, so a ruler could not be configured in Interface Builder or survive archiving. * CPScrollView.j: Set itself as the scroll view of decoded rulers. * CPTextView/CPRulerMarker.j: Archive the marker's ruler, location, image and image origin, movability, removability and represented object. * CPTextView/CPRulerView.j: Archive the ruler's orientation, client view, accessory view, markers, thicknesses, origin offset and measurement units. * CPTextView/CPTextView.j: Archive usesRuler. * Tools/nib2cib/NSTextView.j, Tools/nib2cib/NSTextViewSharedData.j: Read the Uses Ruler flag. * Tests/AppKit/CPRulerViewTest.j: Add tests for ruler layout, measurement units, archiving and the Uses Ruler setting. * Tests/Manual/TextEditor/Resources/MainMenu.xib, Resources/MainMenu.cib: Add the standard Format menu, whose Text submenu shows and hides the ruler. * Tests/Manual/TextEditor/Resources/DocumentWindow.xib, Resources/DocumentWindow.cib: Rebuild the cib with the ruler setting, and adjust the window's initial position.
505 lines
15 KiB
Plaintext
505 lines
15 KiB
Plaintext
/*
|
|
* CPRulerMarker.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 "CGGeometry.j"
|
|
@import "CPEvent.j"
|
|
@import "CPGraphicsContext.j"
|
|
@import "CPImage.j"
|
|
|
|
@class CPRulerView
|
|
|
|
@global CPApp
|
|
@global CPHorizontalRuler
|
|
|
|
|
|
/*!
|
|
Distance, in points, that the pointer must move beyond the ruler before a removable
|
|
marker is removed when it is released.
|
|
*/
|
|
var CPRulerMarkerRemovalDistance = 0.0;
|
|
|
|
/*!
|
|
@ingroup appkit
|
|
@class CPRulerMarker
|
|
|
|
A CPRulerMarker shows a location on a CPRulerView, such as a tab stop or a paragraph
|
|
indent. The marker is drawn by its ruler view as an image, positioned so that the
|
|
image's \c imageOrigin lies at the marker's location on the ruler's baseline. The
|
|
marker's location is expressed in the coordinate system of the ruler's client view.
|
|
|
|
The user can drag a movable marker along the ruler, and drag a removable marker off the
|
|
ruler to remove it. The ruler's client view is consulted through the CPRulerView client
|
|
methods before and after each change.
|
|
*/
|
|
@implementation CPRulerMarker : CPObject
|
|
{
|
|
CPRulerView _ruler;
|
|
float _markerLocation;
|
|
CPImage _image;
|
|
CGPoint _imageOrigin;
|
|
BOOL _isMovable;
|
|
BOOL _isRemovable;
|
|
BOOL _isDragging;
|
|
BOOL _isOffRuler;
|
|
id _representedObject;
|
|
|
|
float _trackingStartLocation;
|
|
float _trackingStartPoint;
|
|
float _trackingStartCrossPoint;
|
|
float _trackingCrossOffset;
|
|
BOOL _trackingIsAdding;
|
|
BOOL _trackingMoved;
|
|
}
|
|
|
|
/*!
|
|
Initializes a marker for \c aRulerView at \c aLocation, drawn with \c anImage. The
|
|
marker is associated with the ruler view, but not added to it. A new marker is movable
|
|
and not removable.
|
|
@param aRulerView the ruler view the marker belongs to
|
|
@param aLocation the marker's location, in the client view's coordinate system
|
|
@param anImage the image drawn for the marker
|
|
@param anImageOrigin the point in the image, measured from its top left corner, placed
|
|
at the marker's location on the ruler's baseline
|
|
@return the initialized marker
|
|
*/
|
|
- (id)initWithRulerView:(CPRulerView)aRulerView markerLocation:(float)aLocation image:(CPImage)anImage imageOrigin:(CGPoint)anImageOrigin
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_ruler = aRulerView;
|
|
_markerLocation = aLocation;
|
|
_image = anImage;
|
|
_imageOrigin = CGPointMakeCopy(anImageOrigin);
|
|
_isMovable = YES;
|
|
_isRemovable = NO;
|
|
_isDragging = NO;
|
|
_isOffRuler = NO;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
// MARK: Getting the Ruler View
|
|
|
|
/*!
|
|
Returns the ruler view the marker belongs to.
|
|
*/
|
|
- (CPRulerView)ruler
|
|
{
|
|
return _ruler;
|
|
}
|
|
|
|
// MARK: Setting the Image
|
|
|
|
/*!
|
|
Returns the image drawn for the marker.
|
|
*/
|
|
- (CPImage)image
|
|
{
|
|
return _image;
|
|
}
|
|
|
|
/*!
|
|
Sets the image drawn for the marker.
|
|
@param anImage the new image
|
|
*/
|
|
- (void)setImage:(CPImage)anImage
|
|
{
|
|
if (_image === anImage)
|
|
return;
|
|
|
|
_image = anImage;
|
|
[_ruler setNeedsDisplay:YES];
|
|
}
|
|
|
|
/*!
|
|
Returns the point in the image, measured from its top left corner, that is placed at
|
|
the marker's location on the ruler's baseline.
|
|
*/
|
|
- (CGPoint)imageOrigin
|
|
{
|
|
return _imageOrigin;
|
|
}
|
|
|
|
/*!
|
|
Sets the point in the image, measured from its top left corner, that is placed at the
|
|
marker's location on the ruler's baseline.
|
|
@param aPoint the new image origin
|
|
*/
|
|
- (void)setImageOrigin:(CGPoint)aPoint
|
|
{
|
|
_imageOrigin = CGPointMakeCopy(aPoint);
|
|
[_ruler setNeedsDisplay:YES];
|
|
}
|
|
|
|
/*!
|
|
Returns the rectangle that the marker's image occupies, in the ruler view's coordinate
|
|
system.
|
|
*/
|
|
- (CGRect)imageRectInRuler
|
|
{
|
|
var imageSize = [_image size] || CGSizeMakeZero(),
|
|
point = [_ruler _rulerPointForLocation:_markerLocation],
|
|
baseline = [_ruler baselineLocation];
|
|
|
|
// Whole-point positions keep every marker image sharp and identical, wherever it lies on the ruler.
|
|
// While the user drags the marker outward, it follows the pointer across the ruler.
|
|
var crossPosition = baseline + (_isDragging ? _trackingCrossOffset : 0.0);
|
|
|
|
if ([_ruler orientation] === CPHorizontalRuler)
|
|
return CGRectMake(ROUND(point - _imageOrigin.x), ROUND(crossPosition - _imageOrigin.y), imageSize.width, imageSize.height);
|
|
|
|
return CGRectMake(ROUND(crossPosition - _imageOrigin.x), ROUND(point - _imageOrigin.y), imageSize.width, imageSize.height);
|
|
}
|
|
|
|
/*!
|
|
Returns the extent of the marker's image on the marker side of the ruler's baseline:
|
|
above the baseline on a horizontal ruler, and to its left on a vertical ruler.
|
|
*/
|
|
- (float)thicknessRequiredInRuler
|
|
{
|
|
return [_ruler orientation] === CPHorizontalRuler ? _imageOrigin.y : _imageOrigin.x;
|
|
}
|
|
|
|
// MARK: Setting Movability
|
|
|
|
/*!
|
|
Returns \c YES if the user can drag the marker along the ruler.
|
|
*/
|
|
- (BOOL)isMovable
|
|
{
|
|
return _isMovable;
|
|
}
|
|
|
|
/*!
|
|
Sets whether the user can drag the marker along the ruler.
|
|
@param shouldBeMovable \c YES to let the user move the marker
|
|
*/
|
|
- (void)setMovable:(BOOL)shouldBeMovable
|
|
{
|
|
_isMovable = shouldBeMovable;
|
|
}
|
|
|
|
/*!
|
|
Returns \c YES if the user can remove the marker by dragging it off the ruler.
|
|
*/
|
|
- (BOOL)isRemovable
|
|
{
|
|
return _isRemovable;
|
|
}
|
|
|
|
/*!
|
|
Sets whether the user can remove the marker by dragging it off the ruler.
|
|
@param shouldBeRemovable \c YES to let the user remove the marker
|
|
*/
|
|
- (void)setRemovable:(BOOL)shouldBeRemovable
|
|
{
|
|
_isRemovable = shouldBeRemovable;
|
|
}
|
|
|
|
// MARK: Setting the Location
|
|
|
|
/*!
|
|
Returns the marker's location, in the client view's coordinate system.
|
|
*/
|
|
- (float)markerLocation
|
|
{
|
|
return _markerLocation;
|
|
}
|
|
|
|
/*!
|
|
Sets the marker's location, in the client view's coordinate system.
|
|
@param aLocation the new location
|
|
*/
|
|
- (void)setMarkerLocation:(float)aLocation
|
|
{
|
|
if (_markerLocation === aLocation)
|
|
return;
|
|
|
|
_markerLocation = aLocation;
|
|
[_ruler setNeedsDisplay:YES];
|
|
}
|
|
|
|
// MARK: Setting the Represented Object
|
|
|
|
/*!
|
|
Returns the object the marker represents, such as a CPTextTab.
|
|
*/
|
|
- (id)representedObject
|
|
{
|
|
return _representedObject;
|
|
}
|
|
|
|
/*!
|
|
Sets the object the marker represents.
|
|
@param anObject the represented object
|
|
*/
|
|
- (void)setRepresentedObject:(id)anObject
|
|
{
|
|
_representedObject = anObject;
|
|
}
|
|
|
|
// MARK: Drawing and Event Handling
|
|
|
|
/*!
|
|
Returns \c YES while the user is dragging the marker.
|
|
*/
|
|
- (BOOL)isDragging
|
|
{
|
|
return _isDragging;
|
|
}
|
|
|
|
/*!
|
|
Draws the marker's image if it lies within \c aRect, in the ruler view's coordinate
|
|
system. While a removable marker is dragged off the ruler, it is drawn faded.
|
|
@param aRect the rectangle being drawn
|
|
*/
|
|
- (void)drawRect:(CGRect)aRect
|
|
{
|
|
if (!_image || [_image loadStatus] !== CPImageLoadStatusCompleted)
|
|
return;
|
|
|
|
var imageRect = [self imageRectInRuler];
|
|
|
|
if (!CGRectIntersectsRect(imageRect, aRect))
|
|
return;
|
|
|
|
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
|
|
|
CGContextSaveGState(context);
|
|
|
|
// Off the ruler's outer edge the marker follows the pointer and is clipped by the ruler.
|
|
// Off its inner edge it stays on its line, so it is drawn faded instead.
|
|
if (_isOffRuler && _trackingCrossOffset === 0.0)
|
|
CGContextSetAlpha(context, 0.4);
|
|
|
|
CGContextDrawImage(context, imageRect, _image);
|
|
CGContextRestoreGState(context);
|
|
}
|
|
|
|
/*!
|
|
Starts tracking the user's manipulation of the marker, from the mouse-down event
|
|
\c anEvent. Tracking continues until the mouse button is released.
|
|
|
|
If \c isAdding is \c YES, the marker is not yet in the ruler: it is added when the user
|
|
releases it over the ruler, if the client view agrees. Otherwise the marker is moved,
|
|
and removed if it is removable and released away from the ruler.
|
|
@param anEvent the mouse-down event that started the manipulation
|
|
@param isAdding \c YES if the marker is being added to the ruler
|
|
@return \c YES if tracking started, or \c NO if the marker cannot be moved or removed,
|
|
or the client view refused
|
|
*/
|
|
- (BOOL)trackMouse:(CPEvent)anEvent adding:(BOOL)isAdding
|
|
{
|
|
if (!isAdding && !_isMovable && !_isRemovable)
|
|
return NO;
|
|
|
|
if (isAdding && ![_ruler _clientShouldAddMarker:self])
|
|
return NO;
|
|
|
|
if (!isAdding && _isMovable && ![_ruler _clientShouldMoveMarker:self])
|
|
return NO;
|
|
|
|
_trackingIsAdding = isAdding;
|
|
_trackingMoved = NO;
|
|
_trackingStartLocation = _markerLocation;
|
|
_trackingStartPoint = [self _axisPointForEvent:anEvent];
|
|
_trackingStartCrossPoint = [self _crossPointForEvent:anEvent];
|
|
_trackingCrossOffset = 0.0;
|
|
_isDragging = YES;
|
|
_isOffRuler = NO;
|
|
|
|
if (isAdding)
|
|
{
|
|
_markerLocation = [_ruler _clientWillAddMarker:self atLocation:_markerLocation];
|
|
[_ruler _setTrackedMarker:self];
|
|
}
|
|
|
|
[_ruler setNeedsDisplay:YES];
|
|
|
|
[CPApp setTarget:self
|
|
selector:@selector(_trackMouseWithEvent:)
|
|
forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask
|
|
untilDate:nil
|
|
inMode:nil
|
|
dequeue:YES];
|
|
|
|
return YES;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (void)_trackMouseWithEvent:(CPEvent)anEvent
|
|
{
|
|
var type = [anEvent type];
|
|
|
|
if (type === CPLeftMouseDragged)
|
|
{
|
|
_trackingMoved = YES;
|
|
_isOffRuler = [self _isEventOffRuler:anEvent] && (_trackingIsAdding || _isRemovable);
|
|
|
|
// As in Cocoa, the marker stays on its line while the pointer is on the ruler, and follows
|
|
// the pointer out over the ruler's outer edge, where the ruler clips it.
|
|
_trackingCrossOffset = (_trackingIsAdding || _isRemovable) ? MIN(0.0, [self _crossPointForEvent:anEvent] - _trackingStartCrossPoint) : 0.0;
|
|
|
|
if (_trackingIsAdding || _isMovable)
|
|
{
|
|
var proposedLocation = _trackingStartLocation + [self _axisPointForEvent:anEvent] - _trackingStartPoint;
|
|
|
|
_markerLocation = _trackingIsAdding ? [_ruler _clientWillAddMarker:self atLocation:proposedLocation]
|
|
: [_ruler _clientWillMoveMarker:self toLocation:proposedLocation];
|
|
}
|
|
|
|
[_ruler setNeedsDisplay:YES];
|
|
|
|
[CPApp setTarget:self
|
|
selector:@selector(_trackMouseWithEvent:)
|
|
forNextEventMatchingMask:CPLeftMouseDraggedMask | CPLeftMouseUpMask
|
|
untilDate:nil
|
|
inMode:nil
|
|
dequeue:YES];
|
|
|
|
return;
|
|
}
|
|
|
|
if (type !== CPLeftMouseUp)
|
|
return;
|
|
|
|
var wasOffRuler = _isOffRuler;
|
|
|
|
_trackingCrossOffset = 0.0;
|
|
|
|
_isDragging = NO;
|
|
_isOffRuler = NO;
|
|
|
|
if (_trackingIsAdding)
|
|
{
|
|
[_ruler _setTrackedMarker:nil];
|
|
|
|
if (!wasOffRuler)
|
|
{
|
|
[_ruler addMarker:self];
|
|
[_ruler _clientDidAddMarker:self];
|
|
}
|
|
}
|
|
else if (wasOffRuler && [_ruler _clientShouldRemoveMarker:self])
|
|
{
|
|
[_ruler removeMarker:self];
|
|
[_ruler _clientDidRemoveMarker:self];
|
|
}
|
|
else if (_trackingMoved)
|
|
{
|
|
if (wasOffRuler)
|
|
_markerLocation = _trackingStartLocation;
|
|
|
|
if (_markerLocation !== _trackingStartLocation)
|
|
[_ruler _clientDidMoveMarker:self];
|
|
}
|
|
|
|
[_ruler setNeedsDisplay:YES];
|
|
}
|
|
|
|
/* @ignore */
|
|
- (float)_crossPointForEvent:(CPEvent)anEvent
|
|
{
|
|
var point = [_ruler convertPoint:[anEvent locationInWindow] fromView:nil];
|
|
|
|
return [_ruler orientation] === CPHorizontalRuler ? point.y : point.x;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (float)_axisPointForEvent:(CPEvent)anEvent
|
|
{
|
|
var point = [_ruler convertPoint:[anEvent locationInWindow] fromView:nil];
|
|
|
|
return [_ruler orientation] === CPHorizontalRuler ? point.x : point.y;
|
|
}
|
|
|
|
/* @ignore */
|
|
- (BOOL)_isEventOffRuler:(CPEvent)anEvent
|
|
{
|
|
// Only distance across the ruler counts. Dragging past either end of the ruler moves the marker.
|
|
var point = [_ruler convertPoint:[anEvent locationInWindow] fromView:nil],
|
|
bounds = [_ruler bounds];
|
|
|
|
if ([_ruler orientation] === CPHorizontalRuler)
|
|
return point.y < CGRectGetMinY(bounds) - CPRulerMarkerRemovalDistance || point.y > CGRectGetMaxY(bounds) + CPRulerMarkerRemovalDistance;
|
|
|
|
return point.x < CGRectGetMinX(bounds) - CPRulerMarkerRemovalDistance || point.x > CGRectGetMaxX(bounds) + CPRulerMarkerRemovalDistance;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
var CPRulerMarkerRulerKey = @"CPRulerMarkerRulerKey",
|
|
CPRulerMarkerLocationKey = @"CPRulerMarkerLocationKey",
|
|
CPRulerMarkerImageKey = @"CPRulerMarkerImageKey",
|
|
CPRulerMarkerImageOriginKey = @"CPRulerMarkerImageOriginKey",
|
|
CPRulerMarkerMovableKey = @"CPRulerMarkerMovableKey",
|
|
CPRulerMarkerRemovableKey = @"CPRulerMarkerRemovableKey",
|
|
CPRulerMarkerRepresentedObjectKey = @"CPRulerMarkerRepresentedObjectKey";
|
|
|
|
@implementation CPRulerMarker (CPCoding)
|
|
|
|
/*!
|
|
Initializes a marker from an archive.
|
|
*/
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
self = [super init];
|
|
|
|
if (self)
|
|
{
|
|
_ruler = [aCoder decodeObjectForKey:CPRulerMarkerRulerKey];
|
|
_markerLocation = [aCoder decodeFloatForKey:CPRulerMarkerLocationKey];
|
|
_image = [aCoder decodeObjectForKey:CPRulerMarkerImageKey];
|
|
_imageOrigin = [aCoder decodePointForKey:CPRulerMarkerImageOriginKey];
|
|
_isMovable = [aCoder decodeBoolForKey:CPRulerMarkerMovableKey];
|
|
_isRemovable = [aCoder decodeBoolForKey:CPRulerMarkerRemovableKey];
|
|
_representedObject = [aCoder decodeObjectForKey:CPRulerMarkerRepresentedObjectKey];
|
|
_isDragging = NO;
|
|
_isOffRuler = NO;
|
|
_trackingCrossOffset = 0.0;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
/*!
|
|
Archives the marker. The ruler is archived conditionally.
|
|
*/
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
[aCoder encodeConditionalObject:_ruler forKey:CPRulerMarkerRulerKey];
|
|
[aCoder encodeFloat:_markerLocation forKey:CPRulerMarkerLocationKey];
|
|
[aCoder encodeObject:_image forKey:CPRulerMarkerImageKey];
|
|
[aCoder encodePoint:_imageOrigin forKey:CPRulerMarkerImageOriginKey];
|
|
[aCoder encodeBool:_isMovable forKey:CPRulerMarkerMovableKey];
|
|
[aCoder encodeBool:_isRemovable forKey:CPRulerMarkerRemovableKey];
|
|
[aCoder encodeObject:_representedObject forKey:CPRulerMarkerRepresentedObjectKey];
|
|
}
|
|
|
|
@end
|