{-# LANGUAGE OverloadedStrings #-} -- | Displays a single on-screen row. A row may contain multiple -- screen lines and multiple columns. -- -- This module only deals with a single row at a time. Each cell in -- the row can have more than one screen line; this module will make -- sure that the cells have appropriate padding on the bottom so that -- the row appears nicely. This module will also justify each cell so -- that its left side or right side is ragged; however, you first have -- to specify how wide you want the cell to be. -- -- This module is a little dumber than you might first think it could -- be. For instance it would be possible to write a function that -- takes a number of rows and automatically justifies all the cells by -- finding the widest cell in a column. Indeed I might eventually -- write such a function because it might be useful in, for example, -- the multi-commodity balance report. However, such a function would -- not be useful in all cases; in particular, the Posts report is very -- complicated to lay out, and the automatic function described above -- would not do the right thing. -- -- So this module offers some useful automation, even if it is at a -- level that is apparently lower that what is possible. Thus the -- present 'row' function likely will not change, even if eventually I -- add a 'table' function that automatically justifies many rows. module Penny.Cabin.Row ( Justification(LeftJustify, RightJustify), ColumnSpec(ColumnSpec, justification, width, padSpec, bits), Width(Width, unWidth), row ) where import Data.List (transpose) import Data.Monoid (mempty) import qualified Data.Text as X import qualified Penny.Cabin.Scheme as E import qualified System.Console.Rainbow as R -- | How to justify cells. LeftJustify leaves the right side -- ragged. RightJustify leaves the left side ragged. data Justification = LeftJustify | RightJustify deriving Show -- | A cell of text output. You tell the cell how to justify itself -- and how wide it is. You also tell it the background colors to -- use. The cell will be appropriately justified (that is, text -- aligned between left and right margins) and padded (with lines of -- blank text added on the bottom as needed) when joined with other -- cells into a Row. data ColumnSpec = ColumnSpec { justification :: Justification , width :: Width , padSpec :: (E.Label, E.EvenOdd) , bits :: [R.Chunk] } newtype JustifiedCell = JustifiedCell (R.Chunk, R.Chunk) data JustifiedColumn = JustifiedColumn { justifiedCells :: [JustifiedCell] , _justifiedWidth :: Width , _justifiedPadSpec :: (E.Label, E.EvenOdd) } newtype PaddedColumns = PaddedColumns [[JustifiedCell]] newtype CellsByRow = CellsByRow [[JustifiedCell]] newtype CellRowsWithNewlines = CellRowsWithNewlines [[JustifiedCell]] newtype Width = Width { unWidth :: Int } deriving (Eq, Ord, Show) justify :: Width -> Justification -> E.Label -> E.EvenOdd -> E.Changers -> R.Chunk -> JustifiedCell justify (Width w) j l eo chgrs pc = JustifiedCell (left, right) where origWidth = X.length . R.text $ pc pad = E.getEvenOddLabelValue l eo chgrs . R.Chunk mempty $ t t = X.replicate (max 0 (w - origWidth)) (X.singleton ' ') (left, right) = case j of LeftJustify -> (pc, pad) RightJustify -> (pad, pc) newtype Height = Height Int deriving (Show, Eq, Ord) height :: [[a]] -> Height height xs = case xs of [] -> Height 0 ls -> Height . maximum . map length $ ls row :: E.Changers -> [ColumnSpec] -> [R.Chunk] row chgrs = concat . concat . toBits . toCellRowsWithNewlines . toCellsByRow . bottomPad chgrs . map (justifiedColumn chgrs) justifiedColumn :: E.Changers -> ColumnSpec -> JustifiedColumn justifiedColumn chgrs (ColumnSpec j w (l, eo) bs) = JustifiedColumn cs w (l, eo) where cs = map (justify w j l eo chgrs) bs bottomPad :: E.Changers -> [JustifiedColumn] -> PaddedColumns bottomPad chgrs jcs = PaddedColumns pcs where justCells = map justifiedCells jcs (Height h) = height justCells pcs = map toPaddedColumn jcs toPaddedColumn (JustifiedColumn cs (Width w) (lbl, eo)) = let l = length cs nPads = max 0 $ h - l pad = E.getEvenOddLabelValue lbl eo chgrs . R.Chunk mempty $ t t = X.replicate w (X.singleton ' ') pads = replicate nPads $ JustifiedCell (mempty, pad) in cs ++ pads toCellsByRow :: PaddedColumns -> CellsByRow toCellsByRow (PaddedColumns cs) = CellsByRow (transpose cs) toCellRowsWithNewlines :: CellsByRow -> CellRowsWithNewlines toCellRowsWithNewlines (CellsByRow bs) = CellRowsWithNewlines bs' where bs' = foldr f [] bs newline = JustifiedCell (mempty, "\n") f cells acc = (cells ++ [newline]) : acc toBits :: CellRowsWithNewlines -> [[[R.Chunk]]] toBits (CellRowsWithNewlines cs) = map (map toB) cs where toB (JustifiedCell (c1, c2)) = [c1, c2]