aboutsummaryrefslogtreecommitdiffhomepage
path: root/cocoa
diff options
context:
space:
mode:
authornyamatongwe <unknown>2013-03-15 23:34:46 +1100
committernyamatongwe <unknown>2013-03-15 23:34:46 +1100
commit93f47df6328a33e7d2044afbe135d1189157e63d (patch)
treefeeba874832959d2fced7cd5836d46a732310785 /cocoa
parent9b1adea4079a2384f93acb83bd4d1a7a7b709819 (diff)
downloadscintilla-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.h13
-rw-r--r--cocoa/ScintillaCocoa.mm298
-rw-r--r--cocoa/ScintillaView.h29
-rw-r--r--cocoa/ScintillaView.mm352
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();
}
//--------------------------------------------------------------------------------------------------