/* Nile1Ops.java * * Copyright (c) 2000, Ted Nelson and Tuomas Lukka * * You may use and distribute under the terms of either the GNU Lesser * General Public License, either version 2 of the license or, * at your choice, any later version. Alternatively, you may use and * distribute under the terms of the XPL. * * See the LICENSE.lgpl and LICENSE.xpl files for the specific terms of * the licenses. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the README * file for more details. * */ /* * Written by Tuomas Lukka */ package org.gzigzag.module; import org.gzigzag.*; import java.util.*; /** A library of routines for operating on Nile1 streams. * @see Nile1 * @deprecated Use Nile2Ops. */ public abstract class Nile1Ops { public static final String rcsid = "$Id: Nile1Ops.java,v 1.1 2002/10/07 12:37:34 tuukkah Exp $"; public static boolean dbg = true; static void p(String s) { if(dbg) System.out.println(s); } static void pa(String s) { System.out.println(s); } /** Split the given cell into two at the offset offset. * The original cell will remain the first. * Returns the cell that is after the split. * If offset is zero or greater than or equal to the number of characters * in c, nothing is done and either the current cell or the next cell is * returned. *

* The most special case involves doing this at the very end of the stream, * in which case an empty new cell is created and returned. * @param c The cursor to split at * @param always Whether to split even if the offset is at the beginning * or end of a cell. Used e.g. for inserting a paragraph. * @return Never null. */ static public ZZCell split(ZZCursor c, boolean always) { // XXX Adjust cursors!!! ZZCell c0 = c.get(); int offs = c.getOffs(); if(always) { ZZCell c1 = c0.N("d.nile"); String s = c0.getText(); c0.setText(s.substring(0, offs)); c1.setText(s.substring(offs)); return c1; } else { if(offs == 0) return c0; if(offs == c0.getText().length()) return c0.getOrNewCell("d.nile"); return split(c, true); } } /** Break a paragraph at the given position. * Returns the first cell of the (possibly empty) new paragraph. */ static public ZZCell breakParagraph(ZZCursor c) { ZZCell n = split(c, true); if(n.s("d.nile-struct") != null) throw new ZZError("split structure already!"); ZZCell str = n.N("d.nile-struct"); setStrLevel(str, 0); return n; } /** Change the structural level of a paragraph. * @param c Any cell in the paragraph - does not need to be the first. * @param level The new level. */ static public void setLevel(ZZCell c, int level) { ZZCell para = findStruct(c, -1, true); ZZCell str = para.s("d.nile-struct"); if(str == null) return; setStrLevel(str, level); } /** Get the level of a paragraph. * @param c Any cell in the paragraph - does not need to be the first. */ static public int getLevel(ZZCell c) { ZZCell para = findStruct(c, -1, true); ZZCell str = para.s("d.nile-struct"); if(str == null) return 6; return getStrLevel(str); } /** Insert the given string into the stream at cursor. */ static public void insert(ZZCursor c, String txt) { throw new ZZError("Not implemented"); } /** The level of structure the given structure cell is. * Note that the levels are a bit interesting: it returns * zero for paragraphs, 1 for H6, 2 for H5 and so on. * -1 is returned for cells that are not at a structural border. */ static int getStrLevel(ZZCell str) { String txt = str.getText(); if(txt.equals("MP")) // Middle of paragraph - special level. return -1; if(txt.equals("P")) return 0; if(txt.equals("H1")) return 6; if(txt.equals("H2")) return 5; if(txt.equals("H3")) return 4; if(txt.equals("H4")) return 3; if(txt.equals("H5")) return 2; if(txt.equals("H6")) return 1; throw new ZZError("Invalid nile-struct "+txt); } static String[] levels = new String[] { "P", "H6", "H5", "H4", "H3", "H2", "H1" }; /** Set the level of a structural cell. */ static void setStrLevel(ZZCell str, int level) { if(level < 0 || level > levels.length) throw new ZZError("Request to set invalid level! "+level); str.setText(levels[level]); } /** Find an angle in the rank, i.e.the next cell on a rank with * a connection into a certain direction. */ static ZZCell findAngle(ZZCell c, String dim, int dir, String adim, int adir, boolean includeThis) { LoopDetector ld = new LoopDetector(); if(includeThis && c.s(adim, adir) != null) return c; c = c.s(dim, dir); while(c != null) { if(c.s(adim, adir) != null) return c; ld.detect(c); c = c.s(dim, dir); } return null; } /** Find the next or previous cell that is a structural level element. */ static ZZCell findStruct(ZZCell c, int dir, boolean includeThis) { ZZCell ret = findAngle(c, "d.nile", dir, "d.nile-struct", 1, includeThis); if(ret == null && dir == -1) { if(!includeThis && c.s("d.nile", -1) == null) return null; return c.h("d.nile", -1); } return ret; } /** An editing mode, i.e.the definition of what are the breaks * that are being operated on in this mode. */ public interface Mode { /** Set the mode cursor, and possibly selection cursor, to the * given cursor. * This is called in response to e.g. mouse click, so the position * may be adjusted to the beginning of the previous unit. */ void setCursor(ZZCell view, ZZCursor c); /** Move the cursor nsteps units. */ void moveCursor(ZZCell view, int nsteps); /** Extend/shrink the selection by n units in given direction. */ void adjust(ZZCell view, int end, int nsteps); /** Hop the selection. */ void hop(ZZCell view, int nsteps); /** Delete the current selection, move cursor backwards or forwerds. */ // void delete(ZZCell view, int movedir); } public static class EdgeMode implements Mode { Nile1Text.Edger edger; EdgeMode(Nile1Text.Edger edger) { this.edger = edger; } public void setCursor(ZZCell view, ZZCursor c) { Stream.Iterator iter = giter(c); edger.edge(iter, true, -1, Nile1Text.START); curs(view).set(iter.get()); edger.edge(iter, false, 1, Nile1Text.END); endcurs(view).set(iter.get()); } // Move from end of area if forwards, from start if backwards. public void moveCursor(ZZCell view, int nsteps) { if(nsteps <= 0) { Stream.Iterator iter = giter(curs(view)); if(!edger.edge(iter, false, nsteps, Nile1Text.START)) return; curs(view).set(iter.get()); edger.edge(iter, false, 1, Nile1Text.END); endcurs(view).set(iter.get()); } else { Stream.Iterator iter = giter(endcurs(view)); if(!edger.edge(iter, false, nsteps, Nile1Text.END)) return; endcurs(view).set(iter.get()); edger.edge(iter, false, -1, Nile1Text.START); curs(view).set(iter.get()); } } public void adjust(ZZCell view, int end, int nsteps) { ZZCursor c1 = curs(view); ZZCursor c2 = endcurs(view); if(end < 0) { Stream.Iterator iter = Stream.getIterator(c1, "d.nile"); if(!edger.edge(iter, false, nsteps, Nile1Text.START)) return; ZZCursor curs = iter.get(); if(!isOrdered(curs, c2)) return; c1.set(curs); } else { Stream.Iterator iter = Stream.getIterator(c2, "d.nile"); if(!edger.edge(iter, false, nsteps, Nile1Text.END)) return; ZZCursor curs = iter.get(); if(!isOrdered(c1, curs)) return; c2.set(curs); } // XXX Check that they do not cross... } public void hop(ZZCell view, int nsteps) { p("Cut out the selection for hop"); ZZCell selec = cutSelection(view); if(selec == null) return; ZZCursor c1 = curs(view); p("Now, find place to paste at hopped location"); Stream.Iterator iter = Stream.getIterator(c1, "d.nile"); edger.edge(iter, false, nsteps, Nile1Text.START); c1.set(iter.get()); p("And paste"); paste(view, selec, true); } } static Mode wordmode = new EdgeMode(new Nile1Text.WordEdger()); static Mode sentencemode = new EdgeMode(new Nile1Text.SentenceEdger()); static public Mode getMode(ZZCell view) { // return wordmode; return sentencemode; } // XXX static ZZCell adjStart(ZZCell c, int adj) { if(adj > 0) { // XXX Easily screws up font change! ZZCursor c1 = new ZZCursorVirtual(c, 0); if(!Stream.move(c1, "d.nile", adj)) throw new ZZError("ARRRRGH!"); if(c1.get().equals(c)) { c.setText(c.getText().substring(adj)); } else { c = split(c1, false); c.disconnect("d.nile", -1); } } else if(adj < 0) { // XXX Spans String s = ""; while(adj++ < 0) s = s + " "; // XXX ZZCell n = c.N("d.nile", -1); n.setText(s); } return c; } static ZZCell adjEnd(ZZCell c, int adj) { if(adj > 0) { ZZCursor c1 = new ZZCursorVirtual(c, c.getText().length()); if(!Stream.move(c1, "d.nile", -adj)) throw new ZZError("ARRRRGH!"); // XXX c = c1.get(); c.setText(c.getText().substring(0, c1.getOffs())); c.disconnect("d.nile", 1); } else if(adj < 0) { c.setText(c.getText()+" "); } return c; } static void adjustSelection(Stream.Iterator start, Stream.Iterator end) { Nile1Text.wordEdge(start, true, 1, Nile1Text.START); Nile1Text.wordEdge(end, true, -1, Nile1Text.END); } static ZZCell cutSelection(ZZCell view) { ZZCursor curs1 = curs(view); p("Cut"); dump(curs1.get()); ZZCursor curs2 = endcurs(view); if(!isOrdered(curs1, curs2)) return null; Stream.Iterator iter1 = Stream.getIterator(curs1, "d.nile"); Stream.Iterator iter2 = Stream.getIterator(curs2, "d.nile"); int[] adj = new int[2]; Nile1Text.adjustCut(iter1, iter2, adj); ZZCursor c1 = iter1.get(); ZZCursor c2 = iter2.get(); ZZCell after = split(c2, false); p("\nAFTER SPLIT:"); dump(after); ZZCell first = split(c1, false); p("\nFIRST SPLIT:"); dump(first); ZZCell prev = first.s("d.nile", -1); ZZCell last = after.s("d.nile", -1); after.disconnect("d.nile", -1); first.disconnect("d.nile", -1); if(prev != null) prev.connect("d.nile", after); p("AFTER RECONNECT"); dump(prev); curs(view).set(after); curs(view).setOffs(0); // Now, if more characters need to be left out, do it. first = adjStart(first, adj[0]); last = adjEnd(last, adj[1]); if(adj[1] != 0) throw new ZZError("End adj not implemented"); p("AFTER CUTADJ"); dump(first); return first; } /** Paste the given stream into the given cursor. * The range and the text are adjusted for word-by-word * insertion. * @param view The viewcell that gives the location to * paste into. Used later to set the new range, * if desired. * @param what The first cell of the stream to paste in. * @param setRange Whether to set the selection of the given * view to the pasted range afterwards. */ static void paste(ZZCell view, ZZCell what, boolean setRange) { ZZCursor v = curs(view); p("PASTE"); dump(v.get()); p("PASTING WHAT"); dump(what); int[] adj = new int[2]; Stream.Iterator to = Stream.getIterator(v, "d.nile"); Stream.Iterator start = giter(new ZZCursorVirtual(what, 0)); ZZCell last = what.h("d.nile", 1); Stream.Iterator end = giter(new ZZCursorVirtual(last, last.getText().length())); Nile1Text.adjustPaste(to, start, end, adj); what = adjStart(start.get().get(), adj[0]); adjEnd(end.get().get(), adj[1]); p("WHAT ADJUSTED"); dump(what); ZZCell after = split(v, false); ZZCell before = after.s("d.nile", -1); after.disconnect("d.nile", -1); after.connect("d.nile", -1, what.h("d.nile", 1)); if(before != null) before.connect("d.nile", 1, what); if(setRange) { v.set(what); v.setOffs(0); start = giter(v); ZZCursor ev = endcurs(view); ev.set(after); ev.setOffs(0); end = giter(ev); adjustSelection(start, end); v.set(start.get()); ev.set(end.get()); } p("AFTER EVERYTHING"); dump(what); } // XXX Should use the tree for help... static public boolean isOrdered(ZZCursor cu1, ZZCursor cu2) { ZZCell c1 = cu1.get(); ZZCell c2 = cu2.get(); if(c1 == null || c2 == null) return false; if(c1.equals(c2)) return cu1.getOffs() <= cu2.getOffs(); for(; c1 != null && !c1.equals(c2); c1 = c1.s("d.nile")); if(c1 == null) return false; return (c1.equals(c2)); } // Shorthand... static public ZZCursorReal curs(ZZCell view) { return new ZZCursorReal(view); } static public Stream.Iterator giter(ZZCursor c) { return Stream.getIterator(c, "d.nile"); } static public ZZCursorReal endcurs(ZZCell view) { ZZCell end = view.getOrNewCell("d.nile-sel"); return new ZZCursorReal(end); } static public void dump(ZZCell start) { if(start == null) { pa("Nile stream Null start!"); return; } ZZCell init = start.h("d.nile", -1); pa("Nile stream! "+start); boolean first = true; for(ZZCell c = init; c != null; c=c.s("d.nile")) { pa((c.s("d.nile-struct") != null ? "***S*** " : " ") + " Cell "+c+" "+ (c.equals(start) ? " <----------------START" : "")); if(!first && c.equals(init)) { pa(" WAS A LOOOOOOOP!!!!!!!!!!"); throw new ZZFatalError("LOOPING NILE STREAM! BAD BAD TROUBLE!"); } first = false; } } }