Files
gzz-mirror/Java/media/VStream.java
2026-09-14 20:19:29 -04:00

656 lines
19 KiB
Java

/*
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.
* <p>
* TODO: d.markup, active references to parts of another vstream
* (via including complete vstream objects!)
* <p>
* 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.
* <p>
* Important policy decision pending: what to do about non-span cells
* in vstreams?
* <p>
* 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<ret.length; i++) ret[i] = (Span)sp.elementAt(i);
return ret;
}
static public String[] getStrings(ZZCursor p1, ZZCursor p2) {
Vector sp = new Vector();
if(p1.get() == p2.get()) {
p("Gs==: "+p1.getOffs()+" "+p2.getOffs());
sp.addElement(
p1.get().getText().substring(p1.getOffs(), p2.getOffs()+1)
);
} else {
sp.addElement(
p1.get().getText().substring(p1.getOffs())
);
ZZCell c = p1.get();
while((c=c.s(dim,1)) != null && c != p2.get()) {
sp.addElement(c.getText());
}
if(c!=null)
sp.addElement(c.getText().substring(0, p2.getOffs()+1));
}
String[] ret = new String[sp.size()];
for(int i=0; i<ret.length; i++) ret[i] = (String)sp.elementAt(i);
return ret;
}
static public void findMarks(ZZCell c, int o1, int o2,
Vector mkindto, Vector mkcellto) {
Vector res = null;
if(o1 <= 0 && c.s("d.cut", 1) != null) {
res = new Vector();
// A cut == null cell
res.addElement(new Integer(0));
res.addElement(null);
}
while((c = c.s("d.cursor", 1))!=null) {
try {
int i = Integer.parseInt(c.getText());
if((o1 < 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<res.size(); i+=2) {
r[i/2] = ((Integer)res.elementAt(i)).intValue();
rc[i/2] = (ZZCell)res.elementAt(i+1);
}
mkindto.addElement(r);
mkcellto.addElement(rc);
} else {
mkindto.addElement(null);
mkcellto.addElement(null);
}
}
static public Rend getRend(ZZCursor p1, ZZCursor p2) {
Vector sp = new Vector();
Vector cells = new Vector();
Vector strs = new Vector();
Vector mk = new Vector();
Vector mkc = new Vector();
Rend r = new Rend();
if(p1.get() == p2.get()) {
r.spans = new Span[] {p1.get().getSpan().subSpan(p1.getOffs(), p2.getOffs()+1)};
r.strs = new String[] {
p1.get().getText().substring(p1.getOffs(), p2.getOffs()+1)
};
findMarks(p1.get(), p1.getOffs(), p2.getOffs()+1, mk, mkc);
r.marks = new int[][] { (int[])mk.elementAt(0) };
r.markcells = new ZZCell[][] { (ZZCell[])mkc.elementAt(0) };
r.cells = new ZZCell[] { p1.get() };
return r;
}
sp.addElement(p1.get().getSpan().subSpan(p1.getOffs()));
strs.addElement(p1.get().getText().substring(p1.getOffs()));
cells.addElement(p1.get());
findMarks(p1.get(), p1.getOffs(), -1, mk, mkc);
ZZCell c = p1.get();
while((c=c.s(dim, 1)) != null && c != p2.get()) {
sp.addElement(c.getSpan());
strs.addElement(c.getText());
cells.addElement(c);
findMarks(c, 0, -1, mk, mkc);
}
if(c!=null) {
int n = p2.getOffs() + 1;
p("RENDEND: "+n+" '"+c.getText()+"' ");
if(c.getText().length() < n) n = c.getText().length();
sp.addElement(c.getSpan().subSpan(0, n));
strs.addElement(c.getText().substring(0, n));
findMarks(c, 0, p2.getOffs(), mk, mkc);
cells.addElement(c);
}
r.spans = new Span[sp.size()];
r.strs = new String[sp.size()];
r.marks = new int[sp.size()][];
r.markcells = new ZZCell[sp.size()][];
r.cells = new ZZCell[sp.size()];
for(int i=0; i<r.spans.length; i++) {
r.spans[i] = (Span)sp.elementAt(i);
r.strs[i] = (String)strs.elementAt(i);
r.marks[i] = (int[])mk.elementAt(i);
r.markcells[i] = (ZZCell[])mkc.elementAt(i);
r.cells[i] = (ZZCell)cells.elementAt(i);
}
return r;
}
// XXX The following two are ridiculous now that Pos
// has been changed to Cursor.
static public ZZCursor posFromCursor(ZZCell curscell) {
// XXX Go to cursor-cargo, then get offset, then go to cell.
int of = 0;
curscell = curscell.h("d.cursor-cargo", -1);
try {
of = Integer.parseInt(curscell.getText());
} catch(NumberFormatException e) {
// Nothin'
}
curscell = curscell.h("d.cursor", -1);
if(curscell.getSpan() == null) return null;
return new ZZCursorVirtual(curscell, of);
}
static public void setCursorFromPos(ZZCell c, ZZCursor p) {
ZZCursorReal.set(c, p.get());
c.h("d.cursor-cargo", -1).setText(""+p.getOffs());
}
static public void insert(ZZCursor p, String s) {
p("Insert: "+p+" "+p.get());
StringScroll sc = p.get().getSpace().getStringScroll();
long offs = sc.append(s);
p("Insertsc: "+sc+" "+offs);
Span sp = Span.create(
Address.scrollOffs(sc, offs),
Address.scrollOffs(sc, offs + s.length() - 1)
);
insert(p, sp);
}
static public void moveCurs(ZZCell c0, int offs0, int offs1, ZZCell r,
int noffs) {
ZZCell[] rnk = c0.readRank("d.cursor", 1, false);
for(int i=0; i<rnk.length; i++) {
try {
int of = Integer.parseInt(rnk[i].getText());
if((offs0 < -1 || of >= 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<rnk.length; i++) {
try {
int of = Integer.parseInt(rnk[i].getText());
if(of >= 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<rnk.length; i++) {
try {
int of = Integer.parseInt(rnk[i].getText());
if(of > 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<rnk.length; i++) {
try {
int of = Integer.parseInt(rnk[i].getText());
if(of >= 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<rnk.length; i++) {
// Insert first - exception mustn't cancel insert.
p0.get().insert("d.cursor", 1, rnk[i]);
try {
int of = Integer.parseInt(rnk[i].getText());
// If there was an integer
rnk[i].setText(""+p0.getOffs());
} catch(NumberFormatException e) {
// Nothin'
}
}
c.delete();
}
// Then, the final cell.
if(c == null) return; // No cell - oops - deleted to end.
c.setSpan(c.getSpan().subSpan(p1.getOffs()+1));
rnk = p0.get().readRank("d.cursor", 1, false);
for(int i=0; i<rnk.length; i++) {
try {
int of = Integer.parseInt(rnk[i].getText());
if(of > 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));
}
}
}