1 2/** 3 * Implementation of the native Cocoa View that serves as container for the scintilla parts. 4 * @file ScintillaView.mm 5 * 6 * Created by Mike Lischke. 7 * 8 * Copyright 2011, 2013, Oracle and/or its affiliates. All rights reserved. 9 * Copyright 2009, 2011 Sun Microsystems, Inc. All rights reserved. 10 * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt). 11 */ 12 13#include <cmath> 14 15#include <string_view> 16#include <vector> 17 18#import "Platform.h" 19#import "ScintillaView.h" 20#import "ScintillaCocoa.h" 21 22using namespace Scintilla; 23 24// Add backend property to ScintillaView as a private category. 25// Specified here as backend accessed by SCIMarginView and SCIContentView. 26@interface ScintillaView() 27@property(nonatomic, readonly) Scintilla::ScintillaCocoa *backend; 28@end 29 30// Two additional cursors we need, which aren't provided by Cocoa. 31static NSCursor *reverseArrowCursor; 32static NSCursor *waitCursor; 33 34NSString *const SCIUpdateUINotification = @"SCIUpdateUI"; 35 36/** 37 * Provide an NSCursor object that matches the Window::Cursor enumeration. 38 */ 39static NSCursor *cursorFromEnum(Window::Cursor cursor) { 40 switch (cursor) { 41 case Window::cursorText: 42 return [NSCursor IBeamCursor]; 43 case Window::cursorArrow: 44 return [NSCursor arrowCursor]; 45 case Window::cursorWait: 46 return waitCursor; 47 case Window::cursorHoriz: 48 return [NSCursor resizeLeftRightCursor]; 49 case Window::cursorVert: 50 return [NSCursor resizeUpDownCursor]; 51 case Window::cursorReverseArrow: 52 return reverseArrowCursor; 53 case Window::cursorUp: 54 default: 55 return [NSCursor arrowCursor]; 56 } 57} 58 59@implementation SCIScrollView 60- (void) tile { 61 [super tile]; 62 63#if defined(MAC_OS_X_VERSION_10_14) 64 if (std::floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_13) { 65 NSRect frame = self.contentView.frame; 66 frame.origin.x = self.verticalRulerView.requiredThickness; 67 frame.size.width -= frame.origin.x; 68 self.contentView.frame = frame; 69 } 70#endif 71} 72@end 73 74// Add marginWidth and owner properties as a private category. 75@interface SCIMarginView() 76@property(assign) int marginWidth; 77@property(nonatomic, weak) ScintillaView *owner; 78@end 79 80@implementation SCIMarginView { 81 int marginWidth; 82 ScintillaView *__weak owner; 83 NSMutableArray *currentCursors; 84} 85 86@synthesize marginWidth, owner; 87 88- (instancetype) initWithScrollView: (NSScrollView *) aScrollView { 89 self = [super initWithScrollView: aScrollView orientation: NSVerticalRuler]; 90 if (self != nil) { 91 owner = nil; 92 marginWidth = 20; 93 currentCursors = [NSMutableArray arrayWithCapacity: 0]; 94 for (size_t i=0; i<=SC_MAX_MARGIN; i++) { 95 [currentCursors addObject: reverseArrowCursor]; 96 } 97 self.clientView = aScrollView.documentView; 98 if ([self respondsToSelector: @selector(setAccessibilityLabel:)]) 99 self.accessibilityLabel = @"Scintilla Margin"; 100 } 101 return self; 102} 103 104 105- (void) setFrame: (NSRect) frame { 106 super.frame = frame; 107 108 [self.window invalidateCursorRectsForView: self]; 109} 110 111- (CGFloat) requiredThickness { 112 return marginWidth; 113} 114 115- (void) drawHashMarksAndLabelsInRect: (NSRect) aRect { 116 if (owner) { 117 NSRect contentRect = self.scrollView.contentView.bounds; 118 NSRect marginRect = self.bounds; 119 // Ensure paint to bottom of view to avoid glitches 120 if (marginRect.size.height > contentRect.size.height) { 121 // Legacy scroll bar mode leaves a poorly painted corner 122 aRect = marginRect; 123 } 124 owner.backend->PaintMargin(aRect); 125 } 126} 127 128/** 129 * Called by the framework if it wants to show a context menu for the margin. 130 */ 131- (NSMenu *) menuForEvent: (NSEvent *) theEvent { 132 NSMenu *menu = [owner menuForEvent: theEvent]; 133 if (menu) { 134 return menu; 135 } else if (owner.backend->ShouldDisplayPopupOnMargin()) { 136 return owner.backend->CreateContextMenu(theEvent); 137 } else { 138 return nil; 139 } 140} 141 142- (void) mouseDown: (NSEvent *) theEvent { 143 NSClipView *textView = self.scrollView.contentView; 144 [textView.window makeFirstResponder: textView]; 145 owner.backend->MouseDown(theEvent); 146} 147 148- (void) rightMouseDown: (NSEvent *) theEvent { 149 [NSMenu popUpContextMenu: [self menuForEvent: theEvent] withEvent: theEvent forView: self]; 150 151 owner.backend->RightMouseDown(theEvent); 152} 153 154- (void) mouseDragged: (NSEvent *) theEvent { 155 owner.backend->MouseMove(theEvent); 156} 157 158- (void) mouseMoved: (NSEvent *) theEvent { 159 owner.backend->MouseMove(theEvent); 160} 161 162- (void) mouseUp: (NSEvent *) theEvent { 163 owner.backend->MouseUp(theEvent); 164} 165 166// Not a simple button so return failure 167- (BOOL) accessibilityPerformPress { 168 return NO; 169} 170 171/** 172 * This method is called to give us the opportunity to define our mouse sensitive rectangle. 173 */ 174- (void) resetCursorRects { 175 [super resetCursorRects]; 176 177 int x = 0; 178 NSRect marginRect = self.bounds; 179 size_t co = currentCursors.count; 180 for (size_t i=0; i<co; i++) { 181 long cursType = owner.backend->WndProc(SCI_GETMARGINCURSORN, i, 0); 182 long width =owner.backend->WndProc(SCI_GETMARGINWIDTHN, i, 0); 183 NSCursor *cc = cursorFromEnum(static_cast<Window::Cursor>(cursType)); 184 currentCursors[i] = cc; 185 marginRect.origin.x = x; 186 marginRect.size.width = width; 187 [self addCursorRect: marginRect cursor: cc]; 188 [cc setOnMouseEntered: YES]; 189 x += width; 190 } 191} 192 193@end 194 195// Add owner property as a private category. 196@interface SCIContentView() 197@property(nonatomic, weak) ScintillaView *owner; 198@end 199 200@implementation SCIContentView { 201 ScintillaView *__weak mOwner; 202 NSCursor *mCurrentCursor; 203 NSTrackingArea *trackingArea; 204 205 // Set when we are in composition mode and partial input is displayed. 206 NSRange mMarkedTextRange; 207} 208 209@synthesize owner = mOwner; 210 211//-------------------------------------------------------------------------------------------------- 212 213- (NSView *) initWithFrame: (NSRect) frame { 214 self = [super initWithFrame: frame]; 215 216 if (self != nil) { 217 // Some initialization for our view. 218 mCurrentCursor = [NSCursor arrowCursor]; 219 trackingArea = nil; 220 mMarkedTextRange = NSMakeRange(NSNotFound, 0); 221 222 [self registerForDraggedTypes: @[NSStringPboardType, ScintillaRecPboardType, NSFilenamesPboardType]]; 223 224 // Set up accessibility in the text role 225 if ([self respondsToSelector: @selector(setAccessibilityElement:)]) { 226 self.accessibilityElement = TRUE; 227 self.accessibilityEnabled = TRUE; 228 self.accessibilityLabel = NSLocalizedString(@"Scintilla", nil); // No real localization 229 self.accessibilityRoleDescription = @"source code editor"; 230 self.accessibilityRole = NSAccessibilityTextAreaRole; 231 self.accessibilityIdentifier = @"Scintilla"; 232 } 233 } 234 235 return self; 236} 237 238//-------------------------------------------------------------------------------------------------- 239 240/** 241 * When the view is resized or scrolled we need to update our tracking area. 242 */ 243- (void) updateTrackingAreas { 244 if (trackingArea) { 245 [self removeTrackingArea: trackingArea]; 246 } 247 248 int opts = (NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved); 249 trackingArea = [[NSTrackingArea alloc] initWithRect: self.bounds 250 options: opts 251 owner: self 252 userInfo: nil]; 253 [self addTrackingArea: trackingArea]; 254 [super updateTrackingAreas]; 255} 256 257//-------------------------------------------------------------------------------------------------- 258 259/** 260 * When the view is resized we need to let the backend know. 261 */ 262- (void) setFrame: (NSRect) frame { 263 super.frame = frame; 264 265 mOwner.backend->Resize(); 266} 267 268//-------------------------------------------------------------------------------------------------- 269 270/** 271 * Called by the backend if a new cursor must be set for the view. 272 */ 273- (void) setCursor: (int) cursor { 274 Window::Cursor eCursor = (Window::Cursor)cursor; 275 mCurrentCursor = cursorFromEnum(eCursor); 276 277 // Trigger recreation of the cursor rectangle(s). 278 [self.window invalidateCursorRectsForView: self]; 279 [mOwner updateMarginCursors]; 280} 281 282//-------------------------------------------------------------------------------------------------- 283 284/** 285 * This method is called to give us the opportunity to define our mouse sensitive rectangle. 286 */ 287- (void) resetCursorRects { 288 [super resetCursorRects]; 289 290 // We only have one cursor rect: our bounds. 291 const NSRect visibleBounds = mOwner.backend->GetBounds(); 292 [self addCursorRect: visibleBounds cursor: mCurrentCursor]; 293 [mCurrentCursor setOnMouseEntered: YES]; 294} 295 296//-------------------------------------------------------------------------------------------------- 297 298/** 299 * Called before repainting. 300 */ 301- (void) viewWillDraw { 302 if (!mOwner) { 303 [super viewWillDraw]; 304 return; 305 } 306 307 const NSRect *rects; 308 NSInteger nRects = 0; 309 [self getRectsBeingDrawn: &rects count: &nRects]; 310 if (nRects > 0) { 311 NSRect rectUnion = rects[0]; 312 for (int i=0; i<nRects; i++) { 313 rectUnion = NSUnionRect(rectUnion, rects[i]); 314 } 315 mOwner.backend->WillDraw(rectUnion); 316 } 317 [super viewWillDraw]; 318} 319 320//-------------------------------------------------------------------------------------------------- 321 322/** 323 * Called before responsive scrolling overdraw. 324 */ 325- (void) prepareContentInRect: (NSRect) rect { 326 if (mOwner) 327 mOwner.backend->WillDraw(rect); 328#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080 329 [super prepareContentInRect: rect]; 330#endif 331} 332 333//-------------------------------------------------------------------------------------------------- 334 335/** 336 * Gets called by the runtime when the view needs repainting. 337 */ 338- (void) drawRect: (NSRect) rect { 339 CGContextRef context = (CGContextRef) [NSGraphicsContext currentContext].graphicsPort; 340 341 if (!mOwner.backend->Draw(rect, context)) { 342 dispatch_async(dispatch_get_main_queue(), ^ { 343 [self setNeedsDisplay: YES]; 344 }); 345 } 346} 347 348//-------------------------------------------------------------------------------------------------- 349 350/** 351 * Windows uses a client coordinate system where the upper left corner is the origin in a window 352 * (and so does Scintilla). We have to adjust for that. However by returning YES here, we are 353 * already done with that. 354 * Note that because of returning YES here most coordinates we use now (e.g. for painting, 355 * invalidating rectangles etc.) are given with +Y pointing down! 356 */ 357- (BOOL) isFlipped { 358 return YES; 359} 360 361//-------------------------------------------------------------------------------------------------- 362 363- (BOOL) isOpaque { 364 return YES; 365} 366 367//-------------------------------------------------------------------------------------------------- 368 369/** 370 * Implement the "click through" behavior by telling the caller we accept the first mouse event too. 371 */ 372- (BOOL) acceptsFirstMouse: (NSEvent *) theEvent { 373#pragma unused(theEvent) 374 return YES; 375} 376 377//-------------------------------------------------------------------------------------------------- 378 379/** 380 * Make this view accepting events as first responder. 381 */ 382- (BOOL) acceptsFirstResponder { 383 return YES; 384} 385 386//-------------------------------------------------------------------------------------------------- 387 388/** 389 * Called by the framework if it wants to show a context menu for the editor. 390 */ 391- (NSMenu *) menuForEvent: (NSEvent *) theEvent { 392 NSMenu *menu = [mOwner menuForEvent: theEvent]; 393 if (menu) { 394 return menu; 395 } else if (mOwner.backend->ShouldDisplayPopupOnText()) { 396 return mOwner.backend->CreateContextMenu(theEvent); 397 } else { 398 return nil; 399 } 400} 401 402//-------------------------------------------------------------------------------------------------- 403 404// Adoption of NSTextInputClient protocol. 405 406- (NSAttributedString *) attributedSubstringForProposedRange: (NSRange) aRange actualRange: (NSRangePointer) actualRange { 407 const NSInteger lengthCharacters = self.accessibilityNumberOfCharacters; 408 if (aRange.location > lengthCharacters) { 409 return nil; 410 } 411 const NSRange posRange = mOwner.backend->PositionsFromCharacters(aRange); 412 // The backend validated aRange and may have removed characters beyond the end of the document. 413 const NSRange charRange = mOwner.backend->CharactersFromPositions(posRange); 414 if (!NSEqualRanges(aRange, charRange)) { 415 *actualRange = charRange; 416 } 417 418 [mOwner message: SCI_SETTARGETRANGE wParam: posRange.location lParam: NSMaxRange(posRange)]; 419 std::string text([mOwner message: SCI_TARGETASUTF8], 0); 420 [mOwner message: SCI_TARGETASUTF8 wParam: 0 lParam: reinterpret_cast<sptr_t>(&text[0])]; 421 text = FixInvalidUTF8(text); 422 NSString *result = @(text.c_str()); 423 NSMutableAttributedString *asResult = [[NSMutableAttributedString alloc] initWithString: result]; 424 425 const NSRange rangeAS = NSMakeRange(0, asResult.length); 426 // SCI_GETSTYLEAT reports a signed byte but want an unsigned to index into styles 427 const char styleByte = static_cast<char>([mOwner message: SCI_GETSTYLEAT wParam: posRange.location]); 428 const long style = static_cast<unsigned char>(styleByte); 429 std::string fontName([mOwner message: SCI_STYLEGETFONT wParam: style lParam: 0], 0); 430 [mOwner message: SCI_STYLEGETFONT wParam: style lParam: (sptr_t)&fontName[0]]; 431 const CGFloat fontSize = [mOwner message: SCI_STYLEGETSIZEFRACTIONAL wParam: style] / 100.0f; 432 NSString *sFontName = @(fontName.c_str()); 433 NSFont *font = [NSFont fontWithName: sFontName size: fontSize]; 434 if (font) { 435 [asResult addAttribute: NSFontAttributeName value: font range: rangeAS]; 436 } 437 438 return asResult; 439} 440 441//-------------------------------------------------------------------------------------------------- 442 443- (NSUInteger) characterIndexForPoint: (NSPoint) point { 444 const NSRect rectPoint = {point, NSZeroSize}; 445 const NSRect rectInWindow = [self.window convertRectFromScreen: rectPoint]; 446 const NSRect rectLocal = [self.superview.superview convertRect: rectInWindow fromView: nil]; 447 448 const long position = [mOwner message: SCI_CHARPOSITIONFROMPOINT 449 wParam: rectLocal.origin.x 450 lParam: rectLocal.origin.y]; 451 if (position == INVALID_POSITION) { 452 return NSNotFound; 453 } else { 454 const NSRange index = mOwner.backend->CharactersFromPositions(NSMakeRange(position, 0)); 455 return index.location; 456 } 457} 458 459//-------------------------------------------------------------------------------------------------- 460 461- (void) doCommandBySelector: (SEL) selector { 462#pragma clang diagnostic push 463#pragma clang diagnostic ignored "-Warc-performSelector-leaks" 464 if ([self respondsToSelector: selector]) 465 [self performSelector: selector withObject: nil]; 466#pragma clang diagnostic pop 467} 468 469//-------------------------------------------------------------------------------------------------- 470 471- (NSRect) firstRectForCharacterRange: (NSRange) aRange actualRange: (NSRangePointer) actualRange { 472#pragma unused(actualRange) 473 const NSRange posRange = mOwner.backend->PositionsFromCharacters(aRange); 474 475 NSRect rect; 476 rect.origin.x = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: posRange.location]; 477 rect.origin.y = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: posRange.location]; 478 const NSUInteger rangeEnd = NSMaxRange(posRange); 479 rect.size.width = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: rangeEnd] - rect.origin.x; 480 rect.size.height = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: rangeEnd] - rect.origin.y; 481 rect.size.height += [mOwner message: SCI_TEXTHEIGHT wParam: 0 lParam: 0]; 482 const NSRect rectInWindow = [self.superview.superview convertRect: rect toView: nil]; 483 const NSRect rectScreen = [self.window convertRectToScreen: rectInWindow]; 484 485 return rectScreen; 486} 487 488//-------------------------------------------------------------------------------------------------- 489 490- (BOOL) hasMarkedText { 491 return mMarkedTextRange.length > 0; 492} 493 494//-------------------------------------------------------------------------------------------------- 495 496/** 497 * General text input. Used to insert new text at the current input position, replacing the current 498 * selection if there is any. 499 * First removes the replacementRange. 500 */ 501- (void) insertText: (id) aString replacementRange: (NSRange) replacementRange { 502 if ((mMarkedTextRange.location != NSNotFound) && (replacementRange.location != NSNotFound)) { 503 NSLog(@"Trying to insertText when there is both a marked range and a replacement range"); 504 } 505 506 // Remove any previously marked text first. 507 mOwner.backend->CompositionUndo(); 508 if (mMarkedTextRange.location != NSNotFound) { 509 const NSRange posRangeMark = mOwner.backend->PositionsFromCharacters(mMarkedTextRange); 510 [mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeMark.location]; 511 } 512 mMarkedTextRange = NSMakeRange(NSNotFound, 0); 513 514 if (replacementRange.location == (NSNotFound-1)) 515 // This occurs when the accent popup is visible and menu selected. 516 // Its replacing a non-existent position so do nothing. 517 return; 518 519 if (replacementRange.location != NSNotFound) { 520 const NSRange posRangeReplacement = mOwner.backend->PositionsFromCharacters(replacementRange); 521 [mOwner message: SCI_DELETERANGE 522 wParam: posRangeReplacement.location 523 lParam: posRangeReplacement.length]; 524 [mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeReplacement.location]; 525 } 526 527 NSString *newText = @""; 528 if ([aString isKindOfClass: [NSString class]]) 529 newText = (NSString *) aString; 530 else if ([aString isKindOfClass: [NSAttributedString class]]) 531 newText = (NSString *) [aString string]; 532 533 mOwner.backend->InsertText(newText, EditModel::CharacterSource::directInput); 534} 535 536//-------------------------------------------------------------------------------------------------- 537 538- (NSRange) markedRange { 539 return mMarkedTextRange; 540} 541 542//-------------------------------------------------------------------------------------------------- 543 544- (NSRange) selectedRange { 545 const NSRange posRangeSel = [mOwner selectedRangePositions]; 546 if (posRangeSel.length == 0) { 547 NSTextInputContext *tic = [NSTextInputContext currentInputContext]; 548 // Chinese input causes malloc crash when empty selection returned with actual 549 // position so return NSNotFound. 550 // If this is applied to European input, it stops the accented character 551 // chooser from appearing. 552 // May need to add more input source names. 553 if ([tic.selectedKeyboardInputSource 554 isEqualToString: @"com.apple.inputmethod.TCIM.Cangjie"]) { 555 return NSMakeRange(NSNotFound, 0); 556 } 557 } 558 return mOwner.backend->CharactersFromPositions(posRangeSel); 559} 560 561//-------------------------------------------------------------------------------------------------- 562 563/** 564 * Called by the input manager to set text which might be combined with further input to form 565 * the final text (e.g. composition of ^ and a to â). 566 * 567 * @param aString The text to insert, either what has been marked already or what is selected already 568 * or simply added at the current insertion point. Depending on what is available. 569 * @param range The range of the new text to select (given relative to the insertion point of the new text). 570 * @param replacementRange The range to remove before insertion. 571 */ 572- (void) setMarkedText: (id) aString selectedRange: (NSRange) range replacementRange: (NSRange) replacementRange { 573 NSString *newText = @""; 574 if ([aString isKindOfClass: [NSString class]]) 575 newText = (NSString *) aString; 576 else if ([aString isKindOfClass: [NSAttributedString class]]) 577 newText = (NSString *) [aString string]; 578 579 // Replace marked text if there is one. 580 if (mMarkedTextRange.length > 0) { 581 mOwner.backend->CompositionUndo(); 582 if (replacementRange.location != NSNotFound) { 583 // This situation makes no sense and has not occurred in practice. 584 NSLog(@"Can not handle a replacement range when there is also a marked range"); 585 } else { 586 replacementRange = mMarkedTextRange; 587 const NSRange posRangeMark = mOwner.backend->PositionsFromCharacters(mMarkedTextRange); 588 [mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeMark.location]; 589 } 590 } else { 591 // Must perform deletion before entering composition mode or else 592 // both document and undo history will not contain the deleted text 593 // leading to an inaccurate and unusable undo history. 594 595 // Convert selection virtual space into real space 596 mOwner.backend->ConvertSelectionVirtualSpace(); 597 598 if (replacementRange.location != NSNotFound) { 599 const NSRange posRangeReplacement = mOwner.backend->PositionsFromCharacters(replacementRange); 600 [mOwner message: SCI_DELETERANGE 601 wParam: posRangeReplacement.location 602 lParam: posRangeReplacement.length]; 603 } else { // No marked or replacement range, so replace selection 604 if (!mOwner.backend->ScintillaCocoa::ClearAllSelections()) { 605 // Some of the selection is protected so can not perform composition here 606 return; 607 } 608 // Ensure only a single selection. 609 mOwner.backend->SelectOnlyMainSelection(); 610 const NSRange posRangeSel = [mOwner selectedRangePositions]; 611 replacementRange = mOwner.backend->CharactersFromPositions(posRangeSel); 612 } 613 } 614 615 // To support IME input to multiple selections, the following code would 616 // need to insert newText at each selection, mark each piece of new text and then 617 // select range relative to each insertion. 618 619 if (newText.length) { 620 // Switching into composition. 621 mOwner.backend->CompositionStart(); 622 623 NSRange posRangeCurrent = mOwner.backend->PositionsFromCharacters(NSMakeRange(replacementRange.location, 0)); 624 // Note: Scintilla internally works almost always with bytes instead chars, so we need to take 625 // this into account when determining selection ranges and such. 626 ptrdiff_t lengthInserted = mOwner.backend->InsertText(newText, EditModel::CharacterSource::tentativeInput); 627 posRangeCurrent.length = lengthInserted; 628 mMarkedTextRange = mOwner.backend->CharactersFromPositions(posRangeCurrent); 629 // Mark the just inserted text. Keep the marked range for later reset. 630 [mOwner setGeneralProperty: SCI_SETINDICATORCURRENT value: INDICATOR_IME]; 631 [mOwner setGeneralProperty: SCI_INDICATORFILLRANGE 632 parameter: posRangeCurrent.location 633 value: posRangeCurrent.length]; 634 } else { 635 mMarkedTextRange = NSMakeRange(NSNotFound, 0); 636 // Re-enable undo action collection if composition ended (indicated by an empty mark string). 637 mOwner.backend->CompositionCommit(); 638 } 639 640 // Select the part which is indicated in the given range. It does not scroll the caret into view. 641 if (range.length > 0) { 642 // range is in characters so convert to bytes for selection. 643 range.location += replacementRange.location; 644 NSRange posRangeSelect = mOwner.backend->PositionsFromCharacters(range); 645 [mOwner setGeneralProperty: SCI_SETSELECTION parameter: NSMaxRange(posRangeSelect) value: posRangeSelect.location]; 646 } 647} 648 649//-------------------------------------------------------------------------------------------------- 650 651- (void) unmarkText { 652 if (mMarkedTextRange.length > 0) { 653 mOwner.backend->CompositionCommit(); 654 mMarkedTextRange = NSMakeRange(NSNotFound, 0); 655 } 656} 657 658//-------------------------------------------------------------------------------------------------- 659 660- (NSArray *) validAttributesForMarkedText { 661 return @[]; 662} 663 664// End of the NSTextInputClient protocol adoption. 665 666//-------------------------------------------------------------------------------------------------- 667 668/** 669 * Generic input method. It is used to pass on keyboard input to Scintilla. The control itself only 670 * handles shortcuts. The input is then forwarded to the Cocoa text input system, which in turn does 671 * its own input handling (character composition via NSTextInputClient protocol): 672 */ 673- (void) keyDown: (NSEvent *) theEvent { 674 bool handled = false; 675 if (mMarkedTextRange.length == 0) 676 handled = mOwner.backend->KeyboardInput(theEvent); 677 if (!handled) { 678 NSArray *events = @[theEvent]; 679 [self interpretKeyEvents: events]; 680 } 681} 682 683//-------------------------------------------------------------------------------------------------- 684 685- (void) mouseDown: (NSEvent *) theEvent { 686 mOwner.backend->MouseDown(theEvent); 687} 688 689//-------------------------------------------------------------------------------------------------- 690 691- (void) mouseDragged: (NSEvent *) theEvent { 692 mOwner.backend->MouseMove(theEvent); 693} 694 695//-------------------------------------------------------------------------------------------------- 696 697- (void) mouseUp: (NSEvent *) theEvent { 698 mOwner.backend->MouseUp(theEvent); 699} 700 701//-------------------------------------------------------------------------------------------------- 702 703- (void) mouseMoved: (NSEvent *) theEvent { 704 mOwner.backend->MouseMove(theEvent); 705} 706 707//-------------------------------------------------------------------------------------------------- 708 709- (void) mouseEntered: (NSEvent *) theEvent { 710 mOwner.backend->MouseEntered(theEvent); 711} 712 713//-------------------------------------------------------------------------------------------------- 714 715- (void) mouseExited: (NSEvent *) theEvent { 716 mOwner.backend->MouseExited(theEvent); 717} 718 719//-------------------------------------------------------------------------------------------------- 720 721/** 722 * Implementing scrollWheel makes scrolling work better even if just 723 * calling super. 724 * Mouse wheel with command key may magnify text if enabled. 725 * Pinch gestures and key commands can also be used for magnification. 726 */ 727- (void) scrollWheel: (NSEvent *) theEvent { 728#ifdef SCROLL_WHEEL_MAGNIFICATION 729 if (([theEvent modifierFlags] & NSEventModifierFlagCommand) != 0) { 730 mOwner.backend->MouseWheel(theEvent); 731 return; 732 } 733#endif 734 [super scrollWheel: theEvent]; 735} 736 737//-------------------------------------------------------------------------------------------------- 738 739/** 740 * Ensure scrolling is aligned to whole lines instead of starting part-way through a line 741 */ 742- (NSRect) adjustScroll: (NSRect) proposedVisibleRect { 743 if (!mOwner) 744 return proposedVisibleRect; 745 NSRect rc = proposedVisibleRect; 746 // Snap to lines 747 NSRect contentRect = self.bounds; 748 if ((rc.origin.y > 0) && (NSMaxY(rc) < contentRect.size.height)) { 749 // Only snap for positions inside the document - allow outside 750 // for overshoot. 751 long lineHeight = mOwner.backend->WndProc(SCI_TEXTHEIGHT, 0, 0); 752 rc.origin.y = std::round(static_cast<XYPOSITION>(rc.origin.y) / lineHeight) * lineHeight; 753 } 754 // Snap to whole points - on retina displays this avoids visual debris 755 // when scrolling horizontally. 756 if ((rc.origin.x > 0) && (NSMaxX(rc) < contentRect.size.width)) { 757 // Only snap for positions inside the document - allow outside 758 // for overshoot. 759 rc.origin.x = std::round(static_cast<XYPOSITION>(rc.origin.x)); 760 } 761 return rc; 762} 763 764//-------------------------------------------------------------------------------------------------- 765 766/** 767 * The editor is getting the foreground control (the one getting the input focus). 768 */ 769- (BOOL) becomeFirstResponder { 770 mOwner.backend->WndProc(SCI_SETFOCUS, 1, 0); 771 return YES; 772} 773 774//-------------------------------------------------------------------------------------------------- 775 776/** 777 * The editor is losing the input focus. 778 */ 779- (BOOL) resignFirstResponder { 780 mOwner.backend->WndProc(SCI_SETFOCUS, 0, 0); 781 return YES; 782} 783 784//-------------------------------------------------------------------------------------------------- 785 786/** 787 * Implement NSDraggingSource. 788 */ 789 790- (NSDragOperation) draggingSession: (NSDraggingSession *) session 791 sourceOperationMaskForDraggingContext: (NSDraggingContext) context { 792#pragma unused(session) 793 switch (context) { 794 case NSDraggingContextOutsideApplication: 795 return NSDragOperationCopy | NSDragOperationMove | NSDragOperationDelete; 796 797 case NSDraggingContextWithinApplication: 798 default: 799 return NSDragOperationCopy | NSDragOperationMove | NSDragOperationDelete; 800 } 801} 802 803- (void) draggingSession: (NSDraggingSession *) session 804 movedToPoint: (NSPoint) screenPoint { 805#pragma unused(session, screenPoint) 806} 807 808- (void) draggingSession: (NSDraggingSession *) session 809 endedAtPoint: (NSPoint) screenPoint 810 operation: (NSDragOperation) operation { 811#pragma unused(session, screenPoint) 812 if (operation == NSDragOperationDelete) { 813 mOwner.backend->WndProc(SCI_CLEAR, 0, 0); 814 } 815} 816 817/** 818 * Implement NSDraggingDestination. 819 */ 820 821//-------------------------------------------------------------------------------------------------- 822 823/** 824 * Called when an external drag operation enters the view. 825 */ 826- (NSDragOperation) draggingEntered: (id <NSDraggingInfo>) sender { 827 return mOwner.backend->DraggingEntered(sender); 828} 829 830//-------------------------------------------------------------------------------------------------- 831 832/** 833 * Called frequently during an external drag operation if we are the target. 834 */ 835- (NSDragOperation) draggingUpdated: (id <NSDraggingInfo>) sender { 836 return mOwner.backend->DraggingUpdated(sender); 837} 838 839//-------------------------------------------------------------------------------------------------- 840 841/** 842 * Drag image left the view. Clean up if necessary. 843 */ 844- (void) draggingExited: (id <NSDraggingInfo>) sender { 845 mOwner.backend->DraggingExited(sender); 846} 847 848//-------------------------------------------------------------------------------------------------- 849 850- (BOOL) prepareForDragOperation: (id <NSDraggingInfo>) sender { 851#pragma unused(sender) 852 return YES; 853} 854 855//-------------------------------------------------------------------------------------------------- 856 857- (BOOL) performDragOperation: (id <NSDraggingInfo>) sender { 858 return mOwner.backend->PerformDragOperation(sender); 859} 860 861//-------------------------------------------------------------------------------------------------- 862 863/** 864 * Drag operation is done. Notify editor. 865 */ 866- (void) concludeDragOperation: (id <NSDraggingInfo>) sender { 867 // Clean up is the same as if we are no longer the drag target. 868 mOwner.backend->DraggingExited(sender); 869} 870 871//-------------------------------------------------------------------------------------------------- 872 873// NSResponder actions. 874 875- (void) selectAll: (id) sender { 876#pragma unused(sender) 877 mOwner.backend->SelectAll(); 878} 879 880- (void) deleteBackward: (id) sender { 881#pragma unused(sender) 882 mOwner.backend->DeleteBackward(); 883} 884 885- (void) cut: (id) sender { 886#pragma unused(sender) 887 mOwner.backend->Cut(); 888} 889 890- (void) copy: (id) sender { 891#pragma unused(sender) 892 mOwner.backend->Copy(); 893} 894 895- (void) paste: (id) sender { 896#pragma unused(sender) 897 if (mMarkedTextRange.location != NSNotFound) { 898 [[NSTextInputContext currentInputContext] discardMarkedText]; 899 mOwner.backend->CompositionCommit(); 900 mMarkedTextRange = NSMakeRange(NSNotFound, 0); 901 } 902 mOwner.backend->Paste(); 903} 904 905- (void) undo: (id) sender { 906#pragma unused(sender) 907 if (mMarkedTextRange.location != NSNotFound) { 908 [[NSTextInputContext currentInputContext] discardMarkedText]; 909 mOwner.backend->CompositionCommit(); 910 mMarkedTextRange = NSMakeRange(NSNotFound, 0); 911 } 912 mOwner.backend->Undo(); 913} 914 915- (void) redo: (id) sender { 916#pragma unused(sender) 917 mOwner.backend->Redo(); 918} 919 920- (BOOL) canUndo { 921 return mOwner.backend->CanUndo() && (mMarkedTextRange.location == NSNotFound); 922} 923 924- (BOOL) canRedo { 925 return mOwner.backend->CanRedo(); 926} 927 928- (BOOL) validateUserInterfaceItem: (id <NSValidatedUserInterfaceItem>) anItem { 929 SEL action = anItem.action; 930 if (action==@selector(undo:)) { 931 return [self canUndo]; 932 } else if (action==@selector(redo:)) { 933 return [self canRedo]; 934 } else if (action==@selector(cut:) || action==@selector(copy:) || action==@selector(clear:)) { 935 return mOwner.backend->HasSelection(); 936 } else if (action==@selector(paste:)) { 937 return mOwner.backend->CanPaste(); 938 } 939 return YES; 940} 941 942- (void) clear: (id) sender { 943 [self deleteBackward: sender]; 944} 945 946- (BOOL) isEditable { 947 return mOwner.backend->WndProc(SCI_GETREADONLY, 0, 0) == 0; 948} 949 950#pragma mark - NSAccessibility 951 952//-------------------------------------------------------------------------------------------------- 953 954// Adoption of NSAccessibility protocol. 955// NSAccessibility wants to pass ranges in UTF-16 code units, not bytes (like Scintilla) 956// or characters. 957// Needs more testing with non-ASCII and non-BMP text. 958// Needs to take account of folding and wraping. 959 960//-------------------------------------------------------------------------------------------------- 961 962/** 963 * NSAccessibility : Text of the whole document as a string. 964 */ 965- (id) accessibilityValue { 966 const sptr_t length = [mOwner message: SCI_GETLENGTH]; 967 return mOwner.backend->RangeTextAsString(NSMakeRange(0, length)); 968} 969 970//-------------------------------------------------------------------------------------------------- 971 972/** 973 * NSAccessibility : Line of the caret. 974 */ 975- (NSInteger) accessibilityInsertionPointLineNumber { 976 const Sci::Position caret = [mOwner message: SCI_GETCURRENTPOS]; 977 const NSRange rangeCharactersCaret = mOwner.backend->CharactersFromPositions(NSMakeRange(caret, 0)); 978 return mOwner.backend->VisibleLineForIndex(rangeCharactersCaret.location); 979} 980 981//-------------------------------------------------------------------------------------------------- 982 983/** 984 * NSAccessibility : Not implemented and not called by VoiceOver. 985 */ 986- (NSRange) accessibilityRangeForPosition: (NSPoint) point { 987 return NSMakeRange(0, 0); 988} 989 990//-------------------------------------------------------------------------------------------------- 991 992/** 993 * NSAccessibility : Number of characters in the whole document. 994 */ 995- (NSInteger) accessibilityNumberOfCharacters { 996 sptr_t length = [mOwner message: SCI_GETLENGTH]; 997 const NSRange posRange = mOwner.backend->CharactersFromPositions(NSMakeRange(length, 0)); 998 return posRange.location; 999} 1000 1001//-------------------------------------------------------------------------------------------------- 1002 1003/** 1004 * NSAccessibility : The selection text as a string. 1005 */ 1006- (NSString *) accessibilitySelectedText { 1007 const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART]; 1008 const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND]; 1009 const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin); 1010 return mOwner.backend->RangeTextAsString(posRangeSel); 1011} 1012 1013//-------------------------------------------------------------------------------------------------- 1014 1015/** 1016 * NSAccessibility : The character range of the main selection. 1017 */ 1018- (NSRange) accessibilitySelectedTextRange { 1019 const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART]; 1020 const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND]; 1021 const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin); 1022 return mOwner.backend->CharactersFromPositions(posRangeSel); 1023} 1024 1025//-------------------------------------------------------------------------------------------------- 1026 1027/** 1028 * NSAccessibility : The setter for accessibilitySelectedTextRange. 1029 * This method is the only setter required for reasonable VoiceOver behaviour. 1030 */ 1031- (void) setAccessibilitySelectedTextRange: (NSRange) range { 1032 NSRange rangePositions = mOwner.backend->PositionsFromCharacters(range); 1033 [mOwner message: SCI_SETSELECTION wParam: rangePositions.location lParam: NSMaxRange(rangePositions)]; 1034} 1035 1036//-------------------------------------------------------------------------------------------------- 1037 1038/** 1039 * NSAccessibility : Range of the glyph at a character index. 1040 * Currently doesn't try to handle composite characters. 1041 */ 1042- (NSRange) accessibilityRangeForIndex: (NSInteger) index { 1043 sptr_t length = [mOwner message: SCI_GETLENGTH]; 1044 const NSRange rangeLength = mOwner.backend->CharactersFromPositions(NSMakeRange(length, 0)); 1045 NSRange rangePositions = NSMakeRange(length, 0); 1046 if (index < rangeLength.location) { 1047 rangePositions = mOwner.backend->PositionsFromCharacters(NSMakeRange(index, 1)); 1048 } 1049 return mOwner.backend->CharactersFromPositions(rangePositions); 1050} 1051 1052//-------------------------------------------------------------------------------------------------- 1053 1054/** 1055 * NSAccessibility : All the text ranges. 1056 * Currently only returns the main selection. 1057 */ 1058- (NSArray<NSValue *> *) accessibilitySelectedTextRanges { 1059 const sptr_t positionBegin = [mOwner message: SCI_GETSELECTIONSTART]; 1060 const sptr_t positionEnd = [mOwner message: SCI_GETSELECTIONEND]; 1061 const NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin); 1062 NSRange rangeCharacters = mOwner.backend->CharactersFromPositions(posRangeSel); 1063 NSValue *valueRange = [NSValue valueWithRange: (NSRange)rangeCharacters]; 1064 return @[valueRange]; 1065} 1066 1067//-------------------------------------------------------------------------------------------------- 1068 1069/** 1070 * NSAccessibility : Character range currently visible. 1071 */ 1072- (NSRange) accessibilityVisibleCharacterRange { 1073 const sptr_t lineTopVisible = [mOwner message: SCI_GETFIRSTVISIBLELINE]; 1074 const sptr_t lineTop = [mOwner message: SCI_DOCLINEFROMVISIBLE wParam: lineTopVisible]; 1075 const sptr_t lineEndVisible = lineTopVisible + [mOwner message: SCI_LINESONSCREEN] - 1; 1076 const sptr_t lineEnd = [mOwner message: SCI_DOCLINEFROMVISIBLE wParam: lineEndVisible]; 1077 const sptr_t posStartView = [mOwner message: SCI_POSITIONFROMLINE wParam: lineTop]; 1078 const sptr_t posEndView = [mOwner message: SCI_GETLINEENDPOSITION wParam: lineEnd]; 1079 const NSRange posRangeSel = NSMakeRange(posStartView, posEndView-posStartView); 1080 return mOwner.backend->CharactersFromPositions(posRangeSel); 1081} 1082 1083//-------------------------------------------------------------------------------------------------- 1084 1085/** 1086 * NSAccessibility : Character range of a line. 1087 */ 1088- (NSRange) accessibilityRangeForLine: (NSInteger) line { 1089 return mOwner.backend->RangeForVisibleLine(line); 1090} 1091 1092//-------------------------------------------------------------------------------------------------- 1093 1094/** 1095 * NSAccessibility : Line number of a text position in characters. 1096 */ 1097- (NSInteger) accessibilityLineForIndex: (NSInteger) index { 1098 return mOwner.backend->VisibleLineForIndex(index); 1099} 1100 1101//-------------------------------------------------------------------------------------------------- 1102 1103/** 1104 * NSAccessibility : A rectangle that covers a range which will be shown as the 1105 * VoiceOver cursor. 1106 * Producing a nice rectangle is a little tricky particularly when including new 1107 * lines. Needs to improve the case where parts of two lines are included. 1108 */ 1109- (NSRect) accessibilityFrameForRange: (NSRange) range { 1110 const NSRect rectInView = mOwner.backend->FrameForRange(range); 1111 const NSRect rectInWindow = [self.superview.superview convertRect: rectInView toView: nil]; 1112 return [self.window convertRectToScreen: rectInWindow]; 1113} 1114 1115//-------------------------------------------------------------------------------------------------- 1116 1117/** 1118 * NSAccessibility : A range of text as a string. 1119 */ 1120- (NSString *) accessibilityStringForRange: (NSRange) range { 1121 const NSRange posRange = mOwner.backend->PositionsFromCharacters(range); 1122 return mOwner.backend->RangeTextAsString(posRange); 1123} 1124 1125//-------------------------------------------------------------------------------------------------- 1126 1127/** 1128 * NSAccessibility : A range of text as an attributed string. 1129 * Currently no attributes are set. 1130 */ 1131- (NSAttributedString *) accessibilityAttributedStringForRange: (NSRange) range { 1132 const NSRange posRange = mOwner.backend->PositionsFromCharacters(range); 1133 NSString *result = mOwner.backend->RangeTextAsString(posRange); 1134 return [[NSMutableAttributedString alloc] initWithString: result]; 1135} 1136 1137//-------------------------------------------------------------------------------------------------- 1138 1139/** 1140 * NSAccessibility : Show the context menu at the caret. 1141 */ 1142- (BOOL) accessibilityPerformShowMenu { 1143 const sptr_t caret = [mOwner message: SCI_GETCURRENTPOS]; 1144 NSRect rect; 1145 rect.origin.x = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: caret]; 1146 rect.origin.y = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: caret]; 1147 rect.origin.y += [mOwner message: SCI_TEXTHEIGHT wParam: 0 lParam: 0]; 1148 rect.size.width = 1.0; 1149 rect.size.height = 1.0; 1150 NSRect rectInWindow = [self.superview.superview convertRect: rect toView: nil]; 1151 NSPoint pt = rectInWindow.origin; 1152 NSEvent *event = [NSEvent mouseEventWithType: NSEventTypeRightMouseDown 1153 location: pt 1154 modifierFlags: 0 1155 timestamp: 0 1156 windowNumber: self.window.windowNumber 1157 context: nil 1158 eventNumber: 0 1159 clickCount: 1 1160 pressure: 0.0]; 1161 NSMenu *menu = mOwner.backend->CreateContextMenu(event); 1162 [NSMenu popUpContextMenu: menu withEvent: event forView: self]; 1163 return YES; 1164} 1165 1166//-------------------------------------------------------------------------------------------------- 1167 1168 1169@end 1170 1171//-------------------------------------------------------------------------------------------------- 1172 1173@implementation ScintillaView { 1174 // The back end is kind of a controller and model in one. 1175 // It uses the content view for display. 1176 Scintilla::ScintillaCocoa *mBackend; 1177 1178 // This is the actual content to which the backend renders itself. 1179 SCIContentView *mContent; 1180 1181 NSScrollView *scrollView; 1182 SCIMarginView *marginView; 1183 1184 CGFloat zoomDelta; 1185 1186 // Area to display additional controls (e.g. zoom info, caret position, status info). 1187 NSView <InfoBarCommunicator> *mInfoBar; 1188 BOOL mInfoBarAtTop; 1189 1190 id<ScintillaNotificationProtocol> __unsafe_unretained mDelegate; 1191} 1192 1193@synthesize backend = mBackend; 1194@synthesize delegate = mDelegate; 1195@synthesize scrollView; 1196 1197/** 1198 * ScintillaView is a composite control made from an NSView and an embedded NSView that is 1199 * used as canvas for the output (by the backend, using its CGContext), plus other elements 1200 * (scrollers, info bar). 1201 */ 1202 1203//-------------------------------------------------------------------------------------------------- 1204 1205/** 1206 * Initialize custom cursor. 1207 */ 1208+ (void) initialize { 1209 if (self == [ScintillaView class]) { 1210 NSBundle *bundle = [NSBundle bundleForClass: [ScintillaView class]]; 1211 1212 NSString *path = [bundle pathForResource: @"mac_cursor_busy" ofType: @"tiff" inDirectory: nil]; 1213 NSImage *image = [[NSImage alloc] initWithContentsOfFile: path]; 1214 waitCursor = [[NSCursor alloc] initWithImage: image hotSpot: NSMakePoint(2, 2)]; 1215 1216 path = [bundle pathForResource: @"mac_cursor_flipped" ofType: @"tiff" inDirectory: nil]; 1217 image = [[NSImage alloc] initWithContentsOfFile: path]; 1218 reverseArrowCursor = [[NSCursor alloc] initWithImage: image hotSpot: NSMakePoint(15, 2)]; 1219 } 1220} 1221 1222//-------------------------------------------------------------------------------------------------- 1223 1224/** 1225 * Specify the SCIContentView class. Can be overridden in a subclass to provide an SCIContentView subclass. 1226 */ 1227 1228+ (Class) contentViewClass { 1229 return [SCIContentView class]; 1230} 1231 1232//-------------------------------------------------------------------------------------------------- 1233 1234/** 1235 * Receives zoom messages, for example when a "pinch zoom" is performed on the trackpad. 1236 */ 1237- (void) magnifyWithEvent: (NSEvent *) event { 1238#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 1239 zoomDelta += event.magnification * 10.0; 1240 1241 if (std::abs(zoomDelta)>=1.0) { 1242 long zoomFactor = static_cast<long>([self getGeneralProperty: SCI_GETZOOM] + zoomDelta); 1243 [self setGeneralProperty: SCI_SETZOOM parameter: zoomFactor value: 0]; 1244 zoomDelta = 0.0; 1245 } 1246#endif 1247} 1248 1249- (void) beginGestureWithEvent: (NSEvent *) event { 1250// Scintilla is only interested in this event as the starft of a zoom 1251#pragma unused(event) 1252 zoomDelta = 0.0; 1253} 1254 1255//-------------------------------------------------------------------------------------------------- 1256 1257/** 1258 * Sends a new notification of the given type to the default notification center. 1259 */ 1260- (void) sendNotification: (NSString *) notificationName { 1261 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 1262 [center postNotificationName: notificationName object: self]; 1263} 1264 1265//-------------------------------------------------------------------------------------------------- 1266 1267/** 1268 * Called by a connected component (usually the info bar) if something changed there. 1269 * 1270 * @param type The type of the notification. 1271 * @param message Carries the new status message if the type is a status message change. 1272 * @param location Carries the new location (e.g. caret) if the type is a caret change or similar type. 1273 * @param value Carries the new zoom value if the type is a zoom change. 1274 */ 1275- (void) notify: (NotificationType) type message: (NSString *) message location: (NSPoint) location 1276 value: (float) value { 1277// These parameters are just to conform to the protocol 1278#pragma unused(message) 1279#pragma unused(location) 1280 switch (type) { 1281 case IBNZoomChanged: { 1282 // Compute point increase/decrease based on default font size. 1283 long fontSize = [self getGeneralProperty: SCI_STYLEGETSIZE parameter: STYLE_DEFAULT]; 1284 int zoom = (int)(fontSize * (value - 1)); 1285 [self setGeneralProperty: SCI_SETZOOM value: zoom]; 1286 break; 1287 } 1288 default: 1289 break; 1290 }; 1291} 1292 1293//-------------------------------------------------------------------------------------------------- 1294 1295- (void) setCallback: (id <InfoBarCommunicator>) callback { 1296// Not used. Only here to satisfy protocol. 1297#pragma unused(callback) 1298} 1299 1300//-------------------------------------------------------------------------------------------------- 1301 1302/** 1303 * Prevents drawing of the inner view to avoid flickering when doing many visual updates 1304 * (like clearing all marks and setting new ones etc.). 1305 */ 1306- (void) suspendDrawing: (BOOL) suspend { 1307 if (suspend) 1308 [self.window disableFlushWindow]; 1309 else 1310 [self.window enableFlushWindow]; 1311} 1312 1313//-------------------------------------------------------------------------------------------------- 1314 1315/** 1316 * Method receives notifications from Scintilla (e.g. for handling clicks on the 1317 * folder margin or changes in the editor). 1318 * A delegate can be set to receive all notifications. If set no handling takes place here, except 1319 * for action pertaining to internal stuff (like the info bar). 1320 */ 1321- (void) notification: (SCNotification *) scn { 1322 // Parent notification. Details are passed as SCNotification structure. 1323 1324 if (mDelegate != nil) { 1325 [mDelegate notification: scn]; 1326 if (scn->nmhdr.code != SCN_ZOOM && scn->nmhdr.code != SCN_UPDATEUI) 1327 return; 1328 } 1329 1330 switch (scn->nmhdr.code) { 1331 case SCN_MARGINCLICK: { 1332 if (scn->margin == 2) { 1333 // Click on the folder margin. Toggle the current line if possible. 1334 long line = [self getGeneralProperty: SCI_LINEFROMPOSITION parameter: scn->position]; 1335 [self setGeneralProperty: SCI_TOGGLEFOLD value: line]; 1336 } 1337 break; 1338 }; 1339 case SCN_MODIFIED: { 1340 // Decide depending on the modification type what to do. 1341 // There can be more than one modification carried by one notification. 1342 if (scn->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT)) 1343 [self sendNotification: NSTextDidChangeNotification]; 1344 break; 1345 } 1346 case SCN_ZOOM: { 1347 // A zoom change happened. Notify info bar if there is one. 1348 float zoom = [self getGeneralProperty: SCI_GETZOOM parameter: 0]; 1349 long fontSize = [self getGeneralProperty: SCI_STYLEGETSIZE parameter: STYLE_DEFAULT]; 1350 float factor = (zoom / fontSize) + 1; 1351 [mInfoBar notify: IBNZoomChanged message: nil location: NSZeroPoint value: factor]; 1352 break; 1353 } 1354 case SCN_UPDATEUI: { 1355 // Triggered whenever changes in the UI state need to be reflected. 1356 // These can be: caret changes, selection changes etc. 1357 NSPoint caretPosition = mBackend->GetCaretPosition(); 1358 [mInfoBar notify: IBNCaretChanged message: nil location: caretPosition value: 0]; 1359 [self sendNotification: SCIUpdateUINotification]; 1360 if (scn->updated & (SC_UPDATE_SELECTION | SC_UPDATE_CONTENT)) { 1361 [self sendNotification: NSTextViewDidChangeSelectionNotification]; 1362 } 1363 break; 1364 } 1365 case SCN_FOCUSOUT: 1366 [self sendNotification: NSTextDidEndEditingNotification]; 1367 break; 1368 case SCN_FOCUSIN: // Nothing to do for now. 1369 break; 1370 } 1371} 1372 1373//-------------------------------------------------------------------------------------------------- 1374 1375/** 1376 * Setup a special indicator used in the editor to provide visual feedback for 1377 * input composition, depending on language, keyboard etc. 1378 */ 1379- (void) updateIndicatorIME { 1380 [self setColorProperty: SCI_INDICSETFORE parameter: INDICATOR_IME fromHTML: @"#FF0000"]; 1381 const bool drawInBackground = [self message: SCI_GETPHASESDRAW] != 0; 1382 [self setGeneralProperty: SCI_INDICSETUNDER parameter: INDICATOR_IME value: drawInBackground]; 1383 [self setGeneralProperty: SCI_INDICSETSTYLE parameter: INDICATOR_IME value: INDIC_PLAIN]; 1384 [self setGeneralProperty: SCI_INDICSETALPHA parameter: INDICATOR_IME value: 100]; 1385} 1386 1387//-------------------------------------------------------------------------------------------------- 1388 1389/** 1390 * Initialization of the view. Used to setup a few other things we need. 1391 */ 1392- (instancetype) initWithFrame: (NSRect) frame { 1393 self = [super initWithFrame: frame]; 1394 if (self) { 1395 mContent = [[[[self class] contentViewClass] alloc] initWithFrame: NSZeroRect]; 1396 mContent.owner = self; 1397 1398 // Initialize the scrollers but don't show them yet. 1399 // Pick an arbitrary size, just to make NSScroller selecting the proper scroller direction 1400 // (horizontal or vertical). 1401 NSRect scrollerRect = NSMakeRect(0, 0, 100, 10); 1402 scrollView = (NSScrollView *)[[SCIScrollView alloc] initWithFrame: scrollerRect]; 1403#if defined(MAC_OS_X_VERSION_10_14) 1404 // Let SCIScrollView account for other subviews such as vertical ruler by turning off 1405 // automaticallyAdjustsContentInsets. 1406 if (std::floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_13) { 1407 scrollView.contentView.automaticallyAdjustsContentInsets = NO; 1408 scrollView.contentView.contentInsets = NSEdgeInsetsMake(0., 0., 0., 0.); 1409 } 1410#endif 1411 scrollView.documentView = mContent; 1412 [scrollView setHasVerticalScroller: YES]; 1413 [scrollView setHasHorizontalScroller: YES]; 1414 scrollView.autoresizingMask = NSViewWidthSizable|NSViewHeightSizable; 1415 //[scrollView setScrollerStyle:NSScrollerStyleLegacy]; 1416 //[scrollView setScrollerKnobStyle:NSScrollerKnobStyleDark]; 1417 //[scrollView setHorizontalScrollElasticity:NSScrollElasticityNone]; 1418 [self addSubview: scrollView]; 1419 1420 marginView = [[SCIMarginView alloc] initWithScrollView: scrollView]; 1421 marginView.owner = self; 1422 marginView.ruleThickness = marginView.requiredThickness; 1423 scrollView.verticalRulerView = marginView; 1424 [scrollView setHasHorizontalRuler: NO]; 1425 [scrollView setHasVerticalRuler: YES]; 1426 [scrollView setRulersVisible: YES]; 1427 1428 mBackend = new ScintillaCocoa(self, mContent, marginView); 1429 1430 // Establish a connection from the back end to this container so we can handle situations 1431 // which require our attention. 1432 mBackend->SetDelegate(self); 1433 1434 [self updateIndicatorIME]; 1435 1436 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 1437 [center addObserver: self 1438 selector: @selector(applicationDidResignActive:) 1439 name: NSApplicationDidResignActiveNotification 1440 object: nil]; 1441 1442 [center addObserver: self 1443 selector: @selector(applicationDidBecomeActive:) 1444 name: NSApplicationDidBecomeActiveNotification 1445 object: nil]; 1446 1447 [center addObserver: self 1448 selector: @selector(windowWillMove:) 1449 name: NSWindowWillMoveNotification 1450 object: self.window]; 1451 1452 [scrollView.contentView setPostsBoundsChangedNotifications: YES]; 1453 [center addObserver: self 1454 selector: @selector(scrollerAction:) 1455 name: NSViewBoundsDidChangeNotification 1456 object: scrollView.contentView]; 1457 } 1458 return self; 1459} 1460 1461//-------------------------------------------------------------------------------------------------- 1462 1463- (void) dealloc { 1464 [[NSNotificationCenter defaultCenter] removeObserver: self]; 1465 mBackend->Finalise(); 1466 delete mBackend; 1467 mBackend = NULL; 1468 mContent.owner = nil; 1469 [marginView setClientView: nil]; 1470 [scrollView removeFromSuperview]; 1471} 1472 1473//-------------------------------------------------------------------------------------------------- 1474 1475- (void) applicationDidResignActive: (NSNotification *) note { 1476#pragma unused(note) 1477 mBackend->ActiveStateChanged(false); 1478} 1479 1480//-------------------------------------------------------------------------------------------------- 1481 1482- (void) applicationDidBecomeActive: (NSNotification *) note { 1483#pragma unused(note) 1484 mBackend->ActiveStateChanged(true); 1485} 1486 1487//-------------------------------------------------------------------------------------------------- 1488 1489- (void) windowWillMove: (NSNotification *) note { 1490#pragma unused(note) 1491 mBackend->WindowWillMove(); 1492} 1493 1494//-------------------------------------------------------------------------------------------------- 1495 1496- (void) viewDidMoveToWindow { 1497 [super viewDidMoveToWindow]; 1498 1499 [self positionSubViews]; 1500 1501 // Enable also mouse move events for our window (and so this view). 1502 [self.window setAcceptsMouseMovedEvents: YES]; 1503} 1504 1505//-------------------------------------------------------------------------------------------------- 1506 1507/** 1508 * Used to position and size the parts of the editor (content, scrollers, info bar). 1509 */ 1510- (void) positionSubViews { 1511 CGFloat scrollerWidth = [NSScroller scrollerWidthForControlSize: NSControlSizeRegular 1512 scrollerStyle: NSScrollerStyleLegacy]; 1513 1514 NSSize size = self.frame.size; 1515 NSRect barFrame = {{0, size.height - scrollerWidth}, {size.width, scrollerWidth}}; 1516 BOOL infoBarVisible = mInfoBar != nil && !mInfoBar.hidden; 1517 1518 // Horizontal offset of the content. Almost always 0 unless the vertical scroller 1519 // is on the left side. 1520 CGFloat contentX = 0; 1521 NSRect scrollRect = {{contentX, 0}, {size.width, size.height}}; 1522 1523 // Info bar frame. 1524 if (infoBarVisible) { 1525 scrollRect.size.height -= scrollerWidth; 1526 // Initial value already is as if the bar is at top. 1527 if (!mInfoBarAtTop) { 1528 scrollRect.origin.y += scrollerWidth; 1529 barFrame.origin.y = 0; 1530 } 1531 } 1532 1533 if (!NSEqualRects(scrollView.frame, scrollRect)) { 1534 scrollView.frame = scrollRect; 1535 } 1536 1537 if (infoBarVisible) 1538 mInfoBar.frame = barFrame; 1539} 1540 1541//-------------------------------------------------------------------------------------------------- 1542 1543/** 1544 * Set the width of the margin. 1545 */ 1546- (void) setMarginWidth: (int) width { 1547 if (marginView.ruleThickness != width) { 1548 marginView.marginWidth = width; 1549 marginView.ruleThickness = marginView.requiredThickness; 1550 } 1551} 1552 1553//-------------------------------------------------------------------------------------------------- 1554 1555/** 1556 * Triggered by one of the scrollers when it gets manipulated by the user. Notify the backend 1557 * about the change. 1558 */ 1559- (void) scrollerAction: (id) sender { 1560#pragma unused(sender) 1561 mBackend->UpdateForScroll(); 1562} 1563 1564//-------------------------------------------------------------------------------------------------- 1565 1566/** 1567 * Used to reposition our content depending on the size of the view. 1568 */ 1569- (void) setFrame: (NSRect) newFrame { 1570 NSRect previousFrame = self.frame; 1571 super.frame = newFrame; 1572 [self positionSubViews]; 1573 if (!NSEqualRects(previousFrame, newFrame)) { 1574 mBackend->Resize(); 1575 } 1576} 1577 1578//-------------------------------------------------------------------------------------------------- 1579 1580/** 1581 * Getter for the currently selected text in raw form (no formatting information included). 1582 * If there is no text available an empty string is returned. 1583 */ 1584- (NSString *) selectedString { 1585 NSString *result = @""; 1586 1587 const long length = mBackend->WndProc(SCI_GETSELTEXT, 0, 0); 1588 if (length > 0) { 1589 std::string buffer(length + 1, '\0'); 1590 try { 1591 mBackend->WndProc(SCI_GETSELTEXT, length + 1, (sptr_t) &buffer[0]); 1592 1593 result = @(buffer.c_str()); 1594 } catch (...) { 1595 } 1596 } 1597 1598 return result; 1599} 1600 1601//-------------------------------------------------------------------------------------------------- 1602 1603/** 1604 * Delete a range from the document. 1605 */ 1606- (void) deleteRange: (NSRange) aRange { 1607 if (aRange.length > 0) { 1608 NSRange posRange = mBackend->PositionsFromCharacters(aRange); 1609 [self message: SCI_DELETERANGE wParam: posRange.location lParam: posRange.length]; 1610 } 1611} 1612 1613//-------------------------------------------------------------------------------------------------- 1614 1615/** 1616 * Getter for the current text in raw form (no formatting information included). 1617 * If there is no text available an empty string is returned. 1618 */ 1619- (NSString *) string { 1620 NSString *result = @""; 1621 1622 const long length = mBackend->WndProc(SCI_GETLENGTH, 0, 0); 1623 if (length > 0) { 1624 std::string buffer(length + 1, '\0'); 1625 try { 1626 mBackend->WndProc(SCI_GETTEXT, length + 1, (sptr_t) &buffer[0]); 1627 1628 result = @(buffer.c_str()); 1629 } catch (...) { 1630 } 1631 } 1632 1633 return result; 1634} 1635 1636//-------------------------------------------------------------------------------------------------- 1637 1638/** 1639 * Setter for the current text (no formatting included). 1640 */ 1641- (void) setString: (NSString *) aString { 1642 const char *text = aString.UTF8String; 1643 mBackend->WndProc(SCI_SETTEXT, 0, (long) text); 1644} 1645 1646//-------------------------------------------------------------------------------------------------- 1647 1648- (void) insertString: (NSString *) aString atOffset: (int) offset { 1649 const char *text = aString.UTF8String; 1650 mBackend->WndProc(SCI_ADDTEXT, offset, (long) text); 1651} 1652 1653//-------------------------------------------------------------------------------------------------- 1654 1655- (void) setEditable: (BOOL) editable { 1656 mBackend->WndProc(SCI_SETREADONLY, editable ? 0 : 1, 0); 1657} 1658 1659//-------------------------------------------------------------------------------------------------- 1660 1661- (BOOL) isEditable { 1662 return mBackend->WndProc(SCI_GETREADONLY, 0, 0) == 0; 1663} 1664 1665//-------------------------------------------------------------------------------------------------- 1666 1667- (SCIContentView *) content { 1668 return mContent; 1669} 1670 1671//-------------------------------------------------------------------------------------------------- 1672 1673- (void) updateMarginCursors { 1674 [self.window invalidateCursorRectsForView: marginView]; 1675} 1676 1677//-------------------------------------------------------------------------------------------------- 1678 1679/** 1680 * Direct call into the backend to allow uninterpreted access to it. The values to be passed in and 1681 * the result heavily depend on the message that is used for the call. Refer to the Scintilla 1682 * documentation to learn what can be used here. 1683 */ 1684+ (sptr_t) directCall: (ScintillaView *) sender message: (unsigned int) message wParam: (uptr_t) wParam 1685 lParam: (sptr_t) lParam { 1686 return ScintillaCocoa::DirectFunction( 1687 reinterpret_cast<sptr_t>(sender->mBackend), message, wParam, lParam); 1688} 1689 1690- (sptr_t) message: (unsigned int) message wParam: (uptr_t) wParam lParam: (sptr_t) lParam { 1691 return mBackend->WndProc(message, wParam, lParam); 1692} 1693 1694- (sptr_t) message: (unsigned int) message wParam: (uptr_t) wParam { 1695 return mBackend->WndProc(message, wParam, 0); 1696} 1697 1698- (sptr_t) message: (unsigned int) message { 1699 return mBackend->WndProc(message, 0, 0); 1700} 1701 1702//-------------------------------------------------------------------------------------------------- 1703 1704/** 1705 * This is a helper method to set properties in the backend, with native parameters. 1706 * 1707 * @param property Main property like SCI_STYLESETFORE for which a value is to be set. 1708 * @param parameter Additional info for this property like a parameter or index. 1709 * @param value The actual value. It depends on the property what this parameter means. 1710 */ 1711- (void) setGeneralProperty: (int) property parameter: (long) parameter value: (long) value { 1712 mBackend->WndProc(property, parameter, value); 1713} 1714 1715//-------------------------------------------------------------------------------------------------- 1716 1717/** 1718 * A simplified version for setting properties which only require one parameter. 1719 * 1720 * @param property Main property like SCI_STYLESETFORE for which a value is to be set. 1721 * @param value The actual value. It depends on the property what this parameter means. 1722 */ 1723- (void) setGeneralProperty: (int) property value: (long) value { 1724 mBackend->WndProc(property, value, 0); 1725} 1726 1727//-------------------------------------------------------------------------------------------------- 1728 1729/** 1730 * This is a helper method to get a property in the backend, with native parameters. 1731 * 1732 * @param property Main property like SCI_STYLESETFORE for which a value is to get. 1733 * @param parameter Additional info for this property like a parameter or index. 1734 * @param extra Yet another parameter if needed. 1735 * @result A generic value which must be interpreted depending on the property queried. 1736 */ 1737- (long) getGeneralProperty: (int) property parameter: (long) parameter extra: (long) extra { 1738 return mBackend->WndProc(property, parameter, extra); 1739} 1740 1741//-------------------------------------------------------------------------------------------------- 1742 1743/** 1744 * Convenience function to avoid unneeded extra parameter. 1745 */ 1746- (long) getGeneralProperty: (int) property parameter: (long) parameter { 1747 return mBackend->WndProc(property, parameter, 0); 1748} 1749 1750//-------------------------------------------------------------------------------------------------- 1751 1752/** 1753 * Convenience function to avoid unneeded parameters. 1754 */ 1755- (long) getGeneralProperty: (int) property { 1756 return mBackend->WndProc(property, 0, 0); 1757} 1758 1759//-------------------------------------------------------------------------------------------------- 1760 1761/** 1762 * Use this variant if you have to pass in a reference to something (e.g. a text range). 1763 */ 1764- (long) getGeneralProperty: (int) property ref: (const void *) ref { 1765 return mBackend->WndProc(property, 0, (sptr_t) ref); 1766} 1767 1768//-------------------------------------------------------------------------------------------------- 1769 1770/** 1771 * Specialized property setter for colors. 1772 */ 1773- (void) setColorProperty: (int) property parameter: (long) parameter value: (NSColor *) value { 1774 if (value.colorSpaceName != NSDeviceRGBColorSpace) 1775 value = [value colorUsingColorSpaceName: NSDeviceRGBColorSpace]; 1776 long red = static_cast<long>(value.redComponent * 255); 1777 long green = static_cast<long>(value.greenComponent * 255); 1778 long blue = static_cast<long>(value.blueComponent * 255); 1779 1780 long color = (blue << 16) + (green << 8) + red; 1781 mBackend->WndProc(property, parameter, color); 1782} 1783 1784//-------------------------------------------------------------------------------------------------- 1785 1786/** 1787 * Another color property setting, which allows to specify the color as string like in HTML 1788 * documents (i.e. with leading # and either 3 hex digits or 6). 1789 */ 1790- (void) setColorProperty: (int) property parameter: (long) parameter fromHTML: (NSString *) fromHTML { 1791 if (fromHTML.length > 3 && [fromHTML characterAtIndex: 0] == '#') { 1792 bool longVersion = fromHTML.length > 6; 1793 int index = 1; 1794 1795 char value[3] = {0, 0, 0}; 1796 value[0] = static_cast<char>([fromHTML characterAtIndex: index++]); 1797 if (longVersion) 1798 value[1] = static_cast<char>([fromHTML characterAtIndex: index++]); 1799 else 1800 value[1] = value[0]; 1801 1802 unsigned rawRed; 1803 [[NSScanner scannerWithString: @(value)] scanHexInt: &rawRed]; 1804 1805 value[0] = static_cast<char>([fromHTML characterAtIndex: index++]); 1806 if (longVersion) 1807 value[1] = static_cast<char>([fromHTML characterAtIndex: index++]); 1808 else 1809 value[1] = value[0]; 1810 1811 unsigned rawGreen; 1812 [[NSScanner scannerWithString: @(value)] scanHexInt: &rawGreen]; 1813 1814 value[0] = static_cast<char>([fromHTML characterAtIndex: index++]); 1815 if (longVersion) 1816 value[1] = static_cast<char>([fromHTML characterAtIndex: index++]); 1817 else 1818 value[1] = value[0]; 1819 1820 unsigned rawBlue; 1821 [[NSScanner scannerWithString: @(value)] scanHexInt: &rawBlue]; 1822 1823 long color = (rawBlue << 16) + (rawGreen << 8) + rawRed; 1824 mBackend->WndProc(property, parameter, color); 1825 } 1826} 1827 1828//-------------------------------------------------------------------------------------------------- 1829 1830/** 1831 * Specialized property getter for colors. 1832 */ 1833- (NSColor *) getColorProperty: (int) property parameter: (long) parameter { 1834 long color = mBackend->WndProc(property, parameter, 0); 1835 CGFloat red = (color & 0xFF) / 255.0; 1836 CGFloat green = ((color >> 8) & 0xFF) / 255.0; 1837 CGFloat blue = ((color >> 16) & 0xFF) / 255.0; 1838 NSColor *result = [NSColor colorWithDeviceRed: red green: green blue: blue alpha: 1]; 1839 return result; 1840} 1841 1842//-------------------------------------------------------------------------------------------------- 1843 1844/** 1845 * Specialized property setter for references (pointers, addresses). 1846 */ 1847- (void) setReferenceProperty: (int) property parameter: (long) parameter value: (const void *) value { 1848 mBackend->WndProc(property, parameter, (sptr_t) value); 1849} 1850 1851//-------------------------------------------------------------------------------------------------- 1852 1853/** 1854 * Specialized property getter for references (pointers, addresses). 1855 */ 1856- (const void *) getReferenceProperty: (int) property parameter: (long) parameter { 1857 return (const void *) mBackend->WndProc(property, parameter, 0); 1858} 1859 1860//-------------------------------------------------------------------------------------------------- 1861 1862/** 1863 * Specialized property setter for string values. 1864 */ 1865- (void) setStringProperty: (int) property parameter: (long) parameter value: (NSString *) value { 1866 const char *rawValue = value.UTF8String; 1867 mBackend->WndProc(property, parameter, (sptr_t) rawValue); 1868} 1869 1870 1871//-------------------------------------------------------------------------------------------------- 1872 1873/** 1874 * Specialized property getter for string values. 1875 */ 1876- (NSString *) getStringProperty: (int) property parameter: (long) parameter { 1877 const char *rawValue = (const char *) mBackend->WndProc(property, parameter, 0); 1878 return @(rawValue); 1879} 1880 1881//-------------------------------------------------------------------------------------------------- 1882 1883/** 1884 * Specialized property setter for lexer properties, which are commonly passed as strings. 1885 */ 1886- (void) setLexerProperty: (NSString *) name value: (NSString *) value { 1887 const char *rawName = name.UTF8String; 1888 const char *rawValue = value.UTF8String; 1889 mBackend->WndProc(SCI_SETPROPERTY, (sptr_t) rawName, (sptr_t) rawValue); 1890} 1891 1892//-------------------------------------------------------------------------------------------------- 1893 1894/** 1895 * Specialized property getter for references (pointers, addresses). 1896 */ 1897- (NSString *) getLexerProperty: (NSString *) name { 1898 const char *rawName = name.UTF8String; 1899 const char *result = (const char *) mBackend->WndProc(SCI_SETPROPERTY, (sptr_t) rawName, 0); 1900 return @(result); 1901} 1902 1903//-------------------------------------------------------------------------------------------------- 1904 1905/** 1906 * Sets the notification callback 1907 */ 1908- (void) registerNotifyCallback: (intptr_t) windowid value: (SciNotifyFunc) callback { 1909 mBackend->RegisterNotifyCallback(windowid, callback); 1910} 1911 1912 1913//-------------------------------------------------------------------------------------------------- 1914 1915/** 1916 * Sets the new control which is displayed as info bar at the top or bottom of the editor. 1917 * Set newBar to nil if you want to hide the bar again. 1918 * The info bar's height is set to the height of the scrollbar. 1919 */ 1920- (void) setInfoBar: (NSView <InfoBarCommunicator> *) newBar top: (BOOL) top { 1921 if (mInfoBar != newBar) { 1922 [mInfoBar removeFromSuperview]; 1923 1924 mInfoBar = newBar; 1925 mInfoBarAtTop = top; 1926 if (mInfoBar != nil) { 1927 [self addSubview: mInfoBar]; 1928 [mInfoBar setCallback: self]; 1929 } 1930 1931 [self positionSubViews]; 1932 } 1933} 1934 1935//-------------------------------------------------------------------------------------------------- 1936 1937/** 1938 * Sets the edit's info bar status message. This call only has an effect if there is an info bar. 1939 */ 1940- (void) setStatusText: (NSString *) text { 1941 if (mInfoBar != nil) 1942 [mInfoBar notify: IBNStatusChanged message: text location: NSZeroPoint value: 0]; 1943} 1944 1945//-------------------------------------------------------------------------------------------------- 1946 1947- (NSRange) selectedRange { 1948 return [mContent selectedRange]; 1949} 1950 1951//-------------------------------------------------------------------------------------------------- 1952 1953/** 1954 * Return the main selection as an NSRange of positions (not characters). 1955 * Unlike selectedRange, this can return empty ranges inside the document. 1956 */ 1957 1958- (NSRange) selectedRangePositions { 1959 const sptr_t positionBegin = [self message: SCI_GETSELECTIONSTART]; 1960 const sptr_t positionEnd = [self message: SCI_GETSELECTIONEND]; 1961 return NSMakeRange(positionBegin, positionEnd-positionBegin); 1962} 1963 1964 1965//-------------------------------------------------------------------------------------------------- 1966 1967- (void) insertText: (id) aString { 1968 if ([aString isKindOfClass: [NSString class]]) 1969 mBackend->InsertText(aString, EditModel::CharacterSource::directInput); 1970 else if ([aString isKindOfClass: [NSAttributedString class]]) 1971 mBackend->InsertText([aString string], EditModel::CharacterSource::directInput); 1972} 1973 1974//-------------------------------------------------------------------------------------------------- 1975 1976/** 1977 * For backwards compatibility. 1978 */ 1979- (BOOL) findAndHighlightText: (NSString *) searchText 1980 matchCase: (BOOL) matchCase 1981 wholeWord: (BOOL) wholeWord 1982 scrollTo: (BOOL) scrollTo 1983 wrap: (BOOL) wrap { 1984 return [self findAndHighlightText: searchText 1985 matchCase: matchCase 1986 wholeWord: wholeWord 1987 scrollTo: scrollTo 1988 wrap: wrap 1989 backwards: NO]; 1990} 1991 1992//-------------------------------------------------------------------------------------------------- 1993 1994/** 1995 * Searches and marks the first occurrence of the given text and optionally scrolls it into view. 1996 * 1997 * @result YES if something was found, NO otherwise. 1998 */ 1999- (BOOL) findAndHighlightText: (NSString *) searchText 2000 matchCase: (BOOL) matchCase 2001 wholeWord: (BOOL) wholeWord 2002 scrollTo: (BOOL) scrollTo 2003 wrap: (BOOL) wrap 2004 backwards: (BOOL) backwards { 2005 int searchFlags= 0; 2006 if (matchCase) 2007 searchFlags |= SCFIND_MATCHCASE; 2008 if (wholeWord) 2009 searchFlags |= SCFIND_WHOLEWORD; 2010 2011 long selectionStart = [self getGeneralProperty: SCI_GETSELECTIONSTART parameter: 0]; 2012 long selectionEnd = [self getGeneralProperty: SCI_GETSELECTIONEND parameter: 0]; 2013 2014 // Sets the start point for the coming search to the beginning of the current selection. 2015 // For forward searches we have therefore to set the selection start to the current selection end 2016 // for proper incremental search. This does not harm as we either get a new selection if something 2017 // is found or the previous selection is restored. 2018 if (!backwards) 2019 [self getGeneralProperty: SCI_SETSELECTIONSTART parameter: selectionEnd]; 2020 [self setGeneralProperty: SCI_SEARCHANCHOR value: 0]; 2021 sptr_t result; 2022 const char *textToSearch = searchText.UTF8String; 2023 2024 // The following call will also set the selection if something was found. 2025 if (backwards) { 2026 result = [ScintillaView directCall: self 2027 message: SCI_SEARCHPREV 2028 wParam: searchFlags 2029 lParam: (sptr_t) textToSearch]; 2030 if (result < 0 && wrap) { 2031 // Try again from the end of the document if nothing could be found so far and 2032 // wrapped search is set. 2033 [self getGeneralProperty: SCI_SETSELECTIONSTART parameter: [self getGeneralProperty: SCI_GETTEXTLENGTH parameter: 0]]; 2034 [self setGeneralProperty: SCI_SEARCHANCHOR value: 0]; 2035 result = [ScintillaView directCall: self 2036 message: SCI_SEARCHNEXT 2037 wParam: searchFlags 2038 lParam: (sptr_t) textToSearch]; 2039 } 2040 } else { 2041 result = [ScintillaView directCall: self 2042 message: SCI_SEARCHNEXT 2043 wParam: searchFlags 2044 lParam: (sptr_t) textToSearch]; 2045 if (result < 0 && wrap) { 2046 // Try again from the start of the document if nothing could be found so far and 2047 // wrapped search is set. 2048 [self getGeneralProperty: SCI_SETSELECTIONSTART parameter: 0]; 2049 [self setGeneralProperty: SCI_SEARCHANCHOR value: 0]; 2050 result = [ScintillaView directCall: self 2051 message: SCI_SEARCHNEXT 2052 wParam: searchFlags 2053 lParam: (sptr_t) textToSearch]; 2054 } 2055 } 2056 2057 if (result >= 0) { 2058 if (scrollTo) 2059 [self setGeneralProperty: SCI_SCROLLCARET value: 0]; 2060 } else { 2061 // Restore the former selection if we did not find anything. 2062 [self setGeneralProperty: SCI_SETSELECTIONSTART value: selectionStart]; 2063 [self setGeneralProperty: SCI_SETSELECTIONEND value: selectionEnd]; 2064 } 2065 return (result >= 0) ? YES : NO; 2066} 2067 2068//-------------------------------------------------------------------------------------------------- 2069 2070/** 2071 * Searches the given text and replaces 2072 * 2073 * @result Number of entries replaced, 0 if none. 2074 */ 2075- (int) findAndReplaceText: (NSString *) searchText 2076 byText: (NSString *) newText 2077 matchCase: (BOOL) matchCase 2078 wholeWord: (BOOL) wholeWord 2079 doAll: (BOOL) doAll { 2080 // The current position is where we start searching for single occurrences. Otherwise we start at 2081 // the beginning of the document. 2082 long startPosition; 2083 if (doAll) 2084 startPosition = 0; // Start at the beginning of the text if we replace all occurrences. 2085 else 2086 // For a single replacement we start at the current caret position. 2087 startPosition = [self getGeneralProperty: SCI_GETCURRENTPOS]; 2088 long endPosition = [self getGeneralProperty: SCI_GETTEXTLENGTH]; 2089 2090 int searchFlags= 0; 2091 if (matchCase) 2092 searchFlags |= SCFIND_MATCHCASE; 2093 if (wholeWord) 2094 searchFlags |= SCFIND_WHOLEWORD; 2095 [self setGeneralProperty: SCI_SETSEARCHFLAGS value: searchFlags]; 2096 [self setGeneralProperty: SCI_SETTARGETSTART value: startPosition]; 2097 [self setGeneralProperty: SCI_SETTARGETEND value: endPosition]; 2098 2099 const char *textToSearch = searchText.UTF8String; 2100 long sourceLength = strlen(textToSearch); // Length in bytes. 2101 const char *replacement = newText.UTF8String; 2102 long targetLength = strlen(replacement); // Length in bytes. 2103 sptr_t result; 2104 2105 int replaceCount = 0; 2106 if (doAll) { 2107 while (true) { 2108 result = [ScintillaView directCall: self 2109 message: SCI_SEARCHINTARGET 2110 wParam: sourceLength 2111 lParam: (sptr_t) textToSearch]; 2112 if (result < 0) 2113 break; 2114 2115 replaceCount++; 2116 [ScintillaView directCall: self 2117 message: SCI_REPLACETARGET 2118 wParam: targetLength 2119 lParam: (sptr_t) replacement]; 2120 2121 // The replacement changes the target range to the replaced text. Continue after that till the end. 2122 // The text length might be changed by the replacement so make sure the target end is the actual 2123 // text end. 2124 [self setGeneralProperty: SCI_SETTARGETSTART value: [self getGeneralProperty: SCI_GETTARGETEND]]; 2125 [self setGeneralProperty: SCI_SETTARGETEND value: [self getGeneralProperty: SCI_GETTEXTLENGTH]]; 2126 } 2127 } else { 2128 result = [ScintillaView directCall: self 2129 message: SCI_SEARCHINTARGET 2130 wParam: sourceLength 2131 lParam: (sptr_t) textToSearch]; 2132 replaceCount = (result < 0) ? 0 : 1; 2133 2134 if (replaceCount > 0) { 2135 [ScintillaView directCall: self 2136 message: SCI_REPLACETARGET 2137 wParam: targetLength 2138 lParam: (sptr_t) replacement]; 2139 2140 // For a single replace we set the new selection to the replaced text. 2141 [self setGeneralProperty: SCI_SETSELECTIONSTART value: [self getGeneralProperty: SCI_GETTARGETSTART]]; 2142 [self setGeneralProperty: SCI_SETSELECTIONEND value: [self getGeneralProperty: SCI_GETTARGETEND]]; 2143 } 2144 } 2145 2146 return replaceCount; 2147} 2148 2149//-------------------------------------------------------------------------------------------------- 2150 2151- (void) setFontName: (NSString *) font 2152 size: (int) size 2153 bold: (BOOL) bold 2154 italic: (BOOL) italic { 2155 for (int i = 0; i < 128; i++) { 2156 [self setGeneralProperty: SCI_STYLESETFONT 2157 parameter: i 2158 value: (sptr_t)font.UTF8String]; 2159 [self setGeneralProperty: SCI_STYLESETSIZE 2160 parameter: i 2161 value: size]; 2162 [self setGeneralProperty: SCI_STYLESETBOLD 2163 parameter: i 2164 value: bold]; 2165 [self setGeneralProperty: SCI_STYLESETITALIC 2166 parameter: i 2167 value: italic]; 2168 } 2169} 2170 2171//-------------------------------------------------------------------------------------------------- 2172 2173@end 2174 2175