packages feed

vision-0.0.3.0: src/DnD.hs

-- -*-haskell-*-
--  Vision (for the Voice): an XMMS2 client.
--
--  Author:  Oleg Belozeorov
--  Created: 10 Sep. 2010
--
--  Copyright (C) 2009-2010 Oleg Belozeorov
--
--  This program is free software; you can redistribute it and/or
--  modify it under the terms of the GNU General Public License as
--  published by the Free Software Foundation; either version 3 of
--  the License, or (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
--  General Public License for more details.
--

{-# LANGUAGE TypeSynonymInstances, ExistentialQuantification #-}
{-# OPTIONS_GHC -fno-warn-incomplete-patterns #-}

module DnD
  ( TargetClass (..)
  , CommonTargets (..)
  , Targets (..)
  , DragDest (..)
  , setupDragDest
  , getTargetRow
  , reorderRows
  , reorder
  , selectionDataGetStringList
  , selectionDataSetStringList
  ) where

import Control.Applicative
import Control.Monad
import Control.Monad.Trans

import Data.List
import Data.Char
import Data.IORef
import qualified Data.IntMap as IntMap

import Graphics.UI.Gtk

import Utils


class TargetClass t where
  addToTargetList :: TargetList -> InfoId -> t -> IO ()


instance TargetClass TargetTag where
  addToTargetList tl id tg = targetListAdd tl tg [] id


data CommonTargets
  = URITargets

instance TargetClass CommonTargets where
  addToTargetList tl id tg =
    case tg of
      URITargets -> targetListAddUriTargets tl id


data Targets
  = forall t1 t2. (TargetClass t1, TargetClass t2) => t1 :|: t2

infixr 5 :|:

instance TargetClass Targets where
  addToTargetList tl id (t1 :|: t2) = do
    addToTargetList tl id t1
    addToTargetList tl id t2


data DragDest
  = forall t. TargetClass t => t :>: (DragContext -> Point -> SelectionDataM (Bool, Bool))

infix 4 :>:


setupDragDest widget defs acts dests = do
  tl <- targetListNew
  hm <- IntMap.fromList <$> zipWithM (mk tl) [0 .. ] dests

  dragDestSet widget defs acts
  dragDestSetTargetList widget tl

  dropRef <- newIORef False

  widget `on` dragDrop $ \ctxt _ tstamp -> do
    maybeTarget <- dragDestFindTarget widget ctxt (Just tl)
    case maybeTarget of
      Just target -> do
        writeIORef dropRef True
        dragGetData widget ctxt target tstamp
        return True
      Nothing ->
        return False

  widget `on` dragDataReceived $ \ctxt pos infoId tstamp -> do
    drop <- liftIO $ readIORef dropRef
    when drop $ do
      liftIO $ writeIORef dropRef False
      (ok, del) <- case IntMap.lookup (fromIntegral infoId) hm of
        Just handler -> handler ctxt pos
        Nothing      -> return (False, False)
      liftIO $ dragFinish ctxt ok del tstamp

  return ()

  where mk tl id (ts :>: handler) = do
          addToTargetList tl id ts
          return (fromIntegral id, handler)


getTargetRow store view y reorder = do
  maybePos <- treeViewGetPathAtPos view (0, y)
  case maybePos of
    Just ([n], _, _) | reorder ->
      return n
    Just ([n], column, _) -> do
      Rectangle _ cy _ ch <- treeViewGetCellArea view (Just [n]) column
      return $ if y - cy > 2 * ch `div` 3 then n + 1 else n
    Nothing | reorder ->
      pred <$> listStoreGetSize store
    Nothing ->
      listStoreGetSize store

reorderRows store view f _ (_, y) = do
  rows <- selectionDataGet selectionTypeInteger
  liftIO $ withJust rows $ \rows -> do
    base <- getTargetRow store view y True
    f $ reorder base rows
  return (True, False)

reorder = reorderDown 0
  where reorderDown _ _ [] = []
        reorderDown dec base rows@(r:rs)
          | r <= base = (r - dec, base) : reorderDown (dec + 1) base rs
          | otherwise = reorderUp (if dec /= 0 then base + 1 else base) rows
        reorderUp _ [] = []
        reorderUp base (r:rs)
          | r == base = reorderUp (base + 1) rs
          | otherwise = (r, base) : reorderUp (base + 1) rs


selectionDataSetStringList =
  selectionDataSet selectionTypeInteger . intercalate [0] . map (map ord)

selectionDataGetStringList =
  maybe [] brk <$> selectionDataGet selectionTypeInteger
  where brk text = case break (== 0) text of
          (name, [])       -> [map chr name]
          (name, _ : rest) -> map chr name : brk rest