1 2/** 3 * Scintilla source code edit control 4 * @file ScintillaCocoa.mm - Cocoa subclass of ScintillaBase 5 * 6 * Written by Mike Lischke <mlischke@sun.com> 7 * 8 * Loosely based on ScintillaMacOSX.cxx. 9 * Copyright 2003 by Evan Jones <ejones@uwaterloo.ca> 10 * Based on ScintillaGTK.cxx Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org> 11 * The License.txt file describes the conditions under which this software may be distributed. 12 * 13 * Copyright (c) 2009, 2010 Sun Microsystems, Inc. All rights reserved. 14 * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt). 15 */ 16 17#include <cmath> 18 19#include <string_view> 20#include <vector> 21 22#import <Cocoa/Cocoa.h> 23#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 24#import <QuartzCore/CAGradientLayer.h> 25#endif 26#import <QuartzCore/CAAnimation.h> 27#import <QuartzCore/CATransaction.h> 28 29#import "Platform.h" 30#import "ScintillaView.h" 31#import "ScintillaCocoa.h" 32#import "PlatCocoa.h" 33 34using namespace Scintilla; 35 36NSString *ScintillaRecPboardType = @"com.scintilla.utf16-plain-text.rectangular"; 37 38//-------------------------------------------------------------------------------------------------- 39 40// Define keyboard shortcuts (equivalents) the Mac way. 41#define SCI_CMD ( SCI_CTRL) 42#define SCI_SCMD ( SCI_CMD | SCI_SHIFT) 43#define SCI_SMETA ( SCI_META | SCI_SHIFT) 44 45static const KeyToCommand macMapDefault[] = { 46 // OS X specific 47 {SCK_DOWN, SCI_CTRL, SCI_DOCUMENTEND}, 48 {SCK_DOWN, SCI_CSHIFT, SCI_DOCUMENTENDEXTEND}, 49 {SCK_UP, SCI_CTRL, SCI_DOCUMENTSTART}, 50 {SCK_UP, SCI_CSHIFT, SCI_DOCUMENTSTARTEXTEND}, 51 {SCK_LEFT, SCI_CTRL, SCI_VCHOME}, 52 {SCK_LEFT, SCI_CSHIFT, SCI_VCHOMEEXTEND}, 53 {SCK_RIGHT, SCI_CTRL, SCI_LINEEND}, 54 {SCK_RIGHT, SCI_CSHIFT, SCI_LINEENDEXTEND}, 55 56 // Similar to Windows and GTK+ 57 // Where equivalent clashes with OS X standard, use Meta instead 58 {SCK_DOWN, SCI_NORM, SCI_LINEDOWN}, 59 {SCK_DOWN, SCI_SHIFT, SCI_LINEDOWNEXTEND}, 60 {SCK_DOWN, SCI_META, SCI_LINESCROLLDOWN}, 61 {SCK_DOWN, SCI_ASHIFT, SCI_LINEDOWNRECTEXTEND}, 62 {SCK_UP, SCI_NORM, SCI_LINEUP}, 63 {SCK_UP, SCI_SHIFT, SCI_LINEUPEXTEND}, 64 {SCK_UP, SCI_META, SCI_LINESCROLLUP}, 65 {SCK_UP, SCI_ASHIFT, SCI_LINEUPRECTEXTEND}, 66 {'[', SCI_CTRL, SCI_PARAUP}, 67 {'[', SCI_CSHIFT, SCI_PARAUPEXTEND}, 68 {']', SCI_CTRL, SCI_PARADOWN}, 69 {']', SCI_CSHIFT, SCI_PARADOWNEXTEND}, 70 {SCK_LEFT, SCI_NORM, SCI_CHARLEFT}, 71 {SCK_LEFT, SCI_SHIFT, SCI_CHARLEFTEXTEND}, 72 {SCK_LEFT, SCI_ALT, SCI_WORDLEFT}, 73 {SCK_LEFT, SCI_META, SCI_WORDLEFT}, 74 {SCK_LEFT, SCI_SMETA, SCI_WORDLEFTEXTEND}, 75 {SCK_LEFT, SCI_ASHIFT, SCI_CHARLEFTRECTEXTEND}, 76 {SCK_RIGHT, SCI_NORM, SCI_CHARRIGHT}, 77 {SCK_RIGHT, SCI_SHIFT, SCI_CHARRIGHTEXTEND}, 78 {SCK_RIGHT, SCI_ALT, SCI_WORDRIGHT}, 79 {SCK_RIGHT, SCI_META, SCI_WORDRIGHT}, 80 {SCK_RIGHT, SCI_SMETA, SCI_WORDRIGHTEXTEND}, 81 {SCK_RIGHT, SCI_ASHIFT, SCI_CHARRIGHTRECTEXTEND}, 82 {'/', SCI_CTRL, SCI_WORDPARTLEFT}, 83 {'/', SCI_CSHIFT, SCI_WORDPARTLEFTEXTEND}, 84 {'\\', SCI_CTRL, SCI_WORDPARTRIGHT}, 85 {'\\', SCI_CSHIFT, SCI_WORDPARTRIGHTEXTEND}, 86 {SCK_HOME, SCI_NORM, SCI_VCHOME}, 87 {SCK_HOME, SCI_SHIFT, SCI_VCHOMEEXTEND}, 88 {SCK_HOME, SCI_CTRL, SCI_DOCUMENTSTART}, 89 {SCK_HOME, SCI_CSHIFT, SCI_DOCUMENTSTARTEXTEND}, 90 {SCK_HOME, SCI_ALT, SCI_HOMEDISPLAY}, 91 {SCK_HOME, SCI_ASHIFT, SCI_VCHOMERECTEXTEND}, 92 {SCK_END, SCI_NORM, SCI_LINEEND}, 93 {SCK_END, SCI_SHIFT, SCI_LINEENDEXTEND}, 94 {SCK_END, SCI_CTRL, SCI_DOCUMENTEND}, 95 {SCK_END, SCI_CSHIFT, SCI_DOCUMENTENDEXTEND}, 96 {SCK_END, SCI_ALT, SCI_LINEENDDISPLAY}, 97 {SCK_END, SCI_ASHIFT, SCI_LINEENDRECTEXTEND}, 98 {SCK_PRIOR, SCI_NORM, SCI_PAGEUP}, 99 {SCK_PRIOR, SCI_SHIFT, SCI_PAGEUPEXTEND}, 100 {SCK_PRIOR, SCI_ASHIFT, SCI_PAGEUPRECTEXTEND}, 101 {SCK_NEXT, SCI_NORM, SCI_PAGEDOWN}, 102 {SCK_NEXT, SCI_SHIFT, SCI_PAGEDOWNEXTEND}, 103 {SCK_NEXT, SCI_ASHIFT, SCI_PAGEDOWNRECTEXTEND}, 104 {SCK_DELETE, SCI_NORM, SCI_CLEAR}, 105 {SCK_DELETE, SCI_SHIFT, SCI_CUT}, 106 {SCK_DELETE, SCI_CTRL, SCI_DELWORDRIGHT}, 107 {SCK_DELETE, SCI_CSHIFT, SCI_DELLINERIGHT}, 108 {SCK_INSERT, SCI_NORM, SCI_EDITTOGGLEOVERTYPE}, 109 {SCK_INSERT, SCI_SHIFT, SCI_PASTE}, 110 {SCK_INSERT, SCI_CTRL, SCI_COPY}, 111 {SCK_ESCAPE, SCI_NORM, SCI_CANCEL}, 112 {SCK_BACK, SCI_NORM, SCI_DELETEBACK}, 113 {SCK_BACK, SCI_SHIFT, SCI_DELETEBACK}, 114 {SCK_BACK, SCI_CTRL, SCI_DELWORDLEFT}, 115 {SCK_BACK, SCI_ALT, SCI_DELWORDLEFT}, 116 {SCK_BACK, SCI_CSHIFT, SCI_DELLINELEFT}, 117 {'z', SCI_CMD, SCI_UNDO}, 118 {'z', SCI_SCMD, SCI_REDO}, 119 {'x', SCI_CMD, SCI_CUT}, 120 {'c', SCI_CMD, SCI_COPY}, 121 {'v', SCI_CMD, SCI_PASTE}, 122 {'a', SCI_CMD, SCI_SELECTALL}, 123 {SCK_TAB, SCI_NORM, SCI_TAB}, 124 {SCK_TAB, SCI_SHIFT, SCI_BACKTAB}, 125 {SCK_RETURN, SCI_NORM, SCI_NEWLINE}, 126 {SCK_RETURN, SCI_SHIFT, SCI_NEWLINE}, 127 {SCK_ADD, SCI_CMD, SCI_ZOOMIN}, 128 {SCK_SUBTRACT, SCI_CMD, SCI_ZOOMOUT}, 129 {SCK_DIVIDE, SCI_CMD, SCI_SETZOOM}, 130 {'l', SCI_CMD, SCI_LINECUT}, 131 {'l', SCI_CSHIFT, SCI_LINEDELETE}, 132 {'t', SCI_CSHIFT, SCI_LINECOPY}, 133 {'t', SCI_CTRL, SCI_LINETRANSPOSE}, 134 {'d', SCI_CTRL, SCI_SELECTIONDUPLICATE}, 135 {'u', SCI_CTRL, SCI_LOWERCASE}, 136 {'u', SCI_CSHIFT, SCI_UPPERCASE}, 137 {0, 0, 0}, 138}; 139 140//-------------------------------------------------------------------------------------------------- 141 142#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 143 144// Only implement FindHighlightLayer on OS X 10.6+ 145 146/** 147 * Class to display the animated gold roundrect used on OS X for matches. 148 */ 149@interface FindHighlightLayer : CAGradientLayer { 150@private 151 NSString *sFind; 152 long positionFind; 153 BOOL retaining; 154 CGFloat widthText; 155 CGFloat heightLine; 156 NSString *sFont; 157 CGFloat fontSize; 158} 159 160@property(copy) NSString *sFind; 161@property(assign) long positionFind; 162@property(assign) BOOL retaining; 163@property(assign) CGFloat widthText; 164@property(assign) CGFloat heightLine; 165@property(copy) NSString *sFont; 166@property(assign) CGFloat fontSize; 167 168- (void) animateMatch: (CGPoint) ptText bounce: (BOOL) bounce; 169- (void) hideMatch; 170 171@end 172 173//-------------------------------------------------------------------------------------------------- 174 175@implementation FindHighlightLayer 176 177@synthesize sFind, positionFind, retaining, widthText, heightLine, sFont, fontSize; 178 179- (id) init { 180 if (self = [super init]) { 181 [self setNeedsDisplayOnBoundsChange: YES]; 182 // A gold to slightly redder gradient to match other applications 183 CGColorRef colGold = CGColorCreateGenericRGB(1.0, 1.0, 0, 1.0); 184 CGColorRef colGoldRed = CGColorCreateGenericRGB(1.0, 0.8, 0, 1.0); 185 self.colors = @[(__bridge id)colGoldRed, (__bridge id)colGold]; 186 CGColorRelease(colGoldRed); 187 CGColorRelease(colGold); 188 189 CGColorRef colGreyBorder = CGColorCreateGenericGray(0.756f, 0.5f); 190 self.borderColor = colGreyBorder; 191 CGColorRelease(colGreyBorder); 192 193 self.borderWidth = 1.0; 194 self.cornerRadius = 5.0f; 195 self.shadowRadius = 1.0f; 196 self.shadowOpacity = 0.9f; 197 self.shadowOffset = CGSizeMake(0.0f, -2.0f); 198 self.anchorPoint = CGPointMake(0.5, 0.5); 199 } 200 return self; 201 202} 203 204 205const CGFloat paddingHighlightX = 4; 206const CGFloat paddingHighlightY = 2; 207 208- (void) drawInContext: (CGContextRef) context { 209 if (!sFind || !sFont) 210 return; 211 212 CFStringRef str = (__bridge CFStringRef)(sFind); 213 214 CFMutableDictionaryRef styleDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, 215 &kCFTypeDictionaryKeyCallBacks, 216 &kCFTypeDictionaryValueCallBacks); 217 CGColorRef color = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 1.0); 218 CFDictionarySetValue(styleDict, kCTForegroundColorAttributeName, color); 219 CTFontRef fontRef = ::CTFontCreateWithName((CFStringRef)sFont, fontSize, NULL); 220 CFDictionaryAddValue(styleDict, kCTFontAttributeName, fontRef); 221 222 CFAttributedStringRef attrString = ::CFAttributedStringCreate(NULL, str, styleDict); 223 CTLineRef textLine = ::CTLineCreateWithAttributedString(attrString); 224 // Indent from corner of bounds 225 CGContextSetTextPosition(context, paddingHighlightX, 3 + paddingHighlightY); 226 CTLineDraw(textLine, context); 227 228 CFRelease(textLine); 229 CFRelease(attrString); 230 CFRelease(fontRef); 231 CGColorRelease(color); 232 CFRelease(styleDict); 233} 234 235- (void) animateMatch: (CGPoint) ptText bounce: (BOOL) bounce { 236 if (!self.sFind || !(self.sFind).length) { 237 [self hideMatch]; 238 return; 239 } 240 241 CGFloat width = self.widthText + paddingHighlightX * 2; 242 CGFloat height = self.heightLine + paddingHighlightY * 2; 243 244 CGFloat flipper = self.geometryFlipped ? -1.0 : 1.0; 245 246 // Adjust for padding 247 ptText.x -= paddingHighlightX; 248 ptText.y += flipper * paddingHighlightY; 249 250 // Shift point to centre as expanding about centre 251 ptText.x += width / 2.0; 252 ptText.y -= flipper * height / 2.0; 253 254 [CATransaction begin]; 255 [CATransaction setValue: @0.0f forKey: kCATransactionAnimationDuration]; 256 self.bounds = CGRectMake(0, 0, width, height); 257 self.position = ptText; 258 if (bounce) { 259 // Do not reset visibility when just moving 260 self.hidden = NO; 261 self.opacity = 1.0; 262 } 263 [self setNeedsDisplay]; 264 [CATransaction commit]; 265 266 if (bounce) { 267 CABasicAnimation *animBounce = [CABasicAnimation animationWithKeyPath: @"transform.scale"]; 268 animBounce.duration = 0.15; 269 animBounce.autoreverses = YES; 270 animBounce.removedOnCompletion = NO; 271 animBounce.fromValue = @1.0f; 272 animBounce.toValue = @1.25f; 273 274 if (self.retaining) { 275 276 [self addAnimation: animBounce forKey: @"animateFound"]; 277 278 } else { 279 280 CABasicAnimation *animFade = [CABasicAnimation animationWithKeyPath: @"opacity"]; 281 animFade.duration = 0.1; 282 animFade.beginTime = 0.4; 283 animFade.removedOnCompletion = NO; 284 animFade.fromValue = @1.0f; 285 animFade.toValue = @0.0f; 286 287 CAAnimationGroup *group = [CAAnimationGroup animation]; 288 group.duration = 0.5; 289 group.removedOnCompletion = NO; 290 group.fillMode = kCAFillModeForwards; 291 group.animations = @[animBounce, animFade]; 292 293 [self addAnimation: group forKey: @"animateFound"]; 294 } 295 } 296} 297 298- (void) hideMatch { 299 self.sFind = @""; 300 self.positionFind = INVALID_POSITION; 301 self.hidden = YES; 302} 303 304@end 305 306#endif 307 308//-------------------------------------------------------------------------------------------------- 309 310@implementation TimerTarget 311 312- (id) init: (void *) target { 313 self = [super init]; 314 if (self != nil) { 315 mTarget = target; 316 317 // Get the default notification queue for the thread which created the instance (usually the 318 // main thread). We need that later for idle event processing. 319 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 320 notificationQueue = [[NSNotificationQueue alloc] initWithNotificationCenter: center]; 321 [center addObserver: self selector: @selector(idleTriggered:) name: @"Idle" object: self]; 322 } 323 return self; 324} 325 326//-------------------------------------------------------------------------------------------------- 327 328- (void) dealloc { 329 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 330 [center removeObserver: self]; 331} 332 333//-------------------------------------------------------------------------------------------------- 334 335/** 336 * Method called by owning ScintillaCocoa object when it is destroyed. 337 */ 338- (void) ownerDestroyed { 339 mTarget = NULL; 340 notificationQueue = nil; 341} 342 343//-------------------------------------------------------------------------------------------------- 344 345/** 346 * Method called by a timer installed by ScintillaCocoa. This two step approach is needed because 347 * a native Obj-C class is required as target for the timer. 348 */ 349- (void) timerFired: (NSTimer *) timer { 350 if (mTarget) 351 static_cast<ScintillaCocoa *>(mTarget)->TimerFired(timer); 352} 353 354//-------------------------------------------------------------------------------------------------- 355 356/** 357 * Another timer callback for the idle timer. 358 */ 359- (void) idleTimerFired: (NSTimer *) timer { 360#pragma unused(timer) 361 // Idle timer event. 362 // Post a new idle notification, which gets executed when the run loop is idle. 363 // Since we are coalescing on name and sender there will always be only one actual notification 364 // even for multiple requests. 365 NSNotification *notification = [NSNotification notificationWithName: @"Idle" object: self]; 366 [notificationQueue enqueueNotification: notification 367 postingStyle: NSPostWhenIdle 368 coalesceMask: (NSNotificationCoalescingOnName | NSNotificationCoalescingOnSender) 369 forModes: @[NSDefaultRunLoopMode, NSModalPanelRunLoopMode]]; 370} 371 372//-------------------------------------------------------------------------------------------------- 373 374/** 375 * Another step for idle events. The timer (for idle events) simply requests a notification on 376 * idle time. Only when this notification is send we actually call back the editor. 377 */ 378- (void) idleTriggered: (NSNotification *) notification { 379#pragma unused(notification) 380 if (mTarget) 381 static_cast<ScintillaCocoa *>(mTarget)->IdleTimerFired(); 382} 383 384@end 385 386//----------------- ScintillaCocoa ----------------------------------------------------------------- 387 388ScintillaCocoa::ScintillaCocoa(ScintillaView *sciView_, SCIContentView *viewContent, SCIMarginView *viewMargin) { 389 vs.marginInside = false; 390 391 // Don't retain since we're owned by view, which would cause a cycle 392 sciView = sciView_; 393 wMain = (__bridge WindowID)viewContent; 394 wMargin = (__bridge WindowID)viewMargin; 395 396 timerTarget = [[TimerTarget alloc] init: this]; 397 lastMouseEvent = NULL; 398 delegate = NULL; 399 notifyObj = NULL; 400 notifyProc = NULL; 401 capturedMouse = false; 402 enteredSetScrollingSize = false; 403 scrollSpeed = 1; 404 scrollTicks = 2000; 405 observer = NULL; 406 layerFindIndicator = NULL; 407 imeInteraction = imeInline; 408 for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1)) { 409 timers[tr] = nil; 410 } 411 Init(); 412} 413 414//-------------------------------------------------------------------------------------------------- 415 416ScintillaCocoa::~ScintillaCocoa() { 417 [timerTarget ownerDestroyed]; 418} 419 420//-------------------------------------------------------------------------------------------------- 421 422/** 423 * Core initialization of the control. Everything that needs to be set up happens here. 424 */ 425void ScintillaCocoa::Init() { 426 427 // Tell Scintilla not to buffer: Quartz buffers drawing for us. 428 WndProc(SCI_SETBUFFEREDDRAW, 0, 0); 429 430 // We are working with Unicode exclusively. 431 WndProc(SCI_SETCODEPAGE, SC_CP_UTF8, 0); 432 433 // Add Mac specific key bindings. 434 for (int i = 0; macMapDefault[i].key; i++) 435 kmap.AssignCmdKey(macMapDefault[i].key, macMapDefault[i].modifiers, macMapDefault[i].msg); 436 437} 438 439//-------------------------------------------------------------------------------------------------- 440 441/** 442 * We need some clean up. Do it here. 443 */ 444void ScintillaCocoa::Finalise() { 445 ObserverRemove(); 446 for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1)) { 447 FineTickerCancel(tr); 448 } 449 ScintillaBase::Finalise(); 450} 451 452//-------------------------------------------------------------------------------------------------- 453 454void ScintillaCocoa::UpdateObserver(CFRunLoopObserverRef /* observer */, CFRunLoopActivity /* activity */, void *info) { 455 ScintillaCocoa *sci = static_cast<ScintillaCocoa *>(info); 456 sci->IdleWork(); 457} 458 459//-------------------------------------------------------------------------------------------------- 460 461/** 462 * Add an observer to the run loop to perform styling as high-priority idle task. 463 */ 464 465void ScintillaCocoa::ObserverAdd() { 466 if (!observer) { 467 CFRunLoopObserverContext context; 468 context.version = 0; 469 context.info = this; 470 context.retain = NULL; 471 context.release = NULL; 472 context.copyDescription = NULL; 473 474 CFRunLoopRef mainRunLoop = CFRunLoopGetMain(); 475 observer = CFRunLoopObserverCreate(NULL, kCFRunLoopEntry | kCFRunLoopBeforeWaiting, 476 true, 0, UpdateObserver, &context); 477 CFRunLoopAddObserver(mainRunLoop, observer, kCFRunLoopCommonModes); 478 } 479} 480 481//-------------------------------------------------------------------------------------------------- 482 483/** 484 * Remove the run loop observer. 485 */ 486void ScintillaCocoa::ObserverRemove() { 487 if (observer) { 488 CFRunLoopRef mainRunLoop = CFRunLoopGetMain(); 489 CFRunLoopRemoveObserver(mainRunLoop, observer, kCFRunLoopCommonModes); 490 CFRelease(observer); 491 } 492 observer = NULL; 493} 494 495//-------------------------------------------------------------------------------------------------- 496 497void ScintillaCocoa::IdleWork() { 498 Editor::IdleWork(); 499 ObserverRemove(); 500} 501 502//-------------------------------------------------------------------------------------------------- 503 504void ScintillaCocoa::QueueIdleWork(WorkNeeded::workItems items, Sci::Position upTo) { 505 Editor::QueueIdleWork(items, upTo); 506 ObserverAdd(); 507} 508 509//-------------------------------------------------------------------------------------------------- 510 511/** 512 * Convert a Core Foundation string into a std::string in a particular encoding. 513 */ 514 515static std::string EncodedBytesString(CFStringRef cfsRef, CFStringEncoding encoding) { 516 const CFRange rangeAll = {0, CFStringGetLength(cfsRef)}; 517 CFIndex usedLen = 0; 518 CFStringGetBytes(cfsRef, rangeAll, encoding, '?', false, 519 NULL, 0, &usedLen); 520 521 std::string buffer(usedLen, '\0'); 522 if (usedLen > 0) { 523 CFStringGetBytes(cfsRef, rangeAll, encoding, '?', false, 524 reinterpret_cast<UInt8 *>(&buffer[0]), usedLen, NULL); 525 } 526 return buffer; 527} 528 529//-------------------------------------------------------------------------------------------------- 530 531/** 532 * Create a Core Foundation string from a string. 533 * This is a simple wrapper that specifies common arguments (the default allocator and 534 * false for isExternalRepresentation) and avoids casting since strings in Scintilla 535 * contain char, not UInt8 (unsigned char). 536 */ 537 538static CFStringRef CFStringFromString(const char *s, size_t len, CFStringEncoding encoding) { 539 return CFStringCreateWithBytes(kCFAllocatorDefault, 540 reinterpret_cast<const UInt8 *>(s), 541 len, encoding, false); 542} 543 544//-------------------------------------------------------------------------------------------------- 545 546/** 547 * Case folders. 548 */ 549 550class CaseFolderDBCS : public CaseFolderTable { 551 CFStringEncoding encoding; 552public: 553 explicit CaseFolderDBCS(CFStringEncoding encoding_) : encoding(encoding_) { 554 StandardASCII(); 555 } 556 size_t Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) override { 557 if ((lenMixed == 1) && (sizeFolded > 0)) { 558 folded[0] = mapping[static_cast<unsigned char>(mixed[0])]; 559 return 1; 560 } else { 561 CFStringRef cfsVal = CFStringFromString(mixed, lenMixed, encoding); 562 if (!cfsVal) { 563 folded[0] = '\0'; 564 return 1; 565 } 566 567 NSString *sMapped = [(__bridge NSString *)cfsVal stringByFoldingWithOptions: NSCaseInsensitiveSearch 568 locale: [NSLocale currentLocale]]; 569 570 std::string encoded = EncodedBytesString((__bridge CFStringRef)sMapped, encoding); 571 572 size_t lenMapped = encoded.length(); 573 if (lenMapped < sizeFolded) { 574 memcpy(folded, encoded.c_str(), lenMapped); 575 } else { 576 folded[0] = '\0'; 577 lenMapped = 1; 578 } 579 CFRelease(cfsVal); 580 return lenMapped; 581 } 582 } 583}; 584 585CaseFolder *ScintillaCocoa::CaseFolderForEncoding() { 586 if (pdoc->dbcsCodePage == SC_CP_UTF8) { 587 return new CaseFolderUnicode(); 588 } else { 589 CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(), 590 vs.styles[STYLE_DEFAULT].characterSet); 591 if (pdoc->dbcsCodePage == 0) { 592 CaseFolderTable *pcf = new CaseFolderTable(); 593 pcf->StandardASCII(); 594 // Only for single byte encodings 595 for (int i=0x80; i<0x100; i++) { 596 char sCharacter[2] = "A"; 597 sCharacter[0] = static_cast<char>(i); 598 CFStringRef cfsVal = CFStringFromString(sCharacter, 1, encoding); 599 if (!cfsVal) 600 continue; 601 602 NSString *sMapped = [(__bridge NSString *)cfsVal stringByFoldingWithOptions: NSCaseInsensitiveSearch 603 locale: [NSLocale currentLocale]]; 604 605 std::string encoded = EncodedBytesString((__bridge CFStringRef)sMapped, encoding); 606 607 if (encoded.length() == 1) { 608 pcf->SetTranslation(sCharacter[0], encoded[0]); 609 } 610 611 CFRelease(cfsVal); 612 } 613 return pcf; 614 } else { 615 return new CaseFolderDBCS(encoding); 616 } 617 } 618} 619 620 621//-------------------------------------------------------------------------------------------------- 622 623/** 624 * Case-fold the given string depending on the specified case mapping type. 625 */ 626std::string ScintillaCocoa::CaseMapString(const std::string &s, int caseMapping) { 627 if ((s.size() == 0) || (caseMapping == cmSame)) 628 return s; 629 630 if (IsUnicodeMode()) { 631 std::string retMapped(s.length() * maxExpansionCaseConversion, 0); 632 size_t lenMapped = CaseConvertString(&retMapped[0], retMapped.length(), s.c_str(), s.length(), 633 (caseMapping == cmUpper) ? CaseConversionUpper : CaseConversionLower); 634 retMapped.resize(lenMapped); 635 return retMapped; 636 } 637 638 CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(), 639 vs.styles[STYLE_DEFAULT].characterSet); 640 641 CFStringRef cfsVal = CFStringFromString(s.c_str(), s.length(), encoding); 642 if (!cfsVal) { 643 return s; 644 } 645 646 NSString *sMapped; 647 switch (caseMapping) { 648 case cmUpper: 649 sMapped = ((__bridge NSString *)cfsVal).uppercaseString; 650 break; 651 case cmLower: 652 sMapped = ((__bridge NSString *)cfsVal).lowercaseString; 653 break; 654 default: 655 sMapped = (__bridge NSString *)cfsVal; 656 } 657 658 // Back to encoding 659 std::string result = EncodedBytesString((__bridge CFStringRef)sMapped, encoding); 660 CFRelease(cfsVal); 661 return result; 662} 663 664//-------------------------------------------------------------------------------------------------- 665 666/** 667 * Cancel all modes, both for base class and any find indicator. 668 */ 669void ScintillaCocoa::CancelModes() { 670 ScintillaBase::CancelModes(); 671 HideFindIndicator(); 672} 673 674//-------------------------------------------------------------------------------------------------- 675 676/** 677 * Helper function to get the scrolling view. 678 */ 679NSScrollView *ScintillaCocoa::ScrollContainer() const { 680 NSView *container = (__bridge NSView *)(wMain.GetID()); 681 return static_cast<NSScrollView *>(container.superview.superview); 682} 683 684//-------------------------------------------------------------------------------------------------- 685 686/** 687 * Helper function to get the inner container which represents the actual "canvas" we work with. 688 */ 689SCIContentView *ScintillaCocoa::ContentView() { 690 return (__bridge SCIContentView *)(wMain.GetID()); 691} 692 693//-------------------------------------------------------------------------------------------------- 694 695/** 696 * Return the top left visible point relative to the origin point of the whole document. 697 */ 698Scintilla::Point ScintillaCocoa::GetVisibleOriginInMain() const { 699 NSScrollView *scrollView = ScrollContainer(); 700 NSRect contentRect = scrollView.contentView.bounds; 701 return Point(static_cast<XYPOSITION>(contentRect.origin.x), static_cast<XYPOSITION>(contentRect.origin.y)); 702} 703 704//-------------------------------------------------------------------------------------------------- 705 706/** 707 * Instead of returning the size of the inner view we have to return the visible part of it 708 * in order to make scrolling working properly. 709 * The returned value is in document coordinates. 710 */ 711PRectangle ScintillaCocoa::GetClientRectangle() const { 712 NSScrollView *scrollView = ScrollContainer(); 713 NSSize size = scrollView.contentView.bounds.size; 714 Point origin = GetVisibleOriginInMain(); 715 return PRectangle(origin.x, origin.y, static_cast<XYPOSITION>(origin.x+size.width), 716 static_cast<XYPOSITION>(origin.y + size.height)); 717} 718 719//-------------------------------------------------------------------------------------------------- 720 721/** 722 * Allow for prepared rectangle 723 */ 724PRectangle ScintillaCocoa::GetClientDrawingRectangle() { 725#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080 726 NSView *content = ContentView(); 727 if ([content respondsToSelector: @selector(setPreparedContentRect:)]) { 728 NSRect rcPrepared = content.preparedContentRect; 729 if (!NSIsEmptyRect(rcPrepared)) 730 return NSRectToPRectangle(rcPrepared); 731 } 732#endif 733 return ScintillaCocoa::GetClientRectangle(); 734} 735 736//-------------------------------------------------------------------------------------------------- 737 738/** 739 * Converts the given point from base coordinates to local coordinates and at the same time into 740 * a native Point structure. Base coordinates are used for the top window used in the view hierarchy. 741 * Returned value is in view coordinates. 742 */ 743Scintilla::Point ScintillaCocoa::ConvertPoint(NSPoint point) { 744 NSView *container = ContentView(); 745 NSPoint result = [container convertPoint: point fromView: nil]; 746 Scintilla::Point ptOrigin = GetVisibleOriginInMain(); 747 return Point(static_cast<XYPOSITION>(result.x - ptOrigin.x), static_cast<XYPOSITION>(result.y - ptOrigin.y)); 748} 749 750//-------------------------------------------------------------------------------------------------- 751 752/** 753 * Do not clip like superclass as Cocoa is not reporting all of prepared area. 754 */ 755void ScintillaCocoa::RedrawRect(PRectangle rc) { 756 if (!rc.Empty()) 757 wMain.InvalidateRectangle(rc); 758} 759 760//-------------------------------------------------------------------------------------------------- 761 762void ScintillaCocoa::DiscardOverdraw() { 763#if MAC_OS_X_VERSION_MAX_ALLOWED > 1080 764 // If running on 10.9, reset prepared area to visible area 765 NSView *content = ContentView(); 766 if ([content respondsToSelector: @selector(setPreparedContentRect:)]) { 767 content.preparedContentRect = content.visibleRect; 768 } 769#endif 770} 771 772//-------------------------------------------------------------------------------------------------- 773 774/** 775 * Ensure all of prepared content is also redrawn. 776 */ 777void ScintillaCocoa::Redraw() { 778 wMargin.InvalidateAll(); 779 DiscardOverdraw(); 780 wMain.InvalidateAll(); 781} 782 783//-------------------------------------------------------------------------------------------------- 784 785/** 786 * A function to directly execute code that would usually go the long way via window messages. 787 * However this is a Windows metaphor and not used here, hence we just call our fake 788 * window proc. The given parameters directly reflect the message parameters used on Windows. 789 * 790 * @param ptr The target which is to be called. 791 * @param iMessage A code that indicates which message was sent. 792 * @param wParam One of the two free parameters for the message. Traditionally a word sized parameter 793 * (hence the w prefix). 794 * @param lParam The other of the two free parameters. A signed long. 795 */ 796sptr_t ScintillaCocoa::DirectFunction(sptr_t ptr, unsigned int iMessage, uptr_t wParam, 797 sptr_t lParam) { 798 return reinterpret_cast<ScintillaCocoa *>(ptr)->WndProc(iMessage, wParam, lParam); 799} 800 801//-------------------------------------------------------------------------------------------------- 802 803/** 804 * This method is very similar to DirectFunction. On Windows it sends a message (not in the Obj-C sense) 805 * to the target window. Here we simply call our fake window proc. 806 */ 807sptr_t scintilla_send_message(void *sci, unsigned int iMessage, uptr_t wParam, sptr_t lParam) { 808 ScintillaView *control = (__bridge ScintillaView *)(sci); 809 return [control message: iMessage wParam: wParam lParam: lParam]; 810} 811 812//-------------------------------------------------------------------------------------------------- 813 814namespace { 815 816/** 817 * The animated find indicator fails with a "bogus layer size" message on macOS 10.13 818 * and causes drawing failures on macOS 10.12. 819 */ 820 821bool SupportAnimatedFind() { 822 return std::floor(NSAppKitVersionNumber) < NSAppKitVersionNumber10_12; 823} 824 825} 826 827//-------------------------------------------------------------------------------------------------- 828 829/** 830 * That's our fake window procedure. On Windows each window has a dedicated procedure to handle 831 * commands (also used to synchronize UI and background threads), which is not the case in Cocoa. 832 * 833 * Messages handled here are almost solely for special commands of the backend. Everything which 834 * would be system messages on Windows (e.g. for key down, mouse move etc.) are handled by 835 * directly calling appropriate handlers. 836 */ 837sptr_t ScintillaCocoa::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { 838 try { 839 switch (iMessage) { 840 case SCI_GETDIRECTFUNCTION: 841 return reinterpret_cast<sptr_t>(DirectFunction); 842 843 case SCI_GETDIRECTPOINTER: 844 return reinterpret_cast<sptr_t>(this); 845 846 case SCI_SETBIDIRECTIONAL: 847 bidirectional = static_cast<EditModel::Bidirectional>(wParam); 848 // Invalidate all cached information including layout. 849 DropGraphics(true); 850 InvalidateStyleRedraw(); 851 return 0; 852 853 case SCI_TARGETASUTF8: 854 return TargetAsUTF8(CharPtrFromSPtr(lParam)); 855 856 case SCI_ENCODEDFROMUTF8: 857 return EncodedFromUTF8(ConstCharPtrFromUPtr(wParam), 858 CharPtrFromSPtr(lParam)); 859 860 case SCI_SETIMEINTERACTION: 861 // Only inline IME supported on Cocoa 862 break; 863 864 case SCI_GRABFOCUS: 865 [ContentView().window makeFirstResponder: ContentView()]; 866 break; 867 868 case SCI_SETBUFFEREDDRAW: 869 // Buffered drawing not supported on Cocoa 870 view.bufferedDraw = false; 871 break; 872 873 case SCI_FINDINDICATORSHOW: 874 if (SupportAnimatedFind()) { 875 ShowFindIndicatorForRange(NSMakeRange(wParam, lParam-wParam), YES); 876 } 877 return 0; 878 879 case SCI_FINDINDICATORFLASH: 880 if (SupportAnimatedFind()) { 881 ShowFindIndicatorForRange(NSMakeRange(wParam, lParam-wParam), NO); 882 } 883 return 0; 884 885 case SCI_FINDINDICATORHIDE: 886 HideFindIndicator(); 887 return 0; 888 889 case SCI_SETPHASESDRAW: { 890 sptr_t r = ScintillaBase::WndProc(iMessage, wParam, lParam); 891 [sciView updateIndicatorIME]; 892 return r; 893 } 894 895 case SCI_GETACCESSIBILITY: 896 return SC_ACCESSIBILITY_ENABLED; 897 898 default: 899 sptr_t r = ScintillaBase::WndProc(iMessage, wParam, lParam); 900 901 return r; 902 } 903 } catch (std::bad_alloc &) { 904 errorStatus = SC_STATUS_BADALLOC; 905 } catch (...) { 906 errorStatus = SC_STATUS_FAILURE; 907 } 908 return 0; 909} 910 911//-------------------------------------------------------------------------------------------------- 912 913/** 914 * In Windows lingo this is the handler which handles anything that wasn't handled in the normal 915 * window proc which would usually send the message back to generic window proc that Windows uses. 916 */ 917sptr_t ScintillaCocoa::DefWndProc(unsigned int, uptr_t, sptr_t) { 918 return 0; 919} 920 921//-------------------------------------------------------------------------------------------------- 922 923/** 924 * Handle any ScintillaCocoa-specific ticking or call superclass. 925 */ 926void ScintillaCocoa::TickFor(TickReason reason) { 927 if (reason == tickPlatform) { 928 DragScroll(); 929 } else { 930 Editor::TickFor(reason); 931 } 932} 933 934//-------------------------------------------------------------------------------------------------- 935 936/** 937 * Is a particular timer currently running? 938 */ 939bool ScintillaCocoa::FineTickerRunning(TickReason reason) { 940 return timers[reason] != nil; 941} 942 943//-------------------------------------------------------------------------------------------------- 944 945/** 946 * Start a fine-grained timer. 947 */ 948void ScintillaCocoa::FineTickerStart(TickReason reason, int millis, int tolerance) { 949 FineTickerCancel(reason); 950 NSTimer *fineTimer = [NSTimer timerWithTimeInterval: millis / 1000.0 951 target: timerTarget 952 selector: @selector(timerFired:) 953 userInfo: nil 954 repeats: YES]; 955 if (tolerance && [fineTimer respondsToSelector: @selector(setTolerance:)]) { 956 fineTimer.tolerance = tolerance / 1000.0; 957 } 958 timers[reason] = fineTimer; 959 [NSRunLoop.currentRunLoop addTimer: fineTimer forMode: NSDefaultRunLoopMode]; 960 [NSRunLoop.currentRunLoop addTimer: fineTimer forMode: NSModalPanelRunLoopMode]; 961} 962 963//-------------------------------------------------------------------------------------------------- 964 965/** 966 * Cancel a fine-grained timer. 967 */ 968void ScintillaCocoa::FineTickerCancel(TickReason reason) { 969 if (timers[reason]) { 970 [timers[reason] invalidate]; 971 timers[reason] = nil; 972 } 973} 974 975//-------------------------------------------------------------------------------------------------- 976 977bool ScintillaCocoa::SetIdle(bool on) { 978 if (idler.state != on) { 979 idler.state = on; 980 if (idler.state) { 981 // Scintilla ticks = milliseconds 982 NSTimer *idleTimer = [NSTimer scheduledTimerWithTimeInterval: timer.tickSize / 1000.0 983 target: timerTarget 984 selector: @selector(idleTimerFired:) 985 userInfo: nil 986 repeats: YES]; 987 [NSRunLoop.currentRunLoop addTimer: idleTimer forMode: NSModalPanelRunLoopMode]; 988 idler.idlerID = (__bridge IdlerID)idleTimer; 989 } else if (idler.idlerID != NULL) { 990 [(__bridge NSTimer *)(idler.idlerID) invalidate]; 991 idler.idlerID = 0; 992 } 993 } 994 return true; 995} 996 997//-------------------------------------------------------------------------------------------------- 998 999void ScintillaCocoa::CopyToClipboard(const SelectionText &selectedText) { 1000 SetPasteboardData([NSPasteboard generalPasteboard], selectedText); 1001} 1002 1003//-------------------------------------------------------------------------------------------------- 1004 1005void ScintillaCocoa::Copy() { 1006 if (!sel.Empty()) { 1007 SelectionText selectedText; 1008 CopySelectionRange(&selectedText); 1009 CopyToClipboard(selectedText); 1010 } 1011} 1012 1013//-------------------------------------------------------------------------------------------------- 1014 1015bool ScintillaCocoa::CanPaste() { 1016 if (!Editor::CanPaste()) 1017 return false; 1018 1019 return GetPasteboardData([NSPasteboard generalPasteboard], NULL); 1020} 1021 1022//-------------------------------------------------------------------------------------------------- 1023 1024void ScintillaCocoa::Paste() { 1025 Paste(false); 1026} 1027 1028//-------------------------------------------------------------------------------------------------- 1029 1030/** 1031 * Pastes data from the paste board into the editor. 1032 */ 1033void ScintillaCocoa::Paste(bool forceRectangular) { 1034 SelectionText selectedText; 1035 bool ok = GetPasteboardData([NSPasteboard generalPasteboard], &selectedText); 1036 if (forceRectangular) 1037 selectedText.rectangular = forceRectangular; 1038 1039 if (!ok || selectedText.Empty()) 1040 // No data or no flavor we support. 1041 return; 1042 1043 pdoc->BeginUndoAction(); 1044 ClearSelection(false); 1045 InsertPasteShape(selectedText.Data(), selectedText.Length(), 1046 selectedText.rectangular ? pasteRectangular : pasteStream); 1047 pdoc->EndUndoAction(); 1048 1049 Redraw(); 1050 EnsureCaretVisible(); 1051} 1052 1053//-------------------------------------------------------------------------------------------------- 1054 1055void ScintillaCocoa::CTPaint(void *gc, NSRect rc) { 1056#pragma unused(rc) 1057 std::unique_ptr<Surface> surfaceWindow(Surface::Allocate(SC_TECHNOLOGY_DEFAULT)); 1058 surfaceWindow->Init(gc, wMain.GetID()); 1059 surfaceWindow->SetUnicodeMode(SC_CP_UTF8 == ct.codePage); 1060 surfaceWindow->SetDBCSMode(ct.codePage); 1061 ct.PaintCT(surfaceWindow.get()); 1062 surfaceWindow->Release(); 1063} 1064 1065@interface CallTipView : NSControl { 1066 ScintillaCocoa *sci; 1067} 1068 1069@end 1070 1071@implementation CallTipView 1072 1073- (NSView *) initWithFrame: (NSRect) frame { 1074 self = [super initWithFrame: frame]; 1075 1076 if (self) { 1077 sci = NULL; 1078 } 1079 1080 return self; 1081} 1082 1083 1084- (BOOL) isFlipped { 1085 return YES; 1086} 1087 1088- (void) setSci: (ScintillaCocoa *) sci_ { 1089 sci = sci_; 1090} 1091 1092- (void) drawRect: (NSRect) needsDisplayInRect { 1093 if (sci) { 1094 CGContextRef context = (CGContextRef) [NSGraphicsContext currentContext].graphicsPort; 1095 sci->CTPaint(context, needsDisplayInRect); 1096 } 1097} 1098 1099- (void) mouseDown: (NSEvent *) event { 1100 if (sci) { 1101 sci->CallTipMouseDown(event.locationInWindow); 1102 } 1103} 1104 1105// On OS X, only the key view should modify the cursor so the calltip can't. 1106// This view does not become key so resetCursorRects never called. 1107- (void) resetCursorRects { 1108 //[super resetCursorRects]; 1109 //[self addCursorRect: [self bounds] cursor: [NSCursor arrowCursor]]; 1110} 1111 1112@end 1113 1114void ScintillaCocoa::CallTipMouseDown(NSPoint pt) { 1115 NSRect rectBounds = ((__bridge NSView *)(ct.wDraw.GetID())).bounds; 1116 Point location(static_cast<XYPOSITION>(pt.x), 1117 static_cast<XYPOSITION>(rectBounds.size.height - pt.y)); 1118 ct.MouseClick(location); 1119 CallTipClick(); 1120} 1121 1122static bool HeightDifferent(WindowID wCallTip, PRectangle rc) { 1123 NSWindow *callTip = (__bridge NSWindow *)wCallTip; 1124 CGFloat height = NSHeight(callTip.frame); 1125 return height != rc.Height(); 1126} 1127 1128void ScintillaCocoa::CreateCallTipWindow(PRectangle rc) { 1129 if (ct.wCallTip.Created() && HeightDifferent(ct.wCallTip.GetID(), rc)) { 1130 ct.wCallTip.Destroy(); 1131 } 1132 if (!ct.wCallTip.Created()) { 1133 NSRect ctRect = NSMakeRect(rc.top, rc.bottom, rc.Width(), rc.Height()); 1134 NSWindow *callTip = [[NSWindow alloc] initWithContentRect: ctRect 1135 styleMask: NSWindowStyleMaskBorderless 1136 backing: NSBackingStoreBuffered 1137 defer: NO]; 1138 [callTip setLevel: NSFloatingWindowLevel]; 1139 [callTip setHasShadow: YES]; 1140 NSRect ctContent = NSMakeRect(0, 0, rc.Width(), rc.Height()); 1141 CallTipView *caption = [[CallTipView alloc] initWithFrame: ctContent]; 1142 caption.autoresizingMask = NSViewWidthSizable | NSViewMaxYMargin; 1143 [caption setSci: this]; 1144 [callTip.contentView addSubview: caption]; 1145 [callTip orderFront: caption]; 1146 ct.wCallTip = (__bridge_retained WindowID)callTip; 1147 ct.wDraw = (__bridge WindowID)caption; 1148 } 1149} 1150 1151void ScintillaCocoa::AddToPopUp(const char *label, int cmd, bool enabled) { 1152 NSMenuItem *item; 1153 ScintillaContextMenu *menu = (__bridge ScintillaContextMenu *)(popup.GetID()); 1154 [menu setOwner: this]; 1155 [menu setAutoenablesItems: NO]; 1156 1157 if (cmd == 0) { 1158 item = [NSMenuItem separatorItem]; 1159 } else { 1160 item = [[NSMenuItem alloc] init]; 1161 item.title = @(label); 1162 } 1163 item.target = menu; 1164 item.action = @selector(handleCommand:); 1165 item.tag = cmd; 1166 item.enabled = enabled; 1167 1168 [menu addItem: item]; 1169} 1170 1171// ------------------------------------------------------------------------------------------------- 1172 1173void ScintillaCocoa::ClaimSelection() { 1174 // Mac OS X does not have a primary selection. 1175} 1176 1177// ------------------------------------------------------------------------------------------------- 1178 1179/** 1180 * Returns the current caret position (which is tracked as an offset into the entire text string) 1181 * as a row:column pair. The result is zero-based. 1182 */ 1183NSPoint ScintillaCocoa::GetCaretPosition() { 1184 const Sci::Line line = static_cast<Sci::Line>( 1185 pdoc->LineFromPosition(sel.RangeMain().caret.Position())); 1186 NSPoint result; 1187 1188 result.y = line; 1189 result.x = sel.RangeMain().caret.Position() - pdoc->LineStart(line); 1190 return result; 1191} 1192 1193// ------------------------------------------------------------------------------------------------- 1194 1195#pragma mark Drag 1196 1197/** 1198 * Triggered by the tick timer on a regular basis to scroll the content during a drag operation. 1199 */ 1200void ScintillaCocoa::DragScroll() { 1201 if (!posDrag.IsValid()) { 1202 scrollSpeed = 1; 1203 scrollTicks = 2000; 1204 return; 1205 } 1206 1207 // TODO: does not work for wrapped lines, fix it. 1208 Sci::Line line = static_cast<Sci::Line>(pdoc->LineFromPosition(posDrag.Position())); 1209 Sci::Line currentVisibleLine = pcs->DisplayFromDoc(line); 1210 Sci::Line lastVisibleLine = std::min(topLine + LinesOnScreen(), pcs->LinesDisplayed()) - 2; 1211 1212 if (currentVisibleLine <= topLine && topLine > 0) 1213 ScrollTo(topLine - scrollSpeed); 1214 else if (currentVisibleLine >= lastVisibleLine) 1215 ScrollTo(topLine + scrollSpeed); 1216 else { 1217 scrollSpeed = 1; 1218 scrollTicks = 2000; 1219 return; 1220 } 1221 1222 // TODO: also handle horizontal scrolling. 1223 1224 if (scrollSpeed == 1) { 1225 scrollTicks -= timer.tickSize; 1226 if (scrollTicks <= 0) { 1227 scrollSpeed = 5; 1228 scrollTicks = 2000; 1229 } 1230 } 1231 1232} 1233 1234//----------------- DragProviderSource ------------------------------------------------------- 1235 1236@interface DragProviderSource : NSObject <NSPasteboardItemDataProvider> { 1237 SelectionText selectedText; 1238} 1239 1240@end 1241 1242@implementation DragProviderSource 1243 1244- (id) initWithSelectedText: (const SelectionText *) other { 1245 self = [super init]; 1246 1247 if (self) { 1248 selectedText.Copy(*other); 1249 } 1250 1251 return self; 1252} 1253 1254- (void) pasteboard: (NSPasteboard *) pasteboard item: (NSPasteboardItem *) item provideDataForType: (NSString *) type { 1255#pragma unused(item) 1256 if (selectedText.Length() == 0) 1257 return; 1258 1259 if (([type compare: NSPasteboardTypeString] != NSOrderedSame) && 1260 ([type compare: ScintillaRecPboardType] != NSOrderedSame)) 1261 return; 1262 1263 CFStringEncoding encoding = EncodingFromCharacterSet(selectedText.codePage == SC_CP_UTF8, 1264 selectedText.characterSet); 1265 1266 CFStringRef cfsVal = CFStringFromString(selectedText.Data(), selectedText.Length(), encoding); 1267 if (!cfsVal) 1268 return; 1269 1270 if ([type compare: NSPasteboardTypeString] == NSOrderedSame) { 1271 [pasteboard setString: (__bridge NSString *)cfsVal forType: NSStringPboardType]; 1272 } else if ([type compare: ScintillaRecPboardType] == NSOrderedSame) { 1273 // This is specific to scintilla, allows us to drag rectangular selections around the document. 1274 if (selectedText.rectangular) 1275 [pasteboard setString: (__bridge NSString *)cfsVal forType: ScintillaRecPboardType]; 1276 } 1277 1278 if (cfsVal) 1279 CFRelease(cfsVal); 1280} 1281 1282@end 1283 1284//-------------------------------------------------------------------------------------------------- 1285 1286/** 1287 * Called when a drag operation was initiated from within Scintilla. 1288 */ 1289void ScintillaCocoa::StartDrag() { 1290 if (sel.Empty()) 1291 return; 1292 1293 inDragDrop = ddDragging; 1294 1295 FineTickerStart(tickPlatform, timer.tickSize, 0); 1296 1297 // Put the data to be dragged on the drag pasteboard. 1298 SelectionText selectedText; 1299 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName: NSDragPboard]; 1300 CopySelectionRange(&selectedText); 1301 SetPasteboardData(pasteboard, selectedText); 1302 1303 // calculate the bounds of the selection 1304 PRectangle client = GetTextRectangle(); 1305 Sci::Position selStart = sel.RangeMain().Start().Position(); 1306 Sci::Position selEnd = sel.RangeMain().End().Position(); 1307 Sci::Line startLine = static_cast<Sci::Line>(pdoc->LineFromPosition(selStart)); 1308 Sci::Line endLine = static_cast<Sci::Line>(pdoc->LineFromPosition(selEnd)); 1309 Point pt; 1310 Sci::Position startPos; 1311 Sci::Position endPos; 1312 Sci::Position ep; 1313 PRectangle rcSel; 1314 1315 if (startLine==endLine && WndProc(SCI_GETWRAPMODE, 0, 0) != SC_WRAP_NONE) { 1316 // Komodo bug http://bugs.activestate.com/show_bug.cgi?id=87571 1317 // Scintilla bug https://sourceforge.net/tracker/?func=detail&atid=102439&aid=3040200&group_id=2439 1318 // If the width on a wrapped-line selection is negative, 1319 // find a better bounding rectangle. 1320 1321 Point ptStart, ptEnd; 1322 startPos = WndProc(SCI_GETLINESELSTARTPOSITION, startLine, 0); 1323 endPos = WndProc(SCI_GETLINESELENDPOSITION, startLine, 0); 1324 // step back a position if we're counting the newline 1325 ep = WndProc(SCI_GETLINEENDPOSITION, startLine, 0); 1326 if (endPos > ep) endPos = ep; 1327 ptStart = LocationFromPosition(startPos); 1328 ptEnd = LocationFromPosition(endPos); 1329 if (ptStart.y == ptEnd.y) { 1330 // We're just selecting part of one visible line 1331 rcSel.left = ptStart.x; 1332 rcSel.right = ptEnd.x < client.right ? ptEnd.x : client.right; 1333 } else { 1334 // Find the bounding box. 1335 startPos = WndProc(SCI_POSITIONFROMLINE, startLine, 0); 1336 rcSel.left = LocationFromPosition(startPos).x; 1337 rcSel.right = client.right; 1338 } 1339 rcSel.top = ptStart.y; 1340 rcSel.bottom = ptEnd.y + vs.lineHeight; 1341 if (rcSel.bottom > client.bottom) { 1342 rcSel.bottom = client.bottom; 1343 } 1344 } else { 1345 rcSel.top = rcSel.bottom = rcSel.right = rcSel.left = -1; 1346 for (Sci::Line l = startLine; l <= endLine; l++) { 1347 startPos = WndProc(SCI_GETLINESELSTARTPOSITION, l, 0); 1348 endPos = WndProc(SCI_GETLINESELENDPOSITION, l, 0); 1349 if (endPos == startPos) continue; 1350 // step back a position if we're counting the newline 1351 ep = WndProc(SCI_GETLINEENDPOSITION, l, 0); 1352 if (endPos > ep) endPos = ep; 1353 pt = LocationFromPosition(startPos); // top left of line selection 1354 if (pt.x < rcSel.left || rcSel.left < 0) rcSel.left = pt.x; 1355 if (pt.y < rcSel.top || rcSel.top < 0) rcSel.top = pt.y; 1356 pt = LocationFromPosition(endPos); // top right of line selection 1357 pt.y += vs.lineHeight; // get to the bottom of the line 1358 if (pt.x > rcSel.right || rcSel.right < 0) { 1359 if (pt.x > client.right) 1360 rcSel.right = client.right; 1361 else 1362 rcSel.right = pt.x; 1363 } 1364 if (pt.y > rcSel.bottom || rcSel.bottom < 0) { 1365 if (pt.y > client.bottom) 1366 rcSel.bottom = client.bottom; 1367 else 1368 rcSel.bottom = pt.y; 1369 } 1370 } 1371 } 1372 // must convert to global coordinates for drag regions, but also save the 1373 // image rectangle for further calculations and copy operations 1374 1375 // Prepare drag image. 1376 NSRect selectionRectangle = PRectangleToNSRect(rcSel); 1377 1378 SCIContentView *content = ContentView(); 1379 1380 // To get a bitmap of the text we're dragging, we just use Paint on a pixmap surface. 1381 SurfaceImpl sw; 1382 sw.InitPixMap(static_cast<int>(client.Width()), static_cast<int>(client.Height()), NULL, NULL); 1383 1384 const bool lastHideSelection = view.hideSelection; 1385 view.hideSelection = true; 1386 PRectangle imageRect = rcSel; 1387 paintState = painting; 1388 paintingAllText = true; 1389 CGContextRef gcsw = sw.GetContext(); 1390 CGContextTranslateCTM(gcsw, -client.left, -client.top); 1391 Paint(&sw, client); 1392 paintState = notPainting; 1393 view.hideSelection = lastHideSelection; 1394 1395 SurfaceImpl pixmap; 1396 pixmap.InitPixMap(static_cast<int>(imageRect.Width()), static_cast<int>(imageRect.Height()), NULL, NULL); 1397 pixmap.SetUnicodeMode(IsUnicodeMode()); 1398 pixmap.SetDBCSMode(CodePage()); 1399 1400 CGContextRef gc = pixmap.GetContext(); 1401 // To make Paint() work on a bitmap, we have to flip our coordinates and translate the origin 1402 CGContextTranslateCTM(gc, 0, imageRect.Height()); 1403 CGContextScaleCTM(gc, 1.0, -1.0); 1404 1405 pixmap.CopyImageRectangle(sw, imageRect, PRectangle(0.0f, 0.0f, imageRect.Width(), imageRect.Height())); 1406 // XXX TODO: overwrite any part of the image that is not part of the 1407 // selection to make it transparent. right now we just use 1408 // the full rectangle which may include non-selected text. 1409 1410 NSBitmapImageRep *bitmap = NULL; 1411 CGImageRef imagePixmap = pixmap.CreateImage(); 1412 if (imagePixmap) 1413 bitmap = [[NSBitmapImageRep alloc] initWithCGImage: imagePixmap]; 1414 CGImageRelease(imagePixmap); 1415 1416 NSImage *image = [[NSImage alloc] initWithSize: selectionRectangle.size]; 1417 [image addRepresentation: bitmap]; 1418 1419 NSImage *dragImage = [[NSImage alloc] initWithSize: selectionRectangle.size]; 1420 dragImage.backgroundColor = [NSColor clearColor]; 1421 [dragImage lockFocus]; 1422 [image drawAtPoint: NSZeroPoint fromRect: NSZeroRect operation: NSCompositingOperationSourceOver fraction: 0.5]; 1423 [dragImage unlockFocus]; 1424 1425 NSPoint startPoint; 1426 startPoint.x = selectionRectangle.origin.x + client.left; 1427 startPoint.y = selectionRectangle.origin.y + selectionRectangle.size.height + client.top; 1428 1429 NSPasteboardItem *pbItem = [NSPasteboardItem new]; 1430 DragProviderSource *dps = [[DragProviderSource alloc] initWithSelectedText: &selectedText]; 1431 1432 NSArray *pbTypes = selectedText.rectangular ? 1433 @[NSPasteboardTypeString, ScintillaRecPboardType] : 1434 @[NSPasteboardTypeString]; 1435 [pbItem setDataProvider: dps forTypes: pbTypes]; 1436 NSDraggingItem *dragItem = [[NSDraggingItem alloc ]initWithPasteboardWriter: pbItem]; 1437 1438 NSScrollView *scrollContainer = ScrollContainer(); 1439 NSRect contentRect = scrollContainer.contentView.bounds; 1440 NSRect draggingRect = NSOffsetRect(selectionRectangle, contentRect.origin.x, contentRect.origin.y); 1441 [dragItem setDraggingFrame: draggingRect contents: dragImage]; 1442 NSDraggingSession *dragSession = 1443 [content beginDraggingSessionWithItems: @[dragItem] 1444 event: lastMouseEvent 1445 source: content]; 1446 dragSession.animatesToStartingPositionsOnCancelOrFail = YES; 1447 dragSession.draggingFormation = NSDraggingFormationNone; 1448} 1449 1450//-------------------------------------------------------------------------------------------------- 1451 1452/** 1453 * Called when a drag operation reaches the control which was initiated outside. 1454 */ 1455NSDragOperation ScintillaCocoa::DraggingEntered(id <NSDraggingInfo> info) { 1456 FineTickerStart(tickPlatform, timer.tickSize, 0); 1457 return DraggingUpdated(info); 1458} 1459 1460//-------------------------------------------------------------------------------------------------- 1461 1462/** 1463 * Called frequently during a drag operation if we are the target. Keep telling the caller 1464 * what drag operation we accept and update the drop caret position to indicate the 1465 * potential insertion point of the dragged data. 1466 */ 1467NSDragOperation ScintillaCocoa::DraggingUpdated(id <NSDraggingInfo> info) { 1468 // Convert the drag location from window coordinates to view coordinates and 1469 // from there to a text position to finally set the drag position. 1470 Point location = ConvertPoint([info draggingLocation]); 1471 SetDragPosition(SPositionFromLocation(location)); 1472 1473 NSDragOperation sourceDragMask = [info draggingSourceOperationMask]; 1474 if (sourceDragMask == NSDragOperationNone) 1475 return sourceDragMask; 1476 1477 NSPasteboard *pasteboard = [info draggingPasteboard]; 1478 1479 // Return what type of operation we will perform. Prefer move over copy. 1480 if ([pasteboard.types containsObject: NSStringPboardType] || 1481 [pasteboard.types containsObject: ScintillaRecPboardType]) 1482 return (sourceDragMask & NSDragOperationMove) ? NSDragOperationMove : NSDragOperationCopy; 1483 1484 if ([pasteboard.types containsObject: NSFilenamesPboardType]) 1485 return (sourceDragMask & NSDragOperationGeneric); 1486 return NSDragOperationNone; 1487} 1488 1489//-------------------------------------------------------------------------------------------------- 1490 1491/** 1492 * Resets the current drag position as we are no longer the drag target. 1493 */ 1494void ScintillaCocoa::DraggingExited(id <NSDraggingInfo> info) { 1495#pragma unused(info) 1496 SetDragPosition(SelectionPosition(Sci::invalidPosition)); 1497 FineTickerCancel(tickPlatform); 1498 inDragDrop = ddNone; 1499} 1500 1501//-------------------------------------------------------------------------------------------------- 1502 1503/** 1504 * Here is where the real work is done. Insert the text from the pasteboard. 1505 */ 1506bool ScintillaCocoa::PerformDragOperation(id <NSDraggingInfo> info) { 1507 NSPasteboard *pasteboard = [info draggingPasteboard]; 1508 1509 if ([pasteboard.types containsObject: NSFilenamesPboardType]) { 1510 NSArray *files = [pasteboard propertyListForType: NSFilenamesPboardType]; 1511 for (NSString* uri in files) 1512 NotifyURIDropped(uri.UTF8String); 1513 } else { 1514 SelectionText text; 1515 GetPasteboardData(pasteboard, &text); 1516 1517 if (text.Length() > 0) { 1518 NSDragOperation operation = [info draggingSourceOperationMask]; 1519 bool moving = (operation & NSDragOperationMove) != 0; 1520 1521 DropAt(posDrag, text.Data(), text.Length(), moving, text.rectangular); 1522 }; 1523 } 1524 1525 return true; 1526} 1527 1528//-------------------------------------------------------------------------------------------------- 1529 1530void ScintillaCocoa::SetPasteboardData(NSPasteboard *board, const SelectionText &selectedText) { 1531 if (selectedText.Length() == 0) 1532 return; 1533 1534 CFStringEncoding encoding = EncodingFromCharacterSet(selectedText.codePage == SC_CP_UTF8, 1535 selectedText.characterSet); 1536 1537 CFStringRef cfsVal = CFStringFromString(selectedText.Data(), selectedText.Length(), encoding); 1538 if (!cfsVal) 1539 return; 1540 1541 NSArray *pbTypes = selectedText.rectangular ? 1542 @[NSStringPboardType, ScintillaRecPboardType] : 1543 @[NSStringPboardType]; 1544 [board declareTypes: pbTypes owner: nil]; 1545 1546 if (selectedText.rectangular) { 1547 // This is specific to scintilla, allows us to drag rectangular selections around the document. 1548 [board setString: (__bridge NSString *)cfsVal forType: ScintillaRecPboardType]; 1549 } 1550 1551 [board setString: (__bridge NSString *)cfsVal forType: NSStringPboardType]; 1552 1553 if (cfsVal) 1554 CFRelease(cfsVal); 1555} 1556 1557//-------------------------------------------------------------------------------------------------- 1558 1559/** 1560 * Helper method to retrieve the best fitting alternative from the general pasteboard. 1561 */ 1562bool ScintillaCocoa::GetPasteboardData(NSPasteboard *board, SelectionText *selectedText) { 1563 NSArray *supportedTypes = @[ScintillaRecPboardType, 1564 NSStringPboardType]; 1565 NSString *bestType = [board availableTypeFromArray: supportedTypes]; 1566 NSString *data = [board stringForType: bestType]; 1567 1568 if (data != nil) { 1569 if (selectedText != nil) { 1570 CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(), 1571 vs.styles[STYLE_DEFAULT].characterSet); 1572 CFRange rangeAll = {0, static_cast<CFIndex>(data.length)}; 1573 CFIndex usedLen = 0; 1574 CFStringGetBytes((CFStringRef)data, rangeAll, encoding, '?', 1575 false, NULL, 0, &usedLen); 1576 1577 std::vector<UInt8> buffer(usedLen); 1578 1579 CFStringGetBytes((CFStringRef)data, rangeAll, encoding, '?', 1580 false, buffer.data(), usedLen, NULL); 1581 1582 bool rectangular = bestType == ScintillaRecPboardType; 1583 1584 std::string dest(reinterpret_cast<const char *>(buffer.data()), usedLen); 1585 1586 selectedText->Copy(dest, pdoc->dbcsCodePage, 1587 vs.styles[STYLE_DEFAULT].characterSet, rectangular, false); 1588 } 1589 return true; 1590 } 1591 1592 return false; 1593} 1594 1595//-------------------------------------------------------------------------------------------------- 1596 1597// Returns the target converted to UTF8. 1598// Return the length in bytes. 1599Sci::Position ScintillaCocoa::TargetAsUTF8(char *text) const { 1600 const Sci::Position targetLength = targetRange.Length(); 1601 if (IsUnicodeMode()) { 1602 if (text) 1603 pdoc->GetCharRange(text, targetRange.start.Position(), targetLength); 1604 } else { 1605 // Need to convert 1606 const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(), 1607 vs.styles[STYLE_DEFAULT].characterSet); 1608 const std::string s = RangeText(targetRange.start.Position(), targetRange.end.Position()); 1609 CFStringRef cfsVal = CFStringFromString(s.c_str(), s.length(), encoding); 1610 if (!cfsVal) { 1611 return 0; 1612 } 1613 1614 const std::string tmputf = EncodedBytesString(cfsVal, kCFStringEncodingUTF8); 1615 1616 if (text) 1617 memcpy(text, tmputf.c_str(), tmputf.length()); 1618 CFRelease(cfsVal); 1619 return tmputf.length(); 1620 } 1621 return targetLength; 1622} 1623 1624//-------------------------------------------------------------------------------------------------- 1625 1626// Returns the text in the range converted to an NSString. 1627NSString *ScintillaCocoa::RangeTextAsString(NSRange rangePositions) const { 1628 const std::string text = RangeText(rangePositions.location, 1629 NSMaxRange(rangePositions)); 1630 if (IsUnicodeMode()) { 1631 return @(text.c_str()); 1632 } else { 1633 // Need to convert 1634 const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(), 1635 vs.styles[STYLE_DEFAULT].characterSet); 1636 CFStringRef cfsVal = CFStringFromString(text.c_str(), text.length(), encoding); 1637 1638 return (__bridge NSString *)cfsVal; 1639 } 1640} 1641 1642//-------------------------------------------------------------------------------------------------- 1643 1644// Return character range of a line. 1645NSRange ScintillaCocoa::RangeForVisibleLine(NSInteger lineVisible) { 1646 const Range posRangeLine = RangeDisplayLine(static_cast<Sci::Line>(lineVisible)); 1647 return CharactersFromPositions(NSMakeRange(posRangeLine.First(), 1648 posRangeLine.Last() - posRangeLine.First())); 1649} 1650 1651//-------------------------------------------------------------------------------------------------- 1652 1653// Returns visible line number of a text position in characters. 1654NSInteger ScintillaCocoa::VisibleLineForIndex(NSInteger index) { 1655 const NSRange rangePosition = PositionsFromCharacters(NSMakeRange(index, 0)); 1656 const Sci::Line lineVisible = DisplayFromPosition(rangePosition.location); 1657 return lineVisible; 1658} 1659 1660//-------------------------------------------------------------------------------------------------- 1661 1662// Returns a rectangle that frames the range for use by the VoiceOver cursor. 1663NSRect ScintillaCocoa::FrameForRange(NSRange rangeCharacters) { 1664 const NSRange posRange = PositionsFromCharacters(rangeCharacters); 1665 1666 NSUInteger rangeEnd = NSMaxRange(posRange); 1667 const bool endsWithLineEnd = rangeCharacters.length && 1668 (pdoc->GetColumn(rangeEnd) == 0); 1669 1670 Point ptStart = LocationFromPosition(posRange.location); 1671 const PointEnd peEndRange = static_cast<PointEnd>(peSubLineEnd|peLineEnd); 1672 Point ptEnd = LocationFromPosition(rangeEnd, peEndRange); 1673 1674 NSRect rect = NSMakeRect(ptStart.x, ptStart.y, 1675 ptEnd.x - ptStart.x, 1676 ptEnd.y - ptStart.y); 1677 1678 rect.size.width += 2; // Shows the last character better 1679 if (endsWithLineEnd) { 1680 // Add a block to the right to indicate a line end is selected 1681 rect.size.width += 20; 1682 } 1683 1684 rect.size.height += vs.lineHeight; 1685 1686 // Adjust for margin and scroll 1687 rect.origin.x = rect.origin.x - vs.textStart + vs.fixedColumnWidth; 1688 1689 return rect; 1690} 1691 1692//-------------------------------------------------------------------------------------------------- 1693 1694// Returns a rectangle that frames the range for use by the VoiceOver cursor. 1695NSRect ScintillaCocoa::GetBounds() const { 1696 return PRectangleToNSRect(GetClientRectangle()); 1697} 1698 1699//-------------------------------------------------------------------------------------------------- 1700 1701// Translates a UTF8 string into the document encoding. 1702// Return the length of the result in bytes. 1703Sci::Position ScintillaCocoa::EncodedFromUTF8(const char *utf8, char *encoded) const { 1704 const size_t inputLength = (lengthForEncode >= 0) ? lengthForEncode : strlen(utf8); 1705 if (IsUnicodeMode()) { 1706 if (encoded) 1707 memcpy(encoded, utf8, inputLength); 1708 return inputLength; 1709 } else { 1710 // Need to convert 1711 const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(), 1712 vs.styles[STYLE_DEFAULT].characterSet); 1713 1714 CFStringRef cfsVal = CFStringFromString(utf8, inputLength, kCFStringEncodingUTF8); 1715 const std::string sEncoded = EncodedBytesString(cfsVal, encoding); 1716 if (encoded) 1717 memcpy(encoded, sEncoded.c_str(), sEncoded.length()); 1718 CFRelease(cfsVal); 1719 return sEncoded.length(); 1720 } 1721} 1722 1723//-------------------------------------------------------------------------------------------------- 1724 1725void ScintillaCocoa::SetMouseCapture(bool on) { 1726 capturedMouse = on; 1727} 1728 1729//-------------------------------------------------------------------------------------------------- 1730 1731bool ScintillaCocoa::HaveMouseCapture() { 1732 return capturedMouse; 1733} 1734 1735//-------------------------------------------------------------------------------------------------- 1736 1737/** 1738 * Synchronously paint a rectangle of the window. 1739 */ 1740bool ScintillaCocoa::SyncPaint(void *gc, PRectangle rc) { 1741 paintState = painting; 1742 rcPaint = rc; 1743 PRectangle rcText = GetTextRectangle(); 1744 paintingAllText = rcPaint.Contains(rcText); 1745 std::unique_ptr<Surface> sw(Surface::Allocate(SC_TECHNOLOGY_DEFAULT)); 1746 CGContextSetAllowsAntialiasing((CGContextRef)gc, 1747 vs.extraFontFlag != SC_EFF_QUALITY_NON_ANTIALIASED); 1748 CGContextSetAllowsFontSmoothing((CGContextRef)gc, 1749 vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED); 1750 CGContextSetAllowsFontSubpixelPositioning((CGContextRef)gc, 1751 vs.extraFontFlag == SC_EFF_QUALITY_DEFAULT || 1752 vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED); 1753 sw->Init(gc, wMain.GetID()); 1754 Paint(sw.get(), rc); 1755 const bool succeeded = paintState != paintAbandoned; 1756 sw->Release(); 1757 paintState = notPainting; 1758 if (!succeeded) { 1759 NSView *marginView = (__bridge NSView *)(wMargin.GetID()); 1760 [marginView setNeedsDisplay: YES]; 1761 } 1762 return succeeded; 1763} 1764 1765//-------------------------------------------------------------------------------------------------- 1766 1767/** 1768 * Paint the margin into the SCIMarginView space. 1769 */ 1770void ScintillaCocoa::PaintMargin(NSRect aRect) { 1771 CGContextRef gc = (CGContextRef) [NSGraphicsContext currentContext].graphicsPort; 1772 1773 PRectangle rc = NSRectToPRectangle(aRect); 1774 rcPaint = rc; 1775 std::unique_ptr<Surface> sw(Surface::Allocate(SC_TECHNOLOGY_DEFAULT)); 1776 if (sw) { 1777 CGContextSetAllowsAntialiasing(gc, 1778 vs.extraFontFlag != SC_EFF_QUALITY_NON_ANTIALIASED); 1779 CGContextSetAllowsFontSmoothing(gc, 1780 vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED); 1781 CGContextSetAllowsFontSubpixelPositioning(gc, 1782 vs.extraFontFlag == SC_EFF_QUALITY_DEFAULT || 1783 vs.extraFontFlag == SC_EFF_QUALITY_LCD_OPTIMIZED); 1784 sw->Init(gc, wMargin.GetID()); 1785 PaintSelMargin(sw.get(), rc); 1786 sw->Release(); 1787 } 1788} 1789 1790//-------------------------------------------------------------------------------------------------- 1791 1792/** 1793 * Prepare for drawing. 1794 * 1795 * @param rect The area that will be drawn, given in the sender's coordinate system. 1796 */ 1797void ScintillaCocoa::WillDraw(NSRect rect) { 1798 RefreshStyleData(); 1799 PRectangle rcWillDraw = NSRectToPRectangle(rect); 1800 const Sci::Position posAfterArea = PositionAfterArea(rcWillDraw); 1801 const Sci::Position posAfterMax = PositionAfterMaxStyling(posAfterArea, true); 1802 pdoc->StyleToAdjustingLineDuration(posAfterMax); 1803 StartIdleStyling(posAfterMax < posAfterArea); 1804 NotifyUpdateUI(); 1805 if (WrapLines(WrapScope::wsVisible)) { 1806 // Wrap may have reduced number of lines so more lines may need to be styled 1807 const Sci::Position posAfterAreaWrapped = PositionAfterArea(rcWillDraw); 1808 pdoc->EnsureStyledTo(posAfterAreaWrapped); 1809 // The wrapping process has changed the height of some lines so redraw all. 1810 Redraw(); 1811 } 1812} 1813 1814//-------------------------------------------------------------------------------------------------- 1815 1816/** 1817 * ScrollText is empty because scrolling is handled by the NSScrollView. 1818 */ 1819void ScintillaCocoa::ScrollText(Sci::Line) { 1820} 1821 1822//-------------------------------------------------------------------------------------------------- 1823 1824/** 1825 * Modifies the vertical scroll position to make the current top line show up as such. 1826 */ 1827void ScintillaCocoa::SetVerticalScrollPos() { 1828 NSScrollView *scrollView = ScrollContainer(); 1829 if (scrollView) { 1830 NSClipView *clipView = scrollView.contentView; 1831 NSRect contentRect = clipView.bounds; 1832 [clipView scrollToPoint: NSMakePoint(contentRect.origin.x, topLine * vs.lineHeight)]; 1833 [scrollView reflectScrolledClipView: clipView]; 1834 } 1835} 1836 1837//-------------------------------------------------------------------------------------------------- 1838 1839/** 1840 * Modifies the horizontal scroll position to match xOffset. 1841 */ 1842void ScintillaCocoa::SetHorizontalScrollPos() { 1843 PRectangle textRect = GetTextRectangle(); 1844 1845 int maxXOffset = scrollWidth - static_cast<int>(textRect.Width()); 1846 if (maxXOffset < 0) 1847 maxXOffset = 0; 1848 if (xOffset > maxXOffset) 1849 xOffset = maxXOffset; 1850 NSScrollView *scrollView = ScrollContainer(); 1851 if (scrollView) { 1852 NSClipView *clipView = scrollView.contentView; 1853 NSRect contentRect = clipView.bounds; 1854 [clipView scrollToPoint: NSMakePoint(xOffset, contentRect.origin.y)]; 1855 [scrollView reflectScrolledClipView: clipView]; 1856 } 1857 MoveFindIndicatorWithBounce(NO); 1858} 1859 1860//-------------------------------------------------------------------------------------------------- 1861 1862/** 1863 * Used to adjust both scrollers to reflect the current scroll range and position in the editor. 1864 * Arguments no longer used as NSScrollView handles details of scroll bar sizes. 1865 * 1866 * @param nMax Number of lines in the editor. 1867 * @param nPage Number of lines per scroll page. 1868 * @return True if there was a change, otherwise false. 1869 */ 1870bool ScintillaCocoa::ModifyScrollBars(Sci::Line nMax, Sci::Line nPage) { 1871#pragma unused(nMax, nPage) 1872 return SetScrollingSize(); 1873} 1874 1875bool ScintillaCocoa::SetScrollingSize(void) { 1876 bool changes = false; 1877 SCIContentView *inner = ContentView(); 1878 if (!enteredSetScrollingSize) { 1879 enteredSetScrollingSize = true; 1880 NSScrollView *scrollView = ScrollContainer(); 1881 NSClipView *clipView = ScrollContainer().contentView; 1882 NSRect clipRect = clipView.bounds; 1883 CGFloat docHeight = pcs->LinesDisplayed() * vs.lineHeight; 1884 if (!endAtLastLine) 1885 docHeight += (int(scrollView.bounds.size.height / vs.lineHeight)-3) * vs.lineHeight; 1886 // Allow extra space so that last scroll position places whole line at top 1887 int clipExtra = int(clipRect.size.height) % vs.lineHeight; 1888 docHeight += clipExtra; 1889 // Ensure all of clipRect covered by Scintilla drawing 1890 if (docHeight < clipRect.size.height) 1891 docHeight = clipRect.size.height; 1892 CGFloat docWidth = scrollWidth; 1893 bool showHorizontalScroll = horizontalScrollBarVisible && 1894 !Wrapping(); 1895 if (!showHorizontalScroll) 1896 docWidth = clipRect.size.width; 1897 NSRect contentRect = {{0, 0}, {docWidth, docHeight}}; 1898 NSRect contentRectNow = inner.frame; 1899 changes = (contentRect.size.width != contentRectNow.size.width) || 1900 (contentRect.size.height != contentRectNow.size.height); 1901 if (changes) { 1902 inner.frame = contentRect; 1903 } 1904 scrollView.hasVerticalScroller = verticalScrollBarVisible; 1905 scrollView.hasHorizontalScroller = showHorizontalScroll; 1906 SetVerticalScrollPos(); 1907 enteredSetScrollingSize = false; 1908 } 1909 [sciView setMarginWidth: vs.fixedColumnWidth]; 1910 return changes; 1911} 1912 1913//-------------------------------------------------------------------------------------------------- 1914 1915void ScintillaCocoa::Resize() { 1916 SetScrollingSize(); 1917 ChangeSize(); 1918} 1919 1920//-------------------------------------------------------------------------------------------------- 1921 1922/** 1923 * Update fields to match scroll position after receiving a notification that the user has scrolled. 1924 */ 1925void ScintillaCocoa::UpdateForScroll() { 1926 Point ptOrigin = GetVisibleOriginInMain(); 1927 xOffset = static_cast<int>(ptOrigin.x); 1928 Sci::Line newTop = std::min(static_cast<Sci::Line>(ptOrigin.y / vs.lineHeight), MaxScrollPos()); 1929 SetTopLine(newTop); 1930} 1931 1932//-------------------------------------------------------------------------------------------------- 1933 1934/** 1935 * Register a delegate that will be called for notifications and commands. 1936 * This provides similar functionality to RegisterNotifyCallback but in an 1937 * Objective C way. 1938 * 1939 * @param delegate_ A pointer to an object that implements ScintillaNotificationProtocol. 1940 */ 1941 1942void ScintillaCocoa::SetDelegate(id<ScintillaNotificationProtocol> delegate_) { 1943 delegate = delegate_; 1944} 1945 1946//-------------------------------------------------------------------------------------------------- 1947 1948/** 1949 * Used to register a callback function for a given window. This is used to emulate the way 1950 * Windows notifies other controls (mainly up in the view hierarchy) about certain events. 1951 * 1952 * @param windowid A handle to a window. That value is generic and can be anything. It is passed 1953 * through to the callback. 1954 * @param callback The callback function to be used for future notifications. If NULL then no 1955 * notifications will be sent anymore. 1956 */ 1957void ScintillaCocoa::RegisterNotifyCallback(intptr_t windowid, SciNotifyFunc callback) { 1958 notifyObj = windowid; 1959 notifyProc = callback; 1960} 1961 1962//-------------------------------------------------------------------------------------------------- 1963 1964void ScintillaCocoa::NotifyChange() { 1965 if (notifyProc != NULL) 1966 notifyProc(notifyObj, WM_COMMAND, Platform::LongFromTwoShorts(static_cast<short>(GetCtrlID()), SCEN_CHANGE), 1967 (uintptr_t) this); 1968} 1969 1970//-------------------------------------------------------------------------------------------------- 1971 1972void ScintillaCocoa::NotifyFocus(bool focus) { 1973 if (commandEvents && notifyProc) 1974 notifyProc(notifyObj, WM_COMMAND, Platform::LongFromTwoShorts(static_cast<short>(GetCtrlID()), 1975 (focus ? SCEN_SETFOCUS : SCEN_KILLFOCUS)), 1976 (uintptr_t) this); 1977 1978 Editor::NotifyFocus(focus); 1979} 1980 1981//-------------------------------------------------------------------------------------------------- 1982 1983/** 1984 * Used to send a notification (as WM_NOTIFY call) to the procedure, which has been set by the call 1985 * to RegisterNotifyCallback (so it is not necessarily the parent window). 1986 * 1987 * @param scn The notification to send. 1988 */ 1989void ScintillaCocoa::NotifyParent(SCNotification scn) { 1990 scn.nmhdr.hwndFrom = (void *) this; 1991 scn.nmhdr.idFrom = GetCtrlID(); 1992 if (notifyProc != NULL) 1993 notifyProc(notifyObj, WM_NOTIFY, GetCtrlID(), (uintptr_t) &scn); 1994 if (delegate) 1995 [delegate notification: &scn]; 1996 if (scn.nmhdr.code == SCN_UPDATEUI) { 1997 NSView *content = ContentView(); 1998 if (scn.updated & SC_UPDATE_CONTENT) { 1999 NSAccessibilityPostNotification(content, NSAccessibilityValueChangedNotification); 2000 } 2001 if (scn.updated & SC_UPDATE_SELECTION) { 2002 NSAccessibilityPostNotification(content, NSAccessibilitySelectedTextChangedNotification); 2003 } 2004 } 2005} 2006 2007//-------------------------------------------------------------------------------------------------- 2008 2009void ScintillaCocoa::NotifyURIDropped(const char *uri) { 2010 SCNotification scn; 2011 scn.nmhdr.code = SCN_URIDROPPED; 2012 scn.text = uri; 2013 2014 NotifyParent(scn); 2015} 2016 2017//-------------------------------------------------------------------------------------------------- 2018 2019bool ScintillaCocoa::HasSelection() { 2020 return !sel.Empty(); 2021} 2022 2023//-------------------------------------------------------------------------------------------------- 2024 2025bool ScintillaCocoa::CanUndo() { 2026 return pdoc->CanUndo(); 2027} 2028 2029//-------------------------------------------------------------------------------------------------- 2030 2031bool ScintillaCocoa::CanRedo() { 2032 return pdoc->CanRedo(); 2033} 2034 2035//-------------------------------------------------------------------------------------------------- 2036 2037void ScintillaCocoa::TimerFired(NSTimer *timer) { 2038 for (TickReason tr=tickCaret; tr<=tickPlatform; tr = static_cast<TickReason>(tr+1)) { 2039 if (timers[tr] == timer) { 2040 TickFor(tr); 2041 } 2042 } 2043} 2044 2045//-------------------------------------------------------------------------------------------------- 2046 2047void ScintillaCocoa::IdleTimerFired() { 2048 bool more = Idle(); 2049 if (!more) 2050 SetIdle(false); 2051} 2052 2053//-------------------------------------------------------------------------------------------------- 2054 2055/** 2056 * Main entry point for drawing the control. 2057 * 2058 * @param rect The area to paint, given in the sender's coordinate system. 2059 * @param gc The context we can use to paint. 2060 */ 2061bool ScintillaCocoa::Draw(NSRect rect, CGContextRef gc) { 2062 return SyncPaint(gc, NSRectToPRectangle(rect)); 2063} 2064 2065//-------------------------------------------------------------------------------------------------- 2066 2067/** 2068 * Helper function to translate OS X key codes to Scintilla key codes. 2069 */ 2070static inline UniChar KeyTranslate(UniChar unicodeChar, NSEventModifierFlags modifierFlags) { 2071 switch (unicodeChar) { 2072 case NSDownArrowFunctionKey: 2073 return SCK_DOWN; 2074 case NSUpArrowFunctionKey: 2075 return SCK_UP; 2076 case NSLeftArrowFunctionKey: 2077 return SCK_LEFT; 2078 case NSRightArrowFunctionKey: 2079 return SCK_RIGHT; 2080 case NSHomeFunctionKey: 2081 return SCK_HOME; 2082 case NSEndFunctionKey: 2083 return SCK_END; 2084 case NSPageUpFunctionKey: 2085 return SCK_PRIOR; 2086 case NSPageDownFunctionKey: 2087 return SCK_NEXT; 2088 case NSDeleteFunctionKey: 2089 return SCK_DELETE; 2090 case NSInsertFunctionKey: 2091 return SCK_INSERT; 2092 case '\n': 2093 case 3: 2094 return SCK_RETURN; 2095 case 27: 2096 return SCK_ESCAPE; 2097 case '+': 2098 if (modifierFlags & NSEventModifierFlagNumericPad) 2099 return SCK_ADD; 2100 else 2101 return unicodeChar; 2102 case '-': 2103 if (modifierFlags & NSEventModifierFlagNumericPad) 2104 return SCK_SUBTRACT; 2105 else 2106 return unicodeChar; 2107 case '/': 2108 if (modifierFlags & NSEventModifierFlagNumericPad) 2109 return SCK_DIVIDE; 2110 else 2111 return unicodeChar; 2112 case 127: 2113 return SCK_BACK; 2114 case '\t': 2115 case 25: // Shift tab, return to unmodified tab and handle that via modifiers. 2116 return SCK_TAB; 2117 default: 2118 return unicodeChar; 2119 } 2120} 2121 2122//-------------------------------------------------------------------------------------------------- 2123 2124/** 2125 * Translate NSEvent modifier flags into SCI_* modifier flags. 2126 * 2127 * @param modifiers An integer bit set of NSSEvent modifier flags. 2128 * @return A set of SCI_* modifier flags. 2129 */ 2130static int TranslateModifierFlags(NSUInteger modifiers) { 2131 // Signal Control as SCI_META 2132 return 2133 (((modifiers & NSEventModifierFlagShift) != 0) ? SCI_SHIFT : 0) | 2134 (((modifiers & NSEventModifierFlagCommand) != 0) ? SCI_CTRL : 0) | 2135 (((modifiers & NSEventModifierFlagOption) != 0) ? SCI_ALT : 0) | 2136 (((modifiers & NSEventModifierFlagControl) != 0) ? SCI_META : 0); 2137} 2138 2139//-------------------------------------------------------------------------------------------------- 2140 2141/** 2142 * Main keyboard input handling method. It is called for any key down event, including function keys, 2143 * numeric keypad input and whatnot. 2144 * 2145 * @param event The event instance associated with the key down event. 2146 * @return True if the input was handled, false otherwise. 2147 */ 2148bool ScintillaCocoa::KeyboardInput(NSEvent *event) { 2149 // For now filter out function keys. 2150 NSString *input = event.charactersIgnoringModifiers; 2151 2152 bool handled = false; 2153 2154 // Handle each entry individually. Usually we only have one entry anyway. 2155 for (size_t i = 0; i < input.length; i++) { 2156 const UniChar originalKey = [input characterAtIndex: i]; 2157 NSEventModifierFlags modifierFlags = event.modifierFlags; 2158 2159 UniChar key = KeyTranslate(originalKey, modifierFlags); 2160 2161 bool consumed = false; // Consumed as command? 2162 2163 if (KeyDownWithModifiers(key, TranslateModifierFlags(modifierFlags), &consumed)) 2164 handled = true; 2165 if (consumed) 2166 handled = true; 2167 } 2168 2169 return handled; 2170} 2171 2172//-------------------------------------------------------------------------------------------------- 2173 2174/** 2175 * Used to insert already processed text provided by the Cocoa text input system. 2176 */ 2177ptrdiff_t ScintillaCocoa::InsertText(NSString *input, CharacterSource charSource) { 2178 if ([input length] == 0) { 2179 return 0; 2180 } 2181 2182 // There may be multiple characters in input so loop over them 2183 if (IsUnicodeMode()) { 2184 // There may be non-BMP characters as 2 elements in NSString so 2185 // convert to UTF-8 and use UTF8BytesOfLead. 2186 std::string encoded = EncodedBytesString((__bridge CFStringRef)input, 2187 kCFStringEncodingUTF8); 2188 std::string_view sv = encoded; 2189 while (sv.length()) { 2190 const unsigned char leadByte = sv[0]; 2191 const unsigned int bytesInCharacter = UTF8BytesOfLead[leadByte]; 2192 InsertCharacter(sv.substr(0, bytesInCharacter), charSource); 2193 sv.remove_prefix(bytesInCharacter); 2194 } 2195 return encoded.length(); 2196 } else { 2197 const CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(), 2198 vs.styles[STYLE_DEFAULT].characterSet); 2199 ptrdiff_t lengthInserted = 0; 2200 for (NSInteger i = 0; i < [input length]; i++) { 2201 NSString *character = [input substringWithRange:NSMakeRange(i, 1)]; 2202 std::string encoded = EncodedBytesString((__bridge CFStringRef)character, 2203 encoding); 2204 lengthInserted += encoded.length(); 2205 InsertCharacter(encoded, charSource); 2206 } 2207 2208 return lengthInserted; 2209 } 2210} 2211 2212//-------------------------------------------------------------------------------------------------- 2213 2214/** 2215 * Convert from a range of characters to a range of bytes. 2216 */ 2217NSRange ScintillaCocoa::PositionsFromCharacters(NSRange rangeCharacters) const { 2218 Sci::Position start = pdoc->GetRelativePositionUTF16(0, rangeCharacters.location); 2219 if (start == INVALID_POSITION) 2220 start = pdoc->Length(); 2221 Sci::Position end = pdoc->GetRelativePositionUTF16(start, rangeCharacters.length); 2222 if (end == INVALID_POSITION) 2223 end = pdoc->Length(); 2224 return NSMakeRange(start, end - start); 2225} 2226 2227//-------------------------------------------------------------------------------------------------- 2228 2229/** 2230 * Convert from a range of characters from a range of bytes. 2231 */ 2232NSRange ScintillaCocoa::CharactersFromPositions(NSRange rangePositions) const { 2233 const Sci::Position start = pdoc->CountUTF16(0, rangePositions.location); 2234 const Sci::Position len = pdoc->CountUTF16(rangePositions.location, 2235 NSMaxRange(rangePositions)); 2236 return NSMakeRange(start, len); 2237} 2238 2239//-------------------------------------------------------------------------------------------------- 2240 2241/** 2242 * Used to ensure that only one selection is active for input composition as composition 2243 * does not support multi-typing. 2244 */ 2245void ScintillaCocoa::SelectOnlyMainSelection() { 2246 sel.SetSelection(sel.RangeMain()); 2247 Redraw(); 2248} 2249 2250//-------------------------------------------------------------------------------------------------- 2251 2252/** 2253 * Convert virtual space before selection into real space. 2254 */ 2255void ScintillaCocoa::ConvertSelectionVirtualSpace() { 2256 ClearBeforeTentativeStart(); 2257} 2258 2259//-------------------------------------------------------------------------------------------------- 2260 2261/** 2262 * Erase all selected text and return whether the selection is now empty. 2263 * The selection may not be empty if the selection contained protected text. 2264 */ 2265bool ScintillaCocoa::ClearAllSelections() { 2266 ClearSelection(true); 2267 return sel.Empty(); 2268} 2269 2270//-------------------------------------------------------------------------------------------------- 2271 2272/** 2273 * Start composing for IME. 2274 */ 2275void ScintillaCocoa::CompositionStart() { 2276 if (!sel.Empty()) { 2277 NSLog(@"Selection not empty when starting composition"); 2278 } 2279 pdoc->TentativeStart(); 2280} 2281 2282//-------------------------------------------------------------------------------------------------- 2283 2284/** 2285 * Commit the IME text. 2286 */ 2287void ScintillaCocoa::CompositionCommit() { 2288 pdoc->TentativeCommit(); 2289 pdoc->DecorationSetCurrentIndicator(INDICATOR_IME); 2290 pdoc->DecorationFillRange(0, 0, pdoc->Length()); 2291} 2292 2293//-------------------------------------------------------------------------------------------------- 2294 2295/** 2296 * Remove the IME text. 2297 */ 2298void ScintillaCocoa::CompositionUndo() { 2299 pdoc->TentativeUndo(); 2300} 2301 2302//-------------------------------------------------------------------------------------------------- 2303/** 2304 * When switching documents discard any incomplete character composition state as otherwise tries to 2305 * act on the new document. 2306 */ 2307void ScintillaCocoa::SetDocPointer(Document *document) { 2308 // Drop input composition. 2309 NSTextInputContext *inctxt = [NSTextInputContext currentInputContext]; 2310 [inctxt discardMarkedText]; 2311 SCIContentView *inner = ContentView(); 2312 [inner unmarkText]; 2313 Editor::SetDocPointer(document); 2314} 2315 2316//-------------------------------------------------------------------------------------------------- 2317 2318/** 2319 * Convert NSEvent timestamp NSTimeInterval into unsigned int milliseconds wanted by Editor methods. 2320 */ 2321 2322namespace { 2323 2324unsigned int TimeOfEvent(NSEvent *event) { 2325 return static_cast<unsigned int>(event.timestamp * 1000); 2326} 2327 2328} 2329 2330//-------------------------------------------------------------------------------------------------- 2331 2332/** 2333 * Called by the owning view when the mouse pointer enters the control. 2334 */ 2335void ScintillaCocoa::MouseEntered(NSEvent *event) { 2336 if (!HaveMouseCapture()) { 2337 WndProc(SCI_SETCURSOR, (long int)SC_CURSORNORMAL, 0); 2338 2339 // Mouse location is given in screen coordinates and might also be outside of our bounds. 2340 Point location = ConvertPoint(event.locationInWindow); 2341 ButtonMoveWithModifiers(location, 2342 TimeOfEvent(event), 2343 TranslateModifierFlags(event.modifierFlags)); 2344 } 2345} 2346 2347//-------------------------------------------------------------------------------------------------- 2348 2349void ScintillaCocoa::MouseExited(NSEvent * /* event */) { 2350 // Nothing to do here. 2351} 2352 2353//-------------------------------------------------------------------------------------------------- 2354 2355void ScintillaCocoa::MouseDown(NSEvent *event) { 2356 Point location = ConvertPoint(event.locationInWindow); 2357 ButtonDownWithModifiers(location, 2358 TimeOfEvent(event), 2359 TranslateModifierFlags(event.modifierFlags)); 2360} 2361 2362void ScintillaCocoa::RightMouseDown(NSEvent *event) { 2363 Point location = ConvertPoint(event.locationInWindow); 2364 RightButtonDownWithModifiers(location, 2365 TimeOfEvent(event), 2366 TranslateModifierFlags(event.modifierFlags)); 2367} 2368 2369//-------------------------------------------------------------------------------------------------- 2370 2371void ScintillaCocoa::MouseMove(NSEvent *event) { 2372 lastMouseEvent = event; 2373 2374 ButtonMoveWithModifiers(ConvertPoint(event.locationInWindow), 2375 TimeOfEvent(event), 2376 TranslateModifierFlags(event.modifierFlags)); 2377} 2378 2379//-------------------------------------------------------------------------------------------------- 2380 2381void ScintillaCocoa::MouseUp(NSEvent *event) { 2382 ButtonUpWithModifiers(ConvertPoint(event.locationInWindow), 2383 TimeOfEvent(event), 2384 TranslateModifierFlags(event.modifierFlags)); 2385} 2386 2387//-------------------------------------------------------------------------------------------------- 2388 2389void ScintillaCocoa::MouseWheel(NSEvent *event) { 2390 bool command = (event.modifierFlags & NSEventModifierFlagCommand) != 0; 2391 int dY = 0; 2392 2393 // In order to make scrolling with larger offset smoother we scroll less lines the larger the 2394 // delta value is. 2395 if (event.deltaY < 0) 2396 dY = -static_cast<int>(sqrt(-10.0 * event.deltaY)); 2397 else 2398 dY = static_cast<int>(sqrt(10.0 * event.deltaY)); 2399 2400 if (command) { 2401 // Zoom! We play with the font sizes in the styles. 2402 // Number of steps/line is ignored, we just care if sizing up or down. 2403 if (dY > 0.5) 2404 KeyCommand(SCI_ZOOMIN); 2405 else if (dY < -0.5) 2406 KeyCommand(SCI_ZOOMOUT); 2407 } else { 2408 } 2409} 2410 2411//-------------------------------------------------------------------------------------------------- 2412 2413// Helper methods for NSResponder actions. 2414 2415void ScintillaCocoa::SelectAll() { 2416 Editor::SelectAll(); 2417} 2418 2419void ScintillaCocoa::DeleteBackward() { 2420 KeyDownWithModifiers(SCK_BACK, 0, nil); 2421} 2422 2423void ScintillaCocoa::Cut() { 2424 Editor::Cut(); 2425} 2426 2427void ScintillaCocoa::Undo() { 2428 Editor::Undo(); 2429} 2430 2431void ScintillaCocoa::Redo() { 2432 Editor::Redo(); 2433} 2434 2435//-------------------------------------------------------------------------------------------------- 2436 2437bool ScintillaCocoa::ShouldDisplayPopupOnMargin() { 2438 return displayPopupMenu == SC_POPUP_ALL; 2439} 2440 2441bool ScintillaCocoa::ShouldDisplayPopupOnText() { 2442 return displayPopupMenu == SC_POPUP_ALL || displayPopupMenu == SC_POPUP_TEXT; 2443} 2444 2445/** 2446 * Creates and returns a popup menu, which is then displayed by the Cocoa framework. 2447 */ 2448NSMenu *ScintillaCocoa::CreateContextMenu(NSEvent * /* event */) { 2449 // Call ScintillaBase to create the context menu. 2450 ContextMenu(Point(0, 0)); 2451 2452 return (__bridge NSMenu *)(popup.GetID()); 2453} 2454 2455//-------------------------------------------------------------------------------------------------- 2456 2457/** 2458 * An intermediate function to forward context menu commands from the menu action handler to 2459 * scintilla. 2460 */ 2461void ScintillaCocoa::HandleCommand(NSInteger command) { 2462 Command(static_cast<int>(command)); 2463} 2464 2465//-------------------------------------------------------------------------------------------------- 2466 2467void ScintillaCocoa::ActiveStateChanged(bool isActive) { 2468 // If the window is being deactivated, lose the focus and turn off the ticking 2469 if (!isActive) { 2470 DropCaret(); 2471 //SetFocusState( false ); 2472 FineTickerCancel(tickCaret); 2473 } else { 2474 ShowCaretAtCurrentPosition(); 2475 } 2476} 2477 2478//-------------------------------------------------------------------------------------------------- 2479 2480/** 2481 * When the window is about to move, the calltip and autcoimpletion stay in the same spot, 2482 * so cancel them. 2483 */ 2484void ScintillaCocoa::WindowWillMove() { 2485 AutoCompleteCancel(); 2486 ct.CallTipCancel(); 2487} 2488 2489// If building with old SDK, need to define version number for 10.8 2490#ifndef NSAppKitVersionNumber10_8 2491#define NSAppKitVersionNumber10_8 1187 2492#endif 2493 2494//-------------------------------------------------------------------------------------------------- 2495 2496void ScintillaCocoa::ShowFindIndicatorForRange(NSRange charRange, BOOL retaining) { 2497#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 2498 NSView *content = ContentView(); 2499 if (!layerFindIndicator) { 2500 layerFindIndicator = [[FindHighlightLayer alloc] init]; 2501 [content setWantsLayer: YES]; 2502 layerFindIndicator.geometryFlipped = content.layer.geometryFlipped; 2503 if (std::floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_8) { 2504 // Content layer is unflipped on 10.9, but the indicator shows wrong unless flipped 2505 layerFindIndicator.geometryFlipped = YES; 2506 } 2507 [content.layer addSublayer: layerFindIndicator]; 2508 } 2509 [layerFindIndicator removeAnimationForKey: @"animateFound"]; 2510 2511 if (charRange.length) { 2512 CFStringEncoding encoding = EncodingFromCharacterSet(IsUnicodeMode(), 2513 vs.styles[STYLE_DEFAULT].characterSet); 2514 std::vector<char> buffer(charRange.length); 2515 pdoc->GetCharRange(&buffer[0], charRange.location, charRange.length); 2516 2517 CFStringRef cfsFind = CFStringFromString(&buffer[0], charRange.length, encoding); 2518 layerFindIndicator.sFind = (__bridge NSString *)cfsFind; 2519 if (cfsFind) 2520 CFRelease(cfsFind); 2521 layerFindIndicator.retaining = retaining; 2522 layerFindIndicator.positionFind = charRange.location; 2523 // SCI_GETSTYLEAT reports a signed byte but want an unsigned to index into styles 2524 const char styleByte = static_cast<char>(WndProc(SCI_GETSTYLEAT, charRange.location, 0)); 2525 const long style = static_cast<unsigned char>(styleByte); 2526 std::vector<char> bufferFontName(WndProc(SCI_STYLEGETFONT, style, 0) + 1); 2527 WndProc(SCI_STYLEGETFONT, style, (sptr_t)&bufferFontName[0]); 2528 layerFindIndicator.sFont = @(&bufferFontName[0]); 2529 2530 layerFindIndicator.fontSize = WndProc(SCI_STYLEGETSIZEFRACTIONAL, style, 0) / 2531 (float)SC_FONT_SIZE_MULTIPLIER; 2532 layerFindIndicator.widthText = WndProc(SCI_POINTXFROMPOSITION, 0, charRange.location + charRange.length) - 2533 WndProc(SCI_POINTXFROMPOSITION, 0, charRange.location); 2534 layerFindIndicator.heightLine = WndProc(SCI_TEXTHEIGHT, 0, 0); 2535 MoveFindIndicatorWithBounce(YES); 2536 } else { 2537 [layerFindIndicator hideMatch]; 2538 } 2539#endif 2540} 2541 2542void ScintillaCocoa::MoveFindIndicatorWithBounce(BOOL bounce) { 2543#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 2544 if (layerFindIndicator) { 2545 CGPoint ptText = CGPointMake( 2546 WndProc(SCI_POINTXFROMPOSITION, 0, layerFindIndicator.positionFind), 2547 WndProc(SCI_POINTYFROMPOSITION, 0, layerFindIndicator.positionFind)); 2548 ptText.x = ptText.x - vs.fixedColumnWidth + xOffset; 2549 ptText.y += topLine * vs.lineHeight; 2550 if (!layerFindIndicator.geometryFlipped) { 2551 NSView *content = ContentView(); 2552 ptText.y = content.bounds.size.height - ptText.y; 2553 } 2554 [layerFindIndicator animateMatch: ptText bounce: bounce]; 2555 } 2556#endif 2557} 2558 2559void ScintillaCocoa::HideFindIndicator() { 2560#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 2561 if (layerFindIndicator) { 2562 [layerFindIndicator hideMatch]; 2563 } 2564#endif 2565} 2566 2567 2568