############################################################################ # # vibfile.icn -- procedures for reading and writing specs to files # ############################################################################ # # This file is in the public domain. # ############################################################################ $include "vibdefn.icn" $include "vdefns.icn" ############################################################################ # constants and globals that are used only in this file: ############################################################################ $define PTITLE "#===<>===" $define HEADER "#===<>===\tmodify using vib; do not remove this marker line" $define TRAILER "#===<>===\tend of section maintained by vib" $define XHEADER "#===<>===" # for detecting old files $define XTRAILER "#===<>===" $define HMATCH 20 # number of chars that must match in header $define TMATCH 18 # number of chars that must match in trailer global USER_PREFIX, # user code preceding spec USER_SUFFIX # user code following spec ############################################################################ # new_session() creates a new, empty VIB session ############################################################################ procedure new_session() local fname if not flush_session() then fail SIZER := create_sizer() clear_screen() SESSION := newname() label_session() USER_PREFIX := USER_SUFFIX := &null return end ############################################################################ # load_session() reads in a saved VIB session file so that it can be # re-edited. ############################################################################ procedure load_session(s) local line, ifile, l, o ifile := open(s, "r") | fail clear_screen() USER_PREFIX := USER_SUFFIX := "" while line := read(ifile) do { if line ? match((HEADER | XHEADER)[1 +: HMATCH]) then break if line ? match("# Session Code:") then { Notice("Old file format; use uix to convert") USER_PREFIX := USER_SUFFIX := &null fail } USER_PREFIX ||:= line USER_PREFIX ||:= "\n" line := &null } DIRTY := &null if not (\line ? match((HEADER | XHEADER)[1 +: HMATCH])) then { Notice("No interface section found; creating one") USER_PREFIX ||:= "\n\n\n" DIRTY := 1 } while o := load_object(ifile) do case o.typ of { "Button" : init_object(load_button, button_obj(), o) "Text" : init_object(load_text_input, text_input_obj(), o) "Scrollbar" : init_object(load_slider, slider_obj(), o) "Slider" : init_object(load_slider, slider_obj(), o) "Line" : init_object(load_line, line_obj(), o) "Rect" : init_object(load_rect, rect_obj(), o) "Label" : init_object(load_label, label_obj(), o) "Message" : init_object(load_label, label_obj(), o) "Choice" : init_object(load_radio_button, radio_button_obj(), o) "Menu" : init_object(load_menu, menu_obj(), o) "Sizer" : init_object(load_sizer, sizer_obj(), o) "Check" : init_object(load_xbox, button_obj(), o) "List" : init_object(load_list, list_obj(), o) } while USER_SUFFIX ||:= read(ifile) do USER_SUFFIX ||:= "\n" close(ifile) return end ############################################################################# # init_object() initializes an object record and calls a proc to register it. ############################################################################# procedure init_object(proc, r, o) r.id := o.id r.proc := o.proc r.x := integer(o.x) r.y := o.y + CANVASY r.w := integer(o.w) r.h := integer(o.h) r.focus := 0 push(O_LIST, r) # must precede proc call proc(r, o) # call object-specific procedure update_bb(r) draw_object(r) end ############################################################################ # load_object() reads the next object from a saved session file. ############################################################################ procedure load_object(f) local c, s, l, r # find a line where the first nonblank character is a "[" repeat { while (c := reads(f, 1)) & upto(' \t\f', c) if \c == "[" then break s := (c || read(f)) | fail if s ? match((TRAILER | XTRAILER)[1 +: TMATCH]) then fail } # load the list of values l := load_strings(f) | fail # break them down into an ext_rec record r := ext_rec() s := get(l) | fail s ? { r.id := tab(upto(':')) | fail; move(1) r.typ := tab(upto(':')) | fail; move(1) r.sty := tab(upto(':')) | fail; move(1) r.num := tab(upto(':')) | fail; move(1) r.x := tab(upto(',')) | fail; move(1) r.y := tab(upto(',')) | fail; move(1) r.w := tab(upto(',')) | fail; move(1) r.h := tab(upto(':')) | fail; move(1) r.lbl := tab(0) } r.proc := get(l) | "" r.etc := get(l) | [] return r end ############################################################################ # load_strings() reads a list of strings after "[" has already been consumed. ############################################################################ procedure load_strings(f) local l, c, s, n l := [] n := 0 while c := reads(f, 1) do case c of { "]": return l # end of list ",": (n <:= *l) | put(l, &null) " ": next # whitespace: do nothing "\t": next "\r": next "\n": next "[": put(l, load_strings(f)) # nested list "\"": { # string constant s := "" while (c := reads(f, 1)) & not upto('"\n"', c) do if s == "\\" then s ||:= reads(f, 1) else s ||:= c put(l, s) } default: { # anything else: consume to separator s := c while (c := reads(f, 1)) & not upto(',] \t\r\n', c) do s ||:= c put(l, s) if c == "]" then return l } } fail # EOF hit end ############################################################################ # save_session() saves the current session to a file. If "pflag" is set, # the standard prefix is used (for prototype mode). ############################################################################ procedure save_session(s, pflag) local ofile sanity_check() | fail ofile := open(s, "w") if /ofile then { Notice("Could not open " || s, "(FILE WAS NOT SAVED)") fail } if /SIZER.dlog then save_app(ofile, pflag, s) else save_dlog(ofile, pflag, s) close(ofile) if /pflag then DIRTY := &null return end ############################################################################ # sanity_check() issues warnings if certain things don't look right. ############################################################################ procedure sanity_check() local messages, npush, ndflt, nrect, nlist, o messages := [] npush := ndflt := nrect := nlist := 0 every o := !O_LIST do { case type(o) of { "button_obj": { if /o.toggle then npush +:= 1 if \o.dflt then ndflt +:= 1 } "rect_obj": { nrect +:= 1 } "list_obj": { nlist +:= 1 } } } if \SIZER.dlog then { if ndflt > 1 then put(messages, "", "More than one button is marked as the default.", "Only one will be used.") if npush = 0 then put(messages, "", "There is no non-toggle button, so it will not", "be possible to dismiss the dialog box.") if nrect > 0 | nlist > 0 then put(messages, "", "There are one or more regions or text lists,", "but these do not function in dialog boxes.") } else { if ndflt > 0 then put(messages, "", "A button is marked as a dialog default,", "but this is not a dialog specification.") } if *messages = 0 then return push(messages, "Warning:") case TextDialog(messages, , , , ["Continue", "Cancel"], 2) of { "Continue": return "Cancel": fail } end ############################################################################ # save_app() saves the session as an application. If "pflag" is set, # the standard prefix is used (for prototype mode). ############################################################################ procedure save_app(ofile, pflag, filename) local id id := ("" ~== \SIZER.id) | "ui" if \pflag then write(ofile, PTITLE, "\n\n") if \pflag | /USER_PREFIX then { if /pflag then ipl_header(ofile, filename, "Program to", "vsetup") app_prologue(ofile, id, pflag) if /pflag then save_procs(ofile) } else writes(ofile, USER_PREFIX) write(ofile, HEADER) write(ofile, "procedure ", id, "_atts()") writes(ofile, " return [\"size=", SIZER.x + SIZER.w, ",", SIZER.y - CANVASY + SIZER.h, "\", \"bg=", VBackground, "\"") writes(ofile, ", \"label=", "" ~== SIZER.label, "\"") write(ofile, "]") write(ofile,"end") write(ofile) write(ofile, "procedure ", id, "(win, cbk)") write(ofile, "return vsetup(win, cbk,") output_spec(ofile, SIZER) output_all(ofile, O_LIST) write(ofile, " )") write(ofile, "end") write(ofile, TRAILER) if /pflag & \USER_SUFFIX then writes(ofile, USER_SUFFIX) return end ############################################################################ # save_procs() generates empty callback procedures in lexical order. ############################################################################ procedure save_procs(ofile) local o, t, l t := table() every o := !O_LIST do t["" ~== \o.proc] := o l := sort(t, 3) while get(l) do { o := get(l) writes(ofile, "procedure ", o.proc, "(vidget, ") if type(o) == "rect_obj" then write(ofile, "e, x, y)") else write(ofile, "value)") write(ofile, " return") write(ofile, "end") write(ofile) } return end ############################################################################ # save_dlog() saves the session as a dialog. If "pflag" is set, # the standard prefix is used (for prototype mode). ############################################################################ procedure save_dlog(ofile, pflag, filename) local id id := ("" ~== \SIZER.id) | "dl" if \pflag then dlog_prototype(ofile, id) else if /USER_PREFIX then { ipl_header(ofile, filename, "Procedure to", "dsetup") dlog_prologue(ofile) } else writes(ofile, USER_PREFIX) write(ofile, HEADER) write(ofile, "procedure ", id, "(win, deftbl)") write(ofile, "static dstate") write(ofile, "initial dstate := dsetup(win,") output_spec(ofile, SIZER) output_all(ofile, O_LIST) write(ofile, " )") write(ofile, "return dpopup(win, deftbl, dstate)") write(ofile, "end") write(ofile, TRAILER) if /pflag & \USER_SUFFIX then writes(ofile, USER_SUFFIX) return end ############################################################################ # output_all() outputs the members of an object list, sorted by ID, # but with rectangles last so that they can enclose other objects. ############################################################################ record output_rec(obj, key) procedure output_all(f, l) local t, e, k t := [] every e := !l do { if type(e) == "rect_obj" then k := "~" || right(e.w * e.h, 20) || e.id # rects last, by area else k := e.id put(t, output_rec(e, k)) } t := sortf(t, 2) every e := !t do output_spec(f, e.obj) return end ############################################################################ # output_spec() outputs the spec for an object. ############################################################################ procedure output_spec(f, o) local r # set standard fields r := ext_rec(o) r.id := o.id r.proc := o.proc r.x := o.x r.y := o.y - CANVASY r.w := o.w r.h := o.h # set type-dependent fields case type(o) of { "sizer_obj" : save_sizer(r, o) "button_obj" : save_button(r, o) "text_input_obj" : save_text_input(r, o) "line_obj" : save_line(r, o) "rect_obj" : save_rect(r, o) "slider_obj" : save_slider(r, o) "radio_button_obj" : save_radio_button(r, o) "label_obj" : save_label(r, o) "menu_obj" : save_menu(r, o) "list_obj" : save_list_obj(r, o) } writes(f, " [\"") writes(f, r.id, ":", r.typ, ":", r.sty, ":", r.num, ":") writes(f, r.x, ",", r.y, ",", r.w, ",", r.h, ":") writes(f, r.lbl, "\",") if /SIZER.dlog then writes(f, r.proc) if \r.etc then output_list(f, r.etc) write(f, "],") return end ############################################################################ # output_list() outputs a list in Icon form preceded by ",\n". ############################################################################ procedure output_list(f, a) local prefix, elem, n static indent initial indent := " " n := 0 indent ||:= " " writes(f, ",\n", indent, "[") prefix := "" while elem := get(a) do if type(elem) == "list" then { output_list(f, elem) prefix := ",\n" || indent n := 0 } else { writes(f, prefix, image(elem)) if (n +:= 1) % 5 = 0 then prefix := ",\n" || indent else prefix := "," } writes(f, "]") indent := indent[1:-3] end ############################################################################ # prototype() saves, compiles, and executes the current session. ############################################################################ procedure prototype() local f, line if f := open(PROTOFILE) then { line := read(f) close(f) if \line & not (line ? =PTITLE) then { Notice("Cannot create prototype file " || PROTOFILE || ":", "it already contains something that is not a VIB prototype") fail } } # write source file if save_session(PROTOFILE, 1) then { # translate and execute WAttrib("pointer=" || ("wait" | "watch")) system(BUILDPROTO) remove(PROTOFILE) WAttrib("pointer=" || ("left ptr" | "arrow")) system(EXECPROTO) } end ############################################################################ # newname() invents a name when creating a new file. ############################################################################ procedure newname() local s, i, f every i := seq() do { s := "app" || i || ".icn" # invent "app.icn" file name if f := open(s) then close(f) # can't use this name; already exists else return s # found a safe new name } end ############################################################################ # ipl_header() writes a standard IPL application header. ############################################################################ procedure ipl_header(ofile, filename, subject, links) local hline, date hline := repl("#", 76) &dateline ? { tab(upto(',') + 2) date := tab(upto(',') + 6) } write(ofile, hline) write(ofile, "#\n#\tFile: ", filename) write(ofile, "#\n#\tSubject: ", subject, " ...") write(ofile, "#\n#\tAuthor: ") write(ofile, "#\n#\tDate: ", date) write(ofile, "#\n", hline) write(ofile, "#\n#\n#\n", hline) write(ofile, "#\n# Requires:\n#\n", hline) write(ofile, "#\n# Links: ", links) write(ofile, "#\n", hline) write(ofile) return end ############################################################################ # app_prologue() writes a main program and other code for a new application. ############################################################################ procedure app_prologue(f, id, pflag) local vecho, e if \pflag then vecho := ", VEcho" else vecho := "" every write(f, ![ "# This vib interface specification is a working program that responds", "# to vidget events by printing messages. Use a text editor to replace", "# this skeletal program with your own code. Retain the vib section at", "# the end and use vib to make any changes to the interface.", "", "link vsetup", "", "procedure main(args)", " local vidgets, root, paused", "", " (WOpen ! " || id || "_atts()) | stop(\"can't open window\")", " vidgets := " || id || "(" || vecho || ")\t\t\t\t# set up vidgets", " root := vidgets[\"root\"]" ]) # generate a sample VSetItems call for every list object (prototyping only) if \pflag then every e := !O_LIST do if type(e) == "list_obj" then write(f, " VSetItems(vidgets[\"", e.id, "\"], [\"a\", \"b\", \"c\", \"d\"])"); every write(f, ![ "", " paused := 1\t\t\t\t\t# flag no work to do", " repeat {", " # handle any events that are available, or", " # wait for events if there is no other work to do", " while (*Pending() > 0) | \\paused do {", " ProcessEvent(root, QuitCheck)", " }", " # if is set null, code can be added here", " # to perform useful work between checks for input", " }", "end", ""]) end ############################################################################ # dlog_prologue() writes a header for a dialog file. ############################################################################ procedure dlog_prologue(f) every write(f, ![ "# Link this dialog specification with the rest of your program code.", "# Use vib to make any changes.", "", "link dsetup", ""]) end ############################################################################ # dlog_prototype() writes a header for a dialog prototyping run. ############################################################################ procedure dlog_prototype(f, id) write(f, PTITLE) write(f) write(f, "link dsetup, graphics") write(f) write(f, "procedure main(args)") write(f, " remove(", image(PROTOEXE), ")") write(f, " dproto(", id, ", , ", SIZER.x + SIZER.w, ", ", SIZER.y - CANVASY + SIZER.h, ", args)") write(f, "end") write(f) end