diff options
Diffstat (limited to 'cocoa/ScintillaView.mm')
-rw-r--r-- | cocoa/ScintillaView.mm | 241 |
1 files changed, 131 insertions, 110 deletions
diff --git a/cocoa/ScintillaView.mm b/cocoa/ScintillaView.mm index 43ceb8b48..d5f658755 100644 --- a/cocoa/ScintillaView.mm +++ b/cocoa/ScintillaView.mm @@ -19,9 +19,6 @@ using namespace Scintilla; static NSCursor* reverseArrowCursor; static NSCursor* waitCursor; -// The scintilla indicator used for keyboard input. -#define INPUT_INDICATOR INDIC_MAX - 1 - NSString *const SCIUpdateUINotification = @"SCIUpdateUI"; /** @@ -347,14 +344,52 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange { - return nil; + const NSRange posRange = mOwner.backend->PositionsFromCharacters(aRange); + // The backend validated aRange and may have removed characters beyond the end of the document. + const NSRange charRange = mOwner.backend->CharactersFromPositions(posRange); + if (!NSEqualRanges(aRange, charRange)) + { + *actualRange = charRange; + } + + [mOwner message: SCI_SETTARGETRANGE wParam: posRange.location lParam: NSMaxRange(posRange)]; + std::string text([mOwner message: SCI_TARGETASUTF8] + 1, 0); + [mOwner message: SCI_TARGETASUTF8 wParam: 0 lParam: reinterpret_cast<sptr_t>(&text[0])]; + NSString *result = [NSString stringWithUTF8String: text.c_str()]; + NSMutableAttributedString *asResult = [[[NSMutableAttributedString alloc] initWithString:result] autorelease]; + + const NSRange rangeAS = NSMakeRange(0, [asResult length]); + const long style = [mOwner message: SCI_GETSTYLEAT wParam:posRange.location]; + std::string fontName([mOwner message: SCI_STYLEGETFONT wParam:style lParam:0] + 1, 0); + [mOwner message: SCI_STYLEGETFONT wParam:style lParam:(sptr_t)&fontName[0]]; + const CGFloat fontSize = [mOwner message: SCI_STYLEGETSIZEFRACTIONAL wParam:style] / 100.0f; + NSString *sFontName = [NSString stringWithUTF8String: fontName.c_str()]; + NSFont *font = [NSFont fontWithName:sFontName size:fontSize]; + [asResult addAttribute:NSFontAttributeName value:font range:rangeAS]; + + return asResult; } //-------------------------------------------------------------------------------------------------- - (NSUInteger) characterIndexForPoint: (NSPoint) point { - return NSNotFound; + const NSRect rectPoint = {point, NSZeroSize}; + const NSRect rectInWindow = [self.window convertRectFromScreen:rectPoint]; + const NSRect rectLocal = [[[self superview] superview] convertRect:rectInWindow fromView:nil]; + + const long position = [mOwner message: SCI_CHARPOSITIONFROMPOINT + wParam: rectLocal.origin.x + lParam: rectLocal.origin.y]; + if (position == INVALID_POSITION) + { + return NSNotFound; + } + else + { + const NSRange index = mOwner.backend->CharactersFromPositions(NSMakeRange(position, 0)); + return index.location; + } } //-------------------------------------------------------------------------------------------------- @@ -369,32 +404,19 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) - (NSRect) firstRectForCharacterRange: (NSRange) aRange actualRange: (NSRangePointer) actualRange { + const NSRange posRange = mOwner.backend->PositionsFromCharacters(aRange); + NSRect rect; - rect.origin.x = [ScintillaView directCall: mOwner - message: SCI_POINTXFROMPOSITION - wParam: 0 - lParam: aRange.location]; - rect.origin.y = [ScintillaView directCall: mOwner - message: SCI_POINTYFROMPOSITION - wParam: 0 - lParam: aRange.location]; - NSUInteger rangeEnd = aRange.location + aRange.length; - rect.size.width = [ScintillaView directCall: mOwner - message: SCI_POINTXFROMPOSITION - wParam: 0 - lParam: rangeEnd] - rect.origin.x; - rect.size.height = [ScintillaView directCall: mOwner - message: SCI_POINTYFROMPOSITION - wParam: 0 - lParam: rangeEnd] - rect.origin.y; - rect.size.height += [ScintillaView directCall: mOwner - message: SCI_TEXTHEIGHT - wParam: 0 - lParam: 0]; - rect = [[[self superview] superview] convertRect:rect toView:nil]; - rect = [self.window convertRectToScreen:rect]; - - return rect; + rect.origin.x = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: posRange.location]; + rect.origin.y = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: posRange.location]; + const NSUInteger rangeEnd = NSMaxRange(posRange); + rect.size.width = [mOwner message: SCI_POINTXFROMPOSITION wParam: 0 lParam: rangeEnd] - rect.origin.x; + rect.size.height = [mOwner message: SCI_POINTYFROMPOSITION wParam: 0 lParam: rangeEnd] - rect.origin.y; + rect.size.height += [mOwner message: SCI_TEXTHEIGHT wParam: 0 lParam: 0]; + const NSRect rectInWindow = [[[self superview] superview] convertRect:rect toView:nil]; + const NSRect rectScreen = [self.window convertRectToScreen:rectInWindow]; + + return rectScreen; } //-------------------------------------------------------------------------------------------------- @@ -419,14 +441,27 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) } // Remove any previously marked text first. - [self removeMarkedText]; + mOwner.backend->CompositionUndo(); + if (mMarkedTextRange.location != NSNotFound) + { + const NSRange posRangeMark = mOwner.backend->PositionsFromCharacters(mMarkedTextRange); + [mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeMark.location]; + } + mMarkedTextRange = NSMakeRange(NSNotFound, 0); if (replacementRange.location == (NSNotFound-1)) // This occurs when the accent popup is visible and menu selected. // Its replacing a non-existent position so do nothing. return; - [mOwner deleteRange: replacementRange]; + if (replacementRange.location != NSNotFound) + { + const NSRange posRangeReplacement = mOwner.backend->PositionsFromCharacters(replacementRange); + [mOwner message: SCI_DELETERANGE + wParam: posRangeReplacement.location + lParam: posRangeReplacement.length]; + [mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeReplacement.location]; + } NSString* newText = @""; if ([aString isKindOfClass:[NSString class]]) @@ -448,9 +483,10 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) - (NSRange) selectedRange { - long begin = [mOwner getGeneralProperty: SCI_GETSELECTIONSTART parameter: 0]; - long end = [mOwner getGeneralProperty: SCI_GETSELECTIONEND parameter: 0]; - return NSMakeRange(begin, end - begin); + const long positionBegin = [mOwner message: SCI_GETSELECTIONSTART]; + const long positionEnd = [mOwner message: SCI_GETSELECTIONEND]; + NSRange posRangeSel = NSMakeRange(positionBegin, positionEnd-positionBegin); + return mOwner.backend->CharactersFromPositions(posRangeSel); } //-------------------------------------------------------------------------------------------------- @@ -473,86 +509,85 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) if ([aString isKindOfClass:[NSAttributedString class]]) newText = (NSString*) [aString string]; - long currentPosition; - // Replace marked text if there is one. if (mMarkedTextRange.length > 0) { + mOwner.backend->CompositionUndo(); if (replacementRange.location != NSNotFound) { // This situation makes no sense and has not occurred in practice. - // Should the marked range remain marked in addition to the new text - // or should it be removed first? NSLog(@"Can not handle a replacement range when there is also a marked range"); } - - [mOwner deleteRange: mMarkedTextRange]; - currentPosition = mMarkedTextRange.location; + else + { + replacementRange = mMarkedTextRange; + const NSRange posRangeMark = mOwner.backend->PositionsFromCharacters(mMarkedTextRange); + [mOwner message: SCI_SETEMPTYSELECTION wParam: posRangeMark.location]; + } } else { // Must perform deletion before entering composition mode or else // both document and undo history will not contain the deleted text // leading to an inaccurate and unusable undo history. + + // Convert selection virtual space into real space + mOwner.backend->ConvertSelectionVirtualSpace(); if (replacementRange.location != NSNotFound) { - [mOwner deleteRange: replacementRange]; - currentPosition = replacementRange.location; + const NSRange posRangeReplacement = mOwner.backend->PositionsFromCharacters(replacementRange); + [mOwner message: SCI_DELETERANGE + wParam: posRangeReplacement.location + lParam: posRangeReplacement.length]; } else // No marked or replacement range, so replace selection { - // Ensure only a single selection - mOwner.backend->SelectOnlyMainSelection(); - - NSRange selectionRangeCurrent = [self selectedRange]; - if (selectionRangeCurrent.length > 0) - { - [mOwner deleteRange: selectionRangeCurrent]; + if (!mOwner.backend->ScintillaCocoa::ClearAllSelections()) { + // Some of the selection is protected so can not perform composition here + return; } - currentPosition = selectionRangeCurrent.location; + // Ensure only a single selection. + mOwner.backend->SelectOnlyMainSelection(); + replacementRange = [self selectedRange]; } - - // Switching into composition so remember if collecting undo. - undoCollectionWasActive = [mOwner getGeneralProperty: SCI_GETUNDOCOLLECTION] != 0; - - // Keep Scintilla from collecting undo actions for the composition task. - [mOwner setGeneralProperty: SCI_SETUNDOCOLLECTION value: 0]; } - [mOwner message: SCI_SETEMPTYSELECTION wParam: currentPosition]; - // Note: Scintilla internally works almost always with bytes instead chars, so we need to take - // this into account when determining selection ranges and such. - int lengthInserted = mOwner.backend->InsertText(newText); + // To support IME input to multiple selections, the following code would + // need to insert newText at each selection, mark each piece of new text and then + // select range relative to each insertion. - if (lengthInserted > 0) + if ([newText length]) { - mMarkedTextRange = NSMakeRange(currentPosition, lengthInserted); + // Switching into composition. + mOwner.backend->CompositionStart(); + + NSRange posRangeCurrent = mOwner.backend->PositionsFromCharacters(NSMakeRange(replacementRange.location, 0)); + // Note: Scintilla internally works almost always with bytes instead chars, so we need to take + // this into account when determining selection ranges and such. + int lengthInserted = mOwner.backend->InsertText(newText); + posRangeCurrent.length = lengthInserted; + mMarkedTextRange = mOwner.backend->CharactersFromPositions(posRangeCurrent); // Mark the just inserted text. Keep the marked range for later reset. - [mOwner setGeneralProperty: SCI_SETINDICATORCURRENT value: INPUT_INDICATOR]; + [mOwner setGeneralProperty: SCI_SETINDICATORCURRENT value: INDIC_IME]; [mOwner setGeneralProperty: SCI_INDICATORFILLRANGE - parameter: mMarkedTextRange.location - value: mMarkedTextRange.length]; + parameter: posRangeCurrent.location + value: posRangeCurrent.length]; } else { mMarkedTextRange = NSMakeRange(NSNotFound, 0); // Re-enable undo action collection if composition ended (indicated by an empty mark string). - if (undoCollectionWasActive) - [mOwner setGeneralProperty: SCI_SETUNDOCOLLECTION value: range.length == 0]; + mOwner.backend->CompositionCommit(); } // Select the part which is indicated in the given range. It does not scroll the caret into view. if (range.length > 0) { // range is in characters so convert to bytes for selection. - long rangeStart = currentPosition; - for (size_t characterInComposition=0; characterInComposition<range.location; characterInComposition++) - rangeStart = [mOwner getGeneralProperty: SCI_POSITIONAFTER parameter: rangeStart]; - long rangeEnd = rangeStart; - for (size_t characterInRange=0; characterInRange<range.length; characterInRange++) - rangeEnd = [mOwner getGeneralProperty: SCI_POSITIONAFTER parameter: rangeEnd]; - [mOwner setGeneralProperty: SCI_SETSELECTION parameter: rangeEnd value: rangeStart]; + range.location += replacementRange.location; + NSRange posRangeSelect = mOwner.backend->PositionsFromCharacters(range); + [mOwner setGeneralProperty: SCI_SETSELECTION parameter: NSMaxRange(posRangeSelect) value: posRangeSelect.location]; } } @@ -562,35 +597,8 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) { if (mMarkedTextRange.length > 0) { - [mOwner setGeneralProperty: SCI_SETINDICATORCURRENT value: INPUT_INDICATOR]; - [mOwner setGeneralProperty: SCI_INDICATORCLEARRANGE - parameter: mMarkedTextRange.location - value: mMarkedTextRange.length]; - mMarkedTextRange = NSMakeRange(NSNotFound, 0); - - // Reenable undo action collection, after we are done with text composition. - if (undoCollectionWasActive) - [mOwner setGeneralProperty: SCI_SETUNDOCOLLECTION value: 1]; - } -} - -//-------------------------------------------------------------------------------------------------- - -/** - * Removes any currently marked text. - */ -- (void) removeMarkedText -{ - if (mMarkedTextRange.length > 0) - { - // We have already marked text. Replace that. - [mOwner deleteRange: mMarkedTextRange]; - + mOwner.backend->CompositionCommit(); mMarkedTextRange = NSMakeRange(NSNotFound, 0); - - // Reenable undo action collection, after we are done with text composition. - if (undoCollectionWasActive) - [mOwner setGeneralProperty: SCI_SETUNDOCOLLECTION value: 1]; } } @@ -831,12 +839,24 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) - (void) paste: (id) sender { #pragma unused(sender) + if (mMarkedTextRange.location != NSNotFound) + { + [[NSTextInputContext currentInputContext] discardMarkedText]; + mOwner.backend->CompositionCommit(); + mMarkedTextRange = NSMakeRange(NSNotFound, 0); + } mOwner.backend->Paste(); } - (void) undo: (id) sender { #pragma unused(sender) + if (mMarkedTextRange.location != NSNotFound) + { + [[NSTextInputContext currentInputContext] discardMarkedText]; + mOwner.backend->CompositionCommit(); + mMarkedTextRange = NSMakeRange(NSNotFound, 0); + } mOwner.backend->Undo(); } @@ -848,7 +868,7 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) - (BOOL) canUndo { - return mOwner.backend->CanUndo(); + return mOwner.backend->CanUndo() && (mMarkedTextRange.location == NSNotFound); } - (BOOL) canRedo @@ -1141,10 +1161,10 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) // Setup a special indicator used in the editor to provide visual feedback for // input composition, depending on language, keyboard etc. - [self setColorProperty: SCI_INDICSETFORE parameter: INPUT_INDICATOR fromHTML: @"#FF0000"]; - [self setGeneralProperty: SCI_INDICSETUNDER parameter: INPUT_INDICATOR value: 1]; - [self setGeneralProperty: SCI_INDICSETSTYLE parameter: INPUT_INDICATOR value: INDIC_PLAIN]; - [self setGeneralProperty: SCI_INDICSETALPHA parameter: INPUT_INDICATOR value: 100]; + [self setColorProperty: SCI_INDICSETFORE parameter: INDIC_IME fromHTML: @"#FF0000"]; + [self setGeneralProperty: SCI_INDICSETUNDER parameter: INDIC_IME value: 1]; + [self setGeneralProperty: SCI_INDICSETSTYLE parameter: INDIC_IME value: INDIC_PLAIN]; + [self setGeneralProperty: SCI_INDICSETALPHA parameter: INDIC_IME value: 100]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self @@ -1318,7 +1338,8 @@ static NSCursor *cursorFromEnum(Window::Cursor cursor) { if (aRange.length > 0) { - [self message: SCI_DELETERANGE wParam: aRange.location lParam: aRange.length]; + NSRange posRange = mBackend->PositionsFromCharacters(aRange); + [self message: SCI_DELETERANGE wParam: posRange.location lParam: posRange.length]; } } |