mirror of
https://github.com/fiatjaf/vnak.git
synced 2026-07-22 07:48:24 +00:00
838 lines
22 KiB
Go
838 lines
22 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"fiatjaf.com/nostr"
|
|
qt "github.com/mappu/miqt/qt6"
|
|
"github.com/mappu/miqt/qt6/mainthread"
|
|
"golang.org/x/exp/slices"
|
|
)
|
|
|
|
type reqVars struct {
|
|
tab *qt.QWidget
|
|
|
|
authorsVBox *qt.QVBoxLayout
|
|
authorsEdits []*qt.QLineEdit
|
|
idsVBox *qt.QVBoxLayout
|
|
idsEdits []*qt.QLineEdit
|
|
kindsVBox *qt.QVBoxLayout
|
|
kindRows []reqKindRow
|
|
tagsVBox *qt.QVBoxLayout
|
|
tagRows []reqTagRow
|
|
tagsLayout *qt.QVBoxLayout
|
|
relaysHBox *qt.QHBoxLayout
|
|
relaysEdits []*qt.QLineEdit
|
|
sinceEdit *qt.QDateTimeEdit
|
|
sinceCheck *qt.QCheckBox
|
|
untilEdit *qt.QDateTimeEdit
|
|
untilCheck *qt.QCheckBox
|
|
limitSpin *qt.QSpinBox
|
|
limitCheck *qt.QCheckBox
|
|
|
|
filter nostr.Filter
|
|
|
|
outputEdit *qt.QTextEdit
|
|
resultsList *qt.QListWidget
|
|
sendButton *qt.QPushButton
|
|
stopButton *qt.QPushButton
|
|
clearButton *qt.QPushButton
|
|
exportButton *qt.QPushButton
|
|
|
|
subCancel context.CancelCauseFunc
|
|
}
|
|
|
|
type reqKindRow struct {
|
|
hbox *qt.QHBoxLayout
|
|
edit *qt.QLineEdit
|
|
label *qt.QLabel
|
|
}
|
|
|
|
type reqTagRow struct {
|
|
hbox *qt.QHBoxLayout
|
|
key *qt.QLineEdit
|
|
separator *qt.QLabel
|
|
vals []*qt.QLineEdit
|
|
}
|
|
|
|
var req = &reqVars{}
|
|
|
|
func (req *reqVars) addRelayEdit() {
|
|
edit := qt.NewQLineEdit(req.tab)
|
|
req.relaysEdits = append(req.relaysEdits, edit)
|
|
req.relaysHBox.AddWidget(edit.QWidget)
|
|
edit.OnTextChanged(func(text string) {
|
|
if strings.TrimSpace(text) != "" {
|
|
if edit == req.relaysEdits[len(req.relaysEdits)-1] {
|
|
req.addRelayEdit()
|
|
}
|
|
} else {
|
|
n := len(req.relaysEdits)
|
|
if n >= 2 && strings.TrimSpace(req.relaysEdits[n-1].Text()) == "" && strings.TrimSpace(req.relaysEdits[n-2].Text()) == "" {
|
|
req.relaysHBox.RemoveWidget(req.relaysEdits[n-1].QWidget)
|
|
req.relaysEdits[n-1].DeleteLater()
|
|
req.relaysEdits = req.relaysEdits[0 : n-1]
|
|
}
|
|
}
|
|
|
|
// update sendButton state
|
|
hasRelays := false
|
|
for _, edit := range req.relaysEdits {
|
|
if strings.TrimSpace(edit.Text()) != "" {
|
|
hasRelays = true
|
|
break
|
|
}
|
|
}
|
|
req.sendButton.SetEnabled(hasRelays)
|
|
})
|
|
edit.OnReturnPressed(func() {
|
|
req.subscribe()
|
|
})
|
|
}
|
|
|
|
func setupReqTab() *qt.QWidget {
|
|
req.tab = qt.NewQWidget(window.QWidget)
|
|
layout := qt.NewQVBoxLayout2()
|
|
req.tab.SetLayout(layout.QLayout)
|
|
|
|
// ids
|
|
idsLabel := qt.NewQLabel2()
|
|
idsLabel.SetText("ids:")
|
|
layout.AddWidget(idsLabel.QWidget)
|
|
req.idsVBox = qt.NewQVBoxLayout2()
|
|
layout.AddLayout(req.idsVBox.QLayout)
|
|
req.idsEdits = []*qt.QLineEdit{}
|
|
req.addId("")
|
|
|
|
// authors
|
|
authorsLabel := qt.NewQLabel2()
|
|
authorsLabel.SetText("authors:")
|
|
layout.AddWidget(authorsLabel.QWidget)
|
|
req.authorsVBox = qt.NewQVBoxLayout2()
|
|
layout.AddLayout(req.authorsVBox.QLayout)
|
|
req.authorsEdits = []*qt.QLineEdit{}
|
|
req.addAuthor("")
|
|
|
|
// kinds
|
|
kindsLabel := qt.NewQLabel2()
|
|
kindsLabel.SetText("kinds:")
|
|
layout.AddWidget(kindsLabel.QWidget)
|
|
req.kindsVBox = qt.NewQVBoxLayout2()
|
|
layout.AddLayout(req.kindsVBox.QLayout)
|
|
req.kindRows = []reqKindRow{}
|
|
req.addKind("")
|
|
|
|
// tags
|
|
tagsLabel := qt.NewQLabel2()
|
|
tagsLabel.SetText("tags:")
|
|
layout.AddWidget(tagsLabel.QWidget)
|
|
req.tagsLayout = qt.NewQVBoxLayout2()
|
|
layout.AddLayout(req.tagsLayout.QLayout)
|
|
req.tagRows = make([]reqTagRow, 0, 2)
|
|
req.addTagRow("", []string{""})
|
|
|
|
// numeric and boolean inputs
|
|
qtime := qt.NewQDateTime()
|
|
qtime.SetMSecsSinceEpoch(time.Now().UnixMilli())
|
|
inputsHBox := qt.NewQHBoxLayout2()
|
|
layout.AddLayout(inputsHBox.QLayout)
|
|
|
|
// limit
|
|
limitHBox := qt.NewQHBoxLayout2()
|
|
inputsHBox.AddLayout(limitHBox.QLayout)
|
|
limitLabel := qt.NewQLabel2()
|
|
limitLabel.SetText("limit:")
|
|
limitHBox.AddWidget(limitLabel.QWidget)
|
|
req.limitCheck = qt.NewQCheckBox(req.tab)
|
|
req.limitCheck.SetChecked(true)
|
|
limitHBox.AddWidget(req.limitCheck.QWidget)
|
|
req.limitSpin = qt.NewQSpinBox(req.tab)
|
|
req.limitSpin.SetEnabled(true)
|
|
req.limitSpin.SetMinimum(0)
|
|
req.limitSpin.SetMaximum(1000)
|
|
req.limitSpin.SetValue(10)
|
|
limitHBox.AddWidget(req.limitSpin.QWidget)
|
|
req.limitCheck.OnStateChanged(func(state int) {
|
|
req.limitSpin.SetEnabled(state == 2) // 2 is checked
|
|
req.updateReq()
|
|
})
|
|
req.limitSpin.OnValueChanged(func(int) {
|
|
req.updateReq()
|
|
})
|
|
|
|
// since
|
|
sinceHBox := qt.NewQHBoxLayout2()
|
|
sinceHBox.AddStretch()
|
|
inputsHBox.AddLayout(sinceHBox.QLayout)
|
|
sinceLabel := qt.NewQLabel2()
|
|
sinceLabel.SetText("since:")
|
|
sinceHBox.AddWidget(sinceLabel.QWidget)
|
|
req.sinceCheck = qt.NewQCheckBox(req.tab)
|
|
req.sinceCheck.SetChecked(false)
|
|
sinceHBox.AddWidget(req.sinceCheck.QWidget)
|
|
req.sinceEdit = qt.NewQDateTimeEdit(req.tab)
|
|
req.sinceEdit.SetEnabled(false)
|
|
req.sinceEdit.SetDateTime(qtime)
|
|
sinceHBox.AddWidget(req.sinceEdit.QWidget)
|
|
req.sinceCheck.OnStateChanged(func(state int) {
|
|
req.sinceEdit.SetEnabled(state == 2) // 2 is checked
|
|
req.updateReq()
|
|
})
|
|
req.sinceEdit.OnDateTimeChanged(func(*qt.QDateTime) {
|
|
req.updateReq()
|
|
})
|
|
|
|
// until
|
|
untilHBox := qt.NewQHBoxLayout2()
|
|
untilHBox.AddStretch()
|
|
inputsHBox.AddLayout(untilHBox.QLayout)
|
|
untilLabel := qt.NewQLabel2()
|
|
untilLabel.SetText("until:")
|
|
untilHBox.AddWidget(untilLabel.QWidget)
|
|
req.untilCheck = qt.NewQCheckBox(req.tab)
|
|
req.untilCheck.SetChecked(false)
|
|
untilHBox.AddWidget(req.untilCheck.QWidget)
|
|
req.untilEdit = qt.NewQDateTimeEdit(req.tab)
|
|
req.untilEdit.SetEnabled(false)
|
|
req.untilEdit.SetDateTime(qtime)
|
|
untilHBox.AddWidget(req.untilEdit.QWidget)
|
|
req.untilCheck.OnStateChanged(func(state int) {
|
|
req.untilEdit.SetEnabled(state == 2) // 2 is checked
|
|
req.updateReq()
|
|
})
|
|
req.untilEdit.OnDateTimeChanged(func(*qt.QDateTime) {
|
|
req.updateReq()
|
|
})
|
|
|
|
// output
|
|
outputHBox := qt.NewQHBoxLayout2()
|
|
layout.AddLayout(outputHBox.QLayout)
|
|
outputLabel := qt.NewQLabel2()
|
|
outputLabel.SetText("filter:")
|
|
outputHBox.AddWidget(outputLabel.QWidget)
|
|
req.outputEdit = qt.NewQTextEdit(req.tab)
|
|
req.outputEdit.SetReadOnly(true)
|
|
req.outputEdit.SetMaximumHeight(100)
|
|
outputHBox.AddWidget(req.outputEdit.QWidget)
|
|
|
|
req.sendButton = qt.NewQPushButton5("send request", req.tab)
|
|
req.sendButton.OnClicked(req.subscribe)
|
|
req.sendButton.SetEnabled(false)
|
|
|
|
req.stopButton = qt.NewQPushButton5("stop", req.tab)
|
|
req.stopButton.OnClicked(req.cancelSubscription)
|
|
req.stopButton.SetEnabled(false)
|
|
|
|
req.clearButton = qt.NewQPushButton5("clear", req.tab)
|
|
req.clearButton.OnClicked(func() {
|
|
req.resultsList.Clear()
|
|
})
|
|
|
|
req.exportButton = qt.NewQPushButton5("export", req.tab)
|
|
req.exportButton.OnClicked(func() {
|
|
if req.resultsList.Count() == 0 {
|
|
setStatus(tabs.req, "no results to export")
|
|
return
|
|
}
|
|
|
|
fileDialog := qt.NewQFileDialog(req.tab)
|
|
fileDialog.SetWindowTitle("Export Results")
|
|
fileDialog.SetAcceptMode(qt.QFileDialog__AcceptSave)
|
|
fileDialog.SetNameFilter("JSONL Files (*.jsonl)")
|
|
if fileDialog.Exec() == int(qt.QDialog__Accepted) {
|
|
selectedFiles := fileDialog.SelectedFiles()
|
|
if len(selectedFiles) > 0 {
|
|
filePath := selectedFiles[0]
|
|
go func() {
|
|
file, err := os.Create(filePath)
|
|
if err != nil {
|
|
mainthread.Wait(func() {
|
|
setStatus(tabs.req, "failed to create file: %v", err)
|
|
})
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
for i := 0; i < req.resultsList.Count(); i++ {
|
|
item := req.resultsList.Item(i)
|
|
if item != nil {
|
|
line := item.Text() + "\n"
|
|
file.WriteString(line)
|
|
}
|
|
}
|
|
|
|
mainthread.Wait(func() {
|
|
setStatus(tabs.req, "exported %d events to %s", req.resultsList.Count(), filePath)
|
|
})
|
|
}()
|
|
}
|
|
}
|
|
})
|
|
|
|
// relays
|
|
relaysHBox := qt.NewQHBoxLayout2()
|
|
req.relaysHBox = relaysHBox
|
|
layout.AddLayout(relaysHBox.QLayout)
|
|
relaysLabel := qt.NewQLabel2()
|
|
relaysLabel.SetText("relays:")
|
|
relaysHBox.AddWidget(relaysLabel.QWidget)
|
|
|
|
req.relaysEdits = []*qt.QLineEdit{}
|
|
req.addRelayEdit()
|
|
|
|
// results
|
|
resultsVBox := qt.NewQVBoxLayout2()
|
|
resultsLabel := qt.NewQLabel2()
|
|
resultsLabel.SetText("results:")
|
|
req.resultsList = qt.NewQListWidget(req.tab)
|
|
|
|
resultsVBox.AddWidget(resultsLabel.QWidget)
|
|
resultsVBox.AddWidget(req.resultsList.QWidget)
|
|
|
|
subscribeHBox := qt.NewQHBoxLayout2()
|
|
layout.AddLayout(subscribeHBox.QLayout)
|
|
buttonsVBox := qt.NewQVBoxLayout2()
|
|
subscribeHBox.AddLayout(buttonsVBox.QLayout)
|
|
|
|
buttonsVBox.AddWidget(req.sendButton.QWidget)
|
|
buttonsVBox.AddWidget(req.stopButton.QWidget)
|
|
buttonsVBox.AddWidget(req.clearButton.QWidget)
|
|
buttonsVBox.AddWidget(req.exportButton.QWidget)
|
|
|
|
subscribeHBox.AddLayout(resultsVBox.QLayout)
|
|
|
|
// double-click to show pretty JSON
|
|
req.resultsList.OnItemDoubleClicked(func(item *qt.QListWidgetItem) {
|
|
var evt nostr.Event
|
|
if err := json.Unmarshal([]byte(item.Text()), &evt); err != nil {
|
|
return
|
|
}
|
|
pretty, _ := json.MarshalIndent(evt, "", " ")
|
|
dialog := qt.NewQDialog(window.QWidget)
|
|
dialog.SetWindowTitle("event")
|
|
dialog.SetMinimumWidth(400)
|
|
dialog.SetMinimumHeight(500)
|
|
dlayout := qt.NewQVBoxLayout2()
|
|
dialog.SetLayout(dlayout.QLayout)
|
|
textEdit := qt.NewQTextEdit(dialog.QWidget)
|
|
textEdit.SetReadOnly(true)
|
|
textEdit.SetPlainText(string(pretty))
|
|
dlayout.AddWidget(textEdit.QWidget)
|
|
|
|
// buttons
|
|
buttonsHBox := qt.NewQHBoxLayout2()
|
|
dlayout.AddLayout(buttonsHBox.QLayout)
|
|
|
|
editButton := qt.NewQPushButton5("manual edit "+ARROW, dialog.QWidget)
|
|
editButton.OnClicked(func() {
|
|
paste.inputEdit.SetPlainText(string(pretty))
|
|
tabWidget.SetCurrentIndex(tabs.paste)
|
|
dialog.Close()
|
|
})
|
|
buttonsHBox.AddWidget(editButton.QWidget)
|
|
|
|
publishButton := qt.NewQPushButton5("structured edit "+ARROW, dialog.QWidget)
|
|
publishButton.OnClicked(func() {
|
|
event.populate(evt)
|
|
tabWidget.SetCurrentIndex(tabs.event)
|
|
dialog.Close()
|
|
})
|
|
buttonsHBox.AddWidget(publishButton.QWidget)
|
|
|
|
closeButton := qt.NewQPushButton5("close", dialog.QWidget)
|
|
closeButton.OnClicked(func() { dialog.Close() })
|
|
buttonsHBox.AddWidget(closeButton.QWidget)
|
|
|
|
dialog.Exec()
|
|
})
|
|
|
|
return req.tab
|
|
}
|
|
|
|
func (req *reqVars) updateReq() {
|
|
req.filter = nostr.Filter{}
|
|
|
|
// collect authors
|
|
authors := []nostr.PubKey{}
|
|
for _, edit := range req.authorsEdits {
|
|
if pk, err := parsePubKey(strings.TrimSpace(edit.Text())); err == nil {
|
|
authors = append(authors, pk)
|
|
}
|
|
}
|
|
|
|
if len(authors) > 0 {
|
|
req.filter.Authors = authors
|
|
}
|
|
|
|
// collect ids
|
|
ids := []nostr.ID{}
|
|
for _, edit := range req.idsEdits {
|
|
if id, err := parseEventID(strings.TrimSpace(edit.Text())); err == nil {
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
if len(ids) > 0 {
|
|
req.filter.IDs = ids
|
|
}
|
|
|
|
// collect kinds
|
|
kinds := []nostr.Kind{}
|
|
for _, kindRow := range req.kindRows {
|
|
text := strings.TrimSpace(kindRow.edit.Text())
|
|
kindRow.label.SetText("")
|
|
if k, err := strconv.Atoi(text); err == nil {
|
|
kind := nostr.Kind(k)
|
|
kinds = append(kinds, kind)
|
|
|
|
// update kind label
|
|
name := kind.Name()
|
|
if name != "unknown" {
|
|
kindRow.label.SetText(name)
|
|
}
|
|
}
|
|
}
|
|
if len(kinds) > 0 {
|
|
req.filter.Kinds = kinds
|
|
}
|
|
|
|
// collect tags
|
|
tags := make(map[string][]string)
|
|
for _, tagRow := range req.tagRows {
|
|
key := strings.TrimSpace(tagRow.key.Text())
|
|
if key == "" || len(tagRow.vals) == 0 {
|
|
continue
|
|
}
|
|
|
|
values := make([]string, 0, len(tagRow.vals))
|
|
for _, edit := range tagRow.vals {
|
|
text := strings.TrimSpace(edit.Text())
|
|
if text != "" {
|
|
values = append(values, text)
|
|
}
|
|
}
|
|
if len(values) > 0 {
|
|
tags[key] = values
|
|
}
|
|
}
|
|
if len(tags) > 0 {
|
|
req.filter.Tags = tags
|
|
}
|
|
|
|
// since
|
|
if req.sinceCheck.IsChecked() && req.sinceEdit.DateTime().IsValid() {
|
|
ts := nostr.Timestamp(req.sinceEdit.DateTime().ToMSecsSinceEpoch() / 1000)
|
|
req.filter.Since = ts
|
|
}
|
|
|
|
// until
|
|
if req.untilCheck.IsChecked() && req.untilEdit.DateTime().IsValid() {
|
|
ts := nostr.Timestamp(req.untilEdit.DateTime().ToMSecsSinceEpoch() / 1000)
|
|
req.filter.Until = ts
|
|
}
|
|
|
|
// limit
|
|
if req.limitCheck.IsChecked() {
|
|
if req.limitSpin.Value() > 0 {
|
|
req.filter.Limit = req.limitSpin.Value()
|
|
} else {
|
|
req.filter.LimitZero = true
|
|
}
|
|
}
|
|
|
|
jsonBytes, _ := json.Marshal(req.filter)
|
|
req.outputEdit.SetPlainText(string(jsonBytes))
|
|
}
|
|
|
|
func (req *reqVars) cancelSubscription() {
|
|
if req.subCancel != nil {
|
|
req.subCancel(manualCancel)
|
|
req.subCancel = nil
|
|
req.stopButton.SetEnabled(false)
|
|
}
|
|
}
|
|
|
|
func (req *reqVars) subscribe() {
|
|
req.cancelSubscription()
|
|
req.stopButton.SetEnabled(true)
|
|
|
|
// collect relays
|
|
relays := []string{}
|
|
for _, edit := range req.relaysEdits {
|
|
url := strings.TrimSpace(edit.Text())
|
|
if url != "" {
|
|
relays = append(relays, url)
|
|
}
|
|
}
|
|
if len(relays) == 0 {
|
|
setStatus(tabs.req, "no relays specified")
|
|
return
|
|
}
|
|
|
|
// subscribe
|
|
var eoseChan chan struct{}
|
|
var eventsChan chan nostr.RelayEvent
|
|
ctx, cancel := context.WithCancelCause(ctx)
|
|
req.subCancel = cancel
|
|
|
|
if len(relays) == 1 {
|
|
relay, err := sys.Pool.EnsureRelay(relays[0])
|
|
if err != nil {
|
|
setStatus(tabs.req, "failed to connect to %s: %s", niceRelayURL(relays[0]), err)
|
|
return
|
|
}
|
|
|
|
setStatus(tabs.req, "subscribed to "+niceRelayURL(relay.URL))
|
|
sub, err := relay.Subscribe(ctx, req.filter, nostr.SubscriptionOptions{
|
|
Label: "vnak-req-1",
|
|
})
|
|
if err != nil {
|
|
setStatus(tabs.req, "failed to subscribe to %s: %s", niceRelayURL(relay.URL), err)
|
|
return
|
|
}
|
|
|
|
eventsChan = make(chan nostr.RelayEvent)
|
|
go func() {
|
|
for event := range sub.Events {
|
|
eventsChan <- nostr.RelayEvent{
|
|
Event: event,
|
|
Relay: relay,
|
|
}
|
|
}
|
|
}()
|
|
|
|
eoseChan = sub.EndOfStoredEvents
|
|
|
|
go func() {
|
|
reason := <-sub.ClosedReason
|
|
req.subCancel = nil
|
|
mainthread.Wait(func() {
|
|
setStatus(tabs.req, "subscription closed: %s", reason)
|
|
req.stopButton.SetEnabled(false)
|
|
})
|
|
}()
|
|
} else {
|
|
setStatus(tabs.req, "subscribed to "+strings.Join(niceRelayURLs(relays), ", "))
|
|
eventsChan, eoseChan = sys.Pool.SubscribeManyNotifyEOSE(ctx, relays, req.filter, nostr.SubscriptionOptions{Label: "vnak-req"})
|
|
}
|
|
|
|
// collect events
|
|
var eosed atomic.Bool
|
|
go func() {
|
|
for ie := range eventsChan {
|
|
jsonBytes, _ := json.Marshal(ie.Event)
|
|
mainthread.Wait(func() {
|
|
item := qt.NewQListWidgetItem2(string(jsonBytes))
|
|
|
|
if eosed.Load() {
|
|
req.resultsList.InsertItem(0, item)
|
|
} else {
|
|
req.resultsList.AddItemWithItem(item)
|
|
}
|
|
})
|
|
}
|
|
req.subCancel = nil
|
|
mainthread.Wait(func() {
|
|
setStatus(tabs.req, "subscription ended")
|
|
req.stopButton.SetEnabled(false)
|
|
})
|
|
}()
|
|
|
|
go func() {
|
|
<-eoseChan
|
|
eosed.Store(true)
|
|
}()
|
|
}
|
|
|
|
func (req *reqVars) populate(filter nostr.Filter, relays []string) {
|
|
// clear all authors except the first, set the first to ""
|
|
for _, authorEdit := range req.authorsEdits {
|
|
req.authorsVBox.RemoveWidget(authorEdit.QWidget)
|
|
authorEdit.DeleteLater()
|
|
}
|
|
req.authorsEdits = req.authorsEdits[:0]
|
|
|
|
// add authors
|
|
for _, author := range filter.Authors {
|
|
req.addAuthor(author.Hex())
|
|
}
|
|
req.addAuthor("") // extra
|
|
|
|
// clear all ids except the first, set the first to ""
|
|
for _, idEdit := range req.idsEdits {
|
|
req.idsVBox.RemoveWidget(idEdit.QWidget)
|
|
idEdit.DeleteLater()
|
|
}
|
|
req.idsEdits = req.idsEdits[:0]
|
|
|
|
// add ids
|
|
for _, id := range filter.IDs {
|
|
req.addId(id.Hex())
|
|
}
|
|
req.addId("") // extra
|
|
|
|
// clear all kinds except the first, set the first to ""
|
|
for _, kindRow := range req.kindRows {
|
|
kindRow.hbox.RemoveWidget(kindRow.label.QWidget)
|
|
kindRow.hbox.RemoveWidget(kindRow.edit.QWidget)
|
|
kindRow.label.DeleteLater()
|
|
kindRow.edit.DeleteLater()
|
|
req.kindsVBox.RemoveItem(kindRow.hbox.QLayoutItem)
|
|
kindRow.hbox.DeleteLater()
|
|
}
|
|
req.kindRows = req.kindRows[:0]
|
|
|
|
// add kinds
|
|
for _, kind := range filter.Kinds {
|
|
req.addKind(strconv.Itoa(int(kind)))
|
|
}
|
|
req.addKind("") // extra
|
|
|
|
// clear all tag items and rows
|
|
for _, tagRow := range req.tagRows {
|
|
tagRow.hbox.RemoveWidget(tagRow.key.QWidget)
|
|
tagRow.key.QWidget.DeleteLater()
|
|
tagRow.hbox.RemoveWidget(tagRow.separator.QWidget)
|
|
tagRow.separator.DeleteLater()
|
|
|
|
for _, item := range tagRow.vals {
|
|
tagRow.hbox.RemoveWidget(item.QWidget)
|
|
item.DeleteLater()
|
|
}
|
|
req.tagsLayout.RemoveItem(tagRow.hbox.QLayoutItem)
|
|
tagRow.hbox.DeleteLater()
|
|
}
|
|
req.tagRows = req.tagRows[:0]
|
|
|
|
// add tags according to filter
|
|
for key, values := range filter.Tags {
|
|
req.addTagRow(key, values)
|
|
}
|
|
req.addTagRow("", []string{""}) // extra
|
|
|
|
// set since, until, limit
|
|
if filter.Since != 0 {
|
|
req.sinceCheck.SetChecked(true)
|
|
dt := qt.NewQDateTime()
|
|
dt.SetMSecsSinceEpoch(int64(filter.Since) * 1000)
|
|
req.sinceEdit.SetDateTime(dt)
|
|
} else {
|
|
req.sinceCheck.SetChecked(false)
|
|
}
|
|
|
|
if filter.Until != 0 {
|
|
req.untilCheck.SetChecked(true)
|
|
dt := qt.NewQDateTime()
|
|
dt.SetMSecsSinceEpoch(int64(filter.Until) * 1000)
|
|
req.untilEdit.SetDateTime(dt)
|
|
} else {
|
|
req.untilCheck.SetChecked(false)
|
|
}
|
|
|
|
if filter.Limit != 0 {
|
|
req.limitCheck.SetChecked(true)
|
|
req.limitSpin.SetValue(filter.Limit)
|
|
} else if filter.LimitZero {
|
|
req.limitCheck.SetChecked(true)
|
|
req.limitSpin.SetValue(0)
|
|
} else {
|
|
req.limitCheck.SetChecked(false)
|
|
}
|
|
|
|
// set relays
|
|
for _, relayEdit := range req.relaysEdits {
|
|
req.relaysHBox.RemoveWidget(relayEdit.QWidget)
|
|
relayEdit.DeleteLater()
|
|
}
|
|
req.relaysEdits = req.relaysEdits[:0]
|
|
for i, relay := range relays {
|
|
req.addRelayEdit()
|
|
req.relaysEdits[i].SetText(relay)
|
|
}
|
|
|
|
req.updateReq()
|
|
}
|
|
|
|
func (req *reqVars) addAuthor(value string) {
|
|
edit := qt.NewQLineEdit(req.tab)
|
|
edit.SetText(value)
|
|
req.authorsEdits = append(req.authorsEdits, edit)
|
|
req.authorsVBox.AddWidget(edit.QWidget)
|
|
edit.OnTextChanged(func(text string) {
|
|
if strings.TrimSpace(text) != "" {
|
|
if edit == req.authorsEdits[len(req.authorsEdits)-1] {
|
|
req.addAuthor("")
|
|
}
|
|
} else {
|
|
n := len(req.authorsEdits)
|
|
if n >= 2 && strings.TrimSpace(req.authorsEdits[n-1].Text()) == "" && strings.TrimSpace(req.authorsEdits[n-2].Text()) == "" {
|
|
req.authorsVBox.RemoveWidget(req.authorsEdits[n-1].QWidget)
|
|
req.authorsEdits[n-1].DeleteLater()
|
|
req.authorsEdits = req.authorsEdits[0 : n-1]
|
|
}
|
|
}
|
|
req.updateReq()
|
|
})
|
|
}
|
|
|
|
func (req *reqVars) addId(text string) {
|
|
edit := qt.NewQLineEdit(req.tab)
|
|
edit.SetText(text)
|
|
req.idsEdits = append(req.idsEdits, edit)
|
|
req.idsVBox.AddWidget(edit.QWidget)
|
|
edit.OnTextChanged(func(text string) {
|
|
if strings.TrimSpace(text) != "" {
|
|
if edit == req.idsEdits[len(req.idsEdits)-1] {
|
|
req.addId("")
|
|
}
|
|
} else {
|
|
n := len(req.idsEdits)
|
|
if n >= 2 && strings.TrimSpace(req.idsEdits[n-1].Text()) == "" && strings.TrimSpace(req.idsEdits[n-2].Text()) == "" {
|
|
req.idsVBox.RemoveWidget(req.idsEdits[n-1].QWidget)
|
|
req.idsEdits[n-1].DeleteLater()
|
|
req.idsEdits = req.idsEdits[0 : n-1]
|
|
}
|
|
}
|
|
req.updateReq()
|
|
})
|
|
}
|
|
|
|
func (req *reqVars) addKind(text string) {
|
|
hbox := qt.NewQHBoxLayout2()
|
|
req.kindsVBox.AddLayout(hbox.QLayout)
|
|
edit := qt.NewQLineEdit(req.tab)
|
|
edit.SetText(text)
|
|
hbox.AddWidget(edit.QWidget)
|
|
label := qt.NewQLabel2()
|
|
hbox.AddWidget(label.QWidget)
|
|
|
|
req.kindRows = append(req.kindRows, reqKindRow{
|
|
hbox: hbox,
|
|
label: label,
|
|
edit: edit,
|
|
})
|
|
|
|
edit.OnTextChanged(func(text string) {
|
|
if strings.TrimSpace(text) != "" {
|
|
if edit == req.kindRows[len(req.kindRows)-1].edit {
|
|
req.addKind("")
|
|
}
|
|
} else {
|
|
n := len(req.kindRows)
|
|
if n >= 2 && strings.TrimSpace(req.kindRows[n-1].edit.Text()) == "" && strings.TrimSpace(req.kindRows[n-2].edit.Text()) == "" {
|
|
last := req.kindRows[n-1]
|
|
req.kindsVBox.RemoveItem(last.hbox.QLayoutItem)
|
|
last.hbox.RemoveWidget(req.kindRows[n-1].edit.QWidget)
|
|
last.hbox.RemoveWidget(req.kindRows[n-1].label.QWidget)
|
|
last.edit.DeleteLater()
|
|
last.label.DeleteLater()
|
|
last.hbox.DeleteLater()
|
|
req.kindRows = req.kindRows[:n-1]
|
|
}
|
|
}
|
|
req.updateReq()
|
|
})
|
|
}
|
|
|
|
func (req *reqVars) addTagRow(name string, values []string) {
|
|
hbox := qt.NewQHBoxLayout2()
|
|
req.tagsLayout.AddLayout(hbox.QLayout)
|
|
valuesEdits := []*qt.QLineEdit{}
|
|
y := len(req.tagRows)
|
|
|
|
var perhapsDeleteRow func()
|
|
|
|
// name
|
|
editName := qt.NewQLineEdit(req.tab)
|
|
editName.SetText(name)
|
|
hbox.AddWidget(editName.QWidget)
|
|
editName.OnTextChanged(func(text string) {
|
|
if strings.TrimSpace(text) != "" {
|
|
if y == len(req.tagRows)-1 {
|
|
req.addTagRow("", []string{""})
|
|
}
|
|
} else {
|
|
perhapsDeleteRow()
|
|
}
|
|
req.updateReq()
|
|
})
|
|
|
|
// separator
|
|
separator := qt.NewQLabel2()
|
|
separator.SetText(": ")
|
|
hbox.AddWidget(separator.QWidget)
|
|
|
|
// keep track of everything
|
|
req.tagRows = append(req.tagRows, reqTagRow{
|
|
hbox: hbox,
|
|
key: editName,
|
|
separator: separator,
|
|
})
|
|
|
|
var addValue func(string)
|
|
addValue = func(val string) {
|
|
edit := qt.NewQLineEdit(req.tab)
|
|
edit.SetText(val)
|
|
hbox.AddWidget(edit.QWidget)
|
|
x := len(valuesEdits)
|
|
valuesEdits = append(valuesEdits, edit)
|
|
req.tagRows[y].vals = valuesEdits
|
|
|
|
edit.OnTextChanged(func(text string) {
|
|
if strings.TrimSpace(text) != "" {
|
|
if x == len(valuesEdits)-1 {
|
|
addValue("")
|
|
}
|
|
} else {
|
|
nItems := len(valuesEdits)
|
|
if nItems >= 2 &&
|
|
strings.TrimSpace(valuesEdits[nItems-1].Text()) == "" &&
|
|
strings.TrimSpace(valuesEdits[nItems-2].Text()) == "" {
|
|
// when the last values of a tag row are empty remove the last one
|
|
last := valuesEdits[nItems-1]
|
|
hbox.RemoveWidget(last.QWidget)
|
|
last.DeleteLater()
|
|
valuesEdits = valuesEdits[0 : nItems-1]
|
|
req.tagRows[y].vals = valuesEdits
|
|
}
|
|
|
|
perhapsDeleteRow()
|
|
}
|
|
req.updateReq()
|
|
})
|
|
}
|
|
|
|
perhapsDeleteRow = func() {
|
|
nRows := len(req.tagRows)
|
|
hasValue := func(e *qt.QLineEdit) bool { return strings.TrimSpace(e.Text()) != "" }
|
|
if nRows >= 2 &&
|
|
!slices.ContainsFunc(req.tagRows[nRows-1].vals, hasValue) &&
|
|
!slices.ContainsFunc(req.tagRows[nRows-2].vals, hasValue) &&
|
|
strings.TrimSpace(req.tagRows[nRows-1].key.Text()) == "" &&
|
|
strings.TrimSpace(req.tagRows[nRows-2].key.Text()) == "" {
|
|
// when the two last rows are completely empty remove the last one:
|
|
last := req.tagRows[nRows-1]
|
|
last.hbox.RemoveWidget(last.separator.QWidget)
|
|
last.separator.DeleteLater()
|
|
last.hbox.RemoveWidget(last.key.QWidget)
|
|
last.key.DeleteLater()
|
|
for _, val := range last.vals {
|
|
last.hbox.RemoveWidget(val.QWidget)
|
|
val.DeleteLater()
|
|
}
|
|
|
|
req.tagsLayout.RemoveItem(last.hbox.QLayoutItem)
|
|
req.tagRows = req.tagRows[0 : nRows-1]
|
|
}
|
|
}
|
|
|
|
for _, val := range values {
|
|
addValue(val)
|
|
}
|
|
}
|