Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. /
 
 
 package org.apache.cayenne.modeler.util;
 
 import java.util.List;
 
A simple non-editable tree browser with multiple columns for display and navigation of a tree structure. This type of browser is ideal for showing deeply (or infinitely) nested trees/graphs. The most famous example of its use is Mac OS X Finder column view.

MultiColumnBrowser starts at the root of the tree and automatically expands to the right as navigation goes deeper. MultiColumnBrowser uses the same TreeModel as a regular JTree for its navigation model.

Users are notified of selection changes via a TreeSelectionEvents.

Author(s):
Andrei Adamchik
Since:
1.1
 
 public class MultiColumnBrowser extends JPanel {
 
     private static final ImageIcon rightArrow =
         ModelerUtil.buildIcon("scroll_right.gif");
 
     public static final int DEFAULT_MIN_COLUMNS_COUNT = 3;
 
     protected int minColumns;
     protected ListCellRenderer renderer;
     protected TreeModel model;
     protected Object[] selectionPath;
     protected Dimension preferredColumnSize;
 
     private List columns;
     private List treeSelectionListeners;
 
     public MultiColumnBrowser() {
         this();
     }
 
     public MultiColumnBrowser(int minColumns) {
         if (minColumns < ) {
             throw new IllegalArgumentException(
                 "Expected "
                     + 
                     + " or more columns, got: "
                    + minColumns);
        }
        this. = minColumns;
        this. = new PanelController();
        this. = Collections.synchronizedList(new ArrayList());
        initView();
    }
    public void addTreeSelectionListener(TreeSelectionListener listener) {
        synchronized () {
            if (listener != null && !.contains(listener)) {
                .add(listener);
            }
        }
    }
    public void removeTreeSelectionListener(TreeSelectionListener listener) {
        synchronized () {
            .remove(listener);
        }
    }

    
Notifies listeners of a tree selection change.
    protected void fireTreeSelectionEvent(Object[] selectionPath) {
        TreeSelectionEvent e =
            new TreeSelectionEvent(thisnew TreePath(selectionPath), falsenullnull);
        synchronized () {
            Iterator it = .iterator();
            while (it.hasNext()) {
                TreeSelectionListener listener = (TreeSelectionListenerit.next();
                listener.valueChanged(e);
            }
        }
    }

    
Returns current selection path or null if no selection is made.
    public TreePath getSelectionPath() {
        return new TreePath();
    }

    
Returns the minumum number of displayed columns.
    public int getMinColumns() {
        return ;
    }

    
Sets the minumum number of displayed columns.
    public void setMinColumns(int minColumns) {
        this. = minColumns;
    }

    
Returns prefrred size of a single browser column.
    public Dimension getPreferredColumnSize() {
        return ;
    }
    public void setPreferredColumnSize(Dimension preferredColumnSize) {
        this. = preferredColumnSize;
        refreshPreferredSize();
    }

    
Resets currently used renderer to default one that will use the "name" property of objects and display a small arrow to the right of all non-leaf nodes.
    public void setDefaultRenderer() {
        if (!( instanceof MultiColumnBrowserRenderer)) {
            setRenderer(new MultiColumnBrowserRenderer());
        }
    }

    
Returns ListCellRenderer for individual elements of each column.
    public ListCellRenderer getRenderer() {
        return ;
    }

    
Initializes the renderer of column cells.
    public synchronized void setRenderer(ListCellRenderer renderer) {
        if (this. != renderer) {
            this. = renderer;
            // update existing browser
            if ( != null && .size() > 0) {
                Iterator it = .iterator();
                while (it.hasNext()) {
                    JList column = (JListit.next();
                    column.setCellRenderer(renderer);
                }
            }
        }
    }

    
Initializes browser model.
    public synchronized void setModel(TreeModel model) {
        if (model == null) {
            throw new NullPointerException("Null tree model.");
        }
        if (this. != model) {
            this. = model;
            // display first column
            updateFromModel(model.getRoot(), -1);
        }
    }

    
Returns browser model.
    public TreeModel getModel() {
        return ;
    }

    
Returns a current number of columns.
    public int getColumnsCount() {
        return .size();
    }
    // ====================================================
    // Internal private methods
    // ====================================================
    private void initView() {
         = Collections.synchronizedList(new ArrayList());
    }

    
Expands or contracts the view by delta columns. Never contracts the view below minColumns.
    private void adjustViewColumns(int delta) {
        if (delta == 0) {
            return;
        }
        setLayout(new GridLayout(1, .size() + delta, 3, 3));
        if (delta > 0) {
            for (int i = 0; i < deltai++) {
                appendColumn();
            }
        }
        else {
            for (int i = -deltai > 0 && .size() > i--) {
                removeLastColumn();
            }
        }
        refreshPreferredSize();
        revalidate();
    }
    private BrowserPanel appendColumn() {
        BrowserPanel panel = new BrowserPanel();
        panel.setCellRenderer();
        .add(panel);
        JScrollPane scroller =
            new JScrollPane(
                panel,
                .,
                .);
        // note - it is important to set prefrred size on scroller,
        // not on the component itself... otherwise resizing
        // will be very ugly...
        if ( != null) {
            scroller.setPreferredSize();
        }
        add(scroller);
        return panel;
    }
    private BrowserPanel removeLastColumn() {
        if (.size() == 0) {
            return null;
        }
        int index = .size() - 1;
        BrowserPanel panel = (BrowserPanel.remove(index);
        // remove ansestor of the column (JScrollPane)
        remove(index);
        return panel;
    }

    
Refreshes preferred size of the browser to reflect current number of columns.
    private void refreshPreferredSize() {
        if ( != null) {
            int w = getColumnsCount() * (. + 3) + 3;
            int h = . + 6;
            setPreferredSize(new Dimension(wh));
        }
    }

    
Makes numbered column visible if the browser parent allows scrolling.
    private void scrollToColumn(int column) {
        if (getParent() instanceof JViewport) {
            JViewport viewport = (JViewportgetParent();
            // find a rectangle in the middle of the browser
            // and scroll it...
            double x = getWidth() * column / ((doublegetMinColumns());
            double y = getHeight() / 2;
            if ( != null) {
                x -= . / 2;
                if (x < 0) {
                    x = 0;
                }
            }
            Rectangle rectangle = new Rectangle((intx, (inty, 1, 1);
            // Scroll the area into view.
            viewport.scrollRectToVisible(rectangle);
        }
    }

    
Rebuilds view for the new object selection.
    private synchronized void updateFromModel(Object selectedNodeint panelIndex) {
        if( == null) {
             = new Object[0];
        }
        
        // clean up extra columns
        int lastIndex = .;
        // check array range to handle race conditions 
        for (int i = panelIndex + 1;
            i <= lastIndex && i >= 0 && i < .size();
            i++) {
            BrowserPanel column = (BrowserPanel.get(i);
            column.getSelectionModel().clearSelection();
            column.setRootNode(null);
        }
        // build path to selected node
        this. = rebuildPath(selectedNodepanelIndex);
        // a selectedNode is contained in "panelIndex" column, 
        // but its children are in the next column.
        panelIndex++;
        // expand/contract columns as needed
        adjustViewColumns(panelIndex + 1 - .size());
        // selectedNode becomes the root of columns[panelIndex]
        if (!.isLeaf(selectedNode)) {
            BrowserPanel lastPanel = (BrowserPanel.get(panelIndex);
            lastPanel.setRootNode(selectedNode);
            scrollToColumn(panelIndex);
        }
    }

    
Builds a TreePath to the new node, that is known to be a peer or a child of one of the path components. As the method walks the current path backwards, it cleans columns that are not common with the new path.
    private Object[] rebuildPath(Object[] pathObject nodeint panelIndex) {
        Object[] newPath = new Object[panelIndex + 2];
        System.arraycopy(path, 0, newPath, 0, panelIndex + 1);
        newPath[panelIndex + 1] = node;
        return newPath;
    }
    // ====================================================
    // Helper classes
    // ====================================================
    // ====================================================
    // Represents a browser column list model. This is an
    // adapter on top of the TreeModel node, showing the branch
    // containing node children
    // ====================================================
    final class ColumnListModel extends AbstractListModel {
        Object treeNode;
        int children;
        void setTreeNode(Object treeNode) {
            int oldChildren = ;
            this. = treeNode;
            this. = (treeNode != null) ? .getChildCount(treeNode) : 0;
            // must fire an event to refresh the view
            super.fireContentsChanged(
                MultiColumnBrowser.this,
                0,
                Math.max(oldChildren));
        }
        public Object getElementAt(int index) {
            return .getChild(index);
        }
        public int getSize() {
            return ;
        }
    }
    // ====================================================
    // Represents a single browser column
    // ====================================================
    final class BrowserPanel extends JList {
        BrowserPanel() {
            BrowserPanel.this.setModel(new ColumnListModel());
        }
        void setRootNode(Object node) {
            ((ColumnListModelBrowserPanel.this.getModel()).setTreeNode(node);
        }
        Object getTreeNode() {
            return ((ColumnListModelBrowserPanel.this.getModel()).;
        }
    }
    // ====================================================
    // Processes selection events in each column
    // ====================================================
    final class PanelController implements ListSelectionListener {
        public void valueChanged(ListSelectionEvent e) {
            // ignore "adjusting"
            if (!e.getValueIsAdjusting()) {
                BrowserPanel panel = (BrowserPanele.getSource();
                Object selectedNode = panel.getSelectedValue();
                // ignore unselected
                if (selectedNode != null) {
                    updateFromModel(selectedNode.indexOf(panel));
                }
            }
        }
    }
    // ====================================================
    // Default renderer that shows non-leaf nodes with a 
    // small right arrow. Unfortunately we can't subclass
    // DefaultListCellRenerer since it extends JLabel that
    // does not allow the layout that we need.
    // ====================================================
    final class MultiColumnBrowserRenderer implements ListCellRendererSerializable {
        JPanel nonLeafPanel;
        MultiColumnBrowserRenderer() {
             = CellRenderers.listRenderer();
             = new DefaultListCellRenderer() {
                public Border getBorder() {
                    return null;
                }
            };
             = new JPanel();
            .setLayout(new BorderLayout());
            .add(new JLabel(), .);
        }
        public Component getListCellRendererComponent(
            JList list,
            Object value,
            int index,
            boolean isSelected,
            boolean cellHasFocus) {
            if (getModel().isLeaf(value)) {
                return .getListCellRendererComponent(
                    list,
                    value,
                    index,
                    isSelected,
                    cellHasFocus);
            }
            Object renderedValue = ModelerUtil.getObjectName(value);
            if (renderedValue == null) {
                // render NULL as empty string
                renderedValue = " ";
            }
            Component text =
                .getListCellRendererComponent(
                    list,
                    renderedValue,
                    index,
                    isSelected,
                    cellHasFocus);
            .setBackground(text.getBackground());
            .setForeground(text.getForeground());
            .setEnabled(text.isEnabled());
            return ;
        }
    }