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 | |
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.
-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 | ||||
-rw-r--r-- | src/Editor.cxx | 175 | ||||
-rw-r--r-- | src/Editor.h | 7 | ||||
-rw-r--r-- | src/ViewStyle.cxx | 9 | ||||
-rw-r--r-- | src/ViewStyle.h | 4 |
8 files changed, 477 insertions, 410 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(); } //-------------------------------------------------------------------------------------------------- diff --git a/src/Editor.cxx b/src/Editor.cxx index 4f00e501a..8b9084fc5 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -175,6 +175,7 @@ Editor::Editor() { pixmapLine = 0; pixmapSelMargin = 0; pixmapSelPattern = 0; + pixmapSelPatternOffset1 = 0; pixmapIndentGuide = 0; pixmapIndentGuideHighlight = 0; @@ -250,6 +251,8 @@ void Editor::DropGraphics(bool freeObjects) { pixmapSelMargin = 0; delete pixmapSelPattern; pixmapSelPattern = 0; + delete pixmapSelPatternOffset1; + pixmapSelPatternOffset1 = 0; delete pixmapIndentGuide; pixmapIndentGuide = 0; delete pixmapIndentGuideHighlight; @@ -261,6 +264,8 @@ void Editor::DropGraphics(bool freeObjects) { pixmapSelMargin->Release(); if (pixmapSelPattern) pixmapSelPattern->Release(); + if (pixmapSelPatternOffset1) + pixmapSelPatternOffset1->Release(); if (pixmapIndentGuide) pixmapIndentGuide->Release(); if (pixmapIndentGuideHighlight) @@ -275,6 +280,8 @@ void Editor::AllocateGraphics() { pixmapSelMargin = Surface::Allocate(technology); if (!pixmapSelPattern) pixmapSelPattern = Surface::Allocate(technology); + if (!pixmapSelPatternOffset1) + pixmapSelPatternOffset1 = Surface::Allocate(technology); if (!pixmapIndentGuide) pixmapIndentGuide = Surface::Allocate(technology); if (!pixmapIndentGuideHighlight) @@ -308,13 +315,37 @@ void Editor::RefreshStyleData() { } } +Point Editor::GetVisibleOriginInMain() { + return Point(0,0); +} + +Point Editor::DocumentPointFromView(Point ptView) { + Point ptDocument = ptView; + if (wMargin.GetID()) { + Point ptOrigin = GetVisibleOriginInMain(); + ptDocument.x += ptOrigin.x; + ptDocument.y += ptOrigin.y; + } else { + ptDocument.x += xOffset; + ptDocument.y += topLine * vs.lineHeight; + } + return ptDocument; +} + +int Editor::TopLineOfMain() { + if (wMargin.GetID()) + return 0; + else + return topLine; +} + PRectangle Editor::GetClientRectangle() { return wMain.GetClientPosition(); } PRectangle Editor::GetTextRectangle() { PRectangle rc = GetClientRectangle(); - rc.left += vs.fixedColumnWidth; + rc.left += vs.textStart; rc.right -= vs.rightMarginWidth; return rc; } @@ -437,7 +468,7 @@ Point Editor::LocationFromPosition(SelectionPosition pos) { pt.y += vs.lineHeight; } } - pt.x += vs.fixedColumnWidth - xOffset; + pt.x += vs.textStart - xOffset; } pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth; return pt; @@ -449,12 +480,12 @@ Point Editor::LocationFromPosition(int pos) { int Editor::XFromPosition(int pos) { Point pt = LocationFromPosition(pos); - return pt.x - vs.fixedColumnWidth + xOffset; + return pt.x - vs.textStart + xOffset; } int Editor::XFromPosition(SelectionPosition sp) { Point pt = LocationFromPosition(sp); - return pt.x - vs.fixedColumnWidth + xOffset; + return pt.x - vs.textStart + xOffset; } int Editor::LineFromLocation(Point pt) { @@ -462,7 +493,7 @@ int Editor::LineFromLocation(Point pt) { } void Editor::SetTopLine(int topLineNew) { - if (topLine != topLineNew) { + if ((topLine != topLineNew) && (topLineNew >= 0)) { topLine = topLineNew; ContainerNeedsUpdate(SC_UPDATE_V_SCROLL); } @@ -475,13 +506,14 @@ SelectionPosition Editor::SPositionFromLocation(Point pt, bool canReturnInvalid, PRectangle rcClient = GetTextRectangle(); if (!rcClient.Contains(pt)) return SelectionPosition(INVALID_POSITION); - if (pt.x < vs.fixedColumnWidth) + if (pt.x < vs.textStart) return SelectionPosition(INVALID_POSITION); if (pt.y < 0) return SelectionPosition(INVALID_POSITION); } - pt.x = pt.x - vs.fixedColumnWidth + xOffset; - int visibleLine = floor(pt.y / vs.lineHeight) + topLine; + pt = DocumentPointFromView(pt); + pt.x = pt.x - vs.textStart; + int visibleLine = floor(pt.y / vs.lineHeight); if (!canReturnInvalid && (visibleLine < 0)) visibleLine = 0; int lineDoc = cs.DocFromDisplay(visibleLine); @@ -620,6 +652,8 @@ void Editor::Redraw() { //Platform::DebugPrintf("Redraw all\n"); PRectangle rcClient = GetClientRectangle(); wMain.InvalidateRectangle(rcClient); + if (wMargin.GetID()) + wMargin.InvalidateAll(); //wMain.InvalidateAll(); } @@ -629,7 +663,7 @@ void Editor::RedrawSelMargin(int line, bool allAfter) { Redraw(); } else { PRectangle rcSelMargin = GetClientRectangle(); - rcSelMargin.right = vs.fixedColumnWidth; + rcSelMargin.right = rcSelMargin.left + vs.fixedColumnWidth; if (line != -1) { int position = pdoc->LineStart(line); PRectangle rcLine = RectangleFromRange(position, position); @@ -649,7 +683,13 @@ void Editor::RedrawSelMargin(int line, bool allAfter) { if (!allAfter) rcSelMargin.bottom = rcLine.bottom; } - wMain.InvalidateRectangle(rcSelMargin); + if (wMargin.GetID()) { + Point ptOrigin = GetVisibleOriginInMain(); + rcSelMargin.Move(-ptOrigin.x, -ptOrigin.y); + wMargin.InvalidateRectangle(rcSelMargin); + } else { + wMain.InvalidateRectangle(rcSelMargin); + } } } } @@ -667,15 +707,12 @@ PRectangle Editor::RectangleFromRange(int start, int end) { PRectangle rcClient = GetTextRectangle(); PRectangle rc; const int leftTextOverlap = ((xOffset == 0) && (vs.leftMarginWidth > 0)) ? 1 : 0; - rc.left = vs.fixedColumnWidth - leftTextOverlap; - rc.top = (minLine - topLine) * vs.lineHeight; - if (rc.top < 0) - rc.top = 0; + rc.left = vs.textStart - leftTextOverlap; + rc.top = (minLine - TopLineOfMain()) * vs.lineHeight; + if (rc.top < rcClient.top) + rc.top = rcClient.top; rc.right = rcClient.right; - rc.bottom = (maxLine - topLine + 1) * vs.lineHeight; - // Ensure PRectangle is within 16 bit space - rc.top = Platform::Clamp(rc.top, -32000, 32000); - rc.bottom = Platform::Clamp(rc.bottom, -32000, 32000); + rc.bottom = (maxLine - TopLineOfMain() + 1) * vs.lineHeight; return rc; } @@ -1199,7 +1236,9 @@ slop | strict | jumps | even | Caret can go to the margin | When Editor::XYScrollPosition Editor::XYScrollToMakeVisible(const bool useMargin, const bool vert, const bool horiz) { PRectangle rcClient = GetTextRectangle(); const SelectionPosition posCaret = posDrag.IsValid() ? posDrag : sel.RangeMain().caret; - const Point pt = LocationFromPosition(posCaret); + Point pt = LocationFromPosition(posCaret); + const Point ptOrigin = GetVisibleOriginInMain(); + pt.x += ptOrigin.x; const Point ptBottomCaret(pt.x, pt.y + vs.lineHeight - 1); const int lineCaret = DisplayFromPosition(posCaret.Position()); @@ -1543,8 +1582,8 @@ bool Editor::WrapLines(bool fullWrap, int priorityWrapLineStart) { int lineDocTop = cs.DocFromDisplay(topLine); int subLineTop = topLine - cs.DisplayFromDoc(lineDocTop); PRectangle rcTextArea = GetClientRectangle(); - rcTextArea.left = vs.fixedColumnWidth; - rcTextArea.right -= vs.rightMarginWidth; + rcTextArea.left = vs.textStart; + rcTextArea.right -= vs.textStart; wrapWidth = rcTextArea.Width(); RefreshStyleData(); AutoSurface surface(this); @@ -1754,7 +1793,12 @@ void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { if (vs.fixedColumnWidth == 0) return; + RefreshPixMaps(surfWindow); + PRectangle rcMargin = GetClientRectangle(); + Point ptOrigin = GetVisibleOriginInMain(); + rcMargin.Move(0, -ptOrigin.y); + rcMargin.left = 0; rcMargin.right = vs.fixedColumnWidth; if (!rc.Intersects(rcMargin)) @@ -1783,10 +1827,14 @@ void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; if (vs.ms[margin].style != SC_MARGIN_NUMBER) { - if (vs.ms[margin].mask & SC_MASK_FOLDERS) + if (vs.ms[margin].mask & SC_MASK_FOLDERS) { // Required because of special way brush is created for selection margin - surface->FillRectangle(rcSelMargin, *pixmapSelPattern); - else { + // Ensure patterns line up when scrolling with separate margin view + // by choosing correctly aligned variant. + bool invertPhase = static_cast<int>(ptOrigin.y) & 1; + surface->FillRectangle(rcSelMargin, + invertPhase ? *pixmapSelPattern : *pixmapSelPatternOffset1); + } else { ColourDesired colour; switch (vs.ms[margin].style) { case SC_MARGIN_BACK: @@ -1805,9 +1853,9 @@ void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back); } - const int lineStartPaint = rcMargin.top / vs.lineHeight; - int visibleLine = topLine + lineStartPaint; - int yposScreen = lineStartPaint * vs.lineHeight; + const int lineStartPaint = (rcMargin.top + ptOrigin.y) / vs.lineHeight; + int visibleLine = TopLineOfMain() + lineStartPaint; + int yposScreen = lineStartPaint * vs.lineHeight - ptOrigin.y; // Work out whether the top line is whitespace located after a // lessening of fold level which implies a 'fold tail' but which should not // be displayed until the last of a sequence of whitespace. @@ -3299,6 +3347,7 @@ void Editor::RefreshPixMaps(Surface *surfaceWindow) { if (!pixmapSelPattern->Initialised()) { const int patternSize = 8; pixmapSelPattern->InitPixMap(patternSize, patternSize, surfaceWindow, wMain.GetID()); + pixmapSelPatternOffset1->InitPixMap(patternSize, patternSize, surfaceWindow, wMain.GetID()); // This complex procedure is to reproduce the checkerboard dithered pattern used by windows // for scroll bars and Visual Studio for its selection margin. The colour of this pattern is half // way between the chrome colour and the chrome highlight colour making a nice transition @@ -3325,10 +3374,12 @@ void Editor::RefreshPixMaps(Surface *surfaceWindow) { } pixmapSelPattern->FillRectangle(rcPattern, colourFMFill); + pixmapSelPatternOffset1->FillRectangle(rcPattern, colourFMStripes); for (int y = 0; y < patternSize; y++) { for (int x = y % 2; x < patternSize; x+=2) { PRectangle rcPixel(x, y, x+1, y+1); pixmapSelPattern->FillRectangle(rcPixel, colourFMStripes); + pixmapSelPatternOffset1->FillRectangle(rcPixel, colourFMFill); } } } @@ -3456,12 +3507,13 @@ void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { StyleToPositionInView(PositionAfterArea(rcArea)); PRectangle rcClient = GetClientRectangle(); + Point ptOrigin = GetVisibleOriginInMain(); //Platform::DebugPrintf("Client: (%3d,%3d) ... (%3d,%3d) %d\n", // rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); int screenLinePaintFirst = rcArea.top / vs.lineHeight; - int xStart = vs.fixedColumnWidth - xOffset; + int xStart = vs.textStart - xOffset + ptOrigin.x; int ypos = 0; if (!bufferedDraw) ypos += screenLinePaintFirst * vs.lineHeight; @@ -3493,12 +3545,20 @@ void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { surfaceWindow->SetClip(rcArea); if (paintState != paintAbandoned) { - PaintSelMargin(surfaceWindow, rcArea); - - PRectangle rcRightMargin = rcClient; - rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; - if (rcArea.Intersects(rcRightMargin)) { - surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back); + if (vs.marginInside) { + PaintSelMargin(surfaceWindow, rcArea); + PRectangle rcRightMargin = rcClient; + rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; + if (rcArea.Intersects(rcRightMargin)) { + surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back); + } + } else { // Else separate view so separate paint event but leftMargin included to allow overlap + PRectangle rcLeftMargin = rcArea; + rcLeftMargin.left = 0; + rcLeftMargin.right = rcLeftMargin.left + vs.leftMarginWidth; + if (rcArea.Intersects(rcLeftMargin)) { + surfaceWindow->FillRectangle(rcLeftMargin, vs.styles[STYLE_DEFAULT].back); + } } } @@ -3522,7 +3582,7 @@ void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { const int leftTextOverlap = ((xOffset == 0) && (vs.leftMarginWidth > 0)) ? 1 : 0; // Do the painting - if (rcArea.right > vs.fixedColumnWidth - leftTextOverlap) { + if (rcArea.right > vs.textStart - leftTextOverlap) { Surface *surface = surfaceWindow; if (bufferedDraw) { @@ -3532,7 +3592,7 @@ void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { surface->SetUnicodeMode(IsUnicodeMode()); surface->SetDBCSMode(CodePage()); - int visibleLine = topLine + screenLinePaintFirst; + int visibleLine = TopLineOfMain() + screenLinePaintFirst; SelectionPosition posCaret = sel.RangeMain().caret; if (posDrag.IsValid()) @@ -3540,12 +3600,16 @@ void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { int lineCaret = pdoc->LineFromPosition(posCaret.Position()); PRectangle rcTextArea = rcClient; - rcTextArea.left = vs.fixedColumnWidth; - rcTextArea.right -= vs.rightMarginWidth; + if (vs.marginInside) { + rcTextArea.left += vs.textStart; + rcTextArea.right -= vs.rightMarginWidth; + } else { + rcTextArea = rcArea; + } // Remove selection margin from drawing area so text will not be drawn // on it in unbuffered mode. - if (!bufferedDraw) { + if (!bufferedDraw && vs.marginInside) { PRectangle rcClipText = rcTextArea; rcClipText.left -= leftTextOverlap; surfaceWindow->SetClip(rcClipText); @@ -3639,8 +3703,8 @@ void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { DrawCarets(surface, vs, lineDoc, xStart, rcLine, ll, subLine); if (bufferedDraw) { - Point from(vs.fixedColumnWidth-leftTextOverlap, 0); - PRectangle rcCopyArea(vs.fixedColumnWidth-leftTextOverlap, yposScreen, + Point from(vs.textStart-leftTextOverlap, 0); + PRectangle rcCopyArea(vs.textStart-leftTextOverlap, yposScreen, rcClient.right - vs.rightMarginWidth, yposScreen + vs.lineHeight); surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); } @@ -3664,10 +3728,10 @@ void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { // durPaint = 0.00000001; // Right column limit indicator - PRectangle rcBeyondEOF = rcClient; - rcBeyondEOF.left = vs.fixedColumnWidth; - rcBeyondEOF.right = rcBeyondEOF.right - vs.rightMarginWidth; - rcBeyondEOF.top = (cs.LinesDisplayed() - topLine) * vs.lineHeight; + PRectangle rcBeyondEOF = (vs.marginInside) ? rcClient : rcArea; + rcBeyondEOF.left = vs.textStart; + rcBeyondEOF.right = rcBeyondEOF.right - ((vs.marginInside) ? vs.rightMarginWidth : 0); + rcBeyondEOF.top = (cs.LinesDisplayed() - TopLineOfMain()) * vs.lineHeight; if (rcBeyondEOF.top < rcBeyondEOF.bottom) { surfaceWindow->FillRectangle(rcBeyondEOF, vs.styles[STYLE_DEFAULT].back); if (vs.edgeState == EDGE_LINE) { @@ -3937,7 +4001,7 @@ void Editor::ChangeSize() { SetScrollBars(); if (wrapState != eWrapNone) { PRectangle rcTextArea = GetClientRectangle(); - rcTextArea.left = vs.fixedColumnWidth; + rcTextArea.left = vs.textStart; rcTextArea.right -= vs.rightMarginWidth; if (wrapWidth != rcTextArea.Width()) { NeedWrapping(); @@ -4457,7 +4521,7 @@ void Editor::NotifyIndicatorClick(bool click, int position, bool shift, bool ctr bool Editor::NotifyMarginClick(Point pt, bool shift, bool ctrl, bool alt) { int marginClicked = -1; - int x = 0; + int x = vs.textStart - vs.fixedColumnWidth; for (int margin = 0; margin <= SC_MAX_MARGIN; margin++) { if ((pt.x >= x) && (pt.x < x + vs.ms[margin].width)) marginClicked = margin; @@ -6111,7 +6175,8 @@ bool Editor::PointInSelMargin(Point pt) { // Really means: "Point in a margin" if (vs.fixedColumnWidth > 0) { // There is a margin PRectangle rcSelMargin = GetClientRectangle(); - rcSelMargin.right = vs.fixedColumnWidth - vs.leftMarginWidth; + rcSelMargin.right = vs.textStart - vs.leftMarginWidth; + rcSelMargin.left = vs.textStart - vs.fixedColumnWidth; return rcSelMargin.Contains(pt); } else { return false; @@ -6501,6 +6566,8 @@ void Editor::ButtonMove(Point pt) { // Autoscroll PRectangle rcClient = GetClientRectangle(); + Point ptOrigin = GetVisibleOriginInMain(); + rcClient.Move(0, -ptOrigin.y); int lineMove = DisplayFromPosition(movePos.Position()); if (pt.y > rcClient.bottom) { ScrollTo(lineMove - LinesOnScreen() + 1); @@ -6700,7 +6767,7 @@ int Editor::PositionAfterArea(PRectangle rcArea) { // Style to a position within the view. If this causes a change at end of last line then // affects later lines so style all the viewed text. void Editor::StyleToPositionInView(Position pos) { - int endWindow = PositionAfterArea(GetClientRectangle()); + int endWindow = (vs.marginInside) ? (PositionAfterArea(GetClientRectangle())) : (pdoc->Length()); if (pos > endWindow) pos = endWindow; int styleAtEnd = pdoc->StyleAt(pos-1); @@ -6735,8 +6802,13 @@ bool Editor::PaintContains(PRectangle rc) { } bool Editor::PaintContainsMargin() { + if (wMargin.GetID()) { + // With separate margin view, paint of text view + // never contains margin. + return false; + } PRectangle rcSelMargin = GetClientRectangle(); - rcSelMargin.right = vs.fixedColumnWidth; + rcSelMargin.right = vs.textStart; return PaintContains(rcSelMargin); } @@ -7446,7 +7518,8 @@ sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { return 0; } else { Point pt = LocationFromPosition(lParam); - return pt.x; + // Convert to view-relative + return pt.x - vs.textStart + vs.fixedColumnWidth; } case SCI_POINTYFROMPOSITION: diff --git a/src/Editor.h b/src/Editor.h index bf3e26d92..91cbd59a4 100644 --- a/src/Editor.h +++ b/src/Editor.h @@ -148,6 +148,7 @@ protected: // ScintillaBase subclass needs access to much of Editor /** On GTK+, Scintilla is a container widget holding two scroll bars * whereas on Windows there is just one window with both scroll bars turned on. */ Window wMain; ///< The Scintilla parent window + Window wMargin; ///< May be separate when using a scroll view for wMain /** Style resources may be expensive to allocate so are cached between uses. * When a style attribute is changed, this cache is flushed. */ @@ -199,6 +200,7 @@ protected: // ScintillaBase subclass needs access to much of Editor Surface *pixmapLine; Surface *pixmapSelMargin; Surface *pixmapSelPattern; + Surface *pixmapSelPatternOffset1; Surface *pixmapIndentGuide; Surface *pixmapIndentGuideHighlight; @@ -308,6 +310,11 @@ protected: // ScintillaBase subclass needs access to much of Editor void DropGraphics(bool freeObjects); void AllocateGraphics(); + // The top left visible point in main window coordinates. Will be 0,0 except for + // scroll views where it will be equivalent to the current scroll position. + virtual Point GetVisibleOriginInMain(); + Point DocumentPointFromView(Point ptView); // Convert a point from view space to document + int TopLineOfMain(); // Return the line at Main's y coordinate 0 virtual PRectangle GetClientRectangle(); PRectangle GetTextRectangle(); diff --git a/src/ViewStyle.cxx b/src/ViewStyle.cxx index f316d264e..9181d9b24 100644 --- a/src/ViewStyle.cxx +++ b/src/ViewStyle.cxx @@ -198,6 +198,8 @@ ViewStyle::ViewStyle(const ViewStyle &source) { } maskInLine = source.maskInLine; fixedColumnWidth = source.fixedColumnWidth; + marginInside = source.marginInside; + textStart = source.textStart; zoomLevel = source.zoomLevel; viewWhitespace = source.viewWhitespace; whitespaceSize = source.whitespaceSize; @@ -306,13 +308,15 @@ void ViewStyle::Init(size_t stylesSize_) { ms[2].style = SC_MARGIN_SYMBOL; ms[2].width = 0; ms[2].mask = 0; - fixedColumnWidth = leftMarginWidth; + marginInside = true; + fixedColumnWidth = marginInside ? leftMarginWidth : 0; maskInLine = 0xffffffff; for (int margin=0; margin <= SC_MAX_MARGIN; margin++) { fixedColumnWidth += ms[margin].width; if (ms[margin].width > 0) maskInLine &= ~ms[margin].mask; } + textStart = marginInside ? fixedColumnWidth : leftMarginWidth; zoomLevel = 0; viewWhitespace = wsInvisible; whitespaceSize = 1; @@ -387,13 +391,14 @@ void ViewStyle::Refresh(Surface &surface) { aveCharWidth = styles[STYLE_DEFAULT].aveCharWidth; spaceWidth = styles[STYLE_DEFAULT].spaceWidth; - fixedColumnWidth = leftMarginWidth; + fixedColumnWidth = marginInside ? leftMarginWidth : 0; maskInLine = 0xffffffff; for (int margin=0; margin <= SC_MAX_MARGIN; margin++) { fixedColumnWidth += ms[margin].width; if (ms[margin].width > 0) maskInLine &= ~ms[margin].mask; } + textStart = marginInside ? fixedColumnWidth : leftMarginWidth; } void ViewStyle::AllocStyles(size_t sizeNew) { diff --git a/src/ViewStyle.h b/src/ViewStyle.h index ddcc64312..bd26613cb 100644 --- a/src/ViewStyle.h +++ b/src/ViewStyle.h @@ -108,7 +108,9 @@ public: int rightMarginWidth; ///< Spacing margin on right of text int maskInLine; ///< Mask for markers to be put into text because there is nowhere for them to go in margin MarginStyle ms[SC_MAX_MARGIN+1]; - int fixedColumnWidth; + int fixedColumnWidth; ///< Total width of margins + bool marginInside; ///< true: margin included in text view, false: separate views + int textStart; ///< Starting x position of text within the view int zoomLevel; WhiteSpaceVisibility viewWhitespace; int whitespaceSize; |