001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2024 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas/ 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see https://www.gnu.org/licenses/. 027 */ 028 029package edu.wisc.ssec.mcidasv.ui; 030 031import java.awt.Color; 032import java.awt.Component; 033import java.awt.Graphics; 034import java.awt.Insets; 035import java.awt.Rectangle; 036import java.awt.event.MouseAdapter; 037import java.awt.event.MouseEvent; 038import java.beans.PropertyChangeEvent; 039import java.beans.PropertyChangeListener; 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.List; 043 044import javax.swing.BorderFactory; 045import javax.swing.JButton; 046import javax.swing.JColorChooser; 047import javax.swing.JComponent; 048import javax.swing.JLabel; 049import javax.swing.JPanel; 050import javax.swing.JSlider; 051import javax.swing.colorchooser.AbstractColorChooserPanel; 052 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056import ucar.unidata.util.GuiUtils; 057import ucar.unidata.util.LayoutUtil; 058import ucar.unidata.util.MenuUtil; 059import ucar.unidata.util.Misc; 060import ucar.unidata.xml.XmlObjectStore; 061 062import edu.wisc.ssec.mcidasv.Constants; 063 064/** 065 * This is largely the same as {@link GuiUtils.ColorSwatch}, but it remembers 066 * the user's recently selected colors. 067 */ 068 069public class ColorSwatchComponent extends JPanel implements PropertyChangeListener { 070 071 private static final long serialVersionUID = 1L; 072 073 /** Logging object. */ 074 private static final Logger logger = 075 LoggerFactory.getLogger(ColorSwatchComponent.class); 076 077 /** Flag for alpha. */ 078 boolean doAlpha = true; 079 080 /** Color of the swatch. */ 081 Color color; 082 083 /** {@literal "Clear"} button. */ 084 JButton clearBtn; 085 086 /** {@literal "Set"} button. */ 087 JButton setBtn; 088 089 /** Label */ 090 String label; 091 092 /** Application object store. */ 093 private XmlObjectStore store; 094 095 /** 096 * Create a new ColorSwatch for the specified color 097 * 098 * @param store Application object store. Cannot be {@code null}. 099 * @param c Color 100 * @param dialogLabel Dialog title. 101 */ 102 public ColorSwatchComponent(XmlObjectStore store, Color c, String dialogLabel) { 103 this(store, c, dialogLabel, true); 104 } 105 106 /** 107 * Create a new color swatch 108 * 109 * @param store Application object store. Cannot be {@code null}. 110 * @param c Color 111 * @param dialogLabel Dialog title. 112 * @param alphaOk Whether or not to use alpha. 113 */ 114 public ColorSwatchComponent(XmlObjectStore store, Color c, String dialogLabel, boolean alphaOk) { 115 this.doAlpha = alphaOk; 116 this.color = c; 117 this.label = dialogLabel; 118 this.store = store; 119 setMinimumSize(Constants.DEFAULT_COLOR_PICKER_SIZE); 120 setPreferredSize(Constants.DEFAULT_COLOR_PICKER_SIZE); 121 setToolTipText("Click to change color"); 122 setBackground(color); 123 setBorder(BorderFactory.createLoweredBevelBorder()); 124 125 clearBtn = new JButton("Clear"); 126 clearBtn.addActionListener(ae -> setBackground(null)); 127 128 setBtn = new JButton("Set"); 129 setBtn.addActionListener(ae -> setColorFromChooser()); 130 131 this.addMouseListener(new MouseAdapter() { 132 @Override public void mouseClicked(MouseEvent e) { 133 showColorChooser(); 134 } 135 }); 136 } 137 138 /** 139 * Prompt the user to select a {@link Color} using a dialog box. 140 * 141 * @param store Application object store. Cannot be {@code null}. 142 * @param c Parent component. {@code null} is allowed. 143 * @param label Title of the dialog box. 144 * @param color Initially selected color. {@code null} will result in 145 * either the most recently used color, or {@code Color.WHITE} if there 146 * are no persisted colors. 147 * 148 * @return Either the user's selected {@code Color}, or {@code null} if the 149 * user closed the dialog or hit cancel. 150 */ 151 public static Color colorChooserDialog(XmlObjectStore store, Component c, String label, Color color) { 152 List<Color> savedColors = 153 (List<Color>)store.get(Constants.PROP_RECENT_COLORS); 154 if (color == null) { 155 if ((savedColors != null) && !savedColors.isEmpty()) { 156 color = savedColors.get(0); 157 } else { 158 color = Color.WHITE; 159 } 160 } 161 ColorSwatchComponent comp = new ColorSwatchComponent(store, color, label); 162 JColorChooser chooser = new JColorChooser(comp.getBackground()); 163 List<AbstractColorChooserPanel> choosers = 164 new ArrayList<>(Arrays.asList(chooser.getChooserPanels())); 165 choosers.remove(0); 166 PersistableSwatchChooserPanel swatch = new PersistableSwatchChooserPanel(); 167 PersistableSwatchChooserPanel.ColorTracker tracker = new PersistableSwatchChooserPanel.ColorTracker(); 168 tracker.addPropertyChangeListener("colors", comp); 169 170 if (savedColors != null) { 171 tracker.setColors(savedColors); 172 } 173 swatch.setColorTracker(tracker); 174 choosers.add(0, swatch); 175 chooser.setChooserPanels(choosers.toArray(new AbstractColorChooserPanel[0])); 176 swatch.updateRecentSwatchPanel(); 177 if (GuiUtils.showOkCancelDialog(null, label, chooser, null)) { 178 comp.userSelectedNewColor(chooser.getColor()); 179 } 180 return comp.getBackground(); 181 } 182 183 /** 184 * Show the color chooser 185 */ 186 private void showColorChooser() { 187 PersistableSwatchChooserPanel.ColorTracker tracker = new PersistableSwatchChooserPanel.ColorTracker(); 188 tracker.addPropertyChangeListener("colors", this); 189 Color oldColor = this.getBackground(); 190 int alpha = oldColor.getAlpha(); 191 JColorChooser chooser = createChooser(tracker); 192 JSlider alphaSlider = new JSlider(0, 255, alpha); 193 194 JComponent contents; 195 if (doAlpha) { 196 contents = 197 LayoutUtil.centerBottom(chooser, 198 LayoutUtil.inset(LayoutUtil.hbox(new JLabel("Transparency:"), 199 alphaSlider), new Insets(5, 5, 5, 200 5))); 201 } else { 202 contents = chooser; 203 } 204 if (!GuiUtils.showOkCancelDialog(null, label, contents, null)) { 205 return; 206 } 207 alpha = alphaSlider.getValue(); 208 Color newColor = chooser.getColor(); 209 if (newColor != null) { 210 newColor = new Color(newColor.getRed(), newColor.getGreen(), newColor.getBlue(), alpha); 211 this.userSelectedNewColor(newColor); 212 } 213 } 214 215 private JColorChooser createChooser(PersistableSwatchChooserPanel.ColorTracker tracker) { 216 JColorChooser chooser = new JColorChooser(this.getBackground()); 217 List<AbstractColorChooserPanel> choosers = 218 new ArrayList<>(Arrays.asList(chooser.getChooserPanels())); 219 choosers.remove(0); 220 PersistableSwatchChooserPanel swatch = new PersistableSwatchChooserPanel(); 221 List<Color> savedColors; 222 if (store != null) { 223 savedColors = (List<Color>)store.get(Constants.PROP_RECENT_COLORS); 224 } else { 225 // don't want to use Collections.emptyList, as the user may still 226 // attempt to add colors...they just won't be saved in this case. :( 227 savedColors = new ArrayList<>(10); 228 logger.warn("'store' field is null! colors cannot be saved between sessions."); 229 } 230 if (savedColors != null) { 231 tracker.setColors(savedColors); 232 } 233 swatch.setColorTracker(tracker); 234 choosers.add(0, swatch); 235 chooser.setChooserPanels(choosers.toArray(new AbstractColorChooserPanel[0])); 236 swatch.updateRecentSwatchPanel(); 237 return chooser; 238 } 239 240 /** 241 * Called from {@link PersistableSwatchChooserPanel} when the user has 242 * clicked on a color. This is used to store the list of recent color 243 * selections. 244 * 245 * @param evt Event containing both the old list of colors and the new. 246 */ 247 @Override public void propertyChange(PropertyChangeEvent evt) { 248 store.put(Constants.PROP_RECENT_COLORS, evt.getNewValue()); 249 } 250 251 /** 252 * Set color from chooser. 253 */ 254 private void setColorFromChooser() { 255 Color newColor = JColorChooser.showDialog(null, label, 256 this.getBackground()); 257 if (newColor != null) { 258 this.userSelectedNewColor(newColor); 259 } 260 } 261 262 /** 263 * Get the set button. 264 * 265 * @return the set button 266 */ 267 public JButton getSetButton() { 268 return setBtn; 269 } 270 271 /** 272 * Get the clear button. 273 * 274 * @return the clear button 275 */ 276 public JButton getClearButton() { 277 return clearBtn; 278 } 279 280 /** 281 * Get the Color of the swatch. 282 * 283 * @return the swatch color 284 */ 285 public Color getSwatchColor() { 286 return color; 287 } 288 289 /** 290 * User chose a new color. Set the background. This can be overwritted 291 * by client code to act on the color change. 292 * 293 * @param c color 294 */ 295 public void userSelectedNewColor(Color c) { 296 setBackground(c); 297 } 298 299 /** 300 * Set the background to the color. 301 * 302 * @param c Color for background 303 */ 304 @Override public void setBackground(Color c) { 305 color = c; 306 super.setBackground(c); 307 } 308 309 /** 310 * Paint this swatch. 311 * 312 * @param g Graphics 313 */ 314 @Override public void paint(Graphics g) { 315 Rectangle b = getBounds(); 316 if (color != null) { 317 g.setColor(Color.black); 318 for (int x = 0; x < b.width; x += 4) { 319 g.fillRect(x, 0, 2, b.height); 320 } 321 } 322 323 super.paint(g); 324 if (color == null) { 325 g.setColor(Color.black); 326 g.drawLine(0, 0, b.width, b.height); 327 g.drawLine(b.width, 0, 0, b.height); 328 } 329 } 330 331 /** 332 * Get the panel 333 * 334 * @return the panel 335 */ 336 public JComponent getPanel() { 337 return LayoutUtil.hbox(this, clearBtn, 4); 338 } 339 340 /** 341 * _more_ 342 * 343 * @return _more_ 344 */ 345 public Color getColor() { 346 return color; 347 } 348 349 350 /** 351 * Get the panel that shows the swatch and the Set button. 352 * 353 * @return the panel 354 */ 355 public JComponent getSetPanel() { 356 List comps = Misc.newList(this); 357 JButton popupBtn = new JButton("Change"); 358 popupBtn.addActionListener(GuiUtils.makeActionListener(this, 359 "popupNameMenu", popupBtn)); 360 comps.add(popupBtn); 361 return LayoutUtil.hbox(comps, 4); 362 } 363 364 365 /** 366 * Popup the named list menu 367 * 368 * @param popupBtn Popup near this button 369 */ 370 public void popupNameMenu(JButton popupBtn) { 371 List items = new ArrayList(); 372 for (int i = 0; i < GuiUtils.COLORNAMES.length; i++) { 373 items.add(MenuUtil.makeMenuItem(GuiUtils.COLORNAMES[i], this, "setColorName", 374 GuiUtils.COLORNAMES[i])); 375 } 376 items.add(MenuUtil.MENU_SEPARATOR); 377 items.add(MenuUtil.makeMenuItem("Custom", this, "setColorName", "custom")); 378 MenuUtil.showPopupMenu(items, popupBtn); 379 } 380 381 /** 382 * Set the color based on name 383 * 384 * @param name color name 385 */ 386 public void setColorName(String name) { 387 if ("custom".equals(name)) { 388 setColorFromChooser(); 389 } else { 390 Color newColor = GuiUtils.decodeColor(name, getBackground()); 391 if (newColor != null) { 392 this.setBackground(newColor); 393 } 394 } 395 } 396}