diff options
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();  }  //-------------------------------------------------------------------------------------------------- | 
