diff options
| author | Raúl Benencia <rul@kalgan.cc> | 2013-08-20 20:37:34 -0300 | 
|---|---|---|
| committer | Raúl Benencia <rul@kalgan.cc> | 2013-08-20 20:37:34 -0300 | 
| commit | ce68d07f31019bf318a75e0ef9c438f0d25ae846 (patch) | |
| tree | c57d8f0c92bfa5fe6722d2685e1361205580ef1a | |
first commit
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Config.hs | 16 | ||||
| -rw-r--r-- | Email.hs | 77 | ||||
| -rw-r--r-- | Maildir.hs | 125 | ||||
| -rw-r--r-- | Main.hs | 50 | ||||
| -rw-r--r-- | Print.hs | 85 | ||||
| -rw-r--r-- | Rfc1342.hs | 68 | ||||
| -rw-r--r-- | Screen.hs | 217 | ||||
| -rw-r--r-- | State.hs | 72 | 
9 files changed, 713 insertions, 0 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f9bb5e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +Main +*.o +*.hi
\ No newline at end of file diff --git a/Config.hs b/Config.hs new file mode 100644 index 0000000..b2d865b --- /dev/null +++ b/Config.hs @@ -0,0 +1,16 @@ +-- This module is part of Lazymail, a Haskell email client. +-- +-- Copyright (C) 2013 Raúl Benencia <rul@kalgan.cc> +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program.  If not, see <http://www.gnu.org/licenses/>. diff --git a/Email.hs b/Email.hs new file mode 100644 index 0000000..78ffff6 --- /dev/null +++ b/Email.hs @@ -0,0 +1,77 @@ +-- This module is part of Lazymail, a Haskell email client. +-- +-- Copyright (C) 2013 Raúl Benencia <rul@kalgan.cc> +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program.  If not, see <http://www.gnu.org/licenses/>. +module Email where + +import Network.Email.Mailbox(Flag(..), Flags) + +import Text.Parsec.Error(ParseError) +import Text.ParserCombinators.Parsec (parse) +import Text.ParserCombinators.Parsec.Rfc2822 + +data Email = Email { emailPath :: String +                   , parsedEmail :: Message +                   } +              +parseEmail :: String -> Message +parseEmail msg = unwrapEmail $ parse message "<stdin>" $  fixEol msg + +unwrapEmail (Right email) = email +getFields (Message fs _) = fs + +-- There is obviously a pattern here. Find a way to narrow it down. +getReturnPath fs      = do { ReturnPath f <- fs; f } +getFrom fs            = do { From f <- fs; f } +getTo fs              = do { To f <- fs; f } +getCc fs              = do { Cc f <- fs; f } +getBcc fs             = do { Bcc f <- fs; f } +getReplyTo fs         = do { ReplyTo f <- fs; f } +getSubject fs         = do { Subject f <- fs; f } +getMessageID fs       = do { MessageID f <- fs; f } +getInReplyTo fs       = do { InReplyTo f <- fs; f } +getReferences fs      = do { References f <- fs; f } +getComments fs        = do { Comments f <- fs; f } +getKeywords fs        = do { Keywords f <- fs; f } +--getDate fs            = do { Date f <- fs; f } +--getResentDate fs      = do { ResentDate f <- fs; f } +getResentFrom fs      = do { ResentFrom f <- fs; f } +--getResentSender fs    = do { ResentSender f <- fs; f } +getResentTo fs        = do { ResentTo f <- fs; f } +getResentCc fs        = do { ResentCc f <- fs; f } +getResentBcc fs       = do { ResentBcc f <- fs; f } +getResentMessageID fs = do { ResentMessageID f <- fs; f } +--getReceived fs        = do { Received f <- fs; f } + +getBody (Message _ []) = "Empty body"     +getBody (Message _ body) = body + +-- Make sure all lines are terminated by CRLF. +fixEol :: String -> String +fixEol ('\r':'\n':xs)   = '\r' : '\n' : fixEol xs +fixEol ('\n':xs)        = '\r' : '\n' : fixEol xs +fixEol (x:xs)           = x : fixEol xs +fixEol []               = [] + +--data DescriptionPP = DescriptionPP { +--   ppOrder :: [String] -> [String] +-- } + + +-- emailDescription = emailDescriptionWithPP defaultDescriptionPP + +-- emailDescriptionWithPP pp  + + diff --git a/Maildir.hs b/Maildir.hs new file mode 100644 index 0000000..633db23 --- /dev/null +++ b/Maildir.hs @@ -0,0 +1,125 @@ +-- This module is part of Lazymail, a Haskell email client. +-- +-- Copyright (C) 2013 Raúl Benencia <rul@kalgan.cc> +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program.  If not, see <http://www.gnu.org/licenses/>. + +module Maildir  where     + +import Control.Monad.Loops(allM) +import Control.Monad (forM, filterM) +import Data.List(isPrefixOf) +import System.Directory (doesDirectoryExist, getDirectoryContents) +import System.FilePath ((</>)) +import System.IO(IOMode(..), hGetContents, openFile) +import Network.Email.Mailbox(Flag(..), Flags) +    			      +type Maildir = FilePath + +isMaildir :: FilePath -> IO Bool +isMaildir fp = allM doesDirectoryExist [ fp +                                       , fp </> "cur" +                                       , fp </> "new" +                                       , fp </> "tmp"] +                +listIDs :: Maildir -> IO [FilePath] +listIDs md = getNewIDs md `appendM` getReadIDs md +  where mxs `appendM` mxs' = do +          xs  <- mxs +          xs' <- mxs' +          return (xs ++ xs') +           +getNewIDs :: Maildir -> IO [FilePath] +getNewIDs md = getIDs (md </> "new") + +getReadIDs :: Maildir -> IO [FilePath] +getReadIDs md = getIDs (md </> "cur")   +   +getIDs :: FilePath -> IO [FilePath] +getIDs fp = do +  names <-getDirectoryContents fp +  let properNames = filter (`notElem` [".", ".."]) names +  return $ map (fp </>) properNames + +listMessageFlags :: Maildir -> IO [(FilePath, Flags)] +listMessageFlags fp = do +  ids <- (listIDs fp) +  let flags = map getFlags ids +  return (zip ids flags) +   +getFlags :: FilePath -> Flags +getFlags fp = map toFlag $ strip fp +  where strip x +          | null x               = [] +          | ":2," `isPrefixOf` x = drop 3 x +          | otherwise            = let (discard, analyze) = span (/= ':') fp +                                   in strip analyze +                                 +toFlag :: Char -> Flag +toFlag c | c == 'S'  = SEEN +         | c == 'A'  = ANSWERED +         | c == 'F'  = FLAGGED +         | c == 'D'  = DRAFT +         | c == 'P'  = FORWARDED +         | c == 'T'  = DELETED +         | otherwise = OTHERFLAG [c] +                        +getAll :: Maildir -> IO [(FilePath, Flags, String)] +getAll fp = do +  ids <- listIDs fp +  msgs <- mapM (\x -> hGetContents =<< openFile x ReadMode) ids +  let flags = map getFlags ids +  return $ zip3 ids flags msgs +   +{- | Returns information about specific messages. -}   +getMessages :: Maildir -> [FilePath] -> IO [(FilePath, Flags, String)] +getMessages mb list =  do +  messages <- getAll mb +  return $ filter (\(id, f, m) -> id `elem` list) messages +   +--   +-- | Based on getRecursiveContents from Real World Haskell +--   +getMaildirsRecursively :: FilePath -> IO [Maildir]   +getMaildirsRecursively topdir = do +  result <- search topdir +  includeTopDir <- isMaildir topdir +  if includeTopDir +     then return (topdir:result) +     else return result + +  where +    search topdir = do +      names <- getDirectoryContents topdir +      let properNames = filter (`notElem` [".", ".."]) names +      paths <- forM properNames $ \name -> do +        let path = topdir </> name +        isDirectory <- doesDirectoryExist path +        if isDirectory +          then do +            result <- search path +            return ([path] ++ result) +          else return [] + +      filterM isMaildir (concat paths) +   +   +-- Temporal code for testing purposes +defaultPath = "/home/rul/mail/linti/INBOX.academic.c.questions"   +getFirstEmail = do +  lst <- getAll defaultPath +  let (_, _, msg) = head lst +  return msg +   +  
\ No newline at end of file @@ -0,0 +1,50 @@ +-- This module is part of Lazymail, a Haskell email client. +-- +-- Copyright (C) 2013 Raúl Benencia <rul@kalgan.cc> +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program.  If not, see <http://www.gnu.org/licenses/>. + +module Main where + +import System.Environment +import System.Exit +import System.FilePath(takeDirectory) + +import Email +import Maildir +import Screen +import State + +parse ["-h"] = usage   >> exit +parse ["-v"] = version >> exit +parse [md]   = do +  putStrLn $ "Maildirs directory: " ++ md +  entryPoint $ initState { initPath = md }   +          +parse []= usage >> die + +usage   = putStrLn . unlines $ usageText where +  usageText = ["Usage: ./Main [-vh] <maildirs>" +              ,"      where <maildirs> is a directory with Maildirs, or a Maildir itself." +              ,"      Lazymail will recursively search for Maildirs. "] +               +version = putStrLn "Haskell lazymail 0.0001" +exit    = exitWith ExitSuccess +die     = exitWith (ExitFailure 1) + +main :: IO () +main = do +  args <- getArgs +  parse args +  putStrLn "Game over!" diff --git a/Print.hs b/Print.hs new file mode 100644 index 0000000..2e47c39 --- /dev/null +++ b/Print.hs @@ -0,0 +1,85 @@ +-- This module is part of Lazymail, a Haskell email client. +-- +-- Copyright (C) 2013 Raúl Benencia <rul@kalgan.cc> +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program.  If not, see <http://www.gnu.org/licenses/>. + +module Print where + +import Network.Email.Mailbox(Flag(..), Flags) +import Text.ParserCombinators.Parsec.Rfc2822(NameAddr(..)) +import Data.Char ( isSpace ) + +import Email +import Rfc1342 + +nameLen = 20 +ppNameAddr nas = concat $ map ppNameAddr' nas +  where ppNameAddr' na = case nameAddr_name na of +                           Nothing -> nameAddr_addr na +                           Just n  -> decodeField n +                            +ppIndexNameAddr = normalizeLen nameLen . ppNameAddr                            +                            +subjectLen = 90 +ppSubject = decodeField +ppIndexSubject = normalizeLen subjectLen . ppSubject +                            +ppFlags :: Flags -> String +ppFlags = map ppFlag + +ppFlag :: Flag -> Char +ppFlag SEEN      = 'S' +ppFlag ANSWERED  = 'A' +ppFlag FLAGGED   = 'F' +ppFlag DRAFT     = 'D' +ppFlag FORWARDED = 'P' +ppFlag DELETED   = 'T' +ppFlag (OTHERFLAG [c]) = c + +ppSep = "\t" + +normalizeLen len cs = if (length cs > len) +                      then shorten len cs +                      else if (length cs < len) +                           then fillWithSpace len cs +                           else cs +                                 +fillWithSpace len cs = cs ++ (take (len - length cs) . repeat $ ' ') + +-- The following functions are from DynamicLog xmonad-contrib source + +-- | Wrap a string in delimiters, unless it is empty. +wrap :: String  -- ^ left delimiter +     -> String  -- ^ right delimiter +     -> String  -- ^ output string +     -> String +wrap _ _ "" = "" +wrap l r m  = l ++ m ++ r + +-- | Pad a string with a leading and trailing space. +pad :: String -> String +pad = wrap " " " " + +-- | Trim leading and trailing whitespace from a string. +trim :: String -> String +trim = f . f +    where f = reverse . dropWhile isSpace +           +-- | Limit a string to a certain length, adding "..." if truncated. +shorten :: Int -> String -> String +shorten n xs | length xs < n = xs +             | otherwise     = take (n - length end) xs ++ end +  where +    end = "..." diff --git a/Rfc1342.hs b/Rfc1342.hs new file mode 100644 index 0000000..08822b3 --- /dev/null +++ b/Rfc1342.hs @@ -0,0 +1,68 @@ +-- A simple Haskell RFC1342 decoder +-- Copyright (C) 2013 Raúl Benencia <rul@kalgan.cc> +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program.  If not, see <http://www.gnu.org/licenses/>. + +-- This module is an ugly hack. It has been poorly tested and it'll +-- probably blow up in your face. You've been warned. +module Rfc1342 (decodeField) where + +import qualified Codec.Binary.Base64 as B64 +import qualified Codec.Binary.QuotedPrintable as QP + +import Data.Char (toLower, isSpace, chr) +import Data.List(isPrefixOf) +import Data.Word (Word8) + +import Data.Encoding(decodeString) + +-- Encoding imports. If you want to support more encodings, just add'em here. +import Data.Encoding.UTF8 +import Data.Encoding.ISO88591 +import Data.Encoding.ISO88592 +import Data.Encoding.ISO88598 +import Data.Encoding.ISO88599 + +decodeField :: String -> String +decodeField ('=':'?':cs) = decodeWithCharset dec rest +  where (encoding, rest) = span (\c -> c /= '?') cs +        dec = case (map toLower encoding) of +          "utf-8"      -> decodeString UTF8 +          "iso-8859-1" -> decodeString ISO88591 +          "iso-8859-2" -> decodeString ISO88592 +          "iso-8859-8" -> decodeString ISO88598 +          "iso-8859-9" -> decodeString ISO88599 +          _            -> id +decodeField []           = [] +decodeField (c:cs)       = c:decodeField cs + +decodeWithCharset dec [] = [] +decodeWithCharset dec ('?':c:'?':cs) | toLower c == 'b' = dataDecodeWith B64.decode +                                     | toLower c == 'q' = dataDecodeWith QP.decode +                                     | otherwise        = cs +  where (encoded, rest') = span  (\c -> c /= '?') cs +        rest = if "?=" `isPrefixOf` rest' +               then drop 2 rest' +               else rest' +        dataDecodeWith datadec = (_2spc . dec . unwrap . datadec $ encoded) ++ (decodeField $ dropWhile isSpace rest) + +unwrap :: Maybe [Word8] -> String +unwrap Nothing    = [] +unwrap (Just str) = bytesToString str + +bytesToString :: [Word8] -> String +bytesToString = map (chr . fromIntegral) + +-- Sometimes an underscore represents the SPACE character +_2spc = map (\x -> if x == '_' then ' ' else x)
\ No newline at end of file diff --git a/Screen.hs b/Screen.hs new file mode 100644 index 0000000..fa01584 --- /dev/null +++ b/Screen.hs @@ -0,0 +1,217 @@ +-- This module is part of Lazymail, a Haskell email client. +-- +-- Copyright (C) 2013 Raúl Benencia <rul@kalgan.cc> +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program.  If not, see <http://www.gnu.org/licenses/>. + +module Screen where + +import Data.List(isPrefixOf) +import UI.NCurses +import Text.ParserCombinators.Parsec.Rfc2822(Message(..)) + +-- Local imports +import Maildir +import Email +import Print +import Rfc1342 +import State + +ppBaseRow = 0 +ppBaseColumn = 0 + +-- +-- | Main entry point +--                     +entryPoint :: MState -> IO () +entryPoint st' = do +  maildirs <- getMaildirsRecursively (initPath st') +  putStrLn $ "We could get " ++ (show . length) maildirs ++ " maildirs." +  runCurses $ do +    setEcho False +    (rows, columns) <- screenSize     +    selColID <- newColorID ColorBlack ColorWhite 1     +    let st = st' { +            scrRows = rows  - 1 +          , scrColumns = columns - 1 +          , selectedColorID = selColID +          , detectedMDs = maildirs } +    screenLoop st +     +screenLoop :: MState -> Curses ()     +screenLoop st = do +  w <- defaultWindow +  updateWindow w $ do +    clearMain (fromIntegral . scrRows $ st) (fromIntegral . scrColumns $ st) +    drawMode (mode st) st +  render +  st' <- handleEvent st +  if (not . exitRequested) st' +    then screenLoop st' +    else return () +          +-- +-- | Handle an event +--   +handleEvent :: MState -> Curses MState +handleEvent st = loop where +  loop = do +    w <- defaultWindow +    ev <- getEvent w Nothing +    case ev of +      Nothing  -> loop +      Just ev' -> case ev' of +                    EventCharacter c | c == 'q' || c == 'Q' -> return $ st { exitRequested = True }  +                    EventSpecialKey KeyUpArrow  -> return $ decSelectedRow st +                    EventCharacter 'k'          -> return $ decSelectedRow st +                     +                    EventSpecialKey KeyDownArrow -> return $ incSelectedRow st +                    EventCharacter 'j'           -> return $ incSelectedRow st +                     +                    _ ->  loop +          +-- +-- | Pattern match on the received mode and draw it in the screen. +--                     +drawMode :: Mode -> MState -> Update () +drawMode MaildirMode st = drawMaildirHelper st (detectedMDs st) +drawMode EmailMode   st = drawEmailHelper st +drawMode IndexMode   st = drawIndexHelper 0 0 (curRow st) (colPadding st) (selectedEmails st) + +drawMaildirHelper _ [] = return () +drawMaildirHelper st (md:mds) = do +  moveCursor (curRow st) (colPadding st) +  if (selectedRow st == curRow st) +     then do +       setColor $ selectedColorID st +       drawString $ normalizeLen (fromIntegral . scrColumns $ st) md +       setColor defaultColorID +     else drawString $ normalizeLen (fromIntegral . scrColumns $ st) md      +  if curRow st < scrRows st +    then drawMaildirHelper (incCurRow st) mds +    else return ()     + +drawIndexHelper origRow origColumn rows columns [] = moveCursor 0 0   +drawIndexHelper origRow origColumn rows columns ((fp, _, msg):ts) = do +  moveCursor origRow origColumn +  let fs = getFields $ parseEmail msg +  drawString $ show $ origRow + 1 +  drawString $ (ppSep ++) $ ppFlags . getFlags $ fp +  drawString $ (ppSep ++) $ ppIndexNameAddr . getFrom $ fs     +  drawString $ (ppSep ++) $ ppIndexSubject . getSubject $ fs +  if origRow < (rows - 1) +    then drawIndexHelper (origRow + 1) origColumn rows columns ts +    else return () +   +waitFor :: Window -> (Event -> Bool) -> Curses () +waitFor w p = loop where +  loop = do +    ev <- getEvent w Nothing +    case ev of +      Nothing -> loop +      Just ev' -> if p ev' then return () else loop + +extractParsedData :: Either a b -> b +extractParsedData (Right msg) = msg +--extractParsedData (Left err)  = error err +                        +drawEmailHelper st = do +  let fs = getFields $ selectedEmail st +  let cropWith xs = normalizeLen $ (fromIntegral . scrColumns $ st) - (length xs) +  let row = curRow st +  moveCursor row (colPadding st) +  drawString $ ("From: " ++) $ cropWith "From: " . ppNameAddr . getFrom $ fs +  moveCursor (row + 1) (colPadding st) +  drawString $ ("To: " ++) $ cropWith "To: " . ppNameAddr . getTo $ fs +  moveCursor (row + 2) (colPadding st) +  drawString $ ("Subject: " ++) $ cropWith "Subject: " . ppSubject . getSubject $ fs +   +  let body = getBody $ selectedEmail st +  drawBody (row + 4) (colPadding st) $ formatBody body (fromIntegral . scrColumns $ st) +  where drawBody _ _ [] = return () +        drawBody row col (xs:xss) = do +          moveCursor row col +          drawString xs +          if row < (scrRows st) then drawBody (row + 1) col xss else return () +-- +-- | Empty the whole window. Useful when changing modes. +--   +clearMain rows columns = do +  drawEmptyLine 0 +  where +    drawEmptyLine currentRow = do +      moveCursor currentRow 0 +      drawString $ replicate (columns - 1) ' ' +      if currentRow < (rows - 1) +         then drawEmptyLine $ currentRow + 1 +         else return () +   +--   +-- | Convert a String to multiple Strings, cropped by the maximum column +-- | size if necessary. +--       +formatBody :: String -> Int -> [String] +formatBody body maxColumns = format [] [] body where +  format parsed acc []                     = parsed ++ [acc] +  format parsed acc ('\r':'\n':xs) = format (parsed ++ [acc]) [] xs +  format parsed acc rest@(x:xs) | length acc < maxColumns = format parsed (acc ++ [x]) xs +                                | otherwise               = format (parsed ++ [acc]) "+" rest +   + + +-- drawIndex :: Maildir -> IO () +-- drawIndex md = do +--   emails <- getAll md +--   runCurses $ do +--     setEcho False +--     (rows, columns) <- screenSize +--     w <- defaultWindow +--     updateWindow w $ do +--       clearMain (fromIntegral rows) (fromIntegral columns) +--       drawIndexHelper 0 0 (fromIntegral rows) (fromIntegral columns) emails +--     render +--     waitFor w (\ev -> ev == EventCharacter 'q' || ev == EventCharacter 'Q') +--   let (_, _, msg) = head emails  +--   drawEmail $ parseEmail msg +   +-- drawEmail :: Message -> IO () +-- drawEmail email = do +--   runCurses $ do +--     setEcho False +--     (rows, columns) <- screenSize     +--     w <- defaultWindow +--     updateWindow w $ do +--       clearMain (fromIntegral rows) (fromIntegral columns)       +--       drawEmailHelper ppBaseRow ppBaseColumn (fromIntegral rows - 1) (fromIntegral columns - 1) email +--     render +--     waitFor w (\ev -> ev == EventCharacter 'q' || ev == EventCharacter 'Q') +     +-- drawMaildir :: MState -> IO ()    +-- drawMaildir st = do +--   maildirs <- getMaildirsRecursively (initPath st) +--   runCurses $ do +--     setEcho False +--     (rows, columns) <- screenSize     +--     selColID <- newColorID ColorBlack ColorWhite 1     +--     let st' = st { +--             scrRows = rows  - 1 +--           , scrColumns = columns - 1 +--           , selectedColorID = selColID }     +--     w <- defaultWindow +--     updateWindow w $ do +--       clearMain (fromIntegral rows) (fromIntegral columns) +--       drawMaildirHelper st' maildirs       +--     render +--     waitFor w (\ev -> ev == EventCharacter 'q' || ev == EventCharacter 'Q') +  
\ No newline at end of file diff --git a/State.hs b/State.hs new file mode 100644 index 0000000..3826bfc --- /dev/null +++ b/State.hs @@ -0,0 +1,72 @@ +-- This module is part of Lazymail, a Haskell email client. +-- +-- Copyright (C) 2013 Raúl Benencia <rul@kalgan.cc> +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program.  If not, see <http://www.gnu.org/licenses/>. + +-- +-- | The top level application state, and operations on that value. +-- +module State where + +import Text.ParserCombinators.Parsec.Rfc2822(Message, GenericMessage(..)) +import UI.NCurses(ColorID(..), defaultColorID) +import Network.Email.Mailbox(Flag(..), Flags) + +data Mode = MaildirMode | IndexMode | EmailMode + +data MState = MState { +    selectedRow     :: Integer +  , mode            :: Mode +  , initPath        :: String +  , scrRows         :: Integer +  , scrColumns      :: Integer +  , curRow          :: Integer +  , colPadding      :: Integer +  , selectedColorID :: ColorID +  , selectedEmail   :: Message +  , selectedEmails  :: [(String, [Flag], String)] +  , selectedMD      :: String +  , detectedMDs     :: [String] +  , exitRequested   :: Bool     +} + +initState = MState { +    selectedRow     = 0 +  , mode            = MaildirMode +  , initPath        = "" +  , scrRows         = (-1) +  , scrColumns      = (-1) +  , curRow          = 0 +  , colPadding      = 0              +  , selectedColorID = defaultColorID +  , selectedEmail   = Message [] "Dummy email" +  , selectedEmails  = [] +  , selectedMD      = ""                     +  , detectedMDs     = [] +  , exitRequested   = False                       +}  + +incCurRow st = st { curRow = (curRow st) + 1 } + +incSelectedRow st | selectedRow st < fromIntegral limit = st { selectedRow = (selectedRow st) + 1 } +                  | otherwise                           = st +  where +    limit = case (mode st) of +      MaildirMode -> (length $ detectedMDs st ) - 1 +      IndexMode   -> (length $ selectedEmails st) - 1 +      _           -> fromIntegral $ scrRows st + +decSelectedRow st | selectedRow st > 0 = st { selectedRow = (selectedRow st) - 1 } +                  | otherwise          = st
\ No newline at end of file | 
