Browse Source

tw.common: dynamic format strings; tw.unspent: reimplement display

The MMGen Project 2 years ago
parent
commit
f44078a187

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-13.3.dev17
+13.3.dev18

+ 0 - 5
mmgen/proto/btc/tw/addresses.py

@@ -45,11 +45,6 @@ Actions: [q]uit, r[e]draw, add [l]abel:
 		'p':'a_print_detail',
 		'l':'a_comment_add' }
 
-	squeezed_fs_fs     = ' {{n:>{nw}}} {{m}} {{u}}%s {{c}} {{A}} {{d}}'
-	squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}} {{u:{uw}}}%s {{c:{cw}}} {{A:{Aw}}} {{d}}'
-	wide_fs_fs         = ' {{n:>{nw}}} {{m}} {{u}} {{a}} {{c}} {{A}} {{b:<{bw}}} {{D}}'
-	wide_hdr_fs_fs     = ' {{n:>{nw}}} {{m:{mw}}} {{u:{uw}}} {{a:{aw}}} {{c:{cw}}} {{A:{Aw}}} {{b:{bw}}} {{D}}'
-
 	async def get_rpc_data(self):
 
 		msg_r('Getting unspent outputs...')

+ 0 - 3
mmgen/proto/btc/tw/txhistory.py

@@ -253,9 +253,6 @@ Actions: [q]uit, r[e]draw:
 		'p':'a_print_squeezed',
 		'P':'a_print_detail' }
 
-	squeezed_fs_fs     = ' {{n:>{nw}}} {{d:>{dw}}} {txid_fs}{{i}} {{A}} {{o}} {{c}}'
-	squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{d:{dw}}} {txid_fs}{{i:{iw}}} {{A}} {{o:{ow}}} {{c}}'
-
 	async def get_rpc_data(self):
 		blockhash = (
 			await self.rpc.call( 'getblockhash', self.sinceblock )

+ 0 - 5
mmgen/proto/btc/tw/unspent.py

@@ -52,11 +52,6 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel:
 		'w':'a_view_detail',
 		'l':'a_comment_add' }
 
-	col_adj = 38
-	squeezed_fs_fs     = ' {{n:{cw}}} {{t:{tw}}} {{v:2}} {{a}} {{A}} {{c:<}}'
-	squeezed_hdr_fs_fs = ' {{n:{cw}}} {{t:{tw}}} {{a}} {{A}} {{c:<}}'
-	wide_fs_fs         = ' {{n:4}} {{t:{tw}}} {{a}} {{m}} {{A:{aw}}} {cf}{{b:<8}} {{D:<19}} {{l}}'
-
 	async def get_rpc_data(self):
 		# bitcoin-cli help listunspent:
 		# Arguments:

+ 24 - 4
mmgen/proto/eth/tw/addresses.py

@@ -40,10 +40,30 @@ Actions: [q]uit, r[e]draw, [D]elete address, add [l]abel:
 		'w':'a_view_detail',
 		'p':'a_print_detail' }
 
-	squeezed_fs_fs     = ' {{n:>{nw}}} {{m:}}%s {{c:}} {{A:}}'
-	squeezed_hdr_fs_fs = ' {{n:>{nw}}} {{m:{mw}}}%s {{c:{cw}}} {{A:{Aw}}}'
-	wide_fs_fs         = ' {{n:>{nw}}} {{m:}} {{a:}} {{c:}} {{A:}}'
-	wide_hdr_fs_fs     = ' {{n:>{nw}}} {{m:{mw}}} {{a:{aw}}} {{c:{cw}}} {{A:{Aw}}}'
+	def get_column_widths(self,data,wide=False):
+
+		return self.compute_column_widths(
+			widths = { # fixed cols
+				'num':  max(2,len(str(len(data)))+1),
+				'mmid': max(len(d.twmmid.disp) for d in data),
+				'used': 0,
+				'amt':  self.disp_prec + 5,
+				'date': 0,
+				'block': 0,
+				'date_time': 0,
+				'spc':  5, # 4 spaces between cols + 1 leading space in fs
+			},
+			maxws = { # expandable cols
+				'addr':    max(len(d.addr) for d in data) if self.showcoinaddrs else 0,
+				'comment': max(d.comment.screen_width for d in data),
+			},
+			minws = {
+				'addr': 12 if self.showcoinaddrs else 0,
+				'comment': len('Comment'),
+			},
+			maxws_nice = {'addr': 18},
+			wide = wide,
+		)
 
 	async def get_rpc_data(self):
 

+ 39 - 18
mmgen/proto/eth/tw/unspent.py

@@ -20,20 +20,28 @@
 proto.eth.twuo: Ethereum tracking wallet unspent outputs class
 """
 
-from ....globalvars import g
 from ....tw.common import TwLabel
 from ....tw.unspent import TwUnspentOutputs
+from .common import EthereumTwCommon
 
 # No unspent outputs with Ethereum, but naming must be consistent
-class EthereumTwUnspentOutputs(TwUnspentOutputs):
+class EthereumTwUnspentOutputs(EthereumTwCommon,TwUnspentOutputs):
+
+	class display_type(TwUnspentOutputs.display_type):
+
+		class squeezed(TwUnspentOutputs.display_type.squeezed):
+			cols = ('num','addr','mmid','comment','amt','amt2')
+
+		class detail(TwUnspentOutputs.display_type.detail):
+			cols = ('num','addr','mmid','amt','amt2','comment')
 
 	class MMGenTwUnspentOutput(TwUnspentOutputs.MMGenTwUnspentOutput):
 		valid_attrs = {'txid','vout','amt','amt2','comment','twmmid','addr','confs','skip'}
 		invalid_attrs = {'proto'}
 
+	token_cls = False
 	has_age = False
 	can_group = False
-	col_adj = 29
 	hdr_lbl = 'tracked accounts'
 	desc    = 'account balances'
 	item_desc = 'account'
@@ -59,17 +67,35 @@ Actions:         [q]uit view, [p]rint to file, pager [v]iew, [w]ide view,
 		'D':'a_addr_delete',
 		'R':'a_balance_refresh' }
 
-	squeezed_fs_fs = squeezed_hdr_fs_fs = ' {{n:{cw}}} {{a}} {{A}}'
-	wide_fs_fs = ' {{n:4}} {{a}} {{m}} {{A:{aw}}} {{l}}'
 	no_data_errmsg = 'No accounts in tracking wallet!'
 
-	def subheader(self,color):
-		if g.cached_balances:
-			from ....color import nocolor,yellow
-			return (nocolor,yellow)[color](
-				'WARNING: Using cached balances. These may be out of date!') + '\n'
-		else:
-			return ''
+	def get_column_widths(self,data,wide=False):
+		# min screen width: 80 cols
+		# num addr [mmid] [comment] amt [amt2]
+		return self.compute_column_widths(
+			widths = { # fixed cols
+				'num': max(2,len(str(len(data)))+1),
+				'mmid': max(len(d.twmmid.disp) for d in data) if self.show_mmid else 0,
+				'amt': self.disp_prec + 5,
+				'amt2': self.disp_prec + 5 if self.token_cls else 0,
+				'spc': (5 if self.show_mmid else 3) + self.token_cls, # 5(3) spaces in fs
+				'txid': 0,
+				'vout': 0,
+				'block': 0,
+				'date': 0,
+				'date_time': 0,
+			},
+			maxws = { # expandable cols
+				'addr': max(len(d.addr) for d in data),
+				'comment': max(d.comment.screen_width for d in data) if self.show_mmid else 0,
+			},
+			minws = {
+				'addr': 10,
+				'comment': len('Comment') if self.show_mmid else 0,
+			},
+			maxws_nice = {'addr': 14} if self.show_mmid else {},
+			wide = wide,
+		)
 
 	def do_sort(self,key=None,reverse=False):
 		if key == 'txid': return
@@ -86,15 +112,10 @@ Actions:         [q]uit view, [p]rint to file, pager [v]iew, [w]ide view,
 				'confirmations': 0, # TODO
 				} for d in wl]
 
-	def age_disp(self,o,age_fmt): # TODO
-		pass
-
 class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs):
 
 	prompt_fs = 'Total to spend: {} {}\n\n'
-	col_adj = 37
-	squeezed_fs_fs = squeezed_hdr_fs_fs = ' {{n:{cw}}} {{a}} {{A}} {{A2}}'
-	wide_fs_fs = ' {{n:4}} {{a}} {{m}} {{A:{aw}}} {{A2:{aw}}} {{l}}'
+	token_cls = True
 
 	async def __init__(self,proto,*args,**kwargs):
 		await super().__init__(proto,*args,**kwargs)

+ 10 - 29
mmgen/tw/addresses.py

@@ -37,6 +37,14 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 	all_labels = False
 	no_data_errmsg = 'No addresses in tracking wallet!'
 
+	class display_type(TwCommon.display_type):
+
+		class squeezed(TwCommon.display_type.squeezed):
+			cols = ('num','mmid','used','addr','comment','amt','date')
+
+		class detail(TwCommon.display_type.detail):
+			cols = ('num','mmid','used','addr','comment','amt','block','date_time')
+
 	class TwAddress(MMGenListItem):
 		valid_attrs = {'twmmid','addr','al_id','confs','comment','amt','recvd','date','skip'}
 		invalid_attrs = {'proto'}
@@ -153,20 +161,7 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 		else:
 			return ''
 
-	def gen_squeezed_display(self,data,cw,color):
-
-		fs_parms = {
-			'nw': cw.num,
-			'mw': cw.mmid,
-			'uw': cw.used,
-			'aw': cw.addr,
-			'cw': cw.comment,
-			'Aw': cw.amt,
-			'dw': cw.date
-		}
-
-		hdr_fs = (self.squeezed_hdr_fs_fs % ('',' {{a:{aw}}}')[self.showcoinaddrs]).format(**fs_parms)
-		fs = (self.squeezed_fs_fs % ('',' {{a}}')[self.showcoinaddrs]).format(**fs_parms)
+	def gen_squeezed_display(self,data,cw,hdr_fs,fs,color):
 
 		yield hdr_fs.format(
 			n  = '',
@@ -194,21 +189,7 @@ class TwAddresses(MMGenObject,TwCommon,metaclass=AsyncInit):
 				d = self.age_disp( d, self.age_fmt )
 			)
 
-	def gen_detail_display(self,data,cw,color):
-
-		fs_parms = {
-			'nw': cw.num,
-			'mw': cw.mmid,
-			'uw': cw.used,
-			'aw': cw.addr,
-			'cw': cw.comment,
-			'Aw': cw.amt,
-			'bw': self.age_col_params['block'][0],
-			'Dw': self.age_col_params['date_time'][0],
-		}
-
-		hdr_fs = self.wide_hdr_fs_fs.format(**fs_parms)
-		fs = self.wide_fs_fs.format(**fs_parms)
+	def gen_detail_display(self,data,cw,hdr_fs,fs,color):
 
 		yield hdr_fs.format(
 			n  = '',

+ 29 - 2
mmgen/tw/common.py

@@ -42,6 +42,24 @@ class TwCommon:
 	_display_data = {}
 	filters = ()
 
+	fp = namedtuple('fs_params',['fs_key','hdr_fs_repl','fs_repl','hdr_fs','fs'])
+	fs_params = {
+		'num':       fp('n', True, True,  ' {n:>%s}', ' {n:>%s}'),
+		'txid':      fp('t', True, False, ' {t:%s}',  ' {t}'),
+		'vout':      fp('v', True, False, '{v:%s}',   '{v}'),
+		'used':      fp('u', True, False, ' {u:%s}',  ' {u}'),
+		'addr':      fp('a', True, False, ' {a:%s}',  ' {a}'),
+		'mmid':      fp('m', True, False, ' {m:%s}',  ' {m}'),
+		'comment':   fp('c', True, False, ' {c:%s}',  ' {c}'),
+		'amt':       fp('A', True, False, ' {A:%s}',  ' {A}'),
+		'amt2':      fp('B', True, False, ' {B:%s}',  ' {B}'),
+		'date':      fp('d', True, True,  ' {d:%s}',  ' {d:<%s}'),
+		'date_time': fp('D', True, True,  ' {D:%s}',  ' {D:%s}'),
+		'block':     fp('b', True, True,  ' {b:%s}',  ' {b:<%s}'),
+		'inputs':    fp('i', True, False, ' {i:%s}',  ' {i}'),
+		'outputs':   fp('o', True, False, ' {o:%s}',  ' {o}'),
+	}
+
 	age_fmts = ('confs','block','days','date','date_time')
 	age_fmts_date_dependent = ('days','date','date_time')
 	age_fmts_interactive = ('confs','block','days','date','date_time')
@@ -283,12 +301,21 @@ class TwCommon:
 
 			data = self.disp_data = list(self.filter_data()) # method could be a generator
 
-			cw = self.get_column_widths(data,wide=dt.detail) if data and dt.need_column_widths else None
+			if data and dt.need_column_widths:
+				cw = self.get_column_widths(data,wide=dt.detail)
+				cwh = cw._asdict()
+				fp = self.fs_params
+				hdr_fs = ''.join(fp[name].hdr_fs % ((),cwh[name])[fp[name].hdr_fs_repl]
+					for name in dt.cols if cwh[name])
+				fs = ''.join(fp[name].fs % ((),cwh[name])[fp[name].fs_repl]
+					for name in dt.cols if cwh[name])
+			else:
+				cw = hdr_fs = fs = None
 
 			self._display_data[display_type] = '{a}{b}\n{c}\n'.format(
 				a = self.header(color),
 				b = self.subheader(color),
-				c = dt.item_separator.join(getattr(self,dt.fmt_method)(data,cw,color=color))
+				c = dt.item_separator.join(getattr(self,dt.fmt_method)(data,cw,hdr_fs,fs,color=color))
 					if data else (nocolor,yellow)[color]('[no data for requested parameters]')
 			)
 

+ 6 - 15
mmgen/tw/txhistory.py

@@ -25,6 +25,9 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 
 	class display_type(TwCommon.display_type):
 
+		class squeezed(TwCommon.display_type.squeezed):
+			cols = ('num','txid','date','inputs','amt','outputs','comment')
+
 		class detail(TwCommon.display_type.detail):
 			need_column_widths = False
 			item_separator = '\n\n'
@@ -87,7 +90,7 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 
 		return self.compute_column_widths(widths,maxws,minws,maxws_nice,wide=wide)
 
-	def gen_squeezed_display(self,data,cw,color):
+	def gen_squeezed_display(self,data,cw,hdr_fs,fs,color):
 
 		if self.sinceblock:
 			yield f'Displaying transactions since block {self.sinceblock.hl(color=color)}'
@@ -98,18 +101,6 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 			yield 'Due to screen width limitations, not all addresses could be displayed'
 		yield ''
 
-		hdr_fs = self.squeezed_hdr_fs_fs.format(
-			nw = cw.num,
-			dw = self.age_w,
-			txid_fs = f'{{i:{cw.txid}}} ' if self.show_txid else '',
-			iw = cw.inputs,
-			ow = cw.outputs )
-
-		fs = self.squeezed_fs_fs.format(
-			nw = cw.num,
-			dw = self.age_w,
-			txid_fs = f'{{i:{cw.txid}}} ' if self.show_txid else '' )
-
 		yield hdr_fs.format(
 			n = '',
 			t = 'TxID',
@@ -127,9 +118,9 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit):
 				i = d.vouts_disp( 'inputs', width=cw.inputs, color=color ),
 				A = d.amt_disp(self.show_total_amt).fmt( prec=self.disp_prec, color=color ),
 				o = d.vouts_disp( 'outputs', width=cw.outputs, color=color ),
-				c = d.comment.fmt( width=cw.comment, color=color ) ).rstrip()
+				c = d.comment.fmt( width=cw.comment, color=color, nullrepl='-' ) ).rstrip()
 
-	def gen_detail_display(self,data,cw,color):
+	def gen_detail_display(self,data,cw,hdr_fs,fs,color):
 
 		if self.sinceblock:
 			yield f'Displaying transactions since block {self.sinceblock.hl(color=color)}'

+ 92 - 112
mmgen/tw/unspent.py

@@ -20,8 +20,6 @@
 twuo: Tracking wallet unspent outputs class for the MMGen suite
 """
 
-from collections import namedtuple
-
 from ..globalvars import g
 from ..util import msg,suf,fmt
 from ..base_obj import AsyncInit
@@ -41,6 +39,14 @@ from .common import TwCommon,TwMMGenID,get_tw_label
 
 class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
 
+	class display_type(TwCommon.display_type):
+
+		class squeezed(TwCommon.display_type.squeezed):
+			cols = ('num','txid','vout','addr','mmid','comment','amt','amt2','date')
+
+		class detail(TwCommon.display_type.detail):
+			cols = ('num','txid','vout','addr','mmid','amt','amt2','block','date_time','comment')
+
 	def __new__(cls,proto,*args,**kwargs):
 		return MMGenObject.__new__(proto.base_proto_subclass(cls,'tw','unspent'))
 
@@ -124,120 +130,94 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit):
 		return data
 
 	def get_column_widths(self,data,wide=False):
+		# min screen width: 80 cols
+		# num txid vout addr [mmid] [comment] amt [amt2] date
+		maxws_nice = {'txid': 12}
+		if self.show_mmid:
+				maxws_nice['addr'] = 16
+
+		return self.compute_column_widths(
+			widths = { # fixed cols
+				'num': max(2,len(str(len(data)))+1),
+				'vout': 4,
+				'mmid': max(len(d.twmmid.disp) for d in data) if self.show_mmid else 0,
+				'amt': self.disp_prec + 5,
+				'amt2': 0,
+				'block': self.age_col_params['block'][0] if wide else 0,
+				'date_time': self.age_col_params['date_time'][0] if wide else 0,
+				'date': self.age_w,
+				'spc': 7 if self.show_mmid else 5, # 7(5) spaces in fs
+			},
+			maxws = { # expandable cols
+				'txid': self.txid_w,
+				'addr': max(len(d.addr) for d in data),
+				'comment': max(d.comment.screen_width for d in data) if self.show_mmid else 0,
+			},
+			minws = {
+				'txid': 7,
+				'addr': 10,
+				'comment': len('Comment') if self.show_mmid else 0,
+			},
+			maxws_nice = maxws_nice,
+			wide = wide,
+		)
+
+	def gen_squeezed_display(self,data,cw,hdr_fs,fs,color):
 
-		self.cols = self.get_term_columns(g.min_screen_width)
-
-		# allow for 7-digit confirmation nums
-		col1_w = max(3,len(str(len(data)))+1) # num + ')'
-		mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in data) or 12 # DEADBEEF:S:1
-		max_acct_w = max(i.comment.screen_width for i in data) + mmid_w + 1
-		max_btcaddr_w = max(len(i.addr) for i in data)
-		min_addr_w = self.cols - self.col_adj
-		addr_w = min(max_btcaddr_w + (0,1+max_acct_w)[self.show_mmid],min_addr_w)
-		acct_w = min(max_acct_w, max(24,addr_w-10))
-		btaddr_w = addr_w - acct_w - 1
-		comment_w = acct_w - mmid_w - 1
-		tx_w = min(self.txid_w,self.cols-addr_w-29-col1_w) # min=6 TODO
-
-		return namedtuple(
-			'column_widths',
-			['num','mmid','addr','btaddr','comment','tx']
-			)(col1_w,  mmid_w,  addr_w,  btaddr_w,  comment_w,  tx_w)
-
-	def gen_squeezed_display(self,data,cw,color):
-		fs     = self.squeezed_fs_fs.format(     cw=cw.num, tw=cw.tx )
-		hdr_fs = self.squeezed_hdr_fs_fs.format( cw=cw.num, tw=cw.tx )
 		yield hdr_fs.format(
-			n  = 'Num',
-			t  = 'TXid'.ljust(cw.tx - 2) + ' Vout',
-			a  = 'Address'.ljust(cw.addr),
-			A  = f'Amt({self.proto.dcoin})'.ljust(self.disp_prec+5),
-			A2 = f' Amt({self.proto.coin})'.ljust(self.disp_prec+4),
-			c  = self.age_hdr ).rstrip()
-
-		for n,i in enumerate(data):
-			addr_dots = '|' + '.'*(cw.addr-1)
-			mmid_disp = MMGenID.fmtc(
-				(
-					'.'*cw.mmid if i.skip == 'addr' else
-					i.twmmid if i.twmmid.type == 'mmgen' else
-					f'Non-{g.proj_name}'
-				),
-				width = cw.mmid,
-				color = color )
-
-			if self.show_mmid:
-				addr_out = '{} {}{}'.format((
-					type(i.addr).fmtc(addr_dots,width=cw.btaddr,color=color) if i.skip == 'addr' else
-					i.addr.fmt(width=cw.btaddr,color=color)
-				),
-					mmid_disp,
-					(' ' + i.comment.fmt(width=cw.comment,color=color)) if cw.comment > 0 else ''
-				)
-			else:
-				addr_out = (
-					type(i.addr).fmtc(addr_dots,width=cw.addr,color=color) if i.skip=='addr' else
-					i.addr.fmt(width=cw.addr,color=color) )
-
+			n = '',
+			t = 'TxID',
+			v = 'Vout',
+			a = 'Address',
+			m = 'MMGenID',
+			c = 'Comment',
+			A = 'Amt({})'.format(self.proto.dcoin),
+			B = 'Amt({})'.format(self.proto.coin),
+			d = self.age_hdr )
+
+		for n,d in enumerate(data):
 			yield fs.format(
-				n  = str(n+1)+')',
-				t  = (
-					'' if not i.txid else
-					' ' * (cw.tx-4) + '|...' if i.skip  == 'txid' else
-					i.txid.truncate(width=cw.tx,color=True) ),
-				v  = i.vout,
-				a  = addr_out,
-				A  = i.amt.fmt(color=color,prec=self.disp_prec),
-				A2 = (i.amt2.fmt(color=color,prec=self.disp_prec) if i.amt2 is not None else ''),
-				c  = self.age_disp(i,self.age_fmt),
-				).rstrip()
-
-	def gen_detail_display(self,data,cw,color):
-
-		addr_w = max(len(i.addr) for i in data)
-		mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in data) or 12 # DEADBEEF:S:1
-
-		fs = self.wide_fs_fs.format(
-			tw = self.txid_w + 3,
-			cf = '{c:<8} ',
-			aw = self.proto.coin_amt.max_prec + 5 )
-
-		yield fs.format(
-			n  = 'Num',
-			t  = 'Tx ID,Vout',
-			a  = 'Address'.ljust(addr_w),
-			m  = 'MMGen ID'.ljust(mmid_w),
-			A  = f'Amount({self.proto.dcoin})',
-			A2 = f'Amount({self.proto.coin})',
-			c  = 'Confs',  # skipped for eth
-			b  = 'Block',  # skipped for eth
-			D  = 'Date',
-			l  = 'Label' )
-
-		max_comment_len = max([len(i.comment) for i in data if i.comment] or [2])
-
-		for n,i in enumerate(data):
+				n = str(n+1)+')',
+				t = (CoinTxID.fmtc('|' + '.'*(cw.txid-1),color=color) if d.skip  == 'txid'
+					else d.txid.truncate(width=cw.txid,color=color)) if cw.txid else None,
+				v = ' ' + d.vout.fmt(width=cw.vout-1,color=color) if cw.vout else None,
+				a = type(d.addr).fmtc('|' + '.'*(cw.addr-1),width=cw.addr,color=color) if d.skip == 'addr'
+					else d.addr.fmt(width=cw.addr,color=color),
+				m = (MMGenID.fmtc('.'*cw.mmid,color=color) if d.skip == 'addr'
+					else d.twmmid.fmt(width=cw.mmid,color=color)) if cw.mmid else None,
+				c = d.comment.fmt(width=cw.comment,color=color,nullrepl='-') if cw.comment else None,
+				A = d.amt.fmt(color=color,prec=self.disp_prec),
+				B = d.amt2.fmt(color=color,prec=self.disp_prec) if cw.amt2 else None,
+				d = self.age_disp(d,self.age_fmt),
+			)
+
+	def gen_detail_display(self,data,cw,hdr_fs,fs,color):
+
+		yield hdr_fs.format(
+			n = '',
+			t = 'TxID',
+			v = 'Vout',
+			a = 'Address',
+			m = 'MMGenID',
+			A = 'Amt({})'.format(self.proto.dcoin),
+			B = 'Amt({})'.format(self.proto.coin) if cw.amt2 else None,
+			b = 'Block',
+			D = 'Date/Time',
+			c = 'Comment' ).rstrip()
+
+		for n,d in enumerate(data):
 			yield fs.format(
-				n  = str(n+1) + ')',
-				t  = '{},{}'.format(
-						('|'+'.'*63 if i.skip == 'txid' and self.group else i.txid),
-						i.vout ),
-				a  = (
-					'|'+'.' * addr_w if i.skip == 'addr' and self.group else
-					i.addr.fmt(color=color,width=addr_w) ),
-				m  = MMGenID.fmtc( i.twmmid.disp, width=mmid_w, color=color ),
-				A  = i.amt.fmt(color=color),
-				A2 = ( i.amt2.fmt(color=color) if i.amt2 is not None else '' ),
-				c  = i.confs,
-				b  = self.rpc.blockcount - (i.confs - 1),
-				D  = self.age_disp(i,'date_time'),
-				l  = i.comment.hl(color=color) if i.comment else
-					TwComment.fmtc(
-						s        = '',
-						color    = color,
-						nullrepl = '-',
-						width    = max_comment_len )
-				).rstrip()
+				n = str(n+1) + ')',
+				t = d.txid.fmt(color=color) if cw.txid else None,
+				v = ' ' + d.vout.fmt(width=cw.vout-1,color=color) if cw.vout else None,
+				a = d.addr.fmt(width=cw.addr,color=color),
+				m = d.twmmid.fmt(width=cw.mmid,color=color),
+				A = d.amt.fmt(color=color,prec=self.disp_prec),
+				B = d.amt2.fmt(color=color,prec=self.disp_prec) if cw.amt2 else None,
+				b = self.age_disp(d,'block'),
+				D = self.age_disp(d,'date_time'),
+				c = d.comment.fmt(width=cw.comment,color=color,nullrepl='-'),
+			).rstrip()
 
 	def display_total(self):
 		msg('\nTotal unspent: {} {} ({} output{})'.format(