packages feed

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

{- |
Module      :  Selection.hs
Description :  .
Maintainer  :  Benjamin Kodera (c)
License     :  MIT

Stability   :  stable

The Selection-module handles multiple character selection as well as copy and paste functionality.
-}

module Selection (
                    handleSelection,
                    updateCells,
                    relocateCells,
                    clear,
                    getFirstPositions,
                    getBottomRight,
                    getMinimum,
                    pasteReplace,
                    pasteInsert,
                    getCellsByPositons,
                    deselect
                 )
  where

import qualified Data.Map as Map
import Data.IORef
import Control.Monad
import Data.Maybe
import qualified TextAreaContent as TAC
import qualified TextAreaContentUtils as TACU
import qualified Data.List as List
import qualified RedoUndo as History

handleSelection :: TAC.TextAreaContent -> TAC.Position -> TAC.Position -> IO (Bool,[TAC.Position])
handleSelection tac currentPos newPos = do
  positions <- TAC.getPositons tac
  let selectedEntries = getSelectedEntries positions currentPos newPos
  if not (null selectedEntries) then do
    cell <- TAC.getCell tac $ head selectedEntries
    let isAlreadySelected = snd $ fst $ fromJust cell
    updateCells tac selectedEntries $ not isAlreadySelected 
    return (isAlreadySelected,selectedEntries)
  else return (False,[])
  
updateCells :: TAC.TextAreaContent -> [TAC.Position] -> Bool -> IO TAC.Position
updateCells tac positions value = do 
  _updateCells tac positions value
  return $ getBottomRight positions
  
_updateCells :: TAC.TextAreaContent -> [TAC.Position] -> Bool -> IO ()
_updateCells _ [] _ = return ()
_updateCells tac (pos:xs) value = do
  updateCell tac pos value
  _updateCells tac xs value  
  
updateCell :: TAC.TextAreaContent -> TAC.Position -> Bool -> IO ()  
updateCell tac pos value = do
  cell <- TAC.getCell tac pos
  when (isJust cell) $ do
    let ((char,_),color) = fromJust cell
    TAC.deleteCell tac pos
    TAC.putCell tac pos ((char,value),color)

relocateCells :: TAC.TextAreaContent -> [(TAC.Position,(Char,Bool))] -> TAC.Position -> IO TAC.Position
relocateCells tac [] pos = return pos
relocateCells tac content (x,y) = do
  let (positions,cells) = List.unzip content
      (x1,y1) = getMinimum positions
      (x2,y2) = getBottomRight positions
  _relocateCells tac positions (List.map fst cells) (x,y) (x1,y1)
  return (x+x2-x1,y+y2-y1)

_relocateCells :: TAC.TextAreaContent -> [TAC.Position] -> String -> TAC.Position -> TAC.Position -> IO ()
_relocateCells _ [] _ _ _ = return ()
_relocateCells tac (position:positions) (char:chars) newPos offset = do
  relocateCell tac position char newPos offset
  _relocateCells tac positions chars newPos offset
  
relocateCell :: TAC.TextAreaContent -> TAC.Position -> Char -> TAC.Position -> TAC.Position -> IO ()  
relocateCell tac pos@(x,y) char (newX,newY) (offsetX,offsetY) =
  TAC.putCell tac (newX+x-offsetX,newY+y-offsetY) ((char,False),TAC.defaultColor)

-- | clears a selection and removes all selected characters
clear :: TAC.TextAreaContent -> TAC.Position -> IO (TAC.Position,TAC.Position)
clear tac pos@(x,y) = do
  positions <- TAC.getSelectedPositons tac
  if not (null positions) then do
    cells <- Selection.getCellsByPositons tac positions
    History.action tac (getMinimum positions) (TAC.Remove cells)
    clearCells tac positions
    return (getMinimum positions,getMaximum positions)
  else return ((x-1,y),(x-1,y))

clearCells :: TAC.TextAreaContent -> [TAC.Position] -> IO ()
clearCells tac [] = return ()
clearCells tac (x:xs) = do
  TAC.deleteCell tac x
  clearCells tac xs

getCellsByPositons :: TAC.TextAreaContent -> [TAC.Position] -> IO [(Char,Bool)]
getCellsByPositons _ [] = return []
getCellsByPositons tac positions = _getCellsByPositons tac positions []

_getCellsByPositons :: TAC.TextAreaContent -> [TAC.Position] -> [(Char,Bool)] -> IO [(Char,Bool)]
_getCellsByPositons _ [] cells = return cells
_getCellsByPositons tac (x:xs) cells = do
  cell <- TAC.getCell tac x 
  if isJust cell then do 
    let (content,_) = fromJust cell
    _getCellsByPositons tac xs (content:cells)
  else _getCellsByPositons tac xs cells

getSelectedEntries :: [TAC.Position] -> TAC.Position -> TAC.Position -> [TAC.Position]
getSelectedEntries positions (x1,y1) (x2,y2)
  -- down
  | y2 > y1 = List.filter (\(x,y) -> x >= x1 && y == y1 || y > y1 && y < y2 || x < x2 && y == y2) positions
  -- right
  | x2 > x1 && y2 == y1 = List.filter (\(x,y) -> x >= x1 && x < x2 && y == y2) positions 
  -- left
  | x2 < x1 && y2 == y1 = List.filter (\(x,y) -> x < x1 && x >= x2 && y == y2) positions 
  -- up
  | otherwise = List.filter (\(x,y) -> x < x1 && y == y1 || y < y1 && y > y2 || x >= x2 && y == y2) positions

getMaximumY :: [TAC.Position] -> TAC.Coord
getMaximumY = Prelude.foldl (\y1 (_,y2) -> max y1 y2) 0

getMaximumX :: [TAC.Position] -> TAC.Coord
getMaximumX = Prelude.foldl (\x1 (x2,_) -> max x1 x2) 0

getMinimumY :: [TAC.Position] -> TAC.Coord
getMinimumY [] = 0
getMinimumY positions = fst $ minimum $ Prelude.map (\(x,y) -> (y,x)) positions

getMinimumX :: [TAC.Position] -> TAC.Coord
getMinimumX [] = 0
getMinimumX positions = fst $ minimum positions

getTopLeft :: [TAC.Position] -> TAC.Position
getTopLeft [] = (0,0)
getTopLeft positions = (getMinimumX positions, getMinimumY positions)

getBottomRight :: [TAC.Position] -> TAC.Position
getBottomRight [] = (0,0)
getBottomRight positions = (x+1,y)
  where (y,x) = maximum $ Prelude.map (\(x1,y1) -> (y1,x1)) positions
  
getMinimum :: [TAC.Position] -> TAC.Position
getMinimum [] = (0,0)
getMinimum positions = (x,y) 
  where (y,x) = minimum $ Prelude.map (\(x1,y1) -> (y1,x1)) positions

getMaximum :: [TAC.Position] -> TAC.Position
getMaximum [] = (0,0)
getMaximum positions = (x,y) 
  where (y,x) = maximum $ Prelude.map (\(x1,y1) -> (y1,x1)) positions

getFirstPositions :: [TAC.Position] -> [TAC.Position]
getFirstPositions [] = []
getFirstPositions positions = List.map minimum $ List.groupBy (\(x1,y1) (x2,y2) -> y1 == y2) positions

paste :: TAC.TextAreaContent -> TAC.Position -> Bool -> IO TAC.Position
paste tac pos replace = do
  clipboard <- TAC.getClipboard tac
  let (clipboardPositions,cells) = List.unzip clipboard
  when replace $ shiftSubsequentLines tac pos clipboardPositions
  History.action tac pos (TAC.Insert cells)
  selectedPositions <- TAC.getSelectedPositons tac
  newPos <- relocateCells tac clipboard $
    if not (null selectedPositions)
    then getMinimum selectedPositions
    else pos
  clear tac newPos
  return newPos

-- | pastes content from clipboard to position in override mode
pasteReplace :: TAC.TextAreaContent -> TAC.Position -> IO TAC.Position
pasteReplace tac pos = paste tac pos True

-- | pastes content from clipboard to position in insert mode
pasteInsert :: TAC.TextAreaContent -> TAC.Position -> IO TAC.Position
pasteInsert tac pos = paste tac pos False

shiftSubsequentLines :: TAC.TextAreaContent -> TAC.Position -> [TAC.Position] -> IO ()
shiftSubsequentLines tac pos clipboardPositions = do
  let (x1,y1) = getMinimum clipboardPositions
      (x2,y2) = getMaximum clipboardPositions
      (xShift,yShift) = (abs (x1-x2), abs (y1-y2))
  positionsToShiftDown <- TAC.getPositonsFrom tac pos
  shiftDownLines tac positionsToShiftDown yShift    
  TACU.moveChars tac pos (xShift+1,yShift)    

shiftDownLines :: TAC.TextAreaContent -> [TAC.Position] -> TAC.Coord -> IO ()
shiftDownLines tac positionsToShiftDown yShift = do
  let firsts = getFirstPositions positionsToShiftDown
  _shiftDownLines tac firsts yShift
  
_shiftDownLines tac [] _ = return ()
_shiftDownLines _ _ 0 = return ()
_shiftDownLines tac (x:xs) shift = do
  _shiftDownLines tac xs shift
  TACU.moveChars tac x (0,shift)

deselect :: TAC.TextAreaContent -> IO TAC.Position
deselect tac = do
  selectedPositions <- TAC.getSelectedPositons tac
  updateCells tac selectedPositions False