diff options
Diffstat (limited to 'frontends/cocoa/BrowserView.m')
-rw-r--r-- | frontends/cocoa/BrowserView.m | 738 |
1 files changed, 738 insertions, 0 deletions
diff --git a/frontends/cocoa/BrowserView.m b/frontends/cocoa/BrowserView.m new file mode 100644 index 000000000..110214d74 --- /dev/null +++ b/frontends/cocoa/BrowserView.m @@ -0,0 +1,738 @@ +/* + * Copyright 2011 Sven Weidauer <sven.weidauer@gmail.com> + * + * This file is part of NetSurf, http://www.netsurf-browser.org/ + * + * NetSurf is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * NetSurf 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "utils/nsoption.h" +#import "utils/messages.h" +#import "utils/nsurl.h" +#import "utils/utils.h" +#import "netsurf/browser_window.h" +#import "netsurf/plotters.h" +#import "netsurf/content.h" +#import "netsurf/keypress.h" + +#import "cocoa/gui.h" +#import "cocoa/BrowserView.h" +#import "cocoa/HistoryView.h" +#import "cocoa/font.h" +#import "cocoa/coordinates.h" +#import "cocoa/plotter.h" +#import "cocoa/LocalHistoryController.h" +#import "cocoa/BrowserWindowController.h" + +@interface BrowserView () + +@property (readwrite, copy, nonatomic) NSString *markedText; + +@property (readwrite, nonatomic) NSRect caretRect; + +- (void)scrollHorizontal:(CGFloat)amount; +- (void)scrollVertical:(CGFloat)amount; +- (CGFloat)pageScroll; + +- (void)popUpContextMenuForEvent:(NSEvent *)event; + +- (IBAction)cmOpenURLInTab:(id)sender; +- (IBAction)cmOpenURLInWindow:(id)sender; +- (IBAction)cmDownloadURL:(id)sender; + +- (IBAction)cmLinkCopy:(id)sender; +- (IBAction)cmImageCopy:(id)sender; + +@end + +@implementation BrowserView + +@synthesize browser; +@synthesize caretTimer; +@synthesize markedText; + +static const CGFloat CaretWidth = 1.0; +static const NSTimeInterval CaretBlinkTime = 0.8; + +- (instancetype)initWithFrame:(NSRect)frame +{ + if ((self = [super initWithFrame:frame]) == nil) { + return nil; + } + + [self registerForDraggedTypes:[NSArray arrayWithObjects:NSURLPboardType, @"public.url", nil]]; + + return self; +} + +- (void)dealloc +{ + [self setCaretTimer:nil]; + [self setMarkedText:nil]; +} + +- (void)setCaretTimer:(NSTimer *)newTimer +{ + if (newTimer != caretTimer) { + [caretTimer invalidate]; + caretTimer = newTimer; + } +} + +- (void)updateHistory +{ + [history redraw]; +} + +- (void)removeCaret +{ + hasCaret = NO; + [self setNeedsDisplayInRect: self.caretRect]; + + [self setCaretTimer:nil]; +} + +- (void)addCaretAtX: (int)caretX Y: (int)caretY height: (int)caretHeight +{ + if (hasCaret) { + [self setNeedsDisplayInRect:self.caretRect]; + } + + self.caretRect = cocoa_rect_wh(caretX, caretY, CaretWidth, caretHeight); + + hasCaret = YES; + caretVisible = YES; + + if (nil == caretTimer) { + [self setCaretTimer:[NSTimer scheduledTimerWithTimeInterval:CaretBlinkTime target:self selector:@selector(caretBlink:) userInfo:nil repeats:YES]]; + } else { + [caretTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:CaretBlinkTime]]; + } + + [self setNeedsDisplayInRect:self.caretRect]; +} + +- (void)caretBlink:(NSTimer *)timer +{ + if (hasCaret) { + caretVisible = !caretVisible; + [self setNeedsDisplayInRect: self.caretRect]; + } +} + +- (void)drawRect:(NSRect)dirtyRect +{ + @autoreleasepool { + + struct redraw_context ctx = { + .interactive = true, + .background_images = true, + .plot = &cocoa_plotters + }; + + const NSRect *rects = NULL; + NSInteger count = 0; + [self getRectsBeingDrawn:&rects count:&count]; + + for (NSInteger i = 0; i < count; i++) { + const struct rect clip = { + .x0 = cocoa_pt_to_px(NSMinX(rects[i])), + .y0 = cocoa_pt_to_px(NSMinY(rects[i])), + .x1 = cocoa_pt_to_px(NSMaxX(rects[i])), + .y1 = cocoa_pt_to_px(NSMaxY(rects[i])) + }; + + browser_window_redraw(browser, 0, 0, &clip, &ctx); + } + + if (hasCaret && caretVisible && [self needsToDrawRect:self.caretRect]) { + [[NSColor blackColor] set]; + [NSBezierPath fillRect: self.caretRect]; + } + } +} + +- (BOOL)isFlipped +{ + return YES; +} + +- (void)viewDidMoveToWindow +{ + NSTrackingArea *area = [[NSTrackingArea alloc] initWithRect:[self visibleRect] + options:NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect + owner:self + userInfo:nil]; + [self addTrackingArea:area]; +} + +static browser_mouse_state cocoa_mouse_flags_for_event(NSEvent *evt) +{ + browser_mouse_state result = 0; + NSUInteger flags = [evt modifierFlags]; + + if (flags & NSEventModifierFlagShift) + result |= BROWSER_MOUSE_MOD_1; + if (flags & NSEventModifierFlagOption) + result |= BROWSER_MOUSE_MOD_2; + + return result; +} + +- (NSPoint)convertMousePoint:(NSEvent *)event +{ + NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; + float bscale = browser_window_get_scale(browser); + + location.x /= bscale; + location.y /= bscale; + + location.x = cocoa_pt_to_px(location.x); + location.y = cocoa_pt_to_px(location.y); + return location; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + if ([theEvent modifierFlags] & NSEventModifierFlagControl) { + [self popUpContextMenuForEvent:theEvent]; + return; + } + + dragStart = [self convertMousePoint:theEvent]; + + browser_window_mouse_click(browser, + BROWSER_MOUSE_PRESS_1 | cocoa_mouse_flags_for_event(theEvent), + dragStart.x, + dragStart.y); +} + +- (void)rightMouseDown:(NSEvent *)theEvent +{ + [self popUpContextMenuForEvent:theEvent]; +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + if (historyVisible) { + [self setHistoryVisible:NO]; + return; + } + + NSPoint location = [self convertMousePoint:theEvent]; + + browser_mouse_state modifierFlags = cocoa_mouse_flags_for_event(theEvent); + + if (isDragging) { + isDragging = NO; + browser_window_mouse_track(browser, (browser_mouse_state)0, location.x, location.y); + } else { + modifierFlags |= BROWSER_MOUSE_CLICK_1; + if ([theEvent clickCount] == 2) + modifierFlags |= BROWSER_MOUSE_DOUBLE_CLICK; + browser_window_mouse_click(browser, modifierFlags, location.x, location.y); + } +} + +#define squared(x) ((x) * (x)) +#define MinDragDistance (5.0) + +- (void)mouseDragged:(NSEvent *)theEvent +{ + NSPoint location = [self convertMousePoint:theEvent]; + browser_mouse_state modifierFlags = cocoa_mouse_flags_for_event(theEvent); + + if (!isDragging) { + const CGFloat distance = squared(dragStart.x - location.x) + squared(dragStart.y - location.y); + + if (distance >= squared(MinDragDistance)) { + isDragging = YES; + browser_window_mouse_click(browser, + BROWSER_MOUSE_DRAG_1 | modifierFlags, + dragStart.x, + dragStart.y); + } + } + + if (isDragging) { + browser_window_mouse_track(browser, + BROWSER_MOUSE_HOLDING_1 | BROWSER_MOUSE_DRAG_ON | modifierFlags, + location.x, + location.y); + } +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + if (historyVisible) + return; + + NSPoint location = [self convertMousePoint:theEvent]; + + browser_window_mouse_track(browser, + cocoa_mouse_flags_for_event(theEvent), + location.x, + location.y); +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + [[NSCursor arrowCursor] set]; +} + +- (void)keyDown:(NSEvent *)theEvent +{ + if (!historyVisible) { + [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; + } else { + [history keyDown:theEvent]; + } +} + +- (void)insertText:(id)string +{ + for (NSUInteger i = 0, length = [string length]; i < length; i++) { + unichar ch = [string characterAtIndex:i]; + if (!browser_window_key_press(browser, ch)) { + if (ch == ' ') + [self scrollPageDown:self]; + break; + } + } + [self setMarkedText:nil]; +} + +- (void)moveLeft:(id)sender +{ + if (browser_window_key_press(browser, NS_KEY_LEFT)) + return; + [self scrollHorizontal:-[[self enclosingScrollView] horizontalLineScroll]]; +} + +- (void)moveRight:(id)sender +{ + if (browser_window_key_press(browser, NS_KEY_RIGHT)) + return; + [self scrollHorizontal:[[self enclosingScrollView] horizontalLineScroll]]; +} + +- (void)moveUp:(id)sender +{ + if (browser_window_key_press(browser, NS_KEY_UP)) + return; + [self scrollVertical:-[[self enclosingScrollView] lineScroll]]; +} + +- (void)moveDown:(id)sender +{ + if (browser_window_key_press(browser, NS_KEY_DOWN)) + return; + [self scrollVertical:[[self enclosingScrollView] lineScroll]]; +} + +- (void)deleteBackward:(id)sender +{ + if (!browser_window_key_press(browser, NS_KEY_DELETE_LEFT)) { + [NSApp sendAction:@selector(goBack:) to:nil from:self]; + } +} + +- (void)deleteForward:(id)sender +{ + browser_window_key_press(browser, NS_KEY_DELETE_RIGHT); +} + +- (void)cancelOperation:(id)sender +{ + browser_window_key_press(browser, NS_KEY_ESCAPE); +} + +- (void)scrollPageUp:(id)sender +{ + if (browser_window_key_press(browser, NS_KEY_PAGE_UP)) { + return; + } + [self scrollVertical:-[self pageScroll]]; +} + +- (void)scrollPageDown:(id)sender +{ + if (browser_window_key_press(browser, NS_KEY_PAGE_DOWN)) { + return; + } + [self scrollVertical:[self pageScroll]]; +} + +- (void)insertTab:(id)sender +{ + browser_window_key_press(browser, NS_KEY_TAB); +} + +- (void)insertBacktab:(id)sender +{ + browser_window_key_press(browser, NS_KEY_SHIFT_TAB); +} + +- (void)moveToBeginningOfLine:(id)sender +{ + browser_window_key_press(browser, NS_KEY_LINE_START); +} + +- (void)moveToEndOfLine:(id)sender +{ + browser_window_key_press(browser, NS_KEY_LINE_END); +} + +- (void)moveToBeginningOfDocument:(id)sender +{ + if (browser_window_key_press(browser, NS_KEY_TEXT_START)) + return; +} + +- (void)scrollToBeginningOfDocument:(id)sender +{ + NSPoint origin = [self visibleRect].origin; + origin.y = 0; + [self scrollPoint:origin]; +} + +- (void)moveToEndOfDocument:(id)sender +{ + browser_window_key_press(browser, NS_KEY_TEXT_END); +} + +- (void)scrollToEndOfDocument:(id)sender +{ + NSPoint origin = [self visibleRect].origin; + origin.y = NSHeight([self frame]); + [self scrollPoint:origin]; +} + +- (void)insertNewline:(id)sender +{ + browser_window_key_press(browser, NS_KEY_NL); +} + +- (void)selectAll:(id)sender +{ + browser_window_key_press(browser, NS_KEY_SELECT_ALL); +} + +- (void)copy:(id)sender +{ + browser_window_key_press(browser, NS_KEY_COPY_SELECTION); +} + +- (void)cut:(id)sender +{ + browser_window_key_press(browser, NS_KEY_CUT_SELECTION); +} + +- (void)paste:(id)sender +{ + browser_window_key_press(browser, NS_KEY_PASTE); +} + +- (BOOL)acceptsFirstResponder +{ + return YES; +} + +- (void)adjustFrame +{ + browser_window_schedule_reformat(browser); + + [super adjustFrame]; +} + +- (BOOL)isHistoryVisible +{ + return historyVisible; +} + +- (void)setHistoryVisible:(BOOL)newVisible +{ + if (newVisible == historyVisible) + return; + historyVisible = newVisible; + + if (historyVisible) { + if (nil == history) { + history = [[LocalHistoryController alloc] initWithBrowser:self]; + } + [history attachToView:[(BrowserWindowController *)[[self window] windowController] historyButton]]; + } else { + [history detach]; + } +} + +- (void)scrollHorizontal:(CGFloat)amount +{ + NSPoint currentPoint = [self visibleRect].origin; + currentPoint.x += amount; + [self scrollPoint:currentPoint]; +} + +- (void)scrollVertical:(CGFloat)amount +{ + NSPoint currentPoint = [self visibleRect].origin; + currentPoint.y += amount; + [self scrollPoint:currentPoint]; +} + +- (CGFloat)pageScroll +{ + return NSHeight([[self superview] frame]) - [[self enclosingScrollView] pageScroll]; +} + +- (void)popUpContextMenuForEvent:(NSEvent *)event +{ + NSMenu *popupMenu = [[NSMenu alloc] initWithTitle:@""]; + NSPoint point = [self convertMousePoint:event]; + + struct browser_window_features cont; + + browser_window_get_features(browser, point.x, point.y, &cont); + + if (cont.object != NULL) { + NSString *imageURL = [NSString stringWithUTF8String:nsurl_access(hlcache_handle_get_url(cont.object))]; + + [[popupMenu addItemWithTitle:NSLocalizedString(@"Open image in new tab", @"Context menu") + action:@selector(cmOpenURLInTab:) + keyEquivalent:@""] setRepresentedObject:imageURL]; + [[popupMenu addItemWithTitle:NSLocalizedString(@"Open image in new window", @"Context menu") + action:@selector(cmOpenURLInWindow:) + keyEquivalent:@""] setRepresentedObject:imageURL]; + [[popupMenu addItemWithTitle:NSLocalizedString(@"Save image as", @"Context menu") + action:@selector(cmDownloadURL:) + keyEquivalent:@""] setRepresentedObject:imageURL]; + [[popupMenu addItemWithTitle:NSLocalizedString(@"Copy image", @"Context menu") + action:@selector(cmImageCopy:) + keyEquivalent:@""] setRepresentedObject:(__bridge id)content_get_bitmap(cont.object)]; + + [popupMenu addItem:[NSMenuItem separatorItem]]; + } + + if (cont.link != NULL) { + NSString *target = [NSString stringWithUTF8String:nsurl_access(cont.link)]; + + [[popupMenu addItemWithTitle:NSLocalizedString(@"Open link in new tab", @"Context menu") + action:@selector(cmOpenURLInTab:) + keyEquivalent:@""] setRepresentedObject:target]; + [[popupMenu addItemWithTitle:NSLocalizedString(@"Open link in new window", @"Context menu") + action:@selector(cmOpenURLInWindow:) + keyEquivalent:@""] setRepresentedObject:target]; + [[popupMenu addItemWithTitle:NSLocalizedString(@"Save link target", @"Context menu") + action:@selector(cmDownloadURL:) + keyEquivalent:@""] setRepresentedObject:target]; + [[popupMenu addItemWithTitle:NSLocalizedString(@"Copy link", @"Context menu") + action:@selector(cmLinkCopy:) + keyEquivalent:@""] setRepresentedObject:target]; + + [popupMenu addItem:[NSMenuItem separatorItem]]; + } + + [popupMenu addItemWithTitle:NSLocalizedString(@"Back", @"Context menu") + action:@selector(goBack:) + keyEquivalent:@""]; + [popupMenu addItemWithTitle:NSLocalizedString(@"Reload", @"Context menu") + action:@selector(reloadPage:) + keyEquivalent:@""]; + [popupMenu addItemWithTitle:NSLocalizedString(@"Forward", @"Context menu") + action:@selector(goForward:) + keyEquivalent:@""]; + [popupMenu addItemWithTitle:NSLocalizedString(@"View Source", @"Context menu") + action:@selector(viewSource:) + keyEquivalent:@""]; + + [NSMenu popUpContextMenu:popupMenu withEvent:event forView:self]; +} + +- (IBAction)cmOpenURLInTab:(id)sender +{ + nsurl *url; + nserror error; + + error = nsurl_create([[sender representedObject] UTF8String], &url); + if (error == NSERROR_OK) { + error = browser_window_create(BW_CREATE_HISTORY | BW_CREATE_TAB | BW_CREATE_CLONE, + url, + NULL, + browser, + NULL); + nsurl_unref(url); + } + if (error != NSERROR_OK) { + cocoa_warning(messages_get_errorcode(error), 0); + } +} + +- (IBAction)cmOpenURLInWindow:(id)sender +{ + nsurl *url; + nserror error; + + error = nsurl_create([[sender representedObject] UTF8String], &url); + if (error == NSERROR_OK) { + error = browser_window_create(BW_CREATE_HISTORY | BW_CREATE_CLONE, + url, + NULL, + browser, + NULL); + nsurl_unref(url); + } + if (error != NSERROR_OK) { + cocoa_warning(messages_get_errorcode(error), 0); + } +} + +- (IBAction)cmDownloadURL:(id)sender +{ + nsurl *url; + + if (nsurl_create([[sender representedObject] UTF8String], &url) == NSERROR_OK) { + browser_window_navigate(browser, + url, + NULL, + BW_NAVIGATE_DOWNLOAD, + NULL, + NULL, + NULL); + nsurl_unref(url); + } +} + +- (IBAction)cmImageCopy:(id)sender +{ + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + [pb declareTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:nil]; + [pb setData:[[sender representedObject] TIFFRepresentation] forType:NSTIFFPboardType]; +} + +- (IBAction)cmLinkCopy:(id)sender +{ + NSPasteboard *pb = [NSPasteboard generalPasteboard]; + [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; + [pb setString:[sender representedObject] forType:NSStringPboardType]; +} + +// MARK: - +// MARK: Accepting dragged URLs + +- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender +{ + if ((NSDragOperationCopy | NSDragOperationGeneric) & [sender draggingSourceOperationMask]) { + return NSDragOperationCopy; + } + + return NSDragOperationNone; +} + +- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)sender +{ + return YES; +} + +- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender +{ + nsurl *url; + nserror error; + + NSPasteboard *pb = [sender draggingPasteboard]; + + NSString *type = [pb availableTypeFromArray:[NSArray arrayWithObjects:@"public.url", NSURLPboardType, nil]]; + + NSString *urlstr = nil; + + if ([type isEqualToString:NSURLPboardType]) { + urlstr = [[NSURL URLFromPasteboard:pb] absoluteString]; + } else { + urlstr = [pb stringForType:type]; + } + + error = nsurl_create([urlstr UTF8String], &url); + if (error != NSERROR_OK) { + cocoa_warning(messages_get_errorcode(error), 0); + } else { + browser_window_navigate(browser, + url, + NULL, + BW_NAVIGATE_DOWNLOAD, + NULL, + NULL, + NULL); + nsurl_unref(url); + } + + return YES; +} + +// MARK: - +// MARK: NSTextInput protocol implementation + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange +{ + markedText = [aString isEqualToString:@""] ? nil : [aString copy]; +} + +- (void)unmarkText +{ + [self setMarkedText:nil]; +} + +- (BOOL)hasMarkedText +{ + return markedText != nil; +} + +- (NSInteger)conversationIdentifier +{ + return (NSInteger)self; +} + +- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange +{ + return [[NSAttributedString alloc] initWithString:@""]; +} + +- (NSRange)markedRange +{ + return NSMakeRange(NSNotFound, 0); +} + +- (NSRange)selectedRange +{ + return NSMakeRange(NSNotFound, 0); +} + +- (NSRect)firstRectForCharacterRange:(NSRange)theRange +{ + return NSZeroRect; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint +{ + return 0; +} + +- (NSArray *)validAttributesForMarkedText +{ + return [NSArray array]; +} + +- (void)doCommandBySelector:(SEL)sel +{ + [super doCommandBySelector:sel]; +} + +@end |