aboutsummaryrefslogtreecommitdiff
path: root/gui/internal/ui
diff options
context:
space:
mode:
Diffstat (limited to 'gui/internal/ui')
-rw-r--r--gui/internal/ui/forms.go187
1 files changed, 181 insertions, 6 deletions
diff --git a/gui/internal/ui/forms.go b/gui/internal/ui/forms.go
index 928c17e..1606903 100644
--- a/gui/internal/ui/forms.go
+++ b/gui/internal/ui/forms.go
@@ -55,6 +55,8 @@ type formsView struct {
forms []model.Form
sel int
editor *formEditor
+ settingRows []*settingRow
+ onSettings bool
pending glib.SourceHandle
quiet bool
commentsAcknowledged map[string]bool
@@ -110,9 +112,15 @@ func newFormsView(w *Window, owner *rulesView) *formsView {
f.root.Append(right)
f.list.ConnectRowSelected(func(row *gtk.ListBoxRow) {
- if row != nil && !f.quiet {
- f.show(row.Index())
+ if row == nil || f.quiet {
+ return
+ }
+ // Row 0 is the directory itself; the forms follow it.
+ if row.Index() == 0 {
+ f.showSettings()
+ return
}
+ f.show(row.Index() - 1)
})
f.add.ConnectClicked(f.onAdd)
f.del.ConnectClicked(f.onDelete)
@@ -133,6 +141,7 @@ func (f *formsView) reload() error {
keep := f.sel
f.quiet = true
clearList(f.list)
+ f.list.Append(settingsRow())
for _, form := range forms {
row := gtk.NewListBoxRow()
label := gtk.NewLabel(escape(formLabel(form)))
@@ -148,16 +157,181 @@ func (f *formsView) reload() error {
f.list.Append(row)
}
f.quiet = false
- if keep >= 0 && keep < len(forms) {
- f.list.SelectRow(f.list.RowAtIndex(keep))
+ switch {
+ case keep >= 0 && keep < len(forms):
+ f.list.SelectRow(f.list.RowAtIndex(keep + 1))
f.show(keep)
- } else {
+ case f.onSettings:
+ f.list.SelectRow(f.list.RowAtIndex(0))
+ f.showSettings()
+ default:
f.sel = -1
- f.clearEditor("Select a rule to edit it, or Add rule.")
+ f.clearEditor("Select the directory or a rule to edit it, or Add rule.")
}
return nil
}
+// settingsRow is the first line of the list: the directory's own settings.
+func settingsRow() *gtk.ListBoxRow {
+ row := gtk.NewListBoxRow()
+ label := gtk.NewLabel("the directory itself")
+ label.SetXAlign(0)
+ label.SetMarginStart(6)
+ label.SetMarginEnd(6)
+ label.SetMarginTop(2)
+ label.SetMarginBottom(2)
+ row.SetChild(label)
+ return row
+}
+
+// showSettings builds the form for the directory's own settings: one row
+// per setting krino.conf(5) documents, each holding what the file writes,
+// and empty when the file leaves it out (GUI design ยง5.1).
+func (f *formsView) showSettings() {
+ f.sel = -1
+ f.onSettings = true
+ f.editor = nil
+ f.note.SetText("These are the directory's own settings. An empty field is one the file does not set, and krino's default applies.")
+ box := gtk.NewBox(gtk.OrientationVertical, 6)
+ box.SetMarginStart(8)
+ box.SetMarginEnd(8)
+ box.SetMarginTop(6)
+ box.SetMarginBottom(6)
+ f.settingRows = nil
+ for _, head := range model.DirSettings {
+ args, _, err := f.owner.rules.Setting(head)
+ if err != nil {
+ f.owner.setCheck("settings: " + err.Error())
+ return
+ }
+ row := newSettingRow(head, args, f.armSettings)
+ f.settingRows = append(f.settingRows, row)
+ box.Append(row.root)
+ }
+ if child := f.place.FirstChild(); child != nil {
+ f.place.Remove(child)
+ }
+ f.place.Append(box)
+}
+
+// armSettings writes the settings back after the same pause a form edit
+// waits.
+func (f *formsView) armSettings() {
+ if f.pending != 0 {
+ glib.SourceRemove(f.pending)
+ }
+ f.pending = glib.TimeoutAdd(checkDelay, func() bool {
+ f.pending = 0
+ f.applySettings()
+ return false
+ })
+}
+
+// applySettings writes every setting whose field has changed.
+func (f *formsView) applySettings() {
+ changed := false
+ for _, row := range f.settingRows {
+ args := row.value()
+ was, _, err := f.owner.rules.Setting(row.head)
+ if err != nil {
+ f.owner.setCheck("settings: " + err.Error())
+ return
+ }
+ if args == was {
+ continue
+ }
+ if err := f.owner.rules.SetSetting(row.head, args); err != nil {
+ f.owner.setCheck("settings: " + err.Error())
+ row.set(was)
+ continue
+ }
+ changed = true
+ }
+ if changed {
+ f.owner.textChangedByForm()
+ }
+}
+
+// settingRow is one directory setting: its name, and what the file writes
+// for it. A setting with fixed choices gets them; the rest are written as
+// they are, so no value is out of reach.
+type settingRow struct {
+ root *gtk.Box
+ head string
+ entry *gtk.Entry
+ drop *gtk.DropDown
+ items []string
+}
+
+// settingChoices are the settings whose values are a fixed few.
+var settingChoices = map[string][]string{
+ "recursive": {"", "yes", "no"},
+ "case": {"", "ignore", "strict"},
+ "fold": {"", "yes", "no"},
+ "on-conflict": {"", "suffix", "skip", "overwrite"},
+}
+
+// settingHints is the example shown in an empty field.
+var settingHints = map[string]string{
+ "path": `"~/downloads"`,
+ "max-depth": `3`,
+ "min-age": `2m (0, 30s, 2m, 1h, 1d, 1w)`,
+ "max-read": `50M (0 reads it all)`,
+ "max-size": `2G`,
+ "busy": `".part" ".aria2" ".crdownload"`,
+ "ignore": `"*.part" ".*"`,
+}
+
+func newSettingRow(head, args string, changed func()) *settingRow {
+ r := &settingRow{head: head}
+ r.root = gtk.NewBox(gtk.OrientationHorizontal, 6)
+ label := gtk.NewLabel(head)
+ label.SetXAlign(0)
+ label.SetSizeRequest(110, -1)
+ r.root.Append(label)
+ if items, ok := settingChoices[head]; ok {
+ r.items = items
+ shown := make([]string, len(items))
+ copy(shown, items)
+ shown[0] = "default"
+ r.drop = gtk.NewDropDownFromStrings(shown)
+ r.drop.SetSelected(uint(indexOf(items, args)))
+ if indexOf(items, args) < 0 {
+ r.drop.SetSelected(0)
+ }
+ r.drop.Connect("notify::selected", func() { changed() })
+ r.root.Append(r.drop)
+ return r
+ }
+ r.entry = gtk.NewEntry()
+ r.entry.SetText(args)
+ r.entry.SetHExpand(true)
+ r.entry.SetPlaceholderText(settingHints[head])
+ r.entry.SetTooltipText(settingHints[head])
+ r.entry.ConnectChanged(func() { changed() })
+ r.root.Append(r.entry)
+ return r
+}
+
+// value is what the row would write; "" means the setting is left out.
+func (r *settingRow) value() string {
+ if r.drop != nil {
+ return r.items[r.drop.Selected()]
+ }
+ return strings.TrimSpace(r.entry.Text())
+}
+
+// set puts a value back, after a refused edit.
+func (r *settingRow) set(args string) {
+ if r.drop != nil {
+ if i := indexOf(r.items, args); i >= 0 {
+ r.drop.SetSelected(uint(i))
+ }
+ return
+ }
+ r.entry.SetText(args)
+}
+
// formLabel is one line of the list: what the form is, and what it does.
func formLabel(f model.Form) string {
if f.Kind == model.ExcludeForm {
@@ -179,6 +353,7 @@ func (f *formsView) show(i int) {
return
}
f.sel = i
+ f.onSettings = false
form := f.forms[i]
f.note.SetText("")
if has, err := f.owner.rules.FormHasComments(i); err == nil && has {