/* SplitCellFlob1.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.awt.*; import java.awt.event.*; import java.util.*; /** A cell, possibly split over a line break. * The broken parts are chained as a linked list. * The cell of this flob is zero if this is not the first in chain. * The text is rendered in the lowest part, just so that all of it * is inside the rectangle. *
* XXX Rework the nextPart, parent, next, prev relation! */ public class SplitCellFlob1 extends Flob implements SpanFlob { public static final String rcsid = "$Id: SplitCellFlob1.java,v 1.36 2001/04/02 20:21:43 bfallenstein Exp $"; public static boolean dbg = false; static final void p(String s) { if(dbg) System.out.println(s); } static final void pa(String s) { System.out.println(s); } // The XOR modes. static public final int XOR_NO = 0; static public final int XOR_LINE = 1; static public final int XOR_FRAME = 2; static public final int XOR_SOLID = 3; public int xormode = XOR_NO; /** Whether to clip before drawing the contents of this flob. */ public boolean doClip = false; /** The next part of this flob. */ SplitCellFlob1 nextPart; /** The first of this chain. */ public SplitCellFlob1 parent; /** Index of first character. */ public int start; /** Number of characters. */ public int n; /** The text in this flob (substring of c.getText()). * This text is allowed to be modified from outside to allow e.g. for * firstcap. Doing that in any way that does not retain the number * and offsets of characters will screw up beams and many other * things so don't do that ;) */ public String txt; public SpanFlob prev, next; public SpanFlob getPrev() { return prev; } public SpanFlob getNext() { return next; } public void setPrev(SpanFlob sf) { prev = sf; if(prev != null) prev.setNext(this); } public void setNext(SpanFlob sf) { next = sf; } public Color bg, fg = Color.black; Font f; FontMetrics fm; /** Set the background of this flob to the color of the topmost cursor. */ public void showCursor() { // Only take topmost cursor's color ZZCell curs = getCell().s("d.cursor", 1); if(curs != null) { bg = ZZCursorReal.getColor(curs); if(bg == null) pa("No cursor color!"); } else p("No cursors on this cell."); } static boolean intostr = false; public String toString() { if(intostr) return super.toString(); intostr = true; String ret = super.toString() + "(SPLCF1 bg="+bg+ "Parent: ("+parent+") stn: "+start+" "+n+" Txt:'"+txt+"' Cell:"+c +" next: "+nextPart + " )"; intostr = false; return ret; } public SplitCellFlob1(int x, int y, int d, int w, int h, ZZCell c, Font f, FontMetrics fm, SplitCellFlob1 parent, int start, int n) { this(x, y, d, w, h, c, f, fm, parent, start, n, null); } public SplitCellFlob1(int x, int y, int d, int w, int h, ZZCell c, Font f, FontMetrics fm, SplitCellFlob1 parent, int start, int n, Color bg) { this(x, y, d, w, h, c, c.getText().substring(start, start+n), f, fm, parent, start, n, bg); } public SplitCellFlob1(int x, int y, int d, int w, int h, ZZCell c, String txt, Font f, FontMetrics fm, SplitCellFlob1 parent, int start, int n, Color bg) { super(x, y, d, w, h, (parent==null ? c : null)); p("SplitCellFlob: "+x+" "+y+" "+d+" "+w+" "+h+" "+c+" "+f+" "+ fm+" "+parent+" "+start+" "+n+" "+bg); this.f = f; this.fm = fm; if(parent != null) { if(!parent.c.equals(c)) throw new ZZError( "Parent and child splitcellflob must have same cell: "+c+ " "+parent.c); SplitCellFlob1 prev = parent; while(prev.nextPart != null) prev = prev.nextPart; prev.nextPart = this; this.parent = (parent.parent == null ? parent : parent.parent); } this.start = start; this.n = n; this.txt = txt; this.bg = bg; p("Created splitcellflob: "+this); } public SplitCellFlob1(int x, int y, int d, int w, int h, ZZCell c, Font f, FontMetrics fm, SplitCellFlob1 parent, int start, int n, int xormode) { this(x, y, d, w, h, c, f, fm, parent, start, n); } public SplitCellFlob1(int x, int y, int d, int w, int h, ZZCell c, Font f, FontMetrics fm, SplitCellFlob1 parent, int start, int n, int xormode, Color bg) { this(x, y, d, w, h, c, f, fm, parent, start, n, bg); this.xormode = xormode; } public final ZZCell getCell() { return (parent==null ? c : parent.c); } public Span getSpan() { Span sp = getCell().getSpan(); if(sp == null) return null; // XXX test for samity return sp.subSpan(start, start+n); } public Rectangle getRectangle(Span s) { Span t = getCell().getSpan(); int so = (int)(s.getStart().getOffs() - t.getStart().getOffs()); int eo = (int)(s.getEnd().getOffs() - t.getStart().getOffs()); int sw = fm.stringWidth(txt.substring(0, so)); int ew = fm.stringWidth(txt.substring(0, eo)); return new Rectangle(x + sw, y, ew-sw, h); } public Rectangle getRectangle(int offs, int end) { int sw = fm.stringWidth(txt.substring(0, offs)); int ew = fm.stringWidth(txt.substring(0, end)); return new Rectangle(x + sw, y, ew-sw, h); } /* Very quick hack to interpolate from and to non-span flobs: * let this read render(g0, x, y, d, w, h) instead of render(g0). Not at * all what the interpolation SHOULD look like, but better than the * nothing we've had before. */ public void render(Graphics g0, int x, int y, int d, int w, int h) { int my = y + h - fm.getDescent(); // p("SpliCellFlob drawString: '"+txt+"' "+x+" "+my+" "+fg+" "+bg); Graphics g = g0.create(); Color bu = g.getColor(); if (doClip) g.clipRect(x, y, w, h); g.setFont(f); if(bg != null) { g.setColor(bg); g.fillRect(x, y, w, h); } if(fg != null) g.setColor(fg); else g.setColor(bu); p("SpliCellFlob drawString: '"+txt+"' "+x+" "+my+" "+fg+" "+bg); g.drawString(txt, x, my); g.dispose(); if(nextPart != null) nextPart.render(g0); } /** Render an interpolated version. * This is the trickiest routine of this class since we do * want to get the interpolation behaviour just right so that all text * pieces flow to where they are supposed to. */ public void renderInterp(Graphics g0, float fract) { if(interpTo==null) return; if(!(interpTo instanceof SplitCellFlob1)) { super.renderInterp(g0, fract); return; } SplitCellFlob1 t = this; SplitCellFlob1 r = (SplitCellFlob1)interpTo; Graphics g = g0.create(); Shape clip = g.getClip(); if(fg != null) g.setColor(fg); // Render the correct fragment of the two and advance. while(t!=null && r!=null) { if(t.start+t.n < r.start) { t = t.nextPart; continue; } if(t.start > r.start+r.n) { r = r.nextPart; continue; } int st = 0; int en = 0; if(t.start < r.start) st = r.start; else st = t.start; if(t.start+t.n < r.start+r.n) en = t.start+t.n; else en = r.start+r.n; int tx = t.getStrX(st); int rx = r.getStrX(st); int x = (int)(tx + (rx-tx)*fract); int ty = t.y + t.h - t.fm.getDescent(); int ry = r.y + r.h - r.fm.getDescent(); int y = (int)(ty + (ry-ty)*fract); int tw = t.getStrX(en) - tx; int rw = r.getStrX(en) - rx; int w = (int)(tw + (rw-tw)*fract); int realy = (int)(t.y + (r.y-t.y)*fract); if (doClip) { g.clipRect(x, realy, w, t.h); } if(bg != null) { Color col = g.getColor(); g.setColor(bg); g.fillRect(x, realy, tw, t.h); g.setColor(col); } g.setFont(t.f); g.drawString(t.txt.substring(st-t.start, en-t.start), x, y); if (doClip) g.setClip(clip); if(t.start+t.n < r.start+r.n) t = t.nextPart; else r = r.nextPart; } g.dispose(); } public boolean renderXORLine(Graphics g, int cx, int cy) { ZZCursorVirtual vc = (ZZCursorVirtual)hit(cx, cy); if(vc==null) return false; g.setColor(Color.black); g.setXORMode(Color.white); int xo = fm.stringWidth(txt.substring(0, vc.getOffs()-start)); g.drawLine(x+xo, y, x+xo, y+h); return true; } public void renderXORFrame(Graphics g, boolean left, boolean right) { // XXX instead of using f.h-1 & f.y+1, just don't create overlapping // XXX SplitCellFlobs any more? g.setColor(Color.black); g.setXORMode(Color.white); SplitCellFlob1 f = parent != null ? parent : this; if(left) g.drawLine(f.x, f.y+1, f.x, f.y+f.h-1); while(true) { g.drawLine(f.x, f.y+1, f.x+f.w, f.y+1); g.drawLine(f.x, f.y+f.h-1, f.x+f.w, f.y+f.h-1); if(f.nextPart != null) f = f.nextPart; else break; } if(right) g.drawLine(f.x+f.w, f.y+1, f.x+f.w, f.y+f.h-1); } public void renderXORSolid(Graphics g) { g.setColor(Color.black); g.setXORMode(Color.white); SplitCellFlob1 f = parent != null ? parent : this; for(; f != null; f = f.nextPart) { g.fillRect(f.x, f.y, f.w, f.h); } } public boolean renderXOR(Graphics g, int cx, int cy) { if(!insideRect(cx, cy)) return false; if(xormode == XOR_NO) return true; // XXX or false? else if(xormode == XOR_LINE) return renderXORLine(g, cx, cy); else if(xormode == XOR_FRAME) renderXORFrame(g, true, true); else if(xormode == XOR_SOLID) renderXORSolid(g); else throw new ZZError("SplitCellFlob1: Unknown XOR mode"); return true; } /** Get the x-coordinate of a given letter. * @param ind The index, given as starting from the beginning of the cell. * Must satisfy start <= ind <= start+n */ public int getStrX(int ind) { ind -= start; return x + fm.stringWidth(txt.substring(0,ind)); } public Object hit(int x, int y) { if(!insideRect(x, y)) return null; if(xormode == XOR_FRAME || xormode == XOR_SOLID) return new ZZCursorVirtual(getCell(), ZZCursorVirtual.NO_OFFSET); int ind = ZZUtil.findStringHit(txt, x-this.x, fm) + start; return new ZZCursorVirtual(getCell(), ind); } /** Add a line cursor at the given index into the given flobset. */ public void addCurs(FlobSet into, int ind, Color c) { if(parent != null) parent.addCurs0(into, ind, c); else addCurs0(into, ind, c); } private void addCurs0(FlobSet into, int ind, Color c) { if(ind >= start && ind <= start + n) { int x = getStrX(ind) - 1; into.add(new LineDecor(x, y, x, y+h, c, d)); } if(nextPart != null) nextPart.addCurs0(into, ind, c); } }