/*
VStream.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;
import java.util.*;
import java.awt.*;
/** Utility functions for vstreams.
* NOTE! USE THE VirtualCursor OBJECTS ONLY SHORT-TERM, put stuff
* in the structure
* for longer term.
* ANY call to VStream.* routines invalidates all VirtualCursor objects pointing
* to that vstream.
*
* TODO: d.markup, active references to parts of another vstream
* (via including complete vstream objects!)
*
* The vstream format is simple: the headcell on d.2 is a dummy cell
* that is never moved (so that it remains easy to refer to the vstream).
* Note that cells with the span length of 0 (i.e. end less than start)
* are explicitly ALLOWED.
*
* Important policy decision pending: what to do about non-span cells
* in vstreams?
*
* In addition to pointers, we'll probably need spans (of vstream) to complement
* them, for formatting etc.
* @deprecated This is a bit old now - see e.g. Nile and Stream.java.
*/
public class VStream {
public static final String rcsid = "$Id: VStream.java,v 1.28 2000/11/24 16:24:21 tjl Exp $";
public static final boolean dbg = false;
static final void p(String s) { if(dbg) System.out.println(s); }
static String dim = "d.2";
static public class Rend {
public Span[] spans;
public String[] strs;
public ZZCell[] cells;
public int[][] marks;
public ZZCell[][] markcells;
}
static public final void assertOffs(ZZCursor c) {
if(c.getOffs() == c.NO_OFFSET)
throw new ZZError("Need cursor offset");
}
static public ZZCursorVirtual add(ZZCursor vc, int o) {
assertOffs(vc);
ZZCell c = vc.get();
o += vc.getOffs();
// XXX Do we need to enforce spans?
while(o < 0) {
c = c.s(dim, -1);
if(c == null || c.getSpan() == null) return null;
o += (int)c.getSpan().length();
}
while(o > c.getSpan().length()) {
o -= (int)c.getSpan().length();
c = c.s(dim, 1);
if(c == null || c.getSpan() == null) return null;
}
return new ZZCursorVirtual(c, o);
}
static public String error(ZZCell c) {
ZZCell s = c.h(dim, -1);
if(s.getSpan() != null) return "First cell has span";
while((s=s.s(dim, 1))!=null) {
if(s.getSpan() == null)
return "No span in cell!";
}
return null;
}
static public void check(ZZCell c) {
if(!dbg) return;
String e = error(c);
System.out.println("VSTREAM DUMP");
for(ZZCell s = c.h(dim, -1); s != null;
s = s.s(dim, 1)) {
System.out.println("'"+s.getText()+"' from span: "+s.getSpan());
}
if(e == null) return;
throw new ZZError("VStream error in stream! '"+e+"' dumped");
}
// The first cell is the second of the rank.
static public ZZCursor start(ZZCell c) {
c = c.h(dim, -1).s(dim, 1);
if(c==null) return null;
return new ZZCursorVirtual(c, 0);
}
static public ZZCursor end(ZZCell c) {
c = c.h(dim, 1);
return new ZZCursorVirtual(c, c.getText().length());
}
static public void create(ZZCell c, String dim, int dir, Span s) {
ZZCell n = c.N(dim, dir).N("d.2", 1);
if(s != null)
n.setSpan(s);
}
/** Find the first instance of the given character
* that occurs at position p or after.
*/
static public ZZCursor findChar(char c, ZZCursor p) {
assertOffs(p);
String s = p.get().getText();
int io = s.indexOf(c, p.getOffs());
if(io != -1) return new ZZCursorVirtual(p.get(), io);
ZZCell cur = p.get();
while((cur = cur.s(dim, 1))!=null) {
io = cur.getText().indexOf(c);
if(io != -1) return new ZZCursorVirtual(cur, io);
}
return null;
}
/** Return the string between two positions, inclusive.
*/
static public String getStr(ZZCursor p1, ZZCursor p2) {
p("getStr: "+p1+" "+p2);
assertOffs(p1); assertOffs(p2);
StringBuffer s = new StringBuffer();
if(p1.get() == p2.get()) {
return p1.get().getText().substring(p1.getOffs(), p2.getOffs()+1);
}
s.append(p1.get().getText().substring(p1.getOffs()));
ZZCell c = p1.get();
while((c=c.s(dim,1)) != null && c != p2.get()) {
s.append(c.getText());
}
if(c!=null) {
String str = c.getText();
// Have to check whether one past end or end
if(str.length() > p2.getOffs())
s.append(str.substring(0, p2.getOffs()+1));
else
s.append(str);
}
return s.toString();
}
// XXX Join the next two methods
static public Span[] getSpans(ZZCursor p1, ZZCursor p2) {
Vector sp = new Vector();
p("getspans: "+p1+" "+p2+" "+p1.get()+" "+p2.get());
p("getspans1: "+p1.get().getText());
p("getspans2: "+p1.get().getSpan());
p("getspans3: "+p1.get().getSpan().subSpan(p1.getOffs()));
if(p1.get() == p2.get()) {
sp.addElement(
p1.get().getSpan().subSpan(p1.getOffs(), p2.getOffs()+1)
);
} else {
sp.addElement(
p1.get().getSpan().subSpan(p1.getOffs())
);
ZZCell c = p1.get();
while((c=c.s(dim,1)) != null && c != p2.get()) {
sp.addElement(c.getSpan());
}
if(c!=null)
sp.addElement(c.getSpan().subSpan(0, p2.getOffs()+1));
}
Span[] ret = new Span[sp.size()];
for(int i=0; i= o1) && (o2<0 || i <= o2)) {
if(res==null) res = new Vector();
res.addElement(new Integer(i-o1));
res.addElement(c);
}
} catch(NumberFormatException e) {
// Nothin'
}
}
if(res!=null) {
int[] r = new int[res.size()/2];
ZZCell[] rc = new ZZCell[res.size()];
for(int i=0 ;i= offs0) &&
(offs1 < -1 || of <= offs1)) {
rnk[i].setText(""+(of-offs0+noffs));
r.insert("d.cursor", 1, rnk[i]);
}
} catch(NumberFormatException e) {
// Nothin'
}
}
}
/** Split a VStream at position, return the cell just BEFORE the
* split. XXX MAYBE ALL VSTREAMS SHOULD HAVE EMPTY CELL AT BEGINNING AND
* END.
*/
static private ZZCell splitAt(ZZCursor p) {
// If offs == 0, need to do nothing.
if(p.getOffs() == 0) return p.get().s(dim, -1);
Span cur = p.get().getSpan();
if(p.getOffs() == cur.length()) return p.get();
// In the middle -> split and reposition cursors.
ZZCell n1 = p.get().N(dim, 1);
n1.setSpan(cur.subSpan(p.getOffs()));
p.get().setSpan(cur.subSpan(0, p.getOffs()));
ZZCell[] rnk = p.get().readRank("d.cursor", 1, false);
for(int i=0; i= p.getOffs()) {
int nof = of-p.getOffs();
rnk[i].setText(""+nof);
n1.insert("d.cursor", 1, rnk[i]);
}
} catch(NumberFormatException e) {
// Nothin'
}
}
return p.get();
}
/** Insert a cut mark at the given position.
*/
static public void cut(ZZCursor p) {
ZZCell prev = splitAt(p);
ZZCell n = prev.s(dim, 1);
n.disconnect("d.cut", 1);
n.connect("d.cut", 1, n);
}
/** Clear the cuts in the given vstream.
*/
static public void clearcuts(ZZCursor p) {
if ( p == null ) return ;
for(ZZCell c = p.get().h(dim, -1); c!=null;
c = c.s(dim, 1))
if(c.s("d.cut", 1) != null)
c.disconnect("d.cut", 1);
}
static ZZCell findPrevCut(ZZCursor p) {
ZZCell prev;
for(prev=p.get(); prev != null && prev.s("d.cut", 1) == null;
prev = prev.s(dim, -1));
return prev;
}
static ZZCell findNextCut(ZZCursor p) {
ZZCell next;
for(next=p.get().s(dim, 1); next != null &&
next.s("d.cut", 1) == null;
next = next.s(dim, 1));
return next;
}
/** Create a new vstream between the given cut marks.
* XXX This is difficult since Pos doesn't have the forwards-backwards
* info - Swing positions do. This should probably be added...
* Imagine cutting apart at single character edges...
*/
static public ZZCell separate(ZZCursor p) {
ZZCell prev = findPrevCut(p);
ZZCell next = findNextCut(p);
ZZCell tmp;
// based on their nullness, find the section to cut.
if(prev == null && next == null) return null; // nothing.
if(prev == null) {
next.disconnect("d.cut", 1);
next.disconnect(dim, -1);
tmp = p.get().h(dim, -1);
tmp.disconnect(dim, 1);
tmp.connect(dim, 1, next);
return p.get().h(dim, -1).N(dim, -1);
} else {
if(next != null){
//prev.s(dim, -1).connect(dim, 1, next);
next.disconnect(dim, -1);
tmp = prev.s(dim, -1);
tmp.disconnect(dim, 1);
tmp.connect(dim, 1, next);
}
prev.disconnect(dim, -1);
prev.disconnect("d.cut", 1);
return prev.N(dim, -1);
}
// return null;
}
/** Copy the part of the vstream between the nearest cut marks
* into a new vstream.
*/
static public ZZCell separateCopy(ZZCursor p) {
ZZCell nhead = p.get().N("d.tmp",1);
nhead.excise("d.tmp");
ZZCell prev = findPrevCut(p);
ZZCell next = findNextCut(p);
if(prev == null) prev = p.get().h(dim, -1).s(dim, 1);
ZZCell n = nhead;
for(ZZCell c = prev; c != null && c != next; c=c.s(dim, 1)) {
n = n.N(dim, 1);
n.setSpan(c.getSpan());
}
return nhead;
}
/** Insert the given span.
* Cursors at the insertion point will be moved to AFTER the
* insertion. XXX Here's something where more flexibility
* is needed: does a cursor point forwards or backwards?
*/
static public void insert(ZZCursor p, Span s) {
ZZCell prev = splitAt(p);
Span cur = prev.getSpan();
if(prev.getSpan() != null && prev.getSpan().isAppendable(s)) {
int len = (int)prev.getSpan().length();
moveCurs(prev, len, len, prev, (int)(len+s.length()));
prev.setSpan(prev.getSpan().append(s));
return;
}
ZZCell n = prev.N(dim, 1);
n.setSpan(s);
moveCurs(p.get(), p.getOffs(), p.getOffs(), n, 1);
}
/** Assimilate a whole vstream in insertion.
* Also places cuts at the beginning and the end of the result...
* XXX should it just return the POSes?
* XXX Delete the empty cells at ends...
*/
static public void assimilate(ZZCursor p, ZZCell vstream) {
ZZCell prev = splitAt(p);
ZZCell next = prev.s(dim, 1);
ZZCell firstof = vstream.h(dim, -1).s(dim, 1);
ZZCell tail = firstof.h(dim, 1);
prev.disconnect(dim, 1);
firstof.disconnect(dim, -1);
prev.connect(dim, 1, firstof);
firstof.connect("d.cut", 1, firstof);
if(next != null) {
next.disconnect("d.cut", 1);
next.connect("d.cut", 1, next);
next.connect(dim, -1, tail);
}
}
/** A convenience function: delete one character prior to p0.
*/
static public void backspace(ZZCursor p0) {
// while and not if: there might be null-length
// spans in there...
while(p0.getOffs() == 0) {
ZZCell c = p0.get().s(dim, -1);
if(c == null || c.getSpan() == null || c.getSpan().length() == 0)
return;
p0 = new ZZCursorVirtual(c, (int)c.getSpan().length());
}
// Split if needed
if(p0.getOffs() != p0.get().getSpan().length()) {
ZZCell prev = splitAt(p0);
p0.set(prev);
}
// Now we know p0.offs == p0.get().getSpan().length()...
int l = (int)(p0.get().getSpan().length());
if(l==1) {
// move cursors and delete cell
// XXX AUGH! Must move EITHER forwards or backwards,
// depending on which one is there...
ZZCell to = p0.get().s(dim, 1); int offs = 0;
if(to==null) {
to = p0.get().s(dim, -1);
offs = (int)to.getSpan().length();
}
// Ouch...
moveCurs(p0.get(), 0, 0, to, offs);
moveCurs(p0.get(), 1, 1, to, offs);
p0.get().delete();
return;
}
p0.get().setSpan(p0.get().getSpan().subSpan(0, l-1));
moveCurs(p0.get(), l, l, p0.get(), l-1);
}
// XXX Maybe too heavy - always creates new cells.
// untested.
static public void delete(ZZCursor p0, ZZCursor p1) {
ZZCell[] rnk;
if(p0.get() == p1.get()) {
ZZCell n = p0.get().N(dim, 1);
p0.get().setSpan(p0.get().getSpan().subSpan(0, p0.getOffs()));
n.setSpan(p0.get().getSpan().subSpan(p1.getOffs()+1));
// Then, redo cursors
rnk = p0.get().readRank("d.cursor", 1, false);
for(int i=0; i p1.getOffs()) {
int nof = of-p1.getOffs()-1;
rnk[i].setText(""+nof);
n.insert("d.cursor", 1, rnk[i]);
} else if(of >= p0.getOffs()) {
rnk[i].setText(""+p0.getOffs());
}
} catch(NumberFormatException e) {
// Nothin'
}
}
} else {
// 1. first cell's span & cursors
p0.get().setSpan(p0.get().getSpan().subSpan(0, p0.getOffs()));
rnk = p0.get().readRank("d.cursor", 1, false);
for(int i=0; i= p0.getOffs()) {
rnk[i].setText(""+p0.getOffs());
}
} catch(NumberFormatException e) {
// Nothin'
}
}
// Do the cells until we get to the second cell.
ZZCell c = p0.get();
while((c = c.s(dim, 1)) != null && c != p1.get()) {
rnk = c.readRank("d.cursor", 1, false);
for(int i=0; i p1.getOffs()) {
int nof = of-p1.getOffs()-1;
rnk[i].setText(""+nof);
} else {
p0.get().insert("d.cursor", 1, rnk[i]);
rnk[i].setText(""+p0.getOffs());
}
} catch(NumberFormatException e) {
// Nothin'
}
}
}
}
/** Given a blank cell, set that cell's span and add extra
* cells as necessary to copy the span between p0 and p1.
*/
static public void copyNew(ZZCursor p0, ZZCursor p1, ZZCell blank) {
if(p0.get()==p1.get()) {
blank.setSpan(p0.get().getSpan().subSpan(p0.getOffs(), p1.getOffs()+1));
} else {
// First cell
blank.setSpan(p0.get().getSpan().subSpan(p0.getOffs()));
// middle cells
ZZCell c = p0.get();
ZZCell n = blank;
while((c = c.s(dim, 1)) != null && c != p1.get()) {
n = n.N("dim", 1);
n.setSpan(c.getSpan());
}
// Last cell
if(c==null) return;
n = n.N("dim", 1);
n.setSpan(p1.get().getSpan().subSpan(0, p1.getOffs()+1));
}
}
}