All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.viewmodel.OutlineTable Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * 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.netbeans.modules.viewmodel;

import java.awt.BorderLayout;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ActionMap;
import javax.swing.ComponentInputMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.text.DefaultEditorKit;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;

import org.netbeans.spi.viewmodel.Models;
import org.netbeans.spi.viewmodel.ColumnModel;

import org.netbeans.spi.viewmodel.DnDNodeModel;
import org.netbeans.swing.etable.ETableColumn;
import org.netbeans.swing.etable.ETableColumnModel;
import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline;
import org.netbeans.swing.outline.OutlineModel;
import org.openide.awt.Actions;
import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.OutlineView;
import org.openide.explorer.view.Visualizer;

import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Node.Property;
import org.openide.nodes.NodeNotFoundException;
import org.openide.nodes.NodeOp;
import org.openide.nodes.PropertySupport;
import org.openide.util.Exceptions;
import org.openide.windows.TopComponent;


/**
 * Implements table visual representation of the data from models, using Outline view.
 *
 * @author   Martin Entlicher
 */
public class OutlineTable extends JPanel implements
ExplorerManager.Provider, PropertyChangeListener {

    private static final Logger logger = Logger.getLogger(OutlineTable.class.getName());
    
    private ExplorerManager     explorerManager;
    final MyTreeTable           treeTable; // Accessed from tests
    Node.Property[]             columns; // Accessed from tests
    private TableColumn[]       tableColumns;
    private int[]               columnVisibleMap; // Column index -> visible index
    private boolean             ignoreCreateDefaultColumnsFromModel;
    //private IndexedColumn[]     icolumns;
    private boolean             isDefaultColumnAdded;
    private int                 defaultColumnIndex; // The index of the tree column
    private boolean             ignoreMove; // Whether to ignore column movement events
    private boolean             isSettingModelUp; // Whether a model is being set up
    //private List                expandedPaths = new ArrayList ();
    TreeModelRoot               currentTreeModelRoot; // Accessed from test
    
    //private TreeTableView       ttv;
    //private TreeView            tv;
    
    public OutlineTable () {
        setLayout (new BorderLayout ());
            treeTable = new MyTreeTable ();
            treeTable.getOutline().setRootVisible (false);
            treeTable.setVerticalScrollBarPolicy 
                (JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
            treeTable.setHorizontalScrollBarPolicy 
                (JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
            treeTable.setTreeHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
        add (treeTable, "Center");  //NOI18N
//        ttv = new TreeTableView(); // To test only
//        add(ttv, "East");
//        tv = new BeanTreeView(); // To test only
//        add(tv, "West");
        treeTable.getTable().addPropertyChangeListener("createdDefaultColumnsFromModel", new CreatedDefaultColumnsFromModel());
        treeTable.getTable().getColumnModel().addColumnModelListener(new TableColumnModelListener() {

            // Track column visibility changes.
            //   No impact on order property
            //   Change visibility map

            @Override
            public void columnAdded(TableColumnModelEvent e) {
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("columnAdded("+e+") to = "+e.getToIndex());
                    //logger.log(Level.FINE, "  called from", new IllegalStateException("TEST"));
                    TableColumnModel tcme = (TableColumnModel) e.getSource();
                    logger.fine(" column header = '"+tcme.getColumn(e.getToIndex()).getHeaderValue()+"'");
                    dumpColumnVisibleMap();
                }
                if (tableColumns != null && e.getToIndex() >= 0) {
                    // It does not say *which* column was added to the toIndex.
                    int visibleIndex = e.getToIndex();
                    int columnIndex = -1;
                    TableColumnModel tcm = treeTable.getTable().getColumnModel();
                    ETableColumnModel ecm = (ETableColumnModel) tcm;
                    for (int i = 0; i < tableColumns.length; i++) {
                        if (tableColumns[i] != null) {
                            boolean wasHidden = columns[i].isHidden();
                            boolean isHidden = ecm.isColumnHidden(tableColumns[i]);
                            if (wasHidden == true && isHidden == false) {
                                columnIndex = i;
                                break;
                            }
                        }
                    }
                    if (logger.isLoggable(Level.FINE)) {
                        logger.fine("  to index = "+visibleIndex+", column index = "+columnIndex);
                    }
                    if (columnIndex != -1) {
                        int prefferedVisibleIndex = columnVisibleMap[columnIndex];
                        // check if there's a visible column with the same visible index and lower order
                        int columnVisibleIndex = prefferedVisibleIndex;
                        int corder = getColumnOrder(columns[columnIndex]);
                        for (int i = 0; i < columnVisibleMap.length; i++) {
                            if (columnVisibleMap[i] == prefferedVisibleIndex) {
                                if (corder > getColumnOrder(columns[i]) && !columns[i].isHidden()) {
                                    prefferedVisibleIndex++;
                                    break;
                                }
                            }
                        }
                        if (logger.isLoggable(Level.FINE)) {
                            logger.fine("  to index = "+visibleIndex+", column = "+columns[columnIndex].getDisplayName()+", columnVisibleIndex = "+columnVisibleIndex+", prefferedVisibleIndex = "+prefferedVisibleIndex);
                        }
                        columns[columnIndex].setHidden(false);
                        columnVisibleMap[columnIndex] = prefferedVisibleIndex;
                        for (int i = 0; i < columnVisibleMap.length; i++) {
                            if (columnVisibleMap[i] >= columnVisibleIndex && i != columnIndex &&
                                getColumnOrder(columns[i]) >= corder) {
                                
                                columnVisibleMap[i]++;
                            }
                        }
                        if (logger.isLoggable(Level.FINE)) {
                            dumpColumnVisibleMap();
                        }
                        if (prefferedVisibleIndex >= 0 && prefferedVisibleIndex != visibleIndex) {
                            if (logger.isLoggable(Level.FINE)) {
                                logger.fine("moveColumn("+visibleIndex+", "+prefferedVisibleIndex+")");
                            }
                            ignoreMove = true;
                            try {
                                treeTable.getTable().getColumnModel().moveColumn(visibleIndex, prefferedVisibleIndex);
                            } finally {
                                ignoreMove = false;
                            }
                        }
                    }
                }
                if (logger.isLoggable(Level.FINE)) {
                    dumpColumnVisibleMap();
                    logger.fine("columnAdded() done.");
                }
            }

            @Override
            public void columnRemoved(TableColumnModelEvent e) {
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("columnRemoved("+e+") from = "+e.getFromIndex());
                    //logger.log(Level.FINE, "  called from", new IllegalStateException("TEST"));
                    dumpColumnVisibleMap();
                }
                if (tableColumns != null && e.getFromIndex() >= 0) {
                    int visibleIndex = e.getFromIndex();
                    logger.log(Level.FINE, "  from index = {0}", visibleIndex);
                    int columnIndex = getColumnIndex(visibleIndex);
                    if (columnIndex != -1) {
                        columns[columnIndex].setHidden(true);
                        for (int i = 0; i < columnVisibleMap.length; i++) {
                            if (columnVisibleMap[i] >= visibleIndex && columnVisibleMap[i] > 0) {
                                columnVisibleMap[i]--;
                            }
                        }
                    }
                }
                if (logger.isLoggable(Level.FINE)) {
                    dumpColumnVisibleMap();
                    logger.fine("columnRemoved() done.");
                }
            }

            @Override
            public void columnMoved(TableColumnModelEvent e) {
                if (tableColumns == null || ignoreMove) {
                    return ;
                }
                int from = e.getFromIndex();
                int to = e.getToIndex();
                if (from == to) {
                    // Ignore Swing strangeness
                    return ;
                }
                int fc = getColumnIndex(from);
                int tc = getColumnIndex(to);
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("columnMoved("+e+") from = "+from+", to = "+to);
                    logger.fine("  from = "+from+", to = "+to);
                    logger.fine("  fc = "+fc+", tc = "+tc);
                    TableColumnModel tcme = (TableColumnModel) e.getSource();
                    logger.fine(" column headers = '"+tcme.getColumn(e.getFromIndex()).getHeaderValue()+"' => '"+tcme.getColumn(e.getToIndex()).getHeaderValue()+"'");
                    dumpColumnVisibleMap();
                }
                
                int toColumnOrder = getColumnOrder(columns[tc]);
                int fromColumnOrder = getColumnOrder(columns[fc]);
                setColumnOrder(columns[fc], toColumnOrder);
                setColumnOrder(columns[tc], fromColumnOrder);
                fromColumnOrder = columnVisibleMap[fc];
                columnVisibleMap[fc] = columnVisibleMap[tc];
                columnVisibleMap[tc] = fromColumnOrder;

                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("After move:");
                    dumpColumnVisibleMap();
                }
            }
            
            @Override
            public void columnMarginChanged(ChangeEvent e) {}
            @Override
            public void columnSelectionChanged(ListSelectionEvent e) {}
        });
        ActionMap map = getActionMap();
        ExplorerManager manager = getExplorerManager();
        map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(manager));
        map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(manager));
        map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(manager));
        map.put("delete", ExplorerUtils.actionDelete(manager, false));
        setFocusable(false);
    }

    private void dumpColumnVisibleMap() {
        logger.fine("");
        logger.fine("Column Visible Map ("+columnVisibleMap.length+"):");
        for (int i = 0; i < columnVisibleMap.length; i++) {
            logger.fine(" {"+columns[i].getDisplayName()+"} \tvisible map["+i+"] = "+columnVisibleMap[i]+"; columnOrder["+i+"] = "+getColumnOrder(columns[i])+"\t"+(columns[i].isHidden() ? "hidden" : ""));
        }
        logger.fine("");
    }

    private int getColumnOrder(Node.Property column) {
        Integer order = (Integer) column.getValue(Column.PROP_ORDER_NUMBER);
        if (order == null) {
            return -1;
        } else {
            return order.intValue();
        }
    }

    private void setColumnOrder(Node.Property column, int order) {
        column.setValue(Column.PROP_ORDER_NUMBER, order);
        if (order != getColumnOrder(column)) {
            Exceptions.printStackTrace(new IllegalStateException("The order "+order+" could not be set to column "+column));
        }
    }

    private int getColumnIndex(int visibleIndex) {
        for (int i = 0; i < columnVisibleMap.length; i++) {
            if (visibleIndex == columnVisibleMap[i] && !columns[i].isHidden()) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Set list of models.
     * Columns are taken from the first model. Children are listed
     * @param models
     */
    public void setModel (Models.CompoundModel model) {
        setModel(model, null);
    }
    
    /**
     * Set list of models.
     * Columns are taken from the first model. Children are listed
     * @param models
     */
    public void setModel (Models.CompoundModel model, MessageFormat treeNodeDisplayFormat) {
        isSettingModelUp = true;
        try {
        // 2) save current settings (like columns, expanded paths)
        //List ep = treeTable.getExpandedPaths ();
        if (currentTreeModelRoot == null || currentTreeModelRoot.getTreeNodeDisplayFormat() == null) {
            saveWidths ();
            saveSortedState();
        }
        
        //this.model = model;
        
        // 1) destroy old model
        if (currentTreeModelRoot != null) {
            currentTreeModelRoot.destroy ();
            currentTreeModelRoot = null;
        }
        
        // 3) no model => set empty root node & return
        if (model == null) {
            getExplorerManager ().setRootContext (
                new AbstractNode (Children.LEAF)
            );
            return;
        }
        
        // 4) set columns for given model
        String[] nodesColumnName = new String[] { null, null };
        ColumnModel[] cs = model.getColumns ();
        if (logger.isLoggable(Level.FINE)) {
            logger.fine("setModel(): creating columns: ("+cs.length+")");
            for (int i = 0; i < cs.length; i++) {
                logger.fine("  ColumnModel["+i+"] = "+cs[i].getDisplayName()+", ID = "+cs[i].getID()+", visible = "+cs[i].isVisible());
            }
        }
        Node.Property[] columnsToSet = createColumns (cs, nodesColumnName);
        ignoreCreateDefaultColumnsFromModel = true;
        treeTable.setNodesColumnName(nodesColumnName[0], nodesColumnName[1]);
        if (logger.isLoggable(Level.FINE)) {
            logger.fine("setModel(): setNodesColumnName("+Arrays.toString(nodesColumnName)+") done");
        }
        currentTreeModelRoot = new TreeModelRoot (model, treeTable);
        currentTreeModelRoot.setTreeNodeDisplayFormat(treeNodeDisplayFormat);
        TreeModelNode rootNode = currentTreeModelRoot.getRootNode ();
        getExplorerManager ().setRootContext (rootNode);
        // The root node must be ready when setting the columns
        if (logger.isLoggable(Level.FINE)) {
            logger.fine("setModel(): setProperties("+Arrays.toString(columnsToSet)+")");
        }
        if (treeNodeDisplayFormat == null) {
            treeTable.setProperties (columnsToSet);
            updateTableColumns(columnsToSet, null);
        } else {
            treeTable.setProperties (new Property[]{});
        }
        ignoreCreateDefaultColumnsFromModel = false;
        treeTable.setAllowedDragActions(model.getAllowedDragActions());
        treeTable.setAllowedDropActions(model.getAllowedDropActions(null));
        treeTable.setDynamicDropActions(model);

        //treeTable.getTable().tableChanged(new TableModelEvent(treeTable.getOutline().getModel()));
        //getExplorerManager ().setRootContext (rootNode);
        
        // 5) set root node for given model
        // Moved to 4), because the new root node must be ready when setting columns

        // 6) update column widths & expanded nodes
        if (treeNodeDisplayFormat == null) {
            updateColumnWidthsAndSorting();
        }
        //treeTable.expandNodes (expandedPaths);
        // TODO: this is a workaround, we should find a better way later
        /* We must not call children here - it can take a long time...
         * the expansion is performed in TreeModelNode.TreeModelChildren.applyChildren()
        final List backupPath = new ArrayList (expandedPaths);
        if (backupPath.size () == 0)
            TreeModelNode.getRequestProcessor ().post (new Runnable () {
                public void run () {
                    try {
                        final Object[] ch = TreeTable.this.model.getChildren 
                            (TreeTable.this.model.getRoot (), 0, 0);
                        SwingUtilities.invokeLater (new Runnable () {
                            public void run () {
                                expandDefault (ch);
                            }
                        });
                    } catch (UnknownTypeException ex) {}
                }
            });
        else
            SwingUtilities.invokeLater (new Runnable () {
                public void run () {
                    treeTable.expandNodes (backupPath);
                }
            });
         */
        //if (ep.size () > 0) expandedPaths = ep;

        // Sort of hack(?) After close/open of the view the table becomes empty,
        // it looks like the root node stays unexpanded for some reason.
        //treeTable.expandNode(rootNode);
        } finally {
            isSettingModelUp = false;
        }
    }
    
    /**
     * Set list of models.
     * Columns are taken from the first model. Children are listed
     * @param models
     */
    public void setModel (HyperCompoundModel model, MessageFormat treeNodeDisplayFormat) {
        isSettingModelUp = true;
        try {
        // 2) save current settings (like columns, expanded paths)
        //List ep = treeTable.getExpandedPaths ();
        if (currentTreeModelRoot == null || currentTreeModelRoot.getTreeNodeDisplayFormat() == null) {
            saveWidths ();
            saveSortedState();
        }

        //this.model = model;

        // 1) destroy old model
        if (currentTreeModelRoot != null) {
            currentTreeModelRoot.destroy ();
            currentTreeModelRoot = null;
        }

        // 3) no model => set empty root node & return
        if (model == null) {
            getExplorerManager ().setRootContext (
                new AbstractNode (Children.LEAF)
            );
            return;
        }

        // 4) set columns for given model
        String[] nodesColumnName = new String[] { null, null };
        ColumnModel[] cs = model.getColumns ();
        Node.Property[] columnsToSet = createColumns (cs, nodesColumnName);
        ignoreCreateDefaultColumnsFromModel = true;
        treeTable.setNodesColumnName(nodesColumnName[0], nodesColumnName[1]);
        currentTreeModelRoot = new TreeModelRoot (model, treeTable);
        currentTreeModelRoot.setTreeNodeDisplayFormat(treeNodeDisplayFormat);
        TreeModelNode rootNode = currentTreeModelRoot.getRootNode ();
        getExplorerManager ().setRootContext (rootNode);
        // The root node must be ready when setting the columns
        if (treeNodeDisplayFormat == null) {
            treeTable.setProperties (columnsToSet);
            updateTableColumns(columnsToSet, null);
        } else {
            treeTable.setProperties (new Property[]{});
        }
        ignoreCreateDefaultColumnsFromModel = false;
        treeTable.setAllowedDragActions(model.getAllowedDragActions());
        treeTable.setAllowedDropActions(model.getAllowedDropActions(null));

        // 5) set root node for given model
        // Moved to 4), because the new root node must be ready when setting columns

        // 6) update column widths & expanded nodes
        if (treeNodeDisplayFormat == null) {
            updateColumnWidthsAndSorting();
        }
        /* We must not call children here - it can take a long time...
         * the expansion is performed in TreeModelNode.TreeModelChildren.applyChildren()
         */
        } finally {
            isSettingModelUp = false;
        }
    }

    @Override
    public ExplorerManager getExplorerManager () {
        if (explorerManager == null) {
            explorerManager = new ExplorerManager ();
        }
        return explorerManager;
    }
    
    @Override
    public void propertyChange (PropertyChangeEvent evt) {
        String propertyName = evt.getPropertyName ();
        TopComponent tc = (TopComponent) SwingUtilities.
            getAncestorOfClass (TopComponent.class, this);
        if (tc == null) {
            return;
        }
        if (propertyName.equals (TopComponent.Registry.PROP_CURRENT_NODES)) {
            ExplorerUtils.activateActions(getExplorerManager(), equalNodes());
        } else
        if (propertyName.equals (ExplorerManager.PROP_SELECTED_NODES)) {
            tc.setActivatedNodes ((Node[]) evt.getNewValue ());
        }
    }
    
    private boolean equalNodes () {
        Node[] ns1 = TopComponent.getRegistry ().getCurrentNodes ();
        Node[] ns2 = getExplorerManager ().getSelectedNodes ();
        if (ns1 == ns2) {
            return true;
        }
        if ( (ns1 == null) || (ns2 == null) ) {
            return false;
        }
        if (ns1.length != ns2.length) {
            return false;
        }
        int i, k = ns1.length;
        for (i = 0; i < k; i++) {
            if (!ns1 [i].equals (ns2 [i])) {
                return false;
            }
        }
        return true;
    }
    
    private Node.Property[] createColumns (ColumnModel[] cs, String[] nodesColumnNameAndDescription) {
        int i, k = cs.length;
        // Check column IDs:
        {
            Map IDs = new HashMap(k);
            for (i = 0; i < k; i++) {
                String id = cs[i].getID();
                if (IDs.containsKey(id)) {
                    ColumnModel csi = IDs.get(id);
                    logger.severe("\nHave two columns with identical IDs \""+id+"\": "+csi+" ["+csi.getDisplayName()+"] and "+cs[i]+" ["+cs[i].getDisplayName()+"]\n");
                } else {
                    IDs.put(id, cs[i]);
                }
            }
        }
        columns = new Column[k];
        //icolumns = new IndexedColumn[k];
        columnVisibleMap = new int[k];
        isDefaultColumnAdded = false;
        ColumnModel treeColumn = null;
        boolean addDefaultColumn = true;
        List columnList = new ArrayList(k);
        int d = 0;
        boolean[] originalOrder = new boolean[k];
        for (i = 0; i < k; i++) {
            Column c = new Column(cs [i]);
            columns[i] = c;
            //IndexedColumn ic = new IndexedColumn(c, i, cs[i].getCurrentOrderNumber());
            //icolumns[i] = ic;
            int order = cs[i].getCurrentOrderNumber();
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("createColumns(): column {"+c.getDisplayName()+"}: order = "+order+", i = "+i+", d = "+d);
            }
            if (order == -1) {
                order = i;
            } else {
                originalOrder[i] = true;
            }
            order += d;
            columnVisibleMap[i] = order;
            if (cs[i].getType() != null) {
                columnList.add(c);
            } else {
                treeColumn = cs[i];
                nodesColumnNameAndDescription[0] = Actions.cutAmpersand(cs[i].getDisplayName());
                nodesColumnNameAndDescription[1] = cs[i].getShortDescription();
                addDefaultColumn = false;
                defaultColumnIndex = i;
                if (cs[i].getCurrentOrderNumber() == -1) {
                    // By default let this be the first column and increase the orders
                    columnVisibleMap[i] = 0;
                    for (int j = 0; j < i; j++) {
                        columnVisibleMap[j]++;
                    }
                    d = 1;
                }
                c.setHidden(false); // The tree column can not be hidden
            }
        }
        if (addDefaultColumn) {
            PropertySupport.ReadWrite[] columns2 =
                new PropertySupport.ReadWrite [columns.length + 1];
            System.arraycopy (columns, 0, columns2, 1, columns.length);
            columns2 [0] = new DefaultColumn ();
            nodesColumnNameAndDescription[0] = columns2[0].getDisplayName();
            nodesColumnNameAndDescription[1] = columns2[0].getShortDescription();
            columns = columns2;
            int[] columnVisibleMap2 = new int[columnVisibleMap.length + 1];
            columnVisibleMap2[0] = 0;
            for (i = 0; i < k; i++) {
                columnVisibleMap2[i + 1] = columnVisibleMap[i] + 1;
            }
            columnVisibleMap = columnVisibleMap2;
            isDefaultColumnAdded = true;
            defaultColumnIndex = 0;
        }
        if (treeColumn != null) {
            treeTable.setTreeSortable(treeColumn.isSortable());
        }
        if (logger.isLoggable(Level.FINE)) {
            logger.fine("createColumns(): columns before checkOrder()");
            dumpColumnVisibleMap();
        }
        // Check visible map (order) for duplicities and gaps
        checkOrder(columnVisibleMap, originalOrder);
        if (logger.isLoggable(Level.FINE)) {
            logger.fine("createColumns(): columns after checkOrder()");
            dumpColumnVisibleMap();
        }

        int[] columnOrder = new int[columnVisibleMap.length];
        System.arraycopy(columnVisibleMap, 0, columnOrder, 0, columnOrder.length);

        for (i = 0; i < columnVisibleMap.length; i++) {
            setColumnOrder(columns[i], columnOrder[i]);
            if (columns[i].isHidden()) {
                int order = columnOrder[i];
                for (int j = 0; j < columnVisibleMap.length; j++) {
                    if (columnOrder[j] >= order && columnVisibleMap[j] > 0) {
                        columnVisibleMap[j]--;
                    }
                }
            }
        }

        if (logger.isLoggable(Level.FINE)) {
            logger.fine("createColumns:");
            dumpColumnVisibleMap();
        }

        Node.Property[] columnProps = columnList.toArray(new Node.Property[]{});
        tableColumns = null;
        return columnProps;
    }

    /** Squeeze gaps and split duplicities to make it a permutation. */
    private void checkOrder(int[] orders, boolean[] originalOrder) {
        if (logger.isLoggable(Level.FINE)) {
            StringBuilder msg = new StringBuilder("checkOrder(");
            for (int i = 0; i < orders.length; i++) {
                msg.append(orders[i]);
                msg.append(", ");
            }
            msg.append("\b\b)");
            logger.fine(msg.toString());
        }
        int n = orders.length;
        // Find and squeeze gaps:
        for (int i = 0; i < n; i++) {
            // Check if 'i' order is there:
            int min = Integer.MAX_VALUE;
            int j;
            for (j = 0; j < n; j++) {
                if (i == orders[j]) {
                    break;
                } else if (orders[j] > i) {
                    min = Math.min(min, orders[j]);
                }
            }
            if (j == n && min != Integer.MAX_VALUE) {
                // 'i' not found, shift:
                int shift = min - i;
                for (j = 0; j < n; j++) {
                    if (orders[j] > i) {
                        orders[j] -= shift;
                    }
                }
            }
        }
        if (logger.isLoggable(Level.FINE)) {
            StringBuilder msg = new StringBuilder("  squeezed: ");
            for (int i = 0; i < orders.length; i++) {
                msg.append(orders[i]);
                msg.append(", ");
            }
            msg.append("\b\b)");
            logger.fine(msg.toString());
        }
        // Find and split duplicities:
        int[] duplicates = new int[n];
        for (int i = 0; i < n; i++) {
            int d = ++duplicates[orders[i]];
            if (d > 1) {
                int o = orders[i];
                boolean isOriginalOrder = originalOrder[i];
                boolean shiftedOther = false;
                for (int j = 0; j < n; j++) {
                    if (orders[j] > o || orders[j] == o) {
                        if (orders[j] == o) {
                            if (shiftedOther) {
                                continue;
                            }
                            // If the current duplicity has the original order
                            // and the other has not, shift the other.
                            if (j < i && isOriginalOrder && !originalOrder[j]) {
                                shiftedOther = true;
                            } else if (j < i) {
                                // Otherwise we will do the shift when j == i.
                                continue;
                            }
                        }
                        if (j <= i) {
                            duplicates[orders[j]]--;
                        }
                        orders[j]++;
                        if (j <= i) {
                            duplicates[orders[j]]++;
                        }
                    }
                }
            }
        }
        if (logger.isLoggable(Level.FINE)) {
            StringBuilder msg = new StringBuilder("  splitted: ");
            for (int i = 0; i < orders.length; i++) {
                msg.append(orders[i]);
                msg.append(", ");
            }
            msg.append("\b\b)");
            logger.fine(msg.toString());
        }
    }

    private void updateTableColumns(Property[] columnsToSet, TableColumn[] newTColumns) {
        TableColumnModel tcm = treeTable.getTable().getColumnModel();
        ETableColumnModel ecm = (ETableColumnModel) tcm;
        List allColumns = getAllColumns(ecm);
        //int d = (isDefaultColumnAdded) ? 1 : 0;
        int ci = 0;
        int tci = 0;//d;
        TableColumn[] tableColumns = new TableColumn[columns.length];
        if (defaultColumnIndex > 0) {
            tci++;
        }
        for (int i = 0; i < columns.length; i++) {
            if (ci < columnsToSet.length && columns[i] == columnsToSet[ci] && i != defaultColumnIndex) {
                TableColumn tc = allColumns.get(tci); //tcm.getColumn(tci);
                tableColumns[i] = tc;
                if (columns[i] instanceof Column) {
                    Column c = (Column) columns[i];
                    TableCellEditor cellEditor = tc.getCellEditor();
                    if (cellEditor == null) {
                        cellEditor = treeTable.getTable().getDefaultEditor(Node.Property.class);
                    }
                    tc.setCellEditor(new DelegatingCellEditor(
                            c.getName(),
                            cellEditor));
                    TableCellRenderer cellRenderer = tc.getCellRenderer();
                    if (cellRenderer == null) {
                        cellRenderer = treeTable.getTable().getDefaultRenderer(Node.Property.class);
                    }
                    tc.setCellRenderer(new DelegatingCellRenderer(
                            c.getName(),
                            cellRenderer));
                    tc.setPreferredWidth(c.getColumnWidth());
                }
                if (columns[i].isHidden()) {
                    ecm.setColumnHidden(tc, true);
                } else {
                    if (columns[i] instanceof Column) {
                        Column c = (Column) columns[i];
                        tc.setPreferredWidth(c.getColumnWidth());
                    }
                }
                tci++;
                ci++;
            } else {
                TableColumn tc = allColumns.get(0); //tcm.getColumn(0);
                tableColumns[i] = tc;
                if (columns[i] instanceof Column) {
                    Column c = (Column) columns[i];
                    tc.setCellEditor(c.getTableCellEditor());
                    tc.setPreferredWidth(c.getColumnWidth());
                }
                String name = tc.getHeaderValue().toString();
                tc.setCellEditor(new DelegatingCellEditor(
                        name,
                        treeTable.getTable().getCellEditor(0, 0)));
                tc.setCellRenderer(new DelegatingCellRenderer(
                        name,
                        treeTable.getTable().getCellRenderer(0, 0)));
                if (defaultColumnIndex == 0) {
                    tci++;
                }
            }
        }
        if (logger.isLoggable(Level.FINE)) {
            logger.fine("updateTableColumns("+columns.length+"):");
            for (int i = 0; i < columns.length; i++) {
                logger.fine("Column["+i+"] ("+columns[i].getDisplayName()+") = "+((tableColumns[i] != null) ? tableColumns[i].getHeaderValue() : "null")+"\t"+(columns[i].isHidden() ? "hidden" : ""));
            }
        }
        setColumnsOrder();
        this.tableColumns = tableColumns;
    }
    
    private List getAllColumns(ETableColumnModel etcm) {
        try {
            Method getAllColumnsMethod = ETableColumnModel.class.getDeclaredMethod("getAllColumns");
            getAllColumnsMethod.setAccessible(true);
            Object allColumns = getAllColumnsMethod.invoke(etcm);
            return (List) allColumns;
        } catch (IllegalAccessException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IllegalArgumentException ex) {
            Exceptions.printStackTrace(ex);
        } catch (InvocationTargetException ex) {
            Exceptions.printStackTrace(ex);
        } catch (NoSuchMethodException ex) {
            Exceptions.printStackTrace(ex);
        } catch (SecurityException ex) {
            Exceptions.printStackTrace(ex);
        }
        return Collections.emptyList();
    }

    // Re-order the UI columns according to the defined order
    private void setColumnsOrder() {
        logger.fine("setColumnsOrder()");
        TableColumnModel tcm = treeTable.getTable().getColumnModel();
        //int[] shift = new int[columns.length];
        int defaultColumnVisibleIndex = 0;
        for (int i = 0; i < defaultColumnIndex; i++) {
            if (!columns[i].isHidden()) {
                defaultColumnVisibleIndex++;
            }
        }
        if (defaultColumnVisibleIndex != 0 && defaultColumnVisibleIndex < tcm.getColumnCount()) {
            logger.log(Level.FINE, " move default column({0}, {1})", new Object[]{0, defaultColumnVisibleIndex});
            tcm.moveColumn(0, defaultColumnVisibleIndex);
        }

        int n = tcm.getColumnCount();
        int[] order = new int[n];
        int ci = 0;
        for (int i = 0; i < n; i++, ci++) {
            while (ci < columns.length && columns[ci].isHidden()) {
                ci++;
            }
            if (ci >= columns.length) {
                break;
            }
            order[i] = columnVisibleMap[ci];
            logger.log(Level.FINE, "    order[{0}] = {1}", new Object[]{i, order[i]});
        }
        for (int i = 0; i < n; i++) {
            int j = 0;
            for (; j < n; j++) {
                if (order[j] == i) {
                    break;
                }
            }
            if (j == n) {
                // No "j" for order[j] == i.
                continue;
            }
            logger.log(Level.FINE, "  order[{0}] = {1}", new Object[]{j, i});
            if (j != i) {
                for (int k = j; k > i; k--) {
                    order[k] = order[k-1];
                }
                order[i] = i;
                logger.log(Level.FINE, " move column({0}, {1})", new Object[]{j, i});
                tcm.moveColumn(j, i);
            }
        }
    }

    private boolean isHiddenColumn(int index) {
        if (tableColumns == null) {
            return false;
        }
        if (tableColumns[index] == null) {
            return true;
        }
        ETableColumnModel ecm = (ETableColumnModel) treeTable.getTable().getColumnModel();
        return ecm.isColumnHidden(tableColumns[index]);
    }

    void updateColumnWidthsAndSorting() {
        logger.fine("\nupdateColumnWidthsAndSorting():");
        int i, k = columns.length;
        TableColumnModel tcm = treeTable.getTable().getColumnModel();
        ETableColumnModel ecm = (ETableColumnModel) tcm;
        ecm.clearSortedColumns();
        for (i = 0; i < k; i++) {
            if (isHiddenColumn(i)) {
                continue;
            }
            int visibleOrder = columnVisibleMap[i];
            logger.log(Level.FINE, "  visibleOrder[{0}] = {1}, ", new Object[]{i, visibleOrder});
            ETableColumn tc;
            try {
                tc = (ETableColumn) tcm.getColumn (visibleOrder);
            } catch (ArrayIndexOutOfBoundsException aioobex) {
                logger.log(Level.SEVERE,
                        "Column("+i+") "+columns[i].getName()+" visible index = "+visibleOrder+
                        ", columnVisibleMap = "+java.util.Arrays.toString(columnVisibleMap)+
                        ", num of columns = "+tcm.getColumnCount(),
                        aioobex);
                continue ;
            }
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("  GUI column = "+tc.getHeaderValue());
            }
            if (columns[i] instanceof Column) {
                Column c = (Column) columns[i];
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("    Retrieved width "+c.getColumnWidth()+" from "+columns[i].getDisplayName()+"["+i+"] for "+tc.getHeaderValue());
                }
                tc.setPreferredWidth(c.getColumnWidth());
                if (c.isSorted()) {
                    ecm.setColumnSorted(tc, !c.isSortedDescending(), 1);
                }
            }
        }
    }

    private void saveWidths () {
        if (columns == null) {
            return;
        }
        int i, k = columns.length;
        if (k == 0) {
            return ;
        }
        TableColumnModel tcm = treeTable.getTable().getColumnModel();
        ETableColumnModel ecm = (ETableColumnModel) tcm;
        Enumeration etc = tcm.getColumns();
        boolean defaultState = true;
        while(etc.hasMoreElements()) {
            if (etc.nextElement().getWidth() != 75) {
                defaultState = false;
                break;
            }
        }
        if (defaultState) {
            // All columns have the default width 75.
            // It's very likely that the table was not fully initialized => do not save anything.
            return ;
        }
        logger.fine("\nsaveWidths():");
        for (i = 0; i < k; i++) {
            if (isHiddenColumn(i)) {
                continue;
            }
            int visibleOrder = columnVisibleMap[i];
            logger.log(Level.FINE, "  visibleOrder[{0}] = {1}, ", new Object[]{i, visibleOrder});
            if (visibleOrder >= tcm.getColumnCount()) {
                continue;
            }
            TableColumn tc;
            try {
                tc = tcm.getColumn (visibleOrder);
            } catch (ArrayIndexOutOfBoundsException aioobex) {
                logger.log(Level.SEVERE,
                        "Column("+i+") "+columns[i].getName()+" visible index = "+visibleOrder+
                        ", columnVisibleMap = "+java.util.Arrays.toString(columnVisibleMap)+
                        ", num of columns = "+tcm.getColumnCount(),
                        aioobex);
                continue ;
            }
            if (logger.isLoggable(Level.FINE)) {
                logger.log(Level.FINE, "  GUI column = {0}", tc.getHeaderValue());
            }
            if (columns[i] instanceof Column) {
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("    Setting width "+tc.getWidth()+" from "+tc.getHeaderValue()+" to "+columns[i].getDisplayName()+"["+i+"]");
                }
                ((Column) columns[i]).setColumnWidth(tc.getWidth());
            }
        }
    }
    
    private void saveSortedState () {
        if (columns == null) {
            return;
        }
        int i, k = columns.length;
        if (k == 0) {
            return ;
        }
        TableColumnModel tcm = treeTable.getTable().getColumnModel();
        ETableColumnModel ecm = (ETableColumnModel) tcm;
        Enumeration etc = tcm.getColumns();
        logger.fine("\nsaveSortedState():");
        for (i = 0; i < k; i++) {
            if (isHiddenColumn(i)) {
                continue;
            }
            int visibleOrder = columnVisibleMap[i];
            logger.log(Level.FINE, "  visibleOrder[{0}] = {1}, ", new Object[]{i, visibleOrder});
            if (visibleOrder >= tcm.getColumnCount()) {
                continue;
            }
            ETableColumn tc;
            try {
                tc = (ETableColumn) tcm.getColumn (visibleOrder);
            } catch (ArrayIndexOutOfBoundsException aioobex) {
                logger.log(Level.SEVERE,
                        "Column("+i+") "+columns[i].getName()+" visible index = "+visibleOrder+
                        ", columnVisibleMap = "+java.util.Arrays.toString(columnVisibleMap)+
                        ", num of columns = "+tcm.getColumnCount(),
                        aioobex);
                continue ;
            }
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("  GUI column = "+tc.getHeaderValue());
            }
            if (columns[i] instanceof Column) {
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("    Setting sorted "+tc.isSorted()+" descending "+(!tc.isAscending())+" to "+columns[i].getDisplayName()+"["+i+"]");
                }
                ((Column) columns[i]).setSorted(tc.isSorted());
                ((Column) columns[i]).setSortedDescending(!tc.isAscending());
            }
        }
    }

    /** Requests focus for the tree component. Overrides superclass method. */
    @Override
    public boolean requestFocusInWindow () {
        super.requestFocusInWindow();
        return treeTable.requestFocusInWindow ();
    }
    
    @Override
    public void addNotify () {
        TopComponent.getRegistry ().addPropertyChangeListener (this);
        ExplorerUtils.activateActions(getExplorerManager (), true);
        getExplorerManager ().addPropertyChangeListener (this);
        super.addNotify ();
    }
    
    @Override
    public void removeNotify () {
        super.removeNotify ();
        TopComponent.getRegistry ().removePropertyChangeListener (this);
        ExplorerUtils.activateActions(getExplorerManager (), false);
        getExplorerManager ().removePropertyChangeListener (this);
        setModel(null);
    }
    
    public boolean isExpanded (Object node) {
        Node[] ns = currentTreeModelRoot.findNode (node);
        if (ns.length == 0) {
            return false; // Something what does not exist is not expanded ;-)
        }
        return treeTable.isExpanded (ns[0]);
    }

    public void expandNode (Object node) {
        Node[] ns = currentTreeModelRoot.findNode (node);
        for (Node n : ns) {
            treeTable.expandNode (n);
        }
    }

    public void collapseNode (Object node) {
        Node[] ns = currentTreeModelRoot.findNode (node);
        for (Node n : ns) {
            treeTable.collapseNode (n);
        }
    }
    
    private class CreatedDefaultColumnsFromModel implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            TableColumn[] columns = (TableColumn[]) evt.getNewValue();
            if (columns == null) {
                if (currentTreeModelRoot != null && !isSettingModelUp) {
                    // Refreshing a set up table, need to save the column widths
                    saveWidths();
                }
                tableColumns = null;
            } else if (!ignoreCreateDefaultColumnsFromModel) {
                // Update the columns after they are reset:
                Property[] properties = treeTable.getProperties();
                if (properties != null) {
                    updateTableColumns(properties, columns);
                }
            }
        }
        
    }
    
    static class MyTreeTable extends OutlineView {  // Accessed from tests

        private Reference dndModelRef = new WeakReference(null);
        private Property[] properties;

        MyTreeTable () {
            super ();
            Outline outline = getOutline();
            outline.setShowHorizontalLines (true);
            outline.setShowVerticalLines (false);
            filterInputMap(outline, JComponent.WHEN_FOCUSED);
            filterInputMap(outline, JComponent.WHEN_IN_FOCUSED_WINDOW);
            filterInputMap(outline, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
            outline.putClientProperty("PropertyToolTipShortDescription", Boolean.TRUE);
        }
        
        private void filterInputMap(JComponent component, int condition) {
            InputMap imap = component.getInputMap(condition);
            if (imap instanceof ComponentInputMap) {
                imap = new F8FilterComponentInputMap(component, imap);
            } else {
                imap = new F8FilterInputMap(imap);
            }
            component.setInputMap(condition, imap);
        }
        
        JTable getTable () {
            return getOutline();
        }

        @Override
        public void setProperties(Property[] newProperties) {
            this.properties = newProperties;
            super.setProperties(newProperties);
        }
        
        Property[] getProperties() {
            return properties;
        }
        
        void setNodesColumnName(String name, String description) {
            OutlineModel m = getOutline().getOutlineModel();
            if (m instanceof DefaultOutlineModel) {
                ((DefaultOutlineModel) m).setNodesColumnLabel(name);
            }
            setPropertyColumnDescription(name, description);
        }

        /*
        public List getExpandedPaths () {
            List result = new ArrayList ();
            ExplorerManager em = ExplorerManager.find (this);
            TreeNode rtn = Visualizer.findVisualizer (
                em.getRootContext ()
            );
            TreePath tp = new TreePath (rtn); // Get the root
            
            Enumeration exPaths = tree.getExpandedDescendants (tp); 
            if (exPaths == null) return result;
            for (;exPaths.hasMoreElements ();) {
                TreePath ep = (TreePath) exPaths.nextElement ();
                Node en = Visualizer.findNode (ep.getLastPathComponent ());
                String[] path = NodeOp.createPath (en, em.getRootContext ());
                result.add (path);
            }
            return result;
        }
         */
        
        /** Expands all the paths, when exists
         */
        public void expandNodes (List exPaths) {
            for (Iterator it = exPaths.iterator (); it.hasNext ();) {
                String[] sp = (String[]) it.next ();
                TreePath tp = stringPath2TreePath (sp);
                if (tp != null) {
                    getOutline().expandPath(tp);
                    Rectangle rect = getOutline().getPathBounds(tp);
                    if (rect != null) {
                        getOutline().scrollRectToVisible(rect);
                    }
                }
            }
        }

        /** Converts path of strings to TreePath if exists null otherwise
         */
        private TreePath stringPath2TreePath (String[] sp) {
            ExplorerManager em = ExplorerManager.find (this);
            try {
                Node n = NodeOp.findPath (em.getRootContext (), sp); 
                
                // Create the tree path
                TreeNode tns[] = new TreeNode [sp.length + 1];
                
                for (int i = sp.length; i >= 0; i--) {
                    tns[i] = Visualizer.findVisualizer (n);
                    n = n.getParentNode ();
                }                
                return new TreePath (tns);
            } catch (NodeNotFoundException e) {
                return null;
            }
        }

        void setDynamicDropActions(DnDNodeModel model) {
            dndModelRef = new WeakReference(model);
        }

        void setDynamicDropActions(HyperCompoundModel model) {
            dndModelRef = new WeakReference(model);
        }

        @Override
        protected int getAllowedDropActions(Transferable t) {
            Object model = dndModelRef.get();
            if (model instanceof DnDNodeModel) {
                return ((DnDNodeModel) model).getAllowedDropActions(t);
            } else if (model instanceof HyperCompoundModel) {
                return ((HyperCompoundModel) model).getAllowedDropActions(t);
            } else {
                return super.getAllowedDropActions();
            }
        }

    }
    
    private static final class F8FilterComponentInputMap extends ComponentInputMap {
        
        private KeyStroke f8 = KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0);
        
        public F8FilterComponentInputMap(JComponent component, InputMap imap) {
            super(component);
            setParent(imap);
        }

        @Override
        public Object get(KeyStroke keyStroke) {
            if (f8.equals(keyStroke)) {
                return null;
            } else {
                return super.get(keyStroke);
            }
        }
    }
    
    private static final class F8FilterInputMap extends InputMap {
        
        private KeyStroke f8 = KeyStroke.getKeyStroke(KeyEvent.VK_F8, 0);
        
        public F8FilterInputMap(InputMap imap) {
            setParent(imap);
        }

        @Override
        public Object get(KeyStroke keyStroke) {
            if (f8.equals(keyStroke)) {
                return null;
            } else {
                return super.get(keyStroke);
            }
        }
    }
    
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy