viewer.AlignedTree.java Source code

Java tutorial

Introduction

Here is the source code for viewer.AlignedTree.java

Source

// This file is part of Droopy.
// Copyright (C) 2011  Benoit Sigoure.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 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 Lesser
// General Public License for more details.  You should have received a copy
// of the GNU Lesser General Public License along with this program.  If not,
// see <http://www.gnu.org/licenses/>.
package viewer;

import java.util.Arrays;
import java.util.Iterator;

import com.google.gwt.user.client.ui.HasTreeItems;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
import com.google.gwt.user.client.ui.Widget;

/**
 * Like a {@link Tree} but can align {@link HorizontalPanel} items.
 * The idea here is to have something that's a {@link Tree} but that looks
 * like a table.  The idea is to achieve something somewhat like a
 * {@link http://code.google.com/p/google-web-toolkit-incubator/wiki/TreeTable TreeTable}
 * except it's simpler and a bit hackish.
 *
 * First, the tree is rendered once normally.  Once that's done, you can call
 * {@link #align()} to align all the {@link HorizontalPanel} items in the
 * tree.  This causes the tree to be rendered again, and users might notice
 * the double-rendering in a short flicker.  Not super efficient, but gets the
 * job done without having to copy-paste-edit the code of {@link Tree} and
 * {@link TreeItem} as I've seen some GWT projects do.
 *
 * This class also provides a number of static methods to add rows to the
 * tree table.  It's OK to have rows with a variable number of columns.
 *
 * Note: if you create and populate an aligned tree, especially in the event
 * loop, you can't align it immediately.  You have to defer the call to
 * {@link #align()} since the tree needs to be rendered first, so that we can
 * tell what size each widget in the tree has.  Sorry :-/
 */
public class AlignedTree extends Tree {

    private static final int DEFAULT_SPACING = 5; // pixels.

    private final int spacing;

    public AlignedTree() {
        this(DEFAULT_SPACING);
    }

    public AlignedTree(final int spacing) {
        if (spacing < 0) {
            throw new IllegalArgumentException("negative spacing: " + spacing);
        }
        this.spacing = spacing;
    }

    private static InlineLabel label(final String s) {
        return new InlineLabel(s);
    }

    private static boolean needRightAlign(final String s) {
        return s.endsWith("ms") || (!s.isEmpty() && Character.isDigit(s.charAt(0)));
    }

    private static boolean addAutoAlign(final HBox row, final String s) {
        if (needRightAlign(s)) {
            row.addRight(label(s));
            return true;
        } else {
            row.add(label(s));
            return false;
        }
    }

    public void addRow(final String a, final String b) {
        addItem(row(a, b));
    }

    public static HBox row(final String a) {
        final HBox row = new HBox();
        addAutoAlign(row, a);
        return row;
    }

    public static HBox row(final String a, final String b) {
        final HBox row = new HBox();
        row.add(label(a));
        addAutoAlign(row, b);
        return row;
    }

    public void addRow(final String a, final String b, final String c) {
        addItem(row(a, b, c));
    }

    public static HBox row(final String a, final String b, final String c) {
        final HBox row = new HBox();
        row.add(label(a));
        if (addAutoAlign(row, b)) {
            row.addRight(label(c));
        } else {
            addAutoAlign(row, c);
        }
        return row;
    }

    public void addRow(final String a, final String b, final String c, final String d) {
        addItem(row(a, b, c, d));
    }

    public static HBox row(final String a, final String b, final String c, final String d) {
        final HBox row = new HBox();
        row.add(label(a));
        boolean align_right = addAutoAlign(row, b);
        if (align_right) {
            row.addRight(label(c));
        } else {
            align_right = addAutoAlign(row, c);
        }
        if (align_right) {
            row.addRight(label(d));
        } else {
            addAutoAlign(row, d);
        }
        return row;
    }

    /**
     * Aligns all the columns of the horizontal boxes within this tree.
     * This essentially helps make the tree look like a table with columns.
     */
    public void align() {
        align(this, spacing);
    }

    public static void align(final HasTreeItems tree) {
        align(tree, DEFAULT_SPACING);
    }

    public static void align(final HasTreeItems tree, final int spacing) {
        final HorizontalPanel first = findFirstHBox(tree);
        if (first == null) {
            return;
        }

        // 1st pass: find the max width for each "column".
        int[] widths = new int[first.getWidgetCount()];
        for (final Widget w : iter(tree)) {
            if (w instanceof HorizontalPanel) {
                final HorizontalPanel h = (HorizontalPanel) w;
                final int n = h.getWidgetCount();
                if (n > widths.length) {
                    // Uh?  There's no Arrays.copyOf in GWT's emulated JRE.  WTF?
                    //widths = Arrays.copyOf(widths, n);
                    final int[] old = widths;
                    widths = new int[n];
                    System.arraycopy(old, 0, widths, 0, old.length);
                }
                for (int i = 0; i < n; i++) {
                    final int width = h.getWidget(i).getOffsetWidth();
                    if (width > widths[i]) {
                        widths[i] = width;
                    }
                }
            }
        }

        // Add the spacing we were asked to add.
        for (int i = 0; i < widths.length; i++) {
            widths[i] += spacing;
        }

        // 2nd pass: set the width on every cell in each row.
        for (final Widget w : iter(tree)) {
            if (w instanceof HorizontalPanel) {
                final HorizontalPanel h = (HorizontalPanel) w;
                final int n = h.getWidgetCount();
                for (int i = 0; i < n; i++) {
                    h.setCellWidth(h.getWidget(i), widths[i] + "px");
                }
            }
        }
    }

    private static HorizontalPanel findFirstHBox(final HasTreeItems tree) {
        for (final Widget w : iter(tree)) {
            if (w instanceof HorizontalPanel) {
                return (HorizontalPanel) w;
            }
        }
        return null;
    }

    private static Iterable<Widget> iter(final HasTreeItems tree) {
        if (tree instanceof Tree) {
            return (Tree) tree;
        } else if (tree instanceof TreeItem) {
            return new TreeItemIterator((TreeItem) tree);
        } else {
            throw new AssertionError(
                    "should never happen!  tree is neither a" + " TreeItem nor a Iterable: " + tree);
        }
    }

    /**
     * Helper to iterate on {@link TreeItem}s.
     * It seems like an oversight to have {@link Tree} implement the
     * {@code HasWidgets} interface, but not {@link TreeItem}...
     */
    private static final class TreeItemIterator implements Iterable<Widget>, Iterator<Widget> {

        private final TreeItem tree;
        private int current;

        public TreeItemIterator(final TreeItem tree) {
            this.tree = tree;
        }

        public Iterator<Widget> iterator() {
            return this;
        }

        public boolean hasNext() {
            return current < tree.getChildCount();
        }

        public Widget next() {
            return tree.getChild(current++).getWidget();
        }

        public void remove() {
            tree.removeItem(tree.getChild(current));
        }

    }

}