diff options
Diffstat (limited to 'ipl/gprogs/penelope.icn')
-rw-r--r-- | ipl/gprogs/penelope.icn | 1256 |
1 files changed, 1256 insertions, 0 deletions
diff --git a/ipl/gprogs/penelope.icn b/ipl/gprogs/penelope.icn new file mode 100644 index 0000000..55861e3 --- /dev/null +++ b/ipl/gprogs/penelope.icn @@ -0,0 +1,1256 @@ +############################################################################ +# +# File: penelope.icn +# +# Subject: Program to edit graphic patterns +# +# Authors: Ralph E. Griswold and Gregg M. Townsend +# +# Date: May 25, 2001 +# +############################################################################ +# +# This file is in the public domain. +# +############################################################################ +# +# This application provides a variety of facilities for creating and +# editing graphic pattern specifications. For a complete description, +# see IPD234: +# http://www.cs.arizona.edu/icon/docs/ipd234.htm +# +############################################################################ +# +# Requires: Version 9 graphics with 32-column tiles +# +############################################################################ +# +# Links: sort, patxform, vdialog, vsetup, dialog, wopen, xcompat +# +############################################################################ + +link sort +link patxform +link vdialog +link vsetup +link dialog +link wopen +link xcompat + +$define MaxCell 24 # maximum size of grid cell + +$define GridSize (32 * 8) # size of area for edit grid +$define GridXoff (32 * 5) # x offset of grid area +$define GridYoff (32 * 2 + 6) # y offset of grid area + +$define PattXoff (32 * 14) # x offset of pattern area +$define PattYoff (32 * 2) # y offset of pattern area +$define PattWidth (32 * 8) # width of pattern area +$define PattHeight (32 * 8) # heigth of pattern area + +$define IconSize 16 # size of button icons + +$define XformXoff (16 * 2) # x offset of xform area +$define XformYoff (16 * 4) # y offset of xform area + +$define SymmetXoff (16 * 10) # x offset of symmetry area +$define SymmetYoff (16 * 23) # y offset of symmetry area + +$define InfoLength 40 # length of lines in info box + +global allxform # transform-all switch +global hbits # number of bits horizontally +global vbits # number of bits veritcally +global rows # row repesentation of tile +global old_pat # old pattern for undo +global cellsize # size of cell in edit grid +global pattgc # graphic context for pattern +global bordergc # border for tile/pattern +global viewgc # clipping area for viewing +global mode # pattern/tile display mode +global zoom # tile zoom factor +global loadname # name of loaded pattern file +global plist # pattern list +global pindex # index in pattern list +global list_touched # list modification switch +global tile_touched # tile modification switch +global blank_pat # 8x8 blank tile +global response # switch for save dialog +global sym_state # drawing state +global sym_image_current # current drawing images +global sym_image_next # next drawing images +global symmetries # general symmetry state + +global flip_right # icon for right flip +global flip_left # icon for left flip +global flip_vert # icon for vertical flip +global flip_horiz # icon for horizontal flip +global rotate_90 # icon for 90-degree rotation +global rotate_m90 # icon for -90-degree rotation +global rotate_180 # icon for 180-degree rotation +global ident # icon for identity +global hi_ident # highlighted icon for identity +global hi_left # highlighted icon for l-flip +global hi_right # highlighted icon for r-flip +global hi_vert # highlighted icon for v-flip +global hi_horiz # highlighted icon for h-flip +global hi_rot_90 # highlighted icon for 90-rot +global hi_rot_m90 # highlighted icon for -90 rot +global hi_rot_180 # highlighted icon for 180 rot + +global MaxPatt # maximum width for patterns + +record pattrec(tile, note) + +procedure main(args) + local vidgets, e, i, j, x, y, v, h, input, mdigits + +# Initial state + + mdigits := '-' ++ &digits + mode := 1 # initially pattern mode + zoom := 1 # initially 1:1 + symmetries := 0 # initially no symmetries + allxform := &null # initially not all xforms + + sym_state := [ # initially no symmetries + [1, -1, -1, -1], + [-1, -1, -1, -1] + ] + + blank_pat := "8,#0000000000000000" # 8x8 blank tile + + list_touched := &null # pristine state + tile_touched := &null + +# Conservative assumption that only X can handle tiles up to 32 wide + + MaxPatt := if &features == "X Windows" then 32 else 8 + +# Set up initial pattern list + + if loadname := args[1] then { + input := open(loadname) | stop("*** cannot open ", loadname) + if load_file(input) then old_pat := rows2pat(rows) + else stop("*** no patterns in ", loadname) + } + else { + loadname := "untitled.tle" + rows := pat2rows(blank_pat) + old_pat := rows2pat(rows) + plist := [pattrec(rows2pat(rows), "")] + pindex := 1 + } + +# Set up vidgets + + vidgets := ui(, vecho) + + WAttrib("label=" || loadname) + +# Set up graphic contexts + + pattgc := XBind(&window, "fillstyle=textured") # for patterns + bordergc := XBind(&window, "fg=red") # for border + viewgc := XBind(&window) # for tile view + Clip(viewgc, PattXoff, PattYoff, PattWidth, PattHeight) + Clip(bordergc, PattXoff - 1, PattYoff - 1, PattWidth + 2, PattHeight + 2) + +# Assign and draw the icons + + icons() + +# Initial and toggled editing images + + sym_image_next := [ + [ident, hi_rot_90, hi_rot_m90, hi_rot_180], + [hi_right, hi_left, hi_vert, hi_horiz] + ] + sym_image_current := [ + [hi_ident, rotate_90, rotate_m90, rotate_180], + [flip_right, flip_left, flip_vert, flip_horiz] + ] + +# Initial setup of grid and view areas + + setup() | stop("*** cannot set up pattern") + +# Enter event loop + + GetEvents(vidgets["root"], , shortcuts) + +end + +############################################################################ +# +# Callback procedures +# +############################################################################ + +# file menu + +procedure file_cb(vidget, value) + + case value[1] of { + "load @L" : load() + "save @S" : save() + "save as" : save_as() + "read @R" : read_tile() + "write @W" : write_tile() + "quit @Q" : quit() + } + + return + +end + +# editing grid + +procedure grid_cb(vidget, e) + local x, y, i, j + + if e === (&lpress | &rpress | &ldrag | &rdrag) then { + j := (&x - GridXoff) / cellsize + i := (&y - GridYoff) / cellsize + if j < 0 | j >= hbits | i < 0 | i >= vbits then return + if e === (&lpress | &ldrag) then setbit(i, j, "1") + else setbit(i, j, "0") + tile_touched := 1 + } + + return + +end + +# list menu + +procedure list_cb(vidget, value) + local i + + case value[1] of { + "clear" : { # should request confirmation + plist := [pattrec(blank_pat, "")] + } + "reverse" : { + every i := 1 to *plist / 2 do + plist[i] :=: plist[-i] + } + "sort" : { + refresh_tile() + plist := isort(plist, case value[2] of { + "by size": tile_size + "by bits": tile_bits + "by notes": tile_note + }) + } + } + + pindex := 1 + + rows := pat2rows(plist[1].tile) + old_pat := rows2pat(rows) + + list_touched := 1 + + return setup() + +end + +# Penelope logo + +procedure logo_cb(vidgets, event) + + if event === (&lpress | &mpress | &rpress) then + Notice("Penelope", "Version 1.1", + "Ralph E. Griswold and Gregg M. Townsend") + + return + +end + +# note menu + +procedure note_cb(vidget, value) + local result, note, i + + case value[1] of { + "edit @E" : edit_tile() + "find @F" : find_tile() + } + + return + +end + +# symmetry buttons + +procedure symmet_cb(vidget, e) + local col, row, symcount + + if e === (&lpress | &rpress | &mpress) then { + col := (&x - SymmetXoff) / IconSize + 1 + row := (&y - SymmetYoff) / IconSize + 1 + sym_state[row, col] *:= -1 + sym_image_current[row, col] :=: sym_image_next[row, col] + place(SymmetXoff, SymmetYoff, col - 1, row - 1, + sym_image_current[row, col]) + symcount := 0 + every symcount +:= !!sym_state + if symcount = -8 then + Notice("No drawing mode enabled; pattern cannot be edited") + else if (sym_state[1, 1] = 1) & (symcount = -6) then symmetries := 0 + else symmetries := 1 + + return + } + + fail + +end + +# tile menu + +procedure tile_cb(vidget, value) + local result + + case value[1] of { + "next @N" : next_tile() + "previous @P" : previous_tile() + "goto @G" : goto_tile() + "first" : { + refresh_tile() + pindex := 1 + rows := pat2rows(plist[pindex].tile) + tile_touched := 1 + return setup() + } + "last" : { + refresh_tile() + pindex := *plist + rows := pat2rows(plist[pindex].tile) + tile_touched := 1 + return setup() + } + "copy C" : copy_tile() + "revert" : { + rows := pat2rows(plist[pindex].tile) + return setup() + } + "delete D" : delete_tile() + "new" : { + case Dialog("New:", ["width", "height"], [*rows[1], *rows], 3, + ["Okay", "Cancel"]) of { + "Cancel" : fail + "Okay" : { + icheck(dialog_value) | fail + refresh_tile() + rows := list(dialog_value[2], repl("0", dialog_value[1])) + put(plist, pattrec(rows2pat(rows), "")) + pindex := *plist + tile_touched := 1 + return setup() + } + } + } + "info I" : tile_info() + } + + return + +end + +# view menu + +procedure view_cb(vidget, value) + static old_mode, old_zoom + + old_mode := mode + old_zoom := zoom + + case value[1] of { + "pattern" : mode := 1 + "tile" : mode := &null + "tile zoom" : { + mode := &null + case value[2] of { + "1:1" : zoom := 1 + "2:1" : zoom := 2 + "4:1" : zoom := 4 + "8:1" : zoom := 8 + } + } + } + + if (mode ~=== old_mode) | (zoom ~=== old_zoom) then { + DrawRectangle(bordergc, PattXoff - 1, PattYoff - 1, + PattWidth + 1, PattHeight + 1) + EraseArea(PattXoff - 1, PattYoff - 1, PattWidth + 1, PattHeight + 1) + return setup() + } + + return + +end + +# transformation buttons + +procedure xform_cb(vidget, e) + local col, row, save_pindex + + if e === (&lpress | &rpress | &mpress) then { + old_pat := rows2pat(rows) + col := (&x - XformXoff) / IconSize + row := (&y - XformYoff) / IconSize + + if &shift then { + refresh_tile() + save_pindex := pindex + every pindex := 1 to *plist do { + rows := pat2rows((plist[pindex]).tile) + rows := xform(col, row) + (plist[pindex]).tile := rows2pat(rows) + allxform := 1 # all being done + } + allxform := &null # one being done + list_touched := 1 + pindex := save_pindex + rows := pat2rows(plist[pindex].tile) + } + else rows := xform(col, row) | fail + + return setup() + + } + +end + +############################################################################ +# +# Support procedures +# +############################################################################ + +# clear bits on current tile + +procedure clear_tile() + + rows := list(vbits, repl("0", hbits)) + + grid() + + drawpat() + + return + +end + +# copy current tile + +procedure copy_tile() + + refresh_tile() + put(plist, pattrec(old_pat := rows2pat(rows), "")) + rows := pat2rows(old_pat) + pindex := *plist + + list_touched := 1 + + return setup() + +end + +# delete current tile + +procedure delete_tile() + # should ask confirmation + if *plist = 1 then plist := [pattrec(blank_pat, "")] + else { + plist := plist[1 : pindex] ||| plist[pindex + 1 : 0] + if pindex > *plist then pindex := *plist + } + + rows := pat2rows((plist[pindex]).tile) + + list_touched := 1 + + return setup() + +end + +# draw view area + +procedure drawpat() + + if \mode then { # draw pattern + DrawRectangle(bordergc, PattXoff - 1, PattYoff - 1, + PattWidth + 1, PattHeight + 1) + Pattern(pattgc, rows2pat(rows)) + FillRectangle(pattgc, PattXoff, PattYoff, PattWidth, PattHeight) + } + else { # draw tile + EraseArea(PattXoff - 1, PattYoff - 1, PattWidth + 2, PattHeight + 2) + DrawRectangle(bordergc, PattXoff - 1, PattYoff - 1, + (*rows[1] * zoom) + 1, (*rows * zoom) + 1) + DrawRows(viewgc, PattXoff, PattYoff, rows, zoom) + } + return + +end + +# edit annotation on current tile + +procedure edit_tile() + local result + + case Dialog("Edit:", "note", [plist[pindex].note], 80, + ["Okay", "Cancel"]) of { + "Cancel": fail + "Okay": { + plist[pindex].note := dialog_value[1] || " " + list_touched := 1 + } + } + + return + +end + +# find tile with annotation + +procedure find_tile() + local note, i + + case Dialog("Find:", "note", "", 80, ["Okay", "Cancel"]) of { + "Cancel": fail + "Okay": { + note := dialog_value[1] || " " + every i := ((pindex + 1 to *plist) | (1 to *pindex)) do + plist[i].note ? { + if find(note) then { + pindex := i + rows := pat2rows(plist[pindex].tile) + return setup() + } + } + } + } + + Notice("Not found") + + fail + +end + +# go to specified tile + +procedure goto_tile() + local i + + case Dialog("Go to:","#", 1, 5, ["Okay", "Cancel"]) of { + "Cancel": fail + "Okay": i := integer(dialog_value[1]) | { + Notice("Invalid specification") + fail + } + } + refresh_tile() + if i <= 0 then i +:= *plist + 1 + if i <= i <= *plist + 1 then { + pindex := i + old_pat := rows2pat(rows) + rows := pat2rows(plist[pindex].tile) + return setup() + } + else { + Notice("Index out of bounds") + fail + } + +end + +# draw editing grid + +procedure grid() + local x, y + + EraseArea(GridXoff, GridYoff, GridSize - 15, GridSize - 15) + + every x := 0 to hbits * cellsize by cellsize do + DrawLine(GridXoff + x, GridYoff, GridXoff + x, + GridYoff + vbits * cellsize) + every y := 0 to vbits * cellsize by cellsize do + DrawLine(GridXoff, GridYoff + y, GridXoff + hbits * cellsize, + y + GridYoff) + + return + +end + +# check for valid integers + +procedure icheck(values) + local i + + every i := !values do + if not(integer(i)) | (i < 0) then { + Notice("Invalid value") + fail + } + + return + +end + +# assign and draw icons + +procedure icons() + local shift_up, shift_left, shift_right, shift_down, pixmap + local clear, invert, scramble, trim, enlarge, resize, crop + + pixmap := XBind(, , "width=32", "height=32", "fillstyle=masked") + + Pattern(pixmap, "32,#7fffffff421f843f421f843f421f843f421f843f7fffff_ + ff421084214210842142108421421084217fffffff4210fc21_ + 4210fc214210fc214210fc217fffffff421087e1421087e142_ + 1087e1421087e17fffffff7e10fc217e10fc217e10fc217e10_ + fc217fffffff7e10843f7e10843f7e10843f7e10843f7fffff_ + ff00000000") # Penelope logo + + FillRectangle(pixmap, 0, 0, 32, 32) + + CopyArea(pixmap, &window, 0, 0, 32, 32, 26, 373) + + Uncouple(pixmap) + + shift_up := "16,#3ffe6003408141c143e140814081408140814081408140_ + 81408160033ffe0000" + shift_left := "16,#3ffe6003400140014001401140195ffd40194011400140_ + 01400160033ffe0000" + shift_right := "16,#3ffe600340014001400144014c015ffd4c014401400140_ + 01400160033ffe0000" + shift_down := "16,#3ffe60034081408140814081408140814081408143e141_ + c1408160033ffe0000" + flip_left := "16,#3ffe600340014079403940394049408149014e014e014f_ + 01400160033ffe0000" + flip_right := "16,#3ffe600340014f014e014e014901408140494039403940_ + 79400160033ffe0000" + flip_vert := "16,#3ffe6003408141c143e14081408140814081408143e141_ + c1408160033ffe0000" + flip_horiz := "16,#3ffe600340014001400144114c195ffd4c194411400140_ + 01400160033ffe0000" + rotate_90 := "16,#3ffe6003400140f141014201420142014f814701420140_ + 01400160033ffe0000" + rotate_m90 := "16,#3ffe600340014781404140214021402140f94071402140_ + 01400160033ffe0000" + rotate_180 := "16,#3ffe6003400141c140214011401140114111432147c143_ + 01410160033ffe0000" + clear := "16,#3ffe600340014001400140014001400140014001400140_ + 01400160033ffe0000" + invert := "16,#3ffe60ff40ff40ff40ff40ff40ff7fff7f817f817f817f_ + 817f817f833ffe0000" + scramble := "16,#3ffe60034c014c0d418d41814001403159b1598140194c_ + 194c0160033ffe0000" + trim := "16,#3ffe60134011407d40394011400140fd48854c857e854c_ + 8548fd60033ffe0000" + enlarge := "16,#3ffe6083418143fd418148815c017efd48854885488548_ + 8548fd60033ffe0000" + resize := "16,#3ffe6093419943fd419948915c017efd488548857e855c_ + 8548fd60033ffe0000" + crop := "16,#3ffe60034011401147fd441144114411441144115ff144_ + 01440160033ffe0000" + + ident := "16,#3ffe6003400140014001400141c141c141c14001400140_ + 01400160033ffe0000" + + hi_ident := "16,#00001ffc3ffe3ffe3ffe3ffe3e3e3e3e3e3e3ffe3ffe3f_ + fe3ffe1ffc00000000" + hi_rot_90 := "16,#00001ffc3ffe3f0e3efe3dfe3dfe3dfe307e38fe3dfe3f_ + fe3ffe1ffc00000000" + hi_rot_m90 := "16,#00001ffc3ffe387e3fbe3fde3fde3fde3f063f8e3fde3f_ + fe3ffe1ffc00000000" + hi_rot_180 := "16,#00001ffc3ffe3e3e3fde3fee3fee3fee3eee3cde383e3c_ + fe3efe1ffc00000000" + hi_right := "16,#00001ffc3ffe30fe31fe31fe36fe3f7e3fb63fc63fc63f_ + 863ffe1ffc00000000" + hi_left := "16,#00001ffc3ffe3f863fc63fc63fb63f7e36fe31fe31fe30_ + fe3ffe1ffc00000000" + hi_vert := "16,#00001ffc3f7e3e3e3c1e3f7e3f7e3f7e3f7e3f7e3c1e3e_ + 3e3f7e1ffc00000000" + hi_horiz := "16,#00001ffc3ffe3ffe3ffe3bee33e6200233e63bee3ffe3f_ + fe3ffe1ffc00000000" + +# now place the images + + place(XformXoff, XformYoff, 1, 0, shift_up) + place(XformXoff, XformYoff, 0, 1, shift_left) + place(XformXoff, XformYoff, 2, 1, shift_right) + place(XformXoff, XformYoff, 1, 2, shift_down) + place(XformXoff, XformYoff, 0, 4, flip_right) + place(XformXoff, XformYoff, 0, 5, flip_left) + place(XformXoff, XformYoff, 1, 4, flip_vert) + place(XformXoff, XformYoff, 1, 5, flip_horiz) + place(XformXoff, XformYoff, 0, 7, rotate_90) + place(XformXoff, XformYoff, 0, 8, rotate_m90) + place(XformXoff, XformYoff, 1, 7, rotate_180) + place(XformXoff, XformYoff, 0, 10, clear) + place(XformXoff, XformYoff, 1, 10, invert) + place(XformXoff, XformYoff, 2, 10, scramble) + place(XformXoff, XformYoff, 0, 12, trim) + place(XformXoff, XformYoff, 1, 12, enlarge) + place(XformXoff, XformYoff, 2, 12, resize) + place(XformXoff, XformYoff, 0, 14, crop) + + place(SymmetXoff, SymmetYoff, 0, 0, hi_ident) + place(SymmetXoff, SymmetYoff, 1, 0, rotate_90) + place(SymmetXoff, SymmetYoff, 2, 0, rotate_m90) + place(SymmetXoff, SymmetYoff, 3, 0, rotate_180) + place(SymmetXoff, SymmetYoff, 0, 1, flip_right) + place(SymmetXoff, SymmetYoff, 1, 1, flip_left) + place(SymmetXoff, SymmetYoff, 2, 1, flip_vert) + place(SymmetXoff, SymmetYoff, 3, 1, flip_horiz) + + return + +end + +# invert bits on current pattern + +procedure invert() + + rows := pinvert(rows) + + return + +end + +# load tile list + +procedure load() + local input + + refresh_tile() + + if \list_touched then { # check to see if list should be saved + case SaveDialog(, loadname) of { + "Yes": { + loadname := dialog_value + save() + } + } + } + + repeat { + case OpenDialog("Load: ") of { + "Okay": { + loadname := dialog_value + if input := open(loadname) then break + else { + Notice("Can't open " || loadname) + next + } + } + "Cancel": fail + } + } + load_file(input) | { + Notice("No patterns in file") + fail + } + WAttrib("label=" || loadname) + list_touched := &null + + return setup() + +end + +# load from file + +procedure load_file(input) + local line + + plist := [] + while put(plist, read_pattern(input)) + close(input) + pindex := 1 + rows := pat2rows(plist[pindex].tile) | fail + + return + +end + +# go to next tile + +procedure next_tile() + + refresh_tile() + rows := pat2rows(plist[pindex + 1].tile) | { + Notice("No next tile") + fail + } + + pindex +:= 1 + + return setup() + +end + +# place icon + +procedure place(xoff, yoff, col, row, pattern) + + Pattern(pattgc, pattern) + FillRectangle(pattgc, xoff + col * IconSize, + yoff + row * IconSize, IconSize, IconSize) + + return + +end + +# go to previous tile + +procedure previous_tile() + + rows := pat2rows(plist[pindex - 1].tile) | { + Notice("No previous tile") + fail + } + + refresh_tile() + pindex -:= 1 + + return setup() + +end + +# terminate session + +procedure quit() + local result + + refresh_tile() + + if \list_touched then { + case SaveDialog() of { + "Cancel": fail + "No": exit() + "Yes": { + loadname := dialog_value + save() + } + } + } + + exit() + +end + +# read pattern specification + +procedure read_pattern(file) + local line + + line := readpattline(file) | fail + + return pattrec(legaltile(getpatt(line)), getpattnote(line)) + +end + +# read and add tile to tile list + +procedure read_tile() + + refresh_tile() + put(plist, read_pattern(&input)) | fail + pindex := *plist + rows := pat2rows((plist[pindex]).tile) + + list_touched := 1 + + return setup() + +end + +# refresh tile in list + +procedure refresh_tile() + + if \tile_touched := &null then { + plist[pindex].tile := rows2pat(rows) + list_touched := 1 + } + + return + +end + +# save tile list + +procedure save() # should ask if file is to be saved + local output + + refresh_tile() + + if \list_touched then { + output := open(loadname, "w") | { + Notice("Can't open " || loadname) + fail + } + every write_pattern(output, !plist) + close(output) + list_touched := &null + } + + return + +end + +# save tile list in new file + +procedure save_as() + local output + + refresh_tile() + + repeat { + case OpenDialog("Save as:") of { + "Okay": { + if output := open(dialog_value, "w") then break else + Notice("Can't open " || dialog_value) + } + "Cancel": fail + } + } + every write_pattern(output, !plist) + close(output) + + loadname := dialog_value + WAttrib("label=" || loadname) + + list_touched := &null + + return + +end + +# scramble bits of current tile + +procedure bscramble() + + rows := pscramble(rows, "b") + + return + +end + +# set bits of tile + +procedure setbit(i, j, c) + local x, y, xu, yu, xv, yv, xt, yt, action + + if (symmetries = 0) & (rows[i + 1, j + 1] == c) then return # optimization + + x := GridXoff + j * cellsize + 1 # the selected cell itself + y := GridYoff + i * cellsize + 1 + xt := GridXoff + i * cellsize + 1 + yt := GridYoff + j * cellsize + 1 + + i +:= 1 # for computational convenience + j +:= 1 + + xu := GridXoff + (hbits - j) * cellsize + 1 # opposite cells + yu := GridYoff + (vbits - i) * cellsize + 1 + xv := GridXoff + (hbits - i) * cellsize + 1 + yv := GridYoff + (vbits - j) * cellsize + 1 + + action := if c = 1 then FillRectangle else EraseArea + + if sym_state[1, 1] = 1 then { # cell itself + rows[i, j] := c + action(x, y, cellsize - 1, cellsize - 1) + } + if sym_state[1, 2] = 1 then { # 90 degrees + if rows[j, -i] := c then # may be out of bounds + action(xv, yt, cellsize - 1, cellsize - 1) + } + if sym_state[1, 3] = 1 then { # -90 degrees + if rows[-j, i] := c then # may be out of bounds + action(xt, yv, cellsize - 1, cellsize - 1) + } + if sym_state[1, 4] = 1 then { # 180 degrees + rows[-i, -j] := c + action(xu, yu, cellsize - 1, cellsize - 1) + } + if sym_state[2, 1] = 1 then { # left diagonal + if rows[j, i] := c then # may be out of bounds + action(xt, yt, cellsize - 1, cellsize - 1) + } + if sym_state[2, 2] = 1 then { # right diagonal + if rows[-j, -i] := c then # may be out of bounds + action(xv, yv, cellsize - 1, cellsize - 1) + } + if sym_state[2, 3] = 1 then { # vertical + rows[-i, j] := c + action(x, yu, cellsize - 1, cellsize - 1) + } + if sym_state[2, 4] = 1 then { # horizontal + rows[i, -j] := c + action(xu, y, cellsize - 1, cellsize - 1) + } + + drawpat() + + return + +end + +# set up editing grid and view area + +procedure setup() + local i, j + + hbits := *rows[1] + vbits := *rows + + if (hbits | vbits) > 80 then { # based on cell size >= 3 + Notice("Dimensions too large") + fail + } + if hbits > MaxPatt then mode := &null # too large for pattern + + cellsize := MaxCell # cell size on window + cellsize >:= GridSize / (vbits + 4) + cellsize >:= GridSize / (hbits + 4) + + grid() + + every i := 1 to hbits do + every j := 1 to vbits do + if rows[j, i] == "1" then + FillRectangle(GridXoff + (i - 1) * cellsize, + GridYoff + (j - 1) * cellsize, cellsize, cellsize) + + drawpat() + + return + +end + +# keyboard shortcuts + +procedure shortcuts(e) + + if &meta then case map(e) of { + "c" : copy_tile() + "d" : delete_tile() + "e" : edit_tile() + "f" : find_tile() + "g" : goto_tile() + "i" : tile_info() + "l" : load() + "n" : next_tile() + "p" : previous_tile() + "q" : return quit() + "r" : read_tile() + "s" : save() + "u" : undo_xform() + "w" : write_tile() + } + + return + +end + +# return number of bits set in tile for sorting + +procedure tile_bits(x) + + return tilebits(pat2rows(x.tile)) + +end + +# show information about tile + +procedure tile_info() + local line1, line2, line3, line4, pattern, bits, density + + pattern := rows2pat(rows) + bits := tilebits(rows) + density := left(bits / real(*rows[1] * *rows), 6) + + line1 := left(loadname ||" " || pindex || " of " || *plist, InfoLength) + line2 := left(*rows[1] || "x" || *rows || " b=" || bits || " d=" || + density, InfoLength) + line3 := if *pattern > InfoLength then pattern[1+:(InfoLength - 3)] || + "..." else left(pattern, InfoLength) + line4 := left(plist[pindex].note, InfoLength) + + Notice(line1, line2, line3, line4) + + return + +end + +# return annotation of tile for sorting + +procedure tile_note(x) + + return x.note + +end + +# return tile size for sorting + +procedure tile_size(x) + local dims + + dims := tiledim(x.tile) + + return dims.w * dims.h + +end + +# undo transformation + +procedure undo_xform() + + rows := pat2rows(old_pat) + + return setup() + +end + +# write pattern + +procedure write_pattern(file, pattern) + + if *pattern.note = 0 then write(file, pattern.tile) + else write(file, pattern.tile, "\t# ", pattern.note) + + return + +end + +# write tile + +procedure write_tile() + + write_pattern(&output, pattrec(rows2pat(rows), (plist[pindex]).note)) + + return + +end + +# handle transformation + +procedure xform(col, row) + local result + static params + + tile_touched := 1 + + return case col of { + 0: case row of { + 1: pshift(rows, -1, "h") + 4: pflip(rows, "r") + 5: pflip(rows, "l") + 7: protate(rows, 90) + 8: protate(rows, -90) + 10: list(vbits, repl("0", hbits)) + 12: ptrim(rows) + 14: { + if /allxform then { + case Dialog("Crop:", ["left", "right", "top", "bottom"], + 0, 3, ["Okay", "Cancel"]) of { + "Cancel": fail + "Okay": { + icheck(dialog_value) | fail + result := copy(params := dialog_value) + push(result, rows) + pcrop ! result + } + } + } + } + default: fail + } + 1: case row of { + 0: pshift(rows, -1, "v") + 2: pshift(rows, 1, "v") + 4: pflip(rows, "v") + 5: pflip(rows, "h") + 7: protate(rows, 180) + 10: pinvert(rows) + 12: { + if /allxform then { + case Dialog("Enlarge:", ["left", "right", "top", "bottom"], + 0, 3, ["Okay", "Cancel"]) of { + "Cancel": fail + "Okay": { + icheck(dialog_value) | fail + result := copy(params := dialog_value) + push(result, rows) + pborder ! result + } + } + } + } + default: fail + } + 2: case row of { + 1: pshift(rows, 1, "h") + 10: pscramble(rows, "b") + 12: { + if /allxform then { + case Dialog("Center:", ["width", "height"], [*rows[1], *rows], + 3, ["Okay", "Cancel"]) of { + "Cancel": fail + "Okay": { + icheck(dialog_value) | fail + result := copy(params := dialog_value) + push(result, rows) + pcenter ! result + } + } + } + } + default: fail + } + default: fail + } + +end + +#===<<vib:begin>>=== modify using vib; do not remove this marker line +procedure ui_atts() + return ["size=730,420", "bg=pale gray", "label=Penelope"] +end + +procedure ui(win, cbk) +return vsetup(win, cbk, + [":Sizer:::0,0,730,420:Penelope",], + ["file:Menu:pull::0,1,36,21:file",file_cb, + ["load @L","save @S","save as","read @R","write @W", + "quit @Q"]], + ["line1:Line:::1,22,729,22:",], + ["line2:Line:::133,32,133,420:",], + ["line3:Line:::427,22,427,419:",], + ["list:Menu:pull::73,1,36,21:list",list_cb, + ["clear","reverse","delete range","sort", + ["by size","by bits","by notes"]]], + ["note:Menu:pull::145,1,36,21:note",note_cb, + ["edit @E","find @F"]], + ["symmetries:Label:::156,338,70,13:symmetries",], + ["tile:Menu:pull::37,1,36,21:tile",tile_cb, + ["next @N","previous @P","first","last","goto @G", + "delete @D","revert","copy @C","new","info @I"]], + ["transformations:Label:::8,32,105,13:transformations",], + ["view:Menu:pull::110,1,36,21:view",view_cb, + ["pattern","tile","tile zoom", + ["1:1","2:1","4:1","8:1"]]], + ["logo:Rect:invisible::26,373,32,32:",logo_cb], + ["symmet:Rect:grooved::155,363,74,42:",symmet_cb], + ["xform:Rect:grooved::26,57,58,256:",xform_cb], + ["grid:Rect:grooved::153,64,251,256:",grid_cb], + ) +end +#===<<vib:end>>=== end of section maintained by vib |