diff options
Diffstat (limited to 'appl/wm/brutus.b')
| -rw-r--r-- | appl/wm/brutus.b | 2013 |
1 files changed, 2013 insertions, 0 deletions
diff --git a/appl/wm/brutus.b b/appl/wm/brutus.b new file mode 100644 index 00000000..b18d6a9d --- /dev/null +++ b/appl/wm/brutus.b @@ -0,0 +1,2013 @@ +implement Brutus; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + draw: Draw; + Context: import draw; + ctxt: ref Context; + +include "tk.m"; + tk: Tk; + +include "tkclient.m"; + tkclient: Tkclient; + +include "dialog.m"; + dialog: Dialog; + +include "selectfile.m"; + selectfile: Selectfile; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "workdir.m"; + +include "plumbmsg.m"; + plumbmsg: Plumbmsg; + Msg: import plumbmsg; + +include "brutus.m"; +include "brutusext.m"; + +EXTDIR: con "/dis/wm/brutus"; +NEXTRA: con NTAG-NFONTTAG; +DEFFONT: con "/fonts/lucidasans/unicode.8.font"; +DEFFONTNAME: con "Roman"; +DEFSIZE: con 10; +DEFTAG: con "Roman.10"; +SETFONT: con " -font "+DEFFONT+" "; +FOCUS: con "focus .ft.t"; +NOSEL: con ".ft.t tag remove sel sel.first sel.last"; +UPDATE: con "update"; + +# +# Foreign keyboards and languages +# +Remaptab: adt +{ + in, out: int; +}; +include "hebrew.m"; + +BS: con 8; # ^h backspace character +BSW: con 23; # ^w bacspace word +BSL: con 21; # ^u backspace line +ESC: con 27; # ^[ cut selection + +Name: con "Brutus"; + +# build menu +menu_cfg := array[] of { + # menu + "menu .m", + ".m add command -text Cut -command {send edit cut}", + ".m add command -text Paste -command {send edit paste}", + ".m add command -text Snarf -command {send edit snarf}", + ".m add command -text Look -command {send edit look}", +}; + +brutus_cfg := array[] of { + # buttons + "button .b.Tag -text Tag -command {send cmd tag} -state disabled", + "menubutton .b.Font -text Roman -menu .b.Font.menu -underline -1 -state disabled", + "menu .b.Font.menu", + ".b.Font.menu add command -label Roman -command {send cmd font Roman}", + ".b.Font.menu add command -label Italic -command {send cmd font Italic}", + ".b.Font.menu add command -label Bold -command {send cmd font Bold}", + ".b.Font.menu add command -label Type -command {send cmd font Type}", + "checkbutton .b.Applyfont -variable Applyfont -command {send cmd applyfont}} -state disabled", + "button .b.Applyfontnow -text Font -command {send cmd applyfontnow} -state disabled", + "button .b.Applysizenow -text Size -command {send cmd applysizenow} -state disabled", + "button .b.Applyfontsizenow -text F&S -command {send cmd applyfontsizenow} -state disabled", + "menubutton .b.Size -text 10pt -menu .b.Size.menu -underline -1 -state disabled", + "menu .b.Size.menu", + ".b.Size.menu add command -label 6pt -command {send cmd size 6}", + ".b.Size.menu add command -label 8pt -command {send cmd size 8}", + ".b.Size.menu add command -label 10pt -command {send cmd size 10}", + ".b.Size.menu add command -label 12pt -command {send cmd size 12}", + ".b.Size.menu add command -label 16pt -command {send cmd size 16}", + "button .b.Put -text Put -command {send cmd put} -state disabled", + + # text + "frame .ft", + "scrollbar .ft.scroll -command {.ft.t yview}", + "text .ft.t -height 7c -tabs {1c} -wrap word -yscrollcommand {.ft.scroll set}", + FOCUS, + + # pack + "pack .b.File .b.Ext .b.Tag .b.Applyfontnow .b.Applysizenow .b.Applyfontsizenow .b.Applyfont .b.Font .b.Size .b.Put -side left", + "pack .b -anchor w", + "pack .ft.scroll -side left -fill y", + "pack .ft.t -fill both -expand 1", + "pack .ft -fill both -expand 1", + "pack propagate . 0", +}; + +control_cfg := array[] of { + # text + "frame .ft", + "scrollbar .ft.scroll -command {.ft.t yview}", + "text .ft.t -height 4c -wrap word -yscrollcommand {.ft.scroll set}", + "pack .b.File", + "pack .b -anchor w", + "pack .ft.scroll -side left -fill y", + "pack .ft.t -fill both -expand 1", + "pack .ft -fill both -expand 1", + "pack propagate . 0", +}; + +# bindings to build nice controls in text widget +input_cfg := array[] of { + # input + "bind .ft.t <Key> {send keys {%A}}", + "bind .ft.t <Control-h> {send keys {%A}}", + "bind .ft.t <Control-w> {send keys {%A}}", + "bind .ft.t <Control-u> {send keys {%A}}", + "bind .ft.t <Button-1> +{grab set .ft.t; send but1 pressed}", + "bind .ft.t <Double-Button-1> +{grab set .ft.t; send but1 pressed}", + "bind .ft.t <ButtonRelease-1> +{grab release .ft.t; send but1 released}", + "bind .ft.t <Button-2> {send but2 %X %Y}", + "bind .ft.t <Motion-Button-2-Button-1> {}", + "bind .ft.t <Motion-Button-2> {}", + "bind .ft.t <ButtonPress-3> {send but3 pressed}", + "bind .ft.t <ButtonRelease-3> {send but3 released %x %y}", + "bind .ft.t <Motion-Button-3> {}", + "bind .ft.t <Motion-Button-3-Button-1> {}", + "bind .ft.t <Double-Button-3> {}", + "bind .ft.t <Double-ButtonRelease-3> {}", + "bind .ft.t <FocusIn> +{send cmd focus}", + UPDATE +}; + +fontbuts := array[] of { + ".b.Ext", + ".b.Tag", + ".b.Applyfontnow", + ".b.Applysizenow", + ".b.Applyfontsizenow", + ".b.Applyfont", + ".b.Font", + ".b.Size", +}; + +fontname = array[NFONT] of { + "Roman", + "Italic", + "Bold", + "Type", +}; + +sizename = array[NSIZE] of { + "6", + "8", + "10", + "12", + "16", +}; + +tagname = array[NTAG] of { + # first NFONT*NSIZE are font/size names + "Roman.6", + "Roman.8", + "Roman.10", + "Roman.12", + "Roman.16", + "Italic.6", + "Italic.8", + "Italic.10", + "Italic.12", + "Italic.16", + "Bold.6", + "Bold.8", + "Bold.10", + "Bold.12", + "Bold.16", + "Type.6", + "Type.8", + "Type.10", + "Type.12", + "Type.16", + "Example", + "Caption", + "List", + "List-elem", + "Label", + "Label-ref", + "Exercise", + "Heading", + "No-fill", + "Author", + "Title", + "Index", + "Index-topic", +}; + +tagconfig = array[NTAG] of { + "-font /fonts/lucidasans/unicode.6.font", + "-font /fonts/lucidasans/unicode.7.font", + "-font /fonts/lucidasans/unicode.8.font", + "-font /fonts/lucidasans/unicode.10.font", + "-font /fonts/lucidasans/unicode.13.font", + "-font /fonts/lucidasans/italiclatin1.6.font", + "-font /fonts/lucidasans/italiclatin1.7.font", + "-font /fonts/lucidasans/italiclatin1.8.font", + "-font /fonts/lucidasans/italiclatin1.10.font", + "-font /fonts/lucidasans/italiclatin1.13.font", + "-font /fonts/lucidasans/boldlatin1.6.font", + "-font /fonts/lucidasans/boldlatin1.7.font", + "-font /fonts/lucidasans/boldlatin1.8.font", + "-font /fonts/lucidasans/boldlatin1.10.font", + "-font /fonts/lucidasans/boldlatin1.13.font", + "-font /fonts/lucidasans/typelatin1.6.font", + "-font /fonts/lucidasans/typelatin1.7.font", + "-font /fonts/pelm/latin1.9.font", + "-font /fonts/pelm/ascii.12.font", + "-font /fonts/pelm/ascii.16.font", + "-foreground #444444 -lmargin1 1c -lmargin2 1c; .ft.t tag lower Example", + "-foreground #444444; .ft.t tag lower Caption", + "-foreground #444444 -lmargin1 1c -lmargin2 1c; .ft.t tag lower List", + "-foreground #0000A0; .ft.t tag lower List-elem", + "-foreground #444444; .ft.t tag lower Label", + "-foreground #444444; .ft.t tag lower Label-ref", + "-foreground #444444; .ft.t tag lower Exercise", + "-foreground #444444; .ft.t tag lower Heading", + "-foreground #444444; .ft.t tag lower No-fill", + "-foreground #444444; .ft.t tag lower Author", + "-foreground #444444; .ft.t tag lower Title", + "-foreground #444444; .ft.t tag lower Index", + "-foreground #444444; .ft.t tag lower Index-topic", +}; + +enabled := array[] of {"disabled", "normal"}; + +File: adt +{ + tk: ref Tk->Toplevel; + isctl: int; + applyfont: int; + fontsused: int; + name: string; + dirty: int; + font: string; # set by the buttons, not nec. by the text + size: int; # set by the buttons, not nec. by the text + fonttag: string; # set by the buttons, not nec. by the text + configed: array of int; + button1: int; + button3: int; + fontsok: int; # fonts and tags can be set + extensions: list of ref Ext; +}; + +Ext: adt +{ + tkname: string; + modname: string; + mod: Brutusext; + args: string; +}; + +menuindex := "0"; +snarftext := ""; +snarfsgml := ""; +central: chan of (ref File, string); +files: array of ref File; # global but modified only by control thread +plumbed := 0; +curdir := ""; +lang := ""; + +init(c: ref Context, argv: list of string) +{ + ctxt = c; + sys = load Sys Sys->PATH; + if (ctxt == nil) { + sys->fprint(sys->fildes(2), "brutus: no window context\n"); + raise "fail:bad context"; + } + draw = load Draw Draw->PATH; + tk = load Tk Tk->PATH; + tkclient = load Tkclient Tkclient->PATH; + dialog = load Dialog Dialog->PATH; + selectfile = load Selectfile Selectfile->PATH; + bufio = load Bufio Bufio->PATH; + plumbmsg = load Plumbmsg Plumbmsg->PATH; + + if(plumbmsg->init(1, "edit", 1000) >= 0){ + plumbed = 1; + workdir := load Workdir Workdir->PATH; + curdir = workdir->init(); + workdir = nil; + } + + tkclient->init(); + dialog->init(); + selectfile->init(); + sys->pctl(Sys->NEWPGRP, nil); # so we can pass "exit" command to tkclient + + file := ""; + if(argv != nil) + argv = tl argv; + if(argv != nil) + file = hd argv; + central = chan of (ref File, string); + spawn control(ctxt); + <-central; + spawn brutus(ctxt, file); +} + +# build menu button for dynamically generated menu +buttoncfg(label, enable: string): string +{ + return "label .b."+label+" -text "+label + " " + enable + + ";bind .b."+label+" <Button-1> {send cmd "+label+"}" + + ";bind .b."+label+" <ButtonRelease-1> {}" + + ";bind .b."+label+" <Motion-Button-1> {}" + + ";bind .b."+label+" <Double-Button-1> {}" + + ";bind .b."+label+" <Double-ButtonRelease-1> {}" + + ";bind .b."+label+" <Enter> {.b."+label+" configure -background #EEEEEE}" + + ";bind .b."+label+" <Leave> {.b."+label+" configure -background #DDDDDD}"; +} + +tkchans(t: ref Tk->Toplevel): (chan of string, chan of string, chan of string, chan of string, chan of string, chan of string, chan of string) +{ + keys := chan of string; + tk->namechan(t, keys, "keys"); + edit := chan of string; + tk->namechan(t, edit, "edit"); + cmd := chan of string; + tk->namechan(t, cmd, "cmd"); + but1 := chan of string; + tk->namechan(t, but1, "but1"); + but2 := chan of string; + tk->namechan(t, but2, "but2"); + but3 := chan of string; + tk->namechan(t, but3, "but3"); + drag := chan of string; + tk->namechan(t, drag, "Wm_drag"); + return (keys, edit, cmd, but1, but2, but3, drag); +} + +control(ctxt: ref Context) +{ + (t, titlectl) := tkclient->toplevel(ctxt, SETFONT, Name, Tkclient->Appl); + + # f is not used to store anything, just to simplify interfaces + # shared by control and brutus + f := ref File (t, 1, 0, 0, "", 0, DEFFONTNAME, DEFSIZE, DEFTAG, nil, 0, 0, 0, nil); + + tkcmds(t, menu_cfg); + tkcmd(t, "frame .b"); + tkcmd(t, buttoncfg("File", "")); + tkcmds(t, control_cfg); + tkcmds(t, input_cfg); + files = array[1] of ref File; + files[0] = f; + + (keys, edit, cmd, but1, but2, but3, drag) := tkchans(t); + + tkcmd(t, ".ft.t mark set typingstart 1.0; .ft.t mark gravity typingstart left"); + central <-= (nil, ""); # signal readiness +# spawn tkclient->wmctl(t, "task"); + curfile: ref File; + + plumbc := chan of (string, string); + spawn plumbproc(plumbc); + + tkclient->startinput(t, "kbd"::"ptr"::nil); + tkclient->onscreen(t, nil); + tkclient->wmctl(t, "task"); + for(;;) alt { + s := <-t.ctxt.kbd => + tk->keyboard(t, s); + s := <-t.ctxt.ptr => + tk->pointer(t, *s); + + menu := <-t.ctxt.ctl or + menu = <-t.wreq or + menu = <-titlectl => + if(menu == "exit"){ + if(shutdown(ctxt, t)){ + killplumb(); + tkclient->wmctl(t, menu); + } + break; + } + # spawn tkclient->wmctl(t, menu); + tkclient->wmctl(t, menu); + + ecmd := <-edit => + editor(f, ecmd); + tkcmd(t, FOCUS); + + c := <-cmd => + (nil, s) := sys->tokenize(c, " "); + case hd s { + * => + sys->print("unknown control cmd %s\n",c ); + "File" => + filemenu(t, 0, 0); + "new" => + (name, ok, nil) := getfilename(ctxt, t, "file for new window", f.name, 1, 0, 0); + if(ok) + spawn brutus(ctxt, name); + "select" => + n := int hd tl s; + if(n > len files) + break; + if(n > 0) + curfile = files[n]; + tkcmd(files[n].tk, ". map; raise .; focus .ft.t"); + "focus" => + ; + } + + (file, action) := <-central => + (nil, s) := sys->tokenize(action, " "); + case hd s { + * => + sys->print("control unknown central command %s\n", action); + "new" => + curfile = file; + nfiles := array[len files+1] of ref File; + nfiles[0:] = files; + files = nfiles; + nfiles = nil; # make sure references don't linger + files[len files-1] = file; + "name" => + name := nameof(file); + index := 0; + for(i:=1; i<len files; i++) + if(files[i] == file){ + index = i; + break; + } + if(index == 0) + sys->print("can't find file\n"); + "focus" => + if(file != f) + curfile = file; + "select" => + n := int hd tl s; + if(n >= len files) + break; + if(n > 0) + curfile = files[n]; + tkcmd(files[n].tk, ". map; raise .; focus .ft.t; update"); + "exiting" => + if(file == nil) + break; + if(file == curfile) + curfile = nil; + index := 0; + for(i:=1; i<len files; i++) + if(files[i] == file){ + index = i; + break; + } + if(index == 0) + sys->print("can't find file\n"); + else{ + # make a new one rather than slice, to clean up references + nfiles := array[len files-1] of ref File; + for(i=0; i<index; i++) + nfiles[i] = files[i]; + for(; i<len nfiles; i++) + nfiles[i] = files[i+1]; + files = nfiles; + } + file = nil; + } + c := <-keys => + char := typing(f, c); + if(curfile!=nil && char=='\n' && insat(t, "end")) + execute(t, curfile, tkcmd(t, ".ft.t get insert-1line insert")); + + c := <-but1 => + mousebut1(f, c); + + c := <-but2 => + mousebut2(f, c); + + c := <-but3 => + mousebut3(f, c); + + c := <-drag => + if(len c < 6 || c[0:5] != "path=") + break; + spawn brutus(ctxt, c[5:]); + + (fname, addr) := <-plumbc => + for(i:=1; i<len files; i++) + if(files[i].name == fname){ + tkcmd(files[i].tk, ". map; raise .; focus .ft.t"); + showaddr(files[i], addr); + break; + } + if(i == len files){ + if(addr != "") + spawn brutus(ctxt, fname+":"+addr); + else + spawn brutus(ctxt, fname); + } + } +} + +brutus(ctxt: ref Context, filename: string) +{ + addr := ""; + for(i:=len filename; --i>0; ){ + if(filename[i] == ':'){ + (ok, dir) := sys->stat(filename[0:i]); + if(ok >= 0){ + addr = filename[i+1:]; + filename = filename[0:i]; + break; + } + } + } + + (t, titlectl) := tkclient->toplevel(ctxt, SETFONT, Name, Tkclient->Appl); + + f := ref File (t, 0, 0, 0, filename, 0, DEFFONTNAME, DEFSIZE, DEFTAG, nil, 0, 0, 0, nil); + f.configed = array[NTAG] of {* => 0}; + + tkcmds(t, menu_cfg); + tkcmd(t, "frame .b"); + tkcmd(t, buttoncfg("File", "")); + tkcmd(t, buttoncfg("Ext", "-state disabled")); + + tkcmds(t, brutus_cfg); + tkcmds(t, input_cfg); + + # buttons work better when they grab the mouse + a := array[] of {".b.Tag", ".b.Applyfontnow", ".b.Applysizenow", ".b.Applyfontsizenow"}; + for(i=0; i<len a; i++){ + tkcmd(t, "bind "+a[i]+" <Button-1> +{grab set "+a[i]+"}"); + tkcmd(t, "bind "+a[i]+" <ButtonRelease-1> +{grab release "+a[i]+"}"); + } + + (keys, edit, cmd, but1, but2, but3, drag) := tkchans(t); + + configfont(f, "Heading"); + configfont(f, "Title"); + configfont(f, f.fonttag); + tkcmd(t, ".ft.t mark set typingstart 1.0; .ft.t mark gravity typingstart left"); + tkcmd(t, "image create bitmap waiting -file cursor.wait"); + + central <-= (f, "new"); + setfilename(f, filename); + + if(filename != "") + if(loadfile(f, filename) < 0) + dialog->prompt(ctxt, t.image, "error -fg red", + "Open file", + sys->sprint("Can't read %s:\n%r", filename), + 0, "Continue" :: nil); + else + showaddr(f, addr); + + tkclient->onscreen(t, nil); + tkclient->startinput(t, "kbd"::"ptr"::nil); + for(;;) alt { + s := <-t.ctxt.kbd => + tk->keyboard(t, s); + s := <-t.ctxt.ptr => + tk->pointer(t, *s); + + menu := <-t.ctxt.ctl or + menu = <-t.wreq or + menu = <-titlectl => + case menu { + "exit" => + if(f.dirty){ + action := confirm(ctxt, t, nameof(f)+" is dirty", 1); + case action { + "cancel" => + continue; + "exitclean" => + if(dumpfile(f, f.name, f.fontsused) < 0) + continue; + break; + "exitdirty" => + break; + } + } + central <-= (f, "exiting"); + # this one tears down temporaries holding references to f + central <-= (nil, "exiting"); + return; + "task" => + tkcmd(t, ". unmap"); + * => + tkclient->wmctl(t, menu); + } + + ecmd := <-edit => + editor(f, ecmd); + tkcmd(t, FOCUS); + + command := <-cmd => + (nil, c) := sys->tokenize(command, " "); + case hd c { + * => + sys->print("unknown command %s\n", command); + "File" => + filemenu(t, 1, f.fontsok); + "Ext" => + extmenu(t); + "new" => + (name, ok, nil) := getfilename(ctxt, t, "file for new window", f.name, 1, 0, 0); + if(ok) + spawn brutus(ctxt, name); + "open" => + if(f.dirty){ + action := confirm(ctxt, t, nameof(f)+" is dirty", 1); + case action { + "cancel" => + continue; + "exitclean" => + if(dumpfile(f, f.name, f.fontsused) < 0) + continue; + break; + "exitdirty" => + break; + } + } + (name, ok, nil) := getfilename(ctxt, t, "file for this window", f.name, 1, 0, 0); + if(ok && name!=""){ + setfilename(f, name); + if(loadfile(f, name) < 0){ + tkcmd(t, ".ft.t delete 1.0 end"); + dialog->prompt(ctxt, t.image, "error -fg red", + "Open file", + sys->sprint("Can't open %s:\n%r", name), + 0, "Continue"::nil); + } + } + "name" => + (name, ok, nil) := getfilename(ctxt, t, "remembered file name", f.name, 1, 0, 0); + if(ok){ + if(name != f.name){ + setfilename(f, name); + dirty(f, 1); + } + } + "write" => + (name, ok, sgml) := getfilename(ctxt, t, "file to write", f.name, 1, 1, f.fontsused); + if(ok && name!=""){ + if(f.name == ""){ + setfilename(f, name); + dirty(f, 1); + } + dumpfile(f, name, sgml); + } + "fonts" => + if(f.fontsok==0 && f.fontsused==0){ + action := confirm(ctxt, t, "Converting "+nameof(f)+" to SGML", 0); + case action { + "cancel" => + continue; + "exitdirty" => + usingfonts(f); + dirty(f, 1); + } + } + enablefonts(f, !f.fontsok); + "language" => + if(lang == "") + lang = "Hebrew"; + else + lang = ""; + "addext" => + ext := hd tl c; + (args, ok, nil) := getfilename(ctxt, t, "parameters for "+ext, "", 0, 0, 0); + if(ok){ + tkcmd(t, "cursor -image waiting; update"); + addextension(f, ext+" "+args, nil); + usingfonts(f); + dirty(f, 1); + tkcmd(t, "cursor -default; update"); + } + "select" => + central <-= (f, command); + "tag" => + tageditor(ctxt, f); + tkcmd(t, FOCUS); + "font" => + f.font = hd tl c; + tkcmd(t, ".b.Font configure -text "+f.font+";"+UPDATE); + f.fonttag = f.font+"."+string f.size; + configfont(f, f.fonttag); + if(changefont(f, f.font)) + dirty(f, 1); + "size" => + sz := hd tl c; + tkcmd(t, ".b.Size configure -text "+sz+"pt; update"); + f.size = int sz; + f.fonttag = f.font+"."+string f.size; + configfont(f, f.fonttag); + if(changesize(f, string f.size)) + dirty(f, 1); + "applyfont" => + f.applyfont = int tkcmd(t, "variable Applyfont"); + if(f.applyfont) + configfont(f, f.fonttag); + "applyfontnow" => + if(changefont(f, f.font)) + dirty(f, 1); + "applysizenow" => + if(changesize(f, string f.size)) + dirty(f, 1); + "applyfontsizenow" => + if(changefontsize(f, f.fonttag)) + dirty(f, 1); + "put" => + dumpfile(f, f.name, f.fontsused); + "focus" => + central <-= (f, "focus"); + } + + c := <-keys => + typing(f, c); + + c := <-but1 => + mousebut1(f, c); + + c := <-but2 => + mousebut2(f, c); + + c := <-but3 => + mousebut3(f, c); + + c := <-drag => + if(len c < 6 || c[0:5] != "path=") + break; + spawn brutus(ctxt, c[5:]); + } +} + +kbdremap(c: int) : (int, int) +{ + tab: array of Remaptab; + + dir := 1; + case lang{ + "" => + return (c, dir); + "Hebrew" => + tab = hebrewtab; + dir = -1; + * => + sys->print("unknown language %s\n", lang); + return (c, dir); + } + for(i:=0; i<len tab; i++) + if(c == tab[i].in) + return (tab[i].out, dir); + return (c, 1); +} + +typing(f: ref File, c: string): int +{ + t := f.tk; + char := c[1]; + if(char == '\\') + char = c[2]; + update := ";.ft.t see insert;"+UPDATE; + if(char != ESC) + cut(f, 1); + case char { + * => + dir := 1; + if(c[1] != '\\') # safe character; remap it + (c[1], dir) = kbdremap(char); + s := ".ft.t insert insert "+c; + if(dir < 0) + s += ";.ft.t mark set insert insert-1c"; + if(f.applyfont){ + usingfonts(f); + s += f.fonttag; + } + tkcmd(t, s+update); + if(f.fontsused && f.applyfont==0){ + # nasty goo to make sure we don't insert text without a font tag; + # must ask after the fact if default rules set a tag. + names := tkcmd(t, ".ft.t tag names insert-1chars"); + if(!somefont(names)) + tkcmd(t, ".ft.t tag add "+DEFTAG+" insert-1chars"); + } + dirty(f, 1); + ESC => + if(nullsel(t)) + tkcmd(t, ".ft.t tag add sel typingstart insert;"+ + ".ft.t mark set typingstart insert"); + else + cut(f, 1); + tkcmd(t, UPDATE); + BS => + bs(f, "c"); + BSL => + bs(f, "l"); + BSW => + bs(f, "w"); + } + return char; +} + +bs(f: ref File, c: string) +{ + if(!insat(f.tk, "1.0")){ + tkcmd(f.tk, ".ft.t tkTextDelIns -"+c+";.ft.t see insert;"+UPDATE); + dirty(f, 1); + } +} + +mousebut1(f: ref File, c: string) +{ + f.button1 = (c == "pressed"); + f.button3 = 0; # abort any pending button 3 action + tkcmd(f.tk, ".ft.t mark set typingstart insert"); +} + +mousebut2(f: ref File, c: string) +{ + if(f.button1){ + cut(f, 1); + tk->cmd(f.tk, UPDATE); + }else{ + (nil, l) := sys->tokenize(c, " "); + x := int hd l - 50; + y := int hd tl l - int tk->cmd(f.tk, ".m yposition "+menuindex) - 10; +# tkcmd(f.tk, "focus .ft.t"); + tkcmd(f.tk, ".m activate "+menuindex+"; .m post "+string x+" "+string y+ + "; update"); + } +} + +mousebut3(f: ref File, c: string) +{ + t := f.tk; + if(c == "pressed"){ + f.button3 = 1; + if(f.button1){ + paste(f); + tk->cmd(t, "update"); + } + return; + } + if(!plumbed || f.button3==0 || f.button1!=0) + return; + f.button3 = 0; + # Plumb message triggered by release of button 3 + (nil, l) := sys->tokenize(c, " "); + x := int hd tl l; + y := int hd tl tl l; + index := tk->cmd(t, ".ft.t index @"+string x+","+string y); + selindex := tk->cmd(t, ".ft.t tag ranges sel"); + if(selindex != "") + insel := tk->cmd(t, ".ft.t compare sel.first <= "+index)=="1" && + tk->cmd(t, ".ft.t compare sel.last >= "+index)=="1"; + else + insel = 0; + attr := ""; + if(insel) + text := tk->cmd(t, ".ft.t get sel.first sel.last"); + else{ + # have line with text in it + # now extract whitespace-bounded string around click + (nil, w) := sys->tokenize(index, "."); + charno := int hd tl w; + left := tk->cmd(t, ".ft.t index {"+index+" linestart}"); + right := tk->cmd(t, ".ft.t index {"+index+" lineend}"); + line := tk->cmd(t, ".ft.t get "+left+" "+right); + for(i:=charno; i>0; --i) + if(line[i-1]==' ' || line[i-1]=='\t') + break; + for(j:=charno; j<len line; j++) + if(line[j]==' ' || line[j]=='\t') + break; + text = line[i:j]; + attr = "click="+string (charno-i); + } + msg := ref Msg( + "Brutus", + "", + directory(f), + "text", + attr, + array of byte text); + if(msg.send() < 0) + sys->fprint(sys->fildes(2), "brutus: plumbing write error: %r\n"); +} + +directory(f: ref File): string +{ + for(i:=len f.name; --i>=0;) + if(f.name[i] == '/'){ + if(i == 0) + i++; + return f.name[0:i]; + } + return curdir; +} + +enablefonts(f: ref File, enable: int) +{ + for(i:=0; i<len fontbuts; i++) + tkcmd(f.tk, fontbuts[i] + " configure -state "+enabled[enable]); + tkcmd(f.tk, "update"); + f.fontsok = enable; +} + +filemenu(t: ref tk->Toplevel, buttons, fontsok: int) +{ + tkcmd(t, "menu .b.Filemenu"); + tkcmd(t, ".b.Filemenu add command -label New -command {send cmd new}"); + if(buttons){ + tkcmd(t, ".b.Filemenu add command -label Open -command {send cmd open}"); + tkcmd(t, ".b.Filemenu add command -label Name -command {send cmd name}"); + tkcmd(t, ".b.Filemenu add command -label Write -command {send cmd write}"); + if(fontsok) + pre := "Dis"; + else + pre = "En"; + tkcmd(t, ".b.Filemenu add command -label {" + +pre+"able Fonts} -command {send cmd fonts}"); + if(lang == "") + pre = "En"; + else + pre = "Dis"; + tkcmd(t, ".b.Filemenu add command -label {" + +pre+"able Hebrew} -command {send cmd language}"); + } + tkcmd(t, ".b.Filemenu add command -label {["+Name+"]} -command {send cmd select 0}"); + if(files != nil) + for(i:=1; i<len files; i++){ + name := nameof(files[i]); + if(files[i].dirty) + name = "{' "+name+"}"; + else + name = "{ "+name+"}"; + tkcmd(t, ".b.Filemenu add command -label "+name+ + " -command {send cmd select "+string i+"}"); + } + tkcmd(t, "bind .b.Filemenu <Unmap> {destroy .b.Filemenu}"); + x := tk->cmd(t, ".ft.scroll cget actx"); + y := tk->cmd(t, ".ft.scroll cget acty"); + tkcmd(t, ".b.Filemenu post "+x+" "+y+"; grab set .b.Filemenu; update"); +} + +extmenu(t: ref tk->Toplevel) +{ + fd := sys->open(EXTDIR, Sys->OREAD); + if(fd == nil || ((n,dir):=sys->dirread(fd)).t0<=0){ + sys->print("%s: can't find extension directory %s: %r\n", Name, EXTDIR); + return; + } + + tkcmd(t, "menu .b.Extmenu"); + for(i:=0; i<n; i++){ + name := dir[i].name; + if(len name>4 && name[len name-4:]==".dis"){ + name = name[0:len name-4]; + tkcmd(t, ".b.Extmenu add command -label {Add "+name+ + "} -command {send cmd addext "+name+"}"); + } + } + + tkcmd(t, "bind .b.Extmenu <Unmap> {destroy .b.Extmenu}"); + x := tk->cmd(t, ".ft.scroll cget actx"); + y := tk->cmd(t, ".ft.scroll cget acty"); + tkcmd(t, ".b.Extmenu post "+x+" "+y+"; grab set .b.Extmenu; update"); +} + +basepath(file: string): (string, string) +{ + for(i := len file-1; i >= 0; i--) { + if(file[i] == '/') + return (file[0:i], file[i+1:]); + } + return (".", file); +} + +putbut(f: ref File) +{ + state := enabled[f.dirty]; + if(f.name != "") + tkcmd(f.tk, ".b.Put configure -state "+state+"; update"); +} + +dirty(f: ref File, nowdirty: int) +{ + if(f.isctl) + return; + old := f.dirty; + f.dirty = nowdirty; + if(old != nowdirty){ + setfilename(f, f.name); + putbut(f); + } +} + +setfilename(f: ref File, name: string) +{ + oldname := f.name; + f.name = name; + if(oldname=="" && name!="") + putbut(f); + name = Name + ": \"" +nameof(f)+ "\""; + if(f.dirty) + name += " (dirty)"; + tkclient->settitle(f.tk, name); + tkcmd(f.tk, UPDATE); + central <-= (f, "name"); +} + +configfont(f: ref File, tag: string) +{ + for(i:=0; i<NTAG; i++) + if(tag == tagname[i]){ + if(f.configed[i] == 0){ + tkcmd(f.tk, ".ft.t tag configure "+tag+" "+tagconfig[i]); + f.configed[i] = 1; + } + return; + } + sys->print("Brutus: can't configure font %s\n", tag); +} + +insat(t: ref Tk->Toplevel, mark: string): int +{ + return tkcmd(t, ".ft.t compare insert == "+mark) == "1"; +} + +isalnum(s: string): int +{ + if(s == "") + return 0; + c := s[0]; + if('a' <= c && c <= 'z') + return 1; + if('A' <= c && c <= 'Z') + return 1; + if('0' <= c && c <= '9') + return 1; + if(c == '_') + return 1; + if(c > 16rA0) + return 1; + return 0; +} + +editor(f: ref File, ecmd: string) +{ + + case ecmd { + "cut" => + menuindex = "0"; + cut(f, 1); + + "paste" => + menuindex = "1"; + paste(f); + + "snarf" => + menuindex = "2"; + if(nullsel(f.tk)) + return; + snarf(f); + + "look" => + menuindex = "3"; + look(f); + } + tkcmd(f.tk, UPDATE); +} + +nullsel(t: ref Tk->Toplevel): int +{ + return tkcmd(t, ".ft.t tag ranges sel") == ""; +} + +cut(f: ref File, snarfit: int) +{ + if(nullsel(f.tk)) + return; + dirty(f, 1); + if(snarfit) + snarf(f); + # sometimes when clicking fast, selection and insert point can + # separate. the only time this really matters is when typing into + # a double-clicked selection. it's easy to fix here. + tkcmd(f.tk, ".ft.t mark set insert sel.first;.ft.t delete sel.first sel.last"); +} + +snarf(f: ref File) +{ + # convert sel.first and sel.last to numeric forms because sgml() + # must clear selection to avoid <sel> tags in result. + (nil, sel) := sys->tokenize(tkcmd(f.tk, ".ft.t tag ranges sel"), " "); + snarftext = tkcmd(f.tk, ".ft.t get "+hd sel+" "+hd tl sel); + snarfsgml = sgml(f.tk, "-sgml", hd sel, hd tl sel); + tkclient->snarfput(snarftext); +} + +paste(f: ref File) +{ +# good question + snarftext = tkclient->snarfget(); + if(snarftext == "" && (f.fontsused == 0 || snarfsgml == nil)) + return; + cut(f, 0); + dirty(f, 1); + + t := f.tk; + start := tkcmd(t, ".ft.t index insert"); + if(f.fontsused == 0) + tkcmd(t, ".ft.t insert insert '"+snarftext); + else if(f.applyfont) + tkcmd(t, ".ft.t insert insert "+tk->quote(snarftext)+" "+f.fonttag); + else + insert(f, snarfsgml); + tkcmd(t, ".ft.t tag add sel "+start+" insert"); +} + +look(f: ref File) +{ + t := f.tk; + (sel0, sel1) := word(t); + if(sel0 == nil) + return; + text := tkcmd(t, ".ft.t get "+sel0+" "+sel1); + if(text == nil) + return; + tkcmd(t, "cursor -image waiting; update"); + search(nil, f, text, 0, 0); + tkcmd(t, "cursor -default; update"); +} + +# First time fonts are used explicitly, establish font tags for all extant text. +usingfonts(f: ref File) +{ + if(f.fontsused) + return; + tkcmd(f.tk, ".ft.t tag add "+DEFTAG+" 1.0 end"); + f.fontsused = 1; +} + +word(t: ref Tk->Toplevel): (string, string) +{ + start := "sel.first"; + end := "sel.last"; + if(nullsel(t)){ + insert := tkcmd(t, ".ft.t index insert"); + start = tkcmd(t, ".ft.t index {insert wordstart}"); + if(insert == start){ # tk's definition of 'wordstart' is bogus + # if at beginning, tk->cmd will return !error and a0 will be false. + a0 := isalnum(tk->cmd(t, ".ft.t get insert-1chars")); + a1 := isalnum(tk->cmd(t, ".ft.t get insert")); + if(a0==0 && a1==0) + return (nil, nil); + if(a1 == 0) + start = tkcmd(t, ".ft.t index {insert-1chars wordstart}"); + } + end = tkcmd(t, ".ft.t index {"+start+" wordend}"); + if(start == end) + return (nil, nil); + } + return (start, end); +} + +# Change the font associated with the selection +changefont(f: ref File, font: string): int +{ + t := f.tk; + (sel0, sel1) := word(f.tk); + mod := 0; + if(sel0 == nil) + return mod; + usingfonts(f); + for(i:=0; i<NFONT; i++){ + if(fontname[i] == font) + continue; + for(j:=0; j<NSIZE; j++){ + tag := fontname[i]+"."+sizename[j]; + start := sel0; + for(;;){ + range := tkcmd(t, ".ft.t tag nextrange "+tag+" "+start+" "+sel1); + if(len range > 0 && range[0] == '!') + break; + (nil, tt) := sys->tokenize(range, " "); + if(tt == nil) + break; + tkcmd(t, ".ft.t tag remove "+tag+" "+hd tt+" "+hd tl tt); + fs := font+"."+sizename[j]; + tkcmd(t, ".ft.t tag add "+fs+" "+hd tt+" "+hd tl tt); + configfont(f, fs); + start = hd tl tt; + mod = 1; + } + } + } + tkcmd(t, UPDATE); + return mod; +} + +# See if tag list includes a font name +somefont(tag: string): int +{ + (nil, tt) := sys->tokenize(tag, " "); + for(; tt!=nil; tt=tl tt) + for(i:=0; i<NFONT*NSIZE; i++){ + if(tagname[i] == hd tt) + return 1; + } + return 0; +} + +# Change the size associated with the selection +changesize(f: ref File, size: string): int +{ + t := f.tk; + (sel0, sel1) := word(f.tk); + mod := 0; + if(sel0 == nil) + return mod; + usingfonts(f); + for(i:=0; i<NFONT; i++){ + for(j:=0; j<NSIZE; j++){ + if(sizename[j] == size) + continue; + tag := fontname[i]+"."+sizename[j]; + start := sel0; + for(;;){ + range := tkcmd(t, ".ft.t tag nextrange "+tag+" "+start+" "+sel1); + if(len range > 0 && range[0] == '!') + break; + (nil, tt) := sys->tokenize(range, " "); + if(tt == nil) + break; + tkcmd(t, ".ft.t tag remove "+tag+" "+hd tt+" "+hd tl tt); + fs := fontname[i]+"."+size; + tkcmd(t, ".ft.t tag add "+fs+" "+hd tt+" "+hd tl tt); + configfont(f, fs); + start = hd tl tt; + mod = 1; + } + } + } + tkcmd(t, UPDATE); + return mod; +} + +# Change the font and size associated with the selection +changefontsize(f: ref File, newfontsize: string): int +{ + t := f.tk; + (sel0, sel1) := word(f.tk); + if(sel0 == nil) + return 0; + usingfonts(f); + (nil, names) := sys->tokenize(tkcmd(t, ".ft.t tag names"), " "); + # clear old tags + tags := tagname[0:NFONT*NSIZE]; + for(l:=names; l!=nil; l=tl l) + for(i:=0; i<len tags; i++) + if(tags[i] == hd l) + tkcmd(t, ".ft.t tag remove "+hd l+" "+sel0+" "+sel1); + tkcmd(t, ".ft.t tag add "+newfontsize+" "+sel0+" "+sel1+"; update"); + return 1; +} + +listtostring(l: list of string): string +{ + s := "{"; + while(l != nil){ + if(len s == 1) + s += hd l; + else + s += " " + hd l; + l = tl l; + } + s += "}"; + return s; +} + +# splitl based on indices rather than slices. this version returns char +# position of the matching character. +splitl(str: string, i, j: int, pat: string): int +{ + while(i < j){ + c := str[i]; + for(k:=len pat-1; k>=0; k--) + if(c == pat[k]) + return i; + i++; + } + return i; +} + +# splitstrl based on indices rather than slices. this version returns char +# position of the beginning of the matching string. +splitstrl(str: string, i, j: int, pat: string): int +{ + l := len pat; + if(l == 0) # shouldn't happen, but be safe + return j; + first := pat[0]; + while(i <= j-l){ + # check first char for speed + if(str[i] == first){ + for(k:=1; k<l && str[i+k]==pat[k]; k++) + ; + if(k == l) + return i; + } + i++; + } + return j; +} + +# place the text, as annotated by SGML tags, into document +# where indicated by insert mark +insert(f: ref File, sgml: string) +{ + taglist: list of string; + + t := f.tk; + usingfonts(f); + if(f.applyfont) + taglist = f.fonttag :: taglist; + tag := listtostring(taglist); + end := len sgml; + j: int; + for(i:=0; i<end; i=j){ + j = splitl(sgml, i, end, "<&"); + tt := tag; + if(tt=="" || tt=="{}") + tt = DEFTAG; # can happen e.g. when pasting plain text + if(j > i) + tkcmd(t, ".ft.t insert insert "+tk->quote(sgml[i:j])+" "+tt); + if(j < end) + case sgml[j] { + '&' => + if(j+4<=end && sgml[j:j+4]=="<"){ + tkcmd(t, ".ft.t insert insert "+"{<} "+tt); + j += 4; + }else{ + tkcmd(t, ".ft.t insert insert {&} "+tt); + j += 1; + } + '<' => + (nc, newtag, on) := tagstring(sgml, j, end); + if(nc < 0){ + tkcmd(t, ".ft.t insert insert "+"{<} "+tt); + j += 1; + }else if(len newtag>9 && newtag[0:10]=="Extension "){ + addextension(f, newtag[10:], taglist); + j += nc; + }else if(len newtag>9 && newtag[0:7]=="Window "){ + repostextension(f, newtag[7:], taglist); + j += nc; + }else{ + if(on){ + taglist = newtag :: taglist; + configfont(f, newtag); + }else{ + taglist = drop(taglist, newtag); + if(f.applyfont && hasfonts(taglist)==0) + taglist = f.fonttag :: taglist; + } + j += nc; + tag = listtostring(taglist); + } + } + } +} + +drop(l: list of string, s: string): list of string +{ + n: list of string; + while(l != nil){ + if(s != hd l) + n = hd l :: n; + l = tl l; + } + return n; +} + +extid := 0; +addextension(f: ref File, s: string, taglist: list of string) +{ + for(i:=0; i<len s; i++) + if(s[i] == ' ') + break; + if(i == 0 || i == len s){ + sys->print("Brutus: badly formed extension %s\n", s); + return; + } + modname := s[0:i]; + s = s[i+1:]; + + mod: Brutusext; + for(el:=f.extensions; el!=nil; el=tl el) + if(modname == (hd el).modname){ + mod = (hd el).mod; + break; + } + + if(mod == nil){ + file := modname; + if(i < 4 || file[i-4:i] != ".dis") + file += ".dis"; + if(file[0] != '/') + file = "/dis/wm/brutus/" + file; + mod = load Brutusext file; + if(mod == nil){ + sys->print("%s: can't load module %s: %r\n", Name, file); + return; + } + } + mkextension(f, mod, modname, s, taglist); +} + +repostextension(f: ref File, tkname: string, taglist: list of string) +{ + mod: Brutusext; + for(el:=f.extensions; el!=nil; el=tl el) + if(tkname == (hd el).tkname){ + mod = (hd el).mod; + break; + } + if(mod == nil){ + sys->print("Brutus: can't find extension widget %s: %r\n", tkname); + return; + } + + mkextension(f, mod, (hd el).modname, (hd el).args, taglist); +} + +mkextension(f: ref File, mod: Brutusext, modname, args: string, taglist: list of string) +{ + t := f.tk; + + name := ".ext"+string extid++; + mod->init(sys, draw, bufio, tk, tkclient); + err := mod->create(f.name, t, name, args); + if(err != ""){ + sys->print("%s: can't create extension widget %s: %s\n", Name, modname, err); + return; + } + tkcmd(t, ".ft.t window create insert -window "+name); + while(taglist != nil){ + tkcmd(t, ".ft.t tag add "+hd taglist+" "+name); + taglist = tl taglist; + } + f.extensions = ref Ext(name, modname, mod, args) :: f.extensions; +} + +# rewrite <window .ext1> tags into <Extension module args> +extrewrite(f: ref File, sgml: string): string +{ + if(f.extensions == nil) + return sgml; + + new := ""; + + end := len sgml; + j: int; + for(i:=0; i<end; i=j){ + j = splitstrl(sgml, i, end, "<Window "); + if(j > i) + new += sgml[i:j]; + if(j < end){ + j += 8; + for(k:=j; sgml[k]!='>' && k<end; k++) + ; + tkname := sgml[j:k]; + for(el:=f.extensions; el!=nil; el=tl el) + if((hd el).tkname == tkname) + break; + if(el == nil) + sys->print("%s: unrecognized extension %s\n", Name, tkname); + else{ + e := hd el; + new += "<Extension "+e.modname+" "+e.args+">"; + } + j = k+1; # skip '>' + } + } + return new; +} + +hasfonts(l: list of string): int +{ + for(i:=0; i<NFONT*NSIZE; i++) + for(ll:=l; ll!=nil; ll=tl ll) + if(hd ll == tagname[i]) + return 1; + return 0; +} + +# s[i] is known to be a less-than sign +tagstring(s: string, i, end: int): (int, string, int) +{ + tag: string; + + j := splitl(s, i+1, end, ">"); + if(j==end || s[j]!='>') + return (-1, "", 0); + nc := (j-i)+1; + on := 1; + if(s[i+1] == '/'){ + on = 0; + i++; + } + tag = s[i+1:j]; +# NEED TO CHECK VALIDITY OF TAG + return (nc, tag, on); +} + +sgml(t: ref Tk->Toplevel, flag, start, end: string): string +{ + # turn off selection, to avoid getting that in output + sel := tkcmd(t, ".ft.t tag ranges sel"); + if(sel != "") + tkcmd(t, ".ft.t tag remove sel "+sel); + s := tkcmd(t, ".ft.t dump "+flag+" "+start+" "+end); + if(sel != "") + tkcmd(t, ".ft.t tag add sel "+sel); + return s; +} + +loadfile(f: ref File, file: string): int +{ + f.size = DEFSIZE; + f.font = DEFFONTNAME; + f.fonttag = DEFTAG; + f.fontsused = 0; + enablefonts(f, 0); + t := f.tk; + tkcmd(t, ".b.Font configure -text "+f.font); + tkcmd(t, ".b.Size configure -text "+string f.size+"pt"); + tkcmd(t, "cursor -image waiting; update"); + r := loadfile1(f, file); + tkcmd(t, "cursor -default"); + return r; +} + +loadfile1(f: ref File, file: string): int +{ + fd := bufio->open(file, Sys->OREAD); + if(fd == nil) + return -1; + (ok, dir) := sys->fstat(fd.fd); + if(ok < 0){ + fd.close(); + return -1; + } + l := int dir.length; + a := array[l] of byte; + n := fd.read(a, len a); + fd.close(); + if(n != len a) + return -1; + t := f.tk; + tkcmd(t, ".ft.t delete 1.0 end"); + if(len a>=7 && string a[0:7]=="<SGML>\n") + insert(f, string a[7:n]); + else + tkcmd(t, ".ft.t insert 1.0 '"+string a[0:n]); + dirty(f, 0); + tkcmd(t, ".ft.t mark set insert 1.0; update"); + return 1; +} + +dumpfile(f: ref File, file: string, sgml: int): int +{ + tkcmd(f.tk, "cursor -image waiting"); + r := dumpfile1(f, file, sgml); + tkcmd(f.tk, "cursor -default"); + return r; +} + +dumpfile1(f: ref File, file: string, sgml: int): int +{ + if(writefile(f, file, sgml) < 0){ + dialog->prompt(ctxt, f.tk.image, "error -fg red", + "Write file", + sys->sprint("Can't write %s:\n%r", file), + 0, "Continue"::nil); + tkcmd(f.tk, FOCUS); + return -1; + } + return 1; +} + +writefile(f: ref File, file: string, sgmlfmt: int): int +{ + if(file == "") + return -1; + fd := bufio->create(file, Sys->OWRITE, 8r666); + if(fd == nil) + return -1; + + t := f.tk; + flag := ""; + if(sgmlfmt){ + flag = "-sgml"; + prefix := "<SGML>\n"; + if(f.fontsused == 0) + prefix += "<"+DEFTAG+">"; + x := array of byte prefix; + if(fd.write(x, len x) != len x){ + fd.close(); + return -1; + } + } + sgmltext := sgml(t, flag, "1.0", "end"); + if(sgmlfmt) + sgmltext = extrewrite(f, sgmltext); + a := array of byte sgmltext; + if(fd.write(a, len a) != len a){ + fd.close(); + return -1; + } + if(sgmlfmt && f.fontsused==0){ + suffix := array of byte ("</"+DEFTAG+">"); + if(fd.write(suffix, len suffix) != len suffix){ + fd.close(); + return -1; + } + } + if(fd.flush() < 0){ + fd.close(); + return -1; + } + fd.close(); + if(file == f.name){ + dirty(f, sgmlfmt!=f.fontsused); + tkcmd(t, UPDATE); + } + return 1; +} + +shutdown(s: ref Draw->Context, t: ref Tk->Toplevel): int +{ + for(i:=1; i<len files; i++){ + f := files[i]; + if(f.dirty){ + action := confirm(s, t, "file "+nameof(f)+" is dirty", 1); + case action { + "cancel" => + return 0; + "exitclean" => + if(dumpfile(f, f.name, f.fontsused) < 0) + return 0; + "exitdirty" => + break; + } + } + } + return 1; +} + +nameof(f: ref File): string +{ + s := f.name; + if(s == "") + s = "(unnamed)"; + return s; +} + +tkcmd(t: ref Tk->Toplevel, s: string): string +{ + res := tk->cmd(t, s); + if(len res > 0 && res[0] == '!') + sys->print("%s: tk error executing '%s': %s\n", Name, s, res); + return res; +} + +confirm_cfg := array[] of { + "frame .f -borderwidth 2 -relief groove -padx 3 -pady 3", + "frame .f.f", +# "label .f.f.l -bitmap error -foreground red", + "label .f.f.l -text Warning:", + "label .f.f.m", + "button .f.exitclean -text { Write and Proceed } -width 17w -command {send cmd exitclean}", + "button .f.exitdirty -text { Proceed } -width 17w -command {send cmd exitdirty}", + "button .f.cancel -text { Cancel } -width 17w -command {send cmd cancel}", + "pack .f.f.l .f.f.m -side left", + "pack .f.f .f.exitclean .f.exitdirty .f.cancel -padx 10 -pady 10", + "pack .f", +}; + +widget(parent: ref Tk->Toplevel, ctxt: ref Draw->Context, cfg: array of string): ref Tk->Toplevel +{ + x := int tk->cmd(parent, ". cget -x"); + y := int tk->cmd(parent, ". cget -y"); + where := sys->sprint("-x %d -y %d ", x+45, y+25); + (t,nil) := tkclient->toplevel(ctxt, where+SETFONT+" -borderwidth 2 -relief raised", "", tkclient->Plain); + tkcmds(t, cfg); + return t; +} + +tkcmds(top: ref Tk->Toplevel, a: array of string) +{ + for(i := 0; i < len a; i++) + v := tk->cmd(top, a[i]); +} + +confirm(ctxt: ref Draw->Context, parent: ref Tk->Toplevel, message: string, write: int): string +{ + s := confirm1(ctxt, parent, message, write); + tkcmd(parent, FOCUS); + return s; +} + +confirm1(ctxt: ref Draw->Context, parent: ref Tk->Toplevel, message: string, write: int): string +{ + t := widget(parent, ctxt, confirm_cfg); + tkcmd(t, ".f.f.m configure -text '"+message); + if(write == 0) + tkcmd(t, "destroy .f.exitclean"); + tkcmd(t, UPDATE); + cmd := chan of string; + tk->namechan(t, cmd, "cmd"); + return <-cmd; +} + +getfilename_cfg := array[] of { + "frame .f", + "label .f.Message", + "entry .f.Name -width 25w", + "checkbutton .f.SGML -text { Write SGML } -variable SGML", + "button .f.Ok -text { OK } -width 14w -command {send cmd ok}", + "button .f.Browse -text { Browse } -width 14w -command {send cmd browse}", + "button .f.Cancel -text { Cancel } -width 14w -command {send cmd cancel}", + "bind .f.Name <Control-j> {send cmd ok}", + "pack .f.Message .f.Name .f.SGML .f.Ok .f.Browse .f.Cancel -padx 10 -pady 10", + "pack .f", + "focus .f.Name", +}; + +getfilename(ctxt: ref Draw->Context, parent: ref Tk->Toplevel, message, name: string, browse, sgml, nowsgml: int): (string, int, int) +{ + (s, i, issgml) := getfilename1(ctxt, parent, message, name, browse, sgml, nowsgml); + tkcmd(parent, FOCUS); + return (s, i, issgml); +} + +getfilename1(ctxt: ref Draw->Context, parent: ref Tk->Toplevel, message, name: string, browse, sgml, nowsgml: int): (string, int, int) +{ + t := widget(parent, ctxt, getfilename_cfg); + tkcmds(t, getfilename_cfg); + + tkcmd(t, ".f.Message configure -text '"+message); + tk->cmd(t, ".f.Name insert 0 "+name); + if(browse == 0) + tkcmd(t, "destroy .f.Browse"); + if(sgml == 0) + tkcmd(t, "destroy .f.SGML"); + else if(nowsgml) + tkcmd(t, ".f.SGML select"); + tkcmd(t, UPDATE); + cmd := chan of string; + tk->namechan(t, cmd, "cmd"); + tkclient->onscreen(t, "exact"); + tkclient->startinput(t, "kbd"::"ptr"::nil); + for(;;) alt { + s := <-t.ctxt.kbd => + tk->keyboard(t, s); + s := <-t.ctxt.ptr => + tk->pointer(t, *s); + c := <-cmd => + case c { + "ok" => + return (tkcmd(t, ".f.Name get"), 1, int tkcmd(t, "variable SGML")); + "cancel" => + return ("", 0, 0); + "browse" => + name = tkcmd(t, ".f.Name get"); + (dir, path) := basepath(name); + + pat := list of { + "* (All files)", + "*.sgml (SGML dump files)", + "*.html (Web source files)", + "*.tex (Latex source files)", + "*.[bm] (Limbo source files)" + }; + + path = selectfile->filename(ctxt, parent.image, message, pat, dir); + if(path != "") + name = path; + tk->cmd(t, ".f.Name delete 0 end; .f.Name insert 0 "+name+";focus .f.Name; update"); + if(path != "") + return (name, 1, int tkcmd(t, "variable SGML")); + } + } +} + +tageditor(ctxt: ref Draw->Context, f: ref File) +{ + (start, end) := word(f.tk); + if(start == nil) + return; + cfg := array[100] of string; + i := 0; + cfg[i++] = "frame .f"; + (nil, names) := sys->tokenize(tkcmd(f.tk, ".ft.t tag names "+start), " "); + pack := "pack"; + set := array[NEXTRA] of int; + for(j:=0; j<NEXTRA; j++){ + n := tagname[j+NFONT*NSIZE]; + cfg[i++] = "checkbutton .f.c"+string j+" -variable c"+string j+ + " -text {"+n+"} -command {send cmd "+string j+"} -anchor w"; + pack += " .f.c"+string j; + set[j] = 0; + for(l:=names; l!=nil; l=tl l) + if(hd l == n){ + cfg[i++] = ".f.c"+string j+" select"; + set[j] = 1; + } + } + cfg[i++] = "button .f.Ok -text { OK } -width 6w -command {send cmd ok}"; + cfg[i++] = "button .f.Cancel -text { Cancel } -width 6w -command {send cmd cancel}"; + cfg[i++] = pack + " -padx 3 -pady 0 -fill x"; + cfg[i++] = "pack .f.Ok .f.Cancel -padx 2 -pady 2 -side left"; + cfg[i++] = "pack .f; grab set .f; update"; + t := widget(f.tk, ctxt, cfg[0:i]); + cmd := chan of string; + tk->namechan(t, cmd, "cmd"); + + loop: + for(;;){ + case c := <-cmd { + "ok" => + break loop; + "cancel" => + return; + * => + j = int c; + set[j] = (tkcmd(t, "variable c"+c) == "1"); + } + } + for(j=0; j<NEXTRA; j++){ + s := tagname[j+NFONT*NSIZE]; + if(set[j]){ + configfont(f, s); + tkcmd(f.tk, ".ft.t tag add "+s+" "+start+" "+end); + }else + tkcmd(f.tk, ".ft.t tag remove "+s+" "+start+" "+end); + } + dirty(f, 1); + usingfonts(f); + tkcmd(f.tk, UPDATE); +} + +plumbpid: int; +plumbproc(plumbc: chan of (string, string)) +{ + plumbpid = sys->pctl(0, nil); + + for(;;){ + msg := Msg.recv(); + if(msg == nil){ + sys->print("Brutus: can't read /chan/plumb.edit: %r\n"); + plumbpid = 0; + return; + } + if(msg.kind != "text"){ + sys->print("Brutus: can't interpret '%s' kind of message\n", msg.kind); + continue; + } + text := string msg.data; + n := len text; + addr := ""; + for(j:=0; j<n; j++) + if(text[j] == ':'){ + addr = text[j+1:]; + break; + } + file := text[0:j]; + if(len file>0 && file[0]!='/' && len msg.dir>0){ + if(msg.dir[len msg.dir-1] == '/') + file = msg.dir+file; + else + file = msg.dir+"/"+file; + } + plumbc <-= (file, addr); + } +} + +killplumb() +{ + if(plumbed == 0) + return; + plumbmsg->shutdown(); + if(plumbpid <= 0) + return; + fname := sys->sprint("#p/%d/ctl", plumbpid); + fd := sys->open(fname, sys->OWRITE); + if(fd != nil) + sys->write(fd, array of byte "kill\n", 8); +} + +lastpat: string; + +execute(cmdwin: ref Tk->Toplevel, f: ref File, cmd: string) +{ + if(len cmd>1 && cmd[len cmd-1]=='\n') + cmd = cmd[0:len cmd-1]; + if(cmd == "") + return; + if(cmd[0] == '/' || cmd[0]=='?'){ + search(cmdwin, f, cmd[1:], cmd[0]=='?', 1); + return; + } + for(i:=0; i<len cmd; i++) + if(cmd[i]<'0' || '9'<cmd[i]){ + sys->print("bad command %s\n", cmd); + return; + } + t := f.tk; + line := int cmd; + if(!nullsel(t)) + tkcmd(t, NOSEL); + tkcmd(t, ".ft.t tag add sel "+string line+".0 {"+string line+".0 lineend+1char}"); + tkcmd(t, ".ft.t mark set insert "+string line+".0; .ft.t see insert;update"); +} + +search(cmdwin: ref Tk->Toplevel, f: ref File, pat: string, backwards, uselast: int) +{ + t := f.tk; + if(pat == nil) + pat = lastpat; + else if(uselast) + lastpat = pat; + if(pat == nil){ + error(cmdwin, "no pattern"); + return; + } + cmd := ".ft.t search "; + if(backwards) + cmd += "-backwards "; + p := ""; + for(i:=0; i<len pat; i++){ + if(pat[i]== '\\' || pat[i]=='{') + p[len p] = '\\'; + p[len p] = pat[i]; + } + cmd += "{"+p+"} "; + null := nullsel(t); + if(null) + cmd += "insert"; + else if(backwards) + cmd += "sel.first"; + else + cmd += "sel.last"; + s := tk->cmd(t, cmd); + if(s == "") + error(cmdwin, "not found"); + else{ + if(!null) + tkcmd(t, NOSEL); + tkcmd(t, ".ft.t tag add sel "+s+" "+s+"+"+string len pat+"chars"); + tkcmd(t, ".ft.t mark set insert "+s+";.ft.t see insert; update"); + } +} + +showaddr(f: ref File, addr: string) +{ + if(addr=="") + return; + t := f.tk; + if(addr[0]=='#' || ('0'<=addr[0] && addr[0]<='9')){ + # UGLY! just do line and character numbers until we get a + # decent command/address interface set up. + if(!nullsel(t)) + tkcmd(t, NOSEL); + if(addr[0] == '#'){ + addr = addr[1:]; + tkcmd(t, ".ft.t mark set insert {1.0+"+addr+"char}; .ft.t see insert;update"); + }else{ + tkcmd(t, ".ft.t tag add sel "+addr+".0 {"+addr+".0 lineend+1char}"); + tkcmd(t, ".ft.t mark set insert "+addr+".0; .ft.t see insert;update"); + } + } +} + +error(cmdwin: ref Tk->Toplevel, err: string) +{ + if(cmdwin == nil) + return; + tkcmd(cmdwin, ".ft.t insert end '?"+err+"\n"); + if(!nullsel(cmdwin)) + tkcmd(cmdwin, NOSEL); + tkcmd(cmdwin, ".ft.t mark set insert end"); + tkcmd(cmdwin, ".ft.t mark set typingstart end; update"); +} |
