summaryrefslogtreecommitdiff
path: root/pim/cmd/cal9/menu.go
diff options
context:
space:
mode:
Diffstat (limited to 'pim/cmd/cal9/menu.go')
-rw-r--r--pim/cmd/cal9/menu.go263
1 files changed, 38 insertions, 225 deletions
diff --git a/pim/cmd/cal9/menu.go b/pim/cmd/cal9/menu.go
index 4443b00..f210ed0 100644
--- a/pim/cmd/cal9/menu.go
+++ b/pim/cmd/cal9/menu.go
@@ -1,116 +1,16 @@
package main
import (
- "fmt"
- "time"
"os"
"os/exec"
"regexp"
"strings"
- "9front/gui/draw"
+ "9front/gui/ui"
)
-// A button-3 menu, the way page and vdir do it: press 3, drag, release on an
-// item. acme's plumb-on-3 is the outlier on this system, so plumbing lives
-// in here as an item rather than owning the button.
-type menuItem struct {
- label string
- do func(*state)
-}
-
-func (s *state) menu(at draw.Point, mc *draw.Mousectl) {
- items := s.menuItems()
- if len(items) == 0 {
- return
- }
-
- lh := int32(s.f.Height) + 4
- var w int32
- for _, it := range items {
- if x := s.f.Width(it.label) + 20; x > w {
- w = x
- }
- }
- h := lh*int32(len(items)) + 4
- win := s.win.Rect()
- r := draw.Rect(at.X, at.Y, at.X+w, at.Y+h)
- // Keep it on screen.
- if r.Max.X > win.Max.X {
- r = r.Add(draw.Point{X: win.Max.X - r.Max.X})
- }
- if r.Max.Y > win.Max.Y {
- r = r.Add(draw.Point{Y: win.Max.Y - r.Max.Y})
- }
-
- sel := -1
- paint := func() {
- s.fill(r, "tag")
- s.fill(draw.Rect(r.Min.X, r.Min.Y, r.Max.X, r.Min.Y+1), "border")
- s.fill(draw.Rect(r.Min.X, r.Max.Y-1, r.Max.X, r.Max.Y), "border")
- s.fill(draw.Rect(r.Min.X, r.Min.Y, r.Min.X+1, r.Max.Y), "border")
- s.fill(draw.Rect(r.Max.X-1, r.Min.Y, r.Max.X, r.Max.Y), "border")
- for i, it := range items {
- ir := draw.Rect(r.Min.X+1, r.Min.Y+2+int32(i)*lh, r.Max.X-1, r.Min.Y+2+int32(i+1)*lh)
- if i == sel {
- s.fill(ir, "today")
- }
- s.text(draw.Point{X: ir.Min.X + 8, Y: ir.Min.Y + 2}, "ink", it.label)
- }
- s.d.Flush()
- }
- itemAt := func(p draw.Point) int {
- if p.X < r.Min.X || p.X >= r.Max.X {
- return -1
- }
- i := int((p.Y - r.Min.Y - 2) / lh)
- if i < 0 || i >= len(items) || p.Y < r.Min.Y+2 {
- return -1
- }
- return i
- }
- paint()
-
- // Track until button 3 comes back up.
- for m := range mc.C {
- if n := itemAt(m.Point); n != sel {
- sel = n
- paint()
- }
- if m.Buttons&4 == 0 {
- if sel >= 0 {
- items[sel].do(s)
- } else {
- s.redraw()
- }
- return
- }
- }
-}
-
-func (s *state) menuItems() []menuItem {
- items := []menuItem{}
- if s.sel != nil {
- items = append(items, menuItem{"Plumb", func(st *state) {
- if err := st.plumb(st.sel); err != nil {
- fmt.Fprintf(os.Stderr, "cal: plumb: %v\n", err)
- }
- st.redraw()
- }})
- items = append(items, menuItem{"Open event", func(st *state) {
- st.openEvent(st.sel)
- }})
- items = append(items, menuItem{"Close event", func(st *state) {
- st.sel = nil
- st.redraw()
- }})
- }
- if s.sel == nil && !s.pick.IsZero() {
- items = append(items, menuItem{"Close day", func(st *state) {
- st.pick = time.Time{}
- st.redraw()
- }})
- }
+func (s *state) menuItems() []ui.Item {
+ items := []ui.Item{}
// one toggle per calendar, so a crowded work calendar can be put
// aside without unmounting anything
for _, c := range s.cals {
@@ -119,61 +19,42 @@ func (s *state) menuItems() []menuItem {
if s.on[c] {
mark = "[x] "
}
- items = append(items, menuItem{mark + c, func(st *state) {
- st.on[c] = !st.on[c]
- st.load()
- st.redraw()
+ items = append(items, ui.Item{Label: mark + c, Do: func() {
+ s.on[c] = !s.on[c]
+ s.load()
+ s.redraw()
}})
}
- // A second view of the calendar is a second cal, in its own window.
- items = append(items, menuItem{"Open " + strings.ToLower(viewName[s.view]),
- func(st *state) { st.openCal(st.view, st.at) }})
- if !s.pick.IsZero() && s.view != vDay {
- d := s.pick
- items = append(items, menuItem{"Open " + d.Format("2 Jan"),
- func(st *state) { st.openCal(vDay, d) }})
+ if len(writable()) > 0 {
+ items = append(items, ui.Item{Label: "New event", Do: func() {
+ s.New(520, 300, "cal9", "-v", "invite")
+ }})
}
+
+ // A second view of the calendar is a second cal, in its own window.
+ items = append(items, ui.Item{Label: "Open " + strings.ToLower(viewName[s.view]), Do: func() { s.openCal(s.view, s.at) }})
items = append(items,
- menuItem{"Today", func(st *state) {
- st.at = now()
- st.top = 8 * 60
- st.load()
- st.redraw()
- }},
- // rio has no iconify, so "compact" is a resize request: shrink the
- // window and the small-window view takes over by itself.
- menuItem{"Compact", func(st *state) {
- r := st.win.Rect()
- st.wctl(fmt.Sprintf("resize -r %d %d %d %d",
- r.Min.X, r.Min.Y, r.Min.X+320, r.Min.Y+72))
- }},
- menuItem{"Restore", func(st *state) {
- r := st.win.Rect()
- st.wctl(fmt.Sprintf("resize -r %d %d %d %d",
- r.Min.X, r.Min.Y, r.Min.X+820, r.Min.Y+620))
+ ui.Item{Label: "Today", Do: func() {
+ s.at = now()
+ s.top = 8 * 60
+ s.load()
+ s.redraw()
}},
- menuItem{"Move", func(st *state) { st.track(st.mc, false) }},
- menuItem{"Resize", func(st *state) { st.track(st.mc, true) }},
- menuItem{"Hide", func(st *state) { st.wctl("hide") }},
- menuItem{"Exit", func(st *state) { st.quit = true }},
+ // rio has no iconify, so "compact" is a resize request: shrink
+ // the window and the small-window view takes over by itself.
+ // One item, not two: the window is either compact or it is not,
+ // and offering the state you are already in is noise.
+ s.compactItem(),
+ // No move or resize here. rio owns the border -- it puts corner
+ // cursors there and takes the clicks before we see them -- so
+ // dragging it already moves and resizes the window, and doing
+ // it worse from in here helps nobody.
+ ui.Item{Label: "Hide", Do: func() { s.Wctl("hide") }},
+ ui.Item{Label: "Exit", Do: func() { s.quit = true }},
)
return items
}
-// wctl asks rio to do something to our window. Errors are worth showing:
-// outside rio there is no wctl and the menu items simply do nothing.
-func (s *state) wctl(cmd string) {
- f, err := os.OpenFile("/dev/wctl", os.O_WRONLY, 0)
- if err != nil {
- fmt.Fprintf(os.Stderr, "cal: wctl: %v\n", err)
- return
- }
- defer f.Close()
- if _, err := f.WriteString(cmd); err != nil {
- fmt.Fprintf(os.Stderr, "cal: wctl %q: %v\n", cmd, err)
- }
-}
-
var urlRe = regexp.MustCompile(`https?://[^\s<>"]+`)
// plumb sends the event's join link to the plumber, falling back to the
@@ -188,80 +69,12 @@ func (s *state) plumb(e *event) error {
return exec.Command("/bin/plumb", data).Run()
}
-// track implements Move and Resize ourselves. rio's wctl has no "let the
-// user sweep" verb -- only move/resize with an explicit rectangle -- so we
-// follow the pointer and write a new rect as it goes, and rio does the
-// actual work. Any button press drops the window where it is.
-//
-// rio hands out a new image whenever the window's screen rect changes, so
-// this loop has to service resize events as well as motion, or every write
-// would be drawing into a stale image.
-func (s *state) track(mc *draw.Mousectl, resize bool) {
- r := s.win.Rect()
- w, h := r.Dx(), r.Dy()
- grab := draw.Point{} // pointer offset within the window, for Move
- first := true
- last := r
-
- for {
- select {
- case m, ok := <-mc.C:
- if !ok {
- return
- }
- if first {
- grab = draw.Point{X: m.X - r.Min.X, Y: m.Y - r.Min.Y}
- first = false
- continue
- }
- if m.Buttons != 0 { // any press drops it
- s.redraw()
- return
- }
- var nr draw.Rectangle
- if resize {
- nr = draw.Rect(r.Min.X, r.Min.Y, m.X, m.Y)
- if nr.Dx() < 120 {
- nr.Max.X = nr.Min.X + 120
- }
- if nr.Dy() < 60 {
- nr.Max.Y = nr.Min.Y + 60
- }
- } else {
- min := draw.Point{X: m.X - grab.X, Y: m.Y - grab.Y}
- nr = draw.Rect(min.X, min.Y, min.X+w, min.Y+h)
- }
- // Only bother rio when it would actually change something.
- if abs(nr.Min.X-last.Min.X)+abs(nr.Min.Y-last.Min.Y)+
- abs(nr.Max.X-last.Max.X)+abs(nr.Max.Y-last.Max.Y) < 4 {
- continue
- }
- last = nr
- verb := "move"
- if resize {
- verb = "resize"
- }
- s.wctl(fmt.Sprintf("%s -r %d %d %d %d", verb,
- nr.Min.X, nr.Min.Y, nr.Max.X, nr.Max.Y))
-
- case <-mc.Resize:
- win, err := s.d.Reattach("/dev", s.win)
- if err != nil {
- return
- }
- s.win = win
- r = s.win.Rect()
- if !resize {
- w, h = r.Dx(), r.Dy()
- }
- s.redraw()
- }
- }
-}
-
-func abs(n int32) int32 {
- if n < 0 {
- return -n
- }
- return n
+// compactItem offers whichever of compact and restore you are not
+// already in. compactH is the height below which the small view takes
+// over, so it is also how we tell which state we are in.
+func (s *state) compactItem() ui.Item {
+ if s.Win.Rect().Dy() < compactH {
+ return ui.Item{Label: "Restore", Do: func() { s.Resize(820, 620) }}
+ }
+ return ui.Item{Label: "Compact", Do: func() { s.Resize(320, 72) }}
}