Files
2017-04-26 14:35:47 -04:00

221 lines
6.0 KiB
Objective-C

//
// MPEditorView.m
// MacDown
//
// Created by Tzu-ping Chung on 30/8.
// Copyright (c) 2014 Tzu-ping Chung . All rights reserved.
//
#import "MPEditorView.h"
NS_INLINE BOOL MPAreRectsEqual(NSRect r1, NSRect r2)
{
return (r1.origin.x == r2.origin.x && r1.origin.y == r2.origin.y
&& r1.size.width == r2.size.width
&& r1.size.height == r2.size.height);
}
@interface MPEditorView ()
@property NSRect contentRect;
@property CGFloat trailingHeight;
@end
@implementation MPEditorView
#pragma mark - Accessors
@synthesize contentRect = _contentRect;
@synthesize scrollsPastEnd = _scrollsPastEnd;
- (BOOL)scrollsPastEnd
{
@synchronized(self) {
return _scrollsPastEnd;
}
}
- (void)awakeFromNib {
[self registerForDraggedTypes:[NSArray arrayWithObjects: NSDragPboard, nil]];
[super awakeFromNib];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if ([pboard canReadItemWithDataConformingToTypes:[NSArray arrayWithObjects:@"public.jpeg", nil]]) {
if (sourceDragMask & NSDragOperationLink) {
return NSDragOperationLink;
} else if (sourceDragMask & NSDragOperationCopy) {
return NSDragOperationCopy;
}
}
return NSDragOperationNone;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
/* Load data of file. */
NSError *error;
NSData *fileData = [NSData dataWithContentsOfFile: files[0]
options: NSMappedRead
error: &error];
if (!error) {
// convert to base64 representation
NSString *dataString = [fileData base64Encoding];
// insert into text.
NSInteger insertionPoint = [[[self selectedRanges] objectAtIndex:0] rangeValue].location;
[self setString:[NSString stringWithFormat:@"%@![](data:image/jpeg;base64,%@)%@", [[self string] substringToIndex:insertionPoint], dataString, [[self string] substringFromIndex:insertionPoint]]];
[self didChangeText];
} else {
return NO;
}
}
return YES;
}
- (void)setScrollsPastEnd:(BOOL)scrollsPastEnd
{
@synchronized(self) {
_scrollsPastEnd = scrollsPastEnd;
if (scrollsPastEnd)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateContentGeometry];
}];
}
else
{
// Clears contentRect to fallback to self.frame.
self.contentRect = NSZeroRect;
}
}
}
- (NSRect)contentRect
{
@synchronized(self) {
if (MPAreRectsEqual(_contentRect, NSZeroRect))
return self.frame;
return _contentRect;
}
}
- (void)setContentRect:(NSRect)rect
{
@synchronized(self) {
_contentRect = rect;
}
}
- (void)setFrameSize:(NSSize)newSize
{
if (self.scrollsPastEnd)
{
CGFloat ch = self.contentRect.size.height;
CGFloat eh = self.enclosingScrollView.contentSize.height;
CGFloat offset = ch < eh ? ch : eh;
offset -= self.trailingHeight + 2 * self.textContainerInset.height;
if (offset > 0)
newSize.height += offset;
}
[super setFrameSize:newSize];
}
/** Overriden to perform extra operation on initial text setup.
*
* When we first launch the editor, -didChangeText will *not* be called, so we
* override this to perform required resizing. The -updateContentRect is wrapped
* inside an NSOperation to be invoked later since the layout manager will not
* be invoked when the text is first set.
*
* @see didChangeText
* @see updateContentRect
*/
- (void)setString:(NSString *)string
{
[super setString:string];
if (self.scrollsPastEnd)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateContentGeometry];
}];
}
}
#pragma mark - Overrides
/** Overriden to perform extra operation on text change.
*
* Updates content height, and invoke the resizing method to apply it.
*
* @see updateContentRect
*/
- (void)didChangeText
{
[super didChangeText];
if (self.scrollsPastEnd)
[self updateContentGeometry];
}
#pragma mark - Private
- (void)updateContentGeometry
{
static NSCharacterSet *visibleCharacterSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSCharacterSet *ws = [NSCharacterSet whitespaceAndNewlineCharacterSet];
visibleCharacterSet = ws.invertedSet;
});
NSString *content = self.string;
NSLayoutManager *manager = self.layoutManager;
NSTextContainer *container = self.textContainer;
NSRect r = [manager usedRectForTextContainer:container];
NSRange lastRange = [content rangeOfCharacterFromSet:visibleCharacterSet
options:NSBackwardsSearch];
NSRect junkRect = r;
if (lastRange.location != NSNotFound)
{
NSUInteger contentLength = content.length;
NSUInteger firstJunkLocation = lastRange.location + lastRange.length;
NSRange junkRange = NSMakeRange(firstJunkLocation,
contentLength - firstJunkLocation);
junkRect = [manager boundingRectForGlyphRange:junkRange
inTextContainer:container];
}
self.trailingHeight = junkRect.size.height;
NSSize inset = self.textContainerInset;
r.size.width += 2 * inset.width;
r.size.height += 2 * inset.height;
self.contentRect = r;
[self setFrameSize:self.frame.size]; // Force size update.
}
@end