diff options
Diffstat (limited to 'appl/wm/calendar.b')
| -rw-r--r-- | appl/wm/calendar.b | 1064 |
1 files changed, 1064 insertions, 0 deletions
diff --git a/appl/wm/calendar.b b/appl/wm/calendar.b new file mode 100644 index 00000000..6e52afd4 --- /dev/null +++ b/appl/wm/calendar.b @@ -0,0 +1,1064 @@ +implement Calendar; + +# +# Copyright © 2000 Vita Nuova Limited. All rights reserved. +# + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; + Font, Point, Rect: import draw; +include "daytime.m"; + daytime: Daytime; + Tm: import Daytime; +include "tk.m"; + tk: Tk; +include "tkclient.m"; + tkclient: Tkclient; +include "dialog.m"; + dialog: Dialog; +include "readdir.m"; +include "translate.m"; + translate: Translate; + Dict: import translate; +include "arg.m"; + arg: Arg; +include "sh.m"; + +Calendar: module { + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Cal: adt { + w: string; + dx, dy: int; + onepos: int; + top: ref Tk->Toplevel; + sched: ref Schedule; + date: int; + marked: array of int; + make: fn(top: ref Tk->Toplevel, sched: ref Schedule, w: string): (ref Cal, chan of string); + show: fn(cal: self ref Cal, date: int); + mark: fn(cal: self ref Cal, ent: Entry); +}; + +Entry: adt { + date: int; # YYYYMMDD + mark: int; +}; + +Sentry: adt { + ent: Entry; + file: int; +}; + +Schedule: adt { + dir: string; + entries: array of Sentry; + new: fn(dir: string): (ref Schedule, string); + getentry: fn(sched: self ref Schedule, date: int): (int, Entry); + readentry: fn(sched: self ref Schedule, date: int): (Entry, string); + setentry: fn(sched: self ref Schedule, ent: Entry, data: string): (int, string); +}; + +Markset: adt { + new: fn(top: ref Tk->Toplevel, cal: ref Cal, w: string): (ref Markset, chan of string); + set: fn(m: self ref Markset, kind: int); + get: fn(m: self ref Markset): int; + ctl: fn(m: self ref Markset, c: string); + + top: ref Tk->Toplevel; + cal: ref Cal; + w: string; + curr: int; +}; + +DBFSPATH: con "/dis/rawdbfs.dis"; +SCHEDDIR: con "/mnt/schedule"; + +stderr: ref Sys->FD; +dict: ref Dict; +font := "/fonts/lucidasans/unicode.7.font"; +days, months: array of string; + +packcmds := array[] of { +"pack .ctf.show .ctf.set .ctf.date -side right", +"pack .ctf -side top -fill x", + +"pack .cf.head.fwd .cf.head.bwd .cf.head.date -side right", +"pack .cf.head -side top -fill x", +"pack .cf.cal -side top", +"pack .cf -side top", + +"pack .schedf.head.fwd .schedf.head.bwd .schedf.head.date .schedf.head.markset" + + " .schedf.head.save .schedf.head.del -side right", +"pack .schedf.head -side top -fill x", +"pack .schedf.tf.scroll -side left -fill y", +"pack .schedf.tf.t -side top -fill both -expand 1", +"pack .schedf.tf -side top -fill both -expand 1", +"pack .schedf -side top -fill both -expand 1", +}; + +Savebut: con ".schedf.head.save"; +Delbut: con ".schedf.head.del"; + +usage() +{ + sys->fprint(stderr, "usage: calendar [-f font] [/mnt/schedule | schedfile]\n"); + raise "fail:usage"; +} + +init(ctxt: ref Draw->Context, argv: list of string) +{ + loadmods(); + if (ctxt == nil) { + sys->fprint(sys->fildes(2), "calendar: no window context\n"); + raise "fail:bad context"; + } + days = Xa(array[] of {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}); + months = Xa(array[] of {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}); + arg->init(argv); + while ((opt := arg->opt()) != 0) { + case opt { + 'f' => + if ((font = arg->arg()) == nil) + usage(); + * => + usage(); + } + } + argv = arg->argv(); + scheddir := SCHEDDIR; + if (argv != nil) + scheddir = hd argv; + (top, wmctl) := tkclient->toplevel(ctxt, "", X("Calendar"), Tkclient->Appl); + if (top == nil) { + sys->fprint(stderr, "cal: cannot make window: %r\n"); + raise "fail:cannot make window"; + } + (sched, err) := Schedule.new(scheddir); + if (sched == nil) + sys->fprint(stderr, "cal: cannot load schedule: %s\n", err); + currtime := daytime->local(daytime->now()); + if (currtime == nil) { + sys->fprint(stderr, "cannot get local time: %r\n"); + raise "fail:failed to get local time"; + } + date := tm2date(currtime); + sys->pctl(Sys->NEWPGRP|Sys->FORKNS, nil); + + cmdch := chan of string; + tk->namechan(top, cmdch, "cmd"); + wincmds := array[] of { + "frame .ctf", + "button .ctf.set -text {"+X("Set")+"} -command {send cmd settime}", + "button .ctf.show -text {"+X("Show")+"} -command {send cmd showtime}", + + "frame .cf -bd 2 -relief raised", + "frame .cf.head", + "button .cf.head.bwd -text {<<} -command {send cmd bwdmonth}", + "button .cf.head.fwd -text {>>} -command {send cmd fwdmonth}", + "label .cf.head.date -text {XXX 0000}", + + "frame .schedf -bd 2 -relief raised", + "frame .schedf.head", + "button .schedf.head.save -text {"+X("Save")+"} -command {send cmd save}", + "button .schedf.head.del -text {"+X("Del")+"} -command {send cmd del}", + "label .schedf.head.date -text {0000/00/00}", + "canvas .schedf.head.markset", + "button .schedf.head.bwd -text {<<} -command {send cmd bwdday}", + "button .schedf.head.fwd -text {>>} -command {send cmd fwdday}", + "frame .schedf.tf", + "scrollbar .schedf.tf.scroll -command {.schedf.tf.t yview}", + "text .schedf.tf.t -wrap word -yscrollcommand {.schedf.tf.scroll set} -height 7h -width 20w", + "bind .schedf.tf.t <Key> +{send cmd dirty}", + }; + tkcmds(top, wincmds); + (cal, calch) := Cal.make(top, sched, ".cf.cal"); + sync := chan of int; + spawn clock(top, ".ctf.date", sync); + clockpid := <-sync; + (ms, msch) := Markset.new(top, cal, ".schedf.head.markset"); + tkcmds(top, packcmds); + if (sched == nil) + cmd(top, "pack forget .schedf"); + + showdate(top, cal, ms, date); + cmd(top, "pack propagate . 0"); + cmd(top, "update"); + if (date < 19700002) + raisesettime(ctxt, top); + + setting := 0; + dirty := 0; + empty := scheduleempty(top); + currsched := 0; + + tkclient->onscreen(top, nil); + tkclient->startinput(top, "kbd"::"ptr"::nil); + + for (;;) { + enable(top, Savebut, dirty); + enable(top, Delbut, !empty); + cmd(top, "update"); + ndate := date; + alt { + c := <-calch => + (y,m,d) := date2ymd(date); + d = int c; + ndate = ymd2date(y,m,d); + c := <-msch => + ms.ctl(c); + cal.mark(Entry(date, ms.get())); + dirty = 1; + c := <-cmdch => + case c { + "dirty" => + dirty = 1; + nowempty := scheduleempty(top); + if (nowempty != empty) { + if (nowempty) { + ms.set(0); + cal.mark(Entry(date, 0)); + } else { + ms.set(1); + cal.mark(Entry(date, ms.get())); + } + empty = nowempty; + } + "bwdmonth" => + ndate = decmonth(date); + "fwdmonth" => + ndate = incmonth(date); + "bwdday" => + ndate = adddays(date, -1); + "fwdday" => + ndate = adddays(date, 1); + "del" => + if (!empty) { + cmd(top, ".schedf.tf.t delete 1.0 end"); + empty = 1; + dirty = 1; + cal.mark(Entry(date, 0)); + } + "save" => + if (dirty && save(ctxt, top, cal, ms, date) != -1) + dirty = 0; + "settime" => + raisesettime(ctxt, top); + "showtime" => + ndate = tm2date(daytime->local(daytime->now())); + * => + sys->fprint(stderr, "cal: unknown command '%s'\n", c); + } + s := <-top.ctxt.kbd => + tk->keyboard(top, s); + s := <-top.ctxt.ptr => + tk->pointer(top, *s); + c := <-top.ctxt.ctl or + c = <-top.wreq or + c = <-wmctl => + if (c == "exit" && dirty) + save(ctxt, top, cal, ms, date); + tkclient->wmctl(top, c); + } + if (ndate != date) { + e := 0; + if (dirty) + e = save(ctxt, top, cal, ms, date); + if (e != -1) { + dirty = 0; + showdate(top, cal, ms, ndate); + empty = scheduleempty(top); + date = ndate; + cmd(top, "update"); + } + } + } +} + +Markset.new(top: ref Tk->Toplevel, cal: ref Cal, w: string): (ref Markset, chan of string) +{ + cmd(top, w+" configure -width "+string (cal.dx * 2 + 6) + + " -height "+string (cal.dy + 4)); + ch := chan of string; + tk->namechan(top, ch, "markcmd"); + return (ref Markset(top, cal, w, 0), ch); +} + +Markset.set(m: self ref Markset, kind: int) +{ + cmd(m.top, m.w + " delete x"); + if (kind > 0) { + (shape, col) := kind2shapecol(kind); + id := cmd(m.top, m.w + " create " + + shapestr(m.cal, (m.cal.dx/2+2, m.cal.dy/2+2), Square) + + " -fill " + colours[col] + " -tags x"); + cmd(m.top, m.w + " bind " + id + " <ButtonRelease-1> {send markcmd col}"); + id = cmd(m.top, m.w + " create " + + shapestr(m.cal, (m.cal.dx * 3 / 2+4, m.cal.dy/2+2), shape) + + " -tags x -width 2"); + cmd(m.top, m.w + " bind " + id + " <ButtonRelease-1> {send markcmd shape}"); + } + m.curr = kind; +} + +Markset.get(m: self ref Markset): int +{ + return m.curr; +} + +Markset.ctl(m: self ref Markset, c: string) +{ + (shape, col) := kind2shapecol(m.curr); + case c { + "col" => col = (col + 1) % len colours; + "shape" => shape = (shape + 1) % Numshapes; + } + m.set(shapecol2kind((shape, col))); +} + +scheduleempty(top: ref Tk->Toplevel): int +{ + return int cmd(top, ".schedf.tf.t compare 1.0 == end"); +} + +enable(top: ref Tk->Toplevel, but: string, enable: int) +{ + cmd(top, but + " configure -state " + + (array[] of {"disabled", "normal"})[!!enable]); +} + +save(ctxt: ref Draw->Context, top: ref Tk->Toplevel, cal: ref Cal, ms: ref Markset, date: int): int +{ + s := cmd(top, ".schedf.tf.t get 1.0 end"); + empty := scheduleempty(top); + mark := ms.get(); + if (empty) + mark = 0; + ent := Entry(date, mark); + cal.mark(ent); + (ok, err) := cal.sched.setentry(ent, s); + if (ok == -1) { + notice(ctxt, top, "Cannot save entry: " + err); + return -1; + } + return 0; +} + +notice(ctxt: ref Draw->Context, top: ref Tk->Toplevel, s: string) +{ + dialog->prompt(ctxt, top.image, nil, "Notice", s, 0, "OK"::nil); +} + +showdate(top: ref Tk->Toplevel, cal: ref Cal, ms: ref Markset, date: int) +{ + (y,m,d) := date2ymd(date); + cal.show(date); + cmd(top, ".cf.head.date configure -text {" + sys->sprint("%.4d/%.2d", y, m+1) + "}"); + cmd(top, ".schedf.head.date configure -text {" + sys->sprint("%.4d/%.2d/%.2d", y, m+1, d) + "}"); + (ent, s) := cal.sched.readentry(date); + ms.set(ent.mark); + cmd(top, ".schedf.tf.t delete 1.0 end; .schedf.tf.t insert 1.0 '" + s); +} + +nomod(s: string) +{ + sys->fprint(stderr, "cal: cannot load %s: %r\n", s); + raise "fail:bad module"; +} + +loadmods() +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + daytime = load Daytime Daytime->PATH; + if (daytime == nil) + nomod(Daytime->PATH); + tkclient = load Tkclient Tkclient->PATH; + if (tkclient == nil) + nomod(Tkclient->PATH); + translate = load Translate Translate->PATH; + if(translate != nil){ + translate->init(); + (dict, nil) = translate->opendict(translate->mkdictname("", "calendar")); + } + tkclient->init(); + arg = load Arg Arg->PATH; + if (arg == nil) + nomod(Arg->PATH); + dialog = load Dialog Dialog->PATH; + if(dialog == nil) + nomod(Dialog->PATH); + dialog->init(); +} + +s2a(s: string, min, max: int, sep: string): array of int +{ + (ntoks, toks) := sys->tokenize(s, sep); + if (ntoks < min || ntoks > max) + return nil; + a := array[max] of int; + for (i := 0; toks != nil; toks = tl toks) { + if (!isnum(hd toks)) + return nil; + a[i++] = int hd toks; + } + return a[0:i]; +} + +validtm(t: ref Daytime->Tm): int +{ + if (t.hour < 0 || t.hour > 23 + || t.min < 0 || t.min > 59 + || t.sec < 0 || t.sec > 59 + || t.mday < 1 || t.mday > 31 + || t.mon < 0 || t.mon > 11 + || t.year < 70 || t.year > 137) + return 0; + if (t.mon == 1 && dysize(t.year+1900) > 365) + return t.mday <= 29; + return t.mday <= dmsize[t.mon]; +} + +clock(top: ref Tk->Toplevel, w: string, sync: chan of int) +{ + cmd(top, "label " + w); + fd := sys->open("/dev/time", Sys->OREAD); + if (fd == nil) { + sync <-= -1; + return; + } + buf := array[128] of byte; + for (;;) { + sys->seek(fd, big 0, Sys->SEEKSTART); + n := sys->read(fd, buf, len buf); + if (n < 0) { + sys->fprint(stderr, "cal: could not read time: %r\n"); + if (sync != nil) + sync <-= -1; + break; + } + ms := big string buf[0:n] / big 1000; + ct := ms / big 1000; + t := daytime->local(int ct); + + s := sys->sprint("%s %s %d %.2d:%.2d.%.2d", + days[t.wday], months[t.mon], t.mday, t.hour, t.min, t.sec); + cmd(top, w + " configure -text {" + s + "}"); + cmd(top, "update"); + if (sync != nil) { + sync <-= sys->pctl(0, nil); + sync = nil; + } + sys->sleep(int ((ct + big 1) * big 1000 - ms)); + } +} + +# "the world is the lord's and all it contains, +# save the highlands and islands, which belong to macbraynes" +Cal.make(top: ref Tk->Toplevel, sched: ref Schedule, w: string): (ref Cal, chan of string) +{ + f := Font.open(top.display, font); + if (f == nil) { + sys->fprint(stderr, "cal: could not open font %s: %r\n", font); + font = cmd(top, ". cget -font"); + f = Font.open(top.display, font); + } + if (f == nil) + return (nil, nil); + maxw := 0; + for (i := 0; i < 7; i++) { + if ((dw := f.width(days[i] + " ")) > maxw) + maxw = dw; + } + for (i = 10; i < 32; i++) { + if ((dw := f.width(string i + " ")) > maxw) + maxw = dw; + } + cal := ref Cal; + cal.w = w; + cal.dx = maxw; + cal.dy = f.height; + cal.onepos = 0; + cal.top = top; + cal.sched = sched; + cal.marked = array[31] of {* => 0}; + cmd(top, "canvas " + w + " -width " + string (cal.dx * 7) + " -height " + string (cal.dy * 7)); + for (i = 0; i < 7; i++) + cmd(top, w + " create text " + posstr(daypos(cal, i, 0)) + + " -text " + days[i] + " -font " + font); + ch := chan of string; + tk->namechan(top, ch, "ch" + w); + return (cal, ch); +} + +Cal.show(cal: self ref Cal, date: int) +{ + if (date == cal.date) + return; + mon := (date / 100) % 100; + year := date / 10000; + cmd(cal.top, cal.w + " delete curr"); + if (cal.date / 100 != date / 100) { + cmd(cal.top, cal.w + " delete date"); + cmd(cal.top, cal.w + " delete mark"); + for (i := 0; i < len cal.marked; i++) + cal.marked[i] = 0; + (md, wd) := monthinfo(mon, year); + base := year * 10000 + mon * 100; + cal.onepos = wd; + for (i = 0; i < 6; i++) { + for (j := 0; j < 7; j++) { + d := i * 7 + j - wd; + if (d >= 0 && d < md) { + id := cmd(cal.top, cal.w + " create text " + posstr(daypos(cal, j, i+1)) + + " -tags date -text " + string (d+1) + + " -font " + font); + cmd(cal.top, cal.w + " bind " + id + + " <ButtonRelease-1> {send ch" + cal.w + " " + string (d+1) + "}"); + (ok, ent) := cal.sched.getentry(base + d + 1); + if (ok != -1) + cal.mark(ent); + } + } + } + } + if (cal.sched != nil) { + e := date % 100 - 1 + cal.onepos; + p := daypos(cal, e % 7, e / 7 + 1); + cmd(cal.top, cal.w + " create " + shapestr(cal, p, Square) + + " -tags curr -width 3"); + } + cal.date = date; +} + +Cal.mark(cal: self ref Cal, ent: Entry) +{ + if (ent.date / 100 != ent.date / 100) + return; + (nil, nil, d) := date2ymd(ent.date); + d--; + cmd(cal.top, cal.w + " delete m" + string d); + if (ent.mark) { + e := d + cal.onepos; + p := daypos(cal, e % 7, e / 7 + 1); + id := cmd(cal.top, cal.w + " create " + itemshape(cal, p, ent.mark) + + " -tags {mark m"+string d + "}"); + cmd(cal.top, cal.w + " bind " + id + + " <ButtonRelease-1> {send ch" + cal.w + " " + string (d+1) + "}"); + cmd(cal.top, cal.w + " lower " + id); + } + cal.marked[d] = ent.mark; +} + +Oval, Diamond, Square, Numshapes: con iota; + +colours := array[] of { + "red", + "yellow", + "#00eeee", + "white" +}; + +kind2shapecol(kind: int): (int, int) +{ + kind = (kind - 1) & 16rffff; + return ((kind & 16rff) % Numshapes, (kind >> 8) % len colours); +} + +shapecol2kind(shapecol: (int, int)): int +{ + (shape, colour) := shapecol; + return (shape + (colour << 8)) + 1; +} + +itemshape(cal: ref Cal, centre: Point, kind: int): string +{ + (shape, colour) := kind2shapecol(kind); + return shapestr(cal, centre, shape) + " -fill " + colours[colour]; +} + +shapestr(cal: ref Cal, p: Point, kind: int): string +{ + (hdx, hdy) := (cal.dx / 2, cal.dy / 2); + case kind { + Oval => + r := Rect((p.x - hdx, p.y - hdy), (p.x + hdx, p.y + hdy)); + return "oval " + rectstr(r); + Diamond => + return "polygon " + string (p.x - hdx) + " " + string p.y + " " + + string p.x + " " + string (p.y - hdy) + " " + + string (p.x + hdx) + " " + string p.y + " " + + string p.x + " " + string (p.y + hdy) + + " -outline black"; + Square => + r := Rect((p.x - hdx, p.y - hdy), (p.x + hdx, p.y + hdy)); + return "rectangle " + rectstr(r); + * => + sys->fprint(stderr, "cal: unknown shape %d\n", kind); + return nil; + } +} + +rectstr(r: Rect): string +{ + return string r.min.x + " " + string r.min.y + " " + + string r.max.x + " " + string r.max.y; +} + +posstr(p: Point): string +{ + return string p.x + " " + string p.y; +} + +# return centre point of position for day. +daypos(cal: ref Cal, d, w: int): Point +{ + return Point(d * cal.dx + cal.dx / 2, w * cal.dy + cal.dy / 2); +} + +body2entry(body: string): (int, Entry, string) +{ + for (i := 0; i < len body; i++) + if (body[i] == '\n') + break; + if (i == len body) + return (-1, (-1, -1), "invalid schedule header (no newline)"); + (n, toks) := sys->tokenize(body[0:i], " \t\n"); + if (n < 2) + return (-1, (-1, -1), "invalid schedule header (too few fields)"); + date := int hd toks; + (y, m, d) := (date / 10000, (date / 100) % 100, date%100); + if (y < 1970 || y > 2037 || m > 12 || m < 1 || d > 31 || d < 1) + return (-1, (-1,-1), sys->sprint("invalid date (%.8d) in schedule header", date)); + e := Entry(ymd2date(y, m-1, d), int hd tl toks); + return (0, e, body[i+1:]); +} + +startdbfs(f: string): (string, string) +{ + dbfs := load Command DBFSPATH; + if (dbfs == nil) + return (nil, sys->sprint("cannot load %s: %r", DBFSPATH)); + sync := chan of string; + spawn rundbfs(sync, dbfs, f, SCHEDDIR); + e := <-sync; + if (e != nil) + return (nil, e); + return (SCHEDDIR, nil); +} + +rundbfs(sync: chan of string, dbfs: Command, f, d: string) +{ + sys->pctl(Sys->FORKFD, nil); + { + dbfs->init(nil, "dbfs" :: "-r" :: f :: d :: nil); + sync <-= nil; + }exception e{ + "fail:*" => + sync <-= "dbfs failed: " + e[5:]; + exit; + } +} + +Schedule.new(d: string): (ref Schedule, string) +{ + (rc, info) := sys->stat(d); + if (rc == -1) + return (nil, sys->sprint("cannot find %s: %r", d)); + if ((info.mode & Sys->DMDIR) == 0) { + err: string; + (d, err) = startdbfs(d); + if (d == nil) + return (nil, err); + } + (rc, nil) = sys->stat(d + "/new"); + if (rc == -1) + return (nil, "no dbfs mounted on " + d); + + readdir := load Readdir Readdir->PATH; + if (readdir == nil) + return (nil, sys->sprint("cannot load %s: %r", Readdir->PATH)); + sched := ref Schedule; + sched.dir = d; + (de, nil) := readdir->init(d, Readdir->NONE); + if (de == nil) + return (nil, "could not read schedule directory"); + buf := array[Sys->ATOMICIO] of byte; + sched.entries = array[len de] of Sentry; + ne := 0; + for (i := 0; i < len de; i++) { + if (!isnum(de[i].name)) + continue; + f := d + "/" + de[i].name; + fd := sys->open(f, Sys->OREAD); + if (fd == nil) { + sys->fprint(stderr, "cal: cannot open %s: %r\n", f); + } else { + n := sys->read(fd, buf, len buf); + if (n == -1) { + sys->fprint(stderr, "cal: error reading %s: %r\n", f); + } else { + (ok, e, err) := body2entry(string buf[0:n]); + if (ok == -1) + sys->fprint(stderr, "cal: error on entry %s: %s\n", f, err); + else + sched.entries[ne++] = (e, int de[i].name); + err = nil; + } + } + } + sched.entries = sched.entries[0:ne]; + sortentries(sched.entries); + return (sched, nil); +} + +Schedule.getentry(sched: self ref Schedule, date: int): (int, Entry) +{ + if (sched == nil) + return (-1, (-1, -1)); + ent := search(sched, date); + if (ent == -1) + return (-1, (-1,-1)); + return (0, sched.entries[ent].ent); +} + +Schedule.readentry(sched: self ref Schedule, date: int): (Entry, string) +{ + if (sched == nil) + return ((-1, -1), nil); + ent := search(sched, date); + if (ent == -1) + return ((-1, -1), nil); + (nil, fno) := sched.entries[ent]; + + f := sched.dir + "/" + string fno; + fd := sys->open(f, Sys->OREAD); + if (fd == nil) { + sys->fprint(stderr, "cal: cannot open %s: %r", f); + return ((-1, -1), nil); + } + buf := array[Sys->ATOMICIO] of byte; + n := sys->read(fd, buf, len buf); + if (n == -1) { + sys->fprint(stderr, "cal: cannot read %s: %r", f); + return ((-1, -1), nil); + } + (ok, e, body) := body2entry(string buf[0:n]); + if (ok == -1) { + sys->fprint(stderr, "cal: couldn't get body in file %s: %s\n", f, body); + return ((-1, -1), nil); + } + return (e, body); +} + +writeentry(fd: ref Sys->FD, ent: Entry, data: string): (int, string) +{ + ent.date += 100; + b := array of byte (sys->sprint("%d %d\n", ent.date, ent.mark) + data); + if (len b > Sys->ATOMICIO) + return (-1, "entry is too long"); + if (sys->write(fd, b, len b) != len b) + return (-1, sys->sprint("cannot write entry: %r")); + return (0, nil); +} + +Schedule.setentry(sched: self ref Schedule, ent: Entry, data: string): (int, string) +{ + if (sched == nil) + return (-1, "no schedule"); + idx := search(sched, ent.date); + if (idx == -1) { + if (data == nil) + return (0, nil); + fd := sys->open(sched.dir + "/new", Sys->OWRITE); + if (fd == nil) + return (-1, sys->sprint("cannot open new: %r")); + (ok, info) := sys->fstat(fd); + if (ok == -1) + return (-1, sys->sprint("cannot stat new: %r")); + if (!isnum(info.name)) + return (-1, "new dbfs entry is not numeric"); + err: string; + (ok, err) = writeentry(fd, ent, data); + if (ok == -1) + return (ok, err); + (fd, data) = (nil, nil); + e := sched.entries; + for (i := 0; i < len e; i++) + if (ent.date < e[i].ent.date) + break; + ne := array[len e + 1] of Sentry; + (ne[0:], ne[i], ne[i+1:]) = (e[0:i], (ent, int info.name), e[i:]); + sched.entries = ne; + return (0, nil); + } else { + fno := sched.entries[idx].file; + f := sched.dir + "/" + string fno; + if (data == nil) { + sys->remove(f); + sched.entries[idx:] = sched.entries[idx+1:]; + sched.entries = sched.entries[0:len sched.entries - 1]; + return (0, nil); + } else { + sched.entries[idx] = (ent, fno); + fd := sys->open(f, Sys->OWRITE); + if (fd == nil) + return (-1, sys->sprint("cannot open %s: %r", sched.dir + "/" + string fno)); + return writeentry(fd, ent, data); + } + } +} + +search(sched: ref Schedule, date: int): int +{ + e := sched.entries; + lo := 0; + hi := len e - 1; + while (lo <= hi) { + mid := (lo + hi) / 2; + if (date < e[mid].ent.date) + hi = mid - 1; + else if (date > e[mid].ent.date) + lo = mid + 1; + else + return mid; + } + return -1; +} + +sortentries(a: array of Sentry) +{ + m: int; + n := len a; + for(m = n; m > 1; ) { + if(m < 5) + m = 1; + else + m = (5*m-1)/11; + for(i := n-m-1; i >= 0; i--) { + tmp := a[i]; + for(j := i+m; j <= n-1 && tmp.ent.date > a[j].ent.date; j += m) + a[j-m] = a[j]; + a[j-m] = tmp; + } + } +} + +raisesettime(ctxt: ref Draw->Context, top: ref Tk->Toplevel) +{ + panelcmds := array[] of { + "frame .d", + "label .d.title -text {"+X("Date (YYYY/MM/DD):")+"}", + "entry .d.de -width 11w}", + "frame .t", + "label .t.title -text {"+X("Time (HH:MM.SS):")+"}", + "entry .t.te -width 11w}", + "frame .b", + "button .b.set -text Set -command {send cmd set}", + "button .b.cancel -text Cancel -command {send cmd cancel}", + "pack .d .t .b -side top -fill x", + "pack .d.de .d.title -side right", + "pack .t.te .t.title -side right", + "pack .b.set .b.cancel -side right", + }; + fd := sys->open("/dev/time", Sys->OWRITE); + if (fd == nil) { + notice(ctxt, top, X("Cannot set time: ") + sys->sprint("%r")); + return; + } + (panel, wmctl) := tkclient->toplevel(ctxt, "", X("Set Time"), 0); + tkcmds(panel, panelcmds); + cmdch := chan of string; + tk->namechan(panel, cmdch, "cmd"); + t := daytime->local(daytime->now()); + if (t.year < 71) + (t.year, t.mon, t.mday) = (100, 0, 1); + cmd(panel, ".d.de insert 0 " + sys->sprint("%.4d/%.2d/%.2d", + t.year+1900, t.mon+1, t.mday)); + cmd(panel, ".t.te insert 0 " + sys->sprint("%.2d:%.2d.%.2d", t.hour, t.min, t.sec)); + #cmd(panel, "grab set ."); XXX should, but not a good idea with global tk. + # wouldn't work with current dialog->prompt() either... + cmd(panel, "update"); + tkclient->onscreen(panel, nil); + tkclient->startinput(panel, "kbd"::"ptr"::nil); + +loop: for (;;) alt { + s := <-panel.ctxt.kbd => + tk->keyboard(panel, s); + s := <-panel.ctxt.ptr => + tk->pointer(panel, *s); + c := <-cmdch => + case c { + "set" => + err := settime(fd, cmd(panel, ".d.de get"), cmd(panel, ".t.te get")); + if (err == nil) + break loop; + notice(ctxt, panel, X("Cannot set time: ") + err); + "cancel" => + break loop; + * =>; + } + c := <-wmctl => + case c { + "exit" => + break loop; + * => + tkclient->wmctl(panel, c); + } + } +} + +settime(tfd: ref Sys->FD, date, time: string): string +{ + da := s2a(date, 3, 3, "/"); + if (da == nil) + return X("Invalid date syntax"); + ta := s2a(time, 2, 3, ":."); + if (ta == nil) + return X("Invalid time syntax"); + t := ref blanktm; + if (da[2] > 1000) + (da[0], da[1], da[2]) = (da[2], da[1], da[0]); + (t.year, t.mon, t.mday) = (da[0]-1900, da[1]-1, da[2]); + if (len ta == 3) + (t.hour, t.min, t.sec) = (ta[0], ta[1], ta[2]); + else + (t.hour, t.min, t.sec) = (ta[0], ta[1], 0); + if (!validtm(t)) + return X("Invalid time or date given"); + s := string daytime->tm2epoch(t) + "000000"; + if (sys->fprint(tfd, "%s", s) == -1) + return X("write failed:") + sys->sprint(" %r"); + return nil; +} + + +cmd(top: ref Tk->Toplevel, cmd: string): string +{ + e := tk->cmd(top, cmd); + if (e != nil && e[0] == '!') + sys->fprint(stderr, "cal: tk error on '%s': %s\n", cmd, e); + return e; +} + +tkcmds(top: ref Tk->Toplevel, a: array of string) +{ + for (i := 0; i < len a; i++) + cmd(top, a[i]); +} + +isnum(s: string): int +{ + for (i := 0; i < len s; i++) + if (s[i] < '0' || s[i] > '9') + return 0; + return 1; +} + +tm2date(t: ref Tm): int +{ + if (t == nil) + return 19700001; + return ymd2date(t.year+1900, t.mon, t.mday); +} + +date2ymd(date: int): (int, int, int) +{ + return (date / 10000, (date / 100) % 100, date%100); +} + +ymd2date(y, m, d: int): int +{ + return d + m* 100 + y * 10000; +} + +adddays(date, delta: int): int +{ + t := ref blanktm; + t.mday = date % 100; + t.mon = (date / 100) % 100; + t.year = (date / 10000) - 1900; + t.hour = 12; + e := daytime->tm2epoch(t); + e += delta * 24 * 60 * 60; + t = daytime->gmt(e); + if (!validtm(t)) + return date; + return tm2date(t); +} + +incmonth(date: int): int +{ + (y,m,d) := date2ymd(date); + if (m < 11) + m++; + else if (y < 2037) + (y, m) = (y+1, 0); + (n, nil) := monthinfo(m, y); + if (d > n) + d = n; + return ymd2date(y,m,d); +} + +decmonth(date: int): int +{ + (y,m,d) := date2ymd(date); + if (m > 0) + m--; + else if (y > 1970) + (y, m) = (y-1, 11); + (n, nil) := monthinfo(m, y); + if (d > n) + d = n; + return ymd2date(y,m,d); +} + +dmsize := array[] of { + 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 +}; + +dysize(y: int): int +{ + if( (y%4) == 0 && (y % 100 != 0 || y % 400 == 0) ) + return 366; + return 365; +} + +blanktm: Tm; + +# return number of days in month and +# starting day of month/year. +monthinfo(mon, year: int): (int, int) +{ + t := ref blanktm; + t.mday = 1; + t.mon = mon; + t.year = year - 1900; + t = daytime->gmt(daytime->tm2epoch(t)); + md := dmsize[mon]; + if (dysize(year) == 366 && t.mon == 1) + md++; + return (md, t.wday); +} + +X(s: string): string +{ + #sys->print("\"%s\"\n", s); + if (dict == nil) + return s; + return dict.xlate(s); +} + +Xa(a: array of string): array of string +{ + for (i := 0; i < len a; i++) + a[i] = X(a[i]); + return a; +} + |
