import f3.ui.*; import f3.ui.canvas.*; import f3.ui.filter.*; import java.lang.Math; import java.lang.System; // Undo/Redo machinery class Undoable { operation undo(); operation redo(); } class CompoundUndo extends Undoable { attribute list: Undoable*; } operation CompoundUndo.undo() { for (i in reverse list) { i.undo(); } } operation CompoundUndo.redo() { for (i in list) { i.redo(); } } class Modification extends Undoable { attribute obj: *; attribute attr: Attribute; attribute idx: Integer; } class Insert extends Modification { attribute newValue: *; } operation Insert.undo() { var a = bind obj[attr]; delete a[idx]; } operation Insert.redo() { var a = bind obj[attr]; if (sizeof a == 0) { insert newValue into a; } else { insert newValue before a[idx]; } } class Delete extends Modification { attribute oldValue: *; } operation Delete.undo() { var a = bind obj[attr]; insert oldValue before a[idx]; } operation Delete.redo() { var a = bind obj[attr]; delete a[idx]; } class Replace extends Insert, Delete { } operation Replace.undo() { var a = bind obj[attr]; a[idx] = oldValue; } operation Replace.redo() { var a = bind obj[attr]; a[idx] = newValue; } public class PenTool extends CompositeNode { attribute undoList: Undoable*; attribute undoPoint: Integer; operation undo(); operation redo(); operation canUndo(): Boolean; operation canRedo(): Boolean; attribute selected: Boolean; // The list of points that model the curve following this pattern // [cp, pt, cp, cp, pt, cp, cp, pt, ...] // The first point in the list is a dummy control point // The curve actually starts at index 1 (zero-based) attribute points: XY*; attribute curve: Path; // current cp/pt/cp group being dragged attribute dragGroup: Group?; // current point being initialized attribute pointBeingAdded: XY?; operation movePoint(points:XY*, dx:Number, dy:Number): Undoable; operation addPoint(points:XY*): Undoable; operation addEdit(d:Undoable*): Undoable; } attribute PenTool.undoPoint = -1; function PenTool.canUndo() { return undoPoint >= 0; } function PenTool.canRedo() { return undoPoint < sizeof undoList - 1; } operation PenTool.undo() { if (undoPoint >= 0) { undoList[undoPoint--].undo(); } else { println("can't undo: {undoPoint} of {sizeof undoList}"); } } operation PenTool.redo() { if (undoPoint < sizeof undoList-1) { undoList[++undoPoint].redo(); } else { println("can't redo: {undoPoint} of {sizeof undoList}"); } } operation PenTool.addEdit(d:Undoable*) { var undo = if sizeof d == 1 then d else CompoundUndo {list: d}; delete undoList[indexof . > undoPoint]; insert undo into undoList; undoPoint = sizeof undoList -1; } operation PenTool.movePoint(pts:XY*, dx:Number, dy:Number) { if (pts == null) { return; } var undo = CompoundUndo { list: foreach (p in pts) [Replace { obj: p attr: :XY.Attributes[Name=='x'] oldValue: p.x - dx newValue: p.x }, Replace { obj: p attr: :XY.Attributes[Name=='y'] oldValue: p.y - dy newValue: p.y }] }; return undo; } operation PenTool.addPoint(pts:XY*) { if (pts == null) { return; } var size = sizeof this.points; var attr = :PenTool.Attributes[Name=='points']; var result = CompoundUndo { list: foreach(point in pts) Insert { obj: this attr: attr idx: size + indexof point newValue: point } }; for (i in pts) { insert i into this.points; } return result; } attribute PenTool.curve = Path { var curveFill = new Color(0, 1, 0, 0.5) stroke: curveFill strokeWidth: 2 d: bind if sizeof points < 3 then [] else [MoveTo {x: bind points[1].x, y: bind points[1].y}, foreach (i in [4,7..