ui: misc enhancements, allow depth adjustment
- clean up options a bit, enable -V/--value (affects the accounts screen) - more informative top/bottom borders, including key help - number keys adjust the depth limit (accounts screen) - remove obsolete args parameter
This commit is contained in:
parent
d662df77f5
commit
b51f45c675
@ -1,6 +1,7 @@
|
||||
-- The accounts screen, showing accounts and balances like the CLI balance command.
|
||||
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
|
||||
module Hledger.UI.AccountsScreen
|
||||
(screen)
|
||||
@ -11,6 +12,7 @@ import Control.Lens ((^.))
|
||||
import Control.Monad.IO.Class
|
||||
-- import Data.Default
|
||||
import Data.List
|
||||
import Data.Maybe
|
||||
import Data.Monoid
|
||||
import Data.Time.Calendar (Day)
|
||||
import System.FilePath (takeFileName)
|
||||
@ -32,35 +34,52 @@ import qualified Hledger.UI.RegisterScreen2 as RS2 (screen)
|
||||
|
||||
screen = AccountsScreen{
|
||||
asState = list "accounts" V.empty 1
|
||||
,sInitFn = initAccountsScreen
|
||||
,sInitFn = initAccountsScreen Nothing
|
||||
,sDrawFn = drawAccountsScreen
|
||||
,sHandleFn = handleAccountsScreen
|
||||
}
|
||||
|
||||
initAccountsScreen :: Day -> [String] -> AppState -> AppState
|
||||
initAccountsScreen d args st@AppState{aopts=opts, ajournal=j, aScreen=s@AccountsScreen{}} =
|
||||
st{aScreen=s{asState=is'}}
|
||||
initAccountsScreen :: Maybe AccountName -> Day -> AppState -> AppState
|
||||
initAccountsScreen mselacct d st@AppState{aopts=opts, aargs=args, ajournal=j, aScreen=s@AccountsScreen{}} =
|
||||
st{aScreen=s{asState=is''}}
|
||||
where
|
||||
is' = list (Name "accounts") (V.fromList items) 1
|
||||
(items,_total) = balanceReport ropts q j
|
||||
-- crazy hacks dept.
|
||||
-- when we're adjusting depth, mselacct is the account that was selected previously,
|
||||
-- in which case try and keep the selection near where it was
|
||||
is'' = case mselacct of
|
||||
Nothing -> is'
|
||||
Just a -> -- vScrollToBeginning $ viewportScroll "accounts"
|
||||
maybe is' (flip listMoveTo is') mi
|
||||
where
|
||||
mi = findIndex (\((acct,_,_),_) -> acct==a') items
|
||||
a' = maybe a (flip clipAccountName a) $ depth_ ropts
|
||||
|
||||
q = queryFromOpts d ropts
|
||||
-- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items
|
||||
--{query_=unwords' $ locArgs l}
|
||||
ropts = (reportopts_ cliopts)
|
||||
{
|
||||
query_=unwords' args,
|
||||
balancetype_=HistoricalBalance -- XXX balanceReport doesn't respect this yet
|
||||
}
|
||||
cliopts = cliopts_ opts
|
||||
convert | value_ ropts = balanceReportValue j valuedate
|
||||
| otherwise = id
|
||||
where
|
||||
q = queryFromOpts d ropts
|
||||
-- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items
|
||||
--{query_=unwords' $ locArgs l}
|
||||
ropts = (reportopts_ cliopts)
|
||||
{
|
||||
query_=unwords' args,
|
||||
balancetype_=HistoricalBalance -- XXX balanceReport doesn't respect this yet
|
||||
}
|
||||
cliopts = cliopts_ opts
|
||||
valuedate = fromMaybe d $ queryEndDate False q
|
||||
|
||||
(items,_total) = convert $ balanceReport ropts q j
|
||||
|
||||
initAccountsScreen _ _ _ = error "init function called with wrong screen type, should not happen"
|
||||
|
||||
drawAccountsScreen :: AppState -> [Widget]
|
||||
drawAccountsScreen st@AppState{aopts=uopts, ajournal=j, aScreen=AccountsScreen{asState=is}} = [ui]
|
||||
where
|
||||
label = files
|
||||
toplabel = files
|
||||
<+> str " accounts"
|
||||
<+> borderQuery querystr
|
||||
<+> borderQueryStr querystr
|
||||
<+> borderDepthStr depth
|
||||
<+> str " ("
|
||||
<+> cur
|
||||
<+> str " of "
|
||||
@ -72,6 +91,7 @@ drawAccountsScreen st@AppState{aopts=uopts, ajournal=j, aScreen=AccountsScreen{a
|
||||
[f,_] -> (withAttr ("border" <> "bold") $ str $ takeFileName f) <+> str " (& 1 included file)"
|
||||
f:fs -> (withAttr ("border" <> "bold") $ str $ takeFileName f) <+> str (" (& " ++ show (length fs) ++ " included files)")
|
||||
querystr = query_ $ reportopts_ $ cliopts_ uopts
|
||||
depth = depth_ $ reportopts_ $ cliopts_ uopts
|
||||
cur = str (case is^.listSelectedL of
|
||||
Nothing -> "-"
|
||||
Just i -> show (i + 1))
|
||||
@ -91,7 +111,14 @@ drawAccountsScreen st@AppState{aopts=uopts, ajournal=j, aScreen=AccountsScreen{a
|
||||
, FormatField False (Just 40) Nothing TotalField
|
||||
]
|
||||
|
||||
ui = defaultLayout label $ renderList is (drawAccountsItem fmt)
|
||||
bottomlabel = borderKeysStr [
|
||||
-- "up/down/pgup/pgdown/home/end: move"
|
||||
"1-0: adjust depth limit"
|
||||
,"right: show transactions"
|
||||
,"q: quit"
|
||||
]
|
||||
|
||||
ui = defaultLayout toplabel bottomlabel $ renderList is (drawAccountsItem fmt)
|
||||
|
||||
drawAccountsScreen _ = error "draw function called with wrong screen type, should not happen"
|
||||
|
||||
@ -109,18 +136,28 @@ handleAccountsScreen st@AppState{aargs=args, aScreen=scr@AccountsScreen{asState=
|
||||
-- c <- getContext
|
||||
-- let h = c^.availHeightL
|
||||
-- moveSel n l = listMoveBy n l
|
||||
let
|
||||
acct = case listSelectedElement is of
|
||||
Just (_, ((a, _, _), _)) -> a
|
||||
Nothing -> ""
|
||||
case e of
|
||||
Vty.EvKey Vty.KEsc [] -> halt st
|
||||
Vty.EvKey (Vty.KChar 'q') [] -> halt st
|
||||
Vty.EvKey (Vty.KChar '0') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 0 st
|
||||
Vty.EvKey (Vty.KChar '1') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 1 st
|
||||
Vty.EvKey (Vty.KChar '2') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 2 st
|
||||
Vty.EvKey (Vty.KChar '3') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 3 st
|
||||
Vty.EvKey (Vty.KChar '4') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 4 st
|
||||
Vty.EvKey (Vty.KChar '5') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 5 st
|
||||
Vty.EvKey (Vty.KChar '6') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 6 st
|
||||
Vty.EvKey (Vty.KChar '7') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 7 st
|
||||
Vty.EvKey (Vty.KChar '8') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 8 st
|
||||
Vty.EvKey (Vty.KChar '9') [] -> continue $ initAccountsScreen (Just acct) d $ setDepth 9 st
|
||||
Vty.EvKey (Vty.KLeft) [] -> continue $ popScreen st
|
||||
Vty.EvKey (Vty.KRight) [] -> do
|
||||
let st' = screenEnter d args RS2.screen{rs2Acct=acct} st
|
||||
vScrollToBeginning $ viewportScroll "register"
|
||||
continue st'
|
||||
where
|
||||
acct = case listSelectedElement is of
|
||||
Just (_, ((a, _, _), _)) -> a
|
||||
Nothing -> ""
|
||||
|
||||
-- Vty.EvKey (Vty.KPageDown) [] -> continue $ st{aScreen=scr{asState=moveSel h is}}
|
||||
-- Vty.EvKey (Vty.KPageUp) [] -> continue $ st{aScreen=scr{asState=moveSel (-h) is}}
|
||||
@ -131,3 +168,10 @@ handleAccountsScreen st@AppState{aargs=args, aScreen=scr@AccountsScreen{asState=
|
||||
continue $ st{aScreen=scr{asState=is'}}
|
||||
-- continue =<< handleEventLensed st someLens ev
|
||||
handleAccountsScreen _ _ = error "event handler called with wrong screen type, should not happen"
|
||||
|
||||
setDepth :: Int -> AppState -> AppState
|
||||
setDepth depth st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{..}}} =
|
||||
st{aopts=uopts{cliopts_=copts{reportopts_=reportopts_{depth_=md}}}}
|
||||
where
|
||||
md | depth==0 = Nothing
|
||||
| otherwise = Just depth
|
||||
|
||||
@ -65,7 +65,7 @@ runBrickUi opts j = do
|
||||
maybestringopt "theme" $ rawopts_ $ cliopts_ opts
|
||||
args = words' $ query_ $ reportopts_ $ cliopts_ opts
|
||||
scr = AS.screen
|
||||
st = (sInitFn scr) d args
|
||||
st = (sInitFn scr) d
|
||||
AppState{
|
||||
aopts=opts
|
||||
,aargs=args
|
||||
|
||||
@ -23,13 +23,13 @@ prognameandversion :: String
|
||||
prognameandversion = progname ++ " " ++ version :: String
|
||||
|
||||
uiflags = [
|
||||
flagNone ["debug-ui"] (\opts -> setboolopt "rules-file" opts) "run with no terminal output, showing console"
|
||||
-- flagNone ["debug-ui"] (\opts -> setboolopt "rules-file" opts) "run with no terminal output, showing console"
|
||||
flagReq ["theme"] (\s opts -> Right $ setopt "theme" s opts) "THEME" ("use this custom display theme ("++intercalate ", " themeNames++")")
|
||||
,flagNone ["flat"] (\opts -> setboolopt "flat" opts) "show full account names, unindented"
|
||||
,flagReq ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "with --flat, omit this many leading account name components"
|
||||
,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format"
|
||||
,flagReq ["theme"] (\s opts -> Right $ setopt "theme" s opts) "THEME" ("use this custom display theme ("++intercalate ", " themeNames++")")
|
||||
,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "no eliding at all, stronger than --empty"
|
||||
-- ,flagNone ["no-total"] (\opts -> setboolopt "no-total" opts) "don't show the final total"
|
||||
-- ,flagReq ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "with --flat, omit this many leading account name components"
|
||||
-- ,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format"
|
||||
,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "don't compress empty parent accounts on one line"
|
||||
,flagNone ["value","V"] (setboolopt "value") "show amounts as their market value in their default valuation commodity (accounts screen)"
|
||||
]
|
||||
|
||||
--uimode :: Mode [([Char], [Char])]
|
||||
|
||||
@ -37,8 +37,8 @@ screen = RegisterScreen2{
|
||||
,sHandleFn = handleRegisterScreen2
|
||||
}
|
||||
|
||||
initRegisterScreen2 :: Day -> [String] -> AppState -> AppState
|
||||
initRegisterScreen2 d args st@AppState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen2{rs2Acct=acct}} =
|
||||
initRegisterScreen2 :: Day -> AppState -> AppState
|
||||
initRegisterScreen2 d st@AppState{aargs=args, aopts=opts, ajournal=j, aScreen=s@RegisterScreen2{rs2Acct=acct}} =
|
||||
st{aScreen=s{rs2State=l}}
|
||||
where
|
||||
-- gather arguments and queries
|
||||
@ -71,7 +71,7 @@ initRegisterScreen2 d args st@AppState{aopts=opts, ajournal=j, aScreen=s@Registe
|
||||
,case splitOn ", " otheracctsstr of
|
||||
[s] -> s
|
||||
ss -> intercalate ", " ss
|
||||
-- _ -> "<split>"
|
||||
-- _ -> "<split>" -- should do this if accounts field width < 30
|
||||
,showMixedAmountOneLineWithoutPrice change
|
||||
,showMixedAmountOneLineWithoutPrice bal
|
||||
)
|
||||
@ -83,15 +83,15 @@ initRegisterScreen2 d args st@AppState{aopts=opts, ajournal=j, aScreen=s@Registe
|
||||
|
||||
-- (listName someList)
|
||||
|
||||
initRegisterScreen2 _ _ _ = error "init function called with wrong screen type, should not happen"
|
||||
initRegisterScreen2 _ _ = error "init function called with wrong screen type, should not happen"
|
||||
|
||||
drawRegisterScreen2 :: AppState -> [Widget]
|
||||
drawRegisterScreen2 AppState{aopts=_uopts@UIOpts{cliopts_=_copts@CliOpts{reportopts_=_ropts@ReportOpts{query_=querystr}}},
|
||||
aargs=_args, aScreen=RegisterScreen2{rs2State=l,rs2Acct=acct}} = [ui]
|
||||
drawRegisterScreen2 AppState{ -- aopts=_uopts@UIOpts{cliopts_=_copts@CliOpts{reportopts_=_ropts@ReportOpts{query_=querystr}}},
|
||||
aScreen=RegisterScreen2{rs2State=l,rs2Acct=acct}} = [ui]
|
||||
where
|
||||
label = withAttr ("border" <> "bold") (str acct)
|
||||
toplabel = withAttr ("border" <> "bold") (str acct)
|
||||
<+> str " transactions"
|
||||
<+> borderQuery querystr
|
||||
-- <+> borderQueryStr querystr -- no, account transactions report shows all transactions in the acct ?
|
||||
-- <+> str " and subs"
|
||||
<+> str " ("
|
||||
<+> cur
|
||||
@ -149,10 +149,14 @@ drawRegisterScreen2 AppState{aopts=_uopts@UIOpts{cliopts_=_copts@CliOpts{reporto
|
||||
-- allocating equally.
|
||||
descwidth = maxdescacctswidth `div` 2
|
||||
acctswidth = maxdescacctswidth - descwidth
|
||||
|
||||
colwidths = (datewidth,descwidth,acctswidth,changewidth,balwidth)
|
||||
|
||||
render $ defaultLayout label $ renderList l (drawRegisterItem colwidths)
|
||||
bottomlabel = borderKeysStr [
|
||||
-- "up/down/pgup/pgdown/home/end: move"
|
||||
"left: return to accounts"
|
||||
]
|
||||
|
||||
render $ defaultLayout toplabel bottomlabel $ renderList l (drawRegisterItem colwidths)
|
||||
|
||||
drawRegisterScreen2 _ = error "draw function called with wrong screen type, should not happen"
|
||||
|
||||
|
||||
@ -69,6 +69,7 @@ themesList = [
|
||||
(borderAttr , white `on` black & dim),
|
||||
(borderAttr <> "bold", white `on` black & bold),
|
||||
(borderAttr <> "query", yellow `on` black & bold),
|
||||
(borderAttr <> "depth", cyan `on` black & bold),
|
||||
-- ("normal" , black `on` white),
|
||||
("list" , black `on` white), -- regular list items
|
||||
("list" <> "selected" , white `on` blue & bold) -- selected list items
|
||||
|
||||
@ -26,20 +26,20 @@ data AppState = AppState {
|
||||
data Screen =
|
||||
AccountsScreen {
|
||||
asState :: List BalanceReportItem -- ^ the screen's state (data being displayed and widget state)
|
||||
,sInitFn :: Day -> [String] -> AppState -> AppState -- ^ function to initialise the screen's state on entry
|
||||
,sInitFn :: Day -> AppState -> AppState -- ^ function to initialise the screen's state on entry
|
||||
,sHandleFn :: AppState -> V.Event -> EventM (Next AppState) -- ^ brick event handler to use for this screen
|
||||
,sDrawFn :: AppState -> [Widget] -- ^ brick renderer to use for this screen
|
||||
}
|
||||
| RegisterScreen {
|
||||
rsState :: List PostingsReportItem
|
||||
,sInitFn :: Day -> [String] -> AppState -> AppState
|
||||
,sInitFn :: Day -> AppState -> AppState
|
||||
,sHandleFn :: AppState -> V.Event -> EventM (Next AppState)
|
||||
,sDrawFn :: AppState -> [Widget]
|
||||
}
|
||||
| RegisterScreen2 {
|
||||
rs2State :: List (String,String,String,String,String)
|
||||
,rs2Acct :: AccountName -- ^ the account we are showing a register for
|
||||
,sInitFn :: Day -> [String] -> AppState -> AppState
|
||||
,sInitFn :: Day -> AppState -> AppState
|
||||
,sHandleFn :: AppState -> V.Event -> EventM (Next AppState)
|
||||
,sDrawFn :: AppState -> [Widget]
|
||||
}
|
||||
|
||||
@ -8,14 +8,18 @@ module Hledger.UI.UIUtils (
|
||||
-- ,margin
|
||||
,withBorderAttr
|
||||
,topBottomBorderWithLabel
|
||||
,topBottomBorderWithLabels
|
||||
,defaultLayout
|
||||
,borderQuery
|
||||
,borderQueryStr
|
||||
,borderDepthStr
|
||||
,borderKeysStr
|
||||
) where
|
||||
|
||||
import Control.Lens ((^.))
|
||||
-- import Control.Monad
|
||||
-- import Control.Monad.IO.Class
|
||||
-- import Data.Default
|
||||
import Data.List
|
||||
import Data.Monoid
|
||||
import Data.Time.Calendar (Day)
|
||||
import Brick
|
||||
@ -44,9 +48,9 @@ popScreen st = st
|
||||
-- Extra args can be passed to the new screen's init function,
|
||||
-- these can be eg query arguments.
|
||||
screenEnter :: Day -> [String] -> Screen -> AppState -> AppState
|
||||
screenEnter d args scr st = (sInitFn scr) d args $
|
||||
screenEnter d args scr st = (sInitFn scr) d $
|
||||
pushScreen scr
|
||||
st
|
||||
st{aargs=args}
|
||||
|
||||
-- | In the EventM monad, get the named current viewport's width and height,
|
||||
-- or (0,0) if the named viewport is not found.
|
||||
@ -59,8 +63,8 @@ getViewportSize name = do
|
||||
-- liftIO $ putStrLn $ show (w,h)
|
||||
return (w,h)
|
||||
|
||||
defaultLayout label =
|
||||
topBottomBorderWithLabel label .
|
||||
defaultLayout toplabel bottomlabel =
|
||||
topBottomBorderWithLabels (str " "<+>toplabel<+>str " ") (str " "<+>bottomlabel<+>str " ") .
|
||||
margin 1 0 Nothing
|
||||
-- topBottomBorderWithLabel2 label .
|
||||
-- padLeftRight 1 -- XXX should reduce inner widget's width by 2, but doesn't
|
||||
@ -82,6 +86,22 @@ topBottomBorderWithLabel label = \wrapped ->
|
||||
<=>
|
||||
hBorder
|
||||
|
||||
topBottomBorderWithLabels toplabel bottomlabel = \wrapped ->
|
||||
Widget Greedy Greedy $ do
|
||||
c <- getContext
|
||||
let (_w,h) = (c^.availWidthL, c^.availHeightL)
|
||||
h' = h - 2
|
||||
wrapped' = vLimit (h') wrapped
|
||||
debugmsg =
|
||||
""
|
||||
-- " debug: "++show (_w,h')
|
||||
render $
|
||||
hBorderWithLabel (toplabel <+> str debugmsg)
|
||||
<=>
|
||||
wrapped'
|
||||
<=>
|
||||
hBorderWithLabel bottomlabel
|
||||
|
||||
-- XXX should be equivalent to the above, but isn't (page down goes offscreen)
|
||||
_topBottomBorderWithLabel2 label = \wrapped ->
|
||||
let debugmsg = ""
|
||||
@ -123,6 +143,16 @@ withBorderAttr attr = updateAttrMap (applyAttrMappings [(borderAttr, attr)])
|
||||
-- , hCenter $ str "Press Esc to exit."
|
||||
-- ]
|
||||
|
||||
borderQuery :: String -> Widget
|
||||
borderQuery "" = str ""
|
||||
borderQuery qry = str " matching " <+> withAttr (borderAttr <> "query") (str qry)
|
||||
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] -> Widget
|
||||
borderKeysStr keydescs = str $ intercalate sep keydescs
|
||||
where
|
||||
sep = " | "
|
||||
-- sep = " "
|
||||
|
||||
Loading…
Reference in New Issue
Block a user