############################################################################ # # 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 #===<>=== 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 #===<>=== end of section maintained by vib