packages feed

ige-0.1.0.0: src/IGE/Control.hs

module IGE.Control 
  ( runKeyBinding
  , basicKeyBinding
  , textKeyBinding
  )
  where

import Prelude (String)
import Protolude hiding (empty, readFile, writeFile)
import qualified Data.Map.Strict as Map
import Lens.Micro.Platform
import Conduit
import Data.Conduit.TMChan
import Graphics.UI.Gtk hiding (get)
import Data.Graph.Inductive.Graph
import Data.Graph.Inductive.PatriciaTree
import Control.Concurrent.STM
import qualified Data.Sequence as Seq
import Data.Sequence ((<|), (|>), viewl, ViewL(..), Seq(..))
import Control.Monad.Trans.Maybe
import Data.Text (pack)
import Data.Aeson (ToJSON, FromJSON)
import Data.ByteString.Lazy (readFile, writeFile)
import System.Directory (doesFileExist)

import IGE.Types
import IGE.Keys
import IGE.Layout
import IGE.Serialization

navigationKeyMap :: Map.Map KeyVal (EditorM n e ())
navigationKeyMap = Map.fromList [
    (xK_plus, _rm . _at *= ((10/9) :+ 0))
  , (xK_minus, _rm . _at *= ((9/10) :+ 0))
  , (xK_h, transRel (0.5 :+ 0))
  , (xK_j, transRel (0 :+ (-0.5)))
  , (xK_k, transRel (0 :+ 0.5))
  , (xK_l, transRel ((-0.5) :+ 0))
  , (xK_greater, _rm . _at *= cis (pi / 6))
  , (xK_less, _rm . _at *= cis ((- pi) / 6))
  ]

transRel :: ℂ -> EditorM n e ()
transRel z = do
  r <- use $ _rm . _r
  _rm . _trans += (r :+ 0) * z


awaitOrFinish :: (Monad m) => a -> (i -> ConduitM i o m a) -> ConduitM i o m a
awaitOrFinish x action = await >>= maybe (return x) action

updateEditor :: EditorM n e a -> KeyBinding n e a
updateEditor em = do
  x <- runTVarState em
  yield NoLayoutChange
  return x

updateEditorLayout :: EditorM n e a -> KeyBinding n e a
updateEditorLayout em = do
  x <- runTVarState em
  yield LayoutChange
  return x

tailButN :: Int -> [a] -> [a]
tailButN n xs = if length theTail < n then xs else theTail
  where theTail = tailDef [] xs

getString :: String -> KeyBinding n e (Maybe String)
getString init' = do
  updateEditor $ _cmd .= init
  loop
  where
    init = reverse init'
    n = length init
    handleInput kv
      | kv == xK_BackSpace = updateEditor (_cmd %= tailButN n) >> loop 
      | kv == xK_Return = do
        s <- runTVarReader $ view _cmd
        updateEditor (_cmd .= "") >> return (Just $ drop n $ reverse s)
      | kv == xK_Escape = updateEditor (_cmd .= "") >> return Nothing
      | otherwise = loop

    loop = awaitOrFinish Nothing $ \kv -> 
      case keyToChar kv of
        (Just c) -> updateEditor (_cmd %= (c:)) >> loop
        Nothing ->
          handleInput kv

labelChars = "asdfghjkl"

makeLabels :: Int -> String -> [String]
makeLabels n alphabet = helper n alphabet "" Seq.empty
  where
    helper :: Int -> String -> String -> Seq String -> [String]
    helper 0 _ _ queue = reverse <$> toList queue
    helper n (c:cs) postfix queue =
      helper (n - 1) cs postfix (queue |> (c:postfix))
    helper n [] _ queue =
      case viewl queue of
        a :< rest -> helper (n + 1) alphabet a rest
        EmptyL -> []

labelGraph :: Gr n e -> [(String, Node)]
labelGraph gr = zip labels (nodes gr)
  where
    labels = makeLabels (length $ nodes gr) labelChars

getNode :: KeyBinding n e (Maybe Node)
getNode = initLabels >> loop
  where
    initLabels = updateEditor $ do
      gr <- use _graph
      _labels .= labelGraph gr
    reset = updateEditor $ _labels .= []
    loop = awaitOrFinish Nothing $ \kv ->
      case keyToChar kv of
        (Just c) -> do
          labels <- runTVarReader $ view _labels
          let labels' = [(xs, node) | (x:xs, node) <- labels, x == c]
          case labels' of
            [] -> reset >> return Nothing
            [(_, node)] -> reset >> return (Just node)
            xs -> do
              updateEditor $ _labels .= labels'
              loop
        Nothing -> reset >> return Nothing

addNode :: KeyBinding () e ()
addNode =
  updateEditorLayout $ do 
    n <- use _num
    _graph %= insNode (n, ())
    _num += 1

addNodeText :: KeyBinding Text e ()
addNodeText = do
  mlabel <- getString "label: "
  case mlabel of
    Just label -> updateEditorLayout $ do
      n <- use _num
      _graph %= insNode (n, pack label)
      _num += 1
    Nothing -> return ()
      
linkNodes :: KeyBinding n () ()
linkNodes = do
  mnodes <- runMaybeT $ do
    lift $ updateEditor $ _cmd .= reverse "SELECT NODE 1"
    n1 <- MaybeT getNode
    lift $ updateEditor $ _cmd .= reverse "SELECT NODE 2"
    n2 <- MaybeT getNode
    return (n1, n2)
  updateEditor $ _cmd .= ""
  case mnodes of
    Just (n1, n2) ->
      updateEditorLayout $ _graph %= insEdge (n1, n2, ())
    Nothing ->
      return ()

linkNodesText :: KeyBinding n Text ()
linkNodesText = do
  mnodes <- runMaybeT $ do
    lift $ updateEditor $ _cmd .= reverse "SELECT NODE 1"
    n1 <- MaybeT getNode
    lift $ updateEditor $ _cmd .= reverse "SELECT NODE 2"
    n2 <- MaybeT getNode
    label <- MaybeT $ getString "label: "
    return (n1, n2, label)
  case mnodes of
    Just (n1, n2, label) ->
      updateEditorLayout $ _graph %= insEdge (n1, n2, pack label)
    Nothing ->
      return ()

deleteNode :: KeyBinding n e ()
deleteNode = do
  mnode <- getNode
  case mnode of
    Just n -> updateEditor $ _graph %= delNode n
    Nothing -> return ()

readGraph :: (FromJSON n, FromJSON e) => KeyBinding n e ()
readGraph = do
  mnewgraph <- runMaybeT $ do
    filename <- MaybeT $ getString "filename: "
    liftIO (doesFileExist filename) >>= guard
    MaybeT $ graphFromBS <$> liftIO (readFile filename)
  case mnewgraph of
    Just newgraph ->
      updateEditorLayout $ _graph .= newgraph
    Nothing -> return ()
  
writeGraph :: (ToJSON n, ToJSON e) => KeyBinding n e ()
writeGraph = do
  mfilename <- getString "filename: "
  case mfilename of
    Just fn -> do
      g <- runTVarReader $ view _graph
      liftIO $ writeFile fn $ graphToBS g
    Nothing -> return ()

textKeyBinding :: KeyBinding Text Text ()
textKeyBinding = loop
  where
    loop = awaitOrFinish () $ \kv ->
      case Map.lookup kv navigationKeyMap of
        (Just em) -> do
          updateEditor em
          loop
        Nothing -> handleSpecial kv
    handleSpecial kv
      | kv == xK_colon = do
        cmd <- getString ":"
        print cmd
        loop
      | kv == xK_a = addNodeText >> loop
      | kv == xK_c = linkNodesText >> loop
      | kv == xK_d = deleteNode >> loop
      | kv == xK_w = writeGraph >> loop
      | kv == xK_o = readGraph >> loop
      | kv == xK_q = liftIO mainQuit >> return ()
      | otherwise = loop

basicKeyBinding :: KeyBinding () () ()
basicKeyBinding = loop
  where
    loop = awaitOrFinish () $ \kv ->
      case Map.lookup kv navigationKeyMap of
        (Just em) -> do
          updateEditor em
          loop
        Nothing -> handleSpecial kv
    handleSpecial kv
      | kv == xK_colon = do
        cmd <- getString ":"
        print cmd
        loop
      | kv == xK_a = addNode >> loop
      | kv == xK_c = linkNodes >> loop
      | kv == xK_d = deleteNode >> loop
      | kv == xK_q = liftIO mainQuit >> return ()
      | otherwise = loop

refresh :: (WidgetClass w) => w -> Consumer RefreshType (IGEM n e) ()
refresh widget = do
  rt <- await
  if rt == Just LayoutChange
    then runTVarState $ do
      graph <- use _graph
      _nodeMap .= layoutGr graph
    else return ()
  liftIO $ widgetQueueDraw widget
  refresh widget

runKeyBinding :: (WidgetClass w, RenderNode n, RenderEdge e) => TBMChan KeyVal -> TVar (EditorState n e) -> w -> KeyBinding n e () -> IO ()
runKeyBinding keyChan editorState w kb =
  runIGEM editorState $ runConduit $
      sourceTBMChan keyChan
   .| kb
   .| refresh w