packages feed

rail-compiler-editor-0.3.0.0: src/RailEditor/RedoUndo.hs

{- |
Module      :  RedoUndo.hs
Description :  .
Maintainer  :  Christian H. et al.
License     :  MIT

Stability   :  experimental

The RedoUndo-module contains three functions that allow to redo- and undo-actions in the editor.
-}
module RedoUndo (
                 action,-- something was done in the editor
                 undo,  -- allows to undo actions in the editor
                 redo   -- allows to redo actions in the editor
                )
  where
    
    -- imports --
    import Data.IORef
    import Control.Monad
    import qualified TextAreaContent as TAC
    import qualified TextAreaContentUtils as TACU
    
    data UndoRedo = Undo | Redo deriving Eq

    -- functions --
    invert :: (TAC.Action, TAC.Position) -> (TAC.Action, TAC.Position)
    invert a@(TAC.DoNothing, pos) = a
    invert (TAC.Concat act1 act2, pos) = (TAC.Concat (invert act1) (invert act2), pos)
    invert (TAC.Remove content, pos) = (TAC.Insert content, pos)
    invert (TAC.Insert content, pos) = (TAC.Remove content, pos)
    invert (TAC.Replace a b, pos) = (TAC.Replace b a, pos)
    invert (TAC.RemoveLine, (x, y)) = (TAC.InsertLine, (x, y-1))
    invert (TAC.InsertLine, (x, y)) = (TAC.RemoveLine, (x, y+1))

    -- add a given function to our queues
    action :: TAC.TextAreaContent -> TAC.Position -> TAC.Action -> IO ()
    action tac position useraction = do
      redoqueue <- readIORef (TAC.redoQueue tac)
      unless (null redoqueue) $ writeIORef (TAC.redoQueue tac) []
      undoqueue <- readIORef (TAC.undoQueue tac)
      writeIORef (TAC.undoQueue tac) (invert (useraction, position):undoqueue)

    -- gets the action to to and move it from one queue to the opposite one
    shiftaction :: (TAC.ActionQueue, TAC.ActionQueue) -> (TAC.ActionQueue, TAC.ActionQueue, (TAC.Action, TAC.Position))
    shiftaction (from, to) = (tail from, invert (head from):to, head from)

    -- run whatever action given
    runaction :: TAC.TextAreaContent -> (TAC.Action, TAC.Position) -> IO TAC.Position
    runaction _ (TAC.DoNothing, actpos) = return actpos
    runaction tac (TAC.Concat act1 act2, actpos) =
      runaction tac act1 >> runaction tac act2
    runaction tac (TAC.Remove [], actpos) = return actpos
    runaction tac (TAC.Remove (x:xs), actpos) = do
      TAC.deleteCell tac actpos
      TACU.moveChars tac actpos (-1,0)
      runaction tac (TAC.Remove xs, actpos)
    runaction tac (TAC.Insert [], actpos) = return actpos
    runaction tac (TAC.Insert (content@(char,_):xs), actpos) = do
      if char == '\n'
      then TACU.moveLinesDownXShift tac actpos True
      else do
        TACU.moveChars tac actpos (1,0)
        TAC.putCell tac actpos (content, TAC.defaultColor)
      runaction tac (TAC.Insert xs, actpos)
    runaction tac (TAC.Replace a [], actpos) = return actpos
    runaction tac (TAC.Replace a (content@(char,_):xs), actpos@(px, py)) =
      if char == ' '
      then runaction tac (TAC.Remove [content], actpos)
      else do
        TAC.putCell tac actpos (content, TAC.defaultColor)
        runaction tac (TAC.Replace a xs, (px+1, py))
    runaction tac (TAC.RemoveLine, (x, y)) = do
      TACU.moveLinesUp tac y
      return (x, y-1)
    runaction tac (TAC.InsertLine, (x, y)) = do
      TACU.moveLinesDownXShift tac (x, y) True
      return (0, y+1)

    -- allows to undo actions in the editor
    undo :: TAC.TextAreaContent -> TAC.Position -> IO TAC.Position
    undo tac pos = do
      undoqueue <- readIORef (TAC.undoQueue tac)
      if not (null undoqueue)
      then runRedoUndo tac Undo
      else return pos

    -- allows to redo actions in the editor
    redo :: TAC.TextAreaContent -> TAC.Position -> IO TAC.Position
    redo tac pos = do
      redoqueue <- readIORef (TAC.redoQueue tac)
      if not (null redoqueue)
      then runRedoUndo tac Redo
      else return pos

    runRedoUndo :: TAC.TextAreaContent -> UndoRedo -> IO TAC.Position
    runRedoUndo tac cmd = do
      redoqueue <- readIORef (TAC.redoQueue tac)
      undoqueue <- readIORef (TAC.undoQueue tac)
      if cmd == Undo then do
        let (newundo, newredo, action) = shiftaction (undoqueue, redoqueue)
        exec tac newundo newredo action
      else do
        let (newredo, newundo, action) = shiftaction (redoqueue, undoqueue) 
        exec tac newundo newredo action
     
    exec :: TAC.TextAreaContent -> TAC.ActionQueue -> TAC.ActionQueue -> (TAC.Action,TAC.Position) -> IO TAC.Position
    exec tac u r action = do
      writeIORef (TAC.redoQueue tac) r
      writeIORef (TAC.undoQueue tac) u
      runaction tac action