ui: refactor: AppState -> UIState, cleanups
This commit is contained in:
		
							parent
							
								
									0851851ea9
								
							
						
					
					
						commit
						47a8eb53c8
					
				| @ -21,7 +21,7 @@ import qualified Data.Text as T | |||||||
| import Data.Time.Calendar (Day) | import Data.Time.Calendar (Day) | ||||||
| import System.FilePath (takeFileName) | import System.FilePath (takeFileName) | ||||||
| import qualified Data.Vector as V | import qualified Data.Vector as V | ||||||
| import Graphics.Vty as Vty | import Graphics.Vty | ||||||
| import Brick | import Brick | ||||||
| -- import Brick.Widgets.Center | -- import Brick.Widgets.Center | ||||||
| import Brick.Widgets.List | import Brick.Widgets.List | ||||||
| @ -37,6 +37,7 @@ import Hledger.Cli hiding (progname,prognameandversion,green) | |||||||
| import Hledger.UI.UIOptions | import Hledger.UI.UIOptions | ||||||
| -- import Hledger.UI.Theme | -- import Hledger.UI.Theme | ||||||
| import Hledger.UI.UITypes | import Hledger.UI.UITypes | ||||||
|  | import Hledger.UI.UIState | ||||||
| import Hledger.UI.UIUtils | import Hledger.UI.UIUtils | ||||||
| import Hledger.UI.RegisterScreen | import Hledger.UI.RegisterScreen | ||||||
| import Hledger.UI.ErrorScreen | import Hledger.UI.ErrorScreen | ||||||
| @ -50,13 +51,13 @@ accountsScreen = AccountsScreen{ | |||||||
|   ,_asSelectedAccount = "" |   ,_asSelectedAccount = "" | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| asInit :: Day -> Bool -> AppState -> AppState | asInit :: Day -> Bool -> UIState -> UIState | ||||||
| asInit d reset st@AppState{ | asInit d reset ui@UIState{ | ||||||
|   aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}, |   aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}, | ||||||
|   ajournal=j, |   ajournal=j, | ||||||
|   aScreen=s@AccountsScreen{} |   aScreen=s@AccountsScreen{} | ||||||
|   } = |   } = | ||||||
|   st{aopts=uopts', aScreen=s & asList .~ newitems'} |   ui{aopts=uopts', aScreen=s & asList .~ newitems'} | ||||||
|    where |    where | ||||||
|     newitems = list (Name "accounts") (V.fromList displayitems) 1 |     newitems = list (Name "accounts") (V.fromList displayitems) 1 | ||||||
| 
 | 
 | ||||||
| @ -103,8 +104,8 @@ asInit d reset st@AppState{ | |||||||
| 
 | 
 | ||||||
| asInit _ _ _ = error "init function called with wrong screen type, should not happen" | asInit _ _ _ = error "init function called with wrong screen type, should not happen" | ||||||
| 
 | 
 | ||||||
| asDraw :: AppState -> [Widget] | asDraw :: UIState -> [Widget] | ||||||
| asDraw AppState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}} | asDraw UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}} | ||||||
|                            ,ajournal=j |                            ,ajournal=j | ||||||
|                            ,aScreen=s@AccountsScreen{} |                            ,aScreen=s@AccountsScreen{} | ||||||
|                            ,aMode=mode |                            ,aMode=mode | ||||||
| @ -230,8 +231,8 @@ asDrawItem (acctwidth, balwidth) selected AccountsScreenItem{..} = | |||||||
|         sel | selected  = (<> "selected") |         sel | selected  = (<> "selected") | ||||||
|             | otherwise = id |             | otherwise = id | ||||||
| 
 | 
 | ||||||
| asHandle :: AppState -> Vty.Event -> EventM (Next AppState) | asHandle :: UIState -> Event -> EventM (Next UIState) | ||||||
| asHandle st'@AppState{ | asHandle ui0@UIState{ | ||||||
|    aScreen=scr@AccountsScreen{..} |    aScreen=scr@AccountsScreen{..} | ||||||
|   ,aopts=UIOpts{cliopts_=copts} |   ,aopts=UIOpts{cliopts_=copts} | ||||||
|   ,ajournal=j |   ,ajournal=j | ||||||
| @ -247,62 +248,62 @@ asHandle st'@AppState{ | |||||||
|     selacct = case listSelectedElement $ scr ^. asList of |     selacct = case listSelectedElement $ scr ^. asList of | ||||||
|                 Just (_, AccountsScreenItem{..}) -> asItemAccountName |                 Just (_, AccountsScreenItem{..}) -> asItemAccountName | ||||||
|                 Nothing -> scr ^. asSelectedAccount |                 Nothing -> scr ^. asSelectedAccount | ||||||
|     st = st'{aScreen=scr & asSelectedAccount .~ selacct} |     ui = ui0{aScreen=scr & asSelectedAccount .~ selacct} | ||||||
| 
 | 
 | ||||||
|   case mode of |   case mode of | ||||||
|     Minibuffer ed -> |     Minibuffer ed -> | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey KEsc   [] -> continue $ stCloseMinibuffer st |         EvKey KEsc   [] -> continue $ closeMinibuffer ui | ||||||
|         EvKey KEnter [] -> continue $ regenerateScreens j d $ stFilter s $ stCloseMinibuffer st |         EvKey KEnter [] -> continue $ regenerateScreens j d $ setFilter s $ closeMinibuffer ui | ||||||
|                             where s = chomp $ unlines $ getEditContents ed |                             where s = chomp $ unlines $ getEditContents ed | ||||||
|         ev              -> do ed' <- handleEvent ev ed |         ev              -> do ed' <- handleEvent ev ed | ||||||
|                               continue $ st{aMode=Minibuffer ed'} |                               continue $ ui{aMode=Minibuffer ed'} | ||||||
| 
 | 
 | ||||||
|     Help -> |     Help -> | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey (KChar 'q') [] -> halt st |         EvKey (KChar 'q') [] -> halt ui | ||||||
|         _                    -> helpHandle st ev |         _                    -> helpHandle ui ev | ||||||
| 
 | 
 | ||||||
|     Normal -> |     Normal -> | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey (KChar 'q') [] -> halt st |         EvKey (KChar 'q') [] -> halt ui | ||||||
|         -- EvKey (KChar 'l') [MCtrl] -> do |         -- EvKey (KChar 'l') [MCtrl] -> do | ||||||
|         EvKey KEsc [] -> continue $ resetScreens d st |         EvKey KEsc [] -> continue $ resetScreens d ui | ||||||
|         EvKey k [] | k `elem` [KChar 'h', KChar '?'] -> continue $ setMode Help st |         EvKey k [] | k `elem` [KChar 'h', KChar '?'] -> continue $ setMode Help ui | ||||||
|         EvKey (KChar 'g') [] -> liftIO (stReloadJournalIfChanged copts d j st) >>= continue |         EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j ui) >>= continue | ||||||
|         EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> stReloadJournalIfChanged copts d j st |         EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui | ||||||
|         EvKey (KChar '0') [] -> continue $ regenerateScreens j d $ setDepth (Just 0) st |         EvKey (KChar '0') [] -> continue $ regenerateScreens j d $ setDepth (Just 0) ui | ||||||
|         EvKey (KChar '1') [] -> continue $ regenerateScreens j d $ setDepth (Just 1) st |         EvKey (KChar '1') [] -> continue $ regenerateScreens j d $ setDepth (Just 1) ui | ||||||
|         EvKey (KChar '2') [] -> continue $ regenerateScreens j d $ setDepth (Just 2) st |         EvKey (KChar '2') [] -> continue $ regenerateScreens j d $ setDepth (Just 2) ui | ||||||
|         EvKey (KChar '3') [] -> continue $ regenerateScreens j d $ setDepth (Just 3) st |         EvKey (KChar '3') [] -> continue $ regenerateScreens j d $ setDepth (Just 3) ui | ||||||
|         EvKey (KChar '4') [] -> continue $ regenerateScreens j d $ setDepth (Just 4) st |         EvKey (KChar '4') [] -> continue $ regenerateScreens j d $ setDepth (Just 4) ui | ||||||
|         EvKey (KChar '5') [] -> continue $ regenerateScreens j d $ setDepth (Just 5) st |         EvKey (KChar '5') [] -> continue $ regenerateScreens j d $ setDepth (Just 5) ui | ||||||
|         EvKey (KChar '6') [] -> continue $ regenerateScreens j d $ setDepth (Just 6) st |         EvKey (KChar '6') [] -> continue $ regenerateScreens j d $ setDepth (Just 6) ui | ||||||
|         EvKey (KChar '7') [] -> continue $ regenerateScreens j d $ setDepth (Just 7) st |         EvKey (KChar '7') [] -> continue $ regenerateScreens j d $ setDepth (Just 7) ui | ||||||
|         EvKey (KChar '8') [] -> continue $ regenerateScreens j d $ setDepth (Just 8) st |         EvKey (KChar '8') [] -> continue $ regenerateScreens j d $ setDepth (Just 8) ui | ||||||
|         EvKey (KChar '9') [] -> continue $ regenerateScreens j d $ setDepth (Just 9) st |         EvKey (KChar '9') [] -> continue $ regenerateScreens j d $ setDepth (Just 9) ui | ||||||
|         EvKey (KChar '-') [] -> continue $ regenerateScreens j d $ decDepth st |         EvKey (KChar '-') [] -> continue $ regenerateScreens j d $ decDepth ui | ||||||
|         EvKey (KChar '_') [] -> continue $ regenerateScreens j d $ decDepth st |         EvKey (KChar '_') [] -> continue $ regenerateScreens j d $ decDepth ui | ||||||
|         EvKey k [] | k `elem` [KChar '+', KChar '='] -> continue $ regenerateScreens j d $ incDepth st |         EvKey k [] | k `elem` [KChar '+', KChar '='] -> continue $ regenerateScreens j d $ incDepth ui | ||||||
|         EvKey (KChar 'F') [] -> continue $ regenerateScreens j d $ stToggleFlat st |         EvKey (KChar 'F') [] -> continue $ regenerateScreens j d $ toggleFlat ui | ||||||
|         EvKey (KChar 'E') [] -> scrollTop >> (continue $ regenerateScreens j d $ stToggleEmpty st) |         EvKey (KChar 'E') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleEmpty ui) | ||||||
|         EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ stToggleCleared st) |         EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleCleared ui) | ||||||
|         EvKey (KChar 'U') [] -> scrollTop >> (continue $ regenerateScreens j d $ stToggleUncleared st) |         EvKey (KChar 'U') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleUncleared ui) | ||||||
|         EvKey (KChar 'R') [] -> scrollTop >> (continue $ regenerateScreens j d $ stToggleReal st) |         EvKey (KChar 'R') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleReal ui) | ||||||
|         EvKey k [] | k `elem` [KChar '/'] -> continue $ regenerateScreens j d $ stShowMinibuffer st |         EvKey k [] | k `elem` [KChar '/'] -> continue $ regenerateScreens j d $ showMinibuffer ui | ||||||
|         EvKey k [] | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ stResetFilter st) |         EvKey k [] | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ resetFilter ui) | ||||||
|         EvKey (KLeft) []     -> continue $ popScreen st |         EvKey (KLeft) []     -> continue $ popScreen ui | ||||||
|         EvKey (k) [] | k `elem` [KRight, KEnter] -> scrollTopRegister >> continue (screenEnter d scr st) |         EvKey (k) [] | k `elem` [KRight, KEnter] -> scrollTopRegister >> continue (screenEnter d scr ui) | ||||||
|           where |           where | ||||||
|             scr = rsSetAccount selacct registerScreen |             scr = rsSetAccount selacct registerScreen | ||||||
| 
 | 
 | ||||||
|         -- fall through to the list's event handler (handles up/down) |         -- fall through to the list's event handler (handles up/down) | ||||||
|         ev                       -> do |         ev                       -> do | ||||||
|                                      newitems <- handleEvent ev (scr ^. asList) |                                      newitems <- handleEvent ev (scr ^. asList) | ||||||
|                                      continue $ st{aScreen=scr & asList .~ newitems |                                      continue $ ui{aScreen=scr & asList .~ newitems | ||||||
|                                                                 & asSelectedAccount .~ selacct |                                                                 & asSelectedAccount .~ selacct | ||||||
|                                                                 } |                                                                 } | ||||||
|                                  -- continue =<< handleEventLensed st someLens ev |                                  -- continue =<< handleEventLensed ui someLens ev | ||||||
| 
 | 
 | ||||||
|   where |   where | ||||||
|     -- Encourage a more stable scroll position when toggling list items. |     -- Encourage a more stable scroll position when toggling list items. | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| 
 | 
 | ||||||
| module Hledger.UI.ErrorScreen | module Hledger.UI.ErrorScreen | ||||||
|  (errorScreen |  (errorScreen | ||||||
|  ,stReloadJournalIfChanged |  ,uiReloadJournalIfChanged | ||||||
|  ) |  ) | ||||||
| where | where | ||||||
| 
 | 
 | ||||||
| @ -13,7 +13,7 @@ import Control.Monad.IO.Class (liftIO) | |||||||
| import Data.Monoid | import Data.Monoid | ||||||
| -- import Data.Maybe | -- import Data.Maybe | ||||||
| import Data.Time.Calendar (Day) | import Data.Time.Calendar (Day) | ||||||
| import Graphics.Vty as Vty | import Graphics.Vty | ||||||
| import Brick | import Brick | ||||||
| -- import Brick.Widgets.List | -- import Brick.Widgets.List | ||||||
| -- import Brick.Widgets.Border | -- import Brick.Widgets.Border | ||||||
| @ -26,6 +26,7 @@ import Hledger.Cli hiding (progname,prognameandversion,green) | |||||||
| import Hledger.UI.UIOptions | import Hledger.UI.UIOptions | ||||||
| -- import Hledger.UI.Theme | -- import Hledger.UI.Theme | ||||||
| import Hledger.UI.UITypes | import Hledger.UI.UITypes | ||||||
|  | import Hledger.UI.UIState | ||||||
| import Hledger.UI.UIUtils | import Hledger.UI.UIUtils | ||||||
| 
 | 
 | ||||||
| errorScreen :: Screen | errorScreen :: Screen | ||||||
| @ -36,12 +37,12 @@ errorScreen = ErrorScreen{ | |||||||
|   ,esError  = "" |   ,esError  = "" | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| esInit :: Day -> Bool -> AppState -> AppState | esInit :: Day -> Bool -> UIState -> UIState | ||||||
| esInit _ _ st@AppState{aScreen=ErrorScreen{}} = st | esInit _ _ ui@UIState{aScreen=ErrorScreen{}} = ui | ||||||
| esInit _ _ _ = error "init function called with wrong screen type, should not happen" | esInit _ _ _ = error "init function called with wrong screen type, should not happen" | ||||||
| 
 | 
 | ||||||
| esDraw :: AppState -> [Widget] | esDraw :: UIState -> [Widget] | ||||||
| esDraw AppState{ -- aopts=_uopts@UIOpts{cliopts_=_copts@CliOpts{reportopts_=_ropts@ReportOpts{query_=querystr}}}, | esDraw UIState{ -- aopts=_uopts@UIOpts{cliopts_=_copts@CliOpts{reportopts_=_ropts@ReportOpts{query_=querystr}}}, | ||||||
|                              aScreen=ErrorScreen{..} |                              aScreen=ErrorScreen{..} | ||||||
|                              ,aMode=mode} = |                              ,aMode=mode} = | ||||||
|   case mode of |   case mode of | ||||||
| @ -65,8 +66,8 @@ esDraw AppState{ -- aopts=_uopts@UIOpts{cliopts_=_copts@CliOpts{reportopts_=_rop | |||||||
| 
 | 
 | ||||||
| esDraw _ = error "draw function called with wrong screen type, should not happen" | esDraw _ = error "draw function called with wrong screen type, should not happen" | ||||||
| 
 | 
 | ||||||
| esHandle :: AppState -> Vty.Event -> EventM (Next AppState) | esHandle :: UIState -> Event -> EventM (Next UIState) | ||||||
| esHandle st@AppState{ | esHandle ui@UIState{ | ||||||
|    aScreen=s@ErrorScreen{} |    aScreen=s@ErrorScreen{} | ||||||
|   ,aopts=UIOpts{cliopts_=copts} |   ,aopts=UIOpts{cliopts_=copts} | ||||||
|   ,ajournal=j |   ,ajournal=j | ||||||
| @ -75,35 +76,35 @@ esHandle st@AppState{ | |||||||
|   case mode of |   case mode of | ||||||
|     Help -> |     Help -> | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey (KChar 'q') [] -> halt st |         EvKey (KChar 'q') [] -> halt ui | ||||||
|         _                    -> helpHandle st ev |         _                    -> helpHandle ui ev | ||||||
| 
 | 
 | ||||||
|     _ -> do |     _ -> do | ||||||
|       d <- liftIO getCurrentDay |       d <- liftIO getCurrentDay | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey (KChar 'q') [] -> halt st |         EvKey (KChar 'q') [] -> halt ui | ||||||
|         EvKey KEsc        [] -> continue $ resetScreens d st |         EvKey KEsc        [] -> continue $ resetScreens d ui | ||||||
|         EvKey k [] | k `elem` [KChar 'h', KChar '?'] -> continue $ setMode Help st |         EvKey k [] | k `elem` [KChar 'h', KChar '?'] -> continue $ setMode Help ui | ||||||
|         EvKey (KChar 'g') [] -> do |         EvKey (KChar 'g') [] -> do | ||||||
|           (ej, _) <- liftIO $ journalReloadIfChanged copts d j |           (ej, _) <- liftIO $ journalReloadIfChanged copts d j | ||||||
|           case ej of |           case ej of | ||||||
|             Left err -> continue st{aScreen=s{esError=err}} -- show latest parse error |             Left err -> continue ui{aScreen=s{esError=err}} -- show latest parse error | ||||||
|             Right j' -> continue $ regenerateScreens j' d $ popScreen st  -- return to previous screen, and reload it |             Right j' -> continue $ regenerateScreens j' d $ popScreen ui  -- return to previous screen, and reload it | ||||||
|         -- EvKey (KLeft) []     -> continue $ popScreen st |         -- EvKey (KLeft) []     -> continue $ popScreen ui | ||||||
|         -- EvKey (KRight) []    -> error (show curItem) where curItem = listSelectedElement is |         -- EvKey (KRight) []    -> error (show curItem) where curItem = listSelectedElement is | ||||||
|         -- fall through to the list's event handler (handles [pg]up/down) |         -- fall through to the list's event handler (handles [pg]up/down) | ||||||
|         _                       -> do continue st |         _                       -> do continue ui | ||||||
|                                      -- is' <- handleEvent ev is |                                      -- is' <- handleEvent ev is | ||||||
|                                      -- continue st{aScreen=s{rsState=is'}} |                                      -- continue ui{aScreen=s{rsState=is'}} | ||||||
|                                      -- continue =<< handleEventLensed st someLens e |                                      -- continue =<< handleEventLensed ui someLens e | ||||||
| esHandle _ _ = error "event handler called with wrong screen type, should not happen" | esHandle _ _ = error "event handler called with wrong screen type, should not happen" | ||||||
| 
 | 
 | ||||||
| -- If journal file(s) have changed, reload the journal and regenerate all screens. | -- If journal file(s) have changed, reload the journal and regenerate all screens. | ||||||
| -- This is here so it can reference the error screen. | -- This is here so it can reference the error screen. | ||||||
| stReloadJournalIfChanged :: CliOpts -> Day -> Journal -> AppState -> IO AppState | uiReloadJournalIfChanged :: CliOpts -> Day -> Journal -> UIState -> IO UIState | ||||||
| stReloadJournalIfChanged copts d j st = do | uiReloadJournalIfChanged copts d j ui = do | ||||||
|   (ej, _) <- journalReloadIfChanged copts d j |   (ej, _) <- journalReloadIfChanged copts d j | ||||||
|   return $ case ej of |   return $ case ej of | ||||||
|     Right j' -> regenerateScreens j' d st |     Right j' -> regenerateScreens j' d ui | ||||||
|     Left err -> screenEnter d errorScreen{esError=err} st |     Left err -> screenEnter d errorScreen{esError=err} ui | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -107,10 +107,10 @@ runBrickUi uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} j = do | |||||||
|                  (error' $ "--register "++apat++" did not match any account") |                  (error' $ "--register "++apat++" did not match any account") | ||||||
|                  $ filter (regexMatches apat . T.unpack) $ journalAccountNames j |                  $ filter (regexMatches apat . T.unpack) $ journalAccountNames j | ||||||
|           -- Initialising the accounts screen is awkward, requiring |           -- Initialising the accounts screen is awkward, requiring | ||||||
|           -- another temporary AppState value.. |           -- another temporary UIState value.. | ||||||
|           ascr' = aScreen $ |           ascr' = aScreen $ | ||||||
|                   asInit d True $ |                   asInit d True $ | ||||||
|                   AppState{ |                   UIState{ | ||||||
|                     aopts=uopts' |                     aopts=uopts' | ||||||
|                    ,ajournal=j |                    ,ajournal=j | ||||||
|                    ,aScreen=asSetSelectedAccount acct accountsScreen |                    ,aScreen=asSetSelectedAccount acct accountsScreen | ||||||
| @ -118,8 +118,8 @@ runBrickUi uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} j = do | |||||||
|                    ,aMode=Normal |                    ,aMode=Normal | ||||||
|                    } |                    } | ||||||
|    |    | ||||||
|     st = (sInit scr) d True |     ui = (sInit scr) d True | ||||||
|          AppState{ |          UIState{ | ||||||
|             aopts=uopts' |             aopts=uopts' | ||||||
|            ,ajournal=j |            ,ajournal=j | ||||||
|            ,aScreen=scr |            ,aScreen=scr | ||||||
| @ -127,20 +127,15 @@ runBrickUi uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} j = do | |||||||
|            ,aMode=Normal |            ,aMode=Normal | ||||||
|            } |            } | ||||||
| 
 | 
 | ||||||
|     brickapp :: App (AppState) V.Event |     brickapp :: App (UIState) V.Event | ||||||
|     brickapp = App { |     brickapp = App { | ||||||
|         appLiftVtyEvent = id |         appLiftVtyEvent = id | ||||||
|       , appStartEvent   = return |       , appStartEvent   = return | ||||||
|       , appAttrMap      = const theme |       , appAttrMap      = const theme | ||||||
|       , appChooseCursor = showFirstCursor |       , appChooseCursor = showFirstCursor | ||||||
|       , appHandleEvent  = \st ev -> sHandle (aScreen st) st ev |       , appHandleEvent  = \ui ev -> sHandle (aScreen ui) ui ev | ||||||
|       , appDraw         = \st    -> sDraw   (aScreen st) st |       , appDraw         = \ui    -> sDraw   (aScreen ui) ui | ||||||
|          -- XXX bizarro. removing the st arg and parameter above, |  | ||||||
|          -- which according to GHCI does not change the type, |  | ||||||
|          -- causes "Exception: draw function called with wrong screen type" |  | ||||||
|          -- on entering a register. Likewise, removing the st ev args and parameters |  | ||||||
|          -- causes an exception on exiting a register. |  | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|   void $ defaultMain brickapp st |   void $ defaultMain brickapp ui | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import Data.Maybe | |||||||
| import qualified Data.Text as T | import qualified Data.Text as T | ||||||
| import Data.Time.Calendar (Day) | import Data.Time.Calendar (Day) | ||||||
| import qualified Data.Vector as V | import qualified Data.Vector as V | ||||||
| import Graphics.Vty as Vty | import Graphics.Vty | ||||||
| import Brick | import Brick | ||||||
| import Brick.Widgets.List | import Brick.Widgets.List | ||||||
| import Brick.Widgets.Edit | import Brick.Widgets.Edit | ||||||
| @ -33,6 +33,7 @@ import Hledger.Cli hiding (progname,prognameandversion,green) | |||||||
| import Hledger.UI.UIOptions | import Hledger.UI.UIOptions | ||||||
| -- import Hledger.UI.Theme | -- import Hledger.UI.Theme | ||||||
| import Hledger.UI.UITypes | import Hledger.UI.UITypes | ||||||
|  | import Hledger.UI.UIState | ||||||
| import Hledger.UI.UIUtils | import Hledger.UI.UIUtils | ||||||
| import Hledger.UI.TransactionScreen | import Hledger.UI.TransactionScreen | ||||||
| import Hledger.UI.ErrorScreen | import Hledger.UI.ErrorScreen | ||||||
| @ -49,9 +50,9 @@ registerScreen = RegisterScreen{ | |||||||
| rsSetAccount a scr@RegisterScreen{} = scr{rsAccount=a} | rsSetAccount a scr@RegisterScreen{} = scr{rsAccount=a} | ||||||
| rsSetAccount _ scr = scr | rsSetAccount _ scr = scr | ||||||
| 
 | 
 | ||||||
| rsInit :: Day -> Bool -> AppState -> AppState | rsInit :: Day -> Bool -> UIState -> UIState | ||||||
| rsInit d reset st@AppState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen{..}} = | rsInit d reset ui@UIState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen{..}} = | ||||||
|   st{aScreen=s{rsList=newitems'}} |   ui{aScreen=s{rsList=newitems'}} | ||||||
|   where |   where | ||||||
|     -- gather arguments and queries |     -- gather arguments and queries | ||||||
|     ropts = (reportopts_ $ cliopts_ opts) |     ropts = (reportopts_ $ cliopts_ opts) | ||||||
| @ -99,8 +100,8 @@ rsInit d reset st@AppState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen{..}} | |||||||
| 
 | 
 | ||||||
| rsInit _ _ _ = error "init function called with wrong screen type, should not happen" | rsInit _ _ _ = error "init function called with wrong screen type, should not happen" | ||||||
| 
 | 
 | ||||||
| rsDraw :: AppState -> [Widget] | rsDraw :: UIState -> [Widget] | ||||||
| rsDraw AppState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}} | rsDraw UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}} | ||||||
|                             ,aScreen=RegisterScreen{..} |                             ,aScreen=RegisterScreen{..} | ||||||
|                             ,aMode=mode |                             ,aMode=mode | ||||||
|                             } = |                             } = | ||||||
| @ -219,8 +220,8 @@ rsDrawItem (datewidth,descwidth,acctswidth,changewidth,balwidth) selected Regist | |||||||
|     sel | selected  = (<> "selected") |     sel | selected  = (<> "selected") | ||||||
|         | otherwise = id |         | otherwise = id | ||||||
| 
 | 
 | ||||||
| rsHandle :: AppState -> Vty.Event -> EventM (Next AppState) | rsHandle :: UIState -> Event -> EventM (Next UIState) | ||||||
| rsHandle st@AppState{ | rsHandle ui@UIState{ | ||||||
|    aScreen=s@RegisterScreen{..} |    aScreen=s@RegisterScreen{..} | ||||||
|   ,aopts=UIOpts{cliopts_=copts} |   ,aopts=UIOpts{cliopts_=copts} | ||||||
|   ,ajournal=j |   ,ajournal=j | ||||||
| @ -231,31 +232,31 @@ rsHandle st@AppState{ | |||||||
|   case mode of |   case mode of | ||||||
|     Minibuffer ed -> |     Minibuffer ed -> | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey KEsc   [] -> continue $ stCloseMinibuffer st |         EvKey KEsc   [] -> continue $ closeMinibuffer ui | ||||||
|         EvKey KEnter [] -> continue $ regenerateScreens j d $ stFilter s $ stCloseMinibuffer st |         EvKey KEnter [] -> continue $ regenerateScreens j d $ setFilter s $ closeMinibuffer ui | ||||||
|                             where s = chomp $ unlines $ getEditContents ed |                             where s = chomp $ unlines $ getEditContents ed | ||||||
|         ev              -> do ed' <- handleEvent ev ed |         ev              -> do ed' <- handleEvent ev ed | ||||||
|                               continue $ st{aMode=Minibuffer ed'} |                               continue $ ui{aMode=Minibuffer ed'} | ||||||
| 
 | 
 | ||||||
|     Help -> |     Help -> | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey (KChar 'q') [] -> halt st |         EvKey (KChar 'q') [] -> halt ui | ||||||
|         _                    -> helpHandle st ev |         _                    -> helpHandle ui ev | ||||||
| 
 | 
 | ||||||
|     Normal -> |     Normal -> | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey (KChar 'q') [] -> halt st |         EvKey (KChar 'q') [] -> halt ui | ||||||
|         EvKey KEsc        [] -> continue $ resetScreens d st |         EvKey KEsc        [] -> continue $ resetScreens d ui | ||||||
|         EvKey k [] | k `elem` [KChar 'h', KChar '?'] -> continue $ setMode Help st |         EvKey k [] | k `elem` [KChar 'h', KChar '?'] -> continue $ setMode Help ui | ||||||
|         EvKey (KChar 'g') [] -> liftIO (stReloadJournalIfChanged copts d j st) >>= continue |         EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j ui) >>= continue | ||||||
|         EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> stReloadJournalIfChanged copts d j st |         EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui | ||||||
|         EvKey (KChar 'E') [] -> scrollTop >> (continue $ regenerateScreens j d $ stToggleEmpty st) |         EvKey (KChar 'E') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleEmpty ui) | ||||||
|         EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ stToggleCleared st) |         EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleCleared ui) | ||||||
|         EvKey (KChar 'U') [] -> scrollTop >> (continue $ regenerateScreens j d $ stToggleUncleared st) |         EvKey (KChar 'U') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleUncleared ui) | ||||||
|         EvKey (KChar 'R') [] -> scrollTop >> (continue $ regenerateScreens j d $ stToggleReal st) |         EvKey (KChar 'R') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleReal ui) | ||||||
|         EvKey k [] | k `elem` [KChar '/'] -> (continue $ regenerateScreens j d $ stShowMinibuffer st) |         EvKey k [] | k `elem` [KChar '/'] -> (continue $ regenerateScreens j d $ showMinibuffer ui) | ||||||
|         EvKey k [] | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ stResetFilter st) |         EvKey k [] | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ resetFilter ui) | ||||||
|         EvKey (KLeft)     [] -> continue $ popScreen st |         EvKey (KLeft)     [] -> continue $ popScreen ui | ||||||
| 
 | 
 | ||||||
|         EvKey (k) [] | k `elem` [KRight, KEnter] -> do |         EvKey (k) [] | k `elem` [KRight, KEnter] -> do | ||||||
|           case listSelectedElement rsList of |           case listSelectedElement rsList of | ||||||
| @ -267,13 +268,13 @@ rsHandle st@AppState{ | |||||||
|               in |               in | ||||||
|                 continue $ screenEnter d transactionScreen{tsTransaction=(i,t) |                 continue $ screenEnter d transactionScreen{tsTransaction=(i,t) | ||||||
|                                                           ,tsTransactions=numberedts |                                                           ,tsTransactions=numberedts | ||||||
|                                                           ,tsAccount=rsAccount} st |                                                           ,tsAccount=rsAccount} ui | ||||||
|             Nothing -> continue st |             Nothing -> continue ui | ||||||
| 
 | 
 | ||||||
|         -- fall through to the list's event handler (handles [pg]up/down) |         -- fall through to the list's event handler (handles [pg]up/down) | ||||||
|         ev -> do newitems <- handleEvent ev rsList |         ev -> do newitems <- handleEvent ev rsList | ||||||
|                  continue st{aScreen=s{rsList=newitems}} |                  continue ui{aScreen=s{rsList=newitems}} | ||||||
|                  -- continue =<< handleEventLensed st someLens ev |                  -- continue =<< handleEventLensed ui someLens ev | ||||||
| 
 | 
 | ||||||
|   where |   where | ||||||
|     -- Encourage a more stable scroll position when toggling list items (cf AccountsScreen.hs) |     -- Encourage a more stable scroll position when toggling list items (cf AccountsScreen.hs) | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ import Data.Monoid | |||||||
| import qualified Data.Text as T | import qualified Data.Text as T | ||||||
| import Data.Time.Calendar (Day) | import Data.Time.Calendar (Day) | ||||||
| -- import qualified Data.Vector as V | -- import qualified Data.Vector as V | ||||||
| import Graphics.Vty as Vty | import Graphics.Vty | ||||||
| -- import Safe (headDef, lastDef) | -- import Safe (headDef, lastDef) | ||||||
| import Brick | import Brick | ||||||
| import Brick.Widgets.List (listMoveTo) | import Brick.Widgets.List (listMoveTo) | ||||||
| @ -33,6 +33,7 @@ import Hledger.Cli hiding (progname,prognameandversion,green) | |||||||
| import Hledger.UI.UIOptions | import Hledger.UI.UIOptions | ||||||
| -- import Hledger.UI.Theme | -- import Hledger.UI.Theme | ||||||
| import Hledger.UI.UITypes | import Hledger.UI.UITypes | ||||||
|  | import Hledger.UI.UIState | ||||||
| import Hledger.UI.UIUtils | import Hledger.UI.UIUtils | ||||||
| import Hledger.UI.ErrorScreen | import Hledger.UI.ErrorScreen | ||||||
| 
 | 
 | ||||||
| @ -46,14 +47,14 @@ transactionScreen = TransactionScreen{ | |||||||
|   ,tsAccount      = "" |   ,tsAccount      = "" | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| tsInit :: Day -> Bool -> AppState -> AppState | tsInit :: Day -> Bool -> UIState -> UIState | ||||||
| tsInit _d _reset st@AppState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=_ropts}} | tsInit _d _reset ui@UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=_ropts}} | ||||||
|                                            ,ajournal=_j |                                            ,ajournal=_j | ||||||
|                                            ,aScreen=TransactionScreen{..}} = st |                                            ,aScreen=TransactionScreen{..}} = ui | ||||||
| tsInit _ _ _ = error "init function called with wrong screen type, should not happen" | tsInit _ _ _ = error "init function called with wrong screen type, should not happen" | ||||||
| 
 | 
 | ||||||
| tsDraw :: AppState -> [Widget] | tsDraw :: UIState -> [Widget] | ||||||
| tsDraw AppState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}} | tsDraw UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}} | ||||||
|                               ,aScreen=TransactionScreen{ |                               ,aScreen=TransactionScreen{ | ||||||
|                                    tsTransaction=(i,t) |                                    tsTransaction=(i,t) | ||||||
|                                   ,tsTransactions=nts |                                   ,tsTransactions=nts | ||||||
| @ -107,8 +108,8 @@ tsDraw AppState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}} | |||||||
| 
 | 
 | ||||||
| tsDraw _ = error "draw function called with wrong screen type, should not happen" | tsDraw _ = error "draw function called with wrong screen type, should not happen" | ||||||
| 
 | 
 | ||||||
| tsHandle :: AppState -> Vty.Event -> EventM (Next AppState) | tsHandle :: UIState -> Event -> EventM (Next UIState) | ||||||
| tsHandle st@AppState{aScreen=s@TransactionScreen{tsTransaction=(i,t) | tsHandle ui@UIState{aScreen=s@TransactionScreen{tsTransaction=(i,t) | ||||||
|                                                 ,tsTransactions=nts |                                                 ,tsTransactions=nts | ||||||
|                                                 ,tsAccount=acct} |                                                 ,tsAccount=acct} | ||||||
|                     ,aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} |                     ,aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}} | ||||||
| @ -119,8 +120,8 @@ tsHandle st@AppState{aScreen=s@TransactionScreen{tsTransaction=(i,t) | |||||||
|   case mode of |   case mode of | ||||||
|     Help -> |     Help -> | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey (KChar 'q') [] -> halt st |         EvKey (KChar 'q') [] -> halt ui | ||||||
|         _                    -> helpHandle st ev |         _                    -> helpHandle ui ev | ||||||
| 
 | 
 | ||||||
|     _ -> do |     _ -> do | ||||||
|       d <- liftIO getCurrentDay |       d <- liftIO getCurrentDay | ||||||
| @ -128,14 +129,14 @@ tsHandle st@AppState{aScreen=s@TransactionScreen{tsTransaction=(i,t) | |||||||
|         (iprev,tprev) = maybe (i,t) ((i-1),) $ lookup (i-1) nts |         (iprev,tprev) = maybe (i,t) ((i-1),) $ lookup (i-1) nts | ||||||
|         (inext,tnext) = maybe (i,t) ((i+1),) $ lookup (i+1) nts |         (inext,tnext) = maybe (i,t) ((i+1),) $ lookup (i+1) nts | ||||||
|       case ev of |       case ev of | ||||||
|         EvKey (KChar 'q') [] -> halt st |         EvKey (KChar 'q') [] -> halt ui | ||||||
|         EvKey KEsc        [] -> continue $ resetScreens d st |         EvKey KEsc        [] -> continue $ resetScreens d ui | ||||||
|         EvKey k [] | k `elem` [KChar 'h', KChar '?'] -> continue $ setMode Help st |         EvKey k [] | k `elem` [KChar 'h', KChar '?'] -> continue $ setMode Help ui | ||||||
|         EvKey (KChar 'g') [] -> do |         EvKey (KChar 'g') [] -> do | ||||||
|           d <- liftIO getCurrentDay |           d <- liftIO getCurrentDay | ||||||
|           (ej, _) <- liftIO $ journalReloadIfChanged copts d j |           (ej, _) <- liftIO $ journalReloadIfChanged copts d j | ||||||
|           case ej of |           case ej of | ||||||
|             Left err -> continue $ screenEnter d errorScreen{esError=err} st |             Left err -> continue $ screenEnter d errorScreen{esError=err} ui | ||||||
|             Right j' -> do |             Right j' -> do | ||||||
|               -- got to redo the register screen's transactions report, to get the latest transactions list for this screen |               -- got to redo the register screen's transactions report, to get the latest transactions list for this screen | ||||||
|               -- XXX duplicates rsInit |               -- XXX duplicates rsInit | ||||||
| @ -155,21 +156,21 @@ tsHandle st@AppState{aScreen=s@TransactionScreen{tsTransaction=(i,t) | |||||||
|                              Nothing | null numberedts -> (0,nulltransaction) |                              Nothing | null numberedts -> (0,nulltransaction) | ||||||
|                                      | i > fst (last numberedts) -> last numberedts |                                      | i > fst (last numberedts) -> last numberedts | ||||||
|                                      | otherwise -> head numberedts |                                      | otherwise -> head numberedts | ||||||
|                 st' = st{aScreen=s{tsTransaction=(i',t') |                 ui' = ui{aScreen=s{tsTransaction=(i',t') | ||||||
|                                   ,tsTransactions=numberedts |                                   ,tsTransactions=numberedts | ||||||
|                                   ,tsAccount=acct}} |                                   ,tsAccount=acct}} | ||||||
|               continue $ regenerateScreens j' d st' |               continue $ regenerateScreens j' d ui' | ||||||
|         -- if allowing toggling here, we should refresh the txn list from the parent register screen |         -- if allowing toggling here, we should refresh the txn list from the parent register screen | ||||||
|         -- EvKey (KChar 'E') [] -> continue $ regenerateScreens j d $ stToggleEmpty st |         -- EvKey (KChar 'E') [] -> continue $ regenerateScreens j d $ stToggleEmpty ui | ||||||
|         -- EvKey (KChar 'C') [] -> continue $ regenerateScreens j d $ stToggleCleared st |         -- EvKey (KChar 'C') [] -> continue $ regenerateScreens j d $ stToggleCleared ui | ||||||
|         -- EvKey (KChar 'R') [] -> continue $ regenerateScreens j d $ stToggleReal st |         -- EvKey (KChar 'R') [] -> continue $ regenerateScreens j d $ stToggleReal ui | ||||||
|         EvKey KUp   [] -> continue $ regenerateScreens j d st{aScreen=s{tsTransaction=(iprev,tprev)}} |         EvKey KUp   [] -> continue $ regenerateScreens j d ui{aScreen=s{tsTransaction=(iprev,tprev)}} | ||||||
|         EvKey KDown [] -> continue $ regenerateScreens j d st{aScreen=s{tsTransaction=(inext,tnext)}} |         EvKey KDown [] -> continue $ regenerateScreens j d ui{aScreen=s{tsTransaction=(inext,tnext)}} | ||||||
|         EvKey KLeft [] -> continue st'' |         EvKey KLeft [] -> continue ui'' | ||||||
|           where |           where | ||||||
|             st'@AppState{aScreen=scr} = popScreen st |             ui'@UIState{aScreen=scr} = popScreen ui | ||||||
|             st'' = st'{aScreen=rsSelect (fromIntegral i) scr} |             ui'' = ui'{aScreen=rsSelect (fromIntegral i) scr} | ||||||
|         _ -> continue st |         _ -> continue ui | ||||||
| 
 | 
 | ||||||
| tsHandle _ _ = error "event handler called with wrong screen type, should not happen" | tsHandle _ _ = error "event handler called with wrong screen type, should not happen" | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										172
									
								
								hledger-ui/Hledger/UI/UIState.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								hledger-ui/Hledger/UI/UIState.hs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,172 @@ | |||||||
|  | {- | UIState operations. -} | ||||||
|  | 
 | ||||||
|  | {-# LANGUAGE OverloadedStrings #-} | ||||||
|  | {-# LANGUAGE RecordWildCards   #-} | ||||||
|  | 
 | ||||||
|  | module Hledger.UI.UIState | ||||||
|  | where | ||||||
|  | 
 | ||||||
|  | import Brick | ||||||
|  | import Brick.Widgets.Edit | ||||||
|  | import Data.List | ||||||
|  | import Data.Text.Zipper (gotoEOL) | ||||||
|  | import Data.Time.Calendar (Day) | ||||||
|  | 
 | ||||||
|  | import Hledger | ||||||
|  | import Hledger.Cli.CliOptions | ||||||
|  | import Hledger.UI.UITypes | ||||||
|  | import Hledger.UI.UIOptions | ||||||
|  | 
 | ||||||
|  | -- | Toggle between showing only cleared items or all items. | ||||||
|  | toggleCleared :: UIState -> UIState | ||||||
|  | toggleCleared ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=toggleCleared ropts}}} | ||||||
|  |   where | ||||||
|  |     toggleCleared ropts = ropts{cleared_=not $ cleared_ ropts, uncleared_=False, pending_=False} | ||||||
|  | 
 | ||||||
|  | -- | Toggle between showing only pending items or all items. | ||||||
|  | togglePending :: UIState -> UIState | ||||||
|  | togglePending ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=togglePending ropts}}} | ||||||
|  |   where | ||||||
|  |     togglePending ropts = ropts{pending_=not $ pending_ ropts, uncleared_=False, cleared_=False} | ||||||
|  | 
 | ||||||
|  | -- | Toggle between showing only uncleared items or all items. | ||||||
|  | toggleUncleared :: UIState -> UIState | ||||||
|  | toggleUncleared ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=toggleUncleared ropts}}} | ||||||
|  |   where | ||||||
|  |     toggleUncleared ropts = ropts{uncleared_=not $ uncleared_ ropts, cleared_=False, pending_=False} | ||||||
|  | 
 | ||||||
|  | -- | Toggle between showing all and showing only nonempty (more precisely, nonzero) items. | ||||||
|  | toggleEmpty :: UIState -> UIState | ||||||
|  | toggleEmpty ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=toggleEmpty ropts}}} | ||||||
|  |   where | ||||||
|  |     toggleEmpty ropts = ropts{empty_=not $ empty_ ropts} | ||||||
|  | 
 | ||||||
|  | -- | Toggle between flat and tree mode. If in the third "default" mode, go to flat mode. | ||||||
|  | toggleFlat :: UIState -> UIState | ||||||
|  | toggleFlat ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=toggleFlatMode ropts}}} | ||||||
|  |   where | ||||||
|  |     toggleFlatMode ropts@ReportOpts{accountlistmode_=ALFlat} = ropts{accountlistmode_=ALTree} | ||||||
|  |     toggleFlatMode ropts = ropts{accountlistmode_=ALFlat} | ||||||
|  | 
 | ||||||
|  | -- | Toggle between showing all and showing only real (non-virtual) items. | ||||||
|  | toggleReal :: UIState -> UIState | ||||||
|  | toggleReal ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=toggleReal ropts}}} | ||||||
|  |   where | ||||||
|  |     toggleReal ropts = ropts{real_=not $ real_ ropts} | ||||||
|  | 
 | ||||||
|  | -- | Apply a new filter query. | ||||||
|  | setFilter :: String -> UIState -> UIState | ||||||
|  | setFilter s ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{query_=s}}}} | ||||||
|  | 
 | ||||||
|  | -- | Clear all filter queries/flags. | ||||||
|  | resetFilter :: UIState -> UIState | ||||||
|  | resetFilter ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{ | ||||||
|  |      empty_=True | ||||||
|  |     ,cleared_=False | ||||||
|  |     ,pending_=False | ||||||
|  |     ,uncleared_=False | ||||||
|  |     ,real_=False | ||||||
|  |     ,query_="" | ||||||
|  |     }}}} | ||||||
|  | 
 | ||||||
|  | resetDepth :: UIState -> UIState | ||||||
|  | resetDepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = | ||||||
|  |   ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=Nothing}}}} | ||||||
|  | 
 | ||||||
|  | -- | Get the maximum account depth in the current journal. | ||||||
|  | maxDepth :: UIState -> Int | ||||||
|  | maxDepth UIState{ajournal=j} = maximum $ map accountNameLevel $ journalAccountNames j | ||||||
|  | 
 | ||||||
|  | -- | Decrement the current depth limit towards 0. If there was no depth limit, | ||||||
|  | -- set it to one less than the maximum account depth. | ||||||
|  | decDepth :: UIState -> UIState | ||||||
|  | decDepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{..}}}} | ||||||
|  |   = ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=dec depth_}}}} | ||||||
|  |   where | ||||||
|  |     dec (Just d) = Just $ max 0 (d-1) | ||||||
|  |     dec Nothing  = Just $ maxDepth ui - 1 | ||||||
|  | 
 | ||||||
|  | -- | Increment the current depth limit. If this makes it equal to the | ||||||
|  | -- the maximum account depth, remove the depth limit. | ||||||
|  | incDepth :: UIState -> UIState | ||||||
|  | incDepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{..}}}} | ||||||
|  |   = ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=inc depth_}}}} | ||||||
|  |   where | ||||||
|  |     inc (Just d) | d < (maxDepth ui - 1) = Just $ d+1 | ||||||
|  |     inc _ = Nothing | ||||||
|  | 
 | ||||||
|  | -- | Set the current depth limit to the specified depth, or remove the depth limit. | ||||||
|  | -- Also remove the depth limit if the specified depth is greater than the current | ||||||
|  | -- maximum account depth. If the specified depth is negative, reset the depth limit | ||||||
|  | -- to whatever was specified at uiartup. | ||||||
|  | setDepth :: Maybe Int -> UIState -> UIState | ||||||
|  | setDepth mdepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} | ||||||
|  |   = ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=mdepth'}}}} | ||||||
|  |   where | ||||||
|  |     mdepth' = case mdepth of | ||||||
|  |                 Nothing                   -> Nothing | ||||||
|  |                 Just d | d < 0            -> depth_ ropts | ||||||
|  |                        | d >= maxDepth ui -> Nothing | ||||||
|  |                        | otherwise        -> mdepth | ||||||
|  | 
 | ||||||
|  | -- | Open the minibuffer, setting its content to the current query with the cursor at the end. | ||||||
|  | showMinibuffer :: UIState -> UIState | ||||||
|  | showMinibuffer ui = setMode (Minibuffer e) ui | ||||||
|  |   where | ||||||
|  |     e = applyEdit gotoEOL $ editor "minibuffer" (str . unlines) (Just 1) oldq | ||||||
|  |     oldq = query_ $ reportopts_ $ cliopts_ $ aopts ui | ||||||
|  | 
 | ||||||
|  | -- | Close the minibuffer, discarding any edit in progress. | ||||||
|  | closeMinibuffer :: UIState -> UIState | ||||||
|  | closeMinibuffer = setMode Normal | ||||||
|  | 
 | ||||||
|  | setMode :: Mode -> UIState -> UIState | ||||||
|  | setMode m ui = ui{aMode=m} | ||||||
|  | 
 | ||||||
|  | -- | Regenerate the content for the current and previous screens, from a new journal and current date. | ||||||
|  | regenerateScreens :: Journal -> Day -> UIState -> UIState | ||||||
|  | regenerateScreens j d ui@UIState{aScreen=s,aPrevScreens=ss} = | ||||||
|  |   -- XXX clumsy due to entanglement of UIState and Screen. | ||||||
|  |   -- sInit operates only on an appstate's current screen, so | ||||||
|  |   -- remove all the screens from the appstate and then add them back | ||||||
|  |   -- one at a time, regenerating as we go. | ||||||
|  |   let | ||||||
|  |     first:rest = reverse $ s:ss :: [Screen] | ||||||
|  |     ui0 = ui{ajournal=j, aScreen=first, aPrevScreens=[]} :: UIState | ||||||
|  | 
 | ||||||
|  |     ui1 = (sInit first) d False ui0 :: UIState | ||||||
|  |     ui2 = foldl' (\ui s -> (sInit s) d False $ pushScreen s ui) ui1 rest :: UIState | ||||||
|  |   in | ||||||
|  |     ui2 | ||||||
|  | 
 | ||||||
|  | pushScreen :: Screen -> UIState -> UIState | ||||||
|  | pushScreen scr ui = ui{aPrevScreens=(aScreen ui:aPrevScreens ui) | ||||||
|  |                       ,aScreen=scr | ||||||
|  |                       } | ||||||
|  | 
 | ||||||
|  | popScreen :: UIState -> UIState | ||||||
|  | popScreen ui@UIState{aPrevScreens=s:ss} = ui{aScreen=s, aPrevScreens=ss} | ||||||
|  | popScreen ui = ui | ||||||
|  | 
 | ||||||
|  | resetScreens :: Day -> UIState -> UIState | ||||||
|  | resetScreens d ui@UIState{aScreen=s,aPrevScreens=ss} = | ||||||
|  |   (sInit topscreen) d True $ resetDepth $ resetFilter $ closeMinibuffer ui{aScreen=topscreen, aPrevScreens=[]} | ||||||
|  |   where | ||||||
|  |     topscreen = case ss of _:_ -> last ss | ||||||
|  |                            []  -> s | ||||||
|  | 
 | ||||||
|  | -- | Enter a new screen, saving the old screen & state in the | ||||||
|  | -- navigation history and initialising the new screen's state. | ||||||
|  | screenEnter :: Day -> Screen -> UIState -> UIState | ||||||
|  | screenEnter d scr ui = (sInit scr) d True $ | ||||||
|  |                        pushScreen scr | ||||||
|  |                        ui | ||||||
|  | 
 | ||||||
| @ -1,16 +1,16 @@ | |||||||
| {- | | {- | | ||||||
| Overview: | Overview: | ||||||
| hledger-ui's AppState holds the currently active screen and any previously visited | hledger-ui's UIState holds the currently active screen and any previously visited | ||||||
| screens (and their states). | screens (and their states). | ||||||
| The brick App delegates all event-handling and rendering | The brick App delegates all event-handling and rendering | ||||||
| to the AppState's active screen. | to the UIState's active screen. | ||||||
| Screens have their own screen state, render function, event handler, and app state | Screens have their own screen state, render function, event handler, and app state | ||||||
| update function, so they have full control. | update function, so they have full control. | ||||||
| 
 | 
 | ||||||
| @ | @ | ||||||
| Brick.defaultMain brickapp st | Brick.defaultMain brickapp st | ||||||
|   where |   where | ||||||
|     brickapp :: App (AppState) V.Event |     brickapp :: App (UIState) V.Event | ||||||
|     brickapp = App { |     brickapp = App { | ||||||
|         appLiftVtyEvent = id |         appLiftVtyEvent = id | ||||||
|       , appStartEvent   = return |       , appStartEvent   = return | ||||||
| @ -19,9 +19,9 @@ Brick.defaultMain brickapp st | |||||||
|       , appHandleEvent  = \st ev -> sHandle (aScreen st) st ev |       , appHandleEvent  = \st ev -> sHandle (aScreen st) st ev | ||||||
|       , appDraw         = \st    -> sDraw   (aScreen st) st |       , appDraw         = \st    -> sDraw   (aScreen st) st | ||||||
|       } |       } | ||||||
|     st :: AppState |     st :: UIState | ||||||
|     st = (sInit s) d |     st = (sInit s) d | ||||||
|          AppState{ |          UIState{ | ||||||
|             aopts=uopts' |             aopts=uopts' | ||||||
|            ,ajournal=j |            ,ajournal=j | ||||||
|            ,aScreen=s |            ,aScreen=s | ||||||
| @ -40,7 +40,7 @@ module Hledger.UI.UITypes where | |||||||
| 
 | 
 | ||||||
| import Data.Monoid | import Data.Monoid | ||||||
| import Data.Time.Calendar (Day) | import Data.Time.Calendar (Day) | ||||||
| import qualified Graphics.Vty as Vty | import Graphics.Vty | ||||||
| import Brick | import Brick | ||||||
| import Brick.Widgets.List | import Brick.Widgets.List | ||||||
| import Brick.Widgets.Edit (Editor) | import Brick.Widgets.Edit (Editor) | ||||||
| @ -59,12 +59,12 @@ instance Show Editor   where show _ = "<Editor>" | |||||||
| -- As you navigate through screens, the old ones are saved in a stack. | -- As you navigate through screens, the old ones are saved in a stack. | ||||||
| -- The app can be in one of several modes: normal screen operation, | -- The app can be in one of several modes: normal screen operation, | ||||||
| -- showing a help dialog, entering data in the minibuffer etc. | -- showing a help dialog, entering data in the minibuffer etc. | ||||||
| data AppState = AppState { | data UIState = UIState { | ||||||
|    aopts        :: UIOpts       -- ^ the command-line options and query arguments currently in effect |    aopts        :: UIOpts    -- ^ the command-line options and query arguments currently in effect | ||||||
|   ,ajournal     :: Journal      -- ^ the journal being viewed |   ,ajournal     :: Journal   -- ^ the journal being viewed | ||||||
|   ,aPrevScreens :: [Screen]     -- ^ previously visited screens, most recent first |   ,aPrevScreens :: [Screen]  -- ^ previously visited screens, most recent first | ||||||
|   ,aScreen      :: Screen       -- ^ the currently active screen |   ,aScreen      :: Screen    -- ^ the currently active screen | ||||||
|   ,aMode        :: Mode         -- ^ the currently active mode |   ,aMode        :: Mode      -- ^ the currently active mode | ||||||
|   } deriving (Show) |   } deriving (Show) | ||||||
| 
 | 
 | ||||||
| -- | The mode modifies the screen's rendering and event handling. | -- | The mode modifies the screen's rendering and event handling. | ||||||
| @ -86,34 +86,34 @@ instance Eq Editor where _ == _ = True | |||||||
| -- cases need to be handled, and also that their lenses are traversals, not single-value getters. | -- cases need to be handled, and also that their lenses are traversals, not single-value getters. | ||||||
| data Screen = | data Screen = | ||||||
|     AccountsScreen { |     AccountsScreen { | ||||||
|        sInit   :: Day -> Bool -> AppState -> AppState              -- ^ function to initialise or update this screen's state |        sInit   :: Day -> Bool -> UIState -> UIState              -- ^ function to initialise or update this screen's state | ||||||
|       ,sDraw   :: AppState -> [Widget]                             -- ^ brick renderer for this screen |       ,sDraw   :: UIState -> [Widget]                             -- ^ brick renderer for this screen | ||||||
|       ,sHandle :: AppState -> Vty.Event -> EventM (Next AppState)  -- ^ brick event handler for this screen |       ,sHandle :: UIState -> Event -> EventM (Next UIState)  -- ^ brick event handler for this screen | ||||||
|       -- state fields.These ones have lenses: |       -- state fields.These ones have lenses: | ||||||
|       ,_asList            :: List AccountsScreenItem  -- ^ list widget showing account names & balances |       ,_asList            :: List AccountsScreenItem  -- ^ list widget showing account names & balances | ||||||
|       ,_asSelectedAccount :: AccountName              -- ^ a backup of the account name from the list widget's selected item (or "") |       ,_asSelectedAccount :: AccountName              -- ^ a backup of the account name from the list widget's selected item (or "") | ||||||
|     } |     } | ||||||
|   | RegisterScreen { |   | RegisterScreen { | ||||||
|        sInit   :: Day -> Bool -> AppState -> AppState |        sInit   :: Day -> Bool -> UIState -> UIState | ||||||
|       ,sDraw   :: AppState -> [Widget] |       ,sDraw   :: UIState -> [Widget] | ||||||
|       ,sHandle :: AppState -> Vty.Event -> EventM (Next AppState) |       ,sHandle :: UIState -> Event -> EventM (Next UIState) | ||||||
|       -- |       -- | ||||||
|       ,rsList    :: List RegisterScreenItem           -- ^ list widget showing transactions affecting this account |       ,rsList    :: List RegisterScreenItem           -- ^ list widget showing transactions affecting this account | ||||||
|       ,rsAccount :: AccountName                       -- ^ the account this register is for |       ,rsAccount :: AccountName                       -- ^ the account this register is for | ||||||
|     } |     } | ||||||
|   | TransactionScreen { |   | TransactionScreen { | ||||||
|        sInit   :: Day -> Bool -> AppState -> AppState |        sInit   :: Day -> Bool -> UIState -> UIState | ||||||
|       ,sDraw   :: AppState -> [Widget] |       ,sDraw   :: UIState -> [Widget] | ||||||
|       ,sHandle :: AppState -> Vty.Event -> EventM (Next AppState) |       ,sHandle :: UIState -> Event -> EventM (Next UIState) | ||||||
|       -- |       -- | ||||||
|       ,tsTransaction  :: NumberedTransaction          -- ^ the transaction we are currently viewing, and its position in the list |       ,tsTransaction  :: NumberedTransaction          -- ^ the transaction we are currently viewing, and its position in the list | ||||||
|       ,tsTransactions :: [NumberedTransaction]        -- ^ list of transactions we can step through |       ,tsTransactions :: [NumberedTransaction]        -- ^ list of transactions we can step through | ||||||
|       ,tsAccount      :: AccountName                  -- ^ the account whose register we entered this screen from |       ,tsAccount      :: AccountName                  -- ^ the account whose register we entered this screen from | ||||||
|     } |     } | ||||||
|   | ErrorScreen { |   | ErrorScreen { | ||||||
|        sInit   :: Day -> Bool -> AppState -> AppState |        sInit   :: Day -> Bool -> UIState -> UIState | ||||||
|       ,sDraw   :: AppState -> [Widget] |       ,sDraw   :: UIState -> [Widget] | ||||||
|       ,sHandle :: AppState -> Vty.Event -> EventM (Next AppState) |       ,sHandle :: UIState -> Event -> EventM (Next UIState) | ||||||
|       -- |       -- | ||||||
|       ,esError :: String                              -- ^ error message to show |       ,esError :: String                              -- ^ error message to show | ||||||
|     } |     } | ||||||
| @ -139,7 +139,7 @@ data RegisterScreenItem = RegisterScreenItem { | |||||||
| 
 | 
 | ||||||
| type NumberedTransaction = (Integer, Transaction) | type NumberedTransaction = (Integer, Transaction) | ||||||
| 
 | 
 | ||||||
| -- dummy monoid instance needed for lenses for now since the List fields are not common across constructors | -- dummy monoid instance needed make lenses work with List fields not common across constructors | ||||||
| instance Monoid (List a) | instance Monoid (List a) | ||||||
|   where |   where | ||||||
|     mempty        = list "" V.empty 1 |     mempty        = list "" V.empty 1 | ||||||
|  | |||||||
| @ -1,212 +1,26 @@ | |||||||
|  | {- | Rendering & misc. helpers. -} | ||||||
|  | 
 | ||||||
| {-# LANGUAGE OverloadedStrings #-} | {-# LANGUAGE OverloadedStrings #-} | ||||||
| {-# LANGUAGE RecordWildCards   #-} |  | ||||||
| 
 | 
 | ||||||
| module Hledger.UI.UIUtils | module Hledger.UI.UIUtils | ||||||
| --   ( | where | ||||||
| --   pushScreen |  | ||||||
| --  ,popScreen |  | ||||||
| --  ,resetScreens |  | ||||||
| --  ,screenEnter |  | ||||||
| --  ,regenerateScreens |  | ||||||
| --  ,getViewportSize |  | ||||||
| --  -- ,margin |  | ||||||
| --  ,withBorderAttr |  | ||||||
| --  ,topBottomBorderWithLabel |  | ||||||
| --  ,topBottomBorderWithLabels |  | ||||||
| --  ,defaultLayout |  | ||||||
| --  ,borderQueryStr |  | ||||||
| --  ,borderDepthStr |  | ||||||
| --  ,borderKeysStr |  | ||||||
| --  ,minibuffer |  | ||||||
| --  -- |  | ||||||
| --  ,stToggleCleared |  | ||||||
| --  ,stTogglePending |  | ||||||
| --  ,stToggleUncleared |  | ||||||
| --  ,stToggleEmpty |  | ||||||
| --  ,stToggleFlat |  | ||||||
| --  ,stToggleReal |  | ||||||
| --  ,stFilter |  | ||||||
| --  ,stResetFilter |  | ||||||
| --  ,stShowMinibuffer |  | ||||||
| --  ,stCloseMinibuffer |  | ||||||
| --  ) |  | ||||||
|   where |  | ||||||
| 
 | 
 | ||||||
| import Lens.Micro.Platform ((^.)) |  | ||||||
| -- import Control.Monad |  | ||||||
| -- import Control.Monad.IO.Class |  | ||||||
| -- import Data.Default |  | ||||||
| import Data.List |  | ||||||
| import Data.Monoid |  | ||||||
| import Data.Text.Zipper (gotoEOL) |  | ||||||
| import Data.Time.Calendar (Day) |  | ||||||
| import Brick | import Brick | ||||||
| import Brick.Widgets.Dialog |  | ||||||
| -- import Brick.Widgets.List |  | ||||||
| import Brick.Widgets.Edit |  | ||||||
| import Brick.Widgets.Border | import Brick.Widgets.Border | ||||||
| import Brick.Widgets.Border.Style | import Brick.Widgets.Border.Style | ||||||
| import Graphics.Vty as Vty | import Brick.Widgets.Dialog | ||||||
|  | import Brick.Widgets.Edit | ||||||
|  | import Data.List | ||||||
|  | import Data.Monoid | ||||||
|  | import Graphics.Vty | ||||||
|  | import Lens.Micro.Platform | ||||||
| 
 | 
 | ||||||
| import Hledger | import Hledger | ||||||
| import Hledger.Cli.CliOptions |  | ||||||
| import Hledger.UI.UITypes | import Hledger.UI.UITypes | ||||||
| import Hledger.UI.UIOptions | import Hledger.UI.UIState | ||||||
| 
 |  | ||||||
| -- | Toggle between showing only cleared items or all items. |  | ||||||
| stToggleCleared :: AppState -> AppState |  | ||||||
| stToggleCleared st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=toggleCleared ropts}}} |  | ||||||
|   where |  | ||||||
|     toggleCleared ropts = ropts{cleared_=not $ cleared_ ropts, uncleared_=False, pending_=False} |  | ||||||
| 
 |  | ||||||
| -- | Toggle between showing only pending items or all items. |  | ||||||
| stTogglePending :: AppState -> AppState |  | ||||||
| stTogglePending st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=togglePending ropts}}} |  | ||||||
|   where |  | ||||||
|     togglePending ropts = ropts{pending_=not $ pending_ ropts, uncleared_=False, cleared_=False} |  | ||||||
| 
 |  | ||||||
| -- | Toggle between showing only uncleared items or all items. |  | ||||||
| stToggleUncleared :: AppState -> AppState |  | ||||||
| stToggleUncleared st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=toggleUncleared ropts}}} |  | ||||||
|   where |  | ||||||
|     toggleUncleared ropts = ropts{uncleared_=not $ uncleared_ ropts, cleared_=False, pending_=False} |  | ||||||
| 
 |  | ||||||
| -- | Toggle between showing all and showing only nonempty (more precisely, nonzero) items. |  | ||||||
| stToggleEmpty :: AppState -> AppState |  | ||||||
| stToggleEmpty st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=toggleEmpty ropts}}} |  | ||||||
|   where |  | ||||||
|     toggleEmpty ropts = ropts{empty_=not $ empty_ ropts} |  | ||||||
| 
 |  | ||||||
| -- | Toggle between flat and tree mode. If in the third "default" mode, go to flat mode. |  | ||||||
| stToggleFlat :: AppState -> AppState |  | ||||||
| stToggleFlat st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=toggleFlatMode ropts}}} |  | ||||||
|   where |  | ||||||
|     toggleFlatMode ropts@ReportOpts{accountlistmode_=ALFlat} = ropts{accountlistmode_=ALTree} |  | ||||||
|     toggleFlatMode ropts = ropts{accountlistmode_=ALFlat} |  | ||||||
| 
 |  | ||||||
| -- | Toggle between showing all and showing only real (non-virtual) items. |  | ||||||
| stToggleReal :: AppState -> AppState |  | ||||||
| stToggleReal st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=toggleReal ropts}}} |  | ||||||
|   where |  | ||||||
|     toggleReal ropts = ropts{real_=not $ real_ ropts} |  | ||||||
| 
 |  | ||||||
| -- | Apply a new filter query. |  | ||||||
| stFilter :: String -> AppState -> AppState |  | ||||||
| stFilter s st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=ropts{query_=s}}}} |  | ||||||
| 
 |  | ||||||
| -- | Clear all filter queries/flags. |  | ||||||
| stResetFilter :: AppState -> AppState |  | ||||||
| stResetFilter st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=ropts{ |  | ||||||
|      empty_=True |  | ||||||
|     ,cleared_=False |  | ||||||
|     ,pending_=False |  | ||||||
|     ,uncleared_=False |  | ||||||
|     ,real_=False |  | ||||||
|     ,query_="" |  | ||||||
|     }}}} |  | ||||||
| 
 |  | ||||||
| resetDepth :: AppState -> AppState |  | ||||||
| resetDepth st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} = |  | ||||||
|   st{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=Nothing}}}} |  | ||||||
| 
 |  | ||||||
| -- | Get the maximum account depth in the current journal. |  | ||||||
| maxDepth :: AppState -> Int |  | ||||||
| maxDepth AppState{ajournal=j} = maximum $ map accountNameLevel $ journalAccountNames j |  | ||||||
| 
 |  | ||||||
| -- | Decrement the current depth limit towards 0. If there was no depth limit, |  | ||||||
| -- set it to one less than the maximum account depth. |  | ||||||
| decDepth :: AppState -> AppState |  | ||||||
| decDepth st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{..}}}} |  | ||||||
|   = st{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=dec depth_}}}} |  | ||||||
|   where |  | ||||||
|     dec (Just d) = Just $ max 0 (d-1) |  | ||||||
|     dec Nothing  = Just $ maxDepth st - 1 |  | ||||||
| 
 |  | ||||||
| -- | Increment the current depth limit. If this makes it equal to the |  | ||||||
| -- the maximum account depth, remove the depth limit. |  | ||||||
| incDepth :: AppState -> AppState |  | ||||||
| incDepth st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{..}}}} |  | ||||||
|   = st{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=inc depth_}}}} |  | ||||||
|   where |  | ||||||
|     inc (Just d) | d < (maxDepth st - 1) = Just $ d+1 |  | ||||||
|     inc _ = Nothing |  | ||||||
| 
 |  | ||||||
| -- | Set the current depth limit to the specified depth, or remove the depth limit. |  | ||||||
| -- Also remove the depth limit if the specified depth is greater than the current |  | ||||||
| -- maximum account depth. If the specified depth is negative, reset the depth limit |  | ||||||
| -- to whatever was specified at startup. |  | ||||||
| setDepth :: Maybe Int -> AppState -> AppState |  | ||||||
| setDepth mdepth st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} |  | ||||||
|   = st{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=mdepth'}}}} |  | ||||||
|   where |  | ||||||
|     mdepth' = case mdepth of |  | ||||||
|                 Nothing                   -> Nothing |  | ||||||
|                 Just d | d < 0            -> depth_ ropts |  | ||||||
|                        | d >= maxDepth st -> Nothing |  | ||||||
|                        | otherwise        -> mdepth |  | ||||||
| 
 |  | ||||||
| -- | Open the minibuffer, setting its content to the current query with the cursor at the end. |  | ||||||
| stShowMinibuffer st = setMode (Minibuffer e) st |  | ||||||
|   where |  | ||||||
|     e = applyEdit gotoEOL $ editor "minibuffer" (str . unlines) (Just 1) oldq |  | ||||||
|     oldq = query_ $ reportopts_ $ cliopts_ $ aopts st |  | ||||||
| 
 |  | ||||||
| -- | Close the minibuffer, discarding any edit in progress. |  | ||||||
| stCloseMinibuffer = setMode Normal |  | ||||||
| 
 |  | ||||||
| setMode :: Mode -> AppState -> AppState |  | ||||||
| setMode m st = st{aMode=m} |  | ||||||
| 
 |  | ||||||
| -- | Regenerate the content for the current and previous screens, from a new journal and current date. |  | ||||||
| regenerateScreens :: Journal -> Day -> AppState -> AppState |  | ||||||
| regenerateScreens j d st@AppState{aScreen=s,aPrevScreens=ss} = |  | ||||||
|   -- XXX clumsy due to entanglement of AppState and Screen. |  | ||||||
|   -- sInit operates only on an appstate's current screen, so |  | ||||||
|   -- remove all the screens from the appstate and then add them back |  | ||||||
|   -- one at a time, regenerating as we go. |  | ||||||
|   let |  | ||||||
|     first:rest = reverse $ s:ss :: [Screen] |  | ||||||
|     st0 = st{ajournal=j, aScreen=first, aPrevScreens=[]} :: AppState |  | ||||||
|     st1 = (sInit first) d False st0 :: AppState |  | ||||||
|     st2 = foldl' (\st s -> (sInit s) d False $ pushScreen s st) st1 rest :: AppState |  | ||||||
|   in |  | ||||||
|     st2 |  | ||||||
| 
 |  | ||||||
| pushScreen :: Screen -> AppState -> AppState |  | ||||||
| pushScreen scr st = st{aPrevScreens=(aScreen st:aPrevScreens st) |  | ||||||
|                       ,aScreen=scr |  | ||||||
|                       } |  | ||||||
| 
 |  | ||||||
| popScreen :: AppState -> AppState |  | ||||||
| popScreen st@AppState{aPrevScreens=s:ss} = st{aScreen=s, aPrevScreens=ss} |  | ||||||
| popScreen st = st |  | ||||||
| 
 |  | ||||||
| resetScreens :: Day -> AppState -> AppState |  | ||||||
| resetScreens d st@AppState{aScreen=s,aPrevScreens=ss} = |  | ||||||
|   (sInit topscreen) d True $ resetDepth $ stResetFilter $ stCloseMinibuffer st{aScreen=topscreen, aPrevScreens=[]} |  | ||||||
|   where |  | ||||||
|     topscreen = case ss of _:_ -> last ss |  | ||||||
|                            []  -> s |  | ||||||
| 
 |  | ||||||
| -- clearScreens :: AppState -> AppState |  | ||||||
| -- clearScreens st = st{aPrevScreens=[]} |  | ||||||
| 
 |  | ||||||
| -- | Enter a new screen, saving the old screen & state in the |  | ||||||
| -- navigation history and initialising the new screen's state. |  | ||||||
| screenEnter :: Day -> Screen -> AppState -> AppState |  | ||||||
| screenEnter d scr st = (sInit scr) d True $ |  | ||||||
|                        pushScreen scr |  | ||||||
|                        st |  | ||||||
| 
 | 
 | ||||||
| -- | Draw the help dialog, called when help mode is active. | -- | Draw the help dialog, called when help mode is active. | ||||||
|  | helpDialog :: Widget | ||||||
| helpDialog = | helpDialog = | ||||||
|   Widget Fixed Fixed $ do |   Widget Fixed Fixed $ do | ||||||
|     c <- getContext |     c <- getContext | ||||||
| @ -251,22 +65,21 @@ helpDialog = | |||||||
|     renderKey (key,desc) = withAttr (borderAttr <> "keys") (str key) <+> str " " <+> str desc |     renderKey (key,desc) = withAttr (borderAttr <> "keys") (str key) <+> str " " <+> str desc | ||||||
| 
 | 
 | ||||||
| -- | Event handler used when help mode is active. | -- | Event handler used when help mode is active. | ||||||
| helpHandle st ev = | helpHandle :: UIState -> Event -> EventM (Next UIState) | ||||||
|  | helpHandle ui ev = | ||||||
|   case ev of |   case ev of | ||||||
|     EvKey k [] | k `elem` [KEsc, KChar 'h'] -> continue $ setMode Normal st |     EvKey k [] | k `elem` [KEsc, KChar 'h'] -> continue $ setMode Normal ui | ||||||
|     _ -> continue st |     _ -> continue ui | ||||||
| 
 | 
 | ||||||
| -- | In the EventM monad, get the named current viewport's width and height, | -- | Draw the minibuffer. | ||||||
| -- or (0,0) if the named viewport is not found. | minibuffer :: Editor -> Widget | ||||||
| getViewportSize :: Name -> EventM (Int,Int) | minibuffer ed = | ||||||
| getViewportSize name = do |   forceAttr (borderAttr <> "minibuffer") $ | ||||||
|   mvp <- lookupViewport name |   hBox $ | ||||||
|   let (w,h) = case mvp of |   [txt "filter: ", renderEditor ed] | ||||||
|         Just vp -> vp ^. vpSize |  | ||||||
|         Nothing -> (0,0) |  | ||||||
|   -- liftIO $ putStrLn $ show (w,h) |  | ||||||
|   return (w,h) |  | ||||||
| 
 | 
 | ||||||
|  | -- | Wrap a widget in the default hledger-ui screen layout. | ||||||
|  | defaultLayout :: Widget -> Widget -> Widget -> Widget | ||||||
| defaultLayout toplabel bottomlabel = | defaultLayout toplabel bottomlabel = | ||||||
|   topBottomBorderWithLabels (str " "<+>toplabel<+>str " ") (str " "<+>bottomlabel<+>str " ") . |   topBottomBorderWithLabels (str " "<+>toplabel<+>str " ") (str " "<+>bottomlabel<+>str " ") . | ||||||
|   margin 1 0 Nothing |   margin 1 0 Nothing | ||||||
| @ -274,6 +87,26 @@ defaultLayout toplabel bottomlabel = | |||||||
|   -- padLeftRight 1 -- XXX should reduce inner widget's width by 2, but doesn't |   -- padLeftRight 1 -- XXX should reduce inner widget's width by 2, but doesn't | ||||||
|                     -- "the layout adjusts... if you use the core combinators" |                     -- "the layout adjusts... if you use the core combinators" | ||||||
| 
 | 
 | ||||||
|  | borderQueryStr :: String -> Widget | ||||||
|  | borderQueryStr ""  = str "" | ||||||
|  | borderQueryStr qry = str " matching " <+> withAttr (borderAttr <> "query") (str qry) | ||||||
|  | 
 | ||||||
|  | borderDepthStr :: Maybe Int -> Widget | ||||||
|  | borderDepthStr Nothing  = str "" | ||||||
|  | borderDepthStr (Just d) = str " to " <+> withAttr (borderAttr <> "depth") (str $ "depth "++show d) | ||||||
|  | 
 | ||||||
|  | borderKeysStr :: [(String,String)] -> Widget | ||||||
|  | borderKeysStr keydescs = | ||||||
|  |   hBox $ | ||||||
|  |   intersperse sep $ | ||||||
|  |   [withAttr (borderAttr <> "keys") (str keys) <+> str ":" <+> str desc | (keys, desc) <- keydescs] | ||||||
|  |   where | ||||||
|  |     -- sep = str " | " | ||||||
|  |     sep = str " " | ||||||
|  | 
 | ||||||
|  | -- generic | ||||||
|  | 
 | ||||||
|  | topBottomBorderWithLabel :: Widget -> Widget -> Widget | ||||||
| topBottomBorderWithLabel label = \wrapped -> | topBottomBorderWithLabel label = \wrapped -> | ||||||
|   Widget Greedy Greedy $ do |   Widget Greedy Greedy $ do | ||||||
|     c <- getContext |     c <- getContext | ||||||
| @ -290,6 +123,7 @@ topBottomBorderWithLabel label = \wrapped -> | |||||||
|       <=> |       <=> | ||||||
|       hBorder |       hBorder | ||||||
| 
 | 
 | ||||||
|  | topBottomBorderWithLabels :: Widget -> Widget -> Widget -> Widget | ||||||
| topBottomBorderWithLabels toplabel bottomlabel = \wrapped -> | topBottomBorderWithLabels toplabel bottomlabel = \wrapped -> | ||||||
|   Widget Greedy Greedy $ do |   Widget Greedy Greedy $ do | ||||||
|     c <- getContext |     c <- getContext | ||||||
| @ -307,6 +141,7 @@ topBottomBorderWithLabels toplabel bottomlabel = \wrapped -> | |||||||
|       hBorderWithLabel bottomlabel |       hBorderWithLabel bottomlabel | ||||||
| 
 | 
 | ||||||
| -- XXX should be equivalent to the above, but isn't (page down goes offscreen) | -- XXX should be equivalent to the above, but isn't (page down goes offscreen) | ||||||
|  | _topBottomBorderWithLabel2 :: Widget -> Widget -> Widget | ||||||
| _topBottomBorderWithLabel2 label = \wrapped -> | _topBottomBorderWithLabel2 label = \wrapped -> | ||||||
|  let debugmsg = "" |  let debugmsg = "" | ||||||
|  in hBorderWithLabel (label <+> str debugmsg) |  in hBorderWithLabel (label <+> str debugmsg) | ||||||
| @ -340,33 +175,6 @@ margin h v mcolour = \w -> | |||||||
|    -- withBorderStyle (borderStyleFromChar ' ') . |    -- withBorderStyle (borderStyleFromChar ' ') . | ||||||
|    -- applyN n border |    -- applyN n border | ||||||
| 
 | 
 | ||||||
|  | withBorderAttr :: Attr -> Widget -> Widget | ||||||
| withBorderAttr attr = updateAttrMap (applyAttrMappings [(borderAttr, attr)]) | withBorderAttr attr = updateAttrMap (applyAttrMappings [(borderAttr, attr)]) | ||||||
| 
 | 
 | ||||||
| -- _ui = vCenter $ vBox [ hCenter box |  | ||||||
| --                       , str " " |  | ||||||
| --                       , hCenter $ str "Press Esc to exit." |  | ||||||
| --                       ] |  | ||||||
| 
 |  | ||||||
| borderQueryStr :: String -> Widget |  | ||||||
| borderQueryStr ""  = str "" |  | ||||||
| borderQueryStr qry = str " matching " <+> withAttr (borderAttr <> "query") (str qry) |  | ||||||
| 
 |  | ||||||
| borderDepthStr :: Maybe Int -> Widget |  | ||||||
| borderDepthStr Nothing  = str "" |  | ||||||
| borderDepthStr (Just d) = str " to " <+> withAttr (borderAttr <> "depth") (str $ "depth "++show d) |  | ||||||
| 
 |  | ||||||
| borderKeysStr :: [(String,String)] -> Widget |  | ||||||
| borderKeysStr keydescs = |  | ||||||
|   hBox $ |  | ||||||
|   intersperse sep $ |  | ||||||
|   [withAttr (borderAttr <> "keys") (str keys) <+> str ":" <+> str desc | (keys, desc) <- keydescs] |  | ||||||
|   where |  | ||||||
|     -- sep = str " | " |  | ||||||
|     sep = str " " |  | ||||||
| 
 |  | ||||||
| minibuffer :: Editor -> Widget |  | ||||||
| minibuffer ed = |  | ||||||
|   forceAttr (borderAttr <> "minibuffer") $ |  | ||||||
|   hBox $ |  | ||||||
|   [txt "filter: ", renderEditor ed] |  | ||||||
| 
 |  | ||||||
|  | |||||||
| @ -92,6 +92,7 @@ executable hledger-ui | |||||||
|       Hledger.UI.Main |       Hledger.UI.Main | ||||||
|       Hledger.UI.UIOptions |       Hledger.UI.UIOptions | ||||||
|       Hledger.UI.Theme |       Hledger.UI.Theme | ||||||
|  |       Hledger.UI.UIState | ||||||
|       Hledger.UI.UITypes |       Hledger.UI.UITypes | ||||||
|       Hledger.UI.UIUtils |       Hledger.UI.UIUtils | ||||||
|       Hledger.UI.AccountsScreen |       Hledger.UI.AccountsScreen | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user