rail-compiler-editor-0.3.0.0: src/RailEditor/TextAreaContent.hs
{- |
Module : TextAreaContent.hs
Description : .
Maintainer : Kelvin Glaß, Chritoph Graebnitz, Kristin Knorr, Nicolas Lehmann, Benjamin Kodera (c)
License : MIT
Stability : experimental
This TextAreaContent-module stores two data structures that saves all entries for the editor.
The first data structur saves the text-entries, the second data structure saves the color-entries.
-}
module TextAreaContent (
-- * Detail
--
-- | 'TextAreaContent' is a model to be used in combination with 'TextArea' as view.
-- * Types
TextAreaContent,
Position,
Direction,
Coord,
RGBColor(RGBColor),
TextAreaContent.Action(Remove, Insert, RemoveLine, InsertLine, Replace, Concat, DoNothing),
ActionQueue,
RailType(RailString, RailList, RailLambda),
InterpreterContext(IC), dataStack, funcStack, breakMap, inputOffset, curIPPos, railFlags,
RailFlag(Interpret,Step,Blocked,EOFWait),
-- * Constructors
TextAreaContent.init, -- initializes both data structures
-- * Constants
black,
white,
gold,
green,
blue,
red,
defaultColor,
defaultChar,
-- * Methods
serialize,
deserialize,
putValue,
putColor,
putCell,
putDirection,
getPositionedGrid,
getCell,
getDirection,
isEmptyLine,
findLastChar, --needed for KeyHandler
deleteCell,
eqPos,
deleteColors,
TextAreaContent.size,
redoQueue,
undoQueue,
context,
buffer,
getSelectedPositons,
getPositons,
getPositonsFrom,
setClipboard,
getClipboard
) where
import qualified InterfaceDT as IDT
import Data.Map as Map
import Data.IORef
import Data.Maybe
import qualified Data.List as List
import Control.Monad
import qualified Lexer
import Graphics.UI.Gtk as Gtk
data RGBColor = RGBColor Double Double Double deriving Show
data ColorMap = CoMap (IORef (Map Position RGBColor)) (IORef (Coord,Coord))
-- chMap is a Map(y (x coord char)) where y and x are coords
data CharMap = ChMap (IORef (Map Coord (Map Coord (Char,Bool)))) (IORef (Coord,Coord))
-- types for undoredo
data Action = Remove [(Char,Bool)] | Insert [(Char,Bool)] | Replace [(Char,Bool)] [(Char,Bool)] | RemoveLine | InsertLine | Concat (TextAreaContent.Action, Position) (TextAreaContent.Action, Position) | DoNothing deriving Show
type ActionQueue = [(TextAreaContent.Action, Position)]
-- types for interpreter
data RailType = RailString String | RailList [RailType] | RailLambda String Lexer.IP (Map.Map String RailType) deriving (Eq)
data RailFlag = Interpret | Step | Blocked | EOFWait deriving (Eq)
instance Show RailType
where
show (RailString string) = string
show (RailList list) = show list
show (RailLambda string ip _) = string ++ show (Lexer.posx ip, Lexer.posy ip)
data InterpreterContext =
IC {
dataStack :: [RailType],
funcStack :: [(String, Lexer.IP, Map.Map String RailType)],
breakMap :: Map Position Bool,
inputOffset :: Int,
curIPPos :: Position,
railFlags :: [RailFlag]
}
data TextAreaContent =
TAC {
charMap :: CharMap,
colorMap :: ColorMap,
undoQueue :: IORef ActionQueue,
redoQueue :: IORef ActionQueue,
railDirection :: IORef Direction,
context :: IORef InterpreterContext,
buffer :: (Gtk.TextBuffer,Gtk.TextBuffer),
clipboard :: IORef [(Position,(Char,Bool))]
}
type Coord = Int
type Position = (Coord,Coord)
type Direction = (Coord,Coord)
-- Constants
red = RGBColor 1.0 0 0
blue = RGBColor 3.781185626001373e-2 0.21072709239337759 0.965316243228809
green = RGBColor 5.145342183566033e-2 0.9518730449378194 8.746471351186388e-2
gold = RGBColor 1.0 0.46433203631647213 0.0
black = RGBColor 0 0 0
white = RGBColor 1.0 1.0 1.0
defaultColor = red
defaultChar = ' '
--------------------
-- Constructors
-- | creates a new TextAreaContent
init :: Coord -- ^ x-size
-> Coord -- ^ y-size
-> Gtk.TextBuffer
-> Gtk.TextBuffer
-> IO TextAreaContent
init x y inputBuffer outputBuffer= do
size <- newIORef (x,y)
hmapR <- newIORef Map.empty
cmapR <- newIORef Map.empty
undoQ <- newIORef []
redoQ <- newIORef []
dir <- newIORef (1,0)
cont <- newIORef $ IC [] [] Map.empty 0 (-1, -1) []
clipb <- newIORef []
let
cMap = CoMap cmapR size
hMap = ChMap hmapR size
return $ TAC hMap cMap undoQ redoQ dir cont (inputBuffer,outputBuffer) clipb
--------------------
-- Methods
--To compare Positions
eqPos :: Position -> Position -> Bool
eqPos (x,y) (u,v) = (x==u)&&(y==v)
--fill a Map a (Map a b) with a specified content
fillCharMapWith :: Map.Map Coord (Map.Map Coord (Char,Bool)) --Map of content at position y x
-> (Char,Bool) --Content
-> Position -- size
-> Map.Map Coord (Map.Map Coord (Char,Bool))--Map with content at every y x coord (x*y operations)
fillCharMapWith charMap content (xBound,yBound) =
Prelude.foldl (\yMap yK ->
if isNothing $ Map.lookup yK yMap -- line Map nothing?
then insert --insert the filled xMap into yMap
yK
(Prelude.foldl (\xMap xK -> --foldl with empty map
if isNothing $ Map.lookup xK xMap --check for char at x,y
then Map.insert xK content xMap
else xMap) Map.empty [0..xBound])
yMap
else insert --insert the filled xMap into yMap
yK
(Prelude.foldl (\xMap xK -> --foldl with
if isNothing $ Map.lookup xK xMap --check for char at x,y
then Map.insert xK content xMap
else xMap)(fromJust $ Map.lookup yK yMap)[0..xBound])
yMap
)charMap [0..yBound]
--fills a map with a specified content
fillMapWith :: Map.Map Position a -> a -> Coord -> Coord -> Coord -> Coord -> Map.Map Position a
fillMapWith map content _ _ 0 0 = Map.insert (0,0) content map
fillMapWith map content xBound yBound 0 y = fillMapWith (Map.insert (0,y) content map) content xBound yBound xBound (pred y)
fillMapWith map content xBound yBound x y = fillMapWith (Map.insert (x,y) content map) content xBound yBound (pred x) y
-- | creates a string by a TextAreaContent
serialize :: TextAreaContent
-> IO String
serialize areaContent = do
let (ChMap hMap size) = charMap areaContent --get the CharMap pointer
hmap <- readIORef hMap --get the CharMap
(xMax,yMax) <- readIORef size --get the size of the CharMap
--Fill the map with whitespaces
let
wMap = fillCharMapWith hmap (' ',False) (xMax,yMax)
listOfLines = assocs wMap
foldM (\code (y,lineMap) -> do
let lineList = assocs lineMap
l <- foldM (\line (x,(char,_)) -> return $ line++[char]) "" lineList
let cleanLine = (reverse . dropWhile (== ' ') . reverse) l ++ "\n"
return $ code++cleanLine
) "" listOfLines
-- | creates a TextAreaContent by a string
deserialize :: String
-> Gtk.TextBuffer
-> Gtk.TextBuffer
-> IO TextAreaContent
deserialize stringContent inputB outputB = do
areaContent <- TextAreaContent.init newX newY inputB outputB
--readStringListInEntryMap areaContent lined (0,0)
foldM_ (\y line -> do
foldM_ (\x char -> do
putValue areaContent (x,y) (char,False)
return $ x+1
) 0 line
return $ y+1
) 0 lined
return areaContent
where
newX = maximum $ Prelude.map length lined
newY = length lined
lined = lines stringContent
--set the size(x,y) to (x+1,y+1)
resize :: TextAreaContent -> Position-> IO ()
resize areaContent (xm,ym) = do
let
(ChMap charMapIORef charSize) = charMap areaContent
(CoMap colorMapIORef colorSize) = colorMap areaContent
writeIORef charSize (xm,ym)
writeIORef colorSize (xm,ym)
-- | sets the character for a cell identified by a coordinate
-- If the coords are out of bounds the function resizes the TAC.
putValue :: TextAreaContent
-> Position -- ^ coordinates of the required cell
-> (Char,Bool) -- ^ character and selection state
-> IO ()
putValue areaContent (x,y) content = do
(xMax,yMax) <- TextAreaContent.size areaContent
if y > yMax || x > xMax
then do
resize areaContent (xMax + abs (xMax-x),yMax + abs (yMax-y))
putValue areaContent (x,y) content
else do
let
(ChMap hMap size) = charMap areaContent
(CoMap cMap _) = colorMap areaContent
valHMap <- readIORef hMap
let lineMap = Map.lookup y valHMap
modifyIORef' hMap $ Map.insert y $ maybe (insert x content empty) (insert x content) lineMap
-- | sets the color of a cell identified by
-- If the coords are out of bounds the function resizes the TAC.
putColor :: TextAreaContent
-> Position -- ^ coordinates of the required cell
-> RGBColor -- ^ color to put
-> IO ()
putColor areaContent (x,y) color = do
(xMax,yMax) <- TextAreaContent.size areaContent
if x > xMax || y > yMax
then do
resize areaContent (xMax + abs (xMax-x),yMax + abs (yMax-y))
putColor areaContent (x,y) color
else do
let (CoMap cMap _) = colorMap areaContent
modifyIORef' cMap $ Map.insert (x,y) color
deleteColors :: TextAreaContent -> IO ()
deleteColors tac = do
let(CoMap cMap _) = colorMap tac
writeIORef cMap Map.empty
-- / sets a cell
putCell :: TextAreaContent
-> Position -- ^ coordinates of the required cell
-> ((Char,Bool),RGBColor) -- ^ content and color to put
-> IO ()
putCell areaContent coord (content,color) = do
putColor areaContent coord color
putValue areaContent coord content
-- | setting input direction
putDirection :: TextAreaContent
-> Direction
-> IO ()
putDirection tac dir = do
let dirTac = railDirection tac
modifyIORef dirTac $ const dir
-- | getting input direction
getDirection :: TextAreaContent
-> IO Direction
getDirection tac = do
let dirTac = railDirection tac
readIORef dirTac
-- | delets a cell
deleteCell :: TextAreaContent
-> Position
-> IO Bool
deleteCell areaContent (x,y) = do
let
(ChMap hMap _) = charMap areaContent
(CoMap cMap _) = colorMap areaContent
(xMax,yMax) <- TextAreaContent.size areaContent
if x > xMax || y > yMax
then return False
else do
modifyIORef hMap (\m -> del m (x,y))
modifyIORef cMap (Map.delete (x,y))
return True
where
delMap :: Map.Map Coord (Map.Map Coord (Char,Bool)) -> Position -> Map.Map Coord (Char,Bool)
delMap m (x,y) = maybe Map.empty (Map.delete x) (Map.lookup y m)
del :: Map.Map Coord (Map.Map Coord (Char,Bool)) -> Position -> Map.Map Coord (Map.Map Coord (Char,Bool))
del m (x,y)
| delMap m (x,y) == Map.empty = Map.delete y m
| otherwise = Map.insert y (delMap m (x,y)) m
-- | returns the character and the color of a cell
getCell :: TextAreaContent
-> Position -- ^ coordinates of the required cell
-> IO (Maybe ((Char,Bool),RGBColor))
getCell areaContent (x,y) = do
let (ChMap hMap _) = charMap areaContent
let (CoMap cMap _) = colorMap areaContent
hmap <- readIORef hMap
cmap <- readIORef cMap
let valMap = Map.lookup y hmap
let color = Map.findWithDefault defaultColor (x,y) cmap --now lazy
return $ if isNothing valMap then Nothing else do
let mayValue = Map.lookup x $ fromJust valMap
if isNothing mayValue then Nothing else Just (fromJust mayValue, color)
-- / checks if line is empty
isEmptyLine :: TextAreaContent
-> Coord
-> IO Bool
isEmptyLine areaContent line = do
let (ChMap hMap _) = charMap areaContent
hmap <- readIORef hMap
return $ isNothing $ Map.lookup line hmap
{-This function is important for highlighting it returns the datatype
which Lexer needs to lex. -}
getPositionedGrid :: TextAreaContent -> IO IDT.PreProc2Lexer
getPositionedGrid areaContent = do
let (ChMap hMap hSize) = charMap areaContent
s@(xMax,yMax) <- readIORef hSize
hmap <- readIORef hMap
pGrid <- buildPosGrid ([],0) (assocs hmap)
return $ IDT.IPL (Prelude.map (maximize (max xMax yMax)) $ fst pGrid)
where
maximize :: Int -> IDT.PositionedGrid -> IDT.PositionedGrid
maximize msize (grid, offset) = (Map.update updatefirst 0 grid, offset)
where
updatefirst line = Just $ Map.union line emptymap
emptymap = fromList $ zip [0..msize] (repeat ' ')
buildPosGrid :: ([IDT.PositionedGrid],Int) -> [(Int,Map.Map Int (Char,Bool))] -> IO ([IDT.PositionedGrid],Int)
buildPosGrid = foldM
(\(grids,offset) (y,line) -> do
let mayDollar = Map.lookup 0 line
simplifiedLine = Map.map fst line
return $
if isNothing mayDollar
then (insertWhenFct grids simplifiedLine y offset,offset)
else
if fst (fromJust mayDollar) == '$'
then (grids ++ [(Map.insert 0 simplifiedLine Map.empty, y)], y)
else (insertWhenFct grids simplifiedLine y offset, offset)
)
insertWhenFct :: [IDT.PositionedGrid] -> Map.Map Int Char -> Int -> Int -> [IDT.PositionedGrid]
insertWhenFct x line y offset
| List.null x || line == Map.empty = x
| otherwise = List.init x ++[(Map.insert (y-offset) line (fst (last x)), snd (last x))]
findLastChar :: TextAreaContent -> Coord -> IO Coord
findLastChar tac y = do
let (ChMap hMap _) = charMap tac
hmap <- readIORef hMap
let mayLine = Map.lookup y hmap
return $ if isNothing mayLine then -1 else fst $ List.last $ assocs $ fromJust mayLine
-- | returns the size of a TextAreaContent.
size :: TextAreaContent
-> IO Position -- ^ size of the TextAreaContent
size areaContent = do
let (ChMap _ size) = charMap areaContent
readIORef size
getSelectedPositons :: TextAreaContent -> IO [Position]
getSelectedPositons areaContent = do
(xMax,yMax) <- TextAreaContent.size areaContent
filterM (isSelected areaContent) [ (x,y) | y <- [0..yMax], x <- [0..xMax]]
isSelected :: TextAreaContent -> Position -> IO Bool
isSelected tac pos = do
cell <- getCell tac pos
return $ isJust cell && snd (fst $ fromJust cell)
getPositons :: TextAreaContent -> IO [Position]
getPositons areaContent = do
(xMax,yMax) <- TextAreaContent.size areaContent
filterM (isOccupied areaContent) [ (x,y) | y <- [0..yMax], x <- [0..xMax]]
getPositonsFrom :: TextAreaContent -> Position -> IO [Position]
getPositonsFrom areaContent (_,y) = do
(xMax,yMax) <- TextAreaContent.size areaContent
filterM (isOccupied areaContent) [ (x1,y1) | y1 <- [0..yMax], x1 <- [0..xMax], y1 > y]
-- Is a char at pos ?
isOccupied :: TextAreaContent -> Position -> IO Bool
isOccupied tac pos = do
cell <- getCell tac pos
return $ isJust cell
setClipboard :: TextAreaContent -> IO ()
setClipboard tac = do
positions <- getSelectedPositons tac
cells <- getCellsByPositions tac positions
writeIORef (clipboard tac) (List.zip positions cells)
getClipboard :: TextAreaContent -> IO [(Position,(Char,Bool))]
getClipboard tac = readIORef (clipboard tac)
getCellsByPositions :: TextAreaContent -> [Position] -> IO [(Char,Bool)]
getCellsByPositions _ [] = return []
getCellsByPositions tac positions = _getCellsByPositions tac positions []
_getCellsByPositions :: TextAreaContent -> [Position] -> [(Char,Bool)] -> IO [(Char,Bool)]
_getCellsByPositions _ [] cells = return cells
_getCellsByPositions tac (x:xs) cells = do
cell <- getCell tac x
_getCellsByPositions tac xs $
if isJust cell
then cells ++ [fst $ fromJust cell]
else cells