StyleSwatchList.java

/*
 * Copyright (c) 2025 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.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Window;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;

import org.locationtech.jtstest.testbuilder.ui.style.BasicStyle;

public class StyleSwatchList extends JList<StyleSwatchList.StyleSwatch> {

  public static StyleSwatchList create(BasicStyle... styles) {
    DefaultListModel<StyleSwatch> listModel = new DefaultListModel<>();
    for (BasicStyle style : styles) {
      listModel.addElement(new StyleSwatch(style));
    }
    StyleSwatchList ssList = new StyleSwatchList(listModel);
    return ssList;
  }

  public StyleSwatchList(ListModel<StyleSwatch> model) {
    super(model); // Initialize JList with the provided model

    // Set our custom renderer
    this.setCellRenderer(new PanelRenderer());

    // Optional: Set selection mode
    this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    // this.setPreferredSize(new Dimension(16, 16));
    // this.setMaximumSize(new Dimension(16, 10000));
  }

  public BasicStyle getStyle(MouseEvent e) {
    if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
      int index = locationToIndex(e.getPoint()); 
      if (index == -1)
        return null;
      Rectangle cellBnds = getCellBounds(index, index);
      if (! cellBnds.contains(e.getPoint()))
        return null;
      
      // Get the model from this JList instance
      ListModel<StyleSwatch> currentModel = getModel();
      StyleSwatch clickedSS = currentModel.getElementAt(index);
      return clickedSS.style;
    }
    return null;
  }

  /**
   * Custom JPanel to display a rectangle with border and fill colors. This
   * remains a static nested class.
   */
  protected static class StyleSwatch extends JPanel {
    private BasicStyle style;
    // private String name; // Identifier for the panel

    public StyleSwatch(BasicStyle style) {
      this.style = style;
      this.setPreferredSize(new Dimension(20, 16));
      this.setOpaque(true); // Important for rendering in JList
    }

    /*
     * public String getPanelName() { return name; }
     */

    public BasicStyle getStyle() {
      return style;
    }

    @Override
    protected void paintComponent(Graphics g) {
      super.paintComponent(g);

      Graphics2D g2d = (Graphics2D) g.create();
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

      int rectMargin = 3;
      int borderThickness = 2;
      int rectX = rectMargin;
      int rectY = rectMargin;
      int rectWidth = getWidth() - 2 * rectMargin;
      int rectHeight = getHeight() - 2 * rectMargin;

      g2d.setColor(style.getFillColor());
      g2d.fillRect(rectX, rectY, rectWidth, rectHeight);

      g2d.setColor(style.getLineColor());
      g2d.setStroke(new BasicStroke(borderThickness));
      g2d.drawRect(rectX, rectY, rectWidth, rectHeight);

      /*
       * if (name != null && !name.isEmpty()) { FontMetrics fm = g2d.getFontMetrics();
       * g2d.setColor(getContrastColor(fillColor)); int stringWidth =
       * fm.stringWidth(name); int textX = (getWidth() - stringWidth) / 2; int textY =
       * fm.getAscent() + (getHeight() - (fm.getAscent() + fm.getDescent())) / 2;
       * g2d.drawString(name, textX, textY); }
       */
      g2d.dispose();
    }

    /*
     * private Color getContrastColor(Color color) { double luminance = (0.299 *
     * color.getRed() + 0.587 * color.getGreen() + 0.114 * color.getBlue()) / 255.0;
     * return luminance > 0.5 ? Color.BLACK : Color.WHITE; }
     */
  }

  static class PanelRenderer implements ListCellRenderer<StyleSwatch> {
    @Override
    public Component getListCellRendererComponent(JList<? extends StyleSwatch> list, StyleSwatch panel, int index,
        boolean isSelected, boolean cellHasFocus) {
      if (isSelected) {
        panel.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(list.getSelectionBackground().darker(), 1),
            BorderFactory.createEmptyBorder(1, 1, 1, 1)));
      } else {
        panel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
      }
      return panel;
    }
  }

  public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
      // Create a JFrame to host the JList
      JFrame frame = new JFrame("JList Custom Panels Demo");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setLayout(new BorderLayout());

      StyleSwatchList ssList = StyleSwatchList.create(new BasicStyle(Color.BLACK, new Color(255, 100, 100)),
          new BasicStyle(Color.DARK_GRAY, new Color(100, 255, 100)),
          new BasicStyle(Color.BLUE, new Color(100, 100, 255)));

      // Add a MouseListener to this JList
      ssList.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
          BasicStyle s = ssList.getStyle(e);
          String message = "\nStyle: " + s;

          // Get the parent window for the dialog
          Window parentWindow = SwingUtilities.getWindowAncestor(ssList);

          JOptionPane.showMessageDialog(parentWindow, // Parent component for the dialog
              message, "Panel Clicked", JOptionPane.INFORMATION_MESSAGE);
        }
      });
      // Add the JList (within a JScrollPane for scrollability) to the frame
      frame.add(new JScrollPane(ssList), BorderLayout.CENTER);

      // Size the frame and make it visible
      frame.pack();
      frame.setMinimumSize(new Dimension(16, 16));
      frame.setLocationRelativeTo(null); // Center on screen
      frame.setVisible(true);
    });
  }
}