diff options
author | nyamatongwe <unknown> | 2013-03-15 23:34:46 +1100 |
---|---|---|
committer | nyamatongwe <unknown> | 2013-03-15 23:34:46 +1100 |
commit | 93f47df6328a33e7d2044afbe135d1189157e63d (patch) | |
tree | feeba874832959d2fced7cd5836d46a732310785 /cocoa | |
parent | 9b1adea4079a2384f93acb83bd4d1a7a7b709819 (diff) | |
download | scintilla-mirror-93f47df6328a33e7d2044afbe135d1189157e63d.tar.gz |
Use an NSScrollView on Cocoa for kinetic scrolling and hiding scrollbars unless wanted.
Also affects platform-independent code.
Diffstat (limited to 'cocoa')
-rw-r--r-- | cocoa/ScintillaCocoa.h | 13 | ||||
-rw-r--r-- | cocoa/ScintillaCocoa.mm | 298 | ||||
-rw-r--r-- | cocoa/ScintillaView.h | 29 | ||||
-rw-r--r-- | cocoa/ScintillaView.mm | 352 |
4 files changed, 336 insertions, 356 deletions
diff --git a/cocoa/ScintillaCocoa.h b/cocoa/ScintillaCocoa.h index 302e39d97..0c0448118 100644 --- a/cocoa/ScintillaCocoa.h +++ b/cocoa/ScintillaCocoa.h @@ -55,6 +55,7 @@ extern "C" NSString* ScintillaRecPboardType; @class InnerView; +@class MarginView; @class ScintillaView; @class FindHighlightLayer; @@ -109,6 +110,8 @@ private: bool capturedMouse; + bool enteredSetScrollingSize; + // Private so ScintillaCocoa objects can not be copied ScintillaCocoa(const ScintillaCocoa &) : ScintillaBase() {} ScintillaCocoa &operator=(const ScintillaCocoa &) { return * this; } @@ -125,6 +128,7 @@ private: FindHighlightLayer *layerFindIndicator; protected: + Point GetVisibleOriginInMain(); PRectangle GetClientRectangle(); Point ConvertPoint(NSPoint point); @@ -135,17 +139,19 @@ protected: virtual void CancelModes(); public: - ScintillaCocoa(InnerView* view); + ScintillaCocoa(InnerView* view, MarginView* viewMargin); virtual ~ScintillaCocoa(); void RegisterNotifyCallback(intptr_t windowid, SciNotifyFunc callback); sptr_t WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam); ScintillaView* TopContainer(); + NSScrollView* ScrollContainer(); InnerView* ContentView(); bool SyncPaint(void* gc, PRectangle rc); bool Draw(NSRect rect, CGContextRef gc); + void PaintMargin(NSRect aRect); virtual sptr_t DefWndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam); void SetTicking(bool on); @@ -156,9 +162,10 @@ public: void SetVerticalScrollPos(); void SetHorizontalScrollPos(); bool ModifyScrollBars(int nMax, int nPage); + bool SetScrollingSize(void); void Resize(); - void DoScroll(float position, NSScrollerPart part, bool horizontal); - + void UpdateForScroll(); + // Notifications for the owner. void NotifyChange(); void NotifyFocus(bool focus); diff --git a/cocoa/ScintillaCocoa.mm b/cocoa/ScintillaCocoa.mm index 56de63d9c..31b382f3e 100644 --- a/cocoa/ScintillaCocoa.mm +++ b/cocoa/ScintillaCocoa.mm @@ -379,15 +379,17 @@ const CGFloat paddingHighlightY = 2; //----------------- ScintillaCocoa ----------------------------------------------------------------- -ScintillaCocoa::ScintillaCocoa(InnerView* view) +ScintillaCocoa::ScintillaCocoa(InnerView* view, MarginView* viewMargin) { + vs.marginInside = false; wMain = view; // Don't retain since we're owned by view, which would cause a cycle + wMargin = viewMargin; timerTarget = [[TimerTarget alloc] init: this]; lastMouseEvent = NULL; notifyObj = NULL; notifyProc = NULL; capturedMouse = false; - + enteredSetScrollingSize = false; scrollSpeed = 1; scrollTicks = 2000; tickTimer = NULL; @@ -413,7 +415,7 @@ ScintillaCocoa::~ScintillaCocoa() void ScintillaCocoa::Initialise() { Scintilla_LinkLexers(); - + // Tell Scintilla not to buffer: Quartz buffers drawing for us. WndProc(SCI_SETBUFFEREDDRAW, 0, 0); @@ -678,7 +680,17 @@ void ScintillaCocoa::CancelModes() { ScintillaView* ScintillaCocoa::TopContainer() { NSView* container = static_cast<NSView*>(wMain.GetID()); - return static_cast<ScintillaView*>([container superview]); + return static_cast<ScintillaView*>([[[container superview] superview] superview]); +} + +//-------------------------------------------------------------------------------------------------- + +/** + * Helper function to get the scrolling view. + */ +NSScrollView* ScintillaCocoa::ScrollContainer() { + NSView* container = static_cast<NSView*>(wMain.GetID()); + return static_cast<NSScrollView*>([[container superview] superview]); } //-------------------------------------------------------------------------------------------------- @@ -694,28 +706,43 @@ InnerView* ScintillaCocoa::ContentView() //-------------------------------------------------------------------------------------------------- /** + * Return the top left visible point relative to the origin point of the whole document. + */ +Scintilla::Point ScintillaCocoa::GetVisibleOriginInMain() +{ + NSScrollView *scrollView = ScrollContainer(); + NSRect contentRect = [[scrollView contentView] bounds]; + return Point(contentRect.origin.x, contentRect.origin.y); +} + +//-------------------------------------------------------------------------------------------------- + +/** * Instead of returning the size of the inner view we have to return the visible part of it * in order to make scrolling working properly. + * The returned value is in document coordinates. */ PRectangle ScintillaCocoa::GetClientRectangle() { NSView* host = ContentView(); - NSSize size = [host frame].size; - return PRectangle(0, 0, size.width, size.height); + NSSize size = [[host superview] frame].size; + Point origin = GetVisibleOriginInMain(); + return PRectangle(origin.x, origin.y, origin.x+size.width, origin.y + size.height); } //-------------------------------------------------------------------------------------------------- /** * Converts the given point from base coordinates to local coordinates and at the same time into - * a native Point structure. Base coordinates are used for the top window used in the view hierarchy. + * a native Point structure. Base coordinates are used for the top window used in the view hierarchy. + * Returned value is in view coordinates. */ Scintilla::Point ScintillaCocoa::ConvertPoint(NSPoint point) { NSView* container = ContentView(); NSPoint result = [container convertPoint: point fromView: nil]; - - return Point(result.x, result.y); + Scintilla::Point ptOrigin = GetVisibleOriginInMain(); + return Point(result.x - ptOrigin.x, result.y - ptOrigin.y); } //-------------------------------------------------------------------------------------------------- @@ -752,10 +779,10 @@ sptr_t scintilla_send_message(void* sci, unsigned int iMessage, uptr_t wParam, s //-------------------------------------------------------------------------------------------------- -/** +/** * That's our fake window procedure. On Windows each window has a dedicated procedure to handle * commands (also used to synchronize UI and background threads), which is not the case in Cocoa. - * + * * Messages handled here are almost solely for special commands of the backend. Everything which * would be sytem messages on Windows (e.g. for key down, mouse move etc.) are handled by * directly calling appropriate handlers. @@ -771,14 +798,14 @@ sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lPar return reinterpret_cast<sptr_t>(this); case SCI_GRABFOCUS: - [[ContentView() window] makeFirstResponder:ContentView()]; + [[ContentView() window] makeFirstResponder:ContentView()]; break; - + case SCI_SETBUFFEREDDRAW: - // Buffered drawing not supported on Cocoa + // Buffered drawing not supported on Cocoa bufferedDraw = false; break; - + case SCI_FINDINDICATORSHOW: ShowFindIndicatorForRange(NSMakeRange(wParam, lParam-wParam), YES); return 0; @@ -786,12 +813,12 @@ sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lPar case SCI_FINDINDICATORFLASH: ShowFindIndicatorForRange(NSMakeRange(wParam, lParam-wParam), NO); return 0; - + case SCI_FINDINDICATORHIDE: HideFindIndicator(); return 0; - - case WM_UNICHAR: + + case WM_UNICHAR: // Special case not used normally. Characters passed in this way will be inserted // regardless of their value or modifier states. That means no command interpretation is // performed. @@ -803,7 +830,7 @@ sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lPar return 1; } return 0; - + default: sptr_t r = ScintillaBase::WndProc(iMessage, wParam, lParam); @@ -1511,51 +1538,31 @@ bool ScintillaCocoa::SyncPaint(void* gc, PRectangle rc) //-------------------------------------------------------------------------------------------------- /** - * Scrolls the pixels in the window some number of lines. - * Invalidates the pixels scrolled into view. + * Paint the margin into the MarginView space. */ -void ScintillaCocoa::ScrollText(int linesToMove) +void ScintillaCocoa::PaintMargin(NSRect aRect) { - // Move those pixels - NSView *content = ContentView(); - if ([content layer]) { - [content setNeedsDisplay: YES]; - MoveFindIndicatorWithBounce(NO); - return; - } - - [content lockFocus]; - int diff = vs.lineHeight * linesToMove; - PRectangle textRect = GetTextRectangle(); - // Include margins as they must scroll - textRect.left = 0; - NSRect textRectangle = PRectangleToNSRect(textRect); - NSPoint destPoint = textRectangle.origin; - destPoint.y += diff; - NSCopyBits(0, textRectangle, destPoint); - - // Paint them nice - NSRect redrawRectangle = textRectangle; - if (linesToMove < 0) { - // Repaint bottom - redrawRectangle.origin.y = redrawRectangle.origin.y + redrawRectangle.size.height + diff; - redrawRectangle.size.height = -diff; - } else { - // Repaint top - redrawRectangle.size.height = diff; - } - - [content drawRect: redrawRectangle]; - [content unlockFocus]; + CGContextRef gc = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; + + PRectangle rc = NSRectToPRectangle(aRect); + rcPaint = rc; + Surface *sw = Surface::Allocate(SC_TECHNOLOGY_DEFAULT); + if (sw) + { + sw->Init(gc, wMargin.GetID()); + PaintSelMargin(sw, rc); + sw->Release(); + delete sw; + } +} - // If no flush done here then multiple scrolls will get buffered and screen - // will only update a few times a second. - //[[content window] flushWindow]; - // However, doing the flush leads to the caret updating as a separate operation - // which looks bad when scrolling by holding down the down arrow key. +//-------------------------------------------------------------------------------------------------- - // Could invalidate instead of synchronous draw but that may not be as smooth - //[content setNeedsDisplayInRect: redrawRectangle]; +/** + * ScrollText is empty because scrolling is handled by the NSScrollView. + */ +void ScintillaCocoa::ScrollText(int linesToMove) +{ } //-------------------------------------------------------------------------------------------------- @@ -1565,13 +1572,13 @@ void ScintillaCocoa::ScrollText(int linesToMove) */ void ScintillaCocoa::SetVerticalScrollPos() { - ScintillaView* topContainer = TopContainer(); - - // Convert absolute coordinate into the range [0..1]. Keep in mind that the visible area - // does *not* belong to the scroll range. - int maxScrollPos = MaxScrollPos(); - float relativePosition = (maxScrollPos > 0) ? ((float) topLine / maxScrollPos) : 0.0f; - [topContainer setVerticalScrollPosition: relativePosition]; + NSScrollView *scrollView = ScrollContainer(); + if (scrollView) { + NSClipView *clipView = [scrollView contentView]; + NSRect contentRect = [clipView bounds]; + [clipView scrollToPoint: NSMakePoint(contentRect.origin.x, topLine * vs.lineHeight)]; + [scrollView reflectScrolledClipView:clipView]; + } } //-------------------------------------------------------------------------------------------------- @@ -1581,18 +1588,20 @@ void ScintillaCocoa::SetVerticalScrollPos() */ void ScintillaCocoa::SetHorizontalScrollPos() { - ScintillaView* topContainer = TopContainer(); PRectangle textRect = GetTextRectangle(); - - // Convert absolute coordinate into the range [0..1]. Keep in mind that the visible area - // does *not* belong to the scroll range. + int maxXOffset = scrollWidth - textRect.Width(); if (maxXOffset < 0) maxXOffset = 0; if (xOffset > maxXOffset) xOffset = maxXOffset; - float relativePosition = (float) xOffset / maxXOffset; - [topContainer setHorizontalScrollPosition: relativePosition]; + NSScrollView *scrollView = ScrollContainer(); + if (scrollView) { + NSClipView * clipView = [scrollView contentView]; + NSRect contentRect = [clipView bounds]; + [clipView scrollToPoint: NSMakePoint(xOffset, contentRect.origin.y)]; + [scrollView reflectScrolledClipView:clipView]; + } MoveFindIndicatorWithBounce(NO); } @@ -1600,6 +1609,7 @@ void ScintillaCocoa::SetHorizontalScrollPos() /** * Used to adjust both scrollers to reflect the current scroll range and position in the editor. + * Arguments no longer used as NSScrollView handles details of scroll bar sizes. * * @param nMax Number of lines in the editor. * @param nPage Number of lines per scroll page. @@ -1607,105 +1617,66 @@ void ScintillaCocoa::SetHorizontalScrollPos() */ bool ScintillaCocoa::ModifyScrollBars(int nMax, int nPage) { -#pragma unused(nPage) - // Input values are given in lines, not pixels, so we have to convert. - int lineHeight = static_cast<int>(WndProc(SCI_TEXTHEIGHT, 0, 0)); - PRectangle bounds = GetTextRectangle(); - ScintillaView* topContainer = TopContainer(); - - // Set page size to the same value as the scroll range to hide the scrollbar. - int scrollRange = lineHeight * (nMax + 1); // +1 because the caller subtracted one. - int pageSize; - if (verticalScrollBarVisible) - pageSize = bounds.Height(); - else - pageSize = scrollRange; - bool verticalChange = [topContainer setVerticalScrollRange: scrollRange page: pageSize]; - - scrollRange = scrollWidth; - if (horizontalScrollBarVisible) - pageSize = bounds.Width(); - else - pageSize = scrollRange; - bool horizontalChange = [topContainer setHorizontalScrollRange: scrollRange page: pageSize]; - - MoveFindIndicatorWithBounce(NO); - - return verticalChange || horizontalChange; +#pragma unused(nMax, nPage) + return SetScrollingSize(); +} + +bool ScintillaCocoa::SetScrollingSize(void) { + bool changes = false; + InnerView *inner = ContentView(); + if (!enteredSetScrollingSize) { + enteredSetScrollingSize = true; + NSScrollView *scrollView = ScrollContainer(); + NSClipView *clipView = [ScrollContainer() contentView]; + NSRect clipRect = [clipView bounds]; + int docHeight = (cs.LinesDisplayed()+1) * vs.lineHeight; + if (!endAtLastLine) + docHeight += (int([scrollView bounds].size.height / vs.lineHeight)-3) * vs.lineHeight; + // Allow extra space so that last scroll position places whole line at top + int clipExtra = int(clipRect.size.height) % vs.lineHeight; + docHeight += clipExtra; + // Ensure all of clipRect covered by Scintilla drawing + if (docHeight < clipRect.size.height) + docHeight = clipRect.size.height; + int docWidth = scrollWidth; + bool showHorizontalScroll = horizontalScrollBarVisible && + (wrapState == eWrapNone); + if (!showHorizontalScroll) + docWidth = clipRect.size.width; + NSRect contentRect = {0, 0, docWidth, docHeight}; + NSRect contentRectNow = [inner frame]; + changes = (contentRect.size.width != contentRectNow.size.width) || + (contentRect.size.height != contentRectNow.size.height); + if (changes) { + [inner setFrame: contentRect]; + } + [scrollView setHasVerticalScroller: verticalScrollBarVisible]; + [scrollView setHasHorizontalScroller: showHorizontalScroll]; + SetVerticalScrollPos(); + enteredSetScrollingSize = false; + } + [inner.owner setMarginWidth: vs.fixedColumnWidth]; + return changes; } //-------------------------------------------------------------------------------------------------- void ScintillaCocoa::Resize() { + SetScrollingSize(); ChangeSize(); } //-------------------------------------------------------------------------------------------------- /** - * Called by the frontend control when the user manipulates one of the scrollers. - * - * @param position The relative position of the scroller in the range of [0..1]. - * @param part Specifies which part was clicked on by the user, so we can handle thumb tracking - * as well as page and line scrolling. - * @param horizontal True if the horizontal scroller was hit, otherwise false. + * Update fields to match scroll position after receiving a notification that the user has scrolled. */ -void ScintillaCocoa::DoScroll(float position, NSScrollerPart part, bool horizontal) -{ - // If the given scroller part is not the knob (or knob slot) then the given position is not yet - // current and we have to update it. - if (horizontal) - { - // Horizontal offset is given in pixels. - PRectangle textRect = GetTextRectangle(); - int offset = (int) (position * (scrollWidth - textRect.Width())); - int smallChange = (int) (textRect.Width() / 30); - if (smallChange < 5) - smallChange = 5; - switch (part) - { - case NSScrollerDecrementLine: - offset -= smallChange; - break; - case NSScrollerDecrementPage: - offset -= textRect.Width(); - break; - case NSScrollerIncrementLine: - offset += smallChange; - break; - case NSScrollerIncrementPage: - offset += textRect.Width(); - break; - }; - HorizontalScrollTo(offset); - } - else - { - // VerticalScrolling is by line. If the user is scrolling using the knob we can directly - // set the new scroll position. Otherwise we have to compute it first. - if (part == NSScrollerKnob) - ScrollTo(position * MaxScrollPos(), false); - else - { - switch (part) - { - case NSScrollerDecrementLine: - ScrollTo(topLine - 1, true); - break; - case NSScrollerDecrementPage: - ScrollTo(topLine - LinesOnScreen(), true); - break; - case NSScrollerIncrementLine: - ScrollTo(topLine + 1, true); - break; - case NSScrollerIncrementPage: - ScrollTo(topLine + LinesOnScreen(), true); - break; - }; - - } - } +void ScintillaCocoa::UpdateForScroll() { + Point ptOrigin = GetVisibleOriginInMain(); + xOffset = ptOrigin.x; + int newTop = Platform::Minimum(ptOrigin.y / vs.lineHeight, MaxScrollPos()); + SetTopLine(newTop); } //-------------------------------------------------------------------------------------------------- @@ -1980,7 +1951,7 @@ void ScintillaCocoa::MouseDown(NSEvent* event) void ScintillaCocoa::MouseMove(NSEvent* event) { lastMouseEvent = event; - + ButtonMove(ConvertPoint([event locationInWindow])); } @@ -1999,11 +1970,8 @@ void ScintillaCocoa::MouseUp(NSEvent* event) void ScintillaCocoa::MouseWheel(NSEvent* event) { bool command = ([event modifierFlags] & NSCommandKeyMask) != 0; - int dX = 0; int dY = 0; - dX = 10 * [event deltaX]; // Arbitrary scale factor. - // In order to make scrolling with larger offset smoother we scroll less lines the larger the // delta value is. if ([event deltaY] < 0) @@ -2022,8 +1990,6 @@ void ScintillaCocoa::MouseWheel(NSEvent* event) } else { - HorizontalScrollTo(xOffset - dX); - ScrollTo(topLine - dY, true); } } @@ -2152,6 +2118,8 @@ void ScintillaCocoa::MoveFindIndicatorWithBounce(BOOL bounce) CGPoint ptText = CGPointMake( WndProc(SCI_POINTXFROMPOSITION, 0, layerFindIndicator.positionFind), WndProc(SCI_POINTYFROMPOSITION, 0, layerFindIndicator.positionFind)); + ptText.x = ptText.x - vs.fixedColumnWidth + xOffset; + ptText.y += topLine * vs.lineHeight; if (!layerFindIndicator.geometryFlipped) { NSView *content = ContentView(); diff --git a/cocoa/ScintillaView.h b/cocoa/ScintillaView.h index f34ac7e08..87a443f73 100644 --- a/cocoa/ScintillaView.h +++ b/cocoa/ScintillaView.h @@ -27,6 +27,24 @@ extern NSString *SCIUpdateUINotification; @end /** + * MarginView draws line numbers and other margins next to the text view. + */ +@interface MarginView : NSRulerView +{ +@private + int marginWidth; + ScintillaView *owner; + NSMutableArray *currentCursors; +} + +@property (assign) int marginWidth; +@property (assign) ScintillaView *owner; + +- (id)initWithScrollView:(NSScrollView *)aScrollView; + +@end + +/** * InnerView is the Cocoa interface to the Scintilla backend. It handles text input and * provides a canvas for painting the output. */ @@ -63,8 +81,8 @@ extern NSString *SCIUpdateUINotification; // This is the actual content to which the backend renders itself. InnerView* mContent; - NSScroller* mHorizontalScroller; - NSScroller* mVerticalScroller; + NSScrollView *scrollView; + MarginView *marginView; CGFloat zoomDelta; @@ -78,6 +96,7 @@ extern NSString *SCIUpdateUINotification; @property (nonatomic, readonly) Scintilla::ScintillaCocoa* backend; @property (nonatomic, assign) id<ScintillaNotificationProtocol> delegate; +@property (nonatomic, readonly) NSScrollView *scrollView; - (void) dealloc; - (void) positionSubViews; @@ -90,11 +109,7 @@ extern NSString *SCIUpdateUINotification; - (void) suspendDrawing: (BOOL) suspend; // Scroller handling -- (BOOL) setVerticalScrollRange: (int) range page: (int) page; -- (void) setVerticalScrollPosition: (float) position; -- (BOOL) setHorizontalScrollRange: (int) range page: (int) page; -- (void) setHorizontalScrollPosition: (float) position; - +- (void) setMarginWidth: (int) width; - (void) scrollerAction: (id) sender; - (InnerView*) content; diff --git a/cocoa/ScintillaView.mm b/cocoa/ScintillaView.mm index a2ae060d7..9ff84240d 100644 --- a/cocoa/ScintillaView.mm +++ b/cocoa/ScintillaView.mm @@ -48,13 +48,112 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) } +@implementation MarginView + +@synthesize marginWidth, owner; + +- (id)initWithScrollView:(NSScrollView *)aScrollView +{ + self = [super initWithScrollView:aScrollView orientation:NSVerticalRuler]; + if (self != nil) + { + owner = nil; + marginWidth = 20; + currentCursors = [[NSMutableArray arrayWithCapacity:0] retain]; + for (size_t i=0; i<5; i++) + { + [currentCursors addObject: [reverseArrowCursor retain]]; + } + [self setClientView:[aScrollView documentView]]; + } + return self; +} + +- (void) dealloc +{ + [currentCursors release]; + [super dealloc]; +} + +- (void) setFrame: (NSRect) frame +{ + [super setFrame: frame]; + + [[self window] invalidateCursorRectsForView: self]; +} + +- (CGFloat)requiredThickness +{ + return marginWidth; +} + +- (void)drawHashMarksAndLabelsInRect:(NSRect)aRect +{ + if (owner) { + NSRect contentRect = [[[self scrollView] contentView] bounds]; + NSRect marginRect = [self bounds]; + // Ensure paint to bottom of view to avoid glitches + if (marginRect.size.height > contentRect.size.height) { + // Legacy scroll bar mode leaves a poorly painted corner + aRect = marginRect; + } + owner.backend->PaintMargin(aRect); + } +} + +- (void) mouseDown: (NSEvent *) theEvent +{ + owner.backend->MouseDown(theEvent); +} + +- (void) mouseDragged: (NSEvent *) theEvent +{ + owner.backend->MouseMove(theEvent); +} + +- (void) mouseMoved: (NSEvent *) theEvent +{ + owner.backend->MouseMove(theEvent); +} + +- (void) mouseUp: (NSEvent *) theEvent +{ + owner.backend->MouseUp(theEvent); +} + +/** + * This method is called to give us the opportunity to define our mouse sensitive rectangle. + */ +- (void) resetCursorRects +{ + [super resetCursorRects]; + + int x = 0; + NSRect marginRect = [self bounds]; + size_t co = [currentCursors count]; + for (size_t i=0; i<co; i++) + { + int cursType = owner.backend->WndProc(SCI_GETMARGINCURSORN, i, 0); + int width =owner.backend->WndProc(SCI_GETMARGINWIDTHN, i, 0); + NSCursor *cc = cursorFromEnum(static_cast<Window::Cursor>(cursType)); + [currentCursors replaceObjectAtIndex:i withObject: cc]; + marginRect.origin.x = x; + marginRect.size.width = width; + [self addCursorRect: marginRect cursor: cc]; + [cc setOnMouseEntered: YES]; + x += width; + } +} + +@end + @implementation InnerView @synthesize owner = mOwner; //-------------------------------------------------------------------------------------------------- -- (NSView*) initWithFrame: (NSRect) frame +- (NSView*) initWithFrame: (NSRect) frame { self = [super initWithFrame: frame]; @@ -449,9 +548,35 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) //-------------------------------------------------------------------------------------------------- +/** + * Mouse wheel with command key magnifies text. + */ - (void) scrollWheel: (NSEvent *) theEvent { - mOwner.backend->MouseWheel(theEvent); + if (([theEvent modifierFlags] & NSCommandKeyMask) != 0) { + mOwner.backend->MouseWheel(theEvent); + } else { + [super scrollWheel:theEvent]; + } +} + +//-------------------------------------------------------------------------------------------------- + +/** + * Ensure scrolling is aligned to whole lines instead of starting part-way through a line + */ +- (NSRect)adjustScroll:(NSRect)proposedVisibleRect +{ + NSRect rc = proposedVisibleRect; + // Snap to lines + NSRect contentRect = [self bounds]; + if ((rc.origin.y > 0) && (NSMaxY(rc) < contentRect.size.height)) { + // Only snap for positions inside the document - allow outside + // for overshoot. + int lineHeight = mOwner.backend->WndProc(SCI_TEXTHEIGHT, 0, 0); + rc.origin.y = roundf(rc.origin.y / lineHeight) * lineHeight; + } + return rc; } //-------------------------------------------------------------------------------------------------- @@ -632,6 +757,7 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) @synthesize backend = mBackend; @synthesize delegate = mDelegate; +@synthesize scrollView; /** * ScintillaView is a composite control made from an NSView and an embedded NSView that is @@ -843,27 +969,32 @@ static void notification(intptr_t windowid, unsigned int iMessage, uintptr_t wPa if (self) { mContent = [[[InnerView alloc] init] autorelease]; - mBackend = new ScintillaCocoa(mContent); mContent.owner = self; - [self addSubview: mContent]; - + // Initialize the scrollers but don't show them yet. // Pick an arbitrary size, just to make NSScroller selecting the proper scroller direction // (horizontal or vertical). NSRect scrollerRect = NSMakeRect(0, 0, 100, 10); - mHorizontalScroller = [[[NSScroller alloc] initWithFrame: scrollerRect] autorelease]; - [mHorizontalScroller setHidden: YES]; - [mHorizontalScroller setTarget: self]; - [mHorizontalScroller setAction: @selector(scrollerAction:)]; - [self addSubview: mHorizontalScroller]; - - scrollerRect.size = NSMakeSize(10, 100); - mVerticalScroller = [[[NSScroller alloc] initWithFrame: scrollerRect] autorelease]; - [mVerticalScroller setHidden: YES]; - [mVerticalScroller setTarget: self]; - [mVerticalScroller setAction: @selector(scrollerAction:)]; - [self addSubview: mVerticalScroller]; + scrollView = [[[NSScrollView alloc] initWithFrame: scrollerRect] autorelease]; + [scrollView setDocumentView: mContent]; + [scrollView setHasVerticalScroller:YES]; + [scrollView setHasHorizontalScroller:YES]; + [scrollView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; + //[scrollView setScrollerStyle:NSScrollerStyleLegacy]; + //[scrollView setScrollerKnobStyle:NSScrollerKnobStyleDark]; + //[scrollView setHorizontalScrollElasticity:NSScrollElasticityNone]; + [self addSubview: scrollView]; + + marginView = [[MarginView alloc] initWithScrollView:scrollView]; + marginView.owner = self; + [marginView setRuleThickness:[marginView requiredThickness]]; + [scrollView setVerticalRulerView:marginView]; + [scrollView setHasHorizontalRuler:NO]; + [scrollView setHasVerticalRuler:YES]; + [scrollView setRulersVisible:YES]; + mBackend = new ScintillaCocoa(mContent, marginView); + // Establish a connection from the back end to this container so we can handle situations // which require our attention. mBackend->RegisterNotifyCallback(nil, notification); @@ -885,6 +1016,12 @@ static void notification(intptr_t windowid, unsigned int iMessage, uintptr_t wPa selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:nil]; + + [[scrollView contentView] setPostsBoundsChangedNotifications:YES]; + [center addObserver:self + selector:@selector(scrollerAction:) + name:NSViewBoundsDidChangeNotification + object:[scrollView contentView]]; } return self; } @@ -934,193 +1071,47 @@ static void notification(intptr_t windowid, unsigned int iMessage, uintptr_t wPa int scrollerWidth = [NSScroller scrollerWidth]; NSSize size = [self frame].size; - NSRect hScrollerRect = {0, 0, size.width, static_cast<CGFloat>(scrollerWidth)}; - NSRect vScrollerRect = {size.width - scrollerWidth, 0, static_cast<CGFloat>(scrollerWidth), size.height}; NSRect barFrame = {0, size.height - scrollerWidth, size.width, static_cast<CGFloat>(scrollerWidth)}; BOOL infoBarVisible = mInfoBar != nil && ![mInfoBar isHidden]; - + // Horizontal offset of the content. Almost always 0 unless the vertical scroller // is on the left side. int contentX = 0; - - // Vertical scroller frame calculation. - if (![mVerticalScroller isHidden]) - { - // Consider user settings (left vs right vertical scrollbar). - BOOL isLeft = [[[NSUserDefaults standardUserDefaults] stringForKey: @"NSScrollerPosition"] - isEqualToString: @"left"]; - if (isLeft) - { - vScrollerRect.origin.x = 0; - hScrollerRect.origin.x = scrollerWidth; - contentX = scrollerWidth; - }; - - size.width -= scrollerWidth; - hScrollerRect.size.width -= scrollerWidth; - } - - // Same for horizontal scroller. - if (![mHorizontalScroller isHidden]) - { - // Make room for the h-scroller. - size.height -= scrollerWidth; - vScrollerRect.size.height -= scrollerWidth; - vScrollerRect.origin.y += scrollerWidth; - }; - + NSRect scrollRect = {contentX, 0, size.width, size.height}; + // Info bar frame. if (infoBarVisible) { + scrollRect.size.height -= scrollerWidth; // Initial value already is as if the bar is at top. - if (mInfoBarAtTop) + if (!mInfoBarAtTop) { - vScrollerRect.size.height -= scrollerWidth; - size.height -= scrollerWidth; - } - else - { - // Layout info bar and h-scroller side by side in a friendly manner. - int nativeWidth = mInitialInfoBarWidth; - int remainingWidth = barFrame.size.width; - + scrollRect.origin.y += scrollerWidth; barFrame.origin.y = 0; - - if ([mHorizontalScroller isHidden]) - { - // H-scroller is not visible, so take the full space. - vScrollerRect.origin.y += scrollerWidth; - vScrollerRect.size.height -= scrollerWidth; - size.height -= scrollerWidth; - } - else - { - // If the left offset of the h-scroller is > 0 then the v-scroller is on the left side. - // In this case we take the full width, otherwise what has been given to the h-scroller - // and content up to now. - if (hScrollerRect.origin.x == 0) - remainingWidth = size.width; - - // Note: remainingWidth can become < 0, which hides the scroller. - remainingWidth -= nativeWidth; - - hScrollerRect.origin.x = nativeWidth; - hScrollerRect.size.width = remainingWidth; - barFrame.size.width = nativeWidth; - } } } - - NSRect contentRect = {static_cast<CGFloat>(contentX), vScrollerRect.origin.y, size.width, size.height}; - [mContent setFrame: contentRect]; - - if (infoBarVisible) - [mInfoBar setFrame: barFrame]; - if (![mHorizontalScroller isHidden]) - [mHorizontalScroller setFrame: hScrollerRect]; - if (![mVerticalScroller isHidden]) - [mVerticalScroller setFrame: vScrollerRect]; -} -//-------------------------------------------------------------------------------------------------- - -/** - * Called by the backend to adjust the vertical scroller (range and page). - * - * @param range Determines the total size of the scroll area used in the editor. - * @param page Determines how many pixels a page is. - * @result Returns YES if anything changed, otherwise NO. - */ -- (BOOL) setVerticalScrollRange: (int) range page: (int) page -{ - BOOL result = NO; - BOOL hideScroller = page >= range; - - if ([mVerticalScroller isHidden] != hideScroller) - { - result = YES; - [mVerticalScroller setHidden: hideScroller]; - if (!hideScroller) - [mVerticalScroller setFloatValue: 0]; - [self positionSubViews]; + if (!NSEqualRects([scrollView frame], scrollRect)) { + [scrollView setFrame: scrollRect]; + mBackend->Resize(); } - - if (!hideScroller) - { - [mVerticalScroller setEnabled: YES]; - - CGFloat currentProportion = [mVerticalScroller knobProportion]; - CGFloat newProportion = page / (CGFloat) range; - if (currentProportion != newProportion) - { - result = YES; - [mVerticalScroller setKnobProportion: newProportion]; - } - } - - return result; -} -//-------------------------------------------------------------------------------------------------- - -/** - * Used to set the position of the vertical scroll thumb. - * - * @param position The relative position in the range [0..1]; - */ -- (void) setVerticalScrollPosition: (float) position -{ - [mVerticalScroller setFloatValue: position]; + if (infoBarVisible) + [mInfoBar setFrame: barFrame]; } //-------------------------------------------------------------------------------------------------- /** - * Called by the backend to adjust the horizontal scroller (range and page). - * - * @param range Determines the total size of the scroll area used in the editor. - * @param page Determines how many pixels a page is. - * @result Returns YES if anything changed, otherwise NO. + * Set the width of the margin. */ -- (BOOL) setHorizontalScrollRange: (int) range page: (int) page +- (void) setMarginWidth: (int) width { - BOOL result = NO; - BOOL hideScroller = (page >= range) || - (mBackend->WndProc(SCI_GETWRAPMODE, 0, 0) != SC_WRAP_NONE); - - if ([mHorizontalScroller isHidden] != hideScroller) + if (marginView.ruleThickness != width) { - result = YES; - [mHorizontalScroller setHidden: hideScroller]; - [self positionSubViews]; + marginView.marginWidth = width; + [marginView setRuleThickness:[marginView requiredThickness]]; } - - if (!hideScroller) - { - [mHorizontalScroller setEnabled: YES]; - - CGFloat currentProportion = [mHorizontalScroller knobProportion]; - CGFloat newProportion = page / (CGFloat) range; - if (currentProportion != newProportion) - { - result = YES; - [mHorizontalScroller setKnobProportion: newProportion]; - } - } - - return result; -} - -//-------------------------------------------------------------------------------------------------- - -/** - * Used to set the position of the vertical scroll thumb. - * - * @param position The relative position in the range [0..1]; - */ -- (void) setHorizontalScrollPosition: (float) position -{ - [mHorizontalScroller setFloatValue: position]; } //-------------------------------------------------------------------------------------------------- @@ -1131,8 +1122,7 @@ static void notification(intptr_t windowid, unsigned int iMessage, uintptr_t wPa */ - (void) scrollerAction: (id) sender { - float position = [sender doubleValue]; - mBackend->DoScroll(position, [sender hitPart], sender == mHorizontalScroller); + mBackend->UpdateForScroll(); } //-------------------------------------------------------------------------------------------------- |