yi-0.6.0: Yi/UI/Cocoa/TextView.hs
{-# LANGUAGE TemplateHaskell, EmptyDataDecls, MultiParamTypeClasses,
TypeSynonymInstances, FlexibleInstances, DeriveDataTypeable #-}
--
-- Copyright (c) 2008 Gustav Munkby
--
-- | This declares our special text-view class. The textview interpretes
-- mouse events so that mouse-selection in Yi should function as in any
-- Cocoa application
module Yi.UI.Cocoa.TextView
( YiTextView
, _YiTextView
, initializeClass_YiTextView
, _runBuffer
, YiScrollView
, _YiScrollView
, initializeClass_YiScrollView
, _leftScroller
, visibleRange
)where
import Prelude ()
import Yi.Prelude
import Yi.String
import Yi.Buffer hiding (runBuffer)
import Yi.UI.Cocoa.Utils
-- Specify Cocoa imports explicitly, to avoid name-clashes.
-- Since the number of functions recognized by HOC varies
-- between revisions, this seems like the safest choice.
import HOC
import Foundation (
NSPoint(..),NSRange(..),nsMinY,nsWidth,nsOffsetRect,NSArray,
addObject,haskellString,NSMutableArray,NSValue,
_NSMutableArray,array,addObjectsFromArray,rangeValue)
import AppKit (
NSSelectionAffinity,characterRangeForGlyphRangeActualGlyphRange,
glyphRangeForBoundingRectInTextContainer,layoutManager,textContainer,
textContainerOrigin,visibleRect,frame,verticalScroller,NSTextView,
NSTextViewClass,setSelectedRangesAffinityStillSelecting,NSScrollView,
NSScrollViewClass,tile,setFrameOrigin,performDragOperation,
NSTextViewMetaClass,NSScrollViewMetaClass,
acceptableDragTypes,nsStringPboardType,stringForType,NSPasteboard,
convertPointFromView,availableTypeFromArray,_NSPasteboard,
typesFilterableTo,nsFilenamesPboardType,propertyListForType,
glyphIndexForPointInTextContainerFractionOfDistanceThroughGlyph)
import qualified AppKit.NSScrollView (contentView)
import Foreign
import Foreign.C
-- TODO: The correct way of doing this would be to add the
-- protocol constraints on the performDragOperation
-- parameter, but for whatever reason, HOC doesn't
-- do this, so we use this hack to work around it...
$(declareRenamedSelector "draggingLocation" "draggingLocation" [t| IO NSPoint |])
$(declareRenamedSelector "draggingSource" "draggingSource" [t| IO (ID ()) |])
$(declareRenamedSelector "draggingPasteboard" "draggingPasteboard" [t| IO (NSPasteboard ()) |])
instance Has_draggingPasteboard (ID t)
instance Has_draggingSource (ID t)
instance Has_draggingLocation (ID t)
_silenceWarning :: (
ImpType_draggingPasteboard a b,
ImpType_draggingSource c d,
ImpType_draggingLocation e f)
_silenceWarning = undefined
$(declareClass "YiTextView" "NSTextView")
$(exportClass "YiTextView" "ytv_" [
InstanceVariable "runBuffer" [t| BufferM () -> IO () |] [| const $ return () |]
, InstanceVariable "selectingPosition" [t| Maybe Point |] [| Nothing |]
, InstanceMethod 'setSelectedRangesAffinityStillSelecting -- '
, InstanceMethod 'acceptableDragTypes
, InstanceMethod 'performDragOperation
])
-- | Intercept mouse selection so that we can update Yi's selection
-- according to how Cocoa wants it.
ytv_setSelectedRangesAffinityStillSelecting :: NSArray () -> NSSelectionAffinity -> Bool -> YiTextView () -> IO ()
ytv_setSelectedRangesAffinityStillSelecting rs a b v = do
hrs <- fmap castObject <$> haskellList rs :: IO [NSValue ()]
r <- foldl1 unionRegion <$> fmap mkRangeRegion <$> mapM rangeValue hrs :: IO Region
p <- v #. _selectingPosition
case (b, p) of
(True, Nothing) -> do
-- Assume that the initial indication gives starting position
v # setIVar _selectingPosition (Just $ regionStart r)
(False, Just p0) -> do
v # setIVar _selectingPosition Nothing
runbuf <- v #. _runBuffer
runbuf $ do
setVisibleSelection (regionSize r /= 0)
byteIndexB p0 >>= setSelectionMarkPointB
byteIndexB (if regionStart r == p0 then regionEnd r else regionStart r) >>= moveTo
_ -> do
-- Ignore intermediate updates (Cocoa buffers events until selection finishes)
-- Ignore direct updates (to avoid having to detect "our" updates)
return ()
super v # setSelectedRangesAffinityStillSelecting rs a b
ytv_acceptableDragTypes :: YiTextView () -> IO (NSArray ())
ytv_acceptableDragTypes _ = do
ar <- castObject <$> _NSMutableArray # array :: IO (NSMutableArray ())
_NSPasteboard # typesFilterableTo nsStringPboardType >>=
flip addObjectsFromArray ar
ar # addObject nsFilenamesPboardType
return (castObject ar)
-- Implement support for drag and drop...
ytv_performDragOperation :: ID t -> YiTextView () -> IO Bool
ytv_performDragOperation dragInfo slf = do
pb <- dragInfo # draggingPasteboard
ty <- slf # ytv_acceptableDragTypes >>= flip availableTypeFromArray pb
when (ty /= nil) $ do
str <-
if ty == nsFilenamesPboardType
then unlines' <$>
(pb # propertyListForType ty >>= (haskellList.castObject) >>= mapM (haskellString.castObject))
else pb # stringForType ty >>= haskellString
src <- dragInfo # draggingSource
-- Apparently, the selection only looks as if it was updated...
-- we would have to use draggingLocation to figure out where
-- the new text should go. Which means that we need to get back
-- our old trick for translating a mouse-position to a text-position.
-- TODO: Convert ip from a character index to a buffer position (utf8)
pIns' <- dragInfo # draggingLocation >>= flip charAtMouse slf
runbuf <- slf #. _runBuffer
runbuf $ do
hasSel <- getA highlightSelectionA
rSel <- if hasSel && src == (castObject slf) then getSelectRegionB else return emptyRegion
pIns <- byteIndexB (fromIntegral pIns')
moveTo $ fromIntegral pIns
-- To not affect positions we make the "latter" modification first
-- Note that there will be no drag operation if they overlap...
if regionStart rSel < fromIntegral pIns
then insertN str >> deleteRegionB rSel
else deleteRegionB rSel >> insertN str
return (ty /= nil)
-- | Compute the character index of the specified mouse position
charAtMouse :: NSPoint -> YiTextView () -> IO CUInt
charAtMouse p slf = do
-- Determine the text-relative coordinate
container <- slf # textContainer
NSPoint ex ey <- slf # convertPointFromView p nil
NSPoint ox oy <- slf # textContainerOrigin
let mouse = NSPoint (ex - ox) (ey - oy)
-- Determine the index
layout <- slf # layoutManager
pf <- malloc -- I miss stack variables from C... =P
index <- layout # glyphIndexForPointInTextContainerFractionOfDistanceThroughGlyph mouse container pf
fract <- peek pf
free pf
return $ if (fract > 0.5) then index + 1 else index
-- | Compute the currently visible text range in the view
visibleRange :: YiTextView () -> IO NSRange
visibleRange v = do
-- Force redraw of the whole container to capture interactive style changes...
NSPoint x y <- v # textContainerOrigin
r <- v # visibleRect >>= \r -> nsOffsetRect r x y
lm <- v # layoutManager
tc <- v # textContainer
gr <- lm # glyphRangeForBoundingRectInTextContainer r tc
lm # characterRangeForGlyphRangeActualGlyphRange gr nullPtr
$(declareClass "YiScrollView" "NSScrollView")
$(exportClass "YiScrollView" "ysv_" [
InstanceVariable "leftScroller" [t| Bool |] [| False |]
, InstanceMethod 'tile -- '
])
ysv_tile :: YiScrollView () -> IO ()
ysv_tile slf = do
super slf # tile
moveScroller <- slf #. _leftScroller
if not moveScroller
then return ()
else do
-- Copied from NostalgicScrollView (found on /.)
c <- slf # AppKit.NSScrollView.contentView
s <- slf # verticalScroller
sf <- s # frame
s # setFrameOrigin (NSPoint 0.0 (nsMinY sf))
c # frame >>= (flip setFrameOrigin c) . (NSPoint (nsWidth sf)) . nsMinY