From 4a0ee91473619db8a2c8a100758f1c24c39e7e39 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Wed, 24 Apr 2019 15:29:38 +0200 Subject: ui: use tabs in main window and refactor tunnels ui and log dialog into tab pages requires https://github.com/lxn/walk/commit/edb74ee350e9585ddd212acad445ec383950f2cc for status image background Signed-off-by: Alexander Neumann --- ui/logpage.go | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 ui/logpage.go (limited to 'ui/logpage.go') diff --git a/ui/logpage.go b/ui/logpage.go new file mode 100644 index 00000000..4f39ae87 --- /dev/null +++ b/ui/logpage.go @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package ui + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/lxn/win" + + "github.com/lxn/walk" + "golang.zx2c4.com/wireguard/windows/ringlogger" +) + +func NewLogPage(logger *ringlogger.Ringlogger) (*LogPage, error) { + lp := &LogPage{logger: logger} + + var disposables walk.Disposables + defer disposables.Treat() + + var err error + + if lp.TabPage, err = walk.NewTabPage(); err != nil { + return nil, err + } + disposables.Add(lp) + + lp.Disposing().Attach(func() { + lp.model.quit <- true + }) + + lp.SetTitle("Log") + lp.SetLayout(walk.NewVBoxLayout()) + lp.Layout().SetMargins(walk.Margins{18, 18, 18, 18}) + + if lp.logView, err = walk.NewTableView(lp); err != nil { + return nil, err + } + lp.logView.SetAlternatingRowBGColor(walk.Color(win.GetSysColor(win.COLOR_BTNFACE))) + lp.logView.SetLastColumnStretched(true) + + stampCol := walk.NewTableViewColumn() + stampCol.SetName("Stamp") + stampCol.SetTitle("Time") + stampCol.SetFormat("2006-01-02 15:04:05.000") + stampCol.SetWidth(150) + lp.logView.Columns().Add(stampCol) + + msgCol := walk.NewTableViewColumn() + msgCol.SetName("Line") + msgCol.SetTitle("Log message") + lp.logView.Columns().Add(msgCol) + + lp.model = newLogModel(lp, logger) + lp.logView.SetModel(lp.model) + + buttonsContainer, err := walk.NewComposite(lp) + if err != nil { + return nil, err + } + buttonsContainer.SetLayout(walk.NewHBoxLayout()) + buttonsContainer.Layout().SetMargins(walk.Margins{0, 12, 0, 0}) + + walk.NewHSpacer(buttonsContainer) + + saveButton, err := walk.NewPushButton(buttonsContainer) + if err != nil { + return nil, err + } + saveButton.SetText("Save") + saveButton.Clicked().Attach(lp.onSaveButtonClicked) + + disposables.Spare() + + return lp, nil +} + +type LogPage struct { + *walk.TabPage + logView *walk.TableView + logger *ringlogger.Ringlogger + model *logModel +} + +func (lp *LogPage) isAtBottom() bool { + return lp.logView.ItemVisible(len(lp.model.items) - 1) +} + +func (lp *LogPage) scrollToBottom() { + lp.logView.EnsureItemVisible(len(lp.model.items) - 1) +} + +func (lp *LogPage) onSaveButtonClicked() { + fd := walk.FileDialog{ + Filter: "Text Files (*.txt)|*.txt|All Files (*.*)|*.*", + FilePath: fmt.Sprintf("wireguard-log-%s.txt", time.Now().Format("2006-01-02T150405")), + Title: "Export log to file", + } + + form := lp.Form() + + if ok, _ := fd.ShowSave(form); !ok { + return + } + + extensions := []string{".log", ".txt"} + if fd.FilterIndex < 3 && !strings.HasSuffix(fd.FilePath, extensions[fd.FilterIndex-1]) { + fd.FilePath = fd.FilePath + extensions[fd.FilterIndex-1] + } + + writeFileWithOverwriteHandling(form, fd.FilePath, func(file *os.File) error { + if _, err := lp.logger.WriteTo(file); err != nil { + return fmt.Errorf("exportLog: Ringlogger.WriteTo failed: %v", err) + } + + return nil + }) +} + +type logModel struct { + walk.ReflectTableModelBase + lp *LogPage + quit chan bool + logger *ringlogger.Ringlogger + items []ringlogger.FollowLine +} + +func newLogModel(lp *LogPage, logger *ringlogger.Ringlogger) *logModel { + mdl := &logModel{lp: lp, quit: make(chan bool), logger: logger} + var lastCursor uint32 + mdl.items, lastCursor = logger.FollowFromCursor(ringlogger.CursorAll) + + var lastStamp time.Time + if len(mdl.items) > 0 { + lastStamp = mdl.items[len(mdl.items)-1].Stamp + } + + go func() { + ticker := time.NewTicker(time.Second) + + for { + select { + case <-ticker.C: + items, cursor := mdl.logger.FollowFromCursor(ringlogger.CursorAll) + + var stamp time.Time + if len(items) > 0 { + stamp = items[len(items)-1].Stamp + } + + if cursor != lastCursor || stamp.After(lastStamp) { + lastCursor = cursor + lastStamp = stamp + + mdl.lp.Synchronize(func() { + isAtBottom := mdl.lp.isAtBottom() + + mdl.items = items + mdl.PublishRowsReset() + + if isAtBottom { + mdl.lp.scrollToBottom() + } + }) + } + + case <-mdl.quit: + ticker.Stop() + break + } + } + }() + + return mdl +} + +func (mdl *logModel) Items() interface{} { + return mdl.items +} -- cgit v1.2.3-59-g8ed1b