ui: B and V keys toggle display of cost, value

This commit is contained in:
Simon Michael 2019-10-20 07:12:14 -07:00
parent f8269e21ab
commit 332624f9fa
7 changed files with 152 additions and 31 deletions

View File

@ -82,26 +82,44 @@ totallabel = "Period Total"
balancelabel = "Historical Total"
accountTransactionsReport :: ReportOpts -> Journal -> Query -> Query -> AccountTransactionsReport
accountTransactionsReport opts j reportq thisacctq = (label, items)
accountTransactionsReport ropts j reportq thisacctq = (label, items)
where
-- a depth limit does not affect the account transactions report
-- seems unnecessary for some reason XXX
reportq' = -- filterQuery (not . queryIsDepth)
reportq
-- get all transactions, with amounts converted to cost basis if -B
ts1 = jtxns $ journalSelectingAmountFromOpts opts j
-- get all transactions
ts1 = jtxns j
-- apply any cur:SYM filters in reportq'
symq = filterQuery queryIsSym reportq'
ts2 = (if queryIsNull symq then id else map (filterTransactionAmounts symq)) ts1
-- keep just the transactions affecting this account (via possibly realness or status-filtered postings)
realq = filterQuery queryIsReal reportq'
statusq = filterQuery queryIsStatus reportq'
ts3 = filter (matchesTransaction thisacctq . filterTransactionPostings (And [realq, statusq])) ts2
-- maybe convert these transactions to cost or value
prices = journalPriceOracle j
styles = journalCommodityStyles j
periodlast =
fromMaybe (error' "journalApplyValuation: expected a non-empty journal") $ -- XXX shouldn't happen
reportPeriodOrJournalLastDay ropts j
mreportlast = reportPeriodLastDay ropts
today = fromMaybe (error' "journalApplyValuation: could not pick a valuation date, ReportOpts today_ is unset") $ today_ ropts
multiperiod = interval_ ropts /= NoInterval
tval = case value_ ropts of
Just v -> \t -> transactionApplyValuation prices styles periodlast mreportlast today multiperiod t v
Nothing -> id
ts4 = map tval ts3
-- sort by the transaction's register date, for accurate starting balance
ts = sortBy (comparing (transactionRegisterDate reportq' thisacctq)) ts3
ts = sortBy (comparing (transactionRegisterDate reportq' thisacctq)) ts4
(startbal,label)
| balancetype_ opts == HistoricalBalance = (sumPostings priorps, balancelabel)
| balancetype_ ropts == HistoricalBalance = (sumPostings priorps, balancelabel)
| otherwise = (nullmixedamt, totallabel)
where
priorps = dbg1 "priorps" $
@ -113,7 +131,7 @@ accountTransactionsReport opts j reportq thisacctq = (label, items)
case mstartdate of
Just _ -> Date (DateSpan Nothing mstartdate)
Nothing -> None -- no start date specified, there are no prior postings
mstartdate = queryStartDate (date2_ opts) reportq'
mstartdate = queryStartDate (date2_ ropts) reportq'
datelessreportq = filterQuery (not . queryIsDateOrDate2) reportq'
items = reverse $

View File

@ -311,6 +311,8 @@ asHandle ui0@UIState{
VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui
VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor endPos (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui
VtyEvent (EvKey (KChar 'B') []) -> continue $ regenerateScreens j d $ toggleCost ui
VtyEvent (EvKey (KChar 'V') []) -> continue $ regenerateScreens j d $ toggleValue ui
VtyEvent (EvKey (KChar '0') []) -> continue $ regenerateScreens j d $ setDepth (Just 0) ui
VtyEvent (EvKey (KChar '1') []) -> continue $ regenerateScreens j d $ setDepth (Just 1) ui
VtyEvent (EvKey (KChar '2') []) -> continue $ regenerateScreens j d $ setDepth (Just 2) ui

View File

@ -322,6 +322,8 @@ rsHandle ui@UIState{
rsItemTransaction=Transaction{tsourcepos=JournalSourcePos f (l,_)}}) -> (Just (l, Nothing),f)
-- display mode/query toggles
VtyEvent (EvKey (KChar 'B') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleCost ui
VtyEvent (EvKey (KChar 'V') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleValue ui
VtyEvent (EvKey (KChar 'H') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleHistorical ui
VtyEvent (EvKey (KChar 'T') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleTree ui
VtyEvent (EvKey (KChar 'Z') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleEmpty ui

View File

@ -12,6 +12,7 @@ where
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Data.List
import Data.Maybe
#if !(MIN_VERSION_base(4,11,0))
import Data.Monoid
#endif
@ -43,12 +44,22 @@ transactionScreen = TransactionScreen{
tsInit :: Day -> Bool -> UIState -> UIState
tsInit _d _reset ui@UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=_ropts}}
,ajournal=_j
,aScreen=TransactionScreen{..}} = ui
,ajournal=_j
,aScreen=TransactionScreen{..}
} =
-- plog ("initialising TransactionScreen, value_ is "
-- -- ++ (pshow (Just (AtDefault Nothing)::Maybe ValuationType))
-- ++(pshow (value_ _ropts)) -- XXX calling value_ here causes plog to fail with: debug.log: openFile: resource busy (file is locked)
-- ++ "?"
-- ++" and first commodity is")
-- (acommodity$head$amounts$pamount$head$tpostings$snd$tsTransaction)
-- `seq`
ui
tsInit _ _ _ = error "init function called with wrong screen type, should not happen"
tsDraw :: UIState -> [Widget Name]
tsDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}
,ajournal=j
,aScreen=TransactionScreen{tsTransaction=(i,t)
,tsTransactions=nts
,tsAccount=acct
@ -61,8 +72,20 @@ tsDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}
_ -> [maincontent]
where
maincontent = Widget Greedy Greedy $ do
let
prices = journalPriceOracle j
styles = journalCommodityStyles j
periodlast =
fromMaybe (error' "TransactionScreen: expected a non-empty journal") $ -- XXX shouldn't happen
reportPeriodOrJournalLastDay ropts j
mreportlast = reportPeriodLastDay ropts
today = fromMaybe (error' "TransactionScreen: could not pick a valuation date, ReportOpts today_ is unset") $ today_ ropts
multiperiod = interval_ ropts /= NoInterval
render $ defaultLayout toplabel bottomlabel $ str $
showTransactionOneLineAmounts $
(if valuationTypeIsCost ropts then transactionToCost (journalCommodityStyles j) else id) $
(if valuationTypeIsDefaultValue ropts then (\t -> transactionApplyValuation prices styles periodlast mreportlast today multiperiod t (AtDefault Nothing)) else id) $
-- (if real_ ropts then filterTransactionPostings (Real True) else id) -- filter postings by --real
t
where
@ -142,38 +165,35 @@ tsHandle ui@UIState{aScreen=s@TransactionScreen{tsTransaction=(i,t)
where
p = reportPeriod ui
e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> do
-- plog (if e == AppEvent FileChange then "file change" else "manual reload") "" `seq` return ()
d <- liftIO getCurrentDay
ej <- liftIO $ journalReload copts
case ej of
Left err -> continue $ screenEnter d errorScreen{esError=err} ui
Right j' -> do
-- got to redo the register screen's transactions report, to get the latest transactions list for this screen
-- XXX duplicates rsInit
let
ropts' = ropts {depth_=Nothing
,balancetype_=HistoricalBalance
}
q = filterQuery (not . queryIsDepth) $ queryFromOpts d ropts'
thisacctq = Acct $ accountNameToAccountRegex acct -- includes subs
items = reverse $ snd $ accountTransactionsReport ropts j' q thisacctq
ts = map first6 items
numberedts = zip [1..] ts
-- select the best current transaction from the new list
-- stay at the same index if possible, or if we are now past the end, select the last, otherwise select the first
(i',t') = case lookup i numberedts
of Just t'' -> (i,t'')
Nothing | null numberedts -> (0,nulltransaction)
| i > fst (last numberedts) -> last numberedts
| otherwise -> head numberedts
ui' = ui{aScreen=s{tsTransaction=(i',t')
,tsTransactions=numberedts
,tsAccount=acct}}
continue $ regenerateScreens j' d ui'
continue $
regenerateScreens j' d $
regenerateTransactions ropts d j' s acct i $ -- added (inline) 201512 (why ?)
clearCostValue $
ui
VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
-- if allowing toggling here, we should refresh the txn list from the parent register screen
-- for toggles that may change the current/prev/next transactions,
-- we must regenerate the transaction list, like the g handler above ? with regenerateTransactions ? TODO WIP
-- EvKey (KChar 'E') [] -> continue $ regenerateScreens j d $ stToggleEmpty ui
-- EvKey (KChar 'C') [] -> continue $ regenerateScreens j d $ stToggleCleared ui
-- EvKey (KChar 'R') [] -> continue $ regenerateScreens j d $ stToggleReal ui
VtyEvent (EvKey (KChar 'B') []) ->
continue $
regenerateScreens j d $
-- regenerateTransactions ropts d j s acct i $
toggleCost ui
VtyEvent (EvKey (KChar 'V') []) ->
continue $
regenerateScreens j d $
-- regenerateTransactions ropts d j s acct i $
toggleValue ui
VtyEvent e | e `elem` moveUpEvents -> continue $ regenerateScreens j d ui{aScreen=s{tsTransaction=(iprev,tprev)}}
VtyEvent e | e `elem` moveDownEvents -> continue $ regenerateScreens j d ui{aScreen=s{tsTransaction=(inext,tnext)}}
VtyEvent e | e `elem` moveLeftEvents -> continue ui''
@ -186,6 +206,32 @@ tsHandle ui@UIState{aScreen=s@TransactionScreen{tsTransaction=(i,t)
tsHandle _ _ = error "event handler called with wrong screen type, should not happen"
-- Got to redo the register screen's transactions report, to get the latest transactions list for this screen.
-- XXX Duplicates rsInit. Why do we have to do this as well as regenerateScreens ?
regenerateTransactions :: ReportOpts -> Day -> Journal -> Screen -> AccountName -> Integer -> UIState -> UIState
regenerateTransactions ropts d j s acct i ui =
let
ropts' = ropts {depth_=Nothing
,balancetype_=HistoricalBalance
}
q = filterQuery (not . queryIsDepth) $ queryFromOpts d ropts'
thisacctq = Acct $ accountNameToAccountRegex acct -- includes subs
items = reverse $ snd $ accountTransactionsReport ropts j q thisacctq
ts = map first6 items
numberedts = zip [1..] ts
-- select the best current transaction from the new list
-- stay at the same index if possible, or if we are now past the end, select the last, otherwise select the first
(i',t') = case lookup i numberedts
of Just t'' -> (i,t'')
Nothing | null numberedts -> (0,nulltransaction)
| i > fst (last numberedts) -> last numberedts
| otherwise -> head numberedts
in
ui{aScreen=s{tsTransaction=(i',t')
,tsTransactions=numberedts
,tsAccount=acct
}}
-- | Select the nth item on the register screen.
rsSelect i scr@RegisterScreen{..} = scr{rsList=l'}
where l' = listMoveTo (i-1) rsList

View File

@ -108,6 +108,32 @@ toggleEmpty ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=rop
where
toggleEmpty ropts = ropts{empty_=not $ empty_ ropts}
-- | Show primary amounts, not cost or value.
clearCostValue :: UIState -> UIState
clearCostValue ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{value_ = plog "clearing value mode" Nothing}}}}
-- | Toggle between showing the primary amounts or costs.
toggleCost :: UIState -> UIState
toggleCost ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{value_ = valuationToggleCost $ value_ ropts}}}}
-- | Toggle between showing primary amounts or default valuation.
toggleValue :: UIState -> UIState
toggleValue ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{
value_ = plog "toggling value mode to" $ valuationToggleValue $ value_ ropts}}}}
-- | Basic toggling of -B/cost, for hledger-ui.
valuationToggleCost :: Maybe ValuationType -> Maybe ValuationType
valuationToggleCost (Just (AtCost _)) = Nothing
valuationToggleCost _ = Just $ AtCost Nothing
-- | Basic toggling of -V, for hledger-ui.
valuationToggleValue :: Maybe ValuationType -> Maybe ValuationType
valuationToggleValue (Just (AtDefault _)) = Nothing
valuationToggleValue _ = Just $ AtDefault Nothing
-- | Toggle between flat and tree mode. If current mode is unspecified/default, assume it's flat.
toggleTree :: UIState -> UIState
toggleTree ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =

View File

@ -134,8 +134,10 @@ helpDialog _copts =
,withAttr ("help" <> "heading") $ str "Other"
,renderKey ("a ", "add transaction (hledger add)")
,renderKey ("A ", "add transaction (hledger-iadd)")
,renderKey ("B ", "toggle normal/cost mode")
,renderKey ("E ", "open editor")
,renderKey ("I ", "toggle balance assertions")
,renderKey ("V ", "toggle normal/value mode")
,renderKey ("g ", "reload data")
,renderKey ("C-l ", "redraw & recenter")
,renderKey ("C-z ", "suspend")

View File

@ -161,6 +161,31 @@ when invoked from the error screen.
`q` quits the application.
Experimental:
`B` toggles cost mode, showing amounts in their transaction price's
commodity (like toggling the
[`-B/--cost`](https://hledger.org/hledger.html#b-cost) flag).
`V` toggles value mode, showing amounts' current market value in their
default valuation commodity (like toggling the
[`-V/--market`](https://hledger.org/hledger.html#v-market-value) flag).
Note, "current market value" means the value on the report end date if specified, otherwise today.
To see the value on another date, such as the transaction's date, you can
temporarily set a date filter ending on the following day.
Eg to see the contemporaneous value of a transaction on july 30,
go to the accounts or register screen, press `/`, add ` date:-7/30`.
At most one of cost or value mode can be active at once (in hledger-ui).
There's not yet any visual reminder when cost or value mode is active;
for now pressing `B` `B` `V` should reliably reset to normal mode.
With --watch active, if you save an edit to the journal file
while viewing the transaction screen in cost or value mode,
the `B`/`V` keys will stop working.
To work around, press g to force a manual reload, or exit the transaction screen.
Additional screen-specific keys are described below.
# SCREENS