LayerStylePanel.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.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import org.locationtech.jtstest.testbuilder.model.Layer;
import org.locationtech.jtstest.testbuilder.ui.ColorUtil;
import org.locationtech.jtstest.testbuilder.ui.SwingUtil;
import org.locationtech.jtstest.testbuilder.ui.style.BasicStyle;
import org.locationtech.jtstest.testbuilder.ui.style.LayerStyle;
import org.locationtech.jtstest.testbuilder.ui.style.Palette;
import org.locationtech.jtstest.testbuilder.ui.style.VertexStyle;

public class LayerStylePanel extends JPanel {
  private Layer layer;
  private JLabel title;
  private JCheckBox cbShift;
  private JPanel stylePanel;
  private int rowIndex;
  private JCheckBox cbDashed;
  private JSpinner spinnerLineWidth;
  private SpinnerNumberModel lineWidthModel;
  private JCheckBox cbFilled;
  private JSlider sliderFillAlpha;
  private JPanel btnFillColor;
  private JPanel btnLineColor;
  private JSlider sliderLineAlpha;
  
  private JCheckBox cbVertex;
  private JPanel btnVertexColor;
  private JSpinner spinVertexSize;
  private SpinnerNumberModel vertexSizeModel;
  
  private JCheckBox cbLabel;
  private JPanel btnLabelColor;
  private JSpinner spinLabelSize;
  private SpinnerNumberModel labelSizeModel;
  
  private JCheckBox cbStroked;
  private JTextField txtName;
  private JCheckBox cbOrient;
  private JCheckBox cbStructure;
  private JCheckBox cbVertexLabel;
  private JCheckBox cbOffset;
  private JSpinner spinOffsetSize;
  private SpinnerNumberModel offsetSizeModel;
  private JCheckBox cbEndpoint;
  private JComboBox comboPalette;
  private JCheckBox cbSegIndex;
  private JComboBox comboVertexSymbol;

  
  public LayerStylePanel() {
    
    try {
      uiInit();
    } catch (Exception ex) {
      ex.printStackTrace();
    } 
  }
  private BasicStyle geomStyle() {
    return layer.getLayerStyle().getGeomStyle();
  }  
  public void setLayer(Layer layer, boolean isModifiable) {
    this.layer = layer;
    //this.title.setText("Styling - Layer " + layer.getName());
    txtName.setText(layer.getName());
    txtName.setEditable(isModifiable);
    txtName.setFocusable(isModifiable);
    updateStyleControls();
  }
  
  void updateStyleControls() {
    ColorControl.update(btnVertexColor, layer.getLayerStyle().getVertexColor() );
    ColorControl.update(btnLabelColor, layer.getLayerStyle().getLabelColor() );
    ColorControl.update(btnLineColor, geomStyle().getLineColor() );
    ColorControl.update(btnFillColor, geomStyle().getFillColor() );
    sliderLineAlpha.setValue(geomStyle().getLineAlpha());
    sliderFillAlpha.setValue(geomStyle().getFillAlpha());
    cbShift.setSelected(layer.getLayerStyle().isShifted());
    cbVertex.setSelected(layer.getLayerStyle().isVertices());
    cbVertexLabel.setSelected(layer.getLayerStyle().isVertexLabels());
    setVertexSymbol(comboVertexSymbol, layer.getLayerStyle().getVertexSymbol());
    vertexSizeModel.setValue(layer.getLayerStyle().getVertexSize());
    cbLabel.setSelected(layer.getLayerStyle().isLabel());
    labelSizeModel.setValue(layer.getLayerStyle().getLabelSize());
    cbEndpoint.setSelected(layer.getLayerStyle().isEndpoints());
    cbDashed.setSelected(geomStyle().isDashed());
    cbOffset.setSelected(layer.getLayerStyle().isOffset());
    offsetSizeModel.setValue(layer.getLayerStyle().getOffsetSize() );
    cbStroked.setSelected(geomStyle().isStroked());
    cbFilled.setSelected(geomStyle().isFilled());
    cbOrient.setSelected(layer.getLayerStyle().isOrientations());
    cbStructure.setSelected(layer.getLayerStyle().isStructure());
    cbSegIndex.setSelected(layer.getLayerStyle().isSegIndex());
    lineWidthModel.setValue((double) geomStyle().getStrokeWidth());
    setPaletteType(comboPalette, layer.getLayerStyle().getFillType());

    JTSTestBuilder.controller().updateLayerList();
  }
  
  private void uiInit() throws Exception {
    setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
    setLayout(new BorderLayout());
     
     
    //title = new JLabel("Styling");
    //title.setAlignmentX(Component.LEFT_ALIGNMENT);
    //add(title, BorderLayout.NORTH);
    

    add( stylePanel(), BorderLayout.CENTER );
    
    JButton btnReset = SwingUtil.createButton(AppIcons.CLEAR, "Reset style to default", new ActionListener() {
      public void actionPerformed(ActionEvent arg0) {
        if (layer == null) return;
        layer.resetStyle();
        updateStyleControls();
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    JPanel btnPanel = new JPanel();
    btnPanel.setLayout(new BoxLayout(btnPanel, BoxLayout.Y_AXIS));
    btnPanel.add(btnReset);
    add( btnPanel, BorderLayout.EAST);
  }
  
  private JPanel stylePanel() {
    JPanel containerPanel = new JPanel();
    containerPanel.setLayout(new BoxLayout(containerPanel, BoxLayout.Y_AXIS));
    
    stylePanel = new JPanel();
    stylePanel.setLayout(new GridBagLayout());
    stylePanel.setAlignmentX(Component.LEFT_ALIGNMENT);

    containerPanel.add(Box.createVerticalGlue());
    containerPanel.add(stylePanel);
    
    Dimension minSize = new Dimension(5, 100);
    Dimension prefSize = new Dimension(5, 100);
    Dimension maxSize = new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
    containerPanel.add(new Box.Filler(minSize, prefSize, maxSize));

    //=============================================
    txtName = new JTextField();
    txtName.setMaximumSize(new Dimension(100,20));
    txtName.setPreferredSize(new Dimension(100,20));
    txtName.setMinimumSize(new Dimension(100,20));
    
    cbShift = new JCheckBox();
    cbShift.setToolTipText(AppStrings.TIP_STYLE_SHIFT);
    cbShift.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbShift.setText("Shift");
    cbShift.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setShift(cbShift.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });

    addRow("Name", txtName, cbShift);
    
    txtName.getDocument().addDocumentListener(new DocumentListener() {
      public void changedUpdate(DocumentEvent e) {
        update();
      }
      public void removeUpdate(DocumentEvent e) {
        update();
      }
      public void insertUpdate(DocumentEvent e) {
        update();
      }

      public void update() {
        String name = txtName.getText();
        layer.setName(name);
        JTSTestBuilder.controller().updateLayerList();
      }
    });

    //=============================================

    cbVertex = new JCheckBox();
    cbVertex.setToolTipText(AppStrings.TIP_STYLE_VERTEX_ENABLE);
    cbVertex.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbVertex.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setVertices(cbVertex.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    btnVertexColor = ColorControl.create(this, 
        "Vertex",
        AppColors.GEOM_VIEW_BACKGROUND,
        new ColorControl.ColorListener() {
          public void colorChanged(Color clr) {
            if (layer == null) return;
            layer.getLayerStyle().setVertexColor(clr);
            JTSTestBuilder.controller().geometryViewChanged();
          }
        }
       );
    
    vertexSizeModel = new SpinnerNumberModel(4, 0, 100, 1);
    spinVertexSize = new JSpinner(vertexSizeModel);
    spinVertexSize.setMaximumSize(new Dimension(40,16));
    spinVertexSize.setAlignmentX(Component.LEFT_ALIGNMENT);
    spinVertexSize.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        int size = vertexSizeModel.getNumber().intValue();
        layer.getLayerStyle().setVertexSize(size);
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    cbVertexLabel = new JCheckBox();
    cbVertexLabel.setToolTipText(AppStrings.TIP_STYLE_VERTEX_LABEL_ENABLE);
    cbVertexLabel.setText("Label");
    cbVertexLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbVertexLabel.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setVertexLabels(cbVertexLabel.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });

    comboVertexSymbol = new JComboBox(vertexSymbolNames);
    comboVertexSymbol.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        JComboBox cb = (JComboBox)e.getSource();
        int symType = getVertexSymbol(cb);
        layer.getLayerStyle().setVertexSymbol(symType);
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    
    comboVertexSymbol.setToolTipText(AppStrings.TIP_STYLE_SYMBOL);
   
    addRow("Vertices", cbVertex, btnVertexColor, spinVertexSize, comboVertexSymbol, cbVertexLabel);
    //=============================================

    cbStroked = new JCheckBox();
    cbStroked.setToolTipText(AppStrings.TIP_STYLE_LINE_ENABLE);
    cbStroked.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbStroked.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        geomStyle().setStroked(cbStroked.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });

    btnLineColor = ColorControl.create(this, 
        "Line",
        AppColors.GEOM_VIEW_BACKGROUND,
        new ColorControl.ColorListener() {
          public void colorChanged(Color clr) {
            geomStyle().setLineColor(clr);
            layer.getLayerStyle().setColor(clr);
            JTSTestBuilder.controller().geometryViewChanged();
            JTSTestBuilder.controller().updateLayerList();
          }
        }
       );
    JButton btnVertexSynch = createSynchButton("^", "Synch Vertex Color", new ActionListener() {
      public void actionPerformed(ActionEvent arg0) {
        if (layer == null) return;
        Color clr = ColorControl.getColor(btnLineColor);
        layer.getLayerStyle().setColor(clr);
        layer.getLayerStyle().setVertexColor(clr);
        updateStyleControls();
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });

    lineWidthModel = new SpinnerNumberModel(1.0, 0, 100, 0.2);
    spinnerLineWidth = new JSpinner(lineWidthModel);
    //widthSpinner.setMinimumSize(new Dimension(50,12));
    //widthSpinner.setPreferredSize(new Dimension(50,12));
    spinnerLineWidth.setMaximumSize(new Dimension(40,16));
    spinnerLineWidth.setAlignmentX(Component.LEFT_ALIGNMENT);
    
    spinnerLineWidth.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        float width = lineWidthModel.getNumber().floatValue();
        geomStyle().setStrokeWidth(width);
        JTSTestBuilder.controller().geometryViewChanged();
        JTSTestBuilder.controller().updateLayerList();
      }
    });

    sliderLineAlpha = createOpacitySlider(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        JSlider source = (JSlider)e.getSource();
        if (! source.getValueIsAdjusting()) {
          int alpha = (int)source.getValue();
          geomStyle().setLineAlpha(alpha);
          JTSTestBuilder.controller().geometryViewChanged();
          JTSTestBuilder.controller().updateLayerList();
        }
      }
    });
    cbDashed = new JCheckBox();
    cbDashed.setText("Dashed");
    //cbDashed.setToolTipText(AppStrings.STYLE_VERTEX_ENABLE);
    cbDashed.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbDashed.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        geomStyle().setDashed(cbDashed.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    cbOffset = new JCheckBox();
    cbOffset.setText("Offset");
    //cbDashed.setToolTipText(AppStrings.STYLE_VERTEX_ENABLE);
    cbOffset.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbOffset.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setOffset(cbOffset.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    offsetSizeModel = new SpinnerNumberModel(LayerStyle.INIT_OFFSET_SIZE, -100, 100, 1);
    spinOffsetSize = new JSpinner(offsetSizeModel);
    spinOffsetSize.setMaximumSize(new Dimension(40,16));
    spinOffsetSize.setAlignmentX(Component.LEFT_ALIGNMENT);
    spinOffsetSize.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        int size = offsetSizeModel.getNumber().intValue();
        layer.getLayerStyle().setOffsetSize(size);
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });

    addRow("Line", cbStroked, btnLineColor, btnVertexSynch, sliderLineAlpha, spinnerLineWidth, 
        cbDashed, cbOffset, spinOffsetSize);

    //=============================================
    
    cbEndpoint = new JCheckBox();
    cbEndpoint.setText("Endpoints");
    cbEndpoint.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbEndpoint.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setEndpoints(cbEndpoint.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    
    cbOrient = new JCheckBox();
    cbOrient.setText("Orientation");
    cbOrient.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbOrient.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setOrientations(cbOrient.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });

    cbStructure = new JCheckBox();
    cbStructure.setText("Structure");
    cbStructure.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbStructure.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setStructure(cbStructure.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });

    cbSegIndex = new JCheckBox();
    cbSegIndex.setText("Index");
    cbSegIndex.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbSegIndex.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setSegIndex(cbSegIndex.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });

    
   // Leave on separate line to allow room for dash style
    addRow("", cbEndpoint, cbOrient, cbStructure, cbSegIndex);
    //=============================================

    cbFilled = new JCheckBox();
    cbFilled.setToolTipText(AppStrings.TIP_STYLE_FILL_ENABLE);
    cbFilled.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbFilled.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        geomStyle().setFilled(cbFilled.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
        JTSTestBuilder.controller().updateLayerList();
      }
    });
   
    sliderFillAlpha = createOpacitySlider(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        JSlider source = (JSlider)e.getSource();
        if (! source.getValueIsAdjusting()) {
          int alpha = (int)source.getValue();
          geomStyle().setFillAlpha(alpha);
          JTSTestBuilder.controller().geometryViewChanged();
          JTSTestBuilder.controller().updateLayerList();
        }
      }
    });
    btnFillColor = ColorControl.create(this, 
        "Fill",
        AppColors.GEOM_VIEW_BACKGROUND,
        new ColorControl.ColorListener() {
          public void colorChanged(Color clr) {
            geomStyle().setFillColor(clr);
            updateStyleControls();
            JTSTestBuilder.controller().geometryViewChanged();
            JTSTestBuilder.controller().updateLayerList();
          }
        }
       );
    JButton btnLineSynch = createSynchButton("^", "Synch Line Color", new ActionListener() {
      public void actionPerformed(ActionEvent arg0) {
        Color clr = lineColorFromFill( ColorControl.getColor(btnFillColor));
        geomStyle().setLineColor(clr );
        layer.getLayerStyle().setColor(clr);
        updateStyleControls();
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    addRow("Fill", cbFilled, btnFillColor, btnLineSynch, sliderFillAlpha);

    //=============================================

    comboPalette = new JComboBox(paletteNames);
    comboPalette.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        JComboBox cb = (JComboBox)e.getSource();
        int fillType = getPaletteType(cb);
        layer.getLayerStyle().setFillType(fillType);
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    comboPalette.setToolTipText(AppStrings.TIP_STYLE_PALETTE);
    addRow("Palette", comboPalette);
    
    //=============================================

    
    cbLabel = new JCheckBox();
    //cbLabel.setToolTipText(AppStrings.TIP_STYLE_VERTEX_ENABLE);
    cbLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
    cbLabel.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (layer == null) return;
        layer.getLayerStyle().setLabel(cbLabel.isSelected());
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });
    btnLabelColor = ColorControl.create(this, 
        "Label",
        AppColors.GEOM_VIEW_BACKGROUND,
        new ColorControl.ColorListener() {
          public void colorChanged(Color clr) {
            if (layer == null) return;
            layer.getLayerStyle().setLabelColor(clr);
            JTSTestBuilder.controller().geometryViewChanged();
          }
        }
       );
    
    labelSizeModel = new SpinnerNumberModel(4, 0, 100, 1);
    spinLabelSize = new JSpinner(labelSizeModel);
    spinLabelSize.setMaximumSize(new Dimension(40,16));
    spinLabelSize.setAlignmentX(Component.LEFT_ALIGNMENT);
    spinLabelSize.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        int size = labelSizeModel.getNumber().intValue();
        layer.getLayerStyle().setLabelSize(size);
        JTSTestBuilder.controller().geometryViewChanged();
      }
    });


    
    addRow("Label", cbLabel, btnLabelColor, spinLabelSize);
    
    //=============================================
    
    return containerPanel;
  }

  //-----------------------------------------
  static String[] paletteNames = { "Basic", "Varying", "Spectrum", "Spectrum Random" }; 

  private static int getPaletteType(JComboBox comboPal) {
    String palName = (String)comboPal.getSelectedItem();
    
    int paletteType = Palette.TYPE_BASIC;
    if (palName.equalsIgnoreCase(paletteNames[1])) paletteType = Palette.TYPE_VARY;
    if (palName.equalsIgnoreCase(paletteNames[2])) paletteType = Palette.TYPE_SPECTRUM;
    if (palName.equalsIgnoreCase(paletteNames[3])) paletteType = Palette.TYPE_SPECTRUM_RANDOM;
    return paletteType;
  }
  
  private static void setPaletteType(JComboBox comboPal, int paletteType) {
    int index = 0;
    if (paletteType == Palette.TYPE_VARY) index = 1;
    if (paletteType == Palette.TYPE_SPECTRUM) index = 2;
    if (paletteType == Palette.TYPE_SPECTRUM_RANDOM) index = 3;
    comboPal.setSelectedIndex(index);
  }
  
  //-----------------------------------------
  static String[] vertexSymbolNames = { "Square", "Square Hollow", "Circle", "Circle Hollow" }; 

  private static int getVertexSymbol(JComboBox combo) {
    String name = (String)combo.getSelectedItem();
    
    for (int i = 0; i < vertexSymbolNames.length; i++) {
      if (name.equalsIgnoreCase(vertexSymbolNames[i])) return i;
    }
    return VertexStyle.SYM_SQUARE_SOLID;
  }
  
  private static void setVertexSymbol(JComboBox combo, int symbolType) {
    combo.setSelectedIndex(symbolType);
  }
  
  protected static Color lineColorFromFill(Color clr) {
    return ColorUtil.saturate(clr,  1);
    //return clr.darker();
  }

  private JButton createSynchButton(String lbl, String tip, ActionListener actionListener) {
    JButton btn = SwingUtil.createButton(lbl, tip, actionListener);
    btn.setMargin(new Insets(0, 0, 0, 0));
    Dimension dim = new Dimension(16, 20);
    btn.setMinimumSize(dim);
    btn.setPreferredSize(dim);
    btn.setMaximumSize(dim);
    return btn;
  }
  
  private JSlider createOpacitySlider(ChangeListener changeListener) {
    JSlider slide = new JSlider(JSlider.HORIZONTAL, 0, 255, 150);
    Dimension dim = new Dimension(80, 20);
    slide.setMinimumSize(dim);
    slide.setPreferredSize(dim);
    slide.setMaximumSize(dim);
    slide.addChangeListener(changeListener);
    slide.setMajorTickSpacing(32);
    slide.setPaintTicks(true);
    return slide;
  }

  private void addRow(String title, JComponent comp) {
    JLabel lbl = new JLabel(title);
    stylePanel.add(lbl, gbc(0, rowIndex, GridBagConstraints.EAST, 0.1));
    stylePanel.add(comp, gbc(1, rowIndex, GridBagConstraints.WEST, 1));
    rowIndex++;
  }

  /*
  private void xaddRow(String title, JComponent c1, JComponent c2) {
    addRow(title, c1, c2, null, null);
  }
  private void xaddRow(String title, JComponent c1, JComponent c2, JComponent c3) {
    addRow(title, c1, c2, c3, null, null);
  }
  private void xaddRow(String title, JComponent c1, JComponent c2, JComponent c3, JComponent c4) {
    addRow(title, c1, c2, c3, c4, null);
  }
*/
  
  private void addRow(String title, JComponent ... comp) {
    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
    for (JComponent c : comp) {
      panel.add(Box.createRigidArea(new Dimension(2,0)));
      panel.add(c);
    }
    addRow(title, panel);
  }
  
  private GridBagConstraints gbc(int x, int y, int align, double weightX) {
    return new GridBagConstraints(x, y, 
        1, 1, 
        weightX, 1, //weights
        align,
        GridBagConstraints.NONE,
        new Insets(2, 2, 2, 2),
        2,
        0);
  }
}