Skip to content

Commit

Permalink
Generalize the conversation filter logic
Browse files Browse the repository at this point in the history
This lets me use it to populate a context-aware menu that is opened when
clicking a button that is now present on the right hand side of each
entry in the packet struct view. The menu holds, as context, the display
ay filter that corresponds to that level of structure, via the PDML. The
menu lets the user (a) apply the filter as a custom column, or (b)
prepare/apply the filter as a display filter.
  • Loading branch information
gcla committed Feb 26, 2021
1 parent 297057e commit e992f0e
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 73 deletions.
127 changes: 85 additions & 42 deletions ui/convsui.go
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,23 @@ func (w *ConvsUiWidget) makeHeaderConvsUiWidget() gowid.IWidget {
return headerView
}

// convsModelWithRow is able to provide an A and a B for a conversation A <-> B. It looks
// up the model at a specific row to find the conversation.
type convsModelWithRow struct {
model *ConvsModel
row int
}

var _ IFilterModel = (*convsModelWithRow)(nil)

func (c *convsModelWithRow) GetAFilter(dir Direction) string {
return c.model.GetAFilter(c.row, dir)
}

func (c *convsModelWithRow) GetBFilter(dir Direction) string {
return c.model.GetBFilter(c.row, dir)
}

func (w *ConvsUiWidget) doFilterMenuOp(dirOp FilterMask, app gowid.IApp) {
conv1 := w.convHolder.SubWidget()
if conv1 != nil {
Expand All @@ -607,50 +624,12 @@ func (w *ConvsUiWidget) doFilterMenuOp(dirOp FilterMask, app gowid.IApp) {
}
pos := conv1.tbl.Pos()

var filter string
switch dirOp {
case AtfB:
filter = fmt.Sprintf("%s && %s", conv1.model.GetAFilter(pos, Any), conv1.model.GetBFilter(pos, Any))
case AtB:
filter = fmt.Sprintf("%s && %s", conv1.model.GetAFilter(pos, From), conv1.model.GetBFilter(pos, To))
case BtA:
filter = fmt.Sprintf("%s && %s", conv1.model.GetBFilter(pos, From), conv1.model.GetAFilter(pos, To))
case AtfAny:
filter = conv1.model.GetAFilter(pos, Any)
case AtAny:
filter = conv1.model.GetAFilter(pos, From)
case AnytA:
filter = conv1.model.GetAFilter(pos, To)
case AnytfB:
filter = conv1.model.GetBFilter(pos, Any)
case AnytB:
filter = conv1.model.GetBFilter(pos, To)
case BtAny:
filter = conv1.model.GetBFilter(pos, From)
cmodel := &convsModelWithRow{
model: conv1.model,
row: pos,
}

existingFilter := FilterWidget.Value()

switch w.filterSelectedIndex {
case NotSelected:
filter = fmt.Sprintf("!(%s)", filter)
case AndSelected:
if existingFilter != "" {
filter = fmt.Sprintf("%s && (%s)", existingFilter, filter)
}
case OrSelected:
if existingFilter != "" {
filter = fmt.Sprintf("%s || (%s)", existingFilter, filter)
}
case AndNotSelected:
if existingFilter != "" {
filter = fmt.Sprintf("%s && !(%s)", existingFilter, filter)
}
case OrNotSelected:
if existingFilter != "" {
filter = fmt.Sprintf("%s || !(%s)", existingFilter, filter)
}
}
filter := ComputeConvFilterOp(dirOp, w.filterSelectedIndex, cmodel, FilterWidget.Value())

FilterWidget.SetValue(filter, app)

Expand All @@ -668,6 +647,70 @@ func (w *ConvsUiWidget) doFilterMenuOp(dirOp FilterMask, app gowid.IApp) {
}
}

type IFilterModel interface {
GetAFilter(Direction) string
GetBFilter(Direction) string
}

func ComputeConvFilterOp(dirOp FilterMask, comb FilterCombinator, model IFilterModel, curFilter string) string {
var filter string
switch dirOp {
case AtfB:
filter = fmt.Sprintf("%s && %s", model.GetAFilter(Any), model.GetBFilter(Any))
case AtB:
filter = fmt.Sprintf("%s && %s", model.GetAFilter(From), model.GetBFilter(To))
case BtA:
filter = fmt.Sprintf("%s && %s", model.GetBFilter(From), model.GetAFilter(To))
case AtfAny:
filter = model.GetAFilter(Any)
case AtAny:
filter = model.GetAFilter(From)
case AnytA:
filter = model.GetAFilter(To)
case AnytfB:
filter = model.GetBFilter(Any)
case AnytB:
filter = model.GetBFilter(To)
case BtAny:
filter = model.GetBFilter(From)
}

return ComputeFilterCombOp(comb, filter, curFilter)
}

func ComputeFilterCombOp(comb FilterCombinator, newFilter string, curFilter string) string {
switch comb {
case NotSelected:
newFilter = fmt.Sprintf("!(%s)", newFilter)
case AndSelected:
if curFilter != "" {
newFilter = fmt.Sprintf("%s && (%s)", curFilter, newFilter)
} else {
newFilter = fmt.Sprintf("%s", newFilter)
}
case OrSelected:
if curFilter != "" {
newFilter = fmt.Sprintf("%s || (%s)", curFilter, newFilter)
} else {
newFilter = fmt.Sprintf("%s", newFilter)
}
case AndNotSelected:
if curFilter != "" {
newFilter = fmt.Sprintf("%s && !(%s)", curFilter, newFilter)
} else {
newFilter = fmt.Sprintf("!%s", newFilter)
}
case OrNotSelected:
if curFilter != "" {
newFilter = fmt.Sprintf("%s || !(%s)", curFilter, newFilter)
} else {
newFilter = fmt.Sprintf("!%s", newFilter)
}
}

return newFilter
}

func (w *ConvsUiWidget) OnCancel(app gowid.IApp) {
for _, cw := range w.convs {
cw.IWidget = cw.cancelledWidget
Expand Down
20 changes: 16 additions & 4 deletions ui/filterconvs.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ type indirect struct {
*holder.Widget
}

type iFilterMenuActor interface {
HandleFilterMenuSelection(FilterCombinator, gowid.IApp)
}

type convsFilterMenuActor struct{}

var _ iFilterMenuActor = convsFilterMenuActor{}

func (c convsFilterMenuActor) HandleFilterMenuSelection(conv FilterCombinator, app gowid.IApp) {
convsUi.filterSelectedIndex = conv
filterConvsMenu2.Open(filterConvsMenu1Site, app)
}

func buildFilterConvsMenu() {
filterConvsMenu1Holder := &indirect{}
filterConvsMenu2Holder := &indirect{}
Expand All @@ -50,7 +63,7 @@ func buildFilterConvsMenu() {
},
})

w := makeFilterConvsMenuWidget()
w := makeFilterCombineMenuWidget(convsFilterMenuActor{})
filterConvsMenu1Site = menu.NewSite(menu.SiteOptions{
XOffset: -3,
YOffset: -3,
Expand All @@ -65,7 +78,7 @@ func buildFilterConvsMenu() {
filterConvsMenu2Holder.Widget = holder.New(w2)
}

func makeFilterConvsMenuWidget() gowid.IWidget {
func makeFilterCombineMenuWidget(handler iFilterMenuActor) gowid.IWidget {
menuItems := make([]menuutil.SimpleMenuItem, 0)

for i, s := range []string{
Expand All @@ -82,8 +95,7 @@ func makeFilterConvsMenuWidget() gowid.IWidget {
Txt: s,
Key: gowid.MakeKey('1' + rune(i)),
CB: func(app gowid.IApp, w2 gowid.IWidget) {
convsUi.filterSelectedIndex = FilterCombinator(i2)
filterConvsMenu2.Open(filterConvsMenu1Site, app)
handler.HandleFilterMenuSelection(FilterCombinator(i2), app)
},
},
)
Expand Down
149 changes: 122 additions & 27 deletions ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,96 @@ func useAsColumn(filter string, name string, app gowid.IApp) {
RequestReload(app)
}

// Build the menu dynamically when needed so I can include the filter in the widgets
func makePdmlFilterMenu(filter string) *menu.Widget {
sites := make(menuutil.SiteMap)

var pdmlFilterMenu *menu.Widget

openPdmlFilterMenu2 := func(prep bool, w gowid.IWidget, app gowid.IApp) {
st, ok := sites[w]
if !ok {
log.Warnf("Unexpected application state: missing menu site for %v", w)
return
}

// This contains logic to close the two PDML menus opened from the struct
// view and then to either apply or prepare a new display filter based on
// the one that is currently selected by the user (i.e. the one associated
// with the open menu)
actor := &pdmlFilterActor{
filter: curColumnFilter,
prepare: prep,
menu1: pdmlFilterMenu,
}

menuBox := makeFilterCombineMenuWidget(actor)

m2 := menu.New("pdmlfilter2", menuBox, fixed, menu.Options{
Modal: true,
CloseKeysProvided: true,
CloseKeys: []gowid.IKey{
gowid.MakeKey('q'),
gowid.MakeKeyExt(tcell.KeyLeft),
gowid.MakeKeyExt(tcell.KeyEscape),
gowid.MakeKeyExt(tcell.KeyCtrlC),
},
})

// I need to set this up after constructing m2; m2 itself needs
// the menu box widget to display; that needs the actor to process
// the clicks of buttons within that widget, and that actor needs
// the menu m2 so that it can close it.
actor.menu2 = m2

multiMenu2Opener.OpenMenu(m2, st, app)
}

pdmlFilterItems := []menuutil.SimpleMenuItem{
menuutil.SimpleMenuItem{
Txt: fmt.Sprintf("Apply as Column: %s", filter),
Key: gowid.MakeKey('c'),
CB: func(app gowid.IApp, w gowid.IWidget) {
multiMenu1Opener.CloseMenu(pdmlFilterMenu, app)
useAsColumn(curColumnFilter, curColumnFilterName, app)
},
},
menuutil.MakeMenuDivider(),
menuutil.SimpleMenuItem{
Txt: fmt.Sprintf("Apply Filter: %s", filter),
Key: gowid.MakeKey('a'),
CB: func(app gowid.IApp, w gowid.IWidget) {
openPdmlFilterMenu2(false, w, app)
},
},
menuutil.SimpleMenuItem{
Txt: fmt.Sprintf("Prep Filter: %s", filter),
Key: gowid.MakeKey('p'),
CB: func(app gowid.IApp, w gowid.IWidget) {
openPdmlFilterMenu2(true, w, app)
},
},
}

pdmlFilterListBox, pdmlFilterWidth := menuutil.MakeMenuWithHotKeys(pdmlFilterItems, sites)

// this menu is opened from the PDML struct view and has, as context, the current PDML node. I
// need a name for it because I use that var in the closure above.
pdmlFilterMenu = menu.New("pdmlfiltermenu", pdmlFilterListBox, units(pdmlFilterWidth), menu.Options{
Modal: true,
CloseKeysProvided: true,
OpenCloser: &multiMenu1Opener,
CloseKeys: []gowid.IKey{
gowid.MakeKey('q'),
gowid.MakeKeyExt(tcell.KeyLeft),
gowid.MakeKeyExt(tcell.KeyEscape),
gowid.MakeKeyExt(tcell.KeyCtrlC),
},
})

return pdmlFilterMenu
}

//======================================================================

func RequestQuit() {
Expand Down Expand Up @@ -668,7 +758,8 @@ func makeStructNodeWidget(pos tree.IPos, tr tree.IModel) gowid.IWidget {
pdmlMenuButton.OnClick(gowid.MakeWidgetCallback("cb", func(app gowid.IApp, w gowid.IWidget) {
curColumnFilter = tr.(*pdmltree.Model).Name
curColumnFilterName = tr.(*pdmltree.Model).UiName
openTermsharkMenu(true, useAsColumnMenu, pdmlMenuButtonSite, app)
pdmlFilterMenu := makePdmlFilterMenu(curColumnFilter)
multiMenu1Opener.OpenMenu(pdmlFilterMenu, pdmlMenuButtonSite, app)
}))

styledButton1 := styled.New(pdmlMenuButton, gowid.MakePaletteRef("packet-struct-selected"))
Expand Down Expand Up @@ -2359,6 +2450,36 @@ func getHexWidgetToDisplay(row int) *hexdumper2.Widget {

//======================================================================

// pdmlFilterActor closes the menus opened via the PDML struct view, then
// either applies or preps the appropriate display filter
type pdmlFilterActor struct {
filter string
prepare bool
menu1 *menu.Widget
menu2 *menu.Widget
}

var _ iFilterMenuActor = (*pdmlFilterActor)(nil)

func (p *pdmlFilterActor) HandleFilterMenuSelection(comb FilterCombinator, app gowid.IApp) {
multiMenu2Opener.CloseMenu(p.menu2, app)
multiMenu1Opener.CloseMenu(p.menu1, app)

filter := ComputeFilterCombOp(comb, p.filter, FilterWidget.Value())

FilterWidget.SetValue(filter, app)

if p.prepare {
// Don't run the filter, just add to the displayfilter widget. Leave focus there
setFocusOnDisplayFilter(app)
} else {
RequestNewFilter(filter, app)
}

}

//======================================================================

func getStructWidgetKey(row int) []byte {
return []byte(fmt.Sprintf("s%d", row))
}
Expand Down Expand Up @@ -3107,32 +3228,6 @@ func Build() (*gowid.App, error) {

//======================================================================

useAsColumnItems := []menuutil.SimpleMenuItem{
menuutil.SimpleMenuItem{
Txt: "Apply as Column",
Key: gowid.MakeKey('a'),
CB: func(app gowid.IApp, w gowid.IWidget) {
openTermsharkMenu(false, useAsColumnMenu, nil, app)
useAsColumn(curColumnFilter, curColumnFilterName, app)
},
},
}

useAsColumnListBox, useAsColumnWidth := menuutil.MakeMenuWithHotKeys(useAsColumnItems)

useAsColumnMenu = menu.New("useascolumn", useAsColumnListBox, units(useAsColumnWidth), menu.Options{
Modal: true,
CloseKeysProvided: true,
OpenCloser: menu.OpenerFunc(openTermsharkMenu),
CloseKeys: []gowid.IKey{
gowid.MakeKey('q'),
gowid.MakeKeyExt(tcell.KeyLeft),
gowid.MakeKeyExt(tcell.KeyEscape),
gowid.MakeKeyExt(tcell.KeyCtrlC),
},
})

//======================================================================
loadProgress = progress.New(progress.Options{
Normal: gowid.MakePaletteRef("progress-default"),
Complete: gowid.MakePaletteRef("progress-complete"),
Expand Down

0 comments on commit e992f0e

Please sign in to comment.