/* dIntProg Browser. A webbrowser written in Java. * Copyright (C) 2001 Martin Geisler <gimpster@gimpster.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program 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 GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ import java.util.*; import java.awt.Graphics; import java.awt.Color; import javax.swing.*; /** A flexible box that knows how to flow other boxes. It will place * it's child-boxes side-by-side on lines running from left to right * and make new lines as necessary. */ public class BoxFlow extends AbstractFlexibleBox { /** A list of lines, used when drawing the box. */ private LinkedList lines = new LinkedList(); /** The minimum width of the box. This is the greatest minimum * width of all the child-boxes. * @return the minimum width of this box. */ public int getMinimumWidth() { return min_width; } /** The preferred width of this box. This is the sum of the * preferred widths of all the child-boxes. If the box is resized * to it's preferred width, all the child-boxes would fit on a * single line. */ public int getPreferredWidth() { return pref_width; } public void doLayout(Graphics g, JComponent c, int w) { super.doLayout(g, c, w); width = w; min_width = 0; pref_width = 0; reFlow(); } /** Flows the boxes into lines and updates the minimum and * preferred width. */ private void reFlow() { lines.clear(); // Start from fresh. int x_offset = 0; int y_offset = 0; int line_height = 0; Line line = new Line(); ListIterator iterator = boxes.listIterator(); while (iterator.hasNext()) { Box b = (Box)iterator.next(); boolean need_new_box = false; while (!need_new_box) { if (x_offset + b.getWidth() < width) { /* There is room for b on the current line */ if (x_offset == 0 && b instanceof RigidBox) { /* We are at the start of a line so we trim * the box. */ ((RigidBox)b).trim(RigidBox.LEFT); } if (b.getMinimumWidth() > min_width) { min_width = b.getMinimumWidth(); } pref_width = pref_width + b.getWidth(); line.insert(b); x_offset = x_offset + b.getWidth(); need_new_box = true; } else { /* No room for b on the current line */ if (b instanceof RigidBox) { if (((RigidBox)b).splitIsPossible(width - x_offset)) { /* b can be split */ Box head = ((RigidBox)b).splitHead(width-x_offset); Box tail = ((RigidBox)b).splitTail(width-x_offset); line.insert(head); lines.addLast(line); /* We update the minimum and preferred widths */ if (b.getMinimumWidth() > min_width) { min_width = b.getMinimumWidth(); } pref_width = pref_width + head.getWidth(); x_offset = 0; y_offset = y_offset + line.getLineHeight(); line = new Line(); /* We let b point the tail. */ b = tail; need_new_box = false; } else { /* b can't be split */ if (x_offset == 0) { /* We trim the box. */ ((RigidBox)b).trim(RigidBox.LEFT); /* We are at the start of a line, but b can't * be split - we have to make an overfull * hbox. */ Box split = ((RigidBox)b).getSmallestHead(); if (split != null) { /* We got a head */ Browser.debug("Overfull hbox for " + split); line.insert(split); lines.addLast(line); y_offset = y_offset + line.getLineHeight(); /* We update the minimum and * preferred widths */ if (split.getMinimumWidth() > min_width) { min_width = split.getMinimumWidth(); } pref_width = pref_width + split.getWidth(); line = new Line(); need_new_box = false; b = ((RigidBox)b).getLargestTail(); } else { /* Ack - we have to insert all of * the box here. */ if (b.getMinimumWidth() > min_width) { min_width = b.getMinimumWidth(); } /* We update the total line-width * used so far. */ pref_width = pref_width + b.getWidth(); line.insert(b); lines.addLast(line); y_offset = y_offset + line.getLineHeight(); line = new Line(); need_new_box = true; } } else { /* We are in the middle of a line. b can't be * split so we have to make break the line. */ lines.addLast(line); x_offset = 0; y_offset = y_offset + line.getLineHeight(); line = new Line(); need_new_box = false; } } } } } } /* The last line is added to the other lines. */ lines.addLast(line); /* We update the height. */ height = y_offset + line.getLineHeight(); } public void drawAt(int x, int y, DocumentView v) { int x_offset = 0; int y_offset = 0; int num_boxes = 0; ListIterator line_iterator = lines.listIterator(); while (line_iterator.hasNext()) { Line line = (Line)line_iterator.next(); ListIterator box_iterator = line.listIterator(); while (box_iterator.hasNext()) { num_boxes = num_boxes + 1; Box b = (Box)box_iterator.next(); b.drawAt(x + x_offset, y + y_offset + line.getLineHeight() - b.getHeight(), v); x_offset = x_offset + b.getWidth(); } x_offset = 0; y_offset = y_offset + line.getLineHeight(); } if (Browser.debugging) { v.drawRect(x, y, width, height, Color.red); } } /** A simple class that holds a line. */ private class Line { private LinkedList boxes = new LinkedList(); private int line_height = 0; /** Inserts a box in the line. If the box is taller than the * other boxes in the line, the lineheight is updated. * @param b the box to be inserted. */ public void insert(Box b) { if (b.getHeight() > line_height) { line_height = b.getHeight(); } boxes.addLast(b); } /** Returns the height of the tallest box in the line * @return the lineheight. */ public int getLineHeight() { return line_height; } /** Returns an iterator that can be used to go trough the * boxes in the line. * @return an iterator for the boxes in the line. */ public ListIterator listIterator() { return boxes.listIterator(); } } }