LayerListPanel.java

/*
 * Copyright (c) 2016 Martin Davis.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package org.locationtech.jtstest.testbuilder;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.border.Border;

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jtstest.testbuilder.model.Layer;
import org.locationtech.jtstest.testbuilder.model.LayerList;
import org.locationtech.jtstest.testbuilder.model.StaticGeometryContainer;
import org.locationtech.jtstest.testbuilder.ui.ColorUtil;
import org.locationtech.jtstest.testbuilder.ui.SwingUtil;

/**
 * @version 1.7
 */
public class LayerListPanel extends JPanel {
  
  private static final int TAB_INDEX_LAYER = 0;

  private static final String LBL_LAYER = "Style";
  private static final String LBL_VIEW = "View";
  
  JPanel listPanel = new JPanel();
  Box buttonPanel = Box.createVerticalBox();
  JTabbedPane tabPane = new JTabbedPane();
  private LayerStylePanel lyrStylePanel;
  List<LayerItemPanel> layerItems = new ArrayList<LayerItemPanel>();

  private JButton btnCopy;
  private JButton btnInspect;
  private JButton btnUp;
  private JButton btnDown;
  private JButton btnDelete;
  private JButton btnPaste;

  private Layer focusLayer;

  public LayerListPanel() {
    try {
      uiInit();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void uiInit() throws Exception {
    //setSize(300, 250);
    setBackground(AppColors.BACKGROUND);
    setLayout(new BorderLayout());
    
    JPanel panelLeft = new JPanel();
    panelLeft.setLayout(new BorderLayout());

    listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.Y_AXIS));
    listPanel.setBackground(AppColors.BACKGROUND);
    listPanel.setBorder(BorderFactory.createEmptyBorder(2,2,2,0));

    JScrollPane jScrollPane1 = new JScrollPane();
    jScrollPane1.setBackground(AppColors.BACKGROUND);
    jScrollPane1.setOpaque(true);
    jScrollPane1.getViewport().add(listPanel, null);
    jScrollPane1.setPreferredSize(new Dimension(150, 250));

    panelLeft.add(jScrollPane1, BorderLayout.CENTER);
    panelLeft.add(buttonPanel, BorderLayout.EAST);

    btnCopy = SwingUtil.createButton(AppIcons.ADD, 
        "Copy layer to a new layer",
            new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            layerCopy();
          }
        });
    buttonPanel.add(btnCopy);
    
    btnInspect = SwingUtil.createButton(AppIcons.GEOM_INSPECT, 
        "Inspect layer geometry",
            new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            layerInspect();
          }
        });
    buttonPanel.add(btnInspect);
    
    btnPaste = SwingUtil.createButton(AppIcons.PASTE, 
        "Paste geometry into layer",
            new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            layerPaste(focusLayer);
          }
        });
    buttonPanel.add(btnPaste);
    btnUp = SwingUtil.createButton(AppIcons.UP, 
        "Move layer up",
            new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            layerUp(focusLayer);
          }
        });
    buttonPanel.add(btnUp);
    btnDown = SwingUtil.createButton(AppIcons.DOWN, 
        "Move layer down",
            new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            layerDown(focusLayer);
          }
        });
    buttonPanel.add(btnDown);
    
    btnDelete = SwingUtil.createButton(AppIcons.DELETE, 
        AppStrings.TIP_LAYER_CLEAR,
            new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (SwingUtil.isCtlKeyPressed(e)) {
              layerDelete(focusLayer);
            }
            else {
              layerClear(focusLayer);
            }
         }
        });
    buttonPanel.add(btnDelete);
    
    add(panelLeft, BorderLayout.WEST);
    
    lyrStylePanel = new LayerStylePanel();
    GeometryViewStylePanel viewStylePanel = new GeometryViewStylePanel();
    
    //add(lyrStylePanel, BorderLayout.CENTER);    

    //tabFunctions.setBackground(jTabbedPane1.getBackground());
    JScrollPane scrollPane = new JScrollPane();
    scrollPane.getViewport().add(lyrStylePanel, null);
    tabPane.add(scrollPane,  LBL_LAYER);
    tabPane.add(viewStylePanel,   LBL_VIEW);
    add(tabPane, BorderLayout.CENTER);
  }

  protected LayerItemPanel findLayerItem(Layer lyr) {
    for (LayerItemPanel lip : layerItems) {
      if (lip.getLayer() == lyr) return lip;
    }
    return null;
  }

  public void showTabLayerStyle(String title) {
    tabPane.setSelectedIndex(TAB_INDEX_LAYER);
    //tabPane.setTitleAt(0, LBL_LAYER + " - " + title);
    //SwingUtil.showTab(tabPane, LBL_LAYER_STYLE);
  }
  
  public void populateList() {
    listPanel.removeAll();
    layerItems.clear();
    
    addLayers(JTSTestBuilder.model().getLayersTop());
    addLayers(JTSTestBuilder.model().getLayers());
    addLayers(JTSTestBuilder.model().getLayersBase());
    setLayerFocus(layerItems.get(0));
  }

  public void updateList() {
    for (LayerItemPanel layerItemPanel : layerItems) {
      layerItemPanel.update();
    }
  }

  private void addLayers(LayerList lyrList) {
    for (int i = 0; i < lyrList.size(); i++) {
      Layer lyr = lyrList.getLayer(i);
      LayerItemPanel item = new LayerItemPanel(lyr, this);
      listPanel.add(item);
      layerItems.add(item);
    }
  }
  
  public void setLayerFocus(LayerItemPanel layerItem) {
    for (LayerItemPanel item : layerItems) {
      item.setFocusLayer(false);
    }
    layerItem.setFocusLayer(true);
    Layer layer = layerItem.getLayer();
    boolean isModifiable = ! JTSTestBuilder.model().isLayerFixed(layer);
    showTabLayerStyle(layer.getName());
    lyrStylePanel.setLayer(layer, isModifiable);
    focusLayer = layer;
    updateButtons(focusLayer);
  }

  private void updateButtons(Layer lyr) {
    boolean isModifiable = ! JTSTestBuilder.model().isLayerFixed(lyr);

    // every layer is copyable
    btnCopy.setEnabled(true);
    btnPaste.setEnabled(isModifiable && ! lyr.hasGeometry());
    btnUp.setEnabled(isModifiable);
    btnDown.setEnabled(isModifiable);
    btnDelete.setEnabled(isModifiable);
  }

  private void layerCopy() {
    Layer copy = JTSTestBuilder.model().layerCopy(focusLayer);
    populateList();
    setLayerFocus(findLayerItem(copy));
    JTSTestBuilder.controller().geometryViewChanged();
  }

  private void layerInspect() {
    JTSTestBuilder.controller().inspectGeometry(focusLayer.getName(), focusLayer.getGeometry());
  }
  
  private void layerDelete(Layer lyr) {
    // don't remove if non-empty
    if (lyr.hasGeometry()) return;
    
    JTSTestBuilder.model().layerDelete(lyr);
    populateList();
    JTSTestBuilder.controller().geometryViewChanged();
  }
  
  private void layerUp(Layer lyr) {
    JTSTestBuilder.model().layerUp(lyr);
    populateList();
    setLayerFocus(findLayerItem(lyr));
    JTSTestBuilder.controller().geometryViewChanged();
  }
  
  private void layerDown(Layer lyr) {
    JTSTestBuilder.model().layerDown(lyr);
    populateList();
    setLayerFocus(findLayerItem(lyr));
    JTSTestBuilder.controller().geometryViewChanged();
  }
  
  private void layerClear(Layer lyr) {
    lyr.getSource().clear();
    updateButtons(focusLayer);
    JTSTestBuilder.controller().geometryViewChanged();
  }
  
  protected void layerPaste(Layer lyr) {
    try {
      // don't paste into non-empty layers to avoid losing data
      if (lyr.hasGeometry()) return;
      
      Geometry geom = JTSTestBuilder.model().readGeometryFromClipboard();
      // this will error if layer is not modifiable
      StaticGeometryContainer src = (StaticGeometryContainer) lyr.getSource();
      src.setGeometry(geom);
      updateButtons(focusLayer);
      JTSTestBuilder.controller().geometryViewChanged();
    } catch (Exception e) {
      SwingUtil.reportException(this, e);
    }
  }
}

class LayerItemPanel extends JPanel {
  private static Font FONT_FOCUS = new java.awt.Font("Dialog", Font.BOLD, 12);
  private static Font FONT_NORMAL = new java.awt.Font("Dialog", Font.PLAIN, 12);
  
  private Border BORDER_CONTROL = BorderFactory.createLineBorder(CLR_CONTROL);
  private Border BORDER_HIGHLIGHT = BorderFactory.createLineBorder(Color.LIGHT_GRAY);
  
  private static final Color CLR_CONTROL = AppColors.BACKGROUND;
  private static final Color CLR_HIGHLIGHT = ColorUtil.darker(CLR_CONTROL, .95);
  
  private Layer layer;
  private JCheckBox checkbox;
  private LayerListPanel lyrListPanel;
  private LayerItemPanel self;
  private JPanel namePanel;
  private boolean hasFocus;
  private JLabel lblName;
  private JPanel swatch;

  LayerItemPanel(Layer lyr, LayerListPanel lyrListPanel) {
    this.layer = lyr;
    this.lyrListPanel = lyrListPanel;
    try {
      uiInit();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    self = this;
  }

  public String getToolTipText(MouseEvent e) {
    return layer.getNameInfo();
  }
  
  public Layer getLayer() {
    return layer;
  }

  public void setFocusLayer(boolean hasFocus) {
    setBackground(hasFocus ? AppColors.TAB_FOCUS : AppColors.BACKGROUND);
    setBorder(hasFocus ? BORDER_HIGHLIGHT : BORDER_CONTROL );
    lblName.setFont(hasFocus ? FONT_FOCUS : FONT_NORMAL);
    revalidate();
    this.hasFocus = hasFocus;
  }
  
  public boolean isFocusLayer() {
    return hasFocus;
  }
  
  public void update() {
    LayerStyleSwatchControl.update(swatch, layer);
    lblName.setText( layer.getName());
  }
  
  private void uiInit() throws Exception {
    setSize(250, 250);
    setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
    setBackground(AppColors.BACKGROUND);
    //setOpaque(true);
    setAlignmentX(Component.LEFT_ALIGNMENT);
    setBorder(BORDER_CONTROL);
    
    checkbox = new JCheckBox();
    add(checkbox);
    checkbox.setAlignmentX(Component.LEFT_ALIGNMENT);
    checkbox.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        layerVisAction();
      }
    });
    checkbox.setSelected(layer.isEnabled());
    checkbox.setOpaque(false);

    
    lblName = new LayerName(layer);
    lblName.setAlignmentX(Component.LEFT_ALIGNMENT);
    lblName.setMinimumSize(new Dimension(100,12));
    lblName.setPreferredSize(new Dimension(100,12));
    lblName.setMaximumSize(new Dimension(100,12));
    lblName.setFont(FONT_NORMAL);
    lblName.addMouseListener(new HighlightMouseListener(this));
    lblName.addMouseListener(new MouseAdapter()  
    {  
      public void mouseClicked(MouseEvent e)  
      {  
        lyrListPanel.setLayerFocus(self);
      }
    }); 
    
    swatch = LayerStyleSwatchControl.create(layer);
    add(swatch);
    add(Box.createRigidArea(new Dimension(4,0)));
    
    namePanel = new JPanel();
    add(namePanel);
    namePanel.setLayout(new BoxLayout(namePanel, BoxLayout.X_AXIS));
    //namePanel.setBackground(CLR_CONTROL);
    namePanel.setOpaque(false);
    namePanel.setAlignmentX(Component.LEFT_ALIGNMENT);
    namePanel.setMinimumSize(new Dimension(100,14));
    namePanel.setPreferredSize(new Dimension(100,14));
    namePanel.setMaximumSize(new Dimension(100,14));
    //namePanel.setBorder(BORDER_HIGHLIGHT);;
    namePanel.addMouseListener(new HighlightMouseListener(this));
    
    namePanel.add(lblName);
  }

  private void layerVisAction() {
    boolean isVisible = checkbox.isSelected();
    layer.setEnabled(isVisible);
    repaint();
    JTSTestBuilder.controller().geometryViewChanged();
  }
  
  class HighlightMouseListener extends MouseAdapter {
    private LayerItemPanel comp;

    HighlightMouseListener(LayerItemPanel comp) {
      this.comp = comp;
    }
    
    public void mouseEntered(MouseEvent e) {
      if (comp.isFocusLayer()) return;
      comp.setBackground(CLR_HIGHLIGHT);
      //comp.setBorder(BORDER_HIGHLIGHT);
      comp.revalidate();
    }

    public void mouseExited(MouseEvent e) {
      if (comp.isFocusLayer()) return;
      comp.setBackground(AppColors.BACKGROUND);
      //comp.setBorder(BORDER_CONTROL);
      comp.revalidate();
   }
  }

}

class LayerName extends JLabel {
  private Layer layer;

  public LayerName(Layer layer) {
    super(layer.getName());
    this.layer = layer;
    setToolTipText(layer.getName()); 
  }

  public String getToolTipText(MouseEvent e) {
    return layer.getNameSummary();
  }
}

class LayerStyleSwatchControl extends JPanel {

  public static LayerStyleSwatchControl create(Layer layer) {
    LayerStyleSwatchControl ctl = new LayerStyleSwatchControl(layer);
    //update(ctl, layer);  
    return ctl;
  }
  
  private Layer layer;

  public LayerStyleSwatchControl(Layer layer) {
    this.layer = layer;
    Dimension dim = new Dimension(16,16);
    setMinimumSize(dim);
    setPreferredSize(dim);
    setMaximumSize(dim);
    setOpaque(true);
    setToolTipText(layer.getName()); 
  }

  public String getToolTipText(MouseEvent e) {
    return layer.getNameSummary();
  }
  
  public static void update(JPanel ctl, Layer layer) {
    
    Color fillClr = Color.WHITE;
    if (layer.getGeometryStyle().isFilled())
      fillClr = layer.getGeometryStyle().getFillColor();

    int lineWidth = 1;
    if (layer.getGeometryStyle().getStrokeWidth() > 1)
      lineWidth = 2;
    
    /*
    Graphics2D gr = (Graphics2D) ctl.getGraphics();
    gr.setColor(fillClr);
    gr.setPaint(fillClr);
    gr.fillRect(0, 0, 10, 10);
*/
    ctl.setBackground( fillClr );  
    ctl.setBorder(BorderFactory.createLineBorder(layer.getGeometryStyle().getLineColor(), lineWidth));
  }
}