mirror of
https://github.com/fiatjaf/vnak.git
synced 2026-08-09 07:44:58 +00:00
702 lines
18 KiB
Go
702 lines
18 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"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
|
|
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
|
|
}
|
|
|
|
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 setupReqTab() *qt.QWidget {
|
|
req.tab = qt.NewQWidget(window.QWidget)
|
|
layout := qt.NewQVBoxLayout2()
|
|
req.tab.SetLayout(layout.QLayout)
|
|
|
|
// 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("")
|
|
|
|
// 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("")
|
|
|
|
// 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{""})
|
|
|
|
// since
|
|
sinceHBox := qt.NewQHBoxLayout2()
|
|
layout.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)
|
|
{
|
|
qtime := qt.NewQDateTime()
|
|
qtime.SetMSecsSinceEpoch(time.Now().UnixMilli())
|
|
req.sinceEdit.SetDateTime(qtime)
|
|
}
|
|
sinceHBox.AddWidget(req.sinceEdit.QWidget)
|
|
req.sinceCheck.OnStateChanged(func(state int) {
|
|
req.sinceEdit.SetEnabled(state == 2) // 2 is checked
|
|
if state == 2 {
|
|
qtime := qt.NewQDateTime()
|
|
qtime.SetMSecsSinceEpoch(time.Now().UnixMilli())
|
|
req.sinceEdit.SetDateTime(qtime)
|
|
}
|
|
req.updateReq()
|
|
})
|
|
req.sinceEdit.OnDateTimeChanged(func(*qt.QDateTime) {
|
|
req.updateReq()
|
|
})
|
|
|
|
// until
|
|
untilHBox := qt.NewQHBoxLayout2()
|
|
layout.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)
|
|
{
|
|
qtime := qt.NewQDateTime()
|
|
qtime.SetMSecsSinceEpoch(time.Now().UnixMilli())
|
|
req.untilEdit.SetDateTime(qtime)
|
|
}
|
|
untilHBox.AddWidget(req.untilEdit.QWidget)
|
|
req.untilCheck.OnStateChanged(func(state int) {
|
|
req.untilEdit.SetEnabled(state == 2) // 2 is checked
|
|
if state == 2 {
|
|
qtime := qt.NewQDateTime()
|
|
qtime.SetMSecsSinceEpoch(time.Now().UnixMilli())
|
|
req.untilEdit.SetDateTime(qtime)
|
|
}
|
|
req.updateReq()
|
|
})
|
|
req.untilEdit.OnDateTimeChanged(func(*qt.QDateTime) {
|
|
req.updateReq()
|
|
})
|
|
|
|
// limit
|
|
limitHBox := qt.NewQHBoxLayout2()
|
|
layout.AddLayout(limitHBox.QLayout)
|
|
limitLabel := qt.NewQLabel2()
|
|
limitLabel.SetText("limit:")
|
|
limitHBox.AddWidget(limitLabel.QWidget)
|
|
req.limitCheck = qt.NewQCheckBox(req.tab)
|
|
req.limitCheck.SetChecked(false)
|
|
limitHBox.AddWidget(req.limitCheck.QWidget)
|
|
req.limitSpin = qt.NewQSpinBox(req.tab)
|
|
req.limitSpin.SetEnabled(false)
|
|
req.limitSpin.SetMinimum(0)
|
|
req.limitSpin.SetMaximum(1000)
|
|
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()
|
|
})
|
|
|
|
// output
|
|
outputLabel := qt.NewQLabel2()
|
|
outputLabel.SetText("filter:")
|
|
layout.AddWidget(outputLabel.QWidget)
|
|
req.outputEdit = qt.NewQTextEdit(req.tab)
|
|
req.outputEdit.SetReadOnly(true)
|
|
req.outputEdit.SetMaximumHeight(100)
|
|
layout.AddWidget(req.outputEdit.QWidget)
|
|
|
|
// send button
|
|
buttonHBox := qt.NewQHBoxLayout2()
|
|
sendButton := qt.NewQPushButton5("send request", req.tab)
|
|
buttonHBox.AddWidget(sendButton.QWidget)
|
|
buttonHBox.AddStretch()
|
|
|
|
// relays
|
|
relaysLabel := qt.NewQLabel2()
|
|
relaysLabel.SetText("relays:")
|
|
layout.AddWidget(relaysLabel.QWidget)
|
|
relaysHBox := qt.NewQHBoxLayout2()
|
|
layout.AddLayout(relaysHBox.QLayout)
|
|
req.relaysEdits = []*qt.QLineEdit{}
|
|
var addRelayEdit func()
|
|
addRelayEdit = func() {
|
|
edit := qt.NewQLineEdit(req.tab)
|
|
req.relaysEdits = append(req.relaysEdits, edit)
|
|
relaysHBox.AddWidget(edit.QWidget)
|
|
edit.OnTextChanged(func(text string) {
|
|
if strings.TrimSpace(text) != "" {
|
|
if edit == req.relaysEdits[len(req.relaysEdits)-1] {
|
|
addRelayEdit()
|
|
}
|
|
} else {
|
|
n := len(req.relaysEdits)
|
|
if n >= 2 && strings.TrimSpace(req.relaysEdits[n-1].Text()) == "" && strings.TrimSpace(req.relaysEdits[n-2].Text()) == "" {
|
|
relaysHBox.RemoveWidget(req.relaysEdits[n-1].QWidget)
|
|
req.relaysEdits[n-1].DeleteLater()
|
|
req.relaysEdits = req.relaysEdits[0 : n-1]
|
|
}
|
|
}
|
|
})
|
|
edit.OnReturnPressed(func() {
|
|
sendButton.Click()
|
|
})
|
|
}
|
|
addRelayEdit()
|
|
|
|
layout.AddLayout(buttonHBox.QLayout)
|
|
|
|
// results
|
|
resultsLabel := qt.NewQLabel2()
|
|
resultsLabel.SetText("results:")
|
|
layout.AddWidget(resultsLabel.QWidget)
|
|
req.resultsList = qt.NewQListWidget(req.tab)
|
|
req.resultsList.SetMinimumHeight(200)
|
|
layout.AddWidget(req.resultsList.QWidget)
|
|
|
|
// double-click to show pretty JSON
|
|
req.resultsList.OnItemDoubleClicked(func(item *qt.QListWidgetItem) {
|
|
var event nostr.Event
|
|
if err := json.Unmarshal([]byte(item.Text()), &event); err != nil {
|
|
return
|
|
}
|
|
pretty, _ := json.MarshalIndent(event, "", " ")
|
|
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)
|
|
closeButton := qt.NewQPushButton5("close", dialog.QWidget)
|
|
closeButton.OnClicked(func() { dialog.Close() })
|
|
dlayout.AddWidget(closeButton.QWidget)
|
|
dialog.Exec()
|
|
})
|
|
|
|
sendButton.OnClicked(func() {
|
|
req.subscribe()
|
|
})
|
|
|
|
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())
|
|
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)
|
|
} else {
|
|
kindRow.label.SetText("")
|
|
}
|
|
}
|
|
}
|
|
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) subscribe() {
|
|
// collect relays
|
|
relays := []string{}
|
|
for _, edit := range req.relaysEdits {
|
|
url := strings.TrimSpace(edit.Text())
|
|
if url != "" {
|
|
relays = append(relays, url)
|
|
}
|
|
}
|
|
if len(relays) == 0 {
|
|
statusLabel.SetText("no relays specified")
|
|
return
|
|
}
|
|
|
|
// subscribe
|
|
var eoseChan chan struct{}
|
|
var eventsChan chan nostr.Event
|
|
|
|
if len(relays) == 1 {
|
|
relay, err := sys.Pool.EnsureRelay(relays[0])
|
|
if err != nil {
|
|
statusLabel.SetText(fmt.Sprintf("failed to connect to %s: %s", niceRelayURL(relays[0]), err))
|
|
return
|
|
}
|
|
|
|
statusLabel.SetText("subscribed to " + niceRelayURL(relay.URL))
|
|
sub, err := relay.Subscribe(ctx, req.filter, nostr.SubscriptionOptions{
|
|
Label: "nakv-req-1",
|
|
})
|
|
if err != nil {
|
|
statusLabel.SetText(fmt.Sprintf("failed to subscribe to %s: %s", niceRelayURL(relay.URL), err))
|
|
return
|
|
}
|
|
|
|
eventsChan = sub.Events
|
|
eoseChan = sub.EndOfStoredEvents
|
|
|
|
go func() {
|
|
reason := <-sub.ClosedReason
|
|
time.Sleep(time.Second)
|
|
mainthread.Wait(func() {
|
|
statusLabel.SetText(fmt.Sprintf("subscription closed: %s", reason))
|
|
})
|
|
}()
|
|
} else {
|
|
statusLabel.SetText("subscribed to " + strings.Join(niceRelayURLs(relays), ", "))
|
|
eoseChan = make(chan struct{})
|
|
eventsChan = make(chan nostr.Event)
|
|
|
|
go func() {
|
|
for ie := range sys.Pool.SubscribeManyNotifyEOSE(ctx, relays, req.filter, eoseChan,
|
|
nostr.SubscriptionOptions{
|
|
Label: "nakv-req",
|
|
},
|
|
) {
|
|
eventsChan <- ie.Event
|
|
}
|
|
}()
|
|
}
|
|
|
|
// collect events
|
|
eosed := false
|
|
go func() {
|
|
for event := range eventsChan {
|
|
jsonBytes, _ := json.Marshal(event)
|
|
mainthread.Wait(func() {
|
|
item := qt.NewQListWidgetItem2(string(jsonBytes))
|
|
|
|
if eosed {
|
|
req.resultsList.InsertItem(0, item)
|
|
} else {
|
|
req.resultsList.AddItemWithItem(item)
|
|
}
|
|
})
|
|
}
|
|
statusLabel.SetText("subscription ended")
|
|
}()
|
|
|
|
go func() {
|
|
<-eoseChan
|
|
eosed = true
|
|
}()
|
|
}
|
|
|
|
func (req *reqVars) populate(filter nostr.Filter) {
|
|
// 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)
|
|
}
|
|
|
|
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()) == "" {
|
|
hbox.RemoveWidget(valuesEdits[nItems-1].QWidget)
|
|
valuesEdits[nItems-1].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) {
|
|
req.tagsLayout.RemoveItem(hbox.QLayoutItem)
|
|
hbox.DeleteLater()
|
|
req.tagRows = req.tagRows[0 : nRows-1]
|
|
}
|
|
}
|
|
|
|
for _, val := range values {
|
|
addValue(val)
|
|
}
|
|
}
|