/*
* Copyright 2003-2010 Tufts University  Licensed under the
 * Educational Community 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.osedu.org/licenses/ECL-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 tufts.vue.ui;

import tufts.vue.*;
import tufts.vue.ActiveEvent;
import tufts.vue.MouseAdapter;
import static tufts.vue.LWComponent.Flag.*;
import static tufts.vue.LWComponent.HideCause.*;
import static tufts.vue.LWComponent.ChildKind;
import static tufts.vue.LWComponent.Order;
import tufts.vue.LWMap.Layer;
import tufts.vue.gui.*;

import tufts.Util;
import static tufts.Util.reverse;

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.*;
import javax.swing.border.*;

import edu.tufts.vue.metadata.action.SearchAction;


/**
 * @version $Revision: 1.84 $ / $Date: 2010-02-03 19:16:31 $ / $Author: mike $
 * @author Scott Fraize
 */
public class LayersUI extends tufts.vue.gui.Widget implements LWComponent.Listener, LWSelection.Listener//, ActionListener
{
    private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(LayersUI.class);

    private static final boolean SCROLLABLE = true;

    private final java.util.List<Row> mRows = new java.util.ArrayList();
    private final JPanel mToolbar = new JPanel();
    private final JPanel mRowList = new JPanel();

    private final AbstractButton mShowAll = new JToggleButton(VueResources.getString("botton.layer.showall"));

    // PROBLEM: IF WE ALLOW LAYERS IN THE SELECTION, LWComponent.selctedOrParent will
    // start returning TRUE for anything inside the layer.  This screws up
    // hierarchy actions, delete, duplicate, etc.

//     private final LWSelection mSelection = new LWSelection() {
//             @Override
//             protected boolean isSelectable(LWComponent c) {
//                 return c instanceof LWMap.Layer;
//             }
//             @Override
//             protected void postNotify() { /* do nothing */ }
//             @Override
//             public String toString() {
//                 return "LayerSelection[" + paramString() + "]";
//             }
//         };
    
    private LWMap mMap;
    private boolean isDragUnderway;
    private Row mDragRow;
    private GridBagLayout gridbag = new GridBagLayout();
    private GridBagConstraints gBC = new GridBagConstraints();
    private int selectedIndex = -1;
   
    //private static final Collection<VueAction> _selectionWatchers = new java.util.ArrayList();

    private static final Collection<LayerAction> AllLayerActions = new java.util.ArrayList();
    private JPopupMenu popupMenu = new JPopupMenu();
    private JPopupMenu unlockPopupMenu = new JPopupMenu();
    private JMenuItem unLockMenuItem;
    private JMenuItem renameMenuItem;
    private JMenuItem duplicateMenuItem;
    private JMenuItem lockMenuItem;
    private JMenuItem deleteMenuItem;
    //private class LayerAction extends Actions.LWCAction {
    private class LayerAction extends VueAction {

//         @Override
//         protected Collection<VueAction> getSelectionWatchers() {
//             return _selectionWatchers;
//         }
    
        Layer active;

        @Override public boolean overrideIgnoreAllActions() { return true; }
        
        LayerAction(String name, String tip) {
            //super(name, tip, KeyStroke.getKeyStroke(KeyEvent.VK_L, 0), null);
            super(name, tip, null, null);
            AllLayerActions.add(this);
        }
        @Override
        public void actionPerformed(ActionEvent ae) {
            active = getActiveLayer();
            super.actionPerformed(ae);
        }

//         protected java.util.List<LWComponent> selection() {
//             return null;
//         }
        //@Override boolean enabledFor(LWSelection s) { return true; }
        @Override protected boolean enabled() { return true; }
        @Override
        protected LWSelection selection() {
            return null;
            //return mSelection;
        }

//         // This is overridden only because VueAction.enabledFor is
//         // *package* private, instead of protected, and we're
//         // not going to change that right now, so we need a
//         // new enabled method we can override.
//         @Override
//         protected void updateEnabled(LWSelection selection) {
//             if (selection == null)
//                 setEnabled(false);
//             else
//                 setEnabled(enabledWith(selection));
//         }
//        boolean enabledWith(LWSelection s) { return s.size() > 0; }
        
        boolean enabledWith(Layer layer) { return layer != null; }

        // this is called at the end of each action execution
        @Override
        protected void updateSelectionWatchers() {
            super.updateSelectionWatchers();
            updateLayerActionEnabled(getActiveLayer());
            
        }

        @Override
        public String getUndoName(ActionEvent e, Throwable exception) {
            String name = super.getUndoName(e, exception) + " Layer";
            //if (selection().only() instanceof Layer && (this == LAYER_DUPLICATE || this == LAYER_DELETE))
            if (active != null && (this == LAYER_DUPLICATE || this == LAYER_DELETE))
                name += " " + Util.quote(active.getDisplayLabel());
            //name += " " + Util.quote(selection().only().getDisplayLabel());
            return name;
        }

        
        
    }

    static void updateLayerActionEnabled(Layer layer) {
        for (LayerAction a : AllLayerActions)
            a.setEnabled(a.enabledWith(layer));
    }

    

    private static int NewLayerCount = 1;

//     private final VueAction
//         LAYER_NEW = new VueAction("New", "Create a new layer", null, null) {
//                 public void act() {
//                     mMap.addLayer("New Layer " + NewLayerCount++);
//                 }
//                 @Override
//                 public String getUndoName() { return "New Layer"; }
//             };
        

    private final LayerAction

        LAYER_NEW = new LayerAction(VueResources.getString("layer.new"), VueResources.getString("layer.createnewlayer")) {
                @Override
                boolean enabledWith(Layer layer) {
                    return mMap != null;
                }
                public void act() {                	
                    mMap.addLayer(VueResources.getString("layer.newlayer") + NewLayerCount++);                    
                }
                @Override
                public String getUndoName() { return VueResources.getString("layer.newlayer"); }
            },
        
        
        LAYER_DUPLICATE = new LayerAction(VueResources.getString("layer.duplicate"), VueResources.getString("layer.duplicatelayer")) {
//                 public void act() {
//                     for (LWComponent c : reverse(selection()))
//                         mMap.addChild(c.duplicate());
//                 }
                public void act() {
                    final Layer dupe = (Layer) active.duplicate();
                     mMap.addOnTop(active, dupe);
                    setActiveLayer(dupe); // make the new duplicate layer the active layer
                }
            },
        
        LAYER_DELETE = new LayerAction(VueResources.getString("layer.delete"), VueResources.getString("layer.removeall")) {
                @Override
                boolean enabledWith(Layer layer) {
                    return mRows.size() > 1;
                }
                @Override
                public void act() {
                    if (active.numChildren() > 0) {
                        String message = String.format(Locale.getDefault(),VueResources.getString("layer.delete.label"),
                                                       active.getLabel());
                        if (VueUtil.confirm(null,
                                                          message, VueResources.getString("dialog.title.confirmation"),
                                                           JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION)
                            return;
                    }
                            
                    selectedIndex = active.getIndex();
                    mMap.deleteChildPermanently(active); // todo: LWMap should setActiveLayer(null) if active is deleted
                    mMap.setActiveLayer(null);
                    //setActiveLayer(null, true);
                    attemptAlternativeActiveLayer(true); // better if this tried to find the nearest layer, and not check last-active
                }				           
            },
        
        LAYER_MERGE_DOWN = new LayerAction(VueResources.getString("layer.mergedown"), VueResources.getString("layer.mergeintolayer")) {
//                 @Override
//                 boolean enabledWith(LWSelection s) {
//                     return s.size() == 1
//                         && s.first() != mRows.get(mRows.size()-1).layer;
//                 }
                @Override
                boolean enabledWith(Layer layer) {
                    return layer != null && indexOf(layer) < mRows.size() - 1;
                }
                
                @Override
                public void act() {
                    //final LWComponent mergingDown = selection().first();
                    final LWContainer mergingDown = active;
                    final Layer below = (Layer) mRows.get(indexOf(mergingDown) + 1).layer;
                    below.takeAllChildren(mergingDown);
                    setActiveLayer(below);
                    mMap.deleteChildPermanently(mergingDown);
                }
            },
            
            LAYER_FILTER = new LayerAction(VueResources.getString("layer.filter"), VueResources.getString("layer.hideunselected")) {
            	boolean flg = true;
                @Override
                boolean enabledWith(Layer layer) {
                    return true;
                }
                @Override
                public void act() {                	
                	if(flg){                		
	                	((JButton)mToolbar.getComponent(3)).setBorderPainted(true);
	                	((JButton)mToolbar.getComponent(3)).setBorderPainted(true);
	                	((JButton)mToolbar.getComponent(3)).setContentAreaFilled(false);
	                	((JButton)mToolbar.getComponent(3)).setIcon(VueResources.getImageIcon("layer.filter.on"));
	                	((JButton)mToolbar.getComponent(3)).setBorder(BorderFactory.createEtchedBorder(1));
	                	//super.updateSelectionWatchers();	                	
	                	Layer layer = getActiveLayer();
	                	Row row =null;
	               	 	for (Row rows : mRows){
	               	 		row = rows;
		                     if (rows.layer.equals(layer)){		                    	 
		                    	 row.layer.setVisible(true);
		                     }else{		                    	 
		                    	 row.layer.setVisible(false);
		                     }
	                     }	                    	
	                	
                        //locked.setEnabled(layer.isVisible());                        
                        //label.setEnabled(layer.isVisible()); 
	                	VUE.getSelection().setTo(row.layer.getAllDescendents());
                        VUE.getMainWindow().repaint();
                        if (row.layer == getActiveLayer() && !canBeActive(row.layer))
                            if (AUTO_ADJUST_ACTIVE_LAYER) attemptAlternativeActiveLayer(false);                        
                        
	                	setActiveLayer((Layer) layer, !UPDATE);                	
	                	
	                	flg = !flg;
                	}else{
                		((JButton)mToolbar.getComponent(3)).setBorderPainted(false);
	                	((JButton)mToolbar.getComponent(3)).setContentAreaFilled(false);
	                	((JButton)mToolbar.getComponent(3)).setIcon(VueResources.getImageIcon("layer.filter.off"));
	                	((JButton)mToolbar.getComponent(3)).setRolloverIcon(VueResources.getImageIcon("layer.filter.on"));
	                	active = getActiveLayer();  
	                	Row row =null;
	               	 	for (Row rows : mRows){
	               	 		row = rows;	                              	 
	                        row.layer.setVisible(true);	   
	                        VUE.getSelection().setTo(row.layer.getAllDescendents());
	                     }       
	                	flg = !flg;
                	}
                }
                
            },
            
            LAYER_LOCK = new LayerAction(VueResources.getString("layer.lock"), VueResources.getString("layer.lock")) {
                @Override
                boolean enabledWith(Layer layer) {
                    return true;
                }
                @Override
                public void act() {
                   
                }
            }
                
//         LAYER_MERGE = new LayerAction("Merge", "Merge multiple layers") {
//                 @Override
//                 boolean enabledWith(LWSelection s) { return s.size() > 1; }
//                 @Override
//                 public void act() {

//                     final Layer merged = new LWMap.Layer("Merged");

//                     final ArrayList<LWComponent> mergedChildren = new ArrayList();

//                     for (LWComponent c : reverse(selection())) {
//                         merged.takeAllChildren(c);
//                         mMap.deleteChildPermanently(c);
//                     }

// //                     for (LWComponent c : reverse(selection())) {
// //                         mergedChildren.addAll(c.getChildren());
// //                         if (c instanceof LWContainer)
// //                             ((LWContainer)c).setChildren(null);
// //                         mMap.deleteChildPermanently(c);
// //                         //merged.addChildren(c.getChildren(), LWComponent.ADD_PRESORTED);
// //                         //merged.addAll(c.getChildren());
// //                     }
// //                     merged.setChildren(mergedChildren);
                    
//                     mMap.addChild(merged);
//                 }
//                 @Override
//                 public String getUndoName() { return "Merge Layers"; }
//             },
        
        ;
         
    public LayersUI() {
        super("layers");
        setName("layersUI");   
             
        mToolbar.setName("layersUI.tool");
        mRowList.setName("layersUI.rows");
        
        mRowList.setLayout(new  GridBagLayout());
        mRowList.addMouseListener(RowMouseEnterExitTracker); // doesn't work?
        //To Change the GUI
        
        gBC.fill = GridBagConstraints.HORIZONTAL;
        addButton(LAYER_NEW);
        addButton(LAYER_DUPLICATE);
        //addButton(LAYER_MERGE);
        addButton(LAYER_MERGE_DOWN);
        addButton(LAYER_FILTER);
        //addButton(LAYER_LOCK);
        addButton(LAYER_DELETE);
        gBC.weightx = 0.5;
        gBC.gridx = 1;
        gBC.gridy = 0; 
        JPanel panel = new JPanel(){
        	 public void paintComponent(Graphics g)
        	 {
        		 // call paintComponent to ensure the panel displays correctly
        		 super.paintComponent(g);
        		 g.drawLine(10, 5, 10, 25);         		
        	 }        	
        };  
        panel.setForeground(Color.gray);
        panel.setPreferredSize(new Dimension(10,25));         
        mToolbar.add(panel,gBC);  
//         if (DEBUG.Enabled) {
//             mShowAll.addActionListener(new ActionListener() {
//                     public void actionPerformed(ActionEvent e) {
//                         loadLayers(mMap);
//                     }});
//             addButton(mShowAll);
//             addButton(Actions.Group);
//             addButton(Actions.Undo);
//         }
        mToolbar.setBorder(new SubtleSquareBorder(true));
        add(mToolbar, BorderLayout.NORTH);
        if (SCROLLABLE) {        	
            JScrollPane sp = new JScrollPane(mRowList);
            sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
            //sp.setBorder(null);
            add(sp, BorderLayout.CENTER);
            sp.addMouseListener(RowMouseEnterExitTracker); // doesn't always work
        } else {        	
            //mRowList.setSize(300, 40);
            //mRowList.setMaximumSize(new Dimension(300, 40));
            add(mRowList, BorderLayout.NORTH);
        }

//         mSelection.addListener(new LWSelection.Listener() {
//                 public void selectionChanged(LWSelection s) {
//                     LayerAction.updateSelectionWatchers(_selectionWatchers, s);
//                 }
//                 @Override
//                 public String toString() {
//                     return "Handler for " + _selectionWatchers.size() + " " + LayerAction.class.getSimpleName() + "s";
//                 }
//             });

        
        VUE.addActiveListener(LWMap.class, this);
        VUE.getSelection().addListener(this);
        //VUE.addActiveListener(Layer.class, this);
        //VUE.addActiveListener(LWComponent.class, this);
        //setMinimumSize(new Dimension(300,260));  
        renameMenuItem = new JMenuItem(VueResources.getString("layer.rename"));
        popupMenu.add(renameMenuItem);
        popupMenu.addSeparator();
        renameMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
            	Layer layer = getActiveLayer();            	
            	Row row =null;
            	 for (Row rows : mRows){
                  if (rows.layer.equals(layer))
                	  row = rows;
                 
                 }
            	if (row == null)
            		return;            	    	
            	row.label.setVisible(true);
            	row.label.setEnabled(true);
            	row.label.setEditable(true);
            	row.label.requestFocus();     	
            	
            }
        });
        
        unLockMenuItem = new JMenuItem(VueResources.getString("layer.unlock"));
        unlockPopupMenu.add(unLockMenuItem);        
        unLockMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {            	        	
            	Layer layer = getActiveLayer();
            	Row row =null;
            	 for (Row rows : mRows){
                  if (rows.layer.equals(layer))
                	  row = rows;
                 
                 }
            	if (row == null)
            		return;            	
            	row.locked.setSelected(false);
            	row.locked.setIcon(VueResources.getImageIcon("lockOpen"));         
            	layer.setLocked(false); 
            }
        });
        
        duplicateMenuItem = new JMenuItem(VueResources.getString("layer.duplicate"));        
        popupMenu.add(duplicateMenuItem);
        popupMenu.addSeparator();
        duplicateMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
            	 Layer active = getActiveLayer();
            	 final Layer dupe = (Layer) active.duplicate();
                 mMap.addOnTop(active, dupe);
                 setActiveLayer(dupe); // make the new duplicate layer the active layer
            }
        });
        lockMenuItem = new JMenuItem(VueResources.getString("layer.lock"));
        popupMenu.add(lockMenuItem);
        popupMenu.addSeparator();
        lockMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
            	Layer layer = getActiveLayer();
            	Row row =null;
            	 for (Row rows : mRows){
                  if (rows.layer.equals(layer))
                	  row = rows;                 
                 }
            	if (row == null)
            		return;            	
            	row.locked.setSelected(true);
            	row.locked.setIcon(VueResources.getImageIcon("lock"));           
            	layer.setLocked(true);            	
                if (layer == getActiveLayer() && !canBeActive(layer))
                    if (AUTO_ADJUST_ACTIVE_LAYER) attemptAlternativeActiveLayer(false);
            }
        });        
      
        deleteMenuItem = new JMenuItem(VueResources.getString("layer.delete"));      
        popupMenu.add(deleteMenuItem); 
        
        deleteMenuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) { 
            	deleteMenuItem.setEnabled(true);
            	// Modal dialog with OK button
                String message = VueResources.getString("dialog.deletelayer.message");               
                if (VueUtil.confirm(null,
                		message, VueResources.getString("dialog.title.confirmation"),
                        JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION){                
                	Layer active = getActiveLayer();
                	if(!active.isLocked()){
    	            	selectedIndex = active.getIndex();
    	            	mMap.deleteChildPermanently(active); // todo: LWMap should setActiveLayer(null) if active is deleted
    	            	mMap.setActiveLayer(null);                
    	                attemptAlternativeActiveLayer(true); // better if this tried to find the nearest layer, and not check last-active
    	            }
                	if(mRows.size() == 1){
                    	deleteMenuItem.setEnabled(false) ;
                	}
                }            	
            }
        });
    }

    private void addButton(Action a) {
        addButton(new JButton(a));
    }
    private void addButton(AbstractButton b) {
        if (b instanceof JToggleButton)
            ;//b.putClientProperty("JButton.buttonType", "roundRect"); // for Mac Leopard Java
        else
        b.putClientProperty("JButton.buttonType", "textured"); // for Mac Leopard Java        
        
//        Font defaultFont = getFont();
//        Font boldFont = defaultFont.deriveFont(Font.BOLD);
//        Font smallFont = defaultFont.deriveFont((float) boldFont.getSize()-2);
//        b.setFont(smallFont);
        b.setFont(tufts.vue.gui.GUI.LabelFace);
        b.setFocusable(false);
        if(b.getAction() == null) {
            ;
        } else if(b.getAction().equals(LAYER_NEW)){
        	
        	b.setText(VueResources.getString("layersui.newlayer.tooltip"));        	
        	b.setIcon(tufts.vue.VueResources.getImageIcon("metadata.editor.add.up"));
        	b.setRolloverEnabled(true);
        	b.setPreferredSize(new Dimension(90,30));
        	//b.setMinimumSize(new Dimension(90,30));
        	b.setRolloverIcon(VueResources.getImageIcon("metadata.editor.add.down"));
        	b.setBorder(BorderFactory.createEmptyBorder(2,5,2,2));        	
        	b.setHorizontalAlignment(JButton.LEADING); // optional
        	b.setBorderPainted(false);
        	b.setContentAreaFilled(false);
        	gBC.weightx = 0.5;
            gBC.gridx = 0;
            gBC.gridy = 0; 
        }else if(b.getAction().equals(LAYER_DUPLICATE)){    
        	b.setText("");
        	b.setIcon(tufts.vue.VueResources.getImageIcon("layer.duplicate.add"));
        	b.setRolloverEnabled(true);
        	b.setRolloverIcon(VueResources.getImageIcon("layer.duplicate.add.ov"));
        	//b.setBorder(BorderFactory.createEmptyBorder(2,5,2,30));
        	//b.setHorizontalAlignment(JButton.LEADING); // optional
        	b.setBorderPainted(false);
        	b.setContentAreaFilled(false);
        	gBC.weightx = 0.5;
            gBC.gridx = 2;
            gBC.gridy = 0; 
        }else if(b.getAction().equals(LAYER_MERGE_DOWN)){
        	b.setText("");
        	b.setIcon(tufts.vue.VueResources.getImageIcon("layer.merge.add"));
        	b.setRolloverEnabled(true);
        	b.setRolloverIcon(VueResources.getImageIcon("layer.merge.add.ov"));
        	b.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
        	//b.setHorizontalAlignment(JButton.LEADING); // optional
        	b.setBorderPainted(false);
        	b.setContentAreaFilled(false);
        	gBC.weightx = 0.5;
        	gBC.gridx = 3;
            gBC.gridy = 0;
        }else if(b.getAction().equals(LAYER_DELETE)){
        	b.setText("");
        	b.setIcon(tufts.vue.VueResources.getImageIcon("ontologicalmembership.delete.up"));
        	b.setRolloverEnabled(true);
        	b.setRolloverIcon(VueResources.getImageIcon("ontologicalmembership.delete.down"));
        	b.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
        	//b.setHorizontalAlignment(JButton.LEADING); // optional
        	b.setBorderPainted(false);
        	b.setContentAreaFilled(false);
        	gBC.gridx = 6;
            gBC.gridy = 0;
        }else if(b.getAction().equals(LAYER_FILTER)){        	
        	b.setText("");
        	b.setIcon(tufts.vue.VueResources.getImageIcon("layer.filter.off"));
        	b.setRolloverEnabled(true);
        	b.setRolloverIcon(VueResources.getImageIcon("layer.filter.on"));
        	b.setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
        	//b.setHorizontalAlignment(JButton.LEADING); // optional
        	b.setBorderPainted(false);
        	b.setContentAreaFilled(false);
        	gBC.gridx = 4;
            gBC.gridy = 0;
        }/*else if(b.getAction().equals(LAYER_LOCK)){
        	b.setText("");
        	b.setIcon(tufts.vue.VueResources.getImageIcon("lockOpen"));
        	b.setRolloverEnabled(true);
        	b.setRolloverIcon(VueResources.getImageIcon("lock"));
        	b.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
        	//b.setHorizontalAlignment(JButton.LEADING); // optional
        	b.setBorderPainted(false);
        	b.setContentAreaFilled(false);
        	gBC.gridx = 5;
            gBC.gridy = 0;
        }  */      
        mToolbar.setLayout(gridbag);
        mToolbar.add(b,gBC);        

        //b.addActionListener(this);
    }

    @Override
    public void addNotify() {
        super.addNotify();
        //setMinimumSize(new Dimension(400,120+mToolbar.getHeight()));
        SwingUtilities.getWindowAncestor(this).addMouseListener(RowMouseEnterExitTracker);
    }

    public void activeChanged(ActiveEvent e, LWMap map) {
        loadMap(map);
    }

//     public void activeChanged(ActiveEvent e, Layer layer) {
//         // used to just call indic
//     }

//     public void activeChanged(ActiveEvent e, LWComponent c) {
//         enableForSingleSelection(c);
//         // for debug / child-list mode:
//         if (mMap != null && !mMap.isLayered())
//             indicateActiveLayers(null);
//     }

    private static final boolean UPDATE = true;
    
    private void setActiveLayer(Layer c) {
        setActiveLayer(c, !UPDATE);
    }

    private void setActiveLayer(final Layer layer, boolean update) {
        if (DEBUG.EVENTS) Log.debug("SET-ACTIVE: " + layer);
        if (layer != null)
            mMap.setClientData(Layer.class, "last", mMap.getActiveLayer());
        mMap.setActiveLayer(layer);        
        if (update)
            indicateActiveLayers(null);
        else{
        	LWSelection selection = VUE.getSelection(); 
        	indicateActiveLayers(selection.getParents());
        }
        updateLayerActionEnabled(layer);

//         if (DEBUG.Enabled) {
//             GUI.invokeAfterAWT(new Runnable() { public void run() {
//                 VUE.setActive(Layer.class, LayersUI.this, layer);
//             }});
//         }
    }

    private boolean canBeActive(LWComponent layer) {
        return canBeActive(layer, true);
    }
    private boolean canBeActive(LWComponent layer, boolean checkLocking) {
        if (layer == null || layer.isHidden() || layer.isDeleted())
            return false;
        else if (checkLocking && layer.isLocked() && mRows.size() > 1)
            return false;
        else
            return layer instanceof Layer;
    }

    private static final boolean AUTO_ADJUST_ACTIVE_LAYER = false;
    
    private void attemptAlternativeActiveLayer(boolean isDeleteFlg) {

        //if (!AUTO_ADJUST_ACTIVE_LAYER) return;
    	Layer lastActive = null;
        final Layer curActive = getActiveLayer();
        
        if(!isDeleteFlg){
        	lastActive = mMap.getClientData(Layer.class, "last"); 
        }else{
        	if(selectedIndex > 0)
        		lastActive = (Layer) mMap.getChild(selectedIndex-1);
        	else{
        		lastActive = mMap.getClientData(Layer.class, "last"); 
        	}
        }
       
        if (canBeActive(lastActive)) {        	       	
            setActiveLayer(lastActive, UPDATE);
            return;
        }

        LWComponent fullyOpen = null;
        
        // find the top-most visible and unlocked layer:
        for (Row row : mRows)
            if (canBeActive(row.layer))
                fullyOpen = row.layer;

        if (fullyOpen != null) {
            setActiveLayer((Layer) fullyOpen, UPDATE);
            return;
        }
        
        LWComponent visibleButLocked = null;
        
        // find the top-most visible layer:
        for (Row row : mRows)
            if (canBeActive(row.layer, false))
                visibleButLocked = row.layer;

        if (visibleButLocked != null && (curActive == null || (curActive.isHidden() && curActive.isLocked()))) {
            // only switch to visible but locked if the current active is actually worse off
            setActiveLayer((Layer) visibleButLocked, UPDATE);
        }
    }


    private Layer getActiveLayer() {
        if (mMap == null) {
            Log.warn("getActiveLayer when map is null");
            return null;
        } else {
            return mMap.getActiveLayer();
        }
    }
    
    public void selectionChanged(LWSelection s) {

//        System.err.println("selectionChanged: " + s + "; size=" + s.size() + "; " + Arrays.asList(s.toArray()) + "; parents=" + s.getParents());

        updateGrabEnabledForSelection(s);

//         if (!s.getParents().contains(mMap.getActiveLayer()))
//             for (LWComponent c : s.getParents())
//                 if (c instanceof Layer)
//                     mMap.setActiveLayer(c);

        if (s.getParents().size() == 1) {
            LWMap.Layer layer = s.first().getLayer();
            if (layer == null) {
                Log.info("ignoring null layer in single selection parent (presumably not a map-member yet)");
            } else {
                setActiveLayer(layer);
            }
        }


//         if (s.size() == 1 && s.first().getLayer() != null) {
//             //if (DEBUG.Enabled) Log.debug("selectionChanged: single selection; activate layer of: " + s.first());
//             setActiveLayer(s.first().getLayer());
//         } else if (s.getParents().size() == 1 && s.first().getParent() instanceof Layer) {
//             //if (DEBUG.Enabled) Log.debug("selectionChanged: one parent in selection; active parent " + s.first().getParent());
//             setActiveLayer((Layer) s.first().getParent());//         }
        
        indicateActiveLayers(s.getParents());

//         // for debug / child-list mode:
//         if (mMap != null && !mMap.isLayered())
//             indicateActiveLayer(null);
    }

    
    private void loadMap(final LWMap map)
    {
        if (DEBUG.EVENTS) Log.debug("load map " + map);
             
        if (mMap == map)
            return;

        if (mMap != null)
            mMap.removeLWCListener(this);
        
        mMap = map;

        //setActiveLayer(map.getActiveLayer());
        loadLayers(map);

        if (map != null) {
            // todo: we should be able to just listen for LWKey.HierarchyChanged, tho
            // this currently is only generated on UNDO's, and hardly anything is
            // currently listenting for it (OutlineViewTree, and some references to "hier.*)
            map.addLWCListener(this);
        }
    }

    private boolean mLayerReloadRequired = false;

    public void LWCChanged(LWCEvent e) {

        //if (DEBUG.EVENTS) Log.debug("handling " + e + "; source=" + e.getSource());

        // ignore events from children: just want hierarchy events directly from the map
        // (as we're only interested in changes to map layers)
        
        if (e.key == LWKey.UserActionCompleted) {
            if (mLayerReloadRequired)
                loadLayers(mMap);
            else
                repaint(); // repaint the previews
            mLayerReloadRequired = false;
        }
        else if (e.getSource() == mMap || mShowAll.isSelected()) {
            // any hierarcy event on the map itself must involve layers
            if (e.getName().startsWith("hier.")) {
                if (e.getName().startsWith("hier.move.")) {
                    // this immediate reload is required for UI to update during drags
                    // of layer rows -- could also condition this based on the mouse
                    // being held down on a Row as opposed to know this involves
                    // hier.move.forward/backward events.
                    loadLayers(mMap);
                } else {
                    mLayerReloadRequired = true;
                    if (DEBUG.EVENTS) Log.debug("TAGGED FOR RELOAD on " + e.key);
                }
            }
        }
        else if (e.key == LWKey.Deleting && e.getComponent() instanceof Layer) {
            // failsafe only: should already have been handled by above "hier." case
            mLayerReloadRequired = true;
            if (DEBUG.EVENTS) Log.debug("TAGGED FOR RELOAD on " + e.key);
        }
    }
    
    private final Color AlphaWhite = new Color(255,255,255,128);
    
    private static final Color ActiveBG = VueResources.getColor("layerUI.activeLayer.background", Color.blue);
    
    private final Color IncludedBG = Util.alphaMix(AlphaWhite, VueConstants.COLOR_SELECTION);
    //private final Color IncludedBG = VueConstants.COLOR_SELECTION.darker();
    //private final Color IncludedBG = new Color(128,128,255,128);
    
    private final Color SelectedBG = VueConstants.COLOR_SELECTION.brighter();
    
    private void indicateActiveLayers(Collection<LWContainer> parents) {
    	
        final Layer activeLayer = getActiveLayer();
        
        if (parents == null) {

            // update the active layer indication based on a change
            // in the active layer -- not a change in a selection
            // this will see to it that only one layer is indicated,
            // and all other layers are not.

            for (Row row : mRows) {
                //row.activeIcon.setEnabled(row.layer == activeLayer);               
                if (row.layer.isSelected() || row.layer == activeLayer){                	
                //if (row.layer.isSelected())
                    row.setBackground(row.layer instanceof Layer ? ActiveBG : SelectedBG);                    
                }
                else 
                    row.setBackground(null);
                
//                if (row.layer == activeLayer) {
//                    row.setBorder(BorderFactory.createLineBorder(Color.red, 2));
//                }
//                else {
//                    row.setBorder(new CompoundBorder(new MatteBorder(1,0,1,0, Color.lightGray),
//                                                     GUI.makeSpace(3,7,3,7)));
//                }
                
            }

        } else {


            // update the active layer indication based on a change
            // in the selection (hilite *any* layers found in the selection)

            final Set<Layer> layersInSelection = new HashSet(parents.size());
            
            // on empty selection, parents will be empty
            
            for (LWComponent c : parents)
                layersInSelection.add(c.getLayer());
            
            for (Row row : mRows) {

                //row.activeIcon.setEnabled(row.layer == activeLayer);
                
                if (row.layer instanceof Layer) {
                    if (layersInSelection.contains(row.layer)) {
                        //row.layer.setSelected(true);
                        if (row.layer == activeLayer){
                            row.setBackground(ActiveBG);                              
                            if(layersInSelection.size()>1){
                            	row.setBorder(BorderFactory.createLineBorder(Color.blue, 2));
                            }
                        }
                        else{
                            row.setBackground(IncludedBG);
                            row.setBorder(new CompoundBorder(new MatteBorder(1,1,1,1, Color.lightGray),
                                    GUI.makeSpace(3,7,3,7)));
                            //row.setBorder(null);
                        }
                    } else {
                        //row.layer.setSelected(false);
                        if (row.layer == activeLayer){
                            row.setBackground(ActiveBG);                                                    
                            if(layersInSelection.size()>1){
                            	row.setBorder(BorderFactory.createLineBorder(Color.blue, 2));
                            }else{
                            	row.setBorder(new CompoundBorder(new MatteBorder(1,1,1,1, Color.lightGray),
                                        GUI.makeSpace(3,7,3,7)));
                            }
                        }
                        else{
                            row.setBackground(null);
                            row.setBorder(new CompoundBorder(new MatteBorder(1,1,1,1, Color.lightGray),
                                    GUI.makeSpace(3,7,3,7)));
                            //row.setBorder(null);
                        }
                    }
                    
                } else if (row.layer.isSelected()) {
                    row.setBackground(SelectedBG);
                } else{
                    row.setBackground(null);
                }
            }
            
            
        }
        
    }

    private void updateGrabEnabledForSelection(LWSelection s) {

        final Collection<LWContainer> parents = s.getParents();
        //final LWContainer parent0 = parents.isEmpty() ? null : parents.iterator().next();

        boolean disable =
            s.size() < 1
            || s.only() instanceof Layer;

        // todo: to be more precise, could always accumme related parets

        if (!disable) {
            boolean canExtract = false;
            for (LWContainer parent : parents) {
                if (isExtractableParent(parent)) {
                    canExtract = true;
                    break;
                }
            }
            disable = !canExtract;
        }
        
        for (Row row : mRows) {
            if (row.grab == null)
                continue;
            if (disable) {
                row.grab.setEnabled(false);
            } else if (parents.size() == 1 && parents.contains(row.layer)) {
                //Log.debug("DISABLE GRAB IN " + row);
                row.grab.setEnabled(false);
            } else  {
                //Log.debug(" ENABLE GRAB IN " + row);
                row.grab.setEnabled(true);
            }
        }
    }

    private static boolean isExtractableParent(LWContainer parent) {

        return parent instanceof Layer
            || parent instanceof LWMap; // shouldn't happen, but just in case of up-leakage
        
//         if (parent instanceof LWGroup)
//             return false;
//         else if (parent instanceof LWNode)
//             return false;
//         else if (parent instanceof LWSlide)
//             return false;
//         else
//             return true;
    }

    private static boolean layerCanGrab(Layer layer, LWComponent c) {

        final LWContainer parent = c.getParent();

        if (parent == layer)
            return false;
        else
            return isExtractableParent(parent);
        
    }
    
    private void grabFromSelection(Layer layer) {
        final LWSelection selection = VUE.getSelection();
        
        final java.util.List grabbing = new ArrayList();
        
        for (LWComponent c : selection) {
            if (layerCanGrab(layer, c))
                grabbing.add(c);
        }
      
        // todo perf: remove all old layer in one swoop, then add to new
        
        layer.addChildren(grabbing);

        selection.resetStatistics();

        selectionChanged(selection);

        //indicateActiveLayers(selection.getParents());
    }
    

    private void loadLayers(final LWMap map) {

        if (DEBUG.EVENTS) Log.debug("RELOADING LAYERS IN " + map);

        mRows.clear();
        
        if (map != null) {
            
            // handle in reverse order (top layer on top)
            for (LWComponent layer : reverse(map.getChildren())) {
                mRows.add(produceRow(layer));
                if (mShowAll.isSelected()) {
                    //for (LWComponent c : reverse(layer.getChildren()))
                    for (LWComponent c : reverse(layer.getAllDescendents(ChildKind.PROPER, new ArrayList<LWComponent>(), Order.DEPTH)))
                        mRows.add(produceRow(c));
                }
            }

            updateLayerActionEnabled(map.getActiveLayer());
        } else {
            updateLayerActionEnabled(null);
        }
        
        if (!isDragUnderway) {
            updateGrabEnabledForSelection(VUE.getSelection());
            indicateActiveLayers(null);
        }
        
        layoutRows();

        if (getActiveLayer() != null && getActiveLayer().isDeleted()) {
            mMap.setActiveLayer(null);
            attemptAlternativeActiveLayer(true);
        }
    }

    private void layoutRows() {
        layoutRows(mRowList, mRows);
    }
        
    private void layoutRows(final JComponent container, final java.util.List<? extends JComponent> rows)
    {
        GridBagConstraints c = new GridBagConstraints();
        c.weighty = 0; // 1 has all expanding to fill vertical, 0 leaves all at min height
        c.weightx = 1;
        c.gridheight = 1;
        c.gridwidth = 1;
        //c.gridwidth = GridBagConstraints.REMAINDER;
        //c.fill = GridBagConstraints.HORIZONTAL;
        c.fill = GridBagConstraints.HORIZONTAL;
        c.anchor = GridBagConstraints.NORTH;
        c.gridx = 0;
        c.gridy = 0;
        
        // Each Row has a top and a bottom border line, so that
        // one is always visible no matter what, but we normally only
        // want to see a single line, so this will let them
        // overlap during standard display (e.g., but not when drag-reordering)
        c.insets = new Insets(0,0,-1,0);

        container.removeAll();

        if (!rows.isEmpty()) {  
            
            for (JComponent row : rows) {            	
                c.insets.left = (((Row)row).layer.getDepth() - 1) * 56; // refactoring: note Row cast
                row.setOpaque(true);
                container.add(row, c);
                c.gridy++;
            }
        
            if (c.weighty == 0) {
                // now add a default vertical expander so the rest of items stay at the top
                c.weighty = 1;
                c.gridy = rows.size() + 1;
                container.add(new JPanel(), c);
            }
        }       
        
        // will property event or DockWindow API: DockWindow controls this, and only polls it on init
        //setMinimumSize(new Dimension(400,40*rows.size())); 
        container.revalidate(); // needed for Tiger (uneeded on Leopard)
        ////if (isVisible()) SwingUtilities.getWindowAncestor(this).pack();
        container.repaint();
    }

    private Row produceRow(final LWComponent layer)
    {
        Row row = layer.getClientData(Row.class);
        if (row != null) {
            return row;
        } else {
            row = new Row(layer);
            layer.setClientData(Row.class, row);
            return row;
        }
    }

    private int indexOf(final LWComponent layer) {
        int i = 0;
        for (Row row : mRows) {
            if (row.layer == layer)
                return i;
            i++;
        }
        return -1;
    }
    

//     private boolean inExclusiveMode() {
//         return fetchExclusiveRow() != null;
//     }

    private Row fetchExclusiveRow() {
        return mMap.getClientData(Row.class, "exclusive");
    }
    private void storeExclusiveRow(Row row) {
        mMap.setClientData(Row.class, "exclusive", row);
    }

    private Layer fetchPreExclusiveLayer() {
        return mMap.getClientData(Layer.class, "pre-exclusive");
    }
    private void storePreExclusiveLayer(Layer layer) {
        mMap.setClientData(Layer.class, "pre-exclusive", layer);
    }
    
    // TODO: if a row is deleted while in exclusive mode, you won't
    // be able to access it's controls if it's un-deleted when NOT
    // in exclusive mode -- you'll need to enter/exit exlusive mode
    // to re-gain access (to visible/lock buttons)

    private void setExclusiveMode(boolean entering, Row exclusiveRow)
    {
        //Log.debug("SET-EXCLUSIVE-MODE " + entering + "; nowExclusive=" + exclusiveRow);
        
        if (entering)
            storePreExclusiveLayer(getActiveLayer());

        for (Row row : mRows) {

            final LWComponent layer = row.layer;

            if (entering) {
                //layer.setFlag(WAS_LOCKED, layer.isLocked());
                //layer.setFlag(WAS_HIDDEN, layer.isHidden(DEFAULT));
            } else {
                layer.setLocked(row.locked.isSelected());
                layer.setHidden(DEFAULT, !row.visible.isSelected());
                //layer.setLocked(layer.hasFlag(WAS_LOCKED));
                //layer.setHidden(DEFAULT, layer.hasFlag(WAS_HIDDEN));
                //layer.clearFlag(WAS_LOCKED);
                //layer.clearFlag(WAS_HIDDEN);
            }

            row.visible.setEnabled(!entering);
            row.locked.setEnabled(!entering);

            if (row == exclusiveRow)
                continue;

            row.label.setEnabled(entering ? false : row.visible.isSelected());
                
            layer.setHidden(LAYER_EXCLUDED, entering);
        }

        if (!entering) {
            setActiveLayer(fetchPreExclusiveLayer(), true);
            storeExclusiveRow(null);
            storePreExclusiveLayer(null);
        }

    }
        
        

    private static class TextEdit extends JTextField implements FocusListener {

        static final boolean TransparentHack = Util.isMacLeopard();

        static final Color Transparent = new Color(0,0,0,0);

        static final Dimension MaxSize = new Dimension(Short.MAX_VALUE, 30);
        // we'd like to use a shorter max-size to allow more room for the layer
        // if you drag the window very big instead of the text-box, but
        // this doesn't work with our hack that allows the variable width
        // info field to not throw off row-to-row column alignment.
        //static final Dimension MaxSize = new Dimension(200, 30);

        // todo perf: these could be static
        final Border activeBorder;
        final Border inactiveBorder;

        final Row row;
        
        public TextEdit(final Row row) {
            this.row = row;            
            setDragEnabled(false);
            setPreferredSize(MaxSize);
            setMaximumSize(MaxSize);
            
            setText(row.layer.getDisplayLabel());
            setToolTipText(row.layer.getDisplayLabel());
            enableEvents(AWTEvent.MOUSE_EVENT_MASK);
            enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);            
            addFocusListener(this);
            addKeyListener(new KeyAdapter() {
                    public void keyPressed(KeyEvent e) {
                        if (DEBUG.KEYS) Log.debug("KEY " + e);
                        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                            //focusLost(null); // will be called again on actual focus-loss; could call setEditable(false);
                            setEditable(false); // rely's on focusLost being generated
                        }
                    }
                });

            row.layer.addLWCListener(new LWComponent.Listener() {
                    public void LWCChanged(LWCEvent e) {

                        final String text = row.layer.getDisplayLabel();

                        if (text.equals(getText())) {
                            
                            // be sure to skip the setText if the text is the same:
                            // setting a text component to the same value tends to
                            // trigger a java bug where setScrollOffset(0) stops
                            // working, and the left-most text stays obscured if the
                            // text is wider than the display area.
                            
                            return;
                        }                        
                        setText(text);
                        setScrollOffset(0);
                        // also do this later just in case: somes helps, tho
                        // Vista and XP still seem to have a hard time with this bug,
                        // but generally only if the model text is set elsewhere,
                        // and we're just handling a value update here.
                        GUI.invokeAfterAWT(new Runnable() { public void run() {
                            setScrollOffset(0);
                        }});
                    }},
                LWKey.Label);

            activeBorder = getBorder();
//             Insets insets = activeBorder.getBorderInsets(this);
//             if (Util.isMacLeopard()) {
//                 // sometimes this is wrong... would be safer to do this in addNotify
//                 insets.left -= 3;
//                 insets.right -= 3;
//             }
//             inactiveBorder = GUI.makeSpace(insets);
            inactiveBorder = GUI.makeSpace(activeBorder.getBorderInsets(this));

            //setOpaque(true);
            setEditable(false);
        }

        private boolean isConstructed() {
            return activeBorder != null;
        }
        
        @Override
        public void setEnabled(boolean enabled) {
            setForeground(enabled ? Color.black : Color.gray);
        }

        @Override
        public void setEditable(boolean edit) {
            if (isConstructed()) 
                makeEditable(edit); // don't do this during default JTextComponent init
            super.setEditable(edit);
        }
        
        public void focusGained(FocusEvent e) {}                    
        public void focusLost(FocusEvent e) {
            setEditable(false);
            setScrollOffset(0);
            row.layer.setLabel(getText().trim());
            // make sure if text is longer than fits into field, we scroll back to 0 at the left
            row.layer.getMap().getUndoManager().mark();
        }

        private void makeEditable(boolean edit) {
            if (DEBUG.WORK) Log.debug("MAKE EDITABLE " + Util.tags(this) + " " + edit);
            if (edit) {            	
                setFocusable(true);
                setBorder(activeBorder);
                setBackground(Color.white);
                if (!TransparentHack)
                    setOpaque(true);
            } else {            	
                setFocusable(false);
                setBorder(inactiveBorder);
                if (TransparentHack) {
                    setBackground(Transparent);
                } else {
                    setBackground(null);
                    setOpaque(false);
                }
            }
        }

//         @Override
//         public void addNotify() {
//             // horizontal mouse-draggs across a non-edit mode label draw's some selection or
//             // repaints chars with semi-transpareng BG, leading to blocky artifacts -- if this is
//             // caret/hilighter, we should be able to turn it off or change the color, but having
//             // had no success in that, it may just be a repaint issue much harder to fix (e.g.,
//             // sometimes the text itself appears to "bolden" as it repaints)
//             super.addNotify();
//             //getCaret().setVisible(false);
//             //getCaret().setSelectionVisible(false);
//             setSelectionColor(Color.red);
//             setHighlighter(null);
//             //setCaret(null); // we'll get NPE
//         }
        
        @Override
        protected void processEvent(AWTEvent e) {

            // this form of delegation is much simpler than passing everything through
            // our own mouse / mouse motion listeners, and has the added benefit of
            // preventing horizontal cross-text drags from causing the blocky
            // character-level repaint bug (when the background is semi-transparent)
            
            if (!isEditable() && e instanceof MouseEvent) {
                if (e.getID() == MouseEvent.MOUSE_CLICKED) {
                    mouseClicked((MouseEvent)e);
                } else {
                    // This allows mouse press/release/drag events to 
                    // be passed on to the Row as if they happened there.
                    // The coordinate system will be different, but
                    // Row.mouseDragged really only needs the relatve
                    // event-to-event deltas.
                    row.processEventUp(e);
                }
                
            } else
                super.processEvent(e);
        }

        private void mouseClicked(MouseEvent e)
        {
            if (GUI.isDoubleClick(e)) {
                if (DEBUG.MOUSE) Log.debug("DOUBLE CLICK " + this);                
                setEditable(true);
                requestFocus();
            }
        }        

        public String toString() {
            return "TextEdit[" + row + "]";
        }
    }

    private static class Preview extends JPanel {

        final LWComponent layer;

        Preview(LWComponent c) {
            layer = c;
        }
        
        @Override
        public void paintComponent(Graphics _g) {

            //final DrawContext dc = new DrawContext(DEBUG.BOXES ? g.create() : g, layer);
            final DrawContext dc = new DrawContext(_g.create(), layer);
            dc.setDraftQuality();
            dc.setAntiAlias(true);
            dc.setInteractive(false);
                        
            if (layer instanceof Layer == false) {
                //dc.fillBackground(Color.white);
                layer.drawFit(dc, 0);
                return;
            }

            //System.out.println("bounds: " + Util.fmt(getBounds()));
            //System.out.println("  clip: " + Util.fmt(g.getClipRect()));
            
//             if (layer.isVisible()) {
//                 //g.setColor(Color.yellow);
//                 g.setColor(layer.getMap().getFillColor());
//                 ((Graphics2D)g).fill(g.getClipRect());
//             }
                        
            final Rectangle frame = getBounds();

            frame.x = frame.y = 0; // our GC is already offset to Component.getX/getY
            frame.grow(-1, -1);
            //frame.grow(-1, -4); // leave a vertical gap, and a bit of horiz room to prevent clipping at right
                        
            final Point2D.Float offset = new Point2D.Float();
            final Size size = new Size(frame);

            final Rectangle2D.Float allLayerBounds = new Rectangle2D.Float();

            // todo: would be nice if layers cached all their children bounds
            // -- LWMap should be using code for same

            for (LWComponent l : layer.getMap().getChildren())
                LWMap.accruePaintBounds(l.getChildren(), allLayerBounds);
                        
            final double zoom = tufts.vue.ZoomTool
                .computeZoomFit(size,
                                0,
                                allLayerBounds,
                                offset,
                                0.5); // max zoom for preview is 50%
                        
            dc.g.translate(-offset.x + frame.x, -offset.y + frame.y);
            dc.g.scale(zoom, zoom);
            dc.setClipOptimized(false);
            layer.drawZero(dc);

            if (DEBUG.BOXES) {

                // Would be nice if computeZoomFit could also set for us a used
                // viewport size, so we wouldn't have to draw this in the scaled
                // down GC, and it would be easier to create insets.
                dc.setAbsoluteStroke(1);
                //dc.g.setColor(Color.blue);
                //dc.g.draw(allLayerBounds);
                dc.g.setColor(Color.red);
                //Util.grow(allLayerBounds, 5 / (float) zoom);
                //Util.grow(allLayerBounds, 5);
                //allLayerBounds.width -= 1/zoom;
                dc.g.draw(allLayerBounds);
            }
                        

            if (DEBUG.BOXES) {
                final Graphics2D g = (Graphics2D) _g;
                g.setColor(Color.lightGray);
                Rectangle r = getBounds();
                g.drawRect(0,0, r.width-1, r.height-1);
            }

            //g.draw(new Rectangle2D.Float(offset.x,offset.y, allLayerBounds.width, allLayerBounds.height));
                        
            //((Graphics2D)g).draw(frame);
            //((LWContainer)layer).drawChildren(dc);
        }
    }

    private static final Insets LockedInsets = new Insets(4,4,4,0);
    private static final Dimension LayerHeight = new Dimension(0, 38);
    private static final Dimension DefaultHeight = new Dimension(0, 28);

//     private static class MouseTracker extends tufts.vue.MouseAdapter implements Runnable
//     {
//         boolean entered;
//         Row overRow;

//         MouseTracker() {
//             super("layer.row.mouse-tracker");
//         }
        
//         public void mouseEntered(MouseEvent e) {
//             entered = true;
//         }
//         public void mouseExited(MouseEvent e) {
//             entered = false;
//         }

//         // called when mouse-entered happens on Row container
//         public void setRow(Row r) {
//             if (overRow != null && overRow != r)
//                 overRow.rollOff();
//             overRow = r;
//         }
        
//         // called when mouse-exited happens on the Row container
//         public void run() {
//             if (!entered) {
//                 if (DEBUG.FOCUS) Log.debug("MOUSE-TRACKER: no child entered, rolling off for real");
//                 overRow.rollOff();
//             } else {
//                 if (DEBUG.FOCUS) Log.debug("MOUSE-TRACKER: a child was entered, do not roll off row");
//             }
//         }
//     }

    /**
     * Fail-safe mouse enter/exit tracker for Row's.  Only "exits" a Row when a new one
     * is entered, or a parent of all the Rows is exited -- otherwise, if we just rely
     * on standard events, when any child of a Row is mouse-entered, the Row itself is
     * exited, yet this isn't actually rolling-off the row.
     */
    
    private static class MouseTracker extends tufts.vue.MouseAdapter implements Runnable
    {
        Row overRow;

        MouseTracker() {
            super("layer.row.mouse-tracker");
        }
        
        /** MOUSE_ENTERED events on Rows should be forwarded here */
        public void recordMouseEntered(Row row, MouseEvent e) {
            setRow(row);
        }

        public void mouseExited(MouseEvent e) {
            
            // any potential parent of a Row, that could possibly get a mouseExited
            // event, should be re-routed here.  MOUSE_EXITED events can appear very
            // unreliably, so as many parents as possible should be tracking for these
            // events in the hope that at least one of them will get the event, even
            // when the mouse is moving fast.  Even then, we can still miss some.  It
            // appears we may even need to check for WINDOW_LOST_FOCUS events on the top
            // level window for more reliability -- and we must create a listener --
            // there appears to be no Window state we can reliably poll for this!
            
            setRow(null);
        }

        // called when mouse-entered happens on Row container
        void setRow(Row r) {
            if (overRow != null && overRow != r)
                overRow.rollOff();
            overRow = r;
            if (overRow != null)
                overRow.rollOn();
        }

        public void run() {

            // todo: only real fail-safe method of handling this will be to reset a
            // timer each we get here (Row MOUSE_EXITED has happened), and then if after
            // 500ms or so, another Row hasn't been entered, we can roll off the last
            // row.  Tho we should be able to cancel the timer right off and do nothing
            // if overRow has changed since the check was scheduled in the AWT event
            // queue (e.g., right after MOUSE_EXITED, a MOUSE_ENTERED happened on
            // another row, so overRow is different, and until we see another Row
            // MOUSE_EXITED, we don't need the failsafe timer).
            
//             if (overRow != null) {
//                 Window parent = SwingUtilities.getWindowAncestor(overRow);
//                 if (!parent.isFocused()) {
//                     if (DEBUG.FOCUS) Log.debug("rolling off last row as window has lost focus");
//                     setRow(null);
//                 } else if (DEBUG.FOCUS) {
//                     if (DEBUG.FOCUS) Log.debug("parent still focused: " + GUI.name(parent));
//                 }
//             }
        }
    }
    

    private final MouseTracker RowMouseEnterExitTracker = new MouseTracker();
    

    private class Row extends JPanel implements javax.swing.event.MouseInputListener, Runnable {

        final AbstractButton exclusive;
        final AbstractButton visible = new JCheckBox();
        final AbstractButton locked = new JRadioButton();
        //final JLabel activeIcon = new JLabel();

        final JTextField label;
        final JPanel preview;
        final AbstractButton grab;
        
        final LWComponent layer;

        final Color defaultBackground;

        
        Row(final LWComponent layer)
        {
            this.layer = layer;
            label = new TextEdit(this);
            setToolTipText(label.getText());
            setName(layer.toString());
            setLayout(new GridBagLayout());
            setBorder(new CompoundBorder(new MatteBorder(1,0,1,0, Color.lightGray),
                                         GUI.makeSpace(3,7,3,0)));
            if (SCROLLABLE) {
                if (layer instanceof Layer)
                    setPreferredSize(LayerHeight);
                else
                    setPreferredSize(DefaultHeight);
            }
            //setMaximumSize(new Dimension(Short.MAX_VALUE, 64)); // no effect
            //setMinimumSize(new Dimension(150, 100)); // no effect

            addMouseListener(this);
            addMouseMotionListener(this);
            
            if (layer instanceof Layer)
                defaultBackground = Color.white;// Changed for Background
            else
                defaultBackground = Color.white; // debug/test case
            setBackground(defaultBackground);
            
            if (true) {
                // looks a bit messy w/current icons, but more informative
                visible.setName("layer.visible");
                visible.setIcon(VueResources.getImageIcon("pathwayOff"));
                visible.setSelectedIcon(VueResources.getImageIcon("pathwayOn"));
                // need a bigger and/or colored icon -- to tough to see
                locked.setName("layer.locked");
                locked.setIcon(VueResources.getImageIcon("lockOpen"));
                locked.setSelectedIcon(VueResources.getImageIcon("lock"));
                locked.setMargin(LockedInsets);
                locked.setBorder(GUI.makeSpace(1,5,5,1)); // no effect
            }
            
            locked.setSelected(layer.isLocked());
            locked.setBorderPainted(layer.isLocked());
            locked.setOpaque(false);
            locked.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        //locked.setBorderPainted(locked.isSelected());                    	
                        layer.setLocked(locked.isSelected());
                        if (layer == getActiveLayer() && !canBeActive(layer))
                            if (AUTO_ADJUST_ACTIVE_LAYER) attemptAlternativeActiveLayer(false);
                    }});
            

            visible.setSelected(layer.isVisible());
            visible.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                    	layer.setVisible(visible.isSelected());
                        locked.setEnabled(layer.isVisible());                        
                        label.setEnabled(layer.isVisible());        
                        VUE.getMainWindow().repaint();
                        if (layer == getActiveLayer() && !canBeActive(layer))
                            if (AUTO_ADJUST_ACTIVE_LAYER) attemptAlternativeActiveLayer(false);
                        layer.getUndoManager().mark((layer.isVisible() ? "Show" : "Hide")
                                                    + " Layer");
                    }});

            label.setEnabled(layer.isVisible());            
            
            if (layer instanceof Layer) {

                exclusive = new JRadioButton();
                exclusive.setName(VueResources.getString("layer.exclusive"));
                exclusive.setToolTipText(VueResources.getString("layer.quickedit"));
                exclusive.setBorderPainted(false);
                exclusive.setIcon(VueResources.getIcon(VUE.class, "images/quickFocus_ov.png"));
                exclusive.setFocusable(false);
                exclusive.setOpaque(false);
                exclusive.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                            exclusive.setBorderPainted(exclusive.isSelected());
                            Row.this.setExclusive(exclusive.isSelected());
                        }});
            
                //grab = new JButton("Grab");
                //grab.setFont(VueConstants.SmallFont);
                grab = new JRadioButton();                
                grab.setName(VueResources.getString("button.grab"));
                grab.setToolTipText(VueResources.getString("layer.selection.tooltip"));
                grab.setBorderPainted(false);
                grab.setIcon(VueResources.getIcon(VUE.class, "images/grab_ov.png"));
                grab.setFocusable(false); // FYI, no help on ignoring mouse-motion
                grab.setOpaque(false);
                

//                     // todo: use icon-button version when ready to go -- may
//                     // want to use a VueButton
//                     grab = new JButton();
//                     grab.setBorderPainted(false);
//                     // todo: update when Melanie creates new icon for this
//                     grab.setIcon(VueResources.getIcon(VUE.class, "images/hand_open.png"));
//                     grab.putClientProperty("JButton.buttonType", "textured");
//                     grab.putClientProperty("JButton.sizeVariant", "tiny");
                
                grab.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                            if (VUE.getSelection().size() > 0) {
                                grabFromSelection((Layer)layer);
                                VUE.getUndoManager().mark("Move To Layer " + Util.quote(layer.getLabel()));
                            }
                        }});
            } else {
                exclusive = null;
                grab = null;
            }

            
            final JLabel info = new JLabel()
                //{ public Dimension getMinimumSize() { return GUI.ZeroSize; } }
                ;
            info.setMinimumSize(new Dimension(40,30));
            if (layer.supportsChildren()) {
                
                // This might slow down undo of some large-set operations in large maps,
                // such as grabs, as auto-reparenting will currently de-parent each
                // child separately, issuing an event for each.  (All hierarchy events,
                // however, are merged into a single one for undo/redo for each parent).
                
                // Note: depends on Layer having permitZombieEvent(e) return
                // true, otherwise won't update correctly on undo.
                final LWComponent.Listener countListener
                    = new LWComponent.Listener() {
                        public void LWCChanged(LWCEvent e) {
                            //if (DEBUG.Enabled) Log.debug("UPDATING " + Row.this + " " + e);
                            String counts = "";
                            final int nChild = layer.numChildren();
                            final int allChildren = layer.getDescendentCount();
                            info.setForeground(Color.gray);   
                            if (nChild > 0)
                                counts += nChild;
                            if (allChildren != nChild)
                                counts += "/" + allChildren;
                            
                            if(counts.length()==0){
                            	info.setText("");
                            }else{
                            	info.setText("("+counts+")");
                            }                            
                            //if (DEBUG.Enabled) { Row.this.validate(); GUI.paintNow(Row.this); } // slower
                            // above will usually cause a deadlock tho when dropping images and this UI is visible
                            //if (DEBUG.Enabled) { Row.this.validate(); GUI.paintNow(info); } // faster
                        }};
                countListener.LWCChanged(null); // do the initial set
                layer.addLWCListener(countListener, LWKey.ChildrenAdded, LWKey.ChildrenRemoved);
            }


//             activeIcon.setIcon(VueResources.getIcon(VUE.class, "images/hand_open.png"));
//             // todo perf: only actually need instance of each of these for all rows:
//             activeIcon.setDisabledIcon(new GUI.EmptyIcon(activeIcon.getIcon())); 
//             activeIcon.setBorder(GUI.makeSpace(4,0,0,0));

            
            //final JComponent label = new VueTextField(layer.getLabel());
            // VueTextField impl not useful to us (also not used anywhere)
            // -- we need an impl that works just like VueTextPane, except
            // as a single line of text.
            
            //final JLabel info = new JLabel("(" + layer.numChildren() + " items)");
            
            final GridBagConstraints c = new GridBagConstraints();
            c.weighty = 1; // 1 has all expanding to fill vertical, 0 leaves all at min height
            c.anchor = GridBagConstraints.WEST;
            
//             c.insets.right = 4;
//             add(exclusive, c);

            //add(Box.createHorizontalStrut(5), c);
            c.insets.right = 0;
            add(visible, c);
            
            info.setHorizontalAlignment(SwingConstants.RIGHT);

            if (true) {

                // this magic, setting min-size to zero on the info text to 0, and wrapping
                // it in a container with the label, allows it fill left, shriking the
                // edit label if need-be, but never expanding the size of the two
                // components togehter -- that way, all label-edit + info-text groups
                // in all rows will always have the same width, keeping everything
                // in alignment
                
                info.setMinimumSize(new Dimension(40,23));    
                label.setMaximumSize(new Dimension(88,23)); 
                label.setCaretPosition(0);                
                //info.addMouseListener(RowMouseEnterExitTracker);
                Box box = new Box(BoxLayout.X_AXIS);
                //JPanel box = new JPanel();
                label.setPreferredSize(null); // must remove this, or info gets squished to 0 width
                //label.addMouseListener(RowMouseEnterExitTracker);
                box.add(label);
                //box.add(Box.createHorizontalGlue());
                box.add(info);
                if (DEBUG.BOXES) box.setBorder(new LineBorder(Color.red));
                box.setPreferredSize(GUI.MaxSize);
                //box.setPreferredSize(new Dimension(200, 30));
                //box.setMaximumSize(new Dimension(200, 30)); // apparently no use
                
                c.weightx = 1;
                c.fill = GridBagConstraints.HORIZONTAL;
                c.insets.right = 0;
                add(box, c);
                c.insets.left = 0;
                //c.insets.right = 0;
                c.weightx = 0;
                c.fill = GridBagConstraints.NONE;
                
            } else {

                c.weightx = 1;
                c.fill = GridBagConstraints.HORIZONTAL;
                add(label, c);
                c.fill = GridBagConstraints.NONE;
                c.weightx = 0;
                add(info, c);

//                 add(Box.createHorizontalStrut(1));
//                 add(label);
            
//                 //add(Box.createHorizontalGlue());
            
//                 add(Box.createHorizontalStrut(1));
//                 //info.setBorder(new LineBorder(Color.red));
//                 //info.setPreferredSize(new Dimension(70,Short.MAX_VALUE));
//                 //info.setMinimumSize(new Dimension(60,0));
//                 add(info);
            }
            
            //add(Box.createHorizontalGlue(), c);
            info.setMinimumSize(new Dimension(40,23));    
            label.setMinimumSize(new Dimension(88,23));             
            if (layer.hasFlag(INTERNAL)) {            	
                add(locked, c);
                preview = null;
                return;
            }
            
            preview = new Preview(layer);
            //preview.setMinimumSize(new Dimension(128, 64));
            
            //preview.setPreferredSize(GUI.MaxSize);
            //preview.setSize(256,128);
            //preview.setPreferredSize(new Dimension(256, Short.MAX_VALUE));
            //preview.setMaximumSize(GUI.MaxSize);

            if (false && DEBUG.Enabled)
                layer.addLWCListener(new LWComponent.Listener() {
                        public void LWCChanged(LWCEvent e) {
                            // this is heavy duty!  Would be nice if UserActionCompleted
                            // came through the layer, and we could listen for that,
                            // but it comes through the map
                            preview.repaint();
                        }});
            
            

            //add(Box.createHorizontalGlue());
            
            if (preview != null) {
                c.weightx = 1;
                c.fill = GridBagConstraints.BOTH;
                //c.fill = GridBagConstraints.VERTICAL;
                //add(Box.createHorizontalStrut(7));
                add(preview, c);
                c.weightx = 0;
                c.fill = GridBagConstraints.NONE;
                
            }

            if (true) {
                JPanel fixed = new JPanel(new BorderLayout());
                fixed.setOpaque(true);                
                fixed.setBorder(GUI.makeSpace(3,1,3,1));
                fixed.setMinimumSize(new Dimension(53, 0));                
                //fixed.add(exclusive, BorderLayout.WEST);                
                locked.setMinimumSize(new Dimension(25, 0)); 
                if(grab!=null){                	
                	grab.setMinimumSize(new Dimension(25, 0));
                	fixed.add(grab, BorderLayout.CENTER);
                }
                fixed.add(locked, BorderLayout.EAST);                
                c.fill = GridBagConstraints.BOTH;
                add(fixed, c);
            } else {            	
                // old-style before we added hiding these on mouse roll-off
                //add(activeIcon, c);            	
                add(grab, c);
                add(locked, c);
            }            
            // set initial visibility states by simulating a mouse roll-off
            rollOff(); 

        }

        private void add(Component comp, GridBagConstraints c) {
            if (comp == null)
                return;
            //super.add(comp);
            super.add(comp, c);
            //comp.addMouseListener(RowMouseEnterExitTracker);
            //c.gridx++;
        }

        private void processEventUp(AWTEvent e) {
            super.processEvent(e);
        }

        private void setExclusive(final boolean excluding) {

            Row exclusiveRow = fetchExclusiveRow();

            //Log.debug("SET-EXCLUSIVE " + this + " = " + excluding + "; nowExclusive=" + exclusiveRow);

            if (excluding && exclusiveRow == this)
                return;

            final boolean wasExcluding = exclusiveRow != null;
            final Row lastExcluded = exclusiveRow;
            exclusiveRow = this;

            if (wasExcluding && excluding) {
                // disable old row
                lastExcluded.label.setEnabled(false);
                lastExcluded.layer.setHidden(LAYER_EXCLUDED);
                lastExcluded.exclusive.setVisible(false); // simulate rollOff
                lastExcluded.exclusive.setSelected(false);
                lastExcluded.exclusive.setBorderPainted(false); // clear sticky-state border
            }

            if (excluding) {
                if (!wasExcluding)
                    setExclusiveMode(true, this);
                storeExclusiveRow(this);
                if (layer instanceof Layer)
                    setActiveLayer((Layer) layer);
            } else if (exclusiveRow == this) {
                setExclusiveMode(false, null);
                return;
            }

            exclusive.setSelected(excluding);
            label.setEnabled(true);
                        
            layer.clearHidden(DEFAULT);
            layer.clearHidden(LAYER_EXCLUDED);
            layer.setLocked(false);

            indicateActiveLayers(null);
            //tufts.vue.ZoomTool.setZoomFit();
        }


        @Override
        protected void addImpl(Component comp, Object constraints, int index) {
            if (comp instanceof JComponent)
                ((JComponent)comp).setOpaque(false); // needed for Tiger (uneeded on Leopard)
            comp.setFocusable(false);
            //comp.setFocusable(comp instanceof JTextField);
            super.addImpl(comp, constraints, index);
        }
        
        @Override
        public void setBackground(Color bg) {
            //Log.debug(this + " setBackground " + bg);
            if (bg == null) {
                super.setBackground(defaultBackground);
//                 if (label != null)
//                     label.setBackground(defaultBackground);
            } else {
                super.setBackground(bg);
//                 if (label != null) {
//                     if (bg.getAlpha() != 255) {
//                         //label.setBackground(Color.red);
//                         label.setBackground(new Color(0,0,0,0));
//                         label.setOpaque(false);
//                     } else {
//                         label.setBackground(bg);
//                     }
//                 }
            }
        }
        

        private int dragStartX;
        private int dragStartY;
        private int dragStartMouseY;
        private int dragRowIndex;
        private int dragLastY;
        private boolean didReorder;
        private Color saveColor;


        public void mouseEntered(MouseEvent e) {
            RowMouseEnterExitTracker.recordMouseEntered(this, e);
        }
        
        public void mouseExited(MouseEvent e) {
//             //Util.printStackTrace("HERE");
//             RowMouseEnterExitTracker.setRow(this);
            if (DEBUG.FOCUS) Log.debug("SCHEDULING MOUSE-TRACKER on " + e);
            GUI.invokeAfterAWT(RowMouseEnterExitTracker);
            
        }

        void rollOn() { 
        	
            if (grab != null) {
                grab.setVisible(true);
                //grab.setFocusable(false); // NO HELP ON IGNORING MOUSE-MOTION
            }
            if (locked != null){
            	//locked.setBorder(BorderFactory.createEmptyBorder(0, 35, 0, 0));
                locked.setVisible(true);
            }
            if (exclusive != null)
                exclusive.setVisible(true);

        }
        
        void rollOff() { 
        	
            if (grab != null){             	
                grab.setVisible(false);                
            }
            if (locked != null && !locked.isSelected())
                locked.setVisible(false);
            if (exclusive != null && !exclusive.isSelected())
                exclusive.setVisible(false);
        }        
        
        public void mouseClicked(MouseEvent e) {        	
            if (GUI.isDoubleClick(e)) {
            	if(e.isShiftDown()){  
            		if (VUE.getSelection() !=null)
            			VUE.getSelection().add(layer.getChildren());
            		else
            			VUE.getSelection().setTo(new LWSelection(layer.getChildren()));             		
            		          		
            		setActiveLayer((Layer) layer, !UPDATE);
            		
            	}else{
            		VUE.getSelection().setTo(layer.getAllDescendents());
            	}            	
            }
            Layer active = null;
            if(((JButton)mToolbar.getComponent(3)).isBorderPainted()){
            	active = getActiveLayer();  
            	Row row =null;
           	 	for (Row rows : mRows){
           	 		row = rows;
                     if (rows.layer.equals(active)){                    	 
                    	 row.layer.setVisible(true);
                    	 VUE.getSelection().setTo(row.layer.getAllDescendents());
                     }else{                    	 
                    	 row.layer.setVisible(false);
                     }
                 }        		
        		
        	}   
            if(active != null){
            	MouseListener popupListener = new PopupListener(active.isLocked());
            	addMouseListener(popupListener);
            }
        	
            
        }        
        class PopupListener extends MouseAdapter { 
        	boolean lockFlg = true;
        	PopupListener(boolean lockFlg){
        		this.lockFlg = lockFlg;
        	}
            public void mousePressed(MouseEvent e) {            	
                showPopup(e);
            }
            public void mouseReleased(MouseEvent e) {            	
                showPopup(e);
            }
            private void showPopup(MouseEvent e) {
                if (e.isPopupTrigger()) {              	            	
                	if(lockFlg){                		
                		unlockPopupMenu.show(e.getComponent(), e.getX(), e.getY());
                	}else{
                		popupMenu.show(e.getComponent(), e.getX(), e.getY());
                	}
                	
                	
                }
            }
            private void setPopEnabled(boolean isFlg){
            	duplicateMenuItem.setEnabled(isFlg);
        		renameMenuItem.setEnabled(isFlg);                		
        		lockMenuItem.setEnabled(isFlg); 
        		deleteMenuItem.setEnabled(isFlg); 
        		if(mRows.size() == 1){
                	deleteMenuItem.setEnabled(false) ;
            	}
            }
        }
        public void mousePressed(MouseEvent e) {

            if (DEBUG.MOUSE) Log.debug(e);             
            if (layer instanceof Layer) {
// //                 if (e.isShiftDown())
// //                     mSelection.toggle(layer);
// //                 else
//                     mSelection.setTo(layer);
                
//                 if (inExclusiveMode()) // exlusive mode is no longer globally modal
//                     setExclusive(true);
//                 else            	
            		if (!AUTO_ADJUST_ACTIVE_LAYER || layer.isVisible()) 
                    setActiveLayer((Layer) layer, UPDATE);
            		
//                 if (VUE.getSelection().isEmpty() || VUE.getSelection().only() instanceof Layer)
//                     VUE.getSelection().setTo(layer);                   
            } else {
                // this case for debug/test only: we shouldn't normally
                // see regular objects a the top level of the map anymore
                VUE.getSelection().setTo(layer);                
            }
            //LayersUI.this.requestFocus();
            isDragUnderway = false;
            mDragRow = null;
            didReorder = false;
        }
        
        public void mouseReleased(MouseEvent e) {
            isDragUnderway = false;
            mDragRow = null;
            if (saveColor != null) {
                setBackground(saveColor);
                saveColor = null;
            }                
            layoutRows();
            if (didReorder) {
                didReorder = false;

                if (!DYNAMIC_UPDATE) {
                    Util.printStackTrace("unimplemented");
                    // a implement LWContainer.insertAt(index, LWComponent) (can just use mChildren.add(index, c)
                    return;
                }
                
                if (dragRowIndex == mRows.indexOf(this)) {
                    layer.getMap().getUndoManager().resetMark();
                } else {
                    String undoMsg;
                    if (dragRowIndex > mRows.indexOf(this))
                        undoMsg = "Raise Layer ";
                    else
                        undoMsg = "Lower Layer ";
                    layer.getMap().getUndoManager().mark(undoMsg + Util.quote(layer.getLabel()));
                }
                        
            }
        }
        
        public void mouseMoved(MouseEvent e){}

        private int getDropRegionSize() {
            if (mRows.size() > 7) {
                // by using less than total height, we leave a narrow visible region
                // where the original item was as a reminder to the user of where
                // they're dragging from
                return (int) (0.8f * getHeight());
            } else
                return getHeight();
        }

        private static final boolean DYNAMIC_UPDATE = true;

        public void mouseDragged(MouseEvent e) 
        {
            final Point mouse = SwingUtilities.convertPoint(this, e.getPoint(), getParent());
            
            if (!isDragUnderway) {
                //System.out.println("START-DRAG");
                dragStartX = getX();
                dragStartY = getY();
                dragStartMouseY = mouse.y;
                dragLastY = mouse.y;
                dragRowIndex = mRows.indexOf(this);
                getParent().setComponentZOrder(this, 0); // make sure always paints on top
                isDragUnderway = true;
                mDragRow = this;
                Color c = getBackground();
                setBackground(new Color(c.getRed(),c.getGreen(),c.getBlue(),128));
                saveColor = c;
                didReorder = false;
            }

            int newY = dragStartY + (mouse.y - dragStartMouseY);
            final int dy = newY - dragStartY;

            if (newY < 0)
                newY = 0;
            else if (newY > getParent().getHeight() - getHeight())
                newY = getParent().getHeight() - getHeight();

            //System.out.println("newY=" + newY + "; dy=" + dy);

            boolean moved = false;

            final int curIndex = mRows.indexOf(this);
            if (mouse.y < dragLastY && curIndex > 0) {

                final Row above = mRows.get(curIndex - 1);

                if (newY < above.getY() + above.getHeight() / 2) {
                    if (DYNAMIC_UPDATE) {
                        // will trigger reload/relayout of all rows                        
                        moved = layer.getParent().bringForward(layer);
                    } else {
                        if (DEBUG.Enabled) Log.debug("BUMP DOWN " + above);
                        above.setLocation(above.getX(), above.getY() + getDropRegionSize());
                        Collections.swap(mRows, curIndex, curIndex - 1);
                        moved = true;
                    }
                }
                
            } else if (mouse.y > dragLastY && curIndex < mRows.size() - 1) {

                final Row below = mRows.get(curIndex + 1);
                final int bottomEdge = newY + getHeight();
                
                if (bottomEdge > below.getY() + below.getHeight() / 2) {

                    if (DYNAMIC_UPDATE) {
                        // will trigger reload/relayout of all rows                        
                        moved = layer.getParent().sendBackward(layer);
                    } else {
                        if (DEBUG.Enabled) Log.debug("BUMP  UP  " + below);
                        below.setLocation(below.getX(), below.getY() - getDropRegionSize());
                        Collections.swap(mRows, curIndex, curIndex + 1);
                        moved = true;
                    }
                }
            }

            if (moved) {
                loadLayers(mMap);
                getParent().setComponentZOrder(this, 0); // make sure always paints on top
                didReorder = true;
            }
            
            setDragLocation(getX(), newY);

            dragLastY = mouse.y;
            
        }

        @Override
        public void setBounds(int x, int y, int width, int height) {
            // This prevents us from being laid-out to the new location
            // during drags (jumping to the new location for one mouse-move)
            // Must be paired with setDragLocation to work.
            if (mDragRow == this)
                return;
            else
                super.setBounds(x, y, width, height);
        }

        private void setDragLocation(int x, int y) {
            super.setBounds(x, y, getWidth(), getHeight());
        }

        public void run() {
            setVisible(true);
        }
        

        @Override
        public String toString() {
            return "Row[" + mRows.indexOf(this) + "; " + layer + "]";
        }
    }
    public class SubtleSquareBorder implements Border
    {
        protected int m_w = 6;
        protected int m_h = 6;
        protected Color m_topColor = Color.gray;
        protected Color m_bottomColor = Color.gray;
        protected boolean roundc = false; // Do we want rounded corners on the border?
    
        public SubtleSquareBorder(boolean round_corners)
        {    
          roundc = round_corners;
        }
    
        public Insets getBorderInsets(Component c)
        {
            return new Insets(m_h, m_w, m_h, m_w);
        }
    
        public boolean isBorderOpaque()
        {
            return true;
        }
    
        public void paintBorder(Component c, Graphics g, int xx, int yy, int ww, int hh)
        {
            int x = xx + 5;
            int y = yy + 5;
            int w = ww - 12;
            int h = hh-12;
        //w = w - 3;
        //h = h - 3;
        x ++;
        y ++;
    
        // Rounded corners
        if(roundc)
        {
        g.setColor(m_topColor);
        g.drawLine(x, y + 3, x, y + h - 2);        
        g.drawLine(x + 2, y, x + w - 2, y);
        g.drawLine(x, y + 2, x + 2, y); // Top left diagonal
        g.drawLine(x, y + h - 2, x + 2, y + h); // Bottom left diagonal
        g.setColor(m_bottomColor);
        g.drawLine(x + w, y + 2, x + w, y + h - 2);
        g.drawLine(x + 2, y + h, x + w -2, y + h);
        g.drawLine(x + w - 2, y, x + w, y + 2); // Top right diagonal
        g.drawLine(x + w, y + h - 2, x + w -2, y + h); // Bottom right diagonal        
        }
    
        // Square corners
        else
        {
        g.setColor(m_topColor);
        g.drawLine(x, y, x, y + h);
        g.drawLine(x, y, x + w, y);
        g.setColor(m_bottomColor);
        g.drawLine(x + w, y, x + w, y + h);
        g.drawLine(x, y + h, x + w, y + h);
        }
    
        }

    }
}
        
    



    
//         addKeyListener(new KeyAdapter() {
//                 public void keyPressed(KeyEvent e) {
//                     // Why aren't the mark's working?
//                     //System.out.println("KP " + e);
//                     final tufts.vue.LWContainer layer = VUE.getActiveLayer();
//                     if (layer == null)
//                         return;
//                     if (e.getKeyCode() == KeyEvent.VK_UP) {
//                         if (layer.getParent().bringForward(layer)) {
//                             layer.getMap().getUndoManager().mark("Raise Layer " + Util.quote(layer.getLabel()));
//                         }
//                     } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
//                         if (layer.getParent().sendBackward(layer)) {
//                             layer.getMap().getUndoManager().mark("Lower Layer " + Util.quote(layer.getLabel()));
//                         }
//                     }
//                 }
//             });

//     private static final String LAYER_NEW = "New";
//     private static final String LAYER_DUPLICATE = "Duplicate";
//     private static final String LAYER_MERGE = "Merge";
//     private static final String LAYER_DELETE = "Delete";

//     public void actionPerformed(ActionEvent e) {

//         if (mMap == null)
//             return;

//         final String a = e.getActionCommand();
//         final Layer active = mMap.getActiveLayer();
        
//         if (a == LAYER_NEW) {

//             mMap.addChild(new LWMap.Layer());

//         } else if (a == LAYER_DUPLICATE) {

//             Layer dupe = (Layer) active.duplicate();
//             mMap.addChild(dupe);
            
//         } else if (a == LAYER_MERGE) {

//         } else if (a == LAYER_DELETE) {

//             mMap.deleteChildPermanently(active);

//         } else {
            
//             Log.warn("unhandled action: " + e);
//         }

//         mMap.getUndoManager().mark(a + " Layer");

//     }