]> gitweb.factorcode.org Git - factor.git/blobdiff - basis/ui/backend/cocoa/views/views.factor
io.encodings.utf16n: merge with io.encodings.utf16
[factor.git] / basis / ui / backend / cocoa / views / views.factor
index 163be4e20853a6220d8030aa0be74adb641cea2e..be51f635b72c4b39b07c3376a57e491899b14bf6 100644 (file)
@@ -1,27 +1,51 @@
 ! Copyright (C) 2006, 2010 Slava Pestov
 ! See http://factorcode.org/license.txt for BSD license.
+
 USING: accessors alien alien.c-types alien.data alien.strings
-arrays assocs cocoa kernel math cocoa.messages cocoa.subclassing
-cocoa.classes cocoa.views cocoa.application cocoa.pasteboard
-cocoa.runtime cocoa.types cocoa.windows sequences io.encodings.utf8
-ui ui.private ui.gadgets ui.gadgets.private ui.gadgets.worlds ui.gestures
-core-foundation.strings core-graphics core-graphics.types threads
-combinators math.rectangles ;
+arrays assocs classes cocoa cocoa.application cocoa.classes
+cocoa.pasteboard cocoa.runtime cocoa.subclassing cocoa.touchbar
+cocoa.types cocoa.views combinators continuations
+core-foundation.strings core-graphics core-graphics.types
+core-text debugger io.encodings.string io.encodings.utf16
+io.encodings.utf8 kernel literals math math.order math.parser
+math.rectangles math.vectors namespaces opengl sequences
+splitting threads ui.backend.cocoa.input-methods ui.commands
+ui.gadgets ui.gadgets.editors ui.gadgets.line-support
+ui.gadgets.private ui.gadgets.worlds ui.gestures ui.private
+words ;
+
 IN: ui.backend.cocoa.views
 
+SLOT: window
+
 : send-mouse-moved ( view event -- )
-    [ mouse-location ] [ drop window ] 2bi move-hand fire-motion yield ;
+    [ mouse-location ] [ drop window ] 2bi
+    [ move-hand fire-motion yield ] [ drop ] if* ;
 
+! Issue #1453
 : button ( event -- n )
-    #! Cocoa -> Factor UI button mapping
-    -> buttonNumber H{ { 0 1 } { 2 2 } { 1 3 } } at ;
-
-CONSTANT: modifiers
-    {
-        { S+ HEX: 20000 }
-        { C+ HEX: 40000 }
-        { A+ HEX: 100000 }
-        { M+ HEX: 80000 }
+    ! Cocoa -> Factor UI button mapping
+    -> buttonNumber {
+        { 0 [ 1 ] }
+        { 1 [ 3 ] }
+        { 2 [ 2 ] }
+        [ ]
+    } case ;
+
+CONSTANT: NSAlphaShiftKeyMask 0x10000
+CONSTANT: NSShiftKeyMask      0x20000
+CONSTANT: NSControlKeyMask    0x40000
+CONSTANT: NSAlternateKeyMask  0x80000
+CONSTANT: NSCommandKeyMask    0x100000
+CONSTANT: NSNumericPadKeyMask 0x200000
+CONSTANT: NSHelpKeyMask       0x400000
+CONSTANT: NSFunctionKeyMask   0x800000
+
+CONSTANT: modifiers {
+        { S+ $ NSShiftKeyMask }
+        { C+ $ NSControlKeyMask }
+        { M+ $ NSCommandKeyMask }
+        { A+ $ NSAlternateKeyMask }
     }
 
 CONSTANT: key-codes
@@ -62,7 +86,7 @@ CONSTANT: key-codes
     [ event-modifiers ] [ key-code ] bi ;
 
 : send-key-event ( view gesture -- )
-    swap window propagate-key-gesture ;
+    swap window [ propagate-key-gesture ] [ drop ] if* ;
 
 : interpret-key-event ( view event -- )
     NSArray swap -> arrayWithObject: -> interpretKeyEvents: ;
@@ -82,28 +106,25 @@ CONSTANT: key-codes
     [ nip mouse-event>gesture <button-down> ]
     [ mouse-location ]
     [ drop window ]
-    2tri send-button-down ;
+    2tri
+    [ send-button-down ] [ 2drop ] if* ;
 
 : send-button-up$ ( view event -- )
     [ nip mouse-event>gesture <button-up> ]
     [ mouse-location ]
     [ drop window ]
-    2tri send-button-up ;
+    2tri
+    [ send-button-up ] [ 2drop ] if* ;
 
 : send-scroll$ ( view event -- )
     [ nip [ -> deltaX ] [ -> deltaY ] bi [ neg ] bi@ 2array ]
     [ mouse-location ]
     [ drop window ]
-    2tri send-scroll ;
+    2tri
+    [ send-scroll ] [ 2drop ] if* ;
 
-: send-action$ ( view event gesture -- junk )
-    [ drop window ] dip send-action f ;
-
-: add-resize-observer ( observer object -- )
-    [
-        "updateFactorGadgetSize:"
-        "NSViewFrameDidChangeNotification" <NSString>
-    ] dip add-observer ;
+: send-action$ ( view event gesture -- )
+    [ drop window ] dip over [ send-action ] [ 2drop ] if ;
 
 : string-or-nil? ( NSString -- ? )
     [ CF>string NSStringPboardType = ] [ t ] if* ;
@@ -141,154 +162,261 @@ CONSTANT: selector>action H{
     selector>action at
     [ swap world-focus parents-handle-gesture? t ] [ drop f f ] if* ;
 
-CLASS: {
-    { +superclass+ "NSOpenGLView" }
-    { +name+ "FactorView" }
-    { +protocols+ { "NSTextInput" } }
-}
-
-! Rendering
-{ "drawRect:" void { id SEL NSRect }
-    [ 2drop window draw-world ]
-}
-
-! Events
-{ "acceptsFirstMouse:" char { id SEL id }
-    [ 3drop 1 ]
-}
-
-{ "mouseEntered:" void { id SEL id }
-    [ nip send-mouse-moved ]
-}
-
-{ "mouseExited:" void { id SEL id }
-    [ 3drop forget-rollover ]
-}
+: touchbar-commands ( -- commands/f gadget )
+    world get-global [
+        children>> [
+            class-of "commands" word-prop
+            "touchbar" of dup [ commands>> ] when
+        ] map-find
+    ] [ f f ] if* ;
+
+TUPLE: send-touchbar-command target command ;
+
+M: send-touchbar-command send-queued-gesture
+    [ target>> ] [ command>> ] bi invoke-command ;
+
+: touchbar-invoke-command ( n -- )
+    [ touchbar-commands ] dip over [
+        rot nth second
+        send-touchbar-command queue-gesture notify-ui-thread
+        yield
+    ] [ 3drop ] if ;
+
+IMPORT: NSAttributedString
+
+<PRIVATE
+
+:: >codepoint-index ( str utf16-index -- codepoint-index )
+    0 utf16-index 2 * str utf16n encode subseq utf16n decode length ;
+
+:: >utf16-index ( str codepoint-index -- utf16-index )
+    0 codepoint-index str subseq utf16n encode length 2 /i ;
+
+:: earlier-caret/mark ( editor -- loc )
+    editor editor-caret :> caret
+    editor editor-mark :> mark
+    caret first mark first = [
+        caret second mark second < [ caret ] [ mark ] if
+    ] [
+        caret first mark first < [ caret ] [ mark ] if
+    ] if ;
+
+:: make-preedit-underlines ( gadget text range -- underlines )
+    f gadget preedit-selection-mode?<<
+    { } clone :> underlines!
+    text -> length :> text-length
+    0 0 <NSRange> :> effective-range
+    text -> string CF>string :> str
+    str utf16n encode :> byte-16n
+    0 :> cp-loc!
+    "NSMarkedClauseSegment" <NSString> :> segment-attr
+    [ effective-range [ location>> ] [ length>> ] bi + text-length < ] [
+        text
+        segment-attr
+        effective-range [ location>> ] [ length>> ] bi +
+        effective-range >c-ptr
+        -> attribute:atIndex:effectiveRange: drop
+        1 :> thickness!
+        range location>> effective-range location>> = [
+            2 thickness!
+            t gadget preedit-selection-mode?<<
+        ] when
+        underlines
+        effective-range [ location>> ] [ length>> ] bi over +
+        [ str swap >codepoint-index ] bi@ swap - :> len
+        cp-loc cp-loc len + dup cp-loc!
+        2array thickness 2array
+        suffix underlines!
+    ] while 
+    underlines length 1 = [
+        underlines first first 2 2array 1array  ! thickness: 2
+    ] [ underlines ] if ;
+
+:: update-marked-text ( gadget str selectedRange replacementRange -- )
+    replacementRange location>> NSNotFound = [
+        gadget editor-caret first
+        dup gadget editor-line
+        [ 
+            replacementRange location>>
+            >codepoint-index
+            2array gadget set-caret
+        ] [
+            replacementRange [ location>> ] [ length>> ] bi +
+            >codepoint-index
+            2array gadget set-mark
+        ] 2bi
+        gadget earlier-caret/mark dup
+        gadget preedit-start<<
+        0 1 2array v+ gadget preedit-end<<
+    ] unless
+
+    gadget preedit? [
+        gadget remove-preedit-text
+    ] when
+
+    gadget earlier-caret/mark dup
+    gadget preedit-start<<
+    0 str length 2array v+ gadget preedit-end<<
+    str gadget temp-im-input drop
+    gadget preedit-start>>
+    0 str selectedRange location>> >codepoint-index 2array v+
+    dup gadget preedit-selected-start<<
+    0
+    selectedRange [ location>> ] [ length>> ] bi + selectedRange location>>
+    [ str swap >codepoint-index ] bi@ -
+    2array v+
+    dup gadget preedit-selected-end<<
+    dup gadget set-caret gadget set-mark
+    gadget preedit-start>> gadget preedit-end>> = [
+        gadget remove-preedit-info 
+    ] when ;
+
+: set-scale-factor ( n -- )
+    [ 1.0 > ] keep f ? gl-scale-factor set-global
+    cached-lines get-global clear-assoc ;
+
+PRIVATE>
+
+<CLASS: FactorView < NSOpenGLView
+    COCOA-PROTOCOL: NSTextInputClient
+
+    METHOD: void prepareOpenGL [
+        self -> backingScaleFactor set-scale-factor
+        self -> update
+    ] ;
+
+    METHOD: void reshape [
+        self window :> window
+        window [
+            self view-dim window dim<<
+        ] when
+    ] ;
+
+    ! TouchBar
+    METHOD: void touchBarCommand0 [ 0 touchbar-invoke-command ] ;
+    METHOD: void touchBarCommand1 [ 1 touchbar-invoke-command ] ;
+    METHOD: void touchBarCommand2 [ 2 touchbar-invoke-command ] ;
+    METHOD: void touchBarCommand3 [ 3 touchbar-invoke-command ] ;
+    METHOD: void touchBarCommand4 [ 4 touchbar-invoke-command ] ;
+    METHOD: void touchBarCommand5 [ 5 touchbar-invoke-command ] ;
+    METHOD: void touchBarCommand6 [ 6 touchbar-invoke-command ] ;
+    METHOD: void touchBarCommand7 [ 7 touchbar-invoke-command ] ;
+
+    METHOD: id makeTouchBar [
+        touchbar-commands drop [
+            length 8 min <iota> [ number>string ] map
+        ] [ { } ] if* self make-touchbar
+    ] ;
+
+    METHOD: id touchBar: id touchbar makeItemForIdentifier: id string [
+        touchbar-commands drop [
+            [ self string CF>string dup string>number ] dip nth
+            second name>> "com-" ?head drop over
+            "touchBarCommand" prepend make-NSTouchBar-button
+        ] [ f ] if*
+    ] ;
+
+    ! Rendering
+    METHOD: void drawRect: NSRect rect [
+        self window [
+            [ draw-world yield ] [ print-error drop ] recover
+        ] when*
+    ] ;
+
+    ! Light/Dark Mode
+
+!     METHOD: void viewDidChangeEffectiveAppearance [
+!         self -> effectiveAppearance -> name [
+!             CF>string "NSAppearanceNameDarkAqua" =
+!             dark-theme light-theme ? switch-theme-if-default
+!         ] when*
+!     ] ;
+
+    ! Events
+    METHOD: char acceptsFirstMouse: id event [ 0 ] ;
+
+    METHOD: void mouseEntered: id event [ self event send-mouse-moved ] ;
+
+    METHOD: void mouseExited: id event [ forget-rollover ] ;
+
+    METHOD: void mouseMoved: id event [ self event send-mouse-moved ] ;
+
+    METHOD: void mouseDragged: id event [ self event send-mouse-moved ] ;
+
+    METHOD: void rightMouseDragged: id event [ self event send-mouse-moved ] ;
 
-{ "mouseMoved:" void { id SEL id }
-    [ nip send-mouse-moved ]
-}
-
-{ "mouseDragged:" void { id SEL id }
-    [ nip send-mouse-moved ]
-}
+    METHOD: void otherMouseDragged: id event [ self event send-mouse-moved ] ;
 
-{ "rightMouseDragged:" void { id SEL id }
-    [ nip send-mouse-moved ]
-}
-
-{ "otherMouseDragged:" void { id SEL id }
-    [ nip send-mouse-moved ]
-}
-
-{ "mouseDown:" void { id SEL id }
-    [ nip send-button-down$ ]
-}
+    METHOD: void mouseDown: id event [ self event send-button-down$ ] ;
 
-{ "mouseUp:" void { id SEL id }
-    [ nip send-button-up$ ]
-}
+    METHOD: void mouseUp: id event [ self event send-button-up$ ] ;
 
-{ "rightMouseDown:" void { id SEL id }
-    [ nip send-button-down$ ]
-}
+    METHOD: void rightMouseDown: id event [ self event send-button-down$ ] ;
 
-{ "rightMouseUp:" void { id SEL id }
-    [ nip send-button-up$ ]
-}
+    METHOD: void rightMouseUp: id event [ self event send-button-up$ ] ;
 
-{ "otherMouseDown:" void { id SEL id }
-    [ nip send-button-down$ ]
-}
+    METHOD: void otherMouseDown: id event [ self event send-button-down$ ] ;
 
-{ "otherMouseUp:" void { id SEL id }
-    [ nip send-button-up$ ]
-}
+    METHOD: void otherMouseUp: id event [ self event send-button-up$ ] ;
 
-{ "scrollWheel:" void { id SEL id }
-    [ nip send-scroll$ ]
-}
+    METHOD: void scrollWheel: id event [ self event send-scroll$ ] ;
 
-{ "keyDown:" void { id SEL id }
-    [ nip send-key-down-event ]
-}
+    METHOD: void keyDown: id event [ self event send-key-down-event ] ;
 
-{ "keyUp:" void { id SEL id }
-    [ nip send-key-up-event ]
-}
+    METHOD: void keyUp: id event [ self event send-key-up-event ] ;
 
-{ "validateUserInterfaceItem:" char { id SEL id }
+    METHOD: char validateUserInterfaceItem: id event
     [
-        nip -> action
-        2dup [ window ] [ utf8 alien>string ] bi* validate-action
-        [ [ 2drop ] dip >c-bool ] [ SUPER-> validateUserInterfaceItem: ] if
-    ]
-}
+        self window :> window
+        window [
+            window world-focus :> gadget
+            gadget [
+                gadget preedit? not [
+                    window event -> action utf8 alien>string validate-action
+                    [ >c-bool ] [ drop self event SUPER-> validateUserInterfaceItem: ] if
+                ] [ 0 ] if
+            ] [ 0 ] if
+        ] [ 0 ] if
+    ] ;
 
-{ "undo:" id { id SEL id }
-    [ nip undo-action send-action$ ]
-}
+    METHOD: void undo: id event [ self event undo-action send-action$ ] ;
 
-{ "redo:" id { id SEL id }
-    [ nip redo-action send-action$ ]
-}
+    METHOD: void redo: id event [ self event redo-action send-action$ ] ;
 
-{ "cut:" id { id SEL id }
-    [ nip cut-action send-action$ ]
-}
+    METHOD: void cut: id event [ self event cut-action send-action$ ] ;
 
-{ "copy:" id { id SEL id }
-    [ nip copy-action send-action$ ]
-}
+    METHOD: void copy: id event [ self event copy-action send-action$ ] ;
 
-{ "paste:" id { id SEL id }
-    [ nip paste-action send-action$ ]
-}
+    METHOD: void paste: id event [ self event paste-action send-action$ ] ;
 
-{ "delete:" id { id SEL id }
-    [ nip delete-action send-action$ ]
-}
+    METHOD: void delete: id event [ self event delete-action send-action$ ] ;
 
-{ "selectAll:" id { id SEL id }
-    [ nip select-all-action send-action$ ]
-}
+    METHOD: void selectAll: id event [ self event select-all-action send-action$ ] ;
 
-{ "newDocument:" id { id SEL id }
-    [ nip new-action send-action$ ]
-}
+    METHOD: void newDocument: id event [ self event new-action send-action$ ] ;
 
-{ "openDocument:" id { id SEL id }
-    [ nip open-action send-action$ ]
-}
+    METHOD: void openDocument: id event [ self event open-action send-action$ ] ;
 
-{ "saveDocument:" id { id SEL id }
-    [ nip save-action send-action$ ]
-}
+    METHOD: void saveDocument: id event [ self event save-action send-action$ ] ;
 
-{ "saveDocumentAs:" id { id SEL id }
-    [ nip save-as-action send-action$ ]
-}
+    METHOD: void saveDocumentAs: id event [ self event save-as-action send-action$ ] ;
 
-{ "revertDocumentToSaved:" id { id SEL id }
-    [ nip revert-action send-action$ ]
-}
+    METHOD: void revertDocumentToSaved: id event [ self event revert-action send-action$ ] ;
 
-! Multi-touch gestures: this is undocumented.
-! http://cocoadex.com/2008/02/nsevent-modifications-swipe-ro.html
-{ "magnifyWithEvent:" void { id SEL id }
+    ! Multi-touch gestures
+    METHOD: void magnifyWithEvent: id event
     [
-        nip
+        self event
         dup -> deltaZ sgn {
             {  1 [ zoom-in-action send-action$ ] }
             { -1 [ zoom-out-action send-action$ ] }
             {  0 [ 2drop ] }
         } case
-    ]
-}
+    ] ;
 
-{ "swipeWithEvent:" void { id SEL id }
+    METHOD: void swipeWithEvent: id event
     [
-        nip
+        self event
         dup -> deltaX sgn {
             {  1 [ left-action send-action$ ] }
             { -1 [ right-action send-action$ ] }
@@ -302,165 +430,278 @@ CLASS: {
                 ]
             }
         } case
-    ]
-}
+    ] ;
 
-{ "acceptsFirstResponder" char { id SEL }
-    [ 2drop 1 ]
-}
+    METHOD: char acceptsFirstResponder [ 1 ] ;
 
-! Services
-{ "validRequestorForSendType:returnType:" id { id SEL id id }
+    ! Services
+    METHOD: id validRequestorForSendType: id sendType returnType: id returnType
     [
         ! We return either self or nil
-        [ over window-focus ] 2dip
-        valid-service? [ drop ] [ 2drop f ] if
-    ]
-}
+        self window [
+            world-focus sendType returnType
+            valid-service? [ self ] [ f ] if
+        ] [ f ] if*
+    ] ;
 
-{ "writeSelectionToPasteboard:types:" char { id SEL id id }
+    METHOD: char writeSelectionToPasteboard: id pboard types: id types
     [
-        CF>string-array NSStringPboardType swap member? [
-            [ drop window-focus gadget-selection ] dip over
-            [ set-pasteboard-string 1 ] [ 2drop 0 ] if
-        ] [ 3drop 0 ] if
-    ]
-}
-
-{ "readSelectionFromPasteboard:" char { id SEL id }
+        NSStringPboardType types CF>string-array member? [
+            self window [
+                world-focus gadget-selection
+                [ pboard set-pasteboard-string 1 ] [ 0 ] if*
+            ] [ 0 ] if*
+        ] [ 0 ] if
+    ] ;
+
+    METHOD: char readSelectionFromPasteboard: id pboard
     [
-        pasteboard-string dup [
-            [ drop window ] dip swap user-input 1
-        ] [ 3drop 0 ] if
-    ]
-}
-
-! Text input
-{ "insertText:" void { id SEL id }
-    [ nip CF>string swap window user-input ]
-}
-
-{ "hasMarkedText" char { id SEL }
-    [ 2drop 0 ]
-}
-
-{ "markedRange" NSRange { id SEL }
-    [ 2drop 0 0 <NSRange> ]
-}
-
-{ "selectedRange" NSRange { id SEL }
-    [ 2drop 0 0 <NSRange> ]
-}
-
-{ "setMarkedText:selectedRange:" void { id SEL id NSRange }
-    [ 2drop 2drop ]
-}
-
-{ "unmarkText" void { id SEL }
-    [ 2drop ]
-}
-
-{ "validAttributesForMarkedText" id { id SEL }
-    [ 2drop NSArray -> array ]
-}
-
-{ "attributedSubstringFromRange:" id { id SEL NSRange }
-    [ 3drop f ]
-}
-
-{ "characterIndexForPoint:" NSUInteger { id SEL NSPoint }
-    [ 3drop 0 ]
-}
-
-{ "firstRectForCharacterRange:" NSRect { id SEL NSRange }
-    [ 3drop 0 0 0 0 <CGRect> ]
-}
-
-{ "conversationIdentifier" NSInteger { id SEL }
-    [ drop alien-address ]
-}
-
-! Initialization
-{ "updateFactorGadgetSize:" void { id SEL id }
-    [ 2drop [ window ] [ view-dim ] bi >>dim drop yield ]
-}
-
-{ "doCommandBySelector:" void { id SEL SEL }
-    [ 3drop ]
-}
-
-{ "initWithFrame:pixelFormat:" id { id SEL NSRect id }
+        self window :> window
+        window [
+            pboard pasteboard-string
+            [ window user-input 1 ] [ 0 ] if*
+        ] [ 0 ] if
+    ] ;
+
+    ! Text input
+    METHOD: void insertText: id text replacementRange: NSRange replacementRange [
+        self window :> window
+        window [
+            "" clone :> str!
+            text NSString -> class -> isKindOfClass: 0 = not [
+                text CF>string str!
+            ] [
+                text -> string CF>string str!
+            ] if
+            window world-focus :> gadget
+            gadget [
+                gadget support-input-methods? [
+                    replacementRange location>> NSNotFound = [
+                        gadget editor-caret first
+                        dup gadget editor-line
+                        [ 
+                            replacementRange location>> >codepoint-index
+                            2array gadget set-caret
+                        ] [
+                            replacementRange [ location>> ] [ length>> ] bi +
+                            >codepoint-index
+                            2array gadget set-mark
+                        ] 2bi
+                    ] unless
+                    gadget preedit? [
+                        gadget remove-preedit-text
+                        gadget remove-preedit-info
+                        str gadget user-input* drop
+                        f gadget preedit-selection-mode?<<
+                    ] [
+                        str window user-input
+                    ] if
+                ] [ 
+                    str window user-input
+                ] if
+            ] when
+        ] when
+    ] ;
+
+    METHOD: char hasMarkedText [
+        self window :> window
+        window [
+            window world-focus :> gadget
+            gadget [
+                gadget preedit? 1 0 ?
+            ] [ 0 ] if
+        ] [ 0 ] if
+    ] ;
+
+    METHOD: NSRange markedRange [
+        self window :> window
+        window [
+            window world-focus :> gadget
+            gadget [
+                gadget preedit? [
+                    gadget preedit-start>> second
+                    gadget preedit-end>> second < [
+                        gadget preedit-start>> first gadget editor-line :> str
+                        gadget preedit-start>> second           ! location
+                        gadget preedit-end>> second
+                        [ str swap >utf16-index ] bi@ over -    ! length
+                    ] [ NSNotFound 0 ] if
+                ] [ NSNotFound 0 ] if
+            ] [ NSNotFound 0 ] if
+        ] [ NSNotFound 0 ] if
+        <NSRange>
+    ] ;
+
+    METHOD: NSRange selectedRange [
+        self window :> window
+        window [
+            window world-focus :> gadget
+            gadget [
+                gadget support-input-methods? [
+                    gadget editor-caret first gadget editor-line :> str
+                    gadget preedit? [
+                        str
+                        gadget preedit-selected-start>> second
+                        gadget preedit-start>> second
+                        - >utf16-index                        ! location
+                        gadget preedit-selected-end>> second
+                        gadget preedit-selected-start>> second
+                        [ str swap >utf16-index ] bi@ -       ! length
+                    ] [
+                        str gadget editor-caret second >utf16-index 0
+                    ] if
+                ] [ 0 0 ] if
+            ] [ 0 0 ] if
+        ] [ 0 0 ] if 
+        <NSRange>
+    ] ;
+
+    METHOD: void setMarkedText: id text selectedRange: NSRange selectedRange
+                                     replacementRange: NSRange replacementRange [
+        self window :> window
+        window [
+            window world-focus :> gadget
+            gadget [
+                { } clone :> underlines!
+                "" clone :> str!
+                text NSString -> class -> isKindOfClass: 0 = not [
+                    text CF>string str!
+                ] [
+                    text -> string CF>string str!
+                    gadget support-input-methods? [
+                        gadget text selectedRange make-preedit-underlines underlines!
+                    ] when
+                ] if
+                gadget support-input-methods? [
+                    gadget str selectedRange replacementRange update-marked-text
+                    underlines gadget preedit-underlines<<
+                ] when
+            ] when
+        ] when
+    ] ;
+
+    METHOD: void unmarkText [
+        self window :> window
+        window [
+            window world-focus :> gadget
+            gadget [
+                gadget support-input-methods? [
+                    gadget preedit? [
+                        gadget {
+                            [ preedit-start>> second ]
+                            [ preedit-end>> second ]
+                            [ preedit-start>> first ]
+                            [ editor-line ]
+                        } cleave subseq
+                        gadget remove-preedit-text
+                        gadget remove-preedit-info
+                        gadget user-input* drop
+                    ] when
+                    f gadget preedit-selection-mode?<<
+                ] when
+            ] when
+        ] when
+    ] ;
+
+    METHOD: id validAttributesForMarkedText [
+        NSArray "NSMarkedClauseSegment" <NSString> -> arrayWithObject:
+    ] ;
+
+    METHOD: id attributedSubstringForProposedRange: NSRange aRange
+                                       actualRange: id actualRange [ f ] ;
+
+    METHOD: NSUInteger characterIndexForPoint: NSPoint point [ 0 ] ;
+
+    METHOD: NSRect firstRectForCharacterRange: NSRange aRange
+                                  actualRange: NSRange actualRange [
+        self window :> window
+        window [
+            window world-focus :> gadget
+            gadget [
+                gadget support-input-methods? [
+                    gadget editor-caret first gadget editor-line :> str
+                    str aRange location>> >codepoint-index :> start-pos
+                    gadget editor-caret first start-pos 2array gadget loc>x
+                    gadget caret-loc second gadget caret-dim second + 
+                    2array                     ! character pos
+                    gadget screen-loc v+       ! + gadget pos
+                    { 1 -1 } v*
+                    window handle>> window>> dup -> frame -> contentRectForFrameRect:
+                    CGRect-top-left 2array v+  ! + window pos
+                    first2 [ >fixnum ] bi@ 0 gadget line-height >fixnum
+                ] [ 0 0 0 0 ] if
+            ] [ 0 0 0 0 ] if
+        ] [ 0 0 0 0 ] if
+        <CGRect>
+    ] ;
+
+    METHOD: void doCommandBySelector: SEL selector [ ] ;
+
+    ! Initialization
+    METHOD: id initWithFrame: NSRect frame pixelFormat: id pixelFormat
     [
-        [ drop ] 2dip
-        SUPER-> initWithFrame:pixelFormat:
-        dup dup add-resize-observer
-    ]
-}
+        self frame pixelFormat SUPER-> initWithFrame:pixelFormat:
+    ] ;
 
-{ "isOpaque" char { id SEL }
-    [
-        2drop 0
-    ]
-}
+    METHOD: char isOpaque [ 0 ] ;
 
-{ "dealloc" void { id SEL }
+    METHOD: void dealloc
     [
-        drop
-        [ remove-observer ]
-        [ SUPER-> dealloc ]
-        bi
-    ]
-} ;
+        self remove-observer
+        self SUPER-> dealloc
+    ] ;
+;CLASS>
 
 : sync-refresh-to-screen ( GLView -- )
-    -> openGLContext -> CGLContextObj NSOpenGLCPSwapInterval 1 <int>
+    -> openGLContext -> CGLContextObj NSOpenGLCPSwapInterval 1 int <ref>
     CGLSetParameter drop ;
 
 : <FactorView> ( dim pixel-format -- view )
-    [ FactorView ] 2dip <GLView> [ sync-refresh-to-screen ] keep ;
+    [ FactorView ] 2dip <GLView>
+    [ -> backingScaleFactor set-scale-factor ] keep
+    [ sync-refresh-to-screen ] keep ;
 
 : save-position ( world window -- )
     -> frame CGRect-top-left 2array >>window-loc drop ;
 
-CLASS: {
-    { +superclass+ "NSObject" }
-    { +name+ "FactorWindowDelegate" }
-}
+<CLASS: FactorWindowDelegate < NSObject
 
-{ "windowDidMove:" void { id SEL id }
+    METHOD: void windowDidMove: id notification
     [
-        2nip -> object [ -> contentView window ] keep save-position
-    ]
-}
+        notification -> object -> contentView window
+        [ notification -> object save-position ] when*
+    ] ;
 
-{ "windowDidBecomeKey:" void { id SEL id }
+    METHOD: void windowDidBecomeKey: id notification
     [
-        2nip -> object -> contentView window focus-world
-    ]
-}
+        notification -> object -> contentView window
+        [ focus-world ] when*
+    ] ;
 
-{ "windowDidResignKey:" void { id SEL id }
+    METHOD: void windowDidResignKey: id notification
     [
         forget-rollover
-        2nip -> object -> contentView
-        dup -> isInFullScreenMode 0 =
-        [ window [ unfocus-world ] when* ]
-        [ drop ] if
-    ]
-}
+        notification -> object -> contentView :> view
+        view window :> window
+        window [
+            view -> isInFullScreenMode 0 =
+            [ window unfocus-world ] when
+        ] when
+    ] ;
 
-{ "windowShouldClose:" char { id SEL id }
-    [
-        3drop 1
-    ]
-}
+    METHOD: char windowShouldClose: id notification [ 1 ] ;
 
-{ "windowWillClose:" void { id SEL id }
+    METHOD: void windowWillClose: id notification
     [
-        2nip -> object -> contentView
+        notification -> object -> contentView
         [ window ungraft ] [ unregister-window ] bi
-    ]
-} ;
+    ] ;
+
+    METHOD: void windowDidChangeBackingProperties: id notification
+    [
+        notification -> object -> backingScaleFactor set-scale-factor
+    ] ;
+;CLASS>
 
 : install-window-delegate ( window -- )
     FactorWindowDelegate install-delegate ;